Thursday June 28, 2007
Visual Editor (Part 1)
This is the first of what promises to be a multi-part draft tutorial, at the end of which you will (hopefully) have a working multiview editor for XML files. I say 'hopefully', because I haven't actually got to a successful end point yet, myself. So, potentially, this tutorial will come to an abrupt end three weeks from now, when I throw my hands in the air and say 'I give up!'. But I doubt that. Somehow or another everything should come together somewhere along the line. At the end of this first part, you will simply have a two-pane editor for JavaHelp TOC (i.e., 'table of contents') files, with a source view and an empty visual view:


You will also have generated Java objects from the DTD that defines the TOC. You will do this so that we can access the XML elements and attributes in the TOC file and build a visual representation for them in a later installment of this tutorial. The Java objects will be generated from JAXB, using the new JAXB Wizard, which you can find in some of the recent development builds for 6.0. In this installment, we will not touch JAXB at all in the code, we will simply use the JAXB wizard in the preparatory phase, add them to our module, and then build the basic visual editor framework that you can see in the screenshots above. In the next part, hopefully tomorrow, we'll begin dealing with the JAXB objects.
If this tutorial reaches a successful conclusion, it will (once the XML Multiview API is stabilized) become one of the official NetBeans Platform tutorials. If you (like me) want to see this API stabilized (i.e., currently it is a 'friend' API, which means that NetBeans does not officially support it and no javadoc is currently available for this API), please add your comments to issue 107858 and/or add your vote to that issue.
A. Preparatory Steps
The purpose of this section is to create JAXB objects and put them in a new module project, where we will create our visual editor.
- Create a new Java application and name it 'JAXBWizardOutput'.
- If you already have a DTD or Schema that you want to create an editor for, you can skip this step. If not, follow this step to pick out a DTD or Schema from the NetBeans repository. Create a new empty DTD file, from the XML category in the New File wizard. Name it 'InputDTD'. Go to Tools > DTDs and Schemas.

Pick out some DTD or Schema that you want to provide support for. Click the 'Open in Editor' button. Copy that content into your empty DTD file. (Ctr-A, Ctrl-C to copy all, then Ctrl-A, Ctrl-V to paste all.) Now you have a DTD or Schema that you can use in the next step.
Alternatively, assuming you want to follow along exactly with these instructions and create a visual editor for JavaHelp TOC files, just download the DTD here: http://java.sun.com/products/javahelp/toc_2_0.dtd
- Go to the New File wizard. In the New File wizard, find 'JAXB Binding' in the 'XML' category. Click Next. You are now in the JAXB Wizard. Use the JAXB Wizard to generate Java objects from the DTD or Schema:

- Create a new module project. Call it 'CoolMultiEditor'. Add a package called 'model' and copy the generated classes into it. This is where you can find them in the Java application, note that they are in the 'build' folder, which is only visible in the Files window:

- Set a dependency on the JAX-WS 2.1 module. This is a non-API module. It requires that you set an 'implementation dependency', which is described below in the context of the XML Multiview API. Once you have set this dependency, the error messages in the JAXB objects that you copied into the 'model' package will disappear, because the JAX-WS 2.1 module contains the JAXB packages that the generated JAXB objects depend on.
The purpose of this section is to let the IDE recognize XML files that conform to our selected DTD or Schema. We use the New File Type wizard for this purpose, which will also generate code for opening our file in the NetBeans editor. In effect, we will be creating the source view for our XML file in this section.
- Use the New File Type wizard to recognize XML files with the name space supported by the DTD or Schema you selected in step 1. In this panel, make sure that the MIME type ends in "+xml" and then paste the public id into the 'by XML Root Element' field:

Now complete the wizard. I set 'Toc' as the class name prefix, with the result that my data object ended up being called 'TocDataObject'. I also have classes called 'TocDataLoader' and 'TocDataNode'.
- After you complete the wizard, you need to tweak the MIME type resolver (TocResolver.xml) so that the public id of your DTD or Schema is used to recognize the file type:
<doctype public-id="-//Sun Microsystems Inc.//DTD JavaHelp TOC Version 2.0//EN"/>

Alternatively, you could use some other approach, such as using the root element's name. But the above is safer, because the root element is not necessarily unique, while the public id is undoubtedly a unique identifier for an XML file. (I mean, that's the whole point of a public id.)
- Install the module. Look in the Projects window, Files window, or Favorites window, for a file with that public id and notice the new icon.
- Double-click it and it opens in the NetBeans editor as an XML file.
We now have a source view. Let's use the XML Multiview Editor API to create a design view.
C. Creating the Design View
The purpose of this section is to create the simplest imaginable design view (i.e., the empty one shown at the start of this blog entry) for the XML file that we're dealing with, i.e., for JavaHelp TOC files.
- Dependencies. Set dependencies on both MultiView APIs:

Note: The XML Multiview Editor API doesn't always appear in the list above the first time round. Sometimes, I need to close the above dialog box, and the one below that, and then reopen them and switch the checkbox on and off, and do various other random things before it shows up.
Make sure that you set an 'implementation dependency' on the XML Multiview Editor API, otherwise you will not be able to refer to it in your code. You will get a compile error about the API not being a friend of your module. So, for the XML Multiview Editor, click Edit in the Libraries panel (in the Project Properties dialog box) and simply click 'Implementation Version', as shown here:

Note: You should have done the same earlier, for the JAX-WS 2.1 module.
- XmlMultiViewDataObject. The very first step, before any other, in implementing this API, is to change the data object so that it extends XmlMultiViewDataObject. Do so by typing it in the data object class, as shown here:

Now you will need to fix imports (Ctrl-Shift-I). Then click on the lightbulb and let the IDE generate skeleton abstract methods that you will need to implement to conform to the API.
You now have this in the data object definition:
public class TocDataObject extends XmlMultiViewDataObject { public TocDataObject(FileObject pf, TocDataLoader loader) throws DataObjectExistsException, IOException { super(pf, loader); CookieSet cookies = getCookieSet(); cookies.add((Node.Cookie) DataEditorSupport.create(this, getPrimaryEntry(), cookies)); } protected Node createNodeDelegate() { return new TocDataNode(this, getLookup()); } public @Override Lookup getLookup() { return getCookieSet().getLookup(); } protected DesignMultiViewDesc[] getMultiViewDesc() { throw new UnsupportedOperationException("Not supported yet."); } protected String getPrefixMark() { throw new UnsupportedOperationException("Not supported yet."); } }Above, the only really interesting method is the one in bold. It returns an instance of DesignMultiViewDesc, which is comparable to Interface MultiViewDescription in the MultiView Windows API. It simply describes the design view. We will fill it out in the next step.
- DesignMultiViewDesc. Let's describe our design view and return it in the getMultiViewDesc() method, discussed in the previous step. We need a description for each design view. In this case, (at least, at this stage), we only have one design view, but multiple design views could be returned. Each design view is a new tab in the editor. Have a look in the web.xml editor, for an example of an editor with multiple design views. Here we return our design view:
protected DesignMultiViewDesc[] getMultiViewDesc() { return new DesignMultiViewDesc[]{new DesignView(this, TYPE_TOOLBAR)}; }Here we declare the variable, representing the type of design view, which will be discussed in some future installment of this series:
private static final int TYPE_TOOLBAR = 0;
And now let's define our first design view:
private static class DesignView extends DesignMultiViewDesc { private int type; DesignView(TocDataObject dObj, int type) { super(dObj, "Design"); this.type = type; } public org.netbeans.core.spi.multiview.MultiViewElement createElement() { TocDataObject dObj = (TocDataObject) getDataObject(); return new TocToolBarMVElement(dObj); } public java.awt.Image getIcon() { return org.openide.util.Utilities.loadImage("org/netbeans/modules/coolmultieditor/icon-16.png"); //NOI18N } public String preferredID() { return "Toc_multiview_design" + String.valueOf(type); } }Above, the interesting method is in bold. The createElement() method does all the work in this class. It returns an instance of the the standard Multiview API's MultiViewElement interface, which is why we needed a dependency on that API. Here we return an instance of ToolBarMultiViewElement, which inherits from MultiViewElement. This class is the heart of this API. Once you come to terms with this class, everything starts making some sense. One can see this class as the controller of your implementation of the API. It instantiates a factory that generates panels. Each panel is generated in an inner class that extends SectionView. In step 7 below, we have a dummy implementation of this class. Ultimately, all the complexity of this API will be implemented in this dummy implementation.
- JAXB. First, in your data object, add this method, which gives you a connection to one of your JAXB objects:
public Toc getToc() { return null; }This will, later, return one of our JAXB objects. The TOC object that is returned here comes from the 'model' package that you defined earlier, so you should end up with this new import statement:
import org.netbeans.modules.coolmultieditor.model.Toc;
- InnerPanelFactory. Let's also create our panel factory. It will generate new panels, depending on the type of object that you want the panel to represent. (Multiple panels could appear within the same design view, as we will discover later.) So create a new class called PanelFactory.java and let it extend InnerPanelFactory. Here it is, the simplest implementation imaginable:
public class PanelFactory implements InnerPanelFactory { private TocDataObject dObj; private ToolBarDesignEditor editor; PanelFactory(ToolBarDesignEditor editor, TocDataObject dObj) { this.dObj=dObj; this.editor=editor; } public SectionInnerPanel createInnerPanel(Object key) { return new TocPanel((SectionView)editor.getContentView()); } }Above, the createInnerPanel method, which is in bold, will generate new panels in the visual view, depending on what kind of object is sent here.
- SectionInnerPanel. Now we will create the TocPanel, referred to in the previous class. Create a new JPanel class called TocPanel.java. Later, we will be able to use the 'Matisse' GUI Builder to design our visual view. Change its extension class from javax.swing.JPanel to SectionInnerPanel. You will need to create some dummy implementations of the methods setValue, linkButtonPressed, and getErrorComponent. Also, the constructor should be as follows:
public TocPanel(SectionView view) { super(view); initComponents(); } - ToolBarMultiViewElement. We now have all the pieces that the controller, i.e., an implementation of ToolBarMultiViewElement, needs. Here is a minimal implementation of this class, which gathers up the panel factory and the panel that we created in the previous two steps, using them to generate the visual view that you see in the introduction of this blog entry. The class below will be discussed in a lot of detail in future installments. Just take it on trust for now:
class TocToolBarMVElement extends ToolBarMultiViewElement { private TocDataObject dObj; private SectionView view; private ToolBarDesignEditor comp; private PanelFactory factory; public TocToolBarMVElement(TocDataObject dObj) { super(dObj); this.dObj = dObj; comp = new ToolBarDesignEditor(); factory = new PanelFactory(comp, dObj); setVisualEditor(comp); } public SectionView getSectionView() { return view; } public void componentShowing() { super.componentShowing(); view = new TocView(dObj); comp.setContentView(view); view.open(); } private class TocView extends SectionView { TocView(TocDataObject dObj) { super(factory); Toc toc = dObj.getToc(); Node itemNode = new ItemNode(toc); setRoot(itemNode); } } private class ItemNode extends org.openide.nodes.AbstractNode { ItemNode(Toc toc) { super(org.openide.nodes.Children.LEAF); setDisplayName(dObj.getPrimaryFile().getNameExt()); } } }
Hurray. That's it. You're done. You have now created the framework for a visual editor. The Projects window should look something like this:

When you install the module, you should see exactly what is shown at the start of this blog entry. Future installments will continue from this point, using the generated JAXB objects to present a visual view on top of XML files that define the TOC in a JavaHelp system.
In other news. Check out Wade Chandler's cool new article called NetBeans: Introductions to the Open-Source Project, More Than an IDE on http://www.developer.com. It is the first of a (hopefully long) series. Congratulations, Wade!
Jun 28 2007, 06:05:39 AM PDT Permalink


