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