001/* A representative of a text file contained in an external text editor.
002
003 Copyright (c) 1998-2014 The Regents of the University of California and
004 Research in Motion Limited.
005 All rights reserved.
006 Permission is hereby granted, without written agreement and without
007 license or royalty fees, to use, copy, modify, and distribute this
008 software and its documentation for any purpose, provided that the above
009 copyright notice and the following two paragraphs appear in all copies
010 of this software.
011
012 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA OR RESEARCH IN MOTION
013 LIMITED BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL,
014 INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS
015 SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA
016 OR RESEARCH IN MOTION LIMITED HAVE BEEN ADVISED OF THE POSSIBILITY OF
017 SUCH DAMAGE.
018
019 THE UNIVERSITY OF CALIFORNIA AND RESEARCH IN MOTION LIMITED
020 SPECIFICALLY DISCLAIM ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
021 THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
022 PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS"
023 BASIS, AND THE UNIVERSITY OF CALIFORNIA AND RESEARCH IN MOTION
024 LIMITED HAVE NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
025 ENHANCEMENTS, OR MODIFICATIONS.
026
027 PT_COPYRIGHT_VERSION_2
028 COPYRIGHTENDKEY
029
030 */
031package ptolemy.actor.gui;
032
033import java.io.File;
034import java.io.FileWriter;
035import java.net.MalformedURLException;
036import java.net.URL;
037
038import ptolemy.kernel.CompositeEntity;
039import ptolemy.kernel.util.Attribute;
040import ptolemy.kernel.util.IllegalActionException;
041import ptolemy.kernel.util.InternalErrorException;
042import ptolemy.kernel.util.NameDuplicationException;
043import ptolemy.kernel.util.Workspace;
044
045///////////////////////////////////////////////////////////////////
046//// ExternalTextEffigy
047
048/**
049 An external EDITOR-based effigy for a text file (see {@link
050 ExternalTextTableau}).
051
052 @author Zoltan Kemenczy, Research in Motion Limited
053 @version $Id$
054 @since Ptolemy II 2.2
055 @Pt.ProposedRating Red (neuendor)
056 @Pt.AcceptedRating Red (neuendor)
057 */
058public class ExternalTextEffigy extends TextEffigy {
059    /** Create a new effigy in the specified workspace with an empty string
060     *  for its name.
061     *  @param workspace The workspace for this effigy.
062     */
063    public ExternalTextEffigy(Workspace workspace) {
064        super(workspace);
065    }
066
067    /** Create a new effigy in the given directory with the given name.
068     *  @param container The directory that contains this effigy.
069     *  @param name The name of this effigy.
070     *  @exception IllegalActionException If the entity cannot be contained
071     *   by the proposed container.
072     *  @exception NameDuplicationException If the name coincides with
073     *   an entity already in the container.
074     */
075    public ExternalTextEffigy(CompositeEntity container, String name)
076            throws IllegalActionException, NameDuplicationException {
077        super(container, name);
078    }
079
080    ///////////////////////////////////////////////////////////////////
081    ////                         public methods                    ////
082
083    /** If the argument is the <i>identifier</i> parameter, then tell
084     *  the external editor to finally open the file specified by the
085     *  identifier (as opposed to at newTextEffigy(container, text) time
086     *  at which the document file is not yet specified. This greatly
087     *  simplifies the interaction with the external text editor: instead
088     *  of first telling it to create a text buffer with some name and
089     *  no file attached, the buffer, its associated file name, and any
090     *  text saved by newTextEffigy(container, text) is given to the
091     *  text editor in one transaction. NOTE: This depends on
092     *  TextEditorTableau.createTableau(effigy) setting the identifier
093     *  expression after newTextEffigy(container, text).
094     *  @param attribute The attribute that changed.
095     *  @exception IllegalActionException If the base class throws it.
096     */
097    @Override
098    public void attributeChanged(Attribute attribute)
099            throws IllegalActionException {
100        // Let Effigy handle it first
101        super.attributeChanged(attribute);
102
103        // Now do the external-text-specific stuff
104        if (attribute == identifier) {
105            URL url;
106
107            try {
108                url = new URL(identifier.getExpression());
109
110                File file = new File(url.getFile());
111                String path = file.getAbsolutePath().replace('\\', '/');
112                showContent(path);
113            } catch (MalformedURLException ex) {
114                // just ignore. This happens when the tableau sets
115                // "Unnamed" arbitrarily
116            }
117        }
118    }
119
120    /** Create a new effigy in the given container containing the specified
121     *  text.  The new effigy will have a new instance of
122     *  DefaultStyledDocument associated with it.
123     *  @param container The container for the effigy.
124     *  @param text The text to insert in the effigy.
125     *  @return A new instance of TextEffigy.
126     *  @exception Exception If the text effigy cannot be
127     *   contained by the specified container, or if the specified
128     *   text cannot be inserted into the document.
129     */
130    public static TextEffigy newTextEffigy(CompositeEntity container,
131            String text) throws Exception {
132        // Create a new effigy.
133        ExternalTextEffigy effigy = new ExternalTextEffigy(container,
134                container.uniqueName("effigy"));
135
136        // Cheat: we'll get the text off the container at
137        // show(Content)-time. This get's around the problem of stale
138        // text after the model is updated and answers YES to the
139        // question regarding moml viewing in
140        // TextEditorTableau.createTableau()...
141        effigy.setUseContainerMoML(true);
142        return effigy;
143    }
144
145    /** Create a new ExternalTextEffigy.
146     *  @param container The container for the effigy.
147     *  @param base The base for relative file references, or null if
148     *   there are no relative file references.  This is ignored in this
149     *   class.
150     *  @param in The input URL, or null if there is none.
151     *  @return A new instance of TextEffigy.
152     *  @exception Exception If the URL cannot be read, or if the data
153     *   is malformed in some way.
154     */
155    public static TextEffigy newTextEffigy(CompositeEntity container, URL base,
156            URL in) throws Exception {
157        ExternalTextEffigy effigy = new ExternalTextEffigy(container,
158                container.uniqueName("effigy"));
159
160        // A URL has been given.  Read it.
161        // Note: Here the text editor would be given the in URL to
162        // open. However, to simplify the interaction with the external
163        // text editor, the handling of 1) opening existing text
164        // URLs, and 2) effigies of moml files where we want the current
165        // moml content of an already open PtolemyEffigy (its moml file
166        // is not read), the opening of the file is delayed until its
167        // identifier attribute is updated.
168        return effigy;
169    }
170
171    /** Pass the modifiable flag onto the external text editor. */
172    @Override
173    public void setModifiable(boolean flag) {
174        super.setModifiable(flag);
175    }
176
177    /** Signal the external text editor to (re)display its buffer
178     associated with this effigy. */
179    public void show() {
180        showContent(_pathName);
181    }
182
183    ///////////////////////////////////////////////////////////////////
184    ////                         private methods                   ////
185    // Set private useContainerMoML attribute
186    private void setUseContainerMoML(boolean useContainerMoML) {
187        _useContainerMoML = useContainerMoML;
188    }
189
190    private void showContent(String path) {
191        try {
192            File tmpFile = null;
193            String todo;
194
195            if (_useContainerMoML) {
196                // Open the file from storage, erase the buffer, then set
197                // the current content from the MoML content of the
198                // container
199                String text = ((PtolemyEffigy) getContainer()).getModel()
200                        .exportMoML();
201                tmpFile = File.createTempFile("effigy", "");
202
203                String tmpFilePathName = tmpFile.getAbsolutePath().replace('\\',
204                        '/');
205                FileWriter writer = null;
206
207                try {
208                    writer = new FileWriter(tmpFile);
209                    writer.write(text);
210                } finally {
211                    if (writer != null) {
212                        writer.close();
213                    }
214                }
215
216                todo = "gnudoit (find-file (symbol-name '" + path + "))"
217                        + "(setq buffer-read-only nil)" + "(erase-buffer)"
218                        + "(insert-file-contents " + "    (symbol-name '"
219                        + tmpFilePathName + "))" + "(set-buffer-modified-p nil)"
220                        + "(setq buffer-read-only t)" + "(buffer-name)";
221            } else {
222                // Reading file content from storage
223                todo = "gnudoit (find-file (symbol-name '" + path + "))"
224                        + "(buffer-name)";
225            }
226
227            Process process = Runtime.getRuntime().exec(todo);
228
229            // After many simplifcations, at this point, _bufferName is
230            // not really needed anymore, but I'll keep its code as a
231            // comment of how to read gnudoit results back from emacs
232            // (An optimization would first query emacs for a buffer
233            // named by _bufferName content and then simply switch to
234            // that buffer as opposed to always re-creating it.)
235            //- BufferedInputStream result =
236            //-    new BufferedInputStream(process.getInputStream());
237            process.waitFor();
238
239            //- byte[] buffer = new byte[result.available()];
240            //- result.read(buffer, 0, buffer.length);
241            // Delete any linefeeds and carriage returns.
242            //- int i = buffer.length -1;
243            //- for (; buffer[i] == '\r' || buffer[i] == '\n'; i--);
244            //- _bufferName = new String(buffer, 0, i + 1);
245            _pathName = path;
246
247            if (tmpFile != null) {
248                if (!tmpFile.delete()) {
249                    throw new InternalErrorException(
250                            "Failed to delete \"" + tmpFile + "\"?");
251                }
252            }
253        } catch (Throwable throwable) {
254            throw new RuntimeException(getFullName(), throwable);
255        }
256    }
257
258    ///////////////////////////////////////////////////////////////////
259    ////                         private members                   ////
260    private String _pathName;
261
262    private boolean _useContainerMoML;
263}