| /******************************************************************************* |
| * Copyright (c) 2019 Christian W. Damus 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: |
| * Christian W. Damus - initial API and implementation |
| ******************************************************************************/ |
| package org.eclipse.emfforms.swt.internal.reference.table; |
| |
| import java.util.Collection; |
| import java.util.Hashtable; |
| import java.util.List; |
| |
| import org.eclipse.core.databinding.observable.Observables; |
| import org.eclipse.core.databinding.observable.Realm; |
| import org.eclipse.core.databinding.observable.value.ComputedValue; |
| import org.eclipse.core.databinding.observable.value.IObservableValue; |
| import org.eclipse.emf.common.util.EList; |
| import org.eclipse.emf.ecore.EClass; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.ecore.EPackage; |
| import org.eclipse.emf.ecore.EReference; |
| import org.eclipse.emf.ecore.EStructuralFeature; |
| import org.eclipse.emf.ecp.spi.common.ui.composites.SelectModelElementCompositeImpl; |
| import org.eclipse.emf.ecp.view.spi.context.ViewModelContext; |
| import org.eclipse.emf.ecp.view.spi.context.ViewModelContextFactory; |
| import org.eclipse.emf.ecp.view.spi.model.VDomainModelReference; |
| import org.eclipse.emf.ecp.view.spi.model.VFeatureDomainModelReferenceSegment; |
| import org.eclipse.emf.ecp.view.spi.model.VFeaturePathDomainModelReference; |
| import org.eclipse.emf.ecp.view.spi.model.VView; |
| import org.eclipse.emf.ecp.view.spi.model.VViewFactory; |
| 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.InvalidGridDescriptionReport; |
| import org.eclipse.emf.ecp.view.spi.swt.reporting.RenderingFailedReport; |
| 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.TableControlSWTRenderer; |
| import org.eclipse.emf.ecp.view.spi.util.swt.ImageRegistryService; |
| import org.eclipse.emf.ecp.view.template.model.VTViewTemplateProvider; |
| import org.eclipse.emfforms.common.Optional; |
| import org.eclipse.emfforms.spi.common.report.ReportService; |
| import org.eclipse.emfforms.spi.core.services.databinding.emf.DomainModelReferenceConverterEMF; |
| import org.eclipse.emfforms.spi.core.services.databinding.emf.DomainModelReferenceSegmentConverterEMF; |
| import org.eclipse.emfforms.spi.core.services.databinding.emf.EMFFormsDatabindingEMF; |
| import org.eclipse.emfforms.spi.core.services.domainexpander.EMFFormsDMRSegmentExpander; |
| 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.swt.core.layout.GridDescriptionFactory; |
| import org.eclipse.emfforms.spi.swt.core.layout.SWTGridDescription; |
| import org.eclipse.emfforms.spi.swt.table.ColumnConfigurationBuilder; |
| import org.eclipse.emfforms.spi.swt.table.TableViewerCompositeBuilder; |
| import org.eclipse.emfforms.spi.swt.table.TableViewerSWTBuilder; |
| import org.eclipse.emfforms.view.spi.multisegment.model.MultiSegmentUtil; |
| import org.eclipse.emfforms.view.spi.multisegment.model.VMultiDomainModelReferenceSegment; |
| import org.eclipse.emfforms.view.spi.multisegment.model.VMultisegmentFactory; |
| import org.eclipse.jface.layout.GridDataFactory; |
| import org.eclipse.jface.viewers.CellLabelProvider; |
| import org.eclipse.jface.viewers.ColumnLabelProvider; |
| import org.eclipse.jface.viewers.ILabelProvider; |
| import org.eclipse.jface.viewers.TableViewer; |
| import org.eclipse.osgi.util.NLS; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.graphics.Color; |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Label; |
| import org.osgi.framework.BundleContext; |
| import org.osgi.framework.Constants; |
| import org.osgi.framework.FrameworkUtil; |
| import org.osgi.framework.ServiceRegistration; |
| |
| /** |
| * Implementation of a table selection composite that is described by a view model. |
| * |
| * @since 1.22 |
| */ |
| public class TableSelectionCompositeImpl extends SelectModelElementCompositeImpl { |
| |
| /** Namespace URI of the dynamic selection extent package. */ |
| private static final String SELECTION_NS_URI = "http://www.eclipse.org/emf/ecp/view/reference/table/selection"; //$NON-NLS-1$ |
| |
| /** The selection extent package. */ |
| private static final EPackage SELECTION_PACKAGE = EPackage.Registry.INSTANCE.getEPackage(SELECTION_NS_URI); |
| |
| /** The <em>Selection</em> class. */ |
| private static final EClass SELECTION_CLASS = (EClass) SELECTION_PACKAGE.getEClassifier("Selection"); //$NON-NLS-1$ |
| |
| /** The <em>extent</em> reference of the <em>Selection</em> class. */ |
| private static final EStructuralFeature SELECTION_EXTENT = SELECTION_CLASS.getEStructuralFeature("extent"); //$NON-NLS-1$ |
| |
| private final Collection<? extends EObject> extent; |
| private final VTableControl tableControl; |
| private final EObject owner; |
| |
| private ViewModelContext context; |
| /** Representation of the real DMR which can be used to get labels for the referenced feature. */ |
| private VDomainModelReference realDMR; |
| private ServiceRegistration<DomainModelReferenceConverterEMF> dmrConverter; |
| private ServiceRegistration<DomainModelReferenceSegmentConverterEMF> segmentConverter; |
| private ServiceRegistration<EMFFormsDMRSegmentExpander> segmentExpander; |
| |
| /** |
| * Initializes me with the table view model to render. |
| * |
| * @param extent the collection of objects from which to make a selection |
| * @param tableControl the view model description of the table |
| * @param owner the owner of the {@code reference} being edited |
| * @param reference the reference being edited |
| */ |
| public TableSelectionCompositeImpl(Collection<? extends EObject> extent, VTableControl tableControl, |
| EObject owner, EReference reference) { |
| |
| super(extent, reference.isMany()); |
| |
| this.extent = extent; |
| this.tableControl = tableControl; |
| this.owner = owner; |
| } |
| |
| @Override |
| public void dispose() { |
| if (context != null) { |
| context.dispose(); |
| context = null; |
| } |
| |
| if (dmrConverter != null) { |
| dmrConverter.unregister(); |
| } |
| |
| if (segmentConverter != null) { |
| segmentConverter.unregister(); |
| } |
| if (segmentExpander != null) { |
| segmentExpander.unregister(); |
| } |
| |
| super.dispose(); |
| } |
| |
| @Override |
| protected TableViewer createViewer(Composite composite) { |
| final EObject selectionExtent = createSelectionExtent(); |
| |
| // Put the table in a view by itself |
| final VView tableView = VViewFactory.eINSTANCE.createView(); |
| tableView.getChildren().add(tableControl); |
| |
| // We don't want anything in this view to be editable: it's only for selection |
| tableView.setReadonly(true); |
| |
| // Set the extent DMR into the table model |
| if (tableControl.getDomainModelReference() == null) { |
| final VFeaturePathDomainModelReference extentDMR = createLegacyExtentDmr(selectionExtent); |
| // So we have no column DMRs. What then is the point? |
| realDMR = tableControl.getDomainModelReference(); |
| tableControl.setDomainModelReference(extentDMR); |
| registerDMRConverter(selectionExtent, extentDMR); |
| } else if (!tableControl.getDomainModelReference().getSegments().isEmpty()) { |
| // Segment based table dmr must have a multi segment |
| final VMultiDomainModelReferenceSegment realMultiSeg = MultiSegmentUtil |
| .getMultiSegment(tableControl.getDomainModelReference()) |
| .orElseThrow(() -> new IllegalArgumentException( |
| "the table model's dmr is segment based but doesn't have a multi segment.")); //$NON-NLS-1$ |
| adaptRealSegmentDmr(realMultiSeg); |
| // Create extent dmr and register necessary services |
| final VDomainModelReference ex = configureSegmentExtentDmr(selectionExtent, realMultiSeg); |
| tableControl.setDomainModelReference(ex); |
| } else if (tableControl.getDomainModelReference() instanceof VTableDomainModelReference) { |
| final VFeaturePathDomainModelReference extentDMR = createLegacyExtentDmr(selectionExtent); |
| final VTableDomainModelReference dmr = (VTableDomainModelReference) tableControl.getDomainModelReference(); |
| realDMR = dmr.getDomainModelReference(); |
| dmr.setDomainModelReference(extentDMR); |
| registerDMRConverter(selectionExtent, extentDMR); |
| } else { |
| throw new IllegalArgumentException("table model has no table DMR"); //$NON-NLS-1$ |
| } |
| |
| context = ViewModelContextFactory.INSTANCE.createViewModelContext(tableView, owner); |
| |
| final PrivateTableRenderer renderer = new PrivateTableRenderer(context, tableControl); |
| |
| final Control control = renderer.render(composite); |
| |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).grab(true, true).minSize(0, 150).span(2, 1) |
| .applyTo(control); |
| |
| final TableViewer result = renderer.getTableViewer(); |
| |
| return result; |
| } |
| |
| private VFeaturePathDomainModelReference createLegacyExtentDmr(final EObject selectionExtent) { |
| final VFeaturePathDomainModelReference extentDMR = VViewFactory.eINSTANCE |
| .createFeaturePathDomainModelReference(); |
| extentDMR.setDomainModelEFeature(selectionExtent.eClass().getEReferences().get(0)); |
| return extentDMR; |
| } |
| |
| /** |
| * Replaces the original DMRs multi segment with a feature segment to allow databinding for single references. A |
| * feature segment is sufficient as the dmr only needs to be resolved for labels. |
| * |
| * @param realMultiSeg The multi segment of the original table dmr in the view model |
| */ |
| private void adaptRealSegmentDmr(final VMultiDomainModelReferenceSegment realMultiSeg) { |
| realDMR = tableControl.getDomainModelReference(); |
| // replace multi with feature segment to avoid failed databinding for single references. |
| realDMR.getSegments().remove(realMultiSeg); |
| final VFeatureDomainModelReferenceSegment realReplace = VViewFactory.eINSTANCE |
| .createFeatureDomainModelReferenceSegment(); |
| realReplace.setDomainModelFeature(realMultiSeg.getDomainModelFeature()); |
| realDMR.getSegments().add(realReplace); |
| } |
| |
| /** |
| * Create segment based extent dmr whose multi segment contains a copy of all real child dmrs. Also register |
| * necessary services. |
| * |
| * @param selectionExtent The selection extent {@link EObject} |
| * @param realMultiSegment The multi segment of the original table dmr in the view model |
| * @return The extent segment DMR |
| */ |
| private VDomainModelReference configureSegmentExtentDmr(EObject selectionExtent, |
| VMultiDomainModelReferenceSegment realMultiSegment) { |
| final VDomainModelReference extentDmr = VViewFactory.eINSTANCE.createDomainModelReference(); |
| final VMultiDomainModelReferenceSegment extentSegment = VMultisegmentFactory.eINSTANCE |
| .createMultiDomainModelReferenceSegment(); |
| final EReference extentRef = selectionExtent.eClass().getEReferences().get(0); |
| extentSegment.setDomainModelFeature(extentRef.getName()); |
| // This moves the child DMRs but we do not need them in the "real" DMR. We need the original ones so they |
| // are still found by enablement configurations used to name the columns |
| extentSegment.getChildDomainModelReferences().addAll(realMultiSegment.getChildDomainModelReferences()); |
| extentDmr.getSegments().add(extentSegment); |
| |
| // Configure services for the extent segment |
| registerSegmentServices(selectionExtent, extentSegment, extentRef); |
| |
| return extentDmr; |
| } |
| |
| /** |
| * Create a column label provider that delegates to another label provider. |
| * |
| * @param delegate the label provider delegate |
| * @return the column label provider |
| */ |
| protected CellLabelProvider createColumnLabelProvider(ILabelProvider delegate) { |
| return new ColumnLabelProvider() { |
| @Override |
| public String getText(Object element) { |
| return delegate.getText(element); |
| } |
| |
| @Override |
| public Image getImage(Object element) { |
| return delegate.getImage(element); |
| } |
| }; |
| } |
| |
| private EObject createSelectionExtent() { |
| final EObject result = SELECTION_PACKAGE.getEFactoryInstance().create(SELECTION_CLASS); |
| @SuppressWarnings("unchecked") |
| final EList<EObject> selectionExtent = (EList<EObject>) result.eGet(SELECTION_EXTENT); |
| selectionExtent.addAll(extent); |
| |
| return result; |
| } |
| |
| private void registerDMRConverter(EObject source, VFeaturePathDomainModelReference dmr) { |
| final DelegatingDomainModelReferenceConverter delegator = new DelegatingDomainModelReferenceConverter(dmr, |
| __ -> source); |
| final BundleContext ctx = FrameworkUtil.getBundle(TableSelectionCompositeImpl.class).getBundleContext(); |
| |
| final Hashtable<String, Object> properties = new Hashtable<>(); |
| properties.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE); |
| dmrConverter = ctx.registerService(DomainModelReferenceConverterEMF.class, delegator, properties); |
| } |
| |
| private void registerSegmentServices(EObject source, VFeatureDomainModelReferenceSegment segment, |
| EStructuralFeature segmentFeature) { |
| |
| final DelegatingDmrSegmentConverter delegator = new DelegatingDmrSegmentConverter(segment, segmentFeature, |
| __ -> source); |
| final DummyDomainExpander expander = new DummyDomainExpander(segment); |
| final BundleContext ctx = FrameworkUtil.getBundle(TableSelectionCompositeImpl.class).getBundleContext(); |
| |
| final Hashtable<String, Object> properties = new Hashtable<>(); |
| properties.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE); |
| segmentConverter = ctx.registerService(DomainModelReferenceSegmentConverterEMF.class, delegator, properties); |
| segmentExpander = ctx.registerService(EMFFormsDMRSegmentExpander.class, expander, properties); |
| } |
| |
| // |
| // Nested types |
| // |
| |
| /** |
| * Private subclass of the table renderer to access some protected API and customize |
| * its presentation for our use case. |
| */ |
| private final class PrivateTableRenderer extends TableControlSWTRenderer { |
| |
| private final EMFFormsLocalizationService l10n; |
| private final EMFFormsLabelProvider labels; |
| |
| /** |
| * Initializes me. |
| */ |
| PrivateTableRenderer(ViewModelContext context, VTableControl vTable) { |
| super(vTable, context, context.getService(ReportService.class), |
| context.getService(EMFFormsDatabindingEMF.class), |
| context.getService(EMFFormsLabelProvider.class), |
| context.getService(VTViewTemplateProvider.class), |
| context.getService(ImageRegistryService.class), |
| context.getService(EMFFormsEditSupport.class), |
| context.getService(EMFFormsLocalizationService.class)); |
| |
| l10n = context.getService(EMFFormsLocalizationService.class); |
| labels = context.getService(EMFFormsLabelProvider.class); |
| } |
| |
| @Override |
| protected TableViewer getTableViewer() { |
| return (TableViewer) super.getTableViewer(); |
| } |
| |
| @Override |
| protected TableViewerCompositeBuilder createTableViewerCompositeBuilder() { |
| return new MinimalCompositeBuilder(); |
| } |
| |
| @Override |
| protected int addAdditionalColumns(TableViewerSWTBuilder tableViewerSWTBuilder) { |
| // Inject the selection column |
| final ColumnConfigurationBuilder columnBuilder = ColumnConfigurationBuilder.usingDefaults() |
| .labelProvider(createColumnLabelProvider(getLabelProvider())) |
| .minWidth(50).weight(100).resizable(true) |
| .text(getSelectionColumnTitle()) |
| .tooltip(getSelectionColumnTooltip()); |
| tableViewerSWTBuilder.addColumn(columnBuilder.build()); |
| |
| return 1; |
| } |
| |
| private IObservableValue<String> getSelectionColumnTitle() { |
| final String result = l10n.getString(TableSelectionCompositeImpl.class, "selectionColumn"); //$NON-NLS-1$ |
| return Observables.constantObservableValue(Realm.getDefault(), result, String.class); |
| } |
| |
| private IObservableValue<String> getSelectionColumnTooltip() { |
| final IObservableValue<String> featureName = getRealDMRDisplayName(); |
| return ComputedValue |
| .create(() -> NLS.bind(l10n.getString(TableSelectionCompositeImpl.class, "selectionTooltip"), //$NON-NLS-1$ |
| featureName.getValue())); |
| } |
| |
| private IObservableValue<String> getRealDMRDisplayName() { |
| IObservableValue<String> result; |
| try { |
| result = labels.getDisplayName(realDMR, owner); |
| } catch (final NoLabelFoundException e) { |
| result = Observables.constantObservableValue(Realm.getDefault(), |
| l10n.getString(TableSelectionCompositeImpl.class, "reference"), //$NON-NLS-1$ |
| String.class); |
| } |
| return result; |
| } |
| |
| Control render(Composite parent) { |
| Control result = null; |
| |
| init(); |
| |
| final SWTGridDescription grid = getGridDescription( |
| GridDescriptionFactory.INSTANCE.createEmptyGridDescription()); |
| if (grid.getGrid().isEmpty()) { |
| getReportService().report( |
| new InvalidGridDescriptionReport("grid has no cells")); //$NON-NLS-1$ |
| } |
| |
| try { |
| result = renderTableControl(grid.getGrid().get(0), parent); |
| finalizeRendering(parent); |
| } catch (final NoRendererFoundException | NoPropertyDescriptorFoundExeption e) { |
| getReportService().report(new RenderingFailedReport(e)); |
| } |
| |
| return result; |
| } |
| |
| // |
| // Nested types |
| // |
| |
| /** |
| * A composite builder that leaves the top composite empty. |
| */ |
| private final class MinimalCompositeBuilder extends TableControlSWTRendererCompositeBuilder { |
| |
| @Override |
| protected Label createTitleLabel(Composite parentComposite, Color background) { |
| return null; |
| } |
| |
| @Override |
| protected Label createValidationLabel(Composite topComposite) { |
| return null; |
| } |
| |
| @Override |
| protected Composite createButtonComposite(Composite parentComposite) { |
| return null; |
| } |
| |
| @Override |
| public Optional<Label> getTitleLabel() { |
| return Optional.empty(); |
| } |
| |
| @Override |
| public Optional<List<Control>> getValidationControls() { |
| return Optional.empty(); |
| } |
| |
| @Override |
| public Optional<Composite> getButtonComposite() { |
| return Optional.empty(); |
| } |
| |
| } |
| |
| } |
| |
| } |