]> git.0d.be Git - empathy.git/blobdiff - libempathy-gtk/empathy-ui-utils.c
individual_view_drag_end: remove the auto scroll
[empathy.git] / libempathy-gtk / empathy-ui-utils.c
index 8dbf6afd95b1a022c11dbb8ef371bf1a69474bfa..f3761e5cdf989db13cc4ace95b5760779c5151a5 100644 (file)
@@ -44,6 +44,7 @@
 
 #include "empathy-ui-utils.h"
 #include "empathy-images.h"
+#include "empathy-live-search.h"
 #include "empathy-smiley-manager.h"
 
 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
@@ -405,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);
@@ -491,7 +491,10 @@ empathy_pixbuf_from_avatar_scaled (EmpathyAvatar *avatar,
                          G_CALLBACK (pixbuf_from_avatar_size_prepared_cb),
                          &data);
 
-       if (!gdk_pixbuf_loader_write (loader, avatar->data, avatar->len, &error)) {
+       if (avatar->len == 0) {
+               g_warning ("Avatar has 0 length");
+               return NULL;
+       } else if (!gdk_pixbuf_loader_write (loader, avatar->data, avatar->len, &error)) {
                g_warning ("Couldn't write avatar image:%p with "
                           "length:%" G_GSIZE_FORMAT " to pixbuf loader: %s",
                           avatar->data, avatar->len, error->message);
@@ -526,13 +529,18 @@ typedef struct {
        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)
+                                          gint                height,
+                                          GCancellable       *cancellable)
 {
        PixbufAvatarFromIndividualClosure *closure;
 
@@ -544,6 +552,8 @@ pixbuf_avatar_from_individual_closure_new (FolksIndividual    *individual,
        closure->result = g_object_ref (result);
        closure->width = width;
        closure->height = height;
+       if (cancellable != NULL)
+               closure->cancellable = g_object_ref (cancellable);
 
        return closure;
 }
@@ -552,65 +562,140 @@ static void
 pixbuf_avatar_from_individual_closure_free (
                PixbufAvatarFromIndividualClosure *closure)
 {
+       g_clear_object (&closure->cancellable);
+       tp_clear_object (&closure->loader);
        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)
+avatar_icon_load_close_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;
-       }
+       g_input_stream_close_finish (G_INPUT_STREAM (object), result, &error);
 
-       size_data.width = closure->width;
-       size_data.height = closure->height;
-       size_data.preserve_aspect_ratio = TRUE;
+       if (error != NULL) {
+               DEBUG ("Failed to close pixbuf stream: %s", error->message);
+               g_error_free (error);
+       }
+}
 
-       loader = gdk_pixbuf_loader_new ();
+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;
 
-       g_signal_connect (loader, "size-prepared",
-                         G_CALLBACK (pixbuf_from_avatar_size_prepared_cb),
-                         &size_data);
+       /* 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;
+       }
 
-       if (!gdk_pixbuf_loader_write (loader, (guchar *) data, data_size,
-                               &error)) {
+       /* 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;
        }
-       if (!gdk_pixbuf_loader_close (loader, &error)) {
-               DEBUG ("Failed to close pixbuf loader: %s",
-                       error ? error->message : "No error given");
+
+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;
        }
 
-       g_simple_async_result_set_op_res_gpointer (closure->result,
-                       avatar_pixbuf_from_loader (loader), g_object_unref);
+       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);
-       g_free (data);
-       tp_clear_object (&loader);
+       tp_clear_object (&stream);
        pixbuf_avatar_from_individual_closure_free (closure);
 }
 
@@ -623,7 +708,7 @@ empathy_pixbuf_avatar_from_individual_scaled_async (
                GAsyncReadyCallback  callback,
                gpointer             user_data)
 {
-       GFile *avatar_file;
+       GLoadableIcon *avatar_icon;
        GSimpleAsyncResult *result;
        PixbufAvatarFromIndividualClosure *closure;
 
@@ -631,26 +716,26 @@ empathy_pixbuf_avatar_from_individual_scaled_async (
                        callback, user_data,
                        empathy_pixbuf_avatar_from_individual_scaled_async);
 
-       avatar_file =
+       avatar_icon =
                folks_avatar_details_get_avatar (FOLKS_AVATAR_DETAILS (individual));
-       if (avatar_file == NULL)
-               goto out;
+       if (avatar_icon == NULL) {
+               g_simple_async_result_set_error (result, TP_ERRORS,
+                       TP_ERROR_INVALID_ARGUMENT, "no avatar found");
+
+               g_simple_async_result_complete (result);
+               g_object_unref (result);
+               return;
+       }
 
        closure = pixbuf_avatar_from_individual_closure_new (individual, result,
-                                                            width, height);
-       if (closure == NULL)
-               goto out;
+                                                            width, height,
+                                                            cancellable);
 
-       g_file_load_contents_async (avatar_file, cancellable,
-                       avatar_file_load_contents_cb, closure);
+       g_return_if_fail (closure != NULL);
 
-       g_object_unref (result);
+       g_loadable_icon_load_async (avatar_icon, width, cancellable,
+                       avatar_icon_load_cb, closure);
 
-       return;
-
-out:
-       g_simple_async_result_set_op_res_gpointer (result, NULL, NULL);
-       g_simple_async_result_complete (result);
        g_object_unref (result);
 }
 
@@ -865,6 +950,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);
 
@@ -1533,56 +1621,6 @@ 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_WINDOW_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_window_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);
-}
-
 /* Takes care of moving the window to the current workspace. */
 void
 empathy_window_present_with_time (GtkWindow *window,
@@ -1614,9 +1652,6 @@ empathy_window_present_with_time (GtkWindow *window,
                gtk_window_present (window);
        else
                gtk_window_present_with_time (window, timestamp);
-
-       gtk_window_set_skip_taskbar_hint (window, FALSE);
-       gtk_window_deiconify (window);
 }
 
 void
@@ -1730,7 +1765,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 ();
@@ -1785,9 +1821,31 @@ file_manager_send_file_response_cb (GtkDialog      *widget,
                g_object_unref (file);
        }
 
+       g_object_unref (contact);
        gtk_widget_destroy (GTK_WIDGET (widget));
 }
 
+static gboolean
+filter_cb (const GtkFileFilterInfo *filter_info,
+               gpointer data)
+{
+       /* filter out socket files */
+       return tp_strdiff (filter_info->mime_type, "inode/socket");
+}
+
+static GtkFileFilter *
+create_file_filter (void)
+{
+       GtkFileFilter *filter;
+
+       filter = gtk_file_filter_new ();
+
+       gtk_file_filter_add_custom (filter, GTK_FILE_FILTER_MIME_TYPE, filter_cb,
+               NULL, NULL);
+
+       return filter;
+}
+
 void
 empathy_send_file_with_file_chooser (EmpathyContact *contact)
 {
@@ -1822,9 +1880,12 @@ empathy_send_file_with_file_chooser (EmpathyContact *contact)
        gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (widget),
                g_get_home_dir ());
 
+       gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (widget),
+               create_file_filter ());
+
        g_signal_connect (widget, "response",
                          G_CALLBACK (file_manager_send_file_response_cb),
-                         contact);
+                         g_object_ref (contact));
 
        gtk_widget_show (widget);
 }
@@ -1873,8 +1934,8 @@ file_manager_receive_file_response_cb (GtkDialog *dialog,
                                _("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);
+                       file_size_str = g_format_size (file_size);
+                       free_space_str = g_format_size (free_space);
 
                        gtk_message_dialog_format_secondary_text (
                                GTK_MESSAGE_DIALOG (message),
@@ -1995,3 +2056,241 @@ empathy_context_menu_new (GtkWidget *attach_to)
 
        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;
+}
+
+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);
+}
+
+/* Most of the workspace manipulation code has been copied from libwnck
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2005-2007 Vincent Untz
+ */
+static void
+_wnck_activate_workspace (Screen *screen,
+    int new_active_space,
+    Time timestamp)
+{
+  Display *display;
+  Window   root;
+  XEvent   xev;
+
+  display = DisplayOfScreen (screen);
+  root = RootWindowOfScreen (screen);
+
+  xev.xclient.type = ClientMessage;
+  xev.xclient.serial = 0;
+  xev.xclient.send_event = True;
+  xev.xclient.display = display;
+  xev.xclient.window = root;
+  xev.xclient.message_type = gdk_x11_get_xatom_by_name ("_NET_CURRENT_DESKTOP");
+  xev.xclient.format = 32;
+  xev.xclient.data.l[0] = new_active_space;
+  xev.xclient.data.l[1] = timestamp;
+  xev.xclient.data.l[2] = 0;
+  xev.xclient.data.l[3] = 0;
+  xev.xclient.data.l[4] = 0;
+
+  gdk_error_trap_push ();
+  XSendEvent (display, root, False,
+      SubstructureRedirectMask | SubstructureNotifyMask,
+      &xev);
+  XSync (display, False);
+  gdk_error_trap_pop_ignored ();
+}
+
+static gboolean
+_wnck_get_cardinal (Screen *screen,
+    Window xwindow,
+    Atom atom,
+    int *val)
+{
+  Display *display;
+  Atom type;
+  int format;
+  gulong nitems;
+  gulong bytes_after;
+  gulong *num;
+  int err, result;
+
+  display = DisplayOfScreen (screen);
+
+  *val = 0;
+
+  gdk_error_trap_push ();
+  type = None;
+  result = XGetWindowProperty (display, xwindow, atom,
+      0, G_MAXLONG, False, XA_CARDINAL, &type, &format, &nitems,
+      &bytes_after, (void *) &num);
+  err = gdk_error_trap_pop ();
+  if (err != Success ||
+      result != Success)
+    return FALSE;
+
+  if (type != XA_CARDINAL)
+    {
+      XFree (num);
+      return FALSE;
+    }
+
+  *val = *num;
+
+  XFree (num);
+
+  return TRUE;
+}
+
+static int
+window_get_workspace (Screen *xscreen,
+    Window win)
+{
+  int number;
+
+  if (!_wnck_get_cardinal (xscreen, win,
+        gdk_x11_get_xatom_by_name ("_NET_WM_DESKTOP"), &number))
+    return -1;
+
+  return number;
+}
+
+/* Ask X to move to the desktop on which @window currently is
+ * and the present @window. */
+void
+empathy_move_to_window_desktop (GtkWindow *window,
+    guint32 timestamp)
+{
+  GdkScreen *screen;
+  Screen *xscreen;
+  GdkWindow *gdk_window;
+  int workspace;
+
+  screen = gtk_window_get_screen (window);
+  xscreen = gdk_x11_screen_get_xscreen (screen);
+  gdk_window = gtk_widget_get_window (GTK_WIDGET (window));
+
+  workspace = window_get_workspace (xscreen,
+      gdk_x11_window_get_xid (gdk_window));
+  if (workspace == -1)
+    goto out;
+
+  _wnck_activate_workspace (xscreen, workspace, timestamp);
+
+out:
+  gtk_window_present_with_time (window, timestamp);
+}