blob: ef1fa4c28ed748646a4ff00d8b336ceaa35c3a89 [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> - Bug 460052, 460074
******************************************************************************/
package org.eclipse.nebula.widgets.nattable.group;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.eclipse.nebula.widgets.nattable.group.ColumnGroupModel.ColumnGroup;
import org.eclipse.nebula.widgets.nattable.group.command.GroupColumnReorderCommandHandler;
import org.eclipse.nebula.widgets.nattable.group.command.GroupColumnReorderEndCommandHandler;
import org.eclipse.nebula.widgets.nattable.group.command.GroupColumnReorderStartCommandHandler;
import org.eclipse.nebula.widgets.nattable.group.command.GroupMultiColumnReorderCommandHandler;
import org.eclipse.nebula.widgets.nattable.group.command.ReorderColumnGroupCommandHandler;
import org.eclipse.nebula.widgets.nattable.group.command.ReorderColumnGroupEndCommandHandler;
import org.eclipse.nebula.widgets.nattable.group.command.ReorderColumnGroupStartCommandHandler;
import org.eclipse.nebula.widgets.nattable.group.command.ReorderColumnsAndGroupsCommandHandler;
import org.eclipse.nebula.widgets.nattable.layer.AbstractLayerTransform;
import org.eclipse.nebula.widgets.nattable.layer.IUniqueIndexLayer;
import org.eclipse.nebula.widgets.nattable.reorder.command.MultiColumnReorderCommand;
import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer.MoveDirectionEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Adds functionality allowing the reordering of the the column groups.
*/
public class ColumnGroupReorderLayer extends AbstractLayerTransform implements IUniqueIndexLayer {
private static final Logger LOG = LoggerFactory.getLogger(ColumnGroupReorderLayer.class);
private final ColumnGroupModel model;
private int reorderFromColumnPosition;
public ColumnGroupReorderLayer(IUniqueIndexLayer underlyingLayer, ColumnGroupModel model) {
super(underlyingLayer);
this.model = model;
registerCommandHandlers();
}
/**
*
* @param fromColumnPosition
* The column position of a column in the column group that
* should be reordered.
* @param toColumnPosition
* The column position to which a column group should be
* reordered to.
* @return <code>true</code> if the reorder command was executed and
* consumed successfully
*/
public boolean reorderColumnGroup(int fromColumnPosition, int toColumnPosition) {
int fromColumnIndex = this.underlyingLayer.getColumnIndexByPosition(fromColumnPosition);
List<Integer> fromColumnPositions = getColumnGroupPositions(fromColumnIndex);
return this.underlyingLayer.doCommand(
new MultiColumnReorderCommand(this, fromColumnPositions, toColumnPosition));
}
/**
* @return The {@link ColumnGroupModel} that is used to specify the column
* groups.
*/
public ColumnGroupModel getModel() {
return this.model;
}
/**
* @since 1.3
*/
@Override
public IUniqueIndexLayer getUnderlyingLayer() {
return (IUniqueIndexLayer) super.getUnderlyingLayer();
}
// Configuration
@Override
protected void registerCommandHandlers() {
registerCommandHandler(new ReorderColumnGroupCommandHandler(this));
registerCommandHandler(new ReorderColumnGroupStartCommandHandler(this));
registerCommandHandler(new ReorderColumnGroupEndCommandHandler(this));
registerCommandHandler(new ReorderColumnsAndGroupsCommandHandler(this));
registerCommandHandler(new GroupColumnReorderCommandHandler(this));
registerCommandHandler(new GroupMultiColumnReorderCommandHandler(this));
registerCommandHandler(new GroupColumnReorderStartCommandHandler(this));
registerCommandHandler(new GroupColumnReorderEndCommandHandler(this));
}
// Horizontal features
// Columns
@Override
public int getColumnPositionByIndex(int columnIndex) {
return getUnderlyingLayer().getColumnPositionByIndex(columnIndex);
}
// Vertical features
// Rows
@Override
public int getRowPositionByIndex(int rowIndex) {
return getUnderlyingLayer().getRowPositionByIndex(rowIndex);
}
// Column Groups
/**
* @param fromColumnIndex
* The column index of a column that is part of a column group.
* @return The column positions for all the columns in the column group
* specified by the given index.
*/
public List<Integer> getColumnGroupPositions(int fromColumnIndex) {
List<Integer> fromColumnIndexes =
this.model.getColumnGroupByIndex(fromColumnIndex).getMembers();
List<Integer> fromColumnPositions = new ArrayList<>(fromColumnIndexes.size());
for (Integer columnIndex : fromColumnIndexes) {
fromColumnPositions.add(
getUnderlyingLayer().getColumnPositionByIndex(columnIndex.intValue()));
}
// These positions are actually consecutive but the Column Group does
// not know about the order
Collections.sort(fromColumnPositions);
return fromColumnPositions;
}
/**
* Used to support column reordering via drag and drop.
*
* @return The position from which a column reorder operation was started
* from.
*/
public int getReorderFromColumnPosition() {
return this.reorderFromColumnPosition;
}
/**
* Used to support column reordering via drag and drop.
*
* @param fromColumnPosition
* The position from which a column reorder operation was started
* from.
*/
public void setReorderFromColumnPosition(int fromColumnPosition) {
this.reorderFromColumnPosition = fromColumnPosition;
}
/**
* Updates the {@link ColumnGroupModel} with respect to a column reordering
* operation.
*
* @param fromColumnPositions
* The column positions that should be reordered.
* @param toColumnPosition
* The column position to which a column should be reordered.
* @param reorderToLeftEdge
* Whether the toPosition should be calculated for attachment to
* the left or not.
* @return <code>true</code> if only a {@link ColumnGroupModel} modification
* was triggered and no column reordering is necessary,
* <code>false</code> if additionally a column reordering needs to
* be performed.
* @since 1.3
*/
public boolean updateColumnGroupModel(
List<Integer> fromColumnPositions, int toColumnPosition, boolean reorderToLeftEdge) {
boolean consumeCommand = false;
final int fromColumnPositionsCount = fromColumnPositions.size();
if (toColumnPosition > fromColumnPositions.get(fromColumnPositionsCount - 1)) {
// Moving from left to right
int rightMostPosition = fromColumnPositions.get(fromColumnPositionsCount - 1);
consumeCommand = updateColumnGroupModel(rightMostPosition, toColumnPosition, reorderToLeftEdge, fromColumnPositions);
} else if (toColumnPosition < fromColumnPositions.get(fromColumnPositionsCount - 1)) {
// Moving from right to left
int leftMostPosition = fromColumnPositions.get(0);
consumeCommand = updateColumnGroupModel(leftMostPosition, toColumnPosition, reorderToLeftEdge, fromColumnPositions);
}
return consumeCommand;
}
/**
* Updates the {@link ColumnGroupModel} with respect to a column reordering
* operation.
*
* @param fromColumnPosition
* The column position that should be reordered.
* @param toColumnPosition
* The column position to which a column should be reordered.
* @param reorderToLeftEdge
* Whether the toPosition should be calculated for attachment to
* the left or not.
* @return <code>true</code> if only a {@link ColumnGroupModel} modification
* was triggered and no column reordering is necessary,
* <code>false</code> if additionally a column reordering needs to
* be performed.
* @since 1.3
*/
public boolean updateColumnGroupModel(
int fromColumnPosition, int toColumnPosition, boolean reorderToLeftEdge) {
return updateColumnGroupModel(
fromColumnPosition, toColumnPosition, reorderToLeftEdge, Arrays.asList(fromColumnPosition));
}
/**
* Transforms the given collection of column positions to an array of column
* indexes.
*
* @param fromColumnPositions
* The column positions to transform.
* @return An array that contains the indexes for the given positions.
* @since 1.3
*/
protected int[] getColumnIndexesForPositions(List<Integer> fromColumnPositions) {
int[] fromColumnIndexes = new int[fromColumnPositions.size()];
for (int i = 0; i < fromColumnPositions.size(); i++) {
int from = fromColumnPositions.get(i);
fromColumnIndexes[i] = this.getUnderlyingLayer().getColumnIndexByPosition(from);
}
return fromColumnIndexes;
}
/**
* Updates the {@link ColumnGroupModel} with respect to a column reordering
* operation.
*
* @param fromColumnPosition
* The column position that should be used for determining the
* necessary operation.
* @param toColumnPosition
* The column position to which a column should be reordered.
* @param reorderToLeftEdge
* Whether the toPosition should be calculated for attachment to
* the left or not.
* @param fromColumnPositions
* The column positions that should be reordered.
* @return <code>true</code> if only a {@link ColumnGroupModel} modification
* was triggered and no column reordering is necessary,
* <code>false</code> if additionally a column reordering needs to
* be performed.
* @since 1.3
*/
public boolean updateColumnGroupModel(
int fromColumnPosition, int toColumnPosition, boolean reorderToLeftEdge, List<Integer> fromColumnPositions) {
MoveDirectionEnum moveDirection =
ColumnGroupUtils.getMoveDirection(fromColumnPosition, toColumnPosition);
if (reorderToLeftEdge
&& toColumnPosition > 0
&& MoveDirectionEnum.RIGHT == moveDirection) {
toColumnPosition--;
}
if (fromColumnPosition == -1 || toColumnPosition == -1) {
LOG.error("Invalid reorder positions, fromPosition: {}, toPosition: {}", fromColumnPosition, toColumnPosition); //$NON-NLS-1$
}
boolean jump = Math.abs(fromColumnPosition - toColumnPosition) > 1;
int fromColumnIndex = this.getUnderlyingLayer().getColumnIndexByPosition(fromColumnPosition);
int toColumnIndex = this.getUnderlyingLayer().getColumnIndexByPosition(toColumnPosition);
// check if the column to reorder is part of a group
ColumnGroup fromColumnGroup = this.model.getColumnGroupByIndex(fromColumnIndex);
ColumnGroup toColumnGroup = this.model.getColumnGroupByIndex(toColumnIndex);
int[] fromColumnIndexes = getColumnIndexesForPositions(fromColumnPositions);
boolean consumeCommand = false;
// if from and to column groups are the same, let the reorder happen
// without group modifications
if (fromColumnGroup != null
&& toColumnGroup != null
&& fromColumnGroup.getName().equals(toColumnGroup.getName())
&& fromColumnPosition == toColumnPosition) {
// special case
// handle first/last column in last group
if (fromColumnIndexes.length > 1) {
if (MoveDirectionEnum.RIGHT == moveDirection) {
// check if there are columns to the left that are reordered
// too
int collectionPos = fromColumnPositions.indexOf(Integer.valueOf(fromColumnPosition));
if (collectionPos > 0) {
this.model.insertColumnIndexes(toColumnGroup.getName(), fromColumnIndexes);
} else {
this.model.removeColumnIndexes(fromColumnGroup.getName(), fromColumnIndexes);
}
consumeCommand = true;
} else if (MoveDirectionEnum.LEFT == moveDirection) {
// check if there are columns to the right that are
// reordered too
int collectionPos = fromColumnPositions.indexOf(Integer.valueOf(fromColumnPosition));
if (collectionPos == 0) {
this.model.insertColumnIndexes(toColumnGroup.getName(), fromColumnIndexes);
} else {
this.model.removeColumnIndexes(fromColumnGroup.getName(), fromColumnIndexes);
}
consumeCommand = true;
} else {
// if there are multiple columns to reorder but no move
// direction, we probably perform a reorder to add new
// columns to an existing group
consumeCommand = false;
}
} else {
// only remove if we are at the edge of a column group
if (ColumnGroupUtils.isLeftEdgeOfAColumnGroup(this, fromColumnPosition, fromColumnIndex, this.model)
|| ColumnGroupUtils.isRightEdgeOfAColumnGroup(this, fromColumnPosition, fromColumnIndex, this.model)) {
this.model.removeColumnIndexes(fromColumnGroup.getName(), fromColumnIndexes);
}
consumeCommand = true;
}
} else if (fromColumnGroup == null && toColumnGroup != null) {
// special case
// add the column to the column group if we step instead of jumping
consumeCommand = this.model.insertColumnIndexes(toColumnGroup.getName(), fromColumnIndexes);
if (jump) {
consumeCommand = false;
}
} else if (fromColumnGroup != null && toColumnGroup == null) {
this.model.removeColumnIndexes(fromColumnGroup.getName(), fromColumnIndexes);
} else if (fromColumnGroup == null && toColumnGroup == null && fromColumnPosition == toColumnPosition) {
// this might happen on drag and drop operations when trying to add
// a column back into an adjacent column group
int adjacentPos = (moveDirection == MoveDirectionEnum.RIGHT) ? fromColumnPosition + 1 : fromColumnPosition - 1;
int adjacentIndex = this.getUnderlyingLayer().getColumnIndexByPosition(adjacentPos);
// check if there is an adjacent column group
ColumnGroup adjacentColumnGroup = this.model.getColumnGroupByIndex(adjacentIndex);
if (adjacentColumnGroup != null) {
this.model.insertColumnIndexes(adjacentColumnGroup.getName(), fromColumnIndexes);
consumeCommand = false;
}
} else if (fromColumnGroup != null
&& toColumnGroup != null
&& !fromColumnGroup.getName().equals(toColumnGroup.getName())) {
// the target location would be the first position in another column
// group, but since we do not reorder to left edge, we only move out
// of the current column group
if (MoveDirectionEnum.RIGHT == moveDirection) {
if (toColumnGroup != null) {
// check if reorder is started from right edge of a group
boolean fromRightEdge = ColumnGroupUtils.isRightEdgeOfAColumnGroup(
this, fromColumnPosition, fromColumnIndex, this.model);
boolean removed = true;
if (fromColumnGroup != null) {
removed = this.model.removeColumnIndexes(fromColumnGroup.getName(), fromColumnIndexes);
}
if (removed && ((!fromRightEdge && !jump) || jump)) {
// column removed from another group
// not from the right edge of another group
// add it to the following group
consumeCommand = this.model.insertColumnIndexes(toColumnGroup.getName(), fromColumnIndexes);
if (jump) {
consumeCommand = !jump;
}
} else {
consumeCommand = !jump;
}
}
} else if (MoveDirectionEnum.LEFT == moveDirection) {
if (toColumnGroup != null) {
// check if reorder is started from left edge of a group
boolean fromLeftEdge = ColumnGroupUtils.isLeftEdgeOfAColumnGroup(
this, fromColumnPosition, fromColumnIndex, this.model);
boolean removed = true;
if (fromColumnGroup != null) {
removed = this.model.removeColumnIndexes(fromColumnGroup.getName(), fromColumnIndexes);
}
if (removed && ((!fromLeftEdge && !jump) || jump)) {
// column removed from another group
// not from the left edge of another group
// add it to the previous group
consumeCommand = this.model.insertColumnIndexes(toColumnGroup.getName(), fromColumnIndexes);
if (jump) {
consumeCommand = !jump;
}
} else {
consumeCommand = !jump;
}
}
}
}
// don't consume the command to perform reordering
return consumeCommand;
}
}