Недавно вышел Qt 5.11 и мне подумалось, что сейчас самое время обновить до него кое-какие мои проектики на Qt 1.0… Ладно, шучу :) На самом деле мне стало интересно, насколько хорошо за все эти годы развития фреймворка Qt нам удавалось сохранять обратную совместимость кода.
Qt гарантирует совместимость на уровне кода и бинарников при обновлении между минорными версиями фреймворка (и мы серьёзно относимся к этому обещанию). Вы не должны переписывать (или даже перекомпилировать) свой код при переходе на другую минорную версию Qt. Однако переходы между мажорными версиями требовали от нас идти на некоторые жертвы ради прогресса. С релиза Qt 1.0 в 1996 году мы ломали совместимость кода четыре раза: в версиях 2.0, 3.0, 4.0 (ох, это было болезненно!) и 5.0.
Мы старались даже в мажорных версиях сломать как можно меньше всего, но всё же это приходилось делать. Отсюда возникает вопрос: насколько сложно портировать приложение, написанное во времена Qt 1.0 до современного Qt 5.11?
Для ответа на этот вопрос я взял пример кода, который поставлялся с документацией на Qt 1.0 и постарался собрать его с помощью Qt 5. Наши публичные архивы содержат изменения начиная с версии 1.41, так что мне пришлось изрядно покопаться в дрейнейшей истории, пройти через логи четырёх разных систем контроля версий… но это я уже отвлекаюсь. Проект, который я планирую собрать, называется «t14» — поскольку это иллюстрация к 14-ой (и последней) главе оригинального руководства.
И вот, что мне пришлось проделать для его сборки.
Импортируем оригинальный проект: 10 files changed, 798 insertions(+)
Чистое произведение искусства: так в 1996-ом году официально рекомендовалось писать программы на Qt.
Переходим на qmake: 3 files changed, 2 insertions(+), 148 deletions(-)
До выхода qmake был tmake. Он был написан на Perl. Базовый синтаксис был похож, только tmake ещё позволял включать прямо в файл проекта код на Perl, чего (к счастью!) уже нельзя сделать в qmake. Кроме того, tmake не был публично описанной частью фреймворка, так что для релизов мы готовили make-файлы. Наличие совершенно разных внутренней и внешней системы сборки кода всегда добавляло в релизы элемент неожиданности.
Исправляем include-файлы: 4 files changed, 8 insertions(+), 8 deletions(-)
В те древние времена, когда писался данный пример, операционная система Windows ограничивала длину имени файла до 8 символов. Мы, конечно, могли ориентироваться на Unix и сделать нормальные имена, но, если код должен был быть портируемым — приходилось использовать «восхитительные» названия файлов типа «qscrbar.h» и «qbttngrp.h».
Добавляем отсутствующие include-файлы: 1 file changed, 3 insertions(+)
Зависимости от неявных инклюдов были и остаются проблемой.
Меняем TRUE/FALSE на true/false: 1 file changed, 13 insertions(+), 13 deletions(-)
Ох уж эти сегодняшние дети. Счастья своего не осознают! Мы были вынуждены писать свой собственный булевый тип данных, поскольку встроенного в язык у нас не было!
Исправляем ссылки на сущности, которые переехали в пространство имён «Qt»: 3 files changed, 15 insertions(+), 15 deletions(-)
«Qt» было добавлено в 1998 году и являлось тогда… классом. Да, классом, поскольку у нас тогда не было пространств имён.
Убираем аргумент «name»: 6 files changed, 26 insertions(+), 26 deletions(-)
Все конструкторы подклассов QObject принимали параметром имя объекта.
API класса QScrollBar изменился: 1 file changed, 5 insertions(+), 5 deletions(-)
Иногда нам приходилось избавляться от старых, плохо спроектированных API. Использование индивидуальных сеттеров — значительно лучшая практика, чем конструктор, принимающий 7 аргументов.
Используем QString вместо const char*: 2 files changed, 2 insertions(+), 2 deletions(-)
QString существует в Qt с 1994 года. Раньше это была 8-битная строка в кодировке Latin1 с автоматическим преобразованием к const char*, так что кое-где API использовало аргументы типа const char*. Поддержка Unicode появилась в Qt 2.0.
warning() теперь называется qWarning(): 1 file changed, 1 insertion(+), 1 deletion(-)
Мы вообще-то избегаем добавлять сущности в глобальное пространство имён. Ну, разве что, если они начинаются на «Q». Эта буква принадлежит нам.
Убираем ненужные вызовы старых методов QApplication: 1 file changed, 2 deletions(-)
Qt сегодня многое делает сам, хорошо и автоматически. А в 1996 году многие дисплеи могли отображать лишь 8 бит на пиксель, так что для показа более 256 цветов нужно было ещё постараться.
Заменяем QAccel на QShortcut: 1 file changed, 4 insertions(+), 3 deletions(-)
QShortcut — одновременно мощнее и проще. А ещё его название — не аббревиатура. QShortcut был добавлено в 2004 году.
Исправляем логику перерисовки: 1 file changed, 7 insertions(+), 7 deletions(-)
В 90-ые мы могли прямо рисовать на виджетах, когда захотим. Сегодня у нас есть буферизация и композиция, так что всё становится и сложнее, и проще одновременно. Мы отправляем запрос на изменение и позже, когда получим сигнал на перерисовку, рисуем. Чуть больше кода, но намного логичнее.
QObject::killTimers() больше не существует: 2 files changed, 3 insertions(+), 2 deletions(-)
Эта функция была слишком опасной. Она убивала все таймеры объекта, включая и использовавшиеся внутри Qt. Сегодня вы должны убивать таймеры осознанно и индивидуально.
QWMatrix теперь называется QMatrix: 1 file changed, 2 insertions(+), 2 deletions(-)
Просто изменение названия класса.
Метод QWidget::setBackgroundColor() был удалён: 1 file changed, 3 insertions(+), 1 deletion(-)
Цвет фона виджета — уже не столь простая сущность: он инкапсулирован внутри QPalette вместе с другими свойствами. Кроме того, дочерние виджеты теперь по-умолчанию прозрачны. Мы должны рассказать Qt, как именно хотим рисовать фон.
Не получается заполнить pixmap содержимым виджета: 1 file changed, 1 insertion(+), 1 deletion(-)
Я использовал для этого прозрачный pixmap, поскольку теперь Qt их поддерживает.
Рисование прямоугольников изменилось: 1 file changed, 1 insertion(+), 1 deletion(-)
Это, наверное, наиболее кардинальное изменение из всех вышеописанных. Начиная с Qt 4.0 метод QPainter::drawRect() был изменён таким образом, чтобы «штриховочный прямоугольник имел размер rectangle.size() + ширина пера». Таким образом, нам необходимо вычесть ширину пера (в нашем случае это 1) перед передачей прямоугольника в QPainter.
Теперь у нас есть полнофункциональный порт примера из оригинального обучающего материала, вот скриншот:
Ой… Кое-где текст выглядит обрезанным. Выяснилось, что это произошло из-за жестко прописанных в коде размеров и позиций елементов, а размеры шрифтов, оказывается, поменялись с 1996 года. Решением будет использование QLayout, который не был доступен во времена Qt 1.0 (первая версия появилась в Qt 1.1. и была полностью переписана к выходу Qt 2.0).
Используем QLayout вместо жестко прописанных размеров: 4 files changed, 29 insertions(+), 24 deletions(-)
И вот с этим, последним, изменением всё, наконец, выглядит так, как и должно:
В чём же мораль этой истории? У нас получилось (и даже не заняло много времени) портировать код 22-летней давности с Qt 1.0 на Qt 5.11. Это возможно. У меня, наверное, больше времени заняло написание данной статьи, чем правки самого кода. Большинство API всё ещё живы в последних версиях Qt даже спустя столько лет и версий. Некоторые из них остались неизменными, другие были расширены и доработаны, но всё же сохранили узнаваемость. Все изменения были направлены на улучшения читабельности, безопасности, поддерживаемости и производительности приложений на Qt.
Автор: tangro