001/* An icon that displays a specified java.awt.Shape.
002
003 Copyright (c) 2003-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.Shape;
032import java.awt.geom.Rectangle2D;
033import java.util.Arrays;
034import java.util.Iterator;
035
036import javax.swing.SwingUtilities;
037
038import diva.canvas.Figure;
039import diva.canvas.toolbox.BasicFigure;
040import ptolemy.gui.Top;
041import ptolemy.kernel.util.IllegalActionException;
042import ptolemy.kernel.util.NameDuplicationException;
043import ptolemy.kernel.util.NamedObj;
044import ptolemy.kernel.util.Workspace;
045
046///////////////////////////////////////////////////////////////////
047//// ShapeIcon
048
049/**
050 An icon that displays a specified java.awt.Shape.
051
052 @author Edward A. Lee
053 @version $Id$
054 @since Ptolemy II 4.0
055 @Pt.ProposedRating Yellow (eal)
056 @Pt.AcceptedRating Red (johnr)
057 */
058public class ShapeIcon extends DynamicEditorIcon {
059    /** Create a new icon with the given name in the given container.
060     *  @param container The container.
061     *  @param name The name of the attribute.
062     *  @exception IllegalActionException If the attribute is not of an
063     *   acceptable class for the container.
064     *  @exception NameDuplicationException If the name coincides with
065     *   an attribute already in the container.
066     */
067    public ShapeIcon(NamedObj container, String name)
068            throws IllegalActionException, NameDuplicationException {
069        super(container, name);
070    }
071
072    /** Create a new icon with the given name in the given container
073     *  with the given default shape.
074     *  @param container The container.
075     *  @param name The name of the attribute.
076     *  @param defaultShape The default shape, which is ignored by this
077     *  constructor.
078     *  @exception IllegalActionException If the attribute is not of an
079     *   acceptable class for the container.
080     *  @exception NameDuplicationException If the name coincides with
081     *   an attribute already in the container.
082     */
083    public ShapeIcon(NamedObj container, String name, Shape defaultShape)
084            throws IllegalActionException, NameDuplicationException {
085        super(container, name);
086        //_defaultShape = defaultShape;
087        setShape(defaultShape);
088    }
089
090    ///////////////////////////////////////////////////////////////////
091    ////                         public methods                    ////
092
093    /** Clone the object into the specified workspace. The new object is
094     *  <i>not</i> added to the directory of that workspace (you must do this
095     *  yourself if you want it there).
096     *  The result is an object with no container.
097     *  @param workspace The workspace for the cloned object.
098     *  @exception CloneNotSupportedException Not thrown in this base class
099     *  @return The new Attribute.
100     */
101    @Override
102    public Object clone(Workspace workspace) throws CloneNotSupportedException {
103        ShapeIcon newObject = (ShapeIcon) super.clone(workspace);
104
105        // NOTE: Do not set back to the default shape!
106        // newObject._shape = _defaultShape;
107        return newObject;
108    }
109
110    /** Create a new default background figure, which is the shape set
111     *  by setShape, if it has been called, or a small box if not.
112     *  This must be called in the Swing thread, or a concurrent
113     *  modification exception could occur.
114     *  @return A figure representing the specified shape.
115     */
116    @Override
117    public Figure createBackgroundFigure() {
118        // NOTE: This gets called every time that the graph gets
119        // repainted, which seems excessive to me.  This will happen
120        // every time there is a modification to the model that is
121        // carried out by a MoMLChangeRequest.
122        // The Diva graph package implements a model-view-controller
123        // architecture, which implies that this needs to return a new
124        // figure each time it is called.  The reason is that the figure
125        // may go into a different view, and transformations may be applied
126        // to that figure in that view.  However, this class needs to be
127        // able to update that figure when setShape() is called.  Hence,
128        // this class keeps a list of all the figures it has created.
129        // The references to these figures, however, have to be weak
130        // references, so that this class does not interfere with garbage
131        // collection of the figure when the view is destroyed.
132        BasicFigure newFigure;
133
134        if (_shape != null) {
135            newFigure = new BasicFigure(_shape);
136        } else {
137            // Create a white rectangle.
138            newFigure = new BasicFigure(
139                    new Rectangle2D.Double(0.0, 0.0, 20.0, 20.0));
140        }
141
142        // By default, the origin should be the upper left.
143        newFigure.setCentered(_centered);
144        newFigure.setLineWidth(_lineWidth);
145        newFigure.setDashArray(_dashArray);
146        newFigure.setStrokePaint(_lineColor);
147        newFigure.setFillPaint(_fillColor);
148        newFigure.setRotation(_rotation);
149
150        _addLiveFigure(newFigure);
151
152        return newFigure;
153    }
154
155    /** Return whether the figure should be centered on its origin.
156     *  @return False If the origin of the figure, as
157     *   returned by getOrigin(), is the upper left corner.
158     */
159    public boolean isCentered() {
160        return _centered;
161    }
162
163    /** Specify whether the figure should be centered or not.
164     *  By default, the origin of the figure is the center.
165     *  This is deferred and executed in the Swing thread.
166     *  @param centered False to make the figure's origin at the
167     *   upper left.
168     */
169    public void setCentered(boolean centered) {
170        // Avoid calling swing if things haven't actually changed.
171        if (_centered == centered) {
172            return;
173        }
174
175        _centered = centered;
176
177        // Update the shapes of all the figures that this icon has
178        // created (which may be in multiple views). This has to be
179        // done in the Swing thread.  Assuming that createBackgroundFigure()
180        // is also called in the Swing thread, there is no possibility of
181        // conflict here where that method is trying to add to the _figures
182        // list while this method is traversing it.
183        Runnable doSet = new Runnable() {
184            @Override
185            public void run() {
186                synchronized (_figures) {
187                    Iterator figures = _liveFigureIterator();
188
189                    while (figures.hasNext()) {
190                        Object figure = figures.next();
191                        ((BasicFigure) figure).setCentered(_centered);
192                    }
193                }
194            }
195        };
196
197        SwingUtilities.invokeLater(doSet);
198    }
199
200    /** Specify the dash array to use for rendering lines.
201     *  This is deferred and executed in the Swing thread.
202     *  @param dashArray The dash array.
203     */
204    public void setDashArray(float[] dashArray) {
205        // Avoid calling swing if things haven't actually changed.
206        if (_dashArray != null && Arrays.equals(_dashArray, dashArray)) {
207            return;
208        }
209
210        _dashArray = dashArray;
211
212        // Update the shapes of all the figures that this icon has
213        // created (which may be in multiple views). This has to be
214        // done in the Swing thread.  Assuming that createBackgroundFigure()
215        // is also called in the Swing thread, there is no possibility of
216        // conflict here where that method is trying to add to the _figures
217        // list while this method is traversing it.
218        Runnable doSet = new Runnable() {
219            @Override
220            public void run() {
221                synchronized (_figures) {
222                    Iterator figures = _liveFigureIterator();
223
224                    while (figures.hasNext()) {
225                        Object figure = figures.next();
226                        ((BasicFigure) figure).setDashArray(_dashArray);
227                    }
228                }
229            }
230        };
231
232        SwingUtilities.invokeLater(doSet);
233    }
234
235    /** Specify the fill color to use.  This is deferred and executed
236     *  in the Swing thread.
237     *  @param fillColor The fill color to use.
238     */
239    public void setFillColor(Color fillColor) {
240        // Avoid calling swing if things haven't actually changed.
241        if (_fillColor != null && _fillColor.equals(fillColor)) {
242            return;
243        }
244
245        _fillColor = fillColor;
246
247        // Update the shapes of all the figures that this icon has
248        // created (which may be in multiple views). This has to be
249        // done in the Swing thread.  Assuming that createBackgroundFigure()
250        // is also called in the Swing thread, there is no possibility of
251        // conflict here where that method is trying to add to the _figures
252        // list while this method is traversing it.
253        Runnable doSet = new Runnable() {
254            @Override
255            public void run() {
256                synchronized (_figures) {
257                    Iterator figures = _liveFigureIterator();
258
259                    while (figures.hasNext()) {
260                        Object figure = figures.next();
261                        ((BasicFigure) figure).setFillPaint(_fillColor);
262                    }
263                }
264            }
265        };
266
267        SwingUtilities.invokeLater(doSet);
268    }
269
270    /** Specify the line color to use.  This is deferred and executed
271     *  in the Swing thread.
272     *  @param lineColor The line color to use.
273     */
274    public void setLineColor(Color lineColor) {
275        // Avoid calling swing if things haven't actually changed.
276        if (_lineColor != null && _lineColor.equals(lineColor)) {
277            return;
278        }
279
280        _lineColor = lineColor;
281
282        // Update the shapes of all the figures that this icon has
283        // created (which may be in multiple views). This has to be
284        // done in the Swing thread.  Assuming that createBackgroundFigure()
285        // is also called in the Swing thread, there is no possibility of
286        // conflict here where that method is trying to add to the _figures
287        // list while this method is traversing it.
288        Runnable doSet = new Runnable() {
289            @Override
290            public void run() {
291                synchronized (_figures) {
292                    Iterator figures = _liveFigureIterator();
293
294                    while (figures.hasNext()) {
295                        Object figure = figures.next();
296                        ((BasicFigure) figure).setStrokePaint(_lineColor);
297                    }
298                }
299            }
300        };
301
302        SwingUtilities.invokeLater(doSet);
303    }
304
305    /** Specify the line width to use.  This is deferred and executed
306     *  in the Swing thread.
307     *  @param lineWidth The line width to use.
308     */
309    public void setLineWidth(float lineWidth) {
310        // Avoid calling swing if things haven't actually changed.
311        if (_lineWidth == lineWidth) {
312            return;
313        }
314
315        _lineWidth = lineWidth;
316
317        // Update the shapes of all the figures that this icon has
318        // created (which may be in multiple views). This has to be
319        // done in the Swing thread.  Assuming that createBackgroundFigure()
320        // is also called in the Swing thread, there is no possibility of
321        // conflict here where that method is trying to add to the _figures
322        // list while this method is traversing it.
323        Runnable doSet = new Runnable() {
324            @Override
325            public void run() {
326                synchronized (_figures) {
327                    Iterator figures = _liveFigureIterator();
328
329                    while (figures.hasNext()) {
330                        Object figure = figures.next();
331                        ((BasicFigure) figure).setLineWidth(_lineWidth);
332                    }
333                }
334            }
335        };
336
337        SwingUtilities.invokeLater(doSet);
338    }
339
340    /** Specify the rotation angle in radians.  This is deferred and executed
341     *  in the Swing thread.
342     *  @param angle The rotation angle in radians.
343     */
344    public void setRotation(final double angle) {
345        // Avoid calling swing if things haven't actually changed.
346        if (_rotation == angle) {
347            return;
348        }
349
350        _rotation = angle;
351
352        // Update the shapes of all the figures that this icon has
353        // created (which may be in multiple views). This has to be
354        // done in the Swing thread.  Assuming that createBackgroundFigure()
355        // is also called in the Swing thread, there is no possibility of
356        // conflict here where that method is trying to add to the _figures
357        // list while this method is traversing it.
358        Runnable doSet = new Runnable() {
359            @Override
360            public void run() {
361                synchronized (_figures) {
362                    Iterator figures = _liveFigureIterator();
363
364                    while (figures.hasNext()) {
365                        BasicFigure figure = (BasicFigure) figures.next();
366                        figure.setRotation(angle);
367                    }
368                }
369            }
370        };
371
372        SwingUtilities.invokeLater(doSet);
373    }
374
375    /** Specify a path to display.  This is deferred and executed
376     *  in the Swing thread.
377     *  @param path The path to display.
378     */
379    public void setShape(Shape path) {
380        _shape = path;
381
382        // Update the shapes of all the figures that this icon has
383        // created (which may be in multiple views). This has to be
384        // done in the Swing thread.  Assuming that createBackgroundFigure()
385        // is also called in the Swing thread, there is no possibility of
386        // conflict here where that method is trying to add to the _figures
387        // list while this method is traversing it.
388        Runnable doSet = new Runnable() {
389            @Override
390            public void run() {
391                synchronized (_figures) {
392                    Iterator figures = _liveFigureIterator();
393
394                    while (figures.hasNext()) {
395                        Object figure = figures.next();
396                        ((BasicFigure) figure).setPrototypeShape(_shape);
397                    }
398                }
399            }
400        };
401
402        // NOTE: Elsewhere, we use SwingUtilities.invokeLater().
403        // Can this create problems with the order of updates being
404        // different, since the following line may execute immediately
405        // if this method is called in Swing event thread, whereas if
406        // we called SwingUtilities.invokeLater(), then the update
407        // would be deferred.
408        Top.deferIfNecessary(doSet);
409    }
410
411    ///////////////////////////////////////////////////////////////////
412    ////                         private variables                 ////
413    // Indicator of whether the figure should be centered on its origin.
414    private boolean _centered = false;
415
416    // Dash array, if specified.
417    private float[] _dashArray;
418
419    // Default shape specified in the constructor.
420    //private Shape _defaultShape;
421
422    // The specified fill color.
423    private Color _fillColor = Color.white;
424
425    // The specified line color.
426    private Color _lineColor = Color.black;
427
428    // The specified line width.
429    private float _lineWidth = 1f;
430
431    // The rotation.
432    private double _rotation = 0.0;
433
434    // The shape that is rendered.
435    private Shape _shape;
436}