001/*
002
003Copyright (c) 2011-2016 The Regents of the University of California.
004All rights reserved.
005
006Permission is hereby granted, without written agreement and without
007license or royalty fees, to use, copy, modify, and distribute this
008software and its documentation for any purpose, provided that the above
009copyright notice and the following two paragraphs appear in all copies
010of this software.
011
012IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA LIABLE TO ANY PARTY
013FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
014ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
015THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
016SUCH DAMAGE.
017
018THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
019INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
020MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
021PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
022CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
023ENHANCEMENTS, OR MODIFICATIONS.
024
025PT_COPYRIGHT_VERSION_2
026COPYRIGHTENDKEY
027 */
028package ptolemy.vergil.basic.layout.kieler;
029
030import java.util.List;
031
032import de.cau.cs.kieler.core.properties.IProperty;
033import de.cau.cs.kieler.core.properties.Property;
034import de.cau.cs.kieler.kiml.klayoutdata.KLayoutData;
035import de.cau.cs.kieler.kiml.klayoutdata.KShapeLayout;
036import de.cau.cs.kieler.kiml.options.Direction;
037import de.cau.cs.kieler.kiml.options.EdgeRouting;
038import de.cau.cs.kieler.kiml.options.LayoutOptions;
039import de.cau.cs.kieler.klay.layered.p1cycles.CycleBreakingStrategy;
040import de.cau.cs.kieler.klay.layered.p2layers.LayeringStrategy;
041import de.cau.cs.kieler.klay.layered.p3order.CrossingMinimizationStrategy;
042import de.cau.cs.kieler.klay.layered.p4nodes.NodePlacementStrategy;
043import de.cau.cs.kieler.klay.layered.properties.FixedAlignment;
044import de.cau.cs.kieler.klay.layered.properties.Properties;
045import diva.graph.GraphModel;
046import ptolemy.data.BooleanToken;
047import ptolemy.data.DoubleToken;
048import ptolemy.kernel.CompositeEntity;
049import ptolemy.kernel.util.IllegalActionException;
050import ptolemy.vergil.actor.ActorGraphModel;
051import ptolemy.vergil.basic.layout.AbstractLayoutConfiguration;
052import ptolemy.vergil.basic.layout.AbstractLayoutConfiguration.InteractionMode;
053import ptolemy.vergil.basic.layout.ActorLayoutConfiguration;
054import ptolemy.vergil.basic.layout.ModalLayoutConfiguration;
055import ptolemy.vergil.modal.FSMGraphModel;
056
057/**
058 * Responsible for translating layout configuration parameters into the KIELER format.
059 * Parameters are read from an instance of the {@link AbstractLayoutConfiguration} attribute,
060 * which is attached to composite entities when the configuration dialog is opened.
061 *
062 * @see AbstractLayoutConfiguration
063 * @author Miro Spoenemann, Christoph Daniel Schulze, Ulf Rueegg
064 * @version $Id$
065 * @since Ptolemy II 10.0
066 * @Pt.ProposedRating Red (msp)
067 * @Pt.AcceptedRating Red (msp)
068 */
069public class Parameters {
070
071    /**
072     * Create a parameters instance.
073     *
074     * @param compositeEntity the composite entity for which parameters are retrieved.
075     */
076    public Parameters(CompositeEntity compositeEntity) {
077        _compositeEntity = compositeEntity;
078    }
079
080    /**
081     * Configure the KIELER layout using a property holder.
082     *
083     * @param parentLayout
084     *          the layout of the parent node
085     * @param graphModel
086     *          the graph model of the current diagram
087     * @exception IllegalActionException
088     *          if one of the parameters has the wrong type
089     */
090    public void configureLayout(KShapeLayout parentLayout,
091            GraphModel graphModel) throws IllegalActionException {
092
093        // Configuration values specified by user.
094        List<AbstractLayoutConfiguration> configAttributes = _compositeEntity
095                .attributeList(AbstractLayoutConfiguration.class);
096
097        AbstractLayoutConfiguration layoutConfiguration = null;
098        if (!configAttributes.isEmpty()) {
099            layoutConfiguration = configAttributes.get(0);
100        }
101
102        // Note that when the layout configuration dialog of a model has never been opened,
103        // there is no configuration element
104        // Otherwise subsequently applied layout options override each other
105        // in the following calls, with the user-specified, diagram-specific
106        // options being the strongest
107
108        // 1. default values
109        _applyDefault(parentLayout);
110
111        // 2. common configuration values
112        _applyAbstractConfiguration(parentLayout, layoutConfiguration);
113
114        // 3. model specific configuration values
115        if (graphModel instanceof ActorGraphModel) {
116            _applyActorConfiguration(parentLayout,
117                    (ActorLayoutConfiguration) layoutConfiguration);
118        } else if (graphModel instanceof FSMGraphModel) {
119            _applyModalConfiguration(parentLayout,
120                    (ModalLayoutConfiguration) layoutConfiguration);
121        }
122
123    }
124
125    ///////////////////////////////////////////////////////////////////
126    ////                         private methods                   ////
127
128    /**
129     * An initial, default layout configuration applied to every graph.
130     *
131     * @param parentLayout
132     *          the layout of the parent node
133     */
134    private void _applyDefault(KLayoutData parentLayout)
135            throws IllegalActionException {
136
137        // Set general default values.
138        parentLayout.setProperty(LayoutOptions.DIRECTION, Direction.RIGHT);
139        parentLayout.setProperty(LayoutOptions.BORDER_SPACING, 5.0f);
140        parentLayout.setProperty(Properties.EDGE_SPACING_FACTOR, 1.5f);
141
142        parentLayout.setProperty(LayoutOptions.SPACING, SPACING.getDefault());
143        parentLayout.setProperty(LayoutOptions.ASPECT_RATIO,
144                ASPECT_RATIO.getDefault());
145
146    }
147
148    /**
149     * This applies user-specified layout options the graph,
150     * if they exist.
151     *
152     * @param parentLayout
153     *          the layout of the parent node
154     * @param abstractConfiguration
155     *          the container with user-specified options, may be null
156     * @exception IllegalActionException
157     *          thrown if one of the parameters has the wrong type
158     */
159    private void _applyAbstractConfiguration(KLayoutData parentLayout,
160            AbstractLayoutConfiguration abstractConfiguration)
161            throws IllegalActionException {
162
163        if (abstractConfiguration != null) {
164            // Whether decorations are to be laid out or left as they are
165            BooleanToken decorationsToken = BooleanToken.convert(
166                    abstractConfiguration.includeDecorations.getToken());
167            parentLayout.setProperty(DECORATIONS,
168                    decorationsToken.booleanValue());
169
170            // Spacing between diagram elements
171            DoubleToken spacingToken = DoubleToken
172                    .convert(abstractConfiguration.spacing.getToken());
173            parentLayout.setProperty(SPACING,
174                    (float) spacingToken.doubleValue());
175
176            // Target aspect ratio for the diagram
177            DoubleToken logAspectToken = DoubleToken
178                    .convert(abstractConfiguration.logAspectRatio.getToken());
179            parentLayout.setProperty(ASPECT_RATIO,
180                    (float) Math.pow(10, logAspectToken.doubleValue()));
181
182            // The interaction mode (constraints the layout according to what the
183            // diagram currently looks like)
184            InteractionMode interactionMode = (InteractionMode) abstractConfiguration.interactionMode
185                    .getChosenValue();
186            if (interactionMode != null) {
187                // The switch cases fall through on purpose!
188                switch (interactionMode) {
189                case Full:
190                    parentLayout.setProperty(Properties.CROSS_MIN,
191                            CrossingMinimizationStrategy.INTERACTIVE);
192                case Columns:
193                    parentLayout.setProperty(Properties.NODE_LAYERING,
194                            LayeringStrategy.INTERACTIVE);
195                case Cycles:
196                    parentLayout.setProperty(Properties.CYCLE_BREAKING,
197                            CycleBreakingStrategy.INTERACTIVE);
198                default:
199                    // Don't change the configuration in all other cases
200                }
201            }
202        }
203
204    }
205
206    /**
207     * This applies default layout options for actor models
208     * as well as user-specified layout options specific to actor models,
209     * if they exist.
210     *
211     * @param parentLayout
212     *          the layout of the parent node
213     * @param actorConfiguration
214     *          the container with user-specified options, may be null
215     * @exception IllegalActionException
216     *          thrown if one of the parameters has the wrong type
217     */
218    private void _applyActorConfiguration(KLayoutData parentLayout,
219            ActorLayoutConfiguration actorConfiguration)
220            throws IllegalActionException {
221
222        // Set default values for actor models.
223        parentLayout.setProperty(LayoutOptions.EDGE_ROUTING,
224                EdgeRouting.ORTHOGONAL);
225
226        // User-specified properties
227        if (actorConfiguration != null) {
228            // The node placement algorithm to use
229            BooleanToken minimizeBendsToken = BooleanToken
230                    .convert(actorConfiguration.minimizeBends.getToken());
231            if (minimizeBendsToken.booleanValue()) {
232                parentLayout.setProperty(Properties.NODE_PLACER,
233                        NodePlacementStrategy.BRANDES_KOEPF);
234            } else {
235                parentLayout.setProperty(Properties.NODE_PLACER,
236                        NodePlacementStrategy.LINEAR_SEGMENTS);
237            }
238        }
239    }
240
241    /**
242     * This applies default layout options for FSM models
243     * as well as user-specified layout options specific to FSM models,
244     * if they exist.
245     *
246     * @param parentLayout
247     *          the layout of the parent node
248     * @param modalConfiguration
249     *          the container with user-specified options, may be null
250     * @exception IllegalActionException
251     *          thrown if one of the parameters has the wrong type
252     */
253    private void _applyModalConfiguration(KLayoutData parentLayout,
254            ModalLayoutConfiguration modalConfiguration)
255            throws IllegalActionException {
256
257        // Set default values for modal models.
258        parentLayout.setProperty(LayoutOptions.EDGE_ROUTING,
259                EdgeRouting.SPLINES);
260        parentLayout.setProperty(LayoutOptions.DIRECTION,
261                ModalLayoutConfiguration.DEF_DIRECTION);
262
263        float spacing = parentLayout.getProperty(SPACING);
264        parentLayout.setProperty(SPACING, spacing / 2f);
265        parentLayout.setProperty(Properties.OBJ_SPACING_IN_LAYER_FACTOR, 8f);
266
267        // The node placement algorithm to use
268        parentLayout.setProperty(Properties.NODE_PLACER,
269                NodePlacementStrategy.BRANDES_KOEPF);
270        parentLayout.setProperty(Properties.FIXED_ALIGNMENT,
271                FixedAlignment.BALANCED);
272
273        // User-specified
274        if (modalConfiguration != null) {
275
276            // For FSMs the user can choose whether he wants to use splines
277            // or not. Depending on the choice we have to adapt the layout options
278            BooleanToken useSplines = BooleanToken
279                    .convert(modalConfiguration.drawSplines.getToken());
280            parentLayout.setProperty(SPLINES, useSplines.booleanValue());
281
282            if (useSplines.booleanValue()) {
283                // spline routing
284                parentLayout.setProperty(LayoutOptions.EDGE_ROUTING,
285                        EdgeRouting.SPLINES);
286            } else {
287                // arc-style routing
288                parentLayout.setProperty(SPACING, spacing / 4f);
289                parentLayout.setProperty(Properties.OBJ_SPACING_IN_LAYER_FACTOR,
290                        20f);
291            }
292
293            // direction
294            Direction dir = (Direction) modalConfiguration.direction
295                    .getChosenValue();
296            parentLayout.setProperty(LayoutOptions.DIRECTION, dir);
297        }
298    }
299
300    /** Layout option that determines whether decoration nodes are included in layout. */
301    public static final IProperty<Boolean> DECORATIONS = new Property<Boolean>(
302            "ptolemy.vergil.basic.layout.decorations",
303            AbstractLayoutConfiguration.DEF_DECORATIONS);
304
305    /** Layout option for the overall spacing between elements. */
306    public static final IProperty<Float> SPACING = new Property<Float>(
307            LayoutOptions.SPACING,
308            (float) AbstractLayoutConfiguration.DEF_SPACING);
309
310    /** Layout option for the aspect ratio of connected components. */
311    public static final IProperty<Float> ASPECT_RATIO = new Property<Float>(
312            LayoutOptions.ASPECT_RATIO,
313            (float) AbstractLayoutConfiguration.DEF_ASPECT_RATIO);
314
315    /** Layout option that determines whether splines should be used for FSMs. */
316    public static final IProperty<Boolean> SPLINES = new Property<Boolean>(
317            "ptolemy.vergil.basic.layout.splines",
318            ModalLayoutConfiguration.DEF_USE_SPLINES);
319
320    ///////////////////////////////////////////////////////////////////
321    ////                         private variables                 ////
322
323    /** the parent entity from which the layout configuration is read. */
324    private CompositeEntity _compositeEntity;
325
326}