]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-log-window.c
cba8ae2c729397f172a05be5714343099db2e441
[empathy.git] / libempathy-gtk / empathy-log-window.c
1 /*
2  * Copyright (C) 2006-2007 Imendio AB
3  * Copyright (C) 2007-2011 Collabora Ltd.
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of the
8  * License, or (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 GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public
16  * License along with this program; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA  02110-1301  USA
19  *
20  * Authors: Martyn Russell <martyn@imendio.com>
21  *          Xavier Claessens <xclaesse@gmail.com>
22  *          Emilio Pozuelo Monfort <emilio.pozuelo@collabora.co.uk>
23  */
24
25 #include "config.h"
26
27 #include <glib/gi18n-lib.h>
28 #include <webkit/webkit.h>
29
30 #include <telepathy-glib/proxy-subclass.h>
31
32 #include <extensions/extensions.h>
33
34 #include <libempathy/action-chain-internal.h>
35 #include <libempathy/empathy-camera-monitor.h>
36 #include <libempathy/empathy-gsettings.h>
37 #include <libempathy/empathy-request-util.h>
38 #include <libempathy/empathy-utils.h>
39
40 #include "empathy-log-window.h"
41 #include "empathy-account-chooser.h"
42 #include "empathy-call-utils.h"
43 #include "empathy-individual-information-dialog.h"
44 #include "empathy-images.h"
45 #include "empathy-theme-manager.h"
46 #include "empathy-ui-utils.h"
47 #include "empathy-webkit-utils.h"
48 #include "empathy-geometry.h"
49
50 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
51 #include <libempathy/empathy-debug.h>
52
53 #define EMPATHY_NS "http://live.gnome.org/Empathy"
54
55 G_DEFINE_TYPE (EmpathyLogWindow, empathy_log_window, GTK_TYPE_WINDOW);
56
57 struct _EmpathyLogWindowPriv
58 {
59   GtkWidget *vbox;
60
61   GtkWidget *button_profile;
62   GtkWidget *button_chat;
63   GtkWidget *button_call;
64   GtkWidget *button_video;
65
66   GtkWidget *search_entry;
67
68   GtkWidget *notebook;
69   GtkWidget *spinner;
70
71   GtkWidget *treeview_who;
72   GtkWidget *treeview_what;
73   GtkWidget *treeview_when;
74   GtkWidget *webview;
75
76   GtkTreeStore *store_events;
77
78   GtkWidget *account_chooser;
79
80   gchar *last_find;
81
82   /* List of selected GDates, free with g_list_free_full (l, g_date_free) */
83   GList *current_dates;
84
85   TplActionChain *chain;
86   TplLogManager *log_manager;
87
88   /* Hash of TpChannel<->TpAccount for use by the observer until we can
89    * get a TpAccount from a TpConnection or wherever */
90   GHashTable *channels;
91   TpBaseClient *observer;
92
93   EmpathyContact *selected_contact;
94   EmpathyContact *events_contact;
95
96   EmpathyCameraMonitor *camera_monitor;
97   GBinding *button_video_binding;
98
99   /* Used to cancel logger calls when no longer needed */
100   guint count;
101
102   /* List of owned TplLogSearchHits, free with tpl_log_search_hit_free */
103   GList *hits;
104   guint source;
105
106   /* Only used while waiting for the account chooser to be ready */
107   TpAccount *selected_account;
108   gchar *selected_chat_id;
109   gboolean selected_is_chatroom;
110
111   GSettings *gsettings_chat;
112   GSettings *gsettings_desktop;
113 };
114
115 static void log_window_search_entry_changed_cb   (GtkWidget        *entry,
116                                                   EmpathyLogWindow *self);
117 static void log_window_search_entry_activate_cb  (GtkWidget        *widget,
118                                                   EmpathyLogWindow *self);
119 static void log_window_search_entry_icon_pressed_cb (GtkEntry      *entry,
120                                                   GtkEntryIconPosition icon_pos,
121                                                   GdkEvent *event,
122                                                   gpointer user_data);
123 static void log_window_who_populate              (EmpathyLogWindow *self);
124 static void log_window_who_setup                 (EmpathyLogWindow *self);
125 static void log_window_when_setup                (EmpathyLogWindow *self);
126 static void log_window_what_setup                (EmpathyLogWindow *self);
127 static void log_window_events_setup              (EmpathyLogWindow *self);
128 static void log_window_chats_accounts_changed_cb (GtkWidget        *combobox,
129                                                   EmpathyLogWindow *self);
130 static void log_window_chats_set_selected        (EmpathyLogWindow *self);
131 static void log_window_chats_get_messages        (EmpathyLogWindow *self,
132                                                   gboolean force_get_dates);
133 static void log_window_when_changed_cb           (GtkTreeSelection *selection,
134                                                   EmpathyLogWindow *self);
135 static void log_window_delete_menu_clicked_cb    (GtkMenuItem      *menuitem,
136                                                   EmpathyLogWindow *self);
137 static void start_spinner                        (void);
138
139 static void log_window_create_observer           (EmpathyLogWindow *window);
140 static gboolean log_window_events_button_press_event (GtkWidget *webview,
141     GdkEventButton *event, EmpathyLogWindow *self);
142 static void log_window_update_buttons_sensitivity (EmpathyLogWindow *self);
143
144 static void
145 empathy_account_chooser_filter_has_logs (TpAccount *account,
146     EmpathyAccountChooserFilterResultCallback callback,
147     gpointer callback_data,
148     gpointer user_data);
149
150 enum
151 {
152   PAGE_EVENTS,
153   PAGE_SPINNER,
154   PAGE_EMPTY
155 };
156
157 enum
158 {
159   COL_TYPE_ANY,
160   COL_TYPE_SEPARATOR,
161   COL_TYPE_NORMAL
162 };
163
164 enum
165 {
166   COL_WHO_TYPE,
167   COL_WHO_ICON,
168   COL_WHO_NAME,
169   COL_WHO_NAME_SORT_KEY,
170   COL_WHO_ID,
171   COL_WHO_ACCOUNT,
172   COL_WHO_TARGET,
173   COL_WHO_COUNT
174 };
175
176 enum
177 {
178   COL_WHAT_TYPE,
179   COL_WHAT_SUBTYPE,
180   COL_WHAT_SENSITIVE,
181   COL_WHAT_TEXT,
182   COL_WHAT_ICON,
183   COL_WHAT_COUNT
184 };
185
186 enum
187 {
188   COL_WHEN_DATE,
189   COL_WHEN_TEXT,
190   COL_WHEN_ICON,
191   COL_WHEN_COUNT
192 };
193
194 enum
195 {
196   COL_EVENTS_TYPE,
197   COL_EVENTS_TS,
198   COL_EVENTS_PRETTY_DATE,
199   COL_EVENTS_ICON,
200   COL_EVENTS_TEXT,
201   COL_EVENTS_ACCOUNT,
202   COL_EVENTS_TARGET,
203   COL_EVENTS_EVENT,
204   COL_EVENTS_COUNT
205 };
206
207 #define CALENDAR_ICON "stock_calendar"
208
209 /* Seconds between two messages to be considered one conversation */
210 #define MAX_GAP 30*60
211
212 #define WHAT_TYPE_SEPARATOR -1
213
214 typedef enum
215 {
216   EVENT_CALL_INCOMING = 1 << 0,
217   EVENT_CALL_OUTGOING = 1 << 1,
218   EVENT_CALL_MISSED   = 1 << 2,
219   EVENT_CALL_ALL      = 1 << 3,
220 } EventSubtype;
221
222 static gboolean
223 log_window_get_selected (EmpathyLogWindow *window,
224     GList **accounts,
225     GList **entities,
226     gboolean *anyone,
227     GList **dates,
228     TplEventTypeMask *event_mask,
229     EventSubtype *subtype);
230
231 static EmpathyLogWindow *log_window = NULL;
232
233 static gboolean has_element;
234
235 #ifndef _date_copy
236 #define _date_copy(d) g_date_new_julian (g_date_get_julian (d))
237 #endif
238
239 typedef struct
240 {
241   EmpathyLogWindow *self;
242   TpAccount *account;
243   TplEntity *entity;
244   GDate *date;
245   TplEventTypeMask event_mask;
246   EventSubtype subtype;
247   guint count;
248 } Ctx;
249
250 static Ctx *
251 ctx_new (EmpathyLogWindow *self,
252     TpAccount *account,
253     TplEntity *entity,
254     GDate *date,
255     TplEventTypeMask event_mask,
256     EventSubtype subtype,
257     guint count)
258 {
259   Ctx *ctx = g_slice_new0 (Ctx);
260
261   ctx->self = self;
262   if (account != NULL)
263     ctx->account = g_object_ref (account);
264   if (entity != NULL)
265     ctx->entity = g_object_ref (entity);
266   if (date != NULL)
267     ctx->date = _date_copy (date);
268   ctx->event_mask = event_mask;
269   ctx->subtype = subtype;
270   ctx->count = count;
271
272   return ctx;
273 }
274
275 static void
276 ctx_free (Ctx *ctx)
277 {
278   tp_clear_object (&ctx->account);
279   tp_clear_object (&ctx->entity);
280   tp_clear_pointer (&ctx->date, g_date_free);
281
282   g_slice_free (Ctx, ctx);
283 }
284
285 static void
286 select_account_once_ready (EmpathyLogWindow *self,
287     TpAccount *account,
288     const gchar *chat_id,
289     gboolean is_chatroom)
290 {
291   EmpathyAccountChooser *account_chooser;
292
293   account_chooser = EMPATHY_ACCOUNT_CHOOSER (self->priv->account_chooser);
294
295   tp_clear_object (&self->priv->selected_account);
296   self->priv->selected_account = g_object_ref (account);
297
298   g_free (self->priv->selected_chat_id);
299   self->priv->selected_chat_id = g_strdup (chat_id);
300
301   self->priv->selected_is_chatroom = is_chatroom;
302
303   empathy_account_chooser_set_account (account_chooser,
304       self->priv->selected_account);
305 }
306
307 static void
308 toolbutton_profile_clicked (GtkToolButton *toolbutton,
309     EmpathyLogWindow *self)
310 {
311   FolksIndividual *individual;
312
313   g_return_if_fail (self != NULL);
314   g_return_if_fail (EMPATHY_IS_CONTACT (self->priv->selected_contact));
315
316   individual = empathy_ensure_individual_from_tp_contact (
317       empathy_contact_get_tp_contact (self->priv->selected_contact));
318
319   empathy_display_individual_info (individual);
320
321   g_object_unref (individual);
322 }
323
324 static void
325 toolbutton_chat_clicked (GtkToolButton *toolbutton,
326     EmpathyLogWindow *self)
327 {
328   g_return_if_fail (self != NULL);
329   g_return_if_fail (EMPATHY_IS_CONTACT (self->priv->selected_contact));
330
331   empathy_chat_with_contact (self->priv->selected_contact,
332       gtk_get_current_event_time ());
333 }
334
335 static void
336 toolbutton_av_clicked (GtkToolButton *toolbutton,
337     EmpathyLogWindow *self)
338 {
339   gboolean video;
340
341   g_return_if_fail (self != NULL);
342   g_return_if_fail (EMPATHY_IS_CONTACT (self->priv->selected_contact));
343
344   video = (GTK_WIDGET (toolbutton) == self->priv->button_video);
345
346   empathy_call_new_with_streams (
347       empathy_contact_get_id (self->priv->selected_contact),
348       empathy_contact_get_account (self->priv->selected_contact),
349       TRUE, video, gtk_get_current_event_time ());
350 }
351
352 static void
353 insert_or_change_row (EmpathyLogWindow *self,
354     const char *method,
355     GtkTreeModel *model,
356     GtkTreePath *path,
357     GtkTreeIter *iter)
358 {
359   char *str = gtk_tree_path_to_string (path);
360   char *script, *text, *date, *stock_icon;
361   char *icon = NULL;
362
363   gtk_tree_model_get (model, iter,
364       COL_EVENTS_TEXT, &text,
365       COL_EVENTS_PRETTY_DATE, &date,
366       COL_EVENTS_ICON, &stock_icon,
367       -1);
368
369   if (!tp_str_empty (stock_icon))
370     {
371       GtkIconInfo *icon_info = gtk_icon_theme_lookup_icon (
372           gtk_icon_theme_get_default (),
373           stock_icon,
374           GTK_ICON_SIZE_MENU, 0);
375
376       if (icon_info != NULL)
377         icon = g_strdup (gtk_icon_info_get_filename (icon_info));
378
379       gtk_icon_info_free (icon_info);
380     }
381
382   script = g_strdup_printf ("javascript:%s([%s], '%s', '%s', '%s');",
383       method,
384       g_strdelimit (str, ":", ','),
385       text,
386       icon != NULL ? icon : "",
387       date);
388
389   webkit_web_view_execute_script (WEBKIT_WEB_VIEW (self->priv->webview),
390       script);
391
392   g_free (str);
393   g_free (text);
394   g_free (date);
395   g_free (stock_icon);
396   g_free (icon);
397   g_free (script);
398 }
399
400 static void
401 store_events_row_inserted (GtkTreeModel *model,
402     GtkTreePath *path,
403     GtkTreeIter *iter,
404     EmpathyLogWindow *self)
405 {
406   insert_or_change_row (self, "insertRow", model, path, iter);
407 }
408
409 static void
410 store_events_row_changed (GtkTreeModel *model,
411     GtkTreePath *path,
412     GtkTreeIter *iter,
413     EmpathyLogWindow *self)
414 {
415   insert_or_change_row (self, "changeRow", model, path, iter);
416 }
417
418 static void
419 store_events_row_deleted (GtkTreeModel *model,
420     GtkTreePath *path,
421     EmpathyLogWindow *self)
422 {
423   char *str = gtk_tree_path_to_string (path);
424   char *script;
425
426   script = g_strdup_printf ("javascript:deleteRow([%s]);",
427       g_strdelimit (str, ":", ','));
428
429   webkit_web_view_execute_script (WEBKIT_WEB_VIEW (self->priv->webview),
430       script);
431
432   g_free (str);
433   g_free (script);
434 }
435
436 static void
437 store_events_has_child_rows (GtkTreeModel *model,
438     GtkTreePath *path,
439     GtkTreeIter *iter,
440     EmpathyLogWindow *self)
441 {
442   char *str = gtk_tree_path_to_string (path);
443   char *script;
444
445   script = g_strdup_printf ("javascript:hasChildRows([%s], %u);",
446       g_strdelimit (str, ":", ','),
447       gtk_tree_model_iter_has_child (model, iter));
448
449   webkit_web_view_execute_script (WEBKIT_WEB_VIEW (self->priv->webview),
450       script);
451
452   g_free (str);
453   g_free (script);
454 }
455
456 static void
457 store_events_rows_reordered (GtkTreeModel *model,
458     GtkTreePath *path,
459     GtkTreeIter *iter,
460     int *new_order,
461     EmpathyLogWindow *self)
462 {
463   char *str = gtk_tree_path_to_string (path);
464   int i, children = gtk_tree_model_iter_n_children (model, iter);
465   char **new_order_strv, *new_order_s;
466   char *script;
467
468   new_order_strv = g_new0 (char *, children + 1);
469
470   for (i = 0; i < children; i++)
471     new_order_strv[i] = g_strdup_printf ("%i", new_order[i]);
472
473   new_order_s = g_strjoinv (",", new_order_strv);
474
475   script = g_strdup_printf ("javascript:reorderRows([%s], [%s]);",
476       str == NULL ? "" : g_strdelimit (str, ":", ','),
477       new_order_s);
478
479   webkit_web_view_execute_script (WEBKIT_WEB_VIEW (self->priv->webview),
480       script);
481
482   g_free (str);
483   g_free (script);
484   g_free (new_order_s);
485   g_strfreev (new_order_strv);
486 }
487
488 static gboolean
489 events_webview_handle_navigation (WebKitWebView *webview,
490     WebKitWebFrame *frame,
491     WebKitNetworkRequest *request,
492     WebKitWebNavigationAction *navigation_action,
493     WebKitWebPolicyDecision *policy_decision,
494     EmpathyLogWindow *window)
495 {
496   empathy_url_show (GTK_WIDGET (webview),
497       webkit_network_request_get_uri (request));
498
499   webkit_web_policy_decision_ignore (policy_decision);
500   return TRUE;
501 }
502
503 static GObject *
504 empathy_log_window_constructor (GType type,
505     guint n_props,
506     GObjectConstructParam *props)
507 {
508   GObject *retval;
509
510   if (log_window != NULL)
511     {
512       retval = (GObject *) log_window;
513     }
514   else
515     {
516       retval = G_OBJECT_CLASS (empathy_log_window_parent_class)
517           ->constructor (type, n_props, props);
518
519       log_window = EMPATHY_LOG_WINDOW (retval);
520       g_object_add_weak_pointer (retval, (gpointer) &log_window);
521     }
522
523   return retval;
524 }
525
526 static void
527 empathy_log_window_dispose (GObject *object)
528 {
529   EmpathyLogWindow *self = EMPATHY_LOG_WINDOW (object);
530
531   if (self->priv->source != 0)
532     {
533       g_source_remove (self->priv->source);
534       self->priv->source = 0;
535     }
536
537   if (self->priv->current_dates != NULL)
538     {
539       g_list_free_full (self->priv->current_dates,
540           (GDestroyNotify) g_date_free);
541       self->priv->current_dates = NULL;
542     }
543
544   tp_clear_pointer (&self->priv->chain, _tpl_action_chain_free);
545   tp_clear_pointer (&self->priv->channels, g_hash_table_unref);
546
547   tp_clear_object (&self->priv->observer);
548   tp_clear_object (&self->priv->log_manager);
549   tp_clear_object (&self->priv->selected_account);
550   tp_clear_object (&self->priv->selected_contact);
551   tp_clear_object (&self->priv->events_contact);
552   tp_clear_object (&self->priv->camera_monitor);
553
554   tp_clear_object (&self->priv->gsettings_chat);
555   tp_clear_object (&self->priv->gsettings_desktop);
556
557   tp_clear_object (&self->priv->store_events);
558
559   G_OBJECT_CLASS (empathy_log_window_parent_class)->dispose (object);
560 }
561
562 static void
563 empathy_log_window_finalize (GObject *object)
564 {
565   EmpathyLogWindow *self = EMPATHY_LOG_WINDOW (object);
566
567   g_free (self->priv->last_find);
568   g_free (self->priv->selected_chat_id);
569
570   G_OBJECT_CLASS (empathy_log_window_parent_class)->finalize (object);
571 }
572
573 static void
574 empathy_log_window_class_init (
575   EmpathyLogWindowClass *empathy_log_window_class)
576 {
577   GObjectClass *object_class = G_OBJECT_CLASS (empathy_log_window_class);
578
579   g_type_class_add_private (empathy_log_window_class,
580       sizeof (EmpathyLogWindowPriv));
581
582   object_class->constructor = empathy_log_window_constructor;
583   object_class->dispose = empathy_log_window_dispose;
584   object_class->finalize = empathy_log_window_finalize;
585 }
586
587 static void
588 empathy_log_window_init (EmpathyLogWindow *self)
589 {
590   EmpathyAccountChooser *account_chooser;
591   GtkBuilder *gui;
592   gchar *filename;
593   GFile *gfile;
594   GtkWidget *vbox, *accounts, *search, *label, *closeitem;
595   GtkWidget *scrolledwindow_events;
596   gchar *uri;
597
598   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
599       EMPATHY_TYPE_LOG_WINDOW, EmpathyLogWindowPriv);
600
601   self->priv->chain = _tpl_action_chain_new_async (NULL, NULL, NULL);
602
603   self->priv->camera_monitor = empathy_camera_monitor_dup_singleton ();
604
605   self->priv->log_manager = tpl_log_manager_dup_singleton ();
606
607   self->priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
608   self->priv->gsettings_desktop = g_settings_new (
609       EMPATHY_PREFS_DESKTOP_INTERFACE_SCHEMA);
610
611   gtk_window_set_title (GTK_WINDOW (self), _("History"));
612   gtk_widget_set_can_focus (GTK_WIDGET (self), FALSE);
613   gtk_window_set_default_size (GTK_WINDOW (self), 800, 600);
614
615   filename = empathy_file_lookup ("empathy-log-window.ui", "libempathy-gtk");
616   gui = empathy_builder_get_file (filename,
617       "vbox1", &self->priv->vbox,
618       "toolbutton_profile", &self->priv->button_profile,
619       "toolbutton_chat", &self->priv->button_chat,
620       "toolbutton_call", &self->priv->button_call,
621       "toolbutton_video", &self->priv->button_video,
622       "toolbutton_accounts", &accounts,
623       "toolbutton_search", &search,
624       "imagemenuitem_close", &closeitem,
625       "treeview_who", &self->priv->treeview_who,
626       "treeview_what", &self->priv->treeview_what,
627       "treeview_when", &self->priv->treeview_when,
628       "scrolledwindow_events", &scrolledwindow_events,
629       "notebook", &self->priv->notebook,
630       "spinner", &self->priv->spinner,
631       NULL);
632   g_free (filename);
633
634   empathy_builder_connect (gui, self,
635       "toolbutton_profile", "clicked", toolbutton_profile_clicked,
636       "toolbutton_chat", "clicked", toolbutton_chat_clicked,
637       "toolbutton_call", "clicked", toolbutton_av_clicked,
638       "toolbutton_video", "clicked", toolbutton_av_clicked,
639       "imagemenuitem_delete", "activate", log_window_delete_menu_clicked_cb,
640       NULL);
641
642   gtk_container_add (GTK_CONTAINER (self), self->priv->vbox);
643
644   g_object_unref (gui);
645
646   g_signal_connect_swapped (closeitem, "activate",
647       G_CALLBACK (gtk_widget_destroy), self);
648
649   /* Account chooser for chats */
650   vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 3);
651
652   self->priv->account_chooser = empathy_account_chooser_new ();
653   account_chooser = EMPATHY_ACCOUNT_CHOOSER (self->priv->account_chooser);
654   empathy_account_chooser_set_has_all_option (account_chooser, TRUE);
655   empathy_account_chooser_set_filter (account_chooser,
656       empathy_account_chooser_filter_has_logs, NULL);
657   empathy_account_chooser_set_all (account_chooser);
658
659   gtk_style_context_add_class (gtk_widget_get_style_context (self->priv->account_chooser),
660                                GTK_STYLE_CLASS_RAISED);
661
662   g_signal_connect (self->priv->account_chooser, "changed",
663       G_CALLBACK (log_window_chats_accounts_changed_cb),
664       self);
665
666   label = gtk_label_new (_("Show"));
667
668   gtk_box_pack_start (GTK_BOX (vbox),
669       self->priv->account_chooser,
670       FALSE, FALSE, 0);
671
672   gtk_box_pack_start (GTK_BOX (vbox),
673       label,
674       FALSE, FALSE, 0);
675
676   gtk_widget_show_all (vbox);
677   gtk_container_add (GTK_CONTAINER (accounts), vbox);
678
679   /* Search entry */
680   vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 3);
681
682   self->priv->search_entry = gtk_entry_new ();
683   gtk_entry_set_icon_from_icon_name (GTK_ENTRY (self->priv->search_entry),
684       GTK_ENTRY_ICON_SECONDARY, "edit-find-symbolic");
685   gtk_entry_set_icon_sensitive (GTK_ENTRY (self->priv->search_entry),
686                                 GTK_ENTRY_ICON_SECONDARY, FALSE);
687
688   label = gtk_label_new (_("Search"));
689
690   gtk_box_pack_start (GTK_BOX (vbox),
691       self->priv->search_entry,
692       FALSE, FALSE, 0);
693
694   gtk_box_pack_start (GTK_BOX (vbox),
695       label,
696       FALSE, FALSE, 0);
697
698   gtk_widget_show_all (vbox);
699   gtk_container_add (GTK_CONTAINER (search), vbox);
700
701   g_signal_connect (self->priv->search_entry, "changed",
702       G_CALLBACK (log_window_search_entry_changed_cb),
703       self);
704
705   g_signal_connect (self->priv->search_entry, "activate",
706       G_CALLBACK (log_window_search_entry_activate_cb),
707       self);
708
709   g_signal_connect (self->priv->search_entry, "icon-press",
710       G_CALLBACK (log_window_search_entry_icon_pressed_cb),
711       self);
712
713   /* Contacts */
714   log_window_events_setup (self);
715   log_window_who_setup (self);
716   log_window_what_setup (self);
717   log_window_when_setup (self);
718
719   log_window_create_observer (self);
720
721   log_window_who_populate (self);
722
723   /* events */
724   self->priv->webview = webkit_web_view_new ();
725   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwindow_events),
726       GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
727   gtk_container_add (GTK_CONTAINER (scrolledwindow_events),
728       self->priv->webview);
729   gtk_widget_show (self->priv->webview);
730
731   empathy_webkit_bind_font_setting (WEBKIT_WEB_VIEW (self->priv->webview),
732       self->priv->gsettings_desktop,
733       EMPATHY_PREFS_DESKTOP_INTERFACE_FONT_NAME);
734
735   filename = empathy_file_lookup ("empathy-log-window.html", "data");
736   gfile = g_file_new_for_path (filename);
737   g_free (filename);
738
739   uri = g_file_get_uri (gfile);
740   webkit_web_view_load_uri (WEBKIT_WEB_VIEW (self->priv->webview), uri);
741   g_object_unref (gfile);
742   g_free (uri);
743
744   /* handle all navigation externally */
745   g_signal_connect (self->priv->webview, "navigation-policy-decision-requested",
746       G_CALLBACK (events_webview_handle_navigation), self);
747
748   /* listen to changes to the treemodel */
749   g_signal_connect (self->priv->store_events, "row-inserted",
750       G_CALLBACK (store_events_row_inserted), self);
751   g_signal_connect (self->priv->store_events, "row-changed",
752       G_CALLBACK (store_events_row_changed), self);
753   g_signal_connect (self->priv->store_events, "row-deleted",
754       G_CALLBACK (store_events_row_deleted), self);
755   g_signal_connect (self->priv->store_events, "rows-reordered",
756       G_CALLBACK (store_events_rows_reordered), self);
757   g_signal_connect (self->priv->store_events, "row-has-child-toggled",
758       G_CALLBACK (store_events_has_child_rows), self);
759
760   /* track clicked row */
761   g_signal_connect (self->priv->webview, "button-press-event",
762       G_CALLBACK (log_window_events_button_press_event), self);
763
764   log_window_update_buttons_sensitivity (self);
765   gtk_widget_show (GTK_WIDGET (self));
766
767   empathy_geometry_bind (GTK_WINDOW (self), "log-window");
768 }
769
770 GtkWidget *
771 empathy_log_window_show (TpAccount *account,
772      const gchar *chat_id,
773      gboolean is_chatroom,
774      GtkWindow *parent)
775 {
776   log_window = g_object_new (EMPATHY_TYPE_LOG_WINDOW, NULL);
777
778   gtk_window_present (GTK_WINDOW (log_window));
779
780   if (account != NULL && chat_id != NULL)
781     select_account_once_ready (log_window, account, chat_id, is_chatroom);
782
783   if (parent != NULL)
784     gtk_window_set_transient_for (GTK_WINDOW (log_window),
785         GTK_WINDOW (parent));
786
787   return GTK_WIDGET (log_window);
788 }
789
790 static gboolean
791 account_equal (TpAccount *a,
792     TpAccount *b)
793 {
794   return g_str_equal (tp_proxy_get_object_path (a),
795       tp_proxy_get_object_path (b));
796 }
797
798 static gboolean
799 entity_equal (TplEntity *a,
800     TplEntity *b)
801 {
802   return g_str_equal (tpl_entity_get_identifier (a),
803       tpl_entity_get_identifier (b));
804 }
805
806 static gboolean
807 is_same_confroom (TplEvent *e1,
808     TplEvent *e2)
809 {
810   TplEntity *sender1 = tpl_event_get_sender (e1);
811   TplEntity *receiver1 = tpl_event_get_receiver (e1);
812   TplEntity *sender2 = tpl_event_get_sender (e2);
813   TplEntity *receiver2 = tpl_event_get_receiver (e2);
814   TplEntity *room1, *room2;
815
816   if (receiver1 == NULL || receiver2 == NULL)
817     return FALSE;
818
819   if (tpl_entity_get_entity_type (sender1) == TPL_ENTITY_ROOM)
820     room1 = sender1;
821   else if (tpl_entity_get_entity_type (receiver1) == TPL_ENTITY_ROOM)
822     room1 = receiver1;
823   else
824     return FALSE;
825
826   if (tpl_entity_get_entity_type (sender2) == TPL_ENTITY_ROOM)
827     room2 = sender2;
828   else if (tpl_entity_get_entity_type (receiver2) == TPL_ENTITY_ROOM)
829     room2 = receiver2;
830   else
831     return FALSE;
832
833   return g_str_equal (tpl_entity_get_identifier (room1),
834       tpl_entity_get_identifier (room2));
835 }
836
837 static void
838 maybe_refresh_logs (TpChannel *channel,
839     TpAccount *account)
840 {
841   GList *accounts = NULL, *entities = NULL, *dates = NULL;
842   GList *acc, *ent;
843   TplEventTypeMask event_mask;
844   GDate *anytime = NULL, *today = NULL;
845   GDateTime *now = NULL;
846   gboolean refresh = FALSE;
847   gboolean anyone;
848   const gchar *type;
849
850   if (!log_window_get_selected (log_window,
851       &accounts, &entities, &anyone, &dates, &event_mask, NULL))
852     {
853       DEBUG ("Could not get selected rows");
854       return;
855     }
856
857   type = tp_channel_get_channel_type (channel);
858
859   /* If the channel type is not in the What pane, whatever has happened
860    * won't be displayed in the events pane. */
861   if (!tp_strdiff (type, TP_IFACE_CHANNEL_TYPE_TEXT) &&
862       !(event_mask & TPL_EVENT_MASK_TEXT))
863     goto out;
864   if ((!tp_strdiff (type, TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA) ||
865        !tp_strdiff (type, TP_IFACE_CHANNEL_TYPE_CALL)) &&
866       !(event_mask & TPL_EVENT_MASK_CALL))
867     goto out;
868
869   anytime = g_date_new_dmy (2, 1, -1);
870   now = g_date_time_new_now_local ();
871   today = g_date_new_dmy (g_date_time_get_day_of_month (now),
872       g_date_time_get_month (now),
873       g_date_time_get_year (now));
874
875   /* If Today (or anytime) isn't selected, anything that has happened now
876    * won't be displayed. */
877   if (!g_list_find_custom (dates, anytime, (GCompareFunc) g_date_compare) &&
878       !g_list_find_custom (dates, today, (GCompareFunc) g_date_compare))
879     goto out;
880
881   if (anyone)
882     {
883       refresh = TRUE;
884       goto out;
885     }
886
887   for (acc = accounts, ent = entities;
888        acc != NULL && ent != NULL;
889        acc = g_list_next (acc), ent = g_list_next (ent))
890     {
891       if (!account_equal (account, acc->data))
892         continue;
893
894       if (!tp_strdiff (tp_channel_get_identifier (channel),
895                        tpl_entity_get_identifier (ent->data)))
896         {
897           refresh = TRUE;
898           break;
899         }
900     }
901
902  out:
903   tp_clear_pointer (&anytime, g_date_free);
904   tp_clear_pointer (&today, g_date_free);
905   tp_clear_pointer (&now, g_date_time_unref);
906   g_list_free_full (accounts, g_object_unref);
907   g_list_free_full (entities, g_object_unref);
908   g_list_free_full (dates, (GFreeFunc) g_date_free);
909
910   if (refresh)
911     {
912       DEBUG ("Refreshing logs after received event");
913
914       /* FIXME:  We need to populate the entities in case we
915        * didn't have any previous logs with this contact. */
916       log_window_chats_get_messages (log_window, FALSE);
917     }
918 }
919
920 static void
921 on_msg_sent (TpTextChannel *channel,
922     TpSignalledMessage *message,
923     guint flags,
924     gchar *token,
925     EmpathyLogWindow *self)
926 {
927   TpAccount *account = g_hash_table_lookup (self->priv->channels, channel);
928
929   maybe_refresh_logs (TP_CHANNEL (channel), account);
930 }
931
932 static void
933 on_msg_received (TpTextChannel *channel,
934     TpSignalledMessage *message,
935     EmpathyLogWindow *self)
936 {
937   TpMessage *msg = TP_MESSAGE (message);
938   TpChannelTextMessageType type = tp_message_get_message_type (msg);
939   TpAccount *account = g_hash_table_lookup (self->priv->channels, channel);
940
941   if (type != TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL &&
942       type != TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION)
943     return;
944
945   maybe_refresh_logs (TP_CHANNEL (channel), account);
946 }
947
948 static void
949 on_channel_ended (TpChannel *channel,
950     guint domain,
951     gint code,
952     gchar *message,
953     EmpathyLogWindow *self)
954 {
955   if (self->priv->channels != NULL)
956     g_hash_table_remove (self->priv->channels, channel);
957 }
958
959 static void
960 on_call_ended (TpChannel *channel,
961     guint domain,
962     gint code,
963     gchar *message,
964     EmpathyLogWindow *self)
965 {
966   TpAccount *account = g_hash_table_lookup (self->priv->channels, channel);
967
968   maybe_refresh_logs (channel, account);
969
970   if (self->priv->channels != NULL)
971     g_hash_table_remove (self->priv->channels, channel);
972 }
973
974 static void
975 observe_channels (TpSimpleObserver *observer,
976     TpAccount *account,
977     TpConnection *connection,
978     GList *channels,
979     TpChannelDispatchOperation *dispatch_operation,
980     GList *requests,
981     TpObserveChannelsContext *context,
982     gpointer user_data)
983 {
984   EmpathyLogWindow *self = user_data;
985
986   GList *l;
987
988   for (l = channels; l != NULL; l = g_list_next (l))
989     {
990       TpChannel *channel = l->data;
991       const gchar *type = tp_channel_get_channel_type (channel);
992
993       if (!tp_strdiff (type, TP_IFACE_CHANNEL_TYPE_TEXT))
994         {
995           TpTextChannel *text_channel = TP_TEXT_CHANNEL (channel);
996
997           g_hash_table_insert (self->priv->channels,
998               g_object_ref (channel), g_object_ref (account));
999
1000           tp_g_signal_connect_object (text_channel, "message-sent",
1001               G_CALLBACK (on_msg_sent), self, 0);
1002           tp_g_signal_connect_object (text_channel, "message-received",
1003               G_CALLBACK (on_msg_received), self, 0);
1004           tp_g_signal_connect_object (channel, "invalidated",
1005               G_CALLBACK (on_channel_ended), self, 0);
1006         }
1007       else if (!tp_strdiff (type, TP_IFACE_CHANNEL_TYPE_CALL) ||
1008           !tp_strdiff (type, TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA))
1009         {
1010           g_hash_table_insert (self->priv->channels,
1011               g_object_ref (channel), g_object_ref (account));
1012
1013           tp_g_signal_connect_object (channel, "invalidated",
1014               G_CALLBACK (on_call_ended), self, 0);
1015         }
1016       else
1017         {
1018           g_warning ("Unknown channel type: %s", type);
1019         }
1020     }
1021
1022   tp_observe_channels_context_accept (context);
1023 }
1024
1025 static void
1026 log_window_create_observer (EmpathyLogWindow *self)
1027 {
1028   TpAccountManager *am;
1029
1030   am = tp_account_manager_dup ();
1031
1032   self->priv->observer = tp_simple_observer_new_with_am (am, TRUE, "LogWindow",
1033       TRUE, observe_channels,
1034       g_object_ref (self), g_object_unref);
1035
1036   self->priv->channels = g_hash_table_new_full (g_direct_hash, g_direct_equal,
1037       g_object_unref, g_object_unref);
1038
1039   tp_base_client_take_observer_filter (self->priv->observer,
1040       tp_asv_new (
1041           TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
1042             TP_IFACE_CHANNEL_TYPE_TEXT,
1043           NULL));
1044   tp_base_client_take_observer_filter (self->priv->observer,
1045       tp_asv_new (
1046           TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
1047             TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA,
1048           NULL));
1049   tp_base_client_take_observer_filter (self->priv->observer,
1050       tp_asv_new (
1051           TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
1052             TP_IFACE_CHANNEL_TYPE_CALL,
1053           NULL));
1054
1055   tp_base_client_register (self->priv->observer, NULL);
1056
1057   g_object_unref (am);
1058 }
1059
1060 static TplEntity *
1061 event_get_target (TplEvent *event)
1062 {
1063   TplEntity *sender = tpl_event_get_sender (event);
1064   TplEntity *receiver = tpl_event_get_receiver (event);
1065
1066   if (tpl_entity_get_entity_type (sender) == TPL_ENTITY_SELF)
1067     return receiver;
1068
1069   return sender;
1070 }
1071
1072 static gboolean
1073 model_is_parent (GtkTreeModel *model,
1074     GtkTreeIter *iter,
1075     TplEvent *event)
1076 {
1077   TplEvent *stored_event;
1078   TplEntity *target;
1079   TpAccount *account;
1080   gboolean found = FALSE;
1081   GtkTreeIter parent;
1082
1083   if (gtk_tree_model_iter_parent (model, &parent, iter))
1084     return FALSE;
1085
1086   gtk_tree_model_get (model, iter,
1087       COL_EVENTS_ACCOUNT, &account,
1088       COL_EVENTS_TARGET, &target,
1089       COL_EVENTS_EVENT, &stored_event,
1090       -1);
1091
1092   if (G_OBJECT_TYPE (event) == G_OBJECT_TYPE (stored_event) &&
1093       account_equal (account, tpl_event_get_account (event)) &&
1094       (entity_equal (target, event_get_target (event)) ||
1095       is_same_confroom (event, stored_event)))
1096     {
1097       GtkTreeIter child;
1098       gint64 timestamp;
1099
1100       gtk_tree_model_iter_nth_child (model, &child, iter,
1101           gtk_tree_model_iter_n_children (model, iter) - 1);
1102
1103       gtk_tree_model_get (model, &child,
1104           COL_EVENTS_TS, &timestamp,
1105           -1);
1106
1107       if (ABS (tpl_event_get_timestamp (event) - timestamp) < MAX_GAP)
1108         {
1109           /* The gap is smaller than 30 min */
1110           found = TRUE;
1111         }
1112     }
1113
1114   g_object_unref (stored_event);
1115   g_object_unref (account);
1116   g_object_unref (target);
1117
1118   return found;
1119 }
1120
1121 static gchar *
1122 get_display_string_for_chat_message (EmpathyMessage *message,
1123     TplEvent *event)
1124 {
1125   EmpathyContact *sender, *receiver, *target;
1126   TplEntity *ent_sender, *ent_receiver;
1127   const gchar *format;
1128
1129   sender = empathy_message_get_sender (message);
1130   receiver = empathy_message_get_receiver (message);
1131
1132   ent_sender = tpl_event_get_sender (event);
1133   ent_receiver = tpl_event_get_receiver (event);
1134
1135   /* If this is a MUC, we want to show "Chat in <room>". */
1136   if (tpl_entity_get_entity_type (ent_sender) == TPL_ENTITY_ROOM ||
1137       (ent_receiver != NULL &&
1138       tpl_entity_get_entity_type (ent_receiver) == TPL_ENTITY_ROOM))
1139     format = _("Chat in %s");
1140   else
1141     format = _("Chat with %s");
1142
1143   if (tpl_entity_get_entity_type (ent_sender) == TPL_ENTITY_ROOM)
1144     target = sender;
1145   else if (ent_receiver != NULL &&
1146       tpl_entity_get_entity_type (ent_receiver) == TPL_ENTITY_ROOM)
1147     target = receiver;
1148   else if (empathy_contact_is_user (sender))
1149     target = receiver;
1150   else
1151     target = sender;
1152
1153   return g_markup_printf_escaped (format, empathy_contact_get_alias (target));
1154 }
1155
1156 static void
1157 get_parent_iter_for_message (TplEvent *event,
1158     EmpathyMessage *message,
1159     GtkTreeIter *parent)
1160 {
1161   GtkTreeStore *store;
1162   GtkTreeModel *model;
1163   GtkTreeIter iter;
1164   gboolean parent_found = FALSE;
1165   gboolean next;
1166
1167   store = log_window->priv->store_events;
1168   model = GTK_TREE_MODEL (store);
1169
1170   for (next = gtk_tree_model_get_iter_first (model, &iter);
1171        next;
1172        next = gtk_tree_model_iter_next (model, &iter))
1173     {
1174       if ((parent_found = model_is_parent (model, &iter, event)))
1175         break;
1176     }
1177
1178   if (parent_found)
1179     {
1180       *parent = iter;
1181     }
1182   else
1183     {
1184       GDateTime *date;
1185       gchar *body, *pretty_date;
1186
1187       date = g_date_time_new_from_unix_local (
1188           tpl_event_get_timestamp (event));
1189
1190       pretty_date = g_date_time_format (date,
1191           C_("A date with the time", "%A, %e %B %Y %X"));
1192
1193       body = get_display_string_for_chat_message (message, event);
1194
1195       gtk_tree_store_append (store, &iter, NULL);
1196       gtk_tree_store_set (store, &iter,
1197           COL_EVENTS_TS, tpl_event_get_timestamp (event),
1198           COL_EVENTS_PRETTY_DATE, pretty_date,
1199           COL_EVENTS_TEXT, body,
1200           COL_EVENTS_ICON, "format-justify-fill",
1201           COL_EVENTS_ACCOUNT, tpl_event_get_account (event),
1202           COL_EVENTS_TARGET, event_get_target (event),
1203           COL_EVENTS_EVENT, event,
1204           -1);
1205
1206       *parent = iter;
1207
1208       g_free (body);
1209       g_free (pretty_date);
1210       g_date_time_unref (date);
1211     }
1212 }
1213
1214 static const gchar *
1215 get_icon_for_event (TplEvent *event)
1216 {
1217   const gchar *icon = NULL;
1218
1219   if (TPL_IS_TEXT_EVENT (event))
1220     {
1221       TplTextEvent *text = TPL_TEXT_EVENT (event);
1222
1223       if (!tp_str_empty (tpl_text_event_get_supersedes_token (text)))
1224         icon = EMPATHY_IMAGE_EDIT_MESSAGE;
1225     }
1226   else if (TPL_IS_CALL_EVENT (event))
1227     {
1228       TplCallEvent *call = TPL_CALL_EVENT (event);
1229       TpCallStateChangeReason reason = tpl_call_event_get_end_reason (call);
1230       TplEntity *sender = tpl_event_get_sender (event);
1231       TplEntity *receiver = tpl_event_get_receiver (event);
1232
1233       if (reason == TP_CALL_STATE_CHANGE_REASON_NO_ANSWER)
1234         icon = EMPATHY_IMAGE_CALL_MISSED;
1235       else if (tpl_entity_get_entity_type (sender) == TPL_ENTITY_SELF)
1236         icon = EMPATHY_IMAGE_CALL_OUTGOING;
1237       else if (tpl_entity_get_entity_type (receiver) == TPL_ENTITY_SELF)
1238         icon = EMPATHY_IMAGE_CALL_INCOMING;
1239     }
1240
1241   return icon;
1242 }
1243
1244 static void
1245 log_window_append_chat_message (TplEvent *event,
1246     EmpathyMessage *message)
1247 {
1248   GtkTreeStore *store = log_window->priv->store_events;
1249   GtkTreeIter iter, parent;
1250   gchar *pretty_date, *alias, *body;
1251   GDateTime *date;
1252   EmpathyStringParser *parsers;
1253   GString *msg;
1254
1255   date = g_date_time_new_from_unix_local (
1256       tpl_event_get_timestamp (event));
1257
1258   pretty_date = g_date_time_format (date, "%X");
1259
1260   get_parent_iter_for_message (event, message, &parent);
1261
1262   alias = g_markup_escape_text (
1263       tpl_entity_get_alias (tpl_event_get_sender (event)), -1);
1264
1265   /* escape the text */
1266   parsers = empathy_webkit_get_string_parser (
1267       g_settings_get_boolean (log_window->priv->gsettings_chat,
1268         EMPATHY_PREFS_CHAT_SHOW_SMILEYS));
1269   msg = g_string_new ("");
1270
1271   empathy_string_parser_substr (empathy_message_get_body (message), -1,
1272       parsers, msg);
1273
1274   if (tpl_text_event_get_message_type (TPL_TEXT_EVENT (event))
1275       == TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION)
1276     {
1277       /* Translators: this is an emote: '* Danielle waves' */
1278       body = g_strdup_printf (_("<i>* %s %s</i>"), alias, msg->str);
1279     }
1280   else
1281     {
1282       /* Translators: this is a message: 'Danielle: hello'
1283        * The string in bold is the sender's name */
1284       body = g_strdup_printf (_("<b>%s:</b> %s"), alias, msg->str);
1285     }
1286
1287   gtk_tree_store_append (store, &iter, &parent);
1288   gtk_tree_store_set (store, &iter,
1289       COL_EVENTS_TS, tpl_event_get_timestamp (event),
1290       COL_EVENTS_PRETTY_DATE, pretty_date,
1291       COL_EVENTS_TEXT, body,
1292       COL_EVENTS_ICON, get_icon_for_event (event),
1293       COL_EVENTS_ACCOUNT, tpl_event_get_account (event),
1294       COL_EVENTS_TARGET, event_get_target (event),
1295       COL_EVENTS_EVENT, event,
1296       -1);
1297
1298   g_string_free (msg, TRUE);
1299   g_free (body);
1300   g_free (alias);
1301   g_free (pretty_date);
1302   g_date_time_unref (date);
1303 }
1304
1305 static void
1306 log_window_append_call (TplEvent *event,
1307     EmpathyMessage *message)
1308 {
1309   TplCallEvent *call = TPL_CALL_EVENT (event);
1310   GtkTreeStore *store = log_window->priv->store_events;
1311   GtkTreeIter iter, child;
1312   gchar *pretty_date, *duration, *finished;
1313   GDateTime *started_date, *finished_date;
1314   GTimeSpan span;
1315
1316   /* If searching, only add the call if the search string appears anywhere */
1317   if (!EMP_STR_EMPTY (log_window->priv->last_find))
1318     {
1319       if (strstr (tpl_entity_get_identifier (tpl_event_get_sender (event)),
1320               log_window->priv->last_find) == NULL &&
1321           strstr (tpl_entity_get_identifier (tpl_event_get_receiver (event)),
1322               log_window->priv->last_find) == NULL &&
1323           strstr (tpl_call_event_get_detailed_end_reason (call),
1324               log_window->priv->last_find) == NULL)
1325         {
1326           DEBUG ("TplCallEvent doesn't match search string, ignoring");
1327           return;
1328         }
1329     }
1330
1331   started_date = g_date_time_new_from_unix_local (
1332       tpl_event_get_timestamp (event));
1333
1334   pretty_date = g_date_time_format (started_date,
1335       C_("A date with the time", "%A, %e %B %Y %X"));
1336
1337   gtk_tree_store_append (store, &iter, NULL);
1338   gtk_tree_store_set (store, &iter,
1339       COL_EVENTS_TS, tpl_event_get_timestamp (event),
1340       COL_EVENTS_PRETTY_DATE, pretty_date,
1341       COL_EVENTS_TEXT, empathy_message_get_body (message),
1342       COL_EVENTS_ICON, get_icon_for_event (event),
1343       COL_EVENTS_ACCOUNT, tpl_event_get_account (event),
1344       COL_EVENTS_TARGET, event_get_target (event),
1345       COL_EVENTS_EVENT, event,
1346       -1);
1347
1348   if (tpl_call_event_get_end_reason (call) != TP_CALL_STATE_CHANGE_REASON_NO_ANSWER)
1349     {
1350       gchar *body;
1351       gchar *tmp;
1352
1353       span = tpl_call_event_get_duration (TPL_CALL_EVENT (event));
1354
1355       if (span < 60)
1356         {
1357           tmp = g_strdup_printf ("%" G_GINT64_FORMAT, span);
1358           duration = g_strdup_printf (
1359               ngettext ("%s second", "%s seconds", span), tmp);
1360           g_free (tmp);
1361         }
1362       else
1363         {
1364           tmp = g_strdup_printf ("%" G_GINT64_FORMAT, span / 60);
1365           duration = g_strdup_printf (
1366               ngettext ("%s minute", "%s minutes", span / 60), tmp);
1367           g_free (tmp);
1368         }
1369
1370       finished_date = g_date_time_add (started_date, -span);
1371       finished = g_date_time_format (finished_date, "%X");
1372       g_date_time_unref (finished_date);
1373
1374       body = g_strdup_printf (_("Call took %s, ended at %s"),
1375           duration, finished);
1376
1377       g_free (duration);
1378       g_free (finished);
1379
1380       gtk_tree_store_append (store, &child, &iter);
1381       gtk_tree_store_set (store, &child,
1382           COL_EVENTS_TS, tpl_event_get_timestamp (event),
1383           COL_EVENTS_TEXT, body,
1384           COL_EVENTS_ACCOUNT, tpl_event_get_account (event),
1385           COL_EVENTS_TARGET, event_get_target (event),
1386           COL_EVENTS_EVENT, event,
1387           -1);
1388
1389       g_free (body);
1390     }
1391
1392   g_free (pretty_date);
1393   g_date_time_unref (started_date);
1394 }
1395
1396 static void
1397 log_window_append_message (TplEvent *event,
1398     EmpathyMessage *message)
1399 {
1400   if (TPL_IS_TEXT_EVENT (event))
1401     log_window_append_chat_message (event, message);
1402   else if (TPL_IS_CALL_EVENT (event))
1403     log_window_append_call (event, message);
1404   else
1405     DEBUG ("Message type not handled");
1406 }
1407
1408 static void
1409 add_all_accounts_and_entities (GList **accounts,
1410     GList **entities)
1411 {
1412   GtkTreeView      *view;
1413   GtkTreeModel     *model;
1414   GtkTreeIter       iter;
1415
1416   view = GTK_TREE_VIEW (log_window->priv->treeview_who);
1417   model = gtk_tree_view_get_model (view);
1418
1419   if (!gtk_tree_model_get_iter_first (model, &iter))
1420     return;
1421
1422   do
1423     {
1424       TpAccount *account;
1425       TplEntity *entity;
1426       gint type;
1427
1428       gtk_tree_model_get (model, &iter,
1429           COL_WHO_ACCOUNT, &account,
1430           COL_WHO_TARGET, &entity,
1431           COL_WHO_TYPE, &type,
1432           -1);
1433
1434       if (type != COL_TYPE_NORMAL)
1435         continue;
1436
1437       if (accounts != NULL)
1438         *accounts = g_list_append (*accounts, account);
1439
1440       if (entities != NULL)
1441         *entities = g_list_append (*entities, entity);
1442     }
1443   while (gtk_tree_model_iter_next (model, &iter));
1444 }
1445
1446 static gboolean
1447 log_window_get_selected (EmpathyLogWindow *self,
1448     GList **accounts,
1449     GList **entities,
1450     gboolean *anyone,
1451     GList **dates,
1452     TplEventTypeMask *event_mask,
1453     EventSubtype *subtype)
1454 {
1455   GtkTreeView      *view;
1456   GtkTreeModel     *model;
1457   GtkTreeSelection *selection;
1458   GtkTreeIter       iter;
1459   TplEventTypeMask  ev = 0;
1460   EventSubtype      st = 0;
1461   GList            *paths, *l;
1462   gint              type;
1463
1464   view = GTK_TREE_VIEW (self->priv->treeview_who);
1465   model = gtk_tree_view_get_model (view);
1466   selection = gtk_tree_view_get_selection (view);
1467
1468   paths = gtk_tree_selection_get_selected_rows (selection, NULL);
1469   if (paths == NULL)
1470     return FALSE;
1471
1472   if (accounts != NULL)
1473     *accounts = NULL;
1474   if (entities != NULL)
1475     *entities = NULL;
1476   if (anyone != NULL)
1477     *anyone = FALSE;
1478
1479   for (l = paths; l != NULL; l = l->next)
1480     {
1481       GtkTreePath *path = l->data;
1482       TpAccount *account;
1483       TplEntity *entity;
1484
1485       gtk_tree_model_get_iter (model, &iter, path);
1486       gtk_tree_model_get (model, &iter,
1487           COL_WHO_ACCOUNT, &account,
1488           COL_WHO_TARGET, &entity,
1489           COL_WHO_TYPE, &type,
1490           -1);
1491
1492       if (type == COL_TYPE_ANY)
1493         {
1494           if (accounts != NULL || entities != NULL)
1495             add_all_accounts_and_entities (accounts, entities);
1496           if (anyone != NULL)
1497             *anyone = TRUE;
1498           break;
1499         }
1500
1501       if (accounts != NULL)
1502         *accounts = g_list_append (*accounts, g_object_ref (account));
1503
1504       if (entities != NULL)
1505         *entities = g_list_append (*entities, g_object_ref (entity));
1506
1507       g_object_unref (account);
1508       g_object_unref (entity);
1509     }
1510   g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free);
1511
1512   view = GTK_TREE_VIEW (self->priv->treeview_what);
1513   model = gtk_tree_view_get_model (view);
1514   selection = gtk_tree_view_get_selection (view);
1515
1516   paths = gtk_tree_selection_get_selected_rows (selection, NULL);
1517   for (l = paths; l != NULL; l = l->next)
1518     {
1519       GtkTreePath *path = l->data;
1520       TplEventTypeMask mask;
1521       EventSubtype submask;
1522
1523       gtk_tree_model_get_iter (model, &iter, path);
1524       gtk_tree_model_get (model, &iter,
1525           COL_WHAT_TYPE, &mask,
1526           COL_WHAT_SUBTYPE, &submask,
1527           -1);
1528
1529       ev |= mask;
1530       st |= submask;
1531     }
1532   g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free);
1533
1534   view = GTK_TREE_VIEW (self->priv->treeview_when);
1535   model = gtk_tree_view_get_model (view);
1536   selection = gtk_tree_view_get_selection (view);
1537
1538   if (dates != NULL)
1539     {
1540       *dates = NULL;
1541
1542       paths = gtk_tree_selection_get_selected_rows (selection, NULL);
1543       for (l = paths; l != NULL; l = l->next)
1544         {
1545           GtkTreePath *path = l->data;
1546           GDate *date;
1547
1548           gtk_tree_model_get_iter (model, &iter, path);
1549           gtk_tree_model_get (model, &iter,
1550               COL_WHEN_DATE, &date,
1551               -1);
1552
1553           *dates = g_list_append (*dates, date);
1554         }
1555       g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free);
1556     }
1557
1558   if (event_mask != NULL)
1559     *event_mask = ev;
1560
1561   if (subtype != NULL)
1562     *subtype = st;
1563
1564   return TRUE;
1565 }
1566
1567 static gboolean
1568 model_has_entity (GtkTreeModel *model,
1569     GtkTreePath *path,
1570     GtkTreeIter *iter,
1571     gpointer data)
1572 {
1573   TplLogSearchHit *hit = data;
1574   TplEntity *e;
1575   TpAccount *a;
1576   gboolean ret = FALSE;
1577
1578   gtk_tree_model_get (model, iter,
1579       COL_WHO_TARGET, &e,
1580       COL_WHO_ACCOUNT, &a,
1581       -1);
1582
1583   if (e != NULL && entity_equal (hit->target, e) &&
1584       a != NULL && account_equal (hit->account, a))
1585     {
1586       ret = has_element = TRUE;
1587     }
1588
1589   tp_clear_object (&e);
1590   tp_clear_object (&a);
1591
1592   return ret;
1593 }
1594
1595 static gboolean
1596 model_has_date (GtkTreeModel *model,
1597     GtkTreePath *path,
1598     GtkTreeIter *iter,
1599     gpointer data)
1600 {
1601   GDate *date = data;
1602   GDate *d;
1603
1604   gtk_tree_model_get (model, iter,
1605       COL_WHEN_DATE, &d,
1606       -1);
1607
1608   if (!g_date_compare (date, d))
1609     {
1610       has_element = TRUE;
1611       g_date_free (d);
1612       return TRUE;
1613     }
1614
1615   g_date_free (d);
1616   return FALSE;
1617 }
1618
1619 static void
1620 get_events_for_date (TplActionChain *chain, gpointer user_data);
1621
1622 static void
1623 populate_events_from_search_hits (GList *accounts,
1624     GList *targets,
1625     GList *dates)
1626 {
1627   TplEventTypeMask event_mask;
1628   EventSubtype subtype;
1629   GDate *anytime;
1630   GList *l;
1631   gboolean is_anytime = FALSE;
1632
1633   if (!log_window_get_selected (log_window,
1634       NULL, NULL, NULL, NULL, &event_mask, &subtype))
1635     return;
1636
1637   anytime = g_date_new_dmy (2, 1, -1);
1638   if (g_list_find_custom (dates, anytime, (GCompareFunc) g_date_compare))
1639     is_anytime = TRUE;
1640
1641   for (l = log_window->priv->hits; l != NULL; l = l->next)
1642     {
1643       TplLogSearchHit *hit = l->data;
1644       GList *acc, *targ;
1645       gboolean found = FALSE;
1646
1647       /* Protect against invalid data (corrupt or old log files). */
1648       if (hit->account == NULL || hit->target == NULL)
1649         continue;
1650
1651       for (acc = accounts, targ = targets;
1652            acc != NULL && targ != NULL && !found;
1653            acc = acc->next, targ = targ->next)
1654         {
1655           TpAccount *account = acc->data;
1656           TplEntity *target = targ->data;
1657
1658           if (account_equal (hit->account, account) &&
1659               entity_equal (hit->target, target))
1660             found = TRUE;
1661         }
1662
1663         if (!found)
1664           continue;
1665
1666       if (is_anytime ||
1667           g_list_find_custom (dates, hit->date, (GCompareFunc) g_date_compare)
1668               != NULL)
1669         {
1670           Ctx *ctx;
1671
1672           ctx = ctx_new (log_window, hit->account, hit->target, hit->date,
1673               event_mask, subtype, log_window->priv->count);
1674           _tpl_action_chain_append (log_window->priv->chain,
1675               get_events_for_date, ctx);
1676         }
1677     }
1678
1679   start_spinner ();
1680   _tpl_action_chain_start (log_window->priv->chain);
1681
1682   g_date_free (anytime);
1683 }
1684
1685 static gchar *
1686 format_date_for_display (GDate *date)
1687 {
1688   gchar *text;
1689   GDate *now = NULL;
1690   gint days_elapsed;
1691
1692   /* g_date_strftime sucks */
1693
1694   now = g_date_new ();
1695   g_date_set_time_t (now, time (NULL));
1696
1697   days_elapsed = g_date_days_between (date, now);
1698
1699   if (days_elapsed < 0)
1700     {
1701       text = NULL;
1702     }
1703   else if (days_elapsed == 0)
1704     {
1705       text = g_strdup (_("Today"));
1706     }
1707   else if (days_elapsed == 1)
1708     {
1709       text = g_strdup (_("Yesterday"));
1710     }
1711   else
1712     {
1713       GDateTime *dt;
1714
1715       dt = g_date_time_new_utc (g_date_get_year (date),
1716           g_date_get_month (date), g_date_get_day (date),
1717           0, 0, 0);
1718
1719       if (days_elapsed <= 7)
1720         text = g_date_time_format (dt, "%A");
1721       else
1722         text = g_date_time_format (dt,
1723             /* Translators: A date such as '23 May 2010' (strftime format) */
1724             _("%e %B %Y"));
1725
1726       g_date_time_unref (dt);
1727     }
1728
1729   g_date_free (now);
1730
1731   return text;
1732 }
1733
1734 static void
1735 add_date_if_needed (EmpathyLogWindow *self,
1736     GDate *date)
1737 {
1738   GtkTreeModel *model;
1739   GtkListStore *store;
1740   gchar *text;
1741
1742   model = gtk_tree_view_get_model (GTK_TREE_VIEW (
1743         log_window->priv->treeview_when));
1744   store = GTK_LIST_STORE (model);
1745
1746   /* Add the date if it's not already there */
1747   has_element = FALSE;
1748   gtk_tree_model_foreach (model, model_has_date, date);
1749   if (has_element)
1750     return;
1751
1752   text = format_date_for_display (date);
1753
1754   gtk_list_store_insert_with_values (store, NULL, -1,
1755       COL_WHEN_DATE, date,
1756       COL_WHEN_TEXT, text,
1757       COL_WHEN_ICON, CALENDAR_ICON,
1758       -1);
1759
1760   g_free (text);
1761 }
1762
1763 static void
1764 populate_dates_from_search_hits (GList *accounts,
1765     GList *targets)
1766 {
1767   GList *l;
1768   GtkTreeView *view;
1769   GtkTreeModel *model;
1770   GtkListStore *store;
1771   GtkTreeSelection *selection;
1772   GtkTreeIter iter;
1773
1774   if (log_window == NULL)
1775     return;
1776
1777   view = GTK_TREE_VIEW (log_window->priv->treeview_when);
1778   model = gtk_tree_view_get_model (view);
1779   store = GTK_LIST_STORE (model);
1780   selection = gtk_tree_view_get_selection (view);
1781
1782   for (l = log_window->priv->hits; l != NULL; l = l->next)
1783     {
1784       TplLogSearchHit *hit = l->data;
1785       GList *acc, *targ;
1786       gboolean found = FALSE;
1787
1788       /* Protect against invalid data (corrupt or old log files). */
1789       if (hit->account == NULL || hit->target == NULL)
1790         continue;
1791
1792       for (acc = accounts, targ = targets;
1793            acc != NULL && targ != NULL && !found;
1794            acc = acc->next, targ = targ->next)
1795         {
1796           TpAccount *account = acc->data;
1797           TplEntity *target = targ->data;
1798
1799           if (account_equal (hit->account, account) &&
1800               entity_equal (hit->target, target))
1801             found = TRUE;
1802         }
1803
1804         if (!found)
1805           continue;
1806
1807       add_date_if_needed (log_window, hit->date);
1808     }
1809
1810   if (gtk_tree_model_get_iter_first (model, &iter))
1811     {
1812       GDate *date;
1813
1814       date = g_date_new_dmy (1, 1, -1),
1815
1816       gtk_list_store_prepend (store, &iter);
1817       gtk_list_store_set (store, &iter,
1818           COL_WHEN_DATE, date,
1819           COL_WHEN_TEXT, "separator",
1820           -1);
1821
1822       g_date_free (date);
1823
1824       date = g_date_new_dmy (2, 1, -1),
1825       gtk_list_store_prepend (store, &iter);
1826       gtk_list_store_set (store, &iter,
1827           COL_WHEN_DATE, date,
1828           COL_WHEN_TEXT, _("Anytime"),
1829           -1);
1830
1831       g_date_free (date);
1832
1833       if (gtk_tree_model_iter_nth_child (model, &iter, NULL, 2))
1834         gtk_tree_selection_select_iter (selection, &iter);
1835     }
1836 }
1837
1838 static void
1839 add_event_to_store (EmpathyLogWindow *self,
1840     TpAccount *account,
1841     TplEntity *entity)
1842 {
1843   GtkListStore *store;
1844   TplEntityType type = tpl_entity_get_entity_type (entity);
1845   EmpathyContact *contact;
1846   const gchar *name;
1847   gchar *sort_key;
1848   gboolean room = type == TPL_ENTITY_ROOM;
1849
1850   store = GTK_LIST_STORE (gtk_tree_view_get_model (
1851         GTK_TREE_VIEW (log_window->priv->treeview_who)));
1852
1853   contact = empathy_contact_from_tpl_contact (account, entity);
1854
1855   name = empathy_contact_get_alias (contact);
1856   sort_key = g_utf8_collate_key (name, -1);
1857
1858   gtk_list_store_insert_with_values (store, NULL, -1,
1859       COL_WHO_TYPE, COL_TYPE_NORMAL,
1860       COL_WHO_ICON, room ? EMPATHY_IMAGE_GROUP_MESSAGE
1861                          : EMPATHY_IMAGE_AVATAR_DEFAULT,
1862       COL_WHO_NAME, name,
1863       COL_WHO_NAME_SORT_KEY, sort_key,
1864       COL_WHO_ID, tpl_entity_get_identifier (entity),
1865       COL_WHO_ACCOUNT, account,
1866       COL_WHO_TARGET, entity,
1867       -1);
1868
1869   g_free (sort_key);
1870   g_object_unref (contact);
1871 }
1872
1873 static void
1874 populate_entities_from_search_hits (void)
1875 {
1876   EmpathyAccountChooser *account_chooser;
1877   TpAccount *account;
1878   GtkTreeView *view;
1879   GtkTreeModel *model;
1880   GtkTreeSelection *selection;
1881   GtkTreeIter iter;
1882   GtkListStore *store;
1883   GList *l;
1884
1885   view = GTK_TREE_VIEW (log_window->priv->treeview_who);
1886   model = gtk_tree_view_get_model (view);
1887   store = GTK_LIST_STORE (model);
1888   selection = gtk_tree_view_get_selection (view);
1889
1890   gtk_list_store_clear (store);
1891
1892   account_chooser = EMPATHY_ACCOUNT_CHOOSER (log_window->priv->account_chooser);
1893   account = empathy_account_chooser_get_account (account_chooser);
1894
1895   for (l = log_window->priv->hits; l; l = l->next)
1896     {
1897       TplLogSearchHit *hit = l->data;
1898
1899       /* Protect against invalid data (corrupt or old log files). */
1900       if (hit->account == NULL || hit->target == NULL)
1901         continue;
1902
1903       /* Filter based on the selected account */
1904       if (account != NULL && !account_equal (account, hit->account))
1905         continue;
1906
1907       /* Add the entity if it's not already there */
1908       has_element = FALSE;
1909       gtk_tree_model_foreach (model, model_has_entity, hit);
1910       if (!has_element)
1911         {
1912           add_event_to_store (log_window, hit->account, hit->target);
1913         }
1914     }
1915
1916   if (gtk_tree_model_get_iter_first (model, &iter))
1917     {
1918       gtk_list_store_prepend (store, &iter);
1919       gtk_list_store_set (store, &iter,
1920           COL_WHO_TYPE, COL_TYPE_SEPARATOR,
1921           COL_WHO_NAME, "separator",
1922           -1);
1923
1924       gtk_list_store_prepend (store, &iter);
1925       gtk_list_store_set (store, &iter,
1926           COL_WHO_TYPE, COL_TYPE_ANY,
1927           COL_WHO_NAME, _("Anyone"),
1928           -1);
1929     }
1930
1931   /* Select 'Anyone' */
1932   if (gtk_tree_model_get_iter_first (model, &iter))
1933     gtk_tree_selection_select_iter (selection, &iter);
1934 }
1935
1936 static void
1937 log_manager_searched_new_cb (GObject *manager,
1938     GAsyncResult *result,
1939     gpointer user_data)
1940 {
1941   GList *hits;
1942   GtkTreeView *view;
1943   GtkTreeSelection *selection;
1944   GError *error = NULL;
1945
1946   if (log_window == NULL)
1947     return;
1948
1949   if (!tpl_log_manager_search_finish (TPL_LOG_MANAGER (manager),
1950       result, &hits, &error))
1951     {
1952       DEBUG ("%s. Aborting", error->message);
1953       g_error_free (error);
1954       return;
1955     }
1956
1957   tp_clear_pointer (&log_window->priv->hits, tpl_log_manager_search_free);
1958   log_window->priv->hits = hits;
1959
1960   view = GTK_TREE_VIEW (log_window->priv->treeview_when);
1961   selection = gtk_tree_view_get_selection (view);
1962
1963   g_signal_handlers_unblock_by_func (selection,
1964       log_window_when_changed_cb,
1965       log_window);
1966
1967   populate_entities_from_search_hits ();
1968 }
1969
1970 static void
1971 log_window_find_populate (EmpathyLogWindow *self,
1972     const gchar *search_criteria)
1973 {
1974   GtkTreeView *view;
1975   GtkTreeModel *model;
1976   GtkTreeSelection *selection;
1977   GtkListStore *store;
1978
1979   gtk_tree_store_clear (self->priv->store_events);
1980
1981   view = GTK_TREE_VIEW (self->priv->treeview_who);
1982   model = gtk_tree_view_get_model (view);
1983   store = GTK_LIST_STORE (model);
1984
1985   gtk_list_store_clear (store);
1986
1987   view = GTK_TREE_VIEW (self->priv->treeview_when);
1988   model = gtk_tree_view_get_model (view);
1989   store = GTK_LIST_STORE (model);
1990   selection = gtk_tree_view_get_selection (view);
1991
1992   gtk_list_store_clear (store);
1993
1994   if (EMP_STR_EMPTY (search_criteria))
1995     {
1996       tp_clear_pointer (&self->priv->hits, tpl_log_manager_search_free);
1997       webkit_web_view_set_highlight_text_matches (
1998           WEBKIT_WEB_VIEW (self->priv->webview), FALSE);
1999       log_window_who_populate (self);
2000       return;
2001     }
2002
2003   g_signal_handlers_block_by_func (selection,
2004       log_window_when_changed_cb,
2005       self);
2006
2007   /* highlight the search text */
2008   webkit_web_view_mark_text_matches (WEBKIT_WEB_VIEW (self->priv->webview),
2009       search_criteria, FALSE, 0);
2010
2011   tpl_log_manager_search_async (self->priv->log_manager,
2012       search_criteria, TPL_EVENT_MASK_ANY,
2013       log_manager_searched_new_cb, NULL);
2014 }
2015
2016 static gboolean
2017 start_find_search (EmpathyLogWindow *self)
2018 {
2019   const gchar *str;
2020
2021   str = gtk_entry_get_text (GTK_ENTRY (self->priv->search_entry));
2022
2023   /* Don't find the same crap again */
2024   if (self->priv->last_find && !tp_strdiff (self->priv->last_find, str))
2025     return FALSE;
2026
2027   g_free (self->priv->last_find);
2028   self->priv->last_find = g_strdup (str);
2029
2030   log_window_find_populate (self, str);
2031
2032   return FALSE;
2033 }
2034
2035 static void
2036 log_window_search_entry_changed_cb (GtkWidget *entry,
2037     EmpathyLogWindow *self)
2038 {
2039   const gchar *str;
2040
2041   str = gtk_entry_get_text (GTK_ENTRY (self->priv->search_entry));
2042
2043   if (!tp_str_empty (str))
2044     {
2045       gtk_entry_set_icon_from_icon_name (GTK_ENTRY (self->priv->search_entry),
2046           GTK_ENTRY_ICON_SECONDARY, "edit-clear-symbolic");
2047       gtk_entry_set_icon_sensitive (GTK_ENTRY (self->priv->search_entry),
2048           GTK_ENTRY_ICON_SECONDARY, TRUE);
2049     }
2050   else
2051     {
2052       gtk_entry_set_icon_from_icon_name (GTK_ENTRY (self->priv->search_entry),
2053           GTK_ENTRY_ICON_SECONDARY, "edit-find-symbolic");
2054       gtk_entry_set_icon_sensitive (GTK_ENTRY (self->priv->search_entry),
2055           GTK_ENTRY_ICON_SECONDARY, FALSE);
2056     }
2057
2058   if (self->priv->source != 0)
2059     g_source_remove (self->priv->source);
2060   self->priv->source = g_timeout_add (500, (GSourceFunc) start_find_search,
2061       self);
2062 }
2063
2064 static void
2065 log_window_search_entry_activate_cb (GtkWidget *entry,
2066     EmpathyLogWindow *self)
2067 {
2068   start_find_search (self);
2069 }
2070
2071 static void
2072 log_window_search_entry_icon_pressed_cb (GtkEntry *entry,
2073     GtkEntryIconPosition icon_pos,
2074     GdkEvent *event,
2075     gpointer user_data)
2076 {
2077   if (icon_pos != GTK_ENTRY_ICON_SECONDARY)
2078     return;
2079
2080   gtk_entry_buffer_set_text (gtk_entry_get_buffer (entry),
2081     "", -1);
2082 }
2083
2084 static void
2085 do_update_buttons_sensitivity (EmpathyLogWindow *self)
2086 {
2087   EmpathyCapabilities capabilities;
2088   gboolean profile, chat, call, video;
2089
2090   tp_clear_object (&self->priv->button_video_binding);
2091
2092   if (self->priv->selected_contact != NULL)
2093     {
2094       capabilities = empathy_contact_get_capabilities (
2095           self->priv->selected_contact);
2096
2097       profile = chat = TRUE;
2098       call = capabilities & EMPATHY_CAPABILITIES_AUDIO;
2099       video = capabilities & EMPATHY_CAPABILITIES_VIDEO;
2100     }
2101   else
2102     {
2103       profile = chat = call = video = FALSE;
2104     }
2105
2106   gtk_widget_set_sensitive (self->priv->button_profile, profile);
2107   gtk_widget_set_sensitive (self->priv->button_chat, chat);
2108   gtk_widget_set_sensitive (self->priv->button_call, call);
2109
2110   if (video)
2111     {
2112       self->priv->button_video_binding = g_object_bind_property (
2113           self->priv->camera_monitor, "available",
2114           self->priv->button_video, "sensitive",
2115           G_BINDING_SYNC_CREATE);
2116     }
2117   else
2118     {
2119       /* Don't override the binding */
2120       gtk_widget_set_sensitive (self->priv->button_video, video);
2121     }
2122 }
2123
2124 static void
2125 contact_capabilities_changed_cb (EmpathyContact *contact,
2126     GParamSpec *spec,
2127     EmpathyLogWindow *self)
2128 {
2129   do_update_buttons_sensitivity (self);
2130 }
2131
2132 static void
2133 log_window_update_buttons_sensitivity (EmpathyLogWindow *self)
2134 {
2135   GtkTreeView *view;
2136   GtkTreeModel *model;
2137   GtkTreeSelection *selection;
2138   TpAccount *account;
2139   TplEntity *target;
2140   GtkTreeIter iter;
2141   GList *paths;
2142   GtkTreePath *path;
2143
2144   if (self->priv->selected_contact != NULL)
2145     {
2146       g_signal_handlers_disconnect_by_func (self->priv->selected_contact,
2147           contact_capabilities_changed_cb, self);
2148
2149       tp_clear_object (&self->priv->selected_contact);
2150     }
2151
2152   view = GTK_TREE_VIEW (self->priv->treeview_who);
2153   model = gtk_tree_view_get_model (view);
2154   selection = gtk_tree_view_get_selection (view);
2155
2156   if (!gtk_tree_model_get_iter_first (model, &iter))
2157     goto events;
2158
2159   if (gtk_tree_selection_count_selected_rows (selection) != 1)
2160     goto events;
2161
2162   if (gtk_tree_selection_iter_is_selected (selection, &iter))
2163     goto events;
2164
2165   paths = gtk_tree_selection_get_selected_rows (selection, &model);
2166   g_return_if_fail (paths != NULL);
2167
2168   path = paths->data;
2169   gtk_tree_model_get_iter (model, &iter, path);
2170   gtk_tree_model_get (model, &iter,
2171       COL_WHO_ACCOUNT, &account,
2172       COL_WHO_TARGET, &target,
2173       -1);
2174
2175   g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free);
2176
2177   self->priv->selected_contact = empathy_contact_from_tpl_contact (account,
2178       target);
2179
2180   g_object_unref (account);
2181   g_object_unref (target);
2182
2183   goto out;
2184
2185  events:
2186   /* If the Who pane doesn't contain a contact (e.g. it has many
2187    * selected, or has 'Anyone', let's try to get the contact from
2188    * the selected event. */
2189
2190   if (self->priv->events_contact != NULL)
2191     self->priv->selected_contact = g_object_ref (self->priv->events_contact);
2192
2193  out:
2194   if (self->priv->selected_contact != NULL)
2195     {
2196       tp_g_signal_connect_object (self->priv->selected_contact,
2197           "notify::capabilities", G_CALLBACK (contact_capabilities_changed_cb),
2198           self, 0);
2199     }
2200
2201   do_update_buttons_sensitivity (self);
2202 }
2203
2204 static void
2205 log_window_update_what_iter_sensitivity (GtkTreeModel *model,
2206     GtkTreeIter *iter,
2207     gboolean sensitive)
2208 {
2209   GtkTreeStore *store = GTK_TREE_STORE (model);
2210   GtkTreeIter child;
2211   gboolean next;
2212
2213   gtk_tree_store_set (store, iter,
2214       COL_WHAT_SENSITIVE, sensitive,
2215       -1);
2216
2217   for (next = gtk_tree_model_iter_children (model, &child, iter);
2218        next;
2219        next = gtk_tree_model_iter_next (model, &child))
2220     {
2221       gtk_tree_store_set (store, &child,
2222           COL_WHAT_SENSITIVE, sensitive,
2223           -1);
2224     }
2225 }
2226
2227 static void
2228 log_window_update_what_sensitivity (EmpathyLogWindow *self)
2229 {
2230   GtkTreeView *view;
2231   GtkTreeModel *model;
2232   GtkTreeIter iter;
2233   GList *accounts, *targets, *acc, *targ;
2234   gboolean next;
2235
2236   if (!log_window_get_selected (self, &accounts, &targets, NULL, NULL,
2237       NULL, NULL))
2238     return;
2239
2240   view = GTK_TREE_VIEW (self->priv->treeview_what);
2241   model = gtk_tree_view_get_model (view);
2242
2243   /* For each event type... */
2244   for (next = gtk_tree_model_get_iter_first (model, &iter);
2245        next;
2246        next = gtk_tree_model_iter_next (model, &iter))
2247     {
2248       TplEventTypeMask type;
2249
2250       gtk_tree_model_get (model, &iter,
2251           COL_WHAT_TYPE, &type,
2252           -1);
2253
2254       /* ...we set the type and its subtypes (if any) unsensitive... */
2255       log_window_update_what_iter_sensitivity (model, &iter, FALSE);
2256
2257       for (acc = accounts, targ = targets;
2258            acc != NULL && targ != NULL;
2259            acc = acc->next, targ = targ->next)
2260         {
2261           TpAccount *account = acc->data;
2262           TplEntity *target = targ->data;
2263
2264           if (tpl_log_manager_exists (self->priv->log_manager,
2265                   account, target, type))
2266             {
2267               /* And then we set it (and its subtypes, again, if any)
2268                * as sensitive if there are logs of that type. */
2269               log_window_update_what_iter_sensitivity (model, &iter, TRUE);
2270               break;
2271             }
2272         }
2273     }
2274
2275   g_list_free_full (accounts, g_object_unref);
2276   g_list_free_full (targets, g_object_unref);
2277 }
2278
2279 static void
2280 log_window_who_changed_cb (GtkTreeSelection *selection,
2281     EmpathyLogWindow *self)
2282 {
2283   GtkTreeView *view;
2284   GtkTreeModel *model;
2285   GtkTreeIter iter;
2286
2287   DEBUG ("log_window_who_changed_cb");
2288
2289   view = gtk_tree_selection_get_tree_view (selection);
2290   model = gtk_tree_view_get_model (view);
2291
2292   if (gtk_tree_model_get_iter_first (model, &iter))
2293     {
2294       /* If 'Anyone' is selected, everything else should be deselected */
2295       if (gtk_tree_selection_iter_is_selected (selection, &iter))
2296         {
2297           g_signal_handlers_block_by_func (selection,
2298               log_window_who_changed_cb,
2299               self);
2300
2301           gtk_tree_selection_unselect_all (selection);
2302           gtk_tree_selection_select_iter (selection, &iter);
2303
2304           g_signal_handlers_unblock_by_func (selection,
2305               log_window_who_changed_cb,
2306               self);
2307         }
2308     }
2309
2310   log_window_update_what_sensitivity (self);
2311   log_window_update_buttons_sensitivity (self);
2312
2313   /* The contact changed, so the dates need to be updated */
2314   log_window_chats_get_messages (self, TRUE);
2315 }
2316
2317 static void
2318 log_manager_got_entities_cb (GObject *manager,
2319     GAsyncResult *result,
2320     gpointer user_data)
2321 {
2322   Ctx                   *ctx = user_data;
2323   GList                 *entities;
2324   GList                 *l;
2325   GtkTreeView           *view;
2326   GtkTreeModel          *model;
2327   GtkTreeSelection      *selection;
2328   GtkListStore          *store;
2329   GtkTreeIter            iter;
2330   GError                *error = NULL;
2331   gboolean               select_account = FALSE;
2332
2333   if (log_window == NULL)
2334     goto out;
2335
2336   if (log_window->priv->count != ctx->count)
2337     goto out;
2338
2339   if (!tpl_log_manager_get_entities_finish (TPL_LOG_MANAGER (manager),
2340       result, &entities, &error))
2341     {
2342       DEBUG ("%s. Aborting", error->message);
2343       g_error_free (error);
2344       goto out;
2345     }
2346
2347   view = GTK_TREE_VIEW (ctx->self->priv->treeview_who);
2348   model = gtk_tree_view_get_model (view);
2349   selection = gtk_tree_view_get_selection (view);
2350   store = GTK_LIST_STORE (model);
2351
2352   /* Block signals to stop the logs being retrieved prematurely  */
2353   g_signal_handlers_block_by_func (selection,
2354       log_window_who_changed_cb, ctx->self);
2355
2356   for (l = entities; l; l = l->next)
2357     {
2358       add_event_to_store (ctx->self, ctx->account, l->data);
2359
2360       if (ctx->self->priv->selected_account != NULL &&
2361           !tp_strdiff (tp_proxy_get_object_path (ctx->account),
2362           tp_proxy_get_object_path (ctx->self->priv->selected_account)))
2363         select_account = TRUE;
2364     }
2365   g_list_free_full (entities, g_object_unref);
2366
2367   if (gtk_tree_model_get_iter_first (model, &iter))
2368     {
2369       gint type;
2370
2371       gtk_tree_model_get (model, &iter,
2372           COL_WHO_TYPE, &type,
2373           -1);
2374
2375       if (type != COL_TYPE_ANY)
2376         {
2377           gtk_list_store_prepend (store, &iter);
2378           gtk_list_store_set (store, &iter,
2379               COL_WHO_TYPE, COL_TYPE_SEPARATOR,
2380               COL_WHO_NAME, "separator",
2381               -1);
2382
2383           gtk_list_store_prepend (store, &iter);
2384           gtk_list_store_set (store, &iter,
2385               COL_WHO_TYPE, COL_TYPE_ANY,
2386               COL_WHO_NAME, _("Anyone"),
2387               -1);
2388         }
2389     }
2390
2391   /* Unblock signals */
2392   g_signal_handlers_unblock_by_func (selection,
2393       log_window_who_changed_cb,
2394       ctx->self);
2395
2396   /* We display the selected account if we populate the model with chats from
2397    * this account. */
2398   if (select_account)
2399     log_window_chats_set_selected (ctx->self);
2400
2401 out:
2402   _tpl_action_chain_continue (log_window->priv->chain);
2403   ctx_free (ctx);
2404 }
2405
2406 static void
2407 get_entities_for_account (TplActionChain *chain, gpointer user_data)
2408 {
2409   Ctx *ctx = user_data;
2410
2411   tpl_log_manager_get_entities_async (ctx->self->priv->log_manager, ctx->account,
2412       log_manager_got_entities_cb, ctx);
2413 }
2414
2415 static void
2416 select_first_entity (TplActionChain *chain, gpointer user_data)
2417 {
2418   EmpathyLogWindow *self = user_data;
2419   GtkTreeView *view;
2420   GtkTreeModel *model;
2421   GtkTreeSelection *selection;
2422   GtkTreeIter iter;
2423
2424   view = GTK_TREE_VIEW (self->priv->treeview_who);
2425   model = gtk_tree_view_get_model (view);
2426   selection = gtk_tree_view_get_selection (view);
2427
2428   if (gtk_tree_model_get_iter_first (model, &iter))
2429     gtk_tree_selection_select_iter (selection, &iter);
2430
2431   _tpl_action_chain_continue (self->priv->chain);
2432 }
2433
2434 static void
2435 log_window_who_populate (EmpathyLogWindow *self)
2436 {
2437   EmpathyAccountChooser *account_chooser;
2438   TpAccount *account;
2439   gboolean all_accounts;
2440   GtkTreeView *view;
2441   GtkTreeModel *model;
2442   GtkTreeSelection *selection;
2443   GtkListStore *store;
2444   Ctx *ctx;
2445
2446   if (self->priv->hits != NULL)
2447     {
2448       populate_entities_from_search_hits ();
2449       return;
2450     }
2451
2452   account_chooser = EMPATHY_ACCOUNT_CHOOSER (self->priv->account_chooser);
2453   account = empathy_account_chooser_dup_account (account_chooser);
2454   all_accounts = empathy_account_chooser_has_all_selected (account_chooser);
2455
2456   view = GTK_TREE_VIEW (self->priv->treeview_who);
2457   model = gtk_tree_view_get_model (view);
2458   selection = gtk_tree_view_get_selection (view);
2459   store = GTK_LIST_STORE (model);
2460
2461   /* Block signals to stop the logs being retrieved prematurely  */
2462   g_signal_handlers_block_by_func (selection,
2463       log_window_who_changed_cb,
2464       self);
2465
2466   gtk_list_store_clear (store);
2467
2468   /* Unblock signals */
2469   g_signal_handlers_unblock_by_func (selection,
2470       log_window_who_changed_cb,
2471       self);
2472
2473   _tpl_action_chain_clear (self->priv->chain);
2474   self->priv->count++;
2475
2476   if (!all_accounts && account == NULL)
2477     {
2478       return;
2479     }
2480   else if (!all_accounts)
2481     {
2482       ctx = ctx_new (self, account, NULL, NULL, 0, 0, self->priv->count);
2483       _tpl_action_chain_append (self->priv->chain, get_entities_for_account, ctx);
2484     }
2485   else
2486     {
2487       TpAccountManager *manager;
2488       GList *accounts, *l;
2489
2490       manager = empathy_account_chooser_get_account_manager (account_chooser);
2491       accounts = tp_account_manager_dup_valid_accounts (manager);
2492
2493       for (l = accounts; l != NULL; l = l->next)
2494         {
2495           account = l->data;
2496
2497           ctx = ctx_new (self, account, NULL, NULL, 0, 0, self->priv->count);
2498           _tpl_action_chain_append (self->priv->chain,
2499               get_entities_for_account, ctx);
2500         }
2501
2502       g_list_free_full (accounts, g_object_unref);
2503     }
2504   _tpl_action_chain_append (self->priv->chain, select_first_entity, self);
2505   _tpl_action_chain_start (self->priv->chain);
2506 }
2507
2508 static gint
2509 sort_by_name_key (GtkTreeModel *model,
2510     GtkTreeIter *a,
2511     GtkTreeIter *b,
2512     gpointer user_data)
2513 {
2514   gchar *key1, *key2;
2515   gint type1, type2;
2516   gint ret;
2517
2518   gtk_tree_model_get (model, a,
2519       COL_WHO_TYPE, &type1,
2520       COL_WHO_NAME_SORT_KEY, &key1,
2521       -1);
2522
2523   gtk_tree_model_get (model, b,
2524       COL_WHO_TYPE, &type2,
2525       COL_WHO_NAME_SORT_KEY, &key2,
2526       -1);
2527
2528   if (type1 == COL_TYPE_ANY)
2529     ret = -1;
2530   else if (type2 == COL_TYPE_ANY)
2531     ret = 1;
2532   else if (type1 == COL_TYPE_SEPARATOR)
2533     ret = -1;
2534   else if (type2 == COL_TYPE_SEPARATOR)
2535     ret = 1;
2536   else
2537     ret = g_strcmp0 (key1, key2);
2538
2539   g_free (key1);
2540   g_free (key2);
2541
2542   return ret;
2543 }
2544
2545 static gboolean
2546 who_row_is_separator (GtkTreeModel *model,
2547     GtkTreeIter *iter,
2548     gpointer data)
2549 {
2550   gint type;
2551
2552   gtk_tree_model_get (model, iter,
2553       COL_WHO_TYPE, &type,
2554       -1);
2555
2556   return (type == COL_TYPE_SEPARATOR);
2557 }
2558
2559 static void
2560 log_window_find_row (EmpathyLogWindow *self,
2561     GdkEventButton *event)
2562 {
2563   WebKitHitTestResult *hit = webkit_web_view_get_hit_test_result (
2564       WEBKIT_WEB_VIEW (self->priv->webview), event);
2565   WebKitDOMNode *inner_node;
2566
2567   tp_clear_object (&self->priv->events_contact);
2568
2569   g_object_get (hit,
2570       "inner-node", &inner_node,
2571       NULL);
2572
2573   if (inner_node != NULL)
2574     {
2575       GtkTreeModel *model = GTK_TREE_MODEL (self->priv->store_events);
2576       WebKitDOMNode *node;
2577       const char *path = NULL;
2578       GtkTreeIter iter;
2579
2580       /* walk back up the DOM tree looking for a node with empathy:path set */
2581       for (node = inner_node; node != NULL;
2582            node = webkit_dom_node_get_parent_node (node))
2583         {
2584           if (!WEBKIT_DOM_IS_ELEMENT (node))
2585             continue;
2586
2587           path = webkit_dom_element_get_attribute_ns (
2588               WEBKIT_DOM_ELEMENT (node), EMPATHY_NS, "path");
2589
2590           if (!tp_str_empty (path))
2591             break;
2592         }
2593
2594       /* look up the contact for this path */
2595       if (!tp_str_empty (path) &&
2596           gtk_tree_model_get_iter_from_string (model, &iter, path))
2597         {
2598           TpAccount *account;
2599           TplEntity *target;
2600
2601           gtk_tree_model_get (model, &iter,
2602               COL_EVENTS_ACCOUNT, &account,
2603               COL_EVENTS_TARGET, &target,
2604               -1);
2605
2606           self->priv->events_contact = empathy_contact_from_tpl_contact (
2607               account, target);
2608
2609           g_object_unref (account);
2610           g_object_unref (target);
2611         }
2612
2613       g_object_unref (inner_node);
2614     }
2615
2616   g_object_unref (hit);
2617
2618   log_window_update_buttons_sensitivity (self);
2619 }
2620
2621 static gboolean
2622 log_window_events_button_press_event (GtkWidget *webview,
2623     GdkEventButton *event,
2624     EmpathyLogWindow *self)
2625 {
2626   switch (event->button)
2627     {
2628       case 1:
2629         log_window_find_row (self, event);
2630         break;
2631
2632       case 3:
2633         empathy_webkit_context_menu_for_event (
2634             WEBKIT_WEB_VIEW (webview), event, 0);
2635         return TRUE;
2636
2637       default:
2638         break;
2639     }
2640
2641   return FALSE;
2642 }
2643
2644 static void
2645 log_window_events_setup (EmpathyLogWindow *self)
2646 {
2647   GtkTreeSortable   *sortable;
2648   GtkTreeStore      *store;
2649
2650   /* new store */
2651   self->priv->store_events = store = gtk_tree_store_new (COL_EVENTS_COUNT,
2652       G_TYPE_INT,           /* type */
2653       G_TYPE_INT64,         /* timestamp */
2654       G_TYPE_STRING,        /* stringified date */
2655       G_TYPE_STRING,        /* icon */
2656       G_TYPE_STRING,        /* name */
2657       TP_TYPE_ACCOUNT,      /* account */
2658       TPL_TYPE_ENTITY,      /* target */
2659       TPL_TYPE_EVENT);      /* event */
2660
2661   sortable = GTK_TREE_SORTABLE (store);
2662
2663   gtk_tree_sortable_set_sort_column_id (sortable,
2664       COL_EVENTS_TS,
2665       GTK_SORT_ASCENDING);
2666 }
2667
2668 static void
2669 log_window_who_setup (EmpathyLogWindow *self)
2670 {
2671   GtkTreeView       *view;
2672   GtkTreeModel      *model;
2673   GtkTreeSelection  *selection;
2674   GtkTreeSortable   *sortable;
2675   GtkTreeViewColumn *column;
2676   GtkListStore      *store;
2677   GtkCellRenderer   *cell;
2678
2679   view = GTK_TREE_VIEW (self->priv->treeview_who);
2680   selection = gtk_tree_view_get_selection (view);
2681
2682   /* new store */
2683   store = gtk_list_store_new (COL_WHO_COUNT,
2684       G_TYPE_INT,           /* type */
2685       G_TYPE_STRING,        /* icon */
2686       G_TYPE_STRING,        /* name */
2687       G_TYPE_STRING,        /* name sort key */
2688       G_TYPE_STRING,        /* id */
2689       TP_TYPE_ACCOUNT,      /* account */
2690       TPL_TYPE_ENTITY);     /* target */
2691
2692   model = GTK_TREE_MODEL (store);
2693   sortable = GTK_TREE_SORTABLE (store);
2694
2695   gtk_tree_view_set_model (view, model);
2696
2697   /* new column */
2698   column = gtk_tree_view_column_new ();
2699   gtk_tree_view_column_set_title (column, _("Who"));
2700
2701   cell = gtk_cell_renderer_pixbuf_new ();
2702   gtk_tree_view_column_pack_start (column, cell, FALSE);
2703   gtk_tree_view_column_add_attribute (column, cell,
2704       "icon-name",
2705       COL_WHO_ICON);
2706
2707   cell = gtk_cell_renderer_text_new ();
2708   g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
2709   gtk_tree_view_column_pack_start (column, cell, TRUE);
2710   gtk_tree_view_column_add_attribute (column, cell,
2711       "text",
2712       COL_WHO_NAME);
2713
2714   gtk_tree_view_append_column (view, column);
2715
2716   /* set up treeview properties */
2717   gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
2718   gtk_tree_view_set_row_separator_func (view, who_row_is_separator,
2719       NULL, NULL);
2720
2721   gtk_tree_sortable_set_sort_column_id (sortable,
2722       COL_WHO_NAME_SORT_KEY,
2723       GTK_SORT_ASCENDING);
2724   gtk_tree_sortable_set_sort_func (sortable,
2725       COL_WHO_NAME_SORT_KEY, sort_by_name_key,
2726       NULL, NULL);
2727
2728   gtk_tree_view_set_search_column (view, COL_WHO_NAME);
2729   gtk_tree_view_set_tooltip_column (view, COL_WHO_ID);
2730
2731   /* set up signals */
2732   g_signal_connect (selection, "changed",
2733       G_CALLBACK (log_window_who_changed_cb), self);
2734
2735   g_object_unref (store);
2736 }
2737
2738 static void
2739 log_window_chats_accounts_changed_cb (GtkWidget *combobox,
2740     EmpathyLogWindow *self)
2741 {
2742   /* Clear all current messages shown in the textview */
2743   gtk_tree_store_clear (self->priv->store_events);
2744
2745   log_window_who_populate (self);
2746 }
2747
2748 static void
2749 log_window_chats_set_selected (EmpathyLogWindow *self)
2750 {
2751   GtkTreeView          *view;
2752   GtkTreeModel         *model;
2753   GtkTreeSelection     *selection;
2754   GtkTreeIter           iter;
2755   GtkTreePath          *path;
2756   gboolean              next;
2757
2758   view = GTK_TREE_VIEW (self->priv->treeview_who);
2759   model = gtk_tree_view_get_model (view);
2760   selection = gtk_tree_view_get_selection (view);
2761
2762   for (next = gtk_tree_model_get_iter_first (model, &iter);
2763        next;
2764        next = gtk_tree_model_iter_next (model, &iter))
2765     {
2766       TpAccount   *this_account;
2767       TplEntity   *this_target;
2768       const gchar *this_chat_id;
2769       gboolean     this_is_chatroom;
2770       gint         this_type;
2771
2772       gtk_tree_model_get (model, &iter,
2773           COL_WHO_TYPE, &this_type,
2774           COL_WHO_ACCOUNT, &this_account,
2775           COL_WHO_TARGET, &this_target,
2776           -1);
2777
2778       if (this_type != COL_TYPE_NORMAL)
2779         continue;
2780
2781       this_chat_id = tpl_entity_get_identifier (this_target);
2782       this_is_chatroom = tpl_entity_get_entity_type (this_target)
2783           == TPL_ENTITY_ROOM;
2784
2785       if (this_account == self->priv->selected_account &&
2786           !tp_strdiff (this_chat_id, self->priv->selected_chat_id) &&
2787           this_is_chatroom == self->priv->selected_is_chatroom)
2788         {
2789           gtk_tree_selection_select_iter (selection, &iter);
2790           path = gtk_tree_model_get_path (model, &iter);
2791           gtk_tree_view_scroll_to_cell (view, path, NULL, TRUE, 0.5, 0.0);
2792           gtk_tree_path_free (path);
2793           g_object_unref (this_account);
2794           g_object_unref (this_target);
2795           break;
2796         }
2797
2798       g_object_unref (this_account);
2799       g_object_unref (this_target);
2800     }
2801
2802   tp_clear_object (&self->priv->selected_account);
2803   tp_clear_pointer (&self->priv->selected_chat_id, g_free);
2804 }
2805
2806 static gint
2807 sort_by_date (GtkTreeModel *model,
2808     GtkTreeIter *a,
2809     GtkTreeIter *b,
2810     gpointer user_data)
2811 {
2812   GDate *date1, *date2;
2813   gint result;
2814
2815   gtk_tree_model_get (model, a,
2816       COL_WHEN_DATE, &date1,
2817       -1);
2818
2819   gtk_tree_model_get (model, b,
2820       COL_WHEN_DATE, &date2,
2821       -1);
2822
2823   result =  g_date_compare (date1, date2);
2824
2825   g_date_free (date1);
2826   g_date_free (date2);
2827   return result;
2828 }
2829
2830 static gboolean
2831 when_row_is_separator (GtkTreeModel *model,
2832     GtkTreeIter *iter,
2833     gpointer data)
2834 {
2835   gchar *when;
2836   gboolean ret;
2837
2838   gtk_tree_model_get (model, iter,
2839       COL_WHEN_TEXT, &when,
2840       -1);
2841
2842   ret = !tp_strdiff (when, "separator");
2843   g_free (when);
2844   return ret;
2845 }
2846
2847 static void
2848 log_window_when_changed_cb (GtkTreeSelection *selection,
2849     EmpathyLogWindow *self)
2850 {
2851   GtkTreeView *view;
2852   GtkTreeModel *model;
2853   GtkTreeIter iter;
2854
2855   DEBUG ("log_window_when_changed_cb");
2856
2857   view = gtk_tree_selection_get_tree_view (selection);
2858   model = gtk_tree_view_get_model (view);
2859
2860   /* If 'Anytime' is selected, everything else should be deselected */
2861   if (gtk_tree_model_get_iter_first (model, &iter))
2862     {
2863       if (gtk_tree_selection_iter_is_selected (selection, &iter))
2864         {
2865           g_signal_handlers_block_by_func (selection,
2866               log_window_when_changed_cb,
2867               self);
2868
2869           gtk_tree_selection_unselect_all (selection);
2870           gtk_tree_selection_select_iter (selection, &iter);
2871
2872           g_signal_handlers_unblock_by_func (selection,
2873               log_window_when_changed_cb,
2874               self);
2875         }
2876     }
2877
2878   log_window_chats_get_messages (self, FALSE);
2879 }
2880
2881 static void
2882 log_window_when_setup (EmpathyLogWindow *self)
2883 {
2884   GtkTreeView       *view;
2885   GtkTreeModel      *model;
2886   GtkTreeSelection  *selection;
2887   GtkTreeSortable   *sortable;
2888   GtkTreeViewColumn *column;
2889   GtkListStore      *store;
2890   GtkCellRenderer   *cell;
2891
2892   view = GTK_TREE_VIEW (self->priv->treeview_when);
2893   selection = gtk_tree_view_get_selection (view);
2894
2895   /* new store */
2896   store = gtk_list_store_new (COL_WHEN_COUNT,
2897       G_TYPE_DATE,        /* date */
2898       G_TYPE_STRING,      /* stringified date */
2899       G_TYPE_STRING);     /* icon */
2900
2901   model = GTK_TREE_MODEL (store);
2902   sortable = GTK_TREE_SORTABLE (store);
2903
2904   gtk_tree_view_set_model (view, model);
2905
2906   /* new column */
2907   column = gtk_tree_view_column_new ();
2908   gtk_tree_view_column_set_title (column, _("When"));
2909
2910   cell = gtk_cell_renderer_pixbuf_new ();
2911   gtk_tree_view_column_pack_start (column, cell, FALSE);
2912   gtk_tree_view_column_add_attribute (column, cell,
2913       "icon-name", COL_WHEN_ICON);
2914
2915   cell = gtk_cell_renderer_text_new ();
2916   g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
2917   gtk_tree_view_column_pack_start (column, cell, TRUE);
2918   gtk_tree_view_column_add_attribute (column, cell,
2919       "text",
2920       COL_WHEN_TEXT);
2921
2922   gtk_tree_view_append_column (view, column);
2923
2924   /* set up treeview properties */
2925   gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
2926   gtk_tree_view_set_row_separator_func (view, when_row_is_separator,
2927       NULL, NULL);
2928   gtk_tree_sortable_set_sort_column_id (sortable,
2929       COL_WHEN_DATE,
2930       GTK_SORT_DESCENDING);
2931   gtk_tree_sortable_set_sort_func (sortable,
2932       COL_WHEN_DATE, sort_by_date,
2933       NULL, NULL);
2934
2935   gtk_tree_view_set_search_column (view, COL_WHEN_TEXT);
2936
2937   /* set up signals */
2938   g_signal_connect (selection, "changed",
2939       G_CALLBACK (log_window_when_changed_cb),
2940       self);
2941
2942   g_object_unref (store);
2943 }
2944
2945 static gboolean
2946 what_row_is_separator (GtkTreeModel *model,
2947     GtkTreeIter *iter,
2948     gpointer data)
2949 {
2950   gint type;
2951
2952   gtk_tree_model_get (model, iter,
2953       COL_WHAT_TYPE, &type,
2954       -1);
2955
2956   return (type == WHAT_TYPE_SEPARATOR);
2957 }
2958
2959 static void
2960 log_window_what_changed_cb (GtkTreeSelection *selection,
2961     EmpathyLogWindow *self)
2962 {
2963   GtkTreeView *view;
2964   GtkTreeModel *model;
2965   GtkTreeIter iter;
2966
2967   DEBUG ("log_window_what_changed_cb");
2968
2969   view = gtk_tree_selection_get_tree_view (selection);
2970   model = gtk_tree_view_get_model (view);
2971
2972   /* If 'Anything' is selected, everything else should be deselected */
2973   if (gtk_tree_model_get_iter_first (model, &iter))
2974     {
2975       if (gtk_tree_selection_iter_is_selected (selection, &iter))
2976         {
2977           g_signal_handlers_block_by_func (selection,
2978               log_window_what_changed_cb,
2979               self);
2980
2981           gtk_tree_selection_unselect_all (selection);
2982           gtk_tree_selection_select_iter (selection, &iter);
2983
2984           g_signal_handlers_unblock_by_func (selection,
2985               log_window_what_changed_cb,
2986               self);
2987         }
2988     }
2989
2990   /* The dates need to be updated if we're not searching */
2991   log_window_chats_get_messages (self, self->priv->hits == NULL);
2992 }
2993
2994 static gboolean
2995 log_window_what_collapse_row_cb (GtkTreeView *tree_view,
2996     GtkTreeIter *iter,
2997     GtkTreePath *path,
2998     gpointer user_data)
2999 {
3000   /* Reject collapsing */
3001   return TRUE;
3002 }
3003
3004 struct event
3005 {
3006   gint type;
3007   EventSubtype subtype;
3008   const gchar *icon;
3009   const gchar *text;
3010 };
3011
3012 static void
3013 log_window_what_setup (EmpathyLogWindow *self)
3014 {
3015   GtkTreeView       *view;
3016   GtkTreeModel      *model;
3017   GtkTreeSelection  *selection;
3018   GtkTreeViewColumn *column;
3019   GtkTreeIter        iter;
3020   GtkTreeStore      *store;
3021   GtkCellRenderer   *cell;
3022   GtkTreeIter parent;
3023   guint i;
3024
3025   struct event events [] = {
3026     { TPL_EVENT_MASK_ANY, 0, NULL, _("Anything") },
3027     { WHAT_TYPE_SEPARATOR, 0, NULL, "separator" },
3028     { TPL_EVENT_MASK_TEXT, 0, "format-justify-fill", _("Text chats") },
3029     { TPL_EVENT_MASK_CALL, EVENT_CALL_ALL, EMPATHY_IMAGE_CALL, _("Calls") },
3030   };
3031
3032   struct event call_events [] = {
3033     { TPL_EVENT_MASK_CALL, EVENT_CALL_INCOMING, EMPATHY_IMAGE_CALL_INCOMING, _("Incoming calls") },
3034     { TPL_EVENT_MASK_CALL, EVENT_CALL_OUTGOING, EMPATHY_IMAGE_CALL_OUTGOING, _("Outgoing calls") },
3035     { TPL_EVENT_MASK_CALL, EVENT_CALL_MISSED, EMPATHY_IMAGE_CALL_MISSED, _("Missed calls") }
3036   };
3037
3038   view = GTK_TREE_VIEW (self->priv->treeview_what);
3039   selection = gtk_tree_view_get_selection (view);
3040
3041   /* new store */
3042   store = gtk_tree_store_new (COL_WHAT_COUNT,
3043       G_TYPE_INT,         /* history type */
3044       G_TYPE_INT,         /* history subtype */
3045       G_TYPE_BOOLEAN,     /* sensitive */
3046       G_TYPE_STRING,      /* stringified history type */
3047       G_TYPE_STRING);     /* icon */
3048
3049   model = GTK_TREE_MODEL (store);
3050
3051   gtk_tree_view_set_model (view, model);
3052
3053   /* new column */
3054   column = gtk_tree_view_column_new ();
3055   gtk_tree_view_column_set_title (column, _("What"));
3056
3057   cell = gtk_cell_renderer_pixbuf_new ();
3058   gtk_tree_view_column_pack_start (column, cell, FALSE);
3059   gtk_tree_view_column_add_attribute (column, cell,
3060       "icon-name", COL_WHAT_ICON);
3061
3062   cell = gtk_cell_renderer_text_new ();
3063   g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
3064   gtk_tree_view_column_pack_start (column, cell, TRUE);
3065   gtk_tree_view_column_add_attribute (column, cell,
3066       "text", COL_WHAT_TEXT);
3067   gtk_tree_view_column_add_attribute (column, cell,
3068       "sensitive", COL_WHAT_SENSITIVE);
3069
3070   gtk_tree_view_append_column (view, column);
3071   gtk_tree_view_set_search_column (view, COL_WHAT_TEXT);
3072
3073   /* set up treeview properties */
3074   gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
3075   gtk_tree_view_set_show_expanders (view, FALSE);
3076   gtk_tree_view_set_level_indentation (view, 12);
3077   gtk_tree_view_expand_all (view);
3078   gtk_tree_view_set_row_separator_func (view, what_row_is_separator,
3079       NULL, NULL);
3080
3081   /* populate */
3082   for (i = 0; i < G_N_ELEMENTS (events); i++)
3083     {
3084       gtk_tree_store_append (store, &iter, NULL);
3085       gtk_tree_store_set (store, &iter,
3086           COL_WHAT_TYPE, events[i].type,
3087           COL_WHAT_SUBTYPE, events[i].subtype,
3088           COL_WHAT_SENSITIVE, TRUE,
3089           COL_WHAT_TEXT, events[i].text,
3090           COL_WHAT_ICON, events[i].icon,
3091           -1);
3092     }
3093
3094   gtk_tree_model_iter_nth_child (model, &parent, NULL, 3);
3095   for (i = 0; i < G_N_ELEMENTS (call_events); i++)
3096     {
3097       gtk_tree_store_append (store, &iter, &parent);
3098       gtk_tree_store_set (store, &iter,
3099           COL_WHAT_TYPE, call_events[i].type,
3100           COL_WHAT_SUBTYPE, call_events[i].subtype,
3101           COL_WHAT_SENSITIVE, TRUE,
3102           COL_WHAT_TEXT, call_events[i].text,
3103           COL_WHAT_ICON, call_events[i].icon,
3104           -1);
3105     }
3106
3107   gtk_tree_view_expand_all (view);
3108
3109   /* select 'Anything' */
3110   if (gtk_tree_model_get_iter_first (model, &iter))
3111     gtk_tree_selection_select_iter (selection, &iter);
3112
3113   /* set up signals */
3114   g_signal_connect (view, "test-collapse-row",
3115       G_CALLBACK (log_window_what_collapse_row_cb),
3116       NULL);
3117   g_signal_connect (selection, "changed",
3118       G_CALLBACK (log_window_what_changed_cb),
3119       self);
3120
3121   g_object_unref (store);
3122 }
3123
3124 static void
3125 log_window_maybe_expand_events (void)
3126 {
3127   GtkTreeModel      *model = GTK_TREE_MODEL (log_window->priv->store_events);
3128
3129   /* If there's only one result, expand it */
3130   if (gtk_tree_model_iter_n_children (model, NULL) == 1)
3131     webkit_web_view_execute_script (
3132         WEBKIT_WEB_VIEW (log_window->priv->webview),
3133         "javascript:expandAll()");
3134 }
3135
3136 static gboolean
3137 show_spinner (gpointer data)
3138 {
3139   gboolean active;
3140
3141   if (log_window == NULL)
3142     return FALSE;
3143
3144   g_object_get (log_window->priv->spinner, "active", &active, NULL);
3145
3146   if (active)
3147     gtk_notebook_set_current_page (GTK_NOTEBOOK (log_window->priv->notebook),
3148         PAGE_SPINNER);
3149
3150   return FALSE;
3151 }
3152
3153 static void
3154 show_events (TplActionChain *chain,
3155     gpointer user_data)
3156 {
3157   log_window_maybe_expand_events ();
3158   gtk_spinner_stop (GTK_SPINNER (log_window->priv->spinner));
3159   gtk_notebook_set_current_page (GTK_NOTEBOOK (log_window->priv->notebook),
3160       PAGE_EVENTS);
3161
3162   _tpl_action_chain_continue (chain);
3163 }
3164
3165 static void
3166 start_spinner (void)
3167 {
3168   gtk_spinner_start (GTK_SPINNER (log_window->priv->spinner));
3169   gtk_notebook_set_current_page (GTK_NOTEBOOK (log_window->priv->notebook),
3170       PAGE_EMPTY);
3171
3172   g_timeout_add (1000, show_spinner, NULL);
3173   _tpl_action_chain_append (log_window->priv->chain, show_events, NULL);
3174 }
3175
3176 static void
3177 log_window_got_messages_for_date_cb (GObject *manager,
3178     GAsyncResult *result,
3179     gpointer user_data)
3180 {
3181   Ctx *ctx = user_data;
3182   GtkTreeModel *model;
3183   GtkTreeIter iter;
3184   GList *events;
3185   GList *l;
3186   GError *error = NULL;
3187   gint n;
3188
3189   if (log_window == NULL)
3190     {
3191       ctx_free (ctx);
3192       return;
3193     }
3194
3195   if (log_window->priv->count != ctx->count)
3196     goto out;
3197
3198   if (!tpl_log_manager_get_events_for_date_finish (TPL_LOG_MANAGER (manager),
3199       result, &events, &error))
3200     {
3201       DEBUG ("Unable to retrieve messages for the selected date: %s. Aborting",
3202           error->message);
3203       g_error_free (error);
3204       goto out;
3205     }
3206
3207   for (l = events; l; l = l->next)
3208     {
3209       TplEvent *event = l->data;
3210       gboolean append = TRUE;
3211
3212       if (TPL_IS_CALL_EVENT (l->data)
3213           && ctx->event_mask & TPL_EVENT_MASK_CALL
3214           && ctx->event_mask != TPL_EVENT_MASK_ANY)
3215         {
3216           TplCallEvent *call = l->data;
3217
3218           append = FALSE;
3219
3220           if (ctx->subtype & EVENT_CALL_ALL)
3221             {
3222               append = TRUE;
3223             }
3224           else
3225             {
3226               TpCallStateChangeReason reason =
3227                 tpl_call_event_get_end_reason (call);
3228               TplEntity *sender = tpl_event_get_sender (event);
3229               TplEntity *receiver = tpl_event_get_receiver (event);
3230
3231               if (reason == TP_CALL_STATE_CHANGE_REASON_NO_ANSWER)
3232                 {
3233                   if (ctx->subtype & EVENT_CALL_MISSED)
3234                     append = TRUE;
3235                 }
3236               else if (ctx->subtype & EVENT_CALL_OUTGOING
3237                   && tpl_entity_get_entity_type (sender) == TPL_ENTITY_SELF)
3238                 {
3239                   append = TRUE;
3240                 }
3241               else if (ctx->subtype & EVENT_CALL_INCOMING
3242                   && tpl_entity_get_entity_type (receiver) == TPL_ENTITY_SELF)
3243                 {
3244                   append = TRUE;
3245                 }
3246             }
3247         }
3248
3249       if (append)
3250         {
3251           EmpathyMessage *msg = empathy_message_from_tpl_log_event (event);
3252           log_window_append_message (event, msg);
3253           tp_clear_object (&msg);
3254         }
3255
3256       g_object_unref (event);
3257     }
3258   g_list_free (events);
3259
3260   model = GTK_TREE_MODEL (log_window->priv->store_events);
3261   n = gtk_tree_model_iter_n_children (model, NULL) - 1;
3262
3263   if (n >= 0 && gtk_tree_model_iter_nth_child (model, &iter, NULL, n))
3264     {
3265       GtkTreePath *path;
3266       char *str, *script;
3267
3268       path = gtk_tree_model_get_path (model, &iter);
3269       str = gtk_tree_path_to_string (path);
3270
3271       script = g_strdup_printf ("javascript:scrollToRow([%s]);",
3272           g_strdelimit (str, ":", ','));
3273
3274       webkit_web_view_execute_script (
3275           WEBKIT_WEB_VIEW (log_window->priv->webview),
3276           script);
3277
3278       gtk_tree_path_free (path);
3279       g_free (str);
3280       g_free (script);
3281     }
3282
3283  out:
3284   ctx_free (ctx);
3285
3286   _tpl_action_chain_continue (log_window->priv->chain);
3287 }
3288
3289 static void
3290 get_events_for_date (TplActionChain *chain, gpointer user_data)
3291 {
3292   Ctx *ctx = user_data;
3293
3294   tpl_log_manager_get_events_for_date_async (ctx->self->priv->log_manager,
3295       ctx->account, ctx->entity, ctx->event_mask,
3296       ctx->date,
3297       log_window_got_messages_for_date_cb,
3298       ctx);
3299 }
3300
3301 static void
3302 log_window_get_messages_for_dates (EmpathyLogWindow *self,
3303     GList *dates)
3304 {
3305   GList *accounts, *targets, *acc, *targ, *l;
3306   TplEventTypeMask event_mask;
3307   EventSubtype subtype;
3308   GDate *date, *anytime, *separator;
3309
3310   if (!log_window_get_selected (self,
3311       &accounts, &targets, NULL, NULL, &event_mask, &subtype))
3312     return;
3313
3314   anytime = g_date_new_dmy (2, 1, -1);
3315   separator = g_date_new_dmy (1, 1, -1);
3316
3317   _tpl_action_chain_clear (self->priv->chain);
3318   self->priv->count++;
3319
3320   for (acc = accounts, targ = targets;
3321        acc != NULL && targ != NULL;
3322        acc = acc->next, targ = targ->next)
3323     {
3324       TpAccount *account = acc->data;
3325       TplEntity *target = targ->data;
3326
3327       for (l = dates; l != NULL; l = l->next)
3328         {
3329           date = l->data;
3330
3331           /* Get events */
3332           if (g_date_compare (date, anytime) != 0)
3333             {
3334               Ctx *ctx;
3335
3336               ctx = ctx_new (self, account, target, date, event_mask, subtype,
3337                   self->priv->count);
3338               _tpl_action_chain_append (self->priv->chain, get_events_for_date, ctx);
3339             }
3340           else
3341             {
3342               GtkTreeView *view = GTK_TREE_VIEW (self->priv->treeview_when);
3343               GtkTreeModel *model = gtk_tree_view_get_model (view);
3344               GtkTreeIter iter;
3345               gboolean next;
3346               GDate *d;
3347
3348               for (next = gtk_tree_model_get_iter_first (model, &iter);
3349                    next;
3350                    next = gtk_tree_model_iter_next (model, &iter))
3351                 {
3352                   Ctx *ctx;
3353
3354                   gtk_tree_model_get (model, &iter,
3355                       COL_WHEN_DATE, &d,
3356                       -1);
3357
3358                   if (g_date_compare (d, anytime) != 0 &&
3359                       g_date_compare (d, separator) != 0)
3360                     {
3361                       ctx = ctx_new (self, account, target, d,
3362                           event_mask, subtype, self->priv->count);
3363                       _tpl_action_chain_append (self->priv->chain, get_events_for_date, ctx);
3364                     }
3365
3366                   g_date_free (d);
3367                 }
3368             }
3369         }
3370     }
3371
3372   start_spinner ();
3373   _tpl_action_chain_start (self->priv->chain);
3374
3375   g_list_free_full (accounts, g_object_unref);
3376   g_list_free_full (targets, g_object_unref);
3377   g_date_free (separator);
3378   g_date_free (anytime);
3379 }
3380
3381 static void
3382 log_manager_got_dates_cb (GObject *manager,
3383     GAsyncResult *result,
3384     gpointer user_data)
3385 {
3386   Ctx *ctx = user_data;
3387   GtkTreeView *view;
3388   GtkTreeModel *model;
3389   GtkListStore *store;
3390   GtkTreeIter iter;
3391   GList *dates;
3392   GList *l;
3393   GError *error = NULL;
3394
3395   if (log_window == NULL)
3396     {
3397       ctx_free (ctx);
3398       return;
3399     }
3400
3401   if (log_window->priv->count != ctx->count)
3402     goto out;
3403
3404   if (!tpl_log_manager_get_dates_finish (TPL_LOG_MANAGER (manager),
3405        result, &dates, &error))
3406     {
3407       DEBUG ("Unable to retrieve messages' dates: %s. Aborting",
3408           error->message);
3409       goto out;
3410     }
3411
3412   view = GTK_TREE_VIEW (log_window->priv->treeview_when);
3413   model = gtk_tree_view_get_model (view);
3414   store = GTK_LIST_STORE (model);
3415
3416   for (l = dates; l != NULL; l = l->next)
3417     {
3418       add_date_if_needed (log_window, l->data);
3419     }
3420
3421   if (gtk_tree_model_get_iter_first (model, &iter))
3422     {
3423       gchar *separator = NULL;
3424
3425       if (gtk_tree_model_iter_next (model, &iter))
3426         {
3427           gtk_tree_model_get (model, &iter,
3428               COL_WHEN_TEXT, &separator,
3429               -1);
3430         }
3431
3432       if (g_strcmp0 (separator, "separator") != 0)
3433         {
3434           GDate *date;
3435
3436           date = g_date_new_dmy (1, 1, -1);
3437
3438           gtk_list_store_prepend (store, &iter);
3439           gtk_list_store_set (store, &iter,
3440               COL_WHEN_DATE, date,
3441               COL_WHEN_TEXT, "separator",
3442               -1);
3443
3444           g_date_free (date);
3445
3446           date = g_date_new_dmy (2, 1, -1);
3447
3448           gtk_list_store_prepend (store, &iter);
3449           gtk_list_store_set (store, &iter,
3450               COL_WHEN_DATE, date,
3451               COL_WHEN_TEXT, _("Anytime"),
3452               -1);
3453
3454           g_date_free (date);
3455         }
3456
3457       g_free (separator);
3458     }
3459
3460   g_list_free_full (dates, g_free);
3461  out:
3462   ctx_free (ctx);
3463   _tpl_action_chain_continue (log_window->priv->chain);
3464 }
3465
3466 static void
3467 select_date (TplActionChain *chain, gpointer user_data)
3468 {
3469   GtkTreeView *view;
3470   GtkTreeModel *model;
3471   GtkTreeSelection *selection;
3472   GtkTreeIter iter;
3473   gboolean next;
3474   gboolean selected = FALSE;
3475
3476   view = GTK_TREE_VIEW (log_window->priv->treeview_when);
3477   model = gtk_tree_view_get_model (view);
3478   selection = gtk_tree_view_get_selection (view);
3479
3480   if (log_window->priv->current_dates != NULL)
3481     {
3482       for (next = gtk_tree_model_get_iter_first (model, &iter);
3483            next;
3484            next = gtk_tree_model_iter_next (model, &iter))
3485         {
3486           GDate *date;
3487
3488           gtk_tree_model_get (model, &iter,
3489               COL_WHEN_DATE, &date,
3490               -1);
3491
3492           if (g_list_find_custom (log_window->priv->current_dates, date,
3493                   (GCompareFunc) g_date_compare) != NULL)
3494             {
3495               GtkTreePath *path;
3496
3497               gtk_tree_selection_select_iter (selection, &iter);
3498               path = gtk_tree_model_get_path (model, &iter);
3499               gtk_tree_view_scroll_to_cell (view, path, NULL, FALSE, 0, 0);
3500               selected = TRUE;
3501
3502               gtk_tree_path_free (path);
3503             }
3504
3505           g_date_free (date);
3506         }
3507     }
3508
3509   if (!selected)
3510     {
3511       /* Show messages of the most recent date */
3512       if (gtk_tree_model_iter_nth_child (model, &iter, NULL, 2))
3513         gtk_tree_selection_select_iter (selection, &iter);
3514     }
3515
3516   _tpl_action_chain_continue (log_window->priv->chain);
3517 }
3518
3519 static void
3520 get_dates_for_entity (TplActionChain *chain, gpointer user_data)
3521 {
3522   Ctx *ctx = user_data;
3523
3524   tpl_log_manager_get_dates_async (ctx->self->priv->log_manager,
3525       ctx->account, ctx->entity, ctx->event_mask,
3526       log_manager_got_dates_cb, ctx);
3527 }
3528
3529 static void
3530 log_window_chats_get_messages (EmpathyLogWindow *self,
3531     gboolean force_get_dates)
3532 {
3533   GList *accounts, *targets, *dates;
3534   TplEventTypeMask event_mask;
3535   GtkTreeView *view;
3536   GtkTreeModel *model;
3537   GtkListStore *store;
3538   GtkTreeSelection *selection;
3539
3540   if (!log_window_get_selected (self, &accounts, &targets, NULL,
3541       &dates, &event_mask, NULL))
3542     return;
3543
3544   view = GTK_TREE_VIEW (self->priv->treeview_when);
3545   selection = gtk_tree_view_get_selection (view);
3546   model = gtk_tree_view_get_model (view);
3547   store = GTK_LIST_STORE (model);
3548
3549   /* Clear all current messages shown in the textview */
3550   gtk_tree_store_clear (self->priv->store_events);
3551
3552   _tpl_action_chain_clear (self->priv->chain);
3553   self->priv->count++;
3554
3555   /* If there's a search use the returned hits */
3556   if (self->priv->hits != NULL)
3557     {
3558       if (force_get_dates)
3559         {
3560           g_signal_handlers_block_by_func (selection,
3561               log_window_when_changed_cb,
3562               self);
3563
3564           gtk_list_store_clear (store);
3565
3566           g_signal_handlers_unblock_by_func (selection,
3567               log_window_when_changed_cb,
3568               self);
3569
3570           populate_dates_from_search_hits (accounts, targets);
3571         }
3572       else
3573         {
3574           populate_events_from_search_hits (accounts, targets, dates);
3575         }
3576     }
3577   /* Either use the supplied date or get the last */
3578   else if (force_get_dates || dates == NULL)
3579     {
3580       GList *acc, *targ;
3581
3582       if (self->priv->current_dates != NULL)
3583         {
3584           g_list_free_full (self->priv->current_dates,
3585               (GDestroyNotify) g_date_free);
3586           self->priv->current_dates = NULL;
3587         }
3588
3589       if (gtk_tree_selection_count_selected_rows (selection) > 0)
3590         {
3591           GList *paths, *l;
3592           GtkTreeIter iter;
3593
3594           paths = gtk_tree_selection_get_selected_rows (selection, NULL);
3595
3596           for (l = paths; l != NULL; l = l->next)
3597             {
3598               GtkTreePath *path = l->data;
3599               GDate *date;
3600
3601               gtk_tree_model_get_iter (model, &iter, path);
3602               gtk_tree_model_get (model, &iter,
3603                   COL_WHEN_DATE, &date,
3604                   -1);
3605
3606               /* The list takes ownership of the date. */
3607               self->priv->current_dates =
3608                   g_list_prepend (self->priv->current_dates, date);
3609             }
3610
3611           g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free);
3612         }
3613
3614       g_signal_handlers_block_by_func (selection,
3615           log_window_when_changed_cb,
3616           self);
3617
3618       gtk_list_store_clear (store);
3619
3620       g_signal_handlers_unblock_by_func (selection,
3621           log_window_when_changed_cb,
3622           self);
3623
3624       /* Get a list of dates and show them on the treeview */
3625       for (targ = targets, acc = accounts;
3626            targ != NULL && acc != NULL;
3627            targ = targ->next, acc = acc->next)
3628         {
3629           TpAccount *account = acc->data;
3630           TplEntity *target = targ->data;
3631           Ctx *ctx = ctx_new (self, account, target, NULL, event_mask, 0,
3632               self->priv->count);
3633
3634           _tpl_action_chain_append (self->priv->chain, get_dates_for_entity, ctx);
3635         }
3636       _tpl_action_chain_append (self->priv->chain, select_date, NULL);
3637       _tpl_action_chain_start (self->priv->chain);
3638     }
3639   else
3640     {
3641       /* Show messages of the selected date */
3642       log_window_get_messages_for_dates (self, dates);
3643     }
3644
3645   g_list_free_full (accounts, g_object_unref);
3646   g_list_free_full (targets, g_object_unref);
3647   g_list_free_full (dates, (GFreeFunc) g_date_free);
3648 }
3649
3650 typedef struct {
3651   EmpathyAccountChooserFilterResultCallback callback;
3652   gpointer user_data;
3653 } FilterCallbackData;
3654
3655 static void
3656 got_entities (GObject *manager,
3657     GAsyncResult *result,
3658     gpointer user_data)
3659 {
3660   FilterCallbackData *data = user_data;
3661   GList *entities;
3662   GError *error = NULL;
3663
3664   if (!tpl_log_manager_get_entities_finish (TPL_LOG_MANAGER (manager),
3665       result, &entities, &error))
3666     {
3667       DEBUG ("Could not get entities: %s", error->message);
3668       g_error_free (error);
3669       data->callback (FALSE, data->user_data);
3670     }
3671   else
3672     {
3673       data->callback (entities != NULL, data->user_data);
3674
3675       g_list_free_full (entities, g_object_unref);
3676     }
3677
3678   g_slice_free (FilterCallbackData, data);
3679 }
3680
3681 static void
3682 empathy_account_chooser_filter_has_logs (TpAccount *account,
3683     EmpathyAccountChooserFilterResultCallback callback,
3684     gpointer callback_data,
3685     gpointer user_data)
3686 {
3687   TplLogManager *manager = tpl_log_manager_dup_singleton ();
3688   FilterCallbackData *cb_data = g_slice_new0 (FilterCallbackData);
3689
3690   cb_data->callback = callback;
3691   cb_data->user_data = callback_data;
3692
3693   tpl_log_manager_get_entities_async (manager, account, got_entities, cb_data);
3694
3695   g_object_unref (manager);
3696 }
3697
3698 static void
3699 log_window_logger_clear_account_cb (TpProxy *proxy,
3700     const GError *error,
3701     gpointer user_data,
3702     GObject *weak_object)
3703 {
3704   EmpathyLogWindow *self = EMPATHY_LOG_WINDOW (user_data);
3705
3706   if (error != NULL)
3707     g_warning ("Error when clearing logs: %s", error->message);
3708
3709   /* Refresh the log viewer so the logs are cleared if the account
3710    * has been deleted */
3711   gtk_tree_store_clear (self->priv->store_events);
3712   log_window_who_populate (self);
3713
3714   /* Re-filter the account chooser so the accounts without logs get
3715    * greyed out */
3716   empathy_account_chooser_refilter (
3717       EMPATHY_ACCOUNT_CHOOSER (self->priv->account_chooser));
3718 }
3719
3720 static void
3721 log_window_delete_menu_clicked_cb (GtkMenuItem *menuitem,
3722     EmpathyLogWindow *self)
3723 {
3724   GtkWidget *dialog, *content_area, *hbox, *label;
3725   EmpathyAccountChooser *account_chooser;
3726   gint response_id;
3727   TpDBusDaemon *bus;
3728   TpProxy *logger;
3729   GError *error = NULL;
3730
3731   account_chooser = (EmpathyAccountChooser *) empathy_account_chooser_new ();
3732   empathy_account_chooser_set_has_all_option (account_chooser, TRUE);
3733
3734   empathy_account_chooser_refilter (account_chooser);
3735
3736   /* Select the same account as in the history window */
3737   empathy_account_chooser_set_account (account_chooser,
3738       empathy_account_chooser_get_account (
3739         EMPATHY_ACCOUNT_CHOOSER (self->priv->account_chooser)));
3740
3741   dialog = gtk_message_dialog_new_with_markup (GTK_WINDOW (self),
3742       GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING,
3743       GTK_BUTTONS_NONE,
3744       _("Are you sure you want to delete all logs of previous conversations?"));
3745
3746   gtk_dialog_add_buttons (GTK_DIALOG (dialog),
3747       GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
3748       _("Clear All"), GTK_RESPONSE_APPLY,
3749       NULL);
3750
3751   content_area = gtk_message_dialog_get_message_area (
3752       GTK_MESSAGE_DIALOG (dialog));
3753
3754   hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
3755   label = gtk_label_new (_("Delete from:"));
3756   gtk_box_pack_start (GTK_BOX (hbox), label,
3757       FALSE, FALSE, 0);
3758   gtk_box_pack_start (GTK_BOX (hbox), GTK_WIDGET (account_chooser),
3759       FALSE, FALSE, 0);
3760   gtk_box_pack_start (GTK_BOX (content_area), hbox,
3761       FALSE, FALSE, 0);
3762
3763   gtk_widget_show_all (hbox);
3764
3765   response_id = gtk_dialog_run (GTK_DIALOG (dialog));
3766
3767   if (response_id != GTK_RESPONSE_APPLY)
3768     goto out;
3769
3770   bus = tp_dbus_daemon_dup (&error);
3771   if (error != NULL)
3772     {
3773       g_warning ("Could not delete logs: %s", error->message);
3774       g_error_free (error);
3775       goto out;
3776     }
3777
3778   logger = g_object_new (TP_TYPE_PROXY,
3779       "bus-name", "org.freedesktop.Telepathy.Logger",
3780       "object-path", "/org/freedesktop/Telepathy/Logger",
3781       "dbus-daemon", bus,
3782       NULL);
3783   g_object_unref (bus);
3784
3785   tp_proxy_add_interface_by_id (logger, EMP_IFACE_QUARK_LOGGER);
3786
3787   if (empathy_account_chooser_has_all_selected (account_chooser))
3788     {
3789       DEBUG ("Deleting logs for all the accounts");
3790
3791       emp_cli_logger_call_clear (logger, -1,
3792           log_window_logger_clear_account_cb,
3793           self, NULL, G_OBJECT (self));
3794     }
3795   else
3796     {
3797       TpAccount *account;
3798
3799       account = empathy_account_chooser_get_account (account_chooser);
3800
3801       DEBUG ("Deleting logs for %s", tp_proxy_get_object_path (account));
3802
3803       emp_cli_logger_call_clear_account (logger, -1,
3804           tp_proxy_get_object_path (account),
3805           log_window_logger_clear_account_cb,
3806           self, NULL, G_OBJECT (self));
3807     }
3808
3809   g_object_unref (logger);
3810  out:
3811   gtk_widget_destroy (dialog);
3812 }