1 /*******************************************************************************
2 * Copyright (c) 2007, 2012 Association for Decentralized Information Management
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
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.browsing.ui.swt.widgets;
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;
53 public class TrackedCombo implements Widget {
55 private static final int EDITING = 1 << 0;
56 private static final int MODIFIED_DURING_EDITING = 1 << 1;
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.
63 private static final int MOUSE_DOWN_FIRST_TIME = 1 << 2;
64 private static final int MOUSE_INSIDE_CONTROL = 1 << 3;
68 private int caretPositionBeforeEdit;
70 private String textBeforeEdit;
72 final private org.eclipse.swt.widgets.Combo combo;
74 private CompositeListener listener;
76 private ListenerList modifyListeners;
78 private IInputValidator validator;
80 private ITrackedColorProvider colorProvider;
82 private final ResourceManager resourceManager;
84 private ReadFactory<?, Map<String, Object>> itemFactory;
85 protected ReadFactory<?, String> selectionFactory;
90 public void setItemFactory(ReadFactory<?, Map<String, Object>> itemFactory) {
91 this.itemFactory = itemFactory;
94 public void setSelectionFactory(ReadFactory<?, String> selectionFactory) {
95 this.selectionFactory = selectionFactory;
98 public void setFont(Font font) {
103 public void setInput(ISessionContext context, Object input) {
105 if (modifyListeners != null) {
106 for (Object listener : modifyListeners.getListeners()) {
107 if(listener instanceof Widget) {
108 ((Widget) listener).setInput(context, input);
113 if(itemFactory != null) {
114 itemFactory.listen(context, input, new Listener<Map<String, Object>>() {
117 public void exception(Throwable t) {
122 public void execute(final Map<String, Object> items) {
123 if(isDisposed()) return;
124 Runnable r = new Runnable() {
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);
133 combo.removeModifyListener(listener);
134 combo.setData(items);
135 combo.clearSelection();
138 } catch (Throwable t) {
142 for(String key : items.keySet()) {
143 // System.out.println("-" + key);
145 combo.setData(key, index++);
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);
152 // if(modifyListeners != null)
153 // for(Object listener : modifyListeners.getListeners()) combo.addModifyListener((ModifyListener)listener);
155 combo.addModifyListener(listener);
156 //label.setSize(200, 20);
157 // label.getParent().layout();
158 // label.getParent().getParent().layout();
162 if(SWTThread.getThreadAccess().currentThreadAccess())
165 combo.getDisplay().asyncExec(r);
169 public boolean isDisposed() {
170 return combo.isDisposed();
176 if(selectionFactory != null) {
177 selectionFactory.listen(context, input, new Listener<String>() {
180 public void exception(Throwable t) {
185 public void execute(final String selectionKey) {
186 if(isDisposed()) return;
187 combo.getDisplay().asyncExec(new Runnable() {
191 if(isDisposed()) return;
192 // System.out.println("Combo received new selection key: " + selectionKey);
194 if(selectionKey == null) return;
195 combo.setData("_SelectionKey", selectionKey);
196 Integer selectionIndex = (Integer)combo.getData(selectionKey);
197 if(selectionIndex != null) combo.select(selectionIndex);
205 public boolean isDisposed() {
206 return combo.isDisposed();
214 public void manualSelect(int index) {
216 String key = combo.getItem(index);
217 combo.setData("_SelectionKey", key);
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));
228 public Color getEditingBackground() {
233 public Color getHoverBackground() {
234 return resourceManager.createColor(highlightColor);
238 public Color getInactiveBackground() {
239 return resourceManager.createColor(inactiveColor);
243 public Color getInvalidBackground() {
244 return resourceManager.createColor(invalidInputColor);
249 * A composite of many UI listeners for creating the functionality of this
252 private class CompositeListener
253 implements ModifyListener, DisposeListener, KeyListener, MouseTrackListener,
254 MouseListener, FocusListener, SelectionListener
256 // Keyboard/editing events come in the following order:
263 public void modifyText(ModifyEvent e) {
264 //System.out.println("modifyText: " + e);
267 String valid = isTextValid();
269 setBackground(colorProvider.getInvalidBackground());
272 setBackground(colorProvider.getEditingBackground());
274 setBackground(colorProvider.getInactiveBackground());
279 public void widgetDisposed(DisposeEvent e) {
280 getWidget().removeModifyListener(this);
284 public void keyPressed(KeyEvent e) {
285 //System.out.println("keyPressed: " + e);
287 // ESC, ENTER & keypad ENTER must not start editing
288 if (e.keyCode == SWT.ESC)
291 if (e.keyCode == SWT.F2) {
293 } else if (e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) {
295 } else if (e.keyCode == SWT.TAB) {
296 combo.traverse(((e.stateMask & SWT.SHIFT) != 0) ? SWT.TRAVERSE_TAB_PREVIOUS : SWT.TRAVERSE_TAB_NEXT);
298 } else if (e.character != '\0') {
303 // ESC reverts any changes made during this edit
304 if (e.keyCode == SWT.ESC) {
307 if (e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) {
315 public void keyReleased(KeyEvent e) {
316 //System.out.println("keyReleased: " + e);
320 public void mouseEnter(MouseEvent e) {
321 //System.out.println("mouseEnter");
323 setBackground(colorProvider.getHoverBackground());
325 setMouseInsideControl(true);
329 public void mouseExit(MouseEvent e) {
330 //System.out.println("mouseExit");
332 setBackground(colorProvider.getInactiveBackground());
334 setMouseInsideControl(false);
338 public void mouseHover(MouseEvent e) {
339 //System.out.println("mouseHover");
340 setMouseInsideControl(true);
344 public void mouseDoubleClick(MouseEvent e) {
345 //System.out.println("mouseDoubleClick: " + e);
347 getWidget().setSelection(new Point(0, combo.getText().length()));
352 public void mouseDown(MouseEvent e) {
353 //System.out.println("mouseDown: " + e);
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.
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;
370 public void mouseUp(MouseEvent e) {
374 public void focusGained(FocusEvent e) {
375 //System.out.println("focusGained");
382 public void focusLost(FocusEvent e) {
383 //System.out.println("focusLost");
390 public void widgetDefaultSelected(SelectionEvent e) {
395 public void widgetSelected(SelectionEvent e) {
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);
411 * Common initialization. Assumes that text is already created.
413 private void initialize() {
414 Assert.isNotNull(combo);
416 combo.setBackground(colorProvider.getInactiveBackground());
417 // combo.setDoubleClickEnabled(false);
419 listener = new CompositeListener();
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);
430 private void startEdit(boolean selectAll) {
432 // Print some debug incase we end are in an invalid state
433 System.out.println("TrackedText: BUG: startEdit called when in editing state");
435 //System.out.println("start edit: selectall=" + selectAll + ", text=" + text.getText() + ", caretpos=" + caretPositionBeforeEdit);
437 // Backup text-field data for reverting purposes
438 caretPositionBeforeEdit = combo.getSelection().x;
439 textBeforeEdit = combo.getText();
441 // Signal editing state
442 setBackground(colorProvider.getEditingBackground());
445 combo.setSelection(new Point(0, combo.getText().length()));
447 state |= EDITING | MOUSE_DOWN_FIRST_TIME;
450 private void applyEdit() {
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);
468 private void endEdit() {
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();
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);
482 private void revertEdit() {
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");
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);
495 private boolean isEditing() {
496 return (state & EDITING) != 0;
499 private void setModified(boolean modified) {
501 state |= MODIFIED_DURING_EDITING;
503 state &= ~MODIFIED_DURING_EDITING;
507 private boolean isMouseInsideControl() {
508 return (state & MOUSE_INSIDE_CONTROL) != 0;
511 private boolean isModified() {
512 return (state & MODIFIED_DURING_EDITING) != 0;
515 private void setMouseInsideControl(boolean inside) {
517 state |= MOUSE_INSIDE_CONTROL;
519 state &= ~MOUSE_INSIDE_CONTROL;
522 public void setEditable(boolean editable) {
524 combo.setEnabled(true);
525 setBackground(isMouseInsideControl() ? colorProvider.getHoverBackground() : colorProvider.getInactiveBackground());
527 combo.setEnabled(false);
528 combo.setBackground(null);
532 public void setText(String text) {
533 this.combo.setText(text);
536 public void setTextWithoutNotify(String text) {
537 this.combo.removeModifyListener(listener);
539 this.combo.addModifyListener(listener);
542 public org.eclipse.swt.widgets.Combo getWidget() {
546 public synchronized void addModifyListener(TextModifyListener listener) {
547 if (modifyListeners == null) {
548 modifyListeners = new ListenerList(ListenerList.IDENTITY);
550 modifyListeners.add(listener);
553 public synchronized void removeModifyListener(TextModifyListener listener) {
554 if (modifyListeners == null)
556 modifyListeners.remove(listener);
559 public void setInputValidator(IInputValidator validator) {
560 if (validator != this.validator) {
561 this.validator = validator;
565 private String isTextValid() {
566 if (validator != null) {
567 return validator.isValid(getWidget().getText());
572 public void setColorProvider(ITrackedColorProvider provider) {
573 Assert.isNotNull(provider);
574 this.colorProvider = provider;
577 public void setBackground(Color color) {
578 if (!combo.getEnabled()) {
579 // Do not alter background when the widget is not editable.
582 combo.setBackground(color);
585 public void setForeground(Color color) {
586 combo.setForeground(color);
589 public boolean isDisposed() {
590 return combo.isDisposed();
593 public Display getDisplay() {
594 return combo.getDisplay();