001/* The node controller for objects with icons. 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 */ 027package ptolemy.vergil.basic; 028 029// This file must work with Ptiny, so do not import packages 030// from ptolemy.domains, actor.lib, or org. 031 032import java.awt.Color; 033import java.util.ArrayList; 034import java.util.HashMap; 035import java.util.List; 036import java.util.Map; 037 038import diva.canvas.Figure; 039import diva.canvas.toolbox.SVGUtilities; 040import diva.graph.GraphController; 041import diva.graph.GraphModel; 042import diva.graph.NodeRenderer; 043import ptolemy.actor.ExecutionAttributes; 044import ptolemy.actor.gui.ColorAttribute; 045import ptolemy.kernel.Entity; 046import ptolemy.kernel.util.Attribute; 047import ptolemy.kernel.util.ChangeRequest; 048import ptolemy.kernel.util.Decorator; 049import ptolemy.kernel.util.DecoratorAttributes; 050import ptolemy.kernel.util.IllegalActionException; 051import ptolemy.kernel.util.InternalErrorException; 052import ptolemy.kernel.util.KernelException; 053import ptolemy.kernel.util.Locatable; 054import ptolemy.kernel.util.NameDuplicationException; 055import ptolemy.kernel.util.NamedObj; 056import ptolemy.kernel.util.StringAttribute; 057import ptolemy.vergil.icon.EditorIcon; 058import ptolemy.vergil.icon.XMLIcon; 059import ptolemy.vergil.kernel.AnimationRenderer; 060import ptolemy.vergil.kernel.AttributeController; 061import ptolemy.vergil.kernel.ShadowRenderer; 062 063/////////////////////////////////////////////////////////////////// 064//// IconController 065 066/** 067 This class provides interaction with nodes that represent Ptolemy II 068 objects that are represented on screen as icons, such as attributes 069 and entities. It provides a double click binding to edit the parameters 070 of the node, and a context menu containing a command to edit parameters 071 ("Configure"). This adds to the base class the ability to render an 072 icon for the object being controlled, where the icon is specified 073 by a contained attribute of class EditorIcon (typically, but not 074 necessarily named "_icon"). 075 076 @author Steve Neuendorffer and Edward A. Lee 077 @version $Id$ 078 @since Ptolemy II 2.0 079 @Pt.ProposedRating Red (eal) 080 @Pt.AcceptedRating Red (johnr) 081 */ 082public class IconController extends ParameterizedNodeController { 083 /** Create a controller associated with the specified graph 084 * controller. 085 * @param controller The associated graph controller. 086 */ 087 public IconController(GraphController controller) { 088 super(controller); 089 setNodeRenderer(new IconRenderer()); 090 } 091 092 /////////////////////////////////////////////////////////////////// 093 //// private variables //// 094 095 /** Map used to keep track of icons that have been created 096 * but not yet assigned to a container. 097 */ 098 private static Map _iconsPendingContainer = new HashMap(); 099 100 /////////////////////////////////////////////////////////////////// 101 //// inner classes //// 102 103 /** An icon renderer. */ 104 public class IconRenderer implements NodeRenderer { 105 // FindBugs wants this static, but adding a constructor that takes 106 // a GraphModel does not work. If you create a model that has 107 // a composite and then save it, dragging around the composite 108 // results in duplicate composites. 109 110 /** Render a visual representation of the given node. If the 111 * StringAttribute _color of the node is set then use that color to 112 * highlight the node. If the StringAttribute _explanation of the node 113 * is set then use it to set the tooltip. 114 * @see diva.graph.NodeRenderer#render(java.lang.Object) 115 */ 116 @Override 117 public Figure render(Object n) { 118 Locatable location = (Locatable) n; 119 final NamedObj object = location.getContainer(); 120 121 // NOTE: this code is similar to that in PtolemyTreeCellRenderer 122 Figure result = null; 123 124 try { 125 List iconList = object.attributeList(EditorIcon.class); 126 127 // Check to see whether there is an icon that has been created, 128 // but not inserted. 129 if (iconList.size() == 0) { 130 XMLIcon alreadyCreated = (XMLIcon) _iconsPendingContainer 131 .get(object); 132 133 if (alreadyCreated != null) { 134 iconList.add(alreadyCreated); 135 } 136 } 137 138 // If there are still no icons, then we need to create one. 139 if (iconList.size() == 0) { 140 // NOTE: This used to directly create an XMLIcon within 141 // the container "object". However, this is not cosher, 142 // since we may not be able to get write access on the 143 // workspace. We instead use a hack supported by XMLIcon 144 // to create an XMLIcon with no container (this does not 145 // require write access to the workspace), and specify 146 // to it what the container will eventually be. Then 147 // we queue a change request to make that the container. 148 // Further, we have to make a record of the figure, indexed 149 // by the object, in case some other change request is 150 // executed before this gets around to setting the 151 // container. Otherwise, that second change request 152 // will result in the creation of a second figure. 153 154 final EditorIcon icon = XMLIcon 155 .getXMLIcon(object.workspace(), "_icon"); 156 icon.setContainerToBe(object); 157 icon.setPersistent(false); 158 result = icon.createFigure(); 159 160 // NOTE: Make sure this is done before the change request 161 // below is executed, which may be as early as when it is 162 // requested. 163 _iconsPendingContainer.put(object, icon); 164 165 // NOTE: Make sure the source of this change request is 166 // the graph model. Otherwise, this change request will 167 // trigger a redraw of the entire graph, which will result 168 // in another call to this very same method, which will 169 // result in creation of yet another figure before this 170 // method even returns! 171 GraphController controller = IconController.this 172 .getController(); 173 GraphModel graphModel = controller.getGraphModel(); 174 ChangeRequest request = new ChangeRequest(graphModel, 175 "Set the container of a new XMLIcon.") { 176 // NOTE: The KernelException should not be thrown, 177 // but if it is, it will be handled properly. 178 @Override 179 protected void _execute() throws KernelException { 180 _iconsPendingContainer.remove(object); 181 182 // If the icon already has a container, do nothing. 183 if (icon.getContainer() != null) { 184 return; 185 } 186 187 // If the container already has an icon, do nothing. 188 // MatlabWirelessSoundDetection.xml has some _icon properties, 189 // so we check for them. 190 List icons = object.attributeList(EditorIcon.class); 191 if (icons != null && icons.size() > 0 192 || object.getAttribute("_icon") != null) { 193 return; 194 } 195 196 icon.setContainer(object); 197 } 198 }; 199 200 request.setPersistent(false); 201 object.requestChange(request); 202 } else if (iconList.size() >= 1) { 203 // Use only the last icon in the list. 204 EditorIcon icon = (EditorIcon) iconList 205 .get(iconList.size() - 1); 206 result = icon.createFigure(); 207 } 208 } catch (KernelException ex) { 209 throw new InternalErrorException(null, ex, 210 "Could not create icon " + "in " + object + " even " 211 + "though one did not previously exist."); 212 } 213 214 if (result == null) { 215 throw new InternalErrorException("Failed to create icon."); 216 } else { 217 result.setToolTipText(object.getClassName()); 218 } 219 220 // Check to see if it has 221 // attributes that specify its color or an explanation. 222 // Old way to specify a color. 223 try { 224 StringAttribute colorAttr = (StringAttribute) object 225 .getAttribute("_color", StringAttribute.class); 226 if (colorAttr != null) { 227 String color = colorAttr.getExpression(); 228 AnimationRenderer animationRenderer = new AnimationRenderer( 229 SVGUtilities.getColor(color)); 230 animationRenderer.renderSelected(result); 231 } 232 } catch (IllegalActionException e) { 233 // Ignore 234 } 235 236 // New way to specify a highlight color. 237 AttributeController.renderHighlight(object, result); 238 239 try { 240 // clear highlighting 241 Attribute highlightColor = object 242 .getAttribute("_decoratorHighlightColor"); 243 if (highlightColor != null) { 244 object.removeAttribute(highlightColor); 245 } 246 247 List<Decorator> decorators = new ArrayList(); 248 decorators.addAll(object.decorators()); 249 250 for (Decorator decorator : decorators) { 251 DecoratorAttributes decoratorAttributes = object 252 .getDecoratorAttributes(decorator); 253 if (decoratorAttributes instanceof ExecutionAttributes) { 254 if (decoratorAttributes.getDecorator() != null 255 && ((ExecutionAttributes) decoratorAttributes) 256 .enabled()) { 257 try { 258 if (object.getAttribute( 259 "_decoratorHighlightColor") == null) { 260 highlightColor = new ColorAttribute(object, 261 "_decoratorHighlightColor"); 262 Attribute attribute = ((NamedObj) decorator) 263 .getAttribute( 264 "decoratorHighlightColor"); 265 String colorExpression = "{0.5, 0.5, 0.5, 0.5}"; 266 if (attribute != null) { 267 colorExpression = (((ColorAttribute) attribute) 268 .getToken()).toString(); 269 } 270 ((ColorAttribute) highlightColor) 271 .setExpression(colorExpression); 272 } 273 } catch (NameDuplicationException e) { 274 // Not gonna happen. 275 } 276 } 277 } 278 } 279 280 } catch (IllegalActionException e1) { 281 // TODO Auto-generated catch block 282 e1.printStackTrace(); 283 } 284 285 AttributeController.renderDecoratorHighlight(object, result); 286 287 // If a shadow is specified, render it now. 288 // The shadow attribute can be contained by the container 289 // so that it is applied to all icons corresponding to Entity 290 // objects (not attributes). This can be overridden for each 291 // object (including attributes) by providing an individual 292 // shadow specification. An empty color results in no shadow. 293 try { 294 // If the object itself has a shadow specification, use that. 295 ColorAttribute shadowAttribute = (ColorAttribute) object 296 .getAttribute("_shadowColor", ColorAttribute.class); 297 if (shadowAttribute != null) { 298 if (!shadowAttribute.getExpression().trim().equals("")) { 299 Color color = shadowAttribute.asColor(); 300 // FIXME: How to set the size of the shadow? 301 ShadowRenderer animationRenderer = new ShadowRenderer( 302 color); 303 animationRenderer.renderSelected(result); 304 } 305 } else if (object instanceof Entity) { 306 // If the container has a shadow specification, use that. 307 NamedObj container = object.getContainer(); 308 if (container != null) { 309 shadowAttribute = (ColorAttribute) container 310 .getAttribute("_shadowColor", 311 ColorAttribute.class); 312 if (shadowAttribute != null && !shadowAttribute 313 .getExpression().trim().equals("")) { 314 Color color = shadowAttribute.asColor(); 315 // FIXME: How to set the size of the shadow? 316 ShadowRenderer animationRenderer = new ShadowRenderer( 317 color); 318 animationRenderer.renderSelected(result); 319 } 320 } 321 } 322 } catch (IllegalActionException e) { 323 // Ignore. 324 } 325 326 try { 327 StringAttribute explanationAttribute = (StringAttribute) object 328 .getAttribute("_explanation", StringAttribute.class); 329 if (explanationAttribute != null) { 330 result.setToolTipText(explanationAttribute.getExpression()); 331 } 332 } catch (IllegalActionException e) { 333 // Ignore. 334 } 335 336 return result; 337 } 338 } 339}