]> git.0d.be Git - empathy.git/blobdiff - src/empathy-chat-window.c
use a single window, with tabs
[empathy.git] / src / empathy-chat-window.c
index 8dec7c08f51e523fbf91eb0faecb322fe4056a25..2f41d3d9b887d5b258dcf4b819727531701fd6cd 100644 (file)
  *          Rômulo Fernandes Machado <romulo@castorgroup.net>
  */
 
-#include <config.h>
-
-#include <string.h>
+#include "config.h"
+#include "empathy-chat-window.h"
 
-#include <gtk/gtk.h>
-#include <gdk/gdkkeysyms.h>
-#include <gdk/gdkx.h>
 #include <glib/gi18n.h>
-#include <libnotify/notification.h>
-
-#include <libempathy/empathy-client-factory.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-request-util.h>
-#include <libempathy/empathy-individual-manager.h>
-
-#include <libempathy-gtk/empathy-images.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-manager.h>
-#include <libempathy-gtk/empathy-ui-utils.h>
-#include <libempathy-gtk/empathy-notify-manager.h>
+#include <tp-account-widgets/tpaw-builder.h>
+#include <tp-account-widgets/tpaw-utils.h>
 
-#include "empathy-chat-manager.h"
-#include "empathy-chat-window.h"
 #include "empathy-about-dialog.h"
+#include "empathy-chat-manager.h"
+#include "empathy-chatroom-manager.h"
+#include "empathy-client-factory.h"
+#include "empathy-geometry.h"
+#include "empathy-gsettings.h"
+#include "empathy-images.h"
 #include "empathy-invite-participant-dialog.h"
+#include "empathy-notify-manager.h"
+#include "empathy-request-util.h"
+#include "empathy-sound-manager.h"
+#include "empathy-ui-utils.h"
+#include "empathy-utils.h"
+#include "empathy-new-message-dialog.h"
 
 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
-#include <libempathy/empathy-debug.h>
+#include "empathy-debug.h"
 
 /* Macro to compare guint32 X timestamps, while accounting for wrapping around
  */
     || (t1 >= t2 && (t1 - t2) > (G_MAXUINT32/2)) \
   )
 
+enum
+{
+  PROP_INDIVIDUAL_MGR = 1
+};
+
 struct _EmpathyChatWindowPriv
 {
   EmpathyChat *current_chat;
@@ -75,7 +70,7 @@ struct _EmpathyChatWindowPriv
   gboolean dnd_same_window;
   EmpathyChatroomManager *chatroom_manager;
   EmpathyNotifyManager *notify_mgr;
-  GtkWidget *dialog;
+  EmpathyIndividualManager *individual_mgr;
   GtkWidget *notebook;
   NotifyNotification *notification;
 
@@ -89,6 +84,8 @@ struct _EmpathyChatWindowPriv
   GtkUIManager *ui_manager;
   GtkAction *menu_conv_insert_smiley;
   GtkAction *menu_conv_favorite;
+  GtkAction *menu_conv_join_chat;
+  GtkAction *menu_conv_leave_chat;
   GtkAction *menu_conv_always_urgent;
   GtkAction *menu_conv_toggle_contacts;
 
@@ -174,7 +171,7 @@ 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)
+G_DEFINE_TYPE (EmpathyChatWindow, empathy_chat_window, GTK_TYPE_BIN)
 
 static void
 chat_window_accel_cb (GtkAccelGroup *accelgroup,
@@ -317,7 +314,7 @@ confirm_close (EmpathyChatWindow *self,
     }
 
   dialog = gtk_message_dialog_new (
-    GTK_WINDOW (self->priv->dialog),
+    GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self))),
     GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
     GTK_MESSAGE_WARNING,
     GTK_BUTTONS_CANCEL,
@@ -385,15 +382,17 @@ chat_tab_style_updated_cb (GtkWidget *hbox,
   GtkWidget *button;
   int char_width, h, w;
   PangoContext *context;
-  const PangoFontDescription *font_desc;
+  PangoFontDescription *font_desc;
   PangoFontMetrics *metrics;
 
   button = g_object_get_data (G_OBJECT (user_data),
     "chat-window-tab-close-button");
   context = gtk_widget_get_pango_context (hbox);
 
-  font_desc = gtk_style_context_get_font (gtk_widget_get_style_context (hbox),
-      GTK_STATE_FLAG_NORMAL);
+  gtk_style_context_get (gtk_widget_get_style_context (hbox),
+      GTK_STATE_FLAG_NORMAL,
+      "font", &font_desc,
+      NULL);
 
   metrics = pango_context_get_metrics (context, font_desc,
     pango_context_get_language (context));
@@ -409,6 +408,7 @@ chat_tab_style_updated_cb (GtkWidget *hbox,
     12 * PANGO_PIXELS (char_width) + 2 * w, -1);
 
   gtk_widget_set_size_request (button, w, h);
+  pango_font_description_free (font_desc);
 }
 
 static GtkWidget *
@@ -595,11 +595,13 @@ chat_window_conversation_menu_update (EmpathyChatWindow *self)
 }
 
 static void
-chat_window_contact_menu_update (EmpathyChatWindow *self,
-    EmpathyChatWindow *window)
+chat_window_contact_menu_update (EmpathyChatWindow *self)
 {
   GtkWidget *menu, *submenu, *orig_submenu;
 
+  if (self->priv->current_chat == NULL)
+    return;
+
   if (self->priv->updating_menu)
     return;
   self->priv->updating_menu = TRUE;
@@ -615,7 +617,7 @@ chat_window_contact_menu_update (EmpathyChatWindow *self,
       if (submenu != NULL)
         {
           /* gtk_menu_attach_to_widget () doesn't behave nicely here */
-          g_object_set_data (G_OBJECT (submenu), "window", self->priv->dialog);
+          g_object_set_data (G_OBJECT (submenu), "window", self);
 
           gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
           gtk_widget_show (menu);
@@ -630,7 +632,7 @@ chat_window_contact_menu_update (EmpathyChatWindow *self,
     {
       tp_g_signal_connect_object (orig_submenu,
           "notify::visible",
-          (GCallback)_submenu_notify_visible_changed_cb, window, 0);
+          (GCallback)_submenu_notify_visible_changed_cb, self, 0);
     }
 
   self->priv->updating_menu = FALSE;
@@ -728,7 +730,7 @@ chat_window_title_update (EmpathyChatWindow *self)
   gchar *name;
 
   name = get_window_title_name (self);
-  gtk_window_set_title (GTK_WINDOW (self->priv->dialog), name);
+  //gtk_window_set_title (GTK_WINDOW (self), name);
   g_free (name);
 }
 
@@ -746,8 +748,8 @@ chat_window_icon_update (EmpathyChatWindow *self,
   /* Update window icon */
   if (new_messages)
     {
-      gtk_window_set_icon_name (GTK_WINDOW (self->priv->dialog),
-          EMPATHY_IMAGE_MESSAGE);
+      //gtk_window_set_icon_name (GTK_WINDOW (self),
+      //    EMPATHY_IMAGE_MESSAGE);
     }
   else
     {
@@ -759,14 +761,14 @@ chat_window_icon_update (EmpathyChatWindow *self,
           remote_contact = empathy_chat_get_remote_contact (self->priv->current_chat);
           icon = empathy_pixbuf_avatar_from_contact_scaled (remote_contact,
               0, 0);
-          gtk_window_set_icon (GTK_WINDOW (self->priv->dialog), icon);
+          //gtk_window_set_icon (GTK_WINDOW (self), icon);
 
           if (icon != NULL)
             g_object_unref (icon);
         }
       else
         {
-          gtk_window_set_icon_name (GTK_WINDOW (self->priv->dialog), NULL);
+          //gtk_window_set_icon_name (GTK_WINDOW (self), NULL);
         }
     }
 }
@@ -817,7 +819,7 @@ chat_window_update (EmpathyChatWindow *self,
      menu watching. */
   if (update_contact_menu)
     {
-      chat_window_contact_menu_update (self, self);
+      chat_window_contact_menu_update (self);
     }
 
   chat_window_title_update (self);
@@ -967,10 +969,10 @@ chat_window_update_chat_tab_full (EmpathyChat *chat,
       g_free (tmp);
     }
 
-  if (!EMP_STR_EMPTY (status))
+  if (!TPAW_STR_EMPTY (status))
     append_markup_printf (tooltip, "\n<i>%s</i>", status);
 
-  if (!EMP_STR_EMPTY (subject))
+  if (!TPAW_STR_EMPTY (subject))
     append_markup_printf (tooltip, "\n<b>%s</b> %s",
         _("Topic:"), subject);
 
@@ -982,9 +984,9 @@ chat_window_update_chat_tab_full (EmpathyChat *chat,
       const gchar * const *types;
 
       types = empathy_contact_get_client_types (remote_contact);
-      if (types != NULL && !tp_strdiff (types[0], "phone"))
+      if (empathy_client_types_contains_mobile_device ((GStrv) types))
         {
-          /* I'm on a phone ! */
+          /* I'm on a mobile device ! */
           gchar *tmp = name;
 
           name = g_strdup_printf ("☎ %s", name);
@@ -1076,13 +1078,11 @@ chat_window_insert_smiley_activate_cb (EmpathySmileyManager *manager,
   EmpathyChatWindow *self = user_data;
   EmpathyChat *chat;
   GtkTextBuffer *buffer;
-  GtkTextIter iter;
 
   chat = self->priv->current_chat;
-
   buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
-  gtk_text_buffer_get_end_iter (buffer, &iter);
-  gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
+
+  empathy_chat_insert_smiley (buffer, smiley);
 }
 
 static void
@@ -1092,6 +1092,7 @@ chat_window_conv_activate_cb (GtkAction *action,
   gboolean is_room;
   gboolean active;
   EmpathyContact *remote_contact = NULL;
+  gboolean disconnected;
 
   /* Favorite room menu */
   is_room = empathy_chat_is_room (self->priv->current_chat);
@@ -1136,9 +1137,45 @@ chat_window_conv_activate_cb (GtkAction *action,
         GTK_TOGGLE_ACTION (self->priv->menu_conv_toggle_contacts), active);
     }
 
+  /* Menu-items to be visible for MUCs only */
   gtk_action_set_visible (self->priv->menu_conv_toggle_contacts,
       (remote_contact == NULL));
 
+  disconnected = (empathy_chat_get_tp_chat (self->priv->current_chat) == NULL);
+  if (disconnected)
+    {
+      gtk_action_set_visible (self->priv->menu_conv_join_chat, TRUE);
+      gtk_action_set_visible (self->priv->menu_conv_leave_chat, FALSE);
+    }
+  else
+    {
+      TpChannel *channel = NULL;
+      TpContact *self_contact = NULL;
+      TpHandle  self_handle = 0;
+
+      channel = (TpChannel *) (empathy_chat_get_tp_chat (
+          self->priv->current_chat));
+      self_contact = tp_channel_group_get_self_contact (channel);
+      if (self_contact == NULL)
+      {
+        /* The channel may not be a group */
+        gtk_action_set_visible (self->priv->menu_conv_leave_chat, FALSE);
+      }
+      else
+      {
+        self_handle = tp_contact_get_handle (self_contact);
+        /* There is sometimes a lag between the members-changed signal
+           emitted on tp-chat and invalidated signal being emitted on the channel.
+           Leave Chat menu-item should be sensitive only till our self-handle is
+           a part of channel-members */
+        gtk_action_set_visible (self->priv->menu_conv_leave_chat,
+            self_handle != 0);
+      }
+
+      /* Join Chat is insensitive for a connected chat */
+      gtk_action_set_visible (self->priv->menu_conv_join_chat, FALSE);
+    }
+
   if (remote_contact != NULL)
     g_object_unref (remote_contact);
 }
@@ -1220,7 +1257,7 @@ chat_window_invite_participant_activate_cb (GtkAction *action,
   tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
 
   dialog = empathy_invite_participant_dialog_new (
-      GTK_WINDOW (self->priv->dialog), tp_chat);
+      GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self))), tp_chat);
 
   gtk_widget_show (dialog);
 
@@ -1247,6 +1284,29 @@ out:
   gtk_widget_destroy (dialog);
 }
 
+static void
+chat_window_join_chat_activate_cb (GtkAction *action,
+    EmpathyChatWindow *self)
+{
+    g_return_if_fail (self->priv->current_chat != NULL);
+
+    empathy_chat_join_muc (self->priv->current_chat,
+        empathy_chat_get_id (self->priv->current_chat));
+}
+
+static void
+chat_window_leave_chat_activate_cb (GtkAction *action,
+    EmpathyChatWindow *self)
+{
+    EmpathyTpChat * tp_chat;
+
+    g_return_if_fail (self->priv->current_chat != NULL);
+
+    tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
+    if (tp_chat != NULL)
+        empathy_tp_chat_leave (tp_chat, "");
+}
+
 static void
 chat_window_close_activate_cb (GtkAction *action,
     EmpathyChatWindow *self)
@@ -1381,6 +1441,19 @@ chat_window_tabs_previous_activate_cb (GtkAction *action,
   gtk_notebook_prev_page (GTK_NOTEBOOK (self->priv->notebook));
 }
 
+void
+empathy_chat_window_next_tab (EmpathyChatWindow *self)
+{
+  chat_window_tabs_next_activate_cb (NULL, self);
+}
+
+void
+empathy_chat_window_prev_tab (EmpathyChatWindow *self)
+{
+  chat_window_tabs_previous_activate_cb (NULL, self);
+}
+
+
 static void
 chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
     EmpathyChatWindow *self)
@@ -1425,10 +1498,11 @@ chat_window_tabs_right_activate_cb (GtkAction *action,
   chat_window_menu_context_update (self, num_pages);
 }
 
-static EmpathyChatWindow *
+EmpathyChatWindow *
 empathy_chat_window_new (void)
 {
-  return EMPATHY_CHAT_WINDOW (g_object_new (EMPATHY_TYPE_CHAT_WINDOW, NULL));
+  return g_object_new (EMPATHY_TYPE_CHAT_WINDOW,
+      NULL);
 }
 
 static void
@@ -1443,21 +1517,21 @@ chat_window_detach_activate_cb (GtkAction *action,
 
   empathy_chat_window_move_chat (self, new_window, chat);
 
-  gtk_widget_show (new_window->priv->dialog);
+  gtk_widget_show (GTK_WIDGET (new_window));
 }
 
 static void
 chat_window_help_contents_activate_cb (GtkAction *action,
     EmpathyChatWindow *self)
 {
-  empathy_url_show (self->priv->dialog, "help:empathy");
+  empathy_url_show (GTK_WIDGET (self), "help:empathy");
 }
 
 static void
 chat_window_help_about_activate_cb (GtkAction *action,
     EmpathyChatWindow *self)
 {
-  empathy_about_dialog_new (GTK_WINDOW (self->priv->dialog));
+  empathy_about_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self))));
 }
 
 static gboolean
@@ -1504,7 +1578,7 @@ static void
 chat_window_set_urgency_hint (EmpathyChatWindow *self,
     gboolean urgent)
 {
-  gtk_window_set_urgency_hint (GTK_WINDOW (self->priv->dialog), urgent);
+  //gtk_window_set_urgency_hint (GTK_WINDOW (self), urgent);
 }
 
 static void
@@ -1565,13 +1639,13 @@ chat_window_show_or_update_notification (EmpathyChatWindow *self,
       const gchar *category = empathy_chat_is_room (chat)
         ? EMPATHY_NOTIFICATION_CATEGORY_MENTIONED
         : EMPATHY_NOTIFICATION_CATEGORY_CHAT;
-      notification = notify_notification_new (header, escaped, NULL);
+
+      notification = empathy_notify_manager_create_notification (header,
+          escaped, NULL);
 
       if (self->priv->notification == NULL)
         self->priv->notification = notification;
 
-      notify_notification_set_timeout (notification, NOTIFY_EXPIRES_DEFAULT);
-
       tp_g_signal_connect_object (notification, "closed",
             G_CALLBACK (chat_window_notification_closed_cb), self, 0);
 
@@ -1607,7 +1681,7 @@ empathy_chat_window_has_focus (EmpathyChatWindow *self)
 
   g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (self), FALSE);
 
-  g_object_get (self->priv->dialog, "has-toplevel-focus", &has_focus, NULL);
+  g_object_get ( gtk_widget_get_toplevel (GTK_WIDGET (self)), "has-toplevel-focus", &has_focus, NULL);
 
   return has_focus;
 }
@@ -1636,7 +1710,7 @@ chat_window_new_message_cb (EmpathyChat *chat,
 
   if (empathy_contact_is_user (sender))
     {
-      empathy_sound_manager_play (self->priv->sound_mgr, GTK_WIDGET (self->priv->dialog),
+      empathy_sound_manager_play (self->priv->sound_mgr, GTK_WIDGET (self),
           EMPATHY_SOUND_MESSAGE_OUTGOING);
       return;
     }
@@ -1696,7 +1770,7 @@ chat_window_new_message_cb (EmpathyChat *chat,
       if (!pending)
         {
           empathy_sound_manager_play (self->priv->sound_mgr,
-              GTK_WIDGET (self->priv->dialog), EMPATHY_SOUND_MESSAGE_INCOMING);
+              GTK_WIDGET (self), EMPATHY_SOUND_MESSAGE_INCOMING);
 
           chat_window_show_or_update_notification (self, message, chat);
         }
@@ -1777,8 +1851,8 @@ notebook_create_window_cb (GtkNotebook *source,
 
   empathy_chat_window_move_chat (window, new_window, chat);
 
-  gtk_widget_show (new_window->priv->dialog);
-  gtk_window_move (GTK_WINDOW (new_window->priv->dialog), x, y);
+  gtk_widget_show (GTK_WIDGET (new_window));
+  //gtk_window_move (GTK_WINDOW (new_window), x, y);
 
   return NULL;
 }
@@ -1891,7 +1965,7 @@ chat_window_page_removed_cb (GtkNotebook *notebook,
 
   if (self->priv->chats == NULL)
     {
-      g_object_unref (self);
+      gtk_widget_destroy (GTK_WIDGET (self));
     }
   else
     {
@@ -1904,6 +1978,9 @@ chat_window_focus_in_event_cb (GtkWidget *widget,
     GdkEvent *event,
     EmpathyChatWindow *self)
 {
+  if (self->priv->current_chat == NULL) {
+          return FALSE;
+  }
   empathy_chat_messages_read (self->priv->current_chat);
 
   chat_window_set_urgency_hint (self, FALSE);
@@ -1914,6 +1991,52 @@ chat_window_focus_in_event_cb (GtkWidget *widget,
   return FALSE;
 }
 
+static void
+contacts_loaded_cb (EmpathyIndividualManager *mgr,
+    EmpathyChatWindow *self)
+{
+  chat_window_contact_menu_update (self);
+}
+
+static gboolean
+chat_window_focus_out_event_cb (GtkWidget *widget,
+    GdkEvent *event,
+    EmpathyChatWindow *self)
+{
+  if (self->priv->individual_mgr != NULL)
+    return FALSE;
+
+  /* Keep the individual manager alive so we won't fetch everything from Folks
+   * each time we need to use it. Loading FolksAggregator can takes quite a
+   * while (if user has a huge LDAP abook for example) and it blocks
+   * the mainloop during most of this loading. We workaround this by loading
+   * it when the chat window has been unfocused and so, hopefully, not impact
+   * the reactivity of the chat window too much.
+   *
+   * The individual manager (and so Folks) is needed to know to which
+   * FolksIndividual a TpContact belongs, including:
+   * - empathy_chat_get_contact_menu: to list all the personas of the contact
+   * - empathy_display_individual_info: to invoke gnome-contacts with the
+   *   FolksIndividual.id of the contact
+   * - drag_data_received_individual_id: to find the individual associated
+   *   with the ID we received from the DnD in order to invite him.
+   */
+  self->priv->individual_mgr = empathy_individual_manager_dup_singleton ();
+
+  if (!empathy_individual_manager_get_contacts_loaded (
+      self->priv->individual_mgr))
+    {
+      /* We want to update the contact menu when Folks is loaded so we can
+       * list all the personas of the contact. */
+      tp_g_signal_connect_object (self->priv->individual_mgr, "contacts-loaded",
+          G_CALLBACK (contacts_loaded_cb), self, 0);
+    }
+
+  g_object_notify (G_OBJECT (self), "individual-manager");
+
+  return FALSE;
+}
+
 static gboolean
 chat_window_drag_drop (GtkWidget *widget,
     GdkDragContext *context,
@@ -2009,7 +2132,6 @@ drag_data_received_individual_id (EmpathyChatWindow *self,
     guint time_)
 {
   const gchar *id;
-  EmpathyIndividualManager *manager = NULL;
   FolksIndividual *individual;
   EmpathyTpChat *chat;
   TpContact *tp_contact;
@@ -2034,9 +2156,13 @@ drag_data_received_individual_id (EmpathyChatWindow *self,
       goto out;
     }
 
-  manager = empathy_individual_manager_dup_singleton ();
+  if (self->priv->individual_mgr == NULL)
+    /* Not likely as we have to focus out the chat window in order to start
+     * the DnD but best to be safe. */
+    goto out;
 
-  individual = empathy_individual_manager_lookup_member (manager, id);
+  individual = empathy_individual_manager_lookup_member (
+          self->priv->individual_mgr, id);
   if (individual == NULL)
     {
       DEBUG ("Failed to find individual %s", id);
@@ -2061,7 +2187,6 @@ drag_data_received_individual_id (EmpathyChatWindow *self,
 
 out:
   gtk_drag_finish (context, TRUE, FALSE, time_);
-  tp_clear_object (&manager);
 }
 
 static void
@@ -2224,6 +2349,7 @@ chat_window_finalize (GObject *object)
   g_object_unref (self->priv->gsettings_notif);
   g_object_unref (self->priv->gsettings_ui);
   g_object_unref (self->priv->sound_mgr);
+  g_clear_object (&self->priv->individual_mgr);
 
   if (self->priv->notification != NULL)
     {
@@ -2246,21 +2372,57 @@ chat_window_finalize (GObject *object)
     }
 
   chat_windows = g_list_remove (chat_windows, self);
-  gtk_widget_destroy (self->priv->dialog);
 
   G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
 }
 
+static void
+chat_window_get_property (GObject *object,
+    guint property_id,
+    GValue *value,
+    GParamSpec *pspec)
+{
+  EmpathyChatWindow *self = EMPATHY_CHAT_WINDOW (object);
+
+  switch (property_id)
+    {
+      case PROP_INDIVIDUAL_MGR:
+        g_value_set_object (value, self->priv->individual_mgr);
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+        break;
+    }
+}
+
 static void
 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GParamSpec *spec;
 
+  object_class->get_property = chat_window_get_property;
   object_class->finalize = chat_window_finalize;
 
+  spec = g_param_spec_object ("individual-manager", "individual-manager",
+      "EmpathyIndividualManager",
+      EMPATHY_TYPE_INDIVIDUAL_MANAGER,
+      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_INDIVIDUAL_MGR, spec);
+
   g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
 }
 
+static void
+chat_window_chat_new_message_cb (GSimpleAction *action,
+    GVariant *parameter,
+    gpointer user_data)
+{
+  EmpathyChatWindow *self = user_data;
+
+  //empathy_new_message_dialog_show (GTK_WINDOW (self));
+}
+
+
 static void
 empathy_chat_window_init (EmpathyChatWindow *self)
 {
@@ -2271,6 +2433,7 @@ empathy_chat_window_init (EmpathyChatWindow *self)
   GtkWidget *submenu;
   guint i;
   GtkWidget *chat_vbox;
+  GtkWidget *main_box;
   gchar *filename;
   EmpathySmileyManager *smiley_manager;
 
@@ -2278,12 +2441,14 @@ empathy_chat_window_init (EmpathyChatWindow *self)
     EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
 
   filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
-  gui = empathy_builder_get_file (filename,
-      "chat_window", &self->priv->dialog,
+  gui = tpaw_builder_get_file (filename,
       "chat_vbox", &chat_vbox,
+      "main_box", &main_box,
       "ui_manager", &self->priv->ui_manager,
       "menu_conv_insert_smiley", &self->priv->menu_conv_insert_smiley,
       "menu_conv_favorite", &self->priv->menu_conv_favorite,
+      "menu_conv_join_chat", &self->priv->menu_conv_join_chat,
+      "menu_conv_leave_chat", &self->priv->menu_conv_leave_chat,
       "menu_conv_always_urgent", &self->priv->menu_conv_always_urgent,
       "menu_conv_toggle_contacts", &self->priv->menu_conv_toggle_contacts,
       "menu_edit_cut", &self->priv->menu_edit_cut,
@@ -2299,13 +2464,15 @@ empathy_chat_window_init (EmpathyChatWindow *self)
       NULL);
   g_free (filename);
 
-  empathy_builder_connect (gui, self,
+  tpaw_builder_connect (gui, self,
       "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_join_chat", "activate", chat_window_join_chat_activate_cb,
+      "menu_conv_leave_chat", "activate", chat_window_leave_chat_activate_cb,
       "menu_conv_close", "activate", chat_window_close_activate_cb,
       "menu_edit", "activate", chat_window_edit_activate_cb,
       "menu_edit_cut", "activate", chat_window_cut_activate_cb,
@@ -2322,10 +2489,7 @@ empathy_chat_window_init (EmpathyChatWindow *self)
       "menu_help_about", "activate", chat_window_help_about_activate_cb,
       NULL);
 
-  g_object_ref (self->priv->ui_manager);
-  g_object_unref (gui);
-
-  empathy_set_css_provider (GTK_WIDGET (self->priv->dialog));
+  empathy_set_css_provider (GTK_WIDGET (self));
 
   self->priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
   self->priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
@@ -2335,10 +2499,13 @@ empathy_chat_window_init (EmpathyChatWindow *self)
   self->priv->sound_mgr = empathy_sound_manager_dup_singleton ();
 
   self->priv->notebook = gtk_notebook_new ();
+  //gtk_notebook_set_show_tabs (GTK_NOTEBOOK (self->priv->notebook), FALSE);
 
   g_signal_connect (self->priv->notebook, "create-window",
       G_CALLBACK (notebook_create_window_cb), self);
 
+  gtk_container_add (GTK_CONTAINER (self), main_box);
+
   gtk_notebook_set_group_name (GTK_NOTEBOOK (self->priv->notebook),
     "EmpathyChatWindow");
   gtk_notebook_set_scrollable (GTK_NOTEBOOK (self->priv->notebook), TRUE);
@@ -2346,9 +2513,10 @@ empathy_chat_window_init (EmpathyChatWindow *self)
   gtk_box_pack_start (GTK_BOX (chat_vbox), self->priv->notebook, TRUE, TRUE, 0);
   gtk_widget_show (self->priv->notebook);
 
+#if 0 /* no top level window yet at this point */
   /* Set up accels */
   accel_group = gtk_accel_group_new ();
-  gtk_window_add_accel_group (GTK_WINDOW (self->priv->dialog), accel_group);
+  gtk_window_add_accel_group (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self))), accel_group);
 
   for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++)
     {
@@ -2360,6 +2528,7 @@ empathy_chat_window_init (EmpathyChatWindow *self)
     }
 
   g_object_unref (accel_group);
+#endif
 
   /* Set up drag target lists */
   self->priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
@@ -2382,10 +2551,12 @@ empathy_chat_window_init (EmpathyChatWindow *self)
    * block/unblock them at some later stage.
    */
 
-  g_signal_connect (self->priv->dialog, "delete_event",
+  g_signal_connect (self, "delete_event",
       G_CALLBACK (chat_window_delete_event_cb), self);
-  g_signal_connect (self->priv->dialog, "focus_in_event",
+  g_signal_connect (self, "focus_in_event",
       G_CALLBACK (chat_window_focus_in_event_cb), self);
+  g_signal_connect (self, "focus_out_event",
+      G_CALLBACK (chat_window_focus_out_event_cb), self);
   g_signal_connect_after (self->priv->notebook, "switch_page",
       G_CALLBACK (chat_window_page_switched_cb), self);
   g_signal_connect (self->priv->notebook, "page_added",
@@ -2424,6 +2595,9 @@ empathy_chat_window_init (EmpathyChatWindow *self)
 
   chat_window_chat_manager_chats_changed_cb (self->priv->chat_manager,
       empathy_chat_manager_get_num_closed_chats (self->priv->chat_manager), self);
+
+  g_object_ref (self->priv->ui_manager);
+  g_object_unref (gui);
 }
 
 /* Returns the window to open a new tab in if there is a suitable window,
@@ -2438,6 +2612,7 @@ empathy_chat_window_get_default (gboolean room)
 
   separate_windows = g_settings_get_boolean (gsettings,
       EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
+  separate_windows = FALSE;
 
   g_object_unref (gsettings);
 
@@ -2452,6 +2627,7 @@ empathy_chat_window_get_default (gboolean room)
 
       chat_window = l->data;
 
+#if 0
       empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
 
       /* Skip the window if there aren't any rooms in it */
@@ -2461,6 +2637,7 @@ empathy_chat_window_get_default (gboolean room)
       /* Skip the window if there aren't any 1-1 chats in it */
       if (!room && nb_private == 0)
         continue;
+#endif
 
       return chat_window;
     }
@@ -2491,6 +2668,7 @@ empathy_chat_window_add_chat (EmpathyChatWindow *self,
 
       separate_windows = g_settings_get_boolean (self->priv->gsettings_ui,
           EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
+          separate_windows = FALSE;
 
       if (empathy_chat_is_room (chat))
         name = "room-window";
@@ -2500,24 +2678,24 @@ empathy_chat_window_add_chat (EmpathyChatWindow *self,
           gint x, y;
 
           /* Save current position of the window */
-          gtk_window_get_position (GTK_WINDOW (self->priv->dialog), &x, &y);
+          //gtk_window_get_position (GTK_WINDOW (self), &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 (self->priv->dialog), name);
+          //empathy_geometry_bind (GTK_WINDOW (self), 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 (self->priv->dialog), x, y);
+          //gtk_window_move (GTK_WINDOW (self), 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);
         }
 
-      empathy_geometry_bind (GTK_WINDOW (self->priv->dialog), name);
+      //empathy_geometry_bind (GTK_WINDOW (self), name);
     }
 
   child = GTK_WIDGET (chat);
@@ -2642,7 +2820,7 @@ empathy_chat_window_find_chat (TpAccount *account,
 {
   GList *l;
 
-  g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
+  g_return_val_if_fail (!TPAW_STR_EMPTY (id), NULL);
 
   for (l = chat_windows; l; l = l->next)
     {
@@ -2665,14 +2843,21 @@ empathy_chat_window_find_chat (TpAccount *account,
   return NULL;
 }
 
-void
+EmpathyChatWindow *
 empathy_chat_window_present_chat (EmpathyChat *chat,
     gint64 timestamp)
 {
   EmpathyChatWindow *self;
   guint32 x_timestamp;
 
-  g_return_if_fail (EMPATHY_IS_CHAT (chat));
+  if (chat == NULL) {
+    /* initial window */
+    self = empathy_chat_window_new ();
+    gtk_widget_show (GTK_WIDGET (self));
+    return self;
+  }
+
+  g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
 
   self = chat_window_find_chat (chat);
 
@@ -2686,7 +2871,7 @@ empathy_chat_window_present_chat (EmpathyChat *chat,
 
           /* we want to display the newly created window even if we
            * don't present it */
-          gtk_widget_show (self->priv->dialog);
+          gtk_widget_show (GTK_WIDGET (self));
         }
 
       empathy_chat_window_add_chat (self, chat);
@@ -2696,7 +2881,7 @@ empathy_chat_window_present_chat (EmpathyChat *chat,
    * an action by the user
    */
   if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
-    return;
+    return self;
 
   if (x_timestamp != GDK_CURRENT_TIME)
     {
@@ -2706,20 +2891,21 @@ empathy_chat_window_present_chat (EmpathyChat *chat,
 
       if (self->priv->x_user_action_time != 0
         && X_EARLIER_OR_EQL (x_timestamp, self->priv->x_user_action_time))
-        return;
+        return self;
 
       self->priv->x_user_action_time = x_timestamp;
     }
 
   empathy_chat_window_switch_to_chat (self, chat);
 
-  /* Don't use empathy_window_present_with_time () which would move the window
+  /* Don't use tpaw_window_present_with_time () which would move the window
    * to our current desktop but move to the window's desktop instead. This is
    * more coherent with Shell's 'app is ready' notication which moves the view
    * to the app desktop rather than moving the app itself. */
-  empathy_move_to_window_desktop (GTK_WINDOW (self->priv->dialog), x_timestamp);
+  empathy_move_to_window_desktop (GTK_WINDOW (gtk_widget_get_toplevel(GTK_WIDGET(self))), x_timestamp);
 
   gtk_widget_grab_focus (chat->input_text_view);
+  return self;
 }
 
 static void
@@ -2743,3 +2929,9 @@ empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
   if (nb_private != NULL)
     *nb_private = _nb_private;
 }
+
+EmpathyIndividualManager *
+empathy_chat_window_get_individual_manager (EmpathyChatWindow *self)
+{
+  return self->priv->individual_mgr;
+}