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