001/* An icon that renders the value of an attribute of the container. 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.Random; 035 036import javax.swing.SwingConstants; 037 038import diva.canvas.CompositeFigure; 039import diva.canvas.Figure; 040import diva.canvas.toolbox.BasicRectangle; 041import diva.canvas.toolbox.LabelFigure; 042import ptolemy.actor.gui.ColorAttribute; 043import ptolemy.data.ArrayToken; 044import ptolemy.data.RecordToken; 045import ptolemy.data.StringToken; 046import ptolemy.data.Token; 047import ptolemy.data.expr.Parameter; 048import ptolemy.data.expr.StringParameter; 049import ptolemy.data.expr.Variable; 050import ptolemy.data.type.ArrayType; 051import ptolemy.data.type.BaseType; 052import ptolemy.kernel.util.Attribute; 053import ptolemy.kernel.util.IllegalActionException; 054import ptolemy.kernel.util.NameDuplicationException; 055import ptolemy.kernel.util.NamedObj; 056import ptolemy.kernel.util.Settable; 057 058/////////////////////////////////////////////////////////////////// 059//// TableIcon 060 061/** 062 An icon that displays the value of a variable of the container in a 063 table. The attribute is assumed to be an instance of Variable, and its name 064 is given by the parameter <i>variableName</i>. Its value must be an 065 array of records. A subset of the fields in the records given by 066 the <i>fields</i> parameter is displayed in the icon. 067 068 @author Edward A. Lee 069 @version $Id$ 070 @since Ptolemy II 8.0 071 @Pt.ProposedRating Yellow (eal) 072 @Pt.AcceptedRating Red (johnr) 073 */ 074public class TableIcon extends DynamicEditorIcon { 075 /** Create a new icon with the given name in the given container. 076 * The container is required to implement Settable, or an exception 077 * will be thrown. 078 * @param container The container for this attribute. 079 * @param name The name of this attribute. 080 * @exception IllegalActionException If thrown by the parent 081 * class or while setting an attribute 082 * @exception NameDuplicationException If the name coincides with 083 * an attribute already in the container. 084 */ 085 public TableIcon(NamedObj container, String name) 086 throws NameDuplicationException, IllegalActionException { 087 super(container, name); 088 089 variableName = new StringParameter(this, "variableName"); 090 091 boxColor = new ColorAttribute(this, "boxColor"); 092 boxColor.setExpression("{1.0, 1.0, 1.0, 1.0}"); 093 094 Variable UNBOUNDED = new Variable(this, "UNBOUNDED"); 095 UNBOUNDED.setVisibility(Settable.NONE); 096 UNBOUNDED.setExpression("0"); 097 098 maxRows = new Parameter(this, "maxRows"); 099 maxRows.setTypeEquals(BaseType.INT); 100 maxRows.setExpression("UNBOUNDED"); 101 102 Variable ALL = new Variable(this, "ALL"); 103 ALL.setVisibility(Settable.NONE); 104 Token emptyStringArray = new ArrayToken(BaseType.STRING); 105 ALL.setToken(emptyStringArray); 106 107 fields = new Parameter(this, "fields"); 108 fields.setTypeEquals(new ArrayType(BaseType.STRING)); 109 fields.setExpression("ALL"); 110 111 colorKey = new StringParameter(this, "colorKey"); 112 } 113 114 /////////////////////////////////////////////////////////////////// 115 //// parameters //// 116 117 /** Color of the box. This defaults to white. */ 118 public ColorAttribute boxColor; 119 120 /** A column name to use as a color key. If this string is 121 * non-empty, then it specifies a column name that is used 122 * to determine a color for each row. The value in that 123 * row and column determines the color via a hash function, 124 * so that if two rows are identical in that column, then 125 * they are also identical in color. This is a string that 126 * defaults to empty, indicating that all rows should 127 * be displayed in black. 128 */ 129 public StringParameter colorKey; 130 131 /** The fields to display in the table. 132 * This is an array of strings specifying the field 133 * names to display. It defaults to ALL, which indicates 134 * that all fields should be displayed. 135 */ 136 public Parameter fields; 137 138 /** The maximum number of rows to display. This is an integer, with 139 * default value UNBOUNDED. 140 */ 141 public Parameter maxRows; 142 143 /** The name of the variable in the container 144 * whose value should be displayed in the icon. The variable 145 * value must be an array of records. This is a string that 146 * defaults to the empty string. 147 */ 148 public StringParameter variableName; 149 150 /////////////////////////////////////////////////////////////////// 151 //// public methods //// 152 153 /** Create a new background figure. This overrides the base class 154 * to draw a box around the value display, where the width of the 155 * box depends on the value. 156 * @return A new figure. 157 */ 158 @Override 159 public Figure createBackgroundFigure() { 160 NamedObj container = getContainer(); 161 CompositeFigure result = new CompositeFigure(); 162 if (container != null) { 163 try { 164 ArrayToken fieldsValue = (ArrayToken) fields.getToken(); 165 Attribute associatedAttribute = container 166 .getAttribute(variableName.getExpression()); 167 if (associatedAttribute instanceof Variable) { 168 Token value = ((Variable) associatedAttribute).getToken(); 169 if (value instanceof ArrayToken) { 170 // Find the number of rows and columns. 171 int numRows = ((ArrayToken) value).length(); 172 int numColumns = fieldsValue.length(); 173 if (numColumns == 0) { 174 // All columns should be included. 175 // Make a pass to figure out how many that is. 176 for (int i = 0; i < numRows; i++) { 177 Token row = ((ArrayToken) value).getElement(i); 178 if (row instanceof RecordToken) { 179 int rowWidth = ((RecordToken) row) 180 .labelSet().size(); 181 if (rowWidth > numColumns) { 182 numColumns = rowWidth; 183 } 184 } 185 } 186 } 187 188 // Find the width of each column and the height of each row. 189 // All rows are the same height, but column widths can vary. 190 double rowHeight = 0.0; 191 double columnWidth[] = new double[numColumns]; 192 for (int i = 1; i < numColumns; i++) { 193 columnWidth[i] = 0.0; 194 } 195 LabelFigure tableElement[][] = new LabelFigure[numRows][numColumns]; 196 // Iterate over rows. 197 for (int i = 0; i < numRows; i++) { 198 Token row = ((ArrayToken) value).getElement(i); 199 if (row instanceof RecordToken) { 200 if (fieldsValue.length() == 0) { 201 // Display all fields. 202 Iterator labelSet = ((RecordToken) row) 203 .labelSet().iterator(); 204 int j = 0; 205 while (labelSet.hasNext()) { 206 String column = (String) labelSet 207 .next(); 208 tableElement[i][j] = _labelFigure( 209 (RecordToken) row, column); 210 Rectangle2D bounds = tableElement[i][j] 211 .getBounds(); 212 double width = bounds.getWidth(); 213 if (width > columnWidth[j]) { 214 columnWidth[j] = width; 215 } 216 double height = bounds.getHeight(); 217 if (height > rowHeight) { 218 rowHeight = height; 219 } 220 j++; 221 } 222 } else { 223 // Display specified fields. 224 for (int j = 0; j < fieldsValue 225 .length(); j++) { 226 if (j >= numColumns) { 227 break; 228 } 229 String column = ((StringToken) fieldsValue 230 .getElement(j)).stringValue(); 231 tableElement[i][j] = _labelFigure( 232 (RecordToken) row, column); 233 Rectangle2D bounds = tableElement[i][j] 234 .getBounds(); 235 double width = bounds.getWidth(); 236 if (width > columnWidth[j]) { 237 columnWidth[j] = width; 238 } 239 double height = bounds.getHeight(); 240 if (height > rowHeight) { 241 rowHeight = height; 242 } 243 } 244 } 245 } 246 } 247 248 // Now make a pass to position and add all the figures. 249 double rowPosition = _VERTICAL_PADDING; 250 // Iterate over rows. 251 for (int i = 0; i < numRows; i++) { 252 Token row = ((ArrayToken) value).getElement(i); 253 if (row instanceof RecordToken) { 254 if (fieldsValue.length() == 0) { 255 // Display all fields. 256 Iterator labelSet = ((RecordToken) row) 257 .labelSet().iterator(); 258 int j = 0; 259 double columnPosition = _HORIZONTAL_PADDING; 260 while (labelSet.hasNext()) { 261 /*String column = (String) */labelSet 262 .next(); 263 tableElement[i][j].translateTo( 264 columnPosition, rowPosition); 265 result.add(tableElement[i][j]); 266 columnPosition += columnWidth[j] 267 + _HORIZONTAL_PADDING; 268 j++; 269 } 270 } else { 271 // Display specified fields. 272 double columnPosition = _HORIZONTAL_PADDING; 273 for (int j = 0; j < fieldsValue 274 .length(); j++) { 275 // String column = ((StringToken)fieldsValue.getElement(j)).stringValue(); 276 tableElement[i][j].translateTo( 277 columnPosition, rowPosition); 278 result.add(tableElement[i][j]); 279 columnPosition += columnWidth[j] 280 + _HORIZONTAL_PADDING; 281 } 282 } 283 } 284 rowPosition += rowHeight + _VERTICAL_PADDING; 285 } 286 } 287 } 288 } catch (IllegalActionException e) { 289 // Stick the error message in the icon. 290 result.add(new LabelFigure(e.getMessage())); 291 } 292 } 293 // Now put a box around it all. 294 Rectangle2D bounds = result.getBounds(); 295 // Double the padding below to allow for both sides. 296 double width = Math.floor(bounds.getWidth()) + _HORIZONTAL_PADDING * 2; 297 double height = Math.floor(bounds.getHeight()) + _VERTICAL_PADDING * 2; 298 Figure rectangle = new BasicRectangle(0, 0, width, height, 299 boxColor.asColor(), 1); 300 CompositeFigure finalResult = new CompositeFigure(rectangle); 301 finalResult.add(result); 302 return finalResult; 303 } 304 305 /////////////////////////////////////////////////////////////////// 306 //// protected members //// 307 308 /** The font used. */ 309 protected static final Font _labelFont = new Font("SansSerif", Font.PLAIN, 310 12); 311 312 /** The amount of padding to use around the edges. */ 313 protected static final double _HORIZONTAL_PADDING = 5.0; 314 315 /** The amount of padding to use around the edges. */ 316 protected static final double _VERTICAL_PADDING = 3.0; 317 318 /////////////////////////////////////////////////////////////////// 319 //// private methods //// 320 321 /** Return a label figure for the specified token. 322 * @param row The row. 323 * @param column The column. 324 * @return A label figure. 325 */ 326 private LabelFigure _labelFigure(RecordToken row, String column) { 327 Token token = row.get(column); 328 String label = ""; 329 if (token instanceof StringToken) { 330 label = ((StringToken) token).stringValue(); 331 } else if (token != null) { 332 label = token.toString(); 333 } 334 Color color = Color.black; 335 try { 336 if (!colorKey.stringValue().equals("")) { 337 Token colorToken = row.get(colorKey.stringValue()); 338 if (colorToken != null) { 339 color = _uniqueColor(colorToken.toString()); 340 } 341 } 342 } catch (IllegalActionException e) { 343 // Ignore. Use black. 344 } 345 346 LabelFigure tableElement = new LabelFigure(label, _labelFont, 1.0, 347 SwingConstants.NORTH_WEST, color); 348 return tableElement; 349 } 350 351 private Color _uniqueColor(Object object) { 352 // Get a color from the hash code. We will use 353 // the low order 24 bits only. 354 int hashCode = object.hashCode(); 355 // Use the code as a seed for a random number generator. 356 // FindBugs: [H B BC] Random object created and used only once [DMI_RANDOM_USED_ONLY_ONCE] 357 // Actually this is the intend since you want a unique color for 358 // each specific object (hence it can't be completely random). 359 int code = new Random(hashCode).nextInt(); 360 float red = (code >> 16 & 0xff) / 256.0f; 361 float green = (code >> 8 & 0xff) / 256.0f; 362 float blue = (code & 0xff) / 256.0f; 363 // Make sure the color is at least as dark as close to a pure red, green, or blue. 364 // This means that the magnitude of the r,g,b vector is no greater than 1.0. 365 float magnitude = (float) Math 366 .sqrt(red * red + green * green + blue * blue); 367 if (magnitude < 0.8f) { 368 magnitude = 0.8f; 369 } 370 371 Color result = new Color(red / magnitude, green / magnitude, 372 blue / magnitude); 373 return result; 374 } 375}