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