前言
最近一个接到这样一个需求,需要在FreeCAD的中加入一个自定义的工程文件。这需要搞懂原本框架中的新建工程、打开工程到保存撤销回复再到保存另存的一整套流程,还需要搞懂工程与UI之间的对应关系。于是开始翻看FreeCAD得源代码,一边看一边惊呼,居然还这样操作。然是我才疏学浅,这源代码使我受益匪浅。
整个工程得流程,可以分为三个部分:
- Command。这部分主要往程序主框架中注册一些命令,如新建、打开、保存等这些基本操作,然后将这些命令放到界面上去供用户使用。
- UI。这个主要指工程所对应的ui,比如在一个模型工程,那么这个工程必定会有一个显示模型的一个3D视窗。这个视窗就是这里只得ui
- 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;
这个类的主要做了两件事:
- 为commad创action,包括action对应的图标、menuText、whatThis、快捷键等显示相关的信息。
- 定义了一个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()); }
理一下思路:
- 创建一个类,继承commad类。(真懒啊,国外的大佬,创建类都要用宏来实现)。
- 在构造函数中初始化command的信息,主要是action的显示信息以及快捷键等。
- 实现activated函数,把这个命令想要触发的东西都写在这个里面。
最后只需要调用RegisterCommand()函数就可以成功注册命令了。
文章评论