]> git.0d.be Git - empathy.git/blobdiff - libempathy-gtk/empathy-ui-utils.c
Merge branch 'sasl'
[empathy.git] / libempathy-gtk / empathy-ui-utils.c
index aeb665c7499532a4eae181dcacc4b6c5bfc06d19..4c128742405b72274640e1b50b70b986c9594de2 100644 (file)
@@ -1,7 +1,7 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
 /*
  * Copyright (C) 2002-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
@@ -23,6 +23,7 @@
  *          Martyn Russell <martyn@imendio.com>
  *          Xavier Claessens <xclaesse@gmail.com>
  *          Jonny Lamb <jonny.lamb@collabora.co.uk>
+ *          Travis Reitter <travis.reitter@collabora.co.uk>
  *
  *          Part of this file is copied from GtkSourceView (gtksourceiter.c):
  *          Paolo Maggi
 #include <gtk/gtk.h>
 #include <gio/gio.h>
 
+#include <telepathy-glib/util.h>
+#include <folks/folks.h>
+
 #include "empathy-ui-utils.h"
 #include "empathy-images.h"
-#include "empathy-conf.h"
+#include "empathy-smiley-manager.h"
 
 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
 #include <libempathy/empathy-debug.h>
 #include <libempathy/empathy-idle.h>
 #include <libempathy/empathy-ft-factory.h>
 
-#define SCHEMES "(https?|s?ftps?|nntp|news|javascript|about|ghelp|apt|telnet|"\
-               "file|webcal|mailto)"
-#define BODY "([^\\ \\n]+)"
-#define END_BODY "([^\\ \\n]*[^,;\?><()\\ \"\\.\\n])"
-#define URI_REGEX "("SCHEMES"://"END_BODY")" \
-                 "|((mailto:)?"BODY"@"BODY"\\."END_BODY")"\
-                 "|((www|ftp)\\."END_BODY")"
-
 void
 empathy_gtk_init (void)
 {
@@ -72,19 +68,6 @@ empathy_gtk_init (void)
        initialized = TRUE;
 }
 
-GRegex *
-empathy_uri_regex_dup_singleton (void)
-{
-       static GRegex *uri_regex = NULL;
-
-       /* We intentionally leak the regex so it's not recomputed */
-       if (!uri_regex) {
-               uri_regex = g_regex_new (URI_REGEX, 0, 0, NULL);
-       }
-
-       return g_regex_ref (uri_regex);
-}
-
 static GtkBuilder *
 builder_get_file_valist (const gchar *filename,
                         const gchar *first_object,
@@ -98,6 +81,7 @@ builder_get_file_valist (const gchar *filename,
        DEBUG ("Loading file %s", filename);
 
        gui = gtk_builder_new ();
+       gtk_builder_set_translation_domain (gui, GETTEXT_PACKAGE);
        if (!gtk_builder_add_from_file (gui, filename, &error)) {
                g_critical ("GtkBuilder Error (%s): %s",
                                filename, error->message);
@@ -145,20 +129,20 @@ empathy_builder_get_file (const gchar *filename,
 }
 
 void
-empathy_builder_connect (GtkBuilder *gui,
-                        gpointer    user_data,
-                        gchar      *first_object,
+empathy_builder_connect (GtkBuilder  *gui,
+                        gpointer     user_data,
+                        const gchar *first_object,
                         ...)
 {
        va_list      args;
        const gchar *name;
-       const gchar *signal;
+       const gchar *sig;
        GObject     *object;
        GCallback    callback;
 
        va_start (args, first_object);
        for (name = first_object; name; name = va_arg (args, const gchar *)) {
-               signal = va_arg (args, const gchar *);
+               sig = va_arg (args, const gchar *);
                callback = va_arg (args, GCallback);
 
                object = gtk_builder_get_object (gui, name);
@@ -167,7 +151,7 @@ empathy_builder_connect (GtkBuilder *gui,
                        continue;
                }
 
-               g_signal_connect (object, signal, callback, user_data);
+               g_signal_connect (object, sig, callback, user_data);
        }
 
        va_end (args);
@@ -201,14 +185,28 @@ empathy_icon_name_for_presence (TpConnectionPresenceType presence)
        case TP_CONNECTION_PRESENCE_TYPE_AWAY:
                return EMPATHY_IMAGE_AWAY;
        case TP_CONNECTION_PRESENCE_TYPE_EXTENDED_AWAY:
-               return EMPATHY_IMAGE_EXT_AWAY;
+               if (gtk_icon_theme_has_icon (gtk_icon_theme_get_default (),
+                                            EMPATHY_IMAGE_EXT_AWAY))
+                       return EMPATHY_IMAGE_EXT_AWAY;
+
+               /* The 'extended-away' icon is not an official one so we fallback to idle if
+                * it's not implemented */
+               return EMPATHY_IMAGE_IDLE;
        case TP_CONNECTION_PRESENCE_TYPE_HIDDEN:
-               return EMPATHY_IMAGE_HIDDEN;
+               if (gtk_icon_theme_has_icon (gtk_icon_theme_get_default (),
+                                            EMPATHY_IMAGE_HIDDEN))
+                       return EMPATHY_IMAGE_HIDDEN;
+
+               /* The 'hidden' icon is not an official one so we fallback to offline if
+                * it's not implemented */
+               return EMPATHY_IMAGE_OFFLINE;
        case TP_CONNECTION_PRESENCE_TYPE_OFFLINE:
        case TP_CONNECTION_PRESENCE_TYPE_ERROR:
-       case TP_CONNECTION_PRESENCE_TYPE_UNKNOWN:
                return EMPATHY_IMAGE_OFFLINE;
+       case TP_CONNECTION_PRESENCE_TYPE_UNKNOWN:
+               return EMPATHY_IMAGE_PENDING;
        case TP_CONNECTION_PRESENCE_TYPE_UNSET:
+       default:
                return NULL;
        }
 
@@ -227,6 +225,34 @@ empathy_icon_name_for_contact (EmpathyContact *contact)
        return empathy_icon_name_for_presence (presence);
 }
 
+const gchar *
+empathy_icon_name_for_individual (FolksIndividual *individual)
+{
+       FolksPresenceType folks_presence;
+       TpConnectionPresenceType presence;
+
+       folks_presence =
+           folks_presence_get_presence_type (FOLKS_PRESENCE (individual));
+       presence = empathy_folks_presence_type_to_tp (folks_presence);
+
+       return empathy_icon_name_for_presence (presence);
+}
+
+const gchar *
+empathy_protocol_name_for_contact (EmpathyContact   *contact)
+{
+       TpAccount     *account;
+
+       g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
+
+       account = empathy_contact_get_account (contact);
+       if (account == NULL) {
+               return NULL;
+       }
+
+       return tp_account_get_icon_name (account);
+}
+
 GdkPixbuf *
 empathy_pixbuf_from_data (gchar *data,
                          gsize  data_size)
@@ -250,7 +276,7 @@ empathy_pixbuf_from_data_and_mime (gchar  *data,
        }
 
        loader = gdk_pixbuf_loader_new ();
-       if (!gdk_pixbuf_loader_write (loader, data, data_size, &error)) {
+       if (!gdk_pixbuf_loader_write (loader, (guchar *) data, data_size, &error)) {
                DEBUG ("Failed to write to pixbuf loader: %s",
                        error ? error->message : "No error given");
                goto out;
@@ -413,6 +439,35 @@ empathy_gdk_pixbuf_is_opaque (GdkPixbuf *pixbuf)
        return TRUE;
 }
 
+static GdkPixbuf *
+avatar_pixbuf_from_loader (GdkPixbufLoader *loader)
+{
+       GdkPixbuf *pixbuf;
+
+       pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
+       if (!gdk_pixbuf_get_has_alpha (pixbuf)) {
+               GdkPixbuf *rounded_pixbuf;
+
+               rounded_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
+                                                gdk_pixbuf_get_width (pixbuf),
+                                                gdk_pixbuf_get_height (pixbuf));
+               gdk_pixbuf_copy_area (pixbuf, 0, 0,
+                                     gdk_pixbuf_get_width (pixbuf),
+                                     gdk_pixbuf_get_height (pixbuf),
+                                     rounded_pixbuf,
+                                     0, 0);
+               pixbuf = rounded_pixbuf;
+       } else {
+               g_object_ref (pixbuf);
+       }
+
+       if (empathy_gdk_pixbuf_is_opaque (pixbuf)) {
+               empathy_avatar_pixbuf_roundify (pixbuf);
+       }
+
+       return pixbuf;
+}
+
 GdkPixbuf *
 empathy_pixbuf_from_avatar_scaled (EmpathyAvatar *avatar,
                                  gint          width,
@@ -446,27 +501,7 @@ empathy_pixbuf_from_avatar_scaled (EmpathyAvatar *avatar,
        }
 
        gdk_pixbuf_loader_close (loader, NULL);
-
-       pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
-       if (!gdk_pixbuf_get_has_alpha (pixbuf)) {
-               GdkPixbuf *rounded_pixbuf;
-
-               rounded_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
-                                                gdk_pixbuf_get_width (pixbuf),
-                                                gdk_pixbuf_get_height (pixbuf));
-               gdk_pixbuf_copy_area (pixbuf, 0, 0,
-                                     gdk_pixbuf_get_width (pixbuf),
-                                     gdk_pixbuf_get_height (pixbuf),
-                                     rounded_pixbuf,
-                                     0, 0);
-               pixbuf = rounded_pixbuf;
-       } else {
-               g_object_ref (pixbuf);
-       }
-
-       if (empathy_gdk_pixbuf_is_opaque (pixbuf)) {
-               empathy_avatar_pixbuf_roundify (pixbuf);
-       }
+       pixbuf = avatar_pixbuf_from_loader (loader);
 
        g_object_unref (loader);
 
@@ -487,6 +522,264 @@ empathy_pixbuf_avatar_from_contact_scaled (EmpathyContact *contact,
        return empathy_pixbuf_from_avatar_scaled (avatar, width, height);
 }
 
+typedef struct {
+       FolksIndividual *individual;
+       GSimpleAsyncResult *result;
+       guint width;
+       guint height;
+} PixbufAvatarFromIndividualClosure;
+
+static PixbufAvatarFromIndividualClosure *
+pixbuf_avatar_from_individual_closure_new (FolksIndividual    *individual,
+                                          GSimpleAsyncResult *result,
+                                          gint                width,
+                                          gint                height)
+{
+       PixbufAvatarFromIndividualClosure *closure;
+
+       g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
+       g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
+
+       closure = g_new0 (PixbufAvatarFromIndividualClosure, 1);
+       closure->individual = g_object_ref (individual);
+       closure->result = g_object_ref (result);
+       closure->width = width;
+       closure->height = height;
+
+       return closure;
+}
+
+static void
+pixbuf_avatar_from_individual_closure_free (
+               PixbufAvatarFromIndividualClosure *closure)
+{
+       g_object_unref (closure->individual);
+       g_object_unref (closure->result);
+       g_free (closure);
+}
+
+static void
+avatar_file_load_contents_cb (GObject      *object,
+                             GAsyncResult *result,
+                             gpointer      user_data)
+{
+       GFile *file = G_FILE (object);
+       PixbufAvatarFromIndividualClosure *closure = user_data;
+       char *data = NULL;
+       gsize data_size;
+       struct SizeData size_data;
+       GError *error = NULL;
+       GdkPixbufLoader *loader = NULL;
+
+       if (!g_file_load_contents_finish (file, result, &data, &data_size,
+                               NULL, &error)) {
+               DEBUG ("failed to load avatar from file: %s",
+                               error->message);
+               g_simple_async_result_set_from_error (closure->result, error);
+               goto out;
+       }
+
+       size_data.width = closure->width;
+       size_data.height = closure->height;
+       size_data.preserve_aspect_ratio = TRUE;
+
+       loader = gdk_pixbuf_loader_new ();
+
+       g_signal_connect (loader, "size-prepared",
+                         G_CALLBACK (pixbuf_from_avatar_size_prepared_cb),
+                         &size_data);
+
+       if (!gdk_pixbuf_loader_write (loader, (guchar *) data, data_size,
+                               &error)) {
+               DEBUG ("Failed to write to pixbuf loader: %s",
+                       error ? error->message : "No error given");
+               g_simple_async_result_set_from_error (closure->result, error);
+               goto out;
+       }
+       if (!gdk_pixbuf_loader_close (loader, &error)) {
+               DEBUG ("Failed to close pixbuf loader: %s",
+                       error ? error->message : "No error given");
+               g_simple_async_result_set_from_error (closure->result, error);
+               goto out;
+       }
+
+       g_simple_async_result_set_op_res_gpointer (closure->result,
+                       avatar_pixbuf_from_loader (loader), g_object_unref);
+
+out:
+       g_simple_async_result_complete (closure->result);
+
+       g_clear_error (&error);
+       g_free (data);
+       tp_clear_object (&loader);
+       pixbuf_avatar_from_individual_closure_free (closure);
+}
+
+void
+empathy_pixbuf_avatar_from_individual_scaled_async (
+               FolksIndividual     *individual,
+               gint                 width,
+               gint                 height,
+               GCancellable        *cancellable,
+               GAsyncReadyCallback  callback,
+               gpointer             user_data)
+{
+       GFile *avatar_file;
+       GSimpleAsyncResult *result;
+       PixbufAvatarFromIndividualClosure *closure;
+
+       result = g_simple_async_result_new (G_OBJECT (individual),
+                       callback, user_data,
+                       empathy_pixbuf_avatar_from_individual_scaled_async);
+
+       avatar_file = folks_avatar_get_avatar (FOLKS_AVATAR (individual));
+       if (avatar_file == NULL)
+               goto out;
+
+       closure = pixbuf_avatar_from_individual_closure_new (individual, result,
+                                                            width, height);
+       if (closure == NULL)
+               goto out;
+
+       g_file_load_contents_async (avatar_file, cancellable,
+                       avatar_file_load_contents_cb, closure);
+
+       g_object_unref (result);
+
+       return;
+
+out:
+       g_simple_async_result_set_op_res_gpointer (result, NULL, NULL);
+       g_simple_async_result_complete (result);
+       g_object_unref (result);
+}
+
+/* Return a ref on the GdkPixbuf */
+GdkPixbuf *
+empathy_pixbuf_avatar_from_individual_scaled_finish (
+               FolksIndividual *individual,
+               GAsyncResult *result,
+               GError **error)
+{
+       GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
+       gboolean result_valid;
+       GdkPixbuf *pixbuf;
+
+       g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
+       g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (simple), NULL);
+
+       if (g_simple_async_result_propagate_error (simple, error))
+               return NULL;
+
+       result_valid = g_simple_async_result_is_valid (result,
+                       G_OBJECT (individual),
+                       empathy_pixbuf_avatar_from_individual_scaled_async);
+       g_return_val_if_fail (result_valid, NULL);
+
+       pixbuf = g_simple_async_result_get_op_res_gpointer (simple);
+       return pixbuf != NULL ? g_object_ref (pixbuf) : NULL;
+}
+
+GdkPixbuf *
+empathy_pixbuf_contact_status_icon (EmpathyContact *contact,
+                                  gboolean       show_protocol)
+{
+       const gchar *icon_name;
+
+       g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
+
+       icon_name = empathy_icon_name_for_contact (contact);
+
+       if (icon_name == NULL) {
+               return NULL;
+       }
+       return empathy_pixbuf_contact_status_icon_with_icon_name (contact,
+           icon_name,
+           show_protocol);
+}
+
+GdkPixbuf *
+empathy_pixbuf_contact_status_icon_with_icon_name (EmpathyContact *contact,
+                                         const gchar    *icon_name,
+                                         gboolean       show_protocol)
+{
+       GdkPixbuf *pix_status;
+       GdkPixbuf *pix_protocol;
+       gchar     *icon_filename;
+       gint       height, width;
+       gint       numerator, denominator;
+
+       g_return_val_if_fail (EMPATHY_IS_CONTACT (contact) ||
+                       (show_protocol == FALSE), NULL);
+       g_return_val_if_fail (icon_name != NULL, NULL);
+
+       numerator = 3;
+       denominator = 4;
+
+       icon_filename = empathy_filename_from_icon_name (icon_name,
+                                                        GTK_ICON_SIZE_MENU);
+       if (icon_filename == NULL) {
+               DEBUG ("icon name: %s could not be found\n", icon_name);
+               return NULL;
+       }
+
+       pix_status = gdk_pixbuf_new_from_file (icon_filename, NULL);
+
+       if (pix_status == NULL) {
+               DEBUG ("Could not open icon %s\n", icon_filename);
+               g_free (icon_filename);
+               return NULL;
+       }
+
+       g_free (icon_filename);
+
+       if (!show_protocol)
+               return pix_status;
+
+       height = gdk_pixbuf_get_height (pix_status);
+       width = gdk_pixbuf_get_width (pix_status);
+
+       pix_protocol = empathy_pixbuf_protocol_from_contact_scaled (contact,
+                                                                   width * numerator / denominator,
+                                                                   height * numerator / denominator);
+
+       if (pix_protocol == NULL) {
+               return pix_status;
+       }
+       gdk_pixbuf_composite (pix_protocol, pix_status,
+           0, height - height * numerator / denominator,
+           width * numerator / denominator, height * numerator / denominator,
+           0, height - height * numerator / denominator,
+           1, 1,
+           GDK_INTERP_BILINEAR, 255);
+
+       g_object_unref (pix_protocol);
+
+       return pix_status;
+}
+
+GdkPixbuf *
+empathy_pixbuf_protocol_from_contact_scaled (EmpathyContact *contact,
+                                         gint           width,
+                                         gint           height)
+{
+       TpAccount *account;
+       gchar     *filename;
+       GdkPixbuf *pixbuf = NULL;
+
+       g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
+
+       account = empathy_contact_get_account (contact);
+       filename = empathy_filename_from_icon_name (tp_account_get_icon_name (account),
+                                                   GTK_ICON_SIZE_MENU);
+       if (filename != NULL) {
+               pixbuf = gdk_pixbuf_new_from_file_at_size (filename, width, height, NULL);
+               g_free (filename);
+       }
+
+       return pixbuf;
+}
+
 GdkPixbuf *
 empathy_pixbuf_scale_down_if_necessary (GdkPixbuf *pixbuf, gint max_size)
 {
@@ -1271,7 +1564,7 @@ empathy_window_iconify (GtkWindow *window, GtkStatusIcon *status_icon)
 
        gtk_status_icon_get_geometry (status_icon, NULL, &icon_location, NULL);
        gdk_window = gtk_widget_get_window (GTK_WIDGET (window));
-       dpy = gdk_x11_drawable_get_xdisplay (gdk_window);
+       dpy = GDK_WINDOW_XDISPLAY (gdk_window);
 
        data[0] = icon_location.x;
        data[1] = icon_location.y;
@@ -1280,7 +1573,8 @@ empathy_window_iconify (GtkWindow *window, GtkStatusIcon *status_icon)
 
        XChangeProperty (dpy,
                         GDK_WINDOW_XID (gdk_window),
-                        gdk_x11_get_xatom_by_name_for_display (gdk_drawable_get_display (gdk_window),
+                        gdk_x11_get_xatom_by_name_for_display (
+                               gdk_window_get_display (gdk_window),
                         "_NET_WM_ICON_GEOMETRY"),
                         XA_CARDINAL, 32, PropModeReplace,
                         (guchar *)&data, 4);
@@ -1291,10 +1585,9 @@ empathy_window_iconify (GtkWindow *window, GtkStatusIcon *status_icon)
 
 /* Takes care of moving the window to the current workspace. */
 void
-empathy_window_present (GtkWindow *window,
-                       gboolean   steal_focus)
+empathy_window_present_with_time (GtkWindow *window,
+                       guint32 timestamp)
 {
-       guint32 timestamp;
        GdkWindow *gdk_window;
 
        g_return_if_fail (GTK_IS_WINDOW (window));
@@ -1302,17 +1595,36 @@ empathy_window_present (GtkWindow *window,
        /* Move the window to the current workspace before trying to show it.
         * This is the behaviour people expect when clicking on the statusbar icon. */
        gdk_window = gtk_widget_get_window (GTK_WIDGET (window));
-       if (gdk_window)
+       if (gdk_window) {
+               gint x, y;
+               gint w, h;
+
+               /* Has no effect if the WM has viewports, like compiz */
                gdk_x11_window_move_to_current_desktop (gdk_window);
 
-       timestamp = gtk_get_current_event_time ();
-       gtk_window_present_with_time (window, timestamp);
+               /* If window is still off-screen, hide it to force it to
+                * reposition on the current workspace. */
+               gtk_window_get_position (window, &x, &y);
+               gtk_window_get_size (window, &w, &h);
+               if (!EMPATHY_RECT_IS_ON_SCREEN (x, y, w, h))
+                       gtk_widget_hide (GTK_WIDGET (window));
+       }
+
+       if (timestamp == GDK_CURRENT_TIME)
+               gtk_window_present (window);
+       else
+               gtk_window_present_with_time (window, timestamp);
+
        gtk_window_set_skip_taskbar_hint (window, FALSE);
-       /* FIXME: This shouldn't be required as gtk_window_present's doc says
-        *        it deiconify automatically. */
        gtk_window_deiconify (window);
 }
 
+void
+empathy_window_present (GtkWindow *window)
+{
+  empathy_window_present_with_time (window, gtk_get_current_event_time ());
+}
+
 GtkWindow *
 empathy_get_toplevel_window (GtkWidget *widget)
 {
@@ -1322,7 +1634,7 @@ empathy_get_toplevel_window (GtkWidget *widget)
 
        toplevel = gtk_widget_get_toplevel (widget);
        if (GTK_IS_WINDOW (toplevel) &&
-           GTK_WIDGET_TOPLEVEL (toplevel)) {
+           gtk_widget_is_toplevel (toplevel)) {
                return GTK_WINDOW (toplevel);
        }
 
@@ -1406,39 +1718,56 @@ empathy_url_show (GtkWidget *parent,
        g_free (real_url);
 }
 
-static void
-link_button_hook (GtkLinkButton *button,
-                 const gchar *link,
-                 gpointer user_data)
+void
+empathy_send_file (EmpathyContact *contact, GFile *file)
 {
-       empathy_url_show (GTK_WIDGET (button), link);
-}
+       EmpathyFTFactory *factory;
+       GtkRecentManager *manager;
+       gchar *uri;
 
-GtkWidget *
-empathy_link_button_new (const gchar *url,
-                       const gchar *title)
-{
-       static gboolean hook = FALSE;
+       g_return_if_fail (EMPATHY_IS_CONTACT (contact));
+       g_return_if_fail (G_IS_FILE (file));
 
-       if (!hook) {
-               hook = TRUE;
-               gtk_link_button_set_uri_hook (link_button_hook, NULL, NULL);
-       }
+       factory = empathy_ft_factory_dup_singleton ();
+
+       empathy_ft_factory_new_transfer_outgoing (factory, contact, file);
 
-       return gtk_link_button_new_with_label (url, title);
+       uri = g_file_get_uri (file);
+       manager = gtk_recent_manager_get_default ();
+       gtk_recent_manager_add_item (manager, uri);
+       g_free (uri);
+
+       g_object_unref (factory);
 }
 
 void
-empathy_toggle_button_set_state_quietly (GtkWidget *widget,
-                                       GCallback  callback,
-                                       gpointer   user_data,
-                                       gboolean   active)
+empathy_send_file_from_uri_list (EmpathyContact *contact, const gchar *uri_list)
 {
-       g_return_if_fail (GTK_IS_TOGGLE_BUTTON (widget));
+       const gchar *nl;
+       GFile *file;
+
+       /* Only handle a single file for now.  It would be wicked cool to be
+          able to do multiple files, offering to zip them or whatever like
+          nautilus-sendto does.  Note that text/uri-list is defined to have
+          each line terminated by \r\n, but we can be tolerant of applications
+          that only use \n or don't terminate single-line entries.
+       */
+       nl = strstr (uri_list, "\r\n");
+       if (!nl) {
+               nl = strchr (uri_list, '\n');
+       }
+       if (nl) {
+               gchar *uri = g_strndup (uri_list, nl - uri_list);
+               file = g_file_new_for_uri (uri);
+               g_free (uri);
+       }
+       else {
+               file = g_file_new_for_uri (uri_list);
+       }
 
-       g_signal_handlers_block_by_func (widget, callback, user_data);
-       g_object_set (widget, "active", active, NULL);
-       g_signal_handlers_unblock_by_func (widget, callback, user_data);
+       empathy_send_file (contact, file);
+
+       g_object_unref (file);
 }
 
 static void
@@ -1446,25 +1775,13 @@ file_manager_send_file_response_cb (GtkDialog      *widget,
                                    gint            response_id,
                                    EmpathyContact *contact)
 {
-       EmpathyFTFactory *factory;
        GFile *file;
-       gchar *uri;
-       GtkRecentManager *manager;
 
        if (response_id == GTK_RESPONSE_OK) {
                file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (widget));
-               uri = g_file_get_uri (file);
-
-               factory = empathy_ft_factory_dup_singleton ();
-
-               empathy_ft_factory_new_transfer_outgoing (factory, contact,
-                                                         file);
 
-               manager = gtk_recent_manager_get_default ();
-               gtk_recent_manager_add_item (manager, uri);
+               empathy_send_file (contact, file);
 
-               g_free (uri);
-               g_object_unref (factory);
                g_object_unref (file);
        }
 
@@ -1496,12 +1813,15 @@ empathy_send_file_with_file_chooser (EmpathyContact *contact)
        gtk_widget_show (button);
        gtk_dialog_add_action_widget (GTK_DIALOG (widget), button,
                                      GTK_RESPONSE_OK);
-       GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
+       gtk_widget_set_can_default (button, TRUE);
        gtk_dialog_set_default_response (GTK_DIALOG (widget),
                                         GTK_RESPONSE_OK);
 
        gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (widget), FALSE);
 
+       gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (widget),
+               g_get_home_dir ());
+
        g_signal_connect (widget, "response",
                          G_CALLBACK (file_manager_send_file_response_cb),
                          contact);
@@ -1540,8 +1860,17 @@ void
 empathy_receive_file_with_file_chooser (EmpathyFTHandler *handler)
 {
        GtkWidget *widget;
+       const gchar *dir;
+       EmpathyContact *contact;
+       gchar *title;
+
+       contact = empathy_ft_handler_get_contact (handler);
+       g_assert (contact != NULL);
 
-       widget = gtk_file_chooser_dialog_new (_("Select a destination"),
+       title = g_strdup_printf (_("Incoming file from %s"),
+               empathy_contact_get_alias (contact));
+
+       widget = gtk_file_chooser_dialog_new (title,
                                              NULL,
                                              GTK_FILE_CHOOSER_ACTION_SAVE,
                                              GTK_STOCK_CANCEL,
@@ -1554,8 +1883,16 @@ empathy_receive_file_with_file_chooser (EmpathyFTHandler *handler)
        gtk_file_chooser_set_do_overwrite_confirmation
                (GTK_FILE_CHOOSER (widget), TRUE);
 
+       dir = g_get_user_special_dir (G_USER_DIRECTORY_DOWNLOAD);
+       if (dir == NULL)
+               /* Fallback to $HOME if $XDG_DOWNLOAD_DIR is not set */
+               dir = g_get_home_dir ();
+
+       gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (widget), dir);
+
        g_signal_connect (widget, "response",
                G_CALLBACK (file_manager_receive_file_response_cb), handler);
 
        gtk_widget_show (widget);
+       g_free (title);
 }