| /******************************************************************************* |
| * Copyright (c) 2013, 2020 Dirk Fauth and others. |
| * |
| * This program and the accompanying materials are made |
| * available under the terms of the Eclipse Public License 2.0 |
| * which is available at https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Dirk Fauth <dirk.fauth@googlemail.com> - initial API and implementation |
| * Ryan McHale <rpmc22@gmail.com> - Bug 484716 |
| *******************************************************************************/ |
| package org.eclipse.nebula.widgets.nattable.filterrow.combobox; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.List; |
| |
| import org.eclipse.jface.viewers.CheckboxTableViewer; |
| import org.eclipse.jface.viewers.ICheckStateListener; |
| import org.eclipse.jface.viewers.ICheckStateProvider; |
| import org.eclipse.jface.viewers.ILabelProvider; |
| import org.eclipse.jface.viewers.ILabelProviderListener; |
| import org.eclipse.jface.viewers.IStructuredContentProvider; |
| import org.eclipse.jface.viewers.Viewer; |
| import org.eclipse.nebula.widgets.nattable.Messages; |
| import org.eclipse.nebula.widgets.nattable.style.CellStyleAttributes; |
| import org.eclipse.nebula.widgets.nattable.style.HorizontalAlignmentEnum; |
| import org.eclipse.nebula.widgets.nattable.style.IStyle; |
| import org.eclipse.nebula.widgets.nattable.util.GUIHelper; |
| import org.eclipse.nebula.widgets.nattable.widget.NatCombo; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.events.FocusAdapter; |
| import org.eclipse.swt.events.FocusEvent; |
| import org.eclipse.swt.events.SelectionAdapter; |
| import org.eclipse.swt.events.SelectionEvent; |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.layout.FormAttachment; |
| import org.eclipse.swt.layout.FormData; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.TableColumn; |
| import org.eclipse.swt.widgets.TableItem; |
| import org.eclipse.swt.widgets.Text; |
| |
| /** |
| * Specialisation of NatCombo which doesn't populate the selected values to the |
| * Text control. Instead the String representation of the selected values are |
| * stored in a local member. Doing this the selection is only visible in the |
| * dropdown via selection/check state of the contained items. |
| * <p> |
| * Usually this combo will be created with the SWT.CHECK style bit. This way the |
| * selected items are visualized by showing checked checkboxes. Also adds a |
| * <i>Select All</i> item for convenience that de-/selects all items on click. |
| */ |
| public class FilterNatCombo extends NatCombo { |
| |
| /** |
| * The viewer that contains the select all item in the dropdown control. |
| * |
| * @since 1.4 |
| */ |
| protected CheckboxTableViewer selectAllItemViewer; |
| /** |
| * The local selection String storage. |
| */ |
| private String filterText; |
| |
| /** |
| * List of ICheckStateListener that should be added to the select all table |
| * once it is created. Kept locally because the table creation is deferred |
| * to the first access. |
| */ |
| private List<ICheckStateListener> checkStateListener = new ArrayList<>(); |
| |
| /** |
| * Creates a new FilterNatCombo using the given IStyle for rendering, |
| * showing the default number 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 style |
| * The style for the Text Control to construct. Uses this style |
| * adding internal styles via ConfigRegistry. |
| */ |
| public FilterNatCombo(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 FilterNatCombo 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 Text Control to construct. Uses this style |
| * adding internal styles via ConfigRegistry. |
| */ |
| public FilterNatCombo(Composite parent, IStyle cellStyle, int maxVisibleItems, int style) { |
| this(parent, |
| cellStyle, |
| maxVisibleItems, |
| style, |
| GUIHelper.getImage("down_2")); //$NON-NLS-1$ |
| } |
| |
| /** |
| * Creates a new FilterNatCombo 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 Text Control to construct. Uses this style |
| * adding internal styles via ConfigRegistry. |
| * |
| * @param showDropdownFilter |
| * Flag indicating whether the filter of the dropdown control |
| * should be displayed |
| * |
| * @since 1.4 |
| */ |
| public FilterNatCombo(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 FilterNatCombo 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 FilterNatCombo(Composite parent, IStyle cellStyle, int maxVisibleItems, int style, Image iconImage) { |
| this(parent, cellStyle, maxVisibleItems, style, iconImage, false); |
| } |
| |
| /** |
| * Creates a new FilterNatCombo 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 filter of the dropdown control |
| * should be displayed |
| * |
| * @since 1.4 |
| */ |
| public FilterNatCombo(Composite parent, IStyle cellStyle, int maxVisibleItems, int style, Image iconImage, boolean showDropdownFilter) { |
| super(parent, cellStyle, maxVisibleItems, style, iconImage, showDropdownFilter); |
| } |
| |
| @Override |
| protected void calculateBounds() { |
| if (this.dropdownShell != null && !this.dropdownShell.isDisposed()) { |
| Point size = getSize(); |
| |
| int gridLineAdjustment = this.dropdownTable.getGridLineWidth() * 2; |
| |
| // 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 |
| int listHeight = getVisibleItemCount() * this.dropdownTable.getItemHeight() + gridLineAdjustment; |
| |
| // 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(); |
| this.selectAllItemViewer.getTable().getColumn(0).pack(); |
| |
| int listWidth = Math.max( |
| this.dropdownTable.computeSize(SWT.DEFAULT, listHeight, true).x, |
| size.x); |
| |
| int viewerHeight = this.selectAllItemViewer.getTable().getItemHeight(); |
| listWidth = Math.max( |
| this.selectAllItemViewer.getTable().computeSize(SWT.DEFAULT, viewerHeight, true).x, |
| listWidth); |
| |
| Point textPosition = this.text.toDisplay(this.text.getLocation()); |
| |
| int filterTextBoxHeight = this.showDropdownFilter ? this.filterBox.computeSize(SWT.DEFAULT, SWT.DEFAULT).y : 0; |
| this.dropdownShell.setBounds( |
| textPosition.x, |
| textPosition.y + this.text.getBounds().height, |
| listWidth + (this.dropdownTable.getGridLineWidth() * 2), |
| listHeight + viewerHeight + filterTextBoxHeight); |
| |
| // as we performed auto resize for the columns, we now need to |
| // ensure again that the columns |
| // span the whole table width in case they shrunk |
| calculateColumnWidth(); |
| } |
| } |
| |
| @Override |
| protected void calculateColumnWidth() { |
| super.calculateColumnWidth(); |
| |
| this.selectAllItemViewer.getTable().getColumn(0).setWidth( |
| this.dropdownTable.getColumn(0).getWidth()); |
| } |
| |
| @Override |
| protected void createDropdownControl(int style) { |
| super.createDropdownControl(style); |
| |
| int dropdownListStyle = style | SWT.NO_SCROLL |
| | HorizontalAlignmentEnum.getSWTStyle(this.cellStyle) |
| | SWT.FULL_SELECTION; |
| this.selectAllItemViewer = |
| CheckboxTableViewer.newCheckList(this.dropdownShell, dropdownListStyle); |
| |
| // add a column to be able to resize the item width in the dropdown |
| new TableColumn(this.selectAllItemViewer.getTable(), SWT.NONE); |
| this.selectAllItemViewer.getTable().addListener(SWT.Resize, event -> calculateColumnWidth()); |
| |
| FormData data = new FormData(); |
| if (this.showDropdownFilter) { |
| data.top = new FormAttachment(this.filterBox, 0, SWT.BOTTOM); |
| } else { |
| data.top = new FormAttachment(this.dropdownShell, 0, SWT.TOP); |
| } |
| data.left = new FormAttachment(0); |
| data.right = new FormAttachment(100); |
| this.selectAllItemViewer.getTable().setLayoutData(data); |
| |
| data = new FormData(); |
| data.top = new FormAttachment(this.selectAllItemViewer.getControl(), 0, SWT.BOTTOM); |
| data.left = new FormAttachment(0); |
| data.right = new FormAttachment(100); |
| data.bottom = new FormAttachment(100); |
| this.dropdownTable.setLayoutData(data); |
| |
| this.selectAllItemViewer.setContentProvider(new IStructuredContentProvider() { |
| |
| @Override |
| public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { |
| // no action on inputChanged |
| } |
| |
| @Override |
| public void dispose() { |
| // no action on dispose |
| } |
| |
| @SuppressWarnings("unchecked") |
| @Override |
| public Object[] getElements(Object inputElement) { |
| return ((Collection<String>) inputElement).toArray(); |
| } |
| }); |
| |
| this.selectAllItemViewer.setLabelProvider(new ILabelProvider() { |
| |
| @Override |
| public void removeListener(ILabelProviderListener listener) { |
| // no additional listener support |
| } |
| |
| @Override |
| public boolean isLabelProperty(Object element, String property) { |
| return false; |
| } |
| |
| @Override |
| public void dispose() { |
| // no action on dispose |
| } |
| |
| @Override |
| public void addListener(ILabelProviderListener listener) { |
| // no additional listener support |
| } |
| |
| @Override |
| public String getText(Object element) { |
| return element.toString(); |
| } |
| |
| @Override |
| public Image getImage(Object element) { |
| return null; |
| } |
| }); |
| |
| final String selectAllLabel = Messages.getString("FilterNatCombo.selectAll"); //$NON-NLS-1$ |
| List<String> input = new ArrayList<>(); |
| input.add(selectAllLabel); |
| this.selectAllItemViewer.setInput(input); |
| |
| this.selectAllItemViewer.getTable().setBackground( |
| this.cellStyle.getAttributeValue(CellStyleAttributes.BACKGROUND_COLOR)); |
| this.selectAllItemViewer.getTable().setForeground( |
| this.cellStyle.getAttributeValue(CellStyleAttributes.FOREGROUND_COLOR)); |
| this.selectAllItemViewer.getTable().setFont( |
| this.cellStyle.getAttributeValue(CellStyleAttributes.FONT)); |
| |
| this.selectAllItemViewer.getTable().addFocusListener( |
| new FocusAdapter() { |
| @Override |
| public void focusGained(FocusEvent e) { |
| showDropdownControl(); |
| } |
| }); |
| |
| this.selectAllItemViewer.addCheckStateListener(event -> { |
| if (event.getChecked()) { |
| // select all |
| FilterNatCombo.this.dropdownTable.selectAll(); |
| } else { |
| // deselect all |
| FilterNatCombo.this.dropdownTable.deselectAll(); |
| } |
| |
| // after selection is performed we need to ensure that |
| // selection and checkboxes are in sync |
| for (TableItem tableItem : FilterNatCombo.this.dropdownTable.getItems()) { |
| tableItem.setChecked( |
| FilterNatCombo.this.dropdownTable.isSelected( |
| FilterNatCombo.this.itemList.indexOf(tableItem.getText()))); |
| } |
| |
| // sync the selectionStateMap based on the state of the select |
| // all checkbox |
| for (String item : FilterNatCombo.this.itemList) { |
| FilterNatCombo.this.selectionStateMap.put(item, event.getChecked()); |
| } |
| |
| updateTextControl(!FilterNatCombo.this.multiselect); |
| }); |
| |
| for (ICheckStateListener l : this.checkStateListener) { |
| this.selectAllItemViewer.addCheckStateListener(l); |
| } |
| |
| // set an ICheckStateProvider that sets the checkbox state of the select |
| // all checkbox regarding the selection of the items in the dropdown |
| this.selectAllItemViewer.setCheckStateProvider(new ICheckStateProvider() { |
| |
| @Override |
| public boolean isGrayed(Object element) { |
| return getSelectionCount() < FilterNatCombo.this.itemList.size(); |
| } |
| |
| @Override |
| public boolean isChecked(Object element) { |
| return getSelectionCount() > 0; |
| } |
| }); |
| |
| // add a selection listener to the items that simply refreshes the |
| // select all checkbox |
| this.dropdownTable.addSelectionListener(new SelectionAdapter() { |
| @Override |
| public void widgetSelected(SelectionEvent e) { |
| FilterNatCombo.this.selectAllItemViewer.refresh(); |
| } |
| }); |
| } |
| |
| @Override |
| protected void setDropdownSelection(String[] selection) { |
| super.setDropdownSelection(selection); |
| if (this.selectAllItemViewer != null) |
| this.selectAllItemViewer.refresh(); |
| } |
| |
| /** |
| * Add an ICheckStateListener to the viewer of the dropdown that contains |
| * the select all item. Needed so the editor is able to commit after the |
| * click on the select all checkbox is performed. |
| * |
| * @param listener |
| * The listener to add to the select all item |
| */ |
| public void addCheckStateListener(ICheckStateListener listener) { |
| if (listener != null) { |
| if (this.selectAllItemViewer != null) { |
| this.selectAllItemViewer.addCheckStateListener(listener); |
| } |
| this.checkStateListener.add(listener); |
| } |
| } |
| |
| @Override |
| protected void updateTextControl(boolean hideDropdown) { |
| this.filterText = getTransformedTextForSelection(); |
| if (hideDropdown) { |
| hideDropdownControl(); |
| } |
| } |
| |
| @Override |
| public int getSelectionIndex() { |
| if (!getDropdownTable().isDisposed()) { |
| return getDropdownTable().getSelectionIndex(); |
| } else if (this.filterText != null && this.filterText.length() > 0) { |
| return this.itemList.indexOf(this.filterText); |
| } |
| return -1; |
| } |
| |
| @Override |
| public String[] getSelection() { |
| String[] result = getTransformedSelection(); |
| if (result == null || (result.length == 0 && this.filterText != null)) { |
| result = getTextAsArray(); |
| } |
| return result; |
| } |
| |
| @Override |
| 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.filterText = textValue; |
| } |
| |
| @Override |
| public void select(int index) { |
| if (!getDropdownTable().isDisposed()) { |
| getDropdownTable().select(index); |
| this.filterText = getTransformedTextForSelection(); |
| } else if (index >= 0) { |
| this.filterText = this.itemList.get(index); |
| } |
| } |
| |
| @Override |
| public void select(int[] indeces) { |
| if (!getDropdownTable().isDisposed()) { |
| getDropdownTable().select(indeces); |
| this.filterText = getTransformedTextForSelection(); |
| } else { |
| String[] selectedItems = new String[indeces.length]; |
| for (int i = 0; i < indeces.length; i++) { |
| if (indeces[i] >= 0) { |
| selectedItems[i] = this.itemList.get(indeces[i]); |
| } |
| } |
| this.filterText = getTransformedText(selectedItems); |
| } |
| } |
| |
| @Override |
| 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; |
| } |
| } |
| // if at least one value was selected, add the prefix and suffix |
| // we check the values array instead of the result length because |
| // there can be also an empty String be selected |
| if (values.length > 0) { |
| result = this.multiselectTextPrefix + result + this.multiselectTextSuffix; |
| } |
| } else if (values.length > 0) { |
| result = values[0]; |
| } |
| return result; |
| } |
| |
| @Override |
| protected String[] getTextAsArray() { |
| if (this.filterText != null && this.filterText.length() > 0) { |
| String transform = this.filterText; |
| int prefixLength = this.multiselectTextPrefix.length(); |
| int suffixLength = this.multiselectTextSuffix.length(); |
| transform = transform.substring(prefixLength, transform.length() - suffixLength); |
| return transform.split(this.multiselectValueSeparator); |
| } |
| return new String[] {}; |
| } |
| } |