]> git.0d.be Git - empathy.git/blobdiff - libempathy-gtk/empathy-chat.c
coding style fixes
[empathy.git] / libempathy-gtk / empathy-chat.c
index 36b4136ee40154d667c9c80c54c455b97c82fb79..d273a3792536b161095434f2e5c31d7f4a052478 100644 (file)
@@ -1,7 +1,7 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
 /*
  * Copyright (C) 2002-2007 Imendio AB
- * Copyright (C) 2007-2008 Collabora Ltd.
+ * Copyright (C) 2007-2010 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
 #include <string.h>
 #include <stdlib.h>
 
+#undef G_DISABLE_DEPRECATED /* for GCompletion */
 #include <gdk/gdkkeysyms.h>
 #include <glib/gi18n-lib.h>
 #include <gtk/gtk.h>
 
 #include <telepathy-glib/account-manager.h>
 #include <telepathy-glib/util.h>
-#ifdef ENABLE_TPL
 #include <telepathy-logger/log-manager.h>
-#else
-
-#include <libempathy/empathy-log-manager.h>
-#endif /* ENABLE_TPL */
+#include <telepathy-logger/text-event.h>
 #include <libempathy/empathy-contact-list.h>
+#include <libempathy/empathy-gsettings.h>
+#include <libempathy/empathy-keyring.h>
 #include <libempathy/empathy-utils.h>
-#include <libempathy/empathy-dispatcher.h>
+#include <libempathy/empathy-request-util.h>
+#include <libempathy/empathy-chatroom-manager.h>
 
 #include "empathy-chat.h"
-#include "empathy-conf.h"
 #include "empathy-spell.h"
-#include "empathy-contact-list-store.h"
-#include "empathy-contact-list-view.h"
-#include "empathy-contact-menu.h"
+#include "empathy-contact-dialogs.h"
+#include "empathy-individual-store-channel.h"
+#include "empathy-individual-view.h"
+#include "empathy-input-text-view.h"
 #include "empathy-search-bar.h"
 #include "empathy-theme-manager.h"
+#include "empathy-theme-adium.h"
 #include "empathy-smiley-manager.h"
 #include "empathy-ui-utils.h"
 #include "empathy-string-parser.h"
+#include "extensions/extensions.h"
 
 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
 #include <libempathy/empathy-debug.h>
 
-#define CHAT_DIR_CREATE_MODE  (S_IRUSR | S_IWUSR | S_IXUSR)
-#define CHAT_FILE_CREATE_MODE (S_IRUSR | S_IWUSR)
-#define IS_ENTER(v) (v == GDK_Return || v == GDK_ISO_Enter || v == GDK_KP_Enter)
-#define MAX_INPUT_HEIGHT 150
+#define IS_ENTER(v) (v == GDK_KEY_Return || v == GDK_KEY_ISO_Enter || v == GDK_KEY_KP_Enter)
 #define COMPOSING_STOP_TIMEOUT 5
 
 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyChat)
-typedef struct {
+struct _EmpathyChatPriv {
        EmpathyTpChat     *tp_chat;
        TpAccount         *account;
        gchar             *id;
        gchar             *name;
        gchar             *subject;
+       EmpathyContact    *self_contact;
        EmpathyContact    *remote_contact;
        gboolean           show_contacts;
 
-#ifdef ENABLE_TPL
+       GSettings         *gsettings_chat;
+       GSettings         *gsettings_ui;
+
        TplLogManager     *log_manager;
-#else
-       EmpathyLogManager *log_manager;
-#endif /* ENABLE_TPL */
        TpAccountManager  *account_manager;
        GList             *input_history;
        GList             *input_history_current;
@@ -93,6 +92,23 @@ typedef struct {
        gint               contacts_width;
        gboolean           has_input_vscroll;
 
+       /* TRUE if spell checking is enabled, FALSE otherwise.
+        * This is to keep track of the last state of spell checking
+        * when it changes. */
+       gboolean           spell_checking_enabled;
+
+       /* These store the signal handler ids for the enclosed text entry. */
+       gulong             insert_text_id;
+       gulong             delete_range_id;
+       gulong             notify_cursor_position_id;
+
+       /* Source func ID for update_misspelled_words () */
+       guint              update_misspelled_words_id;
+       /* Source func ID for save_paned_pos_timeout () */
+       guint              save_paned_pos_id;
+       /* Source func ID for chat_contacts_visible_timeout_cb () */
+       guint              contacts_visible_id;
+
        GtkWidget         *widget;
        GtkWidget         *hpaned;
        GtkWidget         *vbox_left;
@@ -100,12 +116,14 @@ typedef struct {
        GtkWidget         *scrolled_window_input;
        GtkWidget         *scrolled_window_contacts;
        GtkWidget         *hbox_topic;
+       GtkWidget         *expander_topic;
        GtkWidget         *label_topic;
        GtkWidget         *contact_list_view;
        GtkWidget         *info_bar_vbox;
        GtkWidget         *search_bar;
 
        guint              unread_messages;
+       guint              unread_messages_when_offline;
        /* TRUE if the pending messages can be displayed. This is to avoid to show
         * pending messages *before* messages from logs. (#603980) */
        gboolean           can_show_pending;
@@ -132,7 +150,21 @@ typedef struct {
         * notified again about the already notified pending messages when the
         * messages in tab will be properly shown */
        gboolean           retrieving_backlogs;
-} EmpathyChatPriv;
+       gboolean           sms_channel;
+
+       /* we need to know whether populate-popup happened in response to
+        * the keyboard or the mouse. We can't ask GTK for the most recent
+        * event, because it will be a notify event. Instead we track it here */
+       GdkEventType       most_recent_event_type;
+
+       /* A regex matching our own current nickname in the room, or %NULL if
+        * !empathy_chat_is_room (). */
+       GRegex            *highlight_regex;
+
+       /* TRUE if empathy_chat_is_room () and there are unread highlighted messages.
+        * Cleared by empathy_chat_messages_read (). */
+       gboolean           highlighted;
+};
 
 typedef struct {
        gchar *text; /* Original message that was specified
@@ -144,6 +176,7 @@ typedef struct {
 enum {
        COMPOSING,
        NEW_MESSAGE,
+       PART_COMMAND_ENTERED,
        LAST_SIGNAL
 };
 
@@ -156,11 +189,16 @@ enum {
        PROP_SUBJECT,
        PROP_REMOTE_CONTACT,
        PROP_SHOW_CONTACTS,
+       PROP_SMS_CHANNEL,
+       PROP_N_MESSAGES_SENDING,
+       PROP_NB_UNREAD_MESSAGES,
 };
 
 static guint signals[LAST_SIGNAL] = { 0 };
 
-G_DEFINE_TYPE (EmpathyChat, empathy_chat, GTK_TYPE_BIN);
+G_DEFINE_TYPE (EmpathyChat, empathy_chat, GTK_TYPE_BOX);
+
+static gboolean update_misspelled_words (gpointer data);
 
 static void
 chat_get_property (GObject    *object,
@@ -179,7 +217,7 @@ chat_get_property (GObject    *object,
                g_value_set_object (value, priv->account);
                break;
        case PROP_NAME:
-               g_value_set_string (value, empathy_chat_get_name (chat));
+               g_value_take_string (value, empathy_chat_dup_name (chat));
                break;
        case PROP_ID:
                g_value_set_string (value, priv->id);
@@ -193,6 +231,17 @@ chat_get_property (GObject    *object,
        case PROP_SHOW_CONTACTS:
                g_value_set_boolean (value, priv->show_contacts);
                break;
+       case PROP_SMS_CHANNEL:
+               g_value_set_boolean (value, priv->sms_channel);
+               break;
+       case PROP_N_MESSAGES_SENDING:
+               g_value_set_uint (value,
+                       empathy_chat_get_n_messages_sending (chat));
+               break;
+       case PROP_NB_UNREAD_MESSAGES:
+               g_value_set_uint (value,
+                       empathy_chat_get_nb_unread_messages (chat));
+               break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
                break;
@@ -221,61 +270,41 @@ chat_set_property (GObject      *object,
 }
 
 static void
-chat_connect_channel_reconnected (EmpathyDispatchOperation *dispatch,
-                                 const GError             *error,
-                                 gpointer                  user_data)
-{
-       EmpathyChat *chat = EMPATHY_CHAT (user_data);
-       EmpathyTpChat *tpchat;
-
-       if (error != NULL) {
-               empathy_chat_view_append_event (chat->view,
-                       _("Failed to reconnect this chat"));
-               return;
-       }
-
-       tpchat = EMPATHY_TP_CHAT (
-               empathy_dispatch_operation_get_channel_wrapper (dispatch));
-
-       if (empathy_dispatch_operation_claim (dispatch)) {
-               empathy_chat_set_tp_chat (chat, tpchat);
-       }
-}
-
-static void
-reconnected_connection_ready_cb (TpConnection *connection,
-                       const GError *error,
-                       gpointer user_data)
+account_reconnected (EmpathyChat *chat,
+                       TpAccount *account)
 {
-       EmpathyChat *chat = user_data;
        EmpathyChatPriv *priv = GET_PRIV (chat);
 
-       if (error != NULL) {
-               DEBUG ("connection is not ready: %s", error->message);
-               goto out;
-       }
-
        DEBUG ("Account reconnected, request a new Text channel");
 
+       /* FIXME: Ideally we should ask to handle ourself the channel so we can
+       * report the error if any but this is blocked by
+       * https://bugs.freedesktop.org/show_bug.cgi?id=13422 */
        switch (priv->handle_type) {
                case TP_HANDLE_TYPE_CONTACT:
-                       empathy_dispatcher_chat_with_contact_id (
-                               connection, priv->id,
-                               chat_connect_channel_reconnected,
-                               chat);
+                       if (priv->sms_channel)
+                               empathy_sms_contact_id (
+                                       account, priv->id,
+                                       TP_USER_ACTION_TIME_NOT_USER_ACTION,
+                                       NULL, NULL);
+                       else
+                               empathy_chat_with_contact_id (
+                                       account, priv->id,
+                                       TP_USER_ACTION_TIME_NOT_USER_ACTION,
+                                       NULL, NULL);
                        break;
                case TP_HANDLE_TYPE_ROOM:
-                       empathy_dispatcher_join_muc (connection,
-                               priv->id,
-                               chat_connect_channel_reconnected,
-                               chat);
+                       empathy_join_muc (account, priv->id,
+                               TP_USER_ACTION_TIME_NOT_USER_ACTION);
                        break;
+               case TP_HANDLE_TYPE_NONE:
+               case TP_HANDLE_TYPE_LIST:
+               case TP_HANDLE_TYPE_GROUP:
                default:
                        g_assert_not_reached ();
                        break;
        }
 
-out:
        g_object_unref (chat);
 }
 
@@ -289,21 +318,18 @@ chat_new_connection_cb (TpAccount   *account,
                        EmpathyChat *chat)
 {
        EmpathyChatPriv *priv = GET_PRIV (chat);
-       TpConnection *connection;
 
        if (new_status != TP_CONNECTION_STATUS_CONNECTED)
                return;
 
-       connection = tp_account_get_connection (account);
-
        if (priv->tp_chat != NULL || account != priv->account ||
            priv->handle_type == TP_HANDLE_TYPE_NONE ||
            EMP_STR_EMPTY (priv->id))
                return;
 
        g_object_ref (chat);
-       tp_connection_call_when_ready (connection, reconnected_connection_ready_cb,
-                                  chat);
+
+       account_reconnected (chat, account);
 }
 
 static void
@@ -319,16 +345,50 @@ chat_composing_remove_timeout (EmpathyChat *chat)
        }
 }
 
+static void
+set_chate_state_cb (GObject *source,
+                   GAsyncResult *result,
+                   gpointer user_data)
+{
+       GError *error = NULL;
+
+       if (!tp_text_channel_set_chat_state_finish (TP_TEXT_CHANNEL (source), result,
+               &error)) {
+               DEBUG ("Failed to set chat state: %s", error->message);
+               g_error_free (error);
+       }
+}
+
+static void
+set_chat_state (EmpathyChat *self,
+               TpChannelChatState state)
+{
+       EmpathyChatPriv *priv = GET_PRIV (self);
+
+       if (!tp_proxy_has_interface_by_id (priv->tp_chat,
+               TP_IFACE_QUARK_CHANNEL_INTERFACE_CHAT_STATE))
+               return;
+
+       tp_text_channel_set_chat_state_async (TP_TEXT_CHANNEL (priv->tp_chat), state,
+               set_chate_state_cb, self);
+}
+
 static gboolean
 chat_composing_stop_timeout_cb (EmpathyChat *chat)
 {
        EmpathyChatPriv *priv;
+       gboolean send_chat_states;
 
        priv = GET_PRIV (chat);
 
        priv->composing_stop_timeout_id = 0;
-       empathy_tp_chat_set_state (priv->tp_chat,
-                                  TP_CHANNEL_CHAT_STATE_PAUSED);
+       send_chat_states = g_settings_get_boolean (priv->gsettings_chat,
+                                        EMPATHY_PREFS_CHAT_SEND_CHAT_STATES);
+       if (!send_chat_states) {
+               set_chat_state (chat, TP_CHANNEL_CHAT_STATE_ACTIVE);
+       } else {
+               set_chat_state (chat, TP_CHANNEL_CHAT_STATE_PAUSED);
+       }
 
        return FALSE;
 }
@@ -337,15 +397,21 @@ static void
 chat_composing_start (EmpathyChat *chat)
 {
        EmpathyChatPriv *priv;
+       gboolean send_chat_states;
 
        priv = GET_PRIV (chat);
 
+       send_chat_states = g_settings_get_boolean (priv->gsettings_chat,
+                                         EMPATHY_PREFS_CHAT_SEND_CHAT_STATES);
+       if (!send_chat_states) {
+               return;
+       }
+
        if (priv->composing_stop_timeout_id) {
                /* Just restart the timeout */
                chat_composing_remove_timeout (chat);
        } else {
-               empathy_tp_chat_set_state (priv->tp_chat,
-                                          TP_CHANNEL_CHAT_STATE_COMPOSING);
+               set_chat_state (chat, TP_CHANNEL_CHAT_STATE_COMPOSING);
        }
 
        priv->composing_stop_timeout_id = g_timeout_add_seconds (
@@ -357,13 +423,8 @@ chat_composing_start (EmpathyChat *chat)
 static void
 chat_composing_stop (EmpathyChat *chat)
 {
-       EmpathyChatPriv *priv;
-
-       priv = GET_PRIV (chat);
-
        chat_composing_remove_timeout (chat);
-       empathy_tp_chat_set_state (priv->tp_chat,
-                                  TP_CHANNEL_CHAT_STATE_ACTIVE);
+       set_chat_state (chat, TP_CHANNEL_CHAT_STATE_ACTIVE);
 }
 
 static gint
@@ -642,55 +703,71 @@ chat_input_history_update (EmpathyChat *chat,
        g_free (text);
 }
 
-static void
-chat_command_join_cb (EmpathyDispatchOperation *dispatch,
-                     const GError             *error,
-                     gpointer                  user_data)
-{
-       EmpathyChat *chat = user_data;
-
-       if (error != NULL) {
-               DEBUG ("Error: %s", error->message);
-               empathy_chat_view_append_event (chat->view,
-                       _("Failed to join chat room"));
-       }
-}
-
 typedef struct {
        EmpathyChat *chat;
        gchar *message;
 } ChatCommandMsgData;
 
 static void
-chat_command_msg_cb (EmpathyDispatchOperation *dispatch,
-                             const GError             *error,
+chat_command_msg_cb (GObject *source,
+                             GAsyncResult *result,
                              gpointer                  user_data)
 {
        ChatCommandMsgData *data = user_data;
+       GError *error = NULL;
+       TpChannel *channel;
+
+       channel = tp_account_channel_request_ensure_and_observe_channel_finish (
+                                       TP_ACCOUNT_CHANNEL_REQUEST (source), result, &error);
+
+       if (channel == NULL) {
+               DEBUG ("Failed to get channel: %s", error->message);
+               g_error_free (error);
 
-       if (error != NULL) {
                empathy_chat_view_append_event (data->chat->view,
                        _("Failed to open private chat"));
                goto OUT;
        }
 
-       if (!EMP_STR_EMPTY (data->message)) {
-               EmpathyTpChat *tpchat;
-               EmpathyMessage *message;
+       if (!EMP_STR_EMPTY (data->message) && TP_IS_TEXT_CHANNEL (channel)) {
+               TpTextChannel *text = (TpTextChannel *) channel;
+               TpMessage *msg;
 
-               tpchat = EMPATHY_TP_CHAT (
-                       empathy_dispatch_operation_get_channel_wrapper (dispatch));
+               msg = tp_client_message_new_text (TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL,
+                       data->message);
 
-               message = empathy_message_new (data->message);
-               empathy_tp_chat_send (tpchat, message);
-               g_object_unref (message);
+               tp_text_channel_send_message_async (text, msg, 0, NULL, NULL);
+
+               g_object_unref (msg);
        }
 
+       g_object_unref (channel);
+
 OUT:
        g_free (data->message);
        g_slice_free (ChatCommandMsgData, data);
 }
 
+static gboolean
+nick_command_supported (EmpathyChat *chat)
+{
+       EmpathyChatPriv * priv = GET_PRIV (chat);
+       TpConnection    *connection;
+
+       connection = tp_channel_borrow_connection (TP_CHANNEL (priv->tp_chat));
+       return tp_proxy_has_interface_by_id (connection,
+               EMP_IFACE_QUARK_CONNECTION_INTERFACE_RENAMING);
+}
+
+static gboolean
+part_command_supported (EmpathyChat *chat)
+{
+       EmpathyChatPriv * priv = GET_PRIV (chat);
+
+       return tp_proxy_has_interface_by_id (priv->tp_chat,
+                       TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP);
+}
+
 static void
 chat_command_clear (EmpathyChat *chat,
                    GStrv        strv)
@@ -703,26 +780,30 @@ chat_command_topic (EmpathyChat *chat,
                    GStrv        strv)
 {
        EmpathyChatPriv *priv = GET_PRIV (chat);
-       EmpathyTpChatProperty *property;
-       GValue value = {0, };
 
-       property = empathy_tp_chat_get_property (priv->tp_chat, "subject");
-       if (property == NULL) {
+       if (!empathy_tp_chat_supports_subject (priv->tp_chat)) {
                empathy_chat_view_append_event (chat->view,
                        _("Topic not supported on this conversation"));
                return;
        }
 
-       if (!(property->flags & TP_PROPERTY_FLAG_WRITE)) {
+       if (!empathy_tp_chat_can_set_subject (priv->tp_chat)) {
                empathy_chat_view_append_event (chat->view,
                        _("You are not allowed to change the topic"));
                return;
        }
 
-       g_value_init (&value, G_TYPE_STRING);
-       g_value_set_string (&value, strv[1]);
-       empathy_tp_chat_set_property (priv->tp_chat, "subject", &value);
-       g_value_unset (&value);
+       empathy_tp_chat_set_subject (priv->tp_chat, strv[1]);
+}
+
+void
+empathy_chat_join_muc (EmpathyChat *chat,
+                  const gchar   *room)
+{
+       EmpathyChatPriv *priv = GET_PRIV (chat);
+
+       empathy_join_muc (priv->account, room,
+               empathy_get_current_action_time ());
 }
 
 static void
@@ -730,43 +811,59 @@ chat_command_join (EmpathyChat *chat,
                   GStrv        strv)
 {
        guint i = 0;
-       EmpathyChatPriv *priv = GET_PRIV (chat);
 
        GStrv rooms = g_strsplit_set (strv[1], ", ", -1);
 
+       /* FIXME: Ideally we should ask to handle ourself the channel so we can
+       * report the error if any but this is blocked by
+       * https://bugs.freedesktop.org/show_bug.cgi?id=13422 */
        while (rooms[i] != NULL) {
                /* ignore empty strings */
                if (!EMP_STR_EMPTY (rooms[i])) {
-                       TpConnection *connection;
-
-                       connection = empathy_tp_chat_get_connection (priv->tp_chat);
-                       empathy_dispatcher_join_muc (connection, rooms[i],
-                                                    chat_command_join_cb,
-                                                    chat);
+                       empathy_chat_join_muc (chat, rooms[i]);
                }
                i++;
        }
        g_strfreev (rooms);
 }
 
+static void
+chat_command_part (EmpathyChat *chat,
+                          GStrv        strv)
+{
+       g_signal_emit (chat, signals[PART_COMMAND_ENTERED], 0, strv);
+}
+
 static void
 chat_command_msg_internal (EmpathyChat *chat,
                           const gchar *contact_id,
                           const gchar *message)
 {
        EmpathyChatPriv *priv = GET_PRIV (chat);
-       TpConnection *connection;
        ChatCommandMsgData *data;
+       TpAccountChannelRequest *req;
+       GHashTable *request;
+
+       request = tp_asv_new (
+               TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING, TP_IFACE_CHANNEL_TYPE_TEXT,
+               TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, TP_HANDLE_TYPE_CONTACT,
+               TP_PROP_CHANNEL_TARGET_ID, G_TYPE_STRING, contact_id,
+               NULL);
+
+       req = tp_account_channel_request_new (priv->account, request,
+               empathy_get_current_action_time ());
 
        /* FIXME: We should probably search in members alias. But this
         * is enough for IRC */
        data = g_slice_new (ChatCommandMsgData);
        data->chat = chat;
        data->message = g_strdup (message);
-       connection = empathy_tp_chat_get_connection (priv->tp_chat);
-       empathy_dispatcher_chat_with_contact_id (connection, contact_id,
-                                                chat_command_msg_cb,
-                                                data);
+
+       tp_account_channel_request_ensure_and_observe_channel_async (req,
+               EMPATHY_CHAT_BUS_NAME, NULL, chat_command_msg_cb, data);
+
+       g_object_unref (req);
+       g_hash_table_unref (request);
 }
 
 static void
@@ -785,24 +882,28 @@ chat_command_msg (EmpathyChat *chat,
        chat_command_msg_internal (chat, strv[1], strv[2]);
 }
 
+static void
+callback_for_request_rename (TpProxy *proxy,
+                 const GError *error,
+                 gpointer user_data,
+                 GObject *weak_object)
+{
+       if (error != NULL) {
+               DEBUG ("Call to RequestRename method failed: %s",error->message);
+       }
+}
+
 static void
 chat_command_nick (EmpathyChat *chat,
                   GStrv        strv)
 {
        EmpathyChatPriv *priv = GET_PRIV (chat);
-       TpConnection *connection;
-       GHashTable *new_alias;
-       TpHandle handle;
+       TpProxy *proxy;
 
-       connection = tp_account_get_connection (priv->account);
-       handle = tp_connection_get_self_handle (connection);
-       new_alias = g_hash_table_new (g_direct_hash, g_direct_equal);
-       g_hash_table_insert (new_alias, GUINT_TO_POINTER (handle), strv[1]);
+       proxy = TP_PROXY (tp_account_get_connection (priv->account));
 
-       tp_cli_connection_interface_aliasing_call_set_aliases (connection, -1,
-               new_alias, NULL, NULL, NULL, NULL);
-
-       g_hash_table_destroy (new_alias);
+       emp_cli_connection_interface_renaming_call_request_rename (proxy, -1,
+               strv[1], callback_for_request_rename, NULL, NULL, NULL);
 }
 
 static void
@@ -810,10 +911,30 @@ chat_command_me (EmpathyChat *chat,
                  GStrv        strv)
 {
        EmpathyChatPriv *priv = GET_PRIV (chat);
-       EmpathyMessage *message;
+       TpMessage *message;
+       TpTextChannel *channel;
+
+       channel = (TpTextChannel *) (priv->tp_chat);
+
+       if (!tp_text_channel_supports_message_type (channel,
+                       TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION)) {
+               /* Action message are not supported, 'simulate' the action */
+               gchar *tmp;
+
+               /* The TpChat can't be ready if it doesn't have the self contact */
+               g_assert (priv->self_contact != NULL);
+
+               tmp = g_strdup_printf ("%s %s", empathy_contact_get_alias (priv->self_contact),
+                       strv[1]);
+               message = tp_client_message_new_text (TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL,
+                       tmp);
+               g_free (tmp);
+       }
+       else {
+               message = tp_client_message_new_text (TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION,
+                       strv[1]);
+       }
 
-       message = empathy_message_new (strv[1]);
-       empathy_message_set_tptype (message, TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION);
        empathy_tp_chat_send (priv->tp_chat, message);
        g_object_unref (message);
 }
@@ -823,13 +944,130 @@ chat_command_say (EmpathyChat *chat,
                  GStrv        strv)
 {
        EmpathyChatPriv *priv = GET_PRIV (chat);
-       EmpathyMessage *message;
+       TpMessage *message;
+
+       message = tp_client_message_new_text (TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL,
+               strv[1]);
+       empathy_tp_chat_send (priv->tp_chat, message);
+       g_object_unref (message);
+}
+
+static void
+whois_got_contact_cb (TpConnection *connection,
+                     guint n_contacts,
+                     TpContact * const *contacts,
+                     const gchar * const *requested_ids,
+                     GHashTable *failed_id_errors,
+                     const GError *error,
+                     gpointer user_data,
+                     GObject *weak_object)
+{
+       EmpathyChat *chat = EMPATHY_CHAT (weak_object);
+
+       g_return_if_fail (n_contacts <= 1);
+
+       if (n_contacts == 0) {
+               GHashTableIter iter;
+               gpointer key = NULL, value = NULL;
+               gchar *id;
+               GError *id_error;
+
+               /* tp-glib guarantees that the contacts you requested will be
+                * in failed_id_errors regardless of whether the individual
+                * contact was invalid or the whole operation failed.
+                */
+               g_hash_table_iter_init (&iter, failed_id_errors);
+               g_hash_table_iter_next (&iter, &key, &value);
+               id = key;
+               id_error = value;
+
+               DEBUG ("Error getting TpContact for '%s': %s %u %s",
+                       id, g_quark_to_string (id_error->domain),
+                       id_error->code, id_error->message);
+
+               if (error == NULL) {
+                       /* The specific ID failed. */
+                       gchar *event = g_strdup_printf (
+                               _("“%s” is not a valid contact ID"), id);
+                       empathy_chat_view_append_event (chat->view, event);
+                       g_free (event);
+               }
+               /* Otherwise we're disconnected or something; so the window
+                * will already say ‘Disconnected’, so let's not show anything.
+                */
+       } else {
+               EmpathyContact *empathy_contact;
+               GtkWidget *window;
+
+               g_return_if_fail (contacts[0] != NULL);
+               empathy_contact = empathy_contact_dup_from_tp_contact (
+                       contacts[0]);
+
+               window = gtk_widget_get_toplevel (GTK_WIDGET (chat));
+               /* If we're alive and this command is running, we'd better be
+                * in a window. */
+               g_return_if_fail (window != NULL);
+               g_return_if_fail (gtk_widget_is_toplevel (window));
+               empathy_contact_information_dialog_show (empathy_contact,
+                       GTK_WINDOW (window));
+               g_object_unref (empathy_contact);
+       }
+}
+
+static void
+chat_command_whois (EmpathyChat *chat,
+                   GStrv strv)
+{
+       EmpathyChatPriv *priv = GET_PRIV (chat);
+       TpConnection *conn;
+
+       conn = tp_channel_borrow_connection ((TpChannel *) priv->tp_chat);
+       tp_connection_get_contacts_by_id (conn,
+               /* Element 0 of 'strv' is "whois"; element 1 is the contact ID
+                * entered by the user (including spaces, if any). */
+               1, (const gchar * const *) strv + 1,
+               0, NULL,
+               whois_got_contact_cb, NULL, NULL, G_OBJECT (chat));
+}
+
+static void
+chat_command_whale (EmpathyChat *chat,
+                   GStrv        strv)
+{
+       EmpathyChatPriv *priv = GET_PRIV (chat);
+       TpMessage *message;
+
+       message = tp_client_message_new_text (TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL,
+               "\n\n\n"
+               "•_______________•");
+       empathy_tp_chat_send (priv->tp_chat, message);
+       g_object_unref (message);
+}
 
-       message = empathy_message_new (strv[1]);
+static void
+chat_command_babywhale (EmpathyChat *chat,
+                       GStrv        strv)
+{
+       EmpathyChatPriv *priv = GET_PRIV (chat);
+       TpMessage *message;
+
+       message = tp_client_message_new_text (TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL,
+               "\n"
+               "•_____•");
        empathy_tp_chat_send (priv->tp_chat, message);
        g_object_unref (message);
 }
 
+static void
+chat_command_inspector (EmpathyChat *chat,
+                   GStrv        strv)
+{
+       if (EMPATHY_IS_THEME_ADIUM (chat->view)) {
+               empathy_theme_adium_show_inspector (
+                       EMPATHY_THEME_ADIUM (chat->view));
+       }
+}
+
 static void chat_command_help (EmpathyChat *chat, GStrv strv);
 
 typedef void (*ChatCommandFunc) (EmpathyChat *chat, GStrv strv);
@@ -839,42 +1077,56 @@ typedef struct {
        guint min_parts;
        guint max_parts;
        ChatCommandFunc func;
+       gboolean (*is_supported)(EmpathyChat *chat);
        const gchar *help;
 } ChatCommandItem;
 
 static ChatCommandItem commands[] = {
-       {"clear", 1, 1, chat_command_clear,
+       {"clear", 1, 1, chat_command_clear, NULL,
         N_("/clear: clear all messages from the current conversation")},
 
-       {"topic", 2, 2, chat_command_topic,
+       {"topic", 2, 2, chat_command_topic, NULL,
         N_("/topic <topic>: set the topic of the current conversation")},
 
-       {"join", 2, 2, chat_command_join,
+       {"join", 2, 2, chat_command_join, NULL,
         N_("/join <chat room ID>: join a new chat room")},
 
-       {"j", 2, 2, chat_command_join,
+       {"j", 2, 2, chat_command_join, NULL,
         N_("/j <chat room ID>: join a new chat room")},
 
-       {"query", 2, 3, chat_command_query,
+       /* FIXME: https://bugzilla.gnome.org/show_bug.cgi?id=643295 */
+       {"part", 1, 3, chat_command_part, part_command_supported,
+        N_("/part [<chat room ID>] [<reason>]: leave the chat room, "
+           "by default the current one")},
+
+       {"query", 2, 3, chat_command_query, NULL,
         N_("/query <contact ID> [<message>]: open a private chat")},
 
-       {"msg", 3, 3, chat_command_msg,
+       {"msg", 3, 3, chat_command_msg, NULL,
         N_("/msg <contact ID> <message>: open a private chat")},
 
-       {"nick", 2, 2, chat_command_nick,
+       {"nick", 2, 2, chat_command_nick, nick_command_supported,
         N_("/nick <nickname>: change your nickname on the current server")},
 
-       {"me", 2, 2, chat_command_me,
+       {"me", 2, 2, chat_command_me, NULL,
         N_("/me <message>: send an ACTION message to the current conversation")},
 
-       {"say", 2, 2, chat_command_say,
+       {"say", 2, 2, chat_command_say, NULL,
         N_("/say <message>: send <message> to the current conversation. "
            "This is used to send a message starting with a '/'. For example: "
            "\"/say /join is used to join a new chat room\"")},
 
-       {"help", 1, 2, chat_command_help,
+       {"whois", 2, 2, chat_command_whois, NULL,
+        N_("/whois <contact ID>: display information about a contact")},
+
+       {"help", 1, 2, chat_command_help, NULL,
         N_("/help [<command>]: show all supported commands. "
            "If <command> is defined, show its usage.")},
+
+       {"inspector", 1, 1, chat_command_inspector, NULL, NULL},
+
+       {"whale", 1, 1, chat_command_whale, NULL, NULL},
+       {"babywhale", 1, 1, chat_command_babywhale, NULL, NULL},
 };
 
 static void
@@ -883,6 +1135,10 @@ chat_command_show_help (EmpathyChat     *chat,
 {
        gchar *str;
 
+       if (item->help == NULL) {
+               return;
+       }
+
        str = g_strdup_printf (_("Usage: %s"), _(item->help));
        empathy_chat_view_append_event (chat->view, str);
        g_free (str);
@@ -898,6 +1154,14 @@ chat_command_help (EmpathyChat *chat,
         * strv[1] will be the terminal NULL */
        if (strv[1] == NULL) {
                for (i = 0; i < G_N_ELEMENTS (commands); i++) {
+                       if (commands[i].is_supported != NULL) {
+                               if (!commands[i].is_supported (chat)) {
+                                       continue;
+                               }
+                       }
+                       if (commands[i].help == NULL) {
+                               continue;
+                       }
                        empathy_chat_view_append_event (chat->view,
                                _(commands[i].help));
                }
@@ -906,6 +1170,14 @@ chat_command_help (EmpathyChat *chat,
 
        for (i = 0; i < G_N_ELEMENTS (commands); i++) {
                if (g_ascii_strcasecmp (strv[1], commands[i].prefix) == 0) {
+                       if (commands[i].is_supported != NULL) {
+                               if (!commands[i].is_supported (chat)) {
+                                       break;
+                               }
+                       }
+                       if (commands[i].help == NULL) {
+                               break;
+                       }
                        chat_command_show_help (chat, &commands[i]);
                        return;
                }
@@ -974,7 +1246,7 @@ chat_send (EmpathyChat  *chat,
           const gchar *msg)
 {
        EmpathyChatPriv *priv;
-       EmpathyMessage  *message;
+       TpMessage  *message;
        guint            i;
 
        if (EMP_STR_EMPTY (msg)) {
@@ -1001,6 +1273,11 @@ chat_send (EmpathyChat  *chat,
                        if (c != '\0' && !g_ascii_isspace (c)) {
                                continue;
                        }
+                       if (commands[i].is_supported != NULL) {
+                               if (!commands[i].is_supported (chat)) {
+                                       continue;
+                               }
+                       }
 
                        /* We can't use g_strsplit here because it does
                         * not deal correctly if we have more than one space
@@ -1039,7 +1316,8 @@ chat_send (EmpathyChat  *chat,
                }
        }
 
-       message = empathy_message_new (msg);
+       message = tp_client_message_new_text (TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL,
+               msg);
        empathy_tp_chat_send (priv->tp_chat, message);
        g_object_unref (message);
 }
@@ -1047,13 +1325,10 @@ chat_send (EmpathyChat  *chat,
 static void
 chat_input_text_view_send (EmpathyChat *chat)
 {
-       EmpathyChatPriv *priv;
        GtkTextBuffer  *buffer;
        GtkTextIter     start, end;
        gchar          *msg;
 
-       priv = GET_PRIV (chat);
-
        buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
 
        gtk_text_buffer_get_bounds (buffer, &start, &end);
@@ -1129,191 +1404,501 @@ chat_state_changed_cb (EmpathyTpChat      *tp_chat,
        }
 }
 
-static void
-chat_message_received (EmpathyChat *chat, EmpathyMessage *message)
+static GRegex *
+get_highlight_regex_for (const gchar *name)
 {
-       EmpathyChatPriv *priv = GET_PRIV (chat);
-       EmpathyContact  *sender;
+       GRegex *regex;
+       gchar *name_esc, *pattern;
+       GError *error = NULL;
 
-       sender = empathy_message_get_sender (message);
+       name_esc = g_regex_escape_string (name, -1);
+       pattern = g_strdup_printf ("\\b%s\\b", name_esc);
+       regex = g_regex_new (pattern, G_REGEX_CASELESS | G_REGEX_OPTIMIZE, 0,
+               &error);
 
-       DEBUG ("Appending new message from %s (%d)",
-               empathy_contact_get_name (sender),
-               empathy_contact_get_handle (sender));
+       if (regex == NULL) {
+               DEBUG ("couldn't compile regex /%s/: %s", pattern,
+                       error->message);
 
-       empathy_chat_view_append_message (chat->view, message);
+               g_error_free (error);
+       }
 
-       /* We received a message so the contact is no longer composing */
-       chat_state_changed_cb (priv->tp_chat, sender,
-                              TP_CHANNEL_CHAT_STATE_ACTIVE,
-                              chat);
+       g_free (pattern);
+       g_free (name_esc);
 
-       priv->unread_messages++;
-       g_signal_emit (chat, signals[NEW_MESSAGE], 0, message);
+       return regex;
 }
 
+/* Called when priv->self_contact changes, or priv->self_contact:alias changes.
+ * Only connected if empathy_chat_is_room() is TRUE, for obvious-ish reasons.
+ */
 static void
-chat_message_received_cb (EmpathyTpChat  *tp_chat,
-                         EmpathyMessage *message,
-                         EmpathyChat    *chat)
+chat_self_contact_alias_changed_cb (EmpathyChat *chat)
 {
-       chat_message_received (chat, message);
-}
+       EmpathyChatPriv *priv = GET_PRIV (chat);
 
-static void
-chat_send_error_cb (EmpathyTpChat          *tp_chat,
-                   const gchar            *message_body,
-                   TpChannelTextSendError  error_code,
-                   EmpathyChat            *chat)
-{
-       const gchar *error;
-       gchar       *str;
+       tp_clear_pointer (&priv->highlight_regex, g_regex_unref);
 
-       switch (error_code) {
-       case TP_CHANNEL_TEXT_SEND_ERROR_OFFLINE:
-               error = _("offline");
-               break;
-       case TP_CHANNEL_TEXT_SEND_ERROR_INVALID_CONTACT:
-               error = _("invalid contact");
-               break;
-       case TP_CHANNEL_TEXT_SEND_ERROR_PERMISSION_DENIED:
-               error = _("permission denied");
-               break;
-       case TP_CHANNEL_TEXT_SEND_ERROR_TOO_LONG:
-               error = _("too long message");
-               break;
-       case TP_CHANNEL_TEXT_SEND_ERROR_NOT_IMPLEMENTED:
-               error = _("not implemented");
-               break;
-       default:
-               error = _("unknown");
-               break;
-       }
+       if (priv->self_contact != NULL) {
+               const gchar *alias = empathy_contact_get_alias (priv->self_contact);
 
-       str = g_strdup_printf (_("Error sending message '%s': %s"),
-                              message_body,
-                              error);
-       empathy_chat_view_append_event (chat->view, str);
-       g_free (str);
+               g_return_if_fail (alias != NULL);
+               priv->highlight_regex = get_highlight_regex_for (alias);
+       }
 }
 
-static void
-chat_property_changed_cb (EmpathyTpChat *tp_chat,
-                         const gchar   *name,
-                         GValue        *value,
-                         EmpathyChat   *chat)
+static gboolean
+chat_should_highlight (EmpathyChat *chat,
+       EmpathyMessage *message)
 {
        EmpathyChatPriv *priv = GET_PRIV (chat);
+       const gchar   *msg;
+       TpChannelTextMessageFlags flags;
 
-       if (!tp_strdiff (name, "subject")) {
-               g_free (priv->subject);
-               priv->subject = g_value_dup_string (value);
-               g_object_notify (G_OBJECT (chat), "subject");
+       g_return_val_if_fail (EMPATHY_IS_MESSAGE (message), FALSE);
 
-               if (EMP_STR_EMPTY (priv->subject)) {
-                       gtk_widget_hide (priv->hbox_topic);
-               } else {
-                       gchar *markup_text;
+       if (!empathy_chat_is_room (chat)) {
+               return FALSE;
+       }
 
-                       markup_text = empathy_add_link_markup (priv->subject);
-                       gtk_label_set_markup (GTK_LABEL (priv->label_topic), markup_text);
-                       g_free (markup_text);
+       if (!empathy_message_is_incoming (message)) {
+               return FALSE;
+       }
 
-                       gtk_widget_show (priv->hbox_topic);
-               }
-               if (priv->block_events_timeout_id == 0) {
-                       gchar *str;
+       msg = empathy_message_get_body (message);
+       if (!msg) {
+               return FALSE;
+       }
 
-                       if (!EMP_STR_EMPTY (priv->subject)) {
-                               str = g_strdup_printf (_("Topic set to: %s"), priv->subject);
-                       } else {
-                               str = g_strdup (_("No topic defined"));
-                       }
-                       empathy_chat_view_append_event (EMPATHY_CHAT (chat)->view, str);
-                       g_free (str);
-               }
+       flags = empathy_message_get_flags (message);
+       if (flags & TP_CHANNEL_TEXT_MESSAGE_FLAG_SCROLLBACK) {
+               /* FIXME: Ideally we shouldn't highlight scrollback messages only if they
+                * have already been received by the user before (and so are in the logs) */
+               return FALSE;
        }
-       else if (!tp_strdiff (name, "name")) {
-               g_free (priv->name);
-               priv->name = g_value_dup_string (value);
-               g_object_notify (G_OBJECT (chat), "name");
+
+       if (priv->highlight_regex == NULL) {
+               return FALSE;
        }
+
+       return g_regex_match (priv->highlight_regex, msg, 0, NULL);
 }
 
 static void
-chat_input_text_buffer_changed_cb (GtkTextBuffer *buffer,
-                                   EmpathyChat    *chat)
+chat_message_received (EmpathyChat *chat,
+       EmpathyMessage *message,
+       gboolean pending)
 {
-       GtkTextIter     start, end;
-       gchar          *str;
-       gboolean        spell_checker = FALSE;
+       EmpathyChatPriv *priv = GET_PRIV (chat);
+       EmpathyContact  *sender;
 
-       if (gtk_text_buffer_get_char_count (buffer) == 0) {
-               chat_composing_stop (chat);
-       } else {
-               chat_composing_start (chat);
-       }
+       sender = empathy_message_get_sender (message);
 
-       empathy_conf_get_bool (empathy_conf_get (),
-                           EMPATHY_PREFS_CHAT_SPELL_CHECKER_ENABLED,
-                           &spell_checker);
+       if (empathy_message_is_edit (message)) {
+               DEBUG ("Editing message '%s' to '%s'",
+                       empathy_message_get_supersedes (message),
+                       empathy_message_get_body (message));
 
-       gtk_text_buffer_get_start_iter (buffer, &start);
+               empathy_chat_view_edit_message (chat->view, message);
+       } else {
+               gboolean should_highlight = chat_should_highlight (chat, message);
 
-       if (!spell_checker) {
-               gtk_text_buffer_get_end_iter (buffer, &end);
-               gtk_text_buffer_remove_tag_by_name (buffer, "misspelled", &start, &end);
+               if (should_highlight) {
+                       priv->highlighted = TRUE;
+               }
+
+               DEBUG ("Appending new message '%s' from %s (%d)",
+                       empathy_message_get_token (message),
+                       empathy_contact_get_alias (sender),
+                       empathy_contact_get_handle (sender));
+
+               empathy_chat_view_append_message (chat->view, message, should_highlight);
+
+               if (empathy_message_is_incoming (message)) {
+                       priv->unread_messages++;
+                       g_object_notify (G_OBJECT (chat), "nb-unread-messages");
+               }
+
+               g_signal_emit (chat, signals[NEW_MESSAGE], 0, message, pending,
+                              should_highlight);
+       }
+
+       /* We received a message so the contact is no longer
+        * composing */
+       chat_state_changed_cb (priv->tp_chat, sender,
+                              TP_CHANNEL_CHAT_STATE_ACTIVE,
+                              chat);
+}
+
+static void
+chat_message_received_cb (EmpathyTpChat  *tp_chat,
+                         EmpathyMessage *message,
+                         EmpathyChat    *chat)
+{
+       chat_message_received (chat, message, FALSE);
+}
+
+static void
+chat_message_acknowledged_cb (EmpathyTpChat  *tp_chat,
+                             EmpathyMessage *message,
+                             EmpathyChat    *chat)
+{
+       EmpathyChatPriv *priv = GET_PRIV (chat);
+
+       empathy_chat_view_message_acknowledged (chat->view,
+           message);
+
+       if (!empathy_message_is_edit (message)) {
+               priv->unread_messages--;
+               g_object_notify (G_OBJECT (chat), "nb-unread-messages");
+       }
+}
+
+static void
+append_balance_error (EmpathyChat *chat,
+                     const gchar *message_body)
+{
+       EmpathyChatPriv *priv = GET_PRIV (chat);
+       TpConnection *conn = tp_channel_borrow_connection (TP_CHANNEL (priv->tp_chat));
+       const gchar *uri = tp_connection_get_balance_uri (conn);
+       const gchar *error = _("insufficient balance to send message");
+       gchar *str, *str_markup = NULL;
+
+       if (message_body != NULL) {
+               str = g_strdup_printf (_("Error sending message '%s': %s"), message_body, error);
+       } else {
+               str = g_strdup_printf (_("Error sending message: %s"), error);
+       }
+
+       if (!tp_str_empty (uri)) {
+               /* translators: error used when user doesn't have enough credit on his
+                * account to send the message. */
+               gchar *markup_error = g_strdup_printf (_("insufficient balance to send message."
+                                                        " <a href='%s'>Top up</a>."), uri);
+
+               if (message_body != NULL) {
+                       gchar *escaped_body = g_markup_escape_text (message_body, -1);
+
+                       str_markup = g_strdup_printf (_("Error sending message '%s': %s"),
+                               escaped_body, markup_error);
+
+                       g_free (escaped_body);
+               } else {
+                       str_markup = g_strdup_printf (_("Error sending message: %s"), markup_error);
+               }
+
+               g_free (markup_error);
+       }
+
+       if (str_markup != NULL)
+               empathy_chat_view_append_event_markup (chat->view, str_markup, str);
+       else
+               empathy_chat_view_append_event (chat->view, str);
+
+       g_free (str);
+       g_free (str_markup);
+}
+
+static void
+chat_send_error_cb (EmpathyTpChat          *tp_chat,
+                   const gchar            *message_body,
+                   TpChannelTextSendError  error_code,
+                   const gchar            *dbus_error,
+                   EmpathyChat            *chat)
+{
+       const gchar *error = NULL;
+       gchar       *str;
+
+       if (!tp_strdiff (dbus_error, TP_ERROR_STR_INSUFFICIENT_BALANCE)) {
+               append_balance_error (chat, message_body);
                return;
+       } else if (!tp_strdiff (dbus_error, TP_ERROR_STR_NOT_CAPABLE)) {
+               error = _("not capable");
        }
 
-       if (!empathy_spell_supported ()) {
+       if (error == NULL) {
+               /* if we didn't find a dbus-error, try the old error */
+               switch (error_code) {
+               case TP_CHANNEL_TEXT_SEND_ERROR_OFFLINE:
+                       error = _("offline");
+                       break;
+               case TP_CHANNEL_TEXT_SEND_ERROR_INVALID_CONTACT:
+                       error = _("invalid contact");
+                       break;
+               case TP_CHANNEL_TEXT_SEND_ERROR_PERMISSION_DENIED:
+                       error = _("permission denied");
+                       break;
+               case TP_CHANNEL_TEXT_SEND_ERROR_TOO_LONG:
+                       error = _("too long message");
+                       break;
+               case TP_CHANNEL_TEXT_SEND_ERROR_NOT_IMPLEMENTED:
+                       error = _("not implemented");
+                       break;
+               case TP_CHANNEL_TEXT_SEND_ERROR_UNKNOWN:
+               default:
+                       error = _("unknown");
+                       break;
+               }
+       }
+
+       if (message_body != NULL) {
+                       str = g_strdup_printf (_("Error sending message '%s': %s"),
+                               message_body, error);
+       }
+       else {
+                       str = g_strdup_printf (_("Error sending message: %s"), error);
+       }
+
+       empathy_chat_view_append_event (chat->view, str);
+       g_free (str);
+}
+
+static void
+chat_topic_label_size_allocate_cb (GtkLabel *label,
+                                  GtkAllocation *allocation,
+                                  EmpathyChat *chat)
+{
+       EmpathyChatPriv *priv = GET_PRIV (chat);
+
+       if (!gtk_label_get_line_wrap (label)) {
+               if (pango_layout_is_ellipsized (gtk_label_get_layout (label)))
+                       gtk_widget_show (priv->expander_topic);
+               else
+                       gtk_widget_hide (priv->expander_topic);
+
                return;
        }
+}
 
-       /* NOTE: this is really inefficient, we shouldn't have to
-          reiterate the whole buffer each time and check each work
-          every time. */
-       while (TRUE) {
-               gboolean correct = FALSE;
+static void
+chat_topic_expander_activate_cb (GtkExpander *expander,
+                                GParamSpec *param_spec,
+                                EmpathyChat *chat)
+{
+       EmpathyChatPriv *priv = GET_PRIV (chat);
 
-               /* if at start */
-               if (gtk_text_iter_is_start (&start)) {
-                       end = start;
+       if (gtk_expander_get_expanded (expander)) {
+               gtk_label_set_ellipsize (GTK_LABEL (priv->label_topic), PANGO_ELLIPSIZE_NONE);
+               gtk_label_set_line_wrap (GTK_LABEL (priv->label_topic), TRUE);
+       } else {
+               gtk_label_set_ellipsize (GTK_LABEL (priv->label_topic), PANGO_ELLIPSIZE_END);
+               gtk_label_set_line_wrap (GTK_LABEL (priv->label_topic), FALSE);
+       }
+}
 
-                       if (!gtk_text_iter_forward_word_end (&end)) {
-                               /* no whole word yet */
-                               break;
-                       }
+static void
+chat_subject_changed_cb (EmpathyChat *chat)
+{
+       EmpathyChatPriv *priv = GET_PRIV (chat);
+
+               g_free (priv->subject);
+               priv->subject = g_strdup (empathy_tp_chat_get_subject (priv->tp_chat));
+               g_object_notify (G_OBJECT (chat), "subject");
+
+               if (EMP_STR_EMPTY (priv->subject)) {
+                       gtk_widget_hide (priv->hbox_topic);
                } else {
-                       if (!gtk_text_iter_forward_word_end (&end)) {
-                               /* must be the end of the buffer */
-                               break;
+                       gchar *markup_topic;
+                       gchar *markup_text;
+
+                       markup_topic = empathy_add_link_markup (priv->subject);
+                       markup_text = g_strdup_printf ("<span weight=\"bold\">%s</span> %s",
+                               _("Topic:"), markup_topic);
+
+                       gtk_label_set_markup (GTK_LABEL (priv->label_topic), markup_text);
+                       g_free (markup_text);
+                       g_free (markup_topic);
+
+                       gtk_widget_show (priv->hbox_topic);
+               }
+               if (priv->block_events_timeout_id == 0) {
+                       gchar *str = NULL;
+
+                       if (!EMP_STR_EMPTY (priv->subject)) {
+                               const gchar *actor = empathy_tp_chat_get_subject_actor (priv->tp_chat);
+
+                               if (tp_str_empty (actor)) {
+                                       str = g_strdup_printf (_("Topic set to: %s"), priv->subject);
+                               } else {
+                                       str = g_strdup_printf (_("Topic set by %s to: %s"),
+                                                              actor, priv->subject);
+                               }
+                       } else if (empathy_tp_chat_supports_subject (priv->tp_chat)) {
+                               /* No need to display this 'event' is no topic can be defined anyway */
+                               str = g_strdup (_("No topic defined"));
                        }
 
-                       start = end;
-                       gtk_text_iter_backward_word_start (&start);
+                       if (str != NULL) {
+                               empathy_chat_view_append_event (EMPATHY_CHAT (chat)->view, str);
+                               g_free (str);
+                       }
+               }
+}
+
+static void
+chat_title_changed_cb (EmpathyChat *chat)
+{
+       EmpathyChatPriv *priv = GET_PRIV (chat);
+
+               g_free (priv->name);
+               priv->name = g_strdup (empathy_tp_chat_get_title (priv->tp_chat));
+               g_object_notify (G_OBJECT (chat), "name");
+}
+
+static gboolean
+chat_input_text_get_word_from_iter (GtkTextIter   *iter,
+                                    GtkTextIter   *start,
+                                    GtkTextIter   *end)
+{
+       GtkTextIter word_start = *iter;
+       GtkTextIter word_end = *iter;
+       GtkTextIter tmp;
+
+       if (gtk_text_iter_inside_word (&word_end) &&
+                       !gtk_text_iter_ends_word (&word_end)) {
+               gtk_text_iter_forward_word_end (&word_end);
+       }
+
+       tmp = word_end;
+
+       if (gtk_text_iter_get_char (&tmp) == '\'') {
+               gtk_text_iter_forward_char (&tmp);
+
+               if (g_unichar_isalpha (gtk_text_iter_get_char (&tmp))) {
+                       gtk_text_iter_forward_word_end (&word_end);
+               }
+       }
+
+
+       if (gtk_text_iter_inside_word (&word_start) ||
+                       gtk_text_iter_ends_word (&word_start)) {
+               if (!gtk_text_iter_starts_word (&word_start) ||
+                               gtk_text_iter_equal (&word_start, &word_end)) {
+                       gtk_text_iter_backward_word_start (&word_start);
                }
 
+               tmp = word_start;
+               gtk_text_iter_backward_char (&tmp);
+
+               if (gtk_text_iter_get_char (&tmp) == '\'') {
+                       gtk_text_iter_backward_char (&tmp);
+
+                       if (g_unichar_isalpha (gtk_text_iter_get_char (&tmp))) {
+                               gtk_text_iter_backward_word_start (&word_start);
+                       }
+               }
+       }
+
+       *start = word_start;
+       *end = word_end;
+       return TRUE;
+}
+
+static void
+chat_input_text_buffer_insert_text_cb (GtkTextBuffer *buffer,
+                                       GtkTextIter   *location,
+                                       gchar         *text,
+                                       gint           len,
+                                       EmpathyChat   *chat)
+{
+       GtkTextIter iter, pos;
+
+       /* Remove all misspelled tags in the inserted text.
+        * This happens when text is inserted within a misspelled word. */
+       gtk_text_buffer_get_iter_at_offset (buffer, &iter,
+                                           gtk_text_iter_get_offset (location) - len);
+       gtk_text_buffer_remove_tag_by_name (buffer, "misspelled",
+                                           &iter, location);
+
+       gtk_text_buffer_get_iter_at_mark (buffer, &pos, gtk_text_buffer_get_insert (buffer));
+
+       do {
+               GtkTextIter start, end;
+               gchar *str;
+
+               if (!chat_input_text_get_word_from_iter (&iter, &start, &end))
+                       continue;
+
                str = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
 
-               /* spell check string if not a command */
-               if (str[0] != '/') {
-                       correct = empathy_spell_check (str);
+               if (gtk_text_iter_in_range (&pos, &start, &end) ||
+                               gtk_text_iter_equal (&pos, &end) ||
+                               empathy_spell_check (str)) {
+                       gtk_text_buffer_remove_tag_by_name (buffer, "misspelled", &start, &end);
                } else {
-                       correct = TRUE;
+                       gtk_text_buffer_apply_tag_by_name (buffer, "misspelled", &start, &end);
                }
 
-               if (!correct) {
-                       gtk_text_buffer_apply_tag_by_name (buffer, "misspelled", &start, &end);
+               g_free (str);
+
+       } while (gtk_text_iter_forward_word_end (&iter) &&
+                gtk_text_iter_compare (&iter, location) <= 0);
+}
+
+static void
+chat_input_text_buffer_delete_range_cb (GtkTextBuffer *buffer,
+                                        GtkTextIter   *start,
+                                        GtkTextIter   *end,
+                                        EmpathyChat   *chat)
+{
+       GtkTextIter word_start, word_end;
+
+       if (chat_input_text_get_word_from_iter (start, &word_start, &word_end)) {
+               gtk_text_buffer_remove_tag_by_name (buffer, "misspelled",
+                                                   &word_start, &word_end);
+       }
+}
+
+static void
+chat_input_text_buffer_changed_cb (GtkTextBuffer *buffer,
+                                   EmpathyChat    *chat)
+{
+       if (gtk_text_buffer_get_char_count (buffer) == 0) {
+               chat_composing_stop (chat);
+       } else {
+               chat_composing_start (chat);
+       }
+}
+
+static void
+chat_input_text_buffer_notify_cursor_position_cb (GtkTextBuffer *buffer,
+                                                  GParamSpec    *pspec,
+                                                  EmpathyChat    *chat)
+{
+       GtkTextIter pos;
+       GtkTextIter prev_pos;
+       GtkTextIter word_start;
+       GtkTextIter word_end;
+       GtkTextMark *mark;
+       gchar *str;
+
+       mark = gtk_text_buffer_get_mark (buffer, "previous-cursor-position");
+
+       gtk_text_buffer_get_iter_at_mark (buffer, &pos,
+                                         gtk_text_buffer_get_insert (buffer));
+       gtk_text_buffer_get_iter_at_mark (buffer, &prev_pos, mark);
+
+       if (!chat_input_text_get_word_from_iter (&prev_pos, &word_start, &word_end))
+               goto out;
+
+       if (!gtk_text_iter_in_range (&pos, &word_start, &word_end) &&
+                       !gtk_text_iter_equal (&pos, &word_end)) {
+               str = gtk_text_buffer_get_text (buffer,
+                                       &word_start, &word_end, FALSE);
+
+               if (!empathy_spell_check (str)) {
+                       gtk_text_buffer_apply_tag_by_name (buffer,
+                                       "misspelled", &word_start, &word_end);
                } else {
-                       gtk_text_buffer_remove_tag_by_name (buffer, "misspelled", &start, &end);
+                       gtk_text_buffer_remove_tag_by_name (buffer,
+                                       "misspelled", &word_start, &word_end);
                }
 
                g_free (str);
-
-               /* set start iter to the end iters position */
-               start = end;
        }
+
+out:
+       gtk_text_buffer_move_mark (buffer, mark, &pos);
 }
 
 static gboolean
@@ -1335,17 +1920,19 @@ chat_input_key_press_event_cb (GtkWidget   *widget,
 
        priv = GET_PRIV (chat);
 
+       priv->most_recent_event_type = event->type;
+
        /* Catch ctrl+up/down so we can traverse messages we sent */
        if ((event->state & GDK_CONTROL_MASK) &&
-           (event->keyval == GDK_Up ||
-            event->keyval == GDK_Down)) {
+           (event->keyval == GDK_KEY_Up ||
+            event->keyval == GDK_KEY_Down)) {
                GtkTextBuffer *buffer;
                const gchar   *str;
 
                buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
                chat_input_history_update (chat, buffer);
 
-               if (event->keyval == GDK_Up) {
+               if (event->keyval == GDK_KEY_Up) {
                        str = chat_input_history_get_next (chat);
                } else {
                        str = chat_input_history_get_prev (chat);
@@ -1374,8 +1961,8 @@ chat_input_key_press_event_cb (GtkWidget   *widget,
                 */
 
                view = GTK_TEXT_VIEW (chat->input_text_view);
-               if (gtk_im_context_filter_keypress (view->im_context, event)) {
-                       GTK_TEXT_VIEW (chat->input_text_view)->need_im_reset = TRUE;
+               if (gtk_text_view_im_context_filter_keypress (view, event)) {
+                       gtk_text_view_reset_im_context (view);
                        return TRUE;
                }
 
@@ -1391,24 +1978,24 @@ chat_input_key_press_event_cb (GtkWidget   *widget,
                return FALSE;
        }
        if (!(event->state & GDK_CONTROL_MASK) &&
-           event->keyval == GDK_Page_Up) {
+           event->keyval == GDK_KEY_Page_Up) {
                adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (text_view_sw));
                gtk_adjustment_set_value (adj, gtk_adjustment_get_value (adj) - gtk_adjustment_get_page_size (adj));
                return TRUE;
        }
        if ((event->state & GDK_CONTROL_MASK) != GDK_CONTROL_MASK &&
-           event->keyval == GDK_Page_Down) {
+           event->keyval == GDK_KEY_Page_Down) {
                adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (text_view_sw));
                val = MIN (gtk_adjustment_get_value (adj) + gtk_adjustment_get_page_size (adj),
                           gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj));
                gtk_adjustment_set_value (adj, val);
                return TRUE;
        }
-       if (event->keyval == GDK_Escape) {
+       if (event->keyval == GDK_KEY_Escape) {
                empathy_search_bar_hide (EMPATHY_SEARCH_BAR (priv->search_bar));
        }
        if (!(event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) &&
-           event->keyval == GDK_Tab) {
+           event->keyval == GDK_KEY_Tab) {
                GtkTextBuffer *buffer;
                GtkTextIter    start, current;
                gchar         *nick, *completed;
@@ -1438,7 +2025,6 @@ chat_input_key_press_event_cb (GtkWidget   *widget,
                if (completed) {
                        guint        len;
                        const gchar *text;
-                       gchar       *complete_char = NULL;
                        GString     *message = NULL;
                        GList       *l;
 
@@ -1453,7 +2039,7 @@ chat_input_key_press_event_cb (GtkWidget   *widget,
                                 * which might be cased all wrong.
                                 * Fixes #120876
                                 * */
-                               text = empathy_contact_get_name (completed_list->data);
+                               text = empathy_contact_get_alias (completed_list->data);
                        } else {
                                text = completed;
 
@@ -1463,7 +2049,7 @@ chat_input_key_press_event_cb (GtkWidget   *widget,
                                 * */
                                 message = g_string_new ("");
                                 for (l = completed_list; l != NULL; l = l->next) {
-                                       g_string_append (message, empathy_contact_get_name (l->data));
+                                       g_string_append (message, empathy_contact_get_alias (l->data));
                                        g_string_append (message, " - ");
                                 }
                                 empathy_chat_view_append_event (chat->view, message->str);
@@ -1472,16 +2058,20 @@ chat_input_key_press_event_cb (GtkWidget   *widget,
 
                        gtk_text_buffer_insert_at_cursor (buffer, text, strlen (text));
 
-                       if (len == 1 && is_start_of_buffer &&
-                           empathy_conf_get_string (empathy_conf_get (),
-                                                    EMPATHY_PREFS_CHAT_NICK_COMPLETION_CHAR,
-                                                    &complete_char) &&
-                           complete_char != NULL) {
+                       if (len == 1 && is_start_of_buffer) {
+                           gchar *complete_char;
+
+                           complete_char = g_settings_get_string (
+                                   priv->gsettings_chat,
+                                   EMPATHY_PREFS_CHAT_NICK_COMPLETION_CHAR);
+
+                           if (complete_char != NULL) {
                                gtk_text_buffer_insert_at_cursor (buffer,
                                                                  complete_char,
                                                                  strlen (complete_char));
                                gtk_text_buffer_insert_at_cursor (buffer, " ", 1);
                                g_free (complete_char);
+                           }
                        }
 
                        g_free (completed);
@@ -1508,40 +2098,6 @@ chat_text_view_focus_in_event_cb (GtkWidget  *widget,
        return TRUE;
 }
 
-static gboolean
-chat_input_set_size_request_idle (gpointer sw)
-{
-       gtk_widget_set_size_request (sw, -1, MAX_INPUT_HEIGHT);
-
-       return FALSE;
-}
-
-static void
-chat_input_size_request_cb (GtkWidget      *widget,
-                           GtkRequisition *requisition,
-                           EmpathyChat    *chat)
-{
-       EmpathyChatPriv *priv = GET_PRIV (chat);
-       GtkWidget       *sw;
-
-       sw = gtk_widget_get_parent (widget);
-       if (requisition->height >= MAX_INPUT_HEIGHT && !priv->has_input_vscroll) {
-               g_idle_add (chat_input_set_size_request_idle, sw);
-               gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
-                                               GTK_POLICY_NEVER,
-                                               GTK_POLICY_ALWAYS);
-               priv->has_input_vscroll = TRUE;
-       }
-
-       if (requisition->height < MAX_INPUT_HEIGHT && priv->has_input_vscroll) {
-               gtk_widget_set_size_request (sw, -1, -1);
-               gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
-                                               GTK_POLICY_NEVER,
-                                               GTK_POLICY_NEVER);
-               priv->has_input_vscroll = FALSE;
-       }
-}
-
 static void
 chat_input_realize_cb (GtkWidget   *widget,
                       EmpathyChat *chat)
@@ -1552,6 +2108,14 @@ chat_input_realize_cb (GtkWidget   *widget,
        }
 }
 
+static void
+chat_input_has_focus_notify_cb (GtkWidget   *widget,
+                               GParamSpec  *pspec,
+                               EmpathyChat *chat)
+{
+       empathy_chat_view_focus_toggled (chat->view, gtk_widget_has_focus (widget));
+}
+
 static void
 chat_insert_smiley_activate_cb (EmpathySmileyManager *manager,
                                EmpathySmiley        *smiley,
@@ -1614,33 +2178,196 @@ chat_spelling_menu_activate_cb (GtkMenuItem     *menu_item,
                                gtk_menu_item_get_label (menu_item));
 }
 
+
+static GtkWidget *
+chat_spelling_build_suggestions_menu (const gchar *code,
+                                     EmpathyChatSpell *chat_spell)
+{
+       GList     *suggestions, *l;
+       GtkWidget *menu, *menu_item;
+
+       suggestions = empathy_spell_get_suggestions (code, chat_spell->word);
+       if (suggestions == NULL)
+               return NULL;
+
+       menu = gtk_menu_new ();
+       for (l = suggestions; l; l = l->next) {
+               menu_item = gtk_menu_item_new_with_label (l->data);
+               g_signal_connect (G_OBJECT (menu_item), "activate",
+                                 G_CALLBACK (chat_spelling_menu_activate_cb),
+                                 chat_spell);
+               gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
+       }
+       empathy_spell_free_suggestions (suggestions);
+
+       gtk_widget_show_all (menu);
+
+       return menu;
+}
+
 static GtkWidget *
 chat_spelling_build_menu (EmpathyChatSpell *chat_spell)
 {
-    GtkWidget *menu, *menu_item;
-    GList     *suggestions, *l;
+       GtkWidget *menu, *submenu, *item;
+       GList     *codes, *l;
+
+       codes = empathy_spell_get_enabled_language_codes ();
+       g_assert (codes != NULL);
+
+       if (g_list_length (codes) > 1) {
+               menu = gtk_menu_new ();
+
+               for (l = codes; l; l = l->next) {
+                       const gchar *code, *name;
+
+                       code = l->data;
+                       name = empathy_spell_get_language_name (code);
+                       if (!name)
+                               continue;
+
+                       item = gtk_image_menu_item_new_with_label (name);
+
+                       submenu = chat_spelling_build_suggestions_menu (
+                                       code, chat_spell);
+                       if (submenu == NULL)
+                               gtk_widget_set_sensitive (item, FALSE);
+                       else
+                               gtk_menu_item_set_submenu (GTK_MENU_ITEM (item),
+                                                          submenu);
+                       gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
+               }
+       } else {
+               menu = chat_spelling_build_suggestions_menu (codes->data,
+                                                            chat_spell);
+               if (menu == NULL) {
+                       menu = gtk_menu_new ();
+                       item = gtk_menu_item_new_with_label (_("(No Suggestions)"));
+                       gtk_widget_set_sensitive (item, FALSE);
+                       gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+               }
+       }
+       g_list_free (codes);
+
+       gtk_widget_show_all (menu);
+
+       return menu;
+}
+
+typedef struct {
+       EmpathyChat  *chat;
+       gchar        *word;
+       gchar        *code;
+} EmpathyChatWord;
+
+static EmpathyChatWord *
+chat_word_new (EmpathyChat  *chat,
+               const gchar  *word,
+               const gchar  *code)
+{
+       EmpathyChatWord *chat_word;
 
-    menu = gtk_menu_new ();
-    suggestions = empathy_spell_get_suggestions (chat_spell->word);
-    if (suggestions == NULL) {
-        menu_item = gtk_menu_item_new_with_label (_("(No Suggestions)"));
-       gtk_widget_set_sensitive (menu_item, FALSE);
-        gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
-    } else {
-        for (l = suggestions; l; l = l->next) {
-            menu_item = gtk_menu_item_new_with_label (l->data);
-            g_signal_connect (G_OBJECT (menu_item),
-                          "activate",
-                          G_CALLBACK (chat_spelling_menu_activate_cb),
-                          chat_spell);
-            gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
-        }
-    }
-    empathy_spell_free_suggestions (suggestions);
+       chat_word = g_slice_new0 (EmpathyChatWord);
 
-    gtk_widget_show_all (menu);
+       chat_word->chat = g_object_ref (chat);
+       chat_word->word = g_strdup (word);
+       chat_word->code = g_strdup (code);
 
-    return menu;
+       return chat_word;
+}
+
+static void
+chat_word_free (EmpathyChatWord *chat_word)
+{
+       g_object_unref (chat_word->chat);
+       g_free (chat_word->word);
+       g_free (chat_word->code);
+       g_slice_free (EmpathyChatWord, chat_word);
+}
+
+static void
+chat_add_to_dictionary_activate_cb (GtkMenuItem     *menu_item,
+                                   EmpathyChatWord *chat_word)
+{
+       EmpathyChatPriv *priv = GET_PRIV (chat_word->chat);
+
+       empathy_spell_add_to_dictionary (chat_word->code,
+                                        chat_word->word);
+       priv->update_misspelled_words_id =
+               g_idle_add (update_misspelled_words, chat_word->chat);
+}
+
+static GtkWidget *
+chat_spelling_build_add_to_dictionary_item (EmpathyChatSpell *chat_spell)
+{
+       GtkWidget       *menu, *item, *lang_item, *image;
+       GList           *codes, *l;
+       gchar           *label;
+       const gchar     *code, *name;
+       EmpathyChatWord *chat_word;
+
+       codes = empathy_spell_get_enabled_language_codes ();
+       g_assert (codes != NULL);
+       if (g_list_length (codes) > 1) {
+               /* translators: %s is the selected word */
+               label = g_strdup_printf (_("Add '%s' to Dictionary"),
+                                       chat_spell->word);
+               item = gtk_image_menu_item_new_with_mnemonic (label);
+               g_free (label);
+               image = gtk_image_new_from_icon_name (GTK_STOCK_ADD,
+                                                     GTK_ICON_SIZE_MENU);
+               gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item),
+                                              image);
+
+               menu = gtk_menu_new ();
+
+               for (l = codes; l; l = l->next) {
+                       code = l->data;
+                       name = empathy_spell_get_language_name (code);
+                       if (name == NULL)
+                               continue;
+
+                       lang_item = gtk_image_menu_item_new_with_label (name);
+
+                       chat_word= chat_word_new (chat_spell->chat,
+                                                 chat_spell->word, code);
+                       g_object_set_data_full (G_OBJECT (lang_item),
+                               "chat-word", chat_word,
+                               (GDestroyNotify) chat_word_free);
+
+                       g_signal_connect (G_OBJECT (lang_item), "activate",
+                               G_CALLBACK (chat_add_to_dictionary_activate_cb),
+                               chat_word);
+                       gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), lang_item);
+               }
+               gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), menu);
+       } else {
+               code = codes->data;
+               name = empathy_spell_get_language_name (code);
+               g_assert (name != NULL);
+               /* translators: first %s is the selected word,
+                * second %s is the language name of the target dictionary */
+               label = g_strdup_printf (_("Add '%s' to %s Dictionary"),
+                                       chat_spell->word, name);
+               item = gtk_image_menu_item_new_with_mnemonic (label);
+               g_free (label);
+               image = gtk_image_new_from_icon_name (GTK_STOCK_ADD,
+                                                     GTK_ICON_SIZE_MENU);
+               gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
+
+               chat_word = chat_word_new (chat_spell->chat, chat_spell->word,
+                                          code);
+               g_object_set_data_full (G_OBJECT (item), "chat-word", chat_word,
+                                       (GDestroyNotify) chat_word_free);
+
+               g_signal_connect (G_OBJECT (item), "activate",
+                                 G_CALLBACK (chat_add_to_dictionary_activate_cb),
+                                 chat_word);
+       }
+       g_list_free (codes);
+
+       gtk_widget_show_all (item);
+
+       return item;
 }
 
 static void
@@ -1650,12 +2377,24 @@ chat_text_send_cb (GtkMenuItem *menuitem,
        chat_input_text_view_send (chat);
 }
 
+static gboolean
+chat_input_button_press_event_cb (GtkTextView    *view,
+                                 GdkEventButton *event,
+                                 EmpathyChat    *chat)
+{
+       EmpathyChatPriv *priv = GET_PRIV (chat);
+
+       priv->most_recent_event_type = event->type;
+
+       return FALSE;
+}
+
 static void
 chat_input_populate_popup_cb (GtkTextView *view,
                              GtkMenu     *menu,
                              EmpathyChat *chat)
 {
-       EmpathyChatPriv      *priv;
+       EmpathyChatPriv      *priv = GET_PRIV (chat);
        GtkTextBuffer        *buffer;
        GtkTextTagTable      *table;
        GtkTextTag           *tag;
@@ -1665,11 +2404,11 @@ chat_input_populate_popup_cb (GtkTextView *view,
        gchar                *str = NULL;
        EmpathyChatSpell     *chat_spell;
        GtkWidget            *spell_menu;
+       GtkWidget            *spell_item;
        EmpathySmileyManager *smiley_manager;
        GtkWidget            *smiley_menu;
        GtkWidget            *image;
 
-       priv = GET_PRIV (chat);
        buffer = gtk_text_view_get_buffer (view);
 
        /* Add the emoticon menu. */
@@ -1706,12 +2445,34 @@ chat_input_populate_popup_cb (GtkTextView *view,
        /* Add the spell check menu item. */
        table = gtk_text_buffer_get_tag_table (buffer);
        tag = gtk_text_tag_table_lookup (table, "misspelled");
-       gtk_widget_get_pointer (GTK_WIDGET (view), &x, &y);
-       gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view),
-                                              GTK_TEXT_WINDOW_WIDGET,
-                                              x, y,
-                                              &x, &y);
-       gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view), &iter, x, y);
+
+       switch (priv->most_recent_event_type) {
+           case GDK_BUTTON_PRESS:
+               /* get the location from the pointer */
+               gdk_window_get_device_position (gtk_widget_get_window (GTK_WIDGET (view)),
+                       gdk_device_manager_get_client_pointer (gdk_display_get_device_manager (
+                               gtk_widget_get_display (GTK_WIDGET (view)))), &x, &y, NULL);
+
+               gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view),
+                                                      GTK_TEXT_WINDOW_WIDGET,
+                                                      x, y,
+                                                      &x, &y);
+               gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view),
+                                                   &iter, x, y);
+               break;
+
+           default:
+               g_warn_if_reached ();
+               /* assume the KEY_PRESS case */
+
+           case GDK_KEY_PRESS:
+               /* get the location from the cursor */
+               gtk_text_buffer_get_iter_at_mark (buffer, &iter,
+                               gtk_text_buffer_get_insert (buffer));
+               break;
+
+       }
+
        start = end = iter;
        if (gtk_text_iter_backward_to_tag_toggle (&start, tag) &&
            gtk_text_iter_forward_to_tag_toggle (&end, tag)) {
@@ -1722,13 +2483,14 @@ chat_input_populate_popup_cb (GtkTextView *view,
        if (!EMP_STR_EMPTY (str)) {
                chat_spell = chat_spell_new (chat, str, start, end);
                g_object_set_data_full (G_OBJECT (menu),
-                                       "chat_spell", chat_spell,
+                                       "chat-spell", chat_spell,
                                        (GDestroyNotify) chat_spell_free);
 
                item = gtk_separator_menu_item_new ();
                gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
                gtk_widget_show (item);
 
+               /* Spelling suggestions */
                item = gtk_image_menu_item_new_with_mnemonic (_("_Spelling Suggestions"));
                image = gtk_image_new_from_icon_name (GTK_STOCK_SPELL_CHECK,
                                                      GTK_ICON_SIZE_MENU);
@@ -1737,49 +2499,46 @@ chat_input_populate_popup_cb (GtkTextView *view,
                spell_menu = chat_spelling_build_menu (chat_spell);
                gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), spell_menu);
 
+
+               spell_item = gtk_separator_menu_item_new ();
+               gtk_menu_shell_append (GTK_MENU_SHELL (spell_menu), spell_item);
+               gtk_widget_show (spell_item);
+
+               /* Add to dictionary */
+               spell_item = chat_spelling_build_add_to_dictionary_item (chat_spell);
+
+               gtk_menu_shell_append (GTK_MENU_SHELL (spell_menu), spell_item);
+               gtk_widget_show (spell_item);
+
                gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
                gtk_widget_show (item);
        }
 }
 
 
-#ifdef ENABLE_TPL
 static gboolean
-chat_log_filter (TplLogEntry *log,
+chat_log_filter (TplEvent *event,
                 gpointer user_data)
-#else
-static gboolean
-chat_log_filter (EmpathyMessage *message,
-                gpointer user_data)
-#endif /* ENABLE_TPL */
 {
        EmpathyChat *chat = user_data;
-#ifdef ENABLE_TPL
        EmpathyMessage *message;
-#endif /* ENABLE_TPL */
        EmpathyChatPriv *priv = GET_PRIV (chat);
        const GList *pending;
 
-#ifdef ENABLE_TPL
-       g_return_val_if_fail (TPL_IS_LOG_ENTRY (log), FALSE);
-#else
-       g_return_val_if_fail (EMPATHY_IS_MESSAGE (message), FALSE);
-#endif /* ENABLE_TPL */
+       g_return_val_if_fail (TPL_IS_EVENT (event), FALSE);
        g_return_val_if_fail (EMPATHY_IS_CHAT (chat), FALSE);
 
        pending = empathy_tp_chat_get_pending_messages (priv->tp_chat);
-#ifdef ENABLE_TPL
-       message = empathy_message_from_tpl_log_entry (log);
-#endif /* ENABLE_TPL */
+       message = empathy_message_from_tpl_log_event (event);
 
        for (; pending; pending = g_list_next (pending)) {
                if (empathy_message_equal (message, pending->data)) {
+                       g_object_unref (message);
                        return FALSE;
                }
        }
-#ifdef ENABLE_TPL
+
        g_object_unref (message);
-#endif
        return TRUE;
 }
 
@@ -1801,12 +2560,11 @@ show_pending_messages (EmpathyChat *chat) {
 
        for (l = messages; l != NULL ; l = g_list_next (l)) {
                EmpathyMessage *message = EMPATHY_MESSAGE (l->data);
-               chat_message_received (chat, message);
+               chat_message_received (chat, message, TRUE);
        }
 }
 
 
-#ifdef ENABLE_TPL
 static void
 got_filtered_messages_cb (GObject *manager,
                GAsyncResult *result,
@@ -1818,9 +2576,8 @@ got_filtered_messages_cb (GObject *manager,
        EmpathyChatPriv *priv = GET_PRIV (chat);
        GError *error = NULL;
 
-       messages = tpl_log_manager_get_filtered_messages_async_finish (result, &error);
-
-       if (error != NULL) {
+       if (!tpl_log_manager_get_filtered_events_finish (TPL_LOG_MANAGER (manager),
+               result, &messages, &error)) {
                DEBUG ("%s. Aborting.", error->message);
                empathy_chat_view_append_event (chat->view,
                        _("Failed to retrieve recent logs"));
@@ -1830,12 +2587,40 @@ got_filtered_messages_cb (GObject *manager,
 
        for (l = messages; l; l = g_list_next (l)) {
                EmpathyMessage *message;
-               g_assert (TPL_IS_LOG_ENTRY (l->data));
 
-               message = empathy_message_from_tpl_log_entry (l->data);
+               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 (chat->view, message);
+               if (empathy_message_is_edit (message)) {
+                       /* this is an edited message, create a synthetic event
+                        * using the supersedes token and
+                        * original-message-sent timestamp, that we can then
+                        * replace */
+                       EmpathyMessage *syn_msg = g_object_new (
+                               EMPATHY_TYPE_MESSAGE,
+                               "body", "",
+                               "token", empathy_message_get_supersedes (message),
+                               "type", empathy_message_get_tptype (message),
+                               "timestamp", empathy_message_get_original_timestamp (message),
+                               "incoming", empathy_message_is_incoming (message),
+                               "is-backlog", TRUE,
+                               "receiver", empathy_message_get_receiver (message),
+                               "sender", empathy_message_get_sender (message),
+                               NULL);
+
+                       empathy_chat_view_append_message (chat->view, syn_msg,
+                                                         chat_should_highlight (chat, syn_msg));
+                       empathy_chat_view_edit_message (chat->view, message);
+
+                       g_object_unref (syn_msg);
+               } else {
+                       /* append the latest message */
+                       empathy_chat_view_append_message (chat->view, message,
+                                                         chat_should_highlight (chat, message));
+               }
+
                g_object_unref (message);
        }
        g_list_free (messages);
@@ -1853,17 +2638,12 @@ out:
        /* Turn back on scrolling */
        empathy_chat_view_scroll (chat->view, TRUE);
 }
-#endif /* ENABLE_TPL */
-
 
 static void
 chat_add_logs (EmpathyChat *chat)
 {
        EmpathyChatPriv *priv = GET_PRIV (chat);
-       gboolean         is_chatroom;
-#ifndef ENABLE_TPL
-       GList           *messages, *l;
-#endif /* ENABLE_TPL */
+       TplEntity       *target;
 
        if (!priv->id) {
                return;
@@ -1873,38 +2653,23 @@ chat_add_logs (EmpathyChat *chat)
        empathy_chat_view_scroll (chat->view, FALSE);
 
        /* Add messages from last conversation */
-       is_chatroom = priv->handle_type == TP_HANDLE_TYPE_ROOM;
-
-#ifndef ENABLE_TPL
-       messages = empathy_log_manager_get_filtered_messages (priv->log_manager,
-                                                             priv->account,
-                                                             priv->id,
-                                                             is_chatroom,
-                                                             5,
-                                                             chat_log_filter,
-                                                             chat);
-
-       for (l = messages; l; l = g_list_next (l)) {
-               empathy_chat_view_append_message (chat->view, l->data);
-               g_object_unref (l->data);
-       }
-
-       g_list_free (messages);
+       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);
 
-       /* Turn back on scrolling */
-       empathy_chat_view_scroll (chat->view, TRUE);
-#else
        priv->retrieving_backlogs = TRUE;
-       tpl_log_manager_get_filtered_messages_async (priv->log_manager,
-                                                             priv->account,
-                                                             priv->id,
-                                                             is_chatroom,
-                                                             5,
-                                                             chat_log_filter,
-                                                             chat,
-                                                             got_filtered_messages_cb,
-                                                             (gpointer) chat);
-#endif /* ENABLE_TPL */
+       tpl_log_manager_get_filtered_events_async (priv->log_manager,
+                                                  priv->account,
+                                                  target,
+                                                  TPL_EVENT_MASK_TEXT,
+                                                  5,
+                                                  chat_log_filter,
+                                                  chat,
+                                                  got_filtered_messages_cb,
+                                                  (gpointer) chat);
+
+       g_object_unref (target);
 }
 
 static gint
@@ -1948,7 +2713,7 @@ build_part_message (guint           reason,
        const gchar *actor_name = NULL;
 
        if (actor != NULL) {
-               actor_name = empathy_contact_get_name (actor);
+               actor_name = empathy_contact_get_alias (actor);
        }
 
        /* Having an actor only really makes sense for a few actions... */
@@ -2004,7 +2769,7 @@ chat_members_changed_cb (EmpathyTpChat  *tp_chat,
                         EmpathyChat    *chat)
 {
        EmpathyChatPriv *priv = GET_PRIV (chat);
-       const gchar *name = empathy_contact_get_name (contact);
+       const gchar *name = empathy_contact_get_alias (contact);
        gchar *str;
 
        g_return_if_fail (TP_CHANNEL_GROUP_CHANGE_REASON_RENAMED != reason);
@@ -2039,8 +2804,8 @@ chat_member_renamed_cb (EmpathyTpChat  *tp_chat,
                gchar *str;
 
                str = g_strdup_printf (_("%s is now known as %s"),
-                                      empathy_contact_get_name (old_contact),
-                                      empathy_contact_get_name (new_contact));
+                                      empathy_contact_get_alias (old_contact),
+                                      empathy_contact_get_alias (new_contact));
                empathy_chat_view_append_event (chat->view, str);
                g_free (str);
        }
@@ -2048,9 +2813,22 @@ chat_member_renamed_cb (EmpathyTpChat  *tp_chat,
 }
 
 static gboolean
-chat_reset_size_request (gpointer widget)
+chat_contacts_visible_timeout_cb (gpointer chat)
 {
-       gtk_widget_set_size_request (widget, -1, -1);
+       EmpathyChatPriv *priv = GET_PRIV (chat);
+
+       /* Relax the size request */
+       gtk_widget_set_size_request (priv->vbox_left, -1, -1);
+
+       /* Set the position of the slider. This must be done here because
+        * GtkPaned need to know its size allocation and it will be settled only
+        * after the gtk_window_resize () tough effect. */
+       if (priv->contacts_width > 0) {
+               gtk_paned_set_position (GTK_PANED (priv->hpaned),
+                                       priv->contacts_width);
+       }
+
+       priv->contacts_visible_id = 0;
 
        return FALSE;
 }
@@ -2060,7 +2838,6 @@ chat_update_contacts_visibility (EmpathyChat *chat,
                         gboolean show)
 {
        EmpathyChatPriv *priv = GET_PRIV (chat);
-       GtkAllocation allocation;
 
        if (!priv->scrolled_window_contacts) {
                return;
@@ -2071,8 +2848,9 @@ chat_update_contacts_visibility (EmpathyChat *chat,
        }
 
        if (show && priv->contact_list_view == NULL) {
-               EmpathyContactListStore *store;
+               EmpathyIndividualStore *store;
                gint                     min_width;
+               GtkAllocation            allocation;
 
                /* We are adding the contact list to the chat, we don't want the
                 * chat view to become too small. If the chat view is already
@@ -2083,22 +2861,35 @@ chat_update_contacts_visibility (EmpathyChat *chat,
                gtk_widget_get_allocation (priv->vbox_left, &allocation);
                min_width = MIN (allocation.width, 250);
                gtk_widget_set_size_request (priv->vbox_left, min_width, -1);
-               g_idle_add (chat_reset_size_request, priv->vbox_left);
 
-               if (priv->contacts_width > 0) {
-                       gtk_paned_set_position (GTK_PANED (priv->hpaned),
-                                               priv->contacts_width);
-               }
+               /* There is no way to know when the window resize will happen
+                * since it is WM's decision. Let's hope it won't be longer. */
+               if (priv->contacts_visible_id != 0)
+                       g_source_remove (priv->contacts_visible_id);
+               priv->contacts_visible_id = g_timeout_add (500,
+                       chat_contacts_visible_timeout_cb, chat);
+
+               store = EMPATHY_INDIVIDUAL_STORE (
+                               empathy_individual_store_channel_new ((TpChannel *) priv->tp_chat));
+
+               empathy_individual_store_set_show_groups (store, FALSE);
+
+               priv->contact_list_view = GTK_WIDGET (empathy_individual_view_new (store,
+                       EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP,
+                       EMPATHY_INDIVIDUAL_FEATURE_ADD_CONTACT |
+                       EMPATHY_INDIVIDUAL_FEATURE_CHAT |
+                       EMPATHY_INDIVIDUAL_FEATURE_CALL |
+                       EMPATHY_INDIVIDUAL_FEATURE_LOG |
+                       EMPATHY_INDIVIDUAL_FEATURE_INFO));
+
+               empathy_individual_view_set_show_offline (
+                       EMPATHY_INDIVIDUAL_VIEW (priv->contact_list_view), TRUE);
+               empathy_individual_view_set_show_uninteresting (
+                       EMPATHY_INDIVIDUAL_VIEW (priv->contact_list_view), TRUE);
 
-               store = empathy_contact_list_store_new (EMPATHY_CONTACT_LIST (priv->tp_chat));
-               priv->contact_list_view = GTK_WIDGET (empathy_contact_list_view_new (store,
-                       EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP,
-                       EMPATHY_CONTACT_FEATURE_CHAT |
-                       EMPATHY_CONTACT_FEATURE_CALL |
-                       EMPATHY_CONTACT_FEATURE_LOG |
-                       EMPATHY_CONTACT_FEATURE_INFO));
                gtk_container_add (GTK_CONTAINER (priv->scrolled_window_contacts),
                                   priv->contact_list_view);
+
                gtk_widget_show (priv->contact_list_view);
                gtk_widget_show (priv->scrolled_window_contacts);
                g_object_unref (store);
@@ -2125,6 +2916,32 @@ empathy_chat_set_show_contacts (EmpathyChat *chat,
        g_object_notify (G_OBJECT (chat), "show-contacts");
 }
 
+static void
+chat_self_contact_changed_cb (EmpathyChat *chat)
+{
+       EmpathyChatPriv *priv = GET_PRIV (chat);
+
+       if (priv->self_contact != NULL) {
+               g_signal_handlers_disconnect_by_func (priv->self_contact,
+                                                     chat_self_contact_alias_changed_cb,
+                                                     chat);
+       }
+       g_clear_object (&priv->self_contact);
+
+       priv->self_contact = empathy_tp_chat_get_self_contact (priv->tp_chat);
+       if (priv->self_contact != NULL) {
+               g_object_ref (priv->self_contact);
+
+               if (empathy_chat_is_room (chat)) {
+                       g_signal_connect_swapped (priv->self_contact, "notify::alias",
+                                         G_CALLBACK (chat_self_contact_alias_changed_cb),
+                                         chat);
+               }
+       }
+
+       chat_self_contact_alias_changed_cb (chat);
+}
+
 static void
 chat_remote_contact_changed_cb (EmpathyChat *chat)
 {
@@ -2144,10 +2961,7 @@ chat_remote_contact_changed_cb (EmpathyChat *chat)
                priv->handle_type = TP_HANDLE_TYPE_CONTACT;
        }
        else if (priv->tp_chat != NULL) {
-               TpChannel *channel;
-
-               channel = empathy_tp_chat_get_channel (priv->tp_chat);
-               g_object_get (channel, "handle-type", &priv->handle_type, NULL);
+               tp_channel_get_handle ((TpChannel *) priv->tp_chat, &priv->handle_type);
        }
 
        chat_update_contacts_visibility (chat, priv->show_contacts);
@@ -2157,7 +2971,10 @@ chat_remote_contact_changed_cb (EmpathyChat *chat)
 }
 
 static void
-chat_destroy_cb (EmpathyTpChat *tp_chat,
+chat_invalidated_cb (EmpathyTpChat *tp_chat,
+                guint domain,
+                gint code,
+                gchar *message,
                 EmpathyChat   *chat)
 {
        EmpathyChatPriv *priv;
@@ -2177,16 +2994,144 @@ chat_destroy_cb (EmpathyTpChat *tp_chat,
        gtk_widget_set_sensitive (chat->input_text_view, FALSE);
 
        chat_update_contacts_visibility (chat, FALSE);
+
+       priv->unread_messages_when_offline = priv->unread_messages;
 }
 
 static gboolean
-chat_hpaned_pos_changed_cb (GtkWidget* hpaned, gpointer user_data)
+update_misspelled_words (gpointer data)
+{
+       EmpathyChat *chat = EMPATHY_CHAT (data);
+       EmpathyChatPriv *priv = GET_PRIV (chat);
+       GtkTextBuffer *buffer;
+       GtkTextIter iter;
+       gint length;
+
+       buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
+
+       gtk_text_buffer_get_end_iter (buffer, &iter);
+       length = gtk_text_iter_get_offset (&iter);
+       chat_input_text_buffer_insert_text_cb (buffer, &iter,
+                                              NULL, length, chat);
+
+       priv->update_misspelled_words_id = 0;
+
+       return FALSE;
+}
+
+static void
+conf_spell_checking_cb (GSettings *gsettings_chat,
+                       const gchar *key,
+                       gpointer user_data)
 {
+       EmpathyChat *chat = EMPATHY_CHAT (user_data);
+       EmpathyChatPriv *priv = GET_PRIV (chat);
+       gboolean spell_checker;
+       GtkTextBuffer *buffer;
+
+       if (strcmp (key, EMPATHY_PREFS_CHAT_SPELL_CHECKER_ENABLED) != 0)
+               return;
+
+       spell_checker = g_settings_get_boolean (gsettings_chat,
+                       EMPATHY_PREFS_CHAT_SPELL_CHECKER_ENABLED);
+
+       if (!empathy_spell_supported ()) {
+               spell_checker = FALSE;
+       }
+
+       buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
+
+       if (spell_checker == priv->spell_checking_enabled) {
+               if (spell_checker) {
+                       /* Possibly changed dictionaries,
+                        * update misspelled words. Need to do so in idle
+                        * so the spell checker is updated. */
+                       priv->update_misspelled_words_id =
+                               g_idle_add (update_misspelled_words, chat);
+               }
+
+               return;
+       }
+
+       if (spell_checker) {
+               GtkTextIter iter;
+
+               priv->notify_cursor_position_id = tp_g_signal_connect_object  (
+                               buffer, "notify::cursor-position",
+                               G_CALLBACK (chat_input_text_buffer_notify_cursor_position_cb),
+                               chat, 0);
+               priv->insert_text_id = tp_g_signal_connect_object  (
+                               buffer, "insert-text",
+                               G_CALLBACK (chat_input_text_buffer_insert_text_cb),
+                               chat, G_CONNECT_AFTER);
+               priv->delete_range_id = tp_g_signal_connect_object  (
+                               buffer, "delete-range",
+                               G_CALLBACK (chat_input_text_buffer_delete_range_cb),
+                               chat, G_CONNECT_AFTER);
+
+               gtk_text_buffer_create_tag (buffer, "misspelled",
+                                           "underline", PANGO_UNDERLINE_ERROR,
+                                           NULL);
+
+               gtk_text_buffer_get_iter_at_mark (buffer, &iter,
+                                                 gtk_text_buffer_get_insert (buffer));
+               gtk_text_buffer_create_mark (buffer, "previous-cursor-position",
+                                            &iter, TRUE);
+
+               /* Mark misspelled words in the existing buffer.
+                * Need to do so in idle so the spell checker is updated. */
+               priv->update_misspelled_words_id =
+                       g_idle_add (update_misspelled_words, chat);
+       } else {
+               GtkTextTagTable *table;
+               GtkTextTag *tag;
+
+               g_signal_handler_disconnect (buffer, priv->notify_cursor_position_id);
+               priv->notify_cursor_position_id = 0;
+               g_signal_handler_disconnect (buffer, priv->insert_text_id);
+               priv->insert_text_id = 0;
+               g_signal_handler_disconnect (buffer, priv->delete_range_id);
+               priv->delete_range_id = 0;
+
+               table = gtk_text_buffer_get_tag_table (buffer);
+               tag = gtk_text_tag_table_lookup (table, "misspelled");
+               gtk_text_tag_table_remove (table, tag);
+
+               gtk_text_buffer_delete_mark_by_name (buffer,
+                                                    "previous-cursor-position");
+       }
+
+       priv->spell_checking_enabled = spell_checker;
+}
+
+static gboolean
+save_paned_pos_timeout (gpointer data)
+{
+       EmpathyChat *self = data;
        gint hpaned_pos;
-       hpaned_pos = gtk_paned_get_position (GTK_PANED(hpaned));
-       empathy_conf_set_int (empathy_conf_get (),
-                             EMPATHY_PREFS_UI_CHAT_WINDOW_PANED_POS,
-                             hpaned_pos);
+
+       hpaned_pos = gtk_paned_get_position (GTK_PANED (self->priv->hpaned));
+
+       g_settings_set_int (self->priv->gsettings_ui,
+                           EMPATHY_PREFS_UI_CHAT_WINDOW_PANED_POS,
+                           hpaned_pos);
+
+       return FALSE;
+}
+
+static gboolean
+chat_hpaned_pos_changed_cb (GtkWidget* hpaned,
+               GParamSpec *spec,
+               gpointer user_data)
+{
+       EmpathyChat *chat = EMPATHY_CHAT (user_data);
+
+       if (chat->priv->save_paned_pos_id != 0)
+               g_source_remove (chat->priv->save_paned_pos_id);
+
+       chat->priv->save_paned_pos_id = g_timeout_add_seconds (1,
+               save_paned_pos_timeout, chat);
+
        return TRUE;
 }
 
@@ -2198,7 +3143,7 @@ chat_create_ui (EmpathyChat *chat)
        GList           *list = NULL;
        gchar           *filename;
        GtkTextBuffer   *buffer;
-       gint              paned_pos;
+       EmpathyThemeManager *theme_mgr;
 
        filename = empathy_file_lookup ("empathy-chat.ui",
                                        "libempathy-gtk");
@@ -2209,14 +3154,23 @@ chat_create_ui (EmpathyChat *chat)
                                        "scrolled_window_chat", &priv->scrolled_window_chat,
                                        "scrolled_window_input", &priv->scrolled_window_input,
                                        "hbox_topic", &priv->hbox_topic,
+                                       "expander_topic", &priv->expander_topic,
                                        "label_topic", &priv->label_topic,
                                        "scrolled_window_contacts", &priv->scrolled_window_contacts,
                                        "info_bar_vbox", &priv->info_bar_vbox,
                                        NULL);
+
+       empathy_builder_connect (gui, chat,
+               "expander_topic", "notify::expanded", chat_topic_expander_activate_cb,
+               "label_topic", "size-allocate", chat_topic_label_size_allocate_cb,
+               NULL);
+
        g_free (filename);
 
        /* Add message view. */
-       chat->view = empathy_theme_manager_create_view (empathy_theme_manager_get ());
+       theme_mgr = empathy_theme_manager_dup_singleton ();
+       chat->view = empathy_theme_manager_create_view (theme_mgr);
+       g_object_unref (theme_mgr);
        /* If this is a GtkTextView, it's set as a drag destination for text/plain
           and other types, even though it's non-editable and doesn't accept any
           drags.  This steals drag motion for anything inside the scrollbars,
@@ -2231,33 +3185,31 @@ chat_create_ui (EmpathyChat *chat)
        gtk_widget_show (GTK_WIDGET (chat->view));
 
        /* Add input GtkTextView */
-       chat->input_text_view = g_object_new (GTK_TYPE_TEXT_VIEW,
-                                             "pixels-above-lines", 2,
-                                             "pixels-below-lines", 2,
-                                             "pixels-inside-wrap", 1,
-                                             "right-margin", 2,
-                                             "left-margin", 2,
-                                             "wrap-mode", GTK_WRAP_WORD_CHAR,
-                                             NULL);
+       chat->input_text_view = empathy_input_text_view_new ();
+       g_signal_connect (chat->input_text_view, "notify::has-focus",
+                         G_CALLBACK (chat_input_has_focus_notify_cb),
+                         chat);
        g_signal_connect (chat->input_text_view, "key-press-event",
                          G_CALLBACK (chat_input_key_press_event_cb),
                          chat);
-       g_signal_connect (chat->input_text_view, "size-request",
-                         G_CALLBACK (chat_input_size_request_cb),
-                         chat);
        g_signal_connect (chat->input_text_view, "realize",
                          G_CALLBACK (chat_input_realize_cb),
                          chat);
+       g_signal_connect (chat->input_text_view, "button-press-event",
+                         G_CALLBACK (chat_input_button_press_event_cb),
+                         chat);
        g_signal_connect (chat->input_text_view, "populate-popup",
                          G_CALLBACK (chat_input_populate_popup_cb),
                          chat);
        buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
-       empathy_signal_connect_weak  (buffer, "changed",
+       tp_g_signal_connect_object  (buffer, "changed",
                          G_CALLBACK (chat_input_text_buffer_changed_cb),
-                         G_OBJECT (chat));
-       gtk_text_buffer_create_tag (buffer, "misspelled",
-                                   "underline", PANGO_UNDERLINE_ERROR,
-                                   NULL);
+                         chat, 0);
+       tp_g_signal_connect_object (priv->gsettings_chat,
+                       "changed::" EMPATHY_PREFS_CHAT_SPELL_CHECKER_ENABLED,
+                       G_CALLBACK (conf_spell_checking_cb), chat, 0);
+       conf_spell_checking_cb (priv->gsettings_chat,
+                               EMPATHY_PREFS_CHAT_SPELL_CHECKER_ENABLED, chat);
        gtk_container_add (GTK_CONTAINER (priv->scrolled_window_input),
                           chat->input_text_view);
        gtk_widget_show (chat->input_text_view);
@@ -2274,80 +3226,27 @@ chat_create_ui (EmpathyChat *chat)
 
        g_signal_connect (priv->hpaned, "notify::position",
                          G_CALLBACK (chat_hpaned_pos_changed_cb),
-                         NULL);
-
-        /* Load the paned position */
-       if (empathy_conf_get_int (empathy_conf_get (),
-                                EMPATHY_PREFS_UI_CHAT_WINDOW_PANED_POS,
-                                &paned_pos)
-               && paned_pos)
-               gtk_paned_set_position (GTK_PANED(priv->hpaned), paned_pos);
-
-       /* Set widget focus order */
-       list = g_list_append (NULL, priv->search_bar);
-       list = g_list_append (list, priv->scrolled_window_input);
-       gtk_container_set_focus_chain (GTK_CONTAINER (priv->vbox_left), list);
-       g_list_free (list);
-
-       list = g_list_append (NULL, priv->vbox_left);
-       list = g_list_append (list, priv->scrolled_window_contacts);
-       gtk_container_set_focus_chain (GTK_CONTAINER (priv->hpaned), list);
-       g_list_free (list);
-
-       list = g_list_append (NULL, priv->hpaned);
-       list = g_list_append (list, priv->hbox_topic);
-       gtk_container_set_focus_chain (GTK_CONTAINER (priv->widget), list);
-       g_list_free (list);
-
-       /* Add the main widget in the chat widget */
-       gtk_container_add (GTK_CONTAINER (chat), priv->widget);
-       g_object_unref (gui);
-}
-
-static void
-chat_size_request (GtkWidget      *widget,
-                  GtkRequisition *requisition)
-{
-  GtkBin *bin = GTK_BIN (widget);
-  GtkWidget *child;
-
-  requisition->width = gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2;
-  requisition->height = gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2;
-
-  child = gtk_bin_get_child (bin);
-
-  if (child && gtk_widget_get_visible (child))
-    {
-      GtkRequisition child_requisition;
-
-      gtk_widget_size_request (child, &child_requisition);
-
-      requisition->width += child_requisition.width;
-      requisition->height += child_requisition.height;
-    }
-}
-
-static void
-chat_size_allocate (GtkWidget     *widget,
-                   GtkAllocation *allocation)
-{
-  GtkBin *bin = GTK_BIN (widget);
-  GtkAllocation child_allocation;
-  GtkWidget *child;
+                         chat);
 
-  gtk_widget_set_allocation (widget, allocation);
+       /* Set widget focus order */
+       list = g_list_append (NULL, priv->search_bar);
+       list = g_list_append (list, priv->scrolled_window_input);
+       gtk_container_set_focus_chain (GTK_CONTAINER (priv->vbox_left), list);
+       g_list_free (list);
 
-  child = gtk_bin_get_child (bin);
+       list = g_list_append (NULL, priv->vbox_left);
+       list = g_list_append (list, priv->scrolled_window_contacts);
+       gtk_container_set_focus_chain (GTK_CONTAINER (priv->hpaned), list);
+       g_list_free (list);
 
-  if (child && gtk_widget_get_visible (child))
-    {
-      child_allocation.x = allocation->x + gtk_container_get_border_width (GTK_CONTAINER (widget));
-      child_allocation.y = allocation->y + gtk_container_get_border_width (GTK_CONTAINER (widget));
-      child_allocation.width = MAX (allocation->width - gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2, 0);
-      child_allocation.height = MAX (allocation->height - gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2, 0);
+       list = g_list_append (NULL, priv->hpaned);
+       list = g_list_append (list, priv->hbox_topic);
+       gtk_container_set_focus_chain (GTK_CONTAINER (priv->widget), list);
+       g_list_free (list);
 
-      gtk_widget_size_allocate (child, &child_allocation);
-    }
+       /* Add the main widget in the chat widget */
+       gtk_box_pack_start (GTK_BOX (chat), priv->widget, TRUE, TRUE, 0);
+       g_object_unref (gui);
 }
 
 static void
@@ -2361,6 +3260,18 @@ chat_finalize (GObject *object)
 
        DEBUG ("Finalized: %p", object);
 
+       if (priv->update_misspelled_words_id != 0)
+               g_source_remove (priv->update_misspelled_words_id);
+
+       if (priv->save_paned_pos_id != 0)
+               g_source_remove (priv->save_paned_pos_id);
+
+       if (priv->contacts_visible_id != 0)
+               g_source_remove (priv->contacts_visible_id);
+
+       g_object_unref (priv->gsettings_chat);
+       g_object_unref (priv->gsettings_ui);
+
        g_list_foreach (priv->input_history, (GFunc) chat_input_history_entry_free, NULL);
        g_list_free (priv->input_history);
 
@@ -2374,25 +3285,37 @@ chat_finalize (GObject *object)
 
        if (priv->tp_chat) {
                g_signal_handlers_disconnect_by_func (priv->tp_chat,
-                       chat_destroy_cb, chat);
+                       chat_invalidated_cb, chat);
                g_signal_handlers_disconnect_by_func (priv->tp_chat,
                        chat_message_received_cb, chat);
+               g_signal_handlers_disconnect_by_func (priv->tp_chat,
+                       chat_message_acknowledged_cb, chat);
                g_signal_handlers_disconnect_by_func (priv->tp_chat,
                        chat_send_error_cb, chat);
                g_signal_handlers_disconnect_by_func (priv->tp_chat,
                        chat_state_changed_cb, chat);
-               g_signal_handlers_disconnect_by_func (priv->tp_chat,
-                       chat_property_changed_cb, chat);
                g_signal_handlers_disconnect_by_func (priv->tp_chat,
                        chat_members_changed_cb, chat);
+               g_signal_handlers_disconnect_by_func (priv->tp_chat,
+                       chat_self_contact_changed_cb, chat);
                g_signal_handlers_disconnect_by_func (priv->tp_chat,
                        chat_remote_contact_changed_cb, chat);
-               empathy_tp_chat_leave (priv->tp_chat);
+               g_signal_handlers_disconnect_by_func (priv->tp_chat,
+                       chat_title_changed_cb, chat);
+               g_signal_handlers_disconnect_by_func (priv->tp_chat,
+                       chat_subject_changed_cb, chat);
+               empathy_tp_chat_leave (priv->tp_chat, "");
                g_object_unref (priv->tp_chat);
        }
        if (priv->account) {
                g_object_unref (priv->account);
        }
+       if (priv->self_contact) {
+               g_signal_handlers_disconnect_by_func (priv->self_contact,
+                                                     chat_self_contact_alias_changed_cb,
+                                                     chat);
+               g_object_unref (priv->self_contact);
+       }
        if (priv->remote_contact) {
                g_object_unref (priv->remote_contact);
        }
@@ -2406,6 +3329,8 @@ chat_finalize (GObject *object)
        g_free (priv->subject);
        g_completion_free (priv->completion);
 
+       tp_clear_pointer (&priv->highlight_regex, g_regex_unref);
+
        G_OBJECT_CLASS (empathy_chat_parent_class)->finalize (object);
 }
 
@@ -2415,20 +3340,20 @@ chat_constructed (GObject *object)
        EmpathyChat *chat = EMPATHY_CHAT (object);
        EmpathyChatPriv *priv = GET_PRIV (chat);
 
-       if (priv->handle_type != TP_HANDLE_TYPE_ROOM)
+       if (priv->handle_type != TP_HANDLE_TYPE_ROOM) {
+               /* First display logs from the logger and then display pending messages */
                chat_add_logs (chat);
-#ifndef ENABLE_TPL
-       /* When async API are involved, pending message are shown at the end of the
-        * callbacks' chain fired by chat_add_logs */
-       priv->can_show_pending = TRUE;
-       show_pending_messages (chat);
-#endif /* ENABLE_TPL */
+       }
+        else {
+               /* Just display pending messages for rooms */
+               priv->can_show_pending = TRUE;
+               show_pending_messages (chat);
+       }
 }
 
 static void
 empathy_chat_class_init (EmpathyChatClass *klass)
 {
-       GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
        GObjectClass   *object_class = G_OBJECT_CLASS (klass);
 
        object_class->finalize = chat_finalize;
@@ -2436,9 +3361,6 @@ empathy_chat_class_init (EmpathyChatClass *klass)
        object_class->set_property = chat_set_property;
        object_class->constructed = chat_constructed;
 
-       widget_class->size_request = chat_size_request;
-       widget_class->size_allocate = chat_size_allocate;
-
        g_object_class_install_property (object_class,
                                         PROP_TP_CHAT,
                                         g_param_spec_object ("tp-chat",
@@ -2497,25 +3419,67 @@ empathy_chat_class_init (EmpathyChatClass *klass)
                                                               G_PARAM_READWRITE |
                                                               G_PARAM_STATIC_STRINGS));
 
+       g_object_class_install_property (object_class,
+                                        PROP_SMS_CHANNEL,
+                                        g_param_spec_boolean ("sms-channel",
+                                                              "SMS Channel",
+                                                              "TRUE if this channel is for sending SMSes",
+                                                              FALSE,
+                                                              G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_property (object_class,
+                                        PROP_N_MESSAGES_SENDING,
+                                        g_param_spec_uint ("n-messages-sending",
+                                                           "Num Messages Sending",
+                                                           "The number of messages being sent",
+                                                           0, G_MAXUINT, 0,
+                                                           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_property (object_class,
+                                        PROP_NB_UNREAD_MESSAGES,
+                                        g_param_spec_uint ("nb-unread-messages",
+                                                           "Num Unread Messages",
+                                                           "The number of unread messages",
+                                                           0, G_MAXUINT, 0,
+                                                           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
        signals[COMPOSING] =
                g_signal_new ("composing",
                              G_OBJECT_CLASS_TYPE (object_class),
                              G_SIGNAL_RUN_LAST,
                              0,
                              NULL, NULL,
-                             g_cclosure_marshal_VOID__BOOLEAN,
+                             g_cclosure_marshal_generic,
                              G_TYPE_NONE,
                              1, G_TYPE_BOOLEAN);
 
+       /**
+        * EmpathyChat::new-message:
+        * @self: the #EmpathyChat
+        * @message: the new message
+        * @pending: whether the message was in the pending queue when @self
+        *  was created
+        * @should_highlight: %TRUE if the message mentions the local user
+        */
        signals[NEW_MESSAGE] =
                g_signal_new ("new-message",
                              G_OBJECT_CLASS_TYPE (object_class),
                              G_SIGNAL_RUN_LAST,
                              0,
                              NULL, NULL,
-                             g_cclosure_marshal_VOID__OBJECT,
+                             g_cclosure_marshal_generic,
                              G_TYPE_NONE,
-                             1, EMPATHY_TYPE_MESSAGE);
+                             3, EMPATHY_TYPE_MESSAGE, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN);
+
+       signals[PART_COMMAND_ENTERED] =
+                       g_signal_new ("part-command-entered",
+                                 G_OBJECT_CLASS_TYPE (object_class),
+                                 G_SIGNAL_RUN_LAST,
+                                 0,
+                                 NULL, NULL,
+                                 g_cclosure_marshal_generic,
+                                 G_TYPE_NONE,
+                                 1, G_TYPE_STRV);
 
        g_type_class_add_private (object_class, sizeof (EmpathyChatPriv));
 }
@@ -2540,7 +3504,7 @@ account_manager_prepared_cb (GObject *source_object,
        EmpathyChat *chat = user_data;
        GError *error = NULL;
 
-       if (!tp_account_manager_prepare_finish (account_manager, result, &error)) {
+       if (!tp_proxy_prepare_finish (account_manager, result, &error)) {
                DEBUG ("Failed to prepare the account manager: %s", error->message);
                g_error_free (error);
                return;
@@ -2550,9 +3514,9 @@ account_manager_prepared_cb (GObject *source_object,
 
        for (l = accounts; l != NULL; l = l->next) {
                TpAccount *account = l->data;
-               empathy_signal_connect_weak (account, "status-changed",
+               tp_g_signal_connect_object (account, "status-changed",
                                             G_CALLBACK (chat_new_connection_cb),
-                                            G_OBJECT (chat));
+                                            chat, 0);
        }
 
        g_list_free (accounts);
@@ -2565,22 +3529,21 @@ empathy_chat_init (EmpathyChat *chat)
                EMPATHY_TYPE_CHAT, EmpathyChatPriv);
 
        chat->priv = priv;
-#ifndef ENABLE_TPL
-       priv->log_manager = empathy_log_manager_dup_singleton ();
-#else
        priv->log_manager = tpl_log_manager_dup_singleton ();
-#endif /* ENABLE_TPL */
-       priv->contacts_width = -1;
+       priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
+       priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
+
+       priv->contacts_width = g_settings_get_int (priv->gsettings_ui,
+               EMPATHY_PREFS_UI_CHAT_WINDOW_PANED_POS);
        priv->input_history = NULL;
        priv->input_history_current = NULL;
        priv->account_manager = tp_account_manager_dup ();
 
-       tp_account_manager_prepare_async (priv->account_manager, NULL,
+       tp_proxy_prepare_async (priv->account_manager, NULL,
                                          account_manager_prepared_cb, chat);
 
-       empathy_conf_get_bool (empathy_conf_get (),
-                              EMPATHY_PREFS_CHAT_SHOW_CONTACTS_IN_ROOMS,
-                              &priv->show_contacts);
+       priv->show_contacts = g_settings_get_boolean (priv->gsettings_chat,
+                       EMPATHY_PREFS_CHAT_SHOW_CONTACTS_IN_ROOMS);
 
        /* Block events for some time to avoid having "has come online" or
         * "joined" messages. */
@@ -2588,7 +3551,7 @@ empathy_chat_init (EmpathyChat *chat)
                g_timeout_add_seconds (1, chat_block_events_timeout_cb, chat);
 
        /* Add nick name completion */
-       priv->completion = g_completion_new ((GCompletionFunc) empathy_contact_get_name);
+       priv->completion = g_completion_new ((GCompletionFunc) empathy_contact_get_alias);
        g_completion_set_compare (priv->completion, chat_contacts_completion_func);
 
        chat_create_ui (chat);
@@ -2610,75 +3573,251 @@ empathy_chat_get_tp_chat (EmpathyChat *chat)
        return priv->tp_chat;
 }
 
-static void display_password_info_bar (EmpathyChat *self,
-                                      gboolean retry);
+typedef struct
+{
+       EmpathyChat *self;
+       GtkWidget *info_bar;
+       gulong response_id;
+       GtkWidget *button;
+       GtkWidget *label;
+       GtkWidget *entry;
+       GtkWidget *spinner;
+       gchar *password;
+} PasswordData;
+
+static void
+passwd_remember_button_cb (GtkButton *button,
+                         PasswordData *data)
+{
+       gtk_info_bar_response (GTK_INFO_BAR (data->info_bar), GTK_RESPONSE_OK);
+}
+
+static void
+passwd_not_now_button_cb (GtkButton *button,
+                         PasswordData *data)
+{
+       gtk_info_bar_response (GTK_INFO_BAR (data->info_bar), GTK_RESPONSE_NO);
+}
+
+static void
+remember_password_infobar_response_cb (GtkWidget *info_bar,
+                                      gint response_id,
+                                      PasswordData *data)
+{
+       EmpathyChatPriv *priv = GET_PRIV (data->self);
+
+       if (response_id == GTK_RESPONSE_OK) {
+               DEBUG ("Saving room password");
+               empathy_keyring_set_room_password_async (priv->account,
+                                                        empathy_tp_chat_get_id (priv->tp_chat),
+                                                        data->password,
+                                                        NULL, NULL);
+       }
+
+       gtk_widget_destroy (info_bar);
+       g_free (data->password);
+       g_slice_free (PasswordData, data);
+}
+
+static void
+chat_prompt_to_save_password (EmpathyChat *self,
+                             PasswordData *data)
+{
+       GtkWidget *content_area;
+       GtkWidget *hbox;
+       GtkWidget *image;
+       GtkWidget *label;
+       GtkWidget *alig;
+       GtkWidget *button;
+
+       /* save the password in case it needs to be saved */
+       data->password = g_strdup (gtk_entry_get_text (GTK_ENTRY (data->entry)));
+
+       /* Remove all previous widgets */
+       content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (data->info_bar));
+       gtk_container_forall (GTK_CONTAINER (content_area),
+                             (GtkCallback) gtk_widget_destroy, NULL);
+       data->button = NULL;
+       data->label = NULL;
+       data->entry = NULL;
+       data->spinner = NULL;
+
+       gtk_info_bar_set_message_type (GTK_INFO_BAR (data->info_bar),
+                                      GTK_MESSAGE_QUESTION);
+
+       hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5);
+       gtk_box_pack_start (GTK_BOX (content_area), hbox, TRUE, TRUE, 0);
+
+       /* Add image */
+       image = gtk_image_new_from_stock (GTK_STOCK_DIALOG_AUTHENTICATION,
+                                         GTK_ICON_SIZE_DIALOG);
+       gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
+
+       /* Add message */
+       label = gtk_label_new (_("Would you like to store this password?"));
+       gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0);
+
+       /* Add 'Remember' button */
+       alig = gtk_alignment_new (0, 0.5, 1, 0);
+
+       button = gtk_button_new_with_label (_("Remember"));
+       gtk_container_add (GTK_CONTAINER (alig), button);
+       gtk_box_pack_start (GTK_BOX (hbox), alig, FALSE, FALSE, 0);
+
+       g_signal_connect (button, "clicked", G_CALLBACK (passwd_remember_button_cb),
+                         data);
+
+       /* Add 'Not now' button */
+       alig = gtk_alignment_new (0, 0.5, 1, 0);
+
+       button = gtk_button_new_with_label (_("Not now"));
+       gtk_container_add (GTK_CONTAINER (alig), button);
+       gtk_box_pack_start (GTK_BOX (hbox), alig, FALSE, FALSE, 0);
+
+       g_signal_connect (button, "clicked", G_CALLBACK (passwd_not_now_button_cb),
+                         data);
+
+       /* go! */
+       g_signal_handler_disconnect (data->info_bar, data->response_id);
+       g_signal_connect (data->info_bar, "response",
+                         G_CALLBACK (remember_password_infobar_response_cb), data);
+
+       gtk_widget_show_all (data->info_bar);
+}
 
 static void
 provide_password_cb (GObject *tp_chat,
                     GAsyncResult *res,
                     gpointer user_data)
 {
-       EmpathyChat *self = EMPATHY_CHAT (user_data);
+       PasswordData *data = user_data;
+       EmpathyChat *self = data->self;
        EmpathyChatPriv *priv = GET_PRIV (self);
        GError *error = NULL;
 
-       if (!empathy_tp_chat_provide_password_finish (EMPATHY_TP_CHAT (tp_chat), res,
+       if (!tp_channel_provide_password_finish (TP_CHANNEL (tp_chat), res,
                                                      &error)) {
                DEBUG ("error: %s", error->message);
                /* FIXME: what should we do if that's another error? Close the channel?
                 * Display the raw D-Bus error to the user isn't very useful */
-               if (g_error_matches (error, TP_ERRORS, TP_ERROR_AUTHENTICATION_FAILED))
-                       display_password_info_bar (self, TRUE);
+               if (g_error_matches (error, TP_ERRORS, TP_ERROR_AUTHENTICATION_FAILED)) {
+                       /* entry */
+                       gtk_entry_set_text (GTK_ENTRY (data->entry), "");
+                       gtk_widget_set_sensitive (data->entry, TRUE);
+                       gtk_widget_grab_focus (data->entry);
+
+                       /* info bar */
+                       gtk_info_bar_set_message_type (
+                           GTK_INFO_BAR (data->info_bar),
+                           GTK_MESSAGE_ERROR);
+
+                       /* button */
+                       gtk_widget_set_sensitive (data->button, TRUE);
+                       gtk_button_set_label (GTK_BUTTON (data->button),
+                           _("Retry"));
+
+                       /* label */
+                       gtk_label_set_text (GTK_LABEL (data->label),
+                           _("Wrong password; please try again:"));
+
+                       /* spinner */
+                       gtk_spinner_stop (GTK_SPINNER (data->spinner));
+                       gtk_widget_hide (data->spinner);
+               }
                g_error_free (error);
                return;
        }
 
+       if (empathy_keyring_is_available ()) {
+               /* ask whether they want to save the password */
+               chat_prompt_to_save_password (self, data);
+       } else {
+               /* Get rid of the password info bar finally */
+               gtk_widget_destroy (data->info_bar);
+               g_slice_free (PasswordData, data);
+       }
+
        /* Room joined */
        gtk_widget_set_sensitive (priv->hpaned, TRUE);
+       gtk_widget_set_sensitive (self->input_text_view, TRUE);
        gtk_widget_grab_focus (self->input_text_view);
 }
 
 static void
 password_infobar_response_cb (GtkWidget *info_bar,
                              gint response_id,
-                             EmpathyChat *self)
+                             PasswordData *data)
 {
-       EmpathyChatPriv *priv = GET_PRIV (self);
-       GtkWidget *entry;
+       EmpathyChatPriv *priv = GET_PRIV (data->self);
        const gchar *password;
 
-       if (response_id != GTK_RESPONSE_OK)
-               goto out;
+       if (response_id != GTK_RESPONSE_OK) {
+               gtk_widget_destroy (info_bar);
+               g_slice_free (PasswordData, data);
+               return;
+       }
 
-       entry = g_object_get_data (G_OBJECT (info_bar), "password-entry");
-       g_assert (entry != NULL);
+       password = gtk_entry_get_text (GTK_ENTRY (data->entry));
 
-       password = gtk_entry_get_text (GTK_ENTRY (entry));
+       tp_channel_provide_password_async (TP_CHANNEL (priv->tp_chat), password,
+                                               provide_password_cb, data);
 
-       empathy_tp_chat_provide_password_async (priv->tp_chat, password,
-                                               provide_password_cb, self);
+       gtk_widget_set_sensitive (data->button, FALSE);
+       gtk_widget_set_sensitive (data->entry, FALSE);
 
- out:
-       gtk_widget_destroy (info_bar);
+       gtk_spinner_start (GTK_SPINNER (data->spinner));
+       gtk_widget_show (data->spinner);
 }
 
 static void
 password_entry_activate_cb (GtkWidget *entry,
-                         GtkWidget *info_bar)
+                         PasswordData *data)
 {
-       gtk_info_bar_response (GTK_INFO_BAR (info_bar), GTK_RESPONSE_OK);
+       gtk_info_bar_response (GTK_INFO_BAR (data->info_bar), GTK_RESPONSE_OK);
 }
 
 static void
 passwd_join_button_cb (GtkButton *button,
-                         GtkWidget *info_bar)
+                         PasswordData *data)
+{
+       gtk_info_bar_response (GTK_INFO_BAR (data->info_bar), GTK_RESPONSE_OK);
+}
+
+static void
+clear_icon_released_cb (GtkEntry *entry,
+                       GtkEntryIconPosition icon_pos,
+                       GdkEvent *event,
+                       PasswordData *data)
+{
+       gtk_entry_set_text (entry, "");
+}
+
+static void
+password_entry_changed_cb (GtkEditable *entry,
+                          PasswordData *data)
+{
+       const gchar *str;
+
+       str = gtk_entry_get_text (GTK_ENTRY (entry));
+
+       gtk_entry_set_icon_sensitive (GTK_ENTRY (entry),
+           GTK_ENTRY_ICON_SECONDARY, !EMP_STR_EMPTY (str));
+}
+
+static void
+infobar_chat_invalidated_cb (TpProxy       *proxy,
+                          guint          domain,
+                          gint           code,
+                          gchar         *message,
+                          gpointer       password_infobar)
 {
-       gtk_info_bar_response (GTK_INFO_BAR (info_bar), GTK_RESPONSE_OK);
+       /* Destroy the password infobar whenever a channel is invalidated
+        * so we don't have multiple infobars when the MUC is rejoined */
+       gtk_widget_destroy (GTK_WIDGET (password_infobar));
 }
 
 static void
-display_password_info_bar (EmpathyChat *self,
-                          gboolean retry)
+display_password_info_bar (EmpathyChat *self)
 {
        EmpathyChatPriv *priv = GET_PRIV (self);
        GtkWidget *info_bar;
@@ -2689,29 +3828,19 @@ display_password_info_bar (EmpathyChat *self,
        GtkWidget *entry;
        GtkWidget *alig;
        GtkWidget *button;
-       GtkMessageType type;
-       const gchar *msg, *button_label;
+       GtkWidget *spinner;
+       PasswordData *data;
 
-       if (retry) {
-               /* Previous password was wrong */
-               type = GTK_MESSAGE_ERROR;
-               msg = _("Wrong password; please try again:");
-               button_label = _("Retry");
-       }
-       else {
-               /* First time we're trying to join */
-               type = GTK_MESSAGE_QUESTION;
-               msg = _("This room is protected by a password:");
-               button_label = _("Join");
-       }
+       data = g_slice_new0 (PasswordData);
 
        info_bar = gtk_info_bar_new ();
-       gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), type);
+       gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar),
+           GTK_MESSAGE_QUESTION);
 
        content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
 
-       hbox = gtk_hbox_new (FALSE, 3);
-       gtk_container_add (GTK_CONTAINER (content_area), hbox);
+       hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5);
+       gtk_box_pack_start (GTK_BOX (content_area), hbox, TRUE, TRUE, 0);
 
        /* Add image */
        image = gtk_image_new_from_stock (GTK_STOCK_DIALOG_AUTHENTICATION,
@@ -2719,7 +3848,7 @@ display_password_info_bar (EmpathyChat *self,
        gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
 
        /* Add message */
-       label = gtk_label_new (msg);
+       label = gtk_label_new (_("This room is protected by a password:"));
        gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
 
        /* Add password entry */
@@ -2727,56 +3856,152 @@ display_password_info_bar (EmpathyChat *self,
        gtk_entry_set_visibility (GTK_ENTRY (entry), FALSE);
        gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
 
+       gtk_entry_set_icon_from_stock (GTK_ENTRY (entry),
+                                      GTK_ENTRY_ICON_SECONDARY, GTK_STOCK_CLEAR);
+       gtk_entry_set_icon_sensitive (GTK_ENTRY (entry),
+                                     GTK_ENTRY_ICON_SECONDARY, FALSE);
+
+       g_signal_connect (entry, "icon-release",
+                         G_CALLBACK (clear_icon_released_cb), data);
+       g_signal_connect (entry, "changed",
+                         G_CALLBACK (password_entry_changed_cb), data);
+
        g_signal_connect (entry, "activate",
-                         G_CALLBACK (password_entry_activate_cb), info_bar);
+                         G_CALLBACK (password_entry_activate_cb), data);
 
        /* Focus the password entry once it's realized */
        g_signal_connect (entry, "realize", G_CALLBACK (gtk_widget_grab_focus), NULL);
 
        /* Add 'Join' button */
-       alig = gtk_alignment_new (0, 0.5, 0, 0);
+       alig = gtk_alignment_new (0, 0.5, 1, 0);
 
-       button = gtk_button_new_with_label (button_label);
+       button = gtk_button_new_with_label (_("Join"));
        gtk_container_add (GTK_CONTAINER (alig), button);
        gtk_box_pack_start (GTK_BOX (hbox), alig, FALSE, FALSE, 0);
 
        g_signal_connect (button, "clicked", G_CALLBACK (passwd_join_button_cb),
-                         info_bar);
+                         data);
 
-       g_object_set_data (G_OBJECT (info_bar), "password-entry", entry);
+       /* Add spinner */
+       spinner = gtk_spinner_new ();
+       gtk_box_pack_end (GTK_BOX (hbox), spinner, FALSE, FALSE, 0);
+
+       /* Save some data for messing around with later */
+       data->self = self;
+       data->info_bar = info_bar;
+       data->button = button;
+       data->label = label;
+       data->entry = entry;
+       data->spinner = spinner;
 
        gtk_box_pack_start (GTK_BOX (priv->info_bar_vbox), info_bar,
-                           FALSE, FALSE, 3);
+                           TRUE, TRUE, 3);
        gtk_widget_show_all (hbox);
 
-       g_signal_connect (info_bar, "response",
-                         G_CALLBACK (password_infobar_response_cb), self);
+       tp_g_signal_connect_object (priv->tp_chat,
+                                 "invalidated", G_CALLBACK (infobar_chat_invalidated_cb),
+                                 info_bar, 0);
+
+       data->response_id = g_signal_connect (info_bar, "response",
+                                             G_CALLBACK (password_infobar_response_cb), data);
 
        gtk_widget_show_all (info_bar);
+       /* ... but hide the spinner */
+       gtk_widget_hide (spinner);
+
+       /* prevent the user from typing anything */
+       gtk_widget_set_sensitive (self->input_text_view, FALSE);
 }
 
 static void
-chat_password_needed_changed_cb (EmpathyChat *self)
+provide_saved_password_cb (GObject *tp_chat,
+                          GAsyncResult *res,
+                          gpointer user_data)
+{
+       EmpathyChat *self = user_data;
+       EmpathyChatPriv *priv = GET_PRIV (self);
+       GError *error = NULL;
+
+       if (!tp_channel_provide_password_finish (TP_CHANNEL (tp_chat), res,
+                                                     &error)) {
+               DEBUG ("error: %s", error->message);
+               /* FIXME: what should we do if that's another error? Close the channel?
+                * Display the raw D-Bus error to the user isn't very useful */
+               if (g_error_matches (error, TP_ERRORS, TP_ERROR_AUTHENTICATION_FAILED)) {
+                       display_password_info_bar (self);
+                       gtk_widget_set_sensitive (priv->hpaned, FALSE);
+               }
+               g_error_free (error);
+               return;
+       }
+
+       /* Room joined */
+       gtk_widget_set_sensitive (priv->hpaned, TRUE);
+       gtk_widget_grab_focus (self->input_text_view);
+}
+
+static void
+chat_room_got_password_cb (GObject *source,
+                          GAsyncResult *result,
+                          gpointer user_data)
 {
+       EmpathyChat *self = user_data;
        EmpathyChatPriv *priv = GET_PRIV (self);
+       const gchar *password;
+       GError *error = NULL;
+
+       password = empathy_keyring_get_room_password_finish (priv->account,
+           result, &error);
 
-       if (empathy_tp_chat_password_needed (priv->tp_chat)) {
-               display_password_info_bar (self, FALSE);
+       if (error != NULL) {
+               DEBUG ("Couldn't get room password: %s\n", error->message);
+               g_clear_error (&error);
+
+               display_password_info_bar (self);
                gtk_widget_set_sensitive (priv->hpaned, FALSE);
+               return;
+       }
+
+       tp_channel_provide_password_async (TP_CHANNEL (priv->tp_chat), password,
+                                               provide_saved_password_cb, self);
+}
+
+static void
+chat_password_needed_changed_cb (EmpathyChat *self)
+{
+       EmpathyChatPriv *priv = GET_PRIV (self);
+
+       if (tp_channel_password_needed (TP_CHANNEL (priv->tp_chat))) {
+               empathy_keyring_get_room_password_async (priv->account,
+                                                        empathy_tp_chat_get_id (priv->tp_chat),
+                                                        chat_room_got_password_cb, self);
        }
 }
 
+static void
+chat_sms_channel_changed_cb (EmpathyChat *self)
+{
+       EmpathyChatPriv *priv = GET_PRIV (self);
+
+       priv->sms_channel = tp_text_channel_is_sms_channel (
+               (TpTextChannel *) priv->tp_chat);
+       g_object_notify (G_OBJECT (self), "sms-channel");
+}
+
+static void
+chat_n_messages_sending_changed_cb (EmpathyChat *self)
+{
+       g_object_notify (G_OBJECT (self), "n-messages-sending");
+}
+
 void
 empathy_chat_set_tp_chat (EmpathyChat   *chat,
                          EmpathyTpChat *tp_chat)
 {
        EmpathyChatPriv *priv = GET_PRIV (chat);
-       TpConnection    *connection;
-       GPtrArray       *properties;
 
        g_return_if_fail (EMPATHY_IS_CHAT (chat));
        g_return_if_fail (EMPATHY_IS_TP_CHAT (tp_chat));
-       g_return_if_fail (empathy_tp_chat_is_ready (tp_chat));
 
        if (priv->tp_chat) {
                return;
@@ -2787,57 +4012,57 @@ empathy_chat_set_tp_chat (EmpathyChat   *chat,
        }
 
        priv->tp_chat = g_object_ref (tp_chat);
-       connection = empathy_tp_chat_get_connection (priv->tp_chat);
-       priv->account = g_object_ref (empathy_get_account_for_connection (connection));
+       priv->account = g_object_ref (empathy_tp_chat_get_account (priv->tp_chat));
 
-       g_signal_connect (tp_chat, "destroy",
-                         G_CALLBACK (chat_destroy_cb),
+       g_signal_connect (tp_chat, "invalidated",
+                         G_CALLBACK (chat_invalidated_cb),
                          chat);
-       g_signal_connect (tp_chat, "message-received",
+       g_signal_connect (tp_chat, "message-received-empathy",
                          G_CALLBACK (chat_message_received_cb),
                          chat);
+       g_signal_connect (tp_chat, "message_acknowledged",
+                         G_CALLBACK (chat_message_acknowledged_cb),
+                         chat);
        g_signal_connect (tp_chat, "send-error",
                          G_CALLBACK (chat_send_error_cb),
                          chat);
-       g_signal_connect (tp_chat, "chat-state-changed",
+       g_signal_connect (tp_chat, "chat-state-changed-empathy",
                          G_CALLBACK (chat_state_changed_cb),
                          chat);
-       g_signal_connect (tp_chat, "property-changed",
-                         G_CALLBACK (chat_property_changed_cb),
-                         chat);
        g_signal_connect (tp_chat, "members-changed",
                          G_CALLBACK (chat_members_changed_cb),
                          chat);
        g_signal_connect (tp_chat, "member-renamed",
                          G_CALLBACK (chat_member_renamed_cb),
                          chat);
+       g_signal_connect_swapped (tp_chat, "notify::self-contact",
+                                 G_CALLBACK (chat_self_contact_changed_cb),
+                                 chat);
        g_signal_connect_swapped (tp_chat, "notify::remote-contact",
                                  G_CALLBACK (chat_remote_contact_changed_cb),
                                  chat);
        g_signal_connect_swapped (tp_chat, "notify::password-needed",
                                  G_CALLBACK (chat_password_needed_changed_cb),
                                  chat);
+       g_signal_connect_swapped (tp_chat, "notify::is-sms-channel",
+                                 G_CALLBACK (chat_sms_channel_changed_cb),
+                                 chat);
+       g_signal_connect_swapped (tp_chat, "notify::n-messages-sending",
+                                 G_CALLBACK (chat_n_messages_sending_changed_cb),
+                                 chat);
+       g_signal_connect_swapped (tp_chat, "notify::title",
+                                 G_CALLBACK (chat_title_changed_cb),
+                                 chat);
+       g_signal_connect_swapped (tp_chat, "notify::subject",
+                                 G_CALLBACK (chat_subject_changed_cb),
+                                 chat);
 
        /* Get initial value of properties */
-       properties = empathy_tp_chat_get_properties (priv->tp_chat);
-       if (properties != NULL) {
-               guint i;
-
-               for (i = 0; i < properties->len; i++) {
-                       EmpathyTpChatProperty *property;
-
-                       property = g_ptr_array_index (properties, i);
-                       if (property->value == NULL)
-                               continue;
-
-                       chat_property_changed_cb (priv->tp_chat,
-                                                 property->name,
-                                                 property->value,
-                                                 chat);
-               }
-       }
-
+       chat_sms_channel_changed_cb (chat);
+       chat_self_contact_changed_cb (chat);
        chat_remote_contact_changed_cb (chat);
+       chat_title_changed_cb (chat);
+       chat_subject_changed_cb (chat);
 
        if (chat->input_text_view) {
                gtk_widget_set_sensitive (chat->input_text_view, TRUE);
@@ -2879,8 +4104,8 @@ empathy_chat_get_id (EmpathyChat *chat)
        return priv->id;
 }
 
-const gchar *
-empathy_chat_get_name (EmpathyChat *chat)
+gchar *
+empathy_chat_dup_name (EmpathyChat *chat)
 {
        EmpathyChatPriv *priv = GET_PRIV (chat);
        const gchar *ret;
@@ -2888,14 +4113,23 @@ empathy_chat_get_name (EmpathyChat *chat)
        g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
 
        ret = priv->name;
+
        if (!ret && priv->remote_contact) {
-               ret = empathy_contact_get_name (priv->remote_contact);
+               ret = empathy_contact_get_alias (priv->remote_contact);
        }
 
        if (!ret)
                ret = priv->id;
 
-       return ret ? ret : _("Conversation");
+       if (!ret)
+               ret = _("Conversation");
+
+       if (priv->sms_channel)
+               /* Translators: this string is a something like
+                * "Escher Cat (SMS)" */
+               return g_strdup_printf (_("%s (SMS)"), ret);
+       else
+               return g_strdup (ret);
 }
 
 const gchar *
@@ -2923,21 +4157,30 @@ empathy_chat_get_contact_menu (EmpathyChat *chat)
 {
        EmpathyChatPriv *priv = GET_PRIV (chat);
        GtkWidget       *menu = NULL;
+       FolksIndividual *individual;
+       TpContact *contact;
 
        g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
 
-       if (priv->remote_contact) {
-               menu = empathy_contact_menu_new (priv->remote_contact,
-                                                EMPATHY_CONTACT_FEATURE_CALL |
-                                                EMPATHY_CONTACT_FEATURE_LOG |
-                                                EMPATHY_CONTACT_FEATURE_INFO);
-       }
-       else if (priv->contact_list_view) {
-               EmpathyContactListView *view;
+       if (priv->remote_contact == NULL)
+               return NULL;
 
-               view = EMPATHY_CONTACT_LIST_VIEW (priv->contact_list_view);
-               menu = empathy_contact_list_view_get_contact_menu (view);
-       }
+       contact = empathy_contact_get_tp_contact (priv->remote_contact);
+       if (contact == NULL)
+               return NULL;
+
+       individual = empathy_ensure_individual_from_tp_contact (contact);
+
+       if (individual == NULL)
+               return NULL;
+
+       menu = empathy_individual_menu_new (individual,
+                                        EMPATHY_INDIVIDUAL_FEATURE_CALL |
+                                        EMPATHY_INDIVIDUAL_FEATURE_LOG |
+                                        EMPATHY_INDIVIDUAL_FEATURE_INFO |
+                                        EMPATHY_INDIVIDUAL_FEATURE_BLOCK, NULL);
+
+       g_object_unref (individual);
 
        return menu;
 }
@@ -2995,6 +4238,31 @@ empathy_chat_copy (EmpathyChat *chat)
 
                gtk_text_buffer_copy_clipboard (buffer, clipboard);
        }
+       else {
+               gint start_offset;
+               gint end_offset;
+               EmpathyChatPriv *priv = GET_PRIV (chat);
+
+               if (gtk_label_get_selection_bounds (GTK_LABEL (priv->label_topic),
+                                                              &start_offset,
+                                                              &end_offset)) {
+                       gchar *start;
+                       gchar *end;
+                       gchar *selection;
+                       const gchar *topic;
+                       GtkClipboard *clipboard;
+
+                       topic = gtk_label_get_text (GTK_LABEL (priv->label_topic));
+                       start = g_utf8_offset_to_pointer (topic, start_offset);
+                       end = g_utf8_offset_to_pointer (topic, end_offset);
+                       selection = g_strndup (start, end - start);
+
+                       clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+                       gtk_clipboard_set_text (clipboard, selection, -1);
+
+                       g_free (selection);
+               }
+       }
 }
 
 void
@@ -3008,8 +4276,13 @@ empathy_chat_paste (EmpathyChat *chat)
 
        priv = GET_PRIV (chat);
 
+       if (gtk_widget_get_visible (priv->search_bar)) {
+               empathy_search_bar_paste_clipboard (EMPATHY_SEARCH_BAR (priv->search_bar));
+               return;
+       }
+
        if (priv->tp_chat == NULL ||
-           !GTK_WIDGET_IS_SENSITIVE (chat->input_text_view))
+           !gtk_widget_is_sensitive (chat->input_text_view))
                return;
 
        buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
@@ -3059,6 +4332,16 @@ empathy_chat_is_room (EmpathyChat *chat)
        return (priv->handle_type == TP_HANDLE_TYPE_ROOM);
 }
 
+gboolean
+empathy_chat_is_highlighted (EmpathyChat *chat)
+{
+       EmpathyChatPriv *priv = GET_PRIV (chat);
+
+       g_return_val_if_fail (EMPATHY_IS_CHAT (chat), FALSE);
+
+       return priv->highlighted;
+}
+
 guint
 empathy_chat_get_nb_unread_messages (EmpathyChat *self)
 {
@@ -3082,8 +4365,80 @@ empathy_chat_messages_read (EmpathyChat *self)
        if (priv->retrieving_backlogs)
                return;
 
-       if (priv->tp_chat != NULL ) {
-                       empathy_tp_chat_acknowledge_all_messages (priv->tp_chat);
+       if (priv->tp_chat != NULL) {
+               tp_text_channel_ack_all_pending_messages_async (
+                       TP_TEXT_CHANNEL (priv->tp_chat), NULL, NULL);
+       }
+
+       priv->highlighted = FALSE;
+
+       if (priv->unread_messages_when_offline > 0) {
+               /* We can't ack those as the connection has gone away so just consider
+               * them as read. */
+               priv->unread_messages -= priv->unread_messages_when_offline;
+               g_object_notify (G_OBJECT (self), "nb-unread-messages");
+               priv->unread_messages_when_offline = 0;
+       }
+}
+
+/* Return TRUE if on of the contacts in this chat is composing */
+gboolean
+empathy_chat_is_composing (EmpathyChat *chat)
+{
+  return chat->priv->compositors != NULL;
+}
+
+gboolean
+empathy_chat_is_sms_channel (EmpathyChat *self)
+{
+       EmpathyChatPriv *priv = GET_PRIV (self);
+
+       g_return_val_if_fail (EMPATHY_IS_CHAT (self), 0);
+
+       return priv->sms_channel;
+}
+
+guint
+empathy_chat_get_n_messages_sending (EmpathyChat *self)
+{
+       EmpathyChatPriv *priv;
+
+       g_return_val_if_fail (EMPATHY_IS_CHAT (self), 0);
+
+       priv = GET_PRIV (self);
+
+       if (priv->tp_chat == NULL) {
+               return 0;
+       } else {
+               guint n_messages;
+
+               g_object_get (priv->tp_chat,
+                       "n-messages-sending", &n_messages,
+                       NULL);
+
+               return n_messages;
        }
-       priv->unread_messages = 0;
+}
+
+gchar *
+empathy_chat_dup_text (EmpathyChat *self)
+{
+       GtkTextBuffer *buffer;
+       GtkTextIter     start, end;
+
+       buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self->input_text_view));
+
+       gtk_text_buffer_get_bounds (buffer, &start, &end);
+       return gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
+}
+
+void
+empathy_chat_set_text (EmpathyChat *self,
+                      const gchar *text)
+{
+       GtkTextBuffer *buffer;
+
+       buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self->input_text_view));
+
+       gtk_text_buffer_set_text (buffer, text, -1);
 }