001/* The node controller for class definitions. 002 003 Copyright (c) 2003-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.Color; 031import java.awt.Toolkit; 032import java.awt.event.ActionEvent; 033import java.awt.event.KeyEvent; 034import java.lang.ref.WeakReference; 035import java.util.Iterator; 036import java.util.List; 037 038import javax.swing.Action; 039import javax.swing.KeyStroke; 040 041import diva.canvas.CompositeFigure; 042import diva.canvas.Figure; 043import diva.canvas.toolbox.BasicFigure; 044import diva.graph.GraphController; 045import diva.graph.JGraph; 046import diva.gui.GUIUtilities; 047import ptolemy.kernel.InstantiableNamedObj; 048import ptolemy.kernel.util.Instantiable; 049import ptolemy.kernel.util.NamedObj; 050import ptolemy.moml.MoMLChangeRequest; 051import ptolemy.util.MessageHandler; 052import ptolemy.vergil.basic.OffsetMoMLChangeRequest; 053import ptolemy.vergil.toolbox.FigureAction; 054import ptolemy.vergil.toolbox.MenuActionFactory; 055 056/////////////////////////////////////////////////////////////////// 057//// ClassDefinitionController 058 059/** 060 This class provides interaction with nodes that represent Ptolemy II 061 classes. This extends the base class by providing mechanisms in the 062 context menu for creating an instance, creating a subclass, 063 and converting to an instance. 064 <p> 065 NOTE: There should be only one instance of this class associated with 066 a given GraphController. This is because this controller listens for 067 changes to the graph and re-renders the ports of any actor instance 068 in the graph when the graph changes. If there is more than one instance, 069 this rendering will be done twice, which can result in bugs like port 070 labels appearing twice. 071 072 @author Edward A. Lee and Steve Neuendorffer 073 @version $Id$ 074 @since Ptolemy II 4.0 075 @Pt.ProposedRating Red (eal) 076 @Pt.AcceptedRating Red (johnr) 077 */ 078public class ClassDefinitionController extends ActorController { 079 /** Create an actor instance controller associated with the 080 * specified graph controller with full access. 081 * @param controller The associated graph controller. 082 */ 083 public ClassDefinitionController(GraphController controller) { 084 this(controller, FULL); 085 } 086 087 /** Create a controller associated with the specified graph 088 * controller with the specified access. 089 * @param controller The associated graph controller. 090 * @param access The access level, one of FULL or PARTIAL. 091 */ 092 public ClassDefinitionController(GraphController controller, 093 Access access) { 094 super(controller, access); 095 096 if (access == FULL) { 097 // Use a submenu. 098 Action[] actions = { _createInstanceAction, _createSubclassAction, 099 _convertToInstanceAction }; 100 _menuFactory.addMenuItemFactory( 101 new MenuActionFactory(actions, "Class Actions")); 102 } 103 } 104 105 /** Add hot keys to the actions in the given JGraph. 106 * @param jgraph The JGraph to which hot keys are to be added. 107 */ 108 @Override 109 public void addHotKeys(JGraph jgraph) { 110 super.addHotKeys(jgraph); 111 // _convertToInstanceAction does not have a hot key. 112 // GUIUtilities.addHotKey(jgraph, _convertToInstanceAction); 113 GUIUtilities.addHotKey(jgraph, _createInstanceAction); 114 GUIUtilities.addHotKey(jgraph, _createSubclassAction); 115 } 116 117 /////////////////////////////////////////////////////////////////// 118 //// protected methods //// 119 120 /** Draw the node at its location. This overrides the base class 121 * to highlight the actor to indicate that it is a class definition. 122 */ 123 @Override 124 protected Figure _renderNode(Object node) { 125 Figure nf = super._renderNode(node); 126 127 if (nf instanceof CompositeFigure) { 128 // This cast should be safe... 129 CompositeFigure cf = (CompositeFigure) nf; 130 Figure backgroundFigure = cf.getBackgroundFigure(); 131 132 // This might be null because the node is hidden. 133 if (backgroundFigure != null) { 134 BasicFigure bf = new BasicFigure(backgroundFigure.getBounds(), 135 4.0f); 136 bf.setStrokePaint(_HIGHLIGHT_COLOR); 137 // Put the highlighting in the background, 138 // behind the actor label. 139 int index = cf.getFigureCount(); 140 if (index < 0) { 141 index = 0; 142 } 143 cf.add(index, bf); 144 } 145 146 return cf; 147 } 148 149 return nf; 150 } 151 152 /////////////////////////////////////////////////////////////////// 153 //// protected variables //// 154 155 /** The action that handles converting a class to an instance. 156 */ 157 protected ConvertToInstanceAction _convertToInstanceAction = new ConvertToInstanceAction( 158 "Convert to Instance"); 159 160 /** The action that handles creating an instance from a class. 161 */ 162 protected CreateInstanceAction _createInstanceAction = new CreateInstanceAction( 163 "Create Instance"); 164 165 /** The action that handles creating a subclass from a class. 166 */ 167 protected CreateSubclassAction _createSubclassAction = new CreateSubclassAction( 168 "Create Subclass"); 169 170 /////////////////////////////////////////////////////////////////// 171 //// private methods //// 172 173 /** Create a change request to create an instance or a subclass 174 * of the object. Do nothing if the specified object does not 175 * implement Instantiable. 176 * @see Instantiable 177 * @param object The class to subclass or instantiate. 178 * @param subclass True to create a subclass, false to create 179 * an instance. 180 */ 181 private void _createChangeRequest(NamedObj object, boolean subclass) { 182 if (!(object instanceof Instantiable)) { 183 return; 184 } 185 NamedObj container = object.getContainer(); 186 StringBuffer moml = new StringBuffer(); 187 moml.append("<group name=\"auto\">"); 188 189 // FIXME: Can we adjust the location here? 190 // NOTE: This controller is expected to be used 191 // only for class definitions, which must be instances 192 // of InstantiableNamedObj, so this cast should be safe. 193 // However, the key bindings are active even if it's not 194 // a class, so if it's not a class, we just do nothing here. 195 if (((Instantiable) object).isClassDefinition()) { 196 if (subclass) { 197 moml.append("<class name=\"" + "SubclassOf" + object.getName() 198 + "\" extends=\"" + object.getName() + "\"/>"); 199 } else { 200 moml.append("<entity name=\"" + "InstanceOf" + object.getName() 201 + "\" class=\"" + object.getName() + "\"/>"); 202 } 203 204 moml.append("</group>"); 205 206 MoMLChangeRequest request = new OffsetMoMLChangeRequest(this, 207 container, moml.toString()); 208 container.requestChange(request); 209 } 210 } 211 212 /////////////////////////////////////////////////////////////////// 213 //// private variables //// 214 215 /** A fourth argument would make this highlight translucent, which enables 216 * combination with other highlights. However, this forces printing 217 * to PDF to rasterize the image, which results in far lower quality. 218 */ 219 private static Color _HIGHLIGHT_COLOR = new Color(150, 150, 255); 220 221 /////////////////////////////////////////////////////////////////// 222 //// inner classes //// 223 /////////////////////////////////////////////////////////////////// 224 //// ConvertToInstanceAction 225 // An action to convert a class to an instance. 226 @SuppressWarnings("serial") 227 private class ConvertToInstanceAction extends FigureAction { 228 public ConvertToInstanceAction(String commandName) { 229 super(commandName); 230 } 231 232 @Override 233 public void actionPerformed(ActionEvent e) { 234 // If access is not full, do nothing. 235 if (_access != FULL) { 236 return; 237 } 238 239 // Determine which entity was selected for the create instance action. 240 super.actionPerformed(e); 241 242 // NOTE: This cast should be safe because this controller is 243 // used for actors. 244 InstantiableNamedObj object = (InstantiableNamedObj) getTarget(); 245 NamedObj container = object.getContainer(); 246 247 // Assumes MoML parser will convert to instance. 248 if (!object.isClassDefinition()) { 249 // Object is already an instance. Do nothing. 250 return; 251 } 252 253 // If the class has objects that defer to it, then 254 // refuse to convert. 255 boolean hasDeferrals = false; 256 List deferred = object.getChildren(); 257 StringBuffer names = new StringBuffer(); 258 259 if (deferred != null) { 260 // List contains weak references, so it's not 261 // sufficient to just check the length. 262 Iterator deferrers = deferred.iterator(); 263 264 while (deferrers.hasNext()) { 265 WeakReference deferrer = (WeakReference) deferrers.next(); 266 NamedObj deferrerObject = (NamedObj) deferrer.get(); 267 268 if (deferrerObject != null) { 269 hasDeferrals = true; 270 271 if (names.length() > 0) { 272 names.append(", "); 273 } 274 275 names.append(deferrerObject.getFullName()); 276 } 277 } 278 } 279 280 if (hasDeferrals) { 281 MessageHandler.error("Cannot convert to instance because " 282 + "there are instances and/or subclasses:\n" 283 + names.toString()); 284 return; 285 } 286 287 String moml = "<entity name=\"" + object.getName() + "\"/>"; 288 MoMLChangeRequest request = new MoMLChangeRequest(this, container, 289 moml); 290 container.requestChange(request); 291 } 292 } 293 294 /////////////////////////////////////////////////////////////////// 295 //// CreateInstanceAction 296 // An action to instantiate a class. 297 @SuppressWarnings("serial") 298 private class CreateInstanceAction extends FigureAction { 299 public CreateInstanceAction(String commandName) { 300 super(commandName); 301 putValue(GUIUtilities.ACCELERATOR_KEY, KeyStroke.getKeyStroke( 302 KeyEvent.VK_I, 303 Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); 304 } 305 306 @Override 307 public void actionPerformed(ActionEvent e) { 308 // If access is not full, do nothing. 309 if (_access != FULL) { 310 return; 311 } 312 313 // Determine which entity was selected for the create instance action. 314 super.actionPerformed(e); 315 316 NamedObj object = getTarget(); 317 _createChangeRequest(object, false); 318 } 319 } 320 321 /////////////////////////////////////////////////////////////////// 322 //// CreateSubclassAction 323 // An action to subclass a class. 324 @SuppressWarnings("serial") 325 private class CreateSubclassAction extends FigureAction { 326 public CreateSubclassAction(String commandName) { 327 super(commandName); 328 putValue(GUIUtilities.ACCELERATOR_KEY, KeyStroke.getKeyStroke( 329 KeyEvent.VK_U, 330 Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); 331 } 332 333 @Override 334 public void actionPerformed(ActionEvent e) { 335 // If access is not full, do nothing. 336 if (_access != FULL) { 337 return; 338 } 339 340 // Determine which entity was selected for the 341 // create subclass action. 342 super.actionPerformed(e); 343 344 NamedObj object = getTarget(); 345 _createChangeRequest(object, true); 346 } 347 } 348}