001/* The graph controller for FSM 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.Toolkit;
031import java.awt.event.ActionEvent;
032import java.awt.event.InputEvent;
033import java.awt.event.KeyEvent;
034import java.awt.geom.Point2D;
035
036import javax.swing.Action;
037import javax.swing.JMenu;
038import javax.swing.JToolBar;
039
040import diva.canvas.Figure;
041import diva.canvas.FigureLayer;
042import diva.canvas.Site;
043import diva.canvas.connector.AutonomousSite;
044import diva.canvas.connector.Connector;
045import diva.canvas.connector.ConnectorManipulator;
046import diva.canvas.event.LayerEvent;
047import diva.canvas.event.MouseFilter;
048import diva.canvas.interactor.AbstractInteractor;
049import diva.canvas.interactor.CompositeInteractor;
050import diva.canvas.interactor.GrabHandle;
051import diva.canvas.interactor.Interactor;
052import diva.graph.GraphException;
053import diva.graph.GraphPane;
054import diva.graph.NodeRenderer;
055import diva.gui.GUIUtilities;
056import diva.gui.toolbox.FigureIcon;
057import ptolemy.actor.gui.Configuration;
058import ptolemy.domains.modal.kernel.State;
059import ptolemy.kernel.CompositeEntity;
060import ptolemy.kernel.Entity;
061import ptolemy.kernel.util.ChangeRequest;
062import ptolemy.kernel.util.InternalErrorException;
063import ptolemy.kernel.util.KernelException;
064import ptolemy.kernel.util.Location;
065import ptolemy.kernel.util.NamedObj;
066import ptolemy.moml.LibraryAttribute;
067import ptolemy.moml.MoMLChangeRequest;
068import ptolemy.util.MessageHandler;
069import ptolemy.vergil.actor.ExternalIOPortController;
070import ptolemy.vergil.basic.BasicGraphFrame;
071import ptolemy.vergil.basic.NamedObjController;
072import ptolemy.vergil.kernel.AttributeController;
073import ptolemy.vergil.kernel.Link;
074import ptolemy.vergil.kernel.PortDialogAction;
075import ptolemy.vergil.modal.modal.ModalTransitionController;
076import ptolemy.vergil.toolbox.FigureAction;
077import ptolemy.vergil.unit.ConfigureUnitsAction;
078
079///////////////////////////////////////////////////////////////////
080//// FSMGraphController
081
082/**
083 A Graph Controller for FSM models.  This controller allows states to be
084 dragged and dropped onto its graph. Links can be created by
085 control-clicking and dragging from one state to another.
086
087 @author Steve Neuendorffer, Contributor: Edward A. Lee
088 @version $Id$
089 @since Ptolemy II 8.0
090 @Pt.ProposedRating Red (eal)
091 @Pt.AcceptedRating Red (johnr)
092 */
093public class FSMGraphController extends FSMViewerGraphController {
094    /** Create a new basic controller with default
095     *  terminal and edge interactors.
096     */
097    public FSMGraphController() {
098        super();
099    }
100
101    ///////////////////////////////////////////////////////////////////
102    ////                         public methods                    ////
103
104    /** Add commands to the specified menu and toolbar, as appropriate
105     *  for this controller.  In this class, commands are added to create
106     *  ports and relations.
107     *  @param menu The menu to add to, or null if none.
108     *  @param toolbar The toolbar to add to, or null if none.
109     */
110    @Override
111    public void addToMenuAndToolbar(JMenu menu, JToolBar toolbar) {
112        super.addToMenuAndToolbar(menu, toolbar);
113
114        // Only include the port actions if there is an actor library.
115        // The ptinyViewer configuration uses this.
116        if (getConfiguration().getEntity("actor library") != null) {
117            diva.gui.GUIUtilities.addMenuItem(menu, _newInputPortAction);
118            diva.gui.GUIUtilities.addToolBarButton(toolbar,
119                    _newInputPortAction);
120            diva.gui.GUIUtilities.addMenuItem(menu, _newOutputPortAction);
121            diva.gui.GUIUtilities.addToolBarButton(toolbar,
122                    _newOutputPortAction);
123            diva.gui.GUIUtilities.addMenuItem(menu, _newInOutPortAction);
124            diva.gui.GUIUtilities.addToolBarButton(toolbar,
125                    _newInOutPortAction);
126            diva.gui.GUIUtilities.addMenuItem(menu, _newInputMultiportAction);
127            diva.gui.GUIUtilities.addToolBarButton(toolbar,
128                    _newInputMultiportAction);
129            diva.gui.GUIUtilities.addMenuItem(menu, _newOutputMultiportAction);
130            diva.gui.GUIUtilities.addToolBarButton(toolbar,
131                    _newOutputMultiportAction);
132            diva.gui.GUIUtilities.addMenuItem(menu, _newInOutMultiportAction);
133            diva.gui.GUIUtilities.addToolBarButton(toolbar,
134                    _newInOutMultiportAction);
135
136            // Add an item that adds new states.
137            menu.addSeparator();
138            diva.gui.GUIUtilities.addMenuItem(menu, _newStateAction);
139            diva.gui.GUIUtilities.addToolBarButton(toolbar, _newStateAction);
140        }
141    }
142
143    /** Set the configuration.  The configuration is used when
144     *  opening documentation files.
145     *  @param configuration The configuration.
146     */
147    @Override
148    public void setConfiguration(Configuration configuration) {
149        super.setConfiguration(configuration);
150
151        if (_portDialogAction != null) {
152            _portDialogAction.setConfiguration(configuration);
153        }
154
155        if (_configureUnitsAction != null) {
156            _configureUnitsAction.setConfiguration(configuration);
157        }
158
159    }
160
161    ///////////////////////////////////////////////////////////////////
162    ////                         protected methods                 ////
163
164    /** Create the controllers for nodes in this graph.
165     *  In this class, controllers with FULL access are created.
166     *  This is called by the constructor, so derived classes that
167     *  override this must be careful not to reference local variables
168     *  defined in the derived classes, because the derived classes
169     *  will not have been fully constructed by the time this is called.
170     */
171    @Override
172    protected void _createControllers() {
173        _attributeController = new AttributeController(this,
174                AttributeController.FULL);
175        _portController = new ExternalIOPortController(this,
176                AttributeController.FULL);
177        _stateController = new StateController(this, AttributeController.FULL);
178        _modalTransitionController = new ModalTransitionController(this);
179        _transitionController = new TransitionController(this);
180    }
181
182    /** Initialize interaction on the graph pane. This method
183     *  is called by the setGraphPane() method of the superclass.
184     *  This initialization cannot be done in the constructor because
185     *  the controller does not yet have a reference to its pane
186     *  at that time.
187     */
188    @Override
189    protected void initializeInteraction() {
190        // NOTE: This method name does not have a leading underscore
191        // because it is a diva method.
192        super.initializeInteraction();
193
194        /* GraphPane pane = */getGraphPane();
195
196        // Add a menu command to configure the ports.
197        _portDialogAction = new PortDialogAction("Ports");
198        _portDialogAction.setConfiguration(getConfiguration());
199
200        _configureMenuFactory.addAction(_portDialogAction, "Customize");
201        _configureUnitsAction = new ConfigureUnitsAction("Units Constraints");
202        _configureMenuFactory.addAction(_configureUnitsAction, "Customize");
203        _configureUnitsAction.setConfiguration(getConfiguration());
204
205        // Create the interactor that drags new edges.
206        _linkCreator = new LinkCreator();
207        _linkCreator.setMouseFilter(_shortcutFilter);
208
209        // NOTE: Do not use _initializeInteraction() because we are
210        // still in the constructor, and that method is overloaded in
211        // derived classes.
212        ((CompositeInteractor) _stateController.getNodeInteractor())
213                .addInteractor(_linkCreator);
214    }
215
216    /** Initialize interactions for the specified controller.  This
217     *  method is called when a new controller is constructed. In this
218     *  class, this method attaches a link creator to the controller
219     *  if the controller is an instance of StateController.
220     *  @param controller The controller for which to initialize interaction.
221     */
222    @Override
223    protected void _initializeInteraction(NamedObjController controller) {
224        super._initializeInteraction(controller);
225
226        if (controller instanceof StateController) {
227            Interactor interactor = controller.getNodeInteractor();
228
229            if (interactor instanceof CompositeInteractor) {
230                ((CompositeInteractor) interactor).addInteractor(_linkCreator);
231            }
232        }
233    }
234
235    ///////////////////////////////////////////////////////////////////
236    ////                         private variables                 ////
237
238    private ConfigureUnitsAction _configureUnitsAction;
239
240    /** The interactor that interactively creates edges. */
241    private LinkCreator _linkCreator; // For control-click
242
243    /** The action for creating states. */
244    private NewStateAction _newStateAction = new NewStateAction();
245
246    /** The filter for shortcut operations.  This is used for creation
247     *  of relations and creation of links from relations. Under PC,
248     *  this is a control-1 click.  Under Mac OS X, the control key is
249     *  used for context menus and this corresponds to the command-1
250     *  click.  For details, see the Apple java archive
251     *  http://lists.apple.com/archives/java-dev User: archives,
252     *  passwd: archives
253     */
254    private MouseFilter _shortcutFilter = new MouseFilter(
255            InputEvent.BUTTON1_MASK,
256            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
257
258    /** Action for creating a new input port. */
259    private Action _newInputPortAction = new NewPortAction(
260            ExternalIOPortController._GENERIC_INPUT, "New input port",
261            KeyEvent.VK_I,
262            new String[][] {
263                    { "/ptolemy/vergil/actor/img/single_in.gif",
264                            GUIUtilities.LARGE_ICON },
265                    { "/ptolemy/vergil/actor/img/single_in_o.gif",
266                            GUIUtilities.ROLLOVER_ICON },
267                    { "/ptolemy/vergil/actor/img/single_in_ov.gif",
268                            GUIUtilities.ROLLOVER_SELECTED_ICON },
269                    { "/ptolemy/vergil/actor/img/single_in_on.gif",
270                            GUIUtilities.SELECTED_ICON } });
271
272    /** Action for creating a new output port. */
273    private Action _newOutputPortAction = new NewPortAction(
274            ExternalIOPortController._GENERIC_OUTPUT, "New output port",
275            KeyEvent.VK_O,
276            new String[][] {
277                    { "/ptolemy/vergil/actor/img/single_out.gif",
278                            GUIUtilities.LARGE_ICON },
279                    { "/ptolemy/vergil/actor/img/single_out_o.gif",
280                            GUIUtilities.ROLLOVER_ICON },
281                    { "/ptolemy/vergil/actor/img/single_out_ov.gif",
282                            GUIUtilities.ROLLOVER_SELECTED_ICON },
283                    { "/ptolemy/vergil/actor/img/single_out_on.gif",
284                            GUIUtilities.SELECTED_ICON } });
285
286    /** Action for creating a new input/output port. */
287    private Action _newInOutPortAction = new NewPortAction(
288            ExternalIOPortController._GENERIC_INOUT, "New input/output port",
289            KeyEvent.VK_P,
290            new String[][] {
291                    { "/ptolemy/vergil/actor/img/single_inout.gif",
292                            GUIUtilities.LARGE_ICON },
293                    { "/ptolemy/vergil/actor/img/single_inout_o.gif",
294                            GUIUtilities.ROLLOVER_ICON },
295                    { "/ptolemy/vergil/actor/img/single_inout_ov.gif",
296                            GUIUtilities.ROLLOVER_SELECTED_ICON },
297                    { "/ptolemy/vergil/actor/img/single_inout_on.gif",
298                            GUIUtilities.SELECTED_ICON } });
299
300    /** Action for creating a new input multiport. */
301    private Action _newInputMultiportAction = new NewPortAction(
302            ExternalIOPortController._GENERIC_INPUT_MULTIPORT,
303            "New input multiport", KeyEvent.VK_N,
304            new String[][] {
305                    { "/ptolemy/vergil/actor/img/multi_in.gif",
306                            GUIUtilities.LARGE_ICON },
307                    { "/ptolemy/vergil/actor/img/multi_in_o.gif",
308                            GUIUtilities.ROLLOVER_ICON },
309                    { "/ptolemy/vergil/actor/img/multi_in_ov.gif",
310                            GUIUtilities.ROLLOVER_SELECTED_ICON },
311                    { "/ptolemy/vergil/actor/img/multi_in_on.gif",
312                            GUIUtilities.SELECTED_ICON } });
313
314    /** Action for creating a new output multiport. */
315    private Action _newOutputMultiportAction = new NewPortAction(
316            ExternalIOPortController._GENERIC_OUTPUT_MULTIPORT,
317            "New output multiport", KeyEvent.VK_U,
318            new String[][] {
319                    { "/ptolemy/vergil/actor/img/multi_out.gif",
320                            GUIUtilities.LARGE_ICON },
321                    { "/ptolemy/vergil/actor/img/multi_out_o.gif",
322                            GUIUtilities.ROLLOVER_ICON },
323                    { "/ptolemy/vergil/actor/img/multi_out_ov.gif",
324                            GUIUtilities.ROLLOVER_SELECTED_ICON },
325                    { "/ptolemy/vergil/actor/img/multi_out_on.gif",
326                            GUIUtilities.SELECTED_ICON } });
327
328    /** Action for creating a new inout multiport. */
329    private Action _newInOutMultiportAction = new NewPortAction(
330            ExternalIOPortController._GENERIC_INOUT_MULTIPORT,
331            "New input/output multiport", KeyEvent.VK_T,
332            new String[][] {
333                    { "/ptolemy/vergil/actor/img/multi_inout.gif",
334                            GUIUtilities.LARGE_ICON },
335                    { "/ptolemy/vergil/actor/img/multi_inout_o.gif",
336                            GUIUtilities.ROLLOVER_ICON },
337                    { "/ptolemy/vergil/actor/img/multi_inout_ov.gif",
338                            GUIUtilities.ROLLOVER_SELECTED_ICON },
339                    { "/ptolemy/vergil/actor/img/multi_inout_on.gif",
340                            GUIUtilities.SELECTED_ICON } });
341
342    /** The port dialog factory. */
343    private PortDialogAction _portDialogAction;
344
345    /** Prototype state for rendering. */
346    private static Location _prototypeState;
347
348    static {
349        CompositeEntity container = new CompositeEntity();
350
351        try {
352            State state = new State(container, "S");
353            _prototypeState = new Location(state, "_location");
354        } catch (KernelException ex) {
355            // This should not happen.
356            throw new InternalErrorException(null, ex, null);
357        }
358    }
359
360    ///////////////////////////////////////////////////////////////////
361    ////                         inner classes                     ////
362    ///////////////////////////////////////////////////////////////////
363    //// LinkCreator
364
365    /** An interactor that interactively drags edges from one terminal
366     *  to another.
367     */
368    protected class LinkCreator extends AbstractInteractor {
369        /** Initiate creation of an arc. */
370        @Override
371        public void mousePressed(LayerEvent event) {
372            Figure source = event.getFigureSource();
373            NamedObj sourceObject = (NamedObj) source.getUserObject();
374
375            Link link = new Link();
376
377            // Set the tail, going through the model so the link is added
378            // to the list of links.
379            FSMGraphModel model = (FSMGraphModel) getGraphModel();
380            model.getArcModel().setTail(link, sourceObject);
381
382            try {
383                // add it to the foreground layer.
384                FigureLayer layer = getGraphPane().getForegroundLayer();
385                Site headSite;
386                Site tailSite;
387
388                // Temporary sites.  One of these will get removed later.
389                headSite = new AutonomousSite(layer, event.getLayerX(),
390                        event.getLayerY());
391                tailSite = new AutonomousSite(layer, event.getLayerX(),
392                        event.getLayerY());
393
394                // Render the edge.
395                Connector c = getEdgeController(link).render(link, layer,
396                        tailSite, headSite);
397
398                // get the actual attach site.
399                tailSite = getEdgeController(link).getConnectorTarget()
400                        .getTailSite(c, source, event.getLayerX(),
401                                event.getLayerY());
402
403                if (tailSite == null) {
404                    throw new RuntimeException("Invalid connector target: "
405                            + "no valid site found for tail of new connector.");
406                }
407
408                // And reattach the connector.
409                c.setTailSite(tailSite);
410
411                // Add it to the selection so it gets a manipulator, and
412                // make events go to the grab-handle under the mouse
413                Figure ef = getFigure(link);
414                getSelectionModel().addSelection(ef);
415
416                ConnectorManipulator cm = (ConnectorManipulator) ef.getParent();
417                GrabHandle gh = cm.getHeadHandle();
418                layer.grabPointer(event, gh);
419            } catch (Exception ex) {
420                MessageHandler.error("Drag connection failed:", ex);
421            }
422        }
423    }
424
425    ///////////////////////////////////////////////////////////////////
426    //// NewStateAction
427
428    /** An action to create a new state. */
429    @SuppressWarnings("serial")
430    public class NewStateAction extends FigureAction {
431        /** Construct a new state. */
432        public NewStateAction() {
433            super("New State");
434            putValue("tooltip", "New State");
435
436            NodeRenderer renderer = new StateController.StateRenderer(
437                    getGraphModel());
438            Figure figure = renderer.render(_prototypeState);
439
440            // Standard toolbar icons are 25x25 pixels.
441            FigureIcon icon = new FigureIcon(figure, 25, 25, 1, true);
442            putValue(diva.gui.GUIUtilities.LARGE_ICON, icon);
443            putValue("tooltip", "New State");
444            putValue(diva.gui.GUIUtilities.MNEMONIC_KEY,
445                    Integer.valueOf(KeyEvent.VK_W));
446        }
447
448        /** Execute the action. */
449        @Override
450        public void actionPerformed(ActionEvent e) {
451            super.actionPerformed(e);
452
453            double x;
454            double y;
455
456            if (getSourceType() == TOOLBAR_TYPE
457                    || getSourceType() == MENUBAR_TYPE) {
458                // No location in the action, so put it in the middle.
459                BasicGraphFrame frame = FSMGraphController.this.getFrame();
460                Point2D center;
461
462                if (frame != null) {
463                    // Put in the middle of the visible part.
464                    center = frame.getCenter();
465                    x = center.getX();
466                    y = center.getY();
467                } else {
468                    // Put in the middle of the pane.
469                    GraphPane pane = getGraphPane();
470                    center = pane.getSize();
471                    x = center.getX() / 2;
472                    y = center.getY() / 2;
473                }
474            } else {
475                x = getX();
476                y = getY();
477            }
478
479            FSMGraphModel graphModel = (FSMGraphModel) getGraphModel();
480            NamedObj toplevel = graphModel.getPtolemyModel();
481
482            String stateName = toplevel.uniqueName("state");
483
484            // Create the state.
485            String moml = null;
486            String locationName = "_location";
487
488            // Try to get the class name for the state from the library,
489            // so that the library and the toolbar are assured of creating
490            // the same object.
491            try {
492                LibraryAttribute attribute = (LibraryAttribute) toplevel
493                        .getAttribute("_library", LibraryAttribute.class);
494
495                if (attribute != null) {
496                    CompositeEntity library = attribute.getLibrary();
497                    Entity prototype = library.getEntity("state");
498
499                    if (prototype != null) {
500                        moml = prototype.exportMoML(stateName);
501
502                        // FIXME: Get location name from prototype.
503                    }
504                }
505            } catch (Exception ex) {
506                // Ignore and use the default.
507                // Avoid a FindBugs warning about ignored exception.
508                moml = null;
509            }
510
511            if (moml == null) {
512                moml = "<entity name=\"" + stateName
513                        + "\" class=\"ptolemy.domains.modal.kernel.State\">\n"
514                        + "<property name=\"" + locationName
515                        + "\" class=\"ptolemy.kernel.util.Location\""
516                        + " value=\"[" + x + ", " + y + "]\"/>\n"
517                        + "</entity>\n";
518            }
519
520            ChangeRequest request = new MoMLChangeRequest(this, toplevel, moml);
521            toplevel.requestChange(request);
522
523            try {
524                request.waitForCompletion();
525            } catch (Exception ex) {
526                throw new GraphException(ex);
527            }
528        }
529    }
530}