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