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}