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}