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