001/* An Icon is the graphical representation of an entity or attribute. 002 003 Copyright (c) 1999-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.icon; 029 030import java.awt.Color; 031import java.awt.Font; 032import java.awt.geom.Rectangle2D; 033import java.util.Iterator; 034import java.util.List; 035 036import javax.swing.Icon; 037import javax.swing.SwingConstants; 038 039import diva.canvas.CanvasUtilities; 040import diva.canvas.CompositeFigure; 041import diva.canvas.Figure; 042import diva.canvas.toolbox.BasicFigure; 043import diva.canvas.toolbox.BasicRectangle; 044import diva.canvas.toolbox.LabelFigure; 045import diva.gui.toolbox.FigureIcon; 046import ptolemy.actor.gui.PtolemyPreferences; 047import ptolemy.data.BooleanToken; 048import ptolemy.data.StringToken; 049import ptolemy.data.Token; 050import ptolemy.data.expr.Parameter; 051import ptolemy.kernel.util.Attribute; 052import ptolemy.kernel.util.IconAttribute; 053import ptolemy.kernel.util.IllegalActionException; 054import ptolemy.kernel.util.InternalErrorException; 055import ptolemy.kernel.util.Locatable; 056import ptolemy.kernel.util.NameDuplicationException; 057import ptolemy.kernel.util.Nameable; 058import ptolemy.kernel.util.NamedObj; 059import ptolemy.kernel.util.Settable; 060import ptolemy.kernel.util.Workspace; 061import ptolemy.vergil.basic.RelativeLocatable; 062import ptolemy.vergil.basic.RelativeLocation; 063import ptolemy.vergil.kernel.RelativeLinkFigure; 064import ptolemy.vergil.kernel.attributes.FilledShapeAttribute; 065 066/////////////////////////////////////////////////////////////////// 067//// EditorIcon 068 069/** 070 An icon is the visual representation of an entity or attribute. 071 The visual representation is a Diva Figure. This class is an attribute 072 that serves as a factory for such figures. This base class creates the 073 figure by composing the figures of any contained attributes that have 074 icons. If there are no such contained attributes, then it creates a 075 default figure that is a white rectangle. This class also provides 076 a facility for generating a Swing icon (i.e. an instance of 077 javax.swing.Icon) from that figure (the createIcon() method). 078 <p> 079 The icon consists of a background figure, created by the 080 createBackgroundFigure() method, and a decorated version, created 081 by the createFigure() method. The decorated version has, in this 082 base class, a label showing the name of the entity, unless the entity 083 contains a parameter called "_hideName" with value true. 084 The swing icon created by createIcon() does not include the 085 decorations, but rather is only the background figure. 086 <p> 087 The decorated version can also optionally show parameter values 088 below the icon. If the preference named "_showParameters" 089 has value "All", then all parameters are shown. If it has 090 value "Overridden parameters only", then it will show 091 only overridden parameters. In either case, only 092 parameters that are visible and settable (see the Settable 093 interface) will be shown, regardless of whether they are overridden. 094 <p> 095 When the preference "_showParameters" has value 096 "Overridden parameters only", then some parameter values 097 may be suppressed even if they are overridden. In particular, 098 if an attribute contains a parameter named "_hide" with value 099 true, then that parameter is not shown even if requested. 100 If the container of the attribute contains a parameter named 101 "_hideAllParameters" with value true, then none of its 102 parameters are shown. 103 This is useful, for example, if the icon itself shows 104 the parameter, as with decorative visual elements. 105 <p> 106 Derived classes may simply populate this attribute with other 107 visible attributes (attributes that contain icons), or they can 108 override the createBackgroundFigure() method. This will affect 109 both the Diva Figure and the Swing Icon representations. 110 Derived classes can also create the figure or the icon in a 111 different way entirely (for example, starting with a Swing 112 icon and creating the figure using a SwingWrapper) by overriding 113 both createBackgroundFigure() and createIcon(). However, the 114 icon editor provided by EditIconFrame and EditIconTableau 115 will only show (and allow editing) of those icon components 116 created by populating this attribute with other visible 117 attributes. 118 <p> 119 This attribute contains another attribute that is an 120 instance of EditIconTableau. This has the effect that 121 an instance of Configuration, when it attempts to open 122 an instance of this class, will use EditIconTableau, 123 which in turn uses EditIconFrame to provide an icon 124 editor. 125 126 @author Steve Neuendorffer, John Reekie, Edward A. Lee 127 @version $Id$ 128 @since Ptolemy II 2.0 129 @Pt.ProposedRating Yellow (neuendor) 130 @Pt.AcceptedRating Red (johnr) 131 @see EditIconFrame 132 @see EditIconTableau 133 @see ptolemy.actor.gui.Configuration 134 */ 135public class EditorIcon extends Attribute implements IconAttribute { 136 /** Construct an icon in the specified workspace and name. 137 * This constructor is typically used in conjunction with 138 * setContainerToBe() and createFigure() to create an icon 139 * and generate a figure without having to have write access 140 * to the workspace. 141 * If the workspace argument is null, then use the default workspace. 142 * The object is added to the directory of the workspace. 143 * @see #setContainerToBe(NamedObj) 144 * Increment the version number of the workspace. 145 * @param workspace The workspace that will list the attribute. 146 * @param name The name of this attribute. 147 * @exception IllegalActionException If the specified name contains 148 * a period. 149 */ 150 public EditorIcon(Workspace workspace, String name) 151 throws IllegalActionException { 152 super(workspace); 153 154 try { 155 setName(name); 156 157 // Create a tableau factory so that an instance 158 // of this class is opened using the EditIconTableau. 159 Attribute tableauFactory = new EditIconTableau.Factory(this, 160 "_tableauFactory"); 161 tableauFactory.setPersistent(false); 162 } catch (NameDuplicationException ex) { 163 throw new InternalErrorException(ex); 164 } 165 } 166 167 /** Create a new icon with the given name in the given container. 168 * @param container The container. 169 * @param name The name of the attribute. 170 * @exception IllegalActionException If the attribute is not of an 171 * acceptable class for the container. 172 * @exception NameDuplicationException If the name coincides with 173 * an attribute already in the container. 174 */ 175 public EditorIcon(NamedObj container, String name) 176 throws IllegalActionException, NameDuplicationException { 177 super(container, name); 178 179 // Create a tableau factory so that an instance 180 // of this class is opened using the EditIconTableau. 181 Attribute tableauFactory = new EditIconTableau.Factory(this, 182 "_tableauFactory"); 183 tableauFactory.setPersistent(false); 184 } 185 186 /////////////////////////////////////////////////////////////////// 187 //// public methods //// 188 189 /** Clone the object into the specified workspace. The new object is 190 * <i>not</i> added to the directory of that workspace (you must do this 191 * yourself if you want it there). 192 * The result is an object with no container. 193 * @param workspace The workspace for the cloned object. 194 * @exception CloneNotSupportedException Not thrown in this base class 195 * @return The new Attribute. 196 */ 197 @Override 198 public Object clone(Workspace workspace) throws CloneNotSupportedException { 199 EditorIcon newObject = (EditorIcon) super.clone(workspace); 200 newObject._containerToBe = null; 201 newObject._iconCache = null; 202 return newObject; 203 } 204 205 /** Create a new background figure. This figure is a composition of 206 * the figures of any contained visible attributes. If there are no such 207 * visible attributes, then this figure is a simple white box. 208 * If you override this method, keep in mind that this method is expected 209 * to manufacture a new figure each time, since figures are 210 * inexpensive and contain their own location and transformations. 211 * This method should never return null. 212 * @return A new figure. 213 */ 214 public Figure createBackgroundFigure() { 215 // If this icon itself contains any visible attributes, then 216 // compose their background figures to make this one. 217 CompositeFigure figure = null; 218 Iterator attributes = attributeList().iterator(); 219 220 while (attributes.hasNext()) { 221 Attribute attribute = (Attribute) attributes.next(); 222 223 // There is a level of indirection where the "subIcon" is a 224 // "visible attribute" containing an attribute named "_icon" 225 // that actually has the icon. 226 Iterator subIcons = attribute.attributeList(EditorIcon.class) 227 .iterator(); 228 229 while (subIcons.hasNext()) { 230 EditorIcon subIcon = (EditorIcon) subIcons.next(); 231 232 if (figure == null) { 233 // NOTE: This used to use a constructor that 234 // takes a "background figure" argument, which would 235 // then treat this first figure specially. This is not 236 // right since getShape() on the figure will then return 237 // only the shape of the composite, which results in the 238 // wrong selection region being highlighted. 239 figure = new CompositeFigure(); 240 } 241 242 Figure subFigure = subIcon.createBackgroundFigure(); 243 244 // Translate the figure to the location of the subfigure, 245 // if there is a location. Also, center it if necessary. 246 try { 247 // NOTE: This is inelegant, but only the subclasses 248 // have the notion of centering. 249 // FIXME: Don't use FilledShapeAttribute... promote 250 // centered capability to a base class. 251 if (attribute instanceof FilledShapeAttribute 252 && subFigure instanceof BasicFigure) { 253 boolean centeredValue = ((BooleanToken) ((FilledShapeAttribute) attribute).centered 254 .getToken()).booleanValue(); 255 256 if (centeredValue) { 257 ((BasicFigure) subFigure).setCentered(true); 258 } 259 } 260 261 Locatable location = (Locatable) attribute 262 .getAttribute("_location", Locatable.class); 263 264 if (location != null) { 265 double[] locationValue = location.getLocation(); 266 CanvasUtilities.translateTo(subFigure, locationValue[0], 267 locationValue[1]); 268 } 269 } catch (IllegalActionException e) { 270 throw new InternalErrorException(e); 271 } 272 273 figure.add(subFigure); 274 } 275 } 276 277 if (figure == null) { 278 // There are no component figures. 279 return _createDefaultBackgroundFigure(); 280 } else { 281 return figure; 282 } 283 } 284 285 /** Create a new Diva figure that visually represents this icon. 286 * The figure will be an instance of CompositeFigure with the 287 * figure returned by createBackgroundFigure() as its background. 288 * This method adds a LabelFigure to the CompositeFigure that 289 * contains the name of the container of this icon, unless the 290 * container has a parameter called "_hideName" with value true. 291 * If the container has an attribute called "_centerName" with 292 * value true, then the name is rendered 293 * in the center of the background figure, rather than above it. 294 * This method should never return null, even if the icon has 295 * not been properly initialized. 296 * @return A new CompositeFigure consisting of the background figure 297 * and a label. 298 */ 299 public Figure createFigure() { 300 Figure background = createBackgroundFigure(); 301 Rectangle2D backBounds; 302 try { 303 // Applets can throw a NPE here if there are problems getting 304 // the image. 305 backBounds = background.getBounds(); 306 } catch (Exception ex) { 307 throw new InternalErrorException(this, ex, 308 "Failed to get the bounds of the background figure \"" 309 + (background == null ? "null" : background)); 310 } 311 CompositeFigure figure = new CompositeFigure(background); 312 313 NamedObj container = (NamedObj) getContainerOrContainerToBe(); 314 315 // RelativeLocatables are drawn with a line that indicates to which object 316 // they are connected. This line is drawn by RelativeLinkFigure. 317 if (container instanceof RelativeLocatable) { 318 List<RelativeLocation> locations = container 319 .attributeList(RelativeLocation.class); 320 if (locations.size() > 0) { 321 RelativeLocation relativeLocation = locations.get(0); 322 figure.add(new RelativeLinkFigure(relativeLocation)); 323 } 324 } 325 326 // Create the label, unless this is a visible attribute, 327 // which typically carries no label. 328 // NOTE: backward compatibility problem... 329 // Old style annotations now have labels... 330 if (!_isPropertySet(container, "_hideName")) { 331 String name = container.getDisplayName(); 332 333 // Do not add a label figure if the name is null. 334 if (name != null && !name.equals("")) { 335 if (!_isPropertySet(container, "_centerName")) { 336 LabelFigure label = new LabelFigure(name, _labelFont, 1.0, 337 SwingConstants.SOUTH_WEST); 338 339 // Shift the label slightly right so it doesn't 340 // collide with ports. 341 label.translateTo(backBounds.getX() + 5, backBounds.getY()); 342 figure.add(label); 343 } else { 344 LabelFigure label = new LabelFigure(name, _labelFont, 1.0, 345 SwingConstants.CENTER); 346 label.translateTo(backBounds.getCenterX(), 347 backBounds.getCenterY()); 348 figure.add(label); 349 } 350 } 351 } 352 353 // If specified by a preference, then show parameters. 354 Token show = PtolemyPreferences.preferenceValueLocal(container, 355 "_showParameters"); 356 357 if (show instanceof StringToken) { 358 String value = ((StringToken) show).stringValue(); 359 boolean showOverriddenParameters = value 360 .equals("Overridden parameters only"); 361 boolean showAllParameters = value.equals("All"); 362 363 if (showOverriddenParameters 364 && !_isPropertySet(container, "_hideAllParameters") 365 || showAllParameters) { 366 StringBuffer parameters = new StringBuffer(); 367 Iterator settables = container.attributeList(Settable.class) 368 .iterator(); 369 370 while (settables.hasNext()) { 371 Settable settable = (Settable) settables.next(); 372 373 if (settable.getVisibility() != Settable.FULL) { 374 continue; 375 } 376 377 if (!showAllParameters 378 && !((NamedObj) settable).isOverridden()) { 379 continue; 380 } 381 382 if (!showAllParameters 383 && _isPropertySet((NamedObj) settable, "_hide")) { 384 continue; 385 } 386 387 // Skip parameters whose names begin with underscore. 388 // These are intended to be hidden. 389 if (settable.getName().startsWith("_")) { 390 continue; 391 } 392 393 String name = settable.getName(); 394 String displayName = settable.getDisplayName(); 395 parameters.append(displayName); 396 397 if (showAllParameters && !name.equals(displayName)) { 398 parameters.append(" (" + name + ")"); 399 } 400 401 parameters.append(": "); 402 parameters.append(settable.getExpression()); 403 404 if (settables.hasNext()) { 405 parameters.append("\n"); 406 } 407 } 408 409 LabelFigure label = new LabelFigure(parameters.toString(), 410 _parameterFont, 1.0, SwingConstants.NORTH_WEST); 411 412 // Shift the label slightly right and down so it doesn't 413 // collide with ports. 414 label.translateTo(backBounds.getX() + 5, 415 backBounds.getY() + backBounds.getHeight() + 5); 416 figure.add(label); 417 } 418 } 419 420 return figure; 421 } 422 423 /** Create a new Swing icon. In this base class, this icon is created 424 * from the background figure returned by createBackgroundFigure(). 425 * Note that the background figure does NOT include a label for the name. 426 * This method might be suitable, for example, for creating a small icon 427 * for use in a library. 428 * @return A new Swing Icon. 429 */ 430 public Icon createIcon() { 431 // In this class, we cache the rendered icon, since creating icons from 432 // figures is expensive. 433 if (_iconCache != null) { 434 return _iconCache; 435 } 436 437 // No cached object, so rerender the icon. 438 Figure figure = createBackgroundFigure(); 439 _iconCache = new FigureIcon(figure, 20, 15); 440 return _iconCache; 441 } 442 443 /** Return the container of this object, if there is one, or 444 * if not, the container specified by setContainerToBe(), if 445 * there is one, or if not, null. This rather specialized method is 446 * used to create an icon and generate a figure without having 447 * to have write access to the workspace. To use it, use the 448 * constructor that takes a workspace and a name, then call 449 * setContainerToBe() to indicate what the container will be. You 450 * can then call createFigure() or createBackgroundFigure(), 451 * and the appropriate figure for the container specified here 452 * will be used. Then queue a ChangeRequest that sets the 453 * container to the same specified container. Once the container 454 * has been set by calling setContainer(), then the object 455 * specified to this method is no longer relevant. 456 * 457 * @see #setContainerToBe(NamedObj) 458 * @return The container of this object, if there is one, or 459 * if not hte container specified by setContainerToBe(). 460 */ 461 public Nameable getContainerOrContainerToBe() { 462 Nameable container = getContainer(); 463 464 if (container != null) { 465 return container; 466 } else { 467 return _containerToBe; 468 } 469 } 470 471 /** Indicate that the container of this icon will eventually 472 * be the specified object. This rather specialized method is 473 * used to create an icon and generate a figure without having 474 * to have write access to the workspace. To use it, use the 475 * constructor that takes a workspace and a name, then call 476 * this method to indicate what the container will be. You 477 * can then call createFigure() or createBackgroundFigure(), 478 * and the appropriate figure for the container specified here 479 * will be used. Then queue a ChangeRequest that sets the 480 * container to the same specified container. Once the container 481 * has been set by calling setContainer(), then the object 482 * specified to this method is no longer relevant. 483 * @param container The container that will eventually be set. 484 * @see #getContainerOrContainerToBe() 485 */ 486 public void setContainerToBe(NamedObj container) { 487 _containerToBe = container; 488 } 489 490 /////////////////////////////////////////////////////////////////// 491 //// protected methods //// 492 493 /** Create a new default background figure, which is a white box. 494 * Subclasses of this class should generally override 495 * the createBackgroundFigure method instead. This method is provided 496 * so that subclasses are always able to create a default figure even if 497 * an error occurs or the subclass has not been properly initialized. 498 * @return A figure representing a rectangular white box. 499 */ 500 protected Figure _createDefaultBackgroundFigure() { 501 // NOTE: It is tempting to create a RectangleAttribute for 502 // the rectangle, but that won't work... It has to be created 503 // as a change request, and we can't get the figure until the 504 // change request is processed. This is what the code would 505 // look like: 506 507 /* 508 StringBuffer moml = new StringBuffer(); 509 moml.append("<group name=\"auto\">" + 510 "<property name=\"defaultFigure\" " + 511 "class=\"ptolemy.vergil.kernel.attributes.RectangleAttribute\">\n" + 512 "<property name=\"width\" value=\"60\"/>\n" + 513 "<property name=\"height\" value=\"40\"/>\n" + 514 "<property name=\"centered\" value=\"true\"/>\n" + 515 "<property name=\"fillColor\" value=\"{1.0, 1.0, 1.0, 1.0}\"/>\n" + 516 "</property></group>" ); 517 MoMLChangeRequest request = new MoMLChangeRequest( 518 this, this, moml.toString()); 519 requestChange(request); 520 */ 521 return new BasicRectangle(-30, -20, 60, 40, Color.white, 1); 522 } 523 524 /** Return true if the property of the specified name is set for 525 * the specified object. A property is specified if the specified 526 * object contains an attribute with the specified name and that 527 * attribute is either not a boolean-valued parameter, or it is 528 * a boolean-valued parameter with value true. 529 * @param object The object. 530 * @param name The property name. 531 * @return True if the property is set. 532 */ 533 protected boolean _isPropertySet(NamedObj object, String name) { 534 Attribute attribute = object.getAttribute(name); 535 536 if (attribute == null) { 537 return false; 538 } 539 540 if (attribute instanceof Parameter) { 541 try { 542 Token token = ((Parameter) attribute).getToken(); 543 544 if (token instanceof BooleanToken) { 545 if (!((BooleanToken) token).booleanValue()) { 546 return false; 547 } 548 } 549 } catch (IllegalActionException e) { 550 // Ignore, using default of true. 551 } 552 } 553 554 return true; 555 } 556 557 /** Recreate the figure. Call this to cause createIcon() to call 558 * createBackgroundFigure() to obtain a new figure. 559 */ 560 protected void _recreateFigure() { 561 _iconCache = null; 562 } 563 564 /////////////////////////////////////////////////////////////////// 565 //// protected variables //// 566 567 /** The container to be eventually the container for this icon. */ 568 protected NamedObj _containerToBe; 569 570 /** The cached Swing icon. */ 571 protected Icon _iconCache = null; 572 573 /////////////////////////////////////////////////////////////////// 574 //// private variables //// 575 576 /** Font for name labels. */ 577 private static Font _labelFont = new Font("SansSerif", Font.PLAIN, 12); 578 579 /** Font for parameter values. */ 580 private static Font _parameterFont = new Font("SansSerif", Font.PLAIN, 9); 581}