Start of killing off unneeded GTK_CHECK_VERSION checks in pidgin. Refs #10024
[pidgin-git.git] / pidgin / gtkutils.c
blob323bd959be71f9497c61a1707d40aa7a3920eb72
1 /**
2 * @file gtkutils.c GTK+ utility functions
3 * @ingroup pidgin
4 */
6 /* pidgin
8 * Pidgin is the legal property of its developers, whose names are too numerous
9 * to list here. Please refer to the COPYRIGHT file distributed with this
10 * source distribution.
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
26 #define _PIDGIN_GTKUTILS_C_
28 #include "internal.h"
29 #include "pidgin.h"
31 #ifndef _WIN32
32 # include <X11/Xlib.h>
33 #else
34 # ifdef small
35 # undef small
36 # endif
37 #endif /*_WIN32*/
39 #ifdef USE_GTKSPELL
40 # include <gtkspell/gtkspell.h>
41 # ifdef _WIN32
42 # include "wspell.h"
43 # endif
44 #endif
46 #include <gdk/gdkkeysyms.h>
48 #include "conversation.h"
49 #include "debug.h"
50 #include "desktopitem.h"
51 #include "imgstore.h"
52 #include "notify.h"
53 #include "prefs.h"
54 #include "prpl.h"
55 #include "request.h"
56 #include "signals.h"
57 #include "sound.h"
58 #include "util.h"
60 #include "gtkaccount.h"
61 #include "gtkprefs.h"
63 #include "gtkconv.h"
64 #include "gtkdialogs.h"
65 #include "gtkimhtml.h"
66 #include "gtkimhtmltoolbar.h"
67 #include "pidginstock.h"
68 #include "gtkthemes.h"
69 #include "gtkutils.h"
70 #include "pidgin/minidialog.h"
72 typedef struct {
73 GtkWidget *menu;
74 gint default_item;
75 } AopMenu;
77 static guint accels_save_timer = 0;
78 static GList *gnome_url_handlers = NULL;
80 static gboolean
81 url_clicked_idle_cb(gpointer data)
83 purple_notify_uri(NULL, data);
84 g_free(data);
85 return FALSE;
88 static gboolean
89 url_clicked_cb(GtkIMHtml *unused, GtkIMHtmlLink *link)
91 const char *uri = gtk_imhtml_link_get_url(link);
92 g_idle_add(url_clicked_idle_cb, g_strdup(uri));
93 return TRUE;
96 static GtkIMHtmlFuncs gtkimhtml_cbs = {
97 (GtkIMHtmlGetImageFunc)purple_imgstore_find_by_id,
98 (GtkIMHtmlGetImageDataFunc)purple_imgstore_get_data,
99 (GtkIMHtmlGetImageSizeFunc)purple_imgstore_get_size,
100 (GtkIMHtmlGetImageFilenameFunc)purple_imgstore_get_filename,
101 purple_imgstore_ref_by_id,
102 purple_imgstore_unref_by_id,
105 void
106 pidgin_setup_imhtml(GtkWidget *imhtml)
108 PangoFontDescription *desc = NULL;
109 g_return_if_fail(imhtml != NULL);
110 g_return_if_fail(GTK_IS_IMHTML(imhtml));
112 pidgin_themes_smiley_themeize(imhtml);
114 gtk_imhtml_set_funcs(GTK_IMHTML(imhtml), &gtkimhtml_cbs);
116 if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/use_theme_font")) {
117 const char *font = purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/custom_font");
118 desc = pango_font_description_from_string(font);
119 } else if (purple_running_gnome()) {
120 /* Use the GNOME "document" font, if applicable */
121 char *path;
123 if ((path = g_find_program_in_path("gconftool-2"))) {
124 char *font = NULL;
125 char *err = NULL;
126 g_free(path);
127 if (g_spawn_command_line_sync(
128 "gconftool-2 -g /desktop/gnome/interface/document_font_name",
129 &font, &err, NULL, NULL)) {
130 desc = pango_font_description_from_string(font);
132 g_free(err);
133 g_free(font);
137 if (desc) {
138 gtk_widget_modify_font(imhtml, desc);
139 pango_font_description_free(desc);
143 static
144 void pidgin_window_init(GtkWindow *wnd, const char *title, guint border_width, const char *role, gboolean resizable)
146 if (title)
147 gtk_window_set_title(wnd, title);
148 #ifdef _WIN32
149 else
150 gtk_window_set_title(wnd, PIDGIN_ALERT_TITLE);
151 #endif
152 gtk_container_set_border_width(GTK_CONTAINER(wnd), border_width);
153 if (role)
154 gtk_window_set_role(wnd, role);
155 gtk_window_set_resizable(wnd, resizable);
158 GtkWidget *
159 pidgin_create_window(const char *title, guint border_width, const char *role, gboolean resizable)
161 GtkWindow *wnd = NULL;
163 wnd = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
164 pidgin_window_init(wnd, title, border_width, role, resizable);
166 return GTK_WIDGET(wnd);
169 GtkWidget *
170 pidgin_create_dialog(const char *title, guint border_width, const char *role, gboolean resizable)
172 GtkWindow *wnd = NULL;
174 wnd = GTK_WINDOW(gtk_dialog_new());
175 pidgin_window_init(wnd, title, border_width, role, resizable);
176 g_object_set(G_OBJECT(wnd), "has-separator", FALSE, NULL);
178 return GTK_WIDGET(wnd);
181 GtkWidget *
182 pidgin_dialog_get_vbox_with_properties(GtkDialog *dialog, gboolean homogeneous, gint spacing)
184 GtkBox *vbox = GTK_BOX(GTK_DIALOG(dialog)->vbox);
185 gtk_box_set_homogeneous(vbox, homogeneous);
186 gtk_box_set_spacing(vbox, spacing);
187 return GTK_WIDGET(vbox);
190 GtkWidget *pidgin_dialog_get_vbox(GtkDialog *dialog)
192 return GTK_DIALOG(dialog)->vbox;
195 GtkWidget *pidgin_dialog_get_action_area(GtkDialog *dialog)
197 return GTK_DIALOG(dialog)->action_area;
200 GtkWidget *pidgin_dialog_add_button(GtkDialog *dialog, const char *label,
201 GCallback callback, gpointer callbackdata)
203 GtkWidget *button = gtk_button_new_from_stock(label);
204 GtkWidget *bbox = pidgin_dialog_get_action_area(dialog);
205 gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
206 if (callback)
207 g_signal_connect(G_OBJECT(button), "clicked", callback, callbackdata);
208 gtk_widget_show(button);
209 return button;
212 GtkWidget *
213 pidgin_create_imhtml(gboolean editable, GtkWidget **imhtml_ret, GtkWidget **toolbar_ret, GtkWidget **sw_ret)
215 GtkWidget *frame;
216 GtkWidget *imhtml;
217 GtkWidget *sep;
218 GtkWidget *sw;
219 GtkWidget *toolbar = NULL;
220 GtkWidget *vbox;
222 frame = gtk_frame_new(NULL);
223 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
225 vbox = gtk_vbox_new(FALSE, 0);
226 gtk_container_add(GTK_CONTAINER(frame), vbox);
227 gtk_widget_show(vbox);
229 if (editable) {
230 toolbar = gtk_imhtmltoolbar_new();
231 gtk_box_pack_start(GTK_BOX(vbox), toolbar, FALSE, FALSE, 0);
232 gtk_widget_show(toolbar);
234 sep = gtk_hseparator_new();
235 gtk_box_pack_start(GTK_BOX(vbox), sep, FALSE, FALSE, 0);
236 g_signal_connect_swapped(G_OBJECT(toolbar), "show", G_CALLBACK(gtk_widget_show), sep);
237 g_signal_connect_swapped(G_OBJECT(toolbar), "hide", G_CALLBACK(gtk_widget_hide), sep);
238 gtk_widget_show(sep);
241 sw = gtk_scrolled_window_new(NULL, NULL);
242 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
243 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
244 gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0);
245 gtk_widget_show(sw);
247 imhtml = gtk_imhtml_new(NULL, NULL);
248 gtk_imhtml_set_editable(GTK_IMHTML(imhtml), editable);
249 gtk_imhtml_set_format_functions(GTK_IMHTML(imhtml), GTK_IMHTML_ALL ^ GTK_IMHTML_IMAGE);
250 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(imhtml), GTK_WRAP_WORD_CHAR);
251 #ifdef USE_GTKSPELL
252 if (editable && purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/spellcheck"))
253 pidgin_setup_gtkspell(GTK_TEXT_VIEW(imhtml));
254 #endif
255 gtk_widget_show(imhtml);
257 if (editable) {
258 gtk_imhtmltoolbar_attach(GTK_IMHTMLTOOLBAR(toolbar), imhtml);
259 gtk_imhtmltoolbar_associate_smileys(GTK_IMHTMLTOOLBAR(toolbar), "default");
261 pidgin_setup_imhtml(imhtml);
263 gtk_container_add(GTK_CONTAINER(sw), imhtml);
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 int 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_defaults(GTK_BOX(bbox), ibox);
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_defaults(GTK_BOX(bbox), lbox);
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 = gdk_pixbuf_new_from_file(filename, NULL);
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 PurplePluginProtocolInfo *prpl_info;
677 PurplePlugin *plugin;
678 GdkPixbuf *pixbuf = NULL;
679 GtkSizeGroup *sg;
680 GList *p;
681 const char *gtalk_name = NULL;
682 int i;
684 aop_menu = g_malloc0(sizeof(AopMenu));
685 aop_menu->default_item = -1;
686 aop_menu->menu = gtk_menu_new();
687 gtk_widget_show(aop_menu->menu);
688 sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
690 if (purple_find_prpl("prpl-jabber"))
691 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;
698 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin);
700 if (gtalk_name && strcmp(gtalk_name, plugin->info->name) < 0) {
701 char *filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "protocols",
702 "16", "google-talk.png", NULL);
703 GtkWidget *item;
705 pixbuf = gdk_pixbuf_new_from_file(filename, NULL);
706 g_free(filename);
708 gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu->menu),
709 item = aop_menu_item_new(sg, pixbuf, gtalk_name, "prpl-jabber", "protocol"));
710 g_object_set_data(G_OBJECT(item), "fake", GINT_TO_POINTER(1));
712 if (pixbuf)
713 g_object_unref(pixbuf);
715 gtalk_name = NULL;
716 i++;
719 pixbuf = pidgin_create_prpl_icon_from_prpl(plugin, PIDGIN_PRPL_ICON_SMALL, NULL);
721 gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu->menu),
722 aop_menu_item_new(sg, pixbuf, plugin->info->name, plugin->info->id, "protocol"));
724 if (pixbuf)
725 g_object_unref(pixbuf);
727 if (default_proto_id != NULL && !strcmp(plugin->info->id, default_proto_id))
728 aop_menu->default_item = i;
731 g_object_unref(sg);
733 return aop_menu;
736 GtkWidget *
737 pidgin_protocol_option_menu_new(const char *id, GCallback cb,
738 gpointer user_data)
740 return aop_option_menu_new(create_protocols_menu(id), cb, user_data);
743 const char *
744 pidgin_protocol_option_menu_get_selected(GtkWidget *optmenu)
746 return (const char *)aop_option_menu_get_selected(optmenu, NULL);
749 PurpleAccount *
750 pidgin_account_option_menu_get_selected(GtkWidget *optmenu)
752 return (PurpleAccount *)aop_option_menu_get_selected(optmenu, NULL);
755 static AopMenu *
756 create_account_menu(PurpleAccount *default_account,
757 PurpleFilterAccountFunc filter_func, gboolean show_all)
759 AopMenu *aop_menu = NULL;
760 PurpleAccount *account;
761 GdkPixbuf *pixbuf = NULL;
762 GList *list;
763 GList *p;
764 GtkSizeGroup *sg;
765 int i;
766 char buf[256];
768 if (show_all)
769 list = purple_accounts_get_all();
770 else
771 list = purple_connections_get_all();
773 aop_menu = g_malloc0(sizeof(AopMenu));
774 aop_menu->default_item = -1;
775 aop_menu->menu = gtk_menu_new();
776 gtk_widget_show(aop_menu->menu);
777 sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
779 for (p = list, i = 0; p != NULL; p = p->next, i++) {
780 PurplePlugin *plugin;
782 if (show_all)
783 account = (PurpleAccount *)p->data;
784 else {
785 PurpleConnection *gc = (PurpleConnection *)p->data;
787 account = purple_connection_get_account(gc);
790 if (filter_func && !filter_func(account)) {
791 i--;
792 continue;
795 plugin = purple_find_prpl(purple_account_get_protocol_id(account));
797 pixbuf = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL);
799 if (pixbuf) {
800 if (purple_account_is_disconnected(account) && show_all &&
801 purple_connections_get_all())
802 gdk_pixbuf_saturate_and_pixelate(pixbuf, pixbuf, 0.0, FALSE);
805 if (purple_account_get_alias(account)) {
806 g_snprintf(buf, sizeof(buf), "%s (%s) (%s)",
807 purple_account_get_username(account),
808 purple_account_get_alias(account),
809 purple_account_get_protocol_name(account));
810 } else {
811 g_snprintf(buf, sizeof(buf), "%s (%s)",
812 purple_account_get_username(account),
813 purple_account_get_protocol_name(account));
816 gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu->menu),
817 aop_menu_item_new(sg, pixbuf, buf, account, "account"));
819 if (pixbuf)
820 g_object_unref(pixbuf);
822 if (default_account && account == default_account)
823 aop_menu->default_item = i;
826 g_object_unref(sg);
828 return aop_menu;
831 static void
832 regenerate_account_menu(GtkWidget *optmenu)
834 gboolean show_all;
835 PurpleAccount *account;
836 PurpleFilterAccountFunc filter_func;
838 account = (PurpleAccount *)aop_option_menu_get_selected(optmenu, NULL);
839 show_all = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(optmenu), "show_all"));
840 filter_func = g_object_get_data(G_OBJECT(optmenu), "filter_func");
842 aop_option_menu_replace_menu(optmenu, create_account_menu(account, filter_func, show_all));
845 static void
846 account_menu_sign_on_off_cb(PurpleConnection *gc, GtkWidget *optmenu)
848 regenerate_account_menu(optmenu);
851 static void
852 account_menu_added_removed_cb(PurpleAccount *account, GtkWidget *optmenu)
854 regenerate_account_menu(optmenu);
857 static gboolean
858 account_menu_destroyed_cb(GtkWidget *optmenu, GdkEvent *event,
859 void *user_data)
861 purple_signals_disconnect_by_handle(optmenu);
863 return FALSE;
866 void
867 pidgin_account_option_menu_set_selected(GtkWidget *optmenu, PurpleAccount *account)
869 aop_option_menu_select_by_data(optmenu, account);
872 GtkWidget *
873 pidgin_account_option_menu_new(PurpleAccount *default_account,
874 gboolean show_all, GCallback cb,
875 PurpleFilterAccountFunc filter_func,
876 gpointer user_data)
878 GtkWidget *optmenu;
880 /* Create the option menu */
881 optmenu = aop_option_menu_new(create_account_menu(default_account, filter_func, show_all), cb, user_data);
883 g_signal_connect(G_OBJECT(optmenu), "destroy",
884 G_CALLBACK(account_menu_destroyed_cb), NULL);
886 /* Register the purple sign on/off event callbacks. */
887 purple_signal_connect(purple_connections_get_handle(), "signed-on",
888 optmenu, PURPLE_CALLBACK(account_menu_sign_on_off_cb),
889 optmenu);
890 purple_signal_connect(purple_connections_get_handle(), "signed-off",
891 optmenu, PURPLE_CALLBACK(account_menu_sign_on_off_cb),
892 optmenu);
893 purple_signal_connect(purple_accounts_get_handle(), "account-added",
894 optmenu, PURPLE_CALLBACK(account_menu_added_removed_cb),
895 optmenu);
896 purple_signal_connect(purple_accounts_get_handle(), "account-removed",
897 optmenu, PURPLE_CALLBACK(account_menu_added_removed_cb),
898 optmenu);
900 /* Set some data. */
901 g_object_set_data(G_OBJECT(optmenu), "user_data", user_data);
902 g_object_set_data(G_OBJECT(optmenu), "show_all", GINT_TO_POINTER(show_all));
903 g_object_set_data(G_OBJECT(optmenu), "filter_func", filter_func);
905 return optmenu;
908 gboolean
909 pidgin_check_if_dir(const char *path, GtkFileSelection *filesel)
911 char *dirname = NULL;
913 if (g_file_test(path, G_FILE_TEST_IS_DIR)) {
914 /* append a / if needed */
915 if (path[strlen(path) - 1] != G_DIR_SEPARATOR) {
916 dirname = g_strconcat(path, G_DIR_SEPARATOR_S, NULL);
918 gtk_file_selection_set_filename(filesel, (dirname != NULL) ? dirname : path);
919 g_free(dirname);
920 return TRUE;
923 return FALSE;
926 void
927 pidgin_setup_gtkspell(GtkTextView *textview)
929 #ifdef USE_GTKSPELL
930 GError *error = NULL;
931 char *locale = NULL;
933 g_return_if_fail(textview != NULL);
934 g_return_if_fail(GTK_IS_TEXT_VIEW(textview));
936 if (gtkspell_new_attach(textview, locale, &error) == NULL && error)
938 purple_debug_warning("gtkspell", "Failed to setup GtkSpell: %s\n",
939 error->message);
940 g_error_free(error);
942 #endif /* USE_GTKSPELL */
945 void
946 pidgin_save_accels_cb(GtkAccelGroup *accel_group, guint arg1,
947 GdkModifierType arg2, GClosure *arg3,
948 gpointer data)
950 purple_debug(PURPLE_DEBUG_MISC, "accels",
951 "accel changed, scheduling save.\n");
953 if (!accels_save_timer)
954 accels_save_timer = purple_timeout_add_seconds(5, pidgin_save_accels,
955 NULL);
958 gboolean
959 pidgin_save_accels(gpointer data)
961 char *filename = NULL;
963 filename = g_build_filename(purple_user_dir(), G_DIR_SEPARATOR_S,
964 "accels", NULL);
965 purple_debug(PURPLE_DEBUG_MISC, "accels", "saving accels to %s\n", filename);
966 gtk_accel_map_save(filename);
967 g_free(filename);
969 accels_save_timer = 0;
970 return FALSE;
973 void
974 pidgin_load_accels()
976 char *filename = NULL;
978 filename = g_build_filename(purple_user_dir(), G_DIR_SEPARATOR_S,
979 "accels", NULL);
980 gtk_accel_map_load(filename);
981 g_free(filename);
984 static void
985 show_retrieveing_info(PurpleConnection *conn, const char *name)
987 PurpleNotifyUserInfo *info = purple_notify_user_info_new();
988 purple_notify_user_info_add_pair(info, _("Information"), _("Retrieving..."));
989 purple_notify_userinfo(conn, name, info, NULL, NULL);
990 purple_notify_user_info_destroy(info);
993 void pidgin_retrieve_user_info(PurpleConnection *conn, const char *name)
995 show_retrieveing_info(conn, name);
996 serv_get_info(conn, name);
999 void pidgin_retrieve_user_info_in_chat(PurpleConnection *conn, const char *name, int chat)
1001 char *who = NULL;
1002 PurplePluginProtocolInfo *prpl_info = NULL;
1004 if (chat < 0) {
1005 pidgin_retrieve_user_info(conn, name);
1006 return;
1009 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(conn->prpl);
1010 if (prpl_info != NULL && prpl_info->get_cb_real_name)
1011 who = prpl_info->get_cb_real_name(conn, chat, name);
1012 if (prpl_info == NULL || prpl_info->get_cb_info == NULL) {
1013 pidgin_retrieve_user_info(conn, who ? who : name);
1014 g_free(who);
1015 return;
1018 show_retrieveing_info(conn, who ? who : name);
1019 prpl_info->get_cb_info(conn, chat, name);
1020 g_free(who);
1023 gboolean
1024 pidgin_parse_x_im_contact(const char *msg, gboolean all_accounts,
1025 PurpleAccount **ret_account, char **ret_protocol,
1026 char **ret_username, char **ret_alias)
1028 char *protocol = NULL;
1029 char *username = NULL;
1030 char *alias = NULL;
1031 char *str;
1032 char *c, *s;
1033 gboolean valid;
1035 g_return_val_if_fail(msg != NULL, FALSE);
1036 g_return_val_if_fail(ret_protocol != NULL, FALSE);
1037 g_return_val_if_fail(ret_username != NULL, FALSE);
1039 s = str = g_strdup(msg);
1041 while (*s != '\r' && *s != '\n' && *s != '\0')
1043 char *key, *value;
1045 key = s;
1047 /* Grab the key */
1048 while (*s != '\r' && *s != '\n' && *s != '\0' && *s != ' ')
1049 s++;
1051 if (*s == '\r') s++;
1053 if (*s == '\n')
1055 s++;
1056 continue;
1059 if (*s != '\0') *s++ = '\0';
1061 /* Clear past any whitespace */
1062 while (*s != '\0' && *s == ' ')
1063 s++;
1065 /* Now let's grab until the end of the line. */
1066 value = s;
1068 while (*s != '\r' && *s != '\n' && *s != '\0')
1069 s++;
1071 if (*s == '\r') *s++ = '\0';
1072 if (*s == '\n') *s++ = '\0';
1074 if ((c = strchr(key, ':')) != NULL)
1076 if (!g_ascii_strcasecmp(key, "X-IM-Username:"))
1077 username = g_strdup(value);
1078 else if (!g_ascii_strcasecmp(key, "X-IM-Protocol:"))
1079 protocol = g_strdup(value);
1080 else if (!g_ascii_strcasecmp(key, "X-IM-Alias:"))
1081 alias = g_strdup(value);
1085 if (username != NULL && protocol != NULL)
1087 valid = TRUE;
1089 *ret_username = username;
1090 *ret_protocol = protocol;
1092 if (ret_alias != NULL)
1093 *ret_alias = alias;
1095 /* Check for a compatible account. */
1096 if (ret_account != NULL)
1098 GList *list;
1099 PurpleAccount *account = NULL;
1100 GList *l;
1101 const char *protoname;
1103 if (all_accounts)
1104 list = purple_accounts_get_all();
1105 else
1106 list = purple_connections_get_all();
1108 for (l = list; l != NULL; l = l->next)
1110 PurpleConnection *gc;
1111 PurplePluginProtocolInfo *prpl_info = NULL;
1112 PurplePlugin *plugin;
1114 if (all_accounts)
1116 account = (PurpleAccount *)l->data;
1118 plugin = purple_plugins_find_with_id(
1119 purple_account_get_protocol_id(account));
1121 if (plugin == NULL)
1123 account = NULL;
1125 continue;
1128 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin);
1130 else
1132 gc = (PurpleConnection *)l->data;
1133 account = purple_connection_get_account(gc);
1135 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
1138 protoname = prpl_info->list_icon(account, NULL);
1140 if (!strcmp(protoname, protocol))
1141 break;
1143 account = NULL;
1146 /* Special case for AIM and ICQ */
1147 if (account == NULL && (!strcmp(protocol, "aim") ||
1148 !strcmp(protocol, "icq")))
1150 for (l = list; l != NULL; l = l->next)
1152 PurpleConnection *gc;
1153 PurplePluginProtocolInfo *prpl_info = NULL;
1154 PurplePlugin *plugin;
1156 if (all_accounts)
1158 account = (PurpleAccount *)l->data;
1160 plugin = purple_plugins_find_with_id(
1161 purple_account_get_protocol_id(account));
1163 if (plugin == NULL)
1165 account = NULL;
1167 continue;
1170 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin);
1172 else
1174 gc = (PurpleConnection *)l->data;
1175 account = purple_connection_get_account(gc);
1177 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
1180 protoname = prpl_info->list_icon(account, NULL);
1182 if (!strcmp(protoname, "aim") || !strcmp(protoname, "icq"))
1183 break;
1185 account = NULL;
1189 *ret_account = account;
1192 else
1194 valid = FALSE;
1196 g_free(username);
1197 g_free(protocol);
1198 g_free(alias);
1201 g_free(str);
1203 return valid;
1206 void
1207 pidgin_set_accessible_label (GtkWidget *w, GtkWidget *l)
1209 AtkObject *acc;
1210 const gchar *label_text;
1211 const gchar *existing_name;
1213 acc = gtk_widget_get_accessible (w);
1215 /* If this object has no name, set it's name with the label text */
1216 existing_name = atk_object_get_name (acc);
1217 if (!existing_name) {
1218 label_text = gtk_label_get_text (GTK_LABEL(l));
1219 if (label_text)
1220 atk_object_set_name (acc, label_text);
1223 pidgin_set_accessible_relations(w, l);
1226 void
1227 pidgin_set_accessible_relations (GtkWidget *w, GtkWidget *l)
1229 AtkObject *acc, *label;
1230 AtkObject *rel_obj[1];
1231 AtkRelationSet *set;
1232 AtkRelation *relation;
1234 acc = gtk_widget_get_accessible (w);
1235 label = gtk_widget_get_accessible (l);
1237 /* Make sure mnemonics work */
1238 gtk_label_set_mnemonic_widget(GTK_LABEL(l), w);
1240 /* Create the labeled-by relation */
1241 set = atk_object_ref_relation_set (acc);
1242 rel_obj[0] = label;
1243 relation = atk_relation_new (rel_obj, 1, ATK_RELATION_LABELLED_BY);
1244 atk_relation_set_add (set, relation);
1245 g_object_unref (relation);
1246 g_object_unref(set);
1248 /* Create the label-for relation */
1249 set = atk_object_ref_relation_set (label);
1250 rel_obj[0] = acc;
1251 relation = atk_relation_new (rel_obj, 1, ATK_RELATION_LABEL_FOR);
1252 atk_relation_set_add (set, relation);
1253 g_object_unref (relation);
1254 g_object_unref(set);
1257 void
1258 pidgin_menu_position_func_helper(GtkMenu *menu,
1259 gint *x,
1260 gint *y,
1261 gboolean *push_in,
1262 gpointer data)
1264 GtkWidget *widget;
1265 GtkRequisition requisition;
1266 GdkScreen *screen;
1267 GdkRectangle monitor;
1268 gint monitor_num;
1269 gint space_left, space_right, space_above, space_below;
1270 gint needed_width;
1271 gint needed_height;
1272 gint xthickness;
1273 gint ythickness;
1274 gboolean rtl;
1276 g_return_if_fail(GTK_IS_MENU(menu));
1278 widget = GTK_WIDGET(menu);
1279 screen = gtk_widget_get_screen(widget);
1280 xthickness = widget->style->xthickness;
1281 ythickness = widget->style->ythickness;
1282 rtl = (gtk_widget_get_direction(widget) == GTK_TEXT_DIR_RTL);
1285 * We need the requisition to figure out the right place to
1286 * popup the menu. In fact, we always need to ask here, since
1287 * if a size_request was queued while we weren't popped up,
1288 * the requisition won't have been recomputed yet.
1290 gtk_widget_size_request (widget, &requisition);
1292 monitor_num = gdk_screen_get_monitor_at_point (screen, *x, *y);
1294 push_in = FALSE;
1297 * The placement of popup menus horizontally works like this (with
1298 * RTL in parentheses)
1300 * - If there is enough room to the right (left) of the mouse cursor,
1301 * position the menu there.
1303 * - Otherwise, if if there is enough room to the left (right) of the
1304 * mouse cursor, position the menu there.
1306 * - Otherwise if the menu is smaller than the monitor, position it
1307 * on the side of the mouse cursor that has the most space available
1309 * - Otherwise (if there is simply not enough room for the menu on the
1310 * monitor), position it as far left (right) as possible.
1312 * Positioning in the vertical direction is similar: first try below
1313 * mouse cursor, then above.
1315 gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
1317 space_left = *x - monitor.x;
1318 space_right = monitor.x + monitor.width - *x - 1;
1319 space_above = *y - monitor.y;
1320 space_below = monitor.y + monitor.height - *y - 1;
1322 /* position horizontally */
1324 /* the amount of space we need to position the menu. Note the
1325 * menu is offset "xthickness" pixels
1327 needed_width = requisition.width - xthickness;
1329 if (needed_width <= space_left ||
1330 needed_width <= space_right)
1332 if ((rtl && needed_width <= space_left) ||
1333 (!rtl && needed_width > space_right))
1335 /* position left */
1336 *x = *x + xthickness - requisition.width + 1;
1338 else
1340 /* position right */
1341 *x = *x - xthickness;
1344 /* x is clamped on-screen further down */
1346 else if (requisition.width <= monitor.width)
1348 /* the menu is too big to fit on either side of the mouse
1349 * cursor, but smaller than the monitor. Position it on
1350 * the side that has the most space
1352 if (space_left > space_right)
1354 /* left justify */
1355 *x = monitor.x;
1357 else
1359 /* right justify */
1360 *x = monitor.x + monitor.width - requisition.width;
1363 else /* menu is simply too big for the monitor */
1365 if (rtl)
1367 /* right justify */
1368 *x = monitor.x + monitor.width - requisition.width;
1370 else
1372 /* left justify */
1373 *x = monitor.x;
1377 /* Position vertically. The algorithm is the same as above, but
1378 * simpler because we don't have to take RTL into account.
1380 needed_height = requisition.height - ythickness;
1382 if (needed_height <= space_above ||
1383 needed_height <= space_below)
1385 if (needed_height <= space_below)
1386 *y = *y - ythickness;
1387 else
1388 *y = *y + ythickness - requisition.height + 1;
1390 *y = CLAMP (*y, monitor.y,
1391 monitor.y + monitor.height - requisition.height);
1393 else if (needed_height > space_below && needed_height > space_above)
1395 if (space_below >= space_above)
1396 *y = monitor.y + monitor.height - requisition.height;
1397 else
1398 *y = monitor.y;
1400 else
1402 *y = monitor.y;
1407 void
1408 pidgin_treeview_popup_menu_position_func(GtkMenu *menu,
1409 gint *x,
1410 gint *y,
1411 gboolean *push_in,
1412 gpointer data)
1414 GtkWidget *widget = GTK_WIDGET(data);
1415 GtkTreeView *tv = GTK_TREE_VIEW(data);
1416 GtkTreePath *path;
1417 GtkTreeViewColumn *col;
1418 GdkRectangle rect;
1419 gint ythickness = GTK_WIDGET(menu)->style->ythickness;
1421 gdk_window_get_origin (widget->window, x, y);
1422 gtk_tree_view_get_cursor (tv, &path, &col);
1423 gtk_tree_view_get_cell_area (tv, path, col, &rect);
1425 *x += rect.x+rect.width;
1426 *y += rect.y+rect.height+ythickness;
1427 pidgin_menu_position_func_helper(menu, x, y, push_in, data);
1430 enum {
1431 DND_FILE_TRANSFER,
1432 DND_IM_IMAGE,
1433 DND_BUDDY_ICON
1436 typedef struct {
1437 char *filename;
1438 PurpleAccount *account;
1439 char *who;
1440 } _DndData;
1442 static void dnd_image_ok_callback(_DndData *data, int choice)
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 id = purple_imgstore_add_with_id(filedata, size, data->filename);
1500 gtk_text_buffer_get_iter_at_mark(GTK_IMHTML(gtkconv->entry)->text_buffer, &iter,
1501 gtk_text_buffer_get_insert(GTK_IMHTML(gtkconv->entry)->text_buffer));
1502 gtk_imhtml_insert_image_at_iter(GTK_IMHTML(gtkconv->entry), id, &iter);
1503 purple_imgstore_unref_by_id(id);
1505 break;
1507 g_free(data->filename);
1508 g_free(data->who);
1509 g_free(data);
1512 static void dnd_image_cancel_callback(_DndData *data, int choice)
1514 g_free(data->filename);
1515 g_free(data->who);
1516 g_free(data);
1519 static void dnd_set_icon_ok_cb(_DndData *data)
1521 dnd_image_ok_callback(data, DND_BUDDY_ICON);
1524 static void dnd_set_icon_cancel_cb(_DndData *data)
1526 g_free(data->filename);
1527 g_free(data->who);
1528 g_free(data);
1531 void
1532 pidgin_dnd_file_manage(GtkSelectionData *sd, PurpleAccount *account, const char *who)
1534 GList *tmp;
1535 GdkPixbuf *pb;
1536 GList *files = purple_uri_list_extract_filenames((const gchar *)sd->data);
1537 PurpleConnection *gc = purple_account_get_connection(account);
1538 PurplePluginProtocolInfo *prpl_info = NULL;
1539 gboolean file_send_ok = FALSE;
1540 #ifndef _WIN32
1541 PurpleDesktopItem *item;
1542 #endif
1544 g_return_if_fail(account != NULL);
1545 g_return_if_fail(who != NULL);
1547 for(tmp = files; tmp != NULL ; tmp = g_list_next(tmp)) {
1548 gchar *filename = tmp->data;
1549 gchar *basename = g_path_get_basename(filename);
1551 /* Set the default action: don't send anything */
1552 file_send_ok = FALSE;
1554 /* XXX - Make ft API support creating a transfer with more than one file */
1555 if (!g_file_test(filename, G_FILE_TEST_EXISTS)) {
1556 continue;
1559 /* XXX - make ft api suupport sending a directory */
1560 /* Are we dealing with a directory? */
1561 if (g_file_test(filename, G_FILE_TEST_IS_DIR)) {
1562 char *str, *str2;
1564 str = g_strdup_printf(_("Cannot send folder %s."), basename);
1565 str2 = g_strdup_printf(_("%s cannot transfer a folder. You will need to send the files within individually."), PIDGIN_NAME);
1567 purple_notify_error(NULL, NULL,
1568 str, str2);
1570 g_free(str);
1571 g_free(str2);
1573 continue;
1576 /* Are we dealing with an image? */
1577 pb = gdk_pixbuf_new_from_file(filename, NULL);
1578 if (pb) {
1579 _DndData *data = g_malloc(sizeof(_DndData));
1580 gboolean ft = FALSE, im = FALSE;
1582 data->who = g_strdup(who);
1583 data->filename = g_strdup(filename);
1584 data->account = account;
1586 if (gc)
1587 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
1589 if (prpl_info && prpl_info->options & OPT_PROTO_IM_IMAGE)
1590 im = TRUE;
1592 if (prpl_info && prpl_info->can_receive_file)
1593 ft = prpl_info->can_receive_file(gc, who);
1594 else if (prpl_info && prpl_info->send_file)
1595 ft = TRUE;
1597 if (im && ft)
1598 purple_request_choice(NULL, NULL,
1599 _("You have dragged an image"),
1600 _("You can send this image as a file transfer, "
1601 "embed it into this message, or use it as the buddy icon for this user."),
1602 DND_FILE_TRANSFER, "OK", (GCallback)dnd_image_ok_callback,
1603 "Cancel", (GCallback)dnd_image_cancel_callback,
1604 account, who, NULL,
1605 data,
1606 _("Set as buddy icon"), DND_BUDDY_ICON,
1607 _("Send image file"), DND_FILE_TRANSFER,
1608 _("Insert in message"), DND_IM_IMAGE,
1609 NULL);
1610 else if (!(im || ft))
1611 purple_request_yes_no(NULL, NULL, _("You have dragged an image"),
1612 _("Would you like to set it as the buddy icon for this user?"),
1613 PURPLE_DEFAULT_ACTION_NONE,
1614 account, who, NULL,
1615 data, (GCallback)dnd_set_icon_ok_cb, (GCallback)dnd_set_icon_cancel_cb);
1616 else
1617 purple_request_choice(NULL, NULL,
1618 _("You have dragged an image"),
1619 (ft ? _("You can send this image as a file transfer, or use it as the buddy icon for this user.") :
1620 _("You can insert this image into this message, or use it as the buddy icon for this user")),
1621 (ft ? DND_FILE_TRANSFER : DND_IM_IMAGE),
1622 "OK", (GCallback)dnd_image_ok_callback,
1623 "Cancel", (GCallback)dnd_image_cancel_callback,
1624 account, who, NULL,
1625 data,
1626 _("Set as buddy icon"), DND_BUDDY_ICON,
1627 (ft ? _("Send image file") : _("Insert in message")), (ft ? DND_FILE_TRANSFER : DND_IM_IMAGE),
1628 NULL);
1629 g_object_unref(G_OBJECT(pb));
1630 return;
1633 #ifndef _WIN32
1634 /* Are we trying to send a .desktop file? */
1635 else if (purple_str_has_suffix(basename, ".desktop") && (item = purple_desktop_item_new_from_file(filename))) {
1636 PurpleDesktopItemType dtype;
1637 char key[64];
1638 const char *itemname = NULL;
1640 const char * const *langs;
1641 int i;
1642 langs = g_get_language_names();
1643 for (i = 0; langs[i]; i++) {
1644 g_snprintf(key, sizeof(key), "Name[%s]", langs[i]);
1645 itemname = purple_desktop_item_get_string(item, key);
1646 break;
1649 if (!itemname)
1650 itemname = purple_desktop_item_get_string(item, "Name");
1652 dtype = purple_desktop_item_get_entry_type(item);
1653 switch (dtype) {
1654 PurpleConversation *conv;
1655 PidginConversation *gtkconv;
1657 case PURPLE_DESKTOP_ITEM_TYPE_LINK:
1658 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, who);
1659 gtkconv = PIDGIN_CONVERSATION(conv);
1660 gtk_imhtml_insert_link(GTK_IMHTML(gtkconv->entry),
1661 gtk_text_buffer_get_insert(GTK_IMHTML(gtkconv->entry)->text_buffer),
1662 purple_desktop_item_get_string(item, "URL"), itemname);
1663 break;
1664 default:
1665 /* I don't know if we really want to do anything here. Most of
1666 * the desktop item types are crap like "MIME Type" (I have no
1667 * clue how that would be a desktop item) and "Comment"...
1668 * nothing we can really send. The only logical one is
1669 * "Application," but do we really want to send a binary and
1670 * nothing else? Probably not. I'll just give an error and
1671 * return. */
1672 /* The original patch sent the icon used by the launcher. That's probably wrong */
1673 purple_notify_error(NULL, NULL, _("Cannot send launcher"),
1674 _("You dragged a desktop launcher. Most "
1675 "likely you wanted to send the target "
1676 "of this launcher instead of this "
1677 "launcher itself."));
1678 break;
1680 purple_desktop_item_unref(item);
1681 return;
1683 #endif /* _WIN32 */
1685 /* Everything is fine, let's send */
1686 serv_send_file(gc, who, filename);
1687 g_free(filename);
1689 g_list_free(files);
1692 void pidgin_buddy_icon_get_scale_size(GdkPixbuf *buf, PurpleBuddyIconSpec *spec, PurpleIconScaleRules rules, int *width, int *height)
1694 *width = gdk_pixbuf_get_width(buf);
1695 *height = gdk_pixbuf_get_height(buf);
1697 if ((spec == NULL) || !(spec->scale_rules & rules))
1698 return;
1700 purple_buddy_icon_get_scale_size(spec, width, height);
1702 /* and now for some arbitrary sanity checks */
1703 if(*width > 100)
1704 *width = 100;
1705 if(*height > 100)
1706 *height = 100;
1709 GdkPixbuf * pidgin_create_status_icon(PurpleStatusPrimitive prim, GtkWidget *w, const char *size)
1711 GtkIconSize icon_size = gtk_icon_size_from_name(size);
1712 GdkPixbuf *pixbuf = NULL;
1713 const char *stock = pidgin_stock_id_from_status_primitive(prim);
1715 pixbuf = gtk_widget_render_icon (w, stock ? stock : PIDGIN_STOCK_STATUS_AVAILABLE,
1716 icon_size, "GtkWidget");
1717 return pixbuf;
1720 static const char *
1721 stock_id_from_status_primitive_idle(PurpleStatusPrimitive prim, gboolean idle)
1723 const char *stock = NULL;
1724 switch (prim) {
1725 case PURPLE_STATUS_UNSET:
1726 stock = NULL;
1727 break;
1728 case PURPLE_STATUS_UNAVAILABLE:
1729 stock = idle ? PIDGIN_STOCK_STATUS_BUSY_I : PIDGIN_STOCK_STATUS_BUSY;
1730 break;
1731 case PURPLE_STATUS_AWAY:
1732 stock = idle ? PIDGIN_STOCK_STATUS_AWAY_I : PIDGIN_STOCK_STATUS_AWAY;
1733 break;
1734 case PURPLE_STATUS_EXTENDED_AWAY:
1735 stock = idle ? PIDGIN_STOCK_STATUS_XA_I : PIDGIN_STOCK_STATUS_XA;
1736 break;
1737 case PURPLE_STATUS_INVISIBLE:
1738 stock = PIDGIN_STOCK_STATUS_INVISIBLE;
1739 break;
1740 case PURPLE_STATUS_OFFLINE:
1741 stock = idle ? PIDGIN_STOCK_STATUS_OFFLINE_I : PIDGIN_STOCK_STATUS_OFFLINE;
1742 break;
1743 default:
1744 stock = idle ? PIDGIN_STOCK_STATUS_AVAILABLE_I : PIDGIN_STOCK_STATUS_AVAILABLE;
1745 break;
1747 return stock;
1750 const char *
1751 pidgin_stock_id_from_status_primitive(PurpleStatusPrimitive prim)
1753 return stock_id_from_status_primitive_idle(prim, FALSE);
1756 const char *
1757 pidgin_stock_id_from_presence(PurplePresence *presence)
1759 PurpleStatus *status;
1760 PurpleStatusType *type;
1761 PurpleStatusPrimitive prim;
1762 gboolean idle;
1764 g_return_val_if_fail(presence, NULL);
1766 status = purple_presence_get_active_status(presence);
1767 type = purple_status_get_type(status);
1768 prim = purple_status_type_get_primitive(type);
1770 idle = purple_presence_is_idle(presence);
1772 return stock_id_from_status_primitive_idle(prim, idle);
1775 GdkPixbuf *
1776 pidgin_create_prpl_icon(PurpleAccount *account, PidginPrplIconSize size)
1778 PurplePlugin *prpl;
1780 g_return_val_if_fail(account != NULL, NULL);
1782 prpl = purple_find_prpl(purple_account_get_protocol_id(account));
1783 if (prpl == NULL)
1784 return NULL;
1785 return pidgin_create_prpl_icon_from_prpl(prpl, size, account);
1788 static void
1789 menu_action_cb(GtkMenuItem *item, gpointer object)
1791 gpointer data;
1792 void (*callback)(gpointer, gpointer);
1794 callback = g_object_get_data(G_OBJECT(item), "purplecallback");
1795 data = g_object_get_data(G_OBJECT(item), "purplecallbackdata");
1797 if (callback)
1798 callback(object, data);
1801 GtkWidget *
1802 pidgin_append_menu_action(GtkWidget *menu, PurpleMenuAction *act,
1803 gpointer object)
1805 GtkWidget *menuitem;
1807 if (act == NULL) {
1808 return pidgin_separator(menu);
1811 if (act->children == NULL) {
1812 menuitem = gtk_menu_item_new_with_mnemonic(act->label);
1814 if (act->callback != NULL) {
1815 g_object_set_data(G_OBJECT(menuitem),
1816 "purplecallback",
1817 act->callback);
1818 g_object_set_data(G_OBJECT(menuitem),
1819 "purplecallbackdata",
1820 act->data);
1821 g_signal_connect(G_OBJECT(menuitem), "activate",
1822 G_CALLBACK(menu_action_cb),
1823 object);
1824 } else {
1825 gtk_widget_set_sensitive(menuitem, FALSE);
1828 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1829 } else {
1830 GList *l = NULL;
1831 GtkWidget *submenu = NULL;
1832 GtkAccelGroup *group;
1834 menuitem = gtk_menu_item_new_with_mnemonic(act->label);
1835 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1837 submenu = gtk_menu_new();
1838 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
1840 group = gtk_menu_get_accel_group(GTK_MENU(menu));
1841 if (group) {
1842 char *path = g_strdup_printf("%s/%s", GTK_MENU_ITEM(menuitem)->accel_path, act->label);
1843 gtk_menu_set_accel_path(GTK_MENU(submenu), path);
1844 g_free(path);
1845 gtk_menu_set_accel_group(GTK_MENU(submenu), group);
1848 for (l = act->children; l; l = l->next) {
1849 PurpleMenuAction *act = (PurpleMenuAction *)l->data;
1851 pidgin_append_menu_action(submenu, act, object);
1853 g_list_free(act->children);
1854 act->children = NULL;
1856 purple_menu_action_free(act);
1857 return menuitem;
1860 typedef struct
1862 GtkWidget *entry;
1863 GtkWidget *accountopt;
1865 PidginFilterBuddyCompletionEntryFunc filter_func;
1866 gpointer filter_func_user_data;
1868 GtkListStore *store;
1869 } PidginCompletionData;
1871 static gboolean buddyname_completion_match_func(GtkEntryCompletion *completion,
1872 const gchar *key, GtkTreeIter *iter, gpointer user_data)
1874 GtkTreeModel *model;
1875 GValue val1;
1876 GValue val2;
1877 const char *tmp;
1879 model = gtk_entry_completion_get_model (completion);
1881 val1.g_type = 0;
1882 gtk_tree_model_get_value(model, iter, 2, &val1);
1883 tmp = g_value_get_string(&val1);
1884 if (tmp != NULL && purple_str_has_prefix(tmp, key))
1886 g_value_unset(&val1);
1887 return TRUE;
1889 g_value_unset(&val1);
1891 val2.g_type = 0;
1892 gtk_tree_model_get_value(model, iter, 3, &val2);
1893 tmp = g_value_get_string(&val2);
1894 if (tmp != NULL && purple_str_has_prefix(tmp, key))
1896 g_value_unset(&val2);
1897 return TRUE;
1899 g_value_unset(&val2);
1901 return FALSE;
1904 static gboolean buddyname_completion_match_selected_cb(GtkEntryCompletion *completion,
1905 GtkTreeModel *model, GtkTreeIter *iter, PidginCompletionData *data)
1907 GValue val;
1908 GtkWidget *optmenu = data->accountopt;
1909 PurpleAccount *account;
1911 val.g_type = 0;
1912 gtk_tree_model_get_value(model, iter, 1, &val);
1913 gtk_entry_set_text(GTK_ENTRY(data->entry), g_value_get_string(&val));
1914 g_value_unset(&val);
1916 gtk_tree_model_get_value(model, iter, 4, &val);
1917 account = g_value_get_pointer(&val);
1918 g_value_unset(&val);
1920 if (account == NULL)
1921 return TRUE;
1923 if (optmenu != NULL)
1924 aop_option_menu_select_by_data(optmenu, account);
1926 return TRUE;
1929 static void
1930 add_buddyname_autocomplete_entry(GtkListStore *store, const char *buddy_alias, const char *contact_alias,
1931 const PurpleAccount *account, const char *buddyname)
1933 GtkTreeIter iter;
1934 gboolean completion_added = FALSE;
1935 gchar *normalized_buddyname;
1936 gchar *tmp;
1938 tmp = g_utf8_normalize(buddyname, -1, G_NORMALIZE_DEFAULT);
1939 normalized_buddyname = g_utf8_casefold(tmp, -1);
1940 g_free(tmp);
1942 /* There's no sense listing things like: 'xxx "xxx"'
1943 when the name and buddy alias match. */
1944 if (buddy_alias && strcmp(buddy_alias, buddyname)) {
1945 char *completion_entry = g_strdup_printf("%s \"%s\"", buddyname, buddy_alias);
1946 char *tmp2 = g_utf8_normalize(buddy_alias, -1, G_NORMALIZE_DEFAULT);
1948 tmp = g_utf8_casefold(tmp2, -1);
1949 g_free(tmp2);
1951 gtk_list_store_append(store, &iter);
1952 gtk_list_store_set(store, &iter,
1953 0, completion_entry,
1954 1, buddyname,
1955 2, normalized_buddyname,
1956 3, tmp,
1957 4, account,
1958 -1);
1959 g_free(completion_entry);
1960 g_free(tmp);
1961 completion_added = TRUE;
1964 /* There's no sense listing things like: 'xxx "xxx"'
1965 when the name and contact alias match. */
1966 if (contact_alias && strcmp(contact_alias, buddyname)) {
1967 /* We don't want duplicates when the contact and buddy alias match. */
1968 if (!buddy_alias || strcmp(contact_alias, buddy_alias)) {
1969 char *completion_entry = g_strdup_printf("%s \"%s\"",
1970 buddyname, contact_alias);
1971 char *tmp2 = g_utf8_normalize(contact_alias, -1, G_NORMALIZE_DEFAULT);
1973 tmp = g_utf8_casefold(tmp2, -1);
1974 g_free(tmp2);
1976 gtk_list_store_append(store, &iter);
1977 gtk_list_store_set(store, &iter,
1978 0, completion_entry,
1979 1, buddyname,
1980 2, normalized_buddyname,
1981 3, tmp,
1982 4, account,
1983 -1);
1984 g_free(completion_entry);
1985 g_free(tmp);
1986 completion_added = TRUE;
1990 if (completion_added == FALSE) {
1991 /* Add the buddy's name. */
1992 gtk_list_store_append(store, &iter);
1993 gtk_list_store_set(store, &iter,
1994 0, buddyname,
1995 1, buddyname,
1996 2, normalized_buddyname,
1997 3, NULL,
1998 4, account,
1999 -1);
2002 g_free(normalized_buddyname);
2005 static void get_log_set_name(PurpleLogSet *set, gpointer value, PidginCompletionData *data)
2007 PidginFilterBuddyCompletionEntryFunc filter_func = data->filter_func;
2008 gpointer user_data = data->filter_func_user_data;
2010 /* 1. Don't show buddies because we will have gotten them already.
2011 * 2. The boxes that use this autocomplete code handle only IMs. */
2012 if (!set->buddy && set->type == PURPLE_LOG_IM) {
2013 PidginBuddyCompletionEntry entry;
2014 entry.is_buddy = FALSE;
2015 entry.entry.logged_buddy = set;
2017 if (filter_func(&entry, user_data)) {
2018 add_buddyname_autocomplete_entry(data->store,
2019 NULL, NULL, set->account, set->name);
2024 static void
2025 add_completion_list(PidginCompletionData *data)
2027 PurpleBlistNode *gnode, *cnode, *bnode;
2028 PidginFilterBuddyCompletionEntryFunc filter_func = data->filter_func;
2029 gpointer user_data = data->filter_func_user_data;
2030 GHashTable *sets;
2032 gtk_list_store_clear(data->store);
2034 for (gnode = purple_get_blist()->root; gnode != NULL; gnode = gnode->next)
2036 if (!PURPLE_BLIST_NODE_IS_GROUP(gnode))
2037 continue;
2039 for (cnode = gnode->child; cnode != NULL; cnode = cnode->next)
2041 if (!PURPLE_BLIST_NODE_IS_CONTACT(cnode))
2042 continue;
2044 for (bnode = cnode->child; bnode != NULL; bnode = bnode->next)
2046 PidginBuddyCompletionEntry entry;
2047 entry.is_buddy = TRUE;
2048 entry.entry.buddy = (PurpleBuddy *) bnode;
2050 if (filter_func(&entry, user_data)) {
2051 add_buddyname_autocomplete_entry(data->store,
2052 ((PurpleContact *)cnode)->alias,
2053 purple_buddy_get_contact_alias(entry.entry.buddy),
2054 entry.entry.buddy->account,
2055 entry.entry.buddy->name
2062 sets = purple_log_get_log_sets();
2063 g_hash_table_foreach(sets, (GHFunc)get_log_set_name, data);
2064 g_hash_table_destroy(sets);
2068 static void
2069 buddyname_autocomplete_destroyed_cb(GtkWidget *widget, gpointer data)
2071 g_free(data);
2072 purple_signals_disconnect_by_handle(widget);
2075 static void
2076 repopulate_autocomplete(gpointer something, gpointer data)
2078 add_completion_list(data);
2081 void
2082 pidgin_setup_screenname_autocomplete_with_filter(GtkWidget *entry, GtkWidget *accountopt, PidginFilterBuddyCompletionEntryFunc filter_func, gpointer user_data)
2084 PidginCompletionData *data;
2087 * Store the displayed completion value, the buddy name, the UTF-8
2088 * normalized & casefolded buddy name, the UTF-8 normalized &
2089 * casefolded value for comparison, and the account.
2091 GtkListStore *store;
2093 GtkEntryCompletion *completion;
2095 data = g_new0(PidginCompletionData, 1);
2096 store = gtk_list_store_new(5, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER);
2098 data->entry = entry;
2099 data->accountopt = accountopt;
2100 if (filter_func == NULL) {
2101 data->filter_func = pidgin_screenname_autocomplete_default_filter;
2102 data->filter_func_user_data = NULL;
2103 } else {
2104 data->filter_func = filter_func;
2105 data->filter_func_user_data = user_data;
2107 data->store = store;
2109 add_completion_list(data);
2111 /* Sort the completion list by buddy name */
2112 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store),
2113 1, GTK_SORT_ASCENDING);
2115 completion = gtk_entry_completion_new();
2116 gtk_entry_completion_set_match_func(completion, buddyname_completion_match_func, NULL, NULL);
2118 g_signal_connect(G_OBJECT(completion), "match-selected",
2119 G_CALLBACK(buddyname_completion_match_selected_cb), data);
2121 gtk_entry_set_completion(GTK_ENTRY(entry), completion);
2122 g_object_unref(completion);
2124 gtk_entry_completion_set_model(completion, GTK_TREE_MODEL(store));
2125 g_object_unref(store);
2127 gtk_entry_completion_set_text_column(completion, 0);
2129 purple_signal_connect(purple_connections_get_handle(), "signed-on", entry,
2130 PURPLE_CALLBACK(repopulate_autocomplete), data);
2131 purple_signal_connect(purple_connections_get_handle(), "signed-off", entry,
2132 PURPLE_CALLBACK(repopulate_autocomplete), data);
2134 purple_signal_connect(purple_accounts_get_handle(), "account-added", entry,
2135 PURPLE_CALLBACK(repopulate_autocomplete), data);
2136 purple_signal_connect(purple_accounts_get_handle(), "account-removed", entry,
2137 PURPLE_CALLBACK(repopulate_autocomplete), data);
2139 g_signal_connect(G_OBJECT(entry), "destroy", G_CALLBACK(buddyname_autocomplete_destroyed_cb), data);
2142 gboolean
2143 pidgin_screenname_autocomplete_default_filter(const PidginBuddyCompletionEntry *completion_entry, gpointer all_accounts) {
2144 gboolean all = GPOINTER_TO_INT(all_accounts);
2146 if (completion_entry->is_buddy) {
2147 return all || purple_account_is_connected(completion_entry->entry.buddy->account);
2148 } else {
2149 return all || (completion_entry->entry.logged_buddy->account != NULL && purple_account_is_connected(completion_entry->entry.logged_buddy->account));
2153 void
2154 pidgin_setup_screenname_autocomplete(GtkWidget *entry, GtkWidget *accountopt, gboolean all) {
2155 pidgin_setup_screenname_autocomplete_with_filter(entry, accountopt, pidgin_screenname_autocomplete_default_filter, GINT_TO_POINTER(all));
2160 void pidgin_set_cursor(GtkWidget *widget, GdkCursorType cursor_type)
2162 GdkCursor *cursor;
2164 g_return_if_fail(widget != NULL);
2165 if (widget->window == NULL)
2166 return;
2168 cursor = gdk_cursor_new(cursor_type);
2169 gdk_window_set_cursor(widget->window, cursor);
2170 gdk_cursor_unref(cursor);
2172 gdk_display_flush(gdk_drawable_get_display(GDK_DRAWABLE(widget->window)));
2175 void pidgin_clear_cursor(GtkWidget *widget)
2177 g_return_if_fail(widget != NULL);
2178 if (widget->window == NULL)
2179 return;
2181 gdk_window_set_cursor(widget->window, NULL);
2184 struct _icon_chooser {
2185 GtkWidget *icon_filesel;
2186 GtkWidget *icon_preview;
2187 GtkWidget *icon_text;
2189 void (*callback)(const char*,gpointer);
2190 gpointer data;
2193 static void
2194 icon_filesel_choose_cb(GtkWidget *widget, gint response, struct _icon_chooser *dialog)
2196 char *filename, *current_folder;
2198 if (response != GTK_RESPONSE_ACCEPT) {
2199 if (response == GTK_RESPONSE_CANCEL) {
2200 gtk_widget_destroy(dialog->icon_filesel);
2202 dialog->icon_filesel = NULL;
2203 if (dialog->callback)
2204 dialog->callback(NULL, dialog->data);
2205 g_free(dialog);
2206 return;
2209 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog->icon_filesel));
2210 current_folder = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(dialog->icon_filesel));
2211 if (current_folder != NULL) {
2212 purple_prefs_set_path(PIDGIN_PREFS_ROOT "/filelocations/last_icon_folder", current_folder);
2213 g_free(current_folder);
2217 if (dialog->callback)
2218 dialog->callback(filename, dialog->data);
2219 gtk_widget_destroy(dialog->icon_filesel);
2220 g_free(filename);
2221 g_free(dialog);
2225 static void
2226 icon_preview_change_cb(GtkFileChooser *widget, struct _icon_chooser *dialog)
2228 GdkPixbuf *pixbuf, *scale;
2229 int height, width;
2230 char *basename, *markup, *size;
2231 struct stat st;
2232 char *filename;
2234 filename = gtk_file_chooser_get_preview_filename(
2235 GTK_FILE_CHOOSER(dialog->icon_filesel));
2237 if (!filename || g_stat(filename, &st) || !(pixbuf = gdk_pixbuf_new_from_file(filename, NULL)))
2239 gtk_image_set_from_pixbuf(GTK_IMAGE(dialog->icon_preview), NULL);
2240 gtk_label_set_markup(GTK_LABEL(dialog->icon_text), "");
2241 g_free(filename);
2242 return;
2245 width = gdk_pixbuf_get_width(pixbuf);
2246 height = gdk_pixbuf_get_height(pixbuf);
2247 basename = g_path_get_basename(filename);
2248 size = purple_str_size_to_units(st.st_size);
2249 markup = g_strdup_printf(_("<b>File:</b> %s\n"
2250 "<b>File size:</b> %s\n"
2251 "<b>Image size:</b> %dx%d"),
2252 basename, size, width, height);
2254 scale = gdk_pixbuf_scale_simple(pixbuf, width * 50 / height,
2255 50, GDK_INTERP_BILINEAR);
2256 gtk_image_set_from_pixbuf(GTK_IMAGE(dialog->icon_preview), scale);
2257 gtk_label_set_markup(GTK_LABEL(dialog->icon_text), markup);
2259 g_object_unref(G_OBJECT(pixbuf));
2260 g_object_unref(G_OBJECT(scale));
2261 g_free(filename);
2262 g_free(basename);
2263 g_free(size);
2264 g_free(markup);
2268 GtkWidget *pidgin_buddy_icon_chooser_new(GtkWindow *parent, void(*callback)(const char *, gpointer), gpointer data) {
2269 struct _icon_chooser *dialog = g_new0(struct _icon_chooser, 1);
2271 GtkWidget *vbox;
2272 const char *current_folder;
2274 dialog->callback = callback;
2275 dialog->data = data;
2277 current_folder = purple_prefs_get_path(PIDGIN_PREFS_ROOT "/filelocations/last_icon_folder");
2279 dialog->icon_filesel = gtk_file_chooser_dialog_new(_("Buddy Icon"),
2280 parent,
2281 GTK_FILE_CHOOSER_ACTION_OPEN,
2282 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2283 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
2284 NULL);
2285 gtk_dialog_set_default_response(GTK_DIALOG(dialog->icon_filesel), GTK_RESPONSE_ACCEPT);
2286 if ((current_folder != NULL) && (*current_folder != '\0'))
2287 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog->icon_filesel),
2288 current_folder);
2290 dialog->icon_preview = gtk_image_new();
2291 dialog->icon_text = gtk_label_new(NULL);
2293 vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
2294 gtk_widget_set_size_request(GTK_WIDGET(vbox), -1, 50);
2295 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(dialog->icon_preview), TRUE, FALSE, 0);
2296 gtk_box_pack_end(GTK_BOX(vbox), GTK_WIDGET(dialog->icon_text), FALSE, FALSE, 0);
2297 gtk_widget_show_all(vbox);
2299 gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog->icon_filesel), vbox);
2300 gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(dialog->icon_filesel), TRUE);
2301 gtk_file_chooser_set_use_preview_label(GTK_FILE_CHOOSER(dialog->icon_filesel), FALSE);
2303 g_signal_connect(G_OBJECT(dialog->icon_filesel), "update-preview",
2304 G_CALLBACK(icon_preview_change_cb), dialog);
2305 g_signal_connect(G_OBJECT(dialog->icon_filesel), "response",
2306 G_CALLBACK(icon_filesel_choose_cb), dialog);
2307 icon_preview_change_cb(NULL, dialog);
2309 #ifdef _WIN32
2310 g_signal_connect(G_OBJECT(dialog->icon_filesel), "show",
2311 G_CALLBACK(winpidgin_ensure_onscreen), dialog->icon_filesel);
2312 #endif
2314 return dialog->icon_filesel;
2318 static gboolean
2319 str_array_match(char **a, char **b)
2321 int i, j;
2323 if (!a || !b)
2324 return FALSE;
2325 for (i = 0; a[i] != NULL; i++)
2326 for (j = 0; b[j] != NULL; j++)
2327 if (!g_ascii_strcasecmp(a[i], b[j]))
2328 return TRUE;
2329 return FALSE;
2332 gpointer
2333 pidgin_convert_buddy_icon(PurplePlugin *plugin, const char *path, size_t *len)
2335 PurplePluginProtocolInfo *prpl_info;
2336 char **prpl_formats;
2337 int width, height;
2338 char **pixbuf_formats = NULL;
2339 GdkPixbufFormat *format;
2340 GdkPixbuf *pixbuf;
2341 gchar *contents;
2342 gsize length;
2344 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin);
2346 g_return_val_if_fail(prpl_info->icon_spec.format != NULL, NULL);
2349 format = gdk_pixbuf_get_file_info(path, &width, &height);
2351 if (format == NULL)
2352 return NULL;
2354 pixbuf_formats = gdk_pixbuf_format_get_extensions(format);
2355 prpl_formats = g_strsplit(prpl_info->icon_spec.format,",",0);
2356 if (str_array_match(pixbuf_formats, prpl_formats) && /* This is an acceptable format AND */
2357 (!(prpl_info->icon_spec.scale_rules & PURPLE_ICON_SCALE_SEND) || /* The prpl doesn't scale before it sends OR */
2358 (prpl_info->icon_spec.min_width <= width &&
2359 prpl_info->icon_spec.max_width >= width &&
2360 prpl_info->icon_spec.min_height <= height &&
2361 prpl_info->icon_spec.max_height >= height))) /* The icon is the correct size */
2363 g_strfreev(prpl_formats);
2364 g_strfreev(pixbuf_formats);
2366 /* We don't need to scale the image. */
2367 contents = NULL;
2368 if (!g_file_get_contents(path, &contents, &length, NULL))
2370 g_free(contents);
2371 return NULL;
2374 else
2376 int i;
2377 GError *error = NULL;
2378 GdkPixbuf *scale;
2379 gboolean success = FALSE;
2380 char *filename = NULL;
2382 g_strfreev(pixbuf_formats);
2384 pixbuf = gdk_pixbuf_new_from_file(path, &error);
2385 if (error) {
2386 purple_debug_error("buddyicon", "Could not open icon for conversion: %s\n", error->message);
2387 g_error_free(error);
2388 g_strfreev(prpl_formats);
2389 return NULL;
2392 if ((prpl_info->icon_spec.scale_rules & PURPLE_ICON_SCALE_SEND) &&
2393 (width < prpl_info->icon_spec.min_width ||
2394 width > prpl_info->icon_spec.max_width ||
2395 height < prpl_info->icon_spec.min_height ||
2396 height > prpl_info->icon_spec.max_height))
2398 int new_width = width;
2399 int new_height = height;
2401 purple_buddy_icon_get_scale_size(&prpl_info->icon_spec, &new_width, &new_height);
2403 scale = gdk_pixbuf_scale_simple(pixbuf, new_width, new_height,
2404 GDK_INTERP_HYPER);
2405 g_object_unref(G_OBJECT(pixbuf));
2406 pixbuf = scale;
2409 for (i = 0; prpl_formats[i]; i++) {
2410 FILE *fp;
2412 g_free(filename);
2413 fp = purple_mkstemp(&filename, TRUE);
2414 if (!fp)
2416 g_free(filename);
2417 return NULL;
2419 fclose(fp);
2421 purple_debug_info("buddyicon", "Converting buddy icon to %s as %s\n", prpl_formats[i], filename);
2422 /* The "compression" param wasn't supported until gdk-pixbuf 2.8.
2423 * Using it in previous versions causes the save to fail (and an assert message). */
2424 if ((gdk_pixbuf_major_version > 2 || (gdk_pixbuf_major_version == 2
2425 && gdk_pixbuf_minor_version >= 8))
2426 && strcmp(prpl_formats[i], "png") == 0) {
2427 if (gdk_pixbuf_save(pixbuf, filename, prpl_formats[i],
2428 &error, "compression", "9", NULL)) {
2429 success = TRUE;
2430 break;
2432 } else if (gdk_pixbuf_save(pixbuf, filename, prpl_formats[i],
2433 &error, NULL)) {
2434 success = TRUE;
2435 break;
2438 /* The NULL checking is necessary due to this bug:
2439 * http://bugzilla.gnome.org/show_bug.cgi?id=405539 */
2440 purple_debug_warning("buddyicon", "Could not convert to %s: %s\n", prpl_formats[i],
2441 (error && error->message) ? error->message : "Unknown error");
2442 g_error_free(error);
2443 error = NULL;
2445 g_strfreev(prpl_formats);
2446 g_object_unref(G_OBJECT(pixbuf));
2447 if (!success) {
2448 purple_debug_error("buddyicon", "Could not convert icon to usable format.\n");
2449 return NULL;
2452 contents = NULL;
2453 if (!g_file_get_contents(filename, &contents, &length, NULL))
2455 purple_debug_error("buddyicon",
2456 "Could not read '%s', which we just wrote to disk.\n",
2457 filename);
2459 g_free(contents);
2460 g_free(filename);
2461 return NULL;
2464 g_unlink(filename);
2465 g_free(filename);
2468 /* Check the image size */
2470 * TODO: If the file is too big, it would be cool if we checked if
2471 * the prpl supported jpeg, and then we could convert to that
2472 * and use a lower quality setting.
2474 if ((prpl_info->icon_spec.max_filesize != 0) &&
2475 (length > prpl_info->icon_spec.max_filesize))
2477 gchar *tmp;
2478 tmp = g_strdup_printf(_("The file '%s' is too large for %s. Please try a smaller image.\n"),
2479 path, plugin->info->name);
2480 purple_notify_error(NULL, _("Icon Error"),
2481 _("Could not set icon"), tmp);
2482 purple_debug_info("buddyicon",
2483 "'%s' was converted to an image which is %" G_GSIZE_FORMAT
2484 " bytes, but the maximum icon size for %s is %" G_GSIZE_FORMAT
2485 " bytes\n", path, length, plugin->info->name,
2486 prpl_info->icon_spec.max_filesize);
2487 g_free(tmp);
2488 return NULL;
2491 if (len)
2492 *len = length;
2493 return contents;
2496 void pidgin_set_custom_buddy_icon(PurpleAccount *account, const char *who, const char *filename)
2498 PurpleBuddy *buddy;
2499 PurpleContact *contact;
2501 buddy = purple_find_buddy(account, who);
2502 if (!buddy) {
2503 purple_debug_info("custom-icon", "You can only set custom icon for someone in your buddylist.\n");
2504 return;
2507 contact = purple_buddy_get_contact(buddy);
2508 purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode*)contact, filename);
2511 char *pidgin_make_pretty_arrows(const char *str)
2513 char *ret;
2514 char **split = g_strsplit(str, "->", -1);
2515 ret = g_strjoinv("\342\207\250", split);
2516 g_strfreev(split);
2518 split = g_strsplit(ret, "<-", -1);
2519 g_free(ret);
2520 ret = g_strjoinv("\342\207\246", split);
2521 g_strfreev(split);
2523 return ret;
2526 void pidgin_set_urgent(GtkWindow *window, gboolean urgent)
2528 gtk_window_set_urgency_hint(window, urgent);
2529 #if defined _WIN32
2530 winpidgin_window_flash(window, urgent);
2531 #endif
2534 static GSList *minidialogs = NULL;
2536 static void *
2537 pidgin_utils_get_handle(void)
2539 static int handle;
2541 return &handle;
2544 static void connection_signed_off_cb(PurpleConnection *gc)
2546 GSList *list, *l_next;
2547 for (list = minidialogs; list; list = l_next) {
2548 l_next = list->next;
2549 if (g_object_get_data(G_OBJECT(list->data), "gc") == gc) {
2550 gtk_widget_destroy(GTK_WIDGET(list->data));
2555 static void alert_killed_cb(GtkWidget *widget)
2557 minidialogs = g_slist_remove(minidialogs, widget);
2560 struct _old_button_clicked_cb_data
2562 PidginUtilMiniDialogCallback cb;
2563 gpointer data;
2566 static void
2567 old_mini_dialog_button_clicked_cb(PidginMiniDialog *mini_dialog,
2568 GtkButton *button,
2569 gpointer user_data)
2571 struct _old_button_clicked_cb_data *data = user_data;
2572 data->cb(data->data, button);
2575 static void
2576 old_mini_dialog_destroy_cb(GtkWidget *dialog,
2577 GList *cb_datas)
2579 while (cb_datas != NULL)
2581 g_free(cb_datas->data);
2582 cb_datas = g_list_delete_link(cb_datas, cb_datas);
2586 GtkWidget *
2587 pidgin_make_mini_dialog(PurpleConnection *gc,
2588 const char *icon_name,
2589 const char *primary,
2590 const char *secondary,
2591 void *user_data,
2592 ...)
2594 PidginMiniDialog *mini_dialog;
2595 const char *button_text;
2596 GList *cb_datas = NULL;
2597 va_list args;
2598 static gboolean first_call = TRUE;
2600 if (first_call) {
2601 first_call = FALSE;
2602 purple_signal_connect(purple_connections_get_handle(), "signed-off",
2603 pidgin_utils_get_handle(),
2604 PURPLE_CALLBACK(connection_signed_off_cb), NULL);
2607 mini_dialog = pidgin_mini_dialog_new(primary, secondary, icon_name);
2608 g_object_set_data(G_OBJECT(mini_dialog), "gc" ,gc);
2609 g_signal_connect(G_OBJECT(mini_dialog), "destroy",
2610 G_CALLBACK(alert_killed_cb), NULL);
2612 va_start(args, user_data);
2613 while ((button_text = va_arg(args, char*))) {
2614 struct _old_button_clicked_cb_data *data = NULL;
2615 PidginMiniDialogCallback wrapper_cb = NULL;
2616 PidginUtilMiniDialogCallback callback =
2617 va_arg(args, PidginUtilMiniDialogCallback);
2619 if (callback != NULL) {
2620 data = g_new0(struct _old_button_clicked_cb_data, 1);
2621 data->cb = callback;
2622 data->data = user_data;
2623 wrapper_cb = old_mini_dialog_button_clicked_cb;
2625 pidgin_mini_dialog_add_button(mini_dialog, button_text,
2626 wrapper_cb, data);
2627 cb_datas = g_list_append(cb_datas, data);
2629 va_end(args);
2631 g_signal_connect(G_OBJECT(mini_dialog), "destroy",
2632 G_CALLBACK(old_mini_dialog_destroy_cb), cb_datas);
2634 return GTK_WIDGET(mini_dialog);
2638 * "This is so dead sexy."
2639 * "Two thumbs up."
2640 * "Best movie of the year."
2642 * This is the function that handles CTRL+F searching in the buddy list.
2643 * It finds the top-most buddy/group/chat/whatever containing the
2644 * entered string.
2646 * It's somewhat ineffecient, because we strip all the HTML from the
2647 * "name" column of the buddy list (because the GtkTreeModel does not
2648 * contain the screen name in a non-markedup format). But the alternative
2649 * is to add an extra column to the GtkTreeModel. And this function is
2650 * used rarely, so it shouldn't matter TOO much.
2652 gboolean pidgin_tree_view_search_equal_func(GtkTreeModel *model, gint column,
2653 const gchar *key, GtkTreeIter *iter, gpointer data)
2655 gchar *enteredstring;
2656 gchar *tmp;
2657 gchar *withmarkup;
2658 gchar *nomarkup;
2659 gchar *normalized;
2660 gboolean result;
2661 size_t i;
2662 size_t len;
2663 PangoLogAttr *log_attrs;
2664 gchar *word;
2666 if (g_ascii_strcasecmp(key, "Global Thermonuclear War") == 0)
2668 purple_notify_info(NULL, "WOPR",
2669 "Wouldn't you prefer a nice game of chess?", NULL);
2670 return FALSE;
2673 gtk_tree_model_get(model, iter, column, &withmarkup, -1);
2674 if (withmarkup == NULL) /* This is probably a separator */
2675 return TRUE;
2677 tmp = g_utf8_normalize(key, -1, G_NORMALIZE_DEFAULT);
2678 enteredstring = g_utf8_casefold(tmp, -1);
2679 g_free(tmp);
2681 nomarkup = purple_markup_strip_html(withmarkup);
2682 tmp = g_utf8_normalize(nomarkup, -1, G_NORMALIZE_DEFAULT);
2683 g_free(nomarkup);
2684 normalized = g_utf8_casefold(tmp, -1);
2685 g_free(tmp);
2687 if (purple_str_has_prefix(normalized, enteredstring))
2689 g_free(withmarkup);
2690 g_free(enteredstring);
2691 g_free(normalized);
2692 return FALSE;
2696 /* Use Pango to separate by words. */
2697 len = g_utf8_strlen(normalized, -1);
2698 log_attrs = g_new(PangoLogAttr, len + 1);
2700 pango_get_log_attrs(normalized, strlen(normalized), -1, NULL, log_attrs, len + 1);
2702 word = normalized;
2703 result = TRUE;
2704 for (i = 0; i < (len - 1) ; i++)
2706 if (log_attrs[i].is_word_start &&
2707 purple_str_has_prefix(word, enteredstring))
2709 result = FALSE;
2710 break;
2712 word = g_utf8_next_char(word);
2714 g_free(log_attrs);
2716 /* The non-Pango version. */
2717 #if 0
2718 word = normalized;
2719 result = TRUE;
2720 while (word[0] != '\0')
2722 gunichar c = g_utf8_get_char(word);
2723 if (!g_unichar_isalnum(c))
2725 word = g_utf8_find_next_char(word, NULL);
2726 if (purple_str_has_prefix(word, enteredstring))
2728 result = FALSE;
2729 break;
2732 else
2733 word = g_utf8_find_next_char(word, NULL);
2735 #endif
2737 g_free(withmarkup);
2738 g_free(enteredstring);
2739 g_free(normalized);
2741 return result;
2745 gboolean pidgin_gdk_pixbuf_is_opaque(GdkPixbuf *pixbuf) {
2746 int width, height, rowstride, i;
2747 unsigned char *pixels;
2748 unsigned char *row;
2750 if (!gdk_pixbuf_get_has_alpha(pixbuf))
2751 return TRUE;
2753 width = gdk_pixbuf_get_width (pixbuf);
2754 height = gdk_pixbuf_get_height (pixbuf);
2755 rowstride = gdk_pixbuf_get_rowstride (pixbuf);
2756 pixels = gdk_pixbuf_get_pixels (pixbuf);
2758 row = pixels;
2759 for (i = 3; i < rowstride; i+=4) {
2760 if (row[i] < 0xfe)
2761 return FALSE;
2764 for (i = 1; i < height - 1; i++) {
2765 row = pixels + (i*rowstride);
2766 if (row[3] < 0xfe || row[rowstride-1] < 0xfe) {
2767 return FALSE;
2771 row = pixels + ((height-1) * rowstride);
2772 for (i = 3; i < rowstride; i+=4) {
2773 if (row[i] < 0xfe)
2774 return FALSE;
2777 return TRUE;
2780 void pidgin_gdk_pixbuf_make_round(GdkPixbuf *pixbuf) {
2781 int width, height, rowstride;
2782 guchar *pixels;
2783 if (!gdk_pixbuf_get_has_alpha(pixbuf))
2784 return;
2785 width = gdk_pixbuf_get_width(pixbuf);
2786 height = gdk_pixbuf_get_height(pixbuf);
2787 rowstride = gdk_pixbuf_get_rowstride(pixbuf);
2788 pixels = gdk_pixbuf_get_pixels(pixbuf);
2790 if (width < 6 || height < 6)
2791 return;
2792 /* Top left */
2793 pixels[3] = 0;
2794 pixels[7] = 0x80;
2795 pixels[11] = 0xC0;
2796 pixels[rowstride + 3] = 0x80;
2797 pixels[rowstride * 2 + 3] = 0xC0;
2799 /* Top right */
2800 pixels[width * 4 - 1] = 0;
2801 pixels[width * 4 - 5] = 0x80;
2802 pixels[width * 4 - 9] = 0xC0;
2803 pixels[rowstride + (width * 4) - 1] = 0x80;
2804 pixels[(2 * rowstride) + (width * 4) - 1] = 0xC0;
2806 /* Bottom left */
2807 pixels[(height - 1) * rowstride + 3] = 0;
2808 pixels[(height - 1) * rowstride + 7] = 0x80;
2809 pixels[(height - 1) * rowstride + 11] = 0xC0;
2810 pixels[(height - 2) * rowstride + 3] = 0x80;
2811 pixels[(height - 3) * rowstride + 3] = 0xC0;
2813 /* Bottom right */
2814 pixels[height * rowstride - 1] = 0;
2815 pixels[(height - 1) * rowstride - 1] = 0x80;
2816 pixels[(height - 2) * rowstride - 1] = 0xC0;
2817 pixels[height * rowstride - 5] = 0x80;
2818 pixels[height * rowstride - 9] = 0xC0;
2821 const char *pidgin_get_dim_grey_string(GtkWidget *widget) {
2822 static char dim_grey_string[8] = "";
2823 GtkStyle *style;
2825 if (!widget)
2826 return "dim grey";
2828 style = gtk_widget_get_style(widget);
2829 if (!style)
2830 return "dim grey";
2832 snprintf(dim_grey_string, sizeof(dim_grey_string), "#%02x%02x%02x",
2833 style->text_aa[GTK_STATE_NORMAL].red >> 8,
2834 style->text_aa[GTK_STATE_NORMAL].green >> 8,
2835 style->text_aa[GTK_STATE_NORMAL].blue >> 8);
2836 return dim_grey_string;
2839 static void
2840 combo_box_changed_cb(GtkComboBox *combo_box, GtkEntry *entry)
2842 char *text = gtk_combo_box_get_active_text(combo_box);
2843 gtk_entry_set_text(entry, text ? text : "");
2844 g_free(text);
2847 static gboolean
2848 entry_key_pressed_cb(GtkWidget *entry, GdkEventKey *key, GtkComboBox *combo)
2850 if (key->keyval == GDK_Down || key->keyval == GDK_Up) {
2851 gtk_combo_box_popup(combo);
2852 return TRUE;
2854 return FALSE;
2857 GtkWidget *
2858 pidgin_text_combo_box_entry_new(const char *default_item, GList *items)
2860 GtkComboBox *ret = NULL;
2861 GtkWidget *the_entry = NULL;
2863 ret = GTK_COMBO_BOX(gtk_combo_box_new_text());
2864 the_entry = gtk_entry_new();
2865 gtk_container_add(GTK_CONTAINER(ret), the_entry);
2867 if (default_item)
2868 gtk_entry_set_text(GTK_ENTRY(the_entry), default_item);
2870 for (; items != NULL ; items = items->next) {
2871 char *text = items->data;
2872 if (text && *text)
2873 gtk_combo_box_append_text(ret, text);
2876 g_signal_connect(G_OBJECT(ret), "changed", (GCallback)combo_box_changed_cb, the_entry);
2877 g_signal_connect_after(G_OBJECT(the_entry), "key-press-event", G_CALLBACK(entry_key_pressed_cb), ret);
2879 return GTK_WIDGET(ret);
2882 const char *pidgin_text_combo_box_entry_get_text(GtkWidget *widget)
2884 return gtk_entry_get_text(GTK_ENTRY(GTK_BIN((widget))->child));
2887 void pidgin_text_combo_box_entry_set_text(GtkWidget *widget, const char *text)
2889 gtk_entry_set_text(GTK_ENTRY(GTK_BIN((widget))->child), (text));
2892 GtkWidget *
2893 pidgin_add_widget_to_vbox(GtkBox *vbox, const char *widget_label, GtkSizeGroup *sg, GtkWidget *widget, gboolean expand, GtkWidget **p_label)
2895 GtkWidget *hbox;
2896 GtkWidget *label = NULL;
2898 if (widget_label) {
2899 hbox = gtk_hbox_new(FALSE, 5);
2900 gtk_widget_show(hbox);
2901 gtk_box_pack_start(vbox, hbox, FALSE, FALSE, 0);
2903 label = gtk_label_new_with_mnemonic(widget_label);
2904 gtk_widget_show(label);
2905 if (sg) {
2906 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
2907 gtk_size_group_add_widget(sg, label);
2909 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
2910 } else {
2911 hbox = GTK_WIDGET(vbox);
2914 gtk_widget_show(widget);
2915 gtk_box_pack_start(GTK_BOX(hbox), widget, expand, TRUE, 0);
2916 if (label) {
2917 gtk_label_set_mnemonic_widget(GTK_LABEL(label), widget);
2918 pidgin_set_accessible_label (widget, label);
2921 if (p_label)
2922 (*p_label) = label;
2923 return hbox;
2926 gboolean pidgin_auto_parent_window(GtkWidget *widget)
2928 #if 0
2929 /* This looks at the most recent window that received focus, and makes
2930 * that the parent window. */
2931 #ifndef _WIN32
2932 static GdkAtom _WindowTime = GDK_NONE;
2933 static GdkAtom _Cardinal = GDK_NONE;
2934 GList *windows = NULL;
2935 GtkWidget *parent = NULL;
2936 time_t window_time = 0;
2938 windows = gtk_window_list_toplevels();
2940 if (_WindowTime == GDK_NONE) {
2941 _WindowTime = gdk_x11_xatom_to_atom(gdk_x11_get_xatom_by_name("_NET_WM_USER_TIME"));
2943 if (_Cardinal == GDK_NONE) {
2944 _Cardinal = gdk_atom_intern("CARDINAL", FALSE);
2947 while (windows) {
2948 GtkWidget *window = windows->data;
2949 guchar *data = NULL;
2950 int al = 0;
2951 time_t value;
2953 windows = g_list_delete_link(windows, windows);
2955 if (window == widget ||
2956 !GTK_WIDGET_VISIBLE(window))
2957 continue;
2959 if (!gdk_property_get(window->window, _WindowTime, _Cardinal, 0, sizeof(time_t), FALSE,
2960 NULL, NULL, &al, &data))
2961 continue;
2962 value = *(time_t *)data;
2963 if (window_time < value) {
2964 window_time = value;
2965 parent = window;
2967 g_free(data);
2969 if (windows)
2970 g_list_free(windows);
2971 if (parent) {
2972 if (!gtk_get_current_event() && gtk_window_has_toplevel_focus(GTK_WINDOW(parent))) {
2973 /* The window is in focus, and the new window was not triggered by a keypress/click
2974 * event. So do not set it transient, to avoid focus stealing and all that.
2976 return FALSE;
2978 gtk_window_set_transient_for(GTK_WINDOW(widget), GTK_WINDOW(parent));
2979 return TRUE;
2981 return FALSE;
2982 #endif
2983 #else
2984 /* This finds the currently active window and makes that the parent window. */
2985 GList *windows = NULL;
2986 GtkWidget *parent = NULL;
2987 GdkEvent *event = gtk_get_current_event();
2988 GdkWindow *menu = NULL;
2990 if (event == NULL)
2991 /* The window was not triggered by a user action. */
2992 return FALSE;
2994 /* We need to special case events from a popup menu. */
2995 if (event->type == GDK_BUTTON_RELEASE) {
2996 /* XXX: Neither of the following works:
2997 menu = event->button.window;
2998 menu = gdk_window_get_parent(event->button.window);
2999 menu = gdk_window_get_toplevel(event->button.window);
3001 } else if (event->type == GDK_KEY_PRESS)
3002 menu = event->key.window;
3004 windows = gtk_window_list_toplevels();
3005 while (windows) {
3006 GtkWidget *window = windows->data;
3007 windows = g_list_delete_link(windows, windows);
3009 if (window == widget ||
3010 !GTK_WIDGET_VISIBLE(window)) {
3011 continue;
3014 if (gtk_window_has_toplevel_focus(GTK_WINDOW(window)) ||
3015 (menu && menu == window->window)) {
3016 parent = window;
3017 break;
3020 if (windows)
3021 g_list_free(windows);
3022 if (parent) {
3023 gtk_window_set_transient_for(GTK_WINDOW(widget), GTK_WINDOW(parent));
3024 return TRUE;
3026 return FALSE;
3027 #endif
3030 GdkPixbuf * pidgin_pixbuf_from_imgstore(PurpleStoredImage *image)
3032 GdkPixbuf *pixbuf;
3033 GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
3034 gdk_pixbuf_loader_write(loader, purple_imgstore_get_data(image),
3035 purple_imgstore_get_size(image), NULL);
3036 gdk_pixbuf_loader_close(loader, NULL);
3037 pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
3038 if (pixbuf)
3039 g_object_ref(pixbuf);
3040 g_object_unref(loader);
3041 return pixbuf;
3044 static void url_copy(GtkWidget *w, gchar *url)
3046 GtkClipboard *clipboard;
3048 clipboard = gtk_widget_get_clipboard(w, GDK_SELECTION_PRIMARY);
3049 gtk_clipboard_set_text(clipboard, url, -1);
3051 clipboard = gtk_widget_get_clipboard(w, GDK_SELECTION_CLIPBOARD);
3052 gtk_clipboard_set_text(clipboard, url, -1);
3055 static gboolean
3056 link_context_menu(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu)
3058 GtkWidget *img, *item;
3059 const char *url;
3061 url = gtk_imhtml_link_get_url(link);
3063 /* Open Link */
3064 img = gtk_image_new_from_stock(GTK_STOCK_JUMP_TO, GTK_ICON_SIZE_MENU);
3065 item = gtk_image_menu_item_new_with_mnemonic(_("_Open Link"));
3066 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
3067 g_signal_connect_swapped(G_OBJECT(item), "activate", G_CALLBACK(gtk_imhtml_link_activate), link);
3068 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
3070 /* Copy Link Location */
3071 img = gtk_image_new_from_stock(GTK_STOCK_COPY, GTK_ICON_SIZE_MENU);
3072 item = gtk_image_menu_item_new_with_mnemonic(_("_Copy Link Location"));
3073 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
3074 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(url_copy), (gpointer)url);
3075 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
3077 return TRUE;
3080 static gboolean
3081 copy_email_address(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu)
3083 GtkWidget *img, *item;
3084 const char *text;
3085 char *address;
3086 #define MAILTOSIZE (sizeof("mailto:") - 1)
3088 text = gtk_imhtml_link_get_url(link);
3089 g_return_val_if_fail(text && strlen(text) > MAILTOSIZE, FALSE);
3090 address = (char*)text + MAILTOSIZE;
3092 /* Copy Email Address */
3093 img = gtk_image_new_from_stock(GTK_STOCK_COPY, GTK_ICON_SIZE_MENU);
3094 item = gtk_image_menu_item_new_with_mnemonic(_("_Copy Email Address"));
3095 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
3096 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(url_copy), address);
3097 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
3099 return TRUE;
3102 static void
3103 file_open_uri(GtkIMHtml *imhtml, const char *uri)
3105 /* Copied from gtkft.c:open_button_cb */
3106 #ifdef _WIN32
3107 /* If using Win32... */
3108 int code;
3109 if (G_WIN32_HAVE_WIDECHAR_API()) {
3110 wchar_t *wc_filename = g_utf8_to_utf16(
3111 uri, -1, NULL, NULL, NULL);
3113 code = (int)ShellExecuteW(NULL, NULL, wc_filename, NULL, NULL,
3114 SW_SHOW);
3116 g_free(wc_filename);
3117 } else {
3118 char *l_filename = g_locale_from_utf8(
3119 uri, -1, NULL, NULL, NULL);
3121 code = (int)ShellExecuteA(NULL, NULL, l_filename, NULL, NULL,
3122 SW_SHOW);
3124 g_free(l_filename);
3127 if (code == SE_ERR_ASSOCINCOMPLETE || code == SE_ERR_NOASSOC)
3129 purple_notify_error(imhtml, NULL,
3130 _("There is no application configured to open this type of file."), NULL);
3132 else if (code < 32)
3134 purple_notify_error(imhtml, NULL,
3135 _("An error occurred while opening the file."), NULL);
3136 purple_debug_warning("gtkutils", "filename: %s; code: %d\n", uri, code);
3138 #else
3139 char *command = NULL;
3140 char *tmp = NULL;
3141 GError *error = NULL;
3143 if (purple_running_gnome())
3145 char *escaped = g_shell_quote(uri);
3146 command = g_strdup_printf("gnome-open %s", escaped);
3147 g_free(escaped);
3149 else if (purple_running_kde())
3151 char *escaped = g_shell_quote(uri);
3153 if (purple_str_has_suffix(uri, ".desktop"))
3154 command = g_strdup_printf("kfmclient openURL %s 'text/plain'", escaped);
3155 else
3156 command = g_strdup_printf("kfmclient openURL %s", escaped);
3157 g_free(escaped);
3159 else
3161 purple_notify_uri(NULL, uri);
3162 return;
3165 if (purple_program_is_valid(command))
3167 gint exit_status;
3168 if (!g_spawn_command_line_sync(command, NULL, NULL, &exit_status, &error))
3170 tmp = g_strdup_printf(_("Error launching %s: %s"),
3171 uri, error->message);
3172 purple_notify_error(imhtml, NULL, _("Unable to open file."), tmp);
3173 g_free(tmp);
3174 g_error_free(error);
3176 if (exit_status != 0)
3178 char *primary = g_strdup_printf(_("Error running %s"), command);
3179 char *secondary = g_strdup_printf(_("Process returned error code %d"),
3180 exit_status);
3181 purple_notify_error(imhtml, NULL, primary, secondary);
3182 g_free(tmp);
3185 #endif
3188 #define FILELINKSIZE (sizeof("file://") - 1)
3189 static gboolean
3190 file_clicked_cb(GtkIMHtml *imhtml, GtkIMHtmlLink *link)
3192 const char *uri = gtk_imhtml_link_get_url(link) + FILELINKSIZE;
3193 file_open_uri(imhtml, uri);
3194 return TRUE;
3197 static gboolean
3198 open_containing_cb(GtkIMHtml *imhtml, const char *url)
3200 char *dir = g_path_get_dirname(url + FILELINKSIZE);
3201 file_open_uri(imhtml, dir);
3202 g_free(dir);
3203 return TRUE;
3206 static gboolean
3207 file_context_menu(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu)
3209 GtkWidget *img, *item;
3210 const char *url;
3212 url = gtk_imhtml_link_get_url(link);
3214 /* Open File */
3215 img = gtk_image_new_from_stock(GTK_STOCK_JUMP_TO, GTK_ICON_SIZE_MENU);
3216 item = gtk_image_menu_item_new_with_mnemonic(_("_Open File"));
3217 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
3218 g_signal_connect_swapped(G_OBJECT(item), "activate", G_CALLBACK(gtk_imhtml_link_activate), link);
3219 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
3221 /* Open Containing Directory */
3222 img = gtk_image_new_from_stock(GTK_STOCK_DIRECTORY, GTK_ICON_SIZE_MENU);
3223 item = gtk_image_menu_item_new_with_mnemonic(_("Open _Containing Directory"));
3224 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
3226 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(open_containing_cb), (gpointer)url);
3227 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
3229 return TRUE;
3232 #define AUDIOLINKSIZE (sizeof("audio://") - 1)
3233 static gboolean
3234 audio_clicked_cb(GtkIMHtml *imhtml, GtkIMHtmlLink *link)
3236 const char *uri;
3237 PidginConversation *conv = g_object_get_data(G_OBJECT(imhtml), "gtkconv");
3238 if (!conv) /* no playback in debug window */
3239 return TRUE;
3240 uri = gtk_imhtml_link_get_url(link) + AUDIOLINKSIZE;
3241 purple_sound_play_file(uri, NULL);
3242 return TRUE;
3245 static void
3246 savefile_write_cb(gpointer user_data, char *file)
3248 char *temp_file = user_data;
3249 gchar *contents;
3250 gsize length;
3251 GError *error;
3253 if (!g_file_get_contents(temp_file, &contents, &length, &error)) {
3254 purple_debug_error("gtkutils", "Unable to read contents of %s: %s\n",
3255 temp_file, error->message);
3256 g_error_free(error);
3257 return;
3260 if (!purple_util_write_data_to_file_absolute(file, contents, length)) {
3261 purple_debug_error("gtkutils", "Unable to write contents to %s\n",
3262 file);
3266 static gboolean
3267 save_file_cb(GtkWidget *item, const char *url)
3269 PidginConversation *conv = g_object_get_data(G_OBJECT(item), "gtkconv");
3270 if (!conv)
3271 return TRUE;
3272 purple_request_file(conv->active_conv, _("Save File"), NULL, TRUE,
3273 G_CALLBACK(savefile_write_cb), NULL,
3274 conv->active_conv->account, NULL, conv->active_conv,
3275 (void *)url);
3276 return TRUE;
3279 static gboolean
3280 audio_context_menu(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu)
3282 GtkWidget *img, *item;
3283 const char *url;
3284 PidginConversation *conv = g_object_get_data(G_OBJECT(imhtml), "gtkconv");
3285 if (!conv) /* No menu in debug window */
3286 return TRUE;
3288 url = gtk_imhtml_link_get_url(link);
3290 /* Play Sound */
3291 img = gtk_image_new_from_stock(GTK_STOCK_MEDIA_PLAY, GTK_ICON_SIZE_MENU);
3292 item = gtk_image_menu_item_new_with_mnemonic(_("_Play Sound"));
3293 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
3295 g_signal_connect_swapped(G_OBJECT(item), "activate", G_CALLBACK(gtk_imhtml_link_activate), link);
3296 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
3298 /* Save File */
3299 img = gtk_image_new_from_stock(GTK_STOCK_SAVE, GTK_ICON_SIZE_MENU);
3300 item = gtk_image_menu_item_new_with_mnemonic(_("_Save File"));
3301 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
3302 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(save_file_cb), (gpointer)(url+AUDIOLINKSIZE));
3303 g_object_set_data(G_OBJECT(item), "gtkconv", conv);
3304 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
3306 return TRUE;
3309 /* XXX: The following two functions are for demonstration purposes only! */
3310 static gboolean
3311 open_dialog(GtkIMHtml *imhtml, GtkIMHtmlLink *link)
3313 const char *url;
3314 const char *str;
3316 url = gtk_imhtml_link_get_url(link);
3317 if (!url || strlen(url) < sizeof("open://"))
3318 return FALSE;
3320 str = url + sizeof("open://") - 1;
3322 if (strcmp(str, "accounts") == 0)
3323 pidgin_accounts_window_show();
3324 else if (strcmp(str, "prefs") == 0)
3325 pidgin_prefs_show();
3326 else
3327 return FALSE;
3328 return TRUE;
3331 static gboolean
3332 dummy(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu)
3334 return TRUE;
3337 static gboolean
3338 register_gnome_url_handlers(void)
3340 char *tmp;
3341 char *err;
3342 char *c;
3343 char *start;
3345 tmp = g_find_program_in_path("gconftool-2");
3346 if (tmp == NULL)
3347 return FALSE;
3349 g_free(tmp);
3350 tmp = NULL;
3352 if (!g_spawn_command_line_sync("gconftool-2 --all-dirs /desktop/gnome/url-handlers",
3353 &tmp, &err, NULL, NULL))
3355 g_free(tmp);
3356 g_free(err);
3357 g_return_val_if_reached(FALSE);
3359 g_free(err);
3360 err = NULL;
3362 for (c = start = tmp ; *c ; c++)
3364 /* Skip leading spaces. */
3365 if (c == start && *c == ' ')
3366 start = c + 1;
3367 else if (*c == '\n')
3369 *c = '\0';
3370 if (g_str_has_prefix(start, "/desktop/gnome/url-handlers/"))
3372 char *cmd;
3373 char *tmp2 = NULL;
3374 char *protocol;
3376 /* If there is an enabled boolean, honor it. */
3377 cmd = g_strdup_printf("gconftool-2 -g %s/enabled", start);
3378 if (g_spawn_command_line_sync(cmd, &tmp2, &err, NULL, NULL))
3380 g_free(err);
3381 err = NULL;
3382 if (!strcmp(tmp2, "false\n"))
3384 g_free(tmp2);
3385 g_free(cmd);
3386 start = c + 1;
3387 continue;
3390 g_free(cmd);
3391 g_free(tmp2);
3393 start += sizeof("/desktop/gnome/url-handlers/") - 1;
3395 protocol = g_strdup_printf("%s:", start);
3396 gnome_url_handlers = g_list_prepend(gnome_url_handlers, protocol);
3397 gtk_imhtml_class_register_protocol(protocol, url_clicked_cb, link_context_menu);
3399 start = c + 1;
3402 g_free(tmp);
3404 return (gnome_url_handlers != NULL);
3407 void pidgin_utils_init(void)
3409 gtk_imhtml_class_register_protocol("http://", url_clicked_cb, link_context_menu);
3410 gtk_imhtml_class_register_protocol("https://", url_clicked_cb, link_context_menu);
3411 gtk_imhtml_class_register_protocol("ftp://", url_clicked_cb, link_context_menu);
3412 gtk_imhtml_class_register_protocol("gopher://", url_clicked_cb, link_context_menu);
3413 gtk_imhtml_class_register_protocol("mailto:", url_clicked_cb, copy_email_address);
3415 gtk_imhtml_class_register_protocol("file://", file_clicked_cb, file_context_menu);
3416 gtk_imhtml_class_register_protocol("audio://", audio_clicked_cb, audio_context_menu);
3418 /* Example custom URL handler. */
3419 gtk_imhtml_class_register_protocol("open://", open_dialog, dummy);
3421 /* If we're under GNOME, try registering the system URL handlers. */
3422 if (purple_running_gnome())
3423 register_gnome_url_handlers();
3426 void pidgin_utils_uninit(void)
3428 gtk_imhtml_class_register_protocol("open://", NULL, NULL);
3430 /* If we have GNOME handlers registered, unregister them. */
3431 if (gnome_url_handlers)
3433 GList *l;
3434 for (l = gnome_url_handlers ; l ; l = l->next)
3436 gtk_imhtml_class_register_protocol((char *)l->data, NULL, NULL);
3437 g_free(l->data);
3439 g_list_free(gnome_url_handlers);
3440 gnome_url_handlers = NULL;
3441 return;
3444 gtk_imhtml_class_register_protocol("audio://", NULL, NULL);
3445 gtk_imhtml_class_register_protocol("file://", NULL, NULL);
3447 gtk_imhtml_class_register_protocol("http://", NULL, NULL);
3448 gtk_imhtml_class_register_protocol("https://", NULL, NULL);
3449 gtk_imhtml_class_register_protocol("ftp://", NULL, NULL);
3450 gtk_imhtml_class_register_protocol("mailto:", NULL, NULL);
3451 gtk_imhtml_class_register_protocol("gopher://", NULL, NULL);