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