От GTK к PyQt.
Автор : Philippe Fremy
Перевод : Andi Peredri
<>Введение>
Все началось с того, что я захотел написать игру Klotski. Однако в сети я нашел Gnotski и решил провести испытание: сколько уйдет времени на то, чтобы перенести программу с GTK на Qt, и как трудно это сделать? В конечном счете, я нашел это занимательным и решил сделать несколько вариантов программы, чтобы проанализировать каждый инструментарий.
Для ознакомления с этим исследованием, пожалуйста, загрузите исходный код.
Gnotski
Gnotski - это оригинальная игра, которую вы можете найти в Gnome CVS. Это небольшая и простая программа. Она может быть изменена, улучшена и оптимизирована, но не это является целью. Цель состоит в том, чтобы выяснить, насколько инструментарии похожи и насколько они отличаются.
Вот небольшой пример кода, написанный с поочередным использованием Gtk, Qt и PyQt. В нем нет ничего особо интересного, просто я хочу подчеркнуть, насколько Gtk и Qt имеют похожую структуру.
void gui_draw_pixmap(char *target, gint x, gint y){
GdkRectangle area;
int value;
gdk_draw_pixmap(buffer, space->style->black_gc, tiles_pixmap,
get_piece_nr(target,x,y)*TILE_SIZE, 0,
x*TILE_SIZE, y*TILE_SIZE, TILE_SIZE, TILE_SIZE);
if(get_piece_id(target,x,y)==`*`){
if(get_piece_id(orig_map,x,y)==`.`)
value = 20;
else
value = 22;
gdk_draw_pixmap(buffer, space->style->black_gc, tiles_pixmap,
value*TILE_SIZE+10,10,
x*TILE_SIZE+10, y*TILE_SIZE+10,8,8);
}
area.x = x*TILE_SIZE; area.y = y*TILE_SIZE;
area.width = TILE_SIZE; area.height = TILE_SIZE;
gtk_widget_draw (space, &area);
}
|
C-Klotski
Для переноса Gnotski с Gtk/C на Qt/C++ мне потребовалось 3 часа. Это был первый этап: я попробовал максимизировать общий код и структуру. Я не использовал настоящий C++, а лишь обернул C++ вокруг C-функций ( отсюда название C-Klotski ). Перенос оказался чрезвычайно простым. Gtk и Qt имеют похожую структуру виджетов и механизм сигналов и слотов.
void Klotski::gui_draw_pixmap(char *target, int x, int y)
{
int value;
bitBlt( buffer, 0, 0, tiles_pixmap,
get_piece_nr(target,x,y)*TILE_SIZE, 0,
TILE_SIZE, TILE_SIZE, Qt::CopyROP);
if(get_piece_id(target,x,y)==`*`){
if(get_piece_id(orig_map,x,y)==`.`)
value = 20;
else
value = 22;
bitBlt( buffer, 8, 8, tiles_pixmap, value*TILE_SIZE+10,10,
8, 8, Qt::CopyROP);
}
QPainter p( space );
p.drawPixmap( x*TILE_SIZE, y*TILE_SIZE, *buffer );
p.end();
}
|
Cpp-Klotski
Затем я попробовал переписать программу в стиле С++ с максимизацией объема общего кода. Мне пришлось назначить каждую функцию определенному классу. Это было труднее сделать, потому что мне пришлось реструктуризировать программу. Но реструктуризация была единственной трудностью. В результате проделанной работы код оказался более кратким, но все еще имел много общего с Gnotski.
Вот тот же самый фрагмент кода:
void Board::gui_draw_pixmap(int x, int y)
{
int value;
bitBlt( buffer, 0, 0, tiles_pixmap,
map->piece_nr(x,y)*TILE_SIZE, 0,
TILE_SIZE, TILE_SIZE, Qt::CopyROP);
if(map->piece_id(x,y)==`*`){
if(map->is_goal(x,y))
value = 20;
else
value = 22;
bitBlt( buffer, 8, 8, tiles_pixmap, value*TILE_SIZE+10,10,
8, 8, Qt::CopyROP);
}
QPainter p( this );
p.drawPixmap( x*TILE_SIZE, y*TILE_SIZE, *buffer );
p.end();
}
|
PyQt-Klotski
Третьим этапом был перенос кода на PyQt. Я начал изучать Python недавно и нахожу его великолепным языком. Привязки PyQt выглядят превосходно и имеют очень активного разработчика, поэтому свой выбор я остановил на них. Перенос с Cpp-Klotski оказался тривиальным: 90% работы заключалось лишь в смене синтаксиса ( повсеместное добавление `self.`, удаление `;` и т.д. ). Эта работа могла быть автоматизирована с помощью сценария. Для переноса мне потребовалось 2 часа. Я думаю, что если бы пришлось начинать непосредственно с Gnotski, потребовалось бы менее 4-ех часов.
def gui_draw_pixmap(self,x, y) :
bitBlt( self.buffer, 0, 0, self.tiles_pixmap,
self.map.piece_nr(x,y)*TILE_SIZE, 0,
TILE_SIZE, TILE_SIZE, Qt.CopyROP)
if self.map.piece_id(x,y)==`*` :
if self.map.is_goal(x,y) :
value = 20
else:
value = 22
bitBlt( self.buffer, 8, 8, self.tiles_pixmap,
value*TILE_SIZE+10, 10, 8, 8, Qt.CopyROP)
p = QPainter( self )
p.drawPixmap( x*TILE_SIZE, y*TILE_SIZE, self.buffer );
p.end()
|
Система подсчета результатов
Gnotski использует несколько Gnome-функций для обеспечения подсчета результатов. Так как Qt и KDE не имеют ничего подобного, я не мог перенести эту часть программы. Чтобы обеспечить объективность, я удалил из своих расчетов строки, имевшие отношение к подсчету результатов. Вот они:
83:void game_score();
90:void score_cb(GtkWidget *, gpointer);
453: GNOMEUIINFO_MENU_SCORES_ITEM (score_cb, NULL),
482: gnome_score_init(APPNAME);
571: game_score();
617:void score_cb(GtkWidget *widget, gpointer data){
618: gnome_scores_display (_(APPNAME_LONG), APPNAME, current_level, 0);
619:}
620:
621:void game_score(){
622: gint pos;
623: pos = gnome_score_log(moves,current_level,FALSE);
624: gnome_scores_display(_(APPNAME_LONG), APPNAME, current_level, pos);
625:}
|
В итоге это составляет:
Сравнение размеров
Анализируемые файлы я поместил в каталог stat
каждой из программ. Gnotski смешивает в одной и той же структуре определения уровней и меню. Я не смог использовать ту же структуру непосредственно в Qt, потому что она была слишком характерной для Gnome. Поэтому я использовал почти тот же тип структуры, но отделил код меню. Это объясняет наличие отдельной позиции Level+Menu. Также я поместил в отдельный файл объявления ( Declarations ). И, наконец, позиция Total является суммой Declarations, Level+Menu и Pure code.
Вот результаты:
|
|
Files |
Lines |
Characters |
Gnotski |
Declarations |
Gnotski-declaration.h |
91 |
2454 |
Level+Menu |
gnotski-levels.h |
381 |
8978 |
Pure code |
|
568 |
13829 |
Total |
pieces.h
gnotski-without-score.c |
1040 |
25266 |
|
|
Files |
Lines |
Characters |
C-Klotski |
Declarations |
klotski.h |
126 |
3019 |
Level+Menu |
klotski-levels-menu.h |
327 |
6528 |
Pure code |
|
505 |
10453 |
Total |
pieces.h
klotski.h
levels.h
klotski.cpp |
958 |
19990 |
|
|
Files |
Lines |
Characters |
Cpp-Klotski |
Declarations |
klotski.h |
157 |
3136 |
Level+Menu |
levels-menu.h |
319 |
6502 |
Pure code |
|
492 |
9394 |
Total |
pieces.h
klotski.h
levels.h
klotski.cpp |
968 |
19032 |
|
|
Files |
Lines |
Characters |
PyQt-Klotski |
Declarations |
|
0 |
0 |
Level+Menu |
levels-menu.py |
315 |
6270 |
Pure code |
|
391 |
9357 |
Total |
levels.py
klotski.py
pieces.py |
706 |
15633 |
Выводы
Во-первых, размер кода - это еще не все. Чтобы получить верное представление о различиях между языками и инструментариями, пожалуйста, просмотрите исходный код. Программа достаточно коротка и проста, поэтому для ее понимания Вам не понадобится много времени. Единственная сложная функция - это get_piece_nr()
, которая определяет, какое должно быть выведено изображение (pixmap). Вы можете ее проигнорировать, так как для нашего сравнения это не существенно.
Во-вторых, эта программа слишком коротка и проста. Поэтому наши выводы не определяют лучший инструментарий, а лишь являются первым впечатлением от их сравнения.
Menu+Level
Так как я использовал отличную от Gnotski структуру уровней и меню, составляющая Level+Menu несущественна. Это можно объяснить тем, что моя сруктура более эффективна, чем структура в Gnotski. Заметьте, для определения всех уровней и меню в Gnotski используется 380 строк и 8980 символов, тогда как в Cpp-Klotski - около 320 строк и 6500 символов. В PyQt-Klotski для этих же целей используется всего 315 строк и 6270 символов.
Declarations
Gnotski нуждается лишь в нескольких объявлениях, что составляет 91 строку. C++ требует объявления всех классов и методов, в результате это составляет 126 строк для C-Klotski и 157 строк для Cpp-klotksi. Python не использует объявления.
Различия между C-Klotski и Cpp-Klotski не вызывают удивления. Все объекты C-Klotski имеют неограниченный доступ друг к другу. Это не настоящая объектно-ориентированная программа. В Cpp-Klotski я определил, насколько мне это удалось, объекты с методами для доступа к другим объектам. То есть, объекты не могут получить свободный доступ к другим. Это более безопасно, но это также означает, что для связи объектов Вам потребуется больше методов. Это моя личная интерпретация.
Pure code
Результат таков:
- Gnotski содержит 568 строк и 13829 символов.
- C-Klotski содержит 505 строк и 10453 символа.
- Cpp-Klotski содержит 492 строки и 9394 символа.
- PyQt-Klotski содержит 391 строку и 9357 символов.
C-Klotski является более эффективным, чем Gnotski по числу строк и символов. Я считаю, что это благодаря использованию более эффективного инструментария. При создании одной и той же программы с использованием C/Gtk и C++/Qt Вам потребуется меньшее количество строк и символов, чтобы сделать это с помощью C++/Qt.
Я был немного удивлен столь относительно большим различием между C-Klotski и Cpp-Klotski. Это можно объяснить тем, что при более чистом коде Вы можете делать то же самое проще.
Без сомнения, лидирует PyQt-Klotski. На 100 строк меньше, чем в C-Klotski или Cpp-Klotski. Но почти то же самое число символов, что и в Cpp-Klotski. Я думаю, что это из-за необходимости использования `self.` для доступа к членам класса, что немного неудобно.
Total
В итоге Gnotski - самая большая программа: она насчитывает 1040 строк и 25000 символов. Это обусловлено используемым в ней языком. C++ и Qt способствуют уменьшению объема вводимого кода, в то время как Gtk/Gdk увеличивают его из-за своей объектной реализации на не объектно-ориентированном языке:
- Во всех функциях используется префикс gtk_ или gdk_ . Для избежания этого в C++ используется пространство имен классов.
- Для формирования имен функций используется символ подчеркивания. Для этих же целей в Qt применяется слияние слов с прописной буквы.
- Для похожих функций с отличающимися параметрами используются различные имена. C++ лишен этого недостатка и упрощает код благодаря использованию механизма перегрузки имен функций.
- Для определения типов ( typecasting ) используются сложные макросы. В C++ для этого имеется встроенная поддержка.
Cpp-Klotski и C-Klotski почти эквивалентны: по 960 строк и 19000/19900 символов. PyQt-Klotski подтверждает свое превосходство 700 строками и 15600 символами.
Забавный материал
Давайте еще немного проанализируем:
Избавление от gtk_, gdk_ и _ в Gnotski
toolkits-comparison > cat gnotski/*.h gnotski/*.c | wc
1054 3011 25696
> cat gnotski/*.h gnotski/*.c | sed -e `s/gtk_//g` -e `s/gdk_//g` -e `s/_//g` | wc
1054 3011 24645
|
Меньше лишь на 1000 символов. Я разочарован, поскольку ожидал большего. По-видимому, одного лишь сокращения имен функций не достаточно для того, чтобы приблизить размер Gtk-программ к размеру Qt-программ.
Избавление от self в PyQt-Klotski
toolkits-comparison > cat pyqt-klotski/*.py | wc
706 2156 15633
toolkits-comparison > cat pyqt-klotski/*.py | sed -e `s/self//g` | wc
706 2151 14737
|
Снова меньше лишь на 1000 символов. Мои ожидания опять не оправдались. Но на самом деле это не так уж мало, поскольку составляет 1/15 программы. Я добавлю алиас для `self` в моем редакторе!
Другие примеры
Klotski - не единственный пример, подтверждающий эти выводы.
KVim
Я занимаюсь переносом gvim в KDE. В основном я лишь копирую код из Gtk и вставляю его в KDE/Qt. Почти всегда он становится проще и короче.
VeePee: Поддержка Python в приложениях GNOME и KDE
В настоящий момент VeePee является набором компонентов, позволяющих разработчикам легко добавлять в приложения GNOME и KDE возможности создания сценариев.
VeePee содержит эквивалентный код для Gnome и KDE. Ответ автора из FAQ :
Разве не тяжело разрабатывать пользовательский графический интерфейс одновременно для нескольких инструментариев?
В действительности все инструментарии GUI обеспечивают похожий набор возможностей, однако реализуют их каждый по-своему. В настоящей версии VeePee 74% кода не зависит от GUI, 15% кода специфичны для Gnome и 11% - для KDE.
Таким образом, Gnome-код на 33% больше эквивалентного KDE-кода.
Основной вывод
В ходе этого анализа Вы не должны упустить основной вывод:
Перенос программы с Gtk на Qt тривиален.
Вам необходимо лишь обернуть C-функции классами и методами C++, а также заменить Gtk-код на эквивалентный Qt-код. Этот простой процесс я выполнил несколько раз, например, для kvim.
Личная интерпретация
Инструментарий - это набор виджетов, который обрабатывает асинхронные события. Поэтому, по сути, он объектно-ориентирован. Gtk использует для этого C. Я думаю, что хорошим выбором является использование в Qt такого объектно-ориентированного языка как C++, более подходящего для подобных целей.
Одним из преимуществ Qt является более краткий синтаксис. Для Gtk приходится вводить на 30% больше символов, чем для Qt/C++. Это может занять довольно большой отрезок времени.
Теперь перейдем к PyQt. Python - отличный язык, очень простой и мощный одновременно. Переход от Qt/C++ к PyQt достаточно прост, поскольку все имена сохраняются. Он может быть осуществлен даже Python-сценарием. К тому же Вы можете использовать всю обширную документацию Qt.
Личные рекомендации
Если Вы начинаете разработку нового приложения, пожалуйста, учтите следующие рекомендации:
Если Вы желаете использовать чистый C, используйте Gtk. Он был сделан именно для Вас. Но разве Gtk - это настоящий C ? Это C с конструкторами, деструкторами, объектами, наследованием и т.д. Это C с объектной реализацией. Вы не думаете, что истинный объектно-ориентированный язык мог бы упростить разработку и отладку вашей программы?
Если Вы согласны использовать C++, я настоятельно рекомендую Qt. Доступна обширная превосходная документация, а Ваша программа будет короче. C++ является родным языком для Qt, поэтому отсутствуют задержки в выходе новых версий и проблемы, связанные с обертками ( wrappers ). Один из бывших разработчиков Gtk-- ( привязок C++ для Gtk ) Гуиллаум Лаурент ( Guillaume Laurent ) сейчас с успехом использует Qt/KDE. И напоследок, Qt более развит, чем Gtk. Qt уже не один год обладает теми возможностями, которые были реализованы в последних версиях Gtk: уникод, двумерная графика ( canvas ), XML, потоки и т.д.
Если для Вашего приложения не критично время его выполнения, я рекомендую PyQt даже больше чем Qt. PyQt очень тесно интегрирован с Qt. К тому же Python - великолепный язык! Вы выиграете в размере и читаемости кода, во времени компиляции ( его нет ) и в эффективности. Поскольку язык изначально очень мощный, Вы можете без проблем использовать более сложные конструкции. И не забывайте, что PyQt можно непосредственно использовать в Windows и Linux.
Обратная связь
Вам понравилось это исследование? Или оно Вам не понравилось? Я допустил какие-либо ошибки? Тогда, пожалуйста, напишите мне .
Я признаю, что предпочитаю использовать Qt, но на протяжении этого исследования я старался оставаться нейтральным, насколько это было возможным, за исключением разделов, помеченных как "Личные".
Вам понравилась игра? Тогда посетите написанные мной настоящие klotski. Они созданы с нуля с использованием QCanvas, обладают множеством возможностей и не имеют почти ничего общего с этими klotski.
Было бы великолепно перенести эту же программу на другие инструментарии или языки. Для более интересного исследования мне действительно хотелось бы иметь, например, PyGtk и GtkAda версии.