blob: 71cdb8594055f9dd7539668c0a507b2bee8a1d98 [file] [log] [blame]
/*******************************************************************************
* 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.command;
import java.util.HashMap;
import java.util.HashSet;
import org.eclipse.collections.api.iterator.MutableIntIterator;
import org.eclipse.collections.api.list.primitive.MutableIntList;
import org.eclipse.collections.impl.factory.primitive.IntLists;
import org.eclipse.nebula.widgets.nattable.Messages;
import org.eclipse.nebula.widgets.nattable.command.AbstractLayerCommandHandler;
import org.eclipse.nebula.widgets.nattable.group.command.CreateRowGroupCommand;
import org.eclipse.nebula.widgets.nattable.group.command.DisplayRowGroupRenameDialogCommand;
import org.eclipse.nebula.widgets.nattable.group.command.IRowGroupCommand;
import org.eclipse.nebula.widgets.nattable.group.command.RemoveRowGroupCommand;
import org.eclipse.nebula.widgets.nattable.group.command.UngroupRowCommand;
import org.eclipse.nebula.widgets.nattable.group.event.GroupRowsEvent;
import org.eclipse.nebula.widgets.nattable.group.event.UngroupRowsEvent;
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.RowGroupHeaderLayer;
import org.eclipse.nebula.widgets.nattable.layer.LayerUtil;
import org.eclipse.nebula.widgets.nattable.layer.event.VisualRefreshEvent;
import org.eclipse.nebula.widgets.nattable.reorder.command.MultiRowReorderCommand;
import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer;
import org.eclipse.nebula.widgets.nattable.ui.rename.HeaderRenameDialog;
import org.eclipse.nebula.widgets.nattable.ui.rename.HeaderRenameDialog.RenameDialogLabels;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.MessageBox;
/**
* Command handler for handling {@link IRowGroupCommand}s to create, remove and
* rename row groups.
*
* @since 1.6
*/
public class RowGroupsCommandHandler extends AbstractLayerCommandHandler<IRowGroupCommand> {
private final RowGroupHeaderLayer contextLayer;
private final SelectionLayer selectionLayer;
public RowGroupsCommandHandler(RowGroupHeaderLayer contextLayer, SelectionLayer selectionLayer) {
this.contextLayer = contextLayer;
this.selectionLayer = selectionLayer;
}
@Override
public boolean doCommand(IRowGroupCommand command) {
if (command instanceof CreateRowGroupCommand) {
CreateRowGroupCommand createCommand = ((CreateRowGroupCommand) command);
if (!handleCreateRowGroupCommand(createCommand.getRowGroupName())) {
MessageBox messageBox = new MessageBox(Display.getDefault().getActiveShell(), SWT.INHERIT_DEFAULT | SWT.ICON_ERROR | SWT.OK);
messageBox.setText(Messages.getString("ErrorDialog.title")); //$NON-NLS-1$
messageBox.setMessage(Messages.getString("RowGroups.selectNonGroupedRows")); //$NON-NLS-1$
messageBox.open();
}
return true;
} else if (command instanceof RemoveRowGroupCommand) {
RemoveRowGroupCommand removeRowGroupCommand = (RemoveRowGroupCommand) command;
int rowIndex = removeRowGroupCommand.getRowIndex();
handleRemoveRowGroupCommand(rowIndex);
return true;
} else if (command instanceof UngroupRowCommand) {
handleUngroupCommand();
return true;
} else if (command instanceof DisplayRowGroupRenameDialogCommand) {
return displayRowGroupRenameDialog((DisplayRowGroupRenameDialogCommand) command);
}
return false;
}
/**
* Creates a new row group with the given name out of the currently fully
* selected row positions. If a selected row is part of an existing group,
* the existing group will be removed and all rows belonging to that group
* will be also part of the new group.
*
* @param rowGroupName
* The name of the new row group.
* @return <code>true</code> if the row group could be created,
* <code>false</code> if there are no rows fully selected.
*/
protected boolean handleCreateRowGroupCommand(String rowGroupName) {
int[] fullySelectedRows = this.selectionLayer.getFullySelectedRowPositions();
// we operate on the GroupModel directly to avoid the position
// transformation
GroupModel model = this.contextLayer.getGroupModel();
MutableIntList positionsToGroup = IntLists.mutable.empty();
if (fullySelectedRows != null && fullySelectedRows.length > 0) {
for (int row : fullySelectedRows) {
// convert to position layer
// needed because the group model takes the positions based on
// the position layer
int converted = LayerUtil.convertRowPosition(this.selectionLayer, row, this.contextLayer.getPositionLayer());
if (converted > -1) {
positionsToGroup.add(converted);
}
}
HashSet<Group> existingGroups = new HashSet<>();
for (MutableIntIterator it = positionsToGroup.intIterator(); it.hasNext();) {
int row = it.next();
Group group = model.getGroupByPosition(row);
if (group != null) {
if (!group.isUnbreakable()) {
existingGroups.add(group);
} else {
// if a position of an unbreakable group was found, we
// ignore that position
it.remove();
}
}
}
if (!existingGroups.isEmpty()) {
// expand those groups
this.contextLayer.doCommand(new RowGroupExpandCommand(model, existingGroups));
// get all positions from the other groups
for (Group group : existingGroups) {
positionsToGroup.addAll(group.getVisiblePositions());
// remove existing group and create a new one
this.contextLayer.removeGroup(group);
}
}
positionsToGroup.sortThis();
MutableIntList selectedPositions = IntLists.mutable.ofAll(positionsToGroup.distinct());
if (selectedPositions.size() > 1) {
// if a group is created for more than one row, reorder so
// the positions are consecutive which is necessary for grouping
this.selectionLayer.doCommand(
new MultiRowReorderCommand(this.selectionLayer, selectedPositions.toArray(), selectedPositions.get(0)));
}
// create the row group
this.contextLayer.addGroup(rowGroupName, this.selectionLayer.getRowIndexByPosition(selectedPositions.get(0)), selectedPositions.size());
this.selectionLayer.clear();
this.contextLayer.fireLayerEvent(new GroupRowsEvent(this.contextLayer));
return true;
}
return false;
}
/**
* Remove the row group at the given row index.
*
* @param rowIndex
* The row index to retrieve the row group to remove.
*/
protected void handleRemoveRowGroupCommand(int rowIndex) {
int selectedPosition = this.selectionLayer.getRowPositionByIndex(rowIndex);
int converted = LayerUtil.convertRowPosition(this.selectionLayer, selectedPosition, this.contextLayer.getPositionLayer());
GroupModel model = this.contextLayer.getGroupModel();
Group group = model.getGroupByPosition(converted);
if (group != null && !group.isUnbreakable()) {
if (group.isCollapsed()) {
this.contextLayer.doCommand(new RowGroupExpandCommand(model, group));
}
model.removeGroup(group);
this.contextLayer.fireLayerEvent(new GroupRowsEvent(this.contextLayer));
}
}
/**
* Remove the currently fully selected rows from their corresponding groups.
* Will also trigger a reorder to ensure a consistent group rendering
*/
protected void handleUngroupCommand() {
// Grab fully selected row positions
int[] fullySelectedRows = this.selectionLayer.getFullySelectedRowPositions();
if (fullySelectedRows != null && fullySelectedRows.length > 0) {
MutableIntList positionsToUngroup = IntLists.mutable.empty();
for (int row : fullySelectedRows) {
// convert to position layer
// needed because the group model takes the positions based on
// the position layer
int converted = LayerUtil.convertRowPosition(this.selectionLayer, row, this.contextLayer.getPositionLayer());
if (converted > -1) {
positionsToUngroup.add(converted);
}
}
// we operate on the GroupModel directly to avoid the position
// transformation
GroupModel model = this.contextLayer.getGroupModel();
HashMap<Group, MutableIntList> toRemove = new HashMap<>();
positionsToUngroup.forEach(pos -> {
Group group = model.getGroupByPosition(pos);
if (group != null) {
int endPos = group.getVisibleStartPosition() + group.getVisibleSpan();
if (pos < endPos && !group.isGroupStart(pos)) {
// remember position to remove
MutableIntList remove = toRemove.get(group);
if (remove == null) {
remove = IntLists.mutable.empty();
toRemove.put(group, remove);
}
remove.add(pos);
} else {
model.removePositionsFromGroup(group, pos);
}
}
});
if (!toRemove.isEmpty()) {
toRemove.entrySet().forEach(entry -> {
Group group = entry.getKey();
int endPos = group.getVisibleStartPosition() + group.getVisibleSpan();
this.selectionLayer.doCommand(new MultiRowReorderCommand(this.selectionLayer, entry.getValue().toArray(), endPos));
MutableIntList value = entry.getValue();
int start = endPos - value.size();
int[] positionsToRemove = new int[value.size()];
for (int i = 0; i < entry.getValue().size(); i++) {
positionsToRemove[i] = start + i;
}
model.removePositionsFromGroup(group, positionsToRemove);
});
}
this.selectionLayer.clear();
this.contextLayer.fireLayerEvent(new UngroupRowsEvent(this.contextLayer));
}
}
// TODO NatTable 2.0 - Dialog should not be opened by the command handler
protected boolean displayRowGroupRenameDialog(DisplayRowGroupRenameDialogCommand command) {
int rowPosition = command.getRowPosition();
HeaderRenameDialog dialog = new HeaderRenameDialog(Display.getDefault().getActiveShell(), null, null, RenameDialogLabels.ROW_RENAME);
Rectangle rowHeaderBounds = this.contextLayer.getBoundsByPosition(rowPosition, 0);
Point point = new Point(rowHeaderBounds.x, rowHeaderBounds.y + rowHeaderBounds.height);
dialog.setLocation(command.toDisplayCoordinates(point));
dialog.open();
if (!dialog.isCancelPressed()) {
Group rowGroup = this.contextLayer.getGroupByPosition(rowPosition);
rowGroup.setName(dialog.getNewLabel());
this.contextLayer.fireLayerEvent(new VisualRefreshEvent(this.contextLayer));
}
return true;
}
@Override
public Class<IRowGroupCommand> getCommandClass() {
return IRowGroupCommand.class;
}
}