| /******************************************************************************* |
| * Copyright (c) 2012, 2016 Original authors and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Original authors and others - initial API and implementation |
| * Dirk Fauth <dirk.fauth@googlemail.com> - Bug 453898 |
| * Ryan McHale <rpmc22@gmail.com> - Bug 484716 |
| ******************************************************************************/ |
| package org.eclipse.nebula.widgets.nattable.edit.editor; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.eclipse.nebula.widgets.nattable.data.convert.IDisplayConverter; |
| import org.eclipse.nebula.widgets.nattable.edit.EditConstants; |
| import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer.MoveDirectionEnum; |
| import org.eclipse.nebula.widgets.nattable.util.ArrayUtil; |
| import org.eclipse.nebula.widgets.nattable.widget.EditModeEnum; |
| import org.eclipse.nebula.widgets.nattable.widget.NatCombo; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.custom.BusyIndicator; |
| import org.eclipse.swt.events.ControlAdapter; |
| import org.eclipse.swt.events.ControlEvent; |
| import org.eclipse.swt.events.FocusAdapter; |
| import org.eclipse.swt.events.FocusEvent; |
| import org.eclipse.swt.events.KeyAdapter; |
| import org.eclipse.swt.events.KeyEvent; |
| import org.eclipse.swt.events.MouseAdapter; |
| import org.eclipse.swt.events.MouseEvent; |
| import org.eclipse.swt.events.ShellAdapter; |
| import org.eclipse.swt.events.ShellEvent; |
| import org.eclipse.swt.graphics.Cursor; |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.Text; |
| |
| /** |
| * {@link ICellEditor} implementation to provide combo box editing behaviour. |
| * Uses the {@link NatCombo} as editor control which provides free editing in |
| * the text control part and multi selection in the dropdown part if configured. |
| * <p> |
| * You can create a ComboBoxCellEditor either by setting the items to show |
| * statically by constructor, or by using an {@link IComboBoxDataProvider}. Last |
| * one is a way to dynamically populate the items showed in a combobox in |
| * NatTable. It is not possible to mix these two approaches! |
| * |
| */ |
| public class ComboBoxCellEditor extends AbstractCellEditor { |
| |
| /** |
| * The wrapped editor control. |
| */ |
| private NatCombo combo; |
| |
| /** |
| * The maximum number of items the drop down will show before introducing a |
| * scroll bar. |
| */ |
| protected int maxVisibleItems; |
| |
| /** |
| * The list of canonical values that will be set as selectable items to the |
| * combo. If this {@link ComboBoxCellEditor} is created for using such a |
| * list, the selectable values will be static. |
| * <p> |
| * The values will be converted to the corresponding display values prior |
| * filling the combo. |
| * <p> |
| * If this {@link ComboBoxCellEditor} is created for using a |
| * {@link IComboBoxDataProvider} this list will be ignored. |
| */ |
| private List<?> canonicalValues; |
| |
| /** |
| * The {@link IComboBoxDataProvider} that is used to set the selectable |
| * items to the combo. If this {@link ComboBoxCellEditor} is created using |
| * such a data provider, the selectable value will be dynamic. |
| * <p> |
| * The values will be converted to the corresponding display values prior |
| * filling the combo. |
| * <p> |
| * If a {@link IComboBoxDataProvider} is set, a possible set static list of |
| * canonical values will be ignored. |
| */ |
| private IComboBoxDataProvider dataProvider; |
| |
| /** |
| * Flag that indicates whether a text box is displayed to filter the drop |
| * down options |
| * |
| * @since 1.4 |
| */ |
| protected boolean showDropdownFilter = false; |
| |
| /** |
| * Flag that indicates whether this ComboBoxCellEditor supports free editing |
| * in the text control of the NatCombo or not. By default free editing is |
| * disabled. |
| */ |
| protected boolean freeEdit = false; |
| |
| /** |
| * Flag that indicates whether this ComboBoxCellEditor supports multiple |
| * selection or not. By default multiple selection is disabled. |
| */ |
| protected boolean multiselect = false; |
| |
| /** |
| * Flag that indicates whether this ComboBoxCellEditor shows checkboxes for |
| * items in the dropdown or not. |
| */ |
| protected boolean useCheckbox = false; |
| |
| /** |
| * String that is used to separate values in the String representation |
| * showed in the text control if multiselect is supported. <code>null</code> |
| * to use the default String ", ". |
| */ |
| protected String multiselectValueSeparator = null; |
| |
| /** |
| * String that is used to prefix the generated String representation showed |
| * in the text control if multiselect is supported. Needed to visualize the |
| * multiselection to the user. If this value is <code>null</code> the |
| * default String "[" is used. |
| */ |
| protected String multiselectTextPrefix = null; |
| |
| /** |
| * String that is used to suffix the generated String representation showed |
| * in the text control if multiselect is supported. Needed to visualize the |
| * multiselection to the user. If this value is <code>null</code> the |
| * default String "]" is used. |
| */ |
| protected String multiselectTextSuffix = null; |
| |
| /** |
| * The image to use as overlay to the {@link Text} Control if the dropdown |
| * is visible. It will indicate that the control is an open combo to the |
| * user. If this value is <code>null</code> the default image specified in |
| * NatCombo will be used. |
| */ |
| protected Image iconImage; |
| |
| /** |
| * The list of the canonical values that are currently shown in the opened |
| * NatCombo. Needed in case of multi selection and dynamic changing content |
| * via data provider. |
| */ |
| private List<?> currentCanonicalValues; |
| |
| /** |
| * Create a new single selection {@link ComboBoxCellEditor} based on the |
| * given list of items, showing the default number of items in the dropdown |
| * of the combo. |
| * |
| * @param canonicalValues |
| * Array of items to be shown in the drop down box. These will be |
| * converted using the {@link IDisplayConverter} for display |
| * purposes |
| */ |
| public ComboBoxCellEditor(List<?> canonicalValues) { |
| this(canonicalValues, NatCombo.DEFAULT_NUM_OF_VISIBLE_ITEMS); |
| } |
| |
| /** |
| * Create a new single selection {@link ComboBoxCellEditor} based on the |
| * given list of items. |
| * |
| * @param canonicalValues |
| * Array of items to be shown in the drop down box. These will be |
| * converted using the {@link IDisplayConverter} for display |
| * purposes |
| * @param maxVisibleItems |
| * The maximum number of items the drop down will show before |
| * introducing a scroll bar. |
| */ |
| public ComboBoxCellEditor(List<?> canonicalValues, int maxVisibleItems) { |
| this.canonicalValues = canonicalValues; |
| this.maxVisibleItems = maxVisibleItems; |
| } |
| |
| /** |
| * Create a new single selection {@link ComboBoxCellEditor} based on the |
| * given {@link IComboBoxDataProvider}, showing the default number of items |
| * in the dropdown of the combo. |
| * |
| * @param dataProvider |
| * The {@link IComboBoxDataProvider} that is responsible for |
| * populating the items to the dropdown box. This is the way to |
| * use a ComboBoxCellEditor with dynamic content. |
| */ |
| public ComboBoxCellEditor(IComboBoxDataProvider dataProvider) { |
| this(dataProvider, NatCombo.DEFAULT_NUM_OF_VISIBLE_ITEMS); |
| } |
| |
| /** |
| * Create a new single selection {@link ComboBoxCellEditor} based on the |
| * given {@link IComboBoxDataProvider}. |
| * |
| * @param dataProvider |
| * The {@link IComboBoxDataProvider} that is responsible for |
| * populating the items to the dropdown box. This is the way to |
| * use a ComboBoxCellEditor with dynamic content. |
| * @param maxVisibleItems |
| * The maximum number of items the drop down will show before |
| * introducing a scroll bar. |
| */ |
| public ComboBoxCellEditor(IComboBoxDataProvider dataProvider, int maxVisibleItems) { |
| this.dataProvider = dataProvider; |
| this.maxVisibleItems = maxVisibleItems; |
| } |
| |
| @Override |
| protected Control activateCell(Composite parent, final Object originalCanonicalValue) { |
| this.combo = createEditorControl(parent); |
| |
| // filling and populating a multiselect combo could take some time for |
| // huge data sets |
| BusyIndicator.showWhile(parent.getDisplay(), new Runnable() { |
| @Override |
| public void run() { |
| fillCombo(); |
| |
| setCanonicalValue(originalCanonicalValue); |
| } |
| }); |
| |
| // open the dropdown immediately after the Text control of the NatCombo |
| // is positioned |
| if (this.editMode == EditModeEnum.INLINE) { |
| this.combo.addTextControlListener(new ControlAdapter() { |
| @Override |
| public void controlResized(ControlEvent e) { |
| ComboBoxCellEditor.this.combo.showDropdownControl(originalCanonicalValue instanceof Character); |
| ComboBoxCellEditor.this.combo.removeTextControlListener(this); |
| } |
| |
| @Override |
| public void controlMoved(ControlEvent e) { |
| ComboBoxCellEditor.this.combo.showDropdownControl(originalCanonicalValue instanceof Character); |
| ComboBoxCellEditor.this.combo.removeTextControlListener(this); |
| } |
| }); |
| } |
| |
| return this.combo; |
| } |
| |
| /** |
| * This implementation overrides the default implementation because we can |
| * work on the list of canonical items in the combo directly. Only for |
| * multiselect in combination with free editing, we need to convert here |
| * ourself. |
| */ |
| @Override |
| public Object getCanonicalValue() { |
| if (!this.multiselect) { |
| // single selection handling |
| int selectionIndex = this.combo.getSelectionIndex(); |
| |
| // Item selected from list |
| if (selectionIndex >= 0) { |
| if (this.dataProvider != null) { |
| return this.dataProvider.getValues(getColumnIndex(), getRowIndex()).get(selectionIndex); |
| } else { |
| return this.canonicalValues.get(selectionIndex); |
| } |
| } else { |
| // if there is no selection in the dropdown, we need to check if |
| // there is a free edit in the NatCombo control |
| if (this.combo.getSelection().length > 0) { |
| return super.getCanonicalValue(); |
| } |
| } |
| } else { |
| // multi selection handling |
| int[] selectionIndices = this.combo.getSelectionIndices(); |
| |
| // as we are in multiselection mode, we always return a Collection |
| // and never null |
| List<Object> result = new ArrayList<Object>(); |
| |
| // Item selected from list |
| if (selectionIndices.length > 0) { |
| for (int i : selectionIndices) { |
| result.add(this.currentCanonicalValues.get(i)); |
| } |
| } else { |
| // if there is no selection in the dropdown, we need to check if |
| // there is a free edit in the NatCombo control |
| String[] comboSelection = this.combo.getSelection(); |
| if (comboSelection.length > 0) { |
| for (String selection : comboSelection) { |
| result.add(handleConversion(selection, this.conversionEditErrorHandler)); |
| } |
| } |
| } |
| |
| // if nothing is selected and there is no free edit, we return an |
| // empty Collection |
| return result; |
| } |
| |
| return null; |
| } |
| |
| /** |
| * This implementation overrides the default implementation because of the |
| * special handling for comboboxes. It can handle multi selection and needs |
| * to transfer the converted values into a String array so the values in the |
| * combobox can be selected. |
| * |
| * @param canonicalValue |
| * The canonical value to be set to the wrapped editor control. |
| */ |
| @Override |
| public void setCanonicalValue(Object canonicalValue) { |
| if (canonicalValue != null) { |
| String[] editorValues = null; |
| if (canonicalValue instanceof List<?>) { |
| List<?> temp = (List<?>) canonicalValue; |
| String[] result = new String[temp.size()]; |
| for (int i = 0; i < temp.size(); i++) { |
| result[i] = (String) this.displayConverter.canonicalToDisplayValue( |
| this.layerCell, this.configRegistry, temp.get(i)); |
| } |
| editorValues = result; |
| } else { |
| // in case the SELECT_ALL value is set for selecting all values |
| // in the combo we don't need a conversion and use the value |
| if (EditConstants.SELECT_ALL_ITEMS_VALUE.equals(canonicalValue)) { |
| editorValues = new String[] { canonicalValue.toString() }; |
| } else { |
| editorValues = new String[] { |
| (String) this.displayConverter.canonicalToDisplayValue( |
| this.layerCell, |
| this.configRegistry, |
| canonicalValue) }; |
| } |
| } |
| setEditorValue(editorValues); |
| } |
| } |
| |
| /** |
| * Will set the items selectable in the combo dependent on the configuration |
| * of this {@link ComboBoxCellEditor}. As the combo is only able to handle |
| * Strings in the combo itself, and this editor works directly on the |
| * canonical values, the values are converted in here too. |
| */ |
| private void fillCombo() { |
| List<String> displayValues = new ArrayList<String>(); |
| |
| if (this.dataProvider != null) { |
| this.currentCanonicalValues = this.dataProvider.getValues(getColumnIndex(), getRowIndex()); |
| } else { |
| this.currentCanonicalValues = this.canonicalValues; |
| } |
| |
| for (Object canonicalValue : this.currentCanonicalValues) { |
| Object displayValue = this.displayConverter.canonicalToDisplayValue( |
| this.layerCell, this.configRegistry, canonicalValue); |
| displayValues.add(displayValue != null ? displayValue.toString() : ""); //$NON-NLS-1$ |
| } |
| |
| this.combo.setItems(displayValues.toArray(ArrayUtil.STRING_TYPE_ARRAY)); |
| } |
| |
| @Override |
| public void close() { |
| super.close(); |
| this.currentCanonicalValues = null; |
| } |
| |
| @Override |
| public Object getEditorValue() { |
| if (!this.multiselect) { |
| return this.combo.getSelection()[0]; |
| } |
| return this.combo.getSelection(); |
| } |
| |
| @Override |
| public void setEditorValue(Object value) { |
| this.combo.setSelection((String[]) value); |
| } |
| |
| @Override |
| public NatCombo getEditorControl() { |
| return this.combo; |
| } |
| |
| @Override |
| public NatCombo createEditorControl(Composite parent) { |
| int style = SWT.NONE; |
| if (!this.freeEdit) { |
| style |= SWT.READ_ONLY; |
| } |
| if (this.multiselect) { |
| style |= SWT.MULTI; |
| } |
| if (this.useCheckbox) { |
| style |= SWT.CHECK; |
| } |
| final NatCombo combo = (this.iconImage == null) |
| ? new NatCombo(parent, this.cellStyle, this.maxVisibleItems, style, this.showDropdownFilter) |
| : new NatCombo(parent, this.cellStyle, this.maxVisibleItems, style, this.iconImage, this.showDropdownFilter); |
| |
| combo.setCursor(new Cursor(Display.getDefault(), SWT.CURSOR_IBEAM)); |
| |
| if (this.multiselect) { |
| combo.setMultiselectValueSeparator(this.multiselectValueSeparator); |
| combo.setMultiselectTextBracket(this.multiselectTextPrefix, this.multiselectTextSuffix); |
| } |
| |
| addNatComboListener(combo); |
| return combo; |
| } |
| |
| /** |
| * Registers special listeners to the {@link NatCombo} regarding the |
| * {@link EditModeEnum}, that are needed to commit/close or change the |
| * visibility state of the {@link NatCombo} dependent on UI interactions. |
| * |
| * @param combo |
| * The {@link NatCombo} to add the listeners to. |
| */ |
| protected void addNatComboListener(final NatCombo combo) { |
| combo.addKeyListener(new KeyAdapter() { |
| |
| @Override |
| public void keyPressed(KeyEvent event) { |
| if ((event.keyCode == SWT.CR) |
| || (event.keyCode == SWT.KEYPAD_CR)) { |
| commit(MoveDirectionEnum.NONE, ComboBoxCellEditor.this.editMode == EditModeEnum.INLINE); |
| } else if (event.keyCode == SWT.ESC) { |
| if (ComboBoxCellEditor.this.editMode == EditModeEnum.INLINE) { |
| close(); |
| } else { |
| combo.hideDropdownControl(); |
| } |
| } |
| } |
| |
| }); |
| |
| combo.addMouseListener(new MouseAdapter() { |
| @Override |
| public void mouseUp(MouseEvent e) { |
| commit(MoveDirectionEnum.NONE, |
| (!ComboBoxCellEditor.this.multiselect && ComboBoxCellEditor.this.editMode == EditModeEnum.INLINE)); |
| if (!ComboBoxCellEditor.this.multiselect && ComboBoxCellEditor.this.editMode == EditModeEnum.DIALOG) { |
| // hide the dropdown after a value was selected in the combo |
| // in a dialog |
| combo.hideDropdownControl(); |
| } |
| } |
| }); |
| |
| if (this.editMode == EditModeEnum.INLINE) { |
| combo.addShellListener(new ShellAdapter() { |
| @Override |
| public void shellClosed(ShellEvent e) { |
| close(); |
| } |
| }); |
| } |
| |
| if (this.editMode == EditModeEnum.DIALOG) { |
| combo.addFocusListener(new FocusAdapter() { |
| @Override |
| public void focusLost(FocusEvent e) { |
| combo.hideDropdownControl(); |
| } |
| }); |
| } |
| } |
| |
| /** |
| * Selects the item at the given zero-relative index in the receiver's list. |
| * If the item at the index was already selected, it remains selected. |
| * Indices that are out of range are ignored. |
| * |
| * @param index |
| * the index of the item to select |
| * |
| * @see org.eclipse.swt.widgets.List#select(int) |
| */ |
| public void select(int index) { |
| this.combo.select(index); |
| } |
| |
| /** |
| * Selects the items at the given zero-relative indices in the receiver. The |
| * current selection is not cleared before the new items are selected. |
| * <p> |
| * If the item at a given index is not selected, it is selected. If the item |
| * at a given index was already selected, it remains selected. Indices that |
| * are out of range and duplicate indices are ignored. If the receiver is |
| * single-select and multiple indices are specified, then all indices are |
| * ignored. |
| * |
| * @param indices |
| * the array of indices for the items to select |
| * |
| * @see org.eclipse.swt.widgets.List#select(int[]) |
| */ |
| public void select(int[] indices) { |
| this.combo.select(indices); |
| } |
| |
| /** |
| * @param multiselectValueSeparator |
| * String that should be used to separate values in the String |
| * representation showed in the text control if multiselect is |
| * supported. <code>null</code> to use the default value |
| * separator. |
| * @see NatCombo#DEFAULT_MULTI_SELECT_VALUE_SEPARATOR |
| */ |
| public void setMultiselectValueSeparator(String multiselectValueSeparator) { |
| this.multiselectValueSeparator = multiselectValueSeparator; |
| } |
| |
| /** |
| * Set the prefix and suffix that will parenthesize the text that is created |
| * out of the selected values if this NatCombo supports multiselection. |
| * |
| * @param multiselectTextPrefix |
| * String that should be used to prefix the generated String |
| * representation showed in the text control if multiselect is |
| * supported. <code>null</code> to use the default prefix. |
| * @param multiselectTextSuffix |
| * String that should be used to suffix the generated String |
| * representation showed in the text control if multiselect is |
| * supported. <code>null</code> to use the default suffix. |
| * @see NatCombo#DEFAULT_MULTI_SELECT_PREFIX |
| * @see NatCombo#DEFAULT_MULTI_SELECT_SUFFIX |
| */ |
| public void setMultiselectTextBracket(String multiselectTextPrefix, String multiselectTextSuffix) { |
| this.multiselectTextPrefix = multiselectTextPrefix; |
| this.multiselectTextSuffix = multiselectTextSuffix; |
| } |
| |
| /** |
| * @return The image that is used as overlay to the {@link Text} Control if |
| * the dropdown is visible. It will indicate that the control is an |
| * open combo to the user. If this value is <code>null</code> the |
| * default image specified in NatCombo will be used. |
| */ |
| public Image getIconImage() { |
| return this.iconImage; |
| } |
| |
| /** |
| * @param iconImage |
| * The image to use as overlay to the {@link Text} Control if the |
| * dropdown is visible. It will indicate that the control is an |
| * open combo to the user. If this value is <code>null</code> the |
| * default image specified in NatCombo will be used. |
| */ |
| public void setIconImage(Image iconImage) { |
| this.iconImage = iconImage; |
| } |
| |
| /** |
| * @return <code>true</code> if this ComboBoxCellEditor supports free |
| * editing in the text control of the NatCombo or not. By default |
| * free editing is disabled. |
| */ |
| public boolean isFreeEdit() { |
| return this.freeEdit; |
| } |
| |
| /** |
| * @param freeEdit |
| * <code>true</code> to indicate that this ComboBoxCellEditor |
| * supports free editing in the text control of the NatCombo, |
| * <code>false</code> if not. |
| */ |
| public void setFreeEdit(boolean freeEdit) { |
| this.freeEdit = freeEdit; |
| } |
| |
| /** |
| * @return <code>true</code> if this ComboBoxCellEditor supports multiple |
| * selection or not. By default multiple selection is disabled. |
| */ |
| public boolean isMultiselect() { |
| return this.multiselect; |
| } |
| |
| /** |
| * @param multiselect |
| * <code>true</code> to indicate that this ComboBoxCellEditor |
| * supports multiple selection, <code>false</code> if not. |
| */ |
| public void setMultiselect(boolean multiselect) { |
| this.multiselect = multiselect; |
| } |
| |
| /** |
| * @return <code>true</code> if this ComboBoxCellEditor shows checkboxes for |
| * items in the dropdown. By default there are not checkboxes shown. |
| */ |
| public boolean isUseCheckbox() { |
| return this.useCheckbox; |
| } |
| |
| /** |
| * @param useCheckbox |
| * <code>true</code> if this ComboBoxCellEditor should show |
| * checkboxes for items in the dropdown, <code>false</code> if |
| * not. |
| */ |
| public void setUseCheckbox(boolean useCheckbox) { |
| this.useCheckbox = useCheckbox; |
| } |
| |
| /** |
| * @return <code>true</code> if this ComboBoxCellEditor should show the text |
| * control for filtering items in the dropdown. By default the |
| * filter is not shown. |
| * |
| * @since 1.4 |
| */ |
| public boolean isShowDropdownFilter() { |
| return this.showDropdownFilter; |
| } |
| |
| /** |
| * @param showDropdownFilter |
| * <code>true</code> if this ComboBoxCellEditor should show the |
| * text control for filtering items in the dropdown, |
| * <code>false</code> if not. |
| * |
| * @since 1.4 |
| */ |
| public void setShowDropdownFilter(boolean showDropdownFilter) { |
| this.showDropdownFilter = showDropdownFilter; |
| } |
| } |