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