/******************************************************************************* * Copyright (c) 2007, 2010 Association for Decentralized Information Management * in Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.spreadsheet.ui; import java.awt.Color; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.awt.event.ContainerEvent; import java.awt.event.ContainerListener; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.HierarchyBoundsListener; import java.awt.event.HierarchyEvent; import java.awt.event.HierarchyListener; import java.awt.event.InputMethodEvent; import java.awt.event.InputMethodListener; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyVetoException; import java.beans.VetoableChangeListener; import javax.swing.JTextField; import javax.swing.event.AncestorEvent; import javax.swing.event.AncestorListener; import javax.swing.event.CaretEvent; import javax.swing.event.CaretListener; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.ListenerList; import org.eclipse.jface.dialogs.IInputValidator; /** * This is a TrackedTest SWT Text-widget 'decorator'. * * The widget has 2 main states: editing and inactive. * * It implements the necessary listeners to achieve the text widget behaviour * needed by Simantics. User notification about modifications is provided via * {@link TrackedModifyListener}. * * Examples: * *
 * // #1: create new Text internally, use TrackedModifylistener
 * TrackedText trackedText = new TrackedText(parentComposite, style); 
 * trackedText.addModifyListener(new TrackedModifyListener() {
 *     public void modifyText(TrackedModifyEvent e) {
 *         // text was modified, do something.
 *     }
 * });
 * 
 * // #2: create new Text internally, define the colors for text states.
 * TrackedText trackedText = new TrackedText(text, <instance of ITrackedColorProvider>); 
 * 
* * @author Tuukka Lehtonen */ public class TrackedText { private static final boolean EVENT_DEBUG = false; private static final int EDITING = 1 << 0; private static final int MODIFIED_DURING_EDITING = 1 << 1; /** * Used to tell whether or not a mouseDown has occured after a focusGained * event to be able to select the whole text field when it is pressed for * the first time while the widget holds focus. */ private static final int MOUSE_DOWN_FIRST_TIME = 1 << 2; private static final int MOUSE_INSIDE_CONTROL = 1 << 3; private int state; private int caretPositionBeforeEdit; private String textBeforeEdit; private JTextField text; private CompositeListener listener; private ListenerList modifyListeners; private IInputValidator validator; private ITrackedColorProvider colorProvider; private class DefaultColorProvider implements ITrackedColorProvider { private Color editingColor = new Color(255, 255, 255); // private Color highlightColor = new Color(text.getDisplay(), 254, 255, 197); // private Color inactiveColor = new Color(text.getDisplay(), 245, 246, 190); // private Color invalidInputColor = new Color(text.getDisplay(), 255, 128, 128); @Override public Color getEditingBackground() { return editingColor; } @Override public Color getHoverBackground() { return null; // return highlightColor; } @Override public Color getInactiveBackground() { return null; // return inactiveColor; } @Override public Color getInvalidBackground() { return null; // return invalidInputColor; } void dispose() { // highlightColor.dispose(); // inactiveColor.dispose(); // invalidInputColor.dispose(); } }; // /** // * A composite of many UI listeners for creating the functionality of this // * class. // */ // private class CompositeListener // implements ModifyListener, DisposeListener, KeyListener, MouseTrackListener, // MouseListener, FocusListener // { // // Keyboard/editing events come in the following order: // // 1. keyPressed // // 2. verifyText // // 3. modifyText // // 4. keyReleased // // public void modifyText(ModifyEvent e) { // //System.out.println("modifyText: " + e); // setModified(true); // //// String valid = isTextValid(); //// if (valid != null) { //// setBackground(colorProvider.getInvalidBackground()); //// } else { //// if (isEditing()) //// setBackground(colorProvider.getEditingBackground()); //// else //// setBackground(colorProvider.getInactiveBackground()); //// } // } // // public void widgetDisposed(DisposeEvent e) { // getWidget().removeModifyListener(this); // } // // private boolean isMultiLine() { // return false; //// return (text.getStyle() & SWT.MULTI) != 0; // } // // private boolean hasMultiLineCommitModifier(KeyEvent e) { // return (e.stateMask & SWT.CTRL) != 0; // } // // public void keyPressed(KeyEvent e) { // //System.out.println("keyPressed: " + e); // if (!isEditing()) { // // ESC, ENTER & keypad ENTER must not start editing // if (e.keyCode == SWT.ESC) // return; // // if (!isMultiLine()) { // if (e.keyCode == SWT.F2 || e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) { // startEdit(true); // } else if (e.character != '\0') { // startEdit(false); // } // } else { // // In multi-line mode, TAB must not start editing! // if (e.keyCode == SWT.F2) { // startEdit(true); // } else if (e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) { // if (hasMultiLineCommitModifier(e)) { // e.doit = false; // } else { // startEdit(false); // } // } else if (e.keyCode == SWT.TAB) { // text.traverse(((e.stateMask & SWT.SHIFT) != 0) ? SWT.TRAVERSE_TAB_PREVIOUS : SWT.TRAVERSE_TAB_NEXT); // e.doit = false; // } else if (e.character != '\0') { // startEdit(false); // } // } // } else { // // ESC reverts any changes made during this edit // if (e.keyCode == SWT.ESC) { // revertEdit(); // } // if (!isMultiLine()) { // if (e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) { // applyEdit(); // } // } else { // if (e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) { // if (hasMultiLineCommitModifier(e)) { // applyEdit(); // e.doit = false; // } // } // } // } // } // // public void keyReleased(KeyEvent e) { // //System.out.println("keyReleased: " + e); // } // // public void mouseEnter(MouseEvent e) { // //System.out.println("mouseEnter"); // if (!isEditing()) { // setBackground(colorProvider.getHoverBackground()); // } // setMouseInsideControl(true); // } // // public void mouseExit(MouseEvent e) { // //System.out.println("mouseExit"); // if (!isEditing()) { // setBackground(colorProvider.getInactiveBackground()); // } // setMouseInsideControl(false); // } // // public void mouseHover(MouseEvent e) { // //System.out.println("mouseHover"); // setMouseInsideControl(true); // } // // public void mouseDoubleClick(MouseEvent e) { // //System.out.println("mouseDoubleClick: " + e); // if (e.button == 1) { // getWidget().selectAll(); // } // } // // public void mouseDown(MouseEvent e) { // //System.out.println("mouseDown: " + e); // if (!isEditing()) { // // In reality we should never get here, since focusGained // // always comes before mouseDown, but let's keep this // // fallback just to be safe. // if (e.button == 1) { // startEdit(true); // } // } else { // if (e.button == 1 && (state & MOUSE_DOWN_FIRST_TIME) != 0) { // getWidget().selectAll(); // state &= ~MOUSE_DOWN_FIRST_TIME; // } // } // } // // public void mouseUp(MouseEvent e) { // } // // public void focusGained(FocusEvent e) { // //System.out.println("focusGained"); // if (!isEditing()) { // if (!isMultiLine()) { // // Always start edit on single line texts when focus is gained // startEdit(true); // } // } // } // // public void focusLost(FocusEvent e) { // //System.out.println("focusLost"); // if (isEditing()) { // applyEdit(); // } // } // } public TrackedText(JTextField text) { Assert.isNotNull(text); this.state = 0; this.text = text; this.colorProvider = new DefaultColorProvider(); initialize(); } public TrackedText(JTextField text, ITrackedColorProvider colorProvider) { Assert.isNotNull(text, "text must not be null"); Assert.isNotNull(colorProvider, "colorProvider must not be null"); this.state = 0; this.text = text; this.colorProvider = colorProvider; initialize(); } private class CompositeListener implements ActionListener, CaretListener, AncestorListener, ComponentListener, ContainerListener, FocusListener, HierarchyBoundsListener, HierarchyListener, InputMethodListener, KeyListener, MouseListener, MouseMotionListener, MouseWheelListener, PropertyChangeListener, VetoableChangeListener { @Override public void actionPerformed(ActionEvent arg0) { if(EVENT_DEBUG) System.out.println("actionPerformed " + arg0); } @Override public void caretUpdate(CaretEvent arg0) { if(EVENT_DEBUG) System.out.println("caretUpdate " + arg0); } @Override public void ancestorAdded(AncestorEvent arg0) { if(EVENT_DEBUG) System.out.println("ancestorAdded " + arg0); } @Override public void ancestorMoved(AncestorEvent arg0) { if(EVENT_DEBUG) System.out.println("ancestorMoved " + arg0); } @Override public void ancestorRemoved(AncestorEvent arg0) { if(EVENT_DEBUG) System.out.println("ancestorRemoved " + arg0); } @Override public void componentHidden(ComponentEvent arg0) { if(EVENT_DEBUG) System.out.println("componentHidden " + arg0); } @Override public void componentMoved(ComponentEvent arg0) { if(EVENT_DEBUG) System.out.println("componentMoved " + arg0); } @Override public void componentResized(ComponentEvent arg0) { if(EVENT_DEBUG) System.out.println("componentResized " + arg0); } @Override public void componentShown(ComponentEvent arg0) { if(EVENT_DEBUG) System.out.println("componentShown " + arg0); } @Override public void componentAdded(ContainerEvent arg0) { if(EVENT_DEBUG) System.out.println("componentAdded " + arg0); } @Override public void componentRemoved(ContainerEvent arg0) { if(EVENT_DEBUG) System.out.println("componentRemoved " + arg0); } @Override public void focusGained(FocusEvent arg0) { if(EVENT_DEBUG) System.out.println("focusGained " + arg0); if(!isEditing()) startEdit(false); } @Override public void focusLost(FocusEvent arg0) { if(EVENT_DEBUG) System.out.println("focusLost " + arg0); } @Override public void ancestorMoved(HierarchyEvent arg0) { if(EVENT_DEBUG) System.out.println("ancestorMoved " + arg0); } @Override public void ancestorResized(HierarchyEvent arg0) { if(EVENT_DEBUG) System.out.println("ancestorResized " + arg0); } @Override public void hierarchyChanged(HierarchyEvent arg0) { if(EVENT_DEBUG) System.out.println("hierarchyChanged " + arg0); } @Override public void caretPositionChanged(InputMethodEvent arg0) { if(EVENT_DEBUG) System.out.println("caretPositionChanged " + arg0); } @Override public void inputMethodTextChanged(InputMethodEvent arg0) { if(EVENT_DEBUG) System.out.println("inputMethodTextChanged " + arg0); } @Override public void keyPressed(KeyEvent arg0) { if(EVENT_DEBUG) System.out.println("keyPressed " + arg0); if(arg0.getKeyCode() == KeyEvent.VK_ESCAPE) { revertEdit(); } if(arg0.getKeyCode() == KeyEvent.VK_ENTER) { applyEdit(); } } @Override public void keyReleased(KeyEvent arg0) { if(EVENT_DEBUG) System.out.println("keyReleased " + arg0); setModified(true); if(!isEditing()) startEdit(false); } @Override public void keyTyped(KeyEvent arg0) { if(EVENT_DEBUG) System.out.println("keyTyped " + arg0); } @Override public void mouseClicked(MouseEvent arg0) { if(EVENT_DEBUG) System.out.println("mouseClicked " + arg0); } @Override public void mouseEntered(MouseEvent arg0) { if(EVENT_DEBUG) System.out.println("mouseEntered " + arg0); } @Override public void mouseExited(MouseEvent arg0) { if(EVENT_DEBUG) System.out.println("mouseExited " + arg0); } @Override public void mousePressed(MouseEvent arg0) { if(EVENT_DEBUG) System.out.println("mousePressed " + arg0); } @Override public void mouseReleased(MouseEvent arg0) { if(EVENT_DEBUG) System.out.println("mouseReleased " + arg0); } @Override public void mouseDragged(MouseEvent arg0) { if(EVENT_DEBUG) System.out.println("mouseDragged " + arg0); } @Override public void mouseMoved(MouseEvent arg0) { if(EVENT_DEBUG) System.out.println("mouseMoved " + arg0); } @Override public void mouseWheelMoved(MouseWheelEvent arg0) { if(EVENT_DEBUG) System.out.println("mouseWheelMoved " + arg0); } @Override public void propertyChange(PropertyChangeEvent arg0) { if(EVENT_DEBUG) System.out.println("propertyChange " + arg0); } @Override public void vetoableChange(PropertyChangeEvent arg0) throws PropertyVetoException { if(EVENT_DEBUG) System.out.println("vetoableChange " + arg0); } }; /** * Common initialization. Assumes that text is already created. */ private void initialize() { Assert.isNotNull(text); // text.setBackground(colorProvider.getInactiveBackground()); // text.setDoubleClickEnabled(false); listener = new CompositeListener(); text.addActionListener(listener); text.addCaretListener(listener); text.addAncestorListener(listener); text.addComponentListener(listener); text.addContainerListener(listener); text.addFocusListener(listener); text.addHierarchyBoundsListener(listener); text.addHierarchyListener(listener); text.addInputMethodListener(listener); text.addKeyListener(listener); text.addMouseListener(listener); text.addMouseMotionListener(listener); text.addMouseWheelListener(listener); text.addPropertyChangeListener(listener); text.addVetoableChangeListener(listener); } private void startEdit(boolean selectAll) { if (isEditing()) { // Print some debug incase we end are in an invalid state System.out.println("TrackedText: BUG: startEdit called when in editing state"); } //System.out.println("start edit: selectall=" + selectAll + ", text=" + text.getText() + ", caretpos=" + caretPositionBeforeEdit); // Backup text-field data for reverting purposes caretPositionBeforeEdit = text.getCaretPosition(); textBeforeEdit = text.getText(); // Signal editing state setBackground(colorProvider.getEditingBackground()); if (selectAll) { text.selectAll(); } state |= EDITING | MOUSE_DOWN_FIRST_TIME; } private void applyEdit() { try { if (isTextValid() != null) { text.setText(textBeforeEdit); } else if (isModified() && !text.getText().equals(textBeforeEdit)) { //System.out.println("apply"); if (modifyListeners != null) { TrackedModifyEvent event = new TrackedModifyEvent(text, text.getText()); for (Object o : modifyListeners.getListeners()) { ((TrackedModifyListener) o).modifyText(event); } } } } finally { endEdit(); } } private void endEdit() { if (!isEditing()) { // Print some debug incase we end are in an invalid state //ExceptionUtils.logError(new Exception("BUG: endEdit called when not in editing state")); } setBackground(isMouseInsideControl() ? colorProvider.getHoverBackground() : colorProvider.getInactiveBackground()); //System.out.println("endEdit: " + text.getText() + ", caret: " + text.getCaretLocation() + ", selection: " + text.getSelection()); // Always move the caret to the end of the string text.setCaretPosition(text.getText().length()); state &= ~(EDITING | MOUSE_DOWN_FIRST_TIME); setModified(false); } private void revertEdit() { if (!isEditing()) { // Print some debug incase we end are in an invalid state //ExceptionUtils.logError(new Exception("BUG: revertEdit called when not in editing state")); System.out.println("BUG: revertEdit called when not in editing state"); } text.setText(textBeforeEdit); text.setCaretPosition(caretPositionBeforeEdit); setBackground(isMouseInsideControl() ? colorProvider.getHoverBackground() : colorProvider.getInactiveBackground()); state &= ~(EDITING | MOUSE_DOWN_FIRST_TIME); setModified(false); } private boolean isEditing() { return (state & EDITING) != 0; } private void setModified(boolean modified) { if (modified) { state |= MODIFIED_DURING_EDITING; } else { state &= ~MODIFIED_DURING_EDITING; } } private boolean isMouseInsideControl() { return (state & MOUSE_INSIDE_CONTROL) != 0; } private void setMouseInsideControl(boolean inside) { if (inside) state |= MOUSE_INSIDE_CONTROL; else state &= ~MOUSE_INSIDE_CONTROL; } private boolean isModified() { return (state & MODIFIED_DURING_EDITING) != 0; } public void setEditable(boolean editable) { if (editable) { text.setEditable(true); setBackground(isMouseInsideControl() ? colorProvider.getHoverBackground() : colorProvider.getInactiveBackground()); } else { text.setEditable(false); text.setBackground(null); } } public void setText(String text) { this.text.setText(text); } public void setTextWithoutNotify(String text) { // this.text.removeModifyListener(listener); setText(text); // this.text.addModifyListener(listener); } public JTextField getWidget() { return text; } public synchronized void addModifyListener(TrackedModifyListener listener) { if (modifyListeners == null) { modifyListeners = new ListenerList(ListenerList.IDENTITY); } modifyListeners.add(listener); } public synchronized void removeModifyListener(TrackedModifyListener listener) { if (modifyListeners == null) return; modifyListeners.remove(listener); } public void setInputValidator(IInputValidator validator) { if (validator != this.validator) { this.validator = validator; } } private String isTextValid() { if (validator != null) { return validator.isValid(getWidget().getText()); } return null; } public void setColorProvider(ITrackedColorProvider provider) { Assert.isNotNull(provider); this.colorProvider = provider; } private void setBackground(Color background) { if (!text.isEditable()) { // Do not alter background when the widget is not editable. return; } text.setBackground(background); } }