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}