blob: 6cc908920a10c47225e690025b9d11e825023614 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2013, 2020 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@googlemail.com> - initial API and implementation
*******************************************************************************/
package org.eclipse.nebula.widgets.nattable.edit;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.eclipse.jface.window.Window;
import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
import org.eclipse.nebula.widgets.nattable.edit.command.UpdateDataCommand;
import org.eclipse.nebula.widgets.nattable.edit.editor.ICellEditor;
import org.eclipse.nebula.widgets.nattable.edit.gui.CellEditDialogFactory;
import org.eclipse.nebula.widgets.nattable.edit.gui.ICellEditDialog;
import org.eclipse.nebula.widgets.nattable.layer.ILayer;
import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;
import org.eclipse.nebula.widgets.nattable.style.DisplayMode;
import org.eclipse.nebula.widgets.nattable.widget.EditModeEnum;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Controller to handle the activation of the edit mode of NatTable cells.
*/
public class EditController {
private EditController() {
// private default constructor for helper class
}
private static final Logger LOG = LoggerFactory.getLogger(EditController.class);
/**
* 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,
Object initialCanonicalValue, final IConfigRegistry configRegistry) {
if (cell == null) {
LOG.error("Cell being edited is no longer available. Initial value: {}", initialCanonicalValue); //$NON-NLS-1$
return;
}
try {
// determine the position of the cell to put into edit mode
Rectangle cellBounds = cell.getBounds();
ILayer layer = cell.getLayer();
int columnPosition = cell.getColumnPosition();
int rowPosition = cell.getRowPosition();
// read the configuration for the specified cell for
// - which editor to use for that cell
final List<String> configLabels = cell.getConfigLabels();
// check which editor to use
final ICellEditor cellEditor = configRegistry.getConfigAttribute(
EditConfigAttributes.CELL_EDITOR,
DisplayMode.EDIT,
configLabels);
if (cellEditor.openInline(configRegistry, configLabels)) {
// edit inline
ICellEditHandler editHandler = new InlineEditHandler(layer, columnPosition, rowPosition);
Rectangle editorBounds = layer.getLayerPainter().adjustCellBounds(
columnPosition,
rowPosition,
new Rectangle(cellBounds.x, cellBounds.y, cellBounds.width, cellBounds.height));
cellEditor.activateCell(parent, initialCanonicalValue,
EditModeEnum.INLINE, editHandler, cell, configRegistry);
final Control editorControl = cellEditor.getEditorControl();
editorBounds = cellEditor.calculateControlBounds(editorBounds);
// TODO introduce more generic way to identify the border width
// the currently fixed border width of 1 is handled
// because of the fixed border width possibly applied via
// NatTableBorderOverlayPainter
if (editorBounds.x == 0) {
editorBounds.x += 1;
editorBounds.width -= 1;
}
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();
layer.fireLayerEvent(new CellEditorCreatedEvent(cellEditor));
}
} else {
List<ILayerCell> cells = new ArrayList<>();
cells.add(cell);
editCells(cells, parent, initialCanonicalValue, configRegistry);
}
} catch (Exception e) {
LOG.error("Error while editing cell: Cell: {}; Initial value: {}", cell, initialCanonicalValue, e); //$NON-NLS-1$
}
}
/**
* 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, 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.
ICellEditor cellEditor = configRegistry.getConfigAttribute(
EditConfigAttributes.CELL_EDITOR,
DisplayMode.EDIT,
cells.iterator().next().getConfigLabels());
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
ICellEditDialog dialog = CellEditDialogFactory.createCellEditDialog(
parent != null ? parent.getShell() : null,
initialCanonicalValue,
cells.iterator().next(),
cellEditor,
configRegistry);
int returnValue = dialog.open();
if (returnValue == Window.OK) {
for (ILayerCell selectedCell : cells) {
Object editorValue = dialog.getCommittedValue();
if (dialog.getEditType() != EditTypeEnum.SET) {
editorValue = dialog.calculateValue(
selectedCell.getDataValue(),
editorValue);
}
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 (ILayerCell cell : cells) {
ICellEditHandler editHandler = new InlineEditHandler(
cell.getLayer(),
cell.getColumnPosition(),
cell.getRowPosition());
cellEditor.activateCell(parent, initialCanonicalValue,
EditModeEnum.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(
Collection<ILayerCell> cells, ICellEditor cellEditor, IConfigRegistry configRegistry) {
for (ILayerCell cell : cells) {
if (!cellEditor.supportMultiEdit(configRegistry, cell.getConfigLabels())) {
return false;
}
}
return true;
}
}