001/* A simple graph view for Ptolemy models
002
003 Copyright (c) 1998-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
027 */
028package ptolemy.vergil.modal;
029
030import java.awt.event.ActionEvent;
031import java.awt.event.ActionListener;
032import java.awt.event.KeyEvent;
033import java.io.IOException;
034import java.io.Writer;
035import java.util.HashSet;
036import java.util.LinkedList;
037import java.util.List;
038import java.util.Set;
039
040import javax.swing.JMenu;
041import javax.swing.JMenuItem;
042
043import diva.graph.GraphPane;
044import ptolemy.actor.IOPort;
045import ptolemy.actor.gui.DebugListenerTableau;
046import ptolemy.actor.gui.Effigy;
047import ptolemy.actor.gui.Tableau;
048import ptolemy.actor.gui.TextEffigy;
049import ptolemy.data.BooleanToken;
050import ptolemy.domains.modal.kernel.FSMActor;
051import ptolemy.domains.modal.kernel.State;
052import ptolemy.domains.modal.modal.ModalPort;
053import ptolemy.domains.modal.modal.RefinementPort;
054import ptolemy.gui.ComponentDialog;
055import ptolemy.gui.Query;
056import ptolemy.kernel.CompositeEntity;
057import ptolemy.kernel.util.IllegalActionException;
058import ptolemy.kernel.util.InternalErrorException;
059import ptolemy.kernel.util.KernelException;
060import ptolemy.kernel.util.NamedObj;
061import ptolemy.moml.LibraryAttribute;
062import ptolemy.util.CancelException;
063import ptolemy.util.MessageHandler;
064import ptolemy.vergil.basic.BasicGraphPane;
065import ptolemy.vergil.basic.ExtendedGraphFrame;
066
067///////////////////////////////////////////////////////////////////
068//// FSMGraphFrame
069
070/**
071 This is a graph editor frame for ptolemy FSM models.  Given a composite
072 entity and a tableau, it creates an editor and populates the menus
073 and toolbar.  This overrides the base class to associate with the
074 editor an instance of FSMGraphController.
075
076 @author  Steve Neuendorffer, Contributor: Edward A. Lee
077 @version $Id$
078 @since Ptolemy II 8.0
079 @Pt.ProposedRating Red (neuendor)
080 @Pt.AcceptedRating Red (johnr)
081 */
082@SuppressWarnings("serial")
083public class FSMGraphFrame extends ExtendedGraphFrame
084        implements ActionListener {
085
086    /** Construct a frame associated with the specified FSM model.
087     *  After constructing this, it is necessary
088     *  to call setVisible(true) to make the frame appear.
089     *  This is typically done by calling show() on the controlling tableau.
090     *  This constructor results in a graph frame that obtains its library
091     *  either from the model (if it has one) or the default library defined
092     *  in the configuration.
093     *  @see Tableau#show()
094     *  @param entity The model to put in this frame.
095     *  @param tableau The tableau responsible for this frame.
096     */
097    public FSMGraphFrame(CompositeEntity entity, Tableau tableau) {
098        this(entity, tableau, null);
099    }
100
101    /** Construct a frame associated with the specified FSM model.
102     *  After constructing this, it is necessary
103     *  to call setVisible(true) to make the frame appear.
104     *  This is typically done by calling show() on the controlling tableau.
105     *  This constructor results in a graph frame that obtains its library
106     *  either from the model (if it has one), or the <i>defaultLibrary</i>
107     *  argument (if it is non-null), or the default library defined
108     *  in the configuration.
109     *  @see Tableau#show()
110     *  @param entity The model to put in this frame.
111     *  @param tableau The tableau responsible for this frame.
112     *  @param defaultLibrary An attribute specifying the default library
113     *   to use if the model does not have a library.
114     */
115    public FSMGraphFrame(CompositeEntity entity, Tableau tableau,
116            LibraryAttribute defaultLibrary) {
117        super(entity, tableau, defaultLibrary);
118
119        // Override the default help file.
120        helpFile = "ptolemy/configs/doc/vergilFsmEditorHelp.htm";
121    }
122
123    /** React to the actions specific to this FSM graph frame.
124     *
125     *  @param e The action event.
126     */
127    @Override
128    public void actionPerformed(ActionEvent e) {
129        JMenuItem target = (JMenuItem) e.getSource();
130        String actionCommand = target.getActionCommand();
131        if (actionCommand.equals("Import Design Pattern")) {
132            importDesignPattern();
133        } else if (actionCommand.equals("Export Design Pattern")) {
134            exportDesignPattern();
135        }
136    }
137
138    /** Get the currently selected objects from this document, if any,
139     *  and place them on the clipboard in MoML format.
140     */
141    @Override
142    public void copy() {
143        HashSet<NamedObj> namedObjSet = _getSelectionSet();
144        try {
145            for (NamedObj namedObj : namedObjSet) {
146                if (namedObj instanceof State) {
147                    ((State) namedObj).saveRefinementsInConfigurer
148                            .setToken(BooleanToken.TRUE);
149                }
150            }
151            super.copy();
152        } catch (IllegalActionException e) {
153            MessageHandler.error(
154                    "Unable to set attributes of the selected " + "states.");
155        } finally {
156            for (NamedObj namedObj : namedObjSet) {
157                if (namedObj instanceof State) {
158                    try {
159                        ((State) namedObj).saveRefinementsInConfigurer
160                                .setToken(BooleanToken.FALSE);
161                    } catch (IllegalActionException e) {
162                        // Ignore.
163                    }
164                }
165            }
166        }
167    }
168
169    ///////////////////////////////////////////////////////////////////
170    ////                         protected methods                 ////
171
172    /** Create the menus that are used by this frame.
173     *  It is essential that _createGraphPane() be called before this.
174     */
175    @Override
176    protected void _addMenus() {
177        super._addMenus();
178
179        // _graphMenu is instantiated in BasicGraphFrame.
180        _addLayoutMenu(_graphMenu);
181
182        // Add any commands to graph menu and toolbar that the controller
183        // wants in the graph menu and toolbar.
184        _controller.addToMenuAndToolbar(_graphMenu, _toolbar);
185
186        JMenuItem[] debugMenuItems = _debugMenuItems();
187
188        // NOTE: This has to be initialized here rather than
189        // statically because this method is called by the constructor
190        // of the base class, and static initializers have not yet
191        // been run.
192        _debugMenu = new JMenu("Debug");
193        _debugMenu.setMnemonic(KeyEvent.VK_D);
194
195        ActionListener debugMenuListener = _getDebugMenuListener();
196
197        // Set the action command and listener for each menu item.
198        for (JMenuItem debugMenuItem : debugMenuItems) {
199            debugMenuItem.setActionCommand(debugMenuItem.getText());
200            debugMenuItem.addActionListener(debugMenuListener);
201            _debugMenu.add(debugMenuItem);
202        }
203
204        _menubar.add(_debugMenu);
205    }
206
207    /** Return a new DebugMenuListener.
208     *  @return the new DebugMenuListener.
209     */
210    protected ActionListener _getDebugMenuListener() {
211        return new DebugMenuListener();
212    }
213
214    /** Return an array of debug menu items.
215     *  @return an array of debug menu items.
216     */
217    protected JMenuItem[] _debugMenuItems() {
218        // Add debug menu.
219        JMenuItem[] debugMenuItems = {
220                new JMenuItem("Listen to Director", KeyEvent.VK_D),
221                new JMenuItem("Listen to State Machine", KeyEvent.VK_L),
222                new JMenuItem(_getAnimationMenuText(), KeyEvent.VK_A),
223                new JMenuItem("Stop Animating", KeyEvent.VK_S), };
224        return debugMenuItems;
225    }
226
227    /** Close the window.  Override the base class to remove the debug
228     *  listener, if there is one.
229     *  @return False if the user cancels on a save query.
230     */
231    @Override
232    protected boolean _close() {
233        // Running with a headless display (Xvfb) could result in the model being null.
234        if (getModel() != null) {
235            getModel().removeDebugListener(_controller);
236        }
237        return super._close();
238    }
239
240    /** Create the items in the File menu. A null element in the array
241     *  represents a separator in the menu.
242     *
243     *  @return The items in the File menu.
244     */
245    @Override
246    protected JMenuItem[] _createFileMenuItems() {
247        JMenuItem[] fileMenuItems = super._createFileMenuItems();
248        int i = 0;
249        for (JMenuItem item : fileMenuItems) {
250            i++;
251            if (item.getActionCommand().equals("Save As")) {
252                // Add a SaveAsDesignPattern here.
253                JMenuItem importItem = new JMenuItem("Import Design Pattern",
254                        KeyEvent.VK_D);
255                JMenuItem exportItem = new JMenuItem("Export Design Pattern",
256                        KeyEvent.VK_D);
257                JMenuItem[] newItems = new JMenuItem[fileMenuItems.length + 4];
258                System.arraycopy(fileMenuItems, 0, newItems, 0, i);
259                newItems[i + 1] = importItem;
260                importItem.addActionListener(this);
261                newItems[i + 2] = exportItem;
262                exportItem.addActionListener(this);
263                System.arraycopy(fileMenuItems, i, newItems, i + 4,
264                        fileMenuItems.length - i);
265                return newItems;
266            }
267        }
268        return fileMenuItems;
269    }
270
271    /** Create a new graph pane. Note that this method is called in
272     *  constructor of the base class, so it must be careful to not reference
273     *  local variables that may not have yet been created.
274     *  @param entity The object to be displayed in the pane (which must be
275     *   an instance of CompositeEntity).
276     *  @return The pane that is created.
277     */
278    @Override
279    protected GraphPane _createGraphPane(NamedObj entity) {
280        _controller = new FSMGraphController();
281        _controller.setConfiguration(getConfiguration());
282        _controller.setFrame(this);
283
284        // NOTE: The cast is safe because the constructor accepts
285        // only CompositeEntity.
286        final FSMGraphModel graphModel = new FSMGraphModel(
287                (CompositeEntity) entity);
288        return new BasicGraphPane(_controller, graphModel, entity);
289    }
290
291    /** Export the model into the writer with the given name.
292     *
293     *  @param writer The writer.
294     *  @param model The model to export.
295     *  @param name The name of the exported model.
296     *  @exception IOException If an I/O error occurs.
297     */
298    @Override
299    protected void _exportDesignPattern(Writer writer, NamedObj model,
300            String name) throws IOException {
301        if (_query != null && _query.hasEntry("selected")
302                && _query.getBooleanValue("selected")) {
303            List<State> modifiedStates = new LinkedList<State>();
304            try {
305                Set<?> set = _getSelectionSet();
306                for (Object object : set) {
307                    if (object instanceof State) {
308                        State state = (State) object;
309                        modifiedStates.add(state);
310                        state.saveRefinementsInConfigurer
311                                .setToken(BooleanToken.TRUE);
312                    }
313                }
314                super._exportDesignPattern(writer, model, name);
315            } catch (IllegalActionException e) {
316                throw new InternalErrorException(null, e,
317                        "Unable to set " + "attributes for the states.");
318            } finally {
319                for (State state : modifiedStates) {
320                    try {
321                        state.saveRefinementsInConfigurer
322                                .setToken(BooleanToken.FALSE);
323                    } catch (IllegalActionException e) {
324                        // Ignore.
325                    }
326                }
327            }
328        } else {
329            ((FSMActor) model).exportSubmodel(writer, 0, name);
330        }
331    }
332
333    /** Finish exporting a design pattern.
334     */
335    @Override
336    protected void _finishExportDesignPattern() {
337        super._finishExportDesignPattern();
338
339        for (IOPort port : _modifiedPorts) {
340            try {
341                port.setInput(true);
342            } catch (IllegalActionException e) {
343                // Ignore.
344            }
345        }
346    }
347
348    /** Return the text to be used in the animation menu item. In this base
349     *  class, always return "Animate States".
350     *
351     *  @return The text for the menu item.
352     */
353    protected String _getAnimationMenuText() {
354        return "Animate States";
355    }
356
357    /** Prepare to export a design pattern.
358     *
359     *  @exception InternalErrorException Thrown if attributes of the ports to
360     *   be exported cannot be set.
361     */
362    @Override
363    protected void _prepareExportDesignPattern() {
364        super._prepareExportDesignPattern();
365
366        try {
367            FSMActor actor = (FSMActor) getModel();
368            List<IOPort> ports = actor.portList();
369            _modifiedPorts.clear();
370            for (IOPort port : ports) {
371                if (port instanceof RefinementPort && port.isInput()
372                        && port.isOutput()) {
373                    List<IOPort> connectedPorts = port.connectedPortList();
374                    for (IOPort connectedPort : connectedPorts) {
375                        if (connectedPort instanceof ModalPort
376                                && !connectedPort.isInput()) {
377                            _modifiedPorts.add(port);
378                            port.setInput(false);
379                            break;
380                        }
381                    }
382                }
383            }
384        } catch (Exception e) {
385            throw new InternalErrorException(null, e,
386                    "Fail to prepare for " + "exporting a design pattern.");
387        }
388    }
389
390    ///////////////////////////////////////////////////////////////////
391    ////                         protected variables               ////
392
393    /** The controller.
394     *  The controller is protected so that the subclass
395     * (InterfaceAutomatonGraphFrame) can set it to a more specific
396     * controller.
397     */
398    protected FSMGraphController _controller;
399
400    /** Debug menu for this frame. */
401    protected JMenu _debugMenu;
402
403    ///////////////////////////////////////////////////////////////////
404    ////                         private variables                 ////
405
406    // The delay time specified that last time animation was set.
407    private long _lastDelayTime = 0;
408
409    // The list of ports modified by the previous invocation of
410    // _prepareExportDesignPattern().
411    private List<IOPort> _modifiedPorts = new LinkedList<IOPort>();
412
413    ///////////////////////////////////////////////////////////////////
414    ////                         inner classes                     ////
415
416    /** Listener for debug menu commands. */
417    public class DebugMenuListener implements ActionListener {
418        /** React to a menu command. */
419        @Override
420        public void actionPerformed(ActionEvent e) {
421            JMenuItem target = (JMenuItem) e.getSource();
422            String actionCommand = target.getActionCommand();
423
424            try {
425                if (actionCommand.equals("Listen to Director")) {
426                    Effigy effigy = (Effigy) getTableau().getContainer();
427
428                    // Create a new text effigy inside this one.
429                    Effigy textEffigy = new TextEffigy(effigy,
430                            effigy.uniqueName("debug listener"));
431                    DebugListenerTableau tableau = new DebugListenerTableau(
432                            textEffigy, textEffigy.uniqueName("debugListener"));
433                    tableau.setDebuggable(
434                            ((FSMActor) getModel()).getDirector());
435                } else if (actionCommand.equals("Listen to State Machine")) {
436                    Effigy effigy = (Effigy) getTableau().getContainer();
437
438                    // Create a new text effigy inside this one.
439                    Effigy textEffigy = new TextEffigy(effigy,
440                            effigy.uniqueName("debug listener"));
441                    DebugListenerTableau tableau = new DebugListenerTableau(
442                            textEffigy, textEffigy.uniqueName("debugListener"));
443                    tableau.setDebuggable(getModel());
444                } else if (actionCommand.equals(_getAnimationMenuText())) {
445                    // Dialog to ask for a delay time.
446                    Query query = new Query();
447                    query.addLine("delay", "Time (in ms) to hold highlight",
448                            Long.toString(_lastDelayTime));
449
450                    ComponentDialog dialog = new ComponentDialog(
451                            FSMGraphFrame.this, "Delay for Animation", query);
452
453                    if (dialog.buttonPressed().equals("OK")) {
454                        try {
455                            _lastDelayTime = Long
456                                    .parseLong(query.getStringValue("delay"));
457                            _controller.setAnimationDelay(_lastDelayTime);
458
459                            NamedObj model = getModel();
460
461                            if (model != null && _listeningTo != model) {
462                                if (_listeningTo != null) {
463                                    _listeningTo
464                                            .removeDebugListener(_controller);
465                                }
466
467                                _listeningTo = model;
468                                _listeningTo.addDebugListener(_controller);
469                            }
470                        } catch (NumberFormatException ex) {
471                            MessageHandler
472                                    .error("Invalid time, which is required "
473                                            + "to be an integer: ", ex);
474                        }
475                    }
476                } else if (actionCommand.equals("Stop Animating")
477                        && _listeningTo != null) {
478                    _listeningTo.removeDebugListener(_controller);
479                    _controller.clearAnimation();
480                    _listeningTo = null;
481                }
482            } catch (KernelException ex) {
483                try {
484                    MessageHandler
485                            .warning("Failed to create debug listener: " + ex);
486                } catch (CancelException exception) {
487                }
488            }
489        }
490
491        private NamedObj _listeningTo;
492    }
493
494}