The Source for Java Technology Collaboration
User: Password:
Register | Login help    

Search

Online Books:
java.net on MarkMail:


hansmuller's Blog

Introducing the SceneGraph Project

Posted by hansmuller on January 8, 2008 at 6:50 PM PST

Introducing SceneGraph

I haven't written a blog entry since January when I advertised the fledgling Swing Application Framework (JSR-296) project with an uncharacteristically brief item. Work on that kept me busy until summer, when the Java FX juggernaut got underway here at Sun. Since then I've devoted much of my time to leading a project we've called "Scenario" that provides the graphical runtime for Java FX Script. We're now a public project on java.net called scenegraph.dev.java.net . You'll find downloads of the the 0.4.1 version of the Scenario source and binaries and (very) sketchy javadoc on the site. The 0.x version number is intended to convey the fact that the API hasn't stabilized yet. It's sufficient for experimentation and the implementation was robust enough to support a port of the Java FX Script interpreter, which we've showcased with a Scenario version of the FXPad Demo . Obviously, we don't recommend putting anything based on Scenario into production. Not yet.

The code is being made available under the GPLv2 license. Passion about open source licenses is not something I possess so I'll leave the shouting about the implications of this choice to others. Suffice it to say that, per my limited understanding of these matters, you're free to share the code, and in the process of developing it further. We're moving our discussions of the API to the newly minted Scene Graph java.net forum . If you're interested in the project's evolution, that would be a good place to start looking.

There are quite a few engineers working on Scenario, most of whom have made bigger contributions to the new software than I have, and you'll be hearing from them in their own blogs before too long. For now, what I'd like to do is to provide an introduction to the new Java APIs and just one demo. The team has written a whole raft of demos and we'll be opening up a subproject before too long, that contains the entire demo catalog.

Demo

All of the examples that follow are part of a demo, each one occupies a tab. If you press "control-T" after clicking on a demo, you'll get a nice interactive tree view of the scene graph's structure, thanks to Amy Fowler for that! So press the orange button to launch the demo.

Launch Button

Note also: the demo scales the selected example scene to fit. This is done with a small extension to JSGPanel, take a look at the code if you're interested.

Basics, Hello World

Intro Screenshot1

A scene graph, really a tree for now, is a data structure you create from leaf nodes that represent visual elements like 2D graphics and Swing components, filter nodes that represent visual state, like 2D transforms and composition, and group nodes that manage a list of children. All nodes are subclasses of SGNode and have a parent node, rectangular bounds, visibility, and support for event dispatching. Leaf nodes extend SGLeaf and have paint/repaint methods similar to AWT/Swing. SGFilter nodes have one child, and SGGroups have a list of them. To display a scene graph you set the scene property of a JSGPanel, and add the panel to a Swing application in the usual way. Here's a simple example:

SGText text = new SGText();
text.setText("Hello World");
JSGPanel panel = new JSGPanel();
panel.setScene(text);
panel.setPreferredSize(new Dimension(640, 480));
// create a JFrame, add the panel to it, etc..
    

One unusual line from the previous example, from a Swing programmer's perspective, is that we've explicitly set the preferred size of the JSGPanel. Although the JSGPanel will compute (and recompute...) a preferred size based on bounds of its scene, it's usually a good idea to define a reasonable fixed preferred size instead. To make the scene slightly more interesting to look at, we can set the SGText node's font and color, turn on antialiasing, and configure the panel's background color:

SGText text = new SGText();
text.setText("Hello World");
text.setFont(new Font("SansSerif", Font.PLAIN, 36));
text.setAntialiasingHint(RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
text.setFillPaint(Color.WHITE);
JSGPanel panel = new JSGPanel();
panel.setBackground(Color.BLACK);
panel.setScene(text);
panel.setPreferredSize(new Dimension(640, 480));
    

More Hello World: Groups and Shapes

Intro Screenshot2

Most of the Scenario classes are simple Java Beans. They provide null (no parameters) constructors and mutable properties. The API is intended to be "minimal" in the sense that it exposes the capabilities of the underlying Java 2D and Swing classes but does attempt to substantially simplify or abstract them. Scenario is intended to serve as the basis for higher level abstractions that require a scene graph, notably Java FX Script. For that reason, you may find programming against the Scenario API directly to be a bit tedious. Creating your own abstractions that simplify creating scene graphs is definitely the order of the day.

To add a border to the simple Hello World scene, we'll write a method that takes any scene graph node and puts a garish rounded rectangle border behind it. This example demonstrates using the SGShape leaf node which can render any Java 2D Shape , like lines, arcs, bezier curves, and of course rounded rectangles. Just to highlight the support for both filling and stroking (drawing the outline of) shapes, we'll do both here. The createBorder method creates a red and yellow rounded rectangle that's a little bigger than the node it's given. It returns an SGGroup scene graph node that contains both the original node and the "border".

SGNode createBorder(SGNode node) {
    Rectangle2D nodeR = node.getBounds();
    double borderWidth = 10;
    double x = nodeR.getX() - borderWidth;
    double y = nodeR.getY() - borderWidth;
    double w = nodeR.getWidth() + (2 * borderWidth);
    double h = nodeR.getHeight() + (2 * borderWidth);
    double a = 1.5 * borderWidth;
    SGShape border = new SGShape();
    border.setShape(new RoundRectangle2D.Double(x, y, w, h, a, a));
    border.setFillPaint(new Color(0x660000));
    border.setDrawPaint(new Color(0xFFFF33));
    border.setDrawStroke(new BasicStroke(borderWidth / 2.0));
    border.setMode(SGShape.Mode.STROKE_FILL);
    border.setAntialiasingHint(RenderingHints.VALUE_ANTIALIAS_ON);
    SGGroup borderedNode = new SGGroup();
    borderedNode.add(border);
    borderedNode.add(node);
    return borderedNode;
}
    

Adding a the border doesn't change the code that creates the scene graph very much:

SGText text = new SGText();
text.setText("Hello World");
// same as before ...
panel.setScene(createBorder(text));
    

Rotating Hello World: Transforms

Intro Screenshot3

As you can see in the previous example, the SGNode#getBounds() method returns a bounding box for the its node, in the way same way Component#getBounds() does for AWT and Swing components. In AWT and Swing, a component's parent node recursively defines its origin in terms of a translation, which is the value of getParent().getLocation(). Scene graphs are much more flexible than that. The relationship between a child and its parent node can be defined with any 2D affine transformation.

To rotate a scene graph node around a point, you have to assemble a chain of three transforms that: translate the node so that the rotation point is at the origin, rotate the desired amount, translate the node back to its original location. Transforms are created with SGTransform nodes, which are SGFilter subclasses because they have just one child, which is the node the transform is to be applied to. Here's a method that creates such a chain and uses the node's bounds' center as the rotation point:

SGTransform createRotation(SGNode node) {
    Rectangle2D nodeR = node.getBounds();
    double cx = nodeR.getCenterX();
    double cy = nodeR.getCenterY();
    SGTransform toOriginT = SGTransform.createTranslation(-cx, -cy, node);
    SGTransform.Rotate rotateT = SGTransform.createRotation(0.0, toOriginT);
    return SGTransform.createTranslation(cx, cy, rotateT);
}
    

To use the createRotation method we apply it to the node that's going to spin around its center, and then add the returned value to the scene instead of the node itself. The return value is the chain of three transform nodes followed by the original node. To specify a rotation you have to refer to the second SGTransform.Rotate from the chain and change its rotation property:

SGTransform scene = createRotation(node);
SGTransform.Rotate rotateT = (SGTransform.Rotate)scene.getChild();
rotateT.setRotation(...);
    

Images and More Layout

Intro Screenshot4

Images can be incorporated in a scene graph with the SGImage node type. To add one to our scene so that it appears to the right of the "Hello World" text, we'll have to create an SGGroup node that contains the text and the image, and then use SGTransform nodes to arrange the group's children along a row. Here's a method that creates such a group:

SGNode createRow(SGNode... nodes) {
    double rowHeight = 0.0;
    for(SGNode node : nodes) {
        rowHeight = Math.max(rowHeight, node.getBounds().getHeight());
    }
    SGGroup row = new SGGroup();
    double x = 0.0;
    double gap = 8.0;
    for(SGNode node : nodes) {        
        Rectangle2D nodeR = node.getBounds();
        double y = (rowHeight - nodeR.getHeight()) / 2.0;
        double dx = x - nodeR.getX();
        double dy = y - nodeR.getY();
        SGTransform xlate = SGTransform.createTranslation(dx, dy, node);
        row.add(xlate);
        x += nodeR.getWidth() + gap;
    }
    return row;
}
    

The code to create the SGImage node and the overall scene looks like this:

SGNode createEarth() {
    BufferedImage image = null; 
    // ... code to load the image file
    SGImage sgImage = new SGImage();
    sgImage.setImage(image);
    return sgImage;
}
// ...
SGNode row = createRow(createHelloWorldText(), createEarth());
SGNode scene = createBorder(row);
JSGPanel panel = new JSGPanel();
panel.setScene(scene);
    

Handling Input

Intro Screenshot5

All nodes can handle mouse and keyboard events and the support for doing so is very similar to what AWT/Swing provides. For example to make a node handle mouse events you add an SGMouseListener. SGMouseListener combines the methods from AWT's MouseListener and MouseMotionListener and it adds an SGNode argument to each one. SGKeyListener and SGFocusListener are similar. The source of a scene graph mouse or keyboard event is always a JSGPanel and the additional node argument indicates the node the event was actually dispatched to.

In this example, we've added a mouse listener to the earth node in the scene from the previous example and restored the support for rotating the scene. Dragging the earth with the mouse rotates the scene. Here's the code that sets up the scene and its SGMouseListener:

SGNode earth = createEarth();
SGNode row = createRow(createHelloWorldText(), earth);
SGTransform scene = createRotation(createBorder(row));
final SGTransform.Rotate rotateT = (SGTransform.Rotate)scene.getChild();
SGMouseListener changeRotation = new SGMouseAdapter() {
    @Override public void mouseDragged(MouseEvent e, SGNode node) {
        Rectangle2D r = e.getComponent().getBounds();
        double x = e.getX() - r.getCenterX();
        double y = e.getY() - r.getCenterY();
        rotateT.setRotation(Math.atan2(y, x));
    }
};
earth.addMouseListener(changeRotation);
JSGPanel panel = new JSGPanel();
panel.setScene(scene);
    

Although there's a great deal more to say by way of introducing Scenario, this blog entry has grown long enough that I'd better just take the same tack that Chet did , and declare this "Part 1".

The code for the examples can be found here: http://weblogs.java.net/blog/hansmuller/archive/Intro.java A easy to build version will appear along with the other Scenario demos, later this month.

Comments
Comments are listed in date ascending order (oldest first)

It is rather difficult to build and manipulate with scene graph using AffineTransform nodes. I think it may be simplified for example by integrating of SGNode with SGTransform allowing user to use simple bean methods like setX(), setY(), rotate(), scale(), etc... The main reason is that every "visual" node must be positioned inside scene and thus will always have corresponding translation node.

I've removed the alarming stack trace print that occurs when MasterTimer can't read it's System properties. It was harmless and unneccessary. I wish we had a better way to test sandboxed applications.

When I resize the frame I notice some lines flashing around the edges, giving it a not so smooth resizing feel. Otherwise it looks great. I am using Windows XP with Update N b08.

Thanks for the intro, Hans. Regarding the rotation example: if the rotation were static (or will be rendered many more times than it will be changed) then optimally you would want to concatenate the three transforms into one transform. Will the scene graph be able to detect this and optimize automatically similar to the way Java2D handles images for you? Or, perhaps more realistically, will there be a method we can call to perform such optimizations? Some scene graphs (Java3D, for example) have this ability to "compile" scene graphs.

Nice, Definately Nice...Sun is definately stepping is its game with this JavaFx thing...No frills just JAVA

Datapoint- Ctrl-T worked fine. mouse didn't respond to mouse at all. XP64 / 6N / run as webstart

I get the same exception under linux Java Web Start 1.6.0_03
Using JRE version 1.6.0_03 Java HotSpot(TM) Server VM and ctrl+t doesn't work for me either. Also the spacing between the characters in rotated text seems to be affected by rounding errors of some kind, this is most noticeable under the mouse tab where the characters seem to 'wobble' when rotated by small values. Other than that I'm looking forward to what comes out of this project.

Hi Hans--nice overview, thanks. I have the same problems with the demo--Ctrl T doesn't work for me. But the panels display correctly. Also, in the demo code, the first method, createFill(), sets anti-aliasing on but only fills a shape with a black color--what does AA mean in the context of a black fill? The AA method call makes me think there's room for (maybe the community to offer) some cover methods like "enableAA()" as an alternative to "setAntialiasingHint(RenderingHints.VALUE_TEXT_ANTIALIAS_ON)". The number of method calls involved makes me think an "extension" API designed with fluent interfaces might make this more fun to program with--JMock has been a real inspiration for me as regards fluent interfaces. Those are minor, and something a community member could take on to simplify use of the API. Looking forward to looking into the framework further, keep up the good writing. Thanks, Patrick

CTRL T does not work on OSX either. And I have the same exception

Control-T doesn't seem to do anything? (at least on my system: Windows XP, Java 6 (1.6.0_03))

If you check the MasterTimer.java source (Joe-Bob says check it out!), you'll find that the exception is a harmless diagnostic. I've asked that it be removed, since everyone (including myself) assume it is a fatal error.

OK: control-T doesn't work for me either! It does work when I launch the application locally, but not with the web started version. Will figure what's wrong, sorry about that.

java.security.AccessControlException: access denied (java.util.PropertyPermission com.sun.scenario.animationTimeline.nogaps read) at java.security.AccessControlContext.checkPermission(Unknown Source) at java.security.AccessController.checkPermission(Unknown Source) at java.lang.SecurityManager.checkPermission(Unknown Source) at java.lang.SecurityManager.checkPropertyAccess(Unknown Source) at java.lang.System.getProperty(Unknown Source) at java.lang.Boolean.getBoolean(Unknown Source) at com.sun.scenario.animation.MasterTimer.(MasterTimer.java:54) at com.sun.scenario.animation.Timeline.addFrameJob(Timeline.java:52) at com.sun.scenario.scenegraph.JSGPanelRepainter.(JSGPanelRepainter.java:53) at com.sun.scenario.scenegraph.JSGPanelRepainter.(JSGPanelRepainter.java:45) at com.sun.scenario.scenegraph.JSGPanel.markDirty(JSGPanel.java:525) at com.sun.scenario.scenegraph.JSGPanel.setScene(JSGPanel.java:102) at demo.intro.Intro$ScalingSGPanel.setScene(Intro.java:366) at demo.intro.Intro.createMainPanel(Intro.java:336) at demo.intro.Intro.startup(Intro.java:347)

The problem with control-T appeared to have been that the demo never requested focus. That was easily fixed however when I tried the (supposedly) updated app, no soap. I was somewhat busy with another project at the time and asked Igor Kushnirskiy to take a look at the problem. He pointed out that I hadn't been updating the right .jar file. He might have also pointed out that I'm a knucklehead, but tactful man that he is, he let the facts speak for themselves. So the good news is that Amy's scene graph viewer now appears on control-T, if you've clicked on the demo first.

Posted by: pdoubleya on January 09, 2008 at 02:16 AM Also, in the demo code, the first method, createFill(), sets anti-aliasing on but only fills a shape with a black color--what does AA mean in the context of a black fill? For the black filled arcs that appear in the "smiley" demo I didn't describe, the AA flag will cause the value of pixels that only partially overlap the curve to have an interpolated value that's relative to the color already there. You can see it if you use a screen magnifier. About enabling AA in general: I agree that doing so per node is clunky, albeit flexible. It would be useful to be able to enableAA(), as you say, for a subtree.

Posted by: diverson on January 09, 2008 at 01:37 PM Regarding the rotation example: if the rotation were static (or will be rendered many more times than it will be changed) then optimally you would want to concatenate the three transforms into one transform. Will the scene graph be able to detect this and optimize automatically similar to the way Java2D handles images for you? Or, perhaps more realistically, will there be a method we can call to perform such optimizations? Some scene graphs (Java3D, for example) have this ability to "compile" scene graphs. I agree: caching the relatively static parts of the scene graph in a form that's much closer to what's needed for rendering is definitely the path to better performance. We're working on that.

Introducing the SceneGraph Project

Posted by hansmuller on January 8, 2008 at 6:50 PM PST

Introducing SceneGraph

I haven't written a blog entry since January when I advertised the fledgling Swing Application Framework (JSR-296) project with an uncharacteristically brief item. Work on that kept me busy until summer, when the Java FX juggernaut got underway here at Sun. Since then I've devoted much of my time to leading a project we've called "Scenario" that provides the graphical runtime for Java FX Script. We're now a public project on java.net called scenegraph.dev.java.net . You'll find downloads of the the 0.4.1 version of the Scenario source and binaries and (very) sketchy javadoc on the site. The 0.x version number is intended to convey the fact that the API hasn't stabilized yet. It's sufficient for experimentation and the implementation was robust enough to support a port of the Java FX Script interpreter, which we've showcased with a Scenario version of the FXPad Demo . Obviously, we don't recommend putting anything based on Scenario into production. Not yet.

The code is being made available under the GPLv2 license. Passion about open source licenses is not something I possess so I'll leave the shouting about the implications of this choice to others. Suffice it to say that, per my limited understanding of these matters, you're free to share the code, and in the process of developing it further. We're moving our discussions of the API to the newly minted Scene Graph java.net forum . If you're interested in the project's evolution, that would be a good place to start looking.

There are quite a few engineers working on Scenario, most of whom have made bigger contributions to the new software than I have, and you'll be hearing from them in their own blogs before too long. For now, what I'd like to do is to provide an introduction to the new Java APIs and just one demo. The team has written a whole raft of demos and we'll be opening up a subproject before too long, that contains the entire demo catalog.

Demo

All of the examples that follow are part of a demo, each one occupies a tab. If you press "control-T" after clicking on a demo, you'll get a nice interactive tree view of the scene graph's structure, thanks to Amy Fowler for that! So press the orange button to launch the demo.

Launch Button

Note also: the demo scales the selected example scene to fit. This is done with a small extension to JSGPanel, take a look at the code if you're interested.

Basics, Hello World

Intro Screenshot1

A scene graph, really a tree for now, is a data structure you create from leaf nodes that represent visual elements like 2D graphics and Swing components, filter nodes that represent visual state, like 2D transforms and composition, and group nodes that manage a list of children. All nodes are subclasses of SGNode and have a parent node, rectangular bounds, visibility, and support for event dispatching. Leaf nodes extend SGLeaf and have paint/repaint methods similar to AWT/Swing. SGFilter nodes have one child, and SGGroups have a list of them. To display a scene graph you set the scene property of a JSGPanel, and add the panel to a Swing application in the usual way. Here's a simple example:

SGText text = new SGText();
text.setText("Hello World");
JSGPanel panel = new JSGPanel();
panel.setScene(text);
panel.setPreferredSize(new Dimension(640, 480));
// create a JFrame, add the panel to it, etc..
    

One unusual line from the previous example, from a Swing programmer's perspective, is that we've explicitly set the preferred size of the JSGPanel. Although the JSGPanel will compute (and recompute...) a preferred size based on bounds of its scene, it's usually a good idea to define a reasonable fixed preferred size instead. To make the scene slightly more interesting to look at, we can set the SGText node's font and color, turn on antialiasing, and configure the panel's background color:

SGText text = new SGText();
text.setText("Hello World");
text.setFont(new Font("SansSerif", Font.PLAIN, 36));
text.setAntialiasingHint(RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
text.setFillPaint(Color.WHITE);
JSGPanel panel = new JSGPanel();
panel.setBackground(Color.BLACK);
panel.setScene(text);
panel.setPreferredSize(new Dimension(640, 480));
    

More Hello World: Groups and Shapes

Intro Screenshot2

Most of the Scenario classes are simple Java Beans. They provide null (no parameters) constructors and mutable properties. The API is intended to be "minimal" in the sense that it exposes the capabilities of the underlying Java 2D and Swing classes but does attempt to substantially simplify or abstract them. Scenario is intended to serve as the basis for higher level abstractions that require a scene graph, notably Java FX Script. For that reason, you may find programming against the Scenario API directly to be a bit tedious. Creating your own abstractions that simplify creating scene graphs is definitely the order of the day.

To add a border to the simple Hello World scene, we'll write a method that takes any scene graph node and puts a garish rounded rectangle border behind it. This example demonstrates using the SGShape leaf node which can render any Java 2D Shape , like lines, arcs, bezier curves, and of course rounded rectangles. Just to highlight the support for both filling and stroking (drawing the outline of) shapes, we'll do both here. The createBorder method creates a red and yellow rounded rectangle that's a little bigger than the node it's given. It returns an SGGroup scene graph node that contains both the original node and the "border".

SGNode createBorder(SGNode node) {
    Rectangle2D nodeR = node.getBounds();
    double borderWidth = 10;
    double x = nodeR.getX() - borderWidth;
    double y = nodeR.getY() - borderWidth;
    double w = nodeR.getWidth() + (2 * borderWidth);
    double h = nodeR.getHeight() + (2 * borderWidth);
    double a = 1.5 * borderWidth;
    SGShape border = new SGShape();
    border.setShape(new RoundRectangle2D.Double(x, y, w, h, a, a));
    border.setFillPaint(new Color(0x660000));
    border.setDrawPaint(new Color(0xFFFF33));
    border.setDrawStroke(new BasicStroke(borderWidth / 2.0));
    border.setMode(SGShape.Mode.STROKE_FILL);
    border.setAntialiasingHint(RenderingHints.VALUE_ANTIALIAS_ON);
    SGGroup borderedNode = new SGGroup();
    borderedNode.add(border);
    borderedNode.add(node);
    return borderedNode;
}
    

Adding a the border doesn't change the code that creates the scene graph very much:

SGText text = new SGText();
text.setText("Hello World");
// same as before ...
panel.setScene(createBorder(text));
    

Rotating Hello World: Transforms

Intro Screenshot3

As you can see in the previous example, the SGNode#getBounds() method returns a bounding box for the its node, in the way same way Component#getBounds() does for AWT and Swing components. In AWT and Swing, a component's parent node recursively defines its origin in terms of a translation, which is the value of getParent().getLocation(). Scene graphs are much more flexible than that. The relationship between a child and its parent node can be defined with any 2D affine transformation.

To rotate a scene graph node around a point, you have to assemble a chain of three transforms that: translate the node so that the rotation point is at the origin, rotate the desired amount, translate the node back to its original location. Transforms are created with SGTransform nodes, which are SGFilter subclasses because they have just one child, which is the node the transform is to be applied to. Here's a method that creates such a chain and uses the node's bounds' center as the rotation point:

SGTransform createRotation(SGNode node) {
    Rectangle2D nodeR = node.getBounds();
    double cx = nodeR.getCenterX();
    double cy = nodeR.getCenterY();
    SGTransform toOriginT = SGTransform.createTranslation(-cx, -cy, node);
    SGTransform.Rotate rotateT = SGTransform.createRotation(0.0, toOriginT);
    return SGTransform.createTranslation(cx, cy, rotateT);
}
    

To use the createRotation method we apply it to the node that's going to spin around its center, and then add the returned value to the scene instead of the node itself. The return value is the chain of three transform nodes followed by the original node. To specify a rotation you have to refer to the second SGTransform.Rotate from the chain and change its rotation property:

SGTransform scene = createRotation(node);
SGTransform.Rotate rotateT = (SGTransform.Rotate)scene.getChild();
rotateT.setRotation(...);
    

Images and More Layout

Intro Screenshot4

Images can be incorporated in a scene graph with the SGImage node type. To add one to our scene so that it appears to the right of the "Hello World" text, we'll have to create an SGGroup node that contains the text and the image, and then use SGTransform nodes to arrange the group's children along a row. Here's a method that creates such a group:

SGNode createRow(SGNode... nodes) {
    double rowHeight = 0.0;
    for(SGNode node : nodes) {
        rowHeight = Math.max(rowHeight, node.getBounds().getHeight());
    }
    SGGroup row = new SGGroup();
    double x = 0.0;
    double gap = 8.0;
    for(SGNode node : nodes) {        
        Rectangle2D nodeR = node.getBounds();
        double y = (rowHeight - nodeR.getHeight()) / 2.0;
        double dx = x - nodeR.getX();
        double dy = y - nodeR.getY();
        SGTransform xlate = SGTransform.createTranslation(dx, dy, node);
        row.add(xlate);
        x += nodeR.getWidth() + gap;
    }
    return row;
}
    

The code to create the SGImage node and the overall scene looks like this:

SGNode createEarth() {
    BufferedImage image = null; 
    // ... code to load the image file
    SGImage sgImage = new SGImage();
    sgImage.setImage(image);
    return sgImage;
}
// ...
SGNode row = createRow(createHelloWorldText(), createEarth());
SGNode scene = createBorder(row);
JSGPanel panel = new JSGPanel();
panel.setScene(scene);
    

Handling Input

Intro Screenshot5

All nodes can handle mouse and keyboard events and the support for doing so is very similar to what AWT/Swing provides. For example to make a node handle mouse events you add an SGMouseListener. SGMouseListener combines the methods from AWT's MouseListener and MouseMotionListener and it adds an SGNode argument to each one. SGKeyListener and SGFocusListener are similar. The source of a scene graph mouse or keyboard event is always a JSGPanel and the additional node argument indicates the node the event was actually dispatched to.

In this example, we've added a mouse listener to the earth node in the scene from the previous example and restored the support for rotating the scene. Dragging the earth with the mouse rotates the scene. Here's the code that sets up the scene and its SGMouseListener:

SGNode earth = createEarth();
SGNode row = createRow(createHelloWorldText(), earth);
SGTransform scene = createRotation(createBorder(row));
final SGTransform.Rotate rotateT = (SGTransform.Rotate)scene.getChild();
SGMouseListener changeRotation = new SGMouseAdapter() {
    @Override public void mouseDragged(MouseEvent e, SGNode node) {
        Rectangle2D r = e.getComponent().getBounds();
        double x = e.getX() - r.getCenterX();
        double y = e.getY() - r.getCenterY();
        rotateT.setRotation(Math.atan2(y, x));
    }
};
earth.addMouseListener(changeRotation);
JSGPanel panel = new JSGPanel();
panel.setScene(scene);
    

Although there's a great deal more to say by way of introducing Scenario, this blog entry has grown long enough that I'd better just take the same tack that Chet did , and declare this "Part 1".

The code for the examples can be found here: http://weblogs.java.net/blog/hansmuller/archive/Intro.java A easy to build version will appear along with the other Scenario demos, later this month.

Comments
Comments are listed in date ascending order (oldest first)

It is rather difficult to build and manipulate with scene graph using AffineTransform nodes. I think it may be simplified for example by integrating of SGNode with SGTransform allowing user to use simple bean methods like setX(), setY(), rotate(), scale(), etc... The main reason is that every "visual" node must be positioned inside scene and thus will always have corresponding translation node.

I've removed the alarming stack trace print that occurs when MasterTimer can't read it's System properties. It was harmless and unneccessary. I wish we had a better way to test sandboxed applications.

When I resize the frame I notice some lines flashing around the edges, giving it a not so smooth resizing feel. Otherwise it looks great. I am using Windows XP with Update N b08.

Thanks for the intro, Hans. Regarding the rotation example: if the rotation were static (or will be rendered many more times than it will be changed) then optimally you would want to concatenate the three transforms into one transform. Will the scene graph be able to detect this and optimize automatically similar to the way Java2D handles images for you? Or, perhaps more realistically, will there be a method we can call to perform such optimizations? Some scene graphs (Java3D, for example) have this ability to "compile" scene graphs.

Nice, Definately Nice...Sun is definately stepping is its game with this JavaFx thing...No frills just JAVA

Datapoint- Ctrl-T worked fine. mouse didn't respond to mouse at all. XP64 / 6N / run as webstart

I get the same exception under linux Java Web Start 1.6.0_03
Using JRE version 1.6.0_03 Java HotSpot(TM) Server VM and ctrl+t doesn't work for me either. Also the spacing between the characters in rotated text seems to be affected by rounding errors of some kind, this is most noticeable under the mouse tab where the characters seem to 'wobble' when rotated by small values. Other than that I'm looking forward to what comes out of this project.

Hi Hans--nice overview, thanks. I have the same problems with the demo--Ctrl T doesn't work for me. But the panels display correctly. Also, in the demo code, the first method, createFill(), sets anti-aliasing on but only fills a shape with a black color--what does AA mean in the context of a black fill? The AA method call makes me think there's room for (maybe the community to offer) some cover methods like "enableAA()" as an alternative to "setAntialiasingHint(RenderingHints.VALUE_TEXT_ANTIALIAS_ON)". The number of method calls involved makes me think an "extension" API designed with fluent interfaces might make this more fun to program with--JMock has been a real inspiration for me as regards fluent interfaces. Those are minor, and something a community member could take on to simplify use of the API. Looking forward to looking into the framework further, keep up the good writing. Thanks, Patrick

CTRL T does not work on OSX either. And I have the same exception

Control-T doesn't seem to do anything? (at least on my system: Windows XP, Java 6 (1.6.0_03))

If you check the MasterTimer.java source (Joe-Bob says check it out!), you'll find that the exception is a harmless diagnostic. I've asked that it be removed, since everyone (including myself) assume it is a fatal error.

OK: control-T doesn't work for me either! It does work when I launch the application locally, but not with the web started version. Will figure what's wrong, sorry about that.

java.security.AccessControlException: access denied (java.util.PropertyPermission com.sun.scenario.animationTimeline.nogaps read) at java.security.AccessControlContext.checkPermission(Unknown Source) at java.security.AccessController.checkPermission(Unknown Source) at java.lang.SecurityManager.checkPermission(Unknown Source) at java.lang.SecurityManager.checkPropertyAccess(Unknown Source) at java.lang.System.getProperty(Unknown Source) at java.lang.Boolean.getBoolean(Unknown Source) at com.sun.scenario.animation.MasterTimer.(MasterTimer.java:54) at com.sun.scenario.animation.Timeline.addFrameJob(Timeline.java:52) at com.sun.scenario.scenegraph.JSGPanelRepainter.(JSGPanelRepainter.java:53) at com.sun.scenario.scenegraph.JSGPanelRepainter.(JSGPanelRepainter.java:45) at com.sun.scenario.scenegraph.JSGPanel.markDirty(JSGPanel.java:525) at com.sun.scenario.scenegraph.JSGPanel.setScene(JSGPanel.java:102) at demo.intro.Intro$ScalingSGPanel.setScene(Intro.java:366) at demo.intro.Intro.createMainPanel(Intro.java:336) at demo.intro.Intro.startup(Intro.java:347)

The problem with control-T appeared to have been that the demo never requested focus. That was easily fixed however when I tried the (supposedly) updated app, no soap. I was somewhat busy with another project at the time and asked Igor Kushnirskiy to take a look at the problem. He pointed out that I hadn't been updating the right .jar file. He might have also pointed out that I'm a knucklehead, but tactful man that he is, he let the facts speak for themselves. So the good news is that Amy's scene graph viewer now appears on control-T, if you've clicked on the demo first.

Posted by: pdoubleya on January 09, 2008 at 02:16 AM Also, in the demo code, the first method, createFill(), sets anti-aliasing on but only fills a shape with a black color--what does AA mean in the context of a black fill? For the black filled arcs that appear in the "smiley" demo I didn't describe, the AA flag will cause the value of pixels that only partially overlap the curve to have an interpolated value that's relative to the color already there. You can see it if you use a screen magnifier. About enabling AA in general: I agree that doing so per node is clunky, albeit flexible. It would be useful to be able to enableAA(), as you say, for a subtree.

Posted by: diverson on January 09, 2008 at 01:37 PM Regarding the rotation example: if the rotation were static (or will be rendered many more times than it will be changed) then optimally you would want to concatenate the three transforms into one transform. Will the scene graph be able to detect this and optimize automatically similar to the way Java2D handles images for you? Or, perhaps more realistically, will there be a method we can call to perform such optimizations? Some scene graphs (Java3D, for example) have this ability to "compile" scene graphs. I agree: caching the relatively static parts of the scene graph in a form that's much closer to what's needed for rendering is definitely the path to better performance. We're working on that.

Application Framework Prototype Bows

Posted by hansmuller on January 30, 2007 at 8:03 AM PST

I've made a prototype of the fledgling JSR-296 API available, it's https://appframework.dev.java.net/. There's a quick overview doc and downloads of the source code, the javadoc, and the AppFramework.jar file. If you're interested in this API, please take a look at the overview, and download the code and then take a look at some of the examples and the javadoc. You can post feedback here or, if you want to participate in the long-term discussion, subscribe to the appframework.dev.java.net "users" mailing list: https://appframework.dev.java.net/servlets/ProjectMailingListList . The users alias is the last one listed.

That's all I really wanted to say. I don't want to make too much of a commotion about this version of the design because there's still quite a bit that remains to be done. I was hoping that this would be a sort-of stealth release: not terribly noticeable, unless you know where to look. On the other hand, I know there are Swing developers who aren't members of the JSR-296 expert group, who'd like to take stock of where this project is going. And I know there are experienced Swing developers out there, some of whom have built their own application frameworks, that would like see how this one measures up. I'd welcome feedback from anyone who's interested and I'll promise to respond promptly, unless you bring up a really difficult issue or a really large number of them. That might take longer.

Note also: the JCP defines a milestone called "Early Draft Review" that means the expert group thinks the spec is complete enough to begin fine tuning. We have not reached that milestone yet.

Comments
Comments are listed in date ascending order (oldest first)

Application Framework Prototype Bows

Posted by hansmuller on January 30, 2007 at 8:03 AM PST

I've made a prototype of the fledgling JSR-296 API available, it's https://appframework.dev.java.net/. There's a quick overview doc and downloads of the source code, the javadoc, and the AppFramework.jar file. If you're interested in this API, please take a look at the overview, and download the code and then take a look at some of the examples and the javadoc. You can post feedback here or, if you want to participate in the long-term discussion, subscribe to the appframework.dev.java.net "users" mailing list: https://appframework.dev.java.net/servlets/ProjectMailingListList . The users alias is the last one listed.

That's all I really wanted to say. I don't want to make too much of a commotion about this version of the design because there's still quite a bit that remains to be done. I was hoping that this would be a sort-of stealth release: not terribly noticeable, unless you know where to look. On the other hand, I know there are Swing developers who aren't members of the JSR-296 expert group, who'd like to take stock of where this project is going. And I know there are experienced Swing developers out there, some of whom have built their own application frameworks, that would like see how this one measures up. I'd welcome feedback from anyone who's interested and I'll promise to respond promptly, unless you bring up a really difficult issue or a really large number of them. That might take longer.

Note also: the JCP defines a milestone called "Early Draft Review" that means the expert group thinks the spec is complete enough to begin fine tuning. We have not reached that milestone yet.

Comments
Comments are listed in date ascending order (oldest first)

Property Syntax for Java? A Constructive Alternative

Posted by hansmuller on January 10, 2007 at 5:57 AM PST

Having written, by conservative estimates, about a jillion Java Beans classes over the years, I have to say that I'm amazed that we'd seriously consider changing the Java language to trivialize this kind of Java Bean property. It certainly is a property per the spec, a read/write property at that, but - as a Swing developer - it's the kind of property I almost never write. And if repetitive boilerplate is what we're hunting with this language change, then we're shooting at rabbits while a herd of buffalo thunders by. The mighty buffalo of the Java Beans boilerplate animal kingdom are bound properties. They're the kind of properties we write so that our beans can be automatically and dynamically synchronized with a GUI or with each other. As a desktop developer, I almost always write bound properties.

To write a bound property properly you've got to ensure that your class defines or inherits support for a PropertyChangeListener. That's about 20 lines of code just to get started:

class FooBean {
    private final java.beans.PropertyChangeSupport pcs;
    public FooBean () {
	pcs = new PropertyChangeSupport(this);
    }
    public void addPropertyChangeListener(PropertyChangeListener listener) {
        pcs.addPropertyChangeListener(listener);
    }
    public void removePropertyChangeListener(PropertyChangeListener listener) {
        pcs.removePropertyChangeListener(listener);
    }
    public PropertyChangeListener[] getPropertyChangeListeners() {
        return pcs.getPropertyChangeListeners();
    }
    protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
        pcs.firePropertyChange(propertyName, oldValue, newValue);
    }
}
    

And then there's the definition of each read/write property which should: check the validity of new values in its set method as well as calling firePropertyChange to notify PropertyChangeListeners, and defensively copy the return value (if necessary) in its get method. I suppose one could concoct syntax that would simplify all of this, at least a little, as well as allowing for read-only/write-only variants. But that's not the proposal I wanted to make here.

If you consider the property keyword proposal in light of Java's origins in the C language, then it's pretty clear what the proposal's proponents are really after: structs. It's not about defining properties, it's about simplifying defining a Java class that's comparable to a struct in the C language. So perhaps the proposal should really focus on allowing one to write classes, not properties. Where this:

struct FooBean { Foo foo; }
    
Would by equivalant to this (as before):
class FooBean {
    private Foo foo; 
    public Foo getFoo() { return foo; }
    public void setFoo(Foo foo) { this.foo = foo; }
}
    

If you admit that the focus of the property proposal is really adding support for defining structs in Java, then using "->" to refer to struct properties feels like coming home again.

struct Point { int x, y; }
Point p = new Point(); 
p->x = p->y = 0; // oh joy
    

I'm not a language design expert however I would think that I would be among the target developers for Java language feature designed to support properties. In my humble (ha) opinion, the current proposal serves the needs of Java Beans developers poorly by targeting a special case that doesn't warrant language support. Although I would welcome a proposal that also simplified defining bound properties, I would guess that it would be hard to invent syntax that would handle the general case without being obscure. If there is a consituency for the current proposal, I say: give them structs instead.

Related Topics >> Java Desktop      
Comments
Comments are listed in date ascending order (oldest first)

Properties access with pointers p->x

I am wondering if an extension to java.util.Properties that I've published over on Google Code meets this need? http://code.google.com/p/eproperties/ EProperties supports nested properties objects, and allows deep access using the 'pointer' syntax.

Property Syntax for Java? A Constructive Alternative

Posted by hansmuller on January 10, 2007 at 5:57 AM PST

Having written, by conservative estimates, about a jillion Java Beans classes over the years, I have to say that I'm amazed that we'd seriously consider changing the Java language to trivialize this kind of Java Bean property. It certainly is a property per the spec, a read/write property at that, but - as a Swing developer - it's the kind of property I almost never write. And if repetitive boilerplate is what we're hunting with this language change, then we're shooting at rabbits while a herd of buffalo thunders by. The mighty buffalo of the Java Beans boilerplate animal kingdom are bound properties. They're the kind of properties we write so that our beans can be automatically and dynamically synchronized with a GUI or with each other. As a desktop developer, I almost always write bound properties.

To write a bound property properly you've got to ensure that your class defines or inherits support for a PropertyChangeListener. That's about 20 lines of code just to get started:

class FooBean {
    private final java.beans.PropertyChangeSupport pcs;
    public FooBean () {
	pcs = new PropertyChangeSupport(this);
    }
    public void addPropertyChangeListener(PropertyChangeListener listener) {
        pcs.addPropertyChangeListener(listener);
    }
    public void removePropertyChangeListener(PropertyChangeListener listener) {
        pcs.removePropertyChangeListener(listener);
    }
    public PropertyChangeListener[] getPropertyChangeListeners() {
        return pcs.getPropertyChangeListeners();
    }
    protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
        pcs.firePropertyChange(propertyName, oldValue, newValue);
    }
}
    

And then there's the definition of each read/write property which should: check the validity of new values in its set method as well as calling firePropertyChange to notify PropertyChangeListeners, and defensively copy the return value (if necessary) in its get method. I suppose one could concoct syntax that would simplify all of this, at least a little, as well as allowing for read-only/write-only variants. But that's not the proposal I wanted to make here.

If you consider the property keyword proposal in light of Java's origins in the C language, then it's pretty clear what the proposal's proponents are really after: structs. It's not about defining properties, it's about simplifying defining a Java class that's comparable to a struct in the C language. So perhaps the proposal should really focus on allowing one to write classes, not properties. Where this:

struct FooBean { Foo foo; }
    
Would by equivalant to this (as before):
class FooBean {
    private Foo foo; 
    public Foo getFoo() { return foo; }
    public void setFoo(Foo foo) { this.foo = foo; }
}
    

If you admit that the focus of the property proposal is really adding support for defining structs in Java, then using "->" to refer to struct properties feels like coming home again.

struct Point { int x, y; }
Point p = new Point(); 
p->x = p->y = 0; // oh joy
    

I'm not a language design expert however I would think that I would be among the target developers for Java language feature designed to support properties. In my humble (ha) opinion, the current proposal serves the needs of Java Beans developers poorly by targeting a special case that doesn't warrant language support. Although I would welcome a proposal that also simplified defining bound properties, I would guess that it would be hard to invent syntax that would handle the general case without being obscure. If there is a consituency for the current proposal, I say: give them structs instead.

Related Topics >> Java Desktop      
Comments
Comments are listed in date ascending order (oldest first)

Properties access with pointers p->x

I am wondering if an extension to java.util.Properties that I've published over on Google Code meets this need? http://code.google.com/p/eproperties/ EProperties supports nested properties objects, and allows deep access using the 'pointer' syntax.

Javapolis Session Interrupted by Marriage Proposal

Posted by hansmuller on December 17, 2006 at 4:34 PM PST
Javapolis Session Interrupted by Marriage Proposal

Javapolis 2006

I spent most of last week at the Javapolis conference in Antwerp Belgium. With nearly 3000 attendees, it's the second biggest Java conference after JavaOne, and it's held in a movie cineplex complex called "Metropolis". The venue delivers large halls, comfortable seats (with beverage caddies!), and dramatic lighting. Half of each enormous movie screen is devoted to a live image of the presenter and the camerawork is quite good, pausing for a dramatic closeup now and then. My laptop wasn't working correctly so I had to turn around periodically, to visually sync up with my slides. That meant that the audience was treated to six linear feet of my balding hemisphere glowing in the klieg lights. I was thankful that everyone put up with that, and also with my raspy voice. I'd managed to pick up a cold the day before leaving, and only hours before I was scheduled to speak, I could do little more than croak and wheeze. Things were somewhat better by the showtime, however from the sound of my voice you might think I'd spent the day smoking heavily and shrieking.

My presentation topic was JSR-296 "Swing Application Framework". If you're interested, there's a copy here. Within a few months you'll find a nicely edited slides-and-video version of the presentation on parleys.com, thanks to Stephan Janssen, the tireless Javapolis organizer. And when it appears, do not miss the finale. As Richard Bair and I finished up a demo of a prototype of the Application Framework in NetBeans, Stephan appeared and asked to interrupt the proceedings for a special announcment. He picked up the microphone and asked a woman who's name I didn't catch to come forward. Sure enough, a tall young blonde woman came to the front, looking nonplussed. After a brief pause, a young man wearing a suit came loping down the aisle and in a moment he was facing the woman and making a declaration of some kind in Dutch. She looked pretty nervous and, not knowing where this was all going, we started feeling a bit anxious too. Until he dropped to one knee and assumed the international marriage proposal position. Then we really started to worry, because here was a situation that could only end really well, or really badly. The woman was crying (uh oh) and smiling (whew!), and then nodding her head, and to everyone's relief, we had a happy betrothal. The couple bounded up the steps to a nice round of applause, and Richard and I tried to remember what it was we'd been talking about.

The Javapolis conference was memorable as always. One evening the organizers arranged for enough french fries and mayonaise to serve 3000 (that's a lot), they showed the new James Bond movie, Mark Fleury performed his keynote dressed up like Flavor Flav, Sun raffled away a Nintendo Wii, the technical sessions were solid, and, mine was interrupted by a proposal of marriage. With a happy ending.

Merry Christmas!

Update: Stephan Janssen has published the video, you'll find it here:
http://www.javapolis.com/confluence/display/JP06/2006/12/22/JavaPolis+20...

Related Topics >> Java Desktop      
Comments
Comments are listed in date ascending order (oldest first)

Javapolis Session Interrupted by Marriage Proposal

Posted by hansmuller on December 17, 2006 at 4:34 PM PST
Javapolis Session Interrupted by Marriage Proposal

Javapolis 2006

I spent most of last week at the Javapolis conference in Antwerp Belgium. With nearly 3000 attendees, it's the second biggest Java conference after JavaOne, and it's held in a movie cineplex complex called "Metropolis". The venue delivers large halls, comfortable seats (with beverage caddies!), and dramatic lighting. Half of each enormous movie screen is devoted to a live image of the presenter and the camerawork is quite good, pausing for a dramatic closeup now and then. My laptop wasn't working correctly so I had to turn around periodically, to visually sync up with my slides. That meant that the audience was treated to six linear feet of my balding hemisphere glowing in the klieg lights. I was thankful that everyone put up with that, and also with my raspy voice. I'd managed to pick up a cold the day before leaving, and only hours before I was scheduled to speak, I could do little more than croak and wheeze. Things were somewhat better by the showtime, however from the sound of my voice you might think I'd spent the day smoking heavily and shrieking.

My presentation topic was JSR-296 "Swing Application Framework". If you're interested, there's a copy here. Within a few months you'll find a nicely edited slides-and-video version of the presentation on parleys.com, thanks to Stephan Janssen, the tireless Javapolis organizer. And when it appears, do not miss the finale. As Richard Bair and I finished up a demo of a prototype of the Application Framework in NetBeans, Stephan appeared and asked to interrupt the proceedings for a special announcment. He picked up the microphone and asked a woman who's name I didn't catch to come forward. Sure enough, a tall young blonde woman came to the front, looking nonplussed. After a brief pause, a young man wearing a suit came loping down the aisle and in a moment he was facing the woman and making a declaration of some kind in Dutch. She looked pretty nervous and, not knowing where this was all going, we started feeling a bit anxious too. Until he dropped to one knee and assumed the international marriage proposal position. Then we really started to worry, because here was a situation that could only end really well, or really badly. The woman was crying (uh oh) and smiling (whew!), and then nodding her head, and to everyone's relief, we had a happy betrothal. The couple bounded up the steps to a nice round of applause, and Richard and I tried to remember what it was we'd been talking about.

The Javapolis conference was memorable as always. One evening the organizers arranged for enough french fries and mayonaise to serve 3000 (that's a lot), they showed the new James Bond movie, Mark Fleury performed his keynote dressed up like Flavor Flav, Sun raffled away a Nintendo Wii, the technical sessions were solid, and, mine was interrupted by a proposal of marriage. With a happy ending.

Merry Christmas!

Update: Stephan Janssen has published the video, you'll find it here:
http://www.javapolis.com/confluence/display/JP06/2006/12/22/JavaPolis+20...

Related Topics >> Java Desktop      
Comments
Comments are listed in date ascending order (oldest first)

Dialog Diatribe

Posted by hansmuller on October 27, 2006 at 5:48 PM PDT

I've been writing the occasional small application recently and now and then I blunder into a problem with Java SE that's, uh..., well, annoying. I realize that I'm not the only one who's had this experience and I'm probably not the only one who seeks relief by writing a lengthy diatribe and then sending it to whomever might be guilty of creating the situation. Of course, in my case that's often me, and since relief usually doesn't come from berating oneself, I'm guilty of sending the occasional long crabby missive to the people who are currently responsible for maintaining things that I'm probably responsible for bollocksing up in the first place. It's not a particularly endearing habit.

I sent the following to Swing's technical lead, Shannon Hickey, and he confirmed that the details, though twisted with bile, are essentially correct. So in the interest of furthering my own therapy, and also to ensure that some record of this will be stored away in Google's indices till the end of time, I thought I'd share.

I would think that a fairly common idiom in a Swing application would be to popup a dialog in response to selecting a menu item. Given Matisse, we'll assume that the JDialog has been created with the IDE, rather than some JOptionPane convenience method, and given rudimentary aesthetics, assume the dialog should be centered over the menu item's frame. Accomplishing this seems to be much too difficult:

public void showMyDialog(ActionEvent e) {
    // How to find the Dialog's Frame owner?
    Window dialogOwner = null;
    JDialog dialog = new MyDialog(dialogOwner, true); // true => modal Dialog
    dialog.pack();
    // How to center the Dialog?
    dailog.setVisible(true);
}

The first problem to deal with is mapping from the menu item's ActionEvent to the frame that contains the menu item. The frame will be the dialog's owner as well as the component we're going to center the dialog relative to.

There seems to be an overabundance of SwingUtilities methods that address this trivial problem:

  • Window getWindowAncestor(Component c)
  • Window windowForComponent(Component c)
  • Component getRoot(Component c)
Sadly, none of them "work" for a JMenuItem. They all simply traipse up the parent chain and in our case they find a JPopupMenu and then null.

To find the Frame that owns a JMenuItem, we have to follow the JPopupMenu's "invoker" property, which gets us back into the component hierarchy. So to find the frame that corresponds to an ActionEvent one must write (!):

Frame frameForActionEvent(ActionEvent e) {
    if (e.getSource() instanceof Component) {
        Component c = (Component)e.getSource();
        while(c != null) {
            if (c instanceof Frame) {
	        return (Frame)c;
            }
            c = (c instanceof JPopupMenu) ? ((JPopupMenu)c).getInvoker() : c.getParent();
        }
    }
    return null;
}

It would more useful to have written windowForActionEvent but sadly support for creating Dialogs whose owner is a Window (the parent class for Frames and Dialogs) only appeared in Java SE 6, and I needed code that worked for Java SE 5. It's also worth noting that this works for Applets too, although you'd be forgiven for not guessing that this is true. Applets do have a Frame parent that's created by the Java plugin and whose bounds are the same as the Applet (Panel) itself.

But we're still not done, because we must also center the dialog over the frame. Naturally there are other useful positions for the dialog. Centering a dialog over its frame happens to be what started me on this quest.

I have it on good authority that Windows.setLocationRelativeTo() is the handy method for this job. The javadoc for this method isn't promising:

Sets the location of the window relative to the specified component.

OK so far. Except it sounds like I'm going to have to compute the relative origin of my dialog and deal with edge (of the screen) conditions. Yech.

If the component is not currently showing, or c is null, the window is placed at the center of the screen. The center point can be determined with GraphicsEnvironment.getCenterPoint [sic]

Huh? What does "the component" refer to in this sentence? I assume they're not referring to this Window and I have to wonder what "showing" means in this context. Is is the same thing as getVisible() being true? If not, do I have to hope that my menu item is still "showing" when this method is called? And what's this advice about getCenterPoint (and where's the period)? In my case the Window and its menu item are visible, so I'm hoping none of this stuff applies. Because I don't really understand it.

If the bottom of the component is offscreen, the window is placed to the side of the Component that is closest to the center of the screen. So if the Component is on the right part of the screen, the Window is placed to its left, and visa versa.

This, no doubt, means that the method will endeavor to find a location for my dialog that respects the relative location I've specified, without making part of the dialog appear off-screen. Good, I think.

So I still appear to be stuck with computing an origin for my dialog that centers it relative to its owner. Before I code that, I try leaving the origin of the new dialog at 0,0, which is the default:

public void showMyDialog(ActionEvent e) {
    Window dialogOwner = frameForActionEvent(e);
    JDialog dialog = new MyDialog(dialogOwner, true);
    dialog.pack();
    aboutBox.setLocationRelativeTo(dialogOwner);
    dialog.setVisible(true);
}

Miraculously, this works. The dialog appears centered over the dialogOwner unless that would cause the dialog to appear off-screen. I have no idea why it works, since according to the "spec" (and the name of the method) I should have had to compute an appropriate relative origin for the dialog. But I guess I don't.

Frankly, I think this whole mess is a mini-travesty. If I'm going to show a dialog, I should be able to do so without writing code that digs around the component hierarchy and without experimentally determining what something as simple (and not terribly useful) as Window.setLocationRelativeTo() does.

There, that feels a little better.

A seven year old bug that covers the menu item to frame lookup problem is still open. Given the fact that it's accumulated exactly 0 votes in that time, perhaps no one has ever cared about the problem quite as much as I do at this moment. I would think that a cleaner way to handle this case would be some static methods that handled the entire idiom, for example:

public void showMyDialog(AWTEvent event) {
    Window dialogOwner = Window.eventToWindow(event);
    JDialog dialog = new MyDialog(dialogOwner, true);
    Window.showModalDialog(dialog); // Center dialog relative to its owner
}

And Shannon suggested that the method name might be rationalized as implying that the Window is be moved to a location that makes its relationship to the component parameter obvious. Typically that means centering the Window relative to the component. In return for that tortured explanation, I had to agree to file an RFE about the Window.setLocationRelativeTo() javadoc. I haven't done so yet. But I will.

Thanks for listening.

Related Topics >> Java Desktop      
Comments
Comments are listed in date ascending order (oldest first)

Dialog Diatribe

Posted by hansmuller on October 27, 2006 at 5:48 PM PDT

I've been writing the occasional small application recently and now and then I blunder into a problem with Java SE that's, uh..., well, annoying. I realize that I'm not the only one who's had this experience and I'm probably not the only one who seeks relief by writing a lengthy diatribe and then sending it to whomever might be guilty of creating the situation. Of course, in my case that's often me, and since relief usually doesn't come from berating oneself, I'm guilty of sending the occasional long crabby missive to the people who are currently responsible for maintaining things that I'm probably responsible for bollocksing up in the first place. It's not a particularly endearing habit.

I sent the following to Swing's technical lead, Shannon Hickey, and he confirmed that the details, though twisted with bile, are essentially correct. So in the interest of furthering my own therapy, and also to ensure that some record of this will be stored away in Google's indices till the end of time, I thought I'd share.

I would think that a fairly common idiom in a Swing application would be to popup a dialog in response to selecting a menu item. Given Matisse, we'll assume that the JDialog has been created with the IDE, rather than some JOptionPane convenience method, and given rudimentary aesthetics, assume the dialog should be centered over the menu item's frame. Accomplishing this seems to be much too difficult:

public void showMyDialog(ActionEvent e) {
    // How to find the Dialog's Frame owner?
    Window dialogOwner = null;
    JDialog dialog = new MyDialog(dialogOwner, true); // true => modal Dialog
    dialog.pack();
    // How to center the Dialog?
    dailog.setVisible(true);
}

The first problem to deal with is mapping from the menu item's ActionEvent to the frame that contains the menu item. The frame will be the dialog's owner as well as the component we're going to center the dialog relative to.

There seems to be an overabundance of SwingUtilities methods that address this trivial problem:

  • Window getWindowAncestor(Component c)
  • Window windowForComponent(Component c)
  • Component getRoot(Component c)
Sadly, none of them "work" for a JMenuItem. They all simply traipse up the parent chain and in our case they find a JPopupMenu and then null.

To find the Frame that owns a JMenuItem, we have to follow the JPopupMenu's "invoker" property, which gets us back into the component hierarchy. So to find the frame that corresponds to an ActionEvent one must write (!):

Frame frameForActionEvent(ActionEvent e) {
    if (e.getSource() instanceof Component) {
        Component c = (Component)e.getSource();
        while(c != null) {
            if (c instanceof Frame) {
	        return (Frame)c;
            }
            c = (c instanceof JPopupMenu) ? ((JPopupMenu)c).getInvoker() : c.getParent();
        }
    }
    return null;
}

It would more useful to have written windowForActionEvent but sadly support for creating Dialogs whose owner is a Window (the parent class for Frames and Dialogs) only appeared in Java SE 6, and I needed code that worked for Java SE 5. It's also worth noting that this works for Applets too, although you'd be forgiven for not guessing that this is true. Applets do have a Frame parent that's created by the Java plugin and whose bounds are the same as the Applet (Panel) itself.

But we're still not done, because we must also center the dialog over the frame. Naturally there are other useful positions for the dialog. Centering a dialog over its frame happens to be what started me on this quest.

I have it on good authority that Windows.setLocationRelativeTo() is the handy method for this job. The javadoc for this method isn't promising:

Sets the location of the window relative to the specified component.

OK so far. Except it sounds like I'm going to have to compute the relative origin of my dialog and deal with edge (of the screen) conditions. Yech.

If the component is not currently showing, or c is null, the window is placed at the center of the screen. The center point can be determined with GraphicsEnvironment.getCenterPoint [sic]

Huh? What does "the component" refer to in this sentence? I assume they're not referring to this Window and I have to wonder what "showing" means in this context. Is is the same thing as getVisible() being true? If not, do I have to hope that my menu item is still "showing" when this method is called? And what's this advice about getCenterPoint (and where's the period)? In my case the Window and its menu item are visible, so I'm hoping none of this stuff applies. Because I don't really understand it.

If the bottom of the component is offscreen, the window is placed to the side of the Component that is closest to the center of the screen. So if the Component is on the right part of the screen, the Window is placed to its left, and visa versa.

This, no doubt, means that the method will endeavor to find a location for my dialog that respects the relative location I've specified, without making part of the dialog appear off-screen. Good, I think.

So I still appear to be stuck with computing an origin for my dialog that centers it relative to its owner. Before I code that, I try leaving the origin of the new dialog at 0,0, which is the default:

public void showMyDialog(ActionEvent e) {
    Window dialogOwner = frameForActionEvent(e);
    JDialog dialog = new MyDialog(dialogOwner, true);
    dialog.pack();
    aboutBox.setLocationRelativeTo(dialogOwner);
    dialog.setVisible(true);
}

Miraculously, this works. The dialog appears centered over the dialogOwner unless that would cause the dialog to appear off-screen. I have no idea why it works, since according to the "spec" (and the name of the method) I should have had to compute an appropriate relative origin for the dialog. But I guess I don't.

Frankly, I think this whole mess is a mini-travesty. If I'm going to show a dialog, I should be able to do so without writing code that digs around the component hierarchy and without experimentally determining what something as simple (and not terribly useful) as Window.setLocationRelativeTo() does.

There, that feels a little better.

A seven year old bug that covers the menu item to frame lookup problem is still open. Given the fact that it's accumulated exactly 0 votes in that time, perhaps no one has ever cared about the problem quite as much as I do at this moment. I would think that a cleaner way to handle this case would be some static methods that handled the entire idiom, for example:

public void showMyDialog(AWTEvent event) {
    Window dialogOwner = Window.eventToWindow(event);
    JDialog dialog = new MyDialog(dialogOwner, true);
    Window.showModalDialog(dialog); // Center dialog relative to its owner
}

And Shannon suggested that the method name might be rationalized as implying that the Window is be moved to a location that makes its relationship to the component parameter obvious. Typically that means centering the Window relative to the component. In return for that tortured explanation, I had to agree to file an RFE about the Window.setLocationRelativeTo() javadoc. I haven't done so yet. But I will.

Thanks for listening.

Related Topics >> Java Desktop      
Comments
Comments are listed in date ascending order (oldest first)