FreeCAD源码分析:FreeCADGui模块
济南友泉软件有限公司
FreeCADGui项目实现了界面操作、模型显示与交互等相关功能,项目构建生成FreeCAD(_d).dll动态链接库。
FreeCADGuiPy项目在FreeCADGui基础之上,构建生成FreeCADGui(_d).pyd,实现对Python中“import FreeCADGui”语句的支持。
FreeCADGuiPy比较简单,因此,本文主要针对FreeCADGui进行分析。
一、模块功能概述
FreeCADGui模块基于文档-视图架构实现了多文档CAD软件开发的框架。不仅提供了基于Workbench的界面管理,而且提供了大量的用于完成数据对象渲染的视图。主要功能包括:
- Workbench管理
Workbench实际上针对特定应用的工具及其界面显示。FreeCAD提供了拓展性较强的基于Workbench的软件开发思路,可以通过Workbench定义软件的外观及其具体的功能。
- 停靠窗口管理
停靠窗口实际上是窗口部件的容器,可以将内嵌的窗口部件显示在主窗口的四周,也可以悬浮在桌面。
- 命令管理
Workbench由根据具体问题定义的命令工具集来实现,FreeCAD提供了CommandMnanger用于完成对Command及其子类的管理。
- 菜单管理
菜单(包括工具按钮)实际上是不分命令集的界面表现,通过关联Qmenu与QAction将全部或者部分命令集显示出来,同时建立界面操作与命令响应之间的关联。
- 视图显示
视图用于显示文档对象数据,同时可以将用户界面输入转换成数据对象操作。基于BaseView、MDIView及其子类,实现了文档-视图架构,可用于在界面窗口中显示数据对象。
- 属性系统
基于Qt Model-View架构,实现数据对象显示、编辑与Qt部件的关联。
二、Gui::Appication
Gui::Application提供了多文档应用程序开发的框架,支持文档操作、模块管理、软件启动等相关服务,其内部ApplicationP类型对象维护了文档列表、视图列表、命令管理器、宏管理器等相关数据对象。
struct ApplicationP
{
ApplicationP() :
activeDocument(0L),
isClosing(false),
startingUp(true)
{
// create the macro manager
macroMngr = new MacroManager();
}
~ApplicationP()
{
delete macroMngr;
}
/// list of all handled documents
std::map<const App::Document*, Gui::Document*> documents;
/// Active document
Gui::Document* activeDocument;
MacroManager* macroMngr;
/// List of all registered views
std::list<Gui::BaseView*> passive;
bool isClosing;
bool startingUp;
/// Handles all commands
CommandManager commandManager;
};
存在全局唯一的一个Application对象,
static Application* Application::Instance;
FreeCAD启动时,会通过Application定义的若干静态成员函数完成配置加载、初始化、模块加载、创建界面窗口等工作。
static void Application::initApplication(void);
static void Application::initTypes(void);
static void Application::initOpenInventor(void);
static void Application::runInitGuiScript(void);
static void Application::runApplication(void);
三、主窗口
Gui::MainWindow是FreeCAD在QMainWindow的基础之上提供的主窗口,该类提供了子窗口管理、弹出菜单、事件处理等功能。
存在全局唯一的Gui::MainWindow对象instance,
static MainWindow* MainWindow::instance;
static MainWindow* MainWindow::getInstance();
Gui::MainWindow属性存储在MainWindowP类型的结构体中,
struct MainWindow::MainWindowP* d;
struct MainWindowP
{
QLabel* sizeLabel;
QLabel* actionLabel;
QTimer* actionTimer;
QTimer* activityTimer;
QTimer* visibleTimer;
QMdiArea* mdiArea;
QPointer<MDIView> activeView;
QSignalMapper* windowMapper;
QSplashScreen* splashscreen;
StatusBarObserver* status;
bool whatsthis;
QString whatstext;
Assistant* assistant;
QMap<QString, QPointer<UrlHandler> > urlHandler;
};
变量名 |
类型 |
描述 |
sizeLabel |
QLabel* |
显示当前视图的几何维度 |
actionLabel |
QLabel* |
显示操作信息 |
actionTimer |
QTimer* |
在状态栏显示操作信息5秒 |
activityTimer |
QTimer* |
每300毫秒触发一次,更新界面命令的显示 |
visibleTimer |
QTimer* |
主窗口MainWindow显示定时器 |
mdiArea |
QMdiArea* |
多文档窗口显示的区域 |
activeView |
QPointer<MDIView> |
同一时刻,最多只有一个视图作为当前视图并处于激活状态 |
windowMapper |
QSignalMapper* |
信号映射器, |
splashscreen |
QSplashScreen* |
FreeCAD启动时的欢迎界面 |
status |
StatusBarObserver* |
监控控制台输出,通过CustomMessageEvent类型事件将消息(黑色)、警告(黄色)、错误(红色)输出到状态栏。 |
whatsthis |
bool |
是否显示帮助提示信息(What’s This) |
whatstext |
QString |
帮助提示信息(What’s This) |
assistant |
Assistant* |
调用 Qt assistant显示FreeCAD文档 |
urlHandler |
QMap<QString, QPointer<UrlHandler> > |
加载Url资源 |
3.1 欢迎界面
SplashScreen类不仅提供了自动加载欢迎界面,而且通过监控控制台输出以显示模块的加载进度。
MainWindow在FreeCAD启动的时候,会弹出欢迎界面窗口。Gui::MainWindow提供了以下两个函数完成欢迎界面窗口的创建与销毁。
void MainWindow::startSplasher(void);
void MainWindow::stopSplasher(void);
QPixmap MainWindow::splashImage() const;
实际上,这两个函数调用发生在void Application::runApplication(void)。可以在FreeCADMain MainGui.cpp中配置欢迎界面,
App::Application::Config()["SplashScreen"] = "freecadsplash";
App::Application::Config()["SplashAlignment" ] = "Bottom|Left";
App::Application::Config()["SplashTextColor" ] = "#ffffff"; // white
App::Application::Config()["SplashInfoColor" ] = "#c8c8c8"; // light grey
其中图像资源”freecadsplash”在FreeCADGui resource.qrc中进行了定义。
3.2 命令管理
CommandManager管理所有注册的Command及其派生类型对象,通过将Command与QAction相关联,可以将界面操作转换成Command对象的命令响应。
通常在界面模块文件中,利用CommandManager完成相关命令对象的注册。例如,对于CfdGui模块,在其AppCfdGui.cpp中,
// use a different name to CreateCommand()
void CreateCfdCommands(void);
//void loadStartResource()
//{
// // add resources and reloads the translators
// Q_INIT_RESOURCE(Cfd);
// Gui::Translator::instance()->refresh();
//}
namespace CfdGui {
class Module : public Py::ExtensionModule<Module>
{
public:
Module() : Py::ExtensionModule<Module>("CfdGui")
{
Base::Interpreter().loadModule("Cfd");
initialize("This module is the CfdGui module."); // register with Python
}
virtual ~Module() {}
private:
};
PyObject* initModule()
{
return (new Module)->module().ptr();
}
} // namespace CfdGui
/* Python entry */
PyMOD_INIT_FUNC(CfdGui)
{
if (!Gui::Application::Instance) {
PyErr_SetString(PyExc_ImportError, "Cannot load Gui module in console application.");
PyMOD_Return(0);
}
// load dependent module
try {
Base::Interpreter().runString("import Cfd");
}
catch(const Base::Exception& e) {
PyErr_SetString(PyExc_ImportError, e.what());
PyMOD_Return(0);
}
catch (Py::Exception& e) {
Py::Object o = Py::type(e);
if (o.isString()) {
Py::String s(o);
Base::Console().Error("%s\\n", s.as_std_string("utf-8").c_str());
}
else {
Py::String s(o.repr());
Base::Console().Error("%s\\n", s.as_std_string("utf-8").c_str());
}
// Prints message to console window if we are in interactive mode
PyErr_Print();
}
PyObject* mod = CfdGui::initModule();
Base::Console().Log("Loading GUI of CFD module... done\\n");
// register preferences pages
//new Gui::PrefPageProducer<CfdGui::DlgStartPreferencesImp> ("Cfd");
// instantiating the commands
CreateCfdCommands();
CfdGui::Workbench::init();
// add resources and reloads the translators
//loadStartResource();
PyMOD_Return(mod);
}
void CreateCfdCommands(void)
{
Gui::CommandManager &rcCmdMgr = Gui::Application::Instance->commandManager();
// part, analysis, solver
//rcCmdMgr.addCommand(new CmdFemAddPart());
//rcCmdMgr.addCommand(new CmdFemCreateAnalysis()); // Analysis is created in python
//rcCmdMgr.addCommand(new CmdFemCreateSolver()); // Solver will be extended and created in python
// constraints
// mesh
rcCmdMgr.addCommand(new CmdMeshImport());
// vtk post processing
#ifdef FC_USE_VTK
#endif
}
3.3 主菜单
在Workbench创建之后,会采用MenuManager创建已注册命令相对应的菜单项,仅需要重写Workbench::setMenuBar()即可轻松完成主菜单的定制。
Gui::MenuItem* Workbench::setupMenuBar() const;
3.4 环境菜单
环境菜单是根据界面上下文显示部分已注册的Command命令。通过重写Workbench::setupContextMenu()可以完成环境菜单的定制。
void Workbench::setupContextMenu(const char* recipient,Gui::MenuItem* item) const;
3.5 工具栏
ToolBarManager根据ToolBarItem创建Command相关联的工具按钮,可以通过Workbench::setupToolBars()以定制工具栏。
Gui::ToolBarItem* Workbench::setupToolBars() const;
3.6 状态栏
状态栏用于显示界面操作提示信息、操作执行进度、切换鼠标导航等功能,实际开发中很少涉及状态栏部分的修改。
3.7 停靠窗口
DockWindowManager用于管理停靠窗口的注册与显示等功能,根据需要重写Workbench:: setupDockWindows()以定制主窗口内的停靠窗口。
Gui::DockWindowItems* Workbench::setupDockWindows() const;
FreeCAD内置了常用的停靠窗口,如表所示
类型 |
名称 |
描述 |
ToolBox |
toolBox |
|
TreeView |
||
PropertyView |
||
SelectionView |
||
ComboView |
||
ReportView |
||
PythonView |
||
DAGView |
3.8 视图
视图用于关联文档对象,将文档中的全部或者部分数据对象显示到子窗口内。MDIView在BaseView的基础之上,提供了文档数据的显示功能。
3.9 数据拖放
通过拖拽文件到FreeCAD界面程序,可以自动创建文档对象并完成文档打开、文档数据渲染等工作。
3.10 事件处理
Qt中事件由一个特定的QEvent及其子类来表示,事件类型由QEvent类的枚举类型QEvent::Type来表示。一旦有事件发生。Qt便构建一个相应的QEvent子类的对象,然后将其传递给相应的QObject对象或者子对象。
事件先经过事件过滤器,然后是该对象的event()函数,最后是该对象的事件处理函数。
在MainWindow::event()中,主要完成了帮助信息等工作。
bool MainWindow::event(QEvent *e);
事件过滤器用来在一个QObject中监控其他多个QObject的事件。事件过滤器是由两个函数组成的一种操作,用来完成一个QObject对其他QObject的事件监视,这两个函数分别是installEventFilter()与eventFilter()。
void QObject::installEventFilter(QObject *filterObj);
bool QObject::eventFilter(QObject *watched, QEvent *event);
在MainWindow中,当增加子窗口时,会安装view对应的事件过滤器,当view有时间发生的时候,则会调用MainWindow的eventFilter函数,
void MainWindow::addWindow(MDIView* view);
bool MainWindow::eventFilter(QObject* o, QEvent* e);
而MainWindow::eventFilter也只是用于显示帮助提示信息等工作。
3.11 配置保存与加载
MainWindow在创建的时候,会自动加载已保存的窗口布局、尺寸等相关的信息;当MainWindow销毁之前,会保存窗口相关的布局信息。
/// Loads the main window settings.
void MainWindow::loadWindowSettings();
/// Saves the main window settings.
void MainWindow::saveWindowSettings();
四、Workbench管理
Workbench定义了主窗口工具栏、菜单栏、停靠窗口等外观,提供了不同的功能以供用户使用,仅将模块相关的功能加载到内存,提高了内存的使用效率。
当以Gui模式启动FreeCAD时,即执行
void Gui::Application::runApplication(void)
在这个函数中,会根据App::Application::Config()[“StartWorkbench”]与配置文件选项型的确定Workbench的名字,然后创建Workbench(默认类型是NoneWorkbench)
bool Gui::Application::activateWorkbench(const char* name);
其中App::Application::Config()[“StartWorkbench”]在MainGui.cpp中被复制为”StartWorkbench”。
默认Workbench类型是NonWorkbench
4.1 Workbench
虚基类Workbench定义了创建工具栏、菜单栏、停靠窗口等主窗口子部件的接口。当Workbench生成之后,会调用activate()函数完成工具栏、菜单栏、停靠窗口等部件的创建。
bool Workbench::activate()
{
ToolBarItem* tb = setupToolBars();
setupCustomToolbars(tb, "Toolbar");
ToolBarManager::getInstance()->setup( tb );
delete tb;
ToolBarItem* cb = setupCommandBars();
setupCustomToolbars(cb, "Toolboxbar");
ToolBoxManager::getInstance()->setup( cb );
delete cb;
DockWindowItems* dw = setupDockWindows();
DockWindowManager::instance()->setup( dw );
delete dw;
MenuItem* mb = setupMenuBar();
MenuManager::getInstance()->setup( mb );
delete mb;
setupCustomShortcuts();
return true;
}
可以看到,Workbench实际上是通过调用对应的虚函数完成具体的部件创建工作。
/** Returns a MenuItem tree structure of menus for this workbench. */
virtual MenuItem* setupMenuBar() const=0;
/** Returns a ToolBarItem tree structure of toolbars for this workbench. */
virtual ToolBarItem* setupToolBars() const=0;
/** Returns a ToolBarItem tree structure of command bars for this workbench. */
virtual ToolBarItem* setupCommandBars() const=0;
/** Returns a DockWindowItems structure of dock windows this workbench. */
virtual DockWindowItems* setupDockWindows() const=0;
因此,仅需要重写创建工具栏、菜单栏、命令栏、停靠窗口等部件的虚函数,就可以很容易的完成主窗口界面的设计。而StdWorkbench、BlankWorkbench、NoneWorkbench、TestWorkbench、PythonBaseWorkbench、PythonBlankWorkbench、PythonWorkbench等提供了默认的实现方式,可以根据需要从它们派生来自定义Workbench。(这些Workbench基类的具体使用后续教程会分章节详细介绍。)
4.2 WorkbenchManager
存在全局唯一的WorkbenchManager类型的对象管理所有的Workbench对象。
class GuiExport WorkbenchManager
{
public:
/** Creates the only instance of the WorkbenchManager. */
static WorkbenchManager* instance();
static void destruct();
/** Searches for and returns an existing workbench object with name \\a name. If no
* such workbench exists then a workbench of class \\a className gets created, if possible.
* If the workbench cannot be created 0 is returned.
*/
Workbench* createWorkbench (const std::string& name, const std::string& className);
/** Removes the workbench with name \\a name. If there is no such
* workbench exists nothing happens.
*/
void removeWorkbench(const std::string& name);
/** Returns an instance of the workbench with name \\a name. If there is
* no such workbench 0 is returned.
*/
Workbench* getWorkbench (const std::string& name) const;
/** Activates the workbench with name \\a name. */
bool activate(const std::string& name, const std::string& className);
/** Returns the active workbench. */
Workbench* active() const;
/** Returns a list of all created workbench objects. */
std::list<std::string> workbenches() const;
protected:
WorkbenchManager();
~WorkbenchManager();
private:
static WorkbenchManager* _instance;
Workbench* _activeWorkbench;
std::map<std::string, Workbench*> _workbenches;
};
可以看到,WorkManager提供了WorkBench创建、激活、销毁等功能。当访问这个全局对象的时候,会根据需要创建。
WorkbenchManager* WorkbenchManager::instance()
{
if (_instance == 0)
_instance = new WorkbenchManager;
return _instance;
}
在Gui::Application对象析构的时候会销毁这个全局唯一的WorkManager对象,
Application::~Application()
{
Base::Console().Log("Destruct Gui::Application\\n");
WorkbenchManager::destruct();
SelectionSingleton::destruct();
Translator::destruct();
WidgetFactorySupplier::destruct();
BitmapFactoryInst::destruct();
#if 0
// we must run the garbage collector before shutting down the SoDB
// subsystem because we may reference some class objects of them in Python
Base::Interpreter().cleanupSWIG("SoBase *");
// finish also Inventor subsystem
SoFCDB::finish();
#if (COIN_MAJOR_VERSION >= 2) && (COIN_MINOR_VERSION >= 4)
SoDB::finish();
#elif (COIN_MAJOR_VERSION >= 3)
SoDB::finish();
#else
SoDB::cleanup();
#endif
#endif
{
Base::PyGILStateLocker lock;
Py_DECREF(_pcWorkbenchDictionary);
}
// save macros
try {
MacroCommand::save();
}
catch (const Base::Exception& e) {
std::cerr << "Saving macros failed: " << e.what() << std::endl;
}
//App::GetApplication().Detach(this);
delete d;
Instance = 0;
}
而在Gui::Application::activateWorkbench函数中会根据类型名称调用Gui::WorkbenchManager::activate创建对应的Workbench对象。
bool Application::activateWorkbench(const char* name) ;
bool WorkbenchManager::activate(const std::string& name, const std::string& className)
{
Workbench* wb = createWorkbench(name, className);
if (wb) {
_activeWorkbench = wb;
wb->activate();
return true;
}
return false;
}
4.3 自定义Workbench
FreeCAD最大的特点是可以根据需要自行定义不同的Workbench来实现不同的界面外观,以满足不同的应用需求。
下面关于FreeCAD python模块的编写方法后续文档会进行详细讲解,这里仅以CfdWorkbench定义过程为例,着重分析自定义Workbench的主要步骤。
4.3.1 定义Workbench子类
从Workbench派生子类,然后重写创建工具栏、菜单栏、停靠窗口的虚函数。为了简化Workbench子类的创建,StdWorkbench已经实现了默认的工具栏、菜单、停靠窗口等创建工作。
//CfdWorkbench定义头文件
/**
* The CfdWorkbench class defines a workbench for Computationl Fluid Dynamics (CFD).
* Right now, only the incompressible flow model is considered.
* @author Nene
*/
class CfdWorkbench : public StdWorkbench
{
TYPESYSTEM_HEADER();
public:
CfdWorkbench();
~CfdWorkbench();
protected:
MenuItem* setupMenuBar() const
{
MenuItem* root = StdWorkbench::setupMenuBar();
// your changes
return root;
}
ToolBarItem* setupToolBars() const
{
ToolBarItem* root = StdWorkbench::setupToolBars();
// your changes
return root;
}
ToolBarItem* setupCommandBars() const
{
ToolBarItem* root = StdWorkbench::setupCommandBars();
// your changes
return root;
}
};
//CfdWorkbench源文件
// --------------------------------------------------------------------
TYPESYSTEM_SOURCE(Gui::CfdWorkbench, Gui::StdWorkbench)
CfdWorkbench::CfdWorkbench()
: StdWorkbench()
{
}
CfdWorkbench::~CfdWorkbench()
{
}
4.3.2 注册Workbench
当完成CfdWorkbench,需要注册CfdWorkbench的类型信息,可以在Gui::Application:: initTypes(void)内完成,即
void Application::initTypes(void)
{
// views
Gui::BaseView ::init();
Gui::MDIView ::init();
Gui::View3DInventor ::init();
Gui::AbstractSplitView ::init();
Gui::SplitView3DInventor ::init();
// View Provider
Gui::ViewProvider ::init();
Gui::ViewProviderExtension ::init();
Gui::ViewProviderExtensionPython ::init();
Gui::ViewProviderGroupExtension ::init();
Gui::ViewProviderGroupExtensionPython ::init();
Gui::ViewProviderGeoFeatureGroupExtension ::init();
Gui::ViewProviderGeoFeatureGroupExtensionPython::init();
Gui::ViewProviderOriginGroupExtension ::init();
Gui::ViewProviderOriginGroupExtensionPython ::init();
Gui::ViewProviderExtern ::init();
Gui::ViewProviderDocumentObject ::init();
Gui::ViewProviderFeature ::init();
Gui::ViewProviderDocumentObjectGroup ::init();
Gui::ViewProviderDocumentObjectGroupPython ::init();
Gui::ViewProviderDragger ::init();
Gui::ViewProviderGeometryObject ::init();
Gui::ViewProviderInventorObject ::init();
Gui::ViewProviderVRMLObject ::init();
Gui::ViewProviderAnnotation ::init();
Gui::ViewProviderAnnotationLabel ::init();
Gui::ViewProviderPointMarker ::init();
Gui::ViewProviderMeasureDistance ::init();
Gui::ViewProviderPythonFeature ::init();
Gui::ViewProviderPythonGeometry ::init();
Gui::ViewProviderPlacement ::init();
Gui::ViewProviderOriginFeature ::init();
Gui::ViewProviderPlane ::init();
Gui::ViewProviderLine ::init();
Gui::ViewProviderGeoFeatureGroup ::init();
Gui::ViewProviderGeoFeatureGroupPython ::init();
Gui::ViewProviderOriginGroup ::init();
Gui::ViewProviderPart ::init();
Gui::ViewProviderOrigin ::init();
Gui::ViewProviderMaterialObject ::init();
Gui::ViewProviderMaterialObjectPython ::init();
Gui::ViewProviderTextDocument ::init();
// Workbench
Gui::Workbench ::init();
Gui::StdWorkbench ::init();
Gui::BlankWorkbench ::init();
Gui::NoneWorkbench ::init();
Gui::TestWorkbench ::init();
Gui::PythonBaseWorkbench ::init();
Gui::PythonBlankWorkbench ::init();
Gui::PythonWorkbench ::init();
Gui::CfdWorkbench ::init();
// register transaction type
new App::TransactionProducer<TransactionViewProvider>
(ViewProviderDocumentObject::getClassTypeId());
}
上面的代码同时完成了View、Workebench的注册等工作。
4.3.3 编写Python模块
在Mod目录下,创建CfdWorkbench目录,同时增加Init.py与InitGui.py文件。
Init.py文件内容为:
# FreeCAD init script of the Surface module
# (c) 2001 Juergen Riegel LGPL
InitGui.py的文件内容为:
# Cfd gui init module
# (c) 2001 Juergen Riegel LGPL
class CfdWorkbench ( Workbench ):
"Cfd workbench object"
Icon = """
"""
MenuText = "Cfd"
ToolTip = "Cfd workbench: Create and edit complex surfaces"
def Initialize(self):
# load the module
import SurfaceGui
import FreeCADGui
import Surface
# Set path to icon labels
FreeCADGui.addIconPath('./Gui/Resources/Icons/')
def GetClassName(self):
return "Gui::CfdWorkbench"
Gui.addWorkbench(CfdWorkbench())
程序运行结果,
五、命令框架
在Qt中,通过菜单项、工具按钮、快捷键等就可以触发Qaction对应的槽函数。但是,当主窗口包含的Qaction较多时,就需要在MainWondow里面添加许多的槽函数,很显然,这样就会造成MainWindow类比较臃肿。而且,大多数情况下,以插件的形式提供功能通常都需要修改MainWindow界面。因此,这就需要将QAction与MainWindow进行解耦。
Command(派生于Gui::CommandBase)用于FreeCAD命令的具体实现;而Action则用于关联QAction与Gui::Command。
5.1 Action
Action类实际上维护了QAction与Command之间的关联关系。
class GuiExport Action : public QObject
{
Q_OBJECT
public:
Action (Command* pcCmd, QObject * parent = 0);
/// Action takes ownership of the 'action' object.
Action (Command* pcCmd, QAction* action, QObject * parent);
virtual ~Action();
virtual void addTo (QWidget * w);
virtual void setEnabled(bool);
virtual void setVisible(bool);
void setCheckable(bool);
void setChecked (bool);
bool isChecked() const;
void setShortcut (const QString &);
QKeySequence shortcut() const;
void setIcon (const QIcon &);
void setStatusTip (const QString &);
QString statusTip() const;
void setText (const QString &);
QString text() const;
void setToolTip (const QString &);
QString toolTip() const;
void setWhatsThis (const QString &);
QString whatsThis() const;
void setMenuRole(QAction::MenuRole menuRole);
public Q_SLOTS:
virtual void onActivated ();
virtual void onToggled (bool);
protected:
QAction* _action;
Command *_pcCmd;
};
Action::Action (Command* pcCmd, QObject * parent)
: QObject(parent), _action(new QAction( this )), _pcCmd(pcCmd)
{
_action->setObjectName(QString::fromLatin1(_pcCmd->getName()));
connect(_action, SIGNAL(triggered(bool)), this, SLOT(onActivated()));
}
void Action::onActivated ()
{
_pcCmd->invoke(0);
}
从代码中可以看出,Action将Qaction::triggered(bool)信号关联到了Gui::Command::invoke(int)函数。
5.2 Command
Command派生于CommandBase类,主要用于实现FreeCAD命令的具体操作。
在invoke()函数中,实际上是通过虚函数activated()完成具体的工作。
virtual void Command::activated(int iMsg)=0;
因此,仅需要自泪花Gui::Command,需要实现activated()虚函数,
例如:
class OpenCommand : public Gui::Command
{
public:
OpenCommand() : Gui::Command("Std_Open")
{
// set up menu text, status tip, ...
sMenuText = "&Open";
sToolTipText = "Open a file";
sWhatsThis = "Open a file";
StatusTip = "Open a file";
sPixmap = "Open"; // name of a registered pixmap
sAccel = "Shift+P"; // or "P" or "P, L" or "Ctrl+X, Ctrl+C" for a sequence
}
protected:
void activated(int)
{
QString filter ... // make a filter of all supported file formats
QStringList FileList = QFileDialog::getOpenFileNames(filter, QString::null, getMainWindow());
for (QStringList::Iterator it = FileList.begin(); it != FileList.end(); ++it) {
getGuiApplication()->open((*it).latin1());
}
}
};
5.3 CommandManager
Gui::Application中存在一个CommandManager类型的静态对象用于管理所有的命令对象。CommandManager提供了命令对象添加、删除、运行等操作。
class GuiExport CommandManager
{
public:
/// Construction
CommandManager();
/// Destruction
~CommandManager();
/// Insert a new command into the manager
void addCommand(Command* pCom);
/// Remove a command from the manager
void removeCommand(Command* pCom);
/// Adds the given command to a given widget
bool addTo(const char* Name, QWidget* pcWidget);
/** Returns all commands of a special App Module
* delivers a vector of all commands in the given application module. When no
* name is given the standard commands (build in ) are returned.
* @see Command
*/
std::vector <Command*> getModuleCommands(const char *sModName) const;
/** Returns all commands registered in the manager
* delivers a vector of all commands. If you intereted in commands of
* of a special app module use GetModuleCommands()
* @see Command
*/
std::vector <Command*> getAllCommands(void) const;
/** Returns all commands of a group
* delivers a vector of all commands in the given group.
*/
std::vector <Command*> getGroupCommands(const char *sGrpName) const;
/** Returns the command registered in the manager with the name sName
* If nothing is found it returns a null pointer
* @see Command
*/
Command* getCommandByName(const char* sName) const;
/**
* Runs the command
*/
void runCommandByName (const char* sName) const;
/// method is OBSOLETE use GetModuleCommands() or GetAllCommands()
const std::map<std::string, Command*>& getCommands() const { return _sCommands; }
/// get frequently called by the AppWnd to check the commands are active.
void testActive(void);
void addCommandMode(const char* sContext, const char* sName);
void updateCommands(const char* sContext, int mode);
private:
/// Destroys all commands in the manager and empties the list.
void clearCommands();
std::map<std::string, Command*> _sCommands;
std::map<std::string, std::list<std::string> > _sCommandModes;
};
从CommandManager的定义可以看出,添加、删除命令对象使用的方法为,
void CommandManager::addCommand(Command* pCom);
void CommandManager::removeCommand(Command* pCom);
5.4 注册命令
在Gui::Application构造函数中,通过调用createStandardOperations()函数完成了向CommandManager注册命令。
void Application::createStandardOperations()
{
// register the application Standard commands from CommandStd.cpp
Gui::CreateStdCommands();
Gui::CreateDocCommands();
Gui::CreateFeatCommands();
Gui::CreateMacroCommands();
Gui::CreateViewStdCommands();
Gui::CreateWindowStdCommands();
Gui::CreateStructureCommands();
Gui::CreateTestCommands();
}
六、菜单管理
在Workbench的创建过程中,最终会通过MenuItem、MenuManager这两个类完成对应的菜单创建及关联CommandManager已注册的命令对象。
bool Gui::Application::activateWorkbench(const char* name)
virtual MenuItem* Workbench::setupMenuBar() const=0;
6.1 MenuItem
MenuItem保存了菜单名字及子菜单等信息,菜单名字保存在私有变量_name,
std::string MenuItem::_name;
可以设置菜单的名字
void MenuItem::setCommand(const std::string&);
std::string MenuItem::command() const;
一个菜单项可以包含若干个子菜单,存放在私有成员变量_items中,
QList<MenuItem*> MenuItem::_items
同时,MenuItem提供了增加、删除子菜单项的方法,
void MenuItem::appendItem(MenuItem*);
bool MenuItem::insertItem(MenuItem*, MenuItem*);
MenuItem* MenuItem::afterItem(MenuItem*) const;
void MenuItem::removeItem(MenuItem*);
void MenuItem::clear();
6.2 MenuManager
MenuManager用于辅助创建主窗口菜单的类,存在全局唯一的MenuManager对象,
static MenuManager* MenuManager::getInstance();
依据MenuItem,MenuManager可以为主窗口增加菜单
void MenuManager::setup(MenuItem*) const;
6.3 关联命令
当Workbench::activate()函数中,会完成工具栏、菜单栏、停靠窗口等窗体元素的创建。
bool Workbench::activate()
{
ToolBarItem* tb = setupToolBars();
setupCustomToolbars(tb, "Toolbar");
ToolBarManager::getInstance()->setup( tb );
delete tb;
ToolBarItem* cb = setupCommandBars();
setupCustomToolbars(cb, "Toolboxbar");
ToolBoxManager::getInstance()->setup( cb );
delete cb;
DockWindowItems* dw = setupDockWindows();
DockWindowManager::instance()->setup( dw );
delete dw;
MenuItem* mb = setupMenuBar();
MenuManager::getInstance()->setup( mb );
delete mb;
setupCustomShortcuts();
return true;
}
不同的Workbench子类,通过重写虚函数Workbench::setupMenuBar()完成具体的菜单项的创建,这个函数返回树状菜单列表的根指针。
virtual MenuItem* Gui::Workbench::setupMenuBar() const = 0;
在Gui::MenuManager::setup()函数中,根据MenuItem创建实际的窗口菜单项QMenu。
void MenuManager::setup(MenuItem* menuItems) const;
主菜单项MenuItem对应的QMenu创建成功之后,会递归地创建其子菜单项,同时调用CommandManager::add()函数完成Qmenu对应的Qaction与CommandManager已注册的Command子类的关联。
void MenuManager::setup(MenuItem* item, QMenu* menu) const;
bool CommandManager::addTo(const char* Name, QWidget *pcWidget)
七、停靠窗口(DockWindow)
在Qt中,停靠窗口QDockWidget可以停靠在主窗口QMainWindow中,也可以悬浮起来作为桌面顶级窗口。可以将QWidget及其派生类型的窗口部件嵌入到停靠窗口QDockWidget内,这样就为普通QWidget提供了主窗口内的自动布局功能。
7.1 DockWindow
DockWindow实际上是派生于QWidget的普通窗口,这样DockWindow就可以嵌入到QDockWidget中。另一方面,因为DockWindow同时派生于BaseView,所以DockWindow可以关联Document对象,可以访问Document对象的数据、关联Document对象信号。
class GuiExport DockWindow : public QWidget, public BaseView
{
Q_OBJECT
public:
/** View constructor
* Attach the view to the given document. If the document is 0
* the view will attach to the active document. Be aware there isn't
* always an active document available!
*/
DockWindow ( Gui::Document* pcDocument=0, QWidget *parent=0 );
/** View destructor
* Detach the view from the document, if attached.
*/
virtual ~DockWindow();
/** @name methods to override
*/
//@{
/// get called when the document is updated
virtual void onUpdate(void){}
/// returns the name of the view (important for messages)
virtual const char *getName(void) const { return "DockWindow"; }
/// Message handler
virtual bool onMsg(const char* ,const char** ){ return false; }
/// Message handler test
virtual bool onHasMsg(const char*) const { return false; }
/// overwrite when checking on close state
virtual bool canClose(void){return true;}
//@}
Q_SIGNALS:
/// sends a message to the document
void sendCloseView(MDIView* theView);
};
7.2 DockWindowManager
DockWindowManager用于管理DockWindow的创建、删除等功能,存在全局唯一的DockWindowManager静态对象,
static DockWindowManager* DockWindowManager::instance();
在DockWindowManager内,DockWindowManagerP结构内维护了DockWindow对象列表。
struct DockWindowManager::DockWindowManagerP* d;
namespace Gui {
struct DockWindowManagerP
{
QList<QDockWidget*> _dockedWindows;
QMap<QString, QPointer<QWidget> > _dockWindows;
DockWindowItems _dockWindowItems;
};
} // namespace Gui
7.3 停靠窗口的创建过程
在MainWindow的构造函数中,首先会根据配置文件来注册停靠窗口,
bool DockWindowManager::registerDockWindow(const char* name, QWidget* widget);
实际上就是创建对应的DockWindow对象并将其存放到DockManager::_dockWindows中。
MainWindow::MainWindow(QWidget * parent, Qt::WindowFlags f)
: QMainWindow( parent, f/*WDestructiveClose*/ )
{
DockWindowManager* pDockMgr = DockWindowManager::instance();
std::string hiddenDockWindows;;
const std::map<std::string,std::string>& config = App::Application::Config();
std::map<std::string, std::string>::const_iterator ht = config.find("HiddenDockWindow");
if (ht != config.end())
hiddenDockWindows = ht->second;
// Show all dockable windows over the workbench facility
//
#if 0
// Toolbox
if (hiddenDockWindows.find("Std_ToolBox") == std::string::npos) {
ToolBox* toolBox = new ToolBox(this);
toolBox->setObjectName(QT_TRANSLATE_NOOP("QDockWidget","Toolbox"));
pDockMgr->registerDockWindow("Std_ToolBox", toolBox);
ToolBoxManager::getInstance()->setToolBox( toolBox );
}
#endif
// Tree view
if (hiddenDockWindows.find("Std_TreeView") == std::string::npos) {
//work through parameter.
ParameterGrp::handle group = App::GetApplication().GetUserParameter().
GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("DockWindows")->GetGroup("TreeView");
bool enabled = group->GetBool("Enabled", true);
group->SetBool("Enabled", enabled); //ensure entry exists.
if (enabled) {
TreeDockWidget* tree = new TreeDockWidget(0, this);
tree->setObjectName
(QString::fromLatin1(QT_TRANSLATE_NOOP("QDockWidget","Tree view")));
tree->setMinimumWidth(210);
pDockMgr->registerDockWindow("Std_TreeView", tree);
}
}
// Property view
if (hiddenDockWindows.find("Std_PropertyView") == std::string::npos) {
//work through parameter.
ParameterGrp::handle group = App::GetApplication().GetUserParameter().
GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("DockWindows")->GetGroup("PropertyView");
bool enabled = group->GetBool("Enabled", true);
group->SetBool("Enabled", enabled); //ensure entry exists.
if (enabled) {
PropertyDockView* pcPropView = new PropertyDockView(0, this);
pcPropView->setObjectName
(QString::fromLatin1(QT_TRANSLATE_NOOP("QDockWidget","Property view")));
pcPropView->setMinimumWidth(210);
pDockMgr->registerDockWindow("Std_PropertyView", pcPropView);
}
}
// Selection view
if (hiddenDockWindows.find("Std_SelectionView") == std::string::npos) {
SelectionView* pcSelectionView = new SelectionView(0, this);
pcSelectionView->setObjectName
(QString::fromLatin1(QT_TRANSLATE_NOOP("QDockWidget","Selection view")));
pcSelectionView->setMinimumWidth(210);
pDockMgr->registerDockWindow("Std_SelectionView", pcSelectionView);
}
// Combo view
if (hiddenDockWindows.find("Std_CombiView") == std::string::npos) {
CombiView* pcCombiView = new CombiView(0, this);
pcCombiView->setObjectName(QString::fromLatin1(QT_TRANSLATE_NOOP("QDockWidget","Combo View")));
pcCombiView->setMinimumWidth(150);
pDockMgr->registerDockWindow("Std_CombiView", pcCombiView);
}
#if QT_VERSION < 0x040500
// Report view
if (hiddenDockWindows.find("Std_ReportView") == std::string::npos) {
Gui::DockWnd::ReportView* pcReport = new Gui::DockWnd::ReportView(this);
pcReport->setObjectName
(QString::fromLatin1(QT_TRANSLATE_NOOP("QDockWidget","Report view")));
pDockMgr->registerDockWindow("Std_ReportView", pcReport);
}
#else
// Report view (must be created before PythonConsole!)
if (hiddenDockWindows.find("Std_ReportView") == std::string::npos) {
ReportOutput* pcReport = new ReportOutput(this);
pcReport->setWindowIcon(BitmapFactory().pixmap("MacroEditor"));
pcReport->setObjectName
(QString::fromLatin1(QT_TRANSLATE_NOOP("QDockWidget","Report view")));
pDockMgr->registerDockWindow("Std_ReportView", pcReport);
}
// Python console
if (hiddenDockWindows.find("Std_PythonView") == std::string::npos) {
PythonConsole* pcPython = new PythonConsole(this);
ParameterGrp::handle hGrp = App::GetApplication().GetUserParameter().
GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("General");
if (hGrp->GetBool("PythonWordWrap", true)) {
pcPython->setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
} else {
pcPython->setWordWrapMode(QTextOption::NoWrap);
}
pcPython->setWindowIcon(Gui::BitmapFactory().iconFromTheme("applications-python"));
pcPython->setObjectName
(QString::fromLatin1(QT_TRANSLATE_NOOP("QDockWidget","Python console")));
pDockMgr->registerDockWindow("Std_PythonView", pcPython);
}
//Dag View.
if (hiddenDockWindows.find("Std_DAGView") == std::string::npos) {
//work through parameter.
// old group name
ParameterGrp::handle deprecateGroup = App::GetApplication().GetUserParameter().
GetGroup("BaseApp")->GetGroup("Preferences");
bool enabled = false;
if (deprecateGroup->HasGroup("DAGView")) {
deprecateGroup = deprecateGroup->GetGroup("DAGView");
enabled = deprecateGroup->GetBool("Enabled", enabled);
}
// new group name
ParameterGrp::handle group = App::GetApplication().GetUserParameter().
GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("DockWindows")->GetGroup("DAGView");
enabled = group->GetBool("Enabled", enabled);
group->SetBool("Enabled", enabled); //ensure entry exists.
if (enabled) {
DAG::DockWindow *dagDockWindow = new DAG::DockWindow(nullptr, this);
dagDockWindow->setObjectName
(QString::fromLatin1(QT_TRANSLATE_NOOP("QDockWidget","DAG View")));
pDockMgr->registerDockWindow("Std_DAGView", dagDockWindow);
}
}
}
然后,在Gui::Workbench::activate()函数中,会调用虚函数setupDockWindows()生成停靠窗口信息,并将其添加到QDockWidget中。
bool Workbench::activate()
{
ToolBarItem* tb = setupToolBars();
setupCustomToolbars(tb, "Toolbar");
ToolBarManager::getInstance()->setup( tb );
delete tb;
ToolBarItem* cb = setupCommandBars();
setupCustomToolbars(cb, "Toolboxbar");
ToolBoxManager::getInstance()->setup( cb );
delete cb;
DockWindowItems* dw = setupDockWindows();
DockWindowManager::instance()->setup( dw );
delete dw;
MenuItem* mb = setupMenuBar();
MenuManager::getInstance()->setup( mb );
delete mb;
setupCustomShortcuts();
return true;
}
八、文档
在文档/视图结构里,文档可视为一个应用程序的数据元素的集合,能够被逻辑地组合的一系列数据,包括文本、图形、图象和表格数据。一个文档代表了用户存储或打开的一个文件单位。文档的主要作用是把对数据的处理从对用户界面的处理中分离出来,集中处理数据,同时提供了一个与其它类交互的接口。
在Gui::Application内部维护了Gui::Document文档对象列表,
struct Gui::Application::ApplicationP* d;
struct Gui::ApplicationP
{
ApplicationP() :
activeDocument(0L),
isClosing(false),
startingUp(true)
{
// create the macro manager
macroMngr = new MacroManager();
}
~ApplicationP()
{
delete macroMngr;
}
/// list of all handled documents
std::map<const App::Document*, Gui::Document*> documents;
/// Active document
Gui::Document* activeDocument;
MacroManager* macroMngr;
/// List of all registered views
std::list<Gui::BaseView*> passive;
bool isClosing;
bool startingUp;
/// Handles all commands
CommandManager commandManager;
};
从上面可以看出,FreeCAD采用了多文档的架构,一个程序(Gui::Application)中可以包含多个文档对象(Gui::Document)。
8.1 Gui::Document
Gui::Document内部封装了App::Document对象,主要功能是为了关联BaseView、MDIView及其子类等视图对象。
/// Attach a view (get called by the MDIView constructor)
void Document::attachView(Gui::BaseView* pcView, bool bPassiv = false);
/// Detach a view (get called by the MDIView destructor)
void Document::detachView(Gui::BaseView* pcView, bool bPassiv = false);
当新建文档时,会调用Gui::Document::createView()函数创建View3Dinventor视图对象,
void Document::createView(const Base::Type& typeId) ;
针对MDIView及其子类,Gui::Document提供了创建、访问MDIView视图对象的接口
/// Getter for the active view
Gui::MDIView* Document::getActiveView(void) const;
void setActiveWindow(Gui::MDIView* view);
Gui::MDIView* Document::getEditingViewOfViewProvider(Gui::ViewProvider*) const;
Gui::MDIView* Document::getViewOfViewProvider(Gui::ViewProvider*) const;
Gui::MDIView* Document::getViewOfNode(SoNode*) const;
/// Create a clone of the given view
Gui::MDIView* Document::cloneView(Gui::MDIView*);
九、中心部件:视图
在文档/视图结构里,视图是数据的用户界面,可以从文档中获取数据并将其窗口中显示。视图还可提供用户与文档中数据的交互功能,将用户的输入转化为对数据的操作。
在FreeCAD中,一个视图对象(Gui::MDIView派生类)最多只能与一个文档对象(Gui::Document派生类)相关联,负责显示Gui::Document内的内容以及将用户输入转化为对文档数据的操作。
9.1 BaseView
BaseView用于关联Gui::Document对象,用两种关联方式。第一种方式是关联固定的固定的Gui::Document文档对象;第二种方式是管理当前Gui::Document文档对象。
BaseView::BaseView(Gui::Document* pcDocument = 0);
void BaseView::setDocument(Gui::Document* pcDocument);
当pcDocument为空指针时,将会关联当前文档对象,因为当前可能没有文档对象,所以BaseView可能不会与任何文档对象关联。
此外,BaseView定义若干虚函数用于响应文档对象响应的事件,
/// get called when the document is updated
virtual void BaseView::onUpdate(void){}
/// get called when the document is relabeled (change of its user name)
virtual void BaseView::onRelabel(Gui::Document *){}
/// get called when the document is renamed (change of its internal name)
virtual void BaseView::onRename(Gui::Document *){}
/// returns the name of the view (important for messages)
virtual const char * BaseView::getName(void) const;
/// Message handler
virtual bool BaseView::onMsg(const char* pMsg, const char** ppReturn)=0;
/// Message handler test
virtual bool BaseView::onHasMsg(const char* pMsg) const=0;
/// overwrite when checking on close state
virtual bool BaseView::canClose(void){return true;}
/// delete itself
virtual void BaseView::deleteSelf();
9.2 MDIView
MDIView同时派生于QMainWindow与BaseView,这样不仅可以关联Gui::Document对象,而且支持作为子窗口、顶级窗口、全屏窗口来显示文档内容的功能。
/// MDI view mode enum
enum ViewMode {
Child, /**< Child viewing, view is docked inside the MDI application window */
TopLevel, /**< The view becomes a top level window and can be moved outsinde the application window */
FullScreen /**< The view goes to full screen viewing */
};
virtual void MDIView::setCurrentViewMode(ViewMode mode);
同时,MDIView维护了当前活动文档对象列表,可以高亮显示这些活动文档对象。
ActiveObjectList MDIView::ActiveObjects;
template<typename _T>
inline _T getActiveObject(const char* name) const
{
return ActiveObjects.getObject<_T>(name);
}
void MDIView::setActiveObject(App::DocumentObject*o, const char*n)
{
ActiveObjects.setObject(o, n);
}
bool MDIView::hasActiveObject(const char*n) const
{
return ActiveObjects.hasObject(n);
}
9.3 ViewProvider
在视图窗口中,实际上是通过各种ViewProvider及其子类完成各种对象数据的显示以及与用户的交互功能。ViewProvider类定义对象数据显示控制、对象操作等主要接口。
9.4 视图创建过程
在Application::initApplication()函数中调用Application:: init_types(),完成相关视图类型的注册,
void Application::initTypes(void)
{
// views
Gui::BaseView ::init();
Gui::MDIView ::init();
Gui::View3DInventor ::init();
Gui::AbstractSplitView ::init();
Gui::SplitView3DInventor ::init();
// View Provider
Gui::ViewProvider ::init();
Gui::ViewProviderExtension ::init();
Gui::ViewProviderExtensionPython ::init();
Gui::ViewProviderGroupExtension ::init();
Gui::ViewProviderGroupExtensionPython ::init();
Gui::ViewProviderGeoFeatureGroupExtension ::init();
Gui::ViewProviderGeoFeatureGroupExtensionPython::init();
Gui::ViewProviderOriginGroupExtension ::init();
Gui::ViewProviderOriginGroupExtensionPython ::init();
Gui::ViewProviderExtern ::init();
Gui::ViewProviderDocumentObject ::init();
Gui::ViewProviderFeature ::init();
Gui::ViewProviderDocumentObjectGroup ::init();
Gui::ViewProviderDocumentObjectGroupPython ::init();
Gui::ViewProviderDragger ::init();
Gui::ViewProviderGeometryObject ::init();
Gui::ViewProviderInventorObject ::init();
Gui::ViewProviderVRMLObject ::init();
Gui::ViewProviderAnnotation ::init();
Gui::ViewProviderAnnotationLabel ::init();
Gui::ViewProviderPointMarker ::init();
Gui::ViewProviderMeasureDistance ::init();
Gui::ViewProviderPythonFeature ::init();
Gui::ViewProviderPythonGeometry ::init();
Gui::ViewProviderPlacement ::init();
Gui::ViewProviderOriginFeature ::init();
Gui::ViewProviderPlane ::init();
Gui::ViewProviderLine ::init();
Gui::ViewProviderGeoFeatureGroup ::init();
Gui::ViewProviderGeoFeatureGroupPython ::init();
Gui::ViewProviderOriginGroup ::init();
Gui::ViewProviderPart ::init();
Gui::ViewProviderOrigin ::init();
Gui::ViewProviderMaterialObject ::init();
Gui::ViewProviderMaterialObjectPython ::init();
Gui::ViewProviderTextDocument ::init();
// Workbench
Gui::Workbench ::init();
Gui::StdWorkbench ::init();
Gui::BlankWorkbench ::init();
Gui::NoneWorkbench ::init();
Gui::TestWorkbench ::init();
Gui::PythonBaseWorkbench ::init();
Gui::PythonBlankWorkbench ::init();
Gui::PythonWorkbench ::init();
// register transaction type
new App::TransactionProducer<TransactionViewProvider>
(ViewProviderDocumentObject::getClassTypeId());
}
当新建文档时,会触发Application::slotNewDocument(const App::Document& Doc)响应函数的调用,
void Application::slotNewDocument(const App::Document& Doc)
{
#ifdef FC_DEBUG
std::map<const App::Document*, Gui::Document*>::const_iterator it = d->documents.find(&Doc);
assert(it==d->documents.end());
#endif
Gui::Document* pDoc = new Gui::Document(const_cast<App::Document*>(&Doc),this);
d->documents[&Doc] = pDoc;
// connect the signals to the application for the new document
pDoc->signalNewObject.connect(boost::bind(&Gui::Application::slotNewObject, this, _1));
pDoc->signalDeletedObject.connect(boost::bind(&Gui::Application::slotDeletedObject, this, _1));
pDoc->signalChangedObject.connect(boost::bind(&Gui::Application::slotChangedObject, this, _1, _2));
pDoc->signalRelabelObject.connect(boost::bind(&Gui::Application::slotRelabelObject, this, _1));
pDoc->signalActivatedObject.connect(boost::bind(&Gui::Application::slotActivatedObject, this, _1));
pDoc->signalInEdit.connect(boost::bind(&Gui::Application::slotInEdit, this, _1));
pDoc->signalResetEdit.connect(boost::bind(&Gui::Application::slotResetEdit, this, _1));
signalNewDocument(*pDoc);
pDoc->createView(View3DInventor::getClassTypeId());
// FIXME: Do we really need this further? Calling processEvents() mixes up order of execution in an
// unpredicatable way. At least it seems that with Qt5 we don't need this any more.
#if QT_VERSION < 0x050000
qApp->processEvents(); // make sure to show the window stuff on the right place
#endif
}
可以看到,在这个函数中实际上是通过调用Document::createView()创建视图窗口,通过分析代码可以发现,目前视图通过新建文档仅能View3Dinventor类型的视图。
void Document::createView(const Base::Type& typeId)
{
if (!typeId.isDerivedFrom(MDIView::getClassTypeId()))
return;
std::list<MDIView*> theViews = this->getMDIViewsOfType(typeId);
if (typeId == View3DInventor::getClassTypeId()) {
QtGLWidget* shareWidget = 0;
// VBO rendering doesn't work correctly when we don't share the OpenGL widgets
if (!theViews.empty()) {
View3DInventor* firstView = static_cast<View3DInventor*>(theViews.front());
shareWidget = qobject_cast<QtGLWidget*>(firstView->getViewer()->getGLWidget());
}
View3DInventor* view3D = new View3DInventor(this, getMainWindow(), shareWidget);
if (!theViews.empty()) {
View3DInventor* firstView = static_cast<View3DInventor*>(theViews.front());
std::string overrideMode = firstView->getViewer()->getOverrideMode();
view3D->getViewer()->setOverrideMode(overrideMode);
}
// attach the viewproviders. we need to make sure that we only attach the toplevel ones
// and not viewproviders which are claimed by other providers. To ensure this we first
// add all providers and then remove the ones already claimed
std::map<const App::DocumentObject*,ViewProviderDocumentObject*>::const_iterator It1;
std::vector<App::DocumentObject*> child_vps;
for (It1=d->_ViewProviderMap.begin();It1!=d->_ViewProviderMap.end();++It1) {
view3D->getViewer()->addViewProvider(It1->second);
std::vector<App::DocumentObject*> children = It1->second->claimChildren3D();
child_vps.insert(child_vps.end(), children.begin(), children.end());
}
std::map<std::string,ViewProvider*>::const_iterator It2;
for (It2=d->_ViewProviderMapAnnotation.begin();It2!=d->_ViewProviderMapAnnotation.end();++It2) {
view3D->getViewer()->addViewProvider(It2->second);
std::vector<App::DocumentObject*> children = It2->second->claimChildren3D();
child_vps.insert(child_vps.end(), children.begin(), children.end());
}
for(App::DocumentObject* obj : child_vps)
view3D->getViewer()->removeViewProvider(getViewProvider(obj));
const char* name = getDocument()->Label.getValue();
QString title = QString::fromLatin1("%1 : %2[*]")
.arg(QString::fromUtf8(name)).arg(d->_iWinCount++);
view3D->setWindowTitle(title);
view3D->setWindowModified(this->isModified());
view3D->setWindowIcon(QApplication::windowIcon());
view3D->resize(400, 300);
getMainWindow()->addWindow(view3D);
}
}
十、属性系统
Model/View架构是用来实现数据的存储、处理及其显示的。Model是应用对象,用于来表示数据,定义了数据访问的接口;View是模型的用户界面,负责显示数据与接收用户界面输入。Model/View架构核心是将数据的具体存储方式与数据的界面显示进行分离,View可以采用统一的接口访问Model中的数据。
在Qt中,QAbstractItemModel本身并不存储数据,只是定义了访问模型数据、编辑数据的接口;QModelIndex用来表示数据项的配置;QAbstractItemView通过信号-槽机制关联QAbstractItemModel模型;QAbstractItemDelegate则具体的定义QAbstractItemView显示和编辑数据的方式。
10.1 PropertyItem
PropertyItem是属性数据的存储节点,实际上是采用“双亲孩子法”来存储一棵属性树。节点数据存放在propertyItems链表中,
std::vector<App::Property*> PropertyItem::propertyItems;
10.2 PropertyModel
PropertyModel派生于QabstractItemModel,实现了访问属性结构的属性系统的功能,其内部维护了一个属性系统的根节点,
PropertyItem *PropertyModel::rootItem;
10.3 PropertyEditor
PropertyEditor派生于QtreeView,用于以树形方式显示PropertyModel中的数据项。
十一、多国语言支持
待续。此部分比较简单,主要是Translator通过QTranslator来实现FreeCAD界面对多种语言的支持。
十二、帮助系统
待续。此部分较简单,主要是通过Assistant来显示安装包doc目录下帮助文档。
暂无评论内容