Skip to content

Content of file TableControlSWTRenderer.java

/*******************************************************************************
 * Copyright (c) 2011-2020 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
 * Johannes Faltermeier - refactorings
 * Christian W. Damus - bugs 544116, 544537, 545686, 530314, 547271, 547787, 548592, 552385, 559267
 ******************************************************************************/
package org.eclipse.emf.ecp.view.spi.table.swt;

import static org.eclipse.emfforms.spi.swt.table.ViewerRefreshManager.getRefreshRunnable;

import java.net.MalformedURLException;
import java.net.URL;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.stream.Collectors;

import javax.inject.Inject;

import org.eclipse.core.databinding.Binding;
import org.eclipse.core.databinding.observable.IObserving;
import org.eclipse.core.databinding.observable.Observables;
import org.eclipse.core.databinding.observable.list.IListChangeListener;
import org.eclipse.core.databinding.observable.list.IObservableList;
import org.eclipse.core.databinding.observable.list.ListChangeEvent;
import org.eclipse.core.databinding.observable.map.IObservableMap;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.property.value.IValueProperty;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.common.command.CompoundCommand;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.Enumerator;
import org.eclipse.emf.databinding.EMFDataBindingContext;
import org.eclipse.emf.databinding.EMFProperties;
import org.eclipse.emf.ecore.EClass;
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.ecore.EStructuralFeature.Setting;
import org.eclipse.emf.ecore.InternalEObject;
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.edit.spi.swt.table.ECPCellEditor;
import org.eclipse.emf.ecp.edit.spi.swt.table.ECPCellEditorComparator;
import org.eclipse.emf.ecp.edit.spi.swt.table.ECPCustomUpdateCellEditor;
import org.eclipse.emf.ecp.edit.spi.swt.table.ECPElementAwareCellEditor;
import org.eclipse.emf.ecp.edit.spi.swt.table.ECPFilterableCell;
import org.eclipse.emf.ecp.edit.spi.swt.table.ECPViewerAwareCellEditor;
import org.eclipse.emf.ecp.edit.spi.swt.util.ECPDialogExecutor;
import org.eclipse.emf.ecp.view.internal.table.swt.Activator;
import org.eclipse.emf.ecp.view.internal.table.swt.CellReadOnlyTesterHelper;
import org.eclipse.emf.ecp.view.internal.table.swt.MessageKeys;
import org.eclipse.emf.ecp.view.internal.table.swt.RunnableManager;
import org.eclipse.emf.ecp.view.internal.table.swt.TableConfigurationHelper;
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.core.swt.AbstractControlSWTRendererUtil;
import org.eclipse.emf.ecp.view.spi.model.DiagnosticMessageExtractor;
import org.eclipse.emf.ecp.view.spi.model.LabelAlignment;
import org.eclipse.emf.ecp.view.spi.model.ModelChangeListener;
import org.eclipse.emf.ecp.view.spi.model.ModelChangeNotification;
import org.eclipse.emf.ecp.view.spi.model.VDiagnostic;
import org.eclipse.emf.ecp.view.spi.model.VDomainModelReference;
import org.eclipse.emf.ecp.view.spi.model.VElement;
import org.eclipse.emf.ecp.view.spi.model.VViewPackage;
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.table.model.VEnablementConfiguration;
import org.eclipse.emf.ecp.view.spi.table.model.VTableControl;
import org.eclipse.emf.ecp.view.spi.table.model.VTableDomainModelReference;
import org.eclipse.emf.ecp.view.spi.table.swt.action.AddRowAction;
import org.eclipse.emf.ecp.view.spi.table.swt.action.DuplicateRowAction;
import org.eclipse.emf.ecp.view.spi.table.swt.action.MoveRowDownAction;
import org.eclipse.emf.ecp.view.spi.table.swt.action.MoveRowUpAction;
import org.eclipse.emf.ecp.view.spi.table.swt.action.RemoveRowAction;
import org.eclipse.emf.ecp.view.spi.table.swt.action.TableActionIconButton;
import org.eclipse.emf.ecp.view.spi.table.swt.action.TableRendererActionBar;
import org.eclipse.emf.ecp.view.spi.table.swt.action.TableRendererViewerActionContext;
import org.eclipse.emf.ecp.view.spi.util.swt.ImageRegistryService;
import org.eclipse.emf.ecp.view.template.model.VTStyleProperty;
import org.eclipse.emf.ecp.view.template.model.VTViewTemplateProvider;
import org.eclipse.emf.ecp.view.template.style.background.model.VTBackgroundFactory;
import org.eclipse.emf.ecp.view.template.style.background.model.VTBackgroundStyleProperty;
import org.eclipse.emf.ecp.view.template.style.fontProperties.model.VTFontPropertiesFactory;
import org.eclipse.emf.ecp.view.template.style.fontProperties.model.VTFontPropertiesStyleProperty;
import org.eclipse.emf.ecp.view.template.style.keybinding.model.VTKeyBinding;
import org.eclipse.emf.ecp.view.template.style.keybinding.model.VTKeyBindings;
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.ecp.view.template.style.tableValidation.model.VTTableValidationFactory;
import org.eclipse.emf.ecp.view.template.style.tableValidation.model.VTTableValidationStyleProperty;
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.ui.dnd.EditingDomainViewerDropAdapter;
import org.eclipse.emf.edit.ui.dnd.LocalTransfer;
import org.eclipse.emf.edit.ui.dnd.ViewerDragAdapter;
import org.eclipse.emfforms.common.Optional;
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.databinding.emf.EMFFormsDatabindingEMF;
import org.eclipse.emfforms.spi.core.services.editsupport.EMFFormsEditSupport;
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.EMFFormsSWTLayoutUtil;
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.SWTValidationUiService;
import org.eclipse.emfforms.spi.swt.table.AbstractTableViewerComposite;
import org.eclipse.emfforms.spi.swt.table.CellLabelProviderFactory;
import org.eclipse.emfforms.spi.swt.table.ColumnConfiguration;
import org.eclipse.emfforms.spi.swt.table.ColumnConfigurationBuilder;
import org.eclipse.emfforms.spi.swt.table.DNDProvider;
import org.eclipse.emfforms.spi.swt.table.EditingSupportCreator;
import org.eclipse.emfforms.spi.swt.table.TableConfiguration;
import org.eclipse.emfforms.spi.swt.table.TableConfigurationBuilder;
import org.eclipse.emfforms.spi.swt.table.TableControl;
import org.eclipse.emfforms.spi.swt.table.TableViewerComparator;
import org.eclipse.emfforms.spi.swt.table.TableViewerCompositeBuilder;
import org.eclipse.emfforms.spi.swt.table.TableViewerCreator;
import org.eclipse.emfforms.spi.swt.table.TableViewerFactory;
import org.eclipse.emfforms.spi.swt.table.TableViewerSWTBuilder;
import org.eclipse.emfforms.spi.swt.table.TableViewerSWTCustomization;
import org.eclipse.emfforms.spi.swt.table.ViewerRefreshManager;
import org.eclipse.emfforms.spi.swt.table.action.ActionBar;
import org.eclipse.emfforms.spi.swt.table.action.ActionConfiguration;
import org.eclipse.emfforms.spi.swt.table.action.ActionConfigurationBuilder;
import org.eclipse.emfforms.spi.swt.table.action.TableActionBar;
import org.eclipse.emfforms.spi.swt.table.action.ViewerActionContext;
import org.eclipse.emfforms.view.spi.multisegment.model.MultiSegmentUtil;
import org.eclipse.emfforms.view.spi.multisegment.model.VMultiDomainModelReferenceSegment;
import org.eclipse.jface.databinding.swt.WidgetProperties;
import org.eclipse.jface.databinding.viewers.ObservableListContentProvider;
import org.eclipse.jface.databinding.viewers.ObservableMapCellLabelProvider;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IDialogLabelKeys;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.viewers.AbstractTableViewer;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.CellLabelProvider;
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.DoubleClickEvent;
import org.eclipse.jface.viewers.EditingSupport;
import org.eclipse.jface.viewers.IColorProvider;
import org.eclipse.jface.viewers.IDoubleClickListener;
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.TableViewerEditor;
import org.eclipse.jface.viewers.TableViewerFocusCellManager;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DragSourceListener;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.dnd.DropTargetListener;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
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.Item;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.Widget;
import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;

/**
 * SWT Renderer for Table Control.
 *
 * @author Eugen Neufeld
 * @author Johannes Faltermeier
 *
 */
public class TableControlSWTRenderer extends AbstractControlSWTRenderer<VTableControl> {

	/**
	 * @since 1.10
	 */
	protected static final String FIXED_COLUMNS = "org.eclipse.rap.rwt.fixedColumns"; //$NON-NLS-1$
	/**
	 * @since 1.10
	 */
	protected static final String TABLE_CUSTOM_VARIANT = "org_eclipse_emf_ecp_control_table"; //$NON-NLS-1$
	/**
	 * @since 1.17
	 */
	protected static final Point VALIDATION_PREFERRED_SIZE = new Point(16, 17);

	private final Map<Integer, ECPCellEditorComparator> columnIndexToComparatorMap = new LinkedHashMap<Integer, ECPCellEditorComparator>();

	private final ImageRegistryService imageRegistryService;
	private final EMFDataBindingContext viewModelDBC;
	private final EMFFormsEditSupport emfFormsEditSupport;

	private SWTGridDescription rendererGridDescription;

	private AbstractTableViewer tableViewer;

	private Label validationIcon;
	private boolean showValidationSummaryTooltip;

	private Optional<Integer> minimumHeight;
	private Optional<Integer> maximumHeight;
	private Optional<Integer> visibleLines;
	private AbstractTableViewerComposite<? extends AbstractTableViewer> tableViewerComposite;
	private int regularColumnsStartIndex;
	private boolean isDisposing;
	private IObservableList<?> list;
	private final RunnableManager runnableManager = new RunnableManager(Display.getDefault());

	private TableViewerSWTCustomization<?> customization;

	/** The EReference describing the list shown by the table. */
	private EReference tableEReference;
	private EStructuralFeature[] columnFeatures;
	/** The feature of the column which is currently used for sorting. */
	private java.util.Optional<EStructuralFeature> sortColumnFeature = java.util.Optional.empty();
	private ModelChangeListener autoSortModelChangeListener;
	private final EMFFormsLocalizationService localizationService;
	private final BundleResolver bundleResolver = BundleResolverFactory.createBundleResolver();
	/** DO NOT USE DIRECTLY! Use {@link #getEnumeratorComparator()} instead. */
	private LocalizedEnumeratorComparator enumeratorComparator;

	private EMFFormsLocalizationService l10n;
	private String referenceDisplayName;

	/**
	 * Legacy constructor for backwards compatibility.
	 *
	 * @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 emfFormsEditSupport The {@link EMFFormsEditSupport}
	 * @since 1.8
	 */
	@Deprecated
	// BEGIN COMPLEX CODE
	public TableControlSWTRenderer(
		VTableControl vElement,
		ViewModelContext viewContext,
		ReportService reportService,
		EMFFormsDatabindingEMF emfFormsDatabinding,
		EMFFormsLabelProvider emfFormsLabelProvider,
		VTViewTemplateProvider vtViewTemplateProvider,
		ImageRegistryService imageRegistryService,
		EMFFormsEditSupport emfFormsEditSupport) {
		// END COMPLEX CODE

		this(vElement, viewContext, reportService, emfFormsDatabinding, emfFormsLabelProvider, vtViewTemplateProvider,
			imageRegistryService, emfFormsEditSupport, viewContext.getService(EMFFormsLocalizationService.class));
	}

	/**
	 * 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}
	 * @param emfFormsEditSupport The {@link EMFFormsEditSupport}
	 * @param localizationService The {@link EMFFormsLocalizationService}
	 * @since 1.22
	 */
	@Inject
	// BEGIN COMPLEX CODE
	public TableControlSWTRenderer(
		VTableControl vElement,
		ViewModelContext viewContext,
		ReportService reportService,
		EMFFormsDatabindingEMF emfFormsDatabinding,
		EMFFormsLabelProvider emfFormsLabelProvider,
		VTViewTemplateProvider vtViewTemplateProvider,
		ImageRegistryService imageRegistryService,
		EMFFormsEditSupport emfFormsEditSupport,
		EMFFormsLocalizationService localizationService) {
		// END COMPLEX CODE

		super(vElement, viewContext, reportService, emfFormsDatabinding, emfFormsLabelProvider, vtViewTemplateProvider);
		this.imageRegistryService = imageRegistryService;
		this.emfFormsEditSupport = emfFormsEditSupport;
		this.localizationService = localizationService;
		viewModelDBC = new EMFDataBindingContext();
	}

	@Override
	public SWTGridDescription getGridDescription(SWTGridDescription gridDescription) {
		if (rendererGridDescription == null) {
			if (getTableStyleProperty().getRenderMode() == RenderMode.COMPACT_VERTICALLY) {
				final boolean includeLabel = getVElement().getLabelAlignment() != LabelAlignment.NONE;
				if (getVElement().getLabelAlignment() == LabelAlignment.TOP) {
					Activator.getInstance().log(IStatus.WARNING, MessageFormat.format(
						Messages.TableControlSWTRenderer_LabelAlignmentTopNotSupportForRenderModeCompactVertically,
						getVElement().getName()));
				}
				rendererGridDescription = GridDescriptionFactory.INSTANCE.createCompactGrid(includeLabel, true, this);
			} else {
				rendererGridDescription = GridDescriptionFactory.INSTANCE.createSimpleGrid(1, 1, this);
			}

		}
		return rendererGridDescription;
	}

	@Override
	protected EMFFormsDatabindingEMF getEMFFormsDatabinding() {
		return (EMFFormsDatabindingEMF) super.getEMFFormsDatabinding();
	}

	@Override
	protected Control renderControl(SWTGridCell gridCell, final Composite parent) throws NoRendererFoundException,
		NoPropertyDescriptorFoundExeption {
		// compact label
		if (gridCell.getColumn() == 0 && rendererGridDescription.getColumns() == 3) {
			return createLabel(parent);
		}
		// compact validation
		if (gridCell.getColumn() == 0 && rendererGridDescription.getColumns() == 2
			|| gridCell.getColumn() == 1 && rendererGridDescription.getColumns() == 3) {
			validationIcon = createValidationIcon(parent);
			return validationIcon;
		}
		// Default
		return renderTableControl(gridCell, parent);
	}

	@SuppressWarnings("unchecked")
	@Override
	protected Control createLabel(final Composite parent) {
		final VDomainModelReference dmrToCheck = getDMRToMultiReference();
		final IObservableValue<?> labelText = getLabelText(dmrToCheck);
		final IObservableValue<?> labelTooltipText = getLabelTooltipText(dmrToCheck);

		final Label titleLabel = new Label(parent, AbstractControlSWTRendererUtil
			.getLabelStyleBits(getVTViewTemplateProvider(), getVElement(), getViewModelContext()));
		titleLabel.setData(CUSTOM_VARIANT, "org_eclipse_emf_ecp_control_label"); //$NON-NLS-1$
		titleLabel.setBackground(parent.getBackground());

		viewModelDBC.bindValue(
			WidgetProperties.text().observe(titleLabel),
			labelText);
		viewModelDBC.bindValue(
			WidgetProperties.tooltipText().observe(titleLabel),
			labelTooltipText);

		return titleLabel;
	}

	/**
	 * Renders the Table Control.
	 *
	 * Renders the Table Control including title and validation when {@link RenderMode} is set to
	 * {@link RenderMode#DEFAULT}. Only renders the
	 * Table Control without title and validation when renderMode is set to {@link RenderMode#COMPACT_VERTICALLY}.
	 *
	 * @param gridCell 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 renderTableControl(SWTGridCell gridCell, final Composite parent)
		throws NoRendererFoundException,
		NoPropertyDescriptorFoundExeption {
		try {
			/* get the list-setting which is displayed */
			final VDomainModelReference dmrToCheck = getDMRToMultiReference();

			/* get the observable list */
			list = getEMFFormsDatabinding().getObservableList(dmrToCheck,
				getViewModelContext().getDomainModel());

			/* get the EReference describing the list shown by the table */
			tableEReference = (EReference) list.getElementType();

			final TableRendererViewerActionContext actionContext = createViewerActionContext();
			final ActionConfiguration actionConfiguration = configureActions(actionContext);
			final TableActionBar<? extends AbstractTableViewer> actionBar = createActionBar(actionContext,
				actionConfiguration);

			/* get the label text/tooltip */
			final IObservableValue<?> labelText = getLabelText(dmrToCheck);
			final IObservableValue<?> labelTooltipText = getLabelTooltipText(dmrToCheck);

			/* content provider */
			final ObservableListContentProvider cp = new ObservableListContentProvider();

			final TableControlComparator comparator = getVElement().isMoveUpDownDisabled()
				? createTableViewerComparator()
				: null;

			/* render */
			final TableViewerCompositeBuilder compositeBuilder = createTableViewerCompositeBuilder();

			final TableViewerSWTBuilder tableViewerSWTBuilder = createTableViewerSWTBuilder(parent, list, labelText,
				labelTooltipText, compositeBuilder, cp, comparator, actionBar);
			tableViewerSWTBuilder.customizeActionConfiguration(actionConfiguration);

			tableViewerSWTBuilder
				.configureTable(TableConfigurationBuilder.from(tableViewerSWTBuilder)
					.dataMapEntry(TableConfiguration.DMR, dmrToCheck)
					.dataMapEntry(ViewerRefreshManager.REFRESH_MANAGER,
						(ViewerRefreshManager) this::postRefresh)
					.build());

			regularColumnsStartIndex = 0;

			/* validation column */
			if (!getVElement().isEffectivelyReadonly()) {
				regularColumnsStartIndex++;
				createFixedValidationStatusColumn(tableViewerSWTBuilder);
			}
			regularColumnsStartIndex += addAdditionalColumns(tableViewerSWTBuilder);
			addColumns(tableViewerSWTBuilder, EReference.class.cast(list.getElementType()).getEReferenceType(), cp);

			initCompositeHeight();

			tableViewerComposite = tableViewerSWTBuilder.build();

			/* setup selection changes listener */
			tableViewerComposite.getTableViewer().addSelectionChangedListener(new ViewerSelectionChangedListener());
			tableViewerComposite.getTableViewer().addDoubleClickListener(new DoubleClickListener());

			/* setup sorting via column selection */
			if (getVElement().isMoveUpDownDisabled()) {
				setupSorting(comparator, regularColumnsStartIndex, tableViewerComposite);
			}

			/* get validation icon */
			setupValidation(tableViewerComposite);

			/* create the table viewer editor */
			setTableViewer(tableViewerComposite.getTableViewer());

			SWTDataElementIdHelper.setElementIdDataForVControl(tableViewerComposite, getVElement(),
				getViewModelContext());

			// FIXME doesn't work with table with panel
			// setLayoutData(compositeBuilder.getViewerComposite());
			GridData.class
				.cast(compositeBuilder.getViewerComposite().getLayoutData()).heightHint = getTableHeightHint();

			addRelayoutListenerIfNeeded(list, compositeBuilder.getViewerComposite());

			addResizeListener(tableViewerComposite.getTableViewer().getControl(), regularColumnsStartIndex);

			customization = tableViewerSWTBuilder.getCustomization();

			autoSortModelChangeListener = new AutoSortModelChangeListener();
			getViewModelContext().registerDomainChangeListener(autoSortModelChangeListener);

			return tableViewerComposite;

		} catch (final DatabindingFailedException ex) {
			getReportService().report(new RenderingFailedReport(ex));
			final Label errorLabel = new Label(parent, SWT.NONE);
			errorLabel.setText(ex.getMessage());
			return errorLabel;
		}
	}

	/**
	 * Creates a TableControlComparator.
	 *
	 * @return TableControlComparator
	 * @since 1.22
	 */
	protected TableControlComparator createTableViewerComparator() {
		return new ECPTableViewerComparator();
	}

	/**
	 * Create the {@link ViewerActionContext} for the table viewer.
	 *
	 * @return the {@link TableRendererViewerActionContext}
	 * @throws DatabindingFailedException
	 * @since 1.18
	 */
	protected TableRendererViewerActionContext createViewerActionContext() {
		return new TableRendererViewerActionContext() {

			@Override
			public VTableControl getVElement() {
				return TableControlSWTRenderer.this.getVElement();
			}

			@Override
			public Setting getSetting() {
				try {
					return getEMFFormsDatabinding().getSetting(
						getDMRToMultiReference(), getViewModelContext().getDomainModel());
				} catch (final DatabindingFailedException ex) {
					return null; // this should never happen
				}
			}

			@Override
			public EditingDomain getEditingDomain() {
				return TableControlSWTRenderer.this.getEditingDomain(getViewModelContext().getDomainModel());
			}

			@Override
			public AbstractTableViewer getViewer() {
				return TableControlSWTRenderer.this.getTableViewer();
			}
		};
	}

	/**
	 * Configure the actions applicable to this table viewer.
	 *
	 * @param actionContext the action context
	 * @return an {@link ActionConfigurationImpl} built using the {@link ActionConfigurationBuilder}
	 *
	 * @since 1.18
	 */
	protected ActionConfiguration configureActions(TableRendererViewerActionContext actionContext) {
		final ActionConfigurationBuilder actionConfigBuilder = //
			ActionConfigurationBuilder.usingDefaults();

		/**
		 * Note: EMF Forms distinguishes between read-only and enabled.
		 * Read-only is a declarative state defined by the view model and cannot
		 * be overwritten during runtime whereas the enabled state can.
		 * Therefore, if the view element is marked as read-only we must not configure any buttons.
		 *
		 * @see @{link TableRenderAction#isTableDisabled()}
		 */
		if (getVElement().isEffectivelyReadonly()) {
			return actionConfigBuilder.build();
		}

		final Setting setting = actionContext.getSetting();
		final EClass eClass = ((EReference) setting.getEStructuralFeature()).getEReferenceType();

		if (!getVElement().isMoveUpDownDisabled()) {
			final MoveRowUpAction moveRowUpAction = new MoveRowUpAction(actionContext);
			final MoveRowDownAction moveRowDownAction = new MoveRowDownAction(actionContext);

			actionConfigBuilder
				.addAction(moveRowUpAction)
				.addControlFor(moveRowUpAction, new TableActionIconButton(
					formatTooltipText(eClass, MessageKeys.TableControl_MoveUp), getImage("icons/move_up.png"))) //$NON-NLS-1$
				.addKeySequenceFor(moveRowUpAction,
					getKeyBindingsForAction(MoveRowUpAction.ACTION_ID))
				//
				.addAction(moveRowDownAction)
				.addControlFor(moveRowDownAction, new TableActionIconButton(
					formatTooltipText(eClass, MessageKeys.TableControl_MoveDown), getImage("icons/move_down.png"))) //$NON-NLS-1$
				.addKeySequenceFor(moveRowDownAction,
					getKeyBindingsForAction(MoveRowDownAction.ACTION_ID));
		}

		if (!getVElement().isAddRemoveDisabled()) {
			final AddRowAction addRowAction = new AddRowAction(actionContext) {
				@Override
				public void addRowLegacy(
					final EClass eClass, final EStructuralFeature eStructuralFeature, final EObject eObject) {
					addRow(eClass, eObject, eStructuralFeature);
				}
			};
			final RemoveRowAction removeRowAction = new RemoveRowAction(actionContext) {
				@Override
				public void removeRowLegacy(List<EObject> deletionList, EObject eObject,
					EStructuralFeature eStructuralFeature) {
					deleteRowUserConfirmDialog(deletionList, eObject, eStructuralFeature, getAddButton(),
						getRemoveButton());
				}

				@Override
				public boolean canExecute() {
					final Setting setting = getActionContext().getSetting();
					return super.canExecute() && ConditionalDeleteService.getDeleteService(getViewModelContext())
						.canRemove(setting.getEObject(), setting.getEStructuralFeature(),
							getActionContext().getViewer().getStructuredSelection().toList());
				}
			};

			actionConfigBuilder
				.addAction(addRowAction)
				.addControlFor(addRowAction, new TableActionIconButton(
					formatTooltipText(eClass, MessageKeys.TableControl_AddInstanceOf), getImage("icons/add.png"))) //$NON-NLS-1$
				.addKeySequenceFor(addRowAction,
					getKeyBindingsForAction(AddRowAction.ACTION_ID));
			actionConfigBuilder
				.addAction(removeRowAction)
				.addControlFor(removeRowAction, new TableActionIconButton(
					formatTooltipText(eClass, MessageKeys.TableControl_RemoveSelected), getImage("icons/delete.png"))) //$NON-NLS-1$
				.addKeySequenceFor(removeRowAction,
					getKeyBindingsForAction(RemoveRowAction.ACTION_ID));
		}

		if (!getVElement().isDuplicateDisabled()) {
			final DuplicateRowAction duplicateRow = new DuplicateRowAction(actionContext);

			actionConfigBuilder
				.addAction(duplicateRow)
				.addControlFor(duplicateRow, new TableActionIconButton(
					formatTooltipText(eClass, MessageKeys.TableControl_Duplicate), getImage("icons/duplicate.png"))) //$NON-NLS-1$
				.addKeySequenceFor(duplicateRow,
					getKeyBindingsForAction(DuplicateRowAction.ACTION_ID));
		}

		return actionConfigBuilder.build();
	}

	/**
	 * Helper to extract the configured key bindings form the view template model.
	 *
	 * @param actionId the ID of the action to extract the key bindings for
	 * @param defaultKeybindings the default key bindings to use in case there are no bindings configured
	 * @return an array of key bindings
	 *
	 * @since 1.18
	 */
	protected String[] getKeyBindingsForAction(String actionId, String... defaultKeybindings) {
		final VTKeyBindings bindings = getStyleProperty(VTKeyBindings.class);
		if (bindings == null) {
			return defaultKeybindings;
		}
		final Set<String> ret = new LinkedHashSet<String>();
		for (final VTKeyBinding binding : bindings.getBindings()) {
			if (actionId.equals(binding.getId()) && binding.getKeySequence() != null
				&& !binding.getKeySequence().isEmpty()) {
				ret.add(binding.getKeySequence());
			}
		}
		return ret.toArray(new String[] {});
	}

	/**
	 * Creates an action bar.
	 *
	 * @param actionContext the {@link ViewerActionContext} to use
	 * @param actionConfiguration the {@link ActionConfiguration} to use
	 * @return a action bar builder
	 *
	 * @since 1.18
	 */
	protected TableActionBar<? extends AbstractTableViewer> createActionBar(
		TableRendererViewerActionContext actionContext, ActionConfiguration actionConfiguration) {
		return new TableRendererActionBar(actionContext, actionConfiguration, getViewModelContext());
	}

	/**
	 * Creates the {@link TableViewerCompositeBuilder} used to get Composite hierarchy for this table renderer. This
	 * method can be overwritten by sub classes to customize the layout.
	 *
	 * @return The {@link TableViewerCompositeBuilder}
	 * @since 1.13
	 */
	protected TableViewerCompositeBuilder createTableViewerCompositeBuilder() {
		if (getTableStyleProperty().getRenderMode() == RenderMode.COMPACT_VERTICALLY) {
			return new CompactVerticallyTableControlSWTRendererCompositeBuilder(false, false);
		}
		return new TableControlSWTRendererCompositeBuilder();
	}

	/**
	 * Creates a new {@link TableViewerSWTBuilder}.
	 *
	 * @param parent the parent {@link Composite}
	 * @param list the input object
	 * @param labelText the title
	 * @param labelTooltipText the tooltip
	 * @param compositeBuilder the {@link TableViewerCompositeBuilder}
	 * @param cp the content provider
	 * @param comparator the {@link ViewerComparator}; has no effect if move up/down
	 *            functionality is enabled
	 * @param actionBar the {@link ActionBar}
	 * @return the {@link TableViewerSWTBuilder}
	 * @since 1.18
	 * @deprecated Please use
	 *             {@link #createTableViewerSWTBuilder(Composite, IObservableList, IObservableValue, IObservableValue, TableViewerCompositeBuilder, ObservableListContentProvider, ViewerComparator, TableActionBar)}
	 *             instead
	 */
	@Deprecated
	// CHECKSTYLE.OFF: ParameterNumber
	protected TableViewerSWTBuilder createTableViewerSWTBuilder(Composite parent,
		@SuppressWarnings("rawtypes") IObservableList list,
		@SuppressWarnings("rawtypes") IObservableValue labelText,
		@SuppressWarnings("rawtypes") IObservableValue labelTooltipText, TableViewerCompositeBuilder compositeBuilder,
		ObservableListContentProvider cp, ECPTableViewerComparator comparator,
		TableActionBar<? extends AbstractTableViewer> actionBar) {
		// CHECKSTYLE.ON: ParameterNumber

		return createTableViewerSWTBuilder(parent, list, labelText, labelTooltipText, compositeBuilder, cp,
			(ViewerComparator) comparator,
			actionBar);
	}

	/**
	 * Creates a new {@link TableViewerSWTBuilder}.
	 *
	 * @param parent the parent {@link Composite}
	 * @param list the input object
	 * @param labelText the title
	 * @param labelTooltipText the tooltip
	 * @param compositeBuilder the {@link TableViewerCompositeBuilder}
	 * @param cp the content provider
	 * @param comparator the {@link ViewerComparator}; has no effect if move up/down
	 *            functionality is enabled
	 * @param actionBar the {@link ActionBar}
	 * @return the {@link TableViewerSWTBuilder}
	 * @since 1.22
	 *
	 */
	// CHECKSTYLE.OFF: ParameterNumber
	protected TableViewerSWTBuilder createTableViewerSWTBuilder(Composite parent,
		@SuppressWarnings("rawtypes") IObservableList list,
		@SuppressWarnings("rawtypes") IObservableValue labelText,
		@SuppressWarnings("rawtypes") IObservableValue labelTooltipText, TableViewerCompositeBuilder compositeBuilder,
		ObservableListContentProvider cp, ViewerComparator comparator,
		TableActionBar<? extends AbstractTableViewer> actionBar) {
		// CHECKSTYLE.ON: ParameterNumber
		return TableViewerFactory.fillDefaults(parent, SWT.NONE, list, labelText, labelTooltipText)
			.customizeCompositeStructure(compositeBuilder)
			.customizeActionBar(actionBar)
			.customizeTableViewerCreation(getTableViewerCreator())
			.customizeContentProvider(cp)
			.customizeComparator(comparator)
			.customizeDragAndDrop(new TableControlSWTRendererDragAndDrop());

	}

	/**
	 * Creates a new instance of the {@link TableViewerCreator} to be used.
	 *
	 * @return the {@link TableViewerCreator}
	 * @since 1.10
	 */
	protected TableViewerCreator<? extends AbstractTableViewer> getTableViewerCreator() {
		return new TableControlSWTRendererTableViewerCreator();
	}

	/**
	 * Override this method to add additional static columns at the beginning of the table.
	 *
	 * @param tableViewerSWTBuilder the builder
	 * @return the number of columns added
	 * @since 1.9
	 */
	protected int addAdditionalColumns(TableViewerSWTBuilder tableViewerSWTBuilder) {
		return 0;
	}

	/**
	 * Returns the zero-relative index of the item which is currently selected in the receiver, or -1 if no item is
	 * selected.
	 *
	 * @return the index of the selected item
	 * @since 1.10
	 */
	protected int getSelectionIndex() {
		return ((TableViewer) tableViewer).getTable().getSelectionIndex();
	}

	/**
	 * Returns an array of {@link Item items} which are the columns in the table.
	 *
	 * @return the columns of the table
	 * @since 1.10
	 */
	protected Item[] getColumns() {
		return ((TableViewer) tableViewer).getTable().getColumns();
	}

	/**
	 * Returns the receiver's horizontal scroll bar if it has one, and null if it does not.
	 *
	 * @return the horizontal scroll bar (or null)
	 * @since 1.10
	 */
	protected ScrollBar getHorizontalBar() {
		return ((TableViewer) tableViewer).getTable().getHorizontalBar();
	}

	/**
	 * Returns the receiver's vertical scroll bar if it has one, and null if it does not.
	 *
	 * @return the vertical scroll bar (or null)
	 * @since 1.10
	 */
	protected ScrollBar getVerticalBar() {
		return ((TableViewer) tableViewer).getTable().getVerticalBar();
	}

	private void addResizeListener(final Control control, final int regularColumnsStartIndex) {
		final ControlAdapter controlAdapter = new ControlAdapter() {
			@Override
			public void controlResized(ControlEvent e) {
				updateTableColumnWidths(control, regularColumnsStartIndex);
			}
		};
		control.addControlListener(controlAdapter);

		tableViewerComposite.addColumnListener(controlAdapter);
	}

	private void updateTableColumnWidths(Control table, int regularColumnsStartIndex) {
		if (isDisposing) {
			return;
		}
		final VTableControl tableControl = getVElement();
		final Widget[] allColumns = tableViewerComposite.getColumns();
		for (int i = regularColumnsStartIndex; i < allColumns.length; i++) {
			final Widget tableColumn = allColumns[i];
			final VDomainModelReference columnDMR = getColumnDomainModelReferences().get(i - regularColumnsStartIndex);
			TableConfigurationHelper.updateWidthConfiguration(tableControl, columnDMR, tableColumn);
		}
	}

	private void initCompositeHeight() {
		final VTTableStyleProperty styleProperty = getTableStyleProperty();
		minimumHeight = styleProperty.isSetMinimumHeight() ? Optional.of(styleProperty.getMinimumHeight())
			: Optional.<Integer> empty();
		maximumHeight = styleProperty.isSetMaximumHeight() ? Optional.of(styleProperty.getMaximumHeight())
			: Optional.<Integer> empty();
		visibleLines = styleProperty.isSetVisibleLines() ? Optional.of(styleProperty.getVisibleLines())
			: Optional.<Integer> empty();
	}

	private <T> void addRelayoutListenerIfNeeded(IObservableList<T> list, final Composite composite) {
		if (list == null) {
			return;
		}

		// relayout is only needed if min height != max height
		if (!minimumHeight.isPresent() && !maximumHeight.isPresent()) {
			return;
		}
		if (minimumHeight.isPresent() && maximumHeight.isPresent() && minimumHeight.get() == maximumHeight.get()) {
			return;
		}

		final GridData gridData = GridData.class.cast(composite.getLayoutData());
		list.addListChangeListener(new IListChangeListener<T>() {
			@Override
			public void handleListChange(ListChangeEvent<? extends T> event) {
				gridData.heightHint = getTableHeightHint();
				EMFFormsSWTLayoutUtil.adjustParentSize(composite);
			}
		});
	}

	/**
	 * Adds the table columns to the {@link TableViewerSWTBuilder}.
	 *
	 * @param tableViewerSWTBuilder the builder
	 * @param clazz the {@EClass} of the rendered object
	 * @param cp the content provider
	 * @param tableConfiguration
	 *
	 */
	private void addColumns(TableViewerSWTBuilder tableViewerSWTBuilder, EClass clazz,
		ObservableListContentProvider cp) {
		InternalEObject tempInstance = null;
		if (!clazz.isAbstract() && !clazz.isInterface()) {
			tempInstance = getInstanceOf(clazz);
		}

		final List<VDomainModelReference> columns = getColumnDomainModelReferences().stream().filter(Objects::nonNull)
			.collect(Collectors.toList());
		/* regular columns */
		columnFeatures = new EStructuralFeature[columns.size()];
		for (int i = 0; i < columns.size(); i++) {
			try {
				final VDomainModelReference dmr = columns.get(i);
				final IObservableValue<?> text = getLabelTextForColumn(dmr, clazz);
				final IObservableValue<?> tooltip = getLabelTooltipTextForColumn(dmr, clazz);

				// Use the same editing domain for the columns as for the view's domain object
				final EditingDomain editingDomain = getEditingDomain(getViewModelContext().getDomainModel());
				final IValueProperty<?, ?> valueProperty = getEMFFormsDatabinding().getValueProperty(dmr, clazz,
					editingDomain);
				final EStructuralFeature eStructuralFeature = (EStructuralFeature) valueProperty.getValueType();
				columnFeatures[i] = eStructuralFeature;

				@SuppressWarnings("unchecked")
				final IObservableMap<?, ?> observableMap = valueProperty.observeDetail(cp.getKnownElements());

				final TableControlEditingSupportAndLabelProvider labelProvider = new TableControlEditingSupportAndLabelProvider(
					tempInstance, eStructuralFeature, dmr, valueProperty, observableMap,
					getColumnDomainModelReferences().indexOf(dmr));
				final EditingSupportCreator editingSupportCreator = TableConfigurationHelper
					.isReadOnly(getVElement(), dmr) ? null : labelProvider;

				final Optional<Integer> weightConfig = TableConfigurationHelper.getColumnWeight(getVElement(), dmr);
				final Optional<Integer> widthConfig = TableConfigurationHelper.getColumnWidth(getVElement(), dmr);

				int weight;
				int minWidth;
				if (weightConfig.isPresent()) {
					minWidth = widthConfig.get();
					weight = weightConfig.get();
				} else {
					// TODO ugly: we need this temporary cell editor so early just to get size information
					final Shell tempShell = new Shell();
					final CellEditor tempCellEditor = createCellEditor(tempInstance, eStructuralFeature,
						new Table(tempShell, SWT.NONE));
					weight = ECPCellEditor.class.isInstance(tempCellEditor)
						? ECPCellEditor.class.cast(tempCellEditor).getColumnWidthWeight()
						: 100;
					minWidth = ECPCellEditor.class.isInstance(tempCellEditor)
						? ECPCellEditor.class.cast(tempCellEditor).getMinWidth()
						: 10;
					tempShell.dispose();
				}

				tableViewerSWTBuilder.addColumn(
					ColumnConfigurationBuilder.from(tableViewerSWTBuilder)
						.weight(weight)
						.minWidth(minWidth)
						.text(text)
						.tooltip(tooltip)
						.labelProviderFactory(labelProvider)
						.editingSupportCreator(editingSupportCreator)
						.dataMapEntry(ColumnConfiguration.DMR, dmr)
						.build());

			} catch (final DatabindingFailedException ex) {
				getReportService().report(new RenderingFailedReport(ex));
				continue;
			}
		}

	}

	private void setupValidation(
		final AbstractTableViewerComposite<? extends AbstractTableViewer> tableViewerComposite) {
		if (tableViewerComposite.getValidationControls().isPresent()) {
			final List<Control> validationControls = tableViewerComposite.getValidationControls().get();
			if (validationControls.size() == 1 && Label.class.isInstance(validationControls.get(0))) {
				validationIcon = (Label) validationControls.get(0);
			}

			final VTTableStyleProperty tableStyleProperty = getTableStyleProperty();
			showValidationSummaryTooltip = tableStyleProperty.isShowValidationSummaryTooltip();
		}
	}

	private void setupSorting(final TableViewerComparator comparator, int regularColumnsStartIndex,
		final AbstractTableViewerComposite<? extends AbstractTableViewer> tableViewerComposite) {

		final VTTableStyleProperty tableStyleProperty = getTableStyleProperty();
		if (!tableStyleProperty.isEnableSorting()) {
			return;
		}
		final int length = tableViewerComposite.getColumns().length;
		final List<Integer> sortableColumns = new ArrayList<Integer>();
		for (int i = 0; i < length; i++) {
			if (i >= regularColumnsStartIndex) {
				sortableColumns.add(i);
			}
		}
		tableViewerComposite.setComparator(comparator, sortableColumns);
	}

	private IObservableValue<?> getLabelText(VDomainModelReference dmrToCheck) {
		switch (getVElement().getLabelAlignment()) {
		case NONE:
			return Observables.constantObservableValue("", String.class); //$NON-NLS-1$
		default:
			try {
				return getEMFFormsLabelProvider().getDisplayName(dmrToCheck, getViewModelContext().getDomainModel());
			} catch (final NoLabelFoundException e) {
				// FIXME Expectation?
				getReportService().report(new RenderingFailedReport(e));
				return Observables.constantObservableValue(e.getMessage(), String.class);
			}
		}
	}

	private IObservableValue<?> getLabelTextForColumn(VDomainModelReference dmrToCheck, EClass dmrRootEClass) {
		try {
			// See whether the view model specifies a label for the column
			final Optional<VEnablementConfiguration> config = TableConfigurationHelper
				.findEnablementConfiguration(getVElement(), dmrToCheck);
			if (config.isPresent()) {
				final String label = config.get().getLabel();
				if (label != null && !label.isEmpty()) {
					@SuppressWarnings("unchecked")
					final IValueProperty<VElement, String> labelProperty = EMFProperties
						.value(VViewPackage.Literals.ELEMENT__LABEL);
					return labelProperty.observe(config.get());
				}
			}
			return getEMFFormsLabelProvider().getDisplayName(dmrToCheck, dmrRootEClass);
		} catch (final NoLabelFoundException e) {
			// FIXME Expectation?
			getReportService().report(new RenderingFailedReport(e));
			return Observables.constantObservableValue(e.getMessage(), String.class);
		}
	}

	private IObservableValue<?> getLabelTooltipText(VDomainModelReference dmrToCheck) {
		switch (getVElement().getLabelAlignment()) {
		case NONE:
			return Observables.constantObservableValue("", String.class); //$NON-NLS-1$
		default:
			try {
				return getEMFFormsLabelProvider().getDescription(dmrToCheck, getViewModelContext().getDomainModel());
			} catch (final NoLabelFoundException e) {
				// FIXME Expectation?
				getReportService().report(new RenderingFailedReport(e));
				return Observables.constantObservableValue(e.toString(), String.class);
			}
		}
	}

	private IObservableValue<?> getLabelTooltipTextForColumn(VDomainModelReference dmrToCheck, EClass dmrRootEClass) {
		try {
			return getEMFFormsLabelProvider().getDescription(dmrToCheck, dmrRootEClass);
		} catch (final NoLabelFoundException e) {
			// FIXME Expectation?
			getReportService().report(new RenderingFailedReport(e));
			return Observables.constantObservableValue(e.toString(), String.class);
		}
	}

	/**
	 * @return the {@link VDomainModelReference} which ends at the table setting
	 * @since 1.11
	 */
	protected VDomainModelReference getDMRToMultiReference() {
		// A segment based view model uses the plain dmr
		if (getVElement().getDomainModelReference().eClass() == VViewPackage.Literals.DOMAIN_MODEL_REFERENCE) {
			return getVElement().getDomainModelReference();
		}

		final VTableDomainModelReference tableDomainModelReference = (VTableDomainModelReference) getVElement()
			.getDomainModelReference();
		final VDomainModelReference dmrToCheck = tableDomainModelReference.getDomainModelReference() == null
			? tableDomainModelReference
			: tableDomainModelReference.getDomainModelReference();
		return dmrToCheck;
	}

	/**
	 * Allows to add additional buttons to the button bar of the table control.
	 * <p>
	 * The default implementation does not add additional buttons.
	 * </p>
	 *
	 * @param buttonComposite the composite where the buttons are added
	 * @return the total number of buttons added
	 */
	protected int addButtonsToButtonBar(Composite buttonComposite) {
		return 0;
	}

	/**
	 * Creates and returns the composite which will be the parent for the table viewer.
	 *
	 * @param composite the parent composite including the title/button bar
	 * @return the parent for the table viewer
	 */
	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 the preferred height for the table. This will be passed to the layoutdata.
	 *
	 * @return the height in px
	 */
	// BEGIN COMPLEX CODE
	protected int getTableHeightHint() {
		/* if neither min nor max is set we use a fixed height */
		if (!minimumHeight.isPresent() && !maximumHeight.isPresent() && !visibleLines.isPresent()) {
			return 200;
		}
		// if the visible lines attribute is present, it takes precedence over the minimum & maximum height hints
		if (visibleLines.isPresent()) {
			return computeRequiredHeight(visibleLines.get());
		}
		if (minimumHeight.isPresent() && maximumHeight.isPresent() && minimumHeight.get() == maximumHeight.get()) {
			return minimumHeight.get();
		}

		final int requiredHeight = computeRequiredHeight(null);

		if (minimumHeight.isPresent() && !maximumHeight.isPresent()) {
			return requiredHeight < minimumHeight.get() ? minimumHeight.get() : requiredHeight;
		}

		if (!minimumHeight.isPresent() && maximumHeight.isPresent()) {
			return requiredHeight > maximumHeight.get() ? maximumHeight.get() : requiredHeight;
		}

		if (requiredHeight < minimumHeight.get()) {
			return minimumHeight.get();
		}

		if (requiredHeight > maximumHeight.get()) {
			return maximumHeight.get();
		}

		return requiredHeight;
	}
	// END COMPLEX CODE

	/**
	 * Returns the height in pixels required to display the given number of table items. If the visible items are not
	 * specified, the height required to display all the table items is returned.
	 *
	 * @param visibleLines the number of visible table items
	 * @return the required height
	 * @since 1.13
	 */
	protected int computeRequiredHeight(Integer visibleLines) {
		if (tableViewer == null) {
			return SWT.DEFAULT;
		}
		final TableControl table = tableViewerComposite.getTableControl();
		if (table == null) {
			return SWT.DEFAULT;
		}
		if (table.isDisposed()) {
			return SWT.DEFAULT;
		}
		final int itemHeight = table.getItemHeight();
		// show one empty row if table does not contain any items or visibleLines < 1
		int itemCount;
		if (visibleLines != null) {
			itemCount = Math.max(visibleLines, 1);
		} else {
			itemCount = Math.max(table.getItemCount(), 1);
		}
		final int headerHeight = table.getHeaderVisible() ? table.getHeaderHeight() : 0;
		// 4px needed as a buffer to avoid scrollbars
		final int tableHeight = itemHeight * itemCount + headerHeight + 4;
		return tableHeight;
	}

	/**
	 * Returns the table viewer.
	 *
	 * @return the viewer
	 * @since 1.10
	 */
	protected AbstractTableViewer getTableViewer() {
		return tableViewer;
	}

	/**
	 * Returns the {@link AbstractTableViewerComposite}.
	 *
	 * @return the table viewer composite
	 * @since 1.13
	 */
	@SuppressWarnings("rawtypes")
	protected AbstractTableViewerComposite getTableViewerComposite() {
		return tableViewerComposite;
	}

	/**
	 * Sets the table viewer.
	 *
	 * @param tableViewer the viewer
	 * @since 1.10
	 */
	protected void setTableViewer(AbstractTableViewer tableViewer) {
		this.tableViewer = tableViewer;
	}

	/**
	 * This method gets called when the selection on the {@link TableViewer} (see {@link #getTableViewer()}) has
	 * changed.
	 * <p>
	 * If you override this method make sure to call super.
	 * </p>
	 *
	 * @param event the {@link SelectionChangedEvent}
	 */
	protected void viewerSelectionChanged(SelectionChangedEvent event) {
		// The default implementation does not need to do anything
	}

	private void createFixedValidationStatusColumn(TableViewerSWTBuilder tableViewerSWTBuilder) {
		final VTTableValidationStyleProperty tableValidationStyleProperty = getTableValidationStyleProperty();
		final int columnWidth = tableValidationStyleProperty.getColumnWidth();
		final String columnName = tableValidationStyleProperty.getColumnName();
		final String imagePath = tableValidationStyleProperty.getImagePath();
		Image image = null;
		if (imagePath != null && !imagePath.isEmpty()) {
			try {
				image = getImage(new URL(imagePath));
			} catch (final MalformedURLException ex) {
				getReportService().report(new AbstractReport(ex));
			}
		}

		tableViewerSWTBuilder.addColumn(
			ColumnConfigurationBuilder.usingDefaults()
				.minWidth(columnWidth)
				.text(columnName)
				.tooltip(columnName)
				.labelProvider(new ValidationStatusCellLabelProvider(getVElement()))
				.image(image)
				.build());

	}

	/**
	 * Retrieve images from the {@link ImageRegistryService} using an {@link URL}.
	 *
	 * @param url The {@link URL} pointing to the image
	 * @return The retrieved Image
	 * @since 1.6
	 */
	protected Image getImage(URL url) {
		return imageRegistryService.getImage(url);
	}

	/**
	 * Retrieve images from the {@link ImageRegistryService} using a bundle relative path.
	 *
	 * @param path The bundle relative path pointing to the image
	 * @return The retrieved Image
	 * @since 1.6
	 */
	protected Image getImage(String path) {
		Image result = imageRegistryService.getImage(FrameworkUtil.getBundle(getClass()), path);
		if (result == null && getClass() != TableControlSWTRenderer.class) {
			result = imageRegistryService.getImage(FrameworkUtil.getBundle(TableControlSWTRenderer.class), path);
		}
		return result;
	}

	/**
	 * Returns the {@link VTTableValidationStyleProperty}.
	 *
	 * @return the {@link VTTableValidationStyleProperty}
	 * @since 1.14
	 */
	protected VTTableValidationStyleProperty getTableValidationStyleProperty() {
		VTTableValidationStyleProperty tableValidationStyleProperties = getStyleProperty(
			VTTableValidationStyleProperty.class);
		if (tableValidationStyleProperties == null) {
			tableValidationStyleProperties = createDefaultTableValidationStyleProperty();
		}
		return tableValidationStyleProperties;
	}

	/**
	 * Creates the default {@link VTTableValidationStyleProperty}.
	 *
	 * @return the default {@link VTTableValidationStyleProperty}
	 * @since 1.14
	 */
	protected VTTableValidationStyleProperty createDefaultTableValidationStyleProperty() {
		final VTTableValidationStyleProperty tableValidationProp = VTTableValidationFactory.eINSTANCE
			.createTableValidationStyleProperty();
		tableValidationProp.setColumnWidth(80);
		tableValidationProp.setColumnName(LocalizationServiceHelper.getString(TableControlSWTRenderer.class,
			MessageKeys.TableControl_ValidationStatusColumn));
		tableValidationProp.setImagePath(null);
		return tableValidationProp;
	}

	/**
	 * Returns the {@link VTBackgroundStyleProperty}.
	 *
	 * @return the {@link VTBackgroundStyleProperty}
	 *
	 * @since 1.10
	 */
	protected VTBackgroundStyleProperty getBackgroundStyleProperty() {
		VTBackgroundStyleProperty styleProperty = getStyleProperty(VTBackgroundStyleProperty.class);
		if (styleProperty == null) {
			styleProperty = createDefaultBackgroundStyleProperty();
		}
		return styleProperty;
	}

	/**
	 * Creates the default {@link VTBackgroundStyleProperty}.
	 *
	 * @return the default {@link VTBackgroundStyleProperty}
	 * @since 1.14
	 */
	protected VTBackgroundStyleProperty createDefaultBackgroundStyleProperty() {
		return VTBackgroundFactory.eINSTANCE.createBackgroundStyleProperty();
	}

	/**
	 * Returns the {@link VTTableStyleProperty}.
	 *
	 * @return the {@link VTTableStyleProperty}
	 * @since 1.14
	 */
	protected VTTableStyleProperty getTableStyleProperty() {
		VTTableStyleProperty styleProperty = getStyleProperty(VTTableStyleProperty.class);
		if (styleProperty == null) {
			styleProperty = createDefaultTableStyleProperty();
		}
		return styleProperty;
	}

	/**
	 * Creates the default {@link VTTableStyleProperty}.
	 *
	 * @return the default {@link VTTableStyleProperty}
	 * @since 1.14
	 */
	protected VTTableStyleProperty createDefaultTableStyleProperty() {
		final VTTableStyleProperty tableStyleProperty = VTTableStylePropertyFactory.eINSTANCE
			.createTableStyleProperty();
		tableStyleProperty.setMaximumHeight(200);
		if (!getVElement().isEffectivelyReadonly()) {
			tableStyleProperty.setMinimumHeight(200);
		}
		return tableStyleProperty;
	}

	/**
	 * Returns the {@link VTFontPropertiesStyleProperty}.
	 *
	 * @return the {@link VTFontPropertiesStyleProperty}
	 * @since 1.10
	 */
	protected VTFontPropertiesStyleProperty getFontPropertiesStyleProperty() {
		VTFontPropertiesStyleProperty styleProperty = getStyleProperty(VTFontPropertiesStyleProperty.class);
		if (styleProperty == null) {
			styleProperty = createDefaultFontPropertiesStyleProperty();
		}
		return styleProperty;
	}

	/**
	 * Creates the default {@link VTFontPropertiesStyleProperty}.
	 *
	 * @return the default {@link VTFontPropertiesStyleProperty}
	 * @since 1.14
	 */
	protected VTFontPropertiesStyleProperty createDefaultFontPropertiesStyleProperty() {
		final VTFontPropertiesStyleProperty property = VTFontPropertiesFactory.eINSTANCE
			.createFontPropertiesStyleProperty();
		property.setColorHEX("000000"); //$NON-NLS-1$
		return property;
	}

	private <SP extends VTStyleProperty> SP getStyleProperty(Class<SP> stylePropertyClass) {
		return RendererUtil.getStyleProperty(getVTViewTemplateProvider(), getVElement(), getViewModelContext(),
			stylePropertyClass);
	}

	/**
	 * Returns the {@link Color} specified by the provided String.
	 *
	 * @param colorHex the Hex String describing the color
	 * @return the {@link Color}
	 * @since 1.10
	 *
	 */
	protected Color getSWTColor(String colorHex) {
		final String redString = colorHex.substring(0, 2);
		final String greenString = colorHex.substring(2, 4);
		final String blueString = colorHex.substring(4, 6);
		final int red = Integer.parseInt(redString, 16);
		final int green = Integer.parseInt(greenString, 16);
		final int blue = Integer.parseInt(blueString, 16);
		return new Color(Display.getDefault(), red, green, blue);
	}

	/**
	 * Retrieves this table's column DMRs from the table DMR.
	 *
	 * @return The domain model references defining the columns of this table.
	 * @since 1.19
	 */
	protected EList<VDomainModelReference> getColumnDomainModelReferences() {
		final VDomainModelReference dmr = getVElement().getDomainModelReference();
		final java.util.Optional<VMultiDomainModelReferenceSegment> multiSegment = MultiSegmentUtil
			.getMultiSegment(dmr);
		if (multiSegment.isPresent()) {
			return multiSegment.get().getChildDomainModelReferences();
		}
		return VTableDomainModelReference.class.cast(dmr).getColumnDomainModelReferences();
	}

	/**
	 * This is called in order to setup the editing support for a table column.
	 *
	 * @param tempInstance the temporary input instance of the table
	 * @param feature the feature of the column
	 * @param table the table/parent
	 * @return the cell editor
	 * @since 1.10
	 */
	protected CellEditor createCellEditor(final EObject tempInstance, final EStructuralFeature feature,
		Composite table) {
		return CellEditorFactory.INSTANCE.getCellEditor(feature,
			tempInstance, table, getViewModelContext());
	}

	private InternalEObject getInstanceOf(EClass clazz) {
		return InternalEObject.class.cast(clazz.getEPackage().getEFactoryInstance().create(clazz));
	}

	/**
	 * This method shows a user confirmation dialog when the user attempts to delete a row in the table.
	 *
	 * @deprecated this method will be moved to {@link RemoveRowAction} in the future
	 * @param deletionList the list of selected EObjects to delete
	 * @param eObject The containment reference {@link EObject}
	 * @param structuralFeature The containment reference {@link EStructuralFeature}
	 * @param addButton the add button
	 * @param removeButton the remove button
	 * @since 1.6
	 */
	@Deprecated
	protected void deleteRowUserConfirmDialog(final List<EObject> deletionList, final EObject eObject,
		final EStructuralFeature structuralFeature, final Button addButton, final Button removeButton) {
		final MessageDialog dialog = new MessageDialog(addButton.getShell(),
			LocalizationServiceHelper.getString(TableControlSWTRenderer.class, MessageKeys.TableControl_Delete), null,
			LocalizationServiceHelper.getString(TableControlSWTRenderer.class,
				MessageKeys.TableControl_DeleteAreYouSure),
			MessageDialog.CONFIRM, new String[] {
				JFaceResources.getString(IDialogLabelKeys.YES_LABEL_KEY),
				JFaceResources.getString(IDialogLabelKeys.NO_LABEL_KEY) },
			0);

		new ECPDialogExecutor(dialog) {

			@Override
			public void handleResult(int codeResult) {
				if (codeResult == IDialogConstants.CANCEL_ID
					|| codeResult == SWT.DEFAULT) { // SWT.DEFAULT is return by closing a message dialog
					return;
				}

				deleteRows(deletionList, eObject, structuralFeature);

				final List<?> containments = (List<?>) eObject.eGet(structuralFeature, true);
				if (containments.size() < structuralFeature.getUpperBound()) {
					addButton.setEnabled(true);
				}
				if (containments.size() <= structuralFeature.getLowerBound()) {
					removeButton.setEnabled(false);
				}
			}
		}.execute();
	}

	/**
	 * This is called by {@link #deleteRowUserConfirmDialog(List)} after the user confirmed to delete the selected
	 * elements.
	 *
	 * @deprecated this method will be moved to {@link RemoveRowAction} in the future
	 * @param deletionList the list of {@link EObject EObjects} to delete
	 * @param eObject The containment reference {@link EObject}
	 * @param structuralFeature The containment reference {@link EStructuralFeature}
	 * @since 1.6
	 */
	@Deprecated
	protected void deleteRows(List<EObject> deletionList, final EObject eObject,
		final EStructuralFeature structuralFeature) {

		final EditingDomain editingDomain = getEditingDomain(eObject);

		/* assured by #isApplicable */
		final EReference reference = EReference.class.cast(structuralFeature);
		final List<Object> toDelete = new ArrayList<Object>(deletionList);
		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(toDelete);
		} else {
			removeElements(editingDomain, eObject, reference, toDelete);
		}
	}

	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);
			}
		}
	}

	/**
	 * This method is called to add a new entry in the domain model and thus to create a new row in the table. The
	 * element to create is defined by the provided class.
	 * You can override this method but you have to call super nonetheless.
	 *
	 * @deprecated this method will be move to {@link AddRowAction} in the future
	 * @param clazz the {@link EClass} defining the EObject to create
	 * @param eObject The containment reference {@link EObject}
	 * @param structuralFeature The containment reference {@link EStructuralFeature}
	 * @since 1.6
	 */
	@Deprecated
	protected void addRow(EClass clazz, EObject eObject, EStructuralFeature structuralFeature) {
		Optional<EObject> eObjectToAdd = null;

		/* table service available => use specific behavior to create row */
		if (getViewModelContext().hasService(TableControlService.class)) {
			final TableControlService tableService = getViewModelContext()
				.getService(TableControlService.class);
			eObjectToAdd = tableService.createNewElement(clazz, eObject, structuralFeature);
		}
		/* no table service available, fall back to default */
		if (eObjectToAdd == null) {
			final ReferenceService referenceService = getViewModelContext().getService(ReferenceService.class);
			eObjectToAdd = referenceService.addNewModelElements(eObject, EReference.class.cast(structuralFeature),
				false);
		}

		if (!eObjectToAdd.isPresent()) {
			return;
		}

		final EObject instance = eObjectToAdd.get();
		final EditingDomain editingDomain = getEditingDomain(eObject);
		if (editingDomain == null) {
			return;
		}
		editingDomain.getCommandStack().execute(
			AddCommand.create(editingDomain, eObject, structuralFeature, instance));
		tableViewer.setSelection(new StructuredSelection(eObjectToAdd.get()), true);

	}

	@Override
	protected void applyValidation(VDiagnostic oldDiagnostic, VDiagnostic newDiagnostic) {
		getRunnableManager().executeAsync(new ComputeValidationUpdate(oldDiagnostic, newDiagnostic));
	}

	@Override
	protected void applyValidation() {
		if (!getRunnableManager().isRunning()) {
			getRunnableManager().executeAsync(new ApplyValidationRunnable());
		}
	}

	/**
	 * Obtain my runnable manager (visible for testability).
	 *
	 * @return my runnable manager
	 */
	final RunnableManager getRunnableManager() {
		return runnableManager;
	}

	/**
	 * Post a request to refresh my {@linkplain #getTableViewer() viewer} on the
	 * asynchronous refresh manager.
	 *
	 * @since 1.21
	 * @see #getTableViewer()
	 * @see ViewerRefreshManager
	 */
	protected void postRefresh() {
		postRefresh(getRefreshRunnable(getTableViewer()));
	}

	/**
	 * Post a refresh request on the asynchronous refresh manager.
	 *
	 * @param refreshRunnable the refresh operation to execute
	 * @since 1.21
	 * @see ViewerRefreshManager
	 */
	protected void postRefresh(Runnable refreshRunnable) {
		final Viewer viewer = getTableViewer();
		if (viewer != null && !viewer.getControl().isDisposed()) {
			getRunnableManager().executeAsync(refreshRunnable);
		}
	}

	/**
	 * Returns the add button created by the framework.
	 *
	 * @deprecated use {@link #getControlForAction(String)} instead
	 * @return the addButton
	 * @since 1.6
	 */
	@Deprecated
	protected Button getAddButton() {
		final Optional<Control> control = getControlForAction(AddRowAction.ACTION_ID);
		if (control.isPresent()) {
			return (Button) control.get();
		}
		return null; // happens when add/remove has been disabled in the view model
	}

	/**
	 * Returns the remove button created by the framework.
	 *
	 * @deprecated use {@link #getControlForAction(String)} instead
	 * @return the removeButton
	 * @since 1.6
	 */
	@Deprecated
	protected Button getRemoveButton() {
		final Optional<Control> control = getControlForAction(RemoveRowAction.ACTION_ID);
		if (control.isPresent()) {
			return (Button) control.get();
		}
		return null; // happens when add/remove has been disabled in the view model
	}

	/**
	 * Returns the control created to trigger a certain action.
	 *
	 * @param actionId the action ID of the action that is bound to the control
	 * @return an optional for the control of the given action ID
	 * @since 1.18
	 */
	public Optional<Control> getControlForAction(String actionId) {
		if (tableViewerComposite.getActionBar().isPresent()) {
			return tableViewerComposite.getActionBar().get().getControlById(actionId);
		}
		return Optional.empty();
	}

	/**
	 * Helper method which uses an EMFForms observable value to get the setting for the given
	 * {@link VDomainModelReference}.
	 *
	 * @param dmr the {@link VDomainModelReference}
	 * @param eObject the {@link EObject} to get the setting for
	 * @return an Optional<Setting>
	 *
	 * @since 1.17
	 */
	protected Optional<Setting> getSettingFromObservable(VDomainModelReference dmr, EObject eObject) {
		@SuppressWarnings("rawtypes")
		IObservableValue observableValue = null;
		try {
			observableValue = getEMFFormsDatabinding().getObservableValue(dmr, eObject);

			final EStructuralFeature feature = (EStructuralFeature) observableValue.getValueType();
			final EObject observed = (EObject) ((IObserving) observableValue).getObserved();

			// Given EClass has no such feature
			if (observed == null || observed.eClass().getFeatureID(feature) == -1) {
				return Optional.empty();
			}

			return Optional.of(((InternalEObject) observed).eSetting(feature));

		} catch (final DatabindingFailedException ex) {
			getReportService().report(new DatabindingFailedReport(ex));
			return Optional.empty();
		} finally {
			if (observableValue != null) {
				observableValue.dispose();
			}
		}

	}

	@Override
	protected void applyEnable() {
		updateActionBar();
	}

	@Override
	protected void applyReadOnly() {
		updateActionBar();
	}

	@Override
	protected void applyUnchangeableFeature() {
		getVElement().setAddRemoveDisabled(true);
		getVElement().setDuplicateDisabled(true);
		getVElement().setMoveUpDownDisabled(true);
		updateActionBar();
	}

	/**
	 * Update this table control's action bar if it is present.
	 *
	 * @since 1.20
	 */
	protected void updateActionBar() {
		if (tableViewerComposite != null && tableViewerComposite.getActionBar().isPresent()) {
			tableViewerComposite.getActionBar().get().updateActionBar();
		}
	}

	@Override
	protected void dispose() {
		isDisposing = true;
		rendererGridDescription = null;
		viewModelDBC.dispose();
		if (list != null) {
			list.dispose();
		}
		if (columnIndexToComparatorMap != null) {
			for (final ECPCellEditorComparator value : columnIndexToComparatorMap.values()) {
				if (value instanceof CellEditor) {
					((CellEditor) value).dispose();
				}
			}
			columnIndexToComparatorMap.clear();
		}
		if (tableViewerComposite != null) {
			tableViewerComposite.dispose();
			tableViewerComposite = null;
		}
		if (tableViewer != null) {
			tableViewer.getControl().dispose();
			tableViewer = null;
		}

		if (customization != null) {
			for (final ColumnConfiguration columnConfig : customization.getColumnConfigurations()) {
				columnConfig.visible().dispose();
				columnConfig.dispose();
			}
			customization.getTableConfiguration().dispose();
		}

		if (autoSortModelChangeListener != null) {
			getViewModelContext().unregisterDomainChangeListener(autoSortModelChangeListener);
			autoSortModelChangeListener = null;
		}
		super.dispose();
	}

	/**
	 * Get called by the {@link ECPTableViewerComparator} in order to compare the given objects.
	 *
	 * @param viewer the table viewer
	 * @param left the first object of the comparison
	 * @param right the second object of the comparison
	 * @param propertyIndex index of the selection column. the index is aligned with the index of the associated column
	 *            domain model reference
	 * @param direction 0 (no sorting = insertion order := {@link SWT#NONE}), 1 (ascending := {@link SWT#DOWN}) or 2
	 *            (descending := {@link SWT#UP}) according to the indication displayed at
	 *            the table column.
	 * @return a negative number if the first element is less than the
	 *         second element; the value <code>0</code> if the first element is
	 *         equal to the second element; and a positive number if the first
	 *         element is greater than the second element
	 * @since 1.8
	 */
	// BEGIN COMPLEX CODE
	@SuppressWarnings("unchecked")
	protected int compare(Viewer viewer, Object left, Object right, int direction, int propertyIndex) {
		if (direction == 0) {
			return 0;
		}

		// We might have ignored columns at the beginning
		propertyIndex = propertyIndex - regularColumnsStartIndex;
		if (propertyIndex < 0) {
			return 0;
		}
		int rc = 0;

		final VDomainModelReference dmr = getColumnDomainModelReferences().get(propertyIndex);

		final Optional<Setting> leftSetting = getSettingFromObservable(dmr, (EObject) left);
		final Optional<Setting> rightSetting = getSettingFromObservable(dmr, (EObject) right);

		final Object leftValue = leftSetting.isPresent() ? leftSetting.get().get(true) : null;
		final Object rightValue = rightSetting.isPresent() ? rightSetting.get().get(true) : null;

		if (columnIndexToComparatorMap.containsKey(propertyIndex)) {
			return columnIndexToComparatorMap.get(propertyIndex).compare(leftValue, rightValue, direction);
		}

		if (leftValue == null) {
			if (rightValue == null) {
				rc = 0;
			} else {
				rc = 1;
			}
		} else if (rightValue == null) {
			rc = -1;
		} else {
			if (leftValue instanceof Enumerator) {
				final EStructuralFeature feature = leftSetting.get().getEStructuralFeature();
				rc = getEnumeratorComparator().compare(feature, (Enumerator) leftValue, (Enumerator) rightValue);
			} else if (!(leftValue instanceof String) && leftValue instanceof Comparable
				&& leftValue.getClass().isInstance(rightValue)) {
				rc = Comparable.class.cast(leftValue).compareTo(rightValue);
			} else {
				rc = NumberAwareStringComparator.getInstance().compare(leftValue.toString(), rightValue.toString());
			}
		}
		// If descending order, flip the direction
		if (direction == 2) {
			rc = -rc;
		}
		return rc;
	}
	// END COMPLEX CODE

	@Override
	protected void rootDomainModelChanged() throws DatabindingFailedException {

		final IObservableList<?> oldList = (IObservableList<?>) getTableViewer().getInput();
		oldList.dispose();

		final EObject domainModel = getViewModelContext().getDomainModel();
		final IObservableList<?> list = getEMFFormsDatabinding().getObservableList(getDMRToMultiReference(),
			domainModel);
		getTableViewer().setInput(list);

		if (tableViewerComposite.getActionBar().isPresent()) {
			tableViewerComposite.getActionBar().get().updateActionBar();
		}
	}

	/**
	 * Checks whether an element is editable or not.
	 *
	 * @param element The list entry to be checked
	 * @return True if the element can be edited, false otherwise
	 *
	 * @since 1.11
	 */
	protected boolean canEditObject(Object element) {
		return true;
	}

	/**
	 * Defined whether a cell editor should be created or not.
	 *
	 * @param element The table entry to be checked
	 * @return True if a CellEditor should be created, false otherwise
	 * @since 1.12
	 */
	protected boolean shouldCreateCellEditor(Object element) {
		final boolean isObjectEditable = canEditObject(element);
		if (!isObjectEditable) {
			return false;
		}
		final boolean editable = getVElement().isEffectivelyEnabled()
			&& !getVElement().isEffectivelyReadonly();
		return editable;
	}

	private boolean isDisabled(EObject eObject, VDomainModelReference columnDmr) {

		final Optional<VEnablementConfiguration> enablmentConf = TableConfigurationHelper
			.findEnablementConfiguration(getVElement(), columnDmr);

		if (enablmentConf.isPresent()) {
			return !enablmentConf.get().isEnabled();
		}

		return false;
	}

	/**
	 * Called by the {@link TableControlEditingSupportAndLabelProvider}.
	 *
	 * @param feature the feature of the column
	 * @param cellEditor the cell editor for the column
	 * @param attributeMap the attribute map displayed in the table
	 * @param vTableControl the table view model element
	 * @param dmr the domain model reference of the column
	 * @param table the table control
	 * @return the {@link CellLabelProvider} of the column
	 * @since 1.12
	 */
	protected CellLabelProvider createCellLabelProvider(
		EStructuralFeature feature,
		CellEditor cellEditor,
		@SuppressWarnings("rawtypes") IObservableMap attributeMap,
		VTableControl vTableControl,
		VDomainModelReference dmr,
		Control table) {
		return new ECPCellLabelProvider(
			feature,
			cellEditor,
			attributeMap,
			getVElement(),
			dmr,
			table);
	}

	/**
	 * @return The {@link FeatureAwareComparator} used to compare {@link Enumerator enum values} when sorting the table
	 * @since 1.22
	 */
	protected FeatureAwareComparator<Enumerator> getEnumeratorComparator() {
		if (enumeratorComparator == null) {
			enumeratorComparator = new LocalizedEnumeratorComparator(localizationService, bundleResolver,
				getReportService());
		}
		return enumeratorComparator;
	}

	/**
	 * Reeveal the given {@code object} in my table.
	 *
	 * @param object an object to reveal
	 * @return whether I succeeded in revealing it
	 *
	 * @since 1.22
	 */
	public boolean reveal(Object object) {
		final AbstractTableViewer viewer = getTableViewer();
		final IObservableList<?> list = (IObservableList<?>) viewer.getInput();
		final boolean result = list.contains(object);

		if (result) {
			final IStructuredSelection selection = new StructuredSelection(object);
			if (!selection.equals(viewer.getSelection())) {
				viewer.setSelection(selection, true);
			}
		} else {
			viewer.reveal(object);
		}

		return result;
	}

	/**
	 * The {@link DNDProvider} for this renderer.
	 *
	 * @author Johannes Faltermeier
	 *
	 */
	private final class TableControlSWTRendererDragAndDrop implements DNDProvider {

		/**
		 * The drop adapter.
		 */
		private final class TableControlDropAdapter extends EditingDomainViewerDropAdapter {

			private EObject eObject;
			private EStructuralFeature eStructuralFeature;
			private List<Object> list;

			@SuppressWarnings("unchecked")
			TableControlDropAdapter(EditingDomain domain, Viewer viewer, AbstractTableViewer tableViewer) {
				super(domain, viewer);
				try {
					final Setting setting = getEMFFormsDatabinding().getSetting(getDMRToMultiReference(),
						getViewModelContext().getDomainModel());
					eObject = setting.getEObject();
					eStructuralFeature = setting.getEStructuralFeature();
					list = (List<Object>) setting.get(true);
				} catch (final DatabindingFailedException ex) {
					getReportService().report(new AbstractReport(ex));
				}
			}

			@Override
			protected void helper(DropTargetEvent event) {
				final Object target = extractDropTarget(event.item);
				final Collection<?> dragSource = getDragSource(event);

				if (dragSource == null) {
					/* possible on non-win32 platforms */
					/* in this case we will just wait until the data is available without setting a detail */
					return;
				}

				if (target == null || dragSource.contains(target)) {
					event.detail = DND.DROP_NONE;
					return;
				}

				event.detail = DND.DROP_MOVE;
			}

			@Override
			public void drop(DropTargetEvent event) {

				final Collection<?> dragSource = getDragSource(event);
				final Object target = extractDropTarget(event.item);
				final float location = getLocation(event);

				final List<Command> commands = new ArrayList<Command>();
				final boolean insertAfter = location >= 0.5;

				for (final Object toMove : dragSource) {
					final int indexTarget = list.indexOf(target);
					final int indexToMove = list.indexOf(toMove);

					if (indexTarget == -1 || indexToMove == -1) {
						return;
					}

					final boolean moveIsLocatedBeforeTarget = indexToMove < indexTarget;

					int index;
					if (insertAfter) {
						if (moveIsLocatedBeforeTarget) {
							index = indexTarget;
						} else {
							index = indexTarget + 1;
						}
					} else {
						/* insert Before Target */
						if (moveIsLocatedBeforeTarget) {
							index = indexTarget - 1;
						} else {
							index = indexTarget;
						}
					}

					commands.add(MoveCommand.create(domain, eObject, eStructuralFeature, toMove, index));
				}

				final Command command = new CompoundCommand(commands);

				if (!command.canExecute()) {
					return;
				}
				domain.getCommandStack().execute(command);

				postRefresh();
			}
		}

		@Override
		public int getDragOperations() {
			return getDNDOperations();
		}

		@Override
		public Transfer[] getDragTransferTypes() {
			return getDNDTransferTypes();
		}

		@Override
		public DragSourceListener getDragListener(AbstractTableViewer tableViewer) {
			return new ViewerDragAdapter(tableViewer);
		}

		@Override
		public int getDropOperations() {
			return getDNDOperations();
		}

		@Override
		public Transfer[] getDropTransferTypes() {
			return getDNDTransferTypes();
		}

		@Override
		public DropTargetListener getDropListener(final AbstractTableViewer tableViewer) {
			return new TableControlDropAdapter(getEditingDomain(getViewModelContext().getDomainModel()), tableViewer,
				tableViewer);
		}

		private int getDNDOperations() {
			return DND.DROP_COPY | DND.DROP_MOVE | DND.DROP_LINK;
		}

		private Transfer[] getDNDTransferTypes() {
			return new Transfer[] { LocalTransfer.getInstance() };
		}

		@Override
		public boolean hasDND() {
			return true;
		}
	}

	/**
	 * Double click listener.
	 *
	 */
	private final class DoubleClickListener implements IDoubleClickListener {
		@Override
		public void doubleClick(DoubleClickEvent event) {
			if (!getViewModelContext().hasService(TableControlService.class)) {
				return;
			}
			final ISelection selection = event.getSelection();
			if (!StructuredSelection.class.isInstance(selection)) {
				return;
			}
			final TableControlService tableService = getViewModelContext()
				.getService(TableControlService.class);
			tableService.doubleClick(getVElement(),
				(EObject) StructuredSelection.class.cast(selection).getFirstElement());
		}
	}

	/**
	 * The Listener which is set on the table viewer to inform the renderer about selection changes.
	 *
	 */
	private final class ViewerSelectionChangedListener implements ISelectionChangedListener {
		@Override
		public void selectionChanged(SelectionChangedEvent event) {
			viewerSelectionChanged(event);
		}
	}

	/**
	 * A two-stage asynchronous task that first, in the background, computes the objects
	 * that have actually seen validation changes and then post the viewer update
	 * on the UI thread.
	 */
	private final class ComputeValidationUpdate implements Runnable, RunnableManager.BackgroundStage {
		private final Set<Diagnostic> oldDiagnostics;
		private final Set<Diagnostic> newDiagnostics;

		private Runnable update;

		/**
		 * Initializes me with the old and new diagnostics from which to compute the objects
		 * that need to be updated in my viewer.
		 *
		 * @param oldDiagnostic the old validation state
		 * @param newDiagnostic the new validation state
		 */
		ComputeValidationUpdate(VDiagnostic oldDiagnostic, VDiagnostic newDiagnostic) {
			super();

			// These have to be extracted on the calling thread because the ELists
			// are not thread-safe
			oldDiagnostics = getDiagnostics(oldDiagnostic);
			newDiagnostics = getDiagnostics(newDiagnostic);
		}

		@Override
		public Runnable getNextStage() {
			return update;
		}

		@Override
		public void run() {
			// Compute the difference between the diagnostics to find what actually
			// are the logical changes in validation that will need updates in the UI
			final Set<Diagnostic> difference = difference(oldDiagnostics, newDiagnostics);
			final Set<Object> updates = difference.stream().map(this::getSubject).filter(Objects::nonNull)
				.collect(Collectors.toSet());
			if (!updates.isEmpty()) {
				update = new ApplyValidationRunnable(updates);
			} // Otherwise we don't need the UI update stage
		}

		private Set<Diagnostic> getDiagnostics(VDiagnostic container) {
			return container == null ? Collections.emptySet()
				: container.getDiagnostics().stream()
					.filter(Diagnostic.class::isInstance).map(Diagnostic.class::cast)
					.collect(Collectors.toSet());
		}

		private Object getSubject(Diagnostic diagnostic) {
			final List<?> data = diagnostic.getData();
			return data.isEmpty() ? null : data.get(0);
		}

		private Set<Diagnostic> difference(Set<Diagnostic> set1, Set<Diagnostic> set2) {
			// Diagnostics do not implement equals(), so we have to do it for them.
			// Most straightforward approach is to use a tree set, which uses the
			// comparator's zero result to test equality
			final SortedSet<Diagnostic> sorted1 = new TreeSet<>(this::compare);
			sorted1.addAll(set1);
			final SortedSet<Diagnostic> sorted2 = new TreeSet<>(this::compare);
			sorted2.addAll(set2);

			// Difference each side
			sorted1.removeAll(set2);
			sorted2.removeAll(set1);

			// And the union of that
			sorted1.addAll(sorted2);

			return sorted1;
		}

		private int compare(Diagnostic d1, Diagnostic d2) {
			int result = d1.getSeverity() - d2.getSeverity();
			if (result != 0) {
				return result;
			}

			result = d1.getCode() - d2.getCode();
			if (result != 0) {
				return result;
			}

			result = compare(d1.getSource(), d2.getSource());
			if (result != 0) {
				return result;
			}

			result = compare(d1.getMessage(), d2.getMessage());
			if (result != 0) {
				return result;
			}

			result = compare(d1.getData(), d2.getData());

			// The diagnostics from validation should not have exceptions and
			// the children are immaterial because we've already flattened them

			return result;
		}

		private int compare(String s1, String s2) {
			if (s1 == null) {
				return s2 == null ? 0 : -1;
			}
			if (s2 == null) {
				return +1;
			}
			return s1.compareTo(s2);
		}

		private int compare(List<?> s1, List<?> s2) {
			if (s1 == null) {
				return s2 == null ? 0 : -1;
			}
			if (s2 == null) {
				return +1;
			}

			final int size = s1.size();
			int result = size - s2.size();
			if (result != 0) {
				return result;
			}

			int i = 0;
			for (i = 0; i < size; i++) {
				final Object e1 = s1.get(i);
				final Object e2 = s2.get(i);
				if (e1 != e2) {
					// Arbitrary but stable order
					result = System.identityHashCode(e1) - System.identityHashCode(e2);
					break;
				}
			}

			return result;
		}
	}

	/**
	 * Runnable which is called by {@link TableControlSWTRenderer#applyValidation() applyValidation}.
	 *
	 */
	private final class ApplyValidationRunnable implements Runnable {
		private Collection<?> updates;

		ApplyValidationRunnable() {
			super();
		}

		ApplyValidationRunnable(Collection<?> updates) {
			super();

			this.updates = updates;
		}

		@Override
		public void run() {
			if (isDisposing) {
				return;
			}
			// triggered due to another validation rule before this control is rendered
			// validation rule triggered after the control was disposed
			if (validationIcon == null || validationIcon.isDisposed()) {
				return;
			}

			// no diagnostic set
			if (getVElement().getDiagnostic() == null) {
				return;
			}

			final VDomainModelReference dmr = getDMRToMultiReference();
			final Optional<Setting> setting = getSettingFromObservable(dmr, getViewModelContext().getDomainModel());

			if (!setting.isPresent()) {
				return;
			}

			validationIcon.setImage(getValidationIcon());
			showValidationSummaryTooltip(setting.get(), showValidationSummaryTooltip);

			if (updates != null) {
				// Update these specific objects
				getTableViewer().update(updates.toArray(), null);
			} else {
				// Just refresh everything. We are already in the RunnableManager
				// context, so don't post but do it directly
				getTableViewer().refresh();
			}
		}

		// extracted in order to avoid checkstyle complexity warning
		private void showValidationSummaryTooltip(Setting tableSetting, boolean doShow) {
			if (doShow) {
				validationIcon.setToolTipText(ECPTooltipModifierHelper.modifyString(getVElement().getDiagnostic()
					.getMessage(), null));
				return;
			}

			// Even if the display of a validation summary tooltip is disabled, we still show validation errors directly
			// related to the table feature (e.g. multiplicity errors)
			final StringBuilder builder = new StringBuilder();
			final List<Diagnostic> tableDiagnostics = getVElement().getDiagnostic()
				.getDiagnostic(tableSetting.getEObject(), tableSetting.getEStructuralFeature());
			for (final Diagnostic diagnostic : tableDiagnostics) {
				builder.append(diagnostic.getMessage());
				builder.append("\n"); //$NON-NLS-1$
			}
			final String toolTipText = ECPTooltipModifierHelper.modifyString(builder.toString().trim(), null);
			validationIcon.setToolTipText(toolTipText);
		}
	}

	/**
	 * Implements {@link EditingSupportCreator} and {@link CellLabelProviderFactory} for the table control swt renderer.
	 *
	 * This allows us to access the actual cell editor from the cell label provider.
	 *
	 * @author Johannes Faltermeier
	 *
	 */
	protected final class TableControlEditingSupportAndLabelProvider
		implements EditingSupportCreator, CellLabelProviderFactory {
		private final InternalEObject tempInstance;
		private final EStructuralFeature eStructuralFeature;
		private final VDomainModelReference dmr;
		private final IValueProperty<?, ?> valueProperty;
		private final IObservableMap<?, ?> observableMap;

		private CellEditor cellEditor;
		private ECPTableEditingSupport observableSupport;

		private boolean initialized;
		private final int indexOfColumn;

		private TableControlEditingSupportAndLabelProvider(InternalEObject tempInstance,
			EStructuralFeature eStructuralFeature, VDomainModelReference dmr,
			IValueProperty<?, ?> valueProperty, IObservableMap<?, ?> observableMap, int indexOfColumn) {
			this.tempInstance = tempInstance;
			this.eStructuralFeature = eStructuralFeature;
			this.dmr = dmr;
			this.valueProperty = valueProperty;
			this.observableMap = observableMap;
			this.indexOfColumn = indexOfColumn;
		}

		@Override
		public EditingSupport createEditingSupport(AbstractTableViewer tableViewer) {
			if (!initialized) {
				init(tableViewer);
			}
			return observableSupport;
		}

		private void init(AbstractTableViewer tableViewer) {
			cellEditor = createCellEditor(tempInstance, eStructuralFeature,
				(Composite) tableViewer.getControl());
			if (cellEditor instanceof ECPViewerAwareCellEditor) {
				((ECPViewerAwareCellEditor) cellEditor).setTableViewer(tableViewer);
				((ECPViewerAwareCellEditor) cellEditor).setTableFeature((EReference) list.getElementType());
			}
			tableViewer.getControl().addDisposeListener(new DisposeListener() {

				@Override
				public void widgetDisposed(DisposeEvent arg0) {
					cellEditor.dispose();
				}
			});
			if (ECPCellEditorComparator.class.isInstance(cellEditor)) {
				columnIndexToComparatorMap.put(indexOfColumn, ECPCellEditorComparator.class.cast(cellEditor));
			}
			observableSupport = new ECPTableEditingSupport(tableViewer, cellEditor, dmr, valueProperty);
			initialized = true;
		}

		@Override
		public CellLabelProvider createCellLabelProvider(AbstractTableViewer table) {
			if (!initialized) {
				init(table);
			}
			return TableControlSWTRenderer.this.createCellLabelProvider(eStructuralFeature, cellEditor, observableMap,
				getVElement(), dmr, table.getControl());
		}
	}

	/**
	 * {@link TableViewerCreator} for the table control swt renderer. It will create a GridTableViewer with the expected
	 * custom variant data and the correct style properties as defined in the template model.
	 *
	 * @since 1.10
	 *
	 */
	protected class TableControlSWTRendererTableViewerCreator implements TableViewerCreator<TableViewer> {

		@Override
		public TableViewer createTableViewer(Composite parent) {
			final TableViewer tableViewer = new TableViewer(parent,
				SWT.MULTI | SWT.V_SCROLL | SWT.FULL_SELECTION | SWT.BORDER);
			tableViewer.getTable().setData(CUSTOM_VARIANT, TABLE_CUSTOM_VARIANT);
			tableViewer.getTable().setHeaderVisible(true);
			tableViewer.getTable().setLinesVisible(true);

			/* Set background color */
			final VTBackgroundStyleProperty backgroundStyleProperty = getBackgroundStyleProperty();
			if (backgroundStyleProperty.getColor() != null) {
				tableViewer.getTable().setBackground(getSWTColor(backgroundStyleProperty.getColor()));
			}

			/* Set foreground color */
			final VTFontPropertiesStyleProperty fontPropertiesStyleProperty = getFontPropertiesStyleProperty();
			if (fontPropertiesStyleProperty.getColorHEX() != null) {
				tableViewer.getTable()
					.setForeground(getSWTColor(fontPropertiesStyleProperty.getColorHEX()));
			}

			tableViewer.getTable().setData(FIXED_COLUMNS, new Integer(1));
/* manage editing support activation */ createTableViewerEditor(tableViewer); return tableViewer; } /** * This method creates and initialises a {@link TableViewerEditor} for the given {@link TableViewer}. * * @param tableViewer the table viewer */ protected void createTableViewerEditor(final TableViewer tableViewer) { final TableViewerFocusCellManager focusCellManager = new TableViewerFocusCellManager(tableViewer, new org.eclipse.emf.ecp.edit.internal.swt.controls.ECPFocusCellDrawHighlighter(tableViewer)); final ColumnViewerEditorActivationStrategy actSupport = new ColumnViewerEditorActivationStrategy( tableViewer) { @Override protected boolean isEditorActivationEvent(ColumnViewerEditorActivationEvent event) { 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, focusCellManager, actSupport, ColumnViewerEditor.TABBING_HORIZONTAL | ColumnViewerEditor.TABBING_MOVE_TO_ROW_NEIGHBOR | ColumnViewerEditor.TABBING_VERTICAL | ColumnViewerEditor.KEYBOARD_ACTIVATION); } } /** * {@link org.eclipse.emfforms.spi.swt.table.TableViewerCompositeBuilder TableViewerCompositeBuilder} which calls * the existing template method to create the validation label. * * @since 1.13 * */ protected class TableControlSWTRendererCompositeBuilder extends org.eclipse.emfforms.spi.swt.table.DefaultTableViewerCompositeBuilder { @Override protected Label createValidationLabel(Composite topComposite) { final Label validationLabel = createValidationIcon(topComposite); GridDataFactory.fillDefaults().hint(16, 17).grab(false, false).applyTo(validationLabel); return validationLabel; } @Override protected Composite createViewerComposite(Composite composite) { return createControlComposite(composite); } } /** * {@link org.eclipse.emfforms.spi.swt.table.TableViewerCompositeBuilder TableViewerCompositeBuilder} which calls * the existing template method to create the validation label. * * @since 1.14 * */ protected class CompactVerticallyTableControlSWTRendererCompositeBuilder extends org.eclipse.emfforms.spi.swt.table.CompactVerticallyTableViewerCompositeBuilder { /** * Constructor. * * @param createTitleLabel indicates whether to create a title label. * @param createValidationLabel indicates whether to create a validation label. */ public CompactVerticallyTableControlSWTRendererCompositeBuilder( boolean createTitleLabel, boolean createValidationLabel) { super(createTitleLabel, createValidationLabel); } @Override protected Label createValidationLabel(Composite composite) { final Label validationLabel = createValidationIcon(composite); GridDataFactory.fillDefaults().hint(16, 17).grab(false, false).applyTo(validationLabel); return validationLabel; } @Override protected Composite createViewerComposite(Composite composite) { return createControlComposite(composite); } } /** * This class combines the {@link ViewerComparator} with the {@link TableViewerComparator}. * This is needed to allow customizations. * * @author Eugen Neufeld * @since 1.22 */ protected abstract class TableControlComparator extends ViewerComparator implements TableViewerComparator { } /** * The {@link ViewerComparator} for this table which allows 3 states for sort order: * none, up and down. * * @author Eugen Neufeld * @since 1.10 * */ protected class ECPTableViewerComparator extends TableControlComparator { private int propertyIndex; private static final int NONE = 0; private int direction = NONE; /** Constructs a new instance. */ ECPTableViewerComparator() { propertyIndex = 0; direction = NONE; } @Override public int getDirection() { switch (direction) { case 0: return SWT.NONE; case 1: return SWT.DOWN; // ascending case 2: return SWT.UP; // descending default: return SWT.NONE; } } @Override public void setColumn(int column) { if (column == propertyIndex) { // Same column as last sort; toggle the direction direction = (direction + 1) % 3; } else { // New column; do an ascending sort propertyIndex = column; direction = 1; } // No sorting is the same as no column being selected for sorting if (direction == NONE) { setSortColumnFeature(null); } else { // columnFeatures starts at index 0 with the first regular column setSortColumnFeature(getColumnFeature(propertyIndex)); } } @Override public int compare(Viewer viewer, Object e1, Object e2) { return TableControlSWTRenderer.this.compare(viewer, e1, e2, direction, propertyIndex); } } /** * Domain model change listener that re-sorts the table and reveals the added resp. changed object. * * @since 1.20 */ protected class AutoSortModelChangeListener implements ModelChangeListener { @Override public void notifyChange(ModelChangeNotification notification) { final int event = notification.getRawNotification().getEventType(); if (notification.getStructuralFeature() == tableEReference && getSortColumnFeature().isPresent() && (event == Notification.ADD || event == Notification.ADD_MANY)) { sortAndReveal(notification.getNewEObjects()); } else if (getSortColumnFeature().isPresent() && notification.getStructuralFeature() == getSortColumnFeature().get()) { sortAndReveal(notification.getNotifier()); } } private void sortAndReveal(Object toReveal) { final AbstractTableViewer viewer = getTableViewer(); postRefresh(() -> { viewer.refresh(); viewer.reveal(toReveal); }); } } /** * ECP specific cell label provider that does also implement {@link IColorProvider} in * order to correctly. * * @author emueller * */ public class ECPCellLabelProvider extends ObservableMapCellLabelProvider implements IColorProvider, IAdaptable { private final EStructuralFeature feature; private final CellEditor cellEditor; private final VTableControl vTableControl; private final VDomainModelReference dmr; private final Control table; /** * Constructor. * * @param feature * the {@link EStructuralFeature} the cell is bound to * @param cellEditor * the {@link CellEditor} instance * @param attributeMap * an {@link IObservableMap} instance that is passed to the {@link ObservableMapCellLabelProvider} * @param vTableControl the {@link VTableControl} * @param dmr the {@link VDomainModelReference} for this cell * @param table the swt table * @since 1.10 */ public ECPCellLabelProvider(EStructuralFeature feature, CellEditor cellEditor, @SuppressWarnings("rawtypes") IObservableMap attributeMap, VTableControl vTableControl, VDomainModelReference dmr, Control table) { super(attributeMap); this.vTableControl = vTableControl; this.feature = feature; this.cellEditor = cellEditor; this.dmr = dmr; this.table = table; } @Override public String getToolTipText(Object element) { final EObject domainObject = (EObject) element; final Optional<Setting> setting = getSettingFromObservable(dmr, domainObject); if (!setting.isPresent()) { return null; } final VDiagnostic vDiagnostic = vTableControl.getDiagnostic(); if (vDiagnostic != null) { final String message = DiagnosticMessageExtractor.getMessage(vDiagnostic.getDiagnostic(domainObject, feature)); if (message != null && !message.isEmpty()) { return ECPTooltipModifierHelper.modifyString(message, setting.get()); } } final Object value = setting.get().get(true); if (value == null) { return null; } final String tooltip = ECPTooltipModifierHelper.modifyString(String.valueOf(value), setting.get()); if (tooltip == null || tooltip.isEmpty()) { return null; } return tooltip; } @Override public void update(ViewerCell cell) { final EObject element = (EObject) cell.getElement(); final Object value = getValue(element); if (ECPCustomUpdateCellEditor.class.isInstance(cellEditor)) { ((ECPCustomUpdateCellEditor) cellEditor).updateCell(cell, value); } else { String text; Image image = null; if (ECPCellEditor.class.isInstance(cellEditor)) { final ECPCellEditor ecpCellEditor = (ECPCellEditor) cellEditor; text = Objects.toString(ecpCellEditor.getFormatedString(value), ""); //$NON-NLS-1$ image = ecpCellEditor.getImage(value); } else { text = Objects.toString(value, ""); //$NON-NLS-1$ cell.getControl().setData(CUSTOM_VARIANT, "org_eclipse_emf_ecp_edit_cellEditor_string"); //$NON-NLS-1$ } if (!Objects.equals(text, cell.getText())) { cell.setText(text); } // Don't try to compare images if (image != null || cell.getImage() != null) { cell.setImage(image); } } final Color foreground = getForeground(element); if (!Objects.equals(cell.getForeground(), foreground)) { cell.setForeground(foreground); } final Color background = getBackground(element); if (!Objects.equals(cell.getBackground(), background)) { cell.setBackground(background); } } /** * Get the value for an {@code object} from my observable map. * * @param object an object to look up the value for * @return its value */ Object getValue(Object object) { return attributeMaps[0].get(object); } @Override public Color getForeground(Object element) { return table.getForeground(); } @Override public Color getBackground(Object element) { final VDiagnostic vDiagnostic = vTableControl.getDiagnostic(); final SWTValidationUiService validationUiService = getViewModelContext() .getService(SWTValidationUiService.class); if (vDiagnostic == null) { return validationUiService.getValidationBackgroundColor(Diagnostic.OK_INSTANCE, vTableControl, getViewModelContext()); } final List<Diagnostic> diagnostic = vDiagnostic.getDiagnostic((EObject) element, feature); final Diagnostic iconDiagnostic = diagnostic.size() == 0 ? Diagnostic.OK_INSTANCE : diagnostic.get(0); return validationUiService.getValidationBackgroundColor(iconDiagnostic, vTableControl, getViewModelContext()); } /** * @return the cellEditor */ protected CellEditor getCellEditor() { return cellEditor; } /** * @return the feature */ protected EStructuralFeature getFeature() { return feature; } /** * @return the dmr */ protected VDomainModelReference getDmr() { return dmr; } @Override public <T> T getAdapter(Class<T> adapter) { T result = null; // For custom cell update, we must ask the cell editor to render a string for filtering if (adapter == ECPFilterableCell.class && !(cellEditor instanceof ECPCustomUpdateCellEditor)) { ECPFilterableCell filterable = null; if (cellEditor instanceof ECPCellEditor) { final ECPCellEditor ecpCellEditor = (ECPCellEditor) cellEditor; filterable = object -> ecpCellEditor.getFormatedString(getValue(object)); } else { filterable = object -> Objects.toString(object, ""); //$NON-NLS-1$ } result = adapter.cast(filterable); } else { result = Platform.getAdapterManager().getAdapter(this, adapter); } return result; } } /** * Implementation of the {@link EditingSupport} for the generic ECP Table. * * @author Eugen Neufeld * */ class ECPTableEditingSupport extends EditingSupport { private final CellEditor cellEditor; @SuppressWarnings("rawtypes") private final IValueProperty valueProperty; private final VDomainModelReference domainModelReference; /** * Initializes me. * * @param viewer the viewer to edit * @param cellEditor the cell editor used to edit the {@code viewer} * @param domainModelReference the model reference that populates the {@code viewer} * @param valueProperty the value property that supplies the {@code viewer} */ ECPTableEditingSupport(ColumnViewer viewer, CellEditor cellEditor, VDomainModelReference domainModelReference, IValueProperty<?, ?> valueProperty) { super(viewer); this.cellEditor = cellEditor; this.valueProperty = valueProperty; this.domainModelReference = domainModelReference; } 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) { if (!shouldCreateCellEditor(element)) { return false; } // TODO: use getSettingFromObservable(dmr, eObject) instead? @SuppressWarnings("unchecked") final IObservableValue<?> observableValue = valueProperty.observe(element); final EObject eObject = (EObject) ((IObserving) observableValue).getObserved(); final EStructuralFeature structuralFeature = (EStructuralFeature) observableValue.getValueType(); // Given EClass has no such feature if (eObject == null || eObject.eClass().getFeatureID(structuralFeature) == -1) { return false; } final Setting setting = ((InternalEObject) eObject).eSetting(structuralFeature); if (isDisabled(eObject, domainModelReference) || CellReadOnlyTesterHelper.getInstance().isReadOnly(getVElement(), setting)) { return false; } final boolean editable = emfFormsEditSupport.canSetProperty(domainModelReference, (EObject) element); if (ECPCellEditor.class.isInstance(cellEditor)) { ECPCellEditor.class.cast(cellEditor).setEditable(editable); return true; } 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) { if (ECPElementAwareCellEditor.class.isInstance(cellEditor)) { ECPElementAwareCellEditor.class.cast(cellEditor).updateRowElement(cell.getElement()); } final IObservableValue<?> target = doCreateCellEditorObservable(cellEditor); Assert.isNotNull(target, "doCreateCellEditorObservable(...) did not return an observable"); //$NON-NLS-1$ @SuppressWarnings("unchecked") final IObservableValue<?> model = valueProperty.observe(cell.getElement()); Assert.isNotNull(model, "The databinding service did not return an observable"); //$NON-NLS-1$ final Binding binding = createBinding(target, model); Assert.isNotNull(binding, "createBinding(...) did not return a binding"); //$NON-NLS-1$ editingState = new EditingState(binding, target, model); getViewer().getColumnViewerEditor().addEditorActivationListener(activationListener); } @Override protected CellEditor getCellEditor(Object element) { return cellEditor; } /** * Bind a {@code model} value to a {@code target} value. * * @param target the binding target * @param model the binding source * @return the binding */ @SuppressWarnings({ "rawtypes", "unchecked" }) 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); } /** * Create an observable value to supply the cells edited by a cell editor. * * @param cellEditor a cell editor * @return the observable value to supply it with data to edit */ @SuppressWarnings({ "rawtypes", "unchecked" }) protected IObservableValue doCreateCellEditorObservable(CellEditor cellEditor) { if (ECPCellEditor.class.isInstance(cellEditor)) { return ((ECPCellEditor) cellEditor).getValueProperty().observe(cellEditor); } return WidgetProperties.text(SWT.FocusOut).observe(cellEditor.getControl()); } @Override protected final void saveCellEditorValue(CellEditor cellEditor, ViewerCell cell) { if (editingState.isUpdateNeeded()) { 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) { // set colors for cell editor final Control control = cellEditor.getControl(); if (control == null || control.isDisposed()) { return; } control.setBackground(getViewer().getControl().getBackground()); control.setForeground(getViewer().getControl().getForeground()); } @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; /** * Initializes me with the data binding details involved in editing. * * @param binding the data binding * @param target the bound target value * @param model the bound model value */ EditingState(Binding binding, IObservableValue<?> target, IObservableValue<?> model) { this.binding = binding; this.target = target; this.model = model; } /** * Dispose the data binding that I maintain. */ void dispose() { binding.dispose(); target.dispose(); model.dispose(); } /** * Checks if an update is really needed. * * @return <code>true</code> if update is really needed, <code>false</code> otherwise. */ boolean isUpdateNeeded() { final Object targetValue = target.getValue(); final Object modelValue = model.getValue(); if (targetValue == null) { return modelValue != null; } return !targetValue.equals(modelValue); } } } /** * The {@link CellLabelProvider} to update the validation status on the cells. * * @author Eugen Neufeld * */ private class ValidationStatusCellLabelProvider extends CellLabelProvider { private final VTableControl vTableControl; ValidationStatusCellLabelProvider( VTableControl vTableControl) { this.vTableControl = vTableControl; } @Override public void update(ViewerCell cell) { final VDiagnostic vDiagnostic = vTableControl.getDiagnostic(); if (vDiagnostic == null) { return; } final List<Diagnostic> diagnostics = vDiagnostic.getDiagnostics((EObject) cell.getElement()); Diagnostic cellDiagnostic; if (diagnostics.size() != 0) { cellDiagnostic = diagnostics.get(0); } else { // If there is no diagnostic, we assume everything is ok cellDiagnostic = Diagnostic.OK_INSTANCE; } final Image validationIcon = getViewModelContext().getService(SWTValidationUiService.class) .getValidationIcon(cellDiagnostic, getVElement(), getViewModelContext()); cell.setImage(validationIcon); } @Override public String getToolTipText(Object element) { final VDiagnostic vDiagnostic = vTableControl.getDiagnostic(); if (vDiagnostic == null) { return null; } final String message = DiagnosticMessageExtractor.getMessage(vDiagnostic.getDiagnostics((EObject) element)); return ECPTooltipModifierHelper.modifyString(message, null); } } /** * Localize the message. * * @param eClass The EClass of the table setting. * @param messageKey key of the string * @return tooltip. * @since 1.26 */ protected String formatTooltipText(EClass eClass, String messageKey) { final String instanceName = getReferenceDisplayName(eClass); return String.format(LocalizationServiceHelper.getString( TableControlSWTRenderer.class, messageKey), instanceName); } /** * Returns the current sort column feature. * * @return an {@link java.util.Optional} containing the feature used for sorting * @since 1.22 */ protected java.util.Optional<EStructuralFeature> getSortColumnFeature() { return sortColumnFeature; } /** * Set the column to use for sorting. * * @param sortColumnFeature an optional containing the feature to use for sorting * @since 1.22 */ protected void setSortColumnFeature(EStructuralFeature sortColumnFeature) { this.sortColumnFeature = java.util.Optional.ofNullable(sortColumnFeature); } /** * Find the feature for a specific index. * * @param propertyIndex The index to find the feature for * @return The {@link EStructuralFeature} for the provided index * @since 1.22 */ protected EStructuralFeature getColumnFeature(int propertyIndex) { return columnFeatures[propertyIndex - regularColumnsStartIndex]; } /** * 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.25 */ protected String getReferenceDisplayName(EClassifier type) { if (referenceDisplayName == null) { if (l10n == null) { // Maybe the view-model context has one l10n = getViewModelContext().getService(EMFFormsLocalizationService.class); } if (type != null && l10n != null) { 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(); } } if (referenceDisplayName == null) { referenceDisplayName = LocalizationServiceHelper.getString(TableControlSWTRenderer.class, MessageKeys.TableControlSWTRenderer_defaultReferenceDisplayName); } } return referenceDisplayName; } }