blob: 19af773633eaf7d8437df02ea3a1d53521b6964b [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2013, 2021 Dirk Fauth and others.
#
# 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@gmail.com> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.ecommons.waltable.edit;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.statet.ecommons.waltable.config.IConfigRegistry;
import org.eclipse.statet.ecommons.waltable.coordinate.LRectangle;
import org.eclipse.statet.ecommons.waltable.edit.editor.ICellEditor;
import org.eclipse.statet.ecommons.waltable.edit.gui.CellEditDialogFactory;
import org.eclipse.statet.ecommons.waltable.edit.gui.ICellEditDialog;
import org.eclipse.statet.ecommons.waltable.layer.ILayer;
import org.eclipse.statet.ecommons.waltable.layer.cell.ILayerCell;
import org.eclipse.statet.ecommons.waltable.style.DisplayMode;
import org.eclipse.statet.ecommons.waltable.swt.SWTUtil;
import org.eclipse.statet.internal.ecommons.waltable.WaLTablePlugin;
/**
* Controller to handle the activation of the edit mode of NatTable cells.
*/
public class EditController {
/**
* Activates the edit mode for the given cell. Will determine whether the editor
* should be opened inline or in a subdialog.
*
* @param cell The cell that should be put into the edit mode.
* @param parent The parent Composite, needed for the creation of the editor control.
* @param initialCanonicalValue The value that should be put to the activated editor
* control. Usually this value should be the same as calling
* <code>cell.getDataValue()</code>, but for the special case that an editor
* should be activated pressing a letter or digit key on the current selection,
* the initial value should be the Character representing that key.
* @param configRegistry The {@link IConfigRegistry} containing the configuration of the
* current NatTable instance the command should be executed for.
* This is necessary because the edit controllers in the current architecture
* are not aware of the instance they are running in.
*/
public static void editCell(
final ILayerCell cell, final Composite parent,
final Object initialCanonicalValue, final IConfigRegistry configRegistry) {
try {
//determine the position of the cell to put into edit mode
final LRectangle cellBounds= cell.getBounds();
final ILayer layer= cell.getLayer();
final long columnPosition= cell.getColumnPosition();
final long rowPosition= cell.getRowPosition();
//read the configuration for the specified cell for
//- which editor to use for that cell
final List<String> configLabels= cell.getConfigLabels().getLabels();
//check which editor to use
final ICellEditor cellEditor= configRegistry.getConfigAttribute(
EditConfigAttributes.CELL_EDITOR, DisplayMode.EDIT, configLabels);
if (cellEditor.openInline(configRegistry, configLabels)) {
//edit inline
final ICellEditHandler editHandler= new InlineEditHandler(
layer,
columnPosition,
rowPosition);
org.eclipse.swt.graphics.Rectangle editorBounds= SWTUtil.toSWT(
layer.getLayerPainter().adjustCellBounds(
columnPosition, rowPosition,
new LRectangle(cellBounds.x, cellBounds.y, cellBounds.width, cellBounds.height)));
cellEditor.activateCell(
parent,
initialCanonicalValue,
EditMode.INLINE,
editHandler,
cell,
configRegistry);
final Control editorControl= cellEditor.getEditorControl();
editorBounds= cellEditor.calculateControlBounds(editorBounds);
if (editorControl != null && !editorControl.isDisposed()) {
editorControl.setBounds(editorBounds);
//We need to add the control listeners after setting the bounds to it
//because of the strange behaviour on Mac OS where a control loses focus
//if its bounds are set
cellEditor.addEditorControlListeners();
ActiveCellEditorRegistry.registerActiveCellEditor(cellEditor);
}
}
else {
final List<ILayerCell> cells= new ArrayList<>();
cells.add(cell);
editCells(cells, parent, initialCanonicalValue, configRegistry);
}
}
catch (final Exception e) {
if (cell == null){
WaLTablePlugin.log(new Status(IStatus.ERROR, WaLTablePlugin.BUNDLE_ID,
"Cell being edited is no longer available. Initial value: " + initialCanonicalValue, e )); //$NON-NLS-1$
} else {
WaLTablePlugin.log(new Status(IStatus.ERROR, WaLTablePlugin.BUNDLE_ID,
"Error while editing cell: Cell: " + cell + "; Initial value: " + initialCanonicalValue, e )); //$NON-NLS-1$ //$NON-NLS-2$
}
}
}
/**
* This method is used to edit cells in a sub dialog. In every case this method will
* open a dialog for editing, regardless if the list of cells to edit contain several
* or only one value. Only if the given list of cells to edit is <code>null</code> or
* empty, there is no action performed.
* @param cells The list of cells to edit.
* @param parent The parent composite to access the parent shell, or <code>null</code> to
* create a top-level shell dialog. In the last case, the dialog will be opened
* as non modal.
* @param initialCanonicalValue The value that should be propagated to the editor
* control. Needed because for multi cell editing or editor activation by
* letter/digit key will result in a different value to populate for some
* editors than populating the value out of the cell/data model directly.
* @param configRegistry The {@link IConfigRegistry} containing the configuration of
* the current NatTable instance the command should be executed for. This is
* necessary because the edit controllers in the current architecture are not
* aware of the instance they are running in and therefore it is needed for
* activation of editors.
*/
public static void editCells(
final Collection<ILayerCell> cells, final Composite parent,
final Object initialCanonicalValue, final IConfigRegistry configRegistry) {
if (cells != null && !cells.isEmpty()) {
//get the editor to use, because the editor contains information if
//it allows editing on a multi edit dialog
//Note: this works because previous to calling this method it is checked
// if all cells have the same editor configured. Otherwise this method
// will have serious issues further on.
final ICellEditor cellEditor= configRegistry.getConfigAttribute(
EditConfigAttributes.CELL_EDITOR,
DisplayMode.EDIT,
cells.iterator().next().getConfigLabels().getLabels());
if (cells.size() == 1 ||
(cells.size() > 1 && supportMultiEdit(cells, cellEditor, configRegistry))) {
if (cellEditor.openMultiEditDialog()) {
//as the EditSelectionCommandHandler already ensured that all cells have the same
//configuration, we can simply use any cell for multi cell edit handling
final ICellEditDialog dialog= CellEditDialogFactory.createCellEditDialog(
parent != null ? parent.getShell() : null, initialCanonicalValue,
cells.iterator().next(), cellEditor, configRegistry);
final int returnValue= dialog.open();
if (returnValue == Window.OK) {
for (final ILayerCell selectedCell : cells) {
Object editorValue= dialog.getCommittedValue();
if (!(dialog.getEditType() == EditTypeEnum.SET)) {
editorValue= dialog.calculateValue(selectedCell.getDataValue(0, null), editorValue);
}
final ILayer layer= selectedCell.getLayer();
layer.doCommand(new UpdateDataCommand(
layer, selectedCell.getColumnPosition(), selectedCell.getRowPosition(), editorValue));
}
}
}
else {
//if the editor is configured to do not open a multi edit dialog for
//multi editing, we simply activate all editors for cells that are
//selected for multi editing
//this only works for editors that have no interactive control for
//editing, like for example the CheckBoxCellEditor that directly
//changes the value and closes right away.
for (final ILayerCell cell : cells) {
final ICellEditHandler editHandler= new InlineEditHandler(
cell.getLayer(),
cell.getColumnPosition(),
cell.getRowPosition());
cellEditor.activateCell(
parent,
initialCanonicalValue,
EditMode.INLINE,
editHandler,
cell,
configRegistry);
}
}
}
}
}
/**
* Will check if multi editing is supported. Usually it should be enough checking the editor once.
* But as this can be configured via configuration attribute, and this can differ from cell to cell,
* all cells are checked to be sure.
* @param cells The selected cells that should be multi edited.
* @param cellEditor The cell editor that is the same for every cell.
* @param configRegistry The {@link IConfigRegistry} containing the configuration of
* the current NatTable instance the command should be executed for. This is
* necessary because the edit controllers in the current architecture are not
* aware of the instance they are running in and therefore it is needed for
* activation of editors.
* @return <code>true</code> if the editor supports multi edit for all selected cells,
* <code>false</code> if at least one cell does specify to not support multi edit.
*/
private static boolean supportMultiEdit(final Collection<ILayerCell> cells, final ICellEditor cellEditor, final IConfigRegistry configRegistry) {
for (final ILayerCell cell : cells) {
if (!cellEditor.supportMultiEdit(configRegistry, cell.getConfigLabels().getLabels())) {
return false;
}
}
return true;
}
}