blob: 705d94e884258020047210c21721f9576a3a678d [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.gui;
import java.util.Map;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.nebula.widgets.nattable.Messages;
import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
import org.eclipse.nebula.widgets.nattable.edit.DialogEditHandler;
import org.eclipse.nebula.widgets.nattable.edit.EditTypeEnum;
import org.eclipse.nebula.widgets.nattable.edit.ICellEditHandler;
import org.eclipse.nebula.widgets.nattable.edit.editor.ICellEditor;
import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;
import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer.MoveDirectionEnum;
import org.eclipse.nebula.widgets.nattable.util.GUIHelper;
import org.eclipse.nebula.widgets.nattable.widget.EditModeEnum;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
/**
* Dialog that supports editing of cells in NatTable. Is used for multi cell
* editing and for dialog only editors.
*/
public class CellEditDialog extends Dialog implements ICellEditDialog {
/**
* 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.
*/
protected final Object originalCanonicalValue;
/**
* The cell editor that should be integrated and activated in this dialog.
*/
protected final ICellEditor cellEditor;
/**
* The {@link ICellEditHandler} that should be used by the editor.
*/
protected DialogEditHandler cellEditHandler = new DialogEditHandler();
/**
* The cell that should be edited. Needed because editor activation
* retrieves the configuration for editing directly out of the cell.
*/
protected final ILayerCell cell;
/**
* 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.
*/
protected final IConfigRegistry configRegistry;
/**
* Map that contains custom configurations for this {@link CellEditDialog}.
* We do not use the {@link IDialogSettings} provided by JFace, because they
* are used to store and load the settings in XML rather than overriding the
* behaviour.
*/
protected Map<String, Object> editDialogSettings;
/**
* @param parentShell
* the parent shell, or <code>null</code> to create a top-level
* shell
* @param originalCanonicalValue
* 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 cell
* The cell that should be edited. Needed because editor
* activation retrieves the configuration for editing directly
* out of the cell.
* @param cellEditor
* The {@link ICellEditor} that will be used as editor control
* within the dialog.
* @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 CellEditDialog(Shell parentShell,
Object originalCanonicalValue,
ILayerCell cell,
ICellEditor cellEditor,
IConfigRegistry configRegistry) {
super(parentShell);
this.originalCanonicalValue = originalCanonicalValue;
this.cell = cell;
this.cellEditor = cellEditor;
this.configRegistry = configRegistry;
}
@Override
protected void configureShell(Shell newShell) {
super.configureShell(newShell);
String shellTitle = Messages.getString("CellEditDialog.shellTitle"); //$NON-NLS-1$
Image shellIcon = GUIHelper.getDisplayImage("editor"); //$NON-NLS-1$
if (this.editDialogSettings != null) {
if (this.editDialogSettings.containsKey(DIALOG_SHELL_TITLE)) {
String settingsShellTitle = this.editDialogSettings.get(DIALOG_SHELL_TITLE).toString();
shellTitle = settingsShellTitle;
}
Object settingsShellImage = this.editDialogSettings.get(DIALOG_SHELL_ICON);
if (settingsShellImage != null && settingsShellImage instanceof Image) {
shellIcon = (Image) settingsShellImage;
}
}
newShell.setText(shellTitle);
newShell.setImage(shellIcon);
}
@Override
protected boolean isResizable() {
return false;
}
@Override
protected Point getInitialLocation(Point initialSize) {
if (this.editDialogSettings != null) {
Object settingsLocation = this.editDialogSettings.get(DIALOG_SHELL_LOCATION);
if (settingsLocation != null && settingsLocation instanceof Point) {
return (Point) settingsLocation;
}
}
return super.getInitialLocation(initialSize);
}
@Override
protected Point getInitialSize() {
if (this.editDialogSettings != null) {
Object settingsSize = this.editDialogSettings.get(DIALOG_SHELL_SIZE);
if (settingsSize != null && settingsSize instanceof Point) {
return (Point) settingsSize;
}
}
return super.getInitialSize();
}
@Override
protected void okPressed() {
// if the editor could not be committed, we should not proceed with
// closing the editor, as the entered value is not valid in terms of
// conversion/validation
if (this.cellEditor.commit(MoveDirectionEnum.NONE, true)) {
super.okPressed();
}
}
@Override
protected void cancelPressed() {
this.cellEditor.close();
super.cancelPressed();
}
@Override
protected Control createDialogArea(Composite parent) {
Composite panel = new Composite(parent, SWT.NONE);
GridDataFactory.fillDefaults().grab(true, true).applyTo(panel);
GridLayout panelLayout = new GridLayout(1, true);
panelLayout.marginWidth = 8;
panel.setLayout(panelLayout);
// add a custom message if there is one configured in the edit dialog
// settings
if (this.editDialogSettings != null && this.editDialogSettings.containsKey(DIALOG_MESSAGE)) {
String customMessage = this.editDialogSettings.get(DIALOG_MESSAGE).toString();
Label customMessageLabel = new Label(panel, SWT.NONE);
customMessageLabel.setText(customMessage);
GridDataFactory.fillDefaults()
.grab(true, false)
.applyTo(customMessageLabel);
}
// activate the new editor
this.cellEditor.activateCell(panel,
this.originalCanonicalValue,
EditModeEnum.DIALOG,
this.cellEditHandler,
this.cell,
this.configRegistry);
Control editorControl = this.cellEditor.getEditorControl();
// propagate the ESC event from the editor to the dialog
editorControl.addKeyListener(getEscKeyListener());
// If the editor control already has no layout data set already, apply
// a default one.
// This check ensures that a custom layout data that was specified while
// creating the editor control in the ICellEditor is not overridden.
if (editorControl.getLayoutData() == null) {
GridDataFactory.fillDefaults()
.grab(true, false)
.applyTo(editorControl);
}
return panel;
}
@Override
public Object getCommittedValue() {
return this.cellEditHandler.getCommittedValue();
}
@Override
public EditTypeEnum getEditType() {
return EditTypeEnum.SET;
}
/**
* {@inheritDoc}
*
* @return This implementation will always return processValue, as there is
* no processing specified in this {@link ICellEditDialog}
* implementation and therefore the value that was committed to the
* editor will be updated to the data model.
*/
@Override
public Object calculateValue(Object currentValue, Object processValue) {
return processValue;
}
/**
* @return KeyListener that intercepts the ESC key to cancel editing, close
* the editor and close the dialog.
*/
protected KeyListener getEscKeyListener() {
return new KeyListener() {
@Override
public void keyReleased(KeyEvent e) {
if (e.keyCode == SWT.ESC) {
cancelPressed();
}
}
@Override
public void keyPressed(KeyEvent e) {
if (e.keyCode == SWT.ESC) {
cancelPressed();
}
}
};
}
/**
* {@inheritDoc}
* <p>
* This implementation will check if the given map contains a value for
* {@link ICellEditDialog#DIALOG_SHELL_RESIZABLE}. If there is a value found
* for that configuration, the Shell style will be recalculated based on the
* specified value. The style bits are calculated the same way like in the
* {@link Dialog} constructor. This is performed in here because setting the
* Shell style bits after the creation of the Shell would have no effect.
*/
@Override
public void setDialogSettings(Map<String, Object> editDialogSettings) {
this.editDialogSettings = editDialogSettings;
// if the edit dialog settings contain a configuration for resizable
// behaviour
// we need to override the shellStyle bits before the shell is created
// otherwise this configuration wouldn't have any effect.
if (this.editDialogSettings != null) {
Object settingsResizable = this.editDialogSettings.get(DIALOG_SHELL_RESIZABLE);
if (settingsResizable != null && settingsResizable instanceof Boolean) {
if ((Boolean) settingsResizable) {
setShellStyle(SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL | SWT.MAX | SWT.RESIZE | getDefaultOrientation());
} else {
setShellStyle(SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL | getDefaultOrientation());
}
}
}
}
}