]> git.0d.be Git - empathy.git/blobdiff - libempathy-gtk/empathy-chat.c
Merge commit 'jtellier/confirm-lose-accounts-settings'
[empathy.git] / libempathy-gtk / empathy-chat.c
index 35b91f876ec6813b392d232014e382ab97c59522..c08b49c3fbe7a21b22be279d24dff67eb490e34f 100644 (file)
@@ -15,9 +15,9 @@
  *
  * You should have received a copy of the GNU General Public
  * License along with this program; if not, write to the
- * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- * Boston, MA 02111-1307, USA.
- * 
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA  02110-1301  USA
+ *
  * Authors: Mikael Hallendal <micke@imendio.com>
  *          Richard Hult <richard@imendio.com>
  *          Martyn Russell <martyn@imendio.com>
 #include <libempathy/empathy-log-manager.h>
 #include <libempathy/empathy-contact-list.h>
 #include <libempathy/empathy-utils.h>
+#include <libempathy/empathy-dispatcher.h>
 
 #include "empathy-chat.h"
 #include "empathy-conf.h"
 #include "empathy-spell.h"
-#include "empathy-spell-dialog.h"
 #include "empathy-contact-list-store.h"
 #include "empathy-contact-list-view.h"
 #include "empathy-contact-menu.h"
 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyChat)
 typedef struct {
        EmpathyTpChat     *tp_chat;
-       McAccount         *account;
+       EmpathyAccount    *account;
        gchar             *id;
        gchar             *name;
        gchar             *subject;
        EmpathyContact    *remote_contact;
+       gboolean           show_contacts;
 
        EmpathyLogManager *log_manager;
        EmpathyAccountManager *account_manager;
@@ -107,6 +108,7 @@ enum {
        PROP_NAME,
        PROP_SUBJECT,
        PROP_REMOTE_CONTACT,
+       PROP_SHOW_CONTACTS,
 };
 
 static guint signals[LAST_SIGNAL] = { 0 };
@@ -141,6 +143,9 @@ chat_get_property (GObject    *object,
        case PROP_REMOTE_CONTACT:
                g_value_set_object (value, priv->remote_contact);
                break;
+       case PROP_SHOW_CONTACTS:
+               g_value_set_boolean (value, priv->show_contacts);
+               break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
                break;
@@ -159,6 +164,9 @@ chat_set_property (GObject      *object,
        case PROP_TP_CHAT:
                empathy_chat_set_tp_chat (chat, EMPATHY_TP_CHAT (g_value_get_object (value)));
                break;
+       case PROP_SHOW_CONTACTS:
+               empathy_chat_set_show_contacts (chat, g_value_get_boolean (value));
+               break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
                break;
@@ -166,33 +174,60 @@ chat_set_property (GObject      *object,
 }
 
 static void
-chat_connection_changed_cb (EmpathyAccountManager *manager,
-                           McAccount *account,
-                           TpConnectionStatusReason reason,
-                           TpConnectionStatus current,
-                           TpConnectionStatus previous,
-                           EmpathyChat *chat)
+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
+chat_new_connection_cb (EmpathyAccountManager *manager,
+                       TpConnection *connection,
+                       EmpathyChat *chat)
 {
        EmpathyChatPriv *priv = GET_PRIV (chat);
+       EmpathyAccount *account;
 
-       if (current == TP_CONNECTION_STATUS_CONNECTED && !priv->tp_chat &&
-           empathy_account_equal (account, priv->account) &&
-           priv->handle_type != TP_HANDLE_TYPE_NONE) {
-               TpConnection *connection;
-               MissionControl *mc;
+       account = empathy_account_manager_get_account_for_connection (manager,
+               connection);
+       if (!priv->tp_chat && account == priv->account &&
+           priv->handle_type != TP_HANDLE_TYPE_NONE &&
+           !EMP_STR_EMPTY (priv->id)) {
 
                DEBUG ("Account reconnected, request a new Text channel");
 
-               mc = empathy_mission_control_new ();
-               connection = mission_control_get_tpconnection (mc, account, NULL);
-               tp_connection_run_until_ready (connection, FALSE, NULL, NULL);
-               empathy_connection_request_channel (connection, -1,
-                                                   TP_IFACE_CHANNEL_TYPE_TEXT,
-                                                   priv->handle_type,
-                                                   priv->id, TRUE,
-                                                   NULL, NULL, NULL, NULL);
-               g_object_unref (connection);
-               g_object_unref (mc);
+               switch (priv->handle_type) {
+                       case TP_HANDLE_TYPE_CONTACT:
+                               empathy_dispatcher_chat_with_contact_id (
+                                       connection, priv->id,
+                                       chat_connect_channel_reconnected,
+                                       chat);
+                               break;
+                       case TP_HANDLE_TYPE_ROOM:
+                               empathy_dispatcher_join_muc (connection,
+                                       priv->id,
+                                       chat_connect_channel_reconnected,
+                                       chat);
+                               break;
+                       default:
+                               g_assert_not_reached ();
+                               break;
+               }
        }
 }
 
@@ -256,7 +291,7 @@ chat_composing_stop (EmpathyChat *chat)
                                   TP_CHANNEL_CHAT_STATE_ACTIVE);
 }
 
-static void 
+static void
 chat_sent_message_add (EmpathyChat  *chat,
                       const gchar *str)
 {
@@ -268,7 +303,7 @@ chat_sent_message_add (EmpathyChat  *chat,
 
        /* Save the sent message in our repeat buffer */
        list = priv->sent_messages;
-       
+
        /* Remove any other occurances of this msg */
        while ((item = g_slist_find_custom (list, str, (GCompareFunc) strcmp)) != NULL) {
                list = g_slist_remove_link (list, item);
@@ -299,7 +334,7 @@ chat_sent_message_get_next (EmpathyChat *chat)
 {
        EmpathyChatPriv *priv;
        gint            max;
-       
+
        priv = GET_PRIV (chat);
 
        if (!priv->sent_messages) {
@@ -312,7 +347,7 @@ chat_sent_message_get_next (EmpathyChat *chat)
        if (priv->sent_messages_index < max) {
                priv->sent_messages_index++;
        }
-       
+
        DEBUG ("Returning next message index:%d", priv->sent_messages_index);
 
        return g_slist_nth_data (priv->sent_messages, priv->sent_messages_index);
@@ -326,7 +361,7 @@ chat_sent_message_get_last (EmpathyChat *chat)
        g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
 
        priv = GET_PRIV (chat);
-       
+
        if (!priv->sent_messages) {
                DEBUG ("No sent messages, last message is NULL");
                return NULL;
@@ -348,24 +383,28 @@ chat_send (EmpathyChat  *chat,
        EmpathyChatPriv *priv;
        EmpathyMessage  *message;
 
-       priv = GET_PRIV (chat);
-
-       if (G_STR_EMPTY (msg)) {
+       if (EMP_STR_EMPTY (msg)) {
                return;
        }
 
+       priv = GET_PRIV (chat);
+
        chat_sent_message_add (chat, msg);
 
-       if (g_str_has_prefix (msg, "/clear")) {
+       if (strcmp (msg, "/clear") == 0) {
                empathy_chat_view_clear (chat->view);
                return;
        }
 
-       message = empathy_message_new (msg);
-
-       empathy_tp_chat_send (priv->tp_chat, message);
+       message = empathy_message_new_from_entry (msg);
 
-       g_object_unref (message);
+       if (message == NULL) {
+               empathy_chat_view_append_event (chat->view,
+                       _("Unsupported command"));
+       } else {
+               empathy_tp_chat_send (priv->tp_chat, message);
+               g_object_unref (message);
+       }
 }
 
 static void
@@ -452,9 +491,7 @@ chat_state_changed_cb (EmpathyTpChat      *tp_chat,
 }
 
 static void
-chat_message_received_cb (EmpathyTpChat  *tp_chat,
-                         EmpathyMessage *message,
-                         EmpathyChat    *chat)
+chat_message_received (EmpathyChat *chat, EmpathyMessage *message)
 {
        EmpathyChatPriv *priv = GET_PRIV (chat);
        EmpathyContact  *sender;
@@ -465,28 +502,28 @@ chat_message_received_cb (EmpathyTpChat  *tp_chat,
                empathy_contact_get_name (sender),
                empathy_contact_get_handle (sender));
 
-       if (priv->id) {
-               gboolean is_chatroom;
-
-               is_chatroom = priv->handle_type == TP_HANDLE_TYPE_ROOM;
-               empathy_log_manager_add_message (priv->log_manager,
-                                                priv->id, is_chatroom,
-                                                message);
-       }
-
        empathy_chat_view_append_message (chat->view, message);
 
-       /* We received a message so the contact is no more composing */
-       chat_state_changed_cb (tp_chat, sender,
+       /* 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_signal_emit (chat, signals[NEW_MESSAGE], 0, message);
 }
 
+static void
+chat_message_received_cb (EmpathyTpChat  *tp_chat,
+                         EmpathyMessage *message,
+                         EmpathyChat    *chat)
+{
+       chat_message_received (chat, message);
+       empathy_tp_chat_acknowledge_message (tp_chat, message);
+}
+
 static void
 chat_send_error_cb (EmpathyTpChat          *tp_chat,
-                   EmpathyMessage         *message,
+                   const gchar            *message_body,
                    TpChannelTextSendError  error_code,
                    EmpathyChat            *chat)
 {
@@ -515,7 +552,7 @@ chat_send_error_cb (EmpathyTpChat          *tp_chat,
        }
 
        str = g_strdup_printf (_("Error sending message '%s': %s"),
-                              empathy_message_get_body (message),
+                              message_body,
                               error);
        empathy_chat_view_append_event (chat->view, str);
        g_free (str);
@@ -534,7 +571,7 @@ chat_property_changed_cb (EmpathyTpChat *tp_chat,
                priv->subject = g_value_dup_string (value);
                g_object_notify (G_OBJECT (chat), "subject");
 
-               if (G_STR_EMPTY (priv->subject)) {
+               if (EMP_STR_EMPTY (priv->subject)) {
                        gtk_widget_hide (priv->hbox_topic);
                } else {
                        gtk_label_set_text (GTK_LABEL (priv->label_topic), priv->subject);
@@ -543,7 +580,7 @@ chat_property_changed_cb (EmpathyTpChat *tp_chat,
                if (priv->block_events_timeout_id == 0) {
                        gchar *str;
 
-                       if (!G_STR_EMPTY (priv->subject)) {
+                       if (!EMP_STR_EMPTY (priv->subject)) {
                                str = g_strdup_printf (_("Topic set to: %s"), priv->subject);
                        } else {
                                str = g_strdup (_("No topic defined"));
@@ -559,31 +596,9 @@ chat_property_changed_cb (EmpathyTpChat *tp_chat,
        }
 }
 
-static gboolean
-chat_get_is_command (const gchar *str)
-{
-       g_return_val_if_fail (str != NULL, FALSE);
-
-       if (str[0] != '/') {
-               return FALSE;
-       }
-
-       if (g_str_has_prefix (str, "/me")) {
-               return TRUE;
-       }
-       else if (g_str_has_prefix (str, "/nick")) {
-               return TRUE;
-       }
-       else if (g_str_has_prefix (str, "/topic")) {
-               return TRUE;
-       }
-
-       return FALSE;
-}
-
 static void
 chat_input_text_buffer_changed_cb (GtkTextBuffer *buffer,
-                                  EmpathyChat    *chat)
+                                   EmpathyChat    *chat)
 {
        EmpathyChatPriv *priv;
        GtkTextIter     start, end;
@@ -599,8 +614,8 @@ chat_input_text_buffer_changed_cb (GtkTextBuffer *buffer,
        }
 
        empathy_conf_get_bool (empathy_conf_get (),
-                             EMPATHY_PREFS_CHAT_SPELL_CHECKER_ENABLED,
-                             &spell_checker);
+                           EMPATHY_PREFS_CHAT_SPELL_CHECKER_ENABLED,
+                           &spell_checker);
 
        gtk_text_buffer_get_start_iter (buffer, &start);
 
@@ -640,8 +655,8 @@ chat_input_text_buffer_changed_cb (GtkTextBuffer *buffer,
 
                str = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
 
-               /* spell check string */
-               if (!chat_get_is_command (str)) {
+               /* spell check string if not a command */
+               if (str[0] != '/') {
                        correct = empathy_spell_check (str);
                } else {
                        correct = TRUE;
@@ -673,8 +688,8 @@ chat_input_key_press_event_cb (GtkWidget   *widget,
        priv = GET_PRIV (chat);
 
        /* Catch ctrl+up/down so we can traverse messages we sent */
-       if ((event->state & GDK_CONTROL_MASK) && 
-           (event->keyval == GDK_Up || 
+       if ((event->state & GDK_CONTROL_MASK) &&
+           (event->keyval == GDK_Up ||
             event->keyval == GDK_Down)) {
                GtkTextBuffer *buffer;
                const gchar   *str;
@@ -687,15 +702,15 @@ chat_input_key_press_event_cb (GtkWidget   *widget,
                        str = chat_sent_message_get_last (chat);
                }
 
-               g_signal_handlers_block_by_func (buffer, 
+               g_signal_handlers_block_by_func (buffer,
                                                 chat_input_text_buffer_changed_cb,
                                                 chat);
                gtk_text_buffer_set_text (buffer, str ? str : "", -1);
-               g_signal_handlers_unblock_by_func (buffer, 
+               g_signal_handlers_unblock_by_func (buffer,
                                                   chat_input_text_buffer_changed_cb,
                                                   chat);
 
-               return TRUE;    
+               return TRUE;
        }
 
        /* Catch enter but not ctrl/shift-enter */
@@ -729,13 +744,14 @@ chat_input_key_press_event_cb (GtkWidget   *widget,
        if (!(event->state & GDK_CONTROL_MASK) &&
            event->keyval == GDK_Page_Up) {
                adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (text_view_sw));
-               gtk_adjustment_set_value (adj, adj->value - adj->page_size);
+               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) {
                adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (text_view_sw));
-               val = MIN (adj->value + adj->page_size, adj->upper - adj->page_size);
+               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;
        }
@@ -919,13 +935,42 @@ chat_spell_free (EmpathyChatSpell *chat_spell)
 }
 
 static void
-chat_text_check_word_spelling_cb (GtkMenuItem     *menuitem,
-                                 EmpathyChatSpell *chat_spell)
+chat_spelling_menu_activate_cb (GtkMenuItem     *menu_item,
+                                               EmpathyChatSpell *chat_spell)
 {
-       empathy_spell_dialog_show (chat_spell->chat,
-                                 &chat_spell->start,
-                                 &chat_spell->end,
-                                 chat_spell->word);
+    empathy_chat_correct_word (chat_spell->chat,
+                               &(chat_spell->start),
+                               &(chat_spell->end),
+                               gtk_menu_item_get_label (menu_item));
+}
+
+static GtkWidget *
+chat_spelling_build_menu (EmpathyChatSpell *chat_spell)
+{
+    GtkWidget *menu, *menu_item;
+    GList     *suggestions, *l;
+
+    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);
+
+    gtk_widget_show_all (menu);
+
+    return menu;
 }
 
 static void
@@ -948,7 +993,8 @@ chat_input_populate_popup_cb (GtkTextView *view,
        GtkTextIter           iter, start, end;
        GtkWidget            *item;
        gchar                *str = NULL;
-       EmpathyChatSpell      *chat_spell;
+       EmpathyChatSpell     *chat_spell;
+       GtkWidget            *spell_menu;
        EmpathySmileyManager *smiley_manager;
        GtkWidget            *smiley_menu;
        GtkWidget            *image;
@@ -968,7 +1014,7 @@ chat_input_populate_popup_cb (GtkTextView *view,
        gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
        gtk_widget_show (item);
 
-       smiley_manager = empathy_smiley_manager_new ();
+       smiley_manager = empathy_smiley_manager_dup_singleton ();
        smiley_menu = empathy_smiley_menu_new (smiley_manager,
                                               chat_insert_smiley_activate_cb,
                                               chat);
@@ -978,7 +1024,7 @@ chat_input_populate_popup_cb (GtkTextView *view,
        /* Add the Send menu item. */
        gtk_text_buffer_get_bounds (buffer, &start, &end);
        str = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
-       if (!G_STR_EMPTY (str)) {
+       if (!EMP_STR_EMPTY (str)) {
                item = gtk_menu_item_new_with_mnemonic (_("_Send"));
                g_signal_connect (G_OBJECT (item), "activate",
                                  G_CALLBACK (chat_text_send_cb), chat);
@@ -1003,7 +1049,7 @@ chat_input_populate_popup_cb (GtkTextView *view,
                str = gtk_text_buffer_get_text (buffer,
                                                &start, &end, FALSE);
        }
-       if (!G_STR_EMPTY (str)) {
+       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,
@@ -1013,27 +1059,44 @@ chat_input_populate_popup_cb (GtkTextView *view,
                gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
                gtk_widget_show (item);
 
-               item = gtk_image_menu_item_new_with_mnemonic (_("_Check Word Spelling..."));
+               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);
                gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
-               g_signal_connect (item,
-                                 "activate",
-                                 G_CALLBACK (chat_text_check_word_spelling_cb),
-                                 chat_spell);
+
+               spell_menu = chat_spelling_build_menu (chat_spell);
+               gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), spell_menu);
+
                gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
                gtk_widget_show (item);
        }
 }
 
+static gboolean
+chat_log_filter (EmpathyMessage *message,
+                gpointer user_data)
+{
+       EmpathyChat *chat = (EmpathyChat *) user_data;
+       EmpathyChatPriv *priv = GET_PRIV (chat);
+       const GList *pending;
+
+       pending = empathy_tp_chat_get_pending_messages (priv->tp_chat);
+
+       for (; pending; pending = g_list_next (pending)) {
+               if (empathy_message_equal (message, pending->data)) {
+                       return FALSE;
+               }
+       }
+
+       return TRUE;
+}
+
 static void
 chat_add_logs (EmpathyChat *chat)
 {
        EmpathyChatPriv *priv = GET_PRIV (chat);
        gboolean         is_chatroom;
        GList           *messages, *l;
-       guint            num_messages;
-       guint            i;
 
        if (!priv->id) {
                return;
@@ -1044,25 +1107,20 @@ chat_add_logs (EmpathyChat *chat)
 
        /* Add messages from last conversation */
        is_chatroom = priv->handle_type == TP_HANDLE_TYPE_ROOM;
-       messages = empathy_log_manager_get_last_messages (priv->log_manager,
-                                                         priv->account,
-                                                         priv->id,
-                                                         is_chatroom);
-       num_messages  = g_list_length (messages);
-
-       /* Only keep the 10 last messages */
-       for (i = 0; num_messages - i > 10; i++) {
-               EmpathyMessage *message;
-
-               message = messages->data;
-               messages = g_list_remove (messages, message);
-               g_object_unref (message);
-       }
 
-       for (l = messages; l; l = l->next) {
+       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);
 
        /* Turn back on scrolling */
@@ -1100,6 +1158,62 @@ chat_contacts_completion_func (const gchar *s1,
        return ret;
 }
 
+static gchar *
+build_part_message (guint           reason,
+                   const gchar    *name,
+                   EmpathyContact *actor,
+                   const gchar    *message)
+{
+       GString *s = g_string_new ("");
+       const gchar *actor_name = NULL;
+
+       if (actor != NULL) {
+               actor_name = empathy_contact_get_name (actor);
+       }
+
+       /* Having an actor only really makes sense for a few actions... */
+       switch (reason) {
+       case TP_CHANNEL_GROUP_CHANGE_REASON_OFFLINE:
+               g_string_append_printf (s, _("%s has disconnected"), name);
+               break;
+       case TP_CHANNEL_GROUP_CHANGE_REASON_KICKED:
+               if (actor_name != NULL) {
+                       /* translators: reverse the order of these arguments
+                        * if the kicked should come before the kicker in your locale.
+                        */
+                       g_string_append_printf (s, _("%1$s was kicked by %2$s"),
+                               name, actor_name);
+               } else {
+                       g_string_append_printf (s, _("%s was kicked"), name);
+               }
+               break;
+       case TP_CHANNEL_GROUP_CHANGE_REASON_BANNED:
+               if (actor_name != NULL) {
+                       /* translators: reverse the order of these arguments
+                        * if the banned should come before the banner in your locale.
+                        */
+                       g_string_append_printf (s, _("%1$s was banned by %2$s"),
+                               name, actor_name);
+               } else {
+                       g_string_append_printf (s, _("%s was banned"), name);
+               }
+               break;
+       default:
+               g_string_append_printf (s, _("%s has left the room"), name);
+       }
+
+       if (!EMP_STR_EMPTY (message)) {
+               /* Note to translators: this string is appended to
+                * notifications like "foo has left the room", with the message
+                * given by the user living the room. If this poses a problem,
+                * please let us know. :-)
+                */
+               g_string_append_printf (s, _(" (%s)"), message);
+       }
+
+       return g_string_free (s, FALSE);
+}
+
 static void
 chat_members_changed_cb (EmpathyTpChat  *tp_chat,
                         EmpathyContact *contact,
@@ -1110,24 +1224,21 @@ chat_members_changed_cb (EmpathyTpChat  *tp_chat,
                         EmpathyChat    *chat)
 {
        EmpathyChatPriv *priv = GET_PRIV (chat);
+       const gchar *name = empathy_contact_get_name (contact);
+       gchar *str;
 
-       if (priv->block_events_timeout_id == 0) {
-               gchar *str;
-
-               empathy_contact_run_until_ready (contact,
-                                                EMPATHY_CONTACT_READY_NAME,
-                                                NULL);
+       if (priv->block_events_timeout_id != 0)
+               return;
 
-               if (is_member) {
-                       str = g_strdup_printf (_("%s has joined the room"),
-                                              empathy_contact_get_name (contact));
-               } else {
-                       str = g_strdup_printf (_("%s has left the room"),
-                                              empathy_contact_get_name (contact));
-               }
-               empathy_chat_view_append_event (chat->view, str);
-               g_free (str);
+       if (is_member) {
+               str = g_strdup_printf (_("%s has joined the room"),
+                                      name);
+       } else {
+               str = build_part_message (reason, name, actor, message);
        }
+
+       empathy_chat_view_append_event (chat->view, str);
+       g_free (str);
 }
 
 static gboolean
@@ -1139,15 +1250,18 @@ chat_reset_size_request (gpointer widget)
 }
 
 static void
-chat_set_show_contacts (EmpathyChat *chat, gboolean show)
+chat_update_contacts_visibility (EmpathyChat *chat)
 {
        EmpathyChatPriv *priv = GET_PRIV (chat);
+       gboolean show;
+
+       show = priv->remote_contact == NULL && priv->show_contacts;
 
        if (!priv->scrolled_window_contacts) {
                return;
        }
 
-       if (show) {
+       if (show && priv->contact_list_view == NULL) {
                EmpathyContactListStore *store;
                gint                     min_width;
 
@@ -1168,7 +1282,7 @@ chat_set_show_contacts (EmpathyChat *chat, gboolean show)
 
                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_NONE,
+                       EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP,
                        EMPATHY_CONTACT_FEATURE_CHAT |
                        EMPATHY_CONTACT_FEATURE_CALL |
                        EMPATHY_CONTACT_FEATURE_LOG |
@@ -1178,34 +1292,47 @@ chat_set_show_contacts (EmpathyChat *chat, gboolean show)
                gtk_widget_show (priv->contact_list_view);
                gtk_widget_show (priv->scrolled_window_contacts);
                g_object_unref (store);
-       } else {
+       } else if (!show) {
                priv->contacts_width = gtk_paned_get_position (GTK_PANED (priv->hpaned));
                gtk_widget_hide (priv->scrolled_window_contacts);
-               if (priv->contact_list_view) {
+               if (priv->contact_list_view != NULL) {
                        gtk_widget_destroy (priv->contact_list_view);
                        priv->contact_list_view = NULL;
                }
        }
 }
 
+void
+empathy_chat_set_show_contacts (EmpathyChat *chat,
+                               gboolean     show)
+{
+       EmpathyChatPriv *priv = GET_PRIV (chat);
+
+       priv->show_contacts = show;
+
+       chat_update_contacts_visibility (chat);
+
+       g_object_notify (G_OBJECT (chat), "show-contacts");
+}
+
 static void
 chat_remote_contact_changed_cb (EmpathyChat *chat)
 {
        EmpathyChatPriv *priv = GET_PRIV (chat);
 
-       if (priv->remote_contact) {
+       if (priv->remote_contact != NULL) {
                g_object_unref (priv->remote_contact);
                priv->remote_contact = NULL;
        }
 
        priv->remote_contact = empathy_tp_chat_get_remote_contact (priv->tp_chat);
-       if (priv->remote_contact) {
+       if (priv->remote_contact != NULL) {
                g_object_ref (priv->remote_contact);
                priv->handle_type = TP_HANDLE_TYPE_CONTACT;
                g_free (priv->id);
                priv->id = g_strdup (empathy_contact_get_id (priv->remote_contact));
        }
-       else if (priv->tp_chat) {
+       else if (priv->tp_chat != NULL) {
                TpChannel *channel;
 
                channel = empathy_tp_chat_get_channel (priv->tp_chat);
@@ -1214,7 +1341,7 @@ chat_remote_contact_changed_cb (EmpathyChat *chat)
                priv->id = g_strdup (empathy_tp_chat_get_id (priv->tp_chat));
        }
 
-       chat_set_show_contacts (chat, priv->remote_contact == NULL);
+       chat_update_contacts_visibility (chat);
 
        g_object_notify (G_OBJECT (chat), "remote-contact");
        g_object_notify (G_OBJECT (chat), "id");
@@ -1232,29 +1359,45 @@ chat_destroy_cb (EmpathyTpChat *tp_chat,
                return;
        }
 
+       chat_composing_remove_timeout (chat);
        g_object_unref (priv->tp_chat);
        priv->tp_chat = NULL;
        g_object_notify (G_OBJECT (chat), "tp-chat");
 
        empathy_chat_view_append_event (chat->view, _("Disconnected"));
        gtk_widget_set_sensitive (chat->input_text_view, FALSE);
-       chat_set_show_contacts (chat, FALSE);
+       empathy_chat_set_show_contacts (chat, FALSE);
+}
+
+static void
+show_pending_messages (EmpathyChat *chat) {
+       EmpathyChatPriv *priv = GET_PRIV (chat);
+       const GList *messages, *l;
+
+       if (chat->view == NULL || priv->tp_chat == NULL)
+               return;
+
+       messages = empathy_tp_chat_get_pending_messages (priv->tp_chat);
+
+       for (l = messages; l != NULL ; l = g_list_next (l)) {
+               EmpathyMessage *message = EMPATHY_MESSAGE (l->data);
+               chat_message_received (chat, message);
+       }
+       empathy_tp_chat_acknowledge_messages (priv->tp_chat, messages);
 }
 
 static void
 chat_create_ui (EmpathyChat *chat)
 {
        EmpathyChatPriv *priv = GET_PRIV (chat);
-       GladeXML        *glade;
-       GList           *list = NULL; 
+       GtkBuilder      *gui;
+       GList           *list = NULL;
        gchar           *filename;
        GtkTextBuffer   *buffer;
 
-       filename = empathy_file_lookup ("empathy-chat.glade",
+       filename = empathy_file_lookup ("empathy-chat.ui",
                                        "libempathy-gtk");
-       glade = empathy_glade_get_file (filename,
-                                       "chat_widget",
-                                       NULL,
+       gui = empathy_builder_get_file (filename,
                                        "chat_widget", &priv->widget,
                                        "hpaned", &priv->hpaned,
                                        "vbox_left", &priv->vbox_left,
@@ -1265,7 +1408,6 @@ chat_create_ui (EmpathyChat *chat)
                                        "scrolled_window_contacts", &priv->scrolled_window_contacts,
                                        NULL);
        g_free (filename);
-       g_object_unref (glade);
 
        /* Add message view. */
        chat->view = empathy_theme_manager_create_view (empathy_theme_manager_get ());
@@ -1309,7 +1451,7 @@ chat_create_ui (EmpathyChat *chat)
        gtk_widget_show (chat->input_text_view);
 
        /* Create contact list */
-       chat_set_show_contacts (chat, priv->remote_contact == NULL);
+       chat_update_contacts_visibility (chat);
 
        /* Initialy hide the topic, will be shown if not empty */
        gtk_widget_hide (priv->hbox_topic);
@@ -1331,6 +1473,7 @@ chat_create_ui (EmpathyChat *chat)
 
        /* Add the main widget in the chat widget */
        gtk_container_add (GTK_CONTAINER (chat), priv->widget);
+       g_object_unref (gui);
 }
 
 static void
@@ -1338,15 +1481,18 @@ 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;
 
-  requisition->width = GTK_CONTAINER (widget)->border_width * 2;
-  requisition->height = GTK_CONTAINER (widget)->border_width * 2;
+  child = gtk_bin_get_child (bin);
 
-  if (bin->child && GTK_WIDGET_VISIBLE (bin->child))
+  if (child && GTK_WIDGET_VISIBLE (child))
     {
       GtkRequisition child_requisition;
-      
-      gtk_widget_size_request (bin->child, &child_requisition);
+
+      gtk_widget_size_request (child, &child_requisition);
 
       requisition->width += child_requisition.width;
       requisition->height += child_requisition.height;
@@ -1359,17 +1505,20 @@ chat_size_allocate (GtkWidget     *widget,
 {
   GtkBin *bin = GTK_BIN (widget);
   GtkAllocation child_allocation;
-  
+  GtkWidget *child;
+
   widget->allocation = *allocation;
 
-  if (bin->child && GTK_WIDGET_VISIBLE (bin->child))
+  child = gtk_bin_get_child (bin);
+
+  if (child && GTK_WIDGET_VISIBLE (child))
     {
-      child_allocation.x = allocation->x + GTK_CONTAINER (widget)->border_width;
-      child_allocation.y = allocation->y + GTK_CONTAINER (widget)->border_width;
-      child_allocation.width = MAX (allocation->width - GTK_CONTAINER (widget)->border_width * 2, 0);
-      child_allocation.height = MAX (allocation->height - GTK_CONTAINER (widget)->border_width * 2, 0);
+      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);
 
-      gtk_widget_size_allocate (bin->child, &child_allocation);
+      gtk_widget_size_allocate (child, &child_allocation);
     }
 }
 
@@ -1392,10 +1541,28 @@ chat_finalize (GObject *object)
 
        chat_composing_remove_timeout (chat);
 
+       g_signal_handlers_disconnect_by_func (priv->account_manager,
+                                             chat_new_connection_cb, object);
+
        g_object_unref (priv->account_manager);
        g_object_unref (priv->log_manager);
 
        if (priv->tp_chat) {
+               g_signal_handlers_disconnect_by_func (priv->tp_chat,
+                       chat_destroy_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_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_remote_contact_changed_cb, chat);
+               empathy_tp_chat_close (priv->tp_chat);
                g_object_unref (priv->tp_chat);
        }
        if (priv->account) {
@@ -1412,6 +1579,7 @@ chat_finalize (GObject *object)
        g_free (priv->id);
        g_free (priv->name);
        g_free (priv->subject);
+       g_completion_free (priv->completion);
 
        G_OBJECT_CLASS (empathy_chat_parent_class)->finalize (object);
 }
@@ -1423,6 +1591,7 @@ chat_constructed (GObject *object)
 
        chat_create_ui (chat);
        chat_add_logs (chat);
+       show_pending_messages (chat);
 }
 
 static void
@@ -1446,42 +1615,56 @@ empathy_chat_class_init (EmpathyChatClass *klass)
                                                              "The tp chat object",
                                                              EMPATHY_TYPE_TP_CHAT,
                                                              G_PARAM_CONSTRUCT |
-                                                             G_PARAM_READWRITE));
+                                                             G_PARAM_READWRITE |
+                                                             G_PARAM_STATIC_STRINGS));
        g_object_class_install_property (object_class,
                                         PROP_ACCOUNT,
                                         g_param_spec_object ("account",
                                                              "Account of the chat",
                                                              "The account of the chat",
-                                                             MC_TYPE_ACCOUNT,
-                                                             G_PARAM_READABLE));
+                                                             EMPATHY_TYPE_ACCOUNT,
+                                                             G_PARAM_READABLE |
+                                                             G_PARAM_STATIC_STRINGS));
        g_object_class_install_property (object_class,
                                         PROP_ID,
                                         g_param_spec_string ("id",
                                                              "Chat's id",
                                                              "The id of the chat",
                                                              NULL,
-                                                             G_PARAM_READABLE));
+                                                             G_PARAM_READABLE |
+                                                             G_PARAM_STATIC_STRINGS));
        g_object_class_install_property (object_class,
                                         PROP_NAME,
                                         g_param_spec_string ("name",
                                                              "Chat's name",
                                                              "The name of the chat",
                                                              NULL,
-                                                             G_PARAM_READABLE));
+                                                             G_PARAM_READABLE |
+                                                             G_PARAM_STATIC_STRINGS));
        g_object_class_install_property (object_class,
                                         PROP_SUBJECT,
                                         g_param_spec_string ("subject",
                                                              "Chat's subject",
                                                              "The subject or topic of the chat",
                                                              NULL,
-                                                             G_PARAM_READABLE));
+                                                             G_PARAM_READABLE |
+                                                             G_PARAM_STATIC_STRINGS));
        g_object_class_install_property (object_class,
                                         PROP_REMOTE_CONTACT,
                                         g_param_spec_object ("remote-contact",
                                                              "The remote contact",
                                                              "The remote contact is any",
                                                              EMPATHY_TYPE_CONTACT,
-                                                             G_PARAM_READABLE));
+                                                             G_PARAM_READABLE |
+                                                             G_PARAM_STATIC_STRINGS));
+       g_object_class_install_property (object_class,
+                                        PROP_SHOW_CONTACTS,
+                                        g_param_spec_boolean ("show-contacts",
+                                                              "Contacts' visibility",
+                                                              "The visibility of the contacts' list",
+                                                              TRUE,
+                                                              G_PARAM_READWRITE |
+                                                              G_PARAM_STATIC_STRINGS));
 
        signals[COMPOSING] =
                g_signal_new ("composing",
@@ -1523,17 +1706,21 @@ empathy_chat_init (EmpathyChat *chat)
                EMPATHY_TYPE_CHAT, EmpathyChatPriv);
 
        chat->priv = priv;
-       priv->log_manager = empathy_log_manager_new ();
+       priv->log_manager = empathy_log_manager_dup_singleton ();
        priv->contacts_width = -1;
        priv->sent_messages = NULL;
        priv->sent_messages_index = -1;
-       priv->account_manager = empathy_account_manager_new ();
+       priv->account_manager = empathy_account_manager_dup_singleton ();
 
        g_signal_connect (priv->account_manager,
-                         "account-connection-changed",
-                         G_CALLBACK (chat_connection_changed_cb),
+                         "new-connection",
+                         G_CALLBACK (chat_new_connection_cb),
                          chat);
 
+       empathy_conf_get_bool (empathy_conf_get (),
+                              EMPATHY_PREFS_CHAT_SHOW_CONTACTS_IN_ROOMS,
+                              &priv->show_contacts);
+
        /* Block events for some time to avoid having "has come online" or
         * "joined" messages. */
        priv->block_events_timeout_id =
@@ -1565,6 +1752,7 @@ empathy_chat_set_tp_chat (EmpathyChat   *chat,
                          EmpathyTpChat *tp_chat)
 {
        EmpathyChatPriv *priv = GET_PRIV (chat);
+       TpConnection    *connection;
 
        g_return_if_fail (EMPATHY_IS_CHAT (chat));
        g_return_if_fail (EMPATHY_IS_TP_CHAT (tp_chat));
@@ -1579,8 +1767,15 @@ empathy_chat_set_tp_chat (EmpathyChat   *chat,
        }
 
        priv->tp_chat = g_object_ref (tp_chat);
-       priv->account = g_object_ref (empathy_tp_chat_get_account (tp_chat));
+       connection = empathy_tp_chat_get_connection (priv->tp_chat);
+       priv->account = empathy_account_manager_get_account_for_connection (
+                                                            priv->account_manager,
+                                                            connection);
+       g_object_ref (priv->account);
 
+       g_signal_connect (tp_chat, "destroy",
+                         G_CALLBACK (chat_destroy_cb),
+                         chat);
        g_signal_connect (tp_chat, "message-received",
                          G_CALLBACK (chat_message_received_cb),
                          chat);
@@ -1599,9 +1794,6 @@ empathy_chat_set_tp_chat (EmpathyChat   *chat,
        g_signal_connect_swapped (tp_chat, "notify::remote-contact",
                                  G_CALLBACK (chat_remote_contact_changed_cb),
                                  chat);
-       g_signal_connect (tp_chat, "destroy",
-                         G_CALLBACK (chat_destroy_cb),
-                         chat);
 
        chat_remote_contact_changed_cb (chat);
 
@@ -1612,15 +1804,17 @@ empathy_chat_set_tp_chat (EmpathyChat   *chat,
                }
        }
 
-       empathy_tp_chat_set_acknowledge (priv->tp_chat, TRUE);
-       empathy_tp_chat_emit_pendings (priv->tp_chat);
-
        g_object_notify (G_OBJECT (chat), "tp-chat");
        g_object_notify (G_OBJECT (chat), "id");
        g_object_notify (G_OBJECT (chat), "account");
+
+       /* This is a noop when tp-chat is set at object construction time and causes
+        * the pending messages to be show when it's set on the object after it has
+        * been created */
+       show_pending_messages (chat);
 }
 
-McAccount *
+EmpathyAccount *
 empathy_chat_get_account (EmpathyChat *chat)
 {
        EmpathyChatPriv *priv = GET_PRIV (chat);
@@ -1679,20 +1873,6 @@ empathy_chat_get_remote_contact (EmpathyChat *chat)
        return priv->remote_contact;
 }
 
-guint
-empathy_chat_get_members_count (EmpathyChat *chat)
-{
-       EmpathyChatPriv *priv = GET_PRIV (chat);
-
-       g_return_val_if_fail (EMPATHY_IS_CHAT (chat), 0);
-
-       if (priv->tp_chat) {
-               return empathy_tp_chat_get_members_count (priv->tp_chat);
-       }
-
-       return 0;
-}
-
 GtkWidget *
 empathy_chat_get_contact_menu (EmpathyChat *chat)
 {