1
28 package demo.view.uml;
29
30 import y.base.Node;
31 import y.geom.YPoint;
32 import y.view.Drawable;
33 import y.view.Graph2D;
34 import y.view.Graph2DView;
35 import y.view.LineType;
36 import y.view.NodeRealizer;
37
38 import java.awt.Color;
39 import java.awt.Graphics2D;
40 import java.awt.Rectangle;
41 import java.awt.Shape;
42 import java.awt.geom.GeneralPath;
43 import java.awt.geom.Point2D;
44 import java.awt.geom.Rectangle2D;
45
46
53 class UmlEdgeCreationButtons implements Drawable {
54 private static final int BUTTON_COUNT = 6;
55 private static final int RADIUS = 15;
56 private static final int DIAMETER = RADIUS * 2;
57 private static final int GAP = 20;
58
59 static final int TYPE_ASSOCIATION = 0;
60 static final int TYPE_DEPENDENCY = 1;
61 static final int TYPE_GENERALIZATION = 2;
62 static final int TYPE_REALIZATION = 3;
63 static final int TYPE_AGGREGATION = 4;
64 static final int TYPE_COMPOSITION = 5;
65
66 private final Graph2DView view;
67 private final Graph2D graph;
68 private final Node node;
69 private final YPoint startOffset;
70 private final double[] angles;
71 private int selectedIndex;
72 private double progress;
73
74 UmlEdgeCreationButtons(Graph2DView view, final Node node) {
75 this.view = view;
76 this.node = node;
77 this.graph = view.getGraph2D();
78
79 startOffset = new YPoint(DIAMETER * -1.35, DIAMETER * 0.6);
82 angles = new double[BUTTON_COUNT];
83 for (int i = 0; i < BUTTON_COUNT; i++) {
84 angles[i] = (i + 1) * 0.7853;
85 }
86
87 selectedIndex = -1;
88 progress = 1;
89 }
90
91 public void paint(final Graphics2D graphics) {
92 final Graphics2D gfx = (Graphics2D) graphics.create();
93
94 try {
95 gfx.clip(createClip(graphics));
97
98 final double zoom = 1 / view.getZoom();
100 gfx.scale(zoom, zoom);
101
102 paintButtons(gfx);
103
104 } finally {
105 gfx.dispose();
106 }
107 }
108
109
113 private Shape createClip(final Graphics2D graphics) {
114 final float outline = (UmlRealizerFactory.LINE_EDGE_CREATION_BUTTON_OUTLINE.getLineWidth()) / 2;
115 final GeneralPath clip = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
116 final Rectangle2D.Double bounds = graph.getRealizer(node).getBoundingBox();
117 clip.append(graphics.getClip(), true);
118 final double x = bounds.getX();
119 final double y = bounds.getY();
120 final double w = bounds.getWidth();
121 final double height = bounds.getHeight();
122 final double buttonHeight = (DIAMETER + GAP) / view.getZoom();
123 final double h = height < buttonHeight ? buttonHeight : height;
124
125 clip.moveTo((float) (x - outline), (float) (y - outline));
126 clip.lineTo((float) (x + w + outline), (float) (y - outline));
127 clip.lineTo((float) (x + w + outline), (float) (y + h + outline));
128 clip.lineTo((float) (x - outline), (float) (y + h + outline));
129 clip.closePath();
130 return clip;
131 }
132
133
136 private void paintButtons(final Graphics2D graphics) {
137 final GeneralPath path = new GeneralPath();
138 final Point2D position = new Point2D.Double(0, 0);
139 for (int i = 0; i < BUTTON_COUNT; i++) {
140 calcPosition(i, position);
141 if (i != selectedIndex) {
142 graphics.setColor(UmlRealizerFactory.COLOR_BACKGROUND);
143 } else {
144 graphics.setColor(UmlRealizerFactory.COLOR_SELECTION);
145 }
146 graphics.fillOval((int) position.getX(), (int) position.getY(), DIAMETER, DIAMETER);
147
148 graphics.setColor(Color.DARK_GRAY);
149 graphics.setStroke(LineType.LINE_2);
150 graphics.drawOval((int) position.getX(), (int) position.getY(), DIAMETER, DIAMETER);
151
152 paintIcon(graphics, position, i, path);
153 }
154 }
155
156
160 private void calcPosition(final int buttonIndex, final Point2D position) {
161 final int part = buttonIndex / BUTTON_COUNT;
162 final Point2D anchor = getAnchor();
163 if (progress >= part) {
164 final double angle = angles[buttonIndex] * (progress - part);
165 final double offsetX = startOffset.getX() * Math.cos(angle) - startOffset.getY() * Math.sin(angle);
166 final double offsetY = startOffset.getX() * Math.sin(angle) + startOffset.getY() * Math.cos(angle);
167
168 position.setLocation(anchor.getX() + offsetX - RADIUS, anchor.getY() + offsetY - RADIUS);
169 } else {
170 position.setLocation(anchor.getX() + startOffset.getX() - RADIUS, anchor.getY() + startOffset.getY() - RADIUS);
171 }
172 }
173
174
177 private Point2D getAnchor() {
178 final NodeRealizer realizer = graph.getRealizer(node);
179 final double zoom = view.getZoom();
180 final double outline = (UmlRealizerFactory.LINE_EDGE_CREATION_BUTTON_OUTLINE.getLineWidth() * zoom) * 0.5;
181 final double x = (realizer.getX() + realizer.getWidth()) * zoom + outline;
182 final double y = realizer.getY() * zoom - outline;
183 return new Point2D.Double(x, y);
184 }
185
186
194 private void paintIcon(final Graphics2D graphics, final Point2D position, final int type, final GeneralPath path) {
195 path.reset();
196 graphics.setColor(Color.DARK_GRAY);
197 switch (type) {
198 case TYPE_ASSOCIATION:
199 graphics.setStroke(LineType.LINE_1);
200 graphics.drawLine((int) (position.getX() + DIAMETER * 0.25), (int) (position.getY() + DIAMETER * 0.75),
201 (int) (position.getX() + DIAMETER * 0.75), (int) (position.getY() + DIAMETER * 0.25));
202 break;
203 case TYPE_DEPENDENCY:
204 graphics.setStroke(LineType.DASHED_1);
205 path.moveTo((float) (position.getX() + DIAMETER * 0.25), (float) (position.getY() + DIAMETER * 0.75));
206 path.lineTo((float) (position.getX() + DIAMETER * 0.75), (float) (position.getY() + DIAMETER * 0.25));
207 graphics.draw(path);
208
209 path.reset();
210 graphics.setStroke(LineType.LINE_1);
211 path.moveTo((float) (position.getX() + DIAMETER * 0.75), (float) (position.getY() + DIAMETER * 0.25));
212 path.lineTo((float) (position.getX() + DIAMETER * 0.5), (float) (position.getY() + DIAMETER * 0.375));
213 path.moveTo((float) (position.getX() + DIAMETER * 0.625), (float) (position.getY() + DIAMETER * 0.5));
214 path.lineTo((float) (position.getX() + DIAMETER * 0.75), (float) (position.getY() + DIAMETER * 0.25));
215 graphics.draw(path);
216
217 break;
218 case TYPE_GENERALIZATION:
219 graphics.setStroke(LineType.LINE_1);
220 path.moveTo((float) (position.getX() + DIAMETER * 0.25), (float) (position.getY() + DIAMETER * 0.75));
221 path.lineTo((float) (position.getX() + DIAMETER * 0.5625), (float) (position.getY() + DIAMETER * 0.4375));
222 path.moveTo((float) (position.getX() + DIAMETER * 0.75), (float) (position.getY() + DIAMETER * 0.25));
223 path.lineTo((float) (position.getX() + DIAMETER * 0.5), (float) (position.getY() + DIAMETER * 0.375));
224 path.lineTo((float) (position.getX() + DIAMETER * 0.625), (float) (position.getY() + DIAMETER * 0.5));
225 path.lineTo((float) (position.getX() + DIAMETER * 0.75), (float) (position.getY() + DIAMETER * 0.25));
226 graphics.draw(path);
227
228 break;
229 case TYPE_REALIZATION:
230 graphics.setStroke(LineType.DASHED_1);
231 path.moveTo((float) (position.getX() + DIAMETER * 0.25), (float) (position.getY() + DIAMETER * 0.75));
232 path.lineTo((float) (position.getX() + DIAMETER * 0.5625), (float) (position.getY() + DIAMETER * 0.4375));
233 graphics.draw(path);
234
235 path.reset();
236 graphics.setStroke(LineType.LINE_1);
237 path.moveTo((float) (position.getX() + DIAMETER * 0.75), (float) (position.getY() + DIAMETER * 0.25));
238 path.lineTo((float) (position.getX() + DIAMETER * 0.5), (float) (position.getY() + DIAMETER * 0.375));
239 path.lineTo((float) (position.getX() + DIAMETER * 0.625), (float) (position.getY() + DIAMETER * 0.5));
240 path.lineTo((float) (position.getX() + DIAMETER * 0.625), (float) (position.getY() + DIAMETER * 0.5));
241 path.lineTo((float) (position.getX() + DIAMETER * 0.75), (float) (position.getY() + DIAMETER * 0.25));
242 graphics.draw(path);
243
244 break;
245 case TYPE_AGGREGATION:
246 graphics.setStroke(LineType.LINE_1);
247 path.moveTo((float) (position.getX() + DIAMETER * 0.5), (float) (position.getY() + DIAMETER * 0.5));
248 path.lineTo((float) (position.getX() + DIAMETER * 0.3125), (float) (position.getY() + DIAMETER * 0.5625));
249 path.lineTo((float) (position.getX() + DIAMETER * 0.25), (float) (position.getY() + DIAMETER * 0.75));
250 path.lineTo((float) (position.getX() + DIAMETER * 0.4375), (float) (position.getY() + DIAMETER * 0.6875));
251 path.lineTo((float) (position.getX() + DIAMETER * 0.5), (float) (position.getY() + DIAMETER * 0.5));
252 path.lineTo((float) (position.getX() + DIAMETER * 0.75), (float) (position.getY() + DIAMETER * 0.25));
253
254 graphics.draw(path);
255 break;
256 case TYPE_COMPOSITION:
257 graphics.setStroke(LineType.LINE_1);
258 path.moveTo((float) (position.getX() + DIAMETER * 0.5), (float) (position.getY() + DIAMETER * 0.5));
259 path.lineTo((float) (position.getX() + DIAMETER * 0.3125), (float) (position.getY() + DIAMETER * 0.5625));
260 path.lineTo((float) (position.getX() + DIAMETER * 0.25), (float) (position.getY() + DIAMETER * 0.75));
261 path.lineTo((float) (position.getX() + DIAMETER * 0.4375), (float) (position.getY() + DIAMETER * 0.6875));
262 path.lineTo((float) (position.getX() + DIAMETER * 0.5), (float) (position.getY() + DIAMETER * 0.5));
263 path.lineTo((float) (position.getX() + DIAMETER * 0.75), (float) (position.getY() + DIAMETER * 0.25));
264
265 graphics.draw(path);
266 graphics.fill(path);
267 }
268 }
269
270
273 public Rectangle getBounds() {
274 final double zoom = view.getZoom();
275 final Point2D position = new Point2D.Double(0, 0);
276 double minX = Double.MAX_VALUE;
277 double minY = Double.MAX_VALUE;
278 double maxX = -Double.MAX_VALUE;
279 double maxY = -Double.MAX_VALUE;
280 for (int i = 0; i < BUTTON_COUNT; i++) {
281 calcPosition(i, position);
282
283 final int diameter = (int) (DIAMETER / zoom);
285 final int gap = (int) (GAP / zoom);
286 minX = Math.min(minX, position.getX() / zoom - gap);
287 minY = Math.min(minY, position.getY() / zoom - gap);
288 maxX = Math.max(maxX, position.getX() / zoom + gap + diameter);
289 maxY = Math.max(maxY, position.getY() / zoom + gap + diameter);
290 }
291
292 final int x1 = (int) Math.floor(minX);
293 final int y1 = (int) Math.floor(minY);
294 final int x2 = (int) Math.ceil(maxX);
295 final int y2 = (int) Math.ceil(maxY);
296 return new Rectangle(x1, y1, x2 - x1, y2 - y1);
297 }
298
299
305 public double getProgress() {
306 return progress;
307 }
308
309
315 public void setProgress(final double progress) {
316 this.progress = progress;
317 }
318
319
324 public Node getNode() {
325 return node;
326 }
327
328
334 public void selectButtonAt(final double x, final double y) {
335 final int index = calculateButtonIndexAt(x, y);
336 setSelectedIndex(index);
337 }
338
339
344 public int getSelectedButtonIndex() {
345 return selectedIndex;
346 }
347
348
351 public void setSelectedIndex(final int index) {
352 this.selectedIndex = index;
353 }
354
355
363 public boolean hasButtonAt(final double x, final double y) {
364 final int index = calculateButtonIndexAt(x, y);
365 return index >= 0;
366 }
367
368
376 private int calculateButtonIndexAt(final double x, final double y) {
377 final double zoom = view.getZoom();
378
379 final Point2D.Double position = new Point2D.Double(0, 0);
380 for (int i = 0; i < BUTTON_COUNT; i++) {
381 calcPosition(i, position);
382 final double posX = (position.getX() + RADIUS) / zoom;
383 final double posY = (position.getY() + RADIUS) / zoom;
384 final double radius = RADIUS / zoom;
385 if (radius > Math.sqrt((posX - x) * (posX - x) + (posY - y) * (posY - y))) {
386 return i;
387 }
388 }
389 return -1;
390 }
391
392
395 public boolean contains(final double x, final double y) {
396 return getBounds().contains(x, y);
397 }
398 }
399