001/* A helper class to handle actions in GUI properties.
002
003 Copyright (c) 2008-2014 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
027 */
028package ptolemy.actor.gui.properties;
029
030import java.awt.geom.Point2D;
031import java.io.IOException;
032import java.io.InputStreamReader;
033import java.lang.reflect.Method;
034import java.net.URL;
035import java.util.Iterator;
036import java.util.List;
037
038import javax.swing.JFrame;
039
040import ptolemy.actor.gui.Configuration;
041import ptolemy.actor.gui.PtolemyFrame;
042import ptolemy.actor.gui.Tableau;
043import ptolemy.kernel.util.Attribute;
044import ptolemy.kernel.util.IllegalActionException;
045import ptolemy.kernel.util.InternalErrorException;
046import ptolemy.kernel.util.KernelException;
047import ptolemy.kernel.util.KernelRuntimeException;
048import ptolemy.kernel.util.Location;
049import ptolemy.kernel.util.NameDuplicationException;
050import ptolemy.kernel.util.NamedObj;
051import ptolemy.kernel.util.Singleton;
052import ptolemy.kernel.util.StringAttribute;
053import ptolemy.moml.MoMLChangeRequest;
054import ptolemy.moml.MoMLParser;
055import ptolemy.vergil.basic.BasicGraphFrame;
056
057//////////////////////////////////////////////////////////////////////////
058//// GUIAction
059
060/**
061 A helper class to handle actions in GUI properties.
062
063 @author Thomas Huining Feng
064 @version $Id$
065 @since Ptolemy II 8.0
066 @Pt.ProposedRating Red (tfeng)
067 @Pt.AcceptedRating Red (tfeng)
068 */
069public class GUIAction extends Attribute {
070
071    /** Construct an item with the given name contained by the specified
072     *  entity. The container argument must not be null, or a
073     *  NullPointerException will be thrown.  This attribute will use the
074     *  workspace of the container for synchronization and version counts.
075     *  If the name argument is null, then the name is set to the empty
076     *  string. Increment the version of the workspace.
077     *  @param container The container.
078     *  @param name The name of this attribute.
079     *  @exception IllegalActionException If the attribute is not of an
080     *   acceptable class for the container, or if the name contains a
081     *   period.
082     *  @exception NameDuplicationException If the name coincides with
083     *   an attribute already in the container.
084     */
085    public GUIAction(NamedObj container, String name)
086            throws IllegalActionException, NameDuplicationException {
087        super(container, name);
088    }
089
090    /** Configure the object with data from the specified input source
091     *  (a URL) and/or textual data.  The object should interpret the
092     *  source first, if it is specified, followed by the literal text,
093     *  if that is specified.  The new configuration should usually
094     *  override any old configuration wherever possible, in order to
095     *  ensure that the current state can be successfully retrieved.
096     *  <p>
097     *  This method is defined to throw a very general exception to allow
098     *  classes that implement the interface to use whatever exceptions
099     *  are appropriate.
100     *  @param base The base relative to which references within the input
101     *   are found, or null if this is not known, or there is none.
102     *  @param source The input source, which specifies a URL, or null
103     *   if none.
104     *  @param text Configuration information given as text, or null if
105     *   none.
106     *  @exception Exception If something goes wrong.
107     */
108    public void configure(URL base, String source, String text)
109            throws Exception {
110        _momlSource = source;
111        _momlText = text;
112        _parsedObject = null;
113    }
114
115    /** Return the input source that was specified the last time the configure
116     *  method was called.
117     *  @return The string representation of the input URL, or null if the
118     *  no source has been used to configure this object, or null if no
119     *  external source need be used to configure this object.
120     */
121    public String getConfigureSource() {
122        return _momlSource;
123    }
124
125    /** Return the text string that represents the current configuration of
126     *  this object.  Note that any configuration that was previously
127     *  specified using the source attribute need not be represented here
128     *  as well.
129     *  @return A configuration string, or null if no configuration
130     *  has been used to configure this object, or null if no
131     *  configuration string need be used to configure this object.
132     */
133    public String getConfigureText() {
134        return _momlText;
135    }
136
137    /** Get the frame in which this item is selected.
138     *
139     *  @return The frame.
140     */
141    public JFrame getFrame() {
142        NamedObj container = getContainer();
143        while (container != null && !(container instanceof Tableau)) {
144            container = container.getContainer();
145        }
146        if (container == null) {
147            throw new InternalErrorException("Unable to find tableau.");
148        }
149
150        return ((Tableau) container).getFrame();
151    }
152
153    /** Get the model contained in the current frame.
154     *
155     *  @return The model.
156     */
157    public NamedObj getModel() {
158        JFrame frame = getFrame();
159        if (!(frame instanceof PtolemyFrame)) {
160            throw new InternalErrorException(
161                    "The current frame has " + "no model.");
162        }
163        return ((PtolemyFrame) frame).getModel();
164    }
165
166    /** React to this item being selected. In this base class, if a source
167     *  file is specified in the configuration of this item, e.g.:
168     *  <pre>
169     *    &lt;configure source="some_file.xml"&gt;
170     *    &lt;/configure&gt;
171     *  </pre>
172     *  then the source is read and its contents are used as the moml text.
173     *  The moml text can also be given directly:
174     *  <pre>
175     *    &lt;configure&gt;
176     *      &lt;entity name="C" class="ptolemy.actor.lib.Const"&gt;
177     *      &lt;/entity&gt;
178     *    &lt;/configure&gt;
179     *  </pre>
180     *
181     *  Depending on whether the parse parameter is true or false,
182     *  the moml text may be parsed first or not. If it is parsed, the
183     *  returned NamedObj is used to generate a new moml string to be
184     *  applied to the model in the current tableau (the nearest tableau
185     *  that contains this GUI property). If it is not parsed, then the moml
186     *  text is directly applied to the model.
187     *
188     *  @param parse Whether the configure text should be parsed before applying
189     *   to the current model.
190     *  @exception Exception If error occurs in performing the action.
191     */
192    public void perform(boolean parse) throws Exception {
193        if (_momlText != null) {
194            NamedObj model = getModel();
195
196            String moml;
197            if (parse) {
198                _parseSource();
199                moml = getMoml(model, _parsedObject);
200            } else {
201                if (_momlSource != null) {
202                    URL url = _parser.fileNameToURL(_momlSource, null);
203                    InputStreamReader reader = null;
204                    try {
205                        reader = new InputStreamReader(url.openStream());
206
207                        int bufferSize = 1024;
208                        char[] buffer = new char[bufferSize];
209                        int readSize = 0;
210                        StringBuffer string = new StringBuffer();
211                        while (readSize >= 0) {
212                            readSize = reader.read(buffer);
213                            if (readSize >= 0) {
214                                string.append(buffer, 0, readSize);
215                            }
216                        }
217                        _momlText = string.toString();
218                        _momlSource = null;
219                    } finally {
220                        if (reader != null) {
221                            try {
222                                reader.close();
223                            } catch (IOException ex) {
224                                throw new InternalErrorException(
225                                        "Failed to close \"" + url + "\".");
226                            }
227                        }
228                    }
229                }
230                moml = _momlText;
231            }
232            MoMLChangeRequest request = new MoMLChangeRequest(this, model,
233                    moml) {
234                @Override
235                protected void _postParse(MoMLParser parser) {
236                    Iterator topObjects = parser.topObjectsCreated().iterator();
237                    while (topObjects.hasNext()) {
238                        NamedObj topObject = (NamedObj) topObjects.next();
239                        if (topObject.attributeList(Location.class).isEmpty()) {
240                            try {
241                                Location location = new Location(topObject,
242                                        topObject.uniqueName("_location"));
243                                Point2D center = ((BasicGraphFrame) getFrame())
244                                        .getCenter();
245                                location.setLocation(new double[] {
246                                        center.getX(), center.getY() });
247                            } catch (KernelException e) {
248                                throw new InternalErrorException(e);
249                            }
250                        }
251                    }
252                    parser.clearTopObjectsList();
253                }
254
255                @Override
256                protected void _preParse(MoMLParser parser) {
257                    super._preParse(parser);
258                    parser.clearTopObjectsList();
259                }
260            };
261            request.setUndoable(true);
262            model.requestChange(request);
263        }
264    }
265
266    /** Parse the configuration source if it has not been parsed, and store
267     *  the result in protected field {@link #_parsedObject}.
268     *
269     *  @exception Exception If it occurs in the parsing.
270     */
271    protected void _parseSource() throws Exception {
272        if (_parsedObject == null) {
273            if (_momlSource != null) {
274                URL url = _parser.fileNameToURL(_momlSource, null);
275                _parsedObject = _parser.parse(url, url);
276                _momlSource = null;
277            } else {
278                _parsedObject = _parser.parse(_momlText);
279            }
280            _parser.reset();
281        }
282    }
283
284    /** The input source that was specified the last time the configure
285     *  method was called.
286     */
287    protected String _momlSource;
288
289    /** The text string that represents the current configuration of this
290     *  object.
291     */
292    protected String _momlText;
293
294    /** The object obtained by parsing the moml text, or null.
295     */
296    protected NamedObj _parsedObject;
297
298    /** The parser used to parse the moml text.
299     */
300    protected MoMLParser _parser = new MoMLParser();
301
302    /** Get the moml for the object to be added to the container.
303     *  @param container The container.
304     *  @param object The object whose moml is to be got.
305     *  @return The moml string.
306     *  @exception Exception If error occurs.
307     */
308    private static String getMoml(NamedObj container, NamedObj object)
309            throws Exception {
310        String name;
311
312        if (object instanceof Singleton) {
313            name = object.getName();
314        } else {
315            name = container.uniqueName(object.getName());
316        }
317
318        boolean lsidFlag = true;
319        try {
320            String lsidString = ((StringAttribute) object
321                    .getAttribute("entityId")).getExpression();
322            if (lsidString == null || lsidString.equals("")) {
323                lsidFlag = false;
324            }
325        } catch (Exception eee) {
326            lsidFlag = false;
327        }
328
329        StringAttribute alternateGetMomlActionAttribute = null;
330        alternateGetMomlActionAttribute = (StringAttribute) object
331                .getAttribute("_alternateGetMomlAction");
332        if (alternateGetMomlActionAttribute == null && lsidFlag) {
333            Configuration config = null;
334            List configsList = Configuration.configurations();
335            for (Iterator it = configsList.iterator(); it.hasNext();) {
336                config = (Configuration) it.next();
337                if (config != null) {
338                    break;
339                }
340            }
341            if (config == null) {
342                throw new KernelRuntimeException(object, "Could not find "
343                        + "configuration, list of configurations was "
344                        + configsList.size() + " elements, all were null.");
345            }
346            alternateGetMomlActionAttribute = (StringAttribute) config
347                    .getAttribute("_alternateGetMomlAction");
348        }
349
350        if (alternateGetMomlActionAttribute != null) {
351            String alternateGetMomlClassName = alternateGetMomlActionAttribute
352                    .getExpression();
353            Class getMomlClass = Class.forName(alternateGetMomlClassName);
354            Object getMomlAction = getMomlClass.newInstance();
355            try {
356                Method getMomlMethod = getMomlClass.getMethod("getMoml",
357                        new Class[] { NamedObj.class, String.class });
358                return (String) getMomlMethod.invoke(getMomlAction,
359                        new Object[] { object, name });
360            } catch (NoSuchMethodException e) {
361            }
362        }
363
364        return "<group name=\"auto\">\n" + object.exportMoML(name)
365                + "</group>\n";
366    }
367}