Use conventional style for empty string check
[pidgin-git.git] / pidgin / gtkutils.c
blob8ae28e15b8958be8ee488ff3ed4d076b049e6c3d
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 #ifdef _WIN32
32 # ifdef small
33 # undef small
34 # endif
35 #endif /*_WIN32*/
37 #ifdef USE_GTKSPELL
38 # include <gtkspell/gtkspell.h>
39 # ifdef _WIN32
40 # include "wspell.h"
41 # endif
42 #endif
44 #include <gdk/gdkkeysyms.h>
46 #include "conversation.h"
47 #include "debug.h"
48 #include "desktopitem.h"
49 #include "imgstore.h"
50 #include "notify.h"
51 #include "prefs.h"
52 #include "prpl.h"
53 #include "request.h"
54 #include "signals.h"
55 #include "sound.h"
56 #include "util.h"
58 #include "gtkaccount.h"
59 #include "gtkprefs.h"
61 #include "gtkconv.h"
62 #include "gtkdialogs.h"
63 #include "gtkimhtml.h"
64 #include "gtkimhtmltoolbar.h"
65 #include "pidginstock.h"
66 #include "gtkthemes.h"
67 #include "gtkutils.h"
68 #include "pidgin/minidialog.h"
70 typedef struct {
71 GtkWidget *menu;
72 gint default_item;
73 } AopMenu;
75 static guint accels_save_timer = 0;
76 static GSList *registered_url_handlers = NULL;
78 static gboolean
79 url_clicked_idle_cb(gpointer data)
81 purple_notify_uri(NULL, data);
82 g_free(data);
83 return FALSE;
86 static gboolean
87 url_clicked_cb(GtkIMHtml *unused, GtkIMHtmlLink *link)
89 const char *uri = gtk_imhtml_link_get_url(link);
90 g_idle_add(url_clicked_idle_cb, g_strdup(uri));
91 return TRUE;
94 static GtkIMHtmlFuncs gtkimhtml_cbs = {
95 (GtkIMHtmlGetImageFunc)purple_imgstore_find_by_id,
96 (GtkIMHtmlGetImageDataFunc)purple_imgstore_get_data,
97 (GtkIMHtmlGetImageSizeFunc)purple_imgstore_get_size,
98 (GtkIMHtmlGetImageFilenameFunc)purple_imgstore_get_filename,
99 purple_imgstore_ref_by_id,
100 purple_imgstore_unref_by_id,
103 void
104 pidgin_setup_imhtml(GtkWidget *imhtml)
106 g_return_if_fail(imhtml != NULL);
107 g_return_if_fail(GTK_IS_IMHTML(imhtml));
109 pidgin_themes_smiley_themeize(imhtml);
111 gtk_imhtml_set_funcs(GTK_IMHTML(imhtml), &gtkimhtml_cbs);
113 #ifdef _WIN32
114 if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/use_theme_font")) {
115 PangoFontDescription *desc;
116 const char *font = purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/custom_font");
117 desc = pango_font_description_from_string(font);
118 if (desc) {
119 gtk_widget_modify_font(imhtml, desc);
120 pango_font_description_free(desc);
123 #endif
127 static
128 void pidgin_window_init(GtkWindow *wnd, const char *title, guint border_width, const char *role, gboolean resizable)
130 if (title)
131 gtk_window_set_title(wnd, title);
132 #ifdef _WIN32
133 else
134 gtk_window_set_title(wnd, PIDGIN_ALERT_TITLE);
135 #endif
136 gtk_container_set_border_width(GTK_CONTAINER(wnd), border_width);
137 if (role)
138 gtk_window_set_role(wnd, role);
139 gtk_window_set_resizable(wnd, resizable);
142 GtkWidget *
143 pidgin_create_window(const char *title, guint border_width, const char *role, gboolean resizable)
145 GtkWindow *wnd = NULL;
147 wnd = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
148 pidgin_window_init(wnd, title, border_width, role, resizable);
150 return GTK_WIDGET(wnd);
153 GtkWidget *
154 pidgin_create_small_button(GtkWidget *image)
156 GtkWidget *button;
158 button = gtk_button_new();
159 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
161 /* don't allow focus on the close button */
162 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
164 /* set style to make it as small as possible */
165 gtk_widget_set_name(button, "pidgin-small-close-button");
167 gtk_widget_show(image);
169 gtk_container_add(GTK_CONTAINER(button), image);
171 return button;
174 GtkWidget *
175 pidgin_create_dialog(const char *title, guint border_width, const char *role, gboolean resizable)
177 GtkWindow *wnd = NULL;
179 wnd = GTK_WINDOW(gtk_dialog_new());
180 pidgin_window_init(wnd, title, border_width, role, resizable);
181 g_object_set(G_OBJECT(wnd), "has-separator", FALSE, NULL);
183 return GTK_WIDGET(wnd);
186 GtkWidget *
187 pidgin_dialog_get_vbox_with_properties(GtkDialog *dialog, gboolean homogeneous, gint spacing)
189 GtkBox *vbox = GTK_BOX(GTK_DIALOG(dialog)->vbox);
190 gtk_box_set_homogeneous(vbox, homogeneous);
191 gtk_box_set_spacing(vbox, spacing);
192 return GTK_WIDGET(vbox);
195 GtkWidget *pidgin_dialog_get_vbox(GtkDialog *dialog)
197 return GTK_DIALOG(dialog)->vbox;
200 GtkWidget *pidgin_dialog_get_action_area(GtkDialog *dialog)
202 return GTK_DIALOG(dialog)->action_area;
205 GtkWidget *pidgin_dialog_add_button(GtkDialog *dialog, const char *label,
206 GCallback callback, gpointer callbackdata)
208 GtkWidget *button = gtk_button_new_from_stock(label);
209 GtkWidget *bbox = pidgin_dialog_get_action_area(dialog);
210 gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
211 if (callback)
212 g_signal_connect(G_OBJECT(button), "clicked", callback, callbackdata);
213 gtk_widget_show(button);
214 return button;
217 GtkWidget *
218 pidgin_create_imhtml(gboolean editable, GtkWidget **imhtml_ret, GtkWidget **toolbar_ret, GtkWidget **sw_ret)
220 GtkWidget *frame;
221 GtkWidget *imhtml;
222 GtkWidget *sep;
223 GtkWidget *sw;
224 GtkWidget *toolbar = NULL;
225 GtkWidget *vbox;
227 frame = gtk_frame_new(NULL);
228 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
230 vbox = gtk_vbox_new(FALSE, 0);
231 gtk_container_add(GTK_CONTAINER(frame), vbox);
232 gtk_widget_show(vbox);
234 if (editable) {
235 toolbar = gtk_imhtmltoolbar_new();
236 gtk_box_pack_start(GTK_BOX(vbox), toolbar, FALSE, FALSE, 0);
237 gtk_widget_show(toolbar);
239 sep = gtk_hseparator_new();
240 gtk_box_pack_start(GTK_BOX(vbox), sep, FALSE, FALSE, 0);
241 g_signal_connect_swapped(G_OBJECT(toolbar), "show", G_CALLBACK(gtk_widget_show), sep);
242 g_signal_connect_swapped(G_OBJECT(toolbar), "hide", G_CALLBACK(gtk_widget_hide), sep);
243 gtk_widget_show(sep);
246 imhtml = gtk_imhtml_new(NULL, NULL);
247 gtk_imhtml_set_editable(GTK_IMHTML(imhtml), editable);
248 gtk_imhtml_set_format_functions(GTK_IMHTML(imhtml), GTK_IMHTML_ALL ^ GTK_IMHTML_IMAGE);
249 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(imhtml), GTK_WRAP_WORD_CHAR);
250 #ifdef USE_GTKSPELL
251 if (editable && purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/spellcheck"))
252 pidgin_setup_gtkspell(GTK_TEXT_VIEW(imhtml));
253 #endif
254 gtk_widget_show(imhtml);
256 if (editable) {
257 gtk_imhtmltoolbar_attach(GTK_IMHTMLTOOLBAR(toolbar), imhtml);
258 gtk_imhtmltoolbar_associate_smileys(GTK_IMHTMLTOOLBAR(toolbar), "default");
260 pidgin_setup_imhtml(imhtml);
262 sw = pidgin_make_scrollable(imhtml, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC, GTK_SHADOW_NONE, -1, -1);
263 gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0);
265 if (imhtml_ret != NULL)
266 *imhtml_ret = imhtml;
268 if (editable && (toolbar_ret != NULL))
269 *toolbar_ret = toolbar;
271 if (sw_ret != NULL)
272 *sw_ret = sw;
274 return frame;
277 void
278 pidgin_set_sensitive_if_input(GtkWidget *entry, GtkWidget *dialog)
280 const char *text = gtk_entry_get_text(GTK_ENTRY(entry));
281 gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog), GTK_RESPONSE_OK,
282 (*text != '\0'));
285 void
286 pidgin_toggle_sensitive(GtkWidget *widget, GtkWidget *to_toggle)
288 gboolean sensitivity;
290 if (to_toggle == NULL)
291 return;
293 sensitivity = GTK_WIDGET_IS_SENSITIVE(to_toggle);
295 gtk_widget_set_sensitive(to_toggle, !sensitivity);
298 void
299 pidgin_toggle_sensitive_array(GtkWidget *w, GPtrArray *data)
301 gboolean sensitivity;
302 gpointer element;
303 guint i;
305 for (i=0; i < data->len; i++) {
306 element = g_ptr_array_index(data,i);
307 if (element == NULL)
308 continue;
310 sensitivity = GTK_WIDGET_IS_SENSITIVE(element);
312 gtk_widget_set_sensitive(element, !sensitivity);
316 void
317 pidgin_toggle_showhide(GtkWidget *widget, GtkWidget *to_toggle)
319 if (to_toggle == NULL)
320 return;
322 if (GTK_WIDGET_VISIBLE(to_toggle))
323 gtk_widget_hide(to_toggle);
324 else
325 gtk_widget_show(to_toggle);
328 GtkWidget *pidgin_separator(GtkWidget *menu)
330 GtkWidget *menuitem;
332 menuitem = gtk_separator_menu_item_new();
333 gtk_widget_show(menuitem);
334 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
335 return menuitem;
338 GtkWidget *pidgin_new_item(GtkWidget *menu, const char *str)
340 GtkWidget *menuitem;
341 GtkWidget *label;
343 menuitem = gtk_menu_item_new();
344 if (menu)
345 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
346 gtk_widget_show(menuitem);
348 label = gtk_label_new(str);
349 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
350 gtk_label_set_pattern(GTK_LABEL(label), "_");
351 gtk_container_add(GTK_CONTAINER(menuitem), label);
352 gtk_widget_show(label);
353 /* FIXME: Go back and fix this
354 gtk_widget_add_accelerator(menuitem, "activate", accel, str[0],
355 GDK_MOD1_MASK, GTK_ACCEL_LOCKED);
357 pidgin_set_accessible_label (menuitem, label);
358 return menuitem;
361 GtkWidget *pidgin_new_check_item(GtkWidget *menu, const char *str,
362 GCallback cb, gpointer data, gboolean checked)
364 GtkWidget *menuitem;
365 menuitem = gtk_check_menu_item_new_with_mnemonic(str);
367 if (menu)
368 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
370 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), checked);
372 if (cb)
373 g_signal_connect(G_OBJECT(menuitem), "activate", cb, data);
375 gtk_widget_show_all(menuitem);
377 return menuitem;
380 GtkWidget *
381 pidgin_pixbuf_toolbar_button_from_stock(const char *icon)
383 GtkWidget *button, *image, *bbox;
385 button = gtk_toggle_button_new();
386 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
388 bbox = gtk_vbox_new(FALSE, 0);
390 gtk_container_add (GTK_CONTAINER(button), bbox);
392 image = gtk_image_new_from_stock(icon, gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL));
393 gtk_box_pack_start(GTK_BOX(bbox), image, FALSE, FALSE, 0);
395 gtk_widget_show_all(bbox);
397 return button;
400 GtkWidget *
401 pidgin_pixbuf_button_from_stock(const char *text, const char *icon,
402 PidginButtonOrientation style)
404 GtkWidget *button, *image, *label, *bbox, *ibox, *lbox = NULL;
406 button = gtk_button_new();
408 if (style == PIDGIN_BUTTON_HORIZONTAL) {
409 bbox = gtk_hbox_new(FALSE, 0);
410 ibox = gtk_hbox_new(FALSE, 0);
411 if (text)
412 lbox = gtk_hbox_new(FALSE, 0);
413 } else {
414 bbox = gtk_vbox_new(FALSE, 0);
415 ibox = gtk_vbox_new(FALSE, 0);
416 if (text)
417 lbox = gtk_vbox_new(FALSE, 0);
420 gtk_container_add(GTK_CONTAINER(button), bbox);
422 if (icon) {
423 gtk_box_pack_start(GTK_BOX(bbox), ibox, TRUE, TRUE, 0);
424 image = gtk_image_new_from_stock(icon, GTK_ICON_SIZE_BUTTON);
425 gtk_box_pack_end(GTK_BOX(ibox), image, FALSE, TRUE, 0);
428 if (text) {
429 gtk_box_pack_start(GTK_BOX(bbox), lbox, TRUE, TRUE, 0);
430 label = gtk_label_new(NULL);
431 gtk_label_set_text_with_mnemonic(GTK_LABEL(label), text);
432 gtk_label_set_mnemonic_widget(GTK_LABEL(label), button);
433 gtk_box_pack_start(GTK_BOX(lbox), label, FALSE, TRUE, 0);
434 pidgin_set_accessible_label (button, label);
437 gtk_widget_show_all(bbox);
439 return button;
443 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)
445 GtkWidget *menuitem;
447 GtkWidget *hbox;
448 GtkWidget *label;
450 GtkWidget *image;
452 if (icon == NULL)
453 menuitem = gtk_menu_item_new_with_mnemonic(str);
454 else
455 menuitem = gtk_image_menu_item_new_with_mnemonic(str);
457 if (menu)
458 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
460 if (cb)
461 g_signal_connect(G_OBJECT(menuitem), "activate", cb, data);
463 if (icon != NULL) {
464 image = gtk_image_new_from_stock(icon, gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL));
465 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image);
467 /* FIXME: this isn't right
468 if (mod) {
469 label = gtk_label_new(mod);
470 gtk_box_pack_end(GTK_BOX(hbox), label, FALSE, FALSE, 2);
471 gtk_widget_show(label);
475 if (accel_key) {
476 gtk_widget_add_accelerator(menuitem, "activate", accel, accel_key,
477 accel_mods, GTK_ACCEL_LOCKED);
481 gtk_widget_show_all(menuitem);
483 return menuitem;
486 GtkWidget *
487 pidgin_make_frame(GtkWidget *parent, const char *title)
489 GtkWidget *vbox, *label, *hbox;
490 char *labeltitle;
492 vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
493 gtk_box_pack_start(GTK_BOX(parent), vbox, FALSE, FALSE, 0);
494 gtk_widget_show(vbox);
496 label = gtk_label_new(NULL);
498 labeltitle = g_strdup_printf("<span weight=\"bold\">%s</span>", title);
499 gtk_label_set_markup(GTK_LABEL(label), labeltitle);
500 g_free(labeltitle);
502 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
503 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
504 gtk_widget_show(label);
505 pidgin_set_accessible_label (vbox, label);
507 hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
508 gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
509 gtk_widget_show(hbox);
511 label = gtk_label_new(" ");
512 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
513 gtk_widget_show(label);
515 vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
516 gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);
517 gtk_widget_show(vbox);
519 return vbox;
522 static gpointer
523 aop_option_menu_get_selected(GtkWidget *optmenu, GtkWidget **p_item)
525 GtkWidget *menu = gtk_option_menu_get_menu(GTK_OPTION_MENU(optmenu));
526 GtkWidget *item = gtk_menu_get_active(GTK_MENU(menu));
527 if (p_item)
528 (*p_item) = item;
529 return item ? g_object_get_data(G_OBJECT(item), "aop_per_item_data") : NULL;
532 static void
533 aop_menu_cb(GtkWidget *optmenu, GCallback cb)
535 GtkWidget *item;
536 gpointer per_item_data;
538 per_item_data = aop_option_menu_get_selected(optmenu, &item);
540 if (cb != NULL) {
541 ((void (*)(GtkWidget *, gpointer, gpointer))cb)(item, per_item_data, g_object_get_data(G_OBJECT(optmenu), "user_data"));
545 static GtkWidget *
546 aop_menu_item_new(GtkSizeGroup *sg, GdkPixbuf *pixbuf, const char *lbl, gpointer per_item_data, const char *data)
548 GtkWidget *item;
549 GtkWidget *hbox;
550 GtkWidget *image;
551 GtkWidget *label;
553 item = gtk_menu_item_new();
554 gtk_widget_show(item);
556 hbox = gtk_hbox_new(FALSE, 4);
557 gtk_widget_show(hbox);
559 /* Create the image */
560 if (pixbuf == NULL)
561 image = gtk_image_new();
562 else
563 image = gtk_image_new_from_pixbuf(pixbuf);
564 gtk_widget_show(image);
566 if (sg)
567 gtk_size_group_add_widget(sg, image);
569 /* Create the label */
570 label = gtk_label_new (lbl);
571 gtk_widget_show (label);
572 gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
573 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
575 gtk_container_add(GTK_CONTAINER(item), hbox);
576 gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0);
577 gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
579 g_object_set_data(G_OBJECT (item), data, per_item_data);
580 g_object_set_data(G_OBJECT (item), "aop_per_item_data", per_item_data);
582 pidgin_set_accessible_label(item, label);
584 return item;
587 static GdkPixbuf *
588 pidgin_create_prpl_icon_from_prpl(PurplePlugin *prpl, PidginPrplIconSize size, PurpleAccount *account)
590 PurplePluginProtocolInfo *prpl_info;
591 const char *protoname = NULL;
592 char *tmp;
593 char *filename = NULL;
594 GdkPixbuf *pixbuf;
596 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
597 if (prpl_info->list_icon == NULL)
598 return NULL;
600 protoname = prpl_info->list_icon(account, NULL);
601 if (protoname == NULL)
602 return NULL;
605 * Status icons will be themeable too, and then it will look up
606 * protoname from the theme
608 tmp = g_strconcat(protoname, ".png", NULL);
610 filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "protocols",
611 size == PIDGIN_PRPL_ICON_SMALL ? "16" :
612 size == PIDGIN_PRPL_ICON_MEDIUM ? "22" : "48",
613 tmp, NULL);
614 g_free(tmp);
616 pixbuf = pidgin_pixbuf_new_from_file(filename);
617 g_free(filename);
619 return pixbuf;
622 static GtkWidget *
623 aop_option_menu_new(AopMenu *aop_menu, GCallback cb, gpointer user_data)
625 GtkWidget *optmenu;
627 optmenu = gtk_option_menu_new();
628 gtk_widget_show(optmenu);
629 gtk_option_menu_set_menu(GTK_OPTION_MENU(optmenu), aop_menu->menu);
631 if (aop_menu->default_item != -1)
632 gtk_option_menu_set_history(GTK_OPTION_MENU(optmenu), aop_menu->default_item);
634 g_object_set_data_full(G_OBJECT(optmenu), "aop_menu", aop_menu, (GDestroyNotify)g_free);
635 g_object_set_data(G_OBJECT(optmenu), "user_data", user_data);
637 g_signal_connect(G_OBJECT(optmenu), "changed", G_CALLBACK(aop_menu_cb), cb);
639 return optmenu;
642 static void
643 aop_option_menu_replace_menu(GtkWidget *optmenu, AopMenu *new_aop_menu)
645 if (gtk_option_menu_get_menu(GTK_OPTION_MENU(optmenu)))
646 gtk_option_menu_remove_menu(GTK_OPTION_MENU(optmenu));
648 gtk_option_menu_set_menu(GTK_OPTION_MENU(optmenu), new_aop_menu->menu);
650 if (new_aop_menu->default_item != -1)
651 gtk_option_menu_set_history(GTK_OPTION_MENU(optmenu), new_aop_menu->default_item);
653 g_object_set_data_full(G_OBJECT(optmenu), "aop_menu", new_aop_menu, (GDestroyNotify)g_free);
656 static void
657 aop_option_menu_select_by_data(GtkWidget *optmenu, gpointer data)
659 guint idx;
660 GList *llItr = NULL;
662 for (idx = 0, llItr = GTK_MENU_SHELL(gtk_option_menu_get_menu(GTK_OPTION_MENU(optmenu)))->children;
663 llItr != NULL;
664 llItr = llItr->next, idx++) {
665 if (data == g_object_get_data(G_OBJECT(llItr->data), "aop_per_item_data")) {
666 gtk_option_menu_set_history(GTK_OPTION_MENU(optmenu), idx);
667 break;
672 static AopMenu *
673 create_protocols_menu(const char *default_proto_id)
675 AopMenu *aop_menu = NULL;
676 PurplePlugin *plugin;
677 GdkPixbuf *pixbuf = NULL;
678 GtkSizeGroup *sg;
679 GList *p;
680 const char *gtalk_name = NULL;
681 int i;
683 aop_menu = g_malloc0(sizeof(AopMenu));
684 aop_menu->default_item = -1;
685 aop_menu->menu = gtk_menu_new();
686 gtk_widget_show(aop_menu->menu);
687 sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
689 if (purple_find_prpl("prpl-jabber")) {
690 gtalk_name = _("Google Talk");
693 for (p = purple_plugins_get_protocols(), i = 0;
694 p != NULL;
695 p = p->next, i++) {
697 plugin = (PurplePlugin *)p->data;
699 if (gtalk_name && strcmp(gtalk_name, plugin->info->name) < 0) {
700 char *filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "protocols",
701 "16", "google-talk.png", NULL);
702 GtkWidget *item;
704 pixbuf = pidgin_pixbuf_new_from_file(filename);
705 g_free(filename);
707 gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu->menu),
708 item = aop_menu_item_new(sg, pixbuf, gtalk_name, "prpl-jabber", "protocol"));
709 g_object_set_data(G_OBJECT(item), "fakegoogle", GINT_TO_POINTER(1));
711 if (pixbuf)
712 g_object_unref(pixbuf);
714 /* libpurple3 compatibility */
715 if (purple_strequal(default_proto_id, "prpl-gtalk"))
716 aop_menu->default_item = i;
718 gtalk_name = NULL;
719 i++;
722 pixbuf = pidgin_create_prpl_icon_from_prpl(plugin, PIDGIN_PRPL_ICON_SMALL, NULL);
724 gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu->menu),
725 aop_menu_item_new(sg, pixbuf, plugin->info->name, plugin->info->id, "protocol"));
727 if (pixbuf)
728 g_object_unref(pixbuf);
730 if (default_proto_id != NULL && purple_strequal(plugin->info->id, default_proto_id))
731 aop_menu->default_item = i;
734 g_object_unref(sg);
736 return aop_menu;
739 GtkWidget *
740 pidgin_protocol_option_menu_new(const char *id, GCallback cb,
741 gpointer user_data)
743 return aop_option_menu_new(create_protocols_menu(id), cb, user_data);
746 const char *
747 pidgin_protocol_option_menu_get_selected(GtkWidget *optmenu)
749 return (const char *)aop_option_menu_get_selected(optmenu, NULL);
752 PurpleAccount *
753 pidgin_account_option_menu_get_selected(GtkWidget *optmenu)
755 return (PurpleAccount *)aop_option_menu_get_selected(optmenu, NULL);
758 static AopMenu *
759 create_account_menu(PurpleAccount *default_account,
760 PurpleFilterAccountFunc filter_func, gboolean show_all)
762 AopMenu *aop_menu = NULL;
763 PurpleAccount *account;
764 GdkPixbuf *pixbuf = NULL;
765 GList *list;
766 GList *p;
767 GtkSizeGroup *sg;
768 int i;
769 char buf[256];
771 if (show_all)
772 list = purple_accounts_get_all();
773 else
774 list = purple_connections_get_all();
776 aop_menu = g_malloc0(sizeof(AopMenu));
777 aop_menu->default_item = -1;
778 aop_menu->menu = gtk_menu_new();
779 gtk_widget_show(aop_menu->menu);
780 sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
782 for (p = list, i = 0; p != NULL; p = p->next, i++) {
783 if (show_all)
784 account = (PurpleAccount *)p->data;
785 else {
786 PurpleConnection *gc = (PurpleConnection *)p->data;
788 account = purple_connection_get_account(gc);
791 if (filter_func && !filter_func(account)) {
792 i--;
793 continue;
796 pixbuf = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL);
798 if (pixbuf) {
799 if (purple_account_is_disconnected(account) && show_all &&
800 purple_connections_get_all())
801 gdk_pixbuf_saturate_and_pixelate(pixbuf, pixbuf, 0.0, FALSE);
804 if (purple_account_get_alias(account)) {
805 g_snprintf(buf, sizeof(buf), "%s (%s) (%s)",
806 purple_account_get_username(account),
807 purple_account_get_alias(account),
808 purple_account_get_protocol_name(account));
809 } else {
810 g_snprintf(buf, sizeof(buf), "%s (%s)",
811 purple_account_get_username(account),
812 purple_account_get_protocol_name(account));
815 gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu->menu),
816 aop_menu_item_new(sg, pixbuf, buf, account, "account"));
818 if (pixbuf)
819 g_object_unref(pixbuf);
821 if (default_account && account == default_account)
822 aop_menu->default_item = i;
825 g_object_unref(sg);
827 return aop_menu;
830 static void
831 regenerate_account_menu(GtkWidget *optmenu)
833 gboolean show_all;
834 PurpleAccount *account;
835 PurpleFilterAccountFunc filter_func;
837 account = (PurpleAccount *)aop_option_menu_get_selected(optmenu, NULL);
838 show_all = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(optmenu), "show_all"));
839 filter_func = g_object_get_data(G_OBJECT(optmenu), "filter_func");
841 aop_option_menu_replace_menu(optmenu, create_account_menu(account, filter_func, show_all));
844 static void
845 account_menu_sign_on_off_cb(PurpleConnection *gc, GtkWidget *optmenu)
847 regenerate_account_menu(optmenu);
850 static void
851 account_menu_added_removed_cb(PurpleAccount *account, GtkWidget *optmenu)
853 regenerate_account_menu(optmenu);
856 static gboolean
857 account_menu_destroyed_cb(GtkWidget *optmenu, GdkEvent *event,
858 void *user_data)
860 purple_signals_disconnect_by_handle(optmenu);
862 return FALSE;
865 void
866 pidgin_account_option_menu_set_selected(GtkWidget *optmenu, PurpleAccount *account)
868 aop_option_menu_select_by_data(optmenu, account);
871 GtkWidget *
872 pidgin_account_option_menu_new(PurpleAccount *default_account,
873 gboolean show_all, GCallback cb,
874 PurpleFilterAccountFunc filter_func,
875 gpointer user_data)
877 GtkWidget *optmenu;
879 /* Create the option menu */
880 optmenu = aop_option_menu_new(create_account_menu(default_account, filter_func, show_all), cb, user_data);
882 g_signal_connect(G_OBJECT(optmenu), "destroy",
883 G_CALLBACK(account_menu_destroyed_cb), NULL);
885 /* Register the purple sign on/off event callbacks. */
886 purple_signal_connect(purple_connections_get_handle(), "signed-on",
887 optmenu, PURPLE_CALLBACK(account_menu_sign_on_off_cb),
888 optmenu);
889 purple_signal_connect(purple_connections_get_handle(), "signed-off",
890 optmenu, PURPLE_CALLBACK(account_menu_sign_on_off_cb),
891 optmenu);
892 purple_signal_connect(purple_accounts_get_handle(), "account-added",
893 optmenu, PURPLE_CALLBACK(account_menu_added_removed_cb),
894 optmenu);
895 purple_signal_connect(purple_accounts_get_handle(), "account-removed",
896 optmenu, PURPLE_CALLBACK(account_menu_added_removed_cb),
897 optmenu);
899 /* Set some data. */
900 g_object_set_data(G_OBJECT(optmenu), "user_data", user_data);
901 g_object_set_data(G_OBJECT(optmenu), "show_all", GINT_TO_POINTER(show_all));
902 g_object_set_data(G_OBJECT(optmenu), "filter_func", filter_func);
904 return optmenu;
907 gboolean
908 pidgin_check_if_dir(const char *path, GtkFileSelection *filesel)
910 char *dirname = NULL;
912 if (g_file_test(path, G_FILE_TEST_IS_DIR)) {
913 /* append a / if needed */
914 if (path[strlen(path) - 1] != G_DIR_SEPARATOR) {
915 dirname = g_strconcat(path, G_DIR_SEPARATOR_S, NULL);
917 gtk_file_selection_set_filename(filesel, (dirname != NULL) ? dirname : path);
918 g_free(dirname);
919 return TRUE;
922 return FALSE;
925 void
926 pidgin_setup_gtkspell(GtkTextView *textview)
928 #ifdef USE_GTKSPELL
929 GError *error = NULL;
930 char *locale = NULL;
932 g_return_if_fail(textview != NULL);
933 g_return_if_fail(GTK_IS_TEXT_VIEW(textview));
935 if (gtkspell_new_attach(textview, locale, &error) == NULL && error)
937 purple_debug_warning("gtkspell", "Failed to setup GtkSpell: %s\n",
938 error->message);
939 g_error_free(error);
941 #endif /* USE_GTKSPELL */
944 void
945 pidgin_save_accels_cb(GtkAccelGroup *accel_group, guint arg1,
946 GdkModifierType arg2, GClosure *arg3,
947 gpointer data)
949 purple_debug(PURPLE_DEBUG_MISC, "accels",
950 "accel changed, scheduling save.\n");
952 if (!accels_save_timer)
953 accels_save_timer = purple_timeout_add_seconds(5, pidgin_save_accels,
954 NULL);
957 gboolean
958 pidgin_save_accels(gpointer data)
960 char *filename = NULL;
962 filename = g_build_filename(purple_user_dir(), G_DIR_SEPARATOR_S,
963 "accels", NULL);
964 purple_debug(PURPLE_DEBUG_MISC, "accels", "saving accels to %s\n", filename);
965 gtk_accel_map_save(filename);
966 g_free(filename);
968 accels_save_timer = 0;
969 return FALSE;
972 void
973 pidgin_load_accels()
975 char *filename = NULL;
977 filename = g_build_filename(purple_user_dir(), G_DIR_SEPARATOR_S,
978 "accels", NULL);
979 gtk_accel_map_load(filename);
980 g_free(filename);
983 static void
984 show_retrieveing_info(PurpleConnection *conn, const char *name)
986 PurpleNotifyUserInfo *info = purple_notify_user_info_new();
987 purple_notify_user_info_add_pair(info, _("Information"), _("Retrieving..."));
988 purple_notify_userinfo(conn, name, info, NULL, NULL);
989 purple_notify_user_info_destroy(info);
992 void pidgin_retrieve_user_info(PurpleConnection *conn, const char *name)
994 show_retrieveing_info(conn, name);
995 serv_get_info(conn, name);
998 void pidgin_retrieve_user_info_in_chat(PurpleConnection *conn, const char *name, int chat)
1000 char *who = NULL;
1001 PurplePluginProtocolInfo *prpl_info = NULL;
1003 if (chat < 0) {
1004 pidgin_retrieve_user_info(conn, name);
1005 return;
1008 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(conn->prpl);
1009 if (prpl_info != NULL && prpl_info->get_cb_real_name)
1010 who = prpl_info->get_cb_real_name(conn, chat, name);
1011 if (prpl_info == NULL || prpl_info->get_cb_info == NULL) {
1012 pidgin_retrieve_user_info(conn, who ? who : name);
1013 g_free(who);
1014 return;
1017 show_retrieveing_info(conn, who ? who : name);
1018 prpl_info->get_cb_info(conn, chat, name);
1019 g_free(who);
1022 gboolean
1023 pidgin_parse_x_im_contact(const char *msg, gboolean all_accounts,
1024 PurpleAccount **ret_account, char **ret_protocol,
1025 char **ret_username, char **ret_alias)
1027 char *protocol = NULL;
1028 char *username = NULL;
1029 char *alias = NULL;
1030 char *str;
1031 char *s;
1032 gboolean valid;
1034 g_return_val_if_fail(msg != NULL, FALSE);
1035 g_return_val_if_fail(ret_protocol != NULL, FALSE);
1036 g_return_val_if_fail(ret_username != NULL, FALSE);
1038 s = str = g_strdup(msg);
1040 while (*s != '\r' && *s != '\n' && *s != '\0')
1042 char *key, *value;
1044 key = s;
1046 /* Grab the key */
1047 while (*s != '\r' && *s != '\n' && *s != '\0' && *s != ' ')
1048 s++;
1050 if (*s == '\r') s++;
1052 if (*s == '\n')
1054 s++;
1055 continue;
1058 if (*s != '\0') *s++ = '\0';
1060 /* Clear past any whitespace */
1061 while (*s != '\0' && *s == ' ')
1062 s++;
1064 /* Now let's grab until the end of the line. */
1065 value = s;
1067 while (*s != '\r' && *s != '\n' && *s != '\0')
1068 s++;
1070 if (*s == '\r') *s++ = '\0';
1071 if (*s == '\n') *s++ = '\0';
1073 if (strchr(key, ':') != NULL)
1075 if (!g_ascii_strcasecmp(key, "X-IM-Username:"))
1076 username = g_strdup(value);
1077 else if (!g_ascii_strcasecmp(key, "X-IM-Protocol:"))
1078 protocol = g_strdup(value);
1079 else if (!g_ascii_strcasecmp(key, "X-IM-Alias:"))
1080 alias = g_strdup(value);
1084 if (username != NULL && protocol != NULL)
1086 valid = TRUE;
1088 *ret_username = username;
1089 *ret_protocol = protocol;
1091 if (ret_alias != NULL)
1092 *ret_alias = alias;
1094 /* Check for a compatible account. */
1095 if (ret_account != NULL)
1097 GList *list;
1098 PurpleAccount *account = NULL;
1099 GList *l;
1100 const char *protoname;
1102 if (all_accounts)
1103 list = purple_accounts_get_all();
1104 else
1105 list = purple_connections_get_all();
1107 for (l = list; l != NULL; l = l->next)
1109 PurpleConnection *gc;
1110 PurplePluginProtocolInfo *prpl_info = NULL;
1111 PurplePlugin *plugin;
1113 if (all_accounts)
1115 account = (PurpleAccount *)l->data;
1117 plugin = purple_plugins_find_with_id(
1118 purple_account_get_protocol_id(account));
1120 if (plugin == NULL)
1122 account = NULL;
1124 continue;
1127 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin);
1129 else
1131 gc = (PurpleConnection *)l->data;
1132 account = purple_connection_get_account(gc);
1134 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
1137 protoname = prpl_info->list_icon(account, NULL);
1139 if (purple_strequal(protoname, protocol))
1140 break;
1142 account = NULL;
1145 /* Special case for AIM and ICQ */
1146 if (account == NULL && (purple_strequal(protocol, "aim") ||
1147 purple_strequal(protocol, "icq")))
1149 for (l = list; l != NULL; l = l->next)
1151 PurpleConnection *gc;
1152 PurplePluginProtocolInfo *prpl_info = NULL;
1153 PurplePlugin *plugin;
1155 if (all_accounts)
1157 account = (PurpleAccount *)l->data;
1159 plugin = purple_plugins_find_with_id(
1160 purple_account_get_protocol_id(account));
1162 if (plugin == NULL)
1164 account = NULL;
1166 continue;
1169 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin);
1171 else
1173 gc = (PurpleConnection *)l->data;
1174 account = purple_connection_get_account(gc);
1176 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
1179 protoname = prpl_info->list_icon(account, NULL);
1181 if (purple_strequal(protoname, "aim") || purple_strequal(protoname, "icq"))
1182 break;
1184 account = NULL;
1188 *ret_account = account;
1191 else
1193 valid = FALSE;
1195 g_free(username);
1196 g_free(protocol);
1197 g_free(alias);
1200 g_free(str);
1202 return valid;
1205 void
1206 pidgin_set_accessible_label (GtkWidget *w, GtkWidget *l)
1208 AtkObject *acc;
1209 const gchar *label_text;
1210 const gchar *existing_name;
1212 acc = gtk_widget_get_accessible (w);
1214 /* If this object has no name, set it's name with the label text */
1215 existing_name = atk_object_get_name (acc);
1216 if (!existing_name) {
1217 label_text = gtk_label_get_text (GTK_LABEL(l));
1218 if (label_text)
1219 atk_object_set_name (acc, label_text);
1222 pidgin_set_accessible_relations(w, l);
1225 void
1226 pidgin_set_accessible_relations (GtkWidget *w, GtkWidget *l)
1228 AtkObject *acc, *label;
1229 AtkObject *rel_obj[1];
1230 AtkRelationSet *set;
1231 AtkRelation *relation;
1233 acc = gtk_widget_get_accessible (w);
1234 label = gtk_widget_get_accessible (l);
1236 /* Make sure mnemonics work */
1237 gtk_label_set_mnemonic_widget(GTK_LABEL(l), w);
1239 /* Create the labeled-by relation */
1240 set = atk_object_ref_relation_set (acc);
1241 rel_obj[0] = label;
1242 relation = atk_relation_new (rel_obj, 1, ATK_RELATION_LABELLED_BY);
1243 atk_relation_set_add (set, relation);
1244 g_object_unref (relation);
1245 g_object_unref(set);
1247 /* Create the label-for relation */
1248 set = atk_object_ref_relation_set (label);
1249 rel_obj[0] = acc;
1250 relation = atk_relation_new (rel_obj, 1, ATK_RELATION_LABEL_FOR);
1251 atk_relation_set_add (set, relation);
1252 g_object_unref (relation);
1253 g_object_unref(set);
1256 void
1257 pidgin_menu_position_func_helper(GtkMenu *menu,
1258 gint *x,
1259 gint *y,
1260 gboolean *push_in,
1261 gpointer data)
1263 GtkWidget *widget;
1264 GtkRequisition requisition;
1265 GdkScreen *screen;
1266 GdkRectangle monitor;
1267 gint monitor_num;
1268 gint space_left, space_right, space_above, space_below;
1269 gint needed_width;
1270 gint needed_height;
1271 gint xthickness;
1272 gint ythickness;
1273 gboolean rtl;
1275 g_return_if_fail(GTK_IS_MENU(menu));
1277 widget = GTK_WIDGET(menu);
1278 screen = gtk_widget_get_screen(widget);
1279 xthickness = widget->style->xthickness;
1280 ythickness = widget->style->ythickness;
1281 rtl = (gtk_widget_get_direction(widget) == GTK_TEXT_DIR_RTL);
1284 * We need the requisition to figure out the right place to
1285 * popup the menu. In fact, we always need to ask here, since
1286 * if a size_request was queued while we weren't popped up,
1287 * the requisition won't have been recomputed yet.
1289 gtk_widget_size_request (widget, &requisition);
1291 monitor_num = gdk_screen_get_monitor_at_point (screen, *x, *y);
1293 *push_in = FALSE;
1296 * The placement of popup menus horizontally works like this (with
1297 * RTL in parentheses)
1299 * - If there is enough room to the right (left) of the mouse cursor,
1300 * position the menu there.
1302 * - Otherwise, if if there is enough room to the left (right) of the
1303 * mouse cursor, position the menu there.
1305 * - Otherwise if the menu is smaller than the monitor, position it
1306 * on the side of the mouse cursor that has the most space available
1308 * - Otherwise (if there is simply not enough room for the menu on the
1309 * monitor), position it as far left (right) as possible.
1311 * Positioning in the vertical direction is similar: first try below
1312 * mouse cursor, then above.
1314 gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
1316 space_left = *x - monitor.x;
1317 space_right = monitor.x + monitor.width - *x - 1;
1318 space_above = *y - monitor.y;
1319 space_below = monitor.y + monitor.height - *y - 1;
1321 /* position horizontally */
1323 /* the amount of space we need to position the menu. Note the
1324 * menu is offset "xthickness" pixels
1326 needed_width = requisition.width - xthickness;
1328 if (needed_width <= space_left ||
1329 needed_width <= space_right)
1331 if ((rtl && needed_width <= space_left) ||
1332 (!rtl && needed_width > space_right))
1334 /* position left */
1335 *x = *x + xthickness - requisition.width + 1;
1337 else
1339 /* position right */
1340 *x = *x - xthickness;
1343 /* x is clamped on-screen further down */
1345 else if (requisition.width <= monitor.width)
1347 /* the menu is too big to fit on either side of the mouse
1348 * cursor, but smaller than the monitor. Position it on
1349 * the side that has the most space
1351 if (space_left > space_right)
1353 /* left justify */
1354 *x = monitor.x;
1356 else
1358 /* right justify */
1359 *x = monitor.x + monitor.width - requisition.width;
1362 else /* menu is simply too big for the monitor */
1364 if (rtl)
1366 /* right justify */
1367 *x = monitor.x + monitor.width - requisition.width;
1369 else
1371 /* left justify */
1372 *x = monitor.x;
1376 /* Position vertically. The algorithm is the same as above, but
1377 * simpler because we don't have to take RTL into account.
1379 needed_height = requisition.height - ythickness;
1381 if (needed_height <= space_above ||
1382 needed_height <= space_below)
1384 if (needed_height <= space_below)
1385 *y = *y - ythickness;
1386 else
1387 *y = *y + ythickness - requisition.height + 1;
1389 *y = CLAMP (*y, monitor.y,
1390 monitor.y + monitor.height - requisition.height);
1392 else if (needed_height > space_below && needed_height > space_above)
1394 if (space_below >= space_above)
1395 *y = monitor.y + monitor.height - requisition.height;
1396 else
1397 *y = monitor.y;
1399 else
1401 *y = monitor.y;
1406 void
1407 pidgin_treeview_popup_menu_position_func(GtkMenu *menu,
1408 gint *x,
1409 gint *y,
1410 gboolean *push_in,
1411 gpointer data)
1413 GtkWidget *widget = GTK_WIDGET(data);
1414 GtkTreeView *tv = GTK_TREE_VIEW(data);
1415 GtkTreePath *path;
1416 GtkTreeViewColumn *col;
1417 GdkRectangle rect;
1418 gint ythickness = GTK_WIDGET(menu)->style->ythickness;
1420 gdk_window_get_origin (widget->window, x, y);
1421 gtk_tree_view_get_cursor (tv, &path, &col);
1422 gtk_tree_view_get_cell_area (tv, path, col, &rect);
1424 *x += rect.x+rect.width;
1425 *y += rect.y+rect.height+ythickness;
1426 pidgin_menu_position_func_helper(menu, x, y, push_in, data);
1429 enum {
1430 DND_FILE_TRANSFER,
1431 DND_IM_IMAGE,
1432 DND_BUDDY_ICON
1435 typedef struct {
1436 char *filename;
1437 PurpleAccount *account;
1438 char *who;
1439 } _DndData;
1441 static void dnd_image_ok_callback(_DndData *data, int choice)
1443 const gchar *shortname;
1444 gchar *filedata;
1445 size_t size;
1446 struct stat st;
1447 GError *err = NULL;
1448 PurpleConversation *conv;
1449 PidginConversation *gtkconv;
1450 GtkTextIter iter;
1451 int id;
1452 PurpleBuddy *buddy;
1453 PurpleContact *contact;
1454 switch (choice) {
1455 case DND_BUDDY_ICON:
1456 if (g_stat(data->filename, &st)) {
1457 char *str;
1459 str = g_strdup_printf(_("The following error has occurred loading %s: %s"),
1460 data->filename, g_strerror(errno));
1461 purple_notify_error(NULL, NULL,
1462 _("Failed to load image"),
1463 str);
1464 g_free(str);
1466 break;
1469 buddy = purple_find_buddy(data->account, data->who);
1470 if (!buddy) {
1471 purple_debug_info("custom-icon", "You can only set custom icons for people on your buddylist.\n");
1472 break;
1474 contact = purple_buddy_get_contact(buddy);
1475 purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode*)contact, data->filename);
1476 break;
1477 case DND_FILE_TRANSFER:
1478 serv_send_file(purple_account_get_connection(data->account), data->who, data->filename);
1479 break;
1480 case DND_IM_IMAGE:
1481 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, data->account, data->who);
1482 gtkconv = PIDGIN_CONVERSATION(conv);
1484 if (!g_file_get_contents(data->filename, &filedata, &size,
1485 &err)) {
1486 char *str;
1488 str = g_strdup_printf(_("The following error has occurred loading %s: %s"), data->filename, err->message);
1489 purple_notify_error(NULL, NULL,
1490 _("Failed to load image"),
1491 str);
1493 g_error_free(err);
1494 g_free(str);
1496 break;
1498 shortname = strrchr(data->filename, G_DIR_SEPARATOR);
1499 shortname = shortname ? shortname + 1 : data->filename;
1500 id = purple_imgstore_add_with_id(filedata, size, shortname);
1502 gtk_text_buffer_get_iter_at_mark(GTK_IMHTML(gtkconv->entry)->text_buffer, &iter,
1503 gtk_text_buffer_get_insert(GTK_IMHTML(gtkconv->entry)->text_buffer));
1504 gtk_imhtml_insert_image_at_iter(GTK_IMHTML(gtkconv->entry), id, &iter);
1505 purple_imgstore_unref_by_id(id);
1507 break;
1509 g_free(data->filename);
1510 g_free(data->who);
1511 g_free(data);
1514 static void dnd_image_cancel_callback(_DndData *data, int choice)
1516 g_free(data->filename);
1517 g_free(data->who);
1518 g_free(data);
1521 static void dnd_set_icon_ok_cb(_DndData *data)
1523 dnd_image_ok_callback(data, DND_BUDDY_ICON);
1526 static void dnd_set_icon_cancel_cb(_DndData *data)
1528 g_free(data->filename);
1529 g_free(data->who);
1530 g_free(data);
1533 void
1534 pidgin_dnd_file_manage(GtkSelectionData *sd, PurpleAccount *account, const char *who)
1536 GdkPixbuf *pb;
1537 GList *files = purple_uri_list_extract_filenames((const gchar *)sd->data);
1538 PurpleConnection *gc = purple_account_get_connection(account);
1539 PurplePluginProtocolInfo *prpl_info = NULL;
1540 #ifndef _WIN32
1541 PurpleDesktopItem *item;
1542 #endif
1543 gchar *filename = NULL;
1544 gchar *basename = NULL;
1546 g_return_if_fail(account != NULL);
1547 g_return_if_fail(who != NULL);
1549 for ( ; files; files = g_list_delete_link(files, files)) {
1550 g_free(filename);
1551 g_free(basename);
1553 filename = files->data;
1554 basename = g_path_get_basename(filename);
1556 /* XXX - Make ft API support creating a transfer with more than one file */
1557 if (!g_file_test(filename, G_FILE_TEST_EXISTS)) {
1558 continue;
1561 /* XXX - make ft api suupport sending a directory */
1562 /* Are we dealing with a directory? */
1563 if (g_file_test(filename, G_FILE_TEST_IS_DIR)) {
1564 char *str, *str2;
1566 str = g_strdup_printf(_("Cannot send folder %s."), basename);
1567 str2 = g_strdup_printf(_("%s cannot transfer a folder. You will need to send the files within individually."), PIDGIN_NAME);
1569 purple_notify_error(NULL, NULL,
1570 str, str2);
1572 g_free(str);
1573 g_free(str2);
1574 continue;
1577 /* Are we dealing with an image? */
1578 pb = pidgin_pixbuf_new_from_file(filename);
1579 if (pb) {
1580 _DndData *data = g_malloc(sizeof(_DndData));
1581 gboolean ft = FALSE, im = FALSE;
1583 data->who = g_strdup(who);
1584 data->filename = g_strdup(filename);
1585 data->account = account;
1587 if (gc)
1588 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
1590 if (prpl_info && prpl_info->options & OPT_PROTO_IM_IMAGE)
1591 im = TRUE;
1593 if (prpl_info && prpl_info->can_receive_file)
1594 ft = prpl_info->can_receive_file(gc, who);
1595 else if (prpl_info && prpl_info->send_file)
1596 ft = TRUE;
1598 if (im && ft)
1599 purple_request_choice(NULL, NULL,
1600 _("You have dragged an image"),
1601 _("You can send this image as a file transfer, "
1602 "embed it into this message, or use it as the buddy icon for this user."),
1603 DND_FILE_TRANSFER, _("OK"), (GCallback)dnd_image_ok_callback,
1604 _("Cancel"), (GCallback)dnd_image_cancel_callback,
1605 account, who, NULL,
1606 data,
1607 _("Set as buddy icon"), DND_BUDDY_ICON,
1608 _("Send image file"), DND_FILE_TRANSFER,
1609 _("Insert in message"), DND_IM_IMAGE,
1610 NULL);
1611 else if (!(im || ft))
1612 purple_request_yes_no(NULL, NULL, _("You have dragged an image"),
1613 _("Would you like to set it as the buddy icon for this user?"),
1614 PURPLE_DEFAULT_ACTION_NONE,
1615 account, who, NULL,
1616 data, (GCallback)dnd_set_icon_ok_cb, (GCallback)dnd_set_icon_cancel_cb);
1617 else
1618 purple_request_choice(NULL, NULL,
1619 _("You have dragged an image"),
1620 (ft ? _("You can send this image as a file transfer, or use it as the buddy icon for this user.") :
1621 _("You can insert this image into this message, or use it as the buddy icon for this user")),
1622 (ft ? DND_FILE_TRANSFER : DND_IM_IMAGE),
1623 _("OK"), (GCallback)dnd_image_ok_callback,
1624 _("Cancel"), (GCallback)dnd_image_cancel_callback,
1625 account, who, NULL,
1626 data,
1627 _("Set as buddy icon"), DND_BUDDY_ICON,
1628 (ft ? _("Send image file") : _("Insert in message")), (ft ? DND_FILE_TRANSFER : DND_IM_IMAGE),
1629 NULL);
1630 g_object_unref(G_OBJECT(pb));
1632 g_free(basename);
1633 while (files) {
1634 g_free(files->data);
1635 files = g_list_delete_link(files, files);
1637 return;
1640 #ifndef _WIN32
1641 /* Are we trying to send a .desktop file? */
1642 else if (purple_str_has_suffix(basename, ".desktop") && (item = purple_desktop_item_new_from_file(filename))) {
1643 PurpleDesktopItemType dtype;
1644 char key[64];
1645 const char *itemname = NULL;
1647 const char * const *langs;
1648 langs = g_get_language_names();
1649 if (langs[0]) {
1650 g_snprintf(key, sizeof(key), "Name[%s]", langs[0]);
1651 itemname = purple_desktop_item_get_string(item, key);
1654 if (!itemname)
1655 itemname = purple_desktop_item_get_string(item, "Name");
1657 dtype = purple_desktop_item_get_entry_type(item);
1658 switch (dtype) {
1659 PurpleConversation *conv;
1660 PidginConversation *gtkconv;
1662 case PURPLE_DESKTOP_ITEM_TYPE_LINK:
1663 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, who);
1664 gtkconv = PIDGIN_CONVERSATION(conv);
1665 gtk_imhtml_insert_link(GTK_IMHTML(gtkconv->entry),
1666 gtk_text_buffer_get_insert(GTK_IMHTML(gtkconv->entry)->text_buffer),
1667 purple_desktop_item_get_string(item, "URL"), itemname);
1668 break;
1669 default:
1670 /* I don't know if we really want to do anything here. Most of
1671 * the desktop item types are crap like "MIME Type" (I have no
1672 * clue how that would be a desktop item) and "Comment"...
1673 * nothing we can really send. The only logical one is
1674 * "Application," but do we really want to send a binary and
1675 * nothing else? Probably not. I'll just give an error and
1676 * return. */
1677 /* The original patch sent the icon used by the launcher. That's probably wrong */
1678 purple_notify_error(NULL, NULL, _("Cannot send launcher"),
1679 _("You dragged a desktop launcher. Most "
1680 "likely you wanted to send the target "
1681 "of this launcher instead of this "
1682 "launcher itself."));
1683 break;
1685 purple_desktop_item_unref(item);
1686 g_free(basename);
1687 while (files) {
1688 g_free(files->data);
1689 files = g_list_delete_link(files, files);
1691 return;
1693 #endif /* _WIN32 */
1695 /* Everything is fine, let's send */
1696 serv_send_file(gc, who, filename);
1699 g_free(filename);
1700 g_free(basename);
1703 void pidgin_buddy_icon_get_scale_size(GdkPixbuf *buf, PurpleBuddyIconSpec *spec, PurpleIconScaleRules rules, int *width, int *height)
1705 *width = gdk_pixbuf_get_width(buf);
1706 *height = gdk_pixbuf_get_height(buf);
1708 if ((spec == NULL) || !(spec->scale_rules & rules))
1709 return;
1711 purple_buddy_icon_get_scale_size(spec, width, height);
1713 /* and now for some arbitrary sanity checks */
1714 if(*width > 100)
1715 *width = 100;
1716 if(*height > 100)
1717 *height = 100;
1720 GdkPixbuf * pidgin_create_status_icon(PurpleStatusPrimitive prim, GtkWidget *w, const char *size)
1722 GtkIconSize icon_size = gtk_icon_size_from_name(size);
1723 GdkPixbuf *pixbuf = NULL;
1724 const char *stock = pidgin_stock_id_from_status_primitive(prim);
1726 pixbuf = gtk_widget_render_icon (w, stock ? stock : PIDGIN_STOCK_STATUS_AVAILABLE,
1727 icon_size, "GtkWidget");
1728 return pixbuf;
1731 static const char *
1732 stock_id_from_status_primitive_idle(PurpleStatusPrimitive prim, gboolean idle)
1734 const char *stock = NULL;
1735 switch (prim) {
1736 case PURPLE_STATUS_UNSET:
1737 stock = NULL;
1738 break;
1739 case PURPLE_STATUS_UNAVAILABLE:
1740 stock = idle ? PIDGIN_STOCK_STATUS_BUSY_I : PIDGIN_STOCK_STATUS_BUSY;
1741 break;
1742 case PURPLE_STATUS_AWAY:
1743 stock = idle ? PIDGIN_STOCK_STATUS_AWAY_I : PIDGIN_STOCK_STATUS_AWAY;
1744 break;
1745 case PURPLE_STATUS_EXTENDED_AWAY:
1746 stock = idle ? PIDGIN_STOCK_STATUS_XA_I : PIDGIN_STOCK_STATUS_XA;
1747 break;
1748 case PURPLE_STATUS_INVISIBLE:
1749 stock = PIDGIN_STOCK_STATUS_INVISIBLE;
1750 break;
1751 case PURPLE_STATUS_OFFLINE:
1752 stock = idle ? PIDGIN_STOCK_STATUS_OFFLINE_I : PIDGIN_STOCK_STATUS_OFFLINE;
1753 break;
1754 default:
1755 stock = idle ? PIDGIN_STOCK_STATUS_AVAILABLE_I : PIDGIN_STOCK_STATUS_AVAILABLE;
1756 break;
1758 return stock;
1761 const char *
1762 pidgin_stock_id_from_status_primitive(PurpleStatusPrimitive prim)
1764 return stock_id_from_status_primitive_idle(prim, FALSE);
1767 const char *
1768 pidgin_stock_id_from_presence(PurplePresence *presence)
1770 PurpleStatus *status;
1771 PurpleStatusType *type;
1772 PurpleStatusPrimitive prim;
1773 gboolean idle;
1775 g_return_val_if_fail(presence, NULL);
1777 status = purple_presence_get_active_status(presence);
1778 type = purple_status_get_type(status);
1779 prim = purple_status_type_get_primitive(type);
1781 idle = purple_presence_is_idle(presence);
1783 return stock_id_from_status_primitive_idle(prim, idle);
1786 GdkPixbuf *
1787 pidgin_create_prpl_icon(PurpleAccount *account, PidginPrplIconSize size)
1789 PurplePlugin *prpl;
1791 g_return_val_if_fail(account != NULL, NULL);
1793 prpl = purple_find_prpl(purple_account_get_protocol_id(account));
1794 if (prpl == NULL)
1795 return NULL;
1796 return pidgin_create_prpl_icon_from_prpl(prpl, size, account);
1799 static void
1800 menu_action_cb(GtkMenuItem *item, gpointer object)
1802 gpointer data;
1803 void (*callback)(gpointer, gpointer);
1805 callback = g_object_get_data(G_OBJECT(item), "purplecallback");
1806 data = g_object_get_data(G_OBJECT(item), "purplecallbackdata");
1808 if (callback)
1809 callback(object, data);
1812 GtkWidget *
1813 pidgin_append_menu_action(GtkWidget *menu, PurpleMenuAction *act,
1814 gpointer object)
1816 GtkWidget *menuitem;
1818 if (act == NULL) {
1819 return pidgin_separator(menu);
1822 if (act->children == NULL) {
1823 menuitem = gtk_menu_item_new_with_mnemonic(act->label);
1825 if (act->callback != NULL) {
1826 g_object_set_data(G_OBJECT(menuitem),
1827 "purplecallback",
1828 act->callback);
1829 g_object_set_data(G_OBJECT(menuitem),
1830 "purplecallbackdata",
1831 act->data);
1832 g_signal_connect(G_OBJECT(menuitem), "activate",
1833 G_CALLBACK(menu_action_cb),
1834 object);
1835 } else {
1836 gtk_widget_set_sensitive(menuitem, FALSE);
1839 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1840 } else {
1841 GList *l = NULL;
1842 GtkWidget *submenu = NULL;
1843 GtkAccelGroup *group;
1845 menuitem = gtk_menu_item_new_with_mnemonic(act->label);
1846 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1848 submenu = gtk_menu_new();
1849 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
1851 group = gtk_menu_get_accel_group(GTK_MENU(menu));
1852 if (group) {
1853 char *path = g_strdup_printf("%s/%s", GTK_MENU_ITEM(menuitem)->accel_path, act->label);
1854 gtk_menu_set_accel_path(GTK_MENU(submenu), path);
1855 g_free(path);
1856 gtk_menu_set_accel_group(GTK_MENU(submenu), group);
1859 for (l = act->children; l; l = l->next) {
1860 PurpleMenuAction *act = (PurpleMenuAction *)l->data;
1862 pidgin_append_menu_action(submenu, act, object);
1864 g_list_free(act->children);
1865 act->children = NULL;
1867 purple_menu_action_free(act);
1868 return menuitem;
1871 typedef struct
1873 GtkWidget *entry;
1874 GtkWidget *accountopt;
1876 PidginFilterBuddyCompletionEntryFunc filter_func;
1877 gpointer filter_func_user_data;
1879 GtkListStore *store;
1880 } PidginCompletionData;
1882 static gboolean buddyname_completion_match_func(GtkEntryCompletion *completion,
1883 const gchar *key, GtkTreeIter *iter, gpointer user_data)
1885 GtkTreeModel *model;
1886 GValue val1;
1887 GValue val2;
1888 const char *tmp;
1890 model = gtk_entry_completion_get_model (completion);
1892 val1.g_type = 0;
1893 gtk_tree_model_get_value(model, iter, 2, &val1);
1894 tmp = g_value_get_string(&val1);
1895 if (tmp != NULL && purple_str_has_prefix(tmp, key))
1897 g_value_unset(&val1);
1898 return TRUE;
1900 g_value_unset(&val1);
1902 val2.g_type = 0;
1903 gtk_tree_model_get_value(model, iter, 3, &val2);
1904 tmp = g_value_get_string(&val2);
1905 if (tmp != NULL && purple_str_has_prefix(tmp, key))
1907 g_value_unset(&val2);
1908 return TRUE;
1910 g_value_unset(&val2);
1912 return FALSE;
1915 static gboolean buddyname_completion_match_selected_cb(GtkEntryCompletion *completion,
1916 GtkTreeModel *model, GtkTreeIter *iter, PidginCompletionData *data)
1918 GValue val;
1919 GtkWidget *optmenu = data->accountopt;
1920 PurpleAccount *account;
1922 val.g_type = 0;
1923 gtk_tree_model_get_value(model, iter, 1, &val);
1924 gtk_entry_set_text(GTK_ENTRY(data->entry), g_value_get_string(&val));
1925 g_value_unset(&val);
1927 gtk_tree_model_get_value(model, iter, 4, &val);
1928 account = g_value_get_pointer(&val);
1929 g_value_unset(&val);
1931 if (account == NULL)
1932 return TRUE;
1934 if (optmenu != NULL)
1935 aop_option_menu_select_by_data(optmenu, account);
1937 return TRUE;
1940 static void
1941 add_buddyname_autocomplete_entry(GtkListStore *store, const char *buddy_alias, const char *contact_alias,
1942 const PurpleAccount *account, const char *buddyname)
1944 GtkTreeIter iter;
1945 gboolean completion_added = FALSE;
1946 gchar *normalized_buddyname;
1947 gchar *tmp;
1949 tmp = g_utf8_normalize(buddyname, -1, G_NORMALIZE_DEFAULT);
1950 normalized_buddyname = g_utf8_casefold(tmp, -1);
1951 g_free(tmp);
1953 /* There's no sense listing things like: 'xxx "xxx"'
1954 when the name and buddy alias match. */
1955 if (buddy_alias && !purple_strequal(buddy_alias, buddyname)) {
1956 char *completion_entry = g_strdup_printf("%s \"%s\"", buddyname, buddy_alias);
1957 char *tmp2 = g_utf8_normalize(buddy_alias, -1, G_NORMALIZE_DEFAULT);
1959 tmp = g_utf8_casefold(tmp2, -1);
1960 g_free(tmp2);
1962 gtk_list_store_append(store, &iter);
1963 gtk_list_store_set(store, &iter,
1964 0, completion_entry,
1965 1, buddyname,
1966 2, normalized_buddyname,
1967 3, tmp,
1968 4, account,
1969 -1);
1970 g_free(completion_entry);
1971 g_free(tmp);
1972 completion_added = TRUE;
1975 /* There's no sense listing things like: 'xxx "xxx"'
1976 when the name and contact alias match. */
1977 if (contact_alias && !purple_strequal(contact_alias, buddyname)) {
1978 /* We don't want duplicates when the contact and buddy alias match. */
1979 if (!purple_strequal(contact_alias, buddy_alias)) {
1980 char *completion_entry = g_strdup_printf("%s \"%s\"",
1981 buddyname, contact_alias);
1982 char *tmp2 = g_utf8_normalize(contact_alias, -1, G_NORMALIZE_DEFAULT);
1984 tmp = g_utf8_casefold(tmp2, -1);
1985 g_free(tmp2);
1987 gtk_list_store_append(store, &iter);
1988 gtk_list_store_set(store, &iter,
1989 0, completion_entry,
1990 1, buddyname,
1991 2, normalized_buddyname,
1992 3, tmp,
1993 4, account,
1994 -1);
1995 g_free(completion_entry);
1996 g_free(tmp);
1997 completion_added = TRUE;
2001 if (completion_added == FALSE) {
2002 /* Add the buddy's name. */
2003 gtk_list_store_append(store, &iter);
2004 gtk_list_store_set(store, &iter,
2005 0, buddyname,
2006 1, buddyname,
2007 2, normalized_buddyname,
2008 3, NULL,
2009 4, account,
2010 -1);
2013 g_free(normalized_buddyname);
2016 static void get_log_set_name(PurpleLogSet *set, gpointer value, PidginCompletionData *data)
2018 PidginFilterBuddyCompletionEntryFunc filter_func = data->filter_func;
2019 gpointer user_data = data->filter_func_user_data;
2021 /* 1. Don't show buddies because we will have gotten them already.
2022 * 2. The boxes that use this autocomplete code handle only IMs. */
2023 if (!set->buddy && set->type == PURPLE_LOG_IM) {
2024 PidginBuddyCompletionEntry entry;
2025 entry.is_buddy = FALSE;
2026 entry.entry.logged_buddy = set;
2028 if (filter_func(&entry, user_data)) {
2029 add_buddyname_autocomplete_entry(data->store,
2030 NULL, NULL, set->account, set->name);
2035 static void
2036 add_completion_list(PidginCompletionData *data)
2038 PurpleBlistNode *gnode, *cnode, *bnode;
2039 PidginFilterBuddyCompletionEntryFunc filter_func = data->filter_func;
2040 gpointer user_data = data->filter_func_user_data;
2041 GHashTable *sets;
2043 gtk_list_store_clear(data->store);
2045 for (gnode = purple_get_blist()->root; gnode != NULL; gnode = gnode->next)
2047 if (!PURPLE_BLIST_NODE_IS_GROUP(gnode))
2048 continue;
2050 for (cnode = gnode->child; cnode != NULL; cnode = cnode->next)
2052 if (!PURPLE_BLIST_NODE_IS_CONTACT(cnode))
2053 continue;
2055 for (bnode = cnode->child; bnode != NULL; bnode = bnode->next)
2057 PidginBuddyCompletionEntry entry;
2058 entry.is_buddy = TRUE;
2059 entry.entry.buddy = (PurpleBuddy *) bnode;
2061 if (filter_func(&entry, user_data)) {
2062 add_buddyname_autocomplete_entry(data->store,
2063 ((PurpleContact *)cnode)->alias,
2064 purple_buddy_get_contact_alias(entry.entry.buddy),
2065 entry.entry.buddy->account,
2066 entry.entry.buddy->name
2073 sets = purple_log_get_log_sets();
2074 g_hash_table_foreach(sets, (GHFunc)get_log_set_name, data);
2075 g_hash_table_destroy(sets);
2079 static void
2080 buddyname_autocomplete_destroyed_cb(GtkWidget *widget, gpointer data)
2082 g_free(data);
2083 purple_signals_disconnect_by_handle(widget);
2086 static void
2087 repopulate_autocomplete(gpointer something, gpointer data)
2089 add_completion_list(data);
2092 void
2093 pidgin_setup_screenname_autocomplete_with_filter(GtkWidget *entry, GtkWidget *accountopt, PidginFilterBuddyCompletionEntryFunc filter_func, gpointer user_data)
2095 PidginCompletionData *data;
2098 * Store the displayed completion value, the buddy name, the UTF-8
2099 * normalized & casefolded buddy name, the UTF-8 normalized &
2100 * casefolded value for comparison, and the account.
2102 GtkListStore *store;
2104 GtkEntryCompletion *completion;
2106 data = g_new0(PidginCompletionData, 1);
2107 store = gtk_list_store_new(5, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER);
2109 data->entry = entry;
2110 data->accountopt = accountopt;
2111 if (filter_func == NULL) {
2112 data->filter_func = pidgin_screenname_autocomplete_default_filter;
2113 data->filter_func_user_data = NULL;
2114 } else {
2115 data->filter_func = filter_func;
2116 data->filter_func_user_data = user_data;
2118 data->store = store;
2120 add_completion_list(data);
2122 /* Sort the completion list by buddy name */
2123 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store),
2124 1, GTK_SORT_ASCENDING);
2126 completion = gtk_entry_completion_new();
2127 gtk_entry_completion_set_match_func(completion, buddyname_completion_match_func, NULL, NULL);
2129 g_signal_connect(G_OBJECT(completion), "match-selected",
2130 G_CALLBACK(buddyname_completion_match_selected_cb), data);
2132 gtk_entry_set_completion(GTK_ENTRY(entry), completion);
2133 g_object_unref(completion);
2135 gtk_entry_completion_set_model(completion, GTK_TREE_MODEL(store));
2136 g_object_unref(store);
2138 gtk_entry_completion_set_text_column(completion, 0);
2140 purple_signal_connect(purple_connections_get_handle(), "signed-on", entry,
2141 PURPLE_CALLBACK(repopulate_autocomplete), data);
2142 purple_signal_connect(purple_connections_get_handle(), "signed-off", entry,
2143 PURPLE_CALLBACK(repopulate_autocomplete), data);
2145 purple_signal_connect(purple_accounts_get_handle(), "account-added", entry,
2146 PURPLE_CALLBACK(repopulate_autocomplete), data);
2147 purple_signal_connect(purple_accounts_get_handle(), "account-removed", entry,
2148 PURPLE_CALLBACK(repopulate_autocomplete), data);
2150 g_signal_connect(G_OBJECT(entry), "destroy", G_CALLBACK(buddyname_autocomplete_destroyed_cb), data);
2153 gboolean
2154 pidgin_screenname_autocomplete_default_filter(const PidginBuddyCompletionEntry *completion_entry, gpointer all_accounts) {
2155 gboolean all = GPOINTER_TO_INT(all_accounts);
2157 if (completion_entry->is_buddy) {
2158 return all || purple_account_is_connected(completion_entry->entry.buddy->account);
2159 } else {
2160 return all || (completion_entry->entry.logged_buddy->account != NULL && purple_account_is_connected(completion_entry->entry.logged_buddy->account));
2164 void
2165 pidgin_setup_screenname_autocomplete(GtkWidget *entry, GtkWidget *accountopt, gboolean all) {
2166 pidgin_setup_screenname_autocomplete_with_filter(entry, accountopt, pidgin_screenname_autocomplete_default_filter, GINT_TO_POINTER(all));
2171 void pidgin_set_cursor(GtkWidget *widget, GdkCursorType cursor_type)
2173 GdkCursor *cursor;
2175 g_return_if_fail(widget != NULL);
2176 if (widget->window == NULL)
2177 return;
2179 cursor = gdk_cursor_new(cursor_type);
2180 gdk_window_set_cursor(widget->window, cursor);
2181 gdk_cursor_unref(cursor);
2183 gdk_display_flush(gdk_drawable_get_display(GDK_DRAWABLE(widget->window)));
2186 void pidgin_clear_cursor(GtkWidget *widget)
2188 g_return_if_fail(widget != NULL);
2189 if (widget->window == NULL)
2190 return;
2192 gdk_window_set_cursor(widget->window, NULL);
2195 struct _icon_chooser {
2196 GtkWidget *icon_filesel;
2197 GtkWidget *icon_preview;
2198 GtkWidget *icon_text;
2200 void (*callback)(const char*,gpointer);
2201 gpointer data;
2204 static void
2205 icon_filesel_choose_cb(GtkWidget *widget, gint response, struct _icon_chooser *dialog)
2207 char *filename, *current_folder;
2209 if (response != GTK_RESPONSE_ACCEPT) {
2210 if (response == GTK_RESPONSE_CANCEL) {
2211 gtk_widget_destroy(dialog->icon_filesel);
2213 dialog->icon_filesel = NULL;
2214 if (dialog->callback)
2215 dialog->callback(NULL, dialog->data);
2216 g_free(dialog);
2217 return;
2220 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog->icon_filesel));
2221 current_folder = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(dialog->icon_filesel));
2222 if (current_folder != NULL) {
2223 purple_prefs_set_path(PIDGIN_PREFS_ROOT "/filelocations/last_icon_folder", current_folder);
2224 g_free(current_folder);
2228 if (dialog->callback)
2229 dialog->callback(filename, dialog->data);
2230 gtk_widget_destroy(dialog->icon_filesel);
2231 g_free(filename);
2232 g_free(dialog);
2236 static void
2237 icon_preview_change_cb(GtkFileChooser *widget, struct _icon_chooser *dialog)
2239 GdkPixbuf *pixbuf;
2240 int height, width;
2241 char *basename, *markup, *size;
2242 struct stat st;
2243 char *filename;
2245 filename = gtk_file_chooser_get_preview_filename(
2246 GTK_FILE_CHOOSER(dialog->icon_filesel));
2248 if (!filename || g_stat(filename, &st) || !(pixbuf = pidgin_pixbuf_new_from_file_at_size(filename, 128, 128)))
2250 gtk_image_set_from_pixbuf(GTK_IMAGE(dialog->icon_preview), NULL);
2251 gtk_label_set_markup(GTK_LABEL(dialog->icon_text), "");
2252 g_free(filename);
2253 return;
2256 gdk_pixbuf_get_file_info(filename, &width, &height);
2257 basename = g_path_get_basename(filename);
2258 size = purple_str_size_to_units(st.st_size);
2259 markup = g_strdup_printf(_("<b>File:</b> %s\n"
2260 "<b>File size:</b> %s\n"
2261 "<b>Image size:</b> %dx%d"),
2262 basename, size, width, height);
2264 gtk_image_set_from_pixbuf(GTK_IMAGE(dialog->icon_preview), pixbuf);
2265 gtk_label_set_markup(GTK_LABEL(dialog->icon_text), markup);
2267 g_object_unref(G_OBJECT(pixbuf));
2268 g_free(filename);
2269 g_free(basename);
2270 g_free(size);
2271 g_free(markup);
2275 GtkWidget *pidgin_buddy_icon_chooser_new(GtkWindow *parent, void(*callback)(const char *, gpointer), gpointer data) {
2276 struct _icon_chooser *dialog = g_new0(struct _icon_chooser, 1);
2278 GtkWidget *vbox;
2279 const char *current_folder;
2281 dialog->callback = callback;
2282 dialog->data = data;
2284 current_folder = purple_prefs_get_path(PIDGIN_PREFS_ROOT "/filelocations/last_icon_folder");
2286 dialog->icon_filesel = gtk_file_chooser_dialog_new(_("Buddy Icon"),
2287 parent,
2288 GTK_FILE_CHOOSER_ACTION_OPEN,
2289 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2290 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
2291 NULL);
2292 gtk_dialog_set_default_response(GTK_DIALOG(dialog->icon_filesel), GTK_RESPONSE_ACCEPT);
2293 if ((current_folder != NULL) && (*current_folder != '\0'))
2294 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog->icon_filesel),
2295 current_folder);
2297 dialog->icon_preview = gtk_image_new();
2298 dialog->icon_text = gtk_label_new(NULL);
2300 vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
2301 gtk_widget_set_size_request(GTK_WIDGET(vbox), -1, 50);
2302 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(dialog->icon_preview), TRUE, FALSE, 0);
2303 gtk_box_pack_end(GTK_BOX(vbox), GTK_WIDGET(dialog->icon_text), FALSE, FALSE, 0);
2304 gtk_widget_show_all(vbox);
2306 gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog->icon_filesel), vbox);
2307 gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(dialog->icon_filesel), TRUE);
2308 gtk_file_chooser_set_use_preview_label(GTK_FILE_CHOOSER(dialog->icon_filesel), FALSE);
2310 g_signal_connect(G_OBJECT(dialog->icon_filesel), "update-preview",
2311 G_CALLBACK(icon_preview_change_cb), dialog);
2312 g_signal_connect(G_OBJECT(dialog->icon_filesel), "response",
2313 G_CALLBACK(icon_filesel_choose_cb), dialog);
2314 icon_preview_change_cb(NULL, dialog);
2316 #ifdef _WIN32
2317 g_signal_connect(G_OBJECT(dialog->icon_filesel), "show",
2318 G_CALLBACK(winpidgin_ensure_onscreen), dialog->icon_filesel);
2319 #endif
2321 return dialog->icon_filesel;
2325 * @return True if any string from array a exists in array b.
2327 static gboolean
2328 str_array_match(char **a, char **b)
2330 int i, j;
2332 if (!a || !b)
2333 return FALSE;
2334 for (i = 0; a[i] != NULL; i++)
2335 for (j = 0; b[j] != NULL; j++)
2336 if (!g_ascii_strcasecmp(a[i], b[j]))
2337 return TRUE;
2338 return FALSE;
2341 gpointer
2342 pidgin_convert_buddy_icon(PurplePlugin *plugin, const char *path, size_t *len)
2344 PurplePluginProtocolInfo *prpl_info;
2345 PurpleBuddyIconSpec *spec;
2346 int orig_width, orig_height, new_width, new_height;
2347 GdkPixbufFormat *format;
2348 char **pixbuf_formats;
2349 char **prpl_formats;
2350 GError *error = NULL;
2351 gchar *contents;
2352 gsize length;
2353 GdkPixbuf *pixbuf, *original;
2354 float scale_factor;
2355 int i;
2356 gchar *tmp;
2358 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin);
2359 spec = &prpl_info->icon_spec;
2360 g_return_val_if_fail(spec->format != NULL, NULL);
2362 format = gdk_pixbuf_get_file_info(path, &orig_width, &orig_height);
2363 if (format == NULL) {
2364 purple_debug_warning("buddyicon", "Could not get file info of %s\n", path);
2365 return NULL;
2368 pixbuf_formats = gdk_pixbuf_format_get_extensions(format);
2369 prpl_formats = g_strsplit(spec->format, ",", 0);
2371 if (str_array_match(pixbuf_formats, prpl_formats) && /* This is an acceptable format AND */
2372 (!(spec->scale_rules & PURPLE_ICON_SCALE_SEND) || /* The prpl doesn't scale before it sends OR */
2373 (spec->min_width <= orig_width && spec->max_width >= orig_width &&
2374 spec->min_height <= orig_height && spec->max_height >= orig_height))) /* The icon is the correct size */
2376 g_strfreev(pixbuf_formats);
2378 if (!g_file_get_contents(path, &contents, &length, &error)) {
2379 purple_debug_warning("buddyicon", "Could not get file contents "
2380 "of %s: %s\n", path, error->message);
2381 g_strfreev(prpl_formats);
2382 return NULL;
2385 if (spec->max_filesize == 0 || length < spec->max_filesize) {
2386 /* The supplied image fits the file size, dimensions and type
2387 constraints. Great! Return it without making any changes. */
2388 if (len)
2389 *len = length;
2390 g_strfreev(prpl_formats);
2391 return contents;
2394 /* The image was too big. Fall-through and try scaling it down. */
2395 g_free(contents);
2396 } else {
2397 g_strfreev(pixbuf_formats);
2400 /* The original image wasn't compatible. Scale it or convert file type. */
2401 pixbuf = gdk_pixbuf_new_from_file(path, &error);
2402 if (error) {
2403 purple_debug_warning("buddyicon", "Could not open icon '%s' for "
2404 "conversion: %s\n", path, error->message);
2405 g_error_free(error);
2406 g_strfreev(prpl_formats);
2407 return NULL;
2409 original = g_object_ref(G_OBJECT(pixbuf));
2411 new_width = orig_width;
2412 new_height = orig_height;
2414 /* Make sure the image is the correct dimensions */
2415 if (spec->scale_rules & PURPLE_ICON_SCALE_SEND &&
2416 (orig_width < spec->min_width || orig_width > spec->max_width ||
2417 orig_height < spec->min_height || orig_height > spec->max_height))
2419 purple_buddy_icon_get_scale_size(spec, &new_width, &new_height);
2421 g_object_unref(G_OBJECT(pixbuf));
2422 pixbuf = gdk_pixbuf_scale_simple(original, new_width, new_height, GDK_INTERP_HYPER);
2425 scale_factor = 1;
2426 do {
2427 for (i = 0; prpl_formats[i]; i++) {
2428 int quality = 100;
2429 do {
2430 const char *key = NULL;
2431 const char *value = NULL;
2432 gchar tmp_buf[4];
2434 purple_debug_info("buddyicon", "Converting buddy icon to %s\n", prpl_formats[i]);
2436 if (purple_strequal(prpl_formats[i], "png")) {
2437 key = "compression";
2438 value = "9";
2439 } else if (purple_strequal(prpl_formats[i], "jpeg")) {
2440 sprintf(tmp_buf, "%u", quality);
2441 key = "quality";
2442 value = tmp_buf;
2445 if (!gdk_pixbuf_save_to_buffer(pixbuf, &contents, &length,
2446 prpl_formats[i], &error, key, value, NULL))
2448 /* The NULL checking of error is necessary due to this bug:
2449 * http://bugzilla.gnome.org/show_bug.cgi?id=405539 */
2450 purple_debug_warning("buddyicon",
2451 "Could not convert to %s: %s\n", prpl_formats[i],
2452 (error && error->message) ? error->message : "Unknown error");
2453 g_error_free(error);
2454 error = NULL;
2456 /* We couldn't convert to this image type. Try the next
2457 image type. */
2458 break;
2461 if (spec->max_filesize == 0 || length <= spec->max_filesize) {
2462 /* We were able to save the image as this image type and
2463 have it be within the size constraints. Great! Return
2464 the image. */
2465 purple_debug_info("buddyicon", "Converted image from "
2466 "%dx%d to %dx%d, format=%s, quality=%u, "
2467 "filesize=%zu\n", orig_width, orig_height,
2468 new_width, new_height, prpl_formats[i], quality,
2469 length);
2470 if (len)
2471 *len = length;
2472 g_strfreev(prpl_formats);
2473 g_object_unref(G_OBJECT(pixbuf));
2474 g_object_unref(G_OBJECT(original));
2475 return contents;
2478 g_free(contents);
2480 if (!purple_strequal(prpl_formats[i], "jpeg")) {
2481 /* File size was too big and we can't lower the quality,
2482 so skip to the next image type. */
2483 break;
2486 /* File size was too big, but we're dealing with jpeg so try
2487 lowering the quality. */
2488 quality -= 5;
2489 } while (quality >= 70);
2492 /* We couldn't save the image in any format that was below the max
2493 file size. Maybe we can reduce the image dimensions? */
2494 scale_factor *= 0.8;
2495 new_width = orig_width * scale_factor;
2496 new_height = orig_height * scale_factor;
2497 g_object_unref(G_OBJECT(pixbuf));
2498 pixbuf = gdk_pixbuf_scale_simple(original, new_width, new_height, GDK_INTERP_HYPER);
2499 } while ((new_width > 10 || new_height > 10) && new_width > spec->min_width && new_height > spec->min_height);
2500 g_strfreev(prpl_formats);
2501 g_object_unref(G_OBJECT(pixbuf));
2502 g_object_unref(G_OBJECT(original));
2504 tmp = g_strdup_printf(_("The file '%s' is too large for %s. Please try a smaller image.\n"),
2505 path, plugin->info->name);
2506 purple_notify_error(NULL, _("Icon Error"), _("Could not set icon"), tmp);
2507 g_free(tmp);
2509 return NULL;
2512 void pidgin_set_custom_buddy_icon(PurpleAccount *account, const char *who, const char *filename)
2514 PurpleBuddy *buddy;
2515 PurpleContact *contact;
2517 buddy = purple_find_buddy(account, who);
2518 if (!buddy) {
2519 purple_debug_info("custom-icon", "You can only set custom icon for someone in your buddylist.\n");
2520 return;
2523 contact = purple_buddy_get_contact(buddy);
2524 purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode*)contact, filename);
2527 char *pidgin_make_pretty_arrows(const char *str)
2529 char *ret;
2530 char **split = g_strsplit(str, "->", -1);
2531 ret = g_strjoinv("\342\207\250", split);
2532 g_strfreev(split);
2534 split = g_strsplit(ret, "<-", -1);
2535 g_free(ret);
2536 ret = g_strjoinv("\342\207\246", split);
2537 g_strfreev(split);
2539 return ret;
2542 void pidgin_set_urgent(GtkWindow *window, gboolean urgent)
2544 #if defined _WIN32
2545 winpidgin_window_flash(window, urgent);
2546 #else
2547 gtk_window_set_urgency_hint(window, urgent);
2548 #endif
2551 static GSList *minidialogs = NULL;
2553 static void *
2554 pidgin_utils_get_handle(void)
2556 static int handle;
2558 return &handle;
2561 static void connection_signed_off_cb(PurpleConnection *gc)
2563 GSList *list, *l_next;
2564 for (list = minidialogs; list; list = l_next) {
2565 l_next = list->next;
2566 if (g_object_get_data(G_OBJECT(list->data), "gc") == gc) {
2567 gtk_widget_destroy(GTK_WIDGET(list->data));
2572 static void alert_killed_cb(GtkWidget *widget)
2574 minidialogs = g_slist_remove(minidialogs, widget);
2577 struct _old_button_clicked_cb_data
2579 PidginUtilMiniDialogCallback cb;
2580 gpointer data;
2583 static void
2584 old_mini_dialog_button_clicked_cb(PidginMiniDialog *mini_dialog,
2585 GtkButton *button,
2586 gpointer user_data)
2588 struct _old_button_clicked_cb_data *data = user_data;
2589 data->cb(data->data, button);
2592 static void
2593 old_mini_dialog_destroy_cb(GtkWidget *dialog,
2594 GList *cb_datas)
2596 while (cb_datas != NULL)
2598 g_free(cb_datas->data);
2599 cb_datas = g_list_delete_link(cb_datas, cb_datas);
2603 static void
2604 mini_dialog_init(PidginMiniDialog *mini_dialog, PurpleConnection *gc, void *user_data, va_list args)
2606 const char *button_text;
2607 GList *cb_datas = NULL;
2608 static gboolean first_call = TRUE;
2610 if (first_call) {
2611 first_call = FALSE;
2612 purple_signal_connect(purple_connections_get_handle(), "signed-off",
2613 pidgin_utils_get_handle(),
2614 PURPLE_CALLBACK(connection_signed_off_cb), NULL);
2617 g_object_set_data(G_OBJECT(mini_dialog), "gc" ,gc);
2618 g_signal_connect(G_OBJECT(mini_dialog), "destroy",
2619 G_CALLBACK(alert_killed_cb), NULL);
2621 while ((button_text = va_arg(args, char*))) {
2622 struct _old_button_clicked_cb_data *data = NULL;
2623 PidginMiniDialogCallback wrapper_cb = NULL;
2624 PidginUtilMiniDialogCallback callback =
2625 va_arg(args, PidginUtilMiniDialogCallback);
2627 if (callback != NULL) {
2628 data = g_new0(struct _old_button_clicked_cb_data, 1);
2629 data->cb = callback;
2630 data->data = user_data;
2631 wrapper_cb = old_mini_dialog_button_clicked_cb;
2633 pidgin_mini_dialog_add_button(mini_dialog, button_text,
2634 wrapper_cb, data);
2635 cb_datas = g_list_append(cb_datas, data);
2638 g_signal_connect(G_OBJECT(mini_dialog), "destroy",
2639 G_CALLBACK(old_mini_dialog_destroy_cb), cb_datas);
2642 #define INIT_AND_RETURN_MINI_DIALOG(mini_dialog) \
2643 va_list args; \
2644 va_start(args, user_data); \
2645 mini_dialog_init(mini_dialog, gc, user_data, args); \
2646 va_end(args); \
2647 return GTK_WIDGET(mini_dialog);
2649 GtkWidget *
2650 pidgin_make_mini_dialog(PurpleConnection *gc,
2651 const char *icon_name,
2652 const char *primary,
2653 const char *secondary,
2654 void *user_data,
2655 ...)
2657 PidginMiniDialog *mini_dialog = pidgin_mini_dialog_new(primary, secondary, icon_name);
2658 INIT_AND_RETURN_MINI_DIALOG(mini_dialog);
2661 GtkWidget *
2662 pidgin_make_mini_dialog_with_custom_icon(PurpleConnection *gc,
2663 GdkPixbuf *custom_icon,
2664 const char *primary,
2665 const char *secondary,
2666 void *user_data,
2667 ...)
2669 PidginMiniDialog *mini_dialog = pidgin_mini_dialog_new_with_custom_icon(primary, secondary, custom_icon);
2670 INIT_AND_RETURN_MINI_DIALOG(mini_dialog);
2674 * "This is so dead sexy."
2675 * "Two thumbs up."
2676 * "Best movie of the year."
2678 * This is the function that handles CTRL+F searching in the buddy list.
2679 * It finds the top-most buddy/group/chat/whatever containing the
2680 * entered string.
2682 * It's somewhat ineffecient, because we strip all the HTML from the
2683 * "name" column of the buddy list (because the GtkTreeModel does not
2684 * contain the screen name in a non-markedup format). But the alternative
2685 * is to add an extra column to the GtkTreeModel. And this function is
2686 * used rarely, so it shouldn't matter TOO much.
2688 gboolean pidgin_tree_view_search_equal_func(GtkTreeModel *model, gint column,
2689 const gchar *key, GtkTreeIter *iter, gpointer data)
2691 gchar *enteredstring;
2692 gchar *tmp;
2693 gchar *withmarkup;
2694 gchar *nomarkup;
2695 gchar *normalized;
2696 gboolean result;
2697 size_t i;
2698 size_t len;
2699 PangoLogAttr *log_attrs;
2700 gchar *word;
2702 if (g_ascii_strcasecmp(key, "Global Thermonuclear War") == 0)
2704 purple_notify_info(NULL, "WOPR",
2705 "Wouldn't you prefer a nice game of chess?", NULL);
2706 return FALSE;
2709 gtk_tree_model_get(model, iter, column, &withmarkup, -1);
2710 if (withmarkup == NULL) /* This is probably a separator */
2711 return TRUE;
2713 tmp = g_utf8_normalize(key, -1, G_NORMALIZE_DEFAULT);
2714 enteredstring = g_utf8_casefold(tmp, -1);
2715 g_free(tmp);
2717 nomarkup = purple_markup_strip_html(withmarkup);
2718 tmp = g_utf8_normalize(nomarkup, -1, G_NORMALIZE_DEFAULT);
2719 g_free(nomarkup);
2720 normalized = g_utf8_casefold(tmp, -1);
2721 g_free(tmp);
2723 if (purple_str_has_prefix(normalized, enteredstring))
2725 g_free(withmarkup);
2726 g_free(enteredstring);
2727 g_free(normalized);
2728 return FALSE;
2732 /* Use Pango to separate by words. */
2733 len = g_utf8_strlen(normalized, -1);
2734 log_attrs = g_new(PangoLogAttr, len + 1);
2736 pango_get_log_attrs(normalized, strlen(normalized), -1, NULL, log_attrs, len + 1);
2738 word = normalized;
2739 result = TRUE;
2740 for (i = 0; i < (len - 1) ; i++)
2742 if (log_attrs[i].is_word_start &&
2743 purple_str_has_prefix(word, enteredstring))
2745 result = FALSE;
2746 break;
2748 word = g_utf8_next_char(word);
2750 g_free(log_attrs);
2752 /* The non-Pango version. */
2753 #if 0
2754 word = normalized;
2755 result = TRUE;
2756 while (word[0] != '\0')
2758 gunichar c = g_utf8_get_char(word);
2759 if (!g_unichar_isalnum(c))
2761 word = g_utf8_find_next_char(word, NULL);
2762 if (purple_str_has_prefix(word, enteredstring))
2764 result = FALSE;
2765 break;
2768 else
2769 word = g_utf8_find_next_char(word, NULL);
2771 #endif
2773 g_free(withmarkup);
2774 g_free(enteredstring);
2775 g_free(normalized);
2777 return result;
2781 gboolean pidgin_gdk_pixbuf_is_opaque(GdkPixbuf *pixbuf) {
2782 int height, rowstride, i;
2783 unsigned char *pixels;
2784 unsigned char *row;
2786 if (!gdk_pixbuf_get_has_alpha(pixbuf))
2787 return TRUE;
2789 height = gdk_pixbuf_get_height (pixbuf);
2790 rowstride = gdk_pixbuf_get_rowstride (pixbuf);
2791 pixels = gdk_pixbuf_get_pixels (pixbuf);
2793 row = pixels;
2794 for (i = 3; i < rowstride; i+=4) {
2795 if (row[i] < 0xfe)
2796 return FALSE;
2799 for (i = 1; i < height - 1; i++) {
2800 row = pixels + (i * rowstride);
2801 if (row[3] < 0xfe || row[rowstride - 1] < 0xfe) {
2802 return FALSE;
2806 row = pixels + ((height - 1) * rowstride);
2807 for (i = 3; i < rowstride; i += 4) {
2808 if (row[i] < 0xfe)
2809 return FALSE;
2812 return TRUE;
2815 void pidgin_gdk_pixbuf_make_round(GdkPixbuf *pixbuf) {
2816 int width, height, rowstride;
2817 guchar *pixels;
2818 if (!gdk_pixbuf_get_has_alpha(pixbuf))
2819 return;
2820 width = gdk_pixbuf_get_width(pixbuf);
2821 height = gdk_pixbuf_get_height(pixbuf);
2822 rowstride = gdk_pixbuf_get_rowstride(pixbuf);
2823 pixels = gdk_pixbuf_get_pixels(pixbuf);
2825 if (width < 6 || height < 6)
2826 return;
2827 /* Top left */
2828 pixels[3] = 0;
2829 pixels[7] = 0x80;
2830 pixels[11] = 0xC0;
2831 pixels[rowstride + 3] = 0x80;
2832 pixels[rowstride * 2 + 3] = 0xC0;
2834 /* Top right */
2835 pixels[width * 4 - 1] = 0;
2836 pixels[width * 4 - 5] = 0x80;
2837 pixels[width * 4 - 9] = 0xC0;
2838 pixels[rowstride + (width * 4) - 1] = 0x80;
2839 pixels[(2 * rowstride) + (width * 4) - 1] = 0xC0;
2841 /* Bottom left */
2842 pixels[(height - 1) * rowstride + 3] = 0;
2843 pixels[(height - 1) * rowstride + 7] = 0x80;
2844 pixels[(height - 1) * rowstride + 11] = 0xC0;
2845 pixels[(height - 2) * rowstride + 3] = 0x80;
2846 pixels[(height - 3) * rowstride + 3] = 0xC0;
2848 /* Bottom right */
2849 pixels[height * rowstride - 1] = 0;
2850 pixels[(height - 1) * rowstride - 1] = 0x80;
2851 pixels[(height - 2) * rowstride - 1] = 0xC0;
2852 pixels[height * rowstride - 5] = 0x80;
2853 pixels[height * rowstride - 9] = 0xC0;
2856 const char *pidgin_get_dim_grey_string(GtkWidget *widget) {
2857 static char dim_grey_string[8] = "";
2858 GtkStyle *style;
2860 if (!widget)
2861 return "dim grey";
2863 style = gtk_widget_get_style(widget);
2864 if (!style)
2865 return "dim grey";
2867 snprintf(dim_grey_string, sizeof(dim_grey_string), "#%02x%02x%02x",
2868 style->text_aa[GTK_STATE_NORMAL].red >> 8,
2869 style->text_aa[GTK_STATE_NORMAL].green >> 8,
2870 style->text_aa[GTK_STATE_NORMAL].blue >> 8);
2871 return dim_grey_string;
2874 static void
2875 combo_box_changed_cb(GtkComboBox *combo_box, GtkEntry *entry)
2877 char *text = gtk_combo_box_get_active_text(combo_box);
2878 gtk_entry_set_text(entry, text ? text : "");
2879 g_free(text);
2882 static gboolean
2883 entry_key_pressed_cb(GtkWidget *entry, GdkEventKey *key, GtkComboBox *combo)
2885 if (key->keyval == GDK_Down || key->keyval == GDK_Up) {
2886 gtk_combo_box_popup(combo);
2887 return TRUE;
2889 return FALSE;
2892 GtkWidget *
2893 pidgin_text_combo_box_entry_new(const char *default_item, GList *items)
2895 GtkComboBox *ret = NULL;
2896 GtkWidget *the_entry = NULL;
2898 ret = GTK_COMBO_BOX(gtk_combo_box_entry_new_text());
2899 the_entry = gtk_entry_new();
2900 gtk_container_add(GTK_CONTAINER(ret), the_entry);
2902 if (default_item)
2903 gtk_entry_set_text(GTK_ENTRY(the_entry), default_item);
2905 for (; items != NULL ; items = items->next) {
2906 char *text = items->data;
2907 if (text && *text)
2908 gtk_combo_box_append_text(ret, text);
2911 g_signal_connect(G_OBJECT(ret), "changed", (GCallback)combo_box_changed_cb, the_entry);
2912 g_signal_connect_after(G_OBJECT(the_entry), "key-press-event", G_CALLBACK(entry_key_pressed_cb), ret);
2914 return GTK_WIDGET(ret);
2917 const char *pidgin_text_combo_box_entry_get_text(GtkWidget *widget)
2919 return gtk_entry_get_text(GTK_ENTRY(GTK_BIN((widget))->child));
2922 void pidgin_text_combo_box_entry_set_text(GtkWidget *widget, const char *text)
2924 gtk_entry_set_text(GTK_ENTRY(GTK_BIN((widget))->child), (text));
2927 GtkWidget *
2928 pidgin_add_widget_to_vbox(GtkBox *vbox, const char *widget_label, GtkSizeGroup *sg, GtkWidget *widget, gboolean expand, GtkWidget **p_label)
2930 GtkWidget *hbox;
2931 GtkWidget *label = NULL;
2933 if (widget_label) {
2934 hbox = gtk_hbox_new(FALSE, 5);
2935 gtk_widget_show(hbox);
2936 gtk_box_pack_start(vbox, hbox, FALSE, FALSE, 0);
2938 label = gtk_label_new_with_mnemonic(widget_label);
2939 gtk_widget_show(label);
2940 if (sg) {
2941 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
2942 gtk_size_group_add_widget(sg, label);
2944 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
2945 } else {
2946 hbox = GTK_WIDGET(vbox);
2949 gtk_widget_show(widget);
2950 gtk_box_pack_start(GTK_BOX(hbox), widget, expand, TRUE, 0);
2951 if (label) {
2952 gtk_label_set_mnemonic_widget(GTK_LABEL(label), widget);
2953 pidgin_set_accessible_label (widget, label);
2956 if (p_label)
2957 (*p_label) = label;
2958 return hbox;
2961 gboolean pidgin_auto_parent_window(GtkWidget *widget)
2963 #if 0
2964 /* This looks at the most recent window that received focus, and makes
2965 * that the parent window. */
2966 #ifndef _WIN32
2967 static GdkAtom _WindowTime = GDK_NONE;
2968 static GdkAtom _Cardinal = GDK_NONE;
2969 GList *windows = NULL;
2970 GtkWidget *parent = NULL;
2971 time_t window_time = 0;
2973 windows = gtk_window_list_toplevels();
2975 if (_WindowTime == GDK_NONE) {
2976 _WindowTime = gdk_x11_xatom_to_atom(gdk_x11_get_xatom_by_name("_NET_WM_USER_TIME"));
2978 if (_Cardinal == GDK_NONE) {
2979 _Cardinal = gdk_atom_intern("CARDINAL", FALSE);
2982 while (windows) {
2983 GtkWidget *window = windows->data;
2984 guchar *data = NULL;
2985 int al = 0;
2986 time_t value;
2988 windows = g_list_delete_link(windows, windows);
2990 if (window == widget ||
2991 !GTK_WIDGET_VISIBLE(window))
2992 continue;
2994 if (!gdk_property_get(window->window, _WindowTime, _Cardinal, 0, sizeof(time_t), FALSE,
2995 NULL, NULL, &al, &data))
2996 continue;
2997 value = *(time_t *)data;
2998 if (window_time < value) {
2999 window_time = value;
3000 parent = window;
3002 g_free(data);
3004 if (windows)
3005 g_list_free(windows);
3006 if (parent) {
3007 if (!gtk_get_current_event() && gtk_window_has_toplevel_focus(GTK_WINDOW(parent))) {
3008 /* The window is in focus, and the new window was not triggered by a keypress/click
3009 * event. So do not set it transient, to avoid focus stealing and all that.
3011 return FALSE;
3013 gtk_window_set_transient_for(GTK_WINDOW(widget), GTK_WINDOW(parent));
3014 return TRUE;
3016 return FALSE;
3017 #endif
3018 #else
3019 /* This finds the currently active window and makes that the parent window. */
3020 GList *windows = NULL;
3021 GtkWidget *parent = NULL;
3022 GdkEvent *event = gtk_get_current_event();
3023 GdkWindow *menu = NULL;
3025 if (event == NULL)
3026 /* The window was not triggered by a user action. */
3027 return FALSE;
3029 /* We need to special case events from a popup menu. */
3030 if (event->type == GDK_BUTTON_RELEASE) {
3031 /* XXX: Neither of the following works:
3032 menu = event->button.window;
3033 menu = gdk_window_get_parent(event->button.window);
3034 menu = gdk_window_get_toplevel(event->button.window);
3036 } else if (event->type == GDK_KEY_PRESS)
3037 menu = event->key.window;
3039 windows = gtk_window_list_toplevels();
3040 while (windows) {
3041 GtkWidget *window = windows->data;
3042 windows = g_list_delete_link(windows, windows);
3044 if (window == widget ||
3045 !GTK_WIDGET_VISIBLE(window)) {
3046 continue;
3049 if (gtk_window_has_toplevel_focus(GTK_WINDOW(window)) ||
3050 (menu && menu == window->window)) {
3051 parent = window;
3052 break;
3055 if (windows)
3056 g_list_free(windows);
3057 if (parent) {
3058 gtk_window_set_transient_for(GTK_WINDOW(widget), GTK_WINDOW(parent));
3059 return TRUE;
3061 return FALSE;
3062 #endif
3065 static GObject *pidgin_pixbuf_from_data_helper(const guchar *buf, gsize count, gboolean animated)
3067 GObject *pixbuf;
3068 GdkPixbufLoader *loader;
3069 GError *error = NULL;
3071 loader = gdk_pixbuf_loader_new();
3073 if (!gdk_pixbuf_loader_write(loader, buf, count, &error) || error) {
3074 purple_debug_warning("gtkutils", "gdk_pixbuf_loader_write() "
3075 "failed with size=%zu: %s\n", count,
3076 error ? error->message : "(no error message)");
3077 if (error)
3078 g_error_free(error);
3079 g_object_unref(G_OBJECT(loader));
3080 return NULL;
3083 if (!gdk_pixbuf_loader_close(loader, &error) || error) {
3084 purple_debug_warning("gtkutils", "gdk_pixbuf_loader_close() "
3085 "failed for image of size %zu: %s\n", count,
3086 error ? error->message : "(no error message)");
3087 if (error)
3088 g_error_free(error);
3089 g_object_unref(G_OBJECT(loader));
3090 return NULL;
3093 if (animated)
3094 pixbuf = G_OBJECT(gdk_pixbuf_loader_get_animation(loader));
3095 else
3096 pixbuf = G_OBJECT(gdk_pixbuf_loader_get_pixbuf(loader));
3097 if (!pixbuf) {
3098 purple_debug_warning("gtkutils", "%s() returned NULL for image "
3099 "of size %zu\n",
3100 animated ? "gdk_pixbuf_loader_get_animation"
3101 : "gdk_pixbuf_loader_get_pixbuf", count);
3102 g_object_unref(G_OBJECT(loader));
3103 return NULL;
3106 g_object_ref(pixbuf);
3107 g_object_unref(G_OBJECT(loader));
3109 return pixbuf;
3112 GdkPixbuf *pidgin_pixbuf_from_data(const guchar *buf, gsize count)
3114 return GDK_PIXBUF(pidgin_pixbuf_from_data_helper(buf, count, FALSE));
3117 GdkPixbufAnimation *pidgin_pixbuf_anim_from_data(const guchar *buf, gsize count)
3119 return GDK_PIXBUF_ANIMATION(pidgin_pixbuf_from_data_helper(buf, count, TRUE));
3122 GdkPixbuf *pidgin_pixbuf_from_imgstore(PurpleStoredImage *image)
3124 return pidgin_pixbuf_from_data(purple_imgstore_get_data(image),
3125 purple_imgstore_get_size(image));
3128 GdkPixbuf *pidgin_pixbuf_new_from_file(const gchar *filename)
3130 GdkPixbuf *pixbuf;
3131 GError *error = NULL;
3133 pixbuf = gdk_pixbuf_new_from_file(filename, &error);
3134 if (!pixbuf || error) {
3135 purple_debug_warning("gtkutils", "gdk_pixbuf_new_from_file() "
3136 "returned %s for file %s: %s\n",
3137 pixbuf ? "something" : "nothing",
3138 filename,
3139 error ? error->message : "(no error message)");
3140 if (error)
3141 g_error_free(error);
3142 if (pixbuf)
3143 g_object_unref(G_OBJECT(pixbuf));
3144 return NULL;
3147 return pixbuf;
3150 GdkPixbuf *pidgin_pixbuf_new_from_file_at_size(const char *filename, int width, int height)
3152 GdkPixbuf *pixbuf;
3153 GError *error = NULL;
3155 pixbuf = gdk_pixbuf_new_from_file_at_size(filename,
3156 width, height, &error);
3157 if (!pixbuf || error) {
3158 purple_debug_warning("gtkutils", "gdk_pixbuf_new_from_file_at_size() "
3159 "returned %s for file %s: %s\n",
3160 pixbuf ? "something" : "nothing",
3161 filename,
3162 error ? error->message : "(no error message)");
3163 if (error)
3164 g_error_free(error);
3165 if (pixbuf)
3166 g_object_unref(G_OBJECT(pixbuf));
3167 return NULL;
3170 return pixbuf;
3173 GdkPixbuf *pidgin_pixbuf_new_from_file_at_scale(const char *filename, int width, int height, gboolean preserve_aspect_ratio)
3175 GdkPixbuf *pixbuf;
3176 GError *error = NULL;
3178 pixbuf = gdk_pixbuf_new_from_file_at_scale(filename,
3179 width, height, preserve_aspect_ratio, &error);
3180 if (!pixbuf || error) {
3181 purple_debug_warning("gtkutils", "gdk_pixbuf_new_from_file_at_scale() "
3182 "returned %s for file %s: %s\n",
3183 pixbuf ? "something" : "nothing",
3184 filename,
3185 error ? error->message : "(no error message)");
3186 if (error)
3187 g_error_free(error);
3188 if (pixbuf)
3189 g_object_unref(G_OBJECT(pixbuf));
3190 return NULL;
3193 return pixbuf;
3196 static void url_copy(GtkWidget *w, gchar *url)
3198 GtkClipboard *clipboard;
3200 clipboard = gtk_widget_get_clipboard(w, GDK_SELECTION_PRIMARY);
3201 gtk_clipboard_set_text(clipboard, url, -1);
3203 clipboard = gtk_widget_get_clipboard(w, GDK_SELECTION_CLIPBOARD);
3204 gtk_clipboard_set_text(clipboard, url, -1);
3207 static gboolean
3208 link_context_menu(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu)
3210 GtkWidget *img, *item;
3211 const char *url;
3213 url = gtk_imhtml_link_get_url(link);
3215 /* Open Link */
3216 img = gtk_image_new_from_stock(GTK_STOCK_JUMP_TO, GTK_ICON_SIZE_MENU);
3217 item = gtk_image_menu_item_new_with_mnemonic(_("_Open Link"));
3218 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
3219 g_signal_connect_swapped(G_OBJECT(item), "activate", G_CALLBACK(gtk_imhtml_link_activate), link);
3220 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
3222 /* Copy Link Location */
3223 img = gtk_image_new_from_stock(GTK_STOCK_COPY, GTK_ICON_SIZE_MENU);
3224 item = gtk_image_menu_item_new_with_mnemonic(_("_Copy Link Location"));
3225 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
3226 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(url_copy), (gpointer)url);
3227 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
3229 return TRUE;
3232 static gboolean
3233 copy_email_address(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu)
3235 GtkWidget *img, *item;
3236 const char *text;
3237 char *address;
3238 #define MAILTOSIZE (sizeof("mailto:") - 1)
3240 text = gtk_imhtml_link_get_url(link);
3241 g_return_val_if_fail(text && strlen(text) > MAILTOSIZE, FALSE);
3242 address = (char*)text + MAILTOSIZE;
3244 /* Copy Email Address */
3245 img = gtk_image_new_from_stock(GTK_STOCK_COPY, GTK_ICON_SIZE_MENU);
3246 item = gtk_image_menu_item_new_with_mnemonic(_("_Copy Email Address"));
3247 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
3248 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(url_copy), address);
3249 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
3251 return TRUE;
3255 * @param filename The path to a file. Specifically this is the link target
3256 * from a link in an IM window with the leading "file://" removed.
3258 static void
3259 open_file(GtkIMHtml *imhtml, const char *filename)
3261 /* Copied from gtkft.c:open_button_cb */
3262 #ifdef _WIN32
3263 /* If using Win32... */
3264 int code;
3265 /* Escape URI by replacing double-quote with 2 double-quotes. */
3266 gchar *escaped = purple_strreplace(filename, "\"", "\"\"");
3267 gchar *param = g_strconcat("/select,\"", escaped, "\"", NULL);
3268 wchar_t *wc_param = g_utf8_to_utf16(param, -1, NULL, NULL, NULL);
3270 /* TODO: Better to use SHOpenFolderAndSelectItems()? */
3271 code = (int)ShellExecuteW(NULL, L"OPEN", L"explorer.exe", wc_param, NULL, SW_NORMAL);
3273 g_free(wc_param);
3274 g_free(param);
3275 g_free(escaped);
3277 if (code == SE_ERR_ASSOCINCOMPLETE || code == SE_ERR_NOASSOC)
3279 purple_notify_error(imhtml, NULL,
3280 _("There is no application configured to open this type of file."), NULL);
3282 else if (code < 32)
3284 purple_notify_error(imhtml, NULL,
3285 _("An error occurred while opening the file."), NULL);
3286 purple_debug_warning("gtkutils", "filename: %s; code: %d\n",
3287 filename, code);
3289 #else
3290 char *command = NULL;
3291 char *tmp = NULL;
3292 GError *error = NULL;
3294 if (purple_running_gnome())
3296 char *escaped = g_shell_quote(filename);
3297 command = g_strdup_printf("gnome-open %s", escaped);
3298 g_free(escaped);
3300 else if (purple_running_kde())
3302 char *escaped = g_shell_quote(filename);
3304 if (purple_str_has_suffix(filename, ".desktop"))
3305 command = g_strdup_printf("kfmclient openURL %s 'text/plain'", escaped);
3306 else
3307 command = g_strdup_printf("kfmclient openURL %s", escaped);
3308 g_free(escaped);
3310 else
3312 purple_notify_uri(NULL, filename);
3313 return;
3316 if (purple_program_is_valid(command))
3318 gint exit_status;
3319 if (!g_spawn_command_line_sync(command, NULL, NULL, &exit_status, &error))
3321 tmp = g_strdup_printf(_("Error launching %s: %s"),
3322 filename, error->message);
3323 purple_notify_error(imhtml, NULL, _("Unable to open file."), tmp);
3324 g_free(tmp);
3325 g_error_free(error);
3327 if (exit_status != 0)
3329 char *primary = g_strdup_printf(_("Error running %s"), command);
3330 char *secondary = g_strdup_printf(_("Process returned error code %d"),
3331 exit_status);
3332 purple_notify_error(imhtml, NULL, primary, secondary);
3333 g_free(tmp);
3336 #endif
3339 #define FILELINKSIZE (sizeof("file://") - 1)
3340 static gboolean
3341 file_clicked_cb(GtkIMHtml *imhtml, GtkIMHtmlLink *link)
3343 /* Strip "file://" from the URI. */
3344 const char *filename = gtk_imhtml_link_get_url(link) + FILELINKSIZE;
3345 open_file(imhtml, filename);
3346 return TRUE;
3349 static gboolean
3350 open_containing_cb(GtkIMHtml *imhtml, const char *url)
3352 char *dir = g_path_get_dirname(url + FILELINKSIZE);
3353 open_file(imhtml, dir);
3354 g_free(dir);
3355 return TRUE;
3358 static gboolean
3359 file_context_menu(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu)
3361 GtkWidget *img, *item;
3362 const char *url;
3364 url = gtk_imhtml_link_get_url(link);
3366 /* Open File */
3367 img = gtk_image_new_from_stock(GTK_STOCK_JUMP_TO, GTK_ICON_SIZE_MENU);
3368 item = gtk_image_menu_item_new_with_mnemonic(_("_Open File"));
3369 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
3370 g_signal_connect_swapped(G_OBJECT(item), "activate", G_CALLBACK(gtk_imhtml_link_activate), link);
3371 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
3373 /* Open Containing Directory */
3374 img = gtk_image_new_from_stock(GTK_STOCK_DIRECTORY, GTK_ICON_SIZE_MENU);
3375 item = gtk_image_menu_item_new_with_mnemonic(_("Open _Containing Directory"));
3376 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
3378 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(open_containing_cb), (gpointer)url);
3379 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
3381 return TRUE;
3384 #define AUDIOLINKSIZE (sizeof("audio://") - 1)
3385 static gboolean
3386 audio_clicked_cb(GtkIMHtml *imhtml, GtkIMHtmlLink *link)
3388 const char *uri;
3389 PidginConversation *conv = g_object_get_data(G_OBJECT(imhtml), "gtkconv");
3390 if (!conv) /* no playback in debug window */
3391 return TRUE;
3392 uri = gtk_imhtml_link_get_url(link) + AUDIOLINKSIZE;
3393 purple_sound_play_file(uri, NULL);
3394 return TRUE;
3397 static void
3398 savefile_write_cb(gpointer user_data, char *file)
3400 char *temp_file = user_data;
3401 gchar *contents;
3402 gsize length;
3403 GError *error = NULL;
3405 if (!g_file_get_contents(temp_file, &contents, &length, &error)) {
3406 purple_debug_error("gtkutils", "Unable to read contents of %s: %s\n",
3407 temp_file, error->message);
3408 g_error_free(error);
3409 return;
3412 if (!purple_util_write_data_to_file_absolute(file, contents, length)) {
3413 purple_debug_error("gtkutils", "Unable to write contents to %s\n",
3414 file);
3418 static gboolean
3419 save_file_cb(GtkWidget *item, const char *url)
3421 PidginConversation *conv = g_object_get_data(G_OBJECT(item), "gtkconv");
3422 if (!conv)
3423 return TRUE;
3424 purple_request_file(conv->active_conv, _("Save File"), NULL, TRUE,
3425 G_CALLBACK(savefile_write_cb), NULL,
3426 conv->active_conv->account, NULL, conv->active_conv,
3427 (void *)url);
3428 return TRUE;
3431 static gboolean
3432 audio_context_menu(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu)
3434 GtkWidget *img, *item;
3435 const char *url;
3436 PidginConversation *conv = g_object_get_data(G_OBJECT(imhtml), "gtkconv");
3437 if (!conv) /* No menu in debug window */
3438 return TRUE;
3440 url = gtk_imhtml_link_get_url(link);
3442 /* Play Sound */
3443 img = gtk_image_new_from_stock(GTK_STOCK_MEDIA_PLAY, GTK_ICON_SIZE_MENU);
3444 item = gtk_image_menu_item_new_with_mnemonic(_("_Play Sound"));
3445 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
3447 g_signal_connect_swapped(G_OBJECT(item), "activate", G_CALLBACK(gtk_imhtml_link_activate), link);
3448 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
3450 /* Save File */
3451 img = gtk_image_new_from_stock(GTK_STOCK_SAVE, GTK_ICON_SIZE_MENU);
3452 item = gtk_image_menu_item_new_with_mnemonic(_("_Save File"));
3453 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
3454 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(save_file_cb), (gpointer)(url+AUDIOLINKSIZE));
3455 g_object_set_data(G_OBJECT(item), "gtkconv", conv);
3456 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
3458 return TRUE;
3461 /* XXX: The following two functions are for demonstration purposes only! */
3462 static gboolean
3463 open_dialog(GtkIMHtml *imhtml, GtkIMHtmlLink *link)
3465 const char *url;
3466 const char *str;
3468 url = gtk_imhtml_link_get_url(link);
3469 if (!url || strlen(url) < sizeof("open://"))
3470 return FALSE;
3472 str = url + sizeof("open://") - 1;
3474 if (purple_strequal(str, "accounts"))
3475 pidgin_accounts_window_show();
3476 else if (purple_strequal(str, "prefs"))
3477 pidgin_prefs_show();
3478 else
3479 return FALSE;
3480 return TRUE;
3483 static gboolean
3484 dummy(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu)
3486 return TRUE;
3489 static gboolean
3490 register_gnome_url_handlers(void)
3492 char *tmp;
3493 char *err;
3494 char *c;
3495 char *start;
3497 tmp = g_find_program_in_path("gconftool-2");
3498 if (tmp == NULL)
3499 return FALSE;
3501 g_free(tmp);
3502 tmp = NULL;
3504 if (!g_spawn_command_line_sync("gconftool-2 --all-dirs /desktop/gnome/url-handlers",
3505 &tmp, &err, NULL, NULL))
3507 g_free(tmp);
3508 g_free(err);
3509 g_return_val_if_reached(FALSE);
3511 g_free(err);
3512 err = NULL;
3514 for (c = start = tmp ; *c ; c++)
3516 /* Skip leading spaces. */
3517 if (c == start && *c == ' ')
3518 start = c + 1;
3519 else if (*c == '\n')
3521 *c = '\0';
3522 if (g_str_has_prefix(start, "/desktop/gnome/url-handlers/"))
3524 char *cmd;
3525 char *tmp2 = NULL;
3526 char *protocol;
3528 /* If there is an enabled boolean, honor it. */
3529 cmd = g_strdup_printf("gconftool-2 -g %s/enabled", start);
3530 if (g_spawn_command_line_sync(cmd, &tmp2, &err, NULL, NULL))
3532 g_free(err);
3533 err = NULL;
3534 if (purple_strequal(tmp2, "false\n"))
3536 g_free(tmp2);
3537 g_free(cmd);
3538 start = c + 1;
3539 continue;
3542 g_free(cmd);
3543 g_free(tmp2);
3545 start += sizeof("/desktop/gnome/url-handlers/") - 1;
3547 protocol = g_strdup_printf("%s:", start);
3548 registered_url_handlers = g_slist_prepend(registered_url_handlers, protocol);
3549 gtk_imhtml_class_register_protocol(protocol, url_clicked_cb, link_context_menu);
3551 start = c + 1;
3554 g_free(tmp);
3556 return (registered_url_handlers != NULL);
3559 #ifdef _WIN32
3560 static void
3561 winpidgin_register_win32_url_handlers(void)
3563 int idx = 0;
3564 LONG ret = ERROR_SUCCESS;
3566 do {
3567 DWORD nameSize = 256;
3568 wchar_t start[256];
3569 ret = RegEnumKeyExW(HKEY_CLASSES_ROOT, idx++, start, &nameSize,
3570 NULL, NULL, NULL, NULL);
3571 if (ret == ERROR_SUCCESS) {
3572 HKEY reg_key = NULL;
3573 ret = RegOpenKeyExW(HKEY_CLASSES_ROOT, start, 0, KEY_READ, &reg_key);
3574 if (ret == ERROR_SUCCESS) {
3575 ret = RegQueryValueExW(reg_key, L"URL Protocol", NULL, NULL, NULL, NULL);
3576 if (ret == ERROR_SUCCESS) {
3577 gchar *utf8 = g_utf16_to_utf8(start, -1, NULL, NULL, NULL);
3578 gchar *protocol = g_strdup_printf("%s:", utf8);
3579 g_free(utf8);
3580 registered_url_handlers = g_slist_prepend(registered_url_handlers, protocol);
3581 /* We still pass everything to the "http" "open" handler for security reasons */
3582 gtk_imhtml_class_register_protocol(protocol, url_clicked_cb, link_context_menu);
3584 RegCloseKey(reg_key);
3586 ret = ERROR_SUCCESS;
3588 } while (ret == ERROR_SUCCESS);
3590 if (ret != ERROR_NO_MORE_ITEMS)
3591 purple_debug_error("winpidgin", "Error iterating HKEY_CLASSES_ROOT subkeys: %ld\n",
3592 ret);
3594 #endif
3596 GtkWidget *
3597 pidgin_make_scrollable(GtkWidget *child, GtkPolicyType hscrollbar_policy, GtkPolicyType vscrollbar_policy, GtkShadowType shadow_type, int width, int height)
3599 GtkWidget *sw = gtk_scrolled_window_new(NULL, NULL);
3601 if (G_LIKELY(sw)) {
3602 gtk_widget_show(sw);
3603 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), hscrollbar_policy, vscrollbar_policy);
3604 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw), shadow_type);
3605 if (width != -1 || height != -1)
3606 gtk_widget_set_size_request(sw, width, height);
3607 if (child) {
3608 if (GTK_WIDGET_GET_CLASS(child)->set_scroll_adjustments_signal)
3609 gtk_container_add(GTK_CONTAINER(sw), child);
3610 else
3611 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(sw), child);
3613 return sw;
3616 return child;
3619 void pidgin_utils_init(void)
3621 gtk_imhtml_class_register_protocol("http://", url_clicked_cb, link_context_menu);
3622 gtk_imhtml_class_register_protocol("https://", url_clicked_cb, link_context_menu);
3623 gtk_imhtml_class_register_protocol("ftp://", url_clicked_cb, link_context_menu);
3624 gtk_imhtml_class_register_protocol("gopher://", url_clicked_cb, link_context_menu);
3625 gtk_imhtml_class_register_protocol("mailto:", url_clicked_cb, copy_email_address);
3627 gtk_imhtml_class_register_protocol("file://", file_clicked_cb, file_context_menu);
3628 gtk_imhtml_class_register_protocol("audio://", audio_clicked_cb, audio_context_menu);
3630 /* Example custom URL handler. */
3631 gtk_imhtml_class_register_protocol("open://", open_dialog, dummy);
3633 /* If we're under GNOME, try registering the system URL handlers. */
3634 if (purple_running_gnome())
3635 register_gnome_url_handlers();
3637 /* Used to make small buttons */
3638 gtk_rc_parse_string("style \"pidgin-small-close-button\"\n"
3639 "{\n"
3640 "GtkWidget::focus-padding = 0\n"
3641 "GtkWidget::focus-line-width = 0\n"
3642 "xthickness = 0\n"
3643 "ythickness = 0\n"
3644 "GtkContainer::border-width = 0\n"
3645 "GtkButton::inner-border = {0, 0, 0, 0}\n"
3646 "GtkButton::default-border = {0, 0, 0, 0}\n"
3647 "}\n"
3648 "widget \"*.pidgin-small-close-button\" style \"pidgin-small-close-button\"");
3650 #ifdef _WIN32
3651 winpidgin_register_win32_url_handlers();
3652 #endif
3656 void pidgin_utils_uninit(void)
3658 gtk_imhtml_class_register_protocol("open://", NULL, NULL);
3660 /* If we have GNOME handlers registered, unregister them. */
3661 if (registered_url_handlers)
3663 GSList *l;
3664 for (l = registered_url_handlers; l; l = l->next)
3666 gtk_imhtml_class_register_protocol((char *)l->data, NULL, NULL);
3667 g_free(l->data);
3669 g_slist_free(registered_url_handlers);
3670 registered_url_handlers = NULL;
3671 return;
3674 gtk_imhtml_class_register_protocol("audio://", NULL, NULL);
3675 gtk_imhtml_class_register_protocol("file://", NULL, NULL);
3677 gtk_imhtml_class_register_protocol("http://", NULL, NULL);
3678 gtk_imhtml_class_register_protocol("https://", NULL, NULL);
3679 gtk_imhtml_class_register_protocol("ftp://", NULL, NULL);
3680 gtk_imhtml_class_register_protocol("mailto:", NULL, NULL);
3681 gtk_imhtml_class_register_protocol("gopher://", NULL, NULL);