Ru: Archive: Создание Qt+PySide гибридных приложений

Возможно вы знаете про PySide, python биндинги к Qt. Также существует PyQt библиотека которая предоставляет аналогичную функциональность но к сожалению ограничена GPL или коммерческой версией. PySide распространяется под LGPL лицензией что даёт удобную возможность пользовать в коммерческих приложениях.

Python даёт возможность очень быстрого создания рабочих приложений и PySide как раз предоставляет такую возможность для Qt разработчиков. По моему опыту создание аналогичного функционала возможно в 1.5-2 раза быстрее чем на C++.

Часто возникает необходимость по быстрому присоединить C++ библиотеку (Qt-подобным интерфейсом) к PySide приложению, или же наоборот стоит задача использования Python скриптов в Qt(C++) приложениях. В обоих случаях нужна автоматическая поддержка API классов от Qt и возможность signal-slot соединений между Qt(C++) и Python(PySide) частями приложения. K сожалению данный вопрос практически не освещён и сделать всё и сразу рабочим довольно сложно.

Итак мы будем создавать гибридное приложение состоящее одновременно из C++ и Python частей и использующее Qt как основную GUI библиотеку.

Нам необходимы установленные Qt, Python и PySide.
Создадим следующую структуру каталогов и файлов:

HybridApp/
 |-data/
 |    |-global.h
 |    |-typesystem.xml
 |-hybrid/
 |    |-MainWindow.h
 |    |-MainWindow.cpp
 |    |-hybrid.pro
 |-hybridpy/
 |    |-hybridpy.pro
 |-build.sh
 |-Main.py

Каталог hybrid содержит C++ часть приложения которая будет скомпилирована в dll/dylib/so
Katalog hybridpy содержит обёртку (wrappings) C++ части которая будет проимпортированна в Python часть приложения.
Каталог data содержит описания типов (typesystem) использованных в C++ модуле. Это Xml файл описывающий типы объектов и их особенности при представлении их как Python объектов. Болъше информации о создании typesystems вы можете найти на сайте PySide (http://www.pyside.org/docs/shiboken/contents.html).

В каталоге hybrid создаём C++(Qt) часть приложения. Создадим простейшее QMainWindow приложение:
hybrid/hybrid.pro:

TEMPLATE = app
CONFIG += qt
QT += core gui

UI_DIR = build
RCC_DIR = build
MOC_DIR = build
OBJECTS_DIR = build

HEADERS += MainWindow.h
SOURCES += MainWindow.cpp Main.cpp

hybrid/Main.cpp:

#include <QtGui>
#include "MainWindow.h"
int main(int argc, char ** argv) {
    QApplication app(argc, argv);
    MainWindow window;
    window.resize(1000,700);
    window.show();
    return app.exec();
}

hybrid/MainWindow.h:

#ifndef MainWindow_H
#define MainWindow_H

#include <QMainWindow>
class QPushButton;
class QGraphicsView;
class QGraphicsScene;
class QPlainTextEdit;

class MainWindow : public QMainWindow { Q_OBJECT
public:
    MainWindow(QWidget * parent = 0L);
    virtual ~MainWindow();

signals:
    void runPythonCode(QString);

private slots:
    void runPythonCode();

public:
    QGraphicsView * viewer;
    QGraphicsScene * scene;
    QPlainTextEdit * editor;
    QPushButton * pb_commit;
};
#endif // MainWindow_H

hybrid/MainWindow.cpp:

#include <QtGui>
#include "MainWindow.h"

MainWindow::MainWindow(QWidget * parent):QMainWindow(parent) {
    QSplitter * splitter = new QSplitter;
    setCentralWidget(splitter);
    QWidget * editorContent = new QWidget;
    splitter->addWidget(editorContent);
    QVBoxLayout * layout = new QVBoxLayout;
    editorContent->setLayout(layout);
    editor = new QPlainTextEdit;
    layout->addWidget(editor);
    pb_commit = new QPushButton(tr("Commit"));
    connect(pb_commit, SIGNAL(clicked()),
            this, SLOT(runPythonCode()));
    layout->addWidget(pb_commit);
    scene = new QGraphicsScene(this);
    viewer = new QGraphicsView;
    viewer->setScene(scene);
    splitter->addWidget(viewer);
    splitter->setSizes(QList<int>() << 400 << 600);
}

MainWindow::~MainWindow() {;}
void MainWindow::runPythonCode() { emit runPythonCode(editor->toPlainText()); }

После компиляции у нас приложение с редактором в левой панели и Canvas справа:
image

Можем готовить эту часть для PySide:
1. Изменим pro файл для сборки dll/dylib/so
2. Создадим обёртку для MainWindow класса
3. Пользуем Main.py вместо C main() входной функции

Изменение pro файла довольно простое: меняем TEMPLATE на lib и убираем main.cpp из списка, также поставим TARGET на корень нашего приложения (HybridApp, корень проекта)
hybrid/hybrid.pro:

TEMPLATE = lib
TARGET = ../Hybrid
CONFIG += qt
QT += core gui

UI_DIR = build
RCC_DIR = build
MOC_DIR = build
OBJECTS_DIR = build

HEADERS += MainWindow.h
SOURCES += MainWindow.cpp

Резулататом будет libHybrid.dylib в папке HybridApp (корень проекта)
Теперь создадим обёртку для C++ части приложения. Сборка будет с помощью build.sh скрипта в корне проекта. (Скрипт соберёт всё: C++ часть и обёртку)
build.sh:

#!/bin/sh

cd hybrid
qmake
make
cd ..

cd hybridpy

QTGUI_INC=/Library/Frameworks/QtGui.framework/Versions/4/Headers
QTCORE_INC=/Library/Frameworks/QtCore.framework/Versions/4/Headers
QTTYPESYSTEM=/usr/local/share/PySide/typesystems

generatorrunner --generatorSet=shiboken \
    ../data/global.h \
    --include-paths=../hybrid:$QTCORE_INC:$QTGUI_INC:/usr/include \
    --typesystem-paths=../data:$QTTYPESYSTEM \
    --output-directory=. \
    ../data/typesystem.xml

qmake
make
cd ..

rm -rf PyHybrid.so
ln -s libPyHybrid.dylib PyHybrid.so

Всё создание обёртки это один вызов generatorrunner с путём к Qt headers, Qt typesystems и наш typesystem(описание типов). Затем в папке hybridpy вызов qmake и сборка python модуля.
data/typesystem.xml:


<typesystem package="PyHybrid">
    <load-typesystem name="typesystem_core.xml" generate="no"/>
    <load-typesystem name="typesystem_gui.xml" generate="no"/>
    <object-type name="MainWindow"/>
</typesystem>

data/global.h:

#undef QT_NO_STL
#undef QT_NO_STL_WCHAR

#ifndef NULL
#define NULL    0
#endif

#include <MainWindow.h>

И pro файл для сборки python модуля:
hybridpy/hybridpy.pro:

TEMPLATE = lib
QT += core gui

INCLUDEPATH += hybrid
INCLUDEPATH += ../hybrid

INCLUDEPATH += /usr/include/python2.6
INCLUDEPATH += /usr/local/include/shiboken
INCLUDEPATH += /usr/local/include/PySide
INCLUDEPATH += /usr/local/include/PySide/QtCore
INCLUDEPATH += /usr/local/include/PySide/QtGui

LIBS += -ldl -lpython2.6
LIBS += -lpyside
LIBS += -lshiboken
LIBS += -L.. -lHybrid

TARGET = ../PyHybrid

SOURCES += \
    pyhybrid/pyhybrid_module_wrapper.cpp \
    pyhybrid/mainwindow_wrapper.cpp \

Возможно для данной сборки лучше использовать cmake с которым можно автоматически найти пути к shiboken итд, но для иллюстрации какие файлы пользуются был выбран qmake проект.

Выглядит почти готовым – после запуска build.sh у нас есть python модуль с C++ частью приложения. Момент чтобы соединить обе части в Main.py:
Main.py:

import sys
from PySide.QtCore import *
from PySide.QtGui import *
from PyHybrid import *

class RunScript(QObject):
    def __init__(self, mainWindow):
        QObject.__init__(self)
        self.mainWindow = mainWindow

    def runScript(self, script):
        mainWindow = self.mainWindow
        exec(str(script))

a = QApplication(sys.argv)
w = MainWindow()
r = RunScript(w)
w.setWindowTitle('PyHybrid')
w.resize(1000,800)
w.show()
a.connect(w, SIGNAL('runPythonCode(QString)'), r.runScript)
a.connect(a, SIGNAL('lastWindowClosed()'), a, SLOT('quit()') )
a.exec_()

После запуска этого python скрипта мы получим тоже самое окно нашего C++ приложения, но с некоторым отличием – теперь python код введённый в редакторе можно выполнить прямо в самом приложении с доступом к C++ объектам приложения.

Попробуем небольшой код:mainWindow.statusBar().show()

и у нашего приложения появился status bar.
Поменяем фон Cаnvas?

mainWindow.scene.setBackgroundBrush(QColor('#e0e0ff'))

Можем создавать объекты:

li1 = QGraphicsLineItem(10,10, 500,500)
li1.setPen(QPen(QBrush(QColor("#ff0000")), 3.0, Qt.DashLine))
mainWindow.scene.addItem(li1)

Далее, мы можем создавать новые классы прямо во время выполнения приложения, а почему бы и нет?

mainWindow.viewer.setRenderHint(QPainter.Antialiasing)
class MyItem(QGraphicsItem):
	def boundingRect(self):
		return QRectF(-100,-100,200,200)

	def paint(self, painter, option, widget):
		g = QLinearGradient(-100,-100, 100,100)
		g.setColorAt(0, QColor('#00ff00'))
		g.setColorAt(1, QColor('#ffffff'))
		painter.setBrush(g)
		p = QPen(QBrush(QColor("#ff0000")), 4, Qt.DashLine)
		painter.setPen(p)
		painter.drawRoundedRect(-100,-100,200,200, 30,30)

my1 = MyItem()
mainWindow.scene.addItem(my1)
my1.setPos(200,200)

image

Наше небольшое приложение получило небольшую уникальную возможность: мы можем его программировать прямо во время выполнения, и это программирование не ограничивается существующим API, но мы можем создавать свои классы на Python передавать их в C++ часть где они имея общий, например QOBject/QWidget, интерфейс, могут обрабатываться и использоваться.

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

Что касается приведённого выше приложения, то оно демонстрирует немного другой подход к программированию когда приложение кодится и модифицируется прямо во время выполнения. Конечно нужно сделать автоматическое сохранение всех выполненных фрагментов в файлы или весь код пропадёт после остановки приложения.

This entry was posted in C++, Embedding, Hybrids, PySide, Ru, Tutorial. Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>