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