]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-log-window.c
make check fixes
[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
33 #include <telepathy-glib/telepathy-glib.h>
34 #include <telepathy-glib/proxy-subclass.h>
35
36 #include <telepathy-logger/telepathy-logger.h>
37 #include <telepathy-logger/call-event.h>
38
39 #include <extensions/extensions.h>
40
41 #include <libempathy/action-chain-internal.h>
42 #include <libempathy/empathy-chatroom-manager.h>
43 #include <libempathy/empathy-chatroom.h>
44 #include <libempathy/empathy-message.h>
45 #include <libempathy/empathy-request-util.h>
46 #include <libempathy/empathy-utils.h>
47 #include <libempathy/empathy-time.h>
48
49 #include "empathy-log-window.h"
50 #include "empathy-account-chooser.h"
51 #include "empathy-call-utils.h"
52 #include "empathy-chat-view.h"
53 #include "empathy-contact-dialogs.h"
54 #include "empathy-images.h"
55 #include "empathy-theme-manager.h"
56 #include "empathy-ui-utils.h"
57
58 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
59 #include <libempathy/empathy-debug.h>
60
61 typedef struct
62 {
63   GtkWidget *window;
64
65   GtkWidget *button_profile;
66   GtkWidget *button_chat;
67   GtkWidget *button_call;
68   GtkWidget *button_video;
69
70   GtkWidget *search_entry;
71
72   GtkWidget *treeview_who;
73   GtkWidget *treeview_what;
74   GtkWidget *treeview_when;
75   GtkWidget *treeview_events;
76
77   GtkTreeStore *store_events;
78
79   GtkWidget *account_chooser;
80
81   gchar *last_find;
82
83   TplActionChain *chain;
84   TplLogManager *log_manager;
85
86   /* Used to cancel logger calls when no longer needed */
87   guint count;
88
89   /* List of owned TplLogSearchHits, free with tpl_log_search_hit_free */
90   GList *hits;
91   guint source;
92
93   /* Only used while waiting for the account chooser to be ready */
94   TpAccount *selected_account;
95   gchar *selected_chat_id;
96   gboolean selected_is_chatroom;
97 } EmpathyLogWindow;
98
99 static void log_window_destroy_cb                (GtkWidget        *widget,
100                                                   EmpathyLogWindow *window);
101 static void log_window_search_entry_changed_cb   (GtkWidget        *entry,
102                                                   EmpathyLogWindow *window);
103 static void log_window_search_entry_activate_cb  (GtkWidget        *widget,
104                                                   EmpathyLogWindow *window);
105 static void log_window_who_populate              (EmpathyLogWindow *window);
106 static void log_window_who_setup                 (EmpathyLogWindow *window);
107 static void log_window_when_setup                (EmpathyLogWindow *window);
108 static void log_window_what_setup                (EmpathyLogWindow *window);
109 static void log_window_events_setup              (EmpathyLogWindow *window);
110 static void log_window_chats_accounts_changed_cb (GtkWidget        *combobox,
111                                                   EmpathyLogWindow *window);
112 static void log_window_chats_set_selected        (EmpathyLogWindow *window);
113 static void log_window_chats_get_messages        (EmpathyLogWindow *window,
114                                                   gboolean force_get_dates);
115 static void log_window_when_changed_cb           (GtkTreeSelection *selection,
116                                                   EmpathyLogWindow *window);
117 static void log_window_delete_menu_clicked_cb    (GtkMenuItem      *menuitem,
118                                                   EmpathyLogWindow *window);
119
120 static void
121 empathy_account_chooser_filter_has_logs (TpAccount *account,
122     EmpathyAccountChooserFilterResultCallback callback,
123     gpointer callback_data,
124     gpointer user_data);
125
126 enum
127 {
128   COL_TYPE_ANY,
129   COL_TYPE_SEPARATOR,
130   COL_TYPE_NORMAL
131 };
132
133 enum
134 {
135   COL_WHO_TYPE,
136   COL_WHO_ICON,
137   COL_WHO_NAME,
138   COL_WHO_ACCOUNT,
139   COL_WHO_TARGET,
140   COL_WHO_COUNT
141 };
142
143 enum
144 {
145   COL_WHAT_TYPE,
146   COL_WHAT_SUBTYPE,
147   COL_WHAT_TEXT,
148   COL_WHAT_ICON,
149   COL_WHAT_EXPANDER,
150   COL_WHAT_COUNT
151 };
152
153 enum
154 {
155   COL_WHEN_DATE,
156   COL_WHEN_TEXT,
157   COL_WHEN_ICON,
158   COL_WHEN_COUNT
159 };
160
161 enum
162 {
163   COL_EVENTS_TYPE,
164   COL_EVENTS_TS,
165   COL_EVENTS_PRETTY_DATE,
166   COL_EVENTS_ICON,
167   COL_EVENTS_TEXT,
168   COL_EVENTS_ACCOUNT,
169   COL_EVENTS_TARGET,
170   COL_EVENTS_EVENT,
171   COL_EVENTS_COUNT
172 };
173
174 #define CALENDAR_ICON "stock_calendar"
175
176 typedef enum
177 {
178   EVENT_CALL_INCOMING = 1 << 0,
179   EVENT_CALL_OUTGOING = 1 << 1,
180   EVENT_CALL_MISSED   = 1 << 2,
181   EVENT_CALL_ALL      = 1 << 3,
182 } EventSubtype;
183
184 static EmpathyLogWindow *log_window = NULL;
185
186 static gboolean has_element;
187
188 #ifndef _date_copy
189 #define _date_copy(d) g_date_new_julian (g_date_get_julian (d))
190 #endif
191
192 typedef struct
193 {
194   EmpathyLogWindow *window;
195   TpAccount *account;
196   TplEntity *entity;
197   GDate *date;
198   TplEventTypeMask event_mask;
199   EventSubtype subtype;
200   guint count;
201 } Ctx;
202
203 static Ctx *
204 ctx_new (EmpathyLogWindow *window,
205     TpAccount *account,
206     TplEntity *entity,
207     GDate *date,
208     TplEventTypeMask event_mask,
209     EventSubtype subtype,
210     guint count)
211 {
212   Ctx *ctx = g_slice_new0 (Ctx);
213
214   ctx->window = window;
215   if (account != NULL)
216     ctx->account = g_object_ref (account);
217   if (entity != NULL)
218     ctx->entity = g_object_ref (entity);
219   if (date != NULL)
220     ctx->date = _date_copy (date);
221   ctx->event_mask = event_mask;
222   ctx->subtype = subtype;
223   ctx->count = count;
224
225   return ctx;
226 }
227
228 static void
229 ctx_free (Ctx *ctx)
230 {
231   tp_clear_object (&ctx->account);
232   tp_clear_object (&ctx->entity);
233   if (ctx->date != NULL)
234     g_date_free (ctx->date);
235
236   g_slice_free (Ctx, ctx);
237 }
238
239 static void
240 account_chooser_ready_cb (EmpathyAccountChooser *chooser,
241     EmpathyLogWindow *window)
242 {
243   /* We'll display the account once the model has been populate with the chats
244    * of this account. */
245   empathy_account_chooser_set_account (EMPATHY_ACCOUNT_CHOOSER (
246       window->account_chooser), window->selected_account);
247 }
248
249 static void
250 select_account_once_ready (EmpathyLogWindow *self,
251     TpAccount *account,
252     const gchar *chat_id,
253     gboolean is_chatroom)
254 {
255   EmpathyAccountChooser *account_chooser;
256
257   account_chooser = EMPATHY_ACCOUNT_CHOOSER (self->account_chooser);
258
259   tp_clear_object (&self->selected_account);
260   self->selected_account = g_object_ref (account);
261
262   g_free (self->selected_chat_id);
263   self->selected_chat_id = g_strdup (chat_id);
264
265   self->selected_is_chatroom = is_chatroom;
266
267   if (empathy_account_chooser_is_ready (account_chooser))
268     account_chooser_ready_cb (account_chooser, self);
269   else
270     /* Chat will be selected once the account chooser is ready */
271     g_signal_connect (account_chooser, "ready",
272         G_CALLBACK (account_chooser_ready_cb), self);
273 }
274
275 static void
276 toolbutton_profile_clicked (GtkToolButton *toolbutton,
277     EmpathyLogWindow *window)
278 {
279   GtkTreeView *view;
280   GtkTreeSelection *selection;
281   GtkTreeModel *model;
282   GtkTreeIter iter;
283   TpAccount *account;
284   TplEntity *target;
285   EmpathyContact *contact;
286   gint type;
287
288   g_return_if_fail (window != NULL);
289
290   view = GTK_TREE_VIEW (log_window->treeview_who);
291   selection = gtk_tree_view_get_selection (view);
292
293   if (gtk_tree_selection_get_selected (selection, &model, &iter))
294     {
295       gtk_tree_model_get (model, &iter,
296           COL_WHO_ACCOUNT, &account,
297           COL_WHO_TARGET, &target,
298           COL_WHO_TYPE, &type,
299           -1);
300     }
301
302   g_return_if_fail (type == COL_TYPE_NORMAL);
303
304   contact = empathy_contact_from_tpl_contact (account, target);
305   empathy_contact_information_dialog_show (contact,
306       GTK_WINDOW (window->window));
307
308   g_object_unref (contact);
309   g_object_unref (account);
310   g_object_unref (target);
311 }
312
313 static void
314 toolbutton_chat_clicked (GtkToolButton *toolbutton,
315     EmpathyLogWindow *window)
316 {
317   GtkTreeView *view;
318   GtkTreeSelection *selection;
319   GtkTreeModel *model;
320   GtkTreeIter iter;
321   TpAccount *account;
322   TplEntity *target;
323   EmpathyContact *contact;
324   gint type;
325
326   g_return_if_fail (window != NULL);
327
328   view = GTK_TREE_VIEW (log_window->treeview_who);
329   selection = gtk_tree_view_get_selection (view);
330
331   if (gtk_tree_selection_get_selected (selection, &model, &iter))
332     {
333       gtk_tree_model_get (model, &iter,
334           COL_WHO_ACCOUNT, &account,
335           COL_WHO_TARGET, &target,
336           COL_WHO_TYPE, &type,
337           -1);
338     }
339
340   g_return_if_fail (type == COL_TYPE_NORMAL);
341
342   contact = empathy_contact_from_tpl_contact (account, target);
343   empathy_chat_with_contact (contact,
344       gtk_get_current_event_time ());
345
346   g_object_unref (contact);
347   g_object_unref (account);
348   g_object_unref (target);
349 }
350
351 static void
352 toolbutton_av_clicked (GtkToolButton *toolbutton,
353     EmpathyLogWindow *window)
354 {
355   GtkTreeView *view;
356   GtkTreeSelection *selection;
357   GtkTreeModel *model;
358   GtkTreeIter iter;
359   TpAccount *account;
360   gchar *contact;
361   gint type;
362   gboolean video;
363
364   g_return_if_fail (window != NULL);
365
366   view = GTK_TREE_VIEW (log_window->treeview_who);
367   selection = gtk_tree_view_get_selection (view);
368
369   if (gtk_tree_selection_get_selected (selection, &model, &iter))
370     {
371       gtk_tree_model_get (model, &iter,
372           COL_WHO_ACCOUNT, &account,
373           COL_WHO_NAME, &contact,
374           COL_WHO_TYPE, &type,
375           -1);
376     }
377
378   g_return_if_fail (type == COL_TYPE_NORMAL);
379
380   video = (GTK_WIDGET (toolbutton) == window->button_video);
381
382   empathy_call_new_with_streams (contact, account,
383       TRUE, video, gtk_get_current_event_time ());
384
385   g_free (contact);
386   g_object_unref (account);
387 }
388
389 GtkWidget *
390 empathy_log_window_show (TpAccount  *account,
391     const gchar *chat_id,
392     gboolean     is_chatroom,
393     GtkWindow   *parent)
394 {
395   EmpathyAccountChooser   *account_chooser;
396   GtkBuilder             *gui;
397   gchar                  *filename;
398   EmpathyLogWindow       *window;
399   GtkWidget *vbox, *accounts, *search, *label, *quit;
400
401   if (log_window != NULL)
402     {
403       gtk_window_present (GTK_WINDOW (log_window->window));
404
405       if (account != NULL && chat_id != NULL)
406         select_account_once_ready (log_window, account, chat_id, is_chatroom);
407
408       return log_window->window;
409     }
410
411   log_window = g_new0 (EmpathyLogWindow, 1);
412   log_window->chain = _tpl_action_chain_new_async (NULL, NULL, NULL);
413
414   log_window->log_manager = tpl_log_manager_dup_singleton ();
415
416   window = log_window;
417
418   filename = empathy_file_lookup ("empathy-log-window.ui", "libempathy-gtk");
419   gui = empathy_builder_get_file (filename,
420       "log_window", &window->window,
421       "toolbutton_profile", &window->button_profile,
422       "toolbutton_chat", &window->button_chat,
423       "toolbutton_call", &window->button_call,
424       "toolbutton_video", &window->button_video,
425       "toolbutton_accounts", &accounts,
426       "toolbutton_search", &search,
427       "imagemenuitem_quit", &quit,
428       "treeview_who", &window->treeview_who,
429       "treeview_what", &window->treeview_what,
430       "treeview_when", &window->treeview_when,
431       "treeview_events", &window->treeview_events,
432       NULL);
433   g_free (filename);
434
435   empathy_builder_connect (gui, window,
436       "log_window", "destroy", log_window_destroy_cb,
437       "toolbutton_profile", "clicked", toolbutton_profile_clicked,
438       "toolbutton_chat", "clicked", toolbutton_chat_clicked,
439       "toolbutton_call", "clicked", toolbutton_av_clicked,
440       "toolbutton_video", "clicked", toolbutton_av_clicked,
441       "imagemenuitem_delete", "activate", log_window_delete_menu_clicked_cb,
442       NULL);
443
444   g_object_unref (gui);
445
446   g_object_add_weak_pointer (G_OBJECT (window->window),
447       (gpointer) &log_window);
448
449   g_signal_connect_swapped (quit, "activate",
450       G_CALLBACK (gtk_widget_destroy), window->window);
451
452   /* Account chooser for chats */
453   vbox = gtk_vbox_new (FALSE, 3);
454
455   window->account_chooser = empathy_account_chooser_new ();
456   account_chooser = EMPATHY_ACCOUNT_CHOOSER (window->account_chooser);
457   empathy_account_chooser_set_has_all_option (account_chooser, TRUE);
458   empathy_account_chooser_set_filter (account_chooser,
459       empathy_account_chooser_filter_has_logs, NULL);
460
461   g_signal_connect (window->account_chooser, "changed",
462       G_CALLBACK (log_window_chats_accounts_changed_cb),
463       window);
464
465   label = gtk_label_new (_("Show"));
466
467   gtk_box_pack_start (GTK_BOX (vbox),
468       window->account_chooser,
469       FALSE, FALSE, 0);
470
471   gtk_box_pack_start (GTK_BOX (vbox),
472       label,
473       FALSE, FALSE, 0);
474
475   gtk_widget_show_all (vbox);
476   gtk_container_add (GTK_CONTAINER (accounts), vbox);
477
478   /* Search entry */
479   vbox = gtk_vbox_new (FALSE, 3);
480
481   window->search_entry = gtk_entry_new ();
482   gtk_entry_set_icon_from_stock (GTK_ENTRY (window->search_entry),
483       GTK_ENTRY_ICON_PRIMARY, GTK_STOCK_FIND);
484
485   label = gtk_label_new (_("Search"));
486
487   gtk_box_pack_start (GTK_BOX (vbox),
488       window->search_entry,
489       FALSE, FALSE, 0);
490
491   gtk_box_pack_start (GTK_BOX (vbox),
492       label,
493       FALSE, FALSE, 0);
494
495   gtk_widget_show_all (vbox);
496   gtk_container_add (GTK_CONTAINER (search), vbox);
497
498   g_signal_connect (window->search_entry, "changed",
499       G_CALLBACK (log_window_search_entry_changed_cb),
500       window);
501
502   g_signal_connect (window->search_entry, "activate",
503       G_CALLBACK (log_window_search_entry_activate_cb),
504       window);
505
506   /* Contacts */
507   log_window_events_setup (window);
508   log_window_who_setup (window);
509   log_window_what_setup (window);
510   log_window_when_setup (window);
511
512   log_window_who_populate (window);
513
514   if (account != NULL && chat_id != NULL)
515     select_account_once_ready (window, account, chat_id, is_chatroom);
516
517   if (parent != NULL)
518     gtk_window_set_transient_for (GTK_WINDOW (window->window),
519         GTK_WINDOW (parent));
520
521   gtk_widget_show (window->window);
522
523   return window->window;
524 }
525
526 static void
527 log_window_destroy_cb (GtkWidget *widget,
528     EmpathyLogWindow *window)
529 {
530   if (window->source != 0)
531     g_source_remove (window->source);
532
533   g_free (window->last_find);
534   _tpl_action_chain_free (window->chain);
535   g_object_unref (window->log_manager);
536   tp_clear_object (&window->selected_account);
537   g_free (window->selected_chat_id);
538
539   g_free (window);
540 }
541
542 static gboolean
543 account_equal (TpAccount *a,
544     TpAccount *b)
545 {
546   return g_str_equal (tp_proxy_get_object_path (a),
547       tp_proxy_get_object_path (b));
548 }
549
550 static gboolean
551 entity_equal (TplEntity *a,
552     TplEntity *b)
553 {
554   return g_str_equal (tpl_entity_get_identifier (a),
555       tpl_entity_get_identifier (b));
556 }
557
558 static TplEntity *
559 event_get_target (TplEvent *event)
560 {
561   TplEntity *sender = tpl_event_get_sender (event);
562   TplEntity *receiver = tpl_event_get_receiver (event);
563
564   if (tpl_entity_get_entity_type (sender) == TPL_ENTITY_SELF)
565     return receiver;
566
567   return sender;
568 }
569
570 static gboolean parent_found;
571 static GtkTreeIter model_parent;
572
573 static gboolean
574 model_is_parent (GtkTreeModel *model,
575     GtkTreePath *path,
576     GtkTreeIter *iter,
577     gpointer data)
578 {
579   TplEvent *event = data;
580   TplEvent *stored_event;
581   TplEntity *target;
582   TpAccount *account;
583   gint64 timestamp;
584   gboolean found = FALSE;
585
586   gtk_tree_model_get (model, iter,
587       COL_EVENTS_ACCOUNT, &account,
588       COL_EVENTS_TARGET, &target,
589       COL_EVENTS_TS, &timestamp,
590       COL_EVENTS_EVENT, &stored_event,
591       -1);
592
593   if (G_OBJECT_TYPE (event) == G_OBJECT_TYPE (stored_event) &&
594       account_equal (account, tpl_event_get_account (event)) &&
595       entity_equal (target, event_get_target (event)))
596     {
597       if (ABS (tpl_event_get_timestamp (event) - timestamp) < 1800)
598         {
599           /* The gap is smaller than 30 min */
600           model_parent = *iter;
601           parent_found = found = TRUE;
602         }
603     }
604
605   g_object_unref (stored_event);
606   g_object_unref (account);
607   g_object_unref (target);
608
609   return found;
610 }
611
612 static const gchar *
613 get_contact_alias_for_message (EmpathyMessage *message)
614 {
615   EmpathyContact *sender, *receiver;
616
617   sender = empathy_message_get_sender (message);
618   receiver = empathy_message_get_receiver (message);
619
620   if (empathy_contact_is_user (sender))
621     return empathy_contact_get_alias (receiver);
622
623   return empathy_contact_get_alias (sender);
624 }
625
626 static void
627 get_parent_iter_for_message (TplEvent *event,
628     EmpathyMessage *message,
629     GtkTreeIter *parent)
630 {
631   GtkTreeStore *store;
632   GtkTreeModel *model;
633
634   store = log_window->store_events;
635   model = GTK_TREE_MODEL (store);
636
637   parent_found = FALSE;
638   gtk_tree_model_foreach (model, model_is_parent, event);
639
640   if (parent_found)
641     *parent = model_parent;
642   else
643     {
644       GtkTreeIter iter;
645       GDateTime *date;
646       gchar *body, *pretty_date;
647
648       date = g_date_time_new_from_unix_utc (
649           tpl_event_get_timestamp (event));
650
651       pretty_date = g_date_time_format (date, "%x");
652
653       body = g_strdup_printf (_("Chat with %s"),
654           get_contact_alias_for_message (message));
655
656       gtk_tree_store_append (store, &iter, NULL);
657       gtk_tree_store_set (store, &iter,
658           COL_EVENTS_TS, tpl_event_get_timestamp (event),
659           COL_EVENTS_PRETTY_DATE, pretty_date,
660           COL_EVENTS_TEXT, body,
661           COL_EVENTS_ICON, "stock_text_justify",
662           COL_EVENTS_ACCOUNT, tpl_event_get_account (event),
663           COL_EVENTS_TARGET, event_get_target (event),
664           COL_EVENTS_EVENT, event,
665           -1);
666
667       *parent = iter;
668
669       g_free (body);
670       g_free (pretty_date);
671       g_date_time_unref (date);
672     }
673 }
674
675 static const gchar *
676 get_icon_for_event (TplEvent *event)
677 {
678   const gchar *icon = NULL;
679
680   if (TPL_IS_CALL_EVENT (event))
681     {
682       TplCallEvent *call = TPL_CALL_EVENT (event);
683       TplCallEndReason reason = tpl_call_event_get_end_reason (call);
684       TplEntity *sender = tpl_event_get_sender (event);
685       TplEntity *receiver = tpl_event_get_receiver (event);
686
687       if (reason == TPL_CALL_END_REASON_NO_ANSWER)
688         icon = EMPATHY_IMAGE_CALL_MISSED;
689       else if (tpl_entity_get_entity_type (sender) == TPL_ENTITY_SELF)
690         icon = EMPATHY_IMAGE_CALL_OUTGOING;
691       else if (tpl_entity_get_entity_type (receiver) == TPL_ENTITY_SELF)
692         icon = EMPATHY_IMAGE_CALL_INCOMING;
693     }
694
695   return icon;
696 }
697
698 static void
699 log_window_append_chat_message (TplEvent *event,
700     EmpathyMessage *message)
701 {
702   GtkTreeStore *store = log_window->store_events;
703   GtkTreeIter iter, parent;
704   gchar *pretty_date, *body;
705   GDateTime *date;
706
707   date = g_date_time_new_from_unix_utc (
708       tpl_event_get_timestamp (event));
709
710   pretty_date = g_date_time_format (date, "%x");
711
712   get_parent_iter_for_message (event, message, &parent);
713
714   body = g_strdup_printf (
715       C_("First is a contact, second is what he said", "%s: %s"),
716       tpl_entity_get_alias (tpl_event_get_sender (event)),
717       empathy_message_get_body (message));
718
719   gtk_tree_store_append (store, &iter, &parent);
720   gtk_tree_store_set (store, &iter,
721       COL_EVENTS_TS, tpl_event_get_timestamp (event),
722       COL_EVENTS_PRETTY_DATE, pretty_date,
723       COL_EVENTS_TEXT, body,
724       COL_EVENTS_ICON, get_icon_for_event (event),
725       COL_EVENTS_ACCOUNT, tpl_event_get_account (event),
726       COL_EVENTS_TARGET, event_get_target (event),
727       COL_EVENTS_EVENT, event,
728       -1);
729
730   g_free (body);
731   g_free (pretty_date);
732   g_date_time_unref (date);
733 }
734
735 static void
736 log_window_append_call (TplEvent *event,
737     EmpathyMessage *message)
738 {
739   TplCallEvent *call = TPL_CALL_EVENT (event);
740   GtkTreeStore *store = log_window->store_events;
741   GtkTreeIter iter, child;
742   gchar *pretty_date, *duration, *finished;
743   GDateTime *started_date, *finished_date;
744   GTimeSpan span;
745
746   started_date = g_date_time_new_from_unix_utc (
747       tpl_event_get_timestamp (event));
748
749   pretty_date = g_date_time_format (started_date, "%x");
750
751   gtk_tree_store_append (store, &iter, NULL);
752   gtk_tree_store_set (store, &iter,
753       COL_EVENTS_TS, tpl_event_get_timestamp (event),
754       COL_EVENTS_PRETTY_DATE, pretty_date,
755       COL_EVENTS_TEXT, empathy_message_get_body (message),
756       COL_EVENTS_ICON, get_icon_for_event (event),
757       COL_EVENTS_ACCOUNT, tpl_event_get_account (event),
758       COL_EVENTS_TARGET, event_get_target (event),
759       COL_EVENTS_EVENT, event,
760       -1);
761
762   if (tpl_call_event_get_end_reason (call) != TPL_CALL_END_REASON_NO_ANSWER)
763     {
764       gchar *body;
765
766       span = tpl_call_event_get_duration (TPL_CALL_EVENT (event));
767       if (span < 60)
768         duration = g_strdup_printf (_("%" G_GINT64_FORMAT " seconds"), span);
769       else
770         duration = g_strdup_printf (_("%" G_GINT64_FORMAT " minutes"),
771             span / 60);
772
773       finished_date = g_date_time_add (started_date, -span);
774       finished = g_date_time_format (finished_date, "%X");
775       g_date_time_unref (finished_date);
776
777       body = g_strdup_printf (_("Call took %s, ended at %s"),
778           duration, finished);
779
780       g_free (duration);
781       g_free (finished);
782
783       gtk_tree_store_append (store, &child, &iter);
784       gtk_tree_store_set (store, &child,
785           COL_EVENTS_TS, tpl_event_get_timestamp (event),
786           COL_EVENTS_TEXT, body,
787           COL_EVENTS_ACCOUNT, tpl_event_get_account (event),
788           COL_EVENTS_TARGET, event_get_target (event),
789           COL_EVENTS_EVENT, event,
790           -1);
791
792       g_free (body);
793     }
794
795   g_free (pretty_date);
796   g_date_time_unref (started_date);
797 }
798
799 static void
800 log_window_append_message (TplEvent *event,
801     EmpathyMessage *message)
802 {
803   if (TPL_IS_TEXT_EVENT (event))
804     log_window_append_chat_message (event, message);
805   else if (TPL_IS_CALL_EVENT (event))
806     log_window_append_call (event, message);
807   else
808     DEBUG ("Message type not handled");
809 }
810
811 static void
812 add_all_accounts_and_entities (GList **accounts,
813     GList **entities)
814 {
815   GtkTreeView      *view;
816   GtkTreeModel     *model;
817   GtkTreeIter       iter;
818
819   view = GTK_TREE_VIEW (log_window->treeview_who);
820   model = gtk_tree_view_get_model (view);
821
822   if (!gtk_tree_model_get_iter_first (model, &iter))
823     return;
824
825   do
826     {
827       TpAccount *account;
828       TplEntity *entity;
829       gint type;
830
831       gtk_tree_model_get (model, &iter,
832           COL_WHO_ACCOUNT, &account,
833           COL_WHO_TARGET, &entity,
834           COL_WHO_TYPE, &type,
835           -1);
836
837       if (type != COL_TYPE_NORMAL)
838         continue;
839
840       if (accounts != NULL)
841         *accounts = g_list_append (*accounts, account);
842
843       if (entities != NULL)
844         *entities = g_list_append (*entities, entity);
845     }
846   while (gtk_tree_model_iter_next (model, &iter));
847 }
848
849 static gboolean
850 log_window_get_selected (EmpathyLogWindow *window,
851     GList **accounts,
852     GList **entities,
853     GList **dates,
854     TplEventTypeMask *event_mask,
855     EventSubtype *subtype)
856 {
857   GtkTreeView      *view;
858   GtkTreeModel     *model;
859   GtkTreeSelection *selection;
860   GtkTreeIter       iter;
861   TplEventTypeMask  ev = 0;
862   EventSubtype      st = 0;
863   GList            *paths, *l;
864   gint              type;
865
866   view = GTK_TREE_VIEW (window->treeview_who);
867   model = gtk_tree_view_get_model (view);
868   selection = gtk_tree_view_get_selection (view);
869
870   paths = gtk_tree_selection_get_selected_rows (selection, NULL);
871   if (paths == NULL)
872     return FALSE;
873
874   if (accounts)
875     *accounts = NULL;
876   if (entities)
877     *entities = NULL;
878
879   for (l = paths; l != NULL; l = l->next)
880     {
881       GtkTreePath *path = l->data;
882       TpAccount *account;
883       TplEntity *entity;
884
885       gtk_tree_model_get_iter (model, &iter, path);
886       gtk_tree_model_get (model, &iter,
887           COL_WHO_ACCOUNT, &account,
888           COL_WHO_TARGET, &entity,
889           COL_WHO_TYPE, &type,
890           -1);
891
892       if (type == COL_TYPE_ANY)
893         {
894           if (accounts != NULL || entities != NULL)
895             add_all_accounts_and_entities (accounts, entities);
896           break;
897         }
898
899       if (accounts != NULL)
900         *accounts = g_list_append (*accounts, g_object_ref (account));
901
902       if (entities != NULL)
903         *entities = g_list_append (*entities, g_object_ref (entity));
904
905       g_object_unref (account);
906       g_object_unref (entity);
907     }
908   g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free);
909
910   view = GTK_TREE_VIEW (window->treeview_what);
911   model = gtk_tree_view_get_model (view);
912   selection = gtk_tree_view_get_selection (view);
913
914   paths = gtk_tree_selection_get_selected_rows (selection, NULL);
915   for (l = paths; l != NULL; l = l->next)
916     {
917       GtkTreePath *path = l->data;
918       TplEventTypeMask mask;
919       EventSubtype submask;
920
921       gtk_tree_model_get_iter (model, &iter, path);
922       gtk_tree_model_get (model, &iter,
923           COL_WHAT_TYPE, &mask,
924           COL_WHAT_SUBTYPE, &submask,
925           -1);
926
927       ev |= mask;
928       st |= submask;
929     }
930   g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free);
931
932   view = GTK_TREE_VIEW (window->treeview_when);
933   model = gtk_tree_view_get_model (view);
934   selection = gtk_tree_view_get_selection (view);
935
936   if (dates != NULL)
937     {
938       *dates = NULL;
939
940       paths = gtk_tree_selection_get_selected_rows (selection, NULL);
941       for (l = paths; l != NULL; l = l->next)
942         {
943           GtkTreePath *path = l->data;
944           GDate *date;
945
946           gtk_tree_model_get_iter (model, &iter, path);
947           gtk_tree_model_get (model, &iter,
948               COL_WHEN_DATE, &date,
949               -1);
950
951           *dates = g_list_append (*dates, date);
952         }
953     }
954
955   if (event_mask != NULL)
956     *event_mask = ev;
957
958   if (subtype != NULL)
959     *subtype = st;
960
961   return TRUE;
962 }
963
964 static gboolean
965 model_has_entity (GtkTreeModel *model,
966     GtkTreePath *path,
967     GtkTreeIter *iter,
968     gpointer data)
969 {
970   TplLogSearchHit *hit = data;
971   TplEntity *e;
972   TpAccount *a;
973
974   gtk_tree_model_get (model, iter,
975       COL_WHO_TARGET, &e,
976       COL_WHO_ACCOUNT, &a,
977       -1);
978
979   if (e != NULL && entity_equal (hit->target, e) &&
980       a != NULL && account_equal (hit->account, a))
981     {
982       has_element = TRUE;
983       return TRUE;
984     }
985
986   return FALSE;
987 }
988
989 static gboolean
990 model_has_date (GtkTreeModel *model,
991     GtkTreePath *path,
992     GtkTreeIter *iter,
993     gpointer data)
994 {
995   GDate *date = data;
996   GDate *d;
997
998   gtk_tree_model_get (model, iter,
999       COL_WHEN_DATE, &d,
1000       -1);
1001
1002   if (!g_date_compare (date, d))
1003     {
1004       has_element = TRUE;
1005       return TRUE;
1006     }
1007
1008   return FALSE;
1009 }
1010
1011 static void
1012 get_events_for_date (TplActionChain *chain, gpointer user_data);
1013
1014 static void
1015 populate_events_from_search_hits (GList *accounts,
1016     GList *targets,
1017     GList *dates)
1018 {
1019   TplEventTypeMask event_mask;
1020   EventSubtype subtype;
1021   GDate *anytime;
1022   GList *l;
1023   gboolean is_anytime = FALSE;
1024
1025   if (!log_window_get_selected (log_window,
1026       NULL, NULL, NULL, &event_mask, &subtype))
1027     return;
1028
1029   anytime = g_date_new_dmy (2, 1, -1);
1030   if (g_list_find_custom (dates, anytime, (GCompareFunc) g_date_compare))
1031     is_anytime = TRUE;
1032
1033   for (l = log_window->hits; l != NULL; l = l->next)
1034     {
1035       TplLogSearchHit *hit = l->data;
1036       GList *acc, *targ;
1037       gboolean found = FALSE;
1038
1039       /* Protect against invalid data (corrupt or old log files). */
1040       if (hit->account == NULL || hit->target == NULL)
1041         continue;
1042
1043       for (acc = accounts, targ = targets;
1044            acc != NULL && targ != NULL && !found;
1045            acc = acc->next, targ = targ->next)
1046         {
1047           TpAccount *account = acc->data;
1048           TplEntity *target = targ->data;
1049
1050           if (account_equal (hit->account, account) &&
1051               entity_equal (hit->target, target))
1052             found = TRUE;
1053         }
1054
1055         if (!found)
1056           continue;
1057
1058       if (is_anytime ||
1059           g_list_find_custom (dates, hit->date, (GCompareFunc) g_date_compare)
1060               != NULL)
1061         {
1062           Ctx *ctx;
1063
1064           ctx = ctx_new (log_window, hit->account, hit->target, hit->date,
1065               event_mask, subtype, log_window->count);
1066           _tpl_action_chain_append (log_window->chain,
1067               get_events_for_date, ctx);
1068         }
1069     }
1070
1071   _tpl_action_chain_start (log_window->chain);
1072
1073   g_date_free (anytime);
1074 }
1075
1076 static gchar *
1077 format_date_for_display (GDate *date)
1078 {
1079   gchar *text;
1080   GDate *now = NULL;
1081   gint days_elapsed;
1082
1083   /* g_date_strftime sucks */
1084
1085   now = g_date_new ();
1086   g_date_set_time_t (now, time (NULL));
1087
1088   days_elapsed = g_date_days_between (date, now);
1089
1090   if (days_elapsed < 0)
1091     text = NULL;
1092   else if (days_elapsed == 0)
1093     text = g_strdup (_("Today"));
1094   else if (days_elapsed == 1)
1095     text = g_strdup (_("Yesterday"));
1096   else
1097     {
1098       GDateTime *dt;
1099
1100       dt = g_date_time_new_utc (g_date_get_year (date),
1101           g_date_get_month (date), g_date_get_day (date),
1102           0, 0, 0);
1103
1104       if (days_elapsed <= 7)
1105         text = g_date_time_format (dt, "%A");
1106       else
1107         text = g_date_time_format (dt,
1108             C_("A date such as '23 May 2010', "
1109                "%e is the day, %B the month and %Y the year",
1110                "%e %B %Y"));
1111
1112       g_date_time_unref (dt);
1113     }
1114
1115   g_date_free (now);
1116
1117   return text;
1118 }
1119
1120 static void
1121 populate_dates_from_search_hits (GList *accounts,
1122     GList *targets)
1123 {
1124   GList *l;
1125   GtkTreeView *view;
1126   GtkTreeModel *model;
1127   GtkListStore *store;
1128   GtkTreeSelection *selection;
1129   GtkTreeIter iter;
1130
1131   if (log_window == NULL)
1132     return;
1133
1134   view = GTK_TREE_VIEW (log_window->treeview_when);
1135   model = gtk_tree_view_get_model (view);
1136   store = GTK_LIST_STORE (model);
1137   selection = gtk_tree_view_get_selection (view);
1138
1139   for (l = log_window->hits; l != NULL; l = l->next)
1140     {
1141       TplLogSearchHit *hit = l->data;
1142       GList *acc, *targ;
1143       gboolean found = FALSE;
1144
1145       /* Protect against invalid data (corrupt or old log files). */
1146       if (hit->account == NULL || hit->target == NULL)
1147         continue;
1148
1149       for (acc = accounts, targ = targets;
1150            acc != NULL && targ != NULL && !found;
1151            acc = acc->next, targ = targ->next)
1152         {
1153           TpAccount *account = acc->data;
1154           TplEntity *target = targ->data;
1155
1156           if (account_equal (hit->account, account) &&
1157               entity_equal (hit->target, target))
1158             found = TRUE;
1159         }
1160
1161         if (!found)
1162           continue;
1163
1164       /* Add the date if it's not already there */
1165       has_element = FALSE;
1166       gtk_tree_model_foreach (model, model_has_date, hit->date);
1167       if (!has_element)
1168         {
1169           gchar *text = format_date_for_display (hit->date);
1170
1171           gtk_list_store_append (store, &iter);
1172           gtk_list_store_set (store, &iter,
1173               COL_WHEN_DATE, hit->date,
1174               COL_WHEN_TEXT, text,
1175               COL_WHEN_ICON, CALENDAR_ICON,
1176               -1);
1177         }
1178     }
1179
1180   if (gtk_tree_model_get_iter_first (model, &iter))
1181     {
1182       gtk_list_store_prepend (store, &iter);
1183       gtk_list_store_set (store, &iter,
1184           COL_WHEN_DATE, g_date_new_dmy (1, 1, -1),
1185           COL_WHEN_TEXT, "separator",
1186           -1);
1187
1188       gtk_list_store_prepend (store, &iter);
1189       gtk_list_store_set (store, &iter,
1190           COL_WHEN_DATE, g_date_new_dmy (2, 1, -1),
1191           COL_WHEN_TEXT, _("Anytime"),
1192           -1);
1193
1194       gtk_tree_selection_select_iter (selection, &iter);
1195     }
1196 }
1197
1198 static void
1199 populate_entities_from_search_hits (void)
1200 {
1201   EmpathyAccountChooser *account_chooser;
1202   TpAccount *account;
1203   GtkTreeView *view;
1204   GtkTreeModel *model;
1205   GtkTreeIter iter;
1206   GtkListStore *store;
1207   GList *l;
1208
1209   view = GTK_TREE_VIEW (log_window->treeview_who);
1210   model = gtk_tree_view_get_model (view);
1211   store = GTK_LIST_STORE (model);
1212
1213   gtk_list_store_clear (store);
1214
1215   account_chooser = EMPATHY_ACCOUNT_CHOOSER (log_window->account_chooser);
1216   account = empathy_account_chooser_get_account (account_chooser);
1217
1218   for (l = log_window->hits; l; l = l->next)
1219     {
1220       TplLogSearchHit *hit = l->data;
1221
1222       /* Protect against invalid data (corrupt or old log files). */
1223       if (hit->account == NULL || hit->target == NULL)
1224         continue;
1225
1226       /* Filter based on the selected account */
1227       if (account != NULL && !account_equal (account, hit->account))
1228         continue;
1229
1230       /* Add the entity if it's not already there */
1231       has_element = FALSE;
1232       gtk_tree_model_foreach (model, model_has_entity, hit);
1233       if (!has_element)
1234         {
1235           TplEntityType type = tpl_entity_get_entity_type (hit->target);
1236           gboolean room = type == TPL_ENTITY_ROOM;
1237
1238           gtk_list_store_append (store, &iter);
1239           gtk_list_store_set (store, &iter,
1240               COL_WHO_TYPE, COL_TYPE_NORMAL,
1241               COL_WHO_ICON, room ? EMPATHY_IMAGE_GROUP_MESSAGE
1242                                  : EMPATHY_IMAGE_AVATAR_DEFAULT,
1243               COL_WHO_NAME, tpl_entity_get_alias (hit->target),
1244               COL_WHO_ACCOUNT, hit->account,
1245               COL_WHO_TARGET, hit->target,
1246               -1);
1247         }
1248     }
1249
1250   if (gtk_tree_model_get_iter_first (model, &iter))
1251     {
1252       gtk_list_store_prepend (store, &iter);
1253       gtk_list_store_set (store, &iter,
1254           COL_WHO_TYPE, COL_TYPE_SEPARATOR,
1255           COL_WHO_NAME, "separator",
1256           -1);
1257
1258       gtk_list_store_prepend (store, &iter);
1259       gtk_list_store_set (store, &iter,
1260           COL_WHO_TYPE, COL_TYPE_ANY,
1261           COL_WHO_NAME, _("Anyone"),
1262           -1);
1263     }
1264
1265   /* FIXME: select old entity if still available */
1266 }
1267
1268 static void
1269 log_manager_searched_new_cb (GObject *manager,
1270     GAsyncResult *result,
1271     gpointer user_data)
1272 {
1273   GList *hits;
1274   GtkTreeView *view;
1275   GtkTreeSelection *selection;
1276   GError *error = NULL;
1277
1278   if (log_window == NULL)
1279     return;
1280
1281   if (!tpl_log_manager_search_finish (TPL_LOG_MANAGER (manager),
1282       result, &hits, &error))
1283     {
1284       DEBUG ("%s. Aborting", error->message);
1285       g_error_free (error);
1286       return;
1287     }
1288
1289   tp_clear_pointer (&log_window->hits, tpl_log_manager_search_free);
1290   log_window->hits = hits;
1291
1292   populate_entities_from_search_hits ();
1293
1294   view = GTK_TREE_VIEW (log_window->treeview_when);
1295   selection = gtk_tree_view_get_selection (view);
1296
1297   g_signal_handlers_unblock_by_func (selection,
1298       log_window_when_changed_cb,
1299       log_window);
1300 }
1301
1302 static void
1303 log_window_find_populate (EmpathyLogWindow *window,
1304     const gchar *search_criteria)
1305 {
1306   GtkTreeView *view;
1307   GtkTreeModel *model;
1308   GtkTreeSelection *selection;
1309   GtkListStore *store;
1310
1311   gtk_tree_store_clear (window->store_events);
1312
1313   view = GTK_TREE_VIEW (window->treeview_who);
1314   model = gtk_tree_view_get_model (view);
1315   store = GTK_LIST_STORE (model);
1316
1317   gtk_list_store_clear (store);
1318
1319   view = GTK_TREE_VIEW (window->treeview_when);
1320   model = gtk_tree_view_get_model (view);
1321   store = GTK_LIST_STORE (model);
1322   selection = gtk_tree_view_get_selection (view);
1323
1324   gtk_list_store_clear (store);
1325
1326   if (EMP_STR_EMPTY (search_criteria))
1327     {
1328       tp_clear_pointer (&window->hits, tpl_log_manager_search_free);
1329       log_window_who_populate (window);
1330       return;
1331     }
1332
1333   g_signal_handlers_block_by_func (selection,
1334       log_window_when_changed_cb,
1335       window);
1336
1337   tpl_log_manager_search_async (window->log_manager,
1338       search_criteria, TPL_EVENT_MASK_ANY,
1339       log_manager_searched_new_cb, NULL);
1340 }
1341
1342 static gboolean
1343 start_find_search (EmpathyLogWindow *window)
1344 {
1345   const gchar *str;
1346
1347   str = gtk_entry_get_text (GTK_ENTRY (window->search_entry));
1348
1349   /* Don't find the same crap again */
1350   if (window->last_find && !tp_strdiff (window->last_find, str))
1351     return FALSE;
1352
1353   g_free (window->last_find);
1354   window->last_find = g_strdup (str);
1355
1356   log_window_find_populate (window, str);
1357
1358   return FALSE;
1359 }
1360
1361 static void
1362 log_window_search_entry_changed_cb (GtkWidget *entry,
1363     EmpathyLogWindow *window)
1364 {
1365   if (window->source != 0)
1366     g_source_remove (window->source);
1367   window->source = g_timeout_add (500, (GSourceFunc) start_find_search,
1368       window);
1369 }
1370
1371 static void
1372 log_window_search_entry_activate_cb (GtkWidget *entry,
1373     EmpathyLogWindow *self)
1374 {
1375   start_find_search (self);
1376 }
1377
1378 /*
1379  * Chats Code
1380  */
1381
1382 static void
1383 log_window_who_changed_cb (GtkTreeSelection *selection,
1384     EmpathyLogWindow  *window)
1385 {
1386   GtkTreeView *view;
1387   GtkTreeModel *model;
1388   GtkTreeIter iter;
1389   gboolean someone = FALSE;
1390
1391   DEBUG ("log_window_who_changed_cb");
1392
1393   view = gtk_tree_selection_get_tree_view (selection);
1394   model = gtk_tree_view_get_model (view);
1395
1396   if (gtk_tree_model_get_iter_first (model, &iter))
1397     {
1398       /* If 'Anyone' is selected, everything else should be deselected */
1399       if (gtk_tree_selection_iter_is_selected (selection, &iter))
1400         {
1401           g_signal_handlers_block_by_func (selection,
1402               log_window_who_changed_cb,
1403               window);
1404
1405           gtk_tree_selection_unselect_all (selection);
1406           gtk_tree_selection_select_iter (selection, &iter);
1407
1408           g_signal_handlers_unblock_by_func (selection,
1409               log_window_who_changed_cb,
1410               window);
1411         }
1412       else if (gtk_tree_selection_count_selected_rows (selection) == 1)
1413         someone = TRUE;
1414     }
1415
1416   gtk_widget_set_sensitive (window->button_profile, someone);
1417   gtk_widget_set_sensitive (window->button_chat, someone);
1418   gtk_widget_set_sensitive (window->button_call, someone);
1419   gtk_widget_set_sensitive (window->button_video, someone);
1420
1421   /* The contact changed, so the dates need to be updated */
1422   log_window_chats_get_messages (window, TRUE);
1423 }
1424
1425 static void
1426 log_manager_got_entities_cb (GObject *manager,
1427     GAsyncResult *result,
1428     gpointer user_data)
1429 {
1430   Ctx                   *ctx = user_data;
1431   GList                 *entities;
1432   GList                 *l;
1433   GtkTreeView           *view;
1434   GtkTreeModel          *model;
1435   GtkTreeSelection      *selection;
1436   GtkListStore          *store;
1437   GtkTreeIter            iter;
1438   GError                *error = NULL;
1439   gboolean               select_account = FALSE;
1440
1441   if (log_window == NULL)
1442     goto out;
1443
1444   if (log_window->count != ctx->count)
1445     goto out;
1446
1447   if (!tpl_log_manager_get_entities_finish (TPL_LOG_MANAGER (manager),
1448       result, &entities, &error))
1449     {
1450       DEBUG ("%s. Aborting", error->message);
1451       g_error_free (error);
1452       goto out;
1453     }
1454
1455   view = GTK_TREE_VIEW (ctx->window->treeview_who);
1456   model = gtk_tree_view_get_model (view);
1457   selection = gtk_tree_view_get_selection (view);
1458   store = GTK_LIST_STORE (model);
1459
1460   /* Block signals to stop the logs being retrieved prematurely  */
1461   g_signal_handlers_block_by_func (selection,
1462       log_window_who_changed_cb, ctx->window);
1463
1464   for (l = entities; l; l = l->next)
1465     {
1466       TplEntity *entity = TPL_ENTITY (l->data);
1467       TplEntityType type = tpl_entity_get_entity_type (entity);
1468       gboolean room = type == TPL_ENTITY_ROOM;
1469
1470       gtk_list_store_append (store, &iter);
1471       gtk_list_store_set (store, &iter,
1472           COL_WHO_TYPE, COL_TYPE_NORMAL,
1473           COL_WHO_ICON, room ? EMPATHY_IMAGE_GROUP_MESSAGE
1474                              : EMPATHY_IMAGE_AVATAR_DEFAULT,
1475           COL_WHO_NAME, tpl_entity_get_alias (entity),
1476           COL_WHO_ACCOUNT, ctx->account,
1477           COL_WHO_TARGET, entity,
1478           -1);
1479
1480       if (ctx->window->selected_account != NULL &&
1481           !tp_strdiff (tp_proxy_get_object_path (ctx->account),
1482           tp_proxy_get_object_path (ctx->window->selected_account)))
1483         select_account = TRUE;
1484     }
1485   g_list_free_full (entities, g_object_unref);
1486
1487   if (gtk_tree_model_get_iter_first (model, &iter))
1488     {
1489       gint type;
1490
1491       gtk_tree_model_get (model, &iter,
1492           COL_WHO_TYPE, &type,
1493           -1);
1494
1495       if (type != COL_TYPE_ANY)
1496         {
1497           gtk_list_store_prepend (store, &iter);
1498           gtk_list_store_set (store, &iter,
1499               COL_WHO_TYPE, COL_TYPE_SEPARATOR,
1500               COL_WHO_NAME, "separator",
1501               -1);
1502
1503           gtk_list_store_prepend (store, &iter);
1504           gtk_list_store_set (store, &iter,
1505               COL_WHO_TYPE, COL_TYPE_ANY,
1506               COL_WHO_NAME, _("Anyone"),
1507               -1);
1508         }
1509     }
1510
1511   /* Unblock signals */
1512   g_signal_handlers_unblock_by_func (selection,
1513       log_window_who_changed_cb,
1514       ctx->window);
1515
1516   /* We display the selected account if we populate the model with chats from
1517    * this account. */
1518   if (select_account)
1519     log_window_chats_set_selected (ctx->window);
1520
1521 out:
1522   _tpl_action_chain_continue (log_window->chain);
1523   ctx_free (ctx);
1524 }
1525
1526 static void
1527 get_entities_for_account (TplActionChain *chain, gpointer user_data)
1528 {
1529   Ctx *ctx = user_data;
1530
1531   tpl_log_manager_get_entities_async (ctx->window->log_manager, ctx->account,
1532       log_manager_got_entities_cb, ctx);
1533 }
1534
1535 static void
1536 select_first_entity (TplActionChain *chain, gpointer user_data)
1537 {
1538   GtkTreeView *view;
1539   GtkTreeModel *model;
1540   GtkTreeSelection *selection;
1541   GtkTreeIter iter;
1542
1543   view = GTK_TREE_VIEW (log_window->treeview_who);
1544   model = gtk_tree_view_get_model (view);
1545   selection = gtk_tree_view_get_selection (view);
1546
1547   if (gtk_tree_model_get_iter_first (model, &iter))
1548     gtk_tree_selection_select_iter (selection, &iter);
1549
1550   _tpl_action_chain_continue (log_window->chain);
1551 }
1552
1553 static void
1554 log_window_who_populate (EmpathyLogWindow *window)
1555 {
1556   EmpathyAccountChooser *account_chooser;
1557   TpAccount *account;
1558   gboolean all_accounts;
1559   GtkTreeView *view;
1560   GtkTreeModel *model;
1561   GtkTreeSelection *selection;
1562   GtkListStore *store;
1563   Ctx *ctx;
1564
1565   if (window->hits != NULL)
1566     {
1567       populate_entities_from_search_hits ();
1568       return;
1569     }
1570
1571   account_chooser = EMPATHY_ACCOUNT_CHOOSER (window->account_chooser);
1572   account = empathy_account_chooser_dup_account (account_chooser);
1573   all_accounts = empathy_account_chooser_has_all_selected (account_chooser);
1574
1575   view = GTK_TREE_VIEW (window->treeview_who);
1576   model = gtk_tree_view_get_model (view);
1577   selection = gtk_tree_view_get_selection (view);
1578   store = GTK_LIST_STORE (model);
1579
1580   /* Block signals to stop the logs being retrieved prematurely  */
1581   g_signal_handlers_block_by_func (selection,
1582       log_window_who_changed_cb,
1583       window);
1584
1585   gtk_list_store_clear (store);
1586
1587   /* Unblock signals */
1588   g_signal_handlers_unblock_by_func (selection,
1589       log_window_who_changed_cb,
1590       window);
1591
1592   _tpl_action_chain_clear (window->chain);
1593   window->count++;
1594
1595   if (!all_accounts && account == NULL)
1596     {
1597       return;
1598     }
1599   else if (!all_accounts)
1600     {
1601       ctx = ctx_new (window, account, NULL, NULL, 0, 0, window->count);
1602       _tpl_action_chain_append (window->chain, get_entities_for_account, ctx);
1603     }
1604   else
1605     {
1606       TpAccountManager *manager;
1607       GList *accounts, *l;
1608
1609       manager = empathy_account_chooser_get_account_manager (account_chooser);
1610       accounts = tp_account_manager_get_valid_accounts (manager);
1611
1612       for (l = accounts; l != NULL; l = l->next)
1613         {
1614           account = l->data;
1615
1616           ctx = ctx_new (window, account, NULL, NULL, 0, 0, window->count);
1617           _tpl_action_chain_append (window->chain,
1618               get_entities_for_account, ctx);
1619         }
1620
1621       g_list_free (accounts);
1622     }
1623   _tpl_action_chain_append (window->chain, select_first_entity, NULL);
1624   _tpl_action_chain_start (window->chain);
1625 }
1626
1627 static gint
1628 sort_by_name (GtkTreeModel *model,
1629     GtkTreeIter *a,
1630     GtkTreeIter *b,
1631     gpointer user_data)
1632 {
1633   gchar *name1, *name2;
1634   gint type1, type2;
1635   gint ret;
1636
1637   gtk_tree_model_get (model, a,
1638       COL_WHO_TYPE, &type1,
1639       COL_WHO_NAME, &name1,
1640       -1);
1641
1642   gtk_tree_model_get (model, b,
1643       COL_WHO_TYPE, &type2,
1644       COL_WHO_NAME, &name2,
1645       -1);
1646
1647   if (type1 == COL_TYPE_ANY)
1648     ret = -1;
1649   else if (type2 == COL_TYPE_ANY)
1650     ret = 1;
1651   else if (type1 == COL_TYPE_SEPARATOR)
1652     ret = -1;
1653   else if (type2 == COL_TYPE_SEPARATOR)
1654     ret = 1;
1655   else
1656     ret = g_strcmp0 (name1, name2);
1657
1658   g_free (name1);
1659   g_free (name2);
1660
1661   return ret;
1662 }
1663
1664 static gboolean
1665 who_row_is_separator (GtkTreeModel *model,
1666     GtkTreeIter *iter,
1667     gpointer data)
1668 {
1669   gint type;
1670
1671   gtk_tree_model_get (model, iter,
1672       COL_WHO_TYPE, &type,
1673       -1);
1674
1675   return (type == COL_TYPE_SEPARATOR);
1676 }
1677
1678 static void
1679 log_window_events_setup (EmpathyLogWindow *window)
1680 {
1681   GtkTreeView       *view;
1682   GtkTreeModel      *model;
1683   GtkTreeSelection  *selection;
1684   GtkTreeSortable   *sortable;
1685   GtkTreeViewColumn *column;
1686   GtkTreeStore      *store;
1687   GtkCellRenderer   *cell;
1688
1689   view = GTK_TREE_VIEW (window->treeview_events);
1690   selection = gtk_tree_view_get_selection (view);
1691
1692   /* new store */
1693   window->store_events = store = gtk_tree_store_new (COL_EVENTS_COUNT,
1694       G_TYPE_INT,           /* type */
1695       G_TYPE_INT64,         /* timestamp */
1696       G_TYPE_STRING,        /* stringified date */
1697       G_TYPE_STRING,        /* icon */
1698       G_TYPE_STRING,        /* name */
1699       TP_TYPE_ACCOUNT,      /* account */
1700       TPL_TYPE_ENTITY,      /* target */
1701       TPL_TYPE_EVENT);      /* event */
1702
1703   model = GTK_TREE_MODEL (store);
1704   sortable = GTK_TREE_SORTABLE (store);
1705
1706   gtk_tree_view_set_model (view, model);
1707
1708   /* new column */
1709   column = gtk_tree_view_column_new ();
1710
1711   cell = gtk_cell_renderer_pixbuf_new ();
1712   gtk_tree_view_column_pack_start (column, cell, FALSE);
1713   gtk_tree_view_column_add_attribute (column, cell,
1714       "icon-name", COL_EVENTS_ICON);
1715
1716   cell = gtk_cell_renderer_text_new ();
1717   g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1718   gtk_tree_view_column_pack_start (column, cell, TRUE);
1719   gtk_tree_view_column_add_attribute (column, cell,
1720       "text", COL_EVENTS_TEXT);
1721
1722   cell = gtk_cell_renderer_text_new ();
1723   gtk_tree_view_column_pack_end (column, cell, FALSE);
1724   gtk_tree_view_column_add_attribute (column, cell,
1725       "text", COL_EVENTS_PRETTY_DATE);
1726
1727   gtk_tree_view_append_column (view, column);
1728
1729   /* set up treeview properties */
1730   gtk_tree_selection_set_mode (selection, GTK_SELECTION_NONE);
1731   gtk_tree_view_set_headers_visible (view, FALSE);
1732
1733   gtk_tree_sortable_set_sort_column_id (sortable,
1734       COL_EVENTS_TS,
1735       GTK_SORT_ASCENDING);
1736
1737   g_object_unref (store);
1738 }
1739
1740 static void
1741 log_window_who_setup (EmpathyLogWindow *window)
1742 {
1743   GtkTreeView       *view;
1744   GtkTreeModel      *model;
1745   GtkTreeSelection  *selection;
1746   GtkTreeSortable   *sortable;
1747   GtkTreeViewColumn *column;
1748   GtkListStore      *store;
1749   GtkCellRenderer   *cell;
1750
1751   view = GTK_TREE_VIEW (window->treeview_who);
1752   selection = gtk_tree_view_get_selection (view);
1753
1754   /* new store */
1755   store = gtk_list_store_new (COL_WHO_COUNT,
1756       G_TYPE_INT,           /* type */
1757       G_TYPE_STRING,        /* icon */
1758       G_TYPE_STRING,        /* name */
1759       TP_TYPE_ACCOUNT,      /* account */
1760       TPL_TYPE_ENTITY);     /* target */
1761
1762   model = GTK_TREE_MODEL (store);
1763   sortable = GTK_TREE_SORTABLE (store);
1764
1765   gtk_tree_view_set_model (view, model);
1766
1767   /* new column */
1768   column = gtk_tree_view_column_new ();
1769   gtk_tree_view_column_set_title (column, _("Who"));
1770
1771   cell = gtk_cell_renderer_pixbuf_new ();
1772   gtk_tree_view_column_pack_start (column, cell, FALSE);
1773   gtk_tree_view_column_add_attribute (column, cell,
1774       "icon-name",
1775       COL_WHO_ICON);
1776
1777   cell = gtk_cell_renderer_text_new ();
1778   g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1779   gtk_tree_view_column_pack_start (column, cell, TRUE);
1780   gtk_tree_view_column_add_attribute (column, cell,
1781       "text",
1782       COL_WHO_NAME);
1783
1784   gtk_tree_view_append_column (view, column);
1785
1786   /* set up treeview properties */
1787   gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
1788   gtk_tree_view_set_row_separator_func (view, who_row_is_separator,
1789       NULL, NULL);
1790
1791   gtk_tree_sortable_set_sort_column_id (sortable,
1792       COL_WHO_NAME,
1793       GTK_SORT_ASCENDING);
1794   gtk_tree_sortable_set_sort_func (sortable,
1795       COL_WHO_NAME, sort_by_name,
1796       NULL, NULL);
1797
1798   /* set up signals */
1799   g_signal_connect (selection, "changed",
1800       G_CALLBACK (log_window_who_changed_cb), window);
1801
1802   g_object_unref (store);
1803 }
1804
1805 static void
1806 log_window_chats_accounts_changed_cb (GtkWidget       *combobox,
1807     EmpathyLogWindow *window)
1808 {
1809   /* Clear all current messages shown in the textview */
1810   gtk_tree_store_clear (window->store_events);
1811
1812   log_window_who_populate (window);
1813 }
1814
1815 static void
1816 log_window_chats_set_selected (EmpathyLogWindow *window)
1817 {
1818   GtkTreeView          *view;
1819   GtkTreeModel         *model;
1820   GtkTreeSelection     *selection;
1821   GtkTreeIter           iter;
1822   GtkTreePath          *path;
1823
1824   view = GTK_TREE_VIEW (window->treeview_who);
1825   model = gtk_tree_view_get_model (view);
1826   selection = gtk_tree_view_get_selection (view);
1827
1828   if (!gtk_tree_model_get_iter_first (model, &iter))
1829     return;
1830
1831   do
1832     {
1833       TpAccount   *this_account;
1834       TplEntity   *this_target;
1835       const gchar *this_chat_id;
1836       gboolean     this_is_chatroom;
1837
1838       gtk_tree_model_get (model, &iter,
1839           COL_WHO_ACCOUNT, &this_account,
1840           COL_WHO_TARGET, &this_target,
1841           -1);
1842
1843       this_chat_id = tpl_entity_get_identifier (this_target);
1844       this_is_chatroom = tpl_entity_get_entity_type (this_target)
1845           == TPL_ENTITY_ROOM;
1846
1847       if (this_account == window->selected_account &&
1848           !tp_strdiff (this_chat_id, window->selected_chat_id) &&
1849           this_is_chatroom == window->selected_is_chatroom)
1850         {
1851           gtk_tree_selection_select_iter (selection, &iter);
1852           path = gtk_tree_model_get_path (model, &iter);
1853           gtk_tree_view_scroll_to_cell (view, path, NULL, TRUE, 0.5, 0.0);
1854           gtk_tree_path_free (path);
1855           g_object_unref (this_account);
1856           g_object_unref (this_target);
1857           break;
1858         }
1859
1860       g_object_unref (this_account);
1861       g_object_unref (this_target);
1862     }
1863   while (gtk_tree_model_iter_next (model, &iter));
1864
1865   tp_clear_object (&window->selected_account);
1866   tp_clear_pointer (&window->selected_chat_id, g_free);
1867 }
1868
1869 static gint
1870 sort_by_date (GtkTreeModel *model,
1871     GtkTreeIter *a,
1872     GtkTreeIter *b,
1873     gpointer user_data)
1874 {
1875   GDate *date1, *date2;
1876
1877   gtk_tree_model_get (model, a,
1878       COL_WHEN_DATE, &date1,
1879       -1);
1880
1881   gtk_tree_model_get (model, b,
1882       COL_WHEN_DATE, &date2,
1883       -1);
1884
1885   return g_date_compare (date1, date2);
1886 }
1887
1888 static gboolean
1889 when_row_is_separator (GtkTreeModel *model,
1890     GtkTreeIter *iter,
1891     gpointer data)
1892 {
1893   gchar *when;
1894   gboolean ret;
1895
1896   gtk_tree_model_get (model, iter,
1897       COL_WHEN_TEXT, &when,
1898       -1);
1899
1900   ret = g_str_equal (when, "separator");
1901   g_free (when);
1902   return ret;
1903 }
1904
1905 static void
1906 log_window_when_changed_cb (GtkTreeSelection *selection,
1907     EmpathyLogWindow *window)
1908 {
1909   GtkTreeView *view;
1910   GtkTreeModel *model;
1911   GtkTreeIter iter;
1912
1913   DEBUG ("log_window_when_changed_cb");
1914
1915   view = gtk_tree_selection_get_tree_view (selection);
1916   model = gtk_tree_view_get_model (view);
1917
1918   /* If 'Anytime' is selected, everything else should be deselected */
1919   if (gtk_tree_model_get_iter_first (model, &iter))
1920     {
1921       if (gtk_tree_selection_iter_is_selected (selection, &iter))
1922         {
1923           g_signal_handlers_block_by_func (selection,
1924               log_window_when_changed_cb,
1925               window);
1926
1927           gtk_tree_selection_unselect_all (selection);
1928           gtk_tree_selection_select_iter (selection, &iter);
1929
1930           g_signal_handlers_unblock_by_func (selection,
1931               log_window_when_changed_cb,
1932               window);
1933         }
1934     }
1935
1936   log_window_chats_get_messages (window, FALSE);
1937 }
1938
1939 static void
1940 log_window_when_setup (EmpathyLogWindow *window)
1941 {
1942   GtkTreeView       *view;
1943   GtkTreeModel      *model;
1944   GtkTreeSelection  *selection;
1945   GtkTreeSortable   *sortable;
1946   GtkTreeViewColumn *column;
1947   GtkListStore      *store;
1948   GtkCellRenderer   *cell;
1949
1950   view = GTK_TREE_VIEW (window->treeview_when);
1951   selection = gtk_tree_view_get_selection (view);
1952
1953   /* new store */
1954   store = gtk_list_store_new (COL_WHEN_COUNT,
1955       G_TYPE_DATE,        /* date */
1956       G_TYPE_STRING,      /* stringified date */
1957       G_TYPE_STRING);     /* icon */
1958
1959   model = GTK_TREE_MODEL (store);
1960   sortable = GTK_TREE_SORTABLE (store);
1961
1962   gtk_tree_view_set_model (view, model);
1963
1964   /* new column */
1965   column = gtk_tree_view_column_new ();
1966   gtk_tree_view_column_set_title (column, _("When"));
1967
1968   cell = gtk_cell_renderer_pixbuf_new ();
1969   gtk_tree_view_column_pack_start (column, cell, FALSE);
1970   gtk_tree_view_column_add_attribute (column, cell,
1971       "icon-name", COL_WHEN_ICON);
1972
1973   cell = gtk_cell_renderer_text_new ();
1974   g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1975   gtk_tree_view_column_pack_start (column, cell, TRUE);
1976   gtk_tree_view_column_add_attribute (column, cell,
1977       "text",
1978       COL_WHEN_TEXT);
1979
1980   gtk_tree_view_append_column (view, column);
1981
1982   /* set up treeview properties */
1983   gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
1984   gtk_tree_view_set_row_separator_func (view, when_row_is_separator,
1985       NULL, NULL);
1986   gtk_tree_sortable_set_sort_column_id (sortable,
1987       COL_WHEN_DATE,
1988       GTK_SORT_DESCENDING);
1989   gtk_tree_sortable_set_sort_func (sortable,
1990       COL_WHEN_DATE, sort_by_date,
1991       NULL, NULL);
1992
1993   /* set up signals */
1994   g_signal_connect (selection, "changed",
1995       G_CALLBACK (log_window_when_changed_cb),
1996       window);
1997
1998   g_object_unref (store);
1999 }
2000
2001 static gboolean
2002 what_row_is_separator (GtkTreeModel *model,
2003     GtkTreeIter *iter,
2004     gpointer data)
2005 {
2006   gint type;
2007
2008   gtk_tree_model_get (model, iter,
2009       COL_WHAT_TYPE, &type,
2010       -1);
2011
2012   return (type == -1);
2013 }
2014
2015 static void
2016 log_window_what_changed_cb (GtkTreeSelection *selection,
2017     EmpathyLogWindow *window)
2018 {
2019   GtkTreeView *view;
2020   GtkTreeModel *model;
2021   GtkTreeIter iter;
2022
2023   DEBUG ("log_window_what_changed_cb");
2024
2025   view = gtk_tree_selection_get_tree_view (selection);
2026   model = gtk_tree_view_get_model (view);
2027
2028   /* If 'Anything' is selected, everything else should be deselected */
2029   if (gtk_tree_model_get_iter_first (model, &iter))
2030     {
2031       if (gtk_tree_selection_iter_is_selected (selection, &iter))
2032         {
2033           g_signal_handlers_block_by_func (selection,
2034               log_window_what_changed_cb,
2035               window);
2036
2037           gtk_tree_selection_unselect_all (selection);
2038           gtk_tree_selection_select_iter (selection, &iter);
2039
2040           g_signal_handlers_unblock_by_func (selection,
2041               log_window_what_changed_cb,
2042               window);
2043         }
2044     }
2045
2046   /* The dates need to be updated if we're not searching */
2047   log_window_chats_get_messages (window, window->hits == NULL);
2048 }
2049
2050 static gboolean
2051 log_window_what_collapse_row_cb (GtkTreeView *tree_view,
2052     GtkTreeIter *iter,
2053     GtkTreePath *path,
2054     gpointer user_data)
2055 {
2056   /* Reject collapsing */
2057   return TRUE;
2058 }
2059
2060 struct event
2061 {
2062   gint type;
2063   EventSubtype subtype;
2064   const gchar *icon;
2065   const gchar *text;
2066 };
2067
2068 static void
2069 log_window_what_setup (EmpathyLogWindow *window)
2070 {
2071   GtkTreeView       *view;
2072   GtkTreeModel      *model;
2073   GtkTreeSelection  *selection;
2074   GtkTreeSortable   *sortable;
2075   GtkTreeViewColumn *column;
2076   GtkTreeIter        iter, parent;
2077   GtkTreeStore      *store;
2078   GtkCellRenderer   *cell;
2079   guint i;
2080   struct event events [] = {
2081     { TPL_EVENT_MASK_ANY, 0, NULL, _("Anything") },
2082     { -1, 0, NULL, "separator" },
2083     { TPL_EVENT_MASK_TEXT, 0, "stock_text_justify", _("Text chats") },
2084     { TPL_EVENT_MASK_CALL, EVENT_CALL_ALL, "call-start", _("Calls") }
2085   };
2086   struct event call_events [] = {
2087     { TPL_EVENT_MASK_CALL, EVENT_CALL_INCOMING, "call-start", _("Incoming calls") },
2088     { TPL_EVENT_MASK_CALL, EVENT_CALL_OUTGOING, "call-start", _("Outgoing calls") },
2089     { TPL_EVENT_MASK_CALL, EVENT_CALL_MISSED, "call-stop", _("Missed calls") }
2090   };
2091
2092   view = GTK_TREE_VIEW (window->treeview_what);
2093   selection = gtk_tree_view_get_selection (view);
2094
2095   /* new store */
2096   store = gtk_tree_store_new (COL_WHAT_COUNT,
2097       G_TYPE_INT,         /* history type */
2098       G_TYPE_INT,         /* history subtype */
2099       G_TYPE_STRING,      /* stringified history type */
2100       G_TYPE_STRING,      /* icon */
2101       G_TYPE_BOOLEAN);    /* expander (hidden) */
2102
2103   model = GTK_TREE_MODEL (store);
2104   sortable = GTK_TREE_SORTABLE (store);
2105
2106   gtk_tree_view_set_model (view, model);
2107
2108   /* new column */
2109   column = gtk_tree_view_column_new ();
2110   gtk_tree_view_column_set_title (column, _("What"));
2111
2112   cell = gtk_cell_renderer_pixbuf_new ();
2113   gtk_tree_view_column_pack_start (column, cell, FALSE);
2114   gtk_tree_view_column_add_attribute (column, cell,
2115       "icon-name", COL_WHAT_ICON);
2116
2117   cell = gtk_cell_renderer_text_new ();
2118   g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
2119   gtk_tree_view_column_pack_start (column, cell, TRUE);
2120   gtk_tree_view_column_add_attribute (column, cell,
2121       "text", COL_WHAT_TEXT);
2122
2123   gtk_tree_view_append_column (view, column);
2124
2125   /* set up treeview properties */
2126   gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
2127   gtk_tree_view_set_show_expanders (view, FALSE);
2128   gtk_tree_view_set_level_indentation (view, 12);
2129   gtk_tree_view_expand_all (view);
2130   gtk_tree_view_set_row_separator_func (view, what_row_is_separator,
2131       NULL, NULL);
2132
2133   /* populate */
2134   for (i = 0; i < G_N_ELEMENTS (events); i++)
2135     {
2136       gtk_tree_store_append (store, &iter, NULL);
2137       gtk_tree_store_set (store, &iter,
2138           COL_WHAT_TYPE, events[i].type,
2139           COL_WHAT_SUBTYPE, events[i].subtype,
2140           COL_WHAT_TEXT, events[i].text,
2141           COL_WHAT_ICON, events[i].icon,
2142           -1);
2143     }
2144
2145   gtk_tree_model_iter_nth_child (model, &parent, NULL, 3);
2146   for (i = 0; i < G_N_ELEMENTS (call_events); i++)
2147     {
2148       gtk_tree_store_append (store, &iter, &parent);
2149       gtk_tree_store_set (store, &iter,
2150           COL_WHAT_TYPE, call_events[i].type,
2151           COL_WHAT_SUBTYPE, call_events[i].subtype,
2152           COL_WHAT_TEXT, call_events[i].text,
2153           COL_WHAT_ICON, call_events[i].icon,
2154           -1);
2155     }
2156
2157   gtk_tree_view_expand_all (view);
2158
2159   /* select 'Anything' */
2160   if (gtk_tree_model_get_iter_first (model, &iter))
2161     gtk_tree_selection_select_iter (selection, &iter);
2162
2163   /* set up signals */
2164   g_signal_connect (view, "test-collapse-row",
2165       G_CALLBACK (log_window_what_collapse_row_cb),
2166       NULL);
2167   g_signal_connect (selection, "changed",
2168       G_CALLBACK (log_window_what_changed_cb),
2169       window);
2170
2171   g_object_unref (store);
2172 }
2173
2174 static void
2175 log_window_got_messages_for_date_cb (GObject *manager,
2176     GAsyncResult *result,
2177     gpointer user_data)
2178 {
2179   Ctx *ctx = user_data;
2180   GList *events;
2181   GList *l;
2182   GError *error = NULL;
2183
2184   if (log_window == NULL)
2185     goto out;
2186
2187   if (log_window->count != ctx->count)
2188     goto out;
2189
2190   if (!tpl_log_manager_get_events_for_date_finish (TPL_LOG_MANAGER (manager),
2191       result, &events, &error))
2192     {
2193       DEBUG ("Unable to retrieve messages for the selected date: %s. Aborting",
2194           error->message);
2195       g_error_free (error);
2196       goto out;
2197     }
2198
2199   for (l = events; l; l = l->next)
2200     {
2201       TplEvent *event = l->data;
2202       gboolean append = TRUE;
2203
2204       if (TPL_IS_CALL_EVENT (l->data)
2205           && ctx->event_mask & TPL_EVENT_MASK_CALL
2206           && ctx->event_mask != TPL_EVENT_MASK_ANY)
2207         {
2208           TplCallEvent *call = l->data;
2209
2210           append = FALSE;
2211
2212           if (ctx->subtype & EVENT_CALL_ALL)
2213             append = TRUE;
2214           else
2215             {
2216               TplCallEndReason reason = tpl_call_event_get_end_reason (call);
2217               TplEntity *sender = tpl_event_get_sender (event);
2218               TplEntity *receiver = tpl_event_get_receiver (event);
2219
2220               if (reason == TPL_CALL_END_REASON_NO_ANSWER)
2221                 {
2222                   if (ctx->subtype & EVENT_CALL_MISSED)
2223                     append = TRUE;
2224                 }
2225               else if (ctx->subtype & EVENT_CALL_OUTGOING
2226                   && tpl_entity_get_entity_type (sender) == TPL_ENTITY_SELF)
2227                 append = TRUE;
2228               else if (ctx->subtype & EVENT_CALL_INCOMING
2229                   && tpl_entity_get_entity_type (receiver) == TPL_ENTITY_SELF)
2230                 append = TRUE;
2231             }
2232         }
2233
2234       if (append)
2235         {
2236           EmpathyMessage *msg = empathy_message_from_tpl_log_event (event);
2237           log_window_append_message (event, msg);
2238           g_object_unref (msg);
2239         }
2240
2241       g_object_unref (event);
2242     }
2243   g_list_free (events);
2244
2245  out:
2246   ctx_free (ctx);
2247
2248   _tpl_action_chain_continue (log_window->chain);
2249 }
2250
2251 static void
2252 get_events_for_date (TplActionChain *chain, gpointer user_data)
2253 {
2254   Ctx *ctx = user_data;
2255
2256   tpl_log_manager_get_events_for_date_async (ctx->window->log_manager,
2257       ctx->account, ctx->entity, ctx->event_mask,
2258       ctx->date,
2259       log_window_got_messages_for_date_cb,
2260       ctx);
2261 }
2262
2263 static void
2264 log_window_get_messages_for_dates (EmpathyLogWindow *window,
2265     GList *dates)
2266 {
2267   GList *accounts, *targets, *acc, *targ, *l;
2268   TplEventTypeMask event_mask;
2269   EventSubtype subtype;
2270   GDate *date, *anytime, *separator;
2271
2272   if (!log_window_get_selected (window,
2273       &accounts, &targets, NULL, &event_mask, &subtype))
2274     return;
2275
2276   anytime = g_date_new_dmy (2, 1, -1);
2277   separator = g_date_new_dmy (1, 1, -1);
2278
2279   _tpl_action_chain_clear (window->chain);
2280   window->count++;
2281
2282   for (acc = accounts, targ = targets;
2283        acc != NULL && targ != NULL;
2284        acc = acc->next, targ = targ->next)
2285     {
2286       TpAccount *account = acc->data;
2287       TplEntity *target = targ->data;
2288
2289       for (l = dates; l != NULL; l = l->next)
2290         {
2291           date = l->data;
2292
2293           /* Get events */
2294           if (g_date_compare (date, anytime) != 0)
2295             {
2296               Ctx *ctx;
2297
2298               ctx = ctx_new (window, account, target, date, event_mask, subtype,
2299                   window->count);
2300               _tpl_action_chain_append (window->chain, get_events_for_date, ctx);
2301             }
2302           else
2303             {
2304               GtkTreeView *view = GTK_TREE_VIEW (window->treeview_when);
2305               GtkTreeModel *model = gtk_tree_view_get_model (view);
2306               GtkTreeIter iter;
2307               gboolean next;
2308               GDate *d;
2309
2310               for (next = gtk_tree_model_get_iter_first (model, &iter);
2311                    next;
2312                    next = gtk_tree_model_iter_next (model, &iter))
2313                 {
2314                   Ctx *ctx;
2315
2316                   gtk_tree_model_get (model, &iter,
2317                       COL_WHEN_DATE, &d,
2318                       -1);
2319
2320                   if (g_date_compare (d, anytime) != 0 &&
2321                       g_date_compare (d, separator) != 0)
2322                     {
2323                       ctx = ctx_new (window, account, target, d,
2324                           event_mask, subtype, window->count);
2325                       _tpl_action_chain_append (window->chain, get_events_for_date, ctx);
2326                     }
2327                 }
2328             }
2329         }
2330     }
2331
2332   _tpl_action_chain_start (window->chain);
2333
2334   g_list_free_full (accounts, g_object_unref);
2335   g_list_free_full (targets, g_object_unref);
2336   g_date_free (separator);
2337   g_date_free (anytime);
2338 }
2339
2340 static void
2341 log_manager_got_dates_cb (GObject *manager,
2342     GAsyncResult *result,
2343     gpointer user_data)
2344 {
2345   Ctx *ctx = user_data;
2346   GtkTreeView *view;
2347   GtkTreeModel *model;
2348   GtkTreeSelection *selection;
2349   GtkListStore *store;
2350   GtkTreeIter iter;
2351   GList *dates;
2352   GList *l;
2353   GError *error = NULL;
2354
2355   if (log_window == NULL)
2356     goto out;
2357
2358   if (log_window->count != ctx->count)
2359     goto out;
2360
2361   if (!tpl_log_manager_get_dates_finish (TPL_LOG_MANAGER (manager),
2362        result, &dates, &error))
2363     {
2364       DEBUG ("Unable to retrieve messages' dates: %s. Aborting",
2365           error->message);
2366       goto out;
2367     }
2368
2369   view = GTK_TREE_VIEW (log_window->treeview_when);
2370   model = gtk_tree_view_get_model (view);
2371   store = GTK_LIST_STORE (model);
2372   selection = gtk_tree_view_get_selection (view);
2373
2374   for (l = dates; l != NULL; l = l->next)
2375     {
2376       GDate *date = l->data;
2377
2378       /* Add the date if it's not already there */
2379       has_element = FALSE;
2380       gtk_tree_model_foreach (model, model_has_date, date);
2381       if (!has_element)
2382         {
2383           gchar *text = format_date_for_display (date);
2384
2385           gtk_list_store_append (store, &iter);
2386           gtk_list_store_set (store, &iter,
2387               COL_WHEN_DATE, date,
2388               COL_WHEN_TEXT, text,
2389               COL_WHEN_ICON, CALENDAR_ICON,
2390               -1);
2391
2392           g_free (text);
2393         }
2394     }
2395
2396   if (gtk_tree_model_get_iter_first (model, &iter))
2397     {
2398       gchar *separator = NULL;
2399
2400       if (gtk_tree_model_iter_next (model, &iter))
2401         {
2402           gtk_tree_model_get (model, &iter,
2403               COL_WHEN_TEXT, &separator,
2404               -1);
2405         }
2406
2407       if (g_strcmp0 (separator, "separator") != 0)
2408         {
2409           gtk_list_store_prepend (store, &iter);
2410           gtk_list_store_set (store, &iter,
2411               COL_WHEN_DATE, g_date_new_dmy (1, 1, -1),
2412               COL_WHEN_TEXT, "separator",
2413               -1);
2414
2415           gtk_list_store_prepend (store, &iter);
2416           gtk_list_store_set (store, &iter,
2417               COL_WHEN_DATE, g_date_new_dmy (2, 1, -1),
2418               COL_WHEN_TEXT, _("Anytime"),
2419               -1);
2420         }
2421     }
2422
2423   g_list_free_full (dates, g_free);
2424  out:
2425   ctx_free (ctx);
2426   _tpl_action_chain_continue (log_window->chain);
2427 }
2428
2429 static void
2430 select_first_date (TplActionChain *chain, gpointer user_data)
2431 {
2432   GtkTreeView *view;
2433   GtkTreeModel *model;
2434   GtkTreeSelection *selection;
2435   GtkTreeIter iter;
2436
2437   view = GTK_TREE_VIEW (log_window->treeview_when);
2438   model = gtk_tree_view_get_model (view);
2439   selection = gtk_tree_view_get_selection (view);
2440
2441   /* Show messages of the most recent date */
2442   if (gtk_tree_model_get_iter_first (model, &iter))
2443     gtk_tree_selection_select_iter (selection, &iter);
2444
2445   _tpl_action_chain_continue (log_window->chain);
2446 }
2447
2448 static void
2449 get_dates_for_entity (TplActionChain *chain, gpointer user_data)
2450 {
2451   Ctx *ctx = user_data;
2452
2453   tpl_log_manager_get_dates_async (ctx->window->log_manager,
2454       ctx->account, ctx->entity, ctx->event_mask,
2455       log_manager_got_dates_cb, ctx);
2456 }
2457
2458 static void
2459 log_window_chats_get_messages (EmpathyLogWindow *window,
2460     gboolean force_get_dates)
2461 {
2462   GList *accounts, *targets, *dates;
2463   TplEventTypeMask event_mask;
2464   GtkTreeView *view;
2465   GtkTreeModel *model;
2466   GtkListStore *store;
2467   GtkTreeSelection *selection;
2468
2469   if (!log_window_get_selected (window, &accounts, &targets,
2470       &dates, &event_mask, NULL))
2471     return;
2472
2473   view = GTK_TREE_VIEW (window->treeview_when);
2474   selection = gtk_tree_view_get_selection (view);
2475   model = gtk_tree_view_get_model (view);
2476   store = GTK_LIST_STORE (model);
2477
2478   /* Clear all current messages shown in the textview */
2479   gtk_tree_store_clear (window->store_events);
2480
2481   _tpl_action_chain_clear (window->chain);
2482   window->count++;
2483
2484   /* If there's a search use the returned hits */
2485   if (window->hits != NULL)
2486     {
2487       if (force_get_dates)
2488         {
2489           g_signal_handlers_block_by_func (selection,
2490               log_window_when_changed_cb,
2491               window);
2492
2493           gtk_list_store_clear (store);
2494
2495           g_signal_handlers_unblock_by_func (selection,
2496               log_window_when_changed_cb,
2497               window);
2498
2499           populate_dates_from_search_hits (accounts, targets);
2500         }
2501       else
2502         populate_events_from_search_hits (accounts, targets, dates);
2503     }
2504   /* Either use the supplied date or get the last */
2505   else if (force_get_dates || dates == NULL)
2506     {
2507       GList *acc, *targ;
2508
2509       g_signal_handlers_block_by_func (selection,
2510           log_window_when_changed_cb,
2511           window);
2512
2513       gtk_list_store_clear (store);
2514
2515       g_signal_handlers_unblock_by_func (selection,
2516           log_window_when_changed_cb,
2517           window);
2518
2519       /* Get a list of dates and show them on the treeview */
2520       for (targ = targets, acc = accounts;
2521            targ != NULL && acc != NULL;
2522            targ = targ->next, acc = acc->next)
2523         {
2524           TpAccount *account = acc->data;
2525           TplEntity *target = targ->data;
2526           Ctx *ctx = ctx_new (window, account, target, NULL, event_mask, 0,
2527               window->count);
2528
2529           _tpl_action_chain_append (window->chain, get_dates_for_entity, ctx);
2530         }
2531       _tpl_action_chain_append (window->chain, select_first_date, NULL);
2532       _tpl_action_chain_start (window->chain);
2533     }
2534   else
2535     {
2536       /* Show messages of the selected date */
2537       log_window_get_messages_for_dates (window, dates);
2538     }
2539
2540   g_list_free_full (accounts, g_object_unref);
2541   g_list_free_full (targets, g_object_unref);
2542   g_list_free_full (dates, (GFreeFunc) g_date_free);
2543 }
2544
2545 typedef struct {
2546   EmpathyAccountChooserFilterResultCallback callback;
2547   gpointer user_data;
2548 } FilterCallbackData;
2549
2550 static void
2551 got_entities (GObject *manager,
2552     GAsyncResult *result,
2553     gpointer user_data)
2554 {
2555   FilterCallbackData *data = user_data;
2556   GList *entities;
2557   GError *error = NULL;
2558
2559   if (!tpl_log_manager_get_entities_finish (TPL_LOG_MANAGER (manager),
2560       result, &entities, &error))
2561     {
2562       DEBUG ("Could not get entities: %s", error->message);
2563       g_error_free (error);
2564       data->callback (FALSE, data->user_data);
2565     }
2566   else
2567     {
2568       data->callback (entities != NULL, data->user_data);
2569
2570       g_list_free_full (entities, g_object_unref);
2571   }
2572
2573   g_slice_free (FilterCallbackData, data);
2574 }
2575
2576 static void
2577 empathy_account_chooser_filter_has_logs (TpAccount *account,
2578     EmpathyAccountChooserFilterResultCallback callback,
2579     gpointer callback_data,
2580     gpointer user_data)
2581 {
2582   TplLogManager *manager = tpl_log_manager_dup_singleton ();
2583   FilterCallbackData *cb_data = g_slice_new0 (FilterCallbackData);
2584
2585   cb_data->callback = callback;
2586   cb_data->user_data = callback_data;
2587
2588   tpl_log_manager_get_entities_async (manager, account, got_entities, cb_data);
2589
2590   g_object_unref (manager);
2591 }
2592
2593 static void
2594 log_window_logger_clear_account_cb (TpProxy *proxy,
2595     const GError *error,
2596     gpointer user_data,
2597     GObject *weak_object)
2598 {
2599   EmpathyLogWindow *window = user_data;
2600
2601   if (error != NULL)
2602     g_warning ("Error when clearing logs: %s", error->message);
2603
2604   /* Refresh the log viewer so the logs are cleared if the account
2605    * has been deleted */
2606   gtk_tree_store_clear (window->store_events);
2607   log_window_who_populate (window);
2608
2609   /* Re-filter the account chooser so the accounts without logs get greyed out */
2610   empathy_account_chooser_set_filter (
2611       EMPATHY_ACCOUNT_CHOOSER (window->account_chooser),
2612       empathy_account_chooser_filter_has_logs, NULL);
2613 }
2614
2615 static void
2616 log_window_clear_logs_chooser_select_account (EmpathyAccountChooser *chooser,
2617     EmpathyLogWindow *window)
2618 {
2619   EmpathyAccountChooser *account_chooser;
2620
2621   account_chooser = EMPATHY_ACCOUNT_CHOOSER (window->account_chooser);
2622
2623   empathy_account_chooser_set_account (chooser,
2624       empathy_account_chooser_get_account (account_chooser));
2625 }
2626
2627 static void
2628 log_window_delete_menu_clicked_cb (GtkMenuItem *menuitem,
2629     EmpathyLogWindow *window)
2630 {
2631   GtkWidget *dialog, *content_area, *hbox, *label;
2632   EmpathyAccountChooser *account_chooser;
2633   gint response_id;
2634   TpDBusDaemon *bus;
2635   TpProxy *logger;
2636   GError *error = NULL;
2637
2638   account_chooser = (EmpathyAccountChooser *) empathy_account_chooser_new ();
2639   empathy_account_chooser_set_has_all_option (account_chooser, TRUE);
2640   empathy_account_chooser_set_filter (account_chooser,
2641       empathy_account_chooser_filter_has_logs, NULL);
2642
2643   /* Select the same account as in the history window */
2644   if (empathy_account_chooser_is_ready (account_chooser))
2645     log_window_clear_logs_chooser_select_account (account_chooser, window);
2646   else
2647     g_signal_connect (account_chooser, "ready",
2648         G_CALLBACK (log_window_clear_logs_chooser_select_account), window);
2649
2650   dialog = gtk_message_dialog_new_with_markup (GTK_WINDOW (window->window),
2651       GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING,
2652       GTK_BUTTONS_NONE,
2653       _("Are you sure you want to delete all logs of previous conversations?"));
2654
2655   gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2656       GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2657       _("Clear All"), GTK_RESPONSE_APPLY,
2658       NULL);
2659
2660   content_area = gtk_message_dialog_get_message_area (
2661       GTK_MESSAGE_DIALOG (dialog));
2662
2663   hbox = gtk_hbox_new (FALSE, 6);
2664   label = gtk_label_new (_("Delete from:"));
2665   gtk_box_pack_start (GTK_BOX (hbox), label,
2666       FALSE, FALSE, 0);
2667   gtk_box_pack_start (GTK_BOX (hbox), GTK_WIDGET (account_chooser),
2668       FALSE, FALSE, 0);
2669   gtk_box_pack_start (GTK_BOX (content_area), hbox,
2670       FALSE, FALSE, 0);
2671
2672   gtk_widget_show_all (hbox);
2673
2674   response_id = gtk_dialog_run (GTK_DIALOG (dialog));
2675
2676   if (response_id != GTK_RESPONSE_APPLY)
2677     goto out;
2678
2679   bus = tp_dbus_daemon_dup (&error);
2680   if (error != NULL)
2681     {
2682       g_warning ("Could not delete logs: %s", error->message);
2683       g_error_free (error);
2684       goto out;
2685     }
2686
2687   logger = g_object_new (TP_TYPE_PROXY,
2688       "bus-name", "org.freedesktop.Telepathy.Logger",
2689       "object-path", "/org/freedesktop/Telepathy/Logger",
2690       "dbus-daemon", bus,
2691       NULL);
2692   g_object_unref (bus);
2693
2694   tp_proxy_add_interface_by_id (logger, EMP_IFACE_QUARK_LOGGER);
2695
2696   if (empathy_account_chooser_has_all_selected (account_chooser))
2697     {
2698       DEBUG ("Deleting logs for all the accounts");
2699
2700       emp_cli_logger_call_clear (logger, -1,
2701           log_window_logger_clear_account_cb,
2702           window, NULL, G_OBJECT (window->window));
2703     }
2704   else
2705     {
2706       TpAccount *account;
2707
2708       account = empathy_account_chooser_get_account (account_chooser);
2709
2710       DEBUG ("Deleting logs for %s", tp_proxy_get_object_path (account));
2711
2712       emp_cli_logger_call_clear_account (logger, -1,
2713           tp_proxy_get_object_path (account),
2714           log_window_logger_clear_account_cb,
2715           window, NULL, G_OBJECT (window->window));
2716     }
2717
2718   g_object_unref (logger);
2719  out:
2720   gtk_widget_destroy (dialog);
2721 }