001/* Layout interface between the Ptolemy Diva editor and the KIELER 002 * layout library.*/ 003/* 004 @Copyright (c) 2009-2016 The Regents of the University of California. 005 All rights reserved. 006 007 Permission is hereby granted, without written agreement and without 008 license or royalty fees, to use, copy, modify, and distribute this 009 software and its documentation for any purpose, provided that the 010 above copyright notice and the following two paragraphs appear in all 011 copies of this software. 012 013 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 014 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 015 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 016 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 017 SUCH DAMAGE. 018 019 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 020 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 021 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 022 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 023 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 024 ENHANCEMENTS, OR MODIFICATIONS. 025 026 PT_COPYRIGHT_VERSION_2 027 COPYRIGHTENDKEY 028 */ 029 030package ptolemy.vergil.basic.layout.kieler; 031 032import java.awt.Dimension; 033import java.awt.geom.AffineTransform; 034import java.awt.geom.Point2D; 035import java.awt.geom.Rectangle2D; 036import java.util.Iterator; 037import java.util.LinkedList; 038import java.util.List; 039import java.util.Map; 040 041import javax.swing.SwingConstants; 042 043import com.google.common.collect.BiMap; 044import com.google.common.collect.HashBiMap; 045import com.google.common.collect.Iterators; 046import com.google.common.collect.LinkedListMultimap; 047import com.google.common.collect.ListMultimap; 048import com.google.common.collect.Lists; 049import com.google.common.collect.Maps; 050 051import de.cau.cs.kieler.core.alg.BasicProgressMonitor; 052import de.cau.cs.kieler.core.alg.DefaultFactory; 053import de.cau.cs.kieler.core.alg.IKielerProgressMonitor; 054import de.cau.cs.kieler.core.alg.InstancePool; 055import de.cau.cs.kieler.core.kgraph.KEdge; 056import de.cau.cs.kieler.core.kgraph.KLabel; 057import de.cau.cs.kieler.core.kgraph.KNode; 058import de.cau.cs.kieler.core.kgraph.KPort; 059import de.cau.cs.kieler.core.math.KVector; 060import de.cau.cs.kieler.core.util.Pair; 061import de.cau.cs.kieler.kiml.AbstractLayoutProvider; 062import de.cau.cs.kieler.kiml.klayoutdata.KEdgeLayout; 063import de.cau.cs.kieler.kiml.klayoutdata.KLayoutData; 064import de.cau.cs.kieler.kiml.klayoutdata.KPoint; 065import de.cau.cs.kieler.kiml.klayoutdata.KShapeLayout; 066import de.cau.cs.kieler.kiml.options.Alignment; 067import de.cau.cs.kieler.kiml.options.Direction; 068import de.cau.cs.kieler.kiml.options.EdgeLabelPlacement; 069import de.cau.cs.kieler.kiml.options.EdgeRouting; 070import de.cau.cs.kieler.kiml.options.LayoutOptions; 071import de.cau.cs.kieler.kiml.options.PortConstraints; 072import de.cau.cs.kieler.kiml.options.PortSide; 073import de.cau.cs.kieler.kiml.options.SizeConstraint; 074import de.cau.cs.kieler.kiml.util.KimlUtil; 075import de.cau.cs.kieler.klay.layered.LayeredLayoutProvider; 076import de.cau.cs.kieler.klay.layered.p3order.CrossingMinimizationStrategy; 077import de.cau.cs.kieler.klay.layered.p4nodes.NodePlacementStrategy; 078import de.cau.cs.kieler.klay.layered.properties.LayerConstraint; 079import de.cau.cs.kieler.klay.layered.properties.Properties; 080import diva.canvas.CanvasComponent; 081import diva.canvas.CompositeFigure; 082import diva.canvas.Figure; 083import diva.canvas.connector.AbstractConnector; 084import diva.canvas.toolbox.LabelFigure; 085import diva.graph.GraphModel; 086import diva.graph.layout.AbstractGlobalLayout; 087import diva.graph.layout.LayoutTarget; 088import diva.graph.modular.EdgeModel; 089import ptolemy.actor.Actor; 090import ptolemy.actor.CompositeActor; 091import ptolemy.actor.Director; 092import ptolemy.actor.TypedIOPort; 093import ptolemy.domains.modal.kernel.State; 094import ptolemy.domains.modal.kernel.Transition; 095import ptolemy.gui.Top; 096import ptolemy.kernel.ComponentPort; 097import ptolemy.kernel.CompositeEntity; 098import ptolemy.kernel.Entity; 099import ptolemy.kernel.Port; 100import ptolemy.kernel.Relation; 101import ptolemy.kernel.util.Attribute; 102import ptolemy.kernel.util.IllegalActionException; 103import ptolemy.kernel.util.InternalErrorException; 104import ptolemy.kernel.util.Locatable; 105import ptolemy.kernel.util.Location; 106import ptolemy.kernel.util.NamedObj; 107import ptolemy.moml.Vertex; 108import ptolemy.vergil.actor.ActorGraphModel; 109import ptolemy.vergil.actor.ActorGraphModel.ExternalPortModel; 110import ptolemy.vergil.actor.IOPortController; 111import ptolemy.vergil.actor.KielerLayoutConnector; 112import ptolemy.vergil.actor.KielerLayoutUtil; 113import ptolemy.vergil.actor.LayoutHint; 114import ptolemy.vergil.actor.PortTerminal; 115import ptolemy.vergil.basic.RelativeLocatable; 116import ptolemy.vergil.basic.RelativeLocation; 117import ptolemy.vergil.kernel.Link; 118import ptolemy.vergil.kernel.RelativeLinkFigure; 119import ptolemy.vergil.modal.FSMGraphModel; 120import ptolemy.vergil.modal.KielerLayoutArcConnector; 121import ptolemy.vergil.toolbox.SnapConstraint; 122 123/////////////////////////////////////////////////////////////////// 124////KielerLayout 125/** 126 * Ptolemy Layouter that uses the KIELER layout algorithm from an external 127 * library to layout a given ptolemy model. 128 * <p> 129 * See http://www.informatik.uni-kiel.de/rtsys/kieler/ for more information 130 * about KIELER. 131 * </p> 132 * <p><b> 133 * KIELER - Kiel Integrated Environment for Layout for the Eclipse 134 * RichClientPlatform 135 * </b></p> 136 * <p> 137 * The KIELER project tries to enhance graphical modeling pragmatics. Next to 138 * higher level solutions (meta layout, view management, structure-based editing, 139 * etc.) developed for Eclipse models, it also implements custom layout algorithms. 140 * </p> 141 * <p> 142 * This class interfaces a standalone KIELER layout algorithm for actor oriented 143 * port based graphical diagrams with a Ptolemy diagram. 144 * </p> 145 * <p> 146 * While KIELER is mainly developed for an Eclipse environment, most algorithms 147 * are also available standalone and can be used in a non-Eclipse environment. 148 * This class is an approach to leverage this by employing the algorithms within Ptolemy. 149 * Two standalone external libraries are required, one containing KIELER classes 150 * and a small subset of the Eclipse Modeling Framework (EMF), the other 151 * containing the Google Guava library, which is used as utility. 152 * </p> 153 * <p> 154 * Calling the layout() method will create a new KIELER graph data structure, run 155 * KIELER layout algorithms on it and augment it with resulting layout 156 * information (locations and sizes of nodes, bend points of connections). Then 157 * this layout is applied to the Ptolemy model. Moving of nodes in Ptolemy is 158 * done via adding or changing location attributes. 159 * </p> 160 * <p> 161 * Setting bend points was not supported in Ptolemy. Ptolemy's built-in 162 * connection routing does not consider obstacle avoidance, hence overlaps 163 * with other nodes and connections might appear. In order to gap this problem, 164 * the actual drawing of the connections is performed by {@link KielerLayoutConnector} 165 * instead of the standard Manhattan connector. 166 * This KielerLayout stores bend points of connections 167 * persistently in a Ptolemy model via {@link LayoutHint} attributes 168 * attached to relations. The KielerLayoutConnector then reads these 169 * attributes and routes the edges accordingly. If the bend points are not 170 * valid anymore, the attribute is removed and layout has to be performed again. 171 * </p> 172 * @author Hauke Fuhrmann (<a href="mailto:haf@informatik.uni-kiel.de">haf</a>), 173 * Christian Motika (<a href="mailto:cmot@informatik.uni-kiel.de">cmot</a>), 174 * Miro Spönemann (<a href="mailto:msp@informatik.uni-kiel.de">msp</a>) , 175 * Christoph Daniel Schulze (<a href="mailto:cds@informatik.uni-kiel.de">cds</a>), 176 * Ulf Rueegg 177 * @version $Id$ 178 * @since Ptolemy II 8.0 179 * @Pt.ProposedRating Red (cxh) 180 * @Pt.AcceptedRating Red (cxh) 181 */ 182public class KielerLayout extends AbstractGlobalLayout { 183 184 /** 185 * Construct an instance taking a LayoutTarget for specifying some methods 186 * for layout handling as given by the standard Ptolemy 187 * AbstractGlobalLayout. The KielerLayout will need access to the top level 188 * Ptolemy model, so either use corresponding constructor or call setModel() 189 * prior to layout invocation. 190 * 191 * @param target The LayoutTarget on which layout will be performed 192 */ 193 public KielerLayout(LayoutTarget target) { 194 super(target); 195 } 196 197 /** 198 * Construct an instance setting the LayoutTarget as requested by the 199 * AbstractGlobalLayout and the containing Ptolemy model. Preferred 200 * constructor. 201 * 202 * @param target The LayoutTarget on which layout will be performed 203 * @param ptolemyContainer The composite actor that contains all elements to 204 * be layouted 205 */ 206 public KielerLayout(LayoutTarget target, CompositeActor ptolemyContainer) { 207 super(target); 208 this.setModel(ptolemyContainer); 209 } 210 211 /////////////////////////////////////////////////////////////////// 212 //// public methods //// 213 214 /** 215 * Layout the given composite. Main entry point for the layout action. 216 * Create a KIELER KGraph data structure corresponding to the Ptolemy model, 217 * instantiate a KIELER layout algorithm (AbstractLayoutProvider) and run 218 * its doLayout() method on the KGraph. The KGraph is augmented with 219 * layout information (position and sizes of objects and bend points for 220 * connections). This information is then reapplied to the ptolemy model by 221 * stating MoMLChangeRequests with location attributes for nodes. 222 * Connection bend points are applied using {@link LayoutHint}s. 223 * 224 * @param composite the container of the diagram in terms of a GraphModel. 225 */ 226 @Override 227 public void layout(Object composite) { 228 229 KielerLayoutConnector.setLayoutInProgress(true); 230 KielerLayoutArcConnector.setLayoutInProgress(true); 231 232 // some variables for time statistics 233 long overallTime = System.currentTimeMillis(); 234 235 _report("Performing KIELER layout... "); 236 long graphOverhead = overallTime; 237 238 _graphModel = getLayoutTarget().getGraphModel(); 239 240 // Create a KGraph for the KIELER layout algorithm. 241 KNode parentNode = KimlUtil.createInitializedNode(); 242 KShapeLayout parentLayout = parentNode.getData(KShapeLayout.class); 243 if (_top != null) { 244 Dimension contentSize = _top.getContentSize(); 245 parentLayout.setWidth(contentSize.width); 246 parentLayout.setHeight(contentSize.height); 247 } 248 249 // Some layouts (such as FSM) may build a skeleton 250 // of hierarchical nodes to separate certain elements 251 // of the diagram from each other 252 // The 'mainModelNode' is the node that is supposed 253 // to hold the actual elements of the ptolemy model 254 KNode mainModelNode = parentNode; 255 KShapeLayout mainModelLayout = parentLayout; 256 if (_graphModel instanceof FSMGraphModel) { 257 // create a FSM skeleton to separate input and output ports 258 mainModelNode = _createFsmSkeleton(parentNode); 259 mainModelLayout = mainModelNode.getData(KShapeLayout.class); 260 } 261 262 try { 263 // Configure the layout algorithm by annotating the graph. 264 Parameters parameters = new Parameters(_compositeEntity); 265 parameters.configureLayout(mainModelLayout, 266 getLayoutTarget().getGraphModel()); 267 268 // Now read Ptolemy model and fill the KGraph with the model data. 269 _createGraph(composite, mainModelNode); 270 graphOverhead = System.currentTimeMillis() - graphOverhead; 271 272 // Create the layout provider which performs the actual layout algorithm. 273 InstancePool<AbstractLayoutProvider> layouterPool = _getLayouterPool(); 274 AbstractLayoutProvider layoutProvider = layouterPool.fetch(); 275 276 // Create a progress monitor for execution time measurement. 277 IKielerProgressMonitor progressMonitor = new BasicProgressMonitor(); 278 279 // Perform layout on the created graph. 280 _recursivelyLayout(parentNode, layoutProvider, progressMonitor); 281 282 // Write to XML file for debugging layout (requires XMI resource factory). 283 if (DEBUG) { 284 KimlUtil.persistDataElements(parentNode); 285 KielerGraphUtil._writeToFile(parentNode); 286 } 287 288 // Set initial position as the bounding box of the hierarchical node. 289 KVector offset = KielerGraphUtil._getUpperLeftCorner(parentNode); 290 parentLayout.setXpos(parentLayout.getXpos() - (float) offset.x); 291 parentLayout.setYpos(parentLayout.getYpos() - (float) offset.y); 292 293 long momlRequestOverhead = System.currentTimeMillis(); 294 295 // Create a special change request to apply the computed layout to the model. 296 ApplyLayoutRequest layoutRequest = new ApplyLayoutRequest( 297 _compositeEntity); 298 299 // Apply layout to ptolemy model. 300 _recursivelyApplyLayout(parentNode, layoutRequest); 301 302 // Let the composite actor execute the actual changes. 303 _compositeEntity.requestChange(layoutRequest); 304 305 momlRequestOverhead = System.currentTimeMillis() 306 - momlRequestOverhead; 307 overallTime = System.currentTimeMillis() - overallTime; 308 _report("KIELER layout done in " + overallTime 309 + "ms (Graph conversion " + graphOverhead + "ms, Algorithm " 310 + Math.round(progressMonitor.getExecutionTime() * 1000) 311 + "ms, MoMLChanges " + momlRequestOverhead + "ms)."); 312 313 // Release the layout provider back to the instance pool for later reuse. 314 layouterPool.release(layoutProvider); 315 316 } catch (IllegalActionException exception) { 317 // Throw some Ptolemy runtime exception. 318 throw new InternalErrorException(exception); 319 } 320 321 KielerLayoutConnector.setLayoutInProgress(false); 322 KielerLayoutArcConnector.setLayoutInProgress(false); 323 } 324 325 /** 326 * Set the Ptolemy Model that contains the graph that is to be layouted. The 327 * layouter will require access to the Ptolemy model because the lower level 328 * Diva abstraction does not consider certain properties required by the 329 * KIELER layouter such as port positions. 330 * 331 * @param model The parent composite entity which internal diagram shall be layouted. 332 */ 333 public void setModel(CompositeEntity model) { 334 this._compositeEntity = model; 335 } 336 337 /** 338 * Set the Top window to enable status reports on the status bar. 339 * 340 * @param top The Top window 341 */ 342 public void setTop(Top top) { 343 this._top = top; 344 } 345 346 /////////////////////////////////////////////////////////////////// 347 //// protected methods //// 348 349 /** 350 * Return a pool for layout provider instances that can be reused in subsequent layout 351 * runs. New instances can be fetched from the pool and should be released back to 352 * the pool after use. 353 * 354 * @return a layout provider pool 355 */ 356 protected static synchronized InstancePool<AbstractLayoutProvider> _getLayouterPool() { 357 if (_layoutProviderPool == null) { 358 _layoutProviderPool = new InstancePool<AbstractLayoutProvider>( 359 new DefaultFactory(LayeredLayoutProvider.class)); 360 } 361 return _layoutProviderPool; 362 } 363 364 /////////////////////////////////////////////////////////////////// 365 //// private methods //// 366 367 /** 368 * Performs a recursive layout inside-out (if necessary). 369 * 370 * @param parentNode 371 * a node for which to apply the layout 372 * @param layoutProvider 373 * the layout provider to execute the layout 374 * @param progressMonitor 375 * a progress monitor to measure execution time 376 */ 377 private void _recursivelyLayout(KNode parentNode, 378 AbstractLayoutProvider layoutProvider, 379 IKielerProgressMonitor progressMonitor) { 380 381 for (KNode child : parentNode.getChildren()) { 382 if (!child.getChildren().isEmpty()) { 383 layoutProvider.doLayout(child, progressMonitor.subTask(1)); 384 } 385 } 386 387 layoutProvider.doLayout(parentNode, progressMonitor); 388 } 389 390 /** 391 * Recusively applies {@link #_applyLayout(KNode)} to any {@link KNode} 392 * that has further children. 393 * 394 * @param parentNode The KIELER graph object containing all layout information 395 * to apply to the Ptolemy model 396 * @param layoutRequest the common layout request for the current layout run 397 * @exception IllegalActionException if routing of edges fails. 398 */ 399 private void _recursivelyApplyLayout(KNode parentNode, 400 ApplyLayoutRequest layoutRequest) throws IllegalActionException { 401 402 // the _applyLayout method applies positions to the 403 // children of a node 404 if (!parentNode.getChildren().isEmpty()) { 405 _applyLayout(parentNode, layoutRequest); 406 407 for (KNode child : parentNode.getChildren()) { 408 if (!child.getChildren().isEmpty()) { 409 _recursivelyApplyLayout(child, layoutRequest); 410 } 411 } 412 } 413 } 414 415 /** 416 * Traverse a composite KNode containing corresponding KIELER nodes, ports 417 * and edges for the Ptolemy model and apply all layout information 418 * contained by it back to the Ptolemy model. Do most changes to the Ptolemy 419 * model via MoMLChangeRequests. Set location attributes for all visible 420 * Ptolemy nodes. 421 * Optionally route edges by inserting {@link LayoutHint} attributes. 422 * 423 * @param parentNode The KIELER graph object containing all layout information 424 * to apply to the Ptolemy model 425 * @param layoutRequest the common layout request for the current layout run 426 * @exception IllegalActionException if routing of edges fails. 427 */ 428 private void _applyLayout(KNode parentNode, 429 ApplyLayoutRequest layoutRequest) throws IllegalActionException { 430 431 // Apply node layout. 432 for (KNode knode : parentNode.getChildren()) { 433 KShapeLayout nodeLayout = knode.getData(KShapeLayout.class); 434 KVector nodePos = nodeLayout.createVector(); 435 Object divaNode = _kieler2ptolemyDivaNodes.get(knode); 436 if (divaNode instanceof Location) { 437 Locatable location = (Location) divaNode; 438 439 // Transform coordinate systems. 440 KimlUtil.toAbsolute(nodePos, parentNode); 441 _kNode2Ptolemy(nodePos, divaNode, location); 442 443 // Calculate the snap-to-grid coordinates. 444 double[] snapToGridNodePoint = SnapConstraint 445 .constrainPoint(nodePos.x, nodePos.y); 446 447 // Include the new location in the request. 448 layoutRequest.addLocation(location, snapToGridNodePoint[0], 449 snapToGridNodePoint[1]); 450 } 451 } 452 453 GraphModel graphModel = getLayoutTarget().getGraphModel(); 454 if (graphModel instanceof ActorGraphModel) { 455 // apply edge layout - bend points with layout hints 456 for (Pair<KEdge, Link> entry : _edgeList) { 457 _applyEdgeLayoutBendPointAnnotation(entry.getFirst(), 458 entry.getSecond(), layoutRequest); 459 } 460 } else if (graphModel instanceof FSMGraphModel) { 461 462 // apply edge layout - one single point for specifying a curve 463 for (Pair<KEdge, Link> entry : _edgeList) { 464 465 if (parentNode.getData(KLayoutData.class) 466 .getProperty(Parameters.SPLINES)) { 467 _applyEdgeLayoutBendPointAnnotation(entry.getFirst(), 468 entry.getSecond(), layoutRequest); 469 } else { 470 _applyEdgeLayoutCurve(entry.getFirst(), entry.getSecond(), 471 layoutRequest); 472 } 473 } 474 } 475 } 476 477 /** 478 * Apply the layout of an KEdge to its corresponding Diva Link. This is done 479 * by adding a LayoutHint attribute to the corresponding Relation. Only 480 * Relations are persistent objects in the abstract Ptolemy syntax and 481 * therefore add the bend points for a link in its Relation. Hence, there 482 * may be multiple link bend points stored in one Relation. The LayoutHint 483 * attribute can carry the bend points for multiple Links and identifies the 484 * link by the head and tail of the link. 485 * 486 * @param kedge The KIELER edge that holds the precomputed layout 487 * information, i.e. bend point positions 488 * @exception IllegalActionException Exception will be thrown if replacing 489 * of original relation is not possible, i.e. if unlink() or 490 * link() methods fail. 491 */ 492 private void _applyEdgeLayoutBendPointAnnotation(KEdge kedge, Link link, 493 ApplyLayoutRequest layoutRequest) throws IllegalActionException { 494 List<KPoint> bendPoints = kedge.getData(KEdgeLayout.class) 495 .getBendPoints(); 496 497 // Translate bend points into an array of doubles for the layout hint attribute. 498 double[] layoutHintBendPoints = new double[bendPoints.size() * 2]; 499 int index = 0; 500 KNode parentNode = KielerGraphUtil._getParent(kedge); 501 if (parentNode == null) { 502 throw new IllegalStateException( 503 "Internal layout error. Every edge in the layout graph must have a parent."); 504 } 505 for (KPoint relativeKPoint : bendPoints) { 506 KVector kpoint = relativeKPoint.createVector(); 507 KimlUtil.toAbsolute(kpoint, parentNode); 508 509 // calculate the snap-to-grid coordinates unless we route 510 // edges as splines. With splines the snap to grid feature would 511 // drastically deform the spline routes 512 if (parentNode.getData(KLayoutData.class).getProperty( 513 LayoutOptions.EDGE_ROUTING) != EdgeRouting.SPLINES) { 514 515 double[] snapToGridBendPoint = SnapConstraint 516 .constrainPoint(kpoint.x, kpoint.y); 517 layoutHintBendPoints[index] = snapToGridBendPoint[0]; 518 layoutHintBendPoints[index + 1] = snapToGridBendPoint[1]; 519 } else { 520 521 layoutHintBendPoints[index] = kpoint.x; 522 layoutHintBendPoints[index + 1] = kpoint.y; 523 } 524 525 index += 2; 526 } 527 528 // we support exactly one label (if one exists at all) 529 Point2D.Double labelLocation = null; 530 for (KLabel label : kedge.getLabels()) { 531 labelLocation = new Point2D.Double(); 532 KVector pos = label.getData(KShapeLayout.class).createVector(); 533 KimlUtil.toAbsolute(pos, parentNode); 534 labelLocation.x = pos.x; 535 labelLocation.y = pos.y; 536 break; 537 } 538 539 Relation relation = link.getRelation(); 540 NamedObj head = (NamedObj) link.getHead(); 541 NamedObj tail = (NamedObj) link.getTail(); 542 // Determine correct direction of the edge. 543 if (head != _divaEdgeSource.get(link)) { 544 layoutRequest.addConnection(relation, tail, head, 545 layoutHintBendPoints, labelLocation); 546 } else { 547 layoutRequest.addConnection(relation, head, tail, 548 layoutHintBendPoints, labelLocation); 549 } 550 551 } 552 553 private void _applyEdgeLayoutCurve(KEdge kedge, Link link, 554 ApplyLayoutRequest layoutRequest) { 555 Relation relation = link.getRelation(); 556 // Don't process any self-loops, since that would cause headaches. 557 if (relation instanceof Transition 558 && link.getHead() != link.getTail()) { 559 KEdgeLayout edgeLayout = kedge.getData(KEdgeLayout.class); 560 List<KPoint> bendPoints = edgeLayout.getBendPoints(); 561 562 KNode source = kedge.getSource(); 563 if (source == null) { 564 throw new IllegalStateException("Internal layout error. " 565 + "Every edge must have a source node."); 566 } 567 KShapeLayout sourceLayout = source.getData(KShapeLayout.class); 568 double sourcex = sourceLayout.getXpos() 569 + sourceLayout.getWidth() / 2; 570 double sourcey = sourceLayout.getYpos() 571 + sourceLayout.getHeight() / 2; 572 KNode target = kedge.getTarget(); 573 if (target == null) { 574 throw new IllegalStateException("Internal layout error. " 575 + "Every edge must have a source node."); 576 } 577 KShapeLayout targetLayout = target.getData(KShapeLayout.class); 578 double targetx = targetLayout.getXpos() 579 + targetLayout.getWidth() / 2; 580 double targety = targetLayout.getYpos() 581 + targetLayout.getHeight() / 2; 582 583 // Determine a reference point for drawing the curve. 584 double exitAngle = 0; 585 double refx = 0, refy = 0; 586 587 // 1) if the edge has a label, we use the label's center 588 // as reference 589 // 2) if there are no bend points, use the the center of 590 // a straight line between source and target 591 // 3) if there are bend points, we use the center 592 // between the two "middle" bend points 593 if (!kedge.getLabels().isEmpty()) { 594 KLabel label = kedge.getLabels().get(0); 595 KShapeLayout labelLayout = label.getData(KShapeLayout.class); 596 KVector center = labelLayout.createVector().add( 597 labelLayout.getWidth() / 2f, 598 labelLayout.getHeight() / 2f); 599 refx = center.x; 600 refy = center.y; 601 } else if (bendPoints.isEmpty()) { 602 refx = (edgeLayout.getSourcePoint().getX() 603 + edgeLayout.getTargetPoint().getX()) / 2; 604 refy = (edgeLayout.getSourcePoint().getY() 605 + edgeLayout.getTargetPoint().getY()) / 2; 606 } else { 607 int count = bendPoints.size(); 608 KPoint point1 = bendPoints.get(count / 2 - 1); 609 KPoint point2 = bendPoints.get(count / 2); 610 refx = (point1.getX() + point2.getX()) / 2f; 611 refy = (point1.getY() + point2.getY()) / 2f; 612 } 613 614 // Take the angular difference between the reference point and 615 // the target point as seen from the source. 616 double targetth = Math.atan2(targety - sourcey, targetx - sourcex); 617 double meanth = Math.atan2(refy - sourcey, refx - sourcex); 618 exitAngle = meanth - targetth; 619 620 // Fit the angle into the bounds of [-pi, pi] 621 if (exitAngle > Math.PI) { 622 exitAngle -= 2 * Math.PI; 623 } else if (exitAngle < -Math.PI) { 624 exitAngle += 2 * Math.PI; 625 } 626 627 layoutRequest.addCurve((Transition) relation, exitAngle); 628 } 629 } 630 631 /** 632 * Creates a graph for the KIELER API from a Ptolemy model. Will traverse 633 * the low level GraphModel given by the composite and record all found 634 * elements in the mapping fields of this object that keep a mapping between 635 * Ptolemy/Diva objects and KIELER objects. New KIELER objects (KEdge, 636 * KNode, KPort) are created for their respective Ptolemy counterparts and 637 * initialized with the initial sizes and positions and are put in a 638 * composite KNode (the graph KIELER will perform the layout on later). To 639 * obtain the right mappings, multiple abstraction levels of Ptolemy are 640 * considered here: Diva, as this was the intended original way to do 641 * automatic layout (e.g. by GlobalAbstractLayout) and Ptolemy, since Diva 642 * lacks certain concepts that are relevant for a proper layout, such 643 * as exact port locations for considering port constraints in the 644 * model, which are supported by KIELER. 645 * 646 * @param composite The GraphModel composite object to retrieve the model 647 * information from 648 * @param parentNode KIELER subgraph to receive all connected 649 * model elements 650 */ 651 private void _createGraph(Object composite, KNode parentNode) { 652 _kieler2ptolemyDivaNodes = HashBiMap.create(); 653 _ptolemy2KielerPorts = LinkedListMultimap.create(); 654 _divaEdgeSource = Maps.newHashMap(); 655 _divaEdgeTarget = Maps.newHashMap(); 656 _divaLabel = Maps.newHashMap(); 657 _edgeList = Lists.newLinkedList(); 658 KShapeLayout parentLayout = parentNode.getData(KShapeLayout.class); 659 660 // Determine whether to include unconnected nodes. 661 boolean doBoxLayout = parentLayout.getProperty(Parameters.DECORATIONS); 662 663 // On-the-fly find upper left corner for bounding box of parent node. 664 float globalX = Float.MAX_VALUE, globalY = Float.MAX_VALUE; 665 666 // Traverse the ptolemy graph. 667 ExternalPortModel externalPortModel = null; 668 if (_graphModel instanceof ActorGraphModel) { 669 externalPortModel = ((ActorGraphModel) _graphModel) 670 .getExternalPortModel(); 671 } 672 List<Link> unprocessedEdges = new LinkedList<Link>(); 673 List<NamedObj> unprocessedRelatives = new LinkedList<NamedObj>(); 674 675 // Process nodes. 676 for (Iterator iterator = _graphModel.nodes(composite); iterator 677 .hasNext();) { 678 Object node = iterator.next(); 679 if (!(node instanceof Locatable)) { 680 continue; 681 } 682 Iterator portIter = null; 683 684 // Here we get the corresponding Ptolemy object. 685 // This breaks with Ptolemy/Diva abstraction; for now we need 686 // the ptolemy actor to get the ports and port positions 687 // and to distinguish actors and relation vertices. 688 NamedObj semanticNode = (NamedObj) _graphModel 689 .getSemanticObject(node); 690 691 if (doBoxLayout || PtolemyModelUtil._isConnected(semanticNode)) { 692 // Temporary variable for the new KNode corresponding to one of 693 // the following cases depending on what the semantic object is. 694 KNode knode = null; 695 696 // Handle actors, text, and directors. 697 if (semanticNode instanceof Actor 698 || semanticNode instanceof Attribute) { 699 700 // Create a KIELER node for a ptolemy node. 701 knode = _createKNode(node, semanticNode); 702 703 // Handle the ports of this node. 704 if (semanticNode instanceof Actor 705 && semanticNode instanceof Entity) { 706 Actor actor = (Actor) semanticNode; 707 List<Port> inputs = actor.inputPortList(); 708 List<Port> outputs = actor.outputPortList(); 709 710 // create ports 711 _createKPorts(knode, inputs); 712 _createKPorts(knode, outputs); 713 portIter = _graphModel.nodes(node); 714 } else if (semanticNode instanceof RelativeLocatable) { 715 unprocessedRelatives.add(semanticNode); 716 } 717 } 718 719 // Handle relation vertices. 720 else if (semanticNode instanceof Relation) { 721 // Regard a relation vertex as a KIELER KNode. 722 knode = _createKNodeForVertex((Vertex) node); 723 portIter = Iterators.singletonIterator(node); 724 } 725 726 // Handle internal ports. 727 else if (semanticNode instanceof ComponentPort) { 728 knode = _createKNodeForPort(node, 729 (ComponentPort) semanticNode); 730 portIter = Iterators.singletonIterator(node); 731 } 732 733 // Handle modal model states. 734 else if (semanticNode instanceof State) { 735 knode = _createKNodeForState(node, (State) semanticNode); 736 portIter = Iterators.singletonIterator(node); 737 } 738 739 // Now do some common bookkeeping for all kinds of nodes. 740 if (knode != null) { 741 // some nodes, such as FSM's ports may already 742 // be assigned to a node other than the parent node 743 if (knode.getParent() == null) { 744 knode.setParent(parentNode); 745 } 746 // Get check bounds for global bounding box. 747 KShapeLayout layout = knode.getData(KShapeLayout.class); 748 if (layout.getXpos() < globalX) { 749 globalX = layout.getXpos(); 750 } 751 if (layout.getYpos() < globalY) { 752 globalY = layout.getYpos(); 753 } 754 755 // Store node for later applying layout back. 756 _kieler2ptolemyDivaNodes.put(knode, (Locatable) node); 757 } 758 } 759 760 if (portIter != null) { 761 while (portIter.hasNext()) { 762 Object divaPort = portIter.next(); 763 // Iterate all outgoing edges. 764 Iterator edgeIterator; 765 if (semanticNode instanceof Port 766 && externalPortModel != null) { // internal ports 767 edgeIterator = externalPortModel.outEdges(divaPort); 768 } else { 769 edgeIterator = _graphModel.outEdges(divaPort); 770 } 771 while (edgeIterator.hasNext()) { 772 Object next = edgeIterator.next(); 773 if (next instanceof Link) { 774 unprocessedEdges.add((Link) next); 775 } 776 } 777 } 778 } 779 } 780 781 // Create KIELER edges for Diva edges. 782 if (_graphModel instanceof ActorGraphModel) { 783 _storeEndpoints(unprocessedEdges); 784 } 785 for (Link divaEdge : unprocessedEdges) { 786 _createKEdge(divaEdge); 787 } 788 789 // Create edges for associations of relative locatables to their reference objects. 790 for (NamedObj relativeObj : unprocessedRelatives) { 791 _createKEdgeForAttribute(relativeObj); 792 } 793 794 // Set graph offset. 795 parentLayout.setXpos(globalX); 796 parentLayout.setYpos(globalY); 797 } 798 799 /** 800 * Creates a skeleton for placing elements of FSMs. 801 * 802 * An {@link FSMGraphModel} can have input, output, and inputoutput 803 * ports. Since the diagram is a state diagram the ports are 804 * not connected to any other diagram element. To create a 805 * clear visual experience, we place all inputs on the left 806 * side of the actual diagram, all outputs on the right, and all 807 * inputoutputs above the diagram. 808 * 809 * Inputs and outputs are stacked from top to bottom. Inputoutputs 810 * are placed left to right. The user can change the order 811 * of ports to move related ports close to each other. 812 * 813 * @param root 814 * the root node of the layout graph 815 * @return 816 * a node which should contain the actual diagram's elements 817 */ 818 private KNode _createFsmSkeleton(KNode root) { 819 820 _fsmInputOutput = KimlUtil.createInitializedNode(); 821 // hopefully no diagram gets this large 822 _fsmInputOutput.getData(KShapeLayout.class).setPos(0, 100000); 823 _fsmInputOutput.getData(KLayoutData.class) 824 .setProperty(LayoutOptions.SEPARATE_CC, false); 825 _fsmInputOutput.getData(KLayoutData.class).setProperty( 826 Properties.CROSS_MIN, CrossingMinimizationStrategy.INTERACTIVE); 827 _fsmInputOutput.getData(KLayoutData.class) 828 .setProperty(LayoutOptions.DIRECTION, Direction.DOWN); 829 830 _fsmInput = KimlUtil.createInitializedNode(); 831 _fsmInput.getData(KLayoutData.class) 832 .setProperty(LayoutOptions.SEPARATE_CC, false); 833 _fsmInput.getData(KLayoutData.class).setProperty(Properties.CROSS_MIN, 834 CrossingMinimizationStrategy.INTERACTIVE); 835 836 _fsmOutput = KimlUtil.createInitializedNode(); 837 _fsmOutput.getData(KLayoutData.class) 838 .setProperty(LayoutOptions.SEPARATE_CC, false); 839 _fsmOutput.getData(KLayoutData.class).setProperty(Properties.CROSS_MIN, 840 CrossingMinimizationStrategy.INTERACTIVE); 841 842 KNode newRoot = KimlUtil.createInitializedNode(); 843 newRoot.getData(KShapeLayout.class).setPos(0, 0); 844 845 // add the skeleton to the root node 846 root.getChildren().add(_fsmInputOutput); 847 root.getChildren().add(_fsmInput); 848 root.getChildren().add(_fsmOutput); 849 root.getChildren().add(newRoot); 850 851 // add edges to guarantee proper placement 852 KEdge dummyEdge = KimlUtil.createInitializedEdge(); 853 dummyEdge.setSource(_fsmInput); 854 dummyEdge.setTarget(newRoot); 855 dummyEdge = KimlUtil.createInitializedEdge(); 856 dummyEdge.setSource(_fsmInput); 857 dummyEdge.setTarget(_fsmInputOutput); 858 dummyEdge = KimlUtil.createInitializedEdge(); 859 dummyEdge.setSource(newRoot); 860 dummyEdge.setTarget(_fsmOutput); 861 // this edge is necessary to center the input outputs 862 dummyEdge = KimlUtil.createInitializedEdge(); 863 dummyEdge.setSource(_fsmInputOutput); 864 dummyEdge.setTarget(_fsmOutput); 865 866 KLayoutData rootLayout = root.getData(KLayoutData.class); 867 // assure that the inputoutput port container is placed above the actual diagram 868 rootLayout.setProperty(Properties.CROSS_MIN, 869 CrossingMinimizationStrategy.INTERACTIVE); 870 // balanced positioning 871 rootLayout.setProperty(Properties.NODE_PLACER, 872 NodePlacementStrategy.SIMPLE); 873 874 return newRoot; 875 } 876 877 /** 878 * Create a KIELER edge for a Ptolemy Diva edge object. The KEdge will be 879 * setup between either two ports or relation vertices or mixed. Hence the 880 * KEdge corresponds more likely to a Ptolemy link than a relation. Diva edges 881 * have no direction related to the flow of data in Ptolemy. However, KIELER 882 * uses a directed graph to perform layout and so a meaningful direction 883 * should be set in the KEdge. This direction will be approximated by doing 884 * a tree search beginning on both endpoints of the Diva edge. Whenever 885 * either of the endpoints is connected to a source port, this will be the 886 * source of the KEdge and determine its direction. 887 * 888 * The newly created edge is stored with the corresponding Diva edge in the 889 * global maps _ptolemyDiva2KielerEdges, _kieler2PtolemyDivaEdges, such that 890 * the {@link #_applyLayout(KNode)} method will be able to reapply the 891 * layout. 892 * 893 * @param divaEdge The Ptolemy diva edge object for which to create a new KEdge. 894 */ 895 private void _createKEdge(Link divaEdge) { 896 KEdge kedge = KimlUtil.createInitializedEdge(); 897 898 Object source = _divaEdgeSource.get(divaEdge); 899 if (source == null) { 900 source = divaEdge.getTail(); 901 } 902 Object target = _divaEdgeTarget.get(divaEdge); 903 if (target == null) { 904 target = divaEdge.getHead(); 905 } 906 907 KPort kSourcePort = _getPort(source, divaEdge.getRelation()); 908 if (kSourcePort != null) { 909 kedge.setSourcePort(kSourcePort); 910 kSourcePort.getEdges().add(kedge); 911 kedge.setSource(kSourcePort.getNode()); 912 } else { 913 // Edge is not connected to a port. 914 kedge.setSource(_kieler2ptolemyDivaNodes.inverse().get(source)); 915 } 916 KPort kTargetPort = _getPort(target, divaEdge.getRelation()); 917 if (kTargetPort != null) { 918 kedge.setTargetPort(kTargetPort); 919 kTargetPort.getEdges().add(kedge); 920 kedge.setTarget(kTargetPort.getNode()); 921 } else { 922 // Edge is not connected to a port. 923 kedge.setTarget(_kieler2ptolemyDivaNodes.inverse().get(target)); 924 } 925 926 // Set source and target point so they are not (0, 0). 927 KEdgeLayout edgeLayout = kedge.getData(KEdgeLayout.class); 928 if (source instanceof Locatable) { 929 double[] pos = ((Locatable) source).getLocation(); 930 edgeLayout.getSourcePoint().setX((float) pos[0]); 931 edgeLayout.getSourcePoint().setY((float) pos[1]); 932 } 933 if (target instanceof Locatable) { 934 double[] pos = ((Locatable) target).getLocation(); 935 edgeLayout.getTargetPoint().setX((float) pos[0]); 936 edgeLayout.getTargetPoint().setY((float) pos[1]); 937 } 938 939 // Add the edge to the list. 940 _edgeList.add(new Pair<KEdge, Link>(kedge, divaEdge)); 941 942 // Create a label for the edge. 943 Object figure = getLayoutTarget().getVisualObject(divaEdge); 944 if (figure instanceof AbstractConnector) { 945 LabelFigure labelFigure = ((AbstractConnector) figure) 946 .getLabelFigure(); 947 if (labelFigure != null) { 948 KLabel label = KimlUtil.createInitializedLabel(kedge); 949 label.setText(labelFigure.getString()); 950 KShapeLayout labelLayout = label.getData(KShapeLayout.class); 951 labelLayout.setProperty(LayoutOptions.EDGE_LABEL_PLACEMENT, 952 EdgeLabelPlacement.CENTER); 953 Rectangle2D bounds = labelFigure.getBounds(); 954 labelLayout.setWidth((float) bounds.getWidth()); 955 labelLayout.setHeight((float) bounds.getHeight()); 956 labelLayout.setXpos((edgeLayout.getSourcePoint().getX() 957 + edgeLayout.getTargetPoint().getX()) / 2); 958 labelLayout.setYpos((edgeLayout.getSourcePoint().getY() 959 + edgeLayout.getTargetPoint().getY()) / 2); 960 kedge.getLabels().add(label); 961 962 // remember it 963 _divaLabel.put(label, labelFigure); 964 } 965 } 966 } 967 968 /** 969 * Create a dummy edge for an attribute that is relative locatable. The edge 970 * will only be used to indicate the association between the attribute and 971 * its reference object. 972 * 973 * @param attribute the attribute for which to create a dummy edge 974 */ 975 private void _createKEdgeForAttribute(NamedObj attribute) { 976 Locatable source = KielerLayoutUtil.getLocation(attribute); 977 if (source instanceof RelativeLocation) { 978 NamedObj referenceObj = PtolemyModelUtil 979 ._getReferencedObj((RelativeLocation) source); 980 if (referenceObj != null) { 981 Locatable target = KielerLayoutUtil.getLocation(referenceObj); 982 KNode sourceNode = _kieler2ptolemyDivaNodes.inverse() 983 .get(source); 984 KNode targetNode = _kieler2ptolemyDivaNodes.inverse() 985 .get(target); 986 if (sourceNode != null && targetNode != null) { 987 // Create a dummy edge to connect the comment box to its reference. 988 KEdge newEdge = KimlUtil.createInitializedEdge(); 989 newEdge.setSource(sourceNode); 990 newEdge.setTarget(targetNode); 991 992 KEdgeLayout edgeLayout = newEdge.getData(KEdgeLayout.class); 993 double[] sourcePos = source.getLocation(); 994 edgeLayout.getSourcePoint().setX((float) sourcePos[0]); 995 edgeLayout.getSourcePoint().setY((float) sourcePos[1]); 996 double[] targetPos = target.getLocation(); 997 edgeLayout.getTargetPoint().setX((float) targetPos[0]); 998 edgeLayout.getTargetPoint().setY((float) targetPos[1]); 999 } 1000 } 1001 } 1002 } 1003 1004 /** 1005 * Create a new KIELER KNode corresponding to a Ptolemy diva node and its 1006 * Ptolemy semantic object (e.g. an Actor). 1007 * 1008 * The newly created node is stored with the corresponding diva and ptolemy 1009 * nodes in the global maps _ptolemy2KielerNodes, _kieler2ptolemyDivaNodes, 1010 * _kieler2ptolemyEntityNodes, such that the {@link #_applyLayout(KNode)} 1011 * method will be able to reapply the layout. 1012 * 1013 * @param node The Diva node object. 1014 * @param semanticNode The corresponding Ptolemy semantic object, e.g. an 1015 * Actor or TextAttribute 1016 * @return The initialized KIELER KNode 1017 */ 1018 private KNode _createKNode(Object node, NamedObj semanticNode) { 1019 Rectangle2D bounds; 1020 if (semanticNode instanceof RelativeLocatable) { 1021 // RelativeLocatables may have a dashed line to show which actor 1022 // they are attached to. This line must not be part of the size 1023 // calculation, so we calculate the size of the object manually 1024 // without taking the line into account 1025 Figure figure = (Figure) getLayoutTarget().getVisualObject(node); 1026 1027 // The figure should be a composite figure, but let's be sure 1028 if (figure instanceof CompositeFigure) { 1029 CompositeFigure compFigure = (CompositeFigure) figure; 1030 1031 bounds = new Rectangle2D.Double(); 1032 bounds.add(compFigure.getBackgroundFigure().getBounds()); 1033 1034 // Iterate over the composite figure's component, adding the 1035 // bounds of each to arrive at the final bounds without dashed 1036 // line (RelativeLinkFigure) 1037 Iterator parts = compFigure.figures(); 1038 while (parts.hasNext()) { 1039 Figure part = (Figure) parts.next(); 1040 1041 if (!(part instanceof RelativeLinkFigure)) { 1042 bounds.add(part.getBounds()); 1043 } 1044 } 1045 } else { 1046 // It's not a composite figure, so use figure's bounds 1047 bounds = getLayoutTarget().getBounds(node); 1048 } 1049 } else { 1050 bounds = getLayoutTarget().getBounds(node); 1051 } 1052 1053 // Create new node in KIELER graph and apply the initial size and position 1054 KNode knode = KimlUtil.createInitializedNode(); 1055 KShapeLayout nodeLayout = knode.getData(KShapeLayout.class); 1056 nodeLayout.setWidth((float) bounds.getWidth()); 1057 nodeLayout.setHeight((float) bounds.getHeight()); 1058 nodeLayout.setXpos((float) bounds.getMinX()); 1059 nodeLayout.setYpos((float) bounds.getMinY()); 1060 nodeLayout.setProperty(LayoutOptions.SIZE_CONSTRAINT, 1061 SizeConstraint.fixed()); 1062 if (semanticNode instanceof Attribute) { 1063 nodeLayout.setProperty(LayoutOptions.COMMENT_BOX, true); 1064 } else { 1065 nodeLayout.setProperty(LayoutOptions.PORT_CONSTRAINTS, 1066 PortConstraints.FIXED_POS); 1067 } 1068 1069 // set the node label 1070 KLabel label = KimlUtil.createInitializedLabel(knode); 1071 label.setText(semanticNode.getDisplayName()); 1072 KShapeLayout labelLayout = label.getData(KShapeLayout.class); 1073 labelLayout.setWidth((float) bounds.getWidth() - 2); 1074 labelLayout.setHeight(10); 1075 labelLayout.setXpos(1); 1076 labelLayout.setYpos(1); 1077 knode.getLabels().add(label); 1078 1079 // Draw the director always as first element. 1080 if (semanticNode instanceof Director) { 1081 nodeLayout.setProperty(LayoutOptions.PRIORITY, 1); 1082 } 1083 1084 return knode; 1085 } 1086 1087 /** 1088 * Create a KIELER KNode for a Ptolemy inner port. That is the graphical 1089 * representation for a port of a CompositeActor if you see the contents of 1090 * this CompositeActor. It is represented by a node where the connection may 1091 * touch the node corresponding to its type (input, output, both) on the 1092 * right, left or top. 1093 * 1094 * For now this results a crude approximation of the node, because the 1095 * figure of the original Ptolemy port cannot be obtained by the layout 1096 * target. Hence we cannot ask the port for its original bounds. 1097 * 1098 * @param divaLocation Diva Representation of an inner port 1099 * @param port The Ptolemy inner port. 1100 * @return A new KIELER node corresponding to the Ptolemy inner port. 1101 */ 1102 private KNode _createKNodeForPort(Object divaLocation, ComponentPort port) { 1103 KNode knode = KimlUtil.createInitializedNode(); 1104 KShapeLayout layout = knode.getData(KShapeLayout.class); 1105 layout.setProperty(LayoutOptions.PORT_CONSTRAINTS, 1106 PortConstraints.FIXED_POS); 1107 1108 // Add ports of state diagrams to designated containers to pool them 1109 if (_graphModel instanceof FSMGraphModel 1110 && port instanceof TypedIOPort) { 1111 boolean isInput = ((TypedIOPort) port).isInput(); 1112 boolean isOutput = ((TypedIOPort) port).isOutput(); 1113 knode.getData(KLayoutData.class) 1114 .setProperty(LayoutOptions.ALIGNMENT, Alignment.LEFT); 1115 if (isInput && !isOutput) { 1116 knode.setParent(_fsmInput); 1117 } else if (isOutput && !isInput) { 1118 knode.setParent(_fsmOutput); 1119 } else { 1120 knode.setParent(_fsmInputOutput); 1121 knode.getData(KLayoutData.class) 1122 .setProperty(LayoutOptions.ALIGNMENT, Alignment.BOTTOM); 1123 } 1124 } 1125 1126 Rectangle2D figureBounds = getLayoutTarget().getBounds(divaLocation); 1127 Rectangle2D shapeBounds = figureBounds; 1128 // Try to find more specific bounds of the shape that do not include the name label. 1129 Object obj = getLayoutTarget().getVisualObject(divaLocation); 1130 if (obj instanceof PortTerminal) { 1131 PortTerminal portVisual = (PortTerminal) obj; 1132 shapeBounds = portVisual.getShape().getBounds(); 1133 } 1134 // Set alignment offset in order to set right height. 1135 layout.setHeight( 1136 (float) figureBounds.getHeight() + INNER_PORT_HEIGHT_OFFSET); 1137 layout.setWidth((float) figureBounds.getWidth()); 1138 layout.setProperty(LayoutOptions.SIZE_CONSTRAINT, 1139 SizeConstraint.fixed()); 1140 layout.setXpos((float) figureBounds.getMinX()); 1141 layout.setYpos((float) figureBounds.getMinY()); 1142 1143 // Create KIELER ports to specify anchor points for the layouter. 1144 // These constants are black magic, gotten by trial and error. 1145 KVector portBase = new KVector(MULTIPORT_INNER_OFFSET); 1146 portBase.y += figureBounds.getHeight() - shapeBounds.getHeight(); 1147 int direction = PtolemyModelUtil._getExternalPortDirection(port); 1148 switch (direction) { 1149 case SwingConstants.NORTH: 1150 portBase.x += shapeBounds.getWidth() / 2; 1151 break; 1152 case SwingConstants.SOUTH: 1153 portBase.x += shapeBounds.getWidth() / 2; 1154 portBase.y += shapeBounds.getHeight(); 1155 break; 1156 case SwingConstants.EAST: 1157 portBase.x += shapeBounds.getWidth(); 1158 portBase.y += shapeBounds.getHeight() / 2; 1159 break; 1160 default: 1161 portBase.y += shapeBounds.getHeight() / 2; 1162 } 1163 1164 // Create a port for each incoming relation and set an order. 1165 List relations = port.insideRelationList(); 1166 int maxIndex = relations.size() - 1; 1167 for (int index = 0; index < relations.size(); index++) { 1168 KPort kPort = KimlUtil.createInitializedPort(); 1169 KShapeLayout portLayout = kPort.getData(KShapeLayout.class); 1170 portLayout.setHeight(DEFAULT_PORT_SIZE); 1171 portLayout.setWidth(DEFAULT_PORT_SIZE); 1172 KVector offset = _getMultiportOffsets(port, portLayout, index, 1173 maxIndex, false); 1174 portLayout.setXpos((float) (portBase.x + offset.x)); 1175 portLayout.setYpos((float) (portBase.y + offset.y) 1176 - portLayout.getHeight() / 2); 1177 kPort.setNode(knode); 1178 _ptolemy2KielerPorts.put(port, kPort); 1179 } 1180 return knode; 1181 } 1182 1183 /** 1184 * Create a KIELER node for a Ptolemy Vertex. Vertices of Ptolemy can be 1185 * handles as usual KNodes in KIELER (an alternative would be to handle them 1186 * as connection bend points). 1187 * 1188 * @param vertex The Ptolemy vertex for which to create a KNode 1189 * @return An initialized KNode 1190 */ 1191 private KNode _createKNodeForVertex(Vertex vertex) { 1192 Rectangle2D bounds = getLayoutTarget().getBounds(vertex); 1193 KNode knode = KimlUtil.createInitializedNode(); 1194 KShapeLayout nodeLayout = knode.getData(KShapeLayout.class); 1195 nodeLayout.setHeight((float) bounds.getHeight()); 1196 nodeLayout.setWidth((float) bounds.getWidth()); 1197 nodeLayout.setXpos((float) bounds.getMinX()); 1198 nodeLayout.setYpos((float) bounds.getMinY()); 1199 nodeLayout.setProperty(LayoutOptions.SIZE_CONSTRAINT, 1200 SizeConstraint.fixed()); 1201 nodeLayout.setProperty(LayoutOptions.HYPERNODE, true); 1202 return knode; 1203 } 1204 1205 /** 1206 * Create a KIELER node for a Ptolemy State. 1207 * 1208 * @param node The Diva node object 1209 * @param state The Ptolemy state for which to create a KNode 1210 * @return An initialized KNode 1211 */ 1212 private KNode _createKNodeForState(Object node, State state) { 1213 Rectangle2D bounds = getLayoutTarget().getBounds(node); 1214 KNode knode = KimlUtil.createInitializedNode(); 1215 KShapeLayout nodeLayout = knode.getData(KShapeLayout.class); 1216 nodeLayout.setHeight((float) bounds.getHeight()); 1217 nodeLayout.setWidth((float) bounds.getWidth()); 1218 nodeLayout.setXpos((float) bounds.getMinX()); 1219 nodeLayout.setYpos((float) bounds.getMinY()); 1220 nodeLayout.setProperty(LayoutOptions.SIZE_CONSTRAINT, 1221 SizeConstraint.fixed()); 1222 nodeLayout.setProperty(LayoutOptions.PORT_CONSTRAINTS, 1223 PortConstraints.FREE); 1224 1225 if (PtolemyModelUtil._isInitialState(state) 1226 && !PtolemyModelUtil._isFinalState(state)) { 1227 nodeLayout.setProperty(Properties.LAYER_CONSTRAINT, 1228 LayerConstraint.FIRST); 1229 } 1230 1231 if (!PtolemyModelUtil._isInitialState(state) 1232 && PtolemyModelUtil._isFinalState(state)) { 1233 nodeLayout.setProperty(Properties.LAYER_CONSTRAINT, 1234 LayerConstraint.LAST); 1235 } 1236 1237 KLabel label = KimlUtil.createInitializedLabel(knode); 1238 label.setText(state.getDisplayName()); 1239 KShapeLayout labelLayout = label.getData(KShapeLayout.class); 1240 labelLayout.setWidth(nodeLayout.getWidth() - 2); 1241 labelLayout.setHeight(nodeLayout.getHeight() - 2); 1242 labelLayout.setXpos(1); 1243 labelLayout.setYpos(1); 1244 knode.getLabels().add(label); 1245 return knode; 1246 } 1247 1248 /** 1249 * Create a KIELER KPort corresponding to a Ptolemy Port. Set the size and 1250 * position (relative to parent) and the direction of the port in the KPort 1251 * layout information. Since KIELER does not explicitly support multiports as 1252 * Ptolemy, this is emulated by creating multiple distinct ports with a 1253 * little offset each. Create only one node. For multiports call this method 1254 * multiple times with changed parameters. 1255 * 1256 * The newly created port is stored with the corresponding ptolemy port in 1257 * the global maps _kieler2PtolemyPorts, _ptolemy2KielerPorts, such that the 1258 * {@link #_applyLayout(KNode)} method will be able to reapply the layout. 1259 * 1260 * @param knode The parent KNode of the new port 1261 * @param port The corresponding Ptolemy port (might be a multiport) 1262 * @param index Index of the KPort corresponding to a multiport 1263 * @param maxIndex Width of the multiport, i.e. the number of connected 1264 * edges to that port. 1265 * @param size Custom size (same for width and height) for a port that will 1266 * be used instead of the real Ptolemy port size. If this value 1267 * is negative, the original Ptolemy sizes are used. 1268 */ 1269 private void _createKPort(KNode knode, Port port, int index, int maxIndex, 1270 float size) { 1271 // Create a new KIELER port and initialize its layout. 1272 KPort kport = KimlUtil.createInitializedPort(); 1273 knode.getPorts().add(kport); 1274 KShapeLayout kportlayout = kport.getData(KShapeLayout.class); 1275 kportlayout.setHeight(DEFAULT_PORT_SIZE); 1276 kportlayout.setWidth(DEFAULT_PORT_SIZE); 1277 1278 // Set port side and calculate actual offset. 1279 KVector offset = _getMultiportOffsets(port, kportlayout, index, 1280 maxIndex, true); 1281 1282 // Try to set actual layout (size and position) 1283 Object portObject = getLayoutTarget().getVisualObject(port); 1284 if (portObject instanceof PortTerminal) { 1285 // Get visual Diva figure of port. 1286 PortTerminal portFigure = (PortTerminal) portObject; 1287 // Get bounds of the port figure (= relative to center of actor 1288 // symbol here given by referenceLocation). 1289 Rectangle2D portBounds = portFigure.getBounds(); 1290 // Get the parent Diva figure which is the whole composite consisting of 1291 // the actor icon and its name, which might be arbitrary large. 1292 CanvasComponent parent = portFigure.getParent(); 1293 if (parent instanceof CompositeFigure) { 1294 CompositeFigure parentFigure = (CompositeFigure) parent; 1295 1296 AffineTransform parentTransform = parentFigure 1297 .getTransformContext().getTransform(); 1298 Point2D.Double portLocation = new Point2D.Double( 1299 portBounds.getMinX(), portBounds.getMinY()); 1300 Point2D.Double transformedLocation = new Point2D.Double(); 1301 parentTransform.transform(portLocation, transformedLocation); 1302 // Calculate coordinates relative to the KIELER nodes top left from absolutes. 1303 double width = portBounds.getWidth(); 1304 double height = portBounds.getHeight(); 1305 double x = transformedLocation.getX() 1306 - parentFigure.getBounds().getMinX() + offset.x; 1307 double y = transformedLocation.getY() 1308 - parentFigure.getBounds().getMinY() + offset.y; 1309 kportlayout.setXpos((float) x); 1310 kportlayout.setYpos((float) y); 1311 // No valid size given -> use diagram port size 1312 if (size < 0) { 1313 kportlayout.setWidth((float) width); 1314 kportlayout.setHeight((float) height); 1315 } else { 1316 // If we want to use some custom port size, the new coordinates 1317 // need to be adapted to the new size. 1318 Rectangle2D newPortBounds = new Rectangle2D.Double(); 1319 newPortBounds.setRect(x, y, width, height); 1320 Rectangle2D shrunkPortBounds = new Rectangle2D.Double(); 1321 shrunkPortBounds.setRect(x, y, size, size); 1322 int direction = IOPortController.getDirection( 1323 IOPortController.getCardinality(port)); 1324 Point2D shrunkenLocation = KielerGraphUtil 1325 ._shrinkCoordinates(newPortBounds, shrunkPortBounds, 1326 direction, MULTIPORT_BOTTOM); 1327 kportlayout.setXpos((float) shrunkenLocation.getX()); 1328 kportlayout.setYpos((float) shrunkenLocation.getY()); 1329 kportlayout.setWidth(size); 1330 kportlayout.setHeight(size); 1331 } 1332 } 1333 } 1334 // Put ports in global maps for later use. 1335 _ptolemy2KielerPorts.put(port, kport); 1336 } 1337 1338 /** 1339 * Create KIELER ports (KPort) for a KIELER node (KNode) given a list of 1340 * Ptolemy Port objects and a port type (incoming, outgoing). The new KPorts 1341 * are initialized in terms of size and position from the Ptolemy 1342 * counterparts and attached to the corresponding KNode and registered in 1343 * the mapping fields of this object. 1344 * 1345 * For Ptolemy multiports with multiple connections, multiple KIELER KPorts 1346 * are created with slightly offset location. Hence the layouter can also 1347 * consider the location for connection crossing minimization. Hence 1348 * one Ptolemy port may correspond to multiple KIELER KPorts. 1349 * 1350 * @param knode The KNode to create KIELER ports for. 1351 * @param ports The Ptolemy ports counterparts for which to create KIELER 1352 * ports. 1353 */ 1354 private void _createKPorts(KNode knode, List<Port> ports) { 1355 for (Port port : ports) { 1356 // Handle multiports for inputs. 1357 if (port.linkedRelationList().size() > 1) { 1358 // Create a port for each incoming relation and set an order. 1359 List relations = port.linkedRelationList(); 1360 int maxIndex = relations.size() - 1; 1361 for (int index = 0; index < relations.size(); index++) { 1362 _createKPort(knode, port, index, maxIndex, 1363 DEFAULT_PORT_SIZE); 1364 } 1365 } else { 1366 // If not a multiport, just create one port. 1367 _createKPort(knode, port, 0, 0, -1); 1368 } 1369 } 1370 } 1371 1372 /** 1373 * Get a KIELER KPort for a corresponding Ptolemy object, i.e. a Port or a 1374 * relation Vertex. If the input is a Vertex, it is determined which of the 1375 * two KPorts of the corresponding KNode is returned (since in KIELER a Vertex 1376 * is represented by one node with one input and one output port). 1377 * 1378 * If the input object is a Ptolemy Port, the KPort counterpart is searched 1379 * in the global maps. If additionally the Port is a multiport with multiple 1380 * connection, the given relation is used to determine which KPort 1381 * corresponds to the Port/Relation combination. In KIELER multiports are 1382 * represented by multiple KPorts with slightly offset locations. 1383 * 1384 * @param ptolemyObject The corresponding Ptolemy object, either a Vertex or 1385 * a Port 1386 * @param relation The relation that is connected to the Ptolemy multiport 1387 * @return The found port, or null if no port was found. 1388 */ 1389 private KPort _getPort(Object ptolemyObject, Relation relation) { 1390 if (ptolemyObject instanceof Vertex) { 1391 KNode knode = _kieler2ptolemyDivaNodes.inverse().get(ptolemyObject); 1392 for (KPort port : knode.getPorts()) { 1393 return port; 1394 } 1395 } 1396 List<Relation> relations = null; 1397 if (ptolemyObject instanceof Location) { 1398 // Handle an inner port represented by a Location. 1399 // The real port object is its container. 1400 ptolemyObject = ((Location) ptolemyObject).getContainer(); 1401 if (ptolemyObject instanceof ComponentPort) { 1402 relations = ((ComponentPort) ptolemyObject) 1403 .insideRelationList(); 1404 } 1405 } 1406 if (ptolemyObject instanceof Port && relation != null) { 1407 // Special case for multiports: For a particular relation, get its 1408 // index in the relation list of the port. Then get the KIELER port with 1409 // the same index (as we mapped one Ptolemy multiport to a list of multiple 1410 // KIELER ports). For a simple port, just give the first corresponding port. 1411 if (relations == null) { 1412 // In this case: outer port 1413 relations = ((Port) ptolemyObject).linkedRelationList(); 1414 } 1415 int index = relations.indexOf(relation); 1416 List<KPort> kports = _ptolemy2KielerPorts.get((Port) ptolemyObject); 1417 if (index >= 0 && index < kports.size()) { 1418 return kports.get(index); 1419 } 1420 return kports.get(0); 1421 } 1422 return null; 1423 } 1424 1425 /** 1426 * Transform a location from a KIELER node from KIELER coordinate system to 1427 * ptolemy coordinate system. That is KIELER gives locations to be the upper 1428 * left corner of an item and Ptolemy as the center point of the item. 1429 * 1430 * If the original location is not within the bounds of the referenceNode at 1431 * all, the location updated differently. This is an important distinction between 1432 * nodes and vertices. 1433 * 1434 * @param pos Position of KIELER graphical node. This object will be altered to 1435 * fit the new location. 1436 * @param divaNode the graphical representation in Diva 1437 * @param locatable the location object of the node 1438 */ 1439 private void _kNode2Ptolemy(KVector pos, Object divaNode, 1440 Locatable locatable) { 1441 Point2D location = KielerLayoutUtil.getLocationPoint(locatable); 1442 if (divaNode != null) { 1443 Rectangle2D divaBounds; 1444 if (locatable instanceof RelativeLocation) { 1445 // Consider only the background figure of a composite figure 1446 // if the node is relative locatable, otherwise the link would also 1447 // be included in the bounds. 1448 Figure figure = (Figure) getLayoutTarget() 1449 .getVisualObject(divaNode); 1450 divaBounds = figure.getShape().getBounds2D(); 1451 } else { 1452 divaBounds = getLayoutTarget().getBounds(divaNode); 1453 } 1454 double offsetX = 0, offsetY = 0; 1455 1456 // Check whether the location could be determined. 1457 // If not, we might have something that has no location attribute, 1458 // such as a relation vertex, where we use the center as offset. 1459 if (location != null) { 1460 offsetX = location.getX() - divaBounds.getMinX(); 1461 offsetY = location.getY() - divaBounds.getMinY(); 1462 } else { 1463 offsetX = divaBounds.getWidth() / 2; 1464 offsetY = divaBounds.getHeight() / 2; 1465 } 1466 1467 pos.x += offsetX; 1468 pos.y += offsetY; 1469 } 1470 } 1471 1472 /** 1473 * Report a message to the top window status handler if it is available. 1474 * 1475 * @param message The message to be reported. 1476 */ 1477 private void _report(String message) { 1478 if (_top != null) { 1479 _top.report(message); 1480 } 1481 } 1482 1483 /** 1484 * Determine the direction of dataflow of all edges and store it in the 1485 * local maps. Iterate all edges and try to deduce the type of each edge's 1486 * endpoints, i.e. whether it is an source or target. Do this in multiple 1487 * iterations by first getting clear information from input and output ports 1488 * and then propagate this information to the adjacent edges. 1489 * 1490 * @param unprocessedEdges The list of edges that need processing. 1491 */ 1492 private void _storeEndpoints(List<Link> unprocessedEdges) { 1493 ActorGraphModel aGraph = (ActorGraphModel) getLayoutTarget() 1494 .getGraphModel(); 1495 boolean allDirectionsSet = false; 1496 boolean progress = false; 1497 while (!allDirectionsSet) { 1498 allDirectionsSet = true; 1499 progress = false; 1500 for (Iterator<Link> edgeIter = unprocessedEdges.iterator(); edgeIter 1501 .hasNext();) { 1502 Link edge = edgeIter.next(); 1503 EdgeModel edgeModel = aGraph.getEdgeModel(edge); 1504 1505 Object simpleEndpoint1 = edgeModel.getHead(edge); 1506 Object simpleEndpoint2 = edgeModel.getTail(edge); 1507 Object endpoint1 = aGraph.getSemanticObject(simpleEndpoint1); 1508 Object endpoint2 = aGraph.getSemanticObject(simpleEndpoint2); 1509 1510 // See if we have successfully looked at this edge before. 1511 if (_divaEdgeTarget.containsKey(edge) 1512 && _divaEdgeSource.containsKey(edge)) { 1513 continue; 1514 } 1515 1516 // Check whether endpoints are source or target ports. 1517 if (endpoint1 instanceof Port) { 1518 boolean isInput1 = PtolemyModelUtil 1519 ._isInput((Port) endpoint1); 1520 // Check if we look at inner or outer ports. 1521 if (simpleEndpoint1 instanceof Location) { 1522 // Inner input port is regarded as output. 1523 isInput1 = !isInput1; 1524 } 1525 // Set endpoints. 1526 if (isInput1) { 1527 _divaEdgeTarget.put(edge, simpleEndpoint1); 1528 _divaEdgeSource.put(edge, simpleEndpoint2); 1529 progress = true; 1530 } else { 1531 _divaEdgeTarget.put(edge, simpleEndpoint2); 1532 _divaEdgeSource.put(edge, simpleEndpoint1); 1533 progress = true; 1534 } 1535 1536 } else if (endpoint2 instanceof Port) { 1537 boolean isInput2 = PtolemyModelUtil 1538 ._isInput((Port) endpoint2); 1539 // Check if we look at inner or outer ports. 1540 if (simpleEndpoint2 instanceof Location) { 1541 // Inner input port is regarded as output. 1542 isInput2 = !isInput2; 1543 } 1544 // Set endpoints. 1545 if (isInput2) { 1546 _divaEdgeTarget.put(edge, simpleEndpoint2); 1547 _divaEdgeSource.put(edge, simpleEndpoint1); 1548 progress = true; 1549 } else { 1550 _divaEdgeTarget.put(edge, simpleEndpoint1); 1551 _divaEdgeSource.put(edge, simpleEndpoint2); 1552 progress = true; 1553 } 1554 1555 } else 1556 // See if one of the endpoints is source or target of other edges. 1557 if (_divaEdgeTarget.containsValue(simpleEndpoint1)) { 1558 _divaEdgeTarget.put(edge, simpleEndpoint2); 1559 _divaEdgeSource.put(edge, simpleEndpoint1); 1560 progress = true; 1561 } else if (_divaEdgeTarget.containsValue(simpleEndpoint2)) { 1562 _divaEdgeTarget.put(edge, simpleEndpoint1); 1563 _divaEdgeSource.put(edge, simpleEndpoint2); 1564 progress = true; 1565 } else if (_divaEdgeSource.containsValue(simpleEndpoint1)) { 1566 _divaEdgeTarget.put(edge, simpleEndpoint1); 1567 _divaEdgeSource.put(edge, simpleEndpoint2); 1568 progress = true; 1569 } else if (_divaEdgeSource.containsValue(simpleEndpoint2)) { 1570 _divaEdgeTarget.put(edge, simpleEndpoint2); 1571 _divaEdgeSource.put(edge, simpleEndpoint1); 1572 progress = true; 1573 } else { 1574 // Now we can't deduce any information about this edge. 1575 allDirectionsSet = false; 1576 } 1577 1578 // Guarantee progress by just setting the direction if it cannot be deduced. 1579 if (!edgeIter.hasNext() && !progress) { 1580 _divaEdgeTarget.put(edge, simpleEndpoint1); 1581 _divaEdgeSource.put(edge, simpleEndpoint2); 1582 } 1583 } 1584 } 1585 } 1586 1587 /** 1588 * For a given Ptolemy port, its channel index in a multiport, and the 1589 * maximum index in that multiport, calculate its offset in X and Y 1590 * coordinates. For example, the first channel on the east side has offset 0 1591 * and the next channel is moved below the first one and so on. On the north 1592 * side, the last channel has offset 0 and the first channel is at the most 1593 * left side. 1594 * 1595 * @param port the Ptolemy port 1596 * @param kportlayout the corresponding KPort KShapeLayout 1597 * @param index index of the channel 1598 * @param maxIndex maximum available channel 1599 * @param outer True if the direction of the ports is obtained by calling 1600 * IOPortController.getDirection(). Otherwise, the direction is obtained 1601 * by calling PtolemyModelUtil._getExternalPortDirection(). 1602 * @return offset vector 1603 */ 1604 protected static KVector _getMultiportOffsets(Port port, 1605 KShapeLayout kportlayout, int index, int maxIndex, boolean outer) { 1606 KVector offset = new KVector(); 1607 int direction = 0; 1608 if (outer) { 1609 direction = IOPortController 1610 .getDirection(IOPortController.getCardinality(port)); 1611 } else { 1612 direction = PtolemyModelUtil._getExternalPortDirection(port); 1613 } 1614 switch (direction) { 1615 case SwingConstants.NORTH: 1616 kportlayout.setProperty(LayoutOptions.PORT_SIDE, PortSide.NORTH); 1617 // Ports are extended to left with leftmost port index 0. 1618 offset.x = -((maxIndex - index) * MULTIPORT_OFFSET); 1619 break; 1620 case SwingConstants.EAST: 1621 kportlayout.setProperty(LayoutOptions.PORT_SIDE, PortSide.EAST); 1622 // Ports are extended to bottom with top port index 0. 1623 offset.y = index * MULTIPORT_OFFSET; 1624 break; 1625 case SwingConstants.SOUTH: 1626 kportlayout.setProperty(LayoutOptions.PORT_SIDE, PortSide.SOUTH); 1627 offset.x = index * MULTIPORT_OFFSET; 1628 break; 1629 default: 1630 kportlayout.setProperty(LayoutOptions.PORT_SIDE, PortSide.WEST); 1631 // Ports are extended to top beginning with top port index 0. 1632 offset.y = -((maxIndex - index) * MULTIPORT_OFFSET); 1633 break; 1634 } 1635 return offset; 1636 } 1637 1638 /////////////////////////////////////////////////////////////////// 1639 //// private variables //// 1640 1641 /** 1642 * Offset between KIELER KPorts corresponding to a Ptolemy multiport. I.e. 1643 * the distance between multiple single KPorts. 1644 */ 1645 private static final float MULTIPORT_OFFSET = 5.0f; 1646 1647 /** 1648 * Debug flag that will trigger output of additional information during 1649 * layout run. With the flag set to true especially the KIELER graph 1650 * structure will be written to a file on hard disk in order to review the 1651 * graph later on. 1652 */ 1653 private static final boolean DEBUG = false; 1654 1655 /** 1656 * Default size of a port that will be used in KIELER layout if no explicit 1657 * size (e.g. copied from Ptolemy port) is given. 1658 */ 1659 private static final float DEFAULT_PORT_SIZE = 5.0f; 1660 1661 /** 1662 * The offset height used by KIELER for inner ports to correct connection anchor. 1663 */ 1664 private static final float INNER_PORT_HEIGHT_OFFSET = 11.0f; 1665 1666 /** 1667 * Offset at the x and y-coordinate of the shape of a Multiport to its figure. 1668 */ 1669 private static final KVector MULTIPORT_INNER_OFFSET = new KVector(3f, -3f); 1670 1671 /** 1672 * Offset between bottom of Multiport to the first KIELER KPort 1673 */ 1674 private static final float MULTIPORT_BOTTOM = 4.5f; 1675 1676 /** 1677 * A pool of layout provider instances, which are used to perform the actual 1678 * layout. The pool is accessed statically, so that its instances can be reused 1679 * in subsequent layout runs. 1680 */ 1681 private static InstancePool<AbstractLayoutProvider> _layoutProviderPool; 1682 1683 /** 1684 * The top level Ptolemy composite entity that contains the diagram that is 1685 * to be laid out. 1686 */ 1687 private CompositeEntity _compositeEntity; 1688 1689 /** 1690 * Storage of actual sources of diva edges corresponding to data flow. 1691 */ 1692 private Map<Link, Object> _divaEdgeSource; 1693 1694 /** 1695 * Storage of actual targets of diva edges corresponding to data flow. 1696 */ 1697 private Map<Link, Object> _divaEdgeTarget; 1698 1699 /** 1700 * Mapping of KIELER labels to corresponding diva labels. 1701 */ 1702 private Map<KLabel, LabelFigure> _divaLabel; 1703 1704 /** 1705 * List of KIELER edges and corresponding Diva links. 1706 */ 1707 private List<Pair<KEdge, Link>> _edgeList; 1708 1709 /** 1710 * Map KIELER nodes to Diva Nodes and back. 1711 */ 1712 private BiMap<KNode, Locatable> _kieler2ptolemyDivaNodes; 1713 1714 /** 1715 * Map Ptolemy ports to KIELER ports. A Ptolemy multiport could correspond 1716 * to multiple KIELER ports. Hence it's a mapping to a List of KPorts. 1717 */ 1718 private ListMultimap<Port, KPort> _ptolemy2KielerPorts; 1719 1720 /** 1721 * Pointer to Top in order to report the current status. 1722 */ 1723 private Top _top; 1724 1725 /** The graph model that is about to be laid out. */ 1726 private GraphModel _graphModel; 1727 1728 /** 1729 * When layouting FSMs, we use the following nodes to separate 1730 * ports from the actual model. 1731 */ 1732 private KNode _fsmInput; 1733 private KNode _fsmOutput; 1734 private KNode _fsmInputOutput; 1735 1736}