]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/ComboBoxCellEditor2.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.browsing.ui.swt / src / org / simantics / browsing / ui / swt / ComboBoxCellEditor2.java
diff --git a/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/ComboBoxCellEditor2.java b/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/ComboBoxCellEditor2.java
new file mode 100644 (file)
index 0000000..9c96fcc
--- /dev/null
@@ -0,0 +1,407 @@
+/*******************************************************************************\r
+ * Copyright (c) 2013 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;\r
+\r
+import java.text.MessageFormat;\r
+import java.util.regex.Pattern;\r
+\r
+import org.eclipse.core.runtime.Assert;\r
+import org.eclipse.jface.viewers.CellEditor;\r
+import org.eclipse.jface.viewers.ComboBoxCellEditor;\r
+import org.eclipse.swt.SWT;\r
+import org.eclipse.swt.events.FocusAdapter;\r
+import org.eclipse.swt.events.FocusEvent;\r
+import org.eclipse.swt.events.KeyAdapter;\r
+import org.eclipse.swt.events.KeyEvent;\r
+import org.eclipse.swt.events.SelectionAdapter;\r
+import org.eclipse.swt.events.SelectionEvent;\r
+import org.eclipse.swt.events.TraverseEvent;\r
+import org.eclipse.swt.events.TraverseListener;\r
+import org.eclipse.swt.graphics.GC;\r
+import org.eclipse.swt.widgets.Combo;\r
+import org.eclipse.swt.widgets.Composite;\r
+import org.eclipse.swt.widgets.Control;\r
+\r
+/**\r
+ * Similar to org.eclipse.jface.viewers.ComboBoxCellEditor, but: <br>\r
+ *   Uses Combo instead of CCombo <br>\r
+ *   Set value when combo item is selected, does not wait for CR Key / Focus lost to apply the value.<br>\r
+ *   In ReadOnly mode uses alphanum keys to preselect items.<br>\r
+ * \r
+ * @author Marko Luukkainen <marko.luukkainen@vtt.fi>\r
+ *\r
+ */\r
+public class ComboBoxCellEditor2 extends CellEditor {\r
+       private static final int KEY_INPUT_DELAY = 500;\r
+       \r
+       /**\r
+        * The list of items to present in the combo box.\r
+        */\r
+       private String[] items;\r
+\r
+       /**\r
+        * The zero-based index of the selected item.\r
+        */\r
+       int selection;\r
+\r
+       /**\r
+        * The custom combo box control.\r
+        */\r
+       Combo comboBox;\r
+\r
+       /**\r
+        * Default ComboBoxCellEditor style\r
+        */\r
+       private static final int defaultStyle = SWT.NONE;\r
+\r
+       /**\r
+        * Creates a new cell editor with no control and no st of choices.\r
+        * Initially, the cell editor has no cell validator.\r
+        *\r
+        * @since 2.1\r
+        * @see CellEditor#setStyle\r
+        * @see CellEditor#create\r
+        * @see ComboBoxCellEditor#setItems\r
+        * @see CellEditor#dispose\r
+        */\r
+       public ComboBoxCellEditor2() {\r
+               setStyle(defaultStyle);\r
+       }\r
+\r
+       /**\r
+        * Creates a new cell editor with a combo containing the given list of\r
+        * choices and parented under the given control. The cell editor value is\r
+        * the zero-based index of the selected item. Initially, the cell editor has\r
+        * no cell validator and the first item in the list is selected.\r
+        *\r
+        * @param parent\r
+        *            the parent control\r
+        * @param items\r
+        *            the list of strings for the combo box\r
+        */\r
+       public ComboBoxCellEditor2(Composite parent, String[] items) {\r
+               this(parent, items, defaultStyle);\r
+       }\r
+\r
+       /**\r
+        * Creates a new cell editor with a combo containing the given list of\r
+        * choices and parented under the given control. The cell editor value is\r
+        * the zero-based index of the selected item. Initially, the cell editor has\r
+        * no cell validator and the first item in the list is selected.\r
+        *\r
+        * @param parent\r
+        *            the parent control\r
+        * @param items\r
+        *            the list of strings for the combo box\r
+        * @param style\r
+        *            the style bits\r
+        * @since 2.1\r
+        */\r
+       public ComboBoxCellEditor2(Composite parent, String[] items, int style) {\r
+               super(parent, style);\r
+               setItems(items);\r
+       }\r
+\r
+       /**\r
+        * Returns the list of choices for the combo box\r
+        *\r
+        * @return the list of choices for the combo box\r
+        */\r
+       public String[] getItems() {\r
+               return this.items;\r
+       }\r
+\r
+       /**\r
+        * Sets the list of choices for the combo box\r
+        *\r
+        * @param items\r
+        *            the list of choices for the combo box\r
+        */\r
+       public void setItems(String[] items) {\r
+               Assert.isNotNull(items);\r
+               this.items = items;\r
+               populateComboBoxItems();\r
+       }\r
+\r
+       /*\r
+        * (non-Javadoc) Method declared on CellEditor.\r
+        */\r
+       protected Control createControl(Composite parent) {\r
+\r
+               comboBox = new Combo(parent, getStyle());\r
+               comboBox.setFont(parent.getFont());\r
+\r
+               populateComboBoxItems();\r
+\r
+               if ((getStyle() & SWT.READ_ONLY) > 0) {\r
+                       comboBox.addKeyListener(new AutoCompleteAdapter(comboBox));\r
+               }\r
+               \r
+               comboBox.addKeyListener(new KeyAdapter() {\r
+                       // hook key pressed - see PR 14201\r
+                       public void keyPressed(KeyEvent e) {\r
+                               keyReleaseOccured(e);\r
+                       }\r
+               });\r
+\r
+               comboBox.addSelectionListener(new SelectionAdapter() {\r
+                       public void widgetDefaultSelected(SelectionEvent event) {\r
+                               applyEditorValueAndDeactivate();\r
+                       }\r
+\r
+                       public void widgetSelected(SelectionEvent event) {\r
+                               selection = comboBox.getSelectionIndex();\r
+                               if (!comboBox.getListVisible()) {\r
+                                       /*\r
+                                        * There seems to be no reliable way to detect if selection was done with\r
+                                        * mouse or with arrow keys. The problem is that we want to close the editor,\r
+                                        * if selection was changed with mouse, but keep it open if it was done with\r
+                                        * arrow keys.\r
+                                        */\r
+                                       // close the editor if list is visible. (Mouse selection hides the list)\r
+                                       // Note that this prevents proper selections with arrow keys with hidden list. \r
+                                       applyEditorValueAndDeactivate();\r
+                               }\r
+                               \r
+                       }\r
+               });\r
+               \r
+               comboBox.addTraverseListener(new TraverseListener() {\r
+                       public void keyTraversed(TraverseEvent e) {\r
+                               if (e.detail == SWT.TRAVERSE_ESCAPE\r
+                            || e.detail == SWT.TRAVERSE_RETURN) {\r
+                                       e.doit = false;\r
+                               }\r
+                       }\r
+               });\r
+\r
+               comboBox.addFocusListener(new FocusAdapter() {\r
+                       public void focusLost(FocusEvent e) {\r
+                               ComboBoxCellEditor2.this.focusLost();\r
+                       }\r
+               });\r
+\r
+               return comboBox;\r
+       }\r
+\r
+       /**\r
+        * The <code>ComboBoxCellEditor</code> implementation of this\r
+        * <code>CellEditor</code> framework method returns the zero-based index\r
+        * of the current selection.\r
+        *\r
+        * @return the zero-based index of the current selection wrapped as an\r
+        *         <code>Integer</code>\r
+        */\r
+       protected Object doGetValue() {\r
+               return new Integer(selection);\r
+       }\r
+\r
+       /*\r
+        * (non-Javadoc) Method declared on CellEditor.\r
+        */\r
+       protected void doSetFocus() {\r
+               comboBox.setFocus();\r
+       }\r
+\r
+       /**\r
+        * The <code>ComboBoxCellEditor</code> implementation of this\r
+        * <code>CellEditor</code> framework method sets the minimum width of the\r
+        * cell. The minimum width is 10 characters if <code>comboBox</code> is\r
+        * not <code>null</code> or <code>disposed</code> else it is 60 pixels\r
+        * to make sure the arrow button and some text is visible. The list of\r
+        * CCombo will be wide enough to show its longest item.\r
+        */\r
+       public LayoutData getLayoutData() {\r
+               LayoutData layoutData = super.getLayoutData();\r
+               if ((comboBox == null) || comboBox.isDisposed()) {\r
+                       layoutData.minimumWidth = 60;\r
+               } else {\r
+                       // make the comboBox 10 characters wide\r
+                       GC gc = new GC(comboBox);\r
+                       layoutData.minimumWidth = (gc.getFontMetrics()\r
+                                       .getAverageCharWidth() * 10) + 10;\r
+                       gc.dispose();\r
+               }\r
+               return layoutData;\r
+       }\r
+\r
+       /**\r
+        * The <code>ComboBoxCellEditor</code> implementation of this\r
+        * <code>CellEditor</code> framework method accepts a zero-based index of\r
+        * a selection.\r
+        *\r
+        * @param value\r
+        *            the zero-based index of the selection wrapped as an\r
+        *            <code>Integer</code>\r
+        */\r
+       protected void doSetValue(Object value) {\r
+               Assert.isTrue(comboBox != null && (value instanceof Integer));\r
+               selection = ((Integer) value).intValue();\r
+               comboBox.select(selection);\r
+       }\r
+\r
+       /**\r
+        * Updates the list of choices for the combo box for the current control.\r
+        */\r
+       private void populateComboBoxItems() {\r
+               if (comboBox != null && items != null) {\r
+                       comboBox.removeAll();\r
+                       for (int i = 0; i < items.length; i++) {\r
+                               comboBox.add(items[i], i);\r
+                       }\r
+\r
+                       setValueValid(true);\r
+                       selection = 0;\r
+               }\r
+       }\r
+\r
+       /**\r
+        * Applies the currently selected value and deactivates the cell editor\r
+        */\r
+       void applyEditorValueAndDeactivate() {\r
+               // must set the selection before getting value\r
+               selection = comboBox.getSelectionIndex();\r
+               Object newValue = doGetValue();\r
+               markDirty();\r
+               boolean isValid = isCorrect(newValue);\r
+               setValueValid(isValid);\r
+\r
+               if (!isValid) {\r
+                       // Only format if the 'index' is valid\r
+                       if (items.length > 0 && selection >= 0 && selection < items.length) {\r
+                               // try to insert the current value into the error message.\r
+                               setErrorMessage(MessageFormat.format(getErrorMessage(),\r
+                                               new Object[] { items[selection] }));\r
+                       } else {\r
+                               // Since we don't have a valid index, assume we're using an\r
+                               // 'edit'\r
+                               // combo so format using its text value\r
+                               setErrorMessage(MessageFormat.format(getErrorMessage(),\r
+                                               new Object[] { comboBox.getText() }));\r
+                       }\r
+               }\r
+\r
+               fireApplyEditorValue();\r
+               deactivate();\r
+       }\r
+\r
+       /*\r
+        * (non-Javadoc)\r
+        *\r
+        * @see org.eclipse.jface.viewers.CellEditor#focusLost()\r
+        */\r
+       protected void focusLost() {\r
+               if (isActivated()) {\r
+                       applyEditorValueAndDeactivate();\r
+               }\r
+       }\r
+\r
+       /*\r
+        * (non-Javadoc)\r
+        *\r
+        * @see org.eclipse.jface.viewers.CellEditor#keyReleaseOccured(org.eclipse.swt.events.KeyEvent)\r
+        */\r
+       protected void keyReleaseOccured(KeyEvent keyEvent) {\r
+               if (keyEvent.character == '\u001b') { // Escape character\r
+                       fireCancelEditor();\r
+               } else if (keyEvent.character == '\t') { // tab key\r
+                       applyEditorValueAndDeactivate();\r
+               }\r
+       }\r
+       \r
+       protected int getDoubleClickTimeout() {\r
+               // while we would want to allow double click closing the editor (Closing implementation is in org.eclipse.jface.viewers.ColumnViewerEditor)\r
+               // using the double click detection prevents opening the combo, if the cell selection and edit commands are done "too fast". \r
+               //\r
+               // Hence, in order to use the double click mechanism so that is does not annoy users, the default ColumnViewerEditor must be overridden. \r
+               \r
+               return 0;\r
+       }\r
+       \r
+       private class AutoCompleteAdapter extends KeyAdapter {\r
+               private Combo combo;\r
+               private String matcher = "";\r
+           private int prevEvent = 0;\r
+           private int prevIndex = -1;\r
+           private int toBeSelected = -1;\r
+           protected Pattern alphaNum;\r
+               \r
+           public AutoCompleteAdapter(Combo combo) {\r
+               this.combo = combo;\r
+               alphaNum = Pattern.compile("\\p{Alnum}");\r
+           }\r
+           \r
+               @Override\r
+               public void keyPressed(KeyEvent e) {\r
+                       if (combo.isDisposed())\r
+                               return;\r
+                       if (e.keyCode == SWT.CR) {\r
+                               if (prevIndex != -1) {\r
+                                       combo.select(toBeSelected);\r
+                               }       \r
+                       }\r
+                       if (!alphaNum.matcher(Character.toString(e.character)).matches())\r
+                        return;\r
+                        if ((e.time - prevEvent) > KEY_INPUT_DELAY )\r
+                        matcher = "";\r
+                prevEvent = e.time;\r
+                matcher = matcher += Character.toString(e.character);\r
+                int index = findMatching();\r
+                \r
+                if (index != -1) {\r
+                        combo.setText(combo.getItem(index));\r
+                        toBeSelected = index;\r
+                }\r
+                prevIndex = index;\r
+                e.doit = false;\r
+               }\r
+               \r
+               public int findMatching() {\r
+                       int index = -1;\r
+                       if (prevIndex == -1)\r
+                        index = getMatchingIndex(matcher);\r
+                else {\r
+                        index = getMatchingIndex(matcher,prevIndex);\r
+                        if (index == -1) {\r
+                                index = getMatchingIndex(matcher);\r
+                        }\r
+                        if (index == -1) {\r
+                                matcher = matcher.substring(matcher.length()-1);\r
+                                index = getMatchingIndex(matcher,prevIndex);\r
+                                if (index == -1) {\r
+                                        index = getMatchingIndex(matcher);\r
+                                }\r
+                        }\r
+                }\r
+                       return index;\r
+               }\r
+               \r
+               public int getMatchingIndex(String prefix) {\r
+                       for (int i = 0; i < combo.getItemCount(); i++) {\r
+                        if (combo.getItem(i).toLowerCase().trim().startsWith(matcher)) {\r
+                                return i;\r
+                        }\r
+                }\r
+                       return -1;\r
+               }\r
+               \r
+               public int getMatchingIndex(String prefix, int firstIndex) {\r
+                       for (int i = firstIndex+1; i < combo.getItemCount(); i++) {\r
+                        if (combo.getItem(i).toLowerCase().trim().startsWith(matcher)) {\r
+                                return i;\r
+                        }\r
+                }\r
+                       return -1;\r
+               }\r
+       }\r
+\r
+}\r