Part 3 - XML Multiview + Visual Library
Thursday Mar 08, 2007
Finally I am here with Part 3. The last two parts, Part 1 and Part 2 discussed how to start with the XML multiview and explained the code with reference to the Book example. This part discusses about how to integrate XML multiview and Visual Library by displaying a graph view of Book and chapters on a multiview. We will be editing the Book example code and extending it.
If you are just interested the sources for the full series (XML multiview+Visual Library 2.0), download here.
Here is the original Book example design multiview (Not modified in this series):
Here is the Visual Library in action on a new multiview: Looks colorful than the earlier screenshot?
Visual Library 2.0 is an improved version of the old graph library existed before in Netbeans. Here is the project home page and here is the documentation. It allows to create graphs, in an easy and quick way. You would be surprised to see how easy to create graphs with functions like move, zoom, in-place edit, add connectors and create satellite view. The tutorial written by Geertjan describes the ways in which we can do all these. The current tutorial is based on the Geertjan's tutorial.
Recent Update: As of March 2007, the Visual Library 2.0 was released as a stable API and is available in Netbeans 6.0 update center.
The Visual Library 2.0 is not yet exposed as a standard module API to which we can add the dependency directly. There might be multiple ways to add "Visual Library API" dependency to the module. In all ways, we need to get the jars first. Again there are multiple ways to get these jars. One is described here, which explains how we can download the sources and build locally the required libraries. Here we can download and build the whole IDE or download only required sources and build the distributions for Visual Library and Utilities API. We can also download the jar files hosted on the home page, from here. If you do not want to download the entire source and build the IDE locally, then get 'org-netbeans-api-visual.jar' and 'org-openide-util.jar' either by downloading the hosted distributions or minimal building as described here. We can include these jar files as a 'Library module'. As the original Book example is a single module, lets create a new 'module suite' and include the Book example and the new Library modules in it.
First, Create a new module suite and name it 'BookExampleModuleSuite'. Then expand the module suite project node in the project view and right click on the 'Modules' node and choose 'Add Existing' and choose the Book Example module, that we already have. Now the earlier Book Example module is part of this new module suite. Let's run the module suite now by choosing Run/Run Main Project from the menu, or if the module suite is not the main project, then you can right click on the module suite project node and choose, Run.
Download the Visual Library 2.0 jar files using one of the above mentioned method. Adding the library module for them is simple. Right click on the 'Modules' node and choose 'Add Library' and choose the org-netbeans-api-visual.jar and click 'Next'. Click 'Next' again and click 'Finish' to complete the process. A new module of the type 'Library module' will be created in the module suite. Repeat the steps for org-openide-util.jar. Now we have the dependencies available for use. We will start using them shortly.
Next we will go back to the multiview!
Open BookDataObject.java in the editor. Goto getMultiViewDesc() method and add a new line as highlighted below:
protected DesignMultiViewDesc[] getMultiViewDesc() {
return new DesignMultiViewDesc[]{
new DesignView(this,TYPE_TOOLBAR),
new GraphView(this,TYPE_TOOLBAR)
}
The new line
new GraphView(this,TYPE_TOOLBAR)
adds a new multiview. Its underlined red in the editor as we need to yet create it. Here is the code for new GraphView mutliview:
private static class GraphView extends DesignMultiViewDesc {
private int type;
GraphView(BookDataObject dObj, int type) {
//super(dObj, "Design"+String.valueOf(type));
super(dObj, "Graph");
this.type=type;
}
public org.netbeans.core.spi.multiview.MultiViewElement createElement() {
BookDataObject dObj = (BookDataObject)getDataObject();
return new GraphToolBarMVElement(dObj);
}
public java.awt.Image getIcon() {
return org.openide.util.Utilities.loadImage(
"org/netbeans/modules/bookmultiview/Datasource.gif"); //NOI18N
}
public String preferredID() {
return "book_multiview_design"+String.valueOf(type);
}
}This class is much the same as DesignView, except it creates GraphToolBarMVElement. The line where this multiview element is created is underlined with red wavy line. Let's create GraphToolBarMVElement class now.
/*
* GraphToolBarMVElement.java
*/
package org.netbeans.modules.bookmultiview;
import org.netbeans.modules.bookmultiview.GraphPanelFactory;
import org.netbeans.modules.xml.multiview.ToolBarMultiViewElement;
import org.netbeans.modules.xml.multiview.ui.SectionView;
import org.netbeans.modules.xml.multiview.ui.ToolBarDesignEditor;
public class GraphToolBarMVElement extends ToolBarMultiViewElement{
private ToolBarDesignEditor comp;
private GraphPanelFactory factory;
private BookDataObject dataObject;
private SectionView view;
/** Creates a new instance of GraphToolBarMVElement */
public GraphToolBarMVElement(BookDataObject dataObject) {
super(dataObject);
this.dataObject = dataObject;
}
public SectionView getSectionView() {
return view;
}
}
Right click on the mutliview package and choose New/Java Class. Type the name of the class as : GraphToolBarMVElement and click Ok. Netbeans creates this new class and opens it in the editor. Make this class to extend ToolBarMultiViewElement. Red wavy line appears again on the class, place the cursor on the class, a hint bulb appears on the left margin. Choose that to import the required classes and implement the abstract methods. There is only one method : getSectionView() which is abstract and we need to code. Change the constructor to accept BookDataObject (this the model class which we will be using to create the graph). Define an instance variable to hold this data object. Define another instance variable called view, which is of the type SectionView. Define two more instance variables of type ToolBarDesignEditor and GraphPanelFactory. Right click on the editor and choose 'Fix imports' to import the necessary classes. Return the 'view' object from getSectionView(). Later we will create a section view and assign it to this instance.
First, we will add code to the constructor to initialize the toolbar editor as follows: The new additions are highlighted.
/** Creates a new instance of GraphToolBarMVElement */
public GraphToolBarMVElement(BookDataObject dataObject) {
super(dataObject);
this.dataObject = dataObject;
comp = new ToolBarDesignEditor();
factory=new GraphPanelFactory(comp,dataObject);
setVisualEditor(comp);
}
There two lines which show errors and those lines are where we have GraphPanelFactory. We will create this new class shortly.
Next we will create the section view. We just need one section and a node to hold up a section panel. So here is the inner class called GraphView.
private class GraphView extends SectionView {
GraphView(BookDataObject dObj) {
super(factory);
Children rootChildren = new Children.Array();
Node root = new AbstractNode(rootChildren);
try {
Book book = dObj.getBook();
Node bookNode = new BookNode(book);
rootChildren.add(new Node[] {bookNode});
addSection(new SectionPanel(this,bookNode,book)); //NOI18N
} catch (java.io.IOException ex) {
System.out.println("ex="+ex);
root.setDisplayName("Invalid Book");
}
setRoot(root);
}
}
private class BookNode extends org.openide.nodes.AbstractNode {
BookNode(Book book) {
super(org.openide.nodes.Children.LEAF);
setDisplayName(book.getTitle());
//setIconBase("org/netbeans/modules/web/" +
"dd/multiview/resources/class"); //NOI18N
}
}
Note that GraphView extends from SectionView. It passes the reference to panel factory to the super class to enable the super class to invoke a particular Section inner panel to embed in this section view. We create a root node and a Book node and add it to the root's children. (Refer Nodes API for information on Nodes). We add a new section in this view by calling addSection(). Finally we set the root node by calling setRoot(). Note that we are not creating sections and section containers for chapters here as it was so in the original example. We will represent chapters as graph nodes on the graph. The BookNode class is the same class as of BookToolBarMVElement class, you can copy it here or remove the private access specifier for this class in there. Again, right click and choose 'Fix imports' to import the necessary classes.
One last piece is missing to create and initialize the SectionView. This is done in componentShowing() method, which is overridden here. The method is as follows:
public void componentShowing() {
super.componentShowing();
view=new GraphView(dataObject);
comp.setContentView(view);
try {
view.openPanel(dataObject.getBook());
} catch(java.io.IOException ex){}
ex.printStackTrace();
}Note how we create the GraphView (which is a section view) and assign it to the toolbar design editor. We open the panel by passing the appropriate model element, which is Book in this case. This key is actually used by the Graph Panel Factory to create a particular section inner panel and return it.
Now we create the GraphPanelFactory. The reason to create new panel factory is to keep the original example still working, so that we can compare. This new panel factory will handle the panel display in the new multiview that we are currently creating. You can copy the existing PanelFactory class and paste that as a new file and rename it to GraphPanelFactory. Here is the class:
package org.netbeans.modules.bookmultiview;
import org.netbeans.modules.bookmultiview.bookmodel.Book;
import org.netbeans.modules.bookmultiview.bookmodel.Chapter;
import org.netbeans.modules.xml.multiview.ui.SectionView;
import org.netbeans.modules.xml.multiview.ui.ToolBarDesignEditor;
import org.netbeans.modules.xml.multiview.ui.SectionInnerPanel;
public class GraphPanelFactory implements
org.netbeans.modules.xml.multiview.ui.InnerPanelFactory {
private BookDataObject dObj;
ToolBarDesignEditor editor;
/** Creates a new instance of ServletPanelFactory */
GraphPanelFactory(ToolBarDesignEditor editor, BookDataObject dObj) {
this.dObj=dObj;
this.editor=editor;
}
public SectionInnerPanel createInnerPanel(Object key) {
if (key instanceof Book)
return new BookPanel((SectionView)editor.getContentView(),
dObj, (Book)key);
else
return new ChapterPanel((SectionView)editor.getContentView(),
dObj, (Chapter)key);
}
}
OK, all set to verify our new additions. We have not yet created the graph view yet, but its worth running once before we start the next section.
Right click on project node and click on Clean and Build Project. Once it completes, then again right click on the project node and choose Run. Now the fun part begins, a new IDE (which is actually Netbeans platform application) opens up and allows us to test our module in that. Here is how it looks:

Now we need open abc.story file (this is the only file that we have, for which the example contains the multiview). For that, lets open the module example here in this new IDE. Choose File/Open Project, locate and select the Book example project that we are working on so far. Its ok :) to open it in this new IDE. Another marvel of Netbeans :)
Once you open the example project, expand the Project node, Source Packages node and finally, org.netbeans.modules node. You see the abc.story file as below:

Double click on abc.story file to open it in multiview. You can see still the original multiview and the new 'Graph' multiview in between the original 'Design' view and 'XML' view :

You can see that only the BookPanel inner panel contents are shown. This is because, when we constructed the multiview, we provided a single node for Book. Notice that although we have defined the GraphPanelFactory to return both BookPanel and ChapterPanel, because of the node structure, only BookPanel will get shown.
Ok, enough waiting now... Lets design graph.
Let's copy BookPanel and create a new class called BookGraphPanel. You can use copy and paste to create this new file quickly. You need to rename BookPanel_1 to BookGraphPanel once you paste the class. Switch to 'Design' view (I am referring to Matisse GUI builder view here, see even thats built on multiview) and remove all GUI components by selecting them and hitting 'Delete' button on the keyboard.
Then we will add a new JScrollPane to the existing panel and expand it to fill horizontally and fill vertically from top till about 80%. Then we add two buttons called 'addButton' and 'removeButton'. We rename this JScrollPane to 'graphPane'. This graph pane will hold the graph. After all this, BookGraphPanel will look like this:

Let's switch to 'Source' view. There will be lot of red wavy lines (error lines) everywhere in the file, because we removed all original UI fields. Remove all lines which have the error lines. Below is the BookGraphPanel code, with all error lines removed:
package org.netbeans.modules.bookmultiview;
import org.netbeans.modules.bookmultiview.bookmodel.Book;
import org.netbeans.modules.xml.multiview.ui.SectionInnerPanel;
import org.netbeans.modules.xml.multiview.ui.SectionView;
public class BookGraphPanel extends SectionInnerPanel {
Book book;
BookDataObject dObj;
/** Creates new form BookGraphPanel */
public BookGraphPanel(SectionView view, BookDataObject dObj,
Book book) {
super(view);
this.dObj=dObj;
this.book=book;
initComponents();
}
public void setValue(javax.swing.JComponent source, Object value) {
}
public void documentChanged(javax.swing.text.JTextComponent comp,
String value) {
}
public void rollbackValue(javax.swing.text.JTextComponent source) {
}
protected void signalUIChange() {
dObj.modelUpdatedFromUI();
}
public void linkButtonPressed(Object ddBean, String ddProperty) {
}
public javax.swing.JComponent getErrorComponent(String errorId) {
return null;
}
/** This method is called from within the constructor to
* initialize the form.
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the Form Editor.
*/
// <editor-fold defaultstate="collapsed" desc=" Generated Code ">
// </editor-fold>
// Variables declaration - do not modify
private javax.swing.JButton addButton;
private javax.swing.JScrollPane graphPane;
private javax.swing.JButton removeButton;
// End of variables declaration
}
Greating Visual Library 2.0 graph involves creating a Scene and Widgets. The Scene is the container which holds the other Widgets. There are many types of Widgets available, for example, a LabelWidget can display a line of text and an ImageWidget can show an image. We create a Scene and attach a layout to it and add Widgets, much like the same we do in Swing, we create a container such as JPanel and attach the layout optionally and add components on top of it. For more information on this, refer the Visual Library 2.0 tutorial.
Let's start by creating a Scene and setting a layout to it. We will declare the following instance variables.
Scene scene = new Scene();
int x = 20, y = 130;
LayerWidget mainLayerWidget;
LayerWidget connectionLayerWidget;
LabelWidget mainWidget;
The mainLayerWidget will hold the widgets and connectionLayerWidget will hold the connection widgets that we will create to connect chapters to the book. the mainWidget will represent the Book. By adding widgets to one layer and connections to another layer, we achieve separation and each layer can be manipulated independent of each other. The Scene represents the scene (canvas area) on which the graph will be rendered. The 'x' and 'y' variables are used for placement of the chapter widgets on the scene as we see shortly. The next block of code shows how to initialize these instance variables:
scene.setLayout(LayoutFactory.createAbsoluteLayout());
mainLayerWidget = new LayerWidget(scene);
scene.addChild(mainLayerWidget);
connectionLayerWidget = new LayerWidget(scene);
scene.addChild(connectionLayerWidget);
For this tutorial, we will use the Absolute layout, but there are many other layouts available in the Visual Library 2.0 API that readily place the widgets on the screen in a systematic manner.
Next we will create the mainWidget, which will represent the Book on the graph. Note that this example uses LabelWidget only, although there are many widgets available such as ImageWidget for use. You will get more information on this here : tutorial.
try {
mainWidget = new LabelWidget(scene,
dObj.getBook().getTitle());
mainWidget.setAlignment(LabelWidget.Alignment.CENTER);
mainWidget.setBorder(new RoundedBorder(
10,10,9,9, new Color(153,204,250), Color.BLACK)
);
mainWidget.setPreferredLocation(
new Point(80, 10)
);
mainWidget.getActions().addAction(
ActionFactory.createMoveAction()
);
mainLayerWidget.addChild(mainWidget);
Chapter chapters[] = dObj.getBook().getChapter();
for(int i = 0; i < chapters.length ; i++) {
addChapterToScene(mainWidget, chapters[i],
connectionLayerWidget, mainLayerWidget);
}
JComponent sceneView = scene.createView();
graphPane.setViewportView(sceneView);
} catch (IOException ex) {
ex.printStackTrace();
}mainWidget.setPreferredLocation() is used to fix the location of this book widget on the screen. Remember we are using the Absolute layout and as in the case of swing layouts, here too we need to manage the widget placement, if we use this layout. The createMoveAction() will enable moving of this widget on the screen. Note we add this action to the widget and not to the scene. We get the chapters from the Book data objects and create widgets for each of them, in a separate method : addChapterToScene(). We will see this method shortly. Note at the end of the above code block that the scene actually gives us a JComponent, that we can embed on top of JPanel or JFrame. Here we embed that in the scrollpane by calling setViewportView().
The following code block shows addChapterToScene() method.
private Widget addChapterToScene(final LabelWidget mainWidget,
final Chapter chapter,
final LayerWidget connectionLayerWidget,
final LayerWidget mainLayerWidget) {
LabelWidget chapterWidget = new LabelWidget(scene,
chapter.getTitle());
chapterWidget.setPreferredLocation(new Point(x, y));
chapterWidget.setBorder(new RoundedBorder(10, 10, 3, 3,
new Color(255,255,153), Color.BLACK));
chapterWidget.getActions().addAction(
ActionFactory.createMoveAction()
);
mainLayerWidget.addChild(chapterWidget);
ConnectionWidget connectionWidget =
new ConnectionWidget(scene);
connectionWidget.setSourceAnchor(
AnchorFactory.createRectangularAnchor(
chapterWidget
)
);
connectionWidget.setTargetAnchor(
AnchorFactory.createRectangularAnchor(mainWidget)
);
connectionLayerWidget.addChild(connectionWidget);
x+= 150; y+= 20;
if(x > 400 && x <= 590) {
x+= 150;
}
if(x >= 670) {
y+= 80;
x = 20;
}
return chapterWidget;
}
This method will create a widget for each chapter that is passed into it on the screen. It uses the 'x' and 'y' instance variables to place the widgets appropriately on the screen so that the widgets wont clutter up. This method also creates a connector (its called 'Anchor'). We create anchors on the connection layer. The connection widget represents this anchor.Here 'RounderBorder' is used, which essentially goes right visually with a label widget. But there are a many borders available.
So let's run the example now and see how it looks. When the new IDE opens up, you can open the same module project inside it and expand the project node, Source Packages and org.netbeans.modules nodes in that order. Double click on the abc.story file and it opens the multiview editor on the right side. Click on the 'Graph' multiview and you can see the graph as shown below:
Its so easy to create a graph, and add cool things like move, just write one line and the move functionality will be added. The same way, we can add the zoom functionality even. Just call
scene.getActions().addAction(ActionFactory.createZoomAction());
and you have the zoom functionality!! Use the scroll wheel on the mouse to zoom in and zoom out :)
Ok, lets connect the graph to the XML file. This is the only place which graph view is missing from the 'design' view, or the original multiview. As you would have observed earlier, there are two buttons on the graph view to add and remove chapters from the XML file.
First let's code for 'Add chapter'. We will create a simple panel which will ask the details about the new chapter. Right click on the org.netbeans.modules.bookmutliview and choose 'New/File/folder'. On the wizard panel that comes up, expand Java GUI Forms and choose JPanel on the right. Name the panel as 'AddChapterPanel' click 'Finish'.

Design the panel as shown above. Add label and textfield (named chapterTitleTextField) for the chapter title. Add another label and text area (named paragraphTextArea) for collecting paragraph data. Note that although we can add multiple paragraphs, only one will be considered here.
We will use this panel to collect information about the new chapter. We will show this panel using the Netbeans Dialogs API. For this we will add new dependency for Dialogs API. We will add event handles for the two buttons as follows:
We will start with 'Add New Chapter'. Add an action listener event to the button by right clicking on it in the design view and choosing 'Events/Action/actionPerformed'. In this event handling, we will call a utility method called 'addNewChapter()'.
private void addNewChapter() {
AddChapterPanel addNewChapterPanel =
new AddChapterPanel();
DialogDescriptor addDialog = new DialogDescriptor(
addNewChapterPanel, "Add New Chapter"
);
Object returnValue = DialogDisplayer.getDefault().notify(addDialog);
if(returnValue != null && returnValue.equals(new Integer(0))) {
Chapter newChapter = new Chapter();
newChapter.setTitle(addNewChapterPanel.getChapterTitle());
newChapter.setParagraph(new String[]{
addNewChapterPanel.getParagraph()}
);
try {
dObj.getBook().addChapter(newChapter);
} catch (IOException ex) {
ex.printStackTrace();
}
dObj.modelUpdatedFromUI();
// add a new widget for this new chapter
Widget newWidget = addChapterToScene(mainWidget, newChapter,
connectionLayerWidget, mainLayerWidget);
scene.validate();
newWidget.repaint();
}
}
private void addButtonActionPerformed(java.awt.event.ActionEvent evt) {
addNewChapter();
} Note, how the AddChapterPanel is embedded inside the Netbeans Dialog Descriptor. Examining the return values of dialon displayer is little tricky, although using static constants this can be simplified. We will create a new Chapter object, assign its title and a paragraph and set it back to the BookDataObject. Once we alter the model object (BookDataObject, in our case), we need to notify the XML multiview system that data has changed so that it can re-sync. The notification call is
dObj.modelUpdatedFromUI();
After this, we will create a new widget for this new chapter and place it on the screen and refresh the screen. Viola! we have the new chapter added to the graph and the XML file too at the same time!.

You will see the new chapter added to the graph highlighted in the image below:

At the same time, if you switch to the XML view, you can see that there is a new chapter added at the end of the XML file.
Note, here that, we are handling the two way synchronization ourselves at least by part. We are calling the notification method to tell the XML multiview system when to do sync, while in the normal XML multiview, this is handled for you.
Now let's implement 'Remove Chapter'.
private void removeButtonActionPerformed(java.awt.event.ActionEvent evt) {
Widget selectedWidget = scene.getFocusedWidget();
if(selectedWidget instanceof LabelWidget) {
String chapterName = ((LabelWidget)selectedWidget).getLabel();
Chapter selectedChapter = book.getChapterByTitle(chapterName);
if(selectedChapter != null) {
book.removeChapter(selectedChapter);
dObj.modelUpdatedFromUI();
mainLayerWidget.removeChild(selectedWidget);
// Remove the connection widget.
for(Widget widget : connectionLayerWidget.getChildren()) {
ConnectionWidget connectionWidget =
(ConnectionWidget)widget;
if(connectionWidget
.getSourceAnchor()
.getRelatedWidget().equals(selectedWidget)) {
connectionLayerWidget.removeChild(connectionWidget);
break;
}
}
scene.validate();
scene.repaint();
}
} else System.out.println("innerWidget is not an instance of label widget");
}
Again, add a action listener to the remove button and implement the event handling as above. Here we look for the selected widget from the scene and find out the related Chapter object and remove both widget (from scene) and chapter (from the model data object). Again here, we notify the XML multiview system that the data has changed by calling
dObj.modelUpdatedFromUI();
Note that we remove the widget as well as the connection widget attached to it. Finally we refresh the scene.
For this to work, we need to add 'SelectAction' to every widget (chapter) on the scene. Let's add that in the
addChapterToScene()
method as follows. This code is just after the new widget creation.
chapterWidget.getActions().addAction(
ActionFactory.createSelectAction(
new SelectProvider() {
public boolean isAimingAllowed(Widget widget, Point point, boolean b) {
return true;
}
public boolean isSelectionAllowed(Widget widget, Point point,
boolean b) {
return true;
}
public void select(Widget widget, Point point, boolean b) {
System.out.println("selected : " + widget);
widget.setBorder(new RoundedBorder(10, 10, 3, 3,
new Color(153,153,255), Color.BLACK));
scene.setFocusedWidget(widget);
widget.repaint();
}
}));
For me the select action is working only when the widget is double clicked. We change the border of the widget to make it visible when we select it. We also set the focused widget to the currently selected widget for the scene.
Here is remove in action: The chapter is selected for removal and its highlighted with blue color.

Here the chapter is gone after hitting 'Remove Chapter' button.

If you switch back to the XML view, before and after clicking 'Remove Chapter', you would notice that the XML file will always reflect the current status as of the graph. For example, if we add a chapter, a new chapter tag will be created in the XML file and if we select a chapter node on the graph and click 'Remove Chapter' button, the chapter tag will be gone. This is so easy right?
OK! we are done. We successfully integrated the Visual Library 2.0 with XML multiview with full functionalities from both APIs.
This is the end of a bit long 3 part series which showed you how to create the XML multiview with the Book example (Part 1 and 2) and this last part (Part 3) will showed you how to add Visual Library 2.0 support into the multiview. Go and download the sources for the final product from here.
See you soon with another adventure with Netbeans, XML multiview in particular!
Disclaimer: The modules described here are purely experimental so no guaranties. Use at your own risk.













Posted by Gowri on March 08, 2007 at 08:58 AM IST #
Posted by Vadiraj on March 08, 2007 at 09:43 AM IST #