PyQt interfaces in Bionumerics 6

PyQt is a set of Python bindings to Qt — Nokia’s cross-platform GUI toolkit. Qt is mature, versatile and is released under an Open Source license. It is used in software like Google Earth, KDE, Opera, Skype, VLC media player and VirtualBox (Source: Wikipedia).

One of the advantages of using PyQt for graphical user interface (GUI) development is the bundled Qt Designer makes GUI design very easy and efficient. Widgets can be dragged and dropped onto dialogs, main windows and widgets. The resulting XML user interface (.ui) files can be converted to Python code using the scripts included with PyQt.

Using PyQt from within Bionumerics scripts opens up a number of possibilities — use simple message boxes with error or informative texts, create dialogs and display windows for working with databases and so on. Bionumerics includes some modules including BioPython but PyQt will need to be included manually. It is fairly simple to use an existing PyQt installation.

Requirements

  1. Python 2.6
    The latest stable version as of this writing is 2.6.5. It can be downloaded from http://python.org/download/releases/

  2. PyQt
    Latest version can be downloaded and installed from http://www.riverbankcomputing.co.uk/software/pyqt/download [under Binary packages]. PyQt version as of this writing is PyQt-Py2.6-gpl-4.7.3-2.exe.

  3. To be able to import PyQt modules from within Bionumerics, the module directory:
    C:\Python26\Lib\site-packages
    should be added to PYTHONPATH or appended to sys.path before other import statements.

  4. The Qt DLL’s must be available or added to the system PATH. This will be done automatically by the PyQt installer. If the DLL’s are not added to the system path, it will lead to error messages like “DLL load failed” and the script will fail.

Sample Application

To demonstrate, I am going to use a simple dialog application as an example. This application is used to set the value of a given field of entries selected in the BioNumerics main window. When run, this is how it will appear:

The Set field dialog

Step 1 — Creating the dialog using Qt Designer

The dialog displayed above was created in Qt Designer and the file was saved as setfielddlg.ui. The text at the top, Field and Value labels are all of type QLabel. The combo box used to display existing field names from the database is of type QComboBox. The input field corresponding to Value is of type QLineEdit. The Apply, Close and Deselect All buttons are all of type QPushButton.

A list of tutorials on PyQt can be found here. The book Rapid GUI Programming with Python and Qt is an excellent reference on the topic. Also useful is Riverbank’s documentation of PyQt’s classes.

Step 2 — Generating Python code for the Dialog

The setfielddlg.ui file generated from Qt Designer in the previous step is an XML file describing all the elements of the dialog. This can be converted to Python code using the pyuic4 command included with PyQt4:

pyuic4 -o ui_setfielddlg.py setfielddlg.ui

The -o option specifies the name of the output file. The dialog can now be called from other scripts.

Note

C:\Python26\Lib\site-packages\PyQt4\bin
should be in the system PATH for the pyuic4 command to work.

Step 3 — The main script

set_field.py

"""A simple dialog application to set the field of selected entries in
Bionumerics 6

"""
import sys
import bns

#append paths to PyQt4 and the current directory to sys.path
moduledir = [r'C:\Python26\Lib\site-packages', r'G:\Python\Bionumerics_scripts\src']

for moddir in moduledir:
        if moddir not in sys.path:
                sys.path.append(moddir)

import dbf
from PyQt4.QtCore import SIGNAL
from PyQt4.QtGui import QApplication, QDialog, QMessageBox
import ui_setfielddlg

class Dlg(QDialog, ui_setfielddlg.Ui_Dialog):

    def __init__(self, fieldnames, parent=None):
        super(Dlg, self).__init__(parent)
        self.setupUi(self)

        self.connect(self.close_button, SIGNAL('clicked()'), self.accept)
        self.connect(self.apply_button, SIGNAL('clicked()'), self.setfield)
        self.connect(self.deselect_button, SIGNAL('clicked()'), self.deselect)

        if fieldnames:
            self.field_combo_box.addItems(fieldnames)
        else:
            QMessageBox.critical(None, 'Error', 'Could not get fieldnames')
            return

    def deselect(self):
        '''Deselect all entries if selected'''

        if len(bns.Database.Db.Selection):
            bns.Database.Db.Selection.Clear()
        else:
            return

    def setfield(self):
                '''Sets the field value of selected entries'''

        field = unicode(self.field_combo_box.currentText())
        value = unicode(self.value_line_edit.text())

        selected = dbf.getSelected()

        if selected:
            for item in selected:
                key = item.Key
                try:
                    bns.Database.EntryField(key, field).Content = value
                except Exception, e:
                    QMessageBox.critical(None, 'Error', str(sys.exc_info()))
                bns.Database.Db.Fields.Save()
                else:
                    QMessageBox.information(None,'No entries selected',
                       'Please select entries before running script')

if __name__ == '__main__':
    sys.__dict__['argv'] = ['argv']
    app = QApplication(sys.argv)
    fields = dbf.getFieldNames()
    dialog = Dlg(fields)
    dialog.show()
    __bnscontext__.Stop(app.exec_())

Step 4 — Running the script

Source code of all the scripts used here can be downloaded from my GitHub repository.

The scripts set_field.py and ui_setfielddlg.py and the module dbf.py should be in the same directory. In my case, this was G:\Python\Bionumerics_scripts\src and also specified in the moduledir list in the set_field.py script.

The dialog can be called by using the Scripts -> Run script from file option in Bionumerics and selecting set_field.py.

For this to work, database entries must be selected in the main window of BioNumerics or else the dialog displays a message and does nothing. Select the field from the Field combo box, type in the value that needs to be set for the field and hit Apply. The value field has a maximum length of 80 characters, which is the maxiumum length of a field in BioNumerics.

The script in detail

Some additional notes

Imports

  • bns is the BioNumerics Python module
  • PyQt4 module directories are appended to sys.path, as is the path to the directory of the running script. This is done as to import any additional modules used by the script.
  • dbf is a module I wrote for general database functions. In this script, it is used for getting a list of selected entries (get_selected) and for getting the list of field names in the database - get_field_names.

ui_setfielddlg contains code for the dialog:

import sys
import bns

#append paths to PyQt4 and the current directory to sys.path
moduledir = [r'C:\Python26\Lib\site-packages', r'G:\Python\Bionumerics_scripts\src']

for moddir in moduledir:
    if moddir not in sys.path:
        sys.path.append(moddir)

import dbf
from PyQt4.QtCore import SIGNAL
from PyQt4.QtGui import QApplication, QDialog, QMessageBox
import ui_setfielddlg

The dialog class — Dlg

Takes the fieldnames as an argument and populates the Field combo box if it is not empty. The clicked() signal of the close button is connected to the accept slot, which closes the dialog. This is the same signal that is emitted by when the window is closed from the menubar. The other buttons are connected to their respective slots which are defined within the class.

main

The: sys.__dict__['argv'] = ['argv'] does nothing. For some reason, sys.argv is empty or not defined when running the script from BioNumerics, and the line of the code QApplication(sys.argv) refuses to work without it.

As no commandline arguments are used in the code above, it should cause no harm: __bnscontext__.Stop(app.exec_()) stops the program once the event loop — app.exec_() returns. This is similar to sys.exit(app.exec_()) used in standard PyQt4 programs.

Troubleshooting

Some issues I am aware of:

  • Only one instance of the script should run. Calling it twice causes a crash.
  • The dialog must be closed before exiting Bionumerics or else the program crashes.

Comments