blob: 278b253934c9d810891cb2953c7fd558b1e9cc0a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011-2019 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:
* Lucas Koehler - initial API and implementation
******************************************************************************/
package org.eclipse.emf.ecp.view.internal.editor.handler;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import org.eclipse.core.resources.IFile;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.notify.AdapterFactory;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.impl.AdapterImpl;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.URI;
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.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecp.ui.view.ECPRendererException;
import org.eclipse.emf.ecp.ui.view.swt.ECPSWTViewRenderer;
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.context.ViewModelService;
import org.eclipse.emf.ecp.view.spi.editor.controls.EStructuralFeatureSelectionValidator;
import org.eclipse.emf.ecp.view.spi.editor.controls.SegmentIdeDescriptor;
import org.eclipse.emf.ecp.view.spi.editor.controls.SelectedFeatureViewService;
import org.eclipse.emf.ecp.view.spi.model.VDomainModelReference;
import org.eclipse.emf.ecp.view.spi.model.VDomainModelReferenceSegment;
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.VViewPackage;
import org.eclipse.emf.ecp.view.spi.model.util.SegmentResolvementUtil;
import org.eclipse.emf.ecp.view.spi.provider.ViewProviderHelper;
import org.eclipse.emf.edit.provider.ComposedAdapterFactory;
import org.eclipse.emf.edit.provider.ReflectiveItemProviderAdapterFactory;
import org.eclipse.emf.edit.ui.provider.AdapterFactoryLabelProvider;
import org.eclipse.emfforms.common.internal.validation.ValidationServiceImpl;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
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.TableViewer;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.jface.viewers.TreeSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.wizard.IWizardPage;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
/**
* An advanced DMR creation wizard that allows the user to create a segment based DMR.
* It allows to either create a simple reference path consisting only of feature segments or switching to an advanced
* mode. The advanced mode allows to create the reference path one segment at a time. Thereby, all available segment
* types can be created.
*
* @author Lucas Koehler
*
*/
public class CreateSegmentDmrWizard extends Wizard {
private final VDomainModelReference existingDMR;
private final EStructuralFeatureSelectionValidator selectionValidator;
private final SegmentGenerator segmentGenerator;
private final String lastSegmentTypeInfo;
private final EClass lastSegmentType;
/**
* Contains all types of segments that can be created in the advanced mode. The types are mapped to their
* corresponding {@link SegmentIdeDescriptor}.
*/
private final Map<EClass, SegmentIdeDescriptor> segmentToIdeDescriptorMap = new LinkedHashMap<EClass, SegmentIdeDescriptor>();
private final List<ServiceReference<SegmentIdeDescriptor>> serviceReferences = new LinkedList<ServiceReference<SegmentIdeDescriptor>>();
private SimpleModePage simpleModePage;
private VDomainModelReference advancedDmr;
private VDomainModelReference resultDmr;
private EClass rootEClass;
private SelectEcorePage selectEcorePage;
private SelectEClassWizardPage selectEClassPage;
private Object selectedContainer;
/**
* A wizard used for creating and configuring a DomainModelReference.
*
* @param rootEClass The root {@link EClass} of the DMR to create. IF this is <code>null</code>, the
* wizard offers to chose a root EClass. To make this clear, you might want to use the constructor
* {@link CreateSegmentDmrWizard#CreateSegmentDmrWizard(String, EStructuralFeatureSelectionValidator, SegmentGenerator, EClass, boolean)}.
* @param windowTitle The title for the wizard window
* @param existingDMR The domain model reference to configure. May be null. If this is given the initial selection
* of the wizard is based on this but the given DMR will not be changed. A new one is returned.
* @param selectionValidator Validates whether a selected structural feature is a valid selection (e.g. the
* selection could be required to be a multi reference)
* @param segmentGenerator The {@link SegmentGenerator} used in the simple dmr creation mode
* @param lastSegmentType The type that the last segment in advanced creation mode must have, or
* <strong>null</strong> if there is no restriction
* @param ignoreSegmentIdeRestriction If <code>true</code>, all types of segments are available independently of the
* configuration in their {@link SegmentIdeDescriptor}.
*/
// CHECKSTYLE.OFF: ParameterNumber
public CreateSegmentDmrWizard(final EClass rootEClass, final String windowTitle,
VDomainModelReference existingDMR, EStructuralFeatureSelectionValidator selectionValidator,
SegmentGenerator segmentGenerator, EClass lastSegmentType, boolean ignoreSegmentIdeRestriction) {
// CHECKSTYLE.ON: ParameterNumber
setWindowTitle(windowTitle);
this.rootEClass = rootEClass;
this.existingDMR = existingDMR;
this.selectionValidator = selectionValidator;
this.segmentGenerator = segmentGenerator;
this.lastSegmentType = lastSegmentType;
setForcePreviousAndNextButtons(true);
advancedDmr = VViewFactory.eINSTANCE.createDomainModelReference();
if (lastSegmentType != null) {
lastSegmentTypeInfo = "\nNote: Cannot finish because the last segment must be a " //$NON-NLS-1$
+ lastSegmentType.getName();
} else {
lastSegmentTypeInfo = ""; //$NON-NLS-1$
}
final Collection<SegmentIdeDescriptor> descriptors = collectSegmentIdeDescriptors(ignoreSegmentIdeRestriction);
for (final SegmentIdeDescriptor descriptor : descriptors) {
segmentToIdeDescriptorMap.put(descriptor.getSegmentType(), descriptor);
}
}
/**
* Collect all {@link SegmentIdeDescriptor SegmentIdeDescriptors} for the advanced mode.
*
* @param ignoreSegmentIdeRestriction Whether the descriptors' availability flag must be considered when collecting
* the descriptors
* @return The collection of {@link SegmentIdeDescriptor SegmentIdeDescriptors}; might be empty but never
* <code>null</code>
* @see SegmentIdeDescriptor#isAvailableInIde()
*/
protected Collection<SegmentIdeDescriptor> collectSegmentIdeDescriptors(boolean ignoreSegmentIdeRestriction) {
try {
final BundleContext bundleContext = FrameworkUtil.getBundle(CreateSegmentDmrWizard.class)
.getBundleContext();
final Collection<ServiceReference<SegmentIdeDescriptor>> references = bundleContext
.getServiceReferences(SegmentIdeDescriptor.class, null);
serviceReferences.addAll(references);
return references.stream()
.map(bundleContext::getService)
.filter(Objects::nonNull)
.filter(d -> ignoreSegmentIdeRestriction || d.isAvailableInIde())
.collect(Collectors.toSet());
} catch (final InvalidSyntaxException ex) {
// Should never happen because no filter is used
}
return Collections.emptySet();
}
/**
* A wizard used for creating a new DomainModelReference. Before the DMR is created, the wizard allows
* to select a root EClass for the new DMR.
*
* @param windowTitle The title for the wizard window
* @param selectionValidator Validates whether a selected structural feature is a valid selection (e.g. the
* selection could be required to be a multi reference)
* @param segmentGenerator The {@link SegmentGenerator} used in the simple dmr creation mode
* @param lastSegmentType The type that the last segment in advanced creation mode must have, or
* <strong>null</strong> if there is no restriction
* @param ignoreSegmentIdeRestriction If <code>true</code>, all types of segments are available independently of the
* configuration in their {@link SegmentIdeDescriptor}.
*/
// CHECKSTYLE.OFF: ParameterNumber
public CreateSegmentDmrWizard(final String windowTitle, EStructuralFeatureSelectionValidator selectionValidator,
SegmentGenerator segmentGenerator, EClass lastSegmentType, boolean ignoreSegmentIdeRestriction) {
// CHECKSTYLE.ON: ParameterNumber
this(null, windowTitle, null, selectionValidator, segmentGenerator, lastSegmentType,
ignoreSegmentIdeRestriction);
}
/**
* Creates a new dmr creation with default values for segment generation (= a simple feature segment path), segment
* types (all none restricted types), and the required last segment type (all are allowed) in advanced mode.
*
* @param rootEClass The root {@link EClass} of the DMR to create. IF this is <code>null</code>, the
* wizard offers to chose a root EClass. To make this clear, you might want to use the constructor
* {@link CreateSegmentDmrWizard#CreateSegmentDmrWizard(String, EStructuralFeatureSelectionValidator, SegmentGenerator, EClass, boolean)}.
* @param windowTitle The title for the wizard window
* @param existingDMR The domain model reference to configure. May be null. If this is given the initial selection
* of the wizard is based on this but the given DMR will not be changed. A new one is returned.
* @param selectionValidator Validates whether a selected structural feature is a valid selection (e.g. the
* selection could be required to be a multi reference)
*/
public CreateSegmentDmrWizard(final EClass rootEClass, final String windowTitle,
VDomainModelReference existingDMR, EStructuralFeatureSelectionValidator selectionValidator) {
this(rootEClass, windowTitle, existingDMR, selectionValidator, new FeatureSegmentGenerator(), null, false);
}
/**
* Returns the configured {@link VDomainModelReference}. This is either a new DMR or the edited input DMR.
* The return value is empty if the dialog was cancelled or the dialog is still open.
*
* @return The configured {@link VDomainModelReference} or an empty value if the dialog was cancelled
*/
public Optional<VDomainModelReference> getDomainModelReference() {
return Optional.ofNullable(resultDmr);
}
@Override
public IWizardPage getNextPage(IWizardPage page) {
if (page == selectEcorePage) {
final EPackage p = getEPackage(selectEcorePage.getSelectedContainer());
selectEClassPage.setSelectedEPackage(p);
return selectEClassPage;
} else if (page == selectEClassPage) {
rootEClass = selectEClassPage.getSelectedEClasses().get(0);
simpleModePage.setRootEClass(rootEClass);
return simpleModePage;
}
return super.getNextPage(page);
}
/**
* @return The {@link EPackage} from the selected container
*/
private EPackage getEPackage(Object selectedContainer) {
EPackage ePackage = null;
if (EPackage.class.isInstance(selectedContainer)) {
ePackage = EPackage.class.cast(selectedContainer);
} else if (IFile.class.isInstance(selectedContainer)) {
final ResourceSetImpl resourceSet = new ResourceSetImpl();
final String path = ((IFile) selectedContainer).getFullPath().toString();
final URI uri = URI.createPlatformResourceURI(path, true);
final Resource resource = resourceSet.getResource(uri, true);
if (resource != null) {
final EList<EObject> contents = resource.getContents();
if (contents.size() != 1) {
return null;
}
final EObject object = contents.get(0);
if (!(object instanceof EPackage)) {
return null;
}
ePackage = (EPackage) object;
}
}
return ePackage;
}
@Override
public void dispose() {
final BundleContext bundleContext = FrameworkUtil.getBundle(CreateSegmentDmrWizard.class)
.getBundleContext();
for (final ServiceReference<SegmentIdeDescriptor> serviceRef : serviceReferences) {
bundleContext.ungetService(serviceRef);
}
segmentToIdeDescriptorMap.clear();
serviceReferences.clear();
super.dispose();
}
@Override
public void addPages() {
if (rootEClass == null) {
selectEcorePage = new SelectEcorePage("org.eclipse.emf.ecp.ui.view.editor.controls"); //$NON-NLS-1$
addPage(selectEcorePage);
selectEClassPage = new SelectEClassWizardPage() {
@Override
protected boolean isMultiSelect() {
return false;
}
};
addPage(selectEClassPage);
}
simpleModePage = new SimpleModePage("Configure Domain Model Reference", //$NON-NLS-1$
"Select an EStructuralFeature", //$NON-NLS-1$
"Select a structural feature for the domain model reference or switch to the advanced creation mode\n to create the reference path one feature at a time.", //$NON-NLS-1$
rootEClass, getInitialSelection());
addPage(simpleModePage);
}
/**
* @return The selected ecore file, <strong>IF</strong> a root EClass was selected and it is located in a local
* ecore file.
*/
public Optional<IFile> getSelectedEcore() {
if (IFile.class.isInstance(selectedContainer)) {
return Optional.of((IFile) selectedContainer);
}
return Optional.empty();
}
/**
* Returns the root {@link EClass} of the domain model reference. IF a root EClass was given when this wizard was
* created, it is returned. Otherwise, the EClass selected by the user is returned. If no EClass was given and the
* dialog was cancelled before an EClass was selected, nothing is returned.
*
* @return The root {@link EClass} of the created/configured domain model reference or nothing if no EClass was
* given and selected.
*/
public Optional<EClass> getRootEClass() {
return Optional.ofNullable(rootEClass);
}
/**
* @return The initial selection for the {@link SelectFeaturePathWizardPage SelectFeaturePathWizardPage's} tree
* viewer. Returns an empty selection if there is no existing dmr or it does not contain any segments.
*/
private ISelection getInitialSelection() {
if (existingDMR == null || existingDMR.getSegments().isEmpty()) {
return TreeSelection.EMPTY;
}
final List<EStructuralFeature> pathList = SegmentResolvementUtil
.resolveSegmentsToFeatureList(existingDMR.getSegments(), rootEClass);
if (pathList.size() == existingDMR.getSegments().size()) {
return new TreeSelection(new TreePath(pathList.toArray()));
}
return TreeSelection.EMPTY;
}
@Override
public boolean performFinish() {
if (!canFinish()) {
return false;
}
if (selectEcorePage != null) {
selectedContainer = selectEcorePage.getSelectedContainer();
}
if (simpleModePage.equals(getContainer().getCurrentPage())) {
// simple mode was used
resultDmr = simpleModePage.getDomainModelReference();
} else {
final SegmentCreationPage finalSegmentCreationPage = (SegmentCreationPage) getContainer().getCurrentPage();
final int lastSegmentIndex = finalSegmentCreationPage.index;
/*
* If there are segments in the dmr with a higher index than the final segment creation page,
* they origin from a page with a higher index where the "back" button has been clicked by the user.
* => They need to be removed.
*/
for (int i = advancedDmr.getSegments().size() - 1; i > lastSegmentIndex; i--) {
advancedDmr.getSegments().remove(i);
}
// remove validation adapters - they are only needed during the dmr creation
for (final VDomainModelReferenceSegment segment : advancedDmr.getSegments()) {
for (final Adapter adapter : segment.eAdapters()) {
if (SegmentCreationPage.SegmentAdapter.class.isInstance(adapter)) {
segment.eAdapters().remove(adapter);
// only one is registered, avoid concurrent modification exception
break;
}
}
}
resultDmr = advancedDmr;
}
return true;
}
@Override
public boolean canFinish() {
// "simple" mode
if (simpleModePage.equals(getContainer().getCurrentPage())) {
return simpleModePage.isPageComplete();
}
// "advanced" mode
if (advancedDmr.getSegments().isEmpty() || !getContainer().getCurrentPage().isPageComplete()) {
return false;
}
// TODO check with segment validator
final EList<VDomainModelReferenceSegment> segments = advancedDmr.getSegments();
final VDomainModelReferenceSegment lastSegment = segments.get(segments.size() - 1);
final SegmentIdeDescriptor descriptor = segmentToIdeDescriptorMap.get(lastSegment.eClass());
if (!descriptor.isAllowedAsLastElementInPath()) {
return false;
}
if (lastSegmentType == null) {
return true;
}
return lastSegmentType.isInstance(lastSegment);
}
/**
* The wizard's first page. It allows to either create a simple reference path consisting only of feature segments
* or switching to an advanced mode. The advanced mode allows to create the reference path one reference at a time.
* Thereby, all available segment types can be created.
*
* @author Lucas Koehler
*
*/
private class SimpleModePage extends SelectFeaturePathWizardPage {
protected SimpleModePage(String pageName, String pageTitle, String pageDescription,
EClass rootEClass, ISelection firstSelection) {
super(pageName, pageTitle, pageDescription, rootEClass, firstSelection, segmentGenerator,
selectionValidator, false);
}
@Override
public void createControl(Composite parent) {
super.createControl(parent);
final Composite controlComposite = (Composite) getControl();
final Button advancedModeButton = new Button(controlComposite, SWT.NONE);
GridDataFactory.fillDefaults().align(SWT.FILL, SWT.END).grab(true, false).applyTo(advancedModeButton);
advancedModeButton.setText("Switch To Advanced DMR Creation"); //$NON-NLS-1$
if (!segmentToIdeDescriptorMap.isEmpty()) {
advancedModeButton.setToolTipText(
"Using the advanced Domain Model Reference creation allows to use all available kinds of segments, i.e. index segments."); //$NON-NLS-1$
advancedModeButton.addListener(SWT.Selection, event -> {
final IWizardPage advancedPage = new SegmentCreationPage("Select Segment Type", rootEClass, 0); //$NON-NLS-1$
addPage(advancedPage);
advancedDmr = VViewFactory.eINSTANCE.createDomainModelReference();
getContainer().showPage(advancedPage);
});
} else {
// There are no registered segment types that can be used => Advanced mode is useless
advancedModeButton.setEnabled(false);
advancedModeButton.setToolTipText(
"There are no segment types available for IDE usage. Therefore, the advanced creation mode is deactivated."); //$NON-NLS-1$
}
}
@Override
protected TreeViewer createTreeViewer(Composite composite) {
// only allow single selection
return new TreeViewer(composite, SWT.V_SCROLL | SWT.H_SCROLL | SWT.BORDER | SWT.SINGLE);
}
/**
* Override method and never allow to flip to next page: Advanced mode should only be accessed by using the
* designated button.<br/>
* The next button would allow to go back to the advanced mode after it has been activated and the user pressed
* back on the first advanced page. This could lead to unexpected behavior. Therefore, it is not allowed.
* <hr>
* {@inheritDoc}
*
* @see org.eclipse.jface.wizard.WizardPage#canFlipToNextPage()
*/
@Override
public boolean canFlipToNextPage() {
return false;
}
}
/**
* WizardPage to create one segment. Allows to create all types of registered segments.
*
* @author Lucas Koehler
*
*/
private class SegmentCreationPage extends WizardPage {
private static final String PLEASE_SELECT_A_SEGMENT_TYPE_ERROR_MSG = "Please select a segment type."; //$NON-NLS-1$
private final int index;
private Composite pageComposite;
private ComposedAdapterFactory composedAdapterFactory;
private AdapterFactoryLabelProvider labelProvider;
private EClass rootEClass;
private EClass segmentType;
private VFeatureDomainModelReferenceSegment createdSegment;
private Composite renderComposite;
private SegmentCreationPage nextPage;
private TableViewer tableViewer;
private EStructuralFeature selectedFeature;
/**
* Set to true, if the page is complete except for the successful validation of the current segment. Is still
* true when the validation is successful, too.
*/
private boolean pageCompleteWithoutValidation;
/**
* @param pageName
* @param rootEClass The root {@link EClass} to select the feature from for the path segment created by this
* page
* @param index The index of the created segment in the DMRs segment list. This is necessary to set the segment
* at the correct location in the DMR.
*/
protected SegmentCreationPage(String pageName, EClass rootEClass, int index) {
super(pageName);
setTitle("Create a Segment"); //$NON-NLS-1$
setDescription("Create a segment by selecting its type and an appropriate feature."); //$NON-NLS-1$
this.rootEClass = rootEClass;
this.index = index;
}
@Override
public boolean canFlipToNextPage() {
if (createdSegment == null) {
return false;
}
final SegmentIdeDescriptor ideDescriptor = segmentToIdeDescriptorMap.get(createdSegment.eClass());
if (ideDescriptor.isLastElementInPath()) {
return false;
}
final String domainModelFeature = createdSegment.getDomainModelFeature();
if (domainModelFeature != null && !domainModelFeature.isEmpty()) {
final EStructuralFeature feature = rootEClass
.getEStructuralFeature(createdSegment.getDomainModelFeature());
if (EReference.class.isInstance(feature)) {
// Feature segments can only contain a multi reference at the end of the reference path, never in
// the beginning or the middle
if (VViewPackage.eINSTANCE.getFeatureDomainModelReferenceSegment()
.equals(createdSegment.eClass()) && EReference.class.cast(feature).isMany()) {
return false;
}
if (nextPage.rootEClass != null) {
return super.canFlipToNextPage();
}
}
}
return false;
}
/**
* Sets the root {@link EClass} of this page and updates the input of the table viewer.
*
* @param rootEClass The new root {@link EClass}
*/
public void setRootEClass(EClass rootEClass) {
this.rootEClass = rootEClass;
if (tableViewer != null) {
tableViewer.setInput(rootEClass);
}
}
@Override
public void createControl(final Composite parent) {
nextPage = new SegmentCreationPage(getName(), null, index + 1);
addPage(nextPage);
pageComposite = new Composite(parent, SWT.NONE);
GridLayoutFactory.fillDefaults().applyTo(pageComposite);
GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).grab(true, true).applyTo(pageComposite);
composedAdapterFactory = new ComposedAdapterFactory(new AdapterFactory[] {
new ReflectiveItemProviderAdapterFactory(),
new ComposedAdapterFactory(ComposedAdapterFactory.Descriptor.Registry.INSTANCE) });
labelProvider = new AdapterFactoryLabelProvider(composedAdapterFactory);
final EStructuralFeatureContentProvider contentProvider = new EStructuralFeatureContentProvider(
false);
// get subclasses
final Collection<EClass> segmentClasses = segmentToIdeDescriptorMap.keySet();
final Map<String, EClass> segmentClassesMap = new LinkedHashMap<String, EClass>();
for (final EClass segmentClass : segmentClasses) {
segmentClassesMap.put(segmentClass.getName(), segmentClass);
}
final Composite segmentTypeComposite = new Composite(pageComposite, SWT.NONE);
GridLayoutFactory.fillDefaults().numColumns(2).applyTo(segmentTypeComposite);
GridDataFactory.fillDefaults().align(SWT.FILL, SWT.BEGINNING).grab(true, false)
.applyTo(segmentTypeComposite);
// combo box label
final Label segmentSelectionLabel = new Label(segmentTypeComposite, SWT.NONE);
segmentSelectionLabel.setText("Select Segment Type:"); //$NON-NLS-1$
GridDataFactory.fillDefaults().align(SWT.BEGINNING, SWT.CENTER).grab(false, false)
.applyTo(segmentSelectionLabel);
// configure combo box
final Combo segmentTypeSelector = new Combo(segmentTypeComposite, SWT.READ_ONLY | SWT.DROP_DOWN);
GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false)
.applyTo(segmentTypeSelector);
final String[] items = segmentClassesMap.keySet().toArray(new String[segmentClassesMap.size()]);
Arrays.sort(items);
segmentTypeSelector.setItems(items);
// table viewer with single selection
tableViewer = new TableViewer(pageComposite, SWT.V_SCROLL | SWT.H_SCROLL | SWT.BORDER | SWT.SINGLE);
GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).grab(true, true).applyTo(tableViewer.getControl());
tableViewer.setContentProvider(contentProvider);
tableViewer.setLabelProvider(labelProvider);
tableViewer.setInput(rootEClass);
tableViewer.addSelectionChangedListener(new FeatureSelectionChangedListerner());
segmentTypeSelector.addListener(SWT.Selection,
new SegmentTypeSelectionListener(pageComposite, segmentClassesMap, tableViewer, segmentTypeSelector));
// If available, select the feature segment by default
if (segmentClassesMap.values().contains(VViewPackage.eINSTANCE.getFeatureDomainModelReferenceSegment())) {
final int index = Arrays.binarySearch(items,
VViewPackage.eINSTANCE.getFeatureDomainModelReferenceSegment().getName());
if (index >= 0) {
segmentTypeSelector.select(index);
segmentTypeSelector.notifyListeners(SWT.Selection, new Event());
}
}
setControl(pageComposite);
}
/**
* Creates the {@link Composite} used to render the advanced properties of the current segment.
* If it already exists, the existing one is disposed and a new one created.
*
* @param parent The created {@link Composite Composite's} parent.
* @return The {@link Composite}
*/
private Composite initRenderComposite() {
if (renderComposite != null) {
renderComposite.dispose();
}
renderComposite = new Composite(pageComposite, SWT.NONE);
GridLayoutFactory.fillDefaults().applyTo(renderComposite);
GridDataFactory.fillDefaults().align(SWT.FILL, SWT.END).grab(true, false).applyTo(renderComposite);
return renderComposite;
}
/**
* Listener to handle a segment type selection.
*
* @author Lucas Koehler
*
*/
private final class SegmentTypeSelectionListener implements Listener {
private final Map<String, EClass> segmentClassesMap;
private final TableViewer tableViewer;
private final Combo segmentTypeSelector;
private SegmentTypeSelectionListener(Composite composite, Map<String, EClass> segmentClassesMap,
TableViewer tableViewer, Combo segmentTypeSelector) {
this.segmentClassesMap = segmentClassesMap;
this.tableViewer = tableViewer;
this.segmentTypeSelector = segmentTypeSelector;
}
@Override
public void handleEvent(Event event) {
if (segmentTypeSelector.getText() == null || segmentTypeSelector.getText().isEmpty()) {
setErrorMessage(PLEASE_SELECT_A_SEGMENT_TYPE_ERROR_MSG);
createdSegment = null;
}
if (PLEASE_SELECT_A_SEGMENT_TYPE_ERROR_MSG.equals(getErrorMessage())) {
setErrorMessage(null);
}
segmentType = segmentClassesMap.get(segmentTypeSelector.getText());
// (re)set last segment type info text
if (lastSegmentType != null && !lastSegmentType.isSuperTypeOf(segmentType)
&& !getDescription().endsWith(lastSegmentTypeInfo)) {
setDescription(getDescription() + lastSegmentTypeInfo);
} else if (lastSegmentType != null && lastSegmentType.isSuperTypeOf(segmentType)
&& getDescription().endsWith(lastSegmentTypeInfo)) {
final String desc = getDescription();
setDescription(desc.substring(0, desc.length() - lastSegmentTypeInfo.length()));
}
// create segment and add to dmr
initializeSegment();
// trigger selection changed event to apply current selection to the selected segment type
tableViewer.setSelection(tableViewer.getSelection(), true);
}
}
/**
* An adapter that is registered at the createdSegment to re-validate it on change.
* Additionally, it re-sets the root e class of the next page because it may be affected by the change (e.g. in
* case of a mapping segment).
*
* @author Lucas Koehler
*
*/
private class SegmentAdapter extends AdapterImpl {
@Override
public void notifyChanged(Notification notification) {
if (notification.isTouch()) {
return;
}
if (validateSegment()) {
if (pageCompleteWithoutValidation) {
setErrorMessage(null);
setPageComplete(true);
}
}
if (EReference.class.isInstance(selectedFeature)) {
final SegmentIdeDescriptor ideDescriptor = segmentToIdeDescriptorMap.get(createdSegment.eClass());
nextPage.setRootEClass(ideDescriptor.getReferenceTypeResolver()
.resolveNextEClass(EReference.class.cast(selectedFeature), createdSegment));
}
}
}
/**
* Creates a new instance of the selected segment type and renders its features below the tree viewer.
* Also, the segment is added at the correct position in the wizard's DMR.
*/
private void initializeSegment() {
if (segmentType == null) {
return;
}
// create segment and add to dmr
createdSegment = (VFeatureDomainModelReferenceSegment) EcoreUtil.create(segmentType);
createdSegment.eAdapters().add(new SegmentAdapter());
if (advancedDmr.getSegments().size() > index) {
advancedDmr.getSegments().set(index, createdSegment);
} else {
advancedDmr.getSegments().add(createdSegment);
}
// render segment properties
try {
initRenderComposite();
final VView segmentView = ViewProviderHelper.getView(createdSegment, null);
final ViewModelContext context = ViewModelContextFactory.INSTANCE
.createViewModelContext(segmentView, createdSegment, new SelectedFeatureViewServiceImpl());
ECPSWTViewRenderer.INSTANCE.render(renderComposite, context);
pageComposite.layout();
} catch (final ECPRendererException ex) {
MessageDialog.openError(getShell(), "Rendering Error", //$NON-NLS-1$
"The current segment could not be rendered: " + ex.getMessage()); //$NON-NLS-1$
}
}
/**
* Selection changed listener for the table containing the structural features to select for the current
* segment.
*
* @author Lucas Koehler
*
*/
private class FeatureSelectionChangedListerner implements ISelectionChangedListener {
@Override
public void selectionChanged(SelectionChangedEvent event) {
/*
* If there are segments in the dmr with a higher index than the current page,
* they origin from a page with a higher index where the "back" button has been clicked by the user.
* => They need to be removed, because they are no longer valid when the selection changes
*/
for (int i = advancedDmr.getSegments().size() - 1; i > index; i--) {
advancedDmr.getSegments().remove(i);
}
// Page is not complete until the opposite is proven
setPageComplete(false);
pageCompleteWithoutValidation = false;
// Get the selected element
final IStructuredSelection structuredSelection = (IStructuredSelection) event.getSelection();
if (structuredSelection.isEmpty()) {
return;
}
// Reset segment whenever a new feature is selected because segment configurations may depend on the
// selected feature.
initializeSegment();
// Set the selected feature to the page
final EStructuralFeature structuralFeature = (EStructuralFeature) structuredSelection
.getFirstElement();
selectedFeature = structuralFeature;
if (createdSegment == null) {
setErrorMessage(PLEASE_SELECT_A_SEGMENT_TYPE_ERROR_MSG);
return;
}
// Validate that a valid structural feature was selected
final SegmentIdeDescriptor ideDescriptor = segmentToIdeDescriptorMap.get(createdSegment.eClass());
final String errorMessage = ideDescriptor.getEStructuralFeatureSelectionValidator()
.isValid(structuralFeature);
setErrorMessage(errorMessage);
if (errorMessage != null) {
return;
}
createdSegment.setDomainModelFeature(structuralFeature.getName());
if (EReference.class.isInstance(structuralFeature)) {
final EReference reference = (EReference) structuralFeature;
nextPage.setRootEClass(
ideDescriptor.getReferenceTypeResolver().resolveNextEClass(reference, createdSegment));
if (nextPage.rootEClass == null) {
return;
}
}
pageCompleteWithoutValidation = true;
if (validateSegment()) {
setPageComplete(true);
}
}
}
/**
* Validates this page's segment and sets the appropriate (error) message if necessary.
*
* @return <code>true</code> if the segment is valid, <code>false</code> otherwise
*/
private boolean validateSegment() {
final ValidationServiceImpl validator = new ValidationServiceImpl();
final Diagnostic diagnostic = validator.validate(createdSegment);
if (diagnostic.getSeverity() == Diagnostic.INFO) {
setMessage("Validation Information: " + diagnostic.getMessage()); //$NON-NLS-1$
} else if (diagnostic.getSeverity() == Diagnostic.WARNING) {
setMessage("Validation Warning: " + diagnostic.getMessage()); //$NON-NLS-1$
} else if (diagnostic.getSeverity() == Diagnostic.ERROR) {
setErrorMessage(diagnostic.getMessage());
return false;
}
return true;
}
/**
* {@link ViewModelService} returning the currently selected feature of this {@link SegmentCreationPage}.
*
* @author Lucas Koehler
*/
private class SelectedFeatureViewServiceImpl implements SelectedFeatureViewService {
@Override
public void instantiate(ViewModelContext context) {
// not needed
}
@Override
public void dispose() {
// not needed
}
@Override
public int getPriority() {
return 0;
}
@Override
public EStructuralFeature getFeature() {
return selectedFeature;
}
}
}
}