blob: 54ffb68708d61a9f113f807c3efa6786fa575ee4 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2012, 2020 Original authors 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:
* Original authors and others - initial API and implementation
* Dirk Fauth <dirk.fauth@googlemail.com> - added ITraversalStrategy handling
******************************************************************************/
package org.eclipse.nebula.widgets.nattable.selection;
import org.eclipse.nebula.widgets.nattable.coordinate.PositionCoordinate;
import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;
import org.eclipse.nebula.widgets.nattable.selection.ITraversalStrategy.TraversalScope;
import org.eclipse.nebula.widgets.nattable.selection.command.MoveSelectionCommand;
/**
* Specifies the semantics of moving the selection in the table, based on
* selecting the adjoining cell(s).
*/
public class MoveCellSelectionCommandHandler extends MoveSelectionCommandHandler<MoveSelectionCommand> {
protected PositionCoordinate lastSelectedCellPosition;
protected int newSelectedColumnPosition;
protected int newSelectedRowPosition;
/**
* Create a MoveCellSelectionCommandHandler for the given
* {@link SelectionLayer}. Uses the
* {@link ITraversalStrategy#AXIS_TRAVERSAL_STRATEGY} as default strategy
* for selection movement.
*
* @param selectionLayer
* The {@link SelectionLayer} on which the selection should be
* performed.
*/
public MoveCellSelectionCommandHandler(SelectionLayer selectionLayer) {
super(selectionLayer);
}
/**
* Create a MoveCellSelectionCommandHandler for the given
* {@link SelectionLayer}.
*
* @param selectionLayer
* The {@link SelectionLayer} on which the selection should be
* performed.
* @param traversalStrategy
* The strategy that should be used for selection movements. Can
* not be <code>null</code>.
*/
public MoveCellSelectionCommandHandler(SelectionLayer selectionLayer, ITraversalStrategy traversalStrategy) {
super(selectionLayer, traversalStrategy);
}
/**
* Create a MoveCellSelectionCommandHandler for the given
* {@link SelectionLayer} .
*
* @param selectionLayer
* The {@link SelectionLayer} on which the selection should be
* performed.
* @param horizontalTraversalStrategy
* The strategy that should be used for horizontal selection
* movements. Can not be <code>null</code>.
* @param verticalTraversalStrategy
* The strategy that should be used for vertical selection
* movements. Can not be <code>null</code>.
*/
public MoveCellSelectionCommandHandler(SelectionLayer selectionLayer,
ITraversalStrategy horizontalTraversalStrategy, ITraversalStrategy verticalTraversalStrategy) {
super(selectionLayer, horizontalTraversalStrategy, verticalTraversalStrategy);
}
@Override
protected void moveLastSelectedLeft(ITraversalStrategy traversalStrategy, boolean withShiftMask, boolean withControlMask) {
if (this.selectionLayer.hasColumnSelection()) {
this.lastSelectedCellPosition = this.selectionLayer.getCellPositionToMoveFrom(withShiftMask, withControlMask);
ILayerCell lastSelectedCell = this.selectionLayer.getCellByPosition(
this.lastSelectedCellPosition.columnPosition,
this.lastSelectedCellPosition.rowPosition);
if (lastSelectedCell != null) {
int stepSize = traversalStrategy.getStepCount();
this.newSelectedColumnPosition = (stepSize >= 0) ? (lastSelectedCell.getOriginColumnPosition() - stepSize) : 0;
this.newSelectedRowPosition = this.lastSelectedCellPosition.rowPosition;
ILayerCell newSelected = this.selectionLayer.getCellByPosition(
this.newSelectedColumnPosition,
this.newSelectedRowPosition);
if (newSelected != null) {
this.newSelectedColumnPosition = newSelected.getOriginColumnPosition();
}
// boolean flag to stop traversal if calculated target is
// invalid needed to avoid endless loop if there are no further
// valid traversal targets
boolean stopTraversalOnInvalid = false;
if (this.newSelectedColumnPosition < 0) {
if (traversalStrategy.getTraversalScope().equals(TraversalScope.AXIS)) {
if (!traversalStrategy.isCycle()) {
// on axis scope with no cycle, stop moving
this.newSelectedColumnPosition = 0;
stopTraversalOnInvalid = true;
} else {
// on axis scope with cycle, move to end
while (this.newSelectedColumnPosition < 0) {
this.newSelectedColumnPosition = this.newSelectedColumnPosition + this.selectionLayer.getColumnCount();
}
}
} else if (traversalStrategy.getTraversalScope().equals(TraversalScope.TABLE)) {
// on table scope, move to end
int rowMove = 0;
while (this.newSelectedColumnPosition < 0) {
this.newSelectedColumnPosition = this.newSelectedColumnPosition + this.selectionLayer.getColumnCount();
rowMove++;
}
this.newSelectedRowPosition = this.newSelectedRowPosition - rowMove;
if (this.newSelectedRowPosition < 0) {
if (traversalStrategy.isCycle()) {
// at the top and cycle so go to bottom
this.newSelectedRowPosition = this.newSelectedRowPosition + this.selectionLayer.getRowCount();
} else {
// at the top and no cycle so stop moving
this.newSelectedColumnPosition = 0;
this.newSelectedRowPosition = 0;
stopTraversalOnInvalid = true;
}
}
}
}
if (positionMoved()) {
// check if calculated target is valid, otherwise move to
// adjacent
if (!traversalStrategy.isValidTarget(lastSelectedCell,
this.selectionLayer.getCellByPosition(this.newSelectedColumnPosition, this.newSelectedRowPosition))) {
// for MOVE_ALL we need to go backwards to find the last
// valid position
if (stepSize == SelectionLayer.MOVE_ALL) {
for (int i = this.newSelectedColumnPosition; i < this.selectionLayer.getColumnCount(); i++) {
ILayerCell newPosition = this.selectionLayer.getCellByPosition(i, this.newSelectedRowPosition);
if (traversalStrategy.isValidTarget(lastSelectedCell, newPosition)) {
this.newSelectedColumnPosition = i;
if (!withShiftMask) {
this.selectionLayer.clear(false);
}
this.selectionLayer.selectCell(
this.newSelectedColumnPosition,
this.newSelectedRowPosition,
withShiftMask,
withControlMask);
this.selectionLayer.fireCellSelectionEvent(
this.newSelectedColumnPosition,
this.newSelectedRowPosition,
true,
withShiftMask,
withControlMask);
break;
}
}
} else {
if (!stopTraversalOnInvalid) {
moveLastSelectedLeft(
createIncrementalStrategy(traversalStrategy),
withShiftMask, withControlMask);
} else {
// since the calculated target is invalid and
// invalid traversal movement should stop, the
// new selected position is the last valid one
this.newSelectedColumnPosition = this.lastSelectedCellPosition.columnPosition;
this.newSelectedRowPosition = this.lastSelectedCellPosition.rowPosition;
}
}
} else {
if (stepSize == SelectionLayer.MOVE_ALL && !withShiftMask) {
this.selectionLayer.clear(false);
}
this.selectionLayer.selectCell(
this.newSelectedColumnPosition,
this.newSelectedRowPosition,
withShiftMask,
withControlMask);
this.selectionLayer.fireCellSelectionEvent(
this.newSelectedColumnPosition,
this.newSelectedRowPosition,
true,
withShiftMask,
withControlMask);
}
}
}
}
}
@Override
protected void moveLastSelectedRight(final ITraversalStrategy traversalStrategy, boolean withShiftMask, boolean withControlMask) {
if (this.selectionLayer.hasColumnSelection()) {
this.lastSelectedCellPosition = this.selectionLayer.getCellPositionToMoveFrom(withShiftMask, withControlMask);
ILayerCell lastSelectedCell = this.selectionLayer.getCellByPosition(
this.lastSelectedCellPosition.columnPosition,
this.lastSelectedCellPosition.rowPosition);
if (lastSelectedCell != null) {
int stepSize = traversalStrategy.getStepCount();
this.newSelectedColumnPosition = (stepSize >= 0)
? (lastSelectedCell.getOriginColumnPosition() + lastSelectedCell.getColumnSpan() - 1 + stepSize)
: (this.selectionLayer.getColumnCount() - 1);
this.newSelectedRowPosition = this.lastSelectedCellPosition.rowPosition;
ILayerCell newSelected = this.selectionLayer.getCellByPosition(
this.newSelectedColumnPosition,
this.newSelectedRowPosition);
if (newSelected != null) {
// if cell has column spanning, ensure that the whole cell
// is displayed
this.newSelectedColumnPosition = newSelected.getOriginColumnPosition() + newSelected.getColumnSpan() - 1;
}
// boolean flag to stop traversal if calculated target is
// invalid needed to avoid endless loop if there are no further
// valid traversal targets
boolean stopTraversalOnInvalid = false;
if (this.newSelectedColumnPosition >= this.selectionLayer.getColumnCount()) {
if (traversalStrategy.getTraversalScope().equals(TraversalScope.AXIS)) {
if (!traversalStrategy.isCycle()) {
// on axis scope with no cycle, stop moving
this.newSelectedColumnPosition = this.selectionLayer.getColumnCount() - 1;
stopTraversalOnInvalid = true;
} else {
// on axis scope with cycle, start over at table
// beginning
while (this.newSelectedColumnPosition >= this.selectionLayer.getColumnCount()) {
this.newSelectedColumnPosition = this.newSelectedColumnPosition - this.selectionLayer.getColumnCount();
}
}
} else if (traversalStrategy.getTraversalScope().equals(TraversalScope.TABLE)) {
// on table scope, start over at table beginning
int rowMove = 0;
while (this.newSelectedColumnPosition >= this.selectionLayer.getColumnCount()) {
this.newSelectedColumnPosition = this.newSelectedColumnPosition - this.selectionLayer.getColumnCount();
rowMove++;
}
this.newSelectedRowPosition = this.newSelectedRowPosition + rowMove;
if (this.newSelectedRowPosition >= this.selectionLayer.getRowCount()) {
if (traversalStrategy.isCycle()) {
// at the bottom and cycle so go to top
this.newSelectedRowPosition = this.newSelectedRowPosition - this.selectionLayer.getRowCount();
} else {
// at the bottom and no cycle so stop moving
this.newSelectedColumnPosition = this.selectionLayer.getColumnCount() - 1;
this.newSelectedRowPosition = this.selectionLayer.getRowCount() - 1;
stopTraversalOnInvalid = true;
}
}
}
}
if (positionMoved()) {
// check if calculated target is valid, otherwise move to
// adjacent
if (!traversalStrategy.isValidTarget(lastSelectedCell,
this.selectionLayer.getCellByPosition(this.newSelectedColumnPosition, this.newSelectedRowPosition))) {
// for MOVE_ALL we need to go backwards to find the last
// valid position
if (stepSize == SelectionLayer.MOVE_ALL) {
for (int i = this.newSelectedColumnPosition; i >= 0; i--) {
ILayerCell newPosition = this.selectionLayer.getCellByPosition(i, this.newSelectedRowPosition);
if (traversalStrategy.isValidTarget(lastSelectedCell, newPosition)) {
this.newSelectedColumnPosition = i;
if (!withShiftMask) {
this.selectionLayer.clear(false);
}
this.selectionLayer.selectCell(
this.newSelectedColumnPosition,
this.newSelectedRowPosition,
withShiftMask,
withControlMask);
this.selectionLayer.fireCellSelectionEvent(
this.newSelectedColumnPosition,
this.newSelectedRowPosition,
true,
withShiftMask,
withControlMask);
break;
}
}
} else {
if (!stopTraversalOnInvalid) {
moveLastSelectedRight(
createIncrementalStrategy(traversalStrategy),
withShiftMask, withControlMask);
} else {
// since the calculated target is invalid and
// invalid traversal movement should stop, the
// new selected position is the last valid one
this.newSelectedColumnPosition = this.lastSelectedCellPosition.columnPosition;
this.newSelectedRowPosition = this.lastSelectedCellPosition.rowPosition;
}
}
} else {
if (stepSize == SelectionLayer.MOVE_ALL && !withShiftMask) {
this.selectionLayer.clear(false);
}
this.selectionLayer.selectCell(
this.newSelectedColumnPosition,
this.newSelectedRowPosition,
withShiftMask,
withControlMask);
this.selectionLayer.fireCellSelectionEvent(
this.newSelectedColumnPosition,
this.newSelectedRowPosition,
true,
withShiftMask,
withControlMask);
}
}
}
}
}
@Override
protected void moveLastSelectedUp(ITraversalStrategy traversalStrategy, boolean withShiftMask, boolean withControlMask) {
if (this.selectionLayer.hasRowSelection()) {
this.lastSelectedCellPosition = this.selectionLayer.getCellPositionToMoveFrom(withShiftMask, withControlMask);
ILayerCell lastSelectedCell = this.selectionLayer.getCellByPosition(
this.lastSelectedCellPosition.columnPosition,
this.lastSelectedCellPosition.rowPosition);
if (lastSelectedCell != null) {
int stepSize = traversalStrategy.getStepCount();
this.newSelectedColumnPosition = this.lastSelectedCellPosition.columnPosition;
this.newSelectedRowPosition = (stepSize >= 0) ? lastSelectedCell.getOriginRowPosition() - stepSize : 0;
ILayerCell newSelected = this.selectionLayer.getCellByPosition(
this.newSelectedColumnPosition,
this.newSelectedRowPosition);
if (newSelected != null) {
this.newSelectedRowPosition = newSelected.getOriginRowPosition();
}
// boolean flag to stop traversal if calculated target is
// invalid needed to avoid endless loop if there are no further
// valid traversal targets
boolean stopTraversalOnInvalid = false;
if (this.newSelectedRowPosition < 0) {
if (traversalStrategy.getTraversalScope().equals(TraversalScope.AXIS)) {
if (!traversalStrategy.isCycle()) {
// on axis scope with no cycle, stop moving
this.newSelectedRowPosition = 0;
stopTraversalOnInvalid = true;
} else {
// on axis scope with cycle, move to bottom
while (this.newSelectedRowPosition < 0) {
this.newSelectedRowPosition = this.newSelectedRowPosition + this.selectionLayer.getRowCount();
}
}
} else if (traversalStrategy.getTraversalScope().equals(TraversalScope.TABLE)) {
// on table scope, move to bottom
int columnMove = 0;
while (this.newSelectedRowPosition < 0) {
this.newSelectedRowPosition = this.newSelectedRowPosition + this.selectionLayer.getRowCount();
columnMove++;
}
this.newSelectedColumnPosition = this.newSelectedColumnPosition - columnMove;
if (this.newSelectedColumnPosition < 0) {
if (traversalStrategy.isCycle()) {
// at the beginning and cycle so go to end
this.newSelectedColumnPosition = this.newSelectedColumnPosition + this.selectionLayer.getColumnCount();
} else {
// at the top and no cycle so stop moving
this.newSelectedColumnPosition = 0;
this.newSelectedRowPosition = 0;
stopTraversalOnInvalid = true;
}
}
}
}
if (positionMoved()) {
// check if calculated target is valid, otherwise move to
// adjacent
if (!traversalStrategy.isValidTarget(lastSelectedCell,
this.selectionLayer.getCellByPosition(this.newSelectedColumnPosition, this.newSelectedRowPosition))) {
// for MOVE_ALL we need to go backwards to find the last
// valid position
if (stepSize == SelectionLayer.MOVE_ALL) {
for (int i = this.newSelectedRowPosition; i < this.selectionLayer.getRowCount(); i++) {
ILayerCell newPosition = this.selectionLayer.getCellByPosition(this.newSelectedColumnPosition, i);
if (traversalStrategy.isValidTarget(lastSelectedCell, newPosition)) {
this.newSelectedRowPosition = i;
if (!withShiftMask) {
this.selectionLayer.clear(false);
}
this.selectionLayer.selectCell(
this.newSelectedColumnPosition,
this.newSelectedRowPosition,
withShiftMask,
withControlMask);
this.selectionLayer.fireCellSelectionEvent(
this.newSelectedColumnPosition,
this.newSelectedRowPosition,
true,
withShiftMask,
withControlMask);
break;
}
}
} else {
if (!stopTraversalOnInvalid) {
moveLastSelectedUp(
createIncrementalStrategy(traversalStrategy),
withShiftMask, withControlMask);
} else {
// since the calculated target is invalid and
// invalid traversal movement should stop, the
// new selected position is the last valid one
this.newSelectedColumnPosition = this.lastSelectedCellPosition.columnPosition;
this.newSelectedRowPosition = this.lastSelectedCellPosition.rowPosition;
}
}
} else {
if (stepSize == SelectionLayer.MOVE_ALL && !withShiftMask) {
this.selectionLayer.clear(false);
}
this.selectionLayer.selectCell(
this.newSelectedColumnPosition,
this.newSelectedRowPosition,
withShiftMask,
withControlMask);
this.selectionLayer.fireCellSelectionEvent(
this.newSelectedColumnPosition,
this.newSelectedRowPosition,
true,
withShiftMask,
withControlMask);
}
}
}
}
}
@Override
protected void moveLastSelectedDown(ITraversalStrategy traversalStrategy, boolean withShiftMask, boolean withControlMask) {
if (this.selectionLayer.hasRowSelection()) {
this.lastSelectedCellPosition = this.selectionLayer.getCellPositionToMoveFrom(withShiftMask, withControlMask);
ILayerCell lastSelectedCell = this.selectionLayer.getCellByPosition(
this.lastSelectedCellPosition.columnPosition,
this.lastSelectedCellPosition.rowPosition);
if (lastSelectedCell != null) {
int stepSize = traversalStrategy.getStepCount();
this.newSelectedColumnPosition = this.lastSelectedCellPosition.columnPosition;
this.newSelectedRowPosition = (stepSize >= 0)
? lastSelectedCell.getOriginRowPosition() + lastSelectedCell.getRowSpan() - 1 + stepSize
: this.selectionLayer.getRowCount() - 1;
ILayerCell newSelected = this.selectionLayer.getCellByPosition(
this.newSelectedColumnPosition,
this.newSelectedRowPosition);
if (newSelected != null) {
// if cell has row spanning, ensure that the whole cell is
// displayed
this.newSelectedRowPosition = newSelected.getOriginRowPosition() + newSelected.getRowSpan() - 1;
}
// boolean flag to stop traversal if calculated target is
// invalid needed to avoid endless loop if there are no further
// valid traversal targets
boolean stopTraversalOnInvalid = false;
if (this.newSelectedRowPosition >= this.selectionLayer.getRowCount()) {
if (traversalStrategy.getTraversalScope().equals(TraversalScope.AXIS)) {
if (!traversalStrategy.isCycle()) {
// on axis scope with no cycle, stop moving
this.newSelectedRowPosition = this.selectionLayer.getRowCount() - 1;
stopTraversalOnInvalid = true;
} else {
// on axis scope with cycle, move to top
while (this.newSelectedRowPosition >= this.selectionLayer.getRowCount()) {
this.newSelectedRowPosition = this.newSelectedRowPosition - this.selectionLayer.getRowCount();
}
}
} else if (traversalStrategy.getTraversalScope().equals(TraversalScope.TABLE)) {
// on table scope, move to top
int columnMove = 0;
while (this.newSelectedRowPosition >= this.selectionLayer.getRowCount()) {
this.newSelectedRowPosition = this.newSelectedRowPosition - this.selectionLayer.getRowCount();
columnMove++;
}
this.newSelectedColumnPosition = this.newSelectedColumnPosition + columnMove;
if (this.newSelectedColumnPosition >= this.selectionLayer.getColumnCount()) {
if (traversalStrategy.isCycle()) {
// at the end and cycle so go to beginning
this.newSelectedColumnPosition = this.newSelectedColumnPosition - this.selectionLayer.getColumnCount();
} else {
// at the end and no cycle so stop moving
this.newSelectedColumnPosition = this.selectionLayer.getColumnCount() - 1;
this.newSelectedRowPosition = this.selectionLayer.getRowCount() - 1;
stopTraversalOnInvalid = true;
}
}
}
}
if (positionMoved()) {
// check if calculated target is valid, otherwise move to
// adjacent
int targetColumnPosition = newSelected != null ? newSelected.getColumnPosition() : this.newSelectedColumnPosition;
int targetRowPosition = newSelected != null ? newSelected.getRowPosition() : this.newSelectedRowPosition;
if (!traversalStrategy.isValidTarget(lastSelectedCell,
this.selectionLayer.getCellByPosition(targetColumnPosition, targetRowPosition))) {
// for MOVE_ALL we need to go backwards to find the last
// valid position
if (stepSize == SelectionLayer.MOVE_ALL) {
for (int i = this.newSelectedRowPosition; i >= 0; i--) {
ILayerCell newPosition = this.selectionLayer.getCellByPosition(this.newSelectedColumnPosition, i);
if (traversalStrategy.isValidTarget(lastSelectedCell, newPosition)) {
this.newSelectedRowPosition = i;
if (!withShiftMask) {
this.selectionLayer.clear(false);
}
this.selectionLayer.selectCell(
this.newSelectedColumnPosition,
this.newSelectedRowPosition,
withShiftMask,
withControlMask);
this.selectionLayer.fireCellSelectionEvent(
this.newSelectedColumnPosition,
this.newSelectedRowPosition,
true,
withShiftMask,
withControlMask);
break;
}
}
} else {
if (!stopTraversalOnInvalid) {
moveLastSelectedDown(
createIncrementalStrategy(traversalStrategy),
withShiftMask, withControlMask);
} else {
// since the calculated target is invalid and
// invalid traversal movement should stop, the
// new selected position is the last valid one
this.newSelectedColumnPosition = this.lastSelectedCellPosition.columnPosition;
this.newSelectedRowPosition = this.lastSelectedCellPosition.rowPosition;
}
}
} else {
if (stepSize == SelectionLayer.MOVE_ALL && !withShiftMask) {
this.selectionLayer.clear(false);
}
this.selectionLayer.selectCell(
this.newSelectedColumnPosition,
this.newSelectedRowPosition,
withShiftMask,
withControlMask);
this.selectionLayer.fireCellSelectionEvent(
this.newSelectedColumnPosition,
this.newSelectedRowPosition,
true,
withShiftMask,
withControlMask);
}
}
}
}
}
/**
* Creates a {@link ITraversalStrategy} that wraps the given base strategy
* but returning the step count + 1. Used to perform incremental movement in
* case the base strategy specifies logic to determine whether a target cell
* is a valid move target or not.
*
* @param baseStrategy
* The {@link ITraversalStrategy} to wrap.
* @return A {@link ITraversalStrategy} that wraps the given base strategy
* using the given step count.
*/
protected ITraversalStrategy createIncrementalStrategy(final ITraversalStrategy baseStrategy) {
return new ITraversalStrategy() {
@Override
public TraversalScope getTraversalScope() {
return baseStrategy.getTraversalScope();
}
@Override
public boolean isCycle() {
return baseStrategy.isCycle();
}
@Override
public int getStepCount() {
return baseStrategy.getStepCount() + 1;
}
@Override
public boolean isValidTarget(ILayerCell from, ILayerCell to) {
return baseStrategy.isValidTarget(from, to);
}
};
}
/**
*
* @return <code>true</code> if the selection moved in any direction,
* <code>false</code> if the selection stays at the same position
*/
protected boolean positionMoved() {
return (this.newSelectedColumnPosition != this.lastSelectedCellPosition.columnPosition
|| this.newSelectedRowPosition != this.lastSelectedCellPosition.rowPosition);
}
@Override
public Class<MoveSelectionCommand> getCommandClass() {
return MoveSelectionCommand.class;
}
}