]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-log-window.c
LogWindow: Don't cut the log messages
[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
2008       gtk_tree_model_get (model, &iter,
2009           COL_WHO_ACCOUNT, &this_account,
2010           COL_WHO_TARGET, &this_target,
2011           -1);
2012
2013       this_chat_id = tpl_entity_get_identifier (this_target);
2014       this_is_chatroom = tpl_entity_get_entity_type (this_target)
2015           == TPL_ENTITY_ROOM;
2016
2017       if (this_account == window->selected_account &&
2018           !tp_strdiff (this_chat_id, window->selected_chat_id) &&
2019           this_is_chatroom == window->selected_is_chatroom)
2020         {
2021           gtk_tree_selection_select_iter (selection, &iter);
2022           path = gtk_tree_model_get_path (model, &iter);
2023           gtk_tree_view_scroll_to_cell (view, path, NULL, TRUE, 0.5, 0.0);
2024           gtk_tree_path_free (path);
2025           g_object_unref (this_account);
2026           g_object_unref (this_target);
2027           break;
2028         }
2029
2030       g_object_unref (this_account);
2031       g_object_unref (this_target);
2032     }
2033
2034   tp_clear_object (&window->selected_account);
2035   tp_clear_pointer (&window->selected_chat_id, g_free);
2036 }
2037
2038 static gint
2039 sort_by_date (GtkTreeModel *model,
2040     GtkTreeIter *a,
2041     GtkTreeIter *b,
2042     gpointer user_data)
2043 {
2044   GDate *date1, *date2;
2045
2046   gtk_tree_model_get (model, a,
2047       COL_WHEN_DATE, &date1,
2048       -1);
2049
2050   gtk_tree_model_get (model, b,
2051       COL_WHEN_DATE, &date2,
2052       -1);
2053
2054   return g_date_compare (date1, date2);
2055 }
2056
2057 static gboolean
2058 when_row_is_separator (GtkTreeModel *model,
2059     GtkTreeIter *iter,
2060     gpointer data)
2061 {
2062   gchar *when;
2063   gboolean ret;
2064
2065   gtk_tree_model_get (model, iter,
2066       COL_WHEN_TEXT, &when,
2067       -1);
2068
2069   ret = g_str_equal (when, "separator");
2070   g_free (when);
2071   return ret;
2072 }
2073
2074 static void
2075 log_window_when_changed_cb (GtkTreeSelection *selection,
2076     EmpathyLogWindow *window)
2077 {
2078   GtkTreeView *view;
2079   GtkTreeModel *model;
2080   GtkTreeIter iter;
2081
2082   DEBUG ("log_window_when_changed_cb");
2083
2084   view = gtk_tree_selection_get_tree_view (selection);
2085   model = gtk_tree_view_get_model (view);
2086
2087   /* If 'Anytime' is selected, everything else should be deselected */
2088   if (gtk_tree_model_get_iter_first (model, &iter))
2089     {
2090       if (gtk_tree_selection_iter_is_selected (selection, &iter))
2091         {
2092           g_signal_handlers_block_by_func (selection,
2093               log_window_when_changed_cb,
2094               window);
2095
2096           gtk_tree_selection_unselect_all (selection);
2097           gtk_tree_selection_select_iter (selection, &iter);
2098
2099           g_signal_handlers_unblock_by_func (selection,
2100               log_window_when_changed_cb,
2101               window);
2102         }
2103     }
2104
2105   log_window_chats_get_messages (window, FALSE);
2106 }
2107
2108 static void
2109 log_window_when_setup (EmpathyLogWindow *window)
2110 {
2111   GtkTreeView       *view;
2112   GtkTreeModel      *model;
2113   GtkTreeSelection  *selection;
2114   GtkTreeSortable   *sortable;
2115   GtkTreeViewColumn *column;
2116   GtkListStore      *store;
2117   GtkCellRenderer   *cell;
2118
2119   view = GTK_TREE_VIEW (window->treeview_when);
2120   selection = gtk_tree_view_get_selection (view);
2121
2122   /* new store */
2123   store = gtk_list_store_new (COL_WHEN_COUNT,
2124       G_TYPE_DATE,        /* date */
2125       G_TYPE_STRING,      /* stringified date */
2126       G_TYPE_STRING);     /* icon */
2127
2128   model = GTK_TREE_MODEL (store);
2129   sortable = GTK_TREE_SORTABLE (store);
2130
2131   gtk_tree_view_set_model (view, model);
2132
2133   /* new column */
2134   column = gtk_tree_view_column_new ();
2135   gtk_tree_view_column_set_title (column, _("When"));
2136
2137   cell = gtk_cell_renderer_pixbuf_new ();
2138   gtk_tree_view_column_pack_start (column, cell, FALSE);
2139   gtk_tree_view_column_add_attribute (column, cell,
2140       "icon-name", COL_WHEN_ICON);
2141
2142   cell = gtk_cell_renderer_text_new ();
2143   g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
2144   gtk_tree_view_column_pack_start (column, cell, TRUE);
2145   gtk_tree_view_column_add_attribute (column, cell,
2146       "text",
2147       COL_WHEN_TEXT);
2148
2149   gtk_tree_view_append_column (view, column);
2150
2151   /* set up treeview properties */
2152   gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
2153   gtk_tree_view_set_row_separator_func (view, when_row_is_separator,
2154       NULL, NULL);
2155   gtk_tree_sortable_set_sort_column_id (sortable,
2156       COL_WHEN_DATE,
2157       GTK_SORT_DESCENDING);
2158   gtk_tree_sortable_set_sort_func (sortable,
2159       COL_WHEN_DATE, sort_by_date,
2160       NULL, NULL);
2161
2162   /* set up signals */
2163   g_signal_connect (selection, "changed",
2164       G_CALLBACK (log_window_when_changed_cb),
2165       window);
2166
2167   g_object_unref (store);
2168 }
2169
2170 static gboolean
2171 what_row_is_separator (GtkTreeModel *model,
2172     GtkTreeIter *iter,
2173     gpointer data)
2174 {
2175   gint type;
2176
2177   gtk_tree_model_get (model, iter,
2178       COL_WHAT_TYPE, &type,
2179       -1);
2180
2181   return (type == WHAT_TYPE_SEPARATOR);
2182 }
2183
2184 static void
2185 log_window_what_changed_cb (GtkTreeSelection *selection,
2186     EmpathyLogWindow *window)
2187 {
2188   GtkTreeView *view;
2189   GtkTreeModel *model;
2190   GtkTreeIter iter;
2191
2192   DEBUG ("log_window_what_changed_cb");
2193
2194   view = gtk_tree_selection_get_tree_view (selection);
2195   model = gtk_tree_view_get_model (view);
2196
2197   /* If 'Anything' is selected, everything else should be deselected */
2198   if (gtk_tree_model_get_iter_first (model, &iter))
2199     {
2200       if (gtk_tree_selection_iter_is_selected (selection, &iter))
2201         {
2202           g_signal_handlers_block_by_func (selection,
2203               log_window_what_changed_cb,
2204               window);
2205
2206           gtk_tree_selection_unselect_all (selection);
2207           gtk_tree_selection_select_iter (selection, &iter);
2208
2209           g_signal_handlers_unblock_by_func (selection,
2210               log_window_what_changed_cb,
2211               window);
2212         }
2213     }
2214
2215   /* The dates need to be updated if we're not searching */
2216   log_window_chats_get_messages (window, window->hits == NULL);
2217 }
2218
2219 static gboolean
2220 log_window_what_collapse_row_cb (GtkTreeView *tree_view,
2221     GtkTreeIter *iter,
2222     GtkTreePath *path,
2223     gpointer user_data)
2224 {
2225   /* Reject collapsing */
2226   return TRUE;
2227 }
2228
2229 struct event
2230 {
2231   gint type;
2232   EventSubtype subtype;
2233   const gchar *icon;
2234   const gchar *text;
2235 };
2236
2237 static void
2238 log_window_what_setup (EmpathyLogWindow *window)
2239 {
2240   GtkTreeView       *view;
2241   GtkTreeModel      *model;
2242   GtkTreeSelection  *selection;
2243   GtkTreeSortable   *sortable;
2244   GtkTreeViewColumn *column;
2245   GtkTreeIter        iter, parent;
2246   GtkTreeStore      *store;
2247   GtkCellRenderer   *cell;
2248   guint i;
2249   struct event events [] = {
2250     { TPL_EVENT_MASK_ANY, 0, NULL, _("Anything") },
2251     { WHAT_TYPE_SEPARATOR, 0, NULL, "separator" },
2252     { TPL_EVENT_MASK_TEXT, 0, "stock_text_justify", _("Text chats") },
2253     { TPL_EVENT_MASK_CALL, EVENT_CALL_ALL, "call-start", _("Calls") }
2254   };
2255   struct event call_events [] = {
2256     { TPL_EVENT_MASK_CALL, EVENT_CALL_INCOMING, "call-start", _("Incoming calls") },
2257     { TPL_EVENT_MASK_CALL, EVENT_CALL_OUTGOING, "call-start", _("Outgoing calls") },
2258     { TPL_EVENT_MASK_CALL, EVENT_CALL_MISSED, "call-stop", _("Missed calls") }
2259   };
2260
2261   view = GTK_TREE_VIEW (window->treeview_what);
2262   selection = gtk_tree_view_get_selection (view);
2263
2264   /* new store */
2265   store = gtk_tree_store_new (COL_WHAT_COUNT,
2266       G_TYPE_INT,         /* history type */
2267       G_TYPE_INT,         /* history subtype */
2268       G_TYPE_STRING,      /* stringified history type */
2269       G_TYPE_STRING,      /* icon */
2270       G_TYPE_BOOLEAN);    /* expander (hidden) */
2271
2272   model = GTK_TREE_MODEL (store);
2273   sortable = GTK_TREE_SORTABLE (store);
2274
2275   gtk_tree_view_set_model (view, model);
2276
2277   /* new column */
2278   column = gtk_tree_view_column_new ();
2279   gtk_tree_view_column_set_title (column, _("What"));
2280
2281   cell = gtk_cell_renderer_pixbuf_new ();
2282   gtk_tree_view_column_pack_start (column, cell, FALSE);
2283   gtk_tree_view_column_add_attribute (column, cell,
2284       "icon-name", COL_WHAT_ICON);
2285
2286   cell = gtk_cell_renderer_text_new ();
2287   g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
2288   gtk_tree_view_column_pack_start (column, cell, TRUE);
2289   gtk_tree_view_column_add_attribute (column, cell,
2290       "text", COL_WHAT_TEXT);
2291
2292   gtk_tree_view_append_column (view, column);
2293
2294   /* set up treeview properties */
2295   gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
2296   gtk_tree_view_set_show_expanders (view, FALSE);
2297   gtk_tree_view_set_level_indentation (view, 12);
2298   gtk_tree_view_expand_all (view);
2299   gtk_tree_view_set_row_separator_func (view, what_row_is_separator,
2300       NULL, NULL);
2301
2302   /* populate */
2303   for (i = 0; i < G_N_ELEMENTS (events); i++)
2304     {
2305       gtk_tree_store_append (store, &iter, NULL);
2306       gtk_tree_store_set (store, &iter,
2307           COL_WHAT_TYPE, events[i].type,
2308           COL_WHAT_SUBTYPE, events[i].subtype,
2309           COL_WHAT_TEXT, events[i].text,
2310           COL_WHAT_ICON, events[i].icon,
2311           -1);
2312     }
2313
2314   gtk_tree_model_iter_nth_child (model, &parent, NULL, 3);
2315   for (i = 0; i < G_N_ELEMENTS (call_events); i++)
2316     {
2317       gtk_tree_store_append (store, &iter, &parent);
2318       gtk_tree_store_set (store, &iter,
2319           COL_WHAT_TYPE, call_events[i].type,
2320           COL_WHAT_SUBTYPE, call_events[i].subtype,
2321           COL_WHAT_TEXT, call_events[i].text,
2322           COL_WHAT_ICON, call_events[i].icon,
2323           -1);
2324     }
2325
2326   gtk_tree_view_expand_all (view);
2327
2328   /* select 'Anything' */
2329   if (gtk_tree_model_get_iter_first (model, &iter))
2330     gtk_tree_selection_select_iter (selection, &iter);
2331
2332   /* set up signals */
2333   g_signal_connect (view, "test-collapse-row",
2334       G_CALLBACK (log_window_what_collapse_row_cb),
2335       NULL);
2336   g_signal_connect (selection, "changed",
2337       G_CALLBACK (log_window_what_changed_cb),
2338       window);
2339
2340   g_object_unref (store);
2341 }
2342
2343 static void
2344 start_spinner (void)
2345 {
2346   gtk_spinner_start (GTK_SPINNER (log_window->spinner));
2347   gtk_notebook_set_current_page (GTK_NOTEBOOK (log_window->notebook),
2348       PAGE_EMPTY);
2349 }
2350
2351 static gboolean
2352 show_spinner (gpointer data)
2353 {
2354   gboolean active;
2355
2356   if (log_window == NULL)
2357     return FALSE;
2358
2359   g_object_get (log_window->spinner, "active", &active, NULL);
2360
2361   if (active)
2362     gtk_notebook_set_current_page (GTK_NOTEBOOK (log_window->notebook),
2363         PAGE_SPINNER);
2364
2365   return FALSE;
2366 }
2367
2368 static void
2369 show_events (TplActionChain *chain,
2370     gpointer user_data)
2371 {
2372   gtk_spinner_stop (GTK_SPINNER (log_window->spinner));
2373   gtk_notebook_set_current_page (GTK_NOTEBOOK (log_window->notebook),
2374       PAGE_EVENTS);
2375
2376   _tpl_action_chain_continue (chain);
2377 }
2378
2379 static void
2380 log_window_got_messages_for_date_cb (GObject *manager,
2381     GAsyncResult *result,
2382     gpointer user_data)
2383 {
2384   Ctx *ctx = user_data;
2385   GtkTreeView *view;
2386   GtkTreeModel *model;
2387   GtkTreeIter iter;
2388   GList *events;
2389   GList *l;
2390   GError *error = NULL;
2391   gint n;
2392
2393   if (log_window == NULL)
2394     {
2395       ctx_free (ctx);
2396       return;
2397     }
2398
2399   if (log_window->count != ctx->count)
2400     goto out;
2401
2402   if (!tpl_log_manager_get_events_for_date_finish (TPL_LOG_MANAGER (manager),
2403       result, &events, &error))
2404     {
2405       DEBUG ("Unable to retrieve messages for the selected date: %s. Aborting",
2406           error->message);
2407       g_error_free (error);
2408       goto out;
2409     }
2410
2411   for (l = events; l; l = l->next)
2412     {
2413       TplEvent *event = l->data;
2414       gboolean append = TRUE;
2415
2416       if (TPL_IS_CALL_EVENT (l->data)
2417           && ctx->event_mask & TPL_EVENT_MASK_CALL
2418           && ctx->event_mask != TPL_EVENT_MASK_ANY)
2419         {
2420           TplCallEvent *call = l->data;
2421
2422           append = FALSE;
2423
2424           if (ctx->subtype & EVENT_CALL_ALL)
2425             {
2426               append = TRUE;
2427             }
2428           else
2429             {
2430               TplCallEndReason reason = tpl_call_event_get_end_reason (call);
2431               TplEntity *sender = tpl_event_get_sender (event);
2432               TplEntity *receiver = tpl_event_get_receiver (event);
2433
2434               if (reason == TPL_CALL_END_REASON_NO_ANSWER)
2435                 {
2436                   if (ctx->subtype & EVENT_CALL_MISSED)
2437                     append = TRUE;
2438                 }
2439               else if (ctx->subtype & EVENT_CALL_OUTGOING
2440                   && tpl_entity_get_entity_type (sender) == TPL_ENTITY_SELF)
2441                 {
2442                   append = TRUE;
2443                 }
2444               else if (ctx->subtype & EVENT_CALL_INCOMING
2445                   && tpl_entity_get_entity_type (receiver) == TPL_ENTITY_SELF)
2446                 {
2447                   append = TRUE;
2448                 }
2449             }
2450         }
2451
2452       if (append)
2453         {
2454           EmpathyMessage *msg = empathy_message_from_tpl_log_event (event);
2455           log_window_append_message (event, msg);
2456           g_object_unref (msg);
2457         }
2458
2459       g_object_unref (event);
2460     }
2461   g_list_free (events);
2462
2463   view = GTK_TREE_VIEW (log_window->treeview_events);
2464   model = gtk_tree_view_get_model (view);
2465   n = gtk_tree_model_iter_n_children (model, NULL) - 1;
2466
2467   if (n >= 0 && gtk_tree_model_iter_nth_child (model, &iter, NULL, n))
2468     {
2469       GtkTreePath *path;
2470
2471       path = gtk_tree_model_get_path (model, &iter);
2472       gtk_tree_view_scroll_to_cell (view, path, NULL, FALSE, 0, 0);
2473       gtk_tree_path_free (path);
2474     }
2475
2476  out:
2477   ctx_free (ctx);
2478
2479   _tpl_action_chain_continue (log_window->chain);
2480 }
2481
2482 static void
2483 get_events_for_date (TplActionChain *chain, gpointer user_data)
2484 {
2485   Ctx *ctx = user_data;
2486
2487   tpl_log_manager_get_events_for_date_async (ctx->window->log_manager,
2488       ctx->account, ctx->entity, ctx->event_mask,
2489       ctx->date,
2490       log_window_got_messages_for_date_cb,
2491       ctx);
2492 }
2493
2494 static void
2495 log_window_get_messages_for_dates (EmpathyLogWindow *window,
2496     GList *dates)
2497 {
2498   GList *accounts, *targets, *acc, *targ, *l;
2499   TplEventTypeMask event_mask;
2500   EventSubtype subtype;
2501   GDate *date, *anytime, *separator;
2502
2503   if (!log_window_get_selected (window,
2504       &accounts, &targets, NULL, &event_mask, &subtype))
2505     return;
2506
2507   anytime = g_date_new_dmy (2, 1, -1);
2508   separator = g_date_new_dmy (1, 1, -1);
2509
2510   _tpl_action_chain_clear (window->chain);
2511   window->count++;
2512
2513   for (acc = accounts, targ = targets;
2514        acc != NULL && targ != NULL;
2515        acc = acc->next, targ = targ->next)
2516     {
2517       TpAccount *account = acc->data;
2518       TplEntity *target = targ->data;
2519
2520       for (l = dates; l != NULL; l = l->next)
2521         {
2522           date = l->data;
2523
2524           /* Get events */
2525           if (g_date_compare (date, anytime) != 0)
2526             {
2527               Ctx *ctx;
2528
2529               ctx = ctx_new (window, account, target, date, event_mask, subtype,
2530                   window->count);
2531               _tpl_action_chain_append (window->chain, get_events_for_date, ctx);
2532             }
2533           else
2534             {
2535               GtkTreeView *view = GTK_TREE_VIEW (window->treeview_when);
2536               GtkTreeModel *model = gtk_tree_view_get_model (view);
2537               GtkTreeIter iter;
2538               gboolean next;
2539               GDate *d;
2540
2541               for (next = gtk_tree_model_get_iter_first (model, &iter);
2542                    next;
2543                    next = gtk_tree_model_iter_next (model, &iter))
2544                 {
2545                   Ctx *ctx;
2546
2547                   gtk_tree_model_get (model, &iter,
2548                       COL_WHEN_DATE, &d,
2549                       -1);
2550
2551                   if (g_date_compare (d, anytime) != 0 &&
2552                       g_date_compare (d, separator) != 0)
2553                     {
2554                       ctx = ctx_new (window, account, target, d,
2555                           event_mask, subtype, window->count);
2556                       _tpl_action_chain_append (window->chain, get_events_for_date, ctx);
2557                     }
2558                 }
2559             }
2560         }
2561     }
2562
2563   start_spinner ();
2564   g_timeout_add (1000, show_spinner, NULL);
2565   _tpl_action_chain_append (window->chain, show_events, NULL);
2566   _tpl_action_chain_start (window->chain);
2567
2568   g_list_free_full (accounts, g_object_unref);
2569   g_list_free_full (targets, g_object_unref);
2570   g_date_free (separator);
2571   g_date_free (anytime);
2572 }
2573
2574 static void
2575 log_manager_got_dates_cb (GObject *manager,
2576     GAsyncResult *result,
2577     gpointer user_data)
2578 {
2579   Ctx *ctx = user_data;
2580   GtkTreeView *view;
2581   GtkTreeModel *model;
2582   GtkTreeSelection *selection;
2583   GtkListStore *store;
2584   GtkTreeIter iter;
2585   GList *dates;
2586   GList *l;
2587   GError *error = NULL;
2588
2589   if (log_window == NULL)
2590     goto out;
2591
2592   if (log_window->count != ctx->count)
2593     goto out;
2594
2595   if (!tpl_log_manager_get_dates_finish (TPL_LOG_MANAGER (manager),
2596        result, &dates, &error))
2597     {
2598       DEBUG ("Unable to retrieve messages' dates: %s. Aborting",
2599           error->message);
2600       goto out;
2601     }
2602
2603   view = GTK_TREE_VIEW (log_window->treeview_when);
2604   model = gtk_tree_view_get_model (view);
2605   store = GTK_LIST_STORE (model);
2606   selection = gtk_tree_view_get_selection (view);
2607
2608   for (l = dates; l != NULL; l = l->next)
2609     {
2610       GDate *date = l->data;
2611
2612       /* Add the date if it's not already there */
2613       has_element = FALSE;
2614       gtk_tree_model_foreach (model, model_has_date, date);
2615       if (!has_element)
2616         {
2617           gchar *text = format_date_for_display (date);
2618
2619           gtk_list_store_append (store, &iter);
2620           gtk_list_store_set (store, &iter,
2621               COL_WHEN_DATE, date,
2622               COL_WHEN_TEXT, text,
2623               COL_WHEN_ICON, CALENDAR_ICON,
2624               -1);
2625
2626           g_free (text);
2627         }
2628     }
2629
2630   if (gtk_tree_model_get_iter_first (model, &iter))
2631     {
2632       gchar *separator = NULL;
2633
2634       if (gtk_tree_model_iter_next (model, &iter))
2635         {
2636           gtk_tree_model_get (model, &iter,
2637               COL_WHEN_TEXT, &separator,
2638               -1);
2639         }
2640
2641       if (g_strcmp0 (separator, "separator") != 0)
2642         {
2643           gtk_list_store_prepend (store, &iter);
2644           gtk_list_store_set (store, &iter,
2645               COL_WHEN_DATE, g_date_new_dmy (1, 1, -1),
2646               COL_WHEN_TEXT, "separator",
2647               -1);
2648
2649           gtk_list_store_prepend (store, &iter);
2650           gtk_list_store_set (store, &iter,
2651               COL_WHEN_DATE, g_date_new_dmy (2, 1, -1),
2652               COL_WHEN_TEXT, _("Anytime"),
2653               -1);
2654         }
2655     }
2656
2657   g_list_free_full (dates, g_free);
2658  out:
2659   ctx_free (ctx);
2660   _tpl_action_chain_continue (log_window->chain);
2661 }
2662
2663 static void
2664 select_first_date (TplActionChain *chain, gpointer user_data)
2665 {
2666   GtkTreeView *view;
2667   GtkTreeModel *model;
2668   GtkTreeSelection *selection;
2669   GtkTreeIter iter;
2670
2671   view = GTK_TREE_VIEW (log_window->treeview_when);
2672   model = gtk_tree_view_get_model (view);
2673   selection = gtk_tree_view_get_selection (view);
2674
2675   /* Show messages of the most recent date */
2676   if (gtk_tree_model_iter_nth_child (model, &iter, NULL, 2))
2677     gtk_tree_selection_select_iter (selection, &iter);
2678
2679   _tpl_action_chain_continue (log_window->chain);
2680 }
2681
2682 static void
2683 get_dates_for_entity (TplActionChain *chain, gpointer user_data)
2684 {
2685   Ctx *ctx = user_data;
2686
2687   tpl_log_manager_get_dates_async (ctx->window->log_manager,
2688       ctx->account, ctx->entity, ctx->event_mask,
2689       log_manager_got_dates_cb, ctx);
2690 }
2691
2692 static void
2693 log_window_chats_get_messages (EmpathyLogWindow *window,
2694     gboolean force_get_dates)
2695 {
2696   GList *accounts, *targets, *dates;
2697   TplEventTypeMask event_mask;
2698   GtkTreeView *view;
2699   GtkTreeModel *model;
2700   GtkListStore *store;
2701   GtkTreeSelection *selection;
2702
2703   if (!log_window_get_selected (window, &accounts, &targets,
2704       &dates, &event_mask, NULL))
2705     return;
2706
2707   view = GTK_TREE_VIEW (window->treeview_when);
2708   selection = gtk_tree_view_get_selection (view);
2709   model = gtk_tree_view_get_model (view);
2710   store = GTK_LIST_STORE (model);
2711
2712   /* Clear all current messages shown in the textview */
2713   gtk_tree_store_clear (window->store_events);
2714
2715   _tpl_action_chain_clear (window->chain);
2716   window->count++;
2717
2718   /* If there's a search use the returned hits */
2719   if (window->hits != NULL)
2720     {
2721       if (force_get_dates)
2722         {
2723           g_signal_handlers_block_by_func (selection,
2724               log_window_when_changed_cb,
2725               window);
2726
2727           gtk_list_store_clear (store);
2728
2729           g_signal_handlers_unblock_by_func (selection,
2730               log_window_when_changed_cb,
2731               window);
2732
2733           populate_dates_from_search_hits (accounts, targets);
2734         }
2735       else
2736         {
2737           populate_events_from_search_hits (accounts, targets, dates);
2738         }
2739     }
2740   /* Either use the supplied date or get the last */
2741   else if (force_get_dates || dates == NULL)
2742     {
2743       GList *acc, *targ;
2744
2745       g_signal_handlers_block_by_func (selection,
2746           log_window_when_changed_cb,
2747           window);
2748
2749       gtk_list_store_clear (store);
2750
2751       g_signal_handlers_unblock_by_func (selection,
2752           log_window_when_changed_cb,
2753           window);
2754
2755       /* Get a list of dates and show them on the treeview */
2756       for (targ = targets, acc = accounts;
2757            targ != NULL && acc != NULL;
2758            targ = targ->next, acc = acc->next)
2759         {
2760           TpAccount *account = acc->data;
2761           TplEntity *target = targ->data;
2762           Ctx *ctx = ctx_new (window, account, target, NULL, event_mask, 0,
2763               window->count);
2764
2765           _tpl_action_chain_append (window->chain, get_dates_for_entity, ctx);
2766         }
2767       _tpl_action_chain_append (window->chain, select_first_date, NULL);
2768       _tpl_action_chain_start (window->chain);
2769     }
2770   else
2771     {
2772       /* Show messages of the selected date */
2773       log_window_get_messages_for_dates (window, dates);
2774     }
2775
2776   g_list_free_full (accounts, g_object_unref);
2777   g_list_free_full (targets, g_object_unref);
2778   g_list_free_full (dates, (GFreeFunc) g_date_free);
2779 }
2780
2781 typedef struct {
2782   EmpathyAccountChooserFilterResultCallback callback;
2783   gpointer user_data;
2784 } FilterCallbackData;
2785
2786 static void
2787 got_entities (GObject *manager,
2788     GAsyncResult *result,
2789     gpointer user_data)
2790 {
2791   FilterCallbackData *data = user_data;
2792   GList *entities;
2793   GError *error = NULL;
2794
2795   if (!tpl_log_manager_get_entities_finish (TPL_LOG_MANAGER (manager),
2796       result, &entities, &error))
2797     {
2798       DEBUG ("Could not get entities: %s", error->message);
2799       g_error_free (error);
2800       data->callback (FALSE, data->user_data);
2801     }
2802   else
2803     {
2804       data->callback (entities != NULL, data->user_data);
2805
2806       g_list_free_full (entities, g_object_unref);
2807     }
2808
2809   g_slice_free (FilterCallbackData, data);
2810 }
2811
2812 static void
2813 empathy_account_chooser_filter_has_logs (TpAccount *account,
2814     EmpathyAccountChooserFilterResultCallback callback,
2815     gpointer callback_data,
2816     gpointer user_data)
2817 {
2818   TplLogManager *manager = tpl_log_manager_dup_singleton ();
2819   FilterCallbackData *cb_data = g_slice_new0 (FilterCallbackData);
2820
2821   cb_data->callback = callback;
2822   cb_data->user_data = callback_data;
2823
2824   tpl_log_manager_get_entities_async (manager, account, got_entities, cb_data);
2825
2826   g_object_unref (manager);
2827 }
2828
2829 static void
2830 log_window_logger_clear_account_cb (TpProxy *proxy,
2831     const GError *error,
2832     gpointer user_data,
2833     GObject *weak_object)
2834 {
2835   EmpathyLogWindow *window = user_data;
2836
2837   if (error != NULL)
2838     g_warning ("Error when clearing logs: %s", error->message);
2839
2840   /* Refresh the log viewer so the logs are cleared if the account
2841    * has been deleted */
2842   gtk_tree_store_clear (window->store_events);
2843   log_window_who_populate (window);
2844
2845   /* Re-filter the account chooser so the accounts without logs get greyed out */
2846   empathy_account_chooser_set_filter (
2847       EMPATHY_ACCOUNT_CHOOSER (window->account_chooser),
2848       empathy_account_chooser_filter_has_logs, NULL);
2849 }
2850
2851 static void
2852 log_window_clear_logs_chooser_select_account (EmpathyAccountChooser *chooser,
2853     EmpathyLogWindow *window)
2854 {
2855   EmpathyAccountChooser *account_chooser;
2856
2857   account_chooser = EMPATHY_ACCOUNT_CHOOSER (window->account_chooser);
2858
2859   empathy_account_chooser_set_account (chooser,
2860       empathy_account_chooser_get_account (account_chooser));
2861 }
2862
2863 static void
2864 log_window_delete_menu_clicked_cb (GtkMenuItem *menuitem,
2865     EmpathyLogWindow *window)
2866 {
2867   GtkWidget *dialog, *content_area, *hbox, *label;
2868   EmpathyAccountChooser *account_chooser;
2869   gint response_id;
2870   TpDBusDaemon *bus;
2871   TpProxy *logger;
2872   GError *error = NULL;
2873
2874   account_chooser = (EmpathyAccountChooser *) empathy_account_chooser_new ();
2875   empathy_account_chooser_set_has_all_option (account_chooser, TRUE);
2876   empathy_account_chooser_set_filter (account_chooser,
2877       empathy_account_chooser_filter_has_logs, NULL);
2878
2879   /* Select the same account as in the history window */
2880   if (empathy_account_chooser_is_ready (account_chooser))
2881     log_window_clear_logs_chooser_select_account (account_chooser, window);
2882   else
2883     g_signal_connect (account_chooser, "ready",
2884         G_CALLBACK (log_window_clear_logs_chooser_select_account), window);
2885
2886   dialog = gtk_message_dialog_new_with_markup (GTK_WINDOW (window->window),
2887       GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING,
2888       GTK_BUTTONS_NONE,
2889       _("Are you sure you want to delete all logs of previous conversations?"));
2890
2891   gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2892       GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2893       _("Clear All"), GTK_RESPONSE_APPLY,
2894       NULL);
2895
2896   content_area = gtk_message_dialog_get_message_area (
2897       GTK_MESSAGE_DIALOG (dialog));
2898
2899   hbox = gtk_hbox_new (FALSE, 6);
2900   label = gtk_label_new (_("Delete from:"));
2901   gtk_box_pack_start (GTK_BOX (hbox), label,
2902       FALSE, FALSE, 0);
2903   gtk_box_pack_start (GTK_BOX (hbox), GTK_WIDGET (account_chooser),
2904       FALSE, FALSE, 0);
2905   gtk_box_pack_start (GTK_BOX (content_area), hbox,
2906       FALSE, FALSE, 0);
2907
2908   gtk_widget_show_all (hbox);
2909
2910   response_id = gtk_dialog_run (GTK_DIALOG (dialog));
2911
2912   if (response_id != GTK_RESPONSE_APPLY)
2913     goto out;
2914
2915   bus = tp_dbus_daemon_dup (&error);
2916   if (error != NULL)
2917     {
2918       g_warning ("Could not delete logs: %s", error->message);
2919       g_error_free (error);
2920       goto out;
2921     }
2922
2923   logger = g_object_new (TP_TYPE_PROXY,
2924       "bus-name", "org.freedesktop.Telepathy.Logger",
2925       "object-path", "/org/freedesktop/Telepathy/Logger",
2926       "dbus-daemon", bus,
2927       NULL);
2928   g_object_unref (bus);
2929
2930   tp_proxy_add_interface_by_id (logger, EMP_IFACE_QUARK_LOGGER);
2931
2932   if (empathy_account_chooser_has_all_selected (account_chooser))
2933     {
2934       DEBUG ("Deleting logs for all the accounts");
2935
2936       emp_cli_logger_call_clear (logger, -1,
2937           log_window_logger_clear_account_cb,
2938           window, NULL, G_OBJECT (window->window));
2939     }
2940   else
2941     {
2942       TpAccount *account;
2943
2944       account = empathy_account_chooser_get_account (account_chooser);
2945
2946       DEBUG ("Deleting logs for %s", tp_proxy_get_object_path (account));
2947
2948       emp_cli_logger_call_clear_account (logger, -1,
2949           tp_proxy_get_object_path (account),
2950           log_window_logger_clear_account_cb,
2951           window, NULL, G_OBJECT (window->window));
2952     }
2953
2954   g_object_unref (logger);
2955  out:
2956   gtk_widget_destroy (dialog);
2957 }