-package org.simantics.diagram.elements;\r
-\r
-import java.awt.geom.AffineTransform;\r
-import java.awt.geom.Point2D;\r
-import java.awt.geom.Rectangle2D;\r
-import java.util.ArrayList;\r
-import java.util.Collection;\r
-import java.util.Collections;\r
-import java.util.Comparator;\r
-import java.util.List;\r
-\r
-import org.simantics.db.ReadGraph;\r
-import org.simantics.db.Resource;\r
-import org.simantics.db.UndoContext;\r
-import org.simantics.db.WriteGraph;\r
-import org.simantics.db.common.CommentMetadata;\r
-import org.simantics.db.common.request.IndexRoot;\r
-import org.simantics.db.common.request.WriteRequest;\r
-import org.simantics.db.exception.DatabaseException;\r
-import org.simantics.db.exception.ManyObjectsForFunctionalRelationException;\r
-import org.simantics.db.exception.NoSingleResultException;\r
-import org.simantics.db.exception.ServiceException;\r
-import org.simantics.db.request.Write;\r
-import org.simantics.diagram.query.DiagramRequests;\r
-import org.simantics.diagram.stubs.DiagramResource;\r
-import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;\r
-import org.simantics.g2d.diagram.DiagramClass;\r
-import org.simantics.g2d.diagram.IDiagram;\r
-import org.simantics.g2d.diagram.impl.Diagram;\r
-import org.simantics.g2d.element.ElementClass;\r
-import org.simantics.g2d.element.ElementUtils;\r
-import org.simantics.g2d.element.IElement;\r
-import org.simantics.g2d.element.impl.Element;\r
-import org.simantics.g2d.utils.GeometryUtils;\r
-import org.simantics.scl.commands.Command;\r
-import org.simantics.scl.commands.Commands;\r
-import org.simantics.ui.SimanticsUI;\r
-\r
-/**\r
- * Tools to align, rotate, and flip diagram elements.\r
- * \r
- * TODO : We need to add capability hints to elements to prevent rotating and mirroring elements that do not support that.\r
- * Example: mirrored text does not make any sense.\r
- * \r
- * \r
- * @author Marko Luukkainen <marko.luukkainen@vtt.fi> (implementation)\r
- * @author Tuukka Lehtonen (documentation)\r
- */\r
-public final class ElementTransforms {\r
-\r
- public static enum SIDE { LEFT, RIGHT, TOP, BOTTOM, VERT, HORIZ, VERT_BTW, HORIZ_BTW };\r
-\r
- /**\r
- * Align the specified set of diagram element resources in line with each\r
- * other calculated by the specified side.\r
- * \r
- * <p>\r
- * Alignment requires at least two elements to do anything.\r
- * \r
- * @param resources diagram element resources to rotate\r
- * @param side the side of each element to use for distancing. Does not support between aligments.\r
- */\r
- public static void align(final Resource resources[], final SIDE side) {\r
- if (resources.length < 2)\r
- return;\r
- if (side == SIDE.HORIZ_BTW || side == SIDE.VERT_BTW )\r
- return;\r
-\r
- SimanticsUI.getSession().asyncRequest(new WriteRequest() {\r
-\r
- @Override\r
- public void perform(WriteGraph graph) throws DatabaseException {\r
- graph.markUndoPoint();\r
- IDiagram hints = Diagram.spawnNew(DiagramClass.DEFAULT);\r
-\r
- List<AlignElement> elements = new ArrayList<AlignElement>();\r
- for (Resource r : resources) {\r
- AlignElement e = create(graph, hints, r);\r
- if (e != null)\r
- elements.add(e);\r
- }\r
- if (elements.size() < 2)\r
- return;\r
- double mx = 0;\r
- double my = 0;\r
- for (AlignElement e : elements) {\r
- mx += e.transform[4];\r
- my += e.transform[5];\r
- }\r
- mx /= elements.size();\r
- my /= elements.size();\r
-\r
- // prevent moving symbols into the same position\r
- int count = 0;\r
- for (AlignElement e : elements) {\r
- if (side == SIDE.VERT || side == SIDE.LEFT || side == SIDE.RIGHT) {\r
- if (Math.abs(e.transform[5] - my) < 0.1) {\r
- count++;\r
- }\r
- } else {\r
- if (Math.abs(e.transform[4] - mx) < 0.1) {\r
- count++;\r
- }\r
- }\r
- }\r
- if (count > 1)\r
- return;\r
-\r
- if (side == SIDE.HORIZ || side == SIDE.VERT) {\r
-\r
-\r
- for (AlignElement e : elements) {\r
-\r
- if (side == SIDE.VERT)\r
- DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],mx,e.transform[5]});\r
- else if (side == SIDE.HORIZ) {\r
- DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],e.transform[4],my});\r
- }\r
- }\r
- } else {\r
- double lx, rx;\r
- double ty, by;\r
- lx = elements.get(0).transform[4] + elements.get(0).rotatedBounds.getMinX();\r
- rx = elements.get(0).transform[4] + elements.get(0).rotatedBounds.getMaxX();\r
-\r
- ty = elements.get(0).transform[5] + elements.get(0).rotatedBounds.getMinY();\r
- by = elements.get(0).transform[5] + elements.get(0).rotatedBounds.getMaxY();\r
-\r
- for (int i = 1; i < elements.size(); i++) {\r
- double tlx, trx;\r
- double tty, tby;\r
- tlx = elements.get(i).transform[4] + elements.get(i).rotatedBounds.getMinX();\r
- trx = elements.get(i).transform[4] + elements.get(i).rotatedBounds.getMaxX();\r
-\r
- tty = elements.get(i).transform[5] + elements.get(i).rotatedBounds.getMinY();\r
- tby = elements.get(i).transform[5] + elements.get(i).rotatedBounds.getMaxY();\r
- if (tlx < lx)\r
- lx = tlx;\r
- if (trx > rx)\r
- rx = trx;\r
- if (tty < ty)\r
- ty = tty;\r
- if (tby > by)\r
- by = tby;\r
-\r
- }\r
-\r
- for (AlignElement e : elements) {\r
- mx = e.transform[4];\r
- my = e.transform[5];\r
- if (side == SIDE.LEFT) {\r
- mx = lx - e.rotatedBounds.getMinX() ;\r
- } else if (side == SIDE.RIGHT) {\r
- mx = rx - e.rotatedBounds.getMaxX();\r
- } else if (side == SIDE.TOP) {\r
- my = ty - e.rotatedBounds.getMinY();\r
- } else {\r
- my = by - e.rotatedBounds.getMaxY();\r
- }\r
- DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],mx,my});\r
-\r
- }\r
-\r
- }\r
-\r
- }\r
- });\r
- }\r
-\r
- /**\r
- * Distance specified set of diagram element resources equally. Distancing\r
- * is performed on the specified side of the each element.\r
- * \r
- * <p>\r
- * Distancing requires at least three elements to work.\r
- * \r
- * @param resources diagram element resources to rotate\r
- * @param side the side of each element to use for distancing\r
- */\r
- public static void dist(final Resource resources[], final SIDE side) {\r
-\r
- if (resources.length < 3)\r
- return;\r
-\r
- SimanticsUI.getSession().asyncRequest(new WriteRequest() {\r
-\r
- @Override\r
- public void perform(WriteGraph graph) throws DatabaseException {\r
- graph.markUndoPoint();\r
- IDiagram hints = Diagram.spawnNew(DiagramClass.DEFAULT);\r
-\r
- List<AlignElement> elements = new ArrayList<AlignElement>();\r
- for (Resource r : resources) {\r
- //System.out.println(r + " " + GraphUtils.getReadableName(graph, r));\r
- AlignElement e = create(graph, hints, r);\r
- if (e != null)\r
- elements.add(e);\r
- }\r
- if (elements.size() < 3)\r
- return;\r
- switch (side) {\r
- case LEFT: {\r
- Collections.sort(elements, new XComparator());\r
- AlignElement left = elements.get(0);\r
- AlignElement right = elements.get(elements.size() - 1);\r
-\r
- double leftEdge = left.transform[4] + left.rotatedBounds.getMinX();\r
- double rightEdge = right.transform[4] + right.rotatedBounds.getMinX();\r
-\r
- double totalDist = rightEdge - leftEdge;\r
- double dist = totalDist / (elements.size() - 1);\r
- double d = leftEdge;\r
-\r
- for (int i = 1; i < elements.size() -1; i++) {\r
- d += dist;\r
- AlignElement e = elements.get(i);\r
-\r
- double mx = d - e.rotatedBounds.getMinX();\r
-\r
- DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],mx,e.transform[5]});\r
- }\r
-\r
- break;\r
- }\r
- case VERT: {\r
- Collections.sort(elements, new XComparator());\r
- AlignElement left = elements.get(0);\r
- AlignElement right = elements.get(elements.size() - 1);\r
-\r
- double leftEdge = left.transform[4];\r
- double rightEdge = right.transform[4];\r
-\r
- double totalDist = rightEdge - leftEdge;\r
- double dist = totalDist / (elements.size() - 1);\r
- double d = leftEdge;\r
-\r
- for (int i = 1; i < elements.size() -1; i++) {\r
- d += dist;\r
- AlignElement e = elements.get(i);\r
-\r
- double mx = d;\r
-\r
- DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],mx,e.transform[5]});\r
- }\r
-\r
- break;\r
- }\r
- case RIGHT:{\r
- Collections.sort(elements, new XComparator());\r
- AlignElement left = elements.get(0);\r
- AlignElement right = elements.get(elements.size() - 1);\r
-\r
- double leftEdge = left.transform[4] + left.rotatedBounds.getMaxX();\r
- double rightEdge = right.transform[4] + right.rotatedBounds.getMaxX();\r
-\r
- double totalDist = rightEdge - leftEdge;\r
- double dist = totalDist / (elements.size() - 1);\r
- double d = leftEdge;\r
-\r
- for (int i = 1; i < elements.size() -1; i++) {\r
- d += dist;\r
- AlignElement e = elements.get(i);\r
-\r
- double mx = d - e.rotatedBounds.getMaxX();\r
-\r
- DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],mx,e.transform[5]});\r
- }\r
- break;\r
- }\r
- case VERT_BTW:{\r
- Collections.sort(elements, new XComparator());\r
- AlignElement left = elements.get(0);\r
- AlignElement right = elements.get(elements.size() - 1);\r
-\r
- double leftEdge = left.transform[4] + left.rotatedBounds.getMaxX();\r
- double rightEdge = right.transform[4] + right.rotatedBounds.getMinX();\r
-\r
- double totalDist = rightEdge - leftEdge;\r
- double totalElementSize = 0;\r
- for (int i = 1; i < elements.size() -1; i++) {\r
- totalElementSize += elements.get(i).rotatedBounds.getWidth();\r
- }\r
- double totalAvail = totalDist - totalElementSize;\r
- double dist = totalAvail / (elements.size() - 1);\r
- double d = leftEdge;\r
-\r
- for (int i = 1; i < elements.size() -1; i++) {\r
- d += dist;\r
- AlignElement e = elements.get(i);\r
-\r
- double mx = d - e.rotatedBounds.getMinX();\r
- d += e.bounds.getWidth();\r
-\r
- DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],mx,e.transform[5]});\r
- }\r
-\r
- break;\r
- }\r
- case BOTTOM: {\r
- Collections.sort(elements, new YComparator());\r
- AlignElement top = elements.get(0);\r
- AlignElement bottom = elements.get(elements.size() - 1);\r
-\r
- double topEdge = top.transform[5] + top.rotatedBounds.getMaxY();\r
- double bottomEdge = bottom.transform[5] + bottom.rotatedBounds.getMaxY();\r
-\r
- double totalDist = bottomEdge - topEdge;\r
- double dist = totalDist / (elements.size() - 1);\r
- double d = topEdge;\r
-\r
- for (int i = 1; i < elements.size() -1; i++) {\r
- d += dist;\r
- AlignElement e = elements.get(i);\r
-\r
- double my = d - e.rotatedBounds.getMaxY();\r
-\r
- DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],e.transform[4],my});\r
- }\r
-\r
- break;\r
- }\r
- case TOP: {\r
- Collections.sort(elements, new YComparator());\r
- AlignElement top = elements.get(0);\r
- AlignElement bottom = elements.get(elements.size() - 1);\r
-\r
- double topEdge = top.transform[5] + top.rotatedBounds.getMinY();\r
- double bottomEdge = bottom.transform[5] + bottom.rotatedBounds.getMinY();\r
-\r
- double totalDist = bottomEdge - topEdge;\r
- double dist = totalDist / (elements.size() - 1);\r
- double d = topEdge;\r
-\r
- for (int i = 1; i < elements.size() -1; i++) {\r
- d += dist;\r
- AlignElement e = elements.get(i);\r
-\r
- double my = d - e.rotatedBounds.getMinY();\r
-\r
- DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],e.transform[4],my});\r
- }\r
-\r
- break;\r
- }\r
- case HORIZ: {\r
- Collections.sort(elements, new YComparator());\r
- AlignElement top = elements.get(0);\r
- AlignElement bottom = elements.get(elements.size() - 1);\r
-\r
- double topEdge = top.transform[5];\r
- double bottomEdge = bottom.transform[5];\r
-\r
- double totalDist = bottomEdge - topEdge;\r
- double dist = totalDist / (elements.size() - 1);\r
- double d = topEdge;\r
-\r
- for (int i = 1; i < elements.size() -1; i++) {\r
- d += dist;\r
- AlignElement e = elements.get(i);\r
-\r
- double my = d;\r
-\r
- DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],e.transform[4],my});\r
- }\r
-\r
- break;\r
- }\r
- case HORIZ_BTW: {\r
- Collections.sort(elements, new YComparator());\r
- AlignElement top = elements.get(0);\r
- AlignElement bottom = elements.get(elements.size() - 1);\r
-\r
- double topEdge = top.transform[5] + top.rotatedBounds.getMaxY();\r
- double bottomEdge = bottom.transform[5] + bottom.rotatedBounds.getMinY();\r
-\r
- double totalDist = bottomEdge - topEdge;\r
- double totalElementSize = 0;\r
- for (int i = 1; i < elements.size() -1; i++) {\r
- totalElementSize += elements.get(i).rotatedBounds.getHeight();\r
- }\r
- double totalAvail = totalDist - totalElementSize;\r
- double dist = totalAvail / (elements.size() - 1);\r
- double d = topEdge;\r
-\r
- for (int i = 1; i < elements.size() -1; i++) {\r
- d += dist;\r
- AlignElement e = elements.get(i);\r
-\r
- double my = d - e.rotatedBounds.getMinY();\r
- d += e.rotatedBounds.getHeight();\r
-\r
- DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],e.transform[4],my});\r
- }\r
-\r
- break;\r
- }\r
-\r
- }\r
-\r
-\r
- }\r
- });\r
-\r
- }\r
-\r
- /**\r
- * Rotate specified set of diagram element resources around the center of\r
- * mass of the specified element selection.\r
- * \r
- * @param resources diagram element resources to rotate\r
- * @param clockwise <code>true</code> to rotate 90 degrees clockwise,\r
- * <code>false</code> to rotate 90 degrees counter-clockwise\r
- */\r
- public static void rotate(final Resource resources[], final boolean clockwise) {\r
- SimanticsUI.getSession().asyncRequest(new WriteRequest() {\r
- @Override\r
- public void perform(WriteGraph graph) throws DatabaseException {\r
- graph.markUndoPoint();\r
- IDiagram hints = Diagram.spawnNew(DiagramClass.DEFAULT);\r
-\r
- DiagramResource DIA = DiagramResource.getInstance(graph);\r
- \r
- List<AlignElement> elements = new ArrayList<AlignElement>();\r
- List<Resource> connections = new ArrayList<Resource>();\r
- for (Resource r : resources) {\r
- AlignElement e = create(graph, hints, r);\r
- if (e != null)\r
- elements.add(e);\r
- else if(graph.isInstanceOf(r, DIA.RouteGraphConnection))\r
- connections.add(r);\r
- }\r
- if (elements.size() < 1)\r
- return;\r
-\r
- // Add comment to change set.\r
- CommentMetadata cm = graph.getMetadata(CommentMetadata.class);\r
- graph.addMetadata( cm.add("Rotate " + elements.size() + " elements " + (clockwise ? "clockwise" : "counter-clockwise")) );\r
-\r
- AffineTransform at = clockwise ? AffineTransform.getQuadrantRotateInstance(1)\r
- : AffineTransform.getQuadrantRotateInstance(3);\r
-\r
- if (elements.size() == 1 && connections.isEmpty()) {\r
- for (AlignElement e : elements) {\r
- AffineTransform eat = new AffineTransform(e.transform[0], e.transform[1], e.transform[2], e.transform[3], 0, 0);\r
- eat.preConcatenate(at);\r
- \r
- DiagramGraphUtil.changeTransform(graph, e.element, \r
- new double[]{eat.getScaleX(),eat.getShearY(),eat.getShearX(),eat.getScaleY(),e.transform[4],e.transform[5]});\r
- }\r
- } else {\r
- Rectangle2D selectionBounds = null;\r
- for (AlignElement e : elements) {\r
- if (selectionBounds != null) {\r
- selectionBounds.add(e.transform[4], e.transform[5]);\r
- } else {\r
- selectionBounds = new Rectangle2D.Double(e.transform[4], e.transform[5], 0, 0);\r
- }\r
- }\r
- \r
- double cx = selectionBounds.getCenterX();\r
- double cy = selectionBounds.getCenterY();\r
-\r
- for (AlignElement e : elements) {\r
- double x = e.transform[4];\r
- double y = e.transform[5]; \r
- double dx = x - cx;\r
- double dy = y - cy;\r
- Point2D r = at.transform(new Point2D.Double(dx, dy), null);\r
- double mx = r.getX() + cx;\r
- double my = r.getY() + cy;\r
- AffineTransform eat = new AffineTransform(e.transform[0], e.transform[1], e.transform[2], e.transform[3], 0, 0);\r
- eat.preConcatenate(at);\r
- \r
- DiagramGraphUtil.changeTransform(graph, e.element, \r
- new double[]{eat.getScaleX(),eat.getShearY(),eat.getShearX(),eat.getScaleY(),mx,my});\r
- }\r
- \r
- if(!connections.isEmpty()) {\r
- Command rotateConnection = Commands.get(graph, "Simantics/Diagram/rotateConnection");\r
- Resource model = graph.syncRequest(new IndexRoot(connections.get(0)));\r
- for(Resource r : connections)\r
- rotateConnection.execute(graph, model, r, cx, cy, clockwise);\r
- }\r
- }\r
- }\r
- });\r
- }\r
-\r
- /**\r
- * Flip specified set of diagram element resources around either the x or\r
- * y-axis specified by the mass center of the selection bounding box.\r
- * Each element is considered to weigh an equal amount.\r
- * \r
- * @param resources diagram element resources to flip\r
- * @param xAxis <code>true</code> to flip around x-axis, <code>false</code>\r
- * for y-axis\r
- */\r
- public static void flip(final Resource resources[], final boolean xAxis) {\r
- SimanticsUI.getSession().asyncRequest(new WriteRequest() {\r
- @Override\r
- public void perform(WriteGraph graph) throws DatabaseException {\r
- graph.markUndoPoint();\r
- IDiagram hints = Diagram.spawnNew(DiagramClass.DEFAULT);\r
- \r
- DiagramResource DIA = DiagramResource.getInstance(graph);\r
-\r
- List<AlignElement> elements = new ArrayList<AlignElement>();\r
- List<Resource> connections = new ArrayList<Resource>();\r
- for (Resource r : resources) {\r
- AlignElement e = create(graph, hints, r);\r
- if (e != null)\r
- elements.add(e);\r
- else if(graph.isInstanceOf(r, DIA.RouteGraphConnection))\r
- connections.add(r);\r
- }\r
- if (elements.size() < 1)\r
- return;\r
-\r
- // Add comment to change set.\r
- CommentMetadata cm = graph.getMetadata(CommentMetadata.class);\r
- graph.addMetadata( cm.add("Flip " + elements.size() + " elements " + (xAxis ? "vertically" : "horizontally")) );\r
-\r
- if (elements.size() == 1 && connections.isEmpty()) {\r
- for (AlignElement e : elements) {\r
- if (xAxis) {\r
- AffineTransform at = new AffineTransform(e.transform);\r
- AffineTransform at2 = AffineTransform.getScaleInstance(1, -1);\r
- at.preConcatenate(at2);\r
- DiagramGraphUtil.changeTransform(graph, e.element, new double[]{at.getScaleX(),at.getShearY(),at.getShearX(),at.getScaleY(),e.transform[4],e.transform[5]});\r
- } else {\r
- AffineTransform at = new AffineTransform(e.transform);\r
- AffineTransform at2 = AffineTransform.getScaleInstance(-1, 1);\r
- at.preConcatenate(at2);\r
- DiagramGraphUtil.changeTransform(graph, e.element, new double[]{at.getScaleX(),at.getShearY(),at.getShearX(),at.getScaleY(),e.transform[4],e.transform[5]});\r
- }\r
- }\r
- } else {\r
-\r
- Rectangle2D selectionBounds = null;\r
- for (AlignElement e : elements) {\r
- if (selectionBounds != null) {\r
- selectionBounds.add(e.transform[4], e.transform[5]);\r
- } else {\r
- selectionBounds = new Rectangle2D.Double(e.transform[4], e.transform[5], 0, 0);\r
- }\r
- }\r
-\r
- for (AlignElement e : elements) {\r
- if (xAxis) {\r
- double y = e.transform[5];\r
- double cy = selectionBounds.getCenterY();\r
- double my = cy + cy - y;\r
- AffineTransform at = new AffineTransform(e.transform);\r
- AffineTransform at2 = AffineTransform.getScaleInstance(1, -1);\r
- at.preConcatenate(at2);\r
- DiagramGraphUtil.changeTransform(graph, e.element, new double[]{at.getScaleX(),at.getShearY(),at.getShearX(),at.getScaleY(),e.transform[4],my});\r
- } else {\r
- double x = e.transform[4];\r
- double cx = selectionBounds.getCenterX();\r
- double mx = cx + cx - x;\r
- \r
- AffineTransform at = new AffineTransform(e.transform);\r
- AffineTransform at2 = AffineTransform.getScaleInstance(-1, 1);\r
- at.preConcatenate(at2);\r
- DiagramGraphUtil.changeTransform(graph, e.element, new double[]{at.getScaleX(),at.getShearY(),at.getShearX(),at.getScaleY(),mx,e.transform[5]});\r
- }\r
- }\r
- \r
- if(!connections.isEmpty()) {\r
- Command flipConnection = Commands.get(graph, "Simantics/Diagram/flipConnection");\r
- Resource model = graph.syncRequest(new IndexRoot(connections.get(0)));\r
- for(Resource r : connections)\r
- flipConnection.execute(graph, model, r, xAxis, \r
- xAxis ? selectionBounds.getCenterY() \r
- : selectionBounds.getCenterX());\r
- }\r
- }\r
-\r
- }\r
- });\r
- }\r
-\r
- private static AlignElement create(ReadGraph graph, IDiagram hints, Resource r) throws ManyObjectsForFunctionalRelationException, NoSingleResultException, ServiceException, DatabaseException {\r
- DiagramResource dr = DiagramResource.getInstance(graph);\r
-\r
- if (graph.isInstanceOf(r, dr.Element) && !graph.isInstanceOf(r, dr.Connection) /*&& !graph.isInstanceOf(r, dr.Monitor)*/) {\r
- double transform[] = graph.getPossibleRelatedValue(r, dr.HasTransform);\r
- ElementClass ec = graph.syncRequest(DiagramRequests.getElementClass(graph.getSingleType(r, dr.Element), hints));\r
- IElement e = Element.spawnNew(ec);\r
- Rectangle2D bounds = ElementUtils.getElementBounds(e);\r
- if (transform != null && bounds != null) {\r
- return new AlignElement(r, transform, bounds);\r
- }\r
- }\r
- return null;\r
- }\r
-\r
-\r
- private static class AlignElement {\r
- public Resource element;\r
- public double[] transform;\r
- public Rectangle2D bounds;\r
- public Rectangle2D rotatedBounds;\r
-// public Rectangle2D transformedBounds;\r
-\r
- public AlignElement(Resource element, double[] transform, Rectangle2D bounds) {\r
- this.element = element;\r
- this.transform = transform;\r
- this.bounds = bounds;\r
-// this.transformedBounds = getBounds();\r
- this.rotatedBounds = getRotatedBounds();\r
- }\r
-\r
-// public Rectangle2D getBounds() {\r
-// AffineTransform at = new AffineTransform(transform[0], transform[1], transform[2], transform[3], transform[4], transform[5]);\r
-// return GeometryUtils.transformShape(bounds, at).getBounds2D();\r
-// }\r
-\r
- public Rectangle2D getRotatedBounds() {\r
- AffineTransform at = new AffineTransform(transform[0], transform[1], transform[2], transform[3], 0.0, 0.0);\r
- return GeometryUtils.transformShape(bounds, at).getBounds2D();\r
- }\r
-\r
- public double getDeterminant() {\r
- return transform[0] * transform[3] - transform[1] * transform[2];\r
- }\r
-\r
- }\r
-\r
- private static class XComparator implements Comparator<AlignElement> {\r
- @Override\r
- public int compare(AlignElement o1, AlignElement o2) {\r
- if (o1.transform[4] < o2.transform[4])\r
- return -1;\r
- if (o1.transform[4] > o2.transform[4])\r
- return 1;\r
- return 0;\r
-\r
- }\r
- }\r
-\r
- private static class YComparator implements Comparator<AlignElement> {\r
- @Override\r
- public int compare(AlignElement o1, AlignElement o2) {\r
- if (o1.transform[5] < o2.transform[5])\r
- return -1;\r
- if (o1.transform[5] > o2.transform[5])\r
- return 1;\r
- return 0;\r
-\r
- }\r
- }\r
-\r
- /**\r
- * Set 2D affine transforms for the listed diagram element resources.\r
- * \r
- * @param elements diagram element resources to set transforms for\r
- * @param transforms transforms for each element\r
- */\r
- public static Write setTransformRequest(final Collection<TransformedObject> elements)\r
- {\r
- return new WriteRequest() {\r
- @Override\r
- public void perform(WriteGraph graph) throws DatabaseException {\r
- for (TransformedObject element : elements)\r
- DiagramGraphUtil.changeTransform(graph, element.element, element.transform);\r
- }\r
- };\r
- }\r
-\r
- /**\r
- * Set 2D affine transforms for the listed diagram element resources.\r
- * \r
- * @param undoContext the database undo context to use for the returned\r
- * request\r
- * @param elements diagram element resources to set transforms for\r
- * @param transforms transforms for each element\r
- * @param preConcatenate <code>true</code> to pre-concatenate the\r
- * transforms, <code>false</code> to concatenate\r
- */\r
- public static Write concatenateTransformRequest(\r
- UndoContext undoContext,\r
- final Collection<TransformedObject> elements,\r
- final boolean preConcatenate)\r
- {\r
- return new WriteRequest() {\r
- @Override\r
- public void perform(WriteGraph graph) throws DatabaseException {\r
- for (TransformedObject element : elements) {\r
- AffineTransform at = DiagramGraphUtil.getTransform(graph, element.element);\r
- if (preConcatenate)\r
- at.preConcatenate(element.transform);\r
- else\r
- at.concatenate(element.transform);\r
- DiagramGraphUtil.setTransform(graph, element.element, at);\r
- }\r
- }\r
- };\r
- }\r
-\r
- public static class TransformedObject {\r
- public final Resource element;\r
- public final AffineTransform transform;\r
-\r
- public TransformedObject(Resource element) {\r
- this.element = element;\r
- this.transform = new AffineTransform();\r
- }\r
- public TransformedObject(Resource element, AffineTransform transform) {\r
- this.element = element;\r
- this.transform = transform;\r
- }\r
- }\r
-\r
-}\r
+package org.simantics.diagram.elements;
+
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.simantics.Simantics;
+import org.simantics.db.ReadGraph;
+import org.simantics.db.Resource;
+import org.simantics.db.Statement;
+import org.simantics.db.UndoContext;
+import org.simantics.db.WriteGraph;
+import org.simantics.db.common.CommentMetadata;
+import org.simantics.db.common.request.IndexRoot;
+import org.simantics.db.common.request.WriteRequest;
+import org.simantics.db.common.utils.NameUtils;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.db.exception.ManyObjectsForFunctionalRelationException;
+import org.simantics.db.exception.NoSingleResultException;
+import org.simantics.db.exception.ServiceException;
+import org.simantics.db.request.Write;
+import org.simantics.diagram.content.TerminalMap;
+import org.simantics.diagram.query.DiagramRequests;
+import org.simantics.diagram.stubs.DiagramResource;
+import org.simantics.diagram.synchronization.graph.BasicResources;
+import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;
+import org.simantics.g2d.diagram.DiagramClass;
+import org.simantics.g2d.diagram.IDiagram;
+import org.simantics.g2d.diagram.impl.Diagram;
+import org.simantics.g2d.element.ElementClass;
+import org.simantics.g2d.element.ElementUtils;
+import org.simantics.g2d.element.IElement;
+import org.simantics.g2d.element.impl.Element;
+import org.simantics.g2d.utils.GeometryUtils;
+import org.simantics.scl.commands.Command;
+import org.simantics.scl.commands.Commands;
+import org.simantics.structural2.queries.Terminal;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import gnu.trove.set.hash.THashSet;
+
+/**
+ * Tools to align, rotate, and flip diagram elements.
+ *
+ * TODO : We need to add capability hints to elements to prevent rotating and mirroring elements that do not support that.
+ * Example: mirrored text does not make any sense.
+ *
+ *
+ * @author Marko Luukkainen <marko.luukkainen@vtt.fi> (implementation)
+ * @author Tuukka Lehtonen (documentation)
+ */
+public final class ElementTransforms {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(ElementTransforms.class);
+
+ public static enum SIDE { LEFT, RIGHT, TOP, BOTTOM, VERT, HORIZ, VERT_BTW, HORIZ_BTW };
+
+ /**
+ * Align the specified set of diagram element resources in line with each
+ * other calculated by the specified side.
+ *
+ * <p>
+ * Alignment requires at least two elements to do anything.
+ *
+ * @param resources diagram element resources to rotate
+ * @param side the side of each element to use for distancing. Does not support between aligments.
+ */
+ public static void align(final Resource resources[], final SIDE side) {
+ if (resources.length < 2)
+ return;
+ if (side == SIDE.HORIZ_BTW || side == SIDE.VERT_BTW )
+ return;
+
+ Simantics.getSession().asyncRequest(new WriteRequest() {
+
+ @Override
+ public void perform(WriteGraph graph) throws DatabaseException {
+ graph.markUndoPoint();
+ IDiagram hints = Diagram.spawnNew(DiagramClass.DEFAULT);
+
+ List<AlignElement> elements = new ArrayList<AlignElement>();
+ for (Resource r : resources) {
+ AlignElement e = create(graph, hints, r);
+ if (e != null)
+ elements.add(e);
+ }
+ if (elements.size() < 2)
+ return;
+ double mx = 0;
+ double my = 0;
+ for (AlignElement e : elements) {
+ mx += e.transform[4];
+ my += e.transform[5];
+ }
+ mx /= elements.size();
+ my /= elements.size();
+
+ // prevent moving symbols into the same position
+ int count = 0;
+ for (AlignElement e : elements) {
+ if (side == SIDE.VERT || side == SIDE.LEFT || side == SIDE.RIGHT) {
+ if (Math.abs(e.transform[5] - my) < 0.1) {
+ count++;
+ }
+ } else {
+ if (Math.abs(e.transform[4] - mx) < 0.1) {
+ count++;
+ }
+ }
+ }
+ if (count > 1)
+ return;
+
+ if (side == SIDE.HORIZ || side == SIDE.VERT) {
+
+
+ for (AlignElement e : elements) {
+
+ if (side == SIDE.VERT)
+ DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],mx,e.transform[5]});
+ else if (side == SIDE.HORIZ) {
+ DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],e.transform[4],my});
+ }
+ }
+ } else {
+ double lx, rx;
+ double ty, by;
+ lx = elements.get(0).transform[4] + elements.get(0).rotatedBounds.getMinX();
+ rx = elements.get(0).transform[4] + elements.get(0).rotatedBounds.getMaxX();
+
+ ty = elements.get(0).transform[5] + elements.get(0).rotatedBounds.getMinY();
+ by = elements.get(0).transform[5] + elements.get(0).rotatedBounds.getMaxY();
+
+ for (int i = 1; i < elements.size(); i++) {
+ double tlx, trx;
+ double tty, tby;
+ tlx = elements.get(i).transform[4] + elements.get(i).rotatedBounds.getMinX();
+ trx = elements.get(i).transform[4] + elements.get(i).rotatedBounds.getMaxX();
+
+ tty = elements.get(i).transform[5] + elements.get(i).rotatedBounds.getMinY();
+ tby = elements.get(i).transform[5] + elements.get(i).rotatedBounds.getMaxY();
+ if (tlx < lx)
+ lx = tlx;
+ if (trx > rx)
+ rx = trx;
+ if (tty < ty)
+ ty = tty;
+ if (tby > by)
+ by = tby;
+
+ }
+
+ for (AlignElement e : elements) {
+ mx = e.transform[4];
+ my = e.transform[5];
+ if (side == SIDE.LEFT) {
+ mx = lx - e.rotatedBounds.getMinX() ;
+ } else if (side == SIDE.RIGHT) {
+ mx = rx - e.rotatedBounds.getMaxX();
+ } else if (side == SIDE.TOP) {
+ my = ty - e.rotatedBounds.getMinY();
+ } else {
+ my = by - e.rotatedBounds.getMaxY();
+ }
+ DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],mx,my});
+
+ }
+
+ }
+
+ }
+ });
+ }
+
+ /**
+ * Distance specified set of diagram element resources equally. Distancing
+ * is performed on the specified side of the each element.
+ *
+ * <p>
+ * Distancing requires at least three elements to work.
+ *
+ * @param resources diagram element resources to rotate
+ * @param side the side of each element to use for distancing
+ */
+ public static void dist(final Resource resources[], final SIDE side) {
+
+ if (resources.length < 3)
+ return;
+
+ Simantics.getSession().asyncRequest(new WriteRequest() {
+
+ @Override
+ public void perform(WriteGraph graph) throws DatabaseException {
+ graph.markUndoPoint();
+ IDiagram hints = Diagram.spawnNew(DiagramClass.DEFAULT);
+
+ List<AlignElement> elements = new ArrayList<AlignElement>();
+ for (Resource r : resources) {
+ //System.out.println(r + " " + GraphUtils.getReadableName(graph, r));
+ AlignElement e = create(graph, hints, r);
+ if (e != null)
+ elements.add(e);
+ }
+ if (elements.size() < 3)
+ return;
+ switch (side) {
+ case LEFT: {
+ Collections.sort(elements, new XComparator());
+ AlignElement left = elements.get(0);
+ AlignElement right = elements.get(elements.size() - 1);
+
+ double leftEdge = left.transform[4] + left.rotatedBounds.getMinX();
+ double rightEdge = right.transform[4] + right.rotatedBounds.getMinX();
+
+ double totalDist = rightEdge - leftEdge;
+ double dist = totalDist / (elements.size() - 1);
+ double d = leftEdge;
+
+ for (int i = 1; i < elements.size() -1; i++) {
+ d += dist;
+ AlignElement e = elements.get(i);
+
+ double mx = d - e.rotatedBounds.getMinX();
+
+ DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],mx,e.transform[5]});
+ }
+
+ break;
+ }
+ case VERT: {
+ Collections.sort(elements, new XComparator());
+ AlignElement left = elements.get(0);
+ AlignElement right = elements.get(elements.size() - 1);
+
+ double leftEdge = left.transform[4];
+ double rightEdge = right.transform[4];
+
+ double totalDist = rightEdge - leftEdge;
+ double dist = totalDist / (elements.size() - 1);
+ double d = leftEdge;
+
+ for (int i = 1; i < elements.size() -1; i++) {
+ d += dist;
+ AlignElement e = elements.get(i);
+
+ double mx = d;
+
+ DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],mx,e.transform[5]});
+ }
+
+ break;
+ }
+ case RIGHT:{
+ Collections.sort(elements, new XComparator());
+ AlignElement left = elements.get(0);
+ AlignElement right = elements.get(elements.size() - 1);
+
+ double leftEdge = left.transform[4] + left.rotatedBounds.getMaxX();
+ double rightEdge = right.transform[4] + right.rotatedBounds.getMaxX();
+
+ double totalDist = rightEdge - leftEdge;
+ double dist = totalDist / (elements.size() - 1);
+ double d = leftEdge;
+
+ for (int i = 1; i < elements.size() -1; i++) {
+ d += dist;
+ AlignElement e = elements.get(i);
+
+ double mx = d - e.rotatedBounds.getMaxX();
+
+ DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],mx,e.transform[5]});
+ }
+ break;
+ }
+ case VERT_BTW:{
+ Collections.sort(elements, new XComparator());
+ AlignElement left = elements.get(0);
+ AlignElement right = elements.get(elements.size() - 1);
+
+ double leftEdge = left.transform[4] + left.rotatedBounds.getMaxX();
+ double rightEdge = right.transform[4] + right.rotatedBounds.getMinX();
+
+ double totalDist = rightEdge - leftEdge;
+ double totalElementSize = 0;
+ for (int i = 1; i < elements.size() -1; i++) {
+ totalElementSize += elements.get(i).rotatedBounds.getWidth();
+ }
+ double totalAvail = totalDist - totalElementSize;
+ double dist = totalAvail / (elements.size() - 1);
+ double d = leftEdge;
+
+ for (int i = 1; i < elements.size() -1; i++) {
+ d += dist;
+ AlignElement e = elements.get(i);
+
+ double mx = d - e.rotatedBounds.getMinX();
+ d += e.bounds.getWidth();
+
+ DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],mx,e.transform[5]});
+ }
+
+ break;
+ }
+ case BOTTOM: {
+ Collections.sort(elements, new YComparator());
+ AlignElement top = elements.get(0);
+ AlignElement bottom = elements.get(elements.size() - 1);
+
+ double topEdge = top.transform[5] + top.rotatedBounds.getMaxY();
+ double bottomEdge = bottom.transform[5] + bottom.rotatedBounds.getMaxY();
+
+ double totalDist = bottomEdge - topEdge;
+ double dist = totalDist / (elements.size() - 1);
+ double d = topEdge;
+
+ for (int i = 1; i < elements.size() -1; i++) {
+ d += dist;
+ AlignElement e = elements.get(i);
+
+ double my = d - e.rotatedBounds.getMaxY();
+
+ DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],e.transform[4],my});
+ }
+
+ break;
+ }
+ case TOP: {
+ Collections.sort(elements, new YComparator());
+ AlignElement top = elements.get(0);
+ AlignElement bottom = elements.get(elements.size() - 1);
+
+ double topEdge = top.transform[5] + top.rotatedBounds.getMinY();
+ double bottomEdge = bottom.transform[5] + bottom.rotatedBounds.getMinY();
+
+ double totalDist = bottomEdge - topEdge;
+ double dist = totalDist / (elements.size() - 1);
+ double d = topEdge;
+
+ for (int i = 1; i < elements.size() -1; i++) {
+ d += dist;
+ AlignElement e = elements.get(i);
+
+ double my = d - e.rotatedBounds.getMinY();
+
+ DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],e.transform[4],my});
+ }
+
+ break;
+ }
+ case HORIZ: {
+ Collections.sort(elements, new YComparator());
+ AlignElement top = elements.get(0);
+ AlignElement bottom = elements.get(elements.size() - 1);
+
+ double topEdge = top.transform[5];
+ double bottomEdge = bottom.transform[5];
+
+ double totalDist = bottomEdge - topEdge;
+ double dist = totalDist / (elements.size() - 1);
+ double d = topEdge;
+
+ for (int i = 1; i < elements.size() -1; i++) {
+ d += dist;
+ AlignElement e = elements.get(i);
+
+ double my = d;
+
+ DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],e.transform[4],my});
+ }
+
+ break;
+ }
+ case HORIZ_BTW: {
+ Collections.sort(elements, new YComparator());
+ AlignElement top = elements.get(0);
+ AlignElement bottom = elements.get(elements.size() - 1);
+
+ double topEdge = top.transform[5] + top.rotatedBounds.getMaxY();
+ double bottomEdge = bottom.transform[5] + bottom.rotatedBounds.getMinY();
+
+ double totalDist = bottomEdge - topEdge;
+ double totalElementSize = 0;
+ for (int i = 1; i < elements.size() -1; i++) {
+ totalElementSize += elements.get(i).rotatedBounds.getHeight();
+ }
+ double totalAvail = totalDist - totalElementSize;
+ double dist = totalAvail / (elements.size() - 1);
+ double d = topEdge;
+
+ for (int i = 1; i < elements.size() -1; i++) {
+ d += dist;
+ AlignElement e = elements.get(i);
+
+ double my = d - e.rotatedBounds.getMinY();
+ d += e.rotatedBounds.getHeight();
+
+ DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],e.transform[4],my});
+ }
+
+ break;
+ }
+
+ }
+
+
+ }
+ });
+
+ }
+
+ /**
+ * Rotate specified set of diagram element resources around the center of
+ * mass of the specified element selection.
+ *
+ * @param resources diagram element resources to rotate
+ * @param clockwise <code>true</code> to rotate 90 degrees clockwise,
+ * <code>false</code> to rotate 90 degrees counter-clockwise
+ */
+ public static void rotate(final Resource resources[], final boolean clockwise) {
+ Simantics.getSession().asyncRequest(new WriteRequest() {
+ @Override
+ public void perform(WriteGraph graph) throws DatabaseException {
+ graph.markUndoPoint();
+ IDiagram hints = Diagram.spawnNew(DiagramClass.DEFAULT);
+
+ DiagramResource DIA = DiagramResource.getInstance(graph);
+
+ List<AlignElement> elements = new ArrayList<AlignElement>();
+ List<Resource> connections = new ArrayList<Resource>();
+ for (Resource r : resources) {
+ AlignElement e = create(graph, hints, r);
+ if (e != null)
+ elements.add(e);
+ else if(graph.isInstanceOf(r, DIA.RouteGraphConnection))
+ connections.add(r);
+ }
+ if (elements.size() < 1)
+ return;
+
+ // Add comment to change set.
+ CommentMetadata cm = graph.getMetadata(CommentMetadata.class);
+ graph.addMetadata( cm.add("Rotate " + elements.size() + " elements " + (clockwise ? "clockwise" : "counter-clockwise")) );
+
+ AffineTransform at = clockwise ? AffineTransform.getQuadrantRotateInstance(1)
+ : AffineTransform.getQuadrantRotateInstance(3);
+
+ if (elements.size() == 1 && connections.isEmpty()) {
+ for (AlignElement e : elements) {
+ AffineTransform eat = new AffineTransform(e.transform[0], e.transform[1], e.transform[2], e.transform[3], 0, 0);
+ eat.preConcatenate(at);
+
+ DiagramGraphUtil.changeTransform(graph, e.element,
+ new double[]{eat.getScaleX(),eat.getShearY(),eat.getShearX(),eat.getScaleY(),e.transform[4],e.transform[5]});
+ }
+ } else {
+ Rectangle2D selectionBounds = null;
+ for (AlignElement e : elements) {
+ if (selectionBounds != null) {
+ selectionBounds.add(e.transform[4], e.transform[5]);
+ } else {
+ selectionBounds = new Rectangle2D.Double(e.transform[4], e.transform[5], 0, 0);
+ }
+ }
+
+ double cx = selectionBounds.getCenterX();
+ double cy = selectionBounds.getCenterY();
+
+ for (AlignElement e : elements) {
+ double x = e.transform[4];
+ double y = e.transform[5];
+ double dx = x - cx;
+ double dy = y - cy;
+ Point2D r = at.transform(new Point2D.Double(dx, dy), null);
+ double mx = r.getX() + cx;
+ double my = r.getY() + cy;
+ AffineTransform eat = new AffineTransform(e.transform[0], e.transform[1], e.transform[2], e.transform[3], 0, 0);
+ eat.preConcatenate(at);
+
+ DiagramGraphUtil.changeTransform(graph, e.element,
+ new double[]{eat.getScaleX(),eat.getShearY(),eat.getShearX(),eat.getScaleY(),mx,my});
+ }
+
+ if(!connections.isEmpty()) {
+ Command rotateConnection = Commands.get(graph, "Simantics/Diagram/rotateConnection");
+ Resource model = graph.syncRequest(new IndexRoot(connections.get(0)));
+ for(Resource r : connections)
+ rotateConnection.execute(graph, model, r, cx, cy, clockwise);
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Flip specified set of diagram element resources around either the x or
+ * y-axis specified by the mass center of the selection bounding box.
+ * Each element is considered to weigh an equal amount.
+ *
+ * @param resources diagram element resources to flip
+ * @param xAxis <code>true</code> to flip around x-axis, <code>false</code>
+ * for y-axis
+ */
+ public static void flip(final Resource resources[], final boolean xAxis) {
+ Simantics.getSession().asyncRequest(new WriteRequest() {
+ @Override
+ public void perform(WriteGraph graph) throws DatabaseException {
+ graph.markUndoPoint();
+ IDiagram hints = Diagram.spawnNew(DiagramClass.DEFAULT);
+
+ DiagramResource DIA = DiagramResource.getInstance(graph);
+
+ List<AlignElement> elements = new ArrayList<AlignElement>();
+ List<Resource> connections = new ArrayList<Resource>();
+ for (Resource r : resources) {
+ AlignElement e = create(graph, hints, r);
+ if (e != null)
+ elements.add(e);
+ else if(graph.isInstanceOf(r, DIA.RouteGraphConnection))
+ connections.add(r);
+ }
+ if (elements.size() < 1)
+ return;
+
+ // Add comment to change set.
+ CommentMetadata cm = graph.getMetadata(CommentMetadata.class);
+ graph.addMetadata( cm.add("Flip " + elements.size() + " elements " + (xAxis ? "vertically" : "horizontally")) );
+
+ if (elements.size() == 1 && connections.isEmpty()) {
+ for (AlignElement e : elements) {
+ if (xAxis) {
+ AffineTransform at = new AffineTransform(e.transform);
+ AffineTransform at2 = AffineTransform.getScaleInstance(1, -1);
+ at.preConcatenate(at2);
+ DiagramGraphUtil.changeTransform(graph, e.element, new double[]{at.getScaleX(),at.getShearY(),at.getShearX(),at.getScaleY(),e.transform[4],e.transform[5]});
+ } else {
+ AffineTransform at = new AffineTransform(e.transform);
+ AffineTransform at2 = AffineTransform.getScaleInstance(-1, 1);
+ at.preConcatenate(at2);
+ DiagramGraphUtil.changeTransform(graph, e.element, new double[]{at.getScaleX(),at.getShearY(),at.getShearX(),at.getScaleY(),e.transform[4],e.transform[5]});
+ }
+ }
+ } else {
+
+ Rectangle2D selectionBounds = null;
+ for (AlignElement e : elements) {
+ if (selectionBounds != null) {
+ selectionBounds.add(e.transform[4], e.transform[5]);
+ } else {
+ selectionBounds = new Rectangle2D.Double(e.transform[4], e.transform[5], 0, 0);
+ }
+ }
+
+ for (AlignElement e : elements) {
+ if (xAxis) {
+ double y = e.transform[5];
+ double cy = selectionBounds.getCenterY();
+ double my = cy + cy - y;
+ AffineTransform at = new AffineTransform(e.transform);
+ AffineTransform at2 = AffineTransform.getScaleInstance(1, -1);
+ at.preConcatenate(at2);
+ DiagramGraphUtil.changeTransform(graph, e.element, new double[]{at.getScaleX(),at.getShearY(),at.getShearX(),at.getScaleY(),e.transform[4],my});
+ } else {
+ double x = e.transform[4];
+ double cx = selectionBounds.getCenterX();
+ double mx = cx + cx - x;
+
+ AffineTransform at = new AffineTransform(e.transform);
+ AffineTransform at2 = AffineTransform.getScaleInstance(-1, 1);
+ at.preConcatenate(at2);
+ DiagramGraphUtil.changeTransform(graph, e.element, new double[]{at.getScaleX(),at.getShearY(),at.getShearX(),at.getScaleY(),mx,e.transform[5]});
+ }
+ }
+
+ if(!connections.isEmpty()) {
+ Command flipConnection = Commands.get(graph, "Simantics/Diagram/flipConnection");
+ Resource model = graph.syncRequest(new IndexRoot(connections.get(0)));
+ for(Resource r : connections)
+ flipConnection.execute(graph, model, r, xAxis,
+ xAxis ? selectionBounds.getCenterY()
+ : selectionBounds.getCenterX());
+ }
+ }
+
+ }
+ });
+ }
+
+ private static AlignElement create(ReadGraph graph, IDiagram hints, Resource r) throws ManyObjectsForFunctionalRelationException, NoSingleResultException, ServiceException, DatabaseException {
+ DiagramResource dr = DiagramResource.getInstance(graph);
+
+ if (graph.isInstanceOf(r, dr.Element) && !graph.isInstanceOf(r, dr.Connection) /*&& !graph.isInstanceOf(r, dr.Monitor)*/) {
+ double transform[] = graph.getPossibleRelatedValue(r, dr.HasTransform);
+ ElementClass ec = graph.syncRequest(DiagramRequests.getElementClass(graph.getSingleType(r, dr.Element), hints));
+ IElement e = Element.spawnNew(ec);
+ Rectangle2D bounds = ElementUtils.getElementBounds(e);
+ if (transform != null && bounds != null) {
+ return new AlignElement(r, transform, bounds);
+ }
+ }
+ return null;
+ }
+
+
+ private static class AlignElement {
+ public Resource element;
+ public double[] transform;
+ public Rectangle2D bounds;
+ public Rectangle2D rotatedBounds;
+// public Rectangle2D transformedBounds;
+
+ public AlignElement(Resource element, double[] transform, Rectangle2D bounds) {
+ this.element = element;
+ this.transform = transform;
+ this.bounds = bounds;
+// this.transformedBounds = getBounds();
+ this.rotatedBounds = getRotatedBounds();
+ }
+
+// public Rectangle2D getBounds() {
+// AffineTransform at = new AffineTransform(transform[0], transform[1], transform[2], transform[3], transform[4], transform[5]);
+// return GeometryUtils.transformShape(bounds, at).getBounds2D();
+// }
+
+ public Rectangle2D getRotatedBounds() {
+ AffineTransform at = new AffineTransform(transform[0], transform[1], transform[2], transform[3], 0.0, 0.0);
+ return GeometryUtils.transformShape(bounds, at).getBounds2D();
+ }
+
+ @SuppressWarnings("unused")
+ public double getDeterminant() {
+ return transform[0] * transform[3] - transform[1] * transform[2];
+ }
+
+ }
+
+ private static class XComparator implements Comparator<AlignElement> {
+ @Override
+ public int compare(AlignElement o1, AlignElement o2) {
+ if (o1.transform[4] < o2.transform[4])
+ return -1;
+ if (o1.transform[4] > o2.transform[4])
+ return 1;
+ return 0;
+
+ }
+ }
+
+ private static class YComparator implements Comparator<AlignElement> {
+ @Override
+ public int compare(AlignElement o1, AlignElement o2) {
+ if (o1.transform[5] < o2.transform[5])
+ return -1;
+ if (o1.transform[5] > o2.transform[5])
+ return 1;
+ return 0;
+
+ }
+ }
+
+ /**
+ * Set 2D affine transforms for the listed diagram element resources.
+ *
+ * @param elements diagram element resources to set transforms for
+ * @param transforms transforms for each element
+ */
+ public static Write setTransformRequest(final Collection<TransformedObject> elements)
+ {
+ return new WriteRequest() {
+ @Override
+ public void perform(WriteGraph graph) throws DatabaseException {
+ for (TransformedObject element : elements)
+ DiagramGraphUtil.changeTransform(graph, element.element, element.transform);
+ }
+ };
+ }
+
+ /**
+ * Set 2D affine transforms for the listed diagram element resources.
+ *
+ * @param undoContext the database undo context to use for the returned
+ * request
+ * @param elements diagram element resources to set transforms for
+ * @param transforms transforms for each element
+ * @param preConcatenate <code>true</code> to pre-concatenate the
+ * transforms, <code>false</code> to concatenate
+ */
+ public static Write concatenateTransformRequest(
+ UndoContext undoContext,
+ final Collection<TransformedObject> elements,
+ final boolean preConcatenate)
+ {
+ return new WriteRequest() {
+ @Override
+ public void perform(WriteGraph graph) throws DatabaseException {
+ for (TransformedObject element : elements) {
+ AffineTransform at = DiagramGraphUtil.getTransform(graph, element.element);
+ if (preConcatenate)
+ at.preConcatenate(element.transform);
+ else
+ at.concatenate(element.transform);
+ DiagramGraphUtil.setTransform(graph, element.element, at);
+ }
+ }
+ };
+ }
+
+ public static class TransformedObject {
+ public final Resource element;
+ public final AffineTransform transform;
+
+ public TransformedObject(Resource element) {
+ this.element = element;
+ this.transform = new AffineTransform();
+ }
+ public TransformedObject(Resource element, AffineTransform transform) {
+ this.element = element;
+ this.transform = transform;
+ }
+ }
+
+ // ------------------------------------------------------------------------
+
+ private interface TerminalFunction {
+ /**
+ * @param terminal
+ * @param rotationTheta in radians
+ * @return
+ */
+ AffineTransform transform(Resource connectionPoint, double rotationTheta);
+ default Point2D position(Resource connectionPoint, double rotationTheta) {
+ AffineTransform tr = transform(connectionPoint, rotationTheta);
+ return new Point2D.Double(tr.getTranslateX(), tr.getTranslateY());
+ }
+ Resource toConnectionPoint(Resource terminal);
+ Resource toTerminal(Resource connectionPoint);
+ Set<Resource> connectionPoints();
+ Set<Resource> terminals();
+ }
+
+ private static TerminalFunction terminalPositionFunction(ReadGraph graph, boolean ignoreElementRotation, Resource element) throws DatabaseException {
+ AffineTransform transform = DiagramGraphUtil.getAffineTransform(graph, element);
+ if (ignoreElementRotation) {
+ AffineTransform noRotation = AffineTransform.getTranslateInstance(transform.getTranslateX(), transform.getTranslateY());
+ Point2D scale = org.simantics.scenegraph.utils.GeometryUtils.getScale2D(transform);
+ noRotation.scale(scale.getX(), scale.getY());
+ transform = noRotation;
+ }
+ AffineTransform elementTransform = transform;
+
+ TerminalMap elementTerminals = DiagramGraphUtil.getElementTerminals(graph, element);
+ Map<Resource, AffineTransform> terminalTransforms = new HashMap<>();
+ for (Resource terminal : elementTerminals.getTerminals())
+ terminalTransforms.put(elementTerminals.getConnectionPoint(terminal), DiagramGraphUtil.getAffineTransform(graph, terminal));
+
+ return new TerminalFunction() {
+ @Override
+ public AffineTransform transform(Resource connectionPoint, double rotationTheta) {
+ AffineTransform tr = new AffineTransform(elementTransform);
+ AffineTransform terminalTr = terminalTransforms.get(connectionPoint);
+ if (rotationTheta != 0)
+ tr.rotate(rotationTheta);
+ if (terminalTr != null)
+ tr.concatenate(terminalTr);
+ return tr;
+ }
+ @Override
+ public Resource toConnectionPoint(Resource terminal) {
+ return elementTerminals.getConnectionPoint(terminal);
+ }
+ @Override
+ public Resource toTerminal(Resource connectionPoint) {
+ return elementTerminals.getTerminal(connectionPoint);
+ }
+ @Override
+ public Set<Resource> connectionPoints() {
+ return elementTerminals.getConnectionPoints();
+ }
+ @Override
+ public Set<Resource> terminals() {
+ return elementTerminals.getTerminals();
+ }
+ };
+ }
+
+ private static Set<Terminal> connectedTo(ReadGraph graph, Resource element, Resource connectionPoint) throws DatabaseException {
+ BasicResources BR = BasicResources.getInstance(graph);
+ Set<Terminal> result = new THashSet<>();
+ for (Resource connector : graph.getObjects(element, connectionPoint)) {
+ Resource connection = graph.getPossibleObject(connector, BR.DIA.IsConnectorOf);
+ if (connection == null)
+ continue;
+ for (Resource otherConnector : graph.getObjects(connection, BR.DIA.HasConnector)) {
+ if (!otherConnector.equals(connector)) {
+ for (Statement s : graph.getStatements(otherConnector, BR.STR.Connects)) {
+ if (!s.getObject().equals(connection)) {
+ result.add( new Terminal(s.getObject(), graph.getInverse(s.getPredicate())) );
+ }
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Rotates provided element so that its rotation is the same as the slope of a
+ * line drawn between the two other elements the element is connected to.
+ *
+ * <p>
+ * Note that this requires that the element is connected to exactly two other
+ * elements.
+ *
+ * @param graph
+ * @param element
+ * @return the chosen rotation in degrees
+ * @throws DatabaseException
+ */
+ public static double rotateToNeighborSlope(WriteGraph graph, Resource element) throws DatabaseException {
+
+ if (LOGGER.isDebugEnabled())
+ LOGGER.debug("rotateAccordingToNeighborPositions( {} )", graph.getPossibleURI(element));
+
+ TerminalFunction elementTerminals = terminalPositionFunction(graph, true, element);
+ Set<Resource> connectedComponents = new HashSet<>();
+ Map<Resource, TerminalFunction> componentTerminals = new HashMap<>();
+ Map<Resource, Terminal> connectedTerminals = new HashMap<>();
+
+ for (Resource connectionPoint : elementTerminals.connectionPoints()) {
+ Set<Terminal> connectedTo = connectedTo(graph, element, connectionPoint);
+ if (connectedTo.isEmpty())
+ continue;
+ if (connectedTo.size() > 1) {
+ if (LOGGER.isWarnEnabled())
+ LOGGER.warn("rotateToNeighbors only supports functional connection points, ignoring {}", NameUtils.getURIOrSafeNameInternal(graph, connectionPoint));
+ continue;
+ }
+
+ Terminal t = connectedTo.iterator().next();
+ TerminalFunction tf = terminalPositionFunction(graph, false, t.getComponent());
+ connectedComponents.add(t.getComponent());
+ componentTerminals.put(t.getComponent(), tf);
+ connectedTerminals.put(connectionPoint, t);
+
+ if (LOGGER.isDebugEnabled())
+ LOGGER.info("{} {} is connected to {}", NameUtils.getSafeName(graph, element), NameUtils.getSafeName(graph, connectionPoint), t.toString(graph));
+ }
+
+ if (connectedComponents.size() != 2) {
+ LOGGER.error("rotateToNeighborPositions only works for elements connected to exactly two other elements, {} is connected to {}", NameUtils.getURIOrSafeNameInternal(graph, element), connectedComponents.size());
+ return Double.NaN;
+ }
+
+ Resource[] ends = connectedComponents.toArray(Resource.NONE);
+ AffineTransform[] endTr = {
+ componentTerminals.get(ends[0]).transform(null, 0),
+ componentTerminals.get(ends[1]).transform(null, 0)
+ };
+
+ double slopeTheta = Math.atan2(endTr[1].getTranslateY() - endTr[0].getTranslateY(), endTr[1].getTranslateX() - endTr[0].getTranslateX());
+ double[] thetas = { slopeTheta, slopeTheta + Math.PI };
+ double selectedRotation = slopeTheta;
+ double minCost = Double.MAX_VALUE;
+ for (int i = 0; i < 2; ++i) {
+ double cost = 0;
+ for (Map.Entry<Resource, Terminal> e : connectedTerminals.entrySet()) {
+ Terminal t = e.getValue();
+ TerminalFunction tf = componentTerminals.get(t.getComponent());
+ Point2D otp = tf.position(t.getRelation(), 0);
+ Point2D etp = elementTerminals.position(e.getKey(), thetas[i]);
+ cost += otp.distance(etp);
+ }
+
+ if (LOGGER.isDebugEnabled())
+ LOGGER.info("total cost of theta {} is {}", Math.toDegrees(thetas[i]), cost);
+
+ if (cost < minCost) {
+ minCost = cost;
+ selectedRotation = thetas[i];
+ if (LOGGER.isDebugEnabled())
+ LOGGER.debug("new minimum cost {} found", cost);
+ }
+ }
+
+ AffineTransform newTr = elementTerminals.transform(null, 0);
+ newTr.rotate(selectedRotation);
+ DiagramGraphUtil.setTransform(graph, element, newTr);
+
+ if (LOGGER.isDebugEnabled())
+ LOGGER.debug("set rotation to {} degrees", Math.toDegrees(selectedRotation));
+
+ return Math.toDegrees(selectedRotation);
+ }
+
+}