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