Ruben Laguna’s blog

Painting LabelWidgets on Top of the ConnectionWidget - Netbeans Visual Library

I was fiddling with the Netbeans Visual Library again, trying to show a complex graph on screen and I run into a problem: There were so many connection in the graph that some widgets were hard to read because the ConnectionWidget arrows were printed over them.

LabelWidget hidden under ConnectionWidget

So I tried to I try to rearrange the order of my LayerWidgets to paint the LabelWidgets on top of ConnectionWidgets. A call to mainLayer.bringToFront() should be enough, but this is what I got when I tried that:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<span class='line'>java.lang.IllegalStateException: Widget (org.netbeans.api.visual.widget.LabelWidget@10014f0) was not added into the scene
</span><span class='line'>        at org.netbeans.api.visual.anchor.Anchor.getRelatedSceneLocation(Anchor.java:213)
</span><span class='line'>        at org.netbeans.modules.visual.anchor.RectangularAnchor.compute(RectangularAnchor.java:111)
</span><span class='line'>        at org.netbeans.modules.visual.router.DirectRouter.routeConnection(DirectRouter.java:65)
</span><span class='line'>        at org.netbeans.api.visual.widget.ConnectionWidget.calculateRouting(ConnectionWidget.java:527)
</span><span class='line'>        at org.netbeans.modules.visual.layout.ConnectionWidgetLayout.layout(ConnectionWidgetLayout.java:109)
</span><span class='line'>        at org.netbeans.api.visual.widget.Widget.layout(Widget.java:1350)
</span><span class='line'>        at org.netbeans.api.visual.widget.Widget.layout(Widget.java:1342)
</span><span class='line'>        at org.netbeans.api.visual.widget.LayerWidget.layout(LayerWidget.java:86)
</span><span class='line'>        at org.netbeans.api.visual.widget.Widget.layout(Widget.java:1342)
</span><span class='line'>        at org.netbeans.api.visual.widget.Scene.layoutScene(Scene.java:312)
</span><span class='line'>        at org.netbeans.api.visual.widget.Scene.validate(Scene.java:393)
</span><span class='line'>        at org.netbeans.api.visual.widget.SceneComponent.addNotify(SceneComponent.java:92)
</span><span class='line'>        at java.awt.Container.addImpl(Container.java:1039)
</span><span class='line'>        at java.awt.Container.add(Container.java:896)
</span><span class='line'>        at com.rubenlaguna.modules.mainmodule.GraphTopComponent.setDotFile(GraphTopComponent.java:80)
</span><span class='line'>        at com.rubenlaguna.modules.mainmodule.DotOpenSupport.createCloneableTopComponent(DotOpenSupport.java:31)
</span><span class='line'>        at org.openide.windows.CloneableOpenSupport.openCloneableTopComponent(CloneableOpenSupport.java:197)
</span><span class='line'>        at org.openide.windows.CloneableOpenSupport$1.run(CloneableOpenSupport.java:98)
</span><span class='line'>        at org.openide.util.Mutex.doEvent(Mutex.java:1335)
</span><span class='line'>        at org.openide.util.Mutex.writeAccess(Mutex.java:452)
</span><span class='line'>        at org.openide.windows.CloneableOpenSupport.open(CloneableOpenSupport.java:95)
</span><span class='line'>        at org.openide.actions.OpenAction.performAction(OpenAction.java:81)
</span><span class='line'>        at org.openide.util.actions.NodeAction$DelegateAction$1.run(NodeAction.java:589)
</span><span class='line'>        at org.netbeans.modules.openide.util.ActionsBridge.doPerformAction(ActionsBridge.java:77)
</span><span class='line'>        at org.openide.util.actions.NodeAction$DelegateAction.actionPerformed(NodeAction.java:585)
</span><span class='line'>        at org.openide.explorer.view.TreeView$PopupSupport.mouseClicked(TreeView.java:1515)
</span><span class='line'>        at java.awt.AWTEventMulticaster.mouseClicked(AWTEventMulticaster.java:253)
</span><span class='line'>        at java.awt.AWTEventMulticaster.mouseClicked(AWTEventMulticaster.java:252)
</span><span class='line'>        at java.awt.AWTEventMulticaster.mouseClicked(AWTEventMulticaster.java:252)
</span><span class='line'>        at java.awt.AWTEventMulticaster.mouseClicked(AWTEventMulticaster.java:252)
</span><span class='line'>        at java.awt.Component.processMouseEvent(Component.java:6137)
</span><span class='line'>        at javax.swing.JComponent.processMouseEvent(JComponent.java:3265)
</span><span class='line'>        at java.awt.Component.processEvent(Component.java:5899)
</span><span class='line'>        at java.awt.Container.processEvent(Container.java:2023)
</span><span class='line'>        at java.awt.Component.dispatchEventImpl(Component.java:4501)
</span><span class='line'>        at java.awt.Container.dispatchEventImpl(Container.java:2081)
</span><span class='line'>        at java.awt.Component.dispatchEvent(Component.java:4331)
</span><span class='line'>        at java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4301)
</span><span class='line'>        at java.awt.LightweightDispatcher.processMouseEvent(Container.java:3974)
</span><span class='line'>        at java.awt.LightweightDispatcher.dispatchEvent(Container.java:3895)
</span><span class='line'>        at java.awt.Container.dispatchEventImpl(Container.java:2067)
</span><span class='line'>        at java.awt.Window.dispatchEventImpl(Window.java:2458)
</span><span class='line'>        at java.awt.Component.dispatchEvent(Component.java:4331)
</span><span class='line'>[catch] at java.awt.EventQueue.dispatchEvent(EventQueue.java:599)
</span><span class='line'>        at org.netbeans.core.TimableEventQueue.dispatchEvent(TimableEventQueue.java:104)
</span><span class='line'>        at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:269)
</span><span class='line'>        at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:184)
</span><span class='line'>        at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:174)
</span><span class='line'>        at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:169)
</span><span class='line'>        at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:161)
</span><span class='line'>        at java.awt.EventDispatchThread.run(EventDispatchThread.java:122)</span>

So I posted a question on netbeans graph mailing list but the suggestions there didn’t resolved my problem.

After digging a little bit into the Visual Library source code I come up with a really easy solution. Just call mainLayer.bringToFront() AFTER the scene was painted for the first time on the screen. I should have tried that first!. The result shows LabelWidgets painted overt the ConnectionWidgets so the text in the LabelWidgets is always readable.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<span class='line'>class GraphTopComponent extends CloneableTopComponent {
</span><span class='line'>//.... omitted ....
</span><span class='line'>  private showScene() {
</span><span class='line'>        JComponent c = scene.createView();
</span><span class='line'>        GraphLayout graphLayout = GraphLayoutFactory.createHierarchicalGraphLayout(scene, false);
</span><span class='line'>        ForceDirectedLayout forceDirectedGraphLayout = new ForceDirectedLayout(scene);
</span><span class='line'>        SceneLayout sceneGraphLayout = LayoutFactory.createSceneGraphLayout(scene, graphLayout);
</span><span class='line'>        sceneGraphLayout.invokeLayout();
</span><span class='line'>        this.forceDirectedSceneLayout = LayoutFactory.createSceneGraphLayout(scene, forceDirectedGraphLayout);
</span><span class='line'>        jPanel1.removeAll();
</span><span class='line'>        jPanel1.add(c);
</span><span class='line'>        scene.bringMainLayerToFront();
</span><span class='line'>  }
</span><span class='line'>}
</span><span class='line'>class DotGraphScene extends GraphScene.StringGraph {
</span><span class='line'>//... omitted ...
</span><span class='line'>    void bringMainLayerToFront() {
</span><span class='line'>        mainLayer.bringToFront();
</span><span class='line'>    }
</span><span class='line'>
</span><span class='line'>}</span>

LabelWidgets on top of ConnectionWidgets

Anchor will throw an IllegalStateException if the relatedWidget has no location in the scene. Those widget will not get a position until the LayerWidget that contains them is processed. So you keep the original order of the LayerWidgets until the scene is painted for the first time, by then all widgets have a location in the scene and then it’s posible to change the relative order of the LayerWidgets using LayerWidget.bringToFront().

The only problem with this approach is that is your are using a dynamic layout like me there is a chance that the layout changes the position of the LabelWidget by a noticiable amount and the Anchor will not be updated accordingly until the next iteration of the dynamic layout. Which lead to the artifact marked in red in the previous figure. This is only a real problem if your dynamic layout doesn’t stabilize or if is not very smooth.

Comments

Copyright © 2015 - Ruben Laguna - Powered by Octopress