]> git.0d.be Git - empathy.git/blobdiff - src/empathy-ft-manager.c
Merge branch 'irc-dialog-579800'
[empathy.git] / src / empathy-ft-manager.c
index 2febb5cbc8414d850441a3689cfc5b72c94cee77..98e58d4f318ce7d632b2bd1cb3938eef56182e06 100644 (file)
@@ -22,6 +22,7 @@
  * Authors: Xan Lopez
  *          Marco Barisione <marco@barisione.org>
  *          Jonny Lamb <jonny.lamb@collabora.co.uk>
+ *          Xavier Claessens <xclaesse@gmail.com>
  */
 
 /* The original file transfer manager code was copied from Epiphany */
@@ -32,8 +33,6 @@
 
 #include <glib/gi18n.h>
 #include <gtk/gtk.h>
-#include <libgnomevfs/gnome-vfs.h>
-#include <libgnomeui/libgnomeui.h>
 
 #define DEBUG_FLAG EMPATHY_DEBUG_FT
 #include <libempathy/empathy-debug.h>
@@ -47,6 +46,8 @@
 
 #include "empathy-ft-manager.h"
 
+#include "extensions/extensions.h"
+
 /**
  * SECTION:empathy-ft-manager
  * @short_description: File transfer dialog
@@ -67,13 +68,6 @@ enum
   COL_FT_OBJECT
 };
 
-enum
-{
-  PROGRESS_COL_POS,
-  FILE_COL_POS,
-  REMAINING_COL_POS
-};
-
 /**
  * EmpathyFTManagerPriv:
  *
@@ -102,40 +96,7 @@ enum
 
 G_DEFINE_TYPE (EmpathyFTManager, empathy_ft_manager, G_TYPE_OBJECT);
 
-static EmpathyFTManager *manager_p = NULL;
-
-/**
- * empathy_ft_manager_get_default:
- *
- * Returns a new #EmpathyFTManager if there is not already one, or the existing
- * one if it exists.
- *
- * Returns: a #EmpathyFTManager
- */
-EmpathyFTManager *
-empathy_ft_manager_get_default (void)
-{
-  if (!manager_p)
-    manager_p = g_object_new (EMPATHY_TYPE_FT_MANAGER, NULL);
-
-  return manager_p;
-}
-
-/**
- * empathy_ft_manager_get_dialog:
- * @ft_manager: an #EmpathyFTManager
- *
- * Returns the #GtkWidget of @ft_manager.
- *
- * Returns: the dialog
- */
-GtkWidget *
-empathy_ft_manager_get_dialog (EmpathyFTManager *ft_manager)
-{
-  g_return_val_if_fail (EMPATHY_IS_FT_MANAGER (ft_manager), NULL);
-
-  return ft_manager->priv->window;
-}
+static EmpathyFTManager *manager_singleton = NULL;
 
 static gchar *
 ft_manager_format_interval (gint interval)
@@ -149,8 +110,10 @@ ft_manager_format_interval (gint interval)
   secs = interval;
 
   if (hours > 0)
+    /* Translators: time left, when it is more than one hour */
     return g_strdup_printf (_("%u:%02u.%02u"), hours, mins, secs);
   else
+    /* Translators: time left, when is is less than one hour */
     return g_strdup_printf (_("%02u.%02u"), mins, secs);
 }
 
@@ -168,7 +131,7 @@ ft_manager_update_buttons (EmpathyFTManager *ft_manager)
   GtkTreeModel *model;
   GtkTreeIter iter;
   EmpathyTpFile *tp_file;
-  EmpFileTransferState state;
+  TpFileTransferState state;
   gboolean open_enabled = FALSE;
   gboolean abort_enabled = FALSE;
 
@@ -180,12 +143,14 @@ ft_manager_update_buttons (EmpathyFTManager *ft_manager)
       state = empathy_tp_file_get_state (tp_file, NULL);
 
       /* I can open the file if the transfer is completed and was incoming */
-      open_enabled = (state == EMP_FILE_TRANSFER_STATE_COMPLETED &&
+      open_enabled = (state == TP_FILE_TRANSFER_STATE_COMPLETED &&
         empathy_tp_file_is_incoming (tp_file));
 
       /* I can abort if the transfer is not already finished */
-      abort_enabled = (state != EMP_FILE_TRANSFER_STATE_CANCELLED &&
-        state != EMP_FILE_TRANSFER_STATE_COMPLETED);
+      abort_enabled = (state != TP_FILE_TRANSFER_STATE_CANCELLED &&
+        state != TP_FILE_TRANSFER_STATE_COMPLETED);
+
+      g_object_unref (tp_file);
     }
 
   gtk_widget_set_sensitive (ft_manager->priv->open_button, open_enabled);
@@ -193,23 +158,24 @@ ft_manager_update_buttons (EmpathyFTManager *ft_manager)
 }
 
 static const gchar *
-ft_manager_state_change_reason_to_string (EmpFileTransferStateChangeReason reason)
+ft_manager_state_change_reason_to_string (TpFileTransferStateChangeReason reason)
 {
   switch (reason)
     {
-      case EMP_FILE_TRANSFER_STATE_CHANGE_REASON_NONE:
-        return _("File transfer not completed");
-      case EMP_FILE_TRANSFER_STATE_CHANGE_REASON_LOCAL_STOPPED:
+      case TP_FILE_TRANSFER_STATE_CHANGE_REASON_NONE:
+        return _("No reason was specified");
+      case TP_FILE_TRANSFER_STATE_CHANGE_REASON_REQUESTED:
+        return _("The change in state was requested");      
+      case TP_FILE_TRANSFER_STATE_CHANGE_REASON_LOCAL_STOPPED:
         return _("You canceled the file transfer");
-      case EMP_FILE_TRANSFER_STATE_CHANGE_REASON_REMOTE_STOPPED:
+      case TP_FILE_TRANSFER_STATE_CHANGE_REASON_REMOTE_STOPPED:
         return _("The other participant canceled the file transfer");
-      case EMP_FILE_TRANSFER_STATE_CHANGE_REASON_LOCAL_ERROR:
+      case TP_FILE_TRANSFER_STATE_CHANGE_REASON_LOCAL_ERROR:
         return _("Error while trying to transfer the file");
-      case EMP_FILE_TRANSFER_STATE_CHANGE_REASON_REMOTE_ERROR:
+      case TP_FILE_TRANSFER_STATE_CHANGE_REASON_REMOTE_ERROR:
         return _("The other participant is unable to transfer the file");
-      default:
-        return _("Unknown reason");
     }
+  return _("Unknown reason");
 }
 
 static void
@@ -221,17 +187,18 @@ ft_manager_update_ft_row (EmpathyFTManager *ft_manager,
   GtkTreeIter iter;
   const gchar *filename;
   const gchar *contact_name;
-  gchar *msg;
-  gchar *remaining_str;
+  const gchar *msg;
+  gchar *msg_dup = NULL;
+  gchar *remaining_str = NULL;
   gchar *first_line_format;
-  gchar *first_line;
-  gchar *second_line;
+  gchar *first_line = NULL;
+  gchar *second_line = NULL;
   guint64 transferred_bytes;
   guint64 total_size;
   gint remaining = -1;
   gint percent;
-  EmpFileTransferState state;
-  EmpFileTransferStateChangeReason reason;
+  TpFileTransferState state;
+  TpFileTransferStateChangeReason reason;
   gboolean incoming;
 
   row_ref = ft_manager_get_row_from_tp_file (ft_manager, tp_file);
@@ -246,9 +213,14 @@ ft_manager_update_ft_row (EmpathyFTManager *ft_manager,
 
   switch (state)
     {
-      case EMP_FILE_TRANSFER_STATE_PENDING:
-      case EMP_FILE_TRANSFER_STATE_OPEN:
-      case EMP_FILE_TRANSFER_STATE_ACCEPTED:
+      case TP_FILE_TRANSFER_STATE_NONE:
+        /* This should never happen, the CM is broken. But we avoid warning
+         * because it's not our fault. */
+        DEBUG ("State is NONE, probably a broken CM");
+        break;
+      case TP_FILE_TRANSFER_STATE_PENDING:
+      case TP_FILE_TRANSFER_STATE_OPEN:
+      case TP_FILE_TRANSFER_STATE_ACCEPTED:
         if (incoming)
           /* translators: first %s is filename, second %s is the contact name */
           first_line_format = _("Receiving \"%s\" from %s");
@@ -258,18 +230,13 @@ ft_manager_update_ft_row (EmpathyFTManager *ft_manager,
 
         first_line = g_strdup_printf (first_line_format, filename, contact_name);
 
-        if (state == EMP_FILE_TRANSFER_STATE_OPEN
-            || (incoming && state == EMP_FILE_TRANSFER_STATE_ACCEPTED)
-            || (incoming && state == EMP_FILE_TRANSFER_STATE_PENDING))
+        if (state == TP_FILE_TRANSFER_STATE_OPEN || incoming)
           {
             gchar *total_size_str;
             gchar *transferred_bytes_str;
 
             if (total_size == EMPATHY_TP_FILE_UNKNOWN_SIZE)
-              /* translators: the text before the "|" is context to
-               * help you decide on the correct translation. You MUST
-               * OMIT it in the translated string. */
-              total_size_str = g_strdup (Q_("file size|Unknown"));
+              total_size_str = g_strdup (C_("file size", "Unknown"));
             else
               total_size_str = g_format_size_for_display (total_size);
 
@@ -284,13 +251,13 @@ ft_manager_update_ft_row (EmpathyFTManager *ft_manager,
 
           }
         else
-          second_line = g_strdup (_("Waiting the other participant's response"));
+          second_line = g_strdup (_("Waiting for the other participant's response"));
 
       remaining = empathy_tp_file_get_remaining_time (tp_file);
       break;
 
-    case EMP_FILE_TRANSFER_STATE_COMPLETED:
-      if (empathy_tp_file_is_incoming (tp_file))
+    case TP_FILE_TRANSFER_STATE_COMPLETED:
+      if (incoming)
         /* translators: first %s is filename, second %s
          * is the contact name */
         first_line = g_strdup_printf (
@@ -303,12 +270,12 @@ ft_manager_update_ft_row (EmpathyFTManager *ft_manager,
             _("\"%s\" sent to %s"), filename,
             contact_name);
 
-      second_line = g_strdup ("File transfer completed");
+      second_line = g_strdup (_("File transfer completed"));
 
       break;
 
-    case EMP_FILE_TRANSFER_STATE_CANCELLED:
-      if (empathy_tp_file_is_incoming (tp_file))
+    case TP_FILE_TRANSFER_STATE_CANCELLED:
+      if (incoming)
         /* translators: first %s is filename, second %s
          * is the contact name */
         first_line = g_strdup_printf (
@@ -325,45 +292,40 @@ ft_manager_update_ft_row (EmpathyFTManager *ft_manager,
           ft_manager_state_change_reason_to_string (reason));
 
       break;
-
-    default:
-      g_return_if_reached ();
-
     }
 
-  if (total_size != EMPATHY_TP_FILE_UNKNOWN_SIZE)
+  if (total_size != EMPATHY_TP_FILE_UNKNOWN_SIZE && total_size != 0)
     percent = transferred_bytes * 100 / total_size;
   else
     percent = -1;
 
   if (remaining < 0)
     {
-      if (state == EMP_FILE_TRANSFER_STATE_COMPLETED ||
-          state == EMP_FILE_TRANSFER_STATE_CANCELLED)
-        remaining_str = g_strdup ("");
-      else
-        /* translators: the text before the "|" is context to
-         * help you decide on the correct translation. You
-         * MUST OMIT it in the translated string. */
-        remaining_str = g_strdup (Q_("remaining time|Unknown"));
+      if (state != TP_FILE_TRANSFER_STATE_COMPLETED &&
+          state != TP_FILE_TRANSFER_STATE_CANCELLED)
+        remaining_str = g_strdup (C_("remaining time", "Unknown"));
     }
   else
     remaining_str = ft_manager_format_interval (remaining);
 
-  msg = g_strdup_printf ("%s\n%s", first_line, second_line);
+  if (first_line != NULL && second_line != NULL)
+    msg = msg_dup = g_strdup_printf ("%s\n%s", first_line, second_line);
+  else
+    msg = first_line ? first_line : second_line;
 
+  /* Set new values in the store */
   path = gtk_tree_row_reference_get_path (row_ref);
   gtk_tree_model_get_iter (ft_manager->priv->model, &iter, path);
   gtk_list_store_set (GTK_LIST_STORE (ft_manager->priv->model),
       &iter,
       COL_PERCENT, percent,
-      COL_MESSAGE, msg,
-      COL_REMAINING, remaining_str,
+      COL_MESSAGE, msg ? msg : "",
+      COL_REMAINING, remaining_str ? remaining_str : "",
       -1);
 
   gtk_tree_path_free (path);
 
-  g_free (msg);
+  g_free (msg_dup);
   g_free (first_line);
   g_free (second_line);
   g_free (remaining_str);
@@ -401,10 +363,7 @@ ft_manager_progress_cell_data_func (GtkTreeViewColumn *col,
   if (percent < 0)
     {
       percent = 0;
-      /* Translators: The text before the "|" is context to help you
-       * decide on the correct translation. You MUST OMIT it in the
-       * translated string. */
-      text = Q_("file transfer percent|Unknown");
+      text = C_("file transfer percent", "Unknown");
     }
 
   g_object_set (renderer, "text", text, "value", percent, NULL);
@@ -440,12 +399,14 @@ ft_manager_configure_event_cb (GtkWidget *widget,
 }
 
 static void
-ft_manager_remove_file_from_list (EmpathyFTManager *ft_manager,
+ft_manager_remove_file_from_model (EmpathyFTManager *ft_manager,
                                   EmpathyTpFile *tp_file)
 {
   GtkTreeRowReference *row_ref;
+  GtkTreeSelection *selection;
   GtkTreePath *path = NULL;
-  GtkTreeIter iter, iter2;
+  GtkTreeIter iter;
+  gboolean update_selection;
 
   row_ref = ft_manager_get_row_from_tp_file (ft_manager, tp_file);
   g_return_if_fail (row_ref);
@@ -454,50 +415,33 @@ ft_manager_remove_file_from_list (EmpathyFTManager *ft_manager,
       empathy_contact_get_name (empathy_tp_file_get_contact (tp_file)),
       empathy_tp_file_get_filename (tp_file));
 
-  /* Get the row we'll select after removal ("smart" selection) */
-
+  /* Get the iter from the row_ref */
   path = gtk_tree_row_reference_get_path (row_ref);
-  gtk_tree_model_get_iter (GTK_TREE_MODEL (ft_manager->priv->model),
-      &iter, path);
-  gtk_tree_path_free (path);
-
-  row_ref = NULL;
-  iter2 = iter;
-  if (gtk_tree_model_iter_next (GTK_TREE_MODEL (ft_manager->priv->model), &iter))
-    {
-      path = gtk_tree_model_get_path  (GTK_TREE_MODEL (ft_manager->priv->model), &iter);
-      row_ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (ft_manager->priv->model), path);
-    }
-  else
-    {
-      path = gtk_tree_model_get_path (GTK_TREE_MODEL (ft_manager->priv->model), &iter2);
-      if (gtk_tree_path_prev (path))
-        {
-          row_ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (ft_manager->priv->model),
-              path);
-        }
-    }
+  gtk_tree_model_get_iter (ft_manager->priv->model, &iter, path);
   gtk_tree_path_free (path);
 
-  /* Removal */
-
-  gtk_list_store_remove (GTK_LIST_STORE (ft_manager->priv->model), &iter2);
-  g_object_unref (tp_file);
-
-  /* Actual selection */
+  /* We have to update the selection only if we are removing the selected row */
+  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (ft_manager->priv->treeview));
+  update_selection = gtk_tree_selection_iter_is_selected (selection, &iter);
 
-  if (row_ref != NULL)
+  /* Remove tp_file's row. After that iter points to the next row */
+  if (!gtk_list_store_remove (GTK_LIST_STORE (ft_manager->priv->model), &iter))
     {
-      path = gtk_tree_row_reference_get_path (row_ref);
-      if (path != NULL)
-        {
-          gtk_tree_view_set_cursor (GTK_TREE_VIEW (ft_manager->priv->treeview),
-              path, NULL, FALSE);
-          gtk_tree_path_free (path);
-        }
-      gtk_tree_row_reference_free (row_ref);
+      gint n_row;
+
+      /* There is no next row, set iter to the last row */
+      n_row = gtk_tree_model_iter_n_children (ft_manager->priv->model, NULL);
+      if (n_row > 0)
+        gtk_tree_model_iter_nth_child (ft_manager->priv->model, &iter, NULL,
+          n_row - 1);
+      else
+        update_selection = FALSE;
     }
 
+  if (update_selection)
+    gtk_tree_selection_select_iter (selection, &iter);
+
+  empathy_tp_file_close (tp_file);
 }
 
 static gboolean
@@ -507,125 +451,47 @@ remove_finished_transfer_foreach (gpointer key,
 {
   EmpathyTpFile *tp_file = EMPATHY_TP_FILE (key);
   EmpathyFTManager *self = EMPATHY_FT_MANAGER (user_data);
-  EmpFileTransferState state;
+  TpFileTransferState state;
 
   state = empathy_tp_file_get_state (tp_file, NULL);
-  if (state == EMP_FILE_TRANSFER_STATE_COMPLETED ||
-      state == EMP_FILE_TRANSFER_STATE_CANCELLED)
+  if (state == TP_FILE_TRANSFER_STATE_COMPLETED ||
+      state == TP_FILE_TRANSFER_STATE_CANCELLED)
     {
-      ft_manager_remove_file_from_list (self, tp_file);
+      ft_manager_remove_file_from_model (self, tp_file);
       return TRUE;
     }
 
   return FALSE;
 }
 
-static void
-ft_manager_clear (EmpathyFTManager *ft_manager)
-{
-  DEBUG ("Clearing file transfer list");
-
-  /* Remove completed and cancelled transfers */
-  g_hash_table_foreach_remove (ft_manager->priv->tp_file_to_row_ref,
-      remove_finished_transfer_foreach, ft_manager);
-}
-
 static void
 ft_manager_state_changed_cb (EmpathyTpFile *tp_file,
                              GParamSpec *pspec,
                              EmpathyFTManager *ft_manager)
 {
-  gboolean remove;
-
-  switch (empathy_tp_file_get_state (tp_file, NULL))
+  if (empathy_tp_file_get_state (tp_file, NULL) ==
+      TP_FILE_TRANSFER_STATE_COMPLETED)
     {
-      case EMP_FILE_TRANSFER_STATE_COMPLETED:
-        if (empathy_tp_file_is_incoming (tp_file))
-          {
-            GtkRecentManager *manager;
-            const gchar *uri;
-
-            manager = gtk_recent_manager_get_default ();
-            uri = g_object_get_data (G_OBJECT (tp_file), "uri");
-            gtk_recent_manager_add_item (manager, uri);
-         }
-
-      case EMP_FILE_TRANSFER_STATE_CANCELLED:
-        /* Automatically remove file transfers if the
-         * window if not visible. */
-        /* FIXME how do the user know if the file transfer
-         * failed? */
-        remove = !GTK_WIDGET_VISIBLE (ft_manager->priv->window);
-        break;
+      GtkRecentManager *manager;
+      const gchar *uri;
 
-      default:
-        remove = FALSE;
-        break;
+      manager = gtk_recent_manager_get_default ();
+      uri = g_object_get_data (G_OBJECT (tp_file), "uri");
+      if (uri != NULL)
+        gtk_recent_manager_add_item (manager, uri);
     }
 
-  if (remove)
-    {
-      ft_manager_remove_file_from_list (ft_manager, tp_file);
-      g_hash_table_remove (ft_manager->priv->tp_file_to_row_ref, tp_file);
-    }
-  else
-    {
-      ft_manager_update_ft_row (ft_manager, tp_file);
-    }
+    ft_manager_update_ft_row (ft_manager, tp_file);
 }
 
 static void
-ft_manager_add_tp_file_to_list (EmpathyFTManager *ft_manager,
-                                EmpathyTpFile *tp_file)
+ft_manager_clear (EmpathyFTManager *ft_manager)
 {
-  GtkTreeRowReference  *row_ref;
-  GtkTreeIter iter;
-  GtkTreeSelection *selection;
-  GtkTreePath *path;
-  GtkIconTheme *theme;
-  gchar *icon_name;
-  gchar *content_type;
-
-  gtk_list_store_insert_with_values (GTK_LIST_STORE (ft_manager->priv->model),
-      &iter, G_MAXINT, COL_FT_OBJECT, tp_file, -1);
-
-  path =  gtk_tree_model_get_path (GTK_TREE_MODEL (ft_manager->priv->model),
-      &iter);
-  row_ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (
-      ft_manager->priv->model), path);
-  gtk_tree_path_free (path);
-
-  g_object_ref (tp_file);
-  g_hash_table_insert (ft_manager->priv->tp_file_to_row_ref, tp_file,
-      row_ref);
-
-  ft_manager_update_ft_row (ft_manager, tp_file);
-
-  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (
-      ft_manager->priv->treeview));
-  gtk_tree_selection_select_iter (selection, &iter);
-
-  g_signal_connect (tp_file, "notify::state",
-      G_CALLBACK (ft_manager_state_changed_cb), ft_manager);
-  g_signal_connect (tp_file, "notify::transferred-bytes",
-      G_CALLBACK (ft_manager_transferred_bytes_changed_cb), ft_manager);
-
-  g_object_get (tp_file, "content-type", &content_type, NULL);
-
-  theme = gtk_icon_theme_get_default ();
-  /* FIXME remove the dependency on libgnomeui replacing this function
-   * with gio/gvfs or copying the code from gtk-recent.
-   * With GTK+ 2.14 we can get the GIcon using g_content_type_get_icon
-   * and then use the "gicon" property of GtkCellRendererPixbuf. */
-  icon_name = gnome_icon_lookup (theme, NULL, NULL, NULL, NULL,
-      content_type, GNOME_ICON_LOOKUP_FLAGS_NONE, NULL);
-
-  gtk_list_store_set (GTK_LIST_STORE (
-      ft_manager->priv->model), &iter, COL_ICON, icon_name, -1);
+  DEBUG ("Clearing file transfer list");
 
-  gtk_window_present (GTK_WINDOW (ft_manager->priv->window));
-  g_free (content_type);
-  g_free (icon_name);
+  /* Remove completed and cancelled transfers */
+  g_hash_table_foreach_remove (ft_manager->priv->tp_file_to_row_ref,
+      remove_finished_transfer_foreach, ft_manager);
 }
 
 static void
@@ -647,7 +513,8 @@ ft_manager_open (EmpathyFTManager *ft_manager)
 
   uri = g_object_get_data (G_OBJECT (tp_file), "uri");
   DEBUG ("Opening URI: %s", uri);
-  empathy_url_show (uri);
+  empathy_url_show (GTK_WIDGET (ft_manager->priv->window), uri);
+  g_object_unref (tp_file);
 }
 
 static void
@@ -671,10 +538,11 @@ ft_manager_stop (EmpathyFTManager *ft_manager)
       empathy_tp_file_get_filename (tp_file));
 
   empathy_tp_file_cancel (tp_file);
+  g_object_unref (tp_file);
 }
 
 static void
-ft_manager_response_cb (GtkWidget *dialog,
+ft_manager_response_cb (GtkWidget *widget,
                         gint response,
                         EmpathyFTManager *ft_manager)
 {
@@ -692,9 +560,291 @@ ft_manager_response_cb (GtkWidget *dialog,
     }
 }
 
-/*
- * Receiving files
+static gboolean
+ft_manager_delete_event_cb (GtkWidget *widget,
+                            GdkEvent *event,
+                            EmpathyFTManager *ft_manager)
+{
+  ft_manager_clear (ft_manager);
+  if (g_hash_table_size (ft_manager->priv->tp_file_to_row_ref) > 0)
+    {
+      /* There is still FTs on flight, just hide the window */
+      DEBUG ("Hiding window");
+      gtk_widget_hide (widget);
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static void
+ft_manager_destroy_cb (GtkWidget *widget,
+                       EmpathyFTManager *ft_manager)
+{
+  ft_manager->priv->window = NULL;
+  if (ft_manager->priv->save_geometry_id != 0)
+    g_source_remove (ft_manager->priv->save_geometry_id);
+  g_hash_table_remove_all (ft_manager->priv->tp_file_to_row_ref);
+}
+
+static void
+ft_manager_build_ui (EmpathyFTManager *ft_manager)
+{
+  GtkBuilder *gui;
+  gint x, y, w, h;
+  GtkTreeView *view;
+  GtkListStore *liststore;
+  GtkTreeViewColumn *column;
+  GtkCellRenderer *renderer;
+  GtkTreeSelection *selection;
+  gchar *filename;
+
+  if (ft_manager->priv->window != NULL)
+    return;
+
+  filename = empathy_file_lookup ("empathy-ft-manager.ui", "src");
+  gui = empathy_builder_get_file (filename,
+      "ft_manager_dialog", &ft_manager->priv->window,
+      "ft_list", &ft_manager->priv->treeview,
+      "open_button", &ft_manager->priv->open_button,
+      "abort_button", &ft_manager->priv->abort_button,
+      NULL);
+  g_free (filename);
+
+  empathy_builder_connect (gui, ft_manager,
+      "ft_manager_dialog", "destroy", ft_manager_destroy_cb,
+      "ft_manager_dialog", "response", ft_manager_response_cb,
+      "ft_manager_dialog", "delete-event", ft_manager_delete_event_cb,
+      "ft_manager_dialog", "configure-event", ft_manager_configure_event_cb,
+      NULL);
+
+  g_object_unref (gui);
+
+  /* Window geometry. */
+  empathy_geometry_load ("ft-manager", &x, &y, &w, &h);
+
+  if (x >= 0 && y >= 0)
+    {
+      /* Let the window manager position it if we don't have
+       * good x, y coordinates. */
+      gtk_window_move (GTK_WINDOW (ft_manager->priv->window), x, y);
+    }
+
+  if (w > 0 && h > 0)
+    {
+      /* Use the defaults from the ui file if we don't have
+       * good w, h geometry. */
+      gtk_window_resize (GTK_WINDOW (ft_manager->priv->window), w, h);
+    }
+
+  /* Setup the tree view */
+  view = GTK_TREE_VIEW (ft_manager->priv->treeview);
+  selection = gtk_tree_view_get_selection (view);
+  gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
+  g_signal_connect (selection, "changed",
+      G_CALLBACK (ft_manager_selection_changed), ft_manager);
+  gtk_tree_view_set_headers_visible (view, TRUE);
+  gtk_tree_view_set_enable_search (view, FALSE);
+
+  /* Setup the model */
+  liststore = gtk_list_store_new (5,
+      G_TYPE_INT,     /* percent */
+      G_TYPE_ICON,    /* icon */
+      G_TYPE_STRING,  /* message */
+      G_TYPE_STRING,  /* remaining */
+      G_TYPE_OBJECT); /* ft_file */
+  gtk_tree_view_set_model (view, GTK_TREE_MODEL (liststore));
+  ft_manager->priv->model = GTK_TREE_MODEL (liststore);
+  g_object_unref (liststore);
+
+  /* Progress column */
+  column = gtk_tree_view_column_new ();
+  gtk_tree_view_column_set_title (column, _("%"));
+  gtk_tree_view_column_set_sort_column_id (column, COL_PERCENT);
+  gtk_tree_view_insert_column (view, column, -1);
+
+  renderer = gtk_cell_renderer_progress_new ();
+  g_object_set (renderer, "xalign", 0.5, NULL);
+  gtk_tree_view_column_pack_start (column, renderer, FALSE);
+  gtk_tree_view_column_set_cell_data_func (column, renderer,
+      ft_manager_progress_cell_data_func, NULL, NULL);
+
+  /* Icon and filename column*/
+  column = gtk_tree_view_column_new ();
+  gtk_tree_view_column_set_title (column, _("File"));
+  gtk_tree_view_column_set_expand (column, TRUE);
+  gtk_tree_view_column_set_resizable (column, TRUE);
+  gtk_tree_view_column_set_sort_column_id (column, COL_MESSAGE);
+  gtk_tree_view_column_set_spacing (column, 3);
+  gtk_tree_view_insert_column (view, column, -1);
+
+  renderer = gtk_cell_renderer_pixbuf_new ();
+  g_object_set (renderer, "xpad", 3,
+      "stock-size", GTK_ICON_SIZE_DND, NULL);
+  gtk_tree_view_column_pack_start (column, renderer, FALSE);
+  gtk_tree_view_column_set_attributes (column, renderer,
+      "gicon", COL_ICON, NULL);
+
+  renderer = gtk_cell_renderer_text_new ();
+  g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+  gtk_tree_view_column_pack_start (column, renderer, TRUE);
+  gtk_tree_view_column_set_attributes (column, renderer,
+      "text", COL_MESSAGE, NULL);
+
+  /* Remaining time column */
+  column = gtk_tree_view_column_new ();
+  gtk_tree_view_column_set_title (column, _("Remaining"));
+  gtk_tree_view_column_set_sort_column_id (column, COL_REMAINING);
+  gtk_tree_view_insert_column (view, column, -1);
+
+  renderer = gtk_cell_renderer_text_new ();
+  g_object_set (renderer, "xalign", 0.5, NULL);
+  gtk_tree_view_column_pack_start (column, renderer, FALSE);
+  gtk_tree_view_column_set_attributes (column, renderer,
+      "text", COL_REMAINING, NULL);
+}
+
+static void
+empathy_ft_manager_finalize (GObject *object)
+{
+  EmpathyFTManager *ft_manager = (EmpathyFTManager *) object;
+
+  DEBUG ("%p", object);
+
+  if (ft_manager->priv->window)
+    gtk_widget_destroy (ft_manager->priv->window);
+
+  g_hash_table_destroy (ft_manager->priv->tp_file_to_row_ref);
+
+  G_OBJECT_CLASS (empathy_ft_manager_parent_class)->finalize (object);
+}
+
+static void
+empathy_ft_manager_init (EmpathyFTManager *ft_manager)
+{
+  EmpathyFTManagerPriv *priv;
+
+  priv = G_TYPE_INSTANCE_GET_PRIVATE ((ft_manager), EMPATHY_TYPE_FT_MANAGER,
+      EmpathyFTManagerPriv);
+
+  ft_manager->priv = priv;
+
+  priv->tp_file_to_row_ref = g_hash_table_new_full (g_direct_hash,
+      g_direct_equal, (GDestroyNotify) g_object_unref,
+      (GDestroyNotify) gtk_tree_row_reference_free);
+}
+
+static GObject *
+empathy_ft_manager_constructor (GType type,
+                                guint n_props,
+                                GObjectConstructParam *props)
+{
+  GObject *retval;
+
+  if (manager_singleton)
+    {
+      retval = g_object_ref (manager_singleton);
+    }
+  else
+    {
+      retval = G_OBJECT_CLASS (empathy_ft_manager_parent_class)->constructor
+          (type, n_props, props);
+
+      manager_singleton = EMPATHY_FT_MANAGER (retval);
+      g_object_add_weak_pointer (retval, (gpointer) &manager_singleton);
+    }
+
+  return retval;
+}
+
+static void
+empathy_ft_manager_class_init (EmpathyFTManagerClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = empathy_ft_manager_finalize;
+  object_class->constructor = empathy_ft_manager_constructor;
+
+  g_type_class_add_private (object_class, sizeof (EmpathyFTManagerPriv));
+}
+
+/**
+ * empathy_ft_manager_dup_singleton:
+ *
+ * Returns a reference to the #EmpathyFTManager singleton object.
+ *
+ * Returns: a #EmpathyFTManager
+ */
+EmpathyFTManager *
+empathy_ft_manager_dup_singleton (void)
+{
+  return g_object_new (EMPATHY_TYPE_FT_MANAGER, NULL);
+}
+
+/**
+ * empathy_ft_manager_get_dialog:
+ * @ft_manager: an #EmpathyFTManager
+ *
+ * Returns the #GtkWidget of @ft_manager.
+ *
+ * Returns: the dialog
  */
+GtkWidget *
+empathy_ft_manager_get_dialog (EmpathyFTManager *ft_manager)
+{
+  g_return_val_if_fail (EMPATHY_IS_FT_MANAGER (ft_manager), NULL);
+
+  ft_manager_build_ui (ft_manager);
+
+  return ft_manager->priv->window;
+}
+
+static void
+ft_manager_add_tp_file_to_list (EmpathyFTManager *ft_manager,
+                                EmpathyTpFile *tp_file)
+{
+  GtkTreeRowReference *row_ref;
+  GtkTreeIter iter;
+  GtkTreeSelection *selection;
+  GtkTreePath *path;
+  GIcon *icon;
+  const gchar *content_type;
+
+  ft_manager_build_ui (ft_manager);
+
+  /* Get the icon name from the mime-type of the file. */
+  content_type = empathy_tp_file_get_content_type (tp_file);
+  icon = g_content_type_get_icon (content_type);
+
+  /* Append the ft in the store */
+  gtk_list_store_insert_with_values (GTK_LIST_STORE (ft_manager->priv->model),
+      &iter, G_MAXINT, COL_FT_OBJECT, tp_file, COL_ICON, icon, -1);
+
+  g_object_unref (icon);
+
+  /* Insert the new row_ref in the hash table  */
+  path = gtk_tree_model_get_path (GTK_TREE_MODEL (ft_manager->priv->model),
+      &iter);
+  row_ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (
+      ft_manager->priv->model), path);
+  gtk_tree_path_free (path);
+  g_hash_table_insert (ft_manager->priv->tp_file_to_row_ref,
+      g_object_ref (tp_file), row_ref);
+
+  /* Select the new row */
+  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (
+      ft_manager->priv->treeview));
+  gtk_tree_selection_select_iter (selection, &iter);
+
+  /* Update the row with the initial values, and keep track of changes */
+  ft_manager_update_ft_row (ft_manager, tp_file);
+  g_signal_connect (tp_file, "notify::state",
+      G_CALLBACK (ft_manager_state_changed_cb), ft_manager);
+  g_signal_connect (tp_file, "notify::transferred-bytes",
+      G_CALLBACK (ft_manager_transferred_bytes_changed_cb), ft_manager);
+
+  gtk_window_present (GTK_WINDOW (ft_manager->priv->window));
+}
 
 typedef struct {
   EmpathyFTManager *ft_manager;
@@ -868,6 +1018,7 @@ ft_manager_display_accept_dialog (EmpathyFTManager *ft_manager,
 
   gtk_message_dialog_format_secondary_text
       (GTK_MESSAGE_DIALOG (dialog),
+       /* Translators: the first %s is the file name, the second %s is the file size */
        _("Do you want to accept the file \"%s\" (%s)?"),
        filename, size_str);
 
@@ -921,200 +1072,21 @@ void
 empathy_ft_manager_add_tp_file (EmpathyFTManager *ft_manager,
                                 EmpathyTpFile *tp_file)
 {
-  EmpFileTransferState state;
+  TpFileTransferState state;
 
   g_return_if_fail (EMPATHY_IS_FT_MANAGER (ft_manager));
   g_return_if_fail (EMPATHY_IS_TP_FILE (tp_file));
 
-  DEBUG ("Adding a file transfer: contact=%s, filename=%s",
-      empathy_contact_get_name (empathy_tp_file_get_contact (tp_file)),
-      empathy_tp_file_get_filename (tp_file));
-
   state = empathy_tp_file_get_state (tp_file, NULL);
 
-  if (state == EMP_FILE_TRANSFER_STATE_PENDING &&
+  DEBUG ("Adding a file transfer: contact=%s, filename=%s, state=%d",
+      empathy_contact_get_name (empathy_tp_file_get_contact (tp_file)),
+      empathy_tp_file_get_filename (tp_file), state);
+
+  if (state == TP_FILE_TRANSFER_STATE_PENDING &&
       empathy_tp_file_is_incoming (tp_file))
     ft_manager_display_accept_dialog (ft_manager, tp_file);
   else
     ft_manager_add_tp_file_to_list (ft_manager, tp_file);
 }
 
-static void
-empathy_ft_manager_finalize (GObject *object)
-{
-  EmpathyFTManager *ft_manager = (EmpathyFTManager *) object;
-
-  DEBUG ("Finalizing: %p", object);
-
-  g_hash_table_destroy (ft_manager->priv->tp_file_to_row_ref);
-
-  if (ft_manager->priv->save_geometry_id != 0)
-    g_source_remove (ft_manager->priv->save_geometry_id);
-
-  G_OBJECT_CLASS (empathy_ft_manager_parent_class)->finalize (object);
-}
-
-static gboolean
-ft_manager_delete_event_cb (GtkWidget *widget,
-                            GdkEvent *event,
-                            EmpathyFTManager *ft_manager)
-{
-  ft_manager_clear (ft_manager);
-  if (g_hash_table_size (ft_manager->priv->tp_file_to_row_ref) == 0)
-    {
-      DEBUG ("Destroying window");
-      if (manager_p != NULL)
-        g_object_unref (manager_p);
-
-      manager_p = NULL;
-      return FALSE;
-    }
-  else
-    {
-      DEBUG ("Hiding window");
-      gtk_widget_hide (widget);
-      return TRUE;
-    }
-}
-
-static void
-ft_manager_build_ui (EmpathyFTManager *ft_manager)
-{
-  gint x, y, w, h;
-  GtkListStore *liststore;
-  GtkTreeViewColumn *column;
-  GtkCellRenderer *renderer;
-  GtkTreeSelection *selection;
-  gchar *filename;
-
-  filename = empathy_file_lookup ("empathy-ft-manager.glade",
-      "libempathy-gtk");
-  empathy_glade_get_file (filename,
-      "ft_manager_dialog", NULL,
-      "ft_manager_dialog", &ft_manager->priv->window,
-      "ft_list", &ft_manager->priv->treeview,
-      "open_button", &ft_manager->priv->open_button,
-      "abort_button", &ft_manager->priv->abort_button,
-      NULL);
-  g_free (filename);
-
-  g_signal_connect (ft_manager->priv->window, "response",
-      G_CALLBACK (ft_manager_response_cb), ft_manager);
-  g_signal_connect (ft_manager->priv->window, "delete-event",
-      G_CALLBACK (ft_manager_delete_event_cb), ft_manager);
-  g_signal_connect (ft_manager->priv->window, "configure-event",
-      G_CALLBACK (ft_manager_configure_event_cb), ft_manager);
-
-  /* Window geometry. */
-  empathy_geometry_load ("ft-manager", &x, &y, &w, &h);
-
-  if (x >= 0 && y >= 0)
-    {
-      /* Let the window manager position it if we don't have
-       * good x, y coordinates. */
-      gtk_window_move (GTK_WINDOW (ft_manager->priv->window), x, y);
-    }
-
-  if (w > 0 && h > 0)
-    {
-      /* Use the defaults from the glade file if we don't have
-       * good w, h geometry. */
-      gtk_window_resize (GTK_WINDOW (ft_manager->priv->window), w, h);
-    }
-
-  gtk_tree_selection_set_mode (gtk_tree_view_get_selection (GTK_TREE_VIEW (
-      ft_manager->priv->treeview)), GTK_SELECTION_BROWSE);
-
-  liststore = gtk_list_store_new (5, G_TYPE_INT, G_TYPE_STRING,
-      G_TYPE_STRING, G_TYPE_STRING, G_TYPE_OBJECT);
-
-  gtk_tree_view_set_model (GTK_TREE_VIEW(ft_manager->priv->treeview),
-      GTK_TREE_MODEL (liststore));
-  g_object_unref (liststore);
-  gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(ft_manager->priv->treeview), TRUE);
-
-  /* Icon and filename column*/
-  column = gtk_tree_view_column_new ();
-  gtk_tree_view_column_set_title (column, _("File"));
-  renderer = gtk_cell_renderer_pixbuf_new ();
-  g_object_set (renderer, "xpad", 3, NULL);
-  gtk_tree_view_column_pack_start (column, renderer, FALSE);
-  gtk_tree_view_column_set_attributes (column, renderer,
-      "icon-name", COL_ICON,
-      NULL);
-  g_object_set (renderer, "stock-size", GTK_ICON_SIZE_DND, NULL);
-  renderer = gtk_cell_renderer_text_new ();
-  g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
-  gtk_tree_view_column_pack_start (column, renderer, TRUE);
-  gtk_tree_view_column_set_attributes (column, renderer,
-      "text", COL_MESSAGE,
-      NULL);
-  gtk_tree_view_insert_column (GTK_TREE_VIEW (ft_manager->priv->treeview), column,
-      FILE_COL_POS);
-  gtk_tree_view_column_set_expand (column, TRUE);
-  gtk_tree_view_column_set_resizable (column, TRUE);
-  gtk_tree_view_column_set_sort_column_id (column, COL_MESSAGE);
-  gtk_tree_view_column_set_spacing (column, 3);
-
-  /* Progress column */
-  renderer = gtk_cell_renderer_progress_new ();
-  g_object_set (renderer, "xalign", 0.5, NULL);
-  gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (ft_manager->priv->treeview),
-      PROGRESS_COL_POS, _("%"),
-      renderer,
-      NULL);
-  column = gtk_tree_view_get_column (GTK_TREE_VIEW (ft_manager->priv->treeview),
-      PROGRESS_COL_POS);
-  gtk_tree_view_column_set_cell_data_func(column, renderer,
-      ft_manager_progress_cell_data_func,
-      NULL, NULL);
-  gtk_tree_view_column_set_sort_column_id (column, COL_PERCENT);
-
-  /* Remaining time column */
-  renderer = gtk_cell_renderer_text_new ();
-  g_object_set (renderer, "xalign", 0.5, NULL);
-  gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (
-      ft_manager->priv->treeview), REMAINING_COL_POS, _("Remaining"),
-      renderer, "text", COL_REMAINING, NULL);
-
-  column = gtk_tree_view_get_column (GTK_TREE_VIEW (
-      ft_manager->priv->treeview),
-      REMAINING_COL_POS);
-  gtk_tree_view_column_set_sort_column_id (column, COL_REMAINING);
-
-  gtk_tree_view_set_enable_search (GTK_TREE_VIEW (ft_manager->priv->treeview),
-      FALSE);
-
-  ft_manager->priv->model = GTK_TREE_MODEL (liststore);
-
-  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (
-      ft_manager->priv->treeview));
-  g_signal_connect (selection, "changed",
-      G_CALLBACK (ft_manager_selection_changed), ft_manager);
-}
-
-static void
-empathy_ft_manager_init (EmpathyFTManager *ft_manager)
-{
-  EmpathyFTManagerPriv *priv;
-
-  priv = G_TYPE_INSTANCE_GET_PRIVATE ((ft_manager), EMPATHY_TYPE_FT_MANAGER,
-      EmpathyFTManagerPriv);
-
-  ft_manager->priv = priv;
-
-  priv->tp_file_to_row_ref = g_hash_table_new_full (g_direct_hash,
-      g_direct_equal, NULL, (GDestroyNotify) gtk_tree_row_reference_free);
-
-  ft_manager_build_ui (ft_manager);
-}
-
-static void
-empathy_ft_manager_class_init (EmpathyFTManagerClass *klass)
-{
-  GObjectClass *object_class = G_OBJECT_CLASS (klass);
-
-  object_class->finalize = empathy_ft_manager_finalize;
-
-  g_type_class_add_private (object_class, sizeof (EmpathyFTManagerPriv));
-}