| /***************************************************************************** |
| * Copyright (c) 2018, 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.hierarchical.action; |
| |
| import org.eclipse.nebula.widgets.nattable.NatTable; |
| import org.eclipse.nebula.widgets.nattable.group.ColumnGroupModel; |
| import org.eclipse.nebula.widgets.nattable.group.ColumnGroupUtils; |
| import org.eclipse.nebula.widgets.nattable.hierarchical.HierarchicalTreeLayer; |
| import org.eclipse.nebula.widgets.nattable.layer.ILayer; |
| import org.eclipse.nebula.widgets.nattable.painter.IOverlayPainter; |
| import org.eclipse.nebula.widgets.nattable.reorder.action.ColumnReorderDragMode; |
| import org.eclipse.nebula.widgets.nattable.ui.util.CellEdgeEnum; |
| import org.eclipse.nebula.widgets.nattable.util.GUIHelper; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.events.MouseEvent; |
| import org.eclipse.swt.graphics.Color; |
| import org.eclipse.swt.graphics.Cursor; |
| import org.eclipse.swt.graphics.GC; |
| import org.eclipse.swt.graphics.Rectangle; |
| import org.eclipse.swt.widgets.Display; |
| |
| /** |
| * Specialization of the {@link ColumnReorderDragMode} to be used with a |
| * {@link HierarchicalTreeLayer} to respect hierarchical levels. |
| * |
| * @since 1.6 |
| */ |
| public class HierarchicalTreeColumnReorderDragMode extends ColumnReorderDragMode { |
| |
| private HierarchicalTreeLayer treeLayer; |
| private Cursor currentCursor; |
| |
| protected ColumnGroupModel columnGroupModel; |
| |
| /** |
| * @param treeLayer |
| * The {@link HierarchicalTreeLayer} needed to determine the |
| * level column boundaries. |
| */ |
| public HierarchicalTreeColumnReorderDragMode(HierarchicalTreeLayer treeLayer) { |
| this(treeLayer, null); |
| } |
| |
| /** |
| * Creates a drag mode that validates the drag operation based on the given |
| * tree level structure and the given column group structure. |
| * |
| * @param treeLayer |
| * The {@link HierarchicalTreeLayer} needed to determine the |
| * level column boundaries. |
| * @param model |
| * The {@link ColumnGroupModel} to perform column group based |
| * drag validation. Can be <code>null</code>. |
| */ |
| public HierarchicalTreeColumnReorderDragMode(HierarchicalTreeLayer treeLayer, ColumnGroupModel model) { |
| this.treeLayer = treeLayer; |
| this.columnGroupModel = model; |
| this.targetOverlayPainter = new HierarchicalColumnReorderOverlayPainter(); |
| } |
| |
| @Override |
| public void mouseUp(NatTable natTable, MouseEvent event) { |
| // ensure the default cursor is shown again |
| this.natTable.setCursor(null); |
| super.mouseUp(natTable, event); |
| } |
| |
| @Override |
| protected void fireMoveEndCommand(NatTable natTable, int dragToGridColumnPosition) { |
| if (dragToGridColumnPosition < natTable.getColumnCount()) { |
| int toIndex = natTable.getColumnIndexByPosition(dragToGridColumnPosition); |
| if (toIndex < 0 && this.dragFromGridColumnPosition < dragToGridColumnPosition) { |
| dragToGridColumnPosition++; |
| } |
| } |
| super.fireMoveEndCommand(natTable, dragToGridColumnPosition); |
| } |
| |
| @Override |
| protected boolean isValidTargetColumnPosition(ILayer natLayer, int dragFromGridColumnPosition, int dragToGridColumnPosition) { |
| int fromIndex = natLayer.getColumnIndexByPosition(dragFromGridColumnPosition); |
| int toIndex = natLayer.getColumnIndexByPosition(dragToGridColumnPosition); |
| |
| // it is not allowed to drag a level header column and the index of a |
| // level header is < 0 |
| if (fromIndex < 0) { |
| return false; |
| } |
| |
| if (toIndex < 0 && dragFromGridColumnPosition < dragToGridColumnPosition) { |
| // get the position to the left of the level header |
| toIndex = natLayer.getColumnIndexByPosition(dragToGridColumnPosition - 1); |
| } else if (toIndex < 0 && dragFromGridColumnPosition > dragToGridColumnPosition) { |
| return false; |
| } |
| |
| int fromLevel = this.treeLayer.getLevelByColumnIndex(fromIndex); |
| int toLevel = this.treeLayer.getLevelByColumnIndex(toIndex); |
| if (fromLevel != toLevel && this.treeLayer.isShowTreeLevelHeader()) { |
| return false; |
| } else if (fromLevel != toLevel && !this.treeLayer.isShowTreeLevelHeader()) { |
| // if no tree level headers are shown, test for the column to the |
| // left to check for the right level border |
| toLevel = this.treeLayer.getLevelByColumnIndex(toIndex - 1); |
| if (fromLevel != toLevel) { |
| return false; |
| } |
| } |
| |
| // if column group is configured do the column group checks |
| if (this.columnGroupModel != null) { |
| // Allow moving within the unbreakable group |
| if (this.columnGroupModel.isPartOfAnUnbreakableGroup(fromIndex)) { |
| return ColumnGroupUtils.isInTheSameGroup(fromIndex, toIndex, this.columnGroupModel); |
| } |
| |
| 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.columnGroupModel); |
| } |
| |
| return (!this.columnGroupModel.isPartOfAnUnbreakableGroup(toIndex)) || betweenTwoGroups; |
| } |
| |
| return super.isValidTargetColumnPosition(natLayer, dragFromGridColumnPosition, dragToGridColumnPosition); |
| } |
| |
| /** |
| * {@link IOverlayPainter} that visualizes reordering and changes the cursor |
| * in case the target position is invalid, e.g. when trying to reorder |
| * columns between levels. |
| */ |
| private class HierarchicalColumnReorderOverlayPainter implements IOverlayPainter { |
| |
| private Cursor noOpsCursor; |
| |
| @Override |
| public void paintOverlay(GC gc, ILayer layer) { |
| int dragFromGridColumnPosition = getDragFromGridColumnPosition(); |
| |
| if (HierarchicalTreeColumnReorderDragMode.this.currentEvent.x > HierarchicalTreeColumnReorderDragMode.this.natTable.getWidth()) { |
| return; |
| } |
| |
| CellEdgeEnum moveDirection = getMoveDirection(HierarchicalTreeColumnReorderDragMode.this.currentEvent.x); |
| int dragToGridColumnPosition = getDragToGridColumnPosition( |
| moveDirection, |
| HierarchicalTreeColumnReorderDragMode.this.natTable.getColumnPositionByX(HierarchicalTreeColumnReorderDragMode.this.currentEvent.x)); |
| |
| if (isValidTargetColumnPosition(HierarchicalTreeColumnReorderDragMode.this.natTable, dragFromGridColumnPosition, dragToGridColumnPosition)) { |
| |
| if (HierarchicalTreeColumnReorderDragMode.this.currentCursor != null) { |
| HierarchicalTreeColumnReorderDragMode.this.natTable.setCursor(null); |
| HierarchicalTreeColumnReorderDragMode.this.currentCursor = null; |
| } |
| |
| int dragToColumnHandleX = -1; |
| |
| if (moveDirection != null) { |
| Rectangle selectedColumnHeaderRect = getColumnCell(HierarchicalTreeColumnReorderDragMode.this.currentEvent.x).getBounds(); |
| |
| switch (moveDirection) { |
| case LEFT: |
| dragToColumnHandleX = selectedColumnHeaderRect.x; |
| break; |
| case RIGHT: |
| dragToColumnHandleX = selectedColumnHeaderRect.x + selectedColumnHeaderRect.width; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| if (dragToColumnHandleX > 0) { |
| Color orgBgColor = gc.getBackground(); |
| gc.setBackground(GUIHelper.COLOR_DARK_GRAY); |
| gc.fillRectangle(dragToColumnHandleX - 1, 0, 2, layer.getHeight()); |
| gc.setBackground(orgBgColor); |
| } |
| } else if (HierarchicalTreeColumnReorderDragMode.this.currentCursor == null) { |
| // show no ops cursor |
| if (this.noOpsCursor == null) { |
| this.noOpsCursor = new Cursor(Display.getDefault(), SWT.CURSOR_NO); |
| |
| HierarchicalTreeColumnReorderDragMode.this.natTable.addDisposeListener(e -> HierarchicalColumnReorderOverlayPainter.this.noOpsCursor.dispose()); |
| } |
| |
| HierarchicalTreeColumnReorderDragMode.this.natTable.setCursor(this.noOpsCursor); |
| HierarchicalTreeColumnReorderDragMode.this.currentCursor = this.noOpsCursor; |
| } |
| } |
| } |
| } |