[empathy] empathy-chat: load logs as the user scrolls up or resizes the dialog



commit 027ccda384645f67a2d209d023c5d324d7f15715
Author: Debarshi Ray <debarshir src gnome org>
Date:   Thu Jul 5 16:41:46 2012 +0200

    empathy-chat: load logs as the user scrolls up or resizes the dialog
    
    This requires the TplLogWalker API to iterate over the logs.
    
    Fixes: https://bugzilla.gnome.org/639877

 libempathy-gtk/empathy-chat.c |  218 ++++++++++++++++++++++++++++++++---------
 1 files changed, 173 insertions(+), 45 deletions(-)
---
diff --git a/libempathy-gtk/empathy-chat.c b/libempathy-gtk/empathy-chat.c
index 9a12d1f..e9a914f 100644
--- a/libempathy-gtk/empathy-chat.c
+++ b/libempathy-gtk/empathy-chat.c
@@ -2,6 +2,7 @@
 /*
  * Copyright (C) 2002-2007 Imendio AB
  * Copyright (C) 2007-2010 Collabora Ltd.
+ * Copyright (C) 2012 Red Hat, Inc.
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -80,6 +81,17 @@ struct _EmpathyChatPriv {
 	GSettings         *gsettings_ui;
 
 	TplLogManager     *log_manager;
+	TplLogWalker      *log_walker;
+	/* Are we watching for scrolling movements? */
+	gboolean           watch_scroll;
+	/* Maximum page size of the chat->view. */
+	guint              max_page_size;
+	/* The offset from the lower edge of the chat->view before it
+	 * expanded to fit in the newly fetched logs. This is to
+	 * restore the chat->view to the page it was on before the
+	 * latest batch of logs were inserted. */
+	guint              scroll_offset;
+
 	TpAccountManager  *account_manager;
 	GList             *input_history;
 	GList             *input_history_current;
@@ -197,6 +209,7 @@ static guint signals[LAST_SIGNAL] = { 0 };
 
 G_DEFINE_TYPE (EmpathyChat, empathy_chat, GTK_TYPE_BOX);
 
+static gboolean chat_scrollable_connect (gpointer user_data);
 static gboolean update_misspelled_words (gpointer data);
 
 static void
@@ -2494,21 +2507,15 @@ static gboolean
 chat_log_filter (TplEvent *event,
 		 gpointer user_data)
 {
-	TpWeakRef *wr = user_data;
-	EmpathyChat *chat = tp_weak_ref_dup_object (wr);
+	EmpathyChat *chat = EMPATHY_CHAT (user_data);
+	EmpathyChatPriv *priv = GET_PRIV (chat);
 	EmpathyMessage *message;
-	EmpathyChatPriv *priv;
 	const GList *pending;
 	bool retval = FALSE;
 
-	if (chat == NULL)
-		return FALSE;
-
 	g_return_val_if_fail (TPL_IS_EVENT (event), FALSE);
 	g_return_val_if_fail (EMPATHY_IS_CHAT (chat), FALSE);
 
-	priv = GET_PRIV (chat);
-
 	pending = empathy_tp_chat_get_pending_messages (priv->tp_chat);
 	message = empathy_message_from_tpl_log_event (event);
 
@@ -2521,7 +2528,6 @@ chat_log_filter (TplEvent *event,
 
 out:
 	g_object_unref (message);
-	g_object_unref (chat);
 	return retval;
 }
 
@@ -2548,26 +2554,38 @@ show_pending_messages (EmpathyChat *chat) {
 }
 
 
+static gboolean
+chat_scrollable_set_value (gpointer user_data)
+{
+	EmpathyChat *chat = EMPATHY_CHAT (user_data);
+	EmpathyChatPriv *priv = GET_PRIV (chat);
+	GtkAdjustment *adjustment;
+	guint upper;
+
+	adjustment = gtk_scrollable_get_vadjustment (
+	    GTK_SCROLLABLE (chat->view));
+
+	/* Set the chat->view's adjustment back to the value it had
+	 * before it grew as a result of new logs being inserted.
+	 */
+	upper  = (guint) gtk_adjustment_get_upper (adjustment);
+	gtk_adjustment_set_value (adjustment, upper - priv->scroll_offset);
+
+	return G_SOURCE_REMOVE;
+}
+
 static void
-got_filtered_messages_cb (GObject *manager,
+got_filtered_messages_cb (GObject *walker,
 		GAsyncResult *result,
 		gpointer user_data)
 {
 	GList *l;
 	GList *messages;
-	TpWeakRef *wr = user_data;
-	EmpathyChat *chat = tp_weak_ref_dup_object (wr);
-	EmpathyChatPriv *priv;
+	EmpathyChat *chat = EMPATHY_CHAT (user_data);
+	EmpathyChatPriv *priv = GET_PRIV (chat);
 	GError *error = NULL;
 
-	if (chat == NULL) {
-		tp_weak_ref_destroy (wr);
-		return;
-	}
-
-	priv = GET_PRIV (chat);
-
-	if (!tpl_log_manager_get_filtered_events_finish (TPL_LOG_MANAGER (manager),
+	if (!tpl_log_walker_get_events_finish (TPL_LOG_WALKER (walker),
 		result, &messages, &error)) {
 		DEBUG ("%s. Aborting.", error->message);
 		empathy_theme_adium_append_event (chat->view,
@@ -2576,7 +2594,7 @@ got_filtered_messages_cb (GObject *manager,
 		goto out;
 	}
 
-	for (l = messages; l; l = g_list_next (l)) {
+	for (l = g_list_last (messages); l; l = g_list_previous (l)) {
 		EmpathyMessage *message;
 
 		g_assert (TPL_IS_EVENT (l->data));
@@ -2601,14 +2619,14 @@ got_filtered_messages_cb (GObject *manager,
 				"sender", empathy_message_get_sender (message),
 				NULL);
 
-			empathy_theme_adium_append_message (chat->view, syn_msg,
+			empathy_theme_adium_prepend_message (chat->view, syn_msg,
 							  chat_should_highlight (chat, syn_msg));
 			empathy_theme_adium_edit_message (chat->view, message);
 
 			g_object_unref (syn_msg);
 		} else {
 			/* append the latest message */
-			empathy_theme_adium_append_message (chat->view, message,
+			empathy_theme_adium_prepend_message (chat->view, message,
 							  chat_should_highlight (chat, message));
 		}
 
@@ -2629,43 +2647,141 @@ out:
 	/* Turn back on scrolling */
 	empathy_theme_adium_scroll (chat->view, TRUE);
 
+	/* We start watching the scrolling movements only after the first
+	 * batch of logs have been fetched. Otherwise, if the
+	 * chat->view's page size is too small the scrollbar might hit
+	 * the upper edge and trigger another batch of logs to be
+	 * fetched.
+	 */
+	if (G_UNLIKELY (!priv->watch_scroll &&
+			!tpl_log_walker_is_end (priv->log_walker))) {
+		priv->watch_scroll = TRUE;
+		g_idle_add_full (G_PRIORITY_LOW, chat_scrollable_connect,
+		    g_object_ref (chat), g_object_unref);
+	}
+	else {
+		GtkAdjustment *adjustment;
+		guint upper;
+		guint value;
+
+		/* The chat->view's adjustment won't change unless we
+		 * return to the main loop. Save the current offset
+		 * from the lower edge (or the upper value of the
+		 * adjustment) so that we can restore it later once the
+		 * adjustment grows.
+		 */
+		adjustment = gtk_scrollable_get_vadjustment (
+		    GTK_SCROLLABLE (chat->view));
+		upper = (guint) gtk_adjustment_get_upper (adjustment);
+		value = (guint) gtk_adjustment_get_value (adjustment);
+		priv->scroll_offset = upper - value;
+
+		g_idle_add_full (G_PRIORITY_LOW, chat_scrollable_set_value,
+		    g_object_ref (chat), g_object_unref);
+	}
+
 	g_object_unref (chat);
-	tp_weak_ref_destroy (wr);
 }
 
-static void
+static gboolean
 chat_add_logs (EmpathyChat *chat)
 {
 	EmpathyChatPriv *priv = GET_PRIV (chat);
-	TplEntity       *target;
-	TpWeakRef       *wr;
 
 	if (!priv->id) {
-		return;
+		return G_SOURCE_REMOVE;
 	}
 
 	/* Turn off scrolling temporarily */
 	empathy_theme_adium_scroll (chat->view, FALSE);
 
-	/* Add messages from last conversation */
-	if (priv->handle_type == TP_HANDLE_TYPE_ROOM)
-	  target = tpl_entity_new_from_room_id (priv->id);
-	else
-	  target = tpl_entity_new (priv->id, TPL_ENTITY_CONTACT, NULL, NULL);
+	tpl_log_walker_get_events_async (priv->log_walker, 5,
+	    got_filtered_messages_cb, g_object_ref (chat));
+
+	return G_SOURCE_REMOVE;
+}
+
+static void
+chat_schedule_logs (EmpathyChat *chat)
+{
+	EmpathyChatPriv *priv = GET_PRIV (chat);
+
+	if (priv->retrieving_backlogs)
+		return;
 
 	priv->retrieving_backlogs = TRUE;
-	wr = tp_weak_ref_new (chat, NULL, NULL);
-	tpl_log_manager_get_filtered_events_async (priv->log_manager,
-						   priv->account,
-						   target,
-						   TPL_EVENT_MASK_TEXT,
-						   5,
-						   chat_log_filter,
-						   wr,
-						   got_filtered_messages_cb,
-						   wr);
+	g_timeout_add_full (G_PRIORITY_LOW, 500, /* ms */
+	    (GSourceFunc) chat_add_logs, g_object_ref (chat), g_object_unref);
+}
 
-	g_object_unref (target);
+static void
+chat_view_adjustment_changed_cb (GtkAdjustment *adjustment,
+				 gpointer user_data)
+{
+	EmpathyChat *chat = EMPATHY_CHAT (user_data);
+	EmpathyChatPriv *priv = GET_PRIV (chat);
+	guint page_size;
+
+	if (tpl_log_walker_is_end (priv->log_walker)) {
+		g_signal_handlers_disconnect_by_func (adjustment,
+		    chat_view_adjustment_changed_cb, user_data);
+		return;
+	}
+
+	page_size = (guint) gtk_adjustment_get_page_size (adjustment);
+	if (page_size <= priv->max_page_size)
+		return;
+
+	/* We need to fetch more logs if the page size of the view
+	 * increases, so that there is no empty space at the top.
+	 */
+	if (G_LIKELY (priv->max_page_size != 0))
+		chat_schedule_logs (chat);
+
+	priv->max_page_size = page_size;
+}
+
+static void
+chat_view_adjustment_value_changed_cb (GtkAdjustment *adjustment,
+				       gpointer user_data)
+{
+	EmpathyChat *chat = EMPATHY_CHAT (user_data);
+	EmpathyChatPriv *priv = GET_PRIV (chat);
+	guint lower;
+	guint value;
+
+	if (tpl_log_walker_is_end (priv->log_walker)) {
+		g_signal_handlers_disconnect_by_func (adjustment,
+		    chat_view_adjustment_value_changed_cb, user_data);
+		return;
+	}
+
+	lower = (guint) gtk_adjustment_get_lower (adjustment);
+	value = (guint) gtk_adjustment_get_value (adjustment);
+	if (value != lower)
+		return;
+
+	/* Request for more logs to be fetched if the user hit the
+	 * upper edge of the chat->view.
+	 */
+	chat_schedule_logs (chat);
+}
+
+static gboolean
+chat_scrollable_connect (gpointer user_data)
+{
+	EmpathyChat *chat = EMPATHY_CHAT (user_data);
+	GtkAdjustment *adjustment;
+
+	adjustment = gtk_scrollable_get_vadjustment (
+	    GTK_SCROLLABLE (chat->view));
+
+	g_signal_connect (adjustment, "changed",
+	    G_CALLBACK (chat_view_adjustment_changed_cb), chat);
+	g_signal_connect (adjustment, "value-changed",
+	    G_CALLBACK (chat_view_adjustment_value_changed_cb), chat);
+
+	return G_SOURCE_REMOVE;
 }
 
 static gint
@@ -3278,6 +3394,7 @@ chat_finalize (GObject *object)
 
 	g_object_unref (priv->account_manager);
 	g_object_unref (priv->log_manager);
+	g_object_unref (priv->log_walker);
 
 	if (priv->tp_chat) {
 		g_signal_handlers_disconnect_by_func (priv->tp_chat,
@@ -3335,6 +3452,7 @@ chat_constructed (GObject *object)
 {
 	EmpathyChat *chat = EMPATHY_CHAT (object);
 	EmpathyChatPriv *priv = GET_PRIV (chat);
+	TplEntity *target;
 
 	if (priv->tp_chat != NULL) {
 		TpChannel *channel = TP_CHANNEL (priv->tp_chat);
@@ -3347,6 +3465,16 @@ chat_constructed (GObject *object)
 						    supports_avatars);
 	}
 
+	/* Add messages from last conversation */
+	if (priv->handle_type == TP_HANDLE_TYPE_ROOM)
+		target = tpl_entity_new_from_room_id (priv->id);
+	else
+		target = tpl_entity_new (priv->id, TPL_ENTITY_CONTACT, NULL, NULL);
+
+	priv->log_walker = tpl_log_manager_walk_filtered_events (priv->log_manager, priv->account, target,
+								 TPL_EVENT_MASK_TEXT, chat_log_filter, chat);
+	g_object_unref (target);
+
 	if (priv->handle_type != TP_HANDLE_TYPE_ROOM) {
 		/* First display logs from the logger and then display pending messages */
 		chat_add_logs (chat);



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]