blob: 63c4a143862fd45097beac9e37b2a9a3bfb241e6 [file] [log] [blame]
/*******************************************************************************
* 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;
}
}