blob: 557b0095aa264bc8d51d8b0c1ac6f6f58adc77eb [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.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.eclipse.nebula.widgets.nattable.command.ILayerCommand;
import org.eclipse.nebula.widgets.nattable.coordinate.IValueIterator;
import org.eclipse.nebula.widgets.nattable.coordinate.PositionCoordinate;
import org.eclipse.nebula.widgets.nattable.coordinate.RangeList;
import org.eclipse.nebula.widgets.nattable.coordinate.RangeList.ValueIterator;
import org.eclipse.nebula.widgets.nattable.coordinate.Rectangle;
import org.eclipse.nebula.widgets.nattable.copy.command.CopyDataCommandHandler;
import org.eclipse.nebula.widgets.nattable.edit.command.EditSelectionCommandHandler;
import org.eclipse.nebula.widgets.nattable.grid.command.InitializeAutoResizeColumnsCommandHandler;
import org.eclipse.nebula.widgets.nattable.grid.command.InitializeAutoResizeRowsCommandHandler;
import org.eclipse.nebula.widgets.nattable.hideshow.command.ColumnHideCommand;
import org.eclipse.nebula.widgets.nattable.hideshow.command.MultiColumnHideCommand;
import org.eclipse.nebula.widgets.nattable.hideshow.command.MultiRowHideCommand;
import org.eclipse.nebula.widgets.nattable.hideshow.command.RowHideCommand;
import org.eclipse.nebula.widgets.nattable.layer.IUniqueIndexLayer;
import org.eclipse.nebula.widgets.nattable.layer.LabelStack;
import org.eclipse.nebula.widgets.nattable.layer.TransformIndexLayer;
import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;
import org.eclipse.nebula.widgets.nattable.painter.layer.ILayerPainter;
import org.eclipse.nebula.widgets.nattable.resize.command.ColumnResizeCommand;
import org.eclipse.nebula.widgets.nattable.resize.command.MultiColumnResizeCommand;
import org.eclipse.nebula.widgets.nattable.resize.command.MultiRowResizeCommand;
import org.eclipse.nebula.widgets.nattable.resize.command.RowResizeCommand;
import org.eclipse.nebula.widgets.nattable.search.command.SearchGridCellsCommandHandler;
import org.eclipse.nebula.widgets.nattable.selection.command.ClearAllSelectionsCommand;
import org.eclipse.nebula.widgets.nattable.selection.command.SelectAllCommand;
import org.eclipse.nebula.widgets.nattable.selection.config.DefaultSelectionLayerConfiguration;
import org.eclipse.nebula.widgets.nattable.selection.event.CellSelectionEvent;
import org.eclipse.nebula.widgets.nattable.selection.event.SelectionLayerStructuralChangeEventHandler;
import org.eclipse.nebula.widgets.nattable.style.DisplayMode;
import org.eclipse.nebula.widgets.nattable.style.SelectionStyleLabels;
/**
* Enables selection of column, rows, cells etc. on the table.
* Also responds to UI bindings by changing the current selection.
* Internally it uses the {@link ISelectionModel} to track the selection state.
*
* @see DefaultSelectionLayerConfiguration
* @see MoveDirectionEnum
*/
public class SelectionLayer extends TransformIndexLayer {
public static final int MOVE_ALL = -1;
public static final int NO_SELECTION = -1;
public enum MoveDirectionEnum {
UP, DOWN, LEFT, RIGHT, NONE;
}
protected ISelectionModel selectionModel;
protected IUniqueIndexLayer underlyingLayer;
protected final PositionCoordinate lastSelectedCell;
protected final PositionCoordinate selectionAnchor;
protected Rectangle lastSelectedRegion;
private final SelectRowCommandHandler selectRowCommandHandler;
private final SelectCellCommandHandler selectCellCommandHandler;
private final SelectColumnCommandHandler selectColumnCommandHandler;
public SelectionLayer(IUniqueIndexLayer underlyingLayer) {
this(underlyingLayer, null, true);
}
public SelectionLayer(IUniqueIndexLayer underlyingLayer, boolean useDefaultConfiguration) {
this(underlyingLayer, null, useDefaultConfiguration);
}
public SelectionLayer(IUniqueIndexLayer underlyingLayer, ISelectionModel selectionModel, boolean useDefaultConfiguration) {
this(underlyingLayer, selectionModel, useDefaultConfiguration, true);
}
public SelectionLayer(IUniqueIndexLayer underlyingLayer, ISelectionModel selectionModel, boolean useDefaultConfiguration, boolean registerDefaultEventHandler) {
super(underlyingLayer);
this.underlyingLayer = underlyingLayer;
setLayerPainter(new SelectionLayerPainter());
this.selectionModel = selectionModel != null ? selectionModel : new SelectionModel(this);
lastSelectedCell = new PositionCoordinate(this, NO_SELECTION, NO_SELECTION);
selectionAnchor = new PositionCoordinate(this, NO_SELECTION, NO_SELECTION);
selectRowCommandHandler = new SelectRowCommandHandler(this);
selectCellCommandHandler = new SelectCellCommandHandler(this);
selectColumnCommandHandler = new SelectColumnCommandHandler(this);
registerCommandHandlers();
if(registerDefaultEventHandler){
registerEventHandler(new SelectionLayerStructuralChangeEventHandler(this, this.selectionModel));
}
if (useDefaultConfiguration) {
addConfiguration(new DefaultSelectionLayerConfiguration());
}
}
public ISelectionModel getSelectionModel() {
return selectionModel;
}
public void setSelectionModel(ISelectionModel selectionModel) {
this.selectionModel = selectionModel;
}
@Override
public ILayerPainter getLayerPainter() {
return layerPainter;
}
public void addSelection(Rectangle selection) {
if (selection != lastSelectedRegion) {
selectionAnchor.columnPosition = lastSelectedCell.columnPosition;
selectionAnchor.rowPosition = lastSelectedCell.rowPosition;
lastSelectedRegion = selection;
}
selectionModel.addSelection(selection);
}
public void clear() {
clear(true);
}
public void clear(boolean fireSelectionEvent) {
selectionModel.clearSelection();
boolean validLastSelectedCell = lastSelectedCell.columnPosition != NO_SELECTION && lastSelectedCell.rowPosition != NO_SELECTION;
lastSelectedCell.columnPosition = -1;
lastSelectedCell.rowPosition = -1;
lastSelectedRegion = new Rectangle(0,0,0,0);
selectionAnchor.columnPosition = -1;
selectionAnchor.rowPosition = -1;
if (validLastSelectedCell && fireSelectionEvent) {
fireCellSelectionEvent(lastSelectedCell.columnPosition, lastSelectedCell.rowPosition, false, false, false);
}
}
public void clearSelection(int columnPosition, int rowPosition) {
selectionModel.clearSelection(columnPosition, rowPosition);
}
public void clearSelection(Rectangle selection) {
selectionModel.clearSelection(selection);
//if the selection anchor is within the selection that is removed
//it needs to be cleared also
if (selection.contains(selectionAnchor.columnPosition, selectionAnchor.rowPosition)) {
selectionAnchor.columnPosition = -1;
selectionAnchor.rowPosition = -1;
}
}
public void selectAll() {
Rectangle selection = new Rectangle(0, 0, getColumnCount(), getRowCount());
if(lastSelectedCell.columnPosition == SelectionLayer.NO_SELECTION || lastSelectedCell.rowPosition == SelectionLayer.NO_SELECTION){
lastSelectedCell.rowPosition = 0;
lastSelectedCell.columnPosition = 0;
}
addSelection(selection);
fireCellSelectionEvent(lastSelectedCell.columnPosition, lastSelectedCell.rowPosition, false, false, false);
}
// Cell features
public boolean isCellPositionSelected(int columnPosition, int rowPosition) {
return selectionModel.isCellPositionSelected(columnPosition, rowPosition);
}
public void setSelectedCell(int columnPosition, int rowPosition) {
selectCell(columnPosition, rowPosition, false, false);
}
/**
* When extending a selected area we need to start adding cells from the last selected cell.
* If we are not extending a selection we need to move from the selection <i>anchor</i>.
*/
protected PositionCoordinate getCellPositionToMoveFrom(boolean withShiftMask, boolean withControlMask) {
return (!withShiftMask && !withControlMask) ? getSelectionAnchor() : getLastSelectedCellPosition();
}
public List<PositionCoordinate> getSelectedCellPositions() {
final RangeList selectedColumnPositions = getSelectedColumnPositions();
final RangeList selectedRowPositions = getSelectedRowPositions();
final List<PositionCoordinate> selectedCells = new LinkedList<PositionCoordinate>();
for (final IValueIterator columnIter = selectedColumnPositions.values().iterator(); columnIter.hasNext(); ) {
final int columnPosition = columnIter.nextValue();
for (final IValueIterator rowIter = selectedRowPositions.values().iterator(); rowIter.hasNext(); ) {
final int rowPosition = rowIter.nextValue();
if (selectionModel.isCellPositionSelected(columnPosition, rowPosition)) {
selectedCells.add(new PositionCoordinate(this, columnPosition, rowPosition));
}
}
}
return selectedCells;
}
/**
* Retrieves the ILayerCells out of the SelectionLayer that are currently marked as selected in
* the SelectionModel. Takes spanning into account.
* @return The selected ILayerCells
*/
public Collection<ILayerCell> getSelectedCells() {
Set<ILayerCell> selectedCells = new HashSet<ILayerCell>();
List<PositionCoordinate> selectedCoords = getSelectedCellPositions();
for (PositionCoordinate coord : selectedCoords) {
selectedCells.add(getCellByPosition(coord.columnPosition, coord.rowPosition));
}
return selectedCells;
}
/**
* Calculates the selected cells - taking into account Shift and Ctrl key presses.
*/
public void selectCell(int columnPosition, int rowPosition, boolean withShiftMask, boolean withControlMask) {
selectCellCommandHandler.selectCell(columnPosition, rowPosition, withShiftMask, withControlMask);
}
public void selectRegion(int startColumnPosition, int startRowPosition, int regionWidth, int regionHeight) {
if (lastSelectedRegion == null) {
lastSelectedRegion = new Rectangle(startColumnPosition, startRowPosition, regionWidth, regionHeight);
} else {
lastSelectedRegion.x = startColumnPosition;
lastSelectedRegion.y = startRowPosition;
lastSelectedRegion.width = regionWidth;
lastSelectedRegion.height = regionHeight;
}
selectionModel.addSelection(new Rectangle(lastSelectedRegion.x, lastSelectedRegion.y, lastSelectedRegion.width, lastSelectedRegion.height));
}
// Selection anchor
public PositionCoordinate getSelectionAnchor() {
return selectionAnchor;
}
public void moveSelectionAnchor(int startColumnPositionInRegion, int startRowPosition) {
selectionAnchor.columnPosition = startColumnPositionInRegion;
selectionAnchor.rowPosition = startRowPosition;
}
// Last selected
public PositionCoordinate getLastSelectedCellPosition() {
if (lastSelectedCell.columnPosition != NO_SELECTION && lastSelectedCell.rowPosition != NO_SELECTION) {
return lastSelectedCell;
} else {
return null;
}
}
public void setLastSelectedCell(int columnPosition, int rowPosition) {
lastSelectedCell.columnPosition = columnPosition;
lastSelectedCell.rowPosition = rowPosition;
}
public Rectangle getLastSelectedRegion() {
return lastSelectedRegion;
}
// Column features
public boolean hasColumnSelection() {
return lastSelectedCell.columnPosition != NO_SELECTION;
}
public RangeList getSelectedColumnPositions() {
return selectionModel.getSelectedColumnPositions();
}
public boolean isColumnPositionSelected(int columnPosition) {
return selectionModel.isColumnPositionSelected(columnPosition);
}
public RangeList getFullySelectedColumnPositions() {
return selectionModel.getFullySelectedColumnPositions();
}
public boolean isColumnPositionFullySelected(int columnPosition) {
return selectionModel.isColumnPositionFullySelected(columnPosition);
}
public void selectColumn(int columnPosition, int rowPosition, boolean withShiftMask, boolean withControlMask) {
selectColumnCommandHandler.selectColumn(columnPosition, rowPosition, withShiftMask, withControlMask);
}
// Row features
public boolean hasRowSelection() {
return lastSelectedCell.rowPosition != NO_SELECTION;
}
public int getSelectedRowCount() {
return selectionModel.getSelectedRowCount();
}
public RangeList getSelectedRowPositions() {
return selectionModel.getSelectedRowPositions();
}
public boolean isRowPositionSelected(int rowPosition) {
return selectionModel.isRowPositionSelected(rowPosition);
}
public RangeList getFullySelectedRowPositions() {
return selectionModel.getFullySelectedRowPositions();
}
public boolean isRowPositionFullySelected(int rowPosition) {
return selectionModel.isRowPositionFullySelected(rowPosition);
}
public void selectRow(int columnPosition, int rowPosition, boolean withShiftMask, boolean withControlMask) {
selectRowCommandHandler.selectRows(columnPosition, new RangeList(rowPosition), withShiftMask, withControlMask, rowPosition);
}
// ILayer methods
@Override
public String getDisplayModeByPosition(int columnPosition, int rowPosition) {
if (isCellPositionSelected(columnPosition, rowPosition)) {
return DisplayMode.SELECT;
} else {
return super.getDisplayModeByPosition(columnPosition, rowPosition);
}
}
@Override
public LabelStack getConfigLabelsByPosition(int columnPosition, int rowPosition) {
LabelStack labelStack = super.getConfigLabelsByPosition(columnPosition, rowPosition);
ILayerCell cell = getCellByPosition(columnPosition, rowPosition);
if (cell != null) {
Rectangle cellRectangle = new Rectangle(
cell.getOriginColumnPosition(),
cell.getOriginRowPosition(),
cell.getColumnSpan(),
cell.getRowSpan());
if (cellRectangle.contains(selectionAnchor.columnPosition, selectionAnchor.rowPosition)) {
labelStack.addLabel(SelectionStyleLabels.SELECTION_ANCHOR_STYLE);
}
}
return labelStack;
}
// Command handling
@Override
protected void registerCommandHandlers() {
// Command handlers also registered by the DefaultSelectionLayerConfiguration
registerCommandHandler(selectCellCommandHandler);
registerCommandHandler(selectRowCommandHandler);
registerCommandHandler(selectColumnCommandHandler);
registerCommandHandler(new EditSelectionCommandHandler(this));
registerCommandHandler(new InitializeAutoResizeColumnsCommandHandler(this));
registerCommandHandler(new InitializeAutoResizeRowsCommandHandler(this));
registerCommandHandler(new CopyDataCommandHandler(this));
registerCommandHandler(new SearchGridCellsCommandHandler(this));
}
@Override
public boolean doCommand(ILayerCommand command) {
if (command instanceof SelectAllCommand && command.convertToTargetLayer(this)) {
selectAll();
return true;
} else if (command instanceof ClearAllSelectionsCommand && command.convertToTargetLayer(this)) {
clear();
return true;
} else if (command instanceof ColumnHideCommand && command.convertToTargetLayer(this)) {
return handleColumnHideCommand((ColumnHideCommand)command);
} else if (command instanceof MultiColumnHideCommand && command.convertToTargetLayer(this)) {
return handleMultiColumnHideCommand((MultiColumnHideCommand)command);
} else if (command instanceof RowHideCommand && command.convertToTargetLayer(this)) {
return handleRowHideCommand((RowHideCommand)command);
} else if (command instanceof MultiRowHideCommand && command.convertToTargetLayer(this)) {
return handleMultiRowHideCommand((MultiRowHideCommand)command);
} else if (command instanceof ColumnResizeCommand && command.convertToTargetLayer(this)) {
return handleColumnResizeCommand((ColumnResizeCommand) command);
} else if (command instanceof RowResizeCommand && command.convertToTargetLayer(this)) {
return handleRowResizeCommand((RowResizeCommand) command);
}
return super.doCommand(command);
}
public void fireCellSelectionEvent(int columnPosition, int rowPosition,
boolean forcingEntireCellIntoViewport, boolean withShiftMask, boolean withControlMask) {
fireLayerEvent(new CellSelectionEvent(this, columnPosition, rowPosition,
withShiftMask, withControlMask ));
}
// command transformations
/**
* Will check if there are fully selected column positions. If there is at least one fully
* selected column position, the {@link ColumnHideCommand} will be consumed and a
* {@link MultiColumnHideCommand} will be created and executed further down the layer stack,
* that contains all fully selected column positions. Otherwise the given command will be
* executed further.<br>
*
* This is necessary because neither the ColumnHideShowLayer nor the action that caused the execution
* of the {@link ColumnHideCommand} is aware of the presence of the {@link SelectionLayer}.
* Without this transformation, only the column on which the action was called will be hidden instead
* of all selected ones.
*
* @param command The {@link ColumnHideCommand} to process
* @return <code>true</code> if the command has been handled, <code>false</code> otherwise
*/
protected boolean handleColumnHideCommand(ColumnHideCommand command) {
if (isColumnPositionFullySelected(command.getColumnPosition())) {
return handleMultiColumnHideCommand(new MultiColumnHideCommand(this,
getFullySelectedColumnPositions()) );
} else {
return super.doCommand(command);
}
}
/**
* Previous to processing the given {@link MultiColumnHideCommand} down the layer stack, the
* fully selected column positions selection state will be cleared. This is necessary so the
* selection also disappears for the selected columns. Otherwise after hiding the selection
* will be showed for different columns.
*
* @param command The {@link MultiColumnHideCommand} to process
* @return <code>true</code> if the command has been handled, <code>false</code> otherwise
*/
protected boolean handleMultiColumnHideCommand(MultiColumnHideCommand command) {
for (final IValueIterator columnIter = new ValueIterator(command.getPositions()); columnIter.hasNext(); ) {
final int columnPosition = columnIter.nextValue();
if (isColumnPositionFullySelected(columnPosition)) {
Rectangle selection = new Rectangle(columnPosition, 0, 1, Integer.MAX_VALUE);
clearSelection(selection);
}
}
return super.doCommand(command);
}
/**
* Will check if there are fully selected row positions. If there is at least one fully
* selected row position, the {@link RowHideCommand} will be consumed and a
* {@link MultiRowHideCommand} will be created and executed further down the layer stack,
* that contains all fully selected row positions. Otherwise the given command will be
* executed further.<br>
*
* This is necessary because neither the RowHideShowLayer nor the action that caused the execution
* of the {@link RowHideCommand} is aware of the presence of the {@link SelectionLayer}.
* Without this transformation, only the row on which the action was called will be hidden instead
* of all selected ones.
*
* @param command The {@link RowHideCommand} to process
* @return <code>true</code> if the command has been handled, <code>false</code> otherwise
*/
protected boolean handleRowHideCommand(RowHideCommand command) {
if (isRowPositionFullySelected(command.getRowPosition())) {
return handleMultiRowHideCommand(new MultiRowHideCommand(this,
getFullySelectedRowPositions()) );
} else {
return super.doCommand(command);
}
}
/**
* Previous to processing the given {@link MultiRowHideCommand} down the layer stack, the
* fully selected row positions selection state will be cleared. This is necessary so the
* selection also disappears for the selected rows. Otherwise after hiding the selection
* will be showed for different rows.
*
* @param command The {@link MultiRowHideCommand} to process
* @return <code>true</code> if the command has been handled, <code>false</code> otherwise
*/
protected boolean handleMultiRowHideCommand(MultiRowHideCommand command) {
for (final IValueIterator rowIter = new ValueIterator(command.getPositions()); rowIter.hasNext(); ) {
final int rowPosition = rowIter.nextValue();
if (isRowPositionFullySelected(rowPosition)) {
Rectangle selection = new Rectangle(0, rowPosition, Integer.MAX_VALUE, 1);
clearSelection(selection);
}
}
return super.doCommand(command);
}
/**
* Will check if there are fully selected column positions. If there is at least one fully
* selected column position, the {@link ColumnResizeCommand} will be consumed and a
* {@link MultiColumnResizeCommand} will be created and executed further down the layer stack,
* that contains all fully selected column positions. Otherwise the given command will be
* executed further.<br>
*
* This is necessary because neither the underlying layers are not aware of the presence of the
* {@link SelectionLayer}. Without this transformation, only the column on which the action was
* called will be resized instead of all selected ones.
*
* @param command The {@link ColumnResizeCommand} to process
* @return <code>true</code> if the command has been handled, <code>false</code> otherwise
*/
protected boolean handleColumnResizeCommand(ColumnResizeCommand command) {
if (isColumnPositionFullySelected(command.getColumnPosition())) {
return super.doCommand(new MultiColumnResizeCommand(this,
selectionModel.getFullySelectedColumnPositions(), command.getNewColumnWidth()));
} else {
return super.doCommand(command);
}
}
/**
* Will check if there are fully selected row positions. If there is at least one fully
* selected row position, the {@link RowResizeCommand} will be consumed and a
* {@link MultiRowResizeCommand} will be created and executed further down the layer stack,
* that contains all fully selected row positions. Otherwise the given command will be
* executed further.<br>
*
* This is necessary because neither the underlying layers are not aware of the presence of the
* {@link SelectionLayer}. Without this transformation, only the row on which the action was
* called will be resized instead of all selected ones.
*
* @param command The {@link RowResizeCommand} to process
* @return <code>true</code> if the command has been handled, <code>false</code> otherwise
*/
protected boolean handleRowResizeCommand(RowResizeCommand command) {
if (isRowPositionFullySelected(command.getRowPosition())) {
return super.doCommand(new MultiRowResizeCommand(this,
selectionModel.getFullySelectedRowPositions(), command.getNewHeight()) );
} else {
return super.doCommand(command);
}
}
}