From ae480d70cda2183a872f584434aa72f4affcd357 Mon Sep 17 00:00:00 2001 From: marioalexis Date: Mon, 3 Apr 2023 01:12:15 -0300 Subject: [PATCH] Gui: Add CommandAction descriptor object to access commands action --- src/Gui/Application.cpp | 5 ++ src/Gui/CMakeLists.txt | 2 + src/Gui/Command.cpp | 48 +++++++++++ src/Gui/Command.h | 15 +++- src/Gui/CommandActionPy.cpp | 156 ++++++++++++++++++++++++++++++++++++ src/Gui/CommandActionPy.h | 59 ++++++++++++++ src/Gui/Workbench.cpp | 1 + 7 files changed, 284 insertions(+), 2 deletions(-) create mode 100644 src/Gui/CommandActionPy.cpp create mode 100644 src/Gui/CommandActionPy.h diff --git a/src/Gui/Application.cpp b/src/Gui/Application.cpp index b72bdfe712..2876166ecc 100644 --- a/src/Gui/Application.cpp +++ b/src/Gui/Application.cpp @@ -62,6 +62,7 @@ #include "AxisOriginPy.h" #include "BitmapFactory.h" #include "Command.h" +#include "CommandActionPy.h" #include "CommandPy.h" #include "Control.h" #include "DlgSettingsCacheDirectory.h" @@ -469,6 +470,10 @@ Application::Application(bool GUIenabled) Py::Object(Gui::TaskView::ControlPy::getInstance(), true)); Gui::TaskView::TaskDialogPy::init_type(); + CommandActionPy::init_type(); + Base::Interpreter().addType(CommandActionPy::type_object(), + module, "CommandAction"); + Base::Interpreter().addType(&LinkViewPy::Type, module, "LinkView"); Base::Interpreter().addType(&AxisOriginPy::Type, module, "AxisOrigin"); Base::Interpreter().addType(&CommandPy::Type, module, "Command"); diff --git a/src/Gui/CMakeLists.txt b/src/Gui/CMakeLists.txt index 77917c34bd..6255b96070 100644 --- a/src/Gui/CMakeLists.txt +++ b/src/Gui/CMakeLists.txt @@ -378,6 +378,7 @@ SET(Command_CPP_SRCS CommandPyImp.cpp ShortcutManager.cpp CommandCompleter.cpp + CommandActionPy.cpp ) SET(Command_SRCS ${Command_CPP_SRCS} @@ -387,6 +388,7 @@ SET(Command_SRCS CommandT.h ShortcutManager.h CommandCompleter.h + CommandActionPy.h ) SOURCE_GROUP("Command" FILES ${Command_SRCS}) diff --git a/src/Gui/Command.cpp b/src/Gui/Command.cpp index ad94654755..3eda58d7d4 100644 --- a/src/Gui/Command.cpp +++ b/src/Gui/Command.cpp @@ -1259,6 +1259,11 @@ PythonCommand::PythonCommand(const char* name, PyObject * pcPyCommand, const cha type += int(NoTransaction); eType = type; } + + auto& rcCmdMgr = Gui::Application::Instance->commandManager(); + + connPyCmdInitialized = rcCmdMgr.signalPyCmdInitialized.connect( + boost::bind(&PythonCommand::onActionInit, this)); } PythonCommand::~PythonCommand() @@ -1432,6 +1437,25 @@ bool PythonCommand::isChecked() const } } +void PythonCommand::onActionInit() const +{ + try { + Base::PyGILStateLocker lock; + Py::Object cmd(_pcPyCommand); + if (cmd.hasAttr("OnActionInit")) { + Py::Callable call(cmd.getAttr("OnActionInit")); + Py::Tuple args; + Py::Object ret = call.apply(args); + } + } + catch(Py::Exception& e) { + Base::PyGILStateLocker lock; + e.clear(); + } + + connPyCmdInitialized.disconnect(); +} + //=========================================================================== // PythonGroupCommand //=========================================================================== @@ -1466,6 +1490,11 @@ PythonGroupCommand::PythonGroupCommand(const char* name, PyObject * pcPyCommand) type += int(ForEdit); eType = type; } + + auto& rcCmdMgr = Gui::Application::Instance->commandManager(); + + connPyCmdInitialized = rcCmdMgr.signalPyCmdInitialized.connect( + boost::bind(&PythonGroupCommand::onActionInit, this)); } PythonGroupCommand::~PythonGroupCommand() @@ -1732,6 +1761,25 @@ bool PythonGroupCommand::hasDropDownMenu() const } } +void PythonGroupCommand::onActionInit() const +{ + try { + Base::PyGILStateLocker lock; + Py::Object cmd(_pcPyCommand); + if (cmd.hasAttr("OnActionInit")) { + Py::Callable call(cmd.getAttr("OnActionInit")); + Py::Tuple args; + Py::Object ret = call.apply(args); + } + } + catch(Py::Exception& e) { + Base::PyGILStateLocker lock; + e.clear(); + } + + connPyCmdInitialized.disconnect(); +} + //=========================================================================== // CommandManager //=========================================================================== diff --git a/src/Gui/Command.h b/src/Gui/Command.h index 5d68582f0d..39be8aa89a 100644 --- a/src/Gui/Command.h +++ b/src/Gui/Command.h @@ -686,8 +686,6 @@ protected: bool isActive() override; /// Get the help URL const char* getHelpUrl() const override; - /// Creates the used Action - Action * createAction() override; //@} public: @@ -710,12 +708,18 @@ public: protected: /// Returns the resource values const char* getResource(const char* sName) const; + /// Creates the used Action + Action * createAction() override; /// a pointer to the Python command object PyObject * _pcPyCommand; /// the command object resource dictionary PyObject * _pcPyResourceDict; /// the activation sequence std::string Activation; + //// set the parameters on action creation + void onActionInit() const; + + boost::signals2::connection connPyCmdInitialized; }; /** The Python group command class @@ -761,10 +765,14 @@ public: protected: /// Returns the resource values const char* getResource(const char* sName) const; + //// set the parameters on action creation + void onActionInit() const; /// a pointer to the Python command object PyObject * _pcPyCommand; /// the command object resources PyObject * _pcPyResource; + + boost::signals2::connection connPyCmdInitialized; }; @@ -887,6 +895,9 @@ public: /// Signal on any addition or removal of command boost::signals2::signal signalChanged; + /// Signal to Python command on first workbench activation + boost::signals2::signal signalPyCmdInitialized; + /** * Returns a pointer to a conflicting command, or nullptr if there is no conflict. * In the case of multiple conflicts, only the first is returned. diff --git a/src/Gui/CommandActionPy.cpp b/src/Gui/CommandActionPy.cpp new file mode 100644 index 0000000000..d510c3adf2 --- /dev/null +++ b/src/Gui/CommandActionPy.cpp @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +/*************************************************************************** + * Copyright (c) 2023 Mario Passaglia * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + +#include + +#include "Command.h" +#include "Action.h" +#include "PythonWrapper.h" + +#include "CommandActionPy.h" +#include "CommandPy.h" + + +using namespace Gui; + +CommandActionPy::CommandActionPy(Py::PythonClassInstance* self, Py::Tuple& args, Py::Dict& kwds) + : Py::PythonClass::PythonClass(self, args, kwds) +{ + const char* name; + if (!PyArg_ParseTuple(args.ptr(), "s", &name)) { + throw Py::Exception(); + } + + cmdName = name; + cmd = Application::Instance->commandManager().getCommandByName(name); +} + +CommandActionPy::~CommandActionPy() +{ +} + +Py::Object CommandActionPy::getAction() +{ + if (!cmd) { + cmd = Application::Instance->commandManager().getCommandByName(cmdName.c_str()); + } + + if (cmd) { + Action* action = cmd->getAction(); + PythonWrapper wrap; + wrap.loadWidgetsModule(); + + return wrap.fromQObject(action->action()); + } + else { + return Py::None(); + } +} + +Py::Object CommandActionPy::getCommand() +{ + if (!cmd) { + cmd = Application::Instance->commandManager().getCommandByName(cmdName.c_str()); + } + + if (cmd) { + auto cmdPy = new CommandPy(cmd); + return Py::asObject(cmdPy); + } + + return Py::None(); +} +PYCXX_NOARGS_METHOD_DECL(CommandActionPy, getCommand) + +void CommandActionPy::init_type() +{ + Base::PythonTypeExt ext(behaviors()); + + behaviors().name("Gui.CommandAction"); + behaviors().doc("Descriptor to access the action of the commands"); + behaviors().supportRepr(); + behaviors().supportGetattro(); + behaviors().supportSetattro(); + ext.set_tp_descr_get(&CommandActionPy::descriptorGetter); + ext.set_tp_descr_set(&CommandActionPy::descriptorSetter); + PYCXX_ADD_NOARGS_METHOD(getCommand, getCommand, "Descriptor associated command"); + + behaviors().readyType(); +} + +PyObject* CommandActionPy::descriptorGetter(PyObject* self, PyObject* /*obj*/, PyObject* /*type*/) +{ + auto cmdAction = Py::PythonClassObject(self).getCxxObject(); + + return Py::new_reference_to(cmdAction->getAction()); +} + +int CommandActionPy::descriptorSetter(PyObject* /*self*/, PyObject* /*obj*/, PyObject* value) +{ + if (value) { + PyErr_SetString(PyExc_AttributeError, "Can't overwrite command action"); + } + else { + PyErr_SetString(PyExc_AttributeError, "Can't delete command action"); + } + + return -1; +} + +Py::Object CommandActionPy::repr() +{ + std::stringstream s; + s << this->cmdName << " command action descriptor"; + + return Py::String(s.str()); +} + +Py::Object CommandActionPy::getattro(const Py::String& attr_) +{ + std::string attr = static_cast(attr_); + Py::Dict d; + d["name"] = Py::String(this->cmdName); + if (attr == "__dict__") { + return d; + } + else if (attr == "name") { + return d["name"]; + } + else { + return genericGetAttro(attr_); + } +} + +int CommandActionPy::setattro(const Py::String& attr_, const Py::Object& value) +{ + std::string attr = static_cast(attr_); + if (attr == "name" && value.isString()) { + cmdName = static_cast(Py::String(value)); + cmd = Application::Instance->commandManager().getCommandByName(cmdName.c_str()); + } + else { + return genericSetAttro(attr_, value); + } + + return 0; +} diff --git a/src/Gui/CommandActionPy.h b/src/Gui/CommandActionPy.h new file mode 100644 index 0000000000..c34281a7df --- /dev/null +++ b/src/Gui/CommandActionPy.h @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +/*************************************************************************** + * Copyright (c) 2023 Mario Passaglia * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + +#ifndef GUI_COMMANDACTIONPY_H +#define GUI_COMMANDACTIONPY_H + +#include + + +namespace Gui +{ + +class CommandActionPy : public Py::PythonClass +{ +public: + static void init_type(); + + CommandActionPy(Py::PythonClassInstance* self, Py::Tuple& args, Py::Dict& kdws); + ~CommandActionPy() override; + + Py::Object getCommand(); + +protected: + static PyObject* descriptorGetter(PyObject* self, PyObject* obj, PyObject* type); + static int descriptorSetter(PyObject* self, PyObject* obj, PyObject* value); + Py::Object repr() override; + Py::Object getattro(const Py::String& attr) override; + int setattro(const Py::String& attr_, const Py::Object& value) override; + + Py::Object getAction(); + +private: + std::string cmdName; + Command* cmd = nullptr; +}; + +} // namespace Gui + +#endif // GUI_COMMANDACTIONPY_H diff --git a/src/Gui/Workbench.cpp b/src/Gui/Workbench.cpp index 961e9f4fa2..3eb97f0df5 100644 --- a/src/Gui/Workbench.cpp +++ b/src/Gui/Workbench.cpp @@ -393,6 +393,7 @@ void Workbench::addPermanentMenuItems(MenuItem* mb) const void Workbench::activated() { + Application::Instance->commandManager().signalPyCmdInitialized(); } void Workbench::deactivated()