/******************************************************************************* * Copyright (c) 2012, 2013, 2014 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 * Dirk Fauth - 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; // 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))) { 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.lastSelectedCellPosition.columnPosition, this.lastSelectedCellPosition.rowPosition, 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; // 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()) { if (stepSize == SelectionLayer.MOVE_ALL && !withShiftMask) { this.selectionLayer.clear(false); } // check if calculated target is valid, otherwise move to // adjacent if (!traversalStrategy.isValidTarget(lastSelectedCell, this.selectionLayer.getCellByPosition(this.newSelectedColumnPosition, this.newSelectedRowPosition))) { 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 { this.selectionLayer.selectCell( this.newSelectedColumnPosition, this.newSelectedRowPosition, withShiftMask, withControlMask); this.selectionLayer.fireCellSelectionEvent( this.lastSelectedCellPosition.columnPosition, this.lastSelectedCellPosition.rowPosition, 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; // 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))) { 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 { this.selectionLayer.selectCell( this.newSelectedColumnPosition, this.newSelectedRowPosition, withShiftMask, withControlMask); this.selectionLayer.fireCellSelectionEvent( this.lastSelectedCellPosition.columnPosition, this.lastSelectedCellPosition.rowPosition, 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; // 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 if (!traversalStrategy.isValidTarget(lastSelectedCell, this.selectionLayer.getCellByPosition(this.newSelectedColumnPosition, this.newSelectedRowPosition))) { 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 { this.selectionLayer.selectCell( this.newSelectedColumnPosition, this.newSelectedRowPosition, withShiftMask, withControlMask); this.selectionLayer.fireCellSelectionEvent( this.lastSelectedCellPosition.columnPosition, this.lastSelectedCellPosition.rowPosition, 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; } }