Sat, 13 Apr 2024 20:37:17 -0500
Remove pidgin_dialogs_info and gtkdialogs.[ch] as it is now empty.
We have a new API for this and the old dialog just wasn't to fit in there.
Testing Done:
Ran with the turtles and ran the app.
Reviewed at https://reviews.imfreedom.org/r/3109/
/* * Pidgin - Internet Messenger * Copyright (C) Pidgin Developers <devel@pidgin.im> * * Pidgin is the legal property of its developers, whose names are too numerous * to list here. Please refer to the COPYRIGHT file distributed with this * source distribution. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see <https://www.gnu.org/licenses/>. */ #include <glib/gi18n-lib.h> #include "pidgin/pidginavatar.h" struct _PidginAvatar { GtkBox parent; GtkWidget *icon; GMenuModel *menu; GdkPixbufAnimation *animation; gboolean animate; PurpleBuddy *buddy; PurpleConversation *conversation; }; enum { PROP_0, PROP_ANIMATE, PROP_BUDDY, PROP_CONVERSATION, N_PROPERTIES, }; static GParamSpec *properties[N_PROPERTIES] = {NULL, }; G_DEFINE_FINAL_TYPE(PidginAvatar, pidgin_avatar, GTK_TYPE_BOX) /****************************************************************************** * Helpers *****************************************************************************/ static PurpleBuddy * pidgin_avatar_get_effective_buddy(PidginAvatar *avatar) { PurpleBuddy *buddy = NULL; if(PURPLE_IS_BUDDY(avatar->buddy)) { buddy = PURPLE_BUDDY(avatar->buddy); } else if(PURPLE_IS_IM_CONVERSATION(avatar->conversation)) { PurpleAccount *account = NULL; const gchar *name = NULL; account = purple_conversation_get_account(avatar->conversation); name = purple_conversation_get_name(avatar->conversation); buddy = purple_blist_find_buddy(account, name); } return buddy; } static GdkPixbufAnimation * pidgin_avatar_find_buddy_icon(PurpleBuddy *buddy, G_GNUC_UNUSED PurpleConversation *conversation) { GdkPixbufAnimation *ret = NULL; GInputStream *stream = NULL; PurpleMetaContact *contact = NULL; g_return_val_if_fail(PURPLE_IS_BUDDY(buddy), NULL); /* First check if our user has set a custom icon for this buddy. */ contact = purple_buddy_get_contact(buddy); if(PURPLE_IS_META_CONTACT(contact)) { PurpleBlistNode *node = PURPLE_BLIST_NODE(contact); PurpleImage *custom_image = NULL; custom_image = purple_buddy_icons_node_find_custom_icon(node); if(PURPLE_IS_IMAGE(custom_image)) { gconstpointer data = purple_image_get_data(custom_image); gsize length = purple_image_get_data_size(custom_image); stream = g_memory_input_stream_new_from_data(data, (gssize)length, NULL); } } /* If there is no custom icon, fall back to checking if the buddy has an * icon set. */ if(!G_IS_INPUT_STREAM(stream)) { PurpleBuddyIcon *icon = purple_buddy_get_icon(buddy); if(icon != NULL) { stream = purple_buddy_icon_get_stream(icon); } } if(G_IS_INPUT_STREAM(stream)) { ret = gdk_pixbuf_animation_new_from_stream(stream, NULL, NULL); g_clear_object(&stream); } return ret; } static void pidgin_avatar_update(PidginAvatar *avatar) { PurpleBuddy *buddy = NULL; GdkPixbufAnimation *animation = NULL; GdkPixbuf *pixbuf = NULL; buddy = pidgin_avatar_get_effective_buddy(avatar); if(PURPLE_IS_BUDDY(buddy)) { animation = pidgin_avatar_find_buddy_icon(buddy, avatar->conversation); } g_set_object(&avatar->animation, animation); if(GDK_IS_PIXBUF_ANIMATION(avatar->animation)) { if(avatar->animate && !gdk_pixbuf_animation_is_static_image(avatar->animation)) { pixbuf = GDK_PIXBUF(avatar->animation); } else { pixbuf = gdk_pixbuf_animation_get_static_image(avatar->animation); } } gtk_picture_set_pixbuf(GTK_PICTURE(avatar->icon), pixbuf); g_clear_object(&animation); } /****************************************************************************** * Actions *****************************************************************************/ static void pidgin_avatar_save_dialog_cb(GObject *source, GAsyncResult *result, gpointer data) { PidginAvatar *avatar = data; PurpleBuddy *buddy = NULL; PurpleBuddyIcon *icon = NULL; GtkFileDialog *dialog = GTK_FILE_DIALOG(source); GError *error = NULL; GFile *file = NULL; file = gtk_file_dialog_save_finish(dialog, result, &error); if(error != NULL) { g_warning("failed to select custom avatar destination: %s", error->message); g_clear_error(&error); g_clear_object(&file); return; } buddy = pidgin_avatar_get_effective_buddy(avatar); if(!PURPLE_IS_BUDDY(buddy)) { g_clear_object(&file); return; } icon = purple_buddy_get_icon(buddy); if(icon != NULL) { char *filename = NULL; filename = g_file_get_path(file); purple_buddy_icon_save_to_filename(icon, filename, NULL); g_free(filename); } g_clear_object(&file); } static void pidgin_avatar_save_cb(G_GNUC_UNUSED GSimpleAction *action, G_GNUC_UNUSED GVariant *parameter, gpointer data) { PidginAvatar *avatar = PIDGIN_AVATAR(data); PurpleBuddy *buddy = NULL; PurpleBuddyIcon *icon = NULL; PurpleAccount *account = NULL; GtkFileDialog *dialog = NULL; GtkWindow *window = NULL; const gchar *ext = NULL, *name = NULL; gchar *filename = NULL; buddy = pidgin_avatar_get_effective_buddy(avatar); if(buddy == NULL) { g_return_if_reached(); } icon = purple_buddy_get_icon(buddy); if(icon != NULL) { ext = purple_buddy_icon_get_extension(icon); } account = purple_buddy_get_account(buddy); name = purple_buddy_get_name(buddy); if(ext != NULL) { filename = g_strdup_printf("%s.%s", purple_normalize(account, name), ext); } else { filename = g_strdup(purple_normalize(account, name)); } window = GTK_WINDOW(gtk_widget_get_root(GTK_WIDGET(avatar))); dialog = gtk_file_dialog_new(); gtk_file_dialog_set_title(dialog, _("Save Avatar")); gtk_file_dialog_set_modal(dialog, TRUE); gtk_file_dialog_set_initial_name(dialog, filename); g_free(filename); gtk_file_dialog_save(dialog, window, NULL, pidgin_avatar_save_dialog_cb, avatar); g_clear_object(&dialog); } static void pidgin_avatar_set_custom_dialog_cb(GObject *source, GAsyncResult *result, gpointer data) { PidginAvatar *avatar = data; PurpleBuddy *buddy = NULL; GtkFileDialog *dialog = GTK_FILE_DIALOG(source); GError *error = NULL; GFile *file = NULL; gchar *filename = NULL; file = gtk_file_dialog_open_finish(dialog, result, &error); if(error != NULL) { g_message("failed to select custom avatar: %s", error->message); g_clear_error(&error); g_clear_object(&file); return; } buddy = pidgin_avatar_get_effective_buddy(avatar); if(!PURPLE_IS_BUDDY(buddy)) { g_clear_object(&file); return; } filename = g_file_get_path(file); if(filename != NULL) { PurpleMetaContact *contact = purple_buddy_get_contact(buddy); PurpleBlistNode *node = PURPLE_BLIST_NODE(contact); purple_buddy_icons_node_set_custom_icon_from_file(node, filename); pidgin_avatar_update(avatar); } g_clear_pointer(&filename, g_free); g_clear_object(&file); } static void pidgin_avatar_set_custom_cb(G_GNUC_UNUSED GSimpleAction *action, G_GNUC_UNUSED GVariant *parameter, gpointer data) { PidginAvatar *avatar = PIDGIN_AVATAR(data); GtkFileDialog *dialog = NULL; GtkWindow *window = NULL; window = GTK_WINDOW(gtk_widget_get_root(GTK_WIDGET(avatar))); dialog = gtk_file_dialog_new(); gtk_file_dialog_set_title(dialog, _("Set Custom Avatar")); gtk_file_dialog_set_accept_label(dialog, _("Set Custom")); gtk_file_dialog_set_modal(dialog, TRUE); gtk_file_dialog_open(dialog, window, NULL, pidgin_avatar_set_custom_dialog_cb, avatar); g_clear_object(&dialog); } static void pidgin_avatar_clear_custom_cb(G_GNUC_UNUSED GSimpleAction *action, G_GNUC_UNUSED GVariant *parameter, gpointer data) { PidginAvatar *avatar = PIDGIN_AVATAR(data); PurpleBuddy *buddy = NULL; buddy = pidgin_avatar_get_effective_buddy(avatar); if(PURPLE_IS_BUDDY(buddy)) { PurpleMetaContact *contact = purple_buddy_get_contact(buddy); PurpleBlistNode *node = PURPLE_BLIST_NODE(contact); purple_buddy_icons_node_set_custom_icon_from_file(node, NULL); pidgin_avatar_update(avatar); } } static GActionEntry actions[] = { { .name = "save-avatar", .activate = pidgin_avatar_save_cb, }, { .name = "set-custom-avatar", .activate = pidgin_avatar_set_custom_cb, }, { .name = "clear-custom-avatar", .activate = pidgin_avatar_clear_custom_cb, }, }; /****************************************************************************** * Callbacks *****************************************************************************/ static gboolean pidgin_avatar_button_press_handler(G_GNUC_UNUSED GtkGestureClick *event, G_GNUC_UNUSED gint n_press, gdouble x, gdouble y, gpointer data) { PidginAvatar *avatar = PIDGIN_AVATAR(data); GtkWidget *menu = NULL; menu = gtk_popover_menu_new_from_model(avatar->menu); gtk_widget_set_parent(menu, GTK_WIDGET(avatar)); gtk_popover_set_pointing_to(GTK_POPOVER(menu), &(const GdkRectangle){(int)x, (int)y, 0, 0}); gtk_popover_popup(GTK_POPOVER(menu)); return TRUE; } /* * This function is a callback for when properties change on the buddy we're * tracking. It should not be reused for the conversation we're tracking * because we have to disconnect old handlers and reuse of this function will * cause issues if a buddy is changed but a conversation is not and vice versa. */ static void pidgin_avatar_buddy_icon_updated(G_GNUC_UNUSED GObject *obj, G_GNUC_UNUSED GParamSpec *pspec, gpointer d) { PidginAvatar *avatar = PIDGIN_AVATAR(d); pidgin_avatar_update(avatar); } /* * This function is a callback for when properties change on the conversation * we're tracking. It should not be reused for the buddy we're tracking * because we have to disconnect old handlers and reuse of this function will * cause issues if a buddy is changed but a conversation is not and vice versa. */ static void pidgin_avatar_conversation_updated(G_GNUC_UNUSED GObject *obj, G_GNUC_UNUSED GParamSpec *pspec, gpointer d) { PidginAvatar *avatar = PIDGIN_AVATAR(d); pidgin_avatar_update(avatar); } static gboolean pidgin_avatar_enter_notify_handler(G_GNUC_UNUSED GtkEventControllerMotion *event, G_GNUC_UNUSED gdouble x, G_GNUC_UNUSED gdouble y, gpointer data) { PidginAvatar *avatar = PIDGIN_AVATAR(data); pidgin_avatar_set_animate(avatar, TRUE); return FALSE; } static gboolean pidgin_avatar_leave_notify_handler(G_GNUC_UNUSED GtkEventControllerMotion *event, gpointer data) { PidginAvatar *avatar = PIDGIN_AVATAR(data); pidgin_avatar_set_animate(avatar, FALSE); return FALSE; } /****************************************************************************** * GObject Implementation *****************************************************************************/ static void pidgin_avatar_get_property(GObject *obj, guint param_id, GValue *value, GParamSpec *pspec) { PidginAvatar *avatar = PIDGIN_AVATAR(obj); switch(param_id) { case PROP_ANIMATE: g_value_set_boolean(value, pidgin_avatar_get_animate(avatar)); break; case PROP_BUDDY: g_value_set_object(value, pidgin_avatar_get_buddy(avatar)); break; case PROP_CONVERSATION: g_value_set_object(value, pidgin_avatar_get_conversation(avatar)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec); break; } } static void pidgin_avatar_set_property(GObject *obj, guint param_id, const GValue *value, GParamSpec *pspec) { PidginAvatar *avatar = PIDGIN_AVATAR(obj); switch(param_id) { case PROP_ANIMATE: pidgin_avatar_set_animate(avatar, g_value_get_boolean(value)); break; case PROP_BUDDY: pidgin_avatar_set_buddy(avatar, g_value_get_object(value)); break; case PROP_CONVERSATION: pidgin_avatar_set_conversation(avatar, g_value_get_object(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec); break; } } static void pidgin_avatar_dispose(GObject *obj) { PidginAvatar *avatar = PIDGIN_AVATAR(obj); pidgin_avatar_set_buddy(avatar, NULL); pidgin_avatar_set_conversation(avatar, NULL); g_clear_object(&avatar->animation); G_OBJECT_CLASS(pidgin_avatar_parent_class)->dispose(obj); } static void pidgin_avatar_init(PidginAvatar *avatar) { GSimpleActionGroup *group = NULL; gtk_widget_init_template(GTK_WIDGET(avatar)); gtk_picture_set_content_fit(GTK_PICTURE(avatar->icon), GTK_CONTENT_FIT_SCALE_DOWN); /* Now setup our actions. */ group = g_simple_action_group_new(); g_action_map_add_action_entries(G_ACTION_MAP(group), actions, G_N_ELEMENTS(actions), avatar); gtk_widget_insert_action_group(GTK_WIDGET(avatar), "avatar", G_ACTION_GROUP(group)); } static void pidgin_avatar_class_init(PidginAvatarClass *klass) { GObjectClass *obj_class = G_OBJECT_CLASS(klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); obj_class->get_property = pidgin_avatar_get_property; obj_class->set_property = pidgin_avatar_set_property; obj_class->dispose = pidgin_avatar_dispose; /** * PidginAvatar:animate: * * Whether or not an animated avatar should be animated. */ properties[PROP_ANIMATE] = g_param_spec_boolean( "animate", "animate", "Whether or not to animate an animated avatar", FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS); /** * PidginAvatar:buddy: * * The buddy whose avatar will be displayed. */ properties[PROP_BUDDY] = g_param_spec_object( "buddy", "buddy", "The buddy whose avatar to display", PURPLE_TYPE_BUDDY, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS); /** * PidginAvatar:conversation: * * The conversation which will be used to find the correct buddy. */ properties[PROP_CONVERSATION] = g_param_spec_object( "conversation", "conversation", "The conversation used to find the correct buddy.", PURPLE_TYPE_CONVERSATION, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS); g_object_class_install_properties(obj_class, N_PROPERTIES, properties); gtk_widget_class_set_template_from_resource( widget_class, "/im/pidgin/Pidgin3/avatar.ui" ); gtk_widget_class_bind_template_child(widget_class, PidginAvatar, icon); gtk_widget_class_bind_template_child(widget_class, PidginAvatar, menu); gtk_widget_class_bind_template_callback(widget_class, pidgin_avatar_button_press_handler); gtk_widget_class_bind_template_callback(widget_class, pidgin_avatar_enter_notify_handler); gtk_widget_class_bind_template_callback(widget_class, pidgin_avatar_leave_notify_handler); } /****************************************************************************** * API *****************************************************************************/ GtkWidget * pidgin_avatar_new(void) { return g_object_new(PIDGIN_TYPE_AVATAR, NULL); } void pidgin_avatar_set_animate(PidginAvatar *avatar, gboolean animate) { g_return_if_fail(PIDGIN_IS_AVATAR(avatar)); avatar->animate = animate; if(GDK_IS_PIXBUF_ANIMATION(avatar->animation)) { if(avatar->animate && !gdk_pixbuf_animation_is_static_image(avatar->animation)) { gtk_picture_set_pixbuf(GTK_PICTURE(avatar->icon), GDK_PIXBUF(avatar->animation)); } else { GdkPixbuf *frame = NULL; frame = gdk_pixbuf_animation_get_static_image(avatar->animation); gtk_picture_set_pixbuf(GTK_PICTURE(avatar->icon), frame); } } } gboolean pidgin_avatar_get_animate(PidginAvatar *avatar) { g_return_val_if_fail(PIDGIN_IS_AVATAR(avatar), FALSE); return avatar->animate; } void pidgin_avatar_set_buddy(PidginAvatar *avatar, PurpleBuddy *buddy) { g_return_if_fail(PIDGIN_IS_AVATAR(avatar)); /* Remove our old signal handler. */ if(PURPLE_IS_BUDDY(avatar->buddy)) { g_signal_handlers_disconnect_by_func(avatar->buddy, pidgin_avatar_buddy_icon_updated, avatar); } if(g_set_object(&avatar->buddy, buddy)) { pidgin_avatar_update(avatar); g_object_notify_by_pspec(G_OBJECT(avatar), properties[PROP_BUDDY]); } /* Add the notify signal so we can update when the icon changes. */ if(PURPLE_IS_BUDDY(avatar->buddy)) { g_signal_connect(G_OBJECT(avatar->buddy), "notify::icon", G_CALLBACK(pidgin_avatar_buddy_icon_updated), avatar); } } PurpleBuddy * pidgin_avatar_get_buddy(PidginAvatar *avatar) { g_return_val_if_fail(PIDGIN_IS_AVATAR(avatar), NULL); return avatar->buddy; } void pidgin_avatar_set_conversation(PidginAvatar *avatar, PurpleConversation *conversation) { g_return_if_fail(PIDGIN_IS_AVATAR(avatar)); /* Remove our old signal handler. */ if(PURPLE_IS_CONVERSATION(avatar->conversation)) { g_signal_handlers_disconnect_by_func(avatar->conversation, pidgin_avatar_conversation_updated, avatar); } if(g_set_object(&avatar->conversation, conversation)) { g_object_notify_by_pspec(G_OBJECT(avatar), properties[PROP_CONVERSATION]); } /* Add the notify signal so we can update when the icon changes. */ if(PURPLE_IS_CONVERSATION(avatar->conversation)) { g_signal_connect(G_OBJECT(avatar->conversation), "notify", G_CALLBACK(pidgin_avatar_conversation_updated), avatar); } } PurpleConversation * pidgin_avatar_get_conversation(PidginAvatar *avatar) { g_return_val_if_fail(PIDGIN_IS_AVATAR(avatar), NULL); return avatar->conversation; }