001/* An attribute with a reference to a polygon. 002 003 Copyright (c) 2004-2014 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.kernel.attributes; 029 030import java.awt.Polygon; 031import java.awt.Shape; 032 033import ptolemy.data.ArrayToken; 034import ptolemy.data.DoubleToken; 035import ptolemy.data.expr.Parameter; 036import ptolemy.data.type.ArrayType; 037import ptolemy.data.type.BaseType; 038import ptolemy.kernel.util.Attribute; 039import ptolemy.kernel.util.IllegalActionException; 040import ptolemy.kernel.util.InternalErrorException; 041import ptolemy.kernel.util.NameDuplicationException; 042import ptolemy.kernel.util.NamedObj; 043import ptolemy.kernel.util.Workspace; 044 045/////////////////////////////////////////////////////////////////// 046//// ResizablePolygonAttribute 047 048/** 049 <p>This is an attribute that is rendered as a polygon. The <i>vertices</i> 050 parameter is an array of doubles that specify the vertices of the polygon 051 in the form {x1, y1, x2, y2, ... }. 052 The <i>width</i> and <i>height</i> parameters, somewhat awkwardly, 053 are used to specify the overall width and height. The polygon will be 054 scaled to fit the specified width and height.</p> 055 056 @author Edward A. Lee 057 @version $Id$ 058 @since Ptolemy II 4.0 059 @Pt.ProposedRating Yellow (eal) 060 @Pt.AcceptedRating Red (cxh) 061 */ 062public class ResizablePolygonAttribute extends FilledShapeAttribute { 063 /** Construct an attribute with the given name contained by the 064 * specified container. The container argument must not be null, or a 065 * NullPointerException will be thrown. This attribute will use the 066 * workspace of the container for synchronization and version counts. 067 * If the name argument is null, then the name is set to the empty 068 * string. Increment the version of the workspace. 069 * @param container The container. 070 * @param name The name of this attribute. 071 * @exception IllegalActionException If the attribute is not of an 072 * acceptable class for the container, or if the name contains a period. 073 * @exception NameDuplicationException If the name coincides with 074 * an attribute already in the container. 075 */ 076 public ResizablePolygonAttribute(NamedObj container, String name) 077 throws IllegalActionException, NameDuplicationException { 078 super(container, name); 079 080 vertices = new Parameter(this, "vertices"); 081 082 ArrayType type = new ArrayType(BaseType.DOUBLE); 083 vertices.setTypeEquals(type); 084 vertices.setExpression( 085 "{0.0, 0.0, 50.0, 0.0, 25.0, 50.0, -25.0, 50.0}"); 086 } 087 088 /////////////////////////////////////////////////////////////////// 089 //// parameters //// 090 091 /** The amount of vertices of the corners. 092 * This is a double that defaults to 0.0, which indicates no vertices. 093 * The default value specifies a rhombus. 094 */ 095 public Parameter vertices; 096 097 /////////////////////////////////////////////////////////////////// 098 //// public methods //// 099 100 /** React to a changes in the attributes by changing 101 * the icon. 102 * @param attribute The attribute that changed. 103 * @exception IllegalActionException If the change is not acceptable 104 * to this container (should not be thrown). 105 */ 106 @Override 107 public void attributeChanged(Attribute attribute) 108 throws IllegalActionException { 109 if (attribute == vertices || attribute == width 110 || attribute == height && !_inAttributeChanged) { 111 // Check that the length of the array is even. 112 ArrayToken verticesValue = (ArrayToken) vertices.getToken(); 113 int length = verticesValue.length(); 114 115 if (length / 2 != (length + 1) / 2) { 116 throw new IllegalActionException(this, 117 "Length of the vertices array is required to be even."); 118 } 119 120 try { 121 // Prevent redundant actions here... When we evaluate the 122 // _other_ attribute here (whichever one did _not_ trigger 123 // this call, it will likely trigger another call to 124 // attributeChanged(), which will result in this action 125 // being performed twice. 126 _inAttributeChanged = true; 127 128 double widthValue = ((DoubleToken) width.getToken()) 129 .doubleValue(); 130 double heightValue = ((DoubleToken) height.getToken()) 131 .doubleValue(); 132 _widthValue = widthValue; 133 _heightValue = heightValue; 134 _icon.setShape(_newShape()); 135 } finally { 136 _inAttributeChanged = false; 137 } 138 } else { 139 super.attributeChanged(attribute); 140 } 141 } 142 143 /** Clone the object into the specified workspace. The new object is 144 * <i>not</i> added to the directory of that workspace (you must do this 145 * yourself if you want it there). 146 * The result is an object with no container. 147 * @param workspace The workspace for the cloned object. 148 * @exception CloneNotSupportedException Not thrown in this base class 149 * @return The new Attribute. 150 */ 151 @Override 152 public Object clone(Workspace workspace) throws CloneNotSupportedException { 153 ResizablePolygonAttribute newObject = (ResizablePolygonAttribute) super.clone( 154 workspace); 155 156 // The cloned icon ends up referring to the clonee's shape. 157 // We need to fix that here. Do not use the _newShape() method 158 // of the clone, however, because it may refer to parameters that 159 // have not been created yet. Instead, use this object to generate 160 // the new shape for the clone. 161 newObject._icon.setShape(_newShape()); 162 return newObject; 163 } 164 165 /////////////////////////////////////////////////////////////////// 166 //// protected methods //// 167 168 /** Return the a new polygon with the given vertices. 169 * @return A new shape. 170 */ 171 @Override 172 protected Shape _newShape() { 173 try { 174 ArrayToken verticesValue = (ArrayToken) vertices.getToken(); 175 int length = verticesValue.length(); 176 177 // Keep computations in double as long as possible. 178 double[] xPoints = new double[length / 2]; 179 double[] yPoints = new double[length / 2]; 180 double xMax = Double.NEGATIVE_INFINITY; 181 double xMin = Double.POSITIVE_INFINITY; 182 double yMax = Double.NEGATIVE_INFINITY; 183 double yMin = Double.POSITIVE_INFINITY; 184 185 // First, read vertex values and find the bounds. 186 for (int j = 0; j < length / 2; j++) { 187 xPoints[j] = ((DoubleToken) verticesValue.getElement(2 * j)) 188 .doubleValue(); 189 yPoints[j] = ((DoubleToken) verticesValue.getElement(2 * j + 1)) 190 .doubleValue(); 191 192 if (xPoints[j] > xMax) { 193 xMax = xPoints[j]; 194 } 195 196 if (xPoints[j] < xMin) { 197 xMin = xPoints[j]; 198 } 199 200 if (yPoints[j] > yMax) { 201 yMax = yPoints[j]; 202 } 203 204 if (yPoints[j] < yMin) { 205 yMin = yPoints[j]; 206 } 207 } 208 209 // Next, scale to width and height. 210 double scaleX = _widthValue / (xMax - xMin); 211 double scaleY = _heightValue / (yMax - yMin); 212 213 for (int j = 0; j < length / 2; j++) { 214 xPoints[j] *= scaleX; 215 yPoints[j] *= scaleY; 216 } 217 218 // Finally, convert to int. 219 int[] xInt = new int[length / 2]; 220 int[] yInt = new int[length / 2]; 221 222 for (int i = 0; i < length / 2; i++) { 223 xInt[i] = (int) Math.rint(xPoints[i]); 224 yInt[i] = (int) Math.rint(yPoints[i]); 225 } 226 227 return new Polygon(xInt, yInt, length / 2); 228 } catch (IllegalActionException e) { 229 // This should not occur because attributeChanged() 230 // has accessed the token of vertices. 231 throw new InternalErrorException(e); 232 } 233 } 234 235 /////////////////////////////////////////////////////////////////// 236 //// private variables //// 237 private boolean _inAttributeChanged = false; 238}