| 46 static GParamSpec *properties[N_PROPERTIES] = {NULL, }; |
46 static GParamSpec *properties[N_PROPERTIES] = {NULL, }; |
| 47 |
47 |
| 48 G_DEFINE_TYPE(PidginAvatar, pidgin_avatar, GTK_TYPE_BOX) |
48 G_DEFINE_TYPE(PidginAvatar, pidgin_avatar, GTK_TYPE_BOX) |
| 49 |
49 |
| 50 /****************************************************************************** |
50 /****************************************************************************** |
| |
51 * Helpers |
| |
52 *****************************************************************************/ |
| |
53 static PurpleBuddy * |
| |
54 pidgin_avatar_get_effective_buddy(PidginAvatar *avatar) { |
| |
55 PurpleBuddy *buddy = NULL; |
| |
56 |
| |
57 if(PURPLE_IS_BUDDY(avatar->buddy)) { |
| |
58 buddy = PURPLE_BUDDY(avatar->buddy); |
| |
59 |
| |
60 } else if(PURPLE_IS_IM_CONVERSATION(avatar->conversation)) { |
| |
61 PurpleAccount *account = NULL; |
| |
62 const gchar *name = NULL; |
| |
63 |
| |
64 account = purple_conversation_get_account(avatar->conversation); |
| |
65 |
| |
66 name = purple_conversation_get_name(avatar->conversation); |
| |
67 buddy = purple_blist_find_buddy(account, name); |
| |
68 } |
| |
69 |
| |
70 return buddy; |
| |
71 } |
| |
72 |
| |
73 static GdkPixbufAnimation * |
| |
74 pidgin_avatar_find_buddy_icon(PurpleBuddy *buddy, |
| |
75 PurpleConversation *conversation) |
| |
76 { |
| |
77 GdkPixbufAnimation *ret = NULL; |
| |
78 GInputStream *stream = NULL; |
| |
79 PurpleContact *contact = NULL; |
| |
80 |
| |
81 g_return_val_if_fail(PURPLE_IS_BUDDY(buddy), NULL); |
| |
82 |
| |
83 /* First check if our user has set a custom icon for this buddy. */ |
| |
84 contact = purple_buddy_get_contact(buddy); |
| |
85 if(PURPLE_IS_CONTACT(contact)) { |
| |
86 PurpleBlistNode *node = PURPLE_BLIST_NODE(contact); |
| |
87 PurpleImage *custom_image = NULL; |
| |
88 |
| |
89 custom_image = purple_buddy_icons_node_find_custom_icon(node); |
| |
90 if(PURPLE_IS_IMAGE(custom_image)) { |
| |
91 gconstpointer data = purple_image_get_data(custom_image); |
| |
92 gsize length = purple_image_get_data_size(custom_image); |
| |
93 |
| |
94 stream = g_memory_input_stream_new_from_data(data, (gssize)length, |
| |
95 NULL); |
| |
96 } |
| |
97 } |
| |
98 |
| |
99 /* If there is no custom icon, fall back to checking if the buddy has an |
| |
100 * icon set. |
| |
101 */ |
| |
102 if(!G_IS_INPUT_STREAM(stream)) { |
| |
103 PurpleBuddyIcon *icon = purple_buddy_get_icon(buddy); |
| |
104 |
| |
105 if(icon != NULL) { |
| |
106 stream = purple_buddy_icon_get_stream(icon); |
| |
107 } |
| |
108 } |
| |
109 |
| |
110 /* Finally if we still don't have icon, we fallback to asking the |
| |
111 * conversation for one. |
| |
112 */ |
| |
113 if(!G_IS_INPUT_STREAM(stream) && PURPLE_IS_IM_CONVERSATION(conversation)) { |
| |
114 PurpleBuddyIcon *icon = purple_im_conversation_get_icon(PURPLE_IM_CONVERSATION(conversation)); |
| |
115 |
| |
116 if(icon != NULL) { |
| |
117 stream = purple_buddy_icon_get_stream(icon); |
| |
118 } |
| |
119 } |
| |
120 |
| |
121 if(G_IS_INPUT_STREAM(stream)) { |
| |
122 ret = gdk_pixbuf_animation_new_from_stream(stream, NULL, NULL); |
| |
123 g_clear_object(&stream); |
| |
124 } |
| |
125 |
| |
126 return ret; |
| |
127 } |
| |
128 |
| |
129 static void |
| |
130 pidgin_avatar_update(PidginAvatar *avatar) { |
| |
131 PurpleBuddy *buddy = NULL; |
| |
132 GdkPixbufAnimation *animation = NULL; |
| |
133 GdkPixbuf *pixbuf = NULL; |
| |
134 |
| |
135 buddy = pidgin_avatar_get_effective_buddy(avatar); |
| |
136 if(PURPLE_IS_BUDDY(buddy)) { |
| |
137 animation = pidgin_avatar_find_buddy_icon(buddy, |
| |
138 avatar->conversation); |
| |
139 } |
| |
140 |
| |
141 g_set_object(&avatar->animation, animation); |
| |
142 |
| |
143 if(GDK_IS_PIXBUF_ANIMATION(avatar->animation)) { |
| |
144 if(avatar->animate && |
| |
145 !gdk_pixbuf_animation_is_static_image(avatar->animation)) { |
| |
146 pixbuf = GDK_PIXBUF(avatar->animation); |
| |
147 } else { |
| |
148 pixbuf = gdk_pixbuf_animation_get_static_image(avatar->animation); |
| |
149 } |
| |
150 } |
| |
151 |
| |
152 gtk_picture_set_pixbuf(GTK_PICTURE(avatar->icon), pixbuf); |
| |
153 |
| |
154 g_clear_object(&animation); |
| |
155 } |
| |
156 |
| |
157 /****************************************************************************** |
| 51 * Actions |
158 * Actions |
| 52 *****************************************************************************/ |
159 *****************************************************************************/ |
| 53 static void |
160 static void |
| 54 pidgin_avatar_save_response_cb(GtkNativeDialog *native, gint response, |
161 pidgin_avatar_save_response_cb(GtkNativeDialog *native, gint response, |
| 55 gpointer data) |
162 gpointer data) |
| 56 { |
163 { |
| 57 PidginAvatar *avatar = PIDGIN_AVATAR(data); |
164 PidginAvatar *avatar = PIDGIN_AVATAR(data); |
| |
165 PurpleBuddy *buddy = NULL; |
| 58 PurpleBuddyIcon *icon = NULL; |
166 PurpleBuddyIcon *icon = NULL; |
| 59 |
167 |
| 60 if(response != GTK_RESPONSE_ACCEPT || !PURPLE_IS_BUDDY(avatar->buddy)) { |
168 if(response != GTK_RESPONSE_ACCEPT) { |
| 61 gtk_native_dialog_destroy(native); |
169 gtk_native_dialog_destroy(native); |
| 62 |
170 |
| 63 return; |
171 return; |
| 64 } |
172 } |
| 65 |
173 |
| 66 icon = purple_buddy_get_icon(avatar->buddy); |
174 buddy = pidgin_avatar_get_effective_buddy(avatar); |
| |
175 if(!PURPLE_IS_BUDDY(buddy)) { |
| |
176 gtk_native_dialog_destroy(native); |
| |
177 |
| |
178 return; |
| |
179 } |
| |
180 |
| |
181 icon = purple_buddy_get_icon(buddy); |
| 67 |
182 |
| 68 if(icon != NULL) { |
183 if(icon != NULL) { |
| 69 GtkFileChooser *chooser = GTK_FILE_CHOOSER(native); |
184 GtkFileChooser *chooser = GTK_FILE_CHOOSER(native); |
| 70 GFile *file = NULL; |
185 GFile *file = NULL; |
| 71 gchar *filename = NULL; |
186 gchar *filename = NULL; |
| 85 static void |
200 static void |
| 86 pidgin_avatar_save_cb(GSimpleAction *action, GVariant *parameter, |
201 pidgin_avatar_save_cb(GSimpleAction *action, GVariant *parameter, |
| 87 gpointer data) |
202 gpointer data) |
| 88 { |
203 { |
| 89 PidginAvatar *avatar = PIDGIN_AVATAR(data); |
204 PidginAvatar *avatar = PIDGIN_AVATAR(data); |
| |
205 PurpleBuddy *buddy = NULL; |
| 90 PurpleAccount *account = NULL; |
206 PurpleAccount *account = NULL; |
| 91 GtkFileChooserNative *native = NULL; |
207 GtkFileChooserNative *native = NULL; |
| 92 GtkFileChooser *chooser = NULL; |
208 GtkFileChooser *chooser = NULL; |
| 93 GtkWindow *window = NULL; |
209 GtkWindow *window = NULL; |
| 94 const gchar *ext = NULL, *name = NULL; |
210 const gchar *ext = NULL, *name = NULL; |
| 95 gchar *filename = NULL; |
211 gchar *filename = NULL; |
| 96 |
212 |
| 97 g_return_if_fail(PURPLE_IS_BUDDY(avatar->buddy)); |
213 buddy = pidgin_avatar_get_effective_buddy(avatar); |
| 98 |
214 if(buddy == NULL) { |
| 99 ext = purple_buddy_icon_get_extension(purple_buddy_get_icon(avatar->buddy)); |
215 g_return_if_reached(); |
| 100 |
216 } |
| 101 account = purple_buddy_get_account(avatar->buddy); |
217 |
| 102 name = purple_buddy_get_name(avatar->buddy); |
218 ext = purple_buddy_icon_get_extension(purple_buddy_get_icon(buddy)); |
| |
219 |
| |
220 account = purple_buddy_get_account(buddy); |
| |
221 name = purple_buddy_get_name(buddy); |
| 103 filename = g_strdup_printf("%s.%s", purple_normalize(account, name), ext); |
222 filename = g_strdup_printf("%s.%s", purple_normalize(account, name), ext); |
| 104 |
223 |
| 105 window = GTK_WINDOW(gtk_widget_get_root(GTK_WIDGET(avatar))); |
224 window = GTK_WINDOW(gtk_widget_get_root(GTK_WIDGET(avatar))); |
| 106 native = gtk_file_chooser_native_new(_("Save Avatar"), |
225 native = gtk_file_chooser_native_new(_("Save Avatar"), |
| 107 window, |
226 window, |
| 122 static void |
241 static void |
| 123 pidgin_avatar_set_custom_response_cb(GtkNativeDialog *native, gint response, |
242 pidgin_avatar_set_custom_response_cb(GtkNativeDialog *native, gint response, |
| 124 gpointer data) |
243 gpointer data) |
| 125 { |
244 { |
| 126 PidginAvatar *avatar = PIDGIN_AVATAR(data); |
245 PidginAvatar *avatar = PIDGIN_AVATAR(data); |
| |
246 PurpleBuddy *buddy = NULL; |
| 127 GtkFileChooser *chooser = GTK_FILE_CHOOSER(native); |
247 GtkFileChooser *chooser = GTK_FILE_CHOOSER(native); |
| 128 GFile *file = NULL; |
248 GFile *file = NULL; |
| 129 gchar *filename = NULL; |
249 gchar *filename = NULL; |
| 130 |
250 |
| 131 if(response != GTK_RESPONSE_ACCEPT || !PURPLE_IS_BUDDY(avatar->buddy)) { |
251 if(response != GTK_RESPONSE_ACCEPT) { |
| |
252 gtk_native_dialog_destroy(native); |
| |
253 |
| |
254 return; |
| |
255 } |
| |
256 |
| |
257 buddy = pidgin_avatar_get_effective_buddy(avatar); |
| |
258 if(!PURPLE_IS_BUDDY(buddy)) { |
| 132 gtk_native_dialog_destroy(native); |
259 gtk_native_dialog_destroy(native); |
| 133 |
260 |
| 134 return; |
261 return; |
| 135 } |
262 } |
| 136 |
263 |
| 137 file = gtk_file_chooser_get_file(chooser); |
264 file = gtk_file_chooser_get_file(chooser); |
| 138 filename = g_file_get_path(file); |
265 filename = g_file_get_path(file); |
| 139 if(filename != NULL) { |
266 if(filename != NULL) { |
| 140 PurpleContact *contact = purple_buddy_get_contact(avatar->buddy); |
267 PurpleContact *contact = purple_buddy_get_contact(buddy); |
| 141 PurpleBlistNode *node = PURPLE_BLIST_NODE(contact); |
268 PurpleBlistNode *node = PURPLE_BLIST_NODE(contact); |
| 142 |
269 |
| 143 purple_buddy_icons_node_set_custom_icon_from_file(node, filename); |
270 purple_buddy_icons_node_set_custom_icon_from_file(node, filename); |
| |
271 |
| |
272 pidgin_avatar_update(avatar); |
| 144 } |
273 } |
| 145 |
274 |
| 146 g_free(filename); |
275 g_free(filename); |
| 147 g_object_unref(file); |
276 g_object_unref(file); |
| 148 |
277 |
| 173 static void |
302 static void |
| 174 pidgin_avatar_clear_custom_cb(GSimpleAction *action, GVariant *parameter, |
303 pidgin_avatar_clear_custom_cb(GSimpleAction *action, GVariant *parameter, |
| 175 gpointer data) |
304 gpointer data) |
| 176 { |
305 { |
| 177 PidginAvatar *avatar = PIDGIN_AVATAR(data); |
306 PidginAvatar *avatar = PIDGIN_AVATAR(data); |
| 178 |
307 PurpleBuddy *buddy = NULL; |
| 179 if(PURPLE_IS_BUDDY(avatar->buddy)) { |
308 |
| 180 PurpleContact *contact = purple_buddy_get_contact(avatar->buddy); |
309 buddy = pidgin_avatar_get_effective_buddy(avatar); |
| |
310 if(PURPLE_IS_BUDDY(buddy)) { |
| |
311 PurpleContact *contact = purple_buddy_get_contact(buddy); |
| 181 PurpleBlistNode *node = PURPLE_BLIST_NODE(contact); |
312 PurpleBlistNode *node = PURPLE_BLIST_NODE(contact); |
| 182 |
313 |
| 183 purple_buddy_icons_node_set_custom_icon_from_file(node, NULL); |
314 purple_buddy_icons_node_set_custom_icon_from_file(node, NULL); |
| |
315 |
| |
316 pidgin_avatar_update(avatar); |
| 184 } |
317 } |
| 185 } |
318 } |
| 186 |
319 |
| 187 static GActionEntry actions[] = { |
320 static GActionEntry actions[] = { |
| 188 { |
321 { |
| 196 .activate = pidgin_avatar_clear_custom_cb, |
329 .activate = pidgin_avatar_clear_custom_cb, |
| 197 }, |
330 }, |
| 198 }; |
331 }; |
| 199 |
332 |
| 200 /****************************************************************************** |
333 /****************************************************************************** |
| 201 * Helpers |
|
| 202 *****************************************************************************/ |
|
| 203 static GdkPixbufAnimation * |
|
| 204 pidgin_avatar_find_buddy_icon(PurpleBuddy *buddy, |
|
| 205 PurpleIMConversation *conversation) |
|
| 206 { |
|
| 207 GdkPixbufAnimation *ret = NULL; |
|
| 208 GInputStream *stream = NULL; |
|
| 209 PurpleContact *contact = NULL; |
|
| 210 |
|
| 211 g_return_val_if_fail(PURPLE_IS_BUDDY(buddy), NULL); |
|
| 212 |
|
| 213 /* First check if our user has set a custom icon for this buddy. */ |
|
| 214 contact = purple_buddy_get_contact(buddy); |
|
| 215 if(PURPLE_IS_CONTACT(contact)) { |
|
| 216 PurpleBlistNode *node = PURPLE_BLIST_NODE(contact); |
|
| 217 PurpleImage *custom_image = NULL; |
|
| 218 |
|
| 219 custom_image = purple_buddy_icons_node_find_custom_icon(node); |
|
| 220 if(PURPLE_IS_IMAGE(custom_image)) { |
|
| 221 gconstpointer data = purple_image_get_data(custom_image); |
|
| 222 gsize length = purple_image_get_data_size(custom_image); |
|
| 223 |
|
| 224 stream = g_memory_input_stream_new_from_data(data, (gssize)length, |
|
| 225 NULL); |
|
| 226 } |
|
| 227 } |
|
| 228 |
|
| 229 /* If there is no custom icon, fall back to checking if the buddy has an |
|
| 230 * icon set. |
|
| 231 */ |
|
| 232 if(!G_IS_INPUT_STREAM(stream)) { |
|
| 233 PurpleBuddyIcon *icon = purple_buddy_get_icon(buddy); |
|
| 234 |
|
| 235 if(icon != NULL) { |
|
| 236 stream = purple_buddy_icon_get_stream(icon); |
|
| 237 } |
|
| 238 } |
|
| 239 |
|
| 240 /* Finally if we still don't have icon, we fallback to asking the |
|
| 241 * conversation for one. |
|
| 242 */ |
|
| 243 if(!G_IS_INPUT_STREAM(stream) && PURPLE_IS_IM_CONVERSATION(conversation)) { |
|
| 244 PurpleBuddyIcon *icon = purple_im_conversation_get_icon(PURPLE_IM_CONVERSATION(conversation)); |
|
| 245 |
|
| 246 if(icon != NULL) { |
|
| 247 stream = purple_buddy_icon_get_stream(icon); |
|
| 248 } |
|
| 249 } |
|
| 250 |
|
| 251 if(G_IS_INPUT_STREAM(stream)) { |
|
| 252 ret = gdk_pixbuf_animation_new_from_stream(stream, NULL, NULL); |
|
| 253 g_clear_object(&stream); |
|
| 254 } |
|
| 255 |
|
| 256 return ret; |
|
| 257 } |
|
| 258 |
|
| 259 static void |
|
| 260 pidgin_avatar_update(PidginAvatar *avatar) { |
|
| 261 PurpleAccount *account = NULL; |
|
| 262 GdkPixbufAnimation *animation = NULL; |
|
| 263 GdkPixbuf *pixbuf = NULL; |
|
| 264 |
|
| 265 if(PURPLE_IS_BUDDY(avatar->buddy)) { |
|
| 266 animation = pidgin_avatar_find_buddy_icon(avatar->buddy, NULL); |
|
| 267 } else if(PURPLE_IS_IM_CONVERSATION(avatar->conversation)) { |
|
| 268 PurpleBuddy *buddy = NULL; |
|
| 269 const gchar *name = NULL; |
|
| 270 |
|
| 271 account = purple_conversation_get_account(avatar->conversation); |
|
| 272 |
|
| 273 name = purple_conversation_get_name(avatar->conversation); |
|
| 274 buddy = purple_blist_find_buddy(account, name); |
|
| 275 |
|
| 276 if(PURPLE_IS_BUDDY(buddy)) { |
|
| 277 animation = pidgin_avatar_find_buddy_icon(buddy, |
|
| 278 PURPLE_IM_CONVERSATION(avatar->conversation)); |
|
| 279 } |
|
| 280 } |
|
| 281 |
|
| 282 g_set_object(&avatar->animation, animation); |
|
| 283 |
|
| 284 if(GDK_IS_PIXBUF_ANIMATION(avatar->animation)) { |
|
| 285 if(avatar->animate) { |
|
| 286 pixbuf = GDK_PIXBUF(avatar->animation); |
|
| 287 } else { |
|
| 288 pixbuf = gdk_pixbuf_animation_get_static_image(avatar->animation); |
|
| 289 } |
|
| 290 } |
|
| 291 |
|
| 292 gtk_picture_set_pixbuf(GTK_PICTURE(avatar->icon), pixbuf); |
|
| 293 |
|
| 294 g_clear_object(&animation); |
|
| 295 } |
|
| 296 |
|
| 297 /****************************************************************************** |
|
| 298 * Callbacks |
334 * Callbacks |
| 299 *****************************************************************************/ |
335 *****************************************************************************/ |
| 300 static gboolean |
336 static gboolean |
| 301 pidgin_avatar_button_press_handler(G_GNUC_UNUSED GtkGestureClick *event, |
337 pidgin_avatar_button_press_handler(G_GNUC_UNUSED GtkGestureClick *event, |
| 302 G_GNUC_UNUSED gint n_press, |
338 G_GNUC_UNUSED gint n_press, |
| 438 pidgin_avatar_init(PidginAvatar *avatar) { |
474 pidgin_avatar_init(PidginAvatar *avatar) { |
| 439 GSimpleActionGroup *group = NULL; |
475 GSimpleActionGroup *group = NULL; |
| 440 |
476 |
| 441 gtk_widget_init_template(GTK_WIDGET(avatar)); |
477 gtk_widget_init_template(GTK_WIDGET(avatar)); |
| 442 |
478 |
| |
479 #if GTK_CHECK_VERSION(4,8,0) |
| |
480 gtk_picture_set_content_fit(GTK_PICTURE(avatar->icon), |
| |
481 GTK_CONTENT_FIT_SCALE_DOWN); |
| |
482 #endif |
| 443 /* Now setup our actions. */ |
483 /* Now setup our actions. */ |
| 444 group = g_simple_action_group_new(); |
484 group = g_simple_action_group_new(); |
| 445 g_action_map_add_action_entries(G_ACTION_MAP(group), actions, |
485 g_action_map_add_action_entries(G_ACTION_MAP(group), actions, |
| 446 G_N_ELEMENTS(actions), avatar); |
486 G_N_ELEMENTS(actions), avatar); |
| 447 |
487 |
| 521 g_return_if_fail(PIDGIN_IS_AVATAR(avatar)); |
561 g_return_if_fail(PIDGIN_IS_AVATAR(avatar)); |
| 522 |
562 |
| 523 avatar->animate = animate; |
563 avatar->animate = animate; |
| 524 |
564 |
| 525 if(GDK_IS_PIXBUF_ANIMATION(avatar->animation)) { |
565 if(GDK_IS_PIXBUF_ANIMATION(avatar->animation)) { |
| 526 if(avatar->animate) { |
566 if(avatar->animate && |
| 527 gtk_image_set_from_pixbuf(GTK_IMAGE(avatar->icon), |
567 !gdk_pixbuf_animation_is_static_image(avatar->animation)) { |
| 528 GDK_PIXBUF(avatar->animation)); |
568 gtk_picture_set_pixbuf(GTK_PICTURE(avatar->icon), |
| |
569 GDK_PIXBUF(avatar->animation)); |
| 529 } else { |
570 } else { |
| 530 GdkPixbuf *frame = NULL; |
571 GdkPixbuf *frame = NULL; |
| 531 |
572 |
| 532 frame = gdk_pixbuf_animation_get_static_image(avatar->animation); |
573 frame = gdk_pixbuf_animation_get_static_image(avatar->animation); |
| 533 |
574 |
| 534 gtk_image_set_from_pixbuf(GTK_IMAGE(avatar->icon), frame); |
575 gtk_picture_set_pixbuf(GTK_PICTURE(avatar->icon), frame); |
| 535 } |
576 } |
| 536 } |
577 } |
| 537 } |
578 } |
| 538 |
579 |
| 539 gboolean |
580 gboolean |