1 /*******************************************************************************
2 * Copyright (c) 2013 Association for Decentralized Information Management
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.browsing.ui.swt;
14 import java.text.MessageFormat;
15 import java.util.regex.Pattern;
17 import org.eclipse.core.runtime.Assert;
18 import org.eclipse.jface.viewers.CellEditor;
19 import org.eclipse.jface.viewers.ComboBoxCellEditor;
20 import org.eclipse.swt.SWT;
21 import org.eclipse.swt.events.FocusAdapter;
22 import org.eclipse.swt.events.FocusEvent;
23 import org.eclipse.swt.events.KeyAdapter;
24 import org.eclipse.swt.events.KeyEvent;
25 import org.eclipse.swt.events.SelectionAdapter;
26 import org.eclipse.swt.events.SelectionEvent;
27 import org.eclipse.swt.events.TraverseEvent;
28 import org.eclipse.swt.events.TraverseListener;
29 import org.eclipse.swt.graphics.GC;
30 import org.eclipse.swt.widgets.Combo;
31 import org.eclipse.swt.widgets.Composite;
32 import org.eclipse.swt.widgets.Control;
35 * Similar to org.eclipse.jface.viewers.ComboBoxCellEditor, but: <br>
36 * Uses Combo instead of CCombo <br>
37 * Set value when combo item is selected, does not wait for CR Key / Focus lost to apply the value.<br>
38 * In ReadOnly mode uses alphanum keys to preselect items.<br>
40 * @author Marko Luukkainen <marko.luukkainen@vtt.fi>
43 public class ComboBoxCellEditor2 extends CellEditor {
44 private static final int KEY_INPUT_DELAY = 500;
47 * The list of items to present in the combo box.
49 private String[] items;
52 * The zero-based index of the selected item.
57 * The custom combo box control.
62 * Default ComboBoxCellEditor style
64 private static final int defaultStyle = SWT.NONE;
67 * Creates a new cell editor with no control and no st of choices.
68 * Initially, the cell editor has no cell validator.
71 * @see CellEditor#setStyle
72 * @see CellEditor#create
73 * @see ComboBoxCellEditor#setItems
74 * @see CellEditor#dispose
76 public ComboBoxCellEditor2() {
77 setStyle(defaultStyle);
81 * Creates a new cell editor with a combo containing the given list of
82 * choices and parented under the given control. The cell editor value is
83 * the zero-based index of the selected item. Initially, the cell editor has
84 * no cell validator and the first item in the list is selected.
89 * the list of strings for the combo box
91 public ComboBoxCellEditor2(Composite parent, String[] items) {
92 this(parent, items, defaultStyle);
96 * Creates a new cell editor with a combo containing the given list of
97 * choices and parented under the given control. The cell editor value is
98 * the zero-based index of the selected item. Initially, the cell editor has
99 * no cell validator and the first item in the list is selected.
104 * the list of strings for the combo box
109 public ComboBoxCellEditor2(Composite parent, String[] items, int style) {
110 super(parent, style);
115 * Returns the list of choices for the combo box
117 * @return the list of choices for the combo box
119 public String[] getItems() {
124 * Sets the list of choices for the combo box
127 * the list of choices for the combo box
129 public void setItems(String[] items) {
130 Assert.isNotNull(items);
132 populateComboBoxItems();
136 * (non-Javadoc) Method declared on CellEditor.
138 protected Control createControl(Composite parent) {
140 comboBox = new Combo(parent, getStyle());
141 comboBox.setFont(parent.getFont());
143 populateComboBoxItems();
145 if ((getStyle() & SWT.READ_ONLY) > 0) {
146 comboBox.addKeyListener(new AutoCompleteAdapter(comboBox));
149 comboBox.addKeyListener(new KeyAdapter() {
150 // hook key pressed - see PR 14201
151 public void keyPressed(KeyEvent e) {
152 keyReleaseOccured(e);
156 comboBox.addSelectionListener(new SelectionAdapter() {
157 public void widgetDefaultSelected(SelectionEvent event) {
158 applyEditorValueAndDeactivate();
161 public void widgetSelected(SelectionEvent event) {
162 selection = comboBox.getSelectionIndex();
163 if (!comboBox.getListVisible()) {
165 * There seems to be no reliable way to detect if selection was done with
166 * mouse or with arrow keys. The problem is that we want to close the editor,
167 * if selection was changed with mouse, but keep it open if it was done with
170 // close the editor if list is visible. (Mouse selection hides the list)
171 // Note that this prevents proper selections with arrow keys with hidden list.
172 applyEditorValueAndDeactivate();
178 comboBox.addTraverseListener(new TraverseListener() {
179 public void keyTraversed(TraverseEvent e) {
180 if (e.detail == SWT.TRAVERSE_ESCAPE
181 || e.detail == SWT.TRAVERSE_RETURN) {
187 comboBox.addFocusListener(new FocusAdapter() {
188 public void focusLost(FocusEvent e) {
189 ComboBoxCellEditor2.this.focusLost();
197 * The <code>ComboBoxCellEditor</code> implementation of this
198 * <code>CellEditor</code> framework method returns the zero-based index
199 * of the current selection.
201 * @return the zero-based index of the current selection wrapped as an
202 * <code>Integer</code>
204 protected Object doGetValue() {
205 return new Integer(selection);
209 * (non-Javadoc) Method declared on CellEditor.
211 protected void doSetFocus() {
216 * The <code>ComboBoxCellEditor</code> implementation of this
217 * <code>CellEditor</code> framework method sets the minimum width of the
218 * cell. The minimum width is 10 characters if <code>comboBox</code> is
219 * not <code>null</code> or <code>disposed</code> else it is 60 pixels
220 * to make sure the arrow button and some text is visible. The list of
221 * CCombo will be wide enough to show its longest item.
223 public LayoutData getLayoutData() {
224 LayoutData layoutData = super.getLayoutData();
225 if ((comboBox == null) || comboBox.isDisposed()) {
226 layoutData.minimumWidth = 60;
228 // make the comboBox 10 characters wide
229 GC gc = new GC(comboBox);
230 layoutData.minimumWidth = (gc.getFontMetrics()
231 .getAverageCharWidth() * 10) + 10;
238 * The <code>ComboBoxCellEditor</code> implementation of this
239 * <code>CellEditor</code> framework method accepts a zero-based index of
243 * the zero-based index of the selection wrapped as an
244 * <code>Integer</code>
246 protected void doSetValue(Object value) {
247 Assert.isTrue(comboBox != null && (value instanceof Integer));
248 selection = ((Integer) value).intValue();
249 comboBox.select(selection);
253 * Updates the list of choices for the combo box for the current control.
255 private void populateComboBoxItems() {
256 if (comboBox != null && items != null) {
257 comboBox.removeAll();
258 for (int i = 0; i < items.length; i++) {
259 comboBox.add(items[i], i);
268 * Applies the currently selected value and deactivates the cell editor
270 void applyEditorValueAndDeactivate() {
271 // must set the selection before getting value
272 selection = comboBox.getSelectionIndex();
273 Object newValue = doGetValue();
275 boolean isValid = isCorrect(newValue);
276 setValueValid(isValid);
279 // Only format if the 'index' is valid
280 if (items.length > 0 && selection >= 0 && selection < items.length) {
281 // try to insert the current value into the error message.
282 setErrorMessage(MessageFormat.format(getErrorMessage(),
283 new Object[] { items[selection] }));
285 // Since we don't have a valid index, assume we're using an
287 // combo so format using its text value
288 setErrorMessage(MessageFormat.format(getErrorMessage(),
289 new Object[] { comboBox.getText() }));
293 fireApplyEditorValue();
300 * @see org.eclipse.jface.viewers.CellEditor#focusLost()
302 protected void focusLost() {
304 applyEditorValueAndDeactivate();
311 * @see org.eclipse.jface.viewers.CellEditor#keyReleaseOccured(org.eclipse.swt.events.KeyEvent)
313 protected void keyReleaseOccured(KeyEvent keyEvent) {
314 if (keyEvent.character == '\u001b') { // Escape character
316 } else if (keyEvent.character == '\t') { // tab key
317 applyEditorValueAndDeactivate();
321 protected int getDoubleClickTimeout() {
322 // while we would want to allow double click closing the editor (Closing implementation is in org.eclipse.jface.viewers.ColumnViewerEditor)
323 // using the double click detection prevents opening the combo, if the cell selection and edit commands are done "too fast".
325 // Hence, in order to use the double click mechanism so that is does not annoy users, the default ColumnViewerEditor must be overridden.
330 private class AutoCompleteAdapter extends KeyAdapter {
332 private String matcher = "";
333 private int prevEvent = 0;
334 private int prevIndex = -1;
335 private int toBeSelected = -1;
336 protected Pattern alphaNum;
338 public AutoCompleteAdapter(Combo combo) {
340 alphaNum = Pattern.compile("\\p{Alnum}");
344 public void keyPressed(KeyEvent e) {
345 if (combo.isDisposed())
347 if (e.keyCode == SWT.CR) {
348 if (prevIndex != -1) {
349 combo.select(toBeSelected);
352 if (!alphaNum.matcher(Character.toString(e.character)).matches())
354 if ((e.time - prevEvent) > KEY_INPUT_DELAY )
357 matcher = matcher += Character.toString(e.character);
358 int index = findMatching();
361 combo.setText(combo.getItem(index));
362 toBeSelected = index;
368 public int findMatching() {
371 index = getMatchingIndex(matcher);
373 index = getMatchingIndex(matcher,prevIndex);
375 index = getMatchingIndex(matcher);
378 matcher = matcher.substring(matcher.length()-1);
379 index = getMatchingIndex(matcher,prevIndex);
381 index = getMatchingIndex(matcher);
388 public int getMatchingIndex(String prefix) {
389 for (int i = 0; i < combo.getItemCount(); i++) {
390 if (combo.getItem(i).toLowerCase().trim().startsWith(matcher)) {
397 public int getMatchingIndex(String prefix, int firstIndex) {
398 for (int i = firstIndex+1; i < combo.getItemCount(); i++) {
399 if (combo.getItem(i).toLowerCase().trim().startsWith(matcher)) {