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