| /******************************************************************************* |
| * Copyright (c) 2019, 2020 Dirk Fauth. |
| * |
| * 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: |
| * Dirk Fauth <dirk.fauth@googlemail.com> - initial API and implementation |
| ******************************************************************************/ |
| package org.eclipse.nebula.widgets.nattable.group.performance.action; |
| |
| import org.eclipse.nebula.widgets.nattable.NatTable; |
| import org.eclipse.nebula.widgets.nattable.command.LayerCommandUtil; |
| import org.eclipse.nebula.widgets.nattable.coordinate.ColumnPositionCoordinate; |
| import org.eclipse.nebula.widgets.nattable.coordinate.PositionUtil; |
| import org.eclipse.nebula.widgets.nattable.coordinate.RowPositionCoordinate; |
| import org.eclipse.nebula.widgets.nattable.group.ColumnGroupUtils; |
| import org.eclipse.nebula.widgets.nattable.group.performance.ColumnGroupHeaderLayer; |
| import org.eclipse.nebula.widgets.nattable.group.performance.GroupModel; |
| import org.eclipse.nebula.widgets.nattable.group.performance.GroupModel.Group; |
| import org.eclipse.nebula.widgets.nattable.group.performance.command.ColumnGroupReorderEndCommand; |
| import org.eclipse.nebula.widgets.nattable.group.performance.command.ColumnGroupReorderStartCommand; |
| import org.eclipse.nebula.widgets.nattable.layer.ILayer; |
| import org.eclipse.nebula.widgets.nattable.layer.LayerUtil; |
| import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell; |
| import org.eclipse.nebula.widgets.nattable.reorder.action.ColumnReorderDragMode; |
| import org.eclipse.nebula.widgets.nattable.reorder.command.ColumnReorderEndCommand; |
| import org.eclipse.nebula.widgets.nattable.reorder.command.ColumnReorderStartCommand; |
| import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer.MoveDirectionEnum; |
| import org.eclipse.nebula.widgets.nattable.selection.command.ClearAllSelectionsCommand; |
| import org.eclipse.nebula.widgets.nattable.ui.action.IDragMode; |
| import org.eclipse.nebula.widgets.nattable.ui.util.CellEdgeDetectUtil; |
| import org.eclipse.nebula.widgets.nattable.ui.util.CellEdgeEnum; |
| import org.eclipse.nebula.widgets.nattable.util.GUIHelper; |
| import org.eclipse.swt.events.MouseEvent; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.graphics.Rectangle; |
| |
| /** |
| * Default {@link IDragMode} invoked for 'left click + drag' on the column group |
| * header. |
| * <p> |
| * It overrides the isValidTargetColumnPosition() to calculate if a destination |
| * position is valid for the column group to be reordered to. |
| * <p> |
| * Example, a column group cannot be reordered to be inside another column |
| * group. |
| * |
| * @since 1.6 |
| */ |
| public class ColumnGroupHeaderReorderDragMode extends ColumnReorderDragMode { |
| |
| protected final ColumnGroupHeaderLayer columnGroupHeaderLayer; |
| |
| protected int level; |
| protected int dragFromGridRowPosition; |
| |
| /** |
| * |
| * @param columnGroupHeaderLayer |
| * The {@link ColumnGroupHeaderLayer} to which this drag mode |
| * should be assigned to. |
| */ |
| public ColumnGroupHeaderReorderDragMode(ColumnGroupHeaderLayer columnGroupHeaderLayer) { |
| this.columnGroupHeaderLayer = columnGroupHeaderLayer; |
| } |
| |
| @Override |
| public void mouseDown(NatTable natTable, MouseEvent event) { |
| this.natTable = natTable; |
| this.initialEvent = event; |
| this.currentEvent = this.initialEvent; |
| this.dragFromGridColumnPosition = this.natTable.getColumnPositionByX(this.initialEvent.x); |
| |
| this.dragFromGridRowPosition = this.natTable.getRowPositionByY(this.initialEvent.y); |
| RowPositionCoordinate convertedRow = |
| LayerCommandUtil.convertRowPositionToTargetContext( |
| new RowPositionCoordinate(natTable, this.dragFromGridRowPosition), |
| this.columnGroupHeaderLayer); |
| |
| ColumnPositionCoordinate convertedColumn = |
| LayerCommandUtil.convertColumnPositionToTargetContext( |
| new ColumnPositionCoordinate(natTable, this.dragFromGridColumnPosition), |
| this.columnGroupHeaderLayer); |
| calculateLevel(convertedRow.getRowPosition(), convertedColumn.getColumnPosition()); |
| |
| natTable.addOverlayPainter(this.targetOverlayPainter); |
| |
| natTable.doCommand(new ClearAllSelectionsCommand()); |
| |
| fireMoveStartCommand(natTable, this.dragFromGridColumnPosition); |
| } |
| |
| @Override |
| protected boolean isValidTargetColumnPosition(ILayer natLayer, int fromGridColumnPosition, int toGridColumnPosition) { |
| // check if the reordered level supports reordering |
| if (!this.columnGroupHeaderLayer.isReorderSupportedOnLevel(this.level)) { |
| return false; |
| } |
| |
| if (this.currentEvent != null) { |
| // if this method was triggered by a mouse event, we determine the |
| // to column position by the event |
| // if there is no current mouse event referenced it means the |
| // reorder is triggered programmatically |
| CellEdgeEnum moveDirection = getMoveDirection(this.currentEvent.x); |
| toGridColumnPosition = getDragToGridColumnPosition( |
| moveDirection, |
| this.natTable.getColumnPositionByX(this.currentEvent.x)); |
| } |
| |
| // the drag mode is triggered on the top most layer, e.g. the NatTable |
| // itself, therefore the grid position needs to be converted to the |
| // column group header, but as this one is not an IUniqueIndexLayer, we |
| // need to convert directly to the corresponding position layer |
| int toPosition = LayerUtil.convertColumnPosition(natLayer, toGridColumnPosition, this.columnGroupHeaderLayer.getPositionLayer()); |
| |
| // Allow moving within the unbreakable group |
| int fromPosition = this.columnGroupHeaderLayer.getReorderFromColumnPosition(); |
| |
| if (this.level >= 0) { |
| for (int lvl = (this.level + 1); lvl < this.columnGroupHeaderLayer.getLevelCount(); lvl++) { |
| GroupModel model = this.columnGroupHeaderLayer.getGroupModel(lvl); |
| if (model.isPartOfAnUnbreakableGroup(fromPosition)) { |
| int toCheck = toPosition; |
| if (toPosition < 0 && toGridColumnPosition == natLayer.getColumnCount()) { |
| toCheck = LayerUtil.convertColumnPosition(natLayer, toGridColumnPosition - 1, this.columnGroupHeaderLayer.getPositionLayer()); |
| } else { |
| MoveDirectionEnum moveDirection = PositionUtil.getHorizontalMoveDirection(fromPosition, toCheck); |
| toCheck = MoveDirectionEnum.RIGHT == moveDirection ? toCheck - 1 : toCheck; |
| } |
| return ColumnGroupUtils.isInTheSameGroup( |
| this.columnGroupHeaderLayer, |
| lvl, fromPosition, |
| toCheck); |
| } |
| } |
| |
| // ensure that the target position is valid on every level above |
| for (int lvl = (this.level + 1); lvl < this.columnGroupHeaderLayer.getLevelCount(); lvl++) { |
| GroupModel model = this.columnGroupHeaderLayer.getGroupModel(lvl); |
| |
| boolean betweenTwoGroups = false; |
| if (this.currentEvent != null) { |
| int minX = this.currentEvent.x - GUIHelper.DEFAULT_RESIZE_HANDLE_SIZE; |
| int maxX = this.currentEvent.x + GUIHelper.DEFAULT_RESIZE_HANDLE_SIZE; |
| betweenTwoGroups = ColumnGroupUtils.isBetweenTwoGroups(natLayer, minX, maxX, this.columnGroupHeaderLayer, lvl); |
| } |
| |
| if (!betweenTwoGroups) { |
| if (model.isPartOfAnUnbreakableGroup(toPosition)) { |
| return false; |
| } |
| } |
| } |
| |
| return ColumnGroupUtils.isBetweenTwoGroups( |
| this.columnGroupHeaderLayer, |
| this.level, |
| toPosition, |
| toPosition < this.columnGroupHeaderLayer.getColumnCount(), |
| PositionUtil.getHorizontalMoveDirection(fromGridColumnPosition, toGridColumnPosition)); |
| } else { |
| // we perform column reorder |
| // ensure that the target position is valid on every level |
| for (int level = 0; level < this.columnGroupHeaderLayer.getLevelCount(); level++) { |
| if (!isValidTargetColumnPosition(natLayer, fromGridColumnPosition, toGridColumnPosition, level, fromPosition, toPosition)) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| } |
| |
| /** |
| * Test if the reorder is valid for the given level. |
| * |
| * @param natLayer |
| * The layer on which the drag operation is triggered, typically |
| * the NatTable instance. |
| * @param fromGridColumnPosition |
| * The from position related to the given natLayer. |
| * @param toGridColumnPosition |
| * The to position related to the given natLayer. |
| * @param level |
| * The grouping level for which the check should be performed. |
| * @param fromPosition |
| * The from position related to the positionLayer of the |
| * {@link ColumnGroupHeaderLayer}. |
| * @param toPosition |
| * The to position related to the positionLayer of the |
| * {@link ColumnGroupHeaderLayer}. |
| * @return <code>true</code> if the reorder would be valid on the specified |
| * level, <code>false</code> if not. |
| */ |
| protected boolean isValidTargetColumnPosition( |
| ILayer natLayer, int fromGridColumnPosition, int toGridColumnPosition, |
| int level, int fromPosition, int toPosition) { |
| |
| GroupModel model = this.columnGroupHeaderLayer.getGroupModel(level); |
| |
| // check only in case the group is an unbreakable group or the group |
| // contains only one column, as reordering a column in such a group |
| // is like reordering the whole group and does not break the group |
| if (model.isPartOfAnUnbreakableGroup(fromPosition) |
| && model.getGroupByPosition(fromPosition).getOriginalSpan() > 1) { |
| int toCheck = toPosition; |
| if (toPosition < 0 && toGridColumnPosition == natLayer.getColumnCount()) { |
| toCheck = LayerUtil.convertColumnPosition(natLayer, toGridColumnPosition - 1, this.columnGroupHeaderLayer.getPositionLayer()); |
| } else { |
| MoveDirectionEnum moveDirection = PositionUtil.getHorizontalMoveDirection(fromPosition, toCheck); |
| toCheck = MoveDirectionEnum.RIGHT == moveDirection ? toCheck - 1 : toCheck; |
| } |
| |
| // Allow moving within the unbreakable group |
| return ColumnGroupUtils.isInTheSameGroup( |
| this.columnGroupHeaderLayer, |
| level, fromPosition, |
| toCheck); |
| } |
| |
| boolean betweenTwoGroups = false; |
| if (this.currentEvent != null) { |
| int minX = this.currentEvent.x - GUIHelper.DEFAULT_RESIZE_HANDLE_SIZE; |
| int maxX = this.currentEvent.x + GUIHelper.DEFAULT_RESIZE_HANDLE_SIZE; |
| betweenTwoGroups = ColumnGroupUtils.isBetweenTwoGroups(natLayer, minX, maxX, this.columnGroupHeaderLayer, level); |
| } |
| |
| if (!betweenTwoGroups) { |
| if (model.isPartOfAnUnbreakableGroup(toPosition)) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| @Override |
| protected void fireMoveStartCommand(NatTable natTable, int dragFromGridColumnPosition) { |
| if (this.level >= 0) { |
| natTable.doCommand(new ColumnGroupReorderStartCommand(natTable, this.level, dragFromGridColumnPosition)); |
| } else { |
| natTable.doCommand(new ColumnReorderStartCommand(natTable, dragFromGridColumnPosition)); |
| } |
| } |
| |
| @Override |
| protected void fireMoveEndCommand(NatTable natTable, int dragToGridColumnPosition) { |
| if (this.level >= 0) { |
| natTable.doCommand(new ColumnGroupReorderEndCommand(natTable, this.level, dragToGridColumnPosition)); |
| } else { |
| natTable.doCommand(new ColumnReorderEndCommand(natTable, dragToGridColumnPosition)); |
| } |
| } |
| |
| @Override |
| protected CellEdgeEnum getMoveDirection(int x) { |
| ILayerCell cell = getColumnCell(x); |
| if (cell != null) { |
| Rectangle selectedColumnHeaderRect = cell.getBounds(); |
| return CellEdgeDetectUtil.getHorizontalCellEdge( |
| selectedColumnHeaderRect, |
| new Point(x, this.natTable.getStartYOfRowPosition(this.dragFromGridRowPosition))); |
| } |
| |
| return null; |
| } |
| |
| @Override |
| protected ILayerCell getColumnCell(int x) { |
| int gridColumnPosition = this.natTable.getColumnPositionByX(x); |
| return this.natTable.getCellByPosition(gridColumnPosition, this.dragFromGridRowPosition); |
| } |
| |
| /** |
| * Calculate the group level and based on that the real drag from grid row |
| * position that is reordered. Needed in case there is no group at the |
| * coordinate level and therefore a spanning indicates a group at a lower |
| * level. |
| * |
| * @param rowPosition |
| * The row position from which the drag was started. Needs to be |
| * related to the columnGroupHeaderLayer. |
| * @param columnPosition |
| * The column position from which the drag was started. Needed to |
| * check if there is a group at the calculated level. Needs to be |
| * related to the columnGroupHeaderLayer. |
| */ |
| protected void calculateLevel(int rowPosition, int columnPosition) { |
| this.level = this.columnGroupHeaderLayer.getLevelForRowPosition(rowPosition); |
| |
| Group group = this.columnGroupHeaderLayer.getGroupByPosition(this.level, columnPosition); |
| while (group == null && this.level > 0) { |
| // decrease the level and increase the from row position as we need |
| // to check one row below |
| this.level--; |
| this.dragFromGridRowPosition++; |
| group = this.columnGroupHeaderLayer.getGroupByPosition(this.level, columnPosition); |
| } |
| |
| if (group == null) { |
| // no group found, so we set the level to -1 and trigger column |
| // reordering instead of column group reordering in further steps |
| this.level = -1; |
| this.dragFromGridRowPosition = this.columnGroupHeaderLayer.getRowCount() - 1; |
| } |
| } |
| } |