I just noticed I didn't differentiate bn_IN from bn when I added this translation.
[pidgin-git.git] / pidgin / gtkutils.c
blob34b3a79d1b315307a12cca56a7624ffb05f10c76
1 /**
2 * @file gtkutils.c GTK+ utility functions
3 * @ingroup pidgin
4 */
6 /* pidgin
8 * Pidgin is the legal property of its developers, whose names are too numerous
9 * to list here. Please refer to the COPYRIGHT file distributed with this
10 * source distribution.
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
26 #define _PIDGIN_GTKUTILS_C_
28 #include "internal.h"
29 #include "pidgin.h"
31 #ifndef _WIN32
32 # include <X11/Xlib.h>
33 #else
34 # ifdef small
35 # undef small
36 # endif
37 #endif /*_WIN32*/
39 #ifdef USE_GTKSPELL
40 # include <gtkspell/gtkspell.h>
41 # ifdef _WIN32
42 # include "wspell.h"
43 # endif
44 #endif
46 #include <gdk/gdkkeysyms.h>
48 #include "conversation.h"
49 #include "debug.h"
50 #include "desktopitem.h"
51 #include "imgstore.h"
52 #include "notify.h"
53 #include "prefs.h"
54 #include "prpl.h"
55 #include "request.h"
56 #include "signals.h"
57 #include "sound.h"
58 #include "util.h"
60 #include "gtkaccount.h"
61 #include "gtkprefs.h"
63 #include "gtkconv.h"
64 #include "gtkdialogs.h"
65 #include "gtkimhtml.h"
66 #include "gtkimhtmltoolbar.h"
67 #include "pidginstock.h"
68 #include "gtkthemes.h"
69 #include "gtkutils.h"
70 #include "pidgin/minidialog.h"
72 typedef struct {
73 GtkWidget *menu;
74 gint default_item;
75 } AopMenu;
77 static guint accels_save_timer = 0;
78 static GSList *registered_url_handlers = NULL;
80 static gboolean
81 url_clicked_idle_cb(gpointer data)
83 purple_notify_uri(NULL, data);
84 g_free(data);
85 return FALSE;
88 static gboolean
89 url_clicked_cb(GtkIMHtml *unused, GtkIMHtmlLink *link)
91 const char *uri = gtk_imhtml_link_get_url(link);
92 g_idle_add(url_clicked_idle_cb, g_strdup(uri));
93 return TRUE;
96 static GtkIMHtmlFuncs gtkimhtml_cbs = {
97 (GtkIMHtmlGetImageFunc)purple_imgstore_find_by_id,
98 (GtkIMHtmlGetImageDataFunc)purple_imgstore_get_data,
99 (GtkIMHtmlGetImageSizeFunc)purple_imgstore_get_size,
100 (GtkIMHtmlGetImageFilenameFunc)purple_imgstore_get_filename,
101 purple_imgstore_ref_by_id,
102 purple_imgstore_unref_by_id,
105 void
106 pidgin_setup_imhtml(GtkWidget *imhtml)
108 g_return_if_fail(imhtml != NULL);
109 g_return_if_fail(GTK_IS_IMHTML(imhtml));
111 pidgin_themes_smiley_themeize(imhtml);
113 gtk_imhtml_set_funcs(GTK_IMHTML(imhtml), &gtkimhtml_cbs);
115 #ifdef _WIN32
116 if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/use_theme_font")) {
117 PangoFontDescription *desc;
118 const char *font = purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/custom_font");
119 desc = pango_font_description_from_string(font);
120 if (desc) {
121 gtk_widget_modify_font(imhtml, desc);
122 pango_font_description_free(desc);
125 #endif
129 static
130 void pidgin_window_init(GtkWindow *wnd, const char *title, guint border_width, const char *role, gboolean resizable)
132 if (title)
133 gtk_window_set_title(wnd, title);
134 #ifdef _WIN32
135 else
136 gtk_window_set_title(wnd, PIDGIN_ALERT_TITLE);
137 #endif
138 gtk_container_set_border_width(GTK_CONTAINER(wnd), border_width);
139 if (role)
140 gtk_window_set_role(wnd, role);
141 gtk_window_set_resizable(wnd, resizable);
144 GtkWidget *
145 pidgin_create_window(const char *title, guint border_width, const char *role, gboolean resizable)
147 GtkWindow *wnd = NULL;
149 wnd = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
150 pidgin_window_init(wnd, title, border_width, role, resizable);
152 return GTK_WIDGET(wnd);
155 GtkWidget *
156 pidgin_create_small_button(GtkWidget *image)
158 GtkWidget *button;
160 button = gtk_button_new();
161 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
163 /* don't allow focus on the close button */
164 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
166 /* set style to make it as small as possible */
167 gtk_widget_set_name(button, "pidgin-small-close-button");
169 gtk_widget_show(image);
171 gtk_container_add(GTK_CONTAINER(button), image);
173 return button;
176 GtkWidget *
177 pidgin_create_dialog(const char *title, guint border_width, const char *role, gboolean resizable)
179 GtkWindow *wnd = NULL;
181 wnd = GTK_WINDOW(gtk_dialog_new());
182 pidgin_window_init(wnd, title, border_width, role, resizable);
183 g_object_set(G_OBJECT(wnd), "has-separator", FALSE, NULL);
185 return GTK_WIDGET(wnd);
188 GtkWidget *
189 pidgin_dialog_get_vbox_with_properties(GtkDialog *dialog, gboolean homogeneous, gint spacing)
191 GtkBox *vbox = GTK_BOX(GTK_DIALOG(dialog)->vbox);
192 gtk_box_set_homogeneous(vbox, homogeneous);
193 gtk_box_set_spacing(vbox, spacing);
194 return GTK_WIDGET(vbox);
197 GtkWidget *pidgin_dialog_get_vbox(GtkDialog *dialog)
199 return GTK_DIALOG(dialog)->vbox;
202 GtkWidget *pidgin_dialog_get_action_area(GtkDialog *dialog)
204 return GTK_DIALOG(dialog)->action_area;
207 GtkWidget *pidgin_dialog_add_button(GtkDialog *dialog, const char *label,
208 GCallback callback, gpointer callbackdata)
210 GtkWidget *button = gtk_button_new_from_stock(label);
211 GtkWidget *bbox = pidgin_dialog_get_action_area(dialog);
212 gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
213 if (callback)
214 g_signal_connect(G_OBJECT(button), "clicked", callback, callbackdata);
215 gtk_widget_show(button);
216 return button;
219 GtkWidget *
220 pidgin_create_imhtml(gboolean editable, GtkWidget **imhtml_ret, GtkWidget **toolbar_ret, GtkWidget **sw_ret)
222 GtkWidget *frame;
223 GtkWidget *imhtml;
224 GtkWidget *sep;
225 GtkWidget *sw;
226 GtkWidget *toolbar = NULL;
227 GtkWidget *vbox;
229 frame = gtk_frame_new(NULL);
230 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
232 vbox = gtk_vbox_new(FALSE, 0);
233 gtk_container_add(GTK_CONTAINER(frame), vbox);
234 gtk_widget_show(vbox);
236 if (editable) {
237 toolbar = gtk_imhtmltoolbar_new();
238 gtk_box_pack_start(GTK_BOX(vbox), toolbar, FALSE, FALSE, 0);
239 gtk_widget_show(toolbar);
241 sep = gtk_hseparator_new();
242 gtk_box_pack_start(GTK_BOX(vbox), sep, FALSE, FALSE, 0);
243 g_signal_connect_swapped(G_OBJECT(toolbar), "show", G_CALLBACK(gtk_widget_show), sep);
244 g_signal_connect_swapped(G_OBJECT(toolbar), "hide", G_CALLBACK(gtk_widget_hide), sep);
245 gtk_widget_show(sep);
248 sw = gtk_scrolled_window_new(NULL, NULL);
249 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
250 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
251 gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0);
252 gtk_widget_show(sw);
254 imhtml = gtk_imhtml_new(NULL, NULL);
255 gtk_imhtml_set_editable(GTK_IMHTML(imhtml), editable);
256 gtk_imhtml_set_format_functions(GTK_IMHTML(imhtml), GTK_IMHTML_ALL ^ GTK_IMHTML_IMAGE);
257 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(imhtml), GTK_WRAP_WORD_CHAR);
258 #ifdef USE_GTKSPELL
259 if (editable && purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/spellcheck"))
260 pidgin_setup_gtkspell(GTK_TEXT_VIEW(imhtml));
261 #endif
262 gtk_widget_show(imhtml);
264 if (editable) {
265 gtk_imhtmltoolbar_attach(GTK_IMHTMLTOOLBAR(toolbar), imhtml);
266 gtk_imhtmltoolbar_associate_smileys(GTK_IMHTMLTOOLBAR(toolbar), "default");
268 pidgin_setup_imhtml(imhtml);
270 gtk_container_add(GTK_CONTAINER(sw), imhtml);
272 if (imhtml_ret != NULL)
273 *imhtml_ret = imhtml;
275 if (editable && (toolbar_ret != NULL))
276 *toolbar_ret = toolbar;
278 if (sw_ret != NULL)
279 *sw_ret = sw;
281 return frame;
284 void
285 pidgin_set_sensitive_if_input(GtkWidget *entry, GtkWidget *dialog)
287 const char *text = gtk_entry_get_text(GTK_ENTRY(entry));
288 gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog), GTK_RESPONSE_OK,
289 (*text != '\0'));
292 void
293 pidgin_toggle_sensitive(GtkWidget *widget, GtkWidget *to_toggle)
295 gboolean sensitivity;
297 if (to_toggle == NULL)
298 return;
300 sensitivity = GTK_WIDGET_IS_SENSITIVE(to_toggle);
302 gtk_widget_set_sensitive(to_toggle, !sensitivity);
305 void
306 pidgin_toggle_sensitive_array(GtkWidget *w, GPtrArray *data)
308 gboolean sensitivity;
309 gpointer element;
310 int i;
312 for (i=0; i < data->len; i++) {
313 element = g_ptr_array_index(data,i);
314 if (element == NULL)
315 continue;
317 sensitivity = GTK_WIDGET_IS_SENSITIVE(element);
319 gtk_widget_set_sensitive(element, !sensitivity);
323 void
324 pidgin_toggle_showhide(GtkWidget *widget, GtkWidget *to_toggle)
326 if (to_toggle == NULL)
327 return;
329 if (GTK_WIDGET_VISIBLE(to_toggle))
330 gtk_widget_hide(to_toggle);
331 else
332 gtk_widget_show(to_toggle);
335 GtkWidget *pidgin_separator(GtkWidget *menu)
337 GtkWidget *menuitem;
339 menuitem = gtk_separator_menu_item_new();
340 gtk_widget_show(menuitem);
341 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
342 return menuitem;
345 GtkWidget *pidgin_new_item(GtkWidget *menu, const char *str)
347 GtkWidget *menuitem;
348 GtkWidget *label;
350 menuitem = gtk_menu_item_new();
351 if (menu)
352 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
353 gtk_widget_show(menuitem);
355 label = gtk_label_new(str);
356 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
357 gtk_label_set_pattern(GTK_LABEL(label), "_");
358 gtk_container_add(GTK_CONTAINER(menuitem), label);
359 gtk_widget_show(label);
360 /* FIXME: Go back and fix this
361 gtk_widget_add_accelerator(menuitem, "activate", accel, str[0],
362 GDK_MOD1_MASK, GTK_ACCEL_LOCKED);
364 pidgin_set_accessible_label (menuitem, label);
365 return menuitem;
368 GtkWidget *pidgin_new_check_item(GtkWidget *menu, const char *str,
369 GCallback cb, gpointer data, gboolean checked)
371 GtkWidget *menuitem;
372 menuitem = gtk_check_menu_item_new_with_mnemonic(str);
374 if (menu)
375 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
377 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), checked);
379 if (cb)
380 g_signal_connect(G_OBJECT(menuitem), "activate", cb, data);
382 gtk_widget_show_all(menuitem);
384 return menuitem;
387 GtkWidget *
388 pidgin_pixbuf_toolbar_button_from_stock(const char *icon)
390 GtkWidget *button, *image, *bbox;
392 button = gtk_toggle_button_new();
393 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
395 bbox = gtk_vbox_new(FALSE, 0);
397 gtk_container_add (GTK_CONTAINER(button), bbox);
399 image = gtk_image_new_from_stock(icon, gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL));
400 gtk_box_pack_start(GTK_BOX(bbox), image, FALSE, FALSE, 0);
402 gtk_widget_show_all(bbox);
404 return button;
407 GtkWidget *
408 pidgin_pixbuf_button_from_stock(const char *text, const char *icon,
409 PidginButtonOrientation style)
411 GtkWidget *button, *image, *label, *bbox, *ibox, *lbox = NULL;
413 button = gtk_button_new();
415 if (style == PIDGIN_BUTTON_HORIZONTAL) {
416 bbox = gtk_hbox_new(FALSE, 0);
417 ibox = gtk_hbox_new(FALSE, 0);
418 if (text)
419 lbox = gtk_hbox_new(FALSE, 0);
420 } else {
421 bbox = gtk_vbox_new(FALSE, 0);
422 ibox = gtk_vbox_new(FALSE, 0);
423 if (text)
424 lbox = gtk_vbox_new(FALSE, 0);
427 gtk_container_add(GTK_CONTAINER(button), bbox);
429 if (icon) {
430 gtk_box_pack_start(GTK_BOX(bbox), ibox, TRUE, TRUE, 0);
431 image = gtk_image_new_from_stock(icon, GTK_ICON_SIZE_BUTTON);
432 gtk_box_pack_end(GTK_BOX(ibox), image, FALSE, TRUE, 0);
435 if (text) {
436 gtk_box_pack_start(GTK_BOX(bbox), lbox, TRUE, TRUE, 0);
437 label = gtk_label_new(NULL);
438 gtk_label_set_text_with_mnemonic(GTK_LABEL(label), text);
439 gtk_label_set_mnemonic_widget(GTK_LABEL(label), button);
440 gtk_box_pack_start(GTK_BOX(lbox), label, FALSE, TRUE, 0);
441 pidgin_set_accessible_label (button, label);
444 gtk_widget_show_all(bbox);
446 return button;
450 GtkWidget *pidgin_new_item_from_stock(GtkWidget *menu, const char *str, const char *icon, GCallback cb, gpointer data, guint accel_key, guint accel_mods, char *mod)
452 GtkWidget *menuitem;
454 GtkWidget *hbox;
455 GtkWidget *label;
457 GtkWidget *image;
459 if (icon == NULL)
460 menuitem = gtk_menu_item_new_with_mnemonic(str);
461 else
462 menuitem = gtk_image_menu_item_new_with_mnemonic(str);
464 if (menu)
465 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
467 if (cb)
468 g_signal_connect(G_OBJECT(menuitem), "activate", cb, data);
470 if (icon != NULL) {
471 image = gtk_image_new_from_stock(icon, gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL));
472 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image);
474 /* FIXME: this isn't right
475 if (mod) {
476 label = gtk_label_new(mod);
477 gtk_box_pack_end(GTK_BOX(hbox), label, FALSE, FALSE, 2);
478 gtk_widget_show(label);
482 if (accel_key) {
483 gtk_widget_add_accelerator(menuitem, "activate", accel, accel_key,
484 accel_mods, GTK_ACCEL_LOCKED);
488 gtk_widget_show_all(menuitem);
490 return menuitem;
493 GtkWidget *
494 pidgin_make_frame(GtkWidget *parent, const char *title)
496 GtkWidget *vbox, *label, *hbox;
497 char *labeltitle;
499 vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
500 gtk_box_pack_start(GTK_BOX(parent), vbox, FALSE, FALSE, 0);
501 gtk_widget_show(vbox);
503 label = gtk_label_new(NULL);
505 labeltitle = g_strdup_printf("<span weight=\"bold\">%s</span>", title);
506 gtk_label_set_markup(GTK_LABEL(label), labeltitle);
507 g_free(labeltitle);
509 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
510 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
511 gtk_widget_show(label);
512 pidgin_set_accessible_label (vbox, label);
514 hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
515 gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
516 gtk_widget_show(hbox);
518 label = gtk_label_new(" ");
519 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
520 gtk_widget_show(label);
522 vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
523 gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);
524 gtk_widget_show(vbox);
526 return vbox;
529 static gpointer
530 aop_option_menu_get_selected(GtkWidget *optmenu, GtkWidget **p_item)
532 GtkWidget *menu = gtk_option_menu_get_menu(GTK_OPTION_MENU(optmenu));
533 GtkWidget *item = gtk_menu_get_active(GTK_MENU(menu));
534 if (p_item)
535 (*p_item) = item;
536 return item ? g_object_get_data(G_OBJECT(item), "aop_per_item_data") : NULL;
539 static void
540 aop_menu_cb(GtkWidget *optmenu, GCallback cb)
542 GtkWidget *item;
543 gpointer per_item_data;
545 per_item_data = aop_option_menu_get_selected(optmenu, &item);
547 if (cb != NULL) {
548 ((void (*)(GtkWidget *, gpointer, gpointer))cb)(item, per_item_data, g_object_get_data(G_OBJECT(optmenu), "user_data"));
552 static GtkWidget *
553 aop_menu_item_new(GtkSizeGroup *sg, GdkPixbuf *pixbuf, const char *lbl, gpointer per_item_data, const char *data)
555 GtkWidget *item;
556 GtkWidget *hbox;
557 GtkWidget *image;
558 GtkWidget *label;
560 item = gtk_menu_item_new();
561 gtk_widget_show(item);
563 hbox = gtk_hbox_new(FALSE, 4);
564 gtk_widget_show(hbox);
566 /* Create the image */
567 if (pixbuf == NULL)
568 image = gtk_image_new();
569 else
570 image = gtk_image_new_from_pixbuf(pixbuf);
571 gtk_widget_show(image);
573 if (sg)
574 gtk_size_group_add_widget(sg, image);
576 /* Create the label */
577 label = gtk_label_new (lbl);
578 gtk_widget_show (label);
579 gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
580 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
582 gtk_container_add(GTK_CONTAINER(item), hbox);
583 gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0);
584 gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
586 g_object_set_data(G_OBJECT (item), data, per_item_data);
587 g_object_set_data(G_OBJECT (item), "aop_per_item_data", per_item_data);
589 pidgin_set_accessible_label(item, label);
591 return item;
594 static GdkPixbuf *
595 pidgin_create_prpl_icon_from_prpl(PurplePlugin *prpl, PidginPrplIconSize size, PurpleAccount *account)
597 PurplePluginProtocolInfo *prpl_info;
598 const char *protoname = NULL;
599 char *tmp;
600 char *filename = NULL;
601 GdkPixbuf *pixbuf;
603 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
604 if (prpl_info->list_icon == NULL)
605 return NULL;
607 protoname = prpl_info->list_icon(account, NULL);
608 if (protoname == NULL)
609 return NULL;
612 * Status icons will be themeable too, and then it will look up
613 * protoname from the theme
615 tmp = g_strconcat(protoname, ".png", NULL);
617 filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "protocols",
618 size == PIDGIN_PRPL_ICON_SMALL ? "16" :
619 size == PIDGIN_PRPL_ICON_MEDIUM ? "22" : "48",
620 tmp, NULL);
621 g_free(tmp);
623 pixbuf = gdk_pixbuf_new_from_file(filename, NULL);
624 g_free(filename);
626 return pixbuf;
629 static GtkWidget *
630 aop_option_menu_new(AopMenu *aop_menu, GCallback cb, gpointer user_data)
632 GtkWidget *optmenu;
634 optmenu = gtk_option_menu_new();
635 gtk_widget_show(optmenu);
636 gtk_option_menu_set_menu(GTK_OPTION_MENU(optmenu), aop_menu->menu);
638 if (aop_menu->default_item != -1)
639 gtk_option_menu_set_history(GTK_OPTION_MENU(optmenu), aop_menu->default_item);
641 g_object_set_data_full(G_OBJECT(optmenu), "aop_menu", aop_menu, (GDestroyNotify)g_free);
642 g_object_set_data(G_OBJECT(optmenu), "user_data", user_data);
644 g_signal_connect(G_OBJECT(optmenu), "changed", G_CALLBACK(aop_menu_cb), cb);
646 return optmenu;
649 static void
650 aop_option_menu_replace_menu(GtkWidget *optmenu, AopMenu *new_aop_menu)
652 if (gtk_option_menu_get_menu(GTK_OPTION_MENU(optmenu)))
653 gtk_option_menu_remove_menu(GTK_OPTION_MENU(optmenu));
655 gtk_option_menu_set_menu(GTK_OPTION_MENU(optmenu), new_aop_menu->menu);
657 if (new_aop_menu->default_item != -1)
658 gtk_option_menu_set_history(GTK_OPTION_MENU(optmenu), new_aop_menu->default_item);
660 g_object_set_data_full(G_OBJECT(optmenu), "aop_menu", new_aop_menu, (GDestroyNotify)g_free);
663 static void
664 aop_option_menu_select_by_data(GtkWidget *optmenu, gpointer data)
666 guint idx;
667 GList *llItr = NULL;
669 for (idx = 0, llItr = GTK_MENU_SHELL(gtk_option_menu_get_menu(GTK_OPTION_MENU(optmenu)))->children;
670 llItr != NULL;
671 llItr = llItr->next, idx++) {
672 if (data == g_object_get_data(G_OBJECT(llItr->data), "aop_per_item_data")) {
673 gtk_option_menu_set_history(GTK_OPTION_MENU(optmenu), idx);
674 break;
679 static AopMenu *
680 create_protocols_menu(const char *default_proto_id)
682 AopMenu *aop_menu = NULL;
683 PurplePlugin *plugin;
684 GdkPixbuf *pixbuf = NULL;
685 GtkSizeGroup *sg;
686 GList *p;
687 const char *gtalk_name = NULL;
688 int i;
690 aop_menu = g_malloc0(sizeof(AopMenu));
691 aop_menu->default_item = -1;
692 aop_menu->menu = gtk_menu_new();
693 gtk_widget_show(aop_menu->menu);
694 sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
696 if (purple_find_prpl("prpl-jabber"))
697 gtalk_name = _("Google Talk");
699 for (p = purple_plugins_get_protocols(), i = 0;
700 p != NULL;
701 p = p->next, i++) {
703 plugin = (PurplePlugin *)p->data;
705 if (gtalk_name && strcmp(gtalk_name, plugin->info->name) < 0) {
706 char *filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "protocols",
707 "16", "google-talk.png", NULL);
708 GtkWidget *item;
710 pixbuf = gdk_pixbuf_new_from_file(filename, NULL);
711 g_free(filename);
713 gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu->menu),
714 item = aop_menu_item_new(sg, pixbuf, gtalk_name, "prpl-jabber", "protocol"));
715 g_object_set_data(G_OBJECT(item), "fake", GINT_TO_POINTER(1));
717 if (pixbuf)
718 g_object_unref(pixbuf);
720 gtalk_name = NULL;
721 i++;
724 pixbuf = pidgin_create_prpl_icon_from_prpl(plugin, PIDGIN_PRPL_ICON_SMALL, NULL);
726 gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu->menu),
727 aop_menu_item_new(sg, pixbuf, plugin->info->name, plugin->info->id, "protocol"));
729 if (pixbuf)
730 g_object_unref(pixbuf);
732 if (default_proto_id != NULL && !strcmp(plugin->info->id, default_proto_id))
733 aop_menu->default_item = i;
736 g_object_unref(sg);
738 return aop_menu;
741 GtkWidget *
742 pidgin_protocol_option_menu_new(const char *id, GCallback cb,
743 gpointer user_data)
745 return aop_option_menu_new(create_protocols_menu(id), cb, user_data);
748 const char *
749 pidgin_protocol_option_menu_get_selected(GtkWidget *optmenu)
751 return (const char *)aop_option_menu_get_selected(optmenu, NULL);
754 PurpleAccount *
755 pidgin_account_option_menu_get_selected(GtkWidget *optmenu)
757 return (PurpleAccount *)aop_option_menu_get_selected(optmenu, NULL);
760 static AopMenu *
761 create_account_menu(PurpleAccount *default_account,
762 PurpleFilterAccountFunc filter_func, gboolean show_all)
764 AopMenu *aop_menu = NULL;
765 PurpleAccount *account;
766 GdkPixbuf *pixbuf = NULL;
767 GList *list;
768 GList *p;
769 GtkSizeGroup *sg;
770 int i;
771 char buf[256];
773 if (show_all)
774 list = purple_accounts_get_all();
775 else
776 list = purple_connections_get_all();
778 aop_menu = g_malloc0(sizeof(AopMenu));
779 aop_menu->default_item = -1;
780 aop_menu->menu = gtk_menu_new();
781 gtk_widget_show(aop_menu->menu);
782 sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
784 for (p = list, i = 0; p != NULL; p = p->next, i++) {
785 if (show_all)
786 account = (PurpleAccount *)p->data;
787 else {
788 PurpleConnection *gc = (PurpleConnection *)p->data;
790 account = purple_connection_get_account(gc);
793 if (filter_func && !filter_func(account)) {
794 i--;
795 continue;
798 pixbuf = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL);
800 if (pixbuf) {
801 if (purple_account_is_disconnected(account) && show_all &&
802 purple_connections_get_all())
803 gdk_pixbuf_saturate_and_pixelate(pixbuf, pixbuf, 0.0, FALSE);
806 if (purple_account_get_alias(account)) {
807 g_snprintf(buf, sizeof(buf), "%s (%s) (%s)",
808 purple_account_get_username(account),
809 purple_account_get_alias(account),
810 purple_account_get_protocol_name(account));
811 } else {
812 g_snprintf(buf, sizeof(buf), "%s (%s)",
813 purple_account_get_username(account),
814 purple_account_get_protocol_name(account));
817 gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu->menu),
818 aop_menu_item_new(sg, pixbuf, buf, account, "account"));
820 if (pixbuf)
821 g_object_unref(pixbuf);
823 if (default_account && account == default_account)
824 aop_menu->default_item = i;
827 g_object_unref(sg);
829 return aop_menu;
832 static void
833 regenerate_account_menu(GtkWidget *optmenu)
835 gboolean show_all;
836 PurpleAccount *account;
837 PurpleFilterAccountFunc filter_func;
839 account = (PurpleAccount *)aop_option_menu_get_selected(optmenu, NULL);
840 show_all = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(optmenu), "show_all"));
841 filter_func = g_object_get_data(G_OBJECT(optmenu), "filter_func");
843 aop_option_menu_replace_menu(optmenu, create_account_menu(account, filter_func, show_all));
846 static void
847 account_menu_sign_on_off_cb(PurpleConnection *gc, GtkWidget *optmenu)
849 regenerate_account_menu(optmenu);
852 static void
853 account_menu_added_removed_cb(PurpleAccount *account, GtkWidget *optmenu)
855 regenerate_account_menu(optmenu);
858 static gboolean
859 account_menu_destroyed_cb(GtkWidget *optmenu, GdkEvent *event,
860 void *user_data)
862 purple_signals_disconnect_by_handle(optmenu);
864 return FALSE;
867 void
868 pidgin_account_option_menu_set_selected(GtkWidget *optmenu, PurpleAccount *account)
870 aop_option_menu_select_by_data(optmenu, account);
873 GtkWidget *
874 pidgin_account_option_menu_new(PurpleAccount *default_account,
875 gboolean show_all, GCallback cb,
876 PurpleFilterAccountFunc filter_func,
877 gpointer user_data)
879 GtkWidget *optmenu;
881 /* Create the option menu */
882 optmenu = aop_option_menu_new(create_account_menu(default_account, filter_func, show_all), cb, user_data);
884 g_signal_connect(G_OBJECT(optmenu), "destroy",
885 G_CALLBACK(account_menu_destroyed_cb), NULL);
887 /* Register the purple sign on/off event callbacks. */
888 purple_signal_connect(purple_connections_get_handle(), "signed-on",
889 optmenu, PURPLE_CALLBACK(account_menu_sign_on_off_cb),
890 optmenu);
891 purple_signal_connect(purple_connections_get_handle(), "signed-off",
892 optmenu, PURPLE_CALLBACK(account_menu_sign_on_off_cb),
893 optmenu);
894 purple_signal_connect(purple_accounts_get_handle(), "account-added",
895 optmenu, PURPLE_CALLBACK(account_menu_added_removed_cb),
896 optmenu);
897 purple_signal_connect(purple_accounts_get_handle(), "account-removed",
898 optmenu, PURPLE_CALLBACK(account_menu_added_removed_cb),
899 optmenu);
901 /* Set some data. */
902 g_object_set_data(G_OBJECT(optmenu), "user_data", user_data);
903 g_object_set_data(G_OBJECT(optmenu), "show_all", GINT_TO_POINTER(show_all));
904 g_object_set_data(G_OBJECT(optmenu), "filter_func", filter_func);
906 return optmenu;
909 gboolean
910 pidgin_check_if_dir(const char *path, GtkFileSelection *filesel)
912 char *dirname = NULL;
914 if (g_file_test(path, G_FILE_TEST_IS_DIR)) {
915 /* append a / if needed */
916 if (path[strlen(path) - 1] != G_DIR_SEPARATOR) {
917 dirname = g_strconcat(path, G_DIR_SEPARATOR_S, NULL);
919 gtk_file_selection_set_filename(filesel, (dirname != NULL) ? dirname : path);
920 g_free(dirname);
921 return TRUE;
924 return FALSE;
927 void
928 pidgin_setup_gtkspell(GtkTextView *textview)
930 #ifdef USE_GTKSPELL
931 GError *error = NULL;
932 char *locale = NULL;
934 g_return_if_fail(textview != NULL);
935 g_return_if_fail(GTK_IS_TEXT_VIEW(textview));
937 if (gtkspell_new_attach(textview, locale, &error) == NULL && error)
939 purple_debug_warning("gtkspell", "Failed to setup GtkSpell: %s\n",
940 error->message);
941 g_error_free(error);
943 #endif /* USE_GTKSPELL */
946 void
947 pidgin_save_accels_cb(GtkAccelGroup *accel_group, guint arg1,
948 GdkModifierType arg2, GClosure *arg3,
949 gpointer data)
951 purple_debug(PURPLE_DEBUG_MISC, "accels",
952 "accel changed, scheduling save.\n");
954 if (!accels_save_timer)
955 accels_save_timer = purple_timeout_add_seconds(5, pidgin_save_accels,
956 NULL);
959 gboolean
960 pidgin_save_accels(gpointer data)
962 char *filename = NULL;
964 filename = g_build_filename(purple_user_dir(), G_DIR_SEPARATOR_S,
965 "accels", NULL);
966 purple_debug(PURPLE_DEBUG_MISC, "accels", "saving accels to %s\n", filename);
967 gtk_accel_map_save(filename);
968 g_free(filename);
970 accels_save_timer = 0;
971 return FALSE;
974 void
975 pidgin_load_accels()
977 char *filename = NULL;
979 filename = g_build_filename(purple_user_dir(), G_DIR_SEPARATOR_S,
980 "accels", NULL);
981 gtk_accel_map_load(filename);
982 g_free(filename);
985 static void
986 show_retrieveing_info(PurpleConnection *conn, const char *name)
988 PurpleNotifyUserInfo *info = purple_notify_user_info_new();
989 purple_notify_user_info_add_pair(info, _("Information"), _("Retrieving..."));
990 purple_notify_userinfo(conn, name, info, NULL, NULL);
991 purple_notify_user_info_destroy(info);
994 void pidgin_retrieve_user_info(PurpleConnection *conn, const char *name)
996 show_retrieveing_info(conn, name);
997 serv_get_info(conn, name);
1000 void pidgin_retrieve_user_info_in_chat(PurpleConnection *conn, const char *name, int chat)
1002 char *who = NULL;
1003 PurplePluginProtocolInfo *prpl_info = NULL;
1005 if (chat < 0) {
1006 pidgin_retrieve_user_info(conn, name);
1007 return;
1010 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(conn->prpl);
1011 if (prpl_info != NULL && prpl_info->get_cb_real_name)
1012 who = prpl_info->get_cb_real_name(conn, chat, name);
1013 if (prpl_info == NULL || prpl_info->get_cb_info == NULL) {
1014 pidgin_retrieve_user_info(conn, who ? who : name);
1015 g_free(who);
1016 return;
1019 show_retrieveing_info(conn, who ? who : name);
1020 prpl_info->get_cb_info(conn, chat, name);
1021 g_free(who);
1024 gboolean
1025 pidgin_parse_x_im_contact(const char *msg, gboolean all_accounts,
1026 PurpleAccount **ret_account, char **ret_protocol,
1027 char **ret_username, char **ret_alias)
1029 char *protocol = NULL;
1030 char *username = NULL;
1031 char *alias = NULL;
1032 char *str;
1033 char *s;
1034 gboolean valid;
1036 g_return_val_if_fail(msg != NULL, FALSE);
1037 g_return_val_if_fail(ret_protocol != NULL, FALSE);
1038 g_return_val_if_fail(ret_username != NULL, FALSE);
1040 s = str = g_strdup(msg);
1042 while (*s != '\r' && *s != '\n' && *s != '\0')
1044 char *key, *value;
1046 key = s;
1048 /* Grab the key */
1049 while (*s != '\r' && *s != '\n' && *s != '\0' && *s != ' ')
1050 s++;
1052 if (*s == '\r') s++;
1054 if (*s == '\n')
1056 s++;
1057 continue;
1060 if (*s != '\0') *s++ = '\0';
1062 /* Clear past any whitespace */
1063 while (*s != '\0' && *s == ' ')
1064 s++;
1066 /* Now let's grab until the end of the line. */
1067 value = s;
1069 while (*s != '\r' && *s != '\n' && *s != '\0')
1070 s++;
1072 if (*s == '\r') *s++ = '\0';
1073 if (*s == '\n') *s++ = '\0';
1075 if (strchr(key, ':') != NULL)
1077 if (!g_ascii_strcasecmp(key, "X-IM-Username:"))
1078 username = g_strdup(value);
1079 else if (!g_ascii_strcasecmp(key, "X-IM-Protocol:"))
1080 protocol = g_strdup(value);
1081 else if (!g_ascii_strcasecmp(key, "X-IM-Alias:"))
1082 alias = g_strdup(value);
1086 if (username != NULL && protocol != NULL)
1088 valid = TRUE;
1090 *ret_username = username;
1091 *ret_protocol = protocol;
1093 if (ret_alias != NULL)
1094 *ret_alias = alias;
1096 /* Check for a compatible account. */
1097 if (ret_account != NULL)
1099 GList *list;
1100 PurpleAccount *account = NULL;
1101 GList *l;
1102 const char *protoname;
1104 if (all_accounts)
1105 list = purple_accounts_get_all();
1106 else
1107 list = purple_connections_get_all();
1109 for (l = list; l != NULL; l = l->next)
1111 PurpleConnection *gc;
1112 PurplePluginProtocolInfo *prpl_info = NULL;
1113 PurplePlugin *plugin;
1115 if (all_accounts)
1117 account = (PurpleAccount *)l->data;
1119 plugin = purple_plugins_find_with_id(
1120 purple_account_get_protocol_id(account));
1122 if (plugin == NULL)
1124 account = NULL;
1126 continue;
1129 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin);
1131 else
1133 gc = (PurpleConnection *)l->data;
1134 account = purple_connection_get_account(gc);
1136 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
1139 protoname = prpl_info->list_icon(account, NULL);
1141 if (!strcmp(protoname, protocol))
1142 break;
1144 account = NULL;
1147 /* Special case for AIM and ICQ */
1148 if (account == NULL && (!strcmp(protocol, "aim") ||
1149 !strcmp(protocol, "icq")))
1151 for (l = list; l != NULL; l = l->next)
1153 PurpleConnection *gc;
1154 PurplePluginProtocolInfo *prpl_info = NULL;
1155 PurplePlugin *plugin;
1157 if (all_accounts)
1159 account = (PurpleAccount *)l->data;
1161 plugin = purple_plugins_find_with_id(
1162 purple_account_get_protocol_id(account));
1164 if (plugin == NULL)
1166 account = NULL;
1168 continue;
1171 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin);
1173 else
1175 gc = (PurpleConnection *)l->data;
1176 account = purple_connection_get_account(gc);
1178 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
1181 protoname = prpl_info->list_icon(account, NULL);
1183 if (!strcmp(protoname, "aim") || !strcmp(protoname, "icq"))
1184 break;
1186 account = NULL;
1190 *ret_account = account;
1193 else
1195 valid = FALSE;
1197 g_free(username);
1198 g_free(protocol);
1199 g_free(alias);
1202 g_free(str);
1204 return valid;
1207 void
1208 pidgin_set_accessible_label (GtkWidget *w, GtkWidget *l)
1210 AtkObject *acc;
1211 const gchar *label_text;
1212 const gchar *existing_name;
1214 acc = gtk_widget_get_accessible (w);
1216 /* If this object has no name, set it's name with the label text */
1217 existing_name = atk_object_get_name (acc);
1218 if (!existing_name) {
1219 label_text = gtk_label_get_text (GTK_LABEL(l));
1220 if (label_text)
1221 atk_object_set_name (acc, label_text);
1224 pidgin_set_accessible_relations(w, l);
1227 void
1228 pidgin_set_accessible_relations (GtkWidget *w, GtkWidget *l)
1230 AtkObject *acc, *label;
1231 AtkObject *rel_obj[1];
1232 AtkRelationSet *set;
1233 AtkRelation *relation;
1235 acc = gtk_widget_get_accessible (w);
1236 label = gtk_widget_get_accessible (l);
1238 /* Make sure mnemonics work */
1239 gtk_label_set_mnemonic_widget(GTK_LABEL(l), w);
1241 /* Create the labeled-by relation */
1242 set = atk_object_ref_relation_set (acc);
1243 rel_obj[0] = label;
1244 relation = atk_relation_new (rel_obj, 1, ATK_RELATION_LABELLED_BY);
1245 atk_relation_set_add (set, relation);
1246 g_object_unref (relation);
1247 g_object_unref(set);
1249 /* Create the label-for relation */
1250 set = atk_object_ref_relation_set (label);
1251 rel_obj[0] = acc;
1252 relation = atk_relation_new (rel_obj, 1, ATK_RELATION_LABEL_FOR);
1253 atk_relation_set_add (set, relation);
1254 g_object_unref (relation);
1255 g_object_unref(set);
1258 void
1259 pidgin_menu_position_func_helper(GtkMenu *menu,
1260 gint *x,
1261 gint *y,
1262 gboolean *push_in,
1263 gpointer data)
1265 GtkWidget *widget;
1266 GtkRequisition requisition;
1267 GdkScreen *screen;
1268 GdkRectangle monitor;
1269 gint monitor_num;
1270 gint space_left, space_right, space_above, space_below;
1271 gint needed_width;
1272 gint needed_height;
1273 gint xthickness;
1274 gint ythickness;
1275 gboolean rtl;
1277 g_return_if_fail(GTK_IS_MENU(menu));
1279 widget = GTK_WIDGET(menu);
1280 screen = gtk_widget_get_screen(widget);
1281 xthickness = widget->style->xthickness;
1282 ythickness = widget->style->ythickness;
1283 rtl = (gtk_widget_get_direction(widget) == GTK_TEXT_DIR_RTL);
1286 * We need the requisition to figure out the right place to
1287 * popup the menu. In fact, we always need to ask here, since
1288 * if a size_request was queued while we weren't popped up,
1289 * the requisition won't have been recomputed yet.
1291 gtk_widget_size_request (widget, &requisition);
1293 monitor_num = gdk_screen_get_monitor_at_point (screen, *x, *y);
1295 push_in = FALSE;
1298 * The placement of popup menus horizontally works like this (with
1299 * RTL in parentheses)
1301 * - If there is enough room to the right (left) of the mouse cursor,
1302 * position the menu there.
1304 * - Otherwise, if if there is enough room to the left (right) of the
1305 * mouse cursor, position the menu there.
1307 * - Otherwise if the menu is smaller than the monitor, position it
1308 * on the side of the mouse cursor that has the most space available
1310 * - Otherwise (if there is simply not enough room for the menu on the
1311 * monitor), position it as far left (right) as possible.
1313 * Positioning in the vertical direction is similar: first try below
1314 * mouse cursor, then above.
1316 gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
1318 space_left = *x - monitor.x;
1319 space_right = monitor.x + monitor.width - *x - 1;
1320 space_above = *y - monitor.y;
1321 space_below = monitor.y + monitor.height - *y - 1;
1323 /* position horizontally */
1325 /* the amount of space we need to position the menu. Note the
1326 * menu is offset "xthickness" pixels
1328 needed_width = requisition.width - xthickness;
1330 if (needed_width <= space_left ||
1331 needed_width <= space_right)
1333 if ((rtl && needed_width <= space_left) ||
1334 (!rtl && needed_width > space_right))
1336 /* position left */
1337 *x = *x + xthickness - requisition.width + 1;
1339 else
1341 /* position right */
1342 *x = *x - xthickness;
1345 /* x is clamped on-screen further down */
1347 else if (requisition.width <= monitor.width)
1349 /* the menu is too big to fit on either side of the mouse
1350 * cursor, but smaller than the monitor. Position it on
1351 * the side that has the most space
1353 if (space_left > space_right)
1355 /* left justify */
1356 *x = monitor.x;
1358 else
1360 /* right justify */
1361 *x = monitor.x + monitor.width - requisition.width;
1364 else /* menu is simply too big for the monitor */
1366 if (rtl)
1368 /* right justify */
1369 *x = monitor.x + monitor.width - requisition.width;
1371 else
1373 /* left justify */
1374 *x = monitor.x;
1378 /* Position vertically. The algorithm is the same as above, but
1379 * simpler because we don't have to take RTL into account.
1381 needed_height = requisition.height - ythickness;
1383 if (needed_height <= space_above ||
1384 needed_height <= space_below)
1386 if (needed_height <= space_below)
1387 *y = *y - ythickness;
1388 else
1389 *y = *y + ythickness - requisition.height + 1;
1391 *y = CLAMP (*y, monitor.y,
1392 monitor.y + monitor.height - requisition.height);
1394 else if (needed_height > space_below && needed_height > space_above)
1396 if (space_below >= space_above)
1397 *y = monitor.y + monitor.height - requisition.height;
1398 else
1399 *y = monitor.y;
1401 else
1403 *y = monitor.y;
1408 void
1409 pidgin_treeview_popup_menu_position_func(GtkMenu *menu,
1410 gint *x,
1411 gint *y,
1412 gboolean *push_in,
1413 gpointer data)
1415 GtkWidget *widget = GTK_WIDGET(data);
1416 GtkTreeView *tv = GTK_TREE_VIEW(data);
1417 GtkTreePath *path;
1418 GtkTreeViewColumn *col;
1419 GdkRectangle rect;
1420 gint ythickness = GTK_WIDGET(menu)->style->ythickness;
1422 gdk_window_get_origin (widget->window, x, y);
1423 gtk_tree_view_get_cursor (tv, &path, &col);
1424 gtk_tree_view_get_cell_area (tv, path, col, &rect);
1426 *x += rect.x+rect.width;
1427 *y += rect.y+rect.height+ythickness;
1428 pidgin_menu_position_func_helper(menu, x, y, push_in, data);
1431 enum {
1432 DND_FILE_TRANSFER,
1433 DND_IM_IMAGE,
1434 DND_BUDDY_ICON
1437 typedef struct {
1438 char *filename;
1439 PurpleAccount *account;
1440 char *who;
1441 } _DndData;
1443 static void dnd_image_ok_callback(_DndData *data, int choice)
1445 gchar *filedata;
1446 size_t size;
1447 struct stat st;
1448 GError *err = NULL;
1449 PurpleConversation *conv;
1450 PidginConversation *gtkconv;
1451 GtkTextIter iter;
1452 int id;
1453 PurpleBuddy *buddy;
1454 PurpleContact *contact;
1455 switch (choice) {
1456 case DND_BUDDY_ICON:
1457 if (g_stat(data->filename, &st)) {
1458 char *str;
1460 str = g_strdup_printf(_("The following error has occurred loading %s: %s"),
1461 data->filename, g_strerror(errno));
1462 purple_notify_error(NULL, NULL,
1463 _("Failed to load image"),
1464 str);
1465 g_free(str);
1467 break;
1470 buddy = purple_find_buddy(data->account, data->who);
1471 if (!buddy) {
1472 purple_debug_info("custom-icon", "You can only set custom icons for people on your buddylist.\n");
1473 break;
1475 contact = purple_buddy_get_contact(buddy);
1476 purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode*)contact, data->filename);
1477 break;
1478 case DND_FILE_TRANSFER:
1479 serv_send_file(purple_account_get_connection(data->account), data->who, data->filename);
1480 break;
1481 case DND_IM_IMAGE:
1482 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, data->account, data->who);
1483 gtkconv = PIDGIN_CONVERSATION(conv);
1485 if (!g_file_get_contents(data->filename, &filedata, &size,
1486 &err)) {
1487 char *str;
1489 str = g_strdup_printf(_("The following error has occurred loading %s: %s"), data->filename, err->message);
1490 purple_notify_error(NULL, NULL,
1491 _("Failed to load image"),
1492 str);
1494 g_error_free(err);
1495 g_free(str);
1497 break;
1499 id = purple_imgstore_add_with_id(filedata, size, data->filename);
1501 gtk_text_buffer_get_iter_at_mark(GTK_IMHTML(gtkconv->entry)->text_buffer, &iter,
1502 gtk_text_buffer_get_insert(GTK_IMHTML(gtkconv->entry)->text_buffer));
1503 gtk_imhtml_insert_image_at_iter(GTK_IMHTML(gtkconv->entry), id, &iter);
1504 purple_imgstore_unref_by_id(id);
1506 break;
1508 g_free(data->filename);
1509 g_free(data->who);
1510 g_free(data);
1513 static void dnd_image_cancel_callback(_DndData *data, int choice)
1515 g_free(data->filename);
1516 g_free(data->who);
1517 g_free(data);
1520 static void dnd_set_icon_ok_cb(_DndData *data)
1522 dnd_image_ok_callback(data, DND_BUDDY_ICON);
1525 static void dnd_set_icon_cancel_cb(_DndData *data)
1527 g_free(data->filename);
1528 g_free(data->who);
1529 g_free(data);
1532 void
1533 pidgin_dnd_file_manage(GtkSelectionData *sd, PurpleAccount *account, const char *who)
1535 GdkPixbuf *pb;
1536 GList *files = purple_uri_list_extract_filenames((const gchar *)sd->data);
1537 PurpleConnection *gc = purple_account_get_connection(account);
1538 PurplePluginProtocolInfo *prpl_info = NULL;
1539 #ifndef _WIN32
1540 PurpleDesktopItem *item;
1541 #endif
1542 gchar *filename = NULL;
1543 gchar *basename = NULL;
1545 g_return_if_fail(account != NULL);
1546 g_return_if_fail(who != NULL);
1548 for ( ; files; files = g_list_delete_link(files, files)) {
1549 g_free(filename);
1550 g_free(basename);
1552 filename = files->data;
1553 basename = g_path_get_basename(filename);
1555 /* XXX - Make ft API support creating a transfer with more than one file */
1556 if (!g_file_test(filename, G_FILE_TEST_EXISTS)) {
1557 continue;
1560 /* XXX - make ft api suupport sending a directory */
1561 /* Are we dealing with a directory? */
1562 if (g_file_test(filename, G_FILE_TEST_IS_DIR)) {
1563 char *str, *str2;
1565 str = g_strdup_printf(_("Cannot send folder %s."), basename);
1566 str2 = g_strdup_printf(_("%s cannot transfer a folder. You will need to send the files within individually."), PIDGIN_NAME);
1568 purple_notify_error(NULL, NULL,
1569 str, str2);
1571 g_free(str);
1572 g_free(str2);
1573 continue;
1576 /* Are we dealing with an image? */
1577 pb = gdk_pixbuf_new_from_file(filename, NULL);
1578 if (pb) {
1579 _DndData *data = g_malloc(sizeof(_DndData));
1580 gboolean ft = FALSE, im = FALSE;
1582 data->who = g_strdup(who);
1583 data->filename = g_strdup(filename);
1584 data->account = account;
1586 if (gc)
1587 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
1589 if (prpl_info && prpl_info->options & OPT_PROTO_IM_IMAGE)
1590 im = TRUE;
1592 if (prpl_info && prpl_info->can_receive_file)
1593 ft = prpl_info->can_receive_file(gc, who);
1594 else if (prpl_info && prpl_info->send_file)
1595 ft = TRUE;
1597 if (im && ft)
1598 purple_request_choice(NULL, NULL,
1599 _("You have dragged an image"),
1600 _("You can send this image as a file transfer, "
1601 "embed it into this message, or use it as the buddy icon for this user."),
1602 DND_FILE_TRANSFER, _("OK"), (GCallback)dnd_image_ok_callback,
1603 _("Cancel"), (GCallback)dnd_image_cancel_callback,
1604 account, who, NULL,
1605 data,
1606 _("Set as buddy icon"), DND_BUDDY_ICON,
1607 _("Send image file"), DND_FILE_TRANSFER,
1608 _("Insert in message"), DND_IM_IMAGE,
1609 NULL);
1610 else if (!(im || ft))
1611 purple_request_yes_no(NULL, NULL, _("You have dragged an image"),
1612 _("Would you like to set it as the buddy icon for this user?"),
1613 PURPLE_DEFAULT_ACTION_NONE,
1614 account, who, NULL,
1615 data, (GCallback)dnd_set_icon_ok_cb, (GCallback)dnd_set_icon_cancel_cb);
1616 else
1617 purple_request_choice(NULL, NULL,
1618 _("You have dragged an image"),
1619 (ft ? _("You can send this image as a file transfer, or use it as the buddy icon for this user.") :
1620 _("You can insert this image into this message, or use it as the buddy icon for this user")),
1621 (ft ? DND_FILE_TRANSFER : DND_IM_IMAGE),
1622 _("OK"), (GCallback)dnd_image_ok_callback,
1623 _("Cancel"), (GCallback)dnd_image_cancel_callback,
1624 account, who, NULL,
1625 data,
1626 _("Set as buddy icon"), DND_BUDDY_ICON,
1627 (ft ? _("Send image file") : _("Insert in message")), (ft ? DND_FILE_TRANSFER : DND_IM_IMAGE),
1628 NULL);
1629 g_object_unref(G_OBJECT(pb));
1631 g_free(basename);
1632 while (files) {
1633 g_free(files->data);
1634 files = g_list_delete_link(files, files);
1636 return;
1639 #ifndef _WIN32
1640 /* Are we trying to send a .desktop file? */
1641 else if (purple_str_has_suffix(basename, ".desktop") && (item = purple_desktop_item_new_from_file(filename))) {
1642 PurpleDesktopItemType dtype;
1643 char key[64];
1644 const char *itemname = NULL;
1646 const char * const *langs;
1647 int i;
1648 langs = g_get_language_names();
1649 for (i = 0; langs[i]; i++) {
1650 g_snprintf(key, sizeof(key), "Name[%s]", langs[i]);
1651 itemname = purple_desktop_item_get_string(item, key);
1652 break;
1655 if (!itemname)
1656 itemname = purple_desktop_item_get_string(item, "Name");
1658 dtype = purple_desktop_item_get_entry_type(item);
1659 switch (dtype) {
1660 PurpleConversation *conv;
1661 PidginConversation *gtkconv;
1663 case PURPLE_DESKTOP_ITEM_TYPE_LINK:
1664 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, who);
1665 gtkconv = PIDGIN_CONVERSATION(conv);
1666 gtk_imhtml_insert_link(GTK_IMHTML(gtkconv->entry),
1667 gtk_text_buffer_get_insert(GTK_IMHTML(gtkconv->entry)->text_buffer),
1668 purple_desktop_item_get_string(item, "URL"), itemname);
1669 break;
1670 default:
1671 /* I don't know if we really want to do anything here. Most of
1672 * the desktop item types are crap like "MIME Type" (I have no
1673 * clue how that would be a desktop item) and "Comment"...
1674 * nothing we can really send. The only logical one is
1675 * "Application," but do we really want to send a binary and
1676 * nothing else? Probably not. I'll just give an error and
1677 * return. */
1678 /* The original patch sent the icon used by the launcher. That's probably wrong */
1679 purple_notify_error(NULL, NULL, _("Cannot send launcher"),
1680 _("You dragged a desktop launcher. Most "
1681 "likely you wanted to send the target "
1682 "of this launcher instead of this "
1683 "launcher itself."));
1684 break;
1686 purple_desktop_item_unref(item);
1687 g_free(basename);
1688 while (files) {
1689 g_free(files->data);
1690 files = g_list_delete_link(files, files);
1692 return;
1694 #endif /* _WIN32 */
1696 /* Everything is fine, let's send */
1697 serv_send_file(gc, who, filename);
1700 g_free(filename);
1701 g_free(basename);
1704 void pidgin_buddy_icon_get_scale_size(GdkPixbuf *buf, PurpleBuddyIconSpec *spec, PurpleIconScaleRules rules, int *width, int *height)
1706 *width = gdk_pixbuf_get_width(buf);
1707 *height = gdk_pixbuf_get_height(buf);
1709 if ((spec == NULL) || !(spec->scale_rules & rules))
1710 return;
1712 purple_buddy_icon_get_scale_size(spec, width, height);
1714 /* and now for some arbitrary sanity checks */
1715 if(*width > 100)
1716 *width = 100;
1717 if(*height > 100)
1718 *height = 100;
1721 GdkPixbuf * pidgin_create_status_icon(PurpleStatusPrimitive prim, GtkWidget *w, const char *size)
1723 GtkIconSize icon_size = gtk_icon_size_from_name(size);
1724 GdkPixbuf *pixbuf = NULL;
1725 const char *stock = pidgin_stock_id_from_status_primitive(prim);
1727 pixbuf = gtk_widget_render_icon (w, stock ? stock : PIDGIN_STOCK_STATUS_AVAILABLE,
1728 icon_size, "GtkWidget");
1729 return pixbuf;
1732 static const char *
1733 stock_id_from_status_primitive_idle(PurpleStatusPrimitive prim, gboolean idle)
1735 const char *stock = NULL;
1736 switch (prim) {
1737 case PURPLE_STATUS_UNSET:
1738 stock = NULL;
1739 break;
1740 case PURPLE_STATUS_UNAVAILABLE:
1741 stock = idle ? PIDGIN_STOCK_STATUS_BUSY_I : PIDGIN_STOCK_STATUS_BUSY;
1742 break;
1743 case PURPLE_STATUS_AWAY:
1744 stock = idle ? PIDGIN_STOCK_STATUS_AWAY_I : PIDGIN_STOCK_STATUS_AWAY;
1745 break;
1746 case PURPLE_STATUS_EXTENDED_AWAY:
1747 stock = idle ? PIDGIN_STOCK_STATUS_XA_I : PIDGIN_STOCK_STATUS_XA;
1748 break;
1749 case PURPLE_STATUS_INVISIBLE:
1750 stock = PIDGIN_STOCK_STATUS_INVISIBLE;
1751 break;
1752 case PURPLE_STATUS_OFFLINE:
1753 stock = idle ? PIDGIN_STOCK_STATUS_OFFLINE_I : PIDGIN_STOCK_STATUS_OFFLINE;
1754 break;
1755 default:
1756 stock = idle ? PIDGIN_STOCK_STATUS_AVAILABLE_I : PIDGIN_STOCK_STATUS_AVAILABLE;
1757 break;
1759 return stock;
1762 const char *
1763 pidgin_stock_id_from_status_primitive(PurpleStatusPrimitive prim)
1765 return stock_id_from_status_primitive_idle(prim, FALSE);
1768 const char *
1769 pidgin_stock_id_from_presence(PurplePresence *presence)
1771 PurpleStatus *status;
1772 PurpleStatusType *type;
1773 PurpleStatusPrimitive prim;
1774 gboolean idle;
1776 g_return_val_if_fail(presence, NULL);
1778 status = purple_presence_get_active_status(presence);
1779 type = purple_status_get_type(status);
1780 prim = purple_status_type_get_primitive(type);
1782 idle = purple_presence_is_idle(presence);
1784 return stock_id_from_status_primitive_idle(prim, idle);
1787 GdkPixbuf *
1788 pidgin_create_prpl_icon(PurpleAccount *account, PidginPrplIconSize size)
1790 PurplePlugin *prpl;
1792 g_return_val_if_fail(account != NULL, NULL);
1794 prpl = purple_find_prpl(purple_account_get_protocol_id(account));
1795 if (prpl == NULL)
1796 return NULL;
1797 return pidgin_create_prpl_icon_from_prpl(prpl, size, account);
1800 static void
1801 menu_action_cb(GtkMenuItem *item, gpointer object)
1803 gpointer data;
1804 void (*callback)(gpointer, gpointer);
1806 callback = g_object_get_data(G_OBJECT(item), "purplecallback");
1807 data = g_object_get_data(G_OBJECT(item), "purplecallbackdata");
1809 if (callback)
1810 callback(object, data);
1813 GtkWidget *
1814 pidgin_append_menu_action(GtkWidget *menu, PurpleMenuAction *act,
1815 gpointer object)
1817 GtkWidget *menuitem;
1819 if (act == NULL) {
1820 return pidgin_separator(menu);
1823 if (act->children == NULL) {
1824 menuitem = gtk_menu_item_new_with_mnemonic(act->label);
1826 if (act->callback != NULL) {
1827 g_object_set_data(G_OBJECT(menuitem),
1828 "purplecallback",
1829 act->callback);
1830 g_object_set_data(G_OBJECT(menuitem),
1831 "purplecallbackdata",
1832 act->data);
1833 g_signal_connect(G_OBJECT(menuitem), "activate",
1834 G_CALLBACK(menu_action_cb),
1835 object);
1836 } else {
1837 gtk_widget_set_sensitive(menuitem, FALSE);
1840 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1841 } else {
1842 GList *l = NULL;
1843 GtkWidget *submenu = NULL;
1844 GtkAccelGroup *group;
1846 menuitem = gtk_menu_item_new_with_mnemonic(act->label);
1847 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1849 submenu = gtk_menu_new();
1850 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
1852 group = gtk_menu_get_accel_group(GTK_MENU(menu));
1853 if (group) {
1854 char *path = g_strdup_printf("%s/%s", GTK_MENU_ITEM(menuitem)->accel_path, act->label);
1855 gtk_menu_set_accel_path(GTK_MENU(submenu), path);
1856 g_free(path);
1857 gtk_menu_set_accel_group(GTK_MENU(submenu), group);
1860 for (l = act->children; l; l = l->next) {
1861 PurpleMenuAction *act = (PurpleMenuAction *)l->data;
1863 pidgin_append_menu_action(submenu, act, object);
1865 g_list_free(act->children);
1866 act->children = NULL;
1868 purple_menu_action_free(act);
1869 return menuitem;
1872 typedef struct
1874 GtkWidget *entry;
1875 GtkWidget *accountopt;
1877 PidginFilterBuddyCompletionEntryFunc filter_func;
1878 gpointer filter_func_user_data;
1880 GtkListStore *store;
1881 } PidginCompletionData;
1883 static gboolean buddyname_completion_match_func(GtkEntryCompletion *completion,
1884 const gchar *key, GtkTreeIter *iter, gpointer user_data)
1886 GtkTreeModel *model;
1887 GValue val1;
1888 GValue val2;
1889 const char *tmp;
1891 model = gtk_entry_completion_get_model (completion);
1893 val1.g_type = 0;
1894 gtk_tree_model_get_value(model, iter, 2, &val1);
1895 tmp = g_value_get_string(&val1);
1896 if (tmp != NULL && purple_str_has_prefix(tmp, key))
1898 g_value_unset(&val1);
1899 return TRUE;
1901 g_value_unset(&val1);
1903 val2.g_type = 0;
1904 gtk_tree_model_get_value(model, iter, 3, &val2);
1905 tmp = g_value_get_string(&val2);
1906 if (tmp != NULL && purple_str_has_prefix(tmp, key))
1908 g_value_unset(&val2);
1909 return TRUE;
1911 g_value_unset(&val2);
1913 return FALSE;
1916 static gboolean buddyname_completion_match_selected_cb(GtkEntryCompletion *completion,
1917 GtkTreeModel *model, GtkTreeIter *iter, PidginCompletionData *data)
1919 GValue val;
1920 GtkWidget *optmenu = data->accountopt;
1921 PurpleAccount *account;
1923 val.g_type = 0;
1924 gtk_tree_model_get_value(model, iter, 1, &val);
1925 gtk_entry_set_text(GTK_ENTRY(data->entry), g_value_get_string(&val));
1926 g_value_unset(&val);
1928 gtk_tree_model_get_value(model, iter, 4, &val);
1929 account = g_value_get_pointer(&val);
1930 g_value_unset(&val);
1932 if (account == NULL)
1933 return TRUE;
1935 if (optmenu != NULL)
1936 aop_option_menu_select_by_data(optmenu, account);
1938 return TRUE;
1941 static void
1942 add_buddyname_autocomplete_entry(GtkListStore *store, const char *buddy_alias, const char *contact_alias,
1943 const PurpleAccount *account, const char *buddyname)
1945 GtkTreeIter iter;
1946 gboolean completion_added = FALSE;
1947 gchar *normalized_buddyname;
1948 gchar *tmp;
1950 tmp = g_utf8_normalize(buddyname, -1, G_NORMALIZE_DEFAULT);
1951 normalized_buddyname = g_utf8_casefold(tmp, -1);
1952 g_free(tmp);
1954 /* There's no sense listing things like: 'xxx "xxx"'
1955 when the name and buddy alias match. */
1956 if (buddy_alias && strcmp(buddy_alias, buddyname)) {
1957 char *completion_entry = g_strdup_printf("%s \"%s\"", buddyname, buddy_alias);
1958 char *tmp2 = g_utf8_normalize(buddy_alias, -1, G_NORMALIZE_DEFAULT);
1960 tmp = g_utf8_casefold(tmp2, -1);
1961 g_free(tmp2);
1963 gtk_list_store_append(store, &iter);
1964 gtk_list_store_set(store, &iter,
1965 0, completion_entry,
1966 1, buddyname,
1967 2, normalized_buddyname,
1968 3, tmp,
1969 4, account,
1970 -1);
1971 g_free(completion_entry);
1972 g_free(tmp);
1973 completion_added = TRUE;
1976 /* There's no sense listing things like: 'xxx "xxx"'
1977 when the name and contact alias match. */
1978 if (contact_alias && strcmp(contact_alias, buddyname)) {
1979 /* We don't want duplicates when the contact and buddy alias match. */
1980 if (!buddy_alias || strcmp(contact_alias, buddy_alias)) {
1981 char *completion_entry = g_strdup_printf("%s \"%s\"",
1982 buddyname, contact_alias);
1983 char *tmp2 = g_utf8_normalize(contact_alias, -1, G_NORMALIZE_DEFAULT);
1985 tmp = g_utf8_casefold(tmp2, -1);
1986 g_free(tmp2);
1988 gtk_list_store_append(store, &iter);
1989 gtk_list_store_set(store, &iter,
1990 0, completion_entry,
1991 1, buddyname,
1992 2, normalized_buddyname,
1993 3, tmp,
1994 4, account,
1995 -1);
1996 g_free(completion_entry);
1997 g_free(tmp);
1998 completion_added = TRUE;
2002 if (completion_added == FALSE) {
2003 /* Add the buddy's name. */
2004 gtk_list_store_append(store, &iter);
2005 gtk_list_store_set(store, &iter,
2006 0, buddyname,
2007 1, buddyname,
2008 2, normalized_buddyname,
2009 3, NULL,
2010 4, account,
2011 -1);
2014 g_free(normalized_buddyname);
2017 static void get_log_set_name(PurpleLogSet *set, gpointer value, PidginCompletionData *data)
2019 PidginFilterBuddyCompletionEntryFunc filter_func = data->filter_func;
2020 gpointer user_data = data->filter_func_user_data;
2022 /* 1. Don't show buddies because we will have gotten them already.
2023 * 2. The boxes that use this autocomplete code handle only IMs. */
2024 if (!set->buddy && set->type == PURPLE_LOG_IM) {
2025 PidginBuddyCompletionEntry entry;
2026 entry.is_buddy = FALSE;
2027 entry.entry.logged_buddy = set;
2029 if (filter_func(&entry, user_data)) {
2030 add_buddyname_autocomplete_entry(data->store,
2031 NULL, NULL, set->account, set->name);
2036 static void
2037 add_completion_list(PidginCompletionData *data)
2039 PurpleBlistNode *gnode, *cnode, *bnode;
2040 PidginFilterBuddyCompletionEntryFunc filter_func = data->filter_func;
2041 gpointer user_data = data->filter_func_user_data;
2042 GHashTable *sets;
2044 gtk_list_store_clear(data->store);
2046 for (gnode = purple_get_blist()->root; gnode != NULL; gnode = gnode->next)
2048 if (!PURPLE_BLIST_NODE_IS_GROUP(gnode))
2049 continue;
2051 for (cnode = gnode->child; cnode != NULL; cnode = cnode->next)
2053 if (!PURPLE_BLIST_NODE_IS_CONTACT(cnode))
2054 continue;
2056 for (bnode = cnode->child; bnode != NULL; bnode = bnode->next)
2058 PidginBuddyCompletionEntry entry;
2059 entry.is_buddy = TRUE;
2060 entry.entry.buddy = (PurpleBuddy *) bnode;
2062 if (filter_func(&entry, user_data)) {
2063 add_buddyname_autocomplete_entry(data->store,
2064 ((PurpleContact *)cnode)->alias,
2065 purple_buddy_get_contact_alias(entry.entry.buddy),
2066 entry.entry.buddy->account,
2067 entry.entry.buddy->name
2074 sets = purple_log_get_log_sets();
2075 g_hash_table_foreach(sets, (GHFunc)get_log_set_name, data);
2076 g_hash_table_destroy(sets);
2080 static void
2081 buddyname_autocomplete_destroyed_cb(GtkWidget *widget, gpointer data)
2083 g_free(data);
2084 purple_signals_disconnect_by_handle(widget);
2087 static void
2088 repopulate_autocomplete(gpointer something, gpointer data)
2090 add_completion_list(data);
2093 void
2094 pidgin_setup_screenname_autocomplete_with_filter(GtkWidget *entry, GtkWidget *accountopt, PidginFilterBuddyCompletionEntryFunc filter_func, gpointer user_data)
2096 PidginCompletionData *data;
2099 * Store the displayed completion value, the buddy name, the UTF-8
2100 * normalized & casefolded buddy name, the UTF-8 normalized &
2101 * casefolded value for comparison, and the account.
2103 GtkListStore *store;
2105 GtkEntryCompletion *completion;
2107 data = g_new0(PidginCompletionData, 1);
2108 store = gtk_list_store_new(5, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER);
2110 data->entry = entry;
2111 data->accountopt = accountopt;
2112 if (filter_func == NULL) {
2113 data->filter_func = pidgin_screenname_autocomplete_default_filter;
2114 data->filter_func_user_data = NULL;
2115 } else {
2116 data->filter_func = filter_func;
2117 data->filter_func_user_data = user_data;
2119 data->store = store;
2121 add_completion_list(data);
2123 /* Sort the completion list by buddy name */
2124 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store),
2125 1, GTK_SORT_ASCENDING);
2127 completion = gtk_entry_completion_new();
2128 gtk_entry_completion_set_match_func(completion, buddyname_completion_match_func, NULL, NULL);
2130 g_signal_connect(G_OBJECT(completion), "match-selected",
2131 G_CALLBACK(buddyname_completion_match_selected_cb), data);
2133 gtk_entry_set_completion(GTK_ENTRY(entry), completion);
2134 g_object_unref(completion);
2136 gtk_entry_completion_set_model(completion, GTK_TREE_MODEL(store));
2137 g_object_unref(store);
2139 gtk_entry_completion_set_text_column(completion, 0);
2141 purple_signal_connect(purple_connections_get_handle(), "signed-on", entry,
2142 PURPLE_CALLBACK(repopulate_autocomplete), data);
2143 purple_signal_connect(purple_connections_get_handle(), "signed-off", entry,
2144 PURPLE_CALLBACK(repopulate_autocomplete), data);
2146 purple_signal_connect(purple_accounts_get_handle(), "account-added", entry,
2147 PURPLE_CALLBACK(repopulate_autocomplete), data);
2148 purple_signal_connect(purple_accounts_get_handle(), "account-removed", entry,
2149 PURPLE_CALLBACK(repopulate_autocomplete), data);
2151 g_signal_connect(G_OBJECT(entry), "destroy", G_CALLBACK(buddyname_autocomplete_destroyed_cb), data);
2154 gboolean
2155 pidgin_screenname_autocomplete_default_filter(const PidginBuddyCompletionEntry *completion_entry, gpointer all_accounts) {
2156 gboolean all = GPOINTER_TO_INT(all_accounts);
2158 if (completion_entry->is_buddy) {
2159 return all || purple_account_is_connected(completion_entry->entry.buddy->account);
2160 } else {
2161 return all || (completion_entry->entry.logged_buddy->account != NULL && purple_account_is_connected(completion_entry->entry.logged_buddy->account));
2165 void
2166 pidgin_setup_screenname_autocomplete(GtkWidget *entry, GtkWidget *accountopt, gboolean all) {
2167 pidgin_setup_screenname_autocomplete_with_filter(entry, accountopt, pidgin_screenname_autocomplete_default_filter, GINT_TO_POINTER(all));
2172 void pidgin_set_cursor(GtkWidget *widget, GdkCursorType cursor_type)
2174 GdkCursor *cursor;
2176 g_return_if_fail(widget != NULL);
2177 if (widget->window == NULL)
2178 return;
2180 cursor = gdk_cursor_new(cursor_type);
2181 gdk_window_set_cursor(widget->window, cursor);
2182 gdk_cursor_unref(cursor);
2184 gdk_display_flush(gdk_drawable_get_display(GDK_DRAWABLE(widget->window)));
2187 void pidgin_clear_cursor(GtkWidget *widget)
2189 g_return_if_fail(widget != NULL);
2190 if (widget->window == NULL)
2191 return;
2193 gdk_window_set_cursor(widget->window, NULL);
2196 struct _icon_chooser {
2197 GtkWidget *icon_filesel;
2198 GtkWidget *icon_preview;
2199 GtkWidget *icon_text;
2201 void (*callback)(const char*,gpointer);
2202 gpointer data;
2205 static void
2206 icon_filesel_choose_cb(GtkWidget *widget, gint response, struct _icon_chooser *dialog)
2208 char *filename, *current_folder;
2210 if (response != GTK_RESPONSE_ACCEPT) {
2211 if (response == GTK_RESPONSE_CANCEL) {
2212 gtk_widget_destroy(dialog->icon_filesel);
2214 dialog->icon_filesel = NULL;
2215 if (dialog->callback)
2216 dialog->callback(NULL, dialog->data);
2217 g_free(dialog);
2218 return;
2221 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog->icon_filesel));
2222 current_folder = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(dialog->icon_filesel));
2223 if (current_folder != NULL) {
2224 purple_prefs_set_path(PIDGIN_PREFS_ROOT "/filelocations/last_icon_folder", current_folder);
2225 g_free(current_folder);
2229 if (dialog->callback)
2230 dialog->callback(filename, dialog->data);
2231 gtk_widget_destroy(dialog->icon_filesel);
2232 g_free(filename);
2233 g_free(dialog);
2237 static void
2238 icon_preview_change_cb(GtkFileChooser *widget, struct _icon_chooser *dialog)
2240 GdkPixbuf *pixbuf, *scale;
2241 int height, width;
2242 char *basename, *markup, *size;
2243 struct stat st;
2244 char *filename;
2246 filename = gtk_file_chooser_get_preview_filename(
2247 GTK_FILE_CHOOSER(dialog->icon_filesel));
2249 if (!filename || g_stat(filename, &st) || !(pixbuf = gdk_pixbuf_new_from_file(filename, NULL)))
2251 gtk_image_set_from_pixbuf(GTK_IMAGE(dialog->icon_preview), NULL);
2252 gtk_label_set_markup(GTK_LABEL(dialog->icon_text), "");
2253 g_free(filename);
2254 return;
2257 width = gdk_pixbuf_get_width(pixbuf);
2258 height = gdk_pixbuf_get_height(pixbuf);
2259 basename = g_path_get_basename(filename);
2260 size = purple_str_size_to_units(st.st_size);
2261 markup = g_strdup_printf(_("<b>File:</b> %s\n"
2262 "<b>File size:</b> %s\n"
2263 "<b>Image size:</b> %dx%d"),
2264 basename, size, width, height);
2266 scale = gdk_pixbuf_scale_simple(pixbuf, width * 50 / height,
2267 50, GDK_INTERP_BILINEAR);
2268 gtk_image_set_from_pixbuf(GTK_IMAGE(dialog->icon_preview), scale);
2269 gtk_label_set_markup(GTK_LABEL(dialog->icon_text), markup);
2271 g_object_unref(G_OBJECT(pixbuf));
2272 g_object_unref(G_OBJECT(scale));
2273 g_free(filename);
2274 g_free(basename);
2275 g_free(size);
2276 g_free(markup);
2280 GtkWidget *pidgin_buddy_icon_chooser_new(GtkWindow *parent, void(*callback)(const char *, gpointer), gpointer data) {
2281 struct _icon_chooser *dialog = g_new0(struct _icon_chooser, 1);
2283 GtkWidget *vbox;
2284 const char *current_folder;
2286 dialog->callback = callback;
2287 dialog->data = data;
2289 current_folder = purple_prefs_get_path(PIDGIN_PREFS_ROOT "/filelocations/last_icon_folder");
2291 dialog->icon_filesel = gtk_file_chooser_dialog_new(_("Buddy Icon"),
2292 parent,
2293 GTK_FILE_CHOOSER_ACTION_OPEN,
2294 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2295 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
2296 NULL);
2297 gtk_dialog_set_default_response(GTK_DIALOG(dialog->icon_filesel), GTK_RESPONSE_ACCEPT);
2298 if ((current_folder != NULL) && (*current_folder != '\0'))
2299 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog->icon_filesel),
2300 current_folder);
2302 dialog->icon_preview = gtk_image_new();
2303 dialog->icon_text = gtk_label_new(NULL);
2305 vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
2306 gtk_widget_set_size_request(GTK_WIDGET(vbox), -1, 50);
2307 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(dialog->icon_preview), TRUE, FALSE, 0);
2308 gtk_box_pack_end(GTK_BOX(vbox), GTK_WIDGET(dialog->icon_text), FALSE, FALSE, 0);
2309 gtk_widget_show_all(vbox);
2311 gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog->icon_filesel), vbox);
2312 gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(dialog->icon_filesel), TRUE);
2313 gtk_file_chooser_set_use_preview_label(GTK_FILE_CHOOSER(dialog->icon_filesel), FALSE);
2315 g_signal_connect(G_OBJECT(dialog->icon_filesel), "update-preview",
2316 G_CALLBACK(icon_preview_change_cb), dialog);
2317 g_signal_connect(G_OBJECT(dialog->icon_filesel), "response",
2318 G_CALLBACK(icon_filesel_choose_cb), dialog);
2319 icon_preview_change_cb(NULL, dialog);
2321 #ifdef _WIN32
2322 g_signal_connect(G_OBJECT(dialog->icon_filesel), "show",
2323 G_CALLBACK(winpidgin_ensure_onscreen), dialog->icon_filesel);
2324 #endif
2326 return dialog->icon_filesel;
2330 * @return True if any string from array a exists in array b.
2332 static gboolean
2333 str_array_match(char **a, char **b)
2335 int i, j;
2337 if (!a || !b)
2338 return FALSE;
2339 for (i = 0; a[i] != NULL; i++)
2340 for (j = 0; b[j] != NULL; j++)
2341 if (!g_ascii_strcasecmp(a[i], b[j]))
2342 return TRUE;
2343 return FALSE;
2346 gpointer
2347 pidgin_convert_buddy_icon(PurplePlugin *plugin, const char *path, size_t *len)
2349 PurplePluginProtocolInfo *prpl_info;
2350 PurpleBuddyIconSpec *spec;
2351 int orig_width, orig_height, new_width, new_height;
2352 GdkPixbufFormat *format;
2353 char **pixbuf_formats;
2354 char **prpl_formats;
2355 GError *error = NULL;
2356 gchar *contents;
2357 gsize length;
2358 GdkPixbuf *pixbuf, *original;
2359 float scale_factor;
2360 int i;
2361 gchar *tmp;
2363 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin);
2364 spec = &prpl_info->icon_spec;
2365 g_return_val_if_fail(spec->format != NULL, NULL);
2367 format = gdk_pixbuf_get_file_info(path, &orig_width, &orig_height);
2368 if (format == NULL) {
2369 purple_debug_warning("buddyicon", "Could not get file info of %s\n", path);
2370 return NULL;
2373 pixbuf_formats = gdk_pixbuf_format_get_extensions(format);
2374 prpl_formats = g_strsplit(spec->format, ",", 0);
2376 if (str_array_match(pixbuf_formats, prpl_formats) && /* This is an acceptable format AND */
2377 (!(spec->scale_rules & PURPLE_ICON_SCALE_SEND) || /* The prpl doesn't scale before it sends OR */
2378 (spec->min_width <= orig_width && spec->max_width >= orig_width &&
2379 spec->min_height <= orig_height && spec->max_height >= orig_height))) /* The icon is the correct size */
2381 g_strfreev(pixbuf_formats);
2383 if (!g_file_get_contents(path, &contents, &length, &error)) {
2384 purple_debug_warning("buddyicon", "Could not get file contents "
2385 "of %s: %s\n", path, error->message);
2386 g_strfreev(prpl_formats);
2387 return NULL;
2390 if (spec->max_filesize == 0 || length < spec->max_filesize) {
2391 /* The supplied image fits the file size, dimensions and type
2392 constraints. Great! Return it without making any changes. */
2393 if (len)
2394 *len = length;
2395 g_strfreev(prpl_formats);
2396 return contents;
2399 /* The image was too big. Fall-through and try scaling it down. */
2400 g_free(contents);
2401 } else {
2402 g_strfreev(pixbuf_formats);
2405 /* The original image wasn't compatible. Scale it or convert file type. */
2406 pixbuf = gdk_pixbuf_new_from_file(path, &error);
2407 if (error) {
2408 purple_debug_warning("buddyicon", "Could not open icon '%s' for "
2409 "conversion: %s\n", path, error->message);
2410 g_error_free(error);
2411 g_strfreev(prpl_formats);
2412 return NULL;
2414 original = g_object_ref(G_OBJECT(pixbuf));
2416 new_width = orig_width;
2417 new_height = orig_height;
2419 /* Make sure the image is the correct dimensions */
2420 if (spec->scale_rules & PURPLE_ICON_SCALE_SEND &&
2421 (orig_width < spec->min_width || orig_width > spec->max_width ||
2422 orig_height < spec->min_height || orig_height > spec->max_height))
2424 purple_buddy_icon_get_scale_size(spec, &new_width, &new_height);
2426 g_object_unref(G_OBJECT(pixbuf));
2427 pixbuf = gdk_pixbuf_scale_simple(original, new_width, new_height, GDK_INTERP_HYPER);
2430 scale_factor = 1;
2431 do {
2432 for (i = 0; prpl_formats[i]; i++) {
2433 int quality = 100;
2434 do {
2435 const char *key = NULL;
2436 const char *value = NULL;
2437 gchar tmp_buf[4];
2439 purple_debug_info("buddyicon", "Converting buddy icon to %s\n", prpl_formats[i]);
2441 if (g_str_equal(prpl_formats[i], "png")) {
2442 key = "compression";
2443 value = "9";
2444 } else if (g_str_equal(prpl_formats[i], "jpeg")) {
2445 sprintf(tmp_buf, "%u", quality);
2446 key = "quality";
2447 value = tmp_buf;
2450 if (!gdk_pixbuf_save_to_buffer(pixbuf, &contents, &length,
2451 prpl_formats[i], &error, key, value, NULL))
2453 /* The NULL checking of error is necessary due to this bug:
2454 * http://bugzilla.gnome.org/show_bug.cgi?id=405539 */
2455 purple_debug_warning("buddyicon",
2456 "Could not convert to %s: %s\n", prpl_formats[i],
2457 (error && error->message) ? error->message : "Unknown error");
2458 g_error_free(error);
2459 error = NULL;
2461 /* We couldn't convert to this image type. Try the next
2462 image type. */
2463 break;
2466 if (spec->max_filesize == 0 || length <= spec->max_filesize) {
2467 /* We were able to save the image as this image type and
2468 have it be within the size constraints. Great! Return
2469 the image. */
2470 purple_debug_info("buddyicon", "Converted image from "
2471 "%dx%d to %dx%d, format=%s, quality=%u, "
2472 "filesize=%zu\n", orig_width, orig_height,
2473 new_width, new_height, prpl_formats[i], quality,
2474 length);
2475 if (len)
2476 *len = length;
2477 g_strfreev(prpl_formats);
2478 g_object_unref(G_OBJECT(pixbuf));
2479 g_object_unref(G_OBJECT(original));
2480 return contents;
2483 g_free(contents);
2485 if (!g_str_equal(prpl_formats[i], "jpeg")) {
2486 /* File size was too big and we can't lower the quality,
2487 so skip to the next image type. */
2488 break;
2491 /* File size was too big, but we're dealing with jpeg so try
2492 lowering the quality. */
2493 quality -= 5;
2494 } while (quality >= 70);
2497 /* We couldn't save the image in any format that was below the max
2498 file size. Maybe we can reduce the image dimensions? */
2499 scale_factor *= 0.8;
2500 new_width = orig_width * scale_factor;
2501 new_height = orig_height * scale_factor;
2502 g_object_unref(G_OBJECT(pixbuf));
2503 pixbuf = gdk_pixbuf_scale_simple(original, new_width, new_height, GDK_INTERP_HYPER);
2504 } while ((new_width > 10 || new_height > 10) && new_width > spec->min_width && new_height > spec->min_height);
2505 g_strfreev(prpl_formats);
2506 g_object_unref(G_OBJECT(pixbuf));
2507 g_object_unref(G_OBJECT(original));
2509 tmp = g_strdup_printf(_("The file '%s' is too large for %s. Please try a smaller image.\n"),
2510 path, plugin->info->name);
2511 purple_notify_error(NULL, _("Icon Error"), _("Could not set icon"), tmp);
2512 g_free(tmp);
2514 return NULL;
2517 void pidgin_set_custom_buddy_icon(PurpleAccount *account, const char *who, const char *filename)
2519 PurpleBuddy *buddy;
2520 PurpleContact *contact;
2522 buddy = purple_find_buddy(account, who);
2523 if (!buddy) {
2524 purple_debug_info("custom-icon", "You can only set custom icon for someone in your buddylist.\n");
2525 return;
2528 contact = purple_buddy_get_contact(buddy);
2529 purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode*)contact, filename);
2532 char *pidgin_make_pretty_arrows(const char *str)
2534 char *ret;
2535 char **split = g_strsplit(str, "->", -1);
2536 ret = g_strjoinv("\342\207\250", split);
2537 g_strfreev(split);
2539 split = g_strsplit(ret, "<-", -1);
2540 g_free(ret);
2541 ret = g_strjoinv("\342\207\246", split);
2542 g_strfreev(split);
2544 return ret;
2547 void pidgin_set_urgent(GtkWindow *window, gboolean urgent)
2549 #if defined _WIN32
2550 winpidgin_window_flash(window, urgent);
2551 #else
2552 gtk_window_set_urgency_hint(window, urgent);
2553 #endif
2556 static GSList *minidialogs = NULL;
2558 static void *
2559 pidgin_utils_get_handle(void)
2561 static int handle;
2563 return &handle;
2566 static void connection_signed_off_cb(PurpleConnection *gc)
2568 GSList *list, *l_next;
2569 for (list = minidialogs; list; list = l_next) {
2570 l_next = list->next;
2571 if (g_object_get_data(G_OBJECT(list->data), "gc") == gc) {
2572 gtk_widget_destroy(GTK_WIDGET(list->data));
2577 static void alert_killed_cb(GtkWidget *widget)
2579 minidialogs = g_slist_remove(minidialogs, widget);
2582 struct _old_button_clicked_cb_data
2584 PidginUtilMiniDialogCallback cb;
2585 gpointer data;
2588 static void
2589 old_mini_dialog_button_clicked_cb(PidginMiniDialog *mini_dialog,
2590 GtkButton *button,
2591 gpointer user_data)
2593 struct _old_button_clicked_cb_data *data = user_data;
2594 data->cb(data->data, button);
2597 static void
2598 old_mini_dialog_destroy_cb(GtkWidget *dialog,
2599 GList *cb_datas)
2601 while (cb_datas != NULL)
2603 g_free(cb_datas->data);
2604 cb_datas = g_list_delete_link(cb_datas, cb_datas);
2608 static void
2609 mini_dialog_init(PidginMiniDialog *mini_dialog, PurpleConnection *gc, void *user_data, va_list args)
2611 const char *button_text;
2612 GList *cb_datas = NULL;
2613 static gboolean first_call = TRUE;
2615 if (first_call) {
2616 first_call = FALSE;
2617 purple_signal_connect(purple_connections_get_handle(), "signed-off",
2618 pidgin_utils_get_handle(),
2619 PURPLE_CALLBACK(connection_signed_off_cb), NULL);
2622 g_object_set_data(G_OBJECT(mini_dialog), "gc" ,gc);
2623 g_signal_connect(G_OBJECT(mini_dialog), "destroy",
2624 G_CALLBACK(alert_killed_cb), NULL);
2626 while ((button_text = va_arg(args, char*))) {
2627 struct _old_button_clicked_cb_data *data = NULL;
2628 PidginMiniDialogCallback wrapper_cb = NULL;
2629 PidginUtilMiniDialogCallback callback =
2630 va_arg(args, PidginUtilMiniDialogCallback);
2632 if (callback != NULL) {
2633 data = g_new0(struct _old_button_clicked_cb_data, 1);
2634 data->cb = callback;
2635 data->data = user_data;
2636 wrapper_cb = old_mini_dialog_button_clicked_cb;
2638 pidgin_mini_dialog_add_button(mini_dialog, button_text,
2639 wrapper_cb, data);
2640 cb_datas = g_list_append(cb_datas, data);
2643 g_signal_connect(G_OBJECT(mini_dialog), "destroy",
2644 G_CALLBACK(old_mini_dialog_destroy_cb), cb_datas);
2647 #define INIT_AND_RETURN_MINI_DIALOG(mini_dialog) \
2648 va_list args; \
2649 va_start(args, user_data); \
2650 mini_dialog_init(mini_dialog, gc, user_data, args); \
2651 va_end(args); \
2652 return GTK_WIDGET(mini_dialog);
2654 GtkWidget *
2655 pidgin_make_mini_dialog(PurpleConnection *gc,
2656 const char *icon_name,
2657 const char *primary,
2658 const char *secondary,
2659 void *user_data,
2660 ...)
2662 PidginMiniDialog *mini_dialog = pidgin_mini_dialog_new(primary, secondary, icon_name);
2663 INIT_AND_RETURN_MINI_DIALOG(mini_dialog);
2666 GtkWidget *
2667 pidgin_make_mini_dialog_with_custom_icon(PurpleConnection *gc,
2668 GdkPixbuf *custom_icon,
2669 const char *primary,
2670 const char *secondary,
2671 void *user_data,
2672 ...)
2674 PidginMiniDialog *mini_dialog = pidgin_mini_dialog_new_with_custom_icon(primary, secondary, custom_icon);
2675 INIT_AND_RETURN_MINI_DIALOG(mini_dialog);
2679 * "This is so dead sexy."
2680 * "Two thumbs up."
2681 * "Best movie of the year."
2683 * This is the function that handles CTRL+F searching in the buddy list.
2684 * It finds the top-most buddy/group/chat/whatever containing the
2685 * entered string.
2687 * It's somewhat ineffecient, because we strip all the HTML from the
2688 * "name" column of the buddy list (because the GtkTreeModel does not
2689 * contain the screen name in a non-markedup format). But the alternative
2690 * is to add an extra column to the GtkTreeModel. And this function is
2691 * used rarely, so it shouldn't matter TOO much.
2693 gboolean pidgin_tree_view_search_equal_func(GtkTreeModel *model, gint column,
2694 const gchar *key, GtkTreeIter *iter, gpointer data)
2696 gchar *enteredstring;
2697 gchar *tmp;
2698 gchar *withmarkup;
2699 gchar *nomarkup;
2700 gchar *normalized;
2701 gboolean result;
2702 size_t i;
2703 size_t len;
2704 PangoLogAttr *log_attrs;
2705 gchar *word;
2707 if (g_ascii_strcasecmp(key, "Global Thermonuclear War") == 0)
2709 purple_notify_info(NULL, "WOPR",
2710 "Wouldn't you prefer a nice game of chess?", NULL);
2711 return FALSE;
2714 gtk_tree_model_get(model, iter, column, &withmarkup, -1);
2715 if (withmarkup == NULL) /* This is probably a separator */
2716 return TRUE;
2718 tmp = g_utf8_normalize(key, -1, G_NORMALIZE_DEFAULT);
2719 enteredstring = g_utf8_casefold(tmp, -1);
2720 g_free(tmp);
2722 nomarkup = purple_markup_strip_html(withmarkup);
2723 tmp = g_utf8_normalize(nomarkup, -1, G_NORMALIZE_DEFAULT);
2724 g_free(nomarkup);
2725 normalized = g_utf8_casefold(tmp, -1);
2726 g_free(tmp);
2728 if (purple_str_has_prefix(normalized, enteredstring))
2730 g_free(withmarkup);
2731 g_free(enteredstring);
2732 g_free(normalized);
2733 return FALSE;
2737 /* Use Pango to separate by words. */
2738 len = g_utf8_strlen(normalized, -1);
2739 log_attrs = g_new(PangoLogAttr, len + 1);
2741 pango_get_log_attrs(normalized, strlen(normalized), -1, NULL, log_attrs, len + 1);
2743 word = normalized;
2744 result = TRUE;
2745 for (i = 0; i < (len - 1) ; i++)
2747 if (log_attrs[i].is_word_start &&
2748 purple_str_has_prefix(word, enteredstring))
2750 result = FALSE;
2751 break;
2753 word = g_utf8_next_char(word);
2755 g_free(log_attrs);
2757 /* The non-Pango version. */
2758 #if 0
2759 word = normalized;
2760 result = TRUE;
2761 while (word[0] != '\0')
2763 gunichar c = g_utf8_get_char(word);
2764 if (!g_unichar_isalnum(c))
2766 word = g_utf8_find_next_char(word, NULL);
2767 if (purple_str_has_prefix(word, enteredstring))
2769 result = FALSE;
2770 break;
2773 else
2774 word = g_utf8_find_next_char(word, NULL);
2776 #endif
2778 g_free(withmarkup);
2779 g_free(enteredstring);
2780 g_free(normalized);
2782 return result;
2786 gboolean pidgin_gdk_pixbuf_is_opaque(GdkPixbuf *pixbuf) {
2787 int height, rowstride, i;
2788 unsigned char *pixels;
2789 unsigned char *row;
2791 if (!gdk_pixbuf_get_has_alpha(pixbuf))
2792 return TRUE;
2794 height = gdk_pixbuf_get_height (pixbuf);
2795 rowstride = gdk_pixbuf_get_rowstride (pixbuf);
2796 pixels = gdk_pixbuf_get_pixels (pixbuf);
2798 row = pixels;
2799 for (i = 3; i < rowstride; i+=4) {
2800 if (row[i] < 0xfe)
2801 return FALSE;
2804 for (i = 1; i < height - 1; i++) {
2805 row = pixels + (i * rowstride);
2806 if (row[3] < 0xfe || row[rowstride - 1] < 0xfe) {
2807 return FALSE;
2811 row = pixels + ((height - 1) * rowstride);
2812 for (i = 3; i < rowstride; i += 4) {
2813 if (row[i] < 0xfe)
2814 return FALSE;
2817 return TRUE;
2820 void pidgin_gdk_pixbuf_make_round(GdkPixbuf *pixbuf) {
2821 int width, height, rowstride;
2822 guchar *pixels;
2823 if (!gdk_pixbuf_get_has_alpha(pixbuf))
2824 return;
2825 width = gdk_pixbuf_get_width(pixbuf);
2826 height = gdk_pixbuf_get_height(pixbuf);
2827 rowstride = gdk_pixbuf_get_rowstride(pixbuf);
2828 pixels = gdk_pixbuf_get_pixels(pixbuf);
2830 if (width < 6 || height < 6)
2831 return;
2832 /* Top left */
2833 pixels[3] = 0;
2834 pixels[7] = 0x80;
2835 pixels[11] = 0xC0;
2836 pixels[rowstride + 3] = 0x80;
2837 pixels[rowstride * 2 + 3] = 0xC0;
2839 /* Top right */
2840 pixels[width * 4 - 1] = 0;
2841 pixels[width * 4 - 5] = 0x80;
2842 pixels[width * 4 - 9] = 0xC0;
2843 pixels[rowstride + (width * 4) - 1] = 0x80;
2844 pixels[(2 * rowstride) + (width * 4) - 1] = 0xC0;
2846 /* Bottom left */
2847 pixels[(height - 1) * rowstride + 3] = 0;
2848 pixels[(height - 1) * rowstride + 7] = 0x80;
2849 pixels[(height - 1) * rowstride + 11] = 0xC0;
2850 pixels[(height - 2) * rowstride + 3] = 0x80;
2851 pixels[(height - 3) * rowstride + 3] = 0xC0;
2853 /* Bottom right */
2854 pixels[height * rowstride - 1] = 0;
2855 pixels[(height - 1) * rowstride - 1] = 0x80;
2856 pixels[(height - 2) * rowstride - 1] = 0xC0;
2857 pixels[height * rowstride - 5] = 0x80;
2858 pixels[height * rowstride - 9] = 0xC0;
2861 const char *pidgin_get_dim_grey_string(GtkWidget *widget) {
2862 static char dim_grey_string[8] = "";
2863 GtkStyle *style;
2865 if (!widget)
2866 return "dim grey";
2868 style = gtk_widget_get_style(widget);
2869 if (!style)
2870 return "dim grey";
2872 snprintf(dim_grey_string, sizeof(dim_grey_string), "#%02x%02x%02x",
2873 style->text_aa[GTK_STATE_NORMAL].red >> 8,
2874 style->text_aa[GTK_STATE_NORMAL].green >> 8,
2875 style->text_aa[GTK_STATE_NORMAL].blue >> 8);
2876 return dim_grey_string;
2879 static void
2880 combo_box_changed_cb(GtkComboBox *combo_box, GtkEntry *entry)
2882 char *text = gtk_combo_box_get_active_text(combo_box);
2883 gtk_entry_set_text(entry, text ? text : "");
2884 g_free(text);
2887 static gboolean
2888 entry_key_pressed_cb(GtkWidget *entry, GdkEventKey *key, GtkComboBox *combo)
2890 if (key->keyval == GDK_Down || key->keyval == GDK_Up) {
2891 gtk_combo_box_popup(combo);
2892 return TRUE;
2894 return FALSE;
2897 GtkWidget *
2898 pidgin_text_combo_box_entry_new(const char *default_item, GList *items)
2900 GtkComboBox *ret = NULL;
2901 GtkWidget *the_entry = NULL;
2903 ret = GTK_COMBO_BOX(gtk_combo_box_new_text());
2904 the_entry = gtk_entry_new();
2905 gtk_container_add(GTK_CONTAINER(ret), the_entry);
2907 if (default_item)
2908 gtk_entry_set_text(GTK_ENTRY(the_entry), default_item);
2910 for (; items != NULL ; items = items->next) {
2911 char *text = items->data;
2912 if (text && *text)
2913 gtk_combo_box_append_text(ret, text);
2916 g_signal_connect(G_OBJECT(ret), "changed", (GCallback)combo_box_changed_cb, the_entry);
2917 g_signal_connect_after(G_OBJECT(the_entry), "key-press-event", G_CALLBACK(entry_key_pressed_cb), ret);
2919 return GTK_WIDGET(ret);
2922 const char *pidgin_text_combo_box_entry_get_text(GtkWidget *widget)
2924 return gtk_entry_get_text(GTK_ENTRY(GTK_BIN((widget))->child));
2927 void pidgin_text_combo_box_entry_set_text(GtkWidget *widget, const char *text)
2929 gtk_entry_set_text(GTK_ENTRY(GTK_BIN((widget))->child), (text));
2932 GtkWidget *
2933 pidgin_add_widget_to_vbox(GtkBox *vbox, const char *widget_label, GtkSizeGroup *sg, GtkWidget *widget, gboolean expand, GtkWidget **p_label)
2935 GtkWidget *hbox;
2936 GtkWidget *label = NULL;
2938 if (widget_label) {
2939 hbox = gtk_hbox_new(FALSE, 5);
2940 gtk_widget_show(hbox);
2941 gtk_box_pack_start(vbox, hbox, FALSE, FALSE, 0);
2943 label = gtk_label_new_with_mnemonic(widget_label);
2944 gtk_widget_show(label);
2945 if (sg) {
2946 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
2947 gtk_size_group_add_widget(sg, label);
2949 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
2950 } else {
2951 hbox = GTK_WIDGET(vbox);
2954 gtk_widget_show(widget);
2955 gtk_box_pack_start(GTK_BOX(hbox), widget, expand, TRUE, 0);
2956 if (label) {
2957 gtk_label_set_mnemonic_widget(GTK_LABEL(label), widget);
2958 pidgin_set_accessible_label (widget, label);
2961 if (p_label)
2962 (*p_label) = label;
2963 return hbox;
2966 gboolean pidgin_auto_parent_window(GtkWidget *widget)
2968 #if 0
2969 /* This looks at the most recent window that received focus, and makes
2970 * that the parent window. */
2971 #ifndef _WIN32
2972 static GdkAtom _WindowTime = GDK_NONE;
2973 static GdkAtom _Cardinal = GDK_NONE;
2974 GList *windows = NULL;
2975 GtkWidget *parent = NULL;
2976 time_t window_time = 0;
2978 windows = gtk_window_list_toplevels();
2980 if (_WindowTime == GDK_NONE) {
2981 _WindowTime = gdk_x11_xatom_to_atom(gdk_x11_get_xatom_by_name("_NET_WM_USER_TIME"));
2983 if (_Cardinal == GDK_NONE) {
2984 _Cardinal = gdk_atom_intern("CARDINAL", FALSE);
2987 while (windows) {
2988 GtkWidget *window = windows->data;
2989 guchar *data = NULL;
2990 int al = 0;
2991 time_t value;
2993 windows = g_list_delete_link(windows, windows);
2995 if (window == widget ||
2996 !GTK_WIDGET_VISIBLE(window))
2997 continue;
2999 if (!gdk_property_get(window->window, _WindowTime, _Cardinal, 0, sizeof(time_t), FALSE,
3000 NULL, NULL, &al, &data))
3001 continue;
3002 value = *(time_t *)data;
3003 if (window_time < value) {
3004 window_time = value;
3005 parent = window;
3007 g_free(data);
3009 if (windows)
3010 g_list_free(windows);
3011 if (parent) {
3012 if (!gtk_get_current_event() && gtk_window_has_toplevel_focus(GTK_WINDOW(parent))) {
3013 /* The window is in focus, and the new window was not triggered by a keypress/click
3014 * event. So do not set it transient, to avoid focus stealing and all that.
3016 return FALSE;
3018 gtk_window_set_transient_for(GTK_WINDOW(widget), GTK_WINDOW(parent));
3019 return TRUE;
3021 return FALSE;
3022 #endif
3023 #else
3024 /* This finds the currently active window and makes that the parent window. */
3025 GList *windows = NULL;
3026 GtkWidget *parent = NULL;
3027 GdkEvent *event = gtk_get_current_event();
3028 GdkWindow *menu = NULL;
3030 if (event == NULL)
3031 /* The window was not triggered by a user action. */
3032 return FALSE;
3034 /* We need to special case events from a popup menu. */
3035 if (event->type == GDK_BUTTON_RELEASE) {
3036 /* XXX: Neither of the following works:
3037 menu = event->button.window;
3038 menu = gdk_window_get_parent(event->button.window);
3039 menu = gdk_window_get_toplevel(event->button.window);
3041 } else if (event->type == GDK_KEY_PRESS)
3042 menu = event->key.window;
3044 windows = gtk_window_list_toplevels();
3045 while (windows) {
3046 GtkWidget *window = windows->data;
3047 windows = g_list_delete_link(windows, windows);
3049 if (window == widget ||
3050 !GTK_WIDGET_VISIBLE(window)) {
3051 continue;
3054 if (gtk_window_has_toplevel_focus(GTK_WINDOW(window)) ||
3055 (menu && menu == window->window)) {
3056 parent = window;
3057 break;
3060 if (windows)
3061 g_list_free(windows);
3062 if (parent) {
3063 gtk_window_set_transient_for(GTK_WINDOW(widget), GTK_WINDOW(parent));
3064 return TRUE;
3066 return FALSE;
3067 #endif
3070 GdkPixbuf * pidgin_pixbuf_from_imgstore(PurpleStoredImage *image)
3072 GdkPixbuf *pixbuf;
3073 GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
3074 gdk_pixbuf_loader_write(loader, purple_imgstore_get_data(image),
3075 purple_imgstore_get_size(image), NULL);
3076 gdk_pixbuf_loader_close(loader, NULL);
3077 pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
3078 if (pixbuf)
3079 g_object_ref(pixbuf);
3080 g_object_unref(loader);
3081 return pixbuf;
3084 static void url_copy(GtkWidget *w, gchar *url)
3086 GtkClipboard *clipboard;
3088 clipboard = gtk_widget_get_clipboard(w, GDK_SELECTION_PRIMARY);
3089 gtk_clipboard_set_text(clipboard, url, -1);
3091 clipboard = gtk_widget_get_clipboard(w, GDK_SELECTION_CLIPBOARD);
3092 gtk_clipboard_set_text(clipboard, url, -1);
3095 static gboolean
3096 link_context_menu(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu)
3098 GtkWidget *img, *item;
3099 const char *url;
3101 url = gtk_imhtml_link_get_url(link);
3103 /* Open Link */
3104 img = gtk_image_new_from_stock(GTK_STOCK_JUMP_TO, GTK_ICON_SIZE_MENU);
3105 item = gtk_image_menu_item_new_with_mnemonic(_("_Open Link"));
3106 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
3107 g_signal_connect_swapped(G_OBJECT(item), "activate", G_CALLBACK(gtk_imhtml_link_activate), link);
3108 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
3110 /* Copy Link Location */
3111 img = gtk_image_new_from_stock(GTK_STOCK_COPY, GTK_ICON_SIZE_MENU);
3112 item = gtk_image_menu_item_new_with_mnemonic(_("_Copy Link Location"));
3113 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
3114 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(url_copy), (gpointer)url);
3115 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
3117 return TRUE;
3120 static gboolean
3121 copy_email_address(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu)
3123 GtkWidget *img, *item;
3124 const char *text;
3125 char *address;
3126 #define MAILTOSIZE (sizeof("mailto:") - 1)
3128 text = gtk_imhtml_link_get_url(link);
3129 g_return_val_if_fail(text && strlen(text) > MAILTOSIZE, FALSE);
3130 address = (char*)text + MAILTOSIZE;
3132 /* Copy Email Address */
3133 img = gtk_image_new_from_stock(GTK_STOCK_COPY, GTK_ICON_SIZE_MENU);
3134 item = gtk_image_menu_item_new_with_mnemonic(_("_Copy Email Address"));
3135 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
3136 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(url_copy), address);
3137 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
3139 return TRUE;
3142 static void
3143 file_open_uri(GtkIMHtml *imhtml, const char *uri)
3145 /* Copied from gtkft.c:open_button_cb */
3146 #ifdef _WIN32
3147 /* If using Win32... */
3148 int code;
3149 wchar_t *wc_filename = g_utf8_to_utf16(
3150 uri, -1, NULL, NULL, NULL);
3152 code = (int)ShellExecuteW(NULL, NULL, wc_filename, NULL, NULL,
3153 SW_SHOW);
3155 g_free(wc_filename);
3157 if (code == SE_ERR_ASSOCINCOMPLETE || code == SE_ERR_NOASSOC)
3159 purple_notify_error(imhtml, NULL,
3160 _("There is no application configured to open this type of file."), NULL);
3162 else if (code < 32)
3164 purple_notify_error(imhtml, NULL,
3165 _("An error occurred while opening the file."), NULL);
3166 purple_debug_warning("gtkutils", "filename: %s; code: %d\n", uri, code);
3168 #else
3169 char *command = NULL;
3170 char *tmp = NULL;
3171 GError *error = NULL;
3173 if (purple_running_gnome())
3175 char *escaped = g_shell_quote(uri);
3176 command = g_strdup_printf("gnome-open %s", escaped);
3177 g_free(escaped);
3179 else if (purple_running_kde())
3181 char *escaped = g_shell_quote(uri);
3183 if (purple_str_has_suffix(uri, ".desktop"))
3184 command = g_strdup_printf("kfmclient openURL %s 'text/plain'", escaped);
3185 else
3186 command = g_strdup_printf("kfmclient openURL %s", escaped);
3187 g_free(escaped);
3189 else
3191 purple_notify_uri(NULL, uri);
3192 return;
3195 if (purple_program_is_valid(command))
3197 gint exit_status;
3198 if (!g_spawn_command_line_sync(command, NULL, NULL, &exit_status, &error))
3200 tmp = g_strdup_printf(_("Error launching %s: %s"),
3201 uri, error->message);
3202 purple_notify_error(imhtml, NULL, _("Unable to open file."), tmp);
3203 g_free(tmp);
3204 g_error_free(error);
3206 if (exit_status != 0)
3208 char *primary = g_strdup_printf(_("Error running %s"), command);
3209 char *secondary = g_strdup_printf(_("Process returned error code %d"),
3210 exit_status);
3211 purple_notify_error(imhtml, NULL, primary, secondary);
3212 g_free(tmp);
3215 #endif
3218 #define FILELINKSIZE (sizeof("file://") - 1)
3219 static gboolean
3220 file_clicked_cb(GtkIMHtml *imhtml, GtkIMHtmlLink *link)
3222 const char *uri = gtk_imhtml_link_get_url(link) + FILELINKSIZE;
3223 file_open_uri(imhtml, uri);
3224 return TRUE;
3227 static gboolean
3228 open_containing_cb(GtkIMHtml *imhtml, const char *url)
3230 char *dir = g_path_get_dirname(url + FILELINKSIZE);
3231 file_open_uri(imhtml, dir);
3232 g_free(dir);
3233 return TRUE;
3236 static gboolean
3237 file_context_menu(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu)
3239 GtkWidget *img, *item;
3240 const char *url;
3242 url = gtk_imhtml_link_get_url(link);
3244 /* Open File */
3245 img = gtk_image_new_from_stock(GTK_STOCK_JUMP_TO, GTK_ICON_SIZE_MENU);
3246 item = gtk_image_menu_item_new_with_mnemonic(_("_Open File"));
3247 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
3248 g_signal_connect_swapped(G_OBJECT(item), "activate", G_CALLBACK(gtk_imhtml_link_activate), link);
3249 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
3251 /* Open Containing Directory */
3252 img = gtk_image_new_from_stock(GTK_STOCK_DIRECTORY, GTK_ICON_SIZE_MENU);
3253 item = gtk_image_menu_item_new_with_mnemonic(_("Open _Containing Directory"));
3254 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
3256 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(open_containing_cb), (gpointer)url);
3257 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
3259 return TRUE;
3262 #define AUDIOLINKSIZE (sizeof("audio://") - 1)
3263 static gboolean
3264 audio_clicked_cb(GtkIMHtml *imhtml, GtkIMHtmlLink *link)
3266 const char *uri;
3267 PidginConversation *conv = g_object_get_data(G_OBJECT(imhtml), "gtkconv");
3268 if (!conv) /* no playback in debug window */
3269 return TRUE;
3270 uri = gtk_imhtml_link_get_url(link) + AUDIOLINKSIZE;
3271 purple_sound_play_file(uri, NULL);
3272 return TRUE;
3275 static void
3276 savefile_write_cb(gpointer user_data, char *file)
3278 char *temp_file = user_data;
3279 gchar *contents;
3280 gsize length;
3281 GError *error = NULL;
3283 if (!g_file_get_contents(temp_file, &contents, &length, &error)) {
3284 purple_debug_error("gtkutils", "Unable to read contents of %s: %s\n",
3285 temp_file, error->message);
3286 g_error_free(error);
3287 return;
3290 if (!purple_util_write_data_to_file_absolute(file, contents, length)) {
3291 purple_debug_error("gtkutils", "Unable to write contents to %s\n",
3292 file);
3296 static gboolean
3297 save_file_cb(GtkWidget *item, const char *url)
3299 PidginConversation *conv = g_object_get_data(G_OBJECT(item), "gtkconv");
3300 if (!conv)
3301 return TRUE;
3302 purple_request_file(conv->active_conv, _("Save File"), NULL, TRUE,
3303 G_CALLBACK(savefile_write_cb), NULL,
3304 conv->active_conv->account, NULL, conv->active_conv,
3305 (void *)url);
3306 return TRUE;
3309 static gboolean
3310 audio_context_menu(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu)
3312 GtkWidget *img, *item;
3313 const char *url;
3314 PidginConversation *conv = g_object_get_data(G_OBJECT(imhtml), "gtkconv");
3315 if (!conv) /* No menu in debug window */
3316 return TRUE;
3318 url = gtk_imhtml_link_get_url(link);
3320 /* Play Sound */
3321 img = gtk_image_new_from_stock(GTK_STOCK_MEDIA_PLAY, GTK_ICON_SIZE_MENU);
3322 item = gtk_image_menu_item_new_with_mnemonic(_("_Play Sound"));
3323 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
3325 g_signal_connect_swapped(G_OBJECT(item), "activate", G_CALLBACK(gtk_imhtml_link_activate), link);
3326 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
3328 /* Save File */
3329 img = gtk_image_new_from_stock(GTK_STOCK_SAVE, GTK_ICON_SIZE_MENU);
3330 item = gtk_image_menu_item_new_with_mnemonic(_("_Save File"));
3331 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
3332 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(save_file_cb), (gpointer)(url+AUDIOLINKSIZE));
3333 g_object_set_data(G_OBJECT(item), "gtkconv", conv);
3334 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
3336 return TRUE;
3339 /* XXX: The following two functions are for demonstration purposes only! */
3340 static gboolean
3341 open_dialog(GtkIMHtml *imhtml, GtkIMHtmlLink *link)
3343 const char *url;
3344 const char *str;
3346 url = gtk_imhtml_link_get_url(link);
3347 if (!url || strlen(url) < sizeof("open://"))
3348 return FALSE;
3350 str = url + sizeof("open://") - 1;
3352 if (strcmp(str, "accounts") == 0)
3353 pidgin_accounts_window_show();
3354 else if (strcmp(str, "prefs") == 0)
3355 pidgin_prefs_show();
3356 else
3357 return FALSE;
3358 return TRUE;
3361 static gboolean
3362 dummy(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu)
3364 return TRUE;
3367 static gboolean
3368 register_gnome_url_handlers(void)
3370 char *tmp;
3371 char *err;
3372 char *c;
3373 char *start;
3375 tmp = g_find_program_in_path("gconftool-2");
3376 if (tmp == NULL)
3377 return FALSE;
3379 g_free(tmp);
3380 tmp = NULL;
3382 if (!g_spawn_command_line_sync("gconftool-2 --all-dirs /desktop/gnome/url-handlers",
3383 &tmp, &err, NULL, NULL))
3385 g_free(tmp);
3386 g_free(err);
3387 g_return_val_if_reached(FALSE);
3389 g_free(err);
3390 err = NULL;
3392 for (c = start = tmp ; *c ; c++)
3394 /* Skip leading spaces. */
3395 if (c == start && *c == ' ')
3396 start = c + 1;
3397 else if (*c == '\n')
3399 *c = '\0';
3400 if (g_str_has_prefix(start, "/desktop/gnome/url-handlers/"))
3402 char *cmd;
3403 char *tmp2 = NULL;
3404 char *protocol;
3406 /* If there is an enabled boolean, honor it. */
3407 cmd = g_strdup_printf("gconftool-2 -g %s/enabled", start);
3408 if (g_spawn_command_line_sync(cmd, &tmp2, &err, NULL, NULL))
3410 g_free(err);
3411 err = NULL;
3412 if (!strcmp(tmp2, "false\n"))
3414 g_free(tmp2);
3415 g_free(cmd);
3416 start = c + 1;
3417 continue;
3420 g_free(cmd);
3421 g_free(tmp2);
3423 start += sizeof("/desktop/gnome/url-handlers/") - 1;
3425 protocol = g_strdup_printf("%s:", start);
3426 registered_url_handlers = g_slist_prepend(registered_url_handlers, protocol);
3427 gtk_imhtml_class_register_protocol(protocol, url_clicked_cb, link_context_menu);
3429 start = c + 1;
3432 g_free(tmp);
3434 return (registered_url_handlers != NULL);
3437 #ifdef _WIN32
3438 static void
3439 winpidgin_register_win32_url_handlers(void)
3441 int idx = 0;
3442 LONG ret = ERROR_SUCCESS;
3444 do {
3445 DWORD nameSize = 256;
3446 wchar_t start[256];
3447 ret = RegEnumKeyExW(HKEY_CLASSES_ROOT, idx++, start, &nameSize,
3448 NULL, NULL, NULL, NULL);
3449 if (ret == ERROR_SUCCESS) {
3450 HKEY reg_key = NULL;
3451 ret = RegOpenKeyExW(HKEY_CLASSES_ROOT, start, 0, KEY_READ, &reg_key);
3452 if (ret == ERROR_SUCCESS) {
3453 ret = RegQueryValueExW(reg_key, L"URL Protocol", NULL, NULL, NULL, NULL);
3454 if (ret == ERROR_SUCCESS) {
3455 gchar *utf8 = g_utf16_to_utf8(start, -1, NULL, NULL, NULL);
3456 gchar *protocol = g_strdup_printf("%s:", utf8);
3457 g_free(utf8);
3458 registered_url_handlers = g_slist_prepend(registered_url_handlers, protocol);
3459 /* We still pass everything to the "http" "open" handler for security reasons */
3460 gtk_imhtml_class_register_protocol(protocol, url_clicked_cb, link_context_menu);
3462 RegCloseKey(reg_key);
3464 ret = ERROR_SUCCESS;
3466 } while (ret == ERROR_SUCCESS);
3468 if (ret != ERROR_NO_MORE_ITEMS)
3469 purple_debug_error("winpidgin", "Error iterating HKEY_CLASSES_ROOT subkeys: %ld\n",
3470 ret);
3472 #endif
3474 void pidgin_utils_init(void)
3476 gtk_imhtml_class_register_protocol("http://", url_clicked_cb, link_context_menu);
3477 gtk_imhtml_class_register_protocol("https://", url_clicked_cb, link_context_menu);
3478 gtk_imhtml_class_register_protocol("ftp://", url_clicked_cb, link_context_menu);
3479 gtk_imhtml_class_register_protocol("gopher://", url_clicked_cb, link_context_menu);
3480 gtk_imhtml_class_register_protocol("mailto:", url_clicked_cb, copy_email_address);
3482 gtk_imhtml_class_register_protocol("file://", file_clicked_cb, file_context_menu);
3483 gtk_imhtml_class_register_protocol("audio://", audio_clicked_cb, audio_context_menu);
3485 /* Example custom URL handler. */
3486 gtk_imhtml_class_register_protocol("open://", open_dialog, dummy);
3488 /* If we're under GNOME, try registering the system URL handlers. */
3489 if (purple_running_gnome())
3490 register_gnome_url_handlers();
3492 /* Used to make small buttons */
3493 gtk_rc_parse_string("style \"pidgin-small-close-button\"\n"
3494 "{\n"
3495 "GtkWidget::focus-padding = 0\n"
3496 "GtkWidget::focus-line-width = 0\n"
3497 "xthickness = 0\n"
3498 "ythickness = 0\n"
3499 "GtkContainer::border-width = 0\n"
3500 "GtkButton::inner-border = {0, 0, 0, 0}\n"
3501 "GtkButton::default-border = {0, 0, 0, 0}\n"
3502 "}\n"
3503 "widget \"*.pidgin-small-close-button\" style \"pidgin-small-close-button\"");
3505 #ifdef _WIN32
3506 winpidgin_register_win32_url_handlers();
3507 #endif
3511 void pidgin_utils_uninit(void)
3513 gtk_imhtml_class_register_protocol("open://", NULL, NULL);
3515 /* If we have GNOME handlers registered, unregister them. */
3516 if (registered_url_handlers)
3518 GSList *l;
3519 for (l = registered_url_handlers; l; l = l->next)
3521 gtk_imhtml_class_register_protocol((char *)l->data, NULL, NULL);
3522 g_free(l->data);
3524 g_slist_free(registered_url_handlers);
3525 registered_url_handlers = NULL;
3526 return;
3529 gtk_imhtml_class_register_protocol("audio://", NULL, NULL);
3530 gtk_imhtml_class_register_protocol("file://", NULL, NULL);
3532 gtk_imhtml_class_register_protocol("http://", NULL, NULL);
3533 gtk_imhtml_class_register_protocol("https://", NULL, NULL);
3534 gtk_imhtml_class_register_protocol("ftp://", NULL, NULL);
3535 gtk_imhtml_class_register_protocol("mailto:", NULL, NULL);
3536 gtk_imhtml_class_register_protocol("gopher://", NULL, NULL);