1 /*******************************************************************************
\r
2 * Copyright (c) 2007, 2012 Association for Decentralized Information Management
\r
3 * in Industry THTH ry.
\r
4 * All rights reserved. This program and the accompanying materials
\r
5 * are made available under the terms of the Eclipse Public License v1.0
\r
6 * which accompanies this distribution, and is available at
\r
7 * http://www.eclipse.org/legal/epl-v10.html
\r
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.browsing.ui.swt.widgets;
\r
14 import java.util.Map;
\r
16 import org.eclipse.core.runtime.Assert;
\r
17 import org.eclipse.core.runtime.ListenerList;
\r
18 import org.eclipse.jface.dialogs.IInputValidator;
\r
19 import org.eclipse.jface.resource.ColorDescriptor;
\r
20 import org.eclipse.jface.resource.JFaceResources;
\r
21 import org.eclipse.jface.resource.LocalResourceManager;
\r
22 import org.eclipse.jface.resource.ResourceManager;
\r
23 import org.eclipse.swt.SWT;
\r
24 import org.eclipse.swt.events.DisposeEvent;
\r
25 import org.eclipse.swt.events.DisposeListener;
\r
26 import org.eclipse.swt.events.FocusEvent;
\r
27 import org.eclipse.swt.events.FocusListener;
\r
28 import org.eclipse.swt.events.KeyEvent;
\r
29 import org.eclipse.swt.events.KeyListener;
\r
30 import org.eclipse.swt.events.ModifyEvent;
\r
31 import org.eclipse.swt.events.ModifyListener;
\r
32 import org.eclipse.swt.events.MouseEvent;
\r
33 import org.eclipse.swt.events.MouseListener;
\r
34 import org.eclipse.swt.events.MouseTrackListener;
\r
35 import org.eclipse.swt.events.SelectionEvent;
\r
36 import org.eclipse.swt.events.SelectionListener;
\r
37 import org.eclipse.swt.graphics.Color;
\r
38 import org.eclipse.swt.graphics.Font;
\r
39 import org.eclipse.swt.graphics.Point;
\r
40 import org.eclipse.swt.graphics.RGB;
\r
41 import org.eclipse.swt.widgets.Composite;
\r
42 import org.eclipse.swt.widgets.Display;
\r
43 import org.simantics.browsing.ui.swt.widgets.impl.ITrackedColorProvider;
\r
44 import org.simantics.browsing.ui.swt.widgets.impl.ReadFactory;
\r
45 import org.simantics.browsing.ui.swt.widgets.impl.TextModifyListener;
\r
46 import org.simantics.browsing.ui.swt.widgets.impl.TrackedModifyEvent;
\r
47 import org.simantics.browsing.ui.swt.widgets.impl.Widget;
\r
48 import org.simantics.browsing.ui.swt.widgets.impl.WidgetSupport;
\r
49 import org.simantics.db.management.ISessionContext;
\r
50 import org.simantics.db.procedure.Listener;
\r
51 import org.simantics.utils.threads.SWTThread;
\r
53 public class TrackedCombo implements Widget {
\r
55 private static final int EDITING = 1 << 0;
\r
56 private static final int MODIFIED_DURING_EDITING = 1 << 1;
\r
59 * Used to tell whether or not a mouseDown has occurred after a focusGained
\r
60 * event to be able to select the whole text field when it is pressed for
\r
61 * the first time while the widget holds focus.
\r
63 private static final int MOUSE_DOWN_FIRST_TIME = 1 << 2;
\r
64 private static final int MOUSE_INSIDE_CONTROL = 1 << 3;
\r
68 private int caretPositionBeforeEdit;
\r
70 private String textBeforeEdit;
\r
72 final private org.eclipse.swt.widgets.Combo combo;
\r
74 private CompositeListener listener;
\r
76 private ListenerList modifyListeners;
\r
78 private IInputValidator validator;
\r
80 private ITrackedColorProvider colorProvider;
\r
82 private final ResourceManager resourceManager;
\r
84 private ReadFactory<?, Map<String, Object>> itemFactory;
\r
85 protected ReadFactory<?, String> selectionFactory;
\r
90 public void setItemFactory(ReadFactory<?, Map<String, Object>> itemFactory) {
\r
91 this.itemFactory = itemFactory;
\r
94 public void setSelectionFactory(ReadFactory<?, String> selectionFactory) {
\r
95 this.selectionFactory = selectionFactory;
\r
98 public void setFont(Font font) {
\r
99 combo.setFont(font);
\r
103 public void setInput(ISessionContext context, Object input) {
\r
105 if (modifyListeners != null) {
\r
106 for (Object listener : modifyListeners.getListeners()) {
\r
107 if(listener instanceof Widget) {
\r
108 ((Widget) listener).setInput(context, input);
\r
113 if(itemFactory != null) {
\r
114 itemFactory.listen(context, input, new Listener<Map<String, Object>>() {
\r
117 public void exception(Throwable t) {
\r
118 t.printStackTrace();
\r
122 public void execute(final Map<String, Object> items) {
\r
123 if(isDisposed()) return;
\r
124 Runnable r = new Runnable() {
\r
127 public void run() {
\r
128 if(isDisposed()) return;
\r
129 // System.out.println("Combo received new items: " + items.size());
\r
130 // if(modifyListeners != null)
\r
131 // for(Object listener : modifyListeners.getListeners()) combo.removeModifyListener((ModifyListener)listener);
\r
132 if(listener != null)
\r
133 combo.removeModifyListener(listener);
\r
134 combo.setData(items);
\r
135 combo.clearSelection();
\r
138 } catch (Throwable t) {
\r
139 t.printStackTrace();
\r
142 for(String key : items.keySet()) {
\r
143 // System.out.println("-" + key);
\r
145 combo.setData(key, index++);
\r
147 String selectionKey = (String)combo.getData("_SelectionKey");
\r
148 if(selectionKey != null) {
\r
149 Integer selectionIndex = (Integer)combo.getData(selectionKey);
\r
150 if(selectionIndex != null) combo.select(selectionIndex);
\r
152 // if(modifyListeners != null)
\r
153 // for(Object listener : modifyListeners.getListeners()) combo.addModifyListener((ModifyListener)listener);
\r
154 if(listener != null)
\r
155 combo.addModifyListener(listener);
\r
156 //label.setSize(200, 20);
\r
157 // label.getParent().layout();
\r
158 // label.getParent().getParent().layout();
\r
162 if(SWTThread.getThreadAccess().currentThreadAccess())
\r
165 combo.getDisplay().asyncExec(r);
\r
169 public boolean isDisposed() {
\r
170 return combo.isDisposed();
\r
176 if(selectionFactory != null) {
\r
177 selectionFactory.listen(context, input, new Listener<String>() {
\r
180 public void exception(Throwable t) {
\r
181 t.printStackTrace();
\r
185 public void execute(final String selectionKey) {
\r
186 if(isDisposed()) return;
\r
187 combo.getDisplay().asyncExec(new Runnable() {
\r
190 public void run() {
\r
191 if(isDisposed()) return;
\r
192 // System.out.println("Combo received new selection key: " + selectionKey);
\r
194 if(selectionKey == null) return;
\r
195 combo.setData("_SelectionKey", selectionKey);
\r
196 Integer selectionIndex = (Integer)combo.getData(selectionKey);
\r
197 if(selectionIndex != null) combo.select(selectionIndex);
\r
205 public boolean isDisposed() {
\r
206 return combo.isDisposed();
\r
214 public void manualSelect(int index) {
\r
216 String key = combo.getItem(index);
\r
217 combo.setData("_SelectionKey", key);
\r
218 combo.select(index);
\r
222 private class DefaultColorProvider implements ITrackedColorProvider {
\r
223 private final ColorDescriptor highlightColor = ColorDescriptor.createFrom(new RGB(254, 255, 197));
\r
224 private final ColorDescriptor inactiveColor = ColorDescriptor.createFrom(new RGB(245, 246, 190));
\r
225 private final ColorDescriptor invalidInputColor = ColorDescriptor.createFrom(new RGB(255, 128, 128));
\r
228 public Color getEditingBackground() {
\r
233 public Color getHoverBackground() {
\r
234 return resourceManager.createColor(highlightColor);
\r
238 public Color getInactiveBackground() {
\r
239 return resourceManager.createColor(inactiveColor);
\r
243 public Color getInvalidBackground() {
\r
244 return resourceManager.createColor(invalidInputColor);
\r
249 * A composite of many UI listeners for creating the functionality of this
\r
252 private class CompositeListener
\r
253 implements ModifyListener, DisposeListener, KeyListener, MouseTrackListener,
\r
254 MouseListener, FocusListener, SelectionListener
\r
256 // Keyboard/editing events come in the following order:
\r
263 public void modifyText(ModifyEvent e) {
\r
264 //System.out.println("modifyText: " + e);
\r
267 String valid = isTextValid();
\r
268 if (valid != null) {
\r
269 setBackground(colorProvider.getInvalidBackground());
\r
272 setBackground(colorProvider.getEditingBackground());
\r
274 setBackground(colorProvider.getInactiveBackground());
\r
279 public void widgetDisposed(DisposeEvent e) {
\r
280 getWidget().removeModifyListener(this);
\r
284 public void keyPressed(KeyEvent e) {
\r
285 //System.out.println("keyPressed: " + e);
\r
286 if (!isEditing()) {
\r
287 // ESC, ENTER & keypad ENTER must not start editing
\r
288 if (e.keyCode == SWT.ESC)
\r
291 if (e.keyCode == SWT.F2) {
\r
293 } else if (e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) {
\r
295 } else if (e.keyCode == SWT.TAB) {
\r
296 combo.traverse(((e.stateMask & SWT.SHIFT) != 0) ? SWT.TRAVERSE_TAB_PREVIOUS : SWT.TRAVERSE_TAB_NEXT);
\r
298 } else if (e.character != '\0') {
\r
303 // ESC reverts any changes made during this edit
\r
304 if (e.keyCode == SWT.ESC) {
\r
307 if (e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) {
\r
315 public void keyReleased(KeyEvent e) {
\r
316 //System.out.println("keyReleased: " + e);
\r
320 public void mouseEnter(MouseEvent e) {
\r
321 //System.out.println("mouseEnter");
\r
322 if (!isEditing()) {
\r
323 setBackground(colorProvider.getHoverBackground());
\r
325 setMouseInsideControl(true);
\r
329 public void mouseExit(MouseEvent e) {
\r
330 //System.out.println("mouseExit");
\r
331 if (!isEditing()) {
\r
332 setBackground(colorProvider.getInactiveBackground());
\r
334 setMouseInsideControl(false);
\r
338 public void mouseHover(MouseEvent e) {
\r
339 //System.out.println("mouseHover");
\r
340 setMouseInsideControl(true);
\r
344 public void mouseDoubleClick(MouseEvent e) {
\r
345 //System.out.println("mouseDoubleClick: " + e);
\r
346 if (e.button == 1) {
\r
347 getWidget().setSelection(new Point(0, combo.getText().length()));
\r
352 public void mouseDown(MouseEvent e) {
\r
353 //System.out.println("mouseDown: " + e);
\r
354 if (!isEditing()) {
\r
355 // In reality we should never get here, since focusGained
\r
356 // always comes before mouseDown, but let's keep this
\r
357 // fallback just to be safe.
\r
358 if (e.button == 1) {
\r
362 if (e.button == 1 && (state & MOUSE_DOWN_FIRST_TIME) != 0) {
\r
363 getWidget().setSelection(new Point(0, combo.getText().length()));
\r
364 state &= ~MOUSE_DOWN_FIRST_TIME;
\r
370 public void mouseUp(MouseEvent e) {
\r
374 public void focusGained(FocusEvent e) {
\r
375 //System.out.println("focusGained");
\r
376 if (!isEditing()) {
\r
382 public void focusLost(FocusEvent e) {
\r
383 //System.out.println("focusLost");
\r
390 public void widgetDefaultSelected(SelectionEvent e) {
\r
395 public void widgetSelected(SelectionEvent e) {
\r
401 public TrackedCombo(Composite parent, WidgetSupport support, int style) {
\r
402 combo = new org.eclipse.swt.widgets.Combo(parent, style);
\r
403 combo.setData("org.simantics.browsing.ui.widgets.Combo", this);
\r
404 this.resourceManager = new LocalResourceManager(JFaceResources.getResources(), combo);
\r
405 this.colorProvider = new DefaultColorProvider();
\r
406 support.register(this);
\r
411 * Common initialization. Assumes that text is already created.
\r
413 private void initialize() {
\r
414 Assert.isNotNull(combo);
\r
416 combo.setBackground(colorProvider.getInactiveBackground());
\r
417 // combo.setDoubleClickEnabled(false);
\r
419 listener = new CompositeListener();
\r
421 combo.addModifyListener(listener);
\r
422 combo.addDisposeListener(listener);
\r
423 combo.addKeyListener(listener);
\r
424 combo.addMouseTrackListener(listener);
\r
425 combo.addMouseListener(listener);
\r
426 combo.addFocusListener(listener);
\r
427 combo.addSelectionListener(listener);
\r
430 private void startEdit(boolean selectAll) {
\r
432 // Print some debug incase we end are in an invalid state
\r
433 System.out.println("TrackedText: BUG: startEdit called when in editing state");
\r
435 //System.out.println("start edit: selectall=" + selectAll + ", text=" + text.getText() + ", caretpos=" + caretPositionBeforeEdit);
\r
437 // Backup text-field data for reverting purposes
\r
438 caretPositionBeforeEdit = combo.getSelection().x;
\r
439 textBeforeEdit = combo.getText();
\r
441 // Signal editing state
\r
442 setBackground(colorProvider.getEditingBackground());
\r
445 combo.setSelection(new Point(0, combo.getText().length()));
\r
447 state |= EDITING | MOUSE_DOWN_FIRST_TIME;
\r
450 private void applyEdit() {
\r
452 if (isTextValid() != null) {
\r
453 combo.setText(textBeforeEdit);
\r
454 } else if (isModified() && !combo.getText().equals(textBeforeEdit)) {
\r
455 //System.out.println("apply");
\r
456 if (modifyListeners != null) {
\r
457 TrackedModifyEvent event = new TrackedModifyEvent(combo, combo.getText());
\r
458 for (Object o : modifyListeners.getListeners()) {
\r
459 ((TextModifyListener) o).modifyText(event);
\r
468 private void endEdit() {
\r
469 if (!isEditing()) {
\r
470 // Print some debug incase we end are in an invalid state
\r
471 //ExceptionUtils.logError(new Exception("BUG: endEdit called when not in editing state"));
\r
472 System.out.println();
\r
474 setBackground(isMouseInsideControl() ? colorProvider.getHoverBackground() : colorProvider.getInactiveBackground());
\r
475 //System.out.println("endEdit: " + text.getText() + ", caret: " + text.getCaretLocation() + ", selection: " + text.getSelection());
\r
476 // Always move the caret to the end of the string
\r
477 combo.setSelection(new Point(combo.getText().length(), 0));
\r
478 state &= ~(EDITING | MOUSE_DOWN_FIRST_TIME);
\r
479 setModified(false);
\r
482 private void revertEdit() {
\r
483 if (!isEditing()) {
\r
484 // Print some debug incase we end are in an invalid state
\r
485 //ExceptionUtils.logError(new Exception("BUG: revertEdit called when not in editing state"));
\r
486 System.out.println("BUG: revertEdit called when not in editing state");
\r
488 combo.setText(textBeforeEdit);
\r
489 combo.setSelection(new Point(caretPositionBeforeEdit, 0));
\r
490 setBackground(isMouseInsideControl() ? colorProvider.getHoverBackground() : colorProvider.getInactiveBackground());
\r
491 state &= ~(EDITING | MOUSE_DOWN_FIRST_TIME);
\r
492 setModified(false);
\r
495 private boolean isEditing() {
\r
496 return (state & EDITING) != 0;
\r
499 private void setModified(boolean modified) {
\r
501 state |= MODIFIED_DURING_EDITING;
\r
503 state &= ~MODIFIED_DURING_EDITING;
\r
507 private boolean isMouseInsideControl() {
\r
508 return (state & MOUSE_INSIDE_CONTROL) != 0;
\r
511 private boolean isModified() {
\r
512 return (state & MODIFIED_DURING_EDITING) != 0;
\r
515 private void setMouseInsideControl(boolean inside) {
\r
517 state |= MOUSE_INSIDE_CONTROL;
\r
519 state &= ~MOUSE_INSIDE_CONTROL;
\r
522 public void setEditable(boolean editable) {
\r
524 combo.setEnabled(true);
\r
525 setBackground(isMouseInsideControl() ? colorProvider.getHoverBackground() : colorProvider.getInactiveBackground());
\r
527 combo.setEnabled(false);
\r
528 combo.setBackground(null);
\r
532 public void setText(String text) {
\r
533 this.combo.setText(text);
\r
536 public void setTextWithoutNotify(String text) {
\r
537 this.combo.removeModifyListener(listener);
\r
539 this.combo.addModifyListener(listener);
\r
542 public org.eclipse.swt.widgets.Combo getWidget() {
\r
546 public synchronized void addModifyListener(TextModifyListener listener) {
\r
547 if (modifyListeners == null) {
\r
548 modifyListeners = new ListenerList(ListenerList.IDENTITY);
\r
550 modifyListeners.add(listener);
\r
553 public synchronized void removeModifyListener(TextModifyListener listener) {
\r
554 if (modifyListeners == null)
\r
556 modifyListeners.remove(listener);
\r
559 public void setInputValidator(IInputValidator validator) {
\r
560 if (validator != this.validator) {
\r
561 this.validator = validator;
\r
565 private String isTextValid() {
\r
566 if (validator != null) {
\r
567 return validator.isValid(getWidget().getText());
\r
572 public void setColorProvider(ITrackedColorProvider provider) {
\r
573 Assert.isNotNull(provider);
\r
574 this.colorProvider = provider;
\r
577 public void setBackground(Color color) {
\r
578 if (!combo.getEnabled()) {
\r
579 // Do not alter background when the widget is not editable.
\r
582 combo.setBackground(color);
\r
585 public void setForeground(Color color) {
\r
586 combo.setForeground(color);
\r
589 public boolean isDisposed() {
\r
590 return combo.isDisposed();
\r
593 public Display getDisplay() {
\r
594 return combo.getDisplay();
\r