]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.diagram/src/org/simantics/diagram/adapter/RouteGraphUtils.java
Logger fixes after merge commit:fdbe8762
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / adapter / RouteGraphUtils.java
1 /*******************************************************************************\r
2  * Copyright (c) 2012 Association for Decentralized Information Management\r
3  * in Industry THTH ry.\r
4  * All rights reserved. This program and the accompanying materials\r
5  * are made available under the terms of the Eclipse Public License v1.0\r
6  * which accompanies this distribution, and is available at\r
7  * http://www.eclipse.org/legal/epl-v10.html\r
8  *\r
9  * Contributors:\r
10  *     VTT Technical Research Centre of Finland - initial API and implementation\r
11  *******************************************************************************/\r
12 package org.simantics.diagram.adapter;\r
13 \r
14 import gnu.trove.map.hash.THashMap;\r
15 import gnu.trove.set.hash.THashSet;\r
16 \r
17 import java.awt.Shape;\r
18 import java.awt.geom.AffineTransform;\r
19 import java.awt.geom.Rectangle2D;\r
20 import java.util.Collection;\r
21 import java.util.Collections;\r
22 import java.util.Map;\r
23 import java.util.Set;\r
24 \r
25 import org.simantics.databoard.Bindings;\r
26 import org.simantics.db.ReadGraph;\r
27 import org.simantics.db.Resource;\r
28 import org.simantics.db.Statement;\r
29 import org.simantics.db.common.procedure.adapter.TransientCacheListener;\r
30 import org.simantics.db.common.request.UnaryRead;\r
31 import org.simantics.db.common.utils.NameUtils;\r
32 import org.simantics.db.exception.DatabaseException;\r
33 import org.simantics.diagram.adapter.RouteGraphConnectionClassFactory.BackendConnection;\r
34 import org.simantics.diagram.connection.RouteGraph;\r
35 import org.simantics.diagram.connection.RouteGraphConnectionClass;\r
36 import org.simantics.diagram.connection.RouteLine;\r
37 import org.simantics.diagram.connection.RouteNode;\r
38 import org.simantics.diagram.connection.RouteTerminal;\r
39 import org.simantics.diagram.connection.rendering.arrows.ArrowLineEndStyle;\r
40 import org.simantics.diagram.connection.rendering.arrows.ILineEndStyle;\r
41 import org.simantics.diagram.connection.rendering.arrows.PlainLineEndStyle;\r
42 import org.simantics.diagram.content.EdgeResource;\r
43 import org.simantics.diagram.content.TerminalMap;\r
44 import org.simantics.diagram.query.DiagramRequests;\r
45 import org.simantics.diagram.stubs.DiagramResource;\r
46 import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;\r
47 import org.simantics.diagram.synchronization.graph.RouteGraphConnection;\r
48 import org.simantics.g2d.canvas.ICanvasContext;\r
49 import org.simantics.g2d.canvas.impl.CanvasContext;\r
50 import org.simantics.g2d.diagram.IDiagram;\r
51 import org.simantics.g2d.diagram.impl.ElementDiagram;\r
52 import org.simantics.g2d.element.ElementUtils;\r
53 import org.simantics.g2d.element.IElement;\r
54 import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;\r
55 import org.simantics.g2d.elementclass.FlagClass.Type;\r
56 import org.simantics.layer0.Layer0;\r
57 import org.simantics.scenegraph.utils.GeometryUtils;\r
58 import org.simantics.structural.stubs.StructuralResource2;\r
59 import org.simantics.structural2.modelingRules.CPTerminal;\r
60 import org.simantics.structural2.modelingRules.IAttachmentRelationMap;\r
61 import org.simantics.structural2.modelingRules.IModelingRules;\r
62 import org.simantics.utils.threads.CurrentThread;\r
63 \r
64 public class RouteGraphUtils {\r
65         \r
66         public static boolean DEBUG = false;\r
67         \r
68     public static final ILineEndStyle HEAD  = new ArrowLineEndStyle("fill 2 1 0");\r
69     public static final ILineEndStyle TAIL  = PlainLineEndStyle.INSTANCE;\r
70 \r
71     private static EdgeEnd toEdgeEnd(ReadGraph graph, Resource attachmentRelation, EdgeEnd defaultValue)\r
72             throws DatabaseException {\r
73                 DiagramResource DIA = DiagramResource.getInstance(graph);\r
74         if (graph.isSubrelationOf(attachmentRelation, DIA.HasTailConnector))\r
75             return EdgeEnd.Begin;\r
76         if (graph.isSubrelationOf(attachmentRelation, DIA.HasHeadConnector))\r
77             return EdgeEnd.End;\r
78         return defaultValue;\r
79     }\r
80     \r
81     private static Resource resolveFlagAttachment(ReadGraph graph, Resource connection, Resource flag, IModelingRules modelingRules) throws DatabaseException {\r
82                 DiagramResource DIA = DiagramResource.getInstance(graph);\r
83         Type type = resolveFlagType(graph, connection, flag, modelingRules);\r
84         if (type != null) {\r
85             switch (type) {\r
86                 case In: return DIA.HasPlainConnector;\r
87                 case Out: return DIA.HasArrowConnector;\r
88             }\r
89         }\r
90         return null;\r
91     }\r
92 \r
93     private static Type resolveFlagType(ReadGraph graph, Resource connection, Resource flag, IModelingRules modelingRules) throws DatabaseException {\r
94         return readFlagType(graph, flag);\r
95     }\r
96 \r
97     private static Type readFlagType(ReadGraph graph, Resource flag) throws DatabaseException {\r
98                 DiagramResource DIA = DiagramResource.getInstance(graph);\r
99         Resource flagType = graph.getPossibleObject(flag, DIA.HasFlagType);\r
100         Type type = DiagramGraphUtil.toFlagType(DIA, flagType);\r
101         return type;\r
102     }\r
103 \r
104 //    public static ILineEndStyle loadLineEndStyle(ReadGraph graph, Resource attachmentRelation, ILineEndStyle defaultValue)\r
105 //            throws DatabaseException {\r
106 //        ILineEndStyle style = graph.syncRequest(new LineEndStyle(attachmentRelation),\r
107 //                TransientCacheListener.<ILineEndStyle>instance());\r
108 //        return style != null ? style : defaultValue;\r
109 //    }\r
110 \r
111     /**\r
112      * A request for caching ILineEndStyle results.\r
113      */\r
114     public static class LineEndStyle extends UnaryRead<Resource, ILineEndStyle> {\r
115         public LineEndStyle(Resource attachmentRelation) {\r
116             super(attachmentRelation);\r
117         }\r
118         @Override\r
119         public ILineEndStyle perform(ReadGraph graph) throws DatabaseException {\r
120             return loadLineEndStyle0(graph, parameter);\r
121         }\r
122     }\r
123 \r
124     public static ILineEndStyle loadLineEndStyle0(ReadGraph graph, Resource attachmentRelation)\r
125             throws DatabaseException {\r
126         ILineEndStyle style = graph.getPossibleAdapter(attachmentRelation, ILineEndStyle.class);\r
127         if (style != null)\r
128             return style;\r
129         DiagramResource DIA = DiagramResource.getInstance(graph);\r
130         if (graph.isSubrelationOf(attachmentRelation, DIA.HasHeadConnector))\r
131             return HEAD;\r
132         if (graph.isSubrelationOf(attachmentRelation, DIA.HasTailConnector))\r
133             return TAIL;\r
134         return null;\r
135     }\r
136     \r
137     private static  Statement findTerminalStatement(ReadGraph graph, Resource connection, Resource connector)\r
138             throws DatabaseException {\r
139         \r
140         StructuralResource2 STR = StructuralResource2.getInstance(graph);\r
141         \r
142         for (Statement stm : graph.getStatements(connector, STR.Connects)) {\r
143             if (connection.equals(stm.getObject()))\r
144                 continue;\r
145             return stm;\r
146         }\r
147         return null;\r
148         \r
149     }\r
150 \r
151     private static  Resource getInverseAttachment(ReadGraph graph, Resource attachmentRelation)\r
152             throws DatabaseException {\r
153                 DiagramResource DIA = DiagramResource.getInstance(graph);\r
154         Resource inverse = graph.getPossibleObject(attachmentRelation, DIA.HasInverseAttachment);\r
155         if (inverse != null)\r
156             return inverse;\r
157         if (graph.isSubrelationOf(attachmentRelation, DIA.HasHeadConnector))\r
158             return DIA.HasPlainConnector;\r
159         if (graph.isSubrelationOf(attachmentRelation, DIA.HasTailConnector))\r
160             return DIA.HasArrowConnector;\r
161         return null;\r
162     }\r
163 \r
164         public static RouteGraph load(ReadGraph graph, Resource diagramRuntime, Resource connection) throws DatabaseException {\r
165 \r
166                 ICanvasContext canvas = new CanvasContext(CurrentThread.getThreadAccess());\r
167                 IDiagram diagram = new ElementDiagram(canvas);\r
168                 return load(graph, diagramRuntime, connection, canvas, diagram);\r
169 \r
170         }\r
171 \r
172     public static RouteGraph load(ReadGraph graph, Resource diagramRuntime, Resource connection, ICanvasContext canvas, IDiagram diagram) throws DatabaseException {\r
173                 \r
174                 Layer0 L0 = Layer0.getInstance(graph);\r
175                 DiagramResource DIA = DiagramResource.getInstance(graph);\r
176                 StructuralResource2 STR = StructuralResource2.getInstance(graph);\r
177                 \r
178                 Resource diagramResource = graph.getPossibleObject(connection, L0.PartOf);\r
179                 \r
180         IModelingRules modelingRules = graph.syncRequest(DiagramRequests.getModelingRules(diagramResource, null), TransientCacheListener.<IModelingRules>instance());\r
181 \r
182 //        IModelingRules modelingRules = diagram.getHint(DiagramModelHints.KEY_MODELING_RULES);\r
183 //        Resource diagramRuntime = diagram.getHint(DiagramModelHints.KEY_DIAGRAM_RUNTIME_RESOURCE);\r
184 \r
185         RouteGraph rg = new RouteGraph();\r
186 \r
187         // Default capacity should be enough for common cases.\r
188         Set<EdgeResource> links = new THashSet<EdgeResource>();\r
189         Map<Object, RouteNode> nodeByData = new THashMap<Object, RouteNode>();\r
190 \r
191         // Load all route graph interior RouteNodes: route lines and points\r
192         for (Resource interiorNode : graph.getObjects(connection, DIA.HasInteriorRouteNode)) {\r
193             if (graph.isInstanceOf(interiorNode, DIA.RouteLine)) {\r
194                 Boolean isHorizontal = graph.getRelatedValue(interiorNode, DIA.IsHorizontal, Bindings.BOOLEAN);\r
195                 Double position = graph.getRelatedValue(interiorNode, DIA.HasPosition, Bindings.DOUBLE);\r
196                 RouteLine line = rg.addLine(isHorizontal, position);\r
197                 line.setData( RouteGraphConnection.serialize(graph, interiorNode) );\r
198 \r
199                 nodeByData.put( interiorNode, line );\r
200 \r
201                 for (Resource connectedTo : graph.getObjects(interiorNode, DIA.AreConnected)) {\r
202                     links.add( new EdgeResource(interiorNode, connectedTo) );\r
203                 }\r
204             } else if (graph.isInstanceOf(interiorNode, DIA.RoutePoint)) {\r
205                 // Not supported yet. Ignore.\r
206             }\r
207         }\r
208 \r
209         Rectangle2D bounds = new Rectangle2D.Double();\r
210         Map<Resource, Resource> connectorToModeledAttachment = null;\r
211 \r
212         // Primarily the loader will believe what modeling rules say about\r
213         // connector attachment relations.\r
214         // \r
215         // If modeling rules decide nothing, then we simply believe what the\r
216         // the attachment relations in the graph say.\r
217         // \r
218         // Special case 1: connection with two (2) terminals\r
219         // If the attachment of one of two terminals is decided by modeling\r
220         // rules, the other attachment shall be the opposite of the decided\r
221         // attachment (see forcedAttachmentRelation below).\r
222         // \r
223         // Special case 2: connected to a flag\r
224         // If the attached element is a flag and modeling rules say nothing\r
225         // about it, believe the direction stated by the flag type.\r
226 \r
227         Collection<Statement> toConnectorStatements = graph.getStatements(connection, DIA.HasConnector);\r
228         int terminalCount = 0;\r
229 \r
230         // See if modeling rules judge any of the connection terminal attachments.\r
231         if (modelingRules != null) {\r
232             for (Statement toConnector : toConnectorStatements) {\r
233                 Resource connector = toConnector.getObject();\r
234 \r
235                 Statement terminalStm = findTerminalStatement(graph, connection, connector);\r
236                 if (terminalStm == null)\r
237                     // Ignore broken connector: attached to the connection but not to any terminal.\r
238                     continue;\r
239 \r
240                 Resource terminalElement = terminalStm.getObject();\r
241                 Resource connectionRelation = graph.getPossibleInverse(terminalStm.getPredicate());\r
242                 if (connectionRelation == null)\r
243                     continue;\r
244 \r
245                 ++terminalCount;\r
246 \r
247                 IAttachmentRelationMap map = modelingRules.getAttachmentRelations(graph, connection);\r
248                 Resource attachment = map.get(graph, new CPTerminal(terminalElement, connectionRelation));\r
249                 if (attachment != null) {\r
250                     // Primary: believe modeling rules\r
251                     if (connectorToModeledAttachment == null)\r
252                         connectorToModeledAttachment = new THashMap<Resource, Resource>(toConnectorStatements.size());\r
253                     connectorToModeledAttachment.put(connector, attachment);\r
254                     if (DEBUG)\r
255                         System.out.println("modeling rules decided attachment: " + NameUtils.getSafeName(graph, attachment, true) + " for (" + NameUtils.toString(graph, toConnector, true) + ") & (" + NameUtils.toString(graph, terminalStm, true) + ")");\r
256                 } else if (graph.isInstanceOf(terminalElement, DIA.Flag)) {\r
257                     // Secondary: believe flag type\r
258                     attachment = resolveFlagAttachment(graph, connection, terminalElement, modelingRules);\r
259                     if (attachment != null) {\r
260                         if (connectorToModeledAttachment == null)\r
261                             connectorToModeledAttachment = new THashMap<Resource, Resource>(toConnectorStatements.size());\r
262                         connectorToModeledAttachment.put(connector, attachment);\r
263                         if (DEBUG)\r
264                             System.out.println("flag type decided attachment: " + NameUtils.getSafeName(graph, attachment, true) + " for (" + NameUtils.toString(graph, toConnector, true) + ") & (" + NameUtils.toString(graph, terminalStm, true) + ")");\r
265                     }\r
266                 }\r
267             }\r
268         }\r
269 \r
270         if (connectorToModeledAttachment == null)\r
271             connectorToModeledAttachment = Collections.emptyMap();\r
272 \r
273         Resource forcedAttachmentRelation = null;\r
274         if (terminalCount == 2 && connectorToModeledAttachment.size() == 1) {\r
275             forcedAttachmentRelation = getInverseAttachment(graph, connectorToModeledAttachment.values().iterator().next());\r
276             if (DEBUG)\r
277                 System.out.println("set forced attachment: " + NameUtils.getSafeLabel(graph, forcedAttachmentRelation));\r
278         }\r
279 \r
280         Resource connectionType = graph.getPossibleObject(connection, STR.HasConnectionType); \r
281 \r
282         // Needed to support ConnectionEntity#getTerminalConnections\r
283         Set<BackendConnection> backendConnections = new THashSet<BackendConnection>(toConnectorStatements.size(), 0.75f);\r
284 \r
285         // Load all node terminal connections as RouteTerminals\r
286         for (Statement toConnector : toConnectorStatements) {\r
287             Resource connector = toConnector.getObject();\r
288             Resource attachmentRelation = toConnector.getPredicate();\r
289             if (DEBUG)\r
290                 System.out.println("original attachment relation: " + NameUtils.getSafeLabel(graph, attachmentRelation));\r
291 \r
292             Statement terminalStm = findTerminalStatement(graph, connection, connector);\r
293             if (terminalStm == null)\r
294                 // Ignore broken connector: attached to the connection but not to any terminal.\r
295                 continue;\r
296 \r
297             Resource terminalElement = terminalStm.getObject();\r
298             Resource terminalElementType = graph.getPossibleType(terminalElement, DIA.Element);\r
299             if (terminalElementType == null)\r
300                 // Ignore non-element terminal elements\r
301                 continue;\r
302 \r
303             Resource connectionRelation = graph.getPossibleInverse(terminalStm.getPredicate());\r
304             if (connectionRelation == null)\r
305                 continue;\r
306 \r
307             // Discover node and terminal this connector is connected to.\r
308             TerminalMap terminals = graph.syncRequest(DiagramRequests.elementTypeTerminals(terminalElementType),\r
309                     TransientCacheListener.<TerminalMap>instance());\r
310             Resource terminal = terminals.getTerminal(connectionRelation);\r
311             if (terminal == null) {\r
312                 System.err.println(\r
313                         "RouteGraphUtils: Could not find terminal for connection point "\r
314                         + NameUtils.getSafeName(graph, connectionRelation, true)\r
315                         + " in element "\r
316                         + NameUtils.getSafeName(graph, terminalElement, true)); \r
317                 continue;\r
318             }\r
319 \r
320             double[] position = graph.getRelatedValue(connector, DIA.HasRelativeLocation, Bindings.DOUBLE_ARRAY);\r
321             if (position.length != 2)\r
322                 position = new double[] { 0, 0 };\r
323 \r
324             if (DEBUG) {\r
325                 System.out.println("terminalStm: " + NameUtils.toString(graph, terminalStm));\r
326                 System.out.println("terminal: " + graph.getURI(terminalStm.getPredicate()));\r
327             }\r
328             AffineTransform terminalElementTr = diagramRuntime != null ?\r
329                         DiagramGraphUtil.getDynamicWorldTransform(graph, diagramRuntime, terminalElement) : \r
330                         DiagramGraphUtil.getWorldTransform(graph, terminalElement);\r
331             \r
332             if (DEBUG)\r
333                 System.out.println("terminalElementTr: " + terminalElementTr);\r
334 \r
335             double x = terminalElementTr.getTranslateX();\r
336             double y = terminalElementTr.getTranslateY();\r
337             double minx = x-1, miny = y-1, maxx = x+1, maxy = y+1;\r
338             int direction = 0x0;\r
339 \r
340             // Use modelingRules to ascertain the proper attachmentRelation\r
341             // for this terminal connection, if available.\r
342             Resource att = connectorToModeledAttachment.get(connector);\r
343             if (att != null) {\r
344                 attachmentRelation = att;\r
345                 if (DEBUG)\r
346                     System.out.println("modeling rules attachment: " + NameUtils.getSafeLabel(graph, attachmentRelation));\r
347             } else if (forcedAttachmentRelation != null) {\r
348                 attachmentRelation = forcedAttachmentRelation;\r
349                 if (DEBUG)\r
350                     System.out.println("forced rules attachment: " + NameUtils.getSafeLabel(graph, attachmentRelation));\r
351             }\r
352             if (DEBUG)\r
353                 System.out.println("decided attachment: " + NameUtils.getSafeLabel(graph, attachmentRelation));\r
354 \r
355 //            // Get element bounds to decide allowed terminal direction(s)\r
356             IElement te = graph.syncRequest(DiagramRequests.getElement(canvas, diagram, terminalElement, null));\r
357             ElementUtils.getElementBounds(te, bounds);\r
358             {\r
359                 Shape shp = org.simantics.g2d.utils.GeometryUtils.transformShape(bounds, terminalElementTr);\r
360                 bounds.setFrame(shp.getBounds2D());\r
361             }\r
362 \r
363             // Expand bounds by 2mm to make the connections enter the terminals\r
364             // at a straight angle and from a distance instead of coming in\r
365             // "horizontally".\r
366             GeometryUtils.expandRectangle(bounds, 2);\r
367             minx = bounds.getMinX();\r
368             miny = bounds.getMinY();\r
369             maxx = bounds.getMaxX();\r
370             maxy = bounds.getMaxY();\r
371 \r
372             AffineTransform terminalPos = DiagramGraphUtil.getDynamicAffineTransform(graph, terminalElement, terminal);\r
373             //AffineTransform terminalPos2 = DiagramGraphUtil.getAffineTransform(graph, terminal);\r
374             if (terminalPos != null) {\r
375                 if (DEBUG) {\r
376                     System.out.println("terminalPos: " + terminalPos);\r
377                     //System.out.println("terminalPos2: " + terminalPos2);\r
378                 }\r
379                 terminalElementTr.concatenate(terminalPos);\r
380                 if (DEBUG)\r
381                     System.out.println("terminalElementTr: " + terminalElementTr);\r
382                 x = terminalElementTr.getTranslateX();\r
383                 y = terminalElementTr.getTranslateY();\r
384             }\r
385 \r
386             Integer allowedDirections = graph.getPossibleRelatedValue(terminal, DIA.Terminal_AllowedDirections, Bindings.INTEGER);\r
387             if (allowedDirections != null) {\r
388                 direction |= allowedDirections;\r
389                 direction = rotateDirection(direction, terminalElementTr);\r
390             } else {\r
391                 direction |= RouteGraphConnectionClass.shortestDirectionOutOfBounds(x, y, bounds);\r
392             }\r
393             //System.out.println("DIR(" + x + ", " + y + ", " + bounds + "): " + Integer.toHexString(direction));\r
394 \r
395             backendConnections.add(\r
396                     new BackendConnection(\r
397                             toEdgeEnd(graph, attachmentRelation, EdgeEnd.Begin),\r
398                             terminalElement,\r
399                             terminal)\r
400                     );\r
401 \r
402             if (direction == 0)\r
403                 // Accept any horizontal/vertical direction if nothing is defined\r
404                 direction = 0xf;\r
405 \r
406             if (graph.<Boolean>getRelatedValue(connector, DIA.Connector_straight, Bindings.BOOLEAN))\r
407                 direction |= RouteTerminal.DIR_DIRECT;\r
408             // FIXME: routegraph crashes if this is done for all terminals regardless of the amount of terminals.\r
409 \r
410             if (DEBUG)\r
411                 System.out.println("load line style: " + NameUtils.getSafeLabel(graph, attachmentRelation));\r
412             ILineEndStyle endStyle = RouteGraphConnectionClassFactory.loadLineEndStyle(graph, attachmentRelation, connectionType, RouteGraphConnectionClassFactory.TAIL);\r
413 \r
414             RouteTerminal routeTerminal = rg.addTerminal(x, y, minx, miny, maxx, maxy, direction, endStyle);\r
415             routeTerminal.setData( RouteGraphConnection.serialize(graph, connector) );\r
416 \r
417             nodeByData.put( connector, routeTerminal );\r
418 \r
419             for (Resource connectedTo : graph.getObjects(connector, DIA.AreConnected)) {\r
420                 links.add( new EdgeResource(connectedTo, connector) );\r
421             }\r
422         }\r
423 \r
424         // Finish route graph loading by Linking route nodes together\r
425         for (EdgeResource link : links) {\r
426             RouteNode n1 = nodeByData.get(link.first());\r
427             RouteNode n2 = nodeByData.get(link.second());\r
428             if (n1 == null || n2 == null) {\r
429                 System.err.println("Stray connection link found: " + link.toString(graph));\r
430                 continue;\r
431             }\r
432             rg.link(n1, n2);\r
433         }\r
434 \r
435 //        // Load connection line style.\r
436 //        ConnectionStyle style = readConnectionStyle(graph, canvas, modelingRules, connection);\r
437 //        StyledRouteGraphRenderer renderer = getRenderer(graph, canvas, style);\r
438 //\r
439 //        // Finish element load\r
440 //        element.setHint(RouteGraphConnectionClass.KEY_ROUTEGRAPH, rg);\r
441 //        element.setHint(RouteGraphConnectionClass.KEY_RENDERER, renderer);\r
442 //        element.setHint(RouteGraphConnectionClass.KEY_PICK_TOLERANCE, 0.5);\r
443 //\r
444 //        // Initialize ConnectionEntity in element\r
445 //        element.setHint(ElementHints.KEY_CONNECTION_ENTITY, new CE(diagram, connection, element, backendConnections));\r
446 //\r
447 //        // Setup graph writeback support for route graph modifications\r
448 //        final Session session = graph.getSession();\r
449 //        element.setHint(RouteGraphConnectionClass.KEY_RG_LISTENER, new IRouteGraphListener() {\r
450 //            @Override\r
451 //            public void routeGraphChanged(RouteGraphChangeEvent event) {\r
452 //                scheduleSynchronize(session, connection, event);\r
453 //            }\r
454 //        });\r
455         \r
456         return rg;\r
457 \r
458         }\r
459 \r
460     // ------------------------------------------------------------------------\r
461     // RouteGraph RouteTerminal allowed direction rotation support\r
462     // ------------------------------------------------------------------------\r
463 \r
464     public static int rotateDirection(int direction, AffineTransform at) {\r
465         // When direct routing is enabled, no point in wasting time rotating.\r
466         if ((direction & RouteTerminal.DIR_DIRECT) != 0)\r
467             return direction;\r
468 \r
469         final int mask = (AffineTransform.TYPE_MASK_ROTATION | AffineTransform.TYPE_FLIP);\r
470         boolean rotatedOrFlipped = (at.getType() & mask) != 0;\r
471         if (rotatedOrFlipped) {\r
472             double xAxisAngle = Math.atan2( at.getShearY(), at.getScaleX() );\r
473             double yAxisAngle = Math.atan2( at.getScaleY(), at.getShearX() );\r
474 \r
475             int xQuadrant = mainQuadrant(xAxisAngle);\r
476             int yQuadrant = mainQuadrant(yAxisAngle);\r
477 \r
478             int xDirMask = direction & (RouteTerminal.DIR_LEFT | RouteTerminal.DIR_RIGHT);\r
479             int yDirMask = direction & (RouteTerminal.DIR_DOWN | RouteTerminal.DIR_UP);\r
480 \r
481             int xDirMaskRotated = rotl4(xDirMask, xQuadrant);\r
482             int yDirMaskRotated = rotl4(yDirMask, yQuadrant-1);\r
483 \r
484             direction = xDirMaskRotated | yDirMaskRotated;\r
485         }\r
486 \r
487         return direction;\r
488     }\r
489 \r
490     /**\r
491      * 4-bit rotate left without carry operation.\r
492      * Operates on the 4 least sensitive bits of the integer\r
493      * and leaves the higher bits untouched.\r
494      * @param x the bits to rotate\r
495      * @param n the amount of rotation [0..3]\r
496      * @return\r
497      */\r
498     private static int rotl4(int x, int n) {\r
499         n &= 3;\r
500         if (n == 0)\r
501             return x;\r
502         int hx = x & 0xfffffff0;\r
503         int lx = x & 0xf;\r
504         int xh = (lx << n) & 0xf;\r
505         int xl = (lx >>> (4-n));\r
506         return xh | xl | hx;\r
507     }\r
508 \r
509         /**\r
510      * <pre>\r
511      *  33\r
512      * 2\/0\r
513      * 2/\0\r
514      *  11\r
515      * </pre>\r
516      * \r
517      * @param theta angle in radians\r
518      * @return the quadrant based on the ASCII art above\r
519      */\r
520     private static int mainQuadrant(double theta) {\r
521         if (theta > -DEG_45 && theta <= DEG_45) {\r
522             return 0;\r
523         } else if ((theta > DEG_45 && theta <= DEG_135)) {\r
524             return 1;\r
525         } else if (theta >= -DEG_135 && theta < -DEG_45) {\r
526             return 3;\r
527         }\r
528         return 2;\r
529     }\r
530 \r
531     private static final double DEG_45 = Math.PI/4.;\r
532     private static final double DEG_135 = Math.PI*3./4.;\r
533 \r
534 }\r