PythonScript.h 8.28 KB
Newer Older
Roman Tolchenov's avatar
re #79  
Roman Tolchenov committed
1
/***************************************************************************
2
3
  File                 : PythonScript.h
  Project              : QtiPlot
Roman Tolchenov's avatar
re #79  
Roman Tolchenov committed
4
--------------------------------------------------------------------
5
6
7
  Copyright            : (C) 2006 by Knut Franke
  Email (use @ for *)  : knut.franke*gmx.de
  Description          : Execute Python code from within QtiPlot
Roman Tolchenov's avatar
re #79  
Roman Tolchenov committed
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *  This program is free software; you can redistribute it and/or modify   *
 *  it under the terms of the GNU General Public License as published by   *
 *  the Free Software Foundation; either version 2 of the License, or      *
 *  (at your option) any later version.                                    *
 *                                                                         *
 *  This program is distributed in the hope that it will be useful,        *
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of         *
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          *
 *  GNU General Public License for more details.                           *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the Free Software           *
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor,                    *
 *   Boston, MA  02110-1301  USA                                           *
 *                                                                         *
 ***************************************************************************/
#ifndef PYTHON_SCRIPT_H
#define PYTHON_SCRIPT_H

32
// Python headers have to go first!
Roman Tolchenov's avatar
Roman Tolchenov committed
33
34
#include "MantidQtWidgets/Common/PythonSystemHeader.h"
#include "MantidQtWidgets/Common/PythonThreading.h"
35

36
#include "Script.h"
Roman Tolchenov's avatar
Roman Tolchenov committed
37
#include "MantidQtWidgets/Common/WorkspaceObserver.h"
Roman Tolchenov's avatar
re #79  
Roman Tolchenov committed
38

39
#include <QFileInfo>
40
#include <QDir>
41

Roman Tolchenov's avatar
re #79  
Roman Tolchenov committed
42
class ScriptingEnv;
43
class PythonScripting;
44
struct _sipWrapperType;
Roman Tolchenov's avatar
re #79  
Roman Tolchenov committed
45

46
/**
47
 * This class holds, compiles and executes the Python code.
48
 */
49
class PythonScript : public Script, MantidQt::API::WorkspaceObserver {
50
  Q_OBJECT
51
public:
52
  /// Constructor
53
54
  PythonScript(PythonScripting *env, const QString &name,
               const InteractionType interact, QObject *context);
55
  /// Destructor
56
  ~PythonScript() override;
57

58
59
  /// Set the identifier of the script. If empty, set a default so that the code
  /// object behaves correctly
60
  void setIdentifier(const QString &name) override;
61

62
  /// Create a PyObject that wraps this C++ instance
63
  PyObject *createSipInstanceFromMe();
64

65
  // -------------------------- I/O-like behaviour ------------------
66
  /// Connects the python stdout to a Qt signal
67
  inline void write(const QString &text) { emit print(text); }
68
  /// Simulate file-like object (required for IPython)
69
  inline void flush() {}
70
  /// Simulate file-like object (required for colorama)
71
72
73
  inline bool closed() const { return false; }
  /// Simulate file-like object
  inline bool isatty() const { return false; }
74
  /// Is the given code complete
75
  bool compilesToCompleteStatement(const QString &code) const override;
76
77
78
79

  // -------------------------- Line number tracing ---------------------------
  /// Emits a signal from this object indicating the current line number of the
  /// code. This includes any offset.
80
  void lineNumberChanged(PyObject *codeObject, int lineNo);
81
82
  /// Emit the line change signal
  void sendLineChangeSignal(int lineNo, bool error);
83

84
  /// Create a list of keywords for the code completion API
85
  void generateAutoCompleteList() override;
86

87
88
  /// Special handle for syntax errors as they have no traceback
  QString constructSyntaxErrorStr(PyObject *syntaxError);
89
  /// Convert a traceback to a string
90
91
  void tracebackToMsg(QTextStream &msgStream, PyTracebackObject *traceback,
                      bool root = true);
92

93
  /// Set the name of the passed object so that Python can refer to it
94
  bool setQObject(QObject *val, const char *name) override;
95
  /// Set the name of the integer so that Python can refer to it
96
  bool setInt(int val, const char *name) override;
97
  /// Set the name of the double so that Python can refer to it
98
  bool setDouble(double val, const char *name) override;
99
  /// Set the context for this script
100
  void setContext(QObject *context) override;
101
  /// Resets the local dictionary to the defaults
102
  void clearLocals() override;
Roman Tolchenov's avatar
re #79  
Roman Tolchenov committed
103

104
private:
105
  /// Helper class to ensure the sys.path variable is updated correctly
106
  struct PythonPathHolder {
107
    /// Update the path with the given entry
108
    explicit PythonPathHolder(const QString &entry) : m_path(entry) {
109
      const QFileInfo filePath(m_path);
110
      if (filePath.exists()) {
111
112
        QDir directory = filePath.absoluteDir();
        // Check it is not a package
113
        if (!QFileInfo(directory, "__init__.py").exists()) {
114
115
          m_path = directory.absolutePath();
          appendPath(m_path);
116
        } else {
117
118
          m_path = "";
        }
119
120
121
      }
    }
    /// Remove the entry from the path
122
123
124
    ~PythonPathHolder() {
      if (!m_path.isEmpty())
        removePath(m_path);
125
    }
126

127
    void appendPath(const QString &path) {
128
      ScopedPythonGIL pythonLock;
129
130
      QString code = "if r'%1' not in sys.path:\n"
                     "    sys.path.append(r'%1')";
131
      code = code.arg(path);
132
      PyRun_SimpleString(code.toAscii().constData());
133
    }
134
    void removePath(const QString &path) {
135
      ScopedPythonGIL pythonLock;
136
137
      QString code = "if r'%1' in sys.path:\n"
                     "    sys.path.remove(r'%1')";
138
139
140
      code = code.arg(path);
      PyRun_SimpleString(code.toAscii());
    }
141

142
  private:
143
    QString m_path;
144
145
  };

146
147
  inline PythonScripting *pythonEnv() const { return m_pythonEnv; }
  void initialize(const QString &name, QObject *context);
148
149
150
  void beginStdoutRedirect();
  void endStdoutRedirect();

151
152
  // --------------------------- Script compilation/execution
  // -----------------------------------
153
  /// Compile the code, returning true if it was successful, false otherwise
154
  bool compileImpl() override;
155
  /// Evaluate the current code and return a result as a QVariant
156
  QVariant evaluateImpl() override;
157
  /// Execute the current code and return a boolean indicating success/failure
158
  bool executeImpl() override;
159
  /// Request that this script be aborted
160
  void abortImpl() override;
161
162
  /// Get the value of the Python thread ID when a script is executed
  long getThreadID();
163

164
  /// Performs the call to Python from a string
165
  bool executeString();
166
  /// Executes the code object and returns the result, may be null
167
  PyObject *executeCompiledCode(PyObject *compiledCode);
168
169
  /// Check an object for a result.
  bool checkResult(PyObject *result);
170
  /// Compile to bytecode
171
  PyObject *compileToByteCode(bool for_eval = true);
172

173
174
  // ---------------------------- Variable reference
  // ---------------------------------------------
175
  /// Listen to add notifications from the ADS
176
177
  void addHandle(const std::string &wsName,
                 const Mantid::API::Workspace_sptr ws) override;
178
  /// Listen to add/replace notifications from the ADS
179
180
  void afterReplaceHandle(const std::string &wsName,
                          const Mantid::API::Workspace_sptr ws) override;
181
  /// Listen to delete notifications
182
  void postDeleteHandle(const std::string &wsName) override;
183
  /// Listen to ADS clear notifications
184
  void clearADSHandle() override;
185
  /// Add/update a Python reference to the given workspace
186
187
  void addPythonReference(const std::string &wsName,
                          const Mantid::API::Workspace_sptr ws);
188
  /// Delete a Python reference to the given workspace name
189
  void deletePythonReference(const std::string &wsName);
190

191
192
193
  /// Send out an error and clear it from python.
  void emit_error();

194
  PythonScripting *m_pythonEnv;
195
  PyObject *localDict, *stdoutSave, *stderrSave;
196
197
  PyObject *m_codeFileObject;
  long m_threadID; ///< Python thread id
198
  /// A reference to the IAlgorithm._algorithmInThread static method
199
  PyObject *m_algorithmInThread;
200
  bool isFunction;
201
  QString fileName;
202
  bool m_isInitialized;
203
  PythonPathHolder m_pathHolder;
204
  /// Set of current python variables that point to workspace handles
205
  std::set<std::string> m_workspaceHandles;
Roman Tolchenov's avatar
re #79  
Roman Tolchenov committed
206
207
208
};

#endif