diff --git a/src/Gui/ApplicationPy.cpp b/src/Gui/ApplicationPy.cpp index b0d407e20f73..0457290d2b5b 100644 --- a/src/Gui/ApplicationPy.cpp +++ b/src/Gui/ApplicationPy.cpp @@ -812,37 +812,16 @@ PyObject* Application::sAddCommand(PyObject * /*self*/, PyObject *args,PyObject PyObject* pcCmdObj; if (!PyArg_ParseTuple(args, "sO|s", &pName,&pcCmdObj,&pSource)) // convert args: Python->C return NULL; // NULL triggers exception -#if 0 - std::string source = (pSource ? pSource : ""); - - if (source.empty()) { - try { - Py::Module module(PyImport_ImportModule("inspect"),true); - Py::Dict dict = module.getDict(); - Py::Callable call(dict.getItem("getsourcelines")); - Py::Tuple arg(1); - arg.setItem(0, Py::Object(pcCmdObj).getAttr("Activated")); - Py::Tuple tuple(call.apply(arg)); - Py::List lines(tuple[0]); - - int pos=0; - std::string code = (std::string)(Py::String(lines[1])); - while (code[pos] == ' ' || code[pos] == '\t') - pos++; - for (Py::List::iterator it = lines.begin()+1; it != lines.end(); ++it) { - Py::String str(*it); - source += ((std::string)str).substr(pos); - } - } - catch (Py::Exception& e) { - e.clear(); - } - } - Application::Instance->commandManager().addCommand(new PythonCommand(pName,pcCmdObj,source.c_str())); -#else try { - Application::Instance->commandManager().addCommand(new PythonCommand(pName,pcCmdObj,pSource)); + Base::PyGILStateLocker lock; + Py::Object cmd(pcCmdObj); + if (cmd.hasAttr("GetCommands")) { + Application::Instance->commandManager().addCommand(new PythonGroupCommand(pName, pcCmdObj)); + } + else { + Application::Instance->commandManager().addCommand(new PythonCommand(pName, pcCmdObj, pSource)); + } } catch (const Base::Exception& e) { PyErr_SetString(Base::BaseExceptionFreeCADError, e.what()); @@ -852,7 +831,7 @@ PyObject* Application::sAddCommand(PyObject * /*self*/, PyObject *args,PyObject PyErr_SetString(Base::BaseExceptionFreeCADError, "Unknown C++ exception raised in Application::sAddCommand()"); return 0; } -#endif + Py_INCREF(Py_None); return Py_None; } diff --git a/src/Gui/Command.cpp b/src/Gui/Command.cpp index 0b1dbe27d8be..7952f4593d38 100644 --- a/src/Gui/Command.cpp +++ b/src/Gui/Command.cpp @@ -793,7 +793,7 @@ void MacroCommand::save() // PythonCommand //=========================================================================== -PythonCommand::PythonCommand(const char* name,PyObject * pcPyCommand, const char* pActivationString) +PythonCommand::PythonCommand(const char* name, PyObject * pcPyCommand, const char* pActivationString) : Command(name),_pcPyCommand(pcPyCommand) { if (pActivationString) @@ -945,6 +945,238 @@ const char* PythonCommand::getAccel() const return getResource("Accel"); } +//=========================================================================== +// PythonGroupCommand +//=========================================================================== + +PythonGroupCommand::PythonGroupCommand(const char* name, PyObject * pcPyCommand) + : Command(name),_pcPyCommand(pcPyCommand) +{ + sGroup = "Python"; + + Py_INCREF(_pcPyCommand); + + // call the method "GetResources()" of the command object + _pcPyResource = Interpreter().runMethodObject(_pcPyCommand, "GetResources"); + // check if the "GetResources()" method returns a Dict object + if (!PyDict_Check(_pcPyResource)) { + throw Base::TypeError("PythonGroupCommand::PythonGroupCommand(): Method GetResources() of the Python " + "command object returns the wrong type (has to be Py Dictonary)"); + } + + // check for command type + std::string cmdType = getResource("CmdType"); + if (!cmdType.empty()) { + int type = 0; + if (cmdType.find("AlterDoc") != std::string::npos) + type += int(AlterDoc); + if (cmdType.find("Alter3DView") != std::string::npos) + type += int(Alter3DView); + if (cmdType.find("AlterSelection") != std::string::npos) + type += int(AlterSelection); + if (cmdType.find("ForEdit") != std::string::npos) + type += int(ForEdit); + eType = type; + } +} + +PythonGroupCommand::~PythonGroupCommand() +{ + Base::PyGILStateLocker lock; + Py_DECREF(_pcPyCommand); +} + +void PythonGroupCommand::activated(int iMsg) +{ + try { + Gui::ActionGroup* pcAction = qobject_cast(_pcAction); + QList a = pcAction->actions(); + assert(iMsg < a.size()); + QAction* act = a[iMsg]; + + Base::PyGILStateLocker lock; + Py::Object cmd(_pcPyCommand); + if (cmd.hasAttr("Activated")) { + Py::Callable call(cmd.getAttr("Activated")); + Py::Tuple args(1); + args.setItem(0, Py::Int(iMsg)); + Py::Object ret = call.apply(args); + } + // If the command group doesn't implement the 'Activated' method then invoke the command directly + else { + Gui::CommandManager &rcCmdMgr = Gui::Application::Instance->commandManager(); + rcCmdMgr.runCommandByName(act->property("CommandName").toByteArray()); + } + + // Since the default icon is reset when enabing/disabling the command we have + // to explicitly set the icon of the used command. + pcAction->setIcon(a[iMsg]->icon()); + } + catch(Py::Exception&) { + Base::PyGILStateLocker lock; + Base::PyException e; + Base::Console().Error("Running the Python command '%s' failed:\n%s\n%s", + sName, e.getStackTrace().c_str(), e.what()); + } +} + +bool PythonGroupCommand::isActive(void) +{ + try { + Base::PyGILStateLocker lock; + Py::Object cmd(_pcPyCommand); + if (cmd.hasAttr("IsActive")) { + Py::Callable call(cmd.getAttr("IsActive")); + Py::Tuple args; + Py::Object ret = call.apply(args); + // if return type is not boolean or not true + if (!PyBool_Check(ret.ptr()) || ret.ptr() != Py_True) + return false; + } + } + catch(Py::Exception& e) { + Base::PyGILStateLocker lock; + e.clear(); + return false; + } + + return true; +} + +Action * PythonGroupCommand::createAction(void) +{ + Gui::ActionGroup* pcAction = new Gui::ActionGroup(this, Gui::getMainWindow()); + pcAction->setDropDownMenu(true); + + applyCommandData(this->getName(), pcAction); + + int defaultId = 0; + + try { + Base::PyGILStateLocker lock; + Py::Object cmd(_pcPyCommand); + + Py::Callable call(cmd.getAttr("GetCommands")); + Py::Tuple args; + Py::Tuple ret(call.apply(args)); + for (Py::Tuple::iterator it = ret.begin(); it != ret.end(); ++it) { + Py::String str(*it); + QAction* cmd = pcAction->addAction(QString()); + cmd->setProperty("CommandName", QByteArray(static_cast(str).c_str())); + } + + if (cmd.hasAttr("GetDefaultCommand")) { + Py::Callable call2(cmd.getAttr("GetDefaultCommand")); + Py::Int def(call2.apply(args)); + defaultId = static_cast(def); + } + } + catch(Py::Exception&) { + Base::PyGILStateLocker lock; + Base::PyException e; + Base::Console().Error("createAction() of the Python command '%s' failed:\n%s\n%s", + sName, e.getStackTrace().c_str(), e.what()); + } + + _pcAction = pcAction; + languageChange(); + + if (strcmp(getResource("Pixmap"),"") != 0) { + pcAction->setIcon(Gui::BitmapFactory().pixmap(getResource("Pixmap"))); + } + else { + QList a = pcAction->actions(); + if (a.size() > defaultId) + pcAction->setIcon(a[defaultId]->icon()); + } + + pcAction->setProperty("defaultAction", QVariant(defaultId)); + + return pcAction; +} + +void PythonGroupCommand::languageChange() +{ + if (!_pcAction) + return; + + applyCommandData(this->getName(), _pcAction); + + Gui::CommandManager &rcCmdMgr = Gui::Application::Instance->commandManager(); + Gui::ActionGroup* pcAction = qobject_cast(_pcAction); + QList a = pcAction->actions(); + for (QList::iterator it = a.begin(); it != a.end(); ++it) { + Gui::Command* cmd = rcCmdMgr.getCommandByName((*it)->property("CommandName").toByteArray()); + // Python command use getName as context + if (dynamic_cast(cmd)) { + (*it)->setIcon(Gui::BitmapFactory().pixmap(cmd->getPixmap())); + (*it)->setText(QApplication::translate(cmd->getName(), cmd->getMenuText())); + (*it)->setToolTip(QApplication::translate(cmd->getName(), cmd->getToolTipText())); + (*it)->setStatusTip(QApplication::translate(cmd->getName(), cmd->getStatusTip())); + } + else if (cmd) { + (*it)->setIcon(Gui::BitmapFactory().pixmap(cmd->getPixmap())); + (*it)->setText(QApplication::translate(cmd->className(), cmd->getMenuText())); + (*it)->setToolTip(QApplication::translate(cmd->className(), cmd->getToolTipText())); + (*it)->setStatusTip(QApplication::translate(cmd->className(), cmd->getStatusTip())); + } + } +} + +const char* PythonGroupCommand::getHelpUrl(void) const +{ + return ""; +} + +const char* PythonGroupCommand::getResource(const char* sName) const +{ + PyObject* pcTemp; + + // get the "MenuText" resource string + pcTemp = PyDict_GetItemString(_pcPyResource, sName); + if (!pcTemp) + return ""; + if (!PyString_Check(pcTemp)) { + throw Base::ValueError("PythonGroupCommand::getResource(): Method GetResources() of the Python " + "group command object returns a dictionary which holds not only strings"); + } + + return PyString_AsString(pcTemp); +} + +const char* PythonGroupCommand::getWhatsThis() const +{ + const char* whatsthis = getResource("WhatsThis"); + if (!whatsthis || whatsthis[0] == '\0') + whatsthis = this->getName(); + return whatsthis; +} + +const char* PythonGroupCommand::getMenuText() const +{ + return getResource("MenuText"); +} + +const char* PythonGroupCommand::getToolTipText() const +{ + return getResource("ToolTip"); +} + +const char* PythonGroupCommand::getStatusTip() const +{ + return getResource("StatusTip"); +} + +const char* PythonGroupCommand::getPixmap() const +{ + return getResource("Pixmap"); +} + +const char* PythonGroupCommand::getAccel() const +{ + return getResource("Accel"); +} + //=========================================================================== // CommandManager //=========================================================================== diff --git a/src/Gui/Command.h b/src/Gui/Command.h index 7f51acd51b25..e029824282ad 100644 --- a/src/Gui/Command.h +++ b/src/Gui/Command.h @@ -322,7 +322,7 @@ class GuiExport Command : public CommandBase class PythonCommand: public Command { public: - PythonCommand(const char* name,PyObject * pcPyCommand, const char* pActivationString); + PythonCommand(const char* name, PyObject * pcPyCommand, const char* pActivationString); virtual ~PythonCommand() {} protected: @@ -364,6 +364,53 @@ class PythonCommand: public Command std::string Activation; }; +/** The Python group command class + * @see CommandManager + * @author Werner Mayer + */ +class PythonGroupCommand: public Command +{ +public: + PythonGroupCommand(const char* name, PyObject * pcPyCommand); + virtual ~PythonGroupCommand(); + +protected: + /** @name Methods reimplemented for Command Framework */ + //@{ + /// Method which gets called when activated + virtual void activated(int iMsg); + /// if the command is not always active + virtual bool isActive(void); + /// Get the help URL + const char* getHelpUrl(void) const; + /// Creates the used Action + virtual Action * createAction(void); + //@} + +public: + /** @name Methods to get the properties of the command */ + //@{ + /// Reassigns QAction stuff after the language has changed. + void languageChange(); + const char* className() const + { return "PythonGroupCommand"; } + const char* getWhatsThis () const; + const char* getMenuText () const; + const char* getToolTipText() const; + const char* getStatusTip () const; + const char* getPixmap () const; + const char* getAccel () const; + //@} + +protected: + /// Returns the resource values + const char* getResource(const char* sName) const; + /// a pointer to the Python command object + PyObject * _pcPyCommand; + /// the command object resources + PyObject * _pcPyResource; +}; + /** The script command class * This is a special type of command class. Its used to bind a macro or Python script to the diff --git a/src/Mod/TemplatePyMod/Commands.py b/src/Mod/TemplatePyMod/Commands.py index 7e21251a3af4..432ff0756e9c 100644 --- a/src/Mod/TemplatePyMod/Commands.py +++ b/src/Mod/TemplatePyMod/Commands.py @@ -193,6 +193,44 @@ def Activated(self): def GetResources(self): return {'Pixmap' : 'python', 'MenuText': 'Create a box', 'ToolTip': 'Use Box feature class which is completely written in Python'} +class TemplatePyGrp_1: + def Activated(self): + import FreeCAD + FreeCAD.Console.PrintMessage("TemplatePyGrp_1\n") + + def GetResources(self): + return {'Pixmap' : 'Part_JoinConnect', 'MenuText': 'TemplatePyGrp_1', 'ToolTip': 'Print a message'} + +class TemplatePyGrp_2: + def Activated(self): + import FreeCAD + FreeCAD.Console.PrintMessage("TemplatePyGrp_2\n") + + def GetResources(self): + return {'Pixmap' : 'Part_JoinEmbed', 'MenuText': 'TemplatePyGrp_2', 'ToolTip': 'Print a message'} + +class TemplatePyGrp_3: + def Activated(self): + import FreeCAD + FreeCAD.Console.PrintMessage("TemplatePyGrp_3\n") + + def GetResources(self): + return {'Pixmap' : 'Part_JoinCutout', 'MenuText': 'TemplatePyGrp_3', 'ToolTip': 'Print a message'} + +class TemplatePyGroup: + "Example group command class" + #def Activated(self, index): + # print "TemplatePyGroup activated ;-) " + + def GetCommands(self): + return ("TemplatePyGrp_1", "TemplatePyGrp_2", "TemplatePyGrp_3", "Std_New") + + def GetDefaultCommand(self): + return 2 + + def GetResources(self): + return {'Pixmap' : 'python', 'MenuText': 'Group command', 'ToolTip': 'Example group command'} + #--------------------------------------------------------------------------- # Adds the commands to the FreeCAD command manager #--------------------------------------------------------------------------- @@ -202,4 +240,7 @@ def GetResources(self): FreeCADGui.addCommand('TemplatePyMod_Cmd4',TemplatePyMod_Cmd4()) FreeCADGui.addCommand('TemplatePyMod_Cmd5',TemplatePyMod_Cmd5()) FreeCADGui.addCommand('TemplatePyMod_Cmd6',TemplatePyMod_Cmd6()) - +FreeCADGui.addCommand('TemplatePyGrp_1',TemplatePyGrp_1()) +FreeCADGui.addCommand('TemplatePyGrp_2',TemplatePyGrp_2()) +FreeCADGui.addCommand('TemplatePyGrp_3',TemplatePyGrp_3()) +FreeCADGui.addCommand('TemplatePyGroup',TemplatePyGroup())