1
28 package demo.view.orgchart;
29
30 import demo.view.DemoBase;
31 import demo.view.orgchart.ViewModeFactory.JTreeChartMoveSelectionMode;
32 import demo.view.orgchart.ViewModeFactory.JTreeChartEditMode;
33 import y.algo.GraphConnectivity;
34 import y.algo.Paths;
35 import y.algo.Trees;
36 import y.base.DataMap;
37 import y.base.Edge;
38 import y.base.EdgeCursor;
39 import y.base.EdgeList;
40 import y.base.Node;
41 import y.base.NodeCursor;
42 import y.base.NodeList;
43 import y.base.NodeMap;
44 import y.geom.Geom;
45 import y.layout.NormalizingGraphElementOrderStage;
46 import y.util.Maps;
47 import y.view.AbstractMouseInputEditor;
48 import y.view.Drawable;
49 import y.view.GenericNodeRealizer;
50 import y.view.Graph2D;
51 import y.view.Graph2DView;
52 import y.view.HitInfo;
53 import y.view.Mouse2DEvent;
54 import y.view.MouseInputEditor;
55 import y.view.MouseInputEditorProvider;
56 import y.view.NodeRealizer;
57 import y.view.ViewMode;
58 import y.view.hierarchy.HierarchyManager;
59
60 import javax.swing.Icon;
61 import javax.swing.ImageIcon;
62 import java.awt.Graphics2D;
63 import java.awt.Rectangle;
64 import java.util.ArrayList;
65 import java.util.Iterator;
66 import java.util.List;
67
68
77 public class HoverButton implements MouseInputEditorProvider, Drawable {
78 static final int VERTICAL_CONTROL_OFFSET = 25;
79
80
81 private Node node;
82 private final List buttons;
83 private final OrgChartTreeModel treeModel;
84 private final JTreeChart treeChart;
85 private final NodeMap hiddenNodesChild;
86 private final NodeMap hiddenEdgesChild;
87 private final NodeMap hiddenNodesParent;
88 private final NodeMap hiddenEdgesParent;
89
90 public HoverButton( final JTreeChart treeChart ) {
91 this.treeChart = treeChart;
92 this.treeModel = (OrgChartTreeModel) treeChart.getModel();
93 buttons = new ArrayList();
94 hiddenNodesChild = Maps.createHashedNodeMap();
95 hiddenEdgesChild = Maps.createHashedNodeMap();
96 hiddenNodesParent = Maps.createHashedNodeMap();
97 hiddenEdgesParent = Maps.createHashedNodeMap();
98 buttons.add(new MoveButton("move.png", "move_disabled.png", 0, JTreeChartMoveSelectionMode.MOVE_MODE_SINGLE));
99 buttons.add(new MoveButton("move_plus.png", "move_plus_disabled.png", 1, JTreeChartMoveSelectionMode.MOVE_MODE_ASSISTANT));
100 buttons.add(new MoveButton("move_star.png", "move_star_disabled.png", 2, JTreeChartMoveSelectionMode.MOVE_MODE_ALL));
101 buttons.add(new ExpandButton("down.png", "down_disabled.png", -1, false));
102 buttons.add(new CollapseButton("up.png", "up_disabled.png", -1, false));
103 buttons.add(new AddButton("plus.png", null, 3));
104 buttons.add(new DeleteButton("trash.png", "trash_disabled.png", 4));
105 buttons.add(new ExpandButton("down.png", "down_disabled.png", 5, true));
106 buttons.add(new CollapseButton("up.png", "up_disabled.png", 5, true));
107 treeChart.addDrawable(this);
108 }
109
110 public MouseInputEditor findMouseInputEditor(final Graph2DView view, final double x, final double y, final HitInfo hitInfo) {
111 if (isVisible()) {
112 for (final Iterator iterator = buttons.iterator(); iterator.hasNext(); ) {
113 final NodeButton nb = (NodeButton) iterator.next();
114 if (nb.getBounds().contains(x, y)) {
115 return nb;
116 }
117 }
118 }
119 return null;
120 }
121
122 private boolean isVisible() {
123 return node != null;
124 }
125
126 public void paint(final Graphics2D g) {
127 if (isVisible()) {
128 if (node.getGraph() == null) {
129 node = null;
131 } else {
132 for (final Iterator iterator = buttons.iterator(); iterator.hasNext(); ) {
133 final NodeButton nb = (NodeButton) iterator.next();
134 nb.paint(g);
135 }
136 }
137 }
138 }
139
140 public Rectangle getBounds() {
141 final Rectangle r = new Rectangle(-1, -1, -1, -1);
142 if (isVisible()) {
143 for (final Iterator iterator = buttons.iterator(); iterator.hasNext(); ) {
144 final NodeButton nb = (NodeButton) iterator.next();
145 Geom.calcUnion(r, nb.getBounds(), r);
146 }
147 }
148 return r;
149 }
150
151
157 public String getToolTipText( final double x, final double y ) {
158 if (isVisible()) {
159 for (Iterator it = buttons.iterator(); it.hasNext(); ) {
160 final NodeButton nb = (NodeButton) it.next();
161 if (nb.contains(x, y)) {
162 return nb.getToolTipText();
163 }
164 }
165 }
166 return null;
167 }
168
169
173 public void setNode(final Node node) {
174 if (node == null) {
175 this.node = node;
176 } else {
177 final HierarchyManager hm = treeChart.getGraph2D().getHierarchyManager();
178 if (hm.isNormalNode(node)) {
179 this.node = node;
180 } else {
181 this.node = null;
182 }
183 }
184 }
185
186 static ImageIcon newIcon( final String path ) {
187 return path == null ? null : new ImageIcon(DemoBase.getResource(HoverButton.class, "resources/icons/" + path));
188 }
189
190
191
194 private class AddButton extends NodeButton {
195 AddButton(final String enabled, final String disabled, final int position) {
196 super(enabled, disabled, position);
197 }
198
199
202 void action() {
203 final Graph2D graph = treeChart.getGraph2D();
204 final DataMap comparableEdgeMap = (DataMap) graph.getDataProvider(
205 NormalizingGraphElementOrderStage.COMPARABLE_EDGE_DPKEY);
206 final DataMap comparableNodeMap = (DataMap) graph.getDataProvider(
207 NormalizingGraphElementOrderStage.COMPARABLE_NODE_DPKEY);
208 final DataMap graph2Tree = (DataMap) graph.getDataProvider(JTreeChart.GRAPH_2_TREE_MAP_DPKEY);
209 final DataMap tree2Graph = (DataMap) graph.getDataProvider(JTreeChart.TREE_2_GRAPH_MAP_DPKEY);
210
211 final GenericNodeRealizer gnr = (GenericNodeRealizer) graph.getRealizer(node);
212 final OrgChartTreeModel.Employee employee = (OrgChartTreeModel.Employee) gnr.getUserData();
213 final OrgChartTreeModel.Employee newEmployee = new OrgChartTreeModel.Employee();
214
215 newEmployee.icon = employee.icon;
216 newEmployee.businessUnit = employee.businessUnit;
217 newEmployee.status = employee.status;
218 treeModel.insertNodeInto(newEmployee, employee, employee.getChildCount());
219
220 final Node newNode = graph.createNode();
221 graph.setCenter(newNode, gnr.getCenterX(), gnr.getCenterY());
222 final Edge edge = graph.createEdge(node, newNode);
223 graph2Tree.set(newNode, newEmployee);
224 tree2Graph.set(newEmployee,newNode);
225 comparableEdgeMap.set(edge, new Integer(edge.index()));
226 comparableNodeMap.set(newNode, new Integer(node.index()));
227
228 final HierarchyManager hm = graph.getHierarchyManager();
229 final Node businessUnit = hm.getParentNode(node);
230 if (businessUnit != null) {
231 hm.setParentNode(newNode, businessUnit);
232 }
233
234 setNode(null);
235
236 graph.firePreEvent();
237 graph.unselectAll();
238 graph.setSelected(newNode, true);
239 graph.firePostEvent();
240
241 treeChart.configureNodeRealizer(newNode);
242 treeChart.layoutGraph(true);
243 }
244
245
249 boolean isActive() {
250 return true;
251 }
252
253 String getToolTipText() {
254 return "Adds a new subordinate employee for the selected employee.";
255 }
256 }
257
258
261 private class DeleteButton extends NodeButton {
262 DeleteButton(final String enabled, final String disabled, final int position) {
263 super(enabled, disabled, position);
264 }
265
266
269 void action() {
270 final Graph2D graph = treeChart.getGraph2D();
271 final GenericNodeRealizer gnr = (GenericNodeRealizer) graph.getRealizer(node);
272 final OrgChartTreeModel.Employee employee = (OrgChartTreeModel.Employee) gnr.getUserData();
273 if (!employee.isRoot()) {
274 if (node.outDegree() > 0) {
275 employee.vacate();
276 treeChart.configureNodeRealizer(node);
277 final boolean state = graph.isSelected(node);
280 graph.setSelected(node, !state);
281 graph.setSelected(node, state);
282 } else {
283 treeModel.removeNodeFromParent(employee);
287 graph.removeNode(node);
288 }
289 setNode(null);
290 treeChart.layoutGraph(true);
291 }
292 }
293
294 String getToolTipText() {
295 return "Removes the selected employee.";
296 }
297 }
298
299
302 private class MoveButton extends NodeButton {
303 private final int moveMode;
304
305 MoveButton(final String enabled, final String disabled, final int position, final int moveMode) {
306 super(enabled, disabled, position);
307 this.moveMode = moveMode;
308 }
309
310
314 void action() {
315 final Iterator it = treeChart.getViewModes();
316 if (it.hasNext()) {
317 final ViewMode masterMode = (ViewMode) it.next();
321 final ViewMode activeMode = masterMode.getChild();
322 if (activeMode instanceof JTreeChartEditMode) {
323 final JTreeChartEditMode editMode = (JTreeChartEditMode) activeMode;
324 editMode.startMovement(node, moveMode);
325 treeChart.updateView();
326 }
327 }
328 setNode(null);
329 }
330
331 boolean isActive() {
332 return super.isActive() && !treeChart.isLocalViewEnabled() &&
333 !super.isExpandable(true) && !super.isExpandable(false);
334 }
335
336 boolean acceptEvent( final Mouse2DEvent event ) {
337 return event.getId() == Mouse2DEvent.MOUSE_PRESSED &&
338 event.getButton() == 1;
339 }
340
341 String getToolTipText() {
342 switch (moveMode) {
343 case JTreeChartMoveSelectionMode.MOVE_MODE_SINGLE:
344 return "Moves the selected employee.";
345 case JTreeChartMoveSelectionMode.MOVE_MODE_ASSISTANT:
346 return "Moves the selected employee and all of its assistants.";
347 case JTreeChartMoveSelectionMode.MOVE_MODE_ALL:
348 return "Moves the selected employee and all of its subordinate employees.";
349 default:
350 throw new IllegalStateException("Invalid movement mode: " + moveMode);
351 }
352 }
353 }
354
355
359 private class ExpandButton extends NodeButton {
360 private final boolean expandChildren;
361
362 ExpandButton(final String enabled, final String disabled, final int position, final boolean expandChildren) {
363 super(enabled, disabled, position);
364 yOffset = expandChildren ? VERTICAL_CONTROL_OFFSET : 0;
365 this.expandChildren = expandChildren;
366 }
367
368 public Rectangle getBounds() {
369 final Rectangle bounds = super.getBounds();
370 bounds.width *= 0.5;
371 bounds.height *= 0.5;
372 return bounds;
373 }
374
375
378 void action() {
379 final Graph2D graph2D = treeChart.getGraph2D();
380 if (expandChildren) {
381 int depth = graph2D.N();
382 if (hiddenNodesChild.get(node) != null) {
383 depth = 0;
385 } else {
386 final NodeList successors = GraphConnectivity.getSuccessors(graph2D, new NodeList(node), depth);
388 for (final NodeList leaves = Trees.getLeafNodes(graph2D, true);!leaves.isEmpty();) {
389 final Node n = leaves.popNode();
390 if (successors.contains(n) && hiddenNodesChild.get(n) != null) {
392 depth = Math.min(depth, Paths.findPath(graph2D,node,n,false).size());
393 }
394 }
395 }
396 expandAtDepth(node, depth);
397 } else {
398 Node currentNode = node;
400 while(currentNode.inDegree() >0) {
401 currentNode = currentNode.firstInEdge().source();
402 }
403 final NodeList nodes = (NodeList) hiddenNodesParent.get(currentNode);
405 if (nodes != null) {
406 while(!nodes.isEmpty()) {
407 graph2D.reInsertNode(nodes.popNode());
408 }
409 final EdgeList edges = (EdgeList) hiddenEdgesParent.get(currentNode);
410 while(!edges.isEmpty()) {
411 graph2D.reInsertEdge(edges.popEdge());
412 }
413 hiddenNodesParent.set(currentNode,null);
414 hiddenEdgesParent.set(currentNode,null);
415 }
416 }
417 treeChart.layoutGraph(true);
418 }
419
420
425 private void expandAtDepth(final Node node, final int depth) {
426 if (depth == 0) {
427 final NodeList nodes = (NodeList) hiddenNodesChild.get(node);
429 if (nodes != null) {
430 while(!nodes.isEmpty()) {
431 treeChart.getGraph2D().reInsertNode(nodes.popNode());
432 }
433 for (final EdgeList edges = (EdgeList) hiddenEdgesChild.get(node);!edges.isEmpty();) {
434 treeChart.getGraph2D().reInsertEdge(edges.popEdge());
435 }
436 hiddenNodesChild.set(node,null);
437 hiddenEdgesChild.set(node,null);
438 treeChart.updateView();
439 }
440 } else if (depth > 0) {
441 for (final EdgeCursor edgeCursor = node.outEdges(); edgeCursor.ok(); edgeCursor.next()) {
443 final Edge edge = edgeCursor.edge();
444 expandAtDepth(edge.target(),depth-1);
445 }
446 }
447 }
448
449
454 boolean isActive() {
455 return isExpandable(expandChildren);
456 }
457
458 String getToolTipText() {
459 if (expandChildren) {
460 return "Displays previously hidden subordinates of the selected employee.";
461 } else {
462 return "Displays previously hidden superiors of the selected employee.";
463 }
464 }
465 }
466
467
471 private class CollapseButton extends NodeButton {
472 private final boolean collapseChildren;
473
474 CollapseButton(final String enabled, final String disabled, final int position, final boolean collapseChildren) {
475 super(enabled, disabled, position);
476 yOffset = collapseChildren ? 0 : VERTICAL_CONTROL_OFFSET;
477 this.collapseChildren = collapseChildren;
478 }
479
480 public Rectangle getBounds() {
481 final Rectangle bounds = super.getBounds();
482 bounds.width *= 0.5;
483 bounds.height *= 0.5;
484 return bounds;
485 }
486
487
490 void action() {
491 final Graph2D graph = treeChart.getGraph2D();
492
493 if (collapseChildren) {
494 final NodeMap map = Maps.createHashedNodeMap();
495 Trees.getSubTreeDepths(graph, map);
496 hideAtDepth(graph, node, map.getInt(node));
497 } else {
498 Node currentNode = node;
500 Node lastNode = null;
501 while (currentNode.inDegree() > 0) {
503 lastNode = currentNode;
504 currentNode = currentNode.firstInEdge().source();
505 }
506 if (lastNode != null) {
508 final NodeList hideNodes = new NodeList(currentNode.successors());
509 final EdgeList hideEdges = new EdgeList();
510 hideNodes.remove(lastNode);
512 hideNodes.addAll(GraphConnectivity.getSuccessors(graph, hideNodes, graph.N()));
513 hideNodes.add(currentNode);
514 for (NodeCursor nc = hideNodes.nodes(); nc.ok(); nc.next()) {
515 final Node n = nc.node();
516 hideEdges.addAll(n.edges());
517 graph.removeNode(n);
518 }
519 hiddenEdgesParent.set(lastNode, hideEdges);
520 hiddenNodesParent.set(lastNode, hideNodes);
521 }
522 }
523 treeChart.layoutGraph(true);
524 }
525
526
531 private void hideAtDepth( final Graph2D graph, final Node root, final int depth ) {
532 if (depth == 2) {
533 final EdgeList edgesToHide = new EdgeList();
535 final NodeList nodesToHide = new NodeList(root.successors());
536
537 for (NodeCursor nc = nodesToHide.nodes(); nc.ok(); nc.next()) {
538 final Node child = nc.node();
539 edgesToHide.add(child.firstInEdge());
540 graph.removeNode(child);
541 }
542
543 final EdgeList oldHiddenEdges = (EdgeList) hiddenEdgesChild.get(root);
544 if (oldHiddenEdges != null) {
545 edgesToHide.splice(oldHiddenEdges);
546 }
547 hiddenEdgesChild.set(root, edgesToHide);
548 final NodeList oldHiddenNodes = (NodeList) hiddenNodesChild.get(root);
549 if (oldHiddenNodes != null) {
550 nodesToHide.splice(oldHiddenNodes);
551 }
552 hiddenNodesChild.set(root, nodesToHide);
553 } else if (depth > 2) {
554 for (NodeCursor nc = root.successors(); nc.ok(); nc.next()) {
556 hideAtDepth(graph, nc.node(), depth - 1);
557 }
558 }
559 }
560
561
566 boolean isActive() {
567 return (collapseChildren ? node.outDegree() > 0 : node.inDegree() > 0);
568 }
569
570 String getToolTipText() {
571 if (collapseChildren) {
572 return "Hides subordinates of the selected employee.";
573 } else {
574 return "Hides superiors of the selected employee.";
575 }
576 }
577 }
578
579
582 private abstract class NodeButton extends AbstractMouseInputEditor {
583 private static final int BUTTON_RADIUS = 50;
584
585 int xOffset;
586 int yOffset;
587 final Icon icon;
588 final Icon iconDisabled;
589
590 NodeButton(final String enabled, final String disabled, final int position) {
591 this.icon = newIcon(enabled);
592 this.iconDisabled = newIcon(disabled);
593 if (position == -1) {
594 xOffset = (int) ((position-1.6) * BUTTON_RADIUS);
595 } else {
596 xOffset = (position-2) * BUTTON_RADIUS;
597 }
598 }
599
600
604 public void paint(Graphics2D g) {
605 final Rectangle bounds = getBounds();
606 if (icon != null) {
607 g = (Graphics2D) g.create();
609 g.translate(bounds.x, bounds.y);
610 final double z2 = 1 / treeChart.getZoom();
611 g.scale(z2, z2);
612 if (isActive()) {
613 icon.paintIcon(treeChart.getRootPane(), g, 0, 0);
614 } else {
615 iconDisabled.paintIcon(treeChart.getRootPane(), g, 0, 0);
616 }
617 g.dispose();
618 }
619 }
620
621
625 public Rectangle getBounds() {
626 final Rectangle r = new Rectangle(-1, -1, -1, -1);
627 if (node != null) {
628 final NodeRealizer realizer = treeChart.getGraph2D().getRealizer(node);
629 final double z2 = 1 / treeChart.getZoom();
630 r.x = (int) (realizer.getCenterX() + z2 * (xOffset - BUTTON_RADIUS * 0.5));
631 r.y = (int) (realizer.getY() + realizer.getHeight() + z2 * (10 + yOffset));
632 r.width = (int) (BUTTON_RADIUS * z2);
633 r.height = (int) (BUTTON_RADIUS * z2);
634 }
635 return r;
636 }
637
638
641 void action() {
642 }
643
644
649 boolean isActive() {
650 final GenericNodeRealizer gnr = (GenericNodeRealizer) treeChart.getGraph2D().getRealizer(node);
651 final OrgChartTreeModel.Employee employee = (OrgChartTreeModel.Employee) gnr.getUserData();
652 return !employee.isRoot();
653 }
654
655
662 boolean isExpandable( final boolean successors ) {
663 final NodeMap hiddenNodes = successors
665 ? hiddenNodesChild : hiddenNodesParent;
666 if (hiddenNodes.get(node) != null) {
667 return true;
668 }
669
670 final Graph2D graph = treeChart.getGraph2D();
672 final NodeList nl = new NodeList(node);
673 final NodeList neighbors = successors
674 ? GraphConnectivity.getSuccessors(graph, nl, graph.N())
675 : GraphConnectivity.getPredecessors(graph, nl, graph.N());
676 for (NodeCursor nc = neighbors.nodes(); nc.ok(); nc.next()) {
677 final Node n = nc.node();
678 if (hiddenNodes.get(n) != null) {
679 return true;
680 }
681 }
682
683 return false;
684 }
685
686 public boolean startsEditing(final Mouse2DEvent event) {
687 return contains(event.getX(), event.getY());
688 }
689
690 public void mouse2DEventHappened(final Mouse2DEvent event) {
691 if (contains(event.getX(), event.getY())) {
692 final String text = "You're over it";
693 treeChart.setToolTipText(text);
694 if (acceptEvent(event) && isActive()) {
695 action();
696 }
697 } else {
698 stopEditing();
699 }
700 }
701
702 boolean acceptEvent( final Mouse2DEvent event ) {
703 return event.getId() == Mouse2DEvent.MOUSE_CLICKED &&
704 event.getButton() == 1;
705 }
706
707 boolean contains( final double x, final double y ) {
708 return getBounds().contains(x, y);
709 }
710
711 String getToolTipText() {
712 return null;
713 }
714 }
715 }
716