blob: a20d6b96756c12c5a49bd0d4e8a6fd53fd02abd5 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011 Laurent CARON.
* 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:
* Laurent CARON (laurent.caron@gmail.com) - initial API and implementation
*******************************************************************************/
package org.mihalis.opal.multiChoice;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.mihalis.opal.utils.ResourceManager;
import org.mihalis.opal.utils.SimpleSelectionAdapter;
/**
* The MultiChoice class represents a selectable user interface object that combines a read-only text-field and a set of checkboxes.
*
* <p>
* Note that although this class is a subclass of <code>Composite</code>, it does not make sense to add children to it, or set a layout on it.
* </p>
* <dl>
* <dt><b>Styles:</b>
* <dd>NONE</dd>
* <dt><b>Events:</b>
* <dd>Selection</dd>
* </dl>
*
* @param <T> Class of objects represented by this widget
*/
public class MultiChoice<T> extends Composite {
/** The text. */
private Text text;
/** The arrow. */
private Button arrow;
/** The popup. */
private Shell popup;
/** The scrolled composite. */
private ScrolledComposite scrolledComposite;
/** The filter. */
private Listener listener, filter;
/** The number of columns. */
private int numberOfColumns = 2;
/** The elements. */
private List<T> elements;
/** The selection. */
private Set<T> selection;
/** The checkboxes. */
private List<Button> checkboxes;
/** The has focus. */
private boolean hasFocus;
/** The selection listener. */
private MultiChoiceSelectionListener<T> selectionListener;
/** The last modified. */
private T lastModified;
/** The background. */
private Color foreground, background;
/** The font. */
private Font font;
/** The separator. */
private String separator;
/** The label provider. */
private MultiChoiceLabelProvider labelProvider;
/** The preferred height of popup. */
private int preferredHeightOfPopup;
/**
* Constructs a new instance of this class given its parent.
*
* @param parent a widget which will be the parent of the new instance (cannot be null)
* @param style not used
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
* </ul>
*/
public MultiChoice(final Composite parent, final int style) {
this(parent, style, null);
}
/**
* Constructs a new instance of this class given its parent.
*
* @param parent a widget which will be the parent of the new instance (cannot be null)
* @param style not used
* @param elements list of elements displayed by this widget
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
* </ul>
*/
public MultiChoice(final Composite parent, final int style, final List<T> elements) {
super(parent, style);
final GridLayout gridLayout = new GridLayout(2, false);
gridLayout.horizontalSpacing = gridLayout.verticalSpacing = gridLayout.marginWidth = gridLayout.marginHeight = 0;
this.setLayout(gridLayout);
int readOnlyStyle;
if ((style & SWT.READ_ONLY) == SWT.READ_ONLY) {
readOnlyStyle = SWT.READ_ONLY;
} else {
readOnlyStyle = SWT.NONE;
}
this.text = new Text(this, SWT.SINGLE | readOnlyStyle | SWT.BORDER);
this.text.setBackground(this.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND));
this.text.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, false));
this.arrow = new Button(this, SWT.ARROW | SWT.RIGHT);
this.arrow.setLayoutData(new GridData(GridData.FILL, GridData.FILL, false, false));
createGlobalListener();
addListeners();
this.filter = new Listener() {
@Override
public void handleEvent(final Event event) {
final Shell shell = ((Control) event.widget).getShell();
if (shell == MultiChoice.this.getShell()) {
handleFocusEvents(SWT.FocusOut);
}
}
};
this.selection = new LinkedHashSet<T>();
this.elements = elements;
this.separator = ",";
this.labelProvider = new MultiChoiceDefaultLabelProvider();
createPopup();
setLabel();
}
/**
* Creates the global listener.
*/
private void createGlobalListener() {
this.listener = new Listener() {
@Override
public void handleEvent(final Event event) {
if (MultiChoice.this.popup == event.widget) {
handlePopupEvent(event);
return;
}
if (MultiChoice.this.arrow == event.widget) {
handleButtonEvent(event);
return;
}
if (MultiChoice.this == event.widget) {
handleMultiChoiceEvents(event);
return;
}
if (getShell() == event.widget) {
getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
if (isDisposed()) {
return;
}
handleFocusEvents(SWT.FocusOut);
}
});
}
}
};
}
/**
* Adds the listeners.
*/
private void addListeners() {
final int[] multiChoiceEvent = { SWT.Dispose, SWT.Move, SWT.Resize };
for (final int element : multiChoiceEvent) {
this.addListener(element, this.listener);
}
if ((getStyle() & SWT.READ_ONLY) == 0) {
final Listener validationListener = new Listener() {
@Override
public void handleEvent(final Event event) {
if (!MultiChoice.this.popup.isDisposed() && !MultiChoice.this.popup.isVisible()) {
validateEntry();
}
}
};
this.text.addListener(SWT.FocusOut, validationListener);
}
final int[] buttonEvents = { SWT.Selection, SWT.FocusIn };
for (final int buttonEvent : buttonEvents) {
this.arrow.addListener(buttonEvent, this.listener);
}
}
/**
* Validate entry.
*/
protected void validateEntry() {
final String toValidate = this.text.getText();
final String[] elementsToValidate = toValidate.split(this.separator);
final List<String> fieldsInError = new ArrayList<String>();
this.selection.clear();
for (final String elementToValidate : elementsToValidate) {
final String temp = elementToValidate.trim();
if ("".equals(temp)) {
continue;
}
final T entry = convertEntry(temp);
if (entry == null) {
fieldsInError.add(temp);
} else {
this.selection.add(entry);
}
}
if (fieldsInError.size() == 0) {
updateSelection();
return;
}
final String messageToDisplay;
if (fieldsInError.size() == 1) {
messageToDisplay = String.format(ResourceManager.getLabel(ResourceManager.MULTICHOICE_MESSAGE), fieldsInError.get(0));
} else {
final StringBuilder sb = new StringBuilder();
final Iterator<String> it = fieldsInError.iterator();
while (it.hasNext()) {
sb.append(it.next());
if (it.hasNext()) {
sb.append(",");
}
}
messageToDisplay = String.format(ResourceManager.getLabel(ResourceManager.MULTICHOICE_MESSAGE_PLURAL), sb.toString());
}
getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
final MessageBox mb = new MessageBox(getShell(), SWT.OK | SWT.ICON_ERROR);
mb.setMessage(messageToDisplay);
mb.open();
MultiChoice.this.text.forceFocus();
}
});
}
/**
* Convert entry.
*
* @param elementToValidate the element to validate
* @return the t
*/
private T convertEntry(final String elementToValidate) {
for (final T elt : this.elements) {
if (this.labelProvider.getText(elt).trim().equals(elementToValidate)) {
return elt;
}
}
return null;
}
/**
* Adds the argument to the end of the receiver's list.
*
* @param value the value
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the string is null</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been
* disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
* thread that created the receiver</li>
* </ul>
*/
public void add(final T value) {
checkWidget();
if (value == null) {
SWT.error(SWT.ERROR_NULL_ARGUMENT);
}
if (this.elements == null) {
this.elements = new ArrayList<T>();
}
this.elements.add(value);
refresh();
}
/**
* Adds the argument to the receiver's list at the given zero-relative
* index.
*
* @param value the value
* @param index the index for the item
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the string is null</li>
* <li>ERROR_INVALID_RANGE - if the index is not between 0
* and the number of elements in the list minus 1 (inclusive)
* </li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been
* disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
* thread that created the receiver</li>
* </ul>
*/
public void add(final T value, final int index) {
checkWidget();
checkNullElement();
if (value == null) {
SWT.error(SWT.ERROR_NULL_ARGUMENT);
}
checkRange(index);
this.elements.add(index, value);
refresh();
}
/**
* Adds the argument to the end of the receiver's list.
*
* @param values new items
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the string is null</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public void addAll(final List<T> values) {
checkWidget();
if (values == null) {
SWT.error(SWT.ERROR_NULL_ARGUMENT);
}
if (this.elements == null) {
this.elements = new ArrayList<T>();
}
this.elements.addAll(values);
refresh();
}
/**
* Adds the argument to the end of the receiver's list.
*
* @param values new items
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the string is null</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public void addAll(final T[] values) {
checkWidget();
if (values == null) {
SWT.error(SWT.ERROR_NULL_ARGUMENT);
}
if (this.elements == null) {
this.elements = new ArrayList<T>();
}
for (final T value : values) {
this.elements.add(value);
}
refresh();
}
/**
* Returns the item at the given, zero-relative index in the receiver's list. Throws an exception if the index is out of range.
*
* @param index the index of the item to return
* @return the item at the given index
*
* @exception NullPointerException if there is no item in the receiver
* @exception IllegalArgumentException <ul>
* <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public T getItem(final int index) {
checkWidget();
checkNullElement();
checkRange(index);
return this.elements.get(index);
}
/**
* Returns the number of items contained in the receiver's list.
*
* @return the number of items
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public int getItemCount() {
checkWidget();
if (this.elements == null) {
return 0;
}
return this.elements.size();
}
/**
* Returns the list of items in the receiver's list.
* <p>
* Note: This is not the actual structure used by the receiver to maintain its list of items, so modifying the array will not affect the receiver.
* </p>
*
* @return the items in the receiver's list
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public List<T> getItems() {
checkWidget();
if (this.elements == null) {
return null;
}
return new ArrayList<T>(this.elements);
}
/**
* Removes the item from the receiver's list at the given zero-relative index.
*
* @param index the index for the item
* @exception NullPointerException if there is no item in the receiver
* @exception IllegalArgumentException <ul>
* <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public void removeAt(final int index) {
checkWidget();
checkNullElement();
checkRange(index);
final Object removedElement = this.elements.remove(index);
this.selection.remove(removedElement);
refresh();
}
/**
* Searches the receiver's list starting at the first item until an item is found that is equal to the argument, and removes that item from the list.
*
* @param object the item to remove
* @exception NullPointerException if there is no item in the receiver
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the object is null</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public void remove(final T object) {
if (object == null) {
SWT.error(SWT.ERROR_NULL_ARGUMENT);
}
checkWidget();
checkNullElement();
this.elements.remove(object);
this.selection.remove(object);
refresh();
}
/**
* Remove all items of the receiver.
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been
* disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
* thread that created the receiver</li>
* </ul>
*/
public void removeAll() {
checkWidget();
checkNullElement();
if (this.elements != null) {
this.elements.clear();
}
this.selection.clear();
refresh();
}
/**
* Sets the label provider.
*
* @param labelProvider the Label Provider to set
*/
public void setLabelProvider(final MultiChoiceLabelProvider labelProvider) {
this.labelProvider = labelProvider;
}
/**
* Sets the selection of the receiver. If the item was already selected, it remains selected.
*
* @param selection the new selection
* @exception NullPointerException if there is no item in the receiver
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the selection is null</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public void setSelection(final Set<T> selection) {
checkWidget();
checkNullElement();
if (selection == null) {
SWT.error(SWT.ERROR_NULL_ARGUMENT);
}
this.selection = selection;
updateSelection();
}
/**
* Selects all selected items in the receiver's list.
*
* @exception NullPointerException if there is no item in the receiver
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public void selectAll() {
checkWidget();
checkNullElement();
this.selection.addAll(this.elements);
updateSelection();
}
/**
* 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.
*
* @param index the index of the item to select
* @exception NullPointerException if there is no item in the receiver
* @exception IllegalArgumentException <ul>
* <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public void selectAt(final int index) {
checkWidget();
checkNullElement();
checkRange(index);
this.selection.add(this.elements.get(index));
updateSelection();
}
/**
* Selects an item the receiver's list. If the item was already selected, it
* remains selected.
*
* @param value the value
* @exception NullPointerException if there is no item in the receiver
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the selection is null</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been
* disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
* thread that created the receiver</li>
* </ul>
*/
public void select(final T value) {
checkWidget();
checkNullElement();
if (!this.elements.contains(value)) {
throw new IllegalArgumentException("Value not present in the widget");
}
this.selection.add(value);
updateSelection();
}
/**
* Selects items in the receiver. If the items were already selected, they remain selected.
*
* @param index the indexes of the items to select
* @exception NullPointerException if there is no item in the receiver
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the selection is null</li>
* <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public void setSelectedIndex(final int[] index) {
checkWidget();
checkNullElement();
for (final int i : index) {
checkRange(i);
this.selection.add(this.elements.get(i));
}
updateSelection();
}
/**
* 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
* @exception NullPointerException if there is no item in the receiver
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public int[] getSelectedIndex() {
checkWidget();
checkNullElement();
final List<Integer> selectedIndex = new ArrayList<Integer>();
for (int i = 0; i < this.elements.size(); i++) {
if (this.selection.contains(this.elements.get(i))) {
selectedIndex.add(i);
}
}
final int[] returned = new int[selectedIndex.size()];
for (int i = 0; i < selectedIndex.size(); i++) {
returned[i] = selectedIndex.get(i);
}
return returned;
}
/**
* Returns an array of <code>Object</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
* @exception NullPointerException if there is no item in the receiver
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public List<T> getSelection() {
checkWidget();
checkNullElement();
return new ArrayList<T>(this.selection);
}
/**
* Deselects the item at the given zero-relative index in the receiver's list. If the item at the index was already deselected, it remains deselected. Indices that are out of range are ignored.
*
* @param index the index of the item to deselect
* @exception NullPointerException if there is no item in the receiver
* @exception IllegalArgumentException <ul>
* <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public void deselectAt(final int index) {
this.selection.remove(getItem(index));
updateSelection();
}
/**
* Deselects the item in the receiver's list. If the item at the index was already deselected, it remains deselected.
*
* @param value the item to deselect
* @exception NullPointerException if there is no item in the receiver
* @exception IllegalArgumentException <ul>
* <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public void deselect(final T value) {
checkWidget();
checkNullElement();
this.selection.remove(value);
updateSelection();
}
/**
* Deselects all items in the receiver's list.
*
* @exception NullPointerException if there is no item in the receiver
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been
* disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
* thread that created the receiver</li>
* </ul>
*/
public void deselectAll() {
checkWidget();
checkNullElement();
this.selection.clear();
updateSelection();
}
/**
* Gets the number of columns.
*
* @return the number of columns
*/
public int getNumberOfColumns() {
checkWidget();
return this.numberOfColumns;
}
/**
* Sets the number of columns.
*
* @param numberOfColumns the number of columns
*/
public void setNumberOfColumns(final int numberOfColumns) {
checkWidget();
this.numberOfColumns = numberOfColumns;
this.popup.dispose();
this.popup = null;
createPopup();
}
/**
* Gets the separator.
*
* @return the separator used in the text field. Default value is ","
*/
public String getSeparator() {
return this.separator;
}
/**
* Sets the separator.
*
* @param separator the new value of the separator
*/
public void setSeparator(final String separator) {
this.separator = separator;
}
/**
* Gets the foreground.
*
* @return the foreground
* @see org.eclipse.swt.widgets.Control#getForeground()
*/
@Override
public Color getForeground() {
return this.foreground;
}
/**
* Sets the foreground.
*
* @param foreground the new foreground
* @see org.eclipse.swt.widgets.Control#setForeground(org.eclipse.swt.graphics.Color)
*/
@Override
public void setForeground(final Color foreground) {
this.foreground = foreground;
}
/**
* Gets the background.
*
* @return the background
* @see org.eclipse.swt.widgets.Control#getBackground()
*/
@Override
public Color getBackground() {
return this.background;
}
/**
* Sets the background.
*
* @param background the new background
* @see org.eclipse.swt.widgets.Control#setBackground(org.eclipse.swt.graphics.Color)
*/
@Override
public void setBackground(final Color background) {
this.background = background;
}
/**
* Gets the font.
*
* @return the font
* @see org.eclipse.swt.widgets.Control#getFont()
*/
@Override
public Font getFont() {
return this.font;
}
/**
* Sets the font.
*
* @param font the new font
* @see org.eclipse.swt.widgets.Control#setFont(org.eclipse.swt.graphics.Font)
*/
@Override
public void setFont(final Font font) {
this.font = font;
}
/**
* Refresh the widget (after the add of a new element for example).
*/
public void refresh() {
checkWidget();
this.popup.dispose();
this.popup = null;
createPopup();
updateSelection();
}
/**
* Compute size.
*
* @param wHint the w hint
* @param hHint the h hint
* @param changed the changed
* @return the point
* @see org.eclipse.swt.widgets.Composite#computeSize(int, int, boolean)
*/
@Override
public Point computeSize(final int wHint, final int hHint, final boolean changed) {
checkWidget();
int width = 0, height = 0;
final GC gc = new GC(this.text);
final int spacer = gc.stringExtent(" ").x;
final int textWidth = gc.stringExtent(this.text.getText()).x;
gc.dispose();
final Point textSize = this.text.computeSize(SWT.DEFAULT, SWT.DEFAULT, changed);
final Point arrowSize = this.arrow.computeSize(SWT.DEFAULT, SWT.DEFAULT, changed);
final int borderWidth = getBorderWidth();
height = Math.max(textSize.y, arrowSize.y);
width = textWidth + 2 * spacer + arrowSize.x + 2 * borderWidth;
if (wHint != SWT.DEFAULT) {
width = wHint;
}
if (hHint != SWT.DEFAULT) {
height = hHint;
}
return new Point(width + 2 * borderWidth, height + 2 * borderWidth);
}
/**
* Sets the enabled.
*
* @param enabled the new enabled
* @see org.eclipse.swt.widgets.Control#setEnabled(boolean)
*/
@Override
public void setEnabled(final boolean enabled) {
checkWidget();
this.arrow.setEnabled(enabled);
this.text.setEnabled(enabled);
super.setEnabled(enabled);
}
/**
* Sets the tool tip text.
*
* @param txt the new tool tip text
* @see org.eclipse.swt.widgets.Control#setToolTipText(java.lang.String)
*/
@Override
public void setToolTipText(final String txt) {
checkWidget();
this.text.setToolTipText(txt);
}
/**
* Gets the selection listener.
*
* @return the selection listener
*/
public SelectionListener getSelectionListener() {
checkWidget();
return this.selectionListener;
}
/**
* Sets the selection listener.
*
* @param selectionListener the new selection listener
*/
public void setSelectionListener(final MultiChoiceSelectionListener<T> selectionListener) {
checkWidget();
this.selectionListener = selectionListener;
refresh();
}
/**
* Update the selection.
*/
public void updateSelection() {
checkWidget();
if (isDisposed()) {
return;
}
if (this.popup == null || this.popup.isDisposed() || this.checkboxes == null) {
return;
}
for (int i = 0; i < this.checkboxes.size(); i++) {
final Button currentButton = this.checkboxes.get(i);
if (!currentButton.isDisposed()) {
final Object content = currentButton.getData();
currentButton.setSelection(this.selection.contains(content));
}
}
setLabel();
}
/**
* Gets the last modified.
*
* @return the last modified item
*/
T getLastModified() {
return this.lastModified;
}
/**
* Gets the popup.
*
* @return the popup
*/
Shell getPopup() {
return this.popup;
}
/**
* Create the popup that contains all checkboxes.
*/
private void createPopup() {
this.popup = new Shell(getShell(), SWT.NO_TRIM | SWT.ON_TOP);
this.popup.setLayout(new FillLayout());
final int[] popupEvents = { SWT.Close, SWT.Deactivate, SWT.Dispose };
for (final int popupEvent : popupEvents) {
this.popup.addListener(popupEvent, this.listener);
}
if (this.elements == null) {
return;
}
this.scrolledComposite = new ScrolledComposite(this.popup, SWT.BORDER | SWT.V_SCROLL);
final Composite content = new Composite(this.scrolledComposite, SWT.NONE);
content.setLayout(new GridLayout(this.numberOfColumns, true));
this.checkboxes = new ArrayList<Button>(this.elements.size());
for (final T o : this.elements) {
final Button checkBoxButton = new Button(content, SWT.CHECK);
if (this.font != null) {
checkBoxButton.setFont(this.font);
}
if (this.foreground != null) {
checkBoxButton.setForeground(this.foreground);
}
if (this.background != null) {
checkBoxButton.setBackground(this.background);
}
checkBoxButton.setData(o);
checkBoxButton.setLayoutData(new GridData(GridData.BEGINNING, GridData.CENTER, false, false));
checkBoxButton.setText(this.labelProvider.getText(o));
checkBoxButton.addSelectionListener(new SimpleSelectionAdapter() {
@Override
public void handle(final SelectionEvent e) {
if (checkBoxButton.getSelection()) {
MultiChoice.this.selection.add(o);
} else {
MultiChoice.this.selection.remove(o);
}
MultiChoice.this.lastModified = o;
setLabel();
}
});
if (this.selectionListener != null) {
checkBoxButton.addSelectionListener(this.selectionListener);
}
checkBoxButton.setSelection(this.selection.contains(o));
this.checkboxes.add(checkBoxButton);
}
this.scrolledComposite.setContent(content);
this.scrolledComposite.setExpandHorizontal(false);
this.scrolledComposite.setExpandVertical(true);
content.pack();
this.preferredHeightOfPopup = content.getSize().y;
}
/**
* Set the value of the label, based on the selected items.
*/
private void setLabel() {
if (this.checkboxes == null) {
this.text.setText("");
return;
}
final List<String> values = new ArrayList<String>();
for (final Button current : this.checkboxes) {
if (current.getSelection()) {
values.add(current.getText());
}
}
final StringBuilder sb = new StringBuilder();
final Iterator<String> it = values.iterator();
while (it.hasNext()) {
sb.append(it.next());
if (it.hasNext()) {
sb.append(this.separator);
}
}
this.text.setText(sb.toString());
}
/**
* Handle a focus event.
*
* @param type type of the event to handle
*/
private void handleFocusEvents(final int type) {
if (isDisposed()) {
return;
}
switch (type) {
case SWT.FocusIn: {
if (this.hasFocus) {
return;
}
this.hasFocus = true;
final Shell shell = getShell();
shell.removeListener(SWT.Deactivate, this.listener);
shell.addListener(SWT.Deactivate, this.listener);
final Display display = getDisplay();
display.removeFilter(SWT.FocusIn, this.filter);
display.addFilter(SWT.FocusIn, this.filter);
final Event e = new Event();
notifyListeners(SWT.FocusIn, e);
break;
}
case SWT.FocusOut: {
if (!this.hasFocus) {
return;
}
final Control focusControl = getDisplay().getFocusControl();
if (focusControl == this.arrow) {
return;
}
this.hasFocus = false;
final Shell shell = getShell();
shell.removeListener(SWT.Deactivate, this.listener);
final Display display = getDisplay();
display.removeFilter(SWT.FocusIn, this.filter);
final Event e = new Event();
notifyListeners(SWT.FocusOut, e);
break;
}
}
}
/**
* Handle a multichoice event.
*
* @param event event to handle
*/
private void handleMultiChoiceEvents(final Event event) {
switch (event.type) {
case SWT.Dispose:
if (this.popup != null && !this.popup.isDisposed()) {
this.popup.dispose();
}
final Shell shell = getShell();
shell.removeListener(SWT.Deactivate, this.listener);
final Display display = getDisplay();
display.removeFilter(SWT.FocusIn, this.filter);
this.popup = null;
this.arrow = null;
break;
case SWT.Move:
changeVisibilityOfPopupWindow(false);
break;
case SWT.Resize:
if (isDropped()) {
changeVisibilityOfPopupWindow(false);
}
break;
}
}
/**
* Handle a button event.
*
* @param event event to hangle
*/
private void handleButtonEvent(final Event event) {
switch (event.type) {
case SWT.FocusIn: {
handleFocusEvents(SWT.FocusIn);
break;
}
case SWT.Selection: {
changeVisibilityOfPopupWindow(!isDropped());
break;
}
}
}
/**
* Checks if is dropped.
*
* @return <code>true</code> if the popup is visible and not dropped,
* <code>false</code> otherwise
*/
private boolean isDropped() {
return !this.popup.isDisposed() && this.popup.getVisible();
}
/**
* Handle a popup event.
*
* @param event event to handle
*/
private void handlePopupEvent(final Event event) {
switch (event.type) {
case SWT.Close:
event.doit = false;
changeVisibilityOfPopupWindow(false);
break;
case SWT.Deactivate:
changeVisibilityOfPopupWindow(false);
break;
case SWT.Dispose:
if (this.checkboxes != null) {
this.checkboxes.clear();
}
this.checkboxes = null;
break;
}
}
/**
* Display/Hide the popup window.
*
* @param show if <code>true</code>, displays the popup window. If
* <code>false</code>, hide the popup window
*/
private void changeVisibilityOfPopupWindow(final boolean show) {
if (show == isDropped()) {
return;
}
if (!show) {
this.popup.setVisible(false);
if (!isDisposed()) {
this.text.setFocus();
}
return;
}
if (getShell() != this.popup.getParent()) {
this.popup.dispose();
this.popup = null;
createPopup();
}
final Point arrowRect = this.arrow.toDisplay(this.arrow.getSize().x - 5, this.arrow.getSize().y + this.arrow.getBorderWidth() - 3);
int x = arrowRect.x;
int y = arrowRect.y;
final Rectangle displayRect = getMonitor().getClientArea();
final Rectangle parentRect = getDisplay().map(getParent(), null, getBounds());
this.popup.pack();
final int width = this.popup.getBounds().width;
final int maxHeight = (2 * displayRect.height / 3);
int height = this.popup.getBounds().height;
if (height > maxHeight) {
height = maxHeight;
this.popup.setSize(width, height);
this.scrolledComposite.setMinHeight(this.preferredHeightOfPopup);
this.popup.layout(true);
}
if (y + height > displayRect.y + displayRect.height) {
y = parentRect.y - height;
if (y < 0) {
height += y;
y = parentRect.y - height + 5;
this.popup.setSize(width, height);
this.scrolledComposite.setMinHeight(this.preferredHeightOfPopup);
this.popup.layout(true);
}
}
if (x + width > displayRect.x + displayRect.width) {
x = displayRect.x + displayRect.width - width;
}
this.popup.setLocation(x, y);
this.popup.setVisible(true);
this.popup.setFocus();
}
/**
* Check if the elements attributes is not null.
*
* @exception NullPointerException if there is no item in the receiver
*/
private void checkNullElement() {
if (this.elements == null) {
throw new NullPointerException("There is no element associated to this widget");
}
}
/**
* Check range.
*
* @param index the index
* @throws NullPointerException the null pointer exception
* @throws IllegalArgumentException the illegal argument exception
*/
private void checkRange(final int index) throws NullPointerException {
checkNullElement();
if (index < 0 || index >= this.elements.size()) {
SWT.error(SWT.ERROR_INVALID_RANGE);
}
}
}