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