Source code for this tutorial is available on github as a single zip archive, as a Team Project Set or you can browse the files online.
Step 1: Defining the editor
Create a new Plug-in Project called com.codeandme.multiparteditor.
I already showed how to reuse the XML source editor, so I will keep the editor definition part rather short: Define an editor and a content type in your plugin.xml. It should look like this afterwards:
<?xml version="1.0" encoding="UTF-8"?> <?eclipse version="3.4"?> <plugin> <extension point="org.eclipse.ui.editors"> <editor class="com.codeandme.multiparteditor.SampleEditor" default="false" id="com.codeandme.editor.sampleEditor" name="Sample Editor"> <contentTypeBinding contentTypeId="com.codeandme.contenttype.sample"> </contentTypeBinding> </editor> </extension> <extension point="org.eclipse.core.contenttype.contentTypes"> <content-type base-type="org.eclipse.core.runtime.xml" file-extensions="sample" id="com.codeandme.contenttype.sample" name="Sample File" priority="normal"> </content-type> </extension> </plugin>Switch to the Dependencies tab and add following dependencies:
- org.eclipse.core.resources
- org.eclipse.core.runtime
- org.eclipse.text
- org.eclipse.ui
- org.eclipse.ui.editors
- org.eclipse.ui.forms
- org.eclipse.ui.ide
- org.eclipse.wst.sse.ui
- org.eclipse.wst.xml.core
Step 2: Implementing the editor
Create a new Class com.codeandme.multiparteditor.SampleEditor
package com.codeandme.multiparteditor; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorSite; import org.eclipse.ui.PartInitException; import org.eclipse.ui.forms.editor.FormEditor; import org.eclipse.wst.sse.ui.StructuredTextEditor; public class SampleEditor extends FormEditor { private StructuredTextEditor fSourceEditor; private int fSourceEditorIndex; @Override public void init(final IEditorSite site, final IEditorInput input) throws PartInitException { super.init(site, input); // TODO: load your model here } @Override protected void addPages() { fSourceEditor = new StructuredTextEditor(); fSourceEditor.setEditorPart(this); try { // add form pages addPage(new FirstForm(this, "firstID", "First Page")); // add source page fSourceEditorIndex = addPage(fSourceEditor, getEditorInput()); setPageText(fSourceEditorIndex, "Source"); } catch (final PartInitException e) { // TODO Auto-generated catch block e.printStackTrace(); } } @Override public void doSaveAs() { // not allowed } @Override public boolean isSaveAsAllowed() { return false; } @Override public void doSave(IProgressMonitor monitor) { throw new RuntimeException("To be implemented"); } }That's all you need for your very first FormEditor.
Step 3: Add form pages
Create additional FormPages and add them to your editor. This is best done using WindowBuilder by using the New Wizard: WindowBuilder/SWT Designer/Forms/FormPage.
There are lots of tutorials out there, see here, here, or here to get you going.
Step 4: Keeping your model consistent
As we have multiple parts within our editor that modify the same source, we need some synchronization mechanism. The source editor already operates on a Document and updates its content accordingly. So our FormPages could operate on the same Document but this would result in lots of XML parsing. There are great tools for this out there, however in this example we will use another approach:
The main editor will maintain a model and inform and update an EditorPart before it is activated. That model can import and export its state to XML, which we will use to synchronize model and source editor.
All form pages should modify the model directly. When the editor page is changed, the activated page will be notified to update itself regarding new model data.
Open the SampleEditor and add following code at the end of the addPages() method:
// add listener for changes of the document source getDocument().addDocumentListener(new IDocumentListener() { @Override public void documentAboutToBeChanged(final DocumentEvent event) { // nothing to do } @Override public void documentChanged(final DocumentEvent event) { fSourceDirty = true; } });The listener will be notified whenever the source editor is editing the document. We use an internal flag to indicate that our form pages need an update.
Additionally add/exchange following methods:
/** Keeps track of dirty code from source editor. */ private boolean fSourceDirty = false; @Override public void doSave(final IProgressMonitor monitor) { if (getActivePage() != fSourceEditorIndex) updateSourceFromModel(); fSourceEditor.doSave(monitor); } @Override protected void pageChange(final int newPageIndex) { // check for update from the source code if ((getActivePage() == fSourceEditorIndex) && (fSourceDirty)) updateModelFromSource(); // check for updates to be propagated to the source code if (newPageIndex == fSourceEditorIndex) updateSourceFromModel(); // switch page super.pageChange(newPageIndex); // update page if needed final IFormPage page = getActivePageInstance(); if (page != null) { // TODO update form page with new model data page.setFocus(); } } private void updateModelFromSource() { // TODO update source code for source viewer using new model data fSourceDirty = false; } private void updateSourceFromModel() { // TODO update source page from model // getDocument().set("new source code"); fSourceDirty = false; } private IDocument getDocument() { final IDocumentProvider provider = fSourceEditor.getDocumentProvider(); return provider.getDocument(getEditorInput()); } private IFile getFile() { final IEditorInput input = getEditorInput(); if (input instanceof FileEditorInput) return ((FileEditorInput) input).getFile(); return null; } private String getContent() { return getDocument().get(); }
doSave() on line 5 uses the source page save routine. Therefore we need to make sure, the source code is up to date.
pageChange() on line 13 first checks if we need to either update the model from the source code or the source code from the model. This happens whenever we switch from/to the source view. After the page is switched we check whether the new page is a form page. If so, we need to update the form page (see line 28). This could be done by calling page.setFocus(); and overwrite setFocus() in the FormPage implementation.
Methods getDocument(), getFile(), getContent() are there for your convenience. You can delete the latter two if you don't need them.