1 /*******************************************************************************
\r
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
\r
3 * in Industry THTH ry.
\r
4 * All rights reserved. This program and the accompanying materials
\r
5 * are made available under the terms of the Eclipse Public License v1.0
\r
6 * which accompanies this distribution, and is available at
\r
7 * http://www.eclipse.org/legal/epl-v10.html
\r
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.spreadsheet.ui;
\r
14 import java.awt.Color;
\r
15 import java.awt.event.ActionEvent;
\r
16 import java.awt.event.ActionListener;
\r
17 import java.awt.event.ComponentEvent;
\r
18 import java.awt.event.ComponentListener;
\r
19 import java.awt.event.ContainerEvent;
\r
20 import java.awt.event.ContainerListener;
\r
21 import java.awt.event.FocusEvent;
\r
22 import java.awt.event.FocusListener;
\r
23 import java.awt.event.HierarchyBoundsListener;
\r
24 import java.awt.event.HierarchyEvent;
\r
25 import java.awt.event.HierarchyListener;
\r
26 import java.awt.event.InputMethodEvent;
\r
27 import java.awt.event.InputMethodListener;
\r
28 import java.awt.event.KeyEvent;
\r
29 import java.awt.event.KeyListener;
\r
30 import java.awt.event.MouseEvent;
\r
31 import java.awt.event.MouseListener;
\r
32 import java.awt.event.MouseMotionListener;
\r
33 import java.awt.event.MouseWheelEvent;
\r
34 import java.awt.event.MouseWheelListener;
\r
35 import java.beans.PropertyChangeEvent;
\r
36 import java.beans.PropertyChangeListener;
\r
37 import java.beans.PropertyVetoException;
\r
38 import java.beans.VetoableChangeListener;
\r
40 import javax.swing.JTextField;
\r
41 import javax.swing.event.AncestorEvent;
\r
42 import javax.swing.event.AncestorListener;
\r
43 import javax.swing.event.CaretEvent;
\r
44 import javax.swing.event.CaretListener;
\r
46 import org.eclipse.core.runtime.Assert;
\r
47 import org.eclipse.core.runtime.ListenerList;
\r
48 import org.eclipse.jface.dialogs.IInputValidator;
\r
51 * This is a TrackedTest SWT Text-widget 'decorator'.
\r
53 * The widget has 2 main states: editing and inactive.
\r
55 * It implements the necessary listeners to achieve the text widget behaviour
\r
56 * needed by Simantics. User notification about modifications is provided via
\r
57 * {@link TrackedModifyListener}.
\r
62 * // #1: create new Text internally, use TrackedModifylistener
\r
63 * TrackedText trackedText = new TrackedText(parentComposite, style);
\r
64 * trackedText.addModifyListener(new TrackedModifyListener() {
\r
65 * public void modifyText(TrackedModifyEvent e) {
\r
66 * // text was modified, do something.
\r
70 * // #2: create new Text internally, define the colors for text states.
\r
71 * TrackedText trackedText = new TrackedText(text, <instance of ITrackedColorProvider>);
\r
74 * @author Tuukka Lehtonen
\r
76 public class TrackedText {
\r
78 private static final boolean EVENT_DEBUG = false;
\r
80 private static final int EDITING = 1 << 0;
\r
81 private static final int MODIFIED_DURING_EDITING = 1 << 1;
\r
84 * Used to tell whether or not a mouseDown has occured after a focusGained
\r
85 * event to be able to select the whole text field when it is pressed for
\r
86 * the first time while the widget holds focus.
\r
88 private static final int MOUSE_DOWN_FIRST_TIME = 1 << 2;
\r
89 private static final int MOUSE_INSIDE_CONTROL = 1 << 3;
\r
93 private int caretPositionBeforeEdit;
\r
95 private String textBeforeEdit;
\r
97 private JTextField text;
\r
99 private CompositeListener listener;
\r
101 private ListenerList modifyListeners;
\r
103 private IInputValidator validator;
\r
105 private ITrackedColorProvider colorProvider;
\r
107 private class DefaultColorProvider implements ITrackedColorProvider {
\r
109 private Color editingColor = new Color(255, 255, 255);
\r
111 // private Color highlightColor = new Color(text.getDisplay(), 254, 255, 197);
\r
112 // private Color inactiveColor = new Color(text.getDisplay(), 245, 246, 190);
\r
113 // private Color invalidInputColor = new Color(text.getDisplay(), 255, 128, 128);
\r
116 public Color getEditingBackground() {
\r
117 return editingColor;
\r
121 public Color getHoverBackground() {
\r
123 // return highlightColor;
\r
127 public Color getInactiveBackground() {
\r
129 // return inactiveColor;
\r
133 public Color getInvalidBackground() {
\r
135 // return invalidInputColor;
\r
139 // highlightColor.dispose();
\r
140 // inactiveColor.dispose();
\r
141 // invalidInputColor.dispose();
\r
146 // * A composite of many UI listeners for creating the functionality of this
\r
149 // private class CompositeListener
\r
150 // implements ModifyListener, DisposeListener, KeyListener, MouseTrackListener,
\r
151 // MouseListener, FocusListener
\r
153 // // Keyboard/editing events come in the following order:
\r
154 // // 1. keyPressed
\r
155 // // 2. verifyText
\r
156 // // 3. modifyText
\r
157 // // 4. keyReleased
\r
159 // public void modifyText(ModifyEvent e) {
\r
160 // //System.out.println("modifyText: " + e);
\r
161 // setModified(true);
\r
163 //// String valid = isTextValid();
\r
164 //// if (valid != null) {
\r
165 //// setBackground(colorProvider.getInvalidBackground());
\r
167 //// if (isEditing())
\r
168 //// setBackground(colorProvider.getEditingBackground());
\r
170 //// setBackground(colorProvider.getInactiveBackground());
\r
174 // public void widgetDisposed(DisposeEvent e) {
\r
175 // getWidget().removeModifyListener(this);
\r
178 // private boolean isMultiLine() {
\r
180 //// return (text.getStyle() & SWT.MULTI) != 0;
\r
183 // private boolean hasMultiLineCommitModifier(KeyEvent e) {
\r
184 // return (e.stateMask & SWT.CTRL) != 0;
\r
187 // public void keyPressed(KeyEvent e) {
\r
188 // //System.out.println("keyPressed: " + e);
\r
189 // if (!isEditing()) {
\r
190 // // ESC, ENTER & keypad ENTER must not start editing
\r
191 // if (e.keyCode == SWT.ESC)
\r
194 // if (!isMultiLine()) {
\r
195 // if (e.keyCode == SWT.F2 || e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) {
\r
196 // startEdit(true);
\r
197 // } else if (e.character != '\0') {
\r
198 // startEdit(false);
\r
201 // // In multi-line mode, TAB must not start editing!
\r
202 // if (e.keyCode == SWT.F2) {
\r
203 // startEdit(true);
\r
204 // } else if (e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) {
\r
205 // if (hasMultiLineCommitModifier(e)) {
\r
208 // startEdit(false);
\r
210 // } else if (e.keyCode == SWT.TAB) {
\r
211 // text.traverse(((e.stateMask & SWT.SHIFT) != 0) ? SWT.TRAVERSE_TAB_PREVIOUS : SWT.TRAVERSE_TAB_NEXT);
\r
213 // } else if (e.character != '\0') {
\r
214 // startEdit(false);
\r
218 // // ESC reverts any changes made during this edit
\r
219 // if (e.keyCode == SWT.ESC) {
\r
222 // if (!isMultiLine()) {
\r
223 // if (e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) {
\r
227 // if (e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) {
\r
228 // if (hasMultiLineCommitModifier(e)) {
\r
237 // public void keyReleased(KeyEvent e) {
\r
238 // //System.out.println("keyReleased: " + e);
\r
241 // public void mouseEnter(MouseEvent e) {
\r
242 // //System.out.println("mouseEnter");
\r
243 // if (!isEditing()) {
\r
244 // setBackground(colorProvider.getHoverBackground());
\r
246 // setMouseInsideControl(true);
\r
249 // public void mouseExit(MouseEvent e) {
\r
250 // //System.out.println("mouseExit");
\r
251 // if (!isEditing()) {
\r
252 // setBackground(colorProvider.getInactiveBackground());
\r
254 // setMouseInsideControl(false);
\r
257 // public void mouseHover(MouseEvent e) {
\r
258 // //System.out.println("mouseHover");
\r
259 // setMouseInsideControl(true);
\r
262 // public void mouseDoubleClick(MouseEvent e) {
\r
263 // //System.out.println("mouseDoubleClick: " + e);
\r
264 // if (e.button == 1) {
\r
265 // getWidget().selectAll();
\r
269 // public void mouseDown(MouseEvent e) {
\r
270 // //System.out.println("mouseDown: " + e);
\r
271 // if (!isEditing()) {
\r
272 // // In reality we should never get here, since focusGained
\r
273 // // always comes before mouseDown, but let's keep this
\r
274 // // fallback just to be safe.
\r
275 // if (e.button == 1) {
\r
276 // startEdit(true);
\r
279 // if (e.button == 1 && (state & MOUSE_DOWN_FIRST_TIME) != 0) {
\r
280 // getWidget().selectAll();
\r
281 // state &= ~MOUSE_DOWN_FIRST_TIME;
\r
286 // public void mouseUp(MouseEvent e) {
\r
289 // public void focusGained(FocusEvent e) {
\r
290 // //System.out.println("focusGained");
\r
291 // if (!isEditing()) {
\r
292 // if (!isMultiLine()) {
\r
293 // // Always start edit on single line texts when focus is gained
\r
294 // startEdit(true);
\r
299 // public void focusLost(FocusEvent e) {
\r
300 // //System.out.println("focusLost");
\r
301 // if (isEditing()) {
\r
307 public TrackedText(JTextField text) {
\r
308 Assert.isNotNull(text);
\r
311 this.colorProvider = new DefaultColorProvider();
\r
316 public TrackedText(JTextField text, ITrackedColorProvider colorProvider) {
\r
317 Assert.isNotNull(text, "text must not be null");
\r
318 Assert.isNotNull(colorProvider, "colorProvider must not be null");
\r
321 this.colorProvider = colorProvider;
\r
326 private class CompositeListener implements ActionListener, CaretListener, AncestorListener, ComponentListener, ContainerListener, FocusListener, HierarchyBoundsListener, HierarchyListener,
\r
327 InputMethodListener, KeyListener, MouseListener, MouseMotionListener, MouseWheelListener, PropertyChangeListener, VetoableChangeListener {
\r
330 public void actionPerformed(ActionEvent arg0) {
\r
331 if(EVENT_DEBUG) System.out.println("actionPerformed " + arg0);
\r
335 public void caretUpdate(CaretEvent arg0) {
\r
336 if(EVENT_DEBUG) System.out.println("caretUpdate " + arg0);
\r
340 public void ancestorAdded(AncestorEvent arg0) {
\r
341 if(EVENT_DEBUG) System.out.println("ancestorAdded " + arg0);
\r
345 public void ancestorMoved(AncestorEvent arg0) {
\r
346 if(EVENT_DEBUG) System.out.println("ancestorMoved " + arg0);
\r
350 public void ancestorRemoved(AncestorEvent arg0) {
\r
351 if(EVENT_DEBUG) System.out.println("ancestorRemoved " + arg0);
\r
355 public void componentHidden(ComponentEvent arg0) {
\r
356 if(EVENT_DEBUG) System.out.println("componentHidden " + arg0);
\r
360 public void componentMoved(ComponentEvent arg0) {
\r
361 if(EVENT_DEBUG) System.out.println("componentMoved " + arg0);
\r
365 public void componentResized(ComponentEvent arg0) {
\r
366 if(EVENT_DEBUG) System.out.println("componentResized " + arg0);
\r
370 public void componentShown(ComponentEvent arg0) {
\r
371 if(EVENT_DEBUG) System.out.println("componentShown " + arg0);
\r
375 public void componentAdded(ContainerEvent arg0) {
\r
376 if(EVENT_DEBUG) System.out.println("componentAdded " + arg0);
\r
380 public void componentRemoved(ContainerEvent arg0) {
\r
381 if(EVENT_DEBUG) System.out.println("componentRemoved " + arg0);
\r
385 public void focusGained(FocusEvent arg0) {
\r
386 if(EVENT_DEBUG) System.out.println("focusGained " + arg0);
\r
392 public void focusLost(FocusEvent arg0) {
\r
393 if(EVENT_DEBUG) System.out.println("focusLost " + arg0);
\r
397 public void ancestorMoved(HierarchyEvent arg0) {
\r
398 if(EVENT_DEBUG) System.out.println("ancestorMoved " + arg0);
\r
402 public void ancestorResized(HierarchyEvent arg0) {
\r
403 if(EVENT_DEBUG) System.out.println("ancestorResized " + arg0);
\r
407 public void hierarchyChanged(HierarchyEvent arg0) {
\r
408 if(EVENT_DEBUG) System.out.println("hierarchyChanged " + arg0);
\r
412 public void caretPositionChanged(InputMethodEvent arg0) {
\r
413 if(EVENT_DEBUG) System.out.println("caretPositionChanged " + arg0);
\r
417 public void inputMethodTextChanged(InputMethodEvent arg0) {
\r
418 if(EVENT_DEBUG) System.out.println("inputMethodTextChanged " + arg0);
\r
422 public void keyPressed(KeyEvent arg0) {
\r
423 if(EVENT_DEBUG) System.out.println("keyPressed " + arg0);
\r
424 if(arg0.getKeyCode() == KeyEvent.VK_ESCAPE) {
\r
427 if(arg0.getKeyCode() == KeyEvent.VK_ENTER) {
\r
433 public void keyReleased(KeyEvent arg0) {
\r
434 if(EVENT_DEBUG) System.out.println("keyReleased " + arg0);
\r
441 public void keyTyped(KeyEvent arg0) {
\r
442 if(EVENT_DEBUG) System.out.println("keyTyped " + arg0);
\r
446 public void mouseClicked(MouseEvent arg0) {
\r
447 if(EVENT_DEBUG) System.out.println("mouseClicked " + arg0);
\r
451 public void mouseEntered(MouseEvent arg0) {
\r
452 if(EVENT_DEBUG) System.out.println("mouseEntered " + arg0);
\r
456 public void mouseExited(MouseEvent arg0) {
\r
457 if(EVENT_DEBUG) System.out.println("mouseExited " + arg0);
\r
461 public void mousePressed(MouseEvent arg0) {
\r
462 if(EVENT_DEBUG) System.out.println("mousePressed " + arg0);
\r
466 public void mouseReleased(MouseEvent arg0) {
\r
467 if(EVENT_DEBUG) System.out.println("mouseReleased " + arg0);
\r
471 public void mouseDragged(MouseEvent arg0) {
\r
472 if(EVENT_DEBUG) System.out.println("mouseDragged " + arg0);
\r
476 public void mouseMoved(MouseEvent arg0) {
\r
477 if(EVENT_DEBUG) System.out.println("mouseMoved " + arg0);
\r
481 public void mouseWheelMoved(MouseWheelEvent arg0) {
\r
482 if(EVENT_DEBUG) System.out.println("mouseWheelMoved " + arg0);
\r
486 public void propertyChange(PropertyChangeEvent arg0) {
\r
487 if(EVENT_DEBUG) System.out.println("propertyChange " + arg0);
\r
491 public void vetoableChange(PropertyChangeEvent arg0) throws PropertyVetoException {
\r
492 if(EVENT_DEBUG) System.out.println("vetoableChange " + arg0);
\r
498 * Common initialization. Assumes that text is already created.
\r
500 private void initialize() {
\r
501 Assert.isNotNull(text);
\r
503 // text.setBackground(colorProvider.getInactiveBackground());
\r
504 // text.setDoubleClickEnabled(false);
\r
506 listener = new CompositeListener();
\r
508 text.addActionListener(listener);
\r
509 text.addCaretListener(listener);
\r
510 text.addAncestorListener(listener);
\r
511 text.addComponentListener(listener);
\r
512 text.addContainerListener(listener);
\r
513 text.addFocusListener(listener);
\r
514 text.addHierarchyBoundsListener(listener);
\r
515 text.addHierarchyListener(listener);
\r
516 text.addInputMethodListener(listener);
\r
517 text.addKeyListener(listener);
\r
518 text.addMouseListener(listener);
\r
519 text.addMouseMotionListener(listener);
\r
520 text.addMouseWheelListener(listener);
\r
521 text.addPropertyChangeListener(listener);
\r
522 text.addVetoableChangeListener(listener);
\r
526 private void startEdit(boolean selectAll) {
\r
528 // Print some debug incase we end are in an invalid state
\r
529 System.out.println("TrackedText: BUG: startEdit called when in editing state");
\r
531 //System.out.println("start edit: selectall=" + selectAll + ", text=" + text.getText() + ", caretpos=" + caretPositionBeforeEdit);
\r
533 // Backup text-field data for reverting purposes
\r
534 caretPositionBeforeEdit = text.getCaretPosition();
\r
535 textBeforeEdit = text.getText();
\r
537 // Signal editing state
\r
538 setBackground(colorProvider.getEditingBackground());
\r
543 state |= EDITING | MOUSE_DOWN_FIRST_TIME;
\r
546 private void applyEdit() {
\r
548 if (isTextValid() != null) {
\r
549 text.setText(textBeforeEdit);
\r
550 } else if (isModified() && !text.getText().equals(textBeforeEdit)) {
\r
551 //System.out.println("apply");
\r
552 if (modifyListeners != null) {
\r
553 TrackedModifyEvent event = new TrackedModifyEvent(text, text.getText());
\r
554 for (Object o : modifyListeners.getListeners()) {
\r
555 ((TrackedModifyListener) o).modifyText(event);
\r
564 private void endEdit() {
\r
565 if (!isEditing()) {
\r
566 // Print some debug incase we end are in an invalid state
\r
567 //ExceptionUtils.logError(new Exception("BUG: endEdit called when not in editing state"));
\r
569 setBackground(isMouseInsideControl() ? colorProvider.getHoverBackground() : colorProvider.getInactiveBackground());
\r
570 //System.out.println("endEdit: " + text.getText() + ", caret: " + text.getCaretLocation() + ", selection: " + text.getSelection());
\r
571 // Always move the caret to the end of the string
\r
572 text.setCaretPosition(text.getText().length());
\r
573 state &= ~(EDITING | MOUSE_DOWN_FIRST_TIME);
\r
574 setModified(false);
\r
577 private void revertEdit() {
\r
578 if (!isEditing()) {
\r
579 // Print some debug incase we end are in an invalid state
\r
580 //ExceptionUtils.logError(new Exception("BUG: revertEdit called when not in editing state"));
\r
581 System.out.println("BUG: revertEdit called when not in editing state");
\r
583 text.setText(textBeforeEdit);
\r
584 text.setCaretPosition(caretPositionBeforeEdit);
\r
585 setBackground(isMouseInsideControl() ? colorProvider.getHoverBackground() : colorProvider.getInactiveBackground());
\r
586 state &= ~(EDITING | MOUSE_DOWN_FIRST_TIME);
\r
587 setModified(false);
\r
590 private boolean isEditing() {
\r
591 return (state & EDITING) != 0;
\r
594 private void setModified(boolean modified) {
\r
596 state |= MODIFIED_DURING_EDITING;
\r
598 state &= ~MODIFIED_DURING_EDITING;
\r
602 private boolean isMouseInsideControl() {
\r
603 return (state & MOUSE_INSIDE_CONTROL) != 0;
\r
606 private void setMouseInsideControl(boolean inside) {
\r
608 state |= MOUSE_INSIDE_CONTROL;
\r
610 state &= ~MOUSE_INSIDE_CONTROL;
\r
613 private boolean isModified() {
\r
614 return (state & MODIFIED_DURING_EDITING) != 0;
\r
617 public void setEditable(boolean editable) {
\r
619 text.setEditable(true);
\r
620 setBackground(isMouseInsideControl() ? colorProvider.getHoverBackground() : colorProvider.getInactiveBackground());
\r
622 text.setEditable(false);
\r
623 text.setBackground(null);
\r
627 public void setText(String text) {
\r
628 this.text.setText(text);
\r
631 public void setTextWithoutNotify(String text) {
\r
632 // this.text.removeModifyListener(listener);
\r
634 // this.text.addModifyListener(listener);
\r
637 public JTextField getWidget() {
\r
641 public synchronized void addModifyListener(TrackedModifyListener listener) {
\r
642 if (modifyListeners == null) {
\r
643 modifyListeners = new ListenerList(ListenerList.IDENTITY);
\r
645 modifyListeners.add(listener);
\r
648 public synchronized void removeModifyListener(TrackedModifyListener listener) {
\r
649 if (modifyListeners == null)
\r
651 modifyListeners.remove(listener);
\r
654 public void setInputValidator(IInputValidator validator) {
\r
655 if (validator != this.validator) {
\r
656 this.validator = validator;
\r
660 private String isTextValid() {
\r
661 if (validator != null) {
\r
662 return validator.isValid(getWidget().getText());
\r
667 public void setColorProvider(ITrackedColorProvider provider) {
\r
668 Assert.isNotNull(provider);
\r
669 this.colorProvider = provider;
\r
672 private void setBackground(Color background) {
\r
673 if (!text.isEditable()) {
\r
674 // Do not alter background when the widget is not editable.
\r
677 text.setBackground(background);
\r