X-Git-Url: https://gerrit.simantics.org/r/gitweb?p=simantics%2Fplatform.git;a=blobdiff_plain;f=bundles%2Forg.simantics.browsing.ui.swt%2Fsrc%2Forg%2Fsimantics%2Fbrowsing%2Fui%2Fswt%2Fwidgets%2FTrackedText.java;h=5a12e269083dd907c966794986079a05c63d1ed1;hp=7ab5a50016afdc52ba21346aa288a0ee0e3f8188;hb=0ae2b770234dfc3cbb18bd38f324125cf0faca07;hpb=24e2b34260f219f0d1644ca7a138894980e25b14 diff --git a/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/widgets/TrackedText.java b/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/widgets/TrackedText.java index 7ab5a5001..5a12e2690 100644 --- a/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/widgets/TrackedText.java +++ b/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/widgets/TrackedText.java @@ -1,691 +1,691 @@ -/******************************************************************************* - * Copyright (c) 2007, 2012 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.browsing.ui.swt.widgets; - -import java.util.LinkedList; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.function.Consumer; - -import org.eclipse.core.commands.Command; -import org.eclipse.core.commands.State; -import org.eclipse.core.runtime.Assert; -import org.eclipse.core.runtime.ListenerList; -import org.eclipse.jface.dialogs.IInputValidator; -import org.eclipse.jface.resource.JFaceResources; -import org.eclipse.jface.resource.LocalResourceManager; -import org.eclipse.jface.resource.ResourceManager; -import org.eclipse.swt.SWT; -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.graphics.Font; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Text; -import org.eclipse.ui.PlatformUI; -import org.eclipse.ui.commands.ICommandService; -import org.simantics.browsing.ui.swt.widgets.impl.ITrackedColorProvider; -import org.simantics.browsing.ui.swt.widgets.impl.ReadFactory; -import org.simantics.browsing.ui.swt.widgets.impl.TextModifyListener; -import org.simantics.browsing.ui.swt.widgets.impl.TrackedModifyEvent; -import org.simantics.browsing.ui.swt.widgets.impl.Widget; -import org.simantics.browsing.ui.swt.widgets.impl.WidgetSupport; -import org.simantics.db.management.ISessionContext; -import org.simantics.db.procedure.Listener; -import org.simantics.ui.states.TrackedTextState; - -/** - * This is a TrackedTest SWT Text-widget 'decorator'. - * - * The widget has 2 main states: editing and inactive. - * - * It implements the necessary listeners to achieve the text widget behaviour - * needed by Simantics. User notification about modifications is provided via - * {@link TextModifyListener}. - * - * Examples: - * - *
- * // #1: create new Text internally, use TrackedModifylistener
- * TrackedText trackedText = new TrackedText(parentComposite, style);
- * trackedText.addModifyListener(new TrackedModifyListener() {
- *     public void modifyText(TrackedModifyEvent e) {
- *         // text was modified, do something.
- *     }
- * });
- * 
- * // #2: create new Text internally, define the colors for text states.
- * TrackedText trackedText = new TrackedText(text, <instance of ITrackedColorProvider>);
- * 
- * - * @author Tuukka Lehtonen - */ -public class TrackedText implements Widget { - - public static final String ID = "TRACKED_TEXT"; - - 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 occurred 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 static final int MOUSE_INSIDE_CONTROL = 1 << 3; - - private int state; - - private int caretPositionBeforeEdit; - - private String textBeforeEdit; - - private final Display display; - - private final Text text; - - private CompositeListener listener; - - private ListenerList modifyListeners; - - private IInputValidator validator; - - private ITrackedColorProvider colorProvider; - - private final ResourceManager resourceManager; - - private ReadFactory textFactory; - - private boolean moveCaretAfterEdit = true; - - private boolean selectAllOnStartEdit = true; - - private final CopyOnWriteArrayList> validationListeners = new CopyOnWriteArrayList<>(); - - - // UNDO REDO HANDLER - - private static final int MAX_STACK_SIZE = 25; - - private List undoStack = new LinkedList(); - private List redoStack = new LinkedList(); - - public void setTextFactory(ReadFactory textFactory) { - this.textFactory = textFactory; - } - - public void setFont(Font font) { - text.setFont(font); - } - - public void setMoveCaretAfterEdit(boolean value) { - this.moveCaretAfterEdit = value; - } - - @Override - public void setInput(ISessionContext context, Object input) { - - if (modifyListeners != null) { - for (Object o : modifyListeners.getListeners()) { - if(o instanceof Widget) { - ((Widget) o).setInput(context, input); - } - } - } - - if(textFactory != null) { - textFactory.listen(context, input, new Listener() { - - @Override - public void exception(final Throwable t) { - display.asyncExec(new Runnable() { - - @Override - public void run() { - if(isDisposed()) return; -// System.out.println("Button received new text: " + text); - text.setText(t.toString()); - } - - }); - } - - @Override - public void execute(final String string) { - - if(text.isDisposed()) return; - - display.asyncExec(new Runnable() { - - @Override - public void run() { - if(isDisposed()) return; - text.setText(string == null ? "" : string); -// text.getParent().layout(); -// text.getParent().getParent().layout(); - } - - }); - } - - @Override - public boolean isDisposed() { - return text.isDisposed(); - } - - }); - } - - } - - /** - * 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 - - @Override - public void modifyText(ModifyEvent e) { - //System.out.println("modifyText: " + e); - setModified(true); - - String valid = isTextValid(); - if (valid != null) { - setBackground(colorProvider.getInvalidBackground()); - } else { - if (isEditing()) - setBackground(colorProvider.getEditingBackground()); - else - setBackground(colorProvider.getInactiveBackground()); - } - } - - @Override - public void widgetDisposed(DisposeEvent e) { - getWidget().removeModifyListener(this); - } - - private boolean isMultiLine() { - return (text.getStyle() & SWT.MULTI) != 0; - } - - private boolean hasMultiLineCommitModifier(KeyEvent e) { - return (e.stateMask & SWT.CTRL) != 0; - } - - @Override - 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 (!isMultiLine()) { - if (e.keyCode == SWT.F2 || e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) { - startEdit(selectAllOnStartEdit); - } else if (e.character != '\0') { - startEdit(false); - } - } else { - // In multi-line mode, TAB must not start editing! - if (e.keyCode == SWT.F2) { - startEdit(selectAllOnStartEdit); - } else if (e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) { - if (hasMultiLineCommitModifier(e)) { - e.doit = false; - } else { - startEdit(false); - } - } else if (e.keyCode == SWT.TAB) { - text.traverse(((e.stateMask & SWT.SHIFT) != 0) ? SWT.TRAVERSE_TAB_PREVIOUS : SWT.TRAVERSE_TAB_NEXT); - e.doit = false; - } else if (e.character != '\0') { - startEdit(false); - } - } - } else { - // ESC reverts any changes made during this edit - if (e.keyCode == SWT.ESC) { - revertEdit(); - } - if (!isMultiLine()) { - if (e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) { - applyEdit(); - } - } else { - if (e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) { - if (hasMultiLineCommitModifier(e)) { - applyEdit(); - e.doit = false; - } - } - } - } - } - - @Override - public void keyReleased(KeyEvent e) { - //System.out.println("keyReleased: " + e); - } - - @Override - public void mouseEnter(MouseEvent e) { - //System.out.println("mouseEnter"); - if (!isEditing()) { - setBackground(colorProvider.getHoverBackground()); - } - setMouseInsideControl(true); - } - - @Override - public void mouseExit(MouseEvent e) { - //System.out.println("mouseExit"); - if (!isEditing()) { - setBackground(colorProvider.getInactiveBackground()); - } - setMouseInsideControl(false); - } - - @Override - public void mouseHover(MouseEvent e) { - //System.out.println("mouseHover"); - setMouseInsideControl(true); - } - - @Override - public void mouseDoubleClick(MouseEvent e) { - //System.out.println("mouseDoubleClick: " + e); - if (e.button == 1) { - getWidget().selectAll(); - } - } - - @Override - 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(selectAllOnStartEdit); - } - } else { - if (e.button == 1 && (state & MOUSE_DOWN_FIRST_TIME) != 0) { - if (!isMultiLine()) { - // This is useless for multi-line texts - getWidget().selectAll(); - } - state &= ~MOUSE_DOWN_FIRST_TIME; - } - } - } - - @Override - public void mouseUp(MouseEvent e) { - } - - @Override - public void focusGained(FocusEvent e) { - //System.out.println("focusGained"); - if (!isEditing()) { - if (!isMultiLine()) { - // Always start edit on single line texts when focus is gained - startEdit(selectAllOnStartEdit); - } - } - } - - @Override - public void focusLost(FocusEvent e) { - //System.out.println("focusLost"); - if (isEditing()) { - applyEdit(); - } - } - } - - public TrackedText(Composite parent, WidgetSupport support, int style) { - this.state = 0; - this.text = new Text(parent, style); - text.setData(ID, this); - this.display = text.getDisplay(); - this.resourceManager = new LocalResourceManager(JFaceResources.getResources(), text); - this.colorProvider = new DefaultColorProvider(resourceManager); - if (support!=null) support.register(this); - initialize(); - - createUndoRedoHandler(); - } - - private void createUndoRedoHandler() { - - text.addModifyListener(new ModifyListener() { - - private int eventTimeOut = 1000; - private long lastEventTimeStamp = 0; - - @Override - public void modifyText(ModifyEvent event) { - String newText = text.getText().trim(); - if (event.time - lastEventTimeStamp > eventTimeOut || newText.endsWith(" ")) { - if (newText != null && newText.length() > 0) { - if (undoStack.size() == MAX_STACK_SIZE) { - undoStack.remove(undoStack.size() - 1); - } - addToUndoStack(newText); - } - } - lastEventTimeStamp = (event.time & 0xFFFFFFFFL); - } - }); - - text.addFocusListener(new FocusListener() { - - @Override - public void focusLost(FocusEvent e) { - ICommandService service = (ICommandService) PlatformUI.getWorkbench().getService(ICommandService.class); - Command command = service.getCommand( TrackedTextState.COMMAND_ID ); - State state = command.getState( TrackedTextState.STATE_ID ); - state.setValue(true); - } - - @Override - public void focusGained(FocusEvent e) { - addToUndoStack(text.getText()); - ICommandService service = (ICommandService) PlatformUI.getWorkbench().getService(ICommandService.class); - Command command = service.getCommand( TrackedTextState.COMMAND_ID ); - State state = command.getState( TrackedTextState.STATE_ID ); - state.setValue(false); - } - }); - } - - public ResourceManager getResourceManager() { - return resourceManager; - } - - /** - * Common initialization. Assumes that text is already created. - */ - private void initialize() { - Assert.isNotNull(text); - - text.setBackground(colorProvider.getInactiveBackground()); - text.setDoubleClickEnabled(false); - - listener = new CompositeListener(); - - text.addModifyListener(listener); - text.addDisposeListener(listener); - text.addKeyListener(listener); - text.addMouseTrackListener(listener); - text.addMouseListener(listener); - text.addFocusListener(listener); - } - - public void startEdit(boolean selectAll) { - if (isEditing()) { - // Print some debug incase we end are in an invalid state - System.out.println("TrackedText: BUG: startEdit called when in editing state"); - } -// System.out.println("start edit: selectall=" + selectAll + ", text=" + text.getText() + ", caretpos=" + caretPositionBeforeEdit); - - // Backup text-field data for reverting purposes - caretPositionBeforeEdit = text.getCaretPosition(); - textBeforeEdit = text.getText(); - - // Signal editing state - setBackground(colorProvider.getEditingBackground()); - - if (selectAll) { - text.selectAll(); - } - state |= EDITING | MOUSE_DOWN_FIRST_TIME; - } - - private void applyEdit() { - try { - if (isTextValid() != null) { - text.setText(textBeforeEdit); - } else if (isModified() && !text.getText().equals(textBeforeEdit)) { - //System.out.println("apply"); - if (modifyListeners != null) { - TrackedModifyEvent event = new TrackedModifyEvent(text, text.getText()); - for (Object o : modifyListeners.getListeners()) { - ((TextModifyListener) o).modifyText(event); - } - moveCursorToEnd(); - } - } - } catch (Throwable t) { - t.printStackTrace(); - } finally { - endEdit(); - } - } - - private void endEdit() { - if (text.isDisposed()) - return; - - 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(); - } - setBackground(isMouseInsideControl() ? colorProvider.getHoverBackground() : colorProvider.getInactiveBackground()); -// System.out.println("endEdit: " + text.getText() + ", caret: " + text.getCaretLocation() + ", selection: " + text.getSelection()); - // Always move the caret to the end of the string - if(moveCaretAfterEdit) - 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"); - } - text.setText(textBeforeEdit); - text.setSelection(caretPositionBeforeEdit); - setBackground(isMouseInsideControl() ? colorProvider.getHoverBackground() : colorProvider.getInactiveBackground()); - 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 isMouseInsideControl() { - return (state & MOUSE_INSIDE_CONTROL) != 0; - } - - private void setMouseInsideControl(boolean inside) { - if (inside) - state |= MOUSE_INSIDE_CONTROL; - else - state &= ~MOUSE_INSIDE_CONTROL; - } - - private boolean isModified() { - return (state & MODIFIED_DURING_EDITING) != 0; - } - - public void setSelectAllOnStartEdit(boolean selectAll) { - this.selectAllOnStartEdit = selectAll; - } - - public void setEditable(boolean editable) { - if (editable) { - text.setEditable(true); - setBackground(isMouseInsideControl() ? colorProvider.getHoverBackground() : colorProvider.getInactiveBackground()); - } else { - text.setEditable(false); - text.setBackground(null); - } - } - - public void setText(String text) { - this.text.setText(text); - addToUndoStack(text); - } - - private void addToUndoStack(String text) { - if (isTextValid() != null) - return; - String newText = text.trim(); - if (undoStack.size() == 0) - undoStack.add(0, newText); - else if (!undoStack.get(0).equals(newText)) - undoStack.add(0, newText); - } - - public void setTextWithoutNotify(String text) { - this.text.removeModifyListener(listener); - setText(text); - this.text.addModifyListener(listener); - } - - public Text getWidget() { - return text; - } - - public synchronized void addModifyListener(TextModifyListener listener) { - if (modifyListeners == null) { - modifyListeners = new ListenerList(ListenerList.IDENTITY); - } - modifyListeners.add(listener); - } - - public synchronized void removeModifyListener(TextModifyListener 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) { - String result = validator.isValid(getWidget().getText()); - for(Consumer listener : validationListeners) listener.accept(result); - return result; - } - return null; - } - - public void setColorProvider(ITrackedColorProvider provider) { - Assert.isNotNull(provider); - this.colorProvider = provider; - } - - private void setBackground(Color background) { - if(text.isDisposed()) return; - if (!text.getEditable()) { - // Do not alter background when the widget is not editable. - return; - } - text.setBackground(background); - } - - public boolean isDisposed() { - return text.isDisposed(); - } - - public Display getDisplay() { - return display; - } - - public void addValidationListener(Consumer listener) { - validationListeners.add(listener); - } - - public void removeValidationListener(Consumer listener) { - validationListeners.remove(listener); - } - - public String getText() { - return text.getText(); - } - - public int getCaretPosition() { - return text.getCaretPosition(); - } - - public void undo() { - if (undoStack.size() > 0) { - String lastEdit = undoStack.remove(0); - if (lastEdit.equals(text.getText().trim())) { - if (undoStack.size() == 0) - return; - lastEdit = undoStack.remove(0); - } - String currText = text.getText(); - textBeforeEdit = currText; - text.setText(lastEdit); - moveCursorToEnd(); - redoStack.add(0, currText); - } - } - - public void redo() { - if (redoStack.size() > 0) { - String text = (String) redoStack.remove(0); - moveCursorToEnd(); - String currText = this.text.getText(); - addToUndoStack(currText); - textBeforeEdit = currText; - this.text.setText(text); - moveCursorToEnd(); - } - } - - private void moveCursorToEnd() { - text.setSelection(text.getText().length()); - } -} +/******************************************************************************* + * Copyright (c) 2007, 2012 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.browsing.ui.swt.widgets; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Consumer; + +import org.eclipse.core.commands.Command; +import org.eclipse.core.commands.State; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.ListenerList; +import org.eclipse.jface.dialogs.IInputValidator; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.resource.LocalResourceManager; +import org.eclipse.jface.resource.ResourceManager; +import org.eclipse.swt.SWT; +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.graphics.Font; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.commands.ICommandService; +import org.simantics.browsing.ui.swt.widgets.impl.ITrackedColorProvider; +import org.simantics.browsing.ui.swt.widgets.impl.ReadFactory; +import org.simantics.browsing.ui.swt.widgets.impl.TextModifyListener; +import org.simantics.browsing.ui.swt.widgets.impl.TrackedModifyEvent; +import org.simantics.browsing.ui.swt.widgets.impl.Widget; +import org.simantics.browsing.ui.swt.widgets.impl.WidgetSupport; +import org.simantics.db.management.ISessionContext; +import org.simantics.db.procedure.Listener; +import org.simantics.ui.states.TrackedTextState; + +/** + * This is a TrackedTest SWT Text-widget 'decorator'. + * + * The widget has 2 main states: editing and inactive. + * + * It implements the necessary listeners to achieve the text widget behaviour + * needed by Simantics. User notification about modifications is provided via + * {@link TextModifyListener}. + * + * Examples: + * + *
+ * // #1: create new Text internally, use TrackedModifylistener
+ * TrackedText trackedText = new TrackedText(parentComposite, style);
+ * trackedText.addModifyListener(new TrackedModifyListener() {
+ *     public void modifyText(TrackedModifyEvent e) {
+ *         // text was modified, do something.
+ *     }
+ * });
+ * 
+ * // #2: create new Text internally, define the colors for text states.
+ * TrackedText trackedText = new TrackedText(text, <instance of ITrackedColorProvider>);
+ * 
+ * + * @author Tuukka Lehtonen + */ +public class TrackedText implements Widget { + + public static final String ID = "TRACKED_TEXT"; + + 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 occurred 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 static final int MOUSE_INSIDE_CONTROL = 1 << 3; + + private int state; + + private int caretPositionBeforeEdit; + + private String textBeforeEdit; + + private final Display display; + + private final Text text; + + private CompositeListener listener; + + private ListenerList modifyListeners; + + private IInputValidator validator; + + private ITrackedColorProvider colorProvider; + + private final ResourceManager resourceManager; + + private ReadFactory textFactory; + + private boolean moveCaretAfterEdit = true; + + private boolean selectAllOnStartEdit = true; + + private final CopyOnWriteArrayList> validationListeners = new CopyOnWriteArrayList<>(); + + + // UNDO REDO HANDLER + + private static final int MAX_STACK_SIZE = 25; + + private List undoStack = new LinkedList(); + private List redoStack = new LinkedList(); + + public void setTextFactory(ReadFactory textFactory) { + this.textFactory = textFactory; + } + + public void setFont(Font font) { + text.setFont(font); + } + + public void setMoveCaretAfterEdit(boolean value) { + this.moveCaretAfterEdit = value; + } + + @Override + public void setInput(ISessionContext context, Object input) { + + if (modifyListeners != null) { + for (Object o : modifyListeners.getListeners()) { + if(o instanceof Widget) { + ((Widget) o).setInput(context, input); + } + } + } + + if(textFactory != null) { + textFactory.listen(context, input, new Listener() { + + @Override + public void exception(final Throwable t) { + display.asyncExec(new Runnable() { + + @Override + public void run() { + if(isDisposed()) return; +// System.out.println("Button received new text: " + text); + text.setText(t.toString()); + } + + }); + } + + @Override + public void execute(final String string) { + + if(text.isDisposed()) return; + + display.asyncExec(new Runnable() { + + @Override + public void run() { + if(isDisposed()) return; + text.setText(string == null ? "" : string); +// text.getParent().layout(); +// text.getParent().getParent().layout(); + } + + }); + } + + @Override + public boolean isDisposed() { + return text.isDisposed(); + } + + }); + } + + } + + /** + * 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 + + @Override + public void modifyText(ModifyEvent e) { + //System.out.println("modifyText: " + e); + setModified(true); + + String valid = isTextValid(); + if (valid != null) { + setBackground(colorProvider.getInvalidBackground()); + } else { + if (isEditing()) + setBackground(colorProvider.getEditingBackground()); + else + setBackground(colorProvider.getInactiveBackground()); + } + } + + @Override + public void widgetDisposed(DisposeEvent e) { + getWidget().removeModifyListener(this); + } + + private boolean isMultiLine() { + return (text.getStyle() & SWT.MULTI) != 0; + } + + private boolean hasMultiLineCommitModifier(KeyEvent e) { + return (e.stateMask & SWT.CTRL) != 0; + } + + @Override + 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 (!isMultiLine()) { + if (e.keyCode == SWT.F2 || e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) { + startEdit(selectAllOnStartEdit); + } else if (e.character != '\0') { + startEdit(false); + } + } else { + // In multi-line mode, TAB must not start editing! + if (e.keyCode == SWT.F2) { + startEdit(selectAllOnStartEdit); + } else if (e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) { + if (hasMultiLineCommitModifier(e)) { + e.doit = false; + } else { + startEdit(false); + } + } else if (e.keyCode == SWT.TAB) { + text.traverse(((e.stateMask & SWT.SHIFT) != 0) ? SWT.TRAVERSE_TAB_PREVIOUS : SWT.TRAVERSE_TAB_NEXT); + e.doit = false; + } else if (e.character != '\0') { + startEdit(false); + } + } + } else { + // ESC reverts any changes made during this edit + if (e.keyCode == SWT.ESC) { + revertEdit(); + } + if (!isMultiLine()) { + if (e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) { + applyEdit(); + } + } else { + if (e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) { + if (hasMultiLineCommitModifier(e)) { + applyEdit(); + e.doit = false; + } + } + } + } + } + + @Override + public void keyReleased(KeyEvent e) { + //System.out.println("keyReleased: " + e); + } + + @Override + public void mouseEnter(MouseEvent e) { + //System.out.println("mouseEnter"); + if (!isEditing()) { + setBackground(colorProvider.getHoverBackground()); + } + setMouseInsideControl(true); + } + + @Override + public void mouseExit(MouseEvent e) { + //System.out.println("mouseExit"); + if (!isEditing()) { + setBackground(colorProvider.getInactiveBackground()); + } + setMouseInsideControl(false); + } + + @Override + public void mouseHover(MouseEvent e) { + //System.out.println("mouseHover"); + setMouseInsideControl(true); + } + + @Override + public void mouseDoubleClick(MouseEvent e) { + //System.out.println("mouseDoubleClick: " + e); + if (e.button == 1) { + getWidget().selectAll(); + } + } + + @Override + 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(selectAllOnStartEdit); + } + } else { + if (e.button == 1 && (state & MOUSE_DOWN_FIRST_TIME) != 0) { + if (!isMultiLine()) { + // This is useless for multi-line texts + getWidget().selectAll(); + } + state &= ~MOUSE_DOWN_FIRST_TIME; + } + } + } + + @Override + public void mouseUp(MouseEvent e) { + } + + @Override + public void focusGained(FocusEvent e) { + //System.out.println("focusGained"); + if (!isEditing()) { + if (!isMultiLine()) { + // Always start edit on single line texts when focus is gained + startEdit(selectAllOnStartEdit); + } + } + } + + @Override + public void focusLost(FocusEvent e) { + //System.out.println("focusLost"); + if (isEditing()) { + applyEdit(); + } + } + } + + public TrackedText(Composite parent, WidgetSupport support, int style) { + this.state = 0; + this.text = new Text(parent, style); + text.setData(ID, this); + this.display = text.getDisplay(); + this.resourceManager = new LocalResourceManager(JFaceResources.getResources(), text); + this.colorProvider = new DefaultColorProvider(resourceManager); + if (support!=null) support.register(this); + initialize(); + + createUndoRedoHandler(); + } + + private void createUndoRedoHandler() { + + text.addModifyListener(new ModifyListener() { + + private int eventTimeOut = 1000; + private long lastEventTimeStamp = 0; + + @Override + public void modifyText(ModifyEvent event) { + String newText = text.getText().trim(); + if (event.time - lastEventTimeStamp > eventTimeOut || newText.endsWith(" ")) { + if (newText != null && newText.length() > 0) { + if (undoStack.size() == MAX_STACK_SIZE) { + undoStack.remove(undoStack.size() - 1); + } + addToUndoStack(newText); + } + } + lastEventTimeStamp = (event.time & 0xFFFFFFFFL); + } + }); + + text.addFocusListener(new FocusListener() { + + @Override + public void focusLost(FocusEvent e) { + ICommandService service = (ICommandService) PlatformUI.getWorkbench().getService(ICommandService.class); + Command command = service.getCommand( TrackedTextState.COMMAND_ID ); + State state = command.getState( TrackedTextState.STATE_ID ); + state.setValue(true); + } + + @Override + public void focusGained(FocusEvent e) { + addToUndoStack(text.getText()); + ICommandService service = (ICommandService) PlatformUI.getWorkbench().getService(ICommandService.class); + Command command = service.getCommand( TrackedTextState.COMMAND_ID ); + State state = command.getState( TrackedTextState.STATE_ID ); + state.setValue(false); + } + }); + } + + public ResourceManager getResourceManager() { + return resourceManager; + } + + /** + * Common initialization. Assumes that text is already created. + */ + private void initialize() { + Assert.isNotNull(text); + + text.setBackground(colorProvider.getInactiveBackground()); + text.setDoubleClickEnabled(false); + + listener = new CompositeListener(); + + text.addModifyListener(listener); + text.addDisposeListener(listener); + text.addKeyListener(listener); + text.addMouseTrackListener(listener); + text.addMouseListener(listener); + text.addFocusListener(listener); + } + + public void startEdit(boolean selectAll) { + if (isEditing()) { + // Print some debug incase we end are in an invalid state + System.out.println("TrackedText: BUG: startEdit called when in editing state"); + } +// System.out.println("start edit: selectall=" + selectAll + ", text=" + text.getText() + ", caretpos=" + caretPositionBeforeEdit); + + // Backup text-field data for reverting purposes + caretPositionBeforeEdit = text.getCaretPosition(); + textBeforeEdit = text.getText(); + + // Signal editing state + setBackground(colorProvider.getEditingBackground()); + + if (selectAll) { + text.selectAll(); + } + state |= EDITING | MOUSE_DOWN_FIRST_TIME; + } + + private void applyEdit() { + try { + if (isTextValid() != null) { + text.setText(textBeforeEdit); + } else if (isModified() && !text.getText().equals(textBeforeEdit)) { + //System.out.println("apply"); + if (modifyListeners != null) { + TrackedModifyEvent event = new TrackedModifyEvent(text, text.getText()); + for (Object o : modifyListeners.getListeners()) { + ((TextModifyListener) o).modifyText(event); + } + moveCursorToEnd(); + } + } + } catch (Throwable t) { + t.printStackTrace(); + } finally { + endEdit(); + } + } + + private void endEdit() { + if (text.isDisposed()) + return; + + 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(); + } + setBackground(isMouseInsideControl() ? colorProvider.getHoverBackground() : colorProvider.getInactiveBackground()); +// System.out.println("endEdit: " + text.getText() + ", caret: " + text.getCaretLocation() + ", selection: " + text.getSelection()); + // Always move the caret to the end of the string + if(moveCaretAfterEdit) + 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"); + } + text.setText(textBeforeEdit); + text.setSelection(caretPositionBeforeEdit); + setBackground(isMouseInsideControl() ? colorProvider.getHoverBackground() : colorProvider.getInactiveBackground()); + 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 isMouseInsideControl() { + return (state & MOUSE_INSIDE_CONTROL) != 0; + } + + private void setMouseInsideControl(boolean inside) { + if (inside) + state |= MOUSE_INSIDE_CONTROL; + else + state &= ~MOUSE_INSIDE_CONTROL; + } + + private boolean isModified() { + return (state & MODIFIED_DURING_EDITING) != 0; + } + + public void setSelectAllOnStartEdit(boolean selectAll) { + this.selectAllOnStartEdit = selectAll; + } + + public void setEditable(boolean editable) { + if (editable) { + text.setEditable(true); + setBackground(isMouseInsideControl() ? colorProvider.getHoverBackground() : colorProvider.getInactiveBackground()); + } else { + text.setEditable(false); + text.setBackground(null); + } + } + + public void setText(String text) { + this.text.setText(text); + addToUndoStack(text); + } + + private void addToUndoStack(String text) { + if (isTextValid() != null) + return; + String newText = text.trim(); + if (undoStack.size() == 0) + undoStack.add(0, newText); + else if (!undoStack.get(0).equals(newText)) + undoStack.add(0, newText); + } + + public void setTextWithoutNotify(String text) { + this.text.removeModifyListener(listener); + setText(text); + this.text.addModifyListener(listener); + } + + public Text getWidget() { + return text; + } + + public synchronized void addModifyListener(TextModifyListener listener) { + if (modifyListeners == null) { + modifyListeners = new ListenerList(ListenerList.IDENTITY); + } + modifyListeners.add(listener); + } + + public synchronized void removeModifyListener(TextModifyListener 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) { + String result = validator.isValid(getWidget().getText()); + for(Consumer listener : validationListeners) listener.accept(result); + return result; + } + return null; + } + + public void setColorProvider(ITrackedColorProvider provider) { + Assert.isNotNull(provider); + this.colorProvider = provider; + } + + private void setBackground(Color background) { + if(text.isDisposed()) return; + if (!text.getEditable()) { + // Do not alter background when the widget is not editable. + return; + } + text.setBackground(background); + } + + public boolean isDisposed() { + return text.isDisposed(); + } + + public Display getDisplay() { + return display; + } + + public void addValidationListener(Consumer listener) { + validationListeners.add(listener); + } + + public void removeValidationListener(Consumer listener) { + validationListeners.remove(listener); + } + + public String getText() { + return text.getText(); + } + + public int getCaretPosition() { + return text.getCaretPosition(); + } + + public void undo() { + if (undoStack.size() > 0) { + String lastEdit = undoStack.remove(0); + if (lastEdit.equals(text.getText().trim())) { + if (undoStack.size() == 0) + return; + lastEdit = undoStack.remove(0); + } + String currText = text.getText(); + textBeforeEdit = currText; + text.setText(lastEdit); + moveCursorToEnd(); + redoStack.add(0, currText); + } + } + + public void redo() { + if (redoStack.size() > 0) { + String text = (String) redoStack.remove(0); + moveCursorToEnd(); + String currText = this.text.getText(); + addToUndoStack(currText); + textBeforeEdit = currText; + this.text.setText(text); + moveCursorToEnd(); + } + } + + private void moveCursorToEnd() { + text.setSelection(text.getText().length()); + } +}