Dutch translation updated (Gideon van Melle)
[pidgin-git.git] / pidgin / gtkutils.c
blobd4aadf82e81f27cf9fbf361b776214de02c363f4
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 imhtml = gtk_imhtml_new(NULL, NULL);
249 gtk_imhtml_set_editable(GTK_IMHTML(imhtml), editable);
250 gtk_imhtml_set_format_functions(GTK_IMHTML(imhtml), GTK_IMHTML_ALL ^ GTK_IMHTML_IMAGE);
251 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(imhtml), GTK_WRAP_WORD_CHAR);
252 #ifdef USE_GTKSPELL
253 if (editable && purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/spellcheck"))
254 pidgin_setup_gtkspell(GTK_TEXT_VIEW(imhtml));
255 #endif
256 gtk_widget_show(imhtml);
258 if (editable) {
259 gtk_imhtmltoolbar_attach(GTK_IMHTMLTOOLBAR(toolbar), imhtml);
260 gtk_imhtmltoolbar_associate_smileys(GTK_IMHTMLTOOLBAR(toolbar), "default");
262 pidgin_setup_imhtml(imhtml);
264 sw = pidgin_make_scrollable(imhtml, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC, GTK_SHADOW_NONE, -1, -1);
265 gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0);
267 if (imhtml_ret != NULL)
268 *imhtml_ret = imhtml;
270 if (editable && (toolbar_ret != NULL))
271 *toolbar_ret = toolbar;
273 if (sw_ret != NULL)
274 *sw_ret = sw;
276 return frame;
279 void
280 pidgin_set_sensitive_if_input(GtkWidget *entry, GtkWidget *dialog)
282 const char *text = gtk_entry_get_text(GTK_ENTRY(entry));
283 gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog), GTK_RESPONSE_OK,
284 (*text != '\0'));
287 void
288 pidgin_toggle_sensitive(GtkWidget *widget, GtkWidget *to_toggle)
290 gboolean sensitivity;
292 if (to_toggle == NULL)
293 return;
295 sensitivity = GTK_WIDGET_IS_SENSITIVE(to_toggle);
297 gtk_widget_set_sensitive(to_toggle, !sensitivity);
300 void
301 pidgin_toggle_sensitive_array(GtkWidget *w, GPtrArray *data)
303 gboolean sensitivity;
304 gpointer element;
305 int i;
307 for (i=0; i < data->len; i++) {
308 element = g_ptr_array_index(data,i);
309 if (element == NULL)
310 continue;
312 sensitivity = GTK_WIDGET_IS_SENSITIVE(element);
314 gtk_widget_set_sensitive(element, !sensitivity);
318 void
319 pidgin_toggle_showhide(GtkWidget *widget, GtkWidget *to_toggle)
321 if (to_toggle == NULL)
322 return;
324 if (GTK_WIDGET_VISIBLE(to_toggle))
325 gtk_widget_hide(to_toggle);
326 else
327 gtk_widget_show(to_toggle);
330 GtkWidget *pidgin_separator(GtkWidget *menu)
332 GtkWidget *menuitem;
334 menuitem = gtk_separator_menu_item_new();
335 gtk_widget_show(menuitem);
336 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
337 return menuitem;
340 GtkWidget *pidgin_new_item(GtkWidget *menu, const char *str)
342 GtkWidget *menuitem;
343 GtkWidget *label;
345 menuitem = gtk_menu_item_new();
346 if (menu)
347 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
348 gtk_widget_show(menuitem);
350 label = gtk_label_new(str);
351 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
352 gtk_label_set_pattern(GTK_LABEL(label), "_");
353 gtk_container_add(GTK_CONTAINER(menuitem), label);
354 gtk_widget_show(label);
355 /* FIXME: Go back and fix this
356 gtk_widget_add_accelerator(menuitem, "activate", accel, str[0],
357 GDK_MOD1_MASK, GTK_ACCEL_LOCKED);
359 pidgin_set_accessible_label (menuitem, label);
360 return menuitem;
363 GtkWidget *pidgin_new_check_item(GtkWidget *menu, const char *str,
364 GCallback cb, gpointer data, gboolean checked)
366 GtkWidget *menuitem;
367 menuitem = gtk_check_menu_item_new_with_mnemonic(str);
369 if (menu)
370 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
372 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), checked);
374 if (cb)
375 g_signal_connect(G_OBJECT(menuitem), "activate", cb, data);
377 gtk_widget_show_all(menuitem);
379 return menuitem;
382 GtkWidget *
383 pidgin_pixbuf_toolbar_button_from_stock(const char *icon)
385 GtkWidget *button, *image, *bbox;
387 button = gtk_toggle_button_new();
388 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
390 bbox = gtk_vbox_new(FALSE, 0);
392 gtk_container_add (GTK_CONTAINER(button), bbox);
394 image = gtk_image_new_from_stock(icon, gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL));
395 gtk_box_pack_start(GTK_BOX(bbox), image, FALSE, FALSE, 0);
397 gtk_widget_show_all(bbox);
399 return button;
402 GtkWidget *
403 pidgin_pixbuf_button_from_stock(const char *text, const char *icon,
404 PidginButtonOrientation style)
406 GtkWidget *button, *image, *label, *bbox, *ibox, *lbox = NULL;
408 button = gtk_button_new();
410 if (style == PIDGIN_BUTTON_HORIZONTAL) {
411 bbox = gtk_hbox_new(FALSE, 0);
412 ibox = gtk_hbox_new(FALSE, 0);
413 if (text)
414 lbox = gtk_hbox_new(FALSE, 0);
415 } else {
416 bbox = gtk_vbox_new(FALSE, 0);
417 ibox = gtk_vbox_new(FALSE, 0);
418 if (text)
419 lbox = gtk_vbox_new(FALSE, 0);
422 gtk_container_add(GTK_CONTAINER(button), bbox);
424 if (icon) {
425 gtk_box_pack_start(GTK_BOX(bbox), ibox, TRUE, TRUE, 0);
426 image = gtk_image_new_from_stock(icon, GTK_ICON_SIZE_BUTTON);
427 gtk_box_pack_end(GTK_BOX(ibox), image, FALSE, TRUE, 0);
430 if (text) {
431 gtk_box_pack_start(GTK_BOX(bbox), lbox, TRUE, TRUE, 0);
432 label = gtk_label_new(NULL);
433 gtk_label_set_text_with_mnemonic(GTK_LABEL(label), text);
434 gtk_label_set_mnemonic_widget(GTK_LABEL(label), button);
435 gtk_box_pack_start(GTK_BOX(lbox), label, FALSE, TRUE, 0);
436 pidgin_set_accessible_label (button, label);
439 gtk_widget_show_all(bbox);
441 return button;
445 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)
447 GtkWidget *menuitem;
449 GtkWidget *hbox;
450 GtkWidget *label;
452 GtkWidget *image;
454 if (icon == NULL)
455 menuitem = gtk_menu_item_new_with_mnemonic(str);
456 else
457 menuitem = gtk_image_menu_item_new_with_mnemonic(str);
459 if (menu)
460 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
462 if (cb)
463 g_signal_connect(G_OBJECT(menuitem), "activate", cb, data);
465 if (icon != NULL) {
466 image = gtk_image_new_from_stock(icon, gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL));
467 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image);
469 /* FIXME: this isn't right
470 if (mod) {
471 label = gtk_label_new(mod);
472 gtk_box_pack_end(GTK_BOX(hbox), label, FALSE, FALSE, 2);
473 gtk_widget_show(label);
477 if (accel_key) {
478 gtk_widget_add_accelerator(menuitem, "activate", accel, accel_key,
479 accel_mods, GTK_ACCEL_LOCKED);
483 gtk_widget_show_all(menuitem);
485 return menuitem;
488 GtkWidget *
489 pidgin_make_frame(GtkWidget *parent, const char *title)
491 GtkWidget *vbox, *label, *hbox;
492 char *labeltitle;
494 vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
495 gtk_box_pack_start(GTK_BOX(parent), vbox, FALSE, FALSE, 0);
496 gtk_widget_show(vbox);
498 label = gtk_label_new(NULL);
500 labeltitle = g_strdup_printf("<span weight=\"bold\">%s</span>", title);
501 gtk_label_set_markup(GTK_LABEL(label), labeltitle);
502 g_free(labeltitle);
504 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
505 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
506 gtk_widget_show(label);
507 pidgin_set_accessible_label (vbox, label);
509 hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
510 gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
511 gtk_widget_show(hbox);
513 label = gtk_label_new(" ");
514 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
515 gtk_widget_show(label);
517 vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
518 gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);
519 gtk_widget_show(vbox);
521 return vbox;
524 static gpointer
525 aop_option_menu_get_selected(GtkWidget *optmenu, GtkWidget **p_item)
527 GtkWidget *menu = gtk_option_menu_get_menu(GTK_OPTION_MENU(optmenu));
528 GtkWidget *item = gtk_menu_get_active(GTK_MENU(menu));
529 if (p_item)
530 (*p_item) = item;
531 return item ? g_object_get_data(G_OBJECT(item), "aop_per_item_data") : NULL;
534 static void
535 aop_menu_cb(GtkWidget *optmenu, GCallback cb)
537 GtkWidget *item;
538 gpointer per_item_data;
540 per_item_data = aop_option_menu_get_selected(optmenu, &item);
542 if (cb != NULL) {
543 ((void (*)(GtkWidget *, gpointer, gpointer))cb)(item, per_item_data, g_object_get_data(G_OBJECT(optmenu), "user_data"));
547 static GtkWidget *
548 aop_menu_item_new(GtkSizeGroup *sg, GdkPixbuf *pixbuf, const char *lbl, gpointer per_item_data, const char *data)
550 GtkWidget *item;
551 GtkWidget *hbox;
552 GtkWidget *image;
553 GtkWidget *label;
555 item = gtk_menu_item_new();
556 gtk_widget_show(item);
558 hbox = gtk_hbox_new(FALSE, 4);
559 gtk_widget_show(hbox);
561 /* Create the image */
562 if (pixbuf == NULL)
563 image = gtk_image_new();
564 else
565 image = gtk_image_new_from_pixbuf(pixbuf);
566 gtk_widget_show(image);
568 if (sg)
569 gtk_size_group_add_widget(sg, image);
571 /* Create the label */
572 label = gtk_label_new (lbl);
573 gtk_widget_show (label);
574 gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
575 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
577 gtk_container_add(GTK_CONTAINER(item), hbox);
578 gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0);
579 gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
581 g_object_set_data(G_OBJECT (item), data, per_item_data);
582 g_object_set_data(G_OBJECT (item), "aop_per_item_data", per_item_data);
584 pidgin_set_accessible_label(item, label);
586 return item;
589 static GdkPixbuf *
590 pidgin_create_prpl_icon_from_prpl(PurplePlugin *prpl, PidginPrplIconSize size, PurpleAccount *account)
592 PurplePluginProtocolInfo *prpl_info;
593 const char *protoname = NULL;
594 char *tmp;
595 char *filename = NULL;
596 GdkPixbuf *pixbuf;
598 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
599 if (prpl_info->list_icon == NULL)
600 return NULL;
602 protoname = prpl_info->list_icon(account, NULL);
603 if (protoname == NULL)
604 return NULL;
607 * Status icons will be themeable too, and then it will look up
608 * protoname from the theme
610 tmp = g_strconcat(protoname, ".png", NULL);
612 filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "protocols",
613 size == PIDGIN_PRPL_ICON_SMALL ? "16" :
614 size == PIDGIN_PRPL_ICON_MEDIUM ? "22" : "48",
615 tmp, NULL);
616 g_free(tmp);
618 pixbuf = pidgin_pixbuf_new_from_file(filename);
619 g_free(filename);
621 return pixbuf;
624 static GtkWidget *
625 aop_option_menu_new(AopMenu *aop_menu, GCallback cb, gpointer user_data)
627 GtkWidget *optmenu;
629 optmenu = gtk_option_menu_new();
630 gtk_widget_show(optmenu);
631 gtk_option_menu_set_menu(GTK_OPTION_MENU(optmenu), aop_menu->menu);
633 if (aop_menu->default_item != -1)
634 gtk_option_menu_set_history(GTK_OPTION_MENU(optmenu), aop_menu->default_item);
636 g_object_set_data_full(G_OBJECT(optmenu), "aop_menu", aop_menu, (GDestroyNotify)g_free);
637 g_object_set_data(G_OBJECT(optmenu), "user_data", user_data);
639 g_signal_connect(G_OBJECT(optmenu), "changed", G_CALLBACK(aop_menu_cb), cb);
641 return optmenu;
644 static void
645 aop_option_menu_replace_menu(GtkWidget *optmenu, AopMenu *new_aop_menu)
647 if (gtk_option_menu_get_menu(GTK_OPTION_MENU(optmenu)))
648 gtk_option_menu_remove_menu(GTK_OPTION_MENU(optmenu));
650 gtk_option_menu_set_menu(GTK_OPTION_MENU(optmenu), new_aop_menu->menu);
652 if (new_aop_menu->default_item != -1)
653 gtk_option_menu_set_history(GTK_OPTION_MENU(optmenu), new_aop_menu->default_item);
655 g_object_set_data_full(G_OBJECT(optmenu), "aop_menu", new_aop_menu, (GDestroyNotify)g_free);
658 static void
659 aop_option_menu_select_by_data(GtkWidget *optmenu, gpointer data)
661 guint idx;
662 GList *llItr = NULL;
664 for (idx = 0, llItr = GTK_MENU_SHELL(gtk_option_menu_get_menu(GTK_OPTION_MENU(optmenu)))->children;
665 llItr != NULL;
666 llItr = llItr->next, idx++) {
667 if (data == g_object_get_data(G_OBJECT(llItr->data), "aop_per_item_data")) {
668 gtk_option_menu_set_history(GTK_OPTION_MENU(optmenu), idx);
669 break;
674 static AopMenu *
675 create_protocols_menu(const char *default_proto_id)
677 AopMenu *aop_menu = NULL;
678 PurplePlugin *plugin;
679 GdkPixbuf *pixbuf = NULL;
680 GtkSizeGroup *sg;
681 GList *p;
682 const char *gtalk_name = NULL, *facebook_name = NULL;
683 int i;
685 aop_menu = g_malloc0(sizeof(AopMenu));
686 aop_menu->default_item = -1;
687 aop_menu->menu = gtk_menu_new();
688 gtk_widget_show(aop_menu->menu);
689 sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
691 if (purple_find_prpl("prpl-jabber")) {
692 gtalk_name = _("Google Talk");
693 facebook_name = _("Facebook (XMPP)");
696 for (p = purple_plugins_get_protocols(), i = 0;
697 p != NULL;
698 p = p->next, i++) {
700 plugin = (PurplePlugin *)p->data;
702 if (gtalk_name && strcmp(gtalk_name, plugin->info->name) < 0) {
703 char *filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "protocols",
704 "16", "google-talk.png", NULL);
705 GtkWidget *item;
707 pixbuf = pidgin_pixbuf_new_from_file(filename);
708 g_free(filename);
710 gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu->menu),
711 item = aop_menu_item_new(sg, pixbuf, gtalk_name, "prpl-jabber", "protocol"));
712 g_object_set_data(G_OBJECT(item), "fakegoogle", GINT_TO_POINTER(1));
714 if (pixbuf)
715 g_object_unref(pixbuf);
717 gtalk_name = NULL;
718 i++;
721 if (facebook_name && strcmp(facebook_name, plugin->info->name) < 0) {
722 char *filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "protocols",
723 "16", "facebook.png", NULL);
724 GtkWidget *item;
726 pixbuf = pidgin_pixbuf_new_from_file(filename);
727 g_free(filename);
729 gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu->menu),
730 item = aop_menu_item_new(sg, pixbuf, facebook_name, "prpl-jabber", "protocol"));
731 g_object_set_data(G_OBJECT(item), "fakefacebook", GINT_TO_POINTER(1));
733 if (pixbuf)
734 g_object_unref(pixbuf);
736 facebook_name = NULL;
737 i++;
740 pixbuf = pidgin_create_prpl_icon_from_prpl(plugin, PIDGIN_PRPL_ICON_SMALL, NULL);
742 gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu->menu),
743 aop_menu_item_new(sg, pixbuf, plugin->info->name, plugin->info->id, "protocol"));
745 if (pixbuf)
746 g_object_unref(pixbuf);
748 if (default_proto_id != NULL && !strcmp(plugin->info->id, default_proto_id))
749 aop_menu->default_item = i;
752 g_object_unref(sg);
754 return aop_menu;
757 GtkWidget *
758 pidgin_protocol_option_menu_new(const char *id, GCallback cb,
759 gpointer user_data)
761 return aop_option_menu_new(create_protocols_menu(id), cb, user_data);
764 const char *
765 pidgin_protocol_option_menu_get_selected(GtkWidget *optmenu)
767 return (const char *)aop_option_menu_get_selected(optmenu, NULL);
770 PurpleAccount *
771 pidgin_account_option_menu_get_selected(GtkWidget *optmenu)
773 return (PurpleAccount *)aop_option_menu_get_selected(optmenu, NULL);
776 static AopMenu *
777 create_account_menu(PurpleAccount *default_account,
778 PurpleFilterAccountFunc filter_func, gboolean show_all)
780 AopMenu *aop_menu = NULL;
781 PurpleAccount *account;
782 GdkPixbuf *pixbuf = NULL;
783 GList *list;
784 GList *p;
785 GtkSizeGroup *sg;
786 int i;
787 char buf[256];
789 if (show_all)
790 list = purple_accounts_get_all();
791 else
792 list = purple_connections_get_all();
794 aop_menu = g_malloc0(sizeof(AopMenu));
795 aop_menu->default_item = -1;
796 aop_menu->menu = gtk_menu_new();
797 gtk_widget_show(aop_menu->menu);
798 sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
800 for (p = list, i = 0; p != NULL; p = p->next, i++) {
801 if (show_all)
802 account = (PurpleAccount *)p->data;
803 else {
804 PurpleConnection *gc = (PurpleConnection *)p->data;
806 account = purple_connection_get_account(gc);
809 if (filter_func && !filter_func(account)) {
810 i--;
811 continue;
814 pixbuf = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL);
816 if (pixbuf) {
817 if (purple_account_is_disconnected(account) && show_all &&
818 purple_connections_get_all())
819 gdk_pixbuf_saturate_and_pixelate(pixbuf, pixbuf, 0.0, FALSE);
822 if (purple_account_get_alias(account)) {
823 g_snprintf(buf, sizeof(buf), "%s (%s) (%s)",
824 purple_account_get_username(account),
825 purple_account_get_alias(account),
826 purple_account_get_protocol_name(account));
827 } else {
828 g_snprintf(buf, sizeof(buf), "%s (%s)",
829 purple_account_get_username(account),
830 purple_account_get_protocol_name(account));
833 gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu->menu),
834 aop_menu_item_new(sg, pixbuf, buf, account, "account"));
836 if (pixbuf)
837 g_object_unref(pixbuf);
839 if (default_account && account == default_account)
840 aop_menu->default_item = i;
843 g_object_unref(sg);
845 return aop_menu;
848 static void
849 regenerate_account_menu(GtkWidget *optmenu)
851 gboolean show_all;
852 PurpleAccount *account;
853 PurpleFilterAccountFunc filter_func;
855 account = (PurpleAccount *)aop_option_menu_get_selected(optmenu, NULL);
856 show_all = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(optmenu), "show_all"));
857 filter_func = g_object_get_data(G_OBJECT(optmenu), "filter_func");
859 aop_option_menu_replace_menu(optmenu, create_account_menu(account, filter_func, show_all));
862 static void
863 account_menu_sign_on_off_cb(PurpleConnection *gc, GtkWidget *optmenu)
865 regenerate_account_menu(optmenu);
868 static void
869 account_menu_added_removed_cb(PurpleAccount *account, GtkWidget *optmenu)
871 regenerate_account_menu(optmenu);
874 static gboolean
875 account_menu_destroyed_cb(GtkWidget *optmenu, GdkEvent *event,
876 void *user_data)
878 purple_signals_disconnect_by_handle(optmenu);
880 return FALSE;
883 void
884 pidgin_account_option_menu_set_selected(GtkWidget *optmenu, PurpleAccount *account)
886 aop_option_menu_select_by_data(optmenu, account);
889 GtkWidget *
890 pidgin_account_option_menu_new(PurpleAccount *default_account,
891 gboolean show_all, GCallback cb,
892 PurpleFilterAccountFunc filter_func,
893 gpointer user_data)
895 GtkWidget *optmenu;
897 /* Create the option menu */
898 optmenu = aop_option_menu_new(create_account_menu(default_account, filter_func, show_all), cb, user_data);
900 g_signal_connect(G_OBJECT(optmenu), "destroy",
901 G_CALLBACK(account_menu_destroyed_cb), NULL);
903 /* Register the purple sign on/off event callbacks. */
904 purple_signal_connect(purple_connections_get_handle(), "signed-on",
905 optmenu, PURPLE_CALLBACK(account_menu_sign_on_off_cb),
906 optmenu);
907 purple_signal_connect(purple_connections_get_handle(), "signed-off",
908 optmenu, PURPLE_CALLBACK(account_menu_sign_on_off_cb),
909 optmenu);
910 purple_signal_connect(purple_accounts_get_handle(), "account-added",
911 optmenu, PURPLE_CALLBACK(account_menu_added_removed_cb),
912 optmenu);
913 purple_signal_connect(purple_accounts_get_handle(), "account-removed",
914 optmenu, PURPLE_CALLBACK(account_menu_added_removed_cb),
915 optmenu);
917 /* Set some data. */
918 g_object_set_data(G_OBJECT(optmenu), "user_data", user_data);
919 g_object_set_data(G_OBJECT(optmenu), "show_all", GINT_TO_POINTER(show_all));
920 g_object_set_data(G_OBJECT(optmenu), "filter_func", filter_func);
922 return optmenu;
925 gboolean
926 pidgin_check_if_dir(const char *path, GtkFileSelection *filesel)
928 char *dirname = NULL;
930 if (g_file_test(path, G_FILE_TEST_IS_DIR)) {
931 /* append a / if needed */
932 if (path[strlen(path) - 1] != G_DIR_SEPARATOR) {
933 dirname = g_strconcat(path, G_DIR_SEPARATOR_S, NULL);
935 gtk_file_selection_set_filename(filesel, (dirname != NULL) ? dirname : path);
936 g_free(dirname);
937 return TRUE;
940 return FALSE;
943 void
944 pidgin_setup_gtkspell(GtkTextView *textview)
946 #ifdef USE_GTKSPELL
947 GError *error = NULL;
948 char *locale = NULL;
950 g_return_if_fail(textview != NULL);
951 g_return_if_fail(GTK_IS_TEXT_VIEW(textview));
953 if (gtkspell_new_attach(textview, locale, &error) == NULL && error)
955 purple_debug_warning("gtkspell", "Failed to setup GtkSpell: %s\n",
956 error->message);
957 g_error_free(error);
959 #endif /* USE_GTKSPELL */
962 void
963 pidgin_save_accels_cb(GtkAccelGroup *accel_group, guint arg1,
964 GdkModifierType arg2, GClosure *arg3,
965 gpointer data)
967 purple_debug(PURPLE_DEBUG_MISC, "accels",
968 "accel changed, scheduling save.\n");
970 if (!accels_save_timer)
971 accels_save_timer = purple_timeout_add_seconds(5, pidgin_save_accels,
972 NULL);
975 gboolean
976 pidgin_save_accels(gpointer data)
978 char *filename = NULL;
980 filename = g_build_filename(purple_user_dir(), G_DIR_SEPARATOR_S,
981 "accels", NULL);
982 purple_debug(PURPLE_DEBUG_MISC, "accels", "saving accels to %s\n", filename);
983 gtk_accel_map_save(filename);
984 g_free(filename);
986 accels_save_timer = 0;
987 return FALSE;
990 void
991 pidgin_load_accels()
993 char *filename = NULL;
995 filename = g_build_filename(purple_user_dir(), G_DIR_SEPARATOR_S,
996 "accels", NULL);
997 gtk_accel_map_load(filename);
998 g_free(filename);
1001 static void
1002 show_retrieveing_info(PurpleConnection *conn, const char *name)
1004 PurpleNotifyUserInfo *info = purple_notify_user_info_new();
1005 purple_notify_user_info_add_pair(info, _("Information"), _("Retrieving..."));
1006 purple_notify_userinfo(conn, name, info, NULL, NULL);
1007 purple_notify_user_info_destroy(info);
1010 void pidgin_retrieve_user_info(PurpleConnection *conn, const char *name)
1012 show_retrieveing_info(conn, name);
1013 serv_get_info(conn, name);
1016 void pidgin_retrieve_user_info_in_chat(PurpleConnection *conn, const char *name, int chat)
1018 char *who = NULL;
1019 PurplePluginProtocolInfo *prpl_info = NULL;
1021 if (chat < 0) {
1022 pidgin_retrieve_user_info(conn, name);
1023 return;
1026 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(conn->prpl);
1027 if (prpl_info != NULL && prpl_info->get_cb_real_name)
1028 who = prpl_info->get_cb_real_name(conn, chat, name);
1029 if (prpl_info == NULL || prpl_info->get_cb_info == NULL) {
1030 pidgin_retrieve_user_info(conn, who ? who : name);
1031 g_free(who);
1032 return;
1035 show_retrieveing_info(conn, who ? who : name);
1036 prpl_info->get_cb_info(conn, chat, name);
1037 g_free(who);
1040 gboolean
1041 pidgin_parse_x_im_contact(const char *msg, gboolean all_accounts,
1042 PurpleAccount **ret_account, char **ret_protocol,
1043 char **ret_username, char **ret_alias)
1045 char *protocol = NULL;
1046 char *username = NULL;
1047 char *alias = NULL;
1048 char *str;
1049 char *s;
1050 gboolean valid;
1052 g_return_val_if_fail(msg != NULL, FALSE);
1053 g_return_val_if_fail(ret_protocol != NULL, FALSE);
1054 g_return_val_if_fail(ret_username != NULL, FALSE);
1056 s = str = g_strdup(msg);
1058 while (*s != '\r' && *s != '\n' && *s != '\0')
1060 char *key, *value;
1062 key = s;
1064 /* Grab the key */
1065 while (*s != '\r' && *s != '\n' && *s != '\0' && *s != ' ')
1066 s++;
1068 if (*s == '\r') s++;
1070 if (*s == '\n')
1072 s++;
1073 continue;
1076 if (*s != '\0') *s++ = '\0';
1078 /* Clear past any whitespace */
1079 while (*s != '\0' && *s == ' ')
1080 s++;
1082 /* Now let's grab until the end of the line. */
1083 value = s;
1085 while (*s != '\r' && *s != '\n' && *s != '\0')
1086 s++;
1088 if (*s == '\r') *s++ = '\0';
1089 if (*s == '\n') *s++ = '\0';
1091 if (strchr(key, ':') != NULL)
1093 if (!g_ascii_strcasecmp(key, "X-IM-Username:"))
1094 username = g_strdup(value);
1095 else if (!g_ascii_strcasecmp(key, "X-IM-Protocol:"))
1096 protocol = g_strdup(value);
1097 else if (!g_ascii_strcasecmp(key, "X-IM-Alias:"))
1098 alias = g_strdup(value);
1102 if (username != NULL && protocol != NULL)
1104 valid = TRUE;
1106 *ret_username = username;
1107 *ret_protocol = protocol;
1109 if (ret_alias != NULL)
1110 *ret_alias = alias;
1112 /* Check for a compatible account. */
1113 if (ret_account != NULL)
1115 GList *list;
1116 PurpleAccount *account = NULL;
1117 GList *l;
1118 const char *protoname;
1120 if (all_accounts)
1121 list = purple_accounts_get_all();
1122 else
1123 list = purple_connections_get_all();
1125 for (l = list; l != NULL; l = l->next)
1127 PurpleConnection *gc;
1128 PurplePluginProtocolInfo *prpl_info = NULL;
1129 PurplePlugin *plugin;
1131 if (all_accounts)
1133 account = (PurpleAccount *)l->data;
1135 plugin = purple_plugins_find_with_id(
1136 purple_account_get_protocol_id(account));
1138 if (plugin == NULL)
1140 account = NULL;
1142 continue;
1145 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin);
1147 else
1149 gc = (PurpleConnection *)l->data;
1150 account = purple_connection_get_account(gc);
1152 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
1155 protoname = prpl_info->list_icon(account, NULL);
1157 if (!strcmp(protoname, protocol))
1158 break;
1160 account = NULL;
1163 /* Special case for AIM and ICQ */
1164 if (account == NULL && (!strcmp(protocol, "aim") ||
1165 !strcmp(protocol, "icq")))
1167 for (l = list; l != NULL; l = l->next)
1169 PurpleConnection *gc;
1170 PurplePluginProtocolInfo *prpl_info = NULL;
1171 PurplePlugin *plugin;
1173 if (all_accounts)
1175 account = (PurpleAccount *)l->data;
1177 plugin = purple_plugins_find_with_id(
1178 purple_account_get_protocol_id(account));
1180 if (plugin == NULL)
1182 account = NULL;
1184 continue;
1187 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin);
1189 else
1191 gc = (PurpleConnection *)l->data;
1192 account = purple_connection_get_account(gc);
1194 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
1197 protoname = prpl_info->list_icon(account, NULL);
1199 if (!strcmp(protoname, "aim") || !strcmp(protoname, "icq"))
1200 break;
1202 account = NULL;
1206 *ret_account = account;
1209 else
1211 valid = FALSE;
1213 g_free(username);
1214 g_free(protocol);
1215 g_free(alias);
1218 g_free(str);
1220 return valid;
1223 void
1224 pidgin_set_accessible_label (GtkWidget *w, GtkWidget *l)
1226 AtkObject *acc;
1227 const gchar *label_text;
1228 const gchar *existing_name;
1230 acc = gtk_widget_get_accessible (w);
1232 /* If this object has no name, set it's name with the label text */
1233 existing_name = atk_object_get_name (acc);
1234 if (!existing_name) {
1235 label_text = gtk_label_get_text (GTK_LABEL(l));
1236 if (label_text)
1237 atk_object_set_name (acc, label_text);
1240 pidgin_set_accessible_relations(w, l);
1243 void
1244 pidgin_set_accessible_relations (GtkWidget *w, GtkWidget *l)
1246 AtkObject *acc, *label;
1247 AtkObject *rel_obj[1];
1248 AtkRelationSet *set;
1249 AtkRelation *relation;
1251 acc = gtk_widget_get_accessible (w);
1252 label = gtk_widget_get_accessible (l);
1254 /* Make sure mnemonics work */
1255 gtk_label_set_mnemonic_widget(GTK_LABEL(l), w);
1257 /* Create the labeled-by relation */
1258 set = atk_object_ref_relation_set (acc);
1259 rel_obj[0] = label;
1260 relation = atk_relation_new (rel_obj, 1, ATK_RELATION_LABELLED_BY);
1261 atk_relation_set_add (set, relation);
1262 g_object_unref (relation);
1263 g_object_unref(set);
1265 /* Create the label-for relation */
1266 set = atk_object_ref_relation_set (label);
1267 rel_obj[0] = acc;
1268 relation = atk_relation_new (rel_obj, 1, ATK_RELATION_LABEL_FOR);
1269 atk_relation_set_add (set, relation);
1270 g_object_unref (relation);
1271 g_object_unref(set);
1274 void
1275 pidgin_menu_position_func_helper(GtkMenu *menu,
1276 gint *x,
1277 gint *y,
1278 gboolean *push_in,
1279 gpointer data)
1281 GtkWidget *widget;
1282 GtkRequisition requisition;
1283 GdkScreen *screen;
1284 GdkRectangle monitor;
1285 gint monitor_num;
1286 gint space_left, space_right, space_above, space_below;
1287 gint needed_width;
1288 gint needed_height;
1289 gint xthickness;
1290 gint ythickness;
1291 gboolean rtl;
1293 g_return_if_fail(GTK_IS_MENU(menu));
1295 widget = GTK_WIDGET(menu);
1296 screen = gtk_widget_get_screen(widget);
1297 xthickness = widget->style->xthickness;
1298 ythickness = widget->style->ythickness;
1299 rtl = (gtk_widget_get_direction(widget) == GTK_TEXT_DIR_RTL);
1302 * We need the requisition to figure out the right place to
1303 * popup the menu. In fact, we always need to ask here, since
1304 * if a size_request was queued while we weren't popped up,
1305 * the requisition won't have been recomputed yet.
1307 gtk_widget_size_request (widget, &requisition);
1309 monitor_num = gdk_screen_get_monitor_at_point (screen, *x, *y);
1311 push_in = FALSE;
1314 * The placement of popup menus horizontally works like this (with
1315 * RTL in parentheses)
1317 * - If there is enough room to the right (left) of the mouse cursor,
1318 * position the menu there.
1320 * - Otherwise, if if there is enough room to the left (right) of the
1321 * mouse cursor, position the menu there.
1323 * - Otherwise if the menu is smaller than the monitor, position it
1324 * on the side of the mouse cursor that has the most space available
1326 * - Otherwise (if there is simply not enough room for the menu on the
1327 * monitor), position it as far left (right) as possible.
1329 * Positioning in the vertical direction is similar: first try below
1330 * mouse cursor, then above.
1332 gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
1334 space_left = *x - monitor.x;
1335 space_right = monitor.x + monitor.width - *x - 1;
1336 space_above = *y - monitor.y;
1337 space_below = monitor.y + monitor.height - *y - 1;
1339 /* position horizontally */
1341 /* the amount of space we need to position the menu. Note the
1342 * menu is offset "xthickness" pixels
1344 needed_width = requisition.width - xthickness;
1346 if (needed_width <= space_left ||
1347 needed_width <= space_right)
1349 if ((rtl && needed_width <= space_left) ||
1350 (!rtl && needed_width > space_right))
1352 /* position left */
1353 *x = *x + xthickness - requisition.width + 1;
1355 else
1357 /* position right */
1358 *x = *x - xthickness;
1361 /* x is clamped on-screen further down */
1363 else if (requisition.width <= monitor.width)
1365 /* the menu is too big to fit on either side of the mouse
1366 * cursor, but smaller than the monitor. Position it on
1367 * the side that has the most space
1369 if (space_left > space_right)
1371 /* left justify */
1372 *x = monitor.x;
1374 else
1376 /* right justify */
1377 *x = monitor.x + monitor.width - requisition.width;
1380 else /* menu is simply too big for the monitor */
1382 if (rtl)
1384 /* right justify */
1385 *x = monitor.x + monitor.width - requisition.width;
1387 else
1389 /* left justify */
1390 *x = monitor.x;
1394 /* Position vertically. The algorithm is the same as above, but
1395 * simpler because we don't have to take RTL into account.
1397 needed_height = requisition.height - ythickness;
1399 if (needed_height <= space_above ||
1400 needed_height <= space_below)
1402 if (needed_height <= space_below)
1403 *y = *y - ythickness;
1404 else
1405 *y = *y + ythickness - requisition.height + 1;
1407 *y = CLAMP (*y, monitor.y,
1408 monitor.y + monitor.height - requisition.height);
1410 else if (needed_height > space_below && needed_height > space_above)
1412 if (space_below >= space_above)
1413 *y = monitor.y + monitor.height - requisition.height;
1414 else
1415 *y = monitor.y;
1417 else
1419 *y = monitor.y;
1424 void
1425 pidgin_treeview_popup_menu_position_func(GtkMenu *menu,
1426 gint *x,
1427 gint *y,
1428 gboolean *push_in,
1429 gpointer data)
1431 GtkWidget *widget = GTK_WIDGET(data);
1432 GtkTreeView *tv = GTK_TREE_VIEW(data);
1433 GtkTreePath *path;
1434 GtkTreeViewColumn *col;
1435 GdkRectangle rect;
1436 gint ythickness = GTK_WIDGET(menu)->style->ythickness;
1438 gdk_window_get_origin (widget->window, x, y);
1439 gtk_tree_view_get_cursor (tv, &path, &col);
1440 gtk_tree_view_get_cell_area (tv, path, col, &rect);
1442 *x += rect.x+rect.width;
1443 *y += rect.y+rect.height+ythickness;
1444 pidgin_menu_position_func_helper(menu, x, y, push_in, data);
1447 enum {
1448 DND_FILE_TRANSFER,
1449 DND_IM_IMAGE,
1450 DND_BUDDY_ICON
1453 typedef struct {
1454 char *filename;
1455 PurpleAccount *account;
1456 char *who;
1457 } _DndData;
1459 static void dnd_image_ok_callback(_DndData *data, int choice)
1461 const gchar *shortname;
1462 gchar *filedata;
1463 size_t size;
1464 struct stat st;
1465 GError *err = NULL;
1466 PurpleConversation *conv;
1467 PidginConversation *gtkconv;
1468 GtkTextIter iter;
1469 int id;
1470 PurpleBuddy *buddy;
1471 PurpleContact *contact;
1472 switch (choice) {
1473 case DND_BUDDY_ICON:
1474 if (g_stat(data->filename, &st)) {
1475 char *str;
1477 str = g_strdup_printf(_("The following error has occurred loading %s: %s"),
1478 data->filename, g_strerror(errno));
1479 purple_notify_error(NULL, NULL,
1480 _("Failed to load image"),
1481 str);
1482 g_free(str);
1484 break;
1487 buddy = purple_find_buddy(data->account, data->who);
1488 if (!buddy) {
1489 purple_debug_info("custom-icon", "You can only set custom icons for people on your buddylist.\n");
1490 break;
1492 contact = purple_buddy_get_contact(buddy);
1493 purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode*)contact, data->filename);
1494 break;
1495 case DND_FILE_TRANSFER:
1496 serv_send_file(purple_account_get_connection(data->account), data->who, data->filename);
1497 break;
1498 case DND_IM_IMAGE:
1499 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, data->account, data->who);
1500 gtkconv = PIDGIN_CONVERSATION(conv);
1502 if (!g_file_get_contents(data->filename, &filedata, &size,
1503 &err)) {
1504 char *str;
1506 str = g_strdup_printf(_("The following error has occurred loading %s: %s"), data->filename, err->message);
1507 purple_notify_error(NULL, NULL,
1508 _("Failed to load image"),
1509 str);
1511 g_error_free(err);
1512 g_free(str);
1514 break;
1516 shortname = strrchr(data->filename, G_DIR_SEPARATOR);
1517 shortname = shortname ? shortname + 1 : data->filename;
1518 id = purple_imgstore_add_with_id(filedata, size, shortname);
1520 gtk_text_buffer_get_iter_at_mark(GTK_IMHTML(gtkconv->entry)->text_buffer, &iter,
1521 gtk_text_buffer_get_insert(GTK_IMHTML(gtkconv->entry)->text_buffer));
1522 gtk_imhtml_insert_image_at_iter(GTK_IMHTML(gtkconv->entry), id, &iter);
1523 purple_imgstore_unref_by_id(id);
1525 break;
1527 g_free(data->filename);
1528 g_free(data->who);
1529 g_free(data);
1532 static void dnd_image_cancel_callback(_DndData *data, int choice)
1534 g_free(data->filename);
1535 g_free(data->who);
1536 g_free(data);
1539 static void dnd_set_icon_ok_cb(_DndData *data)
1541 dnd_image_ok_callback(data, DND_BUDDY_ICON);
1544 static void dnd_set_icon_cancel_cb(_DndData *data)
1546 g_free(data->filename);
1547 g_free(data->who);
1548 g_free(data);
1551 void
1552 pidgin_dnd_file_manage(GtkSelectionData *sd, PurpleAccount *account, const char *who)
1554 GdkPixbuf *pb;
1555 GList *files = purple_uri_list_extract_filenames((const gchar *)sd->data);
1556 PurpleConnection *gc = purple_account_get_connection(account);
1557 PurplePluginProtocolInfo *prpl_info = NULL;
1558 #ifndef _WIN32
1559 PurpleDesktopItem *item;
1560 #endif
1561 gchar *filename = NULL;
1562 gchar *basename = NULL;
1564 g_return_if_fail(account != NULL);
1565 g_return_if_fail(who != NULL);
1567 for ( ; files; files = g_list_delete_link(files, files)) {
1568 g_free(filename);
1569 g_free(basename);
1571 filename = files->data;
1572 basename = g_path_get_basename(filename);
1574 /* XXX - Make ft API support creating a transfer with more than one file */
1575 if (!g_file_test(filename, G_FILE_TEST_EXISTS)) {
1576 continue;
1579 /* XXX - make ft api suupport sending a directory */
1580 /* Are we dealing with a directory? */
1581 if (g_file_test(filename, G_FILE_TEST_IS_DIR)) {
1582 char *str, *str2;
1584 str = g_strdup_printf(_("Cannot send folder %s."), basename);
1585 str2 = g_strdup_printf(_("%s cannot transfer a folder. You will need to send the files within individually."), PIDGIN_NAME);
1587 purple_notify_error(NULL, NULL,
1588 str, str2);
1590 g_free(str);
1591 g_free(str2);
1592 continue;
1595 /* Are we dealing with an image? */
1596 pb = pidgin_pixbuf_new_from_file(filename);
1597 if (pb) {
1598 _DndData *data = g_malloc(sizeof(_DndData));
1599 gboolean ft = FALSE, im = FALSE;
1601 data->who = g_strdup(who);
1602 data->filename = g_strdup(filename);
1603 data->account = account;
1605 if (gc)
1606 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
1608 if (prpl_info && prpl_info->options & OPT_PROTO_IM_IMAGE)
1609 im = TRUE;
1611 if (prpl_info && prpl_info->can_receive_file)
1612 ft = prpl_info->can_receive_file(gc, who);
1613 else if (prpl_info && prpl_info->send_file)
1614 ft = TRUE;
1616 if (im && ft)
1617 purple_request_choice(NULL, NULL,
1618 _("You have dragged an image"),
1619 _("You can send this image as a file transfer, "
1620 "embed it into this message, or use it as the buddy icon for this user."),
1621 DND_FILE_TRANSFER, _("OK"), (GCallback)dnd_image_ok_callback,
1622 _("Cancel"), (GCallback)dnd_image_cancel_callback,
1623 account, who, NULL,
1624 data,
1625 _("Set as buddy icon"), DND_BUDDY_ICON,
1626 _("Send image file"), DND_FILE_TRANSFER,
1627 _("Insert in message"), DND_IM_IMAGE,
1628 NULL);
1629 else if (!(im || ft))
1630 purple_request_yes_no(NULL, NULL, _("You have dragged an image"),
1631 _("Would you like to set it as the buddy icon for this user?"),
1632 PURPLE_DEFAULT_ACTION_NONE,
1633 account, who, NULL,
1634 data, (GCallback)dnd_set_icon_ok_cb, (GCallback)dnd_set_icon_cancel_cb);
1635 else
1636 purple_request_choice(NULL, NULL,
1637 _("You have dragged an image"),
1638 (ft ? _("You can send this image as a file transfer, or use it as the buddy icon for this user.") :
1639 _("You can insert this image into this message, or use it as the buddy icon for this user")),
1640 (ft ? DND_FILE_TRANSFER : DND_IM_IMAGE),
1641 _("OK"), (GCallback)dnd_image_ok_callback,
1642 _("Cancel"), (GCallback)dnd_image_cancel_callback,
1643 account, who, NULL,
1644 data,
1645 _("Set as buddy icon"), DND_BUDDY_ICON,
1646 (ft ? _("Send image file") : _("Insert in message")), (ft ? DND_FILE_TRANSFER : DND_IM_IMAGE),
1647 NULL);
1648 g_object_unref(G_OBJECT(pb));
1650 g_free(basename);
1651 while (files) {
1652 g_free(files->data);
1653 files = g_list_delete_link(files, files);
1655 return;
1658 #ifndef _WIN32
1659 /* Are we trying to send a .desktop file? */
1660 else if (purple_str_has_suffix(basename, ".desktop") && (item = purple_desktop_item_new_from_file(filename))) {
1661 PurpleDesktopItemType dtype;
1662 char key[64];
1663 const char *itemname = NULL;
1665 const char * const *langs;
1666 int i;
1667 langs = g_get_language_names();
1668 for (i = 0; langs[i]; i++) {
1669 g_snprintf(key, sizeof(key), "Name[%s]", langs[i]);
1670 itemname = purple_desktop_item_get_string(item, key);
1671 break;
1674 if (!itemname)
1675 itemname = purple_desktop_item_get_string(item, "Name");
1677 dtype = purple_desktop_item_get_entry_type(item);
1678 switch (dtype) {
1679 PurpleConversation *conv;
1680 PidginConversation *gtkconv;
1682 case PURPLE_DESKTOP_ITEM_TYPE_LINK:
1683 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, who);
1684 gtkconv = PIDGIN_CONVERSATION(conv);
1685 gtk_imhtml_insert_link(GTK_IMHTML(gtkconv->entry),
1686 gtk_text_buffer_get_insert(GTK_IMHTML(gtkconv->entry)->text_buffer),
1687 purple_desktop_item_get_string(item, "URL"), itemname);
1688 break;
1689 default:
1690 /* I don't know if we really want to do anything here. Most of
1691 * the desktop item types are crap like "MIME Type" (I have no
1692 * clue how that would be a desktop item) and "Comment"...
1693 * nothing we can really send. The only logical one is
1694 * "Application," but do we really want to send a binary and
1695 * nothing else? Probably not. I'll just give an error and
1696 * return. */
1697 /* The original patch sent the icon used by the launcher. That's probably wrong */
1698 purple_notify_error(NULL, NULL, _("Cannot send launcher"),
1699 _("You dragged a desktop launcher. Most "
1700 "likely you wanted to send the target "
1701 "of this launcher instead of this "
1702 "launcher itself."));
1703 break;
1705 purple_desktop_item_unref(item);
1706 g_free(basename);
1707 while (files) {
1708 g_free(files->data);
1709 files = g_list_delete_link(files, files);
1711 return;
1713 #endif /* _WIN32 */
1715 /* Everything is fine, let's send */
1716 serv_send_file(gc, who, filename);
1719 g_free(filename);
1720 g_free(basename);
1723 void pidgin_buddy_icon_get_scale_size(GdkPixbuf *buf, PurpleBuddyIconSpec *spec, PurpleIconScaleRules rules, int *width, int *height)
1725 *width = gdk_pixbuf_get_width(buf);
1726 *height = gdk_pixbuf_get_height(buf);
1728 if ((spec == NULL) || !(spec->scale_rules & rules))
1729 return;
1731 purple_buddy_icon_get_scale_size(spec, width, height);
1733 /* and now for some arbitrary sanity checks */
1734 if(*width > 100)
1735 *width = 100;
1736 if(*height > 100)
1737 *height = 100;
1740 GdkPixbuf * pidgin_create_status_icon(PurpleStatusPrimitive prim, GtkWidget *w, const char *size)
1742 GtkIconSize icon_size = gtk_icon_size_from_name(size);
1743 GdkPixbuf *pixbuf = NULL;
1744 const char *stock = pidgin_stock_id_from_status_primitive(prim);
1746 pixbuf = gtk_widget_render_icon (w, stock ? stock : PIDGIN_STOCK_STATUS_AVAILABLE,
1747 icon_size, "GtkWidget");
1748 return pixbuf;
1751 static const char *
1752 stock_id_from_status_primitive_idle(PurpleStatusPrimitive prim, gboolean idle)
1754 const char *stock = NULL;
1755 switch (prim) {
1756 case PURPLE_STATUS_UNSET:
1757 stock = NULL;
1758 break;
1759 case PURPLE_STATUS_UNAVAILABLE:
1760 stock = idle ? PIDGIN_STOCK_STATUS_BUSY_I : PIDGIN_STOCK_STATUS_BUSY;
1761 break;
1762 case PURPLE_STATUS_AWAY:
1763 stock = idle ? PIDGIN_STOCK_STATUS_AWAY_I : PIDGIN_STOCK_STATUS_AWAY;
1764 break;
1765 case PURPLE_STATUS_EXTENDED_AWAY:
1766 stock = idle ? PIDGIN_STOCK_STATUS_XA_I : PIDGIN_STOCK_STATUS_XA;
1767 break;
1768 case PURPLE_STATUS_INVISIBLE:
1769 stock = PIDGIN_STOCK_STATUS_INVISIBLE;
1770 break;
1771 case PURPLE_STATUS_OFFLINE:
1772 stock = idle ? PIDGIN_STOCK_STATUS_OFFLINE_I : PIDGIN_STOCK_STATUS_OFFLINE;
1773 break;
1774 default:
1775 stock = idle ? PIDGIN_STOCK_STATUS_AVAILABLE_I : PIDGIN_STOCK_STATUS_AVAILABLE;
1776 break;
1778 return stock;
1781 const char *
1782 pidgin_stock_id_from_status_primitive(PurpleStatusPrimitive prim)
1784 return stock_id_from_status_primitive_idle(prim, FALSE);
1787 const char *
1788 pidgin_stock_id_from_presence(PurplePresence *presence)
1790 PurpleStatus *status;
1791 PurpleStatusType *type;
1792 PurpleStatusPrimitive prim;
1793 gboolean idle;
1795 g_return_val_if_fail(presence, NULL);
1797 status = purple_presence_get_active_status(presence);
1798 type = purple_status_get_type(status);
1799 prim = purple_status_type_get_primitive(type);
1801 idle = purple_presence_is_idle(presence);
1803 return stock_id_from_status_primitive_idle(prim, idle);
1806 GdkPixbuf *
1807 pidgin_create_prpl_icon(PurpleAccount *account, PidginPrplIconSize size)
1809 PurplePlugin *prpl;
1811 g_return_val_if_fail(account != NULL, NULL);
1813 prpl = purple_find_prpl(purple_account_get_protocol_id(account));
1814 if (prpl == NULL)
1815 return NULL;
1816 return pidgin_create_prpl_icon_from_prpl(prpl, size, account);
1819 static void
1820 menu_action_cb(GtkMenuItem *item, gpointer object)
1822 gpointer data;
1823 void (*callback)(gpointer, gpointer);
1825 callback = g_object_get_data(G_OBJECT(item), "purplecallback");
1826 data = g_object_get_data(G_OBJECT(item), "purplecallbackdata");
1828 if (callback)
1829 callback(object, data);
1832 GtkWidget *
1833 pidgin_append_menu_action(GtkWidget *menu, PurpleMenuAction *act,
1834 gpointer object)
1836 GtkWidget *menuitem;
1838 if (act == NULL) {
1839 return pidgin_separator(menu);
1842 if (act->children == NULL) {
1843 menuitem = gtk_menu_item_new_with_mnemonic(act->label);
1845 if (act->callback != NULL) {
1846 g_object_set_data(G_OBJECT(menuitem),
1847 "purplecallback",
1848 act->callback);
1849 g_object_set_data(G_OBJECT(menuitem),
1850 "purplecallbackdata",
1851 act->data);
1852 g_signal_connect(G_OBJECT(menuitem), "activate",
1853 G_CALLBACK(menu_action_cb),
1854 object);
1855 } else {
1856 gtk_widget_set_sensitive(menuitem, FALSE);
1859 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1860 } else {
1861 GList *l = NULL;
1862 GtkWidget *submenu = NULL;
1863 GtkAccelGroup *group;
1865 menuitem = gtk_menu_item_new_with_mnemonic(act->label);
1866 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1868 submenu = gtk_menu_new();
1869 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
1871 group = gtk_menu_get_accel_group(GTK_MENU(menu));
1872 if (group) {
1873 char *path = g_strdup_printf("%s/%s", GTK_MENU_ITEM(menuitem)->accel_path, act->label);
1874 gtk_menu_set_accel_path(GTK_MENU(submenu), path);
1875 g_free(path);
1876 gtk_menu_set_accel_group(GTK_MENU(submenu), group);
1879 for (l = act->children; l; l = l->next) {
1880 PurpleMenuAction *act = (PurpleMenuAction *)l->data;
1882 pidgin_append_menu_action(submenu, act, object);
1884 g_list_free(act->children);
1885 act->children = NULL;
1887 purple_menu_action_free(act);
1888 return menuitem;
1891 typedef struct
1893 GtkWidget *entry;
1894 GtkWidget *accountopt;
1896 PidginFilterBuddyCompletionEntryFunc filter_func;
1897 gpointer filter_func_user_data;
1899 GtkListStore *store;
1900 } PidginCompletionData;
1902 static gboolean buddyname_completion_match_func(GtkEntryCompletion *completion,
1903 const gchar *key, GtkTreeIter *iter, gpointer user_data)
1905 GtkTreeModel *model;
1906 GValue val1;
1907 GValue val2;
1908 const char *tmp;
1910 model = gtk_entry_completion_get_model (completion);
1912 val1.g_type = 0;
1913 gtk_tree_model_get_value(model, iter, 2, &val1);
1914 tmp = g_value_get_string(&val1);
1915 if (tmp != NULL && purple_str_has_prefix(tmp, key))
1917 g_value_unset(&val1);
1918 return TRUE;
1920 g_value_unset(&val1);
1922 val2.g_type = 0;
1923 gtk_tree_model_get_value(model, iter, 3, &val2);
1924 tmp = g_value_get_string(&val2);
1925 if (tmp != NULL && purple_str_has_prefix(tmp, key))
1927 g_value_unset(&val2);
1928 return TRUE;
1930 g_value_unset(&val2);
1932 return FALSE;
1935 static gboolean buddyname_completion_match_selected_cb(GtkEntryCompletion *completion,
1936 GtkTreeModel *model, GtkTreeIter *iter, PidginCompletionData *data)
1938 GValue val;
1939 GtkWidget *optmenu = data->accountopt;
1940 PurpleAccount *account;
1942 val.g_type = 0;
1943 gtk_tree_model_get_value(model, iter, 1, &val);
1944 gtk_entry_set_text(GTK_ENTRY(data->entry), g_value_get_string(&val));
1945 g_value_unset(&val);
1947 gtk_tree_model_get_value(model, iter, 4, &val);
1948 account = g_value_get_pointer(&val);
1949 g_value_unset(&val);
1951 if (account == NULL)
1952 return TRUE;
1954 if (optmenu != NULL)
1955 aop_option_menu_select_by_data(optmenu, account);
1957 return TRUE;
1960 static void
1961 add_buddyname_autocomplete_entry(GtkListStore *store, const char *buddy_alias, const char *contact_alias,
1962 const PurpleAccount *account, const char *buddyname)
1964 GtkTreeIter iter;
1965 gboolean completion_added = FALSE;
1966 gchar *normalized_buddyname;
1967 gchar *tmp;
1969 tmp = g_utf8_normalize(buddyname, -1, G_NORMALIZE_DEFAULT);
1970 normalized_buddyname = g_utf8_casefold(tmp, -1);
1971 g_free(tmp);
1973 /* There's no sense listing things like: 'xxx "xxx"'
1974 when the name and buddy alias match. */
1975 if (buddy_alias && strcmp(buddy_alias, buddyname)) {
1976 char *completion_entry = g_strdup_printf("%s \"%s\"", buddyname, buddy_alias);
1977 char *tmp2 = g_utf8_normalize(buddy_alias, -1, G_NORMALIZE_DEFAULT);
1979 tmp = g_utf8_casefold(tmp2, -1);
1980 g_free(tmp2);
1982 gtk_list_store_append(store, &iter);
1983 gtk_list_store_set(store, &iter,
1984 0, completion_entry,
1985 1, buddyname,
1986 2, normalized_buddyname,
1987 3, tmp,
1988 4, account,
1989 -1);
1990 g_free(completion_entry);
1991 g_free(tmp);
1992 completion_added = TRUE;
1995 /* There's no sense listing things like: 'xxx "xxx"'
1996 when the name and contact alias match. */
1997 if (contact_alias && strcmp(contact_alias, buddyname)) {
1998 /* We don't want duplicates when the contact and buddy alias match. */
1999 if (!buddy_alias || strcmp(contact_alias, buddy_alias)) {
2000 char *completion_entry = g_strdup_printf("%s \"%s\"",
2001 buddyname, contact_alias);
2002 char *tmp2 = g_utf8_normalize(contact_alias, -1, G_NORMALIZE_DEFAULT);
2004 tmp = g_utf8_casefold(tmp2, -1);
2005 g_free(tmp2);
2007 gtk_list_store_append(store, &iter);
2008 gtk_list_store_set(store, &iter,
2009 0, completion_entry,
2010 1, buddyname,
2011 2, normalized_buddyname,
2012 3, tmp,
2013 4, account,
2014 -1);
2015 g_free(completion_entry);
2016 g_free(tmp);
2017 completion_added = TRUE;
2021 if (completion_added == FALSE) {
2022 /* Add the buddy's name. */
2023 gtk_list_store_append(store, &iter);
2024 gtk_list_store_set(store, &iter,
2025 0, buddyname,
2026 1, buddyname,
2027 2, normalized_buddyname,
2028 3, NULL,
2029 4, account,
2030 -1);
2033 g_free(normalized_buddyname);
2036 static void get_log_set_name(PurpleLogSet *set, gpointer value, PidginCompletionData *data)
2038 PidginFilterBuddyCompletionEntryFunc filter_func = data->filter_func;
2039 gpointer user_data = data->filter_func_user_data;
2041 /* 1. Don't show buddies because we will have gotten them already.
2042 * 2. The boxes that use this autocomplete code handle only IMs. */
2043 if (!set->buddy && set->type == PURPLE_LOG_IM) {
2044 PidginBuddyCompletionEntry entry;
2045 entry.is_buddy = FALSE;
2046 entry.entry.logged_buddy = set;
2048 if (filter_func(&entry, user_data)) {
2049 add_buddyname_autocomplete_entry(data->store,
2050 NULL, NULL, set->account, set->name);
2055 static void
2056 add_completion_list(PidginCompletionData *data)
2058 PurpleBlistNode *gnode, *cnode, *bnode;
2059 PidginFilterBuddyCompletionEntryFunc filter_func = data->filter_func;
2060 gpointer user_data = data->filter_func_user_data;
2061 GHashTable *sets;
2063 gtk_list_store_clear(data->store);
2065 for (gnode = purple_get_blist()->root; gnode != NULL; gnode = gnode->next)
2067 if (!PURPLE_BLIST_NODE_IS_GROUP(gnode))
2068 continue;
2070 for (cnode = gnode->child; cnode != NULL; cnode = cnode->next)
2072 if (!PURPLE_BLIST_NODE_IS_CONTACT(cnode))
2073 continue;
2075 for (bnode = cnode->child; bnode != NULL; bnode = bnode->next)
2077 PidginBuddyCompletionEntry entry;
2078 entry.is_buddy = TRUE;
2079 entry.entry.buddy = (PurpleBuddy *) bnode;
2081 if (filter_func(&entry, user_data)) {
2082 add_buddyname_autocomplete_entry(data->store,
2083 ((PurpleContact *)cnode)->alias,
2084 purple_buddy_get_contact_alias(entry.entry.buddy),
2085 entry.entry.buddy->account,
2086 entry.entry.buddy->name
2093 sets = purple_log_get_log_sets();
2094 g_hash_table_foreach(sets, (GHFunc)get_log_set_name, data);
2095 g_hash_table_destroy(sets);
2099 static void
2100 buddyname_autocomplete_destroyed_cb(GtkWidget *widget, gpointer data)
2102 g_free(data);
2103 purple_signals_disconnect_by_handle(widget);
2106 static void
2107 repopulate_autocomplete(gpointer something, gpointer data)
2109 add_completion_list(data);
2112 void
2113 pidgin_setup_screenname_autocomplete_with_filter(GtkWidget *entry, GtkWidget *accountopt, PidginFilterBuddyCompletionEntryFunc filter_func, gpointer user_data)
2115 PidginCompletionData *data;
2118 * Store the displayed completion value, the buddy name, the UTF-8
2119 * normalized & casefolded buddy name, the UTF-8 normalized &
2120 * casefolded value for comparison, and the account.
2122 GtkListStore *store;
2124 GtkEntryCompletion *completion;
2126 data = g_new0(PidginCompletionData, 1);
2127 store = gtk_list_store_new(5, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER);
2129 data->entry = entry;
2130 data->accountopt = accountopt;
2131 if (filter_func == NULL) {
2132 data->filter_func = pidgin_screenname_autocomplete_default_filter;
2133 data->filter_func_user_data = NULL;
2134 } else {
2135 data->filter_func = filter_func;
2136 data->filter_func_user_data = user_data;
2138 data->store = store;
2140 add_completion_list(data);
2142 /* Sort the completion list by buddy name */
2143 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store),
2144 1, GTK_SORT_ASCENDING);
2146 completion = gtk_entry_completion_new();
2147 gtk_entry_completion_set_match_func(completion, buddyname_completion_match_func, NULL, NULL);
2149 g_signal_connect(G_OBJECT(completion), "match-selected",
2150 G_CALLBACK(buddyname_completion_match_selected_cb), data);
2152 gtk_entry_set_completion(GTK_ENTRY(entry), completion);
2153 g_object_unref(completion);
2155 gtk_entry_completion_set_model(completion, GTK_TREE_MODEL(store));
2156 g_object_unref(store);
2158 gtk_entry_completion_set_text_column(completion, 0);
2160 purple_signal_connect(purple_connections_get_handle(), "signed-on", entry,
2161 PURPLE_CALLBACK(repopulate_autocomplete), data);
2162 purple_signal_connect(purple_connections_get_handle(), "signed-off", entry,
2163 PURPLE_CALLBACK(repopulate_autocomplete), data);
2165 purple_signal_connect(purple_accounts_get_handle(), "account-added", entry,
2166 PURPLE_CALLBACK(repopulate_autocomplete), data);
2167 purple_signal_connect(purple_accounts_get_handle(), "account-removed", entry,
2168 PURPLE_CALLBACK(repopulate_autocomplete), data);
2170 g_signal_connect(G_OBJECT(entry), "destroy", G_CALLBACK(buddyname_autocomplete_destroyed_cb), data);
2173 gboolean
2174 pidgin_screenname_autocomplete_default_filter(const PidginBuddyCompletionEntry *completion_entry, gpointer all_accounts) {
2175 gboolean all = GPOINTER_TO_INT(all_accounts);
2177 if (completion_entry->is_buddy) {
2178 return all || purple_account_is_connected(completion_entry->entry.buddy->account);
2179 } else {
2180 return all || (completion_entry->entry.logged_buddy->account != NULL && purple_account_is_connected(completion_entry->entry.logged_buddy->account));
2184 void
2185 pidgin_setup_screenname_autocomplete(GtkWidget *entry, GtkWidget *accountopt, gboolean all) {
2186 pidgin_setup_screenname_autocomplete_with_filter(entry, accountopt, pidgin_screenname_autocomplete_default_filter, GINT_TO_POINTER(all));
2191 void pidgin_set_cursor(GtkWidget *widget, GdkCursorType cursor_type)
2193 GdkCursor *cursor;
2195 g_return_if_fail(widget != NULL);
2196 if (widget->window == NULL)
2197 return;
2199 cursor = gdk_cursor_new(cursor_type);
2200 gdk_window_set_cursor(widget->window, cursor);
2201 gdk_cursor_unref(cursor);
2203 gdk_display_flush(gdk_drawable_get_display(GDK_DRAWABLE(widget->window)));
2206 void pidgin_clear_cursor(GtkWidget *widget)
2208 g_return_if_fail(widget != NULL);
2209 if (widget->window == NULL)
2210 return;
2212 gdk_window_set_cursor(widget->window, NULL);
2215 struct _icon_chooser {
2216 GtkWidget *icon_filesel;
2217 GtkWidget *icon_preview;
2218 GtkWidget *icon_text;
2220 void (*callback)(const char*,gpointer);
2221 gpointer data;
2224 static void
2225 icon_filesel_choose_cb(GtkWidget *widget, gint response, struct _icon_chooser *dialog)
2227 char *filename, *current_folder;
2229 if (response != GTK_RESPONSE_ACCEPT) {
2230 if (response == GTK_RESPONSE_CANCEL) {
2231 gtk_widget_destroy(dialog->icon_filesel);
2233 dialog->icon_filesel = NULL;
2234 if (dialog->callback)
2235 dialog->callback(NULL, dialog->data);
2236 g_free(dialog);
2237 return;
2240 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog->icon_filesel));
2241 current_folder = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(dialog->icon_filesel));
2242 if (current_folder != NULL) {
2243 purple_prefs_set_path(PIDGIN_PREFS_ROOT "/filelocations/last_icon_folder", current_folder);
2244 g_free(current_folder);
2248 if (dialog->callback)
2249 dialog->callback(filename, dialog->data);
2250 gtk_widget_destroy(dialog->icon_filesel);
2251 g_free(filename);
2252 g_free(dialog);
2256 static void
2257 icon_preview_change_cb(GtkFileChooser *widget, struct _icon_chooser *dialog)
2259 GdkPixbuf *pixbuf, *scale;
2260 int height, width;
2261 char *basename, *markup, *size;
2262 struct stat st;
2263 char *filename;
2265 filename = gtk_file_chooser_get_preview_filename(
2266 GTK_FILE_CHOOSER(dialog->icon_filesel));
2268 if (!filename || g_stat(filename, &st) || !(pixbuf = pidgin_pixbuf_new_from_file(filename)))
2270 gtk_image_set_from_pixbuf(GTK_IMAGE(dialog->icon_preview), NULL);
2271 gtk_label_set_markup(GTK_LABEL(dialog->icon_text), "");
2272 g_free(filename);
2273 return;
2276 width = gdk_pixbuf_get_width(pixbuf);
2277 height = gdk_pixbuf_get_height(pixbuf);
2278 basename = g_path_get_basename(filename);
2279 size = purple_str_size_to_units(st.st_size);
2280 markup = g_strdup_printf(_("<b>File:</b> %s\n"
2281 "<b>File size:</b> %s\n"
2282 "<b>Image size:</b> %dx%d"),
2283 basename, size, width, height);
2285 scale = gdk_pixbuf_scale_simple(pixbuf, width * 50 / height,
2286 50, GDK_INTERP_BILINEAR);
2287 gtk_image_set_from_pixbuf(GTK_IMAGE(dialog->icon_preview), scale);
2288 gtk_label_set_markup(GTK_LABEL(dialog->icon_text), markup);
2290 g_object_unref(G_OBJECT(pixbuf));
2291 g_object_unref(G_OBJECT(scale));
2292 g_free(filename);
2293 g_free(basename);
2294 g_free(size);
2295 g_free(markup);
2299 GtkWidget *pidgin_buddy_icon_chooser_new(GtkWindow *parent, void(*callback)(const char *, gpointer), gpointer data) {
2300 struct _icon_chooser *dialog = g_new0(struct _icon_chooser, 1);
2302 GtkWidget *vbox;
2303 const char *current_folder;
2305 dialog->callback = callback;
2306 dialog->data = data;
2308 current_folder = purple_prefs_get_path(PIDGIN_PREFS_ROOT "/filelocations/last_icon_folder");
2310 dialog->icon_filesel = gtk_file_chooser_dialog_new(_("Buddy Icon"),
2311 parent,
2312 GTK_FILE_CHOOSER_ACTION_OPEN,
2313 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2314 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
2315 NULL);
2316 gtk_dialog_set_default_response(GTK_DIALOG(dialog->icon_filesel), GTK_RESPONSE_ACCEPT);
2317 if ((current_folder != NULL) && (*current_folder != '\0'))
2318 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog->icon_filesel),
2319 current_folder);
2321 dialog->icon_preview = gtk_image_new();
2322 dialog->icon_text = gtk_label_new(NULL);
2324 vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
2325 gtk_widget_set_size_request(GTK_WIDGET(vbox), -1, 50);
2326 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(dialog->icon_preview), TRUE, FALSE, 0);
2327 gtk_box_pack_end(GTK_BOX(vbox), GTK_WIDGET(dialog->icon_text), FALSE, FALSE, 0);
2328 gtk_widget_show_all(vbox);
2330 gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog->icon_filesel), vbox);
2331 gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(dialog->icon_filesel), TRUE);
2332 gtk_file_chooser_set_use_preview_label(GTK_FILE_CHOOSER(dialog->icon_filesel), FALSE);
2334 g_signal_connect(G_OBJECT(dialog->icon_filesel), "update-preview",
2335 G_CALLBACK(icon_preview_change_cb), dialog);
2336 g_signal_connect(G_OBJECT(dialog->icon_filesel), "response",
2337 G_CALLBACK(icon_filesel_choose_cb), dialog);
2338 icon_preview_change_cb(NULL, dialog);
2340 #ifdef _WIN32
2341 g_signal_connect(G_OBJECT(dialog->icon_filesel), "show",
2342 G_CALLBACK(winpidgin_ensure_onscreen), dialog->icon_filesel);
2343 #endif
2345 return dialog->icon_filesel;
2349 * @return True if any string from array a exists in array b.
2351 static gboolean
2352 str_array_match(char **a, char **b)
2354 int i, j;
2356 if (!a || !b)
2357 return FALSE;
2358 for (i = 0; a[i] != NULL; i++)
2359 for (j = 0; b[j] != NULL; j++)
2360 if (!g_ascii_strcasecmp(a[i], b[j]))
2361 return TRUE;
2362 return FALSE;
2365 gpointer
2366 pidgin_convert_buddy_icon(PurplePlugin *plugin, const char *path, size_t *len)
2368 PurplePluginProtocolInfo *prpl_info;
2369 PurpleBuddyIconSpec *spec;
2370 int orig_width, orig_height, new_width, new_height;
2371 GdkPixbufFormat *format;
2372 char **pixbuf_formats;
2373 char **prpl_formats;
2374 GError *error = NULL;
2375 gchar *contents;
2376 gsize length;
2377 GdkPixbuf *pixbuf, *original;
2378 float scale_factor;
2379 int i;
2380 gchar *tmp;
2382 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin);
2383 spec = &prpl_info->icon_spec;
2384 g_return_val_if_fail(spec->format != NULL, NULL);
2386 format = gdk_pixbuf_get_file_info(path, &orig_width, &orig_height);
2387 if (format == NULL) {
2388 purple_debug_warning("buddyicon", "Could not get file info of %s\n", path);
2389 return NULL;
2392 pixbuf_formats = gdk_pixbuf_format_get_extensions(format);
2393 prpl_formats = g_strsplit(spec->format, ",", 0);
2395 if (str_array_match(pixbuf_formats, prpl_formats) && /* This is an acceptable format AND */
2396 (!(spec->scale_rules & PURPLE_ICON_SCALE_SEND) || /* The prpl doesn't scale before it sends OR */
2397 (spec->min_width <= orig_width && spec->max_width >= orig_width &&
2398 spec->min_height <= orig_height && spec->max_height >= orig_height))) /* The icon is the correct size */
2400 g_strfreev(pixbuf_formats);
2402 if (!g_file_get_contents(path, &contents, &length, &error)) {
2403 purple_debug_warning("buddyicon", "Could not get file contents "
2404 "of %s: %s\n", path, error->message);
2405 g_strfreev(prpl_formats);
2406 return NULL;
2409 if (spec->max_filesize == 0 || length < spec->max_filesize) {
2410 /* The supplied image fits the file size, dimensions and type
2411 constraints. Great! Return it without making any changes. */
2412 if (len)
2413 *len = length;
2414 g_strfreev(prpl_formats);
2415 return contents;
2418 /* The image was too big. Fall-through and try scaling it down. */
2419 g_free(contents);
2420 } else {
2421 g_strfreev(pixbuf_formats);
2424 /* The original image wasn't compatible. Scale it or convert file type. */
2425 pixbuf = gdk_pixbuf_new_from_file(path, &error);
2426 if (error) {
2427 purple_debug_warning("buddyicon", "Could not open icon '%s' for "
2428 "conversion: %s\n", path, error->message);
2429 g_error_free(error);
2430 g_strfreev(prpl_formats);
2431 return NULL;
2433 original = g_object_ref(G_OBJECT(pixbuf));
2435 new_width = orig_width;
2436 new_height = orig_height;
2438 /* Make sure the image is the correct dimensions */
2439 if (spec->scale_rules & PURPLE_ICON_SCALE_SEND &&
2440 (orig_width < spec->min_width || orig_width > spec->max_width ||
2441 orig_height < spec->min_height || orig_height > spec->max_height))
2443 purple_buddy_icon_get_scale_size(spec, &new_width, &new_height);
2445 g_object_unref(G_OBJECT(pixbuf));
2446 pixbuf = gdk_pixbuf_scale_simple(original, new_width, new_height, GDK_INTERP_HYPER);
2449 scale_factor = 1;
2450 do {
2451 for (i = 0; prpl_formats[i]; i++) {
2452 int quality = 100;
2453 do {
2454 const char *key = NULL;
2455 const char *value = NULL;
2456 gchar tmp_buf[4];
2458 purple_debug_info("buddyicon", "Converting buddy icon to %s\n", prpl_formats[i]);
2460 if (g_str_equal(prpl_formats[i], "png")) {
2461 key = "compression";
2462 value = "9";
2463 } else if (g_str_equal(prpl_formats[i], "jpeg")) {
2464 sprintf(tmp_buf, "%u", quality);
2465 key = "quality";
2466 value = tmp_buf;
2469 if (!gdk_pixbuf_save_to_buffer(pixbuf, &contents, &length,
2470 prpl_formats[i], &error, key, value, NULL))
2472 /* The NULL checking of error is necessary due to this bug:
2473 * http://bugzilla.gnome.org/show_bug.cgi?id=405539 */
2474 purple_debug_warning("buddyicon",
2475 "Could not convert to %s: %s\n", prpl_formats[i],
2476 (error && error->message) ? error->message : "Unknown error");
2477 g_error_free(error);
2478 error = NULL;
2480 /* We couldn't convert to this image type. Try the next
2481 image type. */
2482 break;
2485 if (spec->max_filesize == 0 || length <= spec->max_filesize) {
2486 /* We were able to save the image as this image type and
2487 have it be within the size constraints. Great! Return
2488 the image. */
2489 purple_debug_info("buddyicon", "Converted image from "
2490 "%dx%d to %dx%d, format=%s, quality=%u, "
2491 "filesize=%zu\n", orig_width, orig_height,
2492 new_width, new_height, prpl_formats[i], quality,
2493 length);
2494 if (len)
2495 *len = length;
2496 g_strfreev(prpl_formats);
2497 g_object_unref(G_OBJECT(pixbuf));
2498 g_object_unref(G_OBJECT(original));
2499 return contents;
2502 g_free(contents);
2504 if (!g_str_equal(prpl_formats[i], "jpeg")) {
2505 /* File size was too big and we can't lower the quality,
2506 so skip to the next image type. */
2507 break;
2510 /* File size was too big, but we're dealing with jpeg so try
2511 lowering the quality. */
2512 quality -= 5;
2513 } while (quality >= 70);
2516 /* We couldn't save the image in any format that was below the max
2517 file size. Maybe we can reduce the image dimensions? */
2518 scale_factor *= 0.8;
2519 new_width = orig_width * scale_factor;
2520 new_height = orig_height * scale_factor;
2521 g_object_unref(G_OBJECT(pixbuf));
2522 pixbuf = gdk_pixbuf_scale_simple(original, new_width, new_height, GDK_INTERP_HYPER);
2523 } while ((new_width > 10 || new_height > 10) && new_width > spec->min_width && new_height > spec->min_height);
2524 g_strfreev(prpl_formats);
2525 g_object_unref(G_OBJECT(pixbuf));
2526 g_object_unref(G_OBJECT(original));
2528 tmp = g_strdup_printf(_("The file '%s' is too large for %s. Please try a smaller image.\n"),
2529 path, plugin->info->name);
2530 purple_notify_error(NULL, _("Icon Error"), _("Could not set icon"), tmp);
2531 g_free(tmp);
2533 return NULL;
2536 void pidgin_set_custom_buddy_icon(PurpleAccount *account, const char *who, const char *filename)
2538 PurpleBuddy *buddy;
2539 PurpleContact *contact;
2541 buddy = purple_find_buddy(account, who);
2542 if (!buddy) {
2543 purple_debug_info("custom-icon", "You can only set custom icon for someone in your buddylist.\n");
2544 return;
2547 contact = purple_buddy_get_contact(buddy);
2548 purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode*)contact, filename);
2551 char *pidgin_make_pretty_arrows(const char *str)
2553 char *ret;
2554 char **split = g_strsplit(str, "->", -1);
2555 ret = g_strjoinv("\342\207\250", split);
2556 g_strfreev(split);
2558 split = g_strsplit(ret, "<-", -1);
2559 g_free(ret);
2560 ret = g_strjoinv("\342\207\246", split);
2561 g_strfreev(split);
2563 return ret;
2566 void pidgin_set_urgent(GtkWindow *window, gboolean urgent)
2568 #if defined _WIN32
2569 winpidgin_window_flash(window, urgent);
2570 #else
2571 gtk_window_set_urgency_hint(window, urgent);
2572 #endif
2575 static GSList *minidialogs = NULL;
2577 static void *
2578 pidgin_utils_get_handle(void)
2580 static int handle;
2582 return &handle;
2585 static void connection_signed_off_cb(PurpleConnection *gc)
2587 GSList *list, *l_next;
2588 for (list = minidialogs; list; list = l_next) {
2589 l_next = list->next;
2590 if (g_object_get_data(G_OBJECT(list->data), "gc") == gc) {
2591 gtk_widget_destroy(GTK_WIDGET(list->data));
2596 static void alert_killed_cb(GtkWidget *widget)
2598 minidialogs = g_slist_remove(minidialogs, widget);
2601 struct _old_button_clicked_cb_data
2603 PidginUtilMiniDialogCallback cb;
2604 gpointer data;
2607 static void
2608 old_mini_dialog_button_clicked_cb(PidginMiniDialog *mini_dialog,
2609 GtkButton *button,
2610 gpointer user_data)
2612 struct _old_button_clicked_cb_data *data = user_data;
2613 data->cb(data->data, button);
2616 static void
2617 old_mini_dialog_destroy_cb(GtkWidget *dialog,
2618 GList *cb_datas)
2620 while (cb_datas != NULL)
2622 g_free(cb_datas->data);
2623 cb_datas = g_list_delete_link(cb_datas, cb_datas);
2627 static void
2628 mini_dialog_init(PidginMiniDialog *mini_dialog, PurpleConnection *gc, void *user_data, va_list args)
2630 const char *button_text;
2631 GList *cb_datas = NULL;
2632 static gboolean first_call = TRUE;
2634 if (first_call) {
2635 first_call = FALSE;
2636 purple_signal_connect(purple_connections_get_handle(), "signed-off",
2637 pidgin_utils_get_handle(),
2638 PURPLE_CALLBACK(connection_signed_off_cb), NULL);
2641 g_object_set_data(G_OBJECT(mini_dialog), "gc" ,gc);
2642 g_signal_connect(G_OBJECT(mini_dialog), "destroy",
2643 G_CALLBACK(alert_killed_cb), NULL);
2645 while ((button_text = va_arg(args, char*))) {
2646 struct _old_button_clicked_cb_data *data = NULL;
2647 PidginMiniDialogCallback wrapper_cb = NULL;
2648 PidginUtilMiniDialogCallback callback =
2649 va_arg(args, PidginUtilMiniDialogCallback);
2651 if (callback != NULL) {
2652 data = g_new0(struct _old_button_clicked_cb_data, 1);
2653 data->cb = callback;
2654 data->data = user_data;
2655 wrapper_cb = old_mini_dialog_button_clicked_cb;
2657 pidgin_mini_dialog_add_button(mini_dialog, button_text,
2658 wrapper_cb, data);
2659 cb_datas = g_list_append(cb_datas, data);
2662 g_signal_connect(G_OBJECT(mini_dialog), "destroy",
2663 G_CALLBACK(old_mini_dialog_destroy_cb), cb_datas);
2666 #define INIT_AND_RETURN_MINI_DIALOG(mini_dialog) \
2667 va_list args; \
2668 va_start(args, user_data); \
2669 mini_dialog_init(mini_dialog, gc, user_data, args); \
2670 va_end(args); \
2671 return GTK_WIDGET(mini_dialog);
2673 GtkWidget *
2674 pidgin_make_mini_dialog(PurpleConnection *gc,
2675 const char *icon_name,
2676 const char *primary,
2677 const char *secondary,
2678 void *user_data,
2679 ...)
2681 PidginMiniDialog *mini_dialog = pidgin_mini_dialog_new(primary, secondary, icon_name);
2682 INIT_AND_RETURN_MINI_DIALOG(mini_dialog);
2685 GtkWidget *
2686 pidgin_make_mini_dialog_with_custom_icon(PurpleConnection *gc,
2687 GdkPixbuf *custom_icon,
2688 const char *primary,
2689 const char *secondary,
2690 void *user_data,
2691 ...)
2693 PidginMiniDialog *mini_dialog = pidgin_mini_dialog_new_with_custom_icon(primary, secondary, custom_icon);
2694 INIT_AND_RETURN_MINI_DIALOG(mini_dialog);
2698 * "This is so dead sexy."
2699 * "Two thumbs up."
2700 * "Best movie of the year."
2702 * This is the function that handles CTRL+F searching in the buddy list.
2703 * It finds the top-most buddy/group/chat/whatever containing the
2704 * entered string.
2706 * It's somewhat ineffecient, because we strip all the HTML from the
2707 * "name" column of the buddy list (because the GtkTreeModel does not
2708 * contain the screen name in a non-markedup format). But the alternative
2709 * is to add an extra column to the GtkTreeModel. And this function is
2710 * used rarely, so it shouldn't matter TOO much.
2712 gboolean pidgin_tree_view_search_equal_func(GtkTreeModel *model, gint column,
2713 const gchar *key, GtkTreeIter *iter, gpointer data)
2715 gchar *enteredstring;
2716 gchar *tmp;
2717 gchar *withmarkup;
2718 gchar *nomarkup;
2719 gchar *normalized;
2720 gboolean result;
2721 size_t i;
2722 size_t len;
2723 PangoLogAttr *log_attrs;
2724 gchar *word;
2726 if (g_ascii_strcasecmp(key, "Global Thermonuclear War") == 0)
2728 purple_notify_info(NULL, "WOPR",
2729 "Wouldn't you prefer a nice game of chess?", NULL);
2730 return FALSE;
2733 gtk_tree_model_get(model, iter, column, &withmarkup, -1);
2734 if (withmarkup == NULL) /* This is probably a separator */
2735 return TRUE;
2737 tmp = g_utf8_normalize(key, -1, G_NORMALIZE_DEFAULT);
2738 enteredstring = g_utf8_casefold(tmp, -1);
2739 g_free(tmp);
2741 nomarkup = purple_markup_strip_html(withmarkup);
2742 tmp = g_utf8_normalize(nomarkup, -1, G_NORMALIZE_DEFAULT);
2743 g_free(nomarkup);
2744 normalized = g_utf8_casefold(tmp, -1);
2745 g_free(tmp);
2747 if (purple_str_has_prefix(normalized, enteredstring))
2749 g_free(withmarkup);
2750 g_free(enteredstring);
2751 g_free(normalized);
2752 return FALSE;
2756 /* Use Pango to separate by words. */
2757 len = g_utf8_strlen(normalized, -1);
2758 log_attrs = g_new(PangoLogAttr, len + 1);
2760 pango_get_log_attrs(normalized, strlen(normalized), -1, NULL, log_attrs, len + 1);
2762 word = normalized;
2763 result = TRUE;
2764 for (i = 0; i < (len - 1) ; i++)
2766 if (log_attrs[i].is_word_start &&
2767 purple_str_has_prefix(word, enteredstring))
2769 result = FALSE;
2770 break;
2772 word = g_utf8_next_char(word);
2774 g_free(log_attrs);
2776 /* The non-Pango version. */
2777 #if 0
2778 word = normalized;
2779 result = TRUE;
2780 while (word[0] != '\0')
2782 gunichar c = g_utf8_get_char(word);
2783 if (!g_unichar_isalnum(c))
2785 word = g_utf8_find_next_char(word, NULL);
2786 if (purple_str_has_prefix(word, enteredstring))
2788 result = FALSE;
2789 break;
2792 else
2793 word = g_utf8_find_next_char(word, NULL);
2795 #endif
2797 g_free(withmarkup);
2798 g_free(enteredstring);
2799 g_free(normalized);
2801 return result;
2805 gboolean pidgin_gdk_pixbuf_is_opaque(GdkPixbuf *pixbuf) {
2806 int height, rowstride, i;
2807 unsigned char *pixels;
2808 unsigned char *row;
2810 if (!gdk_pixbuf_get_has_alpha(pixbuf))
2811 return TRUE;
2813 height = gdk_pixbuf_get_height (pixbuf);
2814 rowstride = gdk_pixbuf_get_rowstride (pixbuf);
2815 pixels = gdk_pixbuf_get_pixels (pixbuf);
2817 row = pixels;
2818 for (i = 3; i < rowstride; i+=4) {
2819 if (row[i] < 0xfe)
2820 return FALSE;
2823 for (i = 1; i < height - 1; i++) {
2824 row = pixels + (i * rowstride);
2825 if (row[3] < 0xfe || row[rowstride - 1] < 0xfe) {
2826 return FALSE;
2830 row = pixels + ((height - 1) * rowstride);
2831 for (i = 3; i < rowstride; i += 4) {
2832 if (row[i] < 0xfe)
2833 return FALSE;
2836 return TRUE;
2839 void pidgin_gdk_pixbuf_make_round(GdkPixbuf *pixbuf) {
2840 int width, height, rowstride;
2841 guchar *pixels;
2842 if (!gdk_pixbuf_get_has_alpha(pixbuf))
2843 return;
2844 width = gdk_pixbuf_get_width(pixbuf);
2845 height = gdk_pixbuf_get_height(pixbuf);
2846 rowstride = gdk_pixbuf_get_rowstride(pixbuf);
2847 pixels = gdk_pixbuf_get_pixels(pixbuf);
2849 if (width < 6 || height < 6)
2850 return;
2851 /* Top left */
2852 pixels[3] = 0;
2853 pixels[7] = 0x80;
2854 pixels[11] = 0xC0;
2855 pixels[rowstride + 3] = 0x80;
2856 pixels[rowstride * 2 + 3] = 0xC0;
2858 /* Top right */
2859 pixels[width * 4 - 1] = 0;
2860 pixels[width * 4 - 5] = 0x80;
2861 pixels[width * 4 - 9] = 0xC0;
2862 pixels[rowstride + (width * 4) - 1] = 0x80;
2863 pixels[(2 * rowstride) + (width * 4) - 1] = 0xC0;
2865 /* Bottom left */
2866 pixels[(height - 1) * rowstride + 3] = 0;
2867 pixels[(height - 1) * rowstride + 7] = 0x80;
2868 pixels[(height - 1) * rowstride + 11] = 0xC0;
2869 pixels[(height - 2) * rowstride + 3] = 0x80;
2870 pixels[(height - 3) * rowstride + 3] = 0xC0;
2872 /* Bottom right */
2873 pixels[height * rowstride - 1] = 0;
2874 pixels[(height - 1) * rowstride - 1] = 0x80;
2875 pixels[(height - 2) * rowstride - 1] = 0xC0;
2876 pixels[height * rowstride - 5] = 0x80;
2877 pixels[height * rowstride - 9] = 0xC0;
2880 const char *pidgin_get_dim_grey_string(GtkWidget *widget) {
2881 static char dim_grey_string[8] = "";
2882 GtkStyle *style;
2884 if (!widget)
2885 return "dim grey";
2887 style = gtk_widget_get_style(widget);
2888 if (!style)
2889 return "dim grey";
2891 snprintf(dim_grey_string, sizeof(dim_grey_string), "#%02x%02x%02x",
2892 style->text_aa[GTK_STATE_NORMAL].red >> 8,
2893 style->text_aa[GTK_STATE_NORMAL].green >> 8,
2894 style->text_aa[GTK_STATE_NORMAL].blue >> 8);
2895 return dim_grey_string;
2898 static void
2899 combo_box_changed_cb(GtkComboBox *combo_box, GtkEntry *entry)
2901 char *text = gtk_combo_box_get_active_text(combo_box);
2902 gtk_entry_set_text(entry, text ? text : "");
2903 g_free(text);
2906 static gboolean
2907 entry_key_pressed_cb(GtkWidget *entry, GdkEventKey *key, GtkComboBox *combo)
2909 if (key->keyval == GDK_Down || key->keyval == GDK_Up) {
2910 gtk_combo_box_popup(combo);
2911 return TRUE;
2913 return FALSE;
2916 GtkWidget *
2917 pidgin_text_combo_box_entry_new(const char *default_item, GList *items)
2919 GtkComboBox *ret = NULL;
2920 GtkWidget *the_entry = NULL;
2922 ret = GTK_COMBO_BOX(gtk_combo_box_entry_new_text());
2923 the_entry = gtk_entry_new();
2924 gtk_container_add(GTK_CONTAINER(ret), the_entry);
2926 if (default_item)
2927 gtk_entry_set_text(GTK_ENTRY(the_entry), default_item);
2929 for (; items != NULL ; items = items->next) {
2930 char *text = items->data;
2931 if (text && *text)
2932 gtk_combo_box_append_text(ret, text);
2935 g_signal_connect(G_OBJECT(ret), "changed", (GCallback)combo_box_changed_cb, the_entry);
2936 g_signal_connect_after(G_OBJECT(the_entry), "key-press-event", G_CALLBACK(entry_key_pressed_cb), ret);
2938 return GTK_WIDGET(ret);
2941 const char *pidgin_text_combo_box_entry_get_text(GtkWidget *widget)
2943 return gtk_entry_get_text(GTK_ENTRY(GTK_BIN((widget))->child));
2946 void pidgin_text_combo_box_entry_set_text(GtkWidget *widget, const char *text)
2948 gtk_entry_set_text(GTK_ENTRY(GTK_BIN((widget))->child), (text));
2951 GtkWidget *
2952 pidgin_add_widget_to_vbox(GtkBox *vbox, const char *widget_label, GtkSizeGroup *sg, GtkWidget *widget, gboolean expand, GtkWidget **p_label)
2954 GtkWidget *hbox;
2955 GtkWidget *label = NULL;
2957 if (widget_label) {
2958 hbox = gtk_hbox_new(FALSE, 5);
2959 gtk_widget_show(hbox);
2960 gtk_box_pack_start(vbox, hbox, FALSE, FALSE, 0);
2962 label = gtk_label_new_with_mnemonic(widget_label);
2963 gtk_widget_show(label);
2964 if (sg) {
2965 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
2966 gtk_size_group_add_widget(sg, label);
2968 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
2969 } else {
2970 hbox = GTK_WIDGET(vbox);
2973 gtk_widget_show(widget);
2974 gtk_box_pack_start(GTK_BOX(hbox), widget, expand, TRUE, 0);
2975 if (label) {
2976 gtk_label_set_mnemonic_widget(GTK_LABEL(label), widget);
2977 pidgin_set_accessible_label (widget, label);
2980 if (p_label)
2981 (*p_label) = label;
2982 return hbox;
2985 gboolean pidgin_auto_parent_window(GtkWidget *widget)
2987 #if 0
2988 /* This looks at the most recent window that received focus, and makes
2989 * that the parent window. */
2990 #ifndef _WIN32
2991 static GdkAtom _WindowTime = GDK_NONE;
2992 static GdkAtom _Cardinal = GDK_NONE;
2993 GList *windows = NULL;
2994 GtkWidget *parent = NULL;
2995 time_t window_time = 0;
2997 windows = gtk_window_list_toplevels();
2999 if (_WindowTime == GDK_NONE) {
3000 _WindowTime = gdk_x11_xatom_to_atom(gdk_x11_get_xatom_by_name("_NET_WM_USER_TIME"));
3002 if (_Cardinal == GDK_NONE) {
3003 _Cardinal = gdk_atom_intern("CARDINAL", FALSE);
3006 while (windows) {
3007 GtkWidget *window = windows->data;
3008 guchar *data = NULL;
3009 int al = 0;
3010 time_t value;
3012 windows = g_list_delete_link(windows, windows);
3014 if (window == widget ||
3015 !GTK_WIDGET_VISIBLE(window))
3016 continue;
3018 if (!gdk_property_get(window->window, _WindowTime, _Cardinal, 0, sizeof(time_t), FALSE,
3019 NULL, NULL, &al, &data))
3020 continue;
3021 value = *(time_t *)data;
3022 if (window_time < value) {
3023 window_time = value;
3024 parent = window;
3026 g_free(data);
3028 if (windows)
3029 g_list_free(windows);
3030 if (parent) {
3031 if (!gtk_get_current_event() && gtk_window_has_toplevel_focus(GTK_WINDOW(parent))) {
3032 /* The window is in focus, and the new window was not triggered by a keypress/click
3033 * event. So do not set it transient, to avoid focus stealing and all that.
3035 return FALSE;
3037 gtk_window_set_transient_for(GTK_WINDOW(widget), GTK_WINDOW(parent));
3038 return TRUE;
3040 return FALSE;
3041 #endif
3042 #else
3043 /* This finds the currently active window and makes that the parent window. */
3044 GList *windows = NULL;
3045 GtkWidget *parent = NULL;
3046 GdkEvent *event = gtk_get_current_event();
3047 GdkWindow *menu = NULL;
3049 if (event == NULL)
3050 /* The window was not triggered by a user action. */
3051 return FALSE;
3053 /* We need to special case events from a popup menu. */
3054 if (event->type == GDK_BUTTON_RELEASE) {
3055 /* XXX: Neither of the following works:
3056 menu = event->button.window;
3057 menu = gdk_window_get_parent(event->button.window);
3058 menu = gdk_window_get_toplevel(event->button.window);
3060 } else if (event->type == GDK_KEY_PRESS)
3061 menu = event->key.window;
3063 windows = gtk_window_list_toplevels();
3064 while (windows) {
3065 GtkWidget *window = windows->data;
3066 windows = g_list_delete_link(windows, windows);
3068 if (window == widget ||
3069 !GTK_WIDGET_VISIBLE(window)) {
3070 continue;
3073 if (gtk_window_has_toplevel_focus(GTK_WINDOW(window)) ||
3074 (menu && menu == window->window)) {
3075 parent = window;
3076 break;
3079 if (windows)
3080 g_list_free(windows);
3081 if (parent) {
3082 gtk_window_set_transient_for(GTK_WINDOW(widget), GTK_WINDOW(parent));
3083 return TRUE;
3085 return FALSE;
3086 #endif
3089 static GObject *pidgin_pixbuf_from_data_helper(const guchar *buf, gsize count, gboolean animated)
3091 GObject *pixbuf;
3092 GdkPixbufLoader *loader;
3093 GError *error = NULL;
3095 loader = gdk_pixbuf_loader_new();
3097 if (!gdk_pixbuf_loader_write(loader, buf, count, &error) || error) {
3098 purple_debug_warning("gtkutils", "gdk_pixbuf_loader_write() "
3099 "failed with size=%zu: %s\n", count,
3100 error ? error->message : "(no error message)");
3101 if (error)
3102 g_error_free(error);
3103 g_object_unref(G_OBJECT(loader));
3104 return NULL;
3107 if (!gdk_pixbuf_loader_close(loader, &error) || error) {
3108 purple_debug_warning("gtkutils", "gdk_pixbuf_loader_close() "
3109 "failed for image of size %zu: %s\n", count,
3110 error ? error->message : "(no error message)");
3111 if (error)
3112 g_error_free(error);
3113 g_object_unref(G_OBJECT(loader));
3114 return NULL;
3117 if (animated)
3118 pixbuf = G_OBJECT(gdk_pixbuf_loader_get_animation(loader));
3119 else
3120 pixbuf = G_OBJECT(gdk_pixbuf_loader_get_pixbuf(loader));
3121 if (!pixbuf) {
3122 purple_debug_warning("gtkutils", "%s() returned NULL for image "
3123 "of size %zu\n",
3124 animated ? "gdk_pixbuf_loader_get_animation"
3125 : "gdk_pixbuf_loader_get_pixbuf", count);
3126 g_object_unref(G_OBJECT(loader));
3127 return NULL;
3130 g_object_ref(pixbuf);
3131 g_object_unref(G_OBJECT(loader));
3133 return pixbuf;
3136 GdkPixbuf *pidgin_pixbuf_from_data(const guchar *buf, gsize count)
3138 return GDK_PIXBUF(pidgin_pixbuf_from_data_helper(buf, count, FALSE));
3141 GdkPixbufAnimation *pidgin_pixbuf_anim_from_data(const guchar *buf, gsize count)
3143 return GDK_PIXBUF_ANIMATION(pidgin_pixbuf_from_data_helper(buf, count, TRUE));
3146 GdkPixbuf *pidgin_pixbuf_from_imgstore(PurpleStoredImage *image)
3148 return pidgin_pixbuf_from_data(purple_imgstore_get_data(image),
3149 purple_imgstore_get_size(image));
3152 GdkPixbuf *pidgin_pixbuf_new_from_file(const gchar *filename)
3154 GdkPixbuf *pixbuf;
3155 GError *error = NULL;
3157 pixbuf = gdk_pixbuf_new_from_file(filename, &error);
3158 if (!pixbuf || error) {
3159 purple_debug_warning("gtkutils", "gdk_pixbuf_new_from_file() "
3160 "returned %s for file %s: %s\n",
3161 pixbuf ? "something" : "nothing",
3162 filename,
3163 error ? error->message : "(no error message)");
3164 if (error)
3165 g_error_free(error);
3166 if (pixbuf)
3167 g_object_unref(G_OBJECT(pixbuf));
3168 return NULL;
3171 return pixbuf;
3174 GdkPixbuf *pidgin_pixbuf_new_from_file_at_size(const char *filename, int width, int height)
3176 GdkPixbuf *pixbuf;
3177 GError *error = NULL;
3179 pixbuf = gdk_pixbuf_new_from_file_at_size(filename,
3180 width, height, &error);
3181 if (!pixbuf || error) {
3182 purple_debug_warning("gtkutils", "gdk_pixbuf_new_from_file_at_size() "
3183 "returned %s for file %s: %s\n",
3184 pixbuf ? "something" : "nothing",
3185 filename,
3186 error ? error->message : "(no error message)");
3187 if (error)
3188 g_error_free(error);
3189 if (pixbuf)
3190 g_object_unref(G_OBJECT(pixbuf));
3191 return NULL;
3194 return pixbuf;
3197 GdkPixbuf *pidgin_pixbuf_new_from_file_at_scale(const char *filename, int width, int height, gboolean preserve_aspect_ratio)
3199 GdkPixbuf *pixbuf;
3200 GError *error = NULL;
3202 pixbuf = gdk_pixbuf_new_from_file_at_scale(filename,
3203 width, height, preserve_aspect_ratio, &error);
3204 if (!pixbuf || error) {
3205 purple_debug_warning("gtkutils", "gdk_pixbuf_new_from_file_at_scale() "
3206 "returned %s for file %s: %s\n",
3207 pixbuf ? "something" : "nothing",
3208 filename,
3209 error ? error->message : "(no error message)");
3210 if (error)
3211 g_error_free(error);
3212 if (pixbuf)
3213 g_object_unref(G_OBJECT(pixbuf));
3214 return NULL;
3217 return pixbuf;
3220 static void url_copy(GtkWidget *w, gchar *url)
3222 GtkClipboard *clipboard;
3224 clipboard = gtk_widget_get_clipboard(w, GDK_SELECTION_PRIMARY);
3225 gtk_clipboard_set_text(clipboard, url, -1);
3227 clipboard = gtk_widget_get_clipboard(w, GDK_SELECTION_CLIPBOARD);
3228 gtk_clipboard_set_text(clipboard, url, -1);
3231 static gboolean
3232 link_context_menu(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu)
3234 GtkWidget *img, *item;
3235 const char *url;
3237 url = gtk_imhtml_link_get_url(link);
3239 /* Open Link */
3240 img = gtk_image_new_from_stock(GTK_STOCK_JUMP_TO, GTK_ICON_SIZE_MENU);
3241 item = gtk_image_menu_item_new_with_mnemonic(_("_Open Link"));
3242 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
3243 g_signal_connect_swapped(G_OBJECT(item), "activate", G_CALLBACK(gtk_imhtml_link_activate), link);
3244 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
3246 /* Copy Link Location */
3247 img = gtk_image_new_from_stock(GTK_STOCK_COPY, GTK_ICON_SIZE_MENU);
3248 item = gtk_image_menu_item_new_with_mnemonic(_("_Copy Link Location"));
3249 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
3250 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(url_copy), (gpointer)url);
3251 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
3253 return TRUE;
3256 static gboolean
3257 copy_email_address(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu)
3259 GtkWidget *img, *item;
3260 const char *text;
3261 char *address;
3262 #define MAILTOSIZE (sizeof("mailto:") - 1)
3264 text = gtk_imhtml_link_get_url(link);
3265 g_return_val_if_fail(text && strlen(text) > MAILTOSIZE, FALSE);
3266 address = (char*)text + MAILTOSIZE;
3268 /* Copy Email Address */
3269 img = gtk_image_new_from_stock(GTK_STOCK_COPY, GTK_ICON_SIZE_MENU);
3270 item = gtk_image_menu_item_new_with_mnemonic(_("_Copy Email Address"));
3271 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
3272 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(url_copy), address);
3273 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
3275 return TRUE;
3278 static void
3279 file_open_uri(GtkIMHtml *imhtml, const char *uri)
3281 /* Copied from gtkft.c:open_button_cb */
3282 #ifdef _WIN32
3283 /* If using Win32... */
3284 int code;
3285 wchar_t *wc_filename = g_utf8_to_utf16(
3286 uri, -1, NULL, NULL, NULL);
3288 code = (int)ShellExecuteW(NULL, NULL, wc_filename, NULL, NULL,
3289 SW_SHOW);
3291 g_free(wc_filename);
3293 if (code == SE_ERR_ASSOCINCOMPLETE || code == SE_ERR_NOASSOC)
3295 purple_notify_error(imhtml, NULL,
3296 _("There is no application configured to open this type of file."), NULL);
3298 else if (code < 32)
3300 purple_notify_error(imhtml, NULL,
3301 _("An error occurred while opening the file."), NULL);
3302 purple_debug_warning("gtkutils", "filename: %s; code: %d\n", uri, code);
3304 #else
3305 char *command = NULL;
3306 char *tmp = NULL;
3307 GError *error = NULL;
3309 if (purple_running_gnome())
3311 char *escaped = g_shell_quote(uri);
3312 command = g_strdup_printf("gnome-open %s", escaped);
3313 g_free(escaped);
3315 else if (purple_running_kde())
3317 char *escaped = g_shell_quote(uri);
3319 if (purple_str_has_suffix(uri, ".desktop"))
3320 command = g_strdup_printf("kfmclient openURL %s 'text/plain'", escaped);
3321 else
3322 command = g_strdup_printf("kfmclient openURL %s", escaped);
3323 g_free(escaped);
3325 else
3327 purple_notify_uri(NULL, uri);
3328 return;
3331 if (purple_program_is_valid(command))
3333 gint exit_status;
3334 if (!g_spawn_command_line_sync(command, NULL, NULL, &exit_status, &error))
3336 tmp = g_strdup_printf(_("Error launching %s: %s"),
3337 uri, error->message);
3338 purple_notify_error(imhtml, NULL, _("Unable to open file."), tmp);
3339 g_free(tmp);
3340 g_error_free(error);
3342 if (exit_status != 0)
3344 char *primary = g_strdup_printf(_("Error running %s"), command);
3345 char *secondary = g_strdup_printf(_("Process returned error code %d"),
3346 exit_status);
3347 purple_notify_error(imhtml, NULL, primary, secondary);
3348 g_free(tmp);
3351 #endif
3354 #define FILELINKSIZE (sizeof("file://") - 1)
3355 static gboolean
3356 file_clicked_cb(GtkIMHtml *imhtml, GtkIMHtmlLink *link)
3358 const char *uri = gtk_imhtml_link_get_url(link) + FILELINKSIZE;
3359 file_open_uri(imhtml, uri);
3360 return TRUE;
3363 static gboolean
3364 open_containing_cb(GtkIMHtml *imhtml, const char *url)
3366 char *dir = g_path_get_dirname(url + FILELINKSIZE);
3367 file_open_uri(imhtml, dir);
3368 g_free(dir);
3369 return TRUE;
3372 static gboolean
3373 file_context_menu(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu)
3375 GtkWidget *img, *item;
3376 const char *url;
3378 url = gtk_imhtml_link_get_url(link);
3380 /* Open File */
3381 img = gtk_image_new_from_stock(GTK_STOCK_JUMP_TO, GTK_ICON_SIZE_MENU);
3382 item = gtk_image_menu_item_new_with_mnemonic(_("_Open File"));
3383 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
3384 g_signal_connect_swapped(G_OBJECT(item), "activate", G_CALLBACK(gtk_imhtml_link_activate), link);
3385 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
3387 /* Open Containing Directory */
3388 img = gtk_image_new_from_stock(GTK_STOCK_DIRECTORY, GTK_ICON_SIZE_MENU);
3389 item = gtk_image_menu_item_new_with_mnemonic(_("Open _Containing Directory"));
3390 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
3392 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(open_containing_cb), (gpointer)url);
3393 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
3395 return TRUE;
3398 #define AUDIOLINKSIZE (sizeof("audio://") - 1)
3399 static gboolean
3400 audio_clicked_cb(GtkIMHtml *imhtml, GtkIMHtmlLink *link)
3402 const char *uri;
3403 PidginConversation *conv = g_object_get_data(G_OBJECT(imhtml), "gtkconv");
3404 if (!conv) /* no playback in debug window */
3405 return TRUE;
3406 uri = gtk_imhtml_link_get_url(link) + AUDIOLINKSIZE;
3407 purple_sound_play_file(uri, NULL);
3408 return TRUE;
3411 static void
3412 savefile_write_cb(gpointer user_data, char *file)
3414 char *temp_file = user_data;
3415 gchar *contents;
3416 gsize length;
3417 GError *error = NULL;
3419 if (!g_file_get_contents(temp_file, &contents, &length, &error)) {
3420 purple_debug_error("gtkutils", "Unable to read contents of %s: %s\n",
3421 temp_file, error->message);
3422 g_error_free(error);
3423 return;
3426 if (!purple_util_write_data_to_file_absolute(file, contents, length)) {
3427 purple_debug_error("gtkutils", "Unable to write contents to %s\n",
3428 file);
3432 static gboolean
3433 save_file_cb(GtkWidget *item, const char *url)
3435 PidginConversation *conv = g_object_get_data(G_OBJECT(item), "gtkconv");
3436 if (!conv)
3437 return TRUE;
3438 purple_request_file(conv->active_conv, _("Save File"), NULL, TRUE,
3439 G_CALLBACK(savefile_write_cb), NULL,
3440 conv->active_conv->account, NULL, conv->active_conv,
3441 (void *)url);
3442 return TRUE;
3445 static gboolean
3446 audio_context_menu(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu)
3448 GtkWidget *img, *item;
3449 const char *url;
3450 PidginConversation *conv = g_object_get_data(G_OBJECT(imhtml), "gtkconv");
3451 if (!conv) /* No menu in debug window */
3452 return TRUE;
3454 url = gtk_imhtml_link_get_url(link);
3456 /* Play Sound */
3457 img = gtk_image_new_from_stock(GTK_STOCK_MEDIA_PLAY, GTK_ICON_SIZE_MENU);
3458 item = gtk_image_menu_item_new_with_mnemonic(_("_Play Sound"));
3459 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
3461 g_signal_connect_swapped(G_OBJECT(item), "activate", G_CALLBACK(gtk_imhtml_link_activate), link);
3462 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
3464 /* Save File */
3465 img = gtk_image_new_from_stock(GTK_STOCK_SAVE, GTK_ICON_SIZE_MENU);
3466 item = gtk_image_menu_item_new_with_mnemonic(_("_Save File"));
3467 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
3468 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(save_file_cb), (gpointer)(url+AUDIOLINKSIZE));
3469 g_object_set_data(G_OBJECT(item), "gtkconv", conv);
3470 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
3472 return TRUE;
3475 /* XXX: The following two functions are for demonstration purposes only! */
3476 static gboolean
3477 open_dialog(GtkIMHtml *imhtml, GtkIMHtmlLink *link)
3479 const char *url;
3480 const char *str;
3482 url = gtk_imhtml_link_get_url(link);
3483 if (!url || strlen(url) < sizeof("open://"))
3484 return FALSE;
3486 str = url + sizeof("open://") - 1;
3488 if (strcmp(str, "accounts") == 0)
3489 pidgin_accounts_window_show();
3490 else if (strcmp(str, "prefs") == 0)
3491 pidgin_prefs_show();
3492 else
3493 return FALSE;
3494 return TRUE;
3497 static gboolean
3498 dummy(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu)
3500 return TRUE;
3503 static gboolean
3504 register_gnome_url_handlers(void)
3506 char *tmp;
3507 char *err;
3508 char *c;
3509 char *start;
3511 tmp = g_find_program_in_path("gconftool-2");
3512 if (tmp == NULL)
3513 return FALSE;
3515 g_free(tmp);
3516 tmp = NULL;
3518 if (!g_spawn_command_line_sync("gconftool-2 --all-dirs /desktop/gnome/url-handlers",
3519 &tmp, &err, NULL, NULL))
3521 g_free(tmp);
3522 g_free(err);
3523 g_return_val_if_reached(FALSE);
3525 g_free(err);
3526 err = NULL;
3528 for (c = start = tmp ; *c ; c++)
3530 /* Skip leading spaces. */
3531 if (c == start && *c == ' ')
3532 start = c + 1;
3533 else if (*c == '\n')
3535 *c = '\0';
3536 if (g_str_has_prefix(start, "/desktop/gnome/url-handlers/"))
3538 char *cmd;
3539 char *tmp2 = NULL;
3540 char *protocol;
3542 /* If there is an enabled boolean, honor it. */
3543 cmd = g_strdup_printf("gconftool-2 -g %s/enabled", start);
3544 if (g_spawn_command_line_sync(cmd, &tmp2, &err, NULL, NULL))
3546 g_free(err);
3547 err = NULL;
3548 if (!strcmp(tmp2, "false\n"))
3550 g_free(tmp2);
3551 g_free(cmd);
3552 start = c + 1;
3553 continue;
3556 g_free(cmd);
3557 g_free(tmp2);
3559 start += sizeof("/desktop/gnome/url-handlers/") - 1;
3561 protocol = g_strdup_printf("%s:", start);
3562 registered_url_handlers = g_slist_prepend(registered_url_handlers, protocol);
3563 gtk_imhtml_class_register_protocol(protocol, url_clicked_cb, link_context_menu);
3565 start = c + 1;
3568 g_free(tmp);
3570 return (registered_url_handlers != NULL);
3573 #ifdef _WIN32
3574 static void
3575 winpidgin_register_win32_url_handlers(void)
3577 int idx = 0;
3578 LONG ret = ERROR_SUCCESS;
3580 do {
3581 DWORD nameSize = 256;
3582 wchar_t start[256];
3583 ret = RegEnumKeyExW(HKEY_CLASSES_ROOT, idx++, start, &nameSize,
3584 NULL, NULL, NULL, NULL);
3585 if (ret == ERROR_SUCCESS) {
3586 HKEY reg_key = NULL;
3587 ret = RegOpenKeyExW(HKEY_CLASSES_ROOT, start, 0, KEY_READ, &reg_key);
3588 if (ret == ERROR_SUCCESS) {
3589 ret = RegQueryValueExW(reg_key, L"URL Protocol", NULL, NULL, NULL, NULL);
3590 if (ret == ERROR_SUCCESS) {
3591 gchar *utf8 = g_utf16_to_utf8(start, -1, NULL, NULL, NULL);
3592 gchar *protocol = g_strdup_printf("%s:", utf8);
3593 g_free(utf8);
3594 registered_url_handlers = g_slist_prepend(registered_url_handlers, protocol);
3595 /* We still pass everything to the "http" "open" handler for security reasons */
3596 gtk_imhtml_class_register_protocol(protocol, url_clicked_cb, link_context_menu);
3598 RegCloseKey(reg_key);
3600 ret = ERROR_SUCCESS;
3602 } while (ret == ERROR_SUCCESS);
3604 if (ret != ERROR_NO_MORE_ITEMS)
3605 purple_debug_error("winpidgin", "Error iterating HKEY_CLASSES_ROOT subkeys: %ld\n",
3606 ret);
3608 #endif
3610 GtkWidget *
3611 pidgin_make_scrollable(GtkWidget *child, GtkPolicyType hscrollbar_policy, GtkPolicyType vscrollbar_policy, GtkShadowType shadow_type, int width, int height)
3613 GtkWidget *sw = gtk_scrolled_window_new(NULL, NULL);
3615 if (G_LIKELY(sw)) {
3616 gtk_widget_show(sw);
3617 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), hscrollbar_policy, vscrollbar_policy);
3618 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw), shadow_type);
3619 if (width != -1 || height != -1)
3620 gtk_widget_set_size_request(sw, width, height);
3621 if (child) {
3622 if (GTK_WIDGET_GET_CLASS(child)->set_scroll_adjustments_signal)
3623 gtk_container_add(GTK_CONTAINER(sw), child);
3624 else
3625 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(sw), child);
3627 return sw;
3630 return child;
3633 void pidgin_utils_init(void)
3635 gtk_imhtml_class_register_protocol("http://", url_clicked_cb, link_context_menu);
3636 gtk_imhtml_class_register_protocol("https://", url_clicked_cb, link_context_menu);
3637 gtk_imhtml_class_register_protocol("ftp://", url_clicked_cb, link_context_menu);
3638 gtk_imhtml_class_register_protocol("gopher://", url_clicked_cb, link_context_menu);
3639 gtk_imhtml_class_register_protocol("mailto:", url_clicked_cb, copy_email_address);
3641 gtk_imhtml_class_register_protocol("file://", file_clicked_cb, file_context_menu);
3642 gtk_imhtml_class_register_protocol("audio://", audio_clicked_cb, audio_context_menu);
3644 /* Example custom URL handler. */
3645 gtk_imhtml_class_register_protocol("open://", open_dialog, dummy);
3647 /* If we're under GNOME, try registering the system URL handlers. */
3648 if (purple_running_gnome())
3649 register_gnome_url_handlers();
3651 /* Used to make small buttons */
3652 gtk_rc_parse_string("style \"pidgin-small-close-button\"\n"
3653 "{\n"
3654 "GtkWidget::focus-padding = 0\n"
3655 "GtkWidget::focus-line-width = 0\n"
3656 "xthickness = 0\n"
3657 "ythickness = 0\n"
3658 "GtkContainer::border-width = 0\n"
3659 "GtkButton::inner-border = {0, 0, 0, 0}\n"
3660 "GtkButton::default-border = {0, 0, 0, 0}\n"
3661 "}\n"
3662 "widget \"*.pidgin-small-close-button\" style \"pidgin-small-close-button\"");
3664 #ifdef _WIN32
3665 winpidgin_register_win32_url_handlers();
3666 #endif
3670 void pidgin_utils_uninit(void)
3672 gtk_imhtml_class_register_protocol("open://", NULL, NULL);
3674 /* If we have GNOME handlers registered, unregister them. */
3675 if (registered_url_handlers)
3677 GSList *l;
3678 for (l = registered_url_handlers; l; l = l->next)
3680 gtk_imhtml_class_register_protocol((char *)l->data, NULL, NULL);
3681 g_free(l->data);
3683 g_slist_free(registered_url_handlers);
3684 registered_url_handlers = NULL;
3685 return;
3688 gtk_imhtml_class_register_protocol("audio://", NULL, NULL);
3689 gtk_imhtml_class_register_protocol("file://", NULL, NULL);
3691 gtk_imhtml_class_register_protocol("http://", NULL, NULL);
3692 gtk_imhtml_class_register_protocol("https://", NULL, NULL);
3693 gtk_imhtml_class_register_protocol("ftp://", NULL, NULL);
3694 gtk_imhtml_class_register_protocol("mailto:", NULL, NULL);
3695 gtk_imhtml_class_register_protocol("gopher://", NULL, NULL);