blob: 34a16c08416a85c0b3f451b518e74bdf0ee30cea [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.List;
import java.util.Map;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.nebula.widgets.nattable.Messages;
import org.eclipse.nebula.widgets.nattable.config.CellConfigAttributes;
import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
import org.eclipse.nebula.widgets.nattable.data.convert.ConversionFailedException;
import org.eclipse.nebula.widgets.nattable.data.convert.IDisplayConverter;
import org.eclipse.nebula.widgets.nattable.data.validate.IDataValidator;
import org.eclipse.nebula.widgets.nattable.data.validate.ValidationFailedException;
import org.eclipse.nebula.widgets.nattable.edit.DialogEditHandler;
import org.eclipse.nebula.widgets.nattable.edit.EditConfigAttributes;
import org.eclipse.nebula.widgets.nattable.edit.EditConfigHelper;
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.edit.editor.IEditErrorHandler;
import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;
import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer.MoveDirectionEnum;
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;
/**
* Abstract implementation of a {@link ICellEditor} that is also a
* {@link ICellEditDialog}. By creating a {@link ICellEditor} based on this
* abstract implementation, you are able to create an editor that wraps a SWT or
* JFace dialog. As SWT and JFace dialogs does not extend the same base classes,
* the local instance for the wrapped dialog is of type object in here. In the
* concrete implementation the
* {@link AbstractDialogCellEditor#getDialogInstance()} should return the
* concrete dialog type that is wrapped.
* <p>
* By using this implementation, the {@link CellEditDialogFactory} will return
* the instance of this editor, after it was activated previously.
*/
public abstract class AbstractDialogCellEditor implements ICellEditor, ICellEditDialog {
private static final Logger LOG = LoggerFactory.getLogger(AbstractDialogCellEditor.class);
/**
* The parent Composite, needed for the creation of the dialog.
*/
protected Composite parent;
/**
* The {@link Dialog} that should be used as a cell editor.
*/
protected Object dialog;
/**
* The cell whose editor should be activated.
*/
protected ILayerCell layerCell;
/**
* The {@link ICellEditHandler} that will be used on commit.
*/
protected DialogEditHandler editHandler = new DialogEditHandler();
/**
* The {@link IDisplayConverter} that should be used to convert the input
* value to the canonical value and vice versa.
*/
protected IDisplayConverter displayConverter;
/**
* The {@link IDataValidator} that should be used to validate the input
* value prior committing.
*/
protected IDataValidator dataValidator;
/**
* The error handler that will be used to show conversion errors.
*/
protected IEditErrorHandler conversionEditErrorHandler;
/**
* The error handler that will be used to show validation errors.
*/
protected IEditErrorHandler validationEditErrorHandler;
/**
* The {@link IConfigRegistry} containing the configuration of the current
* NatTable instance. This is necessary because the editors in the current
* architecture are not aware of the NatTable instance they are running in.
*/
protected 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;
@Override
public EditTypeEnum getEditType() {
// by default the value selected in the wrapped dialog should simply be
// set to the data model on commit.
return EditTypeEnum.SET;
}
@Override
public Object calculateValue(Object currentValue, Object processValue) {
// by default the value selected in the wrapped dialog should simply be
// set to the data model on commit.
return processValue;
}
@Override
public abstract int open();
@Override
public Control activateCell(Composite parent,
Object originalCanonicalValue, EditModeEnum editMode,
ICellEditHandler editHandler, ILayerCell cell,
IConfigRegistry configRegistry) {
this.parent = parent;
this.layerCell = cell;
this.configRegistry = configRegistry;
final List<String> configLabels = cell.getConfigLabels();
this.displayConverter = configRegistry.getConfigAttribute(
CellConfigAttributes.DISPLAY_CONVERTER,
DisplayMode.EDIT,
configLabels);
this.dataValidator = configRegistry.getConfigAttribute(
EditConfigAttributes.DATA_VALIDATOR,
DisplayMode.EDIT,
configLabels);
this.conversionEditErrorHandler = EditConfigHelper.getEditErrorHandler(
configRegistry,
EditConfigAttributes.CONVERSION_ERROR_HANDLER,
configLabels);
this.validationEditErrorHandler = EditConfigHelper.getEditErrorHandler(
configRegistry,
EditConfigAttributes.VALIDATION_ERROR_HANDLER,
configLabels);
this.dialog = createDialogInstance();
setCanonicalValue(originalCanonicalValue);
// this method is only needed to initialize the dialog editor, there
// will be no control to return
return null;
}
/**
* Will create the dialog instance that should be wrapped by this
* {@link AbstractDialogCellEditor}. Note that you always need to create and
* return a new instance because on commit or close the dialog will be
* closed, which disposes the shell of the dialog. Therefore the instance
* will not be usable after commit/close.
*
* @return The dialog instance that should be wrapped by this
* {@link AbstractDialogCellEditor}
*/
public abstract Object createDialogInstance();
/**
* @return The current dialog instance that is wrapped by this
* {@link AbstractDialogCellEditor}
*/
public abstract Object getDialogInstance();
@Override
public abstract Object getEditorValue();
@Override
public abstract void setEditorValue(Object value);
@Override
public Object getCanonicalValue() {
return getCanonicalValue(this.conversionEditErrorHandler);
}
@Override
public Object getCanonicalValue(IEditErrorHandler conversionErrorHandler) {
Object canonicalValue;
try {
if (this.displayConverter != null) {
// always do the conversion to check for valid entered data
canonicalValue = this.displayConverter.displayToCanonicalValue(
this.layerCell, this.configRegistry, getEditorValue());
} else {
canonicalValue = getEditorValue();
}
// if the conversion succeeded, remove error rendering if exists
conversionErrorHandler.removeError(this);
} catch (ConversionFailedException e) {
// conversion failed
conversionErrorHandler.displayError(this, this.configRegistry, e);
throw e;
} catch (Exception e) {
// conversion failed
conversionErrorHandler.displayError(this, this.configRegistry, e);
throw new ConversionFailedException(e.getMessage(), e);
}
return canonicalValue;
}
@Override
public void setCanonicalValue(Object canonicalValue) {
Object displayValue;
if (this.displayConverter != null) {
displayValue = this.displayConverter.canonicalToDisplayValue(
this.layerCell, this.configRegistry, canonicalValue);
} else {
displayValue = canonicalValue;
}
setEditorValue(displayValue);
}
@Override
public boolean validateCanonicalValue(Object canonicalValue) {
return validateCanonicalValue(canonicalValue,
this.validationEditErrorHandler);
}
@Override
public boolean validateCanonicalValue(Object canonicalValue,
IEditErrorHandler validationErrorHandler) {
// do the validation if a validator is registered
if (this.dataValidator != null) {
try {
boolean validationResult = this.dataValidator.validate(
this.layerCell, this.configRegistry, canonicalValue);
// if the validation succeeded, remove error rendering if exists
if (validationResult) {
this.validationEditErrorHandler.removeError(this);
} else {
throw new ValidationFailedException(
Messages.getString("AbstractCellEditor.validationFailure")); //$NON-NLS-1$
}
return validationResult;
} catch (Exception e) {
// validation failed
this.validationEditErrorHandler.displayError(this, this.configRegistry, e);
return false;
}
}
return true;
}
@Override
public boolean commit(MoveDirectionEnum direction) {
return commit(direction, true);
}
@Override
public boolean commit(MoveDirectionEnum direction, boolean closeAfterCommit) {
return commit(direction, closeAfterCommit, false);
}
@Override
public boolean commit(MoveDirectionEnum direction, boolean closeAfterCommit, boolean skipValidation) {
if (this.editHandler != null && this.dialog != null && !isClosed()) {
try {
// always do the conversion
Object canonicalValue = getCanonicalValue();
if (skipValidation
|| (!skipValidation && validateCanonicalValue(canonicalValue))) {
boolean committed = this.editHandler.commit(canonicalValue, direction);
if (committed && closeAfterCommit) {
close();
}
return committed;
}
} catch (ConversionFailedException | ValidationFailedException e) {
// do nothing as exceptions caused by conversion/validation are
// handled already we just need this catch block for stopping
// the process if conversion failed with an exception
} catch (Exception e) {
// if another exception occured that wasn't thrown by us, it
// should at least be logged without killing the whole
// application
LOG.error("Error on updating cell value: {}", e.getLocalizedMessage(), e); //$NON-NLS-1$
}
}
return false;
}
@Override
public Object getCommittedValue() {
return this.editHandler.getCommittedValue();
}
@Override
public abstract void close();
@Override
public abstract boolean isClosed();
@Override
public Control getEditorControl() {
// as this editor wraps a dialog, there is no explicit editor control
return null;
}
@Override
public Control createEditorControl(Composite parent) {
// as this editor wraps a dialog, there is no explicit editor control
return null;
}
@Override
public boolean openInline(IConfigRegistry configRegistry, List<String> configLabels) {
return false;
}
@Override
public boolean supportMultiEdit(IConfigRegistry configRegistry, List<String> configLabels) {
return EditConfigHelper.supportMultiEdit(configRegistry, configLabels);
}
@Override
public boolean openMultiEditDialog() {
return true;
}
@Override
public boolean openAdjacentEditor() {
// as editing with a dialog should only result in committing the value
// and
// then set the selection to the edited value, it doesn't make sense to
// open
// the adjacent editor.
return false;
}
@Override
public boolean activateAtAnyPosition() {
return true;
}
@Override
public boolean activateOnTraversal(IConfigRegistry configRegistry, List<String> configLabels) {
return EditConfigHelper.activateEditorOnTraversal(configRegistry, configLabels);
}
@Override
public void addEditorControlListeners() {
// there is no need for special editor control listeners here
}
@Override
public void removeEditorControlListeners() {
// there is no need for special editor control listeners here
}
@Override
public Rectangle calculateControlBounds(Rectangle cellBounds) {
return cellBounds;
}
@Override
public void setDialogSettings(Map<String, Object> editDialogSettings) {
this.editDialogSettings = editDialogSettings;
}
@Override
public int getColumnIndex() {
return this.layerCell.getColumnIndex();
}
@Override
public int getRowIndex() {
return this.layerCell.getRowIndex();
}
@Override
public int getColumnPosition() {
return this.layerCell.getColumnPosition();
}
@Override
public int getRowPosition() {
return this.layerCell.getRowPosition();
}
}