X-Git-Url: https://git.0d.be/?p=empathy.git;a=blobdiff_plain;f=libempathy-gtk%2Fempathy-ui-utils.c;h=32ad451a12115918492780ae78e8ec7854580036;hp=043c03eb30d88b9e6e05c8ba523629ac84ac17c4;hb=7b6b8da406493311445f6c2470a005a542972693;hpb=2ec9b6e89141999f919f8a3bd4a44fd8abad86ff diff --git a/libempathy-gtk/empathy-ui-utils.c b/libempathy-gtk/empathy-ui-utils.c index 043c03eb..32ad451a 100644 --- a/libempathy-gtk/empathy-ui-utils.c +++ b/libempathy-gtk/empathy-ui-utils.c @@ -44,13 +44,12 @@ #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 #include #include -#include -#include #include void @@ -232,8 +231,8 @@ empathy_icon_name_for_individual (FolksIndividual *individual) TpConnectionPresenceType presence; folks_presence = - folks_has_presence_get_presence_type ( - FOLKS_HAS_PRESENCE (individual)); + 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); @@ -407,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); @@ -528,13 +526,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; @@ -546,6 +549,7 @@ pixbuf_avatar_from_individual_closure_new (FolksIndividual *individual, closure->result = g_object_ref (result); closure->width = width; closure->height = height; + closure->cancellable = g_object_ref (cancellable); return closure; } @@ -554,65 +558,140 @@ 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_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); } @@ -625,7 +704,7 @@ empathy_pixbuf_avatar_from_individual_scaled_async ( GAsyncReadyCallback callback, gpointer user_data) { - GFile *avatar_file; + GLoadableIcon *avatar_icon; GSimpleAsyncResult *result; PixbufAvatarFromIndividualClosure *closure; @@ -633,18 +712,19 @@ empathy_pixbuf_avatar_from_individual_scaled_async ( callback, user_data, empathy_pixbuf_avatar_from_individual_scaled_async); - avatar_file = - folks_has_avatar_get_avatar (FOLKS_HAS_AVATAR (individual)); - if (avatar_file == NULL) + 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); + width, height, + cancellable); if (closure == NULL) goto out; - g_file_load_contents_async (avatar_file, cancellable, - avatar_file_load_contents_cb, closure); + g_loadable_icon_load_async (avatar_icon, width, cancellable, + avatar_icon_load_cb, closure); g_object_unref (result); @@ -867,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); @@ -1535,56 +1618,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, @@ -1616,9 +1649,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 @@ -1732,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 (); @@ -1787,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)); } @@ -1826,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); } @@ -1840,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); - empathy_ft_factory_set_destination_for_incoming_handler - (factory, handler, file); + 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); @@ -1898,3 +1985,270 @@ empathy_receive_file_with_file_chooser (EmpathyFTHandler *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); +}