Creative use of the NetBeans Visual Library: the Light Table
The Visual Library is one of the coolest things that the NetBeans guys delivered with NetBeans 6. It is a rich API which allows you to create a sort of "blackboard" where objects can be added, removed, edited, moved, resized, and connected in a visual graph. The cream on the cake is that you can use the Visual Library even in regular Swing applications by just adding the related JAR to the classpath, since it has no dependencies on the NetBeans core.
The Visual Library is deeply used inside NetBeans: it's at the core of
Matisse, the visual designer, as well as the UML support, the J2ME designer, in a few words everything that renders a graph. When Roumen posted a screencast about using the Visual Library I got really excited since I understood that it would have allowed me to easily implement a new feature in blueMarine: the (Virtual) Light Table. It is a visual component where you can drop thumbnails from your photo collection and arrange them as you prefer with the mouse. You can see a screenshot below and a very short screencast (.MOV, 300k) is also available.
Now I'm going to tell you how I was able to implement the LightTable in a matter of hours (tests excluded) yesterday night with the Visual Library.
WARNING: this blog assumes you're confident with the basic concepts of Swing and the DataSystem and Nodes API of NetBeans.
You're going to see all the relevant code in the sketches below; in any case, the sources can be checked out with Subversion from https://bluemarine-incubator.dev.java.net/svn/bluemarine-incubator/trunk...
The binary code isn't available in blueMarine yet (hey, I just wrote it last night! Give me the time to test it... ;-) - it will be available soon by means of the Update Centers and will be part of the next round of blueMarine demos I'll give starting from the next month.
Some blueMarine APIs
Of course I've been facilitated by the consolidation of the blueMarine APIs, that are a set of NetBeans modules for manipulating photos (and - with the latest refactoring - any kind of media type). Since I'd like to focus on the Visual Library and not on blueMarine, I'm just telling you what the few blueMarine classes you'll see in the code listings are about:
Thumbnailis just a reduced size image used as a photo preview. It is smart enough to be asynchronous, that is it can be generated in background and fire events when data (raster and metadata) are available.
ThumbnailManageris the manager for
Thumbnails; you usually call it as
DataObject dataObject = ...;Remember that
Thumbnail thumbnail = thumbnailManager.findThumbnail(dataObject);
DataObjectis the standard way of representing a datum with NetBeans. Whenever you're dealing with a photo, the code above will give you a working thumbnail (or create one if it doesn't exist).
ThumbnailRendereris the renderer for thumbnails. It is able to deal with the asynchronous model of a
Thumbnail, that is to quickly render a placeholder if the Thumbnail is not ready yet, and repaint it later when the image is available.
ThumbnailTrackeris a container of a set of
Nodes which are capable of update themselves when the related
Thumbnails are updated. The typical use is:
Node node = thumbnailTracker.add(dataObject.getNodeDelegate());
For the record, these APIs are part of the blueMarine Core, the foundation APIs of blueMarine. They are going to be frozen an documented soon. Stuff for another blog post.
Scene is the container for the "blackboard", that is the container of all the graphics objects (that are called "Widgets"). While Roumen's screencast introduced the regular
Scene class, I'm going to use an
ObjectScene, which provides some additional features:
- it defines the concept of "selected" widgets, as well as "hovering" widgets (i.e. the object under the mouse) and others; and fires the proper events to notify external listeners;
- it provides an association between a
Widgetand a custom object - that is, the model for your
For instance, the following code will create a working
ObjectScene with the capability of add and remove
DataObjects from it:
/** This object manages a set of thumbnailed nodes. */
private final ThumbnailTracker thumbnailTracker = new ThumbnailTracker();
/** The scene manager from Visual Library. */
private final ObjectScene scene = new ObjectScene();
/** The main layer where Widgets are added. */
private final LayerWidget mainLayer = new LayerWidget(scene);
private void internalAdd (final DataObject dataObject, final Point location)
final Node node = thumbnailTracker.add(dataObject.getNodeDelegate());
final Thumbnail thumbnail = ThumbnailManager.Locator.findThumbnailManager().findThumbnail(dataObject);
final ThumbnailWidget widget = new ThumbnailWidget(scene, thumbnail, node);
final Point sceneLocation = scene.convertViewToScene(viewLocation);
final Point localLocation = mainLayer.convertSceneToLocal(sceneLocation);
scene.validate(); // See first entry of http://graph.netbeans.org/faq.html
private void internalRemove (final DataObject dataObject)
widgets = scene.findWidgets(dataObject);
for (final Widget widget : widgets) // removeObject() doesn't remove widgets
Just a few comments:
ThumbnailWidgetis a specialized visual object that I'll describe below.
Scenecan contain many layers (
LayerWidget), that are the place where
Widgets are placed. In the simplest cases, you just need one of them.
- Capabilities can be added to
Widgets by adding
WidgetActions to them. Most of
WidgetActions can be created from an
ActionFactory(see code sketches below), while some are provided by the
ObjectScene(in the code, the action for managing the selection and the hovering status).
Once you have created an
ObjectScene, you just get the Swing components out of it:
/** The view component. */
private final JComponent view = scene.createView();
/** The view component. */
private final JComponent satelliteView = scene.createSatelliteView();
view is the blackboard renderer itself, and you just need to place it in a Swing
JWindow or such (in most cases you'd put it into a
JScrollPane first); the
satelliteView is an optional component that provides a "bird's eye" view of the blackboard.
As those components are just Swing components, you can integrate them with things such as drag and drop. For instance, the following code enables dropping
DataObjects directly on the LightTable:
private final DropTarget dropTarget = new DropTarget()
public void drop (final DropTargetDropEvent event)
for (final DataFlavor flavor : event.getCurrentDataFlavors())
final Class<?> clazz = flavor.getRepresentationClass();
final DataObject dataObject = (DataObject)event.getTransferable().getTransferData(flavor);
// This tests if the current DataObject has Thumbnail capability.
if (dataObject.getLookup().lookup(Thumbnail.DataProvider.class) != null)
catch (Exception e)
logger.throwing(CLASS, "drop()", e);
Creating a Widget
The Visual Library comes with a lot of pre-made
Widgets, such as
LabelWidget (which contains a text),
ImageWidget (which contains an image), up to
ComponentWidget, which contains a
JComponent. This is really flexible for most cases; nevertheless you can subclass
Widget if you want to do something special.
Which is precisely what I need. In spite of the fact that a
Thumbnail is an image,
ImageWidget is not ok for me, since rendering
Thumbnails is somewhat complex because of their asynchronous behaviour. So I'm going with the creation of a custom
Widget with specialized rendering capabilities:
Keep in mind that you aren't forced to use
public class ThumbnailWidget extends Widget
private static final ThumbnailRenderer thumbnailRenderer = new SimpleThumbnailRenderer();
private final Thumbnail thumbnail;
private final Node node;
public ThumbnailWidget (final Scene scene, final Thumbnail thumbnail, final Node node)
this.thumbnail = thumbnail;
this.node = node;
protected void paintWidget()
final Graphics2D g = getGraphics();
final AffineTransform transformSave = g.getTransform();
final Rectangle bounds = getClientArea();
final double zoomFactor = getScene().getZoomFactor();
g.scale(1 / zoomFactor, 1 / zoomFactor);
Nodes with the Visual Library: but the next code samples will demonstrate why it's a good thing to have them behind the scenes.
Rendering happens in the
paintWidget() method, where you can retrieve a
Graphics2D object and paint all the stuff. Since a
Scene can be zoomed in and out, it's important that you deal properly with the current scale.
The second thing to implement is the capability of automatically update the Widget when the
Thumbnail state changes. Since this capability is pretty important throughout blueMarine, I've already told you that
Nodes coming out from a
ThumbnailTracker have automatic update capabilities. So what I need now is just a
private final NodeListener iconChangeListener = new NodeAdapter()
public void propertyChange (final PropertyChangeEvent event)
// The Nodes API can fire events outside of the AWT Thread
public void run()
// in the constructor of ThumbnailWidget:
repaint(), as the similar method in
JComponent, just causes the current
Widgetto be repainted (another method,
revalidate(), should be called if you have changed the size of the
Widget, which is something I'm not doing here).
Now I would like to have a popup menu working on my
ThumbnailWidget. By default, they don't have one, but you can specify a
PopupMenuProvider for this purpose:
As you can see, the code retrieves the context menu of the
private final PopupMenuProvider popupMenuProvider = new PopupMenuProvider()
public JPopupMenu getPopupMenu (final Widget widget, final Point location)
// in the constructor of ThumbnailWidget:
Node. This is pretty neat, since I'm just transparently getting the actions that have been configured in the NetBeans platform; in other words, I'm being consistent with the fact that whenever I have something that represents a photo, I always get the same functionalities.
Furthermore, I can take advantage of the Nodes API. For instance, I'd like to retouch a bit the popup menu. In facts, it contains an "Add to light table" action that I've defined elsewhere - trust me, I'm not giving the code for this since it's not part of the Visual Library stuff. But there's no meaning in executing that action on a Widget that is already in the Light Table. On the contrary, a "Remove from light table" action would make sense. I can easily exchange the "add to..." with the "remove from..." actions by using a
private class ActionFilterNode extends FilterNode
public ActionFilterNode (final Node node)
public final Action getActions (final boolean context)
final List<Action> actions = new ArrayList<Action>(Arrays.asList(super.getActions(context)));
for (int i = 0; i < actions.size(); i++)
final Action action = actions.get(i);
if ((action != null) && AddToLightTableAction.class.equals(action.getClass()))
return actions.toArray(new Action);
// in the constructor of ThumbnailWidget:
this.node = new ActionFilterNode(node);
Now I'd like to have my
ThumbnailWidgetbrought to front when a simple mouse click is performed on it. On this purpose, I can create a custom
private static final WidgetAction.Adapter bringToFrontAction = new WidgetAction.Adapter()I think that the above code is self-explaining.
public State mouseClicked (final Widget widget, final WidgetMouseEvent event)
if (event.getButton() == MouseEvent.BUTTON1)
// in the constructor of ThumbnailWidget:
Now I'd like that my
ThumbnailWidget can be moved by just clicking and dragging the mouse. This is really easy: I just need to add this in the constructor:
getActions().addAction(ActionFactory.createMoveAction());And what about resizing? The Visual Library provides out-of-the box support for resizing a
Widget. You just need to add the related action in the
getActions().addAction( ActionFactory.createResizeAction());At this point, by pointing the mouse on the Widget border and dragging, it gets resized. Easy! But it's not good for me: my
ThumbnailWidget represents a photo and I don't want arbitrary resizing that changes the aspect ratio of the image. Fortunately I can put a constraint with the following code:
final WidgetAction resizeAction = ActionFactory.createResizeAction(resizeStrategy,
getActions().addAction(resizeAction); // add this BEFORE createMoveAction()
ResizeStrategy lets me override the actual size the will be applied to the Widget:
private final ResizeStrategy resizeStrategy = new ResizeStrategy()
public Rectangle boundsSuggested (final Widget widget,
final Rectangle originalBounds,
final Rectangle suggestedBounds,
final ResizeProvider.ControlPoint controlPoint)
final Rectangle result = new Rectangle(suggestedBounds);
final double deltaW = Math.abs(suggestedBounds.getWidth() - originalBounds.getWidth());
final double deltaH = Math.abs(suggestedBounds.getHeight() - originalBounds.getHeight());
final Insets insets = getBorder().getInsets();
final int mw = insets.left + insets.right;
final int mh = insets.bottom + insets.top;
if (deltaW >= deltaH) // moving mostly in horizontal
result.height = mh + Math.round((result.width - mw) * aspectRatio);
else // moving mostly in vertical
result.width = mw + Math.round((result.height - mh) / aspectRatio);
The code looks a bit tricky, but the point is that I just need to return the new size that I want to apply. I'm basically enforcing the aspect ratio, just caring if the user is moving the mouse mostly horizontally (in this case I'd leave the new width unchanged and compute the height accordingly) or vertically (in this case I'd do the opposite). Also I'm taking the
Insets in the computation, since they are usually non zero due to the capability of setting a
Border to the Widget.
Border. I actually want to change the border of my
- by drawing a solid, white border around the selected widget;
- by drawing a "resize border", with the classic eight control point where you should drag the mouse, when the mouse is hovering on the Widget.
This is also easy: I just need to override the
notifyStateChanged() method, that is called whenever the Widget changes state:
@OverrideJust keep in mind that
protected void notifyStateChanged (final ObjectState oldState, final ObjectState newState)
setBorder(newState.isSelected() ? (newState.isHovered() ? RESIZE_SELECTED_BORDER : SELECTED_BORDER) :
(newState.isHovered() ? RESIZE_BORDER : EMPTY_BORDER));
Widgets aren't the same classes as for the plain Swing component, and they have a specific
BorderFactory. The details for creating a border are quite boring and I'll leave you to inspect the source code for them.
That's enough for this post. I need to run tests on the new component, but I've seen only minor glitches so far and I'm pretty pleased with the small amount of code that the Visual Library required to implement the features I had in mind. The most important point that should be addressed is the persistence of the Light View, that is a way to "remember" the items and their positions. It should be not hard since the
Scene exposes methods for enumerating the contained
Widgets, but I'll deal with this later - also because this first experience with the Visual Library made me thinking of some other cool thing that I could do with it... :-)
And remember that I just scratched the surface of the Visual Library: even if you need more complex renderings, including graphs with nodes, arcs, floating connections, etc... , the Visual Library has probably what you need.