001/* The default Ptolemy layout with place and route. 002 003 Copyright (c) 2011-2016 The Regents of the University of California. 004 All rights reserved. 005 Permission is hereby granted, without written agreement and without 006 license or royalty fees, to use, copy, modify, and distribute this 007 software and its documentation for any purpose, provided that the above 008 copyright notice and the following two paragraphs appear in all copies 009 of this software. 010 011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 015 SUCH DAMAGE. 016 017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 022 ENHANCEMENTS, OR MODIFICATIONS. 023 024 PT_COPYRIGHT_VERSION_2 025 COPYRIGHTENDKEY 026 2 027 */ 028package ptolemy.vergil.basic; 029 030import java.awt.geom.Rectangle2D; 031import java.util.HashMap; 032import java.util.Iterator; 033 034import javax.swing.JFrame; 035import javax.swing.SwingConstants; 036 037import diva.canvas.CanvasUtilities; 038import diva.canvas.Figure; 039import diva.canvas.Site; 040import diva.canvas.connector.FixedNormalSite; 041import diva.canvas.connector.Terminal; 042import diva.graph.GraphController; 043import diva.graph.GraphModel; 044import diva.graph.GraphUtilities; 045import diva.graph.basic.BasicLayoutTarget; 046import diva.graph.layout.LayoutTarget; 047import diva.graph.layout.LevelLayout; 048import ptolemy.actor.gui.Configuration; 049import ptolemy.actor.gui.Tableau; 050import ptolemy.kernel.undo.UndoStackAttribute; 051import ptolemy.kernel.util.IllegalActionException; 052import ptolemy.kernel.util.InternalErrorException; 053import ptolemy.kernel.util.Locatable; 054import ptolemy.kernel.util.Location; 055import ptolemy.kernel.util.NamedObj; 056import ptolemy.moml.MoMLUndoEntry; 057import ptolemy.util.MessageHandler; 058import ptolemy.vergil.actor.ActorGraphFrame; 059import ptolemy.vergil.modal.FSMGraphFrame; 060 061/////////////////////////////////////////////////////////////////// 062//// PtolemyLayoutAction 063 064/** 065Trigger the Ptolemy place and route automatic dataflow layout algorithm 066from withing the Vergil GUI. Operate on the current model, hence the 067model needs to be an input in the doAction() method. 068 069<p>The Ptolemy layout mechanism produces layouts that are not as 070good as the Kieler layout mechanism, so use the @see KielerLayoutMechanism.</p> 071 072@author Christopher Brooks, based on KielerLayoutAction by Christian Motika and BasicGraphFrame by Steve Neuendorffer and Edward A. Lee 073@version $Id$ 074@since Ptolemy II 10.0 075@Pt.ProposedRating Red (cmot) 076@Pt.AcceptedRating Red (cmot) 077 */ 078public class PtolemyLayoutAction extends Object implements IGuiAction { 079 080 /** 081 * Layout the graph if the model is a CompositeActor. Otherwise throw an 082 * exception. The frame type must be ActorGraphFrame. The Ptolemy layouter. 083 * is called with placing and routing. 084 * 085 * @param model the model 086 */ 087 @Override 088 public void doAction(NamedObj model) { 089 try { 090 JFrame frame = null; 091 Iterator tableaux = Configuration.findEffigy(model) 092 .entityList(Tableau.class).iterator(); 093 while (tableaux.hasNext()) { 094 Tableau tableau = (Tableau) tableaux.next(); 095 if (tableau.getFrame() instanceof ActorGraphFrame 096 || tableau.getFrame() instanceof FSMGraphFrame) { 097 frame = tableau.getFrame(); 098 } 099 } 100 _graphFrame = (BasicGraphFrame) frame; 101 102 // fetch everything needed to build the LayoutTarget 103 GraphController graphController = _graphFrame.getJGraph() 104 .getGraphPane().getGraphController(); 105 AbstractBasicGraphModel graphModel = (AbstractBasicGraphModel) _graphFrame 106 .getJGraph().getGraphPane().getGraphController() 107 .getGraphModel(); 108 BasicLayoutTarget layoutTarget = new PtolemyLayoutTarget( 109 graphController); 110 111 // create Ptolemy layouter for this layout target 112 PtolemyLayout layout = new PtolemyLayout(layoutTarget); 113 // layout.setModel((CompositeActor) model); 114 // layout.setApplyEdgeLayout(false); 115 // layout.setApplyEdgeLayoutBendPointAnnotation(true); 116 // layout.setBoxLayout(false); 117 // layout.setTop(graphFrame); 118 119 layout.setOrientation(LevelLayout.HORIZONTAL); 120 layout.setRandomizedPlacement(false); 121 122 // Before doing the layout, need to take a copy of all the current 123 // node locations which can be used to undo the effects of the move. 124 try { 125 NamedObj composite = graphModel.getPtolemyModel(); 126 127 StringBuffer moml = new StringBuffer(); 128 moml.append("<group>\n"); 129 130 // NOTE: this gives at iteration over locations. 131 Iterator<Location> nodes = graphModel.nodes(composite); 132 133 while (nodes.hasNext()) { 134 Location location = nodes.next(); 135 136 // Get the containing element. 137 NamedObj element = location.getContainer(); 138 139 // Give default values in case the previous locations value 140 // has not yet been set. 141 String expression = location.getExpression(); 142 143 if (expression == null) { 144 expression = "0, 0"; 145 } 146 147 // Create the MoML, wrapping the location attribute 148 // in an element referring to the container. 149 String containingElementName = element.getElementName(); 150 moml.append("<" + containingElementName + " name=\"" 151 + element.getName() + "\" >\n"); 152 153 // NOTE: use the moml info element name here in case the 154 // location is a vertex. 155 moml.append("<" + location.getElementName() + " name=\"" 156 + location.getName() + "\" value=\"" + expression 157 + "\" />\n"); 158 moml.append("</" + containingElementName + ">\n"); 159 } 160 161 moml.append("</group>\n"); 162 163 // Push the undo entry onto the stack. 164 MoMLUndoEntry undoEntry = new MoMLUndoEntry(composite, 165 moml.toString()); 166 UndoStackAttribute undoInfo = UndoStackAttribute 167 .getUndoInfo(composite); 168 undoInfo.push(undoEntry); 169 } catch (Throwable throwable) { 170 // Operation not undoable. 171 } 172 173 layout.layout(graphModel.getRoot()); 174 } catch (Exception ex) { 175 // If we do not catch exceptions here, then they 176 // disappear to stdout, which is bad if we launched 177 // where there is no stdout visible. 178 MessageHandler.error("Failed to layout \"" 179 + (model == null ? "name not found" : model.getFullName()) 180 + "\"", ex); 181 } 182 } 183 184 private BasicGraphFrame _graphFrame; 185 186 /////////////////////////////////////////////////////////////////// 187 //// PtolemyLayout 188 189 /** A layout algorithm for laying out ptolemy graphs. Since our edges 190 * are undirected, this layout algorithm turns them into directed edges 191 * aimed consistently. i.e. An edge should always be "out" of an 192 * internal output port and always be "in" of an internal input port. 193 * Conversely, an edge is "out" of an external input port, and "in" of 194 * an external output port. The copying operation also flattens 195 * the graph, because the level layout algorithm doesn't understand 196 * how to layout hierarchical nodes. 197 */ 198 private static class PtolemyLayout extends LevelLayout { 199 // FIXME: input ports should be on left, and output ports on right. 200 201 /** Construct a new levelizing layout with a vertical orientation. */ 202 public PtolemyLayout(LayoutTarget target) { 203 super(target); 204 } 205 206 /** Copy the given graph and make the nodes/edges in the copied 207 * graph point to the nodes/edges in the original. 208 */ 209 @Override 210 protected Object copyComposite(Object origComposite) { 211 LayoutTarget target = getLayoutTarget(); 212 GraphModel model = target.getGraphModel(); 213 diva.graph.basic.BasicGraphModel local = getLocalGraphModel(); 214 Object copyComposite = local.createComposite(null); 215 HashMap<Object, Object> map = new HashMap<Object, Object>(); 216 217 // Copy all the nodes for the graph. 218 for (Iterator<?> i = model.nodes(origComposite); i.hasNext();) { 219 Object origNode = i.next(); 220 221 if (target.isNodeVisible(origNode)) { 222 Rectangle2D r = target.getBounds(origNode); 223 LevelInfo inf = new LevelInfo(); 224 inf.origNode = origNode; 225 inf.x = r.getX(); 226 inf.y = r.getY(); 227 inf.width = r.getWidth(); 228 inf.height = r.getHeight(); 229 230 Object copyNode = local.createNode(inf); 231 local.addNode(this, copyNode, copyComposite); 232 map.put(origNode, copyNode); 233 } 234 } 235 236 // Add all the edges. 237 Iterator<?> i = GraphUtilities 238 .partiallyContainedEdges(origComposite, model); 239 240 while (i.hasNext()) { 241 Object origEdge = i.next(); 242 Object origTail = model.getTail(origEdge); 243 Object origHead = model.getHead(origEdge); 244 245 if (origHead != null && origTail != null) { 246 Figure tailFigure = (Figure) target 247 .getVisualObject(origTail); 248 Figure headFigure = (Figure) target 249 .getVisualObject(origHead); 250 251 // Swap the head and the tail if it will improve the 252 // layout, since LevelLayout only uses directed edges. 253 if (tailFigure instanceof Terminal) { 254 Terminal terminal = (Terminal) tailFigure; 255 Site site = terminal.getConnectSite(); 256 257 if (site instanceof FixedNormalSite) { 258 double normal = site.getNormal(); 259 int direction = CanvasUtilities 260 .getDirection(normal); 261 262 if (direction == SwingConstants.WEST) { 263 Object temp = origTail; 264 origTail = origHead; 265 origHead = temp; 266 } 267 } 268 } else if (headFigure instanceof Terminal) { 269 Terminal terminal = (Terminal) headFigure; 270 Site site = terminal.getConnectSite(); 271 272 if (site instanceof FixedNormalSite) { 273 double normal = site.getNormal(); 274 int direction = CanvasUtilities 275 .getDirection(normal); 276 277 if (direction == SwingConstants.EAST) { 278 Object temp = origTail; 279 origTail = origHead; 280 origHead = temp; 281 } 282 } 283 } 284 285 origTail = _getParentInGraph(model, origComposite, 286 origTail); 287 origHead = _getParentInGraph(model, origComposite, 288 origHead); 289 290 Object copyTail = map.get(origTail); 291 Object copyHead = map.get(origHead); 292 293 if (copyHead != null && copyTail != null) { 294 Object copyEdge = local.createEdge(origEdge); 295 local.setEdgeTail(this, copyEdge, copyTail); 296 local.setEdgeHead(this, copyEdge, copyHead); 297 } 298 } 299 } 300 301 return copyComposite; 302 } 303 304 // Unfortunately, the head and/or tail of the edge may not 305 // be directly contained in the graph. In this case, we need to 306 // figure out which of their parents IS in the graph 307 // and calculate the cost of that instead. 308 private Object _getParentInGraph(GraphModel model, Object graph, 309 Object node) { 310 while (node != null && !model.containsNode(graph, node)) { 311 Object parent = model.getParent(node); 312 313 if (model.isNode(parent)) { 314 node = parent; 315 } else { 316 node = null; 317 } 318 } 319 320 return node; 321 } 322 } 323 324 /////////////////////////////////////////////////////////////////// 325 //// PtolemyLayoutTarget 326 327 /** A layout target that translates locatable nodes. */ 328 private/*static*/class PtolemyLayoutTarget extends BasicLayoutTarget { 329 // FindBugs suggests making this class static so as to decrease 330 // the size of instances and avoid dangling references. 331 // However, we call getVisibleCanvasRectangle(), which cannot 332 // be static. 333 334 /** Construct a new layout target that operates 335 * in the given pane. 336 */ 337 public PtolemyLayoutTarget(GraphController controller) { 338 super(controller); 339 } 340 341 /** Return the viewport of the given graph as a rectangle 342 * in logical coordinates. 343 */ 344 @Override 345 public Rectangle2D getViewport(Object composite) { 346 //GraphModel model = getController().getGraphModel(); 347 348 if (composite == getRootGraph()) { 349 // Take into account the current zoom and pan. 350 Rectangle2D bounds = _graphFrame.getVisibleCanvasRectangle(); 351 352 double width = bounds.getWidth(); 353 double height = bounds.getHeight(); 354 355 double borderPercentage = (1 - getLayoutPercentage()) / 2; 356 double x = borderPercentage * width + bounds.getX(); 357 double y = borderPercentage * height + bounds.getY(); 358 double w = getLayoutPercentage() * width; 359 double h = getLayoutPercentage() * height; 360 return new Rectangle2D.Double(x, y, w, h); 361 } else { 362 return super.getViewport(composite); 363 } 364 } 365 366 /** Translate the figure associated with the given node in the 367 * target's view by the given delta. 368 */ 369 @Override 370 public void translate(Object node, double dx, double dy) { 371 super.translate(node, dx, dy); 372 373 if (node instanceof Locatable) { 374 double[] location = ((Locatable) node).getLocation(); 375 376 if (location == null) { 377 location = new double[2]; 378 379 Figure figure = getController().getFigure(node); 380 location[0] = figure.getBounds().getCenterX(); 381 location[1] = figure.getBounds().getCenterY(); 382 } else { 383 location[0] += dx; 384 location[1] += dy; 385 } 386 387 try { 388 ((Locatable) node).setLocation(location); 389 } catch (IllegalActionException ex) { 390 throw new InternalErrorException(ex.getMessage()); 391 } 392 } 393 } 394 } 395}