| /******************************************************************************* |
| * Copyright (c) 2012, 2020 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 454111 |
| * Ryan McHale <rpmc22@gmail.com> - Bug 484716 |
| ******************************************************************************/ |
| package org.eclipse.nebula.widgets.nattable.widget; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.jface.viewers.ArrayContentProvider; |
| import org.eclipse.jface.viewers.LabelProvider; |
| import org.eclipse.jface.viewers.TableViewer; |
| import org.eclipse.jface.viewers.Viewer; |
| import org.eclipse.jface.viewers.ViewerFilter; |
| import org.eclipse.nebula.widgets.nattable.edit.EditConstants; |
| import org.eclipse.nebula.widgets.nattable.style.CellStyleAttributes; |
| import org.eclipse.nebula.widgets.nattable.style.CellStyleUtil; |
| import org.eclipse.nebula.widgets.nattable.style.HorizontalAlignmentEnum; |
| import org.eclipse.nebula.widgets.nattable.style.IStyle; |
| import org.eclipse.nebula.widgets.nattable.style.VerticalAlignmentEnum; |
| import org.eclipse.nebula.widgets.nattable.ui.matcher.LetterOrDigitKeyEventMatcher; |
| import org.eclipse.nebula.widgets.nattable.util.ArrayUtil; |
| import org.eclipse.nebula.widgets.nattable.util.GUIHelper; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.events.ControlEvent; |
| import org.eclipse.swt.events.ControlListener; |
| import org.eclipse.swt.events.DisposeEvent; |
| import org.eclipse.swt.events.DisposeListener; |
| import org.eclipse.swt.events.FocusEvent; |
| import org.eclipse.swt.events.FocusListener; |
| import org.eclipse.swt.events.KeyAdapter; |
| import org.eclipse.swt.events.KeyEvent; |
| import org.eclipse.swt.events.KeyListener; |
| import org.eclipse.swt.events.MouseAdapter; |
| import org.eclipse.swt.events.MouseEvent; |
| import org.eclipse.swt.events.MouseListener; |
| import org.eclipse.swt.events.PaintEvent; |
| import org.eclipse.swt.events.PaintListener; |
| import org.eclipse.swt.events.SelectionAdapter; |
| import org.eclipse.swt.events.SelectionEvent; |
| import org.eclipse.swt.events.SelectionListener; |
| import org.eclipse.swt.events.ShellListener; |
| import org.eclipse.swt.events.TraverseListener; |
| import org.eclipse.swt.graphics.Color; |
| import org.eclipse.swt.graphics.GC; |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.graphics.Rectangle; |
| import org.eclipse.swt.layout.FormAttachment; |
| import org.eclipse.swt.layout.FormData; |
| import org.eclipse.swt.layout.FormLayout; |
| import org.eclipse.swt.layout.GridData; |
| import org.eclipse.swt.layout.GridLayout; |
| import org.eclipse.swt.widgets.Canvas; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.Event; |
| import org.eclipse.swt.widgets.Listener; |
| import org.eclipse.swt.widgets.Shell; |
| import org.eclipse.swt.widgets.Table; |
| import org.eclipse.swt.widgets.TableColumn; |
| import org.eclipse.swt.widgets.TableItem; |
| import org.eclipse.swt.widgets.Text; |
| |
| /** |
| * Customized combobox control that supports editing directly in the text field |
| * and selecting items from the dropdown. |
| * |
| * <p> |
| * This control supports the ability for multi select in the dropdown of the |
| * combo which is not available for the SWT Combo control. This feature was |
| * added with Nebula NatTable 1.0.0 |
| * |
| * <p> |
| * The following style bits are supported by this control. |
| * |
| * @see SWT#BORDER (if a border should be added to the Text control) |
| * @see SWT#READ_ONLY (default for Text control, if this is missing, the Text |
| * control can be edited) |
| * @see SWT#CHECK (if the items in the combo should be showed with checkboxes) |
| * @see SWT#MULTI (if multi selection is allowed) |
| */ |
| public class NatCombo extends Composite { |
| |
| /** |
| * Default String that is used to separate values in the String |
| * representation showed in the text control if multiselect is supported. |
| */ |
| public static final String DEFAULT_MULTI_SELECT_VALUE_SEPARATOR = ", "; //$NON-NLS-1$ |
| /** |
| * Default String that is used to prefix the generated String representation |
| * showed in the text control if multiselect is supported. |
| */ |
| public static final String DEFAULT_MULTI_SELECT_PREFIX = "["; //$NON-NLS-1$ |
| /** |
| * String that is used to suffix the generated String representation showed |
| * in the text control if multiselect is supported. |
| */ |
| public static final String DEFAULT_MULTI_SELECT_SUFFIX = "]"; //$NON-NLS-1$ |
| /** |
| * The default number of visible items on open the combo. |
| */ |
| public static final int DEFAULT_NUM_OF_VISIBLE_ITEMS = 5; |
| |
| /** |
| * The IStyle that is used for rendering the Text and the combo control. The |
| * important configurations used are horizontal alignment, background and |
| * foreground color and font. |
| */ |
| protected final IStyle cellStyle; |
| |
| /** |
| * The maximum number of visible items of the combo. Setting this value to |
| * -1 will result in always showing all items at once. |
| */ |
| protected int maxVisibleItems; |
| |
| /** |
| * The items that are showed within the combo transformed to a |
| * java.util.List. Needed for indexed operations in the dropdown |
| */ |
| protected java.util.List<String> itemList; |
| |
| /** |
| * Map used to hold the selection state of items in the drop. Needed to |
| * maintain state when filtering |
| * |
| * @since 1.4 |
| */ |
| protected Map<String, Boolean> selectionStateMap; |
| |
| /** |
| * The text control allowing filtering of options |
| * |
| * @since 1.4 |
| */ |
| protected Text filterBox; |
| |
| /** |
| * The text control of this NatCombo, allowing to enter values directly. |
| */ |
| protected Text text; |
| |
| /** |
| * The Shell containing the dropdown of this NatCombo |
| */ |
| protected Shell dropdownShell; |
| |
| /** |
| * The Table control used for the combo component of this NatCombo |
| */ |
| protected Table dropdownTable; |
| |
| /** |
| * The Table control used for the combo component of this NatCombo |
| * |
| * @since 1.4 |
| */ |
| protected TableViewer dropdownTableViewer; |
| |
| /** |
| * The image that is shown at the right edge of the text control if the |
| * NatCombo is opened. |
| */ |
| protected Image iconImage; |
| |
| /** |
| * The style bits that where set on creation time. Needed in case the |
| * dropdown shell was disposed and needs to be created again. |
| * |
| * @since 2.0 - Renamed from style to widgetStyle to avoid possible |
| * confusions with Widget#style. |
| */ |
| protected final int widgetStyle; |
| |
| /** |
| * Flag that indicated whether this NatCombo supports filtering of the |
| * values in the dropdown control |
| * |
| * @since 1.4 |
| */ |
| protected boolean showDropdownFilter; |
| |
| /** |
| * 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; |
| |
| /** |
| * Flag that indicates whether this NatCombo supports multiselect or not. By |
| * default multiselect is disabled. |
| */ |
| protected boolean multiselect; |
| |
| /** |
| * Flag that indicates whether checkboxes should be shown for the items in |
| * the dropdown. |
| */ |
| protected boolean useCheckbox; |
| |
| /** |
| * String that is used to separate values in the String representation |
| * showed in the text control if multiselect is supported. |
| */ |
| protected String multiselectValueSeparator = DEFAULT_MULTI_SELECT_VALUE_SEPARATOR; |
| /** |
| * 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. |
| */ |
| protected String multiselectTextPrefix = DEFAULT_MULTI_SELECT_PREFIX; |
| /** |
| * 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. |
| */ |
| protected String multiselectTextSuffix = DEFAULT_MULTI_SELECT_SUFFIX; |
| |
| /** |
| * Flag that tells whether the NatCombo has focus or not. The flag is set by |
| * the FocusListenerWrapper that is set as focus listener on both, the Text |
| * control and the dropdown table control. This flag is necessary as the |
| * NatCombo has focus if either of both controls have focus. |
| */ |
| private boolean hasFocus = false; |
| /** |
| * The list of FocusListener that contains the listeners that will be |
| * informed if the NatCombo control gains or looses focus. We keep our own |
| * list of listeners because the two controls that are combined in this |
| * control share the same focus. |
| */ |
| private List<FocusListener> focusListener = new ArrayList<FocusListener>(); |
| |
| /** |
| * List of KeyListener that should be added to the dropdown table once it is |
| * created. Kept locally because the table creation is deferred to the first |
| * access. |
| */ |
| private List<KeyListener> keyListener = new ArrayList<KeyListener>(); |
| /** |
| * List of TraverseListener that should be added to the dropdown table once |
| * it is created. Kept locally because the table creation is deferred to the |
| * first access. |
| */ |
| private List<TraverseListener> traverseListener = new ArrayList<TraverseListener>(); |
| /** |
| * List of MouseListener that should be added to the dropdown table once it |
| * is created. Kept locally because the table creation is deferred to the |
| * first access. |
| */ |
| private List<MouseListener> mouseListener = new ArrayList<MouseListener>(); |
| /** |
| * List of SelectionListener that should be added to the dropdown table once |
| * it is created. Kept locally because the table creation is deferred to the |
| * first access. |
| */ |
| private List<SelectionListener> selectionListener = new ArrayList<SelectionListener>(); |
| /** |
| * List of ShellListener that should be added to the dropdown table once it |
| * is created. Kept locally because the table creation is deferred to the |
| * first access. |
| */ |
| private List<ShellListener> shellListener = new ArrayList<ShellListener>(); |
| |
| /** |
| * Creates a new NatCombo using the given IStyle for rendering, showing the |
| * default number of items at once in the dropdown. Creating the NatCombo |
| * with this constructor, there is no free edit and no multiple selection |
| * enabled. |
| * |
| * @param parent |
| * A widget that will be the parent of this NatCombo |
| * @param cellStyle |
| * Style configuration containing horizontal alignment, font, |
| * foreground and background color information. |
| * @param style |
| * The style for the Text Control to construct. Uses this style |
| * adding internal styles via ConfigRegistry. |
| */ |
| public NatCombo(Composite parent, IStyle cellStyle, int style) { |
| this(parent, cellStyle, DEFAULT_NUM_OF_VISIBLE_ITEMS, style, GUIHelper.getImage("down_2")); //$NON-NLS-1$ |
| } |
| |
| /** |
| * Creates a new NatCombo using the given IStyle for rendering, showing the |
| * given amount of items at once in the dropdown. Creating the NatCombo with |
| * this constructor, there is no free edit and no multiple selection |
| * enabled. |
| * |
| * @param parent |
| * A widget that will be the parent of this NatCombo |
| * @param cellStyle |
| * Style configuration containing horizontal alignment, font, |
| * foreground and background color information. |
| * @param maxVisibleItems |
| * the max number of items the drop down will show before |
| * introducing a scroll bar. |
| * @param style |
| * The style for the Text Control to construct. Uses this style |
| * adding internal styles via ConfigRegistry. |
| */ |
| public NatCombo(Composite parent, IStyle cellStyle, int maxVisibleItems, int style) { |
| this(parent, cellStyle, maxVisibleItems, style, GUIHelper.getImage("down_2")); //$NON-NLS-1$ |
| } |
| |
| /** |
| * Creates a new NatCombo using the given IStyle for rendering, showing the |
| * given amount of items at once in the dropdown. Creating the NatCombo with |
| * this constructor, there is no free edit and no multiple selection |
| * enabled. |
| * |
| * @param parent |
| * A widget that will be the parent of this NatCombo |
| * @param cellStyle |
| * Style configuration containing horizontal alignment, font, |
| * foreground and background color information. |
| * @param maxVisibleItems |
| * the max number of items the drop down will show before |
| * introducing a scroll bar. |
| * @param style |
| * The style for the Text Control to construct. Uses this style |
| * adding internal styles via ConfigRegistry. |
| * |
| * @param showDropdownFilter |
| * Flag indicating whether the dropdown filter is displayed |
| * |
| * @since 1.4 |
| */ |
| public NatCombo(Composite parent, IStyle cellStyle, int maxVisibleItems, int style, boolean showDropdownFilter) { |
| this(parent, cellStyle, maxVisibleItems, style, GUIHelper.getImage("down_2"), showDropdownFilter); //$NON-NLS-1$ |
| } |
| |
| /** |
| * Creates a new NatCombo using the given IStyle for rendering, showing the |
| * given amount of items at once in the dropdown. |
| * |
| * @param parent |
| * A widget that will be the parent of this NatCombo |
| * @param cellStyle |
| * Style configuration containing horizontal alignment, font, |
| * foreground and background color information. |
| * @param maxVisibleItems |
| * the max number of items the drop down will show before |
| * introducing a scroll bar. |
| * @param style |
| * The style for the {@link Text} Control to construct. Uses this |
| * style adding internal styles via ConfigRegistry. |
| * @param iconImage |
| * The image to use as overlay to the {@link Text} Control if the |
| * dropdown is visible. Using this image will indicate that the |
| * control is an open combo to the user. |
| */ |
| public NatCombo(Composite parent, IStyle cellStyle, int maxVisibleItems, int style, Image iconImage) { |
| this(parent, cellStyle, maxVisibleItems, style, GUIHelper.getImage("down_2"), false); //$NON-NLS-1$ |
| } |
| |
| /** |
| * Creates a new NatCombo using the given IStyle for rendering, showing the |
| * given amount of items at once in the dropdown. |
| * |
| * @param parent |
| * A widget that will be the parent of this NatCombo |
| * @param cellStyle |
| * Style configuration containing horizontal alignment, font, |
| * foreground and background color information. |
| * @param maxVisibleItems |
| * the max number of items the drop down will show before |
| * introducing a scroll bar. |
| * @param style |
| * The style for the {@link Text} Control to construct. Uses this |
| * style adding internal styles via ConfigRegistry. |
| * @param iconImage |
| * The image to use as overlay to the {@link Text} Control if the |
| * dropdown is visible. Using this image will indicate that the |
| * control is an open combo to the user. |
| * |
| * @param showDropdownFilter |
| * Flag indicating whether the dropdown filter is displayed |
| * |
| * @since 1.4 |
| */ |
| public NatCombo(Composite parent, IStyle cellStyle, int maxVisibleItems, int style, Image iconImage, boolean showDropdownFilter) { |
| super(parent, SWT.NONE); |
| |
| this.cellStyle = cellStyle; |
| this.maxVisibleItems = maxVisibleItems; |
| this.iconImage = iconImage; |
| |
| this.widgetStyle = style; |
| |
| this.showDropdownFilter = showDropdownFilter; |
| this.freeEdit = (style & SWT.READ_ONLY) == 0; |
| this.multiselect = (style & SWT.MULTI) != 0; |
| this.useCheckbox = (style & SWT.CHECK) != 0; |
| |
| GridLayout gridLayout = new GridLayout(2, false); |
| gridLayout.marginWidth = 0; |
| gridLayout.marginHeight = 0; |
| gridLayout.horizontalSpacing = 0; |
| setLayout(gridLayout); |
| |
| createTextControl(style); |
| |
| // typically the dropdown shell should be hidden when the focus is lost |
| // but in case the NatCombo is the first control in a shell, the text |
| // control will get the focus immediately after the shell lost focus. |
| // as handling with focus listeners in such a case fails, we add a move |
| // listener that will update the position of the dropdown shell if the |
| // parent shell moves |
| final Listener moveListener = new Listener() { |
| @Override |
| public void handleEvent(Event event) { |
| calculateBounds(); |
| } |
| }; |
| getShell().addListener(SWT.Move, moveListener); |
| |
| addDisposeListener(new DisposeListener() { |
| @Override |
| public void widgetDisposed(DisposeEvent e) { |
| if (NatCombo.this.dropdownShell != null) { |
| NatCombo.this.dropdownShell.dispose(); |
| } |
| NatCombo.this.text.dispose(); |
| NatCombo.this.getShell().removeListener(SWT.Move, moveListener); |
| } |
| }); |
| } |
| |
| /** |
| * Sets the given items to be the items shown in the dropdown of this |
| * NatCombo. |
| * |
| * @param items |
| * The array of items to set. |
| */ |
| public void setItems(String[] items) { |
| if (items != null) { |
| this.itemList = Arrays.asList(items); |
| this.selectionStateMap = new LinkedHashMap<String, Boolean>(); |
| for (String item : items) { |
| this.selectionStateMap.put(item, Boolean.FALSE); |
| } |
| if (this.dropdownTable != null && !this.dropdownTable.isDisposed()) { |
| this.dropdownTableViewer.setInput(items); |
| } |
| } |
| } |
| |
| /** |
| * Creates the Text control of this NatCombo, adding styles, look&feel |
| * and needed listeners for the control only. |
| * |
| * @param style |
| * The style for the Text Control to construct. Uses this style |
| * adding internal styles via ConfigRegistry. |
| */ |
| protected void createTextControl(int style) { |
| int textStyle = style | HorizontalAlignmentEnum.getSWTStyle(this.cellStyle); |
| this.text = new Text(this, textStyle); |
| this.text.setBackground(this.cellStyle.getAttributeValue(CellStyleAttributes.BACKGROUND_COLOR)); |
| this.text.setForeground(this.cellStyle.getAttributeValue(CellStyleAttributes.FOREGROUND_COLOR)); |
| this.text.setFont(this.cellStyle.getAttributeValue(CellStyleAttributes.FONT)); |
| |
| GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true); |
| this.text.setLayoutData(gridData); |
| |
| this.text.addKeyListener(new KeyAdapter() { |
| @Override |
| public void keyPressed(KeyEvent event) { |
| if (event.keyCode == SWT.ARROW_DOWN |
| || event.keyCode == SWT.ARROW_UP) { |
| showDropdownControl(); |
| |
| int selectionIndex = getDropdownTable().getSelectionIndex(); |
| if (selectionIndex < 0) { |
| select(0); |
| } else { |
| // only visualize the selection in the dropdown, do not |
| // perform a selection |
| getDropdownTable().select(selectionIndex); |
| } |
| |
| // ensure the arrow key events do not have any further |
| // effect |
| event.doit = false; |
| } else if (!LetterOrDigitKeyEventMatcher.isLetterOrDigit(event.character)) { |
| if (NatCombo.this.freeEdit) { |
| // simply clear the selection in dropdownlist so the |
| // free value in text control will be used |
| if (!getDropdownTable().isDisposed()) { |
| getDropdownTable().deselectAll(); |
| for (Map.Entry<String, Boolean> entry : NatCombo.this.selectionStateMap.entrySet()) { |
| entry.setValue(Boolean.FALSE); |
| } |
| } |
| } else { |
| showDropdownControl(); |
| } |
| } |
| } |
| }); |
| |
| this.text.addMouseListener(new MouseAdapter() { |
| |
| @Override |
| public void mouseDown(MouseEvent e) { |
| if (!NatCombo.this.freeEdit) { |
| if (getDropdownTable().isDisposed() |
| || !getDropdownTable().isVisible()) { |
| showDropdownControl(); |
| } else { |
| // if there is no free edit enabled, set the focus back |
| // to the dropdownlist so it handles key strokes itself |
| getDropdownTable().forceFocus(); |
| } |
| } |
| } |
| }); |
| |
| this.text.addControlListener(new ControlListener() { |
| @Override |
| public void controlResized(ControlEvent e) { |
| calculateBounds(); |
| } |
| |
| @Override |
| public void controlMoved(ControlEvent e) { |
| calculateBounds(); |
| } |
| }); |
| |
| this.text.addFocusListener(new FocusListenerWrapper()); |
| |
| final Canvas iconCanvas = new Canvas(this, SWT.NONE) { |
| |
| @Override |
| public Point computeSize(int wHint, int hHint, boolean changed) { |
| Rectangle iconImageBounds = NatCombo.this.iconImage.getBounds(); |
| return new Point(iconImageBounds.width + 2, iconImageBounds.height + 2); |
| } |
| |
| }; |
| |
| gridData = new GridData(GridData.BEGINNING, SWT.FILL, false, true); |
| iconCanvas.setLayoutData(gridData); |
| |
| iconCanvas.addPaintListener(new PaintListener() { |
| |
| @Override |
| public void paintControl(PaintEvent event) { |
| GC gc = event.gc; |
| |
| Rectangle iconCanvasBounds = iconCanvas.getBounds(); |
| Rectangle iconImageBounds = NatCombo.this.iconImage.getBounds(); |
| int horizontalAlignmentPadding = |
| CellStyleUtil.getHorizontalAlignmentPadding( |
| HorizontalAlignmentEnum.CENTER, iconCanvasBounds, iconImageBounds.width); |
| int verticalAlignmentPadding = |
| CellStyleUtil.getVerticalAlignmentPadding( |
| VerticalAlignmentEnum.MIDDLE, iconCanvasBounds, iconImageBounds.height); |
| gc.drawImage(NatCombo.this.iconImage, horizontalAlignmentPadding, verticalAlignmentPadding); |
| |
| Color originalFg = gc.getForeground(); |
| gc.setForeground(GUIHelper.COLOR_WIDGET_BORDER); |
| gc.drawRectangle(0, 0, iconCanvasBounds.width - 1, iconCanvasBounds.height - 1); |
| gc.setForeground(originalFg); |
| } |
| |
| }); |
| |
| iconCanvas.addMouseListener(new MouseAdapter() { |
| |
| @Override |
| public void mouseDown(MouseEvent e) { |
| if (NatCombo.this.dropdownShell != null && !NatCombo.this.dropdownShell.isDisposed()) { |
| if (NatCombo.this.dropdownShell.isVisible()) { |
| NatCombo.this.text.forceFocus(); |
| hideDropdownControl(); |
| } else { |
| showDropdownControl(); |
| } |
| } else { |
| showDropdownControl(); |
| } |
| } |
| }); |
| } |
| |
| /** |
| * Create the dropdown control of this NatCombo, adding styles, |
| * look&feel and needed listeners for the control only. |
| * |
| * @param style |
| * The style for the Table Control to construct. Uses this style |
| * adding internal styles via ConfigRegistry. |
| */ |
| protected void createDropdownControl(int style) { |
| this.dropdownShell = new Shell(getShell(), SWT.MODELESS); |
| |
| // (SWT.V_SCROLL | SWT.NO_SCROLL) prevents appearance of unnecessary |
| // horizontal scrollbar on mac |
| // see: https://bugs.eclipse.org/bugs/show_bug.cgi?id=304128 |
| int scrollStyle = ((this.itemList != null && this.itemList.size() > this.maxVisibleItems) |
| && this.maxVisibleItems > 0) ? (SWT.V_SCROLL | SWT.NO_SCROLL) : SWT.NO_SCROLL; |
| int dropdownListStyle = style |
| | scrollStyle |
| | HorizontalAlignmentEnum.getSWTStyle(this.cellStyle) |
| | SWT.FULL_SELECTION; |
| |
| this.dropdownTable = new Table(this.dropdownShell, dropdownListStyle); |
| this.dropdownTableViewer = new TableViewer(this.dropdownTable); |
| this.dropdownTable.setBackground( |
| this.cellStyle.getAttributeValue(CellStyleAttributes.BACKGROUND_COLOR)); |
| this.dropdownTable.setForeground( |
| this.cellStyle.getAttributeValue(CellStyleAttributes.FOREGROUND_COLOR)); |
| this.dropdownTable.setFont( |
| this.cellStyle.getAttributeValue(CellStyleAttributes.FONT)); |
| |
| // add a column to be able to resize the item width in the dropdown |
| new TableColumn(this.dropdownTable, SWT.NONE); |
| |
| this.dropdownTableViewer.setContentProvider(ArrayContentProvider.getInstance()); |
| |
| this.dropdownTableViewer.setLabelProvider(new LabelProvider() { |
| |
| @Override |
| public boolean isLabelProperty(Object element, String property) { |
| return false; |
| } |
| }); |
| |
| this.dropdownTable.addKeyListener(new KeyAdapter() { |
| @Override |
| public void keyPressed(KeyEvent event) { |
| if (event.keyCode == SWT.ARROW_DOWN) { |
| int selected = NatCombo.this.dropdownTable.getSelectionIndex(); |
| if (selected < 0) { |
| // no selection before, select the first entry |
| if (!NatCombo.this.useCheckbox) { |
| select(0); |
| } else { |
| getDropdownTable().select(0); |
| } |
| event.doit = false; |
| } |
| } |
| } |
| }); |
| |
| this.dropdownTable.addSelectionListener(new SelectionAdapter() { |
| @Override |
| public void widgetSelected(SelectionEvent e) { |
| boolean selected = e.detail != SWT.CHECK; |
| boolean isCtrlPressed = (e.stateMask & SWT.MODIFIER_MASK) == SWT.CTRL; |
| TableItem chosenItem = (TableItem) e.item; |
| |
| // Given the ability to filter we need to find the item's |
| // table index which may not match the index in the itemList |
| int itemTableIndex = NatCombo.this.dropdownTable.indexOf(chosenItem); |
| |
| // This case handles check actions |
| if (!selected) { |
| if (!chosenItem.getChecked()) { |
| NatCombo.this.selectionStateMap.put(chosenItem.getText(), Boolean.FALSE); |
| } else { |
| NatCombo.this.selectionStateMap.put(chosenItem.getText(), Boolean.TRUE); |
| } |
| } else if (!NatCombo.this.useCheckbox) { |
| if (NatCombo.this.multiselect && isCtrlPressed) { |
| boolean isSelected = NatCombo.this.dropdownTable.isSelected(itemTableIndex); |
| NatCombo.this.selectionStateMap.put(chosenItem.getText(), isSelected); |
| if (NatCombo.this.useCheckbox) { |
| chosenItem.setChecked(isSelected); |
| } |
| } else { |
| // A single item was selected. Clear all previous state |
| for (String item : NatCombo.this.itemList) { |
| NatCombo.this.selectionStateMap.put(item, Boolean.FALSE); |
| } |
| |
| // Set the state for the selected item |
| NatCombo.this.selectionStateMap.put(chosenItem.getText(), Boolean.TRUE); |
| } |
| } |
| |
| updateTextControl(false); |
| } |
| }); |
| |
| this.dropdownTable.addKeyListener(new KeyAdapter() { |
| @Override |
| public void keyPressed(KeyEvent event) { |
| if ((event.keyCode == SWT.CR) |
| || (event.keyCode == SWT.KEYPAD_CR)) { |
| updateTextControl(true); |
| } else if (event.keyCode == SWT.F2 && NatCombo.this.freeEdit) { |
| NatCombo.this.text.forceFocus(); |
| hideDropdownControl(); |
| } |
| } |
| }); |
| |
| this.dropdownTable.addFocusListener(new FocusListenerWrapper()); |
| |
| FormLayout layout = new FormLayout(); |
| layout.spacing = 0; |
| layout.marginHeight = 0; |
| layout.marginWidth = 0; |
| this.dropdownShell.setLayout(layout); |
| |
| FormData dropDownLayoutData = new FormData(); |
| dropDownLayoutData.left = new FormAttachment(0); |
| dropDownLayoutData.right = new FormAttachment(100); |
| dropDownLayoutData.bottom = new FormAttachment(100); |
| |
| if (this.showDropdownFilter) { |
| this.filterBox = new Text(this.dropdownShell, SWT.BORDER); |
| this.filterBox.setFont(this.cellStyle.getAttributeValue(CellStyleAttributes.FONT)); |
| this.filterBox.setEnabled(true); |
| this.filterBox.setEditable(true); |
| this.filterBox.addFocusListener(new FocusListenerWrapper()); |
| this.filterBox.addKeyListener(new KeyAdapter() { |
| |
| @Override |
| public void keyReleased(KeyEvent e) { |
| if (null != NatCombo.this.dropdownTableViewer && !NatCombo.this.dropdownTable.isDisposed()) { |
| NatCombo.this.dropdownTableViewer.refresh(); |
| calculateBounds(); |
| setDropdownSelection(getTextAsArray()); |
| } |
| } |
| }); |
| |
| FormData data = new FormData(); |
| data.top = new FormAttachment(0); |
| data.left = new FormAttachment(0); |
| data.right = new FormAttachment(100); |
| this.filterBox.setLayoutData(data); |
| |
| data = new FormData(); |
| dropDownLayoutData.top = new FormAttachment(this.filterBox, 0, SWT.BOTTOM); |
| this.dropdownTable.setLayoutData(dropDownLayoutData); |
| |
| ViewerFilter viewerFilter = new ViewerFilter() { |
| |
| @Override |
| public boolean select(Viewer viewer, Object parentElement, Object element) { |
| if (null != element && element instanceof String) { |
| return ((String) element).toLowerCase().contains(NatCombo.this.filterBox.getText().toLowerCase()); |
| } |
| return false; |
| } |
| }; |
| this.dropdownTableViewer.addFilter(viewerFilter); |
| } else { |
| dropDownLayoutData.top = new FormAttachment(0); |
| this.dropdownTable.setLayoutData(dropDownLayoutData); |
| } |
| |
| if (this.itemList != null) { |
| setItems(this.itemList.toArray(new String[] {})); |
| } |
| |
| // apply the listeners that were registered before the creation of the |
| // dropdown control |
| applyDropdownListener(); |
| |
| setDropdownSelection(getTextAsArray()); |
| } |
| |
| /** |
| * This method will be called if an item of the dropdown control is selected |
| * via mouse click or pressing enter. It will populate the text control with |
| * the information gathered out of the selection in the dropdown control and |
| * hide the dropdown if necessary. |
| * |
| * @param hideDropdown |
| * <code>true</code> if the dropdown should be hidden after |
| * updating the text control |
| */ |
| protected void updateTextControl(boolean hideDropdown) { |
| this.text.setText(getTransformedTextForSelection()); |
| if (hideDropdown) { |
| hideDropdownControl(); |
| } |
| } |
| |
| /** |
| * Shows the dropdown of this NatCombo. Will always calculate the size of |
| * the dropdown regarding the current size of the Text control. |
| */ |
| public void showDropdownControl() { |
| showDropdownControl(false); |
| } |
| |
| /** |
| * Shows the dropdown of this NatCombo. Will always calculate the size of |
| * the dropdown regarding the current size of the Text control. |
| * |
| * @param focusOnText |
| * <code>true</code> if the focus should be set to the text |
| * control instead of the dropdown after opening the dropdown. |
| */ |
| public void showDropdownControl(boolean focusOnText) { |
| if (this.dropdownShell == null || this.dropdownShell.isDisposed()) { |
| createDropdownControl(this.widgetStyle); |
| } |
| calculateBounds(); |
| this.dropdownShell.open(); |
| if (focusOnText) { |
| this.text.forceFocus(); |
| this.text.setSelection(this.text.getText().length()); |
| } |
| } |
| |
| /** |
| * @return The {@link Table} control that is used in the dropdown. Will be |
| * created if it does not exist yet. |
| * @since 1.5 |
| */ |
| protected Table getDropdownTable() { |
| if (this.dropdownTable == null) { |
| createDropdownControl(this.widgetStyle); |
| } |
| return this.dropdownTable; |
| } |
| |
| /** |
| * Hide the dropdown of this NatCombo. |
| */ |
| public void hideDropdownControl() { |
| if (this.dropdownShell != null && !this.dropdownShell.isDisposed()) { |
| this.dropdownShell.setVisible(false); |
| } |
| } |
| |
| /** |
| * Calculates the number of items that should be showed in the dropdown at |
| * once. It is needed to calculate the height of the dropdown. If |
| * maxVisibleItems is configured -1, this method always returns the number |
| * of items in the list. Otherwise if will return the configured maximum |
| * number of items to be visible at once or less if there are less than the |
| * configured maximum. |
| * |
| * @return the number of items that should be showed in the dropdown at |
| * once. |
| */ |
| protected int getVisibleItemCount() { |
| int itemCount = getDropdownTable().getItemCount(); |
| if (itemCount > 0) { |
| // if maxVisibleItems == -1 show all items at once |
| // otherwise use the minimum for item count or max visible item |
| // configuration |
| int visibleItemCount = itemCount; |
| if (this.maxVisibleItems > 0) { |
| visibleItemCount = Math.min(itemCount, this.maxVisibleItems); |
| } |
| itemCount = visibleItemCount; |
| } |
| return itemCount; |
| } |
| |
| /** |
| * Calculates the size and location of the Shell that represents the |
| * dropdown control of this NatCombo. Size and location will be calculated |
| * dependent the position and size of the corresponding Text control and the |
| * information showed in the dropdown. |
| */ |
| protected void calculateBounds() { |
| if (this.dropdownShell != null && !this.dropdownShell.isDisposed()) { |
| Point size = getSize(); |
| // calculate the height by multiplying the number of visible items |
| // with the item height of items in the list and adding 2*grid line |
| // width to work around a calculation error regarding the descent of |
| // the font metrics for the last shown item |
| // Note: if there are no items to show in the combo, calculate with |
| // the item count of 3 so an empty combo will open |
| int listHeight = (getVisibleItemCount() > 0 ? getVisibleItemCount() : 3) |
| * this.dropdownTable.getItemHeight() |
| + this.dropdownTable.getGridLineWidth() * 2; |
| |
| // since introduced the TableColumn for real full row selection, we |
| // call pack() to perform autoresize to ensure the width shows the |
| // whole content |
| this.dropdownTable.getColumn(0).pack(); |
| int listWidth = Math.max( |
| this.dropdownTable.computeSize(SWT.DEFAULT, listHeight, true).x, size.x); |
| |
| Point textPosition = this.text.toDisplay(this.text.getLocation()); |
| |
| // by default the dropdown shell will be created below the cell in |
| // the table |
| int dropdownShellStartingY = textPosition.y + this.text.getBounds().height; |
| |
| int textBottomY = textPosition.y + this.text.getBounds().height + listHeight; |
| // if the bottom of the drowdown is below the display, render it |
| // above the cell |
| if (textBottomY > Display.getCurrent().getBounds().height) { |
| dropdownShellStartingY = textPosition.y - listHeight; |
| } |
| |
| Rectangle parentBounds = getParent().getBounds(); |
| Point parentStart = getParent().toDisplay(parentBounds.x, parentBounds.y); |
| int parentBottomY = parentStart.y + parentBounds.height - parentBounds.y; |
| if (getParent().getHorizontalBar() != null && getParent().getHorizontalBar().isVisible()) { |
| parentBottomY -= getParent().getHorizontalBar().getSize().y; |
| } |
| if (dropdownShellStartingY > parentBottomY) { |
| dropdownShellStartingY = parentBottomY; |
| } |
| |
| int filterTextBoxHeight = this.showDropdownFilter ? this.filterBox.computeSize(SWT.DEFAULT, SWT.DEFAULT).y : 0; |
| Rectangle shellBounds = new Rectangle( |
| textPosition.x, |
| dropdownShellStartingY, |
| listWidth + (this.dropdownTable.getGridLineWidth() * 2), |
| listHeight + filterTextBoxHeight); |
| |
| this.dropdownShell.setBounds(shellBounds); |
| |
| calculateColumnWidth(); |
| } |
| } |
| |
| /** |
| * Calculates and applies the column width to ensure that the column has the |
| * same width as the table itself, so selection is possible for the whole |
| * row. |
| */ |
| protected void calculateColumnWidth() { |
| int width = this.dropdownTable.getBounds().width; |
| // only reduce if a scrollbar is available and visible |
| if (this.dropdownTable.getVerticalBar() != null |
| && this.dropdownTable.getItemCount() > this.maxVisibleItems |
| && this.maxVisibleItems > 0) { |
| width -= this.dropdownTable.getVerticalBar().getSize().x; |
| } |
| this.dropdownTable.getColumn(0).setWidth(width); |
| } |
| |
| /** |
| * Returns the zero-relative index of the item which is currently selected |
| * in the receiver, or -1 if no item is selected. |
| * <p> |
| * Note that this only returns useful results if this NatCombo supports |
| * single selection or only one item is selected. |
| * |
| * @return the index of the selected item or -1 |
| */ |
| public int getSelectionIndex() { |
| if (this.selectionStateMap != null) { |
| for (String item : this.selectionStateMap.keySet()) { |
| if (this.selectionStateMap.get(item)) { |
| return this.itemList.indexOf(item); |
| } |
| } |
| } else if (!this.text.isDisposed()) { |
| return this.itemList.indexOf(this.text.getText()); |
| } |
| return -1; |
| } |
| |
| /** |
| * Returns the zero-relative indices of the items which are currently |
| * selected in the receiver. The order of the indices is unspecified. The |
| * array is empty if no items are selected. |
| * <p> |
| * Note: This is not the actual structure used by the receiver to maintain |
| * its selection, so modifying the array will not affect the receiver. |
| * </p> |
| * |
| * @return the array of indices of the selected items |
| */ |
| public int[] getSelectionIndices() { |
| if (this.selectionStateMap != null) { |
| List<Integer> selectedIndices = new ArrayList<Integer>(); |
| for (String item : this.selectionStateMap.keySet()) { |
| if (this.selectionStateMap.get(item)) { |
| selectedIndices.add(this.itemList.indexOf(item)); |
| } |
| } |
| int[] indices = new int[selectedIndices.size()]; |
| for (int i = 0; i < selectedIndices.size(); i++) { |
| indices[i] = selectedIndices.get(i); |
| } |
| return indices; |
| } else { |
| String[] selectedItems = getTextAsArray(); |
| int[] result = new int[selectedItems.length]; |
| for (int i = 0; i < selectedItems.length; i++) { |
| result[i] = this.itemList.indexOf(selectedItems[i]); |
| } |
| return result; |
| } |
| } |
| |
| /** |
| * Returns the number of selected items contained in the receiver. |
| * |
| * @return the number of selected items |
| */ |
| public int getSelectionCount() { |
| if (this.selectionStateMap != null) { |
| List<Integer> selectedIndices = new ArrayList<Integer>(); |
| for (String item : this.selectionStateMap.keySet()) { |
| if (this.selectionStateMap.get(item)) { |
| selectedIndices.add(this.itemList.indexOf(item)); |
| } |
| } |
| return selectedIndices.size(); |
| } else { |
| return getTextAsArray().length; |
| } |
| } |
| |
| /** |
| * Returns an array of <code>String</code>s that are currently selected in |
| * the receiver. The order of the items is unspecified. An empty array |
| * indicates that no items are selected. |
| * <p> |
| * Note: This is not the actual structure used by the receiver to maintain |
| * its selection, so modifying the array will not affect the receiver. |
| * </p> |
| * |
| * @return an array representing the selection |
| */ |
| public String[] getSelection() { |
| String[] result = getTransformedSelection(); |
| if (result == null |
| || (result.length == 0 && !this.text.isDisposed() && this.text.getText().length() > 0)) { |
| result = getTextAsArray(); |
| } |
| return result; |
| } |
| |
| /** |
| * Selects the items at the given zero-relative indices in the receiver. The |
| * current selection is cleared before the new items are selected. |
| * <p> |
| * 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. |
| * <p> |
| * The text control of this NatCombo will also be updated with the new |
| * selected values. |
| * |
| * @param items |
| * the items to select |
| */ |
| public void setSelection(String[] items) { |
| String textValue = ""; //$NON-NLS-1$ |
| if (items != null) { |
| if (!getDropdownTable().isDisposed()) { |
| setDropdownSelection(items); |
| if (this.freeEdit |
| && getDropdownTable().getSelectionCount() == 0) { |
| textValue = getTransformedText(items); |
| } else { |
| textValue = getTransformedTextForSelection(); |
| } |
| } else { |
| textValue = getTransformedText(items); |
| } |
| } |
| this.text.setText(textValue); |
| |
| if (this.multiselect) { |
| this.text.setSelection(textValue.length() - this.multiselectTextSuffix.length()); |
| } |
| } |
| |
| /** |
| * 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 |
| */ |
| public void select(int index) { |
| if (!getDropdownTable().isDisposed()) { |
| getDropdownTable().select(index); |
| for (int i = 0; i < this.itemList.size(); i++) { |
| this.selectionStateMap.put(this.itemList.get(i), i == index); |
| } |
| this.text.setText(getTransformedTextForSelection()); |
| } else if (index >= 0) { |
| this.text.setText(this.itemList.get(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 |
| */ |
| public void select(int[] indices) { |
| if (!getDropdownTable().isDisposed()) { |
| getDropdownTable().select(indices); |
| List<Integer> indicesList = ArrayUtil.asIntegerList(indices); |
| for (int i = 0; i < this.itemList.size(); i++) { |
| this.selectionStateMap.put(this.itemList.get(i), indicesList.contains(i)); |
| } |
| this.text.setText(getTransformedTextForSelection()); |
| } else { |
| String[] selectedItems = new String[indices.length]; |
| for (int i = 0; i < indices.length; i++) { |
| if (indices[i] >= 0) { |
| selectedItems[i] = this.itemList.get(indices[i]); |
| } |
| } |
| this.text.setText(getTransformedText(selectedItems)); |
| } |
| } |
| |
| /** |
| * @since 1.5 |
| */ |
| protected void applyDropdownListener() { |
| for (KeyListener l : this.keyListener) { |
| if (this.dropdownTable != null && !this.dropdownTable.isDisposed()) { |
| this.dropdownTable.addKeyListener(l); |
| } |
| } |
| for (TraverseListener l : this.traverseListener) { |
| if (this.dropdownTable != null && !this.dropdownTable.isDisposed()) { |
| this.dropdownTable.addTraverseListener(l); |
| } |
| } |
| for (MouseListener l : this.mouseListener) { |
| if (this.dropdownTable != null && !this.dropdownTable.isDisposed()) { |
| this.dropdownTable.addMouseListener(l); |
| } |
| } |
| for (SelectionListener l : this.selectionListener) { |
| if (this.dropdownTable != null && !this.dropdownTable.isDisposed()) { |
| this.dropdownTable.addSelectionListener(l); |
| } |
| } |
| for (ShellListener l : this.shellListener) { |
| if (this.dropdownShell != null && !this.dropdownShell.isDisposed()) { |
| this.dropdownShell.addShellListener(l); |
| } |
| } |
| } |
| |
| @Override |
| public void addKeyListener(KeyListener listener) { |
| if (listener != null) { |
| if (this.text != null && !this.text.isDisposed()) { |
| this.text.addKeyListener(listener); |
| } |
| if (this.dropdownTable != null && !this.dropdownTable.isDisposed()) { |
| this.dropdownTable.addKeyListener(listener); |
| } |
| this.keyListener.add(listener); |
| } |
| } |
| |
| @Override |
| public void removeKeyListener(KeyListener listener) { |
| if (this.text != null && !this.text.isDisposed()) { |
| this.text.removeKeyListener(listener); |
| } |
| if (this.dropdownTable != null && !this.dropdownTable.isDisposed()) { |
| this.dropdownTable.removeKeyListener(listener); |
| } |
| this.keyListener.remove(listener); |
| } |
| |
| @Override |
| public void addTraverseListener(TraverseListener listener) { |
| if (listener != null) { |
| if (this.text != null && !this.text.isDisposed()) { |
| this.text.addTraverseListener(listener); |
| } |
| if (this.dropdownTable != null && !this.dropdownTable.isDisposed()) { |
| this.dropdownTable.addTraverseListener(listener); |
| } |
| this.traverseListener.add(listener); |
| } |
| } |
| |
| @Override |
| public void removeTraverseListener(TraverseListener listener) { |
| if (this.text != null && !this.text.isDisposed()) { |
| this.text.removeTraverseListener(listener); |
| } |
| if (this.dropdownTable != null && !this.dropdownTable.isDisposed()) { |
| this.dropdownTable.removeTraverseListener(listener); |
| } |
| this.traverseListener.remove(listener); |
| } |
| |
| @Override |
| public void addMouseListener(MouseListener listener) { |
| // only add the mouse listener to the dropdown, as clicking in the text |
| // control should not trigger anything else than it is handled by the |
| // text control itself. |
| if (listener != null) { |
| if (this.dropdownTable != null && !this.dropdownTable.isDisposed()) { |
| this.dropdownTable.addMouseListener(listener); |
| } |
| this.mouseListener.add(listener); |
| } |
| } |
| |
| @Override |
| public void removeMouseListener(MouseListener listener) { |
| if (this.dropdownTable != null && !this.dropdownTable.isDisposed()) { |
| this.dropdownTable.removeMouseListener(listener); |
| } |
| this.mouseListener.remove(listener); |
| } |
| |
| @Override |
| public void notifyListeners(int eventType, Event event) { |
| if (this.dropdownTable != null && !this.dropdownTable.isDisposed()) { |
| this.dropdownTable.notifyListeners(eventType, event); |
| } |
| } |
| |
| public void addSelectionListener(SelectionListener listener) { |
| if (listener != null) { |
| if (this.dropdownTable != null && !this.dropdownTable.isDisposed()) { |
| this.dropdownTable.addSelectionListener(listener); |
| } |
| this.selectionListener.add(listener); |
| } |
| } |
| |
| public void removeSelectionListener(SelectionListener listener) { |
| if (this.dropdownTable != null && !this.dropdownTable.isDisposed()) { |
| this.dropdownTable.removeSelectionListener(listener); |
| } |
| this.selectionListener.remove(listener); |
| } |
| |
| public void addShellListener(ShellListener listener) { |
| if (listener != null) { |
| if (this.dropdownShell != null && !this.dropdownShell.isDisposed()) { |
| this.dropdownShell.addShellListener(listener); |
| } |
| this.shellListener.add(listener); |
| } |
| } |
| |
| public void removeShellListener(ShellListener listener) { |
| if (this.dropdownShell != null && !this.dropdownShell.isDisposed()) { |
| this.dropdownShell.removeShellListener(listener); |
| } |
| this.shellListener.remove(listener); |
| } |
| |
| public void addTextControlListener(ControlListener listener) { |
| if (listener != null && this.text != null && !this.text.isDisposed()) { |
| this.text.addControlListener(listener); |
| } |
| } |
| |
| public void removeTextControlListener(ControlListener listener) { |
| if (this.text != null && !this.text.isDisposed()) { |
| this.text.removeControlListener(listener); |
| } |
| } |
| |
| @Override |
| public boolean isFocusControl() { |
| return this.hasFocus; |
| } |
| |
| @Override |
| public boolean forceFocus() { |
| return this.text.forceFocus(); |
| } |
| |
| @Override |
| public void addFocusListener(FocusListener listener) { |
| if (listener != null) { |
| this.focusListener.add(listener); |
| } |
| } |
| |
| @Override |
| public void removeFocusListener(final FocusListener listener) { |
| // The FocusListenerWrapper is executing the focusLost event |
| // in a separate thread with 100ms delay to ensure that the NatCombo |
| // lost focus. This is necessary because the NatCombo is a combination |
| // of a text field and a table as dropdown which do not share the |
| // same focus by default. |
| this.focusListener.remove(listener); |
| } |
| |
| /** |
| * Transforms the selection in the Table control dropdown into a String[]. |
| * Doing this is necessary to provide a SWT List like interface regarding |
| * selections for the NatCombo. |
| * |
| * @return Array containing all selected TableItem text attributes |
| */ |
| protected String[] getTransformedSelection() { |
| List<String> selectedItems = new ArrayList<String>(); |
| for (String item : this.selectionStateMap.keySet()) { |
| Boolean isSelected = this.selectionStateMap.get(item); |
| if (isSelected != null && isSelected) { |
| selectedItems.add(item); |
| } |
| } |
| return selectedItems.toArray(new String[selectedItems.size()]); |
| } |
| |
| /** |
| * Transforms the given String array whose contents represents selected |
| * items to a selection that can be handled by the underlying Table control |
| * in the dropdown. |
| * |
| * @param selection |
| * The Strings that represent the selected items |
| */ |
| protected void setDropdownSelection(String[] selection) { |
| java.util.List<String> selectionList = Arrays.asList(selection); |
| java.util.List<TableItem> selectedItems = new ArrayList<TableItem>(); |
| for (TableItem item : getDropdownTable().getItems()) { |
| if (selectionList.contains(EditConstants.SELECT_ALL_ITEMS_VALUE) |
| || selectionList.contains(item.getText())) { |
| this.selectionStateMap.put(item.getText(), Boolean.TRUE); |
| if (this.useCheckbox) { |
| item.setChecked(true); |
| } else { |
| selectedItems.add(item); |
| } |
| } else { |
| this.selectionStateMap.put(item.getText(), Boolean.FALSE); |
| } |
| } |
| getDropdownTable().setSelection(selectedItems.toArray(new TableItem[] {})); |
| } |
| |
| /** |
| * Will transform the text for the Text control of this NatCombo to an array |
| * of Strings. This is necessary for the multiselect feature. |
| * |
| * <p> |
| * Note that by default the multiselect String is specified to show with |
| * enclosing [] brackets and values separated by ", ". If you need to change |
| * this you need to set the corresponding values in this NatCombo. |
| * |
| * @return The text for the Text control of this NatCombo converted to an |
| * array of Strings. |
| */ |
| protected String[] getTextAsArray() { |
| if (!this.text.isDisposed()) { |
| String transform = this.text.getText(); |
| if (transform.length() > 0) { |
| if (this.multiselect) { |
| // for multiselect the String is defined by default in |
| // format [a, b, c] |
| // the prefix and suffix for multiselect String |
| // representation need to be removed |
| // in free edit mode we need to check if the format is used |
| int prefixLength = this.multiselectTextPrefix.length(); |
| int suffixLength = this.multiselectTextSuffix.length(); |
| if (this.freeEdit) { |
| if (!transform.startsWith(this.multiselectTextPrefix)) { |
| prefixLength = 0; |
| } |
| if (!transform.endsWith(this.multiselectTextSuffix)) { |
| suffixLength = 0; |
| } |
| } |
| transform = transform.substring(prefixLength, transform.length() - suffixLength); |
| |
| // if the transform value length is still > 0, try to split |
| if (transform.length() > 0) { |
| return transform.split(this.multiselectValueSeparator); |
| } |
| } |
| return new String[] { transform }; |
| } |
| } |
| return new String[] {}; |
| } |
| |
| /** |
| * Transforms the selection of the dropdown to a text representation that |
| * can be added to the text control of this combo. |
| * |
| * <p> |
| * Note that by default the multiselect String is specified to show with |
| * enclosing [] brackets and values separated by ", ". If you need to change |
| * this you need to set the corresponding values in this NatCombo. |
| * |
| * @return String representation for the selection within the combo. |
| */ |
| protected String getTransformedTextForSelection() { |
| String result = ""; //$NON-NLS-1$ |
| String[] selection = getTransformedSelection(); |
| if (selection != null) { |
| result = getTransformedText(selection); |
| } |
| return result; |
| } |
| |
| /** |
| * Transforms the given array of Strings to a text representation that can |
| * be added to the text control of this combo. |
| * <p> |
| * If this NatCombo is only configured to support single selection, than |
| * only the first value in the array will be processed. Otherwise the result |
| * will be processed by concatenating the values. |
| * <p> |
| * Note that by default the multiselect String is specified to show with |
| * enclosing [] brackets and values separated by ", ". If you need to change |
| * this you need to set the corresponding values in this NatCombo. |
| * |
| * @param values |
| * The values to build the text representation from. |
| * @return String representation for the selection within the combo. |
| */ |
| protected String getTransformedText(String[] values) { |
| String result = ""; //$NON-NLS-1$ |
| if (this.multiselect) { |
| for (int i = 0; i < values.length; i++) { |
| String selection = values[i]; |
| result += selection; |
| if ((i + 1) < values.length) { |
| result += this.multiselectValueSeparator; |
| } |
| } |
| result = this.multiselectTextPrefix + result + this.multiselectTextSuffix; |
| } else if (values.length > 0) { |
| result = values[0]; |
| } |
| return result; |
| } |
| |
| /** |
| * @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) { |
| if (multiselectValueSeparator == null) { |
| this.multiselectValueSeparator = DEFAULT_MULTI_SELECT_VALUE_SEPARATOR; |
| } else { |
| 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) { |
| if (multiselectTextPrefix == null) { |
| this.multiselectTextPrefix = DEFAULT_MULTI_SELECT_PREFIX; |
| } else { |
| this.multiselectTextPrefix = multiselectTextPrefix; |
| } |
| |
| if (multiselectTextSuffix == null) { |
| this.multiselectTextSuffix = DEFAULT_MULTI_SELECT_SUFFIX; |
| } else { |
| this.multiselectTextSuffix = multiselectTextSuffix; |
| } |
| } |
| |
| /** |
| * FocusListener that is used to ensure that the Text control and the |
| * dropdown table control are sharing the same focus. If either of both |
| * controls looses focus, the local focus flag is set to false and a delayed |
| * background thread for focus lost is started. If the other control gains |
| * focus, the local focus flag is set to true which skips the execution of |
| * the delayed background thread. This means the NatCombo hasn't lost focus. |
| * |
| * @since 1.4 |
| */ |
| public class FocusListenerWrapper implements FocusListener { |
| |
| @Override |
| public void focusLost(final FocusEvent e) { |
| NatCombo.this.hasFocus = false; |
| Display.getCurrent().timerExec(100, new Runnable() { |
| @Override |
| public void run() { |
| if (!NatCombo.this.hasFocus) { |
| List<FocusListener> copy = new ArrayList<FocusListener>(NatCombo.this.focusListener); |
| for (FocusListener f : copy) { |
| f.focusLost(e); |
| } |
| } |
| } |
| }); |
| } |
| |
| @Override |
| public void focusGained(FocusEvent e) { |
| NatCombo.this.hasFocus = true; |
| for (FocusListener f : NatCombo.this.focusListener) { |
| f.focusGained(e); |
| } |
| } |
| } |
| |
| } |