001/* An attribute that causes look inside to open a text editor to
002 edit a string attribute in the container.
003
004 Copyright (c) 2003-2016 The Regents of the University of California.
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 BE LIABLE TO ANY PARTY
013 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
014 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
015 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
016 SUCH DAMAGE.
017
018 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
019 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
020 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
021 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
022 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
023 ENHANCEMENTS, OR MODIFICATIONS.
024
025 PT_COPYRIGHT_VERSION_2
026 COPYRIGHTENDKEY
027
028
029 */
030package ptolemy.vergil.toolbox;
031
032import java.lang.reflect.Constructor;
033import java.util.Iterator;
034
035import javax.swing.text.Document;
036
037import ptolemy.actor.gui.Effigy;
038import ptolemy.actor.gui.PtolemyEffigy;
039import ptolemy.actor.gui.Tableau;
040import ptolemy.actor.gui.TableauFactory;
041import ptolemy.actor.gui.TextEditorTableau;
042import ptolemy.actor.gui.TextEffigy;
043import ptolemy.data.IntToken;
044import ptolemy.data.StringToken;
045import ptolemy.data.Token;
046import ptolemy.data.expr.Parameter;
047import ptolemy.data.expr.Variable;
048import ptolemy.data.type.BaseType;
049import ptolemy.kernel.util.Attribute;
050import ptolemy.kernel.util.IllegalActionException;
051import ptolemy.kernel.util.NameDuplicationException;
052import ptolemy.kernel.util.NamedObj;
053import ptolemy.kernel.util.StringAttribute;
054
055///////////////////////////////////////////////////////////////////
056//// TextEditorTableauFactory
057
058/**
059 This class is an attribute that creates a text editor to edit a specified
060 string attribute in the container of this attribute.  It is similar to
061 TextEditorConfigureFactory, but instead of opening when the actor is configured,
062 it is opened when the user looks inside the actor.
063
064 @author Edward A. Lee
065 @version $Id$
066 @since Ptolemy II 4.0
067 @Pt.ProposedRating Yellow (eal)
068 @Pt.AcceptedRating Red (ptolemy)
069 @see TextEditorConfigureFactory
070 */
071public class TextEditorTableauFactory extends TableauFactory
072        implements TextEditorFactory {
073    /** Create a factory with the given name and container.
074     *  @param container The container.
075     *  @param name The name.
076     *  @exception IllegalActionException If the container is incompatible
077     *   with this attribute.
078     *  @exception NameDuplicationException If the name coincides with
079     *   an attribute already in the container.
080     */
081    public TextEditorTableauFactory(NamedObj container, String name)
082            throws IllegalActionException, NameDuplicationException {
083        super(container, name);
084
085        attributeName = new StringAttribute(this, "attributeName");
086
087        columnsDisplayed = new Parameter(this, "columnsDisplayed");
088        columnsDisplayed.setTypeEquals(BaseType.INT);
089        columnsDisplayed.setExpression("80");
090
091        rowsDisplayed = new Parameter(this, "rowsDisplayed");
092        rowsDisplayed.setTypeEquals(BaseType.INT);
093        rowsDisplayed.setExpression("40");
094
095        syntaxStyle = new StringAttribute(this, "syntaxStyle");
096    }
097
098    ///////////////////////////////////////////////////////////////////
099    ////                         parameters                        ////
100
101    /** The name of the string attribute that is to be edited. */
102    public StringAttribute attributeName;
103
104    /** The horizontal size of the display, in columns. This contains
105     *  an integer, and defaults to 80.
106     */
107    public Parameter columnsDisplayed;
108
109    /** The vertical size of the display, in rows. This contains an
110     *  integer, and defaults to 40.
111     */
112    public Parameter rowsDisplayed;
113
114    /** The style of the text to be edited. This may or may not be
115     *  supported. If the package "org.fife.ui.rsyntaxtextarea" is found in
116     *  the classpath, then the supported styles include
117     *  "text/plain", "text/c", "text/clojure", "text/cpp", "text/cs",
118     *  "text/css", "text/dtd", "text/fortran",
119     *  "text/groovy", "text/html", "text/java",
120     *  "text/javascript", "text/json", "text/jsp",
121     *  "text/latex", "text/makefile",
122     *  "text/perl", "text/php",
123     *  "text/properties", "text/python", "text/ruby", "text/sas",
124     *  "text/scala", "text/sql", "text/tcl", "text/unix", "text/vb",
125     *  "text/bat", and "text/xml".
126     */
127    public StringAttribute syntaxStyle;
128
129    ///////////////////////////////////////////////////////////////////
130    ////                         public methods                    ////
131
132    /** Remove any editor that may have been associated with this object
133     *  by a previous call to createEditor().
134     */
135    @Override
136    public void clear() {
137        _editor = null;
138    }
139
140    /** Create a tableau for the specified effigy. The tableau will be
141     *  created with a new unique name with the specified effigy as its
142     *  container.  If this factory cannot create a tableau
143     *  for the given effigy (it is not an instance of PtolemyEffigy),
144     *  then return null.
145     *  @param effigy The component effigy.
146     *  @return A tableau for the effigy, or null if one cannot be created.
147     *  @exception Exception If the factory should be able to create a
148     *   Tableau for the effigy, but something goes wrong.
149     */
150    @Override
151    public Tableau createTableau(Effigy effigy) throws Exception {
152        if (!(effigy instanceof PtolemyEffigy)) {
153            return null;
154        }
155
156        NamedObj object = ((PtolemyEffigy) effigy).getModel();
157        Attribute attribute = object
158                .getAttribute(attributeName.getExpression());
159
160        if (!(attribute instanceof StringAttribute)
161                && !(attribute instanceof Variable)) {
162            throw new IllegalActionException(object,
163                    "Expected " + object.getFullName()
164                            + " to contain a StringAttribute or Variable named "
165                            + attributeName.getExpression()
166                            + ", but it does not.");
167        }
168
169        // effigy may already contain a texteffigy.
170        TextEffigy textEffigy = null;
171        Iterator subEffigies = effigy.entityList(TextEffigy.class).iterator();
172
173        while (subEffigies.hasNext()) {
174            textEffigy = (TextEffigy) subEffigies.next();
175        }
176
177        String text = getTextToEdit(attribute);
178        // If a syntaxStyle attribute is given, then use it.
179        if (textEffigy == null) {
180            textEffigy = _newTextEffigy(effigy, text);
181            textEffigy.identifier.setExpression(attribute.getName());
182        }
183
184        // textEffigy may already have a tableau.
185        Iterator tableaux = textEffigy.entityList(TextEditorTableau.class)
186                .iterator();
187
188        if (tableaux.hasNext()) {
189            return (TextEditorTableau) tableaux.next();
190        }
191
192        // Need a new tableau, so create an editor for it.
193        if (_editor == null) {
194            int numberOfRows = ((IntToken) rowsDisplayed.getToken()).intValue();
195            int numberOfColumns = ((IntToken) columnsDisplayed.getToken())
196                    .intValue();
197            String name = "Editor for " + attributeName.getExpression() + " of "
198                    + getContainer().getFullName();
199            String style = syntaxStyle.getExpression();
200            if (style != null && !style.trim().equals("")) {
201                // Attempt to specify a syntax-aware text editor.
202                try {
203                    Class editorClass = Class.forName(
204                            "ptolemy.actor.gui.syntax.SyntaxTextEditorForStringAttributes");
205                    Constructor constructor = editorClass.getConstructor(
206                            new Class[] { TextEditorFactory.class,
207                                    Attribute.class, Integer.TYPE, Integer.TYPE,
208                                    String.class, Document.class });
209                    _editor = (TextEditorForStringAttributes) constructor
210                            .newInstance(new Object[] { this, attribute,
211                                    numberOfRows, numberOfColumns, name,
212                                    textEffigy.getDocument() });
213                } catch (Throwable ex) {
214                    // Ignore and use default text editor.
215                    System.out.println(
216                            "Note: failed to open syntax-directed editor: "
217                                    + ex.getMessage());
218                }
219            }
220            if (_editor == null) {
221                _editor = new TextEditorForStringAttributes(this, attribute,
222                        numberOfRows, numberOfColumns, name,
223                        textEffigy.getDocument());
224            }
225        }
226
227        TextEditorTableau tableau = new TextEditorTableau(textEffigy,
228                "_tableau", _editor);
229        return tableau;
230    }
231
232    /** Return the current text of the text editor.
233     *  @return The current text of the text editor, or null if there
234     *   is none.
235     */
236    @Override
237    public String getText() {
238        if (_editor != null) {
239            return _editor.text.getText();
240        }
241
242        return null;
243    }
244
245    /** Return the text value of the specified attribute.
246     *  @param attributeToEdit The attribute.
247     *  @return The text contained by this attribute, or an error message
248     *   if the attribute does not contain text.
249     */
250    public static String getTextToEdit(Attribute attributeToEdit) {
251        String textToEdit = "";
252        if (attributeToEdit instanceof StringAttribute) {
253            textToEdit = ((StringAttribute) attributeToEdit).getExpression();
254        } else if (attributeToEdit instanceof Variable) {
255            try {
256                Token token = ((Variable) attributeToEdit).getToken();
257                if (token instanceof StringToken) {
258                    textToEdit = ((StringToken) token).stringValue();
259                } else {
260                    textToEdit = "Error: Expected a String, but got: "
261                            + token.getType() + " with value "
262                            + token.toString();
263                }
264            } catch (IllegalActionException e) {
265                textToEdit = ((Variable) attributeToEdit).getExpression();
266            }
267        }
268        return textToEdit;
269    }
270
271    ///////////////////////////////////////////////////////////////////
272    ////                         protected methods                 ////
273
274    /** Create a text effigy to be contained by the specified host
275     *  effigy for the specified text.
276     *  @param effigy The host effigy.
277     *  @param text The text.
278     *  @return the TextEffigy.
279     *  @exception Exception If the text effigy cannot be contained
280     *   by the specified container, or if the specified text cannot
281     *   be inserted into the document.
282     */
283    protected TextEffigy _newTextEffigy(Effigy effigy, String text)
284            throws Exception {
285        String style = syntaxStyle.getExpression();
286        return TextEffigy.newTextEffigy(effigy, text, style);
287    }
288
289    ///////////////////////////////////////////////////////////////////
290    ////                         private members                   ////
291
292    /** Keep track of an open editor so that it isn't opened more than
293     *  once.
294     */
295    private TextEditorForStringAttributes _editor;
296}