11 сентября 2009 в 22:16

Пробуем Qt 4.6: Qt Animations и State Machine

Qt*
image
На днях вышло так называемое «технологическое превью» (technological preview) Qt 4.6, которое позволяет уже сейчас попробовать новые фичи, которые войдут в релиз 4.6 этого замечательного фреймворка. Перечислять новшества я не буду — они были достаточно хорошо освещены в этом топике, а подробнее остановлюсь на двух из них: State Machine и Qt Animation Framevork.

Итак, что же они из себя представляют?

State Machine — это ничто иное, как конечный автомат: некий объект с заранее заданным конечным числом состояний и переходами между ними.
В Qt 4.6 реализовано именно так: мы можем создать нужное нам число состояний, и для каждого состояния указать, какие свойства имеет каждый объект в данном состоянии, после чего задать переходы между состояниями, указать стартовую точку и запустить автомат на исполнение.

Но для начала нам потребуется сам Qt 4.6. Я расскажу, как собрать его из исходников для Linux (применимо для любого дистрибутива). Сборка под Windows, думаю, мало отличается, если предварительно установить MinGW. Возможно, собрать можно и с помощью MSVS, но сам я не пробовал.

Сборка Qt 4.6


Я расскажу о своём способе сборки Qt 4.6 без какого-либо «негативного влияния» на текущую версию, установленную из репозитория. Возможно, мой способ не самый оптимальный, но по крайней мере он работает :)
Сразу предупреждаю: для сборки потребуется ~3 ГБ места на диске, а потом ещё ~900 МБ для установки (всё, что останется от сборки, потом можно будет удалить). Занимает так много, потому что во-первых содержит дебаг-информацию (т.к. это ещё не релиз), плюс это не просто библиотеки, но также примеры, документация и т.д.
Для начала скачиваем исходный код отсюда (tar.gz) или отсюда (zip). Распаковываем, переходим в каталог с исходниками, запускаем конфигурирование
$ ./configure --prefix=/opt/Qt4.6

Указывая подобный префикс, мы убиваем двух зайцев: 1) никак не влияем на уже установленную версию; и 2) можем впоследствии удалить всё «лёгким движением руки».
В процессе конфигурирования зададут вопрос о лицензии: отвечаем «о» (open source версия, включающая GPL и LGPL), соглашаемся с ней (говорим «yes»), ждём несколько минут. После окончания нам ещё раз напомнят, с каким префиксом было произведено конфигурирование.
Ну и, наконец, сборка! Запускаем
$ make

И ждём. Ждём. Ждём…
Пока идёт сборка, можно не просто сходить заварить себе чаю/кофе, но и заняться уборкой в комнате: на моём четырёхъядернике сборка в 4 потока заняла 40 минут. Количество потоков (по числу ядер процессора) можно указать при помощи опции "-j":
$ make -j 4

После окончания сборки устанавливаем с помощью
$ sudo make install

В общем случае такая установка крайне нежелательна, но в данном конкретном случае мы можем себе это позволить, т.к. обезопасили себя указанием нужного префикса, и стройность нашей системы от этого не особо пострадает.

Настройка


Теперь нам нужно получить возможность использовать новую версию вместо той, что установлена в системе. Всё, что нам для этого нужно — это использовать правильный qmake, а он уже сам подхватит нужные инклюды и библиотеки и сгенерирует Makefile с их использованием.
Тут у нас есть 3 различных варианта:
  1. каждый раз вызывать qmake с полным указанием пути:
    $ /opt/Qt4.6/bin/qmake

  2. установить путь для qmake на время терминального сеанса:
    $ export PATH=/opt/Qt4.6/bin:$PATH

  3. прописать строку выше в ~/.bashrc, и тогда этот qmake будет использоваться всегда

Я использую второй вариант — для экспериментов это более, чем достаточно.

Пробуем


Для проверки того, что мы всё сделали правильно, скомпилируем небольшую программку:
$ mkdir qt_anim
$ cd qt_anim
$ (vim|emacs|nano|kate|gedit|juffed|...) anim.cpp

anim.cpp:
Copy Source | Copy HTML<br/><br/>#include <QApplication><br/>#include <QWidget><br/> <br/>int main(int argc, char* argv[]) {<br/>    QApplication app(argc, argv);<br/>    QWidget w;<br/>    w.resize(200, 100);<br/>    w.show();<br/>    return app.exec();<br/>}<br/> <br/>

$ qmake -project
$ qmake
$ make
$ ldd qt_anim | grep Qt


После этого нам должны отрапортовать, что используются те библиотеки, на которые мы рассчитываем. Если нет — убедитесь, что используется нужная версия qmake:
$ which qmake

и если версия не та — проверьте, что вы не ошиблись с настройкой PATH.

Начинаем!


Для начала поймём, что такое State Machine и с чем её едят. Ставим задачу: создать окно, в углу которого будет отображаться маленькая картинка, которая при клике по окну будет перемещаться и увеличиваться в размерах.

anim.cpp:
Copy Source | Copy HTML<br/>#include <QApplication><br/>#include "AnimWidget.h" <br/> <br/>int main(int argc, char* argv[]) {<br/>    QApplication app(argc, argv);<br/>    AnimWidget w;<br/>    w.resize(300, 300);<br/>    w.show();<br/>    return app.exec();<br/>}<br/> <br/> <br/>


AnimWidget.h:
Copy Source | Copy HTML<br/>#ifndef __ANIM_WIDGET_H__<br/>#define __ANIM_WIDGET_H__<br/> <br/>#include <QLabel><br/>#include <QStateMachine><br/>#include <QWidget><br/> <br/>class AnimWidget : public QWidget {<br/>Q_OBJECT<br/>public:<br/>    AnimWidget();<br/> <br/>signals:<br/>    /* signal for triggering state machine changes */<br/>    void clicked();<br/> <br/>protected:<br/>    /* here we will be catching clicks */<br/>    virtual void mouseReleaseEvent(QMouseEvent*);<br/> <br/>private:<br/>    QStateMachine machine_;<br/>    QLabel* photo_;<br/>};<br/> <br/>#endif // __ANIM_WIDGET_H__<br/> <br/>


AnimWidget.cpp:
Copy Source | Copy HTML<br/>#include "AnimWidget.h"<br/> <br/>#include <QState><br/>#include <QVariant><br/> <br/>AnimWidget::AnimWidget() {<br/>    photo_ = new QLabel("", this);<br/>    photo_->setGeometry( 0,  0, 40, 40);<br/>    photo_->setScaledContents(true);<br/>    photo_->setPixmap(QPixmap("ottawa.png"));<br/> <br/>    /* creating 2 states */<br/>    QState* st1 = new QState();<br/>    QState* st2 = new QState();<br/> <br/>    /* defining photo's properties for each of them */<br/>    st1->assignProperty(photo_, "geometry", QRect( 0,  0, 40, 40));<br/>    st2->assignProperty(photo_, "geometry", QRect(50, 50, 200, 200));<br/> <br/>    /* define transitions between states by clicking on main window*/<br/>    st1->addTransition(this, SIGNAL(clicked()), st2);<br/>    st2->addTransition(this, SIGNAL(clicked()), st1);<br/> <br/>    /* adding states to state machine */<br/>    machine_.addState(st1);<br/>    machine_.addState(st2);<br/>    machine_.setInitialState(st1);<br/> <br/>    /* starting machine */<br/>    machine_.start();<br/>}<br/> <br/>void AnimWidget::mouseReleaseEvent(QMouseEvent*) {<br/>    emit clicked();<br/>}<br/> <br/>


В строках
st1->assignProperty(photo_, "geometry", QRect(0, 0, 40, 40));
st2->assignProperty(photo_, "geometry", QRect(50, 50, 200, 200));

задаются свойства объектов, присущие им в том или ином состоянии. Стандартные Qt-шные классы содержат определённый набор свойств, а для задания свойств для ваших собственных классов используйте Q_PROPERTY (см. документацию).

Строки
st1->addTransition(this, SIGNAL(clicked()), st2);
st2->addTransition(this, SIGNAL(clicked()), st1);

определяют переходы между состояниями и условия для этих переходов. В нашем случае условием перехода будет являться сигнал «clicked()», брошенный главным окном.
Переход между состояниями может быть и безусловный — тогда не указывается объект и сигнал, а просто указывается оконечное состояние (см. документацию).

И не забудьте указать другую картинку вместо «ottawa.png» и положить рядом с исходниками (или же скачайте исходники по ссылке ниже).

Собираем:
$ qmake -project
$ qmake
$ make
$ ./qt_anim


Кликаем — прыгает!
Но прыгает моментально. Да, переход между состояниями машины происходит мгновенно — но это ведь не то, что мы хотим, верно? Давайте добавим немного анимации для перехода между состояниями. Для этого добавим следующий код перед строкой «machine_.start();»:
Copy Source | Copy HTML<br/>...<br/>/* adding animation */<br/>QPropertyAnimation* an1 = new QPropertyAnimation(photo_, "geometry");<br/>machine_.addDefaultAnimation(an1);<br/>...<br/> <br/>

и не забудем добавить
#include <QPropertyAnimation>
в начало файла.

Тут, в принципе, всё понятно: создаём анимацию для изменения атрибута «geometry» объекта «photo_» и добавляем анимацию в машину.

Собираем, запускаем…

Так намного лучше, правда? Картинка не прыгает, а плавно едет на новое место, изменяясь в размерах.

Можно менять различные параметры анимации, в частности, задавать длительность и «кривую ускорения». Можно сделать, чтобы картинка начинала движение быстро, а потом замедлялась; можно и наоборот, чтобы начинала медленно, а потом разгонялась; а можно и так, чтобы начинала медленно, разгонялась, а потом плавно тормозила. Возможных вариантов — несколько десятков (см. документацию). Я выбрал вариант «разгон — торможение» с кубическим изменением скорости. Добавим следующие строки после создания анимации:
Copy Source | Copy HTML<br/>...<br/>/* customizing the animation */<br/>an1->setEasingCurve(QEasingCurve::InOutCubic);<br/>an1->setDuration(700);<br/>... <br/>


Ну вот, теперь вместо скучного движения с постоянной скоростью, картинка двигается плавно и величаво :)

Собственно говоря, на этом моя статья подходит к концу, а дальше дело остаётся только за вашей фантазией. Применяя этот простой механизм «определяем множество состояний — назначаем свойства объектов для каждого состояния — определяем переходы между состояниями — задаём анимации для переходов», можно добиться впечатляющих результатов.

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

Исходники проекта можно взять тут (66 КБ).

Update: добавил ещё один небольшой проект, сделанный на базе первого (368 КБ, в основном за счёт картинок, кода там не намного больше).
@Mezomish
карма
268,7
рейтинг 0,0
Похожие публикации
Самое читаемое Разработка

Комментарии (56)

  • 0
    У меня вопрос — signals: это фишка компилятора или самого Qt. Просто интересно стало.
    • +1
      фишка Qt
    • 0
      Это фишка Qt. Сигналы потом преобразовываются в обычные функции, которые обрабатываются уже мета-объектом.
    • 0
      Это, своего рода, расширение языка от Qt. Для того что бы это работало, необходимо перед c++ компилятором натравить на исходники moc (Meta Object Compiler), являющийся частью Qt.
      • +1
        >Для того что бы это работало, необходимо перед c++ компилятором натравить на исходники moc (Meta Object Compiler), являющийся частью Qt.

        У читателей может сложиться впечатление, что это нужно делать руками ;)

        Все необходимые телодвижения с moc, uic (который генерирует GUI из XML-файлов, созданных в Дизайнере), компиляцией ресурсов и локализаций — всё это делается незаметно для пользователя, с помощью qmake.
        • 0
          Увы, во времена qmake, мне не приходилось еще Qt использовать на уровне c++…

          А вот во времена Qt 1.44 — точно помню, надо было правила в Makefile-ы писать, для преобразования .cpp в moc.cpp.
        • 0
          Ну не обязптельно qmake. На базе cmake`а можно создать qt проект и cmake тоже сам будет moc вызывать, да и прочие сборочные системы поддаются обучению. Но мне cmake милее всех:)
          • 0
            Можно, конечно, и CMake использовать, и другие системы (у меня у самого есть Qt-проект на CMake), но для него всё же приходится совершать дополнительные телодвижения (QT4_WRAP_UI, QT4_AUTOMOC и так далее). А для qmake этого не надо — он сам найдёт среди хедеров и сорцов те, которые нужно «мокнуть» и «мокнет» без дополнительного напоминания.
            В этом и состоит разница между универсальным и специализированным инструментами :)
          • 0
            а я так и не понял в чем cmake может выиграть перед qmake. Ведь с qmake чтобы собрать программу на qt нам надо сделать лишь несколько простых действий.
            qmake
            make

            а для cmake приходится еще CMakeList.txt писать и постоянно дополнять.
            Может вы проясните в чем плюсы одного перед другим?
            • +1
              помимо няшного вывода сборки ^_^
            • 0
              Перед qmake надо сделать qmake -project, а потом руками вычищать то, что не надо было добавлять в проект и постоянно дополнять тем, что надо.

              CMake предоставляет гораздо больше возможностей. От проверки наличия различных библиотек и запроса номера SVN-ревизии до сборки готовых пакетов (с расширением CPack). Причём в зависимости от платформы по одному и тому же конфигу будет собираться deb, rpm или инсталлятор для Windows.
              • 0
                o_O как…
                а вы не в курсе, qtcreator поддерживает автообновление файла CMakeList.txt при обновлении проекта?
                • 0
                  Нет. Только свои .pro.
              • 0
                Да действительно cmake более фичаст. Кроме того qmake -project в чистом виде годится только для простых qt-only программ. А если по человечески делать то разницы нет. Что .pro редактировать, что Cmakelists.txt. Кроме того cmake умеет программы инсталировать, больше генераторов, больше библиотек знает. Ну и универсален конечно.
                • 0
                  Благодарю :) Давно уже хочу осилить cmake, все руки не доходят. Но похоже пора бы уже )
              • 0
                >Перед qmake надо сделать qmake -project, а потом руками вычищать то, что не надо было добавлять в проект и постоянно дополнять тем, что надо.

                Не обязательно. Можно дописывать руками, как и в CMakeLists.txt. Просто .pro, как я уже сказал, сам делает кое-что из того, для чего в CMakeLists.txt нужно специально писать команды (но это издержки разницы подходов «универсальность-заточенность»).

                >CMake предоставляет гораздо больше возможностей. От проверки наличия различных библиотек....

                Открою секрет: qmake тоже умеет проверять библиотеки. Более того: даже умудряется проверять их версии. У меня в рабочем проекте, к примеру, в .pro файле определяется, какая версия EDS стоит, и в зависимости от этого делает те или иные вещи.

                Насчёт остального — а линком не побалуете? А то я для проверки svn-ревизии вызываю и парсю «svn info» %)
                Про пакеты, кстати, тоже очень заинтересовало.
                • +2
                  1) FindSubversion
                  Subversion_WC_INFO( ${PROJECT_ROOT} SVN )
                  ADD_DEFINITIONS( -DREVISION="${SVN_WC_REVISION}" )

                  2) CPack — то ли расширение, то ли отдельная тулза, продолжающая идеи CMake в направлении автоупаковки. Сам только начал разбираться.
                  • 0
                    Класс, спасибо!
                    «О, сколько нам открытий чудных...» :)
      • 0
        Красивое решение, не стали городить миллион строк препроцессорных директив, а просто добавили нужную функциональность через расширение, возьму на заметку.
        • +1
          Некоторые фреймворки вполне успешно реализуют то же самое на С++ вполне элегантно (пример здесь).
  • НЛО прилетело и опубликовало эту надпись здесь
  • +3
    Qt рулез. После того как на нее подсел — видеть ничего не хочу больше :)
    • –2
      Ну почему же, wxWidgets тоже неплох.
      • +4
        Честно, сидел на нем. Но не дотягивает уже к сожалению.
      • +1
        Уй, ну вы что? Это же невозможно сравнивать. wx как был десять лет назад набором плохо слепленных оберток с ограниченной функциональностью, наполненной багами и несовместимостями, так и остался.
      • +4
        Когда я увидел документацию — закричал и выбросил монитор в окно.
        • –4
          Не надо, на мой взгляд, в wxWidgets документация в три с половиной раза лучше, чем у Qt.
          • +2
            Для сравнения, как оформленны описания почти одинаковых по логике объектов в обоих тулкитах:
            qt.nokia.com/doc/4.6-snapshot/qobject.html
            docs.wxwidgets.org/stable/wx_wxobject.html#wxobject

            ИМХО у Qt документация более читабельна. И охватывает различные моменты использования более полно.
            • –2
              Дело вкуса, конечно, но я всё равно считаю, что у wxWidgets доки понятнее устроены.
    • –4
      Только стоит страшных денех :)
      • +3
        С версии Qt 4.5 LGPL / Free версия позволяет разрабатывать коммерческий продукт абсолютно бесплатно. Коммерческая предоставляет лишь поддержку от тролей.

        Устал уже повторять.
        • 0
          А либы для других языков? Например, PyQT тоже стала LGPL?
          • 0
            • 0
              мой комментарий на эту тему habrahabr.ru/blogs/qt_software/69291/#comment_1972369
            • 0
              Отлично! Хорошие новости.
          • +1
            PyQt — самостоятельный проект, разрабатывающийся совсем другой компанией, но Вы говорите так, как будто это именно Qt «стоит страшных денех», а не PyQt.
            А вопросы насчёт стоимости PyQt — к компании Riverbank Computing :) Проскакивала информация, что Нокия начала свой проект PySide (ссылка выше) именно потому, что устала уговаривать RB выпустить PyQt под LGPL.
            • 0
              Меня интересует совокупная стоимость использования технологии. Совсем недавно проскакивали цифры стоимости использования QT для разработки своих программ. Там получалось все очень невесело.
              Если ситуация изменилась в сторону LGPL, то это очень хорошо.
              • 0
                >Меня интересует совокупная стоимость использования технологии. Совсем недавно проскакивали цифры стоимости использования QT для разработки своих программ.

                «Стоимость использования Qt для разработки своих программ» с марта 2009 года равняется 0 (нулю). Цифры, о которых Вы говорите, либо безнадёжно устарели (и учитывают коммерческую лицензию), либо включают в себя не только Qt, но и ещё некоторый набор средств. Почему при этом общая сумма объявляется «стоимостью использования Qt» — для меня загадка.

                Поймите уже: PyQt — это не часть Qt (не «либа для другого языка», как Вы выразились), это другая технология (разработанная другой компанией). Со своей собственной стоимостью, никак не относящейся к «стоимости Qt».
  • +2
    Где же было QPropertyAnimation пол года назад?
    Мне пришлось писать что-то подобное,
    я писал сначала визуальный объект, а потом класс который содержал все Property объекта,
    и функцию какой передавалось две переменных с разными свойствами,
    в результате возвращается новый класс свойств в котором уже обработаны промежуточные значения между теми двумя входными переменными по определенной функции обработки.
    Пол года назад это мне бы намного сэкономило время :)
    Желающие посмотреть на один из объектов на базе моего механизма смотрите тут: www.youtube.com/watch?v=C-0jDSsBoDA&feature=player_embedded#t=40
    • 0
      Думаю, многие пытались сами писать подобные вещи. Я ещё на Qt3 в одном проекте пытался какую-никакую анимацию изображать. Году этак в 2004-м… :)
      • 0
        Да, всем есть что вспомнить :)
        Хорошо что Qt делает создание программ все проще
    • +2
      Пол года назад это было в Solutions, а еще раньше в Labs ;) Иногда полезно туда заглядывать, достаточно интересных проектов и разработок там.
    • –1
      на руки демонстранта страшно смотреть, у него буд-то паралич (
  • 0
    Прикольно!

    Вот только не нужно было мне спать на лекциях по прикладной теории цифровых автоматов: посмотрел исходники Вашего усовершенствованного проекта и запутался в состояниях :(

    Такой вопрос: если сделать в автомате пару тысяч состояний (без анимации, естественно), как это скажется на производительности?

    P.S. Спасибо за статью.
    • 0
      >Такой вопрос: если сделать в автомате пару тысяч состояний (без анимации, естественно), как это скажется на производительности?

      Не знаю, на таких больших объёмах не тестировал.
  • 0
    И еще вопрос: как сделать так, чтобы во второй версии Вашей программы «выезжающая» картинка была поверх «заезжающей»? Добавить к состоянию еще одно свойство?

    О, и еще: для чего может использоваться безусловный переход состояний?
    • 0
      >И еще вопрос: как сделать так, чтобы во второй версии Вашей программы «выезжающая» картинка была поверх «заезжающей»?

      Думаю, нужно каким-то образом расположить выезжающий на самый верх стека виджетов (так называемый z-index им назначается в порядке из создания). Если честно, такой проблемой ни разу не заморачивался.

      >О, и еще: для чего может использоваться безусловный переход состояний?

      Если мы, к примеру, захотим, чтобы картинка сначала выехала, а потом изменила размер, то разносим это по двум разным состояниям, и переход между ними делаем безусловным. Получится так: кликаем — пошёл процесс перемещения, после завершения которого сразу же начнётся процесс изменения размера без повторного клика.
      • 0
        А, я въехал. Надо было мне сразу вспомнить про анимацию во flash. Тут почти прямая аналогия: состояния автомата — это что-то типа ключевых кадров на таймлайне, только круче, потому что переход может происходить по событиям. Невыспавшийся был… не увидел очевидных вещей.

        Спасибо за ответы :)
  • +1
    Замечательная статья.

    Скажите, а в чём преимущество использования QStateMachine перед стандартным connect?
    • 0
      В том что это более логичный и абстрактный способ описания зависимостей переходов интерфейса из одного состояния в другое, при наступлении определенных событий. Избавляет от рутины в виде создания кучи сигналов/слотов и связывания их. Эту задачу на себя берет QStateMachine. А еще лучше посмотреть документацию по нему, там есть примеры различных моделей конечного автомата, которые можно реализовать, и не все тривиальны, но легко укладываются в логику.
      • 0
        Пользовательские сигналы/слоты всё равно вручную писать придётся. А в случае стандартных сигналов/слотов для connect тоже ничего не надо было добавлять.

        Т.е., я почему спросил, потому что я так понимаю, что объём работы и там и там однаков.

        Дело только в том, что конечный автомат идеологически более правильное и красивое решение. А технически и функционально никаких отличий от connect'a я не вижу.
        • 0
          IMHO не совсем одинаковое количество:
        • 0
          Случайно отправилось :(

          Вот сколько бы нужно было писать кода чтобы реализовать следующий автомат без использования данного фреймворка :

          • 0
            > Вот сколько бы нужно было писать кода

            Сколько? :)
        • 0
          >Т.е., я почему спросил, потому что я так понимаю, что объём работы и там и там однаков.

          Не совсем. В случае state machine количество сигналов равно количеству переходов и никак не зависит от количества объектов. В случае ручных коннектов Вам нужно будет добавлять коннекты для каждого нового объекта.
          Или я не до конца понял Вашу мысль?

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.