 |
Home | Changes | Index | Search | Go
Project Wonderland v0.5: Adding entries to the context menu
by Jordan Slott (jslott@dev.java.net)
Introduction
This tutorial describes how to use the context menu in Project Wonderland. The context menu appears in your Project Wonderland client when you right-click over a Cell. The figure below shows what the Wonderland context menu looks like. The top-most item (in bold) gives the name of the Cell over which the right-click occurred.
Figure 1. The Wonderland context menu
The context menu in Wonderland is similar in idea to context menus is many other graphical applications. The context menu displays a list of menu items that can be selected. Some of these menu items always appear regardless over which Cell you click over; some menu items appear only for specific kinds of Cells. This tutorial describes how to add entries to the Wonderland context menu, both for all Cells and for specific Cells. You will extend the Shape Cell module that you created in a previous set of tutorials.
This tutorial is designed for Project Wonderland v0.5 User Preview 2.
You can find the entire source code for this module in the "unstable" section of the Project Wonderland modules workspace. For instructions on downloading this workspace, see Download, Build and Deploy Project Wonderland v0.5 Modules.
Expected Duration: 30 minutes
Prerequisites
Before completing this tutorial, you should have already successfully completed Part 1,
Part 2, Part 3, and Part 4 of this tutorial series. You will be extending the functionality you implemented there.
Adding an Entry to the Context Menu for all Cells
To add an item to the context menu, you will define a new class InfoContextMenuFactory and place it in the org.jdesktop.wonderland.modules.shape.client package of the Shape Cell tutorial module. The context menu item you will add here will be visible for all Cells. Strictly speaking, it has nothing to do with the Shape Cell itself, but we are using the project infrastructure of this module for this new class.
- Start the Netbeans IDE
- Select File -> Open Project... from the Netbeans main menu. Navigate to your Shape Cell tutorial project, after you have completed Part 4 of that tutorial series.
- Right-click on the org.jdesktop.wonderland.modules.shape.client icon in the Projects view and select New -> Java Class... from the Netbeans IDE context menu
- Enter InfoContextMenuFactory in the Class Name field and click Finish
The Netbeans IDE should generate an empty class named InfoContextMenuFactory. All classes that insert items into the context menu that are not associated with any particular Cell type must do two things:
- Have the @ContextMenuFactory Java annotation
- Implement the ContextMenuFactorySPI interface
Perform the following steps in the source code for the InfoContextMenuFactory class:
- Add the @ContextMenuFactory Java annotation right before the class definition (i.e. right before "public class InfoContextMenuItem ....")
- Add implements ContextMenuFactorySPI to the end of your class definition line
- Select Source -> Fix Imports... from the Netbeans IDE main menu
- Next, click inside of the InfoContextMenuFactory class definition, then right-click and select Insert Code... from the context menu that appears. Select Implement Method from the menu that appears and check getContextMenuItems() in the dialog box and click Generate.
Your class definition should appear as follows:
package org.jdesktop.wonderland.modules.shape.client;
import org.jdesktop.wonderland.client.contextmenu.ContextMenuItem;
import org.jdesktop.wonderland.client.contextmenu.annotation.ContextMenuFactory;
import org.jdesktop.wonderland.client.contextmenu.spi.ContextMenuFactorySPI;
import org.jdesktop.wonderland.client.scenemanager.event.ContextEvent;
@ContextMenuFactory
public class InfoContextMenuFactory implements ContextMenuFactorySPI {
public ContextMenuItem[] getContextMenuItems(ContextEvent event) {
throw new UnsupportedOperationException("Not supported yet.");
}
}
When the client starts-up it queries all modules installed in the system for classes that are annotated with @ContextMenuFactory and implement the ContextMenuFactorySPI interface. Each factory class is then queried for context menu items when their getContextMenuItems() methods are invoked. This method returns an array of ContextMenuItem classes. The ContextMenuItem interface is found in the org.jdesktop.wonderland.client.contextmenu package.
Next, you will implement your getContextMenuItems() method by returning a single context menu item named "Info" that when selected will display some basic information about the Cell in a modal dialog box. Implement your getContextMenuItems() method as follows:
public ContextMenuItem[] getContextMenuItems(ContextEvent event) {
return new ContextMenuItem[] {
new SimpleContextMenuItem("Info", new ContextMenuActionListener() {
public void actionPerformed(ContextMenuItemEvent event) {
Cell cell = event.getCell();
final String msg = "Cell selected has ID " + cell.getCellID();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JOptionPane.showMessageDialog(null, msg);
}
});
}
})
};
}
This method returns an array containing a single item: an instance of the SimpleContextMenuItem class (package org.jdesktop.wonderland.client.contextmenu). This class represents a "simple" context menu item that displays a label and an optional icon image. The SimpleContextMenuItem class also takes an instance of an object that implements the ContextMenuActionListener interface.
The ContextMenuActionListener interface has one method: actionPerformed(). The actionPerformed() method is invoked when the "Info" item is selected. Here, you simply display a modal dialog box with the Cell ID of the Cell over which the right-click happened.
To compile and run your module:
- Run your Wonderland server via the "ant run-server" command where you have the Wonderland source code. Wait for the server to finish initializing
- Do an "ant deploy" in your Shape Cell tutorial example
- Start the Wonderland client.
- Right-click over any Cell and select "Info".
You should see the following dialog box after selecting the "Info" dialog box:
Figure 2. The result of the Info Context Menu Item
Adding an Cell-Specific Context Menu Item
In addition to adding context menu items for all Cell types, you can also add context menu items that appear only for specific kinds of Cells. This is accomplished on the client-side Cell class itself. In this tutorial, you will modify the ShapeCell.java class to add a "Change Shape" context menu item that will change its shape.
(Note: In the four-part tutorial series, a single mouse click also changed the shape of the object. We perform the same action through the context menu as a simple example.).
To add a Cell-specific context menu item, you will add a ContextMenuComponent to your Cell class. In the four-part tutorial series, you were briefly introduced the notion of Cell Components. The ContextMenuComponent is another example of a Cell Component, much like ChannelComponent. Cell Components contains additional functionality that can be attached to any Cell.
Add the following definitions to the top of your ShapeCell.java file:
@UsesCellComponent private ContextMenuComponent contextComp = null;
private ContextMenuFactorySPI menuFactory = null;
Also, add the following import statements:
import org.jdesktop.wonderland.client.cell.annotation.UsesCellComponent;
import org.jdesktop.wonderland.client.contextmenu.cell.ContextMenuComponent;
import org.jdesktop.wonderland.client.contextmenu.spi.ContextMenuFactorySPI;
The @UsesCellComponent annotation is what adds the ContextMenuComponent to your ShapeCell class using a technique called "dependency injection". When an instance of your ShapeCell class is created on the client, the system looks for all @UsesCellComponent annotations and creates and adds those Cell Components to your Cell class. It also fills in your contextComp variable with the Cell Component object it creates. This dependency injection is guaranteed to happen before your setStatus() method is invoked, but no sooner. This means, for example, that you cannot rely on a valid value in contextComp until then; you cannot use this method in the ShapeCell constructor or in the setClientState() method, for example.
Next, you need to add the desired context menu items in your setStatus() method. Within the RENDERING clause of the if statement, add the following code:
if (menuFactory == null) {
final MenuItemListener l = new MenuItemListener();
menuFactory = new ContextMenuFactorySPI() {
public ContextMenuItem[] getContextMenuItems(ContextEvent event) {
return new ContextMenuItem[] {
new SimpleContextMenuItem("Change Shape", l)
};
}
};
contextComp.addContextMenuFactory(menuFactory);
}
This creates a new instance of a factory that creates a single SimpleContextMenuItem and adds it to the ContextMenuComponent when the Cell becomes active. Next, add the following code within the INACTIVE clause of the switch statement:
if (menuFactory != null) {
contextComp.removeContextMenuFactory(menuFactory);
menuFactory = null;
}
This removes the menu item when the Cell becomes inactive. Strictly speaking, this bit of code has very little affect: if the Cell is not visible, then there is no way to activate its context menu. But it is also a good idea to "clean up" references on objects so that garbage collection has an easier time reclaiming unused objects.
The final step is to implement an inner class that implements the ContextMenuActionListener interface as follows:
class MenuItemListener implements ContextMenuActionListener {
public void actionPerformed(ContextMenuItemEvent event) {
shapeType = (shapeType.equals("BOX") == true) ? "SPHERE" : "BOX";
renderer.updateShape();
ShapeCellChangeMessage msg = new ShapeCellChangeMessage(getCellID(), shapeType);
sendCellMessage(msg);
}
}
Much like when a single, left-mouse click happens on the Cell, the actionPerformed() method changes the shape of the object and sends a message to the server to inform all other clients of the shape change. Finally, make sure the following import statements appear in your ShapeCell.java file (although it is a good idea to have your IDE "fix" your import statements for you):
import org.jdesktop.wonderland.client.contextmenu.ContextMenuItem;
import org.jdesktop.wonderland.client.contextmenu.ContextMenuItemEvent;
import org.jdesktop.wonderland.client.cell.annotation.UsesCellComponent;
import org.jdesktop.wonderland.client.contextmenu.ContextMenuActionListener;
import org.jdesktop.wonderland.client.contextmenu.SimpleContextMenuItem;
import org.jdesktop.wonderland.client.contextmenu.cell.ContextMenuComponent;
import org.jdesktop.wonderland.client.contextmenu.spi.ContextMenuFactorySPI;
import org.jdesktop.wonderland.client.scenemanager.event.ContextEvent;
After you compile and deploy your Shape Cell tutorial module to the Wonderland server and re-run your client:
- Select Tools -> Cell Palette... from the Wonderland main menu
- Select "Shape Tutorial" from the list and click Create
- Right-click over the shape that appears in front of you and select "Change Shape". The Shape Cell should change shape
The context menu for the Shape Cell should appear as follows. The "Change Shape" option appears at the bottom of the list.
Figure 3. The Context Menu for the Shape Cell
Conclusion
In this tutorial you learned how to add entries to the Wonderland context menu applicable to all Cell types and specific to only a single Cell type.
|