От GTK к PyQt

From Gtk to 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:}

В итоге это составляет:

Lines Characters
14 435

Сравнение размеров

Анализируемые файлы я поместил в каталог 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

Результат таков:

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 увеличивают его из-за своей объектной реализации на не объектно-ориентированном языке:

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.

Обратная связь

Вам понравилось это исследование? Или оно Вам не понравилось? Я допустил какие-либо ошибки? Тогда, пожалуйста, напишите мне [email protected].

Я признаю, что предпочитаю использовать Qt, но на протяжении этого исследования я старался оставаться нейтральным, насколько это было возможным, за исключением разделов, помеченных как "Личные".

Вам понравилась игра? Тогда посетите написанные мной настоящие klotski. Они созданы с нуля с использованием QCanvas, обладают множеством возможностей и не имеют почти ничего общего с этими klotski.

Было бы великолепно перенести эту же программу на другие инструментарии или языки. Для более интересного исследования мне действительно хотелось бы иметь, например, PyGtk и GtkAda версии.