001/* The node controller for states. 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.KeyEvent; 033import java.util.ArrayList; 034import java.util.HashMap; 035import java.util.List; 036import java.util.Map; 037 038import javax.swing.KeyStroke; 039 040import diva.canvas.Figure; 041import diva.graph.GraphController; 042import diva.graph.GraphModel; 043import diva.graph.NodeRenderer; 044import diva.gui.GUIUtilities; 045import ptolemy.actor.ExecutionAttributes; 046import ptolemy.actor.TypedActor; 047import ptolemy.actor.gui.ColorAttribute; 048import ptolemy.domains.modal.kernel.State; 049import ptolemy.kernel.util.Attribute; 050import ptolemy.kernel.util.ChangeRequest; 051import ptolemy.kernel.util.Decorator; 052import ptolemy.kernel.util.DecoratorAttributes; 053import ptolemy.kernel.util.IllegalActionException; 054import ptolemy.kernel.util.InternalErrorException; 055import ptolemy.kernel.util.KernelException; 056import ptolemy.kernel.util.Locatable; 057import ptolemy.kernel.util.NameDuplicationException; 058import ptolemy.kernel.util.NamedObj; 059import ptolemy.util.MessageHandler; 060import ptolemy.util.StringUtilities; 061import ptolemy.vergil.icon.EditorIcon; 062import ptolemy.vergil.icon.XMLIcon; 063import ptolemy.vergil.kernel.AttributeController; 064import ptolemy.vergil.kernel.AttributeWithIconController; 065import ptolemy.vergil.toolbox.FigureAction; 066 067/////////////////////////////////////////////////////////////////// 068//// StateController 069 070/** 071 This class provides interaction with nodes that represent states in an 072 FSM graph. It provides a double click binding to edit the parameters 073 of the state, and a context menu containing a commands to edit parameters 074 ("Configure"), rename, get documentation, and look inside. The looks 075 inside command opens the refinement of the state, if it exists. 076 077 @author Steve Neuendorffer and Edward A. Lee 078 @version $Id$ 079 @since Ptolemy II 8.0 080 @Pt.ProposedRating Red (eal) 081 @Pt.AcceptedRating Red (johnr) 082 */ 083public class StateController extends AttributeWithIconController { 084 085 /** Create a state controller associated with the specified graph 086 * controller. 087 * @param controller The associated graph controller. 088 */ 089 public StateController(GraphController controller) { 090 this(controller, FULL); 091 } 092 093 /** Create a state controller associated with the specified graph 094 * controller. 095 * @param controller The associated graph controller. 096 * @param access The access level. 097 */ 098 public StateController(GraphController controller, Access access) { 099 super(controller, access); 100 101 setNodeRenderer(new StateRenderer(controller.getGraphModel())); 102 } 103 104 /////////////////////////////////////////////////////////////////// 105 //// private variables //// 106 107 /** Map used to keep track of icons that have been created 108 * but not yet assigned to a container. 109 */ 110 private static Map _iconsPendingContainer = new HashMap(); 111 112 /////////////////////////////////////////////////////////////////// 113 //// inner classes //// 114 115 /** An action to look inside a state at its refinement, if it has one. 116 * NOTE: This requires that the configuration be non null, or it 117 * will report an error with a fairly cryptic message. 118 */ 119 @SuppressWarnings("serial") 120 protected class LookInsideAction extends FigureAction { 121 public LookInsideAction() { 122 super("Look Inside"); 123 124 // If we are in an applet, so Control-L or Command-L will 125 // be caught by the browser as "Open Location", so we don't 126 // supply Control-L or Command-L as a shortcut under applets. 127 if (!StringUtilities.inApplet()) { 128 // For some inexplicable reason, the I key doesn't work here. 129 // So we use L. 130 putValue(GUIUtilities.ACCELERATOR_KEY, KeyStroke.getKeyStroke( 131 KeyEvent.VK_L, 132 Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); 133 } 134 } 135 136 @Override 137 public void actionPerformed(ActionEvent e) { 138 if (_configuration == null) { 139 MessageHandler 140 .error("Cannot look inside without a configuration."); 141 return; 142 } 143 144 super.actionPerformed(e); 145 146 NamedObj target = getTarget(); 147 148 // If the target is not an instance of State, do nothing. 149 if (target instanceof State) { 150 try { 151 TypedActor[] refinements = ((State) target).getRefinement(); 152 153 if (refinements != null && refinements.length > 0) { 154 for (TypedActor refinement : refinements) { 155 // Open each refinement. 156 _configuration.openInstance((NamedObj) refinement); 157 } 158 } else { 159 MessageHandler.error("State has no refinement."); 160 } 161 } catch (Exception ex) { 162 MessageHandler.error("Look inside failed: ", ex); 163 } 164 } 165 } 166 } 167 168 /** Render the state as a circle, unless it has a custom icon. 169 */ 170 public static class StateRenderer implements NodeRenderer { 171 172 /** Construct a state renderer. 173 * @param model The GraphModel. 174 */ 175 public StateRenderer(GraphModel model) { 176 super(); 177 _model = model; 178 } 179 180 /** Render an object. 181 * @param n The object to be rendered. This object should 182 * of type Locatable. 183 * @return A Figure. 184 */ 185 @Override 186 public Figure render(Object n) { 187 Locatable location = (Locatable) n; 188 final NamedObj object = location.getContainer(); 189 EditorIcon icon; 190 191 try { 192 // In theory, there shouldn't be more than one 193 // icon, but if there are, use the last one. 194 List icons = object.attributeList(EditorIcon.class); 195 196 // Check to see whether there is an icon that has been created, 197 // but not inserted. 198 if (icons.size() == 0) { 199 XMLIcon alreadyCreated = (XMLIcon) _iconsPendingContainer 200 .get(object); 201 202 if (alreadyCreated != null) { 203 icons.add(alreadyCreated); 204 } 205 } 206 207 if (icons.size() > 0) { 208 icon = (EditorIcon) icons.get(icons.size() - 1); 209 } else { 210 // NOTE: This code is the same as in 211 // IconController.IconRenderer. 212 // NOTE: This used to directly create an XMLIcon within 213 // the container "object". However, this is not cosher, 214 // since we may not be able to get write access on the 215 // workspace. We instead use a hack supported by XMLIcon 216 // to create an XMLIcon with no container (this does not 217 // require write access to the workspace), and specify 218 // to it what the container will eventually be. Then 219 // we queue a change request to make that the container. 220 // Further, we have to make a record of the figure, indexed 221 // by the object, in case some other change request is 222 // executed before this gets around to setting the 223 // container. Otherwise, that second change request 224 // will result in the creation of a second figure. 225 icon = new XMLIcon(object.workspace(), "_icon"); 226 icon.setContainerToBe(object); 227 icon.setPersistent(false); 228 229 // NOTE: Make sure this is done before the change request 230 // below is executed, which may be as early as when it is 231 // requested. 232 _iconsPendingContainer.put(object, icon); 233 234 // NOTE: Make sure the source of this change request is 235 // the graph model. Otherwise, this change request will 236 // trigger a redraw of the entire graph, which will result 237 // in another call to this very same method, which will 238 // result in creation of yet another figure before this 239 // method even returns! 240 final EditorIcon finalIcon = icon; 241 ChangeRequest request = new ChangeRequest(_model, 242 "Set the container of a new XMLIcon.") { 243 // NOTE: The KernelException should not be thrown, 244 // but if it is, it will be handled properly. 245 @Override 246 protected void _execute() throws KernelException { 247 _iconsPendingContainer.remove(object); 248 249 // If the icon already has a container, do nothing. 250 if (finalIcon.getContainer() != null) { 251 return; 252 } 253 254 // If the container already has an icon, do nothing. 255 if (object.getAttribute("_icon") != null) { 256 return; 257 } 258 259 finalIcon.setContainer(object); 260 } 261 }; 262 263 request.setPersistent(false); 264 object.requestChange(request); 265 } 266 } catch (KernelException ex) { 267 throw new InternalErrorException( 268 "could not create icon " + "in " + object + " even " 269 + "though one did not exist"); 270 } 271 272 Figure figure = icon.createFigure(); 273 figure.setToolTipText(object.getName()); 274 275 // New way to specify a highlight color. 276 AttributeController.renderHighlight(object, figure); 277 278 try { 279 // clear highlighting 280 Attribute highlightColor = object 281 .getAttribute("_decoratorHighlightColor"); 282 if (highlightColor != null) { 283 object.removeAttribute(highlightColor); 284 } 285 286 List<Decorator> decorators = new ArrayList(); 287 decorators.addAll(object.decorators()); 288 289 for (Decorator decorator : decorators) { 290 DecoratorAttributes decoratorAttributes = object 291 .getDecoratorAttributes(decorator); 292 if (decoratorAttributes instanceof ExecutionAttributes) { 293 if (decoratorAttributes.getDecorator() != null 294 && ((ExecutionAttributes) decoratorAttributes) 295 .enabled()) { 296 try { 297 if (object.getAttribute( 298 "_decoratorHighlightColor") == null) { 299 highlightColor = new ColorAttribute(object, 300 "_decoratorHighlightColor"); 301 Attribute attribute = ((NamedObj) decorator) 302 .getAttribute( 303 "decoratorHighlightColor"); 304 String colorExpression = "{0.5, 0.5, 0.5, 0.5}"; 305 if (attribute != null) { 306 colorExpression = (((ColorAttribute) attribute) 307 .getToken()).toString(); 308 } 309 ((ColorAttribute) highlightColor) 310 .setExpression(colorExpression); 311 } 312 } catch (NameDuplicationException e) { 313 // Not gonna happen. 314 } 315 } 316 } 317 } 318 319 } catch (IllegalActionException e1) { 320 // TODO Auto-generated catch block 321 e1.printStackTrace(); 322 } 323 324 AttributeController.renderDecoratorHighlight(object, figure); 325 326 return figure; 327 } 328 329 private GraphModel _model; 330 } 331}