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