001/* The default KIELER layout with place and route.
002
003 Copyright (c) 2010-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.layout.kieler;
029
030import java.util.Iterator;
031import java.util.List;
032
033import javax.swing.JFrame;
034
035import diva.graph.GraphController;
036import diva.graph.GraphModel;
037import diva.graph.basic.BasicLayoutTarget;
038import diva.util.Filter;
039import ptolemy.actor.CompositeActor;
040import ptolemy.actor.gui.Configuration;
041import ptolemy.actor.gui.Effigy;
042import ptolemy.actor.gui.Tableau;
043import ptolemy.data.BooleanToken;
044import ptolemy.domains.modal.kernel.FSMActor;
045import ptolemy.kernel.CompositeEntity;
046import ptolemy.kernel.util.IllegalActionException;
047import ptolemy.kernel.util.InternalErrorException;
048import ptolemy.kernel.util.NamedObj;
049import ptolemy.util.MessageHandler;
050import ptolemy.vergil.actor.ActorGraphFrame;
051import ptolemy.vergil.basic.BasicGraphFrame;
052import ptolemy.vergil.basic.IGuiAction;
053import ptolemy.vergil.basic.PtolemyLayoutAction;
054import ptolemy.vergil.basic.layout.AbstractLayoutConfiguration;
055import ptolemy.vergil.modal.FSMGraphFrame;
056
057///////////////////////////////////////////////////////////////////
058//// KielerLayoutAction
059
060/**
061 * Trigger the KIELER place and route automatic dataflow layout algorithm
062 * from within the Vergil GUI. Operate on the current model, hence the
063 * model needs to be an input in the doAction() method.
064 * <p>
065 * This action implements the {@link Filter} interface to check whether
066 * a given model is supported.
067 * </p>
068 *
069 * @author  Christian Motika
070 * @version $Id$
071 * @since Ptolemy II 10.0
072 * @Pt.ProposedRating Red (cmot)
073 * @Pt.AcceptedRating Red (cmot)
074 */
075public class KielerLayoutAction extends Object implements IGuiAction, Filter {
076
077    ///////////////////////////////////////////////////////////////////
078    ////                         public methods                    ////
079
080    /**
081     * Layout the graph if the model is a CompositeActor. Otherwise throw an
082     * exception. The frame type must be ActorGraphFrame. The KIELER layouter
083     * is called with placing and routing. The routing uses bend point
084     * annotations.
085     *
086     * @param model the model
087     */
088    @Override
089    public void doAction(NamedObj model) {
090        try {
091            if (!accept(model)) {
092                throw new InternalErrorException(
093                        "For now only actor models and modal models are supported by KIELER layout. "
094                                + "The model \"" + model.getFullName()
095                                + "\" is a " + model.getClass().getName()
096                                + " which is not supported yet.");
097            }
098            JFrame frame = null;
099            int tableauxCount = 0;
100            Effigy effigy = Configuration.findEffigy(model);
101            if (effigy == null) {
102                effigy = Configuration.findEffigy(model.getContainer());
103            }
104            if (effigy != null) {
105                Iterator tableaux = effigy.entityList(Tableau.class).iterator();
106                while (tableaux.hasNext()) {
107                    Tableau tableau = (Tableau) tableaux.next();
108                    tableauxCount++;
109                    if (tableau.getFrame() instanceof ActorGraphFrame
110                            || tableau.getFrame() instanceof FSMGraphFrame) {
111                        frame = tableau.getFrame();
112                        break;
113                    }
114                }
115            }
116            // Check for supported type of editor.
117            if (frame == null) {
118                String message = "";
119                if (tableauxCount == 0) {
120                    message = "findEffigy() found no Tableaux.";
121                } else if (effigy != null) {
122                    JFrame firstFrame = effigy.entityList(Tableau.class).get(0)
123                            .getFrame();
124                    message = "The first frame of " + tableauxCount
125                            + " found by findEffigy() is "
126                            + (firstFrame == null ? "null"
127                                    : "a \"" + firstFrame.getClass().getName()
128                                            + "\"")
129                            + ", which is not an ActorGraphFrame or FSMGraphFrame.";
130                }
131                throw new InternalErrorException(model, null,
132                        "For now only actor models and modal models are supported by KIELER layout. "
133                                + message);
134            } else {
135                BasicGraphFrame graphFrame = (BasicGraphFrame) frame;
136
137                // Check if the old layout algorithm should be used
138                if (_useOldAlgorithm(model)) {
139                    new PtolemyLayoutAction().doAction(model);
140                } else {
141                    // Fetch everything needed to build the LayoutTarget.
142                    GraphController graphController = graphFrame.getJGraph()
143                            .getGraphPane().getGraphController();
144                    GraphModel graphModel = graphFrame.getJGraph()
145                            .getGraphPane().getGraphController()
146                            .getGraphModel();
147                    BasicLayoutTarget layoutTarget = new BasicLayoutTarget(
148                            graphController);
149
150                    // Create KIELER layouter for this layout target.
151                    KielerLayout layout = new KielerLayout(layoutTarget);
152                    // the cast is save because of the #accept() method
153                    layout.setModel((CompositeEntity) model);
154                    layout.setTop(graphFrame);
155
156                    layout.layout(graphModel.getRoot());
157                }
158            }
159        } catch (Throwable throwable) {
160            // Catch a throwable in case the guava classes are not found.
161
162            // If we do not catch throwables here, then they
163            // disappear to stdout, which is bad if we launched
164            // where there is no stdout visible.
165            MessageHandler.error("Failed to layout \""
166                    + (model == null ? "name not found" : model.getFullName())
167                    + "\"", throwable);
168        }
169    }
170
171    /**
172     * Check whether the given model is supported by this layout action.
173     *
174     * @param o The object to be be checked.
175     * @return true if the model can be laid out with this action.
176     */
177    @Override
178    public boolean accept(Object o) {
179        return o instanceof CompositeActor || o instanceof FSMActor;
180    }
181
182    ///////////////////////////////////////////////////////////////////
183    ////                         private methods                   ////
184
185    /**
186     * Checks if the given model is configured to use Ptolemy's old layout algorithm.
187     *
188     * @param model the model to check.
189     * @return {@code true} if the model has a layout configuration that explicitly
190     *         instructs us to use the old layout algorithm.
191     */
192    private boolean _useOldAlgorithm(NamedObj model) {
193        try {
194            // Find the model's LayoutConfiguration element
195            List<AbstractLayoutConfiguration> configAttributes = model
196                    .attributeList(AbstractLayoutConfiguration.class);
197
198            // If there is such an element, check if the old algorithm is to be used
199            if (!configAttributes.isEmpty()) {
200                AbstractLayoutConfiguration configuration = configAttributes
201                        .get(0);
202
203                BooleanToken useOldAlgorithm = BooleanToken
204                        .convert(configuration.useOldAlgorithm.getToken());
205                if (useOldAlgorithm.booleanValue()) {
206                    return true;
207                }
208            }
209        } catch (IllegalActionException e) {
210            // Ignore exception -- we'll return false
211        }
212
213        // Default to false
214        return false;
215    }
216
217}