blob: 9aaa9e9b7e4426585690b377ead2964db9480d06 [file] [log] [blame]
/*****************************************************************************
* 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;
}
}
}
}