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