]> git.0d.be Git - empathy.git/blobdiff - src/empathy-chat-window.c
Merge branch 'sasl'
[empathy.git] / src / empathy-chat-window.c
index efdb291847929b6f6d662cc0b24eb9d7710bc8c6..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>
 
 #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 {
@@ -80,7 +83,6 @@ typedef struct {
        GtkWidget   *dialog;
        GtkWidget   *notebook;
        NotifyNotification *notification;
-       NotificationData *notification_data;
 
        GtkTargetList *contact_targets;
        GtkTargetList *file_targets;
@@ -92,6 +94,7 @@ typedef struct {
        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;
@@ -105,13 +108,22 @@ typedef struct {
        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 {
@@ -140,6 +152,20 @@ static const GtkTargetEntry drag_types_dest_file[] = {
 
 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
@@ -395,15 +421,15 @@ chat_window_contact_menu_update (EmpathyChatWindowPriv *priv,
                "/chats_menubar/menu_contact");
        orig_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (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);
        }
 }
 
@@ -513,9 +539,8 @@ chat_window_icon_update (EmpathyChatWindowPriv *priv)
                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);
@@ -780,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,
@@ -823,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
@@ -851,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,
@@ -895,20 +947,15 @@ chat_window_invite_participant_activate_cb (GtkAction         *action,
 
        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:
@@ -1084,7 +1131,7 @@ chat_window_tabs_left_activate_cb (GtkAction         *action,
 {
        EmpathyChatWindowPriv *priv;
        EmpathyChat           *chat;
-       gint                  index_;
+       gint                  index_, num_pages;
 
        priv = GET_PRIV (window);
 
@@ -1097,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
@@ -1105,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);
 
@@ -1115,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
@@ -1201,25 +1260,15 @@ chat_window_set_urgency_hint (EmpathyChatWindow *window,
        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)
 {
-       EmpathyChatWindowPriv *priv = GET_PRIV (cb_data->window);
+       EmpathyChatWindowPriv *priv = GET_PRIV (self);
 
        g_object_unref (notify);
-       free_notification_data (cb_data);
-
        if (priv->notification == notify) {
                priv->notification = NULL;
-               priv->notification_data = NULL;
        }
 }
 
@@ -1240,15 +1289,16 @@ chat_window_show_or_update_notification (EmpathyChatWindow *window,
        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);
        has_x_canonical_append = empathy_notify_manager_has_capability (
@@ -1261,28 +1311,22 @@ chat_window_show_or_update_notification (EmpathyChatWindow *window,
                notify_notification_update (notification,
                                            header, escaped, NULL);
        } else {
-               NotificationData *cb_data = cb_data = g_slice_new0 (NotificationData);
-
-               cb_data->chat = g_object_ref (chat);
-               cb_data->window = window;
-
                /* 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, NULL);
+               notification = notify_notification_new (header, escaped, NULL);
 
                if (priv->notification == NULL) {
                        priv->notification = notification;
-                       priv->notification_data = cb_data;
                }
 
                notify_notification_set_timeout (notification, NOTIFY_EXPIRES_DEFAULT);
 
-               g_signal_connect (notification, "closed",
-                                 G_CALLBACK (chat_window_notification_closed_cb), cb_data);
+               tp_g_signal_connect_object (notification, "closed",
+                                 G_CALLBACK (chat_window_notification_closed_cb), window, 0);
 
                if (has_x_canonical_append) {
                        notify_notification_set_hint_string (notification,
@@ -1321,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;
@@ -1345,7 +1405,7 @@ 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);
        }
 
@@ -1365,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;
        }
@@ -1380,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,
@@ -1410,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)
 {
@@ -1430,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);
 
@@ -1692,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;
@@ -1720,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
@@ -1798,12 +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);
                priv->notification = NULL;
-               free_notification_data (priv->notification_data);
-               priv->notification_data = NULL;
        }
 
        if (priv->contact_targets) {
@@ -1844,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
@@ -1871,6 +1950,7 @@ 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,
@@ -1889,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,
@@ -1910,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);
@@ -2011,34 +2102,41 @@ empathy_chat_window_init (EmpathyChatWindow *window)
 
        priv->chat_manager = empathy_chat_manager_dup_singleton ();
        priv->chat_manager_chats_changed_id =
-               g_signal_connect (priv->chat_manager, "chats-changed",
+               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_chats (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 */
@@ -2049,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)
 {
@@ -2109,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);
        }
@@ -2153,7 +2256,7 @@ empathy_chat_window_add_chat (EmpathyChatWindow *window,
        DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
 }
 
-void
+static void
 empathy_chat_window_remove_chat (EmpathyChatWindow *window,
                                 EmpathyChat       *chat)
 {
@@ -2191,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)
@@ -2220,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)
 {
@@ -2238,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)
@@ -2285,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));
 
@@ -2299,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));
+       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)