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