]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/widgets/TrackedCombo.java
(refs #7358) Initial 4.7 update commit
[simantics/platform.git] / bundles / org.simantics.browsing.ui.swt / src / org / simantics / browsing / ui / swt / widgets / TrackedCombo.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2012 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.browsing.ui.swt.widgets;
13
14 import java.util.Map;
15
16 import org.eclipse.core.runtime.Assert;
17 import org.eclipse.core.runtime.ListenerList;
18 import org.eclipse.jface.dialogs.IInputValidator;
19 import org.eclipse.jface.resource.ColorDescriptor;
20 import org.eclipse.jface.resource.JFaceResources;
21 import org.eclipse.jface.resource.LocalResourceManager;
22 import org.eclipse.jface.resource.ResourceManager;
23 import org.eclipse.swt.SWT;
24 import org.eclipse.swt.events.DisposeEvent;
25 import org.eclipse.swt.events.DisposeListener;
26 import org.eclipse.swt.events.FocusEvent;
27 import org.eclipse.swt.events.FocusListener;
28 import org.eclipse.swt.events.KeyEvent;
29 import org.eclipse.swt.events.KeyListener;
30 import org.eclipse.swt.events.ModifyEvent;
31 import org.eclipse.swt.events.ModifyListener;
32 import org.eclipse.swt.events.MouseEvent;
33 import org.eclipse.swt.events.MouseListener;
34 import org.eclipse.swt.events.MouseTrackListener;
35 import org.eclipse.swt.events.SelectionEvent;
36 import org.eclipse.swt.events.SelectionListener;
37 import org.eclipse.swt.graphics.Color;
38 import org.eclipse.swt.graphics.Font;
39 import org.eclipse.swt.graphics.Point;
40 import org.eclipse.swt.graphics.RGB;
41 import org.eclipse.swt.widgets.Composite;
42 import org.eclipse.swt.widgets.Display;
43 import org.simantics.browsing.ui.swt.widgets.impl.ITrackedColorProvider;
44 import org.simantics.browsing.ui.swt.widgets.impl.ReadFactory;
45 import org.simantics.browsing.ui.swt.widgets.impl.TextModifyListener;
46 import org.simantics.browsing.ui.swt.widgets.impl.TrackedModifyEvent;
47 import org.simantics.browsing.ui.swt.widgets.impl.Widget;
48 import org.simantics.browsing.ui.swt.widgets.impl.WidgetSupport;
49 import org.simantics.db.management.ISessionContext;
50 import org.simantics.db.procedure.Listener;
51 import org.simantics.utils.threads.SWTThread;
52
53 public class TrackedCombo implements Widget {
54
55     private static final int      EDITING                 = 1 << 0;
56     private static final int      MODIFIED_DURING_EDITING = 1 << 1;
57
58     /**
59      * Used to tell whether or not a mouseDown has occurred after a focusGained
60      * event to be able to select the whole text field when it is pressed for
61      * the first time while the widget holds focus.
62      */
63     private static final int      MOUSE_DOWN_FIRST_TIME   = 1 << 2;
64     private static final int      MOUSE_INSIDE_CONTROL    = 1 << 3;
65
66     private int                   state;
67
68     private int                   caretPositionBeforeEdit;
69
70     private String                textBeforeEdit;
71
72     final private org.eclipse.swt.widgets.Combo combo;
73
74     private CompositeListener     listener;
75
76     private ListenerList          modifyListeners;
77
78     private IInputValidator       validator;
79
80     private ITrackedColorProvider colorProvider;
81
82     private final ResourceManager       resourceManager;
83
84     private ReadFactory<?, Map<String, Object>> itemFactory;
85     protected ReadFactory<?, String> selectionFactory;
86
87
88
89
90     public void setItemFactory(ReadFactory<?, Map<String, Object>> itemFactory) {
91         this.itemFactory = itemFactory;
92     }
93
94     public void setSelectionFactory(ReadFactory<?, String> selectionFactory) {
95         this.selectionFactory = selectionFactory;
96     }
97
98     public void setFont(Font font) {
99         combo.setFont(font);
100     }
101
102     @Override
103     public void setInput(ISessionContext context, Object input) {
104
105         if (modifyListeners != null) {
106             for (Object listener : modifyListeners.getListeners()) {
107                 if(listener instanceof Widget) {
108                     ((Widget) listener).setInput(context, input);
109                 }
110             }
111         }
112
113         if(itemFactory != null) {
114             itemFactory.listen(context, input, new Listener<Map<String, Object>>() {
115
116                 @Override
117                 public void exception(Throwable t) {
118                     t.printStackTrace();
119                 }
120
121                 @Override
122                 public void execute(final Map<String, Object> items) {
123                     if(isDisposed()) return;
124                     Runnable r = new Runnable() {
125
126                         @Override
127                         public void run() {
128                             if(isDisposed()) return;
129 //                          System.out.println("Combo received new items: " + items.size());
130 //                            if(modifyListeners != null)
131 //                                for(Object listener : modifyListeners.getListeners()) combo.removeModifyListener((ModifyListener)listener);
132                             if(listener != null)
133                                 combo.removeModifyListener(listener);
134                             combo.setData(items);
135                             combo.clearSelection();
136                             try {
137                                 combo.removeAll();
138                             } catch (Throwable t) {
139                                 t.printStackTrace();
140                             }
141                             int index = 0;
142                             for(String key : items.keySet()) {
143 //                              System.out.println("-" + key);
144                                 combo.add(key);
145                                 combo.setData(key, index++);
146                             }
147                             String selectionKey = (String)combo.getData("_SelectionKey");
148                             if(selectionKey != null) {
149                                 Integer selectionIndex = (Integer)combo.getData(selectionKey);
150                                 if(selectionIndex != null) combo.select(selectionIndex);
151                             }
152 //                            if(modifyListeners != null)
153 //                                for(Object listener : modifyListeners.getListeners()) combo.addModifyListener((ModifyListener)listener);
154                             if(listener != null)
155                                 combo.addModifyListener(listener);
156                             //label.setSize(200, 20);
157 //                          label.getParent().layout();
158 //                          label.getParent().getParent().layout();
159                         }
160
161                     };
162                     if(SWTThread.getThreadAccess().currentThreadAccess())
163                         r.run();
164                     else
165                         combo.getDisplay().asyncExec(r);        
166                 }
167
168                 @Override
169                 public boolean isDisposed() {
170                     return combo.isDisposed();
171                 }
172
173             });
174         }
175
176         if(selectionFactory != null) {
177             selectionFactory.listen(context, input, new Listener<String>() {
178
179                 @Override
180                 public void exception(Throwable t) {
181                     t.printStackTrace();
182                 }
183
184                 @Override
185                 public void execute(final String selectionKey) {
186                     if(isDisposed()) return;
187                     combo.getDisplay().asyncExec(new Runnable() {
188
189                         @Override
190                         public void run() {
191                             if(isDisposed()) return;
192 //                          System.out.println("Combo received new selection key: " + selectionKey);
193
194                             if(selectionKey == null) return;
195                             combo.setData("_SelectionKey", selectionKey);
196                             Integer selectionIndex = (Integer)combo.getData(selectionKey);
197                             if(selectionIndex != null) combo.select(selectionIndex);
198
199                         }
200
201                     });
202                 }
203
204                 @Override
205                 public boolean isDisposed() {
206                     return combo.isDisposed();
207                 }
208
209             });
210         }
211
212     }
213
214     public void manualSelect(int index) {
215         
216         String key = combo.getItem(index);
217         combo.setData("_SelectionKey", key);
218         combo.select(index);
219         
220     }
221     
222     private class DefaultColorProvider implements ITrackedColorProvider {
223         private final ColorDescriptor highlightColor = ColorDescriptor.createFrom(new RGB(254, 255, 197));
224         private final ColorDescriptor inactiveColor = ColorDescriptor.createFrom(new RGB(245, 246, 190));
225         private final ColorDescriptor invalidInputColor = ColorDescriptor.createFrom(new RGB(255, 128, 128));
226
227         @Override
228         public Color getEditingBackground() {
229             return null;
230         }
231
232         @Override
233         public Color getHoverBackground() {
234             return resourceManager.createColor(highlightColor);
235         }
236
237         @Override
238         public Color getInactiveBackground() {
239             return resourceManager.createColor(inactiveColor);
240         }
241
242         @Override
243         public Color getInvalidBackground() {
244             return resourceManager.createColor(invalidInputColor);
245         }
246     };
247
248     /**
249      * A composite of many UI listeners for creating the functionality of this
250      * class.
251      */
252     private class CompositeListener
253     implements ModifyListener, DisposeListener, KeyListener, MouseTrackListener,
254     MouseListener, FocusListener, SelectionListener
255     {
256         // Keyboard/editing events come in the following order:
257         //   1. keyPressed
258         //   2. verifyText
259         //   3. modifyText
260         //   4. keyReleased
261
262         @Override
263         public void modifyText(ModifyEvent e) {
264             //System.out.println("modifyText: " + e);
265             setModified(true);
266
267             String valid = isTextValid();
268             if (valid != null) {
269                 setBackground(colorProvider.getInvalidBackground());
270             } else {
271                 if (isEditing())
272                     setBackground(colorProvider.getEditingBackground());
273                 else
274                     setBackground(colorProvider.getInactiveBackground());
275             }
276         }
277
278         @Override
279         public void widgetDisposed(DisposeEvent e) {
280             getWidget().removeModifyListener(this);
281         }
282
283         @Override
284         public void keyPressed(KeyEvent e) {
285             //System.out.println("keyPressed: " + e);
286             if (!isEditing()) {
287                 // ESC, ENTER & keypad ENTER must not start editing
288                 if (e.keyCode == SWT.ESC)
289                     return;
290
291                 if (e.keyCode == SWT.F2) {
292                     startEdit(true);
293                 } else if (e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) {
294                     startEdit(false);
295                 } else if (e.keyCode == SWT.TAB) {
296                     combo.traverse(((e.stateMask & SWT.SHIFT) != 0) ? SWT.TRAVERSE_TAB_PREVIOUS : SWT.TRAVERSE_TAB_NEXT);
297                     e.doit = false;
298                 } else if (e.character != '\0') {
299                     startEdit(false);
300                 }
301
302             } else {
303                 // ESC reverts any changes made during this edit
304                 if (e.keyCode == SWT.ESC) {
305                     revertEdit();
306                 }
307                 if (e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) {
308                     applyEdit();
309                     e.doit = false;
310                 }
311             }
312         }
313
314         @Override
315         public void keyReleased(KeyEvent e) {
316             //System.out.println("keyReleased: " + e);
317         }
318
319         @Override
320         public void mouseEnter(MouseEvent e) {
321             //System.out.println("mouseEnter");
322             if (!isEditing()) {
323                 setBackground(colorProvider.getHoverBackground());
324             }
325             setMouseInsideControl(true);
326         }
327
328         @Override
329         public void mouseExit(MouseEvent e) {
330             //System.out.println("mouseExit");
331             if (!isEditing()) {
332                 setBackground(colorProvider.getInactiveBackground());
333             }
334             setMouseInsideControl(false);
335         }
336
337         @Override
338         public void mouseHover(MouseEvent e) {
339             //System.out.println("mouseHover");
340             setMouseInsideControl(true);
341         }
342
343         @Override
344         public void mouseDoubleClick(MouseEvent e) {
345             //System.out.println("mouseDoubleClick: " + e);
346             if (e.button == 1) {
347                 getWidget().setSelection(new Point(0, combo.getText().length()));
348             }
349         }
350
351         @Override
352         public void mouseDown(MouseEvent e) {
353             //System.out.println("mouseDown: " + e);
354             if (!isEditing()) {
355                 // In reality we should never get here, since focusGained
356                 // always comes before mouseDown, but let's keep this
357                 // fallback just to be safe.
358                 if (e.button == 1) {
359                     startEdit(true);
360                 }
361             } else {
362                 if (e.button == 1 && (state & MOUSE_DOWN_FIRST_TIME) != 0) {
363                     getWidget().setSelection(new Point(0, combo.getText().length()));
364                     state &= ~MOUSE_DOWN_FIRST_TIME;
365                 }
366             }
367         }
368
369         @Override
370         public void mouseUp(MouseEvent e) {
371         }
372
373         @Override
374         public void focusGained(FocusEvent e) {
375             //System.out.println("focusGained");
376             if (!isEditing()) {
377                 startEdit(true);
378             }
379         }
380
381         @Override
382         public void focusLost(FocusEvent e) {
383             //System.out.println("focusLost");
384             if (isEditing()) {
385                 applyEdit();
386             }
387         }
388
389         @Override
390         public void widgetDefaultSelected(SelectionEvent e) {
391             applyEdit();
392         }
393
394         @Override
395         public void widgetSelected(SelectionEvent e) {
396             applyEdit();
397         }
398         
399     }
400
401     public TrackedCombo(Composite parent, WidgetSupport support, int style) {
402         combo = new org.eclipse.swt.widgets.Combo(parent, style);
403         combo.setData("org.simantics.browsing.ui.widgets.Combo", this);
404         this.resourceManager = new LocalResourceManager(JFaceResources.getResources(), combo);
405         this.colorProvider = new DefaultColorProvider();
406         support.register(this);
407         initialize();
408     }
409
410     /**
411      * Common initialization. Assumes that text is already created.
412      */
413     private void initialize() {
414         Assert.isNotNull(combo);
415
416         combo.setBackground(colorProvider.getInactiveBackground());
417 //        combo.setDoubleClickEnabled(false);
418
419         listener = new CompositeListener();
420
421         combo.addModifyListener(listener);
422         combo.addDisposeListener(listener);
423         combo.addKeyListener(listener);
424         combo.addMouseTrackListener(listener);
425         combo.addMouseListener(listener);
426         combo.addFocusListener(listener);
427         combo.addSelectionListener(listener);
428     }
429
430     private void startEdit(boolean selectAll) {
431         if (isEditing()) {
432             // Print some debug incase we end are in an invalid state
433             System.out.println("TrackedText: BUG: startEdit called when in editing state");
434         }
435         //System.out.println("start edit: selectall=" + selectAll + ", text=" + text.getText() + ", caretpos=" + caretPositionBeforeEdit);
436
437         // Backup text-field data for reverting purposes
438         caretPositionBeforeEdit = combo.getSelection().x;
439         textBeforeEdit = combo.getText();
440
441         // Signal editing state
442         setBackground(colorProvider.getEditingBackground());
443
444         if (selectAll) {
445             combo.setSelection(new Point(0, combo.getText().length()));
446         }
447         state |= EDITING | MOUSE_DOWN_FIRST_TIME;
448     }
449
450     private void applyEdit() {
451         try {
452             if (isTextValid() != null) {
453                 combo.setText(textBeforeEdit);
454             } else if (isModified() && !combo.getText().equals(textBeforeEdit)) {
455                 //System.out.println("apply");
456                 if (modifyListeners != null) {
457                     TrackedModifyEvent event = new TrackedModifyEvent(combo, combo.getText());
458                     for (Object o : modifyListeners.getListeners()) {
459                         ((TextModifyListener) o).modifyText(event);
460                     }
461                 }
462             }
463         } finally {
464             endEdit();
465         }
466     }
467
468     private void endEdit() {
469         if (!isEditing()) {
470             // Print some debug incase we end are in an invalid state
471             //ExceptionUtils.logError(new Exception("BUG: endEdit called when not in editing state"));
472             System.out.println();
473         }
474         setBackground(isMouseInsideControl() ? colorProvider.getHoverBackground() : colorProvider.getInactiveBackground());
475         //System.out.println("endEdit: " + text.getText() + ", caret: " + text.getCaretLocation() + ", selection: " + text.getSelection());
476         // Always move the caret to the end of the string
477         combo.setSelection(new Point(combo.getText().length(), 0));
478         state &= ~(EDITING | MOUSE_DOWN_FIRST_TIME);
479         setModified(false);
480     }
481
482     private void revertEdit() {
483         if (!isEditing()) {
484             // Print some debug incase we end are in an invalid state
485             //ExceptionUtils.logError(new Exception("BUG: revertEdit called when not in editing state"));
486             System.out.println("BUG: revertEdit called when not in editing state");
487         }
488         combo.setText(textBeforeEdit);
489         combo.setSelection(new Point(caretPositionBeforeEdit, 0));
490         setBackground(isMouseInsideControl() ? colorProvider.getHoverBackground() : colorProvider.getInactiveBackground());
491         state &= ~(EDITING | MOUSE_DOWN_FIRST_TIME);
492         setModified(false);
493     }
494
495     private boolean isEditing() {
496         return (state & EDITING) != 0;
497     }
498
499     private void setModified(boolean modified) {
500         if (modified) {
501             state |= MODIFIED_DURING_EDITING;
502         } else {
503             state &= ~MODIFIED_DURING_EDITING;
504         }
505     }
506
507     private boolean isMouseInsideControl() {
508         return (state & MOUSE_INSIDE_CONTROL) != 0;
509     }
510
511     private boolean isModified() {
512         return (state & MODIFIED_DURING_EDITING) != 0;
513     }
514
515     private void setMouseInsideControl(boolean inside) {
516         if (inside)
517             state |= MOUSE_INSIDE_CONTROL;
518         else
519             state &= ~MOUSE_INSIDE_CONTROL;
520     }
521
522     public void setEditable(boolean editable) {
523         if (editable) {
524             combo.setEnabled(true);
525             setBackground(isMouseInsideControl() ? colorProvider.getHoverBackground() : colorProvider.getInactiveBackground());
526         } else {
527             combo.setEnabled(false);
528             combo.setBackground(null);
529         }
530     }
531
532     public void setText(String text) {
533         this.combo.setText(text);
534     }
535
536     public void setTextWithoutNotify(String text) {
537         this.combo.removeModifyListener(listener);
538         setText(text);
539         this.combo.addModifyListener(listener);
540     }
541
542     public org.eclipse.swt.widgets.Combo getWidget() {
543         return combo;
544     }
545
546     public synchronized void addModifyListener(TextModifyListener listener) {
547         if (modifyListeners == null) {
548             modifyListeners = new ListenerList(ListenerList.IDENTITY);
549         }
550         modifyListeners.add(listener);
551     }
552
553     public synchronized void removeModifyListener(TextModifyListener listener) {
554         if (modifyListeners == null)
555             return;
556         modifyListeners.remove(listener);
557     }
558
559     public void setInputValidator(IInputValidator validator) {
560         if (validator != this.validator) {
561             this.validator = validator;
562         }
563     }
564
565     private String isTextValid() {
566         if (validator != null) {
567             return validator.isValid(getWidget().getText());
568         }
569         return null;
570     }
571
572     public void setColorProvider(ITrackedColorProvider provider) {
573         Assert.isNotNull(provider);
574         this.colorProvider = provider;
575     }
576
577     public void setBackground(Color color) {
578         if (!combo.getEnabled()) {
579             // Do not alter background when the widget is not editable.
580             return;
581         }
582         combo.setBackground(color);
583     }
584
585     public void setForeground(Color color) {
586         combo.setForeground(color);
587     }
588
589     public boolean isDisposed() {
590         return combo.isDisposed();
591     }
592
593     public Display getDisplay() {
594         return combo.getDisplay();
595     }
596
597 }