]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/widgets/TrackedCombo.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.browsing.ui.swt / src / org / simantics / browsing / ui / swt / widgets / TrackedCombo.java
diff --git a/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/widgets/TrackedCombo.java b/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/widgets/TrackedCombo.java
new file mode 100644 (file)
index 0000000..a940e41
--- /dev/null
@@ -0,0 +1,597 @@
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2012 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ *     VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.browsing.ui.swt.widgets;\r
+\r
+import java.util.Map;\r
+\r
+import org.eclipse.core.runtime.Assert;\r
+import org.eclipse.core.runtime.ListenerList;\r
+import org.eclipse.jface.dialogs.IInputValidator;\r
+import org.eclipse.jface.resource.ColorDescriptor;\r
+import org.eclipse.jface.resource.JFaceResources;\r
+import org.eclipse.jface.resource.LocalResourceManager;\r
+import org.eclipse.jface.resource.ResourceManager;\r
+import org.eclipse.swt.SWT;\r
+import org.eclipse.swt.events.DisposeEvent;\r
+import org.eclipse.swt.events.DisposeListener;\r
+import org.eclipse.swt.events.FocusEvent;\r
+import org.eclipse.swt.events.FocusListener;\r
+import org.eclipse.swt.events.KeyEvent;\r
+import org.eclipse.swt.events.KeyListener;\r
+import org.eclipse.swt.events.ModifyEvent;\r
+import org.eclipse.swt.events.ModifyListener;\r
+import org.eclipse.swt.events.MouseEvent;\r
+import org.eclipse.swt.events.MouseListener;\r
+import org.eclipse.swt.events.MouseTrackListener;\r
+import org.eclipse.swt.events.SelectionEvent;\r
+import org.eclipse.swt.events.SelectionListener;\r
+import org.eclipse.swt.graphics.Color;\r
+import org.eclipse.swt.graphics.Font;\r
+import org.eclipse.swt.graphics.Point;\r
+import org.eclipse.swt.graphics.RGB;\r
+import org.eclipse.swt.widgets.Composite;\r
+import org.eclipse.swt.widgets.Display;\r
+import org.simantics.browsing.ui.swt.widgets.impl.ITrackedColorProvider;\r
+import org.simantics.browsing.ui.swt.widgets.impl.ReadFactory;\r
+import org.simantics.browsing.ui.swt.widgets.impl.TextModifyListener;\r
+import org.simantics.browsing.ui.swt.widgets.impl.TrackedModifyEvent;\r
+import org.simantics.browsing.ui.swt.widgets.impl.Widget;\r
+import org.simantics.browsing.ui.swt.widgets.impl.WidgetSupport;\r
+import org.simantics.db.management.ISessionContext;\r
+import org.simantics.db.procedure.Listener;\r
+import org.simantics.utils.threads.SWTThread;\r
+\r
+public class TrackedCombo implements Widget {\r
+\r
+    private static final int      EDITING                 = 1 << 0;\r
+    private static final int      MODIFIED_DURING_EDITING = 1 << 1;\r
+\r
+    /**\r
+     * Used to tell whether or not a mouseDown has occurred after a focusGained\r
+     * event to be able to select the whole text field when it is pressed for\r
+     * the first time while the widget holds focus.\r
+     */\r
+    private static final int      MOUSE_DOWN_FIRST_TIME   = 1 << 2;\r
+    private static final int      MOUSE_INSIDE_CONTROL    = 1 << 3;\r
+\r
+    private int                   state;\r
+\r
+    private int                   caretPositionBeforeEdit;\r
+\r
+    private String                textBeforeEdit;\r
+\r
+    final private org.eclipse.swt.widgets.Combo combo;\r
+\r
+    private CompositeListener     listener;\r
+\r
+    private ListenerList          modifyListeners;\r
+\r
+    private IInputValidator       validator;\r
+\r
+    private ITrackedColorProvider colorProvider;\r
+\r
+    private final ResourceManager       resourceManager;\r
+\r
+    private ReadFactory<?, Map<String, Object>> itemFactory;\r
+    protected ReadFactory<?, String> selectionFactory;\r
+\r
+\r
+\r
+\r
+    public void setItemFactory(ReadFactory<?, Map<String, Object>> itemFactory) {\r
+        this.itemFactory = itemFactory;\r
+    }\r
+\r
+    public void setSelectionFactory(ReadFactory<?, String> selectionFactory) {\r
+        this.selectionFactory = selectionFactory;\r
+    }\r
+\r
+    public void setFont(Font font) {\r
+        combo.setFont(font);\r
+    }\r
+\r
+    @Override\r
+    public void setInput(ISessionContext context, Object input) {\r
+\r
+        if (modifyListeners != null) {\r
+            for (Object listener : modifyListeners.getListeners()) {\r
+                if(listener instanceof Widget) {\r
+                    ((Widget) listener).setInput(context, input);\r
+                }\r
+            }\r
+        }\r
+\r
+        if(itemFactory != null) {\r
+            itemFactory.listen(context, input, new Listener<Map<String, Object>>() {\r
+\r
+                @Override\r
+                public void exception(Throwable t) {\r
+                    t.printStackTrace();\r
+                }\r
+\r
+                @Override\r
+                public void execute(final Map<String, Object> items) {\r
+                    if(isDisposed()) return;\r
+                    Runnable r = new Runnable() {\r
+\r
+                        @Override\r
+                        public void run() {\r
+                            if(isDisposed()) return;\r
+//                          System.out.println("Combo received new items: " + items.size());\r
+//                            if(modifyListeners != null)\r
+//                                for(Object listener : modifyListeners.getListeners()) combo.removeModifyListener((ModifyListener)listener);\r
+                            if(listener != null)\r
+                                combo.removeModifyListener(listener);\r
+                            combo.setData(items);\r
+                            combo.clearSelection();\r
+                            try {\r
+                                combo.removeAll();\r
+                            } catch (Throwable t) {\r
+                                t.printStackTrace();\r
+                            }\r
+                            int index = 0;\r
+                            for(String key : items.keySet()) {\r
+//                              System.out.println("-" + key);\r
+                                combo.add(key);\r
+                                combo.setData(key, index++);\r
+                            }\r
+                            String selectionKey = (String)combo.getData("_SelectionKey");\r
+                            if(selectionKey != null) {\r
+                                Integer selectionIndex = (Integer)combo.getData(selectionKey);\r
+                                if(selectionIndex != null) combo.select(selectionIndex);\r
+                            }\r
+//                            if(modifyListeners != null)\r
+//                                for(Object listener : modifyListeners.getListeners()) combo.addModifyListener((ModifyListener)listener);\r
+                            if(listener != null)\r
+                                combo.addModifyListener(listener);\r
+                            //label.setSize(200, 20);\r
+//                          label.getParent().layout();\r
+//                          label.getParent().getParent().layout();\r
+                        }\r
+\r
+                    };\r
+                    if(SWTThread.getThreadAccess().currentThreadAccess())\r
+                       r.run();\r
+                    else\r
+                       combo.getDisplay().asyncExec(r);        \r
+                }\r
+\r
+                @Override\r
+                public boolean isDisposed() {\r
+                    return combo.isDisposed();\r
+                }\r
+\r
+            });\r
+        }\r
+\r
+        if(selectionFactory != null) {\r
+            selectionFactory.listen(context, input, new Listener<String>() {\r
+\r
+                @Override\r
+                public void exception(Throwable t) {\r
+                    t.printStackTrace();\r
+                }\r
+\r
+                @Override\r
+                public void execute(final String selectionKey) {\r
+                    if(isDisposed()) return;\r
+                    combo.getDisplay().asyncExec(new Runnable() {\r
+\r
+                        @Override\r
+                        public void run() {\r
+                            if(isDisposed()) return;\r
+//                          System.out.println("Combo received new selection key: " + selectionKey);\r
+\r
+                            if(selectionKey == null) return;\r
+                            combo.setData("_SelectionKey", selectionKey);\r
+                            Integer selectionIndex = (Integer)combo.getData(selectionKey);\r
+                            if(selectionIndex != null) combo.select(selectionIndex);\r
+\r
+                        }\r
+\r
+                    });\r
+                }\r
+\r
+                @Override\r
+                public boolean isDisposed() {\r
+                    return combo.isDisposed();\r
+                }\r
+\r
+            });\r
+        }\r
+\r
+    }\r
+\r
+    public void manualSelect(int index) {\r
+       \r
+       String key = combo.getItem(index);\r
+       combo.setData("_SelectionKey", key);\r
+       combo.select(index);\r
+       \r
+    }\r
+    \r
+    private class DefaultColorProvider implements ITrackedColorProvider {\r
+        private final ColorDescriptor highlightColor = ColorDescriptor.createFrom(new RGB(254, 255, 197));\r
+        private final ColorDescriptor inactiveColor = ColorDescriptor.createFrom(new RGB(245, 246, 190));\r
+        private final ColorDescriptor invalidInputColor = ColorDescriptor.createFrom(new RGB(255, 128, 128));\r
+\r
+        @Override\r
+        public Color getEditingBackground() {\r
+            return null;\r
+        }\r
+\r
+        @Override\r
+        public Color getHoverBackground() {\r
+            return resourceManager.createColor(highlightColor);\r
+        }\r
+\r
+        @Override\r
+        public Color getInactiveBackground() {\r
+            return resourceManager.createColor(inactiveColor);\r
+        }\r
+\r
+        @Override\r
+        public Color getInvalidBackground() {\r
+            return resourceManager.createColor(invalidInputColor);\r
+        }\r
+    };\r
+\r
+    /**\r
+     * A composite of many UI listeners for creating the functionality of this\r
+     * class.\r
+     */\r
+    private class CompositeListener\r
+    implements ModifyListener, DisposeListener, KeyListener, MouseTrackListener,\r
+    MouseListener, FocusListener, SelectionListener\r
+    {\r
+        // Keyboard/editing events come in the following order:\r
+        //   1. keyPressed\r
+        //   2. verifyText\r
+        //   3. modifyText\r
+        //   4. keyReleased\r
+\r
+        @Override\r
+        public void modifyText(ModifyEvent e) {\r
+            //System.out.println("modifyText: " + e);\r
+            setModified(true);\r
+\r
+            String valid = isTextValid();\r
+            if (valid != null) {\r
+                setBackground(colorProvider.getInvalidBackground());\r
+            } else {\r
+                if (isEditing())\r
+                    setBackground(colorProvider.getEditingBackground());\r
+                else\r
+                    setBackground(colorProvider.getInactiveBackground());\r
+            }\r
+        }\r
+\r
+        @Override\r
+        public void widgetDisposed(DisposeEvent e) {\r
+            getWidget().removeModifyListener(this);\r
+        }\r
+\r
+        @Override\r
+        public void keyPressed(KeyEvent e) {\r
+            //System.out.println("keyPressed: " + e);\r
+            if (!isEditing()) {\r
+                // ESC, ENTER & keypad ENTER must not start editing\r
+                if (e.keyCode == SWT.ESC)\r
+                    return;\r
+\r
+                if (e.keyCode == SWT.F2) {\r
+                    startEdit(true);\r
+                } else if (e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) {\r
+                    startEdit(false);\r
+                } else if (e.keyCode == SWT.TAB) {\r
+                    combo.traverse(((e.stateMask & SWT.SHIFT) != 0) ? SWT.TRAVERSE_TAB_PREVIOUS : SWT.TRAVERSE_TAB_NEXT);\r
+                    e.doit = false;\r
+                } else if (e.character != '\0') {\r
+                    startEdit(false);\r
+                }\r
+\r
+            } else {\r
+                // ESC reverts any changes made during this edit\r
+                if (e.keyCode == SWT.ESC) {\r
+                    revertEdit();\r
+                }\r
+                if (e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) {\r
+                    applyEdit();\r
+                    e.doit = false;\r
+                }\r
+            }\r
+        }\r
+\r
+        @Override\r
+        public void keyReleased(KeyEvent e) {\r
+            //System.out.println("keyReleased: " + e);\r
+        }\r
+\r
+        @Override\r
+        public void mouseEnter(MouseEvent e) {\r
+            //System.out.println("mouseEnter");\r
+            if (!isEditing()) {\r
+                setBackground(colorProvider.getHoverBackground());\r
+            }\r
+            setMouseInsideControl(true);\r
+        }\r
+\r
+        @Override\r
+        public void mouseExit(MouseEvent e) {\r
+            //System.out.println("mouseExit");\r
+            if (!isEditing()) {\r
+                setBackground(colorProvider.getInactiveBackground());\r
+            }\r
+            setMouseInsideControl(false);\r
+        }\r
+\r
+        @Override\r
+        public void mouseHover(MouseEvent e) {\r
+            //System.out.println("mouseHover");\r
+            setMouseInsideControl(true);\r
+        }\r
+\r
+        @Override\r
+        public void mouseDoubleClick(MouseEvent e) {\r
+            //System.out.println("mouseDoubleClick: " + e);\r
+            if (e.button == 1) {\r
+                getWidget().setSelection(new Point(0, combo.getText().length()));\r
+            }\r
+        }\r
+\r
+        @Override\r
+        public void mouseDown(MouseEvent e) {\r
+            //System.out.println("mouseDown: " + e);\r
+            if (!isEditing()) {\r
+                // In reality we should never get here, since focusGained\r
+                // always comes before mouseDown, but let's keep this\r
+                // fallback just to be safe.\r
+                if (e.button == 1) {\r
+                    startEdit(true);\r
+                }\r
+            } else {\r
+                if (e.button == 1 && (state & MOUSE_DOWN_FIRST_TIME) != 0) {\r
+                    getWidget().setSelection(new Point(0, combo.getText().length()));\r
+                    state &= ~MOUSE_DOWN_FIRST_TIME;\r
+                }\r
+            }\r
+        }\r
+\r
+        @Override\r
+        public void mouseUp(MouseEvent e) {\r
+        }\r
+\r
+        @Override\r
+        public void focusGained(FocusEvent e) {\r
+            //System.out.println("focusGained");\r
+            if (!isEditing()) {\r
+                startEdit(true);\r
+            }\r
+        }\r
+\r
+        @Override\r
+        public void focusLost(FocusEvent e) {\r
+            //System.out.println("focusLost");\r
+            if (isEditing()) {\r
+                applyEdit();\r
+            }\r
+        }\r
+\r
+        @Override\r
+        public void widgetDefaultSelected(SelectionEvent e) {\r
+            applyEdit();\r
+        }\r
+\r
+        @Override\r
+        public void widgetSelected(SelectionEvent e) {\r
+            applyEdit();\r
+        }\r
+        \r
+    }\r
+\r
+    public TrackedCombo(Composite parent, WidgetSupport support, int style) {\r
+        combo = new org.eclipse.swt.widgets.Combo(parent, style);\r
+        combo.setData("org.simantics.browsing.ui.widgets.Combo", this);\r
+        this.resourceManager = new LocalResourceManager(JFaceResources.getResources(), combo);\r
+        this.colorProvider = new DefaultColorProvider();\r
+        support.register(this);\r
+        initialize();\r
+    }\r
+\r
+    /**\r
+     * Common initialization. Assumes that text is already created.\r
+     */\r
+    private void initialize() {\r
+        Assert.isNotNull(combo);\r
+\r
+        combo.setBackground(colorProvider.getInactiveBackground());\r
+//        combo.setDoubleClickEnabled(false);\r
+\r
+        listener = new CompositeListener();\r
+\r
+        combo.addModifyListener(listener);\r
+        combo.addDisposeListener(listener);\r
+        combo.addKeyListener(listener);\r
+        combo.addMouseTrackListener(listener);\r
+        combo.addMouseListener(listener);\r
+        combo.addFocusListener(listener);\r
+        combo.addSelectionListener(listener);\r
+    }\r
+\r
+    private void startEdit(boolean selectAll) {\r
+        if (isEditing()) {\r
+            // Print some debug incase we end are in an invalid state\r
+            System.out.println("TrackedText: BUG: startEdit called when in editing state");\r
+        }\r
+        //System.out.println("start edit: selectall=" + selectAll + ", text=" + text.getText() + ", caretpos=" + caretPositionBeforeEdit);\r
+\r
+        // Backup text-field data for reverting purposes\r
+        caretPositionBeforeEdit = combo.getSelection().x;\r
+        textBeforeEdit = combo.getText();\r
+\r
+        // Signal editing state\r
+        setBackground(colorProvider.getEditingBackground());\r
+\r
+        if (selectAll) {\r
+            combo.setSelection(new Point(0, combo.getText().length()));\r
+        }\r
+        state |= EDITING | MOUSE_DOWN_FIRST_TIME;\r
+    }\r
+\r
+    private void applyEdit() {\r
+        try {\r
+            if (isTextValid() != null) {\r
+                combo.setText(textBeforeEdit);\r
+            } else if (isModified() && !combo.getText().equals(textBeforeEdit)) {\r
+                //System.out.println("apply");\r
+                if (modifyListeners != null) {\r
+                    TrackedModifyEvent event = new TrackedModifyEvent(combo, combo.getText());\r
+                    for (Object o : modifyListeners.getListeners()) {\r
+                        ((TextModifyListener) o).modifyText(event);\r
+                    }\r
+                }\r
+            }\r
+        } finally {\r
+            endEdit();\r
+        }\r
+    }\r
+\r
+    private void endEdit() {\r
+        if (!isEditing()) {\r
+            // Print some debug incase we end are in an invalid state\r
+            //ExceptionUtils.logError(new Exception("BUG: endEdit called when not in editing state"));\r
+            System.out.println();\r
+        }\r
+        setBackground(isMouseInsideControl() ? colorProvider.getHoverBackground() : colorProvider.getInactiveBackground());\r
+        //System.out.println("endEdit: " + text.getText() + ", caret: " + text.getCaretLocation() + ", selection: " + text.getSelection());\r
+        // Always move the caret to the end of the string\r
+        combo.setSelection(new Point(combo.getText().length(), 0));\r
+        state &= ~(EDITING | MOUSE_DOWN_FIRST_TIME);\r
+        setModified(false);\r
+    }\r
+\r
+    private void revertEdit() {\r
+        if (!isEditing()) {\r
+            // Print some debug incase we end are in an invalid state\r
+            //ExceptionUtils.logError(new Exception("BUG: revertEdit called when not in editing state"));\r
+            System.out.println("BUG: revertEdit called when not in editing state");\r
+        }\r
+        combo.setText(textBeforeEdit);\r
+        combo.setSelection(new Point(caretPositionBeforeEdit, 0));\r
+        setBackground(isMouseInsideControl() ? colorProvider.getHoverBackground() : colorProvider.getInactiveBackground());\r
+        state &= ~(EDITING | MOUSE_DOWN_FIRST_TIME);\r
+        setModified(false);\r
+    }\r
+\r
+    private boolean isEditing() {\r
+        return (state & EDITING) != 0;\r
+    }\r
+\r
+    private void setModified(boolean modified) {\r
+        if (modified) {\r
+            state |= MODIFIED_DURING_EDITING;\r
+        } else {\r
+            state &= ~MODIFIED_DURING_EDITING;\r
+        }\r
+    }\r
+\r
+    private boolean isMouseInsideControl() {\r
+        return (state & MOUSE_INSIDE_CONTROL) != 0;\r
+    }\r
+\r
+    private boolean isModified() {\r
+        return (state & MODIFIED_DURING_EDITING) != 0;\r
+    }\r
+\r
+    private void setMouseInsideControl(boolean inside) {\r
+        if (inside)\r
+            state |= MOUSE_INSIDE_CONTROL;\r
+        else\r
+            state &= ~MOUSE_INSIDE_CONTROL;\r
+    }\r
+\r
+    public void setEditable(boolean editable) {\r
+        if (editable) {\r
+            combo.setEnabled(true);\r
+            setBackground(isMouseInsideControl() ? colorProvider.getHoverBackground() : colorProvider.getInactiveBackground());\r
+        } else {\r
+            combo.setEnabled(false);\r
+            combo.setBackground(null);\r
+        }\r
+    }\r
+\r
+    public void setText(String text) {\r
+        this.combo.setText(text);\r
+    }\r
+\r
+    public void setTextWithoutNotify(String text) {\r
+        this.combo.removeModifyListener(listener);\r
+        setText(text);\r
+        this.combo.addModifyListener(listener);\r
+    }\r
+\r
+    public org.eclipse.swt.widgets.Combo getWidget() {\r
+        return combo;\r
+    }\r
+\r
+    public synchronized void addModifyListener(TextModifyListener listener) {\r
+        if (modifyListeners == null) {\r
+            modifyListeners = new ListenerList(ListenerList.IDENTITY);\r
+        }\r
+        modifyListeners.add(listener);\r
+    }\r
+\r
+    public synchronized void removeModifyListener(TextModifyListener listener) {\r
+        if (modifyListeners == null)\r
+            return;\r
+        modifyListeners.remove(listener);\r
+    }\r
+\r
+    public void setInputValidator(IInputValidator validator) {\r
+        if (validator != this.validator) {\r
+            this.validator = validator;\r
+        }\r
+    }\r
+\r
+    private String isTextValid() {\r
+        if (validator != null) {\r
+            return validator.isValid(getWidget().getText());\r
+        }\r
+        return null;\r
+    }\r
+\r
+    public void setColorProvider(ITrackedColorProvider provider) {\r
+        Assert.isNotNull(provider);\r
+        this.colorProvider = provider;\r
+    }\r
+\r
+    public void setBackground(Color color) {\r
+        if (!combo.getEnabled()) {\r
+            // Do not alter background when the widget is not editable.\r
+            return;\r
+        }\r
+        combo.setBackground(color);\r
+    }\r
+\r
+    public void setForeground(Color color) {\r
+        combo.setForeground(color);\r
+    }\r
+\r
+    public boolean isDisposed() {\r
+        return combo.isDisposed();\r
+    }\r
+\r
+    public Display getDisplay() {\r
+        return combo.getDisplay();\r
+    }\r
+\r
+}\r