*** Plucked rev 3f699c081b38f9d19632fcee1c23cbc96fce3092 (sulabh@soc.pidgin.im):
[pidgin-git.git] / pidgin / gtkutils.c
blob4a1ba44b5189f7f1b002ae37f398376b9bc62506
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 "util.h"
59 #include "gtkconv.h"
60 #include "gtkdialogs.h"
61 #include "gtkimhtml.h"
62 #include "gtkimhtmltoolbar.h"
63 #include "pidginstock.h"
64 #include "gtkthemes.h"
65 #include "gtkutils.h"
66 #include "pidgin/minidialog.h"
68 typedef struct {
69 GtkWidget *menu;
70 gint default_item;
71 } AopMenu;
73 static guint accels_save_timer = 0;
75 static gboolean
76 url_clicked_idle_cb(gpointer data)
78 purple_notify_uri(NULL, data);
79 g_free(data);
80 return FALSE;
83 static void
84 url_clicked_cb(GtkWidget *w, const char *uri)
86 g_idle_add(url_clicked_idle_cb, g_strdup(uri));
89 static GtkIMHtmlFuncs gtkimhtml_cbs = {
90 (GtkIMHtmlGetImageFunc)purple_imgstore_find_by_id,
91 (GtkIMHtmlGetImageDataFunc)purple_imgstore_get_data,
92 (GtkIMHtmlGetImageSizeFunc)purple_imgstore_get_size,
93 (GtkIMHtmlGetImageFilenameFunc)purple_imgstore_get_filename,
94 purple_imgstore_ref_by_id,
95 purple_imgstore_unref_by_id,
98 void
99 pidgin_setup_imhtml(GtkWidget *imhtml)
101 PangoFontDescription *desc = NULL;
102 g_return_if_fail(imhtml != NULL);
103 g_return_if_fail(GTK_IS_IMHTML(imhtml));
105 g_signal_connect(G_OBJECT(imhtml), "url_clicked",
106 G_CALLBACK(url_clicked_cb), NULL);
108 pidgin_themes_smiley_themeize(imhtml);
110 gtk_imhtml_set_funcs(GTK_IMHTML(imhtml), &gtkimhtml_cbs);
112 if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/use_theme_font")) {
113 const char *font = purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/custom_font");
114 desc = pango_font_description_from_string(font);
115 } else if (purple_running_gnome()) {
116 /* Use the GNOME "document" font, if applicable */
117 char *path;
119 if ((path = g_find_program_in_path("gconftool-2"))) {
120 char *font = NULL;
121 char *err = NULL;
122 g_free(path);
123 if (g_spawn_command_line_sync(
124 "gconftool-2 -g /desktop/gnome/interface/document_font_name",
125 &font, &err, NULL, NULL)) {
126 desc = pango_font_description_from_string(font);
128 g_free(err);
129 g_free(font);
133 if (desc) {
134 gtk_widget_modify_font(imhtml, desc);
135 pango_font_description_free(desc);
139 static
140 void pidgin_window_init(GtkWindow *wnd, const char *title, guint border_width, const char *role, gboolean resizable)
142 if (title)
143 gtk_window_set_title(wnd, title);
144 #ifdef _WIN32
145 else
146 gtk_window_set_title(wnd, PIDGIN_ALERT_TITLE);
147 #endif
148 gtk_container_set_border_width(GTK_CONTAINER(wnd), border_width);
149 if (role)
150 gtk_window_set_role(wnd, role);
151 gtk_window_set_resizable(wnd, resizable);
154 GtkWidget *
155 pidgin_create_window(const char *title, guint border_width, const char *role, gboolean resizable)
157 GtkWindow *wnd = NULL;
159 wnd = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
160 pidgin_window_init(wnd, title, border_width, role, resizable);
162 return GTK_WIDGET(wnd);
165 GtkWidget *
166 pidgin_create_dialog(const char *title, guint border_width, const char *role, gboolean resizable)
168 GtkWindow *wnd = NULL;
170 wnd = GTK_WINDOW(gtk_dialog_new());
171 pidgin_window_init(wnd, title, border_width, role, resizable);
172 g_object_set(G_OBJECT(wnd), "has-separator", FALSE, NULL);
174 return GTK_WIDGET(wnd);
177 GtkWidget *
178 pidgin_dialog_get_vbox_with_properties(GtkDialog *dialog, gboolean homogeneous, gint spacing)
180 GtkBox *vbox = GTK_BOX(GTK_DIALOG(dialog)->vbox);
181 gtk_box_set_homogeneous(vbox, homogeneous);
182 gtk_box_set_spacing(vbox, spacing);
183 return GTK_WIDGET(vbox);
186 GtkWidget *pidgin_dialog_get_vbox(GtkDialog *dialog)
188 return GTK_DIALOG(dialog)->vbox;
191 GtkWidget *pidgin_dialog_get_action_area(GtkDialog *dialog)
193 return GTK_DIALOG(dialog)->action_area;
196 GtkWidget *pidgin_dialog_add_button(GtkDialog *dialog, const char *label,
197 GCallback callback, gpointer callbackdata)
199 GtkWidget *button = gtk_button_new_from_stock(label);
200 GtkWidget *bbox = pidgin_dialog_get_action_area(dialog);
201 gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
202 if (callback)
203 g_signal_connect(G_OBJECT(button), "clicked", callback, callbackdata);
204 gtk_widget_show(button);
205 return button;
208 GtkWidget *
209 pidgin_create_imhtml(gboolean editable, GtkWidget **imhtml_ret, GtkWidget **toolbar_ret, GtkWidget **sw_ret)
211 GtkWidget *frame;
212 GtkWidget *imhtml;
213 GtkWidget *sep;
214 GtkWidget *sw;
215 GtkWidget *toolbar = NULL;
216 GtkWidget *vbox;
218 frame = gtk_frame_new(NULL);
219 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
221 vbox = gtk_vbox_new(FALSE, 0);
222 gtk_container_add(GTK_CONTAINER(frame), vbox);
223 gtk_widget_show(vbox);
225 if (editable) {
226 toolbar = gtk_imhtmltoolbar_new();
227 gtk_box_pack_start(GTK_BOX(vbox), toolbar, FALSE, FALSE, 0);
228 gtk_widget_show(toolbar);
230 sep = gtk_hseparator_new();
231 gtk_box_pack_start(GTK_BOX(vbox), sep, FALSE, FALSE, 0);
232 g_signal_connect_swapped(G_OBJECT(toolbar), "show", G_CALLBACK(gtk_widget_show), sep);
233 g_signal_connect_swapped(G_OBJECT(toolbar), "hide", G_CALLBACK(gtk_widget_hide), sep);
234 gtk_widget_show(sep);
237 sw = gtk_scrolled_window_new(NULL, NULL);
238 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
239 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
240 gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0);
241 gtk_widget_show(sw);
243 imhtml = gtk_imhtml_new(NULL, NULL);
244 gtk_imhtml_set_editable(GTK_IMHTML(imhtml), editable);
245 gtk_imhtml_set_format_functions(GTK_IMHTML(imhtml), GTK_IMHTML_ALL ^ GTK_IMHTML_IMAGE);
246 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(imhtml), GTK_WRAP_WORD_CHAR);
247 #ifdef USE_GTKSPELL
248 if (editable && purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/spellcheck"))
249 pidgin_setup_gtkspell(GTK_TEXT_VIEW(imhtml));
250 #endif
251 gtk_widget_show(imhtml);
253 if (editable) {
254 gtk_imhtmltoolbar_attach(GTK_IMHTMLTOOLBAR(toolbar), imhtml);
255 gtk_imhtmltoolbar_associate_smileys(GTK_IMHTMLTOOLBAR(toolbar), "default");
257 pidgin_setup_imhtml(imhtml);
259 gtk_container_add(GTK_CONTAINER(sw), imhtml);
261 if (imhtml_ret != NULL)
262 *imhtml_ret = imhtml;
264 if (editable && (toolbar_ret != NULL))
265 *toolbar_ret = toolbar;
267 if (sw_ret != NULL)
268 *sw_ret = sw;
270 return frame;
273 void
274 pidgin_set_sensitive_if_input(GtkWidget *entry, GtkWidget *dialog)
276 const char *text = gtk_entry_get_text(GTK_ENTRY(entry));
277 gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog), GTK_RESPONSE_OK,
278 (*text != '\0'));
281 void
282 pidgin_toggle_sensitive(GtkWidget *widget, GtkWidget *to_toggle)
284 gboolean sensitivity;
286 if (to_toggle == NULL)
287 return;
289 sensitivity = GTK_WIDGET_IS_SENSITIVE(to_toggle);
291 gtk_widget_set_sensitive(to_toggle, !sensitivity);
294 void
295 pidgin_toggle_sensitive_array(GtkWidget *w, GPtrArray *data)
297 gboolean sensitivity;
298 gpointer element;
299 int i;
301 for (i=0; i < data->len; i++) {
302 element = g_ptr_array_index(data,i);
303 if (element == NULL)
304 continue;
306 sensitivity = GTK_WIDGET_IS_SENSITIVE(element);
308 gtk_widget_set_sensitive(element, !sensitivity);
312 void
313 pidgin_toggle_showhide(GtkWidget *widget, GtkWidget *to_toggle)
315 if (to_toggle == NULL)
316 return;
318 if (GTK_WIDGET_VISIBLE(to_toggle))
319 gtk_widget_hide(to_toggle);
320 else
321 gtk_widget_show(to_toggle);
324 GtkWidget *pidgin_separator(GtkWidget *menu)
326 GtkWidget *menuitem;
328 menuitem = gtk_separator_menu_item_new();
329 gtk_widget_show(menuitem);
330 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
331 return menuitem;
334 GtkWidget *pidgin_new_item(GtkWidget *menu, const char *str)
336 GtkWidget *menuitem;
337 GtkWidget *label;
339 menuitem = gtk_menu_item_new();
340 if (menu)
341 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
342 gtk_widget_show(menuitem);
344 label = gtk_label_new(str);
345 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
346 gtk_label_set_pattern(GTK_LABEL(label), "_");
347 gtk_container_add(GTK_CONTAINER(menuitem), label);
348 gtk_widget_show(label);
349 /* FIXME: Go back and fix this
350 gtk_widget_add_accelerator(menuitem, "activate", accel, str[0],
351 GDK_MOD1_MASK, GTK_ACCEL_LOCKED);
353 pidgin_set_accessible_label (menuitem, label);
354 return menuitem;
357 GtkWidget *pidgin_new_check_item(GtkWidget *menu, const char *str,
358 GtkSignalFunc sf, gpointer data, gboolean checked)
360 GtkWidget *menuitem;
361 menuitem = gtk_check_menu_item_new_with_mnemonic(str);
363 if (menu)
364 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
366 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), checked);
368 if (sf)
369 g_signal_connect(G_OBJECT(menuitem), "activate", sf, data);
371 gtk_widget_show_all(menuitem);
373 return menuitem;
376 GtkWidget *
377 pidgin_pixbuf_toolbar_button_from_stock(const char *icon)
379 GtkWidget *button, *image, *bbox;
381 button = gtk_toggle_button_new();
382 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
384 bbox = gtk_vbox_new(FALSE, 0);
386 gtk_container_add (GTK_CONTAINER(button), bbox);
388 image = gtk_image_new_from_stock(icon, gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL));
389 gtk_box_pack_start(GTK_BOX(bbox), image, FALSE, FALSE, 0);
391 gtk_widget_show_all(bbox);
393 return button;
396 GtkWidget *
397 pidgin_pixbuf_button_from_stock(const char *text, const char *icon,
398 PidginButtonOrientation style)
400 GtkWidget *button, *image, *label, *bbox, *ibox, *lbox = NULL;
402 button = gtk_button_new();
404 if (style == PIDGIN_BUTTON_HORIZONTAL) {
405 bbox = gtk_hbox_new(FALSE, 0);
406 ibox = gtk_hbox_new(FALSE, 0);
407 if (text)
408 lbox = gtk_hbox_new(FALSE, 0);
409 } else {
410 bbox = gtk_vbox_new(FALSE, 0);
411 ibox = gtk_vbox_new(FALSE, 0);
412 if (text)
413 lbox = gtk_vbox_new(FALSE, 0);
416 gtk_container_add(GTK_CONTAINER(button), bbox);
418 if (icon) {
419 gtk_box_pack_start_defaults(GTK_BOX(bbox), ibox);
420 image = gtk_image_new_from_stock(icon, GTK_ICON_SIZE_BUTTON);
421 gtk_box_pack_end(GTK_BOX(ibox), image, FALSE, TRUE, 0);
424 if (text) {
425 gtk_box_pack_start_defaults(GTK_BOX(bbox), lbox);
426 label = gtk_label_new(NULL);
427 gtk_label_set_text_with_mnemonic(GTK_LABEL(label), text);
428 gtk_label_set_mnemonic_widget(GTK_LABEL(label), button);
429 gtk_box_pack_start(GTK_BOX(lbox), label, FALSE, TRUE, 0);
430 pidgin_set_accessible_label (button, label);
433 gtk_widget_show_all(bbox);
435 return button;
439 GtkWidget *pidgin_new_item_from_stock(GtkWidget *menu, const char *str, const char *icon, GtkSignalFunc sf, gpointer data, guint accel_key, guint accel_mods, char *mod)
441 GtkWidget *menuitem;
443 GtkWidget *hbox;
444 GtkWidget *label;
446 GtkWidget *image;
448 if (icon == NULL)
449 menuitem = gtk_menu_item_new_with_mnemonic(str);
450 else
451 menuitem = gtk_image_menu_item_new_with_mnemonic(str);
453 if (menu)
454 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
456 if (sf)
457 g_signal_connect(G_OBJECT(menuitem), "activate", sf, data);
459 if (icon != NULL) {
460 image = gtk_image_new_from_stock(icon, gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL));
461 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image);
463 /* FIXME: this isn't right
464 if (mod) {
465 label = gtk_label_new(mod);
466 gtk_box_pack_end(GTK_BOX(hbox), label, FALSE, FALSE, 2);
467 gtk_widget_show(label);
471 if (accel_key) {
472 gtk_widget_add_accelerator(menuitem, "activate", accel, accel_key,
473 accel_mods, GTK_ACCEL_LOCKED);
477 gtk_widget_show_all(menuitem);
479 return menuitem;
482 GtkWidget *
483 pidgin_make_frame(GtkWidget *parent, const char *title)
485 GtkWidget *vbox, *label, *hbox;
486 char *labeltitle;
488 vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
489 gtk_box_pack_start(GTK_BOX(parent), vbox, FALSE, FALSE, 0);
490 gtk_widget_show(vbox);
492 label = gtk_label_new(NULL);
494 labeltitle = g_strdup_printf("<span weight=\"bold\">%s</span>", title);
495 gtk_label_set_markup(GTK_LABEL(label), labeltitle);
496 g_free(labeltitle);
498 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
499 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
500 gtk_widget_show(label);
501 pidgin_set_accessible_label (vbox, label);
503 hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
504 gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
505 gtk_widget_show(hbox);
507 label = gtk_label_new(" ");
508 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
509 gtk_widget_show(label);
511 vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
512 gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);
513 gtk_widget_show(vbox);
515 return vbox;
518 static gpointer
519 aop_option_menu_get_selected(GtkWidget *optmenu, GtkWidget **p_item)
521 GtkWidget *menu = gtk_option_menu_get_menu(GTK_OPTION_MENU(optmenu));
522 GtkWidget *item = gtk_menu_get_active(GTK_MENU(menu));
523 if (p_item)
524 (*p_item) = item;
525 return g_object_get_data(G_OBJECT(item), "aop_per_item_data");
528 static void
529 aop_menu_cb(GtkWidget *optmenu, GCallback cb)
531 GtkWidget *item;
532 gpointer per_item_data;
534 per_item_data = aop_option_menu_get_selected(optmenu, &item);
536 if (cb != NULL) {
537 ((void (*)(GtkWidget *, gpointer, gpointer))cb)(item, per_item_data, g_object_get_data(G_OBJECT(optmenu), "user_data"));
541 static GtkWidget *
542 aop_menu_item_new(GtkSizeGroup *sg, GdkPixbuf *pixbuf, const char *lbl, gpointer per_item_data, const char *data)
544 GtkWidget *item;
545 GtkWidget *hbox;
546 GtkWidget *image;
547 GtkWidget *label;
549 item = gtk_menu_item_new();
550 gtk_widget_show(item);
552 hbox = gtk_hbox_new(FALSE, 4);
553 gtk_widget_show(hbox);
555 /* Create the image */
556 if (pixbuf == NULL)
557 image = gtk_image_new();
558 else
559 image = gtk_image_new_from_pixbuf(pixbuf);
560 gtk_widget_show(image);
562 if (sg)
563 gtk_size_group_add_widget(sg, image);
565 /* Create the label */
566 label = gtk_label_new (lbl);
567 gtk_widget_show (label);
568 gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
569 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
571 gtk_container_add(GTK_CONTAINER(item), hbox);
572 gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0);
573 gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
575 g_object_set_data(G_OBJECT (item), data, per_item_data);
576 g_object_set_data(G_OBJECT (item), "aop_per_item_data", per_item_data);
578 pidgin_set_accessible_label(item, label);
580 return item;
583 static GdkPixbuf *
584 pidgin_create_prpl_icon_from_prpl(PurplePlugin *prpl, PidginPrplIconSize size, PurpleAccount *account)
586 PurplePluginProtocolInfo *prpl_info;
587 const char *protoname = NULL;
588 char *tmp;
589 char *filename = NULL;
590 GdkPixbuf *pixbuf;
592 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
593 if (prpl_info->list_icon == NULL)
594 return NULL;
596 protoname = prpl_info->list_icon(account, NULL);
597 if (protoname == NULL)
598 return NULL;
601 * Status icons will be themeable too, and then it will look up
602 * protoname from the theme
604 tmp = g_strconcat(protoname, ".png", NULL);
606 filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "protocols",
607 size == PIDGIN_PRPL_ICON_SMALL ? "16" :
608 size == PIDGIN_PRPL_ICON_MEDIUM ? "22" : "48",
609 tmp, NULL);
610 g_free(tmp);
612 pixbuf = gdk_pixbuf_new_from_file(filename, NULL);
613 g_free(filename);
615 return pixbuf;
618 static GtkWidget *
619 aop_option_menu_new(AopMenu *aop_menu, GCallback cb, gpointer user_data)
621 GtkWidget *optmenu;
623 optmenu = gtk_option_menu_new();
624 gtk_widget_show(optmenu);
625 gtk_option_menu_set_menu(GTK_OPTION_MENU(optmenu), aop_menu->menu);
627 if (aop_menu->default_item != -1)
628 gtk_option_menu_set_history(GTK_OPTION_MENU(optmenu), aop_menu->default_item);
630 g_object_set_data_full(G_OBJECT(optmenu), "aop_menu", aop_menu, (GDestroyNotify)g_free);
631 g_object_set_data(G_OBJECT(optmenu), "user_data", user_data);
633 g_signal_connect(G_OBJECT(optmenu), "changed", G_CALLBACK(aop_menu_cb), cb);
635 return optmenu;
638 static void
639 aop_option_menu_replace_menu(GtkWidget *optmenu, AopMenu *new_aop_menu)
641 if (gtk_option_menu_get_menu(GTK_OPTION_MENU(optmenu)))
642 gtk_option_menu_remove_menu(GTK_OPTION_MENU(optmenu));
644 gtk_option_menu_set_menu(GTK_OPTION_MENU(optmenu), new_aop_menu->menu);
646 if (new_aop_menu->default_item != -1)
647 gtk_option_menu_set_history(GTK_OPTION_MENU(optmenu), new_aop_menu->default_item);
649 g_object_set_data_full(G_OBJECT(optmenu), "aop_menu", new_aop_menu, (GDestroyNotify)g_free);
652 static void
653 aop_option_menu_select_by_data(GtkWidget *optmenu, gpointer data)
655 guint idx;
656 GList *llItr = NULL;
658 for (idx = 0, llItr = GTK_MENU_SHELL(gtk_option_menu_get_menu(GTK_OPTION_MENU(optmenu)))->children;
659 llItr != NULL;
660 llItr = llItr->next, idx++) {
661 if (data == g_object_get_data(G_OBJECT(llItr->data), "aop_per_item_data")) {
662 gtk_option_menu_set_history(GTK_OPTION_MENU(optmenu), idx);
663 break;
668 static AopMenu *
669 create_protocols_menu(const char *default_proto_id)
671 AopMenu *aop_menu = NULL;
672 PurplePluginProtocolInfo *prpl_info;
673 PurplePlugin *plugin;
674 GdkPixbuf *pixbuf = NULL;
675 GtkSizeGroup *sg;
676 GList *p;
677 const char *gtalk_name = NULL;
678 int i;
680 aop_menu = g_malloc0(sizeof(AopMenu));
681 aop_menu->default_item = -1;
682 aop_menu->menu = gtk_menu_new();
683 gtk_widget_show(aop_menu->menu);
684 sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
686 if (purple_find_prpl("prpl-jabber"))
687 gtalk_name = _("Google Talk");
689 for (p = purple_plugins_get_protocols(), i = 0;
690 p != NULL;
691 p = p->next, i++) {
693 plugin = (PurplePlugin *)p->data;
694 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin);
696 if (gtalk_name && strcmp(gtalk_name, plugin->info->name) < 0) {
697 char *filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "protocols",
698 "16", "google-talk.png", NULL);
699 GtkWidget *item;
701 pixbuf = gdk_pixbuf_new_from_file(filename, NULL);
702 g_free(filename);
704 gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu->menu),
705 item = aop_menu_item_new(sg, pixbuf, gtalk_name, "prpl-jabber", "protocol"));
706 g_object_set_data(G_OBJECT(item), "fake", GINT_TO_POINTER(1));
708 if (pixbuf)
709 g_object_unref(pixbuf);
711 gtalk_name = NULL;
712 i++;
715 pixbuf = pidgin_create_prpl_icon_from_prpl(plugin, PIDGIN_PRPL_ICON_SMALL, NULL);
717 gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu->menu),
718 aop_menu_item_new(sg, pixbuf, plugin->info->name, plugin->info->id, "protocol"));
720 if (pixbuf)
721 g_object_unref(pixbuf);
723 if (default_proto_id != NULL && !strcmp(plugin->info->id, default_proto_id))
724 aop_menu->default_item = i;
727 g_object_unref(sg);
729 return aop_menu;
732 GtkWidget *
733 pidgin_protocol_option_menu_new(const char *id, GCallback cb,
734 gpointer user_data)
736 return aop_option_menu_new(create_protocols_menu(id), cb, user_data);
739 const char *
740 pidgin_protocol_option_menu_get_selected(GtkWidget *optmenu)
742 return (const char *)aop_option_menu_get_selected(optmenu, NULL);
745 PurpleAccount *
746 pidgin_account_option_menu_get_selected(GtkWidget *optmenu)
748 return (PurpleAccount *)aop_option_menu_get_selected(optmenu, NULL);
751 static AopMenu *
752 create_account_menu(PurpleAccount *default_account,
753 PurpleFilterAccountFunc filter_func, gboolean show_all)
755 AopMenu *aop_menu = NULL;
756 PurpleAccount *account;
757 GdkPixbuf *pixbuf = NULL;
758 GList *list;
759 GList *p;
760 GtkSizeGroup *sg;
761 int i;
762 char buf[256];
764 if (show_all)
765 list = purple_accounts_get_all();
766 else
767 list = purple_connections_get_all();
769 aop_menu = g_malloc0(sizeof(AopMenu));
770 aop_menu->default_item = -1;
771 aop_menu->menu = gtk_menu_new();
772 gtk_widget_show(aop_menu->menu);
773 sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
775 for (p = list, i = 0; p != NULL; p = p->next, i++) {
776 PurplePlugin *plugin;
778 if (show_all)
779 account = (PurpleAccount *)p->data;
780 else {
781 PurpleConnection *gc = (PurpleConnection *)p->data;
783 account = purple_connection_get_account(gc);
786 if (filter_func && !filter_func(account)) {
787 i--;
788 continue;
791 plugin = purple_find_prpl(purple_account_get_protocol_id(account));
793 pixbuf = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL);
795 if (pixbuf) {
796 if (purple_account_is_disconnected(account) && show_all &&
797 purple_connections_get_all())
798 gdk_pixbuf_saturate_and_pixelate(pixbuf, pixbuf, 0.0, FALSE);
801 if (purple_account_get_alias(account)) {
802 g_snprintf(buf, sizeof(buf), "%s (%s) (%s)",
803 purple_account_get_username(account),
804 purple_account_get_alias(account),
805 purple_account_get_protocol_name(account));
806 } else {
807 g_snprintf(buf, sizeof(buf), "%s (%s)",
808 purple_account_get_username(account),
809 purple_account_get_protocol_name(account));
812 gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu->menu),
813 aop_menu_item_new(sg, pixbuf, buf, account, "account"));
815 if (pixbuf)
816 g_object_unref(pixbuf);
818 if (default_account && account == default_account)
819 aop_menu->default_item = i;
822 g_object_unref(sg);
824 return aop_menu;
827 static void
828 regenerate_account_menu(GtkWidget *optmenu)
830 gboolean show_all;
831 PurpleAccount *account;
832 PurpleFilterAccountFunc filter_func;
834 account = (PurpleAccount *)aop_option_menu_get_selected(optmenu, NULL);
835 show_all = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(optmenu), "show_all"));
836 filter_func = g_object_get_data(G_OBJECT(optmenu), "filter_func");
838 aop_option_menu_replace_menu(optmenu, create_account_menu(account, filter_func, show_all));
841 static void
842 account_menu_sign_on_off_cb(PurpleConnection *gc, GtkWidget *optmenu)
844 regenerate_account_menu(optmenu);
847 static void
848 account_menu_added_removed_cb(PurpleAccount *account, GtkWidget *optmenu)
850 regenerate_account_menu(optmenu);
853 static gboolean
854 account_menu_destroyed_cb(GtkWidget *optmenu, GdkEvent *event,
855 void *user_data)
857 purple_signals_disconnect_by_handle(optmenu);
859 return FALSE;
862 void
863 pidgin_account_option_menu_set_selected(GtkWidget *optmenu, PurpleAccount *account)
865 aop_option_menu_select_by_data(optmenu, account);
868 GtkWidget *
869 pidgin_account_option_menu_new(PurpleAccount *default_account,
870 gboolean show_all, GCallback cb,
871 PurpleFilterAccountFunc filter_func,
872 gpointer user_data)
874 GtkWidget *optmenu;
876 /* Create the option menu */
877 optmenu = aop_option_menu_new(create_account_menu(default_account, filter_func, show_all), cb, user_data);
879 g_signal_connect(G_OBJECT(optmenu), "destroy",
880 G_CALLBACK(account_menu_destroyed_cb), NULL);
882 /* Register the purple sign on/off event callbacks. */
883 purple_signal_connect(purple_connections_get_handle(), "signed-on",
884 optmenu, PURPLE_CALLBACK(account_menu_sign_on_off_cb),
885 optmenu);
886 purple_signal_connect(purple_connections_get_handle(), "signed-off",
887 optmenu, PURPLE_CALLBACK(account_menu_sign_on_off_cb),
888 optmenu);
889 purple_signal_connect(purple_accounts_get_handle(), "account-added",
890 optmenu, PURPLE_CALLBACK(account_menu_added_removed_cb),
891 optmenu);
892 purple_signal_connect(purple_accounts_get_handle(), "account-removed",
893 optmenu, PURPLE_CALLBACK(account_menu_added_removed_cb),
894 optmenu);
896 /* Set some data. */
897 g_object_set_data(G_OBJECT(optmenu), "user_data", user_data);
898 g_object_set_data(G_OBJECT(optmenu), "show_all", GINT_TO_POINTER(show_all));
899 g_object_set_data(G_OBJECT(optmenu), "filter_func", filter_func);
901 return optmenu;
904 gboolean
905 pidgin_check_if_dir(const char *path, GtkFileSelection *filesel)
907 char *dirname = NULL;
909 if (g_file_test(path, G_FILE_TEST_IS_DIR)) {
910 /* append a / if needed */
911 if (path[strlen(path) - 1] != G_DIR_SEPARATOR) {
912 dirname = g_strconcat(path, G_DIR_SEPARATOR_S, NULL);
914 gtk_file_selection_set_filename(filesel, (dirname != NULL) ? dirname : path);
915 g_free(dirname);
916 return TRUE;
919 return FALSE;
922 void
923 pidgin_setup_gtkspell(GtkTextView *textview)
925 #ifdef USE_GTKSPELL
926 GError *error = NULL;
927 char *locale = NULL;
929 g_return_if_fail(textview != NULL);
930 g_return_if_fail(GTK_IS_TEXT_VIEW(textview));
932 if (gtkspell_new_attach(textview, locale, &error) == NULL && error)
934 purple_debug_warning("gtkspell", "Failed to setup GtkSpell: %s\n",
935 error->message);
936 g_error_free(error);
938 #endif /* USE_GTKSPELL */
941 void
942 pidgin_save_accels_cb(GtkAccelGroup *accel_group, guint arg1,
943 GdkModifierType arg2, GClosure *arg3,
944 gpointer data)
946 purple_debug(PURPLE_DEBUG_MISC, "accels",
947 "accel changed, scheduling save.\n");
949 if (!accels_save_timer)
950 accels_save_timer = g_timeout_add(5000, pidgin_save_accels,
951 NULL);
954 gboolean
955 pidgin_save_accels(gpointer data)
957 char *filename = NULL;
959 filename = g_build_filename(purple_user_dir(), G_DIR_SEPARATOR_S,
960 "accels", NULL);
961 purple_debug(PURPLE_DEBUG_MISC, "accels", "saving accels to %s\n", filename);
962 gtk_accel_map_save(filename);
963 g_free(filename);
965 accels_save_timer = 0;
966 return FALSE;
969 void
970 pidgin_load_accels()
972 char *filename = NULL;
974 filename = g_build_filename(purple_user_dir(), G_DIR_SEPARATOR_S,
975 "accels", NULL);
976 gtk_accel_map_load(filename);
977 g_free(filename);
980 static void
981 show_retrieveing_info(PurpleConnection *conn, const char *name)
983 PurpleNotifyUserInfo *info = purple_notify_user_info_new();
984 purple_notify_user_info_add_pair(info, _("Information"), _("Retrieving..."));
985 purple_notify_userinfo(conn, name, info, NULL, NULL);
986 purple_notify_user_info_destroy(info);
989 void pidgin_retrieve_user_info(PurpleConnection *conn, const char *name)
991 show_retrieveing_info(conn, name);
992 serv_get_info(conn, name);
995 void pidgin_retrieve_user_info_in_chat(PurpleConnection *conn, const char *name, int chat)
997 char *who = NULL;
998 PurplePluginProtocolInfo *prpl_info = NULL;
1000 if (chat < 0) {
1001 pidgin_retrieve_user_info(conn, name);
1002 return;
1005 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(conn->prpl);
1006 if (prpl_info != NULL && prpl_info->get_cb_real_name)
1007 who = prpl_info->get_cb_real_name(conn, chat, name);
1008 if (prpl_info == NULL || prpl_info->get_cb_info == NULL) {
1009 pidgin_retrieve_user_info(conn, who ? who : name);
1010 g_free(who);
1011 return;
1014 show_retrieveing_info(conn, who ? who : name);
1015 prpl_info->get_cb_info(conn, chat, name);
1016 g_free(who);
1019 gboolean
1020 pidgin_parse_x_im_contact(const char *msg, gboolean all_accounts,
1021 PurpleAccount **ret_account, char **ret_protocol,
1022 char **ret_username, char **ret_alias)
1024 char *protocol = NULL;
1025 char *username = NULL;
1026 char *alias = NULL;
1027 char *str;
1028 char *c, *s;
1029 gboolean valid;
1031 g_return_val_if_fail(msg != NULL, FALSE);
1032 g_return_val_if_fail(ret_protocol != NULL, FALSE);
1033 g_return_val_if_fail(ret_username != NULL, FALSE);
1035 s = str = g_strdup(msg);
1037 while (*s != '\r' && *s != '\n' && *s != '\0')
1039 char *key, *value;
1041 key = s;
1043 /* Grab the key */
1044 while (*s != '\r' && *s != '\n' && *s != '\0' && *s != ' ')
1045 s++;
1047 if (*s == '\r') s++;
1049 if (*s == '\n')
1051 s++;
1052 continue;
1055 if (*s != '\0') *s++ = '\0';
1057 /* Clear past any whitespace */
1058 while (*s != '\0' && *s == ' ')
1059 s++;
1061 /* Now let's grab until the end of the line. */
1062 value = s;
1064 while (*s != '\r' && *s != '\n' && *s != '\0')
1065 s++;
1067 if (*s == '\r') *s++ = '\0';
1068 if (*s == '\n') *s++ = '\0';
1070 if ((c = strchr(key, ':')) != NULL)
1072 if (!g_ascii_strcasecmp(key, "X-IM-Username:"))
1073 username = g_strdup(value);
1074 else if (!g_ascii_strcasecmp(key, "X-IM-Protocol:"))
1075 protocol = g_strdup(value);
1076 else if (!g_ascii_strcasecmp(key, "X-IM-Alias:"))
1077 alias = g_strdup(value);
1081 if (username != NULL && protocol != NULL)
1083 valid = TRUE;
1085 *ret_username = username;
1086 *ret_protocol = protocol;
1088 if (ret_alias != NULL)
1089 *ret_alias = alias;
1091 /* Check for a compatible account. */
1092 if (ret_account != NULL)
1094 GList *list;
1095 PurpleAccount *account = NULL;
1096 GList *l;
1097 const char *protoname;
1099 if (all_accounts)
1100 list = purple_accounts_get_all();
1101 else
1102 list = purple_connections_get_all();
1104 for (l = list; l != NULL; l = l->next)
1106 PurpleConnection *gc;
1107 PurplePluginProtocolInfo *prpl_info = NULL;
1108 PurplePlugin *plugin;
1110 if (all_accounts)
1112 account = (PurpleAccount *)l->data;
1114 plugin = purple_plugins_find_with_id(
1115 purple_account_get_protocol_id(account));
1117 if (plugin == NULL)
1119 account = NULL;
1121 continue;
1124 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin);
1126 else
1128 gc = (PurpleConnection *)l->data;
1129 account = purple_connection_get_account(gc);
1131 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
1134 protoname = prpl_info->list_icon(account, NULL);
1136 if (!strcmp(protoname, protocol))
1137 break;
1139 account = NULL;
1142 /* Special case for AIM and ICQ */
1143 if (account == NULL && (!strcmp(protocol, "aim") ||
1144 !strcmp(protocol, "icq")))
1146 for (l = list; l != NULL; l = l->next)
1148 PurpleConnection *gc;
1149 PurplePluginProtocolInfo *prpl_info = NULL;
1150 PurplePlugin *plugin;
1152 if (all_accounts)
1154 account = (PurpleAccount *)l->data;
1156 plugin = purple_plugins_find_with_id(
1157 purple_account_get_protocol_id(account));
1159 if (plugin == NULL)
1161 account = NULL;
1163 continue;
1166 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin);
1168 else
1170 gc = (PurpleConnection *)l->data;
1171 account = purple_connection_get_account(gc);
1173 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
1176 protoname = prpl_info->list_icon(account, NULL);
1178 if (!strcmp(protoname, "aim") || !strcmp(protoname, "icq"))
1179 break;
1181 account = NULL;
1185 *ret_account = account;
1188 else
1190 valid = FALSE;
1192 g_free(username);
1193 g_free(protocol);
1194 g_free(alias);
1197 g_free(str);
1199 return valid;
1202 void
1203 pidgin_set_accessible_label (GtkWidget *w, GtkWidget *l)
1205 AtkObject *acc;
1206 const gchar *label_text;
1207 const gchar *existing_name;
1209 acc = gtk_widget_get_accessible (w);
1211 /* If this object has no name, set it's name with the label text */
1212 existing_name = atk_object_get_name (acc);
1213 if (!existing_name) {
1214 label_text = gtk_label_get_text (GTK_LABEL(l));
1215 if (label_text)
1216 atk_object_set_name (acc, label_text);
1219 pidgin_set_accessible_relations(w, l);
1222 void
1223 pidgin_set_accessible_relations (GtkWidget *w, GtkWidget *l)
1225 AtkObject *acc, *label;
1226 AtkObject *rel_obj[1];
1227 AtkRelationSet *set;
1228 AtkRelation *relation;
1230 acc = gtk_widget_get_accessible (w);
1231 label = gtk_widget_get_accessible (l);
1233 /* Make sure mnemonics work */
1234 gtk_label_set_mnemonic_widget(GTK_LABEL(l), w);
1236 /* Create the labeled-by relation */
1237 set = atk_object_ref_relation_set (acc);
1238 rel_obj[0] = label;
1239 relation = atk_relation_new (rel_obj, 1, ATK_RELATION_LABELLED_BY);
1240 atk_relation_set_add (set, relation);
1241 g_object_unref (relation);
1242 g_object_unref(set);
1244 /* Create the label-for relation */
1245 set = atk_object_ref_relation_set (label);
1246 rel_obj[0] = acc;
1247 relation = atk_relation_new (rel_obj, 1, ATK_RELATION_LABEL_FOR);
1248 atk_relation_set_add (set, relation);
1249 g_object_unref (relation);
1250 g_object_unref(set);
1253 void
1254 pidgin_menu_position_func_helper(GtkMenu *menu,
1255 gint *x,
1256 gint *y,
1257 gboolean *push_in,
1258 gpointer data)
1260 #if GTK_CHECK_VERSION(2,2,0)
1261 GtkWidget *widget;
1262 GtkRequisition requisition;
1263 GdkScreen *screen;
1264 GdkRectangle monitor;
1265 gint monitor_num;
1266 gint space_left, space_right, space_above, space_below;
1267 gint needed_width;
1268 gint needed_height;
1269 gint xthickness;
1270 gint ythickness;
1271 gboolean rtl;
1273 g_return_if_fail(GTK_IS_MENU(menu));
1275 widget = GTK_WIDGET(menu);
1276 screen = gtk_widget_get_screen(widget);
1277 xthickness = widget->style->xthickness;
1278 ythickness = widget->style->ythickness;
1279 rtl = (gtk_widget_get_direction(widget) == GTK_TEXT_DIR_RTL);
1282 * We need the requisition to figure out the right place to
1283 * popup the menu. In fact, we always need to ask here, since
1284 * if a size_request was queued while we weren't popped up,
1285 * the requisition won't have been recomputed yet.
1287 gtk_widget_size_request (widget, &requisition);
1289 monitor_num = gdk_screen_get_monitor_at_point (screen, *x, *y);
1291 push_in = FALSE;
1294 * The placement of popup menus horizontally works like this (with
1295 * RTL in parentheses)
1297 * - If there is enough room to the right (left) of the mouse cursor,
1298 * position the menu there.
1300 * - Otherwise, if if there is enough room to the left (right) of the
1301 * mouse cursor, position the menu there.
1303 * - Otherwise if the menu is smaller than the monitor, position it
1304 * on the side of the mouse cursor that has the most space available
1306 * - Otherwise (if there is simply not enough room for the menu on the
1307 * monitor), position it as far left (right) as possible.
1309 * Positioning in the vertical direction is similar: first try below
1310 * mouse cursor, then above.
1312 gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
1314 space_left = *x - monitor.x;
1315 space_right = monitor.x + monitor.width - *x - 1;
1316 space_above = *y - monitor.y;
1317 space_below = monitor.y + monitor.height - *y - 1;
1319 /* position horizontally */
1321 /* the amount of space we need to position the menu. Note the
1322 * menu is offset "xthickness" pixels
1324 needed_width = requisition.width - xthickness;
1326 if (needed_width <= space_left ||
1327 needed_width <= space_right)
1329 if ((rtl && needed_width <= space_left) ||
1330 (!rtl && needed_width > space_right))
1332 /* position left */
1333 *x = *x + xthickness - requisition.width + 1;
1335 else
1337 /* position right */
1338 *x = *x - xthickness;
1341 /* x is clamped on-screen further down */
1343 else if (requisition.width <= monitor.width)
1345 /* the menu is too big to fit on either side of the mouse
1346 * cursor, but smaller than the monitor. Position it on
1347 * the side that has the most space
1349 if (space_left > space_right)
1351 /* left justify */
1352 *x = monitor.x;
1354 else
1356 /* right justify */
1357 *x = monitor.x + monitor.width - requisition.width;
1360 else /* menu is simply too big for the monitor */
1362 if (rtl)
1364 /* right justify */
1365 *x = monitor.x + monitor.width - requisition.width;
1367 else
1369 /* left justify */
1370 *x = monitor.x;
1374 /* Position vertically. The algorithm is the same as above, but
1375 * simpler because we don't have to take RTL into account.
1377 needed_height = requisition.height - ythickness;
1379 if (needed_height <= space_above ||
1380 needed_height <= space_below)
1382 if (needed_height <= space_below)
1383 *y = *y - ythickness;
1384 else
1385 *y = *y + ythickness - requisition.height + 1;
1387 *y = CLAMP (*y, monitor.y,
1388 monitor.y + monitor.height - requisition.height);
1390 else if (needed_height > space_below && needed_height > space_above)
1392 if (space_below >= space_above)
1393 *y = monitor.y + monitor.height - requisition.height;
1394 else
1395 *y = monitor.y;
1397 else
1399 *y = monitor.y;
1401 #endif
1405 void
1406 pidgin_treeview_popup_menu_position_func(GtkMenu *menu,
1407 gint *x,
1408 gint *y,
1409 gboolean *push_in,
1410 gpointer data)
1412 GtkWidget *widget = GTK_WIDGET(data);
1413 GtkTreeView *tv = GTK_TREE_VIEW(data);
1414 GtkTreePath *path;
1415 GtkTreeViewColumn *col;
1416 GdkRectangle rect;
1417 gint ythickness = GTK_WIDGET(menu)->style->ythickness;
1419 gdk_window_get_origin (widget->window, x, y);
1420 gtk_tree_view_get_cursor (tv, &path, &col);
1421 gtk_tree_view_get_cell_area (tv, path, col, &rect);
1423 *x += rect.x+rect.width;
1424 *y += rect.y+rect.height+ythickness;
1425 pidgin_menu_position_func_helper(menu, x, y, push_in, data);
1428 enum {
1429 DND_FILE_TRANSFER,
1430 DND_IM_IMAGE,
1431 DND_BUDDY_ICON
1434 typedef struct {
1435 char *filename;
1436 PurpleAccount *account;
1437 char *who;
1438 } _DndData;
1440 static void dnd_image_ok_callback(_DndData *data, int choice)
1442 gchar *filedata;
1443 size_t size;
1444 struct stat st;
1445 GError *err = NULL;
1446 PurpleConversation *conv;
1447 PidginConversation *gtkconv;
1448 GtkTextIter iter;
1449 int id;
1450 PurpleBuddy *buddy;
1451 PurpleContact *contact;
1452 switch (choice) {
1453 case DND_BUDDY_ICON:
1454 if (g_stat(data->filename, &st)) {
1455 char *str;
1457 str = g_strdup_printf(_("The following error has occurred loading %s: %s"),
1458 data->filename, g_strerror(errno));
1459 purple_notify_error(NULL, NULL,
1460 _("Failed to load image"),
1461 str);
1462 g_free(str);
1464 return;
1467 buddy = purple_find_buddy(data->account, data->who);
1468 if (!buddy) {
1469 purple_debug_info("custom-icon", "You can only set custom icons for people on your buddylist.\n");
1470 break;
1472 contact = purple_buddy_get_contact(buddy);
1473 purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode*)contact, data->filename);
1474 break;
1475 case DND_FILE_TRANSFER:
1476 serv_send_file(purple_account_get_connection(data->account), data->who, data->filename);
1477 break;
1478 case DND_IM_IMAGE:
1479 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, data->account, data->who);
1480 gtkconv = PIDGIN_CONVERSATION(conv);
1482 if (!g_file_get_contents(data->filename, &filedata, &size,
1483 &err)) {
1484 char *str;
1486 str = g_strdup_printf(_("The following error has occurred loading %s: %s"), data->filename, err->message);
1487 purple_notify_error(NULL, NULL,
1488 _("Failed to load image"),
1489 str);
1491 g_error_free(err);
1492 g_free(str);
1494 return;
1496 id = purple_imgstore_add_with_id(filedata, size, data->filename);
1498 gtk_text_buffer_get_iter_at_mark(GTK_IMHTML(gtkconv->entry)->text_buffer, &iter,
1499 gtk_text_buffer_get_insert(GTK_IMHTML(gtkconv->entry)->text_buffer));
1500 gtk_imhtml_insert_image_at_iter(GTK_IMHTML(gtkconv->entry), id, &iter);
1501 purple_imgstore_unref_by_id(id);
1503 break;
1505 g_free(data->filename);
1506 g_free(data->who);
1507 g_free(data);
1510 static void dnd_image_cancel_callback(_DndData *data, int choice)
1512 g_free(data->filename);
1513 g_free(data->who);
1514 g_free(data);
1517 static void dnd_set_icon_ok_cb(_DndData *data)
1519 dnd_image_ok_callback(data, DND_BUDDY_ICON);
1522 static void dnd_set_icon_cancel_cb(_DndData *data)
1524 g_free(data->filename);
1525 g_free(data->who);
1526 g_free(data);
1529 void
1530 pidgin_dnd_file_manage(GtkSelectionData *sd, PurpleAccount *account, const char *who)
1532 GList *tmp;
1533 GdkPixbuf *pb;
1534 GList *files = purple_uri_list_extract_filenames((const gchar *)sd->data);
1535 PurpleConnection *gc = purple_account_get_connection(account);
1536 PurplePluginProtocolInfo *prpl_info = NULL;
1537 gboolean file_send_ok = FALSE;
1538 #ifndef _WIN32
1539 PurpleDesktopItem *item;
1540 #endif
1542 g_return_if_fail(account != NULL);
1543 g_return_if_fail(who != NULL);
1545 for(tmp = files; tmp != NULL ; tmp = g_list_next(tmp)) {
1546 gchar *filename = tmp->data;
1547 gchar *basename = g_path_get_basename(filename);
1549 /* Set the default action: don't send anything */
1550 file_send_ok = FALSE;
1552 /* XXX - Make ft API support creating a transfer with more than one file */
1553 if (!g_file_test(filename, G_FILE_TEST_EXISTS)) {
1554 continue;
1557 /* XXX - make ft api suupport sending a directory */
1558 /* Are we dealing with a directory? */
1559 if (g_file_test(filename, G_FILE_TEST_IS_DIR)) {
1560 char *str, *str2;
1562 str = g_strdup_printf(_("Cannot send folder %s."), basename);
1563 str2 = g_strdup_printf(_("%s cannot transfer a folder. You will need to send the files within individually."), PIDGIN_NAME);
1565 purple_notify_error(NULL, NULL,
1566 str, str2);
1568 g_free(str);
1569 g_free(str2);
1571 continue;
1574 /* Are we dealing with an image? */
1575 pb = gdk_pixbuf_new_from_file(filename, NULL);
1576 if (pb) {
1577 _DndData *data = g_malloc(sizeof(_DndData));
1578 gboolean ft = FALSE, im = FALSE;
1580 data->who = g_strdup(who);
1581 data->filename = g_strdup(filename);
1582 data->account = account;
1584 if (gc)
1585 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
1587 if (prpl_info && prpl_info->options & OPT_PROTO_IM_IMAGE)
1588 im = TRUE;
1590 if (prpl_info && prpl_info->can_receive_file)
1591 ft = prpl_info->can_receive_file(gc, who);
1592 else if (prpl_info && prpl_info->send_file)
1593 ft = TRUE;
1595 if (im && ft)
1596 purple_request_choice(NULL, NULL,
1597 _("You have dragged an image"),
1598 _("You can send this image as a file transfer, "
1599 "embed it into this message, or use it as the buddy icon for this user."),
1600 DND_FILE_TRANSFER, "OK", (GCallback)dnd_image_ok_callback,
1601 "Cancel", (GCallback)dnd_image_cancel_callback,
1602 account, who, NULL,
1603 data,
1604 _("Set as buddy icon"), DND_BUDDY_ICON,
1605 _("Send image file"), DND_FILE_TRANSFER,
1606 _("Insert in message"), DND_IM_IMAGE,
1607 NULL);
1608 else if (!(im || ft))
1609 purple_request_yes_no(NULL, NULL, _("You have dragged an image"),
1610 _("Would you like to set it as the buddy icon for this user?"),
1611 PURPLE_DEFAULT_ACTION_NONE,
1612 account, who, NULL,
1613 data, (GCallback)dnd_set_icon_ok_cb, (GCallback)dnd_set_icon_cancel_cb);
1614 else
1615 purple_request_choice(NULL, NULL,
1616 _("You have dragged an image"),
1617 (ft ? _("You can send this image as a file transfer, or use it as the buddy icon for this user.") :
1618 _("You can insert this image into this message, or use it as the buddy icon for this user")),
1619 (ft ? DND_FILE_TRANSFER : DND_IM_IMAGE),
1620 "OK", (GCallback)dnd_image_ok_callback,
1621 "Cancel", (GCallback)dnd_image_cancel_callback,
1622 account, who, NULL,
1623 data,
1624 _("Set as buddy icon"), DND_BUDDY_ICON,
1625 (ft ? _("Send image file") : _("Insert in message")), (ft ? DND_FILE_TRANSFER : DND_IM_IMAGE),
1626 NULL);
1627 gdk_pixbuf_unref(pb);
1628 return;
1631 #ifndef _WIN32
1632 /* Are we trying to send a .desktop file? */
1633 else if (purple_str_has_suffix(basename, ".desktop") && (item = purple_desktop_item_new_from_file(filename))) {
1634 PurpleDesktopItemType dtype;
1635 char key[64];
1636 const char *itemname = NULL;
1638 #if GTK_CHECK_VERSION(2,6,0)
1639 const char * const *langs;
1640 int i;
1641 langs = g_get_language_names();
1642 for (i = 0; langs[i]; i++) {
1643 g_snprintf(key, sizeof(key), "Name[%s]", langs[i]);
1644 itemname = purple_desktop_item_get_string(item, key);
1645 break;
1647 #else
1648 const char *lang = g_getenv("LANG");
1649 char *dot;
1650 dot = strchr(lang, '.');
1651 if (dot)
1652 *dot = '\0';
1653 g_snprintf(key, sizeof(key), "Name[%s]", lang);
1654 itemname = purple_desktop_item_get_string(item, key);
1655 #endif
1656 if (!itemname)
1657 itemname = purple_desktop_item_get_string(item, "Name");
1659 dtype = purple_desktop_item_get_entry_type(item);
1660 switch (dtype) {
1661 PurpleConversation *conv;
1662 PidginConversation *gtkconv;
1664 case PURPLE_DESKTOP_ITEM_TYPE_LINK:
1665 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, who);
1666 gtkconv = PIDGIN_CONVERSATION(conv);
1667 gtk_imhtml_insert_link(GTK_IMHTML(gtkconv->entry),
1668 gtk_text_buffer_get_insert(GTK_IMHTML(gtkconv->entry)->text_buffer),
1669 purple_desktop_item_get_string(item, "URL"), itemname);
1670 break;
1671 default:
1672 /* I don't know if we really want to do anything here. Most of the desktop item types are crap like
1673 * "MIME Type" (I have no clue how that would be a desktop item) and "Comment"... nothing we can really
1674 * send. The only logical one is "Application," but do we really want to send a binary and nothing else?
1675 * Probably not. I'll just give an error and return. */
1676 /* The original patch sent the icon used by the launcher. That's probably wrong */
1677 purple_notify_error(NULL, NULL, _("Cannot send launcher"), _("You dragged a desktop launcher. "
1678 "Most likely you wanted to send whatever this launcher points to instead of this launcher"
1679 " itself."));
1680 break;
1682 purple_desktop_item_unref(item);
1683 return;
1685 #endif /* _WIN32 */
1687 /* Everything is fine, let's send */
1688 serv_send_file(gc, who, filename);
1689 g_free(filename);
1691 g_list_free(files);
1694 void pidgin_buddy_icon_get_scale_size(GdkPixbuf *buf, PurpleBuddyIconSpec *spec, PurpleIconScaleRules rules, int *width, int *height)
1696 *width = gdk_pixbuf_get_width(buf);
1697 *height = gdk_pixbuf_get_height(buf);
1699 if ((spec == NULL) || !(spec->scale_rules & rules))
1700 return;
1702 purple_buddy_icon_get_scale_size(spec, width, height);
1704 /* and now for some arbitrary sanity checks */
1705 if(*width > 100)
1706 *width = 100;
1707 if(*height > 100)
1708 *height = 100;
1711 GdkPixbuf * pidgin_create_status_icon(PurpleStatusPrimitive prim, GtkWidget *w, const char *size)
1713 GtkIconSize icon_size = gtk_icon_size_from_name(size);
1714 GdkPixbuf *pixbuf = NULL;
1716 if (prim == PURPLE_STATUS_UNAVAILABLE)
1717 pixbuf = gtk_widget_render_icon (w, PIDGIN_STOCK_STATUS_BUSY,
1718 icon_size, "GtkWidget");
1719 else if (prim == PURPLE_STATUS_AWAY)
1720 pixbuf = gtk_widget_render_icon (w, PIDGIN_STOCK_STATUS_AWAY,
1721 icon_size, "GtkWidget");
1722 else if (prim == PURPLE_STATUS_EXTENDED_AWAY)
1723 pixbuf = gtk_widget_render_icon (w, PIDGIN_STOCK_STATUS_XA,
1724 icon_size, "GtkWidget");
1725 else if (prim == PURPLE_STATUS_INVISIBLE)
1726 pixbuf = gtk_widget_render_icon (w, PIDGIN_STOCK_STATUS_INVISIBLE,
1727 icon_size, "GtkWidget");
1728 else if (prim == PURPLE_STATUS_OFFLINE)
1729 pixbuf = gtk_widget_render_icon (w, PIDGIN_STOCK_STATUS_OFFLINE,
1730 icon_size, "GtkWidget");
1731 else
1732 pixbuf = gtk_widget_render_icon (w, PIDGIN_STOCK_STATUS_AVAILABLE,
1733 icon_size, "GtkWidget");
1734 return pixbuf;
1739 GdkPixbuf *
1740 pidgin_create_prpl_icon(PurpleAccount *account, PidginPrplIconSize size)
1742 PurplePlugin *prpl;
1744 g_return_val_if_fail(account != NULL, NULL);
1746 prpl = purple_find_prpl(purple_account_get_protocol_id(account));
1747 if (prpl == NULL)
1748 return NULL;
1749 return pidgin_create_prpl_icon_from_prpl(prpl, size, account);
1752 static void
1753 menu_action_cb(GtkMenuItem *item, gpointer object)
1755 gpointer data;
1756 void (*callback)(gpointer, gpointer);
1758 callback = g_object_get_data(G_OBJECT(item), "purplecallback");
1759 data = g_object_get_data(G_OBJECT(item), "purplecallbackdata");
1761 if (callback)
1762 callback(object, data);
1765 GtkWidget *
1766 pidgin_append_menu_action(GtkWidget *menu, PurpleMenuAction *act,
1767 gpointer object)
1769 GtkWidget *menuitem;
1771 if (act == NULL) {
1772 return pidgin_separator(menu);
1775 if (act->children == NULL) {
1776 menuitem = gtk_menu_item_new_with_mnemonic(act->label);
1778 if (act->callback != NULL) {
1779 g_object_set_data(G_OBJECT(menuitem),
1780 "purplecallback",
1781 act->callback);
1782 g_object_set_data(G_OBJECT(menuitem),
1783 "purplecallbackdata",
1784 act->data);
1785 g_signal_connect(G_OBJECT(menuitem), "activate",
1786 G_CALLBACK(menu_action_cb),
1787 object);
1788 } else {
1789 gtk_widget_set_sensitive(menuitem, FALSE);
1792 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1793 } else {
1794 GList *l = NULL;
1795 GtkWidget *submenu = NULL;
1796 GtkAccelGroup *group;
1798 menuitem = gtk_menu_item_new_with_mnemonic(act->label);
1799 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1801 submenu = gtk_menu_new();
1802 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
1804 group = gtk_menu_get_accel_group(GTK_MENU(menu));
1805 if (group) {
1806 char *path = g_strdup_printf("%s/%s", GTK_MENU_ITEM(menuitem)->accel_path, act->label);
1807 gtk_menu_set_accel_path(GTK_MENU(submenu), path);
1808 g_free(path);
1809 gtk_menu_set_accel_group(GTK_MENU(submenu), group);
1812 for (l = act->children; l; l = l->next) {
1813 PurpleMenuAction *act = (PurpleMenuAction *)l->data;
1815 pidgin_append_menu_action(submenu, act, object);
1817 g_list_free(act->children);
1818 act->children = NULL;
1820 purple_menu_action_free(act);
1821 return menuitem;
1824 #if GTK_CHECK_VERSION(2,3,0)
1825 # define NEW_STYLE_COMPLETION
1826 #endif
1828 typedef struct
1830 GtkWidget *entry;
1831 GtkWidget *accountopt;
1833 PidginFilterBuddyCompletionEntryFunc filter_func;
1834 gpointer filter_func_user_data;
1836 #ifdef NEW_STYLE_COMPLETION
1837 GtkListStore *store;
1838 #else
1839 GCompletion *completion;
1840 gboolean completion_started;
1841 GList *log_items;
1842 #endif /* NEW_STYLE_COMPLETION */
1843 } PidginCompletionData;
1845 #ifndef NEW_STYLE_COMPLETION
1846 static gboolean
1847 completion_entry_event(GtkEditable *entry, GdkEventKey *event,
1848 PidginCompletionData *data)
1850 int pos, end_pos;
1852 if (event->type == GDK_KEY_PRESS && event->keyval == GDK_Tab)
1854 gtk_editable_get_selection_bounds(entry, &pos, &end_pos);
1856 if (data->completion_started &&
1857 pos != end_pos && pos > 1 &&
1858 end_pos == strlen(gtk_entry_get_text(GTK_ENTRY(entry))))
1860 gtk_editable_select_region(entry, 0, 0);
1861 gtk_editable_set_position(entry, -1);
1863 return TRUE;
1866 else if (event->type == GDK_KEY_PRESS && event->length > 0)
1868 char *prefix, *nprefix;
1870 gtk_editable_get_selection_bounds(entry, &pos, &end_pos);
1872 if (data->completion_started &&
1873 pos != end_pos && pos > 1 &&
1874 end_pos == strlen(gtk_entry_get_text(GTK_ENTRY(entry))))
1876 char *temp;
1878 temp = gtk_editable_get_chars(entry, 0, pos);
1879 prefix = g_strconcat(temp, event->string, NULL);
1880 g_free(temp);
1882 else if (pos == end_pos && pos > 1 &&
1883 end_pos == strlen(gtk_entry_get_text(GTK_ENTRY(entry))))
1885 prefix = g_strconcat(gtk_entry_get_text(GTK_ENTRY(entry)),
1886 event->string, NULL);
1888 else
1889 return FALSE;
1891 pos = strlen(prefix);
1892 nprefix = NULL;
1894 g_completion_complete(data->completion, prefix, &nprefix);
1896 if (nprefix != NULL)
1898 gtk_entry_set_text(GTK_ENTRY(entry), nprefix);
1899 gtk_editable_set_position(entry, pos);
1900 gtk_editable_select_region(entry, pos, -1);
1902 data->completion_started = TRUE;
1904 g_free(nprefix);
1905 g_free(prefix);
1907 return TRUE;
1910 g_free(prefix);
1913 return FALSE;
1916 static void
1917 destroy_completion_data(GtkWidget *w, PidginCompletionData *data)
1919 g_list_foreach(data->completion->items, (GFunc)g_free, NULL);
1920 g_completion_free(data->completion);
1922 g_free(data);
1924 #endif /* !NEW_STYLE_COMPLETION */
1926 #ifdef NEW_STYLE_COMPLETION
1927 static gboolean screenname_completion_match_func(GtkEntryCompletion *completion,
1928 const gchar *key, GtkTreeIter *iter, gpointer user_data)
1930 GtkTreeModel *model;
1931 GValue val1;
1932 GValue val2;
1933 const char *tmp;
1935 model = gtk_entry_completion_get_model (completion);
1937 val1.g_type = 0;
1938 gtk_tree_model_get_value(model, iter, 2, &val1);
1939 tmp = g_value_get_string(&val1);
1940 if (tmp != NULL && purple_str_has_prefix(tmp, key))
1942 g_value_unset(&val1);
1943 return TRUE;
1945 g_value_unset(&val1);
1947 val2.g_type = 0;
1948 gtk_tree_model_get_value(model, iter, 3, &val2);
1949 tmp = g_value_get_string(&val2);
1950 if (tmp != NULL && purple_str_has_prefix(tmp, key))
1952 g_value_unset(&val2);
1953 return TRUE;
1955 g_value_unset(&val2);
1957 return FALSE;
1960 static gboolean screenname_completion_match_selected_cb(GtkEntryCompletion *completion,
1961 GtkTreeModel *model, GtkTreeIter *iter, PidginCompletionData *data)
1963 GValue val;
1964 GtkWidget *optmenu = data->accountopt;
1965 PurpleAccount *account;
1967 val.g_type = 0;
1968 gtk_tree_model_get_value(model, iter, 1, &val);
1969 gtk_entry_set_text(GTK_ENTRY(data->entry), g_value_get_string(&val));
1970 g_value_unset(&val);
1972 gtk_tree_model_get_value(model, iter, 4, &val);
1973 account = g_value_get_pointer(&val);
1974 g_value_unset(&val);
1976 if (account == NULL)
1977 return TRUE;
1979 if (optmenu != NULL)
1980 aop_option_menu_select_by_data(optmenu, account);
1982 return TRUE;
1985 static void
1986 add_screenname_autocomplete_entry(GtkListStore *store, const char *buddy_alias, const char *contact_alias,
1987 const PurpleAccount *account, const char *screenname)
1989 GtkTreeIter iter;
1990 gboolean completion_added = FALSE;
1991 gchar *normalized_screenname;
1992 gchar *tmp;
1994 tmp = g_utf8_normalize(screenname, -1, G_NORMALIZE_DEFAULT);
1995 normalized_screenname = g_utf8_casefold(tmp, -1);
1996 g_free(tmp);
1998 /* There's no sense listing things like: 'xxx "xxx"'
1999 when the screenname and buddy alias match. */
2000 if (buddy_alias && strcmp(buddy_alias, screenname)) {
2001 char *completion_entry = g_strdup_printf("%s \"%s\"", screenname, buddy_alias);
2002 char *tmp2 = g_utf8_normalize(buddy_alias, -1, G_NORMALIZE_DEFAULT);
2004 tmp = g_utf8_casefold(tmp2, -1);
2005 g_free(tmp2);
2007 gtk_list_store_append(store, &iter);
2008 gtk_list_store_set(store, &iter,
2009 0, completion_entry,
2010 1, screenname,
2011 2, normalized_screenname,
2012 3, tmp,
2013 4, account,
2014 -1);
2015 g_free(completion_entry);
2016 g_free(tmp);
2017 completion_added = TRUE;
2020 /* There's no sense listing things like: 'xxx "xxx"'
2021 when the screenname and contact alias match. */
2022 if (contact_alias && strcmp(contact_alias, screenname)) {
2023 /* We don't want duplicates when the contact and buddy alias match. */
2024 if (!buddy_alias || strcmp(contact_alias, buddy_alias)) {
2025 char *completion_entry = g_strdup_printf("%s \"%s\"",
2026 screenname, contact_alias);
2027 char *tmp2 = g_utf8_normalize(contact_alias, -1, G_NORMALIZE_DEFAULT);
2029 tmp = g_utf8_casefold(tmp2, -1);
2030 g_free(tmp2);
2032 gtk_list_store_append(store, &iter);
2033 gtk_list_store_set(store, &iter,
2034 0, completion_entry,
2035 1, screenname,
2036 2, normalized_screenname,
2037 3, tmp,
2038 4, account,
2039 -1);
2040 g_free(completion_entry);
2041 g_free(tmp);
2042 completion_added = TRUE;
2046 if (completion_added == FALSE) {
2047 /* Add the buddy's screenname. */
2048 gtk_list_store_append(store, &iter);
2049 gtk_list_store_set(store, &iter,
2050 0, screenname,
2051 1, screenname,
2052 2, normalized_screenname,
2053 3, NULL,
2054 4, account,
2055 -1);
2058 g_free(normalized_screenname);
2060 #endif /* NEW_STYLE_COMPLETION */
2062 static void get_log_set_name(PurpleLogSet *set, gpointer value, PidginCompletionData *data)
2064 PidginFilterBuddyCompletionEntryFunc filter_func = data->filter_func;
2065 gpointer user_data = data->filter_func_user_data;
2067 /* 1. Don't show buddies because we will have gotten them already.
2068 * 2. The boxes that use this autocomplete code handle only IMs. */
2069 if (!set->buddy && set->type == PURPLE_LOG_IM) {
2070 PidginBuddyCompletionEntry entry;
2071 entry.is_buddy = FALSE;
2072 entry.entry.logged_buddy = set;
2074 if (filter_func(&entry, user_data)) {
2075 #ifdef NEW_STYLE_COMPLETION
2076 add_screenname_autocomplete_entry(data->store,
2077 NULL, NULL, set->account, set->name);
2078 #else
2079 /* Steal the name for the GCompletion. */
2080 data->log_items = g_list_append(data->log_items, set->name);
2081 set->name = set->normalized_name = NULL;
2082 #endif /* NEW_STYLE_COMPLETION */
2087 static void
2088 add_completion_list(PidginCompletionData *data)
2090 PurpleBlistNode *gnode, *cnode, *bnode;
2091 PidginFilterBuddyCompletionEntryFunc filter_func = data->filter_func;
2092 gpointer user_data = data->filter_func_user_data;
2093 GHashTable *sets;
2095 #ifdef NEW_STYLE_COMPLETION
2096 gtk_list_store_clear(data->store);
2097 #else
2098 GList *item = g_list_append(NULL, NULL);
2100 g_list_foreach(data->completion->items, (GFunc)g_free, NULL);
2101 g_completion_clear_items(data->completion);
2102 #endif /* NEW_STYLE_COMPLETION */
2104 for (gnode = purple_get_blist()->root; gnode != NULL; gnode = gnode->next)
2106 if (!PURPLE_BLIST_NODE_IS_GROUP(gnode))
2107 continue;
2109 for (cnode = gnode->child; cnode != NULL; cnode = cnode->next)
2111 if (!PURPLE_BLIST_NODE_IS_CONTACT(cnode))
2112 continue;
2114 for (bnode = cnode->child; bnode != NULL; bnode = bnode->next)
2116 PidginBuddyCompletionEntry entry;
2117 entry.is_buddy = TRUE;
2118 entry.entry.buddy = (PurpleBuddy *) bnode;
2120 if (filter_func(&entry, user_data)) {
2121 #ifdef NEW_STYLE_COMPLETION
2122 add_screenname_autocomplete_entry(data->store,
2123 ((PurpleContact *)cnode)->alias,
2124 purple_buddy_get_contact_alias(entry.entry.buddy),
2125 entry.entry.buddy->account,
2126 entry.entry.buddy->name
2128 #else
2129 item->data = g_strdup(entry.entry.buddy->name);
2130 g_completion_add_items(data->completion, item);
2131 #endif /* NEW_STYLE_COMPLETION */
2137 #ifndef NEW_STYLE_COMPLETION
2138 g_list_free(item);
2139 data->log_items = NULL;
2140 #endif /* NEW_STYLE_COMPLETION */
2142 sets = purple_log_get_log_sets();
2143 g_hash_table_foreach(sets, (GHFunc)get_log_set_name, data);
2144 g_hash_table_destroy(sets);
2146 #ifndef NEW_STYLE_COMPLETION
2147 g_completion_add_items(data->completion, data->log_items);
2148 g_list_free(data->log_items);
2149 #endif /* NEW_STYLE_COMPLETION */
2152 static void
2153 screenname_autocomplete_destroyed_cb(GtkWidget *widget, gpointer data)
2155 g_free(data);
2156 purple_signals_disconnect_by_handle(widget);
2159 static void
2160 repopulate_autocomplete(gpointer something, gpointer data)
2162 add_completion_list(data);
2166 void
2167 pidgin_setup_screenname_autocomplete_with_filter(GtkWidget *entry, GtkWidget *accountopt, PidginFilterBuddyCompletionEntryFunc filter_func, gpointer user_data)
2169 PidginCompletionData *data;
2171 #ifdef NEW_STYLE_COMPLETION
2172 /* Store the displayed completion value, the screenname, the UTF-8 normalized & casefolded screenname,
2173 * the UTF-8 normalized & casefolded value for comparison, and the account. */
2174 GtkListStore *store;
2176 GtkEntryCompletion *completion;
2178 data = g_new0(PidginCompletionData, 1);
2179 store = gtk_list_store_new(5, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER);
2181 data->entry = entry;
2182 data->accountopt = accountopt;
2183 if (filter_func == NULL) {
2184 data->filter_func = pidgin_screenname_autocomplete_default_filter;
2185 data->filter_func_user_data = NULL;
2186 } else {
2187 data->filter_func = filter_func;
2188 data->filter_func_user_data = user_data;
2190 data->store = store;
2192 add_completion_list(data);
2194 /* Sort the completion list by screenname. */
2195 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store),
2196 1, GTK_SORT_ASCENDING);
2198 completion = gtk_entry_completion_new();
2199 gtk_entry_completion_set_match_func(completion, screenname_completion_match_func, NULL, NULL);
2201 g_signal_connect(G_OBJECT(completion), "match-selected",
2202 G_CALLBACK(screenname_completion_match_selected_cb), data);
2204 gtk_entry_set_completion(GTK_ENTRY(entry), completion);
2205 g_object_unref(completion);
2207 gtk_entry_completion_set_model(completion, GTK_TREE_MODEL(store));
2208 g_object_unref(store);
2210 gtk_entry_completion_set_text_column(completion, 0);
2212 #else /* !NEW_STYLE_COMPLETION */
2214 data = g_new0(PidginCompletionData, 1);
2216 data->entry = entry;
2217 data->accountopt = accountopt;
2218 if (filter_func == NULL) {
2219 data->filter_func = pidgin_screenname_autocomplete_default_filter;
2220 data->filter_func_user_data = NULL;
2221 } else {
2222 data->filter_func = filter_func;
2223 data->filter_func_user_data = user_data;
2225 data->completion = g_completion_new(NULL);
2226 data->completion_started = FALSE;
2228 add_completion_list(data);
2230 g_completion_set_compare(data->completion, g_ascii_strncasecmp);
2232 g_signal_connect(G_OBJECT(entry), "event",
2233 G_CALLBACK(completion_entry_event), data);
2234 g_signal_connect(G_OBJECT(entry), "destroy",
2235 G_CALLBACK(destroy_completion_data), data);
2237 #endif /* !NEW_STYLE_COMPLETION */
2239 purple_signal_connect(purple_connections_get_handle(), "signed-on", entry,
2240 PURPLE_CALLBACK(repopulate_autocomplete), data);
2241 purple_signal_connect(purple_connections_get_handle(), "signed-off", entry,
2242 PURPLE_CALLBACK(repopulate_autocomplete), data);
2244 purple_signal_connect(purple_accounts_get_handle(), "account-added", entry,
2245 PURPLE_CALLBACK(repopulate_autocomplete), data);
2246 purple_signal_connect(purple_accounts_get_handle(), "account-removed", entry,
2247 PURPLE_CALLBACK(repopulate_autocomplete), data);
2249 g_signal_connect(G_OBJECT(entry), "destroy", G_CALLBACK(screenname_autocomplete_destroyed_cb), data);
2252 gboolean
2253 pidgin_screenname_autocomplete_default_filter(const PidginBuddyCompletionEntry *completion_entry, gpointer all_accounts) {
2254 gboolean all = GPOINTER_TO_INT(all_accounts);
2256 if (completion_entry->is_buddy) {
2257 return all || purple_account_is_connected(completion_entry->entry.buddy->account);
2258 } else {
2259 return all || (completion_entry->entry.logged_buddy->account != NULL && purple_account_is_connected(completion_entry->entry.logged_buddy->account));
2263 void
2264 pidgin_setup_screenname_autocomplete(GtkWidget *entry, GtkWidget *accountopt, gboolean all) {
2265 pidgin_setup_screenname_autocomplete_with_filter(entry, accountopt, pidgin_screenname_autocomplete_default_filter, GINT_TO_POINTER(all));
2270 void pidgin_set_cursor(GtkWidget *widget, GdkCursorType cursor_type)
2272 GdkCursor *cursor;
2274 g_return_if_fail(widget != NULL);
2275 if (widget->window == NULL)
2276 return;
2278 cursor = gdk_cursor_new(cursor_type);
2279 gdk_window_set_cursor(widget->window, cursor);
2280 gdk_cursor_unref(cursor);
2282 #if GTK_CHECK_VERSION(2,4,0)
2283 gdk_display_flush(gdk_drawable_get_display(GDK_DRAWABLE(widget->window)));
2284 #else
2285 gdk_flush();
2286 #endif
2289 void pidgin_clear_cursor(GtkWidget *widget)
2291 g_return_if_fail(widget != NULL);
2292 if (widget->window == NULL)
2293 return;
2295 gdk_window_set_cursor(widget->window, NULL);
2298 struct _icon_chooser {
2299 GtkWidget *icon_filesel;
2300 GtkWidget *icon_preview;
2301 GtkWidget *icon_text;
2303 void (*callback)(const char*,gpointer);
2304 gpointer data;
2307 #if !GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */
2308 static void
2309 icon_filesel_delete_cb(GtkWidget *w, struct _icon_chooser *dialog)
2311 if (dialog->icon_filesel != NULL)
2312 gtk_widget_destroy(dialog->icon_filesel);
2314 if (dialog->callback)
2315 dialog->callback(NULL, dialog->data);
2317 g_free(dialog);
2319 #endif /* FILECHOOSER */
2323 #if GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */
2324 static void
2325 icon_filesel_choose_cb(GtkWidget *widget, gint response, struct _icon_chooser *dialog)
2327 char *filename, *current_folder;
2329 if (response != GTK_RESPONSE_ACCEPT) {
2330 if (response == GTK_RESPONSE_CANCEL) {
2331 gtk_widget_destroy(dialog->icon_filesel);
2333 dialog->icon_filesel = NULL;
2334 if (dialog->callback)
2335 dialog->callback(NULL, dialog->data);
2336 g_free(dialog);
2337 return;
2340 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog->icon_filesel));
2341 current_folder = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(dialog->icon_filesel));
2342 if (current_folder != NULL) {
2343 purple_prefs_set_path(PIDGIN_PREFS_ROOT "/filelocations/last_icon_folder", current_folder);
2344 g_free(current_folder);
2347 #else /* FILECHOOSER */
2348 static void
2349 icon_filesel_choose_cb(GtkWidget *w, struct _icon_chooser *dialog)
2351 char *filename, *current_folder;
2353 filename = g_strdup(gtk_file_selection_get_filename(
2354 GTK_FILE_SELECTION(dialog->icon_filesel)));
2356 /* If they typed in a directory, change there */
2357 if (pidgin_check_if_dir(filename,
2358 GTK_FILE_SELECTION(dialog->icon_filesel)))
2360 g_free(filename);
2361 return;
2364 current_folder = g_path_get_dirname(filename);
2365 if (current_folder != NULL) {
2366 purple_prefs_set_path(PIDGIN_PREFS_ROOT "/filelocations/last_icon_folder", current_folder);
2367 g_free(current_folder);
2370 #endif /* FILECHOOSER */
2371 #if 0 /* mismatched curly braces */
2373 #endif
2374 if (dialog->callback)
2375 dialog->callback(filename, dialog->data);
2376 gtk_widget_destroy(dialog->icon_filesel);
2377 g_free(filename);
2378 g_free(dialog);
2382 static void
2383 #if GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */
2384 icon_preview_change_cb(GtkFileChooser *widget, struct _icon_chooser *dialog)
2385 #else /* FILECHOOSER */
2386 icon_preview_change_cb(GtkTreeSelection *sel, struct _icon_chooser *dialog)
2387 #endif /* FILECHOOSER */
2389 GdkPixbuf *pixbuf, *scale;
2390 int height, width;
2391 char *basename, *markup, *size;
2392 struct stat st;
2393 char *filename;
2395 #if GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */
2396 filename = gtk_file_chooser_get_preview_filename(
2397 GTK_FILE_CHOOSER(dialog->icon_filesel));
2398 #else /* FILECHOOSER */
2399 filename = g_strdup(gtk_file_selection_get_filename(
2400 GTK_FILE_SELECTION(dialog->icon_filesel)));
2401 #endif /* FILECHOOSER */
2403 if (!filename || g_stat(filename, &st) || !(pixbuf = gdk_pixbuf_new_from_file(filename, NULL)))
2405 gtk_image_set_from_pixbuf(GTK_IMAGE(dialog->icon_preview), NULL);
2406 gtk_label_set_markup(GTK_LABEL(dialog->icon_text), "");
2407 g_free(filename);
2408 return;
2411 width = gdk_pixbuf_get_width(pixbuf);
2412 height = gdk_pixbuf_get_height(pixbuf);
2413 basename = g_path_get_basename(filename);
2414 size = purple_str_size_to_units(st.st_size);
2415 markup = g_strdup_printf(_("<b>File:</b> %s\n"
2416 "<b>File size:</b> %s\n"
2417 "<b>Image size:</b> %dx%d"),
2418 basename, size, width, height);
2420 scale = gdk_pixbuf_scale_simple(pixbuf, width * 50 / height,
2421 50, GDK_INTERP_BILINEAR);
2422 gtk_image_set_from_pixbuf(GTK_IMAGE(dialog->icon_preview), scale);
2423 gtk_label_set_markup(GTK_LABEL(dialog->icon_text), markup);
2425 g_object_unref(G_OBJECT(pixbuf));
2426 g_object_unref(G_OBJECT(scale));
2427 g_free(filename);
2428 g_free(basename);
2429 g_free(size);
2430 g_free(markup);
2434 GtkWidget *pidgin_buddy_icon_chooser_new(GtkWindow *parent, void(*callback)(const char *, gpointer), gpointer data) {
2435 struct _icon_chooser *dialog = g_new0(struct _icon_chooser, 1);
2437 #if GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */
2438 GtkWidget *vbox;
2439 #else
2440 GtkWidget *hbox;
2441 GtkWidget *tv;
2442 GtkTreeSelection *sel;
2443 #endif /* FILECHOOSER */
2444 const char *current_folder;
2446 dialog->callback = callback;
2447 dialog->data = data;
2449 if (dialog->icon_filesel != NULL) {
2450 gtk_window_present(GTK_WINDOW(dialog->icon_filesel));
2451 return NULL;
2454 current_folder = purple_prefs_get_path(PIDGIN_PREFS_ROOT "/filelocations/last_icon_folder");
2455 #if GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */
2457 dialog->icon_filesel = gtk_file_chooser_dialog_new(_("Buddy Icon"),
2458 parent,
2459 GTK_FILE_CHOOSER_ACTION_OPEN,
2460 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2461 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
2462 NULL);
2463 gtk_dialog_set_default_response(GTK_DIALOG(dialog->icon_filesel), GTK_RESPONSE_ACCEPT);
2464 if ((current_folder != NULL) && (*current_folder != '\0'))
2465 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog->icon_filesel),
2466 current_folder);
2468 dialog->icon_preview = gtk_image_new();
2469 dialog->icon_text = gtk_label_new(NULL);
2471 vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
2472 gtk_widget_set_size_request(GTK_WIDGET(vbox), -1, 50);
2473 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(dialog->icon_preview), TRUE, FALSE, 0);
2474 gtk_box_pack_end(GTK_BOX(vbox), GTK_WIDGET(dialog->icon_text), FALSE, FALSE, 0);
2475 gtk_widget_show_all(vbox);
2477 gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog->icon_filesel), vbox);
2478 gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(dialog->icon_filesel), TRUE);
2479 gtk_file_chooser_set_use_preview_label(GTK_FILE_CHOOSER(dialog->icon_filesel), FALSE);
2481 g_signal_connect(G_OBJECT(dialog->icon_filesel), "update-preview",
2482 G_CALLBACK(icon_preview_change_cb), dialog);
2483 g_signal_connect(G_OBJECT(dialog->icon_filesel), "response",
2484 G_CALLBACK(icon_filesel_choose_cb), dialog);
2485 icon_preview_change_cb(NULL, dialog);
2486 #else /* FILECHOOSER */
2487 dialog->icon_filesel = gtk_file_selection_new(_("Buddy Icon"));
2488 dialog->icon_preview = gtk_image_new();
2489 dialog->icon_text = gtk_label_new(NULL);
2490 if ((current_folder != NULL) && (*current_folder != '\0'))
2491 gtk_file_selection_set_filename(GTK_FILE_SELECTION(dialog->icon_filesel),
2492 current_folder);
2494 gtk_widget_set_size_request(GTK_WIDGET(dialog->icon_preview),-1, 50);
2495 hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
2496 gtk_box_pack_start(
2497 GTK_BOX(GTK_FILE_SELECTION(dialog->icon_filesel)->main_vbox),
2498 hbox, FALSE, FALSE, 0);
2499 gtk_box_pack_end(GTK_BOX(hbox), dialog->icon_preview,
2500 FALSE, FALSE, 0);
2501 gtk_box_pack_end(GTK_BOX(hbox), dialog->icon_text, FALSE, FALSE, 0);
2503 tv = GTK_FILE_SELECTION(dialog->icon_filesel)->file_list;
2504 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv));
2506 g_signal_connect(G_OBJECT(sel), "changed",
2507 G_CALLBACK(icon_preview_change_cb), dialog);
2508 g_signal_connect(
2509 G_OBJECT(GTK_FILE_SELECTION(dialog->icon_filesel)->ok_button),
2510 "clicked",
2511 G_CALLBACK(icon_filesel_choose_cb), dialog);
2512 g_signal_connect(
2513 G_OBJECT(GTK_FILE_SELECTION(dialog->icon_filesel)->cancel_button),
2514 "clicked",
2515 G_CALLBACK(icon_filesel_delete_cb), dialog);
2516 g_signal_connect(G_OBJECT(dialog->icon_filesel), "destroy",
2517 G_CALLBACK(icon_filesel_delete_cb), dialog);
2518 #endif /* FILECHOOSER */
2520 #ifdef _WIN32
2521 g_signal_connect(G_OBJECT(dialog->icon_filesel), "show",
2522 G_CALLBACK(winpidgin_ensure_onscreen), dialog->icon_filesel);
2523 #endif
2525 return dialog->icon_filesel;
2529 #if GTK_CHECK_VERSION(2,2,0)
2530 static gboolean
2531 str_array_match(char **a, char **b)
2533 int i, j;
2535 if (!a || !b)
2536 return FALSE;
2537 for (i = 0; a[i] != NULL; i++)
2538 for (j = 0; b[j] != NULL; j++)
2539 if (!g_ascii_strcasecmp(a[i], b[j]))
2540 return TRUE;
2541 return FALSE;
2543 #endif
2545 gpointer
2546 pidgin_convert_buddy_icon(PurplePlugin *plugin, const char *path, size_t *len)
2548 PurplePluginProtocolInfo *prpl_info;
2549 #if GTK_CHECK_VERSION(2,2,0)
2550 char **prpl_formats;
2551 int width, height;
2552 char **pixbuf_formats = NULL;
2553 GdkPixbufFormat *format;
2554 GdkPixbuf *pixbuf;
2555 #if !GTK_CHECK_VERSION(2,4,0)
2556 GdkPixbufLoader *loader;
2557 #endif
2558 #endif
2559 gchar *contents;
2560 gsize length;
2562 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin);
2564 g_return_val_if_fail(prpl_info->icon_spec.format != NULL, NULL);
2567 #if GTK_CHECK_VERSION(2,2,0)
2568 #if GTK_CHECK_VERSION(2,4,0)
2569 format = gdk_pixbuf_get_file_info(path, &width, &height);
2570 #else
2571 loader = gdk_pixbuf_loader_new();
2572 if (g_file_get_contents(path, &contents, &length, NULL)) {
2573 gdk_pixbuf_loader_write(loader, contents, length, NULL);
2574 g_free(contents);
2576 gdk_pixbuf_loader_close(loader, NULL);
2577 pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
2578 width = gdk_pixbuf_get_width(pixbuf);
2579 height = gdk_pixbuf_get_height(pixbuf);
2580 format = gdk_pixbuf_loader_get_format(loader);
2581 g_object_unref(G_OBJECT(loader));
2582 #endif
2583 if (format == NULL)
2584 return NULL;
2586 pixbuf_formats = gdk_pixbuf_format_get_extensions(format);
2587 prpl_formats = g_strsplit(prpl_info->icon_spec.format,",",0);
2588 if (str_array_match(pixbuf_formats, prpl_formats) && /* This is an acceptable format AND */
2589 (!(prpl_info->icon_spec.scale_rules & PURPLE_ICON_SCALE_SEND) || /* The prpl doesn't scale before it sends OR */
2590 (prpl_info->icon_spec.min_width <= width &&
2591 prpl_info->icon_spec.max_width >= width &&
2592 prpl_info->icon_spec.min_height <= height &&
2593 prpl_info->icon_spec.max_height >= height))) /* The icon is the correct size */
2594 #endif
2596 #if GTK_CHECK_VERSION(2,2,0)
2597 g_strfreev(prpl_formats);
2598 g_strfreev(pixbuf_formats);
2599 #endif
2600 /* We don't need to scale the image. */
2602 contents = NULL;
2603 if (!g_file_get_contents(path, &contents, &length, NULL))
2605 g_free(contents);
2606 #if GTK_CHECK_VERSION(2,2,0) && !GTK_CHECK_VERSION(2,4,0)
2607 g_object_unref(G_OBJECT(pixbuf));
2608 #endif
2609 return NULL;
2612 #if GTK_CHECK_VERSION(2,2,0)
2613 else
2615 int i;
2616 GError *error = NULL;
2617 GdkPixbuf *scale;
2618 gboolean success = FALSE;
2619 char *filename = NULL;
2621 g_strfreev(pixbuf_formats);
2623 pixbuf = gdk_pixbuf_new_from_file(path, &error);
2624 if (error) {
2625 purple_debug_error("buddyicon", "Could not open icon for conversion: %s\n", error->message);
2626 g_error_free(error);
2627 g_strfreev(prpl_formats);
2628 return NULL;
2631 if ((prpl_info->icon_spec.scale_rules & PURPLE_ICON_SCALE_SEND) &&
2632 (width < prpl_info->icon_spec.min_width ||
2633 width > prpl_info->icon_spec.max_width ||
2634 height < prpl_info->icon_spec.min_height ||
2635 height > prpl_info->icon_spec.max_height))
2637 int new_width = width;
2638 int new_height = height;
2640 purple_buddy_icon_get_scale_size(&prpl_info->icon_spec, &new_width, &new_height);
2642 scale = gdk_pixbuf_scale_simple(pixbuf, new_width, new_height,
2643 GDK_INTERP_HYPER);
2644 g_object_unref(G_OBJECT(pixbuf));
2645 pixbuf = scale;
2648 for (i = 0; prpl_formats[i]; i++) {
2649 FILE *fp;
2651 g_free(filename);
2652 fp = purple_mkstemp(&filename, TRUE);
2653 if (!fp)
2655 g_free(filename);
2656 return NULL;
2658 fclose(fp);
2660 purple_debug_info("buddyicon", "Converting buddy icon to %s as %s\n", prpl_formats[i], filename);
2661 /* The "compression" param wasn't supported until gdk-pixbuf 2.8.
2662 * Using it in previous versions causes the save to fail (and an assert message). */
2663 if ((gdk_pixbuf_major_version > 2 || (gdk_pixbuf_major_version == 2
2664 && gdk_pixbuf_minor_version >= 8))
2665 && strcmp(prpl_formats[i], "png") == 0) {
2666 if (gdk_pixbuf_save(pixbuf, filename, prpl_formats[i],
2667 &error, "compression", "9", NULL)) {
2668 success = TRUE;
2669 break;
2671 } else if (gdk_pixbuf_save(pixbuf, filename, prpl_formats[i],
2672 &error, NULL)) {
2673 success = TRUE;
2674 break;
2677 /* The NULL checking is necessary due to this bug:
2678 * http://bugzilla.gnome.org/show_bug.cgi?id=405539 */
2679 purple_debug_warning("buddyicon", "Could not convert to %s: %s\n", prpl_formats[i],
2680 (error && error->message) ? error->message : "Unknown error");
2681 g_error_free(error);
2682 error = NULL;
2684 g_strfreev(prpl_formats);
2685 g_object_unref(G_OBJECT(pixbuf));
2686 if (!success) {
2687 purple_debug_error("buddyicon", "Could not convert icon to usable format.\n");
2688 return NULL;
2691 contents = NULL;
2692 if (!g_file_get_contents(filename, &contents, &length, NULL))
2694 purple_debug_error("buddyicon",
2695 "Could not read '%s', which we just wrote to disk.\n",
2696 filename);
2698 g_free(contents);
2699 g_free(filename);
2700 return NULL;
2703 g_unlink(filename);
2704 g_free(filename);
2707 /* Check the image size */
2709 * TODO: If the file is too big, it would be cool if we checked if
2710 * the prpl supported jpeg, and then we could convert to that
2711 * and use a lower quality setting.
2713 if ((prpl_info->icon_spec.max_filesize != 0) &&
2714 (length > prpl_info->icon_spec.max_filesize))
2716 gchar *tmp;
2717 tmp = g_strdup_printf(_("The file '%s' is too large for %s. Please try a smaller image.\n"),
2718 path, plugin->info->name);
2719 purple_notify_error(NULL, _("Icon Error"),
2720 _("Could not set icon"), tmp);
2721 purple_debug_info("buddyicon",
2722 "'%s' was converted to an image which is %" G_GSIZE_FORMAT
2723 " bytes, but the maximum icon size for %s is %" G_GSIZE_FORMAT
2724 " bytes\n", path, length, plugin->info->name,
2725 prpl_info->icon_spec.max_filesize);
2726 g_free(tmp);
2727 return NULL;
2730 if (len)
2731 *len = length;
2732 return contents;
2733 #else
2735 * The chosen icon wasn't the right size, and we're using
2736 * GTK+ 2.0 so we can't scale it.
2738 return NULL;
2739 #endif
2742 #if !GTK_CHECK_VERSION(2,6,0)
2743 static void
2744 _gdk_file_scale_size_prepared_cb (GdkPixbufLoader *loader,
2745 int width,
2746 int height,
2747 gpointer data)
2749 struct {
2750 gint width;
2751 gint height;
2752 gboolean preserve_aspect_ratio;
2753 } *info = data;
2755 g_return_if_fail (width > 0 && height > 0);
2757 if (info->preserve_aspect_ratio &&
2758 (info->width > 0 || info->height > 0)) {
2759 if (info->width < 0)
2761 width = width * (double)info->height/(double)height;
2762 height = info->height;
2764 else if (info->height < 0)
2766 height = height * (double)info->width/(double)width;
2767 width = info->width;
2769 else if ((double)height * (double)info->width >
2770 (double)width * (double)info->height) {
2771 width = 0.5 + (double)width * (double)info->height / (double)height;
2772 height = info->height;
2773 } else {
2774 height = 0.5 + (double)height * (double)info->width / (double)width;
2775 width = info->width;
2777 } else {
2778 if (info->width > 0)
2779 width = info->width;
2780 if (info->height > 0)
2781 height = info->height;
2784 #if GTK_CHECK_VERSION(2,2,0) /* 2.0 users are going to have very strangely sized things */
2785 gdk_pixbuf_loader_set_size (loader, width, height);
2786 #else
2787 #warning nosnilmot could not be bothered to fix this properly for you
2788 #warning ... good luck ... your images may end up strange sizes
2789 #endif
2792 GdkPixbuf *
2793 gdk_pixbuf_new_from_file_at_scale(const char *filename, int width, int height,
2794 gboolean preserve_aspect_ratio,
2795 GError **error)
2797 GdkPixbufLoader *loader;
2798 GdkPixbuf *pixbuf;
2799 guchar buffer [4096];
2800 int length;
2801 FILE *f;
2802 struct {
2803 gint width;
2804 gint height;
2805 gboolean preserve_aspect_ratio;
2806 } info;
2807 GdkPixbufAnimation *animation;
2808 GdkPixbufAnimationIter *iter;
2809 gboolean has_frame;
2811 g_return_val_if_fail (filename != NULL, NULL);
2812 g_return_val_if_fail (width > 0 || width == -1, NULL);
2813 g_return_val_if_fail (height > 0 || height == -1, NULL);
2815 f = g_fopen (filename, "rb");
2816 if (!f) {
2817 gint save_errno = errno;
2818 gchar *display_name = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL);
2819 g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (save_errno),
2820 _("Failed to open file '%s': %s"),
2821 display_name ? display_name : "(unknown)",
2822 g_strerror (save_errno));
2823 g_free (display_name);
2824 return NULL;
2827 loader = gdk_pixbuf_loader_new ();
2829 info.width = width;
2830 info.height = height;
2831 info.preserve_aspect_ratio = preserve_aspect_ratio;
2833 g_signal_connect (loader, "size-prepared", G_CALLBACK (_gdk_file_scale_size_prepared_cb), &info);
2835 has_frame = FALSE;
2836 while (!has_frame && !feof (f) && !ferror (f)) {
2837 length = fread (buffer, 1, sizeof (buffer), f);
2838 if (length > 0)
2839 if (!gdk_pixbuf_loader_write (loader, buffer, length, error)) {
2840 gdk_pixbuf_loader_close (loader, NULL);
2841 fclose (f);
2842 g_object_unref (loader);
2843 return NULL;
2846 animation = gdk_pixbuf_loader_get_animation (loader);
2847 if (animation) {
2848 iter = gdk_pixbuf_animation_get_iter (animation, 0);
2849 if (!gdk_pixbuf_animation_iter_on_currently_loading_frame (iter)) {
2850 has_frame = TRUE;
2852 g_object_unref (iter);
2856 fclose (f);
2858 if (!gdk_pixbuf_loader_close (loader, error) && !has_frame) {
2859 g_object_unref (loader);
2860 return NULL;
2863 pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
2865 if (!pixbuf) {
2866 gchar *display_name = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL);
2867 g_object_unref (loader);
2868 g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_FAILED,
2869 _("Failed to load image '%s': reason not known, probably a corrupt image file"),
2870 display_name ? display_name : "(unknown)");
2871 g_free (display_name);
2872 return NULL;
2875 g_object_ref (pixbuf);
2877 g_object_unref (loader);
2879 return pixbuf;
2881 #endif /* ! Gtk 2.6.0 */
2883 void pidgin_set_custom_buddy_icon(PurpleAccount *account, const char *who, const char *filename)
2885 PurpleBuddy *buddy;
2886 PurpleContact *contact;
2888 buddy = purple_find_buddy(account, who);
2889 if (!buddy) {
2890 purple_debug_info("custom-icon", "You can only set custom icon for someone in your buddylist.\n");
2891 return;
2894 contact = purple_buddy_get_contact(buddy);
2895 purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode*)contact, filename);
2898 char *pidgin_make_pretty_arrows(const char *str)
2900 char *ret;
2901 char **split = g_strsplit(str, "->", -1);
2902 ret = g_strjoinv("\342\207\250", split);
2903 g_strfreev(split);
2905 split = g_strsplit(ret, "<-", -1);
2906 g_free(ret);
2907 ret = g_strjoinv("\342\207\246", split);
2908 g_strfreev(split);
2910 return ret;
2913 void pidgin_set_urgent(GtkWindow *window, gboolean urgent)
2915 #if GTK_CHECK_VERSION(2,8,0)
2916 gtk_window_set_urgency_hint(window, urgent);
2917 #elif defined _WIN32
2918 winpidgin_window_flash(window, urgent);
2919 #elif defined GDK_WINDOWING_X11
2920 GdkWindow *gdkwin;
2921 XWMHints *hints;
2923 g_return_if_fail(window != NULL);
2925 gdkwin = GTK_WIDGET(window)->window;
2927 g_return_if_fail(gdkwin != NULL);
2929 hints = XGetWMHints(GDK_WINDOW_XDISPLAY(gdkwin),
2930 GDK_WINDOW_XWINDOW(gdkwin));
2931 if(!hints)
2932 hints = XAllocWMHints();
2934 if (urgent)
2935 hints->flags |= XUrgencyHint;
2936 else
2937 hints->flags &= ~XUrgencyHint;
2938 XSetWMHints(GDK_WINDOW_XDISPLAY(gdkwin),
2939 GDK_WINDOW_XWINDOW(gdkwin), hints);
2940 XFree(hints);
2941 #else
2942 /* do something else? */
2943 #endif
2946 GSList *minidialogs = NULL;
2948 static void *
2949 pidgin_utils_get_handle(void)
2951 static int handle;
2953 return &handle;
2956 static void connection_signed_off_cb(PurpleConnection *gc)
2958 GSList *list, *l_next;
2959 for (list = minidialogs; list; list = l_next) {
2960 l_next = list->next;
2961 if (g_object_get_data(G_OBJECT(list->data), "gc") == gc) {
2962 gtk_widget_destroy(GTK_WIDGET(list->data));
2967 static void alert_killed_cb(GtkWidget *widget)
2969 minidialogs = g_slist_remove(minidialogs, widget);
2972 struct _old_button_clicked_cb_data
2974 PidginUtilMiniDialogCallback cb;
2975 gpointer data;
2978 static void
2979 old_mini_dialog_button_clicked_cb(PidginMiniDialog *mini_dialog,
2980 GtkButton *button,
2981 gpointer user_data)
2983 struct _old_button_clicked_cb_data *data = user_data;
2984 data->cb(data->data, button);
2987 static void
2988 old_mini_dialog_destroy_cb(GtkWidget *dialog,
2989 GList *cb_datas)
2991 while (cb_datas != NULL)
2993 g_free(cb_datas->data);
2994 cb_datas = g_list_delete_link(cb_datas, cb_datas);
2998 GtkWidget *
2999 pidgin_make_mini_dialog(PurpleConnection *gc,
3000 const char *icon_name,
3001 const char *primary,
3002 const char *secondary,
3003 void *user_data,
3004 ...)
3006 PidginMiniDialog *mini_dialog;
3007 const char *button_text;
3008 GList *cb_datas = NULL;
3009 va_list args;
3010 static gboolean first_call = TRUE;
3012 if (first_call) {
3013 first_call = FALSE;
3014 purple_signal_connect(purple_connections_get_handle(), "signed-off",
3015 pidgin_utils_get_handle(),
3016 PURPLE_CALLBACK(connection_signed_off_cb), NULL);
3019 mini_dialog = pidgin_mini_dialog_new(primary, secondary, icon_name);
3020 g_object_set_data(G_OBJECT(mini_dialog), "gc" ,gc);
3021 g_signal_connect(G_OBJECT(mini_dialog), "destroy",
3022 G_CALLBACK(alert_killed_cb), NULL);
3024 va_start(args, user_data);
3025 while ((button_text = va_arg(args, char*))) {
3026 struct _old_button_clicked_cb_data *data = NULL;
3027 PidginMiniDialogCallback wrapper_cb = NULL;
3028 PidginUtilMiniDialogCallback callback =
3029 va_arg(args, PidginUtilMiniDialogCallback);
3031 if (callback != NULL) {
3032 data = g_new0(struct _old_button_clicked_cb_data, 1);
3033 data->cb = callback;
3034 data->data = user_data;
3035 wrapper_cb = old_mini_dialog_button_clicked_cb;
3037 pidgin_mini_dialog_add_button(mini_dialog, button_text,
3038 wrapper_cb, data);
3039 cb_datas = g_list_append(cb_datas, data);
3041 va_end(args);
3043 g_signal_connect(G_OBJECT(mini_dialog), "destroy",
3044 G_CALLBACK(old_mini_dialog_destroy_cb), cb_datas);
3046 return GTK_WIDGET(mini_dialog);
3050 * "This is so dead sexy."
3051 * "Two thumbs up."
3052 * "Best movie of the year."
3054 * This is the function that handles CTRL+F searching in the buddy list.
3055 * It finds the top-most buddy/group/chat/whatever containing the
3056 * entered string.
3058 * It's somewhat ineffecient, because we strip all the HTML from the
3059 * "name" column of the buddy list (because the GtkTreeModel does not
3060 * contain the screen name in a non-markedup format). But the alternative
3061 * is to add an extra column to the GtkTreeModel. And this function is
3062 * used rarely, so it shouldn't matter TOO much.
3064 gboolean pidgin_tree_view_search_equal_func(GtkTreeModel *model, gint column,
3065 const gchar *key, GtkTreeIter *iter, gpointer data)
3067 gchar *enteredstring;
3068 gchar *tmp;
3069 gchar *withmarkup;
3070 gchar *nomarkup;
3071 gchar *normalized;
3072 gboolean result;
3073 size_t i;
3074 size_t len;
3075 PangoLogAttr *log_attrs;
3076 gchar *word;
3078 if (g_ascii_strcasecmp(key, "Global Thermonuclear War") == 0)
3080 purple_notify_info(NULL, "WOPR",
3081 "Wouldn't you prefer a nice game of chess?", NULL);
3082 return FALSE;
3085 gtk_tree_model_get(model, iter, column, &withmarkup, -1);
3086 if (withmarkup == NULL) /* This is probably a separator */
3087 return TRUE;
3089 tmp = g_utf8_normalize(key, -1, G_NORMALIZE_DEFAULT);
3090 enteredstring = g_utf8_casefold(tmp, -1);
3091 g_free(tmp);
3093 nomarkup = purple_markup_strip_html(withmarkup);
3094 tmp = g_utf8_normalize(nomarkup, -1, G_NORMALIZE_DEFAULT);
3095 g_free(nomarkup);
3096 normalized = g_utf8_casefold(tmp, -1);
3097 g_free(tmp);
3099 if (purple_str_has_prefix(normalized, enteredstring))
3101 g_free(withmarkup);
3102 g_free(enteredstring);
3103 g_free(normalized);
3104 return FALSE;
3108 /* Use Pango to separate by words. */
3109 len = g_utf8_strlen(normalized, -1);
3110 log_attrs = g_new(PangoLogAttr, len + 1);
3112 pango_get_log_attrs(normalized, strlen(normalized), -1, NULL, log_attrs, len + 1);
3114 word = normalized;
3115 result = TRUE;
3116 for (i = 0; i < (len - 1) ; i++)
3118 if (log_attrs[i].is_word_start &&
3119 purple_str_has_prefix(word, enteredstring))
3121 result = FALSE;
3122 break;
3124 word = g_utf8_next_char(word);
3126 g_free(log_attrs);
3128 /* The non-Pango version. */
3129 #if 0
3130 word = normalized;
3131 result = TRUE;
3132 while (word[0] != '\0')
3134 gunichar c = g_utf8_get_char(word);
3135 if (!g_unichar_isalnum(c))
3137 word = g_utf8_find_next_char(word, NULL);
3138 if (purple_str_has_prefix(word, enteredstring))
3140 result = FALSE;
3141 break;
3144 else
3145 word = g_utf8_find_next_char(word, NULL);
3147 #endif
3149 g_free(withmarkup);
3150 g_free(enteredstring);
3151 g_free(normalized);
3153 return result;
3157 gboolean pidgin_gdk_pixbuf_is_opaque(GdkPixbuf *pixbuf) {
3158 int width, height, rowstride, i;
3159 unsigned char *pixels;
3160 unsigned char *row;
3162 if (!gdk_pixbuf_get_has_alpha(pixbuf))
3163 return TRUE;
3165 width = gdk_pixbuf_get_width (pixbuf);
3166 height = gdk_pixbuf_get_height (pixbuf);
3167 rowstride = gdk_pixbuf_get_rowstride (pixbuf);
3168 pixels = gdk_pixbuf_get_pixels (pixbuf);
3170 row = pixels;
3171 for (i = 3; i < rowstride; i+=4) {
3172 if (row[i] < 0xfe)
3173 return FALSE;
3176 for (i = 1; i < height - 1; i++) {
3177 row = pixels + (i*rowstride);
3178 if (row[3] < 0xfe || row[rowstride-1] < 0xfe) {
3179 return FALSE;
3183 row = pixels + ((height-1) * rowstride);
3184 for (i = 3; i < rowstride; i+=4) {
3185 if (row[i] < 0xfe)
3186 return FALSE;
3189 return TRUE;
3192 void pidgin_gdk_pixbuf_make_round(GdkPixbuf *pixbuf) {
3193 int width, height, rowstride;
3194 guchar *pixels;
3195 if (!gdk_pixbuf_get_has_alpha(pixbuf))
3196 return;
3197 width = gdk_pixbuf_get_width(pixbuf);
3198 height = gdk_pixbuf_get_height(pixbuf);
3199 rowstride = gdk_pixbuf_get_rowstride(pixbuf);
3200 pixels = gdk_pixbuf_get_pixels(pixbuf);
3202 if (width < 6 || height < 6)
3203 return;
3204 /* Top left */
3205 pixels[3] = 0;
3206 pixels[7] = 0x80;
3207 pixels[11] = 0xC0;
3208 pixels[rowstride + 3] = 0x80;
3209 pixels[rowstride * 2 + 3] = 0xC0;
3211 /* Top right */
3212 pixels[width * 4 - 1] = 0;
3213 pixels[width * 4 - 5] = 0x80;
3214 pixels[width * 4 - 9] = 0xC0;
3215 pixels[rowstride + (width * 4) - 1] = 0x80;
3216 pixels[(2 * rowstride) + (width * 4) - 1] = 0xC0;
3218 /* Bottom left */
3219 pixels[(height - 1) * rowstride + 3] = 0;
3220 pixels[(height - 1) * rowstride + 7] = 0x80;
3221 pixels[(height - 1) * rowstride + 11] = 0xC0;
3222 pixels[(height - 2) * rowstride + 3] = 0x80;
3223 pixels[(height - 3) * rowstride + 3] = 0xC0;
3225 /* Bottom right */
3226 pixels[height * rowstride - 1] = 0;
3227 pixels[(height - 1) * rowstride - 1] = 0x80;
3228 pixels[(height - 2) * rowstride - 1] = 0xC0;
3229 pixels[height * rowstride - 5] = 0x80;
3230 pixels[height * rowstride - 9] = 0xC0;
3233 const char *pidgin_get_dim_grey_string(GtkWidget *widget) {
3234 static char dim_grey_string[8] = "";
3235 GtkStyle *style;
3237 if (!widget)
3238 return "dim grey";
3240 style = gtk_widget_get_style(widget);
3241 if (!style)
3242 return "dim grey";
3244 snprintf(dim_grey_string, sizeof(dim_grey_string), "#%02x%02x%02x",
3245 style->text_aa[GTK_STATE_NORMAL].red >> 8,
3246 style->text_aa[GTK_STATE_NORMAL].green >> 8,
3247 style->text_aa[GTK_STATE_NORMAL].blue >> 8);
3248 return dim_grey_string;
3251 #if !GTK_CHECK_VERSION(2,2,0)
3252 GtkTreePath *
3253 gtk_tree_path_new_from_indices (gint first_index, ...)
3255 int arg;
3256 va_list args;
3257 GtkTreePath *path;
3259 path = gtk_tree_path_new ();
3261 va_start (args, first_index);
3262 arg = first_index;
3264 while (arg != -1)
3266 gtk_tree_path_append_index (path, arg);
3267 arg = va_arg (args, gint);
3270 va_end (args);
3272 return path;
3274 #endif
3276 static void
3277 combo_box_changed_cb(GtkComboBox *combo_box, GtkEntry *entry)
3279 char *text = gtk_combo_box_get_active_text(combo_box);
3280 gtk_entry_set_text(entry, text ? text : "");
3281 g_free(text);
3284 static gboolean
3285 entry_key_pressed_cb(GtkWidget *entry, GdkEventKey *key, GtkComboBox *combo)
3287 if (key->keyval == GDK_Down || key->keyval == GDK_Up) {
3288 gtk_combo_box_popup(combo);
3289 return TRUE;
3291 return FALSE;
3294 GtkWidget *
3295 pidgin_text_combo_box_entry_new(const char *default_item, GList *items)
3297 GtkComboBox *ret = NULL;
3298 GtkWidget *the_entry = NULL;
3300 ret = GTK_COMBO_BOX(gtk_combo_box_new_text());
3301 the_entry = gtk_entry_new();
3302 gtk_container_add(GTK_CONTAINER(ret), the_entry);
3304 if (default_item)
3305 gtk_entry_set_text(GTK_ENTRY(the_entry), default_item);
3307 for (; items != NULL ; items = items->next) {
3308 char *text = items->data;
3309 if (text && *text)
3310 gtk_combo_box_append_text(ret, text);
3313 g_signal_connect(G_OBJECT(ret), "changed", (GCallback)combo_box_changed_cb, the_entry);
3314 g_signal_connect_after(G_OBJECT(the_entry), "key-press-event", G_CALLBACK(entry_key_pressed_cb), ret);
3316 return GTK_WIDGET(ret);
3319 const char *pidgin_text_combo_box_entry_get_text(GtkWidget *widget)
3321 return gtk_entry_get_text(GTK_ENTRY(GTK_BIN((widget))->child));
3324 void pidgin_text_combo_box_entry_set_text(GtkWidget *widget, const char *text)
3326 gtk_entry_set_text(GTK_ENTRY(GTK_BIN((widget))->child), (text));
3329 GtkWidget *
3330 pidgin_add_widget_to_vbox(GtkBox *vbox, const char *widget_label, GtkSizeGroup *sg, GtkWidget *widget, gboolean expand, GtkWidget **p_label)
3332 GtkWidget *hbox;
3333 GtkWidget *label = NULL;
3335 if (widget_label) {
3336 hbox = gtk_hbox_new(FALSE, 5);
3337 gtk_widget_show(hbox);
3338 gtk_box_pack_start(vbox, hbox, FALSE, FALSE, 0);
3340 label = gtk_label_new_with_mnemonic(widget_label);
3341 gtk_widget_show(label);
3342 if (sg) {
3343 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
3344 gtk_size_group_add_widget(sg, label);
3346 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
3347 } else {
3348 hbox = GTK_WIDGET(vbox);
3351 gtk_widget_show(widget);
3352 gtk_box_pack_start(GTK_BOX(hbox), widget, expand, TRUE, 0);
3353 if (label) {
3354 gtk_label_set_mnemonic_widget(GTK_LABEL(label), widget);
3355 pidgin_set_accessible_label (widget, label);
3358 if (p_label)
3359 (*p_label) = label;
3360 return hbox;
3363 gboolean pidgin_auto_parent_window(GtkWidget *widget)
3365 #if 0
3366 /* This looks at the most recent window that received focus, and makes
3367 * that the parent window. */
3368 #ifndef _WIN32
3369 static GdkAtom _WindowTime = GDK_NONE;
3370 static GdkAtom _Cardinal = GDK_NONE;
3371 GList *windows = NULL;
3372 GtkWidget *parent = NULL;
3373 time_t window_time = 0;
3375 windows = gtk_window_list_toplevels();
3377 if (_WindowTime == GDK_NONE) {
3378 _WindowTime = gdk_x11_xatom_to_atom(gdk_x11_get_xatom_by_name("_NET_WM_USER_TIME"));
3380 if (_Cardinal == GDK_NONE) {
3381 _Cardinal = gdk_atom_intern("CARDINAL", FALSE);
3384 while (windows) {
3385 GtkWidget *window = windows->data;
3386 guchar *data = NULL;
3387 int al = 0;
3388 time_t value;
3390 windows = g_list_delete_link(windows, windows);
3392 if (window == widget ||
3393 !GTK_WIDGET_VISIBLE(window))
3394 continue;
3396 if (!gdk_property_get(window->window, _WindowTime, _Cardinal, 0, sizeof(time_t), FALSE,
3397 NULL, NULL, &al, &data))
3398 continue;
3399 value = *(time_t *)data;
3400 if (window_time < value) {
3401 window_time = value;
3402 parent = window;
3404 g_free(data);
3406 if (windows)
3407 g_list_free(windows);
3408 if (parent) {
3409 if (!gtk_get_current_event() && gtk_window_has_toplevel_focus(GTK_WINDOW(parent))) {
3410 /* The window is in focus, and the new window was not triggered by a keypress/click
3411 * event. So do not set it transient, to avoid focus stealing and all that.
3413 return FALSE;
3415 gtk_window_set_transient_for(GTK_WINDOW(widget), GTK_WINDOW(parent));
3416 return TRUE;
3418 return FALSE;
3419 #endif
3420 #else
3421 #if GTK_CHECK_VERSION(2,4,0)
3422 /* This finds the currently active window and makes that the parent window. */
3423 GList *windows = NULL;
3424 GtkWidget *parent = NULL;
3425 GdkEvent *event = gtk_get_current_event();
3426 GdkWindow *menu = NULL;
3428 if (event == NULL)
3429 /* The window was not triggered by a user action. */
3430 return FALSE;
3432 /* We need to special case events from a popup menu. */
3433 if (event->type == GDK_BUTTON_RELEASE) {
3434 /* XXX: Neither of the following works:
3435 menu = event->button.window;
3436 menu = gdk_window_get_parent(event->button.window);
3437 menu = gdk_window_get_toplevel(event->button.window);
3439 } else if (event->type == GDK_KEY_PRESS)
3440 menu = event->key.window;
3442 windows = gtk_window_list_toplevels();
3443 while (windows) {
3444 GtkWidget *window = windows->data;
3445 windows = g_list_delete_link(windows, windows);
3447 if (window == widget ||
3448 !GTK_WIDGET_VISIBLE(window)) {
3449 continue;
3452 if (gtk_window_has_toplevel_focus(GTK_WINDOW(window)) ||
3453 (menu && menu == window->window)) {
3454 parent = window;
3455 break;
3458 if (windows)
3459 g_list_free(windows);
3460 if (parent) {
3461 gtk_window_set_transient_for(GTK_WINDOW(widget), GTK_WINDOW(parent));
3462 return TRUE;
3464 #endif
3465 return FALSE;
3466 #endif
3469 GdkPixbuf * pidgin_pixbuf_from_imgstore(PurpleStoredImage *image)
3471 GdkPixbuf *pixbuf;
3472 GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
3473 gdk_pixbuf_loader_write(loader, purple_imgstore_get_data(image),
3474 purple_imgstore_get_size(image), NULL);
3475 gdk_pixbuf_loader_close(loader, NULL);
3476 pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
3477 if (pixbuf)
3478 g_object_ref(pixbuf);
3479 g_object_unref(loader);
3480 return pixbuf;