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 log_window_get_selected (self, &accounts, &targets, NULL, NULL,
1953 view = GTK_TREE_VIEW (self->priv->treeview_what);
1954 model = gtk_tree_view_get_model (view);
1956 /* For each event type... */
1957 for (next = gtk_tree_model_get_iter_first (model, &iter);
1959 next = gtk_tree_model_iter_next (model, &iter))
1961 TplEventTypeMask type;
1963 gtk_tree_model_get (model, &iter,
1964 COL_WHAT_TYPE, &type,
1967 /* ...we set the type and its subtypes (if any) unsensitive... */
1968 log_window_update_what_iter_sensitivity (model, &iter, FALSE);
1970 for (acc = accounts, targ = targets;
1971 acc != NULL && targ != NULL;
1972 acc = acc->next, targ = targ->next)
1974 TpAccount *account = acc->data;
1975 TplEntity *target = targ->data;
1977 if (tpl_log_manager_exists (self->priv->log_manager,
1978 account, target, type))
1980 /* And then we set it (and its subtypes, again, if any)
1981 * as sensitive if there are logs of that type. */
1982 log_window_update_what_iter_sensitivity (model, &iter, TRUE);
1988 g_list_free_full (accounts, g_object_unref);
1989 g_list_free_full (targets, g_object_unref);
1993 log_window_who_changed_cb (GtkTreeSelection *selection,
1994 EmpathyLogWindow *self)
1997 GtkTreeModel *model;
2000 DEBUG ("log_window_who_changed_cb");
2002 view = gtk_tree_selection_get_tree_view (selection);
2003 model = gtk_tree_view_get_model (view);
2005 if (gtk_tree_model_get_iter_first (model, &iter))
2007 /* If 'Anyone' is selected, everything else should be deselected */
2008 if (gtk_tree_selection_iter_is_selected (selection, &iter))
2010 g_signal_handlers_block_by_func (selection,
2011 log_window_who_changed_cb,
2014 gtk_tree_selection_unselect_all (selection);
2015 gtk_tree_selection_select_iter (selection, &iter);
2017 g_signal_handlers_unblock_by_func (selection,
2018 log_window_who_changed_cb,
2023 log_window_update_what_sensitivity (self);
2024 log_window_update_buttons_sensitivity (self);
2026 /* The contact changed, so the dates need to be updated */
2027 log_window_chats_get_messages (self, TRUE);
2031 log_manager_got_entities_cb (GObject *manager,
2032 GAsyncResult *result,
2035 Ctx *ctx = user_data;
2039 GtkTreeModel *model;
2040 GtkTreeSelection *selection;
2041 GtkListStore *store;
2043 GError *error = NULL;
2044 gboolean select_account = FALSE;
2046 if (log_window == NULL)
2049 if (log_window->priv->count != ctx->count)
2052 if (!tpl_log_manager_get_entities_finish (TPL_LOG_MANAGER (manager),
2053 result, &entities, &error))
2055 DEBUG ("%s. Aborting", error->message);
2056 g_error_free (error);
2060 view = GTK_TREE_VIEW (ctx->self->priv->treeview_who);
2061 model = gtk_tree_view_get_model (view);
2062 selection = gtk_tree_view_get_selection (view);
2063 store = GTK_LIST_STORE (model);
2065 /* Block signals to stop the logs being retrieved prematurely */
2066 g_signal_handlers_block_by_func (selection,
2067 log_window_who_changed_cb, ctx->self);
2069 for (l = entities; l; l = l->next)
2071 TplEntity *entity = TPL_ENTITY (l->data);
2072 TplEntityType type = tpl_entity_get_entity_type (entity);
2073 EmpathyContact *contact;
2074 gboolean room = type == TPL_ENTITY_ROOM;
2076 contact = empathy_contact_from_tpl_contact (ctx->account, entity);
2078 gtk_list_store_append (store, &iter);
2079 gtk_list_store_set (store, &iter,
2080 COL_WHO_TYPE, COL_TYPE_NORMAL,
2081 COL_WHO_ICON, room ? EMPATHY_IMAGE_GROUP_MESSAGE
2082 : EMPATHY_IMAGE_AVATAR_DEFAULT,
2083 COL_WHO_NAME, empathy_contact_get_alias (contact),
2084 COL_WHO_ID, tpl_entity_get_identifier (entity),
2085 COL_WHO_ACCOUNT, ctx->account,
2086 COL_WHO_TARGET, entity,
2089 g_object_unref (contact);
2091 if (ctx->self->priv->selected_account != NULL &&
2092 !tp_strdiff (tp_proxy_get_object_path (ctx->account),
2093 tp_proxy_get_object_path (ctx->self->priv->selected_account)))
2094 select_account = TRUE;
2096 g_list_free_full (entities, g_object_unref);
2098 if (gtk_tree_model_get_iter_first (model, &iter))
2102 gtk_tree_model_get (model, &iter,
2103 COL_WHO_TYPE, &type,
2106 if (type != COL_TYPE_ANY)
2108 gtk_list_store_prepend (store, &iter);
2109 gtk_list_store_set (store, &iter,
2110 COL_WHO_TYPE, COL_TYPE_SEPARATOR,
2111 COL_WHO_NAME, "separator",
2114 gtk_list_store_prepend (store, &iter);
2115 gtk_list_store_set (store, &iter,
2116 COL_WHO_TYPE, COL_TYPE_ANY,
2117 COL_WHO_NAME, _("Anyone"),
2122 /* Unblock signals */
2123 g_signal_handlers_unblock_by_func (selection,
2124 log_window_who_changed_cb,
2127 /* We display the selected account if we populate the model with chats from
2130 log_window_chats_set_selected (ctx->self);
2133 _tpl_action_chain_continue (log_window->priv->chain);
2138 get_entities_for_account (TplActionChain *chain, gpointer user_data)
2140 Ctx *ctx = user_data;
2142 tpl_log_manager_get_entities_async (ctx->self->priv->log_manager, ctx->account,
2143 log_manager_got_entities_cb, ctx);
2147 select_first_entity (TplActionChain *chain, gpointer user_data)
2149 EmpathyLogWindow *self = user_data;
2151 GtkTreeModel *model;
2152 GtkTreeSelection *selection;
2155 view = GTK_TREE_VIEW (self->priv->treeview_who);
2156 model = gtk_tree_view_get_model (view);
2157 selection = gtk_tree_view_get_selection (view);
2159 if (gtk_tree_model_get_iter_first (model, &iter))
2160 gtk_tree_selection_select_iter (selection, &iter);
2162 _tpl_action_chain_continue (self->priv->chain);
2166 log_window_who_populate (EmpathyLogWindow *self)
2168 EmpathyAccountChooser *account_chooser;
2170 gboolean all_accounts;
2172 GtkTreeModel *model;
2173 GtkTreeSelection *selection;
2174 GtkListStore *store;
2177 if (self->priv->hits != NULL)
2179 populate_entities_from_search_hits ();
2183 account_chooser = EMPATHY_ACCOUNT_CHOOSER (self->priv->account_chooser);
2184 account = empathy_account_chooser_dup_account (account_chooser);
2185 all_accounts = empathy_account_chooser_has_all_selected (account_chooser);
2187 view = GTK_TREE_VIEW (self->priv->treeview_who);
2188 model = gtk_tree_view_get_model (view);
2189 selection = gtk_tree_view_get_selection (view);
2190 store = GTK_LIST_STORE (model);
2192 /* Block signals to stop the logs being retrieved prematurely */
2193 g_signal_handlers_block_by_func (selection,
2194 log_window_who_changed_cb,
2197 gtk_list_store_clear (store);
2199 /* Unblock signals */
2200 g_signal_handlers_unblock_by_func (selection,
2201 log_window_who_changed_cb,
2204 _tpl_action_chain_clear (self->priv->chain);
2205 self->priv->count++;
2207 if (!all_accounts && account == NULL)
2211 else if (!all_accounts)
2213 ctx = ctx_new (self, account, NULL, NULL, 0, 0, self->priv->count);
2214 _tpl_action_chain_append (self->priv->chain, get_entities_for_account, ctx);
2218 TpAccountManager *manager;
2219 GList *accounts, *l;
2221 manager = empathy_account_chooser_get_account_manager (account_chooser);
2222 accounts = tp_account_manager_get_valid_accounts (manager);
2224 for (l = accounts; l != NULL; l = l->next)
2228 ctx = ctx_new (self, account, NULL, NULL, 0, 0, self->priv->count);
2229 _tpl_action_chain_append (self->priv->chain,
2230 get_entities_for_account, ctx);
2233 g_list_free (accounts);
2235 _tpl_action_chain_append (self->priv->chain, select_first_entity, self);
2236 _tpl_action_chain_start (self->priv->chain);
2240 sort_by_name (GtkTreeModel *model,
2245 gchar *name1, *name2;
2249 gtk_tree_model_get (model, a,
2250 COL_WHO_TYPE, &type1,
2251 COL_WHO_NAME, &name1,
2254 gtk_tree_model_get (model, b,
2255 COL_WHO_TYPE, &type2,
2256 COL_WHO_NAME, &name2,
2259 if (type1 == COL_TYPE_ANY)
2261 else if (type2 == COL_TYPE_ANY)
2263 else if (type1 == COL_TYPE_SEPARATOR)
2265 else if (type2 == COL_TYPE_SEPARATOR)
2268 ret = g_strcmp0 (name1, name2);
2277 who_row_is_separator (GtkTreeModel *model,
2283 gtk_tree_model_get (model, iter,
2284 COL_WHO_TYPE, &type,
2287 return (type == COL_TYPE_SEPARATOR);
2291 log_window_events_changed_cb (GtkTreeSelection *selection,
2292 EmpathyLogWindow *self)
2294 DEBUG ("log_window_events_changed_cb");
2296 log_window_update_buttons_sensitivity (self);
2300 log_window_events_row_activated_cb (GtkTreeView *view,
2302 GtkTreeViewColumn *column,
2303 EmpathyLogWindow *self)
2305 if (gtk_tree_view_row_expanded (view, path))
2306 gtk_tree_view_collapse_row (view, path);
2308 gtk_tree_view_expand_row (view, path, FALSE);
2312 log_window_events_setup (EmpathyLogWindow *self)
2315 GtkTreeModel *model;
2316 GtkTreeSelection *selection;
2317 GtkTreeSortable *sortable;
2318 GtkTreeViewColumn *column;
2319 GtkTreeStore *store;
2320 GtkCellRenderer *cell;
2322 view = GTK_TREE_VIEW (self->priv->treeview_events);
2323 selection = gtk_tree_view_get_selection (view);
2326 self->priv->store_events = store = gtk_tree_store_new (COL_EVENTS_COUNT,
2327 G_TYPE_INT, /* type */
2328 G_TYPE_INT64, /* timestamp */
2329 G_TYPE_STRING, /* stringified date */
2330 G_TYPE_STRING, /* icon */
2331 G_TYPE_STRING, /* name */
2332 TP_TYPE_ACCOUNT, /* account */
2333 TPL_TYPE_ENTITY, /* target */
2334 TPL_TYPE_EVENT); /* event */
2336 model = GTK_TREE_MODEL (store);
2337 sortable = GTK_TREE_SORTABLE (store);
2339 gtk_tree_view_set_model (view, model);
2342 column = gtk_tree_view_column_new ();
2344 cell = gtk_cell_renderer_pixbuf_new ();
2345 gtk_tree_view_column_pack_start (column, cell, FALSE);
2346 gtk_tree_view_column_add_attribute (column, cell,
2347 "icon-name", COL_EVENTS_ICON);
2349 cell = gtk_cell_renderer_text_new ();
2350 gtk_tree_view_column_pack_start (column, cell, TRUE);
2351 gtk_tree_view_column_add_attribute (column, cell,
2352 "markup", COL_EVENTS_TEXT);
2354 cell = gtk_cell_renderer_text_new ();
2355 g_object_set (cell, "xalign", 1.0, NULL);
2356 gtk_tree_view_column_pack_end (column, cell, FALSE);
2357 gtk_tree_view_column_add_attribute (column, cell,
2358 "text", COL_EVENTS_PRETTY_DATE);
2360 gtk_tree_view_append_column (view, column);
2362 /* set up treeview properties */
2363 gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
2364 gtk_tree_view_set_headers_visible (view, FALSE);
2366 gtk_tree_sortable_set_sort_column_id (sortable,
2368 GTK_SORT_ASCENDING);
2370 gtk_tree_view_set_enable_search (view, FALSE);
2372 /* set up signals */
2373 g_signal_connect (selection, "changed",
2374 G_CALLBACK (log_window_events_changed_cb),
2377 g_signal_connect (view, "row-activated",
2378 G_CALLBACK (log_window_events_row_activated_cb),
2381 g_object_unref (store);
2385 log_window_who_setup (EmpathyLogWindow *self)
2388 GtkTreeModel *model;
2389 GtkTreeSelection *selection;
2390 GtkTreeSortable *sortable;
2391 GtkTreeViewColumn *column;
2392 GtkListStore *store;
2393 GtkCellRenderer *cell;
2395 view = GTK_TREE_VIEW (self->priv->treeview_who);
2396 selection = gtk_tree_view_get_selection (view);
2399 store = gtk_list_store_new (COL_WHO_COUNT,
2400 G_TYPE_INT, /* type */
2401 G_TYPE_STRING, /* icon */
2402 G_TYPE_STRING, /* name */
2403 G_TYPE_STRING, /* id */
2404 TP_TYPE_ACCOUNT, /* account */
2405 TPL_TYPE_ENTITY); /* target */
2407 model = GTK_TREE_MODEL (store);
2408 sortable = GTK_TREE_SORTABLE (store);
2410 gtk_tree_view_set_model (view, model);
2413 column = gtk_tree_view_column_new ();
2414 gtk_tree_view_column_set_title (column, _("Who"));
2416 cell = gtk_cell_renderer_pixbuf_new ();
2417 gtk_tree_view_column_pack_start (column, cell, FALSE);
2418 gtk_tree_view_column_add_attribute (column, cell,
2422 cell = gtk_cell_renderer_text_new ();
2423 g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
2424 gtk_tree_view_column_pack_start (column, cell, TRUE);
2425 gtk_tree_view_column_add_attribute (column, cell,
2429 gtk_tree_view_append_column (view, column);
2431 /* set up treeview properties */
2432 gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
2433 gtk_tree_view_set_row_separator_func (view, who_row_is_separator,
2436 gtk_tree_sortable_set_sort_column_id (sortable,
2438 GTK_SORT_ASCENDING);
2439 gtk_tree_sortable_set_sort_func (sortable,
2440 COL_WHO_NAME, sort_by_name,
2443 gtk_tree_view_set_search_column (view, COL_WHO_NAME);
2444 gtk_tree_view_set_tooltip_column (view, COL_WHO_ID);
2446 /* set up signals */
2447 g_signal_connect (selection, "changed",
2448 G_CALLBACK (log_window_who_changed_cb), self);
2450 g_object_unref (store);
2454 log_window_chats_accounts_changed_cb (GtkWidget *combobox,
2455 EmpathyLogWindow *self)
2457 /* Clear all current messages shown in the textview */
2458 gtk_tree_store_clear (self->priv->store_events);
2460 log_window_who_populate (self);
2464 log_window_chats_set_selected (EmpathyLogWindow *self)
2467 GtkTreeModel *model;
2468 GtkTreeSelection *selection;
2473 view = GTK_TREE_VIEW (self->priv->treeview_who);
2474 model = gtk_tree_view_get_model (view);
2475 selection = gtk_tree_view_get_selection (view);
2477 for (next = gtk_tree_model_get_iter_first (model, &iter);
2479 next = gtk_tree_model_iter_next (model, &iter))
2481 TpAccount *this_account;
2482 TplEntity *this_target;
2483 const gchar *this_chat_id;
2484 gboolean this_is_chatroom;
2487 gtk_tree_model_get (model, &iter,
2488 COL_WHO_TYPE, &this_type,
2489 COL_WHO_ACCOUNT, &this_account,
2490 COL_WHO_TARGET, &this_target,
2493 if (this_type != COL_TYPE_NORMAL)
2496 this_chat_id = tpl_entity_get_identifier (this_target);
2497 this_is_chatroom = tpl_entity_get_entity_type (this_target)
2500 if (this_account == self->priv->selected_account &&
2501 !tp_strdiff (this_chat_id, self->priv->selected_chat_id) &&
2502 this_is_chatroom == self->priv->selected_is_chatroom)
2504 gtk_tree_selection_select_iter (selection, &iter);
2505 path = gtk_tree_model_get_path (model, &iter);
2506 gtk_tree_view_scroll_to_cell (view, path, NULL, TRUE, 0.5, 0.0);
2507 gtk_tree_path_free (path);
2508 g_object_unref (this_account);
2509 g_object_unref (this_target);
2513 g_object_unref (this_account);
2514 g_object_unref (this_target);
2517 tp_clear_object (&self->priv->selected_account);
2518 tp_clear_pointer (&self->priv->selected_chat_id, g_free);
2522 sort_by_date (GtkTreeModel *model,
2527 GDate *date1, *date2;
2529 gtk_tree_model_get (model, a,
2530 COL_WHEN_DATE, &date1,
2533 gtk_tree_model_get (model, b,
2534 COL_WHEN_DATE, &date2,
2537 return g_date_compare (date1, date2);
2541 when_row_is_separator (GtkTreeModel *model,
2548 gtk_tree_model_get (model, iter,
2549 COL_WHEN_TEXT, &when,
2552 ret = g_str_equal (when, "separator");
2558 log_window_when_changed_cb (GtkTreeSelection *selection,
2559 EmpathyLogWindow *self)
2562 GtkTreeModel *model;
2565 DEBUG ("log_window_when_changed_cb");
2567 view = gtk_tree_selection_get_tree_view (selection);
2568 model = gtk_tree_view_get_model (view);
2570 /* If 'Anytime' is selected, everything else should be deselected */
2571 if (gtk_tree_model_get_iter_first (model, &iter))
2573 if (gtk_tree_selection_iter_is_selected (selection, &iter))
2575 g_signal_handlers_block_by_func (selection,
2576 log_window_when_changed_cb,
2579 gtk_tree_selection_unselect_all (selection);
2580 gtk_tree_selection_select_iter (selection, &iter);
2582 g_signal_handlers_unblock_by_func (selection,
2583 log_window_when_changed_cb,
2588 log_window_chats_get_messages (self, FALSE);
2592 log_window_when_setup (EmpathyLogWindow *self)
2595 GtkTreeModel *model;
2596 GtkTreeSelection *selection;
2597 GtkTreeSortable *sortable;
2598 GtkTreeViewColumn *column;
2599 GtkListStore *store;
2600 GtkCellRenderer *cell;
2602 view = GTK_TREE_VIEW (self->priv->treeview_when);
2603 selection = gtk_tree_view_get_selection (view);
2606 store = gtk_list_store_new (COL_WHEN_COUNT,
2607 G_TYPE_DATE, /* date */
2608 G_TYPE_STRING, /* stringified date */
2609 G_TYPE_STRING); /* icon */
2611 model = GTK_TREE_MODEL (store);
2612 sortable = GTK_TREE_SORTABLE (store);
2614 gtk_tree_view_set_model (view, model);
2617 column = gtk_tree_view_column_new ();
2618 gtk_tree_view_column_set_title (column, _("When"));
2620 cell = gtk_cell_renderer_pixbuf_new ();
2621 gtk_tree_view_column_pack_start (column, cell, FALSE);
2622 gtk_tree_view_column_add_attribute (column, cell,
2623 "icon-name", COL_WHEN_ICON);
2625 cell = gtk_cell_renderer_text_new ();
2626 g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
2627 gtk_tree_view_column_pack_start (column, cell, TRUE);
2628 gtk_tree_view_column_add_attribute (column, cell,
2632 gtk_tree_view_append_column (view, column);
2634 /* set up treeview properties */
2635 gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
2636 gtk_tree_view_set_row_separator_func (view, when_row_is_separator,
2638 gtk_tree_sortable_set_sort_column_id (sortable,
2640 GTK_SORT_DESCENDING);
2641 gtk_tree_sortable_set_sort_func (sortable,
2642 COL_WHEN_DATE, sort_by_date,
2645 gtk_tree_view_set_search_column (view, COL_WHEN_TEXT);
2647 /* set up signals */
2648 g_signal_connect (selection, "changed",
2649 G_CALLBACK (log_window_when_changed_cb),
2652 g_object_unref (store);
2656 what_row_is_separator (GtkTreeModel *model,
2662 gtk_tree_model_get (model, iter,
2663 COL_WHAT_TYPE, &type,
2666 return (type == WHAT_TYPE_SEPARATOR);
2670 log_window_what_changed_cb (GtkTreeSelection *selection,
2671 EmpathyLogWindow *self)
2674 GtkTreeModel *model;
2677 DEBUG ("log_window_what_changed_cb");
2679 view = gtk_tree_selection_get_tree_view (selection);
2680 model = gtk_tree_view_get_model (view);
2682 /* If 'Anything' is selected, everything else should be deselected */
2683 if (gtk_tree_model_get_iter_first (model, &iter))
2685 if (gtk_tree_selection_iter_is_selected (selection, &iter))
2687 g_signal_handlers_block_by_func (selection,
2688 log_window_what_changed_cb,
2691 gtk_tree_selection_unselect_all (selection);
2692 gtk_tree_selection_select_iter (selection, &iter);
2694 g_signal_handlers_unblock_by_func (selection,
2695 log_window_what_changed_cb,
2700 /* The dates need to be updated if we're not searching */
2701 log_window_chats_get_messages (self, self->priv->hits == NULL);
2705 log_window_what_collapse_row_cb (GtkTreeView *tree_view,
2710 /* Reject collapsing */
2717 EventSubtype subtype;
2723 log_window_what_setup (EmpathyLogWindow *self)
2726 GtkTreeModel *model;
2727 GtkTreeSelection *selection;
2728 GtkTreeViewColumn *column;
2730 GtkTreeStore *store;
2731 GtkCellRenderer *cell;
2733 struct event events [] = {
2734 { TPL_EVENT_MASK_ANY, 0, NULL, _("Anything") },
2735 { WHAT_TYPE_SEPARATOR, 0, NULL, "separator" },
2736 { TPL_EVENT_MASK_TEXT, 0, "stock_text_justify", _("Text chats") },
2737 #ifdef HAVE_CALL_LOGS
2738 { TPL_EVENT_MASK_CALL, EVENT_CALL_ALL, EMPATHY_IMAGE_CALL, _("Calls") },
2741 #ifdef HAVE_CALL_LOGS
2742 struct event call_events [] = {
2743 { TPL_EVENT_MASK_CALL, EVENT_CALL_INCOMING, EMPATHY_IMAGE_CALL_INCOMING, _("Incoming calls") },
2744 { TPL_EVENT_MASK_CALL, EVENT_CALL_OUTGOING, EMPATHY_IMAGE_CALL_OUTGOING, _("Outgoing calls") },
2745 { TPL_EVENT_MASK_CALL, EVENT_CALL_MISSED, EMPATHY_IMAGE_CALL_MISSED, _("Missed calls") }
2750 view = GTK_TREE_VIEW (self->priv->treeview_what);
2751 selection = gtk_tree_view_get_selection (view);
2754 store = gtk_tree_store_new (COL_WHAT_COUNT,
2755 G_TYPE_INT, /* history type */
2756 G_TYPE_INT, /* history subtype */
2757 G_TYPE_BOOLEAN, /* sensitive */
2758 G_TYPE_STRING, /* stringified history type */
2759 G_TYPE_STRING); /* icon */
2761 model = GTK_TREE_MODEL (store);
2763 gtk_tree_view_set_model (view, model);
2766 column = gtk_tree_view_column_new ();
2767 gtk_tree_view_column_set_title (column, _("What"));
2769 cell = gtk_cell_renderer_pixbuf_new ();
2770 gtk_tree_view_column_pack_start (column, cell, FALSE);
2771 gtk_tree_view_column_add_attribute (column, cell,
2772 "icon-name", COL_WHAT_ICON);
2774 cell = gtk_cell_renderer_text_new ();
2775 g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
2776 gtk_tree_view_column_pack_start (column, cell, TRUE);
2777 gtk_tree_view_column_add_attribute (column, cell,
2778 "text", COL_WHAT_TEXT);
2779 gtk_tree_view_column_add_attribute (column, cell,
2780 "sensitive", COL_WHAT_SENSITIVE);
2782 gtk_tree_view_append_column (view, column);
2783 gtk_tree_view_set_search_column (view, COL_WHAT_TEXT);
2785 /* set up treeview properties */
2786 gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
2787 gtk_tree_view_set_show_expanders (view, FALSE);
2788 gtk_tree_view_set_level_indentation (view, 12);
2789 gtk_tree_view_expand_all (view);
2790 gtk_tree_view_set_row_separator_func (view, what_row_is_separator,
2794 for (i = 0; i < G_N_ELEMENTS (events); i++)
2796 gtk_tree_store_append (store, &iter, NULL);
2797 gtk_tree_store_set (store, &iter,
2798 COL_WHAT_TYPE, events[i].type,
2799 COL_WHAT_SUBTYPE, events[i].subtype,
2800 COL_WHAT_SENSITIVE, TRUE,
2801 COL_WHAT_TEXT, events[i].text,
2802 COL_WHAT_ICON, events[i].icon,
2806 #ifdef HAVE_CALL_LOGS
2807 gtk_tree_model_iter_nth_child (model, &parent, NULL, 3);
2808 for (i = 0; i < G_N_ELEMENTS (call_events); i++)
2810 gtk_tree_store_append (store, &iter, &parent);
2811 gtk_tree_store_set (store, &iter,
2812 COL_WHAT_TYPE, call_events[i].type,
2813 COL_WHAT_SUBTYPE, call_events[i].subtype,
2814 COL_WHAT_SENSITIVE, TRUE,
2815 COL_WHAT_TEXT, call_events[i].text,
2816 COL_WHAT_ICON, call_events[i].icon,
2821 gtk_tree_view_expand_all (view);
2823 /* select 'Anything' */
2824 if (gtk_tree_model_get_iter_first (model, &iter))
2825 gtk_tree_selection_select_iter (selection, &iter);
2827 /* set up signals */
2828 g_signal_connect (view, "test-collapse-row",
2829 G_CALLBACK (log_window_what_collapse_row_cb),
2831 g_signal_connect (selection, "changed",
2832 G_CALLBACK (log_window_what_changed_cb),
2835 g_object_unref (store);
2839 log_window_maybe_expand_events (void)
2842 GtkTreeModel *model;
2844 view = GTK_TREE_VIEW (log_window->priv->treeview_events);
2845 model = gtk_tree_view_get_model (view);
2847 /* If there's only one result, expand it */
2848 if (gtk_tree_model_iter_n_children (model, NULL) == 1)
2849 gtk_tree_view_expand_all (view);
2853 show_spinner (gpointer data)
2857 if (log_window == NULL)
2860 g_object_get (log_window->priv->spinner, "active", &active, NULL);
2863 gtk_notebook_set_current_page (GTK_NOTEBOOK (log_window->priv->notebook),
2870 show_events (TplActionChain *chain,
2873 log_window_maybe_expand_events ();
2874 gtk_spinner_stop (GTK_SPINNER (log_window->priv->spinner));
2875 gtk_notebook_set_current_page (GTK_NOTEBOOK (log_window->priv->notebook),
2878 _tpl_action_chain_continue (chain);
2882 start_spinner (void)
2884 gtk_spinner_start (GTK_SPINNER (log_window->priv->spinner));
2885 gtk_notebook_set_current_page (GTK_NOTEBOOK (log_window->priv->notebook),
2888 g_timeout_add (1000, show_spinner, NULL);
2889 _tpl_action_chain_append (log_window->priv->chain, show_events, NULL);
2893 log_window_got_messages_for_date_cb (GObject *manager,
2894 GAsyncResult *result,
2897 Ctx *ctx = user_data;
2899 GtkTreeModel *model;
2903 GError *error = NULL;
2906 if (log_window == NULL)
2912 if (log_window->priv->count != ctx->count)
2915 if (!tpl_log_manager_get_events_for_date_finish (TPL_LOG_MANAGER (manager),
2916 result, &events, &error))
2918 DEBUG ("Unable to retrieve messages for the selected date: %s. Aborting",
2920 g_error_free (error);
2924 for (l = events; l; l = l->next)
2926 TplEvent *event = l->data;
2927 gboolean append = TRUE;
2929 #ifdef HAVE_CALL_LOGS
2930 if (TPL_IS_CALL_EVENT (l->data)
2931 && ctx->event_mask & TPL_EVENT_MASK_CALL
2932 && ctx->event_mask != TPL_EVENT_MASK_ANY)
2934 TplCallEvent *call = l->data;
2938 if (ctx->subtype & EVENT_CALL_ALL)
2944 TplCallEndReason reason = tpl_call_event_get_end_reason (call);
2945 TplEntity *sender = tpl_event_get_sender (event);
2946 TplEntity *receiver = tpl_event_get_receiver (event);
2948 if (reason == TPL_CALL_END_REASON_NO_ANSWER)
2950 if (ctx->subtype & EVENT_CALL_MISSED)
2953 else if (ctx->subtype & EVENT_CALL_OUTGOING
2954 && tpl_entity_get_entity_type (sender) == TPL_ENTITY_SELF)
2958 else if (ctx->subtype & EVENT_CALL_INCOMING
2959 && tpl_entity_get_entity_type (receiver) == TPL_ENTITY_SELF)
2969 EmpathyMessage *msg = empathy_message_from_tpl_log_event (event);
2970 log_window_append_message (event, msg);
2971 tp_clear_object (&msg);
2974 g_object_unref (event);
2976 g_list_free (events);
2978 view = GTK_TREE_VIEW (log_window->priv->treeview_events);
2979 model = gtk_tree_view_get_model (view);
2980 n = gtk_tree_model_iter_n_children (model, NULL) - 1;
2982 if (n >= 0 && gtk_tree_model_iter_nth_child (model, &iter, NULL, n))
2986 path = gtk_tree_model_get_path (model, &iter);
2987 gtk_tree_view_scroll_to_cell (view, path, NULL, FALSE, 0, 0);
2988 gtk_tree_path_free (path);
2994 _tpl_action_chain_continue (log_window->priv->chain);
2998 get_events_for_date (TplActionChain *chain, gpointer user_data)
3000 Ctx *ctx = user_data;
3002 tpl_log_manager_get_events_for_date_async (ctx->self->priv->log_manager,
3003 ctx->account, ctx->entity, ctx->event_mask,
3005 log_window_got_messages_for_date_cb,
3010 log_window_get_messages_for_dates (EmpathyLogWindow *self,
3013 GList *accounts, *targets, *acc, *targ, *l;
3014 TplEventTypeMask event_mask;
3015 EventSubtype subtype;
3016 GDate *date, *anytime, *separator;
3018 if (!log_window_get_selected (self,
3019 &accounts, &targets, NULL, NULL, &event_mask, &subtype))
3022 anytime = g_date_new_dmy (2, 1, -1);
3023 separator = g_date_new_dmy (1, 1, -1);
3025 _tpl_action_chain_clear (self->priv->chain);
3026 self->priv->count++;
3028 for (acc = accounts, targ = targets;
3029 acc != NULL && targ != NULL;
3030 acc = acc->next, targ = targ->next)
3032 TpAccount *account = acc->data;
3033 TplEntity *target = targ->data;
3035 for (l = dates; l != NULL; l = l->next)
3040 if (g_date_compare (date, anytime) != 0)
3044 ctx = ctx_new (self, account, target, date, event_mask, subtype,
3046 _tpl_action_chain_append (self->priv->chain, get_events_for_date, ctx);
3050 GtkTreeView *view = GTK_TREE_VIEW (self->priv->treeview_when);
3051 GtkTreeModel *model = gtk_tree_view_get_model (view);
3056 for (next = gtk_tree_model_get_iter_first (model, &iter);
3058 next = gtk_tree_model_iter_next (model, &iter))
3062 gtk_tree_model_get (model, &iter,
3066 if (g_date_compare (d, anytime) != 0 &&
3067 g_date_compare (d, separator) != 0)
3069 ctx = ctx_new (self, account, target, d,
3070 event_mask, subtype, self->priv->count);
3071 _tpl_action_chain_append (self->priv->chain, get_events_for_date, ctx);
3079 _tpl_action_chain_start (self->priv->chain);
3081 g_list_free_full (accounts, g_object_unref);
3082 g_list_free_full (targets, g_object_unref);
3083 g_date_free (separator);
3084 g_date_free (anytime);
3088 log_manager_got_dates_cb (GObject *manager,
3089 GAsyncResult *result,
3092 Ctx *ctx = user_data;
3094 GtkTreeModel *model;
3095 GtkListStore *store;
3099 GError *error = NULL;
3101 if (log_window == NULL)
3104 if (log_window->priv->count != ctx->count)
3107 if (!tpl_log_manager_get_dates_finish (TPL_LOG_MANAGER (manager),
3108 result, &dates, &error))
3110 DEBUG ("Unable to retrieve messages' dates: %s. Aborting",
3115 view = GTK_TREE_VIEW (log_window->priv->treeview_when);
3116 model = gtk_tree_view_get_model (view);
3117 store = GTK_LIST_STORE (model);
3119 for (l = dates; l != NULL; l = l->next)
3121 GDate *date = l->data;
3123 /* Add the date if it's not already there */
3124 has_element = FALSE;
3125 gtk_tree_model_foreach (model, model_has_date, date);
3128 gchar *text = format_date_for_display (date);
3130 gtk_list_store_append (store, &iter);
3131 gtk_list_store_set (store, &iter,
3132 COL_WHEN_DATE, date,
3133 COL_WHEN_TEXT, text,
3134 COL_WHEN_ICON, CALENDAR_ICON,
3141 if (gtk_tree_model_get_iter_first (model, &iter))
3143 gchar *separator = NULL;
3145 if (gtk_tree_model_iter_next (model, &iter))
3147 gtk_tree_model_get (model, &iter,
3148 COL_WHEN_TEXT, &separator,
3152 if (g_strcmp0 (separator, "separator") != 0)
3154 gtk_list_store_prepend (store, &iter);
3155 gtk_list_store_set (store, &iter,
3156 COL_WHEN_DATE, g_date_new_dmy (1, 1, -1),
3157 COL_WHEN_TEXT, "separator",
3160 gtk_list_store_prepend (store, &iter);
3161 gtk_list_store_set (store, &iter,
3162 COL_WHEN_DATE, g_date_new_dmy (2, 1, -1),
3163 COL_WHEN_TEXT, _("Anytime"),
3168 g_list_free_full (dates, g_free);
3171 _tpl_action_chain_continue (log_window->priv->chain);
3175 select_date (TplActionChain *chain, gpointer user_data)
3178 GtkTreeModel *model;
3179 GtkTreeSelection *selection;
3182 gboolean selected = FALSE;
3184 view = GTK_TREE_VIEW (log_window->priv->treeview_when);
3185 model = gtk_tree_view_get_model (view);
3186 selection = gtk_tree_view_get_selection (view);
3188 if (log_window->priv->current_dates != NULL)
3190 for (next = gtk_tree_model_get_iter_first (model, &iter);
3192 next = gtk_tree_model_iter_next (model, &iter))
3196 gtk_tree_model_get (model, &iter,
3197 COL_WHEN_DATE, &date,
3200 if (g_list_find_custom (log_window->priv->current_dates, date,
3201 (GCompareFunc) g_date_compare) != NULL)
3205 gtk_tree_selection_select_iter (selection, &iter);
3206 path = gtk_tree_model_get_path (model, &iter);
3207 gtk_tree_view_scroll_to_cell (view, path, NULL, FALSE, 0, 0);
3210 gtk_tree_path_free (path);
3219 /* Show messages of the most recent date */
3220 if (gtk_tree_model_iter_nth_child (model, &iter, NULL, 2))
3221 gtk_tree_selection_select_iter (selection, &iter);
3224 _tpl_action_chain_continue (log_window->priv->chain);
3228 get_dates_for_entity (TplActionChain *chain, gpointer user_data)
3230 Ctx *ctx = user_data;
3232 tpl_log_manager_get_dates_async (ctx->self->priv->log_manager,
3233 ctx->account, ctx->entity, ctx->event_mask,
3234 log_manager_got_dates_cb, ctx);
3238 log_window_chats_get_messages (EmpathyLogWindow *self,
3239 gboolean force_get_dates)
3241 GList *accounts, *targets, *dates;
3242 TplEventTypeMask event_mask;
3244 GtkTreeModel *model;
3245 GtkListStore *store;
3246 GtkTreeSelection *selection;
3248 if (!log_window_get_selected (self, &accounts, &targets, NULL,
3249 &dates, &event_mask, NULL))
3252 view = GTK_TREE_VIEW (self->priv->treeview_when);
3253 selection = gtk_tree_view_get_selection (view);
3254 model = gtk_tree_view_get_model (view);
3255 store = GTK_LIST_STORE (model);
3257 /* Clear all current messages shown in the textview */
3258 gtk_tree_store_clear (self->priv->store_events);
3260 _tpl_action_chain_clear (self->priv->chain);
3261 self->priv->count++;
3263 /* If there's a search use the returned hits */
3264 if (self->priv->hits != NULL)
3266 if (force_get_dates)
3268 g_signal_handlers_block_by_func (selection,
3269 log_window_when_changed_cb,
3272 gtk_list_store_clear (store);
3274 g_signal_handlers_unblock_by_func (selection,
3275 log_window_when_changed_cb,
3278 populate_dates_from_search_hits (accounts, targets);
3282 populate_events_from_search_hits (accounts, targets, dates);
3285 /* Either use the supplied date or get the last */
3286 else if (force_get_dates || dates == NULL)
3290 if (self->priv->current_dates != NULL)
3292 g_list_free_full (self->priv->current_dates,
3293 (GDestroyNotify) g_date_free);
3294 self->priv->current_dates = NULL;
3297 if (gtk_tree_selection_count_selected_rows (selection) > 0)
3302 paths = gtk_tree_selection_get_selected_rows (selection, NULL);
3304 for (l = paths; l != NULL; l = l->next)
3306 GtkTreePath *path = l->data;
3309 gtk_tree_model_get_iter (model, &iter, path);
3310 gtk_tree_model_get (model, &iter,
3311 COL_WHEN_DATE, &date,
3314 /* The list takes ownership of the date. */
3315 self->priv->current_dates =
3316 g_list_prepend (self->priv->current_dates, date);
3319 g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free);
3322 g_signal_handlers_block_by_func (selection,
3323 log_window_when_changed_cb,
3326 gtk_list_store_clear (store);
3328 g_signal_handlers_unblock_by_func (selection,
3329 log_window_when_changed_cb,
3332 /* Get a list of dates and show them on the treeview */
3333 for (targ = targets, acc = accounts;
3334 targ != NULL && acc != NULL;
3335 targ = targ->next, acc = acc->next)
3337 TpAccount *account = acc->data;
3338 TplEntity *target = targ->data;
3339 Ctx *ctx = ctx_new (self, account, target, NULL, event_mask, 0,
3342 _tpl_action_chain_append (self->priv->chain, get_dates_for_entity, ctx);
3344 _tpl_action_chain_append (self->priv->chain, select_date, NULL);
3345 _tpl_action_chain_start (self->priv->chain);
3349 /* Show messages of the selected date */
3350 log_window_get_messages_for_dates (self, dates);
3353 g_list_free_full (accounts, g_object_unref);
3354 g_list_free_full (targets, g_object_unref);
3355 g_list_free_full (dates, (GFreeFunc) g_date_free);
3359 EmpathyAccountChooserFilterResultCallback callback;
3361 } FilterCallbackData;
3364 got_entities (GObject *manager,
3365 GAsyncResult *result,
3368 FilterCallbackData *data = user_data;
3370 GError *error = NULL;
3372 if (!tpl_log_manager_get_entities_finish (TPL_LOG_MANAGER (manager),
3373 result, &entities, &error))
3375 DEBUG ("Could not get entities: %s", error->message);
3376 g_error_free (error);
3377 data->callback (FALSE, data->user_data);
3381 data->callback (entities != NULL, data->user_data);
3383 g_list_free_full (entities, g_object_unref);
3386 g_slice_free (FilterCallbackData, data);
3390 empathy_account_chooser_filter_has_logs (TpAccount *account,
3391 EmpathyAccountChooserFilterResultCallback callback,
3392 gpointer callback_data,
3395 TplLogManager *manager = tpl_log_manager_dup_singleton ();
3396 FilterCallbackData *cb_data = g_slice_new0 (FilterCallbackData);
3398 cb_data->callback = callback;
3399 cb_data->user_data = callback_data;
3401 tpl_log_manager_get_entities_async (manager, account, got_entities, cb_data);
3403 g_object_unref (manager);
3407 log_window_logger_clear_account_cb (TpProxy *proxy,
3408 const GError *error,
3410 GObject *weak_object)
3412 EmpathyLogWindow *self = EMPATHY_LOG_WINDOW (user_data);
3415 g_warning ("Error when clearing logs: %s", error->message);
3417 /* Refresh the log viewer so the logs are cleared if the account
3418 * has been deleted */
3419 gtk_tree_store_clear (self->priv->store_events);
3420 log_window_who_populate (self);
3422 /* Re-filter the account chooser so the accounts without logs get greyed out */
3423 empathy_account_chooser_set_filter (
3424 EMPATHY_ACCOUNT_CHOOSER (self->priv->account_chooser),
3425 empathy_account_chooser_filter_has_logs, NULL);
3429 log_window_clear_logs_chooser_select_account (EmpathyAccountChooser *chooser,
3430 EmpathyLogWindow *self)
3432 EmpathyAccountChooser *account_chooser;
3434 account_chooser = EMPATHY_ACCOUNT_CHOOSER (self->priv->account_chooser);
3436 empathy_account_chooser_set_account (chooser,
3437 empathy_account_chooser_get_account (account_chooser));
3441 log_window_delete_menu_clicked_cb (GtkMenuItem *menuitem,
3442 EmpathyLogWindow *self)
3444 GtkWidget *dialog, *content_area, *hbox, *label;
3445 EmpathyAccountChooser *account_chooser;
3449 GError *error = NULL;
3451 account_chooser = (EmpathyAccountChooser *) empathy_account_chooser_new ();
3452 empathy_account_chooser_set_has_all_option (account_chooser, TRUE);
3453 empathy_account_chooser_set_filter (account_chooser,
3454 empathy_account_chooser_filter_has_logs, NULL);
3456 /* Select the same account as in the history window */
3457 if (empathy_account_chooser_is_ready (account_chooser))
3458 log_window_clear_logs_chooser_select_account (account_chooser, self);
3460 g_signal_connect (account_chooser, "ready",
3461 G_CALLBACK (log_window_clear_logs_chooser_select_account), self);
3463 dialog = gtk_message_dialog_new_with_markup (GTK_WINDOW (self),
3464 GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING,
3466 _("Are you sure you want to delete all logs of previous conversations?"));
3468 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
3469 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
3470 _("Clear All"), GTK_RESPONSE_APPLY,
3473 content_area = gtk_message_dialog_get_message_area (
3474 GTK_MESSAGE_DIALOG (dialog));
3476 hbox = gtk_hbox_new (FALSE, 6);
3477 label = gtk_label_new (_("Delete from:"));
3478 gtk_box_pack_start (GTK_BOX (hbox), label,
3480 gtk_box_pack_start (GTK_BOX (hbox), GTK_WIDGET (account_chooser),
3482 gtk_box_pack_start (GTK_BOX (content_area), hbox,
3485 gtk_widget_show_all (hbox);
3487 response_id = gtk_dialog_run (GTK_DIALOG (dialog));
3489 if (response_id != GTK_RESPONSE_APPLY)
3492 bus = tp_dbus_daemon_dup (&error);
3495 g_warning ("Could not delete logs: %s", error->message);
3496 g_error_free (error);
3500 logger = g_object_new (TP_TYPE_PROXY,
3501 "bus-name", "org.freedesktop.Telepathy.Logger",
3502 "object-path", "/org/freedesktop/Telepathy/Logger",
3505 g_object_unref (bus);
3507 tp_proxy_add_interface_by_id (logger, EMP_IFACE_QUARK_LOGGER);
3509 if (empathy_account_chooser_has_all_selected (account_chooser))
3511 DEBUG ("Deleting logs for all the accounts");
3513 emp_cli_logger_call_clear (logger, -1,
3514 log_window_logger_clear_account_cb,
3515 self, NULL, G_OBJECT (self));
3521 account = empathy_account_chooser_get_account (account_chooser);
3523 DEBUG ("Deleting logs for %s", tp_proxy_get_object_path (account));
3525 emp_cli_logger_call_clear_account (logger, -1,
3526 tp_proxy_get_object_path (account),
3527 log_window_logger_clear_account_cb,
3528 self, NULL, G_OBJECT (self));
3531 g_object_unref (logger);
3533 gtk_widget_destroy (dialog);