]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.diagram/src/org/simantics/diagram/elements/ElementTransforms.java
Fixed all line endings of the repository
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / elements / ElementTransforms.java
index a9cc2d40079cf49ea82b7010f99749bb595c6a78..51d37b99ac844a1ab9ec1f05b0b6adec08514f4c 100644 (file)
-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.List;
+
+import org.simantics.db.ReadGraph;
+import org.simantics.db.Resource;
+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.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.query.DiagramRequests;
+import org.simantics.diagram.stubs.DiagramResource;
+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.ui.SimanticsUI;
+
+/**
+ * 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 {
+
+    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;
+
+        SimanticsUI.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;
+
+        SimanticsUI.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) {
+        SimanticsUI.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) {
+        SimanticsUI.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();
+        }
+
+        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;
+        }
+    }
+
+}