]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-log-window.c
80728a38ff50c25defee9f330982844f5d7d9d6c
[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   log_window_get_selected (self, &accounts, &targets, NULL, NULL,
1951       NULL, NULL);
1952
1953   view = GTK_TREE_VIEW (self->priv->treeview_what);
1954   model = gtk_tree_view_get_model (view);
1955
1956   /* For each event type... */
1957   for (next = gtk_tree_model_get_iter_first (model, &iter);
1958        next;
1959        next = gtk_tree_model_iter_next (model, &iter))
1960     {
1961       TplEventTypeMask type;
1962
1963       gtk_tree_model_get (model, &iter,
1964           COL_WHAT_TYPE, &type,
1965           -1);
1966
1967       /* ...we set the type and its subtypes (if any) unsensitive... */
1968       log_window_update_what_iter_sensitivity (model, &iter, FALSE);
1969
1970       for (acc = accounts, targ = targets;
1971            acc != NULL && targ != NULL;
1972            acc = acc->next, targ = targ->next)
1973         {
1974           TpAccount *account = acc->data;
1975           TplEntity *target = targ->data;
1976
1977           if (tpl_log_manager_exists (self->priv->log_manager,
1978                   account, target, type))
1979             {
1980               /* And then we set it (and its subtypes, again, if any)
1981                * as sensitive if there are logs of that type. */
1982               log_window_update_what_iter_sensitivity (model, &iter, TRUE);
1983               break;
1984             }
1985         }
1986     }
1987
1988   g_list_free_full (accounts, g_object_unref);
1989   g_list_free_full (targets, g_object_unref);
1990 }
1991
1992 static void
1993 log_window_who_changed_cb (GtkTreeSelection *selection,
1994     EmpathyLogWindow *self)
1995 {
1996   GtkTreeView *view;
1997   GtkTreeModel *model;
1998   GtkTreeIter iter;
1999
2000   DEBUG ("log_window_who_changed_cb");
2001
2002   view = gtk_tree_selection_get_tree_view (selection);
2003   model = gtk_tree_view_get_model (view);
2004
2005   if (gtk_tree_model_get_iter_first (model, &iter))
2006     {
2007       /* If 'Anyone' is selected, everything else should be deselected */
2008       if (gtk_tree_selection_iter_is_selected (selection, &iter))
2009         {
2010           g_signal_handlers_block_by_func (selection,
2011               log_window_who_changed_cb,
2012               self);
2013
2014           gtk_tree_selection_unselect_all (selection);
2015           gtk_tree_selection_select_iter (selection, &iter);
2016
2017           g_signal_handlers_unblock_by_func (selection,
2018               log_window_who_changed_cb,
2019               self);
2020         }
2021     }
2022
2023   log_window_update_what_sensitivity (self);
2024   log_window_update_buttons_sensitivity (self);
2025
2026   /* The contact changed, so the dates need to be updated */
2027   log_window_chats_get_messages (self, TRUE);
2028 }
2029
2030 static void
2031 log_manager_got_entities_cb (GObject *manager,
2032     GAsyncResult *result,
2033     gpointer user_data)
2034 {
2035   Ctx                   *ctx = user_data;
2036   GList                 *entities;
2037   GList                 *l;
2038   GtkTreeView           *view;
2039   GtkTreeModel          *model;
2040   GtkTreeSelection      *selection;
2041   GtkListStore          *store;
2042   GtkTreeIter            iter;
2043   GError                *error = NULL;
2044   gboolean               select_account = FALSE;
2045
2046   if (log_window == NULL)
2047     goto out;
2048
2049   if (log_window->priv->count != ctx->count)
2050     goto out;
2051
2052   if (!tpl_log_manager_get_entities_finish (TPL_LOG_MANAGER (manager),
2053       result, &entities, &error))
2054     {
2055       DEBUG ("%s. Aborting", error->message);
2056       g_error_free (error);
2057       goto out;
2058     }
2059
2060   view = GTK_TREE_VIEW (ctx->self->priv->treeview_who);
2061   model = gtk_tree_view_get_model (view);
2062   selection = gtk_tree_view_get_selection (view);
2063   store = GTK_LIST_STORE (model);
2064
2065   /* Block signals to stop the logs being retrieved prematurely  */
2066   g_signal_handlers_block_by_func (selection,
2067       log_window_who_changed_cb, ctx->self);
2068
2069   for (l = entities; l; l = l->next)
2070     {
2071       TplEntity *entity = TPL_ENTITY (l->data);
2072       TplEntityType type = tpl_entity_get_entity_type (entity);
2073       EmpathyContact *contact;
2074       gboolean room = type == TPL_ENTITY_ROOM;
2075
2076       contact = empathy_contact_from_tpl_contact (ctx->account, entity);
2077
2078       gtk_list_store_append (store, &iter);
2079       gtk_list_store_set (store, &iter,
2080           COL_WHO_TYPE, COL_TYPE_NORMAL,
2081           COL_WHO_ICON, room ? EMPATHY_IMAGE_GROUP_MESSAGE
2082                              : EMPATHY_IMAGE_AVATAR_DEFAULT,
2083           COL_WHO_NAME, empathy_contact_get_alias (contact),
2084           COL_WHO_ID, tpl_entity_get_identifier (entity),
2085           COL_WHO_ACCOUNT, ctx->account,
2086           COL_WHO_TARGET, entity,
2087           -1);
2088
2089       g_object_unref (contact);
2090
2091       if (ctx->self->priv->selected_account != NULL &&
2092           !tp_strdiff (tp_proxy_get_object_path (ctx->account),
2093           tp_proxy_get_object_path (ctx->self->priv->selected_account)))
2094         select_account = TRUE;
2095     }
2096   g_list_free_full (entities, g_object_unref);
2097
2098   if (gtk_tree_model_get_iter_first (model, &iter))
2099     {
2100       gint type;
2101
2102       gtk_tree_model_get (model, &iter,
2103           COL_WHO_TYPE, &type,
2104           -1);
2105
2106       if (type != COL_TYPE_ANY)
2107         {
2108           gtk_list_store_prepend (store, &iter);
2109           gtk_list_store_set (store, &iter,
2110               COL_WHO_TYPE, COL_TYPE_SEPARATOR,
2111               COL_WHO_NAME, "separator",
2112               -1);
2113
2114           gtk_list_store_prepend (store, &iter);
2115           gtk_list_store_set (store, &iter,
2116               COL_WHO_TYPE, COL_TYPE_ANY,
2117               COL_WHO_NAME, _("Anyone"),
2118               -1);
2119         }
2120     }
2121
2122   /* Unblock signals */
2123   g_signal_handlers_unblock_by_func (selection,
2124       log_window_who_changed_cb,
2125       ctx->self);
2126
2127   /* We display the selected account if we populate the model with chats from
2128    * this account. */
2129   if (select_account)
2130     log_window_chats_set_selected (ctx->self);
2131
2132 out:
2133   _tpl_action_chain_continue (log_window->priv->chain);
2134   ctx_free (ctx);
2135 }
2136
2137 static void
2138 get_entities_for_account (TplActionChain *chain, gpointer user_data)
2139 {
2140   Ctx *ctx = user_data;
2141
2142   tpl_log_manager_get_entities_async (ctx->self->priv->log_manager, ctx->account,
2143       log_manager_got_entities_cb, ctx);
2144 }
2145
2146 static void
2147 select_first_entity (TplActionChain *chain, gpointer user_data)
2148 {
2149   EmpathyLogWindow *self = user_data;
2150   GtkTreeView *view;
2151   GtkTreeModel *model;
2152   GtkTreeSelection *selection;
2153   GtkTreeIter iter;
2154
2155   view = GTK_TREE_VIEW (self->priv->treeview_who);
2156   model = gtk_tree_view_get_model (view);
2157   selection = gtk_tree_view_get_selection (view);
2158
2159   if (gtk_tree_model_get_iter_first (model, &iter))
2160     gtk_tree_selection_select_iter (selection, &iter);
2161
2162   _tpl_action_chain_continue (self->priv->chain);
2163 }
2164
2165 static void
2166 log_window_who_populate (EmpathyLogWindow *self)
2167 {
2168   EmpathyAccountChooser *account_chooser;
2169   TpAccount *account;
2170   gboolean all_accounts;
2171   GtkTreeView *view;
2172   GtkTreeModel *model;
2173   GtkTreeSelection *selection;
2174   GtkListStore *store;
2175   Ctx *ctx;
2176
2177   if (self->priv->hits != NULL)
2178     {
2179       populate_entities_from_search_hits ();
2180       return;
2181     }
2182
2183   account_chooser = EMPATHY_ACCOUNT_CHOOSER (self->priv->account_chooser);
2184   account = empathy_account_chooser_dup_account (account_chooser);
2185   all_accounts = empathy_account_chooser_has_all_selected (account_chooser);
2186
2187   view = GTK_TREE_VIEW (self->priv->treeview_who);
2188   model = gtk_tree_view_get_model (view);
2189   selection = gtk_tree_view_get_selection (view);
2190   store = GTK_LIST_STORE (model);
2191
2192   /* Block signals to stop the logs being retrieved prematurely  */
2193   g_signal_handlers_block_by_func (selection,
2194       log_window_who_changed_cb,
2195       self);
2196
2197   gtk_list_store_clear (store);
2198
2199   /* Unblock signals */
2200   g_signal_handlers_unblock_by_func (selection,
2201       log_window_who_changed_cb,
2202       self);
2203
2204   _tpl_action_chain_clear (self->priv->chain);
2205   self->priv->count++;
2206
2207   if (!all_accounts && account == NULL)
2208     {
2209       return;
2210     }
2211   else if (!all_accounts)
2212     {
2213       ctx = ctx_new (self, account, NULL, NULL, 0, 0, self->priv->count);
2214       _tpl_action_chain_append (self->priv->chain, get_entities_for_account, ctx);
2215     }
2216   else
2217     {
2218       TpAccountManager *manager;
2219       GList *accounts, *l;
2220
2221       manager = empathy_account_chooser_get_account_manager (account_chooser);
2222       accounts = tp_account_manager_get_valid_accounts (manager);
2223
2224       for (l = accounts; l != NULL; l = l->next)
2225         {
2226           account = l->data;
2227
2228           ctx = ctx_new (self, account, NULL, NULL, 0, 0, self->priv->count);
2229           _tpl_action_chain_append (self->priv->chain,
2230               get_entities_for_account, ctx);
2231         }
2232
2233       g_list_free (accounts);
2234     }
2235   _tpl_action_chain_append (self->priv->chain, select_first_entity, self);
2236   _tpl_action_chain_start (self->priv->chain);
2237 }
2238
2239 static gint
2240 sort_by_name (GtkTreeModel *model,
2241     GtkTreeIter *a,
2242     GtkTreeIter *b,
2243     gpointer user_data)
2244 {
2245   gchar *name1, *name2;
2246   gint type1, type2;
2247   gint ret;
2248
2249   gtk_tree_model_get (model, a,
2250       COL_WHO_TYPE, &type1,
2251       COL_WHO_NAME, &name1,
2252       -1);
2253
2254   gtk_tree_model_get (model, b,
2255       COL_WHO_TYPE, &type2,
2256       COL_WHO_NAME, &name2,
2257       -1);
2258
2259   if (type1 == COL_TYPE_ANY)
2260     ret = -1;
2261   else if (type2 == COL_TYPE_ANY)
2262     ret = 1;
2263   else if (type1 == COL_TYPE_SEPARATOR)
2264     ret = -1;
2265   else if (type2 == COL_TYPE_SEPARATOR)
2266     ret = 1;
2267   else
2268     ret = g_strcmp0 (name1, name2);
2269
2270   g_free (name1);
2271   g_free (name2);
2272
2273   return ret;
2274 }
2275
2276 static gboolean
2277 who_row_is_separator (GtkTreeModel *model,
2278     GtkTreeIter *iter,
2279     gpointer data)
2280 {
2281   gint type;
2282
2283   gtk_tree_model_get (model, iter,
2284       COL_WHO_TYPE, &type,
2285       -1);
2286
2287   return (type == COL_TYPE_SEPARATOR);
2288 }
2289
2290 static void
2291 log_window_events_changed_cb (GtkTreeSelection *selection,
2292     EmpathyLogWindow *self)
2293 {
2294   DEBUG ("log_window_events_changed_cb");
2295
2296   log_window_update_buttons_sensitivity (self);
2297 }
2298
2299 static void
2300 log_window_events_row_activated_cb (GtkTreeView *view,
2301     GtkTreePath *path,
2302     GtkTreeViewColumn *column,
2303     EmpathyLogWindow *self)
2304 {
2305   if (gtk_tree_view_row_expanded (view, path))
2306     gtk_tree_view_collapse_row (view, path);
2307   else
2308     gtk_tree_view_expand_row (view, path, FALSE);
2309 }
2310
2311 static void
2312 log_window_events_setup (EmpathyLogWindow *self)
2313 {
2314   GtkTreeView       *view;
2315   GtkTreeModel      *model;
2316   GtkTreeSelection  *selection;
2317   GtkTreeSortable   *sortable;
2318   GtkTreeViewColumn *column;
2319   GtkTreeStore      *store;
2320   GtkCellRenderer   *cell;
2321
2322   view = GTK_TREE_VIEW (self->priv->treeview_events);
2323   selection = gtk_tree_view_get_selection (view);
2324
2325   /* new store */
2326   self->priv->store_events = store = gtk_tree_store_new (COL_EVENTS_COUNT,
2327       G_TYPE_INT,           /* type */
2328       G_TYPE_INT64,         /* timestamp */
2329       G_TYPE_STRING,        /* stringified date */
2330       G_TYPE_STRING,        /* icon */
2331       G_TYPE_STRING,        /* name */
2332       TP_TYPE_ACCOUNT,      /* account */
2333       TPL_TYPE_ENTITY,      /* target */
2334       TPL_TYPE_EVENT);      /* event */
2335
2336   model = GTK_TREE_MODEL (store);
2337   sortable = GTK_TREE_SORTABLE (store);
2338
2339   gtk_tree_view_set_model (view, model);
2340
2341   /* new column */
2342   column = gtk_tree_view_column_new ();
2343
2344   cell = gtk_cell_renderer_pixbuf_new ();
2345   gtk_tree_view_column_pack_start (column, cell, FALSE);
2346   gtk_tree_view_column_add_attribute (column, cell,
2347       "icon-name", COL_EVENTS_ICON);
2348
2349   cell = gtk_cell_renderer_text_new ();
2350   gtk_tree_view_column_pack_start (column, cell, TRUE);
2351   gtk_tree_view_column_add_attribute (column, cell,
2352       "markup", COL_EVENTS_TEXT);
2353
2354   cell = gtk_cell_renderer_text_new ();
2355   g_object_set (cell, "xalign", 1.0, NULL);
2356   gtk_tree_view_column_pack_end (column, cell, FALSE);
2357   gtk_tree_view_column_add_attribute (column, cell,
2358       "text", COL_EVENTS_PRETTY_DATE);
2359
2360   gtk_tree_view_append_column (view, column);
2361
2362   /* set up treeview properties */
2363   gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
2364   gtk_tree_view_set_headers_visible (view, FALSE);
2365
2366   gtk_tree_sortable_set_sort_column_id (sortable,
2367       COL_EVENTS_TS,
2368       GTK_SORT_ASCENDING);
2369
2370   gtk_tree_view_set_enable_search (view, FALSE);
2371
2372   /* set up signals */
2373   g_signal_connect (selection, "changed",
2374       G_CALLBACK (log_window_events_changed_cb),
2375       self);
2376
2377   g_signal_connect (view, "row-activated",
2378       G_CALLBACK (log_window_events_row_activated_cb),
2379       self);
2380
2381   g_object_unref (store);
2382 }
2383
2384 static void
2385 log_window_who_setup (EmpathyLogWindow *self)
2386 {
2387   GtkTreeView       *view;
2388   GtkTreeModel      *model;
2389   GtkTreeSelection  *selection;
2390   GtkTreeSortable   *sortable;
2391   GtkTreeViewColumn *column;
2392   GtkListStore      *store;
2393   GtkCellRenderer   *cell;
2394
2395   view = GTK_TREE_VIEW (self->priv->treeview_who);
2396   selection = gtk_tree_view_get_selection (view);
2397
2398   /* new store */
2399   store = gtk_list_store_new (COL_WHO_COUNT,
2400       G_TYPE_INT,           /* type */
2401       G_TYPE_STRING,        /* icon */
2402       G_TYPE_STRING,        /* name */
2403       G_TYPE_STRING,        /* id */
2404       TP_TYPE_ACCOUNT,      /* account */
2405       TPL_TYPE_ENTITY);     /* target */
2406
2407   model = GTK_TREE_MODEL (store);
2408   sortable = GTK_TREE_SORTABLE (store);
2409
2410   gtk_tree_view_set_model (view, model);
2411
2412   /* new column */
2413   column = gtk_tree_view_column_new ();
2414   gtk_tree_view_column_set_title (column, _("Who"));
2415
2416   cell = gtk_cell_renderer_pixbuf_new ();
2417   gtk_tree_view_column_pack_start (column, cell, FALSE);
2418   gtk_tree_view_column_add_attribute (column, cell,
2419       "icon-name",
2420       COL_WHO_ICON);
2421
2422   cell = gtk_cell_renderer_text_new ();
2423   g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
2424   gtk_tree_view_column_pack_start (column, cell, TRUE);
2425   gtk_tree_view_column_add_attribute (column, cell,
2426       "text",
2427       COL_WHO_NAME);
2428
2429   gtk_tree_view_append_column (view, column);
2430
2431   /* set up treeview properties */
2432   gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
2433   gtk_tree_view_set_row_separator_func (view, who_row_is_separator,
2434       NULL, NULL);
2435
2436   gtk_tree_sortable_set_sort_column_id (sortable,
2437       COL_WHO_NAME,
2438       GTK_SORT_ASCENDING);
2439   gtk_tree_sortable_set_sort_func (sortable,
2440       COL_WHO_NAME, sort_by_name,
2441       NULL, NULL);
2442
2443   gtk_tree_view_set_search_column (view, COL_WHO_NAME);
2444   gtk_tree_view_set_tooltip_column (view, COL_WHO_ID);
2445
2446   /* set up signals */
2447   g_signal_connect (selection, "changed",
2448       G_CALLBACK (log_window_who_changed_cb), self);
2449
2450   g_object_unref (store);
2451 }
2452
2453 static void
2454 log_window_chats_accounts_changed_cb (GtkWidget *combobox,
2455     EmpathyLogWindow *self)
2456 {
2457   /* Clear all current messages shown in the textview */
2458   gtk_tree_store_clear (self->priv->store_events);
2459
2460   log_window_who_populate (self);
2461 }
2462
2463 static void
2464 log_window_chats_set_selected (EmpathyLogWindow *self)
2465 {
2466   GtkTreeView          *view;
2467   GtkTreeModel         *model;
2468   GtkTreeSelection     *selection;
2469   GtkTreeIter           iter;
2470   GtkTreePath          *path;
2471   gboolean              next;
2472
2473   view = GTK_TREE_VIEW (self->priv->treeview_who);
2474   model = gtk_tree_view_get_model (view);
2475   selection = gtk_tree_view_get_selection (view);
2476
2477   for (next = gtk_tree_model_get_iter_first (model, &iter);
2478        next;
2479        next = gtk_tree_model_iter_next (model, &iter))
2480     {
2481       TpAccount   *this_account;
2482       TplEntity   *this_target;
2483       const gchar *this_chat_id;
2484       gboolean     this_is_chatroom;
2485       gint         this_type;
2486
2487       gtk_tree_model_get (model, &iter,
2488           COL_WHO_TYPE, &this_type,
2489           COL_WHO_ACCOUNT, &this_account,
2490           COL_WHO_TARGET, &this_target,
2491           -1);
2492
2493       if (this_type != COL_TYPE_NORMAL)
2494         continue;
2495
2496       this_chat_id = tpl_entity_get_identifier (this_target);
2497       this_is_chatroom = tpl_entity_get_entity_type (this_target)
2498           == TPL_ENTITY_ROOM;
2499
2500       if (this_account == self->priv->selected_account &&
2501           !tp_strdiff (this_chat_id, self->priv->selected_chat_id) &&
2502           this_is_chatroom == self->priv->selected_is_chatroom)
2503         {
2504           gtk_tree_selection_select_iter (selection, &iter);
2505           path = gtk_tree_model_get_path (model, &iter);
2506           gtk_tree_view_scroll_to_cell (view, path, NULL, TRUE, 0.5, 0.0);
2507           gtk_tree_path_free (path);
2508           g_object_unref (this_account);
2509           g_object_unref (this_target);
2510           break;
2511         }
2512
2513       g_object_unref (this_account);
2514       g_object_unref (this_target);
2515     }
2516
2517   tp_clear_object (&self->priv->selected_account);
2518   tp_clear_pointer (&self->priv->selected_chat_id, g_free);
2519 }
2520
2521 static gint
2522 sort_by_date (GtkTreeModel *model,
2523     GtkTreeIter *a,
2524     GtkTreeIter *b,
2525     gpointer user_data)
2526 {
2527   GDate *date1, *date2;
2528
2529   gtk_tree_model_get (model, a,
2530       COL_WHEN_DATE, &date1,
2531       -1);
2532
2533   gtk_tree_model_get (model, b,
2534       COL_WHEN_DATE, &date2,
2535       -1);
2536
2537   return g_date_compare (date1, date2);
2538 }
2539
2540 static gboolean
2541 when_row_is_separator (GtkTreeModel *model,
2542     GtkTreeIter *iter,
2543     gpointer data)
2544 {
2545   gchar *when;
2546   gboolean ret;
2547
2548   gtk_tree_model_get (model, iter,
2549       COL_WHEN_TEXT, &when,
2550       -1);
2551
2552   ret = g_str_equal (when, "separator");
2553   g_free (when);
2554   return ret;
2555 }
2556
2557 static void
2558 log_window_when_changed_cb (GtkTreeSelection *selection,
2559     EmpathyLogWindow *self)
2560 {
2561   GtkTreeView *view;
2562   GtkTreeModel *model;
2563   GtkTreeIter iter;
2564
2565   DEBUG ("log_window_when_changed_cb");
2566
2567   view = gtk_tree_selection_get_tree_view (selection);
2568   model = gtk_tree_view_get_model (view);
2569
2570   /* If 'Anytime' is selected, everything else should be deselected */
2571   if (gtk_tree_model_get_iter_first (model, &iter))
2572     {
2573       if (gtk_tree_selection_iter_is_selected (selection, &iter))
2574         {
2575           g_signal_handlers_block_by_func (selection,
2576               log_window_when_changed_cb,
2577               self);
2578
2579           gtk_tree_selection_unselect_all (selection);
2580           gtk_tree_selection_select_iter (selection, &iter);
2581
2582           g_signal_handlers_unblock_by_func (selection,
2583               log_window_when_changed_cb,
2584               self);
2585         }
2586     }
2587
2588   log_window_chats_get_messages (self, FALSE);
2589 }
2590
2591 static void
2592 log_window_when_setup (EmpathyLogWindow *self)
2593 {
2594   GtkTreeView       *view;
2595   GtkTreeModel      *model;
2596   GtkTreeSelection  *selection;
2597   GtkTreeSortable   *sortable;
2598   GtkTreeViewColumn *column;
2599   GtkListStore      *store;
2600   GtkCellRenderer   *cell;
2601
2602   view = GTK_TREE_VIEW (self->priv->treeview_when);
2603   selection = gtk_tree_view_get_selection (view);
2604
2605   /* new store */
2606   store = gtk_list_store_new (COL_WHEN_COUNT,
2607       G_TYPE_DATE,        /* date */
2608       G_TYPE_STRING,      /* stringified date */
2609       G_TYPE_STRING);     /* icon */
2610
2611   model = GTK_TREE_MODEL (store);
2612   sortable = GTK_TREE_SORTABLE (store);
2613
2614   gtk_tree_view_set_model (view, model);
2615
2616   /* new column */
2617   column = gtk_tree_view_column_new ();
2618   gtk_tree_view_column_set_title (column, _("When"));
2619
2620   cell = gtk_cell_renderer_pixbuf_new ();
2621   gtk_tree_view_column_pack_start (column, cell, FALSE);
2622   gtk_tree_view_column_add_attribute (column, cell,
2623       "icon-name", COL_WHEN_ICON);
2624
2625   cell = gtk_cell_renderer_text_new ();
2626   g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
2627   gtk_tree_view_column_pack_start (column, cell, TRUE);
2628   gtk_tree_view_column_add_attribute (column, cell,
2629       "text",
2630       COL_WHEN_TEXT);
2631
2632   gtk_tree_view_append_column (view, column);
2633
2634   /* set up treeview properties */
2635   gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
2636   gtk_tree_view_set_row_separator_func (view, when_row_is_separator,
2637       NULL, NULL);
2638   gtk_tree_sortable_set_sort_column_id (sortable,
2639       COL_WHEN_DATE,
2640       GTK_SORT_DESCENDING);
2641   gtk_tree_sortable_set_sort_func (sortable,
2642       COL_WHEN_DATE, sort_by_date,
2643       NULL, NULL);
2644
2645   gtk_tree_view_set_search_column (view, COL_WHEN_TEXT);
2646
2647   /* set up signals */
2648   g_signal_connect (selection, "changed",
2649       G_CALLBACK (log_window_when_changed_cb),
2650       self);
2651
2652   g_object_unref (store);
2653 }
2654
2655 static gboolean
2656 what_row_is_separator (GtkTreeModel *model,
2657     GtkTreeIter *iter,
2658     gpointer data)
2659 {
2660   gint type;
2661
2662   gtk_tree_model_get (model, iter,
2663       COL_WHAT_TYPE, &type,
2664       -1);
2665
2666   return (type == WHAT_TYPE_SEPARATOR);
2667 }
2668
2669 static void
2670 log_window_what_changed_cb (GtkTreeSelection *selection,
2671     EmpathyLogWindow *self)
2672 {
2673   GtkTreeView *view;
2674   GtkTreeModel *model;
2675   GtkTreeIter iter;
2676
2677   DEBUG ("log_window_what_changed_cb");
2678
2679   view = gtk_tree_selection_get_tree_view (selection);
2680   model = gtk_tree_view_get_model (view);
2681
2682   /* If 'Anything' is selected, everything else should be deselected */
2683   if (gtk_tree_model_get_iter_first (model, &iter))
2684     {
2685       if (gtk_tree_selection_iter_is_selected (selection, &iter))
2686         {
2687           g_signal_handlers_block_by_func (selection,
2688               log_window_what_changed_cb,
2689               self);
2690
2691           gtk_tree_selection_unselect_all (selection);
2692           gtk_tree_selection_select_iter (selection, &iter);
2693
2694           g_signal_handlers_unblock_by_func (selection,
2695               log_window_what_changed_cb,
2696               self);
2697         }
2698     }
2699
2700   /* The dates need to be updated if we're not searching */
2701   log_window_chats_get_messages (self, self->priv->hits == NULL);
2702 }
2703
2704 static gboolean
2705 log_window_what_collapse_row_cb (GtkTreeView *tree_view,
2706     GtkTreeIter *iter,
2707     GtkTreePath *path,
2708     gpointer user_data)
2709 {
2710   /* Reject collapsing */
2711   return TRUE;
2712 }
2713
2714 struct event
2715 {
2716   gint type;
2717   EventSubtype subtype;
2718   const gchar *icon;
2719   const gchar *text;
2720 };
2721
2722 static void
2723 log_window_what_setup (EmpathyLogWindow *self)
2724 {
2725   GtkTreeView       *view;
2726   GtkTreeModel      *model;
2727   GtkTreeSelection  *selection;
2728   GtkTreeViewColumn *column;
2729   GtkTreeIter        iter;
2730   GtkTreeStore      *store;
2731   GtkCellRenderer   *cell;
2732   guint i;
2733   struct event events [] = {
2734     { TPL_EVENT_MASK_ANY, 0, NULL, _("Anything") },
2735     { WHAT_TYPE_SEPARATOR, 0, NULL, "separator" },
2736     { TPL_EVENT_MASK_TEXT, 0, "stock_text_justify", _("Text chats") },
2737 #ifdef HAVE_CALL_LOGS
2738     { TPL_EVENT_MASK_CALL, EVENT_CALL_ALL, EMPATHY_IMAGE_CALL, _("Calls") },
2739 #endif
2740   };
2741 #ifdef HAVE_CALL_LOGS
2742   struct event call_events [] = {
2743     { TPL_EVENT_MASK_CALL, EVENT_CALL_INCOMING, EMPATHY_IMAGE_CALL_INCOMING, _("Incoming calls") },
2744     { TPL_EVENT_MASK_CALL, EVENT_CALL_OUTGOING, EMPATHY_IMAGE_CALL_OUTGOING, _("Outgoing calls") },
2745     { TPL_EVENT_MASK_CALL, EVENT_CALL_MISSED, EMPATHY_IMAGE_CALL_MISSED, _("Missed calls") }
2746   };
2747   GtkTreeIter parent;
2748 #endif
2749
2750   view = GTK_TREE_VIEW (self->priv->treeview_what);
2751   selection = gtk_tree_view_get_selection (view);
2752
2753   /* new store */
2754   store = gtk_tree_store_new (COL_WHAT_COUNT,
2755       G_TYPE_INT,         /* history type */
2756       G_TYPE_INT,         /* history subtype */
2757       G_TYPE_BOOLEAN,     /* sensitive */
2758       G_TYPE_STRING,      /* stringified history type */
2759       G_TYPE_STRING);     /* icon */
2760
2761   model = GTK_TREE_MODEL (store);
2762
2763   gtk_tree_view_set_model (view, model);
2764
2765   /* new column */
2766   column = gtk_tree_view_column_new ();
2767   gtk_tree_view_column_set_title (column, _("What"));
2768
2769   cell = gtk_cell_renderer_pixbuf_new ();
2770   gtk_tree_view_column_pack_start (column, cell, FALSE);
2771   gtk_tree_view_column_add_attribute (column, cell,
2772       "icon-name", COL_WHAT_ICON);
2773
2774   cell = gtk_cell_renderer_text_new ();
2775   g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
2776   gtk_tree_view_column_pack_start (column, cell, TRUE);
2777   gtk_tree_view_column_add_attribute (column, cell,
2778       "text", COL_WHAT_TEXT);
2779   gtk_tree_view_column_add_attribute (column, cell,
2780       "sensitive", COL_WHAT_SENSITIVE);
2781
2782   gtk_tree_view_append_column (view, column);
2783   gtk_tree_view_set_search_column (view, COL_WHAT_TEXT);
2784
2785   /* set up treeview properties */
2786   gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
2787   gtk_tree_view_set_show_expanders (view, FALSE);
2788   gtk_tree_view_set_level_indentation (view, 12);
2789   gtk_tree_view_expand_all (view);
2790   gtk_tree_view_set_row_separator_func (view, what_row_is_separator,
2791       NULL, NULL);
2792
2793   /* populate */
2794   for (i = 0; i < G_N_ELEMENTS (events); i++)
2795     {
2796       gtk_tree_store_append (store, &iter, NULL);
2797       gtk_tree_store_set (store, &iter,
2798           COL_WHAT_TYPE, events[i].type,
2799           COL_WHAT_SUBTYPE, events[i].subtype,
2800           COL_WHAT_SENSITIVE, TRUE,
2801           COL_WHAT_TEXT, events[i].text,
2802           COL_WHAT_ICON, events[i].icon,
2803           -1);
2804     }
2805
2806 #ifdef HAVE_CALL_LOGS
2807   gtk_tree_model_iter_nth_child (model, &parent, NULL, 3);
2808   for (i = 0; i < G_N_ELEMENTS (call_events); i++)
2809     {
2810       gtk_tree_store_append (store, &iter, &parent);
2811       gtk_tree_store_set (store, &iter,
2812           COL_WHAT_TYPE, call_events[i].type,
2813           COL_WHAT_SUBTYPE, call_events[i].subtype,
2814           COL_WHAT_SENSITIVE, TRUE,
2815           COL_WHAT_TEXT, call_events[i].text,
2816           COL_WHAT_ICON, call_events[i].icon,
2817           -1);
2818     }
2819 #endif
2820
2821   gtk_tree_view_expand_all (view);
2822
2823   /* select 'Anything' */
2824   if (gtk_tree_model_get_iter_first (model, &iter))
2825     gtk_tree_selection_select_iter (selection, &iter);
2826
2827   /* set up signals */
2828   g_signal_connect (view, "test-collapse-row",
2829       G_CALLBACK (log_window_what_collapse_row_cb),
2830       NULL);
2831   g_signal_connect (selection, "changed",
2832       G_CALLBACK (log_window_what_changed_cb),
2833       self);
2834
2835   g_object_unref (store);
2836 }
2837
2838 static void
2839 log_window_maybe_expand_events (void)
2840 {
2841   GtkTreeView       *view;
2842   GtkTreeModel      *model;
2843
2844   view = GTK_TREE_VIEW (log_window->priv->treeview_events);
2845   model = gtk_tree_view_get_model (view);
2846
2847   /* If there's only one result, expand it */
2848   if (gtk_tree_model_iter_n_children (model, NULL) == 1)
2849     gtk_tree_view_expand_all (view);
2850 }
2851
2852 static gboolean
2853 show_spinner (gpointer data)
2854 {
2855   gboolean active;
2856
2857   if (log_window == NULL)
2858     return FALSE;
2859
2860   g_object_get (log_window->priv->spinner, "active", &active, NULL);
2861
2862   if (active)
2863     gtk_notebook_set_current_page (GTK_NOTEBOOK (log_window->priv->notebook),
2864         PAGE_SPINNER);
2865
2866   return FALSE;
2867 }
2868
2869 static void
2870 show_events (TplActionChain *chain,
2871     gpointer user_data)
2872 {
2873   log_window_maybe_expand_events ();
2874   gtk_spinner_stop (GTK_SPINNER (log_window->priv->spinner));
2875   gtk_notebook_set_current_page (GTK_NOTEBOOK (log_window->priv->notebook),
2876       PAGE_EVENTS);
2877
2878   _tpl_action_chain_continue (chain);
2879 }
2880
2881 static void
2882 start_spinner (void)
2883 {
2884   gtk_spinner_start (GTK_SPINNER (log_window->priv->spinner));
2885   gtk_notebook_set_current_page (GTK_NOTEBOOK (log_window->priv->notebook),
2886       PAGE_EMPTY);
2887
2888   g_timeout_add (1000, show_spinner, NULL);
2889   _tpl_action_chain_append (log_window->priv->chain, show_events, NULL);
2890 }
2891
2892 static void
2893 log_window_got_messages_for_date_cb (GObject *manager,
2894     GAsyncResult *result,
2895     gpointer user_data)
2896 {
2897   Ctx *ctx = user_data;
2898   GtkTreeView *view;
2899   GtkTreeModel *model;
2900   GtkTreeIter iter;
2901   GList *events;
2902   GList *l;
2903   GError *error = NULL;
2904   gint n;
2905
2906   if (log_window == NULL)
2907     {
2908       ctx_free (ctx);
2909       return;
2910     }
2911
2912   if (log_window->priv->count != ctx->count)
2913     goto out;
2914
2915   if (!tpl_log_manager_get_events_for_date_finish (TPL_LOG_MANAGER (manager),
2916       result, &events, &error))
2917     {
2918       DEBUG ("Unable to retrieve messages for the selected date: %s. Aborting",
2919           error->message);
2920       g_error_free (error);
2921       goto out;
2922     }
2923
2924   for (l = events; l; l = l->next)
2925     {
2926       TplEvent *event = l->data;
2927       gboolean append = TRUE;
2928
2929 #ifdef HAVE_CALL_LOGS
2930       if (TPL_IS_CALL_EVENT (l->data)
2931           && ctx->event_mask & TPL_EVENT_MASK_CALL
2932           && ctx->event_mask != TPL_EVENT_MASK_ANY)
2933         {
2934           TplCallEvent *call = l->data;
2935
2936           append = FALSE;
2937
2938           if (ctx->subtype & EVENT_CALL_ALL)
2939             {
2940               append = TRUE;
2941             }
2942           else
2943             {
2944               TplCallEndReason reason = tpl_call_event_get_end_reason (call);
2945               TplEntity *sender = tpl_event_get_sender (event);
2946               TplEntity *receiver = tpl_event_get_receiver (event);
2947
2948               if (reason == TPL_CALL_END_REASON_NO_ANSWER)
2949                 {
2950                   if (ctx->subtype & EVENT_CALL_MISSED)
2951                     append = TRUE;
2952                 }
2953               else if (ctx->subtype & EVENT_CALL_OUTGOING
2954                   && tpl_entity_get_entity_type (sender) == TPL_ENTITY_SELF)
2955                 {
2956                   append = TRUE;
2957                 }
2958               else if (ctx->subtype & EVENT_CALL_INCOMING
2959                   && tpl_entity_get_entity_type (receiver) == TPL_ENTITY_SELF)
2960                 {
2961                   append = TRUE;
2962                 }
2963             }
2964         }
2965 #endif
2966
2967       if (append)
2968         {
2969           EmpathyMessage *msg = empathy_message_from_tpl_log_event (event);
2970           log_window_append_message (event, msg);
2971           tp_clear_object (&msg);
2972         }
2973
2974       g_object_unref (event);
2975     }
2976   g_list_free (events);
2977
2978   view = GTK_TREE_VIEW (log_window->priv->treeview_events);
2979   model = gtk_tree_view_get_model (view);
2980   n = gtk_tree_model_iter_n_children (model, NULL) - 1;
2981
2982   if (n >= 0 && gtk_tree_model_iter_nth_child (model, &iter, NULL, n))
2983     {
2984       GtkTreePath *path;
2985
2986       path = gtk_tree_model_get_path (model, &iter);
2987       gtk_tree_view_scroll_to_cell (view, path, NULL, FALSE, 0, 0);
2988       gtk_tree_path_free (path);
2989     }
2990
2991  out:
2992   ctx_free (ctx);
2993
2994   _tpl_action_chain_continue (log_window->priv->chain);
2995 }
2996
2997 static void
2998 get_events_for_date (TplActionChain *chain, gpointer user_data)
2999 {
3000   Ctx *ctx = user_data;
3001
3002   tpl_log_manager_get_events_for_date_async (ctx->self->priv->log_manager,
3003       ctx->account, ctx->entity, ctx->event_mask,
3004       ctx->date,
3005       log_window_got_messages_for_date_cb,
3006       ctx);
3007 }
3008
3009 static void
3010 log_window_get_messages_for_dates (EmpathyLogWindow *self,
3011     GList *dates)
3012 {
3013   GList *accounts, *targets, *acc, *targ, *l;
3014   TplEventTypeMask event_mask;
3015   EventSubtype subtype;
3016   GDate *date, *anytime, *separator;
3017
3018   if (!log_window_get_selected (self,
3019       &accounts, &targets, NULL, NULL, &event_mask, &subtype))
3020     return;
3021
3022   anytime = g_date_new_dmy (2, 1, -1);
3023   separator = g_date_new_dmy (1, 1, -1);
3024
3025   _tpl_action_chain_clear (self->priv->chain);
3026   self->priv->count++;
3027
3028   for (acc = accounts, targ = targets;
3029        acc != NULL && targ != NULL;
3030        acc = acc->next, targ = targ->next)
3031     {
3032       TpAccount *account = acc->data;
3033       TplEntity *target = targ->data;
3034
3035       for (l = dates; l != NULL; l = l->next)
3036         {
3037           date = l->data;
3038
3039           /* Get events */
3040           if (g_date_compare (date, anytime) != 0)
3041             {
3042               Ctx *ctx;
3043
3044               ctx = ctx_new (self, account, target, date, event_mask, subtype,
3045                   self->priv->count);
3046               _tpl_action_chain_append (self->priv->chain, get_events_for_date, ctx);
3047             }
3048           else
3049             {
3050               GtkTreeView *view = GTK_TREE_VIEW (self->priv->treeview_when);
3051               GtkTreeModel *model = gtk_tree_view_get_model (view);
3052               GtkTreeIter iter;
3053               gboolean next;
3054               GDate *d;
3055
3056               for (next = gtk_tree_model_get_iter_first (model, &iter);
3057                    next;
3058                    next = gtk_tree_model_iter_next (model, &iter))
3059                 {
3060                   Ctx *ctx;
3061
3062                   gtk_tree_model_get (model, &iter,
3063                       COL_WHEN_DATE, &d,
3064                       -1);
3065
3066                   if (g_date_compare (d, anytime) != 0 &&
3067                       g_date_compare (d, separator) != 0)
3068                     {
3069                       ctx = ctx_new (self, account, target, d,
3070                           event_mask, subtype, self->priv->count);
3071                       _tpl_action_chain_append (self->priv->chain, get_events_for_date, ctx);
3072                     }
3073                 }
3074             }
3075         }
3076     }
3077
3078   start_spinner ();
3079   _tpl_action_chain_start (self->priv->chain);
3080
3081   g_list_free_full (accounts, g_object_unref);
3082   g_list_free_full (targets, g_object_unref);
3083   g_date_free (separator);
3084   g_date_free (anytime);
3085 }
3086
3087 static void
3088 log_manager_got_dates_cb (GObject *manager,
3089     GAsyncResult *result,
3090     gpointer user_data)
3091 {
3092   Ctx *ctx = user_data;
3093   GtkTreeView *view;
3094   GtkTreeModel *model;
3095   GtkListStore *store;
3096   GtkTreeIter iter;
3097   GList *dates;
3098   GList *l;
3099   GError *error = NULL;
3100
3101   if (log_window == NULL)
3102     goto out;
3103
3104   if (log_window->priv->count != ctx->count)
3105     goto out;
3106
3107   if (!tpl_log_manager_get_dates_finish (TPL_LOG_MANAGER (manager),
3108        result, &dates, &error))
3109     {
3110       DEBUG ("Unable to retrieve messages' dates: %s. Aborting",
3111           error->message);
3112       goto out;
3113     }
3114
3115   view = GTK_TREE_VIEW (log_window->priv->treeview_when);
3116   model = gtk_tree_view_get_model (view);
3117   store = GTK_LIST_STORE (model);
3118
3119   for (l = dates; l != NULL; l = l->next)
3120     {
3121       GDate *date = l->data;
3122
3123       /* Add the date if it's not already there */
3124       has_element = FALSE;
3125       gtk_tree_model_foreach (model, model_has_date, date);
3126       if (!has_element)
3127         {
3128           gchar *text = format_date_for_display (date);
3129
3130           gtk_list_store_append (store, &iter);
3131           gtk_list_store_set (store, &iter,
3132               COL_WHEN_DATE, date,
3133               COL_WHEN_TEXT, text,
3134               COL_WHEN_ICON, CALENDAR_ICON,
3135               -1);
3136
3137           g_free (text);
3138         }
3139     }
3140
3141   if (gtk_tree_model_get_iter_first (model, &iter))
3142     {
3143       gchar *separator = NULL;
3144
3145       if (gtk_tree_model_iter_next (model, &iter))
3146         {
3147           gtk_tree_model_get (model, &iter,
3148               COL_WHEN_TEXT, &separator,
3149               -1);
3150         }
3151
3152       if (g_strcmp0 (separator, "separator") != 0)
3153         {
3154           gtk_list_store_prepend (store, &iter);
3155           gtk_list_store_set (store, &iter,
3156               COL_WHEN_DATE, g_date_new_dmy (1, 1, -1),
3157               COL_WHEN_TEXT, "separator",
3158               -1);
3159
3160           gtk_list_store_prepend (store, &iter);
3161           gtk_list_store_set (store, &iter,
3162               COL_WHEN_DATE, g_date_new_dmy (2, 1, -1),
3163               COL_WHEN_TEXT, _("Anytime"),
3164               -1);
3165         }
3166     }
3167
3168   g_list_free_full (dates, g_free);
3169  out:
3170   ctx_free (ctx);
3171   _tpl_action_chain_continue (log_window->priv->chain);
3172 }
3173
3174 static void
3175 select_date (TplActionChain *chain, gpointer user_data)
3176 {
3177   GtkTreeView *view;
3178   GtkTreeModel *model;
3179   GtkTreeSelection *selection;
3180   GtkTreeIter iter;
3181   gboolean next;
3182   gboolean selected = FALSE;
3183
3184   view = GTK_TREE_VIEW (log_window->priv->treeview_when);
3185   model = gtk_tree_view_get_model (view);
3186   selection = gtk_tree_view_get_selection (view);
3187
3188   if (log_window->priv->current_dates != NULL)
3189     {
3190       for (next = gtk_tree_model_get_iter_first (model, &iter);
3191            next;
3192            next = gtk_tree_model_iter_next (model, &iter))
3193         {
3194           GDate *date;
3195
3196           gtk_tree_model_get (model, &iter,
3197               COL_WHEN_DATE, &date,
3198               -1);
3199
3200           if (g_list_find_custom (log_window->priv->current_dates, date,
3201                   (GCompareFunc) g_date_compare) != NULL)
3202             {
3203               GtkTreePath *path;
3204
3205               gtk_tree_selection_select_iter (selection, &iter);
3206               path = gtk_tree_model_get_path (model, &iter);
3207               gtk_tree_view_scroll_to_cell (view, path, NULL, FALSE, 0, 0);
3208               selected = TRUE;
3209
3210               gtk_tree_path_free (path);
3211             }
3212
3213           g_date_free (date);
3214         }
3215     }
3216
3217   if (!selected)
3218     {
3219       /* Show messages of the most recent date */
3220       if (gtk_tree_model_iter_nth_child (model, &iter, NULL, 2))
3221         gtk_tree_selection_select_iter (selection, &iter);
3222     }
3223
3224   _tpl_action_chain_continue (log_window->priv->chain);
3225 }
3226
3227 static void
3228 get_dates_for_entity (TplActionChain *chain, gpointer user_data)
3229 {
3230   Ctx *ctx = user_data;
3231
3232   tpl_log_manager_get_dates_async (ctx->self->priv->log_manager,
3233       ctx->account, ctx->entity, ctx->event_mask,
3234       log_manager_got_dates_cb, ctx);
3235 }
3236
3237 static void
3238 log_window_chats_get_messages (EmpathyLogWindow *self,
3239     gboolean force_get_dates)
3240 {
3241   GList *accounts, *targets, *dates;
3242   TplEventTypeMask event_mask;
3243   GtkTreeView *view;
3244   GtkTreeModel *model;
3245   GtkListStore *store;
3246   GtkTreeSelection *selection;
3247
3248   if (!log_window_get_selected (self, &accounts, &targets, NULL,
3249       &dates, &event_mask, NULL))
3250     return;
3251
3252   view = GTK_TREE_VIEW (self->priv->treeview_when);
3253   selection = gtk_tree_view_get_selection (view);
3254   model = gtk_tree_view_get_model (view);
3255   store = GTK_LIST_STORE (model);
3256
3257   /* Clear all current messages shown in the textview */
3258   gtk_tree_store_clear (self->priv->store_events);
3259
3260   _tpl_action_chain_clear (self->priv->chain);
3261   self->priv->count++;
3262
3263   /* If there's a search use the returned hits */
3264   if (self->priv->hits != NULL)
3265     {
3266       if (force_get_dates)
3267         {
3268           g_signal_handlers_block_by_func (selection,
3269               log_window_when_changed_cb,
3270               self);
3271
3272           gtk_list_store_clear (store);
3273
3274           g_signal_handlers_unblock_by_func (selection,
3275               log_window_when_changed_cb,
3276               self);
3277
3278           populate_dates_from_search_hits (accounts, targets);
3279         }
3280       else
3281         {
3282           populate_events_from_search_hits (accounts, targets, dates);
3283         }
3284     }
3285   /* Either use the supplied date or get the last */
3286   else if (force_get_dates || dates == NULL)
3287     {
3288       GList *acc, *targ;
3289
3290       if (self->priv->current_dates != NULL)
3291         {
3292           g_list_free_full (self->priv->current_dates,
3293               (GDestroyNotify) g_date_free);
3294           self->priv->current_dates = NULL;
3295         }
3296
3297       if (gtk_tree_selection_count_selected_rows (selection) > 0)
3298         {
3299           GList *paths, *l;
3300           GtkTreeIter iter;
3301
3302           paths = gtk_tree_selection_get_selected_rows (selection, NULL);
3303
3304           for (l = paths; l != NULL; l = l->next)
3305             {
3306               GtkTreePath *path = l->data;
3307               GDate *date;
3308
3309               gtk_tree_model_get_iter (model, &iter, path);
3310               gtk_tree_model_get (model, &iter,
3311                   COL_WHEN_DATE, &date,
3312                   -1);
3313
3314               /* The list takes ownership of the date. */
3315               self->priv->current_dates =
3316                   g_list_prepend (self->priv->current_dates, date);
3317             }
3318
3319           g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free);
3320         }
3321
3322       g_signal_handlers_block_by_func (selection,
3323           log_window_when_changed_cb,
3324           self);
3325
3326       gtk_list_store_clear (store);
3327
3328       g_signal_handlers_unblock_by_func (selection,
3329           log_window_when_changed_cb,
3330           self);
3331
3332       /* Get a list of dates and show them on the treeview */
3333       for (targ = targets, acc = accounts;
3334            targ != NULL && acc != NULL;
3335            targ = targ->next, acc = acc->next)
3336         {
3337           TpAccount *account = acc->data;
3338           TplEntity *target = targ->data;
3339           Ctx *ctx = ctx_new (self, account, target, NULL, event_mask, 0,
3340               self->priv->count);
3341
3342           _tpl_action_chain_append (self->priv->chain, get_dates_for_entity, ctx);
3343         }
3344       _tpl_action_chain_append (self->priv->chain, select_date, NULL);
3345       _tpl_action_chain_start (self->priv->chain);
3346     }
3347   else
3348     {
3349       /* Show messages of the selected date */
3350       log_window_get_messages_for_dates (self, dates);
3351     }
3352
3353   g_list_free_full (accounts, g_object_unref);
3354   g_list_free_full (targets, g_object_unref);
3355   g_list_free_full (dates, (GFreeFunc) g_date_free);
3356 }
3357
3358 typedef struct {
3359   EmpathyAccountChooserFilterResultCallback callback;
3360   gpointer user_data;
3361 } FilterCallbackData;
3362
3363 static void
3364 got_entities (GObject *manager,
3365     GAsyncResult *result,
3366     gpointer user_data)
3367 {
3368   FilterCallbackData *data = user_data;
3369   GList *entities;
3370   GError *error = NULL;
3371
3372   if (!tpl_log_manager_get_entities_finish (TPL_LOG_MANAGER (manager),
3373       result, &entities, &error))
3374     {
3375       DEBUG ("Could not get entities: %s", error->message);
3376       g_error_free (error);
3377       data->callback (FALSE, data->user_data);
3378     }
3379   else
3380     {
3381       data->callback (entities != NULL, data->user_data);
3382
3383       g_list_free_full (entities, g_object_unref);
3384     }
3385
3386   g_slice_free (FilterCallbackData, data);
3387 }
3388
3389 static void
3390 empathy_account_chooser_filter_has_logs (TpAccount *account,
3391     EmpathyAccountChooserFilterResultCallback callback,
3392     gpointer callback_data,
3393     gpointer user_data)
3394 {
3395   TplLogManager *manager = tpl_log_manager_dup_singleton ();
3396   FilterCallbackData *cb_data = g_slice_new0 (FilterCallbackData);
3397
3398   cb_data->callback = callback;
3399   cb_data->user_data = callback_data;
3400
3401   tpl_log_manager_get_entities_async (manager, account, got_entities, cb_data);
3402
3403   g_object_unref (manager);
3404 }
3405
3406 static void
3407 log_window_logger_clear_account_cb (TpProxy *proxy,
3408     const GError *error,
3409     gpointer user_data,
3410     GObject *weak_object)
3411 {
3412   EmpathyLogWindow *self = EMPATHY_LOG_WINDOW (user_data);
3413
3414   if (error != NULL)
3415     g_warning ("Error when clearing logs: %s", error->message);
3416
3417   /* Refresh the log viewer so the logs are cleared if the account
3418    * has been deleted */
3419   gtk_tree_store_clear (self->priv->store_events);
3420   log_window_who_populate (self);
3421
3422   /* Re-filter the account chooser so the accounts without logs get greyed out */
3423   empathy_account_chooser_set_filter (
3424       EMPATHY_ACCOUNT_CHOOSER (self->priv->account_chooser),
3425       empathy_account_chooser_filter_has_logs, NULL);
3426 }
3427
3428 static void
3429 log_window_clear_logs_chooser_select_account (EmpathyAccountChooser *chooser,
3430     EmpathyLogWindow *self)
3431 {
3432   EmpathyAccountChooser *account_chooser;
3433
3434   account_chooser = EMPATHY_ACCOUNT_CHOOSER (self->priv->account_chooser);
3435
3436   empathy_account_chooser_set_account (chooser,
3437       empathy_account_chooser_get_account (account_chooser));
3438 }
3439
3440 static void
3441 log_window_delete_menu_clicked_cb (GtkMenuItem *menuitem,
3442     EmpathyLogWindow *self)
3443 {
3444   GtkWidget *dialog, *content_area, *hbox, *label;
3445   EmpathyAccountChooser *account_chooser;
3446   gint response_id;
3447   TpDBusDaemon *bus;
3448   TpProxy *logger;
3449   GError *error = NULL;
3450
3451   account_chooser = (EmpathyAccountChooser *) empathy_account_chooser_new ();
3452   empathy_account_chooser_set_has_all_option (account_chooser, TRUE);
3453   empathy_account_chooser_set_filter (account_chooser,
3454       empathy_account_chooser_filter_has_logs, NULL);
3455
3456   /* Select the same account as in the history window */
3457   if (empathy_account_chooser_is_ready (account_chooser))
3458     log_window_clear_logs_chooser_select_account (account_chooser, self);
3459   else
3460     g_signal_connect (account_chooser, "ready",
3461         G_CALLBACK (log_window_clear_logs_chooser_select_account), self);
3462
3463   dialog = gtk_message_dialog_new_with_markup (GTK_WINDOW (self),
3464       GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING,
3465       GTK_BUTTONS_NONE,
3466       _("Are you sure you want to delete all logs of previous conversations?"));
3467
3468   gtk_dialog_add_buttons (GTK_DIALOG (dialog),
3469       GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
3470       _("Clear All"), GTK_RESPONSE_APPLY,
3471       NULL);
3472
3473   content_area = gtk_message_dialog_get_message_area (
3474       GTK_MESSAGE_DIALOG (dialog));
3475
3476   hbox = gtk_hbox_new (FALSE, 6);
3477   label = gtk_label_new (_("Delete from:"));
3478   gtk_box_pack_start (GTK_BOX (hbox), label,
3479       FALSE, FALSE, 0);
3480   gtk_box_pack_start (GTK_BOX (hbox), GTK_WIDGET (account_chooser),
3481       FALSE, FALSE, 0);
3482   gtk_box_pack_start (GTK_BOX (content_area), hbox,
3483       FALSE, FALSE, 0);
3484
3485   gtk_widget_show_all (hbox);
3486
3487   response_id = gtk_dialog_run (GTK_DIALOG (dialog));
3488
3489   if (response_id != GTK_RESPONSE_APPLY)
3490     goto out;
3491
3492   bus = tp_dbus_daemon_dup (&error);
3493   if (error != NULL)
3494     {
3495       g_warning ("Could not delete logs: %s", error->message);
3496       g_error_free (error);
3497       goto out;
3498     }
3499
3500   logger = g_object_new (TP_TYPE_PROXY,
3501       "bus-name", "org.freedesktop.Telepathy.Logger",
3502       "object-path", "/org/freedesktop/Telepathy/Logger",
3503       "dbus-daemon", bus,
3504       NULL);
3505   g_object_unref (bus);
3506
3507   tp_proxy_add_interface_by_id (logger, EMP_IFACE_QUARK_LOGGER);
3508
3509   if (empathy_account_chooser_has_all_selected (account_chooser))
3510     {
3511       DEBUG ("Deleting logs for all the accounts");
3512
3513       emp_cli_logger_call_clear (logger, -1,
3514           log_window_logger_clear_account_cb,
3515           self, NULL, G_OBJECT (self));
3516     }
3517   else
3518     {
3519       TpAccount *account;
3520
3521       account = empathy_account_chooser_get_account (account_chooser);
3522
3523       DEBUG ("Deleting logs for %s", tp_proxy_get_object_path (account));
3524
3525       emp_cli_logger_call_clear_account (logger, -1,
3526           tp_proxy_get_object_path (account),
3527           log_window_logger_clear_account_cb,
3528           self, NULL, G_OBJECT (self));
3529     }
3530
3531   g_object_unref (logger);
3532  out:
3533   gtk_widget_destroy (dialog);
3534 }