| CollapseButton.java |
1 /****************************************************************************
2 * This demo file is part of yFiles for Java 2.14.
3 * Copyright (c) 2000-2017 by yWorks GmbH, Vor dem Kreuzberg 28,
4 * 72070 Tuebingen, Germany. All rights reserved.
5 *
6 * yFiles demo files exhibit yFiles for Java functionalities. Any redistribution
7 * of demo files in source code or binary form, with or without
8 * modification, is not permitted.
9 *
10 * Owners of a valid software license for a yFiles for Java version that this
11 * demo is shipped with are allowed to use the demo source code as basis
12 * for their own yFiles for Java powered applications. Use of such programs is
13 * governed by the rights and conditions as set out in the yFiles for Java
14 * license agreement.
15 *
16 * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
19 * NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
21 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 *
27 ***************************************************************************/
28 package demo.view.mindmap;
29
30 import y.base.GraphEvent;
31 import y.base.GraphListener;
32 import y.base.Node;
33 import y.base.NodeCursor;
34 import y.base.NodeList;
35 import y.view.AbstractMouseInputEditor;
36 import y.view.Drawable;
37 import y.view.Graph2D;
38 import y.view.Graph2DView;
39 import y.view.HitInfo;
40 import y.view.Mouse2DEvent;
41 import y.view.MouseInputEditor;
42 import y.view.MouseInputEditorProvider;
43 import y.view.NodeRealizer;
44
45 import java.awt.Color;
46 import java.awt.Graphics2D;
47 import java.awt.Rectangle;
48 import java.awt.geom.GeneralPath;
49 import java.util.WeakHashMap;
50
51 /**
52 * Hides or unhides children of an item.
53 */
54 class CollapseButton extends AbstractMouseInputEditor implements Drawable, MouseInputEditorProvider, MouseInputEditor {
55 /**
56 * The target node for this collapse button's edit action.
57 */
58 private final Node node;
59 /**
60 * The graph data structure for this collapse button's associated node.
61 */
62 private final Graph2D graph;
63
64 /**
65 * Initializes a new <code>CollapseButton</code> instance.
66 */
67 CollapseButton( final Node node, Graph2D graph ) {
68 this.graph = graph;
69 this.node = node;
70 }
71
72 /**
73 * Returns the bounds of this collapse button in graph (world) coordinates.
74 * @return the bounds of this collapse button in graph (world) coordinates.
75 */
76 public Rectangle getBounds() {
77 Rectangle r = new Rectangle(-1,-1);
78 if (isVisible()) {
79 r = new Rectangle(16, 16);
80 final NodeRealizer realizer = graph.getRealizer(node);
81 //place the collapse button for the center item to the bottom center
82 if (ViewModel.instance.isRoot(node)) {
83 r.x = (int) realizer.getCenterX() - 8;
84 r.y = (int) (realizer.getY() + realizer.getHeight());
85 //place the collapse button depending on the side of an item
86 } else {
87 if (ViewModel.instance.isLeft(node)) {
88 r.x = (int) (realizer.getX() - 13);
89 } else {
90 r.x = (int) (realizer.getX() + realizer.getWidth());
91 }
92 r.y = (int) (realizer.getY() + realizer.getHeight()/2+3);
93 }
94 }
95 return r;
96 }
97
98 /**
99 * Paints a white arrow in a light blue circle to represent this collapse
100 * button.
101 */
102 public void paint(Graphics2D g) {
103 final double x = this.getBounds().x;
104 final double y = this.getBounds().y;
105 if (isVisible()) {
106 //make sure to point the arrow to the right direction
107 g = (Graphics2D) g.create();
108 g.translate(x,y);
109 GeneralPath gp = new GeneralPath();
110 //Right arrow
111 if (!ViewModel.instance.isCollapsed(node) == ViewModel.instance.isLeft(node)) {
112 gp.moveTo(4, 6);
113 gp.lineTo(4, 10);
114 gp.lineTo(8, 10);
115 gp.lineTo(8, 12);
116 gp.lineTo(13, 8);
117 gp.lineTo(8, 4);
118 gp.lineTo(8, 6);
119 //Left arrow
120 } else {
121 gp.moveTo(12, 6);
122 gp.lineTo(12, 10);
123 gp.lineTo(8, 10);
124 gp.lineTo(8, 12);
125 gp.lineTo(3, 8);
126 gp.lineTo(8, 4);
127 gp.lineTo(8, 6);
128 }
129 final Color circleBlue = new Color(6, 164, 255);
130 g.setColor(circleBlue);
131 g.fillOval(0, 0, 16, 16);
132 g.setColor(Color.WHITE);
133 g.fill(gp);
134 g.dispose();
135 }
136 }
137
138 /**
139 * Determines if this collapse button should be visible.
140 * @return <code>true</code> if this collapse button is visible;
141 * <code>false</code> otherwise.
142 */
143 private boolean isVisible() {
144 return graph.contains(node) &&
145 (ViewModel.instance.isCollapsed(node) ||
146 !MindMapUtil.outEdges(node).isEmpty());
147 }
148
149 /**
150 * Collapses or expands the subtree rooted at this collapse button's
151 * associated node.
152 */
153 private void handleClick() {
154 if (isVisible()) {
155 MindMapUtil.toggleCollapseState(graph, node);
156 graph.updateViews();
157 }
158 }
159
160 /**
161 * Determines if this control should be activated for the given event.
162 * @param event the event that happened
163 * @return <code>true</code> if the event position lies inside the bounds
164 * of this control and <code>false</code> otherwise.
165 */
166 public boolean startsEditing(final Mouse2DEvent event) {
167 return getBounds().contains(event.getX(), event.getY());
168 }
169
170 /**
171 * Handles mouse events while this control is active.
172 * The default implementation calls {@link #handleClick()} for mouse clicks.
173 * @param event the event that happened
174 */
175 public void mouse2DEventHappened(final Mouse2DEvent event) {
176 if (getBounds().contains(event.getX(), event.getY())) {
177 if (event.getId() == Mouse2DEvent.MOUSE_CLICKED) {
178 handleClick();
179 stopEditing();
180 }
181 } else {
182 stopEditing();
183 }
184 }
185
186 /**
187 * Returns this <code>CollapseButton</code> instance.
188 * @param view the view that will host the editor
189 * @param x the x-coordinate of the mouse event
190 * @param y the y-coordinate of the mouse event
191 * @param hitInfo the HitInfo that may be used to determine what instance to return or <code>null</code>
192 * @return this <code>CollapseButton</code> instance or <code>null</code> if
193 * the specified coordinates do not lie within this collapse button's bounds.
194 */
195 public MouseInputEditor findMouseInputEditor(Graph2DView view, double x, double y, HitInfo hitInfo) {
196 return getBounds().contains(x, y) ? this : null;
197 }
198
199
200 /**
201 * Adds and removes collapse buttons to the view whenever a node is
202 * created or deleted.
203 */
204 static class Handler implements GraphListener {
205 private final Graph2DView view;
206 private final WeakHashMap node2button;
207
208 /**
209 * Initializes a new <code>Handler</code> instance for the given view.
210 * @param view the view displaying the mind map.
211 */
212 Handler( final Graph2DView view ) {
213 this.view = view;
214 this.node2button = new WeakHashMap();
215
216 view.getGraph2D().addGraphListener(this);
217 }
218
219 /**
220 * Adds collapse buttons on node created/reinserted events and
221 * removes collapse buttons on node removed events.
222 */
223 public void onGraphEvent( final GraphEvent e ) {
224 switch (e.getType()) {
225 case GraphEvent.NODE_CREATION:
226 case GraphEvent.NODE_REINSERTION:
227 onNodeCreated((Node) e.getData());
228 break;
229 case GraphEvent.PRE_NODE_REMOVAL:
230 onNodeDeleted((Node) e.getData());
231 break;
232 case GraphEvent.SUBGRAPH_INSERTION:
233 for (NodeCursor nc = ((NodeList) e.getData()).nodes(); nc.ok(); nc.next()) {
234 onNodeCreated(nc.node());
235 }
236 break;
237 case GraphEvent.SUBGRAPH_REMOVAL:
238 for (NodeCursor nc = ((NodeList) e.getData()).nodes(); nc.ok(); nc.next()) {
239 onNodeDeleted(nc.node());
240 }
241 break;
242 }
243 }
244
245 /**
246 * Adds a collapse button for the specified node to this handler's
247 * associated view.
248 */
249 private void onNodeCreated( final Node node ) {
250 final CollapseButton button = new CollapseButton(node, view.getGraph2D());
251 node2button.put(node, button);
252 view.addDrawable(button);
253 }
254
255 /**
256 * Removes the collapse button associated to the specified node from this
257 * handler's associated view.
258 */
259 private void onNodeDeleted( final Node node ) {
260 final Object button = node2button.get(node);
261 if (button instanceof CollapseButton) {
262 view.removeDrawable(((CollapseButton) button));
263 }
264 }
265 }
266 }
267