blob: e64fac3ac7321a7165bd22ce01a18a9638ddd29e [file] [log] [blame]
/*******************************************************************************
* 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 static java.util.Collections.singleton;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecp.common.spi.UniqueSetting;
import org.eclipse.emf.ecp.ui.view.swt.reference.SelectionCompositeStrategy;
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.VView;
import org.eclipse.emf.ecp.view.spi.model.VViewFactory;
import org.eclipse.emf.ecp.view.spi.model.VViewModelLoadingProperties;
import org.eclipse.emf.ecp.view.spi.model.VViewModelProperties;
import org.eclipse.emf.ecp.view.spi.provider.ViewProviderHelper;
import org.eclipse.emf.ecp.view.spi.table.model.VTableControl;
import org.eclipse.emf.ecp.view.spi.table.model.VTableDomainModelReference;
import org.eclipse.emfforms.bazaar.Bid;
import org.eclipse.emfforms.bazaar.Create;
import org.eclipse.emfforms.spi.core.services.databinding.DatabindingFailedException;
import org.eclipse.emfforms.spi.core.services.databinding.emf.EMFFormsDatabindingEMF;
import org.eclipse.emfforms.view.spi.multisegment.model.MultiSegmentUtil;
import org.eclipse.emfforms.view.spi.multisegment.model.VMultiDomainModelReferenceSegment;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* Provider of a selection composite strategy that builds a selection table viewer
* from a {@linkplain VTableControl table control model}.
*
* @since 1.22
*/
@Component(name = "selectionTableCompositeStrategyProvider")
public class SelectionTableCompositeStrategyProvider implements SelectionCompositeStrategy.Provider {
/**
* Filter key for view registration to apply the view to the context of the selection
* table composite specifically in the case that some other view is also available
* that is intended for the editor. The value of the filter is the name of the reference
* feature for which objects are to be selected.
*/
public static final String VIEW_FILTER_KEY = "selectionTableComposite"; //$NON-NLS-1$
private static final Double BID = 1.0;
private final Map<UniqueSetting, VTableControl> recentlyQueriedTables = new HashMap<>();
private EMFFormsDatabindingEMF databinding;
/**
* Initializes me.
*/
public SelectionTableCompositeStrategyProvider() {
super();
}
/**
* Queries my bid on a selection table composite strategy for selection of objects to add
* to the given {@code reference} of an {@code owner} object.
*
* @param owner the owner of the reference to be edited
* @param reference the reference to which to add objects
*
* @return my bid, or {@code null} if I have nothing to offer
*/
@Bid
public Double provides(EObject owner, EReference reference) {
final UniqueSetting key = UniqueSetting.createSetting(owner, reference);
final VTableControl table = getTableControl(key);
if (table != null) {
recentlyQueriedTables.put(key, table);
}
return table != null ? BID : null;
}
private VTableControl getTableControl(UniqueSetting key) {
VTableControl result = recentlyQueriedTables.get(key);
if (result == null) {
result = getTableControl(key.getEObject(), key.getEStructuralFeature());
}
return result;
}
/**
* Obtain the view model for selecting objects to add the the {@code reference}
* of an {@code owner}.
*
* @param owner the owner of the {@code reference} being edited
* @param feature the reference feature being edited
* @return the view model, or {@code null} if there is none
*/
protected VTableControl getTableControl(EObject owner, EStructuralFeature feature) {
VTableControl result = null;
final VViewModelProperties loadingProperties = getLoadingProperties(owner, feature);
// Require our filter property to be sure only to get views that are specifically
// designed for usage in the selection composite
final VView view = ViewProviderHelper.getView(owner, loadingProperties, singleton(VIEW_FILTER_KEY));
if (view != null) {
// It must have a table for our reference
result = findTableControl(view, owner, feature);
}
return result;
}
/**
* Obtain the view model loading properties for filtering the applicable view models.
*
* @param owner the owner of the {@code reference} being edited
* @param feature the reference feature being edited
* @return the view model filter properties
*/
protected VViewModelProperties getLoadingProperties(EObject owner, EStructuralFeature feature) {
final VViewModelLoadingProperties result = VViewFactory.eINSTANCE.createViewModelLoadingProperties();
result.addNonInheritableProperty(VIEW_FILTER_KEY, feature.getName());
return result;
}
/**
* Create my selection table composite strategy for selection of objects to add
* to the given {@code reference} of an {@code owner} object.
*
* @param owner the owner of the reference to be edited
* @param reference the reference to which to add objects
*
* @return my bid, or {@code null} if I have nothing to offer
*/
@Create
public SelectionCompositeStrategy create(EObject owner, EReference reference) {
SelectionCompositeStrategy result = null;
final UniqueSetting key = UniqueSetting.createSetting(owner, reference);
final VTableControl table = getTableControl(key);
if (table != null) {
// Don't keep the cache in case the next query would have a different result
recentlyQueriedTables.remove(key);
result = strategy(table);
}
return result;
}
private SelectionCompositeStrategy strategy(VTableControl table) {
return (owner, reference, extent) -> new TableSelectionCompositeImpl(
extent, table, owner, reference);
}
/**
* Search for a table control in the given {@code view} that edits a {@code feature}.
*
* @param view a view to search
* @param owner the owner of the {@code feature} being edited
* @param feature the feature to be edited
*
* @return a table editing the {@code feature}, or {@code null} if none
*/
private VTableControl findTableControl(VView view, EObject owner, EStructuralFeature feature) {
VTableControl result = null;
for (final Iterator<EObject> iter = view.eAllContents(); result == null && iter.hasNext();) {
final EObject next = iter.next();
if (next instanceof VTableControl) {
final VTableControl table = (VTableControl) next;
final VDomainModelReference dmr = table.getDomainModelReference();
if (dmr == null) {
continue;
}
if (dmr.getSegments().isEmpty() && dmr instanceof VTableDomainModelReference) {
// The TableDMRConverter ignores single-valued references, but we
// have to let them be specified anyways because it's the only
// thing that can provide the column DMRs
final VTableDomainModelReference tdmr = (VTableDomainModelReference) dmr;
if (tdmr.getDomainModelEFeature() == feature
|| resolvesTo(tdmr.getDomainModelReference(), owner, feature)) {
// That's the one
result = table;
}
} else if (!dmr.getSegments().isEmpty() && MultiSegmentUtil.getMultiSegment(dmr).isPresent()) {
// The MultiSegmentConverter does not handle single-value reference, but we have to let them be
// specified anyways because it's the only thing that can provide the column DMRs.
// Copy the dmr and replace the multi segment with a feature segment in the copied version. See if
// this can be resolved to our target feature.
final VDomainModelReference copiedDmr = EcoreUtil.copy(dmr);
final VMultiDomainModelReferenceSegment multiSegment = MultiSegmentUtil.getMultiSegment(copiedDmr)
.get();
copiedDmr.getSegments().remove(multiSegment);
final VFeatureDomainModelReferenceSegment segment = VViewFactory.eINSTANCE
.createFeatureDomainModelReferenceSegment();
segment.setDomainModelFeature(multiSegment.getDomainModelFeature());
copiedDmr.getSegments().add(segment);
if (resolvesTo(copiedDmr, owner, feature)) {
result = table;
}
} else if (resolvesTo(dmr, owner, feature)) {
// That's the one
result = table;
}
}
}
return result;
}
private boolean resolvesTo(VDomainModelReference dmr, EObject owner, EStructuralFeature feature) {
boolean result = false;
EStructuralFeature.Setting setting;
try {
setting = databinding.getSetting(dmr, owner);
result = setting != null && setting.getEStructuralFeature() == feature;
} catch (final DatabindingFailedException e) {
// This table is of no use to us
}
return result;
}
//
// Component lifecycle and dependencies
//
/**
* Inject my data binding service dependency.
*
* @param databinding the data binding service to set
*/
@Reference(unbind = "-")
void setDatabinding(EMFFormsDatabindingEMF databinding) {
this.databinding = databinding;
}
}