/******************************************************************************* * Copyright (c) 2007, 2010 Association for Decentralized Information Management * in Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.utils.ui.widgets; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.ListenerList; import org.eclipse.jface.dialogs.IInputValidator; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.CCombo; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.FocusEvent; import org.eclipse.swt.events.FocusListener; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.KeyListener; import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseListener; import org.eclipse.swt.events.MouseTrackListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.widgets.Composite; /** * This is a TrackedTest SWT Text-widget 'decorator'. * * It implements the necessary listeners to achieve the text widget behaviour * needed by Simantics. User notification about modifications is provided via an * Action instance given by the user. * * @see org.simantics.utils.ui.widgets.TrackedTextTest * * @author Tuukka Lehtonen */ public class TrackedCCombo { private static final int EDITING = 1 << 0; private static final int MODIFIED_DURING_EDITING = 1 << 1; /** * Used to tell whether or not a mouseDown has occured after a focusGained * event to be able to select the whole text field when it is pressed for * the first time while the widget holds focus. */ private static final int MOUSE_DOWN_FIRST_TIME = 1 << 2; private int state; private String textBeforeEdit; private CCombo combo; private CompositeListener listener; private ListenerList modifyListeners; private IInputValidator validator; //private static Color highlightColor = new Color(null, 250, 250, 250); //private static Color inactiveColor = new Color(null, 240, 240, 240); private static Color highlightColor = new Color(null, 254, 255, 197); private static Color inactiveColor = new Color(null, 245, 246, 190); private static Color invalidInputColor = new Color(null, 255, 128, 128); /** * A composite of many UI listeners for creating the functionality of this * class. */ private class CompositeListener implements ModifyListener, DisposeListener, KeyListener, MouseTrackListener, MouseListener, FocusListener { // Keyboard/editing events come in the following order: // 1. keyPressed // 2. verifyText // 3. modifyText // 4. keyReleased public void modifyText(ModifyEvent e) { //System.out.println("modifyText: " + e); if (isEditing()) setModified(true); String valid = isTextValid(); if (valid != null) { getWidget().setBackground(invalidInputColor); } else { if (isEditing()) getWidget().setBackground(null); else getWidget().setBackground(inactiveColor); } } public void widgetDisposed(DisposeEvent e) { combo.removeModifyListener(this); } public void keyPressed(KeyEvent e) { //System.out.println("keyPressed: " + e); if (!isEditing()) { // ESC, ENTER & keypad ENTER must not start editing if (e.keyCode == SWT.ESC) return; if (e.keyCode == SWT.F2 || e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) { startEdit(true); } else if (e.character != '\0') { startEdit(false); } } else { // ESC reverts any changes made during this edit if (e.keyCode == SWT.ESC) { revertEdit(); } if (e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) { applyEdit(); } } } public void keyReleased(KeyEvent e) { //System.out.println("keyReleased: " + e); } public void mouseEnter(MouseEvent e) { //System.out.println("mouseEnter"); if (!isEditing()) { getWidget().setBackground(highlightColor); } } public void mouseExit(MouseEvent e) { //System.out.println("mouseExit"); if (!isEditing()) { getWidget().setBackground(inactiveColor); } } public void mouseHover(MouseEvent e) { //System.out.println("mouseHover"); } public void mouseDoubleClick(MouseEvent e) { //System.out.println("mouseDoubleClick: " + e); // if (e.button == 1) { // getWidget().selectAll(); // } } public void mouseDown(MouseEvent e) { //System.out.println("mouseDown: " + e); if (!isEditing()) { // In reality we should never get here, since focusGained // always comes before mouseDown, but let's keep this // fallback just to be safe. if (e.button == 1) { startEdit(true); } } else { if (e.button == 1 && (state & MOUSE_DOWN_FIRST_TIME) != 0) { // getWidget().selectAll(); state &= ~MOUSE_DOWN_FIRST_TIME; } } } public void mouseUp(MouseEvent e) { } public void focusGained(FocusEvent e) { //System.out.println("focusGained"); if (!isEditing()) { startEdit(true); } } public void focusLost(FocusEvent e) { //System.out.println("focusLost"); if (isEditing()) { applyEdit(); } } } public TrackedCCombo(CCombo combo) { Assert.isNotNull(combo); this.state = 0; this.combo = combo; initialize(); } public TrackedCCombo(Composite parent, int style) { this.state = 0; this.combo = new CCombo(parent, style); initialize(); } /** * Common initialization. Assumes that text is already created. */ private void initialize() { Assert.isNotNull(combo); combo.setBackground(inactiveColor); listener = new CompositeListener(); combo.addModifyListener(listener); combo.addDisposeListener(listener); combo.addKeyListener(listener); combo.addMouseTrackListener(listener); combo.addMouseListener(listener); combo.addFocusListener(listener); } private void startEdit(boolean selectAll) { if (isEditing()) { // Print some debug incase we end are in an invalid state try { throw new Exception("TrackedText: BUG: startEdit called when in editing state"); } catch (Exception e) { System.out.println(e); } } //System.out.println("start edit: selectall=" + selectAll + ", text=" + text.getText() + ", caretpos=" + caretPositionBeforeEdit); // Backup text-field data for reverting purposes textBeforeEdit = combo.getText(); // Signal editing state combo.setBackground(null); // if (selectAll) { // text.selectAll(); // } state |= EDITING | MOUSE_DOWN_FIRST_TIME; } private void applyEdit() { try { if (isTextValid() != null) { // Just discard the edit. combo.setText(textBeforeEdit); } else if (isModified() && !combo.getText().equals(textBeforeEdit)) { //System.out.println("apply"); if (modifyListeners != null) { TrackedModifyEvent event = new TrackedModifyEvent(combo, combo.getText()); for (Object o : modifyListeners.getListeners()) { ((TrackedModifyListener) o).modifyText(event); } } } } finally { endEdit(); } } private void endEdit() { if (!isEditing()) { // Print some debug incase we end are in an invalid state //ExceptionUtils.logError(new Exception("BUG: endEdit called when not in editing state")); System.out.println("BUG: endEdit called when not in editing state"); } combo.setBackground(inactiveColor); //System.out.println("endEdit: " + text.getText() + ", caret: " + text.getCaretLocation() + ", selection: " + text.getSelection()); // Always move the caret to the end of the string // text.setSelection(text.getCharCount()); state &= ~(EDITING | MOUSE_DOWN_FIRST_TIME); setModified(false); } private void revertEdit() { if (!isEditing()) { // Print some debug incase we end are in an invalid state //ExceptionUtils.logError(new Exception("BUG: revertEdit called when not in editing state")); System.out.println("BUG: revertEdit called when not in editing state"); } combo.setText(textBeforeEdit); // text.setSelection(caretPositionBeforeEdit); combo.setBackground(inactiveColor); state &= ~(EDITING | MOUSE_DOWN_FIRST_TIME); setModified(false); } private boolean isEditing() { return (state & EDITING) != 0; } private void setModified(boolean modified) { if (modified) { state |= MODIFIED_DURING_EDITING; } else { state &= ~MODIFIED_DURING_EDITING; } } private boolean isModified() { return (state & MODIFIED_DURING_EDITING) != 0; } public void setText(String text) { this.combo.setText(text); } public void setTextWithoutNotify(String text) { this.combo.removeModifyListener(listener); setText(text); this.combo.addModifyListener(listener); } public CCombo getWidget() { return combo; } public synchronized void addModifyListener(TrackedModifyListener listener) { if (modifyListeners == null) { modifyListeners = new ListenerList(ListenerList.IDENTITY); } modifyListeners.add(listener); } public synchronized void removeModifyListener(TrackedModifyListener listener) { if (modifyListeners == null) return; modifyListeners.remove(listener); } public void setInputValidator(IInputValidator validator) { if (validator != this.validator) { this.validator = validator; } } private String isTextValid() { if (validator != null) { return validator.isValid(getWidget().getText()); } return null; } }