X

Geertjan's Blog

  • July 1, 2007

Visual Editor (Part 4)

Geertjan Wielenga
Product Manager
In this part, we look at one way of pulling all the elements from the TOC and then we display them in the visual pane of our multiview editor. In doing so, we distinguish between toc items that have the 'target' attribute set and those that don't. If there is no 'target' attribute set, one can assume that the item is not a help topic, because it doesn't have an associated HTML page. Therefore, it must be a 'bucket' or container. And therefore one should make a distinction between these two types of toc items, because one wants to be able to provide different support for them. For example, only a toc item that has a target should be openable. And so we look at how to make that distinction. Here you see that toc items that do not have a target attribute are displayed as a label, while those that do have a target attribute are displayed as a text field:

We are also introduced to an extremely handy NetBeans API class, org.netbeans.modules.xml.multiview.ui.LinkButton. It is part of the unsupported XML MultiView Editor API. Very simply you can provide hyperlinks in the visual view if you use this class. The linkButtonPressed method is invoked when the hyperlink is clicked. So, we will also look at how to create a display where all toc items that have 'target' attributes are represented by LinkButtons, as shown here:

To create all of the above functionality, complete part 1 and part 2 of this series. Then admire part 3, because that's where we're going to end up! Next, take the following steps:

  1. In the TocView class, change add(topLevel) to addSection(topLevel). The latter method is specifically for node section panels, unlike the other which is for components in general. The addSection method adds the section panel to the view, positions it at the top of the page, and gives you a scrollbar. All very handy.

    Note: Apart from this step, everything we do in this blog entry is done in the TocPanel class.

  2. Now let's create some utility methods, for adding a text field, a label, and a link button. The three arguments required by the LinkButton are interesting but obscure. The only one I've found relevant is the third, while the first should always be this (or null). The second, I have no idea what it is for. It passes a bean to the linkButtonPressed method, if I understand correctly, but I'm not sure what one would want to do with it. The third passes a property to the linkButtonPressed method, which can be pretty useful, as we'll see in a future blog entry. (Why will it be useful? Because, if we send the 'target' attribute to the linkButtonPressed method, then we can compare it to the 'target' attribute in the associated map file when the link is clicked (which is the only correct time to do this) and then acquire the attribute in the map file that provides the URL to the help topic. And once we have that, we can open it. But all this will come later.)

    public void addFieldToView(String name, GridBagConstraints gbc) {
    JTextField textField = new JTextField();
    textField.setText(name);
    add(textField, gbc);
    }
    public void addLabToView(String name, GridBagConstraints gbc) {
    JLabel label = new JLabel(name);
    add(label, gbc);
    }
    public void addButtToView(String name, String target, GridBagConstraints gbc) {
    LinkButton butt = new LinkButton(this, null, target);
    butt.setText(name);
    add(butt, gbc);
    }

  3. Here is the GridBagConstraints utility method, which receives an inset, so that we can create an indentation for the different levels in our toc hierarchy:

    public GridBagConstraints createConstraintsForView(int inset) {
    GridBagConstraints gbc = new GridBagConstraints();
    gbc.insets = new Insets(2, inset, 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
    return gbc;
    }

  4. This method is useful for judging whether a toc item is a help topic or not. It takes a 'target' attribute and then returns true or false:

    public boolean isTopic(String target) {
    //If target is null, we return false,
    //because the toc item not a help topic:
    if (target == null) {
    return false;
    //If target is not null, we return true,
    //because the toc item is a help topic:
    } else {
    return true;
    }
    }

    Note: More will be added to the above method. Right now it is a bit superfluous. We could just do the boolean check without a separate method. But, later, this method will be expanded, because more considerations might come into play when determining whether a toc item is a help topic or not.

  5. And here is a really cool utility method, for determining what to add to the panel. We receive one instance of the TocItem class. From that, we get the name from the 'text' attribute and the target from the 'target' attribute. Then we use the previous utility method for judging whether it is a help topic or not. If it isn't a help topic, we let it be represented by a label. If it is a help topic, the link button (or text field, depending which one you're using or which one you're supporting or which one has been set) comes to our help:

    public List iterateThroughOneLevel(Tocitem item, GridBagConstraints gbc) {
    String name = item.getText();
    String target = item.getTarget();
    if (isTopic(target)) {
    addButtToView(name, target, gbc);
    //addFieldToView(name, gbc);
    } else {
    addLabToView(name, gbc);
    }
    return item.getTocitem();
    }

  6. Finally, in the constructor, add this slightly cumbersome (but could be much worse too) iterator for triggering all the utility methods discussed above:

    ListIterator<Tocitem> it = toc.getTocitem().listIterator();
    while (it.hasNext()) {
    //Create the first level of items:
    List<Tocitem> list = iterateThroughOneLevel(it.next(), createConstraintsForView(20));
    ListIterator<Tocitem> it2 = list.listIterator();
    while (it2.hasNext()) {
    //Create the second level of items:
    List<Tocitem> list2 = iterateThroughOneLevel(it2.next(), createConstraintsForView(50));
    ListIterator<Tocitem> it3 = list2.listIterator();
    while (it3.hasNext()) {
    //Create the third level of items:
    List<Tocitem> list3 = iterateThroughOneLevel(it3.next(), createConstraintsForView(80));
    ListIterator<Tocitem> it4 = list3.listIterator();
    while (it4.hasNext()) {
    //Create the fourth level of items:
    List<Tocitem> list4 = iterateThroughOneLevel(it4.next(), createConstraintsForView(110));
    ListIterator<Tocitem> it5 = list4.listIterator();
    while (it5.hasNext()) {
    //Create the fifth level of items:
    iterateThroughOneLevel(it5.next(), createConstraintsForView(110));
    }
    }
    }
    }
    }

    Note: If there is a better way to do the above piece of code, I'd be glad to hear it. Initially it was a lot longer, but I've tried to narrow it down to its bare essentials, although I am sure those essentials could be barer. It would be better if the iterator kept iterating until nothing is left to iterate. But instead, I had to specify the number of iterators, but I'm sure there must be a way round that. (And I'm hoping someone will tell me. Hint. Hint.)

And that's it! The first five levels of any toc (assuming it conforms to our data object definition) will now be displayed in the visual pane when you open a TOC file in the IDE! When you click one of the hyperlinks, if you decide to use the LinkButton class, an error is currently thrown, because we haven't implemented the linkButtonPressed method yet. I have tried to make the code as elegant as possible and I hope that I have succeeded. Note that currently there's no functionality for switching between the text field display and the link button display. One of the two has to be commented out, as it currently is above. The iterateThroughOneLevel method needs to have a trigger added, for switching between the displays. One could be an 'Edit' mode, while the other could be a 'View' mode. However, that will come at a later stage.

In the next part, we will talk about... how to have the related help topic open when the hyperlink is clicked! Now, if that isn't useful functionality, then I don't know what is.

Join the discussion

Comments ( 2 )
  • Matthew Barker Sunday, July 1, 2007
    It looks like a recursive coding pattern, something like this (below),
    may work as a replacement for your 5-level deep nesting
    in your iterator.
    I haven't tested it, not having spare
    time just now, but it's similar to a ToC tree I had to pull out of a database at some time in the past.

    Cheers,

    Matthew

    spewToC( topLevel, 0 );
    ...
    void spewToC( toc, int level ) {

    if ( !toc ) return;

    if ( level >= MAX_LEVEL ) return;


    ListIterator<TocItem> it = null;

    if ( level ) {

    it = iterateThroughOneLevel(toc, createConstraintsForView(20+level\*30) );

    } else {

    level = 0;

    it = toc.getTocItem().listIterator();

    }


    while ( it && it.hasNext() ) {

    spewToC( it.next(), level++ );

    }


    }
  • Geertjan Monday, July 2, 2007
    Hi Matthew, thanks very much for your suggestion and time. I've tried to implement your solution but it isn't working for me yet. Will continue looking into it, for sure. Great to know someone's been working through the same things!
Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.