SALOME源码分析:GUI模块

济南友泉软件有限公司

SALOME是一套开源跨平台的CAE集成开发平台,包含全参数化建模、后处理可视化、求解器集成、分析优化等模块。而SALOME GUI模块则构筑了SALOME的整体框架。

Ref. from SALOME GUI Architecture

SALOME GUI module or SUIT (SALOME User Interface Toolkit) represents a user interface framework for SALOME platform. It reflects the last trends of the GUI developments and best practices of the software development. SUIT is developed as a set of packages implementing reusable software components, allowing building multi-scale CAD applications. With SUIT it is possible both to implement new CAD applications (completely independent from SALOME itself) and develop and integrate fully SALOME-compliant modules - “light-weight” (without any CORBA connection, also called “light”) and “full-scale” (distributed, CORBA-based), using C++ or Python programming languages.

Among other important features SUIT proposes flexible, powerful and safe mechanisms of interaction with other SALOME modules (both distributed and “light”), resources management, viewers and selection handling, exception/signals processing, bringing to the top the multi desktop dockable-windowed user interface which improves usability of SALOME GUI.

本模块涉及的主要技术要点包括:

  • Single Responsibility Principle
  • Layered Architecture
  • Command Pattern
  • Data Object Model
  • Plug-in System

关于上述技术要点,可参见笔者博文大型CAx(CAD/CAE/CAM)工业软件开发中的关键组件

鉴于SALOME GUI模块是整个SALOME的基础模块,宜作为研究的切入点。同时考虑到SALOME GUI模块功能特点与独立性,本文拟主窗口、视图、菜单、工具栏等CAx软件界面组件方面探讨SALOME GUI模块实现的原理。

注1:限于笔者研究水平,难免有表述不当,欢迎批评指正

注2:博文会不定期更新,敬请关注

一、整体流程

suitexe是SALOM提供的加载不同应用的一个启动器,默认会加载LightApp。对应代码为gui/src/SUITApp/SUITApp.cxx。

// gui/src/SUITApp/SUITApp.cxx

//............

int main( int argc, char* argv[] )
{
  //............
  if ( args.empty() )
    args << "LightApp"; // fallback application library

  //............

  // Create session
  Session session( iniFormat );

  // Initialize and start application supplied by the library specified via the parameter
  SUIT_Application* sessionApp = session.startApplication( appName );
  if ( sessionApp )
  {
#ifdef USE_SALOME_STYLE
    Style_Salome::initialize( session->resourceMgr() );
    if ( session->resourceMgr()->booleanValue( "Style", "use_salome_style", true ) )
      Style_Salome::apply();
#endif // USE_SALOME_STYLE

    if ( !debugExceptions )
      app.setHandler( session.handler() );

    if ( splash )
      splash->finish( sessionApp->desktop() );

    return app.exec();
  }

  return 1;
}

SUIT_Session用于根据用户输入参数appName创建不同的应用对象,实际上就是加载不同的动态链接库,并通过调用动态链接库内的createApplication()来生成应用程序对象。 

fcd4bfd930754b38846ba468a6810b47.png

从上图可以看出,SUIT_Application是所用应用对象的基类,应用对象定义文档类型(SUIT_Application::myStudy)、主窗口(SUIT_Application::myDesktop)、公用菜单与公用工具栏(SUIT_Application::myActionMap)、视图(STD_Application::myViewMgrs)等组件。

注:主窗口菜单、工具栏主要来自两部分,一部分是由SUIT_Application::myActionMap所定义菜单与工具栏;另一部分是来自CAM_Module::myActionMap

Ref. from gui/src/SUIT/SUIT_Application.h

An Application is a class which defines application configuration and behaviour. For example Application object defines what Viewers are used in this application, what auxilliary windows are present, how user can dial with them. Also Application object defines an sertain type of data structure by holding of pointer on an instance of SUIT_Study class (which represents Document data structure). In other words Application defines type of sata structure, type of used Viewers, type of main GUI widget (Desktop), and other auxilliary tools.

二、主要组件

2.1 SUIT_Session

SUIT_Session实际上可以看作整个程序的启动器,根据指定的动态链接库名称,调用动态链接库内的createApplication()来创建不同的SUIT_Application对象,进而通过虚函数SUIT_Application::start()来完成主窗口创建、模块加载等启动工作。

Ref. from SUIT_Session.h

The class Sesssion manages launching of Applications. Application must be returned by static function "createApplication" in external library. The Library must be loaded with loadLibrary method and after that application can be started.

2.2 CAM_Application

SUIT_Application用于窗口SUIT_Application::myDesktop与文档对象SUIT_Application::myStudy等的管理。

在SALOME中,主窗口菜单、工具栏等界面组件主要来自两部分:一部分是共用菜单与工具栏,这部分实际上是存储在SUIT_Application::myActionMap;另一部分是与当前模块相关的菜单、工具栏,这部分存储在CAM_Module::myActionMap。

STD_Application(派生于SUIT_Application)定义了文件打开、文件保存等共用菜单栏、工具栏,同时提供了视图管理功能。

CAM_Application(派生于STD_Application)主要提供了功能模块CAM_Module加载与管理的功能。

2.3 数据对象模型

SUIT_Study实际上就是文档对象,用于组织树状结构的数据对象SUIT_DataObject。另外,SUIT_Study提供了基于事务的文档修改的接口。

CAM_Study(派生于SUIT_Study)提供了对CAM_DataObject(派生于SUIT_DataObject)的支持。

2.4 主窗口

SUIT_Desktop定义了菜单管理器SUIT_Desktop::myMenuMgr与工具栏管理器SUIT_Desktop::myToolMgr。根据QAction的编排顺序,菜单管理器myMenuMgr与myToolMgr用于将QAction添加到菜单与工具栏。

/*!
  \class SUIT_Desktop
  Provides standard desktop: main window with
  main menu manager, toolbars manager and logo.
*/
class SUIT_EXPORT SUIT_Desktop : public QtxMainWindow
{
  Q_OBJECT

  class ReparentEvent;

  enum { Reparent = QEvent::User };

public:
  SUIT_Desktop();
  virtual ~SUIT_Desktop();

  QtxActionMenuMgr*        menuMgr() const;
  QtxActionToolMgr*        toolMgr() const;
  QtxLogoMgr*              logoMgr() const;

  virtual SUIT_ViewWindow* activeWindow() const = 0;
  virtual void setActiveWindow(SUIT_ViewWindow*);
  virtual QList<SUIT_ViewWindow*> windows() const = 0;

  int                      logoCount() const;

  void                     logoClear();
  void                     logoRemove( const QString& );
  void                     logoInsert( const QString&, QMovie*, const int = -1 );
  void                     logoInsert( const QString&, const QPixmap&, const int = -1 );

  void                     emitActivated();
  void                     emitMessage( const QString& );

signals:
  void                     activated();
  void                     deactivated();
//  void                     moved();
  void                     windowActivated( SUIT_ViewWindow* );
  void                     closing( SUIT_Desktop*, QCloseEvent* );
  void                     message( const QString& );

protected:
  virtual bool             event( QEvent* );
  virtual void             customEvent( QEvent* );
  virtual void             closeEvent( QCloseEvent* );
  virtual void             childEvent( QChildEvent* );

  virtual void             addWindow( QWidget* ) = 0;

private:
  QtxActionMenuMgr*        myMenuMgr;
  QtxActionToolMgr*        myToolMgr;
  QtxLogoMgr*              myLogoMgr;
};

另外,在STD模块中,派生了STD_SDIDesktop、STD_MDIDesktop、STD_TabDesktop等三种主窗口,STD_Application与CAM_Application默认使用STD_MDIDesktop类型主窗口(参见STD_Application构造函数)。

2.5 视图

SUIT_ViewWindow是所有视图的基类,SUIT_ViewManager、SUIT_ViewModel用于管理某一类型的视图,而STD_Application::myViewMgrs记录了LightApp_Application所支持的所有视图类型;同时每当激活一个Module时,变创建/激活对应的视图。

View Manager:
Handles all the 3D or 2D View windows of the given type (OCC, VTK, Plot2d, etc) (2.9).
View Model
Responsible for appearance and behavior of the View window (provide methods for displaying/erasing of objects, process user actions, like mouse clicks and keystrokes, processes selection of objects in the View window , handles popup menus, etc)
View WIndow
View frame that embeds 3D or 2D viewer in GUI

2.6 CAM_Module

CAM_Module不仅定义了一组菜单、工具栏等界面元素,而且维护了模块相关的对象数据结构。

/*!
  \class CAM_Module
  \brief Base implementation of the module in the CAM application architecture.

  Provides support of menu/toolbars management.
*/
class CAM_EXPORT CAM_Module : public QObject
{
  Q_OBJECT

public:
  CAM_Module();
  CAM_Module( const QString& );
  virtual ~CAM_Module();

  virtual void           initialize( CAM_Application* );

  QString                name() const;
  QString                moduleName() const;
  virtual QPixmap        moduleIcon() const;
  virtual QString        iconName() const;

  CAM_DataModel*         dataModel() const;
  CAM_Application*       application() const;

  virtual void           contextMenuPopup( const QString&, QMenu*, QString& ) {};
  virtual void           updateCommandsStatus() {};

  virtual void           putInfo( const QString&, const int = -1 );

  int                    showNotification(const QString& message, const QString& title, int timeout = -1);
  void                   hideNotification(const QString& message);
  void                   hideNotification(int id);

  bool                   isActiveModule() const;

  virtual void           setMenuShown( const bool );
  void                   setMenuShown( QAction*, const bool );
  void                   setMenuShown( const int, const bool );

  virtual void           setToolShown( const bool );
  void                   setToolShown( QAction*, const bool );
  void                   setToolShown( const int, const bool );

  virtual void           updateModuleVisibilityState();

  virtual bool           activateOperation( int actionId );
  virtual bool           activateOperation( const QString& actionId );
  virtual bool           activateOperation( const QString& actionId, const QString& pluginName );

  // actions/menu/toolbars management

  QtxActionMenuMgr*      menuMgr() const;
  QtxActionToolMgr*      toolMgr() const;

  virtual QAction*       action( const int ) const;
  virtual int            actionId( const QAction* ) const;
  virtual QAction*       createAction( const int, const QString&, const QIcon&, const QString&,
                                       const QString&, const int, QObject* = 0,
                                       const bool = false, QObject* = 0, const char* = 0, const QString& = QString() );
  virtual QAction*       createAction( const int, const QString&, const QIcon&, const QString&,
                                       const QString&, const QKeySequence&, QObject* = 0,
                                       const bool = false, QObject* = 0, const char* = 0, const QString& = QString() );
  QtxActionGroup*        createActionGroup( const int, const bool = true );

  int                    createTool( const QString&, const QString& = QString() );
  int                    createTool( const int, const int, const int = -1 );
  int                    createTool( const int, const QString&, const int = -1 );
  int                    createTool( QAction*, const int, const int = -1, const int = -1 );
  int                    createTool( QAction*, const QString&, const int = -1, const int = -1 );
  void                   clearTool( const QString& title );

  int                    createMenu( const QString&, const int, const int = -1, const int = -1, const int = -1,QMenu * = 0);
  int                    createMenu( const QString&, const QString&, const int = -1, const int = -1, const int = -1 );
  int                    createMenu( const int, const int, const int = -1, const int = -1 );
  int                    createMenu( const int, const QString&, const int = -1, const int = -1 );
  int                    createMenu( QAction*, const int, const int = -1, const int = -1, const int = -1 );
  int                    createMenu( QAction*, const QString&, const int = -1, const int = -1, const int = -1 );

  static QAction*        separator();

public slots:
  virtual bool           activateModule( SUIT_Study* );
  virtual bool           deactivateModule( SUIT_Study* );

  virtual void           connectToStudy( CAM_Study* );

  virtual void           studyClosed( SUIT_Study* );
  virtual void           studyChanged( SUIT_Study*, SUIT_Study* );

  virtual void           onApplicationClosed( SUIT_Application* );

private slots:
  void                   onInfoChanged( QString );

protected: 
  virtual bool           isSelectionCompatible();

  virtual CAM_DataModel* createDataModel();

  void                   setName( const QString& );
  virtual void           setModuleName( const QString& );

  int                    registerAction( const int, QAction* );
  bool                   unregisterAction( const int );
  bool                   unregisterAction( QAction* );
  // IMN 05/03/2015: we copied myActionMap for reset/unset actions accelerator keys
  // after activate/deactivate modules
  QMap<QAction*, QKeySequence> myActionShortcutMap; //!< copy actions shortcut map

  virtual bool           abortAllOperations();

private:
  CAM_Application*       myApp;             //!< parent application object
  QString                myName;            //!< module title (user name)
  QPixmap                myIcon;            //!< module icon
  QString                myInfo;            //!< latest info message
  CAM_DataModel*         myDataModel;       //!< data model
  QMap<int, QAction*>    myActionMap;       //!< menu actions
  bool                   myMenuShown;       //!< menu shown flag
  bool                   myToolShown;       //!< tool shown flag

  friend class CAM_Application;
};

通过重写CAM_Module::initialize()函数可以将创建的QAction对象添加到CAM_Module::myActionMap,同时将其注册到主窗口SUIT_Desktop的菜单管理器SUIT_Desktop::myMenuMgr与工具栏管理器SUIT_Desktop::myToolMgr,进而将这些QAction对象添加到主窗口。

CAM_DataModel实现了树状层次结构的数据对象模型,每个CAM_Module将创建的CAM_DataObject添加到CAM_DataModel::myDataModel。

三、总结与讨论

按照研究问题的顺序,笔者抛出以下问题供大家分析与讨论。

Q1. 试分析SALOME启动器的工作原理与优缺点。

Q2. 总结CAM_Application及其子类的创建过程?

Q3. Module是何时加载的?

A3. 在CAM_Application创建时读取module列表,SUIT_Session::startApplication()会调用CAM_Application::start(),进而调用CAM_Application::loadModules()加载各个模块。

Q4. 在GUI模块中,toolbar、menu等何时创建与更新?

整个界面的toolbar、menu等界面元素来自两部分:

Q5. toolbar与menu是如何出发命令响应的?

Q6. 命令Redo/Undo是如何实现的?

Q7. 每个module都有一个数据对象模型,其与study对象关系是怎样的?

Q8. ViewManager是何时创建的,怎样添加不同的Viewer?

Q9. 切换Module时, SALOME时如何更新菜单、工具栏、停靠窗口、视图等界面元素的?

A9. 切换Module,会调用LightApp_Application::activateModule完成界面元素的更新。

updateModuleActions()更新菜单与工具栏;updateWindows()更新停靠窗口;updateViewManagers()更新视图;

/*!Activate module by \a modName*/
bool LightApp_Application::activateModule( const QString& modName )
{
  QString actName;
  CAM_Module* prevMod = activeModule();

  if ( prevMod )
    actName = prevMod->moduleName();

  QString name = modName;
  if ( !name.isEmpty() && !moduleTitle( modName ).isEmpty() )
    name = moduleTitle( modName );

  if ( actName == name )
    return true;

  putInfo( tr( "ACTIVATING_MODULE" ).arg( name ) );

  saveDockWindowsState();

  if ( infoPanel() )
    infoPanel()->clear();

  bool status = CAM_Application::activateModule( name );

  updateModuleActions();

  putInfo( "" );

  if ( !status )
    return false;

  updateWindows();
  updateViewManagers();

  if ( activeStudy() && activeStudy()->root() && objectBrowser() ) {
    if ( objectBrowser()->root() != activeStudy()->root() )
      objectBrowser()->setRoot( activeStudy()->root() );
    updateObjectBrowser( true );
  }

  if ( activeModule() ) activeModule()->updateModuleVisibilityState();

  updateActions();
  return true;
}

参考文献

Erich Gamma,Richard Helm,Ralph Johnson,John Vlissides. Design Patterns:elements of reusable object-oriented software. Addison Wesley, 1994.

Martin Fowler. Patterns of Enterprise Application Architecture.  Addison Wesley, 2002.

网络资料

SALOMEicon-default.png?t=N7T8https://www.salome-platform.org/

SALOME GUI Architectureicon-default.png?t=N7T8https://docs.salome-platform.org/latest/extra/SALOME_GUI_Architecture.pdf

GUI Architectures icon-default.png?t=N7T8https://martinfowler.com/eaaDev/uiArchs.html

大型CAx(CAD/CAE/CAM)工业软件开发中的关键组件 icon-default.png?t=N7T8https://blog.csdn.net/qq_26221775/article/details/123193318?spm=1001.2014.3001.5501

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值