]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-log-window.c
0056ebe37d2ca672d731f730873f06ae597d0eff
[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
33 #include <telepathy-glib/telepathy-glib.h>
34 #include <telepathy-glib/proxy-subclass.h>
35
36 #include <telepathy-logger/telepathy-logger.h>
37 #include <telepathy-logger/call-event.h>
38
39 #include <extensions/extensions.h>
40
41 #include <libempathy/action-chain-internal.h>
42 #include <libempathy/empathy-chatroom-manager.h>
43 #include <libempathy/empathy-chatroom.h>
44 #include <libempathy/empathy-message.h>
45 #include <libempathy/empathy-request-util.h>
46 #include <libempathy/empathy-utils.h>
47 #include <libempathy/empathy-time.h>
48
49 #include "empathy-log-window.h"
50 #include "empathy-account-chooser.h"
51 #include "empathy-call-utils.h"
52 #include "empathy-chat-view.h"
53 #include "empathy-contact-dialogs.h"
54 #include "empathy-images.h"
55 #include "empathy-theme-manager.h"
56 #include "empathy-ui-utils.h"
57
58 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
59 #include <libempathy/empathy-debug.h>
60
61 typedef struct
62 {
63   GtkWidget *window;
64
65   GtkWidget *button_profile;
66   GtkWidget *button_chat;
67   GtkWidget *button_call;
68   GtkWidget *button_video;
69
70   GtkWidget *search_entry;
71
72   GtkWidget *notebook;
73   GtkWidget *spinner;
74
75   GtkWidget *treeview_who;
76   GtkWidget *treeview_what;
77   GtkWidget *treeview_when;
78   GtkWidget *treeview_events;
79
80   GtkTreeStore *store_events;
81
82   GtkWidget *account_chooser;
83
84   gchar *last_find;
85
86   TplActionChain *chain;
87   TplLogManager *log_manager;
88
89   /* Used to cancel logger calls when no longer needed */
90   guint count;
91
92   /* List of owned TplLogSearchHits, free with tpl_log_search_hit_free */
93   GList *hits;
94   guint source;
95
96   /* Only used while waiting for the account chooser to be ready */
97   TpAccount *selected_account;
98   gchar *selected_chat_id;
99   gboolean selected_is_chatroom;
100 } EmpathyLogWindow;
101
102 static void log_window_destroy_cb                (GtkWidget        *widget,
103                                                   EmpathyLogWindow *window);
104 static void log_window_search_entry_changed_cb   (GtkWidget        *entry,
105                                                   EmpathyLogWindow *window);
106 static void log_window_search_entry_activate_cb  (GtkWidget        *widget,
107                                                   EmpathyLogWindow *window);
108 static void log_window_search_entry_icon_pressed_cb (GtkEntry      *entry,
109                                                   GtkEntryIconPosition icon_pos,
110                                                   GdkEvent *event,
111                                                   gpointer user_data);
112 static void log_window_who_populate              (EmpathyLogWindow *window);
113 static void log_window_who_setup                 (EmpathyLogWindow *window);
114 static void log_window_when_setup                (EmpathyLogWindow *window);
115 static void log_window_what_setup                (EmpathyLogWindow *window);
116 static void log_window_events_setup              (EmpathyLogWindow *window);
117 static void log_window_chats_accounts_changed_cb (GtkWidget        *combobox,
118                                                   EmpathyLogWindow *window);
119 static void log_window_chats_set_selected        (EmpathyLogWindow *window);
120 static void log_window_chats_get_messages        (EmpathyLogWindow *window,
121                                                   gboolean force_get_dates);
122 static void log_window_when_changed_cb           (GtkTreeSelection *selection,
123                                                   EmpathyLogWindow *window);
124 static void log_window_delete_menu_clicked_cb    (GtkMenuItem      *menuitem,
125                                                   EmpathyLogWindow *window);
126
127 static void
128 empathy_account_chooser_filter_has_logs (TpAccount *account,
129     EmpathyAccountChooserFilterResultCallback callback,
130     gpointer callback_data,
131     gpointer user_data);
132
133 enum
134 {
135   PAGE_EVENTS,
136   PAGE_SPINNER,
137   PAGE_EMPTY
138 };
139
140 enum
141 {
142   COL_TYPE_ANY,
143   COL_TYPE_SEPARATOR,
144   COL_TYPE_NORMAL
145 };
146
147 enum
148 {
149   COL_WHO_TYPE,
150   COL_WHO_ICON,
151   COL_WHO_NAME,
152   COL_WHO_ACCOUNT,
153   COL_WHO_TARGET,
154   COL_WHO_COUNT
155 };
156
157 enum
158 {
159   COL_WHAT_TYPE,
160   COL_WHAT_SUBTYPE,
161   COL_WHAT_TEXT,
162   COL_WHAT_ICON,
163   COL_WHAT_EXPANDER,
164   COL_WHAT_COUNT
165 };
166
167 enum
168 {
169   COL_WHEN_DATE,
170   COL_WHEN_TEXT,
171   COL_WHEN_ICON,
172   COL_WHEN_COUNT
173 };
174
175 enum
176 {
177   COL_EVENTS_TYPE,
178   COL_EVENTS_TS,
179   COL_EVENTS_PRETTY_DATE,
180   COL_EVENTS_ICON,
181   COL_EVENTS_TEXT,
182   COL_EVENTS_ACCOUNT,
183   COL_EVENTS_TARGET,
184   COL_EVENTS_EVENT,
185   COL_EVENTS_COUNT
186 };
187
188 #define CALENDAR_ICON "stock_calendar"
189
190 /* Seconds between two messages to be considered one conversation */
191 #define MAX_GAP 30*60
192
193 #define WHAT_TYPE_SEPARATOR -1
194
195 typedef enum
196 {
197   EVENT_CALL_INCOMING = 1 << 0,
198   EVENT_CALL_OUTGOING = 1 << 1,
199   EVENT_CALL_MISSED   = 1 << 2,
200   EVENT_CALL_ALL      = 1 << 3,
201 } EventSubtype;
202
203 static EmpathyLogWindow *log_window = NULL;
204
205 static gboolean has_element;
206
207 #ifndef _date_copy
208 #define _date_copy(d) g_date_new_julian (g_date_get_julian (d))
209 #endif
210
211 typedef struct
212 {
213   EmpathyLogWindow *window;
214   TpAccount *account;
215   TplEntity *entity;
216   GDate *date;
217   TplEventTypeMask event_mask;
218   EventSubtype subtype;
219   guint count;
220 } Ctx;
221
222 static Ctx *
223 ctx_new (EmpathyLogWindow *window,
224     TpAccount *account,
225     TplEntity *entity,
226     GDate *date,
227     TplEventTypeMask event_mask,
228     EventSubtype subtype,
229     guint count)
230 {
231   Ctx *ctx = g_slice_new0 (Ctx);
232
233   ctx->window = window;
234   if (account != NULL)
235     ctx->account = g_object_ref (account);
236   if (entity != NULL)
237     ctx->entity = g_object_ref (entity);
238   if (date != NULL)
239     ctx->date = _date_copy (date);
240   ctx->event_mask = event_mask;
241   ctx->subtype = subtype;
242   ctx->count = count;
243
244   return ctx;
245 }
246
247 static void
248 ctx_free (Ctx *ctx)
249 {
250   tp_clear_object (&ctx->account);
251   tp_clear_object (&ctx->entity);
252   tp_clear_pointer (&ctx->date, g_date_free);
253
254   g_slice_free (Ctx, ctx);
255 }
256
257 static void
258 account_chooser_ready_cb (EmpathyAccountChooser *chooser,
259     EmpathyLogWindow *window)
260 {
261   /* We'll display the account once the model has been populate with the chats
262    * of this account. */
263   empathy_account_chooser_set_account (EMPATHY_ACCOUNT_CHOOSER (
264       window->account_chooser), window->selected_account);
265 }
266
267 static void
268 select_account_once_ready (EmpathyLogWindow *self,
269     TpAccount *account,
270     const gchar *chat_id,
271     gboolean is_chatroom)
272 {
273   EmpathyAccountChooser *account_chooser;
274
275   account_chooser = EMPATHY_ACCOUNT_CHOOSER (self->account_chooser);
276
277   tp_clear_object (&self->selected_account);
278   self->selected_account = g_object_ref (account);
279
280   g_free (self->selected_chat_id);
281   self->selected_chat_id = g_strdup (chat_id);
282
283   self->selected_is_chatroom = is_chatroom;
284
285   if (empathy_account_chooser_is_ready (account_chooser))
286     account_chooser_ready_cb (account_chooser, self);
287   else
288     /* Chat will be selected once the account chooser is ready */
289     g_signal_connect (account_chooser, "ready",
290         G_CALLBACK (account_chooser_ready_cb), self);
291 }
292
293 static void
294 toolbutton_profile_clicked (GtkToolButton *toolbutton,
295     EmpathyLogWindow *window)
296 {
297   GtkTreeView *view;
298   GtkTreeSelection *selection;
299   GtkTreeModel *model;
300   GtkTreeIter iter;
301   GtkTreePath *path;
302   GList *paths;
303   TpAccount *account;
304   TplEntity *target;
305   EmpathyContact *contact;
306   gint type;
307
308   g_return_if_fail (window != NULL);
309
310   view = GTK_TREE_VIEW (log_window->treeview_who);
311   selection = gtk_tree_view_get_selection (view);
312
313   paths = gtk_tree_selection_get_selected_rows (selection, &model);
314   g_return_if_fail (paths != NULL);
315
316   path = paths->data;
317   gtk_tree_model_get_iter (model, &iter, path);
318   gtk_tree_model_get (model, &iter,
319       COL_WHO_ACCOUNT, &account,
320       COL_WHO_TARGET, &target,
321       COL_WHO_TYPE, &type,
322       -1);
323
324   g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free);
325
326   g_return_if_fail (type == COL_TYPE_NORMAL);
327
328   contact = empathy_contact_from_tpl_contact (account, target);
329   empathy_contact_information_dialog_show (contact,
330       GTK_WINDOW (window->window));
331   g_object_unref (contact);
332
333   g_object_unref (account);
334   g_object_unref (target);
335 }
336
337 static void
338 toolbutton_chat_clicked (GtkToolButton *toolbutton,
339     EmpathyLogWindow *window)
340 {
341   GtkTreeView *view;
342   GtkTreeSelection *selection;
343   GtkTreeModel *model;
344   GtkTreeIter iter;
345   GtkTreePath *path;
346   GList *paths;
347   TpAccount *account;
348   TplEntity *target;
349   EmpathyContact *contact;
350   gint type;
351
352   g_return_if_fail (window != NULL);
353
354   view = GTK_TREE_VIEW (log_window->treeview_who);
355   selection = gtk_tree_view_get_selection (view);
356
357   paths = gtk_tree_selection_get_selected_rows (selection, &model);
358   g_return_if_fail (paths != NULL);
359
360   path = paths->data;
361   gtk_tree_model_get_iter (model, &iter, path);
362   gtk_tree_model_get (model, &iter,
363       COL_WHO_ACCOUNT, &account,
364       COL_WHO_TARGET, &target,
365       COL_WHO_TYPE, &type,
366       -1);
367
368   g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free);
369
370   g_return_if_fail (type == COL_TYPE_NORMAL);
371
372   contact = empathy_contact_from_tpl_contact (account, target);
373   empathy_chat_with_contact (contact,
374       gtk_get_current_event_time ());
375
376   g_object_unref (contact);
377   g_object_unref (account);
378   g_object_unref (target);
379 }
380
381 static void
382 toolbutton_av_clicked (GtkToolButton *toolbutton,
383     EmpathyLogWindow *window)
384 {
385   GtkTreeView *view;
386   GtkTreeSelection *selection;
387   GtkTreeModel *model;
388   GtkTreeIter iter;
389   GtkTreePath *path;
390   GList *paths;
391   TpAccount *account;
392   gchar *contact;
393   gint type;
394   gboolean video;
395
396   g_return_if_fail (window != NULL);
397
398   view = GTK_TREE_VIEW (log_window->treeview_who);
399   selection = gtk_tree_view_get_selection (view);
400
401   paths = gtk_tree_selection_get_selected_rows (selection, &model);
402   g_return_if_fail (paths != NULL);
403
404   path = paths->data;
405   gtk_tree_model_get_iter (model, &iter, path);
406   gtk_tree_model_get (model, &iter,
407       COL_WHO_ACCOUNT, &account,
408       COL_WHO_NAME, &contact,
409       COL_WHO_TYPE, &type,
410       -1);
411
412   g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free);
413
414   g_return_if_fail (type == COL_TYPE_NORMAL);
415
416   video = (GTK_WIDGET (toolbutton) == window->button_video);
417
418   empathy_call_new_with_streams (contact, account,
419       TRUE, video, gtk_get_current_event_time ());
420
421   g_free (contact);
422   g_object_unref (account);
423 }
424
425 GtkWidget *
426 empathy_log_window_show (TpAccount  *account,
427     const gchar *chat_id,
428     gboolean     is_chatroom,
429     GtkWindow   *parent)
430 {
431   EmpathyAccountChooser   *account_chooser;
432   GtkBuilder             *gui;
433   gchar                  *filename;
434   EmpathyLogWindow       *window;
435   GtkWidget *vbox, *accounts, *search, *label, *quit;
436
437   if (log_window != NULL)
438     {
439       gtk_window_present (GTK_WINDOW (log_window->window));
440
441       if (account != NULL && chat_id != NULL)
442         select_account_once_ready (log_window, account, chat_id, is_chatroom);
443
444       return log_window->window;
445     }
446
447   log_window = g_new0 (EmpathyLogWindow, 1);
448   log_window->chain = _tpl_action_chain_new_async (NULL, NULL, NULL);
449
450   log_window->log_manager = tpl_log_manager_dup_singleton ();
451
452   window = log_window;
453
454   filename = empathy_file_lookup ("empathy-log-window.ui", "libempathy-gtk");
455   gui = empathy_builder_get_file (filename,
456       "log_window", &window->window,
457       "toolbutton_profile", &window->button_profile,
458       "toolbutton_chat", &window->button_chat,
459       "toolbutton_call", &window->button_call,
460       "toolbutton_video", &window->button_video,
461       "toolbutton_accounts", &accounts,
462       "toolbutton_search", &search,
463       "imagemenuitem_quit", &quit,
464       "treeview_who", &window->treeview_who,
465       "treeview_what", &window->treeview_what,
466       "treeview_when", &window->treeview_when,
467       "treeview_events", &window->treeview_events,
468       "notebook", &window->notebook,
469       "spinner", &window->spinner,
470       NULL);
471   g_free (filename);
472
473   empathy_builder_connect (gui, window,
474       "log_window", "destroy", log_window_destroy_cb,
475       "toolbutton_profile", "clicked", toolbutton_profile_clicked,
476       "toolbutton_chat", "clicked", toolbutton_chat_clicked,
477       "toolbutton_call", "clicked", toolbutton_av_clicked,
478       "toolbutton_video", "clicked", toolbutton_av_clicked,
479       "imagemenuitem_delete", "activate", log_window_delete_menu_clicked_cb,
480       NULL);
481
482   g_object_unref (gui);
483
484   g_object_add_weak_pointer (G_OBJECT (window->window),
485       (gpointer) &log_window);
486
487   g_signal_connect_swapped (quit, "activate",
488       G_CALLBACK (gtk_widget_destroy), window->window);
489
490   /* Account chooser for chats */
491   vbox = gtk_vbox_new (FALSE, 3);
492
493   window->account_chooser = empathy_account_chooser_new ();
494   account_chooser = EMPATHY_ACCOUNT_CHOOSER (window->account_chooser);
495   empathy_account_chooser_set_has_all_option (account_chooser, TRUE);
496   empathy_account_chooser_set_filter (account_chooser,
497       empathy_account_chooser_filter_has_logs, NULL);
498   empathy_account_chooser_set_all (account_chooser);
499
500   g_signal_connect (window->account_chooser, "changed",
501       G_CALLBACK (log_window_chats_accounts_changed_cb),
502       window);
503
504   label = gtk_label_new (_("Show"));
505
506   gtk_box_pack_start (GTK_BOX (vbox),
507       window->account_chooser,
508       FALSE, FALSE, 0);
509
510   gtk_box_pack_start (GTK_BOX (vbox),
511       label,
512       FALSE, FALSE, 0);
513
514   gtk_widget_show_all (vbox);
515   gtk_container_add (GTK_CONTAINER (accounts), vbox);
516
517   /* Search entry */
518   vbox = gtk_vbox_new (FALSE, 3);
519
520   window->search_entry = gtk_entry_new ();
521   gtk_entry_set_icon_from_stock (GTK_ENTRY (window->search_entry),
522       GTK_ENTRY_ICON_PRIMARY, GTK_STOCK_FIND);
523   gtk_entry_set_icon_from_stock (GTK_ENTRY (window->search_entry),
524       GTK_ENTRY_ICON_SECONDARY, GTK_STOCK_CLEAR);
525
526   label = gtk_label_new (_("Search"));
527
528   gtk_box_pack_start (GTK_BOX (vbox),
529       window->search_entry,
530       FALSE, FALSE, 0);
531
532   gtk_box_pack_start (GTK_BOX (vbox),
533       label,
534       FALSE, FALSE, 0);
535
536   gtk_widget_show_all (vbox);
537   gtk_container_add (GTK_CONTAINER (search), vbox);
538
539   g_signal_connect (window->search_entry, "changed",
540       G_CALLBACK (log_window_search_entry_changed_cb),
541       window);
542
543   g_signal_connect (window->search_entry, "activate",
544       G_CALLBACK (log_window_search_entry_activate_cb),
545       window);
546
547   g_signal_connect (window->search_entry, "icon-press",
548       G_CALLBACK (log_window_search_entry_icon_pressed_cb),
549       window);
550
551   /* Contacts */
552   log_window_events_setup (window);
553   log_window_who_setup (window);
554   log_window_what_setup (window);
555   log_window_when_setup (window);
556
557   log_window_who_populate (window);
558
559   if (account != NULL && chat_id != NULL)
560     select_account_once_ready (window, account, chat_id, is_chatroom);
561
562   if (parent != NULL)
563     gtk_window_set_transient_for (GTK_WINDOW (window->window),
564         GTK_WINDOW (parent));
565
566   gtk_widget_show (window->window);
567
568   return window->window;
569 }
570
571 static void
572 log_window_destroy_cb (GtkWidget *widget,
573     EmpathyLogWindow *window)
574 {
575   if (window->source != 0)
576     g_source_remove (window->source);
577
578   g_free (window->last_find);
579   _tpl_action_chain_free (window->chain);
580   g_object_unref (window->log_manager);
581   tp_clear_object (&window->selected_account);
582   g_free (window->selected_chat_id);
583
584   g_free (window);
585 }
586
587 static gboolean
588 account_equal (TpAccount *a,
589     TpAccount *b)
590 {
591   return g_str_equal (tp_proxy_get_object_path (a),
592       tp_proxy_get_object_path (b));
593 }
594
595 static gboolean
596 entity_equal (TplEntity *a,
597     TplEntity *b)
598 {
599   return g_str_equal (tpl_entity_get_identifier (a),
600       tpl_entity_get_identifier (b));
601 }
602
603 static gboolean
604 is_same_confroom (TplEvent *e1,
605     TplEvent *e2)
606 {
607   TplEntity *sender1 = tpl_event_get_sender (e1);
608   TplEntity *receiver1 = tpl_event_get_receiver (e1);
609   TplEntity *sender2 = tpl_event_get_sender (e2);
610   TplEntity *receiver2 = tpl_event_get_receiver (e2);
611   TplEntity *room1, *room2;
612
613   if (tpl_entity_get_entity_type (sender1) == TPL_ENTITY_ROOM)
614     room1 = sender1;
615   else if (tpl_entity_get_entity_type (receiver1) == TPL_ENTITY_ROOM)
616     room1 = receiver1;
617   else
618     return FALSE;
619
620   if (tpl_entity_get_entity_type (sender2) == TPL_ENTITY_ROOM)
621     room2 = sender2;
622   else if (tpl_entity_get_entity_type (receiver2) == TPL_ENTITY_ROOM)
623     room2 = receiver2;
624   else
625     return FALSE;
626
627   return g_str_equal (tpl_entity_get_identifier (room1),
628       tpl_entity_get_identifier (room1));
629 }
630
631 static TplEntity *
632 event_get_target (TplEvent *event)
633 {
634   TplEntity *sender = tpl_event_get_sender (event);
635   TplEntity *receiver = tpl_event_get_receiver (event);
636
637   if (tpl_entity_get_entity_type (sender) == TPL_ENTITY_SELF)
638     return receiver;
639
640   return sender;
641 }
642
643 static gboolean
644 model_is_parent (GtkTreeModel *model,
645     GtkTreeIter *iter,
646     TplEvent *event)
647 {
648   TplEvent *stored_event;
649   TplEntity *target;
650   TpAccount *account;
651   gboolean found = FALSE;
652   GtkTreeIter parent;
653
654   if (gtk_tree_model_iter_parent (model, &parent, iter))
655     return FALSE;
656
657   gtk_tree_model_get (model, iter,
658       COL_EVENTS_ACCOUNT, &account,
659       COL_EVENTS_TARGET, &target,
660       COL_EVENTS_EVENT, &stored_event,
661       -1);
662
663   if (G_OBJECT_TYPE (event) == G_OBJECT_TYPE (stored_event) &&
664       account_equal (account, tpl_event_get_account (event)) &&
665       (entity_equal (target, event_get_target (event)) ||
666       is_same_confroom (event, stored_event)))
667     {
668       GtkTreeIter child;
669       gint64 timestamp;
670
671       gtk_tree_model_iter_nth_child (model, &child, iter,
672           gtk_tree_model_iter_n_children (model, iter) - 1);
673
674       gtk_tree_model_get (model, &child,
675           COL_EVENTS_TS, &timestamp,
676           -1);
677
678       if (ABS (tpl_event_get_timestamp (event) - timestamp) < MAX_GAP)
679         {
680           /* The gap is smaller than 30 min */
681           found = TRUE;
682         }
683     }
684
685   g_object_unref (stored_event);
686   g_object_unref (account);
687   g_object_unref (target);
688
689   return found;
690 }
691
692 static const gchar *
693 get_contact_alias_for_message (EmpathyMessage *message)
694 {
695   EmpathyContact *sender, *receiver;
696
697   sender = empathy_message_get_sender (message);
698   receiver = empathy_message_get_receiver (message);
699
700   if (empathy_contact_is_user (sender))
701     return empathy_contact_get_alias (receiver);
702
703   return empathy_contact_get_alias (sender);
704 }
705
706 static void
707 get_parent_iter_for_message (TplEvent *event,
708     EmpathyMessage *message,
709     GtkTreeIter *parent)
710 {
711   GtkTreeStore *store;
712   GtkTreeModel *model;
713   GtkTreeIter iter;
714   gboolean parent_found = FALSE;
715   gboolean next;
716
717   store = log_window->store_events;
718   model = GTK_TREE_MODEL (store);
719
720   for (next = gtk_tree_model_get_iter_first (model, &iter);
721        next;
722        next = gtk_tree_model_iter_next (model, &iter))
723     {
724       if ((parent_found = model_is_parent (model, &iter, event)))
725         break;
726     }
727
728   if (parent_found)
729     {
730       *parent = iter;
731     }
732   else
733     {
734       GDateTime *date;
735       gchar *body, *pretty_date;
736
737       date = g_date_time_new_from_unix_utc (
738           tpl_event_get_timestamp (event));
739
740       pretty_date = g_date_time_format (date,
741           C_("A date with the time", "%A, %e %B %Y %X"));
742
743       body = g_strdup_printf (_("Chat with %s"),
744           get_contact_alias_for_message (message));
745
746       gtk_tree_store_append (store, &iter, NULL);
747       gtk_tree_store_set (store, &iter,
748           COL_EVENTS_TS, tpl_event_get_timestamp (event),
749           COL_EVENTS_PRETTY_DATE, pretty_date,
750           COL_EVENTS_TEXT, body,
751           COL_EVENTS_ICON, "stock_text_justify",
752           COL_EVENTS_ACCOUNT, tpl_event_get_account (event),
753           COL_EVENTS_TARGET, event_get_target (event),
754           COL_EVENTS_EVENT, event,
755           -1);
756
757       *parent = iter;
758
759       g_free (body);
760       g_free (pretty_date);
761       g_date_time_unref (date);
762     }
763 }
764
765 static const gchar *
766 get_icon_for_event (TplEvent *event)
767 {
768   const gchar *icon = NULL;
769
770   if (TPL_IS_CALL_EVENT (event))
771     {
772       TplCallEvent *call = TPL_CALL_EVENT (event);
773       TplCallEndReason reason = tpl_call_event_get_end_reason (call);
774       TplEntity *sender = tpl_event_get_sender (event);
775       TplEntity *receiver = tpl_event_get_receiver (event);
776
777       if (reason == TPL_CALL_END_REASON_NO_ANSWER)
778         icon = EMPATHY_IMAGE_CALL_MISSED;
779       else if (tpl_entity_get_entity_type (sender) == TPL_ENTITY_SELF)
780         icon = EMPATHY_IMAGE_CALL_OUTGOING;
781       else if (tpl_entity_get_entity_type (receiver) == TPL_ENTITY_SELF)
782         icon = EMPATHY_IMAGE_CALL_INCOMING;
783     }
784
785   return icon;
786 }
787
788 static void
789 log_window_append_chat_message (TplEvent *event,
790     EmpathyMessage *message)
791 {
792   GtkTreeStore *store = log_window->store_events;
793   GtkTreeIter iter, parent;
794   gchar *pretty_date, *body;
795   GDateTime *date;
796
797   date = g_date_time_new_from_unix_utc (
798       tpl_event_get_timestamp (event));
799
800   pretty_date = g_date_time_format (date, "%X");
801
802   get_parent_iter_for_message (event, message, &parent);
803
804   if (tpl_text_event_get_message_type (TPL_TEXT_EVENT (event))
805       == TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION)
806     {
807       body = g_strdup_printf ("* %s %s",
808           tpl_entity_get_alias (tpl_event_get_sender (event)),
809           empathy_message_get_body (message));
810     }
811   else
812     {
813       body = g_strdup_printf (
814           C_("First is a contact, second is what was said", "%s: %s"),
815           tpl_entity_get_alias (tpl_event_get_sender (event)),
816           empathy_message_get_body (message));
817     }
818
819   gtk_tree_store_append (store, &iter, &parent);
820   gtk_tree_store_set (store, &iter,
821       COL_EVENTS_TS, tpl_event_get_timestamp (event),
822       COL_EVENTS_PRETTY_DATE, pretty_date,
823       COL_EVENTS_TEXT, body,
824       COL_EVENTS_ICON, get_icon_for_event (event),
825       COL_EVENTS_ACCOUNT, tpl_event_get_account (event),
826       COL_EVENTS_TARGET, event_get_target (event),
827       COL_EVENTS_EVENT, event,
828       -1);
829
830   g_free (body);
831   g_free (pretty_date);
832   g_date_time_unref (date);
833 }
834
835 static void
836 log_window_append_call (TplEvent *event,
837     EmpathyMessage *message)
838 {
839   TplCallEvent *call = TPL_CALL_EVENT (event);
840   GtkTreeStore *store = log_window->store_events;
841   GtkTreeIter iter, child;
842   gchar *pretty_date, *duration, *finished;
843   GDateTime *started_date, *finished_date;
844   GTimeSpan span;
845
846   started_date = g_date_time_new_from_unix_utc (
847       tpl_event_get_timestamp (event));
848
849   pretty_date = g_date_time_format (started_date,
850       C_("A date with the time", "%A, %e %B %Y %X"));
851
852   gtk_tree_store_append (store, &iter, NULL);
853   gtk_tree_store_set (store, &iter,
854       COL_EVENTS_TS, tpl_event_get_timestamp (event),
855       COL_EVENTS_PRETTY_DATE, pretty_date,
856       COL_EVENTS_TEXT, empathy_message_get_body (message),
857       COL_EVENTS_ICON, get_icon_for_event (event),
858       COL_EVENTS_ACCOUNT, tpl_event_get_account (event),
859       COL_EVENTS_TARGET, event_get_target (event),
860       COL_EVENTS_EVENT, event,
861       -1);
862
863   if (tpl_call_event_get_end_reason (call) != TPL_CALL_END_REASON_NO_ANSWER)
864     {
865       gchar *body;
866
867       span = tpl_call_event_get_duration (TPL_CALL_EVENT (event));
868       if (span < 60)
869         duration = g_strdup_printf (_("%" G_GINT64_FORMAT " seconds"), span);
870       else
871         duration = g_strdup_printf (_("%" G_GINT64_FORMAT " minutes"),
872             span / 60);
873
874       finished_date = g_date_time_add (started_date, -span);
875       finished = g_date_time_format (finished_date, "%X");
876       g_date_time_unref (finished_date);
877
878       body = g_strdup_printf (_("Call took %s, ended at %s"),
879           duration, finished);
880
881       g_free (duration);
882       g_free (finished);
883
884       gtk_tree_store_append (store, &child, &iter);
885       gtk_tree_store_set (store, &child,
886           COL_EVENTS_TS, tpl_event_get_timestamp (event),
887           COL_EVENTS_TEXT, body,
888           COL_EVENTS_ACCOUNT, tpl_event_get_account (event),
889           COL_EVENTS_TARGET, event_get_target (event),
890           COL_EVENTS_EVENT, event,
891           -1);
892
893       g_free (body);
894     }
895
896   g_free (pretty_date);
897   g_date_time_unref (started_date);
898 }
899
900 static void
901 log_window_append_message (TplEvent *event,
902     EmpathyMessage *message)
903 {
904   if (TPL_IS_TEXT_EVENT (event))
905     log_window_append_chat_message (event, message);
906   else if (TPL_IS_CALL_EVENT (event))
907     log_window_append_call (event, message);
908   else
909     DEBUG ("Message type not handled");
910 }
911
912 static void
913 add_all_accounts_and_entities (GList **accounts,
914     GList **entities)
915 {
916   GtkTreeView      *view;
917   GtkTreeModel     *model;
918   GtkTreeIter       iter;
919
920   view = GTK_TREE_VIEW (log_window->treeview_who);
921   model = gtk_tree_view_get_model (view);
922
923   if (!gtk_tree_model_get_iter_first (model, &iter))
924     return;
925
926   do
927     {
928       TpAccount *account;
929       TplEntity *entity;
930       gint type;
931
932       gtk_tree_model_get (model, &iter,
933           COL_WHO_ACCOUNT, &account,
934           COL_WHO_TARGET, &entity,
935           COL_WHO_TYPE, &type,
936           -1);
937
938       if (type != COL_TYPE_NORMAL)
939         continue;
940
941       if (accounts != NULL)
942         *accounts = g_list_append (*accounts, account);
943
944       if (entities != NULL)
945         *entities = g_list_append (*entities, entity);
946     }
947   while (gtk_tree_model_iter_next (model, &iter));
948 }
949
950 static gboolean
951 log_window_get_selected (EmpathyLogWindow *window,
952     GList **accounts,
953     GList **entities,
954     GList **dates,
955     TplEventTypeMask *event_mask,
956     EventSubtype *subtype)
957 {
958   GtkTreeView      *view;
959   GtkTreeModel     *model;
960   GtkTreeSelection *selection;
961   GtkTreeIter       iter;
962   TplEventTypeMask  ev = 0;
963   EventSubtype      st = 0;
964   GList            *paths, *l;
965   gint              type;
966
967   view = GTK_TREE_VIEW (window->treeview_who);
968   model = gtk_tree_view_get_model (view);
969   selection = gtk_tree_view_get_selection (view);
970
971   paths = gtk_tree_selection_get_selected_rows (selection, NULL);
972   if (paths == NULL)
973     return FALSE;
974
975   if (accounts != NULL)
976     *accounts = NULL;
977   if (entities != NULL)
978     *entities = NULL;
979
980   for (l = paths; l != NULL; l = l->next)
981     {
982       GtkTreePath *path = l->data;
983       TpAccount *account;
984       TplEntity *entity;
985
986       gtk_tree_model_get_iter (model, &iter, path);
987       gtk_tree_model_get (model, &iter,
988           COL_WHO_ACCOUNT, &account,
989           COL_WHO_TARGET, &entity,
990           COL_WHO_TYPE, &type,
991           -1);
992
993       if (type == COL_TYPE_ANY)
994         {
995           if (accounts != NULL || entities != NULL)
996             add_all_accounts_and_entities (accounts, entities);
997           break;
998         }
999
1000       if (accounts != NULL)
1001         *accounts = g_list_append (*accounts, g_object_ref (account));
1002
1003       if (entities != NULL)
1004         *entities = g_list_append (*entities, g_object_ref (entity));
1005
1006       g_object_unref (account);
1007       g_object_unref (entity);
1008     }
1009   g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free);
1010
1011   view = GTK_TREE_VIEW (window->treeview_what);
1012   model = gtk_tree_view_get_model (view);
1013   selection = gtk_tree_view_get_selection (view);
1014
1015   paths = gtk_tree_selection_get_selected_rows (selection, NULL);
1016   for (l = paths; l != NULL; l = l->next)
1017     {
1018       GtkTreePath *path = l->data;
1019       TplEventTypeMask mask;
1020       EventSubtype submask;
1021
1022       gtk_tree_model_get_iter (model, &iter, path);
1023       gtk_tree_model_get (model, &iter,
1024           COL_WHAT_TYPE, &mask,
1025           COL_WHAT_SUBTYPE, &submask,
1026           -1);
1027
1028       ev |= mask;
1029       st |= submask;
1030     }
1031   g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free);
1032
1033   view = GTK_TREE_VIEW (window->treeview_when);
1034   model = gtk_tree_view_get_model (view);
1035   selection = gtk_tree_view_get_selection (view);
1036
1037   if (dates != NULL)
1038     {
1039       *dates = NULL;
1040
1041       paths = gtk_tree_selection_get_selected_rows (selection, NULL);
1042       for (l = paths; l != NULL; l = l->next)
1043         {
1044           GtkTreePath *path = l->data;
1045           GDate *date;
1046
1047           gtk_tree_model_get_iter (model, &iter, path);
1048           gtk_tree_model_get (model, &iter,
1049               COL_WHEN_DATE, &date,
1050               -1);
1051
1052           *dates = g_list_append (*dates, date);
1053         }
1054       g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free);
1055     }
1056
1057   if (event_mask != NULL)
1058     *event_mask = ev;
1059
1060   if (subtype != NULL)
1061     *subtype = st;
1062
1063   return TRUE;
1064 }
1065
1066 static gboolean
1067 model_has_entity (GtkTreeModel *model,
1068     GtkTreePath *path,
1069     GtkTreeIter *iter,
1070     gpointer data)
1071 {
1072   TplLogSearchHit *hit = data;
1073   TplEntity *e;
1074   TpAccount *a;
1075   gboolean ret = FALSE;
1076
1077   gtk_tree_model_get (model, iter,
1078       COL_WHO_TARGET, &e,
1079       COL_WHO_ACCOUNT, &a,
1080       -1);
1081
1082   if (e != NULL && entity_equal (hit->target, e) &&
1083       a != NULL && account_equal (hit->account, a))
1084     {
1085       ret = has_element = TRUE;
1086     }
1087
1088   tp_clear_object (&e);
1089   tp_clear_object (&a);
1090
1091   return ret;
1092 }
1093
1094 static gboolean
1095 model_has_date (GtkTreeModel *model,
1096     GtkTreePath *path,
1097     GtkTreeIter *iter,
1098     gpointer data)
1099 {
1100   GDate *date = data;
1101   GDate *d;
1102
1103   gtk_tree_model_get (model, iter,
1104       COL_WHEN_DATE, &d,
1105       -1);
1106
1107   if (!g_date_compare (date, d))
1108     {
1109       has_element = TRUE;
1110       return TRUE;
1111     }
1112
1113   return FALSE;
1114 }
1115
1116 static void
1117 get_events_for_date (TplActionChain *chain, gpointer user_data);
1118
1119 static void
1120 populate_events_from_search_hits (GList *accounts,
1121     GList *targets,
1122     GList *dates)
1123 {
1124   TplEventTypeMask event_mask;
1125   EventSubtype subtype;
1126   GDate *anytime;
1127   GList *l;
1128   gboolean is_anytime = FALSE;
1129
1130   if (!log_window_get_selected (log_window,
1131       NULL, NULL, NULL, &event_mask, &subtype))
1132     return;
1133
1134   anytime = g_date_new_dmy (2, 1, -1);
1135   if (g_list_find_custom (dates, anytime, (GCompareFunc) g_date_compare))
1136     is_anytime = TRUE;
1137
1138   for (l = log_window->hits; l != NULL; l = l->next)
1139     {
1140       TplLogSearchHit *hit = l->data;
1141       GList *acc, *targ;
1142       gboolean found = FALSE;
1143
1144       /* Protect against invalid data (corrupt or old log files). */
1145       if (hit->account == NULL || hit->target == NULL)
1146         continue;
1147
1148       for (acc = accounts, targ = targets;
1149            acc != NULL && targ != NULL && !found;
1150            acc = acc->next, targ = targ->next)
1151         {
1152           TpAccount *account = acc->data;
1153           TplEntity *target = targ->data;
1154
1155           if (account_equal (hit->account, account) &&
1156               entity_equal (hit->target, target))
1157             found = TRUE;
1158         }
1159
1160         if (!found)
1161           continue;
1162
1163       if (is_anytime ||
1164           g_list_find_custom (dates, hit->date, (GCompareFunc) g_date_compare)
1165               != NULL)
1166         {
1167           Ctx *ctx;
1168
1169           ctx = ctx_new (log_window, hit->account, hit->target, hit->date,
1170               event_mask, subtype, log_window->count);
1171           _tpl_action_chain_append (log_window->chain,
1172               get_events_for_date, ctx);
1173         }
1174     }
1175
1176   _tpl_action_chain_start (log_window->chain);
1177
1178   g_date_free (anytime);
1179 }
1180
1181 static gchar *
1182 format_date_for_display (GDate *date)
1183 {
1184   gchar *text;
1185   GDate *now = NULL;
1186   gint days_elapsed;
1187
1188   /* g_date_strftime sucks */
1189
1190   now = g_date_new ();
1191   g_date_set_time_t (now, time (NULL));
1192
1193   days_elapsed = g_date_days_between (date, now);
1194
1195   if (days_elapsed < 0)
1196     {
1197       text = NULL;
1198     }
1199   else if (days_elapsed == 0)
1200     {
1201       text = g_strdup (_("Today"));
1202     }
1203   else if (days_elapsed == 1)
1204     {
1205       text = g_strdup (_("Yesterday"));
1206     }
1207   else
1208     {
1209       GDateTime *dt;
1210
1211       dt = g_date_time_new_utc (g_date_get_year (date),
1212           g_date_get_month (date), g_date_get_day (date),
1213           0, 0, 0);
1214
1215       if (days_elapsed <= 7)
1216         text = g_date_time_format (dt, "%A");
1217       else
1218         text = g_date_time_format (dt,
1219             C_("A date such as '23 May 2010', "
1220                "%e is the day, %B the month and %Y the year",
1221                "%e %B %Y"));
1222
1223       g_date_time_unref (dt);
1224     }
1225
1226   g_date_free (now);
1227
1228   return text;
1229 }
1230
1231 static void
1232 populate_dates_from_search_hits (GList *accounts,
1233     GList *targets)
1234 {
1235   GList *l;
1236   GtkTreeView *view;
1237   GtkTreeModel *model;
1238   GtkListStore *store;
1239   GtkTreeSelection *selection;
1240   GtkTreeIter iter;
1241
1242   if (log_window == NULL)
1243     return;
1244
1245   view = GTK_TREE_VIEW (log_window->treeview_when);
1246   model = gtk_tree_view_get_model (view);
1247   store = GTK_LIST_STORE (model);
1248   selection = gtk_tree_view_get_selection (view);
1249
1250   for (l = log_window->hits; l != NULL; l = l->next)
1251     {
1252       TplLogSearchHit *hit = l->data;
1253       GList *acc, *targ;
1254       gboolean found = FALSE;
1255
1256       /* Protect against invalid data (corrupt or old log files). */
1257       if (hit->account == NULL || hit->target == NULL)
1258         continue;
1259
1260       for (acc = accounts, targ = targets;
1261            acc != NULL && targ != NULL && !found;
1262            acc = acc->next, targ = targ->next)
1263         {
1264           TpAccount *account = acc->data;
1265           TplEntity *target = targ->data;
1266
1267           if (account_equal (hit->account, account) &&
1268               entity_equal (hit->target, target))
1269             found = TRUE;
1270         }
1271
1272         if (!found)
1273           continue;
1274
1275       /* Add the date if it's not already there */
1276       has_element = FALSE;
1277       gtk_tree_model_foreach (model, model_has_date, hit->date);
1278       if (!has_element)
1279         {
1280           gchar *text = format_date_for_display (hit->date);
1281
1282           gtk_list_store_append (store, &iter);
1283           gtk_list_store_set (store, &iter,
1284               COL_WHEN_DATE, hit->date,
1285               COL_WHEN_TEXT, text,
1286               COL_WHEN_ICON, CALENDAR_ICON,
1287               -1);
1288         }
1289     }
1290
1291   if (gtk_tree_model_get_iter_first (model, &iter))
1292     {
1293       gtk_list_store_prepend (store, &iter);
1294       gtk_list_store_set (store, &iter,
1295           COL_WHEN_DATE, g_date_new_dmy (1, 1, -1),
1296           COL_WHEN_TEXT, "separator",
1297           -1);
1298
1299       gtk_list_store_prepend (store, &iter);
1300       gtk_list_store_set (store, &iter,
1301           COL_WHEN_DATE, g_date_new_dmy (2, 1, -1),
1302           COL_WHEN_TEXT, _("Anytime"),
1303           -1);
1304
1305       if (gtk_tree_model_iter_nth_child (model, &iter, NULL, 2))
1306         gtk_tree_selection_select_iter (selection, &iter);
1307     }
1308 }
1309
1310 static void
1311 populate_entities_from_search_hits (void)
1312 {
1313   EmpathyAccountChooser *account_chooser;
1314   TpAccount *account;
1315   GtkTreeView *view;
1316   GtkTreeModel *model;
1317   GtkTreeIter iter;
1318   GtkListStore *store;
1319   GList *l;
1320
1321   view = GTK_TREE_VIEW (log_window->treeview_who);
1322   model = gtk_tree_view_get_model (view);
1323   store = GTK_LIST_STORE (model);
1324
1325   gtk_list_store_clear (store);
1326
1327   account_chooser = EMPATHY_ACCOUNT_CHOOSER (log_window->account_chooser);
1328   account = empathy_account_chooser_get_account (account_chooser);
1329
1330   for (l = log_window->hits; l; l = l->next)
1331     {
1332       TplLogSearchHit *hit = l->data;
1333
1334       /* Protect against invalid data (corrupt or old log files). */
1335       if (hit->account == NULL || hit->target == NULL)
1336         continue;
1337
1338       /* Filter based on the selected account */
1339       if (account != NULL && !account_equal (account, hit->account))
1340         continue;
1341
1342       /* Add the entity if it's not already there */
1343       has_element = FALSE;
1344       gtk_tree_model_foreach (model, model_has_entity, hit);
1345       if (!has_element)
1346         {
1347           TplEntityType type = tpl_entity_get_entity_type (hit->target);
1348           gboolean room = type == TPL_ENTITY_ROOM;
1349
1350           gtk_list_store_append (store, &iter);
1351           gtk_list_store_set (store, &iter,
1352               COL_WHO_TYPE, COL_TYPE_NORMAL,
1353               COL_WHO_ICON, room ? EMPATHY_IMAGE_GROUP_MESSAGE
1354                                  : EMPATHY_IMAGE_AVATAR_DEFAULT,
1355               COL_WHO_NAME, tpl_entity_get_alias (hit->target),
1356               COL_WHO_ACCOUNT, hit->account,
1357               COL_WHO_TARGET, hit->target,
1358               -1);
1359         }
1360     }
1361
1362   if (gtk_tree_model_get_iter_first (model, &iter))
1363     {
1364       gtk_list_store_prepend (store, &iter);
1365       gtk_list_store_set (store, &iter,
1366           COL_WHO_TYPE, COL_TYPE_SEPARATOR,
1367           COL_WHO_NAME, "separator",
1368           -1);
1369
1370       gtk_list_store_prepend (store, &iter);
1371       gtk_list_store_set (store, &iter,
1372           COL_WHO_TYPE, COL_TYPE_ANY,
1373           COL_WHO_NAME, _("Anyone"),
1374           -1);
1375     }
1376
1377   /* FIXME: select old entity if still available */
1378 }
1379
1380 static void
1381 log_manager_searched_new_cb (GObject *manager,
1382     GAsyncResult *result,
1383     gpointer user_data)
1384 {
1385   GList *hits;
1386   GtkTreeView *view;
1387   GtkTreeSelection *selection;
1388   GError *error = NULL;
1389
1390   if (log_window == NULL)
1391     return;
1392
1393   if (!tpl_log_manager_search_finish (TPL_LOG_MANAGER (manager),
1394       result, &hits, &error))
1395     {
1396       DEBUG ("%s. Aborting", error->message);
1397       g_error_free (error);
1398       return;
1399     }
1400
1401   tp_clear_pointer (&log_window->hits, tpl_log_manager_search_free);
1402   log_window->hits = hits;
1403
1404   populate_entities_from_search_hits ();
1405
1406   view = GTK_TREE_VIEW (log_window->treeview_when);
1407   selection = gtk_tree_view_get_selection (view);
1408
1409   g_signal_handlers_unblock_by_func (selection,
1410       log_window_when_changed_cb,
1411       log_window);
1412 }
1413
1414 static void
1415 log_window_find_populate (EmpathyLogWindow *window,
1416     const gchar *search_criteria)
1417 {
1418   GtkTreeView *view;
1419   GtkTreeModel *model;
1420   GtkTreeSelection *selection;
1421   GtkListStore *store;
1422
1423   gtk_tree_store_clear (window->store_events);
1424
1425   view = GTK_TREE_VIEW (window->treeview_who);
1426   model = gtk_tree_view_get_model (view);
1427   store = GTK_LIST_STORE (model);
1428
1429   gtk_list_store_clear (store);
1430
1431   view = GTK_TREE_VIEW (window->treeview_when);
1432   model = gtk_tree_view_get_model (view);
1433   store = GTK_LIST_STORE (model);
1434   selection = gtk_tree_view_get_selection (view);
1435
1436   gtk_list_store_clear (store);
1437
1438   if (EMP_STR_EMPTY (search_criteria))
1439     {
1440       tp_clear_pointer (&window->hits, tpl_log_manager_search_free);
1441       log_window_who_populate (window);
1442       return;
1443     }
1444
1445   g_signal_handlers_block_by_func (selection,
1446       log_window_when_changed_cb,
1447       window);
1448
1449   tpl_log_manager_search_async (window->log_manager,
1450       search_criteria, TPL_EVENT_MASK_ANY,
1451       log_manager_searched_new_cb, NULL);
1452 }
1453
1454 static gboolean
1455 start_find_search (EmpathyLogWindow *window)
1456 {
1457   const gchar *str;
1458
1459   str = gtk_entry_get_text (GTK_ENTRY (window->search_entry));
1460
1461   /* Don't find the same crap again */
1462   if (window->last_find && !tp_strdiff (window->last_find, str))
1463     return FALSE;
1464
1465   g_free (window->last_find);
1466   window->last_find = g_strdup (str);
1467
1468   log_window_find_populate (window, str);
1469
1470   return FALSE;
1471 }
1472
1473 static void
1474 log_window_search_entry_changed_cb (GtkWidget *entry,
1475     EmpathyLogWindow *window)
1476 {
1477   if (window->source != 0)
1478     g_source_remove (window->source);
1479   window->source = g_timeout_add (500, (GSourceFunc) start_find_search,
1480       window);
1481 }
1482
1483 static void
1484 log_window_search_entry_activate_cb (GtkWidget *entry,
1485     EmpathyLogWindow *self)
1486 {
1487   start_find_search (self);
1488 }
1489
1490 static void
1491 log_window_search_entry_icon_pressed_cb (GtkEntry *entry,
1492     GtkEntryIconPosition icon_pos,
1493     GdkEvent *event,
1494     gpointer user_data)
1495 {
1496   if (icon_pos != GTK_ENTRY_ICON_SECONDARY)
1497     return;
1498
1499   gtk_entry_buffer_set_text (gtk_entry_get_buffer (entry),
1500     "", -1);
1501 }
1502
1503 static void
1504 log_window_update_buttons_sensitivity (EmpathyLogWindow *window,
1505     GtkTreeModel *model,
1506     GtkTreeSelection *selection)
1507 {
1508   EmpathyContact *contact;
1509   EmpathyCapabilities capabilities;
1510   TpAccount *account;
1511   TplEntity *target;
1512   GtkTreeIter iter;
1513   GList *paths;
1514   GtkTreePath *path;
1515   gboolean profile, chat, call, video;
1516
1517   profile = chat = call = video = FALSE;
1518
1519   if (!gtk_tree_model_get_iter_first (model, &iter))
1520     goto out;
1521
1522   if (gtk_tree_selection_count_selected_rows (selection) != 1)
1523     goto out;
1524
1525   if (gtk_tree_selection_iter_is_selected (selection, &iter))
1526     goto out;
1527
1528   paths = gtk_tree_selection_get_selected_rows (selection, &model);
1529   g_return_if_fail (paths != NULL);
1530
1531   path = paths->data;
1532   gtk_tree_model_get_iter (model, &iter, path);
1533   gtk_tree_model_get (model, &iter,
1534       COL_WHO_ACCOUNT, &account,
1535       COL_WHO_TARGET, &target,
1536       -1);
1537
1538   g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free);
1539
1540   contact = empathy_contact_from_tpl_contact (account, target);
1541
1542   g_object_unref (account);
1543   g_object_unref (target);
1544
1545   capabilities = empathy_contact_get_capabilities (contact);
1546
1547   profile = chat = TRUE;
1548   call = capabilities & EMPATHY_CAPABILITIES_AUDIO;
1549   video = capabilities & EMPATHY_CAPABILITIES_VIDEO;
1550
1551  out:
1552   gtk_widget_set_sensitive (window->button_profile, profile);
1553   gtk_widget_set_sensitive (window->button_chat, chat);
1554   gtk_widget_set_sensitive (window->button_call, call);
1555   gtk_widget_set_sensitive (window->button_video, video);
1556 }
1557
1558 static void
1559 log_window_who_changed_cb (GtkTreeSelection *selection,
1560     EmpathyLogWindow  *window)
1561 {
1562   GtkTreeView *view;
1563   GtkTreeModel *model;
1564   GtkTreeIter iter;
1565
1566   DEBUG ("log_window_who_changed_cb");
1567
1568   view = gtk_tree_selection_get_tree_view (selection);
1569   model = gtk_tree_view_get_model (view);
1570
1571   if (gtk_tree_model_get_iter_first (model, &iter))
1572     {
1573       /* If 'Anyone' is selected, everything else should be deselected */
1574       if (gtk_tree_selection_iter_is_selected (selection, &iter))
1575         {
1576           g_signal_handlers_block_by_func (selection,
1577               log_window_who_changed_cb,
1578               window);
1579
1580           gtk_tree_selection_unselect_all (selection);
1581           gtk_tree_selection_select_iter (selection, &iter);
1582
1583           g_signal_handlers_unblock_by_func (selection,
1584               log_window_who_changed_cb,
1585               window);
1586         }
1587     }
1588
1589   log_window_update_buttons_sensitivity (window, model, selection);
1590
1591   /* The contact changed, so the dates need to be updated */
1592   log_window_chats_get_messages (window, TRUE);
1593 }
1594
1595 static void
1596 log_manager_got_entities_cb (GObject *manager,
1597     GAsyncResult *result,
1598     gpointer user_data)
1599 {
1600   Ctx                   *ctx = user_data;
1601   GList                 *entities;
1602   GList                 *l;
1603   GtkTreeView           *view;
1604   GtkTreeModel          *model;
1605   GtkTreeSelection      *selection;
1606   GtkListStore          *store;
1607   GtkTreeIter            iter;
1608   GError                *error = NULL;
1609   gboolean               select_account = FALSE;
1610
1611   if (log_window == NULL)
1612     goto out;
1613
1614   if (log_window->count != ctx->count)
1615     goto out;
1616
1617   if (!tpl_log_manager_get_entities_finish (TPL_LOG_MANAGER (manager),
1618       result, &entities, &error))
1619     {
1620       DEBUG ("%s. Aborting", error->message);
1621       g_error_free (error);
1622       goto out;
1623     }
1624
1625   view = GTK_TREE_VIEW (ctx->window->treeview_who);
1626   model = gtk_tree_view_get_model (view);
1627   selection = gtk_tree_view_get_selection (view);
1628   store = GTK_LIST_STORE (model);
1629
1630   /* Block signals to stop the logs being retrieved prematurely  */
1631   g_signal_handlers_block_by_func (selection,
1632       log_window_who_changed_cb, ctx->window);
1633
1634   for (l = entities; l; l = l->next)
1635     {
1636       TplEntity *entity = TPL_ENTITY (l->data);
1637       TplEntityType type = tpl_entity_get_entity_type (entity);
1638       gboolean room = type == TPL_ENTITY_ROOM;
1639
1640       gtk_list_store_append (store, &iter);
1641       gtk_list_store_set (store, &iter,
1642           COL_WHO_TYPE, COL_TYPE_NORMAL,
1643           COL_WHO_ICON, room ? EMPATHY_IMAGE_GROUP_MESSAGE
1644                              : EMPATHY_IMAGE_AVATAR_DEFAULT,
1645           COL_WHO_NAME, tpl_entity_get_alias (entity),
1646           COL_WHO_ACCOUNT, ctx->account,
1647           COL_WHO_TARGET, entity,
1648           -1);
1649
1650       if (ctx->window->selected_account != NULL &&
1651           !tp_strdiff (tp_proxy_get_object_path (ctx->account),
1652           tp_proxy_get_object_path (ctx->window->selected_account)))
1653         select_account = TRUE;
1654     }
1655   g_list_free_full (entities, g_object_unref);
1656
1657   if (gtk_tree_model_get_iter_first (model, &iter))
1658     {
1659       gint type;
1660
1661       gtk_tree_model_get (model, &iter,
1662           COL_WHO_TYPE, &type,
1663           -1);
1664
1665       if (type != COL_TYPE_ANY)
1666         {
1667           gtk_list_store_prepend (store, &iter);
1668           gtk_list_store_set (store, &iter,
1669               COL_WHO_TYPE, COL_TYPE_SEPARATOR,
1670               COL_WHO_NAME, "separator",
1671               -1);
1672
1673           gtk_list_store_prepend (store, &iter);
1674           gtk_list_store_set (store, &iter,
1675               COL_WHO_TYPE, COL_TYPE_ANY,
1676               COL_WHO_NAME, _("Anyone"),
1677               -1);
1678         }
1679     }
1680
1681   /* Unblock signals */
1682   g_signal_handlers_unblock_by_func (selection,
1683       log_window_who_changed_cb,
1684       ctx->window);
1685
1686   /* We display the selected account if we populate the model with chats from
1687    * this account. */
1688   if (select_account)
1689     log_window_chats_set_selected (ctx->window);
1690
1691 out:
1692   _tpl_action_chain_continue (log_window->chain);
1693   ctx_free (ctx);
1694 }
1695
1696 static void
1697 get_entities_for_account (TplActionChain *chain, gpointer user_data)
1698 {
1699   Ctx *ctx = user_data;
1700
1701   tpl_log_manager_get_entities_async (ctx->window->log_manager, ctx->account,
1702       log_manager_got_entities_cb, ctx);
1703 }
1704
1705 static void
1706 select_first_entity (TplActionChain *chain, gpointer user_data)
1707 {
1708   GtkTreeView *view;
1709   GtkTreeModel *model;
1710   GtkTreeSelection *selection;
1711   GtkTreeIter iter;
1712
1713   view = GTK_TREE_VIEW (log_window->treeview_who);
1714   model = gtk_tree_view_get_model (view);
1715   selection = gtk_tree_view_get_selection (view);
1716
1717   if (gtk_tree_model_get_iter_first (model, &iter))
1718     gtk_tree_selection_select_iter (selection, &iter);
1719
1720   _tpl_action_chain_continue (log_window->chain);
1721 }
1722
1723 static void
1724 log_window_who_populate (EmpathyLogWindow *window)
1725 {
1726   EmpathyAccountChooser *account_chooser;
1727   TpAccount *account;
1728   gboolean all_accounts;
1729   GtkTreeView *view;
1730   GtkTreeModel *model;
1731   GtkTreeSelection *selection;
1732   GtkListStore *store;
1733   Ctx *ctx;
1734
1735   if (window->hits != NULL)
1736     {
1737       populate_entities_from_search_hits ();
1738       return;
1739     }
1740
1741   account_chooser = EMPATHY_ACCOUNT_CHOOSER (window->account_chooser);
1742   account = empathy_account_chooser_dup_account (account_chooser);
1743   all_accounts = empathy_account_chooser_has_all_selected (account_chooser);
1744
1745   view = GTK_TREE_VIEW (window->treeview_who);
1746   model = gtk_tree_view_get_model (view);
1747   selection = gtk_tree_view_get_selection (view);
1748   store = GTK_LIST_STORE (model);
1749
1750   /* Block signals to stop the logs being retrieved prematurely  */
1751   g_signal_handlers_block_by_func (selection,
1752       log_window_who_changed_cb,
1753       window);
1754
1755   gtk_list_store_clear (store);
1756
1757   /* Unblock signals */
1758   g_signal_handlers_unblock_by_func (selection,
1759       log_window_who_changed_cb,
1760       window);
1761
1762   _tpl_action_chain_clear (window->chain);
1763   window->count++;
1764
1765   if (!all_accounts && account == NULL)
1766     {
1767       return;
1768     }
1769   else if (!all_accounts)
1770     {
1771       ctx = ctx_new (window, account, NULL, NULL, 0, 0, window->count);
1772       _tpl_action_chain_append (window->chain, get_entities_for_account, ctx);
1773     }
1774   else
1775     {
1776       TpAccountManager *manager;
1777       GList *accounts, *l;
1778
1779       manager = empathy_account_chooser_get_account_manager (account_chooser);
1780       accounts = tp_account_manager_get_valid_accounts (manager);
1781
1782       for (l = accounts; l != NULL; l = l->next)
1783         {
1784           account = l->data;
1785
1786           ctx = ctx_new (window, account, NULL, NULL, 0, 0, window->count);
1787           _tpl_action_chain_append (window->chain,
1788               get_entities_for_account, ctx);
1789         }
1790
1791       g_list_free (accounts);
1792     }
1793   _tpl_action_chain_append (window->chain, select_first_entity, NULL);
1794   _tpl_action_chain_start (window->chain);
1795 }
1796
1797 static gint
1798 sort_by_name (GtkTreeModel *model,
1799     GtkTreeIter *a,
1800     GtkTreeIter *b,
1801     gpointer user_data)
1802 {
1803   gchar *name1, *name2;
1804   gint type1, type2;
1805   gint ret;
1806
1807   gtk_tree_model_get (model, a,
1808       COL_WHO_TYPE, &type1,
1809       COL_WHO_NAME, &name1,
1810       -1);
1811
1812   gtk_tree_model_get (model, b,
1813       COL_WHO_TYPE, &type2,
1814       COL_WHO_NAME, &name2,
1815       -1);
1816
1817   if (type1 == COL_TYPE_ANY)
1818     ret = -1;
1819   else if (type2 == COL_TYPE_ANY)
1820     ret = 1;
1821   else if (type1 == COL_TYPE_SEPARATOR)
1822     ret = -1;
1823   else if (type2 == COL_TYPE_SEPARATOR)
1824     ret = 1;
1825   else
1826     ret = g_strcmp0 (name1, name2);
1827
1828   g_free (name1);
1829   g_free (name2);
1830
1831   return ret;
1832 }
1833
1834 static gboolean
1835 who_row_is_separator (GtkTreeModel *model,
1836     GtkTreeIter *iter,
1837     gpointer data)
1838 {
1839   gint type;
1840
1841   gtk_tree_model_get (model, iter,
1842       COL_WHO_TYPE, &type,
1843       -1);
1844
1845   return (type == COL_TYPE_SEPARATOR);
1846 }
1847
1848 static void
1849 log_window_events_setup (EmpathyLogWindow *window)
1850 {
1851   GtkTreeView       *view;
1852   GtkTreeModel      *model;
1853   GtkTreeSelection  *selection;
1854   GtkTreeSortable   *sortable;
1855   GtkTreeViewColumn *column;
1856   GtkTreeStore      *store;
1857   GtkCellRenderer   *cell;
1858
1859   view = GTK_TREE_VIEW (window->treeview_events);
1860   selection = gtk_tree_view_get_selection (view);
1861
1862   /* new store */
1863   window->store_events = store = gtk_tree_store_new (COL_EVENTS_COUNT,
1864       G_TYPE_INT,           /* type */
1865       G_TYPE_INT64,         /* timestamp */
1866       G_TYPE_STRING,        /* stringified date */
1867       G_TYPE_STRING,        /* icon */
1868       G_TYPE_STRING,        /* name */
1869       TP_TYPE_ACCOUNT,      /* account */
1870       TPL_TYPE_ENTITY,      /* target */
1871       TPL_TYPE_EVENT);      /* event */
1872
1873   model = GTK_TREE_MODEL (store);
1874   sortable = GTK_TREE_SORTABLE (store);
1875
1876   gtk_tree_view_set_model (view, model);
1877
1878   /* new column */
1879   column = gtk_tree_view_column_new ();
1880
1881   cell = gtk_cell_renderer_pixbuf_new ();
1882   gtk_tree_view_column_pack_start (column, cell, FALSE);
1883   gtk_tree_view_column_add_attribute (column, cell,
1884       "icon-name", COL_EVENTS_ICON);
1885
1886   cell = gtk_cell_renderer_text_new ();
1887   gtk_tree_view_column_pack_start (column, cell, TRUE);
1888   gtk_tree_view_column_add_attribute (column, cell,
1889       "text", COL_EVENTS_TEXT);
1890
1891   cell = gtk_cell_renderer_text_new ();
1892   g_object_set (cell, "xalign", 1.0, NULL);
1893   gtk_tree_view_column_pack_end (column, cell, FALSE);
1894   gtk_tree_view_column_add_attribute (column, cell,
1895       "text", COL_EVENTS_PRETTY_DATE);
1896
1897   gtk_tree_view_append_column (view, column);
1898
1899   /* set up treeview properties */
1900   gtk_tree_selection_set_mode (selection, GTK_SELECTION_NONE);
1901   gtk_tree_view_set_headers_visible (view, FALSE);
1902
1903   gtk_tree_sortable_set_sort_column_id (sortable,
1904       COL_EVENTS_TS,
1905       GTK_SORT_ASCENDING);
1906
1907   g_object_unref (store);
1908 }
1909
1910 static void
1911 log_window_who_setup (EmpathyLogWindow *window)
1912 {
1913   GtkTreeView       *view;
1914   GtkTreeModel      *model;
1915   GtkTreeSelection  *selection;
1916   GtkTreeSortable   *sortable;
1917   GtkTreeViewColumn *column;
1918   GtkListStore      *store;
1919   GtkCellRenderer   *cell;
1920
1921   view = GTK_TREE_VIEW (window->treeview_who);
1922   selection = gtk_tree_view_get_selection (view);
1923
1924   /* new store */
1925   store = gtk_list_store_new (COL_WHO_COUNT,
1926       G_TYPE_INT,           /* type */
1927       G_TYPE_STRING,        /* icon */
1928       G_TYPE_STRING,        /* name */
1929       TP_TYPE_ACCOUNT,      /* account */
1930       TPL_TYPE_ENTITY);     /* target */
1931
1932   model = GTK_TREE_MODEL (store);
1933   sortable = GTK_TREE_SORTABLE (store);
1934
1935   gtk_tree_view_set_model (view, model);
1936
1937   /* new column */
1938   column = gtk_tree_view_column_new ();
1939   gtk_tree_view_column_set_title (column, _("Who"));
1940
1941   cell = gtk_cell_renderer_pixbuf_new ();
1942   gtk_tree_view_column_pack_start (column, cell, FALSE);
1943   gtk_tree_view_column_add_attribute (column, cell,
1944       "icon-name",
1945       COL_WHO_ICON);
1946
1947   cell = gtk_cell_renderer_text_new ();
1948   g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1949   gtk_tree_view_column_pack_start (column, cell, TRUE);
1950   gtk_tree_view_column_add_attribute (column, cell,
1951       "text",
1952       COL_WHO_NAME);
1953
1954   gtk_tree_view_append_column (view, column);
1955
1956   /* set up treeview properties */
1957   gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
1958   gtk_tree_view_set_row_separator_func (view, who_row_is_separator,
1959       NULL, NULL);
1960
1961   gtk_tree_sortable_set_sort_column_id (sortable,
1962       COL_WHO_NAME,
1963       GTK_SORT_ASCENDING);
1964   gtk_tree_sortable_set_sort_func (sortable,
1965       COL_WHO_NAME, sort_by_name,
1966       NULL, NULL);
1967
1968   /* set up signals */
1969   g_signal_connect (selection, "changed",
1970       G_CALLBACK (log_window_who_changed_cb), window);
1971
1972   g_object_unref (store);
1973 }
1974
1975 static void
1976 log_window_chats_accounts_changed_cb (GtkWidget       *combobox,
1977     EmpathyLogWindow *window)
1978 {
1979   /* Clear all current messages shown in the textview */
1980   gtk_tree_store_clear (window->store_events);
1981
1982   log_window_who_populate (window);
1983 }
1984
1985 static void
1986 log_window_chats_set_selected (EmpathyLogWindow *window)
1987 {
1988   GtkTreeView          *view;
1989   GtkTreeModel         *model;
1990   GtkTreeSelection     *selection;
1991   GtkTreeIter           iter;
1992   GtkTreePath          *path;
1993   gboolean              next;
1994
1995   view = GTK_TREE_VIEW (window->treeview_who);
1996   model = gtk_tree_view_get_model (view);
1997   selection = gtk_tree_view_get_selection (view);
1998
1999   for (next = gtk_tree_model_get_iter_first (model, &iter);
2000        next;
2001        next = gtk_tree_model_iter_next (model, &iter))
2002     {
2003       TpAccount   *this_account;
2004       TplEntity   *this_target;
2005       const gchar *this_chat_id;
2006       gboolean     this_is_chatroom;
2007       gint         this_type;
2008
2009       gtk_tree_model_get (model, &iter,
2010           COL_WHO_TYPE, &this_type,
2011           COL_WHO_ACCOUNT, &this_account,
2012           COL_WHO_TARGET, &this_target,
2013           -1);
2014
2015       if (this_type != COL_TYPE_NORMAL)
2016         continue;
2017
2018       this_chat_id = tpl_entity_get_identifier (this_target);
2019       this_is_chatroom = tpl_entity_get_entity_type (this_target)
2020           == TPL_ENTITY_ROOM;
2021
2022       if (this_account == window->selected_account &&
2023           !tp_strdiff (this_chat_id, window->selected_chat_id) &&
2024           this_is_chatroom == window->selected_is_chatroom)
2025         {
2026           gtk_tree_selection_select_iter (selection, &iter);
2027           path = gtk_tree_model_get_path (model, &iter);
2028           gtk_tree_view_scroll_to_cell (view, path, NULL, TRUE, 0.5, 0.0);
2029           gtk_tree_path_free (path);
2030           g_object_unref (this_account);
2031           g_object_unref (this_target);
2032           break;
2033         }
2034
2035       g_object_unref (this_account);
2036       g_object_unref (this_target);
2037     }
2038
2039   tp_clear_object (&window->selected_account);
2040   tp_clear_pointer (&window->selected_chat_id, g_free);
2041 }
2042
2043 static gint
2044 sort_by_date (GtkTreeModel *model,
2045     GtkTreeIter *a,
2046     GtkTreeIter *b,
2047     gpointer user_data)
2048 {
2049   GDate *date1, *date2;
2050
2051   gtk_tree_model_get (model, a,
2052       COL_WHEN_DATE, &date1,
2053       -1);
2054
2055   gtk_tree_model_get (model, b,
2056       COL_WHEN_DATE, &date2,
2057       -1);
2058
2059   return g_date_compare (date1, date2);
2060 }
2061
2062 static gboolean
2063 when_row_is_separator (GtkTreeModel *model,
2064     GtkTreeIter *iter,
2065     gpointer data)
2066 {
2067   gchar *when;
2068   gboolean ret;
2069
2070   gtk_tree_model_get (model, iter,
2071       COL_WHEN_TEXT, &when,
2072       -1);
2073
2074   ret = g_str_equal (when, "separator");
2075   g_free (when);
2076   return ret;
2077 }
2078
2079 static void
2080 log_window_when_changed_cb (GtkTreeSelection *selection,
2081     EmpathyLogWindow *window)
2082 {
2083   GtkTreeView *view;
2084   GtkTreeModel *model;
2085   GtkTreeIter iter;
2086
2087   DEBUG ("log_window_when_changed_cb");
2088
2089   view = gtk_tree_selection_get_tree_view (selection);
2090   model = gtk_tree_view_get_model (view);
2091
2092   /* If 'Anytime' is selected, everything else should be deselected */
2093   if (gtk_tree_model_get_iter_first (model, &iter))
2094     {
2095       if (gtk_tree_selection_iter_is_selected (selection, &iter))
2096         {
2097           g_signal_handlers_block_by_func (selection,
2098               log_window_when_changed_cb,
2099               window);
2100
2101           gtk_tree_selection_unselect_all (selection);
2102           gtk_tree_selection_select_iter (selection, &iter);
2103
2104           g_signal_handlers_unblock_by_func (selection,
2105               log_window_when_changed_cb,
2106               window);
2107         }
2108     }
2109
2110   log_window_chats_get_messages (window, FALSE);
2111 }
2112
2113 static void
2114 log_window_when_setup (EmpathyLogWindow *window)
2115 {
2116   GtkTreeView       *view;
2117   GtkTreeModel      *model;
2118   GtkTreeSelection  *selection;
2119   GtkTreeSortable   *sortable;
2120   GtkTreeViewColumn *column;
2121   GtkListStore      *store;
2122   GtkCellRenderer   *cell;
2123
2124   view = GTK_TREE_VIEW (window->treeview_when);
2125   selection = gtk_tree_view_get_selection (view);
2126
2127   /* new store */
2128   store = gtk_list_store_new (COL_WHEN_COUNT,
2129       G_TYPE_DATE,        /* date */
2130       G_TYPE_STRING,      /* stringified date */
2131       G_TYPE_STRING);     /* icon */
2132
2133   model = GTK_TREE_MODEL (store);
2134   sortable = GTK_TREE_SORTABLE (store);
2135
2136   gtk_tree_view_set_model (view, model);
2137
2138   /* new column */
2139   column = gtk_tree_view_column_new ();
2140   gtk_tree_view_column_set_title (column, _("When"));
2141
2142   cell = gtk_cell_renderer_pixbuf_new ();
2143   gtk_tree_view_column_pack_start (column, cell, FALSE);
2144   gtk_tree_view_column_add_attribute (column, cell,
2145       "icon-name", COL_WHEN_ICON);
2146
2147   cell = gtk_cell_renderer_text_new ();
2148   g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
2149   gtk_tree_view_column_pack_start (column, cell, TRUE);
2150   gtk_tree_view_column_add_attribute (column, cell,
2151       "text",
2152       COL_WHEN_TEXT);
2153
2154   gtk_tree_view_append_column (view, column);
2155
2156   /* set up treeview properties */
2157   gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
2158   gtk_tree_view_set_row_separator_func (view, when_row_is_separator,
2159       NULL, NULL);
2160   gtk_tree_sortable_set_sort_column_id (sortable,
2161       COL_WHEN_DATE,
2162       GTK_SORT_DESCENDING);
2163   gtk_tree_sortable_set_sort_func (sortable,
2164       COL_WHEN_DATE, sort_by_date,
2165       NULL, NULL);
2166
2167   /* set up signals */
2168   g_signal_connect (selection, "changed",
2169       G_CALLBACK (log_window_when_changed_cb),
2170       window);
2171
2172   g_object_unref (store);
2173 }
2174
2175 static gboolean
2176 what_row_is_separator (GtkTreeModel *model,
2177     GtkTreeIter *iter,
2178     gpointer data)
2179 {
2180   gint type;
2181
2182   gtk_tree_model_get (model, iter,
2183       COL_WHAT_TYPE, &type,
2184       -1);
2185
2186   return (type == WHAT_TYPE_SEPARATOR);
2187 }
2188
2189 static void
2190 log_window_what_changed_cb (GtkTreeSelection *selection,
2191     EmpathyLogWindow *window)
2192 {
2193   GtkTreeView *view;
2194   GtkTreeModel *model;
2195   GtkTreeIter iter;
2196
2197   DEBUG ("log_window_what_changed_cb");
2198
2199   view = gtk_tree_selection_get_tree_view (selection);
2200   model = gtk_tree_view_get_model (view);
2201
2202   /* If 'Anything' is selected, everything else should be deselected */
2203   if (gtk_tree_model_get_iter_first (model, &iter))
2204     {
2205       if (gtk_tree_selection_iter_is_selected (selection, &iter))
2206         {
2207           g_signal_handlers_block_by_func (selection,
2208               log_window_what_changed_cb,
2209               window);
2210
2211           gtk_tree_selection_unselect_all (selection);
2212           gtk_tree_selection_select_iter (selection, &iter);
2213
2214           g_signal_handlers_unblock_by_func (selection,
2215               log_window_what_changed_cb,
2216               window);
2217         }
2218     }
2219
2220   /* The dates need to be updated if we're not searching */
2221   log_window_chats_get_messages (window, window->hits == NULL);
2222 }
2223
2224 static gboolean
2225 log_window_what_collapse_row_cb (GtkTreeView *tree_view,
2226     GtkTreeIter *iter,
2227     GtkTreePath *path,
2228     gpointer user_data)
2229 {
2230   /* Reject collapsing */
2231   return TRUE;
2232 }
2233
2234 struct event
2235 {
2236   gint type;
2237   EventSubtype subtype;
2238   const gchar *icon;
2239   const gchar *text;
2240 };
2241
2242 static void
2243 log_window_what_setup (EmpathyLogWindow *window)
2244 {
2245   GtkTreeView       *view;
2246   GtkTreeModel      *model;
2247   GtkTreeSelection  *selection;
2248   GtkTreeSortable   *sortable;
2249   GtkTreeViewColumn *column;
2250   GtkTreeIter        iter, parent;
2251   GtkTreeStore      *store;
2252   GtkCellRenderer   *cell;
2253   guint i;
2254   struct event events [] = {
2255     { TPL_EVENT_MASK_ANY, 0, NULL, _("Anything") },
2256     { WHAT_TYPE_SEPARATOR, 0, NULL, "separator" },
2257     { TPL_EVENT_MASK_TEXT, 0, "stock_text_justify", _("Text chats") },
2258     { TPL_EVENT_MASK_CALL, EVENT_CALL_ALL, "call-start", _("Calls") }
2259   };
2260   struct event call_events [] = {
2261     { TPL_EVENT_MASK_CALL, EVENT_CALL_INCOMING, "call-start", _("Incoming calls") },
2262     { TPL_EVENT_MASK_CALL, EVENT_CALL_OUTGOING, "call-start", _("Outgoing calls") },
2263     { TPL_EVENT_MASK_CALL, EVENT_CALL_MISSED, "call-stop", _("Missed calls") }
2264   };
2265
2266   view = GTK_TREE_VIEW (window->treeview_what);
2267   selection = gtk_tree_view_get_selection (view);
2268
2269   /* new store */
2270   store = gtk_tree_store_new (COL_WHAT_COUNT,
2271       G_TYPE_INT,         /* history type */
2272       G_TYPE_INT,         /* history subtype */
2273       G_TYPE_STRING,      /* stringified history type */
2274       G_TYPE_STRING,      /* icon */
2275       G_TYPE_BOOLEAN);    /* expander (hidden) */
2276
2277   model = GTK_TREE_MODEL (store);
2278   sortable = GTK_TREE_SORTABLE (store);
2279
2280   gtk_tree_view_set_model (view, model);
2281
2282   /* new column */
2283   column = gtk_tree_view_column_new ();
2284   gtk_tree_view_column_set_title (column, _("What"));
2285
2286   cell = gtk_cell_renderer_pixbuf_new ();
2287   gtk_tree_view_column_pack_start (column, cell, FALSE);
2288   gtk_tree_view_column_add_attribute (column, cell,
2289       "icon-name", COL_WHAT_ICON);
2290
2291   cell = gtk_cell_renderer_text_new ();
2292   g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
2293   gtk_tree_view_column_pack_start (column, cell, TRUE);
2294   gtk_tree_view_column_add_attribute (column, cell,
2295       "text", COL_WHAT_TEXT);
2296
2297   gtk_tree_view_append_column (view, column);
2298
2299   /* set up treeview properties */
2300   gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
2301   gtk_tree_view_set_show_expanders (view, FALSE);
2302   gtk_tree_view_set_level_indentation (view, 12);
2303   gtk_tree_view_expand_all (view);
2304   gtk_tree_view_set_row_separator_func (view, what_row_is_separator,
2305       NULL, NULL);
2306
2307   /* populate */
2308   for (i = 0; i < G_N_ELEMENTS (events); i++)
2309     {
2310       gtk_tree_store_append (store, &iter, NULL);
2311       gtk_tree_store_set (store, &iter,
2312           COL_WHAT_TYPE, events[i].type,
2313           COL_WHAT_SUBTYPE, events[i].subtype,
2314           COL_WHAT_TEXT, events[i].text,
2315           COL_WHAT_ICON, events[i].icon,
2316           -1);
2317     }
2318
2319   gtk_tree_model_iter_nth_child (model, &parent, NULL, 3);
2320   for (i = 0; i < G_N_ELEMENTS (call_events); i++)
2321     {
2322       gtk_tree_store_append (store, &iter, &parent);
2323       gtk_tree_store_set (store, &iter,
2324           COL_WHAT_TYPE, call_events[i].type,
2325           COL_WHAT_SUBTYPE, call_events[i].subtype,
2326           COL_WHAT_TEXT, call_events[i].text,
2327           COL_WHAT_ICON, call_events[i].icon,
2328           -1);
2329     }
2330
2331   gtk_tree_view_expand_all (view);
2332
2333   /* select 'Anything' */
2334   if (gtk_tree_model_get_iter_first (model, &iter))
2335     gtk_tree_selection_select_iter (selection, &iter);
2336
2337   /* set up signals */
2338   g_signal_connect (view, "test-collapse-row",
2339       G_CALLBACK (log_window_what_collapse_row_cb),
2340       NULL);
2341   g_signal_connect (selection, "changed",
2342       G_CALLBACK (log_window_what_changed_cb),
2343       window);
2344
2345   g_object_unref (store);
2346 }
2347
2348 static void
2349 start_spinner (void)
2350 {
2351   gtk_spinner_start (GTK_SPINNER (log_window->spinner));
2352   gtk_notebook_set_current_page (GTK_NOTEBOOK (log_window->notebook),
2353       PAGE_EMPTY);
2354 }
2355
2356 static gboolean
2357 show_spinner (gpointer data)
2358 {
2359   gboolean active;
2360
2361   if (log_window == NULL)
2362     return FALSE;
2363
2364   g_object_get (log_window->spinner, "active", &active, NULL);
2365
2366   if (active)
2367     gtk_notebook_set_current_page (GTK_NOTEBOOK (log_window->notebook),
2368         PAGE_SPINNER);
2369
2370   return FALSE;
2371 }
2372
2373 static void
2374 show_events (TplActionChain *chain,
2375     gpointer user_data)
2376 {
2377   gtk_spinner_stop (GTK_SPINNER (log_window->spinner));
2378   gtk_notebook_set_current_page (GTK_NOTEBOOK (log_window->notebook),
2379       PAGE_EVENTS);
2380
2381   _tpl_action_chain_continue (chain);
2382 }
2383
2384 static void
2385 log_window_got_messages_for_date_cb (GObject *manager,
2386     GAsyncResult *result,
2387     gpointer user_data)
2388 {
2389   Ctx *ctx = user_data;
2390   GtkTreeView *view;
2391   GtkTreeModel *model;
2392   GtkTreeIter iter;
2393   GList *events;
2394   GList *l;
2395   GError *error = NULL;
2396   gint n;
2397
2398   if (log_window == NULL)
2399     {
2400       ctx_free (ctx);
2401       return;
2402     }
2403
2404   if (log_window->count != ctx->count)
2405     goto out;
2406
2407   if (!tpl_log_manager_get_events_for_date_finish (TPL_LOG_MANAGER (manager),
2408       result, &events, &error))
2409     {
2410       DEBUG ("Unable to retrieve messages for the selected date: %s. Aborting",
2411           error->message);
2412       g_error_free (error);
2413       goto out;
2414     }
2415
2416   for (l = events; l; l = l->next)
2417     {
2418       TplEvent *event = l->data;
2419       gboolean append = TRUE;
2420
2421       if (TPL_IS_CALL_EVENT (l->data)
2422           && ctx->event_mask & TPL_EVENT_MASK_CALL
2423           && ctx->event_mask != TPL_EVENT_MASK_ANY)
2424         {
2425           TplCallEvent *call = l->data;
2426
2427           append = FALSE;
2428
2429           if (ctx->subtype & EVENT_CALL_ALL)
2430             {
2431               append = TRUE;
2432             }
2433           else
2434             {
2435               TplCallEndReason reason = tpl_call_event_get_end_reason (call);
2436               TplEntity *sender = tpl_event_get_sender (event);
2437               TplEntity *receiver = tpl_event_get_receiver (event);
2438
2439               if (reason == TPL_CALL_END_REASON_NO_ANSWER)
2440                 {
2441                   if (ctx->subtype & EVENT_CALL_MISSED)
2442                     append = TRUE;
2443                 }
2444               else if (ctx->subtype & EVENT_CALL_OUTGOING
2445                   && tpl_entity_get_entity_type (sender) == TPL_ENTITY_SELF)
2446                 {
2447                   append = TRUE;
2448                 }
2449               else if (ctx->subtype & EVENT_CALL_INCOMING
2450                   && tpl_entity_get_entity_type (receiver) == TPL_ENTITY_SELF)
2451                 {
2452                   append = TRUE;
2453                 }
2454             }
2455         }
2456
2457       if (append)
2458         {
2459           EmpathyMessage *msg = empathy_message_from_tpl_log_event (event);
2460           log_window_append_message (event, msg);
2461           g_object_unref (msg);
2462         }
2463
2464       g_object_unref (event);
2465     }
2466   g_list_free (events);
2467
2468   view = GTK_TREE_VIEW (log_window->treeview_events);
2469   model = gtk_tree_view_get_model (view);
2470   n = gtk_tree_model_iter_n_children (model, NULL) - 1;
2471
2472   if (n >= 0 && gtk_tree_model_iter_nth_child (model, &iter, NULL, n))
2473     {
2474       GtkTreePath *path;
2475
2476       path = gtk_tree_model_get_path (model, &iter);
2477       gtk_tree_view_scroll_to_cell (view, path, NULL, FALSE, 0, 0);
2478       gtk_tree_path_free (path);
2479     }
2480
2481  out:
2482   ctx_free (ctx);
2483
2484   _tpl_action_chain_continue (log_window->chain);
2485 }
2486
2487 static void
2488 get_events_for_date (TplActionChain *chain, gpointer user_data)
2489 {
2490   Ctx *ctx = user_data;
2491
2492   tpl_log_manager_get_events_for_date_async (ctx->window->log_manager,
2493       ctx->account, ctx->entity, ctx->event_mask,
2494       ctx->date,
2495       log_window_got_messages_for_date_cb,
2496       ctx);
2497 }
2498
2499 static void
2500 log_window_get_messages_for_dates (EmpathyLogWindow *window,
2501     GList *dates)
2502 {
2503   GList *accounts, *targets, *acc, *targ, *l;
2504   TplEventTypeMask event_mask;
2505   EventSubtype subtype;
2506   GDate *date, *anytime, *separator;
2507
2508   if (!log_window_get_selected (window,
2509       &accounts, &targets, NULL, &event_mask, &subtype))
2510     return;
2511
2512   anytime = g_date_new_dmy (2, 1, -1);
2513   separator = g_date_new_dmy (1, 1, -1);
2514
2515   _tpl_action_chain_clear (window->chain);
2516   window->count++;
2517
2518   for (acc = accounts, targ = targets;
2519        acc != NULL && targ != NULL;
2520        acc = acc->next, targ = targ->next)
2521     {
2522       TpAccount *account = acc->data;
2523       TplEntity *target = targ->data;
2524
2525       for (l = dates; l != NULL; l = l->next)
2526         {
2527           date = l->data;
2528
2529           /* Get events */
2530           if (g_date_compare (date, anytime) != 0)
2531             {
2532               Ctx *ctx;
2533
2534               ctx = ctx_new (window, account, target, date, event_mask, subtype,
2535                   window->count);
2536               _tpl_action_chain_append (window->chain, get_events_for_date, ctx);
2537             }
2538           else
2539             {
2540               GtkTreeView *view = GTK_TREE_VIEW (window->treeview_when);
2541               GtkTreeModel *model = gtk_tree_view_get_model (view);
2542               GtkTreeIter iter;
2543               gboolean next;
2544               GDate *d;
2545
2546               for (next = gtk_tree_model_get_iter_first (model, &iter);
2547                    next;
2548                    next = gtk_tree_model_iter_next (model, &iter))
2549                 {
2550                   Ctx *ctx;
2551
2552                   gtk_tree_model_get (model, &iter,
2553                       COL_WHEN_DATE, &d,
2554                       -1);
2555
2556                   if (g_date_compare (d, anytime) != 0 &&
2557                       g_date_compare (d, separator) != 0)
2558                     {
2559                       ctx = ctx_new (window, account, target, d,
2560                           event_mask, subtype, window->count);
2561                       _tpl_action_chain_append (window->chain, get_events_for_date, ctx);
2562                     }
2563                 }
2564             }
2565         }
2566     }
2567
2568   start_spinner ();
2569   g_timeout_add (1000, show_spinner, NULL);
2570   _tpl_action_chain_append (window->chain, show_events, NULL);
2571   _tpl_action_chain_start (window->chain);
2572
2573   g_list_free_full (accounts, g_object_unref);
2574   g_list_free_full (targets, g_object_unref);
2575   g_date_free (separator);
2576   g_date_free (anytime);
2577 }
2578
2579 static void
2580 log_manager_got_dates_cb (GObject *manager,
2581     GAsyncResult *result,
2582     gpointer user_data)
2583 {
2584   Ctx *ctx = user_data;
2585   GtkTreeView *view;
2586   GtkTreeModel *model;
2587   GtkTreeSelection *selection;
2588   GtkListStore *store;
2589   GtkTreeIter iter;
2590   GList *dates;
2591   GList *l;
2592   GError *error = NULL;
2593
2594   if (log_window == NULL)
2595     goto out;
2596
2597   if (log_window->count != ctx->count)
2598     goto out;
2599
2600   if (!tpl_log_manager_get_dates_finish (TPL_LOG_MANAGER (manager),
2601        result, &dates, &error))
2602     {
2603       DEBUG ("Unable to retrieve messages' dates: %s. Aborting",
2604           error->message);
2605       goto out;
2606     }
2607
2608   view = GTK_TREE_VIEW (log_window->treeview_when);
2609   model = gtk_tree_view_get_model (view);
2610   store = GTK_LIST_STORE (model);
2611   selection = gtk_tree_view_get_selection (view);
2612
2613   for (l = dates; l != NULL; l = l->next)
2614     {
2615       GDate *date = l->data;
2616
2617       /* Add the date if it's not already there */
2618       has_element = FALSE;
2619       gtk_tree_model_foreach (model, model_has_date, date);
2620       if (!has_element)
2621         {
2622           gchar *text = format_date_for_display (date);
2623
2624           gtk_list_store_append (store, &iter);
2625           gtk_list_store_set (store, &iter,
2626               COL_WHEN_DATE, date,
2627               COL_WHEN_TEXT, text,
2628               COL_WHEN_ICON, CALENDAR_ICON,
2629               -1);
2630
2631           g_free (text);
2632         }
2633     }
2634
2635   if (gtk_tree_model_get_iter_first (model, &iter))
2636     {
2637       gchar *separator = NULL;
2638
2639       if (gtk_tree_model_iter_next (model, &iter))
2640         {
2641           gtk_tree_model_get (model, &iter,
2642               COL_WHEN_TEXT, &separator,
2643               -1);
2644         }
2645
2646       if (g_strcmp0 (separator, "separator") != 0)
2647         {
2648           gtk_list_store_prepend (store, &iter);
2649           gtk_list_store_set (store, &iter,
2650               COL_WHEN_DATE, g_date_new_dmy (1, 1, -1),
2651               COL_WHEN_TEXT, "separator",
2652               -1);
2653
2654           gtk_list_store_prepend (store, &iter);
2655           gtk_list_store_set (store, &iter,
2656               COL_WHEN_DATE, g_date_new_dmy (2, 1, -1),
2657               COL_WHEN_TEXT, _("Anytime"),
2658               -1);
2659         }
2660     }
2661
2662   g_list_free_full (dates, g_free);
2663  out:
2664   ctx_free (ctx);
2665   _tpl_action_chain_continue (log_window->chain);
2666 }
2667
2668 static void
2669 select_first_date (TplActionChain *chain, gpointer user_data)
2670 {
2671   GtkTreeView *view;
2672   GtkTreeModel *model;
2673   GtkTreeSelection *selection;
2674   GtkTreeIter iter;
2675
2676   view = GTK_TREE_VIEW (log_window->treeview_when);
2677   model = gtk_tree_view_get_model (view);
2678   selection = gtk_tree_view_get_selection (view);
2679
2680   /* Show messages of the most recent date */
2681   if (gtk_tree_model_iter_nth_child (model, &iter, NULL, 2))
2682     gtk_tree_selection_select_iter (selection, &iter);
2683
2684   _tpl_action_chain_continue (log_window->chain);
2685 }
2686
2687 static void
2688 get_dates_for_entity (TplActionChain *chain, gpointer user_data)
2689 {
2690   Ctx *ctx = user_data;
2691
2692   tpl_log_manager_get_dates_async (ctx->window->log_manager,
2693       ctx->account, ctx->entity, ctx->event_mask,
2694       log_manager_got_dates_cb, ctx);
2695 }
2696
2697 static void
2698 log_window_chats_get_messages (EmpathyLogWindow *window,
2699     gboolean force_get_dates)
2700 {
2701   GList *accounts, *targets, *dates;
2702   TplEventTypeMask event_mask;
2703   GtkTreeView *view;
2704   GtkTreeModel *model;
2705   GtkListStore *store;
2706   GtkTreeSelection *selection;
2707
2708   if (!log_window_get_selected (window, &accounts, &targets,
2709       &dates, &event_mask, NULL))
2710     return;
2711
2712   view = GTK_TREE_VIEW (window->treeview_when);
2713   selection = gtk_tree_view_get_selection (view);
2714   model = gtk_tree_view_get_model (view);
2715   store = GTK_LIST_STORE (model);
2716
2717   /* Clear all current messages shown in the textview */
2718   gtk_tree_store_clear (window->store_events);
2719
2720   _tpl_action_chain_clear (window->chain);
2721   window->count++;
2722
2723   /* If there's a search use the returned hits */
2724   if (window->hits != NULL)
2725     {
2726       if (force_get_dates)
2727         {
2728           g_signal_handlers_block_by_func (selection,
2729               log_window_when_changed_cb,
2730               window);
2731
2732           gtk_list_store_clear (store);
2733
2734           g_signal_handlers_unblock_by_func (selection,
2735               log_window_when_changed_cb,
2736               window);
2737
2738           populate_dates_from_search_hits (accounts, targets);
2739         }
2740       else
2741         {
2742           populate_events_from_search_hits (accounts, targets, dates);
2743         }
2744     }
2745   /* Either use the supplied date or get the last */
2746   else if (force_get_dates || dates == NULL)
2747     {
2748       GList *acc, *targ;
2749
2750       g_signal_handlers_block_by_func (selection,
2751           log_window_when_changed_cb,
2752           window);
2753
2754       gtk_list_store_clear (store);
2755
2756       g_signal_handlers_unblock_by_func (selection,
2757           log_window_when_changed_cb,
2758           window);
2759
2760       /* Get a list of dates and show them on the treeview */
2761       for (targ = targets, acc = accounts;
2762            targ != NULL && acc != NULL;
2763            targ = targ->next, acc = acc->next)
2764         {
2765           TpAccount *account = acc->data;
2766           TplEntity *target = targ->data;
2767           Ctx *ctx = ctx_new (window, account, target, NULL, event_mask, 0,
2768               window->count);
2769
2770           _tpl_action_chain_append (window->chain, get_dates_for_entity, ctx);
2771         }
2772       _tpl_action_chain_append (window->chain, select_first_date, NULL);
2773       _tpl_action_chain_start (window->chain);
2774     }
2775   else
2776     {
2777       /* Show messages of the selected date */
2778       log_window_get_messages_for_dates (window, dates);
2779     }
2780
2781   g_list_free_full (accounts, g_object_unref);
2782   g_list_free_full (targets, g_object_unref);
2783   g_list_free_full (dates, (GFreeFunc) g_date_free);
2784 }
2785
2786 typedef struct {
2787   EmpathyAccountChooserFilterResultCallback callback;
2788   gpointer user_data;
2789 } FilterCallbackData;
2790
2791 static void
2792 got_entities (GObject *manager,
2793     GAsyncResult *result,
2794     gpointer user_data)
2795 {
2796   FilterCallbackData *data = user_data;
2797   GList *entities;
2798   GError *error = NULL;
2799
2800   if (!tpl_log_manager_get_entities_finish (TPL_LOG_MANAGER (manager),
2801       result, &entities, &error))
2802     {
2803       DEBUG ("Could not get entities: %s", error->message);
2804       g_error_free (error);
2805       data->callback (FALSE, data->user_data);
2806     }
2807   else
2808     {
2809       data->callback (entities != NULL, data->user_data);
2810
2811       g_list_free_full (entities, g_object_unref);
2812     }
2813
2814   g_slice_free (FilterCallbackData, data);
2815 }
2816
2817 static void
2818 empathy_account_chooser_filter_has_logs (TpAccount *account,
2819     EmpathyAccountChooserFilterResultCallback callback,
2820     gpointer callback_data,
2821     gpointer user_data)
2822 {
2823   TplLogManager *manager = tpl_log_manager_dup_singleton ();
2824   FilterCallbackData *cb_data = g_slice_new0 (FilterCallbackData);
2825
2826   cb_data->callback = callback;
2827   cb_data->user_data = callback_data;
2828
2829   tpl_log_manager_get_entities_async (manager, account, got_entities, cb_data);
2830
2831   g_object_unref (manager);
2832 }
2833
2834 static void
2835 log_window_logger_clear_account_cb (TpProxy *proxy,
2836     const GError *error,
2837     gpointer user_data,
2838     GObject *weak_object)
2839 {
2840   EmpathyLogWindow *window = user_data;
2841
2842   if (error != NULL)
2843     g_warning ("Error when clearing logs: %s", error->message);
2844
2845   /* Refresh the log viewer so the logs are cleared if the account
2846    * has been deleted */
2847   gtk_tree_store_clear (window->store_events);
2848   log_window_who_populate (window);
2849
2850   /* Re-filter the account chooser so the accounts without logs get greyed out */
2851   empathy_account_chooser_set_filter (
2852       EMPATHY_ACCOUNT_CHOOSER (window->account_chooser),
2853       empathy_account_chooser_filter_has_logs, NULL);
2854 }
2855
2856 static void
2857 log_window_clear_logs_chooser_select_account (EmpathyAccountChooser *chooser,
2858     EmpathyLogWindow *window)
2859 {
2860   EmpathyAccountChooser *account_chooser;
2861
2862   account_chooser = EMPATHY_ACCOUNT_CHOOSER (window->account_chooser);
2863
2864   empathy_account_chooser_set_account (chooser,
2865       empathy_account_chooser_get_account (account_chooser));
2866 }
2867
2868 static void
2869 log_window_delete_menu_clicked_cb (GtkMenuItem *menuitem,
2870     EmpathyLogWindow *window)
2871 {
2872   GtkWidget *dialog, *content_area, *hbox, *label;
2873   EmpathyAccountChooser *account_chooser;
2874   gint response_id;
2875   TpDBusDaemon *bus;
2876   TpProxy *logger;
2877   GError *error = NULL;
2878
2879   account_chooser = (EmpathyAccountChooser *) empathy_account_chooser_new ();
2880   empathy_account_chooser_set_has_all_option (account_chooser, TRUE);
2881   empathy_account_chooser_set_filter (account_chooser,
2882       empathy_account_chooser_filter_has_logs, NULL);
2883
2884   /* Select the same account as in the history window */
2885   if (empathy_account_chooser_is_ready (account_chooser))
2886     log_window_clear_logs_chooser_select_account (account_chooser, window);
2887   else
2888     g_signal_connect (account_chooser, "ready",
2889         G_CALLBACK (log_window_clear_logs_chooser_select_account), window);
2890
2891   dialog = gtk_message_dialog_new_with_markup (GTK_WINDOW (window->window),
2892       GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING,
2893       GTK_BUTTONS_NONE,
2894       _("Are you sure you want to delete all logs of previous conversations?"));
2895
2896   gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2897       GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2898       _("Clear All"), GTK_RESPONSE_APPLY,
2899       NULL);
2900
2901   content_area = gtk_message_dialog_get_message_area (
2902       GTK_MESSAGE_DIALOG (dialog));
2903
2904   hbox = gtk_hbox_new (FALSE, 6);
2905   label = gtk_label_new (_("Delete from:"));
2906   gtk_box_pack_start (GTK_BOX (hbox), label,
2907       FALSE, FALSE, 0);
2908   gtk_box_pack_start (GTK_BOX (hbox), GTK_WIDGET (account_chooser),
2909       FALSE, FALSE, 0);
2910   gtk_box_pack_start (GTK_BOX (content_area), hbox,
2911       FALSE, FALSE, 0);
2912
2913   gtk_widget_show_all (hbox);
2914
2915   response_id = gtk_dialog_run (GTK_DIALOG (dialog));
2916
2917   if (response_id != GTK_RESPONSE_APPLY)
2918     goto out;
2919
2920   bus = tp_dbus_daemon_dup (&error);
2921   if (error != NULL)
2922     {
2923       g_warning ("Could not delete logs: %s", error->message);
2924       g_error_free (error);
2925       goto out;
2926     }
2927
2928   logger = g_object_new (TP_TYPE_PROXY,
2929       "bus-name", "org.freedesktop.Telepathy.Logger",
2930       "object-path", "/org/freedesktop/Telepathy/Logger",
2931       "dbus-daemon", bus,
2932       NULL);
2933   g_object_unref (bus);
2934
2935   tp_proxy_add_interface_by_id (logger, EMP_IFACE_QUARK_LOGGER);
2936
2937   if (empathy_account_chooser_has_all_selected (account_chooser))
2938     {
2939       DEBUG ("Deleting logs for all the accounts");
2940
2941       emp_cli_logger_call_clear (logger, -1,
2942           log_window_logger_clear_account_cb,
2943           window, NULL, G_OBJECT (window->window));
2944     }
2945   else
2946     {
2947       TpAccount *account;
2948
2949       account = empathy_account_chooser_get_account (account_chooser);
2950
2951       DEBUG ("Deleting logs for %s", tp_proxy_get_object_path (account));
2952
2953       emp_cli_logger_call_clear_account (logger, -1,
2954           tp_proxy_get_object_path (account),
2955           log_window_logger_clear_account_cb,
2956           window, NULL, G_OBJECT (window->window));
2957     }
2958
2959   g_object_unref (logger);
2960  out:
2961   gtk_widget_destroy (dialog);
2962 }