mark PurpleImageClass as private
[pidgin-git.git] / pidgin / gtkutils.c
blobc591c513b6b8bb5e6ec8fba03ed67c0a1a2195cb
1 /* pidgin
3 * Pidgin is the legal property of its developers, whose names are too numerous
4 * to list here. Please refer to the COPYRIGHT file distributed with this
5 * source distribution.
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
22 #include "internal.h"
23 #include "glibcompat.h"
24 #include "pidgin.h"
26 #ifdef _WIN32
27 # undef small
28 # include <shellapi.h>
29 #endif /*_WIN32*/
31 #include <gdk/gdkkeysyms.h>
33 #include <talkatu.h>
35 #include "conversation.h"
36 #include "debug.h"
37 #include "notify.h"
38 #include "prefs.h"
39 #include "protocol.h"
40 #include "request.h"
41 #include "signals.h"
42 #include "sound.h"
43 #include "util.h"
45 #include "gtkaccount.h"
46 #include "gtkprefs.h"
48 #include "gtkconv.h"
49 #include "gtkdialogs.h"
50 #include "pidginstock.h"
51 #include "gtkrequest.h"
52 #include "gtkutils.h"
53 #include "pidgin/minidialog.h"
55 #include "gtk3compat.h"
58 /******************************************************************************
59 * Enums
60 *****************************************************************************/
62 enum {
63 AOP_ICON_COLUMN,
64 AOP_NAME_COLUMN,
65 AOP_DATA_COLUMN,
66 AOP_COLUMN_COUNT
69 enum {
70 DND_FILE_TRANSFER,
71 DND_IM_IMAGE,
72 DND_BUDDY_ICON
75 enum {
76 COMPLETION_DISPLAYED_COLUMN, /* displayed completion value */
77 COMPLETION_BUDDY_COLUMN, /* buddy name */
78 COMPLETION_NORMALIZED_COLUMN, /* UTF-8 normalized & casefolded buddy name */
79 COMPLETION_COMPARISON_COLUMN, /* UTF-8 normalized & casefolded value for comparison */
80 COMPLETION_ACCOUNT_COLUMN, /* account */
81 COMPLETION_COLUMN_COUNT
84 /******************************************************************************
85 * Structs
86 *****************************************************************************/
88 typedef struct {
89 GtkTreeModel *model;
90 gint default_item;
91 } AopMenu;
93 typedef struct {
94 char *filename;
95 PurpleAccount *account;
96 char *who;
97 } _DndData;
99 typedef struct
101 GtkWidget *entry;
102 GtkWidget *accountopt;
104 PidginFilterBuddyCompletionEntryFunc filter_func;
105 gpointer filter_func_user_data;
107 GtkListStore *store;
108 } PidginCompletionData;
110 struct _icon_chooser {
111 GtkWidget *icon_filesel;
112 GtkWidget *icon_preview;
113 GtkWidget *icon_text;
115 void (*callback)(const char*,gpointer);
116 gpointer data;
119 struct _old_button_clicked_cb_data
121 PidginUtilMiniDialogCallback cb;
122 gpointer data;
125 /******************************************************************************
126 * Globals
127 *****************************************************************************/
129 static guint accels_save_timer = 0;
130 static GSList *minidialogs = NULL;
132 /******************************************************************************
133 * Code
134 *****************************************************************************/
135 static
136 void pidgin_window_init(GtkWindow *wnd, const char *title, guint border_width, const char *role, gboolean resizable)
138 if (title)
139 gtk_window_set_title(wnd, title);
140 #ifdef _WIN32
141 else
142 gtk_window_set_title(wnd, PIDGIN_ALERT_TITLE);
143 #endif
144 gtk_container_set_border_width(GTK_CONTAINER(wnd), border_width);
145 if (role)
146 gtk_window_set_role(wnd, role);
147 gtk_window_set_resizable(wnd, resizable);
150 GtkWidget *
151 pidgin_create_window(const char *title, guint border_width, const char *role, gboolean resizable)
153 GtkWindow *wnd = NULL;
155 wnd = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
156 pidgin_window_init(wnd, title, border_width, role, resizable);
158 return GTK_WIDGET(wnd);
161 GtkWidget *
162 pidgin_create_small_button(GtkWidget *image)
164 GtkWidget *button;
166 button = gtk_button_new();
167 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
169 /* don't allow focus on the close button */
170 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
172 gtk_widget_show(image);
174 gtk_container_add(GTK_CONTAINER(button), image);
176 return button;
179 GtkWidget *
180 pidgin_create_dialog(const char *title, guint border_width, const char *role, gboolean resizable)
182 GtkWindow *wnd = NULL;
184 wnd = GTK_WINDOW(gtk_dialog_new());
185 pidgin_window_init(wnd, title, border_width, role, resizable);
187 return GTK_WIDGET(wnd);
190 GtkWidget *
191 pidgin_create_video_widget(void)
193 GtkWidget *video = NULL;
194 GdkRGBA color = {0.0, 0.0, 0.0, 1.0};
196 video = gtk_drawing_area_new();
197 gtk_widget_override_background_color(video, GTK_STATE_FLAG_NORMAL, &color);
199 /* In order to enable client shadow decorations, GtkDialog from GTK+ 3.0
200 * uses ARGB visual which by default gets inherited by its child widgets.
201 * XVideo adaptors on the other hand often support just depth 24 and
202 * rendering video through xvimagesink onto a widget inside a GtkDialog
203 * then results in no visible output.
205 * This ensures the default system visual of the drawing area doesn't get
206 * overridden by the widget's parent.
208 gtk_widget_set_visual(video,
209 gdk_screen_get_system_visual(gtk_widget_get_screen(video)));
211 return video;
214 GtkWidget *
215 pidgin_dialog_get_vbox_with_properties(GtkDialog *dialog, gboolean homogeneous, gint spacing)
217 GtkBox *vbox = GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog)));
218 gtk_box_set_homogeneous(vbox, homogeneous);
219 gtk_box_set_spacing(vbox, spacing);
220 return GTK_WIDGET(vbox);
223 GtkWidget *pidgin_dialog_get_vbox(GtkDialog *dialog)
225 return gtk_dialog_get_content_area(GTK_DIALOG(dialog));
228 GtkWidget *pidgin_dialog_get_action_area(GtkDialog *dialog)
230 return gtk_dialog_get_action_area(GTK_DIALOG(dialog));
233 GtkWidget *pidgin_dialog_add_button(GtkDialog *dialog, const char *label,
234 GCallback callback, gpointer callbackdata)
236 GtkWidget *button = gtk_button_new_with_mnemonic(label);
237 GtkWidget *bbox = pidgin_dialog_get_action_area(dialog);
239 /* Handle stock labels if passed in until nothing calls this
240 * expecting a GtkStock button */
241 if (label != NULL) {
242 GtkStockItem item;
243 if (gtk_stock_lookup(label, &item)) {
244 g_object_set(button, "use-stock", TRUE, NULL);
248 gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
249 if (callback)
250 g_signal_connect(G_OBJECT(button), "clicked", callback, callbackdata);
251 gtk_widget_show(button);
252 return button;
255 void
256 pidgin_set_sensitive_if_input(GtkWidget *entry, GtkWidget *dialog)
258 const char *text = gtk_entry_get_text(GTK_ENTRY(entry));
259 gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog), GTK_RESPONSE_OK,
260 (*text != '\0'));
263 GtkWidget *pidgin_separator(GtkWidget *menu)
265 GtkWidget *menuitem;
267 menuitem = gtk_separator_menu_item_new();
268 gtk_widget_show(menuitem);
269 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
270 return menuitem;
273 GtkWidget *pidgin_new_check_item(GtkWidget *menu, const char *str,
274 GCallback cb, gpointer data, gboolean checked)
276 GtkWidget *menuitem;
277 menuitem = gtk_check_menu_item_new_with_mnemonic(str);
279 if (menu)
280 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
282 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), checked);
284 if (cb)
285 g_signal_connect(G_OBJECT(menuitem), "activate", cb, data);
287 gtk_widget_show_all(menuitem);
289 return menuitem;
292 GtkWidget *
293 pidgin_pixbuf_toolbar_button_from_stock(const char *icon)
295 GtkWidget *button, *image, *bbox;
297 button = gtk_toggle_button_new();
298 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
300 bbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
302 gtk_container_add (GTK_CONTAINER(button), bbox);
304 image = gtk_image_new_from_stock(icon, gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL));
305 gtk_box_pack_start(GTK_BOX(bbox), image, FALSE, FALSE, 0);
307 gtk_widget_show_all(bbox);
309 return button;
312 GtkWidget *
313 pidgin_pixbuf_button_from_stock(const char *text, const char *icon,
314 PidginButtonOrientation style)
316 GtkWidget *button, *image, *bbox, *ibox, *lbox = NULL;
318 button = gtk_button_new();
320 if (style == PIDGIN_BUTTON_HORIZONTAL) {
321 bbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
322 ibox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
323 if (text)
324 lbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
325 } else {
326 bbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
327 ibox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
328 if (text)
329 lbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
332 gtk_container_add(GTK_CONTAINER(button), bbox);
334 if (icon) {
335 gtk_box_pack_start(GTK_BOX(bbox), ibox, TRUE, TRUE, 0);
336 image = gtk_image_new_from_stock(icon, GTK_ICON_SIZE_BUTTON);
337 gtk_box_pack_end(GTK_BOX(ibox), image, FALSE, TRUE, 0);
340 if (text) {
341 GtkLabel *label;
343 gtk_box_pack_start(GTK_BOX(bbox), lbox, TRUE, TRUE, 0);
344 label = GTK_LABEL(gtk_label_new(NULL));
345 gtk_label_set_text_with_mnemonic(label, text);
346 gtk_label_set_mnemonic_widget(label, button);
347 gtk_box_pack_start(GTK_BOX(lbox), GTK_WIDGET(label),
348 FALSE, TRUE, 0);
349 pidgin_set_accessible_label(button, label);
352 gtk_widget_show_all(bbox);
354 return button;
358 GtkWidget *pidgin_new_menu_item(GtkWidget *menu, const char *mnemonic,
359 const char *icon, GCallback cb, gpointer data)
361 GtkWidget *menuitem;
362 GtkWidget *box;
363 GtkWidget *label;
365 box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, PIDGIN_HIG_BOX_SPACE);
367 menuitem = gtk_menu_item_new();
369 if (cb)
370 g_signal_connect(G_OBJECT(menuitem), "activate", cb, data);
372 if (icon) {
373 GtkWidget *image;
374 image = gtk_image_new_from_stock(icon, GTK_ICON_SIZE_MENU);
375 gtk_container_add(GTK_CONTAINER(box), image);
378 label = gtk_label_new_with_mnemonic(mnemonic);
379 gtk_container_add(GTK_CONTAINER(box), label);
381 gtk_container_add(GTK_CONTAINER(menuitem), box);
383 if (menu)
384 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
386 gtk_widget_show_all(menuitem);
388 return menuitem;
391 GtkWidget *
392 pidgin_make_frame(GtkWidget *parent, const char *title)
394 GtkWidget *vbox, *vbox2, *hbox;
395 GtkLabel *label;
396 char *labeltitle;
398 vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, PIDGIN_HIG_BOX_SPACE);
399 gtk_box_pack_start(GTK_BOX(parent), vbox, FALSE, FALSE, 0);
400 gtk_widget_show(vbox);
402 label = GTK_LABEL(gtk_label_new(NULL));
404 labeltitle = g_strdup_printf("<span weight=\"bold\">%s</span>", title);
405 gtk_label_set_markup(label, labeltitle);
406 g_free(labeltitle);
408 gtk_label_set_xalign(GTK_LABEL(label), 0);
409 gtk_label_set_yalign(GTK_LABEL(label), 0);
410 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(label), FALSE, FALSE, 0);
411 gtk_widget_show(GTK_WIDGET(label));
412 pidgin_set_accessible_label(vbox, label);
414 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, PIDGIN_HIG_BOX_SPACE);
415 gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
416 gtk_widget_show(hbox);
418 label = GTK_LABEL(gtk_label_new(" "));
419 gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(label), FALSE, FALSE, 0);
420 gtk_widget_show(GTK_WIDGET(label));
422 vbox2 = gtk_box_new(GTK_ORIENTATION_VERTICAL, PIDGIN_HIG_BOX_SPACE);
423 gtk_box_pack_start(GTK_BOX(hbox), vbox2, FALSE, FALSE, 0);
424 gtk_widget_show(vbox2);
426 g_object_set_data(G_OBJECT(vbox2), "main-vbox", vbox);
428 return vbox2;
431 static gpointer
432 aop_option_menu_get_selected(GtkWidget *optmenu)
434 gpointer data = NULL;
435 GtkTreeIter iter;
437 g_return_val_if_fail(optmenu != NULL, NULL);
439 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(optmenu), &iter))
440 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)),
441 &iter, AOP_DATA_COLUMN, &data, -1);
443 return data;
446 static void
447 aop_menu_cb(GtkWidget *optmenu, GCallback cb)
449 if (cb != NULL) {
450 ((void (*)(GtkWidget *, gpointer, gpointer))cb)(optmenu,
451 aop_option_menu_get_selected(optmenu),
452 g_object_get_data(G_OBJECT(optmenu), "user_data"));
456 static void
457 aop_option_menu_replace_menu(GtkWidget *optmenu, AopMenu *new_aop_menu)
459 gtk_combo_box_set_model(GTK_COMBO_BOX(optmenu), new_aop_menu->model);
460 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), new_aop_menu->default_item);
461 g_free(new_aop_menu);
464 static GdkPixbuf *
465 pidgin_create_icon_from_protocol(PurpleProtocol *protocol, PidginProtocolIconSize size, PurpleAccount *account)
467 const char *protoname = NULL;
468 char *tmp;
469 char *filename = NULL;
470 GdkPixbuf *pixbuf;
472 protoname = purple_protocol_class_list_icon(protocol, account, NULL);
473 if (protoname == NULL)
474 return NULL;
477 * Status icons will be themeable too, and then it will look up
478 * protoname from the theme
480 tmp = g_strconcat("im-", protoname, ".png", NULL);
482 filename = g_build_filename(PURPLE_DATADIR,
483 "pidgin", "icons", "hicolor",
484 (size == PIDGIN_PROTOCOL_ICON_SMALL) ? "16x16" :
485 ((size == PIDGIN_PROTOCOL_ICON_MEDIUM) ? "22x22" :
486 "48x48"),
487 "apps", tmp, NULL);
488 g_free(tmp);
490 pixbuf = pidgin_pixbuf_new_from_file(filename);
491 g_free(filename);
493 return pixbuf;
496 static GtkWidget *
497 aop_option_menu_new(AopMenu *aop_menu, GCallback cb, gpointer user_data)
499 GtkWidget *optmenu = NULL;
500 GtkCellRenderer *cr = NULL;
502 optmenu = gtk_combo_box_new();
503 gtk_widget_show(optmenu);
504 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(optmenu), cr = gtk_cell_renderer_pixbuf_new(), FALSE);
505 gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(optmenu), cr, "pixbuf", AOP_ICON_COLUMN);
506 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(optmenu), cr = gtk_cell_renderer_text_new(), TRUE);
507 gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(optmenu), cr, "text", AOP_NAME_COLUMN);
509 aop_option_menu_replace_menu(optmenu, aop_menu);
510 g_object_set_data(G_OBJECT(optmenu), "user_data", user_data);
512 g_signal_connect(G_OBJECT(optmenu), "changed", G_CALLBACK(aop_menu_cb), cb);
514 return optmenu;
517 static void
518 aop_option_menu_select_by_data(GtkWidget *optmenu, gpointer data)
520 GtkTreeModel *model;
521 GtkTreeIter iter;
522 gpointer iter_data;
523 model = gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu));
524 if (gtk_tree_model_get_iter_first(model, &iter)) {
525 do {
526 gtk_tree_model_get(model, &iter, AOP_DATA_COLUMN, &iter_data, -1);
527 if (iter_data == data) {
528 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(optmenu), &iter);
529 return;
531 } while (gtk_tree_model_iter_next(model, &iter));
535 static AopMenu *
536 create_protocols_menu(const char *default_proto_id)
538 AopMenu *aop_menu = NULL;
539 PurpleProtocol *protocol;
540 GdkPixbuf *pixbuf = NULL;
541 GtkTreeIter iter;
542 GtkListStore *ls;
543 GList *list, *p;
544 int i;
546 ls = gtk_list_store_new(AOP_COLUMN_COUNT, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_POINTER);
548 aop_menu = g_malloc0(sizeof(AopMenu));
549 aop_menu->default_item = 0;
550 aop_menu->model = GTK_TREE_MODEL(ls);
552 list = purple_protocols_get_all();
554 for (p = list, i = 0;
555 p != NULL;
556 p = p->next, i++) {
558 protocol = PURPLE_PROTOCOL(p->data);
560 pixbuf = pidgin_create_icon_from_protocol(protocol, PIDGIN_PROTOCOL_ICON_SMALL, NULL);
562 gtk_list_store_append(ls, &iter);
563 gtk_list_store_set(ls, &iter,
564 AOP_ICON_COLUMN, pixbuf,
565 AOP_NAME_COLUMN, purple_protocol_get_name(protocol),
566 AOP_DATA_COLUMN, purple_protocol_get_id(protocol),
567 -1);
569 if (pixbuf)
570 g_object_unref(pixbuf);
572 if (default_proto_id != NULL && purple_strequal(purple_protocol_get_id(protocol), default_proto_id))
573 aop_menu->default_item = i;
575 g_list_free(list);
577 return aop_menu;
580 GtkWidget *
581 pidgin_protocol_option_menu_new(const char *id, GCallback cb,
582 gpointer user_data)
584 return aop_option_menu_new(create_protocols_menu(id), cb, user_data);
587 const char *
588 pidgin_protocol_option_menu_get_selected(GtkWidget *optmenu)
590 return (const char *)aop_option_menu_get_selected(optmenu);
593 void
594 pidgin_save_accels_cb(GtkAccelGroup *accel_group, guint arg1,
595 GdkModifierType arg2, GClosure *arg3,
596 gpointer data)
598 purple_debug(PURPLE_DEBUG_MISC, "accels",
599 "accel changed, scheduling save.\n");
601 if (!accels_save_timer)
602 accels_save_timer = g_timeout_add_seconds(5, pidgin_save_accels,
603 NULL);
606 gboolean
607 pidgin_save_accels(gpointer data)
609 char *filename = NULL;
611 filename = g_build_filename(purple_user_dir(), G_DIR_SEPARATOR_S,
612 "accels", NULL);
613 purple_debug(PURPLE_DEBUG_MISC, "accels", "saving accels to %s\n", filename);
614 gtk_accel_map_save(filename);
615 g_free(filename);
617 accels_save_timer = 0;
618 return FALSE;
621 void
622 pidgin_load_accels()
624 char *filename = NULL;
626 filename = g_build_filename(purple_user_dir(), G_DIR_SEPARATOR_S,
627 "accels", NULL);
628 gtk_accel_map_load(filename);
629 g_free(filename);
632 static void
633 show_retrieveing_info(PurpleConnection *conn, const char *name)
635 PurpleNotifyUserInfo *info = purple_notify_user_info_new();
636 purple_notify_user_info_add_pair_plaintext(info, _("Information"), _("Retrieving..."));
637 purple_notify_userinfo(conn, name, info, NULL, NULL);
638 purple_notify_user_info_destroy(info);
641 void pidgin_retrieve_user_info(PurpleConnection *conn, const char *name)
643 show_retrieveing_info(conn, name);
644 purple_serv_get_info(conn, name);
647 void pidgin_retrieve_user_info_in_chat(PurpleConnection *conn, const char *name, int chat)
649 char *who = NULL;
650 PurpleProtocol *protocol = NULL;
652 if (chat < 0) {
653 pidgin_retrieve_user_info(conn, name);
654 return;
657 protocol = purple_connection_get_protocol(conn);
658 if (protocol != NULL)
659 who = purple_protocol_chat_iface_get_user_real_name(protocol, conn, chat, name);
661 pidgin_retrieve_user_info(conn, who ? who : name);
662 g_free(who);
665 gboolean
666 pidgin_parse_x_im_contact(const char *msg, gboolean all_accounts,
667 PurpleAccount **ret_account, char **ret_protocol,
668 char **ret_username, char **ret_alias)
670 char *protocol = NULL;
671 char *username = NULL;
672 char *alias = NULL;
673 char *str;
674 char *s;
675 gboolean valid;
677 g_return_val_if_fail(msg != NULL, FALSE);
678 g_return_val_if_fail(ret_protocol != NULL, FALSE);
679 g_return_val_if_fail(ret_username != NULL, FALSE);
681 s = str = g_strdup(msg);
683 while (*s != '\r' && *s != '\n' && *s != '\0')
685 char *key, *value;
687 key = s;
689 /* Grab the key */
690 while (*s != '\r' && *s != '\n' && *s != '\0' && *s != ' ')
691 s++;
693 if (*s == '\r') s++;
695 if (*s == '\n')
697 s++;
698 continue;
701 if (*s != '\0') *s++ = '\0';
703 /* Clear past any whitespace */
704 while (*s != '\0' && *s == ' ')
705 s++;
707 /* Now let's grab until the end of the line. */
708 value = s;
710 while (*s != '\r' && *s != '\n' && *s != '\0')
711 s++;
713 if (*s == '\r') *s++ = '\0';
714 if (*s == '\n') *s++ = '\0';
716 if (strchr(key, ':') != NULL)
718 if (!g_ascii_strcasecmp(key, "X-IM-Username:"))
719 username = g_strdup(value);
720 else if (!g_ascii_strcasecmp(key, "X-IM-Protocol:"))
721 protocol = g_strdup(value);
722 else if (!g_ascii_strcasecmp(key, "X-IM-Alias:"))
723 alias = g_strdup(value);
727 if (username != NULL && protocol != NULL)
729 valid = TRUE;
731 *ret_username = username;
732 *ret_protocol = protocol;
734 if (ret_alias != NULL)
735 *ret_alias = alias;
737 /* Check for a compatible account. */
738 if (ret_account != NULL)
740 GList *list;
741 PurpleAccount *account = NULL;
742 GList *l;
743 const char *protoname;
745 if (all_accounts)
746 list = purple_accounts_get_all();
747 else
748 list = purple_connections_get_all();
750 for (l = list; l != NULL; l = l->next)
752 PurpleConnection *gc;
753 PurpleProtocol *proto = NULL;
755 if (all_accounts)
757 account = (PurpleAccount *)l->data;
759 proto = purple_protocols_find(
760 purple_account_get_protocol_id(account));
762 if (proto == NULL)
764 account = NULL;
766 continue;
769 else
771 gc = (PurpleConnection *)l->data;
772 account = purple_connection_get_account(gc);
774 proto = purple_connection_get_protocol(gc);
777 protoname = purple_protocol_class_list_icon(proto, account, NULL);
779 if (purple_strequal(protoname, protocol))
780 break;
782 account = NULL;
785 /* Special case for AIM and ICQ */
786 if (account == NULL && (purple_strequal(protocol, "aim") ||
787 purple_strequal(protocol, "icq")))
789 for (l = list; l != NULL; l = l->next)
791 PurpleConnection *gc;
792 PurpleProtocol *proto = NULL;
794 if (all_accounts)
796 account = (PurpleAccount *)l->data;
798 proto = purple_protocols_find(
799 purple_account_get_protocol_id(account));
801 if (proto == NULL)
803 account = NULL;
805 continue;
808 else
810 gc = (PurpleConnection *)l->data;
811 account = purple_connection_get_account(gc);
813 proto = purple_connection_get_protocol(gc);
816 protoname = purple_protocol_class_list_icon(proto, account, NULL);
818 if (purple_strequal(protoname, "aim") || purple_strequal(protoname, "icq"))
819 break;
821 account = NULL;
825 *ret_account = account;
828 else
830 valid = FALSE;
832 g_free(username);
833 g_free(protocol);
834 g_free(alias);
837 g_free(str);
839 return valid;
842 void
843 pidgin_set_accessible_label(GtkWidget *w, GtkLabel *l)
845 AtkObject *acc;
846 const gchar *label_text;
847 const gchar *existing_name;
849 acc = gtk_widget_get_accessible (w);
851 /* If this object has no name, set it's name with the label text */
852 existing_name = atk_object_get_name (acc);
853 if (!existing_name) {
854 label_text = gtk_label_get_text(l);
855 if (label_text)
856 atk_object_set_name (acc, label_text);
859 pidgin_set_accessible_relations(w, l);
862 void
863 pidgin_set_accessible_relations (GtkWidget *w, GtkLabel *l)
865 AtkObject *acc, *label;
866 AtkObject *rel_obj[1];
867 AtkRelationSet *set;
868 AtkRelation *relation;
870 acc = gtk_widget_get_accessible (w);
871 label = gtk_widget_get_accessible(GTK_WIDGET(l));
873 /* Make sure mnemonics work */
874 gtk_label_set_mnemonic_widget(l, w);
876 /* Create the labeled-by relation */
877 set = atk_object_ref_relation_set (acc);
878 rel_obj[0] = label;
879 relation = atk_relation_new (rel_obj, 1, ATK_RELATION_LABELLED_BY);
880 atk_relation_set_add (set, relation);
881 g_object_unref (relation);
882 g_object_unref(set);
884 /* Create the label-for relation */
885 set = atk_object_ref_relation_set (label);
886 rel_obj[0] = acc;
887 relation = atk_relation_new (rel_obj, 1, ATK_RELATION_LABEL_FOR);
888 atk_relation_set_add (set, relation);
889 g_object_unref (relation);
890 g_object_unref(set);
893 void
894 pidgin_menu_position_func_helper(GtkMenu *menu,
895 gint *x,
896 gint *y,
897 gboolean *push_in,
898 gpointer data)
900 GtkWidget *widget;
901 GtkRequisition requisition;
902 GdkScreen *screen;
903 GdkRectangle monitor;
904 gint monitor_num;
905 gint space_left, space_right, space_above, space_below;
906 gboolean rtl;
908 g_return_if_fail(GTK_IS_MENU(menu));
910 widget = GTK_WIDGET(menu);
911 screen = gtk_widget_get_screen(widget);
912 rtl = (gtk_widget_get_direction(widget) == GTK_TEXT_DIR_RTL);
915 * We need the requisition to figure out the right place to
916 * popup the menu. In fact, we always need to ask here, since
917 * if a size_request was queued while we weren't popped up,
918 * the requisition won't have been recomputed yet.
920 gtk_widget_get_preferred_size(widget, NULL, &requisition);
922 monitor_num = gdk_screen_get_monitor_at_point (screen, *x, *y);
924 *push_in = FALSE;
927 * The placement of popup menus horizontally works like this (with
928 * RTL in parentheses)
930 * - If there is enough room to the right (left) of the mouse cursor,
931 * position the menu there.
933 * - Otherwise, if if there is enough room to the left (right) of the
934 * mouse cursor, position the menu there.
936 * - Otherwise if the menu is smaller than the monitor, position it
937 * on the side of the mouse cursor that has the most space available
939 * - Otherwise (if there is simply not enough room for the menu on the
940 * monitor), position it as far left (right) as possible.
942 * Positioning in the vertical direction is similar: first try below
943 * mouse cursor, then above.
945 gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
947 space_left = *x - monitor.x;
948 space_right = monitor.x + monitor.width - *x - 1;
949 space_above = *y - monitor.y;
950 space_below = monitor.y + monitor.height - *y - 1;
952 /* position horizontally */
954 if (requisition.width <= space_left ||
955 requisition.width <= space_right)
957 if ((rtl && requisition.width <= space_left) ||
958 (!rtl && requisition.width > space_right))
960 /* position left */
961 *x = *x - requisition.width + 1;
964 /* x is clamped on-screen further down */
966 else if (requisition.width <= monitor.width)
968 /* the menu is too big to fit on either side of the mouse
969 * cursor, but smaller than the monitor. Position it on
970 * the side that has the most space
972 if (space_left > space_right)
974 /* left justify */
975 *x = monitor.x;
977 else
979 /* right justify */
980 *x = monitor.x + monitor.width - requisition.width;
983 else /* menu is simply too big for the monitor */
985 if (rtl)
987 /* right justify */
988 *x = monitor.x + monitor.width - requisition.width;
990 else
992 /* left justify */
993 *x = monitor.x;
997 /* Position vertically. The algorithm is the same as above, but
998 * simpler because we don't have to take RTL into account.
1001 if (requisition.height <= space_above ||
1002 requisition.height <= space_below)
1004 if (requisition.height > space_below) {
1005 *y = *y - requisition.height + 1;
1008 *y = CLAMP (*y, monitor.y,
1009 monitor.y + monitor.height - requisition.height);
1011 else if (requisition.height > space_below &&
1012 requisition.height > space_above)
1014 if (space_below >= space_above)
1015 *y = monitor.y + monitor.height - requisition.height;
1016 else
1017 *y = monitor.y;
1019 else
1021 *y = monitor.y;
1026 #if !GTK_CHECK_VERSION(3,22,0)
1027 static void
1028 pidgin_treeview_popup_menu_position_func(GtkMenu *menu,
1029 gint *x,
1030 gint *y,
1031 gboolean *push_in,
1032 gpointer data)
1034 GtkWidget *widget = GTK_WIDGET(data);
1035 GtkTreeView *tv = GTK_TREE_VIEW(data);
1036 GtkTreePath *path;
1037 GtkTreeViewColumn *col;
1038 GdkRectangle rect;
1040 gdk_window_get_origin (gtk_widget_get_window(widget), x, y);
1041 gtk_tree_view_get_cursor (tv, &path, &col);
1042 gtk_tree_view_get_cell_area (tv, path, col, &rect);
1044 *x += rect.x+rect.width;
1045 *y += rect.y + rect.height;
1046 pidgin_menu_position_func_helper(menu, x, y, push_in, data);
1048 #endif
1051 void
1052 pidgin_menu_popup_at_treeview_selection(GtkWidget *menu, GtkWidget *treeview)
1054 #if GTK_CHECK_VERSION(3,22,0)
1055 GtkTreePath *path;
1056 GtkTreeViewColumn *column;
1057 GdkWindow *bin_window;
1058 GdkRectangle rect;
1060 gtk_tree_view_get_cursor(GTK_TREE_VIEW(treeview), &path, &column);
1061 g_return_if_fail(path != NULL);
1062 if (column == NULL)
1063 column = gtk_tree_view_get_column(GTK_TREE_VIEW(treeview), 0);
1064 bin_window = gtk_tree_view_get_bin_window(GTK_TREE_VIEW(treeview));
1065 gtk_tree_view_get_cell_area(GTK_TREE_VIEW(treeview), path, column, &rect);
1066 gtk_menu_popup_at_rect(GTK_MENU(menu), bin_window, &rect,
1067 GDK_GRAVITY_SOUTH_WEST, GDK_GRAVITY_NORTH_WEST,
1068 NULL);
1070 gtk_tree_path_free(path);
1071 #else
1072 gtk_menu_popup(GTK_MENU(menu), NULL, NULL,
1073 pidgin_treeview_popup_menu_position_func, treeview,
1074 0, GDK_CURRENT_TIME);
1075 #endif
1079 static void dnd_image_ok_callback(_DndData *data, int choice)
1081 const gchar *shortname;
1082 gchar *filedata;
1083 size_t size;
1084 GStatBuf st;
1085 GError *err = NULL;
1086 PurpleConversation *conv;
1087 PidginConversation *gtkconv;
1088 PurpleBuddy *buddy;
1089 PurpleContact *contact;
1090 PurpleImage *img;
1092 switch (choice) {
1093 case DND_BUDDY_ICON:
1094 if (g_stat(data->filename, &st)) {
1095 char *str;
1097 str = g_strdup_printf(_("The following error has occurred loading %s: %s"),
1098 data->filename, g_strerror(errno));
1099 purple_notify_error(NULL, NULL,
1100 _("Failed to load image"), str, NULL);
1101 g_free(str);
1103 break;
1106 buddy = purple_blist_find_buddy(data->account, data->who);
1107 if (!buddy) {
1108 purple_debug_info("custom-icon", "You can only set custom icons for people on your buddylist.\n");
1109 break;
1111 contact = purple_buddy_get_contact(buddy);
1112 purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode*)contact, data->filename);
1113 break;
1114 case DND_FILE_TRANSFER:
1115 purple_serv_send_file(purple_account_get_connection(data->account), data->who, data->filename);
1116 break;
1117 case DND_IM_IMAGE:
1118 conv = PURPLE_CONVERSATION(purple_im_conversation_new(data->account, data->who));
1119 gtkconv = PIDGIN_CONVERSATION(conv);
1121 if (!g_file_get_contents(data->filename, &filedata, &size,
1122 &err)) {
1123 char *str;
1125 str = g_strdup_printf(_("The following error has occurred loading %s: %s"), data->filename, err->message);
1126 purple_notify_error(NULL, NULL,
1127 _("Failed to load image"), str, NULL);
1129 g_error_free(err);
1130 g_free(str);
1132 break;
1134 shortname = strrchr(data->filename, G_DIR_SEPARATOR);
1135 shortname = shortname ? shortname + 1 : data->filename;
1136 img = purple_image_new_from_data((guint8 *)filedata, size);
1137 purple_image_set_friendly_filename(img, shortname);
1139 # warning fix this when talkatu has a way to programmatically insert an image
1140 // pidgin_webview_insert_image(PIDGIN_WEBVIEW(gtkconv->entry), img);
1141 g_object_unref(img);
1143 break;
1145 g_free(data->filename);
1146 g_free(data->who);
1147 g_free(data);
1150 static void dnd_image_cancel_callback(_DndData *data, int choice)
1152 g_free(data->filename);
1153 g_free(data->who);
1154 g_free(data);
1157 static void dnd_set_icon_ok_cb(_DndData *data)
1159 dnd_image_ok_callback(data, DND_BUDDY_ICON);
1162 static void dnd_set_icon_cancel_cb(_DndData *data)
1164 g_free(data->filename);
1165 g_free(data->who);
1166 g_free(data);
1169 static void
1170 pidgin_dnd_file_send_image(PurpleAccount *account, const gchar *who,
1171 const gchar *filename)
1173 PurpleConnection *gc = purple_account_get_connection(account);
1174 PurpleProtocol *protocol = NULL;
1175 _DndData *data = g_malloc(sizeof(_DndData));
1176 gboolean ft = FALSE, im = FALSE;
1178 data->who = g_strdup(who);
1179 data->filename = g_strdup(filename);
1180 data->account = account;
1182 if (gc)
1183 protocol = purple_connection_get_protocol(gc);
1185 if (!(purple_connection_get_flags(gc) & PURPLE_CONNECTION_FLAG_NO_IMAGES))
1186 im = TRUE;
1188 if (protocol && PURPLE_IS_PROTOCOL_XFER(protocol)) {
1189 PurpleProtocolXferInterface *iface =
1190 PURPLE_PROTOCOL_XFER_GET_IFACE(protocol);
1192 if(iface->can_receive) {
1193 ft = purple_protocol_xfer_can_receive(
1194 PURPLE_PROTOCOL_XFER(protocol),
1195 gc, who);
1196 } else {
1197 ft = (iface->send_file) ? TRUE : FALSE;
1201 if (im && ft) {
1202 purple_request_choice(NULL, NULL,
1203 _("You have dragged an image"),
1204 _("You can send this image as a file "
1205 "transfer, embed it into this message, "
1206 "or use it as the buddy icon for this user."),
1207 (gpointer)DND_FILE_TRANSFER, _("OK"),
1208 (GCallback)dnd_image_ok_callback, _("Cancel"),
1209 (GCallback)dnd_image_cancel_callback,
1210 purple_request_cpar_from_account(account), data,
1211 _("Set as buddy icon"), DND_BUDDY_ICON,
1212 _("Send image file"), DND_FILE_TRANSFER,
1213 _("Insert in message"), DND_IM_IMAGE,
1214 NULL);
1215 } else if (!(im || ft)) {
1216 purple_request_yes_no(NULL, NULL, _("You have dragged an image"),
1217 _("Would you like to set it as the buddy icon for this user?"),
1218 PURPLE_DEFAULT_ACTION_NONE,
1219 purple_request_cpar_from_account(account),
1220 data, (GCallback)dnd_set_icon_ok_cb, (GCallback)dnd_set_icon_cancel_cb);
1221 } else {
1222 purple_request_choice(NULL, NULL,
1223 _("You have dragged an image"),
1224 (ft ? _("You can send this image as a file transfer, or use it as the buddy icon for this user.") :
1225 _("You can insert this image into this message, or use it as the buddy icon for this user")),
1226 GINT_TO_POINTER(ft ? DND_FILE_TRANSFER : DND_IM_IMAGE),
1227 _("OK"), (GCallback)dnd_image_ok_callback,
1228 _("Cancel"), (GCallback)dnd_image_cancel_callback,
1229 purple_request_cpar_from_account(account),
1230 data,
1231 _("Set as buddy icon"), DND_BUDDY_ICON,
1232 (ft ? _("Send image file") : _("Insert in message")), (ft ? DND_FILE_TRANSFER : DND_IM_IMAGE),
1233 NULL);
1238 #ifndef _WIN32
1239 static void
1240 pidgin_dnd_file_send_desktop(PurpleAccount *account, const gchar *who,
1241 const gchar *filename)
1243 gchar *name;
1244 gchar *type;
1245 gchar *url;
1246 GKeyFile *desktop_file;
1247 PurpleConversation *conv;
1248 PidginConversation *gtkconv;
1249 GError *error = NULL;
1251 desktop_file = g_key_file_new();
1253 if (!g_key_file_load_from_file(desktop_file, filename, G_KEY_FILE_NONE, &error)) {
1254 if (error) {
1255 purple_debug_warning("D&D", "Failed to load %s: %s\n",
1256 filename, error->message);
1257 g_error_free(error);
1259 return;
1262 name = g_key_file_get_string(desktop_file, G_KEY_FILE_DESKTOP_GROUP,
1263 G_KEY_FILE_DESKTOP_KEY_NAME, &error);
1264 if (error) {
1265 purple_debug_warning("D&D", "Failed to read the Name from a desktop file: %s\n",
1266 error->message);
1267 g_error_free(error);
1271 type = g_key_file_get_string(desktop_file, G_KEY_FILE_DESKTOP_GROUP,
1272 G_KEY_FILE_DESKTOP_KEY_TYPE, &error);
1273 if (error) {
1274 purple_debug_warning("D&D", "Failed to read the Type from a desktop file: %s\n",
1275 error->message);
1276 g_error_free(error);
1280 url = g_key_file_get_string(desktop_file, G_KEY_FILE_DESKTOP_GROUP,
1281 G_KEY_FILE_DESKTOP_KEY_URL, &error);
1282 if (error) {
1283 purple_debug_warning("D&D", "Failed to read the Type from a desktop file: %s\n",
1284 error->message);
1285 g_error_free(error);
1290 /* If any of this is null, do nothing. */
1291 if (!name || !type || url) {
1292 g_free(type);
1293 g_free(name);
1294 g_free(url);
1296 return;
1299 /* I don't know if we really want to do anything here. Most of
1300 * the desktop item types are crap like "MIME Type" (I have no
1301 * clue how that would be a desktop item) and "Comment"...
1302 * nothing we can really send. The only logical one is
1303 * "Application," but do we really want to send a binary and
1304 * nothing else? Probably not. I'll just give an error and
1305 * return. */
1306 /* The original patch sent the icon used by the launcher. That's probably wrong */
1307 if (purple_strequal(type, "Link")) {
1308 purple_notify_error(NULL, NULL, _("Cannot send launcher"),
1309 _("You dragged a desktop launcher. Most "
1310 "likely you wanted to send the target "
1311 "of this launcher instead of this "
1312 "launcher itself."), NULL);
1314 } else {
1315 GtkTextBuffer *buffer = NULL;
1316 GtkTextMark *mark = NULL;
1317 GtkTextIter iter;
1319 conv = PURPLE_CONVERSATION(purple_im_conversation_new(account, who));
1320 gtkconv = PIDGIN_CONVERSATION(conv);
1322 buffer = talkatu_editor_get_buffer(TALKATU_EDITOR(gtkconv->editor));
1323 mark = gtk_text_buffer_get_insert(buffer);
1325 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1327 talkatu_buffer_insert_link(TALKATU_BUFFER(buffer), &iter, name, url);
1330 g_free(type);
1331 g_free(name);
1332 g_free(url);
1334 #endif /* _WIN32 */
1336 void
1337 pidgin_dnd_file_manage(GtkSelectionData *sd, PurpleAccount *account, const char *who)
1339 GdkPixbuf *pb;
1340 GList *files = purple_uri_list_extract_filenames((const gchar *) gtk_selection_data_get_data(sd));
1341 PurpleConnection *gc = purple_account_get_connection(account);
1342 gchar *filename = NULL;
1343 gchar *basename = NULL;
1345 g_return_if_fail(account != NULL);
1346 g_return_if_fail(who != NULL);
1348 for ( ; files; files = g_list_delete_link(files, files)) {
1349 g_free(filename);
1350 g_free(basename);
1352 filename = files->data;
1353 basename = g_path_get_basename(filename);
1355 /* XXX - Make ft API support creating a transfer with more than one file */
1356 if (!g_file_test(filename, G_FILE_TEST_EXISTS)) {
1357 continue;
1360 /* XXX - make ft api suupport sending a directory */
1361 /* Are we dealing with a directory? */
1362 if (g_file_test(filename, G_FILE_TEST_IS_DIR)) {
1363 char *str, *str2;
1365 str = g_strdup_printf(_("Cannot send folder %s."), basename);
1366 str2 = g_strdup_printf(_("%s cannot transfer a folder. You will need to send the files within individually."), PIDGIN_NAME);
1368 purple_notify_error(NULL, NULL, str, str2,
1369 purple_request_cpar_from_connection(gc));
1371 g_free(str);
1372 g_free(str2);
1373 continue;
1376 /* Are we dealing with an image? */
1377 pb = pidgin_pixbuf_new_from_file(filename);
1378 if (pb) {
1379 pidgin_dnd_file_send_image(account, who, filename);
1381 g_object_unref(G_OBJECT(pb));
1383 continue;
1386 #ifndef _WIN32
1387 /* Are we trying to send a .desktop file? */
1388 else if (purple_str_has_suffix(basename, ".desktop")) {
1389 pidgin_dnd_file_send_desktop(account, who, filename);
1391 continue;
1393 #endif /* _WIN32 */
1395 /* Everything is fine, let's send */
1396 purple_serv_send_file(gc, who, filename);
1399 g_free(filename);
1400 g_free(basename);
1403 void pidgin_buddy_icon_get_scale_size(GdkPixbuf *buf, PurpleBuddyIconSpec *spec, PurpleBuddyIconScaleFlags rules, int *width, int *height)
1405 *width = gdk_pixbuf_get_width(buf);
1406 *height = gdk_pixbuf_get_height(buf);
1408 if ((spec == NULL) || !(spec->scale_rules & rules))
1409 return;
1411 purple_buddy_icon_spec_get_scaled_size(spec, width, height);
1413 /* and now for some arbitrary sanity checks */
1414 if(*width > 100)
1415 *width = 100;
1416 if(*height > 100)
1417 *height = 100;
1420 GdkPixbuf * pidgin_create_status_icon(PurpleStatusPrimitive prim, GtkWidget *w, const char *size)
1422 GtkIconSize icon_size = gtk_icon_size_from_name(size);
1423 GdkPixbuf *pixbuf = NULL;
1424 const char *stock = pidgin_stock_id_from_status_primitive(prim);
1426 pixbuf = gtk_widget_render_icon (w, stock ? stock : PIDGIN_STOCK_STATUS_AVAILABLE,
1427 icon_size, "GtkWidget");
1428 return pixbuf;
1431 static const char *
1432 stock_id_from_status_primitive_idle(PurpleStatusPrimitive prim, gboolean idle)
1434 const char *stock = NULL;
1435 switch (prim) {
1436 case PURPLE_STATUS_UNSET:
1437 stock = NULL;
1438 break;
1439 case PURPLE_STATUS_UNAVAILABLE:
1440 stock = idle ? PIDGIN_STOCK_STATUS_BUSY_I : PIDGIN_STOCK_STATUS_BUSY;
1441 break;
1442 case PURPLE_STATUS_AWAY:
1443 stock = idle ? PIDGIN_STOCK_STATUS_AWAY_I : PIDGIN_STOCK_STATUS_AWAY;
1444 break;
1445 case PURPLE_STATUS_EXTENDED_AWAY:
1446 stock = idle ? PIDGIN_STOCK_STATUS_XA_I : PIDGIN_STOCK_STATUS_XA;
1447 break;
1448 case PURPLE_STATUS_INVISIBLE:
1449 stock = PIDGIN_STOCK_STATUS_INVISIBLE;
1450 break;
1451 case PURPLE_STATUS_OFFLINE:
1452 stock = idle ? PIDGIN_STOCK_STATUS_OFFLINE_I : PIDGIN_STOCK_STATUS_OFFLINE;
1453 break;
1454 default:
1455 stock = idle ? PIDGIN_STOCK_STATUS_AVAILABLE_I : PIDGIN_STOCK_STATUS_AVAILABLE;
1456 break;
1458 return stock;
1461 const char *
1462 pidgin_stock_id_from_status_primitive(PurpleStatusPrimitive prim)
1464 return stock_id_from_status_primitive_idle(prim, FALSE);
1467 const char *
1468 pidgin_stock_id_from_presence(PurplePresence *presence)
1470 PurpleStatus *status;
1471 PurpleStatusType *type;
1472 PurpleStatusPrimitive prim;
1473 gboolean idle;
1475 g_return_val_if_fail(presence, NULL);
1477 status = purple_presence_get_active_status(presence);
1478 type = purple_status_get_status_type(status);
1479 prim = purple_status_type_get_primitive(type);
1481 idle = purple_presence_is_idle(presence);
1483 return stock_id_from_status_primitive_idle(prim, idle);
1486 GdkPixbuf *
1487 pidgin_create_protocol_icon(PurpleAccount *account, PidginProtocolIconSize size)
1489 PurpleProtocol *protocol;
1491 g_return_val_if_fail(account != NULL, NULL);
1493 protocol = purple_protocols_find(purple_account_get_protocol_id(account));
1494 if (protocol == NULL)
1495 return NULL;
1496 return pidgin_create_icon_from_protocol(protocol, size, account);
1499 static void
1500 menu_action_cb(GtkMenuItem *item, gpointer object)
1502 gpointer data;
1503 void (*callback)(gpointer, gpointer);
1505 callback = g_object_get_data(G_OBJECT(item), "purplecallback");
1506 data = g_object_get_data(G_OBJECT(item), "purplecallbackdata");
1508 if (callback)
1509 callback(object, data);
1512 GtkWidget *
1513 pidgin_append_menu_action(GtkWidget *menu, PurpleActionMenu *act,
1514 gpointer object)
1516 GtkWidget *menuitem;
1517 GList *list;
1519 if (act == NULL) {
1520 return pidgin_separator(menu);
1523 menuitem = gtk_menu_item_new_with_mnemonic(
1524 purple_action_menu_get_label(act));
1526 list = purple_action_menu_get_children(act);
1528 if (list == NULL) {
1529 PurpleCallback callback;
1531 callback = purple_action_menu_get_callback(act);
1533 if (callback != NULL) {
1534 g_object_set_data(G_OBJECT(menuitem),
1535 "purplecallback",
1536 callback);
1537 g_object_set_data(G_OBJECT(menuitem),
1538 "purplecallbackdata",
1539 purple_action_menu_get_data(act));
1540 g_signal_connect(G_OBJECT(menuitem), "activate",
1541 G_CALLBACK(menu_action_cb),
1542 object);
1543 } else {
1544 gtk_widget_set_sensitive(menuitem, FALSE);
1547 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1548 } else {
1549 GList *l = NULL;
1550 GtkWidget *submenu = NULL;
1551 GtkAccelGroup *group;
1553 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1555 submenu = gtk_menu_new();
1556 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
1558 group = gtk_menu_get_accel_group(GTK_MENU(menu));
1559 if (group) {
1560 char *path = g_strdup_printf("%s/%s",
1561 gtk_menu_item_get_accel_path(GTK_MENU_ITEM(menuitem)),
1562 purple_action_menu_get_label(act));
1563 gtk_menu_set_accel_path(GTK_MENU(submenu), path);
1564 g_free(path);
1565 gtk_menu_set_accel_group(GTK_MENU(submenu), group);
1568 for (l = list; l; l = l->next) {
1569 PurpleActionMenu *act = (PurpleActionMenu *)l->data;
1571 pidgin_append_menu_action(submenu, act, object);
1573 g_list_free(list);
1574 purple_action_menu_set_children(act, NULL);
1576 purple_action_menu_free(act);
1577 return menuitem;
1580 static gboolean buddyname_completion_match_func(GtkEntryCompletion *completion,
1581 const gchar *key, GtkTreeIter *iter, gpointer user_data)
1583 GtkTreeModel *model;
1584 GValue val1;
1585 GValue val2;
1586 const char *tmp;
1588 model = gtk_entry_completion_get_model(completion);
1590 val1.g_type = 0;
1591 gtk_tree_model_get_value(model, iter, COMPLETION_NORMALIZED_COLUMN, &val1);
1592 tmp = g_value_get_string(&val1);
1593 if (tmp != NULL && purple_str_has_prefix(tmp, key))
1595 g_value_unset(&val1);
1596 return TRUE;
1598 g_value_unset(&val1);
1600 val2.g_type = 0;
1601 gtk_tree_model_get_value(model, iter, COMPLETION_COMPARISON_COLUMN, &val2);
1602 tmp = g_value_get_string(&val2);
1603 if (tmp != NULL && purple_str_has_prefix(tmp, key))
1605 g_value_unset(&val2);
1606 return TRUE;
1608 g_value_unset(&val2);
1610 return FALSE;
1613 static gboolean buddyname_completion_match_selected_cb(GtkEntryCompletion *completion,
1614 GtkTreeModel *model, GtkTreeIter *iter, PidginCompletionData *data)
1616 GValue val;
1617 GtkWidget *optmenu = data->accountopt;
1618 PurpleAccount *account;
1620 val.g_type = 0;
1621 gtk_tree_model_get_value(model, iter, COMPLETION_BUDDY_COLUMN, &val);
1622 gtk_entry_set_text(GTK_ENTRY(data->entry), g_value_get_string(&val));
1623 g_value_unset(&val);
1625 gtk_tree_model_get_value(model, iter, COMPLETION_ACCOUNT_COLUMN, &val);
1626 account = g_value_get_pointer(&val);
1627 g_value_unset(&val);
1629 if (account == NULL)
1630 return TRUE;
1632 if (optmenu != NULL)
1633 aop_option_menu_select_by_data(optmenu, account);
1635 return TRUE;
1638 static void
1639 add_buddyname_autocomplete_entry(GtkListStore *store, const char *buddy_alias, const char *contact_alias,
1640 const PurpleAccount *account, const char *buddyname)
1642 GtkTreeIter iter;
1643 gboolean completion_added = FALSE;
1644 gchar *normalized_buddyname;
1645 gchar *tmp;
1647 tmp = g_utf8_normalize(buddyname, -1, G_NORMALIZE_DEFAULT);
1648 normalized_buddyname = g_utf8_casefold(tmp, -1);
1649 g_free(tmp);
1651 /* There's no sense listing things like: 'xxx "xxx"'
1652 when the name and buddy alias match. */
1653 if (buddy_alias && !purple_strequal(buddy_alias, buddyname)) {
1654 char *completion_entry = g_strdup_printf("%s \"%s\"", buddyname, buddy_alias);
1655 char *tmp2 = g_utf8_normalize(buddy_alias, -1, G_NORMALIZE_DEFAULT);
1657 tmp = g_utf8_casefold(tmp2, -1);
1658 g_free(tmp2);
1660 gtk_list_store_append(store, &iter);
1661 gtk_list_store_set(store, &iter,
1662 COMPLETION_DISPLAYED_COLUMN, completion_entry,
1663 COMPLETION_BUDDY_COLUMN, buddyname,
1664 COMPLETION_NORMALIZED_COLUMN, normalized_buddyname,
1665 COMPLETION_COMPARISON_COLUMN, tmp,
1666 COMPLETION_ACCOUNT_COLUMN, account,
1667 -1);
1668 g_free(completion_entry);
1669 g_free(tmp);
1670 completion_added = TRUE;
1673 /* There's no sense listing things like: 'xxx "xxx"'
1674 when the name and contact alias match. */
1675 if (contact_alias && !purple_strequal(contact_alias, buddyname)) {
1676 /* We don't want duplicates when the contact and buddy alias match. */
1677 if (!purple_strequal(contact_alias, buddy_alias)) {
1678 char *completion_entry = g_strdup_printf("%s \"%s\"",
1679 buddyname, contact_alias);
1680 char *tmp2 = g_utf8_normalize(contact_alias, -1, G_NORMALIZE_DEFAULT);
1682 tmp = g_utf8_casefold(tmp2, -1);
1683 g_free(tmp2);
1685 gtk_list_store_append(store, &iter);
1686 gtk_list_store_set(store, &iter,
1687 COMPLETION_DISPLAYED_COLUMN, completion_entry,
1688 COMPLETION_BUDDY_COLUMN, buddyname,
1689 COMPLETION_NORMALIZED_COLUMN, normalized_buddyname,
1690 COMPLETION_COMPARISON_COLUMN, tmp,
1691 COMPLETION_ACCOUNT_COLUMN, account,
1692 -1);
1693 g_free(completion_entry);
1694 g_free(tmp);
1695 completion_added = TRUE;
1699 if (completion_added == FALSE) {
1700 /* Add the buddy's name. */
1701 gtk_list_store_append(store, &iter);
1702 gtk_list_store_set(store, &iter,
1703 COMPLETION_DISPLAYED_COLUMN, buddyname,
1704 COMPLETION_BUDDY_COLUMN, buddyname,
1705 COMPLETION_NORMALIZED_COLUMN, normalized_buddyname,
1706 COMPLETION_COMPARISON_COLUMN, NULL,
1707 COMPLETION_ACCOUNT_COLUMN, account,
1708 -1);
1711 g_free(normalized_buddyname);
1714 static void get_log_set_name(PurpleLogSet *set, gpointer value, PidginCompletionData *data)
1716 PidginFilterBuddyCompletionEntryFunc filter_func = data->filter_func;
1717 gpointer user_data = data->filter_func_user_data;
1719 /* 1. Don't show buddies because we will have gotten them already.
1720 * 2. The boxes that use this autocomplete code handle only IMs. */
1721 if (!set->buddy && set->type == PURPLE_LOG_IM) {
1722 PidginBuddyCompletionEntry entry;
1723 entry.is_buddy = FALSE;
1724 entry.entry.logged_buddy = set;
1726 if (filter_func(&entry, user_data)) {
1727 add_buddyname_autocomplete_entry(data->store,
1728 NULL, NULL, set->account, set->name);
1733 static void
1734 add_completion_list(PidginCompletionData *data)
1736 PurpleBlistNode *gnode, *cnode, *bnode;
1737 PidginFilterBuddyCompletionEntryFunc filter_func = data->filter_func;
1738 gpointer user_data = data->filter_func_user_data;
1739 GHashTable *sets;
1740 gchar *alias;
1742 gtk_list_store_clear(data->store);
1744 for (gnode = purple_blist_get_default_root(); gnode != NULL;
1745 gnode = gnode->next) {
1746 if (!PURPLE_IS_GROUP(gnode))
1747 continue;
1749 for (cnode = gnode->child; cnode != NULL; cnode = cnode->next)
1751 if (!PURPLE_IS_CONTACT(cnode))
1752 continue;
1754 g_object_get(cnode, "alias", &alias, NULL);
1756 for (bnode = cnode->child; bnode != NULL; bnode = bnode->next)
1758 PidginBuddyCompletionEntry entry;
1759 entry.is_buddy = TRUE;
1760 entry.entry.buddy = (PurpleBuddy *) bnode;
1762 if (filter_func(&entry, user_data)) {
1763 add_buddyname_autocomplete_entry(data->store,
1764 alias,
1765 purple_buddy_get_contact_alias(entry.entry.buddy),
1766 purple_buddy_get_account(entry.entry.buddy),
1767 purple_buddy_get_name(entry.entry.buddy)
1772 g_free(alias);
1776 sets = purple_log_get_log_sets();
1777 g_hash_table_foreach(sets, (GHFunc)get_log_set_name, data);
1778 g_hash_table_destroy(sets);
1782 static void
1783 buddyname_autocomplete_destroyed_cb(GtkWidget *widget, gpointer data)
1785 g_free(data);
1786 purple_signals_disconnect_by_handle(widget);
1789 static void
1790 repopulate_autocomplete(gpointer something, gpointer data)
1792 add_completion_list(data);
1795 void
1796 pidgin_setup_screenname_autocomplete(
1797 GtkWidget *entry, GtkWidget *chooser,
1798 PidginFilterBuddyCompletionEntryFunc filter_func, gpointer user_data)
1800 PidginCompletionData *data;
1803 * Store the displayed completion value, the buddy name, the UTF-8
1804 * normalized & casefolded buddy name, the UTF-8 normalized &
1805 * casefolded value for comparison, and the account.
1807 GtkListStore *store;
1809 GtkEntryCompletion *completion;
1811 data = g_new0(PidginCompletionData, 1);
1812 store = gtk_list_store_new(COMPLETION_COLUMN_COUNT, G_TYPE_STRING,
1813 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
1814 G_TYPE_POINTER);
1816 data->entry = entry;
1817 data->accountopt = chooser;
1818 if (filter_func == NULL) {
1819 data->filter_func = pidgin_screenname_autocomplete_default_filter;
1820 data->filter_func_user_data = NULL;
1821 } else {
1822 data->filter_func = filter_func;
1823 data->filter_func_user_data = user_data;
1825 data->store = store;
1827 add_completion_list(data);
1829 /* Sort the completion list by buddy name */
1830 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store),
1831 COMPLETION_BUDDY_COLUMN,
1832 GTK_SORT_ASCENDING);
1834 completion = gtk_entry_completion_new();
1835 gtk_entry_completion_set_match_func(completion, buddyname_completion_match_func, NULL, NULL);
1837 g_signal_connect(G_OBJECT(completion), "match-selected",
1838 G_CALLBACK(buddyname_completion_match_selected_cb), data);
1840 gtk_entry_set_completion(GTK_ENTRY(entry), completion);
1841 g_object_unref(completion);
1843 gtk_entry_completion_set_model(completion, GTK_TREE_MODEL(store));
1844 g_object_unref(store);
1846 gtk_entry_completion_set_text_column(completion, COMPLETION_DISPLAYED_COLUMN);
1848 purple_signal_connect(purple_connections_get_handle(), "signed-on", entry,
1849 PURPLE_CALLBACK(repopulate_autocomplete), data);
1850 purple_signal_connect(purple_connections_get_handle(), "signed-off", entry,
1851 PURPLE_CALLBACK(repopulate_autocomplete), data);
1853 purple_signal_connect(purple_accounts_get_handle(), "account-added", entry,
1854 PURPLE_CALLBACK(repopulate_autocomplete), data);
1855 purple_signal_connect(purple_accounts_get_handle(), "account-removed", entry,
1856 PURPLE_CALLBACK(repopulate_autocomplete), data);
1858 g_signal_connect(G_OBJECT(entry), "destroy", G_CALLBACK(buddyname_autocomplete_destroyed_cb), data);
1861 gboolean
1862 pidgin_screenname_autocomplete_default_filter(const PidginBuddyCompletionEntry *completion_entry, gpointer all_accounts) {
1863 gboolean all = GPOINTER_TO_INT(all_accounts);
1865 if (completion_entry->is_buddy) {
1866 return all || purple_account_is_connected(purple_buddy_get_account(completion_entry->entry.buddy));
1867 } else {
1868 return all || (completion_entry->entry.logged_buddy->account != NULL && purple_account_is_connected(completion_entry->entry.logged_buddy->account));
1872 void pidgin_set_cursor(GtkWidget *widget, GdkCursorType cursor_type)
1874 GdkDisplay *display;
1875 GdkCursor *cursor;
1877 g_return_if_fail(widget != NULL);
1878 if (gtk_widget_get_window(widget) == NULL)
1879 return;
1881 display = gtk_widget_get_display(widget);
1882 cursor = gdk_cursor_new_for_display(display, cursor_type);
1883 gdk_window_set_cursor(gtk_widget_get_window(widget), cursor);
1885 g_object_unref(cursor);
1887 gdk_display_flush(gdk_window_get_display(gtk_widget_get_window(widget)));
1890 void pidgin_clear_cursor(GtkWidget *widget)
1892 g_return_if_fail(widget != NULL);
1893 if (gtk_widget_get_window(widget) == NULL)
1894 return;
1896 gdk_window_set_cursor(gtk_widget_get_window(widget), NULL);
1899 static void
1900 icon_filesel_choose_cb(GtkWidget *widget, gint response, struct _icon_chooser *dialog)
1902 char *filename, *current_folder;
1904 if (response != GTK_RESPONSE_ACCEPT) {
1905 if (response == GTK_RESPONSE_CANCEL) {
1906 gtk_widget_destroy(dialog->icon_filesel);
1908 dialog->icon_filesel = NULL;
1909 if (dialog->callback)
1910 dialog->callback(NULL, dialog->data);
1911 g_free(dialog);
1912 return;
1915 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog->icon_filesel));
1916 current_folder = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(dialog->icon_filesel));
1917 if (current_folder != NULL) {
1918 purple_prefs_set_path(PIDGIN_PREFS_ROOT "/filelocations/last_icon_folder", current_folder);
1919 g_free(current_folder);
1923 if (dialog->callback)
1924 dialog->callback(filename, dialog->data);
1925 gtk_widget_destroy(dialog->icon_filesel);
1926 g_free(filename);
1927 g_free(dialog);
1931 static void
1932 icon_preview_change_cb(GtkFileChooser *widget, struct _icon_chooser *dialog)
1934 GdkPixbuf *pixbuf;
1935 int height, width;
1936 char *basename, *markup, *size;
1937 GStatBuf st;
1938 char *filename;
1940 filename = gtk_file_chooser_get_preview_filename(
1941 GTK_FILE_CHOOSER(dialog->icon_filesel));
1943 if (!filename || g_stat(filename, &st) || !(pixbuf = pidgin_pixbuf_new_from_file_at_size(filename, 128, 128)))
1945 gtk_image_set_from_pixbuf(GTK_IMAGE(dialog->icon_preview), NULL);
1946 gtk_label_set_markup(GTK_LABEL(dialog->icon_text), "");
1947 g_free(filename);
1948 return;
1951 gdk_pixbuf_get_file_info(filename, &width, &height);
1952 basename = g_path_get_basename(filename);
1953 size = g_format_size(st.st_size);
1954 markup = g_strdup_printf(_("<b>File:</b> %s\n"
1955 "<b>File size:</b> %s\n"
1956 "<b>Image size:</b> %dx%d"),
1957 basename, size, width, height);
1959 gtk_image_set_from_pixbuf(GTK_IMAGE(dialog->icon_preview), pixbuf);
1960 gtk_label_set_markup(GTK_LABEL(dialog->icon_text), markup);
1962 g_object_unref(G_OBJECT(pixbuf));
1963 g_free(filename);
1964 g_free(basename);
1965 g_free(size);
1966 g_free(markup);
1970 GtkWidget *pidgin_buddy_icon_chooser_new(GtkWindow *parent, void(*callback)(const char *, gpointer), gpointer data) {
1971 struct _icon_chooser *dialog = g_new0(struct _icon_chooser, 1);
1973 GtkWidget *vbox;
1974 const char *current_folder;
1976 dialog->callback = callback;
1977 dialog->data = data;
1979 current_folder = purple_prefs_get_path(PIDGIN_PREFS_ROOT "/filelocations/last_icon_folder");
1981 dialog->icon_filesel = gtk_file_chooser_dialog_new(_("Buddy Icon"),
1982 parent,
1983 GTK_FILE_CHOOSER_ACTION_OPEN,
1984 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1985 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
1986 NULL);
1987 gtk_dialog_set_default_response(GTK_DIALOG(dialog->icon_filesel), GTK_RESPONSE_ACCEPT);
1988 if ((current_folder != NULL) && (*current_folder != '\0'))
1989 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog->icon_filesel),
1990 current_folder);
1992 dialog->icon_preview = gtk_image_new();
1993 dialog->icon_text = gtk_label_new(NULL);
1995 vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, PIDGIN_HIG_BOX_SPACE);
1996 gtk_widget_set_size_request(GTK_WIDGET(vbox), -1, 50);
1997 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(dialog->icon_preview), TRUE, FALSE, 0);
1998 gtk_box_pack_end(GTK_BOX(vbox), GTK_WIDGET(dialog->icon_text), FALSE, FALSE, 0);
1999 gtk_widget_show_all(vbox);
2001 gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog->icon_filesel), vbox);
2002 gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(dialog->icon_filesel), TRUE);
2003 gtk_file_chooser_set_use_preview_label(GTK_FILE_CHOOSER(dialog->icon_filesel), FALSE);
2005 g_signal_connect(G_OBJECT(dialog->icon_filesel), "update-preview",
2006 G_CALLBACK(icon_preview_change_cb), dialog);
2007 g_signal_connect(G_OBJECT(dialog->icon_filesel), "response",
2008 G_CALLBACK(icon_filesel_choose_cb), dialog);
2009 icon_preview_change_cb(NULL, dialog);
2011 #ifdef _WIN32
2012 g_signal_connect(G_OBJECT(dialog->icon_filesel), "show",
2013 G_CALLBACK(winpidgin_ensure_onscreen), dialog->icon_filesel);
2014 #endif
2016 return dialog->icon_filesel;
2020 * str_array_match:
2022 * Returns: %TRUE if any string from array @a exists in array @b.
2024 static gboolean
2025 str_array_match(char **a, char **b)
2027 int i, j;
2029 if (!a || !b)
2030 return FALSE;
2031 for (i = 0; a[i] != NULL; i++)
2032 for (j = 0; b[j] != NULL; j++)
2033 if (!g_ascii_strcasecmp(a[i], b[j]))
2034 return TRUE;
2035 return FALSE;
2038 gpointer
2039 pidgin_convert_buddy_icon(PurpleProtocol *protocol, const char *path, size_t *len)
2041 PurpleBuddyIconSpec *spec;
2042 int orig_width, orig_height, new_width, new_height;
2043 GdkPixbufFormat *format;
2044 char **pixbuf_formats;
2045 char **protocol_formats;
2046 GError *error = NULL;
2047 gchar *contents;
2048 gsize length;
2049 GdkPixbuf *pixbuf, *original;
2050 float scale_factor;
2051 int i;
2052 gchar *tmp;
2054 spec = purple_protocol_get_icon_spec(protocol);
2055 g_return_val_if_fail(spec->format != NULL, NULL);
2057 format = gdk_pixbuf_get_file_info(path, &orig_width, &orig_height);
2058 if (format == NULL) {
2059 purple_debug_warning("buddyicon", "Could not get file info of %s\n", path);
2060 return NULL;
2063 pixbuf_formats = gdk_pixbuf_format_get_extensions(format);
2064 protocol_formats = g_strsplit(spec->format, ",", 0);
2066 if (str_array_match(pixbuf_formats, protocol_formats) && /* This is an acceptable format AND */
2067 (!(spec->scale_rules & PURPLE_ICON_SCALE_SEND) || /* The protocol doesn't scale before it sends OR */
2068 (spec->min_width <= orig_width && spec->max_width >= orig_width &&
2069 spec->min_height <= orig_height && spec->max_height >= orig_height))) /* The icon is the correct size */
2071 g_strfreev(pixbuf_formats);
2073 if (!g_file_get_contents(path, &contents, &length, &error)) {
2074 purple_debug_warning("buddyicon", "Could not get file contents "
2075 "of %s: %s\n", path, error->message);
2076 g_strfreev(protocol_formats);
2077 return NULL;
2080 if (spec->max_filesize == 0 || length < spec->max_filesize) {
2081 /* The supplied image fits the file size, dimensions and type
2082 constraints. Great! Return it without making any changes. */
2083 if (len)
2084 *len = length;
2085 g_strfreev(protocol_formats);
2086 return contents;
2089 /* The image was too big. Fall-through and try scaling it down. */
2090 g_free(contents);
2091 } else {
2092 g_strfreev(pixbuf_formats);
2095 /* The original image wasn't compatible. Scale it or convert file type. */
2096 pixbuf = gdk_pixbuf_new_from_file(path, &error);
2097 if (error) {
2098 purple_debug_warning("buddyicon", "Could not open icon '%s' for "
2099 "conversion: %s\n", path, error->message);
2100 g_error_free(error);
2101 g_strfreev(protocol_formats);
2102 return NULL;
2104 original = g_object_ref(pixbuf);
2106 new_width = orig_width;
2107 new_height = orig_height;
2109 /* Make sure the image is the correct dimensions */
2110 if (spec->scale_rules & PURPLE_ICON_SCALE_SEND &&
2111 (orig_width < spec->min_width || orig_width > spec->max_width ||
2112 orig_height < spec->min_height || orig_height > spec->max_height))
2114 purple_buddy_icon_spec_get_scaled_size(spec, &new_width, &new_height);
2116 g_object_unref(G_OBJECT(pixbuf));
2117 pixbuf = gdk_pixbuf_scale_simple(original, new_width, new_height, GDK_INTERP_HYPER);
2120 scale_factor = 1;
2121 do {
2122 for (i = 0; protocol_formats[i]; i++) {
2123 int quality = 100;
2124 do {
2125 const char *key = NULL;
2126 const char *value = NULL;
2127 gchar tmp_buf[4];
2129 purple_debug_info("buddyicon", "Converting buddy icon to %s\n", protocol_formats[i]);
2131 if (purple_strequal(protocol_formats[i], "png")) {
2132 key = "compression";
2133 value = "9";
2134 } else if (purple_strequal(protocol_formats[i], "jpeg")) {
2135 sprintf(tmp_buf, "%u", quality);
2136 key = "quality";
2137 value = tmp_buf;
2140 if (!gdk_pixbuf_save_to_buffer(pixbuf, &contents, &length,
2141 protocol_formats[i], &error, key, value, NULL))
2143 /* The NULL checking of error is necessary due to this bug:
2144 * http://bugzilla.gnome.org/show_bug.cgi?id=405539 */
2145 purple_debug_warning("buddyicon",
2146 "Could not convert to %s: %s\n", protocol_formats[i],
2147 (error && error->message) ? error->message : "Unknown error");
2148 g_error_free(error);
2149 error = NULL;
2151 /* We couldn't convert to this image type. Try the next
2152 image type. */
2153 break;
2156 if (spec->max_filesize == 0 || length <= spec->max_filesize) {
2157 /* We were able to save the image as this image type and
2158 have it be within the size constraints. Great! Return
2159 the image. */
2160 purple_debug_info("buddyicon", "Converted image from "
2161 "%dx%d to %dx%d, format=%s, quality=%u, "
2162 "filesize=%" G_GSIZE_FORMAT "\n",
2163 orig_width, orig_height, new_width, new_height,
2164 protocol_formats[i], quality, length);
2165 if (len)
2166 *len = length;
2167 g_strfreev(protocol_formats);
2168 g_object_unref(G_OBJECT(pixbuf));
2169 g_object_unref(G_OBJECT(original));
2170 return contents;
2173 g_free(contents);
2175 if (!purple_strequal(protocol_formats[i], "jpeg")) {
2176 /* File size was too big and we can't lower the quality,
2177 so skip to the next image type. */
2178 break;
2181 /* File size was too big, but we're dealing with jpeg so try
2182 lowering the quality. */
2183 quality -= 5;
2184 } while (quality >= 70);
2187 /* We couldn't save the image in any format that was below the max
2188 file size. Maybe we can reduce the image dimensions? */
2189 scale_factor *= 0.8;
2190 new_width = orig_width * scale_factor;
2191 new_height = orig_height * scale_factor;
2192 g_object_unref(G_OBJECT(pixbuf));
2193 pixbuf = gdk_pixbuf_scale_simple(original, new_width, new_height, GDK_INTERP_HYPER);
2194 } while ((new_width > 10 || new_height > 10) && new_width > spec->min_width && new_height > spec->min_height);
2195 g_strfreev(protocol_formats);
2196 g_object_unref(G_OBJECT(pixbuf));
2197 g_object_unref(G_OBJECT(original));
2199 tmp = g_strdup_printf(_("The file '%s' is too large for %s. Please try a smaller image.\n"),
2200 path, purple_protocol_get_name(protocol));
2201 purple_notify_error(NULL, _("Icon Error"), _("Could not set icon"), tmp, NULL);
2202 g_free(tmp);
2204 return NULL;
2207 void pidgin_set_urgent(GtkWindow *window, gboolean urgent)
2209 #if defined _WIN32
2210 winpidgin_window_flash(window, urgent);
2211 #else
2212 gtk_window_set_urgency_hint(window, urgent);
2213 #endif
2216 static void *
2217 pidgin_utils_get_handle(void)
2219 static int handle;
2221 return &handle;
2224 static void connection_signed_off_cb(PurpleConnection *gc)
2226 GSList *list, *l_next;
2227 for (list = minidialogs; list; list = l_next) {
2228 l_next = list->next;
2229 if (g_object_get_data(G_OBJECT(list->data), "gc") == gc) {
2230 gtk_widget_destroy(GTK_WIDGET(list->data));
2235 static void alert_killed_cb(GtkWidget *widget)
2237 minidialogs = g_slist_remove(minidialogs, widget);
2240 static void
2241 old_mini_dialog_button_clicked_cb(PidginMiniDialog *mini_dialog,
2242 GtkButton *button,
2243 gpointer user_data)
2245 struct _old_button_clicked_cb_data *data = user_data;
2246 data->cb(data->data, button);
2249 static void
2250 old_mini_dialog_destroy_cb(GtkWidget *dialog,
2251 GList *cb_datas)
2253 while (cb_datas != NULL)
2255 g_free(cb_datas->data);
2256 cb_datas = g_list_delete_link(cb_datas, cb_datas);
2260 static void
2261 mini_dialog_init(PidginMiniDialog *mini_dialog, PurpleConnection *gc, void *user_data, va_list args)
2263 const char *button_text;
2264 GList *cb_datas = NULL;
2265 static gboolean first_call = TRUE;
2267 if (first_call) {
2268 first_call = FALSE;
2269 purple_signal_connect(purple_connections_get_handle(), "signed-off",
2270 pidgin_utils_get_handle(),
2271 PURPLE_CALLBACK(connection_signed_off_cb), NULL);
2274 g_object_set_data(G_OBJECT(mini_dialog), "gc" ,gc);
2275 g_signal_connect(G_OBJECT(mini_dialog), "destroy",
2276 G_CALLBACK(alert_killed_cb), NULL);
2278 while ((button_text = va_arg(args, char*))) {
2279 struct _old_button_clicked_cb_data *data = NULL;
2280 PidginMiniDialogCallback wrapper_cb = NULL;
2281 PidginUtilMiniDialogCallback callback =
2282 va_arg(args, PidginUtilMiniDialogCallback);
2284 if (callback != NULL) {
2285 data = g_new0(struct _old_button_clicked_cb_data, 1);
2286 data->cb = callback;
2287 data->data = user_data;
2288 wrapper_cb = old_mini_dialog_button_clicked_cb;
2290 pidgin_mini_dialog_add_button(mini_dialog, button_text,
2291 wrapper_cb, data);
2292 cb_datas = g_list_append(cb_datas, data);
2295 g_signal_connect(G_OBJECT(mini_dialog), "destroy",
2296 G_CALLBACK(old_mini_dialog_destroy_cb), cb_datas);
2299 #define INIT_AND_RETURN_MINI_DIALOG(mini_dialog) \
2300 va_list args; \
2301 va_start(args, user_data); \
2302 mini_dialog_init(mini_dialog, gc, user_data, args); \
2303 va_end(args); \
2304 return GTK_WIDGET(mini_dialog);
2306 GtkWidget *
2307 pidgin_make_mini_dialog(PurpleConnection *gc,
2308 const char *icon_name,
2309 const char *primary,
2310 const char *secondary,
2311 void *user_data,
2312 ...)
2314 PidginMiniDialog *mini_dialog = pidgin_mini_dialog_new(primary, secondary, icon_name);
2315 INIT_AND_RETURN_MINI_DIALOG(mini_dialog);
2318 GtkWidget *
2319 pidgin_make_mini_dialog_with_custom_icon(PurpleConnection *gc,
2320 GdkPixbuf *custom_icon,
2321 const char *primary,
2322 const char *secondary,
2323 void *user_data,
2324 ...)
2326 PidginMiniDialog *mini_dialog = pidgin_mini_dialog_new_with_custom_icon(primary, secondary, custom_icon);
2327 INIT_AND_RETURN_MINI_DIALOG(mini_dialog);
2331 * "This is so dead sexy."
2332 * "Two thumbs up."
2333 * "Best movie of the year."
2335 * This is the function that handles CTRL+F searching in the buddy list.
2336 * It finds the top-most buddy/group/chat/whatever containing the
2337 * entered string.
2339 * It's somewhat ineffecient, because we strip all the HTML from the
2340 * "name" column of the buddy list (because the GtkTreeModel does not
2341 * contain the screen name in a non-markedup format). But the alternative
2342 * is to add an extra column to the GtkTreeModel. And this function is
2343 * used rarely, so it shouldn't matter TOO much.
2345 gboolean pidgin_tree_view_search_equal_func(GtkTreeModel *model, gint column,
2346 const gchar *key, GtkTreeIter *iter, gpointer data)
2348 gchar *enteredstring;
2349 gchar *tmp;
2350 gchar *withmarkup;
2351 gchar *nomarkup;
2352 gchar *normalized;
2353 gboolean result;
2354 size_t i;
2355 size_t len;
2356 PangoLogAttr *log_attrs;
2357 gchar *word;
2359 if (g_ascii_strcasecmp(key, "Global Thermonuclear War") == 0)
2361 purple_notify_info(NULL, "WOPR", "Wouldn't you prefer a nice "
2362 "game of chess?", NULL, NULL);
2363 return FALSE;
2366 gtk_tree_model_get(model, iter, column, &withmarkup, -1);
2367 if (withmarkup == NULL) /* This is probably a separator */
2368 return TRUE;
2370 tmp = g_utf8_normalize(key, -1, G_NORMALIZE_DEFAULT);
2371 enteredstring = g_utf8_casefold(tmp, -1);
2372 g_free(tmp);
2374 nomarkup = purple_markup_strip_html(withmarkup);
2375 tmp = g_utf8_normalize(nomarkup, -1, G_NORMALIZE_DEFAULT);
2376 g_free(nomarkup);
2377 normalized = g_utf8_casefold(tmp, -1);
2378 g_free(tmp);
2380 if (purple_str_has_prefix(normalized, enteredstring))
2382 g_free(withmarkup);
2383 g_free(enteredstring);
2384 g_free(normalized);
2385 return FALSE;
2389 /* Use Pango to separate by words. */
2390 len = g_utf8_strlen(normalized, -1);
2391 log_attrs = g_new(PangoLogAttr, len + 1);
2393 pango_get_log_attrs(normalized, strlen(normalized), -1, NULL, log_attrs, len + 1);
2395 word = normalized;
2396 result = TRUE;
2397 for (i = 0; i < (len - 1) ; i++)
2399 if (log_attrs[i].is_word_start &&
2400 purple_str_has_prefix(word, enteredstring))
2402 result = FALSE;
2403 break;
2405 word = g_utf8_next_char(word);
2407 g_free(log_attrs);
2409 /* The non-Pango version. */
2410 #if 0
2411 word = normalized;
2412 result = TRUE;
2413 while (word[0] != '\0')
2415 gunichar c = g_utf8_get_char(word);
2416 if (!g_unichar_isalnum(c))
2418 word = g_utf8_find_next_char(word, NULL);
2419 if (purple_str_has_prefix(word, enteredstring))
2421 result = FALSE;
2422 break;
2425 else
2426 word = g_utf8_find_next_char(word, NULL);
2428 #endif
2430 g_free(withmarkup);
2431 g_free(enteredstring);
2432 g_free(normalized);
2434 return result;
2437 const char *pidgin_get_dim_grey_string(GtkWidget *widget) {
2438 static char dim_grey_string[8] = "";
2439 GtkStyleContext *context;
2440 GdkRGBA fg, bg;
2442 if (!widget)
2443 return "dim grey";
2445 context = gtk_widget_get_style_context(widget);
2446 if (!context)
2447 return "dim grey";
2449 gtk_style_context_get_color(context, gtk_style_context_get_state(context),
2450 &fg);
2451 gtk_style_context_get_background_color(context,
2452 gtk_style_context_get_state(context),
2453 &bg);
2454 snprintf(dim_grey_string, sizeof(dim_grey_string), "#%02x%02x%02x",
2455 (unsigned int)((fg.red + bg.red) * 0.5 * 255),
2456 (unsigned int)((fg.green + bg.green) * 0.5 * 255),
2457 (unsigned int)((fg.blue + bg.blue) * 0.5 * 255));
2458 return dim_grey_string;
2461 static void
2462 combo_box_changed_cb(GtkComboBoxText *combo_box, GtkEntry *entry)
2464 char *text = gtk_combo_box_text_get_active_text(combo_box);
2465 gtk_entry_set_text(entry, text ? text : "");
2466 g_free(text);
2469 static gboolean
2470 entry_key_pressed_cb(GtkWidget *entry, GdkEventKey *key, GtkComboBoxText *combo)
2472 if (key->keyval == GDK_KEY_Down || key->keyval == GDK_KEY_Up) {
2473 gtk_combo_box_popup(GTK_COMBO_BOX(combo));
2474 return TRUE;
2476 return FALSE;
2479 GtkWidget *
2480 pidgin_text_combo_box_entry_new(const char *default_item, GList *items)
2482 GtkComboBoxText *ret = NULL;
2483 GtkWidget *the_entry = NULL;
2485 ret = GTK_COMBO_BOX_TEXT(gtk_combo_box_text_new_with_entry());
2486 the_entry = gtk_bin_get_child(GTK_BIN(ret));
2488 if (default_item)
2489 gtk_entry_set_text(GTK_ENTRY(the_entry), default_item);
2491 for (; items != NULL ; items = items->next) {
2492 char *text = items->data;
2493 if (text && *text)
2494 gtk_combo_box_text_append_text(ret, text);
2497 g_signal_connect(G_OBJECT(ret), "changed", (GCallback)combo_box_changed_cb, the_entry);
2498 g_signal_connect_after(G_OBJECT(the_entry), "key-press-event", G_CALLBACK(entry_key_pressed_cb), ret);
2500 return GTK_WIDGET(ret);
2503 const char *pidgin_text_combo_box_entry_get_text(GtkWidget *widget)
2505 return gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((widget)))));
2508 void pidgin_text_combo_box_entry_set_text(GtkWidget *widget, const char *text)
2510 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((widget)))), (text));
2513 GtkWidget *
2514 pidgin_add_widget_to_vbox(GtkBox *vbox, const char *widget_label, GtkSizeGroup *sg, GtkWidget *widget, gboolean expand, GtkWidget **p_label)
2516 GtkWidget *hbox;
2517 GtkWidget *label = NULL;
2519 if (widget_label) {
2520 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5);
2521 gtk_widget_show(hbox);
2522 gtk_box_pack_start(vbox, hbox, FALSE, FALSE, 0);
2524 label = gtk_label_new_with_mnemonic(widget_label);
2525 gtk_widget_show(label);
2526 if (sg) {
2527 gtk_label_set_xalign(GTK_LABEL(label), 0);
2528 gtk_size_group_add_widget(sg, label);
2530 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
2531 } else {
2532 hbox = GTK_WIDGET(vbox);
2535 gtk_widget_show(widget);
2536 gtk_box_pack_start(GTK_BOX(hbox), widget, expand, TRUE, 0);
2537 if (label) {
2538 gtk_label_set_mnemonic_widget(GTK_LABEL(label), widget);
2539 pidgin_set_accessible_label(widget, GTK_LABEL(label));
2542 if (p_label)
2543 (*p_label) = label;
2544 return hbox;
2547 gboolean pidgin_auto_parent_window(GtkWidget *widget)
2549 #if 0
2550 /* This looks at the most recent window that received focus, and makes
2551 * that the parent window. */
2552 #ifndef _WIN32
2553 static GdkAtom _WindowTime = GDK_NONE;
2554 static GdkAtom _Cardinal = GDK_NONE;
2555 GList *windows = NULL;
2556 GtkWidget *parent = NULL;
2557 time_t window_time = 0;
2559 windows = gtk_window_list_toplevels();
2561 if (_WindowTime == GDK_NONE) {
2562 _WindowTime = gdk_x11_xatom_to_atom(gdk_x11_get_xatom_by_name("_NET_WM_USER_TIME"));
2564 if (_Cardinal == GDK_NONE) {
2565 _Cardinal = gdk_atom_intern("CARDINAL", FALSE);
2568 while (windows) {
2569 GtkWidget *window = windows->data;
2570 guchar *data = NULL;
2571 int al = 0;
2572 time_t value;
2574 windows = g_list_delete_link(windows, windows);
2576 if (window == widget ||
2577 !gtk_widget_get_visible(window))
2578 continue;
2580 if (!gdk_property_get(window->window, _WindowTime, _Cardinal, 0, sizeof(time_t), FALSE,
2581 NULL, NULL, &al, &data))
2582 continue;
2583 value = *(time_t *)data;
2584 if (window_time < value) {
2585 window_time = value;
2586 parent = window;
2588 g_free(data);
2590 if (windows)
2591 g_list_free(windows);
2592 if (parent) {
2593 if (!gtk_get_current_event() && gtk_window_has_toplevel_focus(GTK_WINDOW(parent))) {
2594 /* The window is in focus, and the new window was not triggered by a keypress/click
2595 * event. So do not set it transient, to avoid focus stealing and all that.
2597 return FALSE;
2599 gtk_window_set_transient_for(GTK_WINDOW(widget), GTK_WINDOW(parent));
2600 return TRUE;
2602 return FALSE;
2603 #endif
2604 #else
2605 /* This finds the currently active window and makes that the parent window. */
2606 GList *windows = NULL;
2607 GtkWindow *parent = NULL;
2608 GdkEvent *event = gtk_get_current_event();
2609 GdkWindow *menu = NULL;
2610 gpointer parent_from;
2611 PurpleNotifyType notify_type;
2613 parent_from = g_object_get_data(G_OBJECT(widget), "pidgin-parent-from");
2614 if (purple_request_is_valid_ui_handle(parent_from, NULL)) {
2616 gtk_window_set_transient_for(GTK_WINDOW(widget),
2617 gtk_window_get_transient_for(
2618 pidgin_request_get_dialog_window(parent_from)));
2619 return TRUE;
2621 if (purple_notify_is_valid_ui_handle(parent_from, &notify_type) &&
2622 notify_type == PURPLE_NOTIFY_MESSAGE)
2624 gtk_window_set_transient_for(GTK_WINDOW(widget),
2625 gtk_window_get_transient_for(GTK_WINDOW(parent_from)));
2626 return TRUE;
2629 if (event == NULL)
2630 /* The window was not triggered by a user action. */
2631 return FALSE;
2633 /* We need to special case events from a popup menu. */
2634 if (event->type == GDK_BUTTON_RELEASE) {
2635 /* XXX: Neither of the following works:
2636 menu = event->button.window;
2637 menu = gdk_window_get_parent(event->button.window);
2638 menu = gdk_window_get_toplevel(event->button.window);
2640 } else if (event->type == GDK_KEY_PRESS)
2641 menu = event->key.window;
2643 windows = gtk_window_list_toplevels();
2644 while (windows) {
2645 GtkWindow *window = GTK_WINDOW(windows->data);
2646 windows = g_list_delete_link(windows, windows);
2648 if (GPOINTER_TO_INT(g_object_get_data(G_OBJECT(window),
2649 "pidgin-window-is-closing")))
2651 parent = gtk_window_get_transient_for(window);
2652 break;
2655 if (GTK_WIDGET(window) == widget ||
2656 !gtk_widget_get_visible(GTK_WIDGET(window))) {
2657 continue;
2660 if (gtk_window_has_toplevel_focus(window) ||
2661 (menu && menu == gtk_widget_get_window(GTK_WIDGET(window)))) {
2662 parent = window;
2663 break;
2666 if (windows)
2667 g_list_free(windows);
2668 if (parent) {
2669 gtk_window_set_transient_for(GTK_WINDOW(widget), parent);
2670 return TRUE;
2672 return FALSE;
2673 #endif
2676 static GObject *pidgin_pixbuf_from_data_helper(const guchar *buf, gsize count, gboolean animated)
2678 GObject *pixbuf;
2679 GdkPixbufLoader *loader;
2680 GError *error = NULL;
2682 loader = gdk_pixbuf_loader_new();
2684 if (!gdk_pixbuf_loader_write(loader, buf, count, &error) || error) {
2685 purple_debug_warning("gtkutils", "gdk_pixbuf_loader_write() "
2686 "failed with size=%" G_GSIZE_FORMAT ": %s\n", count,
2687 error ? error->message : "(no error message)");
2688 if (error)
2689 g_error_free(error);
2690 g_object_unref(G_OBJECT(loader));
2691 return NULL;
2694 if (!gdk_pixbuf_loader_close(loader, &error) || error) {
2695 purple_debug_warning("gtkutils", "gdk_pixbuf_loader_close() "
2696 "failed for image of size %" G_GSIZE_FORMAT ": %s\n", count,
2697 error ? error->message : "(no error message)");
2698 if (error)
2699 g_error_free(error);
2700 g_object_unref(G_OBJECT(loader));
2701 return NULL;
2704 if (animated)
2705 pixbuf = G_OBJECT(gdk_pixbuf_loader_get_animation(loader));
2706 else
2707 pixbuf = G_OBJECT(gdk_pixbuf_loader_get_pixbuf(loader));
2708 if (!pixbuf) {
2709 purple_debug_warning("gtkutils", "%s() returned NULL for image "
2710 "of size %" G_GSIZE_FORMAT "\n",
2711 animated ? "gdk_pixbuf_loader_get_animation"
2712 : "gdk_pixbuf_loader_get_pixbuf", count);
2713 g_object_unref(G_OBJECT(loader));
2714 return NULL;
2717 g_object_ref(pixbuf);
2718 g_object_unref(G_OBJECT(loader));
2720 return pixbuf;
2723 GdkPixbuf *pidgin_pixbuf_from_data(const guchar *buf, gsize count)
2725 return GDK_PIXBUF(pidgin_pixbuf_from_data_helper(buf, count, FALSE));
2728 GdkPixbufAnimation *pidgin_pixbuf_anim_from_data(const guchar *buf, gsize count)
2730 return GDK_PIXBUF_ANIMATION(pidgin_pixbuf_from_data_helper(buf, count, TRUE));
2733 GdkPixbuf *
2734 pidgin_pixbuf_from_image(PurpleImage *image)
2736 return pidgin_pixbuf_from_data(purple_image_get_data(image),
2737 purple_image_get_data_size(image));
2740 GdkPixbuf *pidgin_pixbuf_new_from_file(const gchar *filename)
2742 GdkPixbuf *pixbuf;
2743 GError *error = NULL;
2745 g_return_val_if_fail(filename != NULL, NULL);
2746 g_return_val_if_fail(filename[0] != '\0', NULL);
2748 pixbuf = gdk_pixbuf_new_from_file(filename, &error);
2749 if (!pixbuf || error) {
2750 purple_debug_warning("gtkutils", "gdk_pixbuf_new_from_file() "
2751 "returned %s for file %s: %s\n",
2752 pixbuf ? "something" : "nothing",
2753 filename,
2754 error ? error->message : "(no error message)");
2755 if (error)
2756 g_error_free(error);
2757 if (pixbuf)
2758 g_object_unref(G_OBJECT(pixbuf));
2759 return NULL;
2762 return pixbuf;
2765 GdkPixbuf *pidgin_pixbuf_new_from_file_at_size(const char *filename, int width, int height)
2767 GdkPixbuf *pixbuf;
2768 GError *error = NULL;
2770 g_return_val_if_fail(filename != NULL, NULL);
2771 g_return_val_if_fail(filename[0] != '\0', NULL);
2773 pixbuf = gdk_pixbuf_new_from_file_at_size(filename,
2774 width, height, &error);
2775 if (!pixbuf || error) {
2776 purple_debug_warning("gtkutils", "gdk_pixbuf_new_from_file_at_size() "
2777 "returned %s for file %s: %s\n",
2778 pixbuf ? "something" : "nothing",
2779 filename,
2780 error ? error->message : "(no error message)");
2781 if (error)
2782 g_error_free(error);
2783 if (pixbuf)
2784 g_object_unref(G_OBJECT(pixbuf));
2785 return NULL;
2788 return pixbuf;
2791 GdkPixbuf *pidgin_pixbuf_new_from_file_at_scale(const char *filename, int width, int height, gboolean preserve_aspect_ratio)
2793 GdkPixbuf *pixbuf;
2794 GError *error = NULL;
2796 g_return_val_if_fail(filename != NULL, NULL);
2797 g_return_val_if_fail(filename[0] != '\0', NULL);
2799 pixbuf = gdk_pixbuf_new_from_file_at_scale(filename,
2800 width, height, preserve_aspect_ratio, &error);
2801 if (!pixbuf || error) {
2802 purple_debug_warning("gtkutils", "gdk_pixbuf_new_from_file_at_scale() "
2803 "returned %s for file %s: %s\n",
2804 pixbuf ? "something" : "nothing",
2805 filename,
2806 error ? error->message : "(no error message)");
2807 if (error)
2808 g_error_free(error);
2809 if (pixbuf)
2810 g_object_unref(G_OBJECT(pixbuf));
2811 return NULL;
2814 return pixbuf;
2817 GdkPixbuf *
2818 pidgin_pixbuf_scale_down(GdkPixbuf *src, guint max_width, guint max_height,
2819 GdkInterpType interp_type, gboolean preserve_ratio)
2821 guint cur_w, cur_h;
2822 GdkPixbuf *dst;
2824 g_return_val_if_fail(src != NULL, NULL);
2826 if (max_width == 0 || max_height == 0) {
2827 g_object_unref(src);
2828 g_return_val_if_reached(NULL);
2831 cur_w = gdk_pixbuf_get_width(src);
2832 cur_h = gdk_pixbuf_get_height(src);
2834 if (cur_w <= max_width && cur_h <= max_height)
2835 return src;
2837 /* cur_ratio = cur_w / cur_h
2838 * max_ratio = max_w / max_h
2841 if (!preserve_ratio) {
2842 cur_w = MIN(cur_w, max_width);
2843 cur_h = MIN(cur_h, max_height);
2844 } else if ((guint64)cur_w * max_height > (guint64)max_width * cur_h) {
2845 /* cur_w / cur_h > max_width / max_height */
2846 cur_h = (guint64)max_width * cur_h / cur_w;
2847 cur_w = max_width;
2848 } else {
2849 cur_w = (guint64)max_height * cur_w / cur_h;
2850 cur_h = max_height;
2853 if (cur_w <= 0)
2854 cur_w = 1;
2855 if (cur_h <= 0)
2856 cur_h = 1;
2858 dst = gdk_pixbuf_scale_simple(src, cur_w, cur_h, interp_type);
2859 g_object_unref(src);
2861 return dst;
2864 GtkWidget *
2865 pidgin_make_scrollable(GtkWidget *child, GtkPolicyType hscrollbar_policy, GtkPolicyType vscrollbar_policy, GtkShadowType shadow_type, int width, int height)
2867 GtkWidget *sw = gtk_scrolled_window_new(NULL, NULL);
2869 if (G_LIKELY(sw)) {
2870 gtk_widget_show(sw);
2871 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), hscrollbar_policy, vscrollbar_policy);
2872 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw), shadow_type);
2873 if (width != -1 || height != -1)
2874 gtk_widget_set_size_request(sw, width, height);
2875 if (child) {
2876 gtk_container_add(GTK_CONTAINER(sw), child);
2878 return sw;
2881 return child;