/*************************************************************************** * Copyright (c) 2004 Jürgen Riegel * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library 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 Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ #include #include #include #include #include #include #include #include "Macro.h" #include "MainWindow.h" #include "PythonConsole.h" #include "PythonConsolePy.h" #include "PythonDebugger.h" using namespace Gui; MacroFile::MacroFile() = default; void MacroFile::open(const char* sName) { // check #if _DEBUG Q_ASSERT(!this->openMacro); #endif // Convert from Utf-8 this->macroName = QString::fromUtf8(sName); if (!this->macroName.endsWith(QLatin1String(".FCMacro"))) { this->macroName += QLatin1String(".FCMacro"); } this->macroInProgress.clear(); this->openMacro = true; } void MacroFile::append(const QString& line) { this->macroInProgress.append(line); } void MacroFile::append(const QStringList& lines) { this->macroInProgress.append(lines); } bool MacroFile::commit() { QFile file(this->macroName); if (!file.open(QFile::WriteOnly)) { return false; } // sort import lines and avoid duplicates QTextStream str(&file); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) str.setCodec("UTF-8"); #endif QStringList importCommand; importCommand << QStringLiteral("import FreeCAD"); QStringList body; for (const auto& it : std::as_const(this->macroInProgress)) { if (it.startsWith(QLatin1String("import ")) || it.startsWith(QLatin1String("#import "))) { if (importCommand.indexOf(it) == -1) { importCommand.push_back(it); } } else { body.push_back(it); } } QString header; header += QStringLiteral("# Macro Begin: "); header += this->macroName; header += QStringLiteral(" +++++++++++++++++++++++++++++++++++++++++++++++++\n"); QString footer = QStringLiteral("# Macro End: "); footer += this->macroName; footer += QStringLiteral(" +++++++++++++++++++++++++++++++++++++++++++++++++\n"); // write the data to the text file str << header; for (const auto& it : std::as_const(importCommand)) { str << it << QLatin1Char('\n'); } str << QLatin1Char('\n'); for (const auto& it : body) { str << it << QLatin1Char('\n'); } str << footer; this->macroInProgress.clear(); this->macroName.clear(); this->openMacro = false; file.close(); return true; } void MacroFile::cancel() { this->macroInProgress.clear(); this->macroName.clear(); this->openMacro = false; } // ---------------------------------------------------------------------------- MacroOutputBuffer::MacroOutputBuffer() = default; void MacroOutputBuffer::addPendingLine(int type, const char* line) { if (!line) { pendingLine.clear(); } else { pendingLine.emplace_back(type, line); } } bool MacroOutputBuffer::addPendingLineIfComment(int type, const char* line) { if (MacroOutputOption::isComment(type)) { pendingLine.emplace_back(type, line); return true; } return false; } void MacroOutputBuffer::incrementIfNoComment(int type) { if (!MacroOutputOption::isComment(type)) { ++totalLines; } } // ---------------------------------------------------------------------------- MacroOutputOption::MacroOutputOption() = default; std::tuple MacroOutputOption::values(int type) const { bool comment = isComment(type); bool record = true; if (isGuiCommand(type)) { if (recordGui && guiAsComment) { comment = true; } else if (!recordGui) { comment = true; record = false; } } return std::make_tuple(comment, record); } bool MacroOutputOption::isComment(int type) { return type == MacroManager::Cmt; } bool MacroOutputOption::isGuiCommand(int type) { return type == MacroManager::Gui; } bool MacroOutputOption::isAppCommand(int type) { return type == MacroManager::App; } // ---------------------------------------------------------------------------- MacroManager::MacroManager() : pyDebugger(new PythonDebugger()) { // Attach to the Parametergroup regarding macros this->params = App::GetApplication().GetParameterGroupByPath( "User parameter:BaseApp/Preferences/Macro" ); this->params->Attach(this); this->params->NotifyAll(); } MacroManager::~MacroManager() { delete pyDebugger; this->params->Detach(this); } std::stack> MacroManager::redirectFuncs; void MacroManager::OnChange(Base::Subject& rCaller, const char* sReason) { (void)rCaller; (void)sReason; option.recordGui = this->params->GetBool("RecordGui", true); option.guiAsComment = this->params->GetBool("GuiAsComment", true); option.scriptToPyConsole = this->params->GetBool("ScriptToPyConsole", true); this->localEnv = this->params->GetBool("LocalEnvironment", true); } void MacroManager::open(MacroType eType, const char* sName) { // check #if _DEBUG assert(eType == File); #else Q_UNUSED(eType); #endif macroFile.open(sName); Base::Console().log("CmdM: Open macro: %s\n", sName); } void MacroManager::commit() { QString macroName = macroFile.fileName(); if (macroFile.commit()) { Base::Console().log("Commit macro: %s\n", (const char*)macroName.toUtf8()); } else { Base::Console().error("Cannot open file to write macro: %s\n", (const char*)macroName.toUtf8()); cancel(); } } void MacroManager::cancel() { QString macroName = macroFile.fileName(); Base::Console().log("Cancel macro: %s\n", (const char*)macroName.toUtf8()); macroFile.cancel(); } void MacroManager::addPendingLine(LineType type, const char* line) { buffer.addPendingLine(type, line); } void MacroManager::addLine(LineType Type, const char* sLine) { if (!sLine) { return; } std::function redirectFunc = redirectFuncs.empty() ? nullptr : redirectFuncs.top(); if (redirectFunc) { redirectFunc(Type, sLine); return; } if (buffer.hasPendingLines()) { if (buffer.addPendingLineIfComment(Type, sLine)) { return; } processPendingLines(); } buffer.incrementIfNoComment(Type); addToOutput(Type, sLine); } void MacroManager::processPendingLines() { decltype(buffer.pendingLine) lines; lines.swap(buffer.pendingLine); for (auto& v : lines) { addLine(static_cast(v.first), v.second.c_str()); } } void MacroManager::makeComment(QStringList& lines) const { for (auto& line : lines) { if (!line.startsWith(QLatin1String("#"))) { line.prepend(QLatin1String("# ")); } } } void MacroManager::addToOutput(LineType type, const char* line) { auto [comment, record] = option.values(type); QStringList lines = QString::fromUtf8(line).split(QLatin1String("\n")); if (comment) { makeComment(lines); } if (record && macroFile.isOpen()) { macroFile.append(lines); } if (option.scriptToPyConsole) { // search for the Python console auto console = getPythonConsole(); if (console) { for (auto& line : lines) { console->printStatement(line); } } } } void MacroManager::setModule(const char* sModule) { if (macroFile.isOpen() && sModule && *sModule != '\0') { macroFile.append(QStringLiteral("import %1").arg(QString::fromLatin1(sModule))); } } PythonConsole* MacroManager::getPythonConsole() const { // search for the Python console if (!this->pyConsole) { this->pyConsole = Gui::getMainWindow()->findChild(); } return this->pyConsole; } namespace Gui { class PythonRedirector { public: PythonRedirector(const char* type, PyObject* obj) : std_out(type) , out(obj) { if (out) { Base::PyGILStateLocker lock; old = PySys_GetObject(std_out); PySys_SetObject(std_out, out); } } ~PythonRedirector() { if (out) { Base::PyGILStateLocker lock; PySys_SetObject(std_out, old); Py_DECREF(out); } } private: const char* std_out; PyObject* out; PyObject* old {nullptr}; }; } // namespace Gui void MacroManager::run(MacroType eType, const char* sName) { Q_UNUSED(eType); try { ParameterGrp::handle hGrp = App::GetApplication() .GetUserParameter() .GetGroup("BaseApp") ->GetGroup("Preferences") ->GetGroup("OutputWindow"); PyObject* pyout = hGrp->GetBool("RedirectPythonOutput", true) ? new OutputStdout : nullptr; PyObject* pyerr = hGrp->GetBool("RedirectPythonErrors", true) ? new OutputStderr : nullptr; PythonRedirector std_out("stdout", pyout); PythonRedirector std_err("stderr", pyerr); // The given path name is expected to be Utf-8 Base::Interpreter().runFile(sName, this->localEnv); } catch (const Base::SystemExitException&) { throw; } catch (const Base::PyException& e) { e.reportException(); } catch (const Base::Exception& e) { qWarning("%s", e.what()); } } PythonDebugger* MacroManager::debugger() const { return pyDebugger; }