ParaView-based Applications

From ParaQ Wiki
Jump to navigationJump to search

Applications based on ParaView have grown since the release of ParaView 3.0. In spite of best of our intentions we soon realized that it's not very easy to create applications that use ParaView core without subscribing to an user interface identical to ParaView's. The infamous pqMainWindowCore ended up being copy-pasted for every application and then tweaked and modified breaking every possible rule for good programming practices. Also it is hard to create ParaView-clone with limited user interface components, since various components have cross dependencies among each other and it becomes hard to ensure that all signals/slots are connected correctly for them to work.

To address these issues, we've been working on a re-factoring inspired by OverView's branding mechanism. The main goals we set out to address are:

  • Facilitate creation of applications radically different from ParaView in work-flow, such as OverView.
  • Facilitate creation of ParaView-variants that use most of ParaView functionality with minor behavioral or user-interface changes.

I've started working on a design based on OverView and other applications to address these goals. The latest state of this development can be checked out from git-hub repository (branch: Branding):

 > git clone git://github.com/utkarshayachit/ParaView.git

The new architecture pivots around two new concepts:

  • Reactions -- these are action handlers. They implement logic to handle the triggering of an action. They also implement logic to keep the enable state for the action update-to-date based on current application state. eg. pqLoadStateReaction -- reaction of load state action, which encapsulates ParaView's response for load state action. Reactions require the QAction to which they are reacting to. Any custom user interface that wants a QAction to be behave exactly like Paraview's, simply instantiates the reaction for it and that's it! The rest it managed by the reaction.
  • Behaviors -- these are abstract application behaviors eg. ParaView always remains connected to a server (builtin by default). This gets encapsulated into pqAlwaysConnectedBehavior. If any custom application wants to a particular behavior, simply instantiates the corresponding behavior! We'll see examples and it should hopefully become clearer.

Now let's look at some example applications to see how all this helps us.

Radically different Application based on ParaView core

First consider an application which is a totally new client based on ParaView core i.e. ServerManager. It has it's own custom everything. Here's how such an application's main.cxx will be:

  #include "pqCoreApplication.h"  // instead of QApplication
  #include "myCustomMainWindow.h"

  int main(int argc, char** argc)
  {
   pqCoreApplication app (argc, argv);
   myCustomMainWindow window;
   window.show();
   return app.exec();
  }

As you can see, this is exactly like creating a Qt application, except that instead of QApplication, we are creating pqCoreApplication which is a QApplication subclass. pqCoreApplication initializes the pqApplicationCore etc. which enables access to the pqObjectBuilder, pqServerManagerModel among other things, which is the thin Qt-layer over ServerManager. No more ProcessModuleGUIHelper, or PVMain or any such whimsical, hard to understand devices. Just create the pqCoreApplication and you are all set.

ParaView-based Applications

Now lets consider applications that are variants of ParaView that use elements for the pqComponents library such as the pipeline browser, object inspector, selection inspector etc. Such applications fall under the purview of branding. There's a new Cmake macro "build_paraview_client" that can be used for this purpose. This macro will be of the following form. Note all parts aren't implemented yet namely the plugins etc. but it's getting there.

 build_paraview_client(
  # The name for this client. This is the name used for the executable created.
  paraview_revamped
  
  # This is the title bar text. If none is provided the name will be used.
  TITLE "Kitware ParaView"
  
  # This is the organization name.
  ORGANIZATION "Kitware Inc."
  
  # PNG Image to be used for the Splash screen. If none is provided, default
  # ParaView splash screen will be shown. 
  SPLASH_IMAGE "${CMAKE_CURRENT_SOURCE_DIR}/Splash.png"
  
  # Not sure how useful this is, but since OverView was using it, we are
  # providing an option to change the text color used when showing the splash
  # screen. Optional, of course.
  SPLASH_TEXT_COLOR "black"
  
  # Provide version information for the client.
  VERSION_MAJOR ${PARAVIEW_VERSION_MAJOR}
  VERSION_MINOR ${PARAVIEW_VERSION_MINOR}
  VERSION_PATCH ${PARAVIEW_VERSION_PATCH}
    
  # Icon to be used for the Mac bundle.
  BUNDLE_ICON   "${CMAKE_CURRENT_SOURCE_DIR}/Icon.icns"
  
  # Icon to be used for the Windows application.
  APPLICATION_ICON "${CMAKE_CURRENT_SOURCE_DIR}/Icon.ico"
                                                                                 
  # Name of the class to use for the main window. If none is specified,
  # default QMainWindow will be used.
  PVMAIN_WINDOW QMainWindow
  PVMAIN_WINDOW_INCLUDE QMainWindow 
  
  # Next specify the plugins that are needed to be built and loaded on startup
  # for this client to work. These must be specified in the order that they
  # should be loaded.
  # Currently, only client-based plugins are supported. i.e. no effort is made
  # to load the plugins on the server side when a new server connection is made.
  # That may be added in future, if deemed necessary.
  REQUIRED_PLUGINS PointSpritePlugin
  
  # Next specify the plugin that are not required, but if enabled, should be
  # loaded on startup.
  # These must be specified in the order that they
  # should be loaded.
  # Currently, only client-based plugins are supported. i.e. no effort is made
  # to load the plugins on the server side when a new server connection is made.
  # That may be added in future, if deemed necessary.
  OPTIONAL_PLUGINS ClientGraphView ClientTreeView
                                                                                 
  # Extra targets that this executable depends on.
  EXTRA_DEPENDENCIES blah1 blah2
                                                                                 
  # GUI Configuration XMLs that are used to configure the client eg. readers,
  # writers, sources menu, filters menu etc.
  GUI_CONFIGURATION_XMLS <list of xml files>
  )

So for the new ParaView client which is a branded -paraview application itself, this looks like (the code is in Applications/Client2):

  #------------------------------------------------------------------------------
  # Build the client
  build_paraview_client(paraview_revamped
    TITLE "ParaView (Revamped)"
    ORGANIZATION  "Kitware Inc."
    VERSION_MAJOR 3 
    VERSION_MINOR 7
    VERSION_PATCH 1
    SPLASH_IMAGE "${CMAKE_CURRENT_SOURCE_DIR}/PVSplashScreen.png"
    PVMAIN_WINDOW pqClient2MainWindow
    PVMAIN_WINDOW_INCLUDE pqClient2MainWindow.h
    EXTRA_DEPENDENCIES pqClient2
    GUI_CONFIGURATION_XMLS
      ${CMAKE_CURRENT_SOURCE_DIR}/ParaViewSources.xml
      ${CMAKE_CURRENT_SOURCE_DIR}/ParaViewFilters.xml
      ${CMAKE_CURRENT_SOURCE_DIR}/ParaViewReaders.xml
      ${CMAKE_CURRENT_SOURCE_DIR}/ParaViewWriters.xml
  )

Here the pqClient2MainWindow is the main window which uses a ui file for the GUI design. It has empty menus as place holders for the File/Sources/Filters and other menus. Now since this client needs a pipeline browser, in the designer, we simply create a QWidget and promote it to pqPipelineBrowser and place it however we want and that's it. No more signal/slot connections or any such nonsense. pqPipelineBrowser is now a first class QWidget subclass that is autonomous in the sense that it relies on pqApplicationCore and works on it's own on simple instantiation. Exactly same is the case with pqProxyTabWidget (which is the object inspector) or pqSelectionInspector etc. etc. All these are QWidget subclasses that you merely need to instantiate in the ui file and you will get those in your application. These components have no cross dependencies. So pipeline browser doesn't depend on object inspector and vice-versa. Thus making it easier for custom clients to include only those components from the ParaView client that they are interested in.

Now lets move on to the menu/toolbars in general QActions. If my custom client has it's own File menu with just once action "Open Data", and I want this action to behave like paraview' open data where it prompts the user for the file to load based on supported readers, I do the following in my MainWindow subclass (or an auto-start plugin's initialization where the UI is being initialized)


  new pqLoadDataReaction(ui.actionLoadData);


This will automatically enable/disable the action based on whether paraview is connected to a server; add action handlers to popup the file dialog listing all readers supported etc. Now how to define the supported readers? The GUI_CONFIGURATION_XMLS !!! Just list the readers under a <ParaViewReaders /> xml element and those file formats will be listed in this dialog.

Now if my application wants a File menu exactly like ParaView's, then we do:


  pqParaViewMenuBuilders::buildFileMenu(*ui.menu_File);


pqParaViewMenuBuilders has helper methods to create the actions and reactions for those actions for all standard paraview menus. Looking at the implementation of those, custom-app writers can pick and choose the reactions for their custom menus.

Finally, behaviors. If our client needs to stay connected to a server always, like ParaView, simply instantiate the pqAlwaysConnectedBehavior in the main window constructor or an auto-start plugin.

The MainWindow implementation for the new ParaView-client currently looks as follows:


  #include "pqClient2MainWindow.h"
  #include "ui_pqClient2MainWindow.h"
  
  #include "pqParaViewMenuBuilders.h"
  #include "pqParaViewBehaviours.h"
  
  class pqClient2MainWindow::pqInternals : public Ui::pqClient2MainWindow
  {
  };
  
  //-----------------------------------------------------------------------------
  pqClient2MainWindow::pqClient2MainWindow()
  {
    this->Internals = new pqInternals();
    this->Internals->setupUi(this);
  
    // enable automatic creation of representation on accept.
    this->Internals->proxyTabWidget->setShowOnAccept(true);
  
    // Populate application menus with actions.
    pqParaViewMenuBuilders::buildFileMenu(*this->Internals->menu_File);
  
    // Populate sources menu.
    pqParaViewMenuBuilders::buildSourcesMenu(*this->Internals->menu_Sources);
  
    // Populate filters menu.
    pqParaViewMenuBuilders::buildFiltersMenu(*this->Internals->menu_Filters);
  
    // Define application behaviours.
    // pqParaViewBehaviours simply creates all the behaviour instances used by ParaView by default.
    // Currently equivalent to following, but will change as new behaviors are added.:
    // new pqDefaultViewBehaviour(this);
    // new pqAlwaysConnectedBehavior(this);
    // new pqNewSourceActiveBehaviour(this);
    new pqParaViewBehaviours(this);
  }
  
  //-----------------------------------------------------------------------------
  pqClient2MainWindow::~pqClient2MainWindow()
  {
    delete this->Internals;
  }


Using behaviors and reactions makes it possible to avoid ending up with a huge monolith as pqMainWindowCore where all application logic gets concentrated. It also makes it possible to pick-and-choose when writing custom apps, avoiding duplication whenever possible.

Please take a look at the ui file and C++ code in ParaView3/Application/Client2 in the git repository to understand how this works,