001/* An icon that renders the name of the container.
002
003 Copyright (c) 2006-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.Font;
031import java.awt.Paint;
032import java.awt.geom.Point2D;
033import java.awt.geom.Rectangle2D;
034
035import javax.swing.Icon;
036import javax.swing.SwingConstants;
037
038import diva.canvas.CompositeFigure;
039import diva.canvas.Figure;
040import diva.canvas.toolbox.BasicRectangle;
041import diva.canvas.toolbox.LabelFigure;
042import diva.canvas.toolbox.RoundedRectangle;
043import diva.gui.toolbox.FigureIcon;
044import ptolemy.actor.gui.ColorAttribute;
045import ptolemy.data.BooleanToken;
046import ptolemy.data.DoubleToken;
047import ptolemy.data.expr.Parameter;
048import ptolemy.data.expr.SingletonParameter;
049import ptolemy.data.type.BaseType;
050import ptolemy.kernel.util.Attribute;
051import ptolemy.kernel.util.IllegalActionException;
052import ptolemy.kernel.util.NameDuplicationException;
053import ptolemy.kernel.util.NamedObj;
054import ptolemy.kernel.util.Settable;
055import ptolemy.vergil.toolbox.SnapConstraint;
056
057///////////////////////////////////////////////////////////////////
058//// NameIcon
059
060/**
061 An icon that displays the name of the container in an appropriately
062 sized box. Put this into a composite actor or in any actor to
063 convert the icon for that actor into a simple box with the name
064 of the actor instance. You will probably also want to set the
065 actor instance to not display its name above its icon. You can
066 do that via the Customize Name dialog (obtained by right clicking
067 on the icon) or by creating a parameter named "_hideName" with
068 value true.
069
070 @author Edward A. Lee
071 @version $Id$
072 @since Ptolemy II 5.2
073 @Pt.ProposedRating Yellow (eal)
074 @Pt.AcceptedRating Red (johnr)
075 */
076public class NameIcon extends EditorIcon {
077
078    /** Create a new icon with the given name in the given container.
079     *  The container is required to implement Settable, or an exception
080     *  will be thrown.
081     *  @param container The container for this attribute.
082     *  @param name The name of this attribute.
083     *  @exception IllegalActionException If thrown by the parent
084     *  class or while setting an attribute.
085     *  @exception NameDuplicationException If the name coincides with
086     *   an attribute already in the container.
087     */
088    public NameIcon(NamedObj container, String name)
089            throws NameDuplicationException, IllegalActionException {
090        super(container, name);
091
092        // Create an icon for this attribute.
093        // This has the side effect of making it visible
094        // in Vergil, and giving a reasonable rendition.
095        TextIcon icon = new TextIcon(this, "_icon");
096        icon.setIconText("-N-");
097        icon.setText(
098                "NameIcon attribute: This sets the icon to be a box with the name.");
099        icon.setPersistent(false);
100
101        // Hide the name.
102        SingletonParameter hide = new SingletonParameter(this, "_hideName");
103        hide.setToken(BooleanToken.TRUE);
104        hide.setVisibility(Settable.EXPERT);
105
106        color = new ColorAttribute(this, "color");
107        color.setExpression("{1.0,1.0,1.0,1.0}");
108
109        rounding = new Parameter(this, "rounding");
110        rounding.setTypeEquals(BaseType.DOUBLE);
111        rounding.setExpression("0.0");
112
113        spacing = new Parameter(this, "spacing");
114        spacing.setTypeEquals(BaseType.DOUBLE);
115        spacing.setExpression("0.0");
116    }
117
118    ///////////////////////////////////////////////////////////////////
119    ////                         parameters                        ////
120
121    /** The background color to use in the box.
122     *  This defaults to white.
123     */
124    public ColorAttribute color;
125
126    /** The amount of rounding of the corners.
127     *  This is a double that defaults to 0.0, which indicates no rounding.
128     */
129    public Parameter rounding;
130
131    /** If greater than zero, then use a double box where the outside
132     *  one is the specified size larger than the inside one.
133     *  This is a double that defaults to 0.0, which indicates a single
134     *  box.
135     */
136    public Parameter spacing;
137
138    ///////////////////////////////////////////////////////////////////
139    ////                         public methods                    ////
140
141    /** React to a changes in the attributes by changing
142     *  the icon.
143     *  @param attribute The attribute that changed.
144     *  @exception IllegalActionException If the change is not acceptable
145     *   to this container (should not be thrown).
146     */
147    @Override
148    public void attributeChanged(Attribute attribute)
149            throws IllegalActionException {
150        if (attribute == rounding) {
151            // Make sure that the new rounding value is valid.
152            double roundingValue = ((DoubleToken) rounding.getToken())
153                    .doubleValue();
154
155            if (roundingValue < 0.0) {
156                throw new IllegalActionException(this,
157                        "Invalid rounding value. Required to be non-negative.");
158            }
159
160            if (roundingValue != _roundingValue) {
161                _roundingValue = roundingValue;
162            }
163        } else if (attribute == spacing) {
164            // Make sure that the new spacing value is valid.
165            double spacingValue = ((DoubleToken) spacing.getToken())
166                    .doubleValue();
167
168            if (spacingValue < 0.0) {
169                throw new IllegalActionException(this,
170                        "Invalid spacing value. Required to be non-negative.");
171            }
172
173            if (spacingValue != _spacingValue) {
174                _spacingValue = spacingValue;
175            }
176        } else {
177            super.attributeChanged(attribute);
178        }
179    }
180
181    /** Create a new background figure.  This overrides the base class
182     *  to draw a box around the value display, where the width of the
183     *  box depends on the value.
184     *  @return A new figure.
185     */
186    @Override
187    public Figure createBackgroundFigure() {
188        Point2D size = _getBackgroundSize();
189        double width = size.getX();
190        double height = size.getY();
191
192        if (_spacingValue == 0.0) {
193            if (_roundingValue == 0.0) {
194                return new BasicRectangle(0, 0, width, height, _getFill(),
195                        _getLineWidth());
196            } else {
197                RoundedRectangle result = new RoundedRectangle(0, 0, width,
198                        height, _getFill(), _getLineWidth(), _roundingValue,
199                        _roundingValue);
200                // FIXME: For book.
201                // result.setStrokePaint(Color.WHITE);
202                return result;
203            }
204        } else {
205            CompositeFigure result;
206            if (_roundingValue == 0.0) {
207                result = new CompositeFigure(new BasicRectangle(-_spacingValue,
208                        -_spacingValue, width + 2 * _spacingValue,
209                        height + 2 * _spacingValue, null, _getLineWidth()));
210                result.add(new BasicRectangle(0, 0, width, height, _getFill(),
211                        _getLineWidth()));
212            } else {
213                result = new CompositeFigure(new RoundedRectangle(
214                        -_spacingValue, -_spacingValue,
215                        width + 2 * _spacingValue, height + 2 * _spacingValue,
216                        null, _getLineWidth(), _roundingValue + _spacingValue,
217                        _roundingValue + _spacingValue));
218                result.add(new RoundedRectangle(0, 0, width, height, _getFill(),
219                        _getLineWidth(), _roundingValue, _roundingValue));
220            }
221            return result;
222        }
223    }
224
225    /** Create a new Diva figure that visually represents this icon.
226     *  The figure will be an instance of LabelFigure that renders the
227     *  name of the container.
228     *  @return A new CompositeFigure consisting of the label.
229     */
230    @Override
231    public Figure createFigure() {
232        CompositeFigure result = (CompositeFigure) super.createFigure();
233
234        String name = "No Name";
235        NamedObj container = getContainer();
236        if (container != null) {
237            name = container.getDisplayName();
238        }
239        LabelFigure label = _getLabel(result, name);
240        result.add(label);
241        return result;
242    }
243
244    /** Create an icon.
245     *
246     *  @return The icon.
247     */
248    @Override
249    public Icon createIcon() {
250        if (_iconCache != null) {
251            return _iconCache;
252        }
253
254        RoundedRectangle figure = new RoundedRectangle(0, 0, 20, 10, _getFill(),
255                1.0f, 5.0, 5.0);
256        _iconCache = new FigureIcon(figure, 20, 15);
257        return _iconCache;
258    }
259
260    /** Override the base class to add or set a _hideName parameter.
261     *  @param container The container to attach this attribute to..
262     *  @exception IllegalActionException If this attribute is not of the
263     *   expected class for the container, or it has no name,
264     *   or the attribute and container are not in the same workspace, or
265     *   the proposed container would result in recursive containment.
266     *  @exception NameDuplicationException If the container already has
267     *   an attribute with the name of this attribute.
268     */
269    @Override
270    public void setContainer(NamedObj container)
271            throws IllegalActionException, NameDuplicationException {
272        NamedObj previousContainer = getContainer();
273        if (previousContainer != container && previousContainer != null) {
274            SingletonParameter hide = (SingletonParameter) previousContainer
275                    .getAttribute("_hideName", SingletonParameter.class);
276            if (hide != null) {
277                hide.setToken(BooleanToken.FALSE);
278            }
279        }
280        super.setContainer(container);
281        if (previousContainer != container && container != null) {
282            // Hide the name.
283            SingletonParameter hide = new SingletonParameter(container,
284                    "_hideName");
285            hide.setToken(BooleanToken.TRUE);
286            hide.setVisibility(Settable.EXPERT);
287        }
288    }
289
290    ///////////////////////////////////////////////////////////////////
291    ////                         protected methods                 ////
292
293    /** Return the background size.
294     *  @return the background size.
295     */
296    protected Point2D _getBackgroundSize() {
297        String name = "No Name";
298        NamedObj container = getContainer();
299        if (container != null) {
300            name = container.getDisplayName();
301        }
302
303        double width = 60;
304        double height = 30;
305
306        // Measure width of the text.  Unfortunately, this
307        // requires generating a label figure that we will not use.
308        LabelFigure label = new LabelFigure(name, _labelFont, 1.0,
309                SwingConstants.CENTER);
310        Rectangle2D stringBounds = label.getBounds();
311
312        // NOTE: Padding of 20.
313        width = Math.floor(stringBounds.getWidth()) + _xPadding;
314        height = Math.floor(stringBounds.getHeight()) + _yPadding;
315        // Quantize the height so that snap to grid still works.
316        // Round up so as to never overlap the text.
317        double defaultResolution = SnapConstraint.getDefaultResolution();
318        height = (Math.floor(height / defaultResolution) + 1)
319                * defaultResolution;
320        // Quantize the width to an even multiple of the resolution.
321        // The icon is centered on snap to grid, so this ensures that both
322        // ends align with the grid.
323        width = Math
324                .floor((width + defaultResolution) / (2.0 * defaultResolution))
325                * 2.0 * defaultResolution;
326
327        return new Point2D.Double(width, height);
328    }
329
330    /** Return the paint to use to fill the icon.
331     *  This base class returns the value of the <i>color</i> attribute.
332     *  @return The paint to use to fill the icon.
333     */
334    protected Paint _getFill() {
335        return color.asColor();
336    }
337
338    /** Get the label to put on the specified background
339     *  figure based on the specified name.
340     *  @param background The background figure on which to put the label.
341     *  @param name The name on which to base the label.
342     *  @return The label figure.
343     */
344    protected LabelFigure _getLabel(CompositeFigure background, String name) {
345        // FIXME: For book.
346        // LabelFigure label = new LabelFigure(name, _labelFont, 1.0, SwingConstants.CENTER, Color.WHITE);
347        LabelFigure label = new LabelFigure(name, _labelFont, 1.0,
348                SwingConstants.CENTER);
349        Rectangle2D backBounds = background.getBackgroundFigure().getBounds();
350        label.translateTo(backBounds.getCenterX(), backBounds.getCenterY());
351        return label;
352    }
353
354    /** Return the line width to use in rendering the box.
355     *  This base class returns 1.0f.
356     *  @return The line width to use in rendering the box.
357     */
358    protected float _getLineWidth() {
359        // FIXME: For book:
360        // return 3.0f;
361        return 1.0f;
362    }
363
364    ///////////////////////////////////////////////////////////////////
365    ////                         protected members                 ////
366
367    /** The font used. */
368    protected static final Font _labelFont = new Font("SansSerif", Font.PLAIN,
369            12);
370
371    /** Most recent value of the rounding parameter. */
372    protected double _roundingValue = 0.0;
373
374    /** Most recent value of the spacing parameter. */
375    protected double _spacingValue = 0.0;
376
377    /** The horizontal padding on the left and right sides of the name. */
378    protected double _xPadding = 20.0;
379
380    /** The vertical padding above and below the name. */
381    protected double _yPadding = 8.0;
382}