blob: 7b3830abfb81ebb1890e042b90008c51af1d61e0 [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:
* Eugen Neufeld - initial API and implementation
* Lucas Koehler - use data binding services
* Martin Fleck - bug 487101
* Christian W. Damus - bugs 527736, 548592, 552385
******************************************************************************/
package org.eclipse.emf.ecp.view.internal.control.multireference;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import javax.inject.Inject;
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.command.Command;
import org.eclipse.emf.common.notify.AdapterFactory;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.databinding.EMFDataBindingContext;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecp.edit.internal.swt.controls.TableViewerColumnBuilder;
import org.eclipse.emf.ecp.edit.spi.ConditionalDeleteService;
import org.eclipse.emf.ecp.edit.spi.DeleteService;
import org.eclipse.emf.ecp.edit.spi.EMFDeleteServiceImpl;
import org.eclipse.emf.ecp.edit.spi.ReferenceService;
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.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.reference.model.VTReferenceFactory;
import org.eclipse.emf.ecp.view.template.style.reference.model.VTReferenceStyleProperty;
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.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.provider.IDisposable;
import org.eclipse.emf.edit.ui.provider.AdapterFactoryLabelProvider;
import org.eclipse.emfforms.spi.common.BundleResolver;
import org.eclipse.emfforms.spi.common.BundleResolver.NoBundleFoundException;
import org.eclipse.emfforms.spi.common.BundleResolverFactory;
import org.eclipse.emfforms.spi.common.report.AbstractReport;
import org.eclipse.emfforms.spi.common.report.ReportService;
import org.eclipse.emfforms.spi.common.sort.NumberAwareStringComparator;
import org.eclipse.emfforms.spi.core.services.databinding.DatabindingFailedException;
import org.eclipse.emfforms.spi.core.services.databinding.DatabindingFailedReport;
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.localization.EMFFormsLocalizationService;
import org.eclipse.emfforms.spi.localization.LocalizationServiceHelper;
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.emfforms.spi.swt.core.ui.ObjectViewerComparator;
import org.eclipse.jface.databinding.swt.WidgetProperties;
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.ColumnViewerEditor;
import org.eclipse.jface.viewers.ColumnViewerEditorActivationEvent;
import org.eclipse.jface.viewers.ColumnViewerEditorActivationStrategy;
import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.TableViewerEditor;
import org.eclipse.osgi.util.NLS;
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.TableColumn;
import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;
/**
* Renderer for MultiReferenceControl.
*
* @author Eugen Neufeld
*
*/
@SuppressWarnings("restriction")
public class MultiReferenceSWTRenderer extends AbstractControlSWTRenderer<VControl> {
private static final String ICON_ADD_EXISTING = "icons/link.png"; //$NON-NLS-1$
private static final String ICON_ADD_NEW = "icons/link_add.png"; //$NON-NLS-1$
private static final String ICON_DELETE = "icons/unset_reference.png"; //$NON-NLS-1$
private static final String ICON_MOVE_DOWN = "icons/move_down.png"; //$NON-NLS-1$
private static final String ICON_MOVE_UP = "icons/move_up.png"; //$NON-NLS-1$
private final BundleResolver bundleResolver = BundleResolverFactory.createBundleResolver();
private final ImageRegistryService imageRegistryService;
private EMFFormsLocalizationService l10n;
/**
* The {@link EObject} that contains the elements rendered in this multi reference.
*/
private Optional<EObject> cachedContainer;
/**
* A user-presentable display name for the reference, used in tool-tips.
*/
private String referenceDisplayName;
/**
* Legacy constructor, initializing me without a localization service. When needed,
* I will attempt to get it from the {@code viewContext}.
*
* @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}
*/
public MultiReferenceSWTRenderer(VControl vElement, ViewModelContext viewContext, ReportService reportService,
EMFFormsDatabinding emfFormsDatabinding, EMFFormsLabelProvider emfFormsLabelProvider,
VTViewTemplateProvider vtViewTemplateProvider, ImageRegistryService imageRegistryService) {
this(vElement, viewContext, reportService, emfFormsDatabinding, emfFormsLabelProvider, vtViewTemplateProvider,
imageRegistryService, null);
}
/**
* Complete constructor, supplying all dependencies.
*
* @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}
* @param localizationService the localization service
*
* @since 1.16
*/
// BEGIN COMPLEX CODE
@Inject
public MultiReferenceSWTRenderer(VControl vElement, ViewModelContext viewContext, ReportService reportService,
EMFFormsDatabinding emfFormsDatabinding, EMFFormsLabelProvider emfFormsLabelProvider,
VTViewTemplateProvider vtViewTemplateProvider, ImageRegistryService imageRegistryService,
EMFFormsLocalizationService localizationService) {
// END COMPLEX CODE
super(vElement, viewContext, reportService, emfFormsDatabinding, emfFormsLabelProvider, vtViewTemplateProvider);
this.imageRegistryService = imageRegistryService;
l10n = localizationService;
viewModelDBC = new EMFDataBindingContext();
}
private Label validationIcon;
private ILabelProvider labelProvider;
private AdapterFactory adapterFactory;
private TableViewer tableViewer;
private final EMFDataBindingContext viewModelDBC;
private IObservableList<?> tableViewerInputList;
private Button btnAddExisting;
private Button btnAddNew;
private Button btnDelete;
private Button btnMoveUp;
private Button btnMoveDown;
private SWTGridDescription rendererGridDescription;
@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;
}
/**
* Updates the table viewer's input observable list. Call this method in sub classes if you detect a change to the
* domain model which requires the re-resolvement of the input list.
* <p>
* <strong>Note:</strong> This method only updates the input list but does not trigger button enablement etc.
*
* @return returns the new input list; might be empty but never <code>null</code>
* @throws DatabindingFailedException if the new list cannot be resolved
*/
protected final IObservableList<?> updateTableViewerInputList() throws DatabindingFailedException {
// Rebind table content
if (tableViewerInputList != null) {
tableViewerInputList.dispose();
}
tableViewerInputList = getReferencedElementsList();
tableViewer.setInput(tableViewerInputList);
return tableViewerInputList;
}
/**
* Returns true if the 'AddExisting' button is shown, false otherwise.
*
* @return true if the 'AddExisting' button is shown, false otherwise
*/
protected boolean showAddExistingButton() {
EReference eReference = null;
try {
eReference = (EReference) getEStructuralFeature();
} catch (final DatabindingFailedException ex) {
getReportService().report(new AbstractReport(ex));
}
if (eReference != null) {
// Always show the add existing button for cross references
if (!eReference.isContainment()) {
return true;
}
VTReferenceStyleProperty referenceStyle = RendererUtil.getStyleProperty(
getVTViewTemplateProvider(), getVElement(), getViewModelContext(), VTReferenceStyleProperty.class);
if (referenceStyle == null) {
referenceStyle = getDefaultReferenceStyle();
}
return referenceStyle.isShowLinkButtonForContainmentReferences();
}
return false;
}
/**
* Returns true if the 'AddNew' button is shown, false otherwise.
*
* @return true if the 'AddNew' button is shown, false otherwise
*/
protected boolean showAddNewButton() {
EReference eReference = null;
try {
eReference = (EReference) getModelValue().getValueType();
} catch (final DatabindingFailedException ex) {
getReportService().report(new AbstractReport(ex));
}
if (eReference != null) {
if (eReference.isContainment()) {
return true;
}
VTReferenceStyleProperty referenceStyle = RendererUtil.getStyleProperty(
getVTViewTemplateProvider(), getVElement(), getViewModelContext(), VTReferenceStyleProperty.class);
if (referenceStyle == null) {
referenceStyle = getDefaultReferenceStyle();
}
return referenceStyle.isShowCreateAndLinkButtonForCrossReferences();
}
return false;
}
/**
* Creates and returns a default version of a {@link VTReferenceStyleProperty}.
*
* @return The default {@link VTReferenceStyleProperty}
*/
protected VTReferenceStyleProperty getDefaultReferenceStyle() {
return VTReferenceFactory.eINSTANCE.createReferenceStyleProperty();
}
/**
* Returns true if the 'Delete' button is shown, false otherwise.
*
* @return true if the 'Delete' button is shown, false otherwise
*/
protected boolean showDeleteButton() {
return true;
}
/**
* Returns true if the 'MoveUp' button is shown, false otherwise.
* Returning true will disable any sorting behavior.
*
* @return true if the 'MoveUp' button is shown, false otherwise
*/
protected boolean showMoveUpButton() {
return false;
}
/**
* Returns true if the 'MoveDown' button is shown, false otherwise.
* Returning true will disable any sorting behavior.
*
* @return true if the 'MoveDown' button is shown, false otherwise
*/
protected boolean showMoveDownButton() {
return false;
}
/**
* Returns the observed {@link EStructuralFeature}.
*
* @return the observed {@link EStructuralFeature}.
* @throws DatabindingFailedException when databinding fails.
*/
protected EStructuralFeature getEStructuralFeature() throws DatabindingFailedException {
return (EStructuralFeature) getModelValue().getValueType();
}
/**
* Returns the {@link EObject} that contains the elements rendered in this multi reference.
*
* @return The {@link EObject} containing the elements rendered in this multi reference or nothing if the container
* couldn't be computed.
*/
protected Optional<EObject> getContainer() {
if (cachedContainer == null || !cachedContainer.isPresent()) {
EObject eObject = null;
try {
eObject = (EObject) IObserving.class.cast(getModelValue()).getObserved();
} catch (final DatabindingFailedException ex) {
getReportService().report(new DatabindingFailedReport(ex));
}
cachedContainer = Optional.ofNullable(eObject);
}
return cachedContainer;
}
/**
* 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
protected Control renderControl(SWTGridCell cell, Composite parent) throws NoRendererFoundException,
NoPropertyDescriptorFoundExeption {
if (rendererGridDescription.getColumns() == 1) {
// Default
return renderMultiReferenceControl(cell, parent);
}
// Compact: render icon
if (cell.getColumn() == 0 && rendererGridDescription.getColumns() > 1) {
validationIcon = createValidationIcon(parent);
return validationIcon;
}
// Compact: render table and buttons next to each other
final Composite composite = new Composite(parent, SWT.NONE);
composite.setBackgroundMode(SWT.INHERIT_FORCE);
GridLayoutFactory.fillDefaults().numColumns(2).applyTo(composite);
final Control multiRefComposite = renderMultiReferenceControl(cell, composite);
GridDataFactory.fillDefaults().grab(true, true).applyTo(multiRefComposite);
try {
final Composite buttonComposite = createButtonComposite(composite);
GridDataFactory.fillDefaults().align(SWT.END, SWT.BEGINNING).applyTo(buttonComposite);
} catch (final DatabindingFailedException ex) {
getReportService().report(new RenderingFailedReport(ex));
return createErrorLabel(composite, ex);
}
updateButtons();
return composite;
}
/**
* Renders the MultiReference Control.
*
* Renders the MultiReference control including validation and buttons when {@link RenderMode} is set to
* {@link RenderMode#DEFAULT}. Only renders the
* MultiReference 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 renderMultiReferenceControl(SWTGridCell cell, Composite parent) throws NoRendererFoundException,
NoPropertyDescriptorFoundExeption {
if (cell.getRow() != 0 || cell.getRenderer() != this) {
throw new IllegalArgumentException("Wrong parameter passed!"); //$NON-NLS-1$
}
final Composite composite = new Composite(parent, SWT.NONE);
composite.setBackgroundMode(SWT.INHERIT_FORCE);
if (getTableStyleProperty().getRenderMode() == RenderMode.COMPACT_VERTICALLY) {
// avoid the default margins set by new GridLayout()
GridLayoutFactory.fillDefaults().applyTo(composite);
} else {
composite.setLayout(new GridLayout(1, false));
try {
createTitleComposite(composite);
} catch (final DatabindingFailedException ex) {
getReportService().report(new RenderingFailedReport(ex));
return createErrorLabel(parent, ex);
}
}
adapterFactory = createAdapterFactory();
labelProvider = createLabelProvider();
final Composite controlComposite = createControlComposite(composite);
try {
createContent(controlComposite);
} catch (final DatabindingFailedException ex) {
getReportService().report(new RenderingFailedReport(ex));
return createErrorLabel(parent, ex);
}
if (getTableStyleProperty().getRenderMode() == RenderMode.DEFAULT) {
initButtons();
updateButtons();
}
SWTDataElementIdHelper.setElementIdDataForVControl(composite, getVElement(), getViewModelContext());
return composite;
}
private void initButtons() {
getTableViewer().addSelectionChangedListener(new ISelectionChangedListener() {
@Override
public void selectionChanged(SelectionChangedEvent event) {
updateButtonEnabling();
}
});
}
/**
* Creates the composite which will be the parent for the table.
*
* @param composite
* the parent composite
* @return the table composite
*/
protected Composite createControlComposite(final 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().applyTo(controlComposite);
return controlComposite;
}
/**
* Returns the height for the table that will be created.
*
* @return the height hint
*/
protected int getTableHeightHint() {
return 300;
}
/**
* Gives access to the tableViewer used to display the attributes.
*
* @return the viewer
*/
protected TableViewer getTableViewer() {
return tableViewer;
}
/**
* 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}
*/
protected Control createErrorLabel(Composite parent, final Exception ex) {
final Label errorLabel = new Label(parent, SWT.NONE);
errorLabel.setText(ex.getMessage());
return errorLabel;
}
/**
* Creates a new {@link AdapterFactory}.
*
* @return the newly created {@link AdapterFactory}.
*/
protected AdapterFactory createAdapterFactory() {
return new ComposedAdapterFactory(new AdapterFactory[] {
new CustomReflectiveItemProviderAdapterFactory(),
new ComposedAdapterFactory(ComposedAdapterFactory.Descriptor.Registry.INSTANCE) });
}
/**
* Returns the {@link AdapterFactory} used by this renderer. To customize the used {@link AdapterFactory} override
* {@link #createAdapterFactory()}.
*
* @return The {@link AdapterFactory} used by this renderer
* @see #createAdapterFactory()
*/
protected final AdapterFactory getAdapterFactory() {
return adapterFactory;
}
/**
* Creates a new {@link ILabelProvider} for the table viewer.
*
* @return the newly created {@link ILabelProvider}.
*/
protected ILabelProvider createLabelProvider() {
final AdapterFactoryLabelProvider labelProvider = new AdapterFactoryLabelProvider(adapterFactory);
labelProvider.setFireLabelUpdateNotifications(true);
return labelProvider;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.ecp.view.spi.core.swt.AbstractControlSWTRenderer#dispose()
*/
@Override
protected void dispose() {
if (IDisposable.class.isInstance(adapterFactory)) {
IDisposable.class.cast(adapterFactory).dispose();
}
labelProvider.dispose();
viewModelDBC.dispose();
super.dispose();
}
/**
* Creates a button that enables reordering the references by the given {@link EStructuralFeature}.
*
* @param parent The parent of the created {@link Button}
* @param structuralFeature The {@link EStructuralFeature} which's references are moved up.
* @return The newly created {@link Button}
*/
protected Button createMoveUpButton(Composite parent, final EStructuralFeature structuralFeature) {
final Button btnMoveUp = new Button(parent, SWT.PUSH);
SWTDataElementIdHelper.setElementIdDataWithSubId(btnMoveUp, getVElement(), "up", getViewModelContext()); //$NON-NLS-1$
GridDataFactory.fillDefaults().grab(true, true).align(SWT.FILL, SWT.FILL).applyTo(btnMoveUp);
btnMoveUp.setImage(getImage(ICON_MOVE_UP));
btnMoveUp.setToolTipText(LocalizationServiceHelper.getString(MultiReferenceSWTRenderer.class,
MessageKeys.MultiReferenceSWTRenderer_moveUpTooltip));
btnMoveUp.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
super.widgetSelected(e);
final Optional<? extends EObject> container = getContainer();
if (container.isPresent()) {
handleMoveUp(tableViewer, container.get(), structuralFeature);
updateButtons();
}
}
});
return btnMoveUp;
}
/**
* Creates a button that enables reordering the references by the given {@link EStructuralFeature}.
*
* @param parent The parent of the created {@link Button}
* @param structuralFeature The {@link EStructuralFeature} which's references are moved down.
* @return The newly created {@link Button}
*/
protected Button createMoveDownButton(Composite parent, final EStructuralFeature structuralFeature) {
final Button btnMoveDown = new Button(parent, SWT.PUSH);
SWTDataElementIdHelper.setElementIdDataWithSubId(btnMoveDown, getVElement(), "down", getViewModelContext()); //$NON-NLS-1$
GridDataFactory.fillDefaults().grab(true, true).align(SWT.FILL, SWT.FILL).applyTo(btnMoveDown);
btnMoveDown.setImage(getImage(ICON_MOVE_DOWN));
btnMoveDown.setToolTipText(LocalizationServiceHelper.getString(MultiReferenceSWTRenderer.class,
MessageKeys.MultiReferenceSWTRenderer_moveDownTooltip));
btnMoveDown.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
super.widgetSelected(e);
final Optional<EObject> container = getContainer();
if (container.isPresent()) {
handleMoveDown(tableViewer, container.get(), structuralFeature);
updateButtons();
}
}
});
return btnMoveDown;
}
/**
* Creates a button that enables the addition of existing references to the given {@link EStructuralFeature}.
*
* @param parent The parent of the created {@link Button}
* @param structuralFeature The {@link EStructuralFeature} to which references are added
* @return The newly created {@link Button}
*/
protected Button createAddExistingButton(Composite parent, final EStructuralFeature structuralFeature) {
final Button btnAddExisting = new Button(parent, SWT.PUSH);
SWTDataElementIdHelper.setElementIdDataWithSubId(btnAddExisting, getVElement(), "link", getViewModelContext()); //$NON-NLS-1$
GridDataFactory.fillDefaults().grab(true, true).align(SWT.FILL, SWT.FILL).applyTo(btnAddExisting);
btnAddExisting.setImage(getImage(ICON_ADD_EXISTING));
btnAddExisting.setToolTipText(NLS.bind(LocalizationServiceHelper.getString(MultiReferenceSWTRenderer.class,
MessageKeys.MultiReferenceSWTRenderer_addExistingTooltip), getReferenceDisplayName()));
btnAddExisting.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
super.widgetSelected(e);
final Optional<EObject> container = getContainer();
if (container.isPresent()) {
handleAddExisting(tableViewer, container.get(), structuralFeature);
updateButtons();
}
}
});
return btnAddExisting;
}
/**
* Creates a button that enables the addition of newly created references to the given {@link EStructuralFeature}.
*
* @param parent The parent of the created {@link Button}
* @param structuralFeature The {@link EStructuralFeature} to which references are added
* @return The newly created {@link Button}
*/
protected Button createAddNewButton(Composite parent, final EStructuralFeature structuralFeature) {
final Button btnAddNew = new Button(parent, SWT.PUSH);
SWTDataElementIdHelper.setElementIdDataWithSubId(btnAddNew, getVElement(), "add", getViewModelContext()); //$NON-NLS-1$
GridDataFactory.fillDefaults().grab(true, true).align(SWT.FILL, SWT.FILL).applyTo(btnAddNew);
btnAddNew.setImage(getImage(ICON_ADD_NEW));
btnAddNew.setToolTipText(NLS.bind(LocalizationServiceHelper.getString(MultiReferenceSWTRenderer.class,
MessageKeys.MultiReferenceSWTRenderer_addNewTooltip), getReferenceDisplayName()));
btnAddNew.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
super.widgetSelected(e);
final Optional<EObject> container = getContainer();
if (container.isPresent()) {
handleAddNew(tableViewer, container.get(), structuralFeature);
updateButtons();
}
}
});
return btnAddNew;
}
/**
* Creates a button that enables the removal of existing references from the given {@link EStructuralFeature}.
*
* @param parent The parent of the newly created {@link Button}
* @param structuralFeature The {@link EStructuralFeature} from which references are removed
* @return The newly created {@link Button}
*/
protected Button createDeleteButton(Composite parent, final EStructuralFeature structuralFeature) {
final Button btnDelete = new Button(parent, SWT.PUSH);
SWTDataElementIdHelper.setElementIdDataWithSubId(btnDelete, getVElement(), "delete", getViewModelContext()); //$NON-NLS-1$
GridDataFactory.fillDefaults().grab(true, true).align(SWT.FILL, SWT.FILL).applyTo(btnDelete);
btnDelete.setImage(getImage(ICON_DELETE));
btnDelete.setToolTipText(LocalizationServiceHelper.getString(MultiReferenceSWTRenderer.class,
MessageKeys.MultiReferenceSWTRenderer_deleteTooltip));
btnDelete.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
super.widgetSelected(e);
final Optional<EObject> container = getContainer();
if (container.isPresent()) {
handleDelete(tableViewer, container.get(), structuralFeature);
updateButtons();
}
}
});
return btnDelete;
}
/**
* Updates button visibility and enablement.
*/
protected void updateButtons() {
updateButtonVisibility();
updateButtonEnabling();
}
/**
* Updates the enablement of 'addExisting', 'addNew', 'delete', 'moveUp' and 'moveDown' buttons according to the
* bound input.
*/
protected void updateButtonEnabling() {
final boolean isEnable = getContainer().isPresent() && getVElement().isEffectivelyEnabled();
final int listSize = tableViewerInputList != null ? tableViewerInputList.size() : 0;
final int selectionIndex = tableViewer != null ? tableViewer.getTable().getSelectionIndex() : -1;
enableUpButton(isEnable, listSize, selectionIndex);
enableDownButton(isEnable, listSize, selectionIndex);
enableAddExistingButton(isEnable, listSize, selectionIndex);
enableAddNewButton(isEnable, listSize, selectionIndex);
enableDeleteButton(isEnable, listSize, selectionIndex);
}
private void enableUpButton(boolean baseEnable, int listSize, int selectionIndex) {
if (btnMoveUp != null && showMoveUpButton()) {
final boolean enabled = baseEnable && listSize > 1 && selectionIndex > 0;
btnMoveUp.setEnabled(enabled);
}
}
private void enableDownButton(boolean baseEnable, int listSize, int selectionIndex) {
if (btnMoveDown != null && showMoveDownButton()) {
final boolean enabled = baseEnable && listSize > 1 && selectionIndex != -1 && selectionIndex < listSize - 1;
btnMoveDown.setEnabled(enabled);
}
}
private void enableAddExistingButton(boolean baseEnable, int listSize, int selectionIndex) {
if (btnAddExisting != null && showAddExistingButton()) {
btnAddExisting.setEnabled(baseEnable);
}
}
private void enableAddNewButton(boolean baseEnable, int listSize, int selectionIndex) {
if (btnAddNew != null && showAddNewButton()) {
btnAddNew.setEnabled(baseEnable);
}
}
private void enableDeleteButton(boolean baseEnable, int listSize, int selectionIndex) {
if (btnDelete != null && showDeleteButton()) {
btnDelete.setEnabled(baseEnable && listSize > 0 && selectionIndex != -1
&& ConditionalDeleteService.getDeleteService(getViewModelContext())
.canDelete(tableViewer.getStructuredSelection().toList()));
}
}
/**
* Updates the visibility of 'addExisting', 'addNew', 'delete', 'moveUp' and 'moveDown' buttons according to the
* bound input.
*/
protected void updateButtonVisibility() {
final boolean isVisible = !getVElement().isEffectivelyReadonly();
if (btnMoveUp != null) {
btnMoveUp.setVisible(showMoveUpButton() && isVisible);
}
if (btnMoveDown != null) {
btnMoveDown.setVisible(showMoveDownButton() && isVisible);
}
if (btnAddExisting != null) {
btnAddExisting.setVisible(showAddExistingButton() && isVisible);
}
if (btnAddNew != null) {
btnAddNew.setVisible(showAddNewButton() && isVisible);
}
if (btnDelete != null) {
btnDelete.setVisible(showDeleteButton() && isVisible);
}
}
/***
* Adds a composite with the buttons 'AddExisting', 'AddNew' and 'Delete' to the given {@link Composite} if
* necessary.
*
* @param parent The parent of the created {@link Composite}
* @return the created Composite
* @throws DatabindingFailedException thrown if the databinding could not be executed successfully
*/
protected Composite createButtonComposite(Composite parent) throws DatabindingFailedException {
final Composite buttonComposite = new Composite(parent, SWT.NONE);
final EStructuralFeature structuralFeature = getEStructuralFeature();
int nrButtons = 0;
if (showMoveUpButton()) {
btnMoveUp = createMoveUpButton(buttonComposite, structuralFeature);
nrButtons++;
}
if (showMoveDownButton()) {
btnMoveDown = createMoveDownButton(buttonComposite, structuralFeature);
nrButtons++;
}
if (showAddExistingButton()) {
btnAddExisting = createAddExistingButton(buttonComposite, structuralFeature);
nrButtons++;
}
if (showAddNewButton()) {
btnAddNew = createAddNewButton(buttonComposite, structuralFeature);
nrButtons++;
}
if (showDeleteButton()) {
btnDelete = createDeleteButton(buttonComposite, structuralFeature);
nrButtons++;
}
GridLayoutFactory.fillDefaults().numColumns(nrButtons).equalWidth(true).applyTo(buttonComposite);
return buttonComposite;
}
/**
* Creates a composite with a label, a validation icon and a button composite.
*
* @param parent The parent of the created {@link Composite}
* @throws DatabindingFailedException thrown if the databinding could not be executed successfully
*/
protected void createTitleComposite(Composite parent)
throws DatabindingFailedException {
final Composite titleComposite = new Composite(parent, SWT.NONE);
titleComposite.setBackgroundMode(SWT.INHERIT_FORCE);
GridDataFactory.fillDefaults().grab(true, false).align(SWT.FILL, SWT.BEGINNING)
.applyTo(titleComposite);
GridLayoutFactory.fillDefaults().numColumns(3).equalWidth(false).applyTo(titleComposite);
final Label filler = new Label(titleComposite, SWT.NONE);
GridDataFactory.fillDefaults().grab(true, false).align(SWT.FILL, SWT.BEGINNING).applyTo(filler);
// VALIDATION
// // set the size of the label to the size of the image
validationIcon = createValidationIcon(titleComposite);
GridDataFactory.fillDefaults().hint(16, 17).grab(false, false).applyTo(validationIcon);
final Composite buttonComposite = createButtonComposite(titleComposite);
GridDataFactory.fillDefaults().grab(true, false).align(SWT.END, SWT.FILL)
.applyTo(buttonComposite);
}
/**
* 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(MultiReferenceSWTRenderer.class), path);
}
/**
* Obtains a user-presentable name for the reference that I edit, to be used for example
* in button tool-tips.
*
* @return the reference display name
* @since 1.16
*/
protected String getReferenceDisplayName() {
if (referenceDisplayName == null) {
try {
if (l10n == null) {
// Maybe the view-model context has one
l10n = getViewModelContext().getService(EMFFormsLocalizationService.class);
}
final EStructuralFeature feature = getEStructuralFeature();
if (feature != null && l10n != null) {
// Use type of the reference so that we don't get a plural (Ecore models typically
// have plural names for many-valued references)
final EClassifier type = feature.getEType();
try {
final Bundle editBundle = bundleResolver.getEditBundle(type);
referenceDisplayName = l10n.getString(editBundle, String.format("_UI_%s_type", type.getName())); //$NON-NLS-1$
} catch (final NoBundleFoundException ex) {
referenceDisplayName = type.getName();
}
}
} catch (final DatabindingFailedException e) {
// We'll default it, below
}
if (referenceDisplayName == null) {
referenceDisplayName = LocalizationServiceHelper.getString(MultiReferenceSWTRenderer.class,
MessageKeys.MultiReferenceSWTRenderer_defaultReferenceDisplayName);
}
}
return referenceDisplayName;
}
private void createContent(Composite composite) throws DatabindingFailedException {
tableViewer = new TableViewer(composite, SWT.MULTI | SWT.V_SCROLL | SWT.FULL_SELECTION
| SWT.BORDER);
tableViewer.getTable().setData(CUSTOM_VARIANT, "org_eclipse_emf_ecp_control_multireference"); //$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 ObjectViewerComparator comparator = new ObjectViewerComparator(this::compare);
final boolean isMoveDisabled = isMoveDisabled();
if (isMoveDisabled) {
tableViewer.setComparator(comparator);
}
final ObservableListContentProvider cp = new ObservableListContentProvider();
final EMFFormsLabelProvider labelService = getEMFFormsLabelProvider();
final TableViewerColumn column = TableViewerColumnBuilder
.create()
.setResizable(false)
.setMoveable(false)
.setStyle(SWT.NONE)
.build(tableViewer);
final IObservableValue textObservableValue = WidgetProperties.text().observe(column.getColumn());
final IObservableValue tooltipObservableValue = WidgetProperties.tooltipText().observe(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) {
// FIXME Expectations?
getReportService().report(new RenderingFailedReport(e));
}
// only enable column sorting if move is disabled
if (isMoveDisabled) {
column.getColumn().addSelectionListener(
getSelectionAdapter(tableViewer, isMoveDisabled ? comparator : null, column.getColumn()));
}
tableViewer.setLabelProvider(labelProvider);
tableViewer.setContentProvider(cp);
updateTableViewerInputList();
final TableColumnLayout layout = new TableColumnLayout();
composite.setLayout(layout);
layout.setColumnData(column.getColumn(), new ColumnWeightData(1, false));
tableViewer.addDoubleClickListener(new IDoubleClickListener() {
@Override
public void doubleClick(DoubleClickEvent event) {
if (!getVElement().isEffectivelyReadonly()) {
final EObject selectedObject = (EObject) IStructuredSelection.class.cast(event.getSelection())
.getFirstElement();
handleDoubleClick(selectedObject);
}
}
});
}
private boolean isMoveDisabled() {
return !showMoveUpButton() && !showMoveDownButton();
}
private SelectionAdapter getSelectionAdapter(final TableViewer tableViewer,
final ObjectViewerComparator comparator, final TableColumn column) {
final SelectionAdapter selectionAdapter = new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
comparator.toggleDirection();
final int dir = comparator.getDirection();
tableViewer.getTable().setSortDirection(dir);
tableViewer.getTable().setSortColumn(column);
tableViewer.refresh();
}
};
return selectionAdapter;
}
/**
* Method for handling a double click.
*
* @param selectedObject the selected {@link EObject}
*/
protected void handleDoubleClick(EObject selectedObject) {
final ReferenceService referenceService = getReferenceService();
referenceService.openInNewContext(selectedObject);
}
/**
* Method for adding an existing element.
*
* @param tableViewer the {@link TableViewer}
* @param eObject The {@link EObject} to add to
* @param structuralFeature The corresponding {@link EStructuralFeature}
*/
protected void handleAddExisting(TableViewer tableViewer, EObject eObject, EStructuralFeature structuralFeature) {
final ReferenceService referenceService = getReferenceService();
referenceService.addExistingModelElements(eObject, (EReference) structuralFeature);
}
/**
* Method for adding a new element.
*
* @param tableViewer the {@link TableViewer}
* @param eObject The {@link EObject} to add to
* @param structuralFeature The corresponding {@link EStructuralFeature}
*/
protected void handleAddNew(TableViewer tableViewer, EObject eObject, EStructuralFeature structuralFeature) {
final ReferenceService referenceService = getReferenceService();
referenceService.addNewModelElements(eObject, (EReference) structuralFeature, true);
}
/**
* Override to customize linking and creation of EObjects in this renderer's EReference.
*
* @return The {@link ReferenceService} used to link and create new EObjects in this renderer's reference.
*/
protected ReferenceService getReferenceService() {
return getViewModelContext().getService(ReferenceService.class);
}
/**
* Method for deleting elements.
*
* @param tableViewer the {@link TableViewer}
* @param eObject The {@link EObject} to delete from
* @param structuralFeature The corresponding {@link EStructuralFeature}
*/
protected void handleDelete(TableViewer tableViewer, EObject eObject, EStructuralFeature structuralFeature) {
@SuppressWarnings("unchecked")
final List<Object> deletionList = IStructuredSelection.class.cast(tableViewer.getSelection()).toList();
final EditingDomain editingDomain = getEditingDomain(eObject);
/* assured by #isApplicable */
final EReference reference = EReference.class.cast(structuralFeature);
if (reference.isContainment()) {
DeleteService deleteService = getViewModelContext().getService(DeleteService.class);
if (deleteService == null) {
/*
* #getService(Class<?>) will report to the reportservice if it could not be found
* Use Default
*/
deleteService = new EMFDeleteServiceImpl();
}
deleteService.deleteElements(deletionList);
} else {
removeElements(editingDomain, eObject, reference, deletionList);
}
}
private void removeElements(EditingDomain editingDomain, Object source, EStructuralFeature feature,
Collection<Object> toRemove) {
final Command removeCommand = RemoveCommand.create(editingDomain, source, feature, toRemove);
if (removeCommand.canExecute()) {
if (editingDomain.getCommandStack() == null) {
removeCommand.execute();
} else {
editingDomain.getCommandStack().execute(removeCommand);
}
}
}
/**
* Method for moving up elements.
*
* @param tableViewer the {@link TableViewer}
* @param eObject The {@link EObject} to delete from
* @param structuralFeature The corresponding {@link EStructuralFeature}
*/
protected void handleMoveUp(TableViewer tableViewer, EObject eObject, EStructuralFeature structuralFeature) {
final List<?> moveUpList = IStructuredSelection.class.cast(tableViewer.getSelection()).toList();
final EditingDomain editingDomain = getEditingDomain(eObject);
for (final Object moveUpObject : moveUpList) {
final int currentIndex = EList.class.cast(eObject.eGet(structuralFeature)).indexOf(moveUpObject);
if (currentIndex <= 0) {
return;
}
editingDomain.getCommandStack()
.execute(
new MoveCommand(editingDomain, eObject, structuralFeature, currentIndex, currentIndex - 1));
}
}
/**
* Method for moving down elements.
*
* @param tableViewer the {@link TableViewer}
* @param eObject The {@link EObject} to delete from
* @param structuralFeature The corresponding {@link EStructuralFeature}
*/
protected void handleMoveDown(TableViewer tableViewer, EObject eObject, EStructuralFeature structuralFeature) {
final List<?> moveDownList = IStructuredSelection.class.cast(tableViewer.getSelection()).toList();
final EditingDomain editingDomain = getEditingDomain(eObject);
// need to reverse to avoid the moves interfering each other
Collections.reverse(moveDownList);
for (final Object moveDownObject : moveDownList) {
final int maxIndex = EList.class.cast(eObject.eGet(structuralFeature)).size() - 1;
final int currentIndex = EList.class.cast(eObject.eGet(structuralFeature)).indexOf(moveDownObject);
if (currentIndex < 0 || currentIndex == maxIndex) {
return;
}
editingDomain.getCommandStack()
.execute(
new MoveCommand(editingDomain, eObject, structuralFeature, currentIndex, currentIndex + 1));
}
}
@Override
protected void rootDomainModelChanged() throws DatabindingFailedException {
// TODO rebinding of text and tooltip needed? If yes, complete!
// if (textBinding != null) {
// textBinding.dispose();
// }
// if (tooltipBinding != null) {
// tooltipBinding.dispose();
// }
// Rebind table content to the new domain model
updateTableViewerInputList();
// Update cachedContainer to allow addition and removal of elements to/from the multi reference.
cachedContainer = Optional.ofNullable((EObject) IObserving.class.cast(getModelValue()).getObserved());
applyEnable();
applyReadOnly();
}
/**
* Computes and returns the observable list of the referenced elements shown by this renderer.
*
* @return The {@link IObservableList} of the referenced elements
* @throws DatabindingFailedException If computing the list failed due to failed databinding
*/
protected IObservableList<?> getReferencedElementsList() throws DatabindingFailedException {
return getEMFFormsDatabinding().getObservableList(getVElement().getDomainModelReference(),
getViewModelContext().getDomainModel());
}
@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;
}
@Override
protected void applyEnable() {
super.applyEnable();
// specific handling for buttons
updateButtonEnabling();
}
@Override
protected void applyReadOnly() {
// specific handling for buttons
// do not let the super method disable the control, so the table is still enabled for sorting for example
updateButtonVisibility();
}
@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));
}
});
}
/**
* <p>
* Sorts two objects based on their labels and the given sorting direction.
* </p>
* <p>
* Override this method to provide a custom sorting algorithm for the objects shown in this control's table.
* </p>
*
* @param direction The sorting direction: 0 == NONE, 1 == UP, 2 == DOWN
* @param object1 The first object of the comparison
* @param object2 The second object of the comparison
* @return 0 if both objects are equal, -1 if object1 will appear higher in the table, 1 if object2 will appear
* higher in the table
*/
protected int compare(int direction, Object object1, Object object2) {
if (direction == 0) {
return 0;
}
int rc = 0;
final String label1 = labelProvider.getText(object1);
final String label2 = labelProvider.getText(object2);
if (label1 == null) {
if (label2 == null) {
rc = 0;
} else {
rc = 1;
}
} else if (label2 == null) {
rc = -1;
} else {
rc = NumberAwareStringComparator.getInstance().compare(label1, label2);
}
// If descending order, flip the direction
if (direction == 2) {
rc = -rc;
}
return rc;
}
/**
* Returns the {@link ILabelProvider} used by this renderer.
*
* @return the {@link ILabelProvider}
*/
protected ILabelProvider getLabelProvider() {
return labelProvider;
}
/**
* Select and reveal an {@code object} in my table.
*
* @param object an object to reveal
*
* @since 1.22
*/
void reveal(Object object) {
checkRenderer();
if (tableViewer != null) {
final ISelection newSelection = new StructuredSelection(object);
if (!newSelection.equals(tableViewer.getSelection())) {
tableViewer.setSelection(newSelection, true);
} else {
tableViewer.reveal(object);
}
}
}
}