Friday, October 28, 2011

Form editor with XML source view

Form based editors seem to be everywhere nowadays. They provide a great way for the user to operate on complex file types like xml files. Building a forms based editor similar to the PDE Plug-in Editor is quite easy.

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.

Monday, October 24, 2011

Reusing XML source editor

The Web Tools Platform Project from Eclipse features a nice XML editor which you can find at various places within the IDE. It is generally used to edit xml files, but you can also find it in the Plug-in Editor when editing the source of plugin.xml directly.

When you have your own config files that store xml content you can extend the default editor and bind it to your content. Basically you may achieve the same result by simply creating a new File Association (Preferences/General/Content Types) for your file type, but as we want to extend the editor in a later tutorial, we will provide an editor extension.

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 content type

Create a new Plug-in project called com.codeandme.editor. Do not add an Activator or UI elements. Go to the Extensions tab of your plugin.xml and add a new extension for org.eclipse.core.contenttype.contentTypes. Right click and select New -> content-type.

Name your content type and select a unique id (we need that afterwards). Make sure the base-type is set to org.eclipse.core.runtime.xml and don't forget to add some file-extensions to bind that type to specific files.
 
Step 2: Creating the editor

Extend org.eclipse.ui.editors and create a new editor. Your editor needs a unique id and some nice name. Before we are going to implement the editor, we add a contentTypeBinding to it. Select the contentTypeId you entered before when we defined the content type.

Now go back to the editor definition and add a class com.codeandme.xmleditor.ExampleEditor with following source:
package com.example.editor;

import org.eclipse.wst.sse.ui.StructuredTextEditor;

public class ExampleEditor extends StructuredTextEditor {

    public ExampleEditor() {
    }
}

Once done your plugin.xml should have following content:
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.4"?>
<plugin>
   <extension
         point="org.eclipse.core.contenttype.contentTypes">
      <content-type
            base-type="org.eclipse.core.runtime.xml"
            file-extensions="example"
            id="com.codeandme.xmleditor.content.example"
            name="Example Files"
            priority="normal">
      </content-type>
   </extension>
   <extension
         point="org.eclipse.ui.editors">
      <editor
            class="com.codeandme.xmleditor.ExampleEditor"
            id="com.codeandme.xmleditor.example"
            name="Example editor">
         <contentTypeBinding
               contentTypeId="com.codeandme.xmleditor.content.example">
         </contentTypeBinding>
      </editor>
   </extension>
</plugin>

It's easy as that. Now you have a basic XML source editor associated with your example files.

Step 3: Using the editor

When using the editor in your own RCP make sure you include the Plug-ins org.eclipse.wst.xml.core and org.eclipse.wst.xml.ui to your RCP.

Tuesday, October 11, 2011

Remember subversion password

With the Indigo release I switched the subversion provider from Subclipse to Subversive as the latter is the native provider from the eclipse foundation. But it didn't work out for the two of us. Subversive never stored my SVN passwords and ignored the Remember password checkbox. This was quite an annoyance, so I switched back to subclipse. Back there the troubles remained until I switched the svn connector from JavaHL to SVNKit.

Step 1: Install Subclipse with SVNKit

Make sure to enable SVNKit components when installing Subclipse.


Step 2: Activate SVNKit

Open the preferences and go to Team/SVN. On the SVN interface section set the Client to SVNKit (Pure Java).