Tworzenie widżetów PyQt4 za pomocą SIP

SIP to narzędzie do automatycznego generowania Pythonowego API z bibliotek C/C++. SIP powstało jako narzędzie do tworzenia API dla biblioteki PyQt4, lecz można stosować je do bibliotek niezwiązanych z Qt (choć w tym przypadku lepszym rozwiązaniem może być BOOST Python). SIP, jak i PyQt znajdziemy w praktycznie każdej dystrybucji Linuksa, a także pod Mac OS X czy MS Windows.

Dokumentacja do SIP istnieje - w antycznej wersji dla Qt3 z niepełnymi przykładami (brak części kodu, pokazano tylko samo obudowanie funkcji), lub też w postaci "popatrz na python-qscintilla". Pomęczyłem trochę Phila oraz Google i udało mi się nakłonić SIPa do współpracy przy tworzeniu API widżeta Qt4. Ogólnie jest to w miarę proste zadanie - dla prostych widżetów.

By stworzyć API dla PyQt4 widżeta musimy mieć jego plik nagłówkowy (np. QLabel.h) oraz odpowiednie biblioteki zainstalowane w systemie (tak jakbyśmy chcieli kompilować program w C++/Qt wykorzystujący ten widżet). Czyli standardowo pobieramy źródła widżetu, kompilujemy i instalujemy. Mając plik nagłówkowy należy zabrać się za stworzenie plików "konfiguracyjnych":

  • plik.sip - plik SIP o nazwie takiej, jak nazwa biblioteki nagłówkowej (zalecane) zawierający "opis" API biblioteki zrozumiały dla SIP.
  • config.py - plik konfiguracyjny generujący Makefile i inne dane potrzebne do kompilacji API
  • config.py.in - dodatkowy plik konfiguracyjny (zazwyczaj pusty?)

Tworzenie API dla QTermWidget

Dla przykładu (a zarazem z powodu że to fajny widżet) wybrałem QTermWidget. Po rozpakowaniu źródeł wystarczy:
qmake
make
make install
Użytkownicy Archlinuxa moga pobrać PKGBUILD z AUR. Z paczki kopiujemy do pustego katalogu plik qtermwidget.h (z katalogu lib). Najważniejszy element tego pliku wygląda tak:
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;
};
Dla tej klasy musimy stworzyć plik qtermwidget.sip określający jakie funkcje, klasy, metody mają być dostępne w API. W uproszczeniu jest to lista takich elementów plus znaczniki SIP. qtermwidget.sip wygląda tak:
%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); 
	
};
Mamy tutaj kilka nowości:
  • %Module QtermWidget 0 - określa nazwę modułu
  • %Import QtCore/QtCoremod.sip
    %Import QtGui/QtGuimod.sip
    dołączenie plików nagłówkowych PyQt4 potrzebnych do opisania widżetu Qt4
  • %TypeHeaderCode
    #include 
    %End
    dołączenie pliku nagłówkowego, w którym znajduje się nasza klasa
Następnie mamy listę publicznych i prywatnych elementów - nazwy skopiowane z kodu klasy. Jest to jeden z prostszych przypadków pliku SIP, lecz w tym przypadku wystarczy.

Kolejny etap to pliki konfiguracyjne. config.py wygląda tak:
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)
Jest to generyczny plik konfiguracyjny i jedyne co będziemy musieli w nim zmieniać w innych widżetach to nazwa "qtermwidget". Należy zwrócić uwagę że "qtermwidgetconfig.py" to nazwa pliku konfiguracyjnego jaki zostanie wygenerowany (a nie config.py). Następnie tworzymy pusty config.py.in.

Mając gotową konfigurację można sfinalizować naszą pracę kompilując API. W tym celu należy sparsować plik SIP poleceniem:
sip -t Qt_4_4_1 -I /usr/share/sip/ -t WS_X11 -c . *sip
Gdzie:
  • Qt_4_4_1: identyfikator instalacji PyQt4 (znajdziemy w /usr/lib/python*/site-packages/PyQt4/pyqtconfig.py)
  • /usr/share/sip/: ścieżka do katalogu SIP, w którym przetrzymywane są pliki sip (w podfolderach, m.in. te z PyQt4)
Kolejne etapy to:
python config.py
make
make install
W ramach testów można uruchomić taki prosty kod aplikacji PyQt4:
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

Kod widżetów

Kod widżetów dla PyQt4 można znaleźć na PyQt4 Extrawidgets.
RkBlog

PyQt, 3 November 2008

Comment article
Comment article RkBlog main page Search RSS Contact