Making PyQt4 widgets with SIP

Check out the new site at https://rkblog.dev.

SIP is a tool for making a Python API for C and C++ libraries, and it is used to make PyQt. Both packages we will find in many Linux distributions. SIP has a documentation - for Qt3, or "look at python qscintilla bindings" :) But with the help of Phil and Google I've came up with a working solution for making PyQt4 widgets from Qt4 widgets.

To make a PyQt4 API we need the widget header file - like QLabel.h, and the libraries it "depends" on. When we have that file we will have to create few extra:

  • afile.sip - file named (recommended) like the header file. Contains SIP-readable description of the wrapped class.
  • config.py - config file, that will make Makefile and other goodies for compiling
  • config.py.in - extra config file (usualy blank?)

Making QTermWidget PyQt4 API

As a example I've selected from qt-apps.org a nice widget - QTermWidget. Unpack the source, and:
qmake
make
make install
Archlinux users can use PKGBUILD from AUR. From the source we need qtermwidget.h (lib folder). The main part of that file is:
class QTermWidget : public QWidget
{
    Q_OBJECT
public:
    
    enum ScrollBarPosition
    {
        /** Do not show the scroll bar. */
        NoScrollBar=0,
        /** Show the scroll bar on the left side of the display. */
        ScrollBarLeft=1,
        /** Show the scroll bar on the right side of the display. */
        ScrollBarRight=2
    };


    //Creation of widget
    QTermWidget(int startnow = 1, //start shell programm immediatelly
		QWidget *parent = 0);
    ~QTermWidget();

    //start shell program if it was not started in constructor
    void startShellProgram();
    
    //look-n-feel, if you don`t like defaults

    //	Terminal font
    // Default is application font with family Monospace, size 10
    void setTerminalFont(QFont &font); 
    
    //	Shell program, default is /bin/bash
    void setShellProgram(QString &progname);
    
    // Shell program args, default is none
    void setArgs(QStringList &args);
    
    //Text codec, default is UTF-8
    void setTextCodec(QTextCodec *codec);

    //Color scheme, default is white on black
    void setColorScheme(int scheme);
    
    //set size
    void setSize(int h, int v);
    
    // History size for scrolling 
    void setHistorySize(int lines); //infinite if lines < 0

    // Presence of scrollbar
    void setScrollBarPosition(ScrollBarPosition);
    
    // Send some text to terminal
    void sendText(QString &text);
            
signals:
    void finished();
        
protected: 
    virtual void resizeEvent(QResizeEvent *);
    
protected slots:
    void sessionFinished();        
    
private:
    void init();    
    TermWidgetImpl *m_impl;
};
Now we have to make a qtermwidget.sip file, that will describe what SIP should wrap. In the simple cases it's a list of method, functions, classes etc. and some SIP tags. qtermwidget.sip looks like this:
%Module QtermWidget 0

%Import QtCore/QtCoremod.sip
%Import QtGui/QtGuimod.sip


class QTermWidget : QWidget {

%TypeHeaderCode
#include <qtermwidget.h>
%End

public:
	QTermWidget(int startnow = 1, QWidget *parent = 0);
	~QTermWidget();
	enum ScrollBarPosition
    {
        NoScrollBar=0,
        ScrollBarLeft=1,
        ScrollBarRight=2
    };
	void setTerminalFont(QFont &font);
	void setShellProgram(QString &progname);
	void setArgs(QStringList &args);
	void setTextCodec(QTextCodec *codec);
	void setColorScheme(int scheme);
	void setSize(int h, int v);
	void setHistorySize(int lines);
	void setScrollBarPosition(ScrollBarPosition);
	void sendText(QString &text);
private:
	void *createTermWidget(int startnow, void *parent); 
	
};
We have few new things:
  • %Module QtermWidget 0 - name of the module
  • %Import QtCore/QtCoremod.sip
    %Import QtGui/QtGuimod.sip
    this adds PyQt4 SIP files needed to describe a Qt4 widget for SIP
  • %TypeHeaderCode
    #include 
    %End
    adding the header file, where wrapped class can be found
Next we have a list of public and private elements, copyed from the class.

Next stage is the config file. config.py looks like this:
import os
import sipconfig
from PyQt4 import pyqtconfig

# The name of the SIP build file generated by SIP and used by the build
# system.
build_file = "qtermwidget.sbf"

# Get the PyQt configuration information.
config = pyqtconfig.Configuration()

# Get the extra SIP flags needed by the imported qt module.  Note that
# this normally only includes those flags (-x and -t) that relate to SIP's
# versioning system.
qt_sip_flags = config.pyqt_sip_flags

# Run SIP to generate the code.  Note that we tell SIP where to find the qt
# module's specification files using the -I flag.
os.system(" ".join([config.sip_bin, "-c", ".", "-b", build_file, "-I", config.pyqt_sip_dir, qt_sip_flags, "qtermwidget.sip"]))

# We are going to install the SIP specification file for this module and
# its configuration module.
installs = []

installs.append(["qtermwidget.sip", os.path.join(config.default_sip_dir, "qtermwidget")])

installs.append(["qtermwidgetconfig.py", config.default_mod_dir])

# Create the Makefile.  The QtModuleMakefile class provided by the
# pyqtconfig module takes care of all the extra preprocessor, compiler and
# linker flags needed by the Qt library.
makefile = pyqtconfig.QtGuiModuleMakefile(
    configuration=config,
    build_file=build_file,
    installs=installs
)

# Add the library we are wrapping.  The name doesn't include any platform
# specific prefixes or extensions (e.g. the "lib" prefix on UNIX, or the
# ".dll" extension on Windows).
makefile.extra_libs = ["qtermwidget"]

# Generate the Makefile itself.
makefile.generate()

# Now we create the configuration module.  This is done by merging a Python
# dictionary (whose values are normally determined dynamically) with a
# (static) template.
content = {
    # Publish where the SIP specifications for this module will be
    # installed.
    "qtermwidget_sip_dir":    config.default_sip_dir,

    # Publish the set of SIP flags needed by this module.  As these are the
    # same flags needed by the qt module we could leave it out, but this
    # allows us to change the flags at a later date without breaking
    # scripts that import the configuration module.
    "qtermwidget_sip_flags":  qt_sip_flags
}

# This creates the qtermwidgetconfig.py module from the qtermwidgetconfig.py.in
# template and the dictionary.
sipconfig.create_config_module("qtermwidgetconfig.py", "config.py.in", content)
It's mostly a generic file, and for other projects like this only the name "qtermwidget" should changes. Note that "qtermwidgetconfig.py" is a config that will be generated (and it's not config.py). Create also a blank config.py.in.

If we have all the configuration done we can finish our work by compiling the API. First parse sip file with:
sip -t Qt_4_4_1 -I /usr/share/sip/ -t WS_X11 -c . *sip
Where:
  • Qt_4_4_1: is the ID of PyQt4 install (can be found in /usr/lib/python*/site-packages/PyQt4/pyqtconfig.py)
  • /usr/share/sip/: path to SIP folder, with installed sip files (for example PyQt4 sip files that we need)
And finaly:
python config.py
make
make install
As a test we can now run a simple PyQt4 app with this widget:
import sys
from PyQt4 import Qt
import QtermWidget

a = Qt.QApplication(sys.argv)
w = QtermWidget.QTermWidget()
#public methods can be used, for example setting font
#w.setTerminalFont(Qt.QFont('Terminus'))

w.show()
a.exec_()
QTermWidget

Widgets Code

My PyQt4 widget bindings can be found on PyQt4 Extrawidgets.
RkBlog

PyQt and GUI, 3 November 2008


Check out the new site at https://rkblog.dev.
Comment article
Comment article RkBlog main page Search RSS Contact