Skip to content

Content of file MultiAttributeSWTRenderer.java

/*******************************************************************************
 * 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; } }