1 /*******************************************************************************
2 * Copyright (c) 2007, 2010 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.utils.ui.widgets;
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.jface.resource.ColorDescriptor;
18 import org.eclipse.jface.resource.JFaceResources;
19 import org.eclipse.jface.resource.LocalResourceManager;
20 import org.eclipse.jface.resource.ResourceManager;
21 import org.eclipse.swt.SWT;
22 import org.eclipse.swt.custom.CaretEvent;
23 import org.eclipse.swt.custom.CaretListener;
24 import org.eclipse.swt.custom.StyledText;
25 import org.eclipse.swt.events.DisposeEvent;
26 import org.eclipse.swt.events.DisposeListener;
27 import org.eclipse.swt.events.FocusEvent;
28 import org.eclipse.swt.events.FocusListener;
29 import org.eclipse.swt.events.KeyEvent;
30 import org.eclipse.swt.events.KeyListener;
31 import org.eclipse.swt.events.ModifyEvent;
32 import org.eclipse.swt.events.ModifyListener;
33 import org.eclipse.swt.events.MouseEvent;
34 import org.eclipse.swt.events.MouseListener;
35 import org.eclipse.swt.events.MouseTrackListener;
36 import org.eclipse.swt.graphics.Color;
37 import org.eclipse.swt.graphics.RGB;
38 import org.eclipse.swt.widgets.Composite;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
43 * This is a TrackedTest SWT Text-widget 'decorator'.
45 * The widget has 2 main states: editing and inactive.
47 * It implements the necessary listeners to achieve the text widget behaviour
48 * needed by Simantics. User notification about modifications is provided via
49 * {@link TrackedModifyListener}.
54 * // #1: create new Text internally, use TrackedModifylistener
55 * TrackedText trackedText = new TrackedText(parentComposite, style);
56 * trackedText.addModifyListener(new TrackedModifyListener() {
57 * public void modifyText(TrackedModifyEvent e) {
58 * // text was modified, do something.
62 * // #2: create new Text internally, define the colors for text states.
63 * TrackedText trackedText = new TrackedText(text, <instance of ITrackedColorProvider>);
66 * @author Tuukka Lehtonen
68 public class TrackedStyledText {
69 private static final Logger LOGGER = LoggerFactory.getLogger(TrackedStyledText.class);
70 private static final int EDITING = 1 << 0;
71 private static final int MODIFIED_DURING_EDITING = 1 << 1;
74 * Used to tell whether or not a mouseDown has occurred after a focusGained
75 * event to be able to select the whole text field when it is pressed for
76 * the first time while the widget holds focus.
78 private static final int MOUSE_DOWN_FIRST_TIME = 1 << 2;
79 private static final int MOUSE_INSIDE_CONTROL = 1 << 3;
83 private int caretPositionBeforeEdit;
85 private String textBeforeEdit;
87 private final StyledText text;
89 private CompositeListener listener;
91 private ListenerList caretListeners;
93 private ListenerList modifyListeners;
95 private IInputValidator validator;
97 private ITrackedColorProvider colorProvider;
99 private ResourceManager resourceManager;
101 private class DefaultColorProvider implements ITrackedColorProvider {
102 private final ColorDescriptor highlightColor = ColorDescriptor.createFrom(new RGB(254, 255, 197));
103 private final ColorDescriptor inactiveColor = ColorDescriptor.createFrom(new RGB(245, 246, 190));
104 private final ColorDescriptor invalidInputColor = ColorDescriptor.createFrom(new RGB(255, 128, 128));
107 public Color getEditingBackground() {
112 public Color getHoverBackground() {
113 return resourceManager.createColor(highlightColor);
117 public Color getInactiveBackground() {
118 return resourceManager.createColor(inactiveColor);
122 public Color getInvalidBackground() {
123 return resourceManager.createColor(invalidInputColor);
128 * A composite of many UI listeners for creating the functionality of this
131 private class CompositeListener
132 implements ModifyListener, DisposeListener, KeyListener, MouseTrackListener,
133 MouseListener, FocusListener, CaretListener
135 // Keyboard/editing events come in the following order:
141 public void modifyText(ModifyEvent e) {
142 //System.out.println("modifyText: " + e);
145 String valid = isTextValid();
147 setBackground(colorProvider.getInvalidBackground());
150 setBackground(colorProvider.getEditingBackground());
152 setBackground(colorProvider.getInactiveBackground());
156 public void widgetDisposed(DisposeEvent e) {
157 getWidget().removeModifyListener(this);
160 private boolean isMultiLine() {
161 return (text.getStyle() & SWT.MULTI) != 0;
164 private boolean hasMultiLineCommitModifier(KeyEvent e) {
165 return (e.stateMask & SWT.CTRL) != 0;
168 public void keyPressed(KeyEvent e) {
169 //System.out.println("keyPressed: " + e);
171 // ESC, ENTER & keypad ENTER must not start editing
172 if (e.keyCode == SWT.ESC)
175 if (!isMultiLine()) {
176 if (e.keyCode == SWT.F2 || e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) {
178 } else if (e.character != '\0') {
182 // In multi-line mode, TAB must not start editing!
183 if (e.keyCode == SWT.F2) {
185 } else if (e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) {
186 if (hasMultiLineCommitModifier(e)) {
191 } else if (e.keyCode == SWT.TAB) {
192 text.traverse(((e.stateMask & SWT.SHIFT) != 0) ? SWT.TRAVERSE_TAB_PREVIOUS : SWT.TRAVERSE_TAB_NEXT);
194 } else if (e.character != '\0') {
199 // ESC reverts any changes made during this edit
200 if (e.keyCode == SWT.ESC) {
203 if (!isMultiLine()) {
204 if (e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) {
208 if (e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) {
209 if (hasMultiLineCommitModifier(e)) {
218 public void keyReleased(KeyEvent e) {
219 //System.out.println("keyReleased: " + e);
222 public void mouseEnter(MouseEvent e) {
223 //System.out.println("mouseEnter");
225 setBackground(colorProvider.getHoverBackground());
227 setMouseInsideControl(true);
230 public void mouseExit(MouseEvent e) {
231 //System.out.println("mouseExit");
233 setBackground(colorProvider.getInactiveBackground());
235 setMouseInsideControl(false);
238 public void mouseHover(MouseEvent e) {
239 //System.out.println("mouseHover");
240 setMouseInsideControl(true);
243 public void mouseDoubleClick(MouseEvent e) {
244 //System.out.println("mouseDoubleClick: " + e);
246 getWidget().selectAll();
250 public void mouseDown(MouseEvent e) {
251 //System.out.println("mouseDown: " + e);
253 // In reality we should never get here, since focusGained
254 // always comes before mouseDown, but let's keep this
255 // fallback just to be safe.
260 if (e.button == 1 && (state & MOUSE_DOWN_FIRST_TIME) != 0) {
261 getWidget().selectAll();
262 state &= ~MOUSE_DOWN_FIRST_TIME;
267 public void mouseUp(MouseEvent e) {
270 public void focusGained(FocusEvent e) {
271 //System.out.println("focusGained");
273 if (!isMultiLine()) {
274 // Always start edit on single line texts when focus is gained
280 public void focusLost(FocusEvent e) {
281 //System.out.println("focusLost");
288 public void caretMoved(CaretEvent event) {
289 fireCaretListeners();
293 public TrackedStyledText(StyledText text) {
294 Assert.isNotNull(text);
297 this.resourceManager = new LocalResourceManager(JFaceResources.getResources(), text);
298 this.colorProvider = new DefaultColorProvider();
303 public TrackedStyledText(Composite parent, int style) {
305 this.text = new StyledText(parent, style);
306 this.resourceManager = new LocalResourceManager(JFaceResources.getResources(), text);
307 this.colorProvider = new DefaultColorProvider();
312 public TrackedStyledText(StyledText text, ITrackedColorProvider colorProvider) {
313 Assert.isNotNull(text, "text must not be null");
314 Assert.isNotNull(colorProvider, "colorProvider must not be null");
317 this.colorProvider = colorProvider;
322 public TrackedStyledText(Composite parent, int style, ITrackedColorProvider colorProvider) {
323 Assert.isNotNull(colorProvider, "colorProvider must not be null");
325 this.text = new StyledText(parent, style);
326 this.colorProvider = colorProvider;
332 * Common initialization. Assumes that text is already created.
334 private void initialize() {
335 Assert.isNotNull(text);
337 text.setBackground(colorProvider.getInactiveBackground());
338 text.setDoubleClickEnabled(false);
340 listener = new CompositeListener();
342 text.addModifyListener(listener);
343 text.addDisposeListener(listener);
344 text.addKeyListener(listener);
345 text.addMouseTrackListener(listener);
346 text.addMouseListener(listener);
347 text.addFocusListener(listener);
348 text.addCaretListener(listener);
351 private void startEdit(boolean selectAll) {
353 // Print some debug incase we end are in an invalid state
354 System.out.println("TrackedText: BUG: startEdit called when in editing state");
356 System.out.println("start edit: selectall=" + selectAll + ", text=" + text.getText() + ", caretpos=" + caretPositionBeforeEdit);
358 // Backup text-field data for reverting purposes
359 caretPositionBeforeEdit = text.getCaretOffset();
360 textBeforeEdit = text.getText();
362 // Signal editing state
363 setBackground(colorProvider.getEditingBackground());
368 state |= EDITING | MOUSE_DOWN_FIRST_TIME;
371 private void applyEdit() {
372 LOGGER.info("apply edit");
374 if (isTextValid() != null) {
375 text.setText(textBeforeEdit);
376 } else if (isModified() && !text.getText().equals(textBeforeEdit)) {
377 //System.out.println("apply");
378 if (modifyListeners != null) {
379 TrackedModifyEvent event = new TrackedModifyEvent(text, text.getText());
380 for (Object o : modifyListeners.getListeners()) {
381 ((TrackedModifyListener) o).modifyText(event);
390 private void fireCaretListeners() {
391 if (caretListeners != null) {
392 for (Object o : caretListeners.getListeners()) {
393 ((TrackedCaretListener) o).caretOrSelectionChanged();
398 private void endEdit() {
399 LOGGER.info("endedit");
401 // Print some debug incase we end are in an invalid state
402 //ExceptionUtils.logError(new Exception("BUG: endEdit called when not in editing state"));
403 System.out.println();
405 setBackground(isMouseInsideControl() ? colorProvider.getHoverBackground() : colorProvider.getInactiveBackground());
406 //System.out.println("endEdit: " + text.getText() + ", caret: " + text.getCaretLocation() + ", selection: " + text.getSelection());
407 // Always move the caret to the end of the string
408 //text.setSelection(text.getCharCount());
409 state &= ~(EDITING | MOUSE_DOWN_FIRST_TIME);
413 private void revertEdit() {
415 // Print some debug incase we end are in an invalid state
416 //ExceptionUtils.logError(new Exception("BUG: revertEdit called when not in editing state"));
417 LOGGER.warn("BUG: revertEdit called when not in editing state");
419 text.setText(textBeforeEdit);
420 text.setSelection(caretPositionBeforeEdit);
421 setBackground(isMouseInsideControl() ? colorProvider.getHoverBackground() : colorProvider.getInactiveBackground());
422 state &= ~(EDITING | MOUSE_DOWN_FIRST_TIME);
426 public boolean isEditing() {
427 return (state & EDITING) != 0;
430 private void setModified(boolean modified) {
432 state |= MODIFIED_DURING_EDITING;
434 state &= ~MODIFIED_DURING_EDITING;
438 private boolean isMouseInsideControl() {
439 return (state & MOUSE_INSIDE_CONTROL) != 0;
442 private void setMouseInsideControl(boolean inside) {
444 state |= MOUSE_INSIDE_CONTROL;
446 state &= ~MOUSE_INSIDE_CONTROL;
449 public boolean isModified() {
450 return (state & MODIFIED_DURING_EDITING) != 0;
453 public void setEditable(boolean editable) {
455 text.setEditable(true);
456 setBackground(isMouseInsideControl() ? colorProvider.getHoverBackground() : colorProvider.getInactiveBackground());
458 text.setEditable(false);
459 text.setBackground(null);
463 public void setText(String text) {
464 this.text.setText(text);
467 public void setTextWithoutNotify(String text) {
468 this.text.removeModifyListener(listener);
470 this.text.addModifyListener(listener);
473 public StyledText getWidget() {
477 public synchronized void addModifyListener(TrackedModifyListener listener) {
478 if (modifyListeners == null) {
479 modifyListeners = new ListenerList(ListenerList.IDENTITY);
481 modifyListeners.add(listener);
484 public synchronized void addCaretListener(TrackedCaretListener listener) {
485 if (caretListeners == null) {
486 caretListeners = new ListenerList(ListenerList.IDENTITY);
488 caretListeners.add(listener);
491 public synchronized void removeModifyListener(TrackedModifyListener listener) {
492 if (modifyListeners == null)
494 modifyListeners.remove(listener);
497 public synchronized void removeCaretListener(TrackedCaretListener listener) {
498 if (caretListeners == null)
500 caretListeners.remove(listener);
503 public void setInputValidator(IInputValidator validator) {
504 if (validator != this.validator) {
505 this.validator = validator;
509 private String isTextValid() {
510 if (validator != null) {
511 return validator.isValid(getWidget().getText());
516 public void setColorProvider(ITrackedColorProvider provider) {
517 Assert.isNotNull(provider);
518 this.colorProvider = provider;
521 private void setBackground(Color background) {
522 if (!text.getEditable()) {
523 // Do not alter background when the widget is not editable.
526 text.setBackground(background);