Process to eventlogger callbacks in idle to better handle multiple events.
[conv-inbox] / src / el-home-applet.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
2 /*
3  *  Copyright (C) 2009 Artem Garmash. All rights reserved.
4  *
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2 of the License, or
8  *  (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program; if not, write to the Free Software
17  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  *
19  * Contact: Artem Garmash <artemgarmash@gmail.com>
20  *
21  */
22
23 #include "config.h"
24 #include "el-home-applet.h"
25
26 #include <hildon/hildon.h>
27 #include <rtcom-eventlogger/eventlogger.h>
28 #include <sqlite3.h>
29 #include <string.h>
30
31 #define EL_HOME_APPLET_GET_PRIVATE(obj) ( \
32         G_TYPE_INSTANCE_GET_PRIVATE (obj, \
33                 EL_TYPE_HOME_APPLET, ELHomeAppletPrivate))
34
35 #define BOX_WIDTH 352
36 #define BOX_HEIGHT 252
37
38 #define C_WIDTH (BOX_WIDTH - 2*HILDON_MARGIN_HALF)
39 #define C_HEIGHT (BOX_HEIGHT - 2*HILDON_MARGIN_HALF)
40 #define C_X HILDON_MARGIN_HALF
41 #define C_Y HILDON_MARGIN_HALF
42
43 #define HEADER_HEIGHT 48
44 #define MESSAGE_HEIGHT (C_HEIGHT - HEADER_HEIGHT)
45 #define MESSAGE_WIDTH (C_WIDTH - 2*HILDON_MARGIN_DEFAULT)
46
47 #define BOX_RADIOUS 10
48
49 struct _ELHomeAppletPrivate
50 {
51         RTComEl *eventlogger;
52
53         GtkWidget *sender;
54         GtkWidget *message;
55         GtkWidget *icon;
56         GtkWidget *unread;
57         GtkWidget *received;
58
59         gint       event_id;
60
61         gboolean   active;
62
63         guint unread_count;
64
65         const gchar *current_font;
66
67         guint idle_id;
68         /* new or updated event id */
69         gint  new_event_id;
70 };
71
72 HD_DEFINE_PLUGIN_MODULE (ELHomeApplet, el_home_applet, HD_TYPE_HOME_PLUGIN_ITEM);
73
74 const gchar* g_module_check_init(GModule *module);
75 const gchar*
76 g_module_check_init(GModule *module)
77 {
78         g_module_make_resident (module);
79         return NULL;
80 }
81
82 static void
83 el_home_applet_class_finalize (ELHomeAppletClass *klass)
84 {
85 }
86
87 static void
88 el_home_applet_realize (GtkWidget *widget)
89 {
90         GdkScreen *screen;
91
92         screen = gtk_widget_get_screen (widget);
93         gtk_widget_set_colormap (widget,
94                                  gdk_screen_get_rgba_colormap (screen));
95
96         gtk_widget_set_app_paintable (widget,
97                                       TRUE);
98
99         GTK_WIDGET_CLASS (el_home_applet_parent_class)->realize (widget);
100 }
101
102 /*
103  * Thanks http://cairographics.org/cookbook/roundedrectangles/
104  */
105 static void
106 rounded_rectangle (cairo_t *cr,
107                    double   x,
108                    double   y,
109                    double   w,
110                    double   h,
111                    double   r)
112 {
113         cairo_move_to (cr, x + r, y);
114         cairo_line_to (cr, x + w - r, y);
115         cairo_curve_to (cr, x + w, y,
116                         x + w, y,
117                         x + w, y + r);
118         cairo_line_to (cr, x + w, y + h - r);
119         cairo_curve_to (cr, x + w, y + h,
120                         x + w, y + h,
121                         x + w - r, y + h);
122         cairo_line_to (cr, x + r, y + h);
123         cairo_curve_to (cr, x, y + h,
124                         x, y + h,
125                         x, y + h - r);
126         cairo_line_to (cr, x, y + r);
127         cairo_curve_to (cr, x, y,
128                         x, y,
129                         x + r, y);
130 }
131
132 static gboolean
133 expose_event (GtkWidget *self, GdkEventExpose *event)
134 {
135         ELHomeAppletPrivate *priv = EL_HOME_APPLET(self)->priv;
136
137         cairo_t *cr;
138         GdkColor color;
139         float red, green, blue;
140
141         /* find theme active color */
142         gtk_style_lookup_color (self->style, "ActiveTextColor", &color);
143         red = color.red/(float)G_MAXUINT16;
144         green = color.green/(float)G_MAXUINT16;
145         blue = color.blue/(float)G_MAXUINT16;
146
147         cr = gdk_cairo_create (self->window);
148         gdk_cairo_region (cr, event->region);
149         cairo_clip (cr);
150
151         cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
152
153         cairo_set_source_rgba (cr, 0.4f, 0.4f, 0.4f, 0.1f);
154         cairo_set_line_width (cr, 3.0f);
155
156         rounded_rectangle (cr,
157                            C_X,
158                            C_Y,
159                            BOX_WIDTH - 2*C_X,
160                            BOX_HEIGHT - 2*C_Y,
161                            BOX_RADIOUS);
162
163         cairo_close_path(cr);
164         cairo_stroke (cr);
165
166         cairo_set_line_width (cr, 1.0f);
167
168         cairo_translate (cr, C_X, C_Y);
169         cairo_move_to (cr, 0, HEADER_HEIGHT);
170         cairo_line_to (cr, 0, BOX_RADIOUS);
171         cairo_curve_to (cr, 0, 0, 0, 0, BOX_RADIOUS, 0);
172         cairo_line_to (cr, C_WIDTH - BOX_RADIOUS, 0);
173         cairo_curve_to (cr, C_WIDTH, 0, C_WIDTH, 0, C_WIDTH, BOX_RADIOUS);
174         cairo_line_to (cr, C_WIDTH, HEADER_HEIGHT);
175         cairo_line_to (cr, 0, HEADER_HEIGHT);
176
177         cairo_close_path(cr);
178
179         cairo_set_source_rgba (cr, 0.2f, 0.2f, 0.2f, 0.8f);
180         cairo_fill_preserve (cr);
181         cairo_set_source_rgba (cr, red, green, blue, 1.0f);
182         cairo_stroke (cr);
183
184         cairo_move_to (cr, 0, HEADER_HEIGHT);
185         cairo_line_to (cr, 0, C_HEIGHT - BOX_RADIOUS);
186         cairo_curve_to (cr, 0, C_HEIGHT, 0, C_HEIGHT, BOX_RADIOUS, C_HEIGHT);
187         cairo_line_to (cr, C_WIDTH - BOX_RADIOUS, C_HEIGHT);
188         cairo_curve_to (cr, C_WIDTH, C_HEIGHT, C_WIDTH, C_HEIGHT, C_WIDTH, C_HEIGHT - BOX_RADIOUS);
189         cairo_line_to (cr, C_WIDTH, HEADER_HEIGHT);
190         cairo_line_to (cr, 0, HEADER_HEIGHT);
191         cairo_close_path(cr);
192
193         if (priv->active)
194                 cairo_set_source_rgba (cr, red, green, blue, 0.8f);
195         else
196                 cairo_set_source_rgba (cr, 0.4f, 0.4f, 0.4f, 0.8f);
197         cairo_fill (cr);
198
199         /* cairo_set_source_rgba (cr, red, green, blue, 1.0f); */
200         /* cairo_translate (cr, -C_X, -C_Y); */
201         /* rounded_rectangle (cr, */
202         /*                    C_X, */
203         /*                    C_Y, */
204         /*                    BOX_WIDTH - 2*C_X, */
205         /*                    BOX_HEIGHT - 2*C_Y, */
206         /*                    BOX_RADIOUS); */
207         /* cairo_close_path(cr); */
208         /* cairo_stroke (cr); */
209
210         cairo_destroy (cr);
211
212         return GTK_WIDGET_CLASS (el_home_applet_parent_class)->expose_event (self, event);
213 }
214
215 static void
216 dispose (GObject *self)
217 {
218         ELHomeAppletPrivate *priv = EL_HOME_APPLET(self)->priv;
219
220         if (priv->idle_id){
221                 g_source_remove (priv->idle_id);
222                 priv->idle_id = 0;
223         }
224         if (priv->eventlogger){
225                 g_object_unref (priv->eventlogger);
226                 priv->eventlogger = NULL;
227         }
228
229         G_OBJECT_CLASS (el_home_applet_parent_class)->dispose (self);
230 }
231
232 static void
233 finalize (GObject *self)
234 {
235         G_OBJECT_CLASS (el_home_applet_parent_class)->finalize (self);
236 }
237
238 static gchar*
239 format_time (time_t t)
240 {
241         static const guint RESULT_SIZE = 32;
242
243         time_t now;
244         struct tm now_tm, t_tm;
245         const gchar *format = "%Y.%m.%d %T";
246         gchar *result = g_malloc0 (RESULT_SIZE);
247
248         now = time (NULL);
249         localtime_r (&now, &now_tm);
250         localtime_r (&t, &t_tm);
251
252         if ((now_tm.tm_year == t_tm.tm_year) &&
253             (now_tm.tm_mon  == t_tm.tm_mon) &&
254             (now_tm.tm_mday == t_tm.tm_mday))
255                 format = "%T";
256
257         strftime (result, RESULT_SIZE, format, &t_tm);
258
259         return result;
260 }
261
262 static void
263 show_event (ELHomeApplet *self, RTComElIter *it)
264 {
265         ELHomeAppletPrivate *priv = self->priv;
266
267         gchar *message = NULL;
268         gchar *remote = NULL;
269         gchar *received = NULL;
270         const gchar *icon_name = NULL;
271
272         if (it && rtcom_el_iter_first (it)){
273                 rtcom_el_iter_dup_string (it, "free-text", &message);
274                 if (message){
275                         const gchar *service;
276                         time_t received_t;
277
278                         rtcom_el_iter_get_int (it, "id", &priv->event_id);
279                         if (rtcom_el_iter_get_int (it, "start-time", (gint*)&received_t))
280                                 received = format_time (received_t);
281
282                         if(!rtcom_el_iter_dup_string (it, "remote-name", &remote))
283                                 rtcom_el_iter_dup_string (it, "remote-id", &remote);
284                         service = rtcom_el_iter_get_service (it);
285                         if (!g_strcmp0 (service, "RTCOM_EL_SERVICE_SMS"))
286                                 icon_name = "chat_unread_sms";
287                         else if (!g_strcmp0 (service, "RTCOM_EL_SERVICE_CHAT"))
288                                 icon_name = "chat_unread_chat";
289                 }
290         }
291         else{
292                 priv->event_id = -1;
293         }
294
295         gtk_label_set_text (GTK_LABEL (priv->message), message);
296         gtk_label_set_text (GTK_LABEL (priv->sender), remote);
297         gtk_label_set_text (GTK_LABEL (priv->received), received);
298
299         if (icon_name){
300                 const gchar *current_icon_name;
301                 gtk_image_get_icon_name (GTK_IMAGE (priv->icon),
302                                          &current_icon_name,
303                                          NULL);
304                 if (g_strcmp0 (current_icon_name, icon_name))
305                         gtk_image_set_from_icon_name (GTK_IMAGE (priv->icon),
306                                                       icon_name,
307                                                       HILDON_ICON_SIZE_FINGER);
308                 gtk_widget_show (priv->icon);
309         }
310         else
311                 gtk_widget_hide (priv->icon);
312
313         g_free (message);
314         g_free (remote);
315 }
316
317 static RTComElIter*
318 make_query (RTComEl *el, gint event_id)
319 {
320         RTComElQuery *query = NULL;
321         RTComElIter *it = NULL;
322
323         static const gchar *services[] = {"RTCOM_EL_SERVICE_SMS",
324                                           "RTCOM_EL_SERVICE_CHAT",
325                                           NULL};
326         static const gchar *event_types[] = {"RTCOM_EL_EVENTTYPE_SMS_INBOUND",
327                                              "RTCOM_EL_EVENTTYPE_CHAT_INBOUND",
328                                              NULL};
329
330         query = rtcom_el_query_new (el);
331         rtcom_el_query_set_limit (query, 1);
332         if (event_id >= 0){
333                 rtcom_el_query_prepare (query,
334                                         "is-read", FALSE, RTCOM_EL_OP_EQUAL,
335                                         "id", event_id, RTCOM_EL_OP_EQUAL,
336                                         "service", services, RTCOM_EL_OP_IN_STRV,
337                                         "event-type", event_types, RTCOM_EL_OP_IN_STRV,
338                                         NULL);
339         }
340         else{
341                 rtcom_el_query_prepare (query,
342                                         "is-read", FALSE, RTCOM_EL_OP_EQUAL,
343                                         "service", services, RTCOM_EL_OP_IN_STRV,
344                                         "event-type", event_types, RTCOM_EL_OP_IN_STRV,
345                                         NULL);
346         }
347         it = rtcom_el_get_events (el, query);
348         g_object_unref(query);
349
350         return it;
351 }
352
353 static void
354 update_unread_label (ELHomeApplet *self)
355 {
356         ELHomeAppletPrivate *priv = self->priv;
357         gchar *text;
358
359         text = g_strdup_printf ("%d", priv->unread_count);
360         gtk_label_set_text (GTK_LABEL (priv->unread), text);
361         g_free (text);
362 }
363
364 static gint
365 query_unread_events (RTComEl *el)
366 {
367         sqlite3 *db;
368         sqlite3_stmt *stmt;
369         int ret;
370         gint count = 0;
371
372         g_object_get (el, "db", &db, NULL);
373
374         if (sqlite3_prepare_v2 (db,
375                                 "SELECT SUM(total_events)-SUM(read_events) FROM GroupCache;",
376                                 -1,
377                                 &stmt,
378                                 NULL) != SQLITE_OK){
379                 g_error ("%s: can't compile SQL", G_STRFUNC);
380                 return -1;
381         }
382
383         while (SQLITE_BUSY == (ret = sqlite3_step (stmt)));
384
385         if (ret == SQLITE_ROW){
386                 count = sqlite3_column_int (stmt, 0);
387         }
388         else{
389                 g_error ("%s: error while executing SQL", G_STRFUNC);
390         }
391
392         sqlite3_finalize (stmt);
393
394         return count;
395 }
396
397 static void
398 read_event (ELHomeApplet *self)
399 {
400         ELHomeAppletPrivate *priv = self->priv;
401         RTComElIter *it = NULL;
402
403         it = make_query (priv->eventlogger, -1);
404         show_event (self, it);
405         if (it) g_object_unref (it);
406 }
407
408 static void
409 mark_as_read (ELHomeApplet *self)
410 {
411         ELHomeAppletPrivate *priv = self->priv;
412
413         if (priv->event_id >= 0){
414                 rtcom_el_set_read_event (priv->eventlogger,
415                                          priv->event_id,
416                                          TRUE,
417                                          NULL);
418                 read_event (self);
419                 priv->unread_count--;
420                 update_unread_label (self);
421         }
422 }
423
424 static gboolean
425 read_new_event (ELHomeApplet *self)
426 {
427         ELHomeAppletPrivate *priv = self->priv;
428
429         if (priv->new_event_id >= 0){
430                 RTComElIter *it = NULL;
431                 it = make_query (priv->eventlogger, priv->new_event_id);
432                 if (it){
433                         if (rtcom_el_iter_first (it))
434                                 show_event (self, it);
435                         g_object_unref (it);
436                 }
437                 priv->unread_count = query_unread_events (priv->eventlogger);
438                 update_unread_label (self);
439         }
440         priv->new_event_id = -1;
441         priv->idle_id = 0;
442
443         return FALSE;
444 }
445
446 static void
447 add_new_idle (ELHomeApplet *self)
448 {
449         ELHomeAppletPrivate *priv = self->priv;
450
451         if (priv->idle_id)
452                 g_source_remove (priv->idle_id);
453         priv->idle_id = g_idle_add ((GSourceFunc)read_new_event,
454                                     self);
455 }
456
457 static void
458 new_event_cb (RTComEl      *backend,
459               gint          event_id,
460               const gchar  *local_uid,
461               const gchar  *remote_uid,
462               const gchar  *remote_ebook_uid,
463               const gchar  *group_uid,
464               const gchar  *service,
465               ELHomeApplet *self)
466 {
467         ELHomeAppletPrivate *priv = self->priv;
468
469         priv->new_event_id = event_id;
470         add_new_idle (self);
471 }
472
473 static gboolean
474 button_release_event_cb (GtkWidget      *widget,
475                          GdkEventButton *event,
476                          ELHomeApplet   *self)
477 {
478         ELHomeAppletPrivate *priv = self->priv;
479
480         if (priv->active){
481                 priv->active = FALSE;
482                 gtk_widget_queue_draw (widget);
483 #ifndef DEBUG_LAYOUT
484                 mark_as_read (self);
485 #endif
486         }
487
488         return TRUE;
489 }
490
491 static gboolean
492 button_press_event_cb (GtkWidget      *widget,
493                        GdkEventButton *event,
494                        ELHomeApplet   *self)
495 {
496         ELHomeAppletPrivate *priv = self->priv;
497
498         if (priv->event_id > 0){
499                 priv->active = TRUE;
500                 gtk_widget_queue_draw (widget);
501         }
502
503         return TRUE;
504 }
505
506 static gboolean
507 leave_notify_event_cb (GtkWidget        *widget,
508                        GdkEventCrossing *event,
509                        ELHomeApplet     *self)
510 {
511         ELHomeAppletPrivate *priv = self->priv;
512
513         if (priv->active){
514                 priv->active = FALSE;
515                 gtk_widget_queue_draw (widget);
516         }
517
518         return FALSE;
519 }
520
521 static void
522 el_home_applet_init (ELHomeApplet *self)
523 {
524         ELHomeAppletPrivate *priv;
525         GtkWidget *event_box;
526         GtkWidget *hbox, *vbox, *align;
527
528         self->priv = EL_HOME_APPLET_GET_PRIVATE (self);
529         priv = self->priv;
530
531         gtk_widget_set_app_paintable (GTK_WIDGET (self), TRUE);
532
533         priv->unread = gtk_label_new ("12");
534         hildon_helper_set_logical_color (priv->unread,
535                                          GTK_RC_FG,
536                                          GTK_STATE_NORMAL,
537                                          "ActiveTextColor");
538         gtk_misc_set_alignment (GTK_MISC (priv->unread),
539                                 1.0f,
540                                 0.5f);
541         gtk_widget_set_size_request (priv->unread,
542                                      -1,
543                                      HEADER_HEIGHT);
544
545         priv->icon = gtk_image_new_from_icon_name ("chat_unread_sms",
546                                                    HILDON_ICON_SIZE_FINGER);
547         gtk_misc_set_alignment (GTK_MISC (priv->icon),
548                                 0.5f,
549                                 0.5f);
550
551         priv->sender = gtk_label_new ("asdf asdf asdf asdf asdf");
552         gtk_misc_set_alignment (GTK_MISC (priv->sender),
553                                 0.5f,
554                                 0.5f);
555         gtk_label_set_ellipsize (GTK_LABEL (priv->sender),
556                                  PANGO_ELLIPSIZE_END);
557         gtk_widget_set_name (priv->sender, "hildon-shadow-label");
558         hildon_helper_set_logical_font (priv->sender, "SystemFont");
559
560         priv->message = g_object_new (GTK_TYPE_LABEL,
561                                       "label", "asdf asdf adsf asdf asdf asdf asdf asdf",
562                                       "wrap", TRUE,
563                                       "wrap-mode", PANGO_WRAP_WORD_CHAR,
564                                       NULL);
565
566         gtk_misc_set_alignment (GTK_MISC (priv->message),
567                                 0.0f,
568                                 0.0f);
569         gtk_widget_set_size_request (priv->message,
570                                      MESSAGE_WIDTH,
571                                      MESSAGE_HEIGHT);
572         gtk_widget_set_name (priv->message, "hildon-shadow-label");
573
574         priv->received = gtk_label_new ("aewf aewf aewf awef");
575         gtk_misc_set_alignment (GTK_MISC (priv->received),
576                                 1.0f,
577                                 0.5f);
578         gtk_widget_set_size_request (priv->received,
579                                      MESSAGE_WIDTH,
580                                      -1);
581         hildon_helper_set_logical_font (priv->received, "SmallSystemFont");
582         gtk_widget_set_name (priv->received, "hildon-shadow-label");
583
584         hbox = gtk_hbox_new (FALSE, 0);
585         gtk_box_pack_start (GTK_BOX (hbox), priv->unread, FALSE, FALSE, 0);
586         gtk_box_pack_start (GTK_BOX (hbox), priv->icon, FALSE, FALSE, 0);
587         gtk_box_pack_start (GTK_BOX (hbox), priv->sender, TRUE, TRUE, 0);
588
589         vbox = gtk_vbox_new (FALSE, 0);
590         gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
591         gtk_box_pack_start (GTK_BOX (vbox), priv->message, TRUE, TRUE, 0);
592         gtk_box_pack_start (GTK_BOX (vbox), priv->received, FALSE, FALSE, 0);
593
594         align = gtk_alignment_new (0.5f, 0.0f, 1.0f, 1.0f);
595         gtk_alignment_set_padding (GTK_ALIGNMENT (align),
596                                    0, 0, HILDON_MARGIN_DEFAULT, HILDON_MARGIN_DEFAULT);
597
598         gtk_container_set_border_width (GTK_CONTAINER (vbox), HILDON_MARGIN_HALF);
599
600         event_box = gtk_event_box_new ();
601         gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
602         gtk_widget_set_size_request (event_box, BOX_WIDTH, BOX_HEIGHT);
603
604         gtk_container_add (GTK_CONTAINER (align), vbox);
605         gtk_container_add (GTK_CONTAINER (event_box), align);
606         gtk_container_add (GTK_CONTAINER (self), event_box);
607
608         g_signal_connect (event_box, "button-press-event",
609                 G_CALLBACK (button_press_event_cb), self);
610         g_signal_connect (event_box, "button-release-event",
611                 G_CALLBACK (button_release_event_cb), self);
612         g_signal_connect (event_box, "leave-notify-event",
613                 G_CALLBACK (leave_notify_event_cb), self);
614
615         gtk_widget_show_all (GTK_WIDGET (event_box));
616
617 #ifndef DEBUG_LAYOUT
618         priv->eventlogger = rtcom_el_new ();
619         g_signal_connect (priv->eventlogger,
620                           "new-event",
621                           G_CALLBACK (new_event_cb),
622                           self);
623         g_signal_connect (priv->eventlogger,
624                           "event-updated",
625                           G_CALLBACK (new_event_cb),
626                           self);
627
628         read_event (self);
629         priv->unread_count = query_unread_events (priv->eventlogger);
630         update_unread_label (self);
631 #endif
632 }
633
634 static void
635 el_home_applet_class_init (ELHomeAppletClass *klass)
636 {
637         GObjectClass *object_class = G_OBJECT_CLASS (klass);
638         GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
639
640         object_class->dispose = dispose;
641         object_class->finalize = finalize;
642         widget_class->expose_event = expose_event;
643         widget_class->realize = el_home_applet_realize;
644
645         g_type_class_add_private (klass, sizeof (ELHomeAppletPrivate));
646 }
647