X-Git-Url: https://git.0d.be/?p=empathy.git;a=blobdiff_plain;f=libempathy-gtk%2Fempathy-ui-utils.c;h=32ad451a12115918492780ae78e8ec7854580036;hp=24f22166b320cdd0a2905eda17732d7b4f4e6a83;hb=7b6b8da406493311445f6c2470a005a542972693;hpb=e403541be39943e428b65f8615352eddea7cece8 diff --git a/libempathy-gtk/empathy-ui-utils.c b/libempathy-gtk/empathy-ui-utils.c index 24f22166..32ad451a 100644 --- a/libempathy-gtk/empathy-ui-utils.c +++ b/libempathy-gtk/empathy-ui-utils.c @@ -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 * Xavier Claessens * Jonny Lamb + * Travis Reitter * * Part of this file is copied from GtkSourceView (gtksourceiter.c): * Paolo Maggi @@ -38,16 +39,17 @@ #include #include +#include +#include + #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 #include -#include -#include #include 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)) @@ -196,6 +205,7 @@ empathy_icon_name_for_presence (TpConnectionPresenceType presence) case TP_CONNECTION_PRESENCE_TYPE_UNKNOWN: return EMPATHY_IMAGE_PENDING; case TP_CONNECTION_PRESENCE_TYPE_UNSET: + default: return NULL; } @@ -214,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) { @@ -382,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); @@ -415,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, @@ -448,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); @@ -489,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) @@ -518,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; @@ -533,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; @@ -672,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); @@ -1340,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)); @@ -1466,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 * @@ -1569,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) { @@ -1603,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 (); @@ -1658,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)); } @@ -1697,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); } @@ -1711,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; + } - empathy_ft_factory_set_destination_for_incoming_handler - (factory, handler, file); + 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); + + 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); @@ -1734,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); - 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, @@ -1759,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 ("%s", + 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 ( + "%s", + 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); +}