]> git.0d.be Git - empathy.git/blobdiff - libempathy-gtk/empathy-ui-utils.c
Merge remote-tracking branch 'pochu/misc-fixes'
[empathy.git] / libempathy-gtk / empathy-ui-utils.c
index 422eb910f3ae69fe0d4b35b1994a96677889b9d1..32ad451a12115918492780ae78e8ec7854580036 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-live-search.h"
 #include "empathy-smiley-manager.h"
-#include "empathy-conf.h"
 
 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
 #include <libempathy/empathy-debug.h>
 #include <libempathy/empathy-utils.h>
-#include <libempathy/empathy-dispatcher.h>
-#include <libempathy/empathy-idle.h>
 #include <libempathy/empathy-ft-factory.h>
 
 void
@@ -78,6 +80,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);
@@ -125,9 +128,9 @@ 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;
@@ -181,7 +184,13 @@ 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:
                if (gtk_icon_theme_has_icon (gtk_icon_theme_get_default (),
                                             EMPATHY_IMAGE_HIDDEN))
@@ -192,9 +201,11 @@ empathy_icon_name_for_presence (TpConnectionPresenceType presence)
                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;
        }
 
@@ -213,6 +224,20 @@ 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_details_get_presence_type (
+               FOLKS_PRESENCE_DETAILS (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)
 {
@@ -381,11 +406,10 @@ empathy_avatar_pixbuf_roundify (GdkPixbuf *pixbuf)
 static gboolean
 empathy_gdk_pixbuf_is_opaque (GdkPixbuf *pixbuf)
 {
-       gint width, height, rowstride, i;
+       gint height, rowstride, i;
        guchar *pixels;
        guchar *row;
 
-       width = gdk_pixbuf_get_width (pixbuf);
        height = gdk_pixbuf_get_height (pixbuf);
        rowstride = gdk_pixbuf_get_rowstride (pixbuf);
        pixels = gdk_pixbuf_get_pixels (pixbuf);
@@ -414,6 +438,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,
@@ -447,27 +500,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);
 
@@ -488,6 +521,247 @@ 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;
+       struct SizeData size_data;
+       GdkPixbufLoader *loader;
+       GCancellable *cancellable;
+       guint8 data[512];
+} PixbufAvatarFromIndividualClosure;
+
+static PixbufAvatarFromIndividualClosure *
+pixbuf_avatar_from_individual_closure_new (FolksIndividual    *individual,
+                                          GSimpleAsyncResult *result,
+                                          gint                width,
+                                          gint                height,
+                                          GCancellable       *cancellable)
+{
+       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;
+       closure->cancellable = g_object_ref (cancellable);
+
+       return closure;
+}
+
+static void
+pixbuf_avatar_from_individual_closure_free (
+               PixbufAvatarFromIndividualClosure *closure)
+{
+       g_object_unref (closure->cancellable);
+       tp_clear_object (&closure->loader);
+       g_object_unref (closure->individual);
+       g_object_unref (closure->result);
+       g_free (closure);
+}
+
+static void
+avatar_icon_load_close_cb (GObject      *object,
+                           GAsyncResult *result,
+                           gpointer      user_data)
+{
+       GError *error = NULL;
+
+       g_input_stream_close_finish (G_INPUT_STREAM (object), result, &error);
+
+       if (error != NULL) {
+               DEBUG ("Failed to close pixbuf stream: %s", error->message);
+               g_error_free (error);
+       }
+}
+
+static void
+avatar_icon_load_read_cb (GObject      *object,
+                          GAsyncResult *result,
+                          gpointer      user_data)
+{
+       GInputStream *stream = G_INPUT_STREAM (object);
+       PixbufAvatarFromIndividualClosure *closure = user_data;
+       gssize n_read;
+       GError *error = NULL;
+
+       /* Finish reading this chunk from the stream */
+       n_read = g_input_stream_read_finish (stream, result, &error);
+       if (error != NULL) {
+               DEBUG ("Failed to finish read from pixbuf stream: %s",
+                       error->message);
+               g_simple_async_result_set_from_error (closure->result, error);
+               goto out_close;
+       }
+
+       /* Write the chunk to the pixbuf loader */
+       if (!gdk_pixbuf_loader_write (closure->loader, (guchar *) closure->data,
+                       n_read, &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_close;
+       }
+
+       if (n_read == 0) {
+               /* EOF? */
+               if (!gdk_pixbuf_loader_close (closure->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;
+               }
+
+               /* We're done. */
+               g_simple_async_result_set_op_res_gpointer (closure->result,
+                       avatar_pixbuf_from_loader (closure->loader),
+                       g_object_unref);
+
+               goto out;
+       } else {
+               /* Loop round and read another chunk. */
+               g_input_stream_read_async (stream, closure->data,
+                       G_N_ELEMENTS (closure->data),
+                       G_PRIORITY_DEFAULT, closure->cancellable,
+                       avatar_icon_load_read_cb, closure);
+
+               return;
+       }
+
+out_close:
+       /* We must close the pixbuf loader before unreffing it. */
+       gdk_pixbuf_loader_close (closure->loader, NULL);
+
+out:
+       /* Close the file for safety (even though it should be
+        * automatically closed when the stream is finalised). */
+       g_input_stream_close_async (stream, G_PRIORITY_DEFAULT, NULL,
+               (GAsyncReadyCallback) avatar_icon_load_close_cb, NULL);
+
+       g_simple_async_result_complete (closure->result);
+
+       g_clear_error (&error);
+       pixbuf_avatar_from_individual_closure_free (closure);
+}
+
+static void
+avatar_icon_load_cb (GObject      *object,
+                     GAsyncResult *result,
+                     gpointer      user_data)
+{
+       GLoadableIcon *icon = G_LOADABLE_ICON (object);
+       PixbufAvatarFromIndividualClosure *closure = user_data;
+       GInputStream *stream;
+       GError *error = NULL;
+
+       stream = g_loadable_icon_load_finish (icon, result, NULL, &error);
+       if (error != NULL) {
+               DEBUG ("Failed to open avatar stream: %s", error->message);
+               g_simple_async_result_set_from_error (closure->result, error);
+               goto out;
+       }
+
+       closure->size_data.width = closure->width;
+       closure->size_data.height = closure->height;
+       closure->size_data.preserve_aspect_ratio = TRUE;
+
+       /* Load the data into a pixbuf loader in chunks. */
+       closure->loader = gdk_pixbuf_loader_new ();
+
+       g_signal_connect (closure->loader, "size-prepared",
+                         G_CALLBACK (pixbuf_from_avatar_size_prepared_cb),
+                         &(closure->size_data));
+
+       /* Begin to read the first chunk. */
+       g_input_stream_read_async (stream, closure->data,
+                       G_N_ELEMENTS (closure->data),
+                       G_PRIORITY_DEFAULT, closure->cancellable,
+                       avatar_icon_load_read_cb, closure);
+
+       g_object_unref (stream);
+
+       return;
+
+out:
+       g_simple_async_result_complete (closure->result);
+
+       g_clear_error (&error);
+       tp_clear_object (&stream);
+       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)
+{
+       GLoadableIcon *avatar_icon;
+       GSimpleAsyncResult *result;
+       PixbufAvatarFromIndividualClosure *closure;
+
+       result = g_simple_async_result_new (G_OBJECT (individual),
+                       callback, user_data,
+                       empathy_pixbuf_avatar_from_individual_scaled_async);
+
+       avatar_icon =
+               folks_avatar_details_get_avatar (FOLKS_AVATAR_DETAILS (individual));
+       if (avatar_icon == NULL)
+               goto out;
+
+       closure = pixbuf_avatar_from_individual_closure_new (individual, result,
+                                                            width, height,
+                                                            cancellable);
+       if (closure == NULL)
+               goto out;
+
+       g_loadable_icon_load_async (avatar_icon, width, cancellable,
+                       avatar_icon_load_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)
@@ -517,7 +791,8 @@ empathy_pixbuf_contact_status_icon_with_icon_name (EmpathyContact *contact,
        gint       height, width;
        gint       numerator, denominator;
 
-       g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
+       g_return_val_if_fail (EMPATHY_IS_CONTACT (contact) ||
+                       (show_protocol == FALSE), NULL);
        g_return_val_if_fail (icon_name != NULL, NULL);
 
        numerator = 3;
@@ -532,13 +807,14 @@ empathy_pixbuf_contact_status_icon_with_icon_name (EmpathyContact *contact,
 
        pix_status = gdk_pixbuf_new_from_file (icon_filename, NULL);
 
-       g_free (icon_filename);
-
        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;
 
@@ -671,6 +947,9 @@ empathy_filename_from_icon_name (const gchar *icon_name,
        }
 
        icon_info = gtk_icon_theme_lookup_icon (icon_theme, icon_name, size, 0);
+       if (icon_info == NULL)
+               return NULL;
+
        ret = g_strdup (gtk_icon_info_get_filename (icon_info));
        gtk_icon_info_free (icon_info);
 
@@ -1339,111 +1618,12 @@ empathy_text_iter_backward_search (const GtkTextIter   *iter,
        return retval;
 }
 
-gboolean
-empathy_window_get_is_visible (GtkWindow *window)
-{
-       GdkWindowState  state;
-       GdkWindow      *gdk_window;
-
-       g_return_val_if_fail (GTK_IS_WINDOW (window), FALSE);
-
-       gdk_window = gtk_widget_get_window (GTK_WIDGET (window));
-       if (!gdk_window) {
-               return FALSE;
-       }
-
-       state = gdk_window_get_state (gdk_window);
-       if (state & (GDK_WINDOW_STATE_WITHDRAWN | GDK_WINDOW_STATE_ICONIFIED)) {
-               return FALSE;
-       }
-
-       return TRUE;
-}
-
-void
-empathy_window_iconify (GtkWindow *window, GtkStatusIcon *status_icon)
-{
-       GdkRectangle  icon_location;
-       gulong        data[4];
-       Display      *dpy;
-       GdkWindow    *gdk_window;
-
-       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);
-
-       data[0] = icon_location.x;
-       data[1] = icon_location.y;
-       data[2] = icon_location.width;
-       data[3] = icon_location.height;
-
-       XChangeProperty (dpy,
-                        GDK_WINDOW_XID (gdk_window),
-                        gdk_x11_get_xatom_by_name_for_display (
-                               gdk_drawable_get_display (gdk_window),
-                        "_NET_WM_ICON_GEOMETRY"),
-                        XA_CARDINAL, 32, PropModeReplace,
-                        (guchar *)&data, 4);
-
-       gtk_window_set_skip_taskbar_hint (window, TRUE);
-       gtk_window_iconify (window);
-}
-
-/* Code from Thomas Thurman
- * http://people.collabora.co.uk/~tthurman/pingwindow.c.txt
- */
-static guint32
-get_server_time (void)
-{
-  Display *display;
-  Window pingingWindow;
-  XEvent propertyEvent;
-  XSetWindowAttributes attrs;
-
-  display = XOpenDisplay (NULL);
-
-  attrs.override_redirect = True;
-  attrs.event_mask = PropertyChangeMask;
-
-  pingingWindow = XCreateWindow (display,
-      XRootWindow (display, 0), /* parent */
-      -100, -100, 1, 1, /* off-screen */
-      0,
-      CopyFromParent,
-      CopyFromParent,
-      (Visual *) CopyFromParent,
-      CWOverrideRedirect | CWEventMask,
-      &attrs);
-
-  /* Change a property. XA_PRIMARY is never really
-   * used for properties, so it's safe.
-   */
-  XChangeProperty (display,
-      pingingWindow,
-      XA_PRIMARY, XA_STRING, 8,
-      PropModeAppend, NULL, 0);
-
-  /* Pick up the event. */
-  XWindowEvent (display,
-      pingingWindow,
-      PropertyChangeMask,
-      &propertyEvent);
-
-  /* If you want to do this often,
-   * just keep the window around and
-   * don't destroy it.
-   */
-  XDestroyWindow (display, pingingWindow);
-
-  return ((XPropertyEvent *) &propertyEvent)->time;
-}
-
 /* Takes care of moving the window to the current workspace. */
 void
-empathy_window_present (GtkWindow *window)
+empathy_window_present_with_time (GtkWindow *window,
+                       guint32 timestamp)
 {
        GdkWindow *gdk_window;
-       guint32 timestamp;
 
        g_return_if_fail (GTK_IS_WINDOW (window));
 
@@ -1465,14 +1645,16 @@ empathy_window_present (GtkWindow *window)
                        gtk_widget_hide (GTK_WIDGET (window));
        }
 
-       timestamp = gtk_get_current_event_time ();
-       if (timestamp == 0)
-               /* No event, fallback to X server time */
-               timestamp = get_server_time ();
+       if (timestamp == GDK_CURRENT_TIME)
+               gtk_window_present (window);
+       else
+               gtk_window_present_with_time (window, timestamp);
+}
 
-       gtk_window_present_with_time (window, timestamp);
-       gtk_window_set_skip_taskbar_hint (window, FALSE);
-       gtk_window_deiconify (window);
+void
+empathy_window_present (GtkWindow *window)
+{
+  empathy_window_present_with_time (window, gtk_get_current_event_time ());
 }
 
 GtkWindow *
@@ -1568,28 +1750,6 @@ empathy_url_show (GtkWidget *parent,
        g_free (real_url);
 }
 
-static void
-link_button_hook (GtkLinkButton *button,
-                 const gchar *link,
-                 gpointer user_data)
-{
-       empathy_url_show (GTK_WIDGET (button), link);
-}
-
-GtkWidget *
-empathy_link_button_new (const gchar *url,
-                       const gchar *title)
-{
-       static gboolean hook = FALSE;
-
-       if (!hook) {
-               hook = TRUE;
-               gtk_link_button_set_uri_hook (link_button_hook, NULL, NULL);
-       }
-
-       return gtk_link_button_new_with_label (url, title);
-}
-
 void
 empathy_send_file (EmpathyContact *contact, GFile *file)
 {
@@ -1602,7 +1762,8 @@ empathy_send_file (EmpathyContact *contact, GFile *file)
 
        factory = empathy_ft_factory_dup_singleton ();
 
-       empathy_ft_factory_new_transfer_outgoing (factory, contact, file);
+       empathy_ft_factory_new_transfer_outgoing (factory, contact, file,
+               empathy_get_current_action_time ());
 
        uri = g_file_get_uri (file);
        manager = gtk_recent_manager_get_default ();
@@ -1657,6 +1818,7 @@ file_manager_send_file_response_cb (GtkDialog      *widget,
                g_object_unref (file);
        }
 
+       g_object_unref (contact);
        gtk_widget_destroy (GTK_WIDGET (widget));
 }
 
@@ -1696,7 +1858,7 @@ empathy_send_file_with_file_chooser (EmpathyContact *contact)
 
        g_signal_connect (widget, "response",
                          G_CALLBACK (file_manager_send_file_response_cb),
-                         contact);
+                         g_object_ref (contact));
 
        gtk_widget_show (widget);
 }
@@ -1710,11 +1872,66 @@ file_manager_receive_file_response_cb (GtkDialog *dialog,
        GFile *file;
 
        if (response == GTK_RESPONSE_OK) {
-               factory = empathy_ft_factory_dup_singleton ();
+               GFile *parent;
+               GFileInfo *info;
+               guint64 free_space, file_size;
+               GError *error = NULL;
+
                file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
+               parent = g_file_get_parent (file);
+               info = g_file_query_filesystem_info (parent,
+                               G_FILE_ATTRIBUTE_FILESYSTEM_FREE,
+                               NULL, &error);
+
+               g_object_unref (parent);
+
+               if (error != NULL) {
+                       g_warning ("Error: %s", error->message);
+
+                       g_object_unref (file);
+                       return;
+               }
+
+               free_space = g_file_info_get_attribute_uint64 (info,
+                               G_FILE_ATTRIBUTE_FILESYSTEM_FREE);
+               file_size = empathy_ft_handler_get_total_bytes (handler);
+
+               g_object_unref (info);
+
+               if (file_size > free_space) {
+                       GtkWidget *message = gtk_message_dialog_new (
+                               GTK_WINDOW (dialog),
+                               GTK_DIALOG_MODAL,
+                               GTK_MESSAGE_ERROR,
+                               GTK_BUTTONS_CLOSE,
+                               _("Insufficient free space to save file"));
+                       char *file_size_str, *free_space_str;
+
+                       file_size_str = g_format_size_for_display (file_size);
+                       free_space_str = g_format_size_for_display (free_space);
+
+                       gtk_message_dialog_format_secondary_text (
+                               GTK_MESSAGE_DIALOG (message),
+                               _("%s of free space are required to save this "
+                                 "file, but only %s is available. Please "
+                                 "choose another location."),
+                               file_size_str, free_space_str);
+
+                       gtk_dialog_run (GTK_DIALOG (message));
+
+                       g_free (file_size_str);
+                       g_free (free_space_str);
+                       gtk_widget_destroy (message);
 
-               empathy_ft_factory_set_destination_for_incoming_handler
-                       (factory, handler, file);
+                       g_object_unref (file);
+
+                       return;
+               }
+
+               factory = empathy_ft_factory_dup_singleton ();
+
+               empathy_ft_factory_set_destination_for_incoming_handler (
+                               factory, handler, file);
 
                g_object_unref (factory);
                g_object_unref (file);
@@ -1733,8 +1950,16 @@ 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);
+
+       title = g_strdup_printf (_("Incoming file from %s"),
+               empathy_contact_get_alias (contact));
 
-       widget = gtk_file_chooser_dialog_new (_("Select a destination"),
+       widget = gtk_file_chooser_dialog_new (title,
                                              NULL,
                                              GTK_FILE_CHOOSER_ACTION_SAVE,
                                              GTK_STOCK_CANCEL,
@@ -1758,5 +1983,272 @@ empathy_receive_file_with_file_chooser (EmpathyFTHandler *handler)
                G_CALLBACK (file_manager_receive_file_response_cb), handler);
 
        gtk_widget_show (widget);
+       g_free (title);
+}
+
+void
+empathy_make_color_whiter (GdkRGBA *color)
+{
+       const GdkRGBA white = { 1.0, 1.0, 1.0, 1.0 };
+
+       color->red = (color->red + white.red) / 2;
+       color->green = (color->green + white.green) / 2;
+       color->blue = (color->blue + white.blue) / 2;
+}
+
+static void
+menu_deactivate_cb (GtkMenu *menu,
+       gpointer user_data)
+{
+       /* FIXME: we shouldn't have to disconnect the signal (bgo #641327) */
+       g_signal_handlers_disconnect_by_func (menu,
+                    menu_deactivate_cb, user_data);
+
+       gtk_menu_detach (menu);
 }
 
+/* Convenient function to create a GtkMenu attached to @attach_to and detach
+ * it when the menu is not displayed any more. This is useful when creating a
+ * context menu that we want to get rid as soon as it as been displayed. */
+GtkWidget *
+empathy_context_menu_new (GtkWidget *attach_to)
+{
+       GtkWidget *menu;
+
+       menu = gtk_menu_new ();
+
+       gtk_menu_attach_to_widget (GTK_MENU (menu), attach_to, NULL);
+
+       /* menu is initially unowned but gtk_menu_attach_to_widget () taked its
+        * floating ref. We can either wait that @attach_to releases its ref when
+        * it will be destroyed (when leaving Empathy most of the time) or explicitely
+        * detach the menu when it's not displayed any more.
+        * We go for the latter as we don't want to keep useless menus in memory
+        * during the whole lifetime of Empathy. */
+       g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb), NULL);
+
+       return menu;
+}
+
+gint64
+empathy_get_current_action_time (void)
+{
+  return (tp_user_action_time_from_x11 (gtk_get_current_event_time ()));
+}
+
+/* @words = empathy_live_search_strip_utf8_string (@text);
+ *
+ * User has to pass both so we don't have to compute @words ourself each time
+ * this function is called. */
+gboolean
+empathy_individual_match_string (FolksIndividual *individual,
+    const char *text,
+    GPtrArray *words)
+{
+  const gchar *str;
+  GeeSet *personas;
+  GeeIterator *iter;
+  gboolean retval = FALSE;
+
+  /* check alias name */
+  str = folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual));
+
+  if (empathy_live_search_match_words (str, words))
+    return TRUE;
+
+  personas = folks_individual_get_personas (individual);
+
+  /* check contact id, remove the @server.com part */
+  iter = gee_iterable_iterator (GEE_ITERABLE (personas));
+  while (retval == FALSE && gee_iterator_next (iter))
+    {
+      FolksPersona *persona = gee_iterator_get (iter);
+      const gchar *p;
+
+      if (empathy_folks_persona_is_interesting (persona))
+        {
+          str = folks_persona_get_display_id (persona);
+
+          /* Accept the persona if @text is a full prefix of his ID; that allows
+           * user to find, say, a jabber contact by typing his JID. */
+          if (g_str_has_prefix (str, text))
+            {
+              retval = TRUE;
+            }
+          else
+            {
+              gchar *dup_str = NULL;
+              gboolean visible;
+
+              p = strstr (str, "@");
+              if (p != NULL)
+                str = dup_str = g_strndup (str, p - str);
+
+              visible = empathy_live_search_match_words (str, words);
+              g_free (dup_str);
+              if (visible)
+                retval = TRUE;
+            }
+        }
+      g_clear_object (&persona);
+    }
+  g_clear_object (&iter);
+
+  /* FIXME: Add more rules here, we could check phone numbers in
+   * contact's vCard for example. */
+  return retval;
+}
+
+static gboolean
+dtmf_dialpad_button_pressed_cb (GObject *button,
+    GtkEntry *entry)
+{
+  GtkEntryBuffer *buffer = gtk_entry_get_buffer (entry);
+  const gchar *label;
+
+  label = g_object_get_data (button, "label");
+  gtk_entry_buffer_insert_text (buffer, -1, label, -1);
+
+  return FALSE;
+}
+
+GtkWidget *
+empathy_create_dtmf_dialpad (GObject *self,
+    GCallback dtmf_button_pressed_cb,
+    GCallback dtmf_button_released_cb)
+{
+  GtkWidget *box, *entry, *table;
+  int i;
+  GQuark button_quark;
+  struct {
+    const gchar *label;
+    const gchar *sublabel;
+    TpDTMFEvent event;
+  } dtmfbuttons[] = { { "1", "",     TP_DTMF_EVENT_DIGIT_1 },
+                      { "2", "abc",  TP_DTMF_EVENT_DIGIT_2 },
+                      { "3", "def",  TP_DTMF_EVENT_DIGIT_3 },
+                      { "4", "ghi",  TP_DTMF_EVENT_DIGIT_4 },
+                      { "5", "jkl",  TP_DTMF_EVENT_DIGIT_5 },
+                      { "6", "mno",  TP_DTMF_EVENT_DIGIT_6 },
+                      { "7", "pqrs", TP_DTMF_EVENT_DIGIT_7 },
+                      { "8", "tuv",  TP_DTMF_EVENT_DIGIT_8 },
+                      { "9", "wxyz", TP_DTMF_EVENT_DIGIT_9 },
+                      { "#", "",     TP_DTMF_EVENT_HASH },
+                      { "0", "",     TP_DTMF_EVENT_DIGIT_0 },
+                      { "*", "",     TP_DTMF_EVENT_ASTERISK },
+                      { NULL, } };
+
+  box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 3);
+
+  entry = gtk_entry_new ();
+  gtk_editable_set_editable (GTK_EDITABLE (entry), FALSE);
+
+  gtk_box_pack_start (GTK_BOX (box), entry, FALSE, FALSE, 3);
+
+  button_quark = g_quark_from_static_string (EMPATHY_DTMF_BUTTON_ID);
+
+  table = gtk_table_new (4, 3, TRUE);
+
+  for (i = 0; dtmfbuttons[i].label != NULL; i++)
+    {
+      GtkWidget *vbox = gtk_vbox_new (FALSE, 0);
+      GtkWidget *button = gtk_button_new ();
+      GtkWidget *label;
+      gchar *str;
+
+      gtk_container_add (GTK_CONTAINER (button), vbox);
+
+      /* main label */
+      label = gtk_label_new ("");
+      str = g_strdup_printf ("<span size='x-large'>%s</span>",
+          dtmfbuttons[i].label);
+      gtk_label_set_markup (GTK_LABEL (label), str);
+      g_free (str);
+
+      g_object_set_data (G_OBJECT (button), "label",
+          (gpointer) dtmfbuttons[i].label);
+
+      gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 3);
+
+      /* sub label */
+      label = gtk_label_new ("");
+      str = g_strdup_printf (
+          "<span foreground='#555555'>%s</span>",
+          dtmfbuttons[i].sublabel);
+      gtk_label_set_markup (GTK_LABEL (label), str);
+      g_free (str);
+
+      gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, TRUE, 0);
+
+      gtk_table_attach (GTK_TABLE (table), button, i % 3, i % 3 + 1,
+        i/3, i/3 + 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 1, 1);
+
+      g_object_set_qdata (G_OBJECT (button), button_quark,
+        GUINT_TO_POINTER (dtmfbuttons[i].event));
+
+      /* To update the GtkEntry */
+      g_signal_connect (G_OBJECT (button), "pressed",
+        G_CALLBACK (dtmf_dialpad_button_pressed_cb), entry);
+
+      g_signal_connect (G_OBJECT (button), "pressed",
+        dtmf_button_pressed_cb, self);
+      g_signal_connect (G_OBJECT (button), "released",
+        dtmf_button_released_cb, self);
+    }
+
+  gtk_box_pack_start (GTK_BOX (box), table, FALSE, FALSE, 3);
+
+  return box;
+}
+
+void
+empathy_launch_program (const gchar *dir,
+    const gchar *name,
+    const gchar *args)
+{
+  GdkDisplay *display;
+  GError *error = NULL;
+  gchar *path, *cmd;
+  GAppInfo *app_info;
+  GdkAppLaunchContext *context = NULL;
+
+  /* Try to run from source directory if possible */
+  path = g_build_filename (g_getenv ("EMPATHY_SRCDIR"), "src",
+      name, NULL);
+
+  if (!g_file_test (path, G_FILE_TEST_EXISTS))
+    {
+      g_free (path);
+      path = g_build_filename (dir, name, NULL);
+    }
+
+  if (args != NULL)
+    cmd = g_strconcat (path, " ", args, NULL);
+  else
+    cmd = g_strdup (path);
+
+  app_info = g_app_info_create_from_commandline (cmd, NULL, 0, &error);
+  if (app_info == NULL)
+    {
+      DEBUG ("Failed to create app info: %s", error->message);
+      g_error_free (error);
+      goto out;
+    }
+
+  display = gdk_display_get_default ();
+  context = gdk_display_get_app_launch_context (display);
+
+  if (!g_app_info_launch (app_info, NULL, (GAppLaunchContext *) context,
+      &error))
+    {
+      g_warning ("Failed to launch %s: %s", name, error->message);
+      g_error_free (error);
+      goto out;
+    }
+
+out:
+  tp_clear_object (&app_info);
+  tp_clear_object (&context);
+  g_free (path);
+  g_free (cmd);
+}