001/* 002 * Copyright (c) 2002-2010 The Regents of the University of California. 003 * All rights reserved. 004 * 005 * '$Author: welker $' 006 * '$Date: 2010-05-06 05:21:26 +0000 (Thu, 06 May 2010) $' 007 * '$Revision: 24234 $' 008 * 009 * Permission is hereby granted, without written agreement and without 010 * license or royalty fees, to use, copy, modify, and distribute this 011 * software and its documentation for any purpose, provided that the above 012 * copyright notice and the following two paragraphs appear in all copies 013 * of this software. 014 * 015 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 016 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 017 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 018 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 019 * SUCH DAMAGE. 020 * 021 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 022 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 023 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 024 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 025 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 026 * ENHANCEMENTS, OR MODIFICATIONS. 027 * 028 */ 029 030package org.geon; 031 032import java.io.BufferedReader; 033import java.io.BufferedWriter; 034import java.io.File; 035import java.io.FileInputStream; 036import java.io.FileReader; 037import java.io.FileWriter; 038import java.io.IOException; 039import java.io.InputStream; 040import java.util.Set; 041 042import javax.xml.parsers.DocumentBuilder; 043import javax.xml.parsers.DocumentBuilderFactory; 044 045import org.w3c.dom.Document; 046import org.w3c.dom.Element; 047import org.w3c.dom.NodeList; 048 049import ptolemy.actor.TypedAtomicActor; 050import ptolemy.actor.TypedIOPort; 051import ptolemy.data.ArrayToken; 052import ptolemy.data.BooleanToken; 053import ptolemy.data.DoubleToken; 054import ptolemy.data.StringToken; 055import ptolemy.data.Token; 056import ptolemy.data.expr.ASTPtRootNode; 057import ptolemy.data.expr.FileParameter; 058import ptolemy.data.expr.ModelScope; 059import ptolemy.data.expr.Parameter; 060import ptolemy.data.expr.ParseTreeEvaluator; 061import ptolemy.data.expr.PtParser; 062import ptolemy.data.type.ArrayType; 063import ptolemy.data.type.BaseType; 064import ptolemy.data.type.Type; 065import ptolemy.data.type.TypeConstant; 066import ptolemy.gui.GraphicalMessageHandler; 067import ptolemy.kernel.CompositeEntity; 068import ptolemy.kernel.util.IllegalActionException; 069import ptolemy.kernel.util.NameDuplicationException; 070 071////////////////////////////////////////////////////////////////////////// 072//// AddPointToSVG 073/** 074 * This actor accepts a point and an SVG file and adds the point to the SVG 075 * file. The point is defined as and array of doubles ({X,Y}). The SVG file may 076 * have conversion ratios for X and Y, specified by a conversion and xRatio, 077 * yRatio tags. The X,Y values are converted using these factors (if available). 078 * The point is added as a tag to the SVG file. The resulting SVG content is 079 * saved to svgOutFile. An example SVG file is available at 080 * lib/testdata/geon/whalen.svg. 081 * 082 * @UserLevelDocumentation This actor accepts a point, represented as an X,Y 083 * array and an SVG file and adds the point to the SVG 084 * file. If the file contains, xRatio, yRatio conversion 085 * tags, the X,Y values will be converted accordingly. 086 * The resulting SVG content is saved to svgOutFile. 087 * This actor is typically used for display purposes. An 088 * example SVG file is available at 089 * lib/testdata/geon/whalen.svg. 090 * @author Efrat Jaeger 091 * @version $Id: AddPointToSVG.java 24234 2010-05-06 05:21:26Z welker $ 092 * @since Ptolemy II 3.0.2 093 */ 094public class AddPointToSVG extends TypedAtomicActor { 095 096 /** 097 * Construct an actor with the given container and name. 098 * 099 * @param container 100 * The container. 101 * @param name 102 * The name of this actor. 103 * @exception IllegalActionException 104 * If the actor cannot be contained by the proposed 105 * container. 106 * @exception NameDuplicationException 107 * If the container already has an actor with this name. 108 */ 109 public AddPointToSVG(CompositeEntity container, String name) 110 throws IllegalActionException, NameDuplicationException { 111 super(container, name); 112 113 point = new TypedIOPort(this, "point", true, false); 114 point.setTypeEquals(new ArrayType(BaseType.DOUBLE)); 115 116 svgFile = new TypedIOPort(this, "svgFile", true, false); 117 svgFile.setTypeEquals(BaseType.STRING); 118 119 svgOutputFile = new TypedIOPort(this, "svgOutputFile", true, false); 120 svgOutputFile.setTypeEquals(BaseType.STRING); 121 122 output = new TypedIOPort(this, "output", false, true); 123 output.setTypeEquals(BaseType.STRING); 124 125 trigger = new TypedIOPort(this, "trigger", true, false); 126 trigger.setMultiport(true); 127 128 svgFileParam = new FileParameter(this, "svgFileParam"); 129 svgFileParam.setDisplayName("SVG File"); 130 131 svgOutFile = new FileParameter(this, "svgOutFile"); 132 svgOutFile.setDisplayName("SVG Output File"); 133 134 confirmOverwrite = new Parameter(this, "confirmOverwrite"); 135 confirmOverwrite.setTypeEquals(BaseType.BOOLEAN); 136 confirmOverwrite.setToken(BooleanToken.TRUE); 137 138 _attachText("_iconDescription", "<svg>\n" 139 + "<rect x=\"-25\" y=\"-20\" " + "width=\"50\" height=\"40\" " 140 + "style=\"fill:white\"/>\n" 141 + "<polygon points=\"-15,-10 -12,-10 -8,-14 -1,-14 3,-10" 142 + " 15,-10 15,10, -15,10\" " + "style=\"fill:red\"/>\n" 143 + "</svg>\n"); 144 } 145 146 // ///////////////////////////////////////////////////////////////// 147 // // ports and parameters //// 148 149 /** 150 * Point to be added. An array of doubles, {X,Y}. 151 * 152 * @UserLevelDocumentation The point to be added to the SVG file. An array 153 * of doubles of the following format: {X,Y}. 154 */ 155 public TypedIOPort point; 156 157 /** 158 * An SVG inupt file port. 159 * 160 * @UserLevelDocumentation The path to the SVG file to be updated. 161 */ 162 public TypedIOPort svgFile; 163 164 /** 165 * An SVG output file port. 166 * 167 * @UserLevelDocumentation The path to the output SVG file. 168 */ 169 public TypedIOPort svgOutputFile; 170 171 /** 172 * Outputs the SVG output file path. 173 * 174 * @UserLevelDocumentation The actor output the path to the modified SVG 175 * file. 176 */ 177 public TypedIOPort output; 178 179 /** 180 * Triggering the actor execution. 181 * 182 * @UserLevelDocumentation This port is used to trigger the actor. 183 */ 184 public TypedIOPort trigger; 185 186 /** 187 * An SVG input file name or URL. This is a string with any form accepted by 188 * FileParameter. 189 * 190 * @UserLevelDocumentation The path to the SVG file to be updated. 191 * @see FileParameter 192 */ 193 public FileParameter svgFileParam; 194 195 /** 196 * An SVG output file name or URL. This is a string with any form accepted 197 * by FileParameter. 198 * 199 * @UserLevelDocumentation The path to the output SVG file. 200 * @see FileParameter 201 */ 202 public FileParameter svgOutFile; 203 204 /** 205 * If false, then overwrite the specified file if it exists without asking. 206 * If true (the default), then if the file exists, ask for confirmation 207 * before overwriting. 208 * 209 * @UserLevelDocumentation If false, then overwrite the specified file if it 210 * exists without asking. If true (the default), 211 * then if the file exists, ask for confirmation 212 * before overwriting. 213 */ 214 public Parameter confirmOverwrite; 215 216 /** 217 * Translate the point into the svg ratio parameters and add it to the file. 218 * Broadcast the URL to the svg output file. 219 * 220 * @exception IllegalActionException 221 * If there's no director. 222 */ 223 public void fire() throws IllegalActionException { 224 225 // trigger the actor. 226 for (int i = 0; i < trigger.getWidth(); i++) { 227 if (trigger.hasToken(i)) { 228 trigger.get(i); 229 } 230 } 231 232 _svgFile = null; 233 _svgOutFile = null; 234 String fileName = ""; 235 // get SVG file. 236 if (svgFile.getWidth() > 0) { 237 fileName = ((StringToken) svgFile.get(0)).stringValue(); 238 int lineEndInd = fileName.indexOf("\n"); 239 if (lineEndInd != -1) { // The string contains a CR. 240 fileName = fileName.substring(0, lineEndInd); 241 } 242 svgFileParam.setExpression(fileName.trim()); 243 } 244 _svgFile = svgFileParam.asFile(); 245 if (!_svgFile.exists()) { 246 throw new IllegalActionException(this, "SVG file " + fileName 247 + " doesn't exist."); 248 } 249 250 fileName = ""; 251 // get SVG output file. 252 if (svgOutputFile.getWidth() > 0) { 253 fileName = ((StringToken) svgOutputFile.get(0)).stringValue(); 254 int lineEndInd = fileName.indexOf("\n"); 255 if (lineEndInd != -1) { // The string contains a CR. 256 fileName = fileName.substring(0, lineEndInd); 257 } 258 svgOutFile.setExpression(fileName); 259 } 260 _svgOutFile = svgOutFile.asFile(); 261 262 boolean confirmOverwriteValue = ((BooleanToken) confirmOverwrite 263 .getToken()).booleanValue(); 264 // Don't ask for confirmation in append mode, since there 265 // will be no loss of data. 266 if (_svgOutFile.exists() && confirmOverwriteValue) { 267 // Query for overwrite. 268 // FIXME: This should be called in the event thread! 269 // There is a chance of deadlock since it is not. 270 if (!GraphicalMessageHandler.yesNoQuestion("OK to overwrite " 271 + _svgOutFile + "?")) { 272 throw new IllegalActionException(this, 273 "Please select another file name."); 274 } 275 } 276 277 // get point. 278 ArrayToken arrayToken = (ArrayToken) point.get(0); 279 xVal = ((DoubleToken) arrayToken.getElement(0)).doubleValue(); 280 yVal = ((DoubleToken) arrayToken.getElement(1)).doubleValue(); 281 282 // get conversion factors. 283 xRatio = ""; 284 yRatio = ""; // reset factors first. 285 _getConversionFactors(); 286 287 // calculate parse tree for ratios. 288 PtParser parser = new PtParser(); 289 _parseTreeEvaluator = new ParseTreeEvaluator(); 290 _scope = new VariableScope(); 291 292 // translate point. 293 ASTPtRootNode _parseTree = null; 294 295 if (!xRatio.equals("")) { 296 _parseTree = parser.generateParseTree(xRatio); 297 param = xVal; 298 xVal = _ratioConvert(_parseTree); 299 } 300 301 if (!yRatio.equals("")) { 302 _parseTree = parser.generateParseTree(yRatio); 303 param = yVal; 304 yVal = _ratioConvert(_parseTree); 305 } 306 307 // add the point to the svg file. 308 _addPointToSVG(); 309 310 // broadcast SVG output file URL. 311 output.broadcast(new StringToken(_svgOutFile.getAbsolutePath())); 312 } 313 314 // ///////////////////////////////////////////////////////////////// 315 // // private methods //// 316 317 /** 318 * Add the point to the SVG file. 319 */ 320 private void _addPointToSVG() { 321 try { 322 BufferedReader br = new BufferedReader(new FileReader(_svgFile)); 323 StringBuffer svgContent = new StringBuffer(); 324 String line; 325 // _svgContent = new StringBuffer(); 326 String extraLine = "<circle cx='" + xVal + "' cy='" + yVal 327 + "' r='2' fill='red' stroke='red'/>"; // TODO: ADD VARIABLE 328 // RADIUS.. 329 // System.out.println("Extra line" + extraLine); 330 while ((line = br.readLine()) != null) { 331 int ind = line.toLowerCase().indexOf("</svg>"); 332 if (ind != -1) { 333 // System.out.println("Inside extra line"); 334 svgContent.append(line.substring(0, ind) + "\n"); 335 svgContent.append(extraLine + "\n"); 336 svgContent.append(line.substring(ind) + "\n"); 337 } else 338 svgContent.append(line + "\n"); 339 } 340 br.close(); 341 BufferedWriter out = new BufferedWriter(new FileWriter(_svgOutFile)); 342 out.write(svgContent.toString()); 343 out.flush(); 344 out.close(); 345 } catch (IOException e) { 346 GraphicalMessageHandler.error("Error opening file", e); 347 } 348 } 349 350 /** 351 * Get the xRatio, yRatio conversion factors, if available, from the SVG 352 * file. 353 */ 354 private void _getConversionFactors() throws IllegalActionException { 355 try { 356 DocumentBuilderFactory factory = DocumentBuilderFactory 357 .newInstance(); 358 factory.setValidating(false); 359 DocumentBuilder builder = factory.newDocumentBuilder(); 360 InputStream is = new FileInputStream(_svgFile); 361 Document doc = builder.parse(is); 362 363 NodeList nodes = doc.getElementsByTagName("conversion"); 364 for (int i = 0; i < nodes.getLength(); i++) { 365 String _id = ((Element) nodes.item(i)).getAttribute("id"); 366 String value = nodes.item(i).getFirstChild().getNodeValue(); 367 // String value = ( (Element) nodes.item(i)).getAttribute(_id); 368 if (_id.equals("xRatio")) 369 xRatio = value; 370 else if (_id.equals("yRatio")) 371 yRatio = value; 372 } 373 } catch (Exception ex) { 374 throw new IllegalActionException(this, "Error parsing SVG file " 375 + _svgFile); 376 } 377 } 378 379 /** 380 * Use the conversion ratio to calculate a coordinate value. 381 * 382 * @param _parseTree 383 * * @throws IllegalActionException 384 */ 385 private double _ratioConvert(ASTPtRootNode _parseTree) 386 throws IllegalActionException { 387 DoubleToken ratioToken = (DoubleToken) _parseTreeEvaluator 388 .evaluateParseTree(_parseTree, _scope); 389 if (ratioToken == null) { 390 throw new IllegalActionException(this, 391 "Expression yields a null result."); 392 } 393 return ratioToken.doubleValue(); 394 } 395 396 private class VariableScope extends ModelScope { 397 398 /** 399 * Look up and return the attribute with the specified name in the 400 * scope. Return null if such an attribute does not exist. 401 * 402 * @return The attribute with the specified name in the scope. 403 */ 404 public Token get(String name) throws IllegalActionException { 405 if (name.equals("val")) { 406 return new DoubleToken(param); 407 } else 408 return null; 409 } 410 411 /** 412 * Look up and return the type of the attribute with the specified name 413 * in the scope. Return null if such an attribute does not exist. 414 * 415 * @return The attribute with the specified name in the scope. 416 */ 417 public Type getType(String name) throws IllegalActionException { 418 if (name.equals("val")) { 419 return BaseType.DOUBLE; 420 } else 421 return null; 422 } 423 424 /** 425 * Look up and return the type term for the specified name in the scope. 426 * Return null if the name is not defined in this scope, or is a 427 * constant type. 428 * 429 * @return The InequalityTerm associated with the given name in the 430 * scope. 431 * @exception IllegalActionException 432 * If a value in the scope exists with the given name, 433 * but cannot be evaluated. 434 */ 435 public ptolemy.graph.InequalityTerm getTypeTerm(String name) 436 throws IllegalActionException { 437 if (name.equals("val")) { 438 return new TypeConstant(BaseType.DOUBLE); 439 } else 440 return null; 441 } 442 443 /** 444 * Return the list of identifiers within the scope. 445 * 446 * @return The list of identifiers within the scope. 447 */ 448 public Set identifierSet() { 449 return getAllScopedVariableNames(null, AddPointToSVG.this); 450 } 451 } 452 453 // ///////////////////////////////////////////////////////////////// 454 // // private members //// 455 456 /** Previous value of fileOrURL parameter. */ 457 private String _previousFileOrURL; 458 private File _svgFile, _svgOutFile; 459 private String xRatio = "", yRatio = ""; 460 private double param, xVal = 0.0, yVal = 0.0; 461 // private ASTPtRootNode _parseTree = null; 462 private ParseTreeEvaluator _parseTreeEvaluator = null; 463 private VariableScope _scope = null; 464}