Fix layout after introducing avatar
[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 #include <libosso-abook/osso-abook-init.h>
31 #include <libosso-abook/osso-abook-aggregator.h>
32 #include <libosso-abook/osso-abook-contact.h>
33 #include <libosso-abook/osso-abook-waitable.h>
34 #include <libosso-abook/osso-abook-contact.h>
35 #include <libosso-abook/osso-abook-touch-contact-starter.h>
36 #include <libosso-abook/osso-abook-temporary-contact-dialog.h>
37 #include <libosso-abook/osso-abook-account-manager.h>
38
39 #define EL_HOME_APPLET_GET_PRIVATE(obj) ( \
40         G_TYPE_INSTANCE_GET_PRIVATE (obj, \
41                 EL_TYPE_HOME_APPLET, ELHomeAppletPrivate))
42
43 #define BOX_WIDTH 352
44 #define BOX_HEIGHT 276
45
46 #define CONTENT_OFFSET_X HILDON_MARGIN_HALF
47 #define CONTENT_OFFSET_Y_TOP 4*HILDON_MARGIN_HALF
48 #define CONTENT_OFFSET_Y_BOTTOM HILDON_MARGIN_HALF
49 #define C_WIDTH (BOX_WIDTH - 2*CONTENT_OFFSET_X)
50 #define C_HEIGHT (BOX_HEIGHT - (CONTENT_OFFSET_Y_TOP + CONTENT_OFFSET_Y_BOTTOM))
51
52 #define HEADER_HEIGHT 48
53 #define MESSAGE_HEIGHT (C_HEIGHT - HEADER_HEIGHT)
54 #define MESSAGE_WIDTH (C_WIDTH - 2*HILDON_MARGIN_DEFAULT)
55
56 #define BOX_RADIOUS 10
57
58 #define SCROLL_PERIOD 100 /* ms */
59 #define SCROLL_STEP 1 /* pixel */
60 #define TEXT_Y_OFFSET (HEADER_HEIGHT + HILDON_MARGIN_HALF)
61
62 struct _ELHomeAppletPrivate
63 {
64         RTComEl *eventlogger;
65
66         GtkWidget *sender;
67         /* GtkWidget *icon; */
68         GtkWidget *unread;
69         GtkWidget *received;
70         GtkWidget *empty;
71         GtkWidget *cut_message;
72         GtkWidget *avatar;
73
74         gchar *message;
75         gint event_id;
76
77         gboolean active_body;
78         gboolean active_header;
79
80         guint unread_count;
81
82         struct {
83                 float red;
84                 float green;
85                 float blue;
86         } active_color;
87         guint8 border_color[4];
88         PangoFontDescription *font_desc;
89
90         guint idle_id;
91
92         cairo_surface_t *message_surface;
93
94         gboolean scroll_on_click;
95         gint scroll_offset;
96         gint hidden_message_height;
97         guint scroll_anim_id;
98
99         OssoABookRoster *aggregator;
100         OssoABookWaitableClosure *aggregator_ready_closure;
101         gchar *contact_id;
102         gchar *remote_id;
103         gchar *local_id;
104         OssoABookContact *contact;
105 };
106
107 HD_DEFINE_PLUGIN_MODULE (ELHomeApplet, el_home_applet, HD_TYPE_HOME_PLUGIN_ITEM);
108
109 const gchar* g_module_check_init (GModule *module);
110 const gchar*
111 g_module_check_init (GModule *module)
112 {
113         g_module_make_resident (module);
114         return NULL;
115 }
116
117 static void
118 el_home_applet_class_finalize (ELHomeAppletClass *klass)
119 {
120 }
121
122 static void
123 el_home_applet_realize (GtkWidget *widget)
124 {
125         GdkScreen *screen;
126
127         screen = gtk_widget_get_screen (widget);
128         gtk_widget_set_colormap (widget,
129                                  gdk_screen_get_rgba_colormap (screen));
130
131         gtk_widget_set_app_paintable (widget,
132                                       TRUE);
133
134         GTK_WIDGET_CLASS (el_home_applet_parent_class)->realize (widget);
135 }
136
137 /*
138  * Thanks http://cairographics.org/cookbook/roundedrectangles/
139  */
140 static void
141 rounded_rectangle (cairo_t *cr,
142                    double   x,
143                    double   y,
144                    double   w,
145                    double   h,
146                    double   r)
147 {
148         cairo_move_to (cr, x + r, y);
149         cairo_line_to (cr, x + w - r, y);
150         cairo_curve_to (cr, x + w, y,
151                         x + w, y,
152                         x + w, y + r);
153         cairo_line_to (cr, x + w, y + h - r);
154         cairo_curve_to (cr, x + w, y + h,
155                         x + w, y + h,
156                         x + w - r, y + h);
157         cairo_line_to (cr, x + r, y + h);
158         cairo_curve_to (cr, x, y + h,
159                         x, y + h,
160                         x, y + h - r);
161         cairo_line_to (cr, x, y + r);
162         cairo_curve_to (cr, x, y,
163                         x, y,
164                         x + r, y);
165 }
166
167 static cairo_surface_t*
168 draw_text (cairo_t              *cr,
169            PangoFontDescription *desc,
170            const gchar          *text,
171            gint                  width,
172            gint                 *height)
173 {
174         PangoLayout *layout;
175         PangoRectangle extent;
176
177         cairo_surface_t *gdk_surface, *result_surface;
178         cairo_t *msg_cr;
179
180         /* Create a PangoLayout, set the font and text */
181         layout = pango_cairo_create_layout (cr);
182         pango_layout_set_text (layout,
183                                text,
184                                -1);
185         pango_layout_set_font_description (layout, desc);
186
187         pango_layout_set_wrap (layout, PANGO_WRAP_WORD_CHAR);
188         pango_layout_set_width (layout, PANGO_SCALE*width);
189
190         pango_layout_get_pixel_extents (layout, NULL, &extent);
191         *height = extent.height;
192
193         gdk_surface = cairo_get_target (cr);
194         result_surface = cairo_surface_create_similar
195                 (gdk_surface,
196                  CAIRO_CONTENT_COLOR_ALPHA,
197                  width,
198                  extent.height);
199         msg_cr = cairo_create (result_surface);
200
201         pango_cairo_update_layout (msg_cr, layout);
202         /* draw shadow */
203         cairo_move_to (msg_cr, 1, 1);
204         cairo_set_source_rgba (msg_cr, 0.2, 0.2, 0.2, 0.8);
205         pango_cairo_show_layout (msg_cr, layout);
206
207         /* draw fg */
208         cairo_move_to (msg_cr, 0, 0);
209         cairo_set_source_rgba (msg_cr, 1.0, 1.0, 1.0, 1.0);
210         pango_cairo_show_layout (msg_cr, layout);
211
212         cairo_destroy (msg_cr);
213         g_object_unref (layout);
214
215         return result_surface;
216 }
217
218 static void
219 stop_scroll_anim (ELHomeAppletPrivate *priv)
220 {
221         if (priv->scroll_anim_id > 0) {
222                 g_source_remove (priv->scroll_anim_id);
223                 priv->scroll_anim_id = 0;
224                 priv->scroll_on_click = FALSE;
225                 gtk_widget_hide (priv->cut_message);
226         }
227 }
228
229 static void
230 style_set_cb (GtkWidget *widget,
231               GtkStyle  *previous_style,
232               ELHomeApplet *self)
233 {
234         ELHomeAppletPrivate *priv = self->priv;
235         GdkColor color;
236         GtkStyle *font_style;
237
238         font_style = gtk_rc_get_style_by_paths (gtk_widget_get_settings (widget),
239                                                 "SystemFont",
240                                                 NULL,
241                                                 G_TYPE_NONE);
242         if (font_style && font_style->font_desc) {
243                 if (priv->font_desc)
244                         pango_font_description_free (priv->font_desc);
245                 priv->font_desc = pango_font_description_copy (font_style->font_desc);
246         }
247
248         if (gtk_style_lookup_color (widget->style,
249                                     "ActiveTextColor",
250                                     &color)) {
251                 priv->active_color.red = color.red/(float)G_MAXUINT16;
252                 priv->active_color.green = color.green/(float)G_MAXUINT16;
253                 priv->active_color.blue = color.blue/(float)G_MAXUINT16;
254
255                 priv->border_color[0] = color.red;
256                 priv->border_color[1] = color.green;
257                 priv->border_color[2] = color.blue;
258                 priv->border_color[3] = 255;
259         }
260 }
261
262 static void
263 notify_on_current_desktop (GObject      *object,
264                            GParamSpec   *unused G_GNUC_UNUSED,
265                            ELHomeApplet *self)
266 {
267         ELHomeAppletPrivate *priv = self->priv;
268         gboolean on;
269
270         g_object_get (object, "is-on-current-desktop", &on, NULL);
271         if (!on) {
272                 stop_scroll_anim (self->priv);
273                 priv->scroll_on_click = priv->scroll_offset;
274                 priv->scroll_offset = 0;
275                 if (priv->scroll_on_click)
276                         gtk_widget_show (priv->cut_message);
277                 gtk_widget_queue_draw (GTK_WIDGET (self));
278         }
279 }
280
281 static gboolean
282 expose_event (GtkWidget *self, GdkEventExpose *event)
283 {
284         ELHomeAppletPrivate *priv = EL_HOME_APPLET(self)->priv;
285         cairo_t *cr;
286         int message_height;
287
288         message_height = C_HEIGHT - priv->received->allocation.height - HEADER_HEIGHT;
289
290         cr = gdk_cairo_create (self->window);
291         gdk_cairo_region (cr, event->region);
292         cairo_clip (cr);
293
294         cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
295
296         /* draw bound box */
297         cairo_set_source_rgba (cr, 0.4f, 0.4f, 0.4f, 0.1f);
298         cairo_set_line_width (cr, 3.0f);
299
300         rounded_rectangle (cr,
301                            CONTENT_OFFSET_X,
302                            CONTENT_OFFSET_Y_TOP,
303                            BOX_WIDTH - 2*CONTENT_OFFSET_X,
304                            BOX_HEIGHT - (CONTENT_OFFSET_Y_TOP + CONTENT_OFFSET_Y_BOTTOM),
305                            BOX_RADIOUS);
306
307         cairo_close_path (cr);
308         cairo_stroke (cr);
309
310         /* draw header */
311         cairo_set_line_width (cr, 1.0f);
312
313         cairo_translate (cr, CONTENT_OFFSET_X, CONTENT_OFFSET_Y_TOP);
314         cairo_move_to (cr, 0, HEADER_HEIGHT);
315         cairo_line_to (cr, 0, BOX_RADIOUS);
316         cairo_curve_to (cr, 0, 0, 0, 0, BOX_RADIOUS, 0);
317         cairo_line_to (cr, C_WIDTH - BOX_RADIOUS, 0);
318         cairo_curve_to (cr, C_WIDTH, 0, C_WIDTH, 0, C_WIDTH, BOX_RADIOUS);
319         cairo_line_to (cr, C_WIDTH, HEADER_HEIGHT);
320         cairo_line_to (cr, 0, HEADER_HEIGHT);
321
322         cairo_close_path (cr);
323
324         if (priv->active_header)
325                 cairo_set_source_rgba (cr,
326                                        priv->active_color.red,
327                                        priv->active_color.green,
328                                        priv->active_color.blue,
329                                        0.8f);
330         else
331                 cairo_set_source_rgba (cr, 0.2f, 0.2f, 0.2f, 0.8f);
332         cairo_fill (cr);
333
334         cairo_move_to (cr, 0, HEADER_HEIGHT);
335         cairo_line_to (cr, C_WIDTH, HEADER_HEIGHT);
336         cairo_set_source_rgba (cr,
337                                priv->active_color.red,
338                                priv->active_color.green,
339                                priv->active_color.blue,
340                                1.0f);
341         cairo_stroke (cr);
342
343         /* draw body */
344         cairo_move_to (cr, 0, HEADER_HEIGHT);
345         cairo_line_to (cr, 0, C_HEIGHT - BOX_RADIOUS);
346         cairo_curve_to (cr, 0, C_HEIGHT, 0, C_HEIGHT, BOX_RADIOUS, C_HEIGHT);
347         cairo_line_to (cr, C_WIDTH - BOX_RADIOUS, C_HEIGHT);
348         cairo_curve_to (cr, C_WIDTH, C_HEIGHT, C_WIDTH, C_HEIGHT, C_WIDTH, C_HEIGHT - BOX_RADIOUS);
349         cairo_line_to (cr, C_WIDTH, HEADER_HEIGHT);
350         cairo_line_to (cr, 0, HEADER_HEIGHT);
351         cairo_close_path (cr);
352
353         /* draw body filling depending on (in)active state */
354         cairo_pattern_t *grad;
355         grad = cairo_pattern_create_linear (0, HEADER_HEIGHT,
356                                             0, C_HEIGHT);
357
358         if (priv->active_body) {
359                 cairo_pattern_add_color_stop_rgba (grad,
360                                                    0.5f,
361                                                    priv->active_color.red,
362                                                    priv->active_color.green,
363                                                    priv->active_color.blue,
364                                                    0.8f);
365                 cairo_pattern_add_color_stop_rgba (grad,
366                                                    1.0f,
367                                                    priv->active_color.red/2,
368                                                    priv->active_color.green/2,
369                                                    priv->active_color.blue/2,
370                                                    0.8f);
371         }
372         else {
373                 cairo_pattern_add_color_stop_rgba (grad, 0.5f,
374                                                    0.4f, 0.4f, 0.4f, 0.8f);
375                 cairo_pattern_add_color_stop_rgba (grad, 1.0f,
376                                                    0.2f, 0.2f, 0.2f, 0.8f);
377         }
378         cairo_set_source (cr, grad);
379         cairo_fill (cr);
380
381         /* cairo_set_source_rgba (cr, red, green, blue, 1.0f); */
382         /* cairo_translate (cr, -CONTENT_OFFSET_X, -C_Y); */
383         /* rounded_rectangle (cr, */
384         /*                    CONTENT_OFFSET_X, */
385         /*                    C_Y, */
386         /*                    BOX_WIDTH - 2*CONTENT_OFFSET_X, */
387         /*                    BOX_HEIGHT - 2*C_Y, */
388         /*                    BOX_RADIOUS); */
389         /* cairo_close_path (cr); */
390         /* cairo_stroke (cr); */
391
392         /* draw message */
393         if (!priv->message_surface) {
394                 gint height;
395
396                 priv->message_surface = draw_text (cr,
397                                                    priv->font_desc,
398                                                    priv->message,
399                                                    MESSAGE_WIDTH,
400                                                    &height);
401
402                 priv->hidden_message_height = height - message_height;
403                 priv->scroll_on_click = priv->hidden_message_height > 0;
404                 if (priv->scroll_on_click)
405                         gtk_widget_show (priv->cut_message);
406         }
407
408         cairo_rectangle (cr,
409                          2*CONTENT_OFFSET_X,
410                          TEXT_Y_OFFSET,
411                          MESSAGE_WIDTH,
412                          message_height);
413         cairo_clip (cr);
414
415         cairo_set_source_surface (cr,
416                                   priv->message_surface,
417                                   2*CONTENT_OFFSET_X,
418                                   TEXT_Y_OFFSET - priv->scroll_offset);
419         cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
420         cairo_paint (cr);
421
422         cairo_pattern_destroy (grad);
423         cairo_destroy (cr);
424
425         return GTK_WIDGET_CLASS (el_home_applet_parent_class)->expose_event (self, event);
426 }
427
428
429 static void
430 clean_state (ELHomeApplet *self)
431 {
432         ELHomeAppletPrivate *priv = self->priv;
433
434         if (priv->message) {
435                 g_free (priv->message);
436                 priv->message = NULL;
437         }
438
439         if (priv->contact_id) {
440                 g_free (priv->contact_id);
441                 priv->contact_id = NULL;
442         }
443         if (priv->local_id) {
444                 g_free (priv->local_id);
445                 priv->local_id = NULL;
446         }
447         if (priv->remote_id) {
448                 g_free (priv->remote_id);
449                 priv->remote_id = NULL;
450         }
451
452         if (priv->contact) {
453                 g_object_unref (priv->contact);
454                 priv->contact = NULL;
455         }
456         if (priv->aggregator) {
457                 if (priv->aggregator_ready_closure){
458                         osso_abook_waitable_cancel (OSSO_ABOOK_WAITABLE (priv->aggregator),
459                                                     priv->aggregator_ready_closure);
460                         priv->aggregator_ready_closure = NULL;
461                 }
462                 osso_abook_roster_stop (priv->aggregator);
463                 g_object_unref (priv->aggregator);
464                 priv->aggregator = NULL;
465         }
466 }
467
468 static void
469 dispose (GObject *self)
470 {
471         ELHomeAppletPrivate *priv = EL_HOME_APPLET(self)->priv;
472
473         stop_scroll_anim (priv);
474         if (priv->idle_id) {
475                 g_source_remove (priv->idle_id);
476                 priv->idle_id = 0;
477         }
478         if (priv->eventlogger) {
479                 g_object_unref (priv->eventlogger);
480                 priv->eventlogger = NULL;
481         }
482         if (priv->font_desc) {
483                 pango_font_description_free (priv->font_desc);
484                 priv->font_desc = NULL;
485         }
486
487         clean_state (EL_HOME_APPLET (self));
488
489         G_OBJECT_CLASS (el_home_applet_parent_class)->dispose (self);
490 }
491
492 static void
493 finalize (GObject *self)
494 {
495         G_OBJECT_CLASS (el_home_applet_parent_class)->finalize (self);
496 }
497
498 static void
499 aggregator_ready_cb (OssoABookWaitable *waitable,
500                      const GError      *error,
501                      gpointer           userdata)
502 {
503         g_warning (G_STRFUNC);
504         ELHomeApplet *self = EL_HOME_APPLET(userdata);
505         ELHomeAppletPrivate *priv = self->priv;
506         GList *contacts = NULL;
507
508         priv->aggregator_ready_closure = NULL;
509
510         if (error) {
511                 g_warning ("Failed to create aggregator: %s", error->message);
512                 return;
513         }
514
515         if (priv->contact_id) {
516                 contacts = osso_abook_aggregator_lookup
517                         (OSSO_ABOOK_AGGREGATOR (priv->aggregator),
518                          priv->contact_id);
519         }
520         else if (priv->local_id && priv->remote_id) {
521                 if (g_strcmp0 (priv->local_id, "ring/tel/ring" == 0)) {
522                         contacts = osso_abook_aggregator_find_contacts_for_phone_number
523                                 (OSSO_ABOOK_AGGREGATOR (priv->aggregator),
524                                  priv->remote_id,
525                                  TRUE);
526                 }
527                 else {
528                         McAccount *account;
529                         account = osso_abook_account_manager_lookup_by_name
530                                 (NULL,
531                                  priv->local_id);
532                         if (account) {
533                                 contacts = osso_abook_aggregator_find_contacts_for_im_contact
534                                         (OSSO_ABOOK_AGGREGATOR (priv->aggregator),
535                                          priv->remote_id,
536                                          account);
537                         }
538                 }
539         }
540
541         if (contacts && contacts->data) {
542                 GdkPixbuf *avatar_image;
543
544                 priv->contact = g_object_ref (OSSO_ABOOK_CONTACT (contacts->data));
545                 gtk_label_set_text (GTK_LABEL (priv->sender),
546                                     osso_abook_contact_get_display_name (priv->contact));
547                 avatar_image = osso_abook_avatar_get_image_rounded
548                         (OSSO_ABOOK_AVATAR (priv->contact),
549                          HILDON_ICON_PIXEL_SIZE_THUMB,
550                          HILDON_ICON_PIXEL_SIZE_THUMB,
551                          TRUE,
552                          -1,
553                          priv->border_color);
554
555                 g_warning ("contact's name %s\nid=%s\npid=%s",
556                            osso_abook_contact_get_display_name (priv->contact),
557                            osso_abook_contact_get_uid (priv->contact),
558                            osso_abook_contact_get_persistent_uid (priv->contact));
559
560
561                 if (avatar_image) {
562                         gtk_image_set_from_pixbuf (GTK_IMAGE (priv->avatar),
563                                                    avatar_image);
564                         gtk_widget_show (priv->avatar);
565                         g_object_unref (avatar_image);
566                 }
567                 gtk_widget_queue_draw (GTK_WIDGET (self));
568         }
569 }
570
571 static void
572 resolve_contact (ELHomeApplet *self)
573 {
574         ELHomeAppletPrivate *priv = self->priv;
575         EBookQuery *query = NULL;
576         GError *error = NULL;
577
578         g_warning ("%s %s %s %s", G_STRFUNC, priv->contact_id, priv->local_id, priv->remote_id);
579
580         if (priv->local_id && priv->remote_id) {
581                 const gchar *vcard = osso_abook_account_manager_get_vcard_field
582                         (NULL, priv->local_id);
583                 if (vcard)
584                         query = e_book_query_vcard_field_test (vcard,
585                                                                E_BOOK_QUERY_IS,
586                                                                priv->remote_id);
587                 else
588                         query = e_book_query_any_field_contains (priv->remote_id);
589         }
590
591         if (query) {
592                 priv->aggregator = osso_abook_aggregator_new_with_query (NULL,
593                                                                          query,
594                                                                          NULL,
595                                                                          1,
596                                                                          &error);
597                 e_book_query_unref (query);
598         }
599         if (error) {
600                 g_warning ("Failed to create aggregator: %s", error->message);
601                 g_error_free (error);
602                 return;
603         }
604
605         if (priv->aggregator) {
606                 priv->aggregator_ready_closure = osso_abook_waitable_call_when_ready
607                         (OSSO_ABOOK_WAITABLE (priv->aggregator),
608                          aggregator_ready_cb,
609                          self, NULL);
610
611                 osso_abook_roster_start (priv->aggregator);
612                 g_warning ("AGG STARTED");
613         }
614 }
615
616 static gchar*
617 format_time (time_t t)
618 {
619         static const guint RESULT_SIZE = 32;
620
621         time_t now;
622         struct tm now_tm, t_tm;
623         const gchar *format = "%x %X";
624         gchar *result = g_malloc0 (RESULT_SIZE);
625
626         now = time (NULL);
627         localtime_r (&now, &now_tm);
628         localtime_r (&t, &t_tm);
629
630         if ((now_tm.tm_year == t_tm.tm_year) &&
631             (now_tm.tm_mon  == t_tm.tm_mon) &&
632             (now_tm.tm_mday == t_tm.tm_mday))
633                 format = "%X";
634
635         strftime (result, RESULT_SIZE, format, &t_tm);
636
637         return result;
638 }
639
640 static void
641 show_event (ELHomeApplet *self, RTComElIter *it)
642 {
643         ELHomeAppletPrivate *priv = self->priv;
644         g_warning (G_STRFUNC);
645         gchar *remote = NULL;
646         gchar *received = NULL;
647         const gchar *icon_name = NULL;
648
649         if (it && rtcom_el_iter_first (it)) {
650                 rtcom_el_iter_dup_string (it, "free-text", &priv->message);
651                 if (priv->message) {
652                         const gchar *service;
653                         time_t received_t;
654
655                         rtcom_el_iter_get_int (it, "id", &priv->event_id);
656                         if (rtcom_el_iter_get_int (it, "start-time", (gint*)&received_t))
657                                 received = format_time (received_t);
658
659                         rtcom_el_iter_dup_string (it, "remote-uid", &priv->remote_id);
660                         if (!rtcom_el_iter_dup_string (it, "remote-name", &remote))
661                                 remote = g_strdup (priv->remote_id);
662
663                         rtcom_el_iter_dup_string (it, "remote-ebook-uid", &priv->contact_id);
664                         rtcom_el_iter_dup_string (it, "local-uid", &priv->local_id);
665                         g_warning ("abook uid %s", priv->contact_id);
666                         service = rtcom_el_iter_get_service (it);
667                         if (!g_strcmp0 (service, "RTCOM_EL_SERVICE_SMS"))
668                                 icon_name = "chat_unread_sms";
669                         else if (!g_strcmp0 (service, "RTCOM_EL_SERVICE_CHAT"))
670                                 icon_name = "chat_unread_chat";
671                 }
672         }
673         else {
674                 priv->event_id = -1;
675         }
676
677         g_warning ("event_id=%d\nremote-uid=%s\nremote_name=%s",
678                    priv->event_id,
679                    priv->remote_id,
680                    remote);
681
682         gtk_widget_hide (priv->avatar);
683
684         if (priv->message) {
685                 gtk_widget_hide (priv->empty);
686         }
687         else {
688                 gtk_widget_show (priv->empty);
689         }
690
691         gtk_label_set_text (GTK_LABEL (priv->received), received);
692
693 #if 0
694         gtk_widget_hide (priv->avatar);
695         if (icon_name) {
696                 const gchar *current_icon_name;
697                 gtk_image_get_icon_name (GTK_IMAGE (priv->icon),
698                                          &current_icon_name,
699                                          NULL);
700                 if (g_strcmp0 (current_icon_name, icon_name))
701                         gtk_image_set_from_icon_name (GTK_IMAGE (priv->icon),
702                                                       icon_name,
703                                                       HILDON_ICON_SIZE_FINGER);
704                 gtk_widget_show (priv->icon);
705         }
706         else
707                 gtk_widget_hide (priv->icon);
708 #endif
709
710         if (remote)
711                 gtk_label_set_text (GTK_LABEL (priv->sender), remote);
712         else
713                 gtk_label_set_text (GTK_LABEL (priv->sender), priv->remote_id);
714         g_free (remote);
715
716         stop_scroll_anim (priv);
717         priv->scroll_offset = 0;
718         if (priv->message_surface) {
719                 cairo_surface_destroy (priv->message_surface);
720                 priv->message_surface = NULL;
721         }
722
723         gtk_widget_hide (priv->cut_message);
724         gtk_widget_queue_draw (GTK_WIDGET (self));
725 }
726
727 static RTComElIter*
728 make_query (RTComEl *el, gint event_id)
729 {
730         RTComElQuery *query = NULL;
731         RTComElIter *it = NULL;
732
733         static const gchar *services[] = {"RTCOM_EL_SERVICE_SMS",
734                                           "RTCOM_EL_SERVICE_CHAT",
735                                           NULL};
736         static const gchar *event_types[] = {"RTCOM_EL_EVENTTYPE_SMS_INBOUND",
737                                              "RTCOM_EL_EVENTTYPE_CHAT_INBOUND",
738                                              NULL};
739
740         query = rtcom_el_query_new (el);
741         rtcom_el_query_set_limit (query, 1);
742         if (event_id >= 0) {
743                 rtcom_el_query_prepare (query,
744                                         "is-read", FALSE, RTCOM_EL_OP_EQUAL,
745                                         "id", event_id, RTCOM_EL_OP_EQUAL,
746                                         "service", services, RTCOM_EL_OP_IN_STRV,
747                                         "event-type", event_types, RTCOM_EL_OP_IN_STRV,
748                                         NULL);
749         }
750         else {
751                 rtcom_el_query_prepare (query,
752                                         "is-read", FALSE, RTCOM_EL_OP_EQUAL,
753                                         "service", services, RTCOM_EL_OP_IN_STRV,
754                                         "event-type", event_types, RTCOM_EL_OP_IN_STRV,
755                                         NULL);
756         }
757         it = rtcom_el_get_events (el, query);
758         g_object_unref (query);
759
760         return it;
761 }
762
763 static void
764 update_unread_label (ELHomeApplet *self)
765 {
766         ELHomeAppletPrivate *priv = self->priv;
767         gchar *text;
768
769         if (priv->unread_count > 0) {
770                 text = g_strdup_printf ("%d", priv->unread_count);
771                 gtk_label_set_text (GTK_LABEL (priv->unread), text);
772                 g_free (text);
773         }
774         else
775                 gtk_label_set_text (GTK_LABEL (priv->unread), NULL);
776 }
777
778 static gint
779 query_unread_events (RTComEl *el)
780 {
781         sqlite3 *db;
782         sqlite3_stmt *stmt;
783         int ret;
784         gint count = 0;
785
786         g_object_get (el, "db", &db, NULL);
787
788         if (sqlite3_prepare_v2 (db,
789                                 "SELECT SUM(total_events)-SUM(read_events) FROM GroupCache;",
790                                 -1,
791                                 &stmt,
792                                 NULL) != SQLITE_OK) {
793                 g_error ("%s: can't compile SQL", G_STRFUNC);
794                 return -1;
795         }
796
797         while (SQLITE_BUSY == (ret = sqlite3_step (stmt)));
798
799         if (ret == SQLITE_ROW) {
800                 count = sqlite3_column_int (stmt, 0);
801         }
802         else {
803                 g_error ("%s: error while executing SQL", G_STRFUNC);
804         }
805
806         sqlite3_finalize (stmt);
807
808         return count;
809 }
810
811 static void
812 read_event (ELHomeApplet *self)
813 {
814         g_warning (G_STRFUNC);
815         ELHomeAppletPrivate *priv = self->priv;
816         RTComElIter *it = NULL;
817
818         clean_state (self);
819
820         it = make_query (priv->eventlogger, -1);
821         show_event (self, it);
822         resolve_contact (self);
823         if (it) g_object_unref (it);
824 }
825
826 static void
827 mark_as_read (ELHomeApplet *self)
828 {
829         ELHomeAppletPrivate *priv = self->priv;
830         g_warning (G_STRFUNC);
831         if (priv->event_id >= 0) {
832                 rtcom_el_set_read_event (priv->eventlogger,
833                                          priv->event_id,
834                                          TRUE,
835                                          NULL);
836         }
837 }
838
839 static gboolean
840 read_new_event (ELHomeApplet *self)
841 {
842         ELHomeAppletPrivate *priv = self->priv;
843         g_warning (G_STRFUNC);
844         read_event (self);
845         priv->unread_count = query_unread_events (priv->eventlogger);
846         update_unread_label (self);
847
848         priv->idle_id = 0;
849
850         return FALSE;
851 }
852
853 static void
854 add_new_idle (ELHomeApplet *self)
855 {
856         ELHomeAppletPrivate *priv = self->priv;
857
858         if (priv->idle_id)
859                 g_source_remove (priv->idle_id);
860         priv->idle_id = g_idle_add ((GSourceFunc)read_new_event,
861                                     self);
862 }
863
864 static void
865 new_event_cb (RTComEl      *backend,
866               gint          event_id,
867               const gchar  *local_uid,
868               const gchar  *remote_uid,
869               const gchar  *remote_ebook_uid,
870               const gchar  *group_uid,
871               const gchar  *service,
872               ELHomeApplet *self)
873 {
874         /* TODO: avoid updating if not related */
875         add_new_idle (self);
876 }
877
878 static gboolean
879 scroll_anim_cb (ELHomeApplet *self)
880 {
881         ELHomeAppletPrivate *priv = self->priv;
882         gboolean to_continue;
883
884         priv->scroll_offset += SCROLL_STEP;
885         gtk_widget_queue_draw_area (GTK_WIDGET (self),
886                                     3*CONTENT_OFFSET_X,
887                                     HEADER_HEIGHT + CONTENT_OFFSET_Y_TOP,
888                                     MESSAGE_WIDTH,
889                                     C_HEIGHT - priv->received->allocation.height - HEADER_HEIGHT);
890
891         to_continue = priv->scroll_offset <= priv->hidden_message_height;
892         if (!to_continue) {
893                 priv->scroll_anim_id = 0;
894                 gtk_widget_hide (priv->cut_message);
895         }
896
897         return to_continue;
898 }
899
900 static gboolean
901 button_press_event_cb (GtkWidget      *widget,
902                        GdkEventButton *event,
903                        ELHomeApplet   *self)
904 {
905         g_warning (G_STRFUNC);
906         ELHomeAppletPrivate *priv = self->priv;
907
908         if (priv->event_id > 0) {
909                 if (event->y < CONTENT_OFFSET_Y_TOP + HEADER_HEIGHT) {
910                         if (priv->aggregator &&
911                             osso_abook_waitable_is_ready
912                             (OSSO_ABOOK_WAITABLE (priv->aggregator), NULL))
913                                 priv->active_header = TRUE;
914                 }
915                 else
916                         priv->active_body = TRUE;
917                 gtk_widget_queue_draw (widget);
918         }
919
920         return TRUE;
921 }
922
923 static GtkWidget*
924 create_contact_starter_dialog (OssoABookAggregator *aggregator, const gchar *contact_id)
925 {
926         GtkWidget *dialog = NULL;
927         GList *contacts = osso_abook_aggregator_lookup (aggregator, contact_id);
928         if (contacts && contacts->data) {
929                 GtkWidget *starter =
930                         osso_abook_touch_contact_starter_new_with_contact
931                         (NULL,
932                          OSSO_ABOOK_CONTACT (contacts->data));
933                 dialog = osso_abook_touch_contact_starter_dialog_new
934                         (NULL,
935                          OSSO_ABOOK_TOUCH_CONTACT_STARTER (starter));
936                 gtk_widget_show_all (starter);
937         }
938
939         g_list_free (contacts);
940
941         return dialog;
942 }
943
944 static GtkWidget*
945 create_temporary_contact_dialog (const gchar *remote_id,
946                                  const gchar *account_id)
947 {
948         GtkWidget *dialog = NULL;
949         const gchar *vcard = NULL;
950
951         if (account_id) {
952             vcard = osso_abook_account_manager_get_vcard_field (NULL, account_id);
953         }
954
955         if (vcard) {
956                 EVCardAttribute *attribute = e_vcard_attribute_new (NULL, vcard);
957
958                 e_vcard_attribute_add_value (attribute, remote_id);
959                 dialog = osso_abook_temporary_contact_dialog_new
960                         (NULL,
961                          NULL, /*EBook            *book,*/
962                          attribute,
963                          NULL /*McAccount        *account*/);
964                 g_signal_connect (dialog,
965                                   "response",
966                                   G_CALLBACK (gtk_widget_destroy),
967                                   NULL);
968                 e_vcard_attribute_free (attribute);
969         }
970
971         return dialog;
972 }
973
974 static gboolean
975 button_release_event_cb (GtkWidget      *widget,
976                          GdkEventButton *event,
977                          ELHomeApplet   *self)
978 {
979         ELHomeAppletPrivate *priv = self->priv;
980         g_warning (G_STRFUNC);
981
982         if (priv->active_body) {
983                 priv->active_body = FALSE;
984                 stop_scroll_anim (priv);
985                 if (priv->scroll_on_click) {
986                         priv->scroll_on_click = FALSE;
987                         priv->scroll_anim_id = g_timeout_add (SCROLL_PERIOD,
988                                                               (GSourceFunc)scroll_anim_cb,
989                                                               self);
990                 }
991                 else {
992 #ifndef DEBUG_LAYOUT
993                         mark_as_read (self);
994 #endif
995                 }
996
997                 gtk_widget_queue_draw (widget);
998         }
999         if (priv->active_header) {
1000                 GtkWidget *dialog = NULL;
1001                 priv->active_header = FALSE;
1002
1003                 if (priv->aggregator && priv->contact_id)
1004                         dialog = create_contact_starter_dialog
1005                                 (OSSO_ABOOK_AGGREGATOR (priv->aggregator),
1006                                  priv->contact_id);
1007                 if (!dialog &&
1008                     priv->remote_id &&
1009                     priv->local_id)
1010                         dialog = create_temporary_contact_dialog (priv->remote_id,
1011                                                                   priv->local_id);
1012
1013                 if (dialog)
1014                         gtk_widget_show (dialog);
1015
1016                 gtk_widget_queue_draw (widget);
1017         }
1018
1019         return TRUE;
1020 }
1021
1022 static gboolean
1023 leave_notify_event_cb (GtkWidget        *widget,
1024                        GdkEventCrossing *event,
1025                        ELHomeApplet     *self)
1026 {
1027         ELHomeAppletPrivate *priv = self->priv;
1028
1029         if (priv->active_body) {
1030                 priv->active_body = FALSE;
1031                 priv->active_header = FALSE;
1032                 stop_scroll_anim (priv);
1033                 gtk_widget_queue_draw (widget);
1034         }
1035         if (priv->active_header) {
1036                 priv->active_header = FALSE;
1037                 gtk_widget_queue_draw (widget);
1038         }
1039
1040         return FALSE;
1041 }
1042
1043 static void
1044 el_home_applet_init (ELHomeApplet *self)
1045 {
1046         ELHomeAppletPrivate *priv;
1047         GtkWidget *event_box;
1048         GtkWidget *hbox, *vbox, *align, *footer;
1049
1050         self->priv = EL_HOME_APPLET_GET_PRIVATE (self);
1051         priv = self->priv;
1052
1053         gtk_widget_set_app_paintable (GTK_WIDGET (self), TRUE);
1054
1055         priv->unread = gtk_label_new ("12");
1056         hildon_helper_set_logical_color (priv->unread,
1057                                          GTK_RC_FG,
1058                                          GTK_STATE_NORMAL,
1059                                          "ActiveTextColor");
1060         gtk_misc_set_alignment (GTK_MISC (priv->unread),
1061                                 1.0f,
1062                                 1.0f);
1063         hildon_helper_set_logical_font (priv->unread, "SmallSystemFont");
1064         /* gtk_widget_set_size_request (priv->unread, */
1065         /*                              -1, */
1066         /*                              HEADER_HEIGHT); */
1067 #if 0
1068         priv->icon = gtk_image_new_from_icon_name ("chat_unread_sms",
1069                                                    HILDON_ICON_SIZE_FINGER);
1070         gtk_misc_set_alignment (GTK_MISC (priv->icon),
1071                                 0.5f,
1072                                 0.5f);
1073 #endif
1074         priv->avatar = gtk_image_new ();
1075         gtk_misc_set_alignment (GTK_MISC (priv->avatar),
1076                                 0.5f,
1077                                 0.5f);
1078
1079         priv->sender = gtk_label_new ("asdf asdf asdf asdf asdf");
1080         gtk_misc_set_alignment (GTK_MISC (priv->sender),
1081                                 0.5f,
1082                                 0.55f);
1083         gtk_label_set_ellipsize (GTK_LABEL (priv->sender),
1084                                  PANGO_ELLIPSIZE_END);
1085         gtk_widget_set_name (priv->sender, "hildon-shadow-label");
1086         hildon_helper_set_logical_font (priv->sender, "SystemFont");
1087         gtk_widget_set_size_request (priv->sender,
1088                                      -1,
1089                                      HILDON_ICON_PIXEL_SIZE_THUMB);
1090
1091         priv->message = g_strdup ("One two three four five six seven eight nine ten");
1092
1093         /* TODO: l10n */
1094         priv->empty = gtk_label_new ("No new messages");
1095         gtk_widget_set_name (priv->empty, "hildon-shadow-label");
1096         GTK_WIDGET_SET_FLAGS (priv->empty, GTK_NO_SHOW_ALL);
1097
1098         priv->received = gtk_label_new ("aewf aewf aewf awef");
1099         gtk_misc_set_alignment (GTK_MISC (priv->received),
1100                                 1.0f,
1101                                 0.5f);
1102         hildon_helper_set_logical_font (priv->received, "SmallSystemFont");
1103         gtk_widget_set_name (priv->received, "hildon-shadow-label");
1104
1105
1106         priv->cut_message = gtk_label_new ("...");
1107         gtk_misc_set_alignment (GTK_MISC (priv->cut_message),
1108                                 0.5f,
1109                                 0.0f);
1110         hildon_helper_set_logical_font (priv->cut_message, "SmallSystemFont");
1111         gtk_widget_set_name (priv->cut_message, "hildon-shadow-label");
1112         GTK_WIDGET_SET_FLAGS (priv->cut_message, GTK_NO_SHOW_ALL);
1113
1114         hbox = gtk_hbox_new (FALSE, 0);
1115         /* gtk_box_pack_start (GTK_BOX (hbox), priv->icon, FALSE, FALSE, 0); */
1116         gtk_box_pack_start (GTK_BOX (hbox), priv->sender, TRUE, TRUE, 0);
1117         gtk_box_pack_start (GTK_BOX (hbox), priv->avatar, FALSE, FALSE, 0);
1118
1119         footer = gtk_hbox_new (FALSE, 0);
1120         gtk_box_pack_start (GTK_BOX (footer), priv->unread, FALSE, FALSE, 0);
1121         gtk_box_pack_start (GTK_BOX (footer), priv->cut_message, TRUE, TRUE, 0);
1122         gtk_box_pack_end (GTK_BOX (footer), priv->received, FALSE, FALSE, 0);
1123
1124         vbox = gtk_vbox_new (FALSE, 0);
1125         gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
1126         gtk_box_pack_start (GTK_BOX (vbox), priv->empty, TRUE, TRUE, 0);
1127         gtk_box_pack_end (GTK_BOX (vbox), footer, FALSE, FALSE, 0);
1128
1129         align = gtk_alignment_new (0.5f, 0.0f, 1.0f, 1.0f);
1130         gtk_alignment_set_padding (GTK_ALIGNMENT (align),
1131                                    0, 0, HILDON_MARGIN_DEFAULT, HILDON_MARGIN_DEFAULT);
1132
1133         gtk_container_set_border_width (GTK_CONTAINER (vbox), HILDON_MARGIN_HALF);
1134
1135         event_box = gtk_event_box_new ();
1136         gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
1137         gtk_widget_set_size_request (event_box, BOX_WIDTH, BOX_HEIGHT);
1138
1139         gtk_container_add (GTK_CONTAINER (align), vbox);
1140         gtk_container_add (GTK_CONTAINER (event_box), align);
1141         gtk_container_add (GTK_CONTAINER (self), event_box);
1142
1143         g_signal_connect (event_box,
1144                           "button-press-event",
1145                           G_CALLBACK (button_press_event_cb),
1146                           self);
1147         g_signal_connect (event_box,
1148                           "button-release-event",
1149                           G_CALLBACK (button_release_event_cb),
1150                           self);
1151         g_signal_connect (event_box,
1152                           "leave-notify-event",
1153                           G_CALLBACK (leave_notify_event_cb),
1154                           self);
1155
1156         g_signal_connect (event_box,
1157                           "style-set",
1158                           G_CALLBACK (style_set_cb),
1159                           self);
1160         g_signal_connect (self,
1161                           "notify::is-on-current-desktop",
1162                           G_CALLBACK (notify_on_current_desktop),
1163                           self);
1164
1165         gtk_widget_show_all (GTK_WIDGET (event_box));
1166
1167 #ifndef DEBUG_LAYOUT
1168         priv->eventlogger = rtcom_el_new ();
1169         g_signal_connect (priv->eventlogger,
1170                           "new-event",
1171                           G_CALLBACK (new_event_cb),
1172                           self);
1173         g_signal_connect (priv->eventlogger,
1174                           "event-updated",
1175                           G_CALLBACK (new_event_cb),
1176                           self);
1177
1178         read_new_event (self);
1179
1180         osso_abook_init_with_name (PACKAGE, NULL);
1181 #endif
1182 }
1183
1184 static void
1185 el_home_applet_class_init (ELHomeAppletClass *klass)
1186 {
1187         GObjectClass *object_class = G_OBJECT_CLASS (klass);
1188         GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1189
1190         object_class->dispose = dispose;
1191         object_class->finalize = finalize;
1192         widget_class->expose_event = expose_event;
1193         widget_class->realize = el_home_applet_realize;
1194
1195         g_type_class_add_private (klass, sizeof (ELHomeAppletPrivate));
1196 }
1197