FreeCAD源码分析:FreeCADGui模块

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目录下帮助文档。

 

参考资料

  1. FreeCAD Developer hub
  • 19
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
FreeCAD 是一款开源的三维建模软件,我们可以使用它进行各种复杂的三维设计和建模任务。源码分析是对软件源代码的深入研究,以了解软件的内部工作原理和实现细节。 首先,进行 FreeCAD 源码分析的主要目的之一是理解软件的整体架构和模块。通过仔细研究源代码,我们可以了解软件是如何组织和管理不同的功能模块的,这对于学习软件的整体设计思想和流程控制非常重要。 其次,源码分析可以帮助我们理解软件内部的数据结构和算法。FreeCAD 使用多种数据结构来表示三维模型和相关数据,例如,顶点列表、边列表和面列表等。通过分析源码,我们可以了解这些数据结构如何实现和组织,以及它们在软件中的具体应用。 此外,源码分析还可以帮助我们理解软件的实现细节和优化技巧。通过研究源代码,我们可以了解开发者是如何解决各种问题和挑战的。例如,我们可以看到他们在算法实现、图形渲染和性能优化方面的技巧和策略。 最后,源码分析促进了开源社区的发展。通过深入研究软件的源代码,我们可以了解软件的工作原理,并可以为项目做出贡献。我们可以提交 bug 报告、提供改进建议、编写文档或贡献代码等,从而推动软件的进一步发展和改进。 综上所述,对 FreeCAD 源码进行分析是了解软件内部工作原理和实现细节的关键步骤。通过源码分析,我们可以深入学习软件的整体架构、数据结构、算法和优化技巧,并为其发展做出贡献。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值