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}