Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_metadata' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 419 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_metadata' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 419 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_metadata' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 419 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_metadata' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 419 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_metadata' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 419 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_metadata' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 419 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_metadata' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 419 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_metadata' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 419 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_metadata' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 419 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_metadata' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 419 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_metadata' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 419 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_metadata' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 419 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_metadata' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 419 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_metadata' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 419 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_metadata' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 419 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_metadata' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 419 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_xhtml' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 555 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_xhtml' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 555 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_xhtml' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 555 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_xhtml' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 555 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_xhtml' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 555 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_xhtml' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 555 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_xhtml' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 555 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_xhtml' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 555 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_xhtml' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 555 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_xhtml' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 555 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_xhtml' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 555 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_xhtml' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 555 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_xhtml' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 555 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_xhtml' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 555 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_xhtml' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 555 Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'Doku_Renderer_xhtml' does not have a method 'section_edit' in /customers/oramezo.org/oramezo.org/httpd.www/inc/parserutils.php on line 555

Программирование на Питоне под Qtopia

Введение

Это небольшое введение в программирование на Питоне на Заурусе. Предполагается, что вы уже знакомы с языком Python, имеете представление о Qt и Qtopia, а также владеете навыками работы в командной строке и сможете заставить свой текстовый редактор правильно сохранять код программ на Питоне.

Здесь вы не найдете информацию о том:

  • как программировать на Питоне
  • как программировать с использованием Qt
  • как программировать под Заурус на C++

Эта работа посвящена вопросам написания и адаптации программ на Питоне для Зауруса с использованием библиотек Qt и Qtopia и расширения PyQt для Питона.

Сначала рекомендуется прочитать Jonathan Gardner's PyQt Tutorial (мой русский перевод)

Почему Питон?

Python - интерпретируемый объектно-ориентированный язык, позволяющий писать в высшей степени переносимые программы. Его синтаксис предельно прост, а возможности - просто огромны.

Вместе с кросс-платформенной библиотекой и набором инструментов Qt он позволяет создавать приложения, которые даже не надо перекомпилировать под каждую новую платформу.

Однако, в программировании для Зауруса есть свои нюансы. Прежде всего, желательно использовать не только саму Qt, но и ее вариант, созданный для мобильных устройств, Qtopia.

Требования

Вам потребуется установить Питон на свой Заурус. Также, если вы захотите проверить работоспособность кода на десктопе, вам потребуется также установить на ней Питон (не ниже версии 2.3), а также саму библиотеку Qt и библиотеку-“обертку” PyQt (в версии для Зауруса она поставляется “в комплекте”).

О том, как установить Питон на Заурус см. статью Установка Питона на Заурусе.

Если ваш десктоп работает под управлением операционной системы Linux, Питон скорее всего у вас уже стоит. Проверить это можно набрав в командной строке python (выход - Ctrl-D). Однако, PyQt скорее всего придется устанавливать дополнительно.

Аналогично, Qt может уже стоять (особенно если вы используете KDE или какие-то программы из него).

Для других операционных систем вам скорее всего потребуется установить все три вещи.

Подробности см. на сайте http://www.python.org (найти дистрибутив для вашей ОС можно по адресу http://www.python.org/download/), http://www.trolltech.com/products/qt/index.html, http://www.qtopia.net/modules/developers/

Также вам нужен будет текстовый редактор, поддерживающий UTF-8 и пригодный для написания кода на Питоне. Одной из характерных черт этого языка является чувствительность к лидирующим пробелам в строках – их количество определяет вложенность операторов (служит операторными скобками). Поэтому неправильная работа редактора с пробелами и табуляциями может привести к трудноуловимым ошибкам в программе. Из всех имеющихся для Зауруса редакторов на мой взгляд лучше всего подходит Emacs. Этот редактор имеет замечательный режим для работы с программами на Питоне. Другой альтернативой является Vim.

Hello, Zaurus

Просто Qt

Итак, приступим. Создайте в редакторе файл hello.py со следующим содержанием:

#!/usr/bin/python
import sys
from qt import *
app = QApplication(sys.argv)
label = QLabel(None, 'label')
label.setText("<h1><i>Hello</i>, Qt!</h1>")
label.resize(QSize(300, 200))
app.setMainWidget(label)
label.show()
app.exec_loop()

Прежде, чем запустить эту программу, рассмотрим по порядку каждую строчку.

Первая строка – стандартный заголовок, сообщающий командному интерпретатору где находится интерпретатор для данного языка.

Далее мы инпортируем модуль sys (откуда мы вызываем функцию, возвращающую список аргументов командной строки), в следующей строке мы импортируем главный модуль PyQt – библиотеки-“обертки”, транслирующий наши запросы из Питона в саму Qt. Обратите внимание, что в отличие от C++, мы не импортируем отдельных модулей для каждого используемого виджета, а сразу все.

В четвертой строке мы создаем объект-приложение и передаем ему список параметров командной строки (Qt сама распознает некоторое количество параметров командной строки).

В трех следующих строках мы создаем и настраиваем текстовую метку, которая используется как главный виджет нашего приложения: сначала мы создаем соответствующий объект, потом задаем текст для него (обратите внимание, что для форматирования можно воспользоваться языком HTML – многие виджеты Qt “понимают” его), наконец, задаем размер.

Далее мы делаем метку центральным виджетом нашего приложения, отображаем ее и передаем управление в цикл обработки событий приложения (обратите внимание, что в Питоне слово “exec” является зарезервированным, поэтому в PyQt функция exec() назвается exec_loop()).

Особенности запуска из консоли

Вы можете запустить эту программу из консоли, набрав

python ./hello.py

или же сделайте его исполняемым командой chmod (я предпочитаю второй вариант). После запуска откроется окно нашего приложения, которое будет выглядеть примерно так:

Вы можете перемещать окно по экрану, распахивать его на весь экран, работает также системное меню (вызываемое щелчком на иконке Qt в левом верхнем углу), закрыть окно можно, как обычно, кнопкой с крестиком.

Однако будьте осторожны: если вы переключитесь сейчас на другое приложение (напимер, ткнете пером в окно консоли, видимое из-под окна “Hello, Zaurus”, вы не сможете нормально продолжить работу с нашим приложением: Заурус переключится в режим “Magnified screen”, что обычно в подобной ситуации приводит к отображению только белого экрана.

Но не пугайтесь. Достаточно любым доступным способом “убить” наше приложение:

  • открыть новую консоль нажатием Fn-N и выполнить команду killall python
  • или же запустить “System Info” и послать процессу hello.py сигнал “9: SIGKILL”.

Такое поведение, по-видимому, связано с какой-то ошибкой в реализации консоли или самой Qtopia.

Обратите также внимание на отсутствие иконки для нашей программы в таскбаре – система по-видимому считает нашу программу подпроцессом консоли. Когда мы научимся запускать программы с рабочего стола, все встанет на свои места.

Qtpe -- делаем приложение в стиле Qtopia

Однако, наше приложение пока не очень похоже на окошки Зауруса. Скорее оно выглядит как обычная программа для десктопа. Давайте займемся адаптацией его под Qtopia.

Много для этого делать не придется: вместо QApplication нужно использовать класс QPEApplication (импортируемый из модуля qtpe), кроме того, мы сразу изменим размер окна на 640×480, чтобы оно занимало весь экран (когда мы будем запускать эту программу с рабочего стола, размер ее окна автоматически будет изменяться таким образом, чтобы оно занимало всю площадь экрана, кроме таскбара).

File: helloqtopia.py

#!/usr/bin/python
import sys
from qt import *
from qtpe import *
app = QPEApplication(sys.argv)
label = QLabel(None, 'label')
label.setText('<h1 align="center"><i>Hello</i>, Zaurus!</h1>')
label.resize(QSize(640, 480))
app.setMainWidget(label)
label.show()
app.exec_loop()

Буквы PE в названиях модуля и класс означают “Palmtop Edition” и происходят от первоначального названия Кутопии (также как и название каталога QtPalmtop).

Когда вы запустите эту программу, вы увидите такое окно:

Обратите внимание на отсутствие курсивного выделения слова “Hello”. Если вы уберете заголовочные теги вокруг надписи, то курсив снова “проявится”. Видимо, это связано как-то либо с ошибками, либо с особенностями рендеринга HTML в Кутопии.

Запускаем везде!

Но тепеь при попытке запуска программы не на Заурусе, Питон будет жаловаться, что не может найти модуль qtpe, и не захочет выполнять программу дальше.

Тут нам на помощь придут обработка исключительных ситуаций в Питоне и преимущества динамической типизации (то, что в Питоне переменная сама по себе не связана типами или классами – в каждый момент времени она может принимать значение любого типа).

Более точно, мы будем перехватывать возникающую исключительную ситуацию, когда Питон не находит нужный нам модуль, и подменять один класс другим. Вот, как это делается:

File: helloqt.py

#!/usr/bin/python
# -*- coding: utf-8 -*-
 
import sys
from qt import *
try:                                    # перехватываем ошибку импорта
    from qtpe import *                  # пытаемся импортировать
    isQtopia = True                     # все в порядке
except:
    isQtopia = False                    # ошибка - это не Qtopia
    QPEApplication = QApplication       # это будет один и тот же класс
 
app = QPEApplication(sys.argv)
label = QLabel(None, 'label')
label.setText('<h1 align="center"><i>Hello</i>, Zaurus!</h1>')
label.resize(QSize(640, 480))
app.setMainWidget(label)
label.show()
app.exec_loop()

Комментарий во второй строке обрабатывается самим Питоном (также он используется Emacs'ом) – он указывает кодировку файла. Если ее не указать, то Питон будет ругаться на наши комментарии, написанные по-русски.

Переменная isQtopia может быть использована в дальнейшем, если в других местах программы потребуется узнать, где исполняется программа.

Цифровые часы

Возьмем теперь пример посложнее. Наша следующая программа будет основана на программе dclock.py из демонстрационных примеров PyQt. Если вы не понимаете, что и как делает эта программа, прочтите руководство по Qt. Вот ее адаптированный исходный текст:

File: dclock.py

#!/usr/bin/python
# -*- coding: utf-8 -*-
# A port to PyQt of the dclock example from Qt v2.x.
# Версия, адаптированная для Qtopia
 
import sys, string
from qt import *
try:
    from qtpe import *
    isQtopia = True
except:
    QPEApplication = QApplication
    isQtopia = False
 
class DigitalClock(QLCDNumber):
    def __init__(self, parent=None, name=None):
        QLCDNumber.__init__(self, parent, name)
        self.setCaption('Digital Clock')
        self.showingColon = 0
        if isQtopia:
          black=QColor(0,0,0)
          self.setBackgroundColor(black)
        self.setFrameStyle(QFrame.Panel | QFrame.Raised)
        self.setLineWidth(2)
        self.showTime()
        self.normalTimer = self.startTimer(500)
        self.showDateTimer = -1
 
    def timerEvent(self, e):
        if e.timerId() == self.showDateTimer:
            self.stopDate()
        else:
            if self.showDateTimer == -1:
                self.showTime()
 
    def mousePressEvent(self, e):
        if e.button() == Qt.LeftButton:
            self.showDate()
 
    def showDate(self):
        if self.showDateTimer != -1:
            return
        d = QDate.currentDate()
        self.display('%2d %2d' % (d.month(), d.day()))
        self.showDateTimer = self.startTimer(2000)
 
    def stopDate(self):
        self.killTimer(self.showDateTimer)
        self.showDateTimer = -1
        self.showTime()
 
    def showTime(self):
        self.showingColon = not self.showingColon
        s = list(str(QTime.currentTime().toString())[:5]) #.left(5)
        if not self.showingColon:
            s[2] = ' '
        if s[0] == '0':
            s[0] = ' '
        s = string.join(s,'')
        self.display(s)
 
a = QPEApplication(sys.argv)
clock = DigitalClock()
clock.resize(170,80)
a.setMainWidget(clock)
clock.show()
a.exec_loop()

Обратите внимание, как используется переменная isQtopia в конструкторе класса: на десктопе цвет часов останется по умолчанию (серым), в то время как на Заурусе часы будут на черном фоне.

Делаем ярлык на рабочем столе

Давайте, наконец, создадим иконку на рабочем столе Зауруса, чтобы запускать наши часы оттуда.

Есть по крайней мере два варианта, как это сделать:

  • положить файл dclock.py в каталог /home/QtPalmtop/bin и создать ярлык, указывающий на него, или
  • положить файл в любое место (например, в домашний каталог /home/zaurus), а в каталоге /home/QtPalmtop/bin создать символическую ссылку (“симлинк”) на этот файл.

Я предпочитаю второй вариант, т.к. в этом случае мне не нужны права суперпользователя чтобы отредактировать файл, кроме того это удобнее, если программа состоит из нескольких файлов (модулей), что обычно для Питона. Чтобы не сваливать все их в /home/QtPalmtop/bin или возиться с настройкой переменной PYTHONPATH, я делаю лишь ссылку на главный запускаемый файл.

Давайте так и поступим:

  • скопируйте файл dclock.py в домашний каталог
  • сделайте его исполняемым: $ chmod +x ./dclock.py
  • станьте суперпользователем и перейдите в /home/QtPalmtop/bin:
 $ su
 # cd /home/QtPalmtop/bin
  • создайте симлинк на наш файл:
 /home/QtPalmtop/bin# ln -s /home/zaurus/dclock.py dclock
  • теперь откройте “Tab Settings” на закладке “Settings” рабочего стола
  • выберите в левой части окна подходящую закладку и создайте новый ярлык (кнопка с чистым листочком на панели внизу)
  • введите имя “Digital Clock”, выберите иконку “Clock”, введите имя программы “dclock”
  • сохраните изменения, нажимая “Ok”
  • перейдите на нужную закладку и снимите у созданной иконки флаг “Display with magnified screen” (почему-то все ярлыки создаются с этим флагом)

Voila! Нажимаем на ярлык и через несколько секунд наслаждаемся результатом:

Наше приложение занимает всю полезную площадь экрана, имеет иконку в панели задач, а также правильно масштабируется при повороте экрана.

Щелчок по окну вызывает краткосрочное переключение на отображение даты.

Главное меню

Давайте усовершенствуем наши часы: добавим меню с пунктами “About” и “Exit”. На Заурусе меню может вызываться нажатием кнопки “Menu”.

Для начала создадим новый класс, являющийся наследником QMainWindow. Добавьте следующий фрагмент кода после класса DigitalClock перед главной программой (начинающейся со строки ”a = QPEApplication(sys.argv)”):

class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent, '', 0)
        self.setCaption('Digital Clock')
        self.setCentralWidget(QWidget(self, "qt_central_widget"))
        self.setupMainMenu()
        mainFormLayout = QVBoxLayout(self.centralWidget(), 1, 2, "mainFormLayout")
        self.clock = DigitalClock(self.centralWidget())
        mainFormLayout.addWidget(self.clock)
 
    def setupMainMenu(self):
        self.actionAbout = QAction(self, "actionAbout")
        self.actionAbout.setText("About Digital Clock")
        self.actionAbout.setMenuText("&About")
        self.actionExit = QAction(self, "actionExit")
        self.actionExit.setText("Exit")
        self.actionExit.setMenuText("E&xit")
        self.actionExit.setAccel(Qt.CTRL+Qt.Key_Q)
        if isQtopia:
            self.menubar = QPEMenuBar(self,"menubar")
        else:
            self.menubar = QMenuBar(self,"menubar")
        self.clockMenu = QPopupMenu(self)
        self.actionAbout.addTo(self.clockMenu)
        self.clockMenu.insertSeparator()
        self.actionExit.addTo(self.clockMenu)
        self.menubar.insertItem("&Clock", self.clockMenu, 0)
        self.connect(self.actionAbout, SIGNAL("activated()"),
                     self.showAboutBox)
        self.connect(self.actionExit, SIGNAL("activated()"),
                     self.close)
 
    def showAboutBox(self):
        QMessageBox.about(self, "About",
                          "<h2>Digital Clock</h2>" +
                          "PyQt demo for Qtopia")

Обратите внимание, что мы создаем главное меню как QMenuBar или QPEMenuBar в зависимости от флага isQtopia.

Затем измените конструктор DigitalClock – уберите оттуда вызов showCaption.

Наконец, внесите изменения в главную программу:

a = QPEApplication(sys.argv)
clock = MainWindow()
clock.resize(170,80)
a.setMainWidget(clock)
clock.show()
a.exec_loop()

Вот, как теперь это выглядит:

Диалоговые окна -- кнопки Ok и Cancel

В диалоговых окнах Кутопии нет необходимости вручную создавать кнопки “Ok” и “Cancel” – каждый диалог уже имеет их в заголовке, а также откликается на нажатие соответствующих кнопок.

Я пользуюсь примерно следующим кодом, помещающим кнопки в диалог только в случае необходимости (опять-таки, основываясь на значении флага isQtopia):

if not utils.isQtopia:
    layout10 = QHBoxLayout(None,0,6,"layout10")
    self.btnOk = QPushButton(self,"btnOk")
    self.btnOk.setText("O&K")
    layout10.addWidget(self.btnOk)
    self.btnCancel = QPushButton(self,"btnCancel")
    self.btnCancel.setText("&Cancel")
    layout10.addWidget(self.btnCancel)
    MainDialogLayout.addLayout(layout10)
    self.btnOk.setDefault(True)
    self.connect(self.btnOk,SIGNAL("clicked()"),self.accept)
    self.connect(self.btnCancel,SIGNAL("clicked()"),self.reject)

Этот код выполнятся только когда программа запущена не на Заурусе (точнее не под Кутопией): создаются две кнопки, связываются с соответствующими слотами диалога и пакуются в QHBoxLayout.

Этот код я вставляю в конец конструктора диалога, когда все другие виджеты уже определены и размещены в окне. Таким образом, кнопки “OK” и “Cancel” всегда будут выводиться в самом низу формы, одна возле другой.

Совместимость версий Qt

В настоящий момент на Заурусе используется Qt версии 2.3.2, в то время как в большинстве других ОС уже давно используется Qt версии не ниже 3. Это необходимо учитывать при написании программ. Дело в том, что в версиях до третьей отсутствуют многие классы и методы. Тут можно, опять-таки, в зависимости от того, где выполняется программа, либо использовать имеющиеся классы, либо заменять их собственными реализациями.

Использование Qt Designer'а и утилиты pyuic

Из-за разницы в версиях иногда бывает вручную доводить код, созданный при помощи Qt Designer'а и pyuic. Иногда бывает достаточно выкинуть пару вызовов несущественных методов, либо заменить их на код, работающий в данной версии.

Ссылки

Copyright (C) Дмитрий Бречалов aka D.Mych, 2005