]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.spreadsheet.ui/src/org/simantics/spreadsheet/ui/TrackedText.java
Fixed all line endings of the repository
[simantics/platform.git] / bundles / org.simantics.spreadsheet.ui / src / org / simantics / spreadsheet / ui / TrackedText.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management
3  * in Industry THTH ry.
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
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.spreadsheet.ui;
13
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;
39
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;
45
46 import org.eclipse.core.runtime.Assert;
47 import org.eclipse.core.runtime.ListenerList;
48 import org.eclipse.jface.dialogs.IInputValidator;
49
50 /**
51  * This is a TrackedTest SWT Text-widget 'decorator'.
52  * 
53  * The widget has 2 main states: editing and inactive.
54  * 
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}.
58  * 
59  * Examples:
60  * 
61  * <pre>
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.
67  *     }
68  * });
69  * 
70  * // #2: create new Text internally, define the colors for text states.
71  * TrackedText trackedText = new TrackedText(text, &lt;instance of ITrackedColorProvider&gt;); 
72  * </pre>
73  * 
74  * @author Tuukka Lehtonen
75  */
76 public class TrackedText {
77     
78     private static final boolean  EVENT_DEBUG = false;
79     
80     private static final int      EDITING                 = 1 << 0;
81     private static final int      MODIFIED_DURING_EDITING = 1 << 1;
82
83     /**
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.
87      */
88     private static final int      MOUSE_DOWN_FIRST_TIME   = 1 << 2;
89     private static final int      MOUSE_INSIDE_CONTROL    = 1 << 3;
90
91     private int                   state;
92
93     private int                   caretPositionBeforeEdit;
94
95     private String                textBeforeEdit;
96
97     private JTextField            text;
98
99     private CompositeListener     listener;
100
101     private ListenerList          modifyListeners;
102
103     private IInputValidator       validator;
104
105     private ITrackedColorProvider colorProvider;
106
107     private class DefaultColorProvider implements ITrackedColorProvider {
108         
109         private Color editingColor = new Color(255, 255, 255);
110         
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);
114
115         @Override
116         public Color getEditingBackground() {
117             return editingColor;
118         }
119
120         @Override
121         public Color getHoverBackground() {
122             return null;
123 //            return highlightColor;
124         }
125
126         @Override
127         public Color getInactiveBackground() {
128             return null;
129 //            return inactiveColor;
130         }
131
132         @Override
133         public Color getInvalidBackground() {
134             return null;
135 //            return invalidInputColor;
136         }
137         
138         void dispose() {
139 //            highlightColor.dispose();
140 //            inactiveColor.dispose();
141 //            invalidInputColor.dispose();
142         }
143     };
144     
145 //    /**
146 //     * A composite of many UI listeners for creating the functionality of this
147 //     * class.
148 //     */
149 //    private class CompositeListener
150 //    implements ModifyListener, DisposeListener, KeyListener, MouseTrackListener,
151 //            MouseListener, FocusListener
152 //    {
153 //        // Keyboard/editing events come in the following order:
154 //        //   1. keyPressed
155 //        //   2. verifyText
156 //        //   3. modifyText
157 //        //   4. keyReleased
158 //        
159 //        public void modifyText(ModifyEvent e) {
160 //            //System.out.println("modifyText: " + e);
161 //            setModified(true);
162 //            
163 ////            String valid = isTextValid();
164 ////            if (valid != null) {
165 ////                setBackground(colorProvider.getInvalidBackground());
166 ////            } else {
167 ////                if (isEditing())
168 ////                    setBackground(colorProvider.getEditingBackground());
169 ////                else
170 ////                    setBackground(colorProvider.getInactiveBackground());
171 ////            }
172 //        }
173 //
174 //        public void widgetDisposed(DisposeEvent e) {
175 //            getWidget().removeModifyListener(this);
176 //        }
177 //        
178 //        private boolean isMultiLine() {
179 //            return false;
180 ////            return (text.getStyle() & SWT.MULTI) != 0;
181 //        }
182 //        
183 //        private boolean hasMultiLineCommitModifier(KeyEvent e) {
184 //            return (e.stateMask & SWT.CTRL) != 0;
185 //        }
186 //
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)
192 //                    return;
193 //
194 //                if (!isMultiLine()) {
195 //                    if (e.keyCode == SWT.F2 || e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) {
196 //                        startEdit(true);
197 //                    } else if (e.character != '\0') {
198 //                        startEdit(false);
199 //                    }
200 //                } else {
201 //                    // In multi-line mode, TAB must not start editing!
202 //                    if (e.keyCode == SWT.F2) {
203 //                        startEdit(true);
204 //                    } else if (e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) {
205 //                        if (hasMultiLineCommitModifier(e)) {
206 //                            e.doit = false;
207 //                        } else {
208 //                            startEdit(false);
209 //                        }
210 //                    } else if (e.keyCode == SWT.TAB) {
211 //                        text.traverse(((e.stateMask & SWT.SHIFT) != 0) ? SWT.TRAVERSE_TAB_PREVIOUS : SWT.TRAVERSE_TAB_NEXT);
212 //                        e.doit = false;
213 //                    } else if (e.character != '\0') {
214 //                        startEdit(false);
215 //                    }
216 //                }
217 //            } else {
218 //                // ESC reverts any changes made during this edit
219 //                if (e.keyCode == SWT.ESC) {
220 //                    revertEdit();
221 //                }
222 //                if (!isMultiLine()) {
223 //                    if (e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) {
224 //                        applyEdit();
225 //                    }
226 //                } else {
227 //                    if (e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) {
228 //                        if (hasMultiLineCommitModifier(e)) {
229 //                            applyEdit();
230 //                            e.doit = false;
231 //                        }
232 //                    }
233 //                }
234 //            }
235 //        }
236 //
237 //        public void keyReleased(KeyEvent e) {
238 //            //System.out.println("keyReleased: " + e);
239 //        }
240 //
241 //        public void mouseEnter(MouseEvent e) {
242 //            //System.out.println("mouseEnter");
243 //            if (!isEditing()) {
244 //                setBackground(colorProvider.getHoverBackground());
245 //            }
246 //            setMouseInsideControl(true);
247 //        }
248 //
249 //        public void mouseExit(MouseEvent e) {
250 //            //System.out.println("mouseExit");
251 //            if (!isEditing()) {
252 //                setBackground(colorProvider.getInactiveBackground());
253 //            }
254 //            setMouseInsideControl(false);
255 //        }
256 //
257 //        public void mouseHover(MouseEvent e) {
258 //            //System.out.println("mouseHover");
259 //            setMouseInsideControl(true);
260 //        }
261 //
262 //        public void mouseDoubleClick(MouseEvent e) {
263 //            //System.out.println("mouseDoubleClick: " + e);
264 //            if (e.button == 1) {
265 //                getWidget().selectAll();
266 //            }
267 //        }
268 //
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) {
276 //                    startEdit(true);
277 //                }
278 //            } else {
279 //                if (e.button == 1 && (state & MOUSE_DOWN_FIRST_TIME) != 0) {
280 //                    getWidget().selectAll();
281 //                    state &= ~MOUSE_DOWN_FIRST_TIME;
282 //                }
283 //            }
284 //        }
285 //
286 //        public void mouseUp(MouseEvent e) {
287 //        }
288 //
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
294 //                    startEdit(true);
295 //                }
296 //            }
297 //        }
298 //
299 //        public void focusLost(FocusEvent e) {
300 //            //System.out.println("focusLost");
301 //            if (isEditing()) {
302 //                applyEdit();
303 //            }
304 //        }
305 //    }
306     
307     public TrackedText(JTextField text) {
308         Assert.isNotNull(text);
309         this.state = 0;
310         this.text = text;
311         this.colorProvider = new DefaultColorProvider();
312
313         initialize();
314     }
315     
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");
319         this.state = 0;
320         this.text = text;
321         this.colorProvider = colorProvider;
322
323         initialize();
324     }
325
326     private class CompositeListener implements ActionListener, CaretListener, AncestorListener, ComponentListener, ContainerListener, FocusListener, HierarchyBoundsListener, HierarchyListener,
327         InputMethodListener, KeyListener, MouseListener, MouseMotionListener, MouseWheelListener, PropertyChangeListener, VetoableChangeListener {
328
329         @Override
330         public void actionPerformed(ActionEvent arg0) {
331             if(EVENT_DEBUG) System.out.println("actionPerformed " + arg0);
332         }
333
334         @Override
335         public void caretUpdate(CaretEvent arg0) {
336             if(EVENT_DEBUG) System.out.println("caretUpdate " + arg0);
337         }
338
339         @Override
340         public void ancestorAdded(AncestorEvent arg0) {
341             if(EVENT_DEBUG) System.out.println("ancestorAdded " + arg0);
342         }
343
344         @Override
345         public void ancestorMoved(AncestorEvent arg0) {
346             if(EVENT_DEBUG) System.out.println("ancestorMoved " + arg0);
347         }
348
349         @Override
350         public void ancestorRemoved(AncestorEvent arg0) {
351             if(EVENT_DEBUG) System.out.println("ancestorRemoved " + arg0);
352         }
353
354         @Override
355         public void componentHidden(ComponentEvent arg0) {
356             if(EVENT_DEBUG) System.out.println("componentHidden " + arg0);
357         }
358
359         @Override
360         public void componentMoved(ComponentEvent arg0) {
361             if(EVENT_DEBUG) System.out.println("componentMoved " + arg0);
362         }
363
364         @Override
365         public void componentResized(ComponentEvent arg0) {
366             if(EVENT_DEBUG) System.out.println("componentResized " + arg0);
367         }
368
369         @Override
370         public void componentShown(ComponentEvent arg0) {
371             if(EVENT_DEBUG) System.out.println("componentShown " + arg0);
372         }
373
374         @Override
375         public void componentAdded(ContainerEvent arg0) {
376             if(EVENT_DEBUG) System.out.println("componentAdded " + arg0);
377         }
378
379         @Override
380         public void componentRemoved(ContainerEvent arg0) {
381             if(EVENT_DEBUG) System.out.println("componentRemoved " + arg0);
382         }
383
384         @Override
385         public void focusGained(FocusEvent arg0) {
386             if(EVENT_DEBUG) System.out.println("focusGained " + arg0);
387             if(!isEditing())
388                 startEdit(false);
389         }
390
391         @Override
392         public void focusLost(FocusEvent arg0) {
393             if(EVENT_DEBUG) System.out.println("focusLost " + arg0);
394         }
395
396         @Override
397         public void ancestorMoved(HierarchyEvent arg0) {
398             if(EVENT_DEBUG) System.out.println("ancestorMoved " + arg0);
399         }
400
401         @Override
402         public void ancestorResized(HierarchyEvent arg0) {
403             if(EVENT_DEBUG) System.out.println("ancestorResized " + arg0);
404         }
405
406         @Override
407         public void hierarchyChanged(HierarchyEvent arg0) {
408             if(EVENT_DEBUG) System.out.println("hierarchyChanged " + arg0);
409         }
410
411         @Override
412         public void caretPositionChanged(InputMethodEvent arg0) {
413             if(EVENT_DEBUG) System.out.println("caretPositionChanged " + arg0);
414         }
415
416         @Override
417         public void inputMethodTextChanged(InputMethodEvent arg0) {
418             if(EVENT_DEBUG) System.out.println("inputMethodTextChanged " + arg0);
419         }
420
421         @Override
422         public void keyPressed(KeyEvent arg0) {
423             if(EVENT_DEBUG) System.out.println("keyPressed " + arg0);
424             if(arg0.getKeyCode() == KeyEvent.VK_ESCAPE) {
425                 revertEdit();
426             }
427             if(arg0.getKeyCode() == KeyEvent.VK_ENTER) {
428                 applyEdit();
429             }
430         }
431
432         @Override
433         public void keyReleased(KeyEvent arg0) {
434             if(EVENT_DEBUG) System.out.println("keyReleased " + arg0);
435             setModified(true);
436             if(!isEditing())
437                 startEdit(false);
438         }
439
440         @Override
441         public void keyTyped(KeyEvent arg0) {
442             if(EVENT_DEBUG) System.out.println("keyTyped " + arg0);
443         }
444
445         @Override
446         public void mouseClicked(MouseEvent arg0) {
447             if(EVENT_DEBUG) System.out.println("mouseClicked " + arg0);
448         }
449
450         @Override
451         public void mouseEntered(MouseEvent arg0) {
452             if(EVENT_DEBUG) System.out.println("mouseEntered " + arg0);
453         }
454
455         @Override
456         public void mouseExited(MouseEvent arg0) {
457             if(EVENT_DEBUG) System.out.println("mouseExited " + arg0);
458         }
459
460         @Override
461         public void mousePressed(MouseEvent arg0) {
462             if(EVENT_DEBUG) System.out.println("mousePressed " + arg0);
463         }
464
465         @Override
466         public void mouseReleased(MouseEvent arg0) {
467             if(EVENT_DEBUG) System.out.println("mouseReleased " + arg0);
468         }
469
470         @Override
471         public void mouseDragged(MouseEvent arg0) {
472             if(EVENT_DEBUG) System.out.println("mouseDragged " + arg0);
473         }
474
475         @Override
476         public void mouseMoved(MouseEvent arg0) {
477             if(EVENT_DEBUG) System.out.println("mouseMoved " + arg0);
478         }
479
480         @Override
481         public void mouseWheelMoved(MouseWheelEvent arg0) {
482             if(EVENT_DEBUG) System.out.println("mouseWheelMoved " + arg0);
483         }
484
485         @Override
486         public void propertyChange(PropertyChangeEvent arg0) {
487             if(EVENT_DEBUG) System.out.println("propertyChange " + arg0);
488         }
489
490         @Override
491         public void vetoableChange(PropertyChangeEvent arg0) throws PropertyVetoException {
492             if(EVENT_DEBUG) System.out.println("vetoableChange " + arg0);
493         }
494         
495     };
496     
497     /**
498      * Common initialization. Assumes that text is already created.
499      */
500     private void initialize() {
501         Assert.isNotNull(text);
502         
503 //        text.setBackground(colorProvider.getInactiveBackground());
504 //        text.setDoubleClickEnabled(false);
505         
506         listener = new CompositeListener();
507
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);
523         
524     }
525     
526     private void startEdit(boolean selectAll) {
527         if (isEditing()) {
528             // Print some debug incase we end are in an invalid state
529             System.out.println("TrackedText: BUG: startEdit called when in editing state");
530         }
531         //System.out.println("start edit: selectall=" + selectAll + ", text=" + text.getText() + ", caretpos=" + caretPositionBeforeEdit);
532
533         // Backup text-field data for reverting purposes
534         caretPositionBeforeEdit = text.getCaretPosition();
535         textBeforeEdit = text.getText();
536
537         // Signal editing state
538         setBackground(colorProvider.getEditingBackground());
539         
540         if (selectAll) {
541             text.selectAll();
542         }
543         state |= EDITING | MOUSE_DOWN_FIRST_TIME;
544     }
545
546     private void applyEdit() {
547         try {
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);
556                     }
557                 }
558             }
559         } finally {
560             endEdit();
561         }
562     }
563     
564     private void endEdit() {
565         if (!isEditing()) {
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"));
568         }
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);
574         setModified(false);
575     }
576
577     private void revertEdit() {
578         if (!isEditing()) {
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");
582         }
583         text.setText(textBeforeEdit);
584         text.setCaretPosition(caretPositionBeforeEdit);
585         setBackground(isMouseInsideControl() ? colorProvider.getHoverBackground() : colorProvider.getInactiveBackground());
586         state &= ~(EDITING | MOUSE_DOWN_FIRST_TIME);
587         setModified(false);
588     }
589     
590     private boolean isEditing() {
591         return (state & EDITING) != 0;
592     }
593     
594     private void setModified(boolean modified) {
595         if (modified) {
596             state |= MODIFIED_DURING_EDITING;
597         } else {
598             state &= ~MODIFIED_DURING_EDITING;
599         }
600     }
601     
602     private boolean isMouseInsideControl() {
603         return (state & MOUSE_INSIDE_CONTROL) != 0;
604     }
605     
606     private void setMouseInsideControl(boolean inside) {
607         if (inside)
608             state |= MOUSE_INSIDE_CONTROL;
609         else
610             state &= ~MOUSE_INSIDE_CONTROL;
611     }
612     
613     private boolean isModified() {
614         return (state & MODIFIED_DURING_EDITING) != 0;
615     }
616     
617     public void setEditable(boolean editable) {
618         if (editable) {
619             text.setEditable(true);
620             setBackground(isMouseInsideControl() ? colorProvider.getHoverBackground() : colorProvider.getInactiveBackground());
621         } else {
622             text.setEditable(false);
623             text.setBackground(null);
624         }
625     }
626     
627     public void setText(String text) {
628         this.text.setText(text);
629     }
630     
631     public void setTextWithoutNotify(String text) {
632 //        this.text.removeModifyListener(listener);
633         setText(text);
634 //        this.text.addModifyListener(listener);
635     }
636
637     public JTextField getWidget() {
638         return text;
639     }
640     
641     public synchronized void addModifyListener(TrackedModifyListener listener) {
642         if (modifyListeners == null) {
643             modifyListeners = new ListenerList(ListenerList.IDENTITY);
644         }
645         modifyListeners.add(listener);
646     }
647     
648     public synchronized void removeModifyListener(TrackedModifyListener listener) {
649         if (modifyListeners == null)
650             return;
651         modifyListeners.remove(listener);
652     }
653     
654     public void setInputValidator(IInputValidator validator) {
655         if (validator != this.validator) {
656             this.validator = validator;
657         }
658     }
659     
660     private String isTextValid() {
661         if (validator != null) {
662             return validator.isValid(getWidget().getText());
663         }
664         return null;
665     }
666     
667     public void setColorProvider(ITrackedColorProvider provider) {
668         Assert.isNotNull(provider);
669         this.colorProvider = provider;
670     }
671     
672     private void setBackground(Color background) {
673         if (!text.isEditable()) {
674             // Do not alter background when the widget is not editable.
675             return;
676         }
677         text.setBackground(background);
678     }
679     
680 }