Qt + Kinetic = Eye-candy за полчаса

    В процессе разработки новой версии QutIMа возникла потребность переписать систему уведомлений, так как старая имела множество недостатков.

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

    Небольшое видео с демонстрацией возможностей:




    Вступление

    В обзорной статье был обойден стороной один достойный фреймворк, найденный мной на просторах Qt.Labs, а именно Kinetic.
    Авторы обещают нам следующее:
    • Анимационный фреймворк — возможность создания как простых, так и сложных видов анимации для любых типов виджетов. Предоставление готовой анимации, возможность добавления и создания пользовательской.
    • Декларативное создание интерфейса — задание интерфейса путем описания его свойств, а не манипуляций с готовыми шаблонами.
    • Продвинутые графические возможности — добавление теней, прозрачности, фильтров, итд.
    Выглядит вкусно, так ведь? Поэтому я, вооружившись терпением и готовностью к неизвестному, написал небольшой пример. Замечу, что времени на это ушло гораздо меньше ожидаемого.
    Перемещения уведомлений получились плавными. Cами они выстраиваются всегда так, чтобы не перекрывать соседние, а в случае, когда текст не влазит, окошко автоматически отмасштабируется.

    Что нужно, для того, чтобы создать библиотеку, которая умеет выводить данные уведомления?
    • KineticNotifications — посредством этого класса и будут отсылаться уведомления. Класс отвечает за отображение и анимацию уведомлений.
    • NotificationWidget — памо всплывающее уведомление, представляет собой QTextBrowser с убраной рамкой и добавленым альфа каналом.
    • Notification Manager — синглтон, который хранит указатели на все запущенные уведомления, управляет их размещением, а также предоставляет настройки.
    Код программы

    Для того, чтобы вызвать уведомление, достаточно пары строк:
    1. KineticNotification *notify = new KineticNotification("id", timeout);
    2. notify->setMessage(QString::fromUtf8("Тест"),
    3.                    QString::fromUtf8("Сообщение"),
    4.                    "./images/avatar.jpg"
    5.                    );
    6. connect(notify, SIGNAL(action2Activated()), QApplication::instance(), SLOT(quit()));
    7. connect(notify, SIGNAL(action1Activated()), SLOT(nextNotify()));
    8. notify->send();

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

    Делается это весьма нехитрым образом:
    1. KineticNotification *notify = NotificationsManager::self()->getById("id");
    2. if ( notify != 0 ) {
    3.     notify->appendMessage(QString::fromUtf8("Добавляем строку текста"));
    4. }

    Важно помнить, что в случае отсутствия уведомления с запрашиваемым id, функция вернёт 0.

    Теперь перейдем к самому главному: к описанию работы State машины.
    Для начала необходимо создать все состояния машины. Всего их будет три:
    • show_state — уведомление показано на экране
    • hide_state — уведомление скрыто
    • final_state — это положение останавливает state машину
    Зададим последовательность состояний:

    1. show_state->addTransition(notification_widget, SIGNAL(action1Activated()), hide_state);
    2. show_state->addTransition(notification_widget, SIGNAL(action2Activated()), hide_state);
    3. hide_state->addTransition(hide_state, SIGNAL(polished()), final_state);
    4. show_state->addTransition(this, SIGNAL(updated()), show_state); //небольшая хитрость

    Последняя строчка позволяет обойтись без дополнительного состояния машины при необходимости изменить положение на экране отрисованного ранее уведомления. После чего нужно задать начальную позицию для уведомления (за правым праем экрана) и позицию, которую уведомление примет, перейдя в состояние show_state. Положение и размер виджета можно задать через метод setGeometry, в качестве аргумента которого выступает QRect, который представляет собой ни что иное, как прямоугольник, у которого задана координата верхней левой точки, а также длинна и высота.
    Чтобы узнать, в какой точке экрана нужно создать уведомление и куда его потом перемещать нам необходимо знать положение предыдущего виджета или, в случае отсутствия такого, нижнего правого угла:
    1. QRect geom = QApplication::desktop()->availableGeometry(QCursor::pos());

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

    Начальная точка получена, теперь можно задать текст, изменить высоту виджета на оптимальную, чтобы в него помещался весь отображаемый текст и картинки.
    1. this->document()->setHtml(data);
    2. this->document()->setTextWidth(NotificationsManager::self()->defaultSize.width());
    3. int width = NotificationsManager::self()->defaultSize.width();
    4. int height = this->document()->size().height();
    5.  
    6. return QSize(width,height);

    Обязательно задаем какая стадия следует за какой и какое условие необходимо для перехода машины из одной стадии в другую
    1. if ( timeOut > 0 ) {
    2.     startTimer(timeOut);
    3.     show_state->addTransition(this, SIGNAL(timeoutReached()), hide_state);
    4. }
    5.  
    6. machine.addState(show_state);
    7. machine.addState(hide_state);
    8. machine.addState(final_state);
    9. machine.setInitialState (show_state);
    10.  

    Но положение и размер уведомлений периодически приходится менять, например если нижний виджет скрылся, то необходимо сдвинуть все остальные уведомления вниз, дабы не создавать пустое пространство. То-же касается добавления текста: рано или поздно придется изменить размер виджета и, соответственно, сдвинуть все остальные, чтобы ни один из них не перекрывал другой. Помимо этого очень важно знать конечное положение уведомления, так как оно нужно для корректного пересчёта положения всей цепочки. В противном случае могут быть получены мгновенные положение и размер уведомления в процессе его движения, в результате чего виджеты примут неправильное положение.
    Для обновления достаточно вызвать в синглтоне функцию, которая по новой пересчитает позиции виджетов от нижнего правого края экрана.
    1. NotificationsManager::self()->updateGeometry();

    Вызовет в каждом из виджетов функцию обновления положения и размера.
    1. show_state->assignProperty(notification_widget, "geometry", geom);
    2. updateGeometry(geom);
    3. geom.moveRight(geom.right() + notification_widget->width() + NotificationsManager::self()->margin);
    4. hide_state->assignProperty(notification_widget, "geometry", geom);

    Таким образом, достаточно при любом событии, которое может привести к изменению положения или размера виджета вызвать функцию обновления. К таким событиям относится удаление виджета и добавление в него дополнительных строк.
    В целом получилось весьма функционально и достаточно лаконично.

    Заключение

    Для тех, кому интересна реализация виджета уведомлений, и кому интересно подробнее изучить работу библиотеки, а также воспользоваться ей, ссылка на исходники, лицензия LGPL. Данная программа тестировалась на Qt4.6tp1, kinetic бранче 4.5 ветки и должна работать с QAnimationFramework из Qt Solutions.
    Приятного использования

    N.B. Опубликовано по просьбе одного хорошего человека из команды разработчиков QutIM, который не имеет аккаунта на хабре, но страстно хотел бы участвовать в жизни сообщества. Вот его почта: sauron@citadelspb.com

    UPD: Приветствуем Gorthauer87!
    Поделиться публикацией

    Похожие публикации

    Комментарии 41

      +1
      Очень нравится qutIM. Уведомления и правда были не очень. Новые на порядок выше чем были
      P.S. Блин, в какой раз насмотрюсь видюх с KDE4, думаю — красиво. Запускаю LiveCD Kubuntu и повозившись пол-часика почему-то возвращаюсь на GNOME…
      P.P.S. Успехов в разработке ;)
        0
        Спасибо, передам. А эпическое возвращение в гном происходить наверняка из-за перегруженности эффектами и некотрой глючности;)
          0
          Дык kwin по сравнению с компизом весьма аскетичен.
          0
          kde 4.3.1? Странно, вроде ничего так уже.
          0
          планируется ли в qutim поддержа протокола Yahoo?
            0
            Я сам не совсем в команде, но, насколько я понимаю, сами разработчики кутима занимаются только ядром программы. За протоколы и прочие примочки ответственно комьюнити, которое в последнее время весьма вялое. Так что однозначно ответить сложно: если найдется человек, который напишет плагин — поддержка будет, нет — значит не судьба.
              –1
              Ну скажем так, команда разработчиков делает те плагины, которые им интересны, никакой работы на заказ не ведётся забесплатно, да и помимо Яхи, которой никто из разрабов не пользуется, есть ещё тьма более высокоприоритетных проблем. Вы же хотите метаконтакты, индивидуальные настройки для каждого контакта, лучшую производительность, более логичный внешний вид и так далее?
              Ну вот все эти вопросы куда более приоритетны, нежели Yahoo. Поэтому помогайте, ежели чего-то хотите.
              Это opensource, здесь вам никто ничего не должен
            +1
            Может перенесёте в Qt Software
              +1
              На самом деле суть хорошего интерфейса не в дикой перегруженности эффектами, прыгающими зайчиками, жесткими тенями… Суть в его простоте. Например, тени с альфа 5% смотрятся гораздо приятнее теней с альфа 50-70%… перебор темных оттенков добавляет усталости, нагромождение элементов управления тормозит визуальный поиск информации… Но, конечно, это мое имхо… Кто не согласен, можно подискутировать )
                0
                Анимация там полностью настраиваемая, вплоть до отключения, прозрачность точно также, можно даже флаги окна будет через конфиг настраивать, все инструменты для этого созданы. А уж для демонстрации возможностей можно и включить эффекты посильнее, дабы люди заинтерисовались
                  0
                  Просто жалко что это воспринимается часто как призыв к действию… (
                +1
                Инвайтик я отправил :)
                  0
                  Огромное спасибо…
                    0
                    Пожалуйста :)
                  +1
                  было б лучше иметь нативные уведомления для каждой ДЕ. через плагины например.
                    0
                    Дык началось всё с того, что я написал нативные уведомления для KDE, посмотрите пакет kde-integration, потом товарищ ZloeSabo написал на основе него плагин для Маковского Growlа, ну а затем я накатал плагин для Notify-OSD, но так как я всё же в основном KDE использую, поэтому оставил поддержку плагина на силы сообщества, но никто за него так и не взялся, ежели есть навык, то можете сами его допилить
                    ссылка
                    А дефолтовый плагин уведомлений как раз и должен обладать тем свойством, что быть столь же платформонезависимым, что и Qt.
                      0
                      ежели кому интересно, я пару дней назад модифицировал и пересобрал библиотеку на Notify-OSD. теперь она не требует libnotifymm, но простой libnotify.

                      Возможно, реализация крива, но если кого заинтересует — с радостью поделюсь. Обращайтесь.
                        0
                        Насколько я помню с notify-osd можно общаться через d-bus. Вообще идеально было бы сделать такую вещь: написать на основе моих уведомлений демона, который бы в плане d-bus интерфейса совместим с libnotify. Получился бы такой простенький кроссплатформенный демон уведомлений.
                          0
                          а разве, если сделать привязку к d-bus, оно будет работать и под виндой? ведь все-таки проект кросс-платформенный, в чем вся его прелесть. :)
                            0
                            Да я и не буду делать именно так :) Хотя дбас в винде есть, но кривоватый
                          0
                          А вообще ежели есть желание улучшить мой старый плагин к Кутиму для notify-osd то все только рады будут
                            0
                            Есть не только желание, но и определенный прогресс… Только вот никак не соображу, куда его сунуть. Прогресс этот. На форум в тему? Или на гугло-коде положить?..
                              0
                              На форум, мне кажется, нормально бы было.
                                0
                                Ежели захотите продолжить/поддержать, то оно тут:
                                svn checkout http://qutim-libnotify.googlecode.com/svn/trunk/ qutim-libnotify
                                0
                                Ну сам код можно и на гуглокод, а анонс можно на форуме опубликовать в соответствующем разделе
                                qutim.org/forum/viewforum.php?f=62
                        0
                        Кстати кто хочет попробовать быстренько собрать и потестить уведомления и имеет установленную Убунту, может слить с launchpadа пакеты с Qt4.6, живут они в kubuntu-experimental кажется
                          +1
                          Еще бы жрал памяти кутим поменьше, а то 30 метров на только что загруженном это перебор.
                            0
                            Думаю в версии 0.2 уже сделать ничего невозможно. А в дальнейшем конечно будем оптимизировать, ибо хочется чтобы Кутим мог работать на платформах типа Maemo.
                            А сейчас то в наш век 2х гиговых планок что такое 30 метров? Гораздо важнее оптимизация по скорости работы
                              0
                              Потребление памяти оно ведь и на скорость работы влияет, как никак.
                                +1
                                Нет чёткой зависимости.

                                Иногда за счёт большего потребления памяти скорость работы увеличивается (к примеру, можно кэшировать в памяти данные с диска, тем самым заменяя медленные обращения к диску во много раз более быстрыми обращениями к памяти).
                                  0
                                  кэширование — это ещё из разряда полезного и реально необходимого потребления :)
                              0
                              Насколько я помню, это Jabber больше всего жрет.
                              Это у вас под виндой 30 метров?:) Вы под линуксом не видели сколько )
                                0
                                15 мегабайт, запущенный, с несколькими табами беседы. Из протоколов — аська, xmpp.
                              0
                              а извините google talk уже в кутиме работает?
                                0
                                У кого-то работает. У кого-то нет.
                                Причем все как-то неоднозначно :)
                                  0
                                  Причем работает с дефолтовыми настройками, а как народ начинает с ними шаманить всё отваливается
                                    0
                                    У мя с дефолтом не работает :)
                                  0
                                  у меня даже gtalk из google apps на собственном домене работает.
                                  0
                                  Работает, правда некоторые жаловались.
                                    0
                                    Почему бы не показывать в данном случае уведомления между уже стоящими виджетами?
                                    Я имею ввиду между погодой и плеером.
                                      0
                                      Потому что они имею совершенно разную природу и суть :) погода и плеер не имеют никакого отношения к виджетам уведомлений

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

                                    Самое читаемое