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}