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