1 package org.simantics.diagram.elements;
3 import java.awt.geom.AffineTransform;
4 import java.awt.geom.Point2D;
5 import java.awt.geom.Rectangle2D;
6 import java.util.ArrayList;
7 import java.util.Collection;
8 import java.util.Collections;
9 import java.util.Comparator;
10 import java.util.List;
12 import org.simantics.db.ReadGraph;
13 import org.simantics.db.Resource;
14 import org.simantics.db.UndoContext;
15 import org.simantics.db.WriteGraph;
16 import org.simantics.db.common.CommentMetadata;
17 import org.simantics.db.common.request.IndexRoot;
18 import org.simantics.db.common.request.WriteRequest;
19 import org.simantics.db.exception.DatabaseException;
20 import org.simantics.db.exception.ManyObjectsForFunctionalRelationException;
21 import org.simantics.db.exception.NoSingleResultException;
22 import org.simantics.db.exception.ServiceException;
23 import org.simantics.db.request.Write;
24 import org.simantics.diagram.query.DiagramRequests;
25 import org.simantics.diagram.stubs.DiagramResource;
26 import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;
27 import org.simantics.g2d.diagram.DiagramClass;
28 import org.simantics.g2d.diagram.IDiagram;
29 import org.simantics.g2d.diagram.impl.Diagram;
30 import org.simantics.g2d.element.ElementClass;
31 import org.simantics.g2d.element.ElementUtils;
32 import org.simantics.g2d.element.IElement;
33 import org.simantics.g2d.element.impl.Element;
34 import org.simantics.g2d.utils.GeometryUtils;
35 import org.simantics.scl.commands.Command;
36 import org.simantics.scl.commands.Commands;
37 import org.simantics.ui.SimanticsUI;
40 * Tools to align, rotate, and flip diagram elements.
42 * TODO : We need to add capability hints to elements to prevent rotating and mirroring elements that do not support that.
43 * Example: mirrored text does not make any sense.
46 * @author Marko Luukkainen <marko.luukkainen@vtt.fi> (implementation)
47 * @author Tuukka Lehtonen (documentation)
49 public final class ElementTransforms {
51 public static enum SIDE { LEFT, RIGHT, TOP, BOTTOM, VERT, HORIZ, VERT_BTW, HORIZ_BTW };
54 * Align the specified set of diagram element resources in line with each
55 * other calculated by the specified side.
58 * Alignment requires at least two elements to do anything.
60 * @param resources diagram element resources to rotate
61 * @param side the side of each element to use for distancing. Does not support between aligments.
63 public static void align(final Resource resources[], final SIDE side) {
64 if (resources.length < 2)
66 if (side == SIDE.HORIZ_BTW || side == SIDE.VERT_BTW )
69 SimanticsUI.getSession().asyncRequest(new WriteRequest() {
72 public void perform(WriteGraph graph) throws DatabaseException {
73 graph.markUndoPoint();
74 IDiagram hints = Diagram.spawnNew(DiagramClass.DEFAULT);
76 List<AlignElement> elements = new ArrayList<AlignElement>();
77 for (Resource r : resources) {
78 AlignElement e = create(graph, hints, r);
82 if (elements.size() < 2)
86 for (AlignElement e : elements) {
90 mx /= elements.size();
91 my /= elements.size();
93 // prevent moving symbols into the same position
95 for (AlignElement e : elements) {
96 if (side == SIDE.VERT || side == SIDE.LEFT || side == SIDE.RIGHT) {
97 if (Math.abs(e.transform[5] - my) < 0.1) {
101 if (Math.abs(e.transform[4] - mx) < 0.1) {
109 if (side == SIDE.HORIZ || side == SIDE.VERT) {
112 for (AlignElement e : elements) {
114 if (side == SIDE.VERT)
115 DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],mx,e.transform[5]});
116 else if (side == SIDE.HORIZ) {
117 DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],e.transform[4],my});
123 lx = elements.get(0).transform[4] + elements.get(0).rotatedBounds.getMinX();
124 rx = elements.get(0).transform[4] + elements.get(0).rotatedBounds.getMaxX();
126 ty = elements.get(0).transform[5] + elements.get(0).rotatedBounds.getMinY();
127 by = elements.get(0).transform[5] + elements.get(0).rotatedBounds.getMaxY();
129 for (int i = 1; i < elements.size(); i++) {
132 tlx = elements.get(i).transform[4] + elements.get(i).rotatedBounds.getMinX();
133 trx = elements.get(i).transform[4] + elements.get(i).rotatedBounds.getMaxX();
135 tty = elements.get(i).transform[5] + elements.get(i).rotatedBounds.getMinY();
136 tby = elements.get(i).transform[5] + elements.get(i).rotatedBounds.getMaxY();
148 for (AlignElement e : elements) {
151 if (side == SIDE.LEFT) {
152 mx = lx - e.rotatedBounds.getMinX() ;
153 } else if (side == SIDE.RIGHT) {
154 mx = rx - e.rotatedBounds.getMaxX();
155 } else if (side == SIDE.TOP) {
156 my = ty - e.rotatedBounds.getMinY();
158 my = by - e.rotatedBounds.getMaxY();
160 DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],mx,my});
171 * Distance specified set of diagram element resources equally. Distancing
172 * is performed on the specified side of the each element.
175 * Distancing requires at least three elements to work.
177 * @param resources diagram element resources to rotate
178 * @param side the side of each element to use for distancing
180 public static void dist(final Resource resources[], final SIDE side) {
182 if (resources.length < 3)
185 SimanticsUI.getSession().asyncRequest(new WriteRequest() {
188 public void perform(WriteGraph graph) throws DatabaseException {
189 graph.markUndoPoint();
190 IDiagram hints = Diagram.spawnNew(DiagramClass.DEFAULT);
192 List<AlignElement> elements = new ArrayList<AlignElement>();
193 for (Resource r : resources) {
194 //System.out.println(r + " " + GraphUtils.getReadableName(graph, r));
195 AlignElement e = create(graph, hints, r);
199 if (elements.size() < 3)
203 Collections.sort(elements, new XComparator());
204 AlignElement left = elements.get(0);
205 AlignElement right = elements.get(elements.size() - 1);
207 double leftEdge = left.transform[4] + left.rotatedBounds.getMinX();
208 double rightEdge = right.transform[4] + right.rotatedBounds.getMinX();
210 double totalDist = rightEdge - leftEdge;
211 double dist = totalDist / (elements.size() - 1);
214 for (int i = 1; i < elements.size() -1; i++) {
216 AlignElement e = elements.get(i);
218 double mx = d - e.rotatedBounds.getMinX();
220 DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],mx,e.transform[5]});
226 Collections.sort(elements, new XComparator());
227 AlignElement left = elements.get(0);
228 AlignElement right = elements.get(elements.size() - 1);
230 double leftEdge = left.transform[4];
231 double rightEdge = right.transform[4];
233 double totalDist = rightEdge - leftEdge;
234 double dist = totalDist / (elements.size() - 1);
237 for (int i = 1; i < elements.size() -1; i++) {
239 AlignElement e = elements.get(i);
243 DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],mx,e.transform[5]});
249 Collections.sort(elements, new XComparator());
250 AlignElement left = elements.get(0);
251 AlignElement right = elements.get(elements.size() - 1);
253 double leftEdge = left.transform[4] + left.rotatedBounds.getMaxX();
254 double rightEdge = right.transform[4] + right.rotatedBounds.getMaxX();
256 double totalDist = rightEdge - leftEdge;
257 double dist = totalDist / (elements.size() - 1);
260 for (int i = 1; i < elements.size() -1; i++) {
262 AlignElement e = elements.get(i);
264 double mx = d - e.rotatedBounds.getMaxX();
266 DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],mx,e.transform[5]});
271 Collections.sort(elements, new XComparator());
272 AlignElement left = elements.get(0);
273 AlignElement right = elements.get(elements.size() - 1);
275 double leftEdge = left.transform[4] + left.rotatedBounds.getMaxX();
276 double rightEdge = right.transform[4] + right.rotatedBounds.getMinX();
278 double totalDist = rightEdge - leftEdge;
279 double totalElementSize = 0;
280 for (int i = 1; i < elements.size() -1; i++) {
281 totalElementSize += elements.get(i).rotatedBounds.getWidth();
283 double totalAvail = totalDist - totalElementSize;
284 double dist = totalAvail / (elements.size() - 1);
287 for (int i = 1; i < elements.size() -1; i++) {
289 AlignElement e = elements.get(i);
291 double mx = d - e.rotatedBounds.getMinX();
292 d += e.bounds.getWidth();
294 DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],mx,e.transform[5]});
300 Collections.sort(elements, new YComparator());
301 AlignElement top = elements.get(0);
302 AlignElement bottom = elements.get(elements.size() - 1);
304 double topEdge = top.transform[5] + top.rotatedBounds.getMaxY();
305 double bottomEdge = bottom.transform[5] + bottom.rotatedBounds.getMaxY();
307 double totalDist = bottomEdge - topEdge;
308 double dist = totalDist / (elements.size() - 1);
311 for (int i = 1; i < elements.size() -1; i++) {
313 AlignElement e = elements.get(i);
315 double my = d - e.rotatedBounds.getMaxY();
317 DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],e.transform[4],my});
323 Collections.sort(elements, new YComparator());
324 AlignElement top = elements.get(0);
325 AlignElement bottom = elements.get(elements.size() - 1);
327 double topEdge = top.transform[5] + top.rotatedBounds.getMinY();
328 double bottomEdge = bottom.transform[5] + bottom.rotatedBounds.getMinY();
330 double totalDist = bottomEdge - topEdge;
331 double dist = totalDist / (elements.size() - 1);
334 for (int i = 1; i < elements.size() -1; i++) {
336 AlignElement e = elements.get(i);
338 double my = d - e.rotatedBounds.getMinY();
340 DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],e.transform[4],my});
346 Collections.sort(elements, new YComparator());
347 AlignElement top = elements.get(0);
348 AlignElement bottom = elements.get(elements.size() - 1);
350 double topEdge = top.transform[5];
351 double bottomEdge = bottom.transform[5];
353 double totalDist = bottomEdge - topEdge;
354 double dist = totalDist / (elements.size() - 1);
357 for (int i = 1; i < elements.size() -1; i++) {
359 AlignElement e = elements.get(i);
363 DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],e.transform[4],my});
369 Collections.sort(elements, new YComparator());
370 AlignElement top = elements.get(0);
371 AlignElement bottom = elements.get(elements.size() - 1);
373 double topEdge = top.transform[5] + top.rotatedBounds.getMaxY();
374 double bottomEdge = bottom.transform[5] + bottom.rotatedBounds.getMinY();
376 double totalDist = bottomEdge - topEdge;
377 double totalElementSize = 0;
378 for (int i = 1; i < elements.size() -1; i++) {
379 totalElementSize += elements.get(i).rotatedBounds.getHeight();
381 double totalAvail = totalDist - totalElementSize;
382 double dist = totalAvail / (elements.size() - 1);
385 for (int i = 1; i < elements.size() -1; i++) {
387 AlignElement e = elements.get(i);
389 double my = d - e.rotatedBounds.getMinY();
390 d += e.rotatedBounds.getHeight();
392 DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],e.transform[4],my});
407 * Rotate specified set of diagram element resources around the center of
408 * mass of the specified element selection.
410 * @param resources diagram element resources to rotate
411 * @param clockwise <code>true</code> to rotate 90 degrees clockwise,
412 * <code>false</code> to rotate 90 degrees counter-clockwise
414 public static void rotate(final Resource resources[], final boolean clockwise) {
415 SimanticsUI.getSession().asyncRequest(new WriteRequest() {
417 public void perform(WriteGraph graph) throws DatabaseException {
418 graph.markUndoPoint();
419 IDiagram hints = Diagram.spawnNew(DiagramClass.DEFAULT);
421 DiagramResource DIA = DiagramResource.getInstance(graph);
423 List<AlignElement> elements = new ArrayList<AlignElement>();
424 List<Resource> connections = new ArrayList<Resource>();
425 for (Resource r : resources) {
426 AlignElement e = create(graph, hints, r);
429 else if(graph.isInstanceOf(r, DIA.RouteGraphConnection))
432 if (elements.size() < 1)
435 // Add comment to change set.
436 CommentMetadata cm = graph.getMetadata(CommentMetadata.class);
437 graph.addMetadata( cm.add("Rotate " + elements.size() + " elements " + (clockwise ? "clockwise" : "counter-clockwise")) );
439 AffineTransform at = clockwise ? AffineTransform.getQuadrantRotateInstance(1)
440 : AffineTransform.getQuadrantRotateInstance(3);
442 if (elements.size() == 1 && connections.isEmpty()) {
443 for (AlignElement e : elements) {
444 AffineTransform eat = new AffineTransform(e.transform[0], e.transform[1], e.transform[2], e.transform[3], 0, 0);
445 eat.preConcatenate(at);
447 DiagramGraphUtil.changeTransform(graph, e.element,
448 new double[]{eat.getScaleX(),eat.getShearY(),eat.getShearX(),eat.getScaleY(),e.transform[4],e.transform[5]});
451 Rectangle2D selectionBounds = null;
452 for (AlignElement e : elements) {
453 if (selectionBounds != null) {
454 selectionBounds.add(e.transform[4], e.transform[5]);
456 selectionBounds = new Rectangle2D.Double(e.transform[4], e.transform[5], 0, 0);
460 double cx = selectionBounds.getCenterX();
461 double cy = selectionBounds.getCenterY();
463 for (AlignElement e : elements) {
464 double x = e.transform[4];
465 double y = e.transform[5];
468 Point2D r = at.transform(new Point2D.Double(dx, dy), null);
469 double mx = r.getX() + cx;
470 double my = r.getY() + cy;
471 AffineTransform eat = new AffineTransform(e.transform[0], e.transform[1], e.transform[2], e.transform[3], 0, 0);
472 eat.preConcatenate(at);
474 DiagramGraphUtil.changeTransform(graph, e.element,
475 new double[]{eat.getScaleX(),eat.getShearY(),eat.getShearX(),eat.getScaleY(),mx,my});
478 if(!connections.isEmpty()) {
479 Command rotateConnection = Commands.get(graph, "Simantics/Diagram/rotateConnection");
480 Resource model = graph.syncRequest(new IndexRoot(connections.get(0)));
481 for(Resource r : connections)
482 rotateConnection.execute(graph, model, r, cx, cy, clockwise);
490 * Flip specified set of diagram element resources around either the x or
491 * y-axis specified by the mass center of the selection bounding box.
492 * Each element is considered to weigh an equal amount.
494 * @param resources diagram element resources to flip
495 * @param xAxis <code>true</code> to flip around x-axis, <code>false</code>
498 public static void flip(final Resource resources[], final boolean xAxis) {
499 SimanticsUI.getSession().asyncRequest(new WriteRequest() {
501 public void perform(WriteGraph graph) throws DatabaseException {
502 graph.markUndoPoint();
503 IDiagram hints = Diagram.spawnNew(DiagramClass.DEFAULT);
505 DiagramResource DIA = DiagramResource.getInstance(graph);
507 List<AlignElement> elements = new ArrayList<AlignElement>();
508 List<Resource> connections = new ArrayList<Resource>();
509 for (Resource r : resources) {
510 AlignElement e = create(graph, hints, r);
513 else if(graph.isInstanceOf(r, DIA.RouteGraphConnection))
516 if (elements.size() < 1)
519 // Add comment to change set.
520 CommentMetadata cm = graph.getMetadata(CommentMetadata.class);
521 graph.addMetadata( cm.add("Flip " + elements.size() + " elements " + (xAxis ? "vertically" : "horizontally")) );
523 if (elements.size() == 1 && connections.isEmpty()) {
524 for (AlignElement e : elements) {
526 AffineTransform at = new AffineTransform(e.transform);
527 AffineTransform at2 = AffineTransform.getScaleInstance(1, -1);
528 at.preConcatenate(at2);
529 DiagramGraphUtil.changeTransform(graph, e.element, new double[]{at.getScaleX(),at.getShearY(),at.getShearX(),at.getScaleY(),e.transform[4],e.transform[5]});
531 AffineTransform at = new AffineTransform(e.transform);
532 AffineTransform at2 = AffineTransform.getScaleInstance(-1, 1);
533 at.preConcatenate(at2);
534 DiagramGraphUtil.changeTransform(graph, e.element, new double[]{at.getScaleX(),at.getShearY(),at.getShearX(),at.getScaleY(),e.transform[4],e.transform[5]});
539 Rectangle2D selectionBounds = null;
540 for (AlignElement e : elements) {
541 if (selectionBounds != null) {
542 selectionBounds.add(e.transform[4], e.transform[5]);
544 selectionBounds = new Rectangle2D.Double(e.transform[4], e.transform[5], 0, 0);
548 for (AlignElement e : elements) {
550 double y = e.transform[5];
551 double cy = selectionBounds.getCenterY();
552 double my = cy + cy - y;
553 AffineTransform at = new AffineTransform(e.transform);
554 AffineTransform at2 = AffineTransform.getScaleInstance(1, -1);
555 at.preConcatenate(at2);
556 DiagramGraphUtil.changeTransform(graph, e.element, new double[]{at.getScaleX(),at.getShearY(),at.getShearX(),at.getScaleY(),e.transform[4],my});
558 double x = e.transform[4];
559 double cx = selectionBounds.getCenterX();
560 double mx = cx + cx - x;
562 AffineTransform at = new AffineTransform(e.transform);
563 AffineTransform at2 = AffineTransform.getScaleInstance(-1, 1);
564 at.preConcatenate(at2);
565 DiagramGraphUtil.changeTransform(graph, e.element, new double[]{at.getScaleX(),at.getShearY(),at.getShearX(),at.getScaleY(),mx,e.transform[5]});
569 if(!connections.isEmpty()) {
570 Command flipConnection = Commands.get(graph, "Simantics/Diagram/flipConnection");
571 Resource model = graph.syncRequest(new IndexRoot(connections.get(0)));
572 for(Resource r : connections)
573 flipConnection.execute(graph, model, r, xAxis,
574 xAxis ? selectionBounds.getCenterY()
575 : selectionBounds.getCenterX());
583 private static AlignElement create(ReadGraph graph, IDiagram hints, Resource r) throws ManyObjectsForFunctionalRelationException, NoSingleResultException, ServiceException, DatabaseException {
584 DiagramResource dr = DiagramResource.getInstance(graph);
586 if (graph.isInstanceOf(r, dr.Element) && !graph.isInstanceOf(r, dr.Connection) /*&& !graph.isInstanceOf(r, dr.Monitor)*/) {
587 double transform[] = graph.getPossibleRelatedValue(r, dr.HasTransform);
588 ElementClass ec = graph.syncRequest(DiagramRequests.getElementClass(graph.getSingleType(r, dr.Element), hints));
589 IElement e = Element.spawnNew(ec);
590 Rectangle2D bounds = ElementUtils.getElementBounds(e);
591 if (transform != null && bounds != null) {
592 return new AlignElement(r, transform, bounds);
599 private static class AlignElement {
600 public Resource element;
601 public double[] transform;
602 public Rectangle2D bounds;
603 public Rectangle2D rotatedBounds;
604 // public Rectangle2D transformedBounds;
606 public AlignElement(Resource element, double[] transform, Rectangle2D bounds) {
607 this.element = element;
608 this.transform = transform;
609 this.bounds = bounds;
610 // this.transformedBounds = getBounds();
611 this.rotatedBounds = getRotatedBounds();
614 // public Rectangle2D getBounds() {
615 // AffineTransform at = new AffineTransform(transform[0], transform[1], transform[2], transform[3], transform[4], transform[5]);
616 // return GeometryUtils.transformShape(bounds, at).getBounds2D();
619 public Rectangle2D getRotatedBounds() {
620 AffineTransform at = new AffineTransform(transform[0], transform[1], transform[2], transform[3], 0.0, 0.0);
621 return GeometryUtils.transformShape(bounds, at).getBounds2D();
624 public double getDeterminant() {
625 return transform[0] * transform[3] - transform[1] * transform[2];
630 private static class XComparator implements Comparator<AlignElement> {
632 public int compare(AlignElement o1, AlignElement o2) {
633 if (o1.transform[4] < o2.transform[4])
635 if (o1.transform[4] > o2.transform[4])
642 private static class YComparator implements Comparator<AlignElement> {
644 public int compare(AlignElement o1, AlignElement o2) {
645 if (o1.transform[5] < o2.transform[5])
647 if (o1.transform[5] > o2.transform[5])
655 * Set 2D affine transforms for the listed diagram element resources.
657 * @param elements diagram element resources to set transforms for
658 * @param transforms transforms for each element
660 public static Write setTransformRequest(final Collection<TransformedObject> elements)
662 return new WriteRequest() {
664 public void perform(WriteGraph graph) throws DatabaseException {
665 for (TransformedObject element : elements)
666 DiagramGraphUtil.changeTransform(graph, element.element, element.transform);
672 * Set 2D affine transforms for the listed diagram element resources.
674 * @param undoContext the database undo context to use for the returned
676 * @param elements diagram element resources to set transforms for
677 * @param transforms transforms for each element
678 * @param preConcatenate <code>true</code> to pre-concatenate the
679 * transforms, <code>false</code> to concatenate
681 public static Write concatenateTransformRequest(
682 UndoContext undoContext,
683 final Collection<TransformedObject> elements,
684 final boolean preConcatenate)
686 return new WriteRequest() {
688 public void perform(WriteGraph graph) throws DatabaseException {
689 for (TransformedObject element : elements) {
690 AffineTransform at = DiagramGraphUtil.getTransform(graph, element.element);
692 at.preConcatenate(element.transform);
694 at.concatenate(element.transform);
695 DiagramGraphUtil.setTransform(graph, element.element, at);
701 public static class TransformedObject {
702 public final Resource element;
703 public final AffineTransform transform;
705 public TransformedObject(Resource element) {
706 this.element = element;
707 this.transform = new AffineTransform();
709 public TransformedObject(Resource element, AffineTransform transform) {
710 this.element = element;
711 this.transform = transform;