001/* An icon stored in XML. 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.io.Reader; 031import java.io.StringReader; 032import java.lang.reflect.Constructor; 033import java.net.URL; 034import java.util.Iterator; 035 036import diva.canvas.Figure; 037import diva.canvas.toolbox.PaintedFigure; 038import diva.canvas.toolbox.SVGParser; 039import diva.gui.toolbox.FigureIcon; 040import diva.util.java2d.PaintedList; 041import diva.util.xml.XmlDocument; 042import diva.util.xml.XmlElement; 043import diva.util.xml.XmlReader; 044import ptolemy.actor.gui.Configuration; 045import ptolemy.kernel.util.ConfigurableAttribute; 046import ptolemy.kernel.util.IllegalActionException; 047import ptolemy.kernel.util.InternalErrorException; 048import ptolemy.kernel.util.NameDuplicationException; 049import ptolemy.kernel.util.Nameable; 050import ptolemy.kernel.util.NamedObj; 051import ptolemy.kernel.util.Settable; 052import ptolemy.kernel.util.StringAttribute; 053import ptolemy.kernel.util.ValueListener; 054import ptolemy.kernel.util.Workspace; 055 056/////////////////////////////////////////////////////////////////// 057//// XMLIcon 058 059/** 060 An icon is a visual representation of an entity. Three such visual 061 representations are supported here. A background figure is returned 062 by the createBackgroundFigure() method. This figure is specified by 063 an attribute named "_iconDescription" of the container, if there is one. 064 If there is no such attribute, then a default icon is used. 065 The createFigure() method returns this same background figure, but 066 decorated with a label giving the name of the container, unless the 067 container contains a parameter named "_hideName" with value true. 068 The createIcon() method returns a Swing icon given by an attribute named 069 "_smallIconDescription", if there is one. If there is no such 070 attribute, then the icon is simply a small representation of the 071 background figure. 072 <p> 073 The XML schema used in the "_iconDescription" and "_smallIconDescription" 074 attributes is SVG (scalable vector graphics), although currently Diva 075 only supports a small subset of SVG. 076 077 @author Steve Neuendorffer, John Reekie, Contributors: Edward A. Lee, Chad Berkley (Kepler) 078 @version $Id$ 079 @since Ptolemy II 2.0 080 @Pt.ProposedRating Yellow (neuendor) 081 @Pt.AcceptedRating Red (johnr) 082 */ 083public class XMLIcon extends DynamicEditorIcon implements ValueListener { 084 /** Construct an icon in the specified workspace and name. 085 * This constructor is typically used in conjunction with 086 * setContainerToBe() and createFigure() to create an icon 087 * and generate a figure without having to have write access 088 * to the workspace. 089 * If the workspace argument is null, then use the default workspace. 090 * The object is added to the directory of the workspace. 091 * @see #setContainerToBe(NamedObj) 092 * Increment the version number of the workspace. 093 * @param workspace The workspace that will list the attribute. 094 * @param name The name of this attribute. 095 * @exception IllegalActionException If the specified name contains 096 * a period. 097 */ 098 public XMLIcon(Workspace workspace, String name) 099 throws IllegalActionException { 100 super(workspace, name); 101 102 try { 103 setName(name); 104 } catch (NameDuplicationException ex) { 105 throw new InternalErrorException(ex); 106 } 107 } 108 109 /** Create a new icon with the given name in the given container. 110 * By default, the icon contains no graphic objects. 111 * @param container The container for this attribute. 112 * @param name The name of this attribute. 113 * @exception IllegalActionException If thrown by the parent 114 * class or while setting an attribute. 115 * @exception NameDuplicationException If the name coincides with 116 * an attribute already in the container. 117 */ 118 public XMLIcon(NamedObj container, String name) 119 throws NameDuplicationException, IllegalActionException { 120 super(container, name); 121 _paintedList = null; 122 _description = null; 123 } 124 125 /** 126 * Instantiate an XMLIcon in a NamedObj. 127 * 128 * <p>This method looks for the _alternateXMLIcon attribute in the 129 * configuration. If it is found, it returns an XMLIcon of the 130 * class found there, if not, it returns an instance of this class. 131 * 132 * @param container The container for this attribute. 133 * @param name The name of this attribute. 134 * @return an instance of the XMLIcon class. 135 * @exception NameDuplicationException If an object with 136 * that name already exists in the container. 137 * @exception IllegalActionException If the specified name contains 138 * a period. 139 */ 140 public static XMLIcon getXMLIcon(NamedObj container, String name) 141 throws NameDuplicationException, IllegalActionException { 142 try { 143 Class XMLIconClass = _getAlternateXMLIcon(); 144 if (XMLIconClass == null) { 145 return new XMLIcon(container, name); 146 } 147 148 Class[] argsClass = new Class[] { NamedObj.class, String.class }; 149 Constructor alternateXMLIconConstructor = XMLIconClass 150 .getConstructor(argsClass); 151 XMLIcon xmlIcon = (XMLIcon) alternateXMLIconConstructor 152 .newInstance(new Object[] { container, name }); 153 return xmlIcon; 154 } catch (Exception ex) { 155 System.out.println( 156 "Warning: could not instantiate alternate XMLIcon class. " 157 + "Using default XMLIcon. : " + ex.getMessage()); 158 ex.printStackTrace(); 159 return new XMLIcon(container, name); 160 } 161 } 162 163 /** 164 * Instantiate an XMLIcon in a Workspace. 165 * 166 * <p>This method looks for the _alternateXMLIcon attribute in the 167 * configuration. If it is found, it returns an XMLIcon of the 168 * class found there, if not, it returns an instance of this class. 169 * 170 * @param workspace The workspace that will list the attribute. 171 * @param name The name of this attribute. 172 * @return an instance of the XMLIcon class. 173 * @exception NameDuplicationException If an object with 174 * that name already exists in the container. 175 * @exception IllegalActionException If the specified name contains 176 * a period. 177 */ 178 public static XMLIcon getXMLIcon(Workspace workspace, String name) 179 throws NameDuplicationException, IllegalActionException { 180 try { 181 Class XMLIconClass = _getAlternateXMLIcon(); 182 if (XMLIconClass == null) { 183 return new XMLIcon(workspace, name); 184 } 185 // Get the new object and return it 186 Class[] argsClass = new Class[] { 187 ptolemy.kernel.util.Workspace.class, String.class }; 188 Constructor alternateXMLIconConstructor = XMLIconClass 189 .getConstructor(argsClass); 190 XMLIcon xmlIcon = (XMLIcon) alternateXMLIconConstructor 191 .newInstance(new Object[] { workspace, name }); 192 return xmlIcon; 193 } catch (Exception ex) { 194 System.out.println( 195 "Warning: could not instantiate alternate XMLIcon class. " 196 + "Using default XMLIcon. : " + ex.getMessage()); 197 ex.printStackTrace(); 198 return new XMLIcon(workspace, name); 199 } 200 } 201 202 /////////////////////////////////////////////////////////////////// 203 //// public methods //// 204 205 /** Clone the object into the specified workspace. The new object is 206 * <i>not</i> added to the directory of that workspace (you must do this 207 * yourself if you want it there). 208 * The result is an object with no container. 209 * @param workspace The workspace for the cloned object. 210 * @exception CloneNotSupportedException Not thrown in this base class 211 * @return The new Attribute. 212 */ 213 @Override 214 public Object clone(Workspace workspace) throws CloneNotSupportedException { 215 XMLIcon newObject = (XMLIcon) super.clone(workspace); 216 newObject._paintedList = null; 217 newObject._description = null; 218 newObject._smallIconDescription = null; 219 return newObject; 220 } 221 222 /** Create a background figure based on this icon. The background figure 223 * will be painted with each graphic element that this icon contains. 224 * @return A figure for this icon. 225 */ 226 @Override 227 public Figure createBackgroundFigure() { 228 // Get the description. 229 NamedObj container = (NamedObj) getContainerOrContainerToBe(); 230 ConfigurableAttribute description = (ConfigurableAttribute) container 231 .getAttribute("_iconDescription"); 232 233 // If the description has changed... 234 if (_description != description) { 235 if (_description != null) { 236 // Remove this as a listener if there 237 // was a previous description. 238 _description.removeValueListener(this); 239 } 240 241 // update the description. 242 _description = description; 243 244 if (_description != null) { 245 // Listen for changes in value to the icon description. 246 _description.addValueListener(this); 247 } 248 249 // clear the caches 250 _recreateFigure(); 251 } 252 253 // Update the painted list. 254 _updatePaintedList(); 255 256 // If the paintedList is still null, then return the default figure. 257 if (_paintedList == null) { 258 return _createDefaultBackgroundFigure(); 259 } 260 261 return new PaintedFigure(_paintedList); 262 } 263 264 /** Create a new Swing icon. This class looks for an attribute 265 * called "_smallIconDescription", and if it exists, uses it to 266 * create the icon. If it does not exist, then it simply creates 267 * a small version of the background figure returned by 268 * createBackgroundFigure(). 269 * @return A new Swing Icon. 270 */ 271 @Override 272 public javax.swing.Icon createIcon() { 273 // In this class, we cache the rendered icon, since creating icons from 274 // figures is expensive. 275 if (_iconCache != null) { 276 return _iconCache; 277 } 278 279 // No cached object, so rerender the icon. 280 // Get the description. 281 NamedObj container = (NamedObj) getContainerOrContainerToBe(); 282 ConfigurableAttribute description = (ConfigurableAttribute) container 283 .getAttribute("_smallIconDescription"); 284 285 // If there is no separate small icon description, return 286 // a scaled version of the background figure, as done by the base 287 // class. 288 if (description == null) { 289 return super.createIcon(); 290 } 291 292 // If the description has changed... 293 if (_smallIconDescription != description) { 294 if (_smallIconDescription != null) { 295 // Remove this as a listener if there 296 // was a previous description. 297 _smallIconDescription.removeValueListener(this); 298 } 299 300 _smallIconDescription = description; 301 302 // Listen for changes in value to the icon description. 303 _smallIconDescription.addValueListener(this); 304 } 305 306 // clear the caches 307 _recreateFigure(); 308 309 // Update the painted list, if necessary 310 Figure figure; 311 312 try { 313 String text = _smallIconDescription.value(); 314 Reader in = new StringReader(text); 315 316 // NOTE: Do we need a base here? 317 XmlDocument document = new XmlDocument((URL) null); 318 XmlReader reader = new XmlReader(); 319 reader.parse(document, in); 320 321 XmlElement root = document.getRoot(); 322 PaintedList paintedList = SVGParser.createPaintedList(root); 323 figure = new PaintedFigure(paintedList); 324 } catch (Exception ex) { 325 return super.createIcon(); 326 } 327 328 // NOTE: The size is hardwired here. Should it be? 329 // The second to last argument specifies the border. 330 // The last says to turn anti-aliasing on. 331 _iconCache = new FigureIcon(figure, 20, 15, 0, true); 332 return _iconCache; 333 } 334 335 /** Return the painted list contained by this icon. 336 * This is used by the icon editor. 337 * @return The painted list contained by this icon. 338 */ 339 public PaintedList paintedList() { 340 if (_paintedList == null) { 341 _updatePaintedList(); 342 } 343 344 return _paintedList; 345 } 346 347 /** Return a string representing this Icon. 348 */ 349 @Override 350 public String toString() { 351 String str = super.toString() + "("; 352 353 // FIXME: Something is missing here. 354 return str + ")"; 355 } 356 357 /** React to the fact that the value of an attribute named 358 * "_iconDescription" contained by the same container has changed 359 * value by redrawing the figure. 360 * @param settable The object that has changed value. 361 */ 362 @Override 363 public void valueChanged(Settable settable) { 364 String name = ((Nameable) settable).getName(); 365 366 if (name.equals("_iconDescription") 367 || name.equals("_smallIconDescription")) { 368 _recreateFigure(); 369 } 370 } 371 372 /////////////////////////////////////////////////////////////////// 373 //// protected methods //// 374 375 /** Return a description of the object. Lines are indented according to 376 * to the level argument using the protected method _getIndentPrefix(). 377 * Zero, one or two brackets can be specified to surround the returned 378 * description. If one is specified it is the the leading bracket. 379 * This is used by derived classes that will append to the description. 380 * Those derived classes are responsible for the closing bracket. 381 * An argument other than 0, 1, or 2 is taken to be equivalent to 0. 382 * This method is read-synchronized on the workspace. 383 * @param detail The level of detail. 384 * @param indent The amount of indenting. 385 * @param bracket The number of surrounding brackets (0, 1, or 2). 386 * @return A description of the object. 387 * @exception IllegalActionException 388 */ 389 @Override 390 protected String _description(int detail, int indent, int bracket) 391 throws IllegalActionException { 392 String result = ""; 393 394 if (bracket == 0) { 395 result += super._description(detail, indent, 0); 396 } else { 397 result += super._description(detail, indent, 1); 398 } 399 400 result += " graphics {\n"; 401 result += "FIXME"; 402 result += _getIndentPrefix(indent) + "}"; 403 404 if (bracket == 2) { 405 result += "}"; 406 } 407 408 return result; 409 } 410 411 /** Recreate the figure. Call to cause createIcon() to call 412 * createBackgroundFigure() to obtain a new figure. 413 */ 414 @Override 415 protected void _recreateFigure() { 416 super._recreateFigure(); 417 _paintedList = null; 418 } 419 420 /////////////////////////////////////////////////////////////////// 421 //// private methods //// 422 423 /** 424 * Check to see if there is an _alternateXMLIcon attribute in the 425 * configuration. This attribute should name a class that 426 * is an alternative to XMLIcon. 427 * If the attribute is present, return the class, if not, return null. 428 */ 429 private static Class _getAlternateXMLIcon() throws Exception { 430 // Applets might not have a configuration, so we check to see 431 // if there is a next configuration. 432 Iterator configurations = Configuration.configurations().iterator(); 433 // FIXME: why does this always skip the first configuration? 434 // Instead, we should check each configuration as we go. 435 if (configurations.hasNext()) { 436 Configuration config = (Configuration) configurations.next(); 437 String alternateXMLIconClassName = null; 438 if (config != null) { 439 // If _alternateXMLIcon is set in the config, use that 440 // class as the XMLIcon instead of the default 441 442 StringAttribute alternateXMLIconAttribute = (StringAttribute) config 443 .getAttribute("_alternateXMLIcon"); 444 if (alternateXMLIconAttribute != null) { 445 alternateXMLIconClassName = alternateXMLIconAttribute 446 .getExpression(); 447 } 448 449 if (alternateXMLIconClassName == null) { //attribute was not found 450 return null; 451 } else { 452 Class alternateXMLIconClass = Class 453 .forName(alternateXMLIconClassName); 454 return alternateXMLIconClass; 455 } 456 } 457 } 458 return null; 459 } 460 461 /** Update the painted list of the icon based on the SVG data 462 * in the associated "_iconDescription" parameter, if there is one. 463 */ 464 private void _updatePaintedList() { 465 // create a new list because the PaintedList we had before 466 // was used to create some PaintedFigures already. 467 // FIXME: test for 'svg' processing instruction 468 if (_description == null) { 469 _paintedList = null; 470 return; 471 } 472 473 try { 474 String text = _description.value(); 475 Reader in = new StringReader(text); 476 477 // FIXME: Do we need a base here? 478 XmlDocument document = new XmlDocument((URL) null); 479 XmlReader reader = new XmlReader(); 480 reader.parse(document, in); 481 482 XmlElement root = document.getRoot(); 483 484 _paintedList = SVGParser.createPaintedList(root); 485 } catch (Exception ex) { 486 // If we fail, then we'll just get a default figure. 487 _paintedList = null; 488 } 489 } 490 491 /////////////////////////////////////////////////////////////////// 492 //// private members //// 493 // The list of painted objects contained in this icon. 494 private PaintedList _paintedList; 495 496 // The description of this icon in XML. 497 private ConfigurableAttribute _description; 498 499 // The description of the small version of the icon in XML. 500 private ConfigurableAttribute _smallIconDescription; 501}