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.net.URI;
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.expr.FileParameter;
056import ptolemy.data.expr.Parameter;
057import ptolemy.data.type.ArrayType;
058import ptolemy.data.type.BaseType;
059import ptolemy.gui.GraphicalMessageHandler;
060import ptolemy.kernel.CompositeEntity;
061import ptolemy.kernel.attributes.URIAttribute;
062import ptolemy.kernel.util.IllegalActionException;
063import ptolemy.kernel.util.NameDuplicationException;
064
065//////////////////////////////////////////////////////////////////////////
066//// AddPointToSVG
067/**
068 * Concatanate several SVG files to a single file. Typical Usage is to create
069 * output displays. The actor accepts the input SVG paths as an array of
070 * strings, and x,y shift values for positioning each of the files and returns
071 * the path of the concatenated output file.
072 * 
073 * @author Efrat Jaeger
074 * @version $Id: SVGConcat.java 24234 2010-05-06 05:21:26Z welker $
075 * @since Ptolemy II 3.0.2
076 */
077public class SVGConcat extends TypedAtomicActor {
078
079        /**
080         * Construct an actor with the given container and name.
081         * 
082         * @param container
083         *            The container.
084         * @param name
085         *            The name of this actor.
086         * @exception IllegalActionException
087         *                If the actor cannot be contained by the proposed
088         *                container.
089         * @exception NameDuplicationException
090         *                If the container already has an actor with this name.
091         */
092        public SVGConcat(CompositeEntity container, String name)
093                        throws IllegalActionException, NameDuplicationException {
094                super(container, name);
095
096                svgFiles = new TypedIOPort(this, "svgFiles", true, false);
097                svgFiles.setTypeEquals(new ArrayType(BaseType.STRING));
098
099                outputPathPort = new TypedIOPort(this, "outputPath", true, false);
100                outputPathPort.setTypeEquals(BaseType.STRING);
101
102                output = new TypedIOPort(this, "output", false, true);
103                output.setTypeEquals(BaseType.STRING);
104
105                outputPathParam = new FileParameter(this, "outputPathParam");
106                outputPathParam.setDisplayName("SVG output file");
107
108                shiftX = new Parameter(this, "shiftX");
109                shiftY = new Parameter(this, "shiftY");
110                shiftX.setExpression("0.0");
111                shiftY.setExpression("0.0");
112
113                // trigger = new TypedIOPort(this, "trigger", true, false);
114                // trigger.setMultiport(true);
115
116                confirmOverwrite = new Parameter(this, "confirmOverwrite");
117                confirmOverwrite.setTypeEquals(BaseType.BOOLEAN);
118                confirmOverwrite.setToken(BooleanToken.TRUE);
119
120                _attachText("_iconDescription", "<svg>\n"
121                                + "<rect x=\"-25\" y=\"-20\" " + "width=\"50\" height=\"40\" "
122                                + "style=\"fill:white\"/>\n"
123                                + "<polygon points=\"-15,-10 -12,-10 -8,-14 -1,-14 3,-10"
124                                + " 15,-10 15,10, -15,10\" " + "style=\"fill:red\"/>\n"
125                                + "</svg>\n");
126        }
127
128        // /////////////////////////////////////////////////////////////////
129        // // ports and parameters ////
130
131        /**
132         * Array of SVG input files.
133         */
134        public TypedIOPort svgFiles;
135
136        /**
137         * An SVG output file URL port.
138         */
139        public TypedIOPort outputPathPort;
140
141        /**
142         * Output SVG output file URL.
143         */
144        public TypedIOPort output;
145
146        /**
147         * Trigger actor execution.
148         */
149        // public TypedIOPort trigger;
150        /**
151         * An SVG output file name or URL. This is a string with any form accepted
152         * by FileParameter.
153         * 
154         * @see FileParameter
155         */
156        public FileParameter outputPathParam;
157
158        /**
159         * Relative shift over the X coordinate during concatenation.
160         */
161        public Parameter shiftX;
162
163        /**
164         * Relative shift over the Y coordinate during concatenation.
165         */
166        public Parameter shiftY;
167
168        /**
169         * If <i>false</i>, then overwrite the specified file if it exists without
170         * asking. If <i>true</i> (the default), then if the file exists, ask for
171         * confirmation before overwriting.
172         */
173        public Parameter confirmOverwrite;
174
175        /**
176         * Translate the point into the svg ratio parameters and add it to the file.
177         * Broadcasts the URL to the svg output file.
178         * 
179         * @exception IllegalActionException
180         *                If there's no director.
181         */
182        public void fire() throws IllegalActionException {
183
184                // trigger the actor.
185                /*
186                 * for (int i = 0; i < trigger.getWidth(); i++) { if
187                 * (trigger.hasToken(i)) { trigger.get(i); } }
188                 */
189
190                _svgFile = null;
191                _svgOutFile = null;
192                String fileName = "";
193                // get SVG output file.
194                if (outputPathPort.getWidth() > 0) {
195                        fileName = ((StringToken) outputPathPort.get(0)).stringValue();
196                        int lineEndInd = fileName.indexOf("\n");
197                        if (lineEndInd != -1) { // The string contains a CR.
198                                fileName = fileName.substring(0, lineEndInd);
199                        }
200                        outputPathParam.setExpression(fileName);
201                }
202                _svgOutFile = outputPathParam.asFile();
203
204                boolean confirmOverwriteValue = ((BooleanToken) confirmOverwrite
205                                .getToken()).booleanValue();
206                // Don't ask for confirmation in append mode, since there
207                // will be no loss of data.
208                if (_svgOutFile.exists() && confirmOverwriteValue) {
209                        // Query for overwrite.
210                        // FIXME: This should be called in the event thread!
211                        // There is a chance of deadlock since it is not.
212                        if (!GraphicalMessageHandler.yesNoQuestion("OK to overwrite "
213                                        + _svgOutFile + "?")) {
214                                throw new IllegalActionException(this,
215                                                "Please select another file name.");
216                        }
217                }
218                double _shiftX = 0.0, _shiftY = 0.0;
219                try {
220                        _shiftX = ((DoubleToken) shiftX.getToken()).doubleValue();
221                } catch (Exception ex) {
222                        _shiftX = 0.0;
223                }
224                try {
225                        _shiftY = ((DoubleToken) shiftY.getToken()).doubleValue();
226                } catch (Exception ex) {
227                        _shiftY = 0.0;
228                }
229
230                // FIXME: Currently assuming all files have the same header
231                // (except for width and height) and the same scripts
232                // and that tags begin with a new line.
233                String line;
234                _maxWidth = 0;
235                _maxHeight = 0;
236                StringBuffer _sbheader = new StringBuffer();
237                StringBuffer _sbmicro = new StringBuffer();
238                StringBuffer _sbcontent = new StringBuffer();
239                // Get SVG input array.
240                ArrayToken arrayToken = (ArrayToken) svgFiles.get(0);
241                for (int i = 0; i < arrayToken.length(); i++) {
242                        String svgFilePath = ((StringToken) arrayToken.getElement(i))
243                                        .stringValue();
244                        _svgFile = new File(svgFilePath);
245                        if (!_svgFile.isAbsolute()) {
246                                // Try to resolve the base directory.
247                                URI modelURI = URIAttribute.getModelURI(this);
248                                if (modelURI != null) {
249                                        URI newURI = modelURI.resolve(svgFilePath);
250                                        _svgFile = new File(newURI);
251                                }
252                        }
253
254                        try {
255                                BufferedReader br = new BufferedReader(new FileReader(_svgFile));
256                                if (i == 0) {// get header.
257                                        while ((line = br.readLine()) != null) {
258                                                int ind = line.toLowerCase().indexOf("width");
259                                                if (ind != -1) {
260                                                        break;
261                                                } else {
262                                                        _sbheader.append(line + "\n");
263                                                }
264                                        }
265                                        // _widthHeightLine = line;
266
267                                        while ((line = br.readLine()) != null) {
268                                                int ind = line.toLowerCase().indexOf("</script>");
269                                                if (ind != -1) {
270                                                        _sbmicro.append(line + "\n");
271                                                        break;
272                                                } else {
273                                                        _sbmicro.append(line + "\n");
274                                                }
275                                        }
276                                } else { // skip to after script.
277                                        while ((line = br.readLine()) != null) {
278                                                int ind = line.toLowerCase().indexOf("</script>");
279                                                if (ind != -1) {
280                                                        break;
281                                                }
282                                        }
283                                }
284
285                                String transform = "<g transform=\"translate(";
286                                if (_shiftX > 0)
287                                        transform += _maxWidth;
288                                else
289                                        transform += "0.0";
290                                transform += ",";
291                                if (_shiftY > 0)
292                                        transform += _maxHeight;
293                                else
294                                        transform += "0.0";
295                                transform += ")\">";
296                                _sbcontent.append(transform);
297
298                                while ((line = br.readLine()) != null) {
299                                        int ind = line.toLowerCase().indexOf("</svg>");
300                                        if (ind != -1) {
301                                                // System.out.println("Inside extra line");
302                                                _sbcontent.append(line.substring(0, ind) + "\n");
303                                                _sbcontent.append("</g>\n");
304                                                break;
305                                        } else
306                                                _sbcontent.append(line + "\n");
307                                }
308                                br.close();
309
310                                _getWidthHeight();
311                                if (_shiftX > 0)
312                                        _maxWidth = _maxWidth + _tempWidth + _shiftX;
313                                else
314                                        _maxWidth = (_maxWidth > _tempWidth) ? _maxWidth
315                                                        : _tempWidth;
316                                if (_shiftY > 0)
317                                        _maxHeight = _maxHeight + _tempHeight + _shiftY;
318                                else
319                                        _maxHeight = (_maxHeight > _tempHeight) ? _maxHeight
320                                                        : _tempHeight;
321
322                        } catch (Exception ex) {
323                                throw new IllegalActionException(this, ex, "Error parsing "
324                                                + _svgFile + " in actor " + this.getName());
325                        }
326                }
327                _sbcontent.append("</svg>");
328                _sbheader.append("     width='" + _maxWidth);
329                _sbheader.append("' height='" + _maxHeight + "'>\n");
330
331                try {
332                        BufferedWriter out = new BufferedWriter(new FileWriter(_svgOutFile));
333                        out.write(_sbheader.toString());
334                        out.write(_sbmicro.toString());
335                        out.write(_sbcontent.toString());
336                        out.flush();
337                        out.close();
338                } catch (IOException ex) {
339                        throw new IllegalActionException(this, ex, "Error writing to file "
340                                        + _svgOutFile);
341                }
342
343                // broadcast SVG output file URL.
344                output.broadcast(new StringToken(_svgOutFile.getAbsolutePath()));
345        }
346
347        // /////////////////////////////////////////////////////////////////
348        // // private methods ////
349
350        private void _getWidthHeight() throws IllegalActionException {
351                try {
352                        DocumentBuilderFactory factory = DocumentBuilderFactory
353                                        .newInstance();
354                        factory.setValidating(false);
355                        DocumentBuilder builder = factory.newDocumentBuilder();
356                        InputStream is = new FileInputStream(_svgFile);
357                        Document doc = builder.parse(is);
358
359                        NodeList nodes = doc.getElementsByTagName("svg");
360                        for (int i = 0; i < nodes.getLength(); i++) {
361                                String attr = ((Element) nodes.item(i)).getAttribute("width");
362                                _tempWidth = Double.parseDouble(attr);
363                                attr = ((Element) nodes.item(i)).getAttribute("height");
364                                _tempHeight = Double.parseDouble(attr);
365                        }
366                } catch (Exception ex) {
367                        throw new IllegalActionException(this, "Error parsing SVG file "
368                                        + _svgFile);
369                }
370        }
371
372        // /////////////////////////////////////////////////////////////////
373        // // private members ////
374
375        /** Previous value of fileOrURL parameter. */
376        private File _svgFile, _svgOutFile;
377        private double param, xVal = 0.0, yVal = 0.0;
378        private double _tempWidth, _tempHeight, _maxWidth, _maxHeight;
379        private String _widthHeightLine;
380}