]> git.0d.be Git - empathy.git/blobdiff - src/empathy-chat-window.c
Merge branch 'sasl'
[empathy.git] / src / empathy-chat-window.c
index 5ad66fd265d029c0abb51b9dd4f6f7f974b7b196..16329ee7be4ebde149b65519b669b92f88e85dd3 100644 (file)
@@ -1,7 +1,7 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
 /*
  * Copyright (C) 2003-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
@@ -32,6 +32,7 @@
 
 #include <gtk/gtk.h>
 #include <gdk/gdkkeysyms.h>
+#include <gdk/gdkx.h>
 #include <glib/gi18n.h>
 #include <libnotify/notification.h>
 
 #include <libempathy/empathy-contact.h>
 #include <libempathy/empathy-message.h>
 #include <libempathy/empathy-chatroom-manager.h>
+#include <libempathy/empathy-gsettings.h>
 #include <libempathy/empathy-utils.h>
 #include <libempathy/empathy-tp-contact-factory.h>
 #include <libempathy/empathy-contact-list.h>
 
 #include <libempathy-gtk/empathy-images.h>
-#include <libempathy-gtk/empathy-conf.h>
 #include <libempathy-gtk/empathy-contact-dialogs.h>
 #include <libempathy-gtk/empathy-log-window.h>
 #include <libempathy-gtk/empathy-geometry.h>
 #include <libempathy-gtk/empathy-smiley-manager.h>
-#include <libempathy-gtk/empathy-sound.h>
+#include <libempathy-gtk/empathy-sound-manager.h>
 #include <libempathy-gtk/empathy-ui-utils.h>
 #include <libempathy-gtk/empathy-notify-manager.h>
 
+#include "empathy-chat-manager.h"
 #include "empathy-chat-window.h"
 #include "empathy-about-dialog.h"
 #include "empathy-invite-participant-dialog.h"
 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
 #include <libempathy/empathy-debug.h>
 
-typedef struct {
-       EmpathyChatWindow *window;
-       EmpathyChat *chat;
-} NotificationData;
+/* Macro to compare guint32 X timestamps, while accounting for wrapping around
+ */
+#define X_EARLIER_OR_EQL(t1, t2) \
+       ((t1 <= t2 && ((t2 - t1) < G_MAXUINT32/2))  \
+         || (t1 >= t2 && (t1 - t2) > (G_MAXUINT32/2)) \
+       )
 
 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyChatWindow)
 typedef struct {
@@ -79,33 +83,47 @@ typedef struct {
        GtkWidget   *dialog;
        GtkWidget   *notebook;
        NotifyNotification *notification;
-       NotificationData *notification_data;
 
        GtkTargetList *contact_targets;
        GtkTargetList *file_targets;
 
+       EmpathyChatManager *chat_manager;
+       gulong chat_manager_chats_changed_id;
+
        /* Menu items. */
        GtkUIManager *ui_manager;
        GtkAction   *menu_conv_insert_smiley;
        GtkAction   *menu_conv_favorite;
+       GtkAction   *menu_conv_always_urgent;
        GtkAction   *menu_conv_toggle_contacts;
 
        GtkAction   *menu_edit_cut;
        GtkAction   *menu_edit_copy;
        GtkAction   *menu_edit_paste;
+       GtkAction   *menu_edit_find;
 
        GtkAction   *menu_tabs_next;
        GtkAction   *menu_tabs_prev;
+       GtkAction   *menu_tabs_undo_close_tab;
        GtkAction   *menu_tabs_left;
        GtkAction   *menu_tabs_right;
        GtkAction   *menu_tabs_detach;
+
+       /* Last user action time we acted upon to show a tab */
+       guint32    x_user_action_time;
+
+       GSettings *gsettings_chat;
+       GSettings *gsettings_notif;
+       GSettings *gsettings_ui;
+
+       EmpathySoundManager *sound_mgr;
 } EmpathyChatWindowPriv;
 
 static GList *chat_windows = NULL;
 
 static const guint tab_accel_keys[] = {
-       GDK_1, GDK_2, GDK_3, GDK_4, GDK_5,
-       GDK_6, GDK_7, GDK_8, GDK_9, GDK_0
+       GDK_KEY_1, GDK_KEY_2, GDK_KEY_3, GDK_KEY_4, GDK_KEY_5,
+       GDK_KEY_6, GDK_KEY_7, GDK_KEY_8, GDK_KEY_9, GDK_KEY_0
 };
 
 typedef enum {
@@ -118,6 +136,7 @@ static const GtkTargetEntry drag_types_dest[] = {
        { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
        { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_TAB },
        { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
+       { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
 };
 
 static const GtkTargetEntry drag_types_dest_contact[] = {
@@ -125,11 +144,28 @@ static const GtkTargetEntry drag_types_dest_contact[] = {
 };
 
 static const GtkTargetEntry drag_types_dest_file[] = {
+       /* must be first to be prioritized, in order to receive the
+        * note's file path from Tomboy instead of an URI */
+       { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
        { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
 };
 
 static void chat_window_update (EmpathyChatWindow *window);
 
+static void empathy_chat_window_add_chat (EmpathyChatWindow *window,
+                             EmpathyChat       *chat);
+
+static void empathy_chat_window_remove_chat (EmpathyChatWindow *window,
+                                EmpathyChat       *chat);
+
+static void empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
+                              EmpathyChatWindow *new_window,
+                              EmpathyChat       *chat);
+
+static void empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
+                              guint *nb_rooms,
+                              guint *nb_private);
+
 G_DEFINE_TYPE (EmpathyChatWindow, empathy_chat_window, G_TYPE_OBJECT);
 
 static void
@@ -328,18 +364,21 @@ chat_window_menu_context_update (EmpathyChatWindowPriv *priv,
 {
        gboolean first_page;
        gboolean last_page;
+       gboolean wrap_around;
        gboolean is_connected;
        gint     page_num;
 
        page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
        first_page = (page_num == 0);
        last_page = (page_num == (num_pages - 1));
+       g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
+                     &wrap_around, NULL);
        is_connected = empathy_chat_get_tp_chat (priv->current_chat) != NULL;
 
-       DEBUG ("Update window : Menu Contexts (Tabs & Conv)");
-
-       gtk_action_set_sensitive (priv->menu_tabs_next, TRUE);
-       gtk_action_set_sensitive (priv->menu_tabs_prev, TRUE);
+       gtk_action_set_sensitive (priv->menu_tabs_next, (!last_page ||
+                                                        wrap_around));
+       gtk_action_set_sensitive (priv->menu_tabs_prev, (!first_page ||
+                                                        wrap_around));
        gtk_action_set_sensitive (priv->menu_tabs_detach, num_pages > 1);
        gtk_action_set_sensitive (priv->menu_tabs_left, !first_page);
        gtk_action_set_sensitive (priv->menu_tabs_right, !last_page);
@@ -382,17 +421,15 @@ chat_window_contact_menu_update (EmpathyChatWindowPriv *priv,
                "/chats_menubar/menu_contact");
        orig_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
 
-       DEBUG ("Update window : Contact Menu");
-
-       if (orig_submenu == NULL || !GTK_WIDGET_VISIBLE (orig_submenu)) {
+       if (orig_submenu == NULL || !gtk_widget_get_visible (orig_submenu)) {
                submenu = empathy_chat_get_contact_menu (priv->current_chat);
                gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
                gtk_widget_show (menu);
        } else {
-               empathy_signal_connect_weak (orig_submenu,
+               tp_g_signal_connect_object (orig_submenu,
                                             "notify::visible",
                                             (GCallback)_submenu_notify_visible_changed_cb,
-                                            G_OBJECT (window));
+                                            window, 0);
        }
 }
 
@@ -482,8 +519,6 @@ chat_window_title_update (EmpathyChatWindowPriv *priv)
 {
        gchar *name;
 
-       DEBUG ("Update window : Title");
-
        name = get_window_title_name (priv);
        gtk_window_set_title (GTK_WINDOW (priv->dialog), name);
        g_free (name);
@@ -499,16 +534,13 @@ chat_window_icon_update (EmpathyChatWindowPriv *priv)
 
        n_chats = g_list_length (priv->chats);
 
-       DEBUG ("Update window : Icon");
-
        /* Update window icon */
        if (priv->chats_new_msg) {
                gtk_window_set_icon_name (GTK_WINDOW (priv->dialog),
                                          EMPATHY_IMAGE_MESSAGE);
        } else {
-               empathy_conf_get_bool (empathy_conf_get (),
-                                      EMPATHY_PREFS_CHAT_AVATAR_IN_ICON,
-                                      &avatar_in_icon);
+               avatar_in_icon = g_settings_get_boolean (priv->gsettings_chat,
+                               EMPATHY_PREFS_CHAT_AVATAR_IN_ICON);
 
                if (n_chats == 1 && avatar_in_icon) {
                        remote_contact = empathy_chat_get_remote_contact (priv->current_chat);
@@ -532,8 +564,6 @@ chat_window_close_button_update (EmpathyChatWindowPriv *priv,
        GtkWidget *chat_close_button;
        gint       i;
 
-       DEBUG ("Update window : Close Button");
-
        if (num_pages == 1) {
                chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), 0);
                chat_close_button = g_object_get_data (G_OBJECT (chat),
@@ -557,8 +587,6 @@ chat_window_update (EmpathyChatWindow *window)
 
        num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
 
-       DEBUG ("Update window");
-
        /* Update Tab menu */
        chat_window_menu_context_update (priv,
                                         num_pages);
@@ -777,8 +805,16 @@ chat_window_conv_activate_cb (GtkAction         *action,
                DEBUG ("This room %s favorite", found ? "is" : "is not");
                gtk_toggle_action_set_active (
                        GTK_TOGGLE_ACTION (priv->menu_conv_favorite), found);
+
+               if (chatroom != NULL)
+                       found = empathy_chatroom_is_always_urgent (chatroom);
+
+               gtk_toggle_action_set_active (
+                       GTK_TOGGLE_ACTION (priv->menu_conv_always_urgent),
+                       found);
        }
        gtk_action_set_visible (priv->menu_conv_favorite, is_room);
+       gtk_action_set_visible (priv->menu_conv_always_urgent, is_room);
 
        /* Show contacts menu */
        g_object_get (priv->current_chat,
@@ -820,19 +856,38 @@ chat_window_favorite_toggled_cb (GtkToggleAction   *toggle_action,
        account = empathy_chat_get_account (priv->current_chat);
        room = empathy_chat_get_id (priv->current_chat);
 
-       chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
-                                                 account, room);
+       chatroom = empathy_chatroom_manager_ensure_chatroom (
+                    priv->chatroom_manager,
+                    account,
+                    room,
+                    empathy_chat_get_name (priv->current_chat));
 
-       if (chatroom == NULL) {
-               const gchar *name;
+       empathy_chatroom_set_favorite (chatroom, active);
+       g_object_unref (chatroom);
+}
 
-               name = empathy_chat_get_name (priv->current_chat);
-               chatroom = empathy_chatroom_new_full (account, room, name, FALSE);
-               empathy_chatroom_manager_add (priv->chatroom_manager, chatroom);
-               g_object_unref (chatroom);
-       }
+static void
+chat_window_always_urgent_toggled_cb (GtkToggleAction   *toggle_action,
+                                EmpathyChatWindow *window)
+{
+       EmpathyChatWindowPriv *priv = GET_PRIV (window);
+       gboolean               active;
+       TpAccount             *account;
+       const gchar           *room;
+       EmpathyChatroom       *chatroom;
 
-       empathy_chatroom_set_favorite (chatroom, active);
+       active = gtk_toggle_action_get_active (toggle_action);
+       account = empathy_chat_get_account (priv->current_chat);
+       room = empathy_chat_get_id (priv->current_chat);
+
+       chatroom = empathy_chatroom_manager_ensure_chatroom (
+                    priv->chatroom_manager,
+                    account,
+                    room,
+                    empathy_chat_get_name (priv->current_chat));
+
+       empathy_chatroom_set_always_urgent (chatroom, active);
+       g_object_unref (chatroom);
 }
 
 static void
@@ -848,7 +903,7 @@ chat_window_contacts_toggled_cb (GtkToggleAction   *toggle_action,
 }
 
 static void
-got_contact_cb (EmpathyTpContactFactory *factory,
+got_contact_cb (TpConnection            *connection,
                 EmpathyContact          *contact,
                 const GError            *error,
                 gpointer                 user_data,
@@ -874,6 +929,7 @@ chat_window_invite_participant_activate_cb (GtkAction         *action,
        EmpathyTpChat         *tp_chat;
        TpChannel             *channel;
        int                    response;
+       TpAccount             *account;
 
        priv = GET_PRIV (window);
 
@@ -881,29 +937,25 @@ chat_window_invite_participant_activate_cb (GtkAction         *action,
 
        tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
        channel = empathy_tp_chat_get_channel (tp_chat);
+       account = empathy_chat_get_account (priv->current_chat);
 
        dialog = empathy_invite_participant_dialog_new (
-                       GTK_WINDOW (priv->dialog));
+                       GTK_WINDOW (priv->dialog), account);
        gtk_widget_show (dialog);
 
        response = gtk_dialog_run (GTK_DIALOG (dialog));
 
        if (response == GTK_RESPONSE_ACCEPT) {
                TpConnection *connection;
-               EmpathyTpContactFactory *factory;
                const char *id;
 
                id = empathy_contact_selector_dialog_get_selected (
-                               EMPATHY_CONTACT_SELECTOR_DIALOG (dialog), NULL);
+                               EMPATHY_CONTACT_SELECTOR_DIALOG (dialog), NULL, NULL);
                if (EMP_STR_EMPTY (id)) goto out;
 
                connection = tp_channel_borrow_connection (channel);
-               factory = empathy_tp_contact_factory_dup_singleton (connection);
-
-               empathy_tp_contact_factory_get_from_id (factory, id,
+               empathy_tp_contact_factory_get_from_id (connection, id,
                        got_contact_cb, tp_chat,  NULL, NULL);
-
-               g_object_unref (factory);
        }
 
 out:
@@ -1000,6 +1052,19 @@ chat_window_paste_activate_cb (GtkAction         *action,
        empathy_chat_paste (priv->current_chat);
 }
 
+static void
+chat_window_find_activate_cb (GtkAction         *action,
+                             EmpathyChatWindow *window)
+{
+       EmpathyChatWindowPriv *priv;
+
+       g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
+
+       priv = GET_PRIV (window);
+
+       empathy_chat_find (priv->current_chat);
+}
+
 static void
 chat_window_tabs_next_activate_cb (GtkAction         *action,
                                   EmpathyChatWindow *window)
@@ -1007,14 +1072,18 @@ chat_window_tabs_next_activate_cb (GtkAction         *action,
        EmpathyChatWindowPriv *priv;
        EmpathyChat           *chat;
        gint                  index_, numPages;
+       gboolean              wrap_around;
 
        priv = GET_PRIV (window);
 
+       g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
+                      &wrap_around, NULL);
+
        chat = priv->current_chat;
        index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
        numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
 
-       if (index_ == (numPages - 1)) {
+       if (index_ == (numPages - 1) && wrap_around) {
                gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), 0);
                return;
        }
@@ -1029,14 +1098,18 @@ chat_window_tabs_previous_activate_cb (GtkAction         *action,
        EmpathyChatWindowPriv *priv;
        EmpathyChat           *chat;
        gint                  index_, numPages;
+       gboolean              wrap_around;
 
        priv = GET_PRIV (window);
 
+       g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
+                      &wrap_around, NULL);
+
        chat = priv->current_chat;
        index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
        numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
 
-       if (index_ <= 0) {
+       if (index_ <= 0 && wrap_around) {
                gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), numPages - 1);
                return;
        }
@@ -1044,13 +1117,21 @@ chat_window_tabs_previous_activate_cb (GtkAction         *action,
        gtk_notebook_prev_page (GTK_NOTEBOOK (priv->notebook));
 }
 
+static void
+chat_window_tabs_undo_close_tab_activate_cb (GtkAction         *action,
+                                            EmpathyChatWindow *window)
+{
+       EmpathyChatWindowPriv *priv = GET_PRIV (window);
+       empathy_chat_manager_undo_closed_chat (priv->chat_manager);
+}
+
 static void
 chat_window_tabs_left_activate_cb (GtkAction         *action,
                                   EmpathyChatWindow *window)
 {
        EmpathyChatWindowPriv *priv;
        EmpathyChat           *chat;
-       gint                  index_;
+       gint                  index_, num_pages;
 
        priv = GET_PRIV (window);
 
@@ -1063,6 +1144,9 @@ chat_window_tabs_left_activate_cb (GtkAction         *action,
        gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
                                    GTK_WIDGET (chat),
                                    index_ - 1);
+
+       num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
+       chat_window_menu_context_update (priv, num_pages);
 }
 
 static void
@@ -1071,7 +1155,7 @@ chat_window_tabs_right_activate_cb (GtkAction         *action,
 {
        EmpathyChatWindowPriv *priv;
        EmpathyChat           *chat;
-       gint                  index_;
+       gint                  index_, num_pages;
 
        priv = GET_PRIV (window);
 
@@ -1081,6 +1165,15 @@ chat_window_tabs_right_activate_cb (GtkAction         *action,
        gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
                                    GTK_WIDGET (chat),
                                    index_ + 1);
+
+       num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
+       chat_window_menu_context_update (priv, num_pages);
+}
+
+static EmpathyChatWindow *
+empathy_chat_window_new (void)
+{
+       return EMPATHY_CHAT_WINDOW (g_object_new (EMPATHY_TYPE_CHAT_WINDOW, NULL));
 }
 
 static void
@@ -1164,35 +1257,19 @@ chat_window_set_urgency_hint (EmpathyChatWindow *window,
 
        priv = GET_PRIV (window);
 
-       DEBUG ("Turning %s urgency hint", urgent ? "on" : "off");
        gtk_window_set_urgency_hint (GTK_WINDOW (priv->dialog), urgent);
 }
 
-static void
-free_notification_data (NotificationData *data)
-{
-       g_object_unref (data->chat);
-       g_slice_free (NotificationData, data);
-}
-
 static void
 chat_window_notification_closed_cb (NotifyNotification *notify,
-                                   NotificationData *cb_data)
+                                   EmpathyChatWindow *self)
 {
-       EmpathyNotificationClosedReason reason = 0;
-       EmpathyChatWindowPriv *priv = GET_PRIV (cb_data->window);
-
-#ifdef notify_notification_get_closed_reason
-       reason = notify_notification_get_closed_reason (notify);
-#endif
-       if (reason == EMPATHY_NOTIFICATION_CLOSED_DISMISSED) {
-               empathy_chat_window_present_chat (cb_data->chat);
-       }
+       EmpathyChatWindowPriv *priv = GET_PRIV (self);
 
        g_object_unref (notify);
-       priv->notification = NULL;
-       free_notification_data (cb_data);
-       priv->notification_data = NULL;
+       if (priv->notification == notify) {
+               priv->notification = NULL;
+       }
 }
 
 static void
@@ -1206,49 +1283,66 @@ chat_window_show_or_update_notification (EmpathyChatWindow *window,
        const char *body;
        GdkPixbuf *pixbuf;
        EmpathyChatWindowPriv *priv = GET_PRIV (window);
-       gboolean res;
+       gboolean res, has_x_canonical_append;
+       NotifyNotification *notification = priv->notification;
 
        if (!empathy_notify_manager_notification_is_enabled (priv->notify_mgr)) {
                return;
        } else {
-               empathy_conf_get_bool (empathy_conf_get (),
-                                      EMPATHY_PREFS_NOTIFICATIONS_FOCUS, &res);
+               res = g_settings_get_boolean (priv->gsettings_notif,
+                               EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
+
                if (!res) {
                        return;
                }
        }
 
        sender = empathy_message_get_sender (message);
-       header = empathy_contact_get_name (sender);
+       header = empathy_contact_get_alias (sender);
        body = empathy_message_get_body (message);
        escaped = g_markup_escape_text (body, -1);
-
-       if (priv->notification != NULL) {
-               notify_notification_update (priv->notification,
+       has_x_canonical_append = empathy_notify_manager_has_capability (
+               priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
+
+       if (notification != NULL && !has_x_canonical_append) {
+               /* if the notification server supports x-canonical-append, it is
+                  better to not use notify_notification_update to avoid
+                  overwriting the current notification message */
+               notify_notification_update (notification,
                                            header, escaped, NULL);
        } else {
-               NotificationData *cb_data = cb_data = g_slice_new0 (NotificationData);
+               /* if the notification server supports x-canonical-append,
+                  the hint will be added, so that the message from the
+                  just created notification will be automatically appended
+                  to an existing notification with the same title.
+                  In this way the previous message will not be lost: the new
+                  message will appear below it, in the same notification */
+               notification = notify_notification_new (header, escaped, NULL);
+
+               if (priv->notification == NULL) {
+                       priv->notification = notification;
+               }
 
-               cb_data->chat = g_object_ref (chat);
-               cb_data->window = window;
+               notify_notification_set_timeout (notification, NOTIFY_EXPIRES_DEFAULT);
 
-               priv->notification_data = cb_data;
-               priv->notification = notify_notification_new (header, escaped, NULL, NULL);
-               notify_notification_set_timeout (priv->notification, NOTIFY_EXPIRES_DEFAULT);
+               tp_g_signal_connect_object (notification, "closed",
+                                 G_CALLBACK (chat_window_notification_closed_cb), window, 0);
 
-               g_signal_connect (priv->notification, "closed",
-                                 G_CALLBACK (chat_window_notification_closed_cb), cb_data);
+               if (has_x_canonical_append) {
+                       notify_notification_set_hint_string (notification,
+                               EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "");
+               }
        }
 
        pixbuf = empathy_notify_manager_get_pixbuf_for_notification (priv->notify_mgr,
                sender, EMPATHY_IMAGE_NEW_MESSAGE);
 
        if (pixbuf != NULL) {
-               notify_notification_set_icon_from_pixbuf (priv->notification, pixbuf);
+               notify_notification_set_icon_from_pixbuf (notification, pixbuf);
                g_object_unref (pixbuf);
        }
 
-       notify_notification_show (priv->notification, NULL);
+       notify_notification_show (notification, NULL);
 
        g_free (escaped);
 }
@@ -1271,9 +1365,25 @@ chat_window_set_highlight_room_tab_label (EmpathyChat *chat)
        g_free (markup);
 }
 
+static gboolean
+empathy_chat_window_has_focus (EmpathyChatWindow *window)
+{
+       EmpathyChatWindowPriv *priv;
+       gboolean              has_focus;
+
+       g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (window), FALSE);
+
+       priv = GET_PRIV (window);
+
+       g_object_get (priv->dialog, "has-toplevel-focus", &has_focus, NULL);
+
+       return has_focus;
+}
+
 static void
 chat_window_new_message_cb (EmpathyChat       *chat,
                            EmpathyMessage    *message,
+                           gboolean pending,
                            EmpathyChatWindow *window)
 {
        EmpathyChatWindowPriv *priv;
@@ -1295,12 +1405,14 @@ chat_window_new_message_cb (EmpathyChat       *chat,
        sender = empathy_message_get_sender (message);
 
        if (empathy_contact_is_user (sender)) {
-               empathy_sound_play (GTK_WIDGET (priv->dialog),
+               empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
                                    EMPATHY_SOUND_MESSAGE_OUTGOING);
        }
 
        if (has_focus && priv->current_chat == chat) {
                /* window and tab are focused so consider the message to be read */
+
+               /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
                empathy_chat_messages_read (chat);
                return;
        }
@@ -1313,11 +1425,27 @@ chat_window_new_message_cb (EmpathyChat       *chat,
        /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
         * If empathy_chat_get_remote_contact () returns NULL, that means it's
         * an unamed MUC (msn-like).
-        * In case of a MUC, we set urgency only if the message contains our
-        * alias. */
+        * In case of a MUC, we set urgency if either:
+        *   a) the chatroom's always_urgent property is TRUE
+        *   b) the message contains our alias
+        */
        if (empathy_chat_is_room (chat) ||
            empathy_chat_get_remote_contact (chat) == NULL) {
-               needs_urgency = empathy_message_should_highlight (message);
+               TpAccount             *account;
+               const gchar           *room;
+               EmpathyChatroom       *chatroom;
+
+               account = empathy_chat_get_account (chat);
+               room = empathy_chat_get_id (chat);
+
+               chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
+                                                         account, room);
+
+               if (empathy_chatroom_is_always_urgent (chatroom)) {
+                       needs_urgency = TRUE;
+               } else {
+                       needs_urgency = empathy_message_should_highlight (message);
+               }
        } else {
                needs_urgency = TRUE;
        }
@@ -1328,17 +1456,22 @@ chat_window_new_message_cb (EmpathyChat       *chat,
                        chat_window_set_highlight_room_tab_label (chat);
                }
 
-               empathy_sound_play (GTK_WIDGET (priv->dialog),
+               empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
                    EMPATHY_SOUND_MESSAGE_INCOMING);
-               chat_window_show_or_update_notification (window, message, chat);
+
+               /* Pending messages have already been displayed in the approver, so we don't
+               * display a notification for those. */
+               if (!pending)
+                       chat_window_show_or_update_notification (window, message, chat);
        }
 
-       /* update the number of unread messages */
+       /* update the number of unread messages and the window icon */
        chat_window_title_update (priv);
+       chat_window_icon_update (priv);
 }
 
 static GtkNotebook *
-chat_window_detach_hook (GtkNotebook *source,
+notebook_create_window_cb (GtkNotebook *source,
                         GtkWidget   *page,
                         gint         x,
                         gint         y,
@@ -1358,15 +1491,15 @@ chat_window_detach_hook (GtkNotebook *source,
 
        empathy_chat_window_move_chat (window, new_window, chat);
 
-       gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
        gtk_widget_show (priv->dialog);
+       gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
 
        return NULL;
 }
 
 static void
 chat_window_page_switched_cb (GtkNotebook      *notebook,
-                             GtkNotebookPage  *page,
+                             gpointer          ignore, /* see note below */
                              gint              page_num,
                              EmpathyChatWindow *window)
 {
@@ -1378,6 +1511,9 @@ chat_window_page_switched_cb (GtkNotebook      *notebook,
 
        priv = GET_PRIV (window);
 
+       /* N.B. in GTK+ 3 child is passed as the first argument to the signal,
+        * but we can't use that while trying to retain GTK+ 2.x compatibility.
+        */
        child = gtk_notebook_get_nth_page (notebook, page_num);
        chat = EMPATHY_CHAT (child);
 
@@ -1500,8 +1636,6 @@ chat_window_focus_in_event_cb (GtkWidget        *widget,
 {
        EmpathyChatWindowPriv *priv;
 
-       DEBUG ("Focus in event, updating title");
-
        priv = GET_PRIV (window);
 
        priv->chats_new_msg = g_list_remove (priv->chats_new_msg, priv->current_chat);
@@ -1516,7 +1650,7 @@ chat_window_focus_in_event_cb (GtkWidget        *widget,
 }
 
 static gboolean
-chat_window_drag_motion (GtkWidget        *widget,
+chat_window_drag_drop (GtkWidget        *widget,
                         GdkDragContext   *context,
                         int               x,
                         int               y,
@@ -1525,14 +1659,36 @@ chat_window_drag_motion (GtkWidget        *widget,
 {
        GdkAtom target;
        EmpathyChatWindowPriv *priv;
-       GdkAtom dest_target;
 
        priv = GET_PRIV (window);
 
-       target = gtk_drag_dest_find_target (widget, context, NULL);
+       target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
+       if (target == GDK_NONE)
+               target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
+
+       if (target != GDK_NONE) {
+               gtk_drag_get_data (widget, context, target, time_);
+               return TRUE;
+       }
 
-       dest_target = gdk_atom_intern_static_string ("text/uri-list");
-       if (target == dest_target) {
+       return FALSE;
+}
+
+static gboolean
+chat_window_drag_motion (GtkWidget        *widget,
+                        GdkDragContext   *context,
+                        int               x,
+                        int               y,
+                        guint             time_,
+                        EmpathyChatWindow *window)
+{
+       GdkAtom target;
+       EmpathyChatWindowPriv *priv;
+
+       priv = GET_PRIV (window);
+
+       target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
+       if (target != GDK_NONE) {
                /* This is a file drag.  Ensure the contact is online and set the
                   drag type to COPY.  Note that it's possible that the tab will
                   be switched by GTK+ after a timeout from drag_motion without
@@ -1561,8 +1717,8 @@ chat_window_drag_motion (GtkWidget        *widget,
                return TRUE;
        }
 
-       dest_target = gdk_atom_intern_static_string ("text/contact-id");
-       if (target == dest_target) {
+       target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
+       if (target != GDK_NONE) {
                /* This is a drag of a contact from a contact list.  Set to COPY.
                   FIXME: If this drag is to a MUC window, it invites the user.
                   Otherwise, it opens a chat.  Should we use a different drag
@@ -1572,9 +1728,7 @@ chat_window_drag_motion (GtkWidget        *widget,
                return TRUE;
        }
 
-       /* Otherwise, it must be a notebook tab drag.  Set to MOVE. */
-       gdk_drag_status (context, GDK_ACTION_MOVE, time_);
-       return TRUE;
+       return FALSE;
 }
 
 static void
@@ -1622,14 +1776,8 @@ chat_window_drag_data_received (GtkWidget        *widget,
                }
 
                if (!chat) {
-                       TpConnection *connection;
-
-                       connection = tp_account_get_connection (account);
-
-                       if (connection) {
-                               empathy_dispatcher_chat_with_contact_id (
-                                       connection, contact_id, NULL, NULL);
-                       }
+                       empathy_dispatcher_chat_with_contact_id (
+                               account, contact_id, gtk_get_current_event_time ());
 
                        g_strfreev (strv);
                        return;
@@ -1650,7 +1798,8 @@ chat_window_drag_data_received (GtkWidget        *widget,
                }
 
                /* Added to take care of any outstanding chat events */
-               empathy_chat_window_present_chat (chat);
+               empathy_chat_window_present_chat (chat,
+                       TP_USER_ACTION_TIME_NOT_USER_ACTION);
 
                /* We should return TRUE to remove the data when doing
                 * GDK_ACTION_MOVE, but we don't here otherwise it has
@@ -1693,29 +1842,27 @@ chat_window_drag_data_received (GtkWidget        *widget,
                        EmpathyChatWindowPriv *priv;
 
                        priv = GET_PRIV (window);
-
-                       if (old_window == window) {
-                               DEBUG ("DND tab (within same window)");
-                               priv->dnd_same_window = TRUE;
-                               gtk_drag_finish (context, TRUE, FALSE, time_);
-                               return;
-                       }
-
-                       priv->dnd_same_window = FALSE;
+                       priv->dnd_same_window = (old_window == window);
+                       DEBUG ("DND tab (within same window: %s)",
+                               priv->dnd_same_window ? "Yes" : "No");
                }
-
-               /* We should return TRUE to remove the data when doing
-                * GDK_ACTION_MOVE, but we don't here otherwise it has
-                * weird consequences, and we handle that internally
-                * anyway with add_chat () and remove_chat ().
-                */
-               gtk_drag_finish (context, TRUE, FALSE, time_);
        } else {
                DEBUG ("DND from unknown source");
                gtk_drag_finish (context, FALSE, FALSE, time_);
        }
 }
 
+static void
+chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
+                                          guint num_chats_in_manager,
+                                          EmpathyChatWindow *window)
+{
+       EmpathyChatWindowPriv *priv = GET_PRIV (window);
+
+       gtk_action_set_sensitive (priv->menu_tabs_undo_close_tab,
+                                 num_chats_in_manager > 0);
+}
+
 static void
 chat_window_finalize (GObject *object)
 {
@@ -1730,16 +1877,14 @@ chat_window_finalize (GObject *object)
        g_object_unref (priv->ui_manager);
        g_object_unref (priv->chatroom_manager);
        g_object_unref (priv->notify_mgr);
+       g_object_unref (priv->gsettings_chat);
+       g_object_unref (priv->gsettings_notif);
+       g_object_unref (priv->gsettings_ui);
+       g_object_unref (priv->sound_mgr);
 
        if (priv->notification != NULL) {
                notify_notification_close (priv->notification, NULL);
-               g_object_unref (priv->notification);
                priv->notification = NULL;
-               if (priv->notification_data != NULL)
-                       {
-                               free_notification_data (priv->notification_data);
-                               priv->notification_data = NULL;
-                       }
        }
 
        if (priv->contact_targets) {
@@ -1749,6 +1894,13 @@ chat_window_finalize (GObject *object)
                gtk_target_list_unref (priv->file_targets);
        }
 
+       if (priv->chat_manager) {
+               g_signal_handler_disconnect (priv->chat_manager,
+                                            priv->chat_manager_chats_changed_id);
+               g_object_unref (priv->chat_manager);
+               priv->chat_manager = NULL;
+       }
+
        chat_windows = g_list_remove (chat_windows, window);
        gtk_widget_destroy (priv->dialog);
 
@@ -1773,8 +1925,6 @@ empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
                "  ythickness = 0\n"
                "}\n"
                "widget \"*.empathy-close-button\" style \"empathy-close-button-style\"");
-
-       gtk_notebook_set_window_creation_hook (chat_window_detach_hook, NULL, NULL);
 }
 
 static void
@@ -1800,12 +1950,15 @@ empathy_chat_window_init (EmpathyChatWindow *window)
                                       "ui_manager", &priv->ui_manager,
                                       "menu_conv_insert_smiley", &priv->menu_conv_insert_smiley,
                                       "menu_conv_favorite", &priv->menu_conv_favorite,
+                                      "menu_conv_always_urgent", &priv->menu_conv_always_urgent,
                                       "menu_conv_toggle_contacts", &priv->menu_conv_toggle_contacts,
                                       "menu_edit_cut", &priv->menu_edit_cut,
                                       "menu_edit_copy", &priv->menu_edit_copy,
                                       "menu_edit_paste", &priv->menu_edit_paste,
+                                      "menu_edit_find", &priv->menu_edit_find,
                                       "menu_tabs_next", &priv->menu_tabs_next,
                                       "menu_tabs_prev", &priv->menu_tabs_prev,
+                                      "menu_tabs_undo_close_tab", &priv->menu_tabs_undo_close_tab,
                                       "menu_tabs_left", &priv->menu_tabs_left,
                                       "menu_tabs_right", &priv->menu_tabs_right,
                                       "menu_tabs_detach", &priv->menu_tabs_detach,
@@ -1816,6 +1969,7 @@ empathy_chat_window_init (EmpathyChatWindow *window)
                              "menu_conv", "activate", chat_window_conv_activate_cb,
                              "menu_conv_clear", "activate", chat_window_clear_activate_cb,
                              "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
+                             "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
                              "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
                              "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
                              "menu_conv_close", "activate", chat_window_close_activate_cb,
@@ -1823,8 +1977,10 @@ empathy_chat_window_init (EmpathyChatWindow *window)
                              "menu_edit_cut", "activate", chat_window_cut_activate_cb,
                              "menu_edit_copy", "activate", chat_window_copy_activate_cb,
                              "menu_edit_paste", "activate", chat_window_paste_activate_cb,
+                             "menu_edit_find", "activate", chat_window_find_activate_cb,
                              "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
                              "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
+                             "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
                              "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
                              "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
                              "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
@@ -1835,10 +1991,20 @@ empathy_chat_window_init (EmpathyChatWindow *window)
        g_object_ref (priv->ui_manager);
        g_object_unref (gui);
 
+       priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
+       priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
+       priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
        priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
 
+       priv->sound_mgr = empathy_sound_manager_dup_singleton ();
+
        priv->notebook = gtk_notebook_new ();
-       gtk_notebook_set_group (GTK_NOTEBOOK (priv->notebook), "EmpathyChatWindow");
+
+       g_signal_connect (priv->notebook, "create-window",
+               G_CALLBACK (notebook_create_window_cb), window);
+
+       gtk_notebook_set_group_name (GTK_NOTEBOOK (priv->notebook),
+               "EmpathyChatWindow");
        gtk_notebook_set_scrollable (GTK_NOTEBOOK (priv->notebook), TRUE);
        gtk_notebook_popup_enable (GTK_NOTEBOOK (priv->notebook));
        gtk_box_pack_start (GTK_BOX (chat_vbox), priv->notebook, TRUE, TRUE, 0);
@@ -1904,7 +2070,7 @@ empathy_chat_window_init (EmpathyChatWindow *window)
 
        /* Set up drag and drop */
        gtk_drag_dest_set (GTK_WIDGET (priv->notebook),
-                          GTK_DEST_DEFAULT_ALL,
+                          GTK_DEST_DEFAULT_HIGHLIGHT,
                           drag_types_dest,
                           G_N_ELEMENTS (drag_types_dest),
                           GDK_ACTION_MOVE | GDK_ACTION_COPY);
@@ -1918,6 +2084,10 @@ empathy_chat_window_init (EmpathyChatWindow *window)
                          "drag-data-received",
                          G_CALLBACK (chat_window_drag_data_received),
                          window);
+       g_signal_connect (priv->notebook,
+                         "drag-drop",
+                         G_CALLBACK (chat_window_drag_drop),
+                         window);
 
        chat_windows = g_list_prepend (chat_windows, window);
 
@@ -1926,29 +2096,47 @@ empathy_chat_window_init (EmpathyChatWindow *window)
        priv->chats_new_msg = NULL;
        priv->chats_composing = NULL;
        priv->current_chat = NULL;
+       priv->notification = NULL;
 
        priv->notify_mgr = empathy_notify_manager_dup_singleton ();
+
+       priv->chat_manager = empathy_chat_manager_dup_singleton ();
+       priv->chat_manager_chats_changed_id =
+               g_signal_connect (priv->chat_manager, "closed-chats-changed",
+                                 G_CALLBACK (chat_window_chat_manager_chats_changed_cb),
+                                 window);
+
+       chat_window_chat_manager_chats_changed_cb (priv->chat_manager,
+                                                  empathy_chat_manager_get_num_closed_chats (priv->chat_manager),
+                                                  window);
 }
 
-EmpathyChatWindow *
-empathy_chat_window_new (void)
+static GtkWidget *
+empathy_chat_window_get_dialog (EmpathyChatWindow *window)
 {
-       return EMPATHY_CHAT_WINDOW (g_object_new (EMPATHY_TYPE_CHAT_WINDOW, NULL));
+       EmpathyChatWindowPriv *priv;
+
+       g_return_val_if_fail (window != NULL, NULL);
+
+       priv = GET_PRIV (window);
+
+       return priv->dialog;
 }
 
-/* Returns the window to open a new tab in if there is only one window
- * visble, otherwise, returns NULL indicating that a new window should
- * be added.
+/* Returns the window to open a new tab in if there is a suitable window,
+ * otherwise, returns NULL indicating that a new window should be added.
  */
-EmpathyChatWindow *
+static EmpathyChatWindow *
 empathy_chat_window_get_default (gboolean room)
 {
+       GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
        GList    *l;
        gboolean  separate_windows = TRUE;
 
-       empathy_conf_get_bool (empathy_conf_get (),
-                             EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS,
-                             &separate_windows);
+       separate_windows = g_settings_get_boolean (gsettings,
+                       EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
+
+       g_object_unref (gsettings);
 
        if (separate_windows) {
                /* Always create a new window */
@@ -1959,44 +2147,33 @@ empathy_chat_window_get_default (gboolean room)
                EmpathyChatWindowPriv *priv;
                EmpathyChatWindow *chat_window;
                GtkWidget         *dialog;
+               guint nb_rooms, nb_private;
 
                chat_window = l->data;
                priv = GET_PRIV (chat_window);
 
                dialog = empathy_chat_window_get_dialog (chat_window);
-               if (empathy_window_get_is_visible (GTK_WINDOW (dialog))) {
-                       guint nb_rooms, nb_private;
-                       empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
 
-                       /* Skip the window if there aren't any rooms in it */
-                       if (room && nb_rooms == 0)
-                               continue;
+               empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
 
-                       /* Skip the window if there aren't any 1-1 chats in it */
-                       if (!room && nb_private == 0)
-                               continue;
+               /* Skip the window if there aren't any rooms in it */
+               if (room && nb_rooms == 0)
+                       continue;
 
-                       /* Found a visible window on this desktop */
-                       return chat_window;
-               }
+               /* Skip the window if there aren't any 1-1 chats in it */
+               if (!room && nb_private == 0)
+                       continue;
+
+               /* Found a window on this desktop, make it visible if necessary */
+               if (!empathy_window_get_is_visible (GTK_WINDOW (dialog)))
+                       empathy_window_present (GTK_WINDOW (dialog));
+               return chat_window;
        }
 
        return NULL;
 }
 
-GtkWidget *
-empathy_chat_window_get_dialog (EmpathyChatWindow *window)
-{
-       EmpathyChatWindowPriv *priv;
-
-       g_return_val_if_fail (window != NULL, NULL);
-
-       priv = GET_PRIV (window);
-
-       return priv->dialog;
-}
-
-void
+static void
 empathy_chat_window_add_chat (EmpathyChatWindow *window,
                              EmpathyChat       *chat)
 {
@@ -2004,6 +2181,7 @@ empathy_chat_window_add_chat (EmpathyChatWindow *window,
        GtkWidget             *label;
        GtkWidget             *popup_label;
        GtkWidget             *child;
+       GValue                value = { 0, };
 
        g_return_if_fail (window != NULL);
        g_return_if_fail (EMPATHY_IS_CHAT (chat));
@@ -2018,16 +2196,32 @@ empathy_chat_window_add_chat (EmpathyChatWindow *window,
                const gchar *name = "chat-window";
                gboolean     separate_windows;
 
-               empathy_conf_get_bool (empathy_conf_get (),
-                                      EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS,
-                                      &separate_windows);
+               separate_windows = g_settings_get_boolean (priv->gsettings_ui,
+                               EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
+
+               if (empathy_chat_is_room (chat))
+                       name = "room-window";
 
                if (separate_windows) {
+                       gint x, y;
+
+                       /* Save current position of the window */
+                       gtk_window_get_position (GTK_WINDOW (priv->dialog), &x, &y);
+
+                       /* First bind to the 'generic' name. So new window for which we didn't
+                       * save a geometry yet will have the geometry of the last saved
+                       * window (bgo #601191). */
+                       empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
+
+                       /* Restore previous position of the window so the newly created window
+                       * won't be in the same position as the latest saved window and so
+                       * completely hide it. */
+                       gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
+
+                       /* Then bind it to the name of the contact/room so we'll save the
+                       * geometry specific to this window */
                        name = empathy_chat_get_id (chat);
                }
-               else if (empathy_chat_is_room (chat)) {
-                       name = "room-window";
-               }
 
                empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
        }
@@ -2051,19 +2245,25 @@ empathy_chat_window_add_chat (EmpathyChatWindow *window,
        gtk_notebook_append_page_menu (GTK_NOTEBOOK (priv->notebook), child, label, popup_label);
        gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
        gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
-       gtk_notebook_set_tab_label_packing (GTK_NOTEBOOK (priv->notebook), child,
-                                           TRUE, TRUE, GTK_PACK_START);
+       g_value_init (&value, G_TYPE_BOOLEAN);
+       g_value_set_boolean (&value, TRUE);
+       gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
+                                         child, "tab-expand" , &value);
+       gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
+                                         child,  "tab-fill" , &value);
+       g_value_unset (&value);
 
        DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
 }
 
-void
+static void
 empathy_chat_window_remove_chat (EmpathyChatWindow *window,
                                 EmpathyChat       *chat)
 {
        EmpathyChatWindowPriv *priv;
        gint                   position;
        EmpathyContact        *remote_contact;
+       EmpathyChatManager    *chat_manager;
 
        g_return_if_fail (window != NULL);
        g_return_if_fail (EMPATHY_IS_CHAT (chat));
@@ -2081,6 +2281,10 @@ empathy_chat_window_remove_chat (EmpathyChatWindow *window,
                                                      chat);
        }
 
+       chat_manager = empathy_chat_manager_dup_singleton ();
+       empathy_chat_manager_closed_chat (chat_manager, chat);
+       g_object_unref (chat_manager);
+
        position = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
                                          GTK_WIDGET (chat));
        gtk_notebook_remove_page (GTK_NOTEBOOK (priv->notebook), position);
@@ -2090,7 +2294,7 @@ empathy_chat_window_remove_chat (EmpathyChatWindow *window,
        g_object_unref (chat);
 }
 
-void
+static void
 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
                               EmpathyChatWindow *new_window,
                               EmpathyChat       *chat)
@@ -2119,7 +2323,7 @@ empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
        g_object_unref (chat);
 }
 
-void
+static void
 empathy_chat_window_switch_to_chat (EmpathyChatWindow *window,
                                    EmpathyChat       *chat)
 {
@@ -2137,21 +2341,6 @@ empathy_chat_window_switch_to_chat (EmpathyChatWindow *window,
                                       page_num);
 }
 
-gboolean
-empathy_chat_window_has_focus (EmpathyChatWindow *window)
-{
-       EmpathyChatWindowPriv *priv;
-       gboolean              has_focus;
-
-       g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (window), FALSE);
-
-       priv = GET_PRIV (window);
-
-       g_object_get (priv->dialog, "has-toplevel-focus", &has_focus, NULL);
-
-       return has_focus;
-}
-
 EmpathyChat *
 empathy_chat_window_find_chat (TpAccount   *account,
                               const gchar *id)
@@ -2184,10 +2373,12 @@ empathy_chat_window_find_chat (TpAccount   *account,
 }
 
 void
-empathy_chat_window_present_chat (EmpathyChat *chat)
+empathy_chat_window_present_chat (EmpathyChat *chat,
+                                 gint64 timestamp)
 {
        EmpathyChatWindow     *window;
        EmpathyChatWindowPriv *priv;
+       guint32 x_timestamp;
 
        g_return_if_fail (EMPATHY_IS_CHAT (chat));
 
@@ -2198,19 +2389,40 @@ empathy_chat_window_present_chat (EmpathyChat *chat)
                window = empathy_chat_window_get_default (empathy_chat_is_room (chat));
                if (!window) {
                        window = empathy_chat_window_new ();
+                       gtk_widget_show_all (GET_PRIV (window)->dialog);
                }
 
                empathy_chat_window_add_chat (window, chat);
        }
 
+       /* Don't force the window to show itself when it wasn't
+        * an action by the user
+        */
+       if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
+               return;
+
        priv = GET_PRIV (window);
+
+       if (x_timestamp != GDK_CURRENT_TIME) {
+               /* Don't present or switch tab if the action was earlier than the
+                * last actions X time, accounting for overflow and the first ever
+               * presentation */
+
+               if (priv->x_user_action_time != 0
+                       && X_EARLIER_OR_EQL (x_timestamp, priv->x_user_action_time))
+                       return;
+
+               priv->x_user_action_time = x_timestamp;
+       }
+
        empathy_chat_window_switch_to_chat (window, chat);
-       empathy_window_present (GTK_WINDOW (priv->dialog), TRUE);
+       empathy_window_present_with_time (GTK_WINDOW (priv->dialog),
+         x_timestamp);
 
-       gtk_widget_grab_focus (chat->input_text_view);
+       gtk_widget_grab_focus (chat->input_text_view);
 }
 
-void
+static void
 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
                               guint *nb_rooms,
                               guint *nb_private)