001/*  The action to open a composite actor model, an ontology, or a
002 *  MoMLModelAttribute.
003
004 Copyright (c) 1998-2016 The Regents of the University of California.
005 All rights reserved.
006 Permission is hereby granted, without written agreement and without
007 license or royalty fees, to use, copy, modify, and distribute this
008 software and its documentation for any purpose, provided that the above
009 copyright notice and the following two paragraphs appear in all copies
010 of this software.
011
012 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
013 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
014 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
015 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
016 SUCH DAMAGE.
017
018 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
019 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
020 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
021 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
022 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
023 ENHANCEMENTS, OR MODIFICATIONS.
024
025 PT_COPYRIGHT_VERSION_2
026 COPYRIGHTENDKEY
027
028 */
029package ptolemy.vergil.basic;
030
031import java.awt.Toolkit;
032import java.awt.event.ActionEvent;
033import java.awt.event.KeyEvent;
034import java.io.File;
035import java.net.URI;
036
037import javax.swing.KeyStroke;
038
039import diva.gui.GUIUtilities;
040import ptolemy.actor.gui.Configuration;
041import ptolemy.actor.gui.Effigy;
042import ptolemy.actor.gui.PtolemyEffigy;
043import ptolemy.actor.gui.Tableau;
044import ptolemy.data.expr.Parameter;
045import ptolemy.data.expr.StringParameter;
046import ptolemy.kernel.util.InternalErrorException;
047import ptolemy.kernel.util.NamedObj;
048import ptolemy.moml.MoMLModelAttribute;
049import ptolemy.util.MessageHandler;
050import ptolemy.util.StringUtilities;
051import ptolemy.vergil.actor.ActorInteractionAddon;
052import ptolemy.vergil.toolbox.FigureAction;
053
054/** <p>The action to open a composite actor model, an ontology, or a
055 *  MoMLModelAttribute. This class must remain named LookInsideAction for
056 *  backward compatibility.</p>
057 *  <p>This used to be a private class contained in
058 *  {@link ptolemy.vergil.actor.ActorController ActorController}, but it is
059 *  now relevant for ptolemy.vergil.ontologies.OntologyEntityController
060 *  OntologyEntityController and
061 *  {@link ptolemy.vergil.basic.MoMLModelAttributeController MoMLModelAttributeController},
062 *  so it has been pulled out into a public class. Additionally MoMLModelAttributeController
063 *  requires a different implementation to open its contained model since it is
064 *  an Attribute and not a CompositeEntity, and thus its contained model does
065 *  not fit in the traditional Ptolemy containment hierarchy.</p>
066 *  <p>Previously
067 *  {@link MoMLModelAttribute} contained its own private LookInsideAction class, but the
068 *  shortcut key binding did not work correctly. This was because in a Ptolemy
069 *  model, a shortcut key can only bind one action for every node in the entire model
070 *  graphical space to any given key. If a model contains both normal
071 *  {@link ptolemy.kernel.CompositeEntity CompositeEntity}
072 *  (including ptolemy.data.ontologies.Ontology entities)
073 *  elements and MoMLModelAttributes, then only one look inside action will be bound
074 *  to the shortcut L key even though each action will be accessible from their
075 *  individual context menus (See {@link GUIUtilities#addHotKey(javax.swing.JComponent,
076 *  javax.swing.Action, javax.swing.KeyStroke) GUIUtilities.addHotKey()}).</p>
077 *  <p>The solution here
078 *  is that there is a single class to implement the look inside action that has
079 *  two private methods to implement look inside for the normal Ptolemy CompositeEntity
080 *  case and the special MoMLModelAttribute case.  The controller for each
081 *  respective Ptolemy element can customize its instance of the LookInsideAction with its
082 *  own menu label for its context menu, but the actual {@link #actionPerformed} method that
083 *  executes the action is the same for all instances. So regardless of which
084 *  LookInsideAction gets bound to the L key, it will work for all Ptolemy elements.</p>
085 *  <p>If the element is not a CompositeEntity or a MoMLModelAttribute, it will
086 *  just open the java text file of the element's class definition.</p>
087 *
088 *  @author Charles Shelton
089 *  @version $Id$
090 *  @since Ptolemy II 10.0
091 *  @Pt.ProposedRating Red (cshelton)
092 *  @Pt.AcceptedRating Red (cshelton)
093 */
094@SuppressWarnings("serial")
095public class LookInsideAction extends FigureAction {
096
097    /** Create a new LookInsideAction object with the given
098     *  string as its menu action label.
099     *  @param menuActionLabel The label of the menu action to be displayed in
100     *   the GUI context menus.
101     */
102    public LookInsideAction(String menuActionLabel) {
103        super(menuActionLabel);
104
105        // Attach a key binding for look inside (also called
106        // open actor).
107        // If we are in an applet, so Control-L or Command-L will
108        // be caught by the browser as "Open Location", so we don't
109        // supply Control-L or Command-L as a shortcut under applets.
110        if (!StringUtilities.inApplet()) {
111            // For some inexplicable reason, the I key doesn't work here.
112            // Use L, which used to be used for layout.
113            // Avoid Control_O, which is open file.
114            putValue(GUIUtilities.ACCELERATOR_KEY, KeyStroke.getKeyStroke(
115                    KeyEvent.VK_L,
116                    Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
117        }
118    }
119
120    ///////////////////////////////////////////////////////////////////
121    ////                         public methods                    ////
122
123    /** Execute the look inside action command received from the event sent from
124     *  the user interface.
125     *  @param event The event received to execute the look inside action.
126     */
127    @Override
128    public void actionPerformed(ActionEvent event) {
129        if (_configuration == null) {
130            MessageHandler.error("Cannot open a model element "
131                    + "without a configuration.");
132            return;
133        }
134
135        // Determine which entity was selected for the open actor action.
136        super.actionPerformed(event);
137        NamedObj object = getTarget();
138
139        if (object instanceof MoMLModelAttribute) {
140            _openContainedModel((MoMLModelAttribute) object);
141        } else {
142            _openModel(object);
143        }
144    }
145
146    /** Set the configuration to be used by the LookInsideAction object.
147     *  @param configuration The configuration.
148     */
149    public void setConfiguration(Configuration configuration) {
150        _configuration = configuration;
151    }
152
153    ///////////////////////////////////////////////////////////////////
154    ////                         private methods                   ////
155
156    /** Open the model contained by the given MoMLModelAttribute. This is
157     *  significantly different from opening a normal CompositeEntity.
158     *  @param momlModelAttribute The MoMLModelAttribute to be opened.
159     */
160    private void _openContainedModel(MoMLModelAttribute momlModelAttribute) {
161        try {
162            NamedObj model = momlModelAttribute.getContainedModel();
163            Tableau tableau = _configuration.openInstance(model);
164            Effigy effigy = (Effigy) tableau.getContainer();
165
166            // If the model is contained in a separate file, then we
167            // need to set its uri parameter.
168            // If it is in the same file, we have more work to do.
169            File modelFile = momlModelAttribute.modelURL.asFile();
170            if (modelFile != null) {
171                // Model is in a separate file.
172                effigy.uri.setURL(momlModelAttribute.modelURL.asURL());
173            } else {
174                // Model is in the same file.
175                tableau.setMaster(false);
176
177                // The effigy returned above has three problems. First,
178                // it's container is the directory in the
179                // configuration. We want it to be contained by the following
180                // containerEffigy.
181                final Effigy containerEffigy = _configuration
182                        .getEffigy(momlModelAttribute.getContainer());
183
184                if (containerEffigy == null) {
185                    throw new InternalErrorException(momlModelAttribute, null,
186                            "Could not get the effigy for "
187                                    + momlModelAttribute);
188                } else {
189                    // Second, the effigy returned above returns the wrong value in its
190                    // masterEffigy() method. That method returns the effigy associated
191                    // with the toplevel, which is the same as effigy. We want it to
192                    // return whatever the masterEffigy of containerEffigy is.
193                    // We accomplish this by substituting a new effigy.
194                    // This technique is borrowed from what is done in
195                    // PtolemyFrame.getEffigy().
196                    PtolemyEffigy newEffigy = new PtolemyEffigy(containerEffigy,
197                            containerEffigy.uniqueName(model.getName())) {
198                        @Override
199                        public Effigy masterEffigy() {
200                            return containerEffigy.masterEffigy();
201                        }
202                    };
203
204                    // Third, the uri attribute and file properties
205                    // of the effigy are not set to
206                    // refer to the file that will actually save the model.
207                    // This could be an external file if the modelURL parameter
208                    // of the MoMLModelAttribute is set.
209                    newEffigy.setModified(effigy.isModified());
210                    URI uri = containerEffigy.uri.getURI();
211                    newEffigy.uri.setURI(uri);
212                    tableau.setContainer(newEffigy);
213                    effigy.setContainer(null);
214
215                    newEffigy.setModel(model);
216                }
217            }
218        } catch (Exception ex) {
219            MessageHandler.error("Unable to open the model contained by "
220                    + momlModelAttribute.getName(), ex);
221        }
222    }
223
224    /** Open the model contained by the given NamedObj. If it is a CompositeEntity,
225     *  the submodel will be opened in a separate window. Otherwise the java
226     *  source code text file for the element's class will be displayed.
227     *  @param modelObject The NamedObj element to be opened.
228     */
229    private void _openModel(NamedObj modelObject) {
230        try {
231            StringParameter actorInteractionAddonParameter;
232            actorInteractionAddonParameter = (StringParameter) _configuration
233                    .getAttribute("_actorInteractionAddon", Parameter.class);
234
235            if (actorInteractionAddonParameter != null) {
236                String actorInteractionAddonClassName = actorInteractionAddonParameter
237                        .stringValue();
238
239                Class actorInteractionAddonClass = Class
240                        .forName(actorInteractionAddonClassName);
241
242                ActorInteractionAddon actorInteractionAddon = (ActorInteractionAddon) actorInteractionAddonClass
243                        .newInstance();
244
245                if (actorInteractionAddon
246                        .isActorOfInterestForAddonController(modelObject)) {
247                    actorInteractionAddon.lookInsideAction(this, modelObject);
248                }
249
250            }
251
252        } catch (Exception e) {
253            MessageHandler.error("Open model element failed.", e);
254            // e.printStackTrace();
255        }
256
257        // NOTE: Used to open source code here if the object
258        // was not a CompositeEntity. But this made it impossible
259        // to associate a custom tableau with an atomic entity.
260        // So now, the Configuration opens the source code as a
261        // last resort.
262        try {
263            _configuration.openModel(modelObject);
264        } catch (Exception ex) {
265            MessageHandler.error("Open model element failed.", ex);
266        }
267    }
268
269    ///////////////////////////////////////////////////////////////////
270    ////                         private variables                 ////
271
272    /** The configuration for the controller that contains this LookInsideAction. */
273    private Configuration _configuration;
274}