001/* A tableau representing a text window. 002 003 Copyright (c) 2000-2016 The Regents of the University of California. 004 All rights reserved. 005 Permission is hereby granted, without written agreement and without 006 license or royalty fees, to use, copy, modify, and distribute this 007 software and its documentation for any purpose, provided that the above 008 copyright notice and the following two paragraphs appear in all copies 009 of this software. 010 011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 015 SUCH DAMAGE. 016 017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 022 ENHANCEMENTS, OR MODIFICATIONS. 023 024 PT_COPYRIGHT_VERSION_2 025 COPYRIGHTENDKEY 026 */ 027package ptolemy.actor.gui; 028 029import java.lang.reflect.Constructor; 030import java.lang.reflect.Method; 031import java.net.URL; 032import java.util.Iterator; 033 034import javax.swing.text.Document; 035 036import ptolemy.kernel.CompositeEntity; 037import ptolemy.kernel.util.IllegalActionException; 038import ptolemy.kernel.util.NameDuplicationException; 039import ptolemy.kernel.util.NamedObj; 040import ptolemy.kernel.util.StringAttribute; 041 042/////////////////////////////////////////////////////////////////// 043//// TextEditorTableau 044 045/** 046 A tableau representing a text window. The constructor of this 047 class creates the window. The text window itself is an instance 048 of TextEditor, and can be accessed using the getFrame() method. 049 As with other tableaux, this is an entity that is contained by 050 an effigy of a model. 051 There can be any number of instances of this class in an effigy. 052 053 @author Steve Neuendorffer and Edward A. Lee 054 @version $Id$ 055 @since Ptolemy II 1.0 056 @Pt.ProposedRating Yellow (eal) 057 @Pt.AcceptedRating Red (cxh) 058 @see Effigy 059 */ 060public class TextEditorTableau extends Tableau { 061 /** Construct a new tableau for the model represented by the given effigy. 062 * @param container The container. 063 * @param name The name. 064 * @exception IllegalActionException If the container does not accept 065 * this entity (this should not occur). 066 * @exception NameDuplicationException If the name coincides with an 067 * attribute already in the container. 068 */ 069 public TextEditorTableau(TextEffigy container, String name) 070 throws IllegalActionException, NameDuplicationException { 071 this(container, name, null); 072 } 073 074 /** Construct a new tableau for the model represented by the given effigy. 075 * @param container The container. 076 * @param name The name. 077 * @param editor The text editor to use, or null to use the default. 078 * @exception IllegalActionException If the container does not accept 079 * this entity (this should not occur). 080 * @exception NameDuplicationException If the name coincides with an 081 * attribute already in the container. 082 */ 083 public TextEditorTableau(TextEffigy container, String name, 084 TextEditor editor) 085 throws IllegalActionException, NameDuplicationException { 086 super(container, name); 087 088 String title = "Unnamed"; 089 TextEditor frame = editor; 090 091 if (frame == null) { 092 frame = new TextEditor(title, container.getDocument()); 093 // Set the title for the Kepler R actor 094 // http://bugzilla.ecoinformatics.org/show_bug.cgi?id=3187 095 setTitle(frame.getTitle()); 096 } 097 098 frame.text.setColumns(80); 099 frame.text.setRows(40); 100 setFrame(frame); 101 frame.setTableau(this); 102 103 // https://chess.eecs.berkeley.edu/bugzilla/show_bug.cgi?id=67 says: 104 // "Create a new model and drag in a Const actor. If you close 105 // the model at this time, vergil prompts to save the model 106 // since there is unsaved modification. However, if you first 107 // do View -> XML View, and then close the model, no prompt 108 // shows up even if the model should be dirty." 109 // So, we don't want to mark the frame as unmodified. 110 // 111 // The above will mark the text object modified. Reverse this. 112 //frame.setModified(false); 113 } 114 115 /////////////////////////////////////////////////////////////////// 116 //// public methods //// 117 118 /** Make the tableau editable or uneditable. Notice that this does 119 * not change whether the effigy is modifiable, so other tableaux 120 * on the same effigy may still modify the associated file. 121 * @param flag False to make the tableau uneditable. 122 */ 123 @Override 124 public void setEditable(boolean flag) { 125 super.setEditable(flag); 126 127 TextEditor editor = (TextEditor) getFrame(); 128 129 if (editor.text != null) { 130 editor.text.setEditable(flag); 131 } 132 } 133 134 /** Make this tableau visible by calling setVisible(true), and 135 * raising or deiconifying its window. 136 * If no frame has been set, then do nothing. 137 */ 138 @Override 139 public void show() { 140 super.show(); 141 TextEditor editor = (TextEditor) getFrame(); 142 editor.adjustFileMenu(); 143 } 144 145 /////////////////////////////////////////////////////////////////// 146 //// inner classes //// 147 148 /** A factory that creates text editor tableaux for Ptolemy models. 149 */ 150 public static class Factory extends TableauFactory { 151 /** Create a factory with the given name and container. 152 * @param container The container entity. 153 * @param name The name of the entity. 154 * @exception IllegalActionException If the container is incompatible 155 * with this attribute. 156 * @exception NameDuplicationException If the name coincides with 157 * an attribute already in the container. 158 */ 159 public Factory(NamedObj container, String name) 160 throws IllegalActionException, NameDuplicationException { 161 super(container, name); 162 163 String editorPreference = "."; 164 165 try { 166 editorPreference = System.getProperty("ptolemy.user.texteditor", 167 "."); 168 } catch (SecurityException security) { 169 // Ignore, we are probably running in a sandbox or as 170 // an applet 171 } 172 173 syntaxStyle = new StringAttribute(this, "syntaxStyle"); 174 175 Class tableauClass; 176 Class effigyClass; 177 178 try { 179 if (editorPreference.equals("emacs")) { 180 tableauClass = Class 181 .forName("ptolemy.actor.gui.ExternalTextTableau"); 182 effigyClass = Class 183 .forName("ptolemy.actor.gui.ExternalTextEffigy"); 184 } else { 185 tableauClass = Class 186 .forName("ptolemy.actor.gui.TextEditorTableau"); 187 effigyClass = Class.forName("ptolemy.actor.gui.TextEffigy"); 188 } 189 190 _tableauConstructor = tableauClass.getConstructor(new Class[] { 191 TextEffigy.class, String.class, TextEditor.class }); 192 _newTextEffigyText = effigyClass.getMethod("newTextEffigy", 193 new Class[] { CompositeEntity.class, String.class, 194 String.class }); 195 _newTextEffigyURL = effigyClass.getMethod("newTextEffigy", 196 new Class[] { CompositeEntity.class, URL.class, 197 URL.class }); 198 } catch (ClassNotFoundException ex) { 199 throw new IllegalActionException(ex.toString()); 200 } catch (NoSuchMethodException ex) { 201 throw new IllegalActionException(ex.toString()); 202 } 203 } 204 205 /** Create a factory with the given name and container and syntax style. 206 * @param container The container entity. 207 * @param name The name of the entity. 208 * @param style The syntax style. 209 * @exception IllegalActionException If the container is incompatible 210 * with this attribute. 211 * @exception NameDuplicationException If the name coincides with 212 * an attribute already in the container. 213 */ 214 public Factory(NamedObj container, String name, String style) 215 throws IllegalActionException, NameDuplicationException { 216 this(container, name); 217 syntaxStyle.setExpression(style); 218 } 219 220 /////////////////////////////////////////////////////////////////// 221 //// parameters //// 222 223 /** The style of the text to be edited. This may or may not be 224 * supported. If the package "org.fife.ui.rsyntaxtextarea" is found in 225 * the classpath, then the supported styles include 226 * "text/plain", "text/c", "text/clojure", "text/cpp", "text/cs", 227 * "text/css", "text/dtd", "text/fortran", 228 * "text/groovy", "text/html", "text/java", 229 * "text/javascript", "text/json", "text/jsp", 230 * "text/latex", "text/makefile", 231 * "text/perl", "text/php", 232 * "text/properties", "text/python", "text/ruby", "text/sas", 233 * "text/scala", "text/sql", "text/tcl", "text/unix", "text/vb", 234 * "text/bat", and "text/xml". 235 */ 236 public StringAttribute syntaxStyle; 237 238 /////////////////////////////////////////////////////////////////// 239 //// public methods //// 240 241 /** If the specified effigy is a TextEffigy and it 242 * already contains a tableau named 243 * "textTableau", then return that tableau; otherwise, create 244 * a new instance of TextEditorTableau in the specified 245 * effigy, and name it "textTableau" and return that tableau. 246 * If the specified effigy is not an instance of TextEffigy, 247 * but contains an instance of TextEffigy, then open a tableau 248 * for that effigy. If it is a PtolemyEffigy, then create a 249 * text effigy with the MoML representation of the model. 250 * Finally, if is not a TextEffigy or a PtolemyEffigy, 251 * and it does not contain a TextEffigy, then attempt to 252 * open its URL and display its date by creating a text effigy, 253 * which will then be contained by the specified effigy. If all 254 * of this fails, then do not create a tableau and return null. 255 * It is the responsibility of callers of this method to check the 256 * return value and call show(). 257 * 258 * @param effigy The effigy. 259 * @return A text editor tableau, or null if one cannot be 260 * found or created. 261 * @exception Exception If the factory should be able to create a 262 * tableau for the effigy, but something goes wrong. 263 */ 264 @Override 265 public Tableau createTableau(Effigy effigy) throws Exception { 266 if (effigy instanceof TextEffigy) { 267 // First see whether the effigy already contains a 268 // TextEditorTableau with the appropriate name. 269 TextEditorTableau tableau = (TextEditorTableau) effigy 270 .getEntity("textTableau"); 271 272 if (tableau == null) { 273 TextEditor editor = null; 274 String style = syntaxStyle.getExpression(); 275 if (style == null || style.trim().equals("")) { 276 style = ((TextEffigy) effigy).getSyntaxStyle(); 277 } 278 if (style != null && !style.trim().equals("")) { 279 // Attempt to specify a syntax-aware text editor. 280 try { 281 Class editorClass = Class.forName( 282 "ptolemy.actor.gui.syntax.SyntaxTextEditor"); 283 Constructor constructor = editorClass 284 .getConstructor(new Class[] { String.class, 285 Document.class }); 286 editor = (TextEditor) constructor 287 .newInstance(new Object[] { "Unnamed", 288 ((TextEffigy) effigy) 289 .getDocument() }); 290 } catch (Throwable ex) { 291 // Ignore and use default text editor. 292 System.out.println( 293 "Note: failed to open syntax-directed editor: " 294 + ex.getMessage()); 295 } 296 } 297 tableau = (TextEditorTableau) _tableauConstructor 298 .newInstance(new Object[] { effigy, "textTableau", 299 editor }); 300 } 301 302 URL url = effigy.uri.getURL(); 303 if (url != null) { 304 // Set the identifier so that if we start vergil and 305 // do File -> New -> Text Editor, type some text, Save 306 // then the title changes from Unnames to the name of the file. 307 effigy.identifier.setExpression(url.toExternalForm()); 308 } 309 310 tableau.setEditable(effigy.isModifiable()); 311 return tableau; 312 } else { 313 // The effigy is not an instance of TextEffigy. 314 // See whether it contains an instance of TextEffigy 315 // named "textEffigy", and if it does return that instance. 316 Iterator effigies = effigy.entityList(TextEffigy.class) 317 .iterator(); 318 319 while (effigies.hasNext()) { 320 TextEffigy textEffigy = (TextEffigy) effigies.next(); 321 322 if (textEffigy.getName().equals("textEffigy")) { 323 return createTableau(textEffigy); 324 } 325 } 326 327 // It does not contain an instance of TextEffigy with 328 // the name "textEffigy". 329 // Attempt to use it's url attribute and create a new 330 // instance of TextEffigy contained by the specified one. 331 URL url = effigy.uri.getURL(); 332 TextEffigy textEffigy; 333 334 if (effigy instanceof PtolemyEffigy) { 335 // NOTE: It seems unfortunate here to have 336 // to distinctly support MoML. Would it make 337 // sense for the Effigy base class to have a method 338 // that gives a textual description of the data? 339 String moml = ((PtolemyEffigy) effigy).getModel() 340 .exportMoML(); 341 textEffigy = (TextEffigy) _newTextEffigyText.invoke(null, 342 new Object[] { effigy, moml, "text/xml" }); 343 344 // NOTE: Used to set this not modifiable, but actually, this 345 // didn't do what we wanted. It marks the _model_ not modifiable, 346 // not the textEffigy! Anyway, we may want to edit and save 347 // somewhere else. And anyway, the next text editor doesn't 348 // seem to prevent editing. 349 // textEffigy.setModifiable(false); 350 textEffigy.setName("textEffigy"); 351 } else { 352 // The View Source choice of the HTMLViewer runs this code. 353 textEffigy = (TextEffigy) _newTextEffigyURL.invoke(null, 354 new Object[] { effigy, url, url }); 355 textEffigy.setName("textEffigy"); 356 } 357 358 TextEditorTableau textTableau = (TextEditorTableau) createTableau( 359 textEffigy); 360 361 if (url != null) { 362 // A NullPointerException was reported here, see 363 // http://bugzilla.ecoinformatics.org/show_bug.cgi?id=5446 364 textEffigy.identifier.setExpression(url.toExternalForm()); 365 } 366 return textTableau; 367 } 368 } 369 370 private Constructor _tableauConstructor; 371 372 private Method _newTextEffigyText; 373 374 private Method _newTextEffigyURL; 375 } 376}