--- /dev/null
+/*******************************************************************************\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