blob: db91399155192a5a5e0ba334220a613f8fd94936 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2012, 2013 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
******************************************************************************/
package org.eclipse.nebula.widgets.nattable.selection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
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.nebula.widgets.nattable.coordinate.IValueIterator;
import org.eclipse.nebula.widgets.nattable.coordinate.RangeList;
import org.eclipse.nebula.widgets.nattable.data.IRowDataProvider;
import org.eclipse.nebula.widgets.nattable.layer.ILayerListener;
import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEvent;
import org.eclipse.nebula.widgets.nattable.selection.command.SelectRowsCommand;
import org.eclipse.nebula.widgets.nattable.selection.event.ISelectionEvent;
/**
* Implementation of ISelectionProvider to add support for JFace selection handling.
*
* @param <T> The type of objects provided by the IRowDataProvider
*/
public class RowSelectionProvider<T> implements ISelectionProvider, ILayerListener {
/**
* The SelectionLayer this ISelectionProvider is connected to.
*/
private SelectionLayer selectionLayer;
/**
* The IRowDataProvider to access the selected row data.
*/
private IRowDataProvider<T> rowDataProvider;
/**
* Flag to determine if only fully selected rows should be used to populate the selection
* or if any selection should be populated. Default is to only populate fully selected rows.
*/
private final boolean fullySelectedRowsOnly;
/**
* Flag to configure whether only SelectionChangedEvents should be fired if the row selection
* changes or even if you just select another column.
*/
private final boolean handleSameRowSelection;
/**
* Collection of ISelectionChangedListeners to this ISelectionProvider
*/
private Set<ISelectionChangedListener> listeners = new HashSet<ISelectionChangedListener>();
/**
* Locally stored previous selection which is used to determine if a SelectionChangedEvent
* should be fired. It is used to avoid firing events if the same row is selected again (default).
* If handleSameRowSelection is set to <code>true</code>, this value is not evaluated at runtime.
*/
private ISelection previousSelection;
/**
* Flag to configure whether <code>setSelection()</code> should add or set the selection.
* <p>
* This was added for convenience because the initial code always added the selection
* on <code>setSelection()</code> by creating a SelectRowsCommand with the withControlMask set to <code>true</code>.
* Looking at the specification, <code>setSelection()</code> is used to set the <b>new</b> selection.
* So the default here is now to set instead of add. But for convenience to older code
* that relied on the add behaviour it is now possible to change it back to adding.
*/
private boolean addSelectionOnSet = false;
/**
* Create a RowSelectionProvider that only handles fully selected rows and only fires
* SelectionChangedEvents if the row selection changes.
* @param selectionLayer The SelectionLayer this ISelectionProvider should be connected to.
* @param rowDataProvider The IRowDataProvider that should be used to access the selected row data.
*/
public RowSelectionProvider(SelectionLayer selectionLayer, IRowDataProvider<T> rowDataProvider) {
this(selectionLayer, rowDataProvider, true);
}
/**
* Create a RowSelectionProvider that only fires SelectionChangedEvents if the row selection changes.
* @param selectionLayer The SelectionLayer this ISelectionProvider should be connected to.
* @param rowDataProvider The IRowDataProvider that should be used to access the selected row data.
* @param fullySelectedRowsOnly Flag to determine if only fully selected rows should be used
* to populate the selection or if any selection should be populated.
*/
public RowSelectionProvider(SelectionLayer selectionLayer, IRowDataProvider<T> rowDataProvider,
boolean fullySelectedRowsOnly) {
this(selectionLayer, rowDataProvider, fullySelectedRowsOnly, false);
}
/**
* Create a RowSelectionProvider configured with the given parameters.
* @param selectionLayer The SelectionLayer this ISelectionProvider should be connected to.
* @param rowDataProvider The IRowDataProvider that should be used to access the selected row data.
* @param fullySelectedRowsOnly Flag to determine if only fully selected rows should be used
* to populate the selection or if any selection should be populated.
* @param handleSameRowSelection Flag to configure whether only SelectionChangedEvents should be
* fired if the row selection changes or even if you just select another column.
*/
public RowSelectionProvider(SelectionLayer selectionLayer, IRowDataProvider<T> rowDataProvider,
boolean fullySelectedRowsOnly, boolean handleSameRowSelection) {
this.selectionLayer = selectionLayer;
this.rowDataProvider = rowDataProvider;
this.fullySelectedRowsOnly = fullySelectedRowsOnly;
this.handleSameRowSelection = handleSameRowSelection;
this.selectionLayer.addLayerListener(this);
}
/**
* Updates this RowSelectionProvider so it handles the selection of another SelectionLayer and
* IRowDataProvider.
* <p>
* This method was introduced to add support for multiple selection provider within one part.
* As replacing the selection provider during the lifetime of a part is not properly supported
* by the workbench, this implementation adds the possibility to exchange the control that
* serves as selection provider by exchanging the references in the selection provider itself.
*
* @param selectionLayer The SelectionLayer this ISelectionProvider should be connected to.
* @param rowDataProvider The IRowDataProvider that should be used to access the selected row data.
*/
public void updateSelectionProvider(SelectionLayer selectionLayer, IRowDataProvider<T> rowDataProvider) {
//unregister as listener from the current set SelectionLayer
this.selectionLayer.removeLayerListener(this);
//update the references on which this RowSelectionProvider should operate
this.selectionLayer = selectionLayer;
this.rowDataProvider = rowDataProvider;
//register on the new set SelectionLayer as listener
this.selectionLayer.addLayerListener(this);
}
@Override
public void addSelectionChangedListener(ISelectionChangedListener listener) {
listeners.add(listener);
}
@Override
public ISelection getSelection() {
return populateRowSelection(selectionLayer, rowDataProvider, fullySelectedRowsOnly);
}
@Override
public void removeSelectionChangedListener(ISelectionChangedListener listener) {
listeners.remove(listener);
}
@Override
@SuppressWarnings("unchecked")
public void setSelection(ISelection selection) {
if (selectionLayer != null && selection instanceof IStructuredSelection) {
if (!addSelectionOnSet) {
selectionLayer.clear(false);
}
if (!selection.isEmpty()) {
List<T> rowObjects = ((IStructuredSelection) selection).toList();
final RangeList rowPositions = new RangeList();
for (T rowObject : rowObjects) {
int rowIndex = rowDataProvider.indexOfRowObject(rowObject);
int rowPosition = selectionLayer.getRowPositionByIndex(rowIndex);
rowPositions.values().add(rowPosition);
}
if (!rowPositions.isEmpty()) {
final int max = rowPositions.values().last();
if (max >= 0) {
selectionLayer.doCommand(new SelectRowsCommand(selectionLayer,
0, rowPositions, false, true, max ));
}
}
}
}
}
@Override
public void handleLayerEvent(ILayerEvent event) {
if (event instanceof ISelectionEvent) {
ISelection selection = getSelection();
if (handleSameRowSelection || !selection.equals(previousSelection)) {
try {
for (ISelectionChangedListener listener : listeners) {
listener.selectionChanged(new SelectionChangedEvent(this, selection));
}
} finally {
previousSelection = selection;
}
}
}
}
@SuppressWarnings("rawtypes")
static StructuredSelection populateRowSelection(SelectionLayer selectionLayer, IRowDataProvider rowDataProvider, boolean fullySelectedRowsOnly) {
List<RowObjectIndexHolder<Object>> rows = new ArrayList<RowObjectIndexHolder<Object>>();
if (selectionLayer != null) {
final RangeList selectedRows = (fullySelectedRowsOnly) ?
selectionLayer.getFullySelectedRowPositions() : selectionLayer.getSelectedRowPositions();
for (final IValueIterator rowIter = selectedRows.values().iterator(); rowIter.hasNext(); ) {
addToSelection(rows, rowIter.nextValue(), selectionLayer, rowDataProvider);
}
}
Collections.sort(rows);
List<Object> rowObjects = new ArrayList<Object>();
for(RowObjectIndexHolder<Object> holder : rows){
rowObjects.add(holder.getRow());
}
return rows.isEmpty() ? StructuredSelection.EMPTY : new StructuredSelection(rowObjects);
}
@SuppressWarnings("rawtypes")
private static void addToSelection(List<RowObjectIndexHolder<Object>> rows, int rowPosition, SelectionLayer selectionLayer, IRowDataProvider rowDataProvider) {
int rowIndex = selectionLayer.getRowIndexByPosition(rowPosition);
if (rowIndex >= 0 && rowIndex < rowDataProvider.getRowCount()) {
Object rowObject = rowDataProvider.getRowObject(rowIndex);
rows.add(new RowObjectIndexHolder<Object>(rowIndex, rowObject));
}
}
/**
* Configure whether <code>setSelection()</code> should add or set the selection.
* <p>
* This was added for convenience because the initial code always added the selection
* on <code>setSelection()</code> by creating a SelectRowsCommand with the withControlMask set to <code>true</code>.
* Looking at the specification, <code>setSelection()</code> is used to set the <b>new</b> selection.
* So the default here is now to set instead of add. But for convenience to older code
* that relied on the add behaviour it is now possible to change it back to adding.
*
* @param addSelectionOnSet <code>true</code> to add the selection on calling <code>setSelection()</code>
* The default is <code>false</code> to behave like specified in RowSelectionProvider
*/
public void setAddSelectionOnSet(boolean addSelectionOnSet) {
this.addSelectionOnSet = addSelectionOnSet;
}
}