FreeeCAD源码剖析之注册Command

2020年11月26日 3725点热度 3人点赞 0条评论

前言

最近一个接到这样一个需求,需要在FreeCAD的中加入一个自定义的工程文件。这需要搞懂原本框架中的新建工程、打开工程到保存撤销回复再到保存另存的一整套流程,还需要搞懂工程与UI之间的对应关系。于是开始翻看FreeCAD得源代码,一边看一边惊呼,居然还这样操作。然是我才疏学浅,这源代码使我受益匪浅。

整个工程得流程,可以分为三个部分:

  1. Command。这部分主要往程序主框架中注册一些命令,如新建、打开、保存等这些基本操作,然后将这些命令放到界面上去供用户使用。
  2. UI。这个主要指工程所对应的ui,比如在一个模型工程,那么这个工程必定会有一个显示模型的一个3D视窗。这个视窗就是这里只得ui
  3. Document。这个类是原框架中对应工程数据的一个类。

Command

Command类是FreeCAD源代码提供的一个抽象类,如果想要实现一个自定义的命令,那么最好是继承这个类。这个类中已经实现了十分多的通用功能。

而Command则是继承了一个叫做CommandBase的类。

/** The CommandBase class
* This lightweigt class is the base class of all commands in FreeCAD. It represents the link between the FreeCAD
* command framework and the QAction world of Qt.
* @author Werner Mayer
*/
class GuiExport CommandBase
{
protected:
CommandBase(const char* sMenu, const char* sToolTip=0, const char* sWhat=0,
const char* sStatus=0, const char* sPixmap=0, const char* sAccel=0);
virtual ~CommandBase();

public:
/**
* Returns the Action object of this command, or 0 if it doesn't exist.
*/
Action* getAction() const;

/** @name Methods to override when creating a new command */
//@{
protected:
/// Creates the used Action when adding to a widget. The default implementation does nothing.
virtual Action * createAction(void);

public:
/// Reassigns QAction stuff after the language has changed.
virtual void languageChange() = 0;
/// Updates the QAction with respect to the passed mode.
virtual void updateAction(int mode) = 0;
/// The C++ class name is needed as context for the translation framework
virtual const char* className() const = 0;
//@}

/** @name Methods to get the properties of the command */
//@{
virtual const char* getMenuText () const { return sMenuText; }
virtual const char* getToolTipText() const { return sToolTipText; }
virtual const char* getStatusTip () const { return sStatusTip; }
virtual const char* getWhatsThis () const { return sWhatsThis; }
virtual const char* getPixmap () const { return sPixmap; }
virtual const char* getAccel () const { return sAccel; }
//@}

/** @name Methods to set the properties of the command */
//@{
void setWhatsThis (const char*);
void setMenuText (const char*);
void setToolTipText(const char*);
void setStatusTip (const char*);
void setPixmap (const char*);
void setAccel (const char*);
//@}

protected:
/** @name Attributes set by the inherited constructor.
*
* They set up the most important properties of the command.
* In the constructor are set default values.
* The real values should be set in the constructor of the inheriting class.
*/
//@{
const char* sMenuText;
const char* sToolTipText;
const char* sWhatsThis;
const char* sStatusTip;
const char* sPixmap;
const char* sAccel;
//@}
protected:
Action *_pcAction;

 

这个类的主要做了两件事:

  1. 为commad创action,包括action对应的图标、menuText、whatThis、快捷键等显示相关的信息。
  2. 定义了一个languageChange()的纯虚函数,用于软件的语言发生改变时,改变command的显示语言。

然后时commad类,主要关注这几个函数。

virtual void activated(int iMsg)=0;

这个纯虚函数,当命令被执行或者对应的action被点击的时候会被调用。

virtual bool isActive(void);

当命令被注册入框架的时候,会定时调用这个函数检查命令的状态。(目前看来设置为false会导致界面上的action变成不可用的状态)

其他的就是一些静态的类,主要时对document、撤销回复、的一些操作,有些我也没搞太懂,这里就不展开了。


来看一下源代码中是如何定义一个命令并将命令注册到框架中的。

/** The Command Macro Standard + isActive() + createAction()
* This macro makes it easier to define a new command.
* The parameters are the class name
* @author Werner Mayer
*/
#define DEF_STD_CMD_AC(X) class X : public Gui::Command \
{\
public:\
X();\
virtual ~X(){}\
virtual const char* className() const\
{ return #X; }\
protected: \
virtual void activated(int iMsg);\
virtual bool isActive(void);\
virtual Gui::Action * createAction(void);\
};
DEF_STD_CMD_AC(StdCmdUndo);

StdCmdUndo::StdCmdUndo()
:Command("Std_Undo")
{
sGroup = QT_TR_NOOP("Edit");
sMenuText = QT_TR_NOOP("&Undo");
sToolTipText = QT_TR_NOOP("Undo exactly one action");
sWhatsThis = "Std_Undo";
sStatusTip = QT_TR_NOOP("Undo exactly one action");
sPixmap = "edit-undo";
sAccel = keySequenceToAccel(QKeySequence::Undo);
eType = ForEdit;
}

void StdCmdUndo::activated(int iMsg)
{
Q_UNUSED(iMsg);
// Application::Instance->slotUndo();
getGuiApplication()->sendMsgToActiveView("Undo");
App::Document* pcDoc=App::GetApplication().getActiveDocument();
//pcDoc->recompute();
pcDoc->flagNeedUpdateBoolean.setValue(0);
doCommand(Command::Gui, "DocumentTools.updateBoolean()");
}

bool StdCmdUndo::isActive(void)
{
return getGuiApplication()->sendHasMsgToActiveView("Undo");
}

void RegisterCommands(void)
{
CommandManager &rcCmdMgr = Application::Instance->commandManager();
rcCmdMgr.addCommand(new StdCmdUndo());
}

理一下思路:

  1. 创建一个类,继承commad类。(真懒啊,国外的大佬,创建类都要用宏来实现)。
  2. 在构造函数中初始化command的信息,主要是action的显示信息以及快捷键等。
  3. 实现activated函数,把这个命令想要触发的东西都写在这个里面。

最后只需要调用RegisterCommand()函数就可以成功注册命令了。

 

大脸猫

这个人虽然很勤快,但什么也没有留下!

文章评论