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}