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