2 * @file gtkutils.c GTK+ utility functions
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_
32 # include <X11/Xlib.h>
40 # include <gtkspell/gtkspell.h>
46 #include <gdk/gdkkeysyms.h>
48 #include "conversation.h"
50 #include "desktopitem.h"
60 #include "gtkaccount.h"
64 #include "gtkdialogs.h"
65 #include "gtkimhtml.h"
66 #include "gtkimhtmltoolbar.h"
67 #include "pidginstock.h"
68 #include "gtkthemes.h"
70 #include "pidgin/minidialog.h"
77 static guint accels_save_timer
= 0;
78 static GSList
*registered_url_handlers
= NULL
;
81 url_clicked_idle_cb(gpointer data
)
83 purple_notify_uri(NULL
, data
);
89 url_clicked_cb(GtkIMHtml
*unused
, GtkIMHtmlLink
*link
)
91 const char *uri
= gtk_imhtml_link_get_url(link
);
92 g_idle_add(url_clicked_idle_cb
, g_strdup(uri
));
96 static GtkIMHtmlFuncs gtkimhtml_cbs
= {
97 (GtkIMHtmlGetImageFunc
)purple_imgstore_find_by_id
,
98 (GtkIMHtmlGetImageDataFunc
)purple_imgstore_get_data
,
99 (GtkIMHtmlGetImageSizeFunc
)purple_imgstore_get_size
,
100 (GtkIMHtmlGetImageFilenameFunc
)purple_imgstore_get_filename
,
101 purple_imgstore_ref_by_id
,
102 purple_imgstore_unref_by_id
,
106 pidgin_setup_imhtml(GtkWidget
*imhtml
)
108 g_return_if_fail(imhtml
!= NULL
);
109 g_return_if_fail(GTK_IS_IMHTML(imhtml
));
111 pidgin_themes_smiley_themeize(imhtml
);
113 gtk_imhtml_set_funcs(GTK_IMHTML(imhtml
), >kimhtml_cbs
);
116 if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/use_theme_font")) {
117 PangoFontDescription
*desc
;
118 const char *font
= purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/custom_font");
119 desc
= pango_font_description_from_string(font
);
121 gtk_widget_modify_font(imhtml
, desc
);
122 pango_font_description_free(desc
);
130 void pidgin_window_init(GtkWindow
*wnd
, const char *title
, guint border_width
, const char *role
, gboolean resizable
)
133 gtk_window_set_title(wnd
, title
);
136 gtk_window_set_title(wnd
, PIDGIN_ALERT_TITLE
);
138 gtk_container_set_border_width(GTK_CONTAINER(wnd
), border_width
);
140 gtk_window_set_role(wnd
, role
);
141 gtk_window_set_resizable(wnd
, resizable
);
145 pidgin_create_window(const char *title
, guint border_width
, const char *role
, gboolean resizable
)
147 GtkWindow
*wnd
= NULL
;
149 wnd
= GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL
));
150 pidgin_window_init(wnd
, title
, border_width
, role
, resizable
);
152 return GTK_WIDGET(wnd
);
156 pidgin_create_small_button(GtkWidget
*image
)
160 button
= gtk_button_new();
161 gtk_button_set_relief(GTK_BUTTON(button
), GTK_RELIEF_NONE
);
163 /* don't allow focus on the close button */
164 gtk_button_set_focus_on_click(GTK_BUTTON(button
), FALSE
);
166 /* set style to make it as small as possible */
167 gtk_widget_set_name(button
, "pidgin-small-close-button");
169 gtk_widget_show(image
);
171 gtk_container_add(GTK_CONTAINER(button
), image
);
177 pidgin_create_dialog(const char *title
, guint border_width
, const char *role
, gboolean resizable
)
179 GtkWindow
*wnd
= NULL
;
181 wnd
= GTK_WINDOW(gtk_dialog_new());
182 pidgin_window_init(wnd
, title
, border_width
, role
, resizable
);
183 g_object_set(G_OBJECT(wnd
), "has-separator", FALSE
, NULL
);
185 return GTK_WIDGET(wnd
);
189 pidgin_dialog_get_vbox_with_properties(GtkDialog
*dialog
, gboolean homogeneous
, gint spacing
)
191 GtkBox
*vbox
= GTK_BOX(GTK_DIALOG(dialog
)->vbox
);
192 gtk_box_set_homogeneous(vbox
, homogeneous
);
193 gtk_box_set_spacing(vbox
, spacing
);
194 return GTK_WIDGET(vbox
);
197 GtkWidget
*pidgin_dialog_get_vbox(GtkDialog
*dialog
)
199 return GTK_DIALOG(dialog
)->vbox
;
202 GtkWidget
*pidgin_dialog_get_action_area(GtkDialog
*dialog
)
204 return GTK_DIALOG(dialog
)->action_area
;
207 GtkWidget
*pidgin_dialog_add_button(GtkDialog
*dialog
, const char *label
,
208 GCallback callback
, gpointer callbackdata
)
210 GtkWidget
*button
= gtk_button_new_from_stock(label
);
211 GtkWidget
*bbox
= pidgin_dialog_get_action_area(dialog
);
212 gtk_box_pack_start(GTK_BOX(bbox
), button
, FALSE
, FALSE
, 0);
214 g_signal_connect(G_OBJECT(button
), "clicked", callback
, callbackdata
);
215 gtk_widget_show(button
);
220 pidgin_create_imhtml(gboolean editable
, GtkWidget
**imhtml_ret
, GtkWidget
**toolbar_ret
, GtkWidget
**sw_ret
)
226 GtkWidget
*toolbar
= NULL
;
229 frame
= gtk_frame_new(NULL
);
230 gtk_frame_set_shadow_type(GTK_FRAME(frame
), GTK_SHADOW_IN
);
232 vbox
= gtk_vbox_new(FALSE
, 0);
233 gtk_container_add(GTK_CONTAINER(frame
), vbox
);
234 gtk_widget_show(vbox
);
237 toolbar
= gtk_imhtmltoolbar_new();
238 gtk_box_pack_start(GTK_BOX(vbox
), toolbar
, FALSE
, FALSE
, 0);
239 gtk_widget_show(toolbar
);
241 sep
= gtk_hseparator_new();
242 gtk_box_pack_start(GTK_BOX(vbox
), sep
, FALSE
, FALSE
, 0);
243 g_signal_connect_swapped(G_OBJECT(toolbar
), "show", G_CALLBACK(gtk_widget_show
), sep
);
244 g_signal_connect_swapped(G_OBJECT(toolbar
), "hide", G_CALLBACK(gtk_widget_hide
), sep
);
245 gtk_widget_show(sep
);
248 imhtml
= gtk_imhtml_new(NULL
, NULL
);
249 gtk_imhtml_set_editable(GTK_IMHTML(imhtml
), editable
);
250 gtk_imhtml_set_format_functions(GTK_IMHTML(imhtml
), GTK_IMHTML_ALL
^ GTK_IMHTML_IMAGE
);
251 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(imhtml
), GTK_WRAP_WORD_CHAR
);
253 if (editable
&& purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/spellcheck"))
254 pidgin_setup_gtkspell(GTK_TEXT_VIEW(imhtml
));
256 gtk_widget_show(imhtml
);
259 gtk_imhtmltoolbar_attach(GTK_IMHTMLTOOLBAR(toolbar
), imhtml
);
260 gtk_imhtmltoolbar_associate_smileys(GTK_IMHTMLTOOLBAR(toolbar
), "default");
262 pidgin_setup_imhtml(imhtml
);
264 sw
= pidgin_make_scrollable(imhtml
, GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
, GTK_SHADOW_NONE
, -1, -1);
265 gtk_box_pack_start(GTK_BOX(vbox
), sw
, TRUE
, TRUE
, 0);
267 if (imhtml_ret
!= NULL
)
268 *imhtml_ret
= imhtml
;
270 if (editable
&& (toolbar_ret
!= NULL
))
271 *toolbar_ret
= toolbar
;
280 pidgin_set_sensitive_if_input(GtkWidget
*entry
, GtkWidget
*dialog
)
282 const char *text
= gtk_entry_get_text(GTK_ENTRY(entry
));
283 gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog
), GTK_RESPONSE_OK
,
288 pidgin_toggle_sensitive(GtkWidget
*widget
, GtkWidget
*to_toggle
)
290 gboolean sensitivity
;
292 if (to_toggle
== NULL
)
295 sensitivity
= GTK_WIDGET_IS_SENSITIVE(to_toggle
);
297 gtk_widget_set_sensitive(to_toggle
, !sensitivity
);
301 pidgin_toggle_sensitive_array(GtkWidget
*w
, GPtrArray
*data
)
303 gboolean sensitivity
;
307 for (i
=0; i
< data
->len
; i
++) {
308 element
= g_ptr_array_index(data
,i
);
312 sensitivity
= GTK_WIDGET_IS_SENSITIVE(element
);
314 gtk_widget_set_sensitive(element
, !sensitivity
);
319 pidgin_toggle_showhide(GtkWidget
*widget
, GtkWidget
*to_toggle
)
321 if (to_toggle
== NULL
)
324 if (GTK_WIDGET_VISIBLE(to_toggle
))
325 gtk_widget_hide(to_toggle
);
327 gtk_widget_show(to_toggle
);
330 GtkWidget
*pidgin_separator(GtkWidget
*menu
)
334 menuitem
= gtk_separator_menu_item_new();
335 gtk_widget_show(menuitem
);
336 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
340 GtkWidget
*pidgin_new_item(GtkWidget
*menu
, const char *str
)
345 menuitem
= gtk_menu_item_new();
347 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
348 gtk_widget_show(menuitem
);
350 label
= gtk_label_new(str
);
351 gtk_misc_set_alignment(GTK_MISC(label
), 0, 0.5);
352 gtk_label_set_pattern(GTK_LABEL(label
), "_");
353 gtk_container_add(GTK_CONTAINER(menuitem
), label
);
354 gtk_widget_show(label
);
355 /* FIXME: Go back and fix this
356 gtk_widget_add_accelerator(menuitem, "activate", accel, str[0],
357 GDK_MOD1_MASK, GTK_ACCEL_LOCKED);
359 pidgin_set_accessible_label (menuitem
, label
);
363 GtkWidget
*pidgin_new_check_item(GtkWidget
*menu
, const char *str
,
364 GCallback cb
, gpointer data
, gboolean checked
)
367 menuitem
= gtk_check_menu_item_new_with_mnemonic(str
);
370 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
372 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem
), checked
);
375 g_signal_connect(G_OBJECT(menuitem
), "activate", cb
, data
);
377 gtk_widget_show_all(menuitem
);
383 pidgin_pixbuf_toolbar_button_from_stock(const char *icon
)
385 GtkWidget
*button
, *image
, *bbox
;
387 button
= gtk_toggle_button_new();
388 gtk_button_set_relief(GTK_BUTTON(button
), GTK_RELIEF_NONE
);
390 bbox
= gtk_vbox_new(FALSE
, 0);
392 gtk_container_add (GTK_CONTAINER(button
), bbox
);
394 image
= gtk_image_new_from_stock(icon
, gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
));
395 gtk_box_pack_start(GTK_BOX(bbox
), image
, FALSE
, FALSE
, 0);
397 gtk_widget_show_all(bbox
);
403 pidgin_pixbuf_button_from_stock(const char *text
, const char *icon
,
404 PidginButtonOrientation style
)
406 GtkWidget
*button
, *image
, *label
, *bbox
, *ibox
, *lbox
= NULL
;
408 button
= gtk_button_new();
410 if (style
== PIDGIN_BUTTON_HORIZONTAL
) {
411 bbox
= gtk_hbox_new(FALSE
, 0);
412 ibox
= gtk_hbox_new(FALSE
, 0);
414 lbox
= gtk_hbox_new(FALSE
, 0);
416 bbox
= gtk_vbox_new(FALSE
, 0);
417 ibox
= gtk_vbox_new(FALSE
, 0);
419 lbox
= gtk_vbox_new(FALSE
, 0);
422 gtk_container_add(GTK_CONTAINER(button
), bbox
);
425 gtk_box_pack_start(GTK_BOX(bbox
), ibox
, TRUE
, TRUE
, 0);
426 image
= gtk_image_new_from_stock(icon
, GTK_ICON_SIZE_BUTTON
);
427 gtk_box_pack_end(GTK_BOX(ibox
), image
, FALSE
, TRUE
, 0);
431 gtk_box_pack_start(GTK_BOX(bbox
), lbox
, TRUE
, TRUE
, 0);
432 label
= gtk_label_new(NULL
);
433 gtk_label_set_text_with_mnemonic(GTK_LABEL(label
), text
);
434 gtk_label_set_mnemonic_widget(GTK_LABEL(label
), button
);
435 gtk_box_pack_start(GTK_BOX(lbox
), label
, FALSE
, TRUE
, 0);
436 pidgin_set_accessible_label (button
, label
);
439 gtk_widget_show_all(bbox
);
445 GtkWidget
*pidgin_new_item_from_stock(GtkWidget
*menu
, const char *str
, const char *icon
, GCallback cb
, gpointer data
, guint accel_key
, guint accel_mods
, char *mod
)
455 menuitem
= gtk_menu_item_new_with_mnemonic(str
);
457 menuitem
= gtk_image_menu_item_new_with_mnemonic(str
);
460 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
463 g_signal_connect(G_OBJECT(menuitem
), "activate", cb
, data
);
466 image
= gtk_image_new_from_stock(icon
, gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
));
467 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem
), image
);
469 /* FIXME: this isn't right
471 label = gtk_label_new(mod);
472 gtk_box_pack_end(GTK_BOX(hbox), label, FALSE, FALSE, 2);
473 gtk_widget_show(label);
478 gtk_widget_add_accelerator(menuitem, "activate", accel, accel_key,
479 accel_mods, GTK_ACCEL_LOCKED);
483 gtk_widget_show_all(menuitem
);
489 pidgin_make_frame(GtkWidget
*parent
, const char *title
)
491 GtkWidget
*vbox
, *label
, *hbox
;
494 vbox
= gtk_vbox_new(FALSE
, PIDGIN_HIG_BOX_SPACE
);
495 gtk_box_pack_start(GTK_BOX(parent
), vbox
, FALSE
, FALSE
, 0);
496 gtk_widget_show(vbox
);
498 label
= gtk_label_new(NULL
);
500 labeltitle
= g_strdup_printf("<span weight=\"bold\">%s</span>", title
);
501 gtk_label_set_markup(GTK_LABEL(label
), labeltitle
);
504 gtk_misc_set_alignment(GTK_MISC(label
), 0, 0);
505 gtk_box_pack_start(GTK_BOX(vbox
), label
, FALSE
, FALSE
, 0);
506 gtk_widget_show(label
);
507 pidgin_set_accessible_label (vbox
, label
);
509 hbox
= gtk_hbox_new(FALSE
, PIDGIN_HIG_BOX_SPACE
);
510 gtk_box_pack_start (GTK_BOX (vbox
), hbox
, FALSE
, FALSE
, 0);
511 gtk_widget_show(hbox
);
513 label
= gtk_label_new(" ");
514 gtk_box_pack_start(GTK_BOX(hbox
), label
, FALSE
, FALSE
, 0);
515 gtk_widget_show(label
);
517 vbox
= gtk_vbox_new(FALSE
, PIDGIN_HIG_BOX_SPACE
);
518 gtk_box_pack_start(GTK_BOX(hbox
), vbox
, FALSE
, FALSE
, 0);
519 gtk_widget_show(vbox
);
525 aop_option_menu_get_selected(GtkWidget
*optmenu
, GtkWidget
**p_item
)
527 GtkWidget
*menu
= gtk_option_menu_get_menu(GTK_OPTION_MENU(optmenu
));
528 GtkWidget
*item
= gtk_menu_get_active(GTK_MENU(menu
));
531 return item
? g_object_get_data(G_OBJECT(item
), "aop_per_item_data") : NULL
;
535 aop_menu_cb(GtkWidget
*optmenu
, GCallback cb
)
538 gpointer per_item_data
;
540 per_item_data
= aop_option_menu_get_selected(optmenu
, &item
);
543 ((void (*)(GtkWidget
*, gpointer
, gpointer
))cb
)(item
, per_item_data
, g_object_get_data(G_OBJECT(optmenu
), "user_data"));
548 aop_menu_item_new(GtkSizeGroup
*sg
, GdkPixbuf
*pixbuf
, const char *lbl
, gpointer per_item_data
, const char *data
)
555 item
= gtk_menu_item_new();
556 gtk_widget_show(item
);
558 hbox
= gtk_hbox_new(FALSE
, 4);
559 gtk_widget_show(hbox
);
561 /* Create the image */
563 image
= gtk_image_new();
565 image
= gtk_image_new_from_pixbuf(pixbuf
);
566 gtk_widget_show(image
);
569 gtk_size_group_add_widget(sg
, image
);
571 /* Create the label */
572 label
= gtk_label_new (lbl
);
573 gtk_widget_show (label
);
574 gtk_label_set_justify(GTK_LABEL(label
), GTK_JUSTIFY_LEFT
);
575 gtk_misc_set_alignment(GTK_MISC(label
), 0.0, 0.5);
577 gtk_container_add(GTK_CONTAINER(item
), hbox
);
578 gtk_box_pack_start(GTK_BOX(hbox
), image
, FALSE
, FALSE
, 0);
579 gtk_box_pack_start(GTK_BOX(hbox
), label
, TRUE
, TRUE
, 0);
581 g_object_set_data(G_OBJECT (item
), data
, per_item_data
);
582 g_object_set_data(G_OBJECT (item
), "aop_per_item_data", per_item_data
);
584 pidgin_set_accessible_label(item
, label
);
590 pidgin_create_prpl_icon_from_prpl(PurplePlugin
*prpl
, PidginPrplIconSize size
, PurpleAccount
*account
)
592 PurplePluginProtocolInfo
*prpl_info
;
593 const char *protoname
= NULL
;
595 char *filename
= NULL
;
598 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(prpl
);
599 if (prpl_info
->list_icon
== NULL
)
602 protoname
= prpl_info
->list_icon(account
, NULL
);
603 if (protoname
== NULL
)
607 * Status icons will be themeable too, and then it will look up
608 * protoname from the theme
610 tmp
= g_strconcat(protoname
, ".png", NULL
);
612 filename
= g_build_filename(DATADIR
, "pixmaps", "pidgin", "protocols",
613 size
== PIDGIN_PRPL_ICON_SMALL
? "16" :
614 size
== PIDGIN_PRPL_ICON_MEDIUM
? "22" : "48",
618 pixbuf
= pidgin_pixbuf_new_from_file(filename
);
625 aop_option_menu_new(AopMenu
*aop_menu
, GCallback cb
, gpointer user_data
)
629 optmenu
= gtk_option_menu_new();
630 gtk_widget_show(optmenu
);
631 gtk_option_menu_set_menu(GTK_OPTION_MENU(optmenu
), aop_menu
->menu
);
633 if (aop_menu
->default_item
!= -1)
634 gtk_option_menu_set_history(GTK_OPTION_MENU(optmenu
), aop_menu
->default_item
);
636 g_object_set_data_full(G_OBJECT(optmenu
), "aop_menu", aop_menu
, (GDestroyNotify
)g_free
);
637 g_object_set_data(G_OBJECT(optmenu
), "user_data", user_data
);
639 g_signal_connect(G_OBJECT(optmenu
), "changed", G_CALLBACK(aop_menu_cb
), cb
);
645 aop_option_menu_replace_menu(GtkWidget
*optmenu
, AopMenu
*new_aop_menu
)
647 if (gtk_option_menu_get_menu(GTK_OPTION_MENU(optmenu
)))
648 gtk_option_menu_remove_menu(GTK_OPTION_MENU(optmenu
));
650 gtk_option_menu_set_menu(GTK_OPTION_MENU(optmenu
), new_aop_menu
->menu
);
652 if (new_aop_menu
->default_item
!= -1)
653 gtk_option_menu_set_history(GTK_OPTION_MENU(optmenu
), new_aop_menu
->default_item
);
655 g_object_set_data_full(G_OBJECT(optmenu
), "aop_menu", new_aop_menu
, (GDestroyNotify
)g_free
);
659 aop_option_menu_select_by_data(GtkWidget
*optmenu
, gpointer data
)
664 for (idx
= 0, llItr
= GTK_MENU_SHELL(gtk_option_menu_get_menu(GTK_OPTION_MENU(optmenu
)))->children
;
666 llItr
= llItr
->next
, idx
++) {
667 if (data
== g_object_get_data(G_OBJECT(llItr
->data
), "aop_per_item_data")) {
668 gtk_option_menu_set_history(GTK_OPTION_MENU(optmenu
), idx
);
675 create_protocols_menu(const char *default_proto_id
)
677 AopMenu
*aop_menu
= NULL
;
678 PurplePlugin
*plugin
;
679 GdkPixbuf
*pixbuf
= NULL
;
682 const char *gtalk_name
= NULL
, *facebook_name
= NULL
;
685 aop_menu
= g_malloc0(sizeof(AopMenu
));
686 aop_menu
->default_item
= -1;
687 aop_menu
->menu
= gtk_menu_new();
688 gtk_widget_show(aop_menu
->menu
);
689 sg
= gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL
);
691 if (purple_find_prpl("prpl-jabber")) {
692 gtalk_name
= _("Google Talk");
693 facebook_name
= _("Facebook (XMPP)");
696 for (p
= purple_plugins_get_protocols(), i
= 0;
700 plugin
= (PurplePlugin
*)p
->data
;
702 if (gtalk_name
&& strcmp(gtalk_name
, plugin
->info
->name
) < 0) {
703 char *filename
= g_build_filename(DATADIR
, "pixmaps", "pidgin", "protocols",
704 "16", "google-talk.png", NULL
);
707 pixbuf
= pidgin_pixbuf_new_from_file(filename
);
710 gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu
->menu
),
711 item
= aop_menu_item_new(sg
, pixbuf
, gtalk_name
, "prpl-jabber", "protocol"));
712 g_object_set_data(G_OBJECT(item
), "fakegoogle", GINT_TO_POINTER(1));
715 g_object_unref(pixbuf
);
721 if (facebook_name
&& strcmp(facebook_name
, plugin
->info
->name
) < 0) {
722 char *filename
= g_build_filename(DATADIR
, "pixmaps", "pidgin", "protocols",
723 "16", "facebook.png", NULL
);
726 pixbuf
= pidgin_pixbuf_new_from_file(filename
);
729 gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu
->menu
),
730 item
= aop_menu_item_new(sg
, pixbuf
, facebook_name
, "prpl-jabber", "protocol"));
731 g_object_set_data(G_OBJECT(item
), "fakefacebook", GINT_TO_POINTER(1));
734 g_object_unref(pixbuf
);
736 facebook_name
= NULL
;
740 pixbuf
= pidgin_create_prpl_icon_from_prpl(plugin
, PIDGIN_PRPL_ICON_SMALL
, NULL
);
742 gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu
->menu
),
743 aop_menu_item_new(sg
, pixbuf
, plugin
->info
->name
, plugin
->info
->id
, "protocol"));
746 g_object_unref(pixbuf
);
748 if (default_proto_id
!= NULL
&& !strcmp(plugin
->info
->id
, default_proto_id
))
749 aop_menu
->default_item
= i
;
758 pidgin_protocol_option_menu_new(const char *id
, GCallback cb
,
761 return aop_option_menu_new(create_protocols_menu(id
), cb
, user_data
);
765 pidgin_protocol_option_menu_get_selected(GtkWidget
*optmenu
)
767 return (const char *)aop_option_menu_get_selected(optmenu
, NULL
);
771 pidgin_account_option_menu_get_selected(GtkWidget
*optmenu
)
773 return (PurpleAccount
*)aop_option_menu_get_selected(optmenu
, NULL
);
777 create_account_menu(PurpleAccount
*default_account
,
778 PurpleFilterAccountFunc filter_func
, gboolean show_all
)
780 AopMenu
*aop_menu
= NULL
;
781 PurpleAccount
*account
;
782 GdkPixbuf
*pixbuf
= NULL
;
790 list
= purple_accounts_get_all();
792 list
= purple_connections_get_all();
794 aop_menu
= g_malloc0(sizeof(AopMenu
));
795 aop_menu
->default_item
= -1;
796 aop_menu
->menu
= gtk_menu_new();
797 gtk_widget_show(aop_menu
->menu
);
798 sg
= gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL
);
800 for (p
= list
, i
= 0; p
!= NULL
; p
= p
->next
, i
++) {
802 account
= (PurpleAccount
*)p
->data
;
804 PurpleConnection
*gc
= (PurpleConnection
*)p
->data
;
806 account
= purple_connection_get_account(gc
);
809 if (filter_func
&& !filter_func(account
)) {
814 pixbuf
= pidgin_create_prpl_icon(account
, PIDGIN_PRPL_ICON_SMALL
);
817 if (purple_account_is_disconnected(account
) && show_all
&&
818 purple_connections_get_all())
819 gdk_pixbuf_saturate_and_pixelate(pixbuf
, pixbuf
, 0.0, FALSE
);
822 if (purple_account_get_alias(account
)) {
823 g_snprintf(buf
, sizeof(buf
), "%s (%s) (%s)",
824 purple_account_get_username(account
),
825 purple_account_get_alias(account
),
826 purple_account_get_protocol_name(account
));
828 g_snprintf(buf
, sizeof(buf
), "%s (%s)",
829 purple_account_get_username(account
),
830 purple_account_get_protocol_name(account
));
833 gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu
->menu
),
834 aop_menu_item_new(sg
, pixbuf
, buf
, account
, "account"));
837 g_object_unref(pixbuf
);
839 if (default_account
&& account
== default_account
)
840 aop_menu
->default_item
= i
;
849 regenerate_account_menu(GtkWidget
*optmenu
)
852 PurpleAccount
*account
;
853 PurpleFilterAccountFunc filter_func
;
855 account
= (PurpleAccount
*)aop_option_menu_get_selected(optmenu
, NULL
);
856 show_all
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(optmenu
), "show_all"));
857 filter_func
= g_object_get_data(G_OBJECT(optmenu
), "filter_func");
859 aop_option_menu_replace_menu(optmenu
, create_account_menu(account
, filter_func
, show_all
));
863 account_menu_sign_on_off_cb(PurpleConnection
*gc
, GtkWidget
*optmenu
)
865 regenerate_account_menu(optmenu
);
869 account_menu_added_removed_cb(PurpleAccount
*account
, GtkWidget
*optmenu
)
871 regenerate_account_menu(optmenu
);
875 account_menu_destroyed_cb(GtkWidget
*optmenu
, GdkEvent
*event
,
878 purple_signals_disconnect_by_handle(optmenu
);
884 pidgin_account_option_menu_set_selected(GtkWidget
*optmenu
, PurpleAccount
*account
)
886 aop_option_menu_select_by_data(optmenu
, account
);
890 pidgin_account_option_menu_new(PurpleAccount
*default_account
,
891 gboolean show_all
, GCallback cb
,
892 PurpleFilterAccountFunc filter_func
,
897 /* Create the option menu */
898 optmenu
= aop_option_menu_new(create_account_menu(default_account
, filter_func
, show_all
), cb
, user_data
);
900 g_signal_connect(G_OBJECT(optmenu
), "destroy",
901 G_CALLBACK(account_menu_destroyed_cb
), NULL
);
903 /* Register the purple sign on/off event callbacks. */
904 purple_signal_connect(purple_connections_get_handle(), "signed-on",
905 optmenu
, PURPLE_CALLBACK(account_menu_sign_on_off_cb
),
907 purple_signal_connect(purple_connections_get_handle(), "signed-off",
908 optmenu
, PURPLE_CALLBACK(account_menu_sign_on_off_cb
),
910 purple_signal_connect(purple_accounts_get_handle(), "account-added",
911 optmenu
, PURPLE_CALLBACK(account_menu_added_removed_cb
),
913 purple_signal_connect(purple_accounts_get_handle(), "account-removed",
914 optmenu
, PURPLE_CALLBACK(account_menu_added_removed_cb
),
918 g_object_set_data(G_OBJECT(optmenu
), "user_data", user_data
);
919 g_object_set_data(G_OBJECT(optmenu
), "show_all", GINT_TO_POINTER(show_all
));
920 g_object_set_data(G_OBJECT(optmenu
), "filter_func", filter_func
);
926 pidgin_check_if_dir(const char *path
, GtkFileSelection
*filesel
)
928 char *dirname
= NULL
;
930 if (g_file_test(path
, G_FILE_TEST_IS_DIR
)) {
931 /* append a / if needed */
932 if (path
[strlen(path
) - 1] != G_DIR_SEPARATOR
) {
933 dirname
= g_strconcat(path
, G_DIR_SEPARATOR_S
, NULL
);
935 gtk_file_selection_set_filename(filesel
, (dirname
!= NULL
) ? dirname
: path
);
944 pidgin_setup_gtkspell(GtkTextView
*textview
)
947 GError
*error
= NULL
;
950 g_return_if_fail(textview
!= NULL
);
951 g_return_if_fail(GTK_IS_TEXT_VIEW(textview
));
953 if (gtkspell_new_attach(textview
, locale
, &error
) == NULL
&& error
)
955 purple_debug_warning("gtkspell", "Failed to setup GtkSpell: %s\n",
959 #endif /* USE_GTKSPELL */
963 pidgin_save_accels_cb(GtkAccelGroup
*accel_group
, guint arg1
,
964 GdkModifierType arg2
, GClosure
*arg3
,
967 purple_debug(PURPLE_DEBUG_MISC
, "accels",
968 "accel changed, scheduling save.\n");
970 if (!accels_save_timer
)
971 accels_save_timer
= purple_timeout_add_seconds(5, pidgin_save_accels
,
976 pidgin_save_accels(gpointer data
)
978 char *filename
= NULL
;
980 filename
= g_build_filename(purple_user_dir(), G_DIR_SEPARATOR_S
,
982 purple_debug(PURPLE_DEBUG_MISC
, "accels", "saving accels to %s\n", filename
);
983 gtk_accel_map_save(filename
);
986 accels_save_timer
= 0;
993 char *filename
= NULL
;
995 filename
= g_build_filename(purple_user_dir(), G_DIR_SEPARATOR_S
,
997 gtk_accel_map_load(filename
);
1002 show_retrieveing_info(PurpleConnection
*conn
, const char *name
)
1004 PurpleNotifyUserInfo
*info
= purple_notify_user_info_new();
1005 purple_notify_user_info_add_pair(info
, _("Information"), _("Retrieving..."));
1006 purple_notify_userinfo(conn
, name
, info
, NULL
, NULL
);
1007 purple_notify_user_info_destroy(info
);
1010 void pidgin_retrieve_user_info(PurpleConnection
*conn
, const char *name
)
1012 show_retrieveing_info(conn
, name
);
1013 serv_get_info(conn
, name
);
1016 void pidgin_retrieve_user_info_in_chat(PurpleConnection
*conn
, const char *name
, int chat
)
1019 PurplePluginProtocolInfo
*prpl_info
= NULL
;
1022 pidgin_retrieve_user_info(conn
, name
);
1026 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(conn
->prpl
);
1027 if (prpl_info
!= NULL
&& prpl_info
->get_cb_real_name
)
1028 who
= prpl_info
->get_cb_real_name(conn
, chat
, name
);
1029 if (prpl_info
== NULL
|| prpl_info
->get_cb_info
== NULL
) {
1030 pidgin_retrieve_user_info(conn
, who
? who
: name
);
1035 show_retrieveing_info(conn
, who
? who
: name
);
1036 prpl_info
->get_cb_info(conn
, chat
, name
);
1041 pidgin_parse_x_im_contact(const char *msg
, gboolean all_accounts
,
1042 PurpleAccount
**ret_account
, char **ret_protocol
,
1043 char **ret_username
, char **ret_alias
)
1045 char *protocol
= NULL
;
1046 char *username
= NULL
;
1052 g_return_val_if_fail(msg
!= NULL
, FALSE
);
1053 g_return_val_if_fail(ret_protocol
!= NULL
, FALSE
);
1054 g_return_val_if_fail(ret_username
!= NULL
, FALSE
);
1056 s
= str
= g_strdup(msg
);
1058 while (*s
!= '\r' && *s
!= '\n' && *s
!= '\0')
1065 while (*s
!= '\r' && *s
!= '\n' && *s
!= '\0' && *s
!= ' ')
1068 if (*s
== '\r') s
++;
1076 if (*s
!= '\0') *s
++ = '\0';
1078 /* Clear past any whitespace */
1079 while (*s
!= '\0' && *s
== ' ')
1082 /* Now let's grab until the end of the line. */
1085 while (*s
!= '\r' && *s
!= '\n' && *s
!= '\0')
1088 if (*s
== '\r') *s
++ = '\0';
1089 if (*s
== '\n') *s
++ = '\0';
1091 if (strchr(key
, ':') != NULL
)
1093 if (!g_ascii_strcasecmp(key
, "X-IM-Username:"))
1094 username
= g_strdup(value
);
1095 else if (!g_ascii_strcasecmp(key
, "X-IM-Protocol:"))
1096 protocol
= g_strdup(value
);
1097 else if (!g_ascii_strcasecmp(key
, "X-IM-Alias:"))
1098 alias
= g_strdup(value
);
1102 if (username
!= NULL
&& protocol
!= NULL
)
1106 *ret_username
= username
;
1107 *ret_protocol
= protocol
;
1109 if (ret_alias
!= NULL
)
1112 /* Check for a compatible account. */
1113 if (ret_account
!= NULL
)
1116 PurpleAccount
*account
= NULL
;
1118 const char *protoname
;
1121 list
= purple_accounts_get_all();
1123 list
= purple_connections_get_all();
1125 for (l
= list
; l
!= NULL
; l
= l
->next
)
1127 PurpleConnection
*gc
;
1128 PurplePluginProtocolInfo
*prpl_info
= NULL
;
1129 PurplePlugin
*plugin
;
1133 account
= (PurpleAccount
*)l
->data
;
1135 plugin
= purple_plugins_find_with_id(
1136 purple_account_get_protocol_id(account
));
1145 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(plugin
);
1149 gc
= (PurpleConnection
*)l
->data
;
1150 account
= purple_connection_get_account(gc
);
1152 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
);
1155 protoname
= prpl_info
->list_icon(account
, NULL
);
1157 if (!strcmp(protoname
, protocol
))
1163 /* Special case for AIM and ICQ */
1164 if (account
== NULL
&& (!strcmp(protocol
, "aim") ||
1165 !strcmp(protocol
, "icq")))
1167 for (l
= list
; l
!= NULL
; l
= l
->next
)
1169 PurpleConnection
*gc
;
1170 PurplePluginProtocolInfo
*prpl_info
= NULL
;
1171 PurplePlugin
*plugin
;
1175 account
= (PurpleAccount
*)l
->data
;
1177 plugin
= purple_plugins_find_with_id(
1178 purple_account_get_protocol_id(account
));
1187 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(plugin
);
1191 gc
= (PurpleConnection
*)l
->data
;
1192 account
= purple_connection_get_account(gc
);
1194 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
);
1197 protoname
= prpl_info
->list_icon(account
, NULL
);
1199 if (!strcmp(protoname
, "aim") || !strcmp(protoname
, "icq"))
1206 *ret_account
= account
;
1224 pidgin_set_accessible_label (GtkWidget
*w
, GtkWidget
*l
)
1227 const gchar
*label_text
;
1228 const gchar
*existing_name
;
1230 acc
= gtk_widget_get_accessible (w
);
1232 /* If this object has no name, set it's name with the label text */
1233 existing_name
= atk_object_get_name (acc
);
1234 if (!existing_name
) {
1235 label_text
= gtk_label_get_text (GTK_LABEL(l
));
1237 atk_object_set_name (acc
, label_text
);
1240 pidgin_set_accessible_relations(w
, l
);
1244 pidgin_set_accessible_relations (GtkWidget
*w
, GtkWidget
*l
)
1246 AtkObject
*acc
, *label
;
1247 AtkObject
*rel_obj
[1];
1248 AtkRelationSet
*set
;
1249 AtkRelation
*relation
;
1251 acc
= gtk_widget_get_accessible (w
);
1252 label
= gtk_widget_get_accessible (l
);
1254 /* Make sure mnemonics work */
1255 gtk_label_set_mnemonic_widget(GTK_LABEL(l
), w
);
1257 /* Create the labeled-by relation */
1258 set
= atk_object_ref_relation_set (acc
);
1260 relation
= atk_relation_new (rel_obj
, 1, ATK_RELATION_LABELLED_BY
);
1261 atk_relation_set_add (set
, relation
);
1262 g_object_unref (relation
);
1263 g_object_unref(set
);
1265 /* Create the label-for relation */
1266 set
= atk_object_ref_relation_set (label
);
1268 relation
= atk_relation_new (rel_obj
, 1, ATK_RELATION_LABEL_FOR
);
1269 atk_relation_set_add (set
, relation
);
1270 g_object_unref (relation
);
1271 g_object_unref(set
);
1275 pidgin_menu_position_func_helper(GtkMenu
*menu
,
1282 GtkRequisition requisition
;
1284 GdkRectangle monitor
;
1286 gint space_left
, space_right
, space_above
, space_below
;
1293 g_return_if_fail(GTK_IS_MENU(menu
));
1295 widget
= GTK_WIDGET(menu
);
1296 screen
= gtk_widget_get_screen(widget
);
1297 xthickness
= widget
->style
->xthickness
;
1298 ythickness
= widget
->style
->ythickness
;
1299 rtl
= (gtk_widget_get_direction(widget
) == GTK_TEXT_DIR_RTL
);
1302 * We need the requisition to figure out the right place to
1303 * popup the menu. In fact, we always need to ask here, since
1304 * if a size_request was queued while we weren't popped up,
1305 * the requisition won't have been recomputed yet.
1307 gtk_widget_size_request (widget
, &requisition
);
1309 monitor_num
= gdk_screen_get_monitor_at_point (screen
, *x
, *y
);
1314 * The placement of popup menus horizontally works like this (with
1315 * RTL in parentheses)
1317 * - If there is enough room to the right (left) of the mouse cursor,
1318 * position the menu there.
1320 * - Otherwise, if if there is enough room to the left (right) of the
1321 * mouse cursor, position the menu there.
1323 * - Otherwise if the menu is smaller than the monitor, position it
1324 * on the side of the mouse cursor that has the most space available
1326 * - Otherwise (if there is simply not enough room for the menu on the
1327 * monitor), position it as far left (right) as possible.
1329 * Positioning in the vertical direction is similar: first try below
1330 * mouse cursor, then above.
1332 gdk_screen_get_monitor_geometry (screen
, monitor_num
, &monitor
);
1334 space_left
= *x
- monitor
.x
;
1335 space_right
= monitor
.x
+ monitor
.width
- *x
- 1;
1336 space_above
= *y
- monitor
.y
;
1337 space_below
= monitor
.y
+ monitor
.height
- *y
- 1;
1339 /* position horizontally */
1341 /* the amount of space we need to position the menu. Note the
1342 * menu is offset "xthickness" pixels
1344 needed_width
= requisition
.width
- xthickness
;
1346 if (needed_width
<= space_left
||
1347 needed_width
<= space_right
)
1349 if ((rtl
&& needed_width
<= space_left
) ||
1350 (!rtl
&& needed_width
> space_right
))
1353 *x
= *x
+ xthickness
- requisition
.width
+ 1;
1357 /* position right */
1358 *x
= *x
- xthickness
;
1361 /* x is clamped on-screen further down */
1363 else if (requisition
.width
<= monitor
.width
)
1365 /* the menu is too big to fit on either side of the mouse
1366 * cursor, but smaller than the monitor. Position it on
1367 * the side that has the most space
1369 if (space_left
> space_right
)
1377 *x
= monitor
.x
+ monitor
.width
- requisition
.width
;
1380 else /* menu is simply too big for the monitor */
1385 *x
= monitor
.x
+ monitor
.width
- requisition
.width
;
1394 /* Position vertically. The algorithm is the same as above, but
1395 * simpler because we don't have to take RTL into account.
1397 needed_height
= requisition
.height
- ythickness
;
1399 if (needed_height
<= space_above
||
1400 needed_height
<= space_below
)
1402 if (needed_height
<= space_below
)
1403 *y
= *y
- ythickness
;
1405 *y
= *y
+ ythickness
- requisition
.height
+ 1;
1407 *y
= CLAMP (*y
, monitor
.y
,
1408 monitor
.y
+ monitor
.height
- requisition
.height
);
1410 else if (needed_height
> space_below
&& needed_height
> space_above
)
1412 if (space_below
>= space_above
)
1413 *y
= monitor
.y
+ monitor
.height
- requisition
.height
;
1425 pidgin_treeview_popup_menu_position_func(GtkMenu
*menu
,
1431 GtkWidget
*widget
= GTK_WIDGET(data
);
1432 GtkTreeView
*tv
= GTK_TREE_VIEW(data
);
1434 GtkTreeViewColumn
*col
;
1436 gint ythickness
= GTK_WIDGET(menu
)->style
->ythickness
;
1438 gdk_window_get_origin (widget
->window
, x
, y
);
1439 gtk_tree_view_get_cursor (tv
, &path
, &col
);
1440 gtk_tree_view_get_cell_area (tv
, path
, col
, &rect
);
1442 *x
+= rect
.x
+rect
.width
;
1443 *y
+= rect
.y
+rect
.height
+ythickness
;
1444 pidgin_menu_position_func_helper(menu
, x
, y
, push_in
, data
);
1455 PurpleAccount
*account
;
1459 static void dnd_image_ok_callback(_DndData
*data
, int choice
)
1461 const gchar
*shortname
;
1466 PurpleConversation
*conv
;
1467 PidginConversation
*gtkconv
;
1471 PurpleContact
*contact
;
1473 case DND_BUDDY_ICON
:
1474 if (g_stat(data
->filename
, &st
)) {
1477 str
= g_strdup_printf(_("The following error has occurred loading %s: %s"),
1478 data
->filename
, g_strerror(errno
));
1479 purple_notify_error(NULL
, NULL
,
1480 _("Failed to load image"),
1487 buddy
= purple_find_buddy(data
->account
, data
->who
);
1489 purple_debug_info("custom-icon", "You can only set custom icons for people on your buddylist.\n");
1492 contact
= purple_buddy_get_contact(buddy
);
1493 purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode
*)contact
, data
->filename
);
1495 case DND_FILE_TRANSFER
:
1496 serv_send_file(purple_account_get_connection(data
->account
), data
->who
, data
->filename
);
1499 conv
= purple_conversation_new(PURPLE_CONV_TYPE_IM
, data
->account
, data
->who
);
1500 gtkconv
= PIDGIN_CONVERSATION(conv
);
1502 if (!g_file_get_contents(data
->filename
, &filedata
, &size
,
1506 str
= g_strdup_printf(_("The following error has occurred loading %s: %s"), data
->filename
, err
->message
);
1507 purple_notify_error(NULL
, NULL
,
1508 _("Failed to load image"),
1516 shortname
= strrchr(data
->filename
, G_DIR_SEPARATOR
);
1517 shortname
= shortname
? shortname
+ 1 : data
->filename
;
1518 id
= purple_imgstore_add_with_id(filedata
, size
, shortname
);
1520 gtk_text_buffer_get_iter_at_mark(GTK_IMHTML(gtkconv
->entry
)->text_buffer
, &iter
,
1521 gtk_text_buffer_get_insert(GTK_IMHTML(gtkconv
->entry
)->text_buffer
));
1522 gtk_imhtml_insert_image_at_iter(GTK_IMHTML(gtkconv
->entry
), id
, &iter
);
1523 purple_imgstore_unref_by_id(id
);
1527 g_free(data
->filename
);
1532 static void dnd_image_cancel_callback(_DndData
*data
, int choice
)
1534 g_free(data
->filename
);
1539 static void dnd_set_icon_ok_cb(_DndData
*data
)
1541 dnd_image_ok_callback(data
, DND_BUDDY_ICON
);
1544 static void dnd_set_icon_cancel_cb(_DndData
*data
)
1546 g_free(data
->filename
);
1552 pidgin_dnd_file_manage(GtkSelectionData
*sd
, PurpleAccount
*account
, const char *who
)
1555 GList
*files
= purple_uri_list_extract_filenames((const gchar
*)sd
->data
);
1556 PurpleConnection
*gc
= purple_account_get_connection(account
);
1557 PurplePluginProtocolInfo
*prpl_info
= NULL
;
1559 PurpleDesktopItem
*item
;
1561 gchar
*filename
= NULL
;
1562 gchar
*basename
= NULL
;
1564 g_return_if_fail(account
!= NULL
);
1565 g_return_if_fail(who
!= NULL
);
1567 for ( ; files
; files
= g_list_delete_link(files
, files
)) {
1571 filename
= files
->data
;
1572 basename
= g_path_get_basename(filename
);
1574 /* XXX - Make ft API support creating a transfer with more than one file */
1575 if (!g_file_test(filename
, G_FILE_TEST_EXISTS
)) {
1579 /* XXX - make ft api suupport sending a directory */
1580 /* Are we dealing with a directory? */
1581 if (g_file_test(filename
, G_FILE_TEST_IS_DIR
)) {
1584 str
= g_strdup_printf(_("Cannot send folder %s."), basename
);
1585 str2
= g_strdup_printf(_("%s cannot transfer a folder. You will need to send the files within individually."), PIDGIN_NAME
);
1587 purple_notify_error(NULL
, NULL
,
1595 /* Are we dealing with an image? */
1596 pb
= pidgin_pixbuf_new_from_file(filename
);
1598 _DndData
*data
= g_malloc(sizeof(_DndData
));
1599 gboolean ft
= FALSE
, im
= FALSE
;
1601 data
->who
= g_strdup(who
);
1602 data
->filename
= g_strdup(filename
);
1603 data
->account
= account
;
1606 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
);
1608 if (prpl_info
&& prpl_info
->options
& OPT_PROTO_IM_IMAGE
)
1611 if (prpl_info
&& prpl_info
->can_receive_file
)
1612 ft
= prpl_info
->can_receive_file(gc
, who
);
1613 else if (prpl_info
&& prpl_info
->send_file
)
1617 purple_request_choice(NULL
, NULL
,
1618 _("You have dragged an image"),
1619 _("You can send this image as a file transfer, "
1620 "embed it into this message, or use it as the buddy icon for this user."),
1621 DND_FILE_TRANSFER
, _("OK"), (GCallback
)dnd_image_ok_callback
,
1622 _("Cancel"), (GCallback
)dnd_image_cancel_callback
,
1625 _("Set as buddy icon"), DND_BUDDY_ICON
,
1626 _("Send image file"), DND_FILE_TRANSFER
,
1627 _("Insert in message"), DND_IM_IMAGE
,
1629 else if (!(im
|| ft
))
1630 purple_request_yes_no(NULL
, NULL
, _("You have dragged an image"),
1631 _("Would you like to set it as the buddy icon for this user?"),
1632 PURPLE_DEFAULT_ACTION_NONE
,
1634 data
, (GCallback
)dnd_set_icon_ok_cb
, (GCallback
)dnd_set_icon_cancel_cb
);
1636 purple_request_choice(NULL
, NULL
,
1637 _("You have dragged an image"),
1638 (ft
? _("You can send this image as a file transfer, or use it as the buddy icon for this user.") :
1639 _("You can insert this image into this message, or use it as the buddy icon for this user")),
1640 (ft
? DND_FILE_TRANSFER
: DND_IM_IMAGE
),
1641 _("OK"), (GCallback
)dnd_image_ok_callback
,
1642 _("Cancel"), (GCallback
)dnd_image_cancel_callback
,
1645 _("Set as buddy icon"), DND_BUDDY_ICON
,
1646 (ft
? _("Send image file") : _("Insert in message")), (ft
? DND_FILE_TRANSFER
: DND_IM_IMAGE
),
1648 g_object_unref(G_OBJECT(pb
));
1652 g_free(files
->data
);
1653 files
= g_list_delete_link(files
, files
);
1659 /* Are we trying to send a .desktop file? */
1660 else if (purple_str_has_suffix(basename
, ".desktop") && (item
= purple_desktop_item_new_from_file(filename
))) {
1661 PurpleDesktopItemType dtype
;
1663 const char *itemname
= NULL
;
1665 const char * const *langs
;
1667 langs
= g_get_language_names();
1668 for (i
= 0; langs
[i
]; i
++) {
1669 g_snprintf(key
, sizeof(key
), "Name[%s]", langs
[i
]);
1670 itemname
= purple_desktop_item_get_string(item
, key
);
1675 itemname
= purple_desktop_item_get_string(item
, "Name");
1677 dtype
= purple_desktop_item_get_entry_type(item
);
1679 PurpleConversation
*conv
;
1680 PidginConversation
*gtkconv
;
1682 case PURPLE_DESKTOP_ITEM_TYPE_LINK
:
1683 conv
= purple_conversation_new(PURPLE_CONV_TYPE_IM
, account
, who
);
1684 gtkconv
= PIDGIN_CONVERSATION(conv
);
1685 gtk_imhtml_insert_link(GTK_IMHTML(gtkconv
->entry
),
1686 gtk_text_buffer_get_insert(GTK_IMHTML(gtkconv
->entry
)->text_buffer
),
1687 purple_desktop_item_get_string(item
, "URL"), itemname
);
1690 /* I don't know if we really want to do anything here. Most of
1691 * the desktop item types are crap like "MIME Type" (I have no
1692 * clue how that would be a desktop item) and "Comment"...
1693 * nothing we can really send. The only logical one is
1694 * "Application," but do we really want to send a binary and
1695 * nothing else? Probably not. I'll just give an error and
1697 /* The original patch sent the icon used by the launcher. That's probably wrong */
1698 purple_notify_error(NULL
, NULL
, _("Cannot send launcher"),
1699 _("You dragged a desktop launcher. Most "
1700 "likely you wanted to send the target "
1701 "of this launcher instead of this "
1702 "launcher itself."));
1705 purple_desktop_item_unref(item
);
1708 g_free(files
->data
);
1709 files
= g_list_delete_link(files
, files
);
1715 /* Everything is fine, let's send */
1716 serv_send_file(gc
, who
, filename
);
1723 void pidgin_buddy_icon_get_scale_size(GdkPixbuf
*buf
, PurpleBuddyIconSpec
*spec
, PurpleIconScaleRules rules
, int *width
, int *height
)
1725 *width
= gdk_pixbuf_get_width(buf
);
1726 *height
= gdk_pixbuf_get_height(buf
);
1728 if ((spec
== NULL
) || !(spec
->scale_rules
& rules
))
1731 purple_buddy_icon_get_scale_size(spec
, width
, height
);
1733 /* and now for some arbitrary sanity checks */
1740 GdkPixbuf
* pidgin_create_status_icon(PurpleStatusPrimitive prim
, GtkWidget
*w
, const char *size
)
1742 GtkIconSize icon_size
= gtk_icon_size_from_name(size
);
1743 GdkPixbuf
*pixbuf
= NULL
;
1744 const char *stock
= pidgin_stock_id_from_status_primitive(prim
);
1746 pixbuf
= gtk_widget_render_icon (w
, stock
? stock
: PIDGIN_STOCK_STATUS_AVAILABLE
,
1747 icon_size
, "GtkWidget");
1752 stock_id_from_status_primitive_idle(PurpleStatusPrimitive prim
, gboolean idle
)
1754 const char *stock
= NULL
;
1756 case PURPLE_STATUS_UNSET
:
1759 case PURPLE_STATUS_UNAVAILABLE
:
1760 stock
= idle
? PIDGIN_STOCK_STATUS_BUSY_I
: PIDGIN_STOCK_STATUS_BUSY
;
1762 case PURPLE_STATUS_AWAY
:
1763 stock
= idle
? PIDGIN_STOCK_STATUS_AWAY_I
: PIDGIN_STOCK_STATUS_AWAY
;
1765 case PURPLE_STATUS_EXTENDED_AWAY
:
1766 stock
= idle
? PIDGIN_STOCK_STATUS_XA_I
: PIDGIN_STOCK_STATUS_XA
;
1768 case PURPLE_STATUS_INVISIBLE
:
1769 stock
= PIDGIN_STOCK_STATUS_INVISIBLE
;
1771 case PURPLE_STATUS_OFFLINE
:
1772 stock
= idle
? PIDGIN_STOCK_STATUS_OFFLINE_I
: PIDGIN_STOCK_STATUS_OFFLINE
;
1775 stock
= idle
? PIDGIN_STOCK_STATUS_AVAILABLE_I
: PIDGIN_STOCK_STATUS_AVAILABLE
;
1782 pidgin_stock_id_from_status_primitive(PurpleStatusPrimitive prim
)
1784 return stock_id_from_status_primitive_idle(prim
, FALSE
);
1788 pidgin_stock_id_from_presence(PurplePresence
*presence
)
1790 PurpleStatus
*status
;
1791 PurpleStatusType
*type
;
1792 PurpleStatusPrimitive prim
;
1795 g_return_val_if_fail(presence
, NULL
);
1797 status
= purple_presence_get_active_status(presence
);
1798 type
= purple_status_get_type(status
);
1799 prim
= purple_status_type_get_primitive(type
);
1801 idle
= purple_presence_is_idle(presence
);
1803 return stock_id_from_status_primitive_idle(prim
, idle
);
1807 pidgin_create_prpl_icon(PurpleAccount
*account
, PidginPrplIconSize size
)
1811 g_return_val_if_fail(account
!= NULL
, NULL
);
1813 prpl
= purple_find_prpl(purple_account_get_protocol_id(account
));
1816 return pidgin_create_prpl_icon_from_prpl(prpl
, size
, account
);
1820 menu_action_cb(GtkMenuItem
*item
, gpointer object
)
1823 void (*callback
)(gpointer
, gpointer
);
1825 callback
= g_object_get_data(G_OBJECT(item
), "purplecallback");
1826 data
= g_object_get_data(G_OBJECT(item
), "purplecallbackdata");
1829 callback(object
, data
);
1833 pidgin_append_menu_action(GtkWidget
*menu
, PurpleMenuAction
*act
,
1836 GtkWidget
*menuitem
;
1839 return pidgin_separator(menu
);
1842 if (act
->children
== NULL
) {
1843 menuitem
= gtk_menu_item_new_with_mnemonic(act
->label
);
1845 if (act
->callback
!= NULL
) {
1846 g_object_set_data(G_OBJECT(menuitem
),
1849 g_object_set_data(G_OBJECT(menuitem
),
1850 "purplecallbackdata",
1852 g_signal_connect(G_OBJECT(menuitem
), "activate",
1853 G_CALLBACK(menu_action_cb
),
1856 gtk_widget_set_sensitive(menuitem
, FALSE
);
1859 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
1862 GtkWidget
*submenu
= NULL
;
1863 GtkAccelGroup
*group
;
1865 menuitem
= gtk_menu_item_new_with_mnemonic(act
->label
);
1866 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
1868 submenu
= gtk_menu_new();
1869 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem
), submenu
);
1871 group
= gtk_menu_get_accel_group(GTK_MENU(menu
));
1873 char *path
= g_strdup_printf("%s/%s", GTK_MENU_ITEM(menuitem
)->accel_path
, act
->label
);
1874 gtk_menu_set_accel_path(GTK_MENU(submenu
), path
);
1876 gtk_menu_set_accel_group(GTK_MENU(submenu
), group
);
1879 for (l
= act
->children
; l
; l
= l
->next
) {
1880 PurpleMenuAction
*act
= (PurpleMenuAction
*)l
->data
;
1882 pidgin_append_menu_action(submenu
, act
, object
);
1884 g_list_free(act
->children
);
1885 act
->children
= NULL
;
1887 purple_menu_action_free(act
);
1894 GtkWidget
*accountopt
;
1896 PidginFilterBuddyCompletionEntryFunc filter_func
;
1897 gpointer filter_func_user_data
;
1899 GtkListStore
*store
;
1900 } PidginCompletionData
;
1902 static gboolean
buddyname_completion_match_func(GtkEntryCompletion
*completion
,
1903 const gchar
*key
, GtkTreeIter
*iter
, gpointer user_data
)
1905 GtkTreeModel
*model
;
1910 model
= gtk_entry_completion_get_model (completion
);
1913 gtk_tree_model_get_value(model
, iter
, 2, &val1
);
1914 tmp
= g_value_get_string(&val1
);
1915 if (tmp
!= NULL
&& purple_str_has_prefix(tmp
, key
))
1917 g_value_unset(&val1
);
1920 g_value_unset(&val1
);
1923 gtk_tree_model_get_value(model
, iter
, 3, &val2
);
1924 tmp
= g_value_get_string(&val2
);
1925 if (tmp
!= NULL
&& purple_str_has_prefix(tmp
, key
))
1927 g_value_unset(&val2
);
1930 g_value_unset(&val2
);
1935 static gboolean
buddyname_completion_match_selected_cb(GtkEntryCompletion
*completion
,
1936 GtkTreeModel
*model
, GtkTreeIter
*iter
, PidginCompletionData
*data
)
1939 GtkWidget
*optmenu
= data
->accountopt
;
1940 PurpleAccount
*account
;
1943 gtk_tree_model_get_value(model
, iter
, 1, &val
);
1944 gtk_entry_set_text(GTK_ENTRY(data
->entry
), g_value_get_string(&val
));
1945 g_value_unset(&val
);
1947 gtk_tree_model_get_value(model
, iter
, 4, &val
);
1948 account
= g_value_get_pointer(&val
);
1949 g_value_unset(&val
);
1951 if (account
== NULL
)
1954 if (optmenu
!= NULL
)
1955 aop_option_menu_select_by_data(optmenu
, account
);
1961 add_buddyname_autocomplete_entry(GtkListStore
*store
, const char *buddy_alias
, const char *contact_alias
,
1962 const PurpleAccount
*account
, const char *buddyname
)
1965 gboolean completion_added
= FALSE
;
1966 gchar
*normalized_buddyname
;
1969 tmp
= g_utf8_normalize(buddyname
, -1, G_NORMALIZE_DEFAULT
);
1970 normalized_buddyname
= g_utf8_casefold(tmp
, -1);
1973 /* There's no sense listing things like: 'xxx "xxx"'
1974 when the name and buddy alias match. */
1975 if (buddy_alias
&& strcmp(buddy_alias
, buddyname
)) {
1976 char *completion_entry
= g_strdup_printf("%s \"%s\"", buddyname
, buddy_alias
);
1977 char *tmp2
= g_utf8_normalize(buddy_alias
, -1, G_NORMALIZE_DEFAULT
);
1979 tmp
= g_utf8_casefold(tmp2
, -1);
1982 gtk_list_store_append(store
, &iter
);
1983 gtk_list_store_set(store
, &iter
,
1984 0, completion_entry
,
1986 2, normalized_buddyname
,
1990 g_free(completion_entry
);
1992 completion_added
= TRUE
;
1995 /* There's no sense listing things like: 'xxx "xxx"'
1996 when the name and contact alias match. */
1997 if (contact_alias
&& strcmp(contact_alias
, buddyname
)) {
1998 /* We don't want duplicates when the contact and buddy alias match. */
1999 if (!buddy_alias
|| strcmp(contact_alias
, buddy_alias
)) {
2000 char *completion_entry
= g_strdup_printf("%s \"%s\"",
2001 buddyname
, contact_alias
);
2002 char *tmp2
= g_utf8_normalize(contact_alias
, -1, G_NORMALIZE_DEFAULT
);
2004 tmp
= g_utf8_casefold(tmp2
, -1);
2007 gtk_list_store_append(store
, &iter
);
2008 gtk_list_store_set(store
, &iter
,
2009 0, completion_entry
,
2011 2, normalized_buddyname
,
2015 g_free(completion_entry
);
2017 completion_added
= TRUE
;
2021 if (completion_added
== FALSE
) {
2022 /* Add the buddy's name. */
2023 gtk_list_store_append(store
, &iter
);
2024 gtk_list_store_set(store
, &iter
,
2027 2, normalized_buddyname
,
2033 g_free(normalized_buddyname
);
2036 static void get_log_set_name(PurpleLogSet
*set
, gpointer value
, PidginCompletionData
*data
)
2038 PidginFilterBuddyCompletionEntryFunc filter_func
= data
->filter_func
;
2039 gpointer user_data
= data
->filter_func_user_data
;
2041 /* 1. Don't show buddies because we will have gotten them already.
2042 * 2. The boxes that use this autocomplete code handle only IMs. */
2043 if (!set
->buddy
&& set
->type
== PURPLE_LOG_IM
) {
2044 PidginBuddyCompletionEntry entry
;
2045 entry
.is_buddy
= FALSE
;
2046 entry
.entry
.logged_buddy
= set
;
2048 if (filter_func(&entry
, user_data
)) {
2049 add_buddyname_autocomplete_entry(data
->store
,
2050 NULL
, NULL
, set
->account
, set
->name
);
2056 add_completion_list(PidginCompletionData
*data
)
2058 PurpleBlistNode
*gnode
, *cnode
, *bnode
;
2059 PidginFilterBuddyCompletionEntryFunc filter_func
= data
->filter_func
;
2060 gpointer user_data
= data
->filter_func_user_data
;
2063 gtk_list_store_clear(data
->store
);
2065 for (gnode
= purple_get_blist()->root
; gnode
!= NULL
; gnode
= gnode
->next
)
2067 if (!PURPLE_BLIST_NODE_IS_GROUP(gnode
))
2070 for (cnode
= gnode
->child
; cnode
!= NULL
; cnode
= cnode
->next
)
2072 if (!PURPLE_BLIST_NODE_IS_CONTACT(cnode
))
2075 for (bnode
= cnode
->child
; bnode
!= NULL
; bnode
= bnode
->next
)
2077 PidginBuddyCompletionEntry entry
;
2078 entry
.is_buddy
= TRUE
;
2079 entry
.entry
.buddy
= (PurpleBuddy
*) bnode
;
2081 if (filter_func(&entry
, user_data
)) {
2082 add_buddyname_autocomplete_entry(data
->store
,
2083 ((PurpleContact
*)cnode
)->alias
,
2084 purple_buddy_get_contact_alias(entry
.entry
.buddy
),
2085 entry
.entry
.buddy
->account
,
2086 entry
.entry
.buddy
->name
2093 sets
= purple_log_get_log_sets();
2094 g_hash_table_foreach(sets
, (GHFunc
)get_log_set_name
, data
);
2095 g_hash_table_destroy(sets
);
2100 buddyname_autocomplete_destroyed_cb(GtkWidget
*widget
, gpointer data
)
2103 purple_signals_disconnect_by_handle(widget
);
2107 repopulate_autocomplete(gpointer something
, gpointer data
)
2109 add_completion_list(data
);
2113 pidgin_setup_screenname_autocomplete_with_filter(GtkWidget
*entry
, GtkWidget
*accountopt
, PidginFilterBuddyCompletionEntryFunc filter_func
, gpointer user_data
)
2115 PidginCompletionData
*data
;
2118 * Store the displayed completion value, the buddy name, the UTF-8
2119 * normalized & casefolded buddy name, the UTF-8 normalized &
2120 * casefolded value for comparison, and the account.
2122 GtkListStore
*store
;
2124 GtkEntryCompletion
*completion
;
2126 data
= g_new0(PidginCompletionData
, 1);
2127 store
= gtk_list_store_new(5, G_TYPE_STRING
, G_TYPE_STRING
, G_TYPE_STRING
, G_TYPE_STRING
, G_TYPE_POINTER
);
2129 data
->entry
= entry
;
2130 data
->accountopt
= accountopt
;
2131 if (filter_func
== NULL
) {
2132 data
->filter_func
= pidgin_screenname_autocomplete_default_filter
;
2133 data
->filter_func_user_data
= NULL
;
2135 data
->filter_func
= filter_func
;
2136 data
->filter_func_user_data
= user_data
;
2138 data
->store
= store
;
2140 add_completion_list(data
);
2142 /* Sort the completion list by buddy name */
2143 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store
),
2144 1, GTK_SORT_ASCENDING
);
2146 completion
= gtk_entry_completion_new();
2147 gtk_entry_completion_set_match_func(completion
, buddyname_completion_match_func
, NULL
, NULL
);
2149 g_signal_connect(G_OBJECT(completion
), "match-selected",
2150 G_CALLBACK(buddyname_completion_match_selected_cb
), data
);
2152 gtk_entry_set_completion(GTK_ENTRY(entry
), completion
);
2153 g_object_unref(completion
);
2155 gtk_entry_completion_set_model(completion
, GTK_TREE_MODEL(store
));
2156 g_object_unref(store
);
2158 gtk_entry_completion_set_text_column(completion
, 0);
2160 purple_signal_connect(purple_connections_get_handle(), "signed-on", entry
,
2161 PURPLE_CALLBACK(repopulate_autocomplete
), data
);
2162 purple_signal_connect(purple_connections_get_handle(), "signed-off", entry
,
2163 PURPLE_CALLBACK(repopulate_autocomplete
), data
);
2165 purple_signal_connect(purple_accounts_get_handle(), "account-added", entry
,
2166 PURPLE_CALLBACK(repopulate_autocomplete
), data
);
2167 purple_signal_connect(purple_accounts_get_handle(), "account-removed", entry
,
2168 PURPLE_CALLBACK(repopulate_autocomplete
), data
);
2170 g_signal_connect(G_OBJECT(entry
), "destroy", G_CALLBACK(buddyname_autocomplete_destroyed_cb
), data
);
2174 pidgin_screenname_autocomplete_default_filter(const PidginBuddyCompletionEntry
*completion_entry
, gpointer all_accounts
) {
2175 gboolean all
= GPOINTER_TO_INT(all_accounts
);
2177 if (completion_entry
->is_buddy
) {
2178 return all
|| purple_account_is_connected(completion_entry
->entry
.buddy
->account
);
2180 return all
|| (completion_entry
->entry
.logged_buddy
->account
!= NULL
&& purple_account_is_connected(completion_entry
->entry
.logged_buddy
->account
));
2185 pidgin_setup_screenname_autocomplete(GtkWidget
*entry
, GtkWidget
*accountopt
, gboolean all
) {
2186 pidgin_setup_screenname_autocomplete_with_filter(entry
, accountopt
, pidgin_screenname_autocomplete_default_filter
, GINT_TO_POINTER(all
));
2191 void pidgin_set_cursor(GtkWidget
*widget
, GdkCursorType cursor_type
)
2195 g_return_if_fail(widget
!= NULL
);
2196 if (widget
->window
== NULL
)
2199 cursor
= gdk_cursor_new(cursor_type
);
2200 gdk_window_set_cursor(widget
->window
, cursor
);
2201 gdk_cursor_unref(cursor
);
2203 gdk_display_flush(gdk_drawable_get_display(GDK_DRAWABLE(widget
->window
)));
2206 void pidgin_clear_cursor(GtkWidget
*widget
)
2208 g_return_if_fail(widget
!= NULL
);
2209 if (widget
->window
== NULL
)
2212 gdk_window_set_cursor(widget
->window
, NULL
);
2215 struct _icon_chooser
{
2216 GtkWidget
*icon_filesel
;
2217 GtkWidget
*icon_preview
;
2218 GtkWidget
*icon_text
;
2220 void (*callback
)(const char*,gpointer
);
2225 icon_filesel_choose_cb(GtkWidget
*widget
, gint response
, struct _icon_chooser
*dialog
)
2227 char *filename
, *current_folder
;
2229 if (response
!= GTK_RESPONSE_ACCEPT
) {
2230 if (response
== GTK_RESPONSE_CANCEL
) {
2231 gtk_widget_destroy(dialog
->icon_filesel
);
2233 dialog
->icon_filesel
= NULL
;
2234 if (dialog
->callback
)
2235 dialog
->callback(NULL
, dialog
->data
);
2240 filename
= gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog
->icon_filesel
));
2241 current_folder
= gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(dialog
->icon_filesel
));
2242 if (current_folder
!= NULL
) {
2243 purple_prefs_set_path(PIDGIN_PREFS_ROOT
"/filelocations/last_icon_folder", current_folder
);
2244 g_free(current_folder
);
2248 if (dialog
->callback
)
2249 dialog
->callback(filename
, dialog
->data
);
2250 gtk_widget_destroy(dialog
->icon_filesel
);
2257 icon_preview_change_cb(GtkFileChooser
*widget
, struct _icon_chooser
*dialog
)
2259 GdkPixbuf
*pixbuf
, *scale
;
2261 char *basename
, *markup
, *size
;
2265 filename
= gtk_file_chooser_get_preview_filename(
2266 GTK_FILE_CHOOSER(dialog
->icon_filesel
));
2268 if (!filename
|| g_stat(filename
, &st
) || !(pixbuf
= pidgin_pixbuf_new_from_file(filename
)))
2270 gtk_image_set_from_pixbuf(GTK_IMAGE(dialog
->icon_preview
), NULL
);
2271 gtk_label_set_markup(GTK_LABEL(dialog
->icon_text
), "");
2276 width
= gdk_pixbuf_get_width(pixbuf
);
2277 height
= gdk_pixbuf_get_height(pixbuf
);
2278 basename
= g_path_get_basename(filename
);
2279 size
= purple_str_size_to_units(st
.st_size
);
2280 markup
= g_strdup_printf(_("<b>File:</b> %s\n"
2281 "<b>File size:</b> %s\n"
2282 "<b>Image size:</b> %dx%d"),
2283 basename
, size
, width
, height
);
2285 scale
= gdk_pixbuf_scale_simple(pixbuf
, width
* 50 / height
,
2286 50, GDK_INTERP_BILINEAR
);
2287 gtk_image_set_from_pixbuf(GTK_IMAGE(dialog
->icon_preview
), scale
);
2288 gtk_label_set_markup(GTK_LABEL(dialog
->icon_text
), markup
);
2290 g_object_unref(G_OBJECT(pixbuf
));
2291 g_object_unref(G_OBJECT(scale
));
2299 GtkWidget
*pidgin_buddy_icon_chooser_new(GtkWindow
*parent
, void(*callback
)(const char *, gpointer
), gpointer data
) {
2300 struct _icon_chooser
*dialog
= g_new0(struct _icon_chooser
, 1);
2303 const char *current_folder
;
2305 dialog
->callback
= callback
;
2306 dialog
->data
= data
;
2308 current_folder
= purple_prefs_get_path(PIDGIN_PREFS_ROOT
"/filelocations/last_icon_folder");
2310 dialog
->icon_filesel
= gtk_file_chooser_dialog_new(_("Buddy Icon"),
2312 GTK_FILE_CHOOSER_ACTION_OPEN
,
2313 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
2314 GTK_STOCK_OPEN
, GTK_RESPONSE_ACCEPT
,
2316 gtk_dialog_set_default_response(GTK_DIALOG(dialog
->icon_filesel
), GTK_RESPONSE_ACCEPT
);
2317 if ((current_folder
!= NULL
) && (*current_folder
!= '\0'))
2318 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog
->icon_filesel
),
2321 dialog
->icon_preview
= gtk_image_new();
2322 dialog
->icon_text
= gtk_label_new(NULL
);
2324 vbox
= gtk_vbox_new(FALSE
, PIDGIN_HIG_BOX_SPACE
);
2325 gtk_widget_set_size_request(GTK_WIDGET(vbox
), -1, 50);
2326 gtk_box_pack_start(GTK_BOX(vbox
), GTK_WIDGET(dialog
->icon_preview
), TRUE
, FALSE
, 0);
2327 gtk_box_pack_end(GTK_BOX(vbox
), GTK_WIDGET(dialog
->icon_text
), FALSE
, FALSE
, 0);
2328 gtk_widget_show_all(vbox
);
2330 gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog
->icon_filesel
), vbox
);
2331 gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(dialog
->icon_filesel
), TRUE
);
2332 gtk_file_chooser_set_use_preview_label(GTK_FILE_CHOOSER(dialog
->icon_filesel
), FALSE
);
2334 g_signal_connect(G_OBJECT(dialog
->icon_filesel
), "update-preview",
2335 G_CALLBACK(icon_preview_change_cb
), dialog
);
2336 g_signal_connect(G_OBJECT(dialog
->icon_filesel
), "response",
2337 G_CALLBACK(icon_filesel_choose_cb
), dialog
);
2338 icon_preview_change_cb(NULL
, dialog
);
2341 g_signal_connect(G_OBJECT(dialog
->icon_filesel
), "show",
2342 G_CALLBACK(winpidgin_ensure_onscreen
), dialog
->icon_filesel
);
2345 return dialog
->icon_filesel
;
2349 * @return True if any string from array a exists in array b.
2352 str_array_match(char **a
, char **b
)
2358 for (i
= 0; a
[i
] != NULL
; i
++)
2359 for (j
= 0; b
[j
] != NULL
; j
++)
2360 if (!g_ascii_strcasecmp(a
[i
], b
[j
]))
2366 pidgin_convert_buddy_icon(PurplePlugin
*plugin
, const char *path
, size_t *len
)
2368 PurplePluginProtocolInfo
*prpl_info
;
2369 PurpleBuddyIconSpec
*spec
;
2370 int orig_width
, orig_height
, new_width
, new_height
;
2371 GdkPixbufFormat
*format
;
2372 char **pixbuf_formats
;
2373 char **prpl_formats
;
2374 GError
*error
= NULL
;
2377 GdkPixbuf
*pixbuf
, *original
;
2382 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(plugin
);
2383 spec
= &prpl_info
->icon_spec
;
2384 g_return_val_if_fail(spec
->format
!= NULL
, NULL
);
2386 format
= gdk_pixbuf_get_file_info(path
, &orig_width
, &orig_height
);
2387 if (format
== NULL
) {
2388 purple_debug_warning("buddyicon", "Could not get file info of %s\n", path
);
2392 pixbuf_formats
= gdk_pixbuf_format_get_extensions(format
);
2393 prpl_formats
= g_strsplit(spec
->format
, ",", 0);
2395 if (str_array_match(pixbuf_formats
, prpl_formats
) && /* This is an acceptable format AND */
2396 (!(spec
->scale_rules
& PURPLE_ICON_SCALE_SEND
) || /* The prpl doesn't scale before it sends OR */
2397 (spec
->min_width
<= orig_width
&& spec
->max_width
>= orig_width
&&
2398 spec
->min_height
<= orig_height
&& spec
->max_height
>= orig_height
))) /* The icon is the correct size */
2400 g_strfreev(pixbuf_formats
);
2402 if (!g_file_get_contents(path
, &contents
, &length
, &error
)) {
2403 purple_debug_warning("buddyicon", "Could not get file contents "
2404 "of %s: %s\n", path
, error
->message
);
2405 g_strfreev(prpl_formats
);
2409 if (spec
->max_filesize
== 0 || length
< spec
->max_filesize
) {
2410 /* The supplied image fits the file size, dimensions and type
2411 constraints. Great! Return it without making any changes. */
2414 g_strfreev(prpl_formats
);
2418 /* The image was too big. Fall-through and try scaling it down. */
2421 g_strfreev(pixbuf_formats
);
2424 /* The original image wasn't compatible. Scale it or convert file type. */
2425 pixbuf
= gdk_pixbuf_new_from_file(path
, &error
);
2427 purple_debug_warning("buddyicon", "Could not open icon '%s' for "
2428 "conversion: %s\n", path
, error
->message
);
2429 g_error_free(error
);
2430 g_strfreev(prpl_formats
);
2433 original
= g_object_ref(G_OBJECT(pixbuf
));
2435 new_width
= orig_width
;
2436 new_height
= orig_height
;
2438 /* Make sure the image is the correct dimensions */
2439 if (spec
->scale_rules
& PURPLE_ICON_SCALE_SEND
&&
2440 (orig_width
< spec
->min_width
|| orig_width
> spec
->max_width
||
2441 orig_height
< spec
->min_height
|| orig_height
> spec
->max_height
))
2443 purple_buddy_icon_get_scale_size(spec
, &new_width
, &new_height
);
2445 g_object_unref(G_OBJECT(pixbuf
));
2446 pixbuf
= gdk_pixbuf_scale_simple(original
, new_width
, new_height
, GDK_INTERP_HYPER
);
2451 for (i
= 0; prpl_formats
[i
]; i
++) {
2454 const char *key
= NULL
;
2455 const char *value
= NULL
;
2458 purple_debug_info("buddyicon", "Converting buddy icon to %s\n", prpl_formats
[i
]);
2460 if (g_str_equal(prpl_formats
[i
], "png")) {
2461 key
= "compression";
2463 } else if (g_str_equal(prpl_formats
[i
], "jpeg")) {
2464 sprintf(tmp_buf
, "%u", quality
);
2469 if (!gdk_pixbuf_save_to_buffer(pixbuf
, &contents
, &length
,
2470 prpl_formats
[i
], &error
, key
, value
, NULL
))
2472 /* The NULL checking of error is necessary due to this bug:
2473 * http://bugzilla.gnome.org/show_bug.cgi?id=405539 */
2474 purple_debug_warning("buddyicon",
2475 "Could not convert to %s: %s\n", prpl_formats
[i
],
2476 (error
&& error
->message
) ? error
->message
: "Unknown error");
2477 g_error_free(error
);
2480 /* We couldn't convert to this image type. Try the next
2485 if (spec
->max_filesize
== 0 || length
<= spec
->max_filesize
) {
2486 /* We were able to save the image as this image type and
2487 have it be within the size constraints. Great! Return
2489 purple_debug_info("buddyicon", "Converted image from "
2490 "%dx%d to %dx%d, format=%s, quality=%u, "
2491 "filesize=%zu\n", orig_width
, orig_height
,
2492 new_width
, new_height
, prpl_formats
[i
], quality
,
2496 g_strfreev(prpl_formats
);
2497 g_object_unref(G_OBJECT(pixbuf
));
2498 g_object_unref(G_OBJECT(original
));
2504 if (!g_str_equal(prpl_formats
[i
], "jpeg")) {
2505 /* File size was too big and we can't lower the quality,
2506 so skip to the next image type. */
2510 /* File size was too big, but we're dealing with jpeg so try
2511 lowering the quality. */
2513 } while (quality
>= 70);
2516 /* We couldn't save the image in any format that was below the max
2517 file size. Maybe we can reduce the image dimensions? */
2518 scale_factor
*= 0.8;
2519 new_width
= orig_width
* scale_factor
;
2520 new_height
= orig_height
* scale_factor
;
2521 g_object_unref(G_OBJECT(pixbuf
));
2522 pixbuf
= gdk_pixbuf_scale_simple(original
, new_width
, new_height
, GDK_INTERP_HYPER
);
2523 } while ((new_width
> 10 || new_height
> 10) && new_width
> spec
->min_width
&& new_height
> spec
->min_height
);
2524 g_strfreev(prpl_formats
);
2525 g_object_unref(G_OBJECT(pixbuf
));
2526 g_object_unref(G_OBJECT(original
));
2528 tmp
= g_strdup_printf(_("The file '%s' is too large for %s. Please try a smaller image.\n"),
2529 path
, plugin
->info
->name
);
2530 purple_notify_error(NULL
, _("Icon Error"), _("Could not set icon"), tmp
);
2536 void pidgin_set_custom_buddy_icon(PurpleAccount
*account
, const char *who
, const char *filename
)
2539 PurpleContact
*contact
;
2541 buddy
= purple_find_buddy(account
, who
);
2543 purple_debug_info("custom-icon", "You can only set custom icon for someone in your buddylist.\n");
2547 contact
= purple_buddy_get_contact(buddy
);
2548 purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode
*)contact
, filename
);
2551 char *pidgin_make_pretty_arrows(const char *str
)
2554 char **split
= g_strsplit(str
, "->", -1);
2555 ret
= g_strjoinv("\342\207\250", split
);
2558 split
= g_strsplit(ret
, "<-", -1);
2560 ret
= g_strjoinv("\342\207\246", split
);
2566 void pidgin_set_urgent(GtkWindow
*window
, gboolean urgent
)
2569 winpidgin_window_flash(window
, urgent
);
2571 gtk_window_set_urgency_hint(window
, urgent
);
2575 static GSList
*minidialogs
= NULL
;
2578 pidgin_utils_get_handle(void)
2585 static void connection_signed_off_cb(PurpleConnection
*gc
)
2587 GSList
*list
, *l_next
;
2588 for (list
= minidialogs
; list
; list
= l_next
) {
2589 l_next
= list
->next
;
2590 if (g_object_get_data(G_OBJECT(list
->data
), "gc") == gc
) {
2591 gtk_widget_destroy(GTK_WIDGET(list
->data
));
2596 static void alert_killed_cb(GtkWidget
*widget
)
2598 minidialogs
= g_slist_remove(minidialogs
, widget
);
2601 struct _old_button_clicked_cb_data
2603 PidginUtilMiniDialogCallback cb
;
2608 old_mini_dialog_button_clicked_cb(PidginMiniDialog
*mini_dialog
,
2612 struct _old_button_clicked_cb_data
*data
= user_data
;
2613 data
->cb(data
->data
, button
);
2617 old_mini_dialog_destroy_cb(GtkWidget
*dialog
,
2620 while (cb_datas
!= NULL
)
2622 g_free(cb_datas
->data
);
2623 cb_datas
= g_list_delete_link(cb_datas
, cb_datas
);
2628 mini_dialog_init(PidginMiniDialog
*mini_dialog
, PurpleConnection
*gc
, void *user_data
, va_list args
)
2630 const char *button_text
;
2631 GList
*cb_datas
= NULL
;
2632 static gboolean first_call
= TRUE
;
2636 purple_signal_connect(purple_connections_get_handle(), "signed-off",
2637 pidgin_utils_get_handle(),
2638 PURPLE_CALLBACK(connection_signed_off_cb
), NULL
);
2641 g_object_set_data(G_OBJECT(mini_dialog
), "gc" ,gc
);
2642 g_signal_connect(G_OBJECT(mini_dialog
), "destroy",
2643 G_CALLBACK(alert_killed_cb
), NULL
);
2645 while ((button_text
= va_arg(args
, char*))) {
2646 struct _old_button_clicked_cb_data
*data
= NULL
;
2647 PidginMiniDialogCallback wrapper_cb
= NULL
;
2648 PidginUtilMiniDialogCallback callback
=
2649 va_arg(args
, PidginUtilMiniDialogCallback
);
2651 if (callback
!= NULL
) {
2652 data
= g_new0(struct _old_button_clicked_cb_data
, 1);
2653 data
->cb
= callback
;
2654 data
->data
= user_data
;
2655 wrapper_cb
= old_mini_dialog_button_clicked_cb
;
2657 pidgin_mini_dialog_add_button(mini_dialog
, button_text
,
2659 cb_datas
= g_list_append(cb_datas
, data
);
2662 g_signal_connect(G_OBJECT(mini_dialog
), "destroy",
2663 G_CALLBACK(old_mini_dialog_destroy_cb
), cb_datas
);
2666 #define INIT_AND_RETURN_MINI_DIALOG(mini_dialog) \
2668 va_start(args, user_data); \
2669 mini_dialog_init(mini_dialog, gc, user_data, args); \
2671 return GTK_WIDGET(mini_dialog);
2674 pidgin_make_mini_dialog(PurpleConnection
*gc
,
2675 const char *icon_name
,
2676 const char *primary
,
2677 const char *secondary
,
2681 PidginMiniDialog
*mini_dialog
= pidgin_mini_dialog_new(primary
, secondary
, icon_name
);
2682 INIT_AND_RETURN_MINI_DIALOG(mini_dialog
);
2686 pidgin_make_mini_dialog_with_custom_icon(PurpleConnection
*gc
,
2687 GdkPixbuf
*custom_icon
,
2688 const char *primary
,
2689 const char *secondary
,
2693 PidginMiniDialog
*mini_dialog
= pidgin_mini_dialog_new_with_custom_icon(primary
, secondary
, custom_icon
);
2694 INIT_AND_RETURN_MINI_DIALOG(mini_dialog
);
2698 * "This is so dead sexy."
2700 * "Best movie of the year."
2702 * This is the function that handles CTRL+F searching in the buddy list.
2703 * It finds the top-most buddy/group/chat/whatever containing the
2706 * It's somewhat ineffecient, because we strip all the HTML from the
2707 * "name" column of the buddy list (because the GtkTreeModel does not
2708 * contain the screen name in a non-markedup format). But the alternative
2709 * is to add an extra column to the GtkTreeModel. And this function is
2710 * used rarely, so it shouldn't matter TOO much.
2712 gboolean
pidgin_tree_view_search_equal_func(GtkTreeModel
*model
, gint column
,
2713 const gchar
*key
, GtkTreeIter
*iter
, gpointer data
)
2715 gchar
*enteredstring
;
2723 PangoLogAttr
*log_attrs
;
2726 if (g_ascii_strcasecmp(key
, "Global Thermonuclear War") == 0)
2728 purple_notify_info(NULL
, "WOPR",
2729 "Wouldn't you prefer a nice game of chess?", NULL
);
2733 gtk_tree_model_get(model
, iter
, column
, &withmarkup
, -1);
2734 if (withmarkup
== NULL
) /* This is probably a separator */
2737 tmp
= g_utf8_normalize(key
, -1, G_NORMALIZE_DEFAULT
);
2738 enteredstring
= g_utf8_casefold(tmp
, -1);
2741 nomarkup
= purple_markup_strip_html(withmarkup
);
2742 tmp
= g_utf8_normalize(nomarkup
, -1, G_NORMALIZE_DEFAULT
);
2744 normalized
= g_utf8_casefold(tmp
, -1);
2747 if (purple_str_has_prefix(normalized
, enteredstring
))
2750 g_free(enteredstring
);
2756 /* Use Pango to separate by words. */
2757 len
= g_utf8_strlen(normalized
, -1);
2758 log_attrs
= g_new(PangoLogAttr
, len
+ 1);
2760 pango_get_log_attrs(normalized
, strlen(normalized
), -1, NULL
, log_attrs
, len
+ 1);
2764 for (i
= 0; i
< (len
- 1) ; i
++)
2766 if (log_attrs
[i
].is_word_start
&&
2767 purple_str_has_prefix(word
, enteredstring
))
2772 word
= g_utf8_next_char(word
);
2776 /* The non-Pango version. */
2780 while (word
[0] != '\0')
2782 gunichar c
= g_utf8_get_char(word
);
2783 if (!g_unichar_isalnum(c
))
2785 word
= g_utf8_find_next_char(word
, NULL
);
2786 if (purple_str_has_prefix(word
, enteredstring
))
2793 word
= g_utf8_find_next_char(word
, NULL
);
2798 g_free(enteredstring
);
2805 gboolean
pidgin_gdk_pixbuf_is_opaque(GdkPixbuf
*pixbuf
) {
2806 int height
, rowstride
, i
;
2807 unsigned char *pixels
;
2810 if (!gdk_pixbuf_get_has_alpha(pixbuf
))
2813 height
= gdk_pixbuf_get_height (pixbuf
);
2814 rowstride
= gdk_pixbuf_get_rowstride (pixbuf
);
2815 pixels
= gdk_pixbuf_get_pixels (pixbuf
);
2818 for (i
= 3; i
< rowstride
; i
+=4) {
2823 for (i
= 1; i
< height
- 1; i
++) {
2824 row
= pixels
+ (i
* rowstride
);
2825 if (row
[3] < 0xfe || row
[rowstride
- 1] < 0xfe) {
2830 row
= pixels
+ ((height
- 1) * rowstride
);
2831 for (i
= 3; i
< rowstride
; i
+= 4) {
2839 void pidgin_gdk_pixbuf_make_round(GdkPixbuf
*pixbuf
) {
2840 int width
, height
, rowstride
;
2842 if (!gdk_pixbuf_get_has_alpha(pixbuf
))
2844 width
= gdk_pixbuf_get_width(pixbuf
);
2845 height
= gdk_pixbuf_get_height(pixbuf
);
2846 rowstride
= gdk_pixbuf_get_rowstride(pixbuf
);
2847 pixels
= gdk_pixbuf_get_pixels(pixbuf
);
2849 if (width
< 6 || height
< 6)
2855 pixels
[rowstride
+ 3] = 0x80;
2856 pixels
[rowstride
* 2 + 3] = 0xC0;
2859 pixels
[width
* 4 - 1] = 0;
2860 pixels
[width
* 4 - 5] = 0x80;
2861 pixels
[width
* 4 - 9] = 0xC0;
2862 pixels
[rowstride
+ (width
* 4) - 1] = 0x80;
2863 pixels
[(2 * rowstride
) + (width
* 4) - 1] = 0xC0;
2866 pixels
[(height
- 1) * rowstride
+ 3] = 0;
2867 pixels
[(height
- 1) * rowstride
+ 7] = 0x80;
2868 pixels
[(height
- 1) * rowstride
+ 11] = 0xC0;
2869 pixels
[(height
- 2) * rowstride
+ 3] = 0x80;
2870 pixels
[(height
- 3) * rowstride
+ 3] = 0xC0;
2873 pixels
[height
* rowstride
- 1] = 0;
2874 pixels
[(height
- 1) * rowstride
- 1] = 0x80;
2875 pixels
[(height
- 2) * rowstride
- 1] = 0xC0;
2876 pixels
[height
* rowstride
- 5] = 0x80;
2877 pixels
[height
* rowstride
- 9] = 0xC0;
2880 const char *pidgin_get_dim_grey_string(GtkWidget
*widget
) {
2881 static char dim_grey_string
[8] = "";
2887 style
= gtk_widget_get_style(widget
);
2891 snprintf(dim_grey_string
, sizeof(dim_grey_string
), "#%02x%02x%02x",
2892 style
->text_aa
[GTK_STATE_NORMAL
].red
>> 8,
2893 style
->text_aa
[GTK_STATE_NORMAL
].green
>> 8,
2894 style
->text_aa
[GTK_STATE_NORMAL
].blue
>> 8);
2895 return dim_grey_string
;
2899 combo_box_changed_cb(GtkComboBox
*combo_box
, GtkEntry
*entry
)
2901 char *text
= gtk_combo_box_get_active_text(combo_box
);
2902 gtk_entry_set_text(entry
, text
? text
: "");
2907 entry_key_pressed_cb(GtkWidget
*entry
, GdkEventKey
*key
, GtkComboBox
*combo
)
2909 if (key
->keyval
== GDK_Down
|| key
->keyval
== GDK_Up
) {
2910 gtk_combo_box_popup(combo
);
2917 pidgin_text_combo_box_entry_new(const char *default_item
, GList
*items
)
2919 GtkComboBox
*ret
= NULL
;
2920 GtkWidget
*the_entry
= NULL
;
2922 ret
= GTK_COMBO_BOX(gtk_combo_box_entry_new_text());
2923 the_entry
= gtk_entry_new();
2924 gtk_container_add(GTK_CONTAINER(ret
), the_entry
);
2927 gtk_entry_set_text(GTK_ENTRY(the_entry
), default_item
);
2929 for (; items
!= NULL
; items
= items
->next
) {
2930 char *text
= items
->data
;
2932 gtk_combo_box_append_text(ret
, text
);
2935 g_signal_connect(G_OBJECT(ret
), "changed", (GCallback
)combo_box_changed_cb
, the_entry
);
2936 g_signal_connect_after(G_OBJECT(the_entry
), "key-press-event", G_CALLBACK(entry_key_pressed_cb
), ret
);
2938 return GTK_WIDGET(ret
);
2941 const char *pidgin_text_combo_box_entry_get_text(GtkWidget
*widget
)
2943 return gtk_entry_get_text(GTK_ENTRY(GTK_BIN((widget
))->child
));
2946 void pidgin_text_combo_box_entry_set_text(GtkWidget
*widget
, const char *text
)
2948 gtk_entry_set_text(GTK_ENTRY(GTK_BIN((widget
))->child
), (text
));
2952 pidgin_add_widget_to_vbox(GtkBox
*vbox
, const char *widget_label
, GtkSizeGroup
*sg
, GtkWidget
*widget
, gboolean expand
, GtkWidget
**p_label
)
2955 GtkWidget
*label
= NULL
;
2958 hbox
= gtk_hbox_new(FALSE
, 5);
2959 gtk_widget_show(hbox
);
2960 gtk_box_pack_start(vbox
, hbox
, FALSE
, FALSE
, 0);
2962 label
= gtk_label_new_with_mnemonic(widget_label
);
2963 gtk_widget_show(label
);
2965 gtk_misc_set_alignment(GTK_MISC(label
), 0, 0.5);
2966 gtk_size_group_add_widget(sg
, label
);
2968 gtk_box_pack_start(GTK_BOX(hbox
), label
, FALSE
, FALSE
, 0);
2970 hbox
= GTK_WIDGET(vbox
);
2973 gtk_widget_show(widget
);
2974 gtk_box_pack_start(GTK_BOX(hbox
), widget
, expand
, TRUE
, 0);
2976 gtk_label_set_mnemonic_widget(GTK_LABEL(label
), widget
);
2977 pidgin_set_accessible_label (widget
, label
);
2985 gboolean
pidgin_auto_parent_window(GtkWidget
*widget
)
2988 /* This looks at the most recent window that received focus, and makes
2989 * that the parent window. */
2991 static GdkAtom _WindowTime
= GDK_NONE
;
2992 static GdkAtom _Cardinal
= GDK_NONE
;
2993 GList
*windows
= NULL
;
2994 GtkWidget
*parent
= NULL
;
2995 time_t window_time
= 0;
2997 windows
= gtk_window_list_toplevels();
2999 if (_WindowTime
== GDK_NONE
) {
3000 _WindowTime
= gdk_x11_xatom_to_atom(gdk_x11_get_xatom_by_name("_NET_WM_USER_TIME"));
3002 if (_Cardinal
== GDK_NONE
) {
3003 _Cardinal
= gdk_atom_intern("CARDINAL", FALSE
);
3007 GtkWidget
*window
= windows
->data
;
3008 guchar
*data
= NULL
;
3012 windows
= g_list_delete_link(windows
, windows
);
3014 if (window
== widget
||
3015 !GTK_WIDGET_VISIBLE(window
))
3018 if (!gdk_property_get(window
->window
, _WindowTime
, _Cardinal
, 0, sizeof(time_t), FALSE
,
3019 NULL
, NULL
, &al
, &data
))
3021 value
= *(time_t *)data
;
3022 if (window_time
< value
) {
3023 window_time
= value
;
3029 g_list_free(windows
);
3031 if (!gtk_get_current_event() && gtk_window_has_toplevel_focus(GTK_WINDOW(parent
))) {
3032 /* The window is in focus, and the new window was not triggered by a keypress/click
3033 * event. So do not set it transient, to avoid focus stealing and all that.
3037 gtk_window_set_transient_for(GTK_WINDOW(widget
), GTK_WINDOW(parent
));
3043 /* This finds the currently active window and makes that the parent window. */
3044 GList
*windows
= NULL
;
3045 GtkWidget
*parent
= NULL
;
3046 GdkEvent
*event
= gtk_get_current_event();
3047 GdkWindow
*menu
= NULL
;
3050 /* The window was not triggered by a user action. */
3053 /* We need to special case events from a popup menu. */
3054 if (event
->type
== GDK_BUTTON_RELEASE
) {
3055 /* XXX: Neither of the following works:
3056 menu = event->button.window;
3057 menu = gdk_window_get_parent(event->button.window);
3058 menu = gdk_window_get_toplevel(event->button.window);
3060 } else if (event
->type
== GDK_KEY_PRESS
)
3061 menu
= event
->key
.window
;
3063 windows
= gtk_window_list_toplevels();
3065 GtkWidget
*window
= windows
->data
;
3066 windows
= g_list_delete_link(windows
, windows
);
3068 if (window
== widget
||
3069 !GTK_WIDGET_VISIBLE(window
)) {
3073 if (gtk_window_has_toplevel_focus(GTK_WINDOW(window
)) ||
3074 (menu
&& menu
== window
->window
)) {
3080 g_list_free(windows
);
3082 gtk_window_set_transient_for(GTK_WINDOW(widget
), GTK_WINDOW(parent
));
3089 static GObject
*pidgin_pixbuf_from_data_helper(const guchar
*buf
, gsize count
, gboolean animated
)
3092 GdkPixbufLoader
*loader
;
3093 GError
*error
= NULL
;
3095 loader
= gdk_pixbuf_loader_new();
3097 if (!gdk_pixbuf_loader_write(loader
, buf
, count
, &error
) || error
) {
3098 purple_debug_warning("gtkutils", "gdk_pixbuf_loader_write() "
3099 "failed with size=%zu: %s\n", count
,
3100 error
? error
->message
: "(no error message)");
3102 g_error_free(error
);
3103 g_object_unref(G_OBJECT(loader
));
3107 if (!gdk_pixbuf_loader_close(loader
, &error
) || error
) {
3108 purple_debug_warning("gtkutils", "gdk_pixbuf_loader_close() "
3109 "failed for image of size %zu: %s\n", count
,
3110 error
? error
->message
: "(no error message)");
3112 g_error_free(error
);
3113 g_object_unref(G_OBJECT(loader
));
3118 pixbuf
= G_OBJECT(gdk_pixbuf_loader_get_animation(loader
));
3120 pixbuf
= G_OBJECT(gdk_pixbuf_loader_get_pixbuf(loader
));
3122 purple_debug_warning("gtkutils", "%s() returned NULL for image "
3124 animated
? "gdk_pixbuf_loader_get_animation"
3125 : "gdk_pixbuf_loader_get_pixbuf", count
);
3126 g_object_unref(G_OBJECT(loader
));
3130 g_object_ref(pixbuf
);
3131 g_object_unref(G_OBJECT(loader
));
3136 GdkPixbuf
*pidgin_pixbuf_from_data(const guchar
*buf
, gsize count
)
3138 return GDK_PIXBUF(pidgin_pixbuf_from_data_helper(buf
, count
, FALSE
));
3141 GdkPixbufAnimation
*pidgin_pixbuf_anim_from_data(const guchar
*buf
, gsize count
)
3143 return GDK_PIXBUF_ANIMATION(pidgin_pixbuf_from_data_helper(buf
, count
, TRUE
));
3146 GdkPixbuf
*pidgin_pixbuf_from_imgstore(PurpleStoredImage
*image
)
3148 return pidgin_pixbuf_from_data(purple_imgstore_get_data(image
),
3149 purple_imgstore_get_size(image
));
3152 GdkPixbuf
*pidgin_pixbuf_new_from_file(const gchar
*filename
)
3155 GError
*error
= NULL
;
3157 pixbuf
= gdk_pixbuf_new_from_file(filename
, &error
);
3158 if (!pixbuf
|| error
) {
3159 purple_debug_warning("gtkutils", "gdk_pixbuf_new_from_file() "
3160 "returned %s for file %s: %s\n",
3161 pixbuf
? "something" : "nothing",
3163 error
? error
->message
: "(no error message)");
3165 g_error_free(error
);
3167 g_object_unref(G_OBJECT(pixbuf
));
3174 GdkPixbuf
*pidgin_pixbuf_new_from_file_at_size(const char *filename
, int width
, int height
)
3177 GError
*error
= NULL
;
3179 pixbuf
= gdk_pixbuf_new_from_file_at_size(filename
,
3180 width
, height
, &error
);
3181 if (!pixbuf
|| error
) {
3182 purple_debug_warning("gtkutils", "gdk_pixbuf_new_from_file_at_size() "
3183 "returned %s for file %s: %s\n",
3184 pixbuf
? "something" : "nothing",
3186 error
? error
->message
: "(no error message)");
3188 g_error_free(error
);
3190 g_object_unref(G_OBJECT(pixbuf
));
3197 GdkPixbuf
*pidgin_pixbuf_new_from_file_at_scale(const char *filename
, int width
, int height
, gboolean preserve_aspect_ratio
)
3200 GError
*error
= NULL
;
3202 pixbuf
= gdk_pixbuf_new_from_file_at_scale(filename
,
3203 width
, height
, preserve_aspect_ratio
, &error
);
3204 if (!pixbuf
|| error
) {
3205 purple_debug_warning("gtkutils", "gdk_pixbuf_new_from_file_at_scale() "
3206 "returned %s for file %s: %s\n",
3207 pixbuf
? "something" : "nothing",
3209 error
? error
->message
: "(no error message)");
3211 g_error_free(error
);
3213 g_object_unref(G_OBJECT(pixbuf
));
3220 static void url_copy(GtkWidget
*w
, gchar
*url
)
3222 GtkClipboard
*clipboard
;
3224 clipboard
= gtk_widget_get_clipboard(w
, GDK_SELECTION_PRIMARY
);
3225 gtk_clipboard_set_text(clipboard
, url
, -1);
3227 clipboard
= gtk_widget_get_clipboard(w
, GDK_SELECTION_CLIPBOARD
);
3228 gtk_clipboard_set_text(clipboard
, url
, -1);
3232 link_context_menu(GtkIMHtml
*imhtml
, GtkIMHtmlLink
*link
, GtkWidget
*menu
)
3234 GtkWidget
*img
, *item
;
3237 url
= gtk_imhtml_link_get_url(link
);
3240 img
= gtk_image_new_from_stock(GTK_STOCK_JUMP_TO
, GTK_ICON_SIZE_MENU
);
3241 item
= gtk_image_menu_item_new_with_mnemonic(_("_Open Link"));
3242 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item
), img
);
3243 g_signal_connect_swapped(G_OBJECT(item
), "activate", G_CALLBACK(gtk_imhtml_link_activate
), link
);
3244 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
3246 /* Copy Link Location */
3247 img
= gtk_image_new_from_stock(GTK_STOCK_COPY
, GTK_ICON_SIZE_MENU
);
3248 item
= gtk_image_menu_item_new_with_mnemonic(_("_Copy Link Location"));
3249 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item
), img
);
3250 g_signal_connect(G_OBJECT(item
), "activate", G_CALLBACK(url_copy
), (gpointer
)url
);
3251 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
3257 copy_email_address(GtkIMHtml
*imhtml
, GtkIMHtmlLink
*link
, GtkWidget
*menu
)
3259 GtkWidget
*img
, *item
;
3262 #define MAILTOSIZE (sizeof("mailto:") - 1)
3264 text
= gtk_imhtml_link_get_url(link
);
3265 g_return_val_if_fail(text
&& strlen(text
) > MAILTOSIZE
, FALSE
);
3266 address
= (char*)text
+ MAILTOSIZE
;
3268 /* Copy Email Address */
3269 img
= gtk_image_new_from_stock(GTK_STOCK_COPY
, GTK_ICON_SIZE_MENU
);
3270 item
= gtk_image_menu_item_new_with_mnemonic(_("_Copy Email Address"));
3271 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item
), img
);
3272 g_signal_connect(G_OBJECT(item
), "activate", G_CALLBACK(url_copy
), address
);
3273 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
3279 file_open_uri(GtkIMHtml
*imhtml
, const char *uri
)
3281 /* Copied from gtkft.c:open_button_cb */
3283 /* If using Win32... */
3285 wchar_t *wc_filename
= g_utf8_to_utf16(
3286 uri
, -1, NULL
, NULL
, NULL
);
3288 code
= (int)ShellExecuteW(NULL
, NULL
, wc_filename
, NULL
, NULL
,
3291 g_free(wc_filename
);
3293 if (code
== SE_ERR_ASSOCINCOMPLETE
|| code
== SE_ERR_NOASSOC
)
3295 purple_notify_error(imhtml
, NULL
,
3296 _("There is no application configured to open this type of file."), NULL
);
3300 purple_notify_error(imhtml
, NULL
,
3301 _("An error occurred while opening the file."), NULL
);
3302 purple_debug_warning("gtkutils", "filename: %s; code: %d\n", uri
, code
);
3305 char *command
= NULL
;
3307 GError
*error
= NULL
;
3309 if (purple_running_gnome())
3311 char *escaped
= g_shell_quote(uri
);
3312 command
= g_strdup_printf("gnome-open %s", escaped
);
3315 else if (purple_running_kde())
3317 char *escaped
= g_shell_quote(uri
);
3319 if (purple_str_has_suffix(uri
, ".desktop"))
3320 command
= g_strdup_printf("kfmclient openURL %s 'text/plain'", escaped
);
3322 command
= g_strdup_printf("kfmclient openURL %s", escaped
);
3327 purple_notify_uri(NULL
, uri
);
3331 if (purple_program_is_valid(command
))
3334 if (!g_spawn_command_line_sync(command
, NULL
, NULL
, &exit_status
, &error
))
3336 tmp
= g_strdup_printf(_("Error launching %s: %s"),
3337 uri
, error
->message
);
3338 purple_notify_error(imhtml
, NULL
, _("Unable to open file."), tmp
);
3340 g_error_free(error
);
3342 if (exit_status
!= 0)
3344 char *primary
= g_strdup_printf(_("Error running %s"), command
);
3345 char *secondary
= g_strdup_printf(_("Process returned error code %d"),
3347 purple_notify_error(imhtml
, NULL
, primary
, secondary
);
3354 #define FILELINKSIZE (sizeof("file://") - 1)
3356 file_clicked_cb(GtkIMHtml
*imhtml
, GtkIMHtmlLink
*link
)
3358 const char *uri
= gtk_imhtml_link_get_url(link
) + FILELINKSIZE
;
3359 file_open_uri(imhtml
, uri
);
3364 open_containing_cb(GtkIMHtml
*imhtml
, const char *url
)
3366 char *dir
= g_path_get_dirname(url
+ FILELINKSIZE
);
3367 file_open_uri(imhtml
, dir
);
3373 file_context_menu(GtkIMHtml
*imhtml
, GtkIMHtmlLink
*link
, GtkWidget
*menu
)
3375 GtkWidget
*img
, *item
;
3378 url
= gtk_imhtml_link_get_url(link
);
3381 img
= gtk_image_new_from_stock(GTK_STOCK_JUMP_TO
, GTK_ICON_SIZE_MENU
);
3382 item
= gtk_image_menu_item_new_with_mnemonic(_("_Open File"));
3383 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item
), img
);
3384 g_signal_connect_swapped(G_OBJECT(item
), "activate", G_CALLBACK(gtk_imhtml_link_activate
), link
);
3385 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
3387 /* Open Containing Directory */
3388 img
= gtk_image_new_from_stock(GTK_STOCK_DIRECTORY
, GTK_ICON_SIZE_MENU
);
3389 item
= gtk_image_menu_item_new_with_mnemonic(_("Open _Containing Directory"));
3390 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item
), img
);
3392 g_signal_connect(G_OBJECT(item
), "activate", G_CALLBACK(open_containing_cb
), (gpointer
)url
);
3393 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
3398 #define AUDIOLINKSIZE (sizeof("audio://") - 1)
3400 audio_clicked_cb(GtkIMHtml
*imhtml
, GtkIMHtmlLink
*link
)
3403 PidginConversation
*conv
= g_object_get_data(G_OBJECT(imhtml
), "gtkconv");
3404 if (!conv
) /* no playback in debug window */
3406 uri
= gtk_imhtml_link_get_url(link
) + AUDIOLINKSIZE
;
3407 purple_sound_play_file(uri
, NULL
);
3412 savefile_write_cb(gpointer user_data
, char *file
)
3414 char *temp_file
= user_data
;
3417 GError
*error
= NULL
;
3419 if (!g_file_get_contents(temp_file
, &contents
, &length
, &error
)) {
3420 purple_debug_error("gtkutils", "Unable to read contents of %s: %s\n",
3421 temp_file
, error
->message
);
3422 g_error_free(error
);
3426 if (!purple_util_write_data_to_file_absolute(file
, contents
, length
)) {
3427 purple_debug_error("gtkutils", "Unable to write contents to %s\n",
3433 save_file_cb(GtkWidget
*item
, const char *url
)
3435 PidginConversation
*conv
= g_object_get_data(G_OBJECT(item
), "gtkconv");
3438 purple_request_file(conv
->active_conv
, _("Save File"), NULL
, TRUE
,
3439 G_CALLBACK(savefile_write_cb
), NULL
,
3440 conv
->active_conv
->account
, NULL
, conv
->active_conv
,
3446 audio_context_menu(GtkIMHtml
*imhtml
, GtkIMHtmlLink
*link
, GtkWidget
*menu
)
3448 GtkWidget
*img
, *item
;
3450 PidginConversation
*conv
= g_object_get_data(G_OBJECT(imhtml
), "gtkconv");
3451 if (!conv
) /* No menu in debug window */
3454 url
= gtk_imhtml_link_get_url(link
);
3457 img
= gtk_image_new_from_stock(GTK_STOCK_MEDIA_PLAY
, GTK_ICON_SIZE_MENU
);
3458 item
= gtk_image_menu_item_new_with_mnemonic(_("_Play Sound"));
3459 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item
), img
);
3461 g_signal_connect_swapped(G_OBJECT(item
), "activate", G_CALLBACK(gtk_imhtml_link_activate
), link
);
3462 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
3465 img
= gtk_image_new_from_stock(GTK_STOCK_SAVE
, GTK_ICON_SIZE_MENU
);
3466 item
= gtk_image_menu_item_new_with_mnemonic(_("_Save File"));
3467 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item
), img
);
3468 g_signal_connect(G_OBJECT(item
), "activate", G_CALLBACK(save_file_cb
), (gpointer
)(url
+AUDIOLINKSIZE
));
3469 g_object_set_data(G_OBJECT(item
), "gtkconv", conv
);
3470 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
3475 /* XXX: The following two functions are for demonstration purposes only! */
3477 open_dialog(GtkIMHtml
*imhtml
, GtkIMHtmlLink
*link
)
3482 url
= gtk_imhtml_link_get_url(link
);
3483 if (!url
|| strlen(url
) < sizeof("open://"))
3486 str
= url
+ sizeof("open://") - 1;
3488 if (strcmp(str
, "accounts") == 0)
3489 pidgin_accounts_window_show();
3490 else if (strcmp(str
, "prefs") == 0)
3491 pidgin_prefs_show();
3498 dummy(GtkIMHtml
*imhtml
, GtkIMHtmlLink
*link
, GtkWidget
*menu
)
3504 register_gnome_url_handlers(void)
3511 tmp
= g_find_program_in_path("gconftool-2");
3518 if (!g_spawn_command_line_sync("gconftool-2 --all-dirs /desktop/gnome/url-handlers",
3519 &tmp
, &err
, NULL
, NULL
))
3523 g_return_val_if_reached(FALSE
);
3528 for (c
= start
= tmp
; *c
; c
++)
3530 /* Skip leading spaces. */
3531 if (c
== start
&& *c
== ' ')
3533 else if (*c
== '\n')
3536 if (g_str_has_prefix(start
, "/desktop/gnome/url-handlers/"))
3542 /* If there is an enabled boolean, honor it. */
3543 cmd
= g_strdup_printf("gconftool-2 -g %s/enabled", start
);
3544 if (g_spawn_command_line_sync(cmd
, &tmp2
, &err
, NULL
, NULL
))
3548 if (!strcmp(tmp2
, "false\n"))
3559 start
+= sizeof("/desktop/gnome/url-handlers/") - 1;
3561 protocol
= g_strdup_printf("%s:", start
);
3562 registered_url_handlers
= g_slist_prepend(registered_url_handlers
, protocol
);
3563 gtk_imhtml_class_register_protocol(protocol
, url_clicked_cb
, link_context_menu
);
3570 return (registered_url_handlers
!= NULL
);
3575 winpidgin_register_win32_url_handlers(void)
3578 LONG ret
= ERROR_SUCCESS
;
3581 DWORD nameSize
= 256;
3583 ret
= RegEnumKeyExW(HKEY_CLASSES_ROOT
, idx
++, start
, &nameSize
,
3584 NULL
, NULL
, NULL
, NULL
);
3585 if (ret
== ERROR_SUCCESS
) {
3586 HKEY reg_key
= NULL
;
3587 ret
= RegOpenKeyExW(HKEY_CLASSES_ROOT
, start
, 0, KEY_READ
, ®_key
);
3588 if (ret
== ERROR_SUCCESS
) {
3589 ret
= RegQueryValueExW(reg_key
, L
"URL Protocol", NULL
, NULL
, NULL
, NULL
);
3590 if (ret
== ERROR_SUCCESS
) {
3591 gchar
*utf8
= g_utf16_to_utf8(start
, -1, NULL
, NULL
, NULL
);
3592 gchar
*protocol
= g_strdup_printf("%s:", utf8
);
3594 registered_url_handlers
= g_slist_prepend(registered_url_handlers
, protocol
);
3595 /* We still pass everything to the "http" "open" handler for security reasons */
3596 gtk_imhtml_class_register_protocol(protocol
, url_clicked_cb
, link_context_menu
);
3598 RegCloseKey(reg_key
);
3600 ret
= ERROR_SUCCESS
;
3602 } while (ret
== ERROR_SUCCESS
);
3604 if (ret
!= ERROR_NO_MORE_ITEMS
)
3605 purple_debug_error("winpidgin", "Error iterating HKEY_CLASSES_ROOT subkeys: %ld\n",
3611 pidgin_make_scrollable(GtkWidget
*child
, GtkPolicyType hscrollbar_policy
, GtkPolicyType vscrollbar_policy
, GtkShadowType shadow_type
, int width
, int height
)
3613 GtkWidget
*sw
= gtk_scrolled_window_new(NULL
, NULL
);
3616 gtk_widget_show(sw
);
3617 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw
), hscrollbar_policy
, vscrollbar_policy
);
3618 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw
), shadow_type
);
3619 if (width
!= -1 || height
!= -1)
3620 gtk_widget_set_size_request(sw
, width
, height
);
3622 if (GTK_WIDGET_GET_CLASS(child
)->set_scroll_adjustments_signal
)
3623 gtk_container_add(GTK_CONTAINER(sw
), child
);
3625 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(sw
), child
);
3633 void pidgin_utils_init(void)
3635 gtk_imhtml_class_register_protocol("http://", url_clicked_cb
, link_context_menu
);
3636 gtk_imhtml_class_register_protocol("https://", url_clicked_cb
, link_context_menu
);
3637 gtk_imhtml_class_register_protocol("ftp://", url_clicked_cb
, link_context_menu
);
3638 gtk_imhtml_class_register_protocol("gopher://", url_clicked_cb
, link_context_menu
);
3639 gtk_imhtml_class_register_protocol("mailto:", url_clicked_cb
, copy_email_address
);
3641 gtk_imhtml_class_register_protocol("file://", file_clicked_cb
, file_context_menu
);
3642 gtk_imhtml_class_register_protocol("audio://", audio_clicked_cb
, audio_context_menu
);
3644 /* Example custom URL handler. */
3645 gtk_imhtml_class_register_protocol("open://", open_dialog
, dummy
);
3647 /* If we're under GNOME, try registering the system URL handlers. */
3648 if (purple_running_gnome())
3649 register_gnome_url_handlers();
3651 /* Used to make small buttons */
3652 gtk_rc_parse_string("style \"pidgin-small-close-button\"\n"
3654 "GtkWidget::focus-padding = 0\n"
3655 "GtkWidget::focus-line-width = 0\n"
3658 "GtkContainer::border-width = 0\n"
3659 "GtkButton::inner-border = {0, 0, 0, 0}\n"
3660 "GtkButton::default-border = {0, 0, 0, 0}\n"
3662 "widget \"*.pidgin-small-close-button\" style \"pidgin-small-close-button\"");
3665 winpidgin_register_win32_url_handlers();
3670 void pidgin_utils_uninit(void)
3672 gtk_imhtml_class_register_protocol("open://", NULL
, NULL
);
3674 /* If we have GNOME handlers registered, unregister them. */
3675 if (registered_url_handlers
)
3678 for (l
= registered_url_handlers
; l
; l
= l
->next
)
3680 gtk_imhtml_class_register_protocol((char *)l
->data
, NULL
, NULL
);
3683 g_slist_free(registered_url_handlers
);
3684 registered_url_handlers
= NULL
;
3688 gtk_imhtml_class_register_protocol("audio://", NULL
, NULL
);
3689 gtk_imhtml_class_register_protocol("file://", NULL
, NULL
);
3691 gtk_imhtml_class_register_protocol("http://", NULL
, NULL
);
3692 gtk_imhtml_class_register_protocol("https://", NULL
, NULL
);
3693 gtk_imhtml_class_register_protocol("ftp://", NULL
, NULL
);
3694 gtk_imhtml_class_register_protocol("mailto:", NULL
, NULL
);
3695 gtk_imhtml_class_register_protocol("gopher://", NULL
, NULL
);