| <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> |
| <html> |
| <head> |
| <title>Integrating EMF and GMF Generated Editors</title> |
| <meta http-equiv="Content-Type" |
| content="text/html; charset=windows-1252"> |
| <link href="../article.css" type="text/css" rel="stylesheet"> |
| </head> |
| <body> |
| |
| <h1>Integrating EMF and GMF Generated Editors</h1> |
| <div class="summary"> |
| <h2>Summary</h2> |
| <p>This article provides a walk-through example how to combine the |
| editor plug-ins generated by the Eclipse Modeling Framework (EMF) and |
| the Graphical Modeling Framework (GMF) to create an integrated editor |
| that supports graphical and list- oder tree-based editing of the same |
| information. The approach outlined in this article can easily be used as |
| a starting point for the implementation of arbitrary multi-page editor |
| solutions that contain GMF-based graphical editors.</p> |
| <div class="author">by Volker Wegert and Alex Shatalin</div> |
| <div class="copyright">Copyright © 2008 Volker Wegert and |
| Alex Shatalin</div> |
| <div class="date">March 20, 2008</div> |
| </div> |
| |
| <div class="content"> |
| <h2>Table of Contents</h2> |
| <ol> |
| <li><a href="#introduction">Introduction</a></li> |
| <li><a href="#setting_the_stage">Setting the Stage</a></li> |
| <li><a href="#upgrading_the_editing_domain">Upgrading the |
| Editing Domain</a></li> |
| <li><a href="#extending_the_editor_input">Extending the Editor |
| Input</a></li> |
| <li><a href="#dissecting_the_emf_editor">Dissecting the EMF |
| Editor</a></li> |
| <li><a href="#selection_handling">Selection Handling</a></li> |
| <li><a href="#combining_the_editors">Combining the Editors</a></li> |
| <li><a href="#taming_the_properties">Taming the Properties</a></li> |
| <li><a href="#completing_menus_and_toolbars">Completing Menus |
| and Toolbars</a></li> |
| <li><a href="#fixing_edit_menu_actions">Fixing Edit Menu |
| Actions</a></li> |
| <li><a href="#creating_the_diagram">Creating the Diagram</a></li> |
| <li><a href="#fixing_the_resource_navigator">Fixing the |
| Resource Navigator</a></li> |
| <li><a href="#rcp">Want some RCP with that?</a></li> |
| <li><a href="#miscellaneous_notes">Miscellaneous Notes</a></li> |
| <li><a href="#conclusion">Conclusion</a></li> |
| <li><a href="#acknowledgements">Acknowledgements</a></li> |
| </ol> |
| |
| <h2><a name="introduction">Introduction</a></h2> |
| <p>Both the Eclipse Modeling Framework (<a |
| href="http://www.eclipse.org/emf">EMF</a>) and the Graphical Modeling |
| Framework (<a href="http://www.eclipse.org/gmf">GMF</a>) are capable of |
| generating editor plug-ins. However, there are many use cases where it's |
| not a question of <i>traditional widget-based editor or graphical |
| editor</i>—a joint solution that provides both widget-based and |
| graphical editing capabilities is called for. Unfortunately, it's not a |
| trivial task to combine the generated editors. This article walks you |
| through the steps required to create an integrated editor. Although we |
| start off from scratch, you should have some basic knowledge about how |
| both EMF and GMF work.</p> |
| |
| <p>This article uses a simple example model—about the smallest |
| working example possible—shown in the following image.</p> |
| <p><img src="screenshots/EcoreModel.png" alt="Topic Map Ecore Model" /></p> |
| <p>The main object stored in a file is a topic map that consists of |
| an arbitrary number of topics and associations. Both topics and |
| associations are labeled. An association always connects two topics.</p> |
| |
| <p>If you just want to see the combined editor in action, you can |
| grab an <a href="files/projects-final-state.zip">archive</a> containing |
| the result of the actions described below.</p> |
| |
| <h2><a name="setting_the_stage">Setting the Stage</a></h2> |
| <p>Before we can try to integrate the generated editors, we first |
| need to setup some playground projects. This is covered by the |
| documentation provided for EMF and GMF, so let's not get lost in the |
| details. You can use the <a href="files/projects-initial-state.zip">archived |
| example projects</a> provided with this article, they contain the models |
| used in this article as well as the generated plug-ins in an "untainted" |
| state. If you prefer to setup your environment manually, just follow |
| this checklist.</p> |
| <ol> |
| <li>Create an empty EMF project and name it <tt>org.example.emfgmf</tt>.</li> |
| <li>Create a new Ecore model as shown above or use the <a |
| href="files/example-models.zip">archive containing the models</a> used |
| in this article. This archive also contains the GMF models to save you |
| some work.</li> |
| <li>Create a generator model for the Ecore model. Remember to |
| adjust the package properties.</li> |
| <li>Generate the model, edit and editor plug-ins. There's no need |
| to generate the test classes, but it doesn't hurt either.</li> |
| <li>Create a Graphical Definition and a Tooling Definition using |
| the wizard.</li> |
| <li>Create a Mapping Model using the wizard. You'll have to adapt |
| the label mappings if you use the sample model provided since the |
| wizard doesn't generate sensible values here.</li> |
| <li>Create a GMF generator model. <b>Change the property <i>Same |
| File for Diagram And Model</i> to <tt>true</tt> and change diagram file |
| extension to match the domain file extension</b> - if you skip this, things |
| will get a bit more complicated later.</li> |
| <li>Generate the diagram editor.</li> |
| </ol> |
| <blockquote> |
| <p><img src="images/tryit.gif" width="61" height="13" alt="Try it!"> |
| You can now launch an Eclipse runtime with the generated plug-ins. |
| Create a project and some topic map to play with. Try editing your topic |
| map with both the tree-like EMF editor and the GMF-based diagram editor |
| and watch the results.</p> |
| </blockquote> |
| |
| <p><img src="screenshots/GeneratedEMFEditor.png" |
| alt="Editor generated by the EMF" /></p> |
| |
| <p><img src="screenshots/GeneratedGMFEditor.png" |
| alt="Editor generated by the GMF" /></p> |
| |
| <p>Now, let's put these two editors together...</p> |
| |
| <h2><a name="upgrading_the_editing_domain">Upgrading the |
| Editing Domain</a></h2> |
| |
| <p>Both the EMF-based and the GMF-based editor use an <tt>EditingDomain</tt> |
| to handle the model objects generated from the Ecore model. To allow for |
| simultaneous modifications of the same model instances in the two editor |
| areas, we have to modify generated code to share the same <tt>EditingDomain</tt> |
| instance across both the EMF-based and the GMF-based editor.</p> |
| |
| <p>The GMF-based diagram editor uses a <tt>TransactionalEditingDomain</tt> |
| while the EMF editor "only" requires an <tt>AdapterFactoryEditingDomain</tt>. |
| In order to combine the editors, we have to "upgrade" the EMF editor to |
| create a <tt>TransactionalEditingDomain</tt> that can then be passed |
| along to the diagram editor. Open the <tt>TopicmapEditor</tt> source and |
| adapt the generated implementation of <tt>initializeEditingDomain()</tt> |
| as shown in the following code: |
| <pre> |
| protected void initializeEditingDomain() { |
| // Create an adapter factory that yields item providers. |
| // |
| adapterFactory = new ComposedAdapterFactory(ComposedAdapterFactory.Descriptor.Registry.INSTANCE); |
| |
| adapterFactory.addAdapterFactory(new ResourceItemProviderAdapterFactory()); |
| adapterFactory.addAdapterFactory(new TopicmapItemProviderAdapterFactory()); |
| adapterFactory.addAdapterFactory(new ReflectiveItemProviderAdapterFactory()); |
| |
| <b>// Create a transactional editing domain |
| // |
| TransactionalEditingDomain domain = TransactionalEditingDomain.Factory.INSTANCE.createEditingDomain(); |
| domain.setID("org.example.emfgmf.diagram.EditingDomain");</b> |
| |
| // Add a listener to set the most recent command's affected objects to be the selection of the viewer with focus. |
| // |
| <b>domain.getCommandStack()</b>.addCommandStackListener( |
| new CommandStackListener() { |
| public void commandStackChanged(final EventObject event) { |
| getContainer().getDisplay().asyncExec |
| (new Runnable() { |
| public void run() { |
| firePropertyChange(IEditorPart.PROP_DIRTY); |
| |
| // Try to select the affected objects. |
| // |
| Command mostRecentCommand = ((CommandStack) event.getSource()).getMostRecentCommand(); |
| if (mostRecentCommand != null) { |
| setSelectionToViewer(mostRecentCommand.getAffectedObjects()); |
| } |
| if (propertySheetPage != null && !propertySheetPage.getControl().isDisposed()) { |
| propertySheetPage.refresh(); |
| } |
| } |
| }); |
| } |
| }); |
| |
| <b>editingDomain = (AdapterFactoryEditingDomain) domain;</b> |
| }</pre> |
| <blockquote> |
| |
| <p><img src="images/tip.gif" width="62" height="13" alt="Tip"> |
| Don't forget to set the <tt>@generated</tt> tag to an arbitrary value to |
| prevent your changes from being overwritten.</p> |
| </blockquote> |
| <p>Also, in order for this to work, the <tt>org.example.emfgmf.editor</tt> |
| plugin needs another dependency to <tt>org.eclipse.emf.transaction</tt>.</p> |
| |
| <p>Make sure that the ID used to initialize the editing domain |
| matches the editing domain ID specified in the GMF generator model |
| (select the <i>Gen Diagram</i> node as shown in the screenshot below).</p> |
| |
| <p><img src="screenshots/EditingDomainID.png" |
| alt="Keeping the Editing Domain IDs in sync" /></p> |
| |
| <h2><a name="extending_the_editor_input">Extending the Editor |
| Input</a></h2> |
| |
| <p>We have already modified the <tt>TopicmapEditor</tt> to create a |
| transactional <tt>EditingDomain</tt> instance. The next step is to pass |
| this instance to each page of the multi-page editor. An <tt>EditorPart</tt> |
| receives its input using some descendant of <tt>IEditorInput</tt>, |
| usually a <tt>FileEditorInput</tt>. In our use case, we need to include |
| a reference to our central <tt>EditingDomain</tt> instance. In general, |
| any implementation of <tt>IEditorInput</tt> could be used as input |
| object for the <tt>EditorPart</tt>s contained in the multi-page editor. |
| Fortunately, the GMF code base already contains a convenient wrapper for |
| our task: the <tt>FileEditorInputProxy</tt>.</p> |
| |
| <p>While it is tempting to simply replace the editor's input with |
| the wrapped-up input, this should be avoided since it causes trouble e. |
| g. with the problem markers. As a rule of thumb, an editor should always |
| return the same input object that it was initialized with or that was |
| passed to it, no matter what happens internally. In our case, we just |
| add an attribute of type <tt>FileEditorInputProxy</tt> to the <tt>TopicmapEditor</tt> |
| and add a method for the lazy initialization of this attribute:</p> |
| <pre> |
| protected IEditorInput getWrappedInput() { |
| if (wrappedInput == null) { |
| if (getEditorInput() instanceof IFileEditorInput) { |
| wrappedInput = new FileEditorInputProxy((IFileEditorInput)getEditorInput(), |
| (TransactionalEditingDomain) getEditingDomain()); |
| } else { |
| wrappedInput = getEditorInput(); |
| } |
| } |
| return wrappedInput; |
| }</pre> |
| <p>You will note that references to <tt>FileEditorInputProxy</tt> |
| can not be resolved at this point - simply because this class is |
| provided by the GMF, and the EMF generated editor plug-in does not know |
| about the GMF yet. We now have two means to resolve this issue: either |
| add the dependencies to the EMF editor plug-in or "inherit" the |
| dependencies from the GMF editor plug-in. Let's take the second |
| approach: Open the <tt>org.example.emfgmf.editor</tt> plug-in manifest |
| and add a dependency to <tt>org.example.emfgmf.diagram</tt>. Then open |
| the <tt>org.example.emfgmf.diagram</tt> plug-in manifest and set the |
| flag <i>reexport this dependency</i> for all <tt>org.eclipse.gmf.*</tt> |
| plug-ins.</p> |
| <blockquote> |
| <p><img src="images/tip.gif" width="62" height="13" alt="Tip"> |
| It's usually faster to change the file <tt>MANIFEST.MF</tt> manually.</p> |
| </blockquote> |
| <p>We also have to adapt the generated class <tt>TopicMapDocumentProvider</tt>. |
| You may have noted that this class refers to <tt>FileEditorInput</tt> in |
| many places, and this leads to trouble as soon as you pass it a <tt>FileEditorInputProxy</tt>. |
| Fortunately we can simply replace <tt>FileEditorInput</tt> with <tt>IFileEditorInput</tt> |
| in the entire class except for the instantiation in <tt>handleElementMoved()</tt>. |
| We then have to tell the <tt>TopicMapDocumentProvider</tt> not to create |
| its own Editing Domain, but to use the one provided by the <tt>FileEditorInputProxy</tt>. |
| The easiest way to do this is to adapt the method <tt>createEmptyDocument()</tt>:</p> |
| <pre> |
| protected IDocument createEmptyDocument() { |
| <b>return createEmptyDocument(null);</b> |
| } |
| |
| <b>protected IDocument createEmptyDocument(Object input) { |
| DiagramDocument document = new DiagramDocument(); |
| if (input instanceof FileEditorInputProxy) { |
| FileEditorInputProxy proxy = (FileEditorInputProxy) input; |
| document.setEditingDomain(proxy.getEditingDomain()); |
| } else { |
| document.setEditingDomain(createEditingDomain()); |
| } |
| return document; |
| }</b></pre> |
| <p>In addition to this, we have to change the call to <tt>createEmptyDocument()</tt> |
| in <tt>createDocument()</tt>:</p> |
| <pre> |
| protected IDocument createDocument(Object element) throws CoreException { |
| //... |
| |
| <b>IDocument document = createEmptyDocument(element);</b> |
| setDocumentContent(document, (IEditorInput) element); |
| setupDocument(element, document); |
| return document; |
| }</pre> |
| <p>We won't be able to see the effects of these changes until later, |
| though.</p> |
| |
| <h2><a name="dissecting_the_emf_editor">Dissecting the EMF |
| Editor</a></h2> |
| <p>The <tt>TopicmapEditor</tt> generated by the EMF is derived from |
| <tt>MultiPageEditorPart</tt>, and this class already provides means to |
| handle multiple editor pages. Unfortunately for us, the generated |
| example creates the editor pages using anonymous subclasses of <tt>ViewerPane</tt>. |
| This means that the <tt>TopicmapEditor</tt> itself has to override some |
| methods of the <tt>MultiPageEditorPart</tt> that are intended to save |
| the developer of descendants some work. While this is perfectly |
| acceptable for a generated example, it becomes a problem when adding the |
| GMF generated editor to the <tt>TopicmapEditor</tt>. Our next task will |
| therefore be to split the generated editor into separate <tt>EditorPart</tt> |
| subclasses. We will do so by creating subclasses of <tt>EditorPart</tt> |
| that will be responsible for one page each. Since we have six pages to |
| deal with, it's worth introducing a common superclass named <tt>TopicmapEditorPart</tt> |
| that extends <tt>EditorPart</tt> and implements <tt>IMenuListener</tt>. |
| We make this class abstract and add some common method implementations:</p> |
| <pre> |
| public abstract class TopicmapEditorPart extends EditorPart implements IMenuListener, IEditingDomainProvider { |
| |
| protected TopicmapEditor parentEditor; |
| |
| public TopicmapEditorPart(TopicmapEditor parent) { |
| super(); |
| this.parentEditor = parent; |
| } |
| |
| protected static String getString(String key) { |
| return TopicMapEditorPlugin.INSTANCE.getString(key); |
| } |
| |
| public EditingDomain getEditingDomain() { |
| return parentEditor.getEditingDomain(); |
| } |
| |
| protected BasicCommandStack getCommandStack() { |
| return ((BasicCommandStack)getEditingDomain().getCommandStack()); |
| } |
| |
| protected AdapterFactory getAdapterFactory() { |
| return ((AdapterFactoryEditingDomain) ((FileEditorInputProxy)getEditorInput()).getEditingDomain()).getAdapterFactory(); |
| } |
| |
| protected void createContextMenuFor(StructuredViewer viewer) { |
| MenuManager contextMenu = new MenuManager("#PopUp"); |
| contextMenu.add(new Separator("additions")); |
| contextMenu.setRemoveAllWhenShown(true); |
| contextMenu.addMenuListener(this); |
| Menu menu= contextMenu.createContextMenu(viewer.getControl()); |
| viewer.getControl().setMenu(menu); |
| getSite().registerContextMenu(contextMenu, new UnwrappingSelectionProvider(viewer)); |
| |
| int dndOperations = DND.DROP_COPY | DND.DROP_MOVE | DND.DROP_LINK; |
| Transfer[] transfers = new Transfer[] { LocalTransfer.getInstance() }; |
| viewer.addDragSupport(dndOperations, transfers, new ViewerDragAdapter(viewer)); |
| viewer.addDropSupport(dndOperations, transfers, new EditingDomainViewerDropAdapter(getEditingDomain(), viewer)); |
| } |
| |
| @Override |
| public void doSave(IProgressMonitor monitor) { |
| // nothing to do here - this is handled by the parent editor |
| } |
| |
| @Override |
| public void doSaveAs() { |
| // nothing to do here - this is handled by the parent editor |
| } |
| |
| @Override |
| public void init(IEditorSite site, IEditorInput input) throws PartInitException { |
| setSite(site); |
| setInput(input); |
| } |
| |
| @Override |
| public boolean isDirty() { |
| return getCommandStack().isSaveNeeded(); |
| } |
| |
| @Override |
| public boolean isSaveAsAllowed() { |
| return true; |
| } |
| |
| public void menuAboutToShow(IMenuManager manager) { |
| // pass the request to show the context menu on to the parent editor |
| ((IMenuListener)parentEditor.getEditorSite().getActionBarContributor()).menuAboutToShow(manager); |
| } |
| |
| public abstract void setInput(Object input); |
| }</pre> |
| <p>Now that we've got the base class, we can create the <tt>EditorPart</tt> |
| derivates to represent the pages of the editor. Each of the editor pages |
| becomes a separate class, and since these classes look rather similar, |
| we'll only take a look at one of them as an example:</p> |
| <pre> |
| public class TableTreeEditorPart extends TopicmapEditorPart { |
| |
| protected TreeViewer viewer; |
| |
| public TableTreeEditorPart(TopicmapEditor parent) { |
| super(parent); |
| } |
| |
| @Override |
| public void createPartControl(Composite parent) { |
| viewer = new TreeViewer(parent, SWT.NONE); |
| Tree tree = viewer.getTree(); |
| tree.setLayoutData(new FillLayout()); |
| tree.setHeaderVisible(true); |
| tree.setLinesVisible(true); |
| |
| TreeColumn objectColumn = new TreeColumn(tree, SWT.NONE); |
| objectColumn.setText(getString("_UI_ObjectColumn_label")); |
| objectColumn.setResizable(true); |
| objectColumn.setWidth(250); |
| |
| TreeColumn selfColumn = new TreeColumn(tree, SWT.NONE); |
| selfColumn.setText(getString("_UI_SelfColumn_label")); |
| selfColumn.setResizable(true); |
| selfColumn.setWidth(200); |
| |
| viewer.setColumnProperties(new String [] {"a", "b"}); |
| viewer.setContentProvider(new AdapterFactoryContentProvider(getAdapterFactory())); |
| viewer.setLabelProvider(new AdapterFactoryLabelProvider(getAdapterFactory())); |
| |
| createContextMenuFor(viewer); |
| getEditorSite().setSelectionProvider(viewer); |
| } |
| |
| @Override |
| public void setFocus() { |
| viewer.getTree().setFocus(); |
| } |
| |
| @Override |
| public void setInput(Object input) { |
| viewer.setInput(input); |
| } |
| }</pre> |
| <p>In order to get the <tt>ParentEditorPart</tt> working, we have to |
| extract the inner class <tt>ReverseAdapterFactoryContentProvider</tt> |
| from the generated <tt>TopicmapEditor</tt> class - fortunately, with the |
| refactoring capabilites of Eclipse, that's only a matter of a few mouse |
| clicks.</p> |
| <p>These classes are then instantiated by the <tt>TopicmapEditor</tt> |
| in its method <tt>createPages()</tt> - this is also where the code to |
| create the contents of each editor part originates from. The generated |
| attributes that hold the viewers are replaced by attributes that keep |
| the editor parts. The instantiation procedure now looks like this:</p> |
| <pre> |
| public void createPages() { |
| // Creates the model from the editor input |
| // |
| createModel(); |
| |
| // Only creates the other pages if there is something that can be edited |
| // |
| if (!getEditingDomain().getResourceSet().getResources().isEmpty() && |
| !(getEditingDomain().getResourceSet().getResources().get(0)).getContents().isEmpty()) { |
| |
| <b>try { |
| int pageIndex; |
| |
| // Create a page for the selection tree view. |
| // |
| selectionEditorPart = new SelectionEditorPart(this); |
| pageIndex = addPage(selectionEditorPart, getWrappedInput()); |
| setPageText(pageIndex, getString("_UI_SelectionPage_label")); |
| selectionEditorPart.setInput(getEditingDomain().getResourceSet()); |
| |
| // Create a page for the parent tree view. |
| // |
| parentEditorPart = new ParentEditorPart(this); |
| pageIndex = addPage(parentEditorPart, getWrappedInput()); |
| setPageText(pageIndex, getString("_UI_ParentPage_label")); |
| |
| // This is the page for the list viewer |
| // |
| listEditorPart = new ListEditorPart(this); |
| pageIndex = addPage(listEditorPart, getWrappedInput()); |
| setPageText(pageIndex, getString("_UI_ListPage_label")); |
| |
| // This is the page for the tree viewer |
| // |
| treeEditorPart = new TreeEditorPart(this); |
| pageIndex = addPage(treeEditorPart, getWrappedInput()); |
| setPageText(pageIndex, getString("_UI_TreePage_label")); |
| |
| |
| // This is the page for the table viewer. |
| // |
| tableEditorPart = new TableEditorPart(this); |
| pageIndex = addPage(tableEditorPart, getWrappedInput()); |
| setPageText(pageIndex, getString("_UI_TablePage_label")); |
| |
| // This is the page for the table tree viewer. |
| // |
| tableTreeEditorPart = new TableTreeEditorPart(this); |
| pageIndex = addPage(tableTreeEditorPart, getWrappedInput()); |
| setPageText(pageIndex, getString("_UI_TreeWithColumnsPage_label")); |
| |
| } catch (PartInitException e) { |
| // add some error handling for production-quality coding |
| e.printStackTrace(); |
| }</b> |
| |
| getSite().getShell().getDisplay().asyncExec |
| (new Runnable() { |
| public void run() { |
| setActivePage(0); |
| } |
| }); |
| } |
| |
| // ... |
| }</pre> |
| <p>Having entrusted the handling of the editor parts to the <tt>MultipageEditorPart</tt>, |
| we can remove the attribute <tt>currentViewerPane</tt> and the methods <tt>setCurrentViewerPane()</tt> |
| and <tt>setFocus()</tt>. The reimplementation of <tt>isDirty()</tt> can |
| also be removed.</p> |
| |
| <h2><a name="selection_handling">Selection Handling</a></h2> |
| <p>Now we have split up the generated editor, but this has disrupted |
| the generated coding for selection handling - we have to clean this up |
| before we move on. The <tt>MultiPageEditorPart</tt> already comes with a |
| mechanism to handle selection changes (see classes <tt>MultiPageEditorSite</tt> |
| and <tt>MultiPageSelectionProvider</tt>), but this mechanism is not used |
| by the generated coding - yet.</p> |
| <p>First we add a new attribute named <tt>selectionProvider</tt> of |
| type <tt>MultiPageSelectionProvider</tt> to the <tt>TopicmapEditor</tt> |
| and make the constructor of the editor class initialize this attribute |
| (passing itself as argument). Then we change the method <tt>init()</tt> |
| to set this instance as a site-wide selection provider instead of the |
| editor itself. The <tt>TopicmapEditor</tt> itself now no longer needs to |
| be a selection provider, so we can remove the interface implementation |
| of <tt>ISelectionProvider</tt> along with the generated attributes <tt>selectionChangedListener</tt>, |
| <tt>selectionChangedListeners</tt> and <tt>editorSelection</tt> as well |
| as the generated methods <tt>addSelectionChangedListener()</tt>, <tt>removeSelectionChangedListener()</tt>, |
| <tt>getSelection()</tt> and <tt>setSelection()</tt>. Note that <tt>setSelection()</tt> |
| contains a line to modify the status line manager - we'll move this call |
| into a separate listener that is initialized by the constructor:</p> |
| <pre> |
| public TopicmapEditor() { |
| super(); |
| initializeEditingDomain(); |
| <b>selectionProvider = new MultiPageSelectionProvider(this); |
| selectionProvider.addSelectionChangedListener(new ISelectionChangedListener() { |
| public void selectionChanged(SelectionChangedEvent event) { |
| setStatusLineManager(event.getSelection()); |
| } |
| });</b> |
| }</pre> |
| <p>We still need to rewrite several references to the methods we |
| just removed. <tt>handleActivate()</tt> contains a call to <tt>setSelection()</tt> |
| that can easily be replaced by a call to the selection provider:</p> |
| <pre> |
| protected void handleActivate() { |
| // Recompute the read only state. |
| // |
| if (editingDomain.getResourceToReadOnlyMap() != null) { |
| editingDomain.getResourceToReadOnlyMap().clear(); |
| |
| // Refresh any actions that may become enabled or disabled. |
| // |
| <b>selectionProvider.setSelection(selectionProvider.getSelection());</b> |
| } |
| |
| // ... |
| }</pre> |
| <p>We can now adapt the method <tt>setCurrentViewer()</tt> and throw |
| out everything that's concerned with selection handling:</p> |
| <pre> |
| public void setCurrentViewer(Viewer viewer) { |
| if (currentViewer != viewer) { |
| currentViewer = viewer; |
| } |
| } |
| </pre> |
| <p>Another method that requires a bit of attention is <tt>handleContentOutlineSelection()</tt>:</p> |
| <pre> |
| public void handleContentOutlineSelection(ISelection selection) { |
| if (!selection.isEmpty() && selection instanceof IStructuredSelection) { |
| List selectedElements = ((IStructuredSelection)selection).toList(); |
| if (getActiveEditor() == selectionEditorPart) { |
| // If the selection viewer is active, we want it to select the same selection as this selection. |
| // |
| selectionProvider.setSelection(new StructuredSelection(selectedElements)); |
| } else { |
| // For any other viewer, set the input directly. |
| // |
| ((TopicmapEditorPart)getActiveEditor()).setInput(selectedElements.get(0)); |
| } |
| } |
| } |
| </pre> |
| |
| <h2><a name="combining_the_editors">Combining the Editors</a></h2> |
| <p>Okay - now that we've completed the preparations (well, most of), |
| taping the editors together is surprisingly easy. We simply add a new |
| attribute named <tt>diagramEditor</tt> of type <tt>TopicMapDiagramEditor</tt> |
| to the <tt>TopicmapEditor</tt> and then extend the method <tt>createPages()</tt> |
| to include the diagram editor in the creation process:</p> |
| <pre> |
| public void createPages() { |
| // ... |
| try { |
| int pageIndex; |
| |
| //... |
| |
| // This is the page for the graphical diagram viewer. |
| // |
| diagramEditor = new TopicMapDiagramEditor(); |
| pageIndex = addPage(diagramEditor, getEditorInput()); |
| setPageText(pageIndex, "Diagram"); |
| |
| } catch (PartInitException e) { |
| // add some error handling for production-quality coding |
| e.printStackTrace(); |
| } |
| |
| // ... |
| |
| }</pre> |
| |
| <p>We also have to modify <tt>handleContentOutlineSelection()</tt> |
| again in order to keep the outline selection working:</p> |
| |
| <pre> |
| public void handleContentOutlineSelection(ISelection selection) { |
| if (!selection.isEmpty() && selection instanceof IStructuredSelection) { |
| List selectedElements = ((IStructuredSelection)selection).toList(); |
| if (getActiveEditor() == selectionEditorPart) { |
| // If the selection viewer is active, we want it to select the same selection as this selection. |
| // |
| selectionProvider.setSelection(new StructuredSelection(selectedElements)); |
| <b>} else if (getActiveEditor() == diagramEditor) { |
| // If the diagram viewer is active, we need to map the selection to the corresponding EditParts. |
| // |
| ArrayList<Object> selectionList = new ArrayList<Object>(); |
| for(Object selectedElement: selectedElements) { |
| if (selectedElement instanceof EObject) { |
| String elementID = EMFCoreUtil.getProxyID((EObject) selectedElement); |
| selectionList.addAll(diagramEditor.getDiagramGraphicalViewer().findEditPartsForElement(elementID, |
| IGraphicalEditPart.class)); |
| } |
| selectionProvider.setSelection(new StructuredSelection(selectionList)); |
| }</b> |
| } else { |
| // For any other viewer, set the input directly. |
| // |
| ((TopicmapEditorPart)getActiveEditor()).setInput(selectedElements.get(0)); |
| } |
| } |
| } |
| </pre> |
| |
| <p><i>Et voilà </i> - we've got the two editors combined in one |
| single multi-page editor.</p> |
| |
| <p><img src="screenshots/IntegratedEMFEditor.png" |
| alt="Combined Editor with list-based page active" /></p> |
| |
| <p><img src="screenshots/IntegratedGMFEditor.png" |
| alt="Combined Editor with graphical diagram page active" /></p> |
| |
| <p>You may want to disable the standalone diagram editor at this |
| point - simply open the <tt>plugin.xml</tt> of the diagram editor and |
| remove the extension declaring the editor (extension point <tt>org.eclipse.ui.editors</tt>). |
| Note that the pre-packaged examples still contain this extension so that |
| you can compare the generated diagram editor and the integrated editors |
| more easily.</p> |
| |
| <p>Although the integrated editor already looks quite impressive, |
| there are several important issues to fix.</p> |
| <ul> |
| <li>The properties view refuses to show any properties while the |
| graphical editor is active.</li> |
| <li>The menu items and toolbars of the graphical editor are still |
| missing.</li> |
| <li>Some of the actions in the Edit menu (like Cut, Copy, Paste |
| and Delete) don't work.</li> |
| <li>When creating a new file, the diagram is missing.</li> |
| <li>When expanding the topic map files in the resource navigator |
| view, a <tt>ClassCastException</tt> is thrown.</li> |
| </ul> |
| |
| <h2><a name="taming_the_properties">Taming the Properties</a></h2> |
| <p>The reason for the properties view not behaving as desired is |
| simply that EMF uses the default properties mechanism while GMF already |
| uses the tabbed properties view. If you're not familiar with the tabbed |
| properties view, there's an <a |
| href="http://www.eclipse.org/articles/Article-Tabbed-Properties/tabbed_properties_view.html">excellent |
| article</a> available that also gives a hint to solve our current problem. |
| In order to use the tabbed properties for both the EMF and GMF generated |
| editor, we have to change the <tt>TopicmapEditor</tt> class so that it |
| implements <tt>ITabbedPropertySheetPageContributor</tt> and have the |
| method <tt>getContributorId()</tt> return <tt>diagramEditor.getContributorId()</tt>. |
| We also need to adapt <tt>getPropertySheetPage()</tt> to create a <tt>PropertiesBrowserPage</tt>:</p> |
| <pre> |
| public IPropertySheetPage getPropertySheetPage() { |
| if (propertySheetPage == null) { |
| <b>propertySheetPage = new PropertiesBrowserPage(this) { |
| public void setActionBars(IActionBars actionBars) { |
| super.setActionBars(actionBars); |
| getActionBarContributor().shareGlobalActions(this, actionBars); |
| } |
| };</b> |
| } |
| return propertySheetPage; |
| }</pre> |
| <p>Note that you also have to change the type of <tt>propertySheetPage</tt>. |
| If you try out the implementation so far, you will notice that the |
| diagram editor now works, but the EMF editors always show <i>Properties |
| not available</i> in the view. The reason for this is that we haven't yet |
| told the properties framework to handle our generated model classes. To |
| do so, open the diagram editor <tt>plugin.xml</tt> file, locate the |
| extension to <tt>org.eclipse.ui.views.properties.tabbed.propertySections</tt> |
| and extend the list of input object types:</p> |
| <pre> |
| <extension point="org.eclipse.ui.views.properties.tabbed.propertySections"> |
| <propertySections contributorId="org.example.emfgmf.diagram"> |
| <!-- ... --> |
| <propertySection |
| id="property.section.domain" |
| tab="property.tab.domain" |
| class="org.example.emfgmf.topicmap.diagram.sheet.TopicMapPropertySection"> |
| <input type="org.eclipse.gmf.runtime.notation.View"/> |
| <input type="org.eclipse.gef.EditPart"/> |
| <b><input type="org.example.emfgmf.topicmap.TopicMap"/> |
| <input type="org.example.emfgmf.topicmap.Topic"/> |
| <input type="org.example.emfgmf.topicmap.Association"/></b> |
| </propertySection> |
| </propertySections> |
| </extension></pre> |
| <p>Granted, this is not the nicest way to solve the problem, but it |
| does the trick: We now have tabbed properties throughout the editor.</p> |
| |
| <p><img src="screenshots/TabbedProperties.png" |
| alt="Tabbed Properties View with EMF editor" /></p> |
| |
| <h2><a name="completing_menus_and_toolbars">Completing Menus |
| and Toolbars</a></h2> |
| |
| <p>In order to get the menu and toolbar contributions working, we |
| need two auxiliary classes. The first one is <a |
| href="files/TopicmapMultipageActionBarContributor.java"><tt>TopicmapMultipageActionBarContributor</tt></a>. |
| This is an implementation of the interface <tt>IEditorActionBarContributor</tt> |
| that will be used as an action bar contributor for the entire multi-page |
| editor. This implementation is basically a composite action bar |
| contributor that is responsible for enabling and disabling the actions |
| contributed by different aggregated subcontrollers when switching |
| between editor pages. This implementation is rather generic and could be |
| used for other multi-page editors with small modifications. In our case, |
| the <tt>TopicmapMultipageActionBarContributor</tt> will handle the |
| switching between two sets of actions contributed by the <tt>TopicmapActionBarContributor</tt> |
| and the <tt>TopicMapDiagramActionBarContributor</tt>.</p> |
| |
| <p>The second class - <a href="files/SubActionBarsExt.java"><tt>SubActionBarsExt</tt></a> |
| - is a utility class for the <tt>TopicmapMultipageActionBarContributor</tt>. |
| It is used to collect the actions contributed by subordinate action bar |
| contributors.</p> |
| |
| <p>We can simply place these files in the generated editor package; |
| they are just not inlined in this article because of their size. Next, |
| we need to adapt two of the existing methods to access the generated <tt>TopicmapActionBarContributor</tt> |
| properly using the newly created <tt>TopicmapMultipageActionBarContributor</tt>. |
| In <tt>TopicmapEditor</tt>, we change <tt>getActionBarContributor()</tt> |
| as shown below:</p> |
| <pre> |
| public EditingDomainActionBarContributor getActionBarContributor() { |
| return (TopicmapActionBarContributor) ((TopicmapMultipageActionBarContributor) getEditorSite() |
| .getActionBarContributor()).getTreeSubActionBars() |
| .getContributor(); |
| }</pre> |
| <p>In <tt>TopicmapEditorPart</tt>, we have to change the |
| implementation of <tt>menuAboutToShow()</tt> like this:</p> |
| <pre> |
| public void menuAboutToShow(IMenuManager manager) { |
| // pass the request to show the context menu on to the parent editor |
| ((TopicmapActionBarContributor) ((TopicmapMultipageActionBarContributor) parentEditor |
| .getEditorSite().getActionBarContributor()) |
| .getTreeSubActionBars().getContributor()) |
| .menuAboutToShow(manager); |
| }</pre> |
| |
| <p>Finally, we have to register our new contributor in the plug-in |
| manifest.</p> |
| <p><img src="screenshots/MultipageABC.png" |
| alt="Register Multipage Action Bar Contributor" /></p> |
| |
| <h2><a name="fixing_edit_menu_actions">Fixing Edit Menu Actions</a></h2> |
| |
| <p>Another problem that arises when combining EMF and GMF generated |
| editors is that some of the actions inside the Edit menu are provided by |
| both editors in different ways. In order to get these actions working |
| the way they should, two slight adjustments need to be made: Let <tt>TopicmapEditor</tt> |
| implement the interface <tt>IDiagramWorkbenchPart</tt> and delegate the |
| method calls to <tt>diagramEditor</tt>. Next, open the diagram editor |
| plug-in manifest and change the extensions to <tt>org.eclipse.gmf.runtime.common.ui.services.action.globalActionHandlerProviders</tt> |
| to point to the topicmap editor:</p> |
| <p><img src="screenshots/GlobalActionHandler.png" |
| alt="Changing the Global Action Handler Provider Registration" /></p> |
| <p>A cleaner way to achieve the re-registration would be to copy the |
| entire extension to the editor plug-in manifest and set this extension |
| to provide its implementation with a higher priority.</p> |
| |
| <h2><a name="creating_the_diagram">Creating the Diagram</a></h2> |
| |
| <p>When creating a new topic map file, the combined editor will now |
| refuse to open the file because the resource contains no diagram. In |
| order to fix this, we need to change the method <tt>performFinish()</tt> |
| of the generated <tt>TopicmapModelWizard</tt>:</p> |
| <pre> |
| public boolean performFinish() { |
| try { |
| // Remember the file. |
| // |
| final IFile modelFile = getModelFile(); |
| |
| // Do the work within an operation. |
| // |
| WorkspaceModifyOperation operation = |
| new WorkspaceModifyOperation() { |
| @Override |
| protected void execute(IProgressMonitor progressMonitor) { |
| try { |
| // Create a resource set |
| // |
| ResourceSet resourceSet = new ResourceSetImpl(); |
| |
| // Get the URI of the model file. |
| // |
| URI fileURI = URI.createPlatformResourceURI(modelFile.getFullPath().toString(), true); |
| |
| // Create a resource for this file. |
| // |
| Resource resource = resourceSet.createResource(fileURI); |
| |
| // Add the initial model object to the contents. |
| // |
| EObject rootObject = createInitialModel(); |
| if (rootObject != null) { |
| resource.getContents().add(rootObject); |
| } |
| |
| <b>// Create the diagram |
| // |
| Diagram diagram = ViewService.createDiagram(rootObject, |
| TopicMapEditPart.MODEL_ID, |
| TopicMapDiagramEditorPlugin.DIAGRAM_PREFERENCES_HINT); |
| if (diagram != null) { |
| resource.getContents().add(diagram); |
| diagram.setName(fileURI.lastSegment()); |
| diagram.setElement(rootObject); |
| }</b> |
| |
| // Save the contents of the resource to the file system. |
| // |
| // ... and so on ...</pre> |
| <p>This will create the diagram right after the <tt>TopicMap</tt> is |
| created and added to the resource.</p> |
| |
| <h2><a name="fixing_the_resource_navigator">Fixing the Resource |
| Navigator</a></h2> |
| |
| <p>When you try to navigate into the contents of the file (that is, |
| try to expand it in the tree view of the resource navigator), you'll see |
| an unhandled exception with a stack trace similar to this:</p> |
| <pre> |
| java.lang.ClassCastException: org.example.emfgmf.topicmap.impl.TopicMapImpl cannot be cast to org.eclipse.gmf.runtime.notation.View |
| at org.example.emfgmf.topicmap.diagram.navigator.TopicMapNavigatorContentProvider.selectViewsByType(TopicMapNavigatorContentProvider.java:379) |
| at org.example.emfgmf.topicmap.diagram.navigator.TopicMapNavigatorContentProvider.getChildren(TopicMapNavigatorContentProvider.java:188) |
| at org.eclipse.ui.internal.navigator.extensions.SafeDelegateTreeContentProvider.getChildren(SafeDelegateTreeContentProvider.java:91) |
| at org.eclipse.ui.internal.navigator.extensions.SafeDelegateTreeContentProvider.getChildren(SafeDelegateTreeContentProvider.java:281) |
| at org.eclipse.ui.internal.navigator.extensions.SafeDelegateTreeContentProvider.getChildren(SafeDelegateTreeContentProvider.java:89) |
| at org.eclipse.ui.internal.navigator.NavigatorContentServiceContentProvider.internalGetChildren(NavigatorContentServiceContentProvider.java:251) |
| at org.eclipse.ui.internal.navigator.NavigatorContentServiceContentProvider.getChildren(NavigatorContentServiceContentProvider.java:643) |
| [...and so on...]</pre> |
| <p>This exception occurs because the diagram editor adds a content |
| provider that does not know how to handle the classes generated by the |
| EMF. The fix for this is simply to disable the corresponding provider in |
| the diagram editor's plug-in manifest:</p> |
| <p><img src="screenshots/DiagramContents.png" |
| alt="Disable Diagram Content Provider" /></p> |
| |
| <h2><a name="rcp">Want some RCP with that?</a></h2> |
| |
| <p>Until now, we've worked in a complete Eclipse workbench |
| environment. Both EMF and GMF support generating RCP editors as well. In |
| order to combine the generated editors in an RCP environment, a few |
| things need to be observed - it is not sufficient to just set the |
| properties in the EMF .genmodel and the GMF generator to RCP (although |
| that's the first step, of course).</p> |
| |
| <p>In the following sections we'll discuss the differences from the |
| solution outlined above that apply to RCP applications. <b>You |
| should walk through the entire process for a non-RCP application at |
| least once before attempting to try this in the RCP environment</b> - after |
| all, these are just the <i>differences</i> in the process that won't |
| make much sense unless you know the process.</p> |
| |
| <p>As with the workbench edition of the integrated editor, we've |
| provided both the <a href="files/rcp-projects-initial-state.zip">projects |
| in their unmodified state</a>, right after generation, as well as the <a |
| href="files/rcp-projects-final-state.zip">projects in their final |
| state</a>.</p> |
| |
| <h2><a name="setting_the_stage">Setting the Stage</a></h2> |
| <p>Before we can try to integrate the generated editors, we first |
| need to setup some playground projects. This is covered by the |
| documentation provided for EMF and GMF, so let's not get lost in the |
| details. You can use the |
| |
| <h3>One editor or <strike>another</strike> the same?</h3> |
| |
| <p>One of the first things you'll notice when running the generated |
| editors is that you'll see two sets of "Open" and "Open URI" actions in |
| the file menu. However, an attempt to create an EMF-only file (using |
| File - New - Topicmap Model) will lead to a strange error message |
| stating that a "Diagram is not present in the resource". The reason for |
| this is that although the file is created using the EMF wizard (and thus |
| doesn't contain a diagram), the application tries to open the file with |
| the GMF generated editor. This happens because in both editor plug-ins, |
| the extensions to <tt>org.eclipse.ui.editors</tt> have the (file) |
| extension property set to "topicmap". Remove this value from the |
| GMF generated editor so that the RCP application will always use |
| the EMF generated editor. While you're at it, you can also remove the |
| extensions to <tt>org.eclipse.ui.actionSets</tt>, |
| <tt>org.eclipse.ui.bindings</tt> and <tt>org.eclipse.ui.commands</tt> |
| to clean up the file menu a little. <b>Make sure you've got the |
| EMF generated editor working before you attempt to perform any of |
| the actions outlined above.</b> You should then be able to follow |
| the steps outlined in section |
| <a href="#upgrading_the_editing_domain">Upgrading the Editing Domain</a>.</p> |
| |
| <h3>Editor Input, revised</h3> |
| |
| <p>In section <a href="#extending_the_editor_input">Extending |
| the Editor Input</a> we used a wrapper class to pass the <tt>FileEditorInput</tt> |
| instance along with the editing domain to all subordinate editors. When |
| building an RCP application, the programs have to operate on <tt>URIEditorInput</tt> |
| instances instead, so we can't use the same wrapper. Fortunately, it's |
| not that difficult to build a specialized wrapper:</p> |
| <pre> |
| public class URIEditorInputProxy extends URIEditorInput { |
| |
| protected TransactionalEditingDomain editingDomain; |
| |
| public URIEditorInputProxy(URI uri, TransactionalEditingDomain domain) { |
| super(uri); |
| this.editingDomain = domain; |
| } |
| |
| public URIEditorInputProxy(IMemento memento, TransactionalEditingDomain domain) { |
| super(memento); |
| this.editingDomain = domain; |
| } |
| |
| public URIEditorInputProxy(URI uri, String name, TransactionalEditingDomain domain) { |
| super(uri, name); |
| this.editingDomain = domain; |
| } |
| |
| public TransactionalEditingDomain getEditingDomain() { |
| return editingDomain; |
| } |
| }</pre> |
| <p>Note that you should place this wrapper in the diagram editor |
| plug-in, for example in the package <tt>...diagram.part</tt>. Now we can |
| integrate this wrapper as outlined in section <a |
| href="#extending_the_editor_input">Extending the Editor Input</a>, |
| including the modifications to the document provider methods. Since the |
| document provider already supports <tt>URIEditorInput</tt> instances, |
| you only have to adapt the methods as outlined above. Note that you |
| still need to re-export the GMF dependencies from the diagram editor |
| plug-in because they are needed later on.</p> |
| |
| <h3>Business as usual</h3> |
| |
| <p>We can then continue as outlined in <a |
| href="#dissecting_the_emf_editor">Dissecting the EMF Editor</a>, <a |
| href="#selection_handling">Selection Handling</a> and <a |
| href="#combining_the_editors">Combining the Editors</a>, with some |
| minor adjustments due to the differences in handling the editor input. |
| After a brief moment of enthusiasm when we can see the integrated editor |
| for the first time, we continue through <a href="#taming_the_properties">Taming |
| the Properties</a>, <a href="#completing_menus_and_toolbars">Completing |
| Menus and Toolbars</a>, <a href="#fixing_edit_menu_actions">Fixing Edit |
| Menu Actions</a> and <a href="#creating_the_diagram">Creating the |
| Diagram</a>.</p> |
| |
| <h2><a name="miscellaneous_notes">Miscellaneous Notes</a></h2> |
| |
| <p>While the integrated editor in its current state is already |
| usable for many applications, some issues still remain. Since this is an |
| article to outline the general procedure and not a comprehensive book, |
| we can't really cover every aspect of the integration in detail. That |
| being said, here are a few notes to illustrate known issues, outline |
| some possible solutions and encourage further reading.</p> |
| |
| <h3>File Extension Issues</h3> |
| |
| <p>If you're encountering strange issues with the modification |
| listeners of your resource sets or other synchronization problems, check |
| that the file extension that is set in the diagram editor plug-in |
| manifest in the extension to <tt>org.eclipse.emf.ecore.extension.parser</tt> |
| matches the actual file extension set for the editor itself. This issue |
| usually occurs if you forget to set <i>Same File for Diagram And |
| Model</i> to <tt>true</tt> when <a href="#setting_the_stage">setting the |
| stage</a>.</p> |
| |
| <h3>Adapting the Outline</h3> |
| |
| <p>The current outline consists of a tree viewer that displays the |
| entire resource contents, including the diagram elements. This might not |
| exactly be what you need: Depending on your requirements, you might want |
| to hide the diagram elements, display the graphical outline or create a |
| different outline view altogether.</p> |
| |
| <p>In order to hide the diagram elements, you could simply add a |
| filter to the tree viewer that's created in the <tt>TopicmapEditor</tt> |
| in <tt>getContentOutlinePage()</tt>:</p> |
| <pre> |
| // Set up the tree viewer. |
| // |
| contentOutlineViewer.setContentProvider(new AdapterFactoryContentProvider(adapterFactory)); |
| contentOutlineViewer.setLabelProvider(new AdapterFactoryLabelProvider(adapterFactory)); |
| <b>ViewerFilter[] outlineFilters = new ViewerFilter[1]; |
| outlineFilters[0] = new ViewerFilter() { |
| @Override |
| public boolean select(Viewer viewer, |
| Object parentElement, Object element) { |
| return !(element instanceof View); |
| } |
| }; |
| contentOutlineViewer.setFilters(outlineFilters);</b></pre> |
| <p>Displaying the graphical outline (or even some hybrid outline) is |
| a bit more difficult - you will want to take a look at the way the <tt>DiagramEditor</tt> |
| provides the outline using its inner class <tt>DiagramOutlinePage</tt>.</p> |
| |
| <h3>Handling Multiple Editor Instances</h3> |
| |
| <p>Eclipse allows the user to open the same file multiple times |
| using the same editor - it is even possible to "duplicate" an editor by |
| right-clicking its tab and choosing <i>New Editor</i> from the context |
| menu. Although this won't happen very often in real-life scenarios, you |
| should spend some time and make sure that the editor instances are |
| synchronized before selling the entire solution to your customers. This |
| also includes handling of the resource change notifications that cause |
| the editor to reload its contents if the file is changed by another |
| application (this is implemented inside the generated diagram editor and |
| should be relocated to the top-level multipage editor class).</p> |
| |
| <h3>Unifying the Undo and Redo Actions</h3> |
| |
| <p>The editor in its current state does not only provide a set of |
| undo and redo actions, it even provides two of them - one for the |
| tree-based editors and one for the diagram editors. This has the effect |
| that you can change something in the diagram, switch over to the tree |
| and change something else there, then switch back to the diagram and |
| undo - this will undo your first change in the diagram, not the second |
| one in the tree-based editor part. This could be changed by extending |
| the <tt>TopicmapMultipageActionBarContributor</tt> to replace the |
| individual undo and redo actions with a set of common actions for the |
| entire editor.</p> |
| |
| <h3>Allow Multiple Diagram Files</h3> |
| |
| <p>You might recall that we set <i>Same File for Diagram And |
| Model</i> to <tt>true</tt> when <a href="#setting_the_stage">setting the |
| stage</a>. The reason for this was that - as you now know - it is already a |
| non-trivial task to integrate the two generated editors if only one |
| diagram exists per data file. Of course you can also generate multiple |
| diagram editors and show them in multiple pages of your top-level editor |
| - but then you also have to implement some logic to keep track of your |
| diagram files, create or load them whenever necessary and make sure that |
| every embedded editor receives a complete resource set. While this is |
| entirely possible, it's outside the scope of this article.</p> |
| |
| <h2><a name="conclusion">Conclusion</a></h2> |
| |
| <p>As you may have noticed, it's not the integration of the editors |
| that accounts for most of the trouble - it's the preparation of the |
| editor generated by EMF and the cleaning up of lots of different issues |
| afterwards that takes up most of the time. Nevertheless, it is possible |
| to weld the editors together to form a single, consistent user |
| interface. It would be great if the EMF and GMF teams could join their |
| efforts to make this task less painful in future versions.</p> |
| |
| <h2><a name="acknowledgements">Acknowledgements</a></h2> |
| |
| <p>This document comprises tips and tricks issued by many people in |
| many different locations. Thanks to <a |
| href="http://hse-at-work.blogspot.com">Seweryn Niemiec</a> who created |
| a first <a href="http://docs.google.com/View?docid=dcqc65c5_168kvf4">short |
| documentation</a> on how to get the editors synchronized, as well as to all |
| the readers and beta-testers of the contents of this article. Many kudos |
| to all the people behind EMF, GMF and Eclipse who made this possible in |
| the first place and never (OK, seldomly) seem to be bothered by |
| more-or-less-stupid questions appearing in their newsgroups.</p> |
| |
| </div> |
| </body> |
| </html> |