001/*  The node controller for entities
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.actor;
029
030import java.awt.Frame;
031import java.awt.event.ActionEvent;
032import java.awt.event.KeyEvent;
033
034import javax.swing.Action;
035import javax.swing.KeyStroke;
036
037import diva.graph.GraphController;
038import diva.graph.JGraph;
039import diva.gui.GUIUtilities;
040import ptolemy.actor.gui.Configuration;
041import ptolemy.actor.gui.DebugListenerTableau;
042import ptolemy.actor.gui.DialogTableau;
043import ptolemy.actor.gui.Effigy;
044import ptolemy.actor.gui.OpenInstanceDialog;
045import ptolemy.actor.gui.Tableau;
046import ptolemy.actor.gui.TableauFrame;
047import ptolemy.actor.gui.TextEffigy;
048import ptolemy.actor.gui.UserActorLibrary;
049import ptolemy.data.expr.Parameter;
050import ptolemy.data.expr.StringParameter;
051import ptolemy.kernel.CompositeEntity;
052import ptolemy.kernel.Entity;
053import ptolemy.kernel.util.KernelException;
054import ptolemy.kernel.util.NamedObj;
055import ptolemy.util.MessageHandler;
056import ptolemy.vergil.basic.BasicGraphController;
057import ptolemy.vergil.basic.BasicGraphFrame;
058import ptolemy.vergil.basic.LookInsideAction;
059import ptolemy.vergil.debugger.BreakpointDialogFactory;
060import ptolemy.vergil.kernel.AttributeController;
061import ptolemy.vergil.kernel.PortDialogAction;
062import ptolemy.vergil.toolbox.EditIconAction;
063import ptolemy.vergil.toolbox.FigureAction;
064import ptolemy.vergil.toolbox.MenuActionFactory;
065import ptolemy.vergil.toolbox.MenuItemFactory;
066import ptolemy.vergil.toolbox.RemoveIconAction;
067import ptolemy.vergil.toolbox.RotateOrFlipPorts;
068import ptolemy.vergil.unit.ConfigureUnitsAction;
069
070///////////////////////////////////////////////////////////////////
071//// ActorController
072
073/**
074 * This class provides interaction with nodes that represent Ptolemy II
075 * entities. It provides a double click binding and context menu entry to edit
076 * the parameters of the node ("Configure"), a command to get documentation, and
077 * a command to open an actor. It can have one of two access levels, FULL or
078 * PARTIAL. If the access level is FULL, the the context menu also contains a
079 * command to rename the node and to configure its ports. In addition, a layout
080 * algorithm is applied so that the figures for ports are automatically placed
081 * on the sides of the figure for the entity.
082 * <p>
083 * NOTE: This class is abstract because it is missing the code for laying out
084 * ports. Use the concrete subclasses ActorInstanceController or
085 * ClassDefinitionController instead.
086 *
087 * @author Steve Neuendorffer and Edward A. Lee, Elaine Cheong, Contributor: Sven Koehler
088 * @version $Id$
089 * @since Ptolemy II 2.0
090 * @Pt.ProposedRating Red (eal)
091 * @Pt.AcceptedRating Red (johnr)
092 * @see ActorInstanceController
093 * @see ClassDefinitionController
094 */
095public abstract class ActorController extends AttributeController {
096    /**
097     * Create an entity controller associated with the specified graph
098     * controller with full access.
099     *
100     * @param controller
101     *            The associated graph controller.
102     */
103    public ActorController(GraphController controller) {
104        this(controller, FULL);
105    }
106
107    /**
108     * Create an entity controller associated with the specified graph
109     * controller.
110     *
111     * @param controller
112     *            The associated graph controller.
113     * @param access
114     *            The access level.
115     */
116    public ActorController(GraphController controller, Access access) {
117        super(controller, access);
118
119        _access = access;
120
121        // "Configure Ports"
122        if (access == FULL) {
123            // Add to the context menu, configure submenu.
124            _portDialogAction = new PortDialogAction("Ports");
125            _configureMenuFactory.addAction(_portDialogAction, "Customize");
126            _configureUnitsAction = new ConfigureUnitsAction(
127                    "Units Constraints");
128            _configureMenuFactory.addAction(_configureUnitsAction, "Customize");
129        }
130
131        // NOTE: The following requires that the configuration be
132        // non-null, or it will report an error.  However, in order to
133        // get the "Look Inside" menu to work for composite actors in
134        // Kepler, we create these menu items now.
135        _menuFactory
136                .addMenuItemFactory(new MenuActionFactory(_lookInsideAction));
137        _menuFactory
138                .addMenuItemFactory(new MenuActionFactory(_openInstanceAction));
139
140        if (_configuration != null) {
141            if (access == FULL) {
142                // Create an Appearance submenu.
143                _createAppearanceSubmenu();
144            }
145        }
146
147        /*
148         * The following proves not so useful since atomic actors do not
149         * typically have suitable constructors (that take only a Workspace
150         * argument) to be usable at the top level.
151         * _menuFactory.addMenuItemFactory( new MenuActionFactory(new
152         * SaveInFileAction()));
153         */
154        // if (((BasicGraphController) getController()).getFrame() != null) {
155        // If we are in an applet, then we have no frame, so no need
156        // for a "Listen to Actor" or "Save in Library" menu choices.
157        // FIXME: this is not perfect, it would be better if we
158        // could just test if we are in an applet or else fix this
159        // so we have a frame.
160        // NOTE: This requires that the configuration be non null, or it
161        // will report an error.
162        _menuFactory.addMenuItemFactory(
163                new MenuActionFactory(new SaveInLibraryAction()));
164
165        // }
166        // "Set Breakpoints"
167        if (access == FULL) {
168            // Add to the context menu.
169            // FIXME: does this work outside of SDF? Should
170            // we check to see if the director is an SDF director?
171            // We should use reflection to check this so that
172            // this class does not require SDFDirector.
173            // See $PTII/doc/coding/debugging.htm
174            _breakpointDialogFactory = new BreakpointDialogFactory(
175                    (BasicGraphController) getController());
176            _menuFactory.addMenuItemFactory(_breakpointDialogFactory);
177        }
178    }
179
180    ///////////////////////////////////////////////////////////////////
181    ////                         public methods                    ////
182
183    /**
184     * If access is FULL, then add the jni.ArgumentDailogFactory() to
185     * _menuFactory. If access is not FULL, then do nothing.
186     *
187     * @param menuItemFactory
188     *            The MenuItemFactory to be added.
189     */
190    public void addMenuItemFactory(MenuItemFactory menuItemFactory) {
191        // This method is called by jni.ThalesGraphFrame to add a context
192        // menu.
193        if (_access == FULL) {
194            _menuFactory.addMenuItemFactory(menuItemFactory);
195        }
196    }
197
198    /**
199     * Add hot keys to the actions in the given JGraph. It would be better that
200     * this method was added higher in the hierarchy. Now most controllers
201     *
202     * @param jgraph
203     *            The JGraph to which hot keys are to be added.
204     */
205    @Override
206    public void addHotKeys(JGraph jgraph) {
207        super.addHotKeys(jgraph);
208        GUIUtilities.addHotKey(jgraph, _lookInsideAction);
209        GUIUtilities.addHotKey(jgraph, _openInstanceAction);
210    }
211
212    /**
213     * Set the configuration. This is used to open documentation files.
214     *
215     * @param configuration
216     *            The configuration.
217     */
218    @Override
219    public void setConfiguration(Configuration configuration) {
220        super.setConfiguration(configuration);
221
222        _lookInsideAction.setConfiguration(configuration);
223
224        if (_portDialogAction != null) {
225            _portDialogAction.setConfiguration(configuration);
226        }
227        if (_configureUnitsAction != null) {
228            _configureUnitsAction.setConfiguration(configuration);
229        }
230
231        if (_configuration != null) {
232            if (_access == FULL) {
233                // Create an Appearance submenu.
234                _createAppearanceSubmenu();
235            }
236        }
237    }
238
239    ///////////////////////////////////////////////////////////////////
240    ////                         protected methods                 ////
241
242    /**
243     * Get the class label of the component.
244     *
245     * @return the class label of the component.
246     */
247    @Override
248    protected String _getComponentType() {
249        return "Actor";
250    }
251
252    ///////////////////////////////////////////////////////////////////
253    ////                         protected variables               ////
254
255    /** The access level defined in the constructor. */
256    protected Access _access;
257
258    /** The action that handles edit custom icon. */
259    protected EditIconAction _editIconAction = new EditIconAction();
260
261    /** An action that handles flipping the ports horizontally. */
262    protected RotateOrFlipPorts _flipPortsHorizontal = new RotateOrFlipPorts(
263            RotateOrFlipPorts.FLIP_HORIZONTAL, "Flip Ports Horizontally");
264
265    /** An action that handles flipping the ports vertically. */
266    protected RotateOrFlipPorts _flipPortsVertical = new RotateOrFlipPorts(
267            RotateOrFlipPorts.FLIP_VERTICAL, "Flip Ports Vertically");
268
269    /**
270     * The action that handles opening an actor. This is accessed by by
271     * ActorViewerController to create a hot key for the editor. The name
272     * "lookInside" is historical and preserved to keep backward compatibility
273     * with subclasses.
274     */
275    protected LookInsideAction _lookInsideAction = new LookInsideAction(
276            "Open Actor");
277
278    /**
279     * The action that handles opening an instance.
280     */
281    protected OpenInstanceAction _openInstanceAction = new OpenInstanceAction();
282
283    /** The action that handles removing a custom icon. */
284    protected RemoveIconAction _removeIconAction = new RemoveIconAction();
285
286    /** An action that handles rotating the ports by 90 degrees. */
287    protected RotateOrFlipPorts _rotatePortsClockwise = new RotateOrFlipPorts(
288            RotateOrFlipPorts.CLOCKWISE, "Rotate Ports Clockwise");
289
290    /** An action that handles rotating the ports by 90 degrees. */
291    protected RotateOrFlipPorts _rotatePortsCounterclockwise = new RotateOrFlipPorts(
292            RotateOrFlipPorts.COUNTERCLOCKWISE,
293            "Rotate Ports Counterclockwise");
294
295    ///////////////////////////////////////////////////////////////////
296    ////                         private methods                   ////
297
298    /**
299     * Create an Appearance submenu.
300     */
301    private void _createAppearanceSubmenu() {
302        _editIconAction.setConfiguration(_configuration);
303        _removeIconAction.setConfiguration(_configuration);
304        Action[] actions = { _editIconAction, _removeIconAction,
305                _flipPortsHorizontal, _flipPortsVertical, _rotatePortsClockwise,
306                _rotatePortsCounterclockwise };
307        _appearanceMenuActionFactory.addActions(actions, "Appearance");
308    }
309
310    ///////////////////////////////////////////////////////////////////
311    ////                         private variables                 ////
312
313    private BreakpointDialogFactory _breakpointDialogFactory;
314
315    private ConfigureUnitsAction _configureUnitsAction;
316
317    private PortDialogAction _portDialogAction;
318
319    ///////////////////////////////////////////////////////////////////
320    ////                         inner classes                     ////
321
322    ///////////////////////////////////////////////////////////////////
323    //// ListenToActorAction
324
325    /**
326     * An action to listen to debug messages in the actor. This is static so
327     * that other classes can use it.
328     */
329    @SuppressWarnings("serial")
330    public static class ListenToActorAction extends FigureAction {
331        // Kepler uses this action.
332
333        /** Create an action to listen to debug messages.
334         *
335         * @param tableauFrame The associated TableauFrame.
336         */
337        public ListenToActorAction(TableauFrame tableauFrame) {
338            super("Listen to Actor");
339            _tableauFrame = tableauFrame;
340        }
341
342        /** Create an action to listen to debug messages in the actor.
343         *
344         * @param controller The controller associated with this action.
345         */
346        public ListenToActorAction(BasicGraphController controller) {
347            super("Listen to Actor");
348            _controller = controller;
349        }
350
351        /** Create an action to listen to debug messages in the actor.
352         *
353         * @param target The actor to which to listen.
354         * @param controller  The controller associated with this action.
355         */
356        public ListenToActorAction(NamedObj target,
357                BasicGraphController controller) {
358            super("Listen to Actor");
359            _target = target;
360            _controller = controller;
361        }
362
363        /** Perform the action.
364         *  @param event The action event
365         */
366        @Override
367        public void actionPerformed(ActionEvent event) {
368            if (_configuration == null && _tableauFrame == null) {
369                MessageHandler.error(
370                        "Cannot listen to actor without a configuration.");
371                return;
372            }
373
374            // Determine which entity was selected for the listen to
375            // actor action.
376            super.actionPerformed(event);
377
378            NamedObj object = _target;
379
380            if (object == null) {
381                object = getTarget();
382            }
383            Tableau tableau = null;
384            try {
385                if (_tableauFrame == null) {
386                    BasicGraphFrame frame = _controller.getFrame();
387                    tableau = frame.getTableau();
388                } else {
389                    tableau = _tableauFrame.getTableau();
390                }
391
392                // effigy is the whole model.
393                Effigy effigy = (Effigy) tableau.getContainer();
394
395                // We want to open a new window that behaves as a
396                // child of the model window. So, we create a new text
397                // effigy inside this one. Specify model's effigy as
398                // a container for this new effigy.
399                Effigy textEffigy = new TextEffigy(effigy,
400                        effigy.uniqueName("debugListener" + object.getName()));
401
402                DebugListenerTableau debugTableau = new DebugListenerTableau(
403                        textEffigy, textEffigy.uniqueName(
404                                "debugListener" + object.getName()));
405                debugTableau.setDebuggable(object);
406            } catch (KernelException ex) {
407                MessageHandler.error("Failed to create debug listener.", ex);
408            }
409        }
410
411        /**
412         * Set the configuration for use by the help screen.
413         *
414         * @param configuration
415         *            The configuration.
416         */
417        public void setConfiguration(Configuration configuration) {
418            _configuration = configuration;
419        }
420
421        private Configuration _configuration;
422        private BasicGraphController _controller;
423        private NamedObj _target;
424        private TableauFrame _tableauFrame = null;
425    }
426
427    ///////////////////////////////////////////////////////////////////
428    //// OpenInstanceAction
429
430    /**
431     * An action to open an instance. This is similar to LookInsideAction except
432     * that it does not open the class definition, but rather opens the
433     * instance.
434     */
435    @SuppressWarnings("serial")
436    private class OpenInstanceAction extends FigureAction {
437        public OpenInstanceAction() {
438            super("Open Instance");
439            putValue(GUIUtilities.ACCELERATOR_KEY, KeyStroke
440                    .getKeyStroke(KeyEvent.VK_L, java.awt.Event.ALT_MASK));
441        }
442
443        @Override
444        public void actionPerformed(ActionEvent event) {
445            if (_configuration == null) {
446                MessageHandler.error("Cannot open an instance "
447                        + "without a configuration.");
448                return;
449            }
450
451            // Determine which entity was selected for the open actor action.
452            super.actionPerformed(event);
453            NamedObj object = getTarget();
454
455            try {
456                StringParameter actorInteractionAddonParameter;
457                actorInteractionAddonParameter = (StringParameter) _configuration
458                        .getAttribute("_actorInteractionAddon",
459                                Parameter.class);
460
461                if (actorInteractionAddonParameter != null) {
462                    String actorInteractionAddonClassName = actorInteractionAddonParameter
463                            .stringValue();
464
465                    Class actorInteractionAddonClass = Class
466                            .forName(actorInteractionAddonClassName);
467
468                    ActorInteractionAddon actorInteractionAddon = (ActorInteractionAddon) actorInteractionAddonClass
469                            .newInstance();
470
471                    if (actorInteractionAddon
472                            .isActorOfInterestForAddonController(object)) {
473                        actorInteractionAddon.openInstanceAction(this, object);
474                    }
475
476                }
477
478            } catch (Exception e) {
479                e.printStackTrace();
480            }
481
482            if (object instanceof CompositeEntity) {
483                try {
484                    _configuration.openInstance(object);
485                } catch (Exception ex) {
486                    MessageHandler.error("Open instance failed.", ex);
487                }
488            } else if (object instanceof Entity) {
489                // If this is not a CompositeEntity, need to
490                // do something different here as the method above will
491                // open the source code as a last resort.
492                Frame parent = getFrame();
493                DialogTableau dialogTableau = DialogTableau.createDialog(parent,
494                        _configuration, ((TableauFrame) parent).getEffigy(),
495                        OpenInstanceDialog.class, (Entity) object);
496
497                if (dialogTableau != null) {
498                    dialogTableau.show();
499                }
500            }
501        }
502    }
503
504    ///////////////////////////////////////////////////////////////////
505    //// SaveInFileAction
506
507    /**
508     * An action to save this actor in a file.
509     */
510    // private class SaveInFileAction extends FigureAction {
511    // /** Create a new action to save a model in a file.
512    // */
513    // public SaveInFileAction() {
514    // super("Save Actor In File");
515    // putValue("tooltip", "Save actor in a file");
516    // }
517    //
518    // /** Save the target object in a file.
519    // * @param event The action event.
520    // */
521    // public void actionPerformed(ActionEvent event) {
522    //// Find the target.
523    // super.actionPerformed(event);
524    //
525    // NamedObj object = getTarget();
526    //
527    // if (object instanceof Entity) {
528    // Entity entity = (Entity) object;
529    //
530    // BasicGraphController controller = (BasicGraphController) getController();
531    // BasicGraphFrame frame = controller.getFrame();
532    //
533    // try {
534    // frame.saveComponentInFile(entity);
535    // } catch (Exception e) {
536    // MessageHandler.error("Save failed.", e);
537    // }
538    // }
539    // }
540    // }
541
542    ///////////////////////////////////////////////////////////////////
543    //// SaveInLibraryAction
544    /**
545     * An action to save this actor in the library.
546     */
547    @SuppressWarnings("serial")
548    private class SaveInLibraryAction extends FigureAction {
549        /**
550         * Create a new action to save an actor in a library.
551         */
552        public SaveInLibraryAction() {
553            super("Save Actor In Library");
554            putValue("tooltip",
555                    "Save the actor as a component in the user library");
556        }
557
558        /**
559         * Create a new instance of the current model in the actor library of
560         * the configuration.
561         *
562         * @param event
563         *            The action event.
564         */
565        @Override
566        public void actionPerformed(ActionEvent event) {
567            // Find the target.
568            super.actionPerformed(event);
569
570            NamedObj object = getTarget();
571
572            if (object instanceof Entity) {
573                Entity entity = (Entity) object;
574                try {
575                    UserActorLibrary.saveComponentInLibrary(_configuration,
576                            entity);
577                } catch (Exception ex) {
578                    // We catch exceptions here because this method used to
579                    // not throw Exceptions, and we don't want to break
580                    // compatibility.
581                    MessageHandler.error(
582                            "Failed to save \"" + entity.getName() + "\".");
583                }
584            }
585        }
586    }
587}