Skip to content

Notes on Building a Plugin

Keith edited this page Jun 4, 2015 · 4 revisions

Introduction

This document provides a few notes on building a plugin for ViTables. My goal was to add a widget to the workspace that would allow one to visualize an array as an image. The plugin was to take a (M,N) array and display an (M,N) image.

Setup

I started working off the development branch because that is where the current work is happening. This means the version is 2.2a1. The development branch uses setuptools instead of distutils which means the develop target is an option. The catch is, the executable no longer dynamically recognizes plugins. The work around is to pass the entry points to the script using a setup script in the plugin directory.

...
setup(
    ...
    
    install_requires = ["ViTables >2.1",],
    dependency_links = [
        "https://github.com/uvemas/ViTables@f6cb68227e10bf0658fd11b8daa56b76452b0341#egg=project-version"
    ],

    entry_points = {
        "vitables.plugins" : 
        "myplugin = myplugin:TargetClass"
    }
    ...
)

Note that this also indicates that the development branch of ViTables needs to be installed. You might have to hand install that.

The Class

vitables.plugins assumes that the class has the fields UID which appears to be a unique id (eg. 'vitables.plugins.import_csv'), NAME which is often the plugin_name, and a COMMENT. I'm not quite sure why these don't go to the default.

The About Page

The quickest hack to get the information page on the preferences dialog is to just import vitables.plugins.aboutpage.AboutPage and mimic the about information from the helpAbout method in the ImportCSV class. In the docstring, there is a little more information about what is going on.

Logging

The direct way to write to the logging display is to call

vitables.utils.getGui().logger.write('the message')

However, this does not provide the information about where the message came from.

When the script is launched, the root logger is setup with a file stream and the standard error is removed as a stream handler. In vitables.vtgui.VTGui.setup_logger_window, the vitables logger is grabbed and the logging window is set as the stream handler. The root logger is not handled. A work around is to add the GUI's logging window as a stream to handle my logging calls. Getting the logging to the file should Just Work™ because the file handler is added to the root logger. Getting the logging to the console might be trickier; however, I don't really care enough right now to figure it out. Add to that the fact that most users will simply double click which means no console any way.

Making the Sub Window

So, to add a QMdiSubWindow we get to have a fun run around. We can simply use to following snippet

workspace = vitables.utils.getGui().workspace
widget = AddWidget()
window = workspace.addSubWindow(widget)

where AddWidget() creates whatever widget you want. The problem with this approach is that vitables expects the QMdiSubWindow object to have the attribute dbt_leaf and getting that attached to the window and persist is a pain.

An alternative is to extend the QMdiSubWindow and add it to the workspace

class MyWindow(QMdiSubWindow):
    def __init__(self, parent, leaf):
        super(MyWindow, self).__init__(parent)
        widget = AddWidget()
        self.setWidget(widget)

        # These fields are required by ``vitables``
        self.pindex = None
        self.dbt_leaf = leaf

        # Cleanly close the window.
        self.setAttribute(QtCore.Qt.WA_DeleteOnClose)

class MyPlugin:
    def __init__(self, parent=None):
        super(MyPlugin, self).__init__(parent)

        # Get the GUI and create an action
        gui = vitables.utils.getGui()
        action = QtGui.QAction("Do Stuff", gui)
        action.triggered.connect(self.doit)

        # Add it to the context menu in the tree
        vitables.utils.addToLeafContextMenu(action)

        # This properly cleans up when the window is closed.
        self.setAttribute(QtCore.Qt.WA_DeleteOnClose)

    def doit(self):
        gui = vitables.utils.getGui()
        dbt = gui.dbs_tree_model # Grab a reference to the tree
        workspace = gui.workspace

        for index in vitables.utils.getSelectedIndexes():
            leaf = dbt.nodeFromIndex(index) # Convert to the tree leaf
            window = MyPlugin(parent=workspace, leaf=leaf)

This get's the basics working. Setting pindex to None is modeled after vitables.vtwidgets.zoom_cell. The leaf must be a leaf in the tree on the left. To get this, we need to grab the index and query the tree to get the correct leaf. This leaf has an attribute node that is the desired HDF5 array. This needs to be stored with the window.