Initial commit
[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
58         gint       event_id;
59
60         gboolean   active;
61
62         guint unread_count;
63
64         const gchar *current_font;
65 };
66
67 HD_DEFINE_PLUGIN_MODULE (ELHomeApplet, el_home_applet, HD_TYPE_HOME_PLUGIN_ITEM);
68
69 const gchar* g_module_check_init(GModule *module);
70 const gchar*
71 g_module_check_init(GModule *module)
72 {
73         g_module_make_resident (module);
74         return NULL;
75 }
76
77 static void
78 el_home_applet_class_finalize (ELHomeAppletClass *klass)
79 {
80 }
81
82 static void
83 el_home_applet_realize (GtkWidget *widget)
84 {
85         GdkScreen *screen;
86
87         screen = gtk_widget_get_screen (widget);
88         gtk_widget_set_colormap (widget,
89                                  gdk_screen_get_rgba_colormap (screen));
90
91         gtk_widget_set_app_paintable (widget,
92                                       TRUE);
93
94         GTK_WIDGET_CLASS (el_home_applet_parent_class)->realize (widget);
95 }
96
97 /*
98  * Thanks http://cairographics.org/cookbook/roundedrectangles/
99  */
100 static void
101 rounded_rectangle (cairo_t *cr,
102                    double   x,
103                    double   y,
104                    double   w,
105                    double   h,
106                    double   r)
107 {
108         cairo_move_to (cr, x + r, y);
109         cairo_line_to (cr, x + w - r, y);
110         cairo_curve_to (cr, x + w, y,
111                         x + w, y,
112                         x + w, y + r);
113         cairo_line_to (cr, x + w, y + h - r);
114         cairo_curve_to (cr, x + w, y + h,
115                         x + w, y + h,
116                         x + w - r, y + h);
117         cairo_line_to (cr, x + r, y + h);
118         cairo_curve_to (cr, x, y + h,
119                         x, y + h,
120                         x, y + h - r);
121         cairo_line_to (cr, x, y + r);
122         cairo_curve_to (cr, x, y,
123                         x, y,
124                         x + r, y);
125 }
126
127 static gboolean
128 expose_event (GtkWidget *self, GdkEventExpose *event)
129 {
130         ELHomeAppletPrivate *priv = EL_HOME_APPLET(self)->priv;
131
132         cairo_t *cr;
133         GdkColor color;
134         float red, green, blue;
135
136         /* find theme active color */
137         gtk_style_lookup_color (self->style, "ActiveTextColor", &color);
138         red = color.red/(float)G_MAXUINT16;
139         green = color.green/(float)G_MAXUINT16;
140         blue = color.blue/(float)G_MAXUINT16;
141
142         cr = gdk_cairo_create (self->window);
143         gdk_cairo_region (cr, event->region);
144         cairo_clip (cr);
145
146         cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
147
148         cairo_set_source_rgba (cr, 0.4f, 0.4f, 0.4f, 0.1f);
149         cairo_set_line_width (cr, 3.0f);
150
151         rounded_rectangle (cr,
152                            C_X,
153                            C_Y,
154                            BOX_WIDTH - 2*C_X,
155                            BOX_HEIGHT - 2*C_Y,
156                            BOX_RADIOUS);
157
158         cairo_close_path(cr);
159         cairo_stroke (cr);
160
161         cairo_set_line_width (cr, 1.0f);
162
163         cairo_translate (cr, C_X, C_Y);
164         cairo_move_to (cr, 0, HEADER_HEIGHT);
165         cairo_line_to (cr, 0, BOX_RADIOUS);
166         cairo_curve_to (cr, 0, 0, 0, 0, BOX_RADIOUS, 0);
167         cairo_line_to (cr, C_WIDTH - BOX_RADIOUS, 0);
168         cairo_curve_to (cr, C_WIDTH, 0, C_WIDTH, 0, C_WIDTH, BOX_RADIOUS);
169         cairo_line_to (cr, C_WIDTH, HEADER_HEIGHT);
170         cairo_line_to (cr, 0, HEADER_HEIGHT);
171
172         cairo_close_path(cr);
173
174         cairo_set_source_rgba (cr, 0.2f, 0.2f, 0.2f, 0.8f);
175         cairo_fill_preserve (cr);
176         cairo_set_source_rgba (cr, red, green, blue, 1.0f);
177         cairo_stroke (cr);
178
179         cairo_move_to (cr, 0, HEADER_HEIGHT);
180         cairo_line_to (cr, 0, C_HEIGHT - BOX_RADIOUS);
181         cairo_curve_to (cr, 0, C_HEIGHT, 0, C_HEIGHT, BOX_RADIOUS, C_HEIGHT);
182         cairo_line_to (cr, C_WIDTH - BOX_RADIOUS, C_HEIGHT);
183         cairo_curve_to (cr, C_WIDTH, C_HEIGHT, C_WIDTH, C_HEIGHT, C_WIDTH, C_HEIGHT - BOX_RADIOUS);
184         cairo_line_to (cr, C_WIDTH, HEADER_HEIGHT);
185         cairo_line_to (cr, 0, HEADER_HEIGHT);
186         cairo_close_path(cr);
187
188         if (priv->active)
189                 cairo_set_source_rgba (cr, red, green, blue, 0.8f);
190         else
191                 cairo_set_source_rgba (cr, 0.4f, 0.4f, 0.4f, 0.8f);
192         cairo_fill (cr);
193
194         /* cairo_set_source_rgba (cr, red, green, blue, 1.0f); */
195         /* cairo_translate (cr, -C_X, -C_Y); */
196         /* rounded_rectangle (cr, */
197         /*                    C_X, */
198         /*                    C_Y, */
199         /*                    BOX_WIDTH - 2*C_X, */
200         /*                    BOX_HEIGHT - 2*C_Y, */
201         /*                    BOX_RADIOUS); */
202         /* cairo_close_path(cr); */
203         /* cairo_stroke (cr); */
204
205         cairo_destroy (cr);
206
207         return GTK_WIDGET_CLASS (el_home_applet_parent_class)->expose_event (self, event);
208 }
209
210 static void
211 dispose (GObject *self)
212 {
213         ELHomeAppletPrivate *priv = EL_HOME_APPLET(self)->priv;
214
215         if (priv->eventlogger){
216                 g_object_unref (priv->eventlogger);
217                 priv->eventlogger = NULL;
218         }
219
220         G_OBJECT_CLASS (el_home_applet_parent_class)->dispose (self);
221 }
222
223 static void
224 finalize (GObject *self)
225 {
226         G_OBJECT_CLASS (el_home_applet_parent_class)->finalize (self);
227 }
228
229 static void
230 show_event (ELHomeApplet *self, RTComElIter *it)
231 {
232         ELHomeAppletPrivate *priv = self->priv;
233
234         gchar *message = NULL;
235         gchar *remote = NULL;
236         const gchar *icon_name = NULL;
237
238         if (it && rtcom_el_iter_first (it)){
239                 rtcom_el_iter_dup_string (it, "free-text", &message);
240                 if (message){
241                         const gchar *service;
242
243                         rtcom_el_iter_get_int (it, "id", &priv->event_id);
244
245                         if(!rtcom_el_iter_dup_string (it, "remote-name", &remote))
246                                 rtcom_el_iter_dup_string (it, "remote-id", &remote);
247                         service = rtcom_el_iter_get_service (it);
248                         if (!g_strcmp0 (service, "RTCOM_EL_SERVICE_SMS"))
249                                 icon_name = "chat_unread_sms";
250                         else if (!g_strcmp0 (service, "RTCOM_EL_SERVICE_CHAT"))
251                                 icon_name = "chat_unread_chat";
252                 }
253         }
254         else{
255                 priv->event_id = -1;
256         }
257
258         gtk_label_set_text (GTK_LABEL (priv->message), message);
259         gtk_label_set_text (GTK_LABEL (priv->sender), remote);
260         if (icon_name){
261                 const gchar *current_icon_name;
262                 gtk_image_get_icon_name (GTK_IMAGE (priv->icon),
263                                          &current_icon_name,
264                                          NULL);
265                 if (g_strcmp0 (current_icon_name, icon_name))
266                         gtk_image_set_from_icon_name (GTK_IMAGE (priv->icon),
267                                                       icon_name,
268                                                       HILDON_ICON_SIZE_FINGER);
269                 gtk_widget_show (priv->icon);
270         }
271         else
272                 gtk_widget_hide (priv->icon);
273
274         g_free (message);
275         g_free (remote);
276 }
277
278 static RTComElIter*
279 make_query (RTComEl *el, gint event_id)
280 {
281         RTComElQuery *query = NULL;
282         RTComElIter *it = NULL;
283
284         static const gchar *services[] = {"RTCOM_EL_SERVICE_SMS",
285                                           "RTCOM_EL_SERVICE_CHAT",
286                                           NULL};
287         static const gchar *event_types[] = {"RTCOM_EL_EVENTTYPE_SMS_INBOUND",
288                                              "RTCOM_EL_EVENTTYPE_CHAT_INBOUND",
289                                              NULL};
290
291         query = rtcom_el_query_new (el);
292         rtcom_el_query_set_limit (query, 1);
293         if (event_id >= 0){
294                 rtcom_el_query_prepare (query,
295                                         "is-read", FALSE, RTCOM_EL_OP_EQUAL,
296                                         "id", event_id, RTCOM_EL_OP_EQUAL,
297                                         "service", services, RTCOM_EL_OP_IN_STRV,
298                                         "event-type", event_types, RTCOM_EL_OP_IN_STRV,
299                                         NULL);
300         }
301         else{
302                 rtcom_el_query_prepare (query,
303                                         "is-read", FALSE, RTCOM_EL_OP_EQUAL,
304                                         "service", services, RTCOM_EL_OP_IN_STRV,
305                                         "event-type", event_types, RTCOM_EL_OP_IN_STRV,
306                                         NULL);
307         }
308         it = rtcom_el_get_events (el, query);
309         g_object_unref(query);
310
311         return it;
312 }
313
314 static void
315 update_unread_label (ELHomeApplet *self)
316 {
317         ELHomeAppletPrivate *priv = self->priv;
318         gchar *text;
319
320         text = g_strdup_printf ("%d", priv->unread_count);
321         gtk_label_set_text (GTK_LABEL (priv->unread), text);
322         g_free (text);
323 }
324
325 static gint
326 query_unread_events (RTComEl *el)
327 {
328         sqlite3 *db;
329         sqlite3_stmt *stmt;
330         int ret;
331         gint count = 0;
332
333         g_object_get (el, "db", &db, NULL);
334
335         if (sqlite3_prepare_v2 (db,
336                                 "SELECT SUM(total_events)-SUM(read_events) FROM GroupCache;",
337                                 -1,
338                                 &stmt,
339                                 NULL) != SQLITE_OK){
340                 g_error ("%s: can't compile SQL", G_STRFUNC);
341                 return -1;
342         }
343
344         while (SQLITE_BUSY == (ret = sqlite3_step (stmt)));
345
346         if (ret == SQLITE_ROW){
347                 count = sqlite3_column_int (stmt, 0);
348         }
349         else{
350                 g_error ("%s: error while executing SQL", G_STRFUNC);
351         }
352
353         sqlite3_finalize (stmt);
354
355         return count;
356 }
357
358 static void
359 read_event (ELHomeApplet *self)
360 {
361         ELHomeAppletPrivate *priv = self->priv;
362         RTComElIter *it = NULL;
363
364         it = make_query (priv->eventlogger, -1);
365         show_event (self, it);
366         if (it) g_object_unref (it);
367 }
368
369 static void
370 mark_as_read (ELHomeApplet *self)
371 {
372         ELHomeAppletPrivate *priv = self->priv;
373
374         if (priv->event_id >= 0){
375                 rtcom_el_set_read_event (priv->eventlogger,
376                                          priv->event_id,
377                                          TRUE,
378                                          NULL);
379                 read_event (self);
380                 priv->unread_count--;
381                 update_unread_label (self);
382         }
383 }
384
385 static void
386 new_event_cb (RTComEl      *backend,
387               gint          event_id,
388               const gchar  *local_uid,
389               const gchar  *remote_uid,
390               const gchar  *remote_ebook_uid,
391               const gchar  *group_uid,
392               const gchar  *service,
393               ELHomeApplet *self)
394 {
395         ELHomeAppletPrivate *priv = self->priv;
396         RTComElIter *it = NULL;
397
398         it = make_query (priv->eventlogger, event_id);
399         if (it){
400                 if (rtcom_el_iter_first (it)){
401                         show_event (self, it);
402                         priv->unread_count++;
403                         update_unread_label (self);
404                 }
405                 g_object_unref (it);
406         }
407 }
408
409 static void
410 event_updated_cb (RTComEl      *backend,
411                   gint          event_id,
412                   const gchar  *local_uid,
413                   const gchar  *remote_uid,
414                   const gchar  *remote_ebook_uid,
415                   const gchar  *group_uid,
416                   const gchar  *service,
417                   ELHomeApplet *self)
418 {
419         ELHomeAppletPrivate *priv = self->priv;
420
421         if (event_id == priv->event_id)
422                 read_event (self);
423
424         priv->unread_count = query_unread_events (priv->eventlogger);
425         update_unread_label (self);
426 }
427
428 static gboolean
429 button_release_event_cb (GtkWidget      *widget,
430                          GdkEventButton *event,
431                          ELHomeApplet   *self)
432 {
433         ELHomeAppletPrivate *priv = self->priv;
434
435         if (priv->active){
436                 priv->active = FALSE;
437                 gtk_widget_queue_draw (widget);
438 #ifndef DEBUG_LAYOUT
439                 mark_as_read (self);
440 #endif
441         }
442
443         return TRUE;
444 }
445
446 static gboolean
447 button_press_event_cb (GtkWidget      *widget,
448                        GdkEventButton *event,
449                        ELHomeApplet   *self)
450 {
451         ELHomeAppletPrivate *priv = self->priv;
452
453         if (priv->event_id > 0){
454                 priv->active = TRUE;
455                 gtk_widget_queue_draw (widget);
456         }
457
458         return TRUE;
459 }
460
461 static gboolean
462 leave_notify_event_cb (GtkWidget        *widget,
463                        GdkEventCrossing *event,
464                        ELHomeApplet     *self)
465 {
466         ELHomeAppletPrivate *priv = self->priv;
467
468         if (priv->active){
469                 priv->active = FALSE;
470                 gtk_widget_queue_draw (widget);
471         }
472
473         return FALSE;
474 }
475
476 static void
477 el_home_applet_init (ELHomeApplet *self)
478 {
479         ELHomeAppletPrivate *priv;
480         GtkWidget *event_box;
481         GtkWidget *hbox, *vbox, *align;
482
483         self->priv = EL_HOME_APPLET_GET_PRIVATE (self);
484         priv = self->priv;
485
486         gtk_widget_set_app_paintable (GTK_WIDGET (self), TRUE);
487
488         priv->unread = gtk_label_new ("12");
489         hildon_helper_set_logical_color (priv->unread,
490                                          GTK_RC_FG,
491                                          GTK_STATE_NORMAL,
492                                          "ActiveTextColor");
493         gtk_misc_set_alignment (GTK_MISC (priv->unread),
494                                 1.0f,
495                                 0.5f);
496         gtk_widget_set_size_request (priv->unread,
497                                      -1,
498                                      HEADER_HEIGHT);
499
500         priv->icon = gtk_image_new_from_icon_name ("chat_unread_sms",
501                                                    HILDON_ICON_SIZE_FINGER);
502         gtk_misc_set_alignment (GTK_MISC (priv->icon),
503                                 0.5f,
504                                 0.5f);
505
506         priv->sender = gtk_label_new ("asdf asdf asdf asdf asdf");
507         gtk_misc_set_alignment (GTK_MISC (priv->sender),
508                                 0.5f,
509                                 0.5f);
510         gtk_label_set_ellipsize (GTK_LABEL (priv->sender),
511                                  PANGO_ELLIPSIZE_END);
512         gtk_widget_set_name (priv->sender, "hildon-shadow-label");
513         hildon_helper_set_logical_font (priv->sender, "SystemFont");
514
515         priv->message = g_object_new (GTK_TYPE_LABEL,
516                                       "label", "asdf asdf adsf asdf asdf asdf asdf asdf",
517                                       "wrap", TRUE,
518                                       "wrap-mode", PANGO_WRAP_WORD_CHAR,
519                                       NULL);
520
521         gtk_misc_set_alignment (GTK_MISC (priv->message),
522                                 0.0f,
523                                 0.0f);
524         gtk_widget_set_size_request (priv->message,
525                                      MESSAGE_WIDTH,
526                                      MESSAGE_HEIGHT);
527         gtk_widget_set_name (priv->message, "hildon-shadow-label");
528
529         hbox = gtk_hbox_new (FALSE, 0);
530         gtk_box_pack_start (GTK_BOX (hbox), priv->unread, FALSE, FALSE, 0);
531         gtk_box_pack_start (GTK_BOX (hbox), priv->icon, FALSE, FALSE, 0);
532         gtk_box_pack_start (GTK_BOX (hbox), priv->sender, TRUE, TRUE, 0);
533
534         vbox = gtk_vbox_new (FALSE, 0);
535         gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
536         gtk_box_pack_start (GTK_BOX (vbox), priv->message, TRUE, TRUE, 0);
537
538         align = gtk_alignment_new (0.5f, 0.0f, 1.0f, 1.0f);
539         gtk_alignment_set_padding (GTK_ALIGNMENT (align),
540                                    0, 0, HILDON_MARGIN_DEFAULT, HILDON_MARGIN_DEFAULT);
541
542         gtk_container_set_border_width (GTK_CONTAINER (vbox), HILDON_MARGIN_HALF);
543
544         event_box = gtk_event_box_new ();
545         gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
546         gtk_widget_set_size_request (event_box, BOX_WIDTH, BOX_HEIGHT);
547
548         gtk_container_add (GTK_CONTAINER (align), vbox);
549         gtk_container_add (GTK_CONTAINER (event_box), align);
550         gtk_container_add (GTK_CONTAINER (self), event_box);
551
552         g_signal_connect (event_box, "button-press-event",
553                 G_CALLBACK (button_press_event_cb), self);
554         g_signal_connect (event_box, "button-release-event",
555                 G_CALLBACK (button_release_event_cb), self);
556         g_signal_connect (event_box, "leave-notify-event",
557                 G_CALLBACK (leave_notify_event_cb), self);
558
559         gtk_widget_show_all (GTK_WIDGET (event_box));
560
561 #ifndef DEBUG_LAYOUT
562         priv->eventlogger = rtcom_el_new ();
563         g_signal_connect (priv->eventlogger,
564                           "new-event",
565                           G_CALLBACK (new_event_cb),
566                           self);
567         g_signal_connect (priv->eventlogger,
568                           "event-updated",
569                           G_CALLBACK (event_updated_cb),
570                           self);
571
572         read_event (self);
573         priv->unread_count = query_unread_events (priv->eventlogger);
574         update_unread_label (self);
575 #endif
576 }
577
578 static void
579 el_home_applet_class_init (ELHomeAppletClass *klass)
580 {
581         GObjectClass *object_class = G_OBJECT_CLASS (klass);
582         GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
583
584         object_class->dispose = dispose;
585         object_class->finalize = finalize;
586         widget_class->expose_event = expose_event;
587         widget_class->realize = el_home_applet_realize;
588
589         g_type_class_add_private (klass, sizeof (ELHomeAppletPrivate));
590 }
591