| NodePortsDemo.java |
1 /****************************************************************************
2 **
3 ** This file is part of yFiles-2.9.
4 **
5 ** yWorks proprietary/confidential. Use is subject to license terms.
6 **
7 ** Redistribution of this file or of an unauthorized byte-code version
8 ** of this file is strictly forbidden.
9 **
10 ** Copyright (c) 2000-2011 by yWorks GmbH, Vor dem Kreuzberg 28,
11 ** 72070 Tuebingen, Germany. All rights reserved.
12 **
13 ***************************************************************************/
14 package demo.view.advanced.ports;
15
16 import demo.view.hierarchy.GroupingDemo;
17 import y.base.Edge;
18 import y.base.Node;
19 import y.geom.YRectangle;
20 import y.layout.LayoutOrientation;
21 import y.layout.Layouter;
22 import y.layout.hierarchic.IncrementalHierarchicLayouter;
23 import y.layout.hierarchic.incremental.SimplexNodePlacer;
24 import y.layout.router.EdgeGroupRouterStage;
25 import y.layout.router.GroupNodeRouterStage;
26 import y.layout.router.OrthogonalEdgeRouter;
27 import y.layout.router.PatchRouterStage;
28 import y.view.EdgeRealizer;
29 import y.view.EditMode;
30 import y.view.GenericNodeRealizer;
31 import y.view.Graph2D;
32 import y.view.Graph2DClipboard;
33 import y.view.Graph2DLayoutExecutor;
34 import y.view.Graph2DUndoManager;
35 import y.view.Graph2DViewActions;
36 import y.view.NodeLabel;
37 import y.view.NodePort;
38 import y.view.NodePortLayoutConfigurator;
39 import y.view.NodeRealizer;
40 import y.view.NodeStateChangeEdgeRouter;
41 import y.view.ProxyShapeNodeRealizer;
42 import y.view.ShapeNodePainter;
43 import y.view.TooltipMode;
44 import y.view.hierarchy.DefaultGenericAutoBoundsFeature;
45 import y.view.hierarchy.DefaultHierarchyGraphFactory;
46 import y.view.hierarchy.GenericGroupNodeRealizer;
47 import y.view.hierarchy.GroupNodePainter;
48 import y.view.hierarchy.HierarchyManager;
49
50 import java.awt.BorderLayout;
51 import java.awt.Color;
52 import java.awt.EventQueue;
53 import java.awt.Graphics2D;
54 import java.awt.Shape;
55 import java.awt.event.ActionEvent;
56 import java.awt.event.InputEvent;
57 import java.awt.event.KeyEvent;
58 import java.awt.geom.Rectangle2D;
59 import java.util.Locale;
60 import java.util.Map;
61 import java.util.WeakHashMap;
62 import javax.swing.AbstractAction;
63 import javax.swing.Action;
64 import javax.swing.ActionMap;
65 import javax.swing.BorderFactory;
66 import javax.swing.InputMap;
67 import javax.swing.JSplitPane;
68 import javax.swing.JToggleButton;
69 import javax.swing.JToolBar;
70 import javax.swing.KeyStroke;
71
72 /**
73 * Demonstrates how to use {@link y.view.NodePort}s.
74 * <p>
75 * Things to try:
76 * </p>
77 * <ul>
78 * <li>Left-click on a port to select it.</li>
79 * <li>Drag a selected port to move it around.</li>
80 * <li>Press DELETE to remove all selected ports as well as all edges
81 * connecting to selected ports and all labels associated top selected
82 * ports.</li>
83 * <li>Drag a port that is not selected to start creating an edge from that
84 * port.</li>
85 * <li>Right-click on a node to display a context menu that allows for
86 * adding additional ports to a node.</li>
87 * <li>Right-click on a port to display a context menu that allows for
88 * <ul>
89 * <li>... adding a label that is associated to the port.</li>
90 * <li>... changing the valid positions of the port.</li>
91 * <li>... removing the port.</li>
92 * </ul>
93 * </li>
94 * <li>Select one or more ports then press CONTROL+A to select all ports.</li>
95 * <li>Select one or more ports then use the selection box (by dragging from
96 * an empty point) to select additional ports.</li>
97 * <li>Select one or more nodes then press CONTROL+ALT+G to create a common
98 * parent group node for the selected nodes.
99 * </ul>
100 * <p>
101 * Class {@link PortConfigurations} demonstrate how to customize the visual
102 * appearance of ports by re-using existing visualizations for nodes.
103 * <p>
104 * Nested classes {@link NormalEdgeProcessor} and {@link InterEdgeProcessor}
105 * demonstrate how to update edge-to-port associations when a group node is
106 * closed or a folder node is opened.
107 * </p>
108 *
109 * @see <a href="http://docs.yworks.com/yfiles/doc/developers-guide/realizer_related.html">Section Realizer-Related Features</a> in the yFiles for Java Developer's Guide
110 */
111 public class NodePortsDemo extends GroupingDemo {
112 private Graph2DUndoManager undoManager;
113
114 private boolean fixPortsForLayout;
115 private boolean groupEdges;
116
117 public NodePortsDemo() {
118 this(null);
119 }
120
121 public NodePortsDemo( final String helpFilePath ) {
122 final JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, new Palette(view), view);
123 splitPane.setBorder(BorderFactory.createEmptyBorder());
124 contentPane.add(splitPane, BorderLayout.CENTER);
125 addHelpPane(helpFilePath);
126 }
127
128 /**
129 * Overwritten to initialize the demo's undo engine.
130 */
131 protected void initialize() {
132 super.initialize();
133
134 new HierarchyManager(view.getGraph2D());
135
136 undoManager = new Graph2DUndoManager(view.getGraph2D());
137 undoManager.setViewContainer(view);
138 }
139
140 /**
141 * Overwritten to add copy/cut/paste key bindings as well as
142 * {@link y.view.NodePort} aware actions to handle closing group nodes and
143 * opening folder nodes.
144 */
145 protected void registerViewActions() {
146 super.registerViewActions();
147
148 final ActionMap amap = view.getCanvasComponent().getActionMap();
149 final InputMap imap = view.getCanvasComponent().getInputMap();
150
151 final Graph2DClipboard clipboard = new Graph2DClipboard(view);
152 amap.put("CUT", clipboard.getCutAction());
153 imap.put(KeyStroke.getKeyStroke(KeyEvent.VK_X, InputEvent.CTRL_MASK),"CUT");
154
155 amap.put("COPY", clipboard.getCopyAction());
156 imap.put(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_MASK), "COPY");
157
158 amap.put("PASTE", clipboard.getPasteAction());
159 imap.put(KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_MASK), "PASTE");
160
161
162 final Graph2DViewActions.CloseGroupsAction closeGroups =
163 new Graph2DViewActions.CloseGroupsAction(view);
164 // add callback implementations that ensure edges connected to ports are
165 // correctly reassigned when proxy realizers are used
166 // additionally, InterEdgeProcessor will assign all inter edges to the
167 // corresponding folder node's first port (if there is one)
168 // see class documentation
169 closeGroups.setNodeStateChangeHandler(new MyNodeStateChangeEdgeRouter(
170 new InterEdgeProcessor(new NormalEdgeProcessor())));
171 amap.put(Graph2DViewActions.CLOSE_GROUPS, closeGroups);
172 final Graph2DViewActions.OpenFoldersAction openFolders =
173 new Graph2DViewActions.OpenFoldersAction(view);
174 // add callback implementations that ensure edges connected to ports are
175 // correctly reassigned when proxy realizers are used
176 // see class documentation
177 openFolders.setNodeStateChangeHandler(new MyNodeStateChangeEdgeRouter(
178 new NormalEdgeProcessor()));
179 amap.put(Graph2DViewActions.OPEN_FOLDERS, openFolders);
180 }
181
182 /**
183 * Overwritten to create a {@link demo.view.advanced.ports.PortEditMode}
184 * instance that provides {@link y.view.NodePort} support.
185 * @return a {@link demo.view.advanced.ports.PortEditMode} instance.
186 */
187 protected EditMode createEditMode() {
188 return new PortEditMode();
189 }
190
191 /**
192 * Overwritten to enable tooltips only for node ports.
193 */
194 protected TooltipMode createTooltipMode() {
195 TooltipMode tooltipMode = new TooltipMode();
196 tooltipMode.setNodeTipEnabled(false);
197 tooltipMode.setEdgeTipEnabled(false);
198 tooltipMode.setNodeTipEnabled(true);
199 return tooltipMode;
200 }
201
202 /**
203 * Overwritten to add controls for undo/redo.
204 * @return the application tool bar.
205 */
206 protected JToolBar createToolBar() {
207 final Action undoAction = undoManager.getUndoAction();
208 undoAction.putValue(Action.SMALL_ICON, getIconResource("resource/undo.png"));
209 undoAction.putValue(Action.SHORT_DESCRIPTION, "Undo");
210
211 final Action redoAction = undoManager.getRedoAction();
212 redoAction.putValue(Action.SMALL_ICON, getIconResource("resource/redo.png"));
213 redoAction.putValue(Action.SHORT_DESCRIPTION, "Redo");
214
215 final Action edgeRoutingAction = new AbstractAction("Route Edges") {
216 public void actionPerformed(final ActionEvent e) {
217 routeOrthogonally();
218 }
219 };
220 edgeRoutingAction.putValue(Action.SMALL_ICON, SHARED_LAYOUT_ICON);
221
222 final Action layoutAction = new AbstractAction("Layout") {
223 public void actionPerformed(final ActionEvent e) {
224 layoutHierarchically();
225 }
226 };
227 layoutAction.putValue(Action.SMALL_ICON, SHARED_LAYOUT_ICON);
228
229 final Action toggleFixedPortsAction = new AbstractAction("Fixed Ports") {
230 public void actionPerformed(final ActionEvent e) {
231 fixPortsForLayout = !fixPortsForLayout;
232 }
233 };
234 toggleFixedPortsAction.putValue(Action.SHORT_DESCRIPTION,
235 "Toggle the 'Fixed Ports' feature of the layout algorithms");
236
237 final Action toggleGroupEdgesAction = new AbstractAction("Edge Grouping") {
238 public void actionPerformed(final ActionEvent e) {
239 groupEdges = !groupEdges;
240 }
241 };
242 toggleGroupEdgesAction.putValue(Action.SHORT_DESCRIPTION,
243 "Toggle the 'Automatic Edge Grouping' feature of the layout algorithms");
244
245 final JToolBar jtb = super.createToolBar();
246 jtb.addSeparator();
247 jtb.add(undoAction);
248 jtb.add(redoAction);
249
250 jtb.addSeparator();
251 jtb.add(createActionControl(layoutAction));
252 jtb.add(createActionControl(edgeRoutingAction));
253 jtb.addSeparator(TOOLBAR_SMALL_SEPARATOR);
254 jtb.add(new JToggleButton(toggleFixedPortsAction));
255 jtb.addSeparator(TOOLBAR_SMALL_SEPARATOR);
256 jtb.add(new JToggleButton(toggleGroupEdgesAction));
257
258 return jtb;
259 }
260
261 /**
262 * Overwritten to prevent the {@link y.view.hierarchy.HierarchyManager}
263 * instance that is created in {@link #initialize()} from being replaced.
264 * @param rootGraph the graph for which a
265 * {@link y.view.hierarchy.HierarchyManager} has to be created.
266 * @return the {@link y.view.hierarchy.HierarchyManager} for the specified
267 * graph.
268 */
269 protected HierarchyManager createHierarchyManager( final Graph2D rootGraph ) {
270 return rootGraph.getHierarchyManager();
271 }
272
273 protected void loadInitialGraph() {
274 // ensure that port configurations are already registered
275 PortConfigurations.INSTANCE.getClass();
276
277 loadGraph("resource/NodePortsDemo.graphml");
278
279 undoManager.resetQueue();
280 }
281
282 /**
283 * Overwritten to change the default group and folder node representations.
284 */
285 protected void configureDefaultGroupNodeRealizers() {
286 //Create additional configuration for default group node realizers
287 Map map = GenericGroupNodeRealizer.createDefaultConfigurationMap();
288
289 GroupNodePainter gnp = new GroupNodePainter(new GroupShapeNodePainter());
290 map.put(GenericNodeRealizer.Painter.class, gnp);
291 map.put(GenericNodeRealizer.ContainsTest.class, gnp);
292 map.put(GenericNodeRealizer.GenericMouseInputEditorProvider.class, gnp);
293 map.put(GenericNodeRealizer.Initializer.class, gnp);
294
295 DefaultGenericAutoBoundsFeature abf = new DefaultGenericAutoBoundsFeature();
296 abf.setConsiderNodeLabelSize(true);
297 map.put(GenericGroupNodeRealizer.GenericAutoBoundsFeature.class, abf);
298 map.put(GenericNodeRealizer.GenericSizeConstraintProvider.class, abf);
299 map.put(GenericNodeRealizer.LabelBoundsChangedHandler.class, abf);
300
301 GenericNodeRealizer.Factory factory = GenericNodeRealizer.getFactory();
302 factory.addConfiguration(CONFIGURATION_GROUP, map);
303
304
305 GenericGroupNodeRealizer gnr = new GenericGroupNodeRealizer();
306
307 //Register first, since this will also configure the node label
308 gnr.setConfiguration(CONFIGURATION_GROUP);
309
310 //Nicer colors
311 gnr.setFillColor(new Color(202,236,255,132));
312 gnr.setLineColor(new Color(102, 102,153,255));
313 NodeLabel label = gnr.getLabel();
314 label.setBackgroundColor(null);
315 label.setTextColor(Color.BLACK);
316 label.setFontSize(15);
317
318
319 //Set default group and folder node realizers
320 DefaultHierarchyGraphFactory hgf = (DefaultHierarchyGraphFactory)
321 getHierarchyManager().getGraphFactory();
322
323 hgf.setProxyNodeRealizerEnabled(true);
324
325 hgf.setDefaultGroupNodeRealizer(gnr.createCopy());
326 hgf.setDefaultFolderNodeRealizer(gnr.createCopy());
327 }
328
329
330
331 private void layoutHierarchically() {
332 final SimplexNodePlacer placer = new SimplexNodePlacer();
333 placer.setBaryCenterModeEnabled(true);
334
335 final IncrementalHierarchicLayouter layouter =
336 new IncrementalHierarchicLayouter();
337 layouter.setLayoutOrientation(LayoutOrientation.LEFT_TO_RIGHT);
338 layouter.setNodePlacer(placer);
339 layouter.getEdgeLayoutDescriptor().setMinimumFirstSegmentLength(20);
340 layouter.getEdgeLayoutDescriptor().setMinimumLastSegmentLength(40);
341 layouter.setMinimumLayerDistance(60);
342
343 doLayout(layouter);
344 }
345
346 private void routeOrthogonally() {
347 doLayout(
348 new EdgeGroupRouterStage(
349 new GroupNodeRouterStage(
350 new PatchRouterStage(
351 new OrthogonalEdgeRouter()))));
352 }
353
354 private void doLayout( final Layouter layouter ) {
355 final Graph2DLayoutExecutor executor = new Graph2DLayoutExecutor();
356
357 final NodePortLayoutConfigurator configurator =
358 executor.getNodePortConfigurator();
359 configurator.setAutomaticPortConstraintsEnabled(fixPortsForLayout);
360 configurator.setAutomaticEdgeGroupsEnabled(groupEdges);
361
362 executor.doLayout(view, layouter);
363 }
364
365
366 public static void main( String[] args ) {
367 EventQueue.invokeLater(new Runnable() {
368 public void run() {
369 Locale.setDefault(Locale.ENGLISH);
370 initLnF();
371 (new NodePortsDemo("resource/nodeportshelp.html")).start();
372 }
373 });
374 }
375
376
377 /**
378 * Specifies the contract of a post-processor for
379 * {@link demo.view.advanced.ports.NodePortsDemo.MyNodeStateChangeEdgeRouter}.
380 */
381 private static interface Processor {
382 /**
383 * Post-processing for
384 * {@link demo.view.advanced.ports.NodePortsDemo.MyNodeStateChangeEdgeRouter#preEdgeStateChange(y.base.Edge, y.base.Node)}.
385 * @param edge the edge whose state is about to change.
386 * @param groupNode the node whose state change will trigger the edge state
387 * change.
388 */
389 public void preEdgeStateChange( Edge edge, Node groupNode );
390
391 /**
392 * Post-processing for
393 * {@link demo.view.advanced.ports.NodePortsDemo.MyNodeStateChangeEdgeRouter#postEdgeStateChange(y.base.Edge, y.base.Node)}.
394 * @param edge the edge whose state has changed.
395 * @param groupNode the node whose state change has triggered the edge state
396 * change.
397 */
398 public void postEdgeStateChange( Edge edge, Node groupNode );
399
400 /**
401 * Post-processing for
402 * {@link demo.view.advanced.ports.NodePortsDemo.MyNodeStateChangeEdgeRouter#postNodeStateChange(y.base.Node)}.
403 * @param groupNode the node whose state has changed.
404 */
405 public void postNodeStateChange( Node groupNode );
406 }
407
408 /**
409 * Simple {@link y.view.NodeStateChangeEdgeRouter} implementation that allows
410 * for arbitrary post-processing in
411 * {@link #preEdgeStateChange(y.base.Edge, y.base.Node)},
412 * {@link #postEdgeStateChange(y.base.Edge, y.base.Node)}, and
413 * {@link #postNodeStateChange(y.base.Node)}.
414 */
415 private static final class MyNodeStateChangeEdgeRouter
416 extends NodeStateChangeEdgeRouter {
417 private final Processor impl;
418
419 MyNodeStateChangeEdgeRouter( final Processor impl ) {
420 this.impl = impl;
421 }
422
423 /**
424 * Overwritten to allow for post-processing.
425 * @param edge the edge whose state is about to change.
426 * @param groupNode the node whose state change will trigger the edge state
427 * change.
428 */
429 protected void preEdgeStateChange( final Edge edge, final Node groupNode ) {
430 if (impl != null) {
431 impl.preEdgeStateChange(edge, groupNode);
432 }
433
434 super.preEdgeStateChange(edge, groupNode);
435 }
436
437 /**
438 * Overwritten to allow for post-processing.
439 * @param edge the edge whose state has changed.
440 * @param groupNode the node whose state change has triggered the edge state
441 * change.
442 */
443 protected void postEdgeStateChange( final Edge edge, final Node groupNode ) {
444 super.postEdgeStateChange(edge, groupNode);
445
446 if (impl != null) {
447 impl.postEdgeStateChange(edge, groupNode);
448 }
449 }
450
451 /**
452 * Overwritten to allow for post-processing.
453 * @param groupNode the node whose state has changed.
454 */
455 public void postNodeStateChange( final Node groupNode ) {
456 super.postNodeStateChange(groupNode);
457
458 if (impl != null) {
459 impl.postNodeStateChange(groupNode);
460 }
461 }
462 }
463
464 /**
465 * Reassigns node ports of normal edges from one realizer delegate to the
466 * other at nodes that use {@link y.view.ProxyShapeNodeRealizer} upon node
467 * state changes.
468 * By default, edges connecting to the <code>i</code>-th port of one realizer
469 * delegate are assigned to the <code>i</code>-th port of the other realizer
470 * delegate (if it exists).
471 * More sophisticated strategies can be realized by customizing method
472 * {@link #remapPort(y.view.NodeRealizer, y.view.EdgeRealizer, y.view.NodePort, boolean)}.
473 */
474 private static final class NormalEdgeProcessor implements Processor {
475 private final Map node2edgeState;
476
477 NormalEdgeProcessor() {
478 node2edgeState = new WeakHashMap();
479 }
480
481
482 /**
483 * Stores the current port information of the specified edge.
484 * @param edge the edge whose state is about to change.
485 * @param groupNode the node whose state change will trigger the edge state
486 */
487 public void preEdgeStateChange( final Edge edge, final Node groupNode ) {
488 if (groupNode.getGraph() instanceof Graph2D) {
489 final Graph2D graph = (Graph2D) groupNode.getGraph();
490 final HierarchyManager hm = graph.getHierarchyManager();
491 if (acceptEdge(hm, edge, groupNode)) {
492 Map edge2state = (Map) node2edgeState.get(groupNode);
493 if (edge2state == null) {
494 edge2state = new WeakHashMap();
495 node2edgeState.put(groupNode, edge2state);
496 }
497
498 if (edge.source() == groupNode) {
499 final NodePort port = NodePort.getSourcePort(graph.getRealizer(edge));
500 if (port != null &&
501 matches(port.getRealizer(), graph.getRealizer(groupNode))) {
502 EdgeState state = (EdgeState) edge2state.get(edge);
503 if (state == null) {
504 state = new EdgeState();
505 edge2state.put(edge, state);
506 }
507 state.sourcePort = port;
508 }
509 }
510 if (edge.target() == groupNode) {
511 final NodePort port = NodePort.getTargetPort(graph.getRealizer(edge));
512 if (port != null &&
513 matches(port.getRealizer(), graph.getRealizer(groupNode))) {
514 EdgeState state = (EdgeState) edge2state.get(edge);
515 if (state == null) {
516 state = new EdgeState();
517 edge2state.put(edge, state);
518 }
519 state.targetPort = port;
520 }
521 }
522 }
523 }
524 }
525
526 /**
527 * Assigns an appropriate port to the specified edge.
528 * Calls
529 * {@link #remapPort(y.view.NodeRealizer, y.view.EdgeRealizer, y.view.NodePort, boolean)}
530 * to determine which port is appropriate.
531 * @param edge the edge whose state has changed.
532 * @param groupNode the node whose state change has triggered the edge state
533 */
534 public void postEdgeStateChange( final Edge edge, final Node groupNode ) {
535 if (groupNode.getGraph() instanceof Graph2D) {
536 final Graph2D graph = (Graph2D) groupNode.getGraph();
537 final HierarchyManager hm = graph.getHierarchyManager();
538 if (acceptEdge(hm, edge, groupNode)) {
539 final Map edge2state = (Map) node2edgeState.get(groupNode);
540 final EdgeState state = (EdgeState) edge2state.get(edge);
541 if (state != null) {
542 if (edge.source() == groupNode) {
543 remapPort(
544 graph.getRealizer(groupNode), graph.getRealizer(edge),
545 state.sourcePort,
546 true);
547 }
548 if (edge.target() == groupNode) {
549 remapPort(
550 graph.getRealizer(groupNode), graph.getRealizer(edge),
551 state.targetPort,
552 false);
553 }
554 }
555 }
556 }
557 }
558
559 /**
560 * Removes the no longer needed stored port information for all edges
561 * related to the specified node.
562 * @param groupNode the node whose state has changed.
563 */
564 public void postNodeStateChange( final Node groupNode ) {
565 node2edgeState.remove(groupNode);
566 }
567
568
569 /**
570 * Remaps the specified edge realizer's port to one that belongs to the
571 * specified node realizer.
572 * @param nr the realizer that provides possible new ports.
573 * @param er the realizer whose port has to be remapped.
574 * @param port the old port.
575 * @param source if <code>true</code> the source port has to be remapped;
576 * otherwise the target port has to be remapped.
577 */
578 void remapPort(
579 final NodeRealizer nr,
580 final EdgeRealizer er,
581 final NodePort port,
582 final boolean source
583 ) {
584 if (port != null) {
585 final NodeRealizer oldNr = port.getRealizer();
586 if (!matches(oldNr, nr) &&
587 oldNr != null &&
588 oldNr.portCount() == nr.portCount()) {
589 bindPort(nr.getPort(indexOf(port)), er, source);
590 }
591 }
592 }
593
594
595 /**
596 * Binds the specified port to the specified edge.
597 * @param port the port to bind to the specified edge.
598 * @param er the realizer representing the edge.
599 * @param source if <code>true</code> the source port has to be bound;
600 * otherwise the target port has to be bound.
601 */
602 private static void bindPort(
603 final NodePort port,
604 final EdgeRealizer er,
605 final boolean source
606 ) {
607 if (source) {
608 NodePort.bindSourcePort(port, er);
609 } else {
610 NodePort.bindTargetPort(port, er);
611 }
612 }
613
614 /**
615 * Returns <code>true</code> if the specified edge's port assignment has to
616 * be remapped and <code>false</code> otherwise.
617 * @param hm the nesting structure of the specified edge's graph.
618 * @param edge the edge to check.
619 * @param groupNode the node whose state changes.
620 * @return <code>true</code> if the specified edge's port assignment has to
621 * be remapped and <code>false</code> otherwise.
622 */
623 private static boolean acceptEdge(
624 final HierarchyManager hm,
625 final Edge edge,
626 final Node groupNode
627 ) {
628 if (hm != null && hm.isFolderNode(groupNode) && hm.isInterEdge(edge)) {
629 return hm.getRealSource(edge) == groupNode ||
630 hm.getRealTarget(edge) == groupNode;
631 } else {
632 return edge.source() == groupNode || edge.target() == groupNode;
633 }
634 }
635
636 /**
637 * Returns the zero-based index of the specified port within its associated
638 * node realizer's collection of ports.
639 * @param port the port whose index is to be determined.
640 * @return the zero-based index of the specified port within its associated
641 * node realizer's collection of ports or <code>-1>/code> if the port
642 * currently is not associated to any node realizer.
643 */
644 private static int indexOf( final NodePort port ) {
645 final NodeRealizer owner = port.getRealizer();
646 if (owner != null) {
647 for (int i = 0, n = owner.portCount(); i < n; ++i) {
648 if (owner.getPort(i) == port) {
649 return i;
650 }
651 }
652 }
653 return -1;
654 }
655
656 /**
657 * Returns <code>true</code> if the first realizer equals the second or the
658 * second realizer's delegate and <code>false</code> otherwise.
659 * @param portNr a realizer associated to a {@link y.view.NodePort}.
660 * @param otherNr another realizer, possibly a
661 * {@link y.view.ProxyShapeNodeRealizer}.
662 * @return <code>true</code> if the first realizer equals the second or the
663 * second realizer's delegate and <code>false</code> otherwise.
664 */
665 private static boolean matches(
666 final NodeRealizer portNr,
667 final NodeRealizer otherNr
668 ) {
669 return portNr == otherNr ||
670 (otherNr instanceof ProxyShapeNodeRealizer &&
671 portNr == ((ProxyShapeNodeRealizer) otherNr).getRealizerDelegate());
672 }
673
674
675 /**
676 * Stores port information for an edge.
677 */
678 private static final class EdgeState {
679 NodePort sourcePort;
680 NodePort targetPort;
681 }
682 }
683
684 /**
685 * Reassigns node ports of edges that are converted to inter edges.
686 * By default, edges are automatically assigned to the first port of the
687 * corresponding folder node (if the node has any ports at all).
688 * More sophisticated strategies can be realized by customizing method
689 * {@link #remapPort(y.view.NodeRealizer, y.view.EdgeRealizer, boolean)}.
690 */
691 private static final class InterEdgeProcessor implements Processor {
692 private final Processor impl;
693
694 InterEdgeProcessor( final Processor impl ) {
695 this.impl = impl;
696 }
697
698 public void preEdgeStateChange( final Edge edge, final Node groupNode ) {
699 if (impl != null) {
700 impl.preEdgeStateChange(edge, groupNode);
701 }
702 }
703
704 /**
705 * Assigns an appropriate port to the specified edge.
706 * Calls
707 * {@link #remapPort(y.view.NodeRealizer, y.view.EdgeRealizer, boolean)}
708 * to determine which port is appropriate.
709 * @param edge the edge whose state has changed.
710 * @param groupNode the node whose state change has triggered the edge state
711 */
712 public void postEdgeStateChange( final Edge edge, final Node groupNode ) {
713 if (impl != null) {
714 impl.postEdgeStateChange(edge, groupNode);
715 }
716
717 if (groupNode.getGraph() instanceof Graph2D) {
718 final Graph2D graph = (Graph2D) groupNode.getGraph();
719 final HierarchyManager hm = graph.getHierarchyManager();
720 if (hm != null && hm.isFolderNode(groupNode) && hm.isInterEdge(edge)) {
721 if (edge.source() == groupNode && hm.getRealSource(edge) != groupNode) {
722 remapPort(
723 graph.getRealizer(groupNode),
724 graph.getRealizer(edge),
725 true);
726 }
727 if (edge.target() == groupNode && hm.getRealTarget(edge) != groupNode) {
728 remapPort(
729 graph.getRealizer(groupNode),
730 graph.getRealizer(edge),
731 false);
732 }
733 }
734 }
735 }
736
737 public void postNodeStateChange( final Node groupNode ) {
738 if (impl != null) {
739 impl.postNodeStateChange(groupNode);
740 }
741 }
742
743 /**
744 * Remaps the specified edge realizer's port to the first port of the
745 * specified node realizer.
746 * @param nr the realizer that provides possible new ports.
747 * @param er the realizer whose port has to be remapped.
748 * @param source if <code>true</code> the source port has to be remapped;
749 * otherwise the target port has to be remapped.
750 */
751 protected void remapPort(
752 final NodeRealizer nr,
753 final EdgeRealizer er,
754 final boolean source
755 ) {
756 if (nr.portCount() > 0) {
757 bindPort(nr.getPort(0), er, source);
758 }
759 }
760
761
762 /**
763 * Binds the specified port to the specified edge.
764 * @param port the port to bind to the specified edge.
765 * @param er the realizer representing the edge.
766 * @param source if <code>true</code> the source port has to be bound;
767 * otherwise the target port has to be bound.
768 */
769 private static void bindPort(
770 final NodePort port,
771 final EdgeRealizer er,
772 final boolean source
773 ) {
774 if (source) {
775 NodePort.bindSourcePort(port, er);
776 } else {
777 NodePort.bindTargetPort(port, er);
778 }
779 }
780 }
781
782 /**
783 * Painter implementation for group nodes that draws a special header
784 * compartment below the groups default label.
785 * In the default group node configuration, this compartment is drawn by
786 * the default label. This means said compartment will be painted over node
787 * ports because node labels are rendered after node ports.
788 */
789 private static final class GroupShapeNodePainter extends ShapeNodePainter {
790 private final static Color BACKGROUND = new Color(153, 204, 255, 255);
791
792 GroupShapeNodePainter() {
793 super(ROUND_RECT);
794 }
795
796 protected void paintFilledShape(
797 final NodeRealizer context,
798 final Graphics2D graphics,
799 final Shape shape
800 ) {
801 super.paintFilledShape(context, graphics, shape);
802
803 if (context.labelCount() > 0) {
804 final Shape oldClip = graphics.getClip();
805 final Color oldColor = graphics.getColor();
806
807 final Rectangle2D cb = oldClip.getBounds2D();
808 final YRectangle r = context.getLabel().getBox();
809 graphics.clip(new Rectangle2D.Double(cb.getX(), r.getY(), cb.getWidth(), r.getHeight()));
810 graphics.setColor(BACKGROUND);
811 graphics.fill(shape);
812
813 graphics.setColor(oldColor);
814 graphics.setClip(oldClip);
815 }
816 }
817 }
818 }
819