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}