001/* 002 * Copyright (c) 1999-2010 The Regents of the University of California. 003 * All rights reserved. 004 * 005 * '$Author: crawl $' 006 * '$Date: 2015-08-24 22:44:14 +0000 (Mon, 24 Aug 2015) $' 007 * '$Revision: 33630 $' 008 * 009 * Permission is hereby granted, without written agreement and without 010 * license or royalty fees, to use, copy, modify, and distribute this 011 * software and its documentation for any purpose, provided that the above 012 * copyright notice and the following two paragraphs appear in all copies 013 * of this software. 014 * 015 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 016 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 017 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 018 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 019 * SUCH DAMAGE. 020 * 021 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 022 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 023 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 024 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 025 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 026 * ENHANCEMENTS, OR MODIFICATIONS. 027 * 028 */ 029 030package org.kepler.gui; 031 032import java.awt.Image; 033import java.awt.Toolkit; 034import java.io.IOException; 035import java.io.InputStream; 036import java.io.InputStreamReader; 037import java.io.Reader; 038import java.io.StringReader; 039import java.net.URL; 040import java.util.Iterator; 041import java.util.List; 042 043import javax.swing.Icon; 044 045import org.apache.batik.dom.svg.SAXSVGDocumentFactory; 046import org.apache.batik.dom.svg.SVGDocumentFactory; 047import org.apache.batik.util.XMLResourceDescriptor; 048import org.apache.commons.logging.Log; 049import org.apache.commons.logging.LogFactory; 050import org.kepler.icon.ComponentEntityConfig; 051import org.w3c.dom.svg.SVGDocument; 052 053import diva.canvas.Figure; 054import diva.canvas.toolbox.ImageFigure; 055import diva.canvas.toolbox.PaintedFigure; 056import diva.canvas.toolbox.SVGParser; 057import diva.gui.toolbox.FigureIcon; 058import diva.util.java2d.PaintedList; 059import diva.util.java2d.svg.SVGPaintedObject; 060import diva.util.java2d.svg.SVGRenderingListener; 061import diva.util.xml.XmlDocument; 062import diva.util.xml.XmlElement; 063import diva.util.xml.XmlReader; 064import ptolemy.actor.Director; 065import ptolemy.kernel.util.Attribute; 066import ptolemy.kernel.util.ChangeRequest; 067import ptolemy.kernel.util.ConfigurableAttribute; 068import ptolemy.kernel.util.IllegalActionException; 069import ptolemy.kernel.util.NameDuplicationException; 070import ptolemy.kernel.util.Nameable; 071import ptolemy.kernel.util.NamedObj; 072import ptolemy.kernel.util.Settable; 073import ptolemy.kernel.util.ValueListener; 074import ptolemy.kernel.util.Workspace; 075import ptolemy.util.MessageHandler; 076import ptolemy.vergil.icon.XMLIcon; 077import util.EmptyChangeRequest; 078 079////////////////////////////////////////////////////////////////////////// 080//// XMLIcon 081////////////////////////////////////////////////////////////////////////// 082 083/** 084 * An icon is a visual representation of an entity. Three such visual 085 * representations are supported here. A background figure is returned by the 086 * createBackgroundFigure() method. This figure is specified by an attribute 087 * named "_iconDescription" of the container, if there is one. If there is no 088 * such attribute, then a default icon is used. The createFigure() method 089 * returns this same background figure, but decorated with a label giving the 090 * name of the container, unless the container contains a parameter named 091 * "_hideName" with value true. The createIcon() method returns a Swing icon 092 * given by an attribute named "_smallIconDescription", if there is one. If 093 * there is no such attribute, then the icon is simply a small representation of 094 * the background figure. 095 * <p> 096 * The XML schema used in the "_iconDescription" and "_smallIconDescription" 097 * attributes is SVG (scalable vector graphics), although currently Diva only 098 * supports a small subset of SVG. 099 * 100 * @author Chad Berkley, based on XMLIcon by Steve Neuendorffer, John Reekie, Contributor: Edward A. Lee 101 * @version $Id: KeplerXMLIcon.java 33630 2015-08-24 22:44:14Z crawl $ 102 * @since Ptolemy II 2.0 103 * @Pt.ProposedRating Yellow (neuendor) 104 * @Pt.AcceptedRating Red (johnr) 105 */ 106 107public class KeplerXMLIcon extends XMLIcon implements ValueListener { 108 109 // parser used to parse XML for SVG rendering 110 private static final String _DEFAULT_PARSER = "org.apache.xerces.parsers.SAXParser"; 111 112 // base uri to pass to createSVGDocument() method of SVGDocumentFactory 113 private static final String _SVG_BASE_URI = "http://www.ecoinformatics.org/"; 114 115 private static Log log; 116 private static boolean isDebugging; 117 118 static { 119 log = LogFactory.getLog("SVG." + XMLIcon.class.getName()); 120 isDebugging = log.isDebugEnabled(); 121 122 String parser = XMLResourceDescriptor.getXMLParserClassName(); 123 if (parser == null || parser.trim().equals("")) { 124 parser = _DEFAULT_PARSER; 125 } 126 _df = new SAXSVGDocumentFactory(parser); 127 } 128 129 /** 130 * Construct an icon in the specified workspace and name. This constructor 131 * is typically used in conjunction with setContainerToBe() and 132 * createFigure() to create an icon and generate a figure without having to 133 * have write access to the workspace. If the workspace argument is null, 134 * then use the default workspace. The object is added to the directory of 135 * the workspace. 136 * 137 * @see #setContainerToBe(NamedObj) Increment the version number of the 138 * workspace. 139 * @param workspace 140 * The workspace that will list the attribute. 141 * @param name 142 * String 143 * @throws IllegalActionException 144 * If the specified name contains a period. 145 */ 146 public KeplerXMLIcon(Workspace workspace, String name) 147 throws IllegalActionException { 148 149 super(workspace, name); 150 } 151 152 /** 153 * Create a new icon with the given name in the given container. By default, 154 * the icon contains no graphic objects. 155 * 156 * @param container 157 * The container for this attribute. 158 * @param name 159 * The name of this attribute. 160 * @throws NameDuplicationException 161 * @throws IllegalActionException 162 */ 163 public KeplerXMLIcon(NamedObj container, String name) 164 throws NameDuplicationException, IllegalActionException { 165 166 super(container, name); 167 } 168 169 // ///////////////////////////////////////////////////////////////// 170 // // public methods //// 171 172 /** 173 * Create a background Figure based on this icon. 174 * 175 * Looks for attributes in the following order, stopping if a match is 176 * found: 177 * 178 * 1) "_svgIcon", which contains a pointer (typically a classpath-relative 179 * file path). If it exists, uses it to create the background Figure 180 * 181 * 2) "_iconDescription", which contains an xml simple-svg description. If 182 * it exists, uses it to create the icon, using the simple Diva rendering 183 * system 184 * 185 * If no match is found, it simply defers to the base class. 186 * 187 * @return A figure for this icon. 188 */ 189 @Override 190 public Figure createBackgroundFigure() { 191 192 // Get the container. 193 NamedObj container = (NamedObj) getContainerOrContainerToBe(); 194 195 // do not use batik to render the icons for non-director attributes. 196 // see http://bugzilla.ecoinformatics.org/show_bug.cgi?id=5266 197 if(container instanceof Attribute && !(container instanceof Director)) { 198 isBatikRendering = false; 199 } 200 201 boolean iconChanged = false; 202 203 if (isBatikRendering) { 204 if (isDebugging) { 205 log 206 .debug("*** createBackgroundFigure() calling _batikCreateBackgroundFigure(" 207 + container.getName() + ")\n "); 208 } 209 210 if (!lsidAssignmentDone) { 211 _doLSIDIconAssignment(container); 212 // this flag is to determine whether the _doLSIDIconAssignment() 213 // method has been called from within the 214 // createBackgroundFigure() method. 215 lsidAssignmentDone = true; 216 } 217 iconChanged = _batikCreateBackgroundFigure(container); 218 219 // If both _svgIconAttrib and _bgFigure are null after calling 220 // _batikCreateBackgroundFigure(), this means that the actor has no 221 // batik-style SVG icon assigned. Since all actors are assigned with 222 // a 223 // batik icon - even if it's just the default blank one - this means 224 // we must be dealing with an annotation or shape actor etc - so we 225 // should not generate a default icon, since this would overwrite 226 // the 227 // desired textual or shape-drawing display. Therefore, simply call: 228 // _divaCreateBackgroundFigure() to handle this icon 229 if (_batikSVGIconAttrib == null && _bgFigure == null) { 230 if (isDebugging) { 231 log 232 .info("\n*** could not assign a Batik SVG icon - therefore rendering " 233 + "old-style Diva SVG icon for " 234 + container.getName() + "\n "); 235 } 236 iconChanged = _divaCreateBackgroundFigure(container); 237 } 238 } else { 239 if (isDebugging) { 240 log 241 .debug("\n*** createBackgroundFigure() calling _divaCreateBackgroundFigure(" 242 + container.getName() + ")\n "); 243 } 244 iconChanged = _divaCreateBackgroundFigure(container); 245 } 246 247 if (iconChanged) { 248 // clear the caches 249 _recreateFigure(); 250 251 // Update the painted list. 252 _updatePaintedList(); 253 } 254 255 // PERMUTATIONS: 256 // (new icon) (cached icon) 257 // _paintedList _bgFigure iconChanged Action 258 // ------------ --------- ------------- ----------------------- 259 // null null true/false return *default* icon 260 // null exists true/false return cached Icon 261 // exists null true/false create & return new Icon 262 // exists exists true create & return new Icon 263 // exists exists false return cached Icon 264 // 265 // There are much cooler ways to do this, but the 266 // following method was used for clarity... 267 // 268 boolean newIconExists = (_paintedList != null); 269 boolean cachedIconExists = (_bgFigure != null); 270 271 if (isDebugging) { 272 log.debug("\n*** createBackgroundFigure() (" + container.getName() 273 + "):" + "\n newIconExists = " + newIconExists 274 + "\n cachedIconExists = " + cachedIconExists 275 + "\n iconChanged = " + iconChanged); 276 } 277 278 if (!newIconExists && !cachedIconExists) { 279 _bgFigure = _getDefaultBackgroundFigure(); 280 281 } else if (!newIconExists && cachedIconExists) { 282 283 // do nothing - cached icon (_bgFigure) will be returned as is 284 285 } else if (newIconExists && !cachedIconExists) { 286 287 _bgFigure = new PaintedFigure(_paintedList); 288 289 } else if (iconChanged) { 290 291 _bgFigure = new PaintedFigure(_paintedList); 292 293 } else { 294 295 // do nothing - cached icon (_bgFigure) will be returned as is 296 } 297 return _bgFigure; 298 } 299 300 /** 301 * Create a new Swing icon to use as a thumbnail in the Actor Library. 302 * 303 * Looks for attributes in the following order, stopping if a match is 304 * found: 305 * 306 * 1) if SVG_BATIK_RENDERING enabled - looks for "_thumbnailRasterIcon", 307 * which contains a pointer (typically a classpath-relative file path). If 308 * it exists, uses it to create the thumbnail icon. If (a) it doesn't exist, 309 * or (b) if rendering method is SVG_DIVA_RENDERING, proceed to next step, 310 * since (a) we're dealing with an annotation or shape actor etc, or (b) we 311 * don't want to render the new-style thumbnail and the old-style actor 312 * icons. 313 * 314 * 2) "_smallIconDescription", which contains an xml simple-svg description. 315 * If it exists, uses it to create the icon, using the simple Diva rendering 316 * system 317 * 318 * If no match is found, a default image is used, as follows: 319 * 320 * 3) looks for a cached actor icon (_bgFigure). 321 * 322 * 4) If no cached one is available, it creates a scaled version of the 323 * background figure by calling _divaCreateBackgroundFigure(), using the 324 * old-style Diva svg rendering to render the xml simple-svg description in 325 * the "_iconDescription" attribute, if it exists. Uses old diva rendering 326 * because batik is very memory intensive, and is not required for this 327 * task. 328 * 329 * 5) If all else fails, it simply defers to the base class. 330 * 331 * @return A Swing Icon. 332 */ 333 @Override 334 public Icon createIcon() { 335 336 // In this class, we cache the rendered icon, since creating icons from 337 // figures is expensive. 338 if (_iconCache != null) { 339 return _iconCache; 340 } 341 342 // We know that at this point, 343 // _iconCache == null 344 345 NamedObj container = (NamedObj) getContainerOrContainerToBe(); 346 347 // do not use batik to render the icons for non-director attributes. 348 // see http://bugzilla.ecoinformatics.org/show_bug.cgi?id=5266 349 if(container instanceof Attribute && !(container instanceof Director)) { 350 isBatikRendering = false; 351 } 352 353 Figure thumbFig = null; 354 355 // 1) if SVG_BATIK_RENDERING enabled... 356 if (isBatikRendering) { 357 358 // ...looks for "_thumbnailRasterIcon", which contains a pointer 359 // (typically a classpath-relative file path). 360 ConfigurableAttribute rasterThumbAtt = null; 361 try { 362 rasterThumbAtt = (ConfigurableAttribute) container 363 .getAttribute(ComponentEntityConfig.RASTER_THUMB_ATTRIB_NAME); 364 } catch (Exception ex2) { 365 if (isDebugging) { 366 log.warn(container.getName() 367 + ": exception getting rasterThumbAtt attribute: " 368 + ex2.getMessage()); 369 } 370 rasterThumbAtt = null; 371 } 372 if (isDebugging) { 373 log.debug("createIcon(): " + container.getClass().getName() 374 + " - just got rasterThumbAtt=" + rasterThumbAtt); 375 } 376 // If it exists, uses it to create the thumbnail icon. 377 if (rasterThumbAtt != null) { 378 379 // If the description has changed, update _divaThumbAttrib 380 // and listeners 381 if (_rasterThumbAttrib != rasterThumbAtt) { 382 if (_rasterThumbAttrib != null) { 383 // Remove this as a listener if there 384 // was a previous description. 385 _rasterThumbAttrib.removeValueListener(this); 386 } 387 388 _rasterThumbAttrib = rasterThumbAtt; 389 390 // Listen for changes in value to the icon description. 391 _rasterThumbAttrib.addValueListener(this); 392 } 393 394 String thumbPath = rasterThumbAtt.getExpression(); 395 if (thumbPath == null || thumbPath.trim().length() < 1) { 396 thumbFig = null; 397 } else { 398 if (isDebugging) { 399 log 400 .debug("createIcon(): SVG_BATIK_RENDERING and rasterThumbAtt=" 401 + thumbPath.trim()); 402 } 403 try { 404 URL url = getClass().getResource(thumbPath.trim()); 405 if (url == null) { 406 if (isDebugging) { 407 log.warn("\n ERROR - createIcon(): " 408 + container.getClass().getName() 409 + " : url==null for thumb path:\n" 410 + thumbPath.trim()); 411 } 412 thumbFig = null; 413 } else { 414 if (isDebugging) { 415 log.debug(":::: " 416 + container.getClass().getName() 417 + " - got thumbPath.trim() = " 418 + thumbPath.trim()); 419 } 420 Toolkit tk = Toolkit.getDefaultToolkit(); 421 Image thumbImg = tk.getImage(url); 422 thumbFig = new ImageFigure(thumbImg); 423 } 424 } catch (Exception ex) { 425 if (isDebugging) { 426 log 427 .warn("createIcon(): " 428 + container.getClass().getName() 429 + "\n exception getting thumbnail icon. Path = " 430 + thumbPath.trim() 431 + "\n exception = " + ex); 432 } 433 thumbFig = null; 434 } 435 } 436 } 437 } 438 // If(a)it doesn't 439 // exist, or (b) if rendering method is SVG_DIVA_RENDERING, proceed to 440 // next step, since (a) we're dealing with an annotation or shape actor 441 // etc, or (b) we don't want to render the new-style thumbnail and the 442 // old-style actor icons.... 443 444 if (thumbFig == null) { 445 // 2) "_smallIconDescription", which contains an xml simple-svg 446 // description. If it exists, uses it to create the icon, using the 447 // simple Diva rendering system 448 ConfigurableAttribute divaThumbAtt = null; 449 try { 450 divaThumbAtt = (ConfigurableAttribute) container 451 .getAttribute(OLD_SVG_THUMB_ATTNAME); 452 } catch (Exception ex1) { 453 divaThumbAtt = null; 454 } 455 456 if (divaThumbAtt != null) { 457 458 // If the description has changed, update _divaThumbAttrib 459 // and listeners 460 if (_divaThumbAttrib != divaThumbAtt) { 461 if (_divaThumbAttrib != null) { 462 // Remove this as a listener if there 463 // was a previous description. 464 _divaThumbAttrib.removeValueListener(this); 465 } 466 467 _divaThumbAttrib = divaThumbAtt; 468 469 // Listen for changes in value to the icon description. 470 _divaThumbAttrib.addValueListener(this); 471 } 472 473 String divaThumbPath = null; 474 try { 475 divaThumbPath = divaThumbAtt.value(); 476 } catch (IOException ex3) { 477 divaThumbPath = null; 478 } 479 if (divaThumbPath == null || divaThumbPath.trim().length() < 1) { 480 thumbFig = null; 481 } else { 482 // clear the caches 483 _recreateFigure(); 484 485 PaintedList paintedList = null; 486 try { 487 paintedList = _divaCreatePaintedList(divaThumbPath 488 .trim()); 489 thumbFig = new PaintedFigure(paintedList); 490 } catch (Exception ex4) { 491 thumbFig = null; 492 } 493 } 494 } 495 } 496 497 // 3) looks for a cached actor icon (_bgFigure). 498 if (thumbFig == null && _bgFigure != null) { 499 thumbFig = _bgFigure; 500 } 501 502 // 4) If no cached one is available, it creates a scaled version of the 503 // background figure by calling _divaCreateBackgroundFigure(), using 504 // old-style Diva svg rendering to render the simple-svg description 505 // in the (*large*) "_iconDescription" attribute, if it exists. Uses 506 // diva because batik is very memory intensive, and is not needed 507 // for this simple svg. 508 if (thumbFig == null) { 509 if (isDebugging) { 510 log.debug("getIcon() : " + container.getClass().getName() 511 + " - doing thumbFig = createBackgroundFigure()"); 512 } 513 thumbFig = createBackgroundFigure(); 514 } 515 516 // 5) If all else fails, it simply defers to the base class. 517 if (thumbFig == null) { 518 return super.createIcon(); 519 } 520 521 // The last argument says to turn anti-aliasing on. 522 if (isBatikRendering) { 523 _iconCache = new FigureIcon(thumbFig, 16, 16, 0, true); 524 } else { 525 // NOTE: The size is hardwired here. Should it be? 526 // The second to last argument specifies the border. 527 _iconCache = new FigureIcon(thumbFig, 20, 15, 0, true); 528 } 529 return _iconCache; 530 } 531 532 /** 533 * Return a string representing this Icon. 534 * 535 * @return String 536 */ 537 @Override 538 public String toString() { 539 String str = super.toString() + "("; 540 541 str += (_svgXML != null) ? _svgXML : ""; 542 return str + ")"; 543 } 544 545 /** 546 * React to the fact that the value of an attribute named "_iconDescription" 547 * contained by the same container has changed value by redrawing the 548 * figure. 549 * 550 * @param settable 551 * The object that has changed value. 552 */ 553 @Override 554 public void valueChanged(Settable settable) { 555 556 String name = ((Nameable) settable).getName(); 557 558 if (name.equals(ComponentEntityConfig.SVG_ICON_ATTRIB_NAME) 559 || name.equals(OLD_SVG_ICON_ATTNAME) 560 || name.equals(OLD_SVG_THUMB_ATTNAME) 561 || name.equals(ComponentEntityConfig.SVG_ICON_ATTRIB_NAME) 562 || name.equals(ComponentEntityConfig.RASTER_THUMB_ATTRIB_NAME)) { 563 564 _recreateFigure(); 565 } 566 } 567 568 // ///////////////////////////////////////////////////////////////// 569 // // private methods //// 570 // ///////////////////////////////////////////////////////////////// 571 572 private void _doLSIDIconAssignment(NamedObj container) { 573 if (isBatikRendering) { 574 try { 575 boolean success = ComponentEntityConfig 576 .tryToAssignIconByLSID((NamedObj) getContainerOrContainerToBe()); 577 if (!success) { 578 throw new Exception("Icon was not assigned by LSID"); 579 } 580 } catch (Exception ex1) { 581 log.debug(ex1.getMessage()); 582 } 583 584 } 585 } 586 587 /** 588 * CALLED ONLY IF svgRenderingMethod is SVG_BATIK_RENDERING Looks for batik 589 * svg icon attribute (@see SVG_ICON_ATTRIB_NAME), and if it exists, and has 590 * changed from previous value (which will be the case if this is the first 591 * call to this method), reads the SVG icon file and puts its String 592 * contents in the global <code>_svgXML</code> variable and returns 593 * <code>true</code>. 594 * 595 * @param container 596 * NamedObj - the container or container-to-be (typically the 597 * actor) 598 * @return boolean true if the attribute exists and has changed (which is 599 * also the case if this is the first call to this method), false 600 * otherwise 601 */ 602 private boolean _batikCreateBackgroundFigure(NamedObj container) { 603 604 // get the SVG_ICON_ATTRIB_NAME attribute 605 ConfigurableAttribute svgIconAtt = null; 606 try { 607 svgIconAtt = (ConfigurableAttribute) container 608 .getAttribute(ComponentEntityConfig.SVG_ICON_ATTRIB_NAME); 609 } catch (Exception ex) { 610 ex.printStackTrace(); 611 if (isDebugging) { 612 log.warn("_batikCreateBackgroundFigure(" 613 + container.getClass().getName() 614 + ") : exception getting svgIcon attribute: " 615 + ex.getMessage() + "\n\n"); 616 } 617 svgIconAtt = null; 618 } 619 if (isDebugging && svgIconAtt != null) { 620 log.warn("_batikCreateBackgroundFigure(" 621 + container.getClass().getName() 622 + ": GOT svgIcon attribute: " + svgIconAtt.getExpression() 623 + "\n\n"); 624 } 625 626 // if svgIconAttrib not null and has changed, get icon svg file contents 627 if (svgIconAtt != null && svgIconAtt != _batikSVGIconAttrib 628 && svgIconAtt.getExpression().trim().length() > 0) { 629 630 try { 631 _svgXML = readResourceAsString(svgIconAtt.getExpression()); 632 } catch (Exception ex) { 633 // if we can't find/read icon, default one will be used 634 if (isDebugging) { 635 log.warn(container.getName() 636 + ": exception getting _svgXML from path (" 637 + svgIconAtt.getExpression() + ") - " 638 + ex.getMessage() + "\n\n"); 639 } 640 } 641 642 if (_batikSVGIconAttrib != null) { 643 // Remove this as a listener if there 644 // was a previous description. 645 _batikSVGIconAttrib.removeValueListener(this); 646 } 647 648 // update the global _svgIconAttrib variable. 649 _batikSVGIconAttrib = svgIconAtt; 650 651 if (_batikSVGIconAttrib != null) { 652 // Listen for changes in value to the icon description. 653 _batikSVGIconAttrib.addValueListener(this); 654 } 655 return true; 656 } 657 return false; 658 } 659 660 /** 661 * CALLED ONLY IF svgRenderingMethod is *not* SVG_BATIK_RENDERING Looks for 662 * old-style diva svg icon attribute ("_iconDescription"), and if it exists, 663 * and has changed from previous value (which will be the case if this is 664 * the first call to this method), puts its String contents in the global 665 * <code>_svgXML</code> variable and returns <code>true</code>. 666 * 667 * @param container 668 * NamedObj - the container or container-to-be (typically the 669 * actor) 670 * @return boolean true if the attribute exists and has changed (which is 671 * also the case if this is the first call to this method), false 672 * otherwise 673 */ 674 private boolean _divaCreateBackgroundFigure(NamedObj container) { 675 676 // get the "_iconDescription" attribute 677 ConfigurableAttribute descriptionConfAtt = null; 678 try { 679 descriptionConfAtt = (ConfigurableAttribute) container 680 .getAttribute(OLD_SVG_ICON_ATTNAME); 681 } catch (Exception ex2) { 682 if (isDebugging) { 683 log.warn(container.getName() 684 + ": exception getting _iconDescription attribute: " 685 + ex2.getMessage() + "\n\n"); 686 } 687 descriptionConfAtt = null; 688 } 689 690 if (isDebugging) { 691 log.debug("_divaCreateBackgroundFigure(" + container.getName() 692 + "): _iconDescription attribute: " + descriptionConfAtt 693 + "\n\n"); 694 } 695 696 // if description not null and has changed, get icon svg file contents 697 if (descriptionConfAtt != null 698 && _divaSVGIconAttrib != descriptionConfAtt) { 699 700 if (_divaSVGIconAttrib != null) { 701 // Remove this as a listener if there 702 // was a previous description. 703 _divaSVGIconAttrib.removeValueListener(this); 704 } 705 706 // update the global _divaSVGIconAttrib variable. 707 _divaSVGIconAttrib = descriptionConfAtt; 708 709 if (_divaSVGIconAttrib != null) { 710 // Listen for changes in value to the icon description. 711 _divaSVGIconAttrib.addValueListener(this); 712 } 713 714 try { 715 _svgXML = _divaSVGIconAttrib.value(); 716 } catch (IOException ex1) { 717 718 ex1.printStackTrace(); 719 } 720 if (isDebugging) { 721 log.debug("_divaCreateBackgroundFigure(" + container.getName() 722 + "): _iconDescription attribute CONTENTS: \n" 723 + _svgXML + "\n\n"); 724 } 725 return true; 726 } 727 return false; 728 } 729 730 /** 731 * Update the painted list of the icon based on the SVG data in the 732 * associated _svgXML variable, if there is one. 733 */ 734 private void _updatePaintedList() { 735 736 if (_svgXML == null || _svgXML.trim().length() == 0) { 737 _paintedList = null; 738 return; 739 } 740 741 try { 742 if (isBatikRendering) { 743 _paintedList = _batikCreatePaintedList(_svgXML); 744 _addListenersToPaintedList(); 745 } else { 746 _paintedList = _divaCreatePaintedList(_svgXML); 747 } 748 } catch (Exception ex) { 749 _paintedList = null; 750 if (isBatikRendering) { 751 _removeListenersFromPaintedList(); 752 } 753 } 754 } 755 756 private PaintedList _batikCreatePaintedList(String svgXMLStr) 757 throws Exception { 758 // START - EXECUTED ONLY IF svgRenderingMethod is SVG_BATIK_RENDERING 759 Reader sr = new StringReader(svgXMLStr); 760 761 final String uri = _SVG_BASE_URI + sr.hashCode(); 762 SVGDocument doc = _df.createSVGDocument(uri, sr); 763 764 PaintedList list = new PaintedList(); 765 String name = doc.getDocumentElement().getNodeName(); 766 767 if (!name.equals("svg")) { 768 throw new IllegalArgumentException( 769 "Input XML has a root name which is '" + name 770 + "' instead of 'svg'"); 771 } 772 773 SVGPaintedObject object = new SVGPaintedObject(doc); 774 if (object != null) { 775 list.add(object); 776 } 777 778 return list; 779 // END - EXECUTED ONLY IF svgRenderingMethod is SVG_BATIK_RENDERING 780 } 781 782 private PaintedList _divaCreatePaintedList(String svgXMLStr) 783 throws Exception { 784 Reader in = new StringReader(svgXMLStr); 785 786 // NOTE: Do we need a base here? 787 XmlDocument document = new XmlDocument((URL) null); 788 XmlReader reader = new XmlReader(); 789 reader.parse(document, in); 790 791 XmlElement root = document.getRoot(); 792 return SVGParser.createPaintedList(root); 793 } 794 795 /** 796 * add Listeners to all elements in the _paintedList, so they can be 797 * repainted when Batik has finished rendering them 798 */ 799 private void _addListenersToPaintedList() { 800 801 if (_paintedList == null) { 802 return; 803 } 804 List objects = _paintedList.paintedObjects; 805 Iterator it = objects.iterator(); 806 while (it.hasNext()) { 807 SVGPaintedObject po = (SVGPaintedObject) (it.next()); 808 po.addSVGRenderingListener(_svgrListener); 809 } 810 } 811 812 /** 813 * remove Listeners from all elements in the _paintedList 814 */ 815 private void _removeListenersFromPaintedList() { 816 817 if (_paintedList == null) { 818 return; 819 } 820 List objects = _paintedList.paintedObjects; 821 Iterator it = objects.iterator(); 822 while (it.hasNext()) { 823 SVGPaintedObject po = (SVGPaintedObject) (it.next()); 824 po.removeSVGRenderingListener(_svgrListener); 825 } 826 } 827 828 // make sure we create the default (blank) BG figure 829 // only once, and then only if it is needed 830 private Figure _getDefaultBackgroundFigure() { 831 832 if (_defaultBackgroundFigure == null) { 833 _defaultBackgroundFigure = _createDefaultBackgroundFigure(); 834 } 835 return _defaultBackgroundFigure; 836 } 837 838 private void fireChangeRequest() { 839 // doing a _bgFigure.repaint() will make the icons show up, but the 840 // ports are in the wrong places, because when the actors are being 841 // rendered, getBounds() called on the SVGPaintedObject returns the 842 // default Dim(20,20), since the SVG hasn't been parsed/built yet, so 843 // SVGPaintedObject doesn't yet know it's finished size. We therefore 844 // need to issue a ChangeRequest (albeit one that doesn't actually 845 // involve any changes) to get the icons to update after the SVG has 846 // finished rendering, which will then cause the ports to be rendered 847 // in the correct locations... 848 // This actually seems like a bit of a hack. Future improvement - see 849 // if there's a better way 850 NamedObj container = toplevel(); 851 if (container == null) { 852 return; 853 } 854 ChangeRequest request = new EmptyChangeRequest(this, "update request"); 855 container.requestChange(request); 856 } 857 858 /** 859 * Read the contents of a resource and return as a String. Use 860 * getResourceAsStream() to find the named resource using this classes 861 * classloader. Then spool the contents into a StringBuffer and return the 862 * String. 863 * 864 * @param name 865 * resource to retrieve. 866 * @return String containing the contents of the named resource 867 */ 868 private String readResourceAsString(String name) { 869 // System.out.println("Trying to open file: " + file.toString()); 870 InputStream is = getClass().getResourceAsStream(name); 871 872 Reader r = null; 873 try { 874 r = new InputStreamReader(is); 875 char[] chars = new char[1024]; 876 877 StringBuffer result = new StringBuffer(""); 878 879 while (true) { 880 try { 881 int bread = r.read(chars); 882 if (bread < 0) { 883 break; 884 } 885 result.append(chars, 0, bread); 886 } catch (IOException e) { 887 MessageHandler.error("Error reading " + name, e); 888 break; 889 } 890 891 } 892 893 return result.toString(); 894 } finally { 895 if(r != null) { 896 try { 897 r.close(); 898 } catch (IOException e) { 899 MessageHandler.error("Error reading " + name, e); 900 } 901 } 902 } 903 904 } 905 906 // ///////////////////////////////////////////////////////////////// 907 // // private members //// 908 // ///////////////////////////////////////////////////////////////// 909 910 // The list of painted objects contained in this icon. 911 private PaintedList _paintedList; 912 913 // The attribute containing the XMS string describing this actor's svg icon, 914 // to be rendered by diva's simple svg rendering framework 915 private ConfigurableAttribute _divaSVGIconAttrib; 916 917 // The attribute containing the description of the thumbnail icon in SVG 918 // XML, 919 // to be rendered by diva. 920 private ConfigurableAttribute _divaThumbAttrib; 921 922 // The attribute containing the path of the raster thumbnail icon 923 private ConfigurableAttribute _rasterThumbAttrib; 924 925 // The attribute containing the path of the actor svg icon, to be 926 // rendered by batik 927 private ConfigurableAttribute _batikSVGIconAttrib; 928 929 // the Figure returned by the createBackgroundFigure() method. Need a global 930 // handle so we can update it after svg rendering is finished 931 private Figure _bgFigure; 932 933 private Figure _defaultBackgroundFigure; 934 935 private static SVGDocumentFactory _df; 936 937 private String _svgXML; 938 939 private boolean isBatikRendering = (StaticGUIResources 940 .getSVGRenderingMethod() == StaticGUIResources.SVG_BATIK_RENDERING); 941 942 private static final String OLD_SVG_ICON_ATTNAME = "_iconDescription"; 943 private static final String OLD_SVG_THUMB_ATTNAME = "_smallIconDescription"; 944 945 private boolean lsidAssignmentDone = false; 946 947 private final SVGRenderingListener _svgrListener = new SVGRenderingListener() { 948 public void svgRenderingComplete() { 949 950 // refresh icon in case it's being used by the getIcon() method 951 // to create an icon in the actor library 952 if (_bgFigure != null) { 953 _bgFigure.repaint(); 954 } 955 956 // redraw model so ports get moved to correct bounds 957 fireChangeRequest(); 958 } 959 }; 960}