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