diff options
| author | Pierre-Charles David | 2017-05-12 14:02:47 +0000 |
|---|---|---|
| committer | Pierre-Charles David | 2017-05-16 14:59:24 +0000 |
| commit | 4e254522535028fc996829f4f56d636a7859cf63 (patch) | |
| tree | 08a66cdc5f49fb39816f72ad9d82b91725c9bbf6 | |
| parent | 8cec6b4409d94a4d2653408dda879365c40da407 (diff) | |
| download | org.eclipse.sirius-4e254522535028fc996829f4f56d636a7859cf63.tar.gz org.eclipse.sirius-4e254522535028fc996829f4f56d636a7859cf63.tar.xz org.eclipse.sirius-4e254522535028fc996829f4f56d636a7859cf63.zip | |
[514682] Add generic wizard for creating new models
Bug: 514682
Change-Id: Ib10a7220d8e10a28a25b88ebe1c371583cbb8e12
Signed-off-by: Pierre-Charles David <pierre-charles.david@obeo.fr>
8 files changed, 1133 insertions, 0 deletions
diff --git a/plugins/org.eclipse.sirius.ui/META-INF/MANIFEST.MF b/plugins/org.eclipse.sirius.ui/META-INF/MANIFEST.MF index 17e3d3cea8..716b0c6f93 100644 --- a/plugins/org.eclipse.sirius.ui/META-INF/MANIFEST.MF +++ b/plugins/org.eclipse.sirius.ui/META-INF/MANIFEST.MF @@ -90,6 +90,7 @@ Export-Package: org.eclipse.sirius.description.contribution.provider;version="2. org.eclipse.sirius.ui.tools.internal.views.modelexplorer.property;version="2.0.4";x-internal:=true, org.eclipse.sirius.ui.tools.internal.views.modelexplorer.resourcelistener;version="5.0.0";x-internal:=true, org.eclipse.sirius.ui.tools.internal.wizards;version="2.0.4";x-internal:=true, + org.eclipse.sirius.ui.tools.internal.wizards.newmodel;version="5.0.0";x-internal:=true, org.eclipse.sirius.ui.tools.internal.wizards.pages;version="2.1.0";x-internal:=true, org.eclipse.sirius.viewpoint.description.audit.provider;version="2.0.4", org.eclipse.sirius.viewpoint.description.provider;version="2.1.0", diff --git a/plugins/org.eclipse.sirius.ui/plugin.properties b/plugins/org.eclipse.sirius.ui/plugin.properties index 17c75a9c9e..ba4bee2ecc 100644 --- a/plugins/org.eclipse.sirius.ui/plugin.properties +++ b/plugins/org.eclipse.sirius.ui/plugin.properties @@ -1467,3 +1467,19 @@ _UI_DecorationDescription_tooltipExpression_description = Expression that provid _UI_DecorationDescription_distributionDirection_description = Used when there are more than one decoration at a given position. _UI_DRepresentationDescriptor_repPath_feature = Rep Path _UI_DAnnotation_references_feature = References + + +CreateEMFModelWizard_windowTitle=Create model +CreateEMFModelWizard_modelNamePrefix=My +CreateEMFModelWizard_errorInstantiateRootElement=Unable to instantiate root element +SelectEMFMetamodelWizardPage_title=Create model +SelectEMFMetamodelWizardPage_description=This page is used to select the metamodel +SelectEMFMetamodelWizardPage_metamodelLabel=Select metamodel: +SelectEMFMetamodelWizardPage_documentationLabel=Documentation: +SelectRootElementWizardPage_title=Create model +SelectRootElementWizardPage_description=This page is used to select the root element +SelectRootElementWizardPage_label=Select root element: +SelectRootElementWizardPage_checkboxLabel=Show only suggested root types +NameAndLocationWizardPage_title=Create model +NameAndLocationWizardPage_description=This page is used to type the name & select the location of the model +NameAndLocationWizardPage_errorMessage=Wrong file extension diff --git a/plugins/org.eclipse.sirius.ui/src/org/eclipse/sirius/ui/tools/internal/wizards/newmodel/CreateEMFModelWizard.java b/plugins/org.eclipse.sirius.ui/src/org/eclipse/sirius/ui/tools/internal/wizards/newmodel/CreateEMFModelWizard.java new file mode 100644 index 0000000000..2b4087818f --- /dev/null +++ b/plugins/org.eclipse.sirius.ui/src/org/eclipse/sirius/ui/tools/internal/wizards/newmodel/CreateEMFModelWizard.java @@ -0,0 +1,191 @@ +/** + * Copyright (c) 2017 Obeo + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Obeo - initial API and implementation + */ +package org.eclipse.sirius.ui.tools.internal.wizards.newmodel; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.Collections; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +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.Registry; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; +import org.eclipse.emf.ecore.util.EcoreUtil; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.wizard.IWizardPage; +import org.eclipse.jface.wizard.Wizard; +import org.eclipse.sirius.viewpoint.provider.Messages; +import org.eclipse.sirius.viewpoint.provider.SiriusEditPlugin; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchPart; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.actions.WorkspaceModifyOperation; +import org.eclipse.ui.part.ISetSelectionTarget; + +/** + * A wizard allowing to create a new EMF model. The model will be an instance of a metamodel available through the given + * {@link Registry}. + * + * @see #CreateEMFModelWizard(Registry, IStructuredSelection) + * + * @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a> + */ +public class CreateEMFModelWizard extends Wizard { + + /** The page to select the metamodel. */ + private SelectEMFMetamodelWizardPage selectEMFMetamodelWizardPage; + + /** The page to select the root element. */ + private SelectRootElementWizardPage selectRootElementWizardPage; + + /** The page to select the name and the location of the model. */ + private NameAndLocationWizardPage nameAndLocationWizardPage; + + /** The data model used by the wizard and its pages. */ + private CreateEMFModelWizardDataModel dataModel; + + private IFile result; + + /** + * Default constructor for this wizard. + * + * @param packageRegistry + * an {@link Registry}, allowing to select a metamodel. + * @param selection + * an {@link IStructuredSelection}, allowing to initialize the location of the model to create. + */ + public CreateEMFModelWizard(Registry packageRegistry, IStructuredSelection selection) { + this.dataModel = new CreateEMFModelWizardDataModel(); + this.selectEMFMetamodelWizardPage = new SelectEMFMetamodelWizardPage(packageRegistry, this.dataModel); + this.selectRootElementWizardPage = new SelectRootElementWizardPage(this.dataModel); + this.nameAndLocationWizardPage = new NameAndLocationWizardPage(selection, this.dataModel); + setWindowTitle(Messages.CreateEMFModelWizard_windowTitle); + this.dataModel.addPropertyChangeListener(this.selectRootElementWizardPage); + this.dataModel.addPropertyChangeListener(this.nameAndLocationWizardPage); + } + + @Override + public void addPages() { + addPage(this.selectEMFMetamodelWizardPage); + addPage(this.selectRootElementWizardPage); + addPage(this.nameAndLocationWizardPage); + } + + @Override + public IWizardPage getNextPage(IWizardPage page) { + IWizardPage nextPage = null; + if (page == this.selectEMFMetamodelWizardPage) { + nextPage = this.selectRootElementWizardPage; + } else if (page == this.selectRootElementWizardPage) { + nextPage = this.nameAndLocationWizardPage; + } else if (page == this.nameAndLocationWizardPage) { + nextPage = null; + } else { + nextPage = super.getNextPage(page); + } + return nextPage; + } + + @Override + public boolean canFinish() { + return this.dataModel.getSelectedPackage() != null && this.dataModel.getSelectedRootElement() != null && this.nameAndLocationWizardPage.validatePage(); + } + + @Override + public boolean performFinish() { + final IFile modelFile = this.nameAndLocationWizardPage.getModelFile(); + + // this code is inspired from the generation of the wizard class of an + // EMF metamodel from an EMF genmodel + WorkspaceModifyOperation operation = new WorkspaceModifyOperation() { + @Override + protected void execute(IProgressMonitor progressMonitor) { + try { + ResourceSet resourceSet = new ResourceSetImpl(); + URI fileURI = URI.createPlatformResourceURI(modelFile.getFullPath().toString(), true); + Resource resource = resourceSet.createResource(fileURI); + EObject rootObject = instantiateRootElement(); + if (rootObject != null) { + resource.getContents().add(rootObject); + } + resource.save(Collections.EMPTY_MAP); + } catch (IllegalArgumentException | IOException e) { + SiriusEditPlugin.getPlugin().getLog().log(new Status(IStatus.ERROR, SiriusEditPlugin.ID, e.getMessage())); + } finally { + progressMonitor.done(); + } + } + }; + + try { + getContainer().run(false, false, operation); + this.result = modelFile; + } catch (InterruptedException e) { + SiriusEditPlugin.getPlugin().getLog().log(new Status(IStatus.ERROR, SiriusEditPlugin.ID, e.getMessage())); + } catch (InvocationTargetException e) { + SiriusEditPlugin.getPlugin().getLog().log(new Status(IStatus.ERROR, SiriusEditPlugin.ID, e.getTargetException().getMessage())); + } + + selectNewlyCreatedFile(modelFile); + + return true; + } + + /** + * Returns the newly created model file if/when the wizard finished successfully. + * + * @return the newly created model file. + */ + public IFile getResult() { + return result; + } + + private void selectNewlyCreatedFile(final IFile modelFile) { + IWorkbenchWindow workbenchWindow = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); + if (workbenchWindow != null) { + IWorkbenchPage page = workbenchWindow.getActivePage(); + if (page != null) { + final IWorkbenchPart activePart = page.getActivePart(); + if (activePart instanceof ISetSelectionTarget) { + final ISelection targetSelection = new StructuredSelection(modelFile); + getShell().getDisplay().asyncExec(new Runnable() { + public void run() { + ((ISetSelectionTarget) activePart).selectReveal(targetSelection); + } + }); + } + } + } + } + + /** + * Instantiates a root element based on the selection in the {@link SelectRootElementWizardPage}. + * + * @return a root element based on the selection in the {@link SelectRootElementWizardPage}. + */ + private EObject instantiateRootElement() { + EClass rootElement = this.dataModel.getSelectedRootElement(); + if (rootElement != null) { + return EcoreUtil.create(rootElement); + } + return null; + } +} diff --git a/plugins/org.eclipse.sirius.ui/src/org/eclipse/sirius/ui/tools/internal/wizards/newmodel/CreateEMFModelWizardDataModel.java b/plugins/org.eclipse.sirius.ui/src/org/eclipse/sirius/ui/tools/internal/wizards/newmodel/CreateEMFModelWizardDataModel.java new file mode 100644 index 0000000000..9017ec834e --- /dev/null +++ b/plugins/org.eclipse.sirius.ui/src/org/eclipse/sirius/ui/tools/internal/wizards/newmodel/CreateEMFModelWizardDataModel.java @@ -0,0 +1,112 @@ +/** + * Copyright (c) 2017 Obeo + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Obeo - initial API and implementation + */ +package org.eclipse.sirius.ui.tools.internal.wizards.newmodel; + +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EPackage; + +/** + * A data model that works along with {@link CreateEMFModelWizard}. + * + * @see CreateEMFModelWizard + * + * @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a> + */ +public class CreateEMFModelWizardDataModel { + + /** The constant for the event representing a selection of a new package. */ + public static final String SELECTED_PACKAGE_EVENT = "selectedPackage"; //$NON-NLS-1$ + + /** + * The constant for the event representing a selection of a new root element. + */ + public static final String SELECTED_ROOT_ELEMENT_EVENT = "selectedRootElement"; //$NON-NLS-1$ + + /** The PropertyChangeSupport to notify changes about this data model. */ + private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); + + /** The selected {@link EPackage}. */ + private Object selectedPackage; + + /** The selected root element. */ + private EClass selectedRootElement; + + /** + * Add a PropertyChangeListener to this data model. + * + * @param listener + * The PropertyChangeListener to be added. + */ + public void addPropertyChangeListener(PropertyChangeListener listener) { + this.pcs.addPropertyChangeListener(listener); + } + + /** + * Remove a PropertyChangeListener from this data model. + * + * @param listener + * The PropertyChangeListener to be removed. + */ + public void removePropertyChangeListener(PropertyChangeListener listener) { + this.pcs.removePropertyChangeListener(listener); + } + + /** + * Get the selected {@link EPackage} from the wizard. + * + * @return the selected {@link EPackage} from the wizard. + */ + public EPackage getSelectedPackage() { + EPackage ePackage = null; + if (this.selectedPackage instanceof EPackage) { + ePackage = (EPackage) this.selectedPackage; + } else if (this.selectedPackage instanceof EPackage.Descriptor) { + ePackage = ((EPackage.Descriptor) this.selectedPackage).getEPackage(); + } + return ePackage; + } + + /** + * Set the selected {@link EPackage}. + * + * @param selectedPackage + * the selected {@link EPackage} + */ + public void setSelectedPackage(Object selectedPackage) { + Object oldSelectedPackage = this.selectedPackage; + this.selectedPackage = selectedPackage; + this.pcs.firePropertyChange(SELECTED_PACKAGE_EVENT, oldSelectedPackage, this.selectedPackage); + } + + /** + * Get the selected root element from the wizard. + * + * @return the selected root element from the wizard. + */ + public EClass getSelectedRootElement() { + return selectedRootElement; + } + + /** + * Set the selected root element. + * + * @param selectedRootElement + * the selected root element. + */ + public void setSelectedRootElement(EClass selectedRootElement) { + EClass oldSelectedRootElement = this.selectedRootElement; + this.selectedRootElement = selectedRootElement; + this.pcs.firePropertyChange(SELECTED_ROOT_ELEMENT_EVENT, oldSelectedRootElement, this.selectedRootElement); + } +} diff --git a/plugins/org.eclipse.sirius.ui/src/org/eclipse/sirius/ui/tools/internal/wizards/newmodel/NameAndLocationWizardPage.java b/plugins/org.eclipse.sirius.ui/src/org/eclipse/sirius/ui/tools/internal/wizards/newmodel/NameAndLocationWizardPage.java new file mode 100644 index 0000000000..5f09b686b3 --- /dev/null +++ b/plugins/org.eclipse.sirius.ui/src/org/eclipse/sirius/ui/tools/internal/wizards/newmodel/NameAndLocationWizardPage.java @@ -0,0 +1,128 @@ +/** + * Copyright (c) 2017 Obeo + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Obeo - initial API and implementation + */ +package org.eclipse.sirius.ui.tools.internal.wizards.newmodel; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.Path; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.sirius.viewpoint.provider.Messages; +import org.eclipse.ui.dialogs.WizardNewFileCreationPage; + +/** + * A wizard page allowing to select a name and a location for the model to create. + * + * @see CreateEMFModelWizard + * + * @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a> + */ +public class NameAndLocationWizardPage extends WizardNewFileCreationPage implements PropertyChangeListener { + + /** The default extension for all emf models. */ + public static final String XMI_DEFAULT_EXTENSION = "xmi"; //$NON-NLS-1$ + + /** The data model used by the wizard and its pages. */ + private CreateEMFModelWizardDataModel dataModel; + + /** + * Create the wizard page. + * + * @param selection + * the selection (cannot be null). + * @param dataModel + * the data model used by this page. + */ + public NameAndLocationWizardPage(IStructuredSelection selection, CreateEMFModelWizardDataModel dataModel) { + super("NameAndLocationWizardPage", selection); //$NON-NLS-1$ + this.dataModel = dataModel; + setTitle(Messages.NameAndLocationWizardPage_title); + setDescription(Messages.NameAndLocationWizardPage_description); + } + + /** + * Get the {@link IFile} corresponding to the model to create. + * + * @return the {@link IFile} corresponding to the model to create. + */ + public IFile getModelFile() { + return ResourcesPlugin.getWorkspace().getRoot().getFile(getContainerFullPath().append(getFileName())); + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (CreateEMFModelWizardDataModel.SELECTED_PACKAGE_EVENT.equals(evt.getPropertyName())) { + setFileName(genDefaultFullFileName()); + } + } + + @Override + protected boolean validatePage() { + boolean isValid = false; + if (super.validatePage()) { + String extension = new Path(getFileName()).getFileExtension(); + // The file extension is generated, so may be it is not the appropriate file extension. + // So we only set a message error if the file has no extension. + if (extension == null) { + setErrorMessage(Messages.NameAndLocationWizardPage_errorMessage); + isValid = false; + } else { + isValid = true; + } + } + return isValid; + } + + /** + * Generates the default file name + extension for the selected {@link EPackage}. + * + * @return the default file name + extension for the selected {@link EPackage}. + */ + private String genDefaultFullFileName() { + return genDefaultFileName() + "." + genDefaultExtensionName(); //$NON-NLS-1$ + } + + /** + * Generates the default file name for the selected {@link EPackage}. + * + * @return the default file name for the selected {@link EPackage}. + */ + private String genDefaultFileName() { + String defaultFileName = Messages.CreateEMFModelWizard_modelNamePrefix; + EPackage ePackage = this.dataModel.getSelectedPackage(); + if (ePackage != null) { + String ePackageName = ePackage.getName(); + defaultFileName += Character.toUpperCase(ePackageName.charAt(0)) + ePackageName.substring(1); + } + return defaultFileName; + } + + /** + * Generates the default file extension for the selected {@link EPackage}, or {@value #XMI_DEFAULT_EXTENSION} if + * default extension can't be computed. + * + * @return the default file extension for the selected {@link EPackage}, or {@value #XMI_DEFAULT_EXTENSION} if + * default extension can't be computed. + */ + private String genDefaultExtensionName() { + String defaultExtensionName = null; + EPackage ePackage = this.dataModel.getSelectedPackage(); + if (ePackage != null) { + defaultExtensionName = ePackage.getName(); + } else { + defaultExtensionName = XMI_DEFAULT_EXTENSION; + } + return defaultExtensionName; + } +} diff --git a/plugins/org.eclipse.sirius.ui/src/org/eclipse/sirius/ui/tools/internal/wizards/newmodel/SelectEMFMetamodelWizardPage.java b/plugins/org.eclipse.sirius.ui/src/org/eclipse/sirius/ui/tools/internal/wizards/newmodel/SelectEMFMetamodelWizardPage.java new file mode 100644 index 0000000000..83c03b7181 --- /dev/null +++ b/plugins/org.eclipse.sirius.ui/src/org/eclipse/sirius/ui/tools/internal/wizards/newmodel/SelectEMFMetamodelWizardPage.java @@ -0,0 +1,296 @@ +/** + * Copyright (c) 2017 Obeo + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Obeo - initial API and implementation + */ +package org.eclipse.sirius.ui.tools.internal.wizards.newmodel; + +import java.util.Map.Entry; + +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.emf.ecore.provider.EcoreEditPlugin; +import org.eclipse.emf.edit.ui.provider.ExtendedImageRegistry; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.sirius.common.tools.DslCommonPlugin; +import org.eclipse.sirius.common.tools.api.ecore.EPackageMetaData; +import org.eclipse.sirius.viewpoint.provider.Messages; +import org.eclipse.swt.SWT; +import org.eclipse.swt.browser.Browser; +import org.eclipse.swt.custom.CLabel; +import org.eclipse.swt.events.KeyAdapter; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.dialogs.FilteredList; + +/** + * A wizard page allowing to select a metamodel ({@link EPackage}) from an {@link EPackage.Registry}. + * + * @see CreateEMFModelWizard + * + * @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a> + */ +public class SelectEMFMetamodelWizardPage extends WizardPage { + + /** The package separator used to display qualified name of an EPackage. */ + private static final String PACKAGE_SEPARATOR = "::"; //$NON-NLS-1$ + + /** The icon used for EPackages. */ + private final Image ePacakgeIcon = ExtendedImageRegistry.getInstance().getImage(EcoreEditPlugin.INSTANCE.getImage("full/obj16/EPackage")); //$NON-NLS-1$ + + /** The text field allowing to filter the list of EPackages. */ + private Text metamodelFilterText; + + /** The list of EPackages. */ + private FilteredList metamodelFilteredList; + + /** The filter used by the text field and the filtered list. */ + private String metamodelFilter; + + /** A browser to display documentation of the selected metamodel. */ + private Browser documentationBrowser; + + /** The EPackage.Registry containing the metamodels to display. */ + private EPackage.Registry packageRegistry; + + /** The data model used by the wizard and its pages. */ + private CreateEMFModelWizardDataModel dataModel; + + /** + * Default constructor for this page. + * + * @param packageRegistry + * an {@link EPackage.Registry}. + * @param dataModel + * the data model used by this page. + */ + public SelectEMFMetamodelWizardPage(EPackage.Registry packageRegistry, CreateEMFModelWizardDataModel dataModel) { + super("SelectEMFMetamodelWizardPage"); //$NON-NLS-1$ + this.packageRegistry = packageRegistry; + this.dataModel = dataModel; + setTitle(Messages.SelectEMFMetamodelWizardPage_title); + setDescription(Messages.SelectEMFMetamodelWizardPage_description); + } + + /** + * Sets the filter pattern. + * + * @param filter + * the filter pattern. + */ + public void setMetamodelFilter(String filter) { + if (this.metamodelFilterText == null) { + this.metamodelFilter = filter; + } else { + this.metamodelFilterText.setText(filter); + } + } + + /** + * Create contents of the wizard. + * + * @param parent + * the parent composite. + */ + public void createControl(Composite parent) { + Composite container = new Composite(parent, SWT.NULL); + + setControl(container); + container.setLayout(new GridLayout(1, false)); + + CLabel lblSelectMetamodel = new CLabel(container, SWT.NONE); + lblSelectMetamodel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false, 1, 1)); + lblSelectMetamodel.setText(Messages.SelectEMFMetamodelWizardPage_metamodelLabel); + + createMetamodelFilterText(container); + createMetamodelFilteredList(container); + setMetamodelFilter("*"); //$NON-NLS-1$ + + createMetamodelDocumentation(container); + } + + /** + * Create the text widget used to filter elements in the filtered list. + * + * @param parent + * the parent composite. + */ + private void createMetamodelFilterText(Composite parent) { + this.metamodelFilterText = new Text(parent, SWT.BORDER); + this.metamodelFilterText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); + this.metamodelFilterText.setText(this.metamodelFilter == null ? "" : this.metamodelFilter); //$NON-NLS-1$ + this.metamodelFilterText.addListener(SWT.Modify, event -> this.metamodelFilteredList.setFilter(this.metamodelFilterText.getText())); + this.metamodelFilterText.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.keyCode == SWT.ARROW_DOWN) { + metamodelFilteredList.setFocus(); + } + } + }); + } + + /** + * Create the filtered list widget used to display the {@link EPackage}s contained in the given + * {@link #packageRegistry}. + * + * @param parent + * the parent composite. + */ + private void createMetamodelFilteredList(Composite parent) { + this.metamodelFilteredList = new FilteredList(parent, SWT.BORDER | SWT.FULL_SELECTION | SWT.V_SCROLL | SWT.H_SCROLL | SWT.SINGLE, new MetamodelsListLabelProvider(), true, false, false); + this.metamodelFilteredList.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1)); + this.metamodelFilteredList.setElements(this.packageRegistry.entrySet().toArray()); + this.metamodelFilteredList.setFilter(this.metamodelFilter == null ? "" : this.metamodelFilter); //$NON-NLS-1$ + + this.metamodelFilteredList.addSelectionListener(new SelectionAdapter() { + @SuppressWarnings("unchecked") + @Override + public void widgetSelected(SelectionEvent e) { + Object[] selection = metamodelFilteredList.getSelection(); + if (selection.length == 1) { + dataModel.setSelectedPackage(((Entry<String, Object>) selection[0]).getValue()); + String documentation = getDocumentation(dataModel.getSelectedPackage()); + documentationBrowser.setText(documentation); + setPageComplete(true); + } else { + dataModel.setSelectedPackage(null); + documentationBrowser.setText(""); //$NON-NLS-1$ + setPageComplete(false); + } + } + }); + } + + /** + * Create a browser that allows to display documentation related to the selected metamodel. + * + * @param parent + * the parent composite. + */ + private void createMetamodelDocumentation(Composite parent) { + Label lblDocumentation = new Label(parent, SWT.NONE); + lblDocumentation.setText(Messages.SelectEMFMetamodelWizardPage_documentationLabel); + + this.documentationBrowser = new Browser(parent, SWT.BORDER); + this.documentationBrowser.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 2)); + } + + /** + * Get the qualified name of the given {@link EPackage}. + * + * @param ePackage + * the given {@link EPackage}. + * @return the qualified name of the given {@link EPackage}. + */ + private String getQualifiedName(EPackage ePackage) { + String ePackageQualifiedName = ""; //$NON-NLS-1$ + if (ePackage != null) { + EPackage eSuperPackage = ePackage.getESuperPackage(); + String eSuperPackageName = getQualifiedName(eSuperPackage); + if (eSuperPackageName != null && !eSuperPackageName.isEmpty()) { + ePackageQualifiedName += eSuperPackageName; + ePackageQualifiedName += PACKAGE_SEPARATOR; + } + ePackageQualifiedName += ePackage.getName(); + } + return ePackageQualifiedName; + } + + /** + * Get the label for the given {@link EPackage} from the registry of EPackageExtraData. + * + * @see EPackageExtraDataRegistry + * @param ePackage + * the given {@link EPackage}. + * @return the label for the given {@link EPackage}, or null if it can't be found. + */ + private String getLabelFromEPackageExtraData(EPackage ePackage) { + EPackageMetaData metaData = DslCommonPlugin.INSTANCE.getEPackageMetaData(ePackage.getNsURI()); + if (metaData != null) { + return metaData.getDisplayName(); + } else { + return null; + } + } + + /** + * Get the documentation for the given {@link EPackage} from the registry of EPackageExtraData. + * + * @see EPackageExtraDataRegistry + * @param ePackage + * the given {@link EPackage}. + * @return the documentation for the given {@link EPackage}, or null if it can't be found. + */ + private String getDocumentationFromEPackageExtraData(EPackage ePackage) { + EPackageMetaData metaData = DslCommonPlugin.INSTANCE.getEPackageMetaData(ePackage.getNsURI()); + if (metaData != null) { + return metaData.getDocumentation(); + } else { + return null; + } + } + + /** + * Get the documentation for the given {@link EPackage} from the registry of EPackageExtraData. + * + * @see EPackageExtraDataRegistry + * @param ePackage + * the given {@link EPackage}. + * @return the documentation for the given {@link EPackage}, or null if it can't be found. + */ + private String getDocumentation(EPackage ePackage) { + if (ePackage == null) { + return null; + } else { + String documentation = getDocumentationFromEPackageExtraData(ePackage); + if (documentation == null) { + documentation = "<code>" + ePackage.getNsURI() + "</code>"; //$NON-NLS-1$//$NON-NLS-2$ + } else { + documentation = documentation + "<br><br><smaller><code>" + ePackage.getNsURI() + "</code></smaller>"; //$NON-NLS-1$ //$NON-NLS-2$ + } + return documentation; + } + } + + /** + * A label provider for the {@link SelectEMFMetamodelWizardPage#metamodelFilteredList}. + */ + @SuppressWarnings("unchecked") + private class MetamodelsListLabelProvider extends LabelProvider { + @Override + public String getText(Object element) { + if (element instanceof Entry) { + String label = null; + Object value = ((Entry<String, Object>) element).getValue(); + if (value instanceof EPackage) { + label = getLabelFromEPackageExtraData((EPackage) value); + if (label == null) { + label = getQualifiedName((EPackage) value); + } + } else { + label = ((Entry<String, Object>) element).getKey(); + } + return label; + } + return super.getText(element); + } + + @Override + public Image getImage(Object element) { + return ePacakgeIcon; + } + } +} diff --git a/plugins/org.eclipse.sirius.ui/src/org/eclipse/sirius/ui/tools/internal/wizards/newmodel/SelectRootElementWizardPage.java b/plugins/org.eclipse.sirius.ui/src/org/eclipse/sirius/ui/tools/internal/wizards/newmodel/SelectRootElementWizardPage.java new file mode 100644 index 0000000000..7a77109a88 --- /dev/null +++ b/plugins/org.eclipse.sirius.ui/src/org/eclipse/sirius/ui/tools/internal/wizards/newmodel/SelectRootElementWizardPage.java @@ -0,0 +1,347 @@ +/** + * Copyright (c) 2017 Obeo + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Obeo - initial API and implementation + */ +package org.eclipse.sirius.ui.tools.internal.wizards.newmodel; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.stream.Collectors; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EClassifier; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.provider.EcoreEditPlugin; +import org.eclipse.emf.edit.ui.provider.ExtendedImageRegistry; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.sirius.common.tools.DslCommonPlugin; +import org.eclipse.sirius.common.tools.api.ecore.EPackageMetaData; +import org.eclipse.sirius.viewpoint.provider.Messages; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.CLabel; +import org.eclipse.swt.events.KeyAdapter; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.dialogs.FilteredList; + +/** + * A wizard page allowing to select a root element ({@link EClass}) from an {@link EPackage}. + * + * @see CreateEMFModelWizard + * + * @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a> + */ +public class SelectRootElementWizardPage extends WizardPage implements PropertyChangeListener { + + /** + * The icon used to represent EClasses. + */ + private Image eClassIcon = ExtendedImageRegistry.getInstance().getImage(EcoreEditPlugin.INSTANCE.getImage("full/obj16/EClass")); //$NON-NLS-1$ + + /** The text field allowing to filter the list of root elements. */ + private Text rootElementText; + + /** + * The checkbox allowing to only display root elements that are not child of other elements. + */ + private Button rootElementCheckbox; + + /** The list of root elements. */ + private FilteredList rootElementFilteredList; + + /** The filter used by the text field and the filtered list. */ + private String rootElementFilter; + + /** The data model used by the wizard and its pages. */ + private CreateEMFModelWizardDataModel dataModel; + + /** + * Default constructor for this page. + * + * @param dataModel + * the data model used by this page. + */ + public SelectRootElementWizardPage(CreateEMFModelWizardDataModel dataModel) { + super("SelectRootElementWizardPage"); //$NON-NLS-1$ + this.dataModel = dataModel; + setTitle(Messages.SelectRootElementWizardPage_title); + setDescription(Messages.SelectRootElementWizardPage_description); + } + + /** + * Sets the filter pattern. + * + * @param filter + * the filter pattern. + */ + public void setRootElementFilter(String filter) { + if (this.rootElementText == null) { + this.rootElementFilter = filter; + } else { + this.rootElementText.setText(filter); + } + } + + /** + * Create contents of the wizard. + * + * @param parent + * the parent composite. + */ + public void createControl(Composite parent) { + Composite container = new Composite(parent, SWT.NULL); + + setControl(container); + container.setLayout(new GridLayout(1, false)); + + CLabel lblSelectRootElement = new CLabel(container, SWT.NONE); + lblSelectRootElement.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false, 1, 1)); + lblSelectRootElement.setText(Messages.SelectRootElementWizardPage_label); + + createRootElementFilterText(container); + createRootElementCheckbox(container); + createRootElementFilteredList(container); + setRootElementFilter("*"); //$NON-NLS-1$ + this.rootElementCheckbox.setSelection(true); + } + + /** + * Create the text widget used to filter elements in the filtered list. + * + * @param parent + * the parent composite. + */ + private void createRootElementFilterText(Composite parent) { + this.rootElementText = new Text(parent, SWT.BORDER); + this.rootElementText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); + this.rootElementText.setText(this.rootElementFilter == null ? "" : this.rootElementFilter); //$NON-NLS-1$ + this.rootElementText.addListener(SWT.Modify, event -> this.rootElementFilteredList.setFilter(this.rootElementText.getText())); + this.rootElementText.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.keyCode == SWT.ARROW_DOWN) { + rootElementFilteredList.setFocus(); + } + } + }); + } + + /** + * Create the checkbox widget used to display only elements that are not child of other elements in the filtered + * list. + * + * @param parent + * the parent composite. + */ + private void createRootElementCheckbox(Composite parent) { + this.rootElementCheckbox = new Button(parent, SWT.CHECK); + this.rootElementCheckbox.setLayoutData(new GridData(SWT.FILL, SWT.LEFT, true, false, 1, 1)); + this.rootElementCheckbox.setText(Messages.SelectRootElementWizardPage_checkboxLabel); + this.rootElementCheckbox.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + updateRootElementFilteredList(); + } + }); + } + + /** + * Create the filtered list widget used to display the root elements contained in the given + * {@link #selectedPackage}. + * + * @param parent + * the parent composite. + */ + private void createRootElementFilteredList(Composite parent) { + this.rootElementFilteredList = new FilteredList(parent, SWT.BORDER | SWT.FULL_SELECTION | SWT.V_SCROLL | SWT.H_SCROLL | SWT.SINGLE, new RootElementsListLabelProvider(), true, false, false); + this.rootElementFilteredList.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1)); + this.rootElementFilteredList.setFilter(this.rootElementFilter == null ? "" : this.rootElementFilter); //$NON-NLS-1$ + this.rootElementFilteredList.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + updateSelectedRootElement(); + } + }); + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (CreateEMFModelWizardDataModel.SELECTED_PACKAGE_EVENT.equals(evt.getPropertyName())) { + updateRootElementFilteredList(); + updateSelectedRootElement(); + } + } + + /** + * Update the selected root element and validate the page. + */ + private void updateSelectedRootElement() { + Object[] selection = this.rootElementFilteredList.getSelection(); + if (selection.length == 1) { + this.dataModel.setSelectedRootElement((EClass) selection[0]); + setPageComplete(true); + } else { + EClass preferredRootElement = getPreferredRootElementFromEPackageExtraData(this.dataModel.getSelectedPackage()); + if (preferredRootElement != null) { + this.dataModel.setSelectedRootElement(preferredRootElement); + setPageComplete(true); + } else { + this.dataModel.setSelectedRootElement(null); + setPageComplete(false); + } + } + } + + /** + * Update the root element filtered list according to the value of the {@link #rootElementCheckbox}. + */ + private void updateRootElementFilteredList() { + if (this.rootElementCheckbox != null) { + Object[] classesArray; + if (this.rootElementCheckbox.getSelection()) { + classesArray = getFilteredConcreteClasses(this.dataModel.getSelectedPackage()).toArray(); + } else { + classesArray = getConcreteClasses(this.dataModel.getSelectedPackage()).toArray(); + } + this.rootElementFilteredList.setElements(classesArray); + EClass preferredRootElement = getPreferredRootElementFromEPackageExtraData(this.dataModel.getSelectedPackage()); + if (preferredRootElement != null) { + this.rootElementFilteredList.setSelection(new Object[] { preferredRootElement }); + } + } + } + + /** + * Get the concrete classes from the given {@link EPackage} (classes that are not abstract and that are not + * interfaces). + * + * @param ePackage + * the given {@link EPackage}. + * @return the concrete classes from the given {@link EPackage}. + */ + private Collection<EClass> getConcreteClasses(EPackage ePackage) { + Collection<EClass> concreteClasses = new HashSet<>(); + if (ePackage != null) { + for (EClassifier eClassifier : ePackage.getEClassifiers()) { + if (eClassifier instanceof EClass && !((EClass) eClassifier).isAbstract() && !((EClass) eClassifier).isInterface()) { + concreteClasses.add((EClass) eClassifier); + } + } + } + return concreteClasses; + } + + /** + * Get the filtered concrete classes from the given {@link EPackage} (classes that are not abstract and that are not + * interfaces, and that are not child of other classes). + * + * @param ePackage + * the given {@link EPackage}. + * @return the filtered concrete classes from the given {@link EPackage}. + */ + private Collection<EClass> getFilteredConcreteClasses(EPackage ePackage) { + /* + * We'll only consider actually instanciable classes. + */ + Collection<EClass> concreteClasses = getConcreteClasses(ePackage); + + /* + * If we have explicit metadata associated with the EPackage, use the suggested roots. + */ + if (ePackage != null) { + String nsURI = ePackage.getNsURI(); + EPackageMetaData metaData = DslCommonPlugin.INSTANCE.getEPackageMetaData(nsURI); + List<EClass> roots = new ArrayList<>(); + if (metaData != null && !metaData.getSuggestedRoots().isEmpty()) { + roots = concreteClasses.stream().filter(c -> metaData.getSuggestedRoots().contains(c.getName())).collect(Collectors.toList()); + } + if (!roots.isEmpty()) { + return roots; + } + } + + /* + * Otherwise, or if there is no instanciable suggested root, try to infer good candidates from the metamodel's + * structure (i.e. prefer elements which are not contained by anything). + */ + return inferRootElementsCandidates(concreteClasses); + } + + private Collection<EClass> inferRootElementsCandidates(Collection<EClass> concreteClasses) { + Collection<EClass> filteredConcreteClasses = new HashSet<>(concreteClasses); + for (EClass eClass : concreteClasses) { + if (filteredConcreteClasses.contains(eClass)) { + eClass.getEAllReferences().stream().filter(EReference::isContainment).forEach(eReference -> { + EClassifier eType = eReference.getEType(); + if (concreteClasses.contains(eType) && eType != eClass) { + filteredConcreteClasses.remove(eType); + } else { + concreteClasses.stream().filter(c -> c.getEAllSuperTypes().contains(eType)).forEach(c -> filteredConcreteClasses.remove(c)); + } + }); + } + } + return filteredConcreteClasses; + } + + /** + * Get the preferred root element for the given {@link EPackage} from the registry of EPackageExtraData. + * + * @see EPackageExtraDataRegistry + * @param ePackage + * the given {@link EPackage}. + * @return the preferred root element for the given {@link EPackage}, or null if it can't be found. + */ + private EClass getPreferredRootElementFromEPackageExtraData(EPackage ePackage) { + if (ePackage != null) { + String nsURI = ePackage.getNsURI(); + EPackageMetaData metaData = DslCommonPlugin.INSTANCE.getEPackageMetaData(nsURI); + if (metaData != null && !metaData.getSuggestedRoots().isEmpty()) { + EClassifier result = ePackage.getEClassifier(metaData.getSuggestedRoots().get(0)); + if (result instanceof EClass) { + return (EClass) result; + } + } + } + return null; + } + + /** + * A label provider for the {@link SelectRootElementWizardPage#rootElementFilteredList}. + */ + private class RootElementsListLabelProvider extends LabelProvider { + + @Override + public String getText(Object element) { + if (element instanceof EClass) { + return ((EClass) element).getName(); + } + return super.getText(element); + } + + @Override + public Image getImage(Object element) { + return eClassIcon; + } + } + +} diff --git a/plugins/org.eclipse.sirius.ui/src/org/eclipse/sirius/viewpoint/provider/Messages.java b/plugins/org.eclipse.sirius.ui/src/org/eclipse/sirius/viewpoint/provider/Messages.java index ceb157ff81..8bf6cb4a23 100644 --- a/plugins/org.eclipse.sirius.ui/src/org/eclipse/sirius/viewpoint/provider/Messages.java +++ b/plugins/org.eclipse.sirius.ui/src/org/eclipse/sirius/viewpoint/provider/Messages.java @@ -211,6 +211,15 @@ public final class Messages { public static String CopyRepresentationAction_name; @TranslatableMessage + public static String CreateEMFModelWizard_windowTitle; + + @TranslatableMessage + public static String CreateEMFModelWizard_modelNamePrefix; + + @TranslatableMessage + public static String CreateEMFModelWizard_errorInstantiateRootElement; + + @TranslatableMessage public static String CreateOrAddResourceWizard_initialEObject; @TranslatableMessage @@ -604,6 +613,15 @@ public final class Messages { public static String MoveRepresentationAction_text; @TranslatableMessage + public static String NameAndLocationWizardPage_title; + + @TranslatableMessage + public static String NameAndLocationWizardPage_description; + + @TranslatableMessage + public static String NameAndLocationWizardPage_errorMessage; + + @TranslatableMessage public static String NavigateToCommand_name; @TranslatableMessage @@ -790,6 +808,30 @@ public final class Messages { public static String SelectAnalysisFilePage_newFileGroup_button; @TranslatableMessage + public static String SelectEMFMetamodelWizardPage_title; + + @TranslatableMessage + public static String SelectEMFMetamodelWizardPage_description; + + @TranslatableMessage + public static String SelectEMFMetamodelWizardPage_metamodelLabel; + + @TranslatableMessage + public static String SelectEMFMetamodelWizardPage_documentationLabel; + + @TranslatableMessage + public static String SelectRootElementWizardPage_title; + + @TranslatableMessage + public static String SelectRootElementWizardPage_description; + + @TranslatableMessage + public static String SelectRootElementWizardPage_label; + + @TranslatableMessage + public static String SelectRootElementWizardPage_checkboxLabel; + + @TranslatableMessage public static String SelectMetamodelWizardPage_browseButton; @TranslatableMessage |
