blob: 4296c3ac048d6c8e890149d987da091af250029a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011-2019 EclipseSource Muenchen GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* David Soto Setzke - initial API and implementation
* Johannes Faltermeier - initial API and implementation
* Christian W. Damus - bug 543348
******************************************************************************/
package org.eclipse.emfforms.spi.view.control.multiattribute;
import java.lang.reflect.InvocationTargetException;
import javax.inject.Inject;
import org.eclipse.core.databinding.Binding;
import org.eclipse.core.databinding.observable.IObserving;
import org.eclipse.core.databinding.observable.list.IObservableList;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.emf.common.notify.AdapterFactory;
import org.eclipse.emf.databinding.EMFDataBindingContext;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.ecore.impl.DynamicEObjectImpl;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecp.edit.spi.swt.table.ECPCellEditor;
import org.eclipse.emf.ecp.view.model.common.edit.provider.CustomReflectiveItemProviderAdapterFactory;
import org.eclipse.emf.ecp.view.model.common.util.RendererUtil;
import org.eclipse.emf.ecp.view.spi.context.ViewModelContext;
import org.eclipse.emf.ecp.view.spi.core.swt.AbstractControlSWTRenderer;
import org.eclipse.emf.ecp.view.spi.model.VControl;
import org.eclipse.emf.ecp.view.spi.model.VDomainModelReference;
import org.eclipse.emf.ecp.view.spi.provider.ECPTooltipModifierHelper;
import org.eclipse.emf.ecp.view.spi.renderer.NoPropertyDescriptorFoundExeption;
import org.eclipse.emf.ecp.view.spi.renderer.NoRendererFoundException;
import org.eclipse.emf.ecp.view.spi.swt.reporting.RenderingFailedReport;
import org.eclipse.emf.ecp.view.spi.util.swt.ImageRegistryService;
import org.eclipse.emf.ecp.view.template.model.VTViewTemplateProvider;
import org.eclipse.emf.ecp.view.template.style.tableStyleProperty.model.RenderMode;
import org.eclipse.emf.ecp.view.template.style.tableStyleProperty.model.VTTableStyleProperty;
import org.eclipse.emf.ecp.view.template.style.tableStyleProperty.model.VTTableStylePropertyFactory;
import org.eclipse.emf.edit.command.AddCommand;
import org.eclipse.emf.edit.command.MoveCommand;
import org.eclipse.emf.edit.command.RemoveCommand;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.emf.edit.provider.ComposedAdapterFactory;
import org.eclipse.emf.edit.ui.provider.AdapterFactoryLabelProvider;
import org.eclipse.emfforms.internal.view.control.multiattribute.Messages;
import org.eclipse.emfforms.internal.view.control.multiattribute.celleditor.CellEditorFactory;
import org.eclipse.emfforms.spi.common.report.AbstractReport;
import org.eclipse.emfforms.spi.common.report.ReportService;
import org.eclipse.emfforms.spi.core.services.databinding.DatabindingFailedException;
import org.eclipse.emfforms.spi.core.services.databinding.EMFFormsDatabinding;
import org.eclipse.emfforms.spi.core.services.label.EMFFormsLabelProvider;
import org.eclipse.emfforms.spi.core.services.label.NoLabelFoundException;
import org.eclipse.emfforms.spi.swt.core.SWTDataElementIdHelper;
import org.eclipse.emfforms.spi.swt.core.layout.GridDescriptionFactory;
import org.eclipse.emfforms.spi.swt.core.layout.SWTGridCell;
import org.eclipse.emfforms.spi.swt.core.layout.SWTGridDescription;
import org.eclipse.jface.databinding.viewers.ObservableListContentProvider;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.layout.TableColumnLayout;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.ColumnViewer;
import org.eclipse.jface.viewers.ColumnViewerEditor;
import org.eclipse.jface.viewers.ColumnViewerEditorActivationEvent;
import org.eclipse.jface.viewers.ColumnViewerEditorActivationListener;
import org.eclipse.jface.viewers.ColumnViewerEditorActivationStrategy;
import org.eclipse.jface.viewers.ColumnViewerEditorDeactivationEvent;
import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.viewers.EditingSupport;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.TableViewerEditor;
import org.eclipse.jface.viewers.TableViewerRow;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableItem;
import org.osgi.framework.FrameworkUtil;
/**
* Renderer for MultiReferenceControl.
*
* @author David Soto Setzke
* @author Johannes Faltermeier
*
*/
public class MultiAttributeSWTRenderer extends AbstractControlSWTRenderer<VControl> {
private static final String ICON_ADD = "icons/add.png"; //$NON-NLS-1$
private static final String ICON_DELETE = "icons/delete.png"; //$NON-NLS-1$
private static final String ICONS_ARROW_DOWN_PNG = "icons/arrow_down.png";//$NON-NLS-1$
private static final String ICONS_ARROW_UP_PNG = "icons/arrow_up.png";//$NON-NLS-1$
private final ImageRegistryService imageRegistryService;
private AdapterFactoryLabelProvider labelProvider;
private ComposedAdapterFactory composedAdapterFactory;
private Composite mainComposite;
private TableViewer tableViewer;
private Button removeButton;
private Button addButton;
private final EMFDataBindingContext viewModelDBC;
private Label validationIcon;
private AddButtonSelectionAdapter addButtonSelectionAdapter;
private RemoveButtonSelectionAdapter removeButtonSelectionAdapter;
private UpButtonSelectionAdapter upButtonSelectionAdapter;
private DownButtonSelectionAdapter downButtonSelectionAdapter;
private ECPListEditingSupport observableSupport;
private SWTGridDescription rendererGridDescription;
private Button upButton;
private Button downButton;
/**
* Default constructor.
*
* @param vElement
* the view model element to be rendered
* @param viewContext
* the view context
* @param emfFormsDatabinding
* The {@link EMFFormsDatabinding}
* @param emfFormsLabelProvider
* The {@link EMFFormsLabelProvider}
* @param reportService
* The {@link ReportService}
* @param vtViewTemplateProvider
* The {@link VTViewTemplateProvider}
* @param imageRegistryService
* The {@link ImageRegistryService}
*/
@Inject
public MultiAttributeSWTRenderer(VControl vElement, ViewModelContext viewContext, ReportService reportService,
EMFFormsDatabinding emfFormsDatabinding, EMFFormsLabelProvider emfFormsLabelProvider,
VTViewTemplateProvider vtViewTemplateProvider, ImageRegistryService imageRegistryService) {
super(vElement, viewContext, reportService, emfFormsDatabinding, emfFormsLabelProvider, vtViewTemplateProvider);
this.imageRegistryService = imageRegistryService;
viewModelDBC = new EMFDataBindingContext();
}
/**
* Creates the default {@link VTTableStyleProperty}.
*
* @return the default {@link VTTableStyleProperty}
* @since 1.14
*/
protected VTTableStyleProperty createDefaultTableStyleProperty() {
return VTTableStylePropertyFactory.eINSTANCE.createTableStyleProperty();
}
/**
* Returns the {@link VTTableStyleProperty}.
*
* @return the {@link VTTableStyleProperty}
* @since 1.14
*/
protected VTTableStyleProperty getTableStyleProperty() {
VTTableStyleProperty styleProperty = RendererUtil.getStyleProperty(getVTViewTemplateProvider(), getVElement(),
getViewModelContext(), VTTableStyleProperty.class);
if (styleProperty == null) {
styleProperty = createDefaultTableStyleProperty();
}
return styleProperty;
}
@Override
public SWTGridDescription getGridDescription(SWTGridDescription gridDescription) {
if (rendererGridDescription == null) {
// create special grid for compact mode
if (getTableStyleProperty().getRenderMode() == RenderMode.COMPACT_VERTICALLY) {
rendererGridDescription = GridDescriptionFactory.INSTANCE.createCompactGrid(false, true, this);
} else {
rendererGridDescription = GridDescriptionFactory.INSTANCE.createSimpleGrid(1, 1, this);
}
}
return rendererGridDescription;
}
/**
* Returns the height for the table that will be created.
*
* @return the height hint
*/
protected int getTableHeightHint() {
return 200;
}
/**
* Gives access to the tableViewer used to display the attributes.
*
* @return the viewer
*/
protected TableViewer getTableViewer() {
return tableViewer;
}
/**
* Creates the composite which will be the parent for the table.
*
* @param composite
* the parent composite
* @return the table composite
*/
protected Composite createControlComposite(Composite composite) {
final Composite controlComposite = new Composite(composite, SWT.NONE);
GridDataFactory.fillDefaults().grab(true, true).align(SWT.FILL, SWT.FILL).hint(1, getTableHeightHint())
.applyTo(controlComposite);
GridLayoutFactory.fillDefaults().numColumns(1).applyTo(controlComposite);
return controlComposite;
}
/**
* Returns an {@link Image} from the image registry.
*
* @param path
* the path to the image
* @return the image
*/
protected Image getImage(String path) {
return imageRegistryService.getImage(FrameworkUtil.getBundle(MultiAttributeSWTRenderer.class), path);
}
private Button createRemoveRowButton(final Composite buttonComposite, IObservableList list) {
final EAttribute attribute = EAttribute.class.cast(list.getElementType());
final Button removeButton = new Button(buttonComposite, SWT.None);
SWTDataElementIdHelper.setElementIdDataWithSubId(removeButton, getVElement(), "remove", getViewModelContext()); //$NON-NLS-1$
final Image image = getImage(ICON_DELETE);
removeButton.setImage(image);
removeButton.setEnabled(!getVElement().isEffectivelyReadonly());
if (list.size() <= attribute.getLowerBound()) {
removeButton.setEnabled(false);
}
return removeButton;
}
private Button createAddRowButton(final Composite buttonComposite, IObservableList list) {
final EAttribute attribute = EAttribute.class.cast(list.getElementType());
final Button addButton = new Button(buttonComposite, SWT.None);
SWTDataElementIdHelper.setElementIdDataWithSubId(addButton, getVElement(), "add", getViewModelContext()); //$NON-NLS-1$
final Image image = getImage(ICON_ADD);
addButton.setImage(image);
if (attribute.getUpperBound() != -1 && list.size() >= attribute.getUpperBound()) {
addButton.setEnabled(false);
}
addButton.setToolTipText(Messages.MultiAttributeSWTRenderer_AddButtonTooltip);
return addButton;
}
@Override
protected void applyReadOnly() {
// do not let the super method disable the control, so the table is still enabled for sorting for example
// when applying read only, all buttons shall be hidden
updateButtonVisibility();
}
@Override
protected boolean ignoreEnableOnReadOnly() {
// always take the enable state into account (read only but enable let the user sort the table content for
// example)
return false;
}
/**
* Updates button visibility and enablement.
*/
protected void updateButtons() {
updateButtonVisibility();
updateButtonEnabling();
}
/**
* Updates the visibility of 'Add', 'Remove', 'Up', 'Down' buttons according to the bound input.
*/
protected void updateButtonVisibility() {
final boolean isVisible = !getVElement().isEffectivelyReadonly();
if (addButton != null) {
addButton.setVisible(isVisible);
}
if (removeButton != null) {
removeButton.setVisible(isVisible);
}
if (upButton != null) {
upButton.setVisible(isVisible);
}
if (downButton != null) {
downButton.setVisible(isVisible);
}
}
/**
* Updates the enablement of 'addExisting', 'addNew', 'delete', 'moveUp' and 'moveDown' buttons according to the
* bound input.
*/
protected void updateButtonEnabling() {
final boolean isEnable = getVElement().isEffectivelyEnabled();
final int listSize = tableViewer != null ? tableViewer.getTable().getItemCount() : 0;
final int selectionIndex = tableViewer != null ? tableViewer.getTable().getSelectionIndex() : -1;
enableUpButton(isEnable, listSize, selectionIndex);
enableDownButton(isEnable, listSize, selectionIndex);
enableAddButton(isEnable, listSize, selectionIndex);
enableDeleteButton(isEnable, listSize, selectionIndex);
}
private void enableUpButton(boolean baseEnable, int listSize, int selectionIndex) {
if (upButton != null) {
final boolean enabled = baseEnable && listSize > 1 && selectionIndex > 0;
upButton.setEnabled(enabled);
}
}
private void enableDownButton(boolean baseEnable, int listSize, int selectionIndex) {
if (downButton != null) {
final boolean enabled = baseEnable && listSize > 1 && selectionIndex != -1 && selectionIndex < listSize - 1;
downButton.setEnabled(enabled);
}
}
private void enableAddButton(boolean baseEnable, int listSize, int selectionIndex) {
if (addButton != null) {
addButton.setEnabled(baseEnable);
}
}
private void enableDeleteButton(boolean baseEnable, int listSize, int selectionIndex) {
if (removeButton != null) {
removeButton.setEnabled(baseEnable && listSize > 0 && selectionIndex != -1);
}
}
@Override
protected Control renderControl(SWTGridCell cell, Composite parent)
throws NoPropertyDescriptorFoundExeption, NoRendererFoundException {
if (rendererGridDescription.getColumns() == 1) {
// Default
return renderMultiAttributeControl(cell, parent);
}
// Compact: render validation icon
if (cell.getColumn() == 0 && rendererGridDescription.getColumns() > 1) {
validationIcon = createValidationIcon(parent);
GridDataFactory.fillDefaults().hint(16, 17).grab(false, false).applyTo(validationIcon);
return validationIcon;
}
// Compact: render list and buttons next to each other
final Composite composite = new Composite(parent, SWT.NONE);
composite.setBackground(parent.getBackground());
try {
final IObservableList list = getEMFFormsDatabinding().getObservableList(
getVElement().getDomainModelReference(),
getViewModelContext().getDomainModel());
GridLayoutFactory.fillDefaults().numColumns(2).applyTo(composite);
final Control multiAttributeComposite = renderMultiAttributeControl(cell, composite);
GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).grab(true, true).applyTo(multiAttributeComposite);
final Composite buttonComposite = createButtonComposite(composite, list);
GridDataFactory.fillDefaults().align(SWT.END, SWT.BEGINNING).grab(false, false).applyTo(buttonComposite);
initButtons(list);
} catch (final DatabindingFailedException ex) {
getReportService().report(new RenderingFailedReport(ex));
return createErrorLabel(composite, ex);
}
return composite;
}
/**
* Renders the MultiAttribute Control.
*
* Renders the MultiReference control including validation and buttons when {@link RenderMode} is set to
* {@link RenderMode#DEFAULT}. Only renders the
* MultiReferfence control without validation and buttons when renderMode is set to
* {@link RenderMode#COMPACT_VERTICALLY}.
*
* @param cell the {@link SWTGridCell}.
* @param parent the {@link Composite}.
* @return the rendered {@link Control}
* @throws NoRendererFoundException the {@link NoRendererFoundException}.
* @throws NoPropertyDescriptorFoundExeption the {@link NoPropertyDescriptorFoundExeption}.
* @since 1.14
*/
protected Control renderMultiAttributeControl(SWTGridCell cell, Composite parent)
throws NoRendererFoundException, NoPropertyDescriptorFoundExeption {
final Composite composite = new Composite(parent, SWT.NONE);
composite.setBackground(parent.getBackground());
if (getTableStyleProperty().getRenderMode() == RenderMode.COMPACT_VERTICALLY) {
// Avoid the default margins of "new GridLayout()"
GridLayoutFactory.fillDefaults().applyTo(composite);
} else {
composite.setLayout(new GridLayout(1, false));
}
createLabelProvider();
IObservableList list;
try {
list = getEMFFormsDatabinding().getObservableList(getVElement().getDomainModelReference(),
getViewModelContext().getDomainModel());
} catch (final DatabindingFailedException ex1) {
getReportService().report(new RenderingFailedReport(ex1));
return composite;
}
if (getTableStyleProperty().getRenderMode() == RenderMode.DEFAULT) {
final Composite titleComposite = createTitleComposite(composite);
createButtonComposite(titleComposite, list);
}
final Composite controlComposite = createControlComposite(composite);
createContent(controlComposite, list);
SWTDataElementIdHelper.setElementIdDataForVControl(composite, getVElement(), getViewModelContext());
if (getTableStyleProperty().getRenderMode() == RenderMode.DEFAULT) {
initButtons(list);
}
return composite;
}
/**
* Creates the Title Composite containing validation and buttons. Buttons have to be initialized afterwards.
*
* @param parent the parent {@link Composite}.
* @return the created {@link Composite}.
* @since 1.14
*/
protected Composite createTitleComposite(Composite parent) {
final Composite titleComposite = new Composite(parent, SWT.NONE);
titleComposite.setBackground(parent.getBackground());
GridDataFactory.fillDefaults().grab(true, false).align(SWT.FILL, SWT.BEGINNING).applyTo(titleComposite);
GridLayoutFactory.fillDefaults().numColumns(2).equalWidth(false).applyTo(titleComposite);
validationIcon = createValidationIcon(titleComposite);
GridDataFactory.fillDefaults().hint(16, 17).grab(false, false).applyTo(validationIcon);
return titleComposite;
}
/**
* Create the button composite. Buttons have to be initialized after the table is created.
*
* @param parent the {@link Composite} parent.
* @param list the {@link IObservableList}.
* @return the created {@link Composite}.
* @since 1.14
*/
protected Composite createButtonComposite(Composite parent, IObservableList list) {
final Composite buttonComposite = new Composite(parent, SWT.NONE);
final EAttribute attribute = EAttribute.class.cast(list.getElementType());
buttonComposite.setBackground(parent.getBackground());
GridDataFactory.fillDefaults().align(SWT.END, SWT.BEGINNING).grab(true, false).applyTo(buttonComposite);
int numButtons = 0;
if (attribute.isOrdered()) {
createUpDownButtons(buttonComposite, list);
numButtons += 2;
}
addButton = createAddRowButton(buttonComposite, list);
removeButton = createRemoveRowButton(buttonComposite, list);
numButtons += 2;
GridLayoutFactory.fillDefaults().numColumns(numButtons).equalWidth(false).applyTo(buttonComposite);
return buttonComposite;
}
/**
* Initializes the buttons. Call this after table is created.
*
* @param list the {@link IObservableList}.
* @since 1.14
*/
protected void initButtons(IObservableList list) {
initAddButton(addButton, list);
initRemoveButton(removeButton, list);
initUpButton(upButton, list);
initDownButton(downButton, list);
getTableViewer().addSelectionChangedListener(new ISelectionChangedListener() {
@Override
public void selectionChanged(SelectionChangedEvent event) {
updateButtonEnabling();
}
});
updateButtons();
}
private void createLabelProvider() {
composedAdapterFactory = new ComposedAdapterFactory(
new AdapterFactory[] { new CustomReflectiveItemProviderAdapterFactory(),
new ComposedAdapterFactory(ComposedAdapterFactory.Descriptor.Registry.INSTANCE) });
labelProvider = new AdapterFactoryLabelProvider(composedAdapterFactory);
labelProvider.setFireLabelUpdateNotifications(true);
}
private void initAddButton(Button addButton, IObservableList list) {
if (addButton == null) {
return;
}
addButtonSelectionAdapter = new AddButtonSelectionAdapter(list);
addButton.addSelectionListener(addButtonSelectionAdapter);
}
private void initRemoveButton(Button removeButton, IObservableList list) {
if (removeButton == null) {
return;
}
removeButtonSelectionAdapter = new RemoveButtonSelectionAdapter(list);
removeButton.addSelectionListener(removeButtonSelectionAdapter);
}
private void initUpButton(Button upButton, IObservableList list) {
if (upButton == null) {
return;
}
upButtonSelectionAdapter = new UpButtonSelectionAdapter(list);
upButton.addSelectionListener(upButtonSelectionAdapter);
upButton.setEnabled(false);
}
private void initDownButton(Button downButton, IObservableList list) {
if (downButton == null) {
return;
}
downButtonSelectionAdapter = new DownButtonSelectionAdapter(list);
downButton.addSelectionListener(downButtonSelectionAdapter);
// by default, button should not be enabled (selection is empty)
downButton.setEnabled(false);
}
/**
* Create the up and down buttons.
*
* @param composite The {@link Composite} to create the buttons on
* @param list The {@link IObservableList} of the current {@link MultiAttributeSWTRenderer}
* @since 1.17
*/
protected void createUpDownButtons(Composite composite, IObservableList list) {
final Image up = getImage(ICONS_ARROW_UP_PNG);
final Image down = getImage(ICONS_ARROW_DOWN_PNG);
upButton = new Button(composite, SWT.PUSH);
SWTDataElementIdHelper.setElementIdDataWithSubId(upButton, getVElement(), "up", getViewModelContext()); //$NON-NLS-1$
upButton.setImage(up);
upButton.setEnabled(!getVElement().isEffectivelyReadonly());
downButton = new Button(composite, SWT.PUSH);
SWTDataElementIdHelper.setElementIdDataWithSubId(downButton, getVElement(), "down", getViewModelContext()); //$NON-NLS-1$
downButton.setImage(down);
downButton.setEnabled(!getVElement().isEffectivelyReadonly());
}
private InternalEObject getInstanceOf(EClass clazz) {
EObject tempInstance;
if (clazz.isInterface() || clazz.isAbstract() || clazz.getInstanceClass() == null) {
tempInstance = new DynamicEObjectImpl(clazz);
} else {
tempInstance = EcoreUtil.create(clazz);
}
return InternalEObject.class.cast(tempInstance);
}
private void createContent(Composite composite, IObservableList list) {
final EAttribute attribute = EAttribute.class.cast(list.getElementType());
tableViewer = new TableViewer(composite, SWT.MULTI | SWT.V_SCROLL | SWT.FULL_SELECTION | SWT.BORDER);
tableViewer.getTable().setData(CUSTOM_VARIANT, "org_eclipse_emf_ecp_control_multiattribute"); //$NON-NLS-1$
tableViewer.getTable().setHeaderVisible(true);
tableViewer.getTable().setLinesVisible(true);
final ColumnViewerEditorActivationStrategy actSupport = new ColumnViewerEditorActivationStrategy(tableViewer) {
@Override
protected boolean isEditorActivationEvent(ColumnViewerEditorActivationEvent event) {
if (getVElement().isEffectivelyReadonly()) {
return false;
}
return event.eventType == ColumnViewerEditorActivationEvent.TRAVERSAL
|| event.eventType == ColumnViewerEditorActivationEvent.MOUSE_CLICK_SELECTION
|| event.eventType == ColumnViewerEditorActivationEvent.KEY_PRESSED && event.keyCode == SWT.CR
|| event.eventType == ColumnViewerEditorActivationEvent.PROGRAMMATIC;
}
};
TableViewerEditor.create(tableViewer, null, actSupport,
ColumnViewerEditor.TABBING_HORIZONTAL | ColumnViewerEditor.TABBING_MOVE_TO_ROW_NEIGHBOR
| ColumnViewerEditor.TABBING_VERTICAL | ColumnViewerEditor.KEYBOARD_ACTIVATION);
ColumnViewerToolTipSupport.enableFor(tableViewer);
final ObservableListContentProvider cp = new ObservableListContentProvider();
final EMFFormsLabelProvider labelService = getEMFFormsLabelProvider();
final TableViewerColumn column = new TableViewerColumn(tableViewer, SWT.NONE);
column.getColumn().setResizable(false);
column.getColumn().setMoveable(false);
final EClass clazz = attribute.getEContainingClass();
InternalEObject tempInstance = null;
if (!clazz.isAbstract() && !clazz.isInterface()) {
tempInstance = getInstanceOf(clazz);
}
final CellEditor cellEditor = createCellEditor(tempInstance, attribute, tableViewer.getTable());
@SuppressWarnings("deprecation")
final IObservableValue textObservableValue = org.eclipse.jface.databinding.swt.SWTObservables
.observeText(column.getColumn());
@SuppressWarnings("deprecation")
final IObservableValue tooltipObservableValue = org.eclipse.jface.databinding.swt.SWTObservables
.observeTooltipText(column.getColumn());
try {
viewModelDBC.bindValue(textObservableValue, labelService
.getDisplayName(getVElement().getDomainModelReference(), getViewModelContext().getDomainModel()));
viewModelDBC.bindValue(tooltipObservableValue, labelService
.getDescription(getVElement().getDomainModelReference(), getViewModelContext().getDomainModel()));
} catch (final NoLabelFoundException e) {
getReportService().report(new RenderingFailedReport(e));
}
tableViewer.setLabelProvider(labelProvider);
tableViewer.setContentProvider(cp);
tableViewer.setInput(list);
final TableColumnLayout layout = new TableColumnLayout();
composite.setLayout(layout);
layout.setColumnData(column.getColumn(), new ColumnWeightData(1, false));
final VDomainModelReference dmr = getVElement().getDomainModelReference();
observableSupport = new ECPListEditingSupport(tableViewer, cellEditor, getVElement(), dmr,
list);
column.setEditingSupport(observableSupport);
}
private CellEditor createCellEditor(final EObject tempInstance, final EAttribute attribute, Table table) {
return CellEditorFactory.INSTANCE.createCellEditor(attribute, tempInstance, table, getViewModelContext());
}
/**
* Returns the attribute value which should be added as a new element.
*
* @param attribute the {@link EAttribute} with the data type
* @return the new value
* @since 1.13
*/
protected Object getValueForNewRow(final EAttribute attribute) {
try {
Object defaultValue = attribute.getDefaultValue();
if (defaultValue == null || !attribute.getEType().isInstance(defaultValue)) {
// Use a singular default value, but not a multiple value if that's
// what is specified in the model, because we shouldn't add more than
// one value
defaultValue = attribute.getEType().getDefaultValue();
}
if (defaultValue == null) {
defaultValue = attribute.getEType().getInstanceClass().getConstructor().newInstance();
}
return defaultValue;
} catch (final InstantiationException ex) {
getReportService().report(new AbstractReport(ex, Messages.MultiAttributeSWTRenderer_AddFailed));
} catch (final IllegalAccessException ex) {
getReportService().report(new AbstractReport(ex, Messages.MultiAttributeSWTRenderer_AddFailed));
} catch (final IllegalArgumentException ex) {
getReportService().report(new AbstractReport(ex, Messages.MultiAttributeSWTRenderer_AddFailed));
} catch (final InvocationTargetException ex) {
getReportService().report(new AbstractReport(ex, Messages.MultiAttributeSWTRenderer_AddFailed));
} catch (final NoSuchMethodException ex) {
getReportService().report(new AbstractReport(ex, Messages.MultiAttributeSWTRenderer_AddFailed));
} catch (final SecurityException ex) {
getReportService().report(new AbstractReport(ex, Messages.MultiAttributeSWTRenderer_AddFailed));
}
throw new IllegalStateException();
}
@Override
protected void applyValidation() {
Display.getDefault().asyncExec(new Runnable() {
@Override
public void run() {
if (validationIcon == null) {
return;
}
if (validationIcon.isDisposed()) {
return;
}
if (getVElement().getDiagnostic() == null) {
return;
}
validationIcon.setImage(getValidationIcon());
validationIcon.setToolTipText(ECPTooltipModifierHelper.modifyString(getVElement().getDiagnostic()
.getMessage(), null));
}
});
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.ecp.view.spi.core.swt.AbstractControlSWTRenderer#applyEnable()
*/
@Override
protected void applyEnable() {
super.applyEnable();
updateButtonEnabling();
}
/**
* @return
*/
private Button getMoveDownButton() {
return downButton;
}
/**
* @return
*/
private Button getMoveUpButton() {
return upButton;
}
/**
* @return
*/
private Button getRemoveButton() {
return removeButton;
}
/**
* @return
*/
private Button getAddButton() {
return addButton;
}
/**
* {@inheritDoc}
*/
@Override
public void dispose() {
if (mainComposite != null) {
mainComposite.dispose();
}
if (labelProvider != null) {
labelProvider.dispose();
}
if (composedAdapterFactory != null) {
composedAdapterFactory.dispose();
}
super.dispose();
}
/**
* Listener for the down button.
*
* @author David Soto Setzke
* @author Johannes Faltermeier
*
*/
private final class DownButtonSelectionAdapter extends SelectionAdapter {
private IObservableList list;
DownButtonSelectionAdapter(IObservableList list) {
setObservableList(list);
}
public void setObservableList(IObservableList list) {
this.list = list;
}
@Override
public void widgetSelected(SelectionEvent e) {
final IObserving observing = IObserving.class.cast(list);
final EObject eObject = EObject.class.cast(observing.getObserved());
final EAttribute attribute = EAttribute.class.cast(list.getElementType());
final int currentIndex = tableViewer.getTable().getSelectionIndex();
if (currentIndex + 1 < tableViewer.getTable().getItems().length) {
final EditingDomain editingDomain = getEditingDomain(eObject);
editingDomain.getCommandStack()
.execute(new MoveCommand(editingDomain, eObject, attribute, currentIndex, currentIndex + 1));
tableViewer.refresh();
tableViewer.getTable().setSelection(currentIndex + 1);
final TableItem[] selection = tableViewer.getTable().getSelection();
if (selection.length > 0) {
tableViewer.getTable().showItem(selection[0]);
}
}
}
}
/**
* Listener for the up button.
*
* @author David Soto Setzke
* @author Johannes Faltermeier
*
*/
private final class UpButtonSelectionAdapter extends SelectionAdapter {
private IObservableList list;
UpButtonSelectionAdapter(IObservableList list) {
setObservableList(list);
}
public void setObservableList(IObservableList list) {
this.list = list;
}
@Override
public void widgetSelected(SelectionEvent e) {
final IObserving observing = IObserving.class.cast(list);
final EObject eObject = EObject.class.cast(observing.getObserved());
final EAttribute attribute = EAttribute.class.cast(list.getElementType());
final int currentIndex = tableViewer.getTable().getSelectionIndex();
if (currentIndex != 0) {
final EditingDomain editingDomain = getEditingDomain(eObject);
editingDomain.getCommandStack()
.execute(new MoveCommand(editingDomain, eObject, attribute, currentIndex, currentIndex - 1));
tableViewer.refresh();
tableViewer.getTable().setSelection(currentIndex - 1);
final TableItem[] selection = tableViewer.getTable().getSelection();
if (selection.length > 0) {
tableViewer.getTable().showItem(selection[0]);
}
}
}
}
/**
* Listener for the remove button.
*
* @author David Soto Setzke
* @author Johannes Faltermeier
*
*/
private final class RemoveButtonSelectionAdapter extends SelectionAdapter {
private IObservableList list;
RemoveButtonSelectionAdapter(IObservableList list) {
setObservableList(list);
}
public void setObservableList(IObservableList list) {
this.list = list;
}
@Override
public void widgetSelected(SelectionEvent e) {
final IObserving observing = IObserving.class.cast(list);
final EObject eObject = EObject.class.cast(observing.getObserved());
final EAttribute attribute = EAttribute.class.cast(list.getElementType());
final EditingDomain editingDomain = getEditingDomain(eObject);
final IStructuredSelection selection = tableViewer.getStructuredSelection();
if (!selection.isEmpty()) {
editingDomain.getCommandStack().execute(RemoveCommand.create(editingDomain, eObject, attribute,
selection.toList()));
postRemove(selection);
}
}
}
/**
* Listener for the add button.
*
* @author David Soto Setzke
* @author Johannes Faltermeier
*
*/
private final class AddButtonSelectionAdapter extends SelectionAdapter {
private IObservableList list;
AddButtonSelectionAdapter(IObservableList list) {
setObservableList(list);
}
public void setObservableList(IObservableList list) {
this.list = list;
}
@Override
public void widgetSelected(SelectionEvent event) {
try {
final IObserving observing = IObserving.class.cast(list);
final EObject eObject = EObject.class.cast(observing.getObserved());
final EAttribute attribute = EAttribute.class.cast(list.getElementType());
final Object defaultValue = getValueForNewRow(attribute);
if (defaultValue == null) {
return;
}
final EditingDomain editingDomain = getEditingDomain(getViewModelContext().getDomainModel());
editingDomain.getCommandStack()
.execute(AddCommand.create(editingDomain, eObject, attribute, defaultValue));
} catch (final IllegalStateException ex) {
/* logged by getValueForNewRow* already */
}
}
}
/**
* Editing support for a single element in a multi {@link EAttribute}.
*
* @author jfaltermeier
*
*/
private class ECPListEditingSupport extends EditingSupport {
private final CellEditor cellEditor;
private final VControl control;
private IObservableList valueProperty;
ECPListEditingSupport(ColumnViewer viewer, CellEditor cellEditor, VControl control,
VDomainModelReference domainModelReference, IObservableList valueProperty) {
super(viewer);
this.cellEditor = cellEditor;
this.control = control;
this.valueProperty = valueProperty;
}
private EditingState editingState;
private final ColumnViewerEditorActivationListenerHelper activationListener = new ColumnViewerEditorActivationListenerHelper();
/**
* Default implementation always returns <code>true</code>.
*
* @see org.eclipse.jface.viewers.EditingSupport#canEdit(java.lang.Object)
*/
@Override
protected boolean canEdit(Object element) {
final boolean editable = control.isEffectivelyEnabled() && !control.isEffectivelyReadonly();
if (ECPCellEditor.class.isInstance(cellEditor)) {
ECPCellEditor.class.cast(cellEditor).setEditable(editable);
return editable;
}
return editable;
}
/**
* Default implementation always returns <code>null</code> as this will
* be handled by the Binding.
*
* @see org.eclipse.jface.viewers.EditingSupport#getValue(java.lang.Object)
*/
@Override
protected Object getValue(Object element) {
// no op
return null;
}
/**
* Default implementation does nothing as this will be handled by the
* Binding.
*
* @see org.eclipse.jface.viewers.EditingSupport#setValue(java.lang.Object,
* java.lang.Object)
*/
@Override
protected void setValue(Object element, Object value) {
// no op
}
/**
* Creates a {@link Binding} between the editor and the element to be
* edited. Invokes {@link #doCreateCellEditorObservable(CellEditor)},
* {@link #doCreateElementObservable(Object, ViewerCell)}, and then
* {@link #createBinding(IObservableValue, IObservableValue)}.
*/
@Override
protected void initializeCellEditorValue(CellEditor cellEditor, ViewerCell cell) {
final IObservableValue target = doCreateCellEditorObservable(cellEditor);
final TableViewerRow viewerRow = (TableViewerRow) cell.getViewerRow();
final TableItem item = (TableItem) viewerRow.getItem();
final int index = item.getParent().indexOf(item);
final EAttribute fakeAttribute = EcoreUtil.copy(EAttribute.class.cast(valueProperty.getElementType()));
fakeAttribute.setUpperBound(1);
fakeAttribute.setLowerBound(0);
final IObservableValue model = new org.eclipse.emf.ecp.edit.internal.swt.util.ECPObservableValue(
valueProperty, index,
fakeAttribute);
final Binding binding = createBinding(target, model);
editingState = new EditingState(binding, target, model);
getViewer().getColumnViewerEditor().addEditorActivationListener(activationListener);
}
@Override
protected CellEditor getCellEditor(Object element) {
return cellEditor;
}
protected Binding createBinding(IObservableValue target, IObservableValue model) {
if (ECPCellEditor.class.isInstance(cellEditor)) {
return getDataBindingContext().bindValue(target, model,
((ECPCellEditor) cellEditor).getTargetToModelStrategy(getDataBindingContext()),
((ECPCellEditor) cellEditor).getModelToTargetStrategy(getDataBindingContext()));
}
return getDataBindingContext().bindValue(target, model);
}
@SuppressWarnings("deprecation")
protected IObservableValue doCreateCellEditorObservable(CellEditor cellEditor) {
if (ECPCellEditor.class.isInstance(cellEditor)) {
return ((ECPCellEditor) cellEditor).getValueProperty().observe(cellEditor);
}
return org.eclipse.jface.databinding.swt.SWTObservables.observeText(cellEditor.getControl(), SWT.FocusOut);
}
@Override
protected final void saveCellEditorValue(CellEditor cellEditor, ViewerCell cell) {
editingState.binding.updateTargetToModel();
}
/**
* A ColumnViewerEditorActivationListener to reset the cells after focus
* lost.
*
* @author Eugen Neufeld
*
*/
private class ColumnViewerEditorActivationListenerHelper extends ColumnViewerEditorActivationListener {
@Override
public void afterEditorActivated(ColumnViewerEditorActivationEvent event) {
// do nothing
}
@Override
public void afterEditorDeactivated(ColumnViewerEditorDeactivationEvent event) {
editingState.dispose();
editingState = null;
getViewer().getColumnViewerEditor().removeEditorActivationListener(this);
final ViewerCell focusCell = getViewer().getColumnViewerEditor().getFocusCell();
if (focusCell != null) {
getViewer().update(focusCell.getElement(), null);
}
}
@Override
public void beforeEditorActivated(ColumnViewerEditorActivationEvent event) {
// do nothing
}
@Override
public void beforeEditorDeactivated(ColumnViewerEditorDeactivationEvent event) {
// do nothing
}
}
/**
* Maintains references to objects that only live for the length of the
* edit cycle.
*/
class EditingState {
private final IObservableValue target;
private final IObservableValue model;
private final Binding binding;
EditingState(Binding binding, IObservableValue target, IObservableValue model) {
this.binding = binding;
this.target = target;
this.model = model;
}
void dispose() {
binding.dispose();
target.dispose();
model.dispose();
}
}
public void setObservableList(IObservableList list) {
valueProperty = list;
}
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.ecp.view.spi.core.swt.AbstractControlSWTRenderer#rootDomainModelChanged()
*/
@Override
protected void rootDomainModelChanged() throws DatabindingFailedException {
final IObservableList oldList = (IObservableList) getTableViewer().getInput();
oldList.dispose();
final IObservableList list = getEMFFormsDatabinding().getObservableList(
getVElement().getDomainModelReference(),
getViewModelContext().getDomainModel());
// addRelayoutListenerIfNeeded(list, composite);
getTableViewer().setInput(list);
addButtonSelectionAdapter.setObservableList(list);
removeButtonSelectionAdapter.setObservableList(list);
upButtonSelectionAdapter.setObservableList(list);
downButtonSelectionAdapter.setObservableList(list);
observableSupport.setObservableList(list);
}
/**
* This is called after the selected elements were deleted so that the user can handle this removal.
*
* @param selection The {@link IStructuredSelection} of the TableViewer before deletion.
* @since 1.17
*/
protected void postRemove(IStructuredSelection selection) {
// do nothing
}
/**
* Creates an error label for the given {@link Exception}.
*
* @param parent The parent of the {@link Label}
* @param ex The {@link Exception} causing the error
* @return The error {@link Label}
* @since 1.14
*/
protected Control createErrorLabel(Composite parent, final Exception ex) {
final Label errorLabel = new Label(parent, SWT.NONE);
errorLabel.setText(ex.getMessage());
return errorLabel;
}
}