Qt + Kinetic = Eye-candy in half an hour

    In the process of developing a new version of QutIM, there was a need to rewrite the notification system, since the old one had many shortcomings.

    Not too long ago a topic appeared on Habré , dedicated to the new features that appeared in Qt. In particular, it describes a new framework designed to facilitate the creation of an animated user interface. If the dear reader is not yet up to date, then I advise you to first read this article.

    A short video demonstrating the possibilities:




    Introduction

    In a review article, one worthy framework I found on Qt.Labs, namely Kinetic, was bypassed .
    The authors promise us the following:
    • Animation framework - the ability to create both simple and complex types of animation for any type of widget. Providing ready-made animation, the ability to add and create custom.
    • Declarative creation of an interface - defining an interface by describing its properties, rather than manipulating ready-made templates.
    • Advanced graphics features - adding shadows, transparency, filters, etc.
    It looks delicious, right? Therefore, armed with patience and readiness for the unknown, I wrote a small example. I note that it took much less time than expected.
    Notifications moved smoothly. They themselves always line up so as not to overlap adjacent ones, and in the case when the text does not fit, the window will be automatically scaled.

    What is needed in order to create a library that can display notification data?
    • KineticNotifications - through this class notifications will be sent. The class is responsible for displaying and animating notifications.
    • NotificationWidget - Pamo pop-up notification, is a QTextBrowser with the frame removed and the alpha channel added.
    • Notification Manager - a singleton that stores pointers to all running notifications, manages their placement, and also provides settings.
    Program code

    In order to trigger a notification, a couple of lines are enough:
    1. KineticNotification  * notify  =  new  KineticNotification ( "id" ,  timeout ) ;
    2. notify -> setMessage ( QString :: fromUtf8 ( "Test" ) ,
    3.                    QString :: fromUtf8 ( "Message" ) ,
    4.                    "./images/avatar.jpg"
    5.                    ) ;
    6. connect ( notify ,  SIGNAL ( action2Activated ( ) ) ,  QApplication :: instance ( ) ,  SLOT ( quit ( ) ) ) ;
    7. connect ( notify ,  SIGNAL ( action1Activated ( ) ) ,  SLOT ( nextNotify ( ) ) ) ;
    8. notify -> send ( ) ;

    Each notification is assigned its own identifier, which must be unique. This allows the identifier to get a pointer to an existing notification, in order, for example, to append a line to it.

    This is done in a very simple way:
    1. KineticNotification  * notify  =  NotificationsManager :: self ( ) -> getById ( "id" ) ;
    2. if  ( notify ! = 0 )  {
    3.     notify -> appendMessage ( QString :: fromUtf8 ( "Add a line of text" ) ) ;
    4. }

    It is important to remember that if there is no notification with the requested id, the function will return 0.

    Now let's move on to the most important thing: to describe the operation of the State machine.
    First you need to create all the states of the machine. There will be three of them:
    • show_state - notification shown on screen
    • hide_state - notification is hidden
    • final_state - this position stops the state machine
    Set the sequence of states:

    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 ) ;  // little trick

    The last line allows you to do without the additional state of the machine, if necessary, to change the position on the screen of the previously drawn notification. After that, you need to set the initial position for the notification (behind the right edge of the screen) and the position that the notification will take by going to the show_state state. The position and size of the widget can be set using the setGeometry method, whose argument is QRect, which is nothing more than a rectangle whose coordinate is the top left point, as well as its length and height.
    To find out at which point on the screen you need to create a notification and where to move it later, we need to know the position of the previous widget or, in the absence of such a lower right corner:
    1. QRect  geom  =  QApplication :: desktop ( ) -> availableGeometry ( QCursor :: pos ( ) ) ;

    And as a result, we get the angle of the monitor on which the mouse pointer is located!

    The starting point is received, now you can set the text, change the height of the widget to the optimal one, so that all displayed text and pictures fit into it.
    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 ) ;

    Be sure to set which stage follows which and what condition is necessary for the transition of the machine from one stage to another
    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.  

    But the position and size of notifications periodically has to be changed, for example, if the lower widget has disappeared, then you need to move all the other notifications down so as not to create empty space. The same applies to adding text: sooner or later you will have to resize the widget and, accordingly, move all the others so that none of them overlap the other. In addition, it is very important to know the final position of the notification, since it is necessary for the correct recalculation of the position of the entire chain. Otherwise, the instant position and size of the notification in the process of its movement can be obtained, as a result of which the widgets will take the wrong position.
    To update, just call the function in the singleton, which will recalculate the positions of widgets from the lower right edge of the screen.
    1. NotificationsManager :: self ( ) -> updateGeometry ( ) ;

    It will call in each widget the function of updating the position and size.
    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 ) ;

    Thus, it is enough for any event that can lead to a change in the position or size of the widget to call the update function. Such events include the removal of the widget and the addition of additional rows to it.
    In general, it turned out to be very functional and rather concise.

    Conclusion

    For those who are interested in the implementation of the notification widget, and who are interested in learning more about the library, as well as use it, a link to the sources, the LGPL license. This program was tested on Qt4.6tp1, kinetic brunch 4.5 branches and should work with QAnimationFramework from Qt Solutions.
    Enjoy using

    N.B. Published at the request of one good person from the QutIM development team who does not have an account on the hub, but passionately would like to participate in the community. Here is his mail: sauron@citadelspb.com

    UPD: Greetings Gorthauer87 !

    Also popular now: