1
/***************************************************************************
2
* Copyright (c) 2004 Jürgen Riegel <juergen.riegel@web.de> *
4
* This file is part of the FreeCAD CAx development system. *
6
* This library is free software; you can redistribute it and/or *
7
* modify it under the terms of the GNU Library General Public *
8
* License as published by the Free Software Foundation; either *
9
* version 2 of the License, or (at your option) any later version. *
11
* This library is distributed in the hope that it will be useful, *
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14
* GNU Library General Public License for more details. *
16
* You should have received a copy of the GNU Library General Public *
17
* License along with this library; see the file COPYING.LIB. If not, *
18
* write to the Free Software Foundation, Inc., 59 Temple Place, *
19
* Suite 330, Boston, MA 02111-1307, USA *
21
***************************************************************************/
24
#include "PreCompiled.h"
29
# include <QTextStream>
32
#include <App/Application.h>
33
#include <Base/Console.h>
34
#include <Base/Exception.h>
35
#include <Base/Interpreter.h>
38
#include "MainWindow.h"
39
#include "PythonConsole.h"
40
#include "PythonConsolePy.h"
41
#include "PythonDebugger.h"
46
MacroFile::MacroFile() = default;
48
void MacroFile::open(const char *sName)
52
Q_ASSERT(!this->openMacro);
56
this->macroName = QString::fromUtf8(sName);
57
if (!this->macroName.endsWith(QLatin1String(".FCMacro")))
58
this->macroName += QLatin1String(".FCMacro");
60
this->macroInProgress.clear();
61
this->openMacro = true;
64
void MacroFile::append(const QString& line)
66
this->macroInProgress.append(line);
69
void MacroFile::append(const QStringList& lines)
71
this->macroInProgress.append(lines);
74
bool MacroFile::commit()
76
QFile file(this->macroName);
77
if (!file.open(QFile::WriteOnly)) {
81
// sort import lines and avoid duplicates
82
QTextStream str(&file);
84
import << QString::fromLatin1("import FreeCAD");
87
for (const auto& it : qAsConst(this->macroInProgress)) {
88
if (it.startsWith(QLatin1String("import ")) ||
89
it.startsWith(QLatin1String("#import "))) {
90
if (import.indexOf(it) == -1)
99
header += QString::fromLatin1("# -*- coding: utf-8 -*-\n\n");
100
header += QString::fromLatin1("# Macro Begin: ");
101
header += this->macroName;
102
header += QString::fromLatin1(" +++++++++++++++++++++++++++++++++++++++++++++++++\n");
104
QString footer = QString::fromLatin1("# Macro End: ");
105
footer += this->macroName;
106
footer += QString::fromLatin1(" +++++++++++++++++++++++++++++++++++++++++++++++++\n");
108
// write the data to the text file
110
for (const auto& it : qAsConst(import)) {
111
str << it << QLatin1Char('\n');
113
str << QLatin1Char('\n');
114
for (const auto& it : body) {
115
str << it << QLatin1Char('\n');
119
this->macroInProgress.clear();
120
this->macroName.clear();
121
this->openMacro = false;
126
void MacroFile::cancel()
128
this->macroInProgress.clear();
129
this->macroName.clear();
130
this->openMacro = false;
133
// ----------------------------------------------------------------------------
135
MacroOutputBuffer::MacroOutputBuffer() = default;
137
void MacroOutputBuffer::addPendingLine(int type, const char* line)
143
pendingLine.emplace_back(type, line);
147
bool MacroOutputBuffer::addPendingLineIfComment(int type, const char* line)
149
if (MacroOutputOption::isComment(type)) {
150
pendingLine.emplace_back(type, line);
157
void MacroOutputBuffer::incrementIfNoComment(int type)
159
if (!MacroOutputOption::isComment(type)) {
164
// ----------------------------------------------------------------------------
166
MacroOutputOption::MacroOutputOption() = default;
168
std::tuple<bool, bool> MacroOutputOption::values(int type) const
170
bool comment = isComment(type);
173
if (isGuiCommand(type)) {
174
if (recordGui && guiAsComment) {
177
else if (!recordGui) {
183
return std::make_tuple(comment, record);
186
bool MacroOutputOption::isComment(int type)
188
return type == MacroManager::Cmt;
191
bool MacroOutputOption::isGuiCommand(int type)
193
return type == MacroManager::Gui;
196
bool MacroOutputOption::isAppCommand(int type)
198
return type == MacroManager::App;
201
// ----------------------------------------------------------------------------
203
MacroManager::MacroManager()
204
: pyDebugger(new PythonDebugger())
206
// Attach to the Parametergroup regarding macros
207
this->params = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Macro");
208
this->params->Attach(this);
209
this->params->NotifyAll();
212
MacroManager::~MacroManager()
215
this->params->Detach(this);
218
void MacroManager::OnChange(Base::Subject<const char*> &rCaller, const char * sReason)
222
option.recordGui = this->params->GetBool("RecordGui", true);
223
option.guiAsComment = this->params->GetBool("GuiAsComment", true);
224
option.scriptToPyConsole = this->params->GetBool("ScriptToPyConsole", true);
225
this->localEnv = this->params->GetBool("LocalEnvironment", true);
228
void MacroManager::open(MacroType eType, const char *sName)
232
assert(eType == File);
237
macroFile.open(sName);
238
Base::Console().Log("CmdM: Open macro: %s\n", sName);
241
void MacroManager::commit()
243
QString macroName = macroFile.fileName();
244
if (macroFile.commit()) {
245
Base::Console().Log("Commit macro: %s\n", (const char*)macroName.toUtf8());
248
Base::Console().Error("Cannot open file to write macro: %s\n",
249
(const char*)macroName.toUtf8());
254
void MacroManager::cancel()
256
QString macroName = macroFile.fileName();
257
Base::Console().Log("Cancel macro: %s\n",(const char*)macroName.toUtf8());
261
void MacroManager::addPendingLine(LineType type, const char* line)
263
buffer.addPendingLine(type, line);
266
void MacroManager::addLine(LineType Type, const char* sLine)
271
if (buffer.hasPendingLines()) {
272
if (buffer.addPendingLineIfComment(Type, sLine)) {
276
processPendingLines();
279
buffer.incrementIfNoComment(Type);
281
addToOutput(Type, sLine);
284
void MacroManager::processPendingLines()
286
decltype(buffer.pendingLine) lines;
287
lines.swap(buffer.pendingLine);
288
for (auto &v : lines) {
289
addLine(static_cast<LineType>(v.first), v.second.c_str());
293
void MacroManager::makeComment(QStringList& lines) const
295
for (auto &line : lines) {
296
if (!line.startsWith(QLatin1String("#"))) {
297
line.prepend(QLatin1String("# "));
302
void MacroManager::addToOutput(LineType type, const char* line)
304
auto [comment, record] = option.values(type);
306
QStringList lines = QString::fromUtf8(line).split(QLatin1String("\n"));
311
if (record && macroFile.isOpen()) {
312
macroFile.append(lines);
315
if (option.scriptToPyConsole) {
316
// search for the Python console
317
auto console = getPythonConsole();
319
for(auto &line : lines) {
320
console->printStatement(line);
326
void MacroManager::setModule(const char* sModule)
328
if (macroFile.isOpen() && sModule && *sModule != '\0') {
329
macroFile.append(QString::fromLatin1("import %1").arg(QString::fromLatin1(sModule)));
333
PythonConsole* MacroManager::getPythonConsole() const
335
// search for the Python console
336
if (!this->pyConsole) {
337
this->pyConsole = Gui::getMainWindow()->findChild<Gui::PythonConsole*>();
340
return this->pyConsole;
344
class PythonRedirector
347
PythonRedirector(const char* type, PyObject* obj) : std_out(type), out(obj)
350
Base::PyGILStateLocker lock;
351
old = PySys_GetObject(std_out);
352
PySys_SetObject(std_out, out);
358
Base::PyGILStateLocker lock;
359
PySys_SetObject(std_out, old);
366
PyObject* old{nullptr};
370
void MacroManager::run(MacroType eType, const char *sName)
375
ParameterGrp::handle hGrp = App::GetApplication().GetUserParameter()
376
.GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("OutputWindow");
377
PyObject* pyout = hGrp->GetBool("RedirectPythonOutput",true) ? new OutputStdout : nullptr;
378
PyObject* pyerr = hGrp->GetBool("RedirectPythonErrors",true) ? new OutputStderr : nullptr;
379
PythonRedirector std_out("stdout",pyout);
380
PythonRedirector std_err("stderr",pyerr);
381
//The given path name is expected to be Utf-8
382
Base::Interpreter().runFile(sName, this->localEnv);
384
catch (const Base::SystemExitException&) {
387
catch (const Base::PyException& e) {
390
catch (const Base::Exception& e) {
391
qWarning("%s",e.what());
395
PythonDebugger* MacroManager::debugger() const