X

Geertjan's Blog

  • June 29, 2007

Visual Editor (Part 2)

Geertjan Wielenga
Product Manager
Let's continue exactly where we left off yesterday. Yesterday's end result was an absolutely minimal visual editor framework. Today we'll build on top of that, a little bit, so that we'll end up with a slightly more complex visual view on top of TOC XML files. At the end of this blog entry, when you open any TOC file (which must conform to the MIME resolver constraints that we set yesterday), a visual view will open on top of the XML source view. It will look like this:

Only the highest level TOC items will be exposed to the visual view at this point. And when we edit the text in the visual view, the underlying source won't be changed. That will come later. However, editing the source will result in the visual view being updated. To get to that point, we will mainly focus on working with the JAXB objects we generated yesterday. Via JAXB, we generated Java objects so that we can traverse the elements and attributes of XML files that conform to the JavaHelp DTD. Today, we'll see how to do that in the IDE. We'll use a code template that I discussed a few days ago, which was very recently introduced into the IDE, so you'll need a 6.0 development build from the last few days before continuing.

  1. In step C4 yesterday, we had a very simple dummy method skeleton for retrieving the Toc Java object. Here's what it looked like:

    public Toc getToc() {
    return null;
    }

    Delete the body of the method and replace it with this line, to create a new instance of our object:

    Toc toc = new Toc();

    A red underline marking appears, because a Toc object is expected to be returned, which we haven't done yet. But, don't worry, we'll do so a bit later.

  2. Below the line above, type these letters and press Tab right after them:

    jaxbu

    Woohoo! You now have a whole JAXB unmarshalling code snippet! Note also that our 'toc' object has been inserted into the first line of the snippet. Your method should now look as follows:

    public Toc getToc() {
    Toc toc = new Toc();
    try {
    JAXBContext jaxbCtx = JAXBContext.newInstance(toc.getClass().getPackage().getName());
    Unmarshaller unmarshaller = jaxbCtx.createUnmarshaller();
    toc = unmarshaller.unmarshal(new File("File path"));
    } catch (JAXBException ex) {
    // TODO handle exception
    Logger.getLogger("global").log(Level.SEVERE, null, ex);
    }
    }

  3. Press Ctrl-Shift-I to generate the necessary import statements. One underline remains, for the "File path". Replace the new File instantiation with the following:

    FileUtil.toFile(this.getPrimaryFile())

    The above will extrapolate our file (which will be an XML file conforming to the JavaHelp DTD) from the data object. Let the IDE cast your Toc object correctly and add a return statement. You should now have this:

    public Toc getToc() {
    Toc toc = new Toc();
    try {
    JAXBContext jaxbCtx = JAXBContext.newInstance(toc.getClass().getPackage().getName());
    Unmarshaller unmarshaller = jaxbCtx.createUnmarshaller();
    toc = (Toc) unmarshaller.unmarshal(FileUtil.toFile(this.getPrimaryFile()));
    } catch (JAXBException ex) {
    // TODO handle exception
    Logger.getLogger("global").log(Level.SEVERE, null, ex);
    }
    return toc;
    }

    Now that we have code to unmarshall our object, let's display something from the object in the visual panel. Okay? That's all we'll do. Nothing too scary.

  4. Open the TocToolBarMVElement.java file. Remember from yesterday, that this is the controller of our XML Multiview Editor API implementation. This class is where most of the action happens. Today we'll do the absolutely simplest thing that we can do under the circumstances.

    In the TocToolBarMVElement class, find an inner class that you created yesterday, in step C7, called TocView. This class extends SectionView. The SectionView class determines the sections that will be displayed in the visual editor. There are three kinds of views that you can think about here:

    • A brand new tab. Currently, we have a Design view and a Source view, each with a toggle button. You could add another panel, with its own toggle button. You could have as many as you like. This level is the highest in the hierarchy of panels in a visual editor. To create new panels, you need to work with the DesignMultiViewDesc class, which was mentioned in step C3 yesterday. It will come back (to haunt us) in this blog series, but not today. You will see that we can even generate those tabs, with those toggle buttons, on the fly. In other words, they don't all need to be pre-defined. But we'll find out more about that later.

    • A section container. Section containers... contain sections. They have a small plus/minus icon, for expanding and collapsing the sections within them. To create section containers, you need to work with the SectionContainer class. Sections are added to this class via the SectionContainer.add method. Section containers, in turn, are added to section views via the SectionView.add method.

    • A section panel. Lowest in the hierachy, the section is represented by a single JPanel or TopComponent. In this context, a JPanel is probably simplest. That's what we'll use here.

  5. First, let's look at what we're going to display in the visual view. Call up code completion for one of the instances of your toc object. For example, you'll find one already in the TocToolBarMVElement class, within the inner TocView class. You'll see code completion, i.e., the methods that the JAXB objects make available to you:

    Let's work with the getTocitem() method. That will give us a list of TOC items. For now, we'll simply display the highest level of TOC items in the visual view. (Later, in future blog entries, we'll retrieve lower-level TOC items from the higher-level TOC items.)

  6. Type this line in the TocView class, right below the Toc object's instantiation:

    List list = toc.getTocitem();

    You will now need to let the IDE create an import statement for the Tocitem object, as well as for java.util.List.

  7. Now we'll iterate through the list:

    List<Tocitem> list = toc.getTocitem();
    for (int i = 0; i < list.size(); i++) {
    Tocitem item = list.get(i);
    item.getText();
    }

    At the end of each iteration, we have a value retrieved from the text element, which sets the TOC item's name. So, we now have the highest level names of our XML file! Hurray! Let's try it out immediately. Set a new dependency, on UI Utilities. That will let us use the NetBeans API StatusDisplayer object, to display our values in the status bar. Build a Stringbuilder and print it to the status bar, like this:

    StringBuilder sb = new StringBuilder();
    List<Tocitem> list = toc.getTocitem();
    for (int i = 0; i < list.size(); i++) {
    Tocitem item = list.get(i);
    item.getText();
    sb.append("[ " + item.getText() + " ] ");
    }
    StatusDisplayer.getDefault().setStatusText(sb.toString());

    Now install the module and open some random TOC file. Depending on what's in there, your status bar should now display the top level topics, as shown here:

    Congratulations. Now all we need to do is display the above items in the visual view. You can now actually comment out the code that we've created in this step. We simply put the code here to test out our object. Now that we know that we can correctly retrieve the values, we can continue with our little procedure. Later we'll find the above snippet useful, but for now, to avoid confusion, just comment it out. So, right now, the TocView class looks as follows:

        private class TocView extends SectionView {
    TocView(TocDataObject dObj) {
    super(factory);
    Toc toc = dObj.getToc();
    // StringBuilder sb = new StringBuilder();
    // List<Tocitem> list = toc.getTocitem();
    // for (int i = 0; i < list.size(); i++) {
    // Tocitem item = list.get(i);
    // item.getText();
    // sb.append("[ " + item.getText() + " ] ");
    // }
    // StatusDisplayer.getDefault().setStatusText(sb.toString());
    Node itemNode = new ItemNode(toc);
    setRoot(itemNode);
    }
    }

  8. Below the itemNode, still within the TocView class above, i.e., the constructor of the inner TocView class, add the following two lines, which will create the new section and add it to the section view:

    SectionPanel topLevel = new SectionPanel(this, itemNode, toc);
    add(topLevel);

    Because of the infrastructure we set up yesterday, there's nothing more that you need to do right now. Just install the module and open a TOC file. You should now see an empty visual view, apart from the name of the file, which comes from the ItemNode, defined at the end of our TocToolBarMVElement class. This is what you should now see:

  9. Now we'll put the top level TOC values in the above visual view. Open TocPanel.java. This class extends SectionInnerPanel. Here, we will copy the snippet of code that we worked with in step 7. However, we have one problem. We currently do not have access to the Toc object. We need to receive it from the PanelFactory, which will instantiate the TocPanel. The PanelFactory needs to receive the Toc object from the ItemNode. And the ItemNode receives it from the SectionView. Now do you understand why this API is complex?

    So now, in TocToolBarMVElement, pass the Toc object to the ItemNode. This is already done, in step C7 from yesterday. Now go to the PanelFactory and change the createInnerPanel method, so that the Toc object will be sent to the TocPanel. The only change from yesterday is the bit in bold below:

    public SectionInnerPanel createInnerPanel(Object key) {
    return new TocPanel((SectionView)editor.getContentView(), (Toc)key);
    }

    Now you can let the TocPanel receive the Toc object in the TocPanel constructor. Copy over the snippet of code from earlier and put it in the constructor. That's a good starting point. Remove the StringBuilder and StatusDisplayer pieces.

    Now... we will generate a text field for each value retrieved from the Toc object. And then we'll add those to the TocPanel.

  10. I don't know any other way of generating Swing components than to use GridBagLayout. I'm sure that's a result of my own ignorance and I am hoping someone will prove that to be true. Remember that we need the text fields to be generated one after the other, below each other. Only with GridBagLayout have I managed to do this, but there must be other ways and ideally this could somehow be done visually via the Matisse GUI Builder.

    Nonetheless, in the meantime, change the JPanel layout to GridBagLayout. Just use the Set Layout menu item that appears when you right-click in the Design view. And then add this code, which positions new text fields correctly and fills in the text from the retrieved top level TOC item:

    public TocPanel(SectionView view, Toc toc) {
    super(view);
    initComponents();
    GridBagConstraints gbc = new GridBagConstraints();
    gbc.insets = new Insets(2, 5, 1, 1); // spacer
    gbc.weightx = 1.0; // allow horiz dispersion
    gbc.anchor = GridBagConstraints.WEST; // align left (requires \^)
    gbc.gridwidth = GridBagConstraints.REMAINDER; // one component per row
    JTextField textField = new JTextField();
    List<Tocitem> list = toc.getTocitem();
    for (int i = 0; i < list.size(); i++) {
    Tocitem item = list.get(i);
    String oneItem = item.getText();
    textField = new JTextField();
    add(textField, gbc);
    textField.setText(oneItem);
    }
    }

    Now all this wasn't so hard after all, was it. Just install the module again and... you'll have as many top level items in the visual view as there are in the underlying TOC file. Here, I have three:

    Remember: The panel's layout must be set to GridBagLayout, as mentioned in the 2nd paragraph of step 10.

Now, the question is: "Does it work?" In other words, when we change something in the visual view, will the change be reflected in the source view? No. Not yet. We need to implement the ModelSynchronizer class for this, which will come in a future blog entry. However, simply by tweaking the data object in one significant place, you can get the other way to work. In other words, when you make changes in the source view, and if you then save those changes, the design view will immediately be updated. To implement this, go to your data object and make sure that the createNodeDelegate method looks as follows:

protected Node createNodeDelegate() {
return new TocDataNode(this);
}

By default, the above method gets the Lookup from the node, which we don't want, because the visual view has its own Lookup. That conflict of Lookups causes problems. So tweak the above method so that it looks as the above, and you will have one-way editing in your visual view. In particular, Vadiraj should be happy with this news, because he had a significant problem in this area and I hope this fixes it. And note that two way editing will come later... once I have it figured out myself.

Join the discussion

Comments ( 5 )
  • Vadiraj Sunday, July 1, 2007
    Thank you Geertjan, this trick and the other one (checking component == null in getComponent() ) fixed the issue. Now I have two way sync (from visual editor to XML view and vice versa) working.
  • Geertjan Sunday, July 1, 2007
    That's great news!
  • JS Sunday, July 20, 2008

    In the first part you wrote "Later, we will be able to use the 'Matisse' GUI Builder to design our visual view." but here you write "I don't know any other way of generating Swing components than to use GridBagLayout. I'm sure that's a result of my own ignorance and I am hoping someone will prove that to be true. Remember that we need the text fields to be generated one after the other, below each other. Only with GridBagLayout have I managed to do this, but there must be other ways and ideally this could somehow be done visually via the Matisse GUI Builder." In the later tutorials you don't seem to get back to this, any chance there will still be a follow-up to this statement? :)


  • guest Wednesday, November 2, 2011

    Hi Geertjan,

    First of all, thank you many times for all the knowledge you transmit systematically about Netbeans and related issues through your writings. You would have your inbox full of thank-you emails if I thanked you every time I have benefitted from your posts.

    I followed the XML Multiview procedures you explained. For the Book example they work nicely. I am now trying to adapt them to a much more complex case for a XML Schema with nested complex components; I am having a hard time making progress towards my goal. I just wanted to ask you if you know other sources of information where I could learn more details about XML Multiviews. I got Heiko Böck's book on the Netbeans Platform; it has provided several pieces of good information but not in this particular area.

    Thanks for any lead pointers you could share.

    - Alberto.


  • Michaela Ba&#269;&iacute;kov&aacute; Saturday, August 16, 2014

    Thank you Geertjan, for this great tutorial, it's exactly what I need for my plugin. Finally something real to use :)

    I'm coding the project on Netbeans 8.0 and there are a few adjustments that I had to make so far:

    1. When parsing XML using JAXB, I have to specify class loader in the newInstance(..) method, otherwise I get ClassCastException:

    javax.xml.bind.JAXBContext jaxbCtx = javax.xml.bind.JAXBContext.newInstance(

    toc.getClass().getPackage().getName(),

    this.getClass().getClassLoader());

    2. When creating new file support using DTD, no TocDataNode is created, therefore I had to create a dummy implementation of it by hand.

    3. When implementing the last step of this tutorial, the method createNodeDelegate(...), I could not figure out where to get the node's children. There is no such constructor in DataNode: http://prntscr.com/4dbgjv

    4. In the first part of the tutorial, step 3, there is an implementation of the DesignView and the getIcon() method. The org.openide.util.Utilities.loadImage(..) method is obsolete, you should use:

    org.openide.util.ImageUtilities.loadImage(..);

    ----

    I also have a few problems with finishing this second part. When opening the toc XML file, the file only opens, when it has the xmlns attribute set properly. However when parsing it using JAXB, it works only WITHOUT the xmlns attribute. Also, when I open the xml file, it opens twice, please see these screenshots:

    http://prntscr.com/4dbg0n

    http://prntscr.com/4dbfyh

    As you can see, there are two opened TocTemplate files (however it's the same file opened with double-click). Only the first one (on the left) has the Design tab and only the second one (on the right) has the proper icon.

    Could you please give me some advice how to solve these problems? Thank you very much!


Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.