diff options
-rw-r--r-- | configure.ac | 37 | ||||
-rw-r--r-- | libempathy-gtk/empathy-account-chooser.c | 67 | ||||
-rw-r--r-- | libempathy-gtk/empathy-account-chooser.h | 5 | ||||
-rw-r--r-- | libempathy-gtk/empathy-images.h | 4 | ||||
-rw-r--r-- | libempathy-gtk/empathy-log-window.c | 3792 | ||||
-rw-r--r-- | libempathy-gtk/empathy-log-window.h | 12 | ||||
-rw-r--r-- | libempathy-gtk/empathy-log-window.ui | 514 | ||||
-rw-r--r-- | libempathy/Makefile.am | 2 | ||||
-rw-r--r-- | libempathy/action-chain-internal.h | 51 | ||||
-rw-r--r-- | libempathy/action-chain.c | 192 | ||||
-rw-r--r-- | libempathy/empathy-contact.c | 96 | ||||
-rw-r--r-- | libempathy/empathy-message.c | 42 | ||||
-rw-r--r-- | po/POTFILES.in | 1 |
13 files changed, 3403 insertions, 1412 deletions
diff --git a/configure.ac b/configure.ac index 1e95f2f66..b192e5f6b 100644 --- a/configure.ac +++ b/configure.ac @@ -42,7 +42,7 @@ LIBCANBERRA_GTK_REQUIRED=0.25 LIBNOTIFY_REQUIRED=0.7.0 TELEPATHY_FARSIGHT_REQUIRED=0.0.14 TELEPATHY_GLIB_REQUIRED=0.15.1 -TELEPATHY_LOGGER=0.2.0 +TELEPATHY_LOGGER=0.2.8 # Optional deps CLUTTER_GTK_REQUIRED=0.90.3 @@ -210,6 +210,38 @@ if test "x$with_call" = "xyes" -a "x$have_farstream" != "xyes"; then fi AM_CONDITIONAL(HAVE_CALL, test "x$have_farstream" = "xyes") + +# ----------------------------------------------------------- +# Call support in tp-logger +# ----------------------------------------------------------- +AC_ARG_WITH(call-logs, + AC_HELP_STRING([--enable-call-logs=@<:@no/yes/auto@:>@], + [build with call logs support]),, + [with_call_logs=auto]) +if test "x$with_call_logs" != "xno" ; then + SAVE_CFLAGS=$CFLAGS + SAVE_CPPFLAGS=$CPPFLAGS + CFLAGS="$CFLAGS $EMPATHY_CFLAGS" + CPPFLAGS="$CPPFLAGS $EMPATHY_CFLAGS" + + AC_CHECK_HEADER(telepathy-logger/call-event.h, + have_call_logs="yes", have_call_logs="no") + + CFLAGS=$SAVE_CFLAGS + CPPFLAGS=$SAVE_CPPFLAGS + + if test "x$have_call_logs" = "xyes"; then + AC_DEFINE(HAVE_CALL_LOGS, 1, [Define if you have call log support]) + fi +else + have_call_logs=no +fi + +if test "x$with_call_logs" = "xyes" -a "x$have_call_logs" != "xyes"; then + AC_MSG_ERROR([Call logs support requested but telepathy-logger wasn't + built with --enable-call]) +fi + # ----------------------------------------------------------- # evolution-data-server (about-me) # ----------------------------------------------------------- @@ -592,7 +624,7 @@ Configure summary: Spell checking (enchant)....: ${have_enchant} Display maps (libchamplain).: ${have_libchamplain} Location awareness (Geoclue): ${have_geoclue} - Geocode support (Geoclue): ${have_geocode} + Geocode support (Geoclue)...: ${have_geocode} Adium themes (Webkit).......: ${have_webkit} Meego widgets...............: ${have_meego} Control center embedding....: ${have_control_center_embedding} @@ -606,4 +638,5 @@ Configure summary: Nautilus-sendto plugin......: ${have_nst} Salut E-D-S support.........: ${with_eds} Exp. Call channel handler...: ${have_farstream} + Exp. Call log support.......: ${have_call_logs} " diff --git a/libempathy-gtk/empathy-account-chooser.c b/libempathy-gtk/empathy-account-chooser.c index 2df4a153a..32b314be1 100644 --- a/libempathy-gtk/empathy-account-chooser.c +++ b/libempathy-gtk/empathy-account-chooser.c @@ -312,6 +312,30 @@ empathy_account_chooser_new (void) return chooser; } +gboolean +empathy_account_chooser_has_all_selected (EmpathyAccountChooser *chooser) +{ + EmpathyAccountChooserPriv *priv; + GtkTreeModel *model; + GtkTreeIter iter; + RowType type; + + g_return_val_if_fail (EMPATHY_IS_ACCOUNT_CHOOSER (chooser), FALSE); + + priv = GET_PRIV (chooser); + + g_return_val_if_fail (priv->has_all_option == TRUE, FALSE); + + model = gtk_combo_box_get_model (GTK_COMBO_BOX (chooser)); + if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (chooser), &iter)) { + return FALSE; + } + + gtk_tree_model_get (model, &iter, COL_ACCOUNT_ROW_TYPE, &type, -1); + + return type == ROW_ALL; +} + /** * empathy_account_chooser_dup_account: * @chooser: an #EmpathyAccountChooser @@ -414,6 +438,30 @@ empathy_account_chooser_set_account (EmpathyAccountChooser *chooser, return data.set; } +void +empathy_account_chooser_set_all (EmpathyAccountChooser *chooser) +{ + EmpathyAccountChooserPriv *priv; + GtkComboBox *combobox; + GtkTreeModel *model; + GtkTreeIter iter; + + g_return_if_fail (EMPATHY_IS_ACCOUNT_CHOOSER (chooser)); + + priv = GET_PRIV (chooser); + + g_return_if_fail (priv->has_all_option); + + combobox = GTK_COMBO_BOX (chooser); + model = gtk_combo_box_get_model (combobox); + + if (gtk_tree_model_get_iter_first (model, &iter)) { + /* 'All accounts' is the first row */ + gtk_combo_box_set_active_iter (combobox, &iter); + priv->account_manually_set = TRUE; + } +} + /** * empathy_account_chooser_get_has_all_option: * @chooser: an #EmpathyAccountChooser @@ -488,7 +536,7 @@ empathy_account_chooser_set_has_all_option (EmpathyAccountChooser *chooser, gtk_list_store_prepend (store, &iter); gtk_list_store_set (store, &iter, - COL_ACCOUNT_TEXT, _("All"), + COL_ACCOUNT_TEXT, _("All accounts"), COL_ACCOUNT_ENABLED, TRUE, COL_ACCOUNT_POINTER, NULL, COL_ACCOUNT_ROW_TYPE, ROW_ALL, @@ -697,8 +745,15 @@ account_chooser_find_account_foreach (GtkTreeModel *model, { FindAccountData *data = user_data; TpAccount *account; + RowType type; - gtk_tree_model_get (model, iter, COL_ACCOUNT_POINTER, &account, -1); + gtk_tree_model_get (model, iter, + COL_ACCOUNT_POINTER, &account, + COL_ACCOUNT_ROW_TYPE, &type, + -1); + + if (type != ROW_ACCOUNT) + return FALSE; if (account == data->account) { data->found = TRUE; @@ -1053,3 +1108,11 @@ empathy_account_chooser_get_account (EmpathyAccountChooser *chooser) return account; } + +TpAccountManager * +empathy_account_chooser_get_account_manager (EmpathyAccountChooser *self) +{ + EmpathyAccountChooserPriv *priv = GET_PRIV (self); + + return priv->manager; +} diff --git a/libempathy-gtk/empathy-account-chooser.h b/libempathy-gtk/empathy-account-chooser.h index 62854b959..7ed920e9f 100644 --- a/libempathy-gtk/empathy-account-chooser.h +++ b/libempathy-gtk/empathy-account-chooser.h @@ -27,7 +27,7 @@ #include <gtk/gtk.h> -#include <telepathy-glib/account.h> +#include <telepathy-glib/telepathy-glib.h> G_BEGIN_DECLS @@ -81,9 +81,12 @@ TpAccount * empathy_account_chooser_get_account (EmpathyAccountChooser TpConnection * empathy_account_chooser_get_connection (EmpathyAccountChooser *chooser); gboolean empathy_account_chooser_set_account (EmpathyAccountChooser *chooser, TpAccount *account); +void empathy_account_chooser_set_all (EmpathyAccountChooser *chooser); +TpAccountManager * empathy_account_chooser_get_account_manager (EmpathyAccountChooser *self); gboolean empathy_account_chooser_get_has_all_option (EmpathyAccountChooser *chooser); void empathy_account_chooser_set_has_all_option (EmpathyAccountChooser *chooser, gboolean has_all_option); +gboolean empathy_account_chooser_has_all_selected (EmpathyAccountChooser *chooser); void empathy_account_chooser_set_filter (EmpathyAccountChooser *chooser, EmpathyAccountChooserFilterFunc filter, gpointer user_data); diff --git a/libempathy-gtk/empathy-images.h b/libempathy-gtk/empathy-images.h index 86f1db641..e2512d495 100644 --- a/libempathy-gtk/empathy-images.h +++ b/libempathy-gtk/empathy-images.h @@ -47,6 +47,10 @@ G_BEGIN_DECLS #define EMPATHY_IMAGE_DOCUMENT_SEND "document-send" #define EMPATHY_IMAGE_AVATAR_DEFAULT "avatar-default" +#define EMPATHY_IMAGE_CALL_MISSED "call-start" +#define EMPATHY_IMAGE_CALL_INCOMING "call-start" +#define EMPATHY_IMAGE_CALL_OUTGOING "call-stop" + G_END_DECLS #endif /* __EMPATHY_IMAGES_ICONS_H__ */ diff --git a/libempathy-gtk/empathy-log-window.c b/libempathy-gtk/empathy-log-window.c index 6dc3b3f5e..4ff658834 100644 --- a/libempathy-gtk/empathy-log-window.c +++ b/libempathy-gtk/empathy-log-window.c @@ -1,7 +1,6 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Copyright (C) 2006-2007 Imendio AB - * Copyright (C) 2007-2008 Collabora Ltd. + * Copyright (C) 2007-2011 Collabora Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -20,6 +19,7 @@ * * Authors: Martyn Russell <martyn@imendio.com> * Xavier Claessens <xclaesse@gmail.com> + * Emilio Pozuelo Monfort <emilio.pozuelo@collabora.co.uk> */ #include "config.h" @@ -30,1361 +30,2953 @@ #include <glib/gi18n-lib.h> #include <gtk/gtk.h> -#include <telepathy-glib/account-manager.h> -#include <telepathy-logger/log-manager.h> +#include <telepathy-glib/telepathy-glib.h> +#include <telepathy-glib/proxy-subclass.h> +#include <telepathy-logger/telepathy-logger.h> +#ifdef HAVE_CALL_LOGS +# include <telepathy-logger/call-event.h> +#endif + +#include <extensions/extensions.h> + +#include <libempathy/action-chain-internal.h> #include <libempathy/empathy-chatroom-manager.h> #include <libempathy/empathy-chatroom.h> #include <libempathy/empathy-message.h> +#include <libempathy/empathy-request-util.h> #include <libempathy/empathy-utils.h> #include <libempathy/empathy-time.h> #include "empathy-log-window.h" #include "empathy-account-chooser.h" +#include "empathy-call-utils.h" #include "empathy-chat-view.h" +#include "empathy-contact-dialogs.h" +#include "empathy-images.h" #include "empathy-theme-manager.h" #include "empathy-ui-utils.h" #define DEBUG_FLAG EMPATHY_DEBUG_OTHER #include <libempathy/empathy-debug.h> -typedef struct { - GtkWidget *window; - - GtkWidget *notebook; - - GtkWidget *entry_find; - GtkWidget *button_find; - GtkWidget *treeview_find; - GtkWidget *scrolledwindow_find; - EmpathyChatView *chatview_find; - GtkWidget *button_previous; - GtkWidget *button_next; - - GtkWidget *vbox_chats; - GtkWidget *account_chooser_chats; - GtkWidget *entry_chats; - GtkWidget *calendar_chats; - GtkWidget *treeview_chats; - GtkWidget *scrolledwindow_chats; - EmpathyChatView *chatview_chats; - - gchar *last_find; - - TplLogManager *log_manager; - - /* Those are only used while waiting for the account chooser to be ready */ - TpAccount *selected_account; - gchar *selected_chat_id; - gboolean selected_is_chatroom; +typedef struct +{ + GtkWidget *window; + + GtkWidget *button_profile; + GtkWidget *button_chat; + GtkWidget *button_call; + GtkWidget *button_video; + + GtkWidget *search_entry; + + GtkWidget *notebook; + GtkWidget *spinner; + + GtkWidget *treeview_who; + GtkWidget *treeview_what; + GtkWidget *treeview_when; + GtkWidget *treeview_events; + + GtkTreeStore *store_events; + + GtkWidget *account_chooser; + + gchar *last_find; + + TplActionChain *chain; + TplLogManager *log_manager; + + /* Used to cancel logger calls when no longer needed */ + guint count; + + /* List of owned TplLogSearchHits, free with tpl_log_search_hit_free */ + GList *hits; + guint source; + + /* Only used while waiting for the account chooser to be ready */ + TpAccount *selected_account; + gchar *selected_chat_id; + gboolean selected_is_chatroom; } EmpathyLogWindow; -static void log_window_destroy_cb (GtkWidget *widget, - EmpathyLogWindow *window); -static void log_window_entry_find_changed_cb (GtkWidget *entry, - EmpathyLogWindow *window); -static void log_window_find_changed_cb (GtkTreeSelection *selection, - EmpathyLogWindow *window); -static void log_window_find_populate (EmpathyLogWindow *window, - const gchar *search_criteria); -static void log_window_find_setup (EmpathyLogWindow *window); -static void log_window_button_find_clicked_cb (GtkWidget *widget, - EmpathyLogWindow *window); -static void log_window_entry_find_activate_cb (GtkWidget *widget, - EmpathyLogWindow *window); -static void log_window_button_next_clicked_cb (GtkWidget *widget, - EmpathyLogWindow *window); -static void log_window_button_previous_clicked_cb (GtkWidget *widget, - EmpathyLogWindow *window); -static void log_window_button_close_clicked_cb (GtkWidget *widget, - EmpathyLogWindow *window); -static void log_window_chats_changed_cb (GtkTreeSelection *selection, - EmpathyLogWindow *window); -static void log_window_chats_populate (EmpathyLogWindow *window); -static void log_window_chats_setup (EmpathyLogWindow *window); -static void log_window_chats_accounts_changed_cb (GtkWidget *combobox, - EmpathyLogWindow *window); -static void log_window_chats_set_selected (EmpathyLogWindow *window); -static gboolean log_window_chats_get_selected (EmpathyLogWindow *window, - TpAccount **account, - TplEntity **target); -static void log_window_chats_get_messages (EmpathyLogWindow *window, - GDate *date_to_show); -static void log_window_calendar_chats_day_selected_cb (GtkWidget *calendar, - EmpathyLogWindow *window); -static void log_window_calendar_chats_month_changed_cb (GtkWidget *calendar, - EmpathyLogWindow *window); -static void log_window_entry_chats_changed_cb (GtkWidget *entry, - EmpathyLogWindow *window); -static void log_window_entry_chats_activate_cb (GtkWidget *entry, - EmpathyLogWindow *window); - -enum { - COL_FIND_ACCOUNT_ICON, - COL_FIND_ACCOUNT_NAME, - COL_FIND_ACCOUNT, - COL_FIND_CHAT_NAME, - COL_FIND_TARGET, - COL_FIND_DATE, - COL_FIND_DATE_READABLE, - COL_FIND_COUNT +static void log_window_destroy_cb (GtkWidget *widget, + EmpathyLogWindow *window); +static void log_window_search_entry_changed_cb (GtkWidget *entry, + EmpathyLogWindow *window); +static void log_window_search_entry_activate_cb (GtkWidget *widget, + EmpathyLogWindow *window); +static void log_window_search_entry_icon_pressed_cb (GtkEntry *entry, + GtkEntryIconPosition icon_pos, + GdkEvent *event, + gpointer user_data); +static void log_window_who_populate (EmpathyLogWindow *window); +static void log_window_who_setup (EmpathyLogWindow *window); +static void log_window_when_setup (EmpathyLogWindow *window); +static void log_window_what_setup (EmpathyLogWindow *window); +static void log_window_events_setup (EmpathyLogWindow *window); +static void log_window_chats_accounts_changed_cb (GtkWidget *combobox, + EmpathyLogWindow *window); +static void log_window_chats_set_selected (EmpathyLogWindow *window); +static void log_window_chats_get_messages (EmpathyLogWindow *window, + gboolean force_get_dates); +static void log_window_when_changed_cb (GtkTreeSelection *selection, + EmpathyLogWindow *window); +static void log_window_delete_menu_clicked_cb (GtkMenuItem *menuitem, + EmpathyLogWindow *window); + +static void +empathy_account_chooser_filter_has_logs (TpAccount *account, + EmpathyAccountChooserFilterResultCallback callback, + gpointer callback_data, + gpointer user_data); + +enum +{ + PAGE_EVENTS, + PAGE_SPINNER, + PAGE_EMPTY +}; + +enum +{ + COL_TYPE_ANY, + COL_TYPE_SEPARATOR, + COL_TYPE_NORMAL +}; + +enum +{ + COL_WHO_TYPE, + COL_WHO_ICON, + COL_WHO_NAME, + COL_WHO_ACCOUNT, + COL_WHO_TARGET, + COL_WHO_COUNT +}; + +enum +{ + COL_WHAT_TYPE, + COL_WHAT_SUBTYPE, + COL_WHAT_TEXT, + COL_WHAT_ICON, + COL_WHAT_COUNT }; -enum { - COL_CHAT_ICON, - COL_CHAT_NAME, - COL_CHAT_ACCOUNT, - COL_CHAT_TARGET, - COL_CHAT_COUNT +enum +{ + COL_WHEN_DATE, + COL_WHEN_TEXT, + COL_WHEN_ICON, + COL_WHEN_COUNT }; +enum +{ + COL_EVENTS_TYPE, + COL_EVENTS_TS, + COL_EVENTS_PRETTY_DATE, + COL_EVENTS_ICON, + COL_EVENTS_TEXT, + COL_EVENTS_ACCOUNT, + COL_EVENTS_TARGET, + COL_EVENTS_EVENT, + COL_EVENTS_COUNT +}; + +#define CALENDAR_ICON "stock_calendar" + +/* Seconds between two messages to be considered one conversation */ +#define MAX_GAP 30*60 + +#define WHAT_TYPE_SEPARATOR -1 + +typedef enum +{ + EVENT_CALL_INCOMING = 1 << 0, + EVENT_CALL_OUTGOING = 1 << 1, + EVENT_CALL_MISSED = 1 << 2, + EVENT_CALL_ALL = 1 << 3, +} EventSubtype; + static EmpathyLogWindow *log_window = NULL; +static gboolean has_element; + +#ifndef _date_copy +#define _date_copy(d) g_date_new_julian (g_date_get_julian (d)) +#endif + +typedef struct +{ + EmpathyLogWindow *window; + TpAccount *account; + TplEntity *entity; + GDate *date; + TplEventTypeMask event_mask; + EventSubtype subtype; + guint count; +} Ctx; + +static Ctx * +ctx_new (EmpathyLogWindow *window, + TpAccount *account, + TplEntity *entity, + GDate *date, + TplEventTypeMask event_mask, + EventSubtype subtype, + guint count) +{ + Ctx *ctx = g_slice_new0 (Ctx); + + ctx->window = window; + if (account != NULL) + ctx->account = g_object_ref (account); + if (entity != NULL) + ctx->entity = g_object_ref (entity); + if (date != NULL) + ctx->date = _date_copy (date); + ctx->event_mask = event_mask; + ctx->subtype = subtype; + ctx->count = count; + + return ctx; +} + static void -account_manager_prepared_cb (GObject *source_object, - GAsyncResult *result, - gpointer user_data) -{ - TpAccountManager *account_manager = TP_ACCOUNT_MANAGER (source_object); - EmpathyLogWindow *window = user_data; - guint account_num; - GList *accounts; - GError *error = NULL; - - if (log_window == NULL) - return; - - if (!tp_account_manager_prepare_finish (account_manager, result, &error)) { - DEBUG ("Failed to prepare account manager: %s", error->message); - g_error_free (error); - return; - } - - accounts = tp_account_manager_get_valid_accounts (account_manager); - account_num = g_list_length (accounts); - g_list_free (accounts); - - if (account_num > 1) { - gtk_widget_show (window->vbox_chats); - gtk_widget_show (window->account_chooser_chats); - } else { - gtk_widget_hide (window->vbox_chats); - gtk_widget_hide (window->account_chooser_chats); - } +ctx_free (Ctx *ctx) +{ + tp_clear_object (&ctx->account); + tp_clear_object (&ctx->entity); + tp_clear_pointer (&ctx->date, g_date_free); + + g_slice_free (Ctx, ctx); } static void account_chooser_ready_cb (EmpathyAccountChooser *chooser, - EmpathyLogWindow *window) + EmpathyLogWindow *window) { - gtk_notebook_set_current_page (GTK_NOTEBOOK (window->notebook), 1); - - /* We'll display the account once the model has been populate with the chats - * of this account. */ - empathy_account_chooser_set_account (EMPATHY_ACCOUNT_CHOOSER ( - window->account_chooser_chats), window->selected_account); + /* We'll display the account once the model has been populate with the chats + * of this account. */ + empathy_account_chooser_set_account (EMPATHY_ACCOUNT_CHOOSER ( + window->account_chooser), window->selected_account); } static void select_account_once_ready (EmpathyLogWindow *self, - TpAccount *account, - const gchar *chat_id, - gboolean is_chatroom) + TpAccount *account, + const gchar *chat_id, + gboolean is_chatroom) { - EmpathyAccountChooser *account_chooser = EMPATHY_ACCOUNT_CHOOSER (self->account_chooser_chats); + EmpathyAccountChooser *account_chooser; - tp_clear_object (&self->selected_account); - self->selected_account = g_object_ref (account); + account_chooser = EMPATHY_ACCOUNT_CHOOSER (self->account_chooser); - g_free (self->selected_chat_id); - self->selected_chat_id = g_strdup (chat_id); + tp_clear_object (&self->selected_account); + self->selected_account = g_object_ref (account); - self->selected_is_chatroom = is_chatroom; + g_free (self->selected_chat_id); + self->selected_chat_id = g_strdup (chat_id); - if (empathy_account_chooser_is_ready (account_chooser)) - account_chooser_ready_cb (account_chooser, self); - else - /* Chat will be selected once the account chooser is ready */ - g_signal_connect (account_chooser, "ready", - G_CALLBACK (account_chooser_ready_cb), self); -} + self->selected_is_chatroom = is_chatroom; -GtkWidget * -empathy_log_window_show (TpAccount *account, - const gchar *chat_id, - gboolean is_chatroom, - GtkWindow *parent) -{ - TpAccountManager *account_manager; - GtkBuilder *gui; - gchar *filename; - EmpathyLogWindow *window; - EmpathyThemeManager *theme_mgr; - - if (log_window != NULL) { - gtk_window_present (GTK_WINDOW (log_window->window)); - - if (account != NULL && chat_id != NULL) { - gtk_notebook_set_current_page (GTK_NOTEBOOK (log_window->notebook), 1); - select_account_once_ready (log_window, account, chat_id, is_chatroom); - } - - return log_window->window; - } - - log_window = g_new0 (EmpathyLogWindow, 1); - log_window->log_manager = tpl_log_manager_dup_singleton (); - - window = log_window; - - filename = empathy_file_lookup ("empathy-log-window.ui", - "libempathy-gtk"); - gui = empathy_builder_get_file (filename, - "log_window", &window->window, - "notebook", &window->notebook, - "entry_find", &window->entry_find, - "button_find", &window->button_find, - "treeview_find", &window->treeview_find, - "scrolledwindow_find", &window->scrolledwindow_find, - "button_previous", &window->button_previous, - "button_next", &window->button_next, - "entry_chats", &window->entry_chats, - "calendar_chats", &window->calendar_chats, - "vbox_chats", &window->vbox_chats, - "treeview_chats", &window->treeview_chats, - "scrolledwindow_chats", &window->scrolledwindow_chats, - NULL); - g_free (filename); - - empathy_builder_connect (gui, window, - "log_window", "destroy", log_window_destroy_cb, - "entry_find", "changed", log_window_entry_find_changed_cb, - "entry_find", "activate", log_window_entry_find_activate_cb, - "button_previous", "clicked", log_window_button_previous_clicked_cb, - "button_next", "clicked", log_window_button_next_clicked_cb, - "button_close", "clicked", log_window_button_close_clicked_cb, - "button_close2", "clicked", log_window_button_close_clicked_cb, - "button_find", "clicked", log_window_button_find_clicked_cb, - "entry_chats", "changed", log_window_entry_chats_changed_cb, - "entry_chats", "activate", log_window_entry_chats_activate_cb, - NULL); - - g_object_unref (gui); - - g_object_add_weak_pointer (G_OBJECT (window->window), - (gpointer) &log_window); - - /* We set this up here so we can block it when needed. */ - g_signal_connect (window->calendar_chats, "day-selected", - G_CALLBACK (log_window_calendar_chats_day_selected_cb), - window); - g_signal_connect (window->calendar_chats, "month-changed", - G_CALLBACK (log_window_calendar_chats_month_changed_cb), - window); - - /* Configure Search EmpathyChatView */ - theme_mgr = empathy_theme_manager_dup_singleton (); - window->chatview_find = empathy_theme_manager_create_view (theme_mgr); - gtk_container_add (GTK_CONTAINER (window->scrolledwindow_find), - GTK_WIDGET (window->chatview_find)); - gtk_widget_show (GTK_WIDGET (window->chatview_find)); - - /* Configure Contacts EmpathyChatView */ - window->chatview_chats = empathy_theme_manager_create_view (theme_mgr); - gtk_container_add (GTK_CONTAINER (window->scrolledwindow_chats), - GTK_WIDGET (window->chatview_chats)); - gtk_widget_show (GTK_WIDGET (window->chatview_chats)); - g_object_unref (theme_mgr); - - /* Account chooser for chats */ - window->account_chooser_chats = empathy_account_chooser_new (); - - gtk_box_pack_start (GTK_BOX (window->vbox_chats), - window->account_chooser_chats, - FALSE, TRUE, 0); - - g_signal_connect (window->account_chooser_chats, "changed", - G_CALLBACK (log_window_chats_accounts_changed_cb), - window); - - /* Populate */ - account_manager = tp_account_manager_dup (); - tp_account_manager_prepare_async (account_manager, NULL, - account_manager_prepared_cb, window); - g_object_unref (account_manager); - - /* Search List */ - log_window_find_setup (window); - - /* Contacts */ - log_window_chats_setup (window); - log_window_chats_populate (window); - - if (account != NULL && chat_id != NULL) - select_account_once_ready (window, account, chat_id, is_chatroom); - - if (parent != NULL) { - gtk_window_set_transient_for (GTK_WINDOW (window->window), - GTK_WINDOW (parent)); - } - - gtk_widget_show (window->window); - - return window->window; + if (empathy_account_chooser_is_ready (account_chooser)) + account_chooser_ready_cb (account_chooser, self); + else + /* Chat will be selected once the account chooser is ready */ + g_signal_connect (account_chooser, "ready", + G_CALLBACK (account_chooser_ready_cb), self); } static void -log_window_destroy_cb (GtkWidget *widget, - EmpathyLogWindow *window) +toolbutton_profile_clicked (GtkToolButton *toolbutton, + EmpathyLogWindow *window) { - g_free (window->last_find); - g_object_unref (window->log_manager); - tp_clear_object (&window->selected_account); - g_free (window->selected_chat_id); + GtkTreeView *view; + GtkTreeSelection *selection; + GtkTreeModel *model; + GtkTreeIter iter; + GtkTreePath *path; + GList *paths; + TpAccount *account; + TplEntity *target; + EmpathyContact *contact; + gint type; - g_free (window); + g_return_if_fail (window != NULL); + + view = GTK_TREE_VIEW (log_window->treeview_who); + selection = gtk_tree_view_get_selection (view); + + paths = gtk_tree_selection_get_selected_rows (selection, &model); + g_return_if_fail (paths != NULL); + + path = paths->data; + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_model_get (model, &iter, + COL_WHO_ACCOUNT, &account, + COL_WHO_TARGET, &target, + COL_WHO_TYPE, &type, + -1); + + g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free); + + g_return_if_fail (type == COL_TYPE_NORMAL); + + contact = empathy_contact_from_tpl_contact (account, target); + empathy_contact_information_dialog_show (contact, + GTK_WINDOW (window->window)); + g_object_unref (contact); + + g_object_unref (account); + g_object_unref (target); } -/* - * Search code. - */ static void -log_window_entry_find_changed_cb (GtkWidget *entry, - EmpathyLogWindow *window) +toolbutton_chat_clicked (GtkToolButton *toolbutton, + EmpathyLogWindow *window) { - const gchar *str; - gboolean is_sensitive = TRUE; + GtkTreeView *view; + GtkTreeSelection *selection; + GtkTreeModel *model; + GtkTreeIter iter; + GtkTreePath *path; + GList *paths; + TpAccount *account; + TplEntity *target; + EmpathyContact *contact; + gint type; - str = gtk_entry_get_text (GTK_ENTRY (window->entry_find)); + g_return_if_fail (window != NULL); - is_sensitive &= !EMP_STR_EMPTY (str); - is_sensitive &= - !window->last_find || - (window->last_find && tp_strdiff (window->last_find, str)); + view = GTK_TREE_VIEW (log_window->treeview_who); + selection = gtk_tree_view_get_selection (view); - gtk_widget_set_sensitive (window->button_find, is_sensitive); + paths = gtk_tree_selection_get_selected_rows (selection, &model); + g_return_if_fail (paths != NULL); + + path = paths->data; + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_model_get (model, &iter, + COL_WHO_ACCOUNT, &account, + COL_WHO_TARGET, &target, + COL_WHO_TYPE, &type, + -1); + + g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free); + + g_return_if_fail (type == COL_TYPE_NORMAL); + + contact = empathy_contact_from_tpl_contact (account, target); + empathy_chat_with_contact (contact, + gtk_get_current_event_time ()); + + g_object_unref (contact); + g_object_unref (account); + g_object_unref (target); } static void -got_events_for_date_cb (GObject *manager, - GAsyncResult *result, - gpointer user_data) -{ - EmpathyLogWindow *window = user_data; - GList *messages; - GList *l; - gboolean can_do_previous; - gboolean can_do_next; - GError *error = NULL; - - if (log_window == NULL) - return; - - if (!tpl_log_manager_get_events_for_date_finish (TPL_LOG_MANAGER (manager), - result, &messages, &error)) { - DEBUG ("Unable to retrieve messages for the selected date: %s. Aborting", - error->message); - empathy_chat_view_append_event (window->chatview_find, - "Unable to retrieve messages for the selected date"); - g_error_free (error); - return; - } - - for (l = messages; l; l = l->next) { - EmpathyMessage *message; - - g_assert (TPL_IS_EVENT (l->data)); - - message = empathy_message_from_tpl_log_event (l->data); - g_object_unref (l->data); - empathy_chat_view_append_message (window->chatview_find, message); - g_object_unref (message); - } - g_list_free (messages); - - /* Scroll to the most recent messages */ - empathy_chat_view_scroll (window->chatview_find, TRUE); - - /* Highlight and find messages */ - empathy_chat_view_highlight (window->chatview_find, - window->last_find, - FALSE); - empathy_chat_view_find_next (window->chatview_find, - window->last_find, - TRUE, - FALSE); - empathy_chat_view_find_abilities (window->chatview_find, - window->last_find, - FALSE, - &can_do_previous, - &can_do_next); - gtk_widget_set_sensitive (window->button_previous, can_do_previous); - gtk_widget_set_sensitive (window->button_next, can_do_next); - gtk_widget_set_sensitive (window->button_find, FALSE); -} - -static GDate * -gdate_from_str (const gchar *str) -{ - guint u; - guint day, month, year; - - if (sscanf (str, "%u", &u) != 1) - return NULL; - - day = (u % 100); - month = ((u / 100) % 100); - year = (u / 10000); - - if (!g_date_valid_dmy (day, month, year)) - return NULL; - - return g_date_new_dmy (day, month, year); +toolbutton_av_clicked (GtkToolButton *toolbutton, + EmpathyLogWindow *window) +{ + GtkTreeView *view; + GtkTreeSelection *selection; + GtkTreeModel *model; + GtkTreeIter iter; + GtkTreePath *path; + GList *paths; + TpAccount *account; + gchar *contact; + gint type; + gboolean video; + + g_return_if_fail (window != NULL); + + view = GTK_TREE_VIEW (log_window->treeview_who); + selection = gtk_tree_view_get_selection (view); + + paths = gtk_tree_selection_get_selected_rows (selection, &model); + g_return_if_fail (paths != NULL); + + path = paths->data; + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_model_get (model, &iter, + COL_WHO_ACCOUNT, &account, + COL_WHO_NAME, &contact, + COL_WHO_TYPE, &type, + -1); + + g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free); + + g_return_if_fail (type == COL_TYPE_NORMAL); + + video = (GTK_WIDGET (toolbutton) == window->button_video); + + empathy_call_new_with_streams (contact, account, + TRUE, video, gtk_get_current_event_time ()); + + g_free (contact); + g_object_unref (account); } -static void -log_window_find_changed_cb (GtkTreeSelection *selection, - EmpathyLogWindow *window) +GtkWidget * +empathy_log_window_show (TpAccount *account, + const gchar *chat_id, + gboolean is_chatroom, + GtkWindow *parent) { - GtkTreeView *view; - GtkTreeModel *model; - GtkTreeIter iter; - TpAccount *account; - TplEntity *target; - gchar *date; - GDate *gdate; + EmpathyAccountChooser *account_chooser; + GtkBuilder *gui; + gchar *filename; + EmpathyLogWindow *window; + GtkWidget *vbox, *accounts, *search, *label, *quit; + + if (log_window != NULL) + { + gtk_window_present (GTK_WINDOW (log_window->window)); + + if (account != NULL && chat_id != NULL) + select_account_once_ready (log_window, account, chat_id, is_chatroom); + + return log_window->window; + } + + log_window = g_new0 (EmpathyLogWindow, 1); + log_window->chain = _tpl_action_chain_new_async (NULL, NULL, NULL); + + log_window->log_manager = tpl_log_manager_dup_singleton (); + + window = log_window; + + filename = empathy_file_lookup ("empathy-log-window.ui", "libempathy-gtk"); + gui = empathy_builder_get_file (filename, + "log_window", &window->window, + "toolbutton_profile", &window->button_profile, + "toolbutton_chat", &window->button_chat, + "toolbutton_call", &window->button_call, + "toolbutton_video", &window->button_video, + "toolbutton_accounts", &accounts, + "toolbutton_search", &search, + "imagemenuitem_quit", &quit, + "treeview_who", &window->treeview_who, + "treeview_what", &window->treeview_what, + "treeview_when", &window->treeview_when, + "treeview_events", &window->treeview_events, + "notebook", &window->notebook, + "spinner", &window->spinner, + NULL); + g_free (filename); + + empathy_builder_connect (gui, window, + "log_window", "destroy", log_window_destroy_cb, + "toolbutton_profile", "clicked", toolbutton_profile_clicked, + "toolbutton_chat", "clicked", toolbutton_chat_clicked, + "toolbutton_call", "clicked", toolbutton_av_clicked, + "toolbutton_video", "clicked", toolbutton_av_clicked, + "imagemenuitem_delete", "activate", log_window_delete_menu_clicked_cb, + NULL); + + g_object_unref (gui); + + g_object_add_weak_pointer (G_OBJECT (window->window), + (gpointer) &log_window); + + g_signal_connect_swapped (quit, "activate", + G_CALLBACK (gtk_widget_destroy), window->window); + + /* Account chooser for chats */ + vbox = gtk_vbox_new (FALSE, 3); + + window->account_chooser = empathy_account_chooser_new (); + account_chooser = EMPATHY_ACCOUNT_CHOOSER (window->account_chooser); + empathy_account_chooser_set_has_all_option (account_chooser, TRUE); + empathy_account_chooser_set_filter (account_chooser, + empathy_account_chooser_filter_has_logs, NULL); + empathy_account_chooser_set_all (account_chooser); + + g_signal_connect (window->account_chooser, "changed", + G_CALLBACK (log_window_chats_accounts_changed_cb), + window); - /* Get selected information */ - view = GTK_TREE_VIEW (window->treeview_find); - model = gtk_tree_view_get_model (view); + label = gtk_label_new (_("Show")); - if (!gtk_tree_selection_get_selected (selection, NULL, &iter)) { - gtk_widget_set_sensitive (window->button_previous, FALSE); - gtk_widget_set_sensitive (window->button_next, FALSE); + gtk_box_pack_start (GTK_BOX (vbox), + window->account_chooser, + FALSE, FALSE, 0); - empathy_chat_view_clear (window->chatview_find); + gtk_box_pack_start (GTK_BOX (vbox), + label, + FALSE, FALSE, 0); - return; - } + gtk_widget_show_all (vbox); + gtk_container_add (GTK_CONTAINER (accounts), vbox); - gtk_widget_set_sensitive (window->button_previous, TRUE); - gtk_widget_set_sensitive (window->button_next, TRUE); + /* Search entry */ + vbox = gtk_vbox_new (FALSE, 3); - gtk_tree_model_get (model, &iter, - COL_FIND_ACCOUNT, &account, - COL_FIND_TARGET, &target, - COL_FIND_DATE, &date, - -1); + window->search_entry = gtk_entry_new (); + gtk_entry_set_icon_from_stock (GTK_ENTRY (window->search_entry), + GTK_ENTRY_ICON_PRIMARY, GTK_STOCK_FIND); + gtk_entry_set_icon_from_stock (GTK_ENTRY (window->search_entry), + GTK_ENTRY_ICON_SECONDARY, GTK_STOCK_CLEAR); - /* Clear all current messages shown in the textview */ - empathy_chat_view_clear (window->chatview_find); + label = gtk_label_new (_("Search")); - /* Turn off scrolling temporarily */ - empathy_chat_view_scroll (window->chatview_find, FALSE); + gtk_box_pack_start (GTK_BOX (vbox), + window->search_entry, + FALSE, FALSE, 0); - /* Get messages */ - gdate = gdate_from_str (date); + gtk_box_pack_start (GTK_BOX (vbox), + label, + FALSE, FALSE, 0); - if (gdate != NULL) { - tpl_log_manager_get_events_for_date_async (window->log_manager, - account, - target, - TPL_EVENT_MASK_TEXT, - gdate, - got_events_for_date_cb, - window); + gtk_widget_show_all (vbox); + gtk_container_add (GTK_CONTAINER (search), vbox); - g_date_free (gdate); - } + g_signal_connect (window->search_entry, "changed", + G_CALLBACK (log_window_search_entry_changed_cb), + window); - g_object_unref (account); - g_object_unref (target); - g_free (date); -} + g_signal_connect (window->search_entry, "activate", + G_CALLBACK (log_window_search_entry_activate_cb), + window); + g_signal_connect (window->search_entry, "icon-press", + G_CALLBACK (log_window_search_entry_icon_pressed_cb), + window); -static void -log_manager_searched_new_cb (GObject *manager, - GAsyncResult *result, - gpointer user_data) -{ - GList *hits; - GList *l; - GtkTreeIter iter; - GtkListStore *store = user_data; - GError *error = NULL; - - if (log_window == NULL) - return; - - if (!tpl_log_manager_search_finish (TPL_LOG_MANAGER (manager), result, - &hits, &error)) { - DEBUG ("%s. Aborting", error->message); - g_error_free (error); - return; - } - - for (l = hits; l; l = l->next) { - TplLogSearchHit *hit; - const gchar *account_name; - const gchar *account_icon; - gchar date_readable[255]; - gchar tmp[255]; - - hit = l->data; - - /* Protect against invalid data (corrupt or old log files. */ - if (hit->account == NULL || hit->target == NULL) { - continue; - } - - g_date_strftime (date_readable, sizeof (date_readable), - EMPATHY_DATE_FORMAT_DISPLAY_SHORT, hit->date); - - g_date_strftime (tmp, sizeof (tmp), - "%Y%m%d", hit->date); - - account_name = tp_account_get_display_name (hit->account); - account_icon = tp_account_get_icon_name (hit->account); - - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, - COL_FIND_ACCOUNT_ICON, account_icon, - COL_FIND_ACCOUNT_NAME, account_name, - COL_FIND_ACCOUNT, hit->account, - COL_FIND_CHAT_NAME, tpl_entity_get_alias (hit->target), - COL_FIND_TARGET, hit->target, - COL_FIND_DATE, tmp, - COL_FIND_DATE_READABLE, date_readable, - -1); - - /* FIXME: Update COL_FIND_CHAT_NAME */ - if (tpl_entity_get_entity_type (hit->target) == TPL_ENTITY_ROOM) { - } else { - } - } - - if (hits != NULL) { - tpl_log_manager_search_free (hits); - } + /* Contacts */ + log_window_events_setup (window); + log_window_who_setup (window); + log_window_what_setup (window); + log_window_when_setup (window); + + log_window_who_populate (window); + + if (account != NULL && chat_id != NULL) + select_account_once_ready (window, account, chat_id, is_chatroom); + + if (parent != NULL) + gtk_window_set_transient_for (GTK_WINDOW (window->window), + GTK_WINDOW (parent)); + + gtk_widget_show (window->window); + + return window->window; } static void -log_window_find_populate (EmpathyLogWindow *window, - const gchar *search_criteria) +log_window_destroy_cb (GtkWidget *widget, + EmpathyLogWindow *window) { - GtkTreeView *view; - GtkTreeModel *model; - GtkListStore *store; + if (window->source != 0) + g_source_remove (window->source); - view = GTK_TREE_VIEW (window->treeview_find); - model = gtk_tree_view_get_model (view); - store = GTK_LIST_STORE (model); + g_free (window->last_find); + _tpl_action_chain_free (window->chain); + g_object_unref (window->log_manager); + tp_clear_object (&window->selected_account); + g_free (window->selected_chat_id); - empathy_chat_view_clear (window->chatview_find); + g_free (window); +} - gtk_list_store_clear (store); +static gboolean +account_equal (TpAccount *a, + TpAccount *b) +{ + return g_str_equal (tp_proxy_get_object_path (a), + tp_proxy_get_object_path (b)); +} - if (EMP_STR_EMPTY (search_criteria)) { - /* Just clear the search. */ - return; - } +static gboolean +entity_equal (TplEntity *a, + TplEntity *b) +{ + return g_str_equal (tpl_entity_get_identifier (a), + tpl_entity_get_identifier (b)); +} - tpl_log_manager_search_async (window->log_manager, - search_criteria, TPL_EVENT_MASK_TEXT, - log_manager_searched_new_cb, (gpointer) store); +static gboolean +is_same_confroom (TplEvent *e1, + TplEvent *e2) +{ + TplEntity *sender1 = tpl_event_get_sender (e1); + TplEntity *receiver1 = tpl_event_get_receiver (e1); + TplEntity *sender2 = tpl_event_get_sender (e2); + TplEntity *receiver2 = tpl_event_get_receiver (e2); + TplEntity *room1, *room2; + + if (tpl_entity_get_entity_type (sender1) == TPL_ENTITY_ROOM) + room1 = sender1; + else if (tpl_entity_get_entity_type (receiver1) == TPL_ENTITY_ROOM) + room1 = receiver1; + else + return FALSE; + + if (tpl_entity_get_entity_type (sender2) == TPL_ENTITY_ROOM) + room2 = sender2; + else if (tpl_entity_get_entity_type (receiver2) == TPL_ENTITY_ROOM) + room2 = receiver2; + else + return FALSE; + + return g_str_equal (tpl_entity_get_identifier (room1), + tpl_entity_get_identifier (room2)); } -static void -log_window_find_setup (EmpathyLogWindow *window) -{ - GtkTreeView *view; - GtkTreeModel *model; - GtkTreeSelection *selection; - GtkTreeSortable *sortable; - GtkTreeViewColumn *column; - GtkListStore *store; - GtkCellRenderer *cell; - gint offset; - - view = GTK_TREE_VIEW (window->treeview_find); - selection = gtk_tree_view_get_selection (view); - - /* New store */ - store = gtk_list_store_new (COL_FIND_COUNT, - G_TYPE_STRING, /* account icon name */ - G_TYPE_STRING, /* account name */ - TP_TYPE_ACCOUNT, /* account */ - G_TYPE_STRING, /* chat name */ - TPL_TYPE_ENTITY, /* target */ - G_TYPE_STRING, /* date */ - G_TYPE_STRING); /* date_readable */ - - model = GTK_TREE_MODEL (store); - sortable = GTK_TREE_SORTABLE (store); - - gtk_tree_view_set_model (view, model); - - /* New column */ - column = gtk_tree_view_column_new (); - - cell = gtk_cell_renderer_pixbuf_new (); - gtk_tree_view_column_pack_start (column, cell, FALSE); - gtk_tree_view_column_add_attribute (column, cell, - "icon-name", - COL_FIND_ACCOUNT_ICON); - - cell = gtk_cell_renderer_text_new (); - gtk_tree_view_column_pack_start (column, cell, TRUE); - gtk_tree_view_column_add_attribute (column, cell, - "text", - COL_FIND_ACCOUNT_NAME); - - gtk_tree_view_column_set_title (column, _("Account")); - gtk_tree_view_append_column (view, column); - - gtk_tree_view_column_set_resizable (column, TRUE); - gtk_tree_view_column_set_clickable (column, TRUE); - - cell = gtk_cell_renderer_text_new (); - offset = gtk_tree_view_insert_column_with_attributes (view, -1, _("Conversation"), - cell, "text", COL_FIND_CHAT_NAME, - NULL); - - column = gtk_tree_view_get_column (view, offset - 1); - gtk_tree_view_column_set_sort_column_id (column, COL_FIND_CHAT_NAME); - gtk_tree_view_column_set_resizable (column, TRUE); - gtk_tree_view_column_set_clickable (column, TRUE); - - cell = gtk_cell_renderer_text_new (); - offset = gtk_tree_view_insert_column_with_attributes (view, -1, _("Date"), - cell, "text", COL_FIND_DATE_READABLE, - NULL); - - column = gtk_tree_view_get_column (view, offset - 1); - gtk_tree_view_column_set_sort_column_id (column, COL_FIND_DATE); - gtk_tree_view_column_set_resizable (column, TRUE); - gtk_tree_view_column_set_clickable (column, TRUE); - - /* Set up treeview properties */ - gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE); - gtk_tree_sortable_set_sort_column_id (sortable, - COL_FIND_DATE, - GTK_SORT_ASCENDING); - - /* Set up signals */ - g_signal_connect (selection, "changed", - G_CALLBACK (log_window_find_changed_cb), - window); - - g_object_unref (store); +static TplEntity * +event_get_target (TplEvent *event) +{ + TplEntity *sender = tpl_event_get_sender (event); + TplEntity *receiver = tpl_event_get_receiver (event); + + if (tpl_entity_get_entity_type (sender) == TPL_ENTITY_SELF) + return receiver; + + return sender; } -static void -start_find_search (EmpathyLogWindow *window) +static gboolean +model_is_parent (GtkTreeModel *model, + GtkTreeIter *iter, + TplEvent *event) { - const gchar *str; + TplEvent *stored_event; + TplEntity *target; + TpAccount *account; + gboolean found = FALSE; + GtkTreeIter parent; + + if (gtk_tree_model_iter_parent (model, &parent, iter)) + return FALSE; + + gtk_tree_model_get (model, iter, + COL_EVENTS_ACCOUNT, &account, + COL_EVENTS_TARGET, &target, + COL_EVENTS_EVENT, &stored_event, + -1); + + if (G_OBJECT_TYPE (event) == G_OBJECT_TYPE (stored_event) && + account_equal (account, tpl_event_get_account (event)) && + (entity_equal (target, event_get_target (event)) || + is_same_confroom (event, stored_event))) + { + GtkTreeIter child; + gint64 timestamp; + + gtk_tree_model_iter_nth_child (model, &child, iter, + gtk_tree_model_iter_n_children (model, iter) - 1); + + gtk_tree_model_get (model, &child, + COL_EVENTS_TS, ×tamp, + -1); + + if (ABS (tpl_event_get_timestamp (event) - timestamp) < MAX_GAP) + { + /* The gap is smaller than 30 min */ + found = TRUE; + } + } + + g_object_unref (stored_event); + g_object_unref (account); + g_object_unref (target); - str = gtk_entry_get_text (GTK_ENTRY (window->entry_find)); + return found; +} + +static const gchar * +get_contact_alias_for_message (EmpathyMessage *message) +{ + EmpathyContact *sender, *receiver; - /* Don't find the same crap again */ - if (window->last_find && !tp_strdiff (window->last_find, str)) { - return; - } + sender = empathy_message_get_sender (message); + receiver = empathy_message_get_receiver (message); - g_free (window->last_find); - window->last_find = g_strdup (str); + if (empathy_contact_is_user (sender)) + return empathy_contact_get_alias (receiver); - log_window_find_populate (window, str); + return empathy_contact_get_alias (sender); } static void -log_window_button_find_clicked_cb (GtkWidget *widget, - EmpathyLogWindow *window) +get_parent_iter_for_message (TplEvent *event, + EmpathyMessage *message, + GtkTreeIter *parent) { - start_find_search (window); + GtkTreeStore *store; + GtkTreeModel *model; + GtkTreeIter iter; + gboolean parent_found = FALSE; + gboolean next; + + store = log_window->store_events; + model = GTK_TREE_MODEL (store); + + for (next = gtk_tree_model_get_iter_first (model, &iter); + next; + next = gtk_tree_model_iter_next (model, &iter)) + { + if ((parent_found = model_is_parent (model, &iter, event))) + break; + } + + if (parent_found) + { + *parent = iter; + } + else + { + GDateTime *date; + gchar *body, *pretty_date; + + date = g_date_time_new_from_unix_utc ( + tpl_event_get_timestamp (event)); + + pretty_date = g_date_time_format (date, + C_("A date with the time", "%A, %e %B %Y %X")); + + body = g_markup_printf_escaped (_("Chat with %s"), + get_contact_alias_for_message (message)); + + gtk_tree_store_append (store, &iter, NULL); + gtk_tree_store_set (store, &iter, + COL_EVENTS_TS, tpl_event_get_timestamp (event), + COL_EVENTS_PRETTY_DATE, pretty_date, + COL_EVENTS_TEXT, body, + COL_EVENTS_ICON, "stock_text_justify", + COL_EVENTS_ACCOUNT, tpl_event_get_account (event), + COL_EVENTS_TARGET, event_get_target (event), + COL_EVENTS_EVENT, event, + -1); + + *parent = iter; + + g_free (body); + g_free (pretty_date); + g_date_time_unref (date); + } +} + +static const gchar * +get_icon_for_event (TplEvent *event) +{ + const gchar *icon = NULL; + +#ifdef HAVE_CALL_LOGS + if (TPL_IS_CALL_EVENT (event)) + { + TplCallEvent *call = TPL_CALL_EVENT (event); + TplCallEndReason reason = tpl_call_event_get_end_reason (call); + TplEntity *sender = tpl_event_get_sender (event); + TplEntity *receiver = tpl_event_get_receiver (event); + + if (reason == TPL_CALL_END_REASON_NO_ANSWER) + icon = EMPATHY_IMAGE_CALL_MISSED; + else if (tpl_entity_get_entity_type (sender) == TPL_ENTITY_SELF) + icon = EMPATHY_IMAGE_CALL_OUTGOING; + else if (tpl_entity_get_entity_type (receiver) == TPL_ENTITY_SELF) + icon = EMPATHY_IMAGE_CALL_INCOMING; + } +#endif + + return icon; } static void -log_window_entry_find_activate_cb (GtkWidget *entry, - EmpathyLogWindow *self) +log_window_append_chat_message (TplEvent *event, + EmpathyMessage *message) { - start_find_search (self); + GtkTreeStore *store = log_window->store_events; + GtkTreeIter iter, parent; + gchar *pretty_date, *body; + GDateTime *date; + + date = g_date_time_new_from_unix_utc ( + tpl_event_get_timestamp (event)); + + pretty_date = g_date_time_format (date, "%X"); + + get_parent_iter_for_message (event, message, &parent); + + if (tpl_text_event_get_message_type (TPL_TEXT_EVENT (event)) + == TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION) + { + /* Translators: this is an emote: '* Danielle waves' */ + body = g_markup_printf_escaped (_("<i>* %s %s</i>"), + tpl_entity_get_alias (tpl_event_get_sender (event)), + empathy_message_get_body (message)); + } + else + { + /* Translators: this is a message: 'Danielle: hello' + * The string in bold is the sender's name */ + body = g_markup_printf_escaped (_("<b>%s:</b> %s"), + tpl_entity_get_alias (tpl_event_get_sender (event)), + empathy_message_get_body (message)); + } + + gtk_tree_store_append (store, &iter, &parent); + gtk_tree_store_set (store, &iter, + COL_EVENTS_TS, tpl_event_get_timestamp (event), + COL_EVENTS_PRETTY_DATE, pretty_date, + COL_EVENTS_TEXT, body, + COL_EVENTS_ICON, get_icon_for_event (event), + COL_EVENTS_ACCOUNT, tpl_event_get_account (event), + COL_EVENTS_TARGET, event_get_target (event), + COL_EVENTS_EVENT, event, + -1); + + g_free (body); + g_free (pretty_date); + g_date_time_unref (date); } +#ifdef HAVE_CALL_LOGS static void -log_window_button_next_clicked_cb (GtkWidget *widget, - EmpathyLogWindow *window) -{ - if (window->last_find) { - gboolean can_do_previous; - gboolean can_do_next; - - empathy_chat_view_find_next (window->chatview_find, - window->last_find, - FALSE, - FALSE); - empathy_chat_view_find_abilities (window->chatview_find, - window->last_find, - FALSE, - &can_do_previous, - &can_do_next); - gtk_widget_set_sensitive (window->button_previous, can_do_previous); - gtk_widget_set_sensitive (window->button_next, can_do_next); - } +log_window_append_call (TplEvent *event, + EmpathyMessage *message) +{ + TplCallEvent *call = TPL_CALL_EVENT (event); + GtkTreeStore *store = log_window->store_events; + GtkTreeIter iter, child; + gchar *pretty_date, *duration, *finished; + GDateTime *started_date, *finished_date; + GTimeSpan span; + + started_date = g_date_time_new_from_unix_utc ( + tpl_event_get_timestamp (event)); + + pretty_date = g_date_time_format (started_date, + C_("A date with the time", "%A, %e %B %Y %X")); + + gtk_tree_store_append (store, &iter, NULL); + gtk_tree_store_set (store, &iter, + COL_EVENTS_TS, tpl_event_get_timestamp (event), + COL_EVENTS_PRETTY_DATE, pretty_date, + COL_EVENTS_TEXT, empathy_message_get_body (message), + COL_EVENTS_ICON, get_icon_for_event (event), + COL_EVENTS_ACCOUNT, tpl_event_get_account (event), + COL_EVENTS_TARGET, event_get_target (event), + COL_EVENTS_EVENT, event, + -1); + + if (tpl_call_event_get_end_reason (call) != TPL_CALL_END_REASON_NO_ANSWER) + { + gchar *body; + + span = tpl_call_event_get_duration (TPL_CALL_EVENT (event)); + if (span < 60) + duration = g_strdup_printf (_("%" G_GINT64_FORMAT " seconds"), span); + else + duration = g_strdup_printf (_("%" G_GINT64_FORMAT " minutes"), + span / 60); + + finished_date = g_date_time_add (started_date, -span); + finished = g_date_time_format (finished_date, "%X"); + g_date_time_unref (finished_date); + + body = g_strdup_printf (_("Call took %s, ended at %s"), + duration, finished); + + g_free (duration); + g_free (finished); + + gtk_tree_store_append (store, &child, &iter); + gtk_tree_store_set (store, &child, + COL_EVENTS_TS, tpl_event_get_timestamp (event), + COL_EVENTS_TEXT, body, + COL_EVENTS_ACCOUNT, tpl_event_get_account (event), + COL_EVENTS_TARGET, event_get_target (event), + COL_EVENTS_EVENT, event, + -1); + + g_free (body); + } + + g_free (pretty_date); + g_date_time_unref (started_date); } +#endif static void -log_window_button_previous_clicked_cb (GtkWidget *widget, - EmpathyLogWindow *window) -{ - if (window->last_find) { - gboolean can_do_previous; - gboolean can_do_next; - - empathy_chat_view_find_previous (window->chatview_find, - window->last_find, - FALSE, - FALSE); - empathy_chat_view_find_abilities (window->chatview_find, - window->last_find, - FALSE, - &can_do_previous, - &can_do_next); - gtk_widget_set_sensitive (window->button_previous, can_do_previous); - gtk_widget_set_sensitive (window->button_next, can_do_next); - } +log_window_append_message (TplEvent *event, + EmpathyMessage *message) +{ + if (TPL_IS_TEXT_EVENT (event)) + log_window_append_chat_message (event, message); +#ifdef HAVE_CALL_LOGS + else if (TPL_IS_CALL_EVENT (event)) + log_window_append_call (event, message); +#endif + else + DEBUG ("Message type not handled"); } static void -log_window_button_close_clicked_cb (GtkWidget *widget, - EmpathyLogWindow *window) +add_all_accounts_and_entities (GList **accounts, + GList **entities) +{ + GtkTreeView *view; + GtkTreeModel *model; + GtkTreeIter iter; + + view = GTK_TREE_VIEW (log_window->treeview_who); + model = gtk_tree_view_get_model (view); + + if (!gtk_tree_model_get_iter_first (model, &iter)) + return; + + do + { + TpAccount *account; + TplEntity *entity; + gint type; + + gtk_tree_model_get (model, &iter, + COL_WHO_ACCOUNT, &account, + COL_WHO_TARGET, &entity, + COL_WHO_TYPE, &type, + -1); + + if (type != COL_TYPE_NORMAL) + continue; + + if (accounts != NULL) + *accounts = g_list_append (*accounts, account); + + if (entities != NULL) + *entities = g_list_append (*entities, entity); + } + while (gtk_tree_model_iter_next (model, &iter)); +} + +static gboolean +log_window_get_selected (EmpathyLogWindow *window, + GList **accounts, + GList **entities, + GList **dates, + TplEventTypeMask *event_mask, + EventSubtype *subtype) { - gtk_widget_destroy (window->window); + GtkTreeView *view; + GtkTreeModel *model; + GtkTreeSelection *selection; + GtkTreeIter iter; + TplEventTypeMask ev = 0; + EventSubtype st = 0; + GList *paths, *l; + gint type; + + view = GTK_TREE_VIEW (window->treeview_who); + model = gtk_tree_view_get_model (view); + selection = gtk_tree_view_get_selection (view); + + paths = gtk_tree_selection_get_selected_rows (selection, NULL); + if (paths == NULL) + return FALSE; + + if (accounts != NULL) + *accounts = NULL; + if (entities != NULL) + *entities = NULL; + + for (l = paths; l != NULL; l = l->next) + { + GtkTreePath *path = l->data; + TpAccount *account; + TplEntity *entity; + + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_model_get (model, &iter, + COL_WHO_ACCOUNT, &account, + COL_WHO_TARGET, &entity, + COL_WHO_TYPE, &type, + -1); + + if (type == COL_TYPE_ANY) + { + if (accounts != NULL || entities != NULL) + add_all_accounts_and_entities (accounts, entities); + break; + } + + if (accounts != NULL) + *accounts = g_list_append (*accounts, g_object_ref (account)); + + if (entities != NULL) + *entities = g_list_append (*entities, g_object_ref (entity)); + + g_object_unref (account); + g_object_unref (entity); + } + g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free); + + view = GTK_TREE_VIEW (window->treeview_what); + model = gtk_tree_view_get_model (view); + selection = gtk_tree_view_get_selection (view); + + paths = gtk_tree_selection_get_selected_rows (selection, NULL); + for (l = paths; l != NULL; l = l->next) + { + GtkTreePath *path = l->data; + TplEventTypeMask mask; + EventSubtype submask; + + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_model_get (model, &iter, + COL_WHAT_TYPE, &mask, + COL_WHAT_SUBTYPE, &submask, + -1); + + ev |= mask; + st |= submask; + } + g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free); + + view = GTK_TREE_VIEW (window->treeview_when); + model = gtk_tree_view_get_model (view); + selection = gtk_tree_view_get_selection (view); + + if (dates != NULL) + { + *dates = NULL; + + paths = gtk_tree_selection_get_selected_rows (selection, NULL); + for (l = paths; l != NULL; l = l->next) + { + GtkTreePath *path = l->data; + GDate *date; + + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_model_get (model, &iter, + COL_WHEN_DATE, &date, + -1); + + *dates = g_list_append (*dates, date); + } + g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free); + } + + if (event_mask != NULL) + *event_mask = ev; + + if (subtype != NULL) + *subtype = st; + + return TRUE; } -/* - * Chats Code - */ +static gboolean +model_has_entity (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + TplLogSearchHit *hit = data; + TplEntity *e; + TpAccount *a; + gboolean ret = FALSE; + + gtk_tree_model_get (model, iter, + COL_WHO_TARGET, &e, + COL_WHO_ACCOUNT, &a, + -1); + + if (e != NULL && entity_equal (hit->target, e) && + a != NULL && account_equal (hit->account, a)) + { + ret = has_element = TRUE; + } + + tp_clear_object (&e); + tp_clear_object (&a); + + return ret; +} + +static gboolean +model_has_date (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + GDate *date = data; + GDate *d; + + gtk_tree_model_get (model, iter, + COL_WHEN_DATE, &d, + -1); + + if (!g_date_compare (date, d)) + { + has_element = TRUE; + return TRUE; + } + + return FALSE; +} static void -log_window_chats_changed_cb (GtkTreeSelection *selection, - EmpathyLogWindow *window) +get_events_for_date (TplActionChain *chain, gpointer user_data); + +static void +populate_events_from_search_hits (GList *accounts, + GList *targets, + GList *dates) { - gboolean selected; + TplEventTypeMask event_mask; + EventSubtype subtype; + GDate *anytime; + GList *l; + gboolean is_anytime = FALSE; - /* The calendar has to be sensitive only if there is something selected */ - selected = log_window_chats_get_selected (window, NULL, NULL); - gtk_widget_set_sensitive (window->calendar_chats, selected); + if (!log_window_get_selected (log_window, + NULL, NULL, NULL, &event_mask, &subtype)) + return; + + anytime = g_date_new_dmy (2, 1, -1); + if (g_list_find_custom (dates, anytime, (GCompareFunc) g_date_compare)) + is_anytime = TRUE; + + for (l = log_window->hits; l != NULL; l = l->next) + { + TplLogSearchHit *hit = l->data; + GList *acc, *targ; + gboolean found = FALSE; + + /* Protect against invalid data (corrupt or old log files). */ + if (hit->account == NULL || hit->target == NULL) + continue; + + for (acc = accounts, targ = targets; + acc != NULL && targ != NULL && !found; + acc = acc->next, targ = targ->next) + { + TpAccount *account = acc->data; + TplEntity *target = targ->data; + + if (account_equal (hit->account, account) && + entity_equal (hit->target, target)) + found = TRUE; + } + + if (!found) + continue; + + if (is_anytime || + g_list_find_custom (dates, hit->date, (GCompareFunc) g_date_compare) + != NULL) + { + Ctx *ctx; - /* Use last date by default */ - gtk_calendar_clear_marks (GTK_CALENDAR (window->calendar_chats)); + ctx = ctx_new (log_window, hit->account, hit->target, hit->date, + event_mask, subtype, log_window->count); + _tpl_action_chain_append (log_window->chain, + get_events_for_date, ctx); + } + } - log_window_chats_get_messages (window, NULL); + _tpl_action_chain_start (log_window->chain); + + g_date_free (anytime); +} + +static gchar * +format_date_for_display (GDate *date) +{ + gchar *text; + GDate *now = NULL; + gint days_elapsed; + + /* g_date_strftime sucks */ + + now = g_date_new (); + g_date_set_time_t (now, time (NULL)); + + days_elapsed = g_date_days_between (date, now); + + if (days_elapsed < 0) + { + text = NULL; + } + else if (days_elapsed == 0) + { + text = g_strdup (_("Today")); + } + else if (days_elapsed == 1) + { + text = g_strdup (_("Yesterday")); + } + else + { + GDateTime *dt; + + dt = g_date_time_new_utc (g_date_get_year (date), + g_date_get_month (date), g_date_get_day (date), + 0, 0, 0); + + if (days_elapsed <= 7) + text = g_date_time_format (dt, "%A"); + else + text = g_date_time_format (dt, + C_("A date such as '23 May 2010', " + "%e is the day, %B the month and %Y the year", + "%e %B %Y")); + + g_date_time_unref (dt); + } + + g_date_free (now); + + return text; } static void -log_manager_got_entities_cb (GObject *manager, - GAsyncResult *result, - gpointer user_data) -{ - GList *entities; - GList *l; - GtkTreeView *view; - GtkTreeModel *model; - GtkTreeSelection *selection; - GtkListStore *store; - GtkTreeIter iter; - GError *error = NULL; - gboolean select_account = FALSE; - TpAccount *account = user_data; - - if (log_window == NULL) - goto out; - - if (!tpl_log_manager_get_entities_finish (TPL_LOG_MANAGER (manager), - result, &entities, &error)) { - DEBUG ("%s. Aborting", error->message); - g_error_free (error); - goto out; - } - - view = GTK_TREE_VIEW (log_window->treeview_chats); - model = gtk_tree_view_get_model (view); - selection = gtk_tree_view_get_selection (view); - store = GTK_LIST_STORE (model); - - for (l = entities; l; l = l->next) { - TplEntity *entity; - - entity = TPL_ENTITY (l->data); - - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, - COL_CHAT_ICON, "empathy-available", /* FIXME */ - COL_CHAT_NAME, tpl_entity_get_alias (entity), - COL_CHAT_ACCOUNT, account, - COL_CHAT_TARGET, entity, - -1); - - if (log_window->selected_account != NULL && - !tp_strdiff (tp_proxy_get_object_path (account), - tp_proxy_get_object_path (log_window->selected_account))) - select_account = TRUE; - - /* FIXME: Update COL_CHAT_ICON/NAME */ - if (tpl_entity_get_entity_type (entity) == TPL_ENTITY_ROOM) { - } else { - } - } - g_list_free_full (entities, g_object_unref); - - /* Unblock signals */ - g_signal_handlers_unblock_by_func (selection, - log_window_chats_changed_cb, - log_window); - - /* We display the selected account if we populate the model with chats from - * this account. */ - if (select_account) - log_window_chats_set_selected (log_window); +populate_dates_from_search_hits (GList *accounts, + GList *targets) +{ + GList *l; + GtkTreeView *view; + GtkTreeModel *model; + GtkListStore *store; + GtkTreeSelection *selection; + GtkTreeIter iter; -out: - g_object_unref (account); + if (log_window == NULL) + return; + + view = GTK_TREE_VIEW (log_window->treeview_when); + model = gtk_tree_view_get_model (view); + store = GTK_LIST_STORE (model); + selection = gtk_tree_view_get_selection (view); + + for (l = log_window->hits; l != NULL; l = l->next) + { + TplLogSearchHit *hit = l->data; + GList *acc, *targ; + gboolean found = FALSE; + + /* Protect against invalid data (corrupt or old log files). */ + if (hit->account == NULL || hit->target == NULL) + continue; + + for (acc = accounts, targ = targets; + acc != NULL && targ != NULL && !found; + acc = acc->next, targ = targ->next) + { + TpAccount *account = acc->data; + TplEntity *target = targ->data; + + if (account_equal (hit->account, account) && + entity_equal (hit->target, target)) + found = TRUE; + } + + if (!found) + continue; + + /* Add the date if it's not already there */ + has_element = FALSE; + gtk_tree_model_foreach (model, model_has_date, hit->date); + if (!has_element) + { + gchar *text = format_date_for_display (hit->date); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + COL_WHEN_DATE, hit->date, + COL_WHEN_TEXT, text, + COL_WHEN_ICON, CALENDAR_ICON, + -1); + } + } + + if (gtk_tree_model_get_iter_first (model, &iter)) + { + gtk_list_store_prepend (store, &iter); + gtk_list_store_set (store, &iter, + COL_WHEN_DATE, g_date_new_dmy (1, 1, -1), + COL_WHEN_TEXT, "separator", + -1); + + gtk_list_store_prepend (store, &iter); + gtk_list_store_set (store, &iter, + COL_WHEN_DATE, g_date_new_dmy (2, 1, -1), + COL_WHEN_TEXT, _("Anytime"), + -1); + + if (gtk_tree_model_iter_nth_child (model, &iter, NULL, 2)) + gtk_tree_selection_select_iter (selection, &iter); + } } static void -log_window_chats_populate (EmpathyLogWindow *window) +populate_entities_from_search_hits (void) { - EmpathyAccountChooser *account_chooser; - TpAccount *account; + EmpathyAccountChooser *account_chooser; + TpAccount *account; + GtkTreeView *view; + GtkTreeModel *model; + GtkTreeIter iter; + GtkListStore *store; + GList *l; - GtkTreeView *view; - GtkTreeModel *model; - GtkTreeSelection *selection; - GtkListStore *store; + view = GTK_TREE_VIEW (log_window->treeview_who); + model = gtk_tree_view_get_model (view); + store = GTK_LIST_STORE (model); + + gtk_list_store_clear (store); + + account_chooser = EMPATHY_ACCOUNT_CHOOSER (log_window->account_chooser); + account = empathy_account_chooser_get_account (account_chooser); + + for (l = log_window->hits; l; l = l->next) + { + TplLogSearchHit *hit = l->data; + + /* Protect against invalid data (corrupt or old log files). */ + if (hit->account == NULL || hit->target == NULL) + continue; + + /* Filter based on the selected account */ + if (account != NULL && !account_equal (account, hit->account)) + continue; + + /* Add the entity if it's not already there */ + has_element = FALSE; + gtk_tree_model_foreach (model, model_has_entity, hit); + if (!has_element) + { + TplEntityType type = tpl_entity_get_entity_type (hit->target); + gboolean room = type == TPL_ENTITY_ROOM; + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + COL_WHO_TYPE, COL_TYPE_NORMAL, + COL_WHO_ICON, room ? EMPATHY_IMAGE_GROUP_MESSAGE + : EMPATHY_IMAGE_AVATAR_DEFAULT, + COL_WHO_NAME, tpl_entity_get_alias (hit->target), + COL_WHO_ACCOUNT, hit->account, + COL_WHO_TARGET, hit->target, + -1); + } + } + + if (gtk_tree_model_get_iter_first (model, &iter)) + { + gtk_list_store_prepend (store, &iter); + gtk_list_store_set (store, &iter, + COL_WHO_TYPE, COL_TYPE_SEPARATOR, + COL_WHO_NAME, "separator", + -1); + + gtk_list_store_prepend (store, &iter); + gtk_list_store_set (store, &iter, + COL_WHO_TYPE, COL_TYPE_ANY, + COL_WHO_NAME, _("Anyone"), + -1); + } + + /* FIXME: select old entity if still available */ +} - account_chooser = EMPATHY_ACCOUNT_CHOOSER (window->account_chooser_chats); - account = empathy_account_chooser_dup_account (account_chooser); +static void +log_manager_searched_new_cb (GObject *manager, + GAsyncResult *result, + gpointer user_data) +{ + GList *hits; + GtkTreeView *view; + GtkTreeSelection *selection; + GError *error = NULL; - view = GTK_TREE_VIEW (window->treeview_chats); - model = gtk_tree_view_get_model (view); - selection = gtk_tree_view_get_selection (view); - store = GTK_LIST_STORE (model); + if (log_window == NULL) + return; - if (account == NULL) { - gtk_list_store_clear (store); - return; - } + if (!tpl_log_manager_search_finish (TPL_LOG_MANAGER (manager), + result, &hits, &error)) + { + DEBUG ("%s. Aborting", error->message); + g_error_free (error); + return; + } - /* Block signals to stop the logs being retrieved prematurely */ - g_signal_handlers_block_by_func (selection, - log_window_chats_changed_cb, - window); + tp_clear_pointer (&log_window->hits, tpl_log_manager_search_free); + log_window->hits = hits; - gtk_list_store_clear (store); + populate_entities_from_search_hits (); - /* Pass the account reference to the callback */ - tpl_log_manager_get_entities_async (window->log_manager, account, - log_manager_got_entities_cb, account); + view = GTK_TREE_VIEW (log_window->treeview_when); + selection = gtk_tree_view_get_selection (view); + + g_signal_handlers_unblock_by_func (selection, + log_window_when_changed_cb, + log_window); } static void -log_window_chats_setup (EmpathyLogWindow *window) +log_window_find_populate (EmpathyLogWindow *window, + const gchar *search_criteria) { - GtkTreeView *view; - GtkTreeModel *model; - GtkTreeSelection *selection; - GtkTreeSortable *sortable; - GtkTreeViewColumn *column; - GtkListStore *store; - GtkCellRenderer *cell; + GtkTreeView *view; + GtkTreeModel *model; + GtkTreeSelection *selection; + GtkListStore *store; - view = GTK_TREE_VIEW (window->treeview_chats); - selection = gtk_tree_view_get_selection (view); + gtk_tree_store_clear (window->store_events); - /* new store */ - store = gtk_list_store_new (COL_CHAT_COUNT, - G_TYPE_STRING, /* icon */ - G_TYPE_STRING, /* name */ - TP_TYPE_ACCOUNT, /* account */ - TPL_TYPE_ENTITY); /* target */ + view = GTK_TREE_VIEW (window->treeview_who); + model = gtk_tree_view_get_model (view); + store = GTK_LIST_STORE (model); - model = GTK_TREE_MODEL (store); - sortable = GTK_TREE_SORTABLE (store); + gtk_list_store_clear (store); - gtk_tree_view_set_model (view, model); + view = GTK_TREE_VIEW (window->treeview_when); + model = gtk_tree_view_get_model (view); + store = GTK_LIST_STORE (model); + selection = gtk_tree_view_get_selection (view); - /* new column */ - column = gtk_tree_view_column_new (); + gtk_list_store_clear (store); - cell = gtk_cell_renderer_pixbuf_new (); - gtk_tree_view_column_pack_start (column, cell, FALSE); - gtk_tree_view_column_add_attribute (column, cell, - "icon-name", - COL_CHAT_ICON); + if (EMP_STR_EMPTY (search_criteria)) + { + tp_clear_pointer (&window->hits, tpl_log_manager_search_free); + log_window_who_populate (window); + return; + } - cell = gtk_cell_renderer_text_new (); - g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL); - gtk_tree_view_column_pack_start (column, cell, TRUE); - gtk_tree_view_column_add_attribute (column, cell, - "text", - COL_CHAT_NAME); + g_signal_handlers_block_by_func (selection, + log_window_when_changed_cb, + window); + + tpl_log_manager_search_async (window->log_manager, + search_criteria, TPL_EVENT_MASK_ANY, + log_manager_searched_new_cb, NULL); +} - gtk_tree_view_append_column (view, column); +static gboolean +start_find_search (EmpathyLogWindow *window) +{ + const gchar *str; - /* set up treeview properties */ - gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE); - gtk_tree_sortable_set_sort_column_id (sortable, - COL_CHAT_NAME, - GTK_SORT_ASCENDING); + str = gtk_entry_get_text (GTK_ENTRY (window->search_entry)); - /* set up signals */ - g_signal_connect (selection, "changed", - G_CALLBACK (log_window_chats_changed_cb), - window); + /* Don't find the same crap again */ + if (window->last_find && !tp_strdiff (window->last_find, str)) + return FALSE; - g_object_unref (store); + g_free (window->last_find); + window->last_find = g_strdup (str); + + log_window_find_populate (window, str); + + return FALSE; } static void -log_window_chats_accounts_changed_cb (GtkWidget *combobox, - EmpathyLogWindow *window) +log_window_search_entry_changed_cb (GtkWidget *entry, + EmpathyLogWindow *window) +{ + if (window->source != 0) + g_source_remove (window->source); + window->source = g_timeout_add (500, (GSourceFunc) start_find_search, + window); +} + +static void +log_window_search_entry_activate_cb (GtkWidget *entry, + EmpathyLogWindow *self) { - /* Clear all current messages shown in the textview */ - empathy_chat_view_clear (window->chatview_chats); + start_find_search (self); +} - log_window_chats_populate (window); +static void +log_window_search_entry_icon_pressed_cb (GtkEntry *entry, + GtkEntryIconPosition icon_pos, + GdkEvent *event, + gpointer user_data) +{ + if (icon_pos != GTK_ENTRY_ICON_SECONDARY) + return; - /* No chat is selected as we just changed the account */ - gtk_widget_set_sensitive (window->calendar_chats, FALSE); + gtk_entry_buffer_set_text (gtk_entry_get_buffer (entry), + "", -1); } static void -log_window_chats_set_selected (EmpathyLogWindow *window) +log_window_update_buttons_sensitivity (EmpathyLogWindow *window, + GtkTreeModel *model, + GtkTreeSelection *selection) { - GtkTreeView *view; - GtkTreeModel *model; - GtkTreeSelection *selection; - GtkTreeIter iter; - GtkTreePath *path; - gboolean ok; - - view = GTK_TREE_VIEW (window->treeview_chats); - model = gtk_tree_view_get_model (view); - selection = gtk_tree_view_get_selection (view); - - if (!gtk_tree_model_get_iter_first (model, &iter)) { - return; - } - - for (ok = TRUE; ok; ok = gtk_tree_model_iter_next (model, &iter)) { - TpAccount *this_account; - TplEntity *this_target; - const gchar *this_chat_id; - gboolean this_is_chatroom; - - gtk_tree_model_get (model, &iter, - COL_CHAT_ACCOUNT, &this_account, - COL_CHAT_TARGET, &this_target, - -1); - - this_chat_id = tpl_entity_get_identifier (this_target); - this_is_chatroom = tpl_entity_get_entity_type (this_target) == TPL_ENTITY_ROOM; - - if (this_account == window->selected_account && - !tp_strdiff (this_chat_id, window->selected_chat_id) && - this_is_chatroom == window->selected_is_chatroom) { - gtk_tree_selection_select_iter (selection, &iter); - path = gtk_tree_model_get_path (model, &iter); - gtk_tree_view_scroll_to_cell (view, path, NULL, TRUE, 0.5, 0.0); - gtk_tree_path_free (path); - g_object_unref (this_account); - g_object_unref (this_target); - break; - } - - g_object_unref (this_account); - g_object_unref (this_target); - } - - tp_clear_object (&window->selected_account); - tp_clear_pointer (&window->selected_chat_id, g_free); + EmpathyContact *contact; + EmpathyCapabilities capabilities; + TpAccount *account; + TplEntity *target; + GtkTreeIter iter; + GList *paths; + GtkTreePath *path; + gboolean profile, chat, call, video; + + profile = chat = call = video = FALSE; + + if (!gtk_tree_model_get_iter_first (model, &iter)) + goto out; + + if (gtk_tree_selection_count_selected_rows (selection) != 1) + goto out; + + if (gtk_tree_selection_iter_is_selected (selection, &iter)) + goto out; + + paths = gtk_tree_selection_get_selected_rows (selection, &model); + g_return_if_fail (paths != NULL); + + path = paths->data; + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_model_get (model, &iter, + COL_WHO_ACCOUNT, &account, + COL_WHO_TARGET, &target, + -1); + + g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free); + + contact = empathy_contact_from_tpl_contact (account, target); + + g_object_unref (account); + g_object_unref (target); + + capabilities = empathy_contact_get_capabilities (contact); + + profile = chat = TRUE; + call = capabilities & EMPATHY_CAPABILITIES_AUDIO; + video = capabilities & EMPATHY_CAPABILITIES_VIDEO; + + out: + gtk_widget_set_sensitive (window->button_profile, profile); + gtk_widget_set_sensitive (window->button_chat, chat); + gtk_widget_set_sensitive (window->button_call, call); + gtk_widget_set_sensitive (window->button_video, video); } -static gboolean -log_window_chats_get_selected (EmpathyLogWindow *window, - TpAccount **account, - TplEntity **target) +static void +log_window_who_changed_cb (GtkTreeSelection *selection, + EmpathyLogWindow *window) { - GtkTreeView *view; - GtkTreeModel *model; - GtkTreeSelection *selection; - GtkTreeIter iter; - TplEntity *targ; - TpAccount *acc = NULL; + GtkTreeView *view; + GtkTreeModel *model; + GtkTreeIter iter; - view = GTK_TREE_VIEW (window->treeview_chats); - model = gtk_tree_view_get_model (view); - selection = gtk_tree_view_get_selection (view); + DEBUG ("log_window_who_changed_cb"); - if (!gtk_tree_selection_get_selected (selection, NULL, &iter)) { - return FALSE; - } + view = gtk_tree_selection_get_tree_view (selection); + model = gtk_tree_view_get_model (view); - gtk_tree_model_get (model, &iter, - COL_CHAT_ACCOUNT, &acc, - COL_CHAT_TARGET, &targ, - -1); + if (gtk_tree_model_get_iter_first (model, &iter)) + { + /* If 'Anyone' is selected, everything else should be deselected */ + if (gtk_tree_selection_iter_is_selected (selection, &iter)) + { + g_signal_handlers_block_by_func (selection, + log_window_who_changed_cb, + window); - if (account != NULL) { - *account = g_object_ref (acc); - } + gtk_tree_selection_unselect_all (selection); + gtk_tree_selection_select_iter (selection, &iter); - if (target != NULL) { - *target = g_object_ref (targ); - } + g_signal_handlers_unblock_by_func (selection, + log_window_who_changed_cb, + window); + } + } - g_object_unref (acc); - g_object_unref (targ); + log_window_update_buttons_sensitivity (window, model, selection); - return TRUE; + /* The contact changed, so the dates need to be updated */ + log_window_chats_get_messages (window, TRUE); } static void -log_window_got_messages_for_date_cb (GObject *manager, +log_manager_got_entities_cb (GObject *manager, GAsyncResult *result, gpointer user_data) { - EmpathyLogWindow *window = user_data; - GList *events; - GList *l; - GError *error = NULL; + Ctx *ctx = user_data; + GList *entities; + GList *l; + GtkTreeView *view; + GtkTreeModel *model; + GtkTreeSelection *selection; + GtkListStore *store; + GtkTreeIter iter; + GError *error = NULL; + gboolean select_account = FALSE; if (log_window == NULL) - return; + goto out; - if (!tpl_log_manager_get_events_for_date_finish (TPL_LOG_MANAGER (manager), - result, &events, &error)) { - DEBUG ("Unable to retrieve messages for the selected date: %s. Aborting", - error->message); - empathy_chat_view_append_event (window->chatview_find, - "Unable to retrieve messages for the selected date"); + if (log_window->count != ctx->count) + goto out; + + if (!tpl_log_manager_get_entities_finish (TPL_LOG_MANAGER (manager), + result, &entities, &error)) + { + DEBUG ("%s. Aborting", error->message); g_error_free (error); - return; - } - - for (l = events; l; l = l->next) { - EmpathyMessage *message = empathy_message_from_tpl_log_event (l->data); - g_object_unref (l->data); - empathy_chat_view_append_message (window->chatview_chats, - message); - g_object_unref (message); - } - g_list_free (events); + goto out; + } + + view = GTK_TREE_VIEW (ctx->window->treeview_who); + model = gtk_tree_view_get_model (view); + selection = gtk_tree_view_get_selection (view); + store = GTK_LIST_STORE (model); + + /* Block signals to stop the logs being retrieved prematurely */ + g_signal_handlers_block_by_func (selection, + log_window_who_changed_cb, ctx->window); + + for (l = entities; l; l = l->next) + { + TplEntity *entity = TPL_ENTITY (l->data); + TplEntityType type = tpl_entity_get_entity_type (entity); + gboolean room = type == TPL_ENTITY_ROOM; + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + COL_WHO_TYPE, COL_TYPE_NORMAL, + COL_WHO_ICON, room ? EMPATHY_IMAGE_GROUP_MESSAGE + : EMPATHY_IMAGE_AVATAR_DEFAULT, + COL_WHO_NAME, tpl_entity_get_alias (entity), + COL_WHO_ACCOUNT, ctx->account, + COL_WHO_TARGET, entity, + -1); + + if (ctx->window->selected_account != NULL && + !tp_strdiff (tp_proxy_get_object_path (ctx->account), + tp_proxy_get_object_path (ctx->window->selected_account))) + select_account = TRUE; + } + g_list_free_full (entities, g_object_unref); + + if (gtk_tree_model_get_iter_first (model, &iter)) + { + gint type; + + gtk_tree_model_get (model, &iter, + COL_WHO_TYPE, &type, + -1); + + if (type != COL_TYPE_ANY) + { + gtk_list_store_prepend (store, &iter); + gtk_list_store_set (store, &iter, + COL_WHO_TYPE, COL_TYPE_SEPARATOR, + COL_WHO_NAME, "separator", + -1); + + gtk_list_store_prepend (store, &iter); + gtk_list_store_set (store, &iter, + COL_WHO_TYPE, COL_TYPE_ANY, + COL_WHO_NAME, _("Anyone"), + -1); + } + } + + /* Unblock signals */ + g_signal_handlers_unblock_by_func (selection, + log_window_who_changed_cb, + ctx->window); + + /* We display the selected account if we populate the model with chats from + * this account. */ + if (select_account) + log_window_chats_set_selected (ctx->window); - /* Turn back on scrolling */ - empathy_chat_view_scroll (window->chatview_find, TRUE); +out: + _tpl_action_chain_continue (log_window->chain); + ctx_free (ctx); +} + +static void +get_entities_for_account (TplActionChain *chain, gpointer user_data) +{ + Ctx *ctx = user_data; - /* Give the search entry main focus */ - gtk_widget_grab_focus (window->entry_chats); + tpl_log_manager_get_entities_async (ctx->window->log_manager, ctx->account, + log_manager_got_entities_cb, ctx); } +static void +select_first_entity (TplActionChain *chain, gpointer user_data) +{ + GtkTreeView *view; + GtkTreeModel *model; + GtkTreeSelection *selection; + GtkTreeIter iter; + + view = GTK_TREE_VIEW (log_window->treeview_who); + model = gtk_tree_view_get_model (view); + selection = gtk_tree_view_get_selection (view); + + if (gtk_tree_model_get_iter_first (model, &iter)) + gtk_tree_selection_select_iter (selection, &iter); + + _tpl_action_chain_continue (log_window->chain); +} static void -log_window_get_messages_for_date (EmpathyLogWindow *window, - GDate *date) +log_window_who_populate (EmpathyLogWindow *window) { + EmpathyAccountChooser *account_chooser; TpAccount *account; - TplEntity *target; + gboolean all_accounts; + GtkTreeView *view; + GtkTreeModel *model; + GtkTreeSelection *selection; + GtkListStore *store; + Ctx *ctx; + + if (window->hits != NULL) + { + populate_entities_from_search_hits (); + return; + } - if (!log_window_chats_get_selected (window, &account, &target)) { + account_chooser = EMPATHY_ACCOUNT_CHOOSER (window->account_chooser); + account = empathy_account_chooser_dup_account (account_chooser); + all_accounts = empathy_account_chooser_has_all_selected (account_chooser); + + view = GTK_TREE_VIEW (window->treeview_who); + model = gtk_tree_view_get_model (view); + selection = gtk_tree_view_get_selection (view); + store = GTK_LIST_STORE (model); + + /* Block signals to stop the logs being retrieved prematurely */ + g_signal_handlers_block_by_func (selection, + log_window_who_changed_cb, + window); + + gtk_list_store_clear (store); + + /* Unblock signals */ + g_signal_handlers_unblock_by_func (selection, + log_window_who_changed_cb, + window); + + _tpl_action_chain_clear (window->chain); + window->count++; + + if (!all_accounts && account == NULL) + { return; - } + } + else if (!all_accounts) + { + ctx = ctx_new (window, account, NULL, NULL, 0, 0, window->count); + _tpl_action_chain_append (window->chain, get_entities_for_account, ctx); + } + else + { + TpAccountManager *manager; + GList *accounts, *l; + + manager = empathy_account_chooser_get_account_manager (account_chooser); + accounts = tp_account_manager_get_valid_accounts (manager); + + for (l = accounts; l != NULL; l = l->next) + { + account = l->data; + + ctx = ctx_new (window, account, NULL, NULL, 0, 0, window->count); + _tpl_action_chain_append (window->chain, + get_entities_for_account, ctx); + } + + g_list_free (accounts); + } + _tpl_action_chain_append (window->chain, select_first_entity, NULL); + _tpl_action_chain_start (window->chain); +} - /* Clear all current messages shown in the textview */ - empathy_chat_view_clear (window->chatview_chats); +static gint +sort_by_name (GtkTreeModel *model, + GtkTreeIter *a, + GtkTreeIter *b, + gpointer user_data) +{ + gchar *name1, *name2; + gint type1, type2; + gint ret; + + gtk_tree_model_get (model, a, + COL_WHO_TYPE, &type1, + COL_WHO_NAME, &name1, + -1); + + gtk_tree_model_get (model, b, + COL_WHO_TYPE, &type2, + COL_WHO_NAME, &name2, + -1); + + if (type1 == COL_TYPE_ANY) + ret = -1; + else if (type2 == COL_TYPE_ANY) + ret = 1; + else if (type1 == COL_TYPE_SEPARATOR) + ret = -1; + else if (type2 == COL_TYPE_SEPARATOR) + ret = 1; + else + ret = g_strcmp0 (name1, name2); + + g_free (name1); + g_free (name2); + + return ret; +} - /* Turn off scrolling temporarily */ - empathy_chat_view_scroll (window->chatview_find, FALSE); +static gboolean +who_row_is_separator (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + gint type; - /* Get events */ - tpl_log_manager_get_events_for_date_async (window->log_manager, - account, target, TPL_EVENT_MASK_TEXT, - date, - log_window_got_messages_for_date_cb, - (gpointer) window); + gtk_tree_model_get (model, iter, + COL_WHO_TYPE, &type, + -1); - g_object_unref (account); - g_object_unref (target); + return (type == COL_TYPE_SEPARATOR); } static void -log_manager_got_dates_cb (GObject *manager, - GAsyncResult *result, - gpointer user_data) +log_window_events_setup (EmpathyLogWindow *window) { - EmpathyLogWindow *window = user_data; - GList *dates; - GList *l; - guint year_selected; - guint month_selected; - gboolean day_selected = FALSE; - GDate *date = NULL; - GError *error = NULL; + GtkTreeView *view; + GtkTreeModel *model; + GtkTreeSelection *selection; + GtkTreeSortable *sortable; + GtkTreeViewColumn *column; + GtkTreeStore *store; + GtkCellRenderer *cell; + + view = GTK_TREE_VIEW (window->treeview_events); + selection = gtk_tree_view_get_selection (view); + + /* new store */ + window->store_events = store = gtk_tree_store_new (COL_EVENTS_COUNT, + G_TYPE_INT, /* type */ + G_TYPE_INT64, /* timestamp */ + G_TYPE_STRING, /* stringified date */ + G_TYPE_STRING, /* icon */ + G_TYPE_STRING, /* name */ + TP_TYPE_ACCOUNT, /* account */ + TPL_TYPE_ENTITY, /* target */ + TPL_TYPE_EVENT); /* event */ + + model = GTK_TREE_MODEL (store); + sortable = GTK_TREE_SORTABLE (store); + + gtk_tree_view_set_model (view, model); + + /* new column */ + column = gtk_tree_view_column_new (); + + cell = gtk_cell_renderer_pixbuf_new (); + gtk_tree_view_column_pack_start (column, cell, FALSE); + gtk_tree_view_column_add_attribute (column, cell, + "icon-name", COL_EVENTS_ICON); + + cell = gtk_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (column, cell, TRUE); + gtk_tree_view_column_add_attribute (column, cell, + "markup", COL_EVENTS_TEXT); + + cell = gtk_cell_renderer_text_new (); + g_object_set (cell, "xalign", 1.0, NULL); + gtk_tree_view_column_pack_end (column, cell, FALSE); + gtk_tree_view_column_add_attribute (column, cell, + "text", COL_EVENTS_PRETTY_DATE); + + gtk_tree_view_append_column (view, column); + + /* set up treeview properties */ + gtk_tree_selection_set_mode (selection, GTK_SELECTION_NONE); + gtk_tree_view_set_headers_visible (view, FALSE); + + gtk_tree_sortable_set_sort_column_id (sortable, + COL_EVENTS_TS, + GTK_SORT_ASCENDING); + + gtk_tree_view_set_enable_search (view, FALSE); + + g_object_unref (store); +} - if (log_window == NULL) - return; +static void +log_window_who_setup (EmpathyLogWindow *window) +{ + GtkTreeView *view; + GtkTreeModel *model; + GtkTreeSelection *selection; + GtkTreeSortable *sortable; + GtkTreeViewColumn *column; + GtkListStore *store; + GtkCellRenderer *cell; + + view = GTK_TREE_VIEW (window->treeview_who); + selection = gtk_tree_view_get_selection (view); + + /* new store */ + store = gtk_list_store_new (COL_WHO_COUNT, + G_TYPE_INT, /* type */ + G_TYPE_STRING, /* icon */ + G_TYPE_STRING, /* name */ + TP_TYPE_ACCOUNT, /* account */ + TPL_TYPE_ENTITY); /* target */ + + model = GTK_TREE_MODEL (store); + sortable = GTK_TREE_SORTABLE (store); + + gtk_tree_view_set_model (view, model); + + /* new column */ + column = gtk_tree_view_column_new (); + gtk_tree_view_column_set_title (column, _("Who")); + + cell = gtk_cell_renderer_pixbuf_new (); + gtk_tree_view_column_pack_start (column, cell, FALSE); + gtk_tree_view_column_add_attribute (column, cell, + "icon-name", + COL_WHO_ICON); + + cell = gtk_cell_renderer_text_new (); + g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL); + gtk_tree_view_column_pack_start (column, cell, TRUE); + gtk_tree_view_column_add_attribute (column, cell, + "text", + COL_WHO_NAME); + + gtk_tree_view_append_column (view, column); + + /* set up treeview properties */ + gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE); + gtk_tree_view_set_row_separator_func (view, who_row_is_separator, + NULL, NULL); + + gtk_tree_sortable_set_sort_column_id (sortable, + COL_WHO_NAME, + GTK_SORT_ASCENDING); + gtk_tree_sortable_set_sort_func (sortable, + COL_WHO_NAME, sort_by_name, + NULL, NULL); + + gtk_tree_view_set_search_column (view, COL_WHO_NAME); + + /* set up signals */ + g_signal_connect (selection, "changed", + G_CALLBACK (log_window_who_changed_cb), window); + + g_object_unref (store); +} - if (!tpl_log_manager_get_dates_finish (TPL_LOG_MANAGER (manager), - result, &dates, &error)) { - DEBUG ("Unable to retrieve messages' dates: %s. Aborting", - error->message); - empathy_chat_view_append_event (window->chatview_find, - "Unable to retrieve messages' dates"); - return; - } +static void +log_window_chats_accounts_changed_cb (GtkWidget *combobox, + EmpathyLogWindow *window) +{ + /* Clear all current messages shown in the textview */ + gtk_tree_store_clear (window->store_events); - for (l = dates; l; l = l->next) { - GDate *d = l->data; + log_window_who_populate (window); +} - gtk_calendar_get_date (GTK_CALENDAR (window->calendar_chats), - &year_selected, - &month_selected, - NULL); +static void +log_window_chats_set_selected (EmpathyLogWindow *window) +{ + GtkTreeView *view; + GtkTreeModel *model; + GtkTreeSelection *selection; + GtkTreeIter iter; + GtkTreePath *path; + gboolean next; + + view = GTK_TREE_VIEW (window->treeview_who); + model = gtk_tree_view_get_model (view); + selection = gtk_tree_view_get_selection (view); + + for (next = gtk_tree_model_get_iter_first (model, &iter); + next; + next = gtk_tree_model_iter_next (model, &iter)) + { + TpAccount *this_account; + TplEntity *this_target; + const gchar *this_chat_id; + gboolean this_is_chatroom; + gint this_type; + + gtk_tree_model_get (model, &iter, + COL_WHO_TYPE, &this_type, + COL_WHO_ACCOUNT, &this_account, + COL_WHO_TARGET, &this_target, + -1); + + if (this_type != COL_TYPE_NORMAL) + continue; + + this_chat_id = tpl_entity_get_identifier (this_target); + this_is_chatroom = tpl_entity_get_entity_type (this_target) + == TPL_ENTITY_ROOM; + + if (this_account == window->selected_account && + !tp_strdiff (this_chat_id, window->selected_chat_id) && + this_is_chatroom == window->selected_is_chatroom) + { + gtk_tree_selection_select_iter (selection, &iter); + path = gtk_tree_model_get_path (model, &iter); + gtk_tree_view_scroll_to_cell (view, path, NULL, TRUE, 0.5, 0.0); + gtk_tree_path_free (path); + g_object_unref (this_account); + g_object_unref (this_target); + break; + } + + g_object_unref (this_account); + g_object_unref (this_target); + } + + tp_clear_object (&window->selected_account); + tp_clear_pointer (&window->selected_chat_id, g_free); +} - month_selected++; +static gint +sort_by_date (GtkTreeModel *model, + GtkTreeIter *a, + GtkTreeIter *b, + gpointer user_data) +{ + GDate *date1, *date2; - if (!l->next) { - date = d; - } + gtk_tree_model_get (model, a, + COL_WHEN_DATE, &date1, + -1); - if (g_date_get_year (d) != year_selected || - g_date_get_month (d) != month_selected) { - continue; - } + gtk_tree_model_get (model, b, + COL_WHEN_DATE, &date2, + -1); + + return g_date_compare (date1, date2); +} - DEBUG ("Marking date: %04u-%02u-%02u", g_date_get_year (d), - g_date_get_month (d), g_date_get_day (d)); +static gboolean +when_row_is_separator (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + gchar *when; + gboolean ret; - gtk_calendar_mark_day (GTK_CALENDAR (window->calendar_chats), - g_date_get_day (d)); + gtk_tree_model_get (model, iter, + COL_WHEN_TEXT, &when, + -1); - if (l->next) { - continue; - } + ret = g_str_equal (when, "separator"); + g_free (when); + return ret; +} + +static void +log_window_when_changed_cb (GtkTreeSelection *selection, + EmpathyLogWindow *window) +{ + GtkTreeView *view; + GtkTreeModel *model; + GtkTreeIter iter; + + DEBUG ("log_window_when_changed_cb"); + + view = gtk_tree_selection_get_tree_view (selection); + model = gtk_tree_view_get_model (view); + + /* If 'Anytime' is selected, everything else should be deselected */ + if (gtk_tree_model_get_iter_first (model, &iter)) + { + if (gtk_tree_selection_iter_is_selected (selection, &iter)) + { + g_signal_handlers_block_by_func (selection, + log_window_when_changed_cb, + window); + + gtk_tree_selection_unselect_all (selection); + gtk_tree_selection_select_iter (selection, &iter); + + g_signal_handlers_unblock_by_func (selection, + log_window_when_changed_cb, + window); + } + } + + log_window_chats_get_messages (window, FALSE); +} + +static void +log_window_when_setup (EmpathyLogWindow *window) +{ + GtkTreeView *view; + GtkTreeModel *model; + GtkTreeSelection *selection; + GtkTreeSortable *sortable; + GtkTreeViewColumn *column; + GtkListStore *store; + GtkCellRenderer *cell; + + view = GTK_TREE_VIEW (window->treeview_when); + selection = gtk_tree_view_get_selection (view); + + /* new store */ + store = gtk_list_store_new (COL_WHEN_COUNT, + G_TYPE_DATE, /* date */ + G_TYPE_STRING, /* stringified date */ + G_TYPE_STRING); /* icon */ + + model = GTK_TREE_MODEL (store); + sortable = GTK_TREE_SORTABLE (store); + + gtk_tree_view_set_model (view, model); + + /* new column */ + column = gtk_tree_view_column_new (); + gtk_tree_view_column_set_title (column, _("When")); + + cell = gtk_cell_renderer_pixbuf_new (); + gtk_tree_view_column_pack_start (column, cell, FALSE); + gtk_tree_view_column_add_attribute (column, cell, + "icon-name", COL_WHEN_ICON); + + cell = gtk_cell_renderer_text_new (); + g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL); + gtk_tree_view_column_pack_start (column, cell, TRUE); + gtk_tree_view_column_add_attribute (column, cell, + "text", + COL_WHEN_TEXT); + + gtk_tree_view_append_column (view, column); + + /* set up treeview properties */ + gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE); + gtk_tree_view_set_row_separator_func (view, when_row_is_separator, + NULL, NULL); + gtk_tree_sortable_set_sort_column_id (sortable, + COL_WHEN_DATE, + GTK_SORT_DESCENDING); + gtk_tree_sortable_set_sort_func (sortable, + COL_WHEN_DATE, sort_by_date, + NULL, NULL); + + gtk_tree_view_set_search_column (view, COL_WHEN_TEXT); + + /* set up signals */ + g_signal_connect (selection, "changed", + G_CALLBACK (log_window_when_changed_cb), + window); + + g_object_unref (store); +} + +static gboolean +what_row_is_separator (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + gint type; - day_selected = TRUE; + gtk_tree_model_get (model, iter, + COL_WHAT_TYPE, &type, + -1); + + return (type == WHAT_TYPE_SEPARATOR); +} - gtk_calendar_select_day (GTK_CALENDAR (window->calendar_chats), - g_date_get_day (d)); - } +static void +log_window_what_changed_cb (GtkTreeSelection *selection, + EmpathyLogWindow *window) +{ + GtkTreeView *view; + GtkTreeModel *model; + GtkTreeIter iter; + + DEBUG ("log_window_what_changed_cb"); + + view = gtk_tree_selection_get_tree_view (selection); + model = gtk_tree_view_get_model (view); + + /* If 'Anything' is selected, everything else should be deselected */ + if (gtk_tree_model_get_iter_first (model, &iter)) + { + if (gtk_tree_selection_iter_is_selected (selection, &iter)) + { + g_signal_handlers_block_by_func (selection, + log_window_what_changed_cb, + window); + + gtk_tree_selection_unselect_all (selection); + gtk_tree_selection_select_iter (selection, &iter); + + g_signal_handlers_unblock_by_func (selection, + log_window_what_changed_cb, + window); + } + } + + /* The dates need to be updated if we're not searching */ + log_window_chats_get_messages (window, window->hits == NULL); +} - if (!day_selected) { - /* Unselect the day in the calendar */ - gtk_calendar_select_day (GTK_CALENDAR (window->calendar_chats), 0); - } +static gboolean +log_window_what_collapse_row_cb (GtkTreeView *tree_view, + GtkTreeIter *iter, + GtkTreePath *path, + gpointer user_data) +{ + /* Reject collapsing */ + return TRUE; +} + +struct event +{ + gint type; + EventSubtype subtype; + const gchar *icon; + const gchar *text; +}; - g_signal_handlers_unblock_by_func (window->calendar_chats, - log_window_calendar_chats_day_selected_cb, +static void +log_window_what_setup (EmpathyLogWindow *window) +{ + GtkTreeView *view; + GtkTreeModel *model; + GtkTreeSelection *selection; + GtkTreeViewColumn *column; + GtkTreeIter iter; + GtkTreeStore *store; + GtkCellRenderer *cell; + guint i; + struct event events [] = { + { TPL_EVENT_MASK_ANY, 0, NULL, _("Anything") }, + { WHAT_TYPE_SEPARATOR, 0, NULL, "separator" }, + { TPL_EVENT_MASK_TEXT, 0, "stock_text_justify", _("Text chats") }, +#ifdef HAVE_CALL_LOGS + { TPL_EVENT_MASK_CALL, EVENT_CALL_ALL, "call-start", _("Calls") }, +#endif + }; +#ifdef HAVE_CALL_LOGS + struct event call_events [] = { + { TPL_EVENT_MASK_CALL, EVENT_CALL_INCOMING, "call-start", _("Incoming calls") }, + { TPL_EVENT_MASK_CALL, EVENT_CALL_OUTGOING, "call-start", _("Outgoing calls") }, + { TPL_EVENT_MASK_CALL, EVENT_CALL_MISSED, "call-stop", _("Missed calls") } + }; + GtkTreeIter parent; +#endif + + view = GTK_TREE_VIEW (window->treeview_what); + selection = gtk_tree_view_get_selection (view); + + /* new store */ + store = gtk_tree_store_new (COL_WHAT_COUNT, + G_TYPE_INT, /* history type */ + G_TYPE_INT, /* history subtype */ + G_TYPE_STRING, /* stringified history type */ + G_TYPE_STRING); /* icon */ + + model = GTK_TREE_MODEL (store); + + gtk_tree_view_set_model (view, model); + + /* new column */ + column = gtk_tree_view_column_new (); + gtk_tree_view_column_set_title (column, _("What")); + + cell = gtk_cell_renderer_pixbuf_new (); + gtk_tree_view_column_pack_start (column, cell, FALSE); + gtk_tree_view_column_add_attribute (column, cell, + "icon-name", COL_WHAT_ICON); + + cell = gtk_cell_renderer_text_new (); + g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL); + gtk_tree_view_column_pack_start (column, cell, TRUE); + gtk_tree_view_column_add_attribute (column, cell, + "text", COL_WHAT_TEXT); + + gtk_tree_view_append_column (view, column); + gtk_tree_view_set_search_column (view, COL_WHAT_TEXT); + + /* set up treeview properties */ + gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE); + gtk_tree_view_set_show_expanders (view, FALSE); + gtk_tree_view_set_level_indentation (view, 12); + gtk_tree_view_expand_all (view); + gtk_tree_view_set_row_separator_func (view, what_row_is_separator, + NULL, NULL); + + /* populate */ + for (i = 0; i < G_N_ELEMENTS (events); i++) + { + gtk_tree_store_append (store, &iter, NULL); + gtk_tree_store_set (store, &iter, + COL_WHAT_TYPE, events[i].type, + COL_WHAT_SUBTYPE, events[i].subtype, + COL_WHAT_TEXT, events[i].text, + COL_WHAT_ICON, events[i].icon, + -1); + } + +#ifdef HAVE_CALL_LOGS + gtk_tree_model_iter_nth_child (model, &parent, NULL, 3); + for (i = 0; i < G_N_ELEMENTS (call_events); i++) + { + gtk_tree_store_append (store, &iter, &parent); + gtk_tree_store_set (store, &iter, + COL_WHAT_TYPE, call_events[i].type, + COL_WHAT_SUBTYPE, call_events[i].subtype, + COL_WHAT_TEXT, call_events[i].text, + COL_WHAT_ICON, call_events[i].icon, + -1); + } +#endif + + gtk_tree_view_expand_all (view); + + /* select 'Anything' */ + if (gtk_tree_model_get_iter_first (model, &iter)) + gtk_tree_selection_select_iter (selection, &iter); + + /* set up signals */ + g_signal_connect (view, "test-collapse-row", + G_CALLBACK (log_window_what_collapse_row_cb), + NULL); + g_signal_connect (selection, "changed", + G_CALLBACK (log_window_what_changed_cb), window); - if (date != NULL) { - /* Show messages of the most recent date */ - log_window_get_messages_for_date (window, date); - } + g_object_unref (store); +} - g_list_foreach (dates, (GFunc) g_free, NULL); - g_list_free (dates); +static void +start_spinner (void) +{ + gtk_spinner_start (GTK_SPINNER (log_window->spinner)); + gtk_notebook_set_current_page (GTK_NOTEBOOK (log_window->notebook), + PAGE_EMPTY); } +static gboolean +show_spinner (gpointer data) +{ + gboolean active; + + if (log_window == NULL) + return FALSE; + + g_object_get (log_window->spinner, "active", &active, NULL); + + if (active) + gtk_notebook_set_current_page (GTK_NOTEBOOK (log_window->notebook), + PAGE_SPINNER); + + return FALSE; +} static void -log_window_chats_get_messages (EmpathyLogWindow *window, - GDate *date) +show_events (TplActionChain *chain, + gpointer user_data) { - TpAccount *account; - TplEntity *target; - guint year_selected; - guint month_selected; - guint day; + gtk_spinner_stop (GTK_SPINNER (log_window->spinner)); + gtk_notebook_set_current_page (GTK_NOTEBOOK (log_window->notebook), + PAGE_EVENTS); + _tpl_action_chain_continue (chain); +} - if (!log_window_chats_get_selected (window, &account, &target)) { - return; - } +static void +log_window_got_messages_for_date_cb (GObject *manager, + GAsyncResult *result, + gpointer user_data) +{ + Ctx *ctx = user_data; + GtkTreeView *view; + GtkTreeModel *model; + GtkTreeIter iter; + GList *events; + GList *l; + GError *error = NULL; + gint n; - g_signal_handlers_block_by_func (window->calendar_chats, - log_window_calendar_chats_day_selected_cb, - window); + if (log_window == NULL) + { + ctx_free (ctx); + return; + } - /* Either use the supplied date or get the last */ - if (date == NULL) { - /* Get a list of dates and show them on the calendar */ - tpl_log_manager_get_dates_async (window->log_manager, - account, target, TPL_EVENT_MASK_TEXT, - log_manager_got_dates_cb, (gpointer) window); - /* signal unblocked at the end of the CB flow */ - } else { - day = g_date_get_day (date); - gtk_calendar_get_date (GTK_CALENDAR (window->calendar_chats), - &year_selected, - &month_selected, - NULL); + if (log_window->count != ctx->count) + goto out; - month_selected++; + if (!tpl_log_manager_get_events_for_date_finish (TPL_LOG_MANAGER (manager), + result, &events, &error)) + { + DEBUG ("Unable to retrieve messages for the selected date: %s. Aborting", + error->message); + g_error_free (error); + goto out; + } + + for (l = events; l; l = l->next) + { + TplEvent *event = l->data; + gboolean append = TRUE; + +#ifdef HAVE_CALL_LOGS + if (TPL_IS_CALL_EVENT (l->data) + && ctx->event_mask & TPL_EVENT_MASK_CALL + && ctx->event_mask != TPL_EVENT_MASK_ANY) + { + TplCallEvent *call = l->data; + + append = FALSE; + + if (ctx->subtype & EVENT_CALL_ALL) + { + append = TRUE; + } + else + { + TplCallEndReason reason = tpl_call_event_get_end_reason (call); + TplEntity *sender = tpl_event_get_sender (event); + TplEntity *receiver = tpl_event_get_receiver (event); + + if (reason == TPL_CALL_END_REASON_NO_ANSWER) + { + if (ctx->subtype & EVENT_CALL_MISSED) + append = TRUE; + } + else if (ctx->subtype & EVENT_CALL_OUTGOING + && tpl_entity_get_entity_type (sender) == TPL_ENTITY_SELF) + { + append = TRUE; + } + else if (ctx->subtype & EVENT_CALL_INCOMING + && tpl_entity_get_entity_type (receiver) == TPL_ENTITY_SELF) + { + append = TRUE; + } + } + } +#endif + + if (append) + { + EmpathyMessage *msg = empathy_message_from_tpl_log_event (event); + log_window_append_message (event, msg); + tp_clear_object (&msg); + } + + g_object_unref (event); + } + g_list_free (events); - if (g_date_get_year (date) != year_selected && - g_date_get_month (date) != month_selected) { - day = 0; - } + view = GTK_TREE_VIEW (log_window->treeview_events); + model = gtk_tree_view_get_model (view); + n = gtk_tree_model_iter_n_children (model, NULL) - 1; - gtk_calendar_select_day (GTK_CALENDAR (window->calendar_chats), day); + if (n >= 0 && gtk_tree_model_iter_nth_child (model, &iter, NULL, n)) + { + GtkTreePath *path; - g_signal_handlers_unblock_by_func (window->calendar_chats, - log_window_calendar_chats_day_selected_cb, - window); - } + path = gtk_tree_model_get_path (model, &iter); + gtk_tree_view_scroll_to_cell (view, path, NULL, FALSE, 0, 0); + gtk_tree_path_free (path); + } - if (date != NULL) { - /* Show messages of the selected date */ - log_window_get_messages_for_date (window, date); - } + out: + ctx_free (ctx); - g_object_unref (account); - g_object_unref (target); + _tpl_action_chain_continue (log_window->chain); } static void -log_window_calendar_chats_day_selected_cb (GtkWidget *calendar, - EmpathyLogWindow *window) +get_events_for_date (TplActionChain *chain, gpointer user_data) { - guint year; - guint month; - guint day; - GDate *date; + Ctx *ctx = user_data; - gtk_calendar_get_date (GTK_CALENDAR (calendar), &year, &month, &day); - if (day == 0) - /* No date selected */ - return; + tpl_log_manager_get_events_for_date_async (ctx->window->log_manager, + ctx->account, ctx->entity, ctx->event_mask, + ctx->date, + log_window_got_messages_for_date_cb, + ctx); +} - /* We need this hear because it appears that the months start from 0 */ - month++; +static void +log_window_get_messages_for_dates (EmpathyLogWindow *window, + GList *dates) +{ + GList *accounts, *targets, *acc, *targ, *l; + TplEventTypeMask event_mask; + EventSubtype subtype; + GDate *date, *anytime, *separator; - date = g_date_new_dmy (day, month, year); + if (!log_window_get_selected (window, + &accounts, &targets, NULL, &event_mask, &subtype)) + return; - DEBUG ("Currently selected date is: %04u-%02u-%02u", year, month, day); + anytime = g_date_new_dmy (2, 1, -1); + separator = g_date_new_dmy (1, 1, -1); + + _tpl_action_chain_clear (window->chain); + window->count++; + + for (acc = accounts, targ = targets; + acc != NULL && targ != NULL; + acc = acc->next, targ = targ->next) + { + TpAccount *account = acc->data; + TplEntity *target = targ->data; + + for (l = dates; l != NULL; l = l->next) + { + date = l->data; + + /* Get events */ + if (g_date_compare (date, anytime) != 0) + { + Ctx *ctx; + + ctx = ctx_new (window, account, target, date, event_mask, subtype, + window->count); + _tpl_action_chain_append (window->chain, get_events_for_date, ctx); + } + else + { + GtkTreeView *view = GTK_TREE_VIEW (window->treeview_when); + GtkTreeModel *model = gtk_tree_view_get_model (view); + GtkTreeIter iter; + gboolean next; + GDate *d; + + for (next = gtk_tree_model_get_iter_first (model, &iter); + next; + next = gtk_tree_model_iter_next (model, &iter)) + { + Ctx *ctx; + + gtk_tree_model_get (model, &iter, + COL_WHEN_DATE, &d, + -1); + + if (g_date_compare (d, anytime) != 0 && + g_date_compare (d, separator) != 0) + { + ctx = ctx_new (window, account, target, d, + event_mask, subtype, window->count); + _tpl_action_chain_append (window->chain, get_events_for_date, ctx); + } + } + } + } + } + + start_spinner (); + g_timeout_add (1000, show_spinner, NULL); + _tpl_action_chain_append (window->chain, show_events, NULL); + _tpl_action_chain_start (window->chain); + + g_list_free_full (accounts, g_object_unref); + g_list_free_full (targets, g_object_unref); + g_date_free (separator); + g_date_free (anytime); +} - log_window_chats_get_messages (window, date); +static void +log_manager_got_dates_cb (GObject *manager, + GAsyncResult *result, + gpointer user_data) +{ + Ctx *ctx = user_data; + GtkTreeView *view; + GtkTreeModel *model; + GtkListStore *store; + GtkTreeIter iter; + GList *dates; + GList *l; + GError *error = NULL; - g_date_free (date); + if (log_window == NULL) + goto out; + + if (log_window->count != ctx->count) + goto out; + + if (!tpl_log_manager_get_dates_finish (TPL_LOG_MANAGER (manager), + result, &dates, &error)) + { + DEBUG ("Unable to retrieve messages' dates: %s. Aborting", + error->message); + goto out; + } + + view = GTK_TREE_VIEW (log_window->treeview_when); + model = gtk_tree_view_get_model (view); + store = GTK_LIST_STORE (model); + + for (l = dates; l != NULL; l = l->next) + { + GDate *date = l->data; + + /* Add the date if it's not already there */ + has_element = FALSE; + gtk_tree_model_foreach (model, model_has_date, date); + if (!has_element) + { + gchar *text = format_date_for_display (date); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + COL_WHEN_DATE, date, + COL_WHEN_TEXT, text, + COL_WHEN_ICON, CALENDAR_ICON, + -1); + + g_free (text); + } + } + + if (gtk_tree_model_get_iter_first (model, &iter)) + { + gchar *separator = NULL; + + if (gtk_tree_model_iter_next (model, &iter)) + { + gtk_tree_model_get (model, &iter, + COL_WHEN_TEXT, &separator, + -1); + } + + if (g_strcmp0 (separator, "separator") != 0) + { + gtk_list_store_prepend (store, &iter); + gtk_list_store_set (store, &iter, + COL_WHEN_DATE, g_date_new_dmy (1, 1, -1), + COL_WHEN_TEXT, "separator", + -1); + + gtk_list_store_prepend (store, &iter); + gtk_list_store_set (store, &iter, + COL_WHEN_DATE, g_date_new_dmy (2, 1, -1), + COL_WHEN_TEXT, _("Anytime"), + -1); + } + } + + g_list_free_full (dates, g_free); + out: + ctx_free (ctx); + _tpl_action_chain_continue (log_window->chain); } static void -log_window_updating_calendar_month_cb (GObject *manager, - GAsyncResult *result, gpointer user_data) -{ - EmpathyLogWindow *window = user_data; - GList *dates; - GList *l; - guint year_selected; - guint month_selected; - GError *error = NULL; - - if (log_window == NULL) - return; - - if (!tpl_log_manager_get_dates_finish (TPL_LOG_MANAGER (manager), - result, &dates, &error)) { - DEBUG ("Unable to retrieve messages' dates: %s. Aborting", - error->message); - empathy_chat_view_append_event (window->chatview_find, - "Unable to retrieve messages' dates"); - g_error_free (error); - return; - } - - gtk_calendar_clear_marks (GTK_CALENDAR (window->calendar_chats)); - g_object_get (window->calendar_chats, - "month", &month_selected, - "year", &year_selected, - NULL); - - /* We need this here because it appears that the months start from 0 */ - month_selected++; - - for (l = dates; l; l = l->next) { - GDate *date = l->data; - - if (g_date_get_year (date) == year_selected && - g_date_get_month (date) == month_selected) { - DEBUG ("Marking date: %04u-%02u-%02u", g_date_get_year (date), - g_date_get_month (date), g_date_get_day (date)); - gtk_calendar_mark_day (GTK_CALENDAR (window->calendar_chats), g_date_get_day (date)); - } - } - - g_list_foreach (dates, (GFunc) g_free, NULL); - g_list_free (dates); - - DEBUG ("Currently showing month %d and year %d", month_selected, - year_selected); +select_first_date (TplActionChain *chain, gpointer user_data) +{ + GtkTreeView *view; + GtkTreeModel *model; + GtkTreeSelection *selection; + GtkTreeIter iter; + + view = GTK_TREE_VIEW (log_window->treeview_when); + model = gtk_tree_view_get_model (view); + selection = gtk_tree_view_get_selection (view); + + /* Show messages of the most recent date */ + if (gtk_tree_model_iter_nth_child (model, &iter, NULL, 2)) + gtk_tree_selection_select_iter (selection, &iter); + + _tpl_action_chain_continue (log_window->chain); } static void -log_window_calendar_chats_month_changed_cb (GtkWidget *calendar, - EmpathyLogWindow *window) +get_dates_for_entity (TplActionChain *chain, gpointer user_data) { - TpAccount *account; - TplEntity *target; + Ctx *ctx = user_data; - gtk_calendar_clear_marks (GTK_CALENDAR (calendar)); + tpl_log_manager_get_dates_async (ctx->window->log_manager, + ctx->account, ctx->entity, ctx->event_mask, + log_manager_got_dates_cb, ctx); +} - if (!log_window_chats_get_selected (window, &account, &target)) { - DEBUG ("No chat selected to get dates for..."); - return; - } +static void +log_window_chats_get_messages (EmpathyLogWindow *window, + gboolean force_get_dates) +{ + GList *accounts, *targets, *dates; + TplEventTypeMask event_mask; + GtkTreeView *view; + GtkTreeModel *model; + GtkListStore *store; + GtkTreeSelection *selection; + + if (!log_window_get_selected (window, &accounts, &targets, + &dates, &event_mask, NULL)) + return; - /* Get the log object for this contact */ - tpl_log_manager_get_dates_async (window->log_manager, account, target, - TPL_EVENT_MASK_TEXT, - log_window_updating_calendar_month_cb, - (gpointer) window); + view = GTK_TREE_VIEW (window->treeview_when); + selection = gtk_tree_view_get_selection (view); + model = gtk_tree_view_get_model (view); + store = GTK_LIST_STORE (model); - g_object_unref (account); - g_object_unref (target); + /* Clear all current messages shown in the textview */ + gtk_tree_store_clear (window->store_events); + + _tpl_action_chain_clear (window->chain); + window->count++; + + /* If there's a search use the returned hits */ + if (window->hits != NULL) + { + if (force_get_dates) + { + g_signal_handlers_block_by_func (selection, + log_window_when_changed_cb, + window); + + gtk_list_store_clear (store); + + g_signal_handlers_unblock_by_func (selection, + log_window_when_changed_cb, + window); + + populate_dates_from_search_hits (accounts, targets); + } + else + { + populate_events_from_search_hits (accounts, targets, dates); + } + } + /* Either use the supplied date or get the last */ + else if (force_get_dates || dates == NULL) + { + GList *acc, *targ; + + g_signal_handlers_block_by_func (selection, + log_window_when_changed_cb, + window); + + gtk_list_store_clear (store); + + g_signal_handlers_unblock_by_func (selection, + log_window_when_changed_cb, + window); + + /* Get a list of dates and show them on the treeview */ + for (targ = targets, acc = accounts; + targ != NULL && acc != NULL; + targ = targ->next, acc = acc->next) + { + TpAccount *account = acc->data; + TplEntity *target = targ->data; + Ctx *ctx = ctx_new (window, account, target, NULL, event_mask, 0, + window->count); + + _tpl_action_chain_append (window->chain, get_dates_for_entity, ctx); + } + _tpl_action_chain_append (window->chain, select_first_date, NULL); + _tpl_action_chain_start (window->chain); + } + else + { + /* Show messages of the selected date */ + log_window_get_messages_for_dates (window, dates); + } + + g_list_free_full (accounts, g_object_unref); + g_list_free_full (targets, g_object_unref); + g_list_free_full (dates, (GFreeFunc) g_date_free); } +typedef struct { + EmpathyAccountChooserFilterResultCallback callback; + gpointer user_data; +} FilterCallbackData; + static void -log_window_entry_chats_changed_cb (GtkWidget *entry, - EmpathyLogWindow *window) +got_entities (GObject *manager, + GAsyncResult *result, + gpointer user_data) { - const gchar *str; + FilterCallbackData *data = user_data; + GList *entities; + GError *error = NULL; - str = gtk_entry_get_text (GTK_ENTRY (window->entry_chats)); - empathy_chat_view_highlight (window->chatview_chats, str, FALSE); + if (!tpl_log_manager_get_entities_finish (TPL_LOG_MANAGER (manager), + result, &entities, &error)) + { + DEBUG ("Could not get entities: %s", error->message); + g_error_free (error); + data->callback (FALSE, data->user_data); + } + else + { + data->callback (entities != NULL, data->user_data); + + g_list_free_full (entities, g_object_unref); + } - if (str != NULL) { - empathy_chat_view_find_next (window->chatview_chats, - str, - TRUE, - FALSE); - } + g_slice_free (FilterCallbackData, data); } static void -log_window_entry_chats_activate_cb (GtkWidget *entry, - EmpathyLogWindow *window) +empathy_account_chooser_filter_has_logs (TpAccount *account, + EmpathyAccountChooserFilterResultCallback callback, + gpointer callback_data, + gpointer user_data) { - const gchar *str; + TplLogManager *manager = tpl_log_manager_dup_singleton (); + FilterCallbackData *cb_data = g_slice_new0 (FilterCallbackData); + + cb_data->callback = callback; + cb_data->user_data = callback_data; - str = gtk_entry_get_text (GTK_ENTRY (window->entry_chats)); + tpl_log_manager_get_entities_async (manager, account, got_entities, cb_data); - if (str != NULL) { - empathy_chat_view_find_next (window->chatview_chats, - str, - FALSE, - FALSE); - } + g_object_unref (manager); +} + +static void +log_window_logger_clear_account_cb (TpProxy *proxy, + const GError *error, + gpointer user_data, + GObject *weak_object) +{ + EmpathyLogWindow *window = user_data; + + if (error != NULL) + g_warning ("Error when clearing logs: %s", error->message); + + /* Refresh the log viewer so the logs are cleared if the account + * has been deleted */ + gtk_tree_store_clear (window->store_events); + log_window_who_populate (window); + + /* Re-filter the account chooser so the accounts without logs get greyed out */ + empathy_account_chooser_set_filter ( + EMPATHY_ACCOUNT_CHOOSER (window->account_chooser), + empathy_account_chooser_filter_has_logs, NULL); +} + +static void +log_window_clear_logs_chooser_select_account (EmpathyAccountChooser *chooser, + EmpathyLogWindow *window) +{ + EmpathyAccountChooser *account_chooser; + + account_chooser = EMPATHY_ACCOUNT_CHOOSER (window->account_chooser); + + empathy_account_chooser_set_account (chooser, + empathy_account_chooser_get_account (account_chooser)); +} + +static void +log_window_delete_menu_clicked_cb (GtkMenuItem *menuitem, + EmpathyLogWindow *window) +{ + GtkWidget *dialog, *content_area, *hbox, *label; + EmpathyAccountChooser *account_chooser; + gint response_id; + TpDBusDaemon *bus; + TpProxy *logger; + GError *error = NULL; + + account_chooser = (EmpathyAccountChooser *) empathy_account_chooser_new (); + empathy_account_chooser_set_has_all_option (account_chooser, TRUE); + empathy_account_chooser_set_filter (account_chooser, + empathy_account_chooser_filter_has_logs, NULL); + + /* Select the same account as in the history window */ + if (empathy_account_chooser_is_ready (account_chooser)) + log_window_clear_logs_chooser_select_account (account_chooser, window); + else + g_signal_connect (account_chooser, "ready", + G_CALLBACK (log_window_clear_logs_chooser_select_account), window); + + dialog = gtk_message_dialog_new_with_markup (GTK_WINDOW (window->window), + GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, + GTK_BUTTONS_NONE, + _("Are you sure you want to delete all logs of previous conversations?")); + + gtk_dialog_add_buttons (GTK_DIALOG (dialog), + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + _("Clear All"), GTK_RESPONSE_APPLY, + NULL); + + content_area = gtk_message_dialog_get_message_area ( + GTK_MESSAGE_DIALOG (dialog)); + + hbox = gtk_hbox_new (FALSE, 6); + label = gtk_label_new (_("Delete from:")); + gtk_box_pack_start (GTK_BOX (hbox), label, + FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (hbox), GTK_WIDGET (account_chooser), + FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (content_area), hbox, + FALSE, FALSE, 0); + + gtk_widget_show_all (hbox); + + response_id = gtk_dialog_run (GTK_DIALOG (dialog)); + + if (response_id != GTK_RESPONSE_APPLY) + goto out; + + bus = tp_dbus_daemon_dup (&error); + if (error != NULL) + { + g_warning ("Could not delete logs: %s", error->message); + g_error_free (error); + goto out; + } + + logger = g_object_new (TP_TYPE_PROXY, + "bus-name", "org.freedesktop.Telepathy.Logger", + "object-path", "/org/freedesktop/Telepathy/Logger", + "dbus-daemon", bus, + NULL); + g_object_unref (bus); + + tp_proxy_add_interface_by_id (logger, EMP_IFACE_QUARK_LOGGER); + + if (empathy_account_chooser_has_all_selected (account_chooser)) + { + DEBUG ("Deleting logs for all the accounts"); + + emp_cli_logger_call_clear (logger, -1, + log_window_logger_clear_account_cb, + window, NULL, G_OBJECT (window->window)); + } + else + { + TpAccount *account; + + account = empathy_account_chooser_get_account (account_chooser); + + DEBUG ("Deleting logs for %s", tp_proxy_get_object_path (account)); + + emp_cli_logger_call_clear_account (logger, -1, + tp_proxy_get_object_path (account), + log_window_logger_clear_account_cb, + window, NULL, G_OBJECT (window->window)); + } + + g_object_unref (logger); + out: + gtk_widget_destroy (dialog); } diff --git a/libempathy-gtk/empathy-log-window.h b/libempathy-gtk/empathy-log-window.h index def0d846b..373f48c36 100644 --- a/libempathy-gtk/empathy-log-window.h +++ b/libempathy-gtk/empathy-log-window.h @@ -1,7 +1,6 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Copyright (C) 2006-2007 Imendio AB - * Copyright (C) 2007-2008 Collabora Ltd. + * Copyright (C) 2007-2011 Collabora Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -20,6 +19,7 @@ * * Authors: Martyn Russell <martyn@imendio.com> * Xavier Claessens <xclaesse@gmail.com> + * Emilio Pozuelo Monfort <emilio.pozuelo@collabora.co.uk> */ #ifndef __EMPATHY_LOG_WINDOW_H__ @@ -29,10 +29,10 @@ G_BEGIN_DECLS -GtkWidget * empathy_log_window_show (TpAccount *account, - const gchar *chat_id, - gboolean chatroom, - GtkWindow *parent); +GtkWidget * empathy_log_window_show (TpAccount *account, + const gchar *chat_id, + gboolean chatroom, + GtkWindow *parent); G_END_DECLS diff --git a/libempathy-gtk/empathy-log-window.ui b/libempathy-gtk/empathy-log-window.ui index a0cbcdcfa..79e4b6db2 100644 --- a/libempathy-gtk/empathy-log-window.ui +++ b/libempathy-gtk/empathy-log-window.ui @@ -1,383 +1,351 @@ -<?xml version="1.0"?> +<?xml version="1.0" encoding="UTF-8"?> <interface> - <!-- interface-requires gtk+ 2.12 --> - <!-- interface-naming-policy toplevel-contextual --> + <!-- interface-requires gtk+ 3.0 --> <object class="GtkWindow" id="log_window"> - <property name="title" translatable="yes">Previous Conversations</property> - <property name="role">log</property> - <property name="default_width">640</property> - <property name="default_height">450</property> - <property name="icon_name">document-open-recent</property> + <property name="can_focus">False</property> + <property name="title" translatable="yes">History</property> + <property name="default_width">800</property> + <property name="default_height">600</property> <child> - <object class="GtkNotebook" id="notebook"> + <object class="GtkVBox" id="vbox1"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="border_width">2</property> + <property name="can_focus">False</property> <child> - <object class="GtkVBox" id="vbox192"> + <object class="GtkMenuBar" id="menubar1"> <property name="visible">True</property> - <property name="border_width">12</property> - <property name="spacing">6</property> + <property name="can_focus">False</property> <child> - <object class="GtkHBox" id="hbox144"> + <object class="GtkMenuItem" id="menuitem1"> <property name="visible">True</property> - <property name="spacing">12</property> - <child> - <object class="GtkLabel" id="label628"> - <property name="visible">True</property> - <property name="label" translatable="yes" comments="Searching *for* something">_For:</property> - <property name="use_underline">True</property> - <property name="mnemonic_widget">entry_find</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="entry_find"> + <property name="can_focus">False</property> + <property name="use_action_appearance">False</property> + <property name="label" translatable="yes">_File</property> + <property name="use_underline">True</property> + <child type="submenu"> + <object class="GtkMenu" id="menu1"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="has_focus">True</property> - <property name="activates_default">True</property> - </object> - <packing> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkButton" id="button_find"> - <property name="label">gtk-find</property> - <property name="visible">True</property> - <property name="sensitive">False</property> - <property name="can_focus">True</property> - <property name="can_default">True</property> - <property name="has_default">True</property> - <property name="receives_default">False</property> - <property name="use_stock">True</property> - <property name="focus_on_click">False</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="position">2</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkVPaned" id="vpaned1"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="position">120</property> - <child> - <object class="GtkScrolledWindow" id="scrolledwindow14"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="hscrollbar_policy">never</property> - <property name="vscrollbar_policy">automatic</property> - <property name="shadow_type">in</property> + <property name="can_focus">False</property> <child> - <object class="GtkTreeView" id="treeview_find"> + <object class="GtkImageMenuItem" id="imagemenuitem_quit"> + <property name="label">gtk-quit</property> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="enable_search">False</property> + <property name="can_focus">False</property> + <property name="use_action_appearance">False</property> + <property name="use_underline">True</property> + <property name="use_stock">True</property> </object> </child> </object> - <packing> - <property name="resize">False</property> - <property name="shrink">True</property> - </packing> </child> - <child> - <object class="GtkVBox" id="vbox215"> + </object> + </child> + <child> + <object class="GtkMenuItem" id="menuitem2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="use_action_appearance">False</property> + <property name="label" translatable="yes">_Edit</property> + <property name="use_underline">True</property> + <child type="submenu"> + <object class="GtkMenu" id="menu2"> <property name="visible">True</property> - <property name="spacing">6</property> - <child> - <object class="GtkScrolledWindow" id="scrolledwindow_find"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="hscrollbar_policy">never</property> - <property name="shadow_type">in</property> - <child> - <placeholder/> - </child> - </object> - <packing> - <property name="position">0</property> - </packing> - </child> + <property name="can_focus">False</property> <child> - <object class="GtkHBox" id="hbox171"> + <object class="GtkImageMenuItem" id="imagemenuitem_delete"> + <property name="label">Delete All History...</property> <property name="visible">True</property> - <property name="spacing">12</property> - <child> - <placeholder/> - </child> - <child> - <object class="GtkButton" id="button_next"> - <property name="label" translatable="yes">Find Next</property> - <property name="visible">True</property> - <property name="sensitive">False</property> - <property name="can_focus">True</property> - <property name="receives_default">False</property> - <property name="image">image1</property> - <property name="focus_on_click">False</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="pack_type">end</property> - <property name="position">2</property> - </packing> - </child> - <child> - <object class="GtkButton" id="button_previous"> - <property name="label" translatable="yes">Find Previous</property> - <property name="visible">True</property> - <property name="sensitive">False</property> - <property name="can_focus">True</property> - <property name="receives_default">False</property> - <property name="image">image2</property> - <property name="focus_on_click">False</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="pack_type">end</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkButton" id="button_close"> - <property name="label">gtk-close</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="use_stock">True</property> - <property name="focus_on_click">False</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="pack_type">end</property> - <property name="position">0</property> - </packing> - </child> + <property name="can_focus">False</property> + <property name="use_action_appearance">False</property> + <property name="use_stock">False</property> </object> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="position">1</property> - </packing> </child> </object> - <packing> - <property name="resize">True</property> - <property name="shrink">True</property> - </packing> </child> </object> - <packing> - <property name="position">1</property> - </packing> - </child> - <child> - <placeholder/> </child> - <child> - <placeholder/> - </child> - </object> - </child> - <child type="tab"> - <object class="GtkLabel" id="label595"> - <property name="visible">True</property> - <property name="label" translatable="yes" comments="Tab Label">Search</property> </object> <packing> - <property name="tab_fill">False</property> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> </packing> </child> <child> - <object class="GtkTable" id="table7"> + <object class="GtkToolbar" id="toolbar1"> <property name="visible">True</property> - <property name="border_width">12</property> - <property name="n_rows">3</property> - <property name="n_columns">2</property> - <property name="column_spacing">6</property> - <property name="row_spacing">6</property> + <property name="can_focus">False</property> + <property name="toolbar_style">both</property> + <child> + <object class="GtkToolButton" id="toolbutton_profile"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="use_action_appearance">False</property> + <property name="label" translatable="yes">Profile</property> + <property name="use_underline">True</property> + <property name="stock_id">gtk-dialog-info</property> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToolButton" id="toolbutton_chat"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="use_action_appearance">False</property> + <property name="label" translatable="yes">Chat</property> + <property name="use_underline">True</property> + <property name="stock_id">gtk-edit</property> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToolButton" id="toolbutton_call"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="use_action_appearance">False</property> + <property name="label" translatable="yes">Call</property> + <property name="use_underline">True</property> + <property name="icon_name">audio-input-microphone</property> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToolButton" id="toolbutton_video"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="use_action_appearance">False</property> + <property name="label" translatable="yes">Video</property> + <property name="use_underline">True</property> + <property name="icon_name">camera-video</property> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> <child> - <object class="GtkVBox" id="vbox_chats"> + <object class="GtkSeparatorToolItem" id="toolbutton_sep1"> <property name="visible">True</property> - <property name="spacing">6</property> + <property name="can_focus">False</property> + <property name="draw">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToolItem" id="toolbutton_accounts"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="use_action_appearance">False</property> <child> <placeholder/> </child> </object> <packing> - <property name="x_options">GTK_FILL</property> - <property name="y_options">GTK_FILL</property> + <property name="expand">False</property> + <property name="homogeneous">True</property> </packing> </child> <child> - <object class="GtkScrolledWindow" id="scrolledwindow_chats"> + <object class="GtkSeparatorToolItem" id="toolbutton_sep2"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="hscrollbar_policy">never</property> - <property name="shadow_type">in</property> + <property name="can_focus">False</property> + <property name="draw">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToolItem" id="toolbutton_search"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="use_action_appearance">False</property> <child> <placeholder/> </child> </object> <packing> - <property name="left_attach">1</property> - <property name="right_attach">2</property> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> + <property name="expand">False</property> + <property name="homogeneous">True</property> </packing> </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkVPaned" id="vpaned1"> + <property name="visible">True</property> + <property name="can_focus">True</property> <child> - <object class="GtkVBox" id="vbox191"> + <object class="GtkHBox" id="hbox1"> + <property name="height_request">160</property> <property name="visible">True</property> - <property name="spacing">6</property> + <property name="can_focus">False</property> <child> - <object class="GtkScrolledWindow" id="scrolledwindow13"> - <property name="width_request">150</property> + <object class="GtkScrolledWindow" id="scrolledwindow_who"> <property name="visible">True</property> <property name="can_focus">True</property> - <property name="hscrollbar_policy">never</property> - <property name="vscrollbar_policy">automatic</property> - <property name="shadow_type">in</property> <child> - <object class="GtkTreeView" id="treeview_chats"> + <object class="GtkTreeView" id="treeview_who"> <property name="visible">True</property> <property name="can_focus">True</property> - <property name="headers_visible">False</property> + <child internal-child="selection"> + <object class="GtkTreeSelection" id="treeview-selection1"/> + </child> </object> </child> </object> <packing> + <property name="expand">True</property> + <property name="fill">True</property> <property name="position">0</property> </packing> </child> <child> - <object class="GtkCalendar" id="calendar_chats"> + <object class="GtkScrolledWindow" id="scrolledwindow_what"> <property name="visible">True</property> <property name="can_focus">True</property> - <property name="sensitive">False</property> + <child> + <object class="GtkTreeView" id="treeview_what"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <child internal-child="selection"> + <object class="GtkTreeSelection" id="treeview-selection2"/> + </child> + </object> + </child> </object> <packing> - <property name="expand">False</property> - <property name="fill">False</property> + <property name="expand">True</property> + <property name="fill">True</property> <property name="position">1</property> </packing> </child> + <child> + <object class="GtkScrolledWindow" id="scrolledwindow_when"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <child> + <object class="GtkTreeView" id="treeview_when"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <child internal-child="selection"> + <object class="GtkTreeSelection" id="treeview-selection3"/> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> </object> <packing> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> - <property name="x_options">GTK_FILL</property> + <property name="resize">False</property> + <property name="shrink">True</property> </packing> </child> <child> - <object class="GtkHBox" id="hbox143"> + <object class="GtkNotebook" id="notebook"> <property name="visible">True</property> - <property name="spacing">6</property> + <property name="can_focus">True</property> + <property name="show_tabs">False</property> + <property name="show_border">False</property> <child> - <object class="GtkImage" id="image247"> + <object class="GtkScrolledWindow" id="scrolledwindow_events"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <child> + <object class="GtkTreeView" id="treeview_events"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <child internal-child="selection"> + <object class="GtkTreeSelection" id="treeview-selection4"/> + </child> + </object> + </child> + </object> + </child> + <child type="tab"> + <object class="GtkLabel" id="label1"> <property name="visible">True</property> - <property name="stock">gtk-find</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">page 2</property> </object> <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="position">0</property> + <property name="tab_fill">False</property> </packing> </child> <child> - <object class="GtkEntry" id="entry_chats"> + <object class="GtkSpinner" id="spinner"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="activates_default">True</property> + <property name="can_focus">False</property> </object> <packing> <property name="position">1</property> </packing> </child> - </object> - <packing> - <property name="left_attach">1</property> - <property name="right_attach">2</property> - <property name="x_options">GTK_FILL</property> - <property name="y_options">GTK_FILL</property> - </packing> - </child> - <child> - <object class="GtkHBox" id="hbox2"> - <property name="visible">True</property> - <property name="spacing">12</property> - <child> - <placeholder/> + <child type="tab"> + <object class="GtkLabel" id="label2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">page 2</property> + </object> + <packing> + <property name="position">1</property> + <property name="tab_fill">False</property> + </packing> </child> <child> - <object class="GtkButton" id="button_close2"> - <property name="label">gtk-close</property> + <object class="GtkScrolledWindow" id="scrolledwindow_empty"> <property name="visible">True</property> <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="use_stock">True</property> + <child> + <object class="GtkTreeView" id="treeview_empty"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <child internal-child="selection"> + <object class="GtkTreeSelection" id="treeview-selection5"/> + </child> + </object> + </child> </object> <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="pack_type">end</property> - <property name="position">1</property> + <property name="position">2</property> </packing> </child> </object> <packing> - <property name="left_attach">1</property> - <property name="right_attach">2</property> - <property name="top_attach">2</property> - <property name="bottom_attach">3</property> - <property name="y_options"></property> + <property name="resize">True</property> + <property name="shrink">True</property> </packing> </child> - <child> - <placeholder/> - </child> </object> <packing> - <property name="position">1</property> - </packing> - </child> - <child type="tab"> - <object class="GtkLabel" id="label596"> - <property name="visible">True</property> - <property name="label" translatable="yes" comments="Tab Label">Conversations</property> - </object> - <packing> - <property name="position">1</property> - <property name="tab_fill">False</property> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">2</property> </packing> </child> </object> </child> </object> - <object class="GtkImage" id="image1"> - <property name="visible">True</property> - <property name="stock">gtk-go-forward</property> - </object> - <object class="GtkImage" id="image2"> - <property name="visible">True</property> - <property name="stock">gtk-go-back</property> - </object> </interface> diff --git a/libempathy/Makefile.am b/libempathy/Makefile.am index 48846311f..56ea9e780 100644 --- a/libempathy/Makefile.am +++ b/libempathy/Makefile.am @@ -28,6 +28,7 @@ BUILT_SOURCES = \ noinst_LTLIBRARIES = libempathy.la libempathy_headers = \ + action-chain-internal.h \ empathy-account-settings.h \ empathy-auth-factory.h \ empathy-channel-factory.h \ @@ -69,6 +70,7 @@ libempathy_headers = \ libempathy_la_SOURCES = \ $(libempathy_headers) \ + action-chain.c \ empathy-account-settings.c \ empathy-auth-factory.c \ empathy-channel-factory.c \ diff --git a/libempathy/action-chain-internal.h b/libempathy/action-chain-internal.h new file mode 100644 index 000000000..14750c938 --- /dev/null +++ b/libempathy/action-chain-internal.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2009 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: Cosimo Alfarano <cosimo.alfarano@collabora.co.uk> + */ + +#ifndef __TPL_ACTION_CHAIN_H__ +#define __TPL_ACTION_CHAIN_H__ + +#include <glib-object.h> +#include <gio/gio.h> + +typedef struct { + GQueue *chain; + GSimpleAsyncResult *simple; + gboolean running; +} TplActionChain; + +TplActionChain *_tpl_action_chain_new_async (GObject *obj, + GAsyncReadyCallback cb, + gpointer user_data); +void _tpl_action_chain_free (TplActionChain *self); +typedef void (*TplPendingAction) (TplActionChain *ctx, gpointer user_data); +void _tpl_action_chain_append (TplActionChain *self, TplPendingAction func, + gpointer user_data); +void _tpl_action_chain_prepend (TplActionChain *self, TplPendingAction func, + gpointer user_data); +void _tpl_action_chain_start (TplActionChain *self); +void _tpl_action_chain_continue (TplActionChain *self); +void _tpl_action_chain_terminate (TplActionChain *self, const GError *error); +void _tpl_action_chain_clear (TplActionChain *self); + +gpointer _tpl_action_chain_get_object (TplActionChain *self); +gboolean _tpl_action_chain_new_finish (GObject *source, + GAsyncResult *result, GError **error); + +#endif // __TPL_ACTION_CHAIN_H__ diff --git a/libempathy/action-chain.c b/libempathy/action-chain.c new file mode 100644 index 000000000..b6bf25ab9 --- /dev/null +++ b/libempathy/action-chain.c @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2009 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: Cosimo Alfarano <cosimo.alfarano@collabora.co.uk> + */ + +#include "config.h" +#include "action-chain-internal.h" + +typedef struct { + TplPendingAction action; + gpointer user_data; +} TplActionLink; + + +TplActionChain * +_tpl_action_chain_new_async (GObject *obj, + GAsyncReadyCallback cb, + gpointer user_data) +{ + TplActionChain *ret = g_slice_new0 (TplActionChain); + + ret->chain = g_queue_new (); + ret->simple = g_simple_async_result_new (obj, cb, user_data, + _tpl_action_chain_new_async); + + g_object_set_data (G_OBJECT (ret->simple), "chain", ret); + + return ret; +} + + +static void +link_free (TplActionLink *l) +{ + g_slice_free (TplActionLink, l); +} + + +void +_tpl_action_chain_free (TplActionChain *self) +{ + g_queue_foreach (self->chain, (GFunc) link_free, NULL); + g_queue_free (self->chain); + g_object_unref (self->simple); + g_slice_free (TplActionChain, self); +} + + +gpointer // FIXME GObject * +_tpl_action_chain_get_object (TplActionChain *self) +{ + GObject *obj; + + g_return_val_if_fail (self != NULL && self->simple != NULL, NULL); + + obj = g_async_result_get_source_object (G_ASYNC_RESULT (self->simple)); + g_object_unref (obj); /* don't want the extra ref */ + + return obj; +} + + +void +_tpl_action_chain_prepend (TplActionChain *self, + TplPendingAction func, + gpointer user_data) +{ + TplActionLink *l; + + l = g_slice_new0 (TplActionLink); + l->action = func; + l->user_data = user_data; + + g_queue_push_head (self->chain, l); +} + + +void +_tpl_action_chain_append (TplActionChain *self, + TplPendingAction func, + gpointer user_data) +{ + TplActionLink *l; + + l = g_slice_new0 (TplActionLink); + l->action = func; + l->user_data = user_data; + + g_queue_push_tail (self->chain, l); +} + +void +_tpl_action_chain_start (TplActionChain *self) +{ + g_return_if_fail (!g_queue_is_empty (self->chain)); + + if (self->running) + return; + + _tpl_action_chain_continue (self); +} + +void +_tpl_action_chain_continue (TplActionChain *self) +{ + if (g_queue_is_empty (self->chain)) + { + self->running = FALSE; + g_simple_async_result_complete (self->simple); + } + else + { + TplActionLink *l = g_queue_pop_head (self->chain); + + self->running = TRUE; + l->action (self, l->user_data); + link_free (l); + if (g_queue_is_empty (self->chain)) + self->running = FALSE; + } +} + + +void +_tpl_action_chain_clear (TplActionChain *self) +{ + g_queue_foreach (self->chain, (GFunc) link_free, NULL); + g_queue_clear (self->chain); +} + +void +_tpl_action_chain_terminate (TplActionChain *self, + const GError *error) +{ + GSimpleAsyncResult *simple = self->simple; + + g_assert (error != NULL); + + g_simple_async_result_set_from_error (simple, error); + g_simple_async_result_complete (simple); +} + + +/** + * _tpl_action_chain_new_finish: + * @source: the #GObject pass to _tpl_action_chain_new_async() + * @result: the #GAsyncResult pass in callback + * @error: a pointer to a #GError that will be set on error, or NULL to ignore + * + * Get the result from running the action chain (%TRUE if the chain completed + * successfully, %FALSE with @error set if it was terminated). + * + * This function also frees the chain. + * + * Returns: %TRUE on success, %FALSE with @error set on error. + */ +gboolean +_tpl_action_chain_new_finish (GObject *source, + GAsyncResult *result, + GError **error) +{ + TplActionChain *chain; + + g_return_val_if_fail (g_simple_async_result_is_valid (result, source, + _tpl_action_chain_new_async), FALSE); + + chain = g_object_get_data (G_OBJECT (result), "chain"); + + g_return_val_if_fail (chain != NULL, FALSE); + + if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), + error)) + return FALSE; + + _tpl_action_chain_free (chain); + return TRUE; +} diff --git a/libempathy/empathy-contact.c b/libempathy/empathy-contact.c index 55bc40bdd..8cca70944 100644 --- a/libempathy/empathy-contact.c +++ b/libempathy/empathy-contact.c @@ -190,14 +190,12 @@ contact_dispose (GObject *object) { EmpathyContactPriv *priv = GET_PRIV (object); - if (priv->tp_contact) + if (priv->tp_contact != NULL) { - g_hash_table_remove (contacts_table, priv->tp_contact); g_signal_handlers_disconnect_by_func (priv->tp_contact, tp_contact_notify_cb, object); - g_object_unref (priv->tp_contact); } - priv->tp_contact = NULL; + tp_clear_object (&priv->tp_contact); if (priv->account) g_object_unref (priv->account); @@ -631,14 +629,48 @@ contact_set_property (GObject *object, }; } +static void +remove_tp_contact (gpointer data, + GObject *object) +{ + g_hash_table_remove (contacts_table, data); +} + static EmpathyContact * empathy_contact_new (TpContact *tp_contact) { + EmpathyContact *retval; + g_return_val_if_fail (TP_IS_CONTACT (tp_contact), NULL); - return g_object_new (EMPATHY_TYPE_CONTACT, + retval = g_object_new (EMPATHY_TYPE_CONTACT, "tp-contact", tp_contact, NULL); + + g_object_weak_ref (G_OBJECT (retval), remove_tp_contact, tp_contact); + + return retval; +} + +typedef struct +{ + TplEntity *entity; + TpAccount *account; +} FindContactData; + +static gboolean +contact_is_tpl_entity (gpointer key, + gpointer value, + gpointer user_data) +{ + EmpathyContact *contact = value; + FindContactData *data = user_data; + + return !tp_strdiff (empathy_contact_get_id (contact), + tpl_entity_get_identifier (data->entity)) && + !tp_strdiff (tp_proxy_get_object_path (data->account), + tp_proxy_get_object_path ( + empathy_contact_get_account (contact))); } EmpathyContact * @@ -647,17 +679,49 @@ empathy_contact_from_tpl_contact (TpAccount *account, { EmpathyContact *retval; gboolean is_user; + EmpathyContact *existing_contact = NULL; g_return_val_if_fail (TPL_IS_ENTITY (tpl_entity), NULL); - is_user = (TPL_ENTITY_SELF == tpl_entity_get_entity_type (tpl_entity)); + if (contacts_table != NULL) + { + FindContactData data; - retval = g_object_new (EMPATHY_TYPE_CONTACT, - "id", tpl_entity_get_identifier (tpl_entity), - "alias", tpl_entity_get_alias (tpl_entity), - "account", account, - "is-user", is_user, - NULL); + data.entity = tpl_entity; + data.account = account; + + existing_contact = g_hash_table_find (contacts_table, + contact_is_tpl_entity, &data); + } + + if (existing_contact != NULL) + { + EmpathyContactPriv *priv; + + retval = g_object_new (EMPATHY_TYPE_CONTACT, + "tp-contact", empathy_contact_get_tp_contact (existing_contact), + NULL); + + priv = GET_PRIV (retval); + + /* contact_set_property() calls empathy_contact_set_alias(), which + * tries to set the alias on the FolksPersona, but we don't want to + * do that when creating an EmpathyContact from a TplEntity. So just + * set priv->alias instead of passing it to g_object_new() instead. */ + g_free (priv->alias); + priv->alias = g_strdup (tpl_entity_get_alias (tpl_entity)); + } + else + { + is_user = (TPL_ENTITY_SELF == tpl_entity_get_entity_type (tpl_entity)); + + retval = g_object_new (EMPATHY_TYPE_CONTACT, + "id", tpl_entity_get_identifier (tpl_entity), + "alias", tpl_entity_get_alias (tpl_entity), + "account", account, + "is-user", is_user, + NULL); + } if (!EMP_STR_EMPTY (tpl_entity_get_avatar_token (tpl_entity))) contact_load_avatar_cache (retval, @@ -697,16 +761,16 @@ const gchar * empathy_contact_get_alias (EmpathyContact *contact) { EmpathyContactPriv *priv; - const gchar *alias; + const gchar *alias = NULL; g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL); priv = GET_PRIV (contact); - if (priv->tp_contact != NULL) - alias = tp_contact_get_alias (priv->tp_contact); - else + if (!EMP_STR_EMPTY (priv->alias)) alias = priv->alias; + else if (priv->tp_contact != NULL) + alias = tp_contact_get_alias (priv->tp_contact); if (!EMP_STR_EMPTY (alias)) return alias; diff --git a/libempathy/empathy-message.c b/libempathy/empathy-message.c index 25ec498ce..d30ce3645 100644 --- a/libempathy/empathy-message.c +++ b/libempathy/empathy-message.c @@ -26,6 +26,8 @@ #include <string.h> +#include <glib/gi18n-lib.h> + #include <telepathy-glib/util.h> #include <telepathy-glib/account.h> #include <telepathy-glib/account-manager.h> @@ -33,6 +35,9 @@ #include <telepathy-logger/entity.h> #include <telepathy-logger/event.h> #include <telepathy-logger/text-event.h> +#ifdef HAVE_CALL_LOGS +# include <telepathy-logger/call-event.h> +#endif #include "empathy-message.h" #include "empathy-utils.h" @@ -304,6 +309,7 @@ empathy_message_from_tpl_log_event (TplEvent *logevent) TplEntity *sender = NULL; gchar *body= NULL; EmpathyContact *contact; + TpChannelTextMessageType type = TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL; g_return_val_if_fail (TPL_IS_EVENT (logevent), NULL); @@ -323,24 +329,36 @@ empathy_message_from_tpl_log_event (TplEvent *logevent) tpl_event_get_account_path (logevent)); g_object_unref (acc_man); - /* TODO Currently only TplTextEvent exists as subclass of TplEvent, in - * future more TplEvent will exist and EmpathyMessage should probably - * be enhanced to support other types of log entries (ie TplCallEvent). - * - * For now we just check (simply) that we are dealing with the only supported type, - * then there will be a if/then/else or switch handling all the supported - * cases. - */ - if (!TPL_IS_TEXT_EVENT (logevent)) + if (TPL_IS_TEXT_EVENT (logevent)) { + body = g_strdup (tpl_text_event_get_message ( + TPL_TEXT_EVENT (logevent))); + + type = tpl_text_event_get_message_type (TPL_TEXT_EVENT (logevent)); + } +#ifdef HAVE_CALL_LOGS + else if (TPL_IS_CALL_EVENT (logevent)) { + TplCallEvent *call = TPL_CALL_EVENT (logevent); + if (tpl_call_event_get_end_reason (call) == TPL_CALL_END_REASON_NO_ANSWER) + body = g_strdup_printf (_("Missed call from %s"), + tpl_entity_get_alias (tpl_event_get_sender (logevent))); + else if (tpl_entity_get_entity_type (tpl_event_get_sender (logevent)) == TPL_ENTITY_SELF) + body = g_strdup_printf (_("Called %s"), + tpl_entity_get_alias (tpl_event_get_receiver (logevent))); + else + body = g_strdup_printf (_("Call from %s"), + tpl_entity_get_alias (tpl_event_get_sender (logevent))); + } +#endif + else { + /* Unknown event type */ return NULL; + } - body = g_strdup (tpl_text_event_get_message ( - TPL_TEXT_EVENT (logevent))); receiver = tpl_event_get_receiver (logevent); sender = tpl_event_get_sender (logevent); retval = g_object_new (EMPATHY_TYPE_MESSAGE, - "type", tpl_text_event_get_message_type (TPL_TEXT_EVENT (logevent)), + "type", type, "body", body, "is-backlog", TRUE, "timestamp", tpl_event_get_timestamp (logevent), diff --git a/po/POTFILES.in b/po/POTFILES.in index a8821f2f3..8ae5858a8 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -6,6 +6,7 @@ data/org.gnome.Empathy.gschema.xml.in data/empathy-accounts.desktop.in.in libempathy/empathy-ft-handler.c +libempathy/empathy-message.c libempathy/empathy-tp-contact-list.c libempathy/empathy-tp-file.c libempathy/empathy-utils.c |