2 * Copyright (C) 2006-2007 Imendio AB
3 * Copyright (C) 2007-2011 Collabora Ltd.
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.
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.
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
20 * Authors: Martyn Russell <martyn@imendio.com>
21 * Xavier Claessens <xclaesse@gmail.com>
22 * Emilio Pozuelo Monfort <emilio.pozuelo@collabora.co.uk>
30 #include <glib/gi18n-lib.h>
33 #include <telepathy-glib/telepathy-glib.h>
34 #include <telepathy-glib/proxy-subclass.h>
36 #include <telepathy-yell/telepathy-yell.h>
38 #include <telepathy-logger/telepathy-logger.h>
40 # include <telepathy-logger/call-event.h>
43 #include <extensions/extensions.h>
45 #include <libempathy/action-chain-internal.h>
46 #include <libempathy/empathy-chatroom-manager.h>
47 #include <libempathy/empathy-chatroom.h>
48 #include <libempathy/empathy-message.h>
49 #include <libempathy/empathy-request-util.h>
50 #include <libempathy/empathy-utils.h>
51 #include <libempathy/empathy-time.h>
53 #include "empathy-log-window.h"
54 #include "empathy-account-chooser.h"
55 #include "empathy-call-utils.h"
56 #include "empathy-chat-view.h"
57 #include "empathy-contact-dialogs.h"
58 #include "empathy-images.h"
59 #include "empathy-theme-manager.h"
60 #include "empathy-ui-utils.h"
62 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
63 #include <libempathy/empathy-debug.h>
65 G_DEFINE_TYPE (EmpathyLogWindow, empathy_log_window, GTK_TYPE_WINDOW);
67 struct _EmpathyLogWindowPriv
71 GtkWidget *button_profile;
72 GtkWidget *button_chat;
73 GtkWidget *button_call;
74 GtkWidget *button_video;
76 GtkWidget *search_entry;
81 GtkWidget *treeview_who;
82 GtkWidget *treeview_what;
83 GtkWidget *treeview_when;
84 GtkWidget *treeview_events;
86 GtkTreeStore *store_events;
88 GtkWidget *account_chooser;
92 /* List of selected GDates, free with g_list_free_full (l, g_date_free) */
95 TplActionChain *chain;
96 TplLogManager *log_manager;
98 /* Hash of TpChannel<->TpAccount for use by the observer until we can
99 * get a TpAccount from a TpConnection or wherever */
100 GHashTable *channels;
101 TpBaseClient *observer;
103 EmpathyContact *selected_contact;
105 /* Used to cancel logger calls when no longer needed */
108 /* List of owned TplLogSearchHits, free with tpl_log_search_hit_free */
112 /* Only used while waiting for the account chooser to be ready */
113 TpAccount *selected_account;
114 gchar *selected_chat_id;
115 gboolean selected_is_chatroom;
118 static void log_window_search_entry_changed_cb (GtkWidget *entry,
119 EmpathyLogWindow *self);
120 static void log_window_search_entry_activate_cb (GtkWidget *widget,
121 EmpathyLogWindow *self);
122 static void log_window_search_entry_icon_pressed_cb (GtkEntry *entry,
123 GtkEntryIconPosition icon_pos,
126 static void log_window_who_populate (EmpathyLogWindow *self);
127 static void log_window_who_setup (EmpathyLogWindow *self);
128 static void log_window_when_setup (EmpathyLogWindow *self);
129 static void log_window_what_setup (EmpathyLogWindow *self);
130 static void log_window_events_setup (EmpathyLogWindow *self);
131 static void log_window_chats_accounts_changed_cb (GtkWidget *combobox,
132 EmpathyLogWindow *self);
133 static void log_window_chats_set_selected (EmpathyLogWindow *self);
134 static void log_window_chats_get_messages (EmpathyLogWindow *self,
135 gboolean force_get_dates);
136 static void log_window_when_changed_cb (GtkTreeSelection *selection,
137 EmpathyLogWindow *self);
138 static void log_window_delete_menu_clicked_cb (GtkMenuItem *menuitem,
139 EmpathyLogWindow *self);
140 static void start_spinner (void);
142 static void log_window_create_observer (EmpathyLogWindow *window);
145 empathy_account_chooser_filter_has_logs (TpAccount *account,
146 EmpathyAccountChooserFilterResultCallback callback,
147 gpointer callback_data,
197 COL_EVENTS_PRETTY_DATE,
206 #define CALENDAR_ICON "stock_calendar"
208 /* Seconds between two messages to be considered one conversation */
209 #define MAX_GAP 30*60
211 #define WHAT_TYPE_SEPARATOR -1
215 EVENT_CALL_INCOMING = 1 << 0,
216 EVENT_CALL_OUTGOING = 1 << 1,
217 EVENT_CALL_MISSED = 1 << 2,
218 EVENT_CALL_ALL = 1 << 3,
222 log_window_get_selected (EmpathyLogWindow *window,
227 TplEventTypeMask *event_mask,
228 EventSubtype *subtype);
230 static EmpathyLogWindow *log_window = NULL;
232 static gboolean has_element;
235 #define _date_copy(d) g_date_new_julian (g_date_get_julian (d))
240 EmpathyLogWindow *self;
244 TplEventTypeMask event_mask;
245 EventSubtype subtype;
250 ctx_new (EmpathyLogWindow *self,
254 TplEventTypeMask event_mask,
255 EventSubtype subtype,
258 Ctx *ctx = g_slice_new0 (Ctx);
262 ctx->account = g_object_ref (account);
264 ctx->entity = g_object_ref (entity);
266 ctx->date = _date_copy (date);
267 ctx->event_mask = event_mask;
268 ctx->subtype = subtype;
277 tp_clear_object (&ctx->account);
278 tp_clear_object (&ctx->entity);
279 tp_clear_pointer (&ctx->date, g_date_free);
281 g_slice_free (Ctx, ctx);
285 account_chooser_ready_cb (EmpathyAccountChooser *chooser,
286 EmpathyLogWindow *self)
288 /* We'll display the account once the model has been populate with the chats
289 * of this account. */
290 empathy_account_chooser_set_account (EMPATHY_ACCOUNT_CHOOSER (
291 self->priv->account_chooser), self->priv->selected_account);
295 select_account_once_ready (EmpathyLogWindow *self,
297 const gchar *chat_id,
298 gboolean is_chatroom)
300 EmpathyAccountChooser *account_chooser;
302 account_chooser = EMPATHY_ACCOUNT_CHOOSER (self->priv->account_chooser);
304 tp_clear_object (&self->priv->selected_account);
305 self->priv->selected_account = g_object_ref (account);
307 g_free (self->priv->selected_chat_id);
308 self->priv->selected_chat_id = g_strdup (chat_id);
310 self->priv->selected_is_chatroom = is_chatroom;
312 if (empathy_account_chooser_is_ready (account_chooser))
313 account_chooser_ready_cb (account_chooser, self);
315 /* Chat will be selected once the account chooser is ready */
316 g_signal_connect (account_chooser, "ready",
317 G_CALLBACK (account_chooser_ready_cb), self);
321 toolbutton_profile_clicked (GtkToolButton *toolbutton,
322 EmpathyLogWindow *self)
324 g_return_if_fail (self != NULL);
325 g_return_if_fail (EMPATHY_IS_CONTACT (self->priv->selected_contact));
327 empathy_contact_information_dialog_show (self->priv->selected_contact,
332 toolbutton_chat_clicked (GtkToolButton *toolbutton,
333 EmpathyLogWindow *self)
335 g_return_if_fail (self != NULL);
336 g_return_if_fail (EMPATHY_IS_CONTACT (self->priv->selected_contact));
338 empathy_chat_with_contact (self->priv->selected_contact,
339 gtk_get_current_event_time ());
343 toolbutton_av_clicked (GtkToolButton *toolbutton,
344 EmpathyLogWindow *self)
348 g_return_if_fail (self != NULL);
349 g_return_if_fail (EMPATHY_IS_CONTACT (self->priv->selected_contact));
351 video = (GTK_WIDGET (toolbutton) == self->priv->button_video);
353 empathy_call_new_with_streams (
354 empathy_contact_get_id (self->priv->selected_contact),
355 empathy_contact_get_account (self->priv->selected_contact),
356 TRUE, video, gtk_get_current_event_time ());
360 empathy_log_window_constructor (GType type,
362 GObjectConstructParam *props)
366 if (log_window != NULL)
368 retval = (GObject *) log_window;
372 retval = G_OBJECT_CLASS (empathy_log_window_parent_class)
373 ->constructor (type, n_props, props);
375 log_window = EMPATHY_LOG_WINDOW (retval);
376 g_object_add_weak_pointer (retval, (gpointer) &log_window);
383 empathy_log_window_dispose (GObject *object)
385 EmpathyLogWindow *self = EMPATHY_LOG_WINDOW (object);
387 if (self->priv->source != 0)
389 g_source_remove (self->priv->source);
390 self->priv->source = 0;
393 if (self->priv->current_dates != NULL)
395 g_list_free_full (self->priv->current_dates,
396 (GDestroyNotify) g_date_free);
397 self->priv->current_dates = NULL;
400 tp_clear_pointer (&self->priv->chain, _tpl_action_chain_free);
401 tp_clear_pointer (&self->priv->channels, g_hash_table_unref);
403 tp_clear_object (&self->priv->observer);
404 tp_clear_object (&self->priv->log_manager);
405 tp_clear_object (&self->priv->selected_account);
406 tp_clear_object (&self->priv->selected_contact);
408 G_OBJECT_CLASS (empathy_log_window_parent_class)->dispose (object);
412 empathy_log_window_finalize (GObject *object)
414 EmpathyLogWindow *self = EMPATHY_LOG_WINDOW (object);
416 g_free (self->priv->last_find);
417 g_free (self->priv->selected_chat_id);
419 G_OBJECT_CLASS (empathy_log_window_parent_class)->finalize (object);
423 empathy_log_window_class_init (
424 EmpathyLogWindowClass *empathy_log_window_class)
426 GObjectClass *object_class = G_OBJECT_CLASS (empathy_log_window_class);
428 g_type_class_add_private (empathy_log_window_class,
429 sizeof (EmpathyLogWindowPriv));
431 object_class->constructor = empathy_log_window_constructor;
432 object_class->dispose = empathy_log_window_dispose;
433 object_class->finalize = empathy_log_window_finalize;
437 empathy_log_window_init (EmpathyLogWindow *self)
439 EmpathyAccountChooser *account_chooser;
442 GtkWidget *vbox, *accounts, *search, *label, *quit;
444 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
445 EMPATHY_TYPE_LOG_WINDOW, EmpathyLogWindowPriv);
447 self->priv->chain = _tpl_action_chain_new_async (NULL, NULL, NULL);
449 self->priv->log_manager = tpl_log_manager_dup_singleton ();
451 gtk_window_set_title (GTK_WINDOW (self), _("History"));
452 gtk_widget_set_can_focus (GTK_WIDGET (self), FALSE);
453 gtk_window_set_default_size (GTK_WINDOW (self), 800, 600);
455 filename = empathy_file_lookup ("empathy-log-window.ui", "libempathy-gtk");
456 gui = empathy_builder_get_file (filename,
457 "vbox1", &self->priv->vbox,
458 "toolbutton_profile", &self->priv->button_profile,
459 "toolbutton_chat", &self->priv->button_chat,
460 "toolbutton_call", &self->priv->button_call,
461 "toolbutton_video", &self->priv->button_video,
462 "toolbutton_accounts", &accounts,
463 "toolbutton_search", &search,
464 "imagemenuitem_quit", &quit,
465 "treeview_who", &self->priv->treeview_who,
466 "treeview_what", &self->priv->treeview_what,
467 "treeview_when", &self->priv->treeview_when,
468 "treeview_events", &self->priv->treeview_events,
469 "notebook", &self->priv->notebook,
470 "spinner", &self->priv->spinner,
474 empathy_builder_connect (gui, self,
475 "toolbutton_profile", "clicked", toolbutton_profile_clicked,
476 "toolbutton_chat", "clicked", toolbutton_chat_clicked,
477 "toolbutton_call", "clicked", toolbutton_av_clicked,
478 "toolbutton_video", "clicked", toolbutton_av_clicked,
479 "imagemenuitem_delete", "activate", log_window_delete_menu_clicked_cb,
482 gtk_container_add (GTK_CONTAINER (self), self->priv->vbox);
484 g_object_unref (gui);
486 g_signal_connect_swapped (quit, "activate",
487 G_CALLBACK (gtk_widget_destroy), self);
489 /* Account chooser for chats */
490 vbox = gtk_vbox_new (FALSE, 3);
492 self->priv->account_chooser = empathy_account_chooser_new ();
493 account_chooser = EMPATHY_ACCOUNT_CHOOSER (self->priv->account_chooser);
494 empathy_account_chooser_set_has_all_option (account_chooser, TRUE);
495 empathy_account_chooser_set_filter (account_chooser,
496 empathy_account_chooser_filter_has_logs, NULL);
497 empathy_account_chooser_set_all (account_chooser);
499 g_signal_connect (self->priv->account_chooser, "changed",
500 G_CALLBACK (log_window_chats_accounts_changed_cb),
503 label = gtk_label_new (_("Show"));
505 gtk_box_pack_start (GTK_BOX (vbox),
506 self->priv->account_chooser,
509 gtk_box_pack_start (GTK_BOX (vbox),
513 gtk_widget_show_all (vbox);
514 gtk_container_add (GTK_CONTAINER (accounts), vbox);
517 vbox = gtk_vbox_new (FALSE, 3);
519 self->priv->search_entry = gtk_entry_new ();
520 gtk_entry_set_icon_from_stock (GTK_ENTRY (self->priv->search_entry),
521 GTK_ENTRY_ICON_PRIMARY, GTK_STOCK_FIND);
522 gtk_entry_set_icon_from_stock (GTK_ENTRY (self->priv->search_entry),
523 GTK_ENTRY_ICON_SECONDARY, GTK_STOCK_CLEAR);
525 label = gtk_label_new (_("Search"));
527 gtk_box_pack_start (GTK_BOX (vbox),
528 self->priv->search_entry,
531 gtk_box_pack_start (GTK_BOX (vbox),
535 gtk_widget_show_all (vbox);
536 gtk_container_add (GTK_CONTAINER (search), vbox);
538 g_signal_connect (self->priv->search_entry, "changed",
539 G_CALLBACK (log_window_search_entry_changed_cb),
542 g_signal_connect (self->priv->search_entry, "activate",
543 G_CALLBACK (log_window_search_entry_activate_cb),
546 g_signal_connect (self->priv->search_entry, "icon-press",
547 G_CALLBACK (log_window_search_entry_icon_pressed_cb),
551 log_window_events_setup (self);
552 log_window_who_setup (self);
553 log_window_what_setup (self);
554 log_window_when_setup (self);
556 log_window_create_observer (self);
558 log_window_who_populate (self);
560 gtk_widget_show (GTK_WIDGET (self));
564 empathy_log_window_show (TpAccount *account,
565 const gchar *chat_id,
566 gboolean is_chatroom,
569 log_window = g_object_new (EMPATHY_TYPE_LOG_WINDOW, NULL);
571 gtk_window_present (GTK_WINDOW (log_window));
573 if (account != NULL && chat_id != NULL)
574 select_account_once_ready (log_window, account, chat_id, is_chatroom);
577 gtk_window_set_transient_for (GTK_WINDOW (log_window),
578 GTK_WINDOW (parent));
580 return GTK_WIDGET (log_window);
584 account_equal (TpAccount *a,
587 return g_str_equal (tp_proxy_get_object_path (a),
588 tp_proxy_get_object_path (b));
592 entity_equal (TplEntity *a,
595 return g_str_equal (tpl_entity_get_identifier (a),
596 tpl_entity_get_identifier (b));
600 is_same_confroom (TplEvent *e1,
603 TplEntity *sender1 = tpl_event_get_sender (e1);
604 TplEntity *receiver1 = tpl_event_get_receiver (e1);
605 TplEntity *sender2 = tpl_event_get_sender (e2);
606 TplEntity *receiver2 = tpl_event_get_receiver (e2);
607 TplEntity *room1, *room2;
609 if (receiver1 == NULL || receiver2 == NULL)
612 if (tpl_entity_get_entity_type (sender1) == TPL_ENTITY_ROOM)
614 else if (tpl_entity_get_entity_type (receiver1) == TPL_ENTITY_ROOM)
619 if (tpl_entity_get_entity_type (sender2) == TPL_ENTITY_ROOM)
621 else if (tpl_entity_get_entity_type (receiver2) == TPL_ENTITY_ROOM)
626 return g_str_equal (tpl_entity_get_identifier (room1),
627 tpl_entity_get_identifier (room2));
631 maybe_refresh_logs (TpChannel *channel,
634 GList *accounts = NULL, *entities = NULL, *dates = NULL;
636 TplEventTypeMask event_mask;
637 GDate *anytime = NULL, *today = NULL;
638 GDateTime *now = NULL;
639 gboolean refresh = FALSE;
643 if (!log_window_get_selected (log_window,
644 &accounts, &entities, &anyone, &dates, &event_mask, NULL))
646 DEBUG ("Could not get selected rows");
650 type = tp_channel_get_channel_type (channel);
652 /* If the channel type is not in the What pane, whatever has happened
653 * won't be displayed in the events pane. */
654 if (!tp_strdiff (type, TP_IFACE_CHANNEL_TYPE_TEXT) &&
655 !(event_mask & TPL_EVENT_MASK_TEXT))
657 if ((!tp_strdiff (type, TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA) ||
658 !tp_strdiff (type, TPY_IFACE_CHANNEL_TYPE_CALL)) &&
659 !(event_mask & TPL_EVENT_MASK_CALL))
662 anytime = g_date_new_dmy (2, 1, -1);
663 now = g_date_time_new_now_local ();
664 today = g_date_new_dmy (g_date_time_get_day_of_month (now),
665 g_date_time_get_month (now),
666 g_date_time_get_year (now));
668 /* If Today (or anytime) isn't selected, anything that has happened now
669 * won't be displayed. */
670 if (!g_list_find_custom (dates, anytime, (GCompareFunc) g_date_compare) &&
671 !g_list_find_custom (dates, today, (GCompareFunc) g_date_compare))
680 for (acc = accounts, ent = entities;
681 acc != NULL && ent != NULL;
682 acc = g_list_next (acc), ent = g_list_next (ent))
684 if (!account_equal (account, acc->data))
687 if (!tp_strdiff (tp_channel_get_identifier (channel),
688 tpl_entity_get_identifier (ent->data)))
696 tp_clear_pointer (&anytime, g_date_free);
697 tp_clear_pointer (&today, g_date_free);
698 tp_clear_pointer (&now, g_date_time_unref);
699 g_list_free_full (accounts, g_object_unref);
700 g_list_free_full (entities, g_object_unref);
701 g_list_free_full (dates, (GFreeFunc) g_date_free);
705 DEBUG ("Refreshing logs after received event");
707 /* FIXME: We need to populate the entities in case we
708 * didn't have any previous logs with this contact. */
709 log_window_chats_get_messages (log_window, FALSE);
714 on_msg_sent (TpTextChannel *channel,
715 TpSignalledMessage *message,
718 EmpathyLogWindow *self)
720 TpAccount *account = g_hash_table_lookup (self->priv->channels, channel);
722 maybe_refresh_logs (TP_CHANNEL (channel), account);
726 on_msg_received (TpTextChannel *channel,
727 TpSignalledMessage *message,
728 EmpathyLogWindow *self)
730 TpMessage *msg = TP_MESSAGE (message);
731 TpChannelTextMessageType type = tp_message_get_message_type (msg);
732 TpAccount *account = g_hash_table_lookup (self->priv->channels, channel);
734 if (type != TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL &&
735 type != TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION)
738 maybe_refresh_logs (TP_CHANNEL (channel), account);
742 on_channel_ended (TpChannel *channel,
746 EmpathyLogWindow *self)
748 if (self->priv->channels != NULL)
749 g_hash_table_remove (self->priv->channels, channel);
753 on_call_ended (TpChannel *channel,
757 EmpathyLogWindow *self)
759 TpAccount *account = g_hash_table_lookup (self->priv->channels, channel);
761 maybe_refresh_logs (channel, account);
763 if (self->priv->channels != NULL)
764 g_hash_table_remove (self->priv->channels, channel);
768 observe_channels (TpSimpleObserver *observer,
770 TpConnection *connection,
772 TpChannelDispatchOperation *dispatch_operation,
774 TpObserveChannelsContext *context,
777 EmpathyLogWindow *self = user_data;
781 for (l = channels; l != NULL; l = g_list_next (l))
783 TpChannel *channel = l->data;
784 const gchar *type = tp_channel_get_channel_type (channel);
786 if (!tp_strdiff (type, TP_IFACE_CHANNEL_TYPE_TEXT))
788 TpTextChannel *text_channel = TP_TEXT_CHANNEL (channel);
790 g_hash_table_insert (self->priv->channels,
791 g_object_ref (channel), g_object_ref (account));
793 tp_g_signal_connect_object (text_channel, "message-sent",
794 G_CALLBACK (on_msg_sent), self, 0);
795 tp_g_signal_connect_object (text_channel, "message-received",
796 G_CALLBACK (on_msg_received), self, 0);
797 tp_g_signal_connect_object (channel, "invalidated",
798 G_CALLBACK (on_channel_ended), self, 0);
800 else if (!tp_strdiff (type, TPY_IFACE_CHANNEL_TYPE_CALL) ||
801 !tp_strdiff (type, TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA))
803 g_hash_table_insert (self->priv->channels,
804 g_object_ref (channel), g_object_ref (account));
806 tp_g_signal_connect_object (channel, "invalidated",
807 G_CALLBACK (on_call_ended), self, 0);
811 g_warning ("Unknown channel type: %s", type);
815 tp_observe_channels_context_accept (context);
819 log_window_create_observer (EmpathyLogWindow *self)
822 GError *error = NULL;
824 dbus = tp_dbus_daemon_dup (&error);
828 DEBUG ("Could not connect to the bus: %s", error->message);
829 g_error_free (error);
833 self->priv->observer = tp_simple_observer_new (dbus, TRUE, "LogWindow",
834 TRUE, observe_channels,
835 g_object_ref (self), g_object_unref);
836 self->priv->channels = g_hash_table_new_full (g_direct_hash, g_direct_equal,
837 g_object_unref, g_object_unref);
839 tp_base_client_take_observer_filter (self->priv->observer,
841 TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
842 TP_IFACE_CHANNEL_TYPE_TEXT,
844 tp_base_client_take_observer_filter (self->priv->observer,
846 TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
847 TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA,
849 tp_base_client_take_observer_filter (self->priv->observer,
851 TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
852 TPY_IFACE_CHANNEL_TYPE_CALL,
855 tp_base_client_register (self->priv->observer, NULL);
857 g_object_unref (dbus);
861 event_get_target (TplEvent *event)
863 TplEntity *sender = tpl_event_get_sender (event);
864 TplEntity *receiver = tpl_event_get_receiver (event);
866 if (tpl_entity_get_entity_type (sender) == TPL_ENTITY_SELF)
873 model_is_parent (GtkTreeModel *model,
877 TplEvent *stored_event;
880 gboolean found = FALSE;
883 if (gtk_tree_model_iter_parent (model, &parent, iter))
886 gtk_tree_model_get (model, iter,
887 COL_EVENTS_ACCOUNT, &account,
888 COL_EVENTS_TARGET, &target,
889 COL_EVENTS_EVENT, &stored_event,
892 if (G_OBJECT_TYPE (event) == G_OBJECT_TYPE (stored_event) &&
893 account_equal (account, tpl_event_get_account (event)) &&
894 (entity_equal (target, event_get_target (event)) ||
895 is_same_confroom (event, stored_event)))
900 gtk_tree_model_iter_nth_child (model, &child, iter,
901 gtk_tree_model_iter_n_children (model, iter) - 1);
903 gtk_tree_model_get (model, &child,
904 COL_EVENTS_TS, ×tamp,
907 if (ABS (tpl_event_get_timestamp (event) - timestamp) < MAX_GAP)
909 /* The gap is smaller than 30 min */
914 g_object_unref (stored_event);
915 g_object_unref (account);
916 g_object_unref (target);
922 get_display_string_for_chat_message (EmpathyMessage *message,
925 EmpathyContact *sender, *receiver, *target;
926 TplEntity *ent_sender, *ent_receiver;
929 sender = empathy_message_get_sender (message);
930 receiver = empathy_message_get_receiver (message);
932 ent_sender = tpl_event_get_sender (event);
933 ent_receiver = tpl_event_get_receiver (event);
935 /* If this is a MUC, we want to show "Chat in <room>". */
936 if (tpl_entity_get_entity_type (ent_sender) == TPL_ENTITY_ROOM ||
937 (ent_receiver != NULL &&
938 tpl_entity_get_entity_type (ent_receiver) == TPL_ENTITY_ROOM))
939 format = _("Chat in %s");
941 format = _("Chat with %s");
943 if (tpl_entity_get_entity_type (ent_sender) == TPL_ENTITY_ROOM)
945 else if (ent_receiver != NULL &&
946 tpl_entity_get_entity_type (ent_receiver) == TPL_ENTITY_ROOM)
948 else if (empathy_contact_is_user (sender))
953 return g_markup_printf_escaped (format, empathy_contact_get_alias (target));
957 get_parent_iter_for_message (TplEvent *event,
958 EmpathyMessage *message,
964 gboolean parent_found = FALSE;
967 store = log_window->priv->store_events;
968 model = GTK_TREE_MODEL (store);
970 for (next = gtk_tree_model_get_iter_first (model, &iter);
972 next = gtk_tree_model_iter_next (model, &iter))
974 if ((parent_found = model_is_parent (model, &iter, event)))
985 gchar *body, *pretty_date;
987 date = g_date_time_new_from_unix_utc (
988 tpl_event_get_timestamp (event));
990 pretty_date = g_date_time_format (date,
991 C_("A date with the time", "%A, %e %B %Y %X"));
993 body = get_display_string_for_chat_message (message, event);
995 gtk_tree_store_append (store, &iter, NULL);
996 gtk_tree_store_set (store, &iter,
997 COL_EVENTS_TS, tpl_event_get_timestamp (event),
998 COL_EVENTS_PRETTY_DATE, pretty_date,
999 COL_EVENTS_TEXT, body,
1000 COL_EVENTS_ICON, "stock_text_justify",
1001 COL_EVENTS_ACCOUNT, tpl_event_get_account (event),
1002 COL_EVENTS_TARGET, event_get_target (event),
1003 COL_EVENTS_EVENT, event,
1009 g_free (pretty_date);
1010 g_date_time_unref (date);
1014 static const gchar *
1015 get_icon_for_event (TplEvent *event)
1017 const gchar *icon = NULL;
1019 if (TPL_IS_TEXT_EVENT (event))
1021 TplTextEvent *text = TPL_TEXT_EVENT (event);
1023 if (!tp_str_empty (tpl_text_event_get_supersedes_token (text)))
1024 icon = EMPATHY_IMAGE_EDIT_MESSAGE;
1026 #ifdef HAVE_CALL_LOGS
1027 else if (TPL_IS_CALL_EVENT (event))
1029 TplCallEvent *call = TPL_CALL_EVENT (event);
1030 TplCallEndReason reason = tpl_call_event_get_end_reason (call);
1031 TplEntity *sender = tpl_event_get_sender (event);
1032 TplEntity *receiver = tpl_event_get_receiver (event);
1034 if (reason == TPL_CALL_END_REASON_NO_ANSWER)
1035 icon = EMPATHY_IMAGE_CALL_MISSED;
1036 else if (tpl_entity_get_entity_type (sender) == TPL_ENTITY_SELF)
1037 icon = EMPATHY_IMAGE_CALL_OUTGOING;
1038 else if (tpl_entity_get_entity_type (receiver) == TPL_ENTITY_SELF)
1039 icon = EMPATHY_IMAGE_CALL_INCOMING;
1047 log_window_append_chat_message (TplEvent *event,
1048 EmpathyMessage *message)
1050 GtkTreeStore *store = log_window->priv->store_events;
1051 GtkTreeIter iter, parent;
1052 gchar *pretty_date, *alias, *body, *msg;
1055 date = g_date_time_new_from_unix_utc (
1056 tpl_event_get_timestamp (event));
1058 pretty_date = g_date_time_format (date, "%X");
1060 get_parent_iter_for_message (event, message, &parent);
1062 msg = g_markup_escape_text (empathy_message_get_body (message), -1);
1063 alias = g_markup_escape_text (
1064 tpl_entity_get_alias (tpl_event_get_sender (event)), -1);
1066 /* If the user is searching, highlight the matched text */
1067 if (!EMP_STR_EMPTY (log_window->priv->last_find))
1069 gchar *str = g_regex_escape_string (log_window->priv->last_find, -1);
1070 gchar *replacement = g_markup_printf_escaped (
1071 "<span background=\"yellow\">%s</span>",
1072 log_window->priv->last_find);
1073 GError *error = NULL;
1074 GRegex *regex = g_regex_new (str, 0, 0, &error);
1078 DEBUG ("Could not create regex: %s", error->message);
1079 g_error_free (error);
1083 gchar *new_msg = g_regex_replace_literal (regex,
1084 empathy_message_get_body (message), -1, 0, replacement,
1087 if (new_msg != NULL)
1089 /* We pass ownership of new_msg to msg, which is freed later */
1095 DEBUG ("Error while performing string substitution: %s",
1097 g_error_free (error);
1102 g_free (replacement);
1104 tp_clear_pointer (®ex, g_regex_unref);
1107 if (tpl_text_event_get_message_type (TPL_TEXT_EVENT (event))
1108 == TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION)
1110 /* Translators: this is an emote: '* Danielle waves' */
1111 body = g_strdup_printf (_("<i>* %s %s</i>"), alias, msg);
1115 /* Translators: this is a message: 'Danielle: hello'
1116 * The string in bold is the sender's name */
1117 body = g_strdup_printf (_("<b>%s:</b> %s"), alias, msg);
1120 gtk_tree_store_append (store, &iter, &parent);
1121 gtk_tree_store_set (store, &iter,
1122 COL_EVENTS_TS, tpl_event_get_timestamp (event),
1123 COL_EVENTS_PRETTY_DATE, pretty_date,
1124 COL_EVENTS_TEXT, body,
1125 COL_EVENTS_ICON, get_icon_for_event (event),
1126 COL_EVENTS_ACCOUNT, tpl_event_get_account (event),
1127 COL_EVENTS_TARGET, event_get_target (event),
1128 COL_EVENTS_EVENT, event,
1134 g_free (pretty_date);
1135 g_date_time_unref (date);
1138 #ifdef HAVE_CALL_LOGS
1140 log_window_append_call (TplEvent *event,
1141 EmpathyMessage *message)
1143 TplCallEvent *call = TPL_CALL_EVENT (event);
1144 GtkTreeStore *store = log_window->priv->store_events;
1145 GtkTreeIter iter, child;
1146 gchar *pretty_date, *duration, *finished;
1147 GDateTime *started_date, *finished_date;
1150 started_date = g_date_time_new_from_unix_utc (
1151 tpl_event_get_timestamp (event));
1153 pretty_date = g_date_time_format (started_date,
1154 C_("A date with the time", "%A, %e %B %Y %X"));
1156 gtk_tree_store_append (store, &iter, NULL);
1157 gtk_tree_store_set (store, &iter,
1158 COL_EVENTS_TS, tpl_event_get_timestamp (event),
1159 COL_EVENTS_PRETTY_DATE, pretty_date,
1160 COL_EVENTS_TEXT, empathy_message_get_body (message),
1161 COL_EVENTS_ICON, get_icon_for_event (event),
1162 COL_EVENTS_ACCOUNT, tpl_event_get_account (event),
1163 COL_EVENTS_TARGET, event_get_target (event),
1164 COL_EVENTS_EVENT, event,
1167 if (tpl_call_event_get_end_reason (call) != TPL_CALL_END_REASON_NO_ANSWER)
1171 span = tpl_call_event_get_duration (TPL_CALL_EVENT (event));
1173 duration = g_strdup_printf (_("%" G_GINT64_FORMAT " seconds"), span);
1175 duration = g_strdup_printf (_("%" G_GINT64_FORMAT " minutes"),
1178 finished_date = g_date_time_add (started_date, -span);
1179 finished = g_date_time_format (finished_date, "%X");
1180 g_date_time_unref (finished_date);
1182 body = g_strdup_printf (_("Call took %s, ended at %s"),
1183 duration, finished);
1188 gtk_tree_store_append (store, &child, &iter);
1189 gtk_tree_store_set (store, &child,
1190 COL_EVENTS_TS, tpl_event_get_timestamp (event),
1191 COL_EVENTS_TEXT, body,
1192 COL_EVENTS_ACCOUNT, tpl_event_get_account (event),
1193 COL_EVENTS_TARGET, event_get_target (event),
1194 COL_EVENTS_EVENT, event,
1200 g_free (pretty_date);
1201 g_date_time_unref (started_date);
1206 log_window_append_message (TplEvent *event,
1207 EmpathyMessage *message)
1209 if (TPL_IS_TEXT_EVENT (event))
1210 log_window_append_chat_message (event, message);
1211 #ifdef HAVE_CALL_LOGS
1212 else if (TPL_IS_CALL_EVENT (event))
1213 log_window_append_call (event, message);
1216 DEBUG ("Message type not handled");
1220 add_all_accounts_and_entities (GList **accounts,
1224 GtkTreeModel *model;
1227 view = GTK_TREE_VIEW (log_window->priv->treeview_who);
1228 model = gtk_tree_view_get_model (view);
1230 if (!gtk_tree_model_get_iter_first (model, &iter))
1239 gtk_tree_model_get (model, &iter,
1240 COL_WHO_ACCOUNT, &account,
1241 COL_WHO_TARGET, &entity,
1242 COL_WHO_TYPE, &type,
1245 if (type != COL_TYPE_NORMAL)
1248 if (accounts != NULL)
1249 *accounts = g_list_append (*accounts, account);
1251 if (entities != NULL)
1252 *entities = g_list_append (*entities, entity);
1254 while (gtk_tree_model_iter_next (model, &iter));
1258 log_window_get_selected (EmpathyLogWindow *self,
1263 TplEventTypeMask *event_mask,
1264 EventSubtype *subtype)
1267 GtkTreeModel *model;
1268 GtkTreeSelection *selection;
1270 TplEventTypeMask ev = 0;
1271 EventSubtype st = 0;
1275 view = GTK_TREE_VIEW (self->priv->treeview_who);
1276 model = gtk_tree_view_get_model (view);
1277 selection = gtk_tree_view_get_selection (view);
1279 paths = gtk_tree_selection_get_selected_rows (selection, NULL);
1283 if (accounts != NULL)
1285 if (entities != NULL)
1290 for (l = paths; l != NULL; l = l->next)
1292 GtkTreePath *path = l->data;
1296 gtk_tree_model_get_iter (model, &iter, path);
1297 gtk_tree_model_get (model, &iter,
1298 COL_WHO_ACCOUNT, &account,
1299 COL_WHO_TARGET, &entity,
1300 COL_WHO_TYPE, &type,
1303 if (type == COL_TYPE_ANY)
1305 if (accounts != NULL || entities != NULL)
1306 add_all_accounts_and_entities (accounts, entities);
1312 if (accounts != NULL)
1313 *accounts = g_list_append (*accounts, g_object_ref (account));
1315 if (entities != NULL)
1316 *entities = g_list_append (*entities, g_object_ref (entity));
1318 g_object_unref (account);
1319 g_object_unref (entity);
1321 g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free);
1323 view = GTK_TREE_VIEW (self->priv->treeview_what);
1324 model = gtk_tree_view_get_model (view);
1325 selection = gtk_tree_view_get_selection (view);
1327 paths = gtk_tree_selection_get_selected_rows (selection, NULL);
1328 for (l = paths; l != NULL; l = l->next)
1330 GtkTreePath *path = l->data;
1331 TplEventTypeMask mask;
1332 EventSubtype submask;
1334 gtk_tree_model_get_iter (model, &iter, path);
1335 gtk_tree_model_get (model, &iter,
1336 COL_WHAT_TYPE, &mask,
1337 COL_WHAT_SUBTYPE, &submask,
1343 g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free);
1345 view = GTK_TREE_VIEW (self->priv->treeview_when);
1346 model = gtk_tree_view_get_model (view);
1347 selection = gtk_tree_view_get_selection (view);
1353 paths = gtk_tree_selection_get_selected_rows (selection, NULL);
1354 for (l = paths; l != NULL; l = l->next)
1356 GtkTreePath *path = l->data;
1359 gtk_tree_model_get_iter (model, &iter, path);
1360 gtk_tree_model_get (model, &iter,
1361 COL_WHEN_DATE, &date,
1364 *dates = g_list_append (*dates, date);
1366 g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free);
1369 if (event_mask != NULL)
1372 if (subtype != NULL)
1379 model_has_entity (GtkTreeModel *model,
1384 TplLogSearchHit *hit = data;
1387 gboolean ret = FALSE;
1389 gtk_tree_model_get (model, iter,
1391 COL_WHO_ACCOUNT, &a,
1394 if (e != NULL && entity_equal (hit->target, e) &&
1395 a != NULL && account_equal (hit->account, a))
1397 ret = has_element = TRUE;
1400 tp_clear_object (&e);
1401 tp_clear_object (&a);
1407 model_has_date (GtkTreeModel *model,
1415 gtk_tree_model_get (model, iter,
1419 if (!g_date_compare (date, d))
1429 get_events_for_date (TplActionChain *chain, gpointer user_data);
1432 populate_events_from_search_hits (GList *accounts,
1436 TplEventTypeMask event_mask;
1437 EventSubtype subtype;
1440 gboolean is_anytime = FALSE;
1442 if (!log_window_get_selected (log_window,
1443 NULL, NULL, NULL, NULL, &event_mask, &subtype))
1446 anytime = g_date_new_dmy (2, 1, -1);
1447 if (g_list_find_custom (dates, anytime, (GCompareFunc) g_date_compare))
1450 for (l = log_window->priv->hits; l != NULL; l = l->next)
1452 TplLogSearchHit *hit = l->data;
1454 gboolean found = FALSE;
1456 /* Protect against invalid data (corrupt or old log files). */
1457 if (hit->account == NULL || hit->target == NULL)
1460 for (acc = accounts, targ = targets;
1461 acc != NULL && targ != NULL && !found;
1462 acc = acc->next, targ = targ->next)
1464 TpAccount *account = acc->data;
1465 TplEntity *target = targ->data;
1467 if (account_equal (hit->account, account) &&
1468 entity_equal (hit->target, target))
1476 g_list_find_custom (dates, hit->date, (GCompareFunc) g_date_compare)
1481 ctx = ctx_new (log_window, hit->account, hit->target, hit->date,
1482 event_mask, subtype, log_window->priv->count);
1483 _tpl_action_chain_append (log_window->priv->chain,
1484 get_events_for_date, ctx);
1489 _tpl_action_chain_start (log_window->priv->chain);
1491 g_date_free (anytime);
1495 format_date_for_display (GDate *date)
1501 /* g_date_strftime sucks */
1503 now = g_date_new ();
1504 g_date_set_time_t (now, time (NULL));
1506 days_elapsed = g_date_days_between (date, now);
1508 if (days_elapsed < 0)
1512 else if (days_elapsed == 0)
1514 text = g_strdup (_("Today"));
1516 else if (days_elapsed == 1)
1518 text = g_strdup (_("Yesterday"));
1524 dt = g_date_time_new_utc (g_date_get_year (date),
1525 g_date_get_month (date), g_date_get_day (date),
1528 if (days_elapsed <= 7)
1529 text = g_date_time_format (dt, "%A");
1531 text = g_date_time_format (dt,
1532 C_("A date such as '23 May 2010', "
1533 "%e is the day, %B the month and %Y the year",
1536 g_date_time_unref (dt);
1545 populate_dates_from_search_hits (GList *accounts,
1550 GtkTreeModel *model;
1551 GtkListStore *store;
1552 GtkTreeSelection *selection;
1555 if (log_window == NULL)
1558 view = GTK_TREE_VIEW (log_window->priv->treeview_when);
1559 model = gtk_tree_view_get_model (view);
1560 store = GTK_LIST_STORE (model);
1561 selection = gtk_tree_view_get_selection (view);
1563 for (l = log_window->priv->hits; l != NULL; l = l->next)
1565 TplLogSearchHit *hit = l->data;
1567 gboolean found = FALSE;
1569 /* Protect against invalid data (corrupt or old log files). */
1570 if (hit->account == NULL || hit->target == NULL)
1573 for (acc = accounts, targ = targets;
1574 acc != NULL && targ != NULL && !found;
1575 acc = acc->next, targ = targ->next)
1577 TpAccount *account = acc->data;
1578 TplEntity *target = targ->data;
1580 if (account_equal (hit->account, account) &&
1581 entity_equal (hit->target, target))
1588 /* Add the date if it's not already there */
1589 has_element = FALSE;
1590 gtk_tree_model_foreach (model, model_has_date, hit->date);
1593 gchar *text = format_date_for_display (hit->date);
1595 gtk_list_store_append (store, &iter);
1596 gtk_list_store_set (store, &iter,
1597 COL_WHEN_DATE, hit->date,
1598 COL_WHEN_TEXT, text,
1599 COL_WHEN_ICON, CALENDAR_ICON,
1604 if (gtk_tree_model_get_iter_first (model, &iter))
1606 gtk_list_store_prepend (store, &iter);
1607 gtk_list_store_set (store, &iter,
1608 COL_WHEN_DATE, g_date_new_dmy (1, 1, -1),
1609 COL_WHEN_TEXT, "separator",
1612 gtk_list_store_prepend (store, &iter);
1613 gtk_list_store_set (store, &iter,
1614 COL_WHEN_DATE, g_date_new_dmy (2, 1, -1),
1615 COL_WHEN_TEXT, _("Anytime"),
1618 if (gtk_tree_model_iter_nth_child (model, &iter, NULL, 2))
1619 gtk_tree_selection_select_iter (selection, &iter);
1624 populate_entities_from_search_hits (void)
1626 EmpathyAccountChooser *account_chooser;
1629 GtkTreeModel *model;
1631 GtkListStore *store;
1634 view = GTK_TREE_VIEW (log_window->priv->treeview_who);
1635 model = gtk_tree_view_get_model (view);
1636 store = GTK_LIST_STORE (model);
1638 gtk_list_store_clear (store);
1640 account_chooser = EMPATHY_ACCOUNT_CHOOSER (log_window->priv->account_chooser);
1641 account = empathy_account_chooser_get_account (account_chooser);
1643 for (l = log_window->priv->hits; l; l = l->next)
1645 TplLogSearchHit *hit = l->data;
1647 /* Protect against invalid data (corrupt or old log files). */
1648 if (hit->account == NULL || hit->target == NULL)
1651 /* Filter based on the selected account */
1652 if (account != NULL && !account_equal (account, hit->account))
1655 /* Add the entity if it's not already there */
1656 has_element = FALSE;
1657 gtk_tree_model_foreach (model, model_has_entity, hit);
1660 TplEntityType type = tpl_entity_get_entity_type (hit->target);
1661 EmpathyContact *contact;
1662 gboolean room = type == TPL_ENTITY_ROOM;
1664 contact = empathy_contact_from_tpl_contact (hit->account,
1667 gtk_list_store_append (store, &iter);
1668 gtk_list_store_set (store, &iter,
1669 COL_WHO_TYPE, COL_TYPE_NORMAL,
1670 COL_WHO_ICON, room ? EMPATHY_IMAGE_GROUP_MESSAGE
1671 : EMPATHY_IMAGE_AVATAR_DEFAULT,
1672 COL_WHO_NAME, empathy_contact_get_alias (contact),
1673 COL_WHO_ID, tpl_entity_get_identifier (hit->target),
1674 COL_WHO_ACCOUNT, hit->account,
1675 COL_WHO_TARGET, hit->target,
1678 g_object_unref (contact);
1682 if (gtk_tree_model_get_iter_first (model, &iter))
1684 gtk_list_store_prepend (store, &iter);
1685 gtk_list_store_set (store, &iter,
1686 COL_WHO_TYPE, COL_TYPE_SEPARATOR,
1687 COL_WHO_NAME, "separator",
1690 gtk_list_store_prepend (store, &iter);
1691 gtk_list_store_set (store, &iter,
1692 COL_WHO_TYPE, COL_TYPE_ANY,
1693 COL_WHO_NAME, _("Anyone"),
1697 /* FIXME: select old entity if still available */
1701 log_manager_searched_new_cb (GObject *manager,
1702 GAsyncResult *result,
1707 GtkTreeSelection *selection;
1708 GError *error = NULL;
1710 if (log_window == NULL)
1713 if (!tpl_log_manager_search_finish (TPL_LOG_MANAGER (manager),
1714 result, &hits, &error))
1716 DEBUG ("%s. Aborting", error->message);
1717 g_error_free (error);
1721 tp_clear_pointer (&log_window->priv->hits, tpl_log_manager_search_free);
1722 log_window->priv->hits = hits;
1724 populate_entities_from_search_hits ();
1726 view = GTK_TREE_VIEW (log_window->priv->treeview_when);
1727 selection = gtk_tree_view_get_selection (view);
1729 g_signal_handlers_unblock_by_func (selection,
1730 log_window_when_changed_cb,
1735 log_window_find_populate (EmpathyLogWindow *self,
1736 const gchar *search_criteria)
1739 GtkTreeModel *model;
1740 GtkTreeSelection *selection;
1741 GtkListStore *store;
1743 gtk_tree_store_clear (self->priv->store_events);
1745 view = GTK_TREE_VIEW (self->priv->treeview_who);
1746 model = gtk_tree_view_get_model (view);
1747 store = GTK_LIST_STORE (model);
1749 gtk_list_store_clear (store);
1751 view = GTK_TREE_VIEW (self->priv->treeview_when);
1752 model = gtk_tree_view_get_model (view);
1753 store = GTK_LIST_STORE (model);
1754 selection = gtk_tree_view_get_selection (view);
1756 gtk_list_store_clear (store);
1758 if (EMP_STR_EMPTY (search_criteria))
1760 tp_clear_pointer (&self->priv->hits, tpl_log_manager_search_free);
1761 log_window_who_populate (self);
1765 g_signal_handlers_block_by_func (selection,
1766 log_window_when_changed_cb,
1769 tpl_log_manager_search_async (self->priv->log_manager,
1770 search_criteria, TPL_EVENT_MASK_ANY,
1771 log_manager_searched_new_cb, NULL);
1775 start_find_search (EmpathyLogWindow *self)
1779 str = gtk_entry_get_text (GTK_ENTRY (self->priv->search_entry));
1781 /* Don't find the same crap again */
1782 if (self->priv->last_find && !tp_strdiff (self->priv->last_find, str))
1785 g_free (self->priv->last_find);
1786 self->priv->last_find = g_strdup (str);
1788 log_window_find_populate (self, str);
1794 log_window_search_entry_changed_cb (GtkWidget *entry,
1795 EmpathyLogWindow *self)
1797 if (self->priv->source != 0)
1798 g_source_remove (self->priv->source);
1799 self->priv->source = g_timeout_add (500, (GSourceFunc) start_find_search,
1804 log_window_search_entry_activate_cb (GtkWidget *entry,
1805 EmpathyLogWindow *self)
1807 start_find_search (self);
1811 log_window_search_entry_icon_pressed_cb (GtkEntry *entry,
1812 GtkEntryIconPosition icon_pos,
1816 if (icon_pos != GTK_ENTRY_ICON_SECONDARY)
1819 gtk_entry_buffer_set_text (gtk_entry_get_buffer (entry),
1824 log_window_update_buttons_sensitivity (EmpathyLogWindow *self)
1827 GtkTreeModel *model;
1828 GtkTreeSelection *selection;
1829 EmpathyCapabilities capabilities;
1835 gboolean profile, chat, call, video;
1837 tp_clear_object (&self->priv->selected_contact);
1839 view = GTK_TREE_VIEW (self->priv->treeview_who);
1840 model = gtk_tree_view_get_model (view);
1841 selection = gtk_tree_view_get_selection (view);
1843 profile = chat = call = video = FALSE;
1845 if (!gtk_tree_model_get_iter_first (model, &iter))
1848 if (gtk_tree_selection_count_selected_rows (selection) != 1)
1851 if (gtk_tree_selection_iter_is_selected (selection, &iter))
1854 paths = gtk_tree_selection_get_selected_rows (selection, &model);
1855 g_return_if_fail (paths != NULL);
1858 gtk_tree_model_get_iter (model, &iter, path);
1859 gtk_tree_model_get (model, &iter,
1860 COL_WHO_ACCOUNT, &account,
1861 COL_WHO_TARGET, &target,
1864 g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free);
1866 self->priv->selected_contact = empathy_contact_from_tpl_contact (account,
1869 g_object_unref (account);
1870 g_object_unref (target);
1872 capabilities = empathy_contact_get_capabilities (self->priv->selected_contact);
1874 profile = chat = TRUE;
1875 call = capabilities & EMPATHY_CAPABILITIES_AUDIO;
1876 video = capabilities & EMPATHY_CAPABILITIES_VIDEO;
1881 /* If the Who pane doesn't contain a contact (e.g. it has many
1882 * selected, or has 'Anyone', let's try to get the contact from
1883 * the selected event. */
1884 view = GTK_TREE_VIEW (self->priv->treeview_events);
1885 model = gtk_tree_view_get_model (view);
1886 selection = gtk_tree_view_get_selection (view);
1888 if (gtk_tree_selection_count_selected_rows (selection) != 1)
1891 if (!gtk_tree_selection_get_selected (selection, NULL, &iter))
1894 gtk_tree_model_get (model, &iter,
1895 COL_EVENTS_ACCOUNT, &account,
1896 COL_EVENTS_TARGET, &target,
1899 self->priv->selected_contact = empathy_contact_from_tpl_contact (account,
1902 g_object_unref (account);
1903 g_object_unref (target);
1905 capabilities = empathy_contact_get_capabilities (self->priv->selected_contact);
1907 profile = chat = TRUE;
1908 call = capabilities & EMPATHY_CAPABILITIES_AUDIO;
1909 video = capabilities & EMPATHY_CAPABILITIES_VIDEO;
1912 gtk_widget_set_sensitive (self->priv->button_profile, profile);
1913 gtk_widget_set_sensitive (self->priv->button_chat, chat);
1914 gtk_widget_set_sensitive (self->priv->button_call, call);
1915 gtk_widget_set_sensitive (self->priv->button_video, video);
1919 log_window_update_what_iter_sensitivity (GtkTreeModel *model,
1923 GtkTreeStore *store = GTK_TREE_STORE (model);
1927 gtk_tree_store_set (store, iter,
1928 COL_WHAT_SENSITIVE, sensitive,
1931 for (next = gtk_tree_model_iter_children (model, &child, iter);
1933 next = gtk_tree_model_iter_next (model, &child))
1935 gtk_tree_store_set (store, &child,
1936 COL_WHAT_SENSITIVE, sensitive,
1942 log_window_update_what_sensitivity (EmpathyLogWindow *self)
1945 GtkTreeModel *model;
1947 GList *accounts, *targets, *acc, *targ;
1950 if (!log_window_get_selected (self, &accounts, &targets, NULL, NULL,
1954 view = GTK_TREE_VIEW (self->priv->treeview_what);
1955 model = gtk_tree_view_get_model (view);
1957 /* For each event type... */
1958 for (next = gtk_tree_model_get_iter_first (model, &iter);
1960 next = gtk_tree_model_iter_next (model, &iter))
1962 TplEventTypeMask type;
1964 gtk_tree_model_get (model, &iter,
1965 COL_WHAT_TYPE, &type,
1968 /* ...we set the type and its subtypes (if any) unsensitive... */
1969 log_window_update_what_iter_sensitivity (model, &iter, FALSE);
1971 for (acc = accounts, targ = targets;
1972 acc != NULL && targ != NULL;
1973 acc = acc->next, targ = targ->next)
1975 TpAccount *account = acc->data;
1976 TplEntity *target = targ->data;
1978 if (tpl_log_manager_exists (self->priv->log_manager,
1979 account, target, type))
1981 /* And then we set it (and its subtypes, again, if any)
1982 * as sensitive if there are logs of that type. */
1983 log_window_update_what_iter_sensitivity (model, &iter, TRUE);
1989 g_list_free_full (accounts, g_object_unref);
1990 g_list_free_full (targets, g_object_unref);
1994 log_window_who_changed_cb (GtkTreeSelection *selection,
1995 EmpathyLogWindow *self)
1998 GtkTreeModel *model;
2001 DEBUG ("log_window_who_changed_cb");
2003 view = gtk_tree_selection_get_tree_view (selection);
2004 model = gtk_tree_view_get_model (view);
2006 if (gtk_tree_model_get_iter_first (model, &iter))
2008 /* If 'Anyone' is selected, everything else should be deselected */
2009 if (gtk_tree_selection_iter_is_selected (selection, &iter))
2011 g_signal_handlers_block_by_func (selection,
2012 log_window_who_changed_cb,
2015 gtk_tree_selection_unselect_all (selection);
2016 gtk_tree_selection_select_iter (selection, &iter);
2018 g_signal_handlers_unblock_by_func (selection,
2019 log_window_who_changed_cb,
2024 log_window_update_what_sensitivity (self);
2025 log_window_update_buttons_sensitivity (self);
2027 /* The contact changed, so the dates need to be updated */
2028 log_window_chats_get_messages (self, TRUE);
2032 log_manager_got_entities_cb (GObject *manager,
2033 GAsyncResult *result,
2036 Ctx *ctx = user_data;
2040 GtkTreeModel *model;
2041 GtkTreeSelection *selection;
2042 GtkListStore *store;
2044 GError *error = NULL;
2045 gboolean select_account = FALSE;
2047 if (log_window == NULL)
2050 if (log_window->priv->count != ctx->count)
2053 if (!tpl_log_manager_get_entities_finish (TPL_LOG_MANAGER (manager),
2054 result, &entities, &error))
2056 DEBUG ("%s. Aborting", error->message);
2057 g_error_free (error);
2061 view = GTK_TREE_VIEW (ctx->self->priv->treeview_who);
2062 model = gtk_tree_view_get_model (view);
2063 selection = gtk_tree_view_get_selection (view);
2064 store = GTK_LIST_STORE (model);
2066 /* Block signals to stop the logs being retrieved prematurely */
2067 g_signal_handlers_block_by_func (selection,
2068 log_window_who_changed_cb, ctx->self);
2070 for (l = entities; l; l = l->next)
2072 TplEntity *entity = TPL_ENTITY (l->data);
2073 TplEntityType type = tpl_entity_get_entity_type (entity);
2074 EmpathyContact *contact;
2075 gboolean room = type == TPL_ENTITY_ROOM;
2077 contact = empathy_contact_from_tpl_contact (ctx->account, entity);
2079 gtk_list_store_append (store, &iter);
2080 gtk_list_store_set (store, &iter,
2081 COL_WHO_TYPE, COL_TYPE_NORMAL,
2082 COL_WHO_ICON, room ? EMPATHY_IMAGE_GROUP_MESSAGE
2083 : EMPATHY_IMAGE_AVATAR_DEFAULT,
2084 COL_WHO_NAME, empathy_contact_get_alias (contact),
2085 COL_WHO_ID, tpl_entity_get_identifier (entity),
2086 COL_WHO_ACCOUNT, ctx->account,
2087 COL_WHO_TARGET, entity,
2090 g_object_unref (contact);
2092 if (ctx->self->priv->selected_account != NULL &&
2093 !tp_strdiff (tp_proxy_get_object_path (ctx->account),
2094 tp_proxy_get_object_path (ctx->self->priv->selected_account)))
2095 select_account = TRUE;
2097 g_list_free_full (entities, g_object_unref);
2099 if (gtk_tree_model_get_iter_first (model, &iter))
2103 gtk_tree_model_get (model, &iter,
2104 COL_WHO_TYPE, &type,
2107 if (type != COL_TYPE_ANY)
2109 gtk_list_store_prepend (store, &iter);
2110 gtk_list_store_set (store, &iter,
2111 COL_WHO_TYPE, COL_TYPE_SEPARATOR,
2112 COL_WHO_NAME, "separator",
2115 gtk_list_store_prepend (store, &iter);
2116 gtk_list_store_set (store, &iter,
2117 COL_WHO_TYPE, COL_TYPE_ANY,
2118 COL_WHO_NAME, _("Anyone"),
2123 /* Unblock signals */
2124 g_signal_handlers_unblock_by_func (selection,
2125 log_window_who_changed_cb,
2128 /* We display the selected account if we populate the model with chats from
2131 log_window_chats_set_selected (ctx->self);
2134 _tpl_action_chain_continue (log_window->priv->chain);
2139 get_entities_for_account (TplActionChain *chain, gpointer user_data)
2141 Ctx *ctx = user_data;
2143 tpl_log_manager_get_entities_async (ctx->self->priv->log_manager, ctx->account,
2144 log_manager_got_entities_cb, ctx);
2148 select_first_entity (TplActionChain *chain, gpointer user_data)
2150 EmpathyLogWindow *self = user_data;
2152 GtkTreeModel *model;
2153 GtkTreeSelection *selection;
2156 view = GTK_TREE_VIEW (self->priv->treeview_who);
2157 model = gtk_tree_view_get_model (view);
2158 selection = gtk_tree_view_get_selection (view);
2160 if (gtk_tree_model_get_iter_first (model, &iter))
2161 gtk_tree_selection_select_iter (selection, &iter);
2163 _tpl_action_chain_continue (self->priv->chain);
2167 log_window_who_populate (EmpathyLogWindow *self)
2169 EmpathyAccountChooser *account_chooser;
2171 gboolean all_accounts;
2173 GtkTreeModel *model;
2174 GtkTreeSelection *selection;
2175 GtkListStore *store;
2178 if (self->priv->hits != NULL)
2180 populate_entities_from_search_hits ();
2184 account_chooser = EMPATHY_ACCOUNT_CHOOSER (self->priv->account_chooser);
2185 account = empathy_account_chooser_dup_account (account_chooser);
2186 all_accounts = empathy_account_chooser_has_all_selected (account_chooser);
2188 view = GTK_TREE_VIEW (self->priv->treeview_who);
2189 model = gtk_tree_view_get_model (view);
2190 selection = gtk_tree_view_get_selection (view);
2191 store = GTK_LIST_STORE (model);
2193 /* Block signals to stop the logs being retrieved prematurely */
2194 g_signal_handlers_block_by_func (selection,
2195 log_window_who_changed_cb,
2198 gtk_list_store_clear (store);
2200 /* Unblock signals */
2201 g_signal_handlers_unblock_by_func (selection,
2202 log_window_who_changed_cb,
2205 _tpl_action_chain_clear (self->priv->chain);
2206 self->priv->count++;
2208 if (!all_accounts && account == NULL)
2212 else if (!all_accounts)
2214 ctx = ctx_new (self, account, NULL, NULL, 0, 0, self->priv->count);
2215 _tpl_action_chain_append (self->priv->chain, get_entities_for_account, ctx);
2219 TpAccountManager *manager;
2220 GList *accounts, *l;
2222 manager = empathy_account_chooser_get_account_manager (account_chooser);
2223 accounts = tp_account_manager_get_valid_accounts (manager);
2225 for (l = accounts; l != NULL; l = l->next)
2229 ctx = ctx_new (self, account, NULL, NULL, 0, 0, self->priv->count);
2230 _tpl_action_chain_append (self->priv->chain,
2231 get_entities_for_account, ctx);
2234 g_list_free (accounts);
2236 _tpl_action_chain_append (self->priv->chain, select_first_entity, self);
2237 _tpl_action_chain_start (self->priv->chain);
2241 sort_by_name (GtkTreeModel *model,
2246 gchar *name1, *name2;
2250 gtk_tree_model_get (model, a,
2251 COL_WHO_TYPE, &type1,
2252 COL_WHO_NAME, &name1,
2255 gtk_tree_model_get (model, b,
2256 COL_WHO_TYPE, &type2,
2257 COL_WHO_NAME, &name2,
2260 if (type1 == COL_TYPE_ANY)
2262 else if (type2 == COL_TYPE_ANY)
2264 else if (type1 == COL_TYPE_SEPARATOR)
2266 else if (type2 == COL_TYPE_SEPARATOR)
2269 ret = g_strcmp0 (name1, name2);
2278 who_row_is_separator (GtkTreeModel *model,
2284 gtk_tree_model_get (model, iter,
2285 COL_WHO_TYPE, &type,
2288 return (type == COL_TYPE_SEPARATOR);
2292 log_window_events_changed_cb (GtkTreeSelection *selection,
2293 EmpathyLogWindow *self)
2295 DEBUG ("log_window_events_changed_cb");
2297 log_window_update_buttons_sensitivity (self);
2301 log_window_events_row_activated_cb (GtkTreeView *view,
2303 GtkTreeViewColumn *column,
2304 EmpathyLogWindow *self)
2306 if (gtk_tree_view_row_expanded (view, path))
2307 gtk_tree_view_collapse_row (view, path);
2309 gtk_tree_view_expand_row (view, path, FALSE);
2313 log_window_events_setup (EmpathyLogWindow *self)
2316 GtkTreeModel *model;
2317 GtkTreeSelection *selection;
2318 GtkTreeSortable *sortable;
2319 GtkTreeViewColumn *column;
2320 GtkTreeStore *store;
2321 GtkCellRenderer *cell;
2323 view = GTK_TREE_VIEW (self->priv->treeview_events);
2324 selection = gtk_tree_view_get_selection (view);
2327 self->priv->store_events = store = gtk_tree_store_new (COL_EVENTS_COUNT,
2328 G_TYPE_INT, /* type */
2329 G_TYPE_INT64, /* timestamp */
2330 G_TYPE_STRING, /* stringified date */
2331 G_TYPE_STRING, /* icon */
2332 G_TYPE_STRING, /* name */
2333 TP_TYPE_ACCOUNT, /* account */
2334 TPL_TYPE_ENTITY, /* target */
2335 TPL_TYPE_EVENT); /* event */
2337 model = GTK_TREE_MODEL (store);
2338 sortable = GTK_TREE_SORTABLE (store);
2340 gtk_tree_view_set_model (view, model);
2343 column = gtk_tree_view_column_new ();
2345 cell = gtk_cell_renderer_pixbuf_new ();
2346 gtk_tree_view_column_pack_start (column, cell, FALSE);
2347 gtk_tree_view_column_add_attribute (column, cell,
2348 "icon-name", COL_EVENTS_ICON);
2350 cell = gtk_cell_renderer_text_new ();
2351 gtk_tree_view_column_pack_start (column, cell, TRUE);
2352 gtk_tree_view_column_add_attribute (column, cell,
2353 "markup", COL_EVENTS_TEXT);
2355 cell = gtk_cell_renderer_text_new ();
2356 g_object_set (cell, "xalign", 1.0, NULL);
2357 gtk_tree_view_column_pack_end (column, cell, FALSE);
2358 gtk_tree_view_column_add_attribute (column, cell,
2359 "text", COL_EVENTS_PRETTY_DATE);
2361 gtk_tree_view_append_column (view, column);
2363 /* set up treeview properties */
2364 gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
2365 gtk_tree_view_set_headers_visible (view, FALSE);
2367 gtk_tree_sortable_set_sort_column_id (sortable,
2369 GTK_SORT_ASCENDING);
2371 gtk_tree_view_set_enable_search (view, FALSE);
2373 /* set up signals */
2374 g_signal_connect (selection, "changed",
2375 G_CALLBACK (log_window_events_changed_cb),
2378 g_signal_connect (view, "row-activated",
2379 G_CALLBACK (log_window_events_row_activated_cb),
2382 g_object_unref (store);
2386 log_window_who_setup (EmpathyLogWindow *self)
2389 GtkTreeModel *model;
2390 GtkTreeSelection *selection;
2391 GtkTreeSortable *sortable;
2392 GtkTreeViewColumn *column;
2393 GtkListStore *store;
2394 GtkCellRenderer *cell;
2396 view = GTK_TREE_VIEW (self->priv->treeview_who);
2397 selection = gtk_tree_view_get_selection (view);
2400 store = gtk_list_store_new (COL_WHO_COUNT,
2401 G_TYPE_INT, /* type */
2402 G_TYPE_STRING, /* icon */
2403 G_TYPE_STRING, /* name */
2404 G_TYPE_STRING, /* id */
2405 TP_TYPE_ACCOUNT, /* account */
2406 TPL_TYPE_ENTITY); /* target */
2408 model = GTK_TREE_MODEL (store);
2409 sortable = GTK_TREE_SORTABLE (store);
2411 gtk_tree_view_set_model (view, model);
2414 column = gtk_tree_view_column_new ();
2415 gtk_tree_view_column_set_title (column, _("Who"));
2417 cell = gtk_cell_renderer_pixbuf_new ();
2418 gtk_tree_view_column_pack_start (column, cell, FALSE);
2419 gtk_tree_view_column_add_attribute (column, cell,
2423 cell = gtk_cell_renderer_text_new ();
2424 g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
2425 gtk_tree_view_column_pack_start (column, cell, TRUE);
2426 gtk_tree_view_column_add_attribute (column, cell,
2430 gtk_tree_view_append_column (view, column);
2432 /* set up treeview properties */
2433 gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
2434 gtk_tree_view_set_row_separator_func (view, who_row_is_separator,
2437 gtk_tree_sortable_set_sort_column_id (sortable,
2439 GTK_SORT_ASCENDING);
2440 gtk_tree_sortable_set_sort_func (sortable,
2441 COL_WHO_NAME, sort_by_name,
2444 gtk_tree_view_set_search_column (view, COL_WHO_NAME);
2445 gtk_tree_view_set_tooltip_column (view, COL_WHO_ID);
2447 /* set up signals */
2448 g_signal_connect (selection, "changed",
2449 G_CALLBACK (log_window_who_changed_cb), self);
2451 g_object_unref (store);
2455 log_window_chats_accounts_changed_cb (GtkWidget *combobox,
2456 EmpathyLogWindow *self)
2458 /* Clear all current messages shown in the textview */
2459 gtk_tree_store_clear (self->priv->store_events);
2461 log_window_who_populate (self);
2465 log_window_chats_set_selected (EmpathyLogWindow *self)
2468 GtkTreeModel *model;
2469 GtkTreeSelection *selection;
2474 view = GTK_TREE_VIEW (self->priv->treeview_who);
2475 model = gtk_tree_view_get_model (view);
2476 selection = gtk_tree_view_get_selection (view);
2478 for (next = gtk_tree_model_get_iter_first (model, &iter);
2480 next = gtk_tree_model_iter_next (model, &iter))
2482 TpAccount *this_account;
2483 TplEntity *this_target;
2484 const gchar *this_chat_id;
2485 gboolean this_is_chatroom;
2488 gtk_tree_model_get (model, &iter,
2489 COL_WHO_TYPE, &this_type,
2490 COL_WHO_ACCOUNT, &this_account,
2491 COL_WHO_TARGET, &this_target,
2494 if (this_type != COL_TYPE_NORMAL)
2497 this_chat_id = tpl_entity_get_identifier (this_target);
2498 this_is_chatroom = tpl_entity_get_entity_type (this_target)
2501 if (this_account == self->priv->selected_account &&
2502 !tp_strdiff (this_chat_id, self->priv->selected_chat_id) &&
2503 this_is_chatroom == self->priv->selected_is_chatroom)
2505 gtk_tree_selection_select_iter (selection, &iter);
2506 path = gtk_tree_model_get_path (model, &iter);
2507 gtk_tree_view_scroll_to_cell (view, path, NULL, TRUE, 0.5, 0.0);
2508 gtk_tree_path_free (path);
2509 g_object_unref (this_account);
2510 g_object_unref (this_target);
2514 g_object_unref (this_account);
2515 g_object_unref (this_target);
2518 tp_clear_object (&self->priv->selected_account);
2519 tp_clear_pointer (&self->priv->selected_chat_id, g_free);
2523 sort_by_date (GtkTreeModel *model,
2528 GDate *date1, *date2;
2530 gtk_tree_model_get (model, a,
2531 COL_WHEN_DATE, &date1,
2534 gtk_tree_model_get (model, b,
2535 COL_WHEN_DATE, &date2,
2538 return g_date_compare (date1, date2);
2542 when_row_is_separator (GtkTreeModel *model,
2549 gtk_tree_model_get (model, iter,
2550 COL_WHEN_TEXT, &when,
2553 ret = g_str_equal (when, "separator");
2559 log_window_when_changed_cb (GtkTreeSelection *selection,
2560 EmpathyLogWindow *self)
2563 GtkTreeModel *model;
2566 DEBUG ("log_window_when_changed_cb");
2568 view = gtk_tree_selection_get_tree_view (selection);
2569 model = gtk_tree_view_get_model (view);
2571 /* If 'Anytime' is selected, everything else should be deselected */
2572 if (gtk_tree_model_get_iter_first (model, &iter))
2574 if (gtk_tree_selection_iter_is_selected (selection, &iter))
2576 g_signal_handlers_block_by_func (selection,
2577 log_window_when_changed_cb,
2580 gtk_tree_selection_unselect_all (selection);
2581 gtk_tree_selection_select_iter (selection, &iter);
2583 g_signal_handlers_unblock_by_func (selection,
2584 log_window_when_changed_cb,
2589 log_window_chats_get_messages (self, FALSE);
2593 log_window_when_setup (EmpathyLogWindow *self)
2596 GtkTreeModel *model;
2597 GtkTreeSelection *selection;
2598 GtkTreeSortable *sortable;
2599 GtkTreeViewColumn *column;
2600 GtkListStore *store;
2601 GtkCellRenderer *cell;
2603 view = GTK_TREE_VIEW (self->priv->treeview_when);
2604 selection = gtk_tree_view_get_selection (view);
2607 store = gtk_list_store_new (COL_WHEN_COUNT,
2608 G_TYPE_DATE, /* date */
2609 G_TYPE_STRING, /* stringified date */
2610 G_TYPE_STRING); /* icon */
2612 model = GTK_TREE_MODEL (store);
2613 sortable = GTK_TREE_SORTABLE (store);
2615 gtk_tree_view_set_model (view, model);
2618 column = gtk_tree_view_column_new ();
2619 gtk_tree_view_column_set_title (column, _("When"));
2621 cell = gtk_cell_renderer_pixbuf_new ();
2622 gtk_tree_view_column_pack_start (column, cell, FALSE);
2623 gtk_tree_view_column_add_attribute (column, cell,
2624 "icon-name", COL_WHEN_ICON);
2626 cell = gtk_cell_renderer_text_new ();
2627 g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
2628 gtk_tree_view_column_pack_start (column, cell, TRUE);
2629 gtk_tree_view_column_add_attribute (column, cell,
2633 gtk_tree_view_append_column (view, column);
2635 /* set up treeview properties */
2636 gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
2637 gtk_tree_view_set_row_separator_func (view, when_row_is_separator,
2639 gtk_tree_sortable_set_sort_column_id (sortable,
2641 GTK_SORT_DESCENDING);
2642 gtk_tree_sortable_set_sort_func (sortable,
2643 COL_WHEN_DATE, sort_by_date,
2646 gtk_tree_view_set_search_column (view, COL_WHEN_TEXT);
2648 /* set up signals */
2649 g_signal_connect (selection, "changed",
2650 G_CALLBACK (log_window_when_changed_cb),
2653 g_object_unref (store);
2657 what_row_is_separator (GtkTreeModel *model,
2663 gtk_tree_model_get (model, iter,
2664 COL_WHAT_TYPE, &type,
2667 return (type == WHAT_TYPE_SEPARATOR);
2671 log_window_what_changed_cb (GtkTreeSelection *selection,
2672 EmpathyLogWindow *self)
2675 GtkTreeModel *model;
2678 DEBUG ("log_window_what_changed_cb");
2680 view = gtk_tree_selection_get_tree_view (selection);
2681 model = gtk_tree_view_get_model (view);
2683 /* If 'Anything' is selected, everything else should be deselected */
2684 if (gtk_tree_model_get_iter_first (model, &iter))
2686 if (gtk_tree_selection_iter_is_selected (selection, &iter))
2688 g_signal_handlers_block_by_func (selection,
2689 log_window_what_changed_cb,
2692 gtk_tree_selection_unselect_all (selection);
2693 gtk_tree_selection_select_iter (selection, &iter);
2695 g_signal_handlers_unblock_by_func (selection,
2696 log_window_what_changed_cb,
2701 /* The dates need to be updated if we're not searching */
2702 log_window_chats_get_messages (self, self->priv->hits == NULL);
2706 log_window_what_collapse_row_cb (GtkTreeView *tree_view,
2711 /* Reject collapsing */
2718 EventSubtype subtype;
2724 log_window_what_setup (EmpathyLogWindow *self)
2727 GtkTreeModel *model;
2728 GtkTreeSelection *selection;
2729 GtkTreeViewColumn *column;
2731 GtkTreeStore *store;
2732 GtkCellRenderer *cell;
2734 struct event events [] = {
2735 { TPL_EVENT_MASK_ANY, 0, NULL, _("Anything") },
2736 { WHAT_TYPE_SEPARATOR, 0, NULL, "separator" },
2737 { TPL_EVENT_MASK_TEXT, 0, "stock_text_justify", _("Text chats") },
2738 #ifdef HAVE_CALL_LOGS
2739 { TPL_EVENT_MASK_CALL, EVENT_CALL_ALL, EMPATHY_IMAGE_CALL, _("Calls") },
2742 #ifdef HAVE_CALL_LOGS
2743 struct event call_events [] = {
2744 { TPL_EVENT_MASK_CALL, EVENT_CALL_INCOMING, EMPATHY_IMAGE_CALL_INCOMING, _("Incoming calls") },
2745 { TPL_EVENT_MASK_CALL, EVENT_CALL_OUTGOING, EMPATHY_IMAGE_CALL_OUTGOING, _("Outgoing calls") },
2746 { TPL_EVENT_MASK_CALL, EVENT_CALL_MISSED, EMPATHY_IMAGE_CALL_MISSED, _("Missed calls") }
2751 view = GTK_TREE_VIEW (self->priv->treeview_what);
2752 selection = gtk_tree_view_get_selection (view);
2755 store = gtk_tree_store_new (COL_WHAT_COUNT,
2756 G_TYPE_INT, /* history type */
2757 G_TYPE_INT, /* history subtype */
2758 G_TYPE_BOOLEAN, /* sensitive */
2759 G_TYPE_STRING, /* stringified history type */
2760 G_TYPE_STRING); /* icon */
2762 model = GTK_TREE_MODEL (store);
2764 gtk_tree_view_set_model (view, model);
2767 column = gtk_tree_view_column_new ();
2768 gtk_tree_view_column_set_title (column, _("What"));
2770 cell = gtk_cell_renderer_pixbuf_new ();
2771 gtk_tree_view_column_pack_start (column, cell, FALSE);
2772 gtk_tree_view_column_add_attribute (column, cell,
2773 "icon-name", COL_WHAT_ICON);
2775 cell = gtk_cell_renderer_text_new ();
2776 g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
2777 gtk_tree_view_column_pack_start (column, cell, TRUE);
2778 gtk_tree_view_column_add_attribute (column, cell,
2779 "text", COL_WHAT_TEXT);
2780 gtk_tree_view_column_add_attribute (column, cell,
2781 "sensitive", COL_WHAT_SENSITIVE);
2783 gtk_tree_view_append_column (view, column);
2784 gtk_tree_view_set_search_column (view, COL_WHAT_TEXT);
2786 /* set up treeview properties */
2787 gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
2788 gtk_tree_view_set_show_expanders (view, FALSE);
2789 gtk_tree_view_set_level_indentation (view, 12);
2790 gtk_tree_view_expand_all (view);
2791 gtk_tree_view_set_row_separator_func (view, what_row_is_separator,
2795 for (i = 0; i < G_N_ELEMENTS (events); i++)
2797 gtk_tree_store_append (store, &iter, NULL);
2798 gtk_tree_store_set (store, &iter,
2799 COL_WHAT_TYPE, events[i].type,
2800 COL_WHAT_SUBTYPE, events[i].subtype,
2801 COL_WHAT_SENSITIVE, TRUE,
2802 COL_WHAT_TEXT, events[i].text,
2803 COL_WHAT_ICON, events[i].icon,
2807 #ifdef HAVE_CALL_LOGS
2808 gtk_tree_model_iter_nth_child (model, &parent, NULL, 3);
2809 for (i = 0; i < G_N_ELEMENTS (call_events); i++)
2811 gtk_tree_store_append (store, &iter, &parent);
2812 gtk_tree_store_set (store, &iter,
2813 COL_WHAT_TYPE, call_events[i].type,
2814 COL_WHAT_SUBTYPE, call_events[i].subtype,
2815 COL_WHAT_SENSITIVE, TRUE,
2816 COL_WHAT_TEXT, call_events[i].text,
2817 COL_WHAT_ICON, call_events[i].icon,
2822 gtk_tree_view_expand_all (view);
2824 /* select 'Anything' */
2825 if (gtk_tree_model_get_iter_first (model, &iter))
2826 gtk_tree_selection_select_iter (selection, &iter);
2828 /* set up signals */
2829 g_signal_connect (view, "test-collapse-row",
2830 G_CALLBACK (log_window_what_collapse_row_cb),
2832 g_signal_connect (selection, "changed",
2833 G_CALLBACK (log_window_what_changed_cb),
2836 g_object_unref (store);
2840 log_window_maybe_expand_events (void)
2843 GtkTreeModel *model;
2845 view = GTK_TREE_VIEW (log_window->priv->treeview_events);
2846 model = gtk_tree_view_get_model (view);
2848 /* If there's only one result, expand it */
2849 if (gtk_tree_model_iter_n_children (model, NULL) == 1)
2850 gtk_tree_view_expand_all (view);
2854 show_spinner (gpointer data)
2858 if (log_window == NULL)
2861 g_object_get (log_window->priv->spinner, "active", &active, NULL);
2864 gtk_notebook_set_current_page (GTK_NOTEBOOK (log_window->priv->notebook),
2871 show_events (TplActionChain *chain,
2874 log_window_maybe_expand_events ();
2875 gtk_spinner_stop (GTK_SPINNER (log_window->priv->spinner));
2876 gtk_notebook_set_current_page (GTK_NOTEBOOK (log_window->priv->notebook),
2879 _tpl_action_chain_continue (chain);
2883 start_spinner (void)
2885 gtk_spinner_start (GTK_SPINNER (log_window->priv->spinner));
2886 gtk_notebook_set_current_page (GTK_NOTEBOOK (log_window->priv->notebook),
2889 g_timeout_add (1000, show_spinner, NULL);
2890 _tpl_action_chain_append (log_window->priv->chain, show_events, NULL);
2894 log_window_got_messages_for_date_cb (GObject *manager,
2895 GAsyncResult *result,
2898 Ctx *ctx = user_data;
2900 GtkTreeModel *model;
2904 GError *error = NULL;
2907 if (log_window == NULL)
2913 if (log_window->priv->count != ctx->count)
2916 if (!tpl_log_manager_get_events_for_date_finish (TPL_LOG_MANAGER (manager),
2917 result, &events, &error))
2919 DEBUG ("Unable to retrieve messages for the selected date: %s. Aborting",
2921 g_error_free (error);
2925 for (l = events; l; l = l->next)
2927 TplEvent *event = l->data;
2928 gboolean append = TRUE;
2930 #ifdef HAVE_CALL_LOGS
2931 if (TPL_IS_CALL_EVENT (l->data)
2932 && ctx->event_mask & TPL_EVENT_MASK_CALL
2933 && ctx->event_mask != TPL_EVENT_MASK_ANY)
2935 TplCallEvent *call = l->data;
2939 if (ctx->subtype & EVENT_CALL_ALL)
2945 TplCallEndReason reason = tpl_call_event_get_end_reason (call);
2946 TplEntity *sender = tpl_event_get_sender (event);
2947 TplEntity *receiver = tpl_event_get_receiver (event);
2949 if (reason == TPL_CALL_END_REASON_NO_ANSWER)
2951 if (ctx->subtype & EVENT_CALL_MISSED)
2954 else if (ctx->subtype & EVENT_CALL_OUTGOING
2955 && tpl_entity_get_entity_type (sender) == TPL_ENTITY_SELF)
2959 else if (ctx->subtype & EVENT_CALL_INCOMING
2960 && tpl_entity_get_entity_type (receiver) == TPL_ENTITY_SELF)
2970 EmpathyMessage *msg = empathy_message_from_tpl_log_event (event);
2971 log_window_append_message (event, msg);
2972 tp_clear_object (&msg);
2975 g_object_unref (event);
2977 g_list_free (events);
2979 view = GTK_TREE_VIEW (log_window->priv->treeview_events);
2980 model = gtk_tree_view_get_model (view);
2981 n = gtk_tree_model_iter_n_children (model, NULL) - 1;
2983 if (n >= 0 && gtk_tree_model_iter_nth_child (model, &iter, NULL, n))
2987 path = gtk_tree_model_get_path (model, &iter);
2988 gtk_tree_view_scroll_to_cell (view, path, NULL, FALSE, 0, 0);
2989 gtk_tree_path_free (path);
2995 _tpl_action_chain_continue (log_window->priv->chain);
2999 get_events_for_date (TplActionChain *chain, gpointer user_data)
3001 Ctx *ctx = user_data;
3003 tpl_log_manager_get_events_for_date_async (ctx->self->priv->log_manager,
3004 ctx->account, ctx->entity, ctx->event_mask,
3006 log_window_got_messages_for_date_cb,
3011 log_window_get_messages_for_dates (EmpathyLogWindow *self,
3014 GList *accounts, *targets, *acc, *targ, *l;
3015 TplEventTypeMask event_mask;
3016 EventSubtype subtype;
3017 GDate *date, *anytime, *separator;
3019 if (!log_window_get_selected (self,
3020 &accounts, &targets, NULL, NULL, &event_mask, &subtype))
3023 anytime = g_date_new_dmy (2, 1, -1);
3024 separator = g_date_new_dmy (1, 1, -1);
3026 _tpl_action_chain_clear (self->priv->chain);
3027 self->priv->count++;
3029 for (acc = accounts, targ = targets;
3030 acc != NULL && targ != NULL;
3031 acc = acc->next, targ = targ->next)
3033 TpAccount *account = acc->data;
3034 TplEntity *target = targ->data;
3036 for (l = dates; l != NULL; l = l->next)
3041 if (g_date_compare (date, anytime) != 0)
3045 ctx = ctx_new (self, account, target, date, event_mask, subtype,
3047 _tpl_action_chain_append (self->priv->chain, get_events_for_date, ctx);
3051 GtkTreeView *view = GTK_TREE_VIEW (self->priv->treeview_when);
3052 GtkTreeModel *model = gtk_tree_view_get_model (view);
3057 for (next = gtk_tree_model_get_iter_first (model, &iter);
3059 next = gtk_tree_model_iter_next (model, &iter))
3063 gtk_tree_model_get (model, &iter,
3067 if (g_date_compare (d, anytime) != 0 &&
3068 g_date_compare (d, separator) != 0)
3070 ctx = ctx_new (self, account, target, d,
3071 event_mask, subtype, self->priv->count);
3072 _tpl_action_chain_append (self->priv->chain, get_events_for_date, ctx);
3080 _tpl_action_chain_start (self->priv->chain);
3082 g_list_free_full (accounts, g_object_unref);
3083 g_list_free_full (targets, g_object_unref);
3084 g_date_free (separator);
3085 g_date_free (anytime);
3089 log_manager_got_dates_cb (GObject *manager,
3090 GAsyncResult *result,
3093 Ctx *ctx = user_data;
3095 GtkTreeModel *model;
3096 GtkListStore *store;
3100 GError *error = NULL;
3102 if (log_window == NULL)
3105 if (log_window->priv->count != ctx->count)
3108 if (!tpl_log_manager_get_dates_finish (TPL_LOG_MANAGER (manager),
3109 result, &dates, &error))
3111 DEBUG ("Unable to retrieve messages' dates: %s. Aborting",
3116 view = GTK_TREE_VIEW (log_window->priv->treeview_when);
3117 model = gtk_tree_view_get_model (view);
3118 store = GTK_LIST_STORE (model);
3120 for (l = dates; l != NULL; l = l->next)
3122 GDate *date = l->data;
3124 /* Add the date if it's not already there */
3125 has_element = FALSE;
3126 gtk_tree_model_foreach (model, model_has_date, date);
3129 gchar *text = format_date_for_display (date);
3131 gtk_list_store_append (store, &iter);
3132 gtk_list_store_set (store, &iter,
3133 COL_WHEN_DATE, date,
3134 COL_WHEN_TEXT, text,
3135 COL_WHEN_ICON, CALENDAR_ICON,
3142 if (gtk_tree_model_get_iter_first (model, &iter))
3144 gchar *separator = NULL;
3146 if (gtk_tree_model_iter_next (model, &iter))
3148 gtk_tree_model_get (model, &iter,
3149 COL_WHEN_TEXT, &separator,
3153 if (g_strcmp0 (separator, "separator") != 0)
3155 gtk_list_store_prepend (store, &iter);
3156 gtk_list_store_set (store, &iter,
3157 COL_WHEN_DATE, g_date_new_dmy (1, 1, -1),
3158 COL_WHEN_TEXT, "separator",
3161 gtk_list_store_prepend (store, &iter);
3162 gtk_list_store_set (store, &iter,
3163 COL_WHEN_DATE, g_date_new_dmy (2, 1, -1),
3164 COL_WHEN_TEXT, _("Anytime"),
3169 g_list_free_full (dates, g_free);
3172 _tpl_action_chain_continue (log_window->priv->chain);
3176 select_date (TplActionChain *chain, gpointer user_data)
3179 GtkTreeModel *model;
3180 GtkTreeSelection *selection;
3183 gboolean selected = FALSE;
3185 view = GTK_TREE_VIEW (log_window->priv->treeview_when);
3186 model = gtk_tree_view_get_model (view);
3187 selection = gtk_tree_view_get_selection (view);
3189 if (log_window->priv->current_dates != NULL)
3191 for (next = gtk_tree_model_get_iter_first (model, &iter);
3193 next = gtk_tree_model_iter_next (model, &iter))
3197 gtk_tree_model_get (model, &iter,
3198 COL_WHEN_DATE, &date,
3201 if (g_list_find_custom (log_window->priv->current_dates, date,
3202 (GCompareFunc) g_date_compare) != NULL)
3206 gtk_tree_selection_select_iter (selection, &iter);
3207 path = gtk_tree_model_get_path (model, &iter);
3208 gtk_tree_view_scroll_to_cell (view, path, NULL, FALSE, 0, 0);
3211 gtk_tree_path_free (path);
3220 /* Show messages of the most recent date */
3221 if (gtk_tree_model_iter_nth_child (model, &iter, NULL, 2))
3222 gtk_tree_selection_select_iter (selection, &iter);
3225 _tpl_action_chain_continue (log_window->priv->chain);
3229 get_dates_for_entity (TplActionChain *chain, gpointer user_data)
3231 Ctx *ctx = user_data;
3233 tpl_log_manager_get_dates_async (ctx->self->priv->log_manager,
3234 ctx->account, ctx->entity, ctx->event_mask,
3235 log_manager_got_dates_cb, ctx);
3239 log_window_chats_get_messages (EmpathyLogWindow *self,
3240 gboolean force_get_dates)
3242 GList *accounts, *targets, *dates;
3243 TplEventTypeMask event_mask;
3245 GtkTreeModel *model;
3246 GtkListStore *store;
3247 GtkTreeSelection *selection;
3249 if (!log_window_get_selected (self, &accounts, &targets, NULL,
3250 &dates, &event_mask, NULL))
3253 view = GTK_TREE_VIEW (self->priv->treeview_when);
3254 selection = gtk_tree_view_get_selection (view);
3255 model = gtk_tree_view_get_model (view);
3256 store = GTK_LIST_STORE (model);
3258 /* Clear all current messages shown in the textview */
3259 gtk_tree_store_clear (self->priv->store_events);
3261 _tpl_action_chain_clear (self->priv->chain);
3262 self->priv->count++;
3264 /* If there's a search use the returned hits */
3265 if (self->priv->hits != NULL)
3267 if (force_get_dates)
3269 g_signal_handlers_block_by_func (selection,
3270 log_window_when_changed_cb,
3273 gtk_list_store_clear (store);
3275 g_signal_handlers_unblock_by_func (selection,
3276 log_window_when_changed_cb,
3279 populate_dates_from_search_hits (accounts, targets);
3283 populate_events_from_search_hits (accounts, targets, dates);
3286 /* Either use the supplied date or get the last */
3287 else if (force_get_dates || dates == NULL)
3291 if (self->priv->current_dates != NULL)
3293 g_list_free_full (self->priv->current_dates,
3294 (GDestroyNotify) g_date_free);
3295 self->priv->current_dates = NULL;
3298 if (gtk_tree_selection_count_selected_rows (selection) > 0)
3303 paths = gtk_tree_selection_get_selected_rows (selection, NULL);
3305 for (l = paths; l != NULL; l = l->next)
3307 GtkTreePath *path = l->data;
3310 gtk_tree_model_get_iter (model, &iter, path);
3311 gtk_tree_model_get (model, &iter,
3312 COL_WHEN_DATE, &date,
3315 /* The list takes ownership of the date. */
3316 self->priv->current_dates =
3317 g_list_prepend (self->priv->current_dates, date);
3320 g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free);
3323 g_signal_handlers_block_by_func (selection,
3324 log_window_when_changed_cb,
3327 gtk_list_store_clear (store);
3329 g_signal_handlers_unblock_by_func (selection,
3330 log_window_when_changed_cb,
3333 /* Get a list of dates and show them on the treeview */
3334 for (targ = targets, acc = accounts;
3335 targ != NULL && acc != NULL;
3336 targ = targ->next, acc = acc->next)
3338 TpAccount *account = acc->data;
3339 TplEntity *target = targ->data;
3340 Ctx *ctx = ctx_new (self, account, target, NULL, event_mask, 0,
3343 _tpl_action_chain_append (self->priv->chain, get_dates_for_entity, ctx);
3345 _tpl_action_chain_append (self->priv->chain, select_date, NULL);
3346 _tpl_action_chain_start (self->priv->chain);
3350 /* Show messages of the selected date */
3351 log_window_get_messages_for_dates (self, dates);
3354 g_list_free_full (accounts, g_object_unref);
3355 g_list_free_full (targets, g_object_unref);
3356 g_list_free_full (dates, (GFreeFunc) g_date_free);
3360 EmpathyAccountChooserFilterResultCallback callback;
3362 } FilterCallbackData;
3365 got_entities (GObject *manager,
3366 GAsyncResult *result,
3369 FilterCallbackData *data = user_data;
3371 GError *error = NULL;
3373 if (!tpl_log_manager_get_entities_finish (TPL_LOG_MANAGER (manager),
3374 result, &entities, &error))
3376 DEBUG ("Could not get entities: %s", error->message);
3377 g_error_free (error);
3378 data->callback (FALSE, data->user_data);
3382 data->callback (entities != NULL, data->user_data);
3384 g_list_free_full (entities, g_object_unref);
3387 g_slice_free (FilterCallbackData, data);
3391 empathy_account_chooser_filter_has_logs (TpAccount *account,
3392 EmpathyAccountChooserFilterResultCallback callback,
3393 gpointer callback_data,
3396 TplLogManager *manager = tpl_log_manager_dup_singleton ();
3397 FilterCallbackData *cb_data = g_slice_new0 (FilterCallbackData);
3399 cb_data->callback = callback;
3400 cb_data->user_data = callback_data;
3402 tpl_log_manager_get_entities_async (manager, account, got_entities, cb_data);
3404 g_object_unref (manager);
3408 log_window_logger_clear_account_cb (TpProxy *proxy,
3409 const GError *error,
3411 GObject *weak_object)
3413 EmpathyLogWindow *self = EMPATHY_LOG_WINDOW (user_data);
3416 g_warning ("Error when clearing logs: %s", error->message);
3418 /* Refresh the log viewer so the logs are cleared if the account
3419 * has been deleted */
3420 gtk_tree_store_clear (self->priv->store_events);
3421 log_window_who_populate (self);
3423 /* Re-filter the account chooser so the accounts without logs get greyed out */
3424 empathy_account_chooser_set_filter (
3425 EMPATHY_ACCOUNT_CHOOSER (self->priv->account_chooser),
3426 empathy_account_chooser_filter_has_logs, NULL);
3430 log_window_clear_logs_chooser_select_account (EmpathyAccountChooser *chooser,
3431 EmpathyLogWindow *self)
3433 EmpathyAccountChooser *account_chooser;
3435 account_chooser = EMPATHY_ACCOUNT_CHOOSER (self->priv->account_chooser);
3437 empathy_account_chooser_set_account (chooser,
3438 empathy_account_chooser_get_account (account_chooser));
3442 log_window_delete_menu_clicked_cb (GtkMenuItem *menuitem,
3443 EmpathyLogWindow *self)
3445 GtkWidget *dialog, *content_area, *hbox, *label;
3446 EmpathyAccountChooser *account_chooser;
3450 GError *error = NULL;
3452 account_chooser = (EmpathyAccountChooser *) empathy_account_chooser_new ();
3453 empathy_account_chooser_set_has_all_option (account_chooser, TRUE);
3454 empathy_account_chooser_set_filter (account_chooser,
3455 empathy_account_chooser_filter_has_logs, NULL);
3457 /* Select the same account as in the history window */
3458 if (empathy_account_chooser_is_ready (account_chooser))
3459 log_window_clear_logs_chooser_select_account (account_chooser, self);
3461 g_signal_connect (account_chooser, "ready",
3462 G_CALLBACK (log_window_clear_logs_chooser_select_account), self);
3464 dialog = gtk_message_dialog_new_with_markup (GTK_WINDOW (self),
3465 GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING,
3467 _("Are you sure you want to delete all logs of previous conversations?"));
3469 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
3470 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
3471 _("Clear All"), GTK_RESPONSE_APPLY,
3474 content_area = gtk_message_dialog_get_message_area (
3475 GTK_MESSAGE_DIALOG (dialog));
3477 hbox = gtk_hbox_new (FALSE, 6);
3478 label = gtk_label_new (_("Delete from:"));
3479 gtk_box_pack_start (GTK_BOX (hbox), label,
3481 gtk_box_pack_start (GTK_BOX (hbox), GTK_WIDGET (account_chooser),
3483 gtk_box_pack_start (GTK_BOX (content_area), hbox,
3486 gtk_widget_show_all (hbox);
3488 response_id = gtk_dialog_run (GTK_DIALOG (dialog));
3490 if (response_id != GTK_RESPONSE_APPLY)
3493 bus = tp_dbus_daemon_dup (&error);
3496 g_warning ("Could not delete logs: %s", error->message);
3497 g_error_free (error);
3501 logger = g_object_new (TP_TYPE_PROXY,
3502 "bus-name", "org.freedesktop.Telepathy.Logger",
3503 "object-path", "/org/freedesktop/Telepathy/Logger",
3506 g_object_unref (bus);
3508 tp_proxy_add_interface_by_id (logger, EMP_IFACE_QUARK_LOGGER);
3510 if (empathy_account_chooser_has_all_selected (account_chooser))
3512 DEBUG ("Deleting logs for all the accounts");
3514 emp_cli_logger_call_clear (logger, -1,
3515 log_window_logger_clear_account_cb,
3516 self, NULL, G_OBJECT (self));
3522 account = empathy_account_chooser_get_account (account_chooser);
3524 DEBUG ("Deleting logs for %s", tp_proxy_get_object_path (account));
3526 emp_cli_logger_call_clear_account (logger, -1,
3527 tp_proxy_get_object_path (account),
3528 log_window_logger_clear_account_cb,
3529 self, NULL, G_OBJECT (self));
3532 g_object_unref (logger);
3534 gtk_widget_destroy (dialog);