/* * Copyright (c) 2012 Data Harmonisation Panel * * All rights reserved. This program and the accompanying materials are made * available under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution. If not, see <http://www.gnu.org/licenses/>. * * Contributors: * HUMBOLDT EU Integrated Project #030962 * Data Harmonisation Panel <http://www.dhpanel.eu> */ package eu.esdihumboldt.hale.ui.util.selector; import java.util.concurrent.CopyOnWriteArraySet; import org.eclipse.jface.layout.TableColumnLayout; import org.eclipse.jface.viewers.ColumnWeightData; import org.eclipse.jface.viewers.ILabelProvider; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.ISelectionProvider; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.TableViewerColumn; import org.eclipse.jface.viewers.ViewerFilter; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import eu.esdihumboldt.hale.ui.util.viewer.ObjectContentProvider; /** * Abstract selector control based on a {@link TableViewer}. * * @param <T> the type of the object to be selected * @param <E> the type of object selected in the dialog * @author Simon Templer */ public abstract class AbstractSelector<T, E> implements ISelectionProvider { private static enum NoObject { NONE; @Override public String toString() { return "<Click to select>"; } } private final CopyOnWriteArraySet<ISelectionChangedListener> listeners = new CopyOnWriteArraySet<ISelectionChangedListener>(); private final TableViewer viewer; private final Composite main; private final ViewerFilter[] filters; /** * Tracks the current input. Set by inputChanged of the content provider. */ private Object currentInput; /** * Create a selector. * * @param parent the parent composite * @param labelProvider the label provider for the selector * @param filters the filters for the selector, may be <code>null</code> */ public AbstractSelector(Composite parent, ILabelProvider labelProvider, ViewerFilter[] filters) { main = new Composite(parent, SWT.NONE); TableColumnLayout columnLayout = new TableColumnLayout(); main.setLayout(columnLayout); // entity selection combo /* * Use MULTI selection so having an empty selection is possible on * Linux/GTK. Otherwise when the control gets the focus it will * automatically select the entry and launch the selection dialog. This * especially is a problem when a selector gets the focus automatically. */ viewer = new TableViewer(main, SWT.BORDER | SWT.MULTI | SWT.FULL_SELECTION | SWT.NO_SCROLL); TableViewerColumn column = new TableViewerColumn(viewer, SWT.NONE); columnLayout.setColumnData(column.getColumn(), new ColumnWeightData(1, false)); viewer.setContentProvider(ObjectContentProvider.getInstance()); viewer.setLabelProvider(labelProvider); this.filters = filters; // initial selection Object select = NoObject.NONE; currentInput = select; viewer.setInput(select); viewer.addSelectionChangedListener(new ISelectionChangedListener() { @Override public void selectionChanged(SelectionChangedEvent event) { if (event.getSelection().isEmpty()) { return; } AbstractViewerSelectionDialog<E, ?> dialog = createSelectionDialog(Display .getCurrent().getActiveShell()); dialog.setFilters(AbstractSelector.this.filters); if (dialog.open() == AbstractViewerSelectionDialog.OK) { T selected = convertFrom(dialog.getObject()); if ((selected == null && currentInput == null) || (selected != null && selected.equals(currentInput))) return; if (selected != null) { currentInput = selected; } else { currentInput = NoObject.NONE; } viewer.setInput(currentInput); /* * XXX Bug on Mac? - Viewer is not refreshed correctly until * user clicks on the wizard. Manually refreshing, layouting * the parent composite or calling * forceActive/forceFocus/setActive on the Shell doesn't * help. XXX is this fixed with TableColumnLayout? */ } viewer.setSelection(new StructuredSelection()); // inform about the input change fireSelectionChange(); } }); } /** * Convert from an object selected in the dialog to a selector object. * * @param object the dialog selected object, may be <code>null</code> * @return the converted object */ protected abstract T convertFrom(E object); /** * Determines if the given object matches the selector's filters. * * @param candidate the object to test * @return if the object is accepted by all filters */ public boolean accepts(Object candidate) { return AbstractViewerSelectionDialog.acceptObject(viewer, filters, candidate); } /** * @see ISelectionProvider#addSelectionChangedListener(ISelectionChangedListener) */ @Override public void addSelectionChangedListener(ISelectionChangedListener listener) { listeners.add(listener); } /** * @see ISelectionProvider#getSelection() */ @Override public ISelection getSelection() { Object input = currentInput; if (input == null || input == NoObject.NONE) { return new StructuredSelection(); } return new StructuredSelection(input); } /** * @see ISelectionProvider#removeSelectionChangedListener(ISelectionChangedListener) */ @Override public void removeSelectionChangedListener(ISelectionChangedListener listener) { listeners.remove(listener); } /** * @see ISelectionProvider#setSelection(ISelection) */ @Override public void setSelection(ISelection selection) { if (!selection.isEmpty() && selection instanceof IStructuredSelection) { Object selected = ((IStructuredSelection) selection).getFirstElement(); if (selected != null) { // run against filters if (AbstractViewerSelectionDialog.acceptObject(viewer, filters, selected)) { // valid selection currentInput = selected; viewer.setInput(selected); fireSelectionChange(); return; } else { // TODO user error message? } } } currentInput = NoObject.NONE; viewer.setInput(NoObject.NONE); viewer.setSelection(StructuredSelection.EMPTY); fireSelectionChange(); } /** * Resets the current selection, but shows the given text instead of the * default. * * @param text the text to show */ public void showText(String text) { currentInput = NoObject.NONE; viewer.setInput(text); viewer.setSelection(StructuredSelection.EMPTY); fireSelectionChange(); } /** * Fires a selection change and sets the last selection to the given * selection. */ protected void fireSelectionChange() { SelectionChangedEvent event = new SelectionChangedEvent(this, getSelection()); for (ISelectionChangedListener listener : listeners) { listener.selectionChanged(event); } } /** * Create the dialog for selecting an entity. * * @param parentShell the parent shell for the dialog * @return the entity dialog */ protected abstract AbstractViewerSelectionDialog<E, ?> createSelectionDialog(Shell parentShell); /** * Get the main selector control * * @return the main control */ public Control getControl() { return main; } /** * Get the selected entity definition * * @return the selected entity definition or <code>null</code> */ @SuppressWarnings("unchecked") public T getSelectedObject() { ISelection selection = getSelection(); if (selection.isEmpty() || !(selection instanceof IStructuredSelection)) { return null; } Object element = ((IStructuredSelection) selection).getFirstElement(); if (element != null && element != NoObject.NONE) { return (T) element; } return null; } }