pidgin/pidgin

Add animation support to PurpleAvatar

16 months ago, Gary Kramlich
ae3fa963c1b3
Parents b7f9345b02a4
Children fb172b78a9ee
Add animation support to PurpleAvatar

This drastically changes the way PurpleAvatar works. Previously the idea was
that developers would create a PurpleAvatar and then add it to the
PurpleAvatarManager (which does not exist). But these changes are going to make
it so that developers will either lookup avatars or ask the manager to create
an avatar based on their data.

Testing Done:
Ran the unit tests.

Reviewed at https://reviews.imfreedom.org/r/2141/
--- a/libpurple/purpleavatar.c Fri Jan 06 01:53:58 2023 -0600
+++ b/libpurple/purpleavatar.c Fri Jan 06 02:10:17 2023 -0600
@@ -22,7 +22,12 @@
GObject parent;
char *filename;
+
GdkPixbuf *pixbuf;
+
+ gboolean animated;
+ GdkPixbufAnimation *animation;
+
PurpleTags *tags;
};
@@ -30,6 +35,8 @@
PROP_0,
PROP_FILENAME,
PROP_PIXBUF,
+ PROP_ANIMATED,
+ PROP_ANIMATION,
PROP_TAGS,
N_PROPERTIES,
};
@@ -38,6 +45,45 @@
G_DEFINE_TYPE(PurpleAvatar, purple_avatar, G_TYPE_OBJECT)
/******************************************************************************
+ * Helpers
+ *****************************************************************************/
+static void
+purple_avatar_set_filename(PurpleAvatar *avatar, const char *filename) {
+ g_return_if_fail(PURPLE_IS_AVATAR(avatar));
+
+ g_free(avatar->filename);
+ avatar->filename = g_strdup(filename);
+
+ g_object_notify_by_pspec(G_OBJECT(avatar), properties[PROP_FILENAME]);
+}
+
+static PurpleAvatar *
+purple_avatar_new_common(const char *filename, GdkPixbufAnimation *animation) {
+ PurpleAvatar *avatar = NULL;
+
+ avatar = g_object_new(PURPLE_TYPE_AVATAR, "filename", filename, NULL);
+
+ if(gdk_pixbuf_animation_is_static_image(animation)) {
+ /* If we loaded a static image, grab the static image and set it to our
+ * pixbuf member, clear the animation, and return the new avatar.
+ */
+
+ avatar->pixbuf = gdk_pixbuf_animation_get_static_image(animation);
+ g_object_ref(avatar->pixbuf);
+
+ g_clear_object(&animation);
+ } else {
+ /* If we did load an animation, set the appropriate properties and
+ * return the avatar.
+ */
+ avatar->animated = TRUE;
+ avatar->animation = animation;
+ }
+
+ return avatar;
+}
+
+/******************************************************************************
* GObject Implementation
*****************************************************************************/
static void
@@ -53,6 +99,12 @@
case PROP_PIXBUF:
g_value_set_object(value, purple_avatar_get_pixbuf(avatar));
break;
+ case PROP_ANIMATED:
+ g_value_set_boolean(value, purple_avatar_get_animated(avatar));
+ break;
+ case PROP_ANIMATION:
+ g_value_set_object(value, purple_avatar_get_animation(avatar));
+ break;
case PROP_TAGS:
g_value_set_object(value, purple_avatar_get_tags(avatar));
break;
@@ -72,9 +124,6 @@
case PROP_FILENAME:
purple_avatar_set_filename(avatar, g_value_get_string(value));
break;
- case PROP_PIXBUF:
- purple_avatar_set_pixbuf(avatar, g_value_get_object(value));
- break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
break;
@@ -87,6 +136,7 @@
g_clear_pointer(&avatar->filename, g_free);
g_clear_object(&avatar->pixbuf);
+ g_clear_object(&avatar->animation);
g_clear_object(&avatar->tags);
G_OBJECT_CLASS(purple_avatar_parent_class)->finalize(obj);
@@ -108,7 +158,7 @@
/**
* PurpleAvatar:filename:
*
- * The filename to save/load the avatar to/from.
+ * The filename that this avatar was created from.
*
* Since: 3.0.0
*/
@@ -116,13 +166,14 @@
"filename", "filename",
"The filename to save/load the avatar from.",
NULL,
- G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
/**
* PurpleAvatar:pixbuf:
*
- * The [class@GdkPixbuf.Pixbuf]. This may be %NULL if
- * [method@Purple.Avatar.load] has not yet been called.
+ * The [class@GdkPixbuf.Pixbuf] of the avatar. If
+ * [property@Purple.Avatar:animated] is %TRUE, this will be a static frame
+ * from the animation.
*
* Since: 3.0.0
*/
@@ -130,7 +181,34 @@
"pixbuf", "pixbuf",
"The pixbuf of the avatar.",
GDK_TYPE_PIXBUF,
- G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * PurpleAvatar:animated:
+ *
+ * Whether or not this avatar is animated.
+ *
+ * Since: 3.0.0
+ */
+ properties[PROP_ANIMATED] = g_param_spec_boolean(
+ "animated", "animated",
+ "Whether or not the avatar is animated.",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * PurpleAvatar:animation:
+ *
+ * The [class@GdkPixbuf.PixbufAnimation] if
+ * [property@Purple.Avatar:animated] is %TRUE.
+ *
+ * Since: 3.0.0
+ */
+ properties[PROP_ANIMATION] = g_param_spec_object(
+ "animation", "animation",
+ "The animation of the avatar.",
+ GDK_TYPE_PIXBUF_ANIMATION,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
/**
* PurpleAvatar:tags:
@@ -143,7 +221,7 @@
"tags", "tags",
"The tags for the avatar.",
PURPLE_TYPE_TAGS,
- G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
}
@@ -152,12 +230,42 @@
* Public API
*****************************************************************************/
PurpleAvatar *
-purple_avatar_new(const char *filename, GdkPixbuf *pixbuf) {
- return g_object_new(
- PURPLE_TYPE_AVATAR,
- "filename", filename,
- "pixbuf", pixbuf,
- NULL);
+purple_avatar_new_from_filename(const char *filename, GError **error) {
+ GdkPixbufAnimation *animation = NULL;
+ GError *local_error = NULL;
+
+ g_return_val_if_fail(filename != NULL, NULL);
+
+ animation = gdk_pixbuf_animation_new_from_file(filename, &local_error);
+ if(!GDK_IS_PIXBUF_ANIMATION(animation) || local_error != NULL) {
+ g_clear_object(&animation);
+
+ g_propagate_error(error, local_error);
+
+ return NULL;
+ }
+
+ return purple_avatar_new_common(filename, animation);
+}
+
+PurpleAvatar *
+purple_avatar_new_from_resource(const char *resource_path, GError **error) {
+ GdkPixbufAnimation *animation = NULL;
+ GError *local_error = NULL;
+
+ g_return_val_if_fail(resource_path != NULL, NULL);
+
+ animation = gdk_pixbuf_animation_new_from_resource(resource_path,
+ &local_error);
+ if(!GDK_IS_PIXBUF_ANIMATION(animation) || local_error != NULL) {
+ g_clear_object(&animation);
+
+ g_propagate_error(error, local_error);
+
+ return NULL;
+ }
+
+ return purple_avatar_new_common(NULL, animation);
}
const char *
@@ -167,30 +275,29 @@
return avatar->filename;
}
-void
-purple_avatar_set_filename(PurpleAvatar *avatar, const char *filename) {
- g_return_if_fail(PURPLE_IS_AVATAR(avatar));
-
- g_free(avatar->filename);
- avatar->filename = g_strdup(filename);
-
- g_object_notify_by_pspec(G_OBJECT(avatar), properties[PROP_FILENAME]);
-}
-
GdkPixbuf *
purple_avatar_get_pixbuf(PurpleAvatar *avatar) {
g_return_val_if_fail(PURPLE_IS_AVATAR(avatar), NULL);
+ if(avatar->animated) {
+ return gdk_pixbuf_animation_get_static_image(avatar->animation);
+ }
+
return avatar->pixbuf;
}
-void
-purple_avatar_set_pixbuf(PurpleAvatar *avatar, GdkPixbuf *pixbuf) {
- g_return_if_fail(PURPLE_IS_AVATAR(avatar));
+gboolean
+purple_avatar_get_animated(PurpleAvatar *avatar) {
+ g_return_val_if_fail(PURPLE_IS_AVATAR(avatar), FALSE);
- if(g_set_object(&avatar->pixbuf, pixbuf)) {
- g_object_notify_by_pspec(G_OBJECT(avatar), properties[PROP_PIXBUF]);
- }
+ return avatar->animated;
+}
+
+GdkPixbufAnimation *
+purple_avatar_get_animation(PurpleAvatar *avatar) {
+ g_return_val_if_fail(PURPLE_IS_AVATAR(avatar), NULL);
+
+ return avatar->animation;
}
PurpleTags *
@@ -199,30 +306,3 @@
return avatar->tags;
}
-
-gboolean
-purple_avatar_load(PurpleAvatar *avatar, GError **error) {
- GError *local_error = NULL;
-
- g_return_val_if_fail(PURPLE_IS_AVATAR(avatar), FALSE);
-
- g_clear_object(&avatar->pixbuf);
-
- avatar->pixbuf = gdk_pixbuf_new_from_file(avatar->filename, &local_error);
-
- if(avatar->pixbuf == NULL) {
- g_propagate_error(error, local_error);
-
- return FALSE;
- }
-
- return TRUE;
-}
-
-gboolean
-purple_avatar_save(PurpleAvatar *avatar, GError **error) {
- g_return_val_if_fail(PURPLE_IS_AVATAR(avatar), FALSE);
-
- return gdk_pixbuf_save(avatar->pixbuf, avatar->filename, "png", error,
- "quality", "100", NULL);
-}
--- a/libpurple/purpleavatar.h Fri Jan 06 01:53:58 2023 -0600
+++ b/libpurple/purpleavatar.h Fri Jan 06 02:10:17 2023 -0600
@@ -45,17 +45,30 @@
*/
/**
- * purple_avatar_new:
- * @filename: (nullable): The filename for the avatar.
- * @pixbuf: (nullable): The [class@GdkPixbuf.Pixbuf] to use.
+ * purple_avatar_new_from_filename:
+ * @filename: The filename for the avatar.
+ * @error: Return address for a #GError, or %NULL.
*
- * Creates a new avatar with @filename and @pixbuf.
+ * Creates a new avatar from @filename.
*
* Returns: (transfer full): The new instance.
*
* Since: 3.0.0
*/
-PurpleAvatar *purple_avatar_new(const char *filename, GdkPixbuf *pixbuf);
+PurpleAvatar *purple_avatar_new_from_filename(const char *filename, GError **error);
+
+/**
+ * purple_avatar_new_from_resource:
+ * @resource_path: The path of the resource file.
+ * @error: Return address for a #GError, or %NULL.
+ *
+ * Creates a new avatar from the resource at @resource_path.
+ *
+ * Returns: (transfer full): The new instance.
+ *
+ * Since: 3.0.0
+ */
+PurpleAvatar *purple_avatar_new_from_resource(const char *resource_path, GError **error);
/**
* purple_avatar_get_filename:
@@ -70,39 +83,45 @@
const char *purple_avatar_get_filename(PurpleAvatar *avatar);
/**
- * purple_avatar_set_filename:
- * @avatar: The instance.
- * @filename: The new filename.
- *
- * Sets the filename of @avatar to @filename.
- *
- * Since: 3.0.0
- */
-void purple_avatar_set_filename(PurpleAvatar *avatar, const char *filename);
-
-/**
* purple_avatar_get_pixbuf:
* @avatar: The instance.
*
* Gets the [class@GdkPixbuf.Pixbuf] of @avatar.
*
- * Returns: The pixbuf of the avatar which could be %NULL.
+ * If [property@Purple.Avatar:animated] is %TRUE, this returns a single frame
+ * of the animation. To get the animation see
+ * [method@Purple.Avatar.get_animation].
+ *
+ * Returns: (transfer none): The pixbuf of the avatar which could be %NULL.
*
* Since: 3.0.0
*/
GdkPixbuf *purple_avatar_get_pixbuf(PurpleAvatar *avatar);
/**
- * purple_avatar_set_pixbuf:
+ * purple_avatar_get_animated:
* @avatar: The instance.
- * @pixbuf: (nullable): The new [class@GdkPixbuf.Pixbuf].
*
- * Sets the [class@GdkPixbuf.Pixbuf] for @avatar to @pixbuf. If @pixbuf is
- * %NULL, the pixbuf will be cleared.
+ * Gets whether or not @avatar is animated.
+ *
+ * Returns: %TRUE if @avatar is animated, %FALSE otherwise.
*
* Since: 3.0.0
*/
-void purple_avatar_set_pixbuf(PurpleAvatar *avatar, GdkPixbuf *pixbuf);
+gboolean purple_avatar_get_animated(PurpleAvatar *avatar);
+
+/**
+ * purple_avatar_get_animation:
+ * @avatar: The instance.
+ *
+ * Gets the [class@GdkPixbuf.PixbufAnimation] if
+ * [property@Purple.Avatar:animated] is %TRUE, otherwise %NULL.
+ *
+ * Returns: (transfer none): The animation or %NULL.
+ *
+ * Since: 3.0.0
+ */
+GdkPixbufAnimation *purple_avatar_get_animation(PurpleAvatar *avatar);
/**
* purple_avatar_get_tags:
@@ -116,36 +135,6 @@
*/
PurpleTags *purple_avatar_get_tags(PurpleAvatar *avatar);
-/**
- * purple_avatar_load:
- * @avatar: The instance.
- * @error: Return address for a #GError, or %NULL.
- *
- * Attempts to load @avatar from disk from [property@Purple.Avatar:filename].
- * If successful, %TRUE is returned, otherwise %FALSE will be returned with
- * @error potentially set.
- *
- * Returns: %TRUE on success or %FALSE on error with @error potentialy set.
- *
- * Since: 3.0.0
- */
-gboolean purple_avatar_load(PurpleAvatar *avatar, GError **error);
-
-/**
- * purple_avatar_save:
- * @avatar: The instance.
- * @error: Return address for a #GError, or %NULL.
- *
- * Attempts to save @avatar to disk using [property@Purple.Avatar:filename].
- * If successful, %TRUE is returned, otherwise %FALSE will be returned with
- * @error potentially set.
- *
- * Returns: %TRUE on success or %FALSE on error with @error potentialy set.
- *
- * Since: 3.0.0
- */
-gboolean purple_avatar_save(PurpleAvatar *avatar, GError **error);
-
G_END_DECLS
#endif /* PURPLE_AVATAR_H */
Binary file libpurple/tests/avatar/animated.gif has changed
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/tests/avatar/meson.build Fri Jan 06 02:10:17 2023 -0600
@@ -0,0 +1,17 @@
+TEST_PURPLE_AVATAR_SOURCES = [
+ 'test_purple_avatar.c'
+]
+
+TEST_PURPLE_AVATAR_RESOURCES = gnome.compile_resources(
+ 'test_purple_avatar_resources',
+ 'test_purple_avatar.gresource.xml',
+ source_dir : '.',
+ c_name : 'test_purple_avatar')
+TEST_PURPLE_AVATAR_SOURCES += TEST_PURPLE_AVATAR_RESOURCES
+
+test_purple_avatar = executable(
+ 'test_purple_avatar',
+ TEST_PURPLE_AVATAR_SOURCES,
+ dependencies : [libpurple_dep, glib, gdk_pixbuf])
+
+test('avatar', test_purple_avatar)
Binary file libpurple/tests/avatar/static.png has changed
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/tests/avatar/test_purple_avatar.c Fri Jan 06 02:10:17 2023 -0600
@@ -0,0 +1,84 @@
+/*
+ * Purple - Internet Messaging Library
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <glib.h>
+
+#include <purple.h>
+
+/******************************************************************************
+ * Tests
+ *****************************************************************************/
+static void
+test_purple_avatar_new_static(void) {
+ PurpleAvatar *avatar = NULL;
+ GdkPixbuf *pixbuf = NULL;
+ GError *error = NULL;
+ const char *resource = "/im/pidgin/libpurple/tests/avatar/static.png";
+
+ avatar = purple_avatar_new_from_resource(resource, &error);
+ g_assert_no_error(error);
+ g_assert_true(PURPLE_IS_AVATAR(avatar));
+
+ g_assert_null(purple_avatar_get_filename(avatar));
+ g_assert_false(purple_avatar_get_animated(avatar));
+ g_assert_null(purple_avatar_get_animation(avatar));
+
+ pixbuf = purple_avatar_get_pixbuf(avatar);
+ g_assert_true(GDK_IS_PIXBUF(pixbuf));
+
+ g_clear_object(&avatar);
+}
+
+static void
+test_purple_avatar_new_animated(void) {
+ PurpleAvatar *avatar = NULL;
+ GdkPixbuf *pixbuf = NULL;
+ GdkPixbufAnimation *animation = NULL;
+ GError *error = NULL;
+ const char *resource = "/im/pidgin/libpurple/tests/avatar/animated.gif";
+
+ avatar = purple_avatar_new_from_resource(resource, &error);
+ g_assert_no_error(error);
+ g_assert_true(PURPLE_IS_AVATAR(avatar));
+
+ g_assert_null(purple_avatar_get_filename(avatar));
+ g_assert_true(purple_avatar_get_animated(avatar));
+
+ pixbuf = purple_avatar_get_pixbuf(avatar);
+ g_assert_true(GDK_IS_PIXBUF(pixbuf));
+
+ animation = purple_avatar_get_animation(avatar);
+ g_assert_true(GDK_IS_PIXBUF_ANIMATION(animation));
+
+ g_clear_object(&avatar);
+}
+
+/******************************************************************************
+ * Main
+ *****************************************************************************/
+gint
+main(gint argc, gchar *argv[]) {
+ g_test_init(&argc, &argv, NULL);
+
+ g_test_add_func("/avatar/new/static",
+ test_purple_avatar_new_static);
+ g_test_add_func("/avatar/new/animated",
+ test_purple_avatar_new_animated);
+
+ return g_test_run();
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/tests/avatar/test_purple_avatar.gresource.xml Fri Jan 06 02:10:17 2023 -0600
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/im/pidgin/libpurple/tests/avatar/">
+ <file>static.png</file>
+ <file>animated.gif</file>
+ </gresource>
+</gresources>
Binary file libpurple/tests/avatar/wave.png has changed
--- a/libpurple/tests/meson.build Fri Jan 06 01:53:58 2023 -0600
+++ b/libpurple/tests/meson.build Fri Jan 06 02:10:17 2023 -0600
@@ -2,7 +2,6 @@
'account_option',
'account_manager',
'authorization_request',
- 'avatar',
'circular_buffer',
'contact',
'contact_info',
@@ -56,4 +55,5 @@
)
endforeach
+subdir('avatar')
subdir('sqlite3')
--- a/libpurple/tests/test_avatar.c Fri Jan 06 01:53:58 2023 -0600
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,97 +0,0 @@
-/*
- * Purple - Internet Messaging Library
- * Copyright (C) Pidgin Developers <devel@pidgin.im>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, see <https://www.gnu.org/licenses/>.
- */
-
-#include <glib.h>
-
-#include <purple.h>
-
-/******************************************************************************
- * Tests
- *****************************************************************************/
-static void
-test_purple_avatar_new(void) {
- PurpleAvatar *avatar = NULL;
- GdkPixbuf *pixbuf = NULL;
-
- pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, 1, 1);
- avatar = purple_avatar_new("filename", pixbuf);
-
- g_assert_true(PURPLE_IS_AVATAR(avatar));
-
- g_assert_cmpstr(purple_avatar_get_filename(avatar), ==, "filename");
- g_assert_true(purple_avatar_get_pixbuf(avatar) == pixbuf);
-
- g_clear_object(&avatar);
- g_clear_object(&pixbuf);
-}
-
-static void
-test_purple_avatar_properties(void) {
- PurpleAvatar *avatar = NULL;
- PurpleTags *tags = NULL;
- GdkPixbuf *pixbuf = NULL;
- GdkPixbuf *pixbuf1 = NULL;
- gchar *filename = NULL;
-
- pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, 1, 1);
-
- /* Use g_object_new so we can test setting properties by name. All of them
- * call the setter methods, so by doing it this way we exercise more of the
- * code.
- */
- avatar = g_object_new(
- PURPLE_TYPE_AVATAR,
- "filename", "filename",
- "pixbuf", pixbuf,
- NULL);
-
- /* Now use g_object_get to read all of the properties. */
- g_object_get(avatar,
- "filename", &filename,
- "pixbuf", &pixbuf1,
- "tags", &tags,
- NULL);
-
- /* Compare all the things. */
- g_assert_cmpstr(filename, ==, "filename");
- g_assert_true(pixbuf1 == pixbuf);
- g_assert_nonnull(tags);
-
- /* Free/unref all the things. */
- g_clear_pointer(&filename, g_free);
- g_clear_object(&pixbuf1);
- g_clear_object(&tags);
-
- g_clear_object(&avatar);
- g_clear_object(&pixbuf);
-}
-
-/******************************************************************************
- * Main
- *****************************************************************************/
-gint
-main(gint argc, gchar *argv[]) {
- g_test_init(&argc, &argv, NULL);
-
- g_test_add_func("/avatar/new",
- test_purple_avatar_new);
- g_test_add_func("/avatar/properties",
- test_purple_avatar_properties);
-
- return g_test_run();
-}