blob: 2c46bba245fc84561c5f4dfcc73544f514735253 [file] [log] [blame]
/*****************************************************************************
* Copyright (c) 2015, 2020 CEA LIST 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:
* Dirk Fauth <dirk.fauth@googlemail.com> - Initial API and implementation
*
*****************************************************************************/
package org.eclipse.nebula.widgets.nattable.fillhandle.action;
import java.util.Date;
import org.eclipse.nebula.widgets.nattable.Messages;
import org.eclipse.nebula.widgets.nattable.NatTable;
import org.eclipse.nebula.widgets.nattable.config.Direction;
import org.eclipse.nebula.widgets.nattable.coordinate.PositionCoordinate;
import org.eclipse.nebula.widgets.nattable.copy.InternalCellClipboard;
import org.eclipse.nebula.widgets.nattable.copy.command.CopyDataToClipboardCommand;
import org.eclipse.nebula.widgets.nattable.fillhandle.command.FillHandlePasteCommand;
import org.eclipse.nebula.widgets.nattable.fillhandle.command.FillHandlePasteCommand.FillHandleOperation;
import org.eclipse.nebula.widgets.nattable.fillhandle.config.FillHandleConfigAttributes;
import org.eclipse.nebula.widgets.nattable.layer.LayerUtil;
import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;
import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer;
import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer.MoveDirectionEnum;
import org.eclipse.nebula.widgets.nattable.style.DisplayMode;
import org.eclipse.nebula.widgets.nattable.ui.action.IDragMode;
import org.eclipse.nebula.widgets.nattable.viewport.action.AutoScrollDragMode;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.MenuAdapter;
import org.eclipse.swt.events.MenuEvent;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
/**
* The {@link IDragMode} that is registered to get triggered for dragging the
* fill drag handle.
*
* @since 1.4
*/
public class FillHandleDragMode extends AutoScrollDragMode {
protected MouseEvent startEvent;
/**
* @since 1.6
*/
protected Point startPosition;
protected MoveDirectionEnum direction;
protected SelectionLayer selectionLayer;
protected ILayerCell selectionCell;
protected InternalCellClipboard clipboard;
protected Menu menu;
/**
*
* @param selectionLayer
* The {@link SelectionLayer} needed to determine the fill handle
* region and perform the update command.
* @param clipboard
* The internal clipboard that carries the cells for the copy
* &amp; paste operation triggered by using the fill handle.
*/
public FillHandleDragMode(SelectionLayer selectionLayer, InternalCellClipboard clipboard) {
super(true, true);
if (selectionLayer == null) {
throw new IllegalArgumentException("SelectionLayer can not be null"); //$NON-NLS-1$
}
this.selectionLayer = selectionLayer;
this.clipboard = clipboard;
}
@Override
public void mouseDown(NatTable natTable, MouseEvent event) {
PositionCoordinate[] selectedCellPositions = this.selectionLayer.getSelectedCellPositions();
if (selectedCellPositions.length > 0) {
this.startEvent = event;
this.selectionCell = this.selectionLayer.getCellByPosition(
selectedCellPositions[0].columnPosition,
selectedCellPositions[0].rowPosition);
this.startPosition = new Point(
this.selectionCell.getColumnPosition(),
this.selectionCell.getRowPosition());
}
}
@Override
protected void performDragAction(
NatTable natTable,
int x, int y,
MoveDirectionEnum horizontal, MoveDirectionEnum vertical) {
int natTableColumnPosition = natTable.getColumnPositionByX(x);
int natTableRowPosition = natTable.getRowPositionByY(y);
int selectedColumnPosition = LayerUtil.convertColumnPosition(natTable, natTableColumnPosition, this.selectionLayer);
int selectedRowPosition = LayerUtil.convertRowPosition(natTable, natTableRowPosition, this.selectionLayer);
if (natTableColumnPosition > -1 && natTableRowPosition > -1) {
Rectangle actionBounds = null;
int xStart = this.startPosition.x;
int yStart = this.startPosition.y;
Rectangle region = this.selectionLayer.getLastSelectedRegion();
// only increase range in one direction
int xDiff = calculateIncreasedPositiveDiff(
x,
(x < this.startEvent.x) ? this.selectionCell.getBounds().x : this.startEvent.x);
int yDiff = calculateIncreasedPositiveDiff(
y,
(y < this.startEvent.y) ? this.selectionCell.getBounds().y : this.startEvent.y);
if (selectedColumnPosition >= region.x && selectedColumnPosition < (region.x + region.width)) {
xDiff = 0;
}
if (selectedRowPosition >= region.y && selectedRowPosition < (region.y + region.height)) {
yDiff = 0;
}
int width = -1;
int height = -1;
// check if only drag operations in one direction are supported
Direction direction = natTable.getConfigRegistry().getConfigAttribute(
FillHandleConfigAttributes.ALLOWED_FILL_DIRECTION,
DisplayMode.NORMAL,
this.selectionCell.getConfigLabels());
if (direction == null) {
direction = Direction.BOTH;
}
if (direction != Direction.NONE) {
if (direction == Direction.VERTICAL
|| (direction == Direction.BOTH && yDiff >= xDiff)) {
int diff = calculateIncreasedPositiveDiff(selectedRowPosition, this.startPosition.y);
height = Math.max(diff, this.selectionLayer.getSelectedRowCount());
width = this.selectionLayer.getSelectedColumnPositions().length;
this.direction = MoveDirectionEnum.DOWN;
if ((selectedRowPosition - this.startPosition.y) < 0) {
yStart = selectedRowPosition;
height = diff + this.selectionLayer.getSelectedRowCount() - 1;
this.direction = MoveDirectionEnum.UP;
}
} else {
int diff = calculateIncreasedPositiveDiff(selectedColumnPosition, this.startPosition.x);
height = this.selectionLayer.getSelectedRowCount();
width = Math.max(diff, this.selectionLayer.getSelectedColumnPositions().length);
this.direction = MoveDirectionEnum.RIGHT;
if ((selectedColumnPosition - this.startPosition.x) < 0) {
xStart = selectedColumnPosition;
width = diff + this.selectionLayer.getSelectedColumnPositions().length - 1;
this.direction = MoveDirectionEnum.LEFT;
}
}
actionBounds = new Rectangle(
xStart,
yStart,
width,
height);
this.selectionLayer.setFillHandleRegion(actionBounds);
natTable.redraw();
}
}
}
/**
* Calculates the difference between the two given values and increases the
* value by one.
*
* @param selectedIndex
* The first value
* @param relativeIndex
* The second value
* @return The difference between the two given values increased by one.
*/
protected int calculateIncreasedPositiveDiff(int selectedIndex, int relativeIndex) {
int diff = selectedIndex - relativeIndex;
if (diff < 0) {
diff *= -1;
}
diff++;
return diff;
}
@Override
public void mouseUp(final NatTable natTable, MouseEvent event) {
// Cancel any active viewport drag
super.mouseUp(natTable, event);
if (natTable.doCommand(
new CopyDataToClipboardCommand(
"\t", //$NON-NLS-1$
System.getProperty("line.separator"), //$NON-NLS-1$
natTable.getConfigRegistry()))) {
if (this.clipboard != null) {
if (showMenu(natTable)) {
openMenu(natTable);
} else {
natTable.doCommand(
new FillHandlePasteCommand(
FillHandleOperation.COPY,
this.direction,
natTable.getConfigRegistry()));
reset(natTable);
}
} else {
natTable.doCommand(
new FillHandlePasteCommand(
FillHandleOperation.COPY,
this.direction,
natTable.getConfigRegistry()));
reset(natTable);
}
} else {
reset(natTable);
}
}
/**
* Check if the menu should be shown for selecting copy or series fill
* operation.
*
* @param natTable
* The NatTable instance on which the operation is performed.
* @return <code>true</code> if the menu should be shown, <code>false</code>
* if not.
*/
protected boolean showMenu(final NatTable natTable) {
if (this.clipboard == null || this.clipboard.getCopiedCells() == null) {
return false;
}
Class<?> type = null;
for (ILayerCell[] cells : this.clipboard.getCopiedCells()) {
for (ILayerCell cell : cells) {
if (cell.getDataValue() == null) {
return false;
} else {
if (type == null) {
type = cell.getDataValue().getClass();
if (!Number.class.isAssignableFrom(type)
&& !Date.class.isAssignableFrom(type)) {
return false;
}
} else if (type != cell.getDataValue().getClass()) {
return false;
}
}
}
}
return true;
}
/**
* Opens a menu that enables a user to select whether values should simply
* be copied or if a series should be filled.
*
* @param natTable
* The NatTable instance on which the operation is performed.
*/
protected void openMenu(final NatTable natTable) {
// lazily create the menu
if (this.menu == null || this.menu.isDisposed()) {
this.menu = new Menu(natTable);
MenuItem copyItem = new MenuItem(this.menu, SWT.PUSH);
copyItem.setText(Messages.getLocalizedMessage("%FillHandleDragMode.menu.item.copy")); //$NON-NLS-1$
copyItem.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
natTable.doCommand(
new FillHandlePasteCommand(
FillHandleOperation.COPY,
FillHandleDragMode.this.direction,
natTable.getConfigRegistry()));
}
});
MenuItem seriesItem = new MenuItem(this.menu, SWT.PUSH);
seriesItem.setText(Messages.getLocalizedMessage("%FillHandleDragMode.menu.item.series")); //$NON-NLS-1$
seriesItem.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
natTable.doCommand(
new FillHandlePasteCommand(
FillHandleOperation.SERIES,
FillHandleDragMode.this.direction,
natTable.getConfigRegistry()));
}
});
// add a menu listener to reset the fill state when the menu is
// closed
this.menu.addMenuListener(new MenuAdapter() {
@Override
public void menuHidden(MenuEvent e) {
// perform the reset operation asynchronously because on
// several OS the hide event is processed BEFORE the
// selection event
Display.getDefault().asyncExec(new Runnable() {
@Override
public void run() {
reset(natTable);
}
});
}
});
// add the dispose listener for disposing the menu
natTable.addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent e) {
FillHandleDragMode.this.menu.dispose();
}
});
}
this.menu.setVisible(true);
}
/**
* Reset the {@link FillHandleDragMode} states, the fill handle region in
* the {@link SelectionLayer} and redraw the given NatTable.
*
* @param natTable
* The NatTable instance on which the operation is performed.
*/
protected void reset(NatTable natTable) {
this.selectionCell = null;
this.startEvent = null;
this.startPosition = null;
this.direction = null;
this.selectionLayer.setFillHandleRegion(null);
this.clipboard.clear();
if (!natTable.isDisposed()) {
natTable.redraw();
}
}
}