6254576a94b54b4904570a7db8ff9ad494880601
[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 #include <libintl.h>
26 #include <hildon/hildon.h>
27 #include <rtcom-eventlogger/eventlogger.h>
28 #include <sqlite3.h>
29 #include <string.h>
30 #include <gconf/gconf-client.h>
31 #include <libosso-abook/osso-abook-init.h>
32 #include <libosso-abook/osso-abook-aggregator.h>
33 #include <libosso-abook/osso-abook-contact.h>
34 #include <libosso-abook/osso-abook-waitable.h>
35 #include <libosso-abook/osso-abook-presence.h>
36 #include <libosso-abook/osso-abook-touch-contact-starter.h>
37 #include <libosso-abook/osso-abook-temporary-contact-dialog.h>
38 #include <libosso-abook/osso-abook-account-manager.h>
39
40 #include <telepathy-glib/interfaces.h>
41 #include <telepathy-glib/dbus.h>
42 #include <rtcom-telepathy-glib/extensions.h>
43
44 #define EL_HOME_APPLET_GET_PRIVATE(obj) ( \
45         G_TYPE_INSTANCE_GET_PRIVATE (obj, \
46                 EL_TYPE_HOME_APPLET, ELHomeAppletPrivate))
47
48 #define BOX_WIDTH 352
49 #define BOX_HEIGHT 284
50
51 #define CONTENT_OFFSET_X HILDON_MARGIN_HALF
52 #define CONTENT_OFFSET_Y_TOP 4*HILDON_MARGIN_HALF
53 #define CONTENT_OFFSET_Y_BOTTOM HILDON_MARGIN_HALF
54 #define C_WIDTH (BOX_WIDTH - 2*CONTENT_OFFSET_X)
55 #define C_HEIGHT (BOX_HEIGHT - (CONTENT_OFFSET_Y_TOP + CONTENT_OFFSET_Y_BOTTOM))
56
57 #define HEADER_HEIGHT 48
58 #define FOOTER_HEIGHT 24
59 #define FOOTER_HEIGHT_PRESS FOOTER_HEIGHT*2 /* approx, used only for checking clicks */
60 #define FOOTER_WIDTH C_WIDTH/4
61 #define FOOTER_WIDTH_PRESS (FOOTER_WIDTH + FOOTER_WIDTH/2) /* approx, used only for checking clicks, bigger than controls */
62
63 #define MESSAGE_HEIGHT (C_HEIGHT - HEADER_HEIGHT - FOOTER_HEIGHT)
64 #define MESSAGE_WIDTH (C_WIDTH - 2*HILDON_MARGIN_DEFAULT)
65
66 #define AVATAR_SIZE HILDON_ICON_PIXEL_SIZE_THUMB
67
68 #define AVATAR_X (C_WIDTH - AVATAR_SIZE - HILDON_MARGIN_DEFAULT)
69 #define AVATAR_Y 3*HILDON_MARGIN_HALF
70
71 #define BOX_RADIOUS 20
72
73 #define SCROLL_PERIOD 100 /* ms */
74 #define SCROLL_STEP 1 /* pixel */
75 #define TEXT_Y_OFFSET (HEADER_HEIGHT + HILDON_MARGIN_HALF)
76
77 #define NOTIFICATION_UI_DBUS_NAME     "org.freedesktop.Telepathy.Client.NotificationUI"
78 #define NOTIFICATION_UI_DBUS_PATH     "/org/freedesktop/Telepathy/Client/NotificationUI"
79 #define NOTIFICATION_UI_DBUS_IFACE    "com.nokia.RtcomNotificationUi"
80
81 #define CONVERSATIONS_UI_DBUS_NAME     "com.nokia.MessagingUI"
82 #define CONVERSATIONS_UI_DBUS_PATH     "/com/nokia/MessagingUI"
83 #define CONVERSATIONS_UI_DBUS_IFACE    "com.nokia.MessagingUI"
84
85 static const gchar *conv_services[] = {"RTCOM_EL_SERVICE_SMS",
86                                        "RTCOM_EL_SERVICE_CHAT",
87                                        NULL};
88 static const gchar *conv_event_types[] = {"RTCOM_EL_EVENTTYPE_SMS_INBOUND",
89                                           "RTCOM_EL_EVENTTYPE_CHAT_INBOUND",
90                                           NULL};
91
92 typedef enum {
93         SELECTED_NONE,
94         SELECTED_HEADER,
95         SELECTED_BODY,
96         SELECTED_FOOTER
97 } WidgetActiveSelection;
98
99 struct _ELHomeAppletPrivate
100 {
101         RTComEl *eventlogger;
102
103         GtkWidget *sender;
104         GtkWidget *icon;
105         GtkWidget *unread;
106         GtkWidget *received;
107         GtkWidget *cut_message;
108
109         /* empty view*/
110         GtkWidget *empty;
111         GtkWidget *sms_total;
112         GtkWidget *chat_total;
113
114         gchar *message;
115         gint event_id;
116
117         WidgetActiveSelection active;
118
119         guint unread_count;
120
121         struct {
122                 float red;
123                 float green;
124                 float blue;
125         } active_color;
126         PangoFontDescription *font_desc;
127
128         GdkPixbuf *avatar_pixbuf;
129         GdkPixbuf *presence_pixbuf;
130
131         guint idle_id;
132
133         cairo_surface_t *message_surface;
134
135         gboolean scroll_on_click;
136         gint scroll_offset;
137         gint hidden_message_height;
138         guint scroll_anim_id;
139
140         OssoABookRoster *aggregator;
141         OssoABookWaitableClosure *aggregator_ready_closure;
142         gchar *contact_id;
143         gchar *remote_id;
144         gchar *local_id;
145         gchar *group_uid;
146         OssoABookContact *contact;
147
148         gboolean time_fmt_24h;
149
150         guint init_timer;
151 };
152
153 HD_DEFINE_PLUGIN_MODULE (ELHomeApplet, el_home_applet, HD_TYPE_HOME_PLUGIN_ITEM);
154
155 const gchar* g_module_check_init (GModule *module);
156 const gchar*
157 g_module_check_init (GModule *module)
158 {
159         g_module_make_resident (module);
160         return NULL;
161 }
162
163 static void
164 el_home_applet_class_finalize (ELHomeAppletClass *klass)
165 {
166 }
167
168 static void
169 el_home_applet_realize (GtkWidget *widget)
170 {
171         GdkScreen *screen;
172
173         screen = gtk_widget_get_screen (widget);
174         gtk_widget_set_colormap (widget,
175                                  gdk_screen_get_rgba_colormap (screen));
176
177         gtk_widget_set_app_paintable (widget,
178                                       TRUE);
179
180         GTK_WIDGET_CLASS (el_home_applet_parent_class)->realize (widget);
181 }
182
183 enum {
184         ROUND_CORNER_TL = 1,
185         ROUND_CORNER_TR = 1<<1,
186         ROUND_CORNER_BL = 1<<2,
187         ROUND_CORNER_BR = 1<<3,
188         ROUND_CORNER_ALL = ROUND_CORNER_TL | ROUND_CORNER_TR |
189                            ROUND_CORNER_BL | ROUND_CORNER_BR
190 };
191
192 /**
193  * Draw rectangle with optional round corners.
194  *
195  * @x
196  * @y
197  * @w width
198  * @h height
199  * @r round corner radious
200  * @round_corners define which corners draw round, ROUND_CORNER_TL,
201  *                ROUND_CORNER_TR, ROUND_CORNER_BL, ROUND_CORNER_BR
202  */
203 static void
204 rounded_rectangle (cairo_t *cr,
205                    double x,
206                    double y,
207                    double w,
208                    double h,
209                    double r,
210                    guint round_corners)
211 {
212         if (round_corners & ROUND_CORNER_TL)
213                 cairo_move_to (cr, x + r, y);
214         else
215                 cairo_move_to (cr, x, y);
216
217         if (round_corners & ROUND_CORNER_TR) {
218                 cairo_line_to (cr, x + w - r, y);
219                 cairo_rel_curve_to (cr,
220                                     r, 0,
221                                     r, 0,
222                                     r, r);
223         }
224         else
225                 cairo_line_to (cr, x + w, y);
226
227         if (round_corners & ROUND_CORNER_BR) {
228                 cairo_line_to (cr, x + w, y + h - r);
229                 cairo_rel_curve_to (cr,
230                                     0, r,
231                                     0, r,
232                                     -r, r);
233         }
234         else
235                 cairo_line_to (cr, x + w, y + h);
236
237         if (round_corners & ROUND_CORNER_BL) {
238                 cairo_line_to (cr, x + r, y + h);
239                 cairo_rel_curve_to (cr,
240                                     -r, 0,
241                                     -r, 0,
242                                     -r, -r);
243         }
244         else
245                 cairo_line_to (cr, x, y + h);
246
247         if (round_corners & ROUND_CORNER_TL) {
248                 cairo_line_to (cr, x, y + r);
249                 cairo_rel_curve_to (cr,
250                                     0, -r,
251                                     0, -r,
252                                     r, -r);
253         }
254         else
255                 cairo_line_to (cr, x, y);
256 }
257
258 static cairo_surface_t*
259 draw_text (cairo_t              *cr,
260            PangoFontDescription *desc,
261            const gchar          *text,
262            gint                  width,
263            gint                 *height)
264 {
265         PangoLayout *layout;
266         PangoRectangle extent;
267
268         cairo_surface_t *gdk_surface, *result_surface;
269         cairo_t *msg_cr;
270
271         /* Create a PangoLayout, set the font and text */
272         layout = pango_cairo_create_layout (cr);
273         pango_layout_set_text (layout,
274                                text,
275                                -1);
276         pango_layout_set_font_description (layout, desc);
277
278         pango_layout_set_wrap (layout, PANGO_WRAP_WORD_CHAR);
279         pango_layout_set_width (layout, PANGO_SCALE*width);
280
281         pango_layout_get_pixel_extents (layout, NULL, &extent);
282         *height = extent.height;
283
284         gdk_surface = cairo_get_target (cr);
285         result_surface = cairo_surface_create_similar
286                 (gdk_surface,
287                  CAIRO_CONTENT_COLOR_ALPHA,
288                  width,
289                  extent.height);
290         msg_cr = cairo_create (result_surface);
291
292         pango_cairo_update_layout (msg_cr, layout);
293         /* draw shadow */
294         cairo_move_to (msg_cr, 1, 1);
295         cairo_set_source_rgba (msg_cr, 0.2, 0.2, 0.2, 0.8);
296         pango_cairo_show_layout (msg_cr, layout);
297
298         /* draw fg */
299         cairo_move_to (msg_cr, 0, 0);
300         cairo_set_source_rgba (msg_cr, 1.0, 1.0, 1.0, 1.0);
301         pango_cairo_show_layout (msg_cr, layout);
302
303         cairo_destroy (msg_cr);
304         g_object_unref (layout);
305
306         return result_surface;
307 }
308
309 static gboolean
310 stop_scroll_anim (ELHomeAppletPrivate *priv)
311 {
312         gboolean result = priv->scroll_anim_id > 0;
313
314         if (result) {
315                 g_source_remove (priv->scroll_anim_id);
316                 priv->scroll_anim_id = 0;
317                 priv->scroll_on_click = FALSE;
318                 gtk_widget_hide (priv->cut_message);
319         }
320
321         return result;
322 }
323
324 static void
325 style_set_cb (GtkWidget *widget,
326               GtkStyle  *previous_style,
327               ELHomeApplet *self)
328 {
329         ELHomeAppletPrivate *priv = self->priv;
330         GdkColor color;
331         GtkStyle *font_style;
332
333         font_style = gtk_rc_get_style_by_paths (gtk_widget_get_settings (widget),
334                                                 "SystemFont",
335                                                 NULL,
336                                                 G_TYPE_NONE);
337         if (font_style && font_style->font_desc) {
338                 if (priv->font_desc)
339                         pango_font_description_free (priv->font_desc);
340                 priv->font_desc = pango_font_description_copy (font_style->font_desc);
341         }
342
343         if (gtk_style_lookup_color (widget->style,
344                                     "ActiveTextColor",
345                                     &color)) {
346                 priv->active_color.red = color.red/(float)G_MAXUINT16;
347                 priv->active_color.green = color.green/(float)G_MAXUINT16;
348                 priv->active_color.blue = color.blue/(float)G_MAXUINT16;
349         }
350 }
351
352 static void
353 reset_scroll (ELHomeApplet *self)
354 {
355         ELHomeAppletPrivate *priv = self->priv;
356
357         if (stop_scroll_anim (self->priv)) {
358                 priv->scroll_on_click = TRUE;/* priv->scroll_offset; */
359                 priv->scroll_offset = 0;
360                 if (priv->scroll_on_click)
361                         gtk_widget_show (priv->cut_message);
362         }
363 }
364
365 static void
366 notify_on_current_desktop (GObject      *object,
367                            GParamSpec   *unused G_GNUC_UNUSED,
368                            ELHomeApplet *self)
369 {
370         gboolean on;
371
372         g_object_get (object, "is-on-current-desktop", &on, NULL);
373         if (!on) {
374                 reset_scroll (self);
375                 gtk_widget_queue_draw (GTK_WIDGET (self));
376         }
377 }
378
379 static gboolean
380 expose_event (GtkWidget *self, GdkEventExpose *event)
381 {
382         ELHomeAppletPrivate *priv = EL_HOME_APPLET(self)->priv;
383         cairo_t *cr;
384         cairo_pattern_t *grad;
385
386         cr = gdk_cairo_create (self->window);
387         gdk_cairo_region (cr, event->region);
388         cairo_clip (cr);
389
390         cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
391
392         /* draw bound box */
393         cairo_set_source_rgba (cr, 0.4f, 0.4f, 0.4f, 0.1f);
394         cairo_set_line_width (cr, 3.0f);
395
396         rounded_rectangle (cr,
397                            CONTENT_OFFSET_X,
398                            CONTENT_OFFSET_Y_TOP,
399                            BOX_WIDTH - 2*CONTENT_OFFSET_X,
400                            BOX_HEIGHT - (CONTENT_OFFSET_Y_TOP + CONTENT_OFFSET_Y_BOTTOM),
401                            BOX_RADIOUS,
402                            ROUND_CORNER_ALL);
403
404         cairo_close_path (cr);
405         cairo_stroke (cr);
406
407         /* draw header */
408         cairo_set_line_width (cr, 1.0f);
409
410         cairo_translate (cr, CONTENT_OFFSET_X, CONTENT_OFFSET_Y_TOP);
411         rounded_rectangle (cr,
412                            0, 0,
413                            C_WIDTH, HEADER_HEIGHT,
414                            BOX_RADIOUS,
415                            ROUND_CORNER_TL | ROUND_CORNER_TR);
416         cairo_close_path (cr);
417
418         switch (priv->active) {
419         case SELECTED_HEADER:
420                 cairo_set_source_rgba (cr,
421                                        priv->active_color.red,
422                                        priv->active_color.green,
423                                        priv->active_color.blue,
424                                        0.8f);
425                 break;
426         default:
427                 cairo_set_source_rgba (cr, 0.2f, 0.2f, 0.2f, 0.8f);
428         }
429
430         cairo_fill (cr);
431
432         cairo_move_to (cr, 0, HEADER_HEIGHT);
433         cairo_line_to (cr, C_WIDTH, HEADER_HEIGHT);
434         cairo_set_source_rgba (cr,
435                                priv->active_color.red,
436                                priv->active_color.green,
437                                priv->active_color.blue,
438                                1.0f);
439         cairo_stroke (cr);
440
441         /* draw body */
442         if (!priv->message) {
443                 rounded_rectangle (cr,
444                                    0, HEADER_HEIGHT,
445                                    C_WIDTH, C_HEIGHT,
446                                    BOX_RADIOUS,
447                                    ROUND_CORNER_BL | ROUND_CORNER_BR);
448                 cairo_close_path (cr);
449         }
450         else
451                 cairo_rectangle (cr, 0, HEADER_HEIGHT,
452                                  C_WIDTH, MESSAGE_HEIGHT);
453
454         /* draw body filling depending on (in)active state */
455         grad = cairo_pattern_create_linear (0, HEADER_HEIGHT,
456                                             0, C_HEIGHT - FOOTER_HEIGHT);
457
458         switch (priv->active) {
459         case SELECTED_BODY:
460                 cairo_pattern_add_color_stop_rgba (grad,
461                                                    0.5f,
462                                                    priv->active_color.red,
463                                                    priv->active_color.green,
464                                                    priv->active_color.blue,
465                                                    0.8f);
466                 cairo_pattern_add_color_stop_rgba (grad,
467                                                    1.0f,
468                                                    priv->active_color.red/2,
469                                                    priv->active_color.green/2,
470                                                    priv->active_color.blue/2,
471                                                    0.8f);
472                 break;
473         default:
474                 cairo_pattern_add_color_stop_rgba (grad, 0.5f,
475                                                    0.4f, 0.4f, 0.4f, 0.8f);
476                 cairo_pattern_add_color_stop_rgba (grad, 1.0f,
477                                                    0.2f, 0.2f, 0.2f, 0.8f);
478         }
479
480         cairo_set_source (cr, grad);
481         cairo_fill (cr);
482
483         cairo_pattern_destroy (grad);
484
485         /* draw avatar */
486         if (priv->avatar_pixbuf) {
487                 rounded_rectangle (cr,
488                                    AVATAR_X, -AVATAR_Y,
489                                    AVATAR_SIZE, AVATAR_SIZE,
490                                    BOX_RADIOUS,
491                                    ROUND_CORNER_ALL);
492                 cairo_close_path (cr);
493
494                 gdk_cairo_set_source_pixbuf (cr,
495                                              priv->avatar_pixbuf,
496                                              AVATAR_X,
497                                              -AVATAR_Y);
498                 cairo_fill_preserve (cr);
499
500                 cairo_set_source_rgba (cr,
501                                        priv->active_color.red,
502                                        priv->active_color.green,
503                                        priv->active_color.blue,
504                                        1.0f);
505                 cairo_stroke (cr);
506         }
507         if (priv->presence_pixbuf) {
508                 guint x = C_WIDTH - HILDON_ICON_PIXEL_SIZE_XSMALL - HILDON_MARGIN_DEFAULT;
509                 guint y = (HEADER_HEIGHT - HILDON_ICON_PIXEL_SIZE_XSMALL)/2;
510
511                 if (priv->avatar_pixbuf)
512                         x -= AVATAR_SIZE + HILDON_MARGIN_DEFAULT;
513
514                 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
515                 gdk_cairo_set_source_pixbuf (cr,
516                                              priv->presence_pixbuf,
517                                              x,
518                                              y);
519                 cairo_paint (cr);
520         }
521
522         if (priv->message) {
523                 /* draw footer unread part bg */
524                 rounded_rectangle (cr,
525                                    0, C_HEIGHT - FOOTER_HEIGHT,
526                                    FOOTER_WIDTH, FOOTER_HEIGHT,
527                                    BOX_RADIOUS,
528                                    ROUND_CORNER_BL);
529                 cairo_close_path (cr);
530
531                 /* draw body filling depending on (in)active state */
532                 switch (priv->active) {
533                 case SELECTED_FOOTER:
534                         cairo_set_source_rgba (cr,
535                                                priv->active_color.red,
536                                                priv->active_color.green,
537                                                priv->active_color.blue,
538                                                0.8f);
539                         break;
540                 default:
541                         cairo_set_source_rgba (cr, 0.1f, 0.1f, 0.1f, 0.9f);
542                 }
543                 cairo_fill (cr);
544
545                 /* draw footer received part bg */
546                 rounded_rectangle (cr,
547                                    FOOTER_WIDTH, C_HEIGHT - FOOTER_HEIGHT,
548                                    C_WIDTH - FOOTER_WIDTH, FOOTER_HEIGHT,
549                                    BOX_RADIOUS,
550                                    ROUND_CORNER_BR);
551                 cairo_close_path (cr);
552
553                 cairo_set_source_rgba (cr, 0.2f, 0.2f, 0.2f, 0.8f);
554                 cairo_fill (cr);
555
556                 /* draw message */
557                 if (!priv->message_surface) {
558                         gint height;
559
560                         priv->message_surface = draw_text (cr,
561                                                            priv->font_desc,
562                                                            priv->message,
563                                                            MESSAGE_WIDTH,
564                                                            &height);
565
566                         priv->hidden_message_height = height - MESSAGE_HEIGHT;
567                         priv->scroll_on_click = priv->hidden_message_height > 0;
568                         if (priv->scroll_on_click)
569                                 gtk_widget_show (priv->cut_message);
570                 }
571
572                 cairo_rectangle (cr,
573                                  2*CONTENT_OFFSET_X,
574                                  TEXT_Y_OFFSET,
575                                  MESSAGE_WIDTH,
576                                  MESSAGE_HEIGHT);
577                 cairo_clip (cr);
578
579                 cairo_set_source_surface (cr,
580                                           priv->message_surface,
581                                           2*CONTENT_OFFSET_X,
582                                           TEXT_Y_OFFSET - priv->scroll_offset);
583                 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
584                 cairo_paint (cr);
585         }
586
587         cairo_destroy (cr);
588
589         return GTK_WIDGET_CLASS (el_home_applet_parent_class)->expose_event (self, event);
590 }
591
592 static void
593 resize_sender (ELHomeAppletPrivate *priv)
594 {
595         guint width = C_WIDTH;
596
597         if (priv->avatar_pixbuf) {
598                 width -= AVATAR_SIZE + HILDON_MARGIN_DEFAULT;
599         }
600
601         if (priv->presence_pixbuf) {
602                 width -= HILDON_ICON_PIXEL_SIZE_XSMALL + HILDON_MARGIN_DEFAULT;
603         }
604
605         gtk_widget_set_size_request (priv->sender,
606                                      width,
607                                      HILDON_ICON_PIXEL_SIZE_THUMB);
608 }
609
610 static void
611 update_presence_pixbuf (ELHomeApplet *self,
612                         OssoABookPresence *presence)
613 {
614         ELHomeAppletPrivate *priv = self->priv;
615         const gchar *icon_name = osso_abook_presence_get_icon_name (presence);
616         gboolean resize = !!priv->presence_pixbuf ^ !!icon_name; /* logical via bit XOR */
617
618         if (priv->presence_pixbuf) {
619                 g_object_unref (priv->presence_pixbuf);
620                 priv->presence_pixbuf = NULL;
621         }
622
623         if (icon_name)
624                 priv->presence_pixbuf = gtk_icon_theme_load_icon
625                         (gtk_icon_theme_get_default (),
626                          icon_name,
627                          HILDON_ICON_PIXEL_SIZE_XSMALL,
628                          0, NULL);
629         if (resize)
630                 resize_sender (priv);
631
632         gtk_widget_queue_draw (GTK_WIDGET (self));
633 }
634
635 static void
636 presence_updated (OssoABookPresence *presence,
637                   GParamSpec *spec,
638                   gpointer *user_data)
639 {
640         ELHomeApplet *self = EL_HOME_APPLET(user_data);
641
642         if (!OSSO_ABOOK_IS_CONTACT(self->priv->contact))
643                 return;
644
645         update_presence_pixbuf (self, presence);
646 }
647
648 static void
649 show_contact (ELHomeApplet *self)
650 {
651         ELHomeAppletPrivate *priv = self->priv;
652
653         g_return_if_fail (priv->contact);
654
655         gtk_label_set_text (GTK_LABEL (priv->sender),
656                             osso_abook_contact_get_display_name (priv->contact));
657         resize_sender (priv);
658         gtk_widget_queue_draw (GTK_WIDGET (self));
659 }
660
661 static void
662 resolve_contact (ELHomeApplet *self)
663 {
664         ELHomeAppletPrivate *priv = self->priv;
665         GList *contacts = NULL;
666
667         if (priv->contact_id) {
668                 contacts = osso_abook_aggregator_lookup
669                         (OSSO_ABOOK_AGGREGATOR (priv->aggregator),
670                          priv->contact_id);
671         }
672         else if (priv->local_id && priv->remote_id) {
673                 if (g_strcmp0 (priv->local_id, "ring/tel/ring" == 0)) {
674                         contacts = osso_abook_aggregator_find_contacts_for_phone_number
675                                 (OSSO_ABOOK_AGGREGATOR (priv->aggregator),
676                                  priv->remote_id,
677                                  TRUE);
678                 }
679                 else {
680                         McAccount *account;
681                         account = osso_abook_account_manager_lookup_by_name
682                                 (NULL,
683                                  priv->local_id);
684                         if (account) {
685                                 contacts = osso_abook_aggregator_find_contacts_for_im_contact
686                                         (OSSO_ABOOK_AGGREGATOR (priv->aggregator),
687                                          priv->remote_id,
688                                          account);
689                         }
690                 }
691         }
692
693         if (contacts && contacts->data) {
694                 priv->contact = g_object_ref (OSSO_ABOOK_CONTACT (contacts->data));
695                 g_signal_connect (priv->contact,
696                                   "notify::presence-status",
697                                   G_CALLBACK (presence_updated),
698                                   self);
699                 priv->avatar_pixbuf = osso_abook_avatar_get_image_scaled
700                         (OSSO_ABOOK_AVATAR (priv->contact),
701                          HILDON_ICON_PIXEL_SIZE_THUMB,
702                          HILDON_ICON_PIXEL_SIZE_THUMB,
703                          TRUE);
704                 update_presence_pixbuf (self,
705                                         OSSO_ABOOK_PRESENCE (priv->contact));
706                 show_contact (self);
707         }
708 }
709
710 static void
711 contacts_added (OssoABookRoster  *roster,
712                 OssoABookContact **contacts,
713                 gpointer          userdata)
714 {
715         ELHomeApplet *self = EL_HOME_APPLET (userdata);
716         ELHomeAppletPrivate *priv = self->priv;
717
718         if (!priv->contact)
719                 resolve_contact (self);
720 }
721
722 static void
723 reset_contact (ELHomeApplet *self)
724 {
725         ELHomeAppletPrivate *priv = self->priv;
726
727         if (priv->avatar_pixbuf) {
728                 g_object_unref (priv->avatar_pixbuf);
729                 priv->avatar_pixbuf = NULL;
730         }
731
732         if (priv->presence_pixbuf) {
733                 g_object_unref (priv->presence_pixbuf);
734                 priv->presence_pixbuf = NULL;
735         }
736
737         if (priv->contact) {
738                 g_signal_handlers_disconnect_by_func (priv->contact,
739                                                       presence_updated,
740                                                       self);
741                 g_object_unref (priv->contact);
742                 priv->contact = NULL;
743         }
744
745         resize_sender (priv);
746 }
747
748 static void
749 contacts_removed (OssoABookRoster *roster,
750                   const gchar     **ids,
751                   gpointer         userdata)
752 {
753         ELHomeApplet *self = EL_HOME_APPLET (userdata);
754         ELHomeAppletPrivate *priv = self->priv;
755
756         if (priv->contact) {
757                 const gchar **contact_id;
758                 const gchar *uid = osso_abook_contact_get_uid (priv->contact);
759
760                 for (contact_id = ids; *contact_id; contact_id++) {
761                         if (strcmp (*contact_id, priv->contact_id) == 0) {
762                                 reset_contact (self);
763
764                                 gtk_widget_queue_draw (GTK_WIDGET (self));
765                                 return;
766                         }
767                         if (strcmp (*contact_id, uid) == 0) {
768                                 reset_contact (self);
769                                 resolve_contact (self);
770                                 gtk_widget_queue_draw (GTK_WIDGET (self));
771                                 return;
772                         }
773                 }
774         }
775 }
776
777 static void
778 clean_contact (ELHomeApplet *self)
779 {
780         ELHomeAppletPrivate *priv = self->priv;
781
782         reset_contact (self);
783
784         if (priv->aggregator) {
785                 if (priv->aggregator_ready_closure){
786                         osso_abook_waitable_cancel (OSSO_ABOOK_WAITABLE (priv->aggregator),
787                                                     priv->aggregator_ready_closure);
788                         priv->aggregator_ready_closure = NULL;
789                 }
790                 g_signal_handlers_disconnect_by_func (priv->aggregator,
791                                                       contacts_added,
792                                                       self);
793                 g_signal_handlers_disconnect_by_func (priv->aggregator,
794                                                       contacts_removed,
795                                                       self);
796                 osso_abook_roster_stop (priv->aggregator);
797                 g_object_unref (priv->aggregator);
798                 priv->aggregator = NULL;
799         }
800 }
801
802 static void
803 clean_state (ELHomeApplet *self)
804 {
805         ELHomeAppletPrivate *priv = self->priv;
806
807         if (priv->message) {
808                 g_free (priv->message);
809                 priv->message = NULL;
810         }
811
812         if (priv->contact_id) {
813                 g_free (priv->contact_id);
814                 priv->contact_id = NULL;
815         }
816         if (priv->local_id) {
817                 g_free (priv->local_id);
818                 priv->local_id = NULL;
819         }
820         if (priv->remote_id) {
821                 g_free (priv->remote_id);
822                 priv->remote_id = NULL;
823         }
824         if (priv->group_uid) {
825                 g_free (priv->group_uid);
826                 priv->group_uid = NULL;
827         }
828 }
829
830 static void
831 dispose (GObject *self)
832 {
833         ELHomeAppletPrivate *priv = EL_HOME_APPLET(self)->priv;
834
835         if (priv->init_timer) {
836                 g_source_remove (priv->init_timer);
837                 priv->init_timer = 0;
838         }
839
840         stop_scroll_anim (priv);
841         if (priv->idle_id) {
842                 g_source_remove (priv->idle_id);
843                 priv->idle_id = 0;
844         }
845         if (priv->eventlogger) {
846                 g_object_unref (priv->eventlogger);
847                 priv->eventlogger = NULL;
848         }
849         if (priv->font_desc) {
850                 pango_font_description_free (priv->font_desc);
851                 priv->font_desc = NULL;
852         }
853
854         clean_state (EL_HOME_APPLET (self));
855         clean_contact (EL_HOME_APPLET (self));
856
857         G_OBJECT_CLASS (el_home_applet_parent_class)->dispose (self);
858 }
859
860 static void
861 finalize (GObject *self)
862 {
863         G_OBJECT_CLASS (el_home_applet_parent_class)->finalize (self);
864 }
865
866 static void
867 aggregator_ready_cb (OssoABookWaitable *waitable,
868                      const GError      *error,
869                      gpointer           userdata)
870 {
871         ELHomeApplet *self = EL_HOME_APPLET (userdata);
872         ELHomeAppletPrivate *priv = self->priv;
873
874         priv->aggregator_ready_closure = NULL;
875
876         if (error) {
877                 g_warning ("Failed to create aggregator: %s", error->message);
878                 return;
879         }
880
881         g_signal_connect (priv->aggregator,
882                           "contacts-added",
883                           G_CALLBACK (contacts_added),
884                           self);
885         g_signal_connect (priv->aggregator,
886                           "contacts-removed",
887                           G_CALLBACK (contacts_removed),
888                           self);
889
890         resolve_contact (self);
891 }
892
893 static void
894 start_aggregator (ELHomeApplet *self)
895 {
896         ELHomeAppletPrivate *priv = self->priv;
897         EBookQuery *query = NULL;
898         GError *error = NULL;
899
900         if (priv->local_id && priv->remote_id) {
901                 const gchar *vcard = osso_abook_account_manager_get_vcard_field
902                         (NULL, priv->local_id);
903                 if (vcard)
904                         query = e_book_query_vcard_field_test (vcard,
905                                                                E_BOOK_QUERY_IS,
906                                                                priv->remote_id);
907                 else
908                         query = e_book_query_any_field_contains (priv->remote_id);
909         }
910
911         if (query) {
912                 priv->aggregator = osso_abook_aggregator_new_with_query (NULL,
913                                                                          query,
914                                                                          NULL,
915                                                                          1,
916                                                                          &error);
917                 e_book_query_unref (query);
918         }
919         if (error) {
920                 g_warning ("Failed to create aggregator: %s", error->message);
921                 g_error_free (error);
922                 return;
923         }
924
925         if (priv->aggregator) {
926                 priv->aggregator_ready_closure = osso_abook_waitable_call_when_ready
927                         (OSSO_ABOOK_WAITABLE (priv->aggregator),
928                          aggregator_ready_cb,
929                          self, NULL);
930
931                 osso_abook_roster_start (priv->aggregator);
932         }
933 }
934
935 static gchar*
936 format_time (time_t t, gboolean time_fmt_24h)
937 {
938         static const guint RESULT_SIZE = 64;
939
940         time_t now;
941         struct tm now_tm, t_tm;
942         const gchar *time_format;
943         gchar *result = g_malloc0 (RESULT_SIZE);
944
945         now = time (NULL);
946         localtime_r (&now, &now_tm);
947         localtime_r (&t, &t_tm);
948
949         if (time_fmt_24h)
950                 time_format = "wdgt_va_24h_time";
951         else
952                 time_format = now_tm.tm_hour > 11 ?
953                         "wdgt_va_12h_time_pm":
954                         "wdgt_va_12h_time_am";
955
956         if ((now_tm.tm_year == t_tm.tm_year) &&
957             (now_tm.tm_mon  == t_tm.tm_mon) &&
958             (now_tm.tm_mday == t_tm.tm_mday))
959                 strftime (result,
960                           RESULT_SIZE,
961                           dgettext ("hildon-libs", time_format),
962                           &t_tm);
963         else {
964                 gchar *full_format = g_strdup_printf ("%s %s",
965                                                       dgettext ("hildon-libs", "wdgt_va_date"),
966                                                       dgettext ("hildon-libs", time_format));
967                 strftime (result, RESULT_SIZE, full_format, &t_tm);
968                 g_free (full_format);
969         }
970
971         return result;
972 }
973
974 static void
975 show_event (ELHomeApplet *self, RTComElIter *it)
976 {
977         ELHomeAppletPrivate *priv = self->priv;
978         const gchar *remote = NULL;
979         gchar *received = NULL;
980         GValueArray *event = NULL;
981
982         if (it && rtcom_el_iter_first (it)) {
983
984                 event = rtcom_el_iter_get_valuearray (it,
985                                                       "id",
986                                                       "start-time",
987                                                       "local-uid",
988                                                       "remote-uid",
989                                                       "remote-name",
990                                                       "remote-ebook-uid",
991                                                       "free-text",
992                                                       "group-uid",
993                                                       NULL);
994                 if (event) {
995                         time_t received_t;
996 #define _VARR_DUP_STR(array, i) g_value_dup_string (g_value_array_get_nth ((array), (i)))
997
998                         priv->event_id = g_value_get_int (g_value_array_get_nth (event, 0));
999                         received_t = g_value_get_int (g_value_array_get_nth (event, 1));
1000                         received = format_time (received_t, priv->time_fmt_24h);
1001                         priv->local_id = _VARR_DUP_STR (event, 2);
1002                         priv->remote_id = _VARR_DUP_STR (event, 3);
1003                         if (priv->remote_id && priv->remote_id[0]) {
1004                                 remote = g_value_get_string (g_value_array_get_nth (event, 4));
1005                                 if (!remote)
1006                                         remote = priv->remote_id;
1007                                 priv->contact_id = _VARR_DUP_STR (event, 5);
1008                         }
1009                         else if (priv->remote_id) {
1010                                 g_free (priv->remote_id);
1011                                 priv->remote_id = NULL;
1012                         }
1013
1014                         priv->message = _VARR_DUP_STR (event, 6);
1015                         priv->group_uid = _VARR_DUP_STR (event, 7);
1016
1017 #undef _VARR_DUP_STR
1018                 }
1019         }
1020         else {
1021                 priv->event_id = -1;
1022         }
1023
1024         if (priv->message) {
1025                 gtk_widget_hide (priv->empty);
1026         }
1027         else {
1028                 gtk_widget_show (priv->empty);
1029         }
1030
1031         gtk_label_set_text (GTK_LABEL (priv->received), received);
1032
1033         if (remote)
1034                 gtk_label_set_text (GTK_LABEL (priv->sender), remote);
1035         else
1036                 gtk_label_set_text (GTK_LABEL (priv->sender), priv->remote_id);
1037
1038         stop_scroll_anim (priv);
1039         priv->scroll_offset = 0;
1040         if (priv->message_surface) {
1041                 cairo_surface_destroy (priv->message_surface);
1042                 priv->message_surface = NULL;
1043         }
1044
1045         if (event)
1046                 g_value_array_free (event);
1047
1048         gtk_widget_hide (priv->cut_message);
1049         gtk_widget_queue_draw (GTK_WIDGET (self));
1050 }
1051
1052 static RTComElIter*
1053 make_query (RTComEl *el, gint event_id)
1054 {
1055         RTComElQuery *query = NULL;
1056         RTComElIter *it = NULL;
1057
1058         query = rtcom_el_query_new (el);
1059         rtcom_el_query_set_limit (query, 1);
1060         if (event_id >= 0) {
1061                 rtcom_el_query_prepare (query,
1062                                         "id", event_id, RTCOM_EL_OP_EQUAL,
1063                                         NULL);
1064         }
1065         else {
1066                 rtcom_el_query_prepare (query,
1067                                         "is-read", FALSE, RTCOM_EL_OP_EQUAL,
1068                                         "service", conv_services, RTCOM_EL_OP_IN_STRV,
1069                                         "event-type", conv_event_types, RTCOM_EL_OP_IN_STRV,
1070                                         NULL);
1071         }
1072         it = rtcom_el_get_events (el, query);
1073         g_object_unref (query);
1074
1075         return it;
1076 }
1077
1078 static void
1079 update_unread_label (ELHomeApplet *self)
1080 {
1081         ELHomeAppletPrivate *priv = self->priv;
1082
1083         if (priv->unread_count > 0) {
1084                 gchar *text;
1085                 text = g_strdup_printf
1086                         ("%d<span foreground=\"red\" rise=\"5000\">*</span>",
1087                          priv->unread_count);
1088
1089                 gtk_label_set_markup (GTK_LABEL (priv->unread), text);
1090                 g_free (text);
1091         }
1092         else
1093                 gtk_label_set_text (GTK_LABEL (priv->unread), NULL);
1094 }
1095
1096 static gint
1097 query_unread_events (RTComEl *el)
1098 {
1099         sqlite3 *db;
1100         sqlite3_stmt *stmt;
1101         int ret;
1102         gint count = 0;
1103
1104         g_object_get (el, "db", &db, NULL);
1105
1106         if (sqlite3_prepare_v2 (db,
1107                                 "SELECT SUM(total_events)-SUM(read_events) FROM GroupCache;",
1108                                 -1,
1109                                 &stmt,
1110                                 NULL) != SQLITE_OK) {
1111                 g_error ("%s: can't compile SQL", G_STRFUNC);
1112                 return -1;
1113         }
1114
1115         while (SQLITE_BUSY == (ret = sqlite3_step (stmt)));
1116
1117         if (ret == SQLITE_ROW) {
1118                 count = sqlite3_column_int (stmt, 0);
1119         }
1120         else {
1121                 g_error ("%s: error while executing SQL", G_STRFUNC);
1122         }
1123
1124         sqlite3_finalize (stmt);
1125
1126         return count;
1127 }
1128
1129 static gboolean
1130 query_read_events (RTComEl *el, const gchar *service, gint *events, gint *conversations)
1131 {
1132         sqlite3 *db;
1133         sqlite3_stmt *stmt;
1134         int ret;
1135         gboolean result = TRUE;
1136
1137         g_object_get (el, "db", &db, NULL);
1138
1139         if (sqlite3_prepare_v2 (db,
1140                                 "SELECT SUM(total_events), COUNT(group_uid) FROM GroupCache, Services "
1141                                 "WHERE GroupCache.service_id=Services.id AND Services.name=?;",
1142                                 -1,
1143                                 &stmt,
1144                                 NULL) != SQLITE_OK) {
1145                 g_error ("%s: can't compile SQL", G_STRFUNC);
1146                 return FALSE;
1147         }
1148         if (sqlite3_bind_text (stmt, 1, service, -1, SQLITE_STATIC) != SQLITE_OK)  {
1149                 g_error ("Failed to bind %s to SQL stmt", service);
1150                 result = FALSE;
1151                 goto DONE;
1152         }
1153
1154         while (SQLITE_BUSY == (ret = sqlite3_step (stmt)));
1155
1156         if (ret == SQLITE_ROW) {
1157                 *events = sqlite3_column_int (stmt, 0);
1158                 *conversations = sqlite3_column_int (stmt, 1);
1159         }
1160         else {
1161                 g_error ("%s: error while executing SQL", G_STRFUNC);
1162                 result = FALSE;
1163                 goto DONE;
1164         }
1165
1166  DONE:
1167         sqlite3_finalize (stmt);
1168
1169         return result;
1170 }
1171
1172 static void
1173 am_ready (OssoABookAccountManager *manager,
1174           const GError            *error,
1175           gpointer                 user_data)
1176 {
1177         ELHomeApplet *self = EL_HOME_APPLET (user_data);
1178         ELHomeAppletPrivate *priv = self->priv;
1179
1180         if (!error &&
1181             priv->local_id &&
1182             !GTK_WIDGET_VISIBLE (priv->icon)) {
1183                 McAccount *account;
1184
1185                 account = osso_abook_account_manager_lookup_by_name (NULL,
1186                                                                      priv->local_id);
1187                 if (account) {
1188                         McProfile *profile = mc_profile_lookup (mc_account_compat_get_profile (account));
1189                         const gchar *icon_name = mc_profile_get_icon_name (profile);
1190                         if (icon_name) {
1191                                 gtk_image_set_from_icon_name (GTK_IMAGE (priv->icon),
1192                                                               icon_name,
1193                                                               HILDON_ICON_SIZE_XSMALL);
1194                                 gtk_widget_show (priv->icon);
1195                         }
1196                 }
1197         }
1198 }
1199
1200 static void
1201 read_event (ELHomeApplet *self)
1202 {
1203         ELHomeAppletPrivate *priv = self->priv;
1204         RTComElIter *it = NULL;
1205         const gchar *icon_name = NULL;
1206         gchar *remote_id;
1207         gchar *local_id;
1208
1209         remote_id = g_strdup (priv->remote_id);
1210         local_id = g_strdup (priv->local_id);
1211
1212         clean_state (self);
1213
1214         it = make_query (priv->eventlogger, -1);
1215         show_event (self, it);
1216
1217         if (it) g_object_unref (it);
1218
1219         if (priv->event_id >= 0) {
1220                 gboolean new_account = g_strcmp0 (priv->local_id, local_id);
1221
1222                 if (g_strcmp0 (priv->remote_id, remote_id) ||
1223                     new_account ||
1224                     !priv->contact) {
1225                         clean_contact (self);
1226                         start_aggregator (self);
1227                 }
1228                 else if (priv->contact) {
1229                         show_contact (self);
1230                 }
1231
1232                 if (new_account) {
1233                         if (g_strcmp0 (priv->local_id, "ring/tel/ring") == 0) {
1234                                 icon_name = "general_sms";
1235                         }
1236                         else {
1237                                 McAccount *account;
1238                                 OssoABookAccountManager *am = osso_abook_account_manager_get_default ();
1239                                 if (!osso_abook_waitable_is_ready (OSSO_ABOOK_WAITABLE (am), NULL)) {
1240                                         osso_abook_account_manager_call_when_ready  (am,
1241                                                                                      am_ready,
1242                                                                                      self,
1243                                                                                      NULL);
1244                                 }
1245                                 else {
1246                                         account = osso_abook_account_manager_lookup_by_name (NULL,
1247                                                                                              priv->local_id);
1248                                         if (account) {
1249                                                 McProfile *profile = mc_profile_lookup (mc_account_compat_get_profile (account));
1250                                                 icon_name = mc_profile_get_icon_name (profile);
1251                                         }
1252                                 }
1253                         }
1254
1255                         if (icon_name) {
1256                                 gtk_image_set_from_icon_name (GTK_IMAGE (priv->icon),
1257                                                               icon_name,
1258                                                               HILDON_ICON_SIZE_XSMALL);
1259                                 gtk_widget_show (priv->icon);
1260                         }
1261                         else
1262                                 gtk_widget_hide (priv->icon);
1263                 }
1264         }
1265         else {
1266                 gchar *text;
1267                 gint n_sms_events = 0, n_sms_convs = 0;
1268                 gint n_chat_events = 0, n_chat_convs = 0;
1269                 const gchar *fmt = "%d <span size=\"small\">(%d)</span>";
1270
1271                 query_read_events (priv->eventlogger,
1272                                    "RTCOM_EL_SERVICE_SMS",
1273                                    &n_sms_events, &n_sms_convs);
1274                 query_read_events (priv->eventlogger,
1275                                    "RTCOM_EL_SERVICE_CHAT",
1276                                    &n_chat_events, &n_chat_convs);
1277
1278                 text = g_strdup_printf (fmt, n_sms_convs, n_sms_events);
1279                 gtk_label_set_markup (GTK_LABEL (priv->sms_total), text);
1280                 g_free (text);
1281
1282                 text = g_strdup_printf (fmt, n_chat_convs, n_chat_events);
1283                 gtk_label_set_markup (GTK_LABEL (priv->chat_total), text);
1284                 g_free (text);
1285
1286                 gtk_label_set_text (GTK_LABEL (priv->sender),
1287                                     dgettext ("rtcom-messaging-ui",
1288                                               "messaging_ap_conversations"));
1289
1290                 clean_contact (self);
1291                 gtk_widget_hide (priv->icon);
1292         }
1293
1294         g_free (local_id);
1295         g_free (remote_id);
1296 }
1297
1298 static void
1299 remove_notification (ELHomeApplet *self)
1300 {
1301         ELHomeAppletPrivate *priv = self->priv;
1302
1303         DBusGConnection *conn;
1304         GError *error;
1305         DBusGProxy *proxy;
1306         GPtrArray *conv_structs;
1307         GType conv_structs_type;
1308         GValueArray *account_info;
1309         GValue value = {0, };
1310         DBusGProxyCall *call;
1311
1312         if (!(priv->remote_id && priv->local_id))
1313                 return;
1314
1315         conn = hd_home_plugin_item_get_dbus_g_connection (HD_HOME_PLUGIN_ITEM (self),
1316                                                           DBUS_BUS_SESSION,
1317                                                           &error);
1318         if (!conn) {
1319                 g_error ("Failed get dbus g connection %s", error->message);
1320                 g_error_free (error);
1321                 return;
1322         }
1323
1324         proxy = dbus_g_proxy_new_for_name (conn,
1325                                            NOTIFICATION_UI_DBUS_NAME,
1326                                            NOTIFICATION_UI_DBUS_PATH,
1327                                            NOTIFICATION_UI_DBUS_IFACE);
1328
1329         conv_structs = g_ptr_array_sized_new (1);
1330         account_info = g_value_array_new (2);
1331
1332         g_value_init (&value, G_TYPE_STRING);
1333         g_value_set_string (&value, priv->local_id);
1334         g_value_array_append (account_info, &value);
1335         g_value_unset (&value);
1336
1337         g_value_init (&value, G_TYPE_STRING);
1338         g_value_set_string (&value, priv->remote_id);
1339         g_value_array_append (account_info, &value);
1340         g_value_unset (&value);
1341
1342         g_ptr_array_add (conv_structs, account_info);
1343
1344         conv_structs_type = dbus_g_type_get_collection
1345                 ("GPtrArray",
1346                  dbus_g_type_get_struct ("GValueArray",
1347                                          G_TYPE_STRING,
1348                                          G_TYPE_STRING,
1349                                          G_TYPE_INVALID));
1350
1351         call = dbus_g_proxy_begin_call (proxy,
1352                                         "ClearConversationNotifications",
1353                                         NULL, NULL, NULL,
1354                                         conv_structs_type,
1355                                         conv_structs,
1356                                         G_TYPE_INVALID);
1357
1358         g_value_array_free (account_info);
1359         g_ptr_array_free (conv_structs, TRUE);
1360
1361         g_object_unref (proxy);
1362 }
1363
1364 static void
1365 mark_as_read (ELHomeApplet *self)
1366 {
1367         ELHomeAppletPrivate *priv = self->priv;
1368
1369         if (priv->event_id >= 0) {
1370                 rtcom_el_set_read_event (priv->eventlogger,
1371                                          priv->event_id,
1372                                          TRUE,
1373                                          NULL);
1374                 remove_notification (self);
1375         }
1376 }
1377
1378 static void
1379 launch_conversations (ELHomeApplet *self)
1380 {
1381         DBusConnection *conn;
1382         DBusMessage *message;
1383         DBusError error;
1384
1385         dbus_error_init (&error);
1386         conn = hd_home_plugin_item_get_dbus_connection (HD_HOME_PLUGIN_ITEM (self),
1387                                                         DBUS_BUS_SESSION,
1388                                                         &error);
1389         if (!conn) {
1390                 if (dbus_error_is_set (&error)) {
1391                         g_error ("Failed to get dbus connection %s", error.message);
1392                         dbus_error_free (&error);
1393                 }
1394                 return;
1395         }
1396
1397         message = dbus_message_new_method_call (CONVERSATIONS_UI_DBUS_NAME,
1398                                                 CONVERSATIONS_UI_DBUS_PATH,
1399                                                 CONVERSATIONS_UI_DBUS_IFACE,
1400                                                 "top_application");
1401         dbus_message_set_no_reply (message, TRUE);
1402
1403         if (dbus_connection_send (conn, message, NULL))
1404                 dbus_connection_flush (conn);
1405         dbus_message_unref (message);
1406
1407         dbus_connection_close (conn);
1408 }
1409
1410 static void
1411 open_conversation (ELHomeApplet *self)
1412 {
1413         ELHomeAppletPrivate *priv = self->priv;
1414         McAccount *account;
1415         const gchar *persistent_id = NULL;
1416
1417         if (!((priv->remote_id || priv->group_uid) && priv->local_id))
1418                 return;
1419
1420         account = osso_abook_account_manager_lookup_by_name (NULL,
1421                                                              priv->local_id);
1422         if (!account)
1423                 return;
1424
1425         if (priv->group_uid &&
1426             g_str_has_prefix (priv->group_uid, "group:")) {
1427                 persistent_id = strchr (priv->group_uid, '-');
1428                 if (persistent_id)
1429                         persistent_id++;
1430         }
1431
1432         if (persistent_id && persistent_id[0] != '\0') {
1433                 GHashTable *properties = tp_asv_new
1434                         (TP_IFACE_CHANNEL ".ChannelType", G_TYPE_STRING,
1435                          TP_IFACE_CHANNEL_TYPE_TEXT,
1436                          TP_IFACE_CHANNEL ".TargetHandleType", G_TYPE_UINT,
1437                          TP_HANDLE_TYPE_NONE,
1438                          RTCOM_TP_IFACE_CHANNEL_INTERFACE_PERSISTENT ".PersistentID",
1439                          G_TYPE_STRING, persistent_id,
1440                          NULL);
1441
1442                 mc_account_channelrequest_ht (account,
1443                                               properties,
1444                                               time (NULL),
1445                                               NULL,
1446                                               MC_ACCOUNT_CR_FLAG_USE_EXISTING,
1447                                               NULL, NULL, NULL, NULL);
1448
1449                 g_hash_table_unref (properties);
1450         }
1451         else if (priv->remote_id) {
1452                 McAccountChannelrequestData request;
1453
1454                 MC_ACCOUNT_CRD_INIT (&request);
1455                 MC_ACCOUNT_CRD_SET (&request, channel_type, TP_IFACE_QUARK_CHANNEL_TYPE_TEXT);
1456                 MC_ACCOUNT_CRD_SET (&request, target_handle_type, TP_HANDLE_TYPE_CONTACT);
1457                 MC_ACCOUNT_CRD_SET (&request, target_id, priv->remote_id);
1458
1459                 mc_account_channelrequest (account,
1460                                            &request,
1461                                            time (NULL),
1462                                            NULL,
1463                                            MC_ACCOUNT_CR_FLAG_USE_EXISTING,
1464                                            NULL, NULL, NULL, NULL);
1465         }
1466 }
1467
1468 static gboolean
1469 read_new_event (ELHomeApplet *self)
1470 {
1471         ELHomeAppletPrivate *priv = self->priv;
1472
1473         read_event (self);
1474
1475         if (priv->event_id >= 0)
1476                 priv->unread_count = query_unread_events (priv->eventlogger);
1477         else
1478                 priv->unread_count = 0;
1479
1480         update_unread_label (self);
1481
1482         priv->idle_id = 0;
1483
1484         return FALSE;
1485 }
1486
1487 static void
1488 add_new_idle (ELHomeApplet *self)
1489 {
1490         ELHomeAppletPrivate *priv = self->priv;
1491
1492         if (priv->idle_id)
1493                 g_source_remove (priv->idle_id);
1494         priv->idle_id = g_idle_add ((GSourceFunc)read_new_event,
1495                                     self);
1496 }
1497
1498 static void
1499 new_event_cb (RTComEl      *backend,
1500               gint          event_id,
1501               const gchar  *local_uid,
1502               const gchar  *remote_uid,
1503               const gchar  *remote_ebook_uid,
1504               const gchar  *group_uid,
1505               const gchar  *service,
1506               ELHomeApplet *self)
1507 {
1508         if (service && service[0] != '\0') {
1509                 const gchar** conv_service = conv_services;
1510                 do {
1511                         if (!g_strcmp0 (*conv_service, service)) {
1512                                 add_new_idle (self);
1513                                 return;
1514                         }
1515                 }
1516                 while(*++conv_service);
1517         }
1518         else
1519                 add_new_idle (self);
1520 }
1521
1522 static void
1523 all_deleted_cb (RTComEl      *backend,
1524                 const gchar  *service,
1525                 ELHomeApplet *self)
1526 {
1527         new_event_cb (backend,
1528                       0,
1529                       NULL,
1530                       NULL,
1531                       NULL,
1532                       NULL,
1533                       service,
1534                       self);
1535 }
1536
1537 static void
1538 refresh_hint_cb (RTComEl     *backend,
1539                  ELHomeApplet *self)
1540 {
1541         add_new_idle (self);
1542 }
1543
1544 static gboolean
1545 scroll_anim_cb (ELHomeApplet *self)
1546 {
1547         ELHomeAppletPrivate *priv = self->priv;
1548         gboolean to_continue;
1549
1550         priv->scroll_offset += SCROLL_STEP;
1551         gtk_widget_queue_draw_area (GTK_WIDGET (self),
1552                                     3*CONTENT_OFFSET_X,
1553                                     HEADER_HEIGHT + CONTENT_OFFSET_Y_TOP,
1554                                     MESSAGE_WIDTH,
1555                                     MESSAGE_HEIGHT);
1556
1557         to_continue = priv->scroll_offset <= priv->hidden_message_height;
1558         if (!to_continue) {
1559                 priv->scroll_anim_id = 0;
1560                 gtk_widget_hide (priv->cut_message);
1561         }
1562
1563         return to_continue;
1564 }
1565
1566 static gboolean
1567 button_press_event_cb (GtkWidget      *widget,
1568                        GdkEventButton *event,
1569                        ELHomeApplet   *self)
1570 {
1571         ELHomeAppletPrivate *priv = self->priv;
1572
1573         if (priv->event_id >= 0) {
1574                 if (event->y < CONTENT_OFFSET_Y_TOP + HEADER_HEIGHT) {
1575                         if (priv->aggregator &&
1576                             osso_abook_waitable_is_ready
1577                             (OSSO_ABOOK_WAITABLE (priv->aggregator), NULL))
1578                                 priv->active = SELECTED_HEADER;
1579                 }
1580                 else if (event->y > (BOX_HEIGHT - CONTENT_OFFSET_Y_BOTTOM - FOOTER_HEIGHT_PRESS) &&
1581                          event->x < FOOTER_WIDTH_PRESS)
1582                         priv->active = SELECTED_FOOTER;
1583                 else
1584                         priv->active = SELECTED_BODY;
1585         }
1586         else {
1587                 priv->active = SELECTED_BODY;
1588         }
1589
1590         gtk_widget_queue_draw (widget);
1591
1592         return TRUE;
1593 }
1594
1595 static GtkWidget*
1596 create_contact_starter_dialog (OssoABookAggregator *aggregator, const gchar *contact_id)
1597 {
1598         GtkWidget *dialog = NULL;
1599         GList *contacts = osso_abook_aggregator_lookup (aggregator, contact_id);
1600         if (contacts && contacts->data) {
1601                 GtkWidget *starter =
1602                         osso_abook_touch_contact_starter_new_with_contact
1603                         (NULL,
1604                          OSSO_ABOOK_CONTACT (contacts->data));
1605                 dialog = osso_abook_touch_contact_starter_dialog_new
1606                         (NULL,
1607                          OSSO_ABOOK_TOUCH_CONTACT_STARTER (starter));
1608                 gtk_widget_show_all (starter);
1609         }
1610
1611         g_list_free (contacts);
1612
1613         return dialog;
1614 }
1615
1616 static GtkWidget*
1617 create_temporary_contact_dialog (const gchar *remote_id,
1618                                  const gchar *account_id)
1619 {
1620         GtkWidget *dialog = NULL;
1621         const gchar *vcard = NULL;
1622         McAccount *account = NULL;
1623
1624         if (account_id) {
1625             vcard = osso_abook_account_manager_get_vcard_field (NULL, account_id);
1626             account = osso_abook_account_manager_lookup_by_name (NULL, account_id);
1627         }
1628
1629         if (vcard && account) {
1630                 EVCardAttribute *attribute = e_vcard_attribute_new (NULL, vcard);
1631
1632                 e_vcard_attribute_add_value (attribute, remote_id);
1633                 dialog = osso_abook_temporary_contact_dialog_new
1634                         (NULL,
1635                          NULL, /*EBook            *book,*/
1636                          attribute,
1637                          account);
1638                 g_signal_connect (dialog,
1639                                   "response",
1640                                   G_CALLBACK (gtk_widget_destroy),
1641                                   NULL);
1642                 e_vcard_attribute_free (attribute);
1643         }
1644
1645         return dialog;
1646 }
1647
1648 static gboolean
1649 button_release_event_cb (GtkWidget      *widget,
1650                          GdkEventButton *event,
1651                          ELHomeApplet   *self)
1652 {
1653         ELHomeAppletPrivate *priv = self->priv;
1654
1655         switch (priv->active) {
1656         case SELECTED_BODY:
1657                 if (priv->event_id >= 0) {
1658                         reset_scroll (self);
1659                         open_conversation (self);
1660                 }
1661                 else
1662                         launch_conversations (self);
1663                 break;
1664         case SELECTED_HEADER: {
1665                 GtkWidget *dialog = NULL;
1666
1667                 reset_scroll (self);
1668
1669                 if (priv->aggregator && priv->contact_id)
1670                         dialog = create_contact_starter_dialog
1671                                 (OSSO_ABOOK_AGGREGATOR (priv->aggregator),
1672                                  priv->contact_id);
1673                 if (!dialog &&
1674                     priv->remote_id &&
1675                     priv->local_id)
1676                         dialog = create_temporary_contact_dialog (priv->remote_id,
1677                                                                   priv->local_id);
1678
1679                 if (dialog)
1680                         gtk_widget_show (dialog);
1681         }
1682                 break;
1683         case SELECTED_FOOTER:
1684                 if (priv->scroll_on_click) {
1685                         priv->scroll_on_click = FALSE;
1686                         priv->scroll_anim_id = g_timeout_add (SCROLL_PERIOD,
1687                                                               (GSourceFunc)scroll_anim_cb,
1688                                                               self);
1689                 }
1690                 else
1691                         mark_as_read (self);
1692                 break;
1693         default:;
1694         }
1695
1696         priv->active = SELECTED_NONE;
1697         gtk_widget_queue_draw (widget);
1698
1699         return TRUE;
1700 }
1701
1702 static gboolean
1703 leave_notify_event_cb (GtkWidget        *widget,
1704                        GdkEventCrossing *event,
1705                        ELHomeApplet     *self)
1706 {
1707         ELHomeAppletPrivate *priv = self->priv;
1708
1709         switch (priv->active) {
1710         case SELECTED_FOOTER:
1711                 stop_scroll_anim (priv);
1712                 /* fall down */
1713         case SELECTED_HEADER:
1714         case SELECTED_BODY:
1715                 gtk_widget_queue_draw (widget);
1716                 break;
1717         default:;
1718         }
1719
1720         priv->active = SELECTED_NONE;
1721         return FALSE;
1722 }
1723
1724 static gboolean
1725 init_eventlogger (ELHomeApplet *self)
1726 {
1727         ELHomeAppletPrivate *priv = self->priv;
1728
1729         priv->eventlogger = rtcom_el_new ();
1730
1731         /* check that db is initialized */
1732         gpointer *db = NULL;
1733         g_object_get (priv->eventlogger, "db", &db, NULL);
1734         if (!db) {
1735                 static int trial = 0;
1736
1737                 g_object_unref (priv->eventlogger);
1738                 priv->eventlogger = NULL;
1739
1740                 if (trial == 0) {
1741                         trial++;
1742                         priv->init_timer = g_timeout_add_seconds (5,
1743                                                                   (GSourceFunc)init_eventlogger,
1744                                                                   self);
1745                         return TRUE; /* return value doesn't matter */
1746                 }
1747                 else if (trial < 5) {
1748                         trial++;
1749                         return TRUE;
1750                 }
1751                 else {
1752                         g_error ("Failed to init eventlogger");
1753                         return FALSE;
1754                 }
1755         }
1756
1757         g_signal_connect (priv->eventlogger,
1758                           "new-event",
1759                           G_CALLBACK (new_event_cb),
1760                           self);
1761         g_signal_connect (priv->eventlogger,
1762                           "event-updated",
1763                           G_CALLBACK (new_event_cb),
1764                           self);
1765         g_signal_connect (priv->eventlogger,
1766                           "event-deleted",
1767                           G_CALLBACK (new_event_cb),
1768                           self);
1769         g_signal_connect (priv->eventlogger,
1770                           "all-deleted",
1771                           G_CALLBACK (all_deleted_cb),
1772                           self);
1773         g_signal_connect (priv->eventlogger,
1774                           "refresh-hint",
1775                           G_CALLBACK (refresh_hint_cb),
1776                           self);
1777
1778         add_new_idle (self);
1779
1780         priv->init_timer = 0;
1781
1782         return FALSE;
1783 }
1784
1785 static void
1786 el_home_applet_init (ELHomeApplet *self)
1787 {
1788         ELHomeAppletPrivate *priv;
1789         GtkWidget *event_box;
1790         GtkWidget *hbox, *vbox, *align, *footer;
1791         GtkWidget *w;
1792         GConfClient *gconf;
1793
1794         self->priv = EL_HOME_APPLET_GET_PRIVATE (self);
1795         priv = self->priv;
1796
1797         gtk_widget_set_app_paintable (GTK_WIDGET (self), TRUE);
1798
1799         priv->unread = gtk_label_new (NULL);
1800         gtk_misc_set_alignment (GTK_MISC (priv->unread),
1801                                 0.0f,
1802                                 0.5f);
1803         hildon_helper_set_logical_font (priv->unread, "SmallSystemFont");
1804
1805         priv->icon = gtk_image_new ();
1806         gtk_misc_set_alignment (GTK_MISC (priv->icon),
1807                                 0.5f,
1808                                 0.5f);
1809
1810         priv->sender = gtk_label_new (NULL);
1811         gtk_misc_set_alignment (GTK_MISC (priv->sender),
1812                                 0.5f,
1813                                 0.55f);
1814         gtk_label_set_ellipsize (GTK_LABEL (priv->sender),
1815                                  PANGO_ELLIPSIZE_END);
1816         gtk_widget_set_name (priv->sender, "hildon-shadow-label");
1817         hildon_helper_set_logical_font (priv->sender, "SystemFont");
1818         gtk_widget_set_size_request (priv->sender,
1819                                      C_WIDTH,
1820                                      HILDON_ICON_PIXEL_SIZE_THUMB);
1821
1822         /* construt empty table */
1823         priv->empty = gtk_fixed_new ();
1824
1825         w = gtk_image_new_from_icon_name ("general_sms", HILDON_ICON_SIZE_FINGER);
1826         gtk_fixed_put (GTK_FIXED (priv->empty), w,
1827                        4*HILDON_MARGIN_DOUBLE,
1828                        2*HILDON_MARGIN_DOUBLE);
1829
1830         w = gtk_image_new_from_icon_name ("general_chat", HILDON_ICON_SIZE_FINGER);
1831         gtk_fixed_put (GTK_FIXED (priv->empty), w,
1832                        4*HILDON_MARGIN_DOUBLE,
1833                        3*HILDON_MARGIN_DOUBLE + HILDON_ICON_PIXEL_SIZE_FINGER);
1834
1835         priv->sms_total = gtk_label_new (NULL);
1836         gtk_widget_set_name (priv->sms_total, "hildon-shadow-label");
1837         gtk_fixed_put (GTK_FIXED (priv->empty), priv->sms_total,
1838                        5*HILDON_MARGIN_DOUBLE  + HILDON_ICON_PIXEL_SIZE_FINGER,
1839                        2*HILDON_MARGIN_DOUBLE + HILDON_MARGIN_HALF);
1840
1841         priv->chat_total = gtk_label_new (NULL);
1842         gtk_widget_set_name (priv->chat_total, "hildon-shadow-label");
1843         gtk_fixed_put (GTK_FIXED (priv->empty), priv->chat_total,
1844                        5*HILDON_MARGIN_DOUBLE  + HILDON_ICON_PIXEL_SIZE_FINGER,
1845                        3*HILDON_MARGIN_DOUBLE + HILDON_MARGIN_HALF + HILDON_ICON_PIXEL_SIZE_FINGER);
1846
1847         gtk_widget_show_all (GTK_WIDGET (priv->empty));
1848         gtk_widget_hide (GTK_WIDGET (priv->empty));
1849         GTK_WIDGET_SET_FLAGS (priv->empty, GTK_NO_SHOW_ALL);
1850
1851         priv->received = gtk_label_new (NULL);
1852         gtk_misc_set_alignment (GTK_MISC (priv->received),
1853                                 1.0f,
1854                                 0.5f);
1855         hildon_helper_set_logical_font (priv->received, "SmallSystemFont");
1856         gtk_widget_set_name (priv->received, "hildon-shadow-label");
1857
1858
1859         priv->cut_message = gtk_label_new ("...");
1860         gtk_misc_set_alignment (GTK_MISC (priv->cut_message),
1861                                 0.5f,
1862                                 0.0f);
1863         hildon_helper_set_logical_font (priv->cut_message, "SmallSystemFont");
1864         gtk_widget_set_name (priv->cut_message, "hildon-shadow-label");
1865         GTK_WIDGET_SET_FLAGS (priv->cut_message, GTK_NO_SHOW_ALL);
1866
1867         hbox = gtk_hbox_new (FALSE, 0);
1868         gtk_box_pack_start (GTK_BOX (hbox), priv->sender, FALSE, FALSE, 0);
1869
1870         footer = gtk_hbox_new (FALSE, HILDON_MARGIN_DEFAULT);
1871         gtk_box_pack_start (GTK_BOX (footer), priv->unread, FALSE, FALSE, 0);
1872         gtk_box_pack_start (GTK_BOX (footer), priv->cut_message, TRUE, TRUE, 0);
1873         gtk_box_pack_end (GTK_BOX (footer), priv->icon, FALSE, FALSE, 0);
1874         gtk_box_pack_end (GTK_BOX (footer), priv->received, FALSE, FALSE, 0);
1875
1876         vbox = gtk_vbox_new (FALSE, 0);
1877         gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
1878         gtk_box_pack_start (GTK_BOX (vbox), priv->empty, TRUE, TRUE, 0);
1879         gtk_box_pack_end (GTK_BOX (vbox), footer, FALSE, FALSE, 0);
1880
1881         align = gtk_alignment_new (0.5f, 0.0f, 1.0f, 1.0f);
1882         gtk_alignment_set_padding (GTK_ALIGNMENT (align),
1883                                    0, 0, HILDON_MARGIN_DEFAULT, HILDON_MARGIN_DEFAULT);
1884
1885         gtk_container_set_border_width (GTK_CONTAINER (vbox), HILDON_MARGIN_HALF);
1886
1887         event_box = gtk_event_box_new ();
1888         gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
1889         gtk_widget_set_size_request (event_box, BOX_WIDTH, BOX_HEIGHT);
1890
1891         gtk_container_add (GTK_CONTAINER (align), vbox);
1892         gtk_container_add (GTK_CONTAINER (event_box), align);
1893         gtk_container_add (GTK_CONTAINER (self), event_box);
1894
1895         g_signal_connect (event_box,
1896                           "button-press-event",
1897                           G_CALLBACK (button_press_event_cb),
1898                           self);
1899         g_signal_connect (event_box,
1900                           "button-release-event",
1901                           G_CALLBACK (button_release_event_cb),
1902                           self);
1903         g_signal_connect (event_box,
1904                           "leave-notify-event",
1905                           G_CALLBACK (leave_notify_event_cb),
1906                           self);
1907
1908         g_signal_connect (event_box,
1909                           "style-set",
1910                           G_CALLBACK (style_set_cb),
1911                           self);
1912         g_signal_connect (self,
1913                           "notify::is-on-current-desktop",
1914                           G_CALLBACK (notify_on_current_desktop),
1915                           self);
1916
1917         gtk_widget_show_all (GTK_WIDGET (event_box));
1918
1919
1920         osso_abook_init_with_name (PACKAGE, NULL);
1921
1922         gconf = gconf_client_get_default ();
1923         priv->time_fmt_24h = gconf_client_get_bool (gconf,
1924                                                     "/apps/clock/time-format",
1925                                                     NULL);
1926         g_object_unref (gconf);
1927
1928         init_eventlogger (self);
1929 }
1930
1931 static void
1932 el_home_applet_class_init (ELHomeAppletClass *klass)
1933 {
1934         GObjectClass *object_class = G_OBJECT_CLASS (klass);
1935         GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1936
1937         object_class->dispose = dispose;
1938         object_class->finalize = finalize;
1939         widget_class->expose_event = expose_event;
1940         widget_class->realize = el_home_applet_realize;
1941
1942         g_type_class_add_private (klass, sizeof (ELHomeAppletPrivate));
1943 }