001/* A tableau for evaluating Python expression interactively.
002
003 Copyright (c) 2003-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 */
027package ptolemy.actor.gui.python;
028
029import java.awt.BorderLayout;
030import java.net.URL;
031
032import javax.swing.BoxLayout;
033import javax.swing.JPanel;
034
035import org.python.core.PyObject;
036import org.python.util.PythonInterpreter;
037
038import ptolemy.actor.gui.Effigy;
039import ptolemy.actor.gui.Tableau;
040import ptolemy.actor.gui.TableauFactory;
041import ptolemy.actor.gui.TableauFrame;
042import ptolemy.gui.ShellInterpreter;
043import ptolemy.gui.ShellTextArea;
044import ptolemy.kernel.util.IllegalActionException;
045import ptolemy.kernel.util.NameDuplicationException;
046import ptolemy.kernel.util.NamedObj;
047
048///////////////////////////////////////////////////////////////////
049//// PythonShellTableau
050
051/**
052 A tableau that provides an interactive shell for evaluating Python expressions.
053 @author Christopher Hylands and Edward A. Lee
054 @version $Id$
055 @since Ptolemy II 3.0
056 @Pt.ProposedRating Red (cxh)
057 @Pt.AcceptedRating Red (cxh)
058 */
059public class PythonShellTableau extends Tableau implements ShellInterpreter {
060    /** Create a new tableau.
061     *  The tableau is itself an entity contained by the effigy
062     *  and having the specified name.  The frame is not made visible
063     *  automatically.  You must call show() to make it visible.
064     *  @param container The containing effigy.
065     *  @param name The name of this tableau within the specified effigy.
066     *  @exception IllegalActionException If the tableau is not acceptable
067     *   to the specified container.
068     *  @exception NameDuplicationException If the container already contains
069     *   an entity with the specified name.
070     */
071    public PythonShellTableau(PythonShellEffigy container, String name)
072            throws IllegalActionException, NameDuplicationException {
073        super(container, name);
074
075        PythonShellFrame frame = new PythonShellFrame(this);
076        setFrame(frame);
077
078        // FIXME: this would be a good place to read in init.py
079        //        _interpreter.execfile(ptolemy.data.expr.UtilityFunctions
080        //                              .findFile("ptolemy/actor/gui/python/init.py");
081    }
082
083    ///////////////////////////////////////////////////////////////////
084    ////                         public methods                    ////
085
086    /** Evaluate the specified command.
087     *  @param command The command.
088     *  @return The return value of the command, or null if there is none.
089     *  @exception Exception If something goes wrong processing the command.
090     */
091    @Override
092    public String evaluateCommand(String command) throws Exception {
093        try {
094            PyObject results = _interpreter.eval(command);
095            return results.toString();
096        } catch (Throwable throwable) {
097            return throwable.toString();
098        }
099    }
100
101    /** Return true if the specified command is complete (ready
102     *  to be interpreted).
103     *  @param command The command.
104     *  @return True.
105     */
106    @Override
107    public boolean isCommandComplete(String command) {
108        return true;
109    }
110
111    ///////////////////////////////////////////////////////////////////
112    ////                         private variables                 ////
113    // The parameter used for evaluation.
114    // FIXME: Perhaps the interpreter should be in its own thread?
115    private PythonInterpreter _interpreter = new PythonInterpreter();
116
117    ///////////////////////////////////////////////////////////////////
118    ////                         inner classes                     ////
119
120    /** The frame that is created by an instance of PythonShellTableau.
121     */
122    @SuppressWarnings("serial")
123    public static class PythonShellFrame extends TableauFrame {
124        // FindBugs suggested refactoring this into a static class.
125
126        /** Construct a frame to display the PythonShell window.
127         *  After constructing this, it is necessary
128         *  to call setVisible(true) to make the frame appear.
129         *  This is typically accomplished by calling show() on
130         *  enclosing tableau.
131         *  @param pythonShellTableau The PythonShellTableau associated with
132         *  this actor.
133         *  @exception IllegalActionException If the model rejects the
134         *   configuration attribute.
135         *  @exception NameDuplicationException If a name collision occurs.
136         */
137        public PythonShellFrame(PythonShellTableau pythonShellTableau)
138                throws IllegalActionException, NameDuplicationException {
139            super(pythonShellTableau);
140
141            JPanel component = new JPanel();
142            component.setLayout(new BoxLayout(component, BoxLayout.Y_AXIS));
143
144            ShellTextArea shellPanel = new ShellTextArea();
145            shellPanel.setInterpreter(pythonShellTableau);
146            component.add(shellPanel);
147            getContentPane().add(component, BorderLayout.CENTER);
148        }
149
150        ///////////////////////////////////////////////////////////////////
151        ////                         protected methods                 ////
152        @Override
153        protected void _help() {
154            try {
155                URL doc = getClass().getClassLoader()
156                        .getResource("ptolemy/actor/gui/python/help.htm");
157                getConfiguration().openModel(null, doc, doc.toExternalForm());
158            } catch (Exception ex) {
159                System.out.println("PythonShellTableau._help(): " + ex);
160                _about();
161            }
162        }
163    }
164
165    /** A factory that creates a control panel to display a Python shell.
166     */
167    public static class Factory extends TableauFactory {
168        /** Create a factory with the given name and container.
169         *  @param container The container.
170         *  @param name The name.
171         *  @exception IllegalActionException If the container is incompatible
172         *   with this attribute.
173         *  @exception NameDuplicationException If the name coincides with
174         *   an attribute already in the container.
175         */
176        public Factory(NamedObj container, String name)
177                throws IllegalActionException, NameDuplicationException {
178            super(container, name);
179        }
180
181        ///////////////////////////////////////////////////////////////////
182        ////                         public methods                    ////
183
184        /** Create a new instance of PythonShellTableau in the specified
185         *  effigy. It is the responsibility of callers of
186         *  this method to check the return value and call show().
187         *  @param effigy The model effigy.
188         *  @return A new control panel tableau if the effigy is
189         *    a PtolemyEffigy, or null otherwise.
190         *  @exception Exception If the factory should be able to create a
191         *   tableau for the effigy, but something goes wrong.
192         */
193        @Override
194        public Tableau createTableau(Effigy effigy) throws Exception {
195            // NOTE: Can create any number of tableaux within the same
196            // effigy.  Is this what we want?
197            if (effigy instanceof PythonShellEffigy) {
198                return new PythonShellTableau((PythonShellEffigy) effigy,
199                        "PythonShellTableau");
200            } else {
201                return null;
202            }
203        }
204    }
205}