Extending OpenAleaLab

This document explains how to concretely extend OpenAleaLab. For theorirical aspects and future plans, see PEP: Plugin management.

OpenAleaLab plugins

Plugin category -> Plugin Factories -> Plugin

Plugin category: A plugin category is defined using entry_point group. It defines implicitly that all plugins in this category adhere to a common interface. One plugin category contains at least one plugin factories.

Plugin factory: A simple class that describe component features. But, it is very important, that class must not imports module or implements features. To get class implementing features, a special method called instantiate can be used.

Plugin Class that actually implements features.

GUI special feature

In particular case of graphical components, a plugin generally defines an adapter able to connect a graphical component (a widget for example) to a specific application.

Example:

The module called oalab.gui.help provides this help widget:

oalab/gui/help.py
class HelpWidget(QtGui.QWidget):

    def openWeb(self, url):
        """
        displays url
        """

    def actions(self):
        """
        Returns a list of 4-uple describing actions:
            ('pane name', 'group name', qaction, button_size).

        Action currently defined are:
            - Open Url
            - Home
        """

OpenAleaLab is the main application that gather all widgets. We want to add HelpWidget in the MainWindow and allow communication between both classes. For that purpose, we create a Plugin called HelpWidget in helper package:

helper/plugins/oalab/helpwidget.py
class HelpWidget(object):

    data = {
    # Data that describe plugin
    }

    def __call__(self, mainwindow):
        # Import widget
        # Instantiate it
        # Ask to mainwindow to add it

It is very important to notice that adding widget in the right area is done by the plugin, not the application. Application does almost nothing, it is just a container of widgets. Real application intelligence is delegated to Plugins (placing and linking components) and components (doing real treatments).

Finally, we register this plugin in setup.py of package helper.

helper/setup.py
setup(
    # setup instructions

    entry_points = {
        'oalab.applet': [
            'helper = helper.plugins.oalab:HelpWidget'
        }
    )

Available entry points

  • oalab.applet [IApplet]: Graphical component displayed in main window.

Known issues

actions

  • method used by applet to provide action is not well described and not enough generic.

app/mainwin/session

  • role of session, application and app not clear

ControlPanel

  • project_manager is currently embedded in MainWindow (should be linked to session or app)

VPLScene

  • VPLScene has dependency to QtCore and QtGui (not expected)
  • Qt is used only to send a “SceneChanged” signal (Qt dependency not justified for only on signal)
  • Due to Qt dependency, scene is currently embeded in MainWindow.
  • As Scene is not graphical, it should be linked to session or application
  • -> Derivating VPLScene from Observed would be enouth.

Warning

FIXED: Scene using Observed/Listeners instead of Qt Signal&Slots works. There is a bug. Is it a new one ?

No handlers could be found for logger "openalea.core.pkgmanager"
Traceback (most recent call last):
  ...
  File "/usr/local/Cellar/python/2.7.6_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.py", line 322, in save
    raise PicklingError("%s must return string or tuple" % reduce)
pickle.PicklingError: <built-in method __reduce_ex__ of VPLScene object at 0x7fa15a5d6ed0> must return string or tuple

ProjectManagerWidget

  • dependency to AppletContainer (paradigm container).
  • ProjectManagerWidget must work without controller.applet_container

ControlPanel

  • hard links with project, colormap and control
  • no abstraction for controls
class ControlPanel
    def update(self)
        colors = self.colormap_editor.getTurtle().getColorList()
        self.session.project.control["color map"] = colors

        objects = self.geometry_editor.getObjects()
        for (manager, obj) in objects:
            if obj != list():
                obj, name = geometry_2_piklable_geometry(manager, obj)
                self.session.project.control[unicode(name)] = obj

        scalars = self.scalars_editor.getScalars()
        for scalar in scalars:
            self.session.project.control[unicode(scalar.name)] = scalar