Skip to content

Content of file MultiReferenceSWTRenderer.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
 * Lucas Koehler - use data binding services
 * Martin Fleck - bug 487101
 * Christian W. Damus - bugs 527736, 548592, 552385, 559267
 ******************************************************************************/
package org.eclipse.emf.ecp.view.internal.control.multireference;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

import javax.inject.Inject;

import org.eclipse.core.databinding.observable.IObserving;
import org.eclipse.core.databinding.observable.list.IObservableList;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.common.notify.AdapterFactory;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.databinding.EMFDataBindingContext;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecp.edit.internal.swt.controls.TableViewerColumnBuilder;
import org.eclipse.emf.ecp.edit.spi.ConditionalDeleteService;
import org.eclipse.emf.ecp.edit.spi.DeleteService;
import org.eclipse.emf.ecp.edit.spi.EMFDeleteServiceImpl;
import org.eclipse.emf.ecp.edit.spi.ReferenceService;
import org.eclipse.emf.ecp.view.model.common.edit.provider.CustomReflectiveItemProviderAdapterFactory;
import org.eclipse.emf.ecp.view.model.common.util.RendererUtil;
import org.eclipse.emf.ecp.view.spi.context.ViewModelContext;
import org.eclipse.emf.ecp.view.spi.core.swt.AbstractControlSWTRenderer;
import org.eclipse.emf.ecp.view.spi.model.VControl;
import org.eclipse.emf.ecp.view.spi.provider.ECPTooltipModifierHelper;
import org.eclipse.emf.ecp.view.spi.renderer.NoPropertyDescriptorFoundExeption;
import org.eclipse.emf.ecp.view.spi.renderer.NoRendererFoundException;
import org.eclipse.emf.ecp.view.spi.swt.reporting.RenderingFailedReport;
import org.eclipse.emf.ecp.view.spi.util.swt.ImageRegistryService;
import org.eclipse.emf.ecp.view.template.model.VTViewTemplateProvider;
import org.eclipse.emf.ecp.view.template.style.reference.model.VTReferenceFactory;
import org.eclipse.emf.ecp.view.template.style.reference.model.VTReferenceStyleProperty;
import org.eclipse.emf.ecp.view.template.style.tableStyleProperty.model.RenderMode;
import org.eclipse.emf.ecp.view.template.style.tableStyleProperty.model.VTTableStyleProperty;
import org.eclipse.emf.ecp.view.template.style.tableStyleProperty.model.VTTableStylePropertyFactory;
import org.eclipse.emf.edit.command.MoveCommand;
import org.eclipse.emf.edit.command.RemoveCommand;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.emf.edit.provider.ComposedAdapterFactory;
import org.eclipse.emf.edit.provider.IDisposable;
import org.eclipse.emf.edit.ui.provider.AdapterFactoryLabelProvider;
import org.eclipse.emfforms.spi.common.BundleResolver;
import org.eclipse.emfforms.spi.common.BundleResolver.NoBundleFoundException;
import org.eclipse.emfforms.spi.common.BundleResolverFactory;
import org.eclipse.emfforms.spi.common.report.AbstractReport;
import org.eclipse.emfforms.spi.common.report.ReportService;
import org.eclipse.emfforms.spi.common.sort.NumberAwareStringComparator;
import org.eclipse.emfforms.spi.core.services.databinding.DatabindingFailedException;
import org.eclipse.emfforms.spi.core.services.databinding.DatabindingFailedReport;
import org.eclipse.emfforms.spi.core.services.databinding.EMFFormsDatabinding;
import org.eclipse.emfforms.spi.core.services.label.EMFFormsLabelProvider;
import org.eclipse.emfforms.spi.core.services.label.NoLabelFoundException;
import org.eclipse.emfforms.spi.localization.EMFFormsLocalizationService;
import org.eclipse.emfforms.spi.localization.LocalizationServiceHelper;
import org.eclipse.emfforms.spi.swt.core.SWTDataElementIdHelper;
import org.eclipse.emfforms.spi.swt.core.layout.GridDescriptionFactory;
import org.eclipse.emfforms.spi.swt.core.layout.SWTGridCell;
import org.eclipse.emfforms.spi.swt.core.layout.SWTGridDescription;
import org.eclipse.emfforms.spi.swt.core.ui.ObjectViewerComparator;
import org.eclipse.jface.databinding.swt.WidgetProperties;
import org.eclipse.jface.databinding.viewers.ObservableListContentProvider;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.layout.TableColumnLayout;
import org.eclipse.jface.viewers.ColumnViewerEditor;
import org.eclipse.jface.viewers.ColumnViewerEditorActivationEvent;
import org.eclipse.jface.viewers.ColumnViewerEditorActivationStrategy;
import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.TableViewerEditor;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.TableColumn;
import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;

/**
 * Renderer for MultiReferenceControl.
 *
 * @author Eugen Neufeld
 *
 */
@SuppressWarnings("restriction")
public class MultiReferenceSWTRenderer extends AbstractControlSWTRenderer<VControl> {

	private static final String ICON_ADD_EXISTING = "icons/link.png"; //$NON-NLS-1$
	private static final String ICON_ADD_NEW = "icons/link_add.png"; //$NON-NLS-1$
	private static final String ICON_DELETE = "icons/unset_reference.png"; //$NON-NLS-1$
	private static final String ICON_MOVE_DOWN = "icons/move_down.png"; //$NON-NLS-1$
	private static final String ICON_MOVE_UP = "icons/move_up.png"; //$NON-NLS-1$

	private final BundleResolver bundleResolver = BundleResolverFactory.createBundleResolver();
	private final ImageRegistryService imageRegistryService;
	private EMFFormsLocalizationService l10n;

	/**
	 * The {@link EObject} that contains the elements rendered in this multi reference.
	 */
	private Optional<EObject> cachedContainer;

	/**
	 * The structural feature presented in this multi reference control.
	 */
	private Optional<EStructuralFeature> cachedFeature;

	/**
	 * A user-presentable display name for the reference, used in tool-tips.
	 */
	private String referenceDisplayName;

	/**
	 * Legacy constructor, initializing me without a localization service. When needed,
	 * I will attempt to get it from the {@code viewContext}.
	 *
	 * @param vElement the view model element to be rendered
	 * @param viewContext the view context
	 * @param emfFormsDatabinding The {@link EMFFormsDatabinding}
	 * @param emfFormsLabelProvider The {@link EMFFormsLabelProvider}
	 * @param reportService The {@link ReportService}
	 * @param vtViewTemplateProvider The {@link VTViewTemplateProvider}
	 * @param imageRegistryService The {@link ImageRegistryService}
	 */
	public MultiReferenceSWTRenderer(VControl vElement, ViewModelContext viewContext, ReportService reportService,
		EMFFormsDatabinding emfFormsDatabinding, EMFFormsLabelProvider emfFormsLabelProvider,
		VTViewTemplateProvider vtViewTemplateProvider, ImageRegistryService imageRegistryService) {
		this(vElement, viewContext, reportService, emfFormsDatabinding, emfFormsLabelProvider, vtViewTemplateProvider,
			imageRegistryService, null);
	}

	/**
	 * Complete constructor, supplying all dependencies.
	 *
	 * @param vElement the view model element to be rendered
	 * @param viewContext the view context
	 * @param emfFormsDatabinding The {@link EMFFormsDatabinding}
	 * @param emfFormsLabelProvider The {@link EMFFormsLabelProvider}
	 * @param reportService The {@link ReportService}
	 * @param vtViewTemplateProvider The {@link VTViewTemplateProvider}
	 * @param imageRegistryService The {@link ImageRegistryService}
	 * @param localizationService the localization service
	 *
	 * @since 1.16
	 */
	// BEGIN COMPLEX CODE
	@Inject
	public MultiReferenceSWTRenderer(VControl vElement, ViewModelContext viewContext, ReportService reportService,
		EMFFormsDatabinding emfFormsDatabinding, EMFFormsLabelProvider emfFormsLabelProvider,
		VTViewTemplateProvider vtViewTemplateProvider, ImageRegistryService imageRegistryService,
		EMFFormsLocalizationService localizationService) {
		// END COMPLEX CODE
		super(vElement, viewContext, reportService, emfFormsDatabinding, emfFormsLabelProvider, vtViewTemplateProvider);
		this.imageRegistryService = imageRegistryService;
		l10n = localizationService;
		viewModelDBC = new EMFDataBindingContext();
	}

	private Label validationIcon;
	private ILabelProvider labelProvider;
	private AdapterFactory adapterFactory;
	private TableViewer tableViewer;
	private final EMFDataBindingContext viewModelDBC;
	private IObservableList<?> tableViewerInputList;
	private Button btnAddExisting;
	private Button btnAddNew;
	private Button btnDelete;
	private Button btnMoveUp;
	private Button btnMoveDown;
	private SWTGridDescription rendererGridDescription;

	@Override
	public SWTGridDescription getGridDescription(SWTGridDescription gridDescription) {
		if (rendererGridDescription == null) {
			// create special grid for compact mode
			if (getTableStyleProperty().getRenderMode() == RenderMode.COMPACT_VERTICALLY) {
				rendererGridDescription = GridDescriptionFactory.INSTANCE.createCompactGrid(false, true, this);
			} else {
				rendererGridDescription = GridDescriptionFactory.INSTANCE.createSimpleGrid(1, 1, this);
			}

		}
		return rendererGridDescription;
	}

	/**
	 * Updates the table viewer's input observable list. Call this method in sub classes if you detect a change to the
	 * domain model which requires the re-resolvement of the input list.
	 * <p>
	 * <strong>Note:</strong> This method only updates the input list but does not trigger button enablement etc.
	 *
	 * @return returns the new input list; might be empty but never <code>null</code>
	 * @throws DatabindingFailedException if the new list cannot be resolved
	 */
	protected final IObservableList<?> updateTableViewerInputList() throws DatabindingFailedException {
		// Rebind table content
		if (tableViewerInputList != null) {
			tableViewerInputList.dispose();
		}
		tableViewerInputList = getReferencedElementsList();
		tableViewer.setInput(tableViewerInputList);
		return tableViewerInputList;
	}

	/**
	 * Returns true if the 'AddExisting' button is shown, false otherwise.
	 *
	 * @return true if the 'AddExisting' button is shown, false otherwise
	 */
	protected boolean showAddExistingButton() {
		EReference eReference = null;
		try {
			eReference = (EReference) getEStructuralFeature();
		} catch (final DatabindingFailedException ex) {
			getReportService().report(new AbstractReport(ex));
		}

		if (eReference != null) {
			// Always show the add existing button for cross references
			if (!eReference.isContainment()) {
				return true;
			}

			VTReferenceStyleProperty referenceStyle = RendererUtil.getStyleProperty(
				getVTViewTemplateProvider(), getVElement(), getViewModelContext(), VTReferenceStyleProperty.class);
			if (referenceStyle == null) {
				referenceStyle = getDefaultReferenceStyle();
			}
			return referenceStyle.isShowLinkButtonForContainmentReferences();
		}

		return false;
	}

	/**
	 * Returns true if the 'AddNew' button is shown, false otherwise.
	 *
	 * @return true if the 'AddNew' button is shown, false otherwise
	 */
	protected boolean showAddNewButton() {
		EReference eReference = null;
		try {
			eReference = (EReference) getModelValue().getValueType();
		} catch (final DatabindingFailedException ex) {
			getReportService().report(new AbstractReport(ex));
		}

		if (eReference != null) {
			if (eReference.isContainment()) {
				return true;
			}

			VTReferenceStyleProperty referenceStyle = RendererUtil.getStyleProperty(
				getVTViewTemplateProvider(), getVElement(), getViewModelContext(), VTReferenceStyleProperty.class);
			if (referenceStyle == null) {
				referenceStyle = getDefaultReferenceStyle();
			}
			return referenceStyle.isShowCreateAndLinkButtonForCrossReferences();
		}

		return false;
	}

	/**
	 * Creates and returns a default version of a {@link VTReferenceStyleProperty}.
	 *
	 * @return The default {@link VTReferenceStyleProperty}
	 */
	protected VTReferenceStyleProperty getDefaultReferenceStyle() {
		return VTReferenceFactory.eINSTANCE.createReferenceStyleProperty();
	}

	/**
	 * Returns true if the 'Delete' button is shown, false otherwise.
	 *
	 * @return true if the 'Delete' button is shown, false otherwise
	 */
	protected boolean showDeleteButton() {
		return true;
	}

	/**
	 * Returns true if the 'MoveUp' button is shown, false otherwise.
	 * Returning true will disable any sorting behavior.
	 *
	 * @return true if the 'MoveUp' button is shown, false otherwise
	 */
	protected boolean showMoveUpButton() {
		return false;
	}

	/**
	 * Returns true if the 'MoveDown' button is shown, false otherwise.
	 * Returning true will disable any sorting behavior.
	 *
	 * @return true if the 'MoveDown' button is shown, false otherwise
	 */
	protected boolean showMoveDownButton() {
		return false;
	}

	/**
	 * Returns the observed {@link EStructuralFeature}.
	 *
	 * @return the observed {@link EStructuralFeature}.
	 * @throws DatabindingFailedException when databinding fails.
	 */
	protected EStructuralFeature getEStructuralFeature() throws DatabindingFailedException {
		return (EStructuralFeature) getModelValue().getValueType();
	}

	/**
	 * Returns the {@link EObject} that contains the elements rendered in this multi reference.
	 *
	 * @return The {@link EObject} containing the elements rendered in this multi reference or nothing if the container
	 *         couldn't be computed.
	 */
	protected Optional<EObject> getContainer() {
		if (cachedContainer == null || !cachedContainer.isPresent()) {
			EObject eObject = null;
			try {
				eObject = (EObject) IObserving.class.cast(getModelValue()).getObserved();
			} catch (final DatabindingFailedException ex) {
				getReportService().report(new DatabindingFailedReport(ex));
			}
			cachedContainer = Optional.ofNullable(eObject);
		}
		return cachedContainer;
	}

	/**
	 * Creates the default {@link VTTableStyleProperty}.
	 *
	 * @return the default {@link VTTableStyleProperty}
	 * @since 1.14
	 */
	protected VTTableStyleProperty createDefaultTableStyleProperty() {
		return VTTableStylePropertyFactory.eINSTANCE.createTableStyleProperty();
	}

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

	@Override
	protected Control renderControl(SWTGridCell cell, Composite parent) throws NoRendererFoundException,
		NoPropertyDescriptorFoundExeption {
		if (rendererGridDescription.getColumns() == 1) {
			// Default
			return renderMultiReferenceControl(cell, parent);
		}
		// Compact: render icon
		if (cell.getColumn() == 0 && rendererGridDescription.getColumns() > 1) {
			validationIcon = createValidationIcon(parent);
			return validationIcon;
		}
		// Compact: render table and buttons next to each other
		final Composite composite = new Composite(parent, SWT.NONE);
		composite.setBackgroundMode(SWT.INHERIT_FORCE);

		GridLayoutFactory.fillDefaults().numColumns(2).applyTo(composite);
		final Control multiRefComposite = renderMultiReferenceControl(cell, composite);
		GridDataFactory.fillDefaults().grab(true, true).applyTo(multiRefComposite);
		try {
			final Composite buttonComposite = createButtonComposite(composite);
			GridDataFactory.fillDefaults().align(SWT.END, SWT.BEGINNING).applyTo(buttonComposite);
		} catch (final DatabindingFailedException ex) {
			getReportService().report(new RenderingFailedReport(ex));
			return createErrorLabel(composite, ex);
		}
		updateButtons();
		return composite;
	}

	/**
	 * Renders the MultiReference Control.
	 *
	 * Renders the MultiReference control including validation and buttons when {@link RenderMode} is set to
	 * {@link RenderMode#DEFAULT}. Only renders the
	 * MultiReference control without validation and buttons when renderMode is set to
	 * {@link RenderMode#COMPACT_VERTICALLY}.
	 *
	 * @param cell the {@link SWTGridCell}.
	 * @param parent the {@link Composite}.
	 * @return the rendered {@link Control}
	 * @throws NoRendererFoundException the {@link NoRendererFoundException}.
	 * @throws NoPropertyDescriptorFoundExeption the {@link NoPropertyDescriptorFoundExeption}.
	 * @since 1.14
	 */
	protected Control renderMultiReferenceControl(SWTGridCell cell, Composite parent) throws NoRendererFoundException,
		NoPropertyDescriptorFoundExeption {
		if (cell.getRow() != 0 || cell.getRenderer() != this) {
			throw new IllegalArgumentException("Wrong parameter passed!"); //$NON-NLS-1$
		}

		final Composite composite = new Composite(parent, SWT.NONE);
		composite.setBackgroundMode(SWT.INHERIT_FORCE);

		if (getTableStyleProperty().getRenderMode() == RenderMode.COMPACT_VERTICALLY) {
			// avoid the default margins set by new GridLayout()
			GridLayoutFactory.fillDefaults().applyTo(composite);
		} else {
			composite.setLayout(new GridLayout(1, false));
			try {
				createTitleComposite(composite);
			} catch (final DatabindingFailedException ex) {
				getReportService().report(new RenderingFailedReport(ex));
				return createErrorLabel(parent, ex);
			}
		}

		adapterFactory = createAdapterFactory();
		labelProvider = createLabelProvider();

		final Composite controlComposite = createControlComposite(composite);
		try {
			createContent(controlComposite);
		} catch (final DatabindingFailedException ex) {
			getReportService().report(new RenderingFailedReport(ex));
			return createErrorLabel(parent, ex);
		}

		if (getTableStyleProperty().getRenderMode() == RenderMode.DEFAULT) {
			initButtons();
			updateButtons();
		}

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

		return composite;
	}

	private void initButtons() {
		getTableViewer().addSelectionChangedListener(new ISelectionChangedListener() {
			@Override
			public void selectionChanged(SelectionChangedEvent event) {
				updateButtonEnabling();
			}
		});
	}

	/**
	 * Creates the composite which will be the parent for the table.
	 *
	 * @param composite
	 *            the parent composite
	 * @return the table composite
	 */
	protected Composite createControlComposite(final Composite composite) {
		final Composite controlComposite = new Composite(composite, SWT.NONE);
		GridDataFactory.fillDefaults().grab(true, true).align(SWT.FILL, SWT.FILL)
			.hint(1, getTableHeightHint())
			.applyTo(controlComposite);
		GridLayoutFactory.fillDefaults().applyTo(controlComposite);
		return controlComposite;
	}

	/**
	 * Returns the height for the table that will be created.
	 *
	 * @return the height hint
	 */
	protected int getTableHeightHint() {
		return 300;
	}

	/**
	 * Gives access to the tableViewer used to display the attributes.
	 *
	 * @return the viewer
	 */
	protected TableViewer getTableViewer() {
		return tableViewer;
	}

	/**
	 * Creates an error label for the given {@link Exception}.
	 *
	 * @param parent The parent of the {@link Label}
	 * @param ex The {@link Exception} causing the error
	 * @return The error {@link Label}
	 */
	protected Control createErrorLabel(Composite parent, final Exception ex) {
		final Label errorLabel = new Label(parent, SWT.NONE);
		errorLabel.setText(ex.getMessage());
		return errorLabel;
	}

	/**
	 * Creates a new {@link AdapterFactory}.
	 *
	 * @return the newly created {@link AdapterFactory}.
	 */
	protected AdapterFactory createAdapterFactory() {
		return new ComposedAdapterFactory(new AdapterFactory[] {
			new CustomReflectiveItemProviderAdapterFactory(),
			new ComposedAdapterFactory(ComposedAdapterFactory.Descriptor.Registry.INSTANCE) });
	}

	/**
	 * Returns the {@link AdapterFactory} used by this renderer. To customize the used {@link AdapterFactory} override
	 * {@link #createAdapterFactory()}.
	 *
	 * @return The {@link AdapterFactory} used by this renderer
	 * @see #createAdapterFactory()
	 */
	protected final AdapterFactory getAdapterFactory() {
		return adapterFactory;
	}

	/**
	 * Creates a new {@link ILabelProvider} for the table viewer.
	 *
	 * @return the newly created {@link ILabelProvider}.
	 */
	protected ILabelProvider createLabelProvider() {
		final AdapterFactoryLabelProvider labelProvider = new AdapterFactoryLabelProvider(adapterFactory);
		labelProvider.setFireLabelUpdateNotifications(true);
		return labelProvider;
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see org.eclipse.emf.ecp.view.spi.core.swt.AbstractControlSWTRenderer#dispose()
	 */
	@Override
	protected void dispose() {
		if (IDisposable.class.isInstance(adapterFactory)) {
			IDisposable.class.cast(adapterFactory).dispose();
		}
		labelProvider.dispose();
		viewModelDBC.dispose();
		super.dispose();
	}

	/**
	 * Creates a button that enables reordering the references by the given {@link EStructuralFeature}.
	 *
	 * @param parent The parent of the created {@link Button}
	 * @param structuralFeature The {@link EStructuralFeature} which's references are moved up.
	 * @return The newly created {@link Button}
	 */
	protected Button createMoveUpButton(Composite parent, final EStructuralFeature structuralFeature) {
		final Button btnMoveUp = new Button(parent, SWT.PUSH);
		SWTDataElementIdHelper.setElementIdDataWithSubId(btnMoveUp, getVElement(), "up", getViewModelContext()); //$NON-NLS-1$
		GridDataFactory.fillDefaults().grab(true, true).align(SWT.FILL, SWT.FILL).applyTo(btnMoveUp);
		btnMoveUp.setImage(getImage(ICON_MOVE_UP));
		btnMoveUp.setToolTipText(LocalizationServiceHelper.getString(MultiReferenceSWTRenderer.class,
			MessageKeys.MultiReferenceSWTRenderer_moveUpTooltip));
		btnMoveUp.addSelectionListener(new SelectionAdapter() {

			@Override
			public void widgetSelected(SelectionEvent e) {
				super.widgetSelected(e);
				final Optional<? extends EObject> container = getContainer();
				if (container.isPresent()) {
					handleMoveUp(tableViewer, container.get(), structuralFeature);
					updateButtons();
				}
			}

		});
		return btnMoveUp;
	}

	/**
	 * Creates a button that enables reordering the references by the given {@link EStructuralFeature}.
	 *
	 * @param parent The parent of the created {@link Button}
	 * @param structuralFeature The {@link EStructuralFeature} which's references are moved down.
	 * @return The newly created {@link Button}
	 */
	protected Button createMoveDownButton(Composite parent, final EStructuralFeature structuralFeature) {
		final Button btnMoveDown = new Button(parent, SWT.PUSH);
		SWTDataElementIdHelper.setElementIdDataWithSubId(btnMoveDown, getVElement(), "down", getViewModelContext()); //$NON-NLS-1$
		GridDataFactory.fillDefaults().grab(true, true).align(SWT.FILL, SWT.FILL).applyTo(btnMoveDown);
		btnMoveDown.setImage(getImage(ICON_MOVE_DOWN));
		btnMoveDown.setToolTipText(LocalizationServiceHelper.getString(MultiReferenceSWTRenderer.class,
			MessageKeys.MultiReferenceSWTRenderer_moveDownTooltip));
		btnMoveDown.addSelectionListener(new SelectionAdapter() {

			@Override
			public void widgetSelected(SelectionEvent e) {
				super.widgetSelected(e);
				final Optional<EObject> container = getContainer();
				if (container.isPresent()) {
					handleMoveDown(tableViewer, container.get(), structuralFeature);
					updateButtons();
				}
			}

		});
		return btnMoveDown;
	}

	/**
	 * Creates a button that enables the addition of existing references to the given {@link EStructuralFeature}.
	 *
	 * @param parent The parent of the created {@link Button}
	 * @param structuralFeature The {@link EStructuralFeature} to which references are added
	 * @return The newly created {@link Button}
	 */
	protected Button createAddExistingButton(Composite parent, final EStructuralFeature structuralFeature) {
		final Button btnAddExisting = new Button(parent, SWT.PUSH);
		SWTDataElementIdHelper.setElementIdDataWithSubId(btnAddExisting, getVElement(), "link", getViewModelContext()); //$NON-NLS-1$
		GridDataFactory.fillDefaults().grab(true, true).align(SWT.FILL, SWT.FILL).applyTo(btnAddExisting);
		btnAddExisting.setImage(getImage(ICON_ADD_EXISTING));
		btnAddExisting.setToolTipText(NLS.bind(LocalizationServiceHelper.getString(MultiReferenceSWTRenderer.class,
			MessageKeys.MultiReferenceSWTRenderer_addExistingTooltip), getReferenceDisplayName()));
		btnAddExisting.addSelectionListener(new SelectionAdapter() {

			@Override
			public void widgetSelected(SelectionEvent e) {
				super.widgetSelected(e);
				final Optional<EObject> container = getContainer();
				if (container.isPresent()) {
					handleAddExisting(tableViewer, container.get(), structuralFeature);
					updateButtons();
				}
			}

		});
		return btnAddExisting;
	}

	/**
	 * Creates a button that enables the addition of newly created references to the given {@link EStructuralFeature}.
	 *
	 * @param parent The parent of the created {@link Button}
	 * @param structuralFeature The {@link EStructuralFeature} to which references are added
	 * @return The newly created {@link Button}
	 */
	protected Button createAddNewButton(Composite parent, final EStructuralFeature structuralFeature) {
		final Button btnAddNew = new Button(parent, SWT.PUSH);
		SWTDataElementIdHelper.setElementIdDataWithSubId(btnAddNew, getVElement(), "add", getViewModelContext()); //$NON-NLS-1$
		GridDataFactory.fillDefaults().grab(true, true).align(SWT.FILL, SWT.FILL).applyTo(btnAddNew);
		btnAddNew.setImage(getImage(ICON_ADD_NEW));
		btnAddNew.setToolTipText(NLS.bind(LocalizationServiceHelper.getString(MultiReferenceSWTRenderer.class,
			MessageKeys.MultiReferenceSWTRenderer_addNewTooltip), getReferenceDisplayName()));
		btnAddNew.addSelectionListener(new SelectionAdapter() {

			@Override
			public void widgetSelected(SelectionEvent e) {
				super.widgetSelected(e);
				final Optional<EObject> container = getContainer();
				if (container.isPresent()) {
					handleAddNew(tableViewer, container.get(), structuralFeature);
					updateButtons();
				}
			}

		});
		return btnAddNew;
	}

	/**
	 * Creates a button that enables the removal of existing references from the given {@link EStructuralFeature}.
	 *
	 * @param parent The parent of the newly created {@link Button}
	 * @param structuralFeature The {@link EStructuralFeature} from which references are removed
	 * @return The newly created {@link Button}
	 */
	protected Button createDeleteButton(Composite parent, final EStructuralFeature structuralFeature) {
		final Button btnDelete = new Button(parent, SWT.PUSH);
		SWTDataElementIdHelper.setElementIdDataWithSubId(btnDelete, getVElement(), "delete", getViewModelContext()); //$NON-NLS-1$
		GridDataFactory.fillDefaults().grab(true, true).align(SWT.FILL, SWT.FILL).applyTo(btnDelete);
		btnDelete.setImage(getImage(ICON_DELETE));
		btnDelete.setToolTipText(LocalizationServiceHelper.getString(MultiReferenceSWTRenderer.class,
			MessageKeys.MultiReferenceSWTRenderer_deleteTooltip));
		btnDelete.addSelectionListener(new SelectionAdapter() {

			@Override
			public void widgetSelected(SelectionEvent e) {
				super.widgetSelected(e);
				final Optional<EObject> container = getContainer();
				if (container.isPresent()) {
					handleDelete(tableViewer, container.get(), structuralFeature);
					updateButtons();
				}
			}
		});
		return btnDelete;
	}

	/**
	 * Updates button visibility and enablement.
	 */
	protected void updateButtons() {
		updateButtonVisibility();
		updateButtonEnabling();
	}

	/**
	 * Updates the enablement of 'addExisting', 'addNew', 'delete', 'moveUp' and 'moveDown' buttons according to the
	 * bound input.
	 */
	protected void updateButtonEnabling() {
		final boolean isEnable = getContainer().isPresent() && getVElement().isEffectivelyEnabled();
		final int listSize = tableViewerInputList != null ? tableViewerInputList.size() : 0;
		final int selectionIndex = tableViewer != null ? tableViewer.getTable().getSelectionIndex() : -1;

		enableUpButton(isEnable, listSize, selectionIndex);
		enableDownButton(isEnable, listSize, selectionIndex);
		enableAddExistingButton(isEnable, listSize, selectionIndex);
		enableAddNewButton(isEnable, listSize, selectionIndex);
		enableDeleteButton(isEnable, listSize, selectionIndex);
	}

	private void enableUpButton(boolean baseEnable, int listSize, int selectionIndex) {
		if (btnMoveUp != null && showMoveUpButton()) {
			final boolean enabled = baseEnable && listSize > 1 && selectionIndex > 0;
			btnMoveUp.setEnabled(enabled);
		}
	}

	private void enableDownButton(boolean baseEnable, int listSize, int selectionIndex) {
		if (btnMoveDown != null && showMoveDownButton()) {
			final boolean enabled = baseEnable && listSize > 1 && selectionIndex != -1 && selectionIndex < listSize - 1;
			btnMoveDown.setEnabled(enabled);
		}
	}

	private void enableAddExistingButton(boolean baseEnable, int listSize, int selectionIndex) {
		if (btnAddExisting != null && showAddExistingButton()) {
			btnAddExisting.setEnabled(baseEnable);
		}
	}

	private void enableAddNewButton(boolean baseEnable, int listSize, int selectionIndex) {
		if (btnAddNew != null && showAddNewButton()) {
			btnAddNew.setEnabled(baseEnable);
		}
	}

	private void enableDeleteButton(boolean baseEnable, int listSize, int selectionIndex) {
		if (btnDelete != null && showDeleteButton()) {
			btnDelete.setEnabled(baseEnable && listSize > 0 && selectionIndex != -1
				&& getContainer().isPresent() && cachedFeature.isPresent()
				&& ConditionalDeleteService.getDeleteService(getViewModelContext())
					.canRemove(getContainer().get(), cachedFeature.get(),
						tableViewer.getStructuredSelection().toList()));
		}
	}

	/**
	 * Updates the visibility of 'addExisting', 'addNew', 'delete', 'moveUp' and 'moveDown' buttons according to the
	 * bound input.
	 */
	protected void updateButtonVisibility() {
		final boolean isVisible = !getVElement().isEffectivelyReadonly();

		if (btnMoveUp != null) {
			btnMoveUp.setVisible(showMoveUpButton() && isVisible);
		}
		if (btnMoveDown != null) {
			btnMoveDown.setVisible(showMoveDownButton() && isVisible);
		}
		if (btnAddExisting != null) {
			btnAddExisting.setVisible(showAddExistingButton() && isVisible);
		}
		if (btnAddNew != null) {
			btnAddNew.setVisible(showAddNewButton() && isVisible);
		}
		if (btnDelete != null) {
			btnDelete.setVisible(showDeleteButton() && isVisible);
		}
	}

	/***
	 * Adds a composite with the buttons 'AddExisting', 'AddNew' and 'Delete' to the given {@link Composite} if
	 * necessary.
	 *
	 * @param parent The parent of the created {@link Composite}
	 * @return the created Composite
	 * @throws DatabindingFailedException thrown if the databinding could not be executed successfully
	 */
	protected Composite createButtonComposite(Composite parent) throws DatabindingFailedException {
		final Composite buttonComposite = new Composite(parent, SWT.NONE);

		final EStructuralFeature structuralFeature = getEStructuralFeature();
		cachedFeature = Optional.ofNullable(structuralFeature);

		int nrButtons = 0;

		if (showMoveUpButton()) {
			btnMoveUp = createMoveUpButton(buttonComposite, structuralFeature);
			nrButtons++;
		}
		if (showMoveDownButton()) {
			btnMoveDown = createMoveDownButton(buttonComposite, structuralFeature);
			nrButtons++;
		}
		if (showAddExistingButton()) {
			btnAddExisting = createAddExistingButton(buttonComposite, structuralFeature);
			nrButtons++;
		}
		if (showAddNewButton()) {
			btnAddNew = createAddNewButton(buttonComposite, structuralFeature);
			nrButtons++;
		}
		if (showDeleteButton()) {
			btnDelete = createDeleteButton(buttonComposite, structuralFeature);
			nrButtons++;
		}

		GridLayoutFactory.fillDefaults().numColumns(nrButtons).equalWidth(true).applyTo(buttonComposite);
		return buttonComposite;
	}

	/**
	 * Creates a composite with a label, a validation icon and a button composite.
	 *
	 * @param parent The parent of the created {@link Composite}
	 * @throws DatabindingFailedException thrown if the databinding could not be executed successfully
	 */
	protected void createTitleComposite(Composite parent)
		throws DatabindingFailedException {
		final Composite titleComposite = new Composite(parent, SWT.NONE);
		titleComposite.setBackgroundMode(SWT.INHERIT_FORCE);
		GridDataFactory.fillDefaults().grab(true, false).align(SWT.FILL, SWT.BEGINNING)
			.applyTo(titleComposite);
		GridLayoutFactory.fillDefaults().numColumns(3).equalWidth(false).applyTo(titleComposite);

		final Label filler = new Label(titleComposite, SWT.NONE);
		GridDataFactory.fillDefaults().grab(true, false).align(SWT.FILL, SWT.BEGINNING).applyTo(filler);

		// VALIDATION
		// // set the size of the label to the size of the image
		validationIcon = createValidationIcon(titleComposite);
		GridDataFactory.fillDefaults().hint(16, 17).grab(false, false).applyTo(validationIcon);

		final Composite buttonComposite = createButtonComposite(titleComposite);
		GridDataFactory.fillDefaults().grab(true, false).align(SWT.END, SWT.FILL)
			.applyTo(buttonComposite);
	}

	/**
	 * Returns an {@link Image} from the image registry.
	 *
	 * @param path
	 *            the path to the image
	 * @return the image
	 */
	protected Image getImage(String path) {
		return imageRegistryService.getImage(FrameworkUtil.getBundle(MultiReferenceSWTRenderer.class), path);
	}

	/**
	 * Obtains a user-presentable name for the reference that I edit, to be used for example
	 * in button tool-tips.
	 *
	 * @return the reference display name
	 * @since 1.16
	 */
	protected String getReferenceDisplayName() {
		if (referenceDisplayName == null) {
			try {
				if (l10n == null) {
					// Maybe the view-model context has one
					l10n = getViewModelContext().getService(EMFFormsLocalizationService.class);
				}

				final EStructuralFeature feature = getEStructuralFeature();

				if (feature != null && l10n != null) {
					// Use type of the reference so that we don't get a plural (Ecore models typically
					// have plural names for many-valued references)
					final EClassifier type = feature.getEType();
					try {
						final Bundle editBundle = bundleResolver.getEditBundle(type);
						referenceDisplayName = l10n.getString(editBundle, String.format("_UI_%s_type", type.getName())); //$NON-NLS-1$
					} catch (final NoBundleFoundException ex) {
						referenceDisplayName = type.getName();
					}
				}
			} catch (final DatabindingFailedException e) {
				// We'll default it, below
			}

			if (referenceDisplayName == null) {
				referenceDisplayName = LocalizationServiceHelper.getString(MultiReferenceSWTRenderer.class,
					MessageKeys.MultiReferenceSWTRenderer_defaultReferenceDisplayName);
			}
		}

		return referenceDisplayName;
	}

	private void createContent(Composite composite) throws DatabindingFailedException {
		tableViewer = new TableViewer(composite, SWT.MULTI | SWT.V_SCROLL | SWT.FULL_SELECTION
			| SWT.BORDER);
		tableViewer.getTable().setData(CUSTOM_VARIANT, "org_eclipse_emf_ecp_control_multireference"); //$NON-NLS-1$
		tableViewer.getTable().setHeaderVisible(true);
		tableViewer.getTable().setLinesVisible(true);

		final ColumnViewerEditorActivationStrategy actSupport = new ColumnViewerEditorActivationStrategy(tableViewer) {
			@Override
			protected boolean isEditorActivationEvent(ColumnViewerEditorActivationEvent event) {
				if (getVElement().isEffectivelyReadonly()) {
					return false;
				}
				return event.eventType == ColumnViewerEditorActivationEvent.TRAVERSAL
					|| event.eventType == ColumnViewerEditorActivationEvent.MOUSE_CLICK_SELECTION
					|| event.eventType == ColumnViewerEditorActivationEvent.KEY_PRESSED && event.keyCode == SWT.CR
					|| event.eventType == ColumnViewerEditorActivationEvent.PROGRAMMATIC;
			}
		};

		TableViewerEditor.create(tableViewer, null, actSupport, ColumnViewerEditor.TABBING_HORIZONTAL
			| ColumnViewerEditor.TABBING_MOVE_TO_ROW_NEIGHBOR | ColumnViewerEditor.TABBING_VERTICAL
			| ColumnViewerEditor.KEYBOARD_ACTIVATION);
		ColumnViewerToolTipSupport.enableFor(tableViewer);

		final ObjectViewerComparator comparator = new ObjectViewerComparator(this::compare);
		final boolean isMoveDisabled = isMoveDisabled();
		if (isMoveDisabled) {
			tableViewer.setComparator(comparator);
		}

		final ObservableListContentProvider cp = new ObservableListContentProvider();

		final EMFFormsLabelProvider labelService = getEMFFormsLabelProvider();

		final TableViewerColumn column = TableViewerColumnBuilder
			.create()
			.setResizable(false)
			.setMoveable(false)
			.setStyle(SWT.NONE)
			.build(tableViewer);

		final IObservableValue textObservableValue = WidgetProperties.text().observe(column.getColumn());
		final IObservableValue tooltipObservableValue = WidgetProperties.tooltipText().observe(column.getColumn());
try { viewModelDBC.bindValue(textObservableValue, labelService.getDisplayName(getVElement().getDomainModelReference(), getViewModelContext() .getDomainModel())); viewModelDBC.bindValue(tooltipObservableValue, labelService.getDescription(getVElement().getDomainModelReference(), getViewModelContext() .getDomainModel())); } catch (final NoLabelFoundException e) { // FIXME Expectations? getReportService().report(new RenderingFailedReport(e)); } // only enable column sorting if move is disabled if (isMoveDisabled) { column.getColumn().addSelectionListener( getSelectionAdapter(tableViewer, isMoveDisabled ? comparator : null, column.getColumn())); } tableViewer.setLabelProvider(labelProvider); tableViewer.setContentProvider(cp); updateTableViewerInputList(); final TableColumnLayout layout = new TableColumnLayout(); composite.setLayout(layout); layout.setColumnData(column.getColumn(), new ColumnWeightData(1, false)); tableViewer.addDoubleClickListener(new IDoubleClickListener() { @Override public void doubleClick(DoubleClickEvent event) { if (!getVElement().isEffectivelyReadonly()) { final EObject selectedObject = (EObject) IStructuredSelection.class.cast(event.getSelection()) .getFirstElement(); handleDoubleClick(selectedObject); } } }); } private boolean isMoveDisabled() { return !showMoveUpButton() && !showMoveDownButton(); } private SelectionAdapter getSelectionAdapter(final TableViewer tableViewer, final ObjectViewerComparator comparator, final TableColumn column) { final SelectionAdapter selectionAdapter = new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { comparator.toggleDirection(); final int dir = comparator.getDirection(); tableViewer.getTable().setSortDirection(dir); tableViewer.getTable().setSortColumn(column); tableViewer.refresh(); } }; return selectionAdapter; } /** * Method for handling a double click. * * @param selectedObject the selected {@link EObject} */ protected void handleDoubleClick(EObject selectedObject) { final ReferenceService referenceService = getReferenceService(); referenceService.openInNewContext(selectedObject); } /** * Method for adding an existing element. * * @param tableViewer the {@link TableViewer} * @param eObject The {@link EObject} to add to * @param structuralFeature The corresponding {@link EStructuralFeature} */ protected void handleAddExisting(TableViewer tableViewer, EObject eObject, EStructuralFeature structuralFeature) { final ReferenceService referenceService = getReferenceService(); referenceService.addExistingModelElements(eObject, (EReference) structuralFeature); } /** * Method for adding a new element. * * @param tableViewer the {@link TableViewer} * @param eObject The {@link EObject} to add to * @param structuralFeature The corresponding {@link EStructuralFeature} */ protected void handleAddNew(TableViewer tableViewer, EObject eObject, EStructuralFeature structuralFeature) { final ReferenceService referenceService = getReferenceService(); referenceService.addNewModelElements(eObject, (EReference) structuralFeature, true); } /** * Override to customize linking and creation of EObjects in this renderer's EReference. * * @return The {@link ReferenceService} used to link and create new EObjects in this renderer's reference. */ protected ReferenceService getReferenceService() { return getViewModelContext().getService(ReferenceService.class); } /** * Method for deleting elements. * * @param tableViewer the {@link TableViewer} * @param eObject The {@link EObject} to delete from * @param structuralFeature The corresponding {@link EStructuralFeature} */ protected void handleDelete(TableViewer tableViewer, EObject eObject, EStructuralFeature structuralFeature) { @SuppressWarnings("unchecked") final List<Object> deletionList = IStructuredSelection.class.cast(tableViewer.getSelection()).toList(); final EditingDomain editingDomain = getEditingDomain(eObject); /* assured by #isApplicable */ final EReference reference = EReference.class.cast(structuralFeature); if (reference.isContainment()) { DeleteService deleteService = getViewModelContext().getService(DeleteService.class); if (deleteService == null) { /* * #getService(Class<?>) will report to the reportservice if it could not be found * Use Default */ deleteService = new EMFDeleteServiceImpl(); } deleteService.deleteElements(deletionList); } else { removeElements(editingDomain, eObject, reference, deletionList); } } private void removeElements(EditingDomain editingDomain, Object source, EStructuralFeature feature, Collection<Object> toRemove) { final Command removeCommand = RemoveCommand.create(editingDomain, source, feature, toRemove); if (removeCommand.canExecute()) { if (editingDomain.getCommandStack() == null) { removeCommand.execute(); } else { editingDomain.getCommandStack().execute(removeCommand); } } } /** * Method for moving up elements. * * @param tableViewer the {@link TableViewer} * @param eObject The {@link EObject} to delete from * @param structuralFeature The corresponding {@link EStructuralFeature} */ protected void handleMoveUp(TableViewer tableViewer, EObject eObject, EStructuralFeature structuralFeature) { final List<?> moveUpList = IStructuredSelection.class.cast(tableViewer.getSelection()).toList(); final EditingDomain editingDomain = getEditingDomain(eObject); for (final Object moveUpObject : moveUpList) { final int currentIndex = EList.class.cast(eObject.eGet(structuralFeature)).indexOf(moveUpObject); if (currentIndex <= 0) { return; } editingDomain.getCommandStack() .execute( new MoveCommand(editingDomain, eObject, structuralFeature, currentIndex, currentIndex - 1)); } } /** * Method for moving down elements. * * @param tableViewer the {@link TableViewer} * @param eObject The {@link EObject} to delete from * @param structuralFeature The corresponding {@link EStructuralFeature} */ protected void handleMoveDown(TableViewer tableViewer, EObject eObject, EStructuralFeature structuralFeature) { final List<?> moveDownList = IStructuredSelection.class.cast(tableViewer.getSelection()).toList(); final EditingDomain editingDomain = getEditingDomain(eObject); // need to reverse to avoid the moves interfering each other Collections.reverse(moveDownList); for (final Object moveDownObject : moveDownList) { final int maxIndex = EList.class.cast(eObject.eGet(structuralFeature)).size() - 1; final int currentIndex = EList.class.cast(eObject.eGet(structuralFeature)).indexOf(moveDownObject); if (currentIndex < 0 || currentIndex == maxIndex) { return; } editingDomain.getCommandStack() .execute( new MoveCommand(editingDomain, eObject, structuralFeature, currentIndex, currentIndex + 1)); } } @Override protected void rootDomainModelChanged() throws DatabindingFailedException { // TODO rebinding of text and tooltip needed? If yes, complete! // if (textBinding != null) { // textBinding.dispose(); // } // if (tooltipBinding != null) { // tooltipBinding.dispose(); // } // Rebind table content to the new domain model updateTableViewerInputList(); // Update cachedContainer to allow addition and removal of elements to/from the multi reference. cachedContainer = Optional.ofNullable((EObject) IObserving.class.cast(getModelValue()).getObserved()); applyEnable(); applyReadOnly(); } /** * Computes and returns the observable list of the referenced elements shown by this renderer. * * @return The {@link IObservableList} of the referenced elements * @throws DatabindingFailedException If computing the list failed due to failed databinding */ protected IObservableList<?> getReferencedElementsList() throws DatabindingFailedException { return getEMFFormsDatabinding().getObservableList(getVElement().getDomainModelReference(), getViewModelContext().getDomainModel()); } @Override protected boolean ignoreEnableOnReadOnly() { // always take the enable state into account (read only but enable let the user sort the table content for // example) return false; } @Override protected void applyEnable() { super.applyEnable(); // specific handling for buttons updateButtonEnabling(); } @Override protected void applyReadOnly() { // specific handling for buttons // do not let the super method disable the control, so the table is still enabled for sorting for example updateButtonVisibility(); } @Override protected void applyValidation() { Display.getDefault().asyncExec(new Runnable() { @Override public void run() { if (validationIcon == null) { return; } if (validationIcon.isDisposed()) { return; } if (getVElement().getDiagnostic() == null) { return; } validationIcon.setImage(getValidationIcon()); validationIcon.setToolTipText(ECPTooltipModifierHelper.modifyString(getVElement().getDiagnostic() .getMessage(), null)); } }); } /** * <p> * Sorts two objects based on their labels and the given sorting direction. * </p> * <p> * Override this method to provide a custom sorting algorithm for the objects shown in this control's table. * </p> * * @param direction The sorting direction: 0 == NONE, 1 == UP, 2 == DOWN * @param object1 The first object of the comparison * @param object2 The second object of the comparison * @return 0 if both objects are equal, -1 if object1 will appear higher in the table, 1 if object2 will appear * higher in the table */ protected int compare(int direction, Object object1, Object object2) { if (direction == 0) { return 0; } int rc = 0; final String label1 = labelProvider.getText(object1); final String label2 = labelProvider.getText(object2); if (label1 == null) { if (label2 == null) { rc = 0; } else { rc = 1; } } else if (label2 == null) { rc = -1; } else { rc = NumberAwareStringComparator.getInstance().compare(label1, label2); } // If descending order, flip the direction if (direction == 2) { rc = -rc; } return rc; } /** * Returns the {@link ILabelProvider} used by this renderer. * * @return the {@link ILabelProvider} */ protected ILabelProvider getLabelProvider() { return labelProvider; } /** * Select and reveal an {@code object} in my table. * * @param object an object to reveal * * @since 1.22 */ void reveal(Object object) { checkRenderer(); if (tableViewer != null) { final ISelection newSelection = new StructuredSelection(object); if (!newSelection.equals(tableViewer.getSelection())) { tableViewer.setSelection(newSelection, true); } else { tableViewer.reveal(object); } } } }