]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.utils.ui/src/org/simantics/utils/ui/widgets/TrackedCCombo.java
Fixed a bug related to the extent of variable frames (see test case)
[simantics/platform.git] / bundles / org.simantics.utils.ui / src / org / simantics / utils / ui / widgets / TrackedCCombo.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.utils.ui.widgets;
13
14 import org.eclipse.core.runtime.Assert;
15 import org.eclipse.core.runtime.ListenerList;
16 import org.eclipse.jface.dialogs.IInputValidator;
17 import org.eclipse.swt.SWT;
18 import org.eclipse.swt.custom.CCombo;
19 import org.eclipse.swt.events.DisposeEvent;
20 import org.eclipse.swt.events.DisposeListener;
21 import org.eclipse.swt.events.FocusEvent;
22 import org.eclipse.swt.events.FocusListener;
23 import org.eclipse.swt.events.KeyEvent;
24 import org.eclipse.swt.events.KeyListener;
25 import org.eclipse.swt.events.ModifyEvent;
26 import org.eclipse.swt.events.ModifyListener;
27 import org.eclipse.swt.events.MouseEvent;
28 import org.eclipse.swt.events.MouseListener;
29 import org.eclipse.swt.events.MouseTrackListener;
30 import org.eclipse.swt.graphics.Color;
31 import org.eclipse.swt.widgets.Composite;
32
33 /**
34  * This is a TrackedTest SWT Text-widget 'decorator'.
35  * 
36  * It implements the necessary listeners to achieve the text widget behaviour
37  * needed by Simantics. User notification about modifications is provided via an
38  * Action instance given by the user.
39  * 
40  * @see org.simantics.utils.ui.widgets.TrackedTextTest
41  * 
42  * @author Tuukka Lehtonen
43  */
44 public class TrackedCCombo {
45     private static final int EDITING = 1 << 0;
46     private static final int MODIFIED_DURING_EDITING = 1 << 1;
47
48     /**
49      * Used to tell whether or not a mouseDown has occured after a focusGained
50      * event to be able to select the whole text field when it is pressed for
51      * the first time while the widget holds focus.
52      */
53     private static final int MOUSE_DOWN_FIRST_TIME = 1 << 2;
54     
55     private int               state;
56
57     private String            textBeforeEdit;
58
59     private CCombo            combo;
60
61     private CompositeListener listener;
62
63     private ListenerList      modifyListeners;
64
65     private IInputValidator   validator;
66
67     //private static Color highlightColor = new Color(null, 250, 250, 250);
68     //private static Color inactiveColor = new Color(null, 240, 240, 240);
69     private static Color highlightColor = new Color(null, 254, 255, 197);
70     private static Color inactiveColor = new Color(null, 245, 246, 190);
71     private static Color invalidInputColor = new Color(null, 255, 128, 128);
72
73     /**
74      * A composite of many UI listeners for creating the functionality of this
75      * class.
76      */
77     private class CompositeListener
78     implements ModifyListener, DisposeListener, KeyListener, MouseTrackListener,
79             MouseListener, FocusListener
80     {
81         // Keyboard/editing events come in the following order:
82         //   1. keyPressed
83         //   2. verifyText
84         //   3. modifyText
85         //   4. keyReleased
86         
87         public void modifyText(ModifyEvent e) {
88             //System.out.println("modifyText: " + e);
89             if (isEditing())
90                 setModified(true);
91
92             String valid = isTextValid();
93             if (valid != null) {
94                 getWidget().setBackground(invalidInputColor);
95             } else {
96                 if (isEditing())
97                     getWidget().setBackground(null);
98                 else
99                     getWidget().setBackground(inactiveColor);
100             }
101         }
102
103         public void widgetDisposed(DisposeEvent e) {
104             combo.removeModifyListener(this);
105         }
106
107         public void keyPressed(KeyEvent e) {
108             //System.out.println("keyPressed: " + e);
109             if (!isEditing()) {
110                 // ESC, ENTER & keypad ENTER must not start editing 
111                 if (e.keyCode == SWT.ESC)
112                     return;
113
114                 if (e.keyCode == SWT.F2 || e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) {
115                     startEdit(true);
116                 } else if (e.character != '\0') {
117                     startEdit(false);
118                 }
119             } else {
120                 // ESC reverts any changes made during this edit
121                 if (e.keyCode == SWT.ESC) {
122                     revertEdit();
123                 }
124                 if (e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) {
125                     applyEdit();
126                 }                    
127             }
128         }
129
130         public void keyReleased(KeyEvent e) {
131             //System.out.println("keyReleased: " + e);
132         }
133
134         public void mouseEnter(MouseEvent e) {
135             //System.out.println("mouseEnter");
136             if (!isEditing()) {
137                 getWidget().setBackground(highlightColor);
138             }
139         }
140
141         public void mouseExit(MouseEvent e) {
142             //System.out.println("mouseExit");
143             if (!isEditing()) {
144                 getWidget().setBackground(inactiveColor);
145             }
146         }
147
148         public void mouseHover(MouseEvent e) {
149             //System.out.println("mouseHover");
150         }
151
152         public void mouseDoubleClick(MouseEvent e) {
153             //System.out.println("mouseDoubleClick: " + e);
154 //            if (e.button == 1) {
155 //                getWidget().selectAll();
156 //            }
157         }
158
159         public void mouseDown(MouseEvent e) {
160             //System.out.println("mouseDown: " + e);
161             if (!isEditing()) {
162                 // In reality we should never get here, since focusGained
163                 // always comes before mouseDown, but let's keep this
164                 // fallback just to be safe.
165                 if (e.button == 1) {
166                     startEdit(true);
167                 }
168             } else {
169                 if (e.button == 1 && (state & MOUSE_DOWN_FIRST_TIME) != 0) {
170 //                    getWidget().selectAll();
171                     state &= ~MOUSE_DOWN_FIRST_TIME;
172                 }
173             }
174         }
175
176         public void mouseUp(MouseEvent e) {
177         }
178
179         public void focusGained(FocusEvent e) {
180             //System.out.println("focusGained");
181             if (!isEditing()) {
182                 startEdit(true);
183             }
184         }
185
186         public void focusLost(FocusEvent e) {
187             //System.out.println("focusLost");
188             if (isEditing()) {
189                 applyEdit();
190             }
191         }
192     }
193     
194     public TrackedCCombo(CCombo combo) {
195         Assert.isNotNull(combo);
196         this.state = 0;
197         this.combo = combo;
198
199         initialize();
200     }
201
202     public TrackedCCombo(Composite parent, int style) {
203         this.state = 0;
204         this.combo = new CCombo(parent, style);
205
206         initialize();
207     }
208
209     /**
210      * Common initialization. Assumes that text is already created.
211      */
212     private void initialize() {
213         Assert.isNotNull(combo);
214         
215         combo.setBackground(inactiveColor);
216         
217         listener = new CompositeListener();
218         
219         combo.addModifyListener(listener);
220         combo.addDisposeListener(listener);
221         combo.addKeyListener(listener);
222         combo.addMouseTrackListener(listener);
223         combo.addMouseListener(listener);
224         combo.addFocusListener(listener);
225     }    
226     
227     private void startEdit(boolean selectAll) {
228         if (isEditing()) {
229             // Print some debug incase we end are in an invalid state
230             try {
231                 throw new Exception("TrackedText: BUG: startEdit called when in editing state");
232             } catch (Exception e) {
233                 System.out.println(e);
234             }
235         }
236         //System.out.println("start edit: selectall=" + selectAll + ", text=" + text.getText() + ", caretpos=" + caretPositionBeforeEdit);
237
238         // Backup text-field data for reverting purposes
239         textBeforeEdit = combo.getText();
240
241         // Signal editing state
242         combo.setBackground(null);
243         
244 //        if (selectAll) {
245 //            text.selectAll();
246 //        }
247         state |= EDITING | MOUSE_DOWN_FIRST_TIME;
248     }
249
250     private void applyEdit() {
251         try {
252             if (isTextValid() != null) {
253                 // Just discard the edit.
254                 combo.setText(textBeforeEdit);
255             } else if (isModified() && !combo.getText().equals(textBeforeEdit)) {
256                 //System.out.println("apply");
257                 if (modifyListeners != null) {
258                     TrackedModifyEvent event = new TrackedModifyEvent(combo, combo.getText());
259                     for (Object o : modifyListeners.getListeners()) {
260                         ((TrackedModifyListener) o).modifyText(event);
261                     }
262                 }
263             }
264         } finally {
265             endEdit();
266         }
267     }
268     
269     private void endEdit() {
270         if (!isEditing()) {
271             // Print some debug incase we end are in an invalid state
272             //ExceptionUtils.logError(new Exception("BUG: endEdit called when not in editing state"));
273             System.out.println("BUG: endEdit called when not in editing state");
274         }
275         combo.setBackground(inactiveColor);
276         //System.out.println("endEdit: " + text.getText() + ", caret: " + text.getCaretLocation() + ", selection: " + text.getSelection());
277         // Always move the caret to the end of the string
278 //        text.setSelection(text.getCharCount());
279         
280         state &= ~(EDITING | MOUSE_DOWN_FIRST_TIME);
281         setModified(false);
282     }
283
284     private void revertEdit() {
285         if (!isEditing()) {
286             // Print some debug incase we end are in an invalid state
287             //ExceptionUtils.logError(new Exception("BUG: revertEdit called when not in editing state"));
288             System.out.println("BUG: revertEdit called when not in editing state");
289         }
290         combo.setText(textBeforeEdit);
291 //        text.setSelection(caretPositionBeforeEdit);
292         combo.setBackground(inactiveColor);
293         state &= ~(EDITING | MOUSE_DOWN_FIRST_TIME);
294         setModified(false);
295     }
296     
297     private boolean isEditing() {
298         return (state & EDITING) != 0;
299     }
300     
301     private void setModified(boolean modified) {
302         if (modified) {
303             state |= MODIFIED_DURING_EDITING;
304         } else {
305             state &= ~MODIFIED_DURING_EDITING;
306         }
307     }
308     
309     private boolean isModified() {
310         return (state & MODIFIED_DURING_EDITING) != 0;
311     }
312     
313     public void setText(String text) {
314         this.combo.setText(text);
315     }
316     
317     public void setTextWithoutNotify(String text) {
318         this.combo.removeModifyListener(listener);
319         setText(text);
320         this.combo.addModifyListener(listener);
321     }
322
323     public CCombo getWidget() {
324         return combo;
325     }
326     
327     public synchronized void addModifyListener(TrackedModifyListener listener) {
328         if (modifyListeners == null) {
329             modifyListeners = new ListenerList(ListenerList.IDENTITY);
330         }
331         modifyListeners.add(listener);
332     }
333     
334     public synchronized void removeModifyListener(TrackedModifyListener listener) {
335         if (modifyListeners == null)
336             return;
337         modifyListeners.remove(listener);
338     }
339     
340     public void setInputValidator(IInputValidator validator) {
341         if (validator != this.validator) {
342             this.validator = validator;
343         }
344     }
345     
346     private String isTextValid() {
347         if (validator != null) {
348             return validator.isValid(getWidget().getText());
349         }
350         return null;
351     }
352 }