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
30 # include <X11/Xlib.h>
38 # include <gtkspell/gtkspell.h>
44 #include <gdk/gdkkeysyms.h>
46 #include "conversation.h"
48 #include "desktopitem.h"
58 #include "gtkdialogs.h"
59 #include "gtkimhtml.h"
60 #include "gtkimhtmltoolbar.h"
61 #include "pidginstock.h"
62 #include "gtkthemes.h"
70 static guint accels_save_timer
= 0;
73 url_clicked_idle_cb(gpointer data
)
75 purple_notify_uri(NULL
, data
);
81 url_clicked_cb(GtkWidget
*w
, const char *uri
)
83 g_idle_add(url_clicked_idle_cb
, g_strdup(uri
));
86 static GtkIMHtmlFuncs gtkimhtml_cbs
= {
87 (GtkIMHtmlGetImageFunc
)purple_imgstore_find_by_id
,
88 (GtkIMHtmlGetImageDataFunc
)purple_imgstore_get_data
,
89 (GtkIMHtmlGetImageSizeFunc
)purple_imgstore_get_size
,
90 (GtkIMHtmlGetImageFilenameFunc
)purple_imgstore_get_filename
,
91 purple_imgstore_ref_by_id
,
92 purple_imgstore_unref_by_id
,
96 pidgin_setup_imhtml(GtkWidget
*imhtml
)
98 PangoFontDescription
*desc
= NULL
;
99 g_return_if_fail(imhtml
!= NULL
);
100 g_return_if_fail(GTK_IS_IMHTML(imhtml
));
102 g_signal_connect(G_OBJECT(imhtml
), "url_clicked",
103 G_CALLBACK(url_clicked_cb
), NULL
);
105 pidgin_themes_smiley_themeize(imhtml
);
107 gtk_imhtml_set_funcs(GTK_IMHTML(imhtml
), >kimhtml_cbs
);
109 if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/use_theme_font")) {
110 const char *font
= purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/custom_font");
111 desc
= pango_font_description_from_string(font
);
112 } else if (purple_running_gnome()) {
113 /* Use the GNOME "document" font, if applicable */
116 if ((path
= g_find_program_in_path("gconftool-2"))) {
118 if (!g_spawn_command_line_sync(
119 "gconftool-2 -g /desktop/gnome/interface/document_font_name",
120 &font
, NULL
, NULL
, NULL
))
123 desc
= pango_font_description_from_string(font
);
128 gtk_widget_modify_font(imhtml
, desc
);
129 pango_font_description_free(desc
);
134 pidgin_create_window(const char *title
, guint border_width
, const char *role
, gboolean resizable
)
136 GtkWindow
*wnd
= NULL
;
138 wnd
= GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL
));
140 gtk_window_set_title(wnd
, title
);
143 gtk_window_set_title(wnd
, PIDGIN_ALERT_TITLE
);
145 gtk_container_set_border_width(GTK_CONTAINER(wnd
), border_width
);
147 gtk_window_set_role(wnd
, role
);
148 gtk_window_set_resizable(wnd
, resizable
);
150 return GTK_WIDGET(wnd
);
154 pidgin_create_imhtml(gboolean editable
, GtkWidget
**imhtml_ret
, GtkWidget
**toolbar_ret
, GtkWidget
**sw_ret
)
160 GtkWidget
*toolbar
= NULL
;
163 frame
= gtk_frame_new(NULL
);
164 gtk_frame_set_shadow_type(GTK_FRAME(frame
), GTK_SHADOW_IN
);
166 vbox
= gtk_vbox_new(FALSE
, 0);
167 gtk_container_add(GTK_CONTAINER(frame
), vbox
);
168 gtk_widget_show(vbox
);
171 toolbar
= gtk_imhtmltoolbar_new();
172 gtk_box_pack_start(GTK_BOX(vbox
), toolbar
, FALSE
, FALSE
, 0);
173 gtk_widget_show(toolbar
);
175 sep
= gtk_hseparator_new();
176 gtk_box_pack_start(GTK_BOX(vbox
), sep
, FALSE
, FALSE
, 0);
177 g_signal_connect_swapped(G_OBJECT(toolbar
), "show", G_CALLBACK(gtk_widget_show
), sep
);
178 g_signal_connect_swapped(G_OBJECT(toolbar
), "hide", G_CALLBACK(gtk_widget_hide
), sep
);
179 gtk_widget_show(sep
);
182 sw
= gtk_scrolled_window_new(NULL
, NULL
);
183 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw
),
184 GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
);
185 gtk_box_pack_start(GTK_BOX(vbox
), sw
, TRUE
, TRUE
, 0);
188 imhtml
= gtk_imhtml_new(NULL
, NULL
);
189 gtk_imhtml_set_editable(GTK_IMHTML(imhtml
), editable
);
190 gtk_imhtml_set_format_functions(GTK_IMHTML(imhtml
), GTK_IMHTML_ALL
^ GTK_IMHTML_IMAGE
);
191 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(imhtml
), GTK_WRAP_WORD_CHAR
);
193 if (editable
&& purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/spellcheck"))
194 pidgin_setup_gtkspell(GTK_TEXT_VIEW(imhtml
));
196 gtk_widget_show(imhtml
);
199 gtk_imhtmltoolbar_attach(GTK_IMHTMLTOOLBAR(toolbar
), imhtml
);
200 gtk_imhtmltoolbar_associate_smileys(GTK_IMHTMLTOOLBAR(toolbar
), "default");
202 pidgin_setup_imhtml(imhtml
);
204 gtk_container_add(GTK_CONTAINER(sw
), imhtml
);
206 if (imhtml_ret
!= NULL
)
207 *imhtml_ret
= imhtml
;
209 if (editable
&& (toolbar_ret
!= NULL
))
210 *toolbar_ret
= toolbar
;
219 pidgin_set_sensitive_if_input(GtkWidget
*entry
, GtkWidget
*dialog
)
221 const char *text
= gtk_entry_get_text(GTK_ENTRY(entry
));
222 gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog
), GTK_RESPONSE_OK
,
227 pidgin_toggle_sensitive(GtkWidget
*widget
, GtkWidget
*to_toggle
)
229 gboolean sensitivity
;
231 if (to_toggle
== NULL
)
234 sensitivity
= GTK_WIDGET_IS_SENSITIVE(to_toggle
);
236 gtk_widget_set_sensitive(to_toggle
, !sensitivity
);
240 pidgin_toggle_sensitive_array(GtkWidget
*w
, GPtrArray
*data
)
242 gboolean sensitivity
;
246 for (i
=0; i
< data
->len
; i
++) {
247 element
= g_ptr_array_index(data
,i
);
251 sensitivity
= GTK_WIDGET_IS_SENSITIVE(element
);
253 gtk_widget_set_sensitive(element
, !sensitivity
);
258 pidgin_toggle_showhide(GtkWidget
*widget
, GtkWidget
*to_toggle
)
260 if (to_toggle
== NULL
)
263 if (GTK_WIDGET_VISIBLE(to_toggle
))
264 gtk_widget_hide(to_toggle
);
266 gtk_widget_show(to_toggle
);
269 GtkWidget
*pidgin_separator(GtkWidget
*menu
)
273 menuitem
= gtk_separator_menu_item_new();
274 gtk_widget_show(menuitem
);
275 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
279 GtkWidget
*pidgin_new_item(GtkWidget
*menu
, const char *str
)
284 menuitem
= gtk_menu_item_new();
286 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
287 gtk_widget_show(menuitem
);
289 label
= gtk_label_new(str
);
290 gtk_misc_set_alignment(GTK_MISC(label
), 0, 0.5);
291 gtk_label_set_pattern(GTK_LABEL(label
), "_");
292 gtk_container_add(GTK_CONTAINER(menuitem
), label
);
293 gtk_widget_show(label
);
294 /* FIXME: Go back and fix this
295 gtk_widget_add_accelerator(menuitem, "activate", accel, str[0],
296 GDK_MOD1_MASK, GTK_ACCEL_LOCKED);
298 pidgin_set_accessible_label (menuitem
, label
);
302 GtkWidget
*pidgin_new_check_item(GtkWidget
*menu
, const char *str
,
303 GtkSignalFunc sf
, gpointer data
, gboolean checked
)
306 menuitem
= gtk_check_menu_item_new_with_mnemonic(str
);
309 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
311 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem
), checked
);
314 g_signal_connect(G_OBJECT(menuitem
), "activate", sf
, data
);
316 gtk_widget_show_all(menuitem
);
322 pidgin_pixbuf_toolbar_button_from_stock(const char *icon
)
324 GtkWidget
*button
, *image
, *bbox
;
326 button
= gtk_toggle_button_new();
327 gtk_button_set_relief(GTK_BUTTON(button
), GTK_RELIEF_NONE
);
329 bbox
= gtk_vbox_new(FALSE
, 0);
331 gtk_container_add (GTK_CONTAINER(button
), bbox
);
333 image
= gtk_image_new_from_stock(icon
, gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
));
334 gtk_box_pack_start(GTK_BOX(bbox
), image
, FALSE
, FALSE
, 0);
336 gtk_widget_show_all(bbox
);
342 pidgin_pixbuf_button_from_stock(const char *text
, const char *icon
,
343 PidginButtonOrientation style
)
345 GtkWidget
*button
, *image
, *label
, *bbox
, *ibox
, *lbox
= NULL
;
347 button
= gtk_button_new();
349 if (style
== PIDGIN_BUTTON_HORIZONTAL
) {
350 bbox
= gtk_hbox_new(FALSE
, 0);
351 ibox
= gtk_hbox_new(FALSE
, 0);
353 lbox
= gtk_hbox_new(FALSE
, 0);
355 bbox
= gtk_vbox_new(FALSE
, 0);
356 ibox
= gtk_vbox_new(FALSE
, 0);
358 lbox
= gtk_vbox_new(FALSE
, 0);
361 gtk_container_add(GTK_CONTAINER(button
), bbox
);
364 gtk_box_pack_start_defaults(GTK_BOX(bbox
), ibox
);
365 image
= gtk_image_new_from_stock(icon
, GTK_ICON_SIZE_BUTTON
);
366 gtk_box_pack_end(GTK_BOX(ibox
), image
, FALSE
, TRUE
, 0);
370 gtk_box_pack_start_defaults(GTK_BOX(bbox
), lbox
);
371 label
= gtk_label_new(NULL
);
372 gtk_label_set_text_with_mnemonic(GTK_LABEL(label
), text
);
373 gtk_label_set_mnemonic_widget(GTK_LABEL(label
), button
);
374 gtk_box_pack_start(GTK_BOX(lbox
), label
, FALSE
, TRUE
, 0);
375 pidgin_set_accessible_label (button
, label
);
378 gtk_widget_show_all(bbox
);
384 GtkWidget
*pidgin_new_item_from_stock(GtkWidget
*menu
, const char *str
, const char *icon
, GtkSignalFunc sf
, gpointer data
, guint accel_key
, guint accel_mods
, char *mod
)
394 menuitem
= gtk_menu_item_new_with_mnemonic(str
);
396 menuitem
= gtk_image_menu_item_new_with_mnemonic(str
);
399 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
402 g_signal_connect(G_OBJECT(menuitem
), "activate", sf
, data
);
405 image
= gtk_image_new_from_stock(icon
, gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
));
406 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem
), image
);
408 /* FIXME: this isn't right
410 label = gtk_label_new(mod);
411 gtk_box_pack_end(GTK_BOX(hbox), label, FALSE, FALSE, 2);
412 gtk_widget_show(label);
417 gtk_widget_add_accelerator(menuitem, "activate", accel, accel_key,
418 accel_mods, GTK_ACCEL_LOCKED);
422 gtk_widget_show_all(menuitem
);
428 pidgin_make_frame(GtkWidget
*parent
, const char *title
)
430 GtkWidget
*vbox
, *label
, *hbox
;
433 vbox
= gtk_vbox_new(FALSE
, PIDGIN_HIG_BOX_SPACE
);
434 gtk_box_pack_start(GTK_BOX(parent
), vbox
, FALSE
, FALSE
, 0);
435 gtk_widget_show(vbox
);
437 label
= gtk_label_new(NULL
);
439 labeltitle
= g_strdup_printf("<span weight=\"bold\">%s</span>", title
);
440 gtk_label_set_markup(GTK_LABEL(label
), labeltitle
);
443 gtk_misc_set_alignment(GTK_MISC(label
), 0, 0);
444 gtk_box_pack_start(GTK_BOX(vbox
), label
, FALSE
, FALSE
, 0);
445 gtk_widget_show(label
);
446 pidgin_set_accessible_label (vbox
, label
);
448 hbox
= gtk_hbox_new(FALSE
, PIDGIN_HIG_BOX_SPACE
);
449 gtk_box_pack_start (GTK_BOX (vbox
), hbox
, FALSE
, FALSE
, 0);
450 gtk_widget_show(hbox
);
452 label
= gtk_label_new(" ");
453 gtk_box_pack_start(GTK_BOX(hbox
), label
, FALSE
, FALSE
, 0);
454 gtk_widget_show(label
);
456 vbox
= gtk_vbox_new(FALSE
, PIDGIN_HIG_BOX_SPACE
);
457 gtk_box_pack_start(GTK_BOX(hbox
), vbox
, FALSE
, FALSE
, 0);
458 gtk_widget_show(vbox
);
464 aop_option_menu_get_selected(GtkWidget
*optmenu
, GtkWidget
**p_item
)
466 GtkWidget
*menu
= gtk_option_menu_get_menu(GTK_OPTION_MENU(optmenu
));
467 GtkWidget
*item
= gtk_menu_get_active(GTK_MENU(menu
));
470 return g_object_get_data(G_OBJECT(item
), "aop_per_item_data");
474 aop_menu_cb(GtkWidget
*optmenu
, GCallback cb
)
477 gpointer per_item_data
;
479 per_item_data
= aop_option_menu_get_selected(optmenu
, &item
);
482 ((void (*)(GtkWidget
*, gpointer
, gpointer
))cb
)(item
, per_item_data
, g_object_get_data(G_OBJECT(optmenu
), "user_data"));
487 aop_menu_item_new(GtkSizeGroup
*sg
, GdkPixbuf
*pixbuf
, const char *lbl
, gpointer per_item_data
, const char *data
)
494 item
= gtk_menu_item_new();
495 gtk_widget_show(item
);
497 hbox
= gtk_hbox_new(FALSE
, 4);
498 gtk_widget_show(hbox
);
500 /* Create the image */
502 image
= gtk_image_new();
504 image
= gtk_image_new_from_pixbuf(pixbuf
);
505 gtk_widget_show(image
);
508 gtk_size_group_add_widget(sg
, image
);
510 /* Create the label */
511 label
= gtk_label_new (lbl
);
512 gtk_widget_show (label
);
513 gtk_label_set_justify(GTK_LABEL(label
), GTK_JUSTIFY_LEFT
);
514 gtk_misc_set_alignment(GTK_MISC(label
), 0.0, 0.5);
516 gtk_container_add(GTK_CONTAINER(item
), hbox
);
517 gtk_box_pack_start(GTK_BOX(hbox
), image
, FALSE
, FALSE
, 0);
518 gtk_box_pack_start(GTK_BOX(hbox
), label
, TRUE
, TRUE
, 0);
520 g_object_set_data(G_OBJECT (item
), data
, per_item_data
);
521 g_object_set_data(G_OBJECT (item
), "aop_per_item_data", per_item_data
);
523 pidgin_set_accessible_label(item
, label
);
529 pidgin_create_prpl_icon_from_prpl(PurplePlugin
*prpl
, PidginPrplIconSize size
, PurpleAccount
*account
)
531 PurplePluginProtocolInfo
*prpl_info
;
532 const char *protoname
= NULL
;
534 char *filename
= NULL
;
537 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(prpl
);
538 if (prpl_info
->list_icon
== NULL
)
541 protoname
= prpl_info
->list_icon(account
, NULL
);
542 if (protoname
== NULL
)
546 * Status icons will be themeable too, and then it will look up
547 * protoname from the theme
549 tmp
= g_strconcat(protoname
, ".png", NULL
);
551 filename
= g_build_filename(DATADIR
, "pixmaps", "pidgin", "protocols",
552 size
== PIDGIN_PRPL_ICON_SMALL
? "16" :
553 size
== PIDGIN_PRPL_ICON_MEDIUM
? "22" : "48",
557 pixbuf
= gdk_pixbuf_new_from_file(filename
, NULL
);
564 aop_option_menu_new(AopMenu
*aop_menu
, GCallback cb
, gpointer user_data
)
568 optmenu
= gtk_option_menu_new();
569 gtk_widget_show(optmenu
);
570 gtk_option_menu_set_menu(GTK_OPTION_MENU(optmenu
), aop_menu
->menu
);
572 if (aop_menu
->default_item
!= -1)
573 gtk_option_menu_set_history(GTK_OPTION_MENU(optmenu
), aop_menu
->default_item
);
575 g_object_set_data_full(G_OBJECT(optmenu
), "aop_menu", aop_menu
, (GDestroyNotify
)g_free
);
576 g_object_set_data(G_OBJECT(optmenu
), "user_data", user_data
);
578 g_signal_connect(G_OBJECT(optmenu
), "changed", G_CALLBACK(aop_menu_cb
), cb
);
584 aop_option_menu_replace_menu(GtkWidget
*optmenu
, AopMenu
*new_aop_menu
)
586 if (gtk_option_menu_get_menu(GTK_OPTION_MENU(optmenu
)))
587 gtk_option_menu_remove_menu(GTK_OPTION_MENU(optmenu
));
589 gtk_option_menu_set_menu(GTK_OPTION_MENU(optmenu
), new_aop_menu
->menu
);
591 if (new_aop_menu
->default_item
!= -1)
592 gtk_option_menu_set_history(GTK_OPTION_MENU(optmenu
), new_aop_menu
->default_item
);
594 g_object_set_data_full(G_OBJECT(optmenu
), "aop_menu", new_aop_menu
, (GDestroyNotify
)g_free
);
598 aop_option_menu_select_by_data(GtkWidget
*optmenu
, gpointer data
)
603 for (idx
= 0, llItr
= GTK_MENU_SHELL(gtk_option_menu_get_menu(GTK_OPTION_MENU(optmenu
)))->children
;
605 llItr
= llItr
->next
, idx
++) {
606 if (data
== g_object_get_data(G_OBJECT(llItr
->data
), "aop_per_item_data")) {
607 gtk_option_menu_set_history(GTK_OPTION_MENU(optmenu
), idx
);
614 create_protocols_menu(const char *default_proto_id
)
616 AopMenu
*aop_menu
= NULL
;
617 PurplePluginProtocolInfo
*prpl_info
;
618 PurplePlugin
*plugin
;
619 GdkPixbuf
*pixbuf
= NULL
;
622 const char *gtalk_name
= NULL
;
625 aop_menu
= g_malloc0(sizeof(AopMenu
));
626 aop_menu
->default_item
= -1;
627 aop_menu
->menu
= gtk_menu_new();
628 gtk_widget_show(aop_menu
->menu
);
629 sg
= gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL
);
631 if (purple_find_prpl("prpl-jabber"))
632 gtalk_name
= _("Google Talk");
634 for (p
= purple_plugins_get_protocols(), i
= 0;
638 plugin
= (PurplePlugin
*)p
->data
;
639 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(plugin
);
641 if (gtalk_name
&& strcmp(gtalk_name
, plugin
->info
->name
) < 0) {
642 char *filename
= g_build_filename(DATADIR
, "pixmaps", "pidgin", "protocols",
643 "16", "google-talk.png", NULL
);
646 pixbuf
= gdk_pixbuf_new_from_file(filename
, NULL
);
649 gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu
->menu
),
650 item
= aop_menu_item_new(sg
, pixbuf
, gtalk_name
, "prpl-jabber", "protocol"));
651 g_object_set_data(G_OBJECT(item
), "fake", GINT_TO_POINTER(1));
654 g_object_unref(pixbuf
);
660 pixbuf
= pidgin_create_prpl_icon_from_prpl(plugin
, PIDGIN_PRPL_ICON_SMALL
, NULL
);
662 gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu
->menu
),
663 aop_menu_item_new(sg
, pixbuf
, plugin
->info
->name
, plugin
->info
->id
, "protocol"));
666 g_object_unref(pixbuf
);
668 if (default_proto_id
!= NULL
&& !strcmp(plugin
->info
->id
, default_proto_id
))
669 aop_menu
->default_item
= i
;
678 pidgin_protocol_option_menu_new(const char *id
, GCallback cb
,
681 return aop_option_menu_new(create_protocols_menu(id
), cb
, user_data
);
685 pidgin_protocol_option_menu_get_selected(GtkWidget
*optmenu
)
687 return (const char *)aop_option_menu_get_selected(optmenu
, NULL
);
691 pidgin_account_option_menu_get_selected(GtkWidget
*optmenu
)
693 return (PurpleAccount
*)aop_option_menu_get_selected(optmenu
, NULL
);
697 create_account_menu(PurpleAccount
*default_account
,
698 PurpleFilterAccountFunc filter_func
, gboolean show_all
)
700 AopMenu
*aop_menu
= NULL
;
701 PurpleAccount
*account
;
702 GdkPixbuf
*pixbuf
= NULL
;
710 list
= purple_accounts_get_all();
712 list
= purple_connections_get_all();
714 aop_menu
= g_malloc0(sizeof(AopMenu
));
715 aop_menu
->default_item
= -1;
716 aop_menu
->menu
= gtk_menu_new();
717 gtk_widget_show(aop_menu
->menu
);
718 sg
= gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL
);
720 for (p
= list
, i
= 0; p
!= NULL
; p
= p
->next
, i
++) {
721 PurplePlugin
*plugin
;
724 account
= (PurpleAccount
*)p
->data
;
726 PurpleConnection
*gc
= (PurpleConnection
*)p
->data
;
728 account
= purple_connection_get_account(gc
);
731 if (filter_func
&& !filter_func(account
)) {
736 plugin
= purple_find_prpl(purple_account_get_protocol_id(account
));
738 pixbuf
= pidgin_create_prpl_icon(account
, PIDGIN_PRPL_ICON_SMALL
);
741 if (purple_account_is_disconnected(account
) && show_all
&&
742 purple_connections_get_all())
743 gdk_pixbuf_saturate_and_pixelate(pixbuf
, pixbuf
, 0.0, FALSE
);
746 if (purple_account_get_alias(account
)) {
747 g_snprintf(buf
, sizeof(buf
), "%s (%s) (%s)",
748 purple_account_get_username(account
),
749 purple_account_get_alias(account
),
750 purple_account_get_protocol_name(account
));
752 g_snprintf(buf
, sizeof(buf
), "%s (%s)",
753 purple_account_get_username(account
),
754 purple_account_get_protocol_name(account
));
757 gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu
->menu
),
758 aop_menu_item_new(sg
, pixbuf
, buf
, account
, "account"));
761 g_object_unref(pixbuf
);
763 if (default_account
&& account
== default_account
)
764 aop_menu
->default_item
= i
;
773 regenerate_account_menu(GtkWidget
*optmenu
)
776 PurpleAccount
*account
;
777 PurpleFilterAccountFunc filter_func
;
779 account
= (PurpleAccount
*)aop_option_menu_get_selected(optmenu
, NULL
);
780 show_all
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(optmenu
), "show_all"));
781 filter_func
= g_object_get_data(G_OBJECT(optmenu
), "filter_func");
783 aop_option_menu_replace_menu(optmenu
, create_account_menu(account
, filter_func
, show_all
));
787 account_menu_sign_on_off_cb(PurpleConnection
*gc
, GtkWidget
*optmenu
)
789 regenerate_account_menu(optmenu
);
793 account_menu_added_removed_cb(PurpleAccount
*account
, GtkWidget
*optmenu
)
795 regenerate_account_menu(optmenu
);
799 account_menu_destroyed_cb(GtkWidget
*optmenu
, GdkEvent
*event
,
802 purple_signals_disconnect_by_handle(optmenu
);
808 pidgin_account_option_menu_set_selected(GtkWidget
*optmenu
, PurpleAccount
*account
)
810 aop_option_menu_select_by_data(optmenu
, account
);
814 pidgin_account_option_menu_new(PurpleAccount
*default_account
,
815 gboolean show_all
, GCallback cb
,
816 PurpleFilterAccountFunc filter_func
,
821 /* Create the option menu */
822 optmenu
= aop_option_menu_new(create_account_menu(default_account
, filter_func
, show_all
), cb
, user_data
);
824 g_signal_connect(G_OBJECT(optmenu
), "destroy",
825 G_CALLBACK(account_menu_destroyed_cb
), NULL
);
827 /* Register the purple sign on/off event callbacks. */
828 purple_signal_connect(purple_connections_get_handle(), "signed-on",
829 optmenu
, PURPLE_CALLBACK(account_menu_sign_on_off_cb
),
831 purple_signal_connect(purple_connections_get_handle(), "signed-off",
832 optmenu
, PURPLE_CALLBACK(account_menu_sign_on_off_cb
),
834 purple_signal_connect(purple_accounts_get_handle(), "account-added",
835 optmenu
, PURPLE_CALLBACK(account_menu_added_removed_cb
),
837 purple_signal_connect(purple_accounts_get_handle(), "account-removed",
838 optmenu
, PURPLE_CALLBACK(account_menu_added_removed_cb
),
842 g_object_set_data(G_OBJECT(optmenu
), "user_data", user_data
);
843 g_object_set_data(G_OBJECT(optmenu
), "show_all", GINT_TO_POINTER(show_all
));
844 g_object_set_data(G_OBJECT(optmenu
), "filter_func", filter_func
);
850 pidgin_check_if_dir(const char *path
, GtkFileSelection
*filesel
)
854 if (g_file_test(path
, G_FILE_TEST_IS_DIR
)) {
855 /* append a / if needed */
856 if (path
[strlen(path
) - 1] != G_DIR_SEPARATOR
) {
857 dirname
= g_strconcat(path
, G_DIR_SEPARATOR_S
, NULL
);
859 dirname
= g_strdup(path
);
861 gtk_file_selection_set_filename(filesel
, dirname
);
870 pidgin_setup_gtkspell(GtkTextView
*textview
)
873 GError
*error
= NULL
;
876 g_return_if_fail(textview
!= NULL
);
877 g_return_if_fail(GTK_IS_TEXT_VIEW(textview
));
879 if (gtkspell_new_attach(textview
, locale
, &error
) == NULL
&& error
)
881 purple_debug_warning("gtkspell", "Failed to setup GtkSpell: %s\n",
885 #endif /* USE_GTKSPELL */
889 pidgin_save_accels_cb(GtkAccelGroup
*accel_group
, guint arg1
,
890 GdkModifierType arg2
, GClosure
*arg3
,
893 purple_debug(PURPLE_DEBUG_MISC
, "accels",
894 "accel changed, scheduling save.\n");
896 if (!accels_save_timer
)
897 accels_save_timer
= g_timeout_add(5000, pidgin_save_accels
,
902 pidgin_save_accels(gpointer data
)
904 char *filename
= NULL
;
906 filename
= g_build_filename(purple_user_dir(), G_DIR_SEPARATOR_S
,
908 purple_debug(PURPLE_DEBUG_MISC
, "accels", "saving accels to %s\n", filename
);
909 gtk_accel_map_save(filename
);
912 accels_save_timer
= 0;
919 char *filename
= NULL
;
921 filename
= g_build_filename(purple_user_dir(), G_DIR_SEPARATOR_S
,
923 gtk_accel_map_load(filename
);
928 show_retrieveing_info(PurpleConnection
*conn
, const char *name
)
930 PurpleNotifyUserInfo
*info
= purple_notify_user_info_new();
931 purple_notify_user_info_add_pair(info
, _("Information"), _("Retrieving..."));
932 purple_notify_userinfo(conn
, name
, info
, NULL
, NULL
);
933 purple_notify_user_info_destroy(info
);
936 void pidgin_retrieve_user_info(PurpleConnection
*conn
, const char *name
)
938 show_retrieveing_info(conn
, name
);
939 serv_get_info(conn
, name
);
942 void pidgin_retrieve_user_info_in_chat(PurpleConnection
*conn
, const char *name
, int chat
)
945 PurplePluginProtocolInfo
*prpl_info
= NULL
;
948 pidgin_retrieve_user_info(conn
, name
);
952 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(conn
->prpl
);
953 if (prpl_info
== NULL
|| prpl_info
->get_cb_info
== NULL
) {
954 pidgin_retrieve_user_info(conn
, name
);
958 if (prpl_info
->get_cb_real_name
)
959 who
= prpl_info
->get_cb_real_name(conn
, chat
, name
);
960 show_retrieveing_info(conn
, who
? who
: name
);
961 prpl_info
->get_cb_info(conn
, chat
, name
);
966 pidgin_parse_x_im_contact(const char *msg
, gboolean all_accounts
,
967 PurpleAccount
**ret_account
, char **ret_protocol
,
968 char **ret_username
, char **ret_alias
)
970 char *protocol
= NULL
;
971 char *username
= NULL
;
977 g_return_val_if_fail(msg
!= NULL
, FALSE
);
978 g_return_val_if_fail(ret_protocol
!= NULL
, FALSE
);
979 g_return_val_if_fail(ret_username
!= NULL
, FALSE
);
981 s
= str
= g_strdup(msg
);
983 while (*s
!= '\r' && *s
!= '\n' && *s
!= '\0')
990 while (*s
!= '\r' && *s
!= '\n' && *s
!= '\0' && *s
!= ' ')
1001 if (*s
!= '\0') *s
++ = '\0';
1003 /* Clear past any whitespace */
1004 while (*s
!= '\0' && *s
== ' ')
1007 /* Now let's grab until the end of the line. */
1010 while (*s
!= '\r' && *s
!= '\n' && *s
!= '\0')
1013 if (*s
== '\r') *s
++ = '\0';
1014 if (*s
== '\n') *s
++ = '\0';
1016 if ((c
= strchr(key
, ':')) != NULL
)
1018 if (!g_ascii_strcasecmp(key
, "X-IM-Username:"))
1019 username
= g_strdup(value
);
1020 else if (!g_ascii_strcasecmp(key
, "X-IM-Protocol:"))
1021 protocol
= g_strdup(value
);
1022 else if (!g_ascii_strcasecmp(key
, "X-IM-Alias:"))
1023 alias
= g_strdup(value
);
1027 if (username
!= NULL
&& protocol
!= NULL
)
1031 *ret_username
= username
;
1032 *ret_protocol
= protocol
;
1034 if (ret_alias
!= NULL
)
1037 /* Check for a compatible account. */
1038 if (ret_account
!= NULL
)
1041 PurpleAccount
*account
= NULL
;
1043 const char *protoname
;
1046 list
= purple_accounts_get_all();
1048 list
= purple_connections_get_all();
1050 for (l
= list
; l
!= NULL
; l
= l
->next
)
1052 PurpleConnection
*gc
;
1053 PurplePluginProtocolInfo
*prpl_info
= NULL
;
1054 PurplePlugin
*plugin
;
1058 account
= (PurpleAccount
*)l
->data
;
1060 plugin
= purple_plugins_find_with_id(
1061 purple_account_get_protocol_id(account
));
1070 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(plugin
);
1074 gc
= (PurpleConnection
*)l
->data
;
1075 account
= purple_connection_get_account(gc
);
1077 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
);
1080 protoname
= prpl_info
->list_icon(account
, NULL
);
1082 if (!strcmp(protoname
, protocol
))
1088 /* Special case for AIM and ICQ */
1089 if (account
== NULL
&& (!strcmp(protocol
, "aim") ||
1090 !strcmp(protocol
, "icq")))
1092 for (l
= list
; l
!= NULL
; l
= l
->next
)
1094 PurpleConnection
*gc
;
1095 PurplePluginProtocolInfo
*prpl_info
= NULL
;
1096 PurplePlugin
*plugin
;
1100 account
= (PurpleAccount
*)l
->data
;
1102 plugin
= purple_plugins_find_with_id(
1103 purple_account_get_protocol_id(account
));
1112 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(plugin
);
1116 gc
= (PurpleConnection
*)l
->data
;
1117 account
= purple_connection_get_account(gc
);
1119 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
);
1122 protoname
= prpl_info
->list_icon(account
, NULL
);
1124 if (!strcmp(protoname
, "aim") || !strcmp(protoname
, "icq"))
1131 *ret_account
= account
;
1149 pidgin_set_accessible_label (GtkWidget
*w
, GtkWidget
*l
)
1152 const gchar
*label_text
;
1153 const gchar
*existing_name
;
1155 acc
= gtk_widget_get_accessible (w
);
1157 /* If this object has no name, set it's name with the label text */
1158 existing_name
= atk_object_get_name (acc
);
1159 if (!existing_name
) {
1160 label_text
= gtk_label_get_text (GTK_LABEL(l
));
1162 atk_object_set_name (acc
, label_text
);
1165 pidgin_set_accessible_relations(w
, l
);
1169 pidgin_set_accessible_relations (GtkWidget
*w
, GtkWidget
*l
)
1171 AtkObject
*acc
, *label
;
1172 AtkObject
*rel_obj
[1];
1173 AtkRelationSet
*set
;
1174 AtkRelation
*relation
;
1176 acc
= gtk_widget_get_accessible (w
);
1177 label
= gtk_widget_get_accessible (l
);
1179 /* Make sure mnemonics work */
1180 gtk_label_set_mnemonic_widget(GTK_LABEL(l
), w
);
1182 /* Create the labeled-by relation */
1183 set
= atk_object_ref_relation_set (acc
);
1185 relation
= atk_relation_new (rel_obj
, 1, ATK_RELATION_LABELLED_BY
);
1186 atk_relation_set_add (set
, relation
);
1187 g_object_unref (relation
);
1189 /* Create the label-for relation */
1190 set
= atk_object_ref_relation_set (label
);
1192 relation
= atk_relation_new (rel_obj
, 1, ATK_RELATION_LABEL_FOR
);
1193 atk_relation_set_add (set
, relation
);
1194 g_object_unref (relation
);
1198 pidgin_menu_position_func_helper(GtkMenu
*menu
,
1204 #if GTK_CHECK_VERSION(2,2,0)
1206 GtkRequisition requisition
;
1208 GdkRectangle monitor
;
1210 gint space_left
, space_right
, space_above
, space_below
;
1217 g_return_if_fail(GTK_IS_MENU(menu
));
1219 widget
= GTK_WIDGET(menu
);
1220 screen
= gtk_widget_get_screen(widget
);
1221 xthickness
= widget
->style
->xthickness
;
1222 ythickness
= widget
->style
->ythickness
;
1223 rtl
= (gtk_widget_get_direction(widget
) == GTK_TEXT_DIR_RTL
);
1226 * We need the requisition to figure out the right place to
1227 * popup the menu. In fact, we always need to ask here, since
1228 * if a size_request was queued while we weren't popped up,
1229 * the requisition won't have been recomputed yet.
1231 gtk_widget_size_request (widget
, &requisition
);
1233 monitor_num
= gdk_screen_get_monitor_at_point (screen
, *x
, *y
);
1238 * The placement of popup menus horizontally works like this (with
1239 * RTL in parentheses)
1241 * - If there is enough room to the right (left) of the mouse cursor,
1242 * position the menu there.
1244 * - Otherwise, if if there is enough room to the left (right) of the
1245 * mouse cursor, position the menu there.
1247 * - Otherwise if the menu is smaller than the monitor, position it
1248 * on the side of the mouse cursor that has the most space available
1250 * - Otherwise (if there is simply not enough room for the menu on the
1251 * monitor), position it as far left (right) as possible.
1253 * Positioning in the vertical direction is similar: first try below
1254 * mouse cursor, then above.
1256 gdk_screen_get_monitor_geometry (screen
, monitor_num
, &monitor
);
1258 space_left
= *x
- monitor
.x
;
1259 space_right
= monitor
.x
+ monitor
.width
- *x
- 1;
1260 space_above
= *y
- monitor
.y
;
1261 space_below
= monitor
.y
+ monitor
.height
- *y
- 1;
1263 /* position horizontally */
1265 /* the amount of space we need to position the menu. Note the
1266 * menu is offset "xthickness" pixels
1268 needed_width
= requisition
.width
- xthickness
;
1270 if (needed_width
<= space_left
||
1271 needed_width
<= space_right
)
1273 if ((rtl
&& needed_width
<= space_left
) ||
1274 (!rtl
&& needed_width
> space_right
))
1277 *x
= *x
+ xthickness
- requisition
.width
+ 1;
1281 /* position right */
1282 *x
= *x
- xthickness
;
1285 /* x is clamped on-screen further down */
1287 else if (requisition
.width
<= monitor
.width
)
1289 /* the menu is too big to fit on either side of the mouse
1290 * cursor, but smaller than the monitor. Position it on
1291 * the side that has the most space
1293 if (space_left
> space_right
)
1301 *x
= monitor
.x
+ monitor
.width
- requisition
.width
;
1304 else /* menu is simply too big for the monitor */
1309 *x
= monitor
.x
+ monitor
.width
- requisition
.width
;
1318 /* Position vertically. The algorithm is the same as above, but
1319 * simpler because we don't have to take RTL into account.
1321 needed_height
= requisition
.height
- ythickness
;
1323 if (needed_height
<= space_above
||
1324 needed_height
<= space_below
)
1326 if (needed_height
<= space_below
)
1327 *y
= *y
- ythickness
;
1329 *y
= *y
+ ythickness
- requisition
.height
+ 1;
1331 *y
= CLAMP (*y
, monitor
.y
,
1332 monitor
.y
+ monitor
.height
- requisition
.height
);
1334 else if (needed_height
> space_below
&& needed_height
> space_above
)
1336 if (space_below
>= space_above
)
1337 *y
= monitor
.y
+ monitor
.height
- requisition
.height
;
1350 pidgin_treeview_popup_menu_position_func(GtkMenu
*menu
,
1356 GtkWidget
*widget
= GTK_WIDGET(data
);
1357 GtkTreeView
*tv
= GTK_TREE_VIEW(data
);
1359 GtkTreeViewColumn
*col
;
1361 gint ythickness
= GTK_WIDGET(menu
)->style
->ythickness
;
1363 gdk_window_get_origin (widget
->window
, x
, y
);
1364 gtk_tree_view_get_cursor (tv
, &path
, &col
);
1365 gtk_tree_view_get_cell_area (tv
, path
, col
, &rect
);
1367 *x
+= rect
.x
+rect
.width
;
1368 *y
+= rect
.y
+rect
.height
+ythickness
;
1369 pidgin_menu_position_func_helper(menu
, x
, y
, push_in
, data
);
1380 PurpleAccount
*account
;
1384 static void dnd_image_ok_callback(_DndData
*data
, int choice
)
1390 PurpleConversation
*conv
;
1391 PidginConversation
*gtkconv
;
1395 case DND_BUDDY_ICON
:
1396 if (g_stat(data
->filename
, &st
)) {
1399 str
= g_strdup_printf(_("The following error has occurred loading %s: %s"),
1400 data
->filename
, strerror(errno
));
1401 purple_notify_error(NULL
, NULL
,
1402 _("Failed to load image"),
1409 pidgin_set_custom_buddy_icon(data
->account
, data
->who
, data
->filename
);
1411 case DND_FILE_TRANSFER
:
1412 serv_send_file(purple_account_get_connection(data
->account
), data
->who
, data
->filename
);
1415 conv
= purple_conversation_new(PURPLE_CONV_TYPE_IM
, data
->account
, data
->who
);
1416 gtkconv
= PIDGIN_CONVERSATION(conv
);
1418 if (!g_file_get_contents(data
->filename
, &filedata
, &size
,
1422 str
= g_strdup_printf(_("The following error has occurred loading %s: %s"), data
->filename
, err
->message
);
1423 purple_notify_error(NULL
, NULL
,
1424 _("Failed to load image"),
1432 id
= purple_imgstore_add_with_id(filedata
, size
, data
->filename
);
1434 gtk_text_buffer_get_iter_at_mark(GTK_IMHTML(gtkconv
->entry
)->text_buffer
, &iter
,
1435 gtk_text_buffer_get_insert(GTK_IMHTML(gtkconv
->entry
)->text_buffer
));
1436 gtk_imhtml_insert_image_at_iter(GTK_IMHTML(gtkconv
->entry
), id
, &iter
);
1437 purple_imgstore_unref_by_id(id
);
1441 free(data
->filename
);
1446 static void dnd_image_cancel_callback(_DndData
*data
, int choice
)
1448 free(data
->filename
);
1453 static void dnd_set_icon_ok_cb(_DndData
*data
)
1455 dnd_image_ok_callback(data
, DND_BUDDY_ICON
);
1458 static void dnd_set_icon_cancel_cb(_DndData
*data
)
1460 free(data
->filename
);
1466 pidgin_dnd_file_manage(GtkSelectionData
*sd
, PurpleAccount
*account
, const char *who
)
1470 GList
*files
= purple_uri_list_extract_filenames((const gchar
*)sd
->data
);
1471 PurpleConnection
*gc
= purple_account_get_connection(account
);
1472 PurplePluginProtocolInfo
*prpl_info
= NULL
;
1473 gboolean file_send_ok
= FALSE
;
1475 PurpleDesktopItem
*item
;
1478 g_return_if_fail(account
!= NULL
);
1479 g_return_if_fail(who
!= NULL
);
1481 for(tmp
= files
; tmp
!= NULL
; tmp
= g_list_next(tmp
)) {
1482 gchar
*filename
= tmp
->data
;
1483 gchar
*basename
= g_path_get_basename(filename
);
1485 /* Set the default action: don't send anything */
1486 file_send_ok
= FALSE
;
1488 /* XXX - Make ft API support creating a transfer with more than one file */
1489 if (!g_file_test(filename
, G_FILE_TEST_EXISTS
)) {
1493 /* XXX - make ft api suupport sending a directory */
1494 /* Are we dealing with a directory? */
1495 if (g_file_test(filename
, G_FILE_TEST_IS_DIR
)) {
1498 str
= g_strdup_printf(_("Cannot send folder %s."), basename
);
1499 str2
= g_strdup_printf(_("%s cannot transfer a folder. You will need to send the files within individually."), PIDGIN_NAME
);
1501 purple_notify_error(NULL
, NULL
,
1510 /* Are we dealing with an image? */
1511 pb
= gdk_pixbuf_new_from_file(filename
, NULL
);
1513 _DndData
*data
= g_malloc(sizeof(_DndData
));
1514 gboolean ft
= FALSE
, im
= FALSE
;
1516 data
->who
= g_strdup(who
);
1517 data
->filename
= g_strdup(filename
);
1518 data
->account
= account
;
1521 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
);
1523 if (prpl_info
&& prpl_info
->options
& OPT_PROTO_IM_IMAGE
)
1526 if (prpl_info
&& prpl_info
->can_receive_file
)
1527 ft
= prpl_info
->can_receive_file(gc
, who
);
1530 purple_request_choice(NULL
, NULL
,
1531 _("You have dragged an image"),
1532 _("You can send this image as a file transfer, "
1533 "embed it into this message, or use it as the buddy icon for this user."),
1534 DND_FILE_TRANSFER
, "OK", (GCallback
)dnd_image_ok_callback
,
1535 "Cancel", (GCallback
)dnd_image_cancel_callback
,
1538 _("Set as buddy icon"), DND_BUDDY_ICON
,
1539 _("Send image file"), DND_FILE_TRANSFER
,
1540 _("Insert in message"), DND_IM_IMAGE
,
1542 else if (!(im
|| ft
))
1543 purple_request_yes_no(NULL
, NULL
, _("You have dragged an image"),
1544 _("Would you like to set it as the buddy icon for this user?"),
1547 data
, (GCallback
)dnd_set_icon_ok_cb
, (GCallback
)dnd_set_icon_cancel_cb
);
1549 purple_request_choice(NULL
, NULL
,
1550 _("You have dragged an image"),
1551 (ft
? _("You can send this image as a file transfer, or use it as the buddy icon for this user.") :
1552 _("You can insert this image into this message, or use it as the buddy icon for this user")),
1553 (ft
? DND_FILE_TRANSFER
: DND_IM_IMAGE
),
1554 "OK", (GCallback
)dnd_image_ok_callback
,
1555 "Cancel", (GCallback
)dnd_image_cancel_callback
,
1558 _("Set as buddy icon"), DND_BUDDY_ICON
,
1559 (ft
? _("Send image file") : _("Insert in message")), (ft
? DND_FILE_TRANSFER
: DND_IM_IMAGE
),
1565 /* Are we trying to send a .desktop file? */
1566 else if (purple_str_has_suffix(basename
, ".desktop") && (item
= purple_desktop_item_new_from_file(filename
))) {
1567 PurpleDesktopItemType dtype
;
1569 const char *itemname
= NULL
;
1571 #if GTK_CHECK_VERSION(2,6,0)
1572 const char * const *langs
;
1574 langs
= g_get_language_names();
1575 for (i
= 0; langs
[i
]; i
++) {
1576 g_snprintf(key
, sizeof(key
), "Name[%s]", langs
[i
]);
1577 itemname
= purple_desktop_item_get_string(item
, key
);
1581 const char *lang
= g_getenv("LANG");
1583 dot
= strchr(lang
, '.');
1586 g_snprintf(key
, sizeof(key
), "Name[%s]", lang
);
1587 itemname
= purple_desktop_item_get_string(item
, key
);
1590 itemname
= purple_desktop_item_get_string(item
, "Name");
1592 dtype
= purple_desktop_item_get_entry_type(item
);
1594 PurpleConversation
*conv
;
1595 PidginConversation
*gtkconv
;
1597 case PURPLE_DESKTOP_ITEM_TYPE_LINK
:
1598 conv
= purple_conversation_new(PURPLE_CONV_TYPE_IM
, account
, who
);
1599 gtkconv
= PIDGIN_CONVERSATION(conv
);
1600 gtk_imhtml_insert_link(GTK_IMHTML(gtkconv
->entry
),
1601 gtk_text_buffer_get_insert(GTK_IMHTML(gtkconv
->entry
)->text_buffer
),
1602 purple_desktop_item_get_string(item
, "URL"), itemname
);
1605 /* I don't know if we really want to do anything here. Most of the desktop item types are crap like
1606 * "MIME Type" (I have no clue how that would be a desktop item) and "Comment"... nothing we can really
1607 * send. The only logical one is "Application," but do we really want to send a binary and nothing else?
1608 * Probably not. I'll just give an error and return. */
1609 /* The original patch sent the icon used by the launcher. That's probably wrong */
1610 purple_notify_error(NULL
, NULL
, _("Cannot send launcher"), _("You dragged a desktop launcher. "
1611 "Most likely you wanted to send whatever this launcher points to instead of this launcher"
1615 purple_desktop_item_unref(item
);
1620 /* Everything is fine, let's send */
1621 serv_send_file(gc
, who
, filename
);
1627 void pidgin_buddy_icon_get_scale_size(GdkPixbuf
*buf
, PurpleBuddyIconSpec
*spec
, PurpleIconScaleRules rules
, int *width
, int *height
)
1629 *width
= gdk_pixbuf_get_width(buf
);
1630 *height
= gdk_pixbuf_get_height(buf
);
1632 if ((spec
== NULL
) || !(spec
->scale_rules
& rules
))
1635 purple_buddy_icon_get_scale_size(spec
, width
, height
);
1637 /* and now for some arbitrary sanity checks */
1644 GdkPixbuf
* pidgin_create_status_icon(PurpleStatusPrimitive prim
, GtkWidget
*w
, const char *size
)
1646 GtkIconSize icon_size
= gtk_icon_size_from_name(size
);
1647 GdkPixbuf
*pixbuf
= NULL
;
1649 if (prim
== PURPLE_STATUS_UNAVAILABLE
)
1650 pixbuf
= gtk_widget_render_icon (w
, PIDGIN_STOCK_STATUS_BUSY
,
1651 icon_size
, "GtkWidget");
1652 else if (prim
== PURPLE_STATUS_AWAY
)
1653 pixbuf
= gtk_widget_render_icon (w
, PIDGIN_STOCK_STATUS_AWAY
,
1654 icon_size
, "GtkWidget");
1655 else if (prim
== PURPLE_STATUS_EXTENDED_AWAY
)
1656 pixbuf
= gtk_widget_render_icon (w
, PIDGIN_STOCK_STATUS_XA
,
1657 icon_size
, "GtkWidget");
1658 else if (prim
== PURPLE_STATUS_INVISIBLE
)
1659 pixbuf
= gtk_widget_render_icon (w
, PIDGIN_STOCK_STATUS_INVISIBLE
,
1660 icon_size
, "GtkWidget");
1661 else if (prim
== PURPLE_STATUS_OFFLINE
)
1662 pixbuf
= gtk_widget_render_icon (w
, PIDGIN_STOCK_STATUS_OFFLINE
,
1663 icon_size
, "GtkWidget");
1665 pixbuf
= gtk_widget_render_icon (w
, PIDGIN_STOCK_STATUS_AVAILABLE
,
1666 icon_size
, "GtkWidget");
1673 pidgin_create_prpl_icon(PurpleAccount
*account
, PidginPrplIconSize size
)
1677 g_return_val_if_fail(account
!= NULL
, NULL
);
1679 prpl
= purple_find_prpl(purple_account_get_protocol_id(account
));
1682 return pidgin_create_prpl_icon_from_prpl(prpl
, size
, account
);
1686 menu_action_cb(GtkMenuItem
*item
, gpointer object
)
1689 void (*callback
)(gpointer
, gpointer
);
1691 callback
= g_object_get_data(G_OBJECT(item
), "purplecallback");
1692 data
= g_object_get_data(G_OBJECT(item
), "purplecallbackdata");
1695 callback(object
, data
);
1699 pidgin_append_menu_action(GtkWidget
*menu
, PurpleMenuAction
*act
,
1702 GtkWidget
*menuitem
;
1705 return pidgin_separator(menu
);
1708 if (act
->children
== NULL
) {
1709 menuitem
= gtk_menu_item_new_with_mnemonic(act
->label
);
1711 if (act
->callback
!= NULL
) {
1712 g_object_set_data(G_OBJECT(menuitem
),
1715 g_object_set_data(G_OBJECT(menuitem
),
1716 "purplecallbackdata",
1718 g_signal_connect(G_OBJECT(menuitem
), "activate",
1719 G_CALLBACK(menu_action_cb
),
1722 gtk_widget_set_sensitive(menuitem
, FALSE
);
1725 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
1728 GtkWidget
*submenu
= NULL
;
1729 GtkAccelGroup
*group
;
1731 menuitem
= gtk_menu_item_new_with_mnemonic(act
->label
);
1732 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
1734 submenu
= gtk_menu_new();
1735 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem
), submenu
);
1737 group
= gtk_menu_get_accel_group(GTK_MENU(menu
));
1739 char *path
= g_strdup_printf("%s/%s", GTK_MENU_ITEM(menuitem
)->accel_path
, act
->label
);
1740 gtk_menu_set_accel_path(GTK_MENU(submenu
), path
);
1742 gtk_menu_set_accel_group(GTK_MENU(submenu
), group
);
1745 for (l
= act
->children
; l
; l
= l
->next
) {
1746 PurpleMenuAction
*act
= (PurpleMenuAction
*)l
->data
;
1748 pidgin_append_menu_action(submenu
, act
, object
);
1750 g_list_free(act
->children
);
1751 act
->children
= NULL
;
1753 purple_menu_action_free(act
);
1757 #if GTK_CHECK_VERSION(2,3,0)
1758 # define NEW_STYLE_COMPLETION
1764 GtkWidget
*accountopt
;
1766 PidginFilterBuddyCompletionEntryFunc filter_func
;
1767 gpointer filter_func_user_data
;
1769 #ifdef NEW_STYLE_COMPLETION
1770 GtkListStore
*store
;
1772 GCompletion
*completion
;
1773 gboolean completion_started
;
1775 #endif /* NEW_STYLE_COMPLETION */
1776 } PidginCompletionData
;
1778 #ifndef NEW_STYLE_COMPLETION
1780 completion_entry_event(GtkEditable
*entry
, GdkEventKey
*event
,
1781 PidginCompletionData
*data
)
1785 if (event
->type
== GDK_KEY_PRESS
&& event
->keyval
== GDK_Tab
)
1787 gtk_editable_get_selection_bounds(entry
, &pos
, &end_pos
);
1789 if (data
->completion_started
&&
1790 pos
!= end_pos
&& pos
> 1 &&
1791 end_pos
== strlen(gtk_entry_get_text(GTK_ENTRY(entry
))))
1793 gtk_editable_select_region(entry
, 0, 0);
1794 gtk_editable_set_position(entry
, -1);
1799 else if (event
->type
== GDK_KEY_PRESS
&& event
->length
> 0)
1801 char *prefix
, *nprefix
;
1803 gtk_editable_get_selection_bounds(entry
, &pos
, &end_pos
);
1805 if (data
->completion_started
&&
1806 pos
!= end_pos
&& pos
> 1 &&
1807 end_pos
== strlen(gtk_entry_get_text(GTK_ENTRY(entry
))))
1811 temp
= gtk_editable_get_chars(entry
, 0, pos
);
1812 prefix
= g_strconcat(temp
, event
->string
, NULL
);
1815 else if (pos
== end_pos
&& pos
> 1 &&
1816 end_pos
== strlen(gtk_entry_get_text(GTK_ENTRY(entry
))))
1818 prefix
= g_strconcat(gtk_entry_get_text(GTK_ENTRY(entry
)),
1819 event
->string
, NULL
);
1824 pos
= strlen(prefix
);
1827 g_completion_complete(data
->completion
, prefix
, &nprefix
);
1829 if (nprefix
!= NULL
)
1831 gtk_entry_set_text(GTK_ENTRY(entry
), nprefix
);
1832 gtk_editable_set_position(entry
, pos
);
1833 gtk_editable_select_region(entry
, pos
, -1);
1835 data
->completion_started
= TRUE
;
1850 destroy_completion_data(GtkWidget
*w
, PidginCompletionData
*data
)
1852 g_list_foreach(data
->completion
->items
, (GFunc
)g_free
, NULL
);
1853 g_completion_free(data
->completion
);
1857 #endif /* !NEW_STYLE_COMPLETION */
1859 #ifdef NEW_STYLE_COMPLETION
1860 static gboolean
screenname_completion_match_func(GtkEntryCompletion
*completion
,
1861 const gchar
*key
, GtkTreeIter
*iter
, gpointer user_data
)
1863 GtkTreeModel
*model
;
1868 model
= gtk_entry_completion_get_model (completion
);
1871 gtk_tree_model_get_value(model
, iter
, 2, &val1
);
1872 tmp
= g_value_get_string(&val1
);
1873 if (tmp
!= NULL
&& purple_str_has_prefix(tmp
, key
))
1875 g_value_unset(&val1
);
1878 g_value_unset(&val1
);
1881 gtk_tree_model_get_value(model
, iter
, 3, &val2
);
1882 tmp
= g_value_get_string(&val2
);
1883 if (tmp
!= NULL
&& purple_str_has_prefix(tmp
, key
))
1885 g_value_unset(&val2
);
1888 g_value_unset(&val2
);
1893 static gboolean
screenname_completion_match_selected_cb(GtkEntryCompletion
*completion
,
1894 GtkTreeModel
*model
, GtkTreeIter
*iter
, PidginCompletionData
*data
)
1897 GtkWidget
*optmenu
= data
->accountopt
;
1898 PurpleAccount
*account
;
1901 gtk_tree_model_get_value(model
, iter
, 1, &val
);
1902 gtk_entry_set_text(GTK_ENTRY(data
->entry
), g_value_get_string(&val
));
1903 g_value_unset(&val
);
1905 gtk_tree_model_get_value(model
, iter
, 4, &val
);
1906 account
= g_value_get_pointer(&val
);
1907 g_value_unset(&val
);
1909 if (account
== NULL
)
1912 if (optmenu
!= NULL
)
1913 aop_option_menu_select_by_data(optmenu
, account
);
1919 add_screenname_autocomplete_entry(GtkListStore
*store
, const char *buddy_alias
, const char *contact_alias
,
1920 const PurpleAccount
*account
, const char *screenname
)
1923 gboolean completion_added
= FALSE
;
1924 gchar
*normalized_screenname
;
1927 tmp
= g_utf8_normalize(screenname
, -1, G_NORMALIZE_DEFAULT
);
1928 normalized_screenname
= g_utf8_casefold(tmp
, -1);
1931 /* There's no sense listing things like: 'xxx "xxx"'
1932 when the screenname and buddy alias match. */
1933 if (buddy_alias
&& strcmp(buddy_alias
, screenname
)) {
1934 char *completion_entry
= g_strdup_printf("%s \"%s\"", screenname
, buddy_alias
);
1935 char *tmp2
= g_utf8_normalize(buddy_alias
, -1, G_NORMALIZE_DEFAULT
);
1937 tmp
= g_utf8_casefold(tmp2
, -1);
1940 gtk_list_store_append(store
, &iter
);
1941 gtk_list_store_set(store
, &iter
,
1942 0, completion_entry
,
1944 2, normalized_screenname
,
1948 g_free(completion_entry
);
1950 completion_added
= TRUE
;
1953 /* There's no sense listing things like: 'xxx "xxx"'
1954 when the screenname and contact alias match. */
1955 if (contact_alias
&& strcmp(contact_alias
, screenname
)) {
1956 /* We don't want duplicates when the contact and buddy alias match. */
1957 if (!buddy_alias
|| strcmp(contact_alias
, buddy_alias
)) {
1958 char *completion_entry
= g_strdup_printf("%s \"%s\"",
1959 screenname
, contact_alias
);
1960 char *tmp2
= g_utf8_normalize(contact_alias
, -1, G_NORMALIZE_DEFAULT
);
1962 tmp
= g_utf8_casefold(tmp2
, -1);
1965 gtk_list_store_append(store
, &iter
);
1966 gtk_list_store_set(store
, &iter
,
1967 0, completion_entry
,
1969 2, normalized_screenname
,
1973 g_free(completion_entry
);
1975 completion_added
= TRUE
;
1979 if (completion_added
== FALSE
) {
1980 /* Add the buddy's screenname. */
1981 gtk_list_store_append(store
, &iter
);
1982 gtk_list_store_set(store
, &iter
,
1985 2, normalized_screenname
,
1991 g_free(normalized_screenname
);
1993 #endif /* NEW_STYLE_COMPLETION */
1995 static void get_log_set_name(PurpleLogSet
*set
, gpointer value
, PidginCompletionData
*data
)
1997 PidginFilterBuddyCompletionEntryFunc filter_func
= data
->filter_func
;
1998 gpointer user_data
= data
->filter_func_user_data
;
2000 /* 1. Don't show buddies because we will have gotten them already.
2001 * 2. The boxes that use this autocomplete code handle only IMs. */
2002 if (!set
->buddy
&& set
->type
== PURPLE_LOG_IM
) {
2003 PidginBuddyCompletionEntry entry
;
2004 entry
.is_buddy
= FALSE
;
2005 entry
.entry
.logged_buddy
= set
;
2007 if (filter_func(&entry
, user_data
)) {
2008 #ifdef NEW_STYLE_COMPLETION
2009 add_screenname_autocomplete_entry(data
->store
,
2010 NULL
, NULL
, set
->account
, set
->name
);
2012 /* Steal the name for the GCompletion. */
2013 data
->log_items
= g_list_append(data
->log_items
, set
->name
);
2014 set
->name
= set
->normalized_name
= NULL
;
2015 #endif /* NEW_STYLE_COMPLETION */
2021 add_completion_list(PidginCompletionData
*data
)
2023 PurpleBlistNode
*gnode
, *cnode
, *bnode
;
2024 PidginFilterBuddyCompletionEntryFunc filter_func
= data
->filter_func
;
2025 gpointer user_data
= data
->filter_func_user_data
;
2028 #ifdef NEW_STYLE_COMPLETION
2029 gtk_list_store_clear(data
->store
);
2031 GList
*item
= g_list_append(NULL
, NULL
);
2033 g_list_foreach(data
->completion
->items
, (GFunc
)g_free
, NULL
);
2034 g_completion_clear_items(data
->completion
);
2035 #endif /* NEW_STYLE_COMPLETION */
2037 for (gnode
= purple_get_blist()->root
; gnode
!= NULL
; gnode
= gnode
->next
)
2039 if (!PURPLE_BLIST_NODE_IS_GROUP(gnode
))
2042 for (cnode
= gnode
->child
; cnode
!= NULL
; cnode
= cnode
->next
)
2044 if (!PURPLE_BLIST_NODE_IS_CONTACT(cnode
))
2047 for (bnode
= cnode
->child
; bnode
!= NULL
; bnode
= bnode
->next
)
2049 PidginBuddyCompletionEntry entry
;
2050 entry
.is_buddy
= TRUE
;
2051 entry
.entry
.buddy
= (PurpleBuddy
*) bnode
;
2053 if (filter_func(&entry
, user_data
)) {
2054 #ifdef NEW_STYLE_COMPLETION
2055 add_screenname_autocomplete_entry(data
->store
,
2056 ((PurpleContact
*)cnode
)->alias
,
2057 purple_buddy_get_contact_alias(entry
.entry
.buddy
),
2058 entry
.entry
.buddy
->account
,
2059 entry
.entry
.buddy
->name
2062 item
->data
= g_strdup(entry
.entry
.buddy
->name
);
2063 g_completion_add_items(data
->completion
, item
);
2064 #endif /* NEW_STYLE_COMPLETION */
2070 #ifndef NEW_STYLE_COMPLETION
2072 data
->log_items
= NULL
;
2073 #endif /* NEW_STYLE_COMPLETION */
2075 sets
= purple_log_get_log_sets();
2076 g_hash_table_foreach(sets
, (GHFunc
)get_log_set_name
, data
);
2077 g_hash_table_destroy(sets
);
2079 #ifndef NEW_STYLE_COMPLETION
2080 g_completion_add_items(data
->completion
, data
->log_items
);
2081 g_list_free(data
->log_items
);
2082 #endif /* NEW_STYLE_COMPLETION */
2086 screenname_autocomplete_destroyed_cb(GtkWidget
*widget
, gpointer data
)
2089 purple_signals_disconnect_by_handle(widget
);
2093 repopulate_autocomplete(gpointer something
, gpointer data
)
2095 add_completion_list(data
);
2100 pidgin_setup_screenname_autocomplete_with_filter(GtkWidget
*entry
, GtkWidget
*accountopt
, PidginFilterBuddyCompletionEntryFunc filter_func
, gpointer user_data
)
2102 PidginCompletionData
*data
;
2104 #ifdef NEW_STYLE_COMPLETION
2105 /* Store the displayed completion value, the screenname, the UTF-8 normalized & casefolded screenname,
2106 * the UTF-8 normalized & casefolded value for comparison, and the account. */
2107 GtkListStore
*store
;
2109 GtkEntryCompletion
*completion
;
2111 data
= g_new0(PidginCompletionData
, 1);
2112 store
= gtk_list_store_new(5, G_TYPE_STRING
, G_TYPE_STRING
, G_TYPE_STRING
, G_TYPE_STRING
, G_TYPE_POINTER
);
2114 data
->entry
= entry
;
2115 data
->accountopt
= accountopt
;
2116 if (filter_func
== NULL
) {
2117 data
->filter_func
= pidgin_screenname_autocomplete_default_filter
;
2118 data
->filter_func_user_data
= NULL
;
2120 data
->filter_func
= filter_func
;
2121 data
->filter_func_user_data
= user_data
;
2123 data
->store
= store
;
2125 add_completion_list(data
);
2127 /* Sort the completion list by screenname. */
2128 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store
),
2129 1, GTK_SORT_ASCENDING
);
2131 completion
= gtk_entry_completion_new();
2132 gtk_entry_completion_set_match_func(completion
, screenname_completion_match_func
, NULL
, NULL
);
2134 g_signal_connect(G_OBJECT(completion
), "match-selected",
2135 G_CALLBACK(screenname_completion_match_selected_cb
), data
);
2137 gtk_entry_set_completion(GTK_ENTRY(entry
), completion
);
2138 g_object_unref(completion
);
2140 gtk_entry_completion_set_model(completion
, GTK_TREE_MODEL(store
));
2141 g_object_unref(store
);
2143 gtk_entry_completion_set_text_column(completion
, 0);
2145 #else /* !NEW_STYLE_COMPLETION */
2147 data
= g_new0(PidginCompletionData
, 1);
2149 data
->entry
= entry
;
2150 data
->accountopt
= accountopt
;
2151 if (filter_func
== NULL
) {
2152 data
->filter_func
= pidgin_screenname_autocomplete_default_filter
;
2153 data
->filter_func_user_data
= NULL
;
2155 data
->filter_func
= filter_func
;
2156 data
->filter_func_user_data
= user_data
;
2158 data
->completion
= g_completion_new(NULL
);
2159 data
->completion_started
= FALSE
;
2161 add_completion_list(data
);
2163 g_completion_set_compare(data
->completion
, g_ascii_strncasecmp
);
2165 g_signal_connect(G_OBJECT(entry
), "event",
2166 G_CALLBACK(completion_entry_event
), data
);
2167 g_signal_connect(G_OBJECT(entry
), "destroy",
2168 G_CALLBACK(destroy_completion_data
), data
);
2170 #endif /* !NEW_STYLE_COMPLETION */
2172 purple_signal_connect(purple_connections_get_handle(), "signed-on", entry
,
2173 PURPLE_CALLBACK(repopulate_autocomplete
), data
);
2174 purple_signal_connect(purple_connections_get_handle(), "signed-off", entry
,
2175 PURPLE_CALLBACK(repopulate_autocomplete
), data
);
2177 purple_signal_connect(purple_accounts_get_handle(), "account-added", entry
,
2178 PURPLE_CALLBACK(repopulate_autocomplete
), data
);
2179 purple_signal_connect(purple_accounts_get_handle(), "account-removed", entry
,
2180 PURPLE_CALLBACK(repopulate_autocomplete
), data
);
2182 g_signal_connect(G_OBJECT(entry
), "destroy", G_CALLBACK(screenname_autocomplete_destroyed_cb
), data
);
2186 pidgin_screenname_autocomplete_default_filter(const PidginBuddyCompletionEntry
*completion_entry
, gpointer all_accounts
) {
2187 gboolean all
= GPOINTER_TO_INT(all_accounts
);
2189 if (completion_entry
->is_buddy
) {
2190 return all
|| purple_account_is_connected(completion_entry
->entry
.buddy
->account
);
2192 return all
|| (completion_entry
->entry
.logged_buddy
->account
!= NULL
&& purple_account_is_connected(completion_entry
->entry
.logged_buddy
->account
));
2197 pidgin_setup_screenname_autocomplete(GtkWidget
*entry
, GtkWidget
*accountopt
, gboolean all
) {
2198 pidgin_setup_screenname_autocomplete_with_filter(entry
, accountopt
, pidgin_screenname_autocomplete_default_filter
, GINT_TO_POINTER(all
));
2203 void pidgin_set_cursor(GtkWidget
*widget
, GdkCursorType cursor_type
)
2207 g_return_if_fail(widget
!= NULL
);
2208 if (widget
->window
== NULL
)
2211 cursor
= gdk_cursor_new(GDK_WATCH
);
2212 gdk_window_set_cursor(widget
->window
, cursor
);
2213 gdk_cursor_unref(cursor
);
2215 #if GTK_CHECK_VERSION(2,4,0)
2216 gdk_display_flush(gdk_drawable_get_display(GDK_DRAWABLE(widget
->window
)));
2222 void pidgin_clear_cursor(GtkWidget
*widget
)
2224 g_return_if_fail(widget
!= NULL
);
2225 if (widget
->window
== NULL
)
2228 gdk_window_set_cursor(widget
->window
, NULL
);
2231 struct _icon_chooser
{
2232 GtkWidget
*icon_filesel
;
2233 GtkWidget
*icon_preview
;
2234 GtkWidget
*icon_text
;
2236 void (*callback
)(const char*,gpointer
);
2240 #if !GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */
2242 icon_filesel_delete_cb(GtkWidget
*w
, struct _icon_chooser
*dialog
)
2244 if (dialog
->icon_filesel
!= NULL
)
2245 gtk_widget_destroy(dialog
->icon_filesel
);
2247 if (dialog
->callback
)
2248 dialog
->callback(NULL
, dialog
->data
);
2252 #endif /* FILECHOOSER */
2256 #if GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */
2258 icon_filesel_choose_cb(GtkWidget
*widget
, gint response
, struct _icon_chooser
*dialog
)
2260 char *filename
, *current_folder
;
2262 if (response
!= GTK_RESPONSE_ACCEPT
) {
2263 if (response
== GTK_RESPONSE_CANCEL
) {
2264 gtk_widget_destroy(dialog
->icon_filesel
);
2266 dialog
->icon_filesel
= NULL
;
2267 if (dialog
->callback
)
2268 dialog
->callback(NULL
, dialog
->data
);
2273 filename
= gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog
->icon_filesel
));
2274 current_folder
= gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(dialog
->icon_filesel
));
2275 if (current_folder
!= NULL
) {
2276 purple_prefs_set_path(PIDGIN_PREFS_ROOT
"/filelocations/last_icon_folder", current_folder
);
2277 g_free(current_folder
);
2280 #else /* FILECHOOSER */
2282 icon_filesel_choose_cb(GtkWidget
*w
, struct _icon_chooser
*dialog
)
2284 char *filename
, *current_folder
;
2286 filename
= g_strdup(gtk_file_selection_get_filename(
2287 GTK_FILE_SELECTION(dialog
->icon_filesel
)));
2289 /* If they typed in a directory, change there */
2290 if (pidgin_check_if_dir(filename
,
2291 GTK_FILE_SELECTION(dialog
->icon_filesel
)))
2297 current_folder
= g_path_get_dirname(filename
);
2298 if (current_folder
!= NULL
) {
2299 purple_prefs_set_path(PIDGIN_PREFS_ROOT
"/filelocations/last_icon_folder", current_folder
);
2300 g_free(current_folder
);
2303 #endif /* FILECHOOSER */
2304 if (dialog
->callback
)
2305 dialog
->callback(filename
, dialog
->data
);
2306 gtk_widget_destroy(dialog
->icon_filesel
);
2313 #if GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */
2314 icon_preview_change_cb(GtkFileChooser
*widget
, struct _icon_chooser
*dialog
)
2315 #else /* FILECHOOSER */
2316 icon_preview_change_cb(GtkTreeSelection
*sel
, struct _icon_chooser
*dialog
)
2317 #endif /* FILECHOOSER */
2319 GdkPixbuf
*pixbuf
, *scale
;
2321 char *basename
, *markup
, *size
;
2325 #if GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */
2326 filename
= gtk_file_chooser_get_preview_filename(
2327 GTK_FILE_CHOOSER(dialog
->icon_filesel
));
2328 #else /* FILECHOOSER */
2329 filename
= g_strdup(gtk_file_selection_get_filename(
2330 GTK_FILE_SELECTION(dialog
->icon_filesel
)));
2331 #endif /* FILECHOOSER */
2333 if (!filename
|| g_stat(filename
, &st
) || !(pixbuf
= gdk_pixbuf_new_from_file(filename
, NULL
)))
2335 gtk_image_set_from_pixbuf(GTK_IMAGE(dialog
->icon_preview
), NULL
);
2336 gtk_label_set_markup(GTK_LABEL(dialog
->icon_text
), "");
2341 width
= gdk_pixbuf_get_width(pixbuf
);
2342 height
= gdk_pixbuf_get_height(pixbuf
);
2343 basename
= g_path_get_basename(filename
);
2344 size
= purple_str_size_to_units(st
.st_size
);
2345 markup
= g_strdup_printf(_("<b>File:</b> %s\n"
2346 "<b>File size:</b> %s\n"
2347 "<b>Image size:</b> %dx%d"),
2348 basename
, size
, width
, height
);
2350 scale
= gdk_pixbuf_scale_simple(pixbuf
, width
* 50 / height
,
2351 50, GDK_INTERP_BILINEAR
);
2352 gtk_image_set_from_pixbuf(GTK_IMAGE(dialog
->icon_preview
), scale
);
2353 gtk_label_set_markup(GTK_LABEL(dialog
->icon_text
), markup
);
2355 g_object_unref(G_OBJECT(pixbuf
));
2356 g_object_unref(G_OBJECT(scale
));
2364 GtkWidget
*pidgin_buddy_icon_chooser_new(GtkWindow
*parent
, void(*callback
)(const char *, gpointer
), gpointer data
) {
2365 struct _icon_chooser
*dialog
= g_new0(struct _icon_chooser
, 1);
2367 #if GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */
2372 GtkTreeSelection
*sel
;
2373 #endif /* FILECHOOSER */
2374 const char *current_folder
;
2376 dialog
->callback
= callback
;
2377 dialog
->data
= data
;
2379 if (dialog
->icon_filesel
!= NULL
) {
2380 gtk_window_present(GTK_WINDOW(dialog
->icon_filesel
));
2384 current_folder
= purple_prefs_get_path(PIDGIN_PREFS_ROOT
"/filelocations/last_icon_folder");
2385 #if GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */
2387 dialog
->icon_filesel
= gtk_file_chooser_dialog_new(_("Buddy Icon"),
2389 GTK_FILE_CHOOSER_ACTION_OPEN
,
2390 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
2391 GTK_STOCK_OPEN
, GTK_RESPONSE_ACCEPT
,
2393 gtk_dialog_set_default_response(GTK_DIALOG(dialog
->icon_filesel
), GTK_RESPONSE_ACCEPT
);
2394 if ((current_folder
!= NULL
) && (*current_folder
!= '\0'))
2395 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog
->icon_filesel
),
2398 dialog
->icon_preview
= gtk_image_new();
2399 dialog
->icon_text
= gtk_label_new(NULL
);
2401 vbox
= gtk_vbox_new(FALSE
, PIDGIN_HIG_BOX_SPACE
);
2402 gtk_widget_set_size_request(GTK_WIDGET(vbox
), -1, 50);
2403 gtk_box_pack_start(GTK_BOX(vbox
), GTK_WIDGET(dialog
->icon_preview
), TRUE
, FALSE
, 0);
2404 gtk_box_pack_end(GTK_BOX(vbox
), GTK_WIDGET(dialog
->icon_text
), FALSE
, FALSE
, 0);
2405 gtk_widget_show_all(vbox
);
2407 gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog
->icon_filesel
), vbox
);
2408 gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(dialog
->icon_filesel
), TRUE
);
2409 gtk_file_chooser_set_use_preview_label(GTK_FILE_CHOOSER(dialog
->icon_filesel
), FALSE
);
2411 g_signal_connect(G_OBJECT(dialog
->icon_filesel
), "update-preview",
2412 G_CALLBACK(icon_preview_change_cb
), dialog
);
2413 g_signal_connect(G_OBJECT(dialog
->icon_filesel
), "response",
2414 G_CALLBACK(icon_filesel_choose_cb
), dialog
);
2415 icon_preview_change_cb(NULL
, dialog
);
2416 #else /* FILECHOOSER */
2417 dialog
->icon_filesel
= gtk_file_selection_new(_("Buddy Icon"));
2418 dialog
->icon_preview
= gtk_image_new();
2419 dialog
->icon_text
= gtk_label_new(NULL
);
2420 if ((current_folder
!= NULL
) && (*current_folder
!= '\0'))
2421 gtk_file_selection_set_filename(GTK_FILE_SELECTION(dialog
->icon_filesel
),
2424 gtk_widget_set_size_request(GTK_WIDGET(dialog
->icon_preview
),-1, 50);
2425 hbox
= gtk_hbox_new(FALSE
, PIDGIN_HIG_BOX_SPACE
);
2427 GTK_BOX(GTK_FILE_SELECTION(dialog
->icon_filesel
)->main_vbox
),
2428 hbox
, FALSE
, FALSE
, 0);
2429 gtk_box_pack_end(GTK_BOX(hbox
), dialog
->icon_preview
,
2431 gtk_box_pack_end(GTK_BOX(hbox
), dialog
->icon_text
, FALSE
, FALSE
, 0);
2433 tv
= GTK_FILE_SELECTION(dialog
->icon_filesel
)->file_list
;
2434 sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(tv
));
2436 g_signal_connect(G_OBJECT(sel
), "changed",
2437 G_CALLBACK(icon_preview_change_cb
), dialog
);
2439 G_OBJECT(GTK_FILE_SELECTION(dialog
->icon_filesel
)->ok_button
),
2441 G_CALLBACK(icon_filesel_choose_cb
), dialog
);
2443 G_OBJECT(GTK_FILE_SELECTION(dialog
->icon_filesel
)->cancel_button
),
2445 G_CALLBACK(icon_filesel_delete_cb
), dialog
);
2446 g_signal_connect(G_OBJECT(dialog
->icon_filesel
), "destroy",
2447 G_CALLBACK(icon_filesel_delete_cb
), dialog
);
2448 #endif /* FILECHOOSER */
2449 return dialog
->icon_filesel
;
2453 #if GTK_CHECK_VERSION(2,2,0)
2455 str_array_match(char **a
, char **b
)
2461 for (i
= 0; a
[i
] != NULL
; i
++)
2462 for (j
= 0; b
[j
] != NULL
; j
++)
2463 if (!g_ascii_strcasecmp(a
[i
], b
[j
]))
2470 pidgin_convert_buddy_icon(PurplePlugin
*plugin
, const char *path
, size_t *len
)
2472 PurplePluginProtocolInfo
*prpl_info
;
2473 #if GTK_CHECK_VERSION(2,2,0)
2474 char **prpl_formats
;
2476 char **pixbuf_formats
= NULL
;
2477 GdkPixbufFormat
*format
;
2479 #if !GTK_CHECK_VERSION(2,4,0)
2480 GdkPixbufLoader
*loader
;
2486 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(plugin
);
2488 g_return_val_if_fail(prpl_info
->icon_spec
.format
!= NULL
, NULL
);
2491 #if GTK_CHECK_VERSION(2,2,0)
2492 #if GTK_CHECK_VERSION(2,4,0)
2493 format
= gdk_pixbuf_get_file_info(path
, &width
, &height
);
2495 loader
= gdk_pixbuf_loader_new();
2496 if (g_file_get_contents(path
, &contents
, &length
, NULL
)) {
2497 gdk_pixbuf_loader_write(loader
, contents
, length
, NULL
);
2500 gdk_pixbuf_loader_close(loader
, NULL
);
2501 pixbuf
= gdk_pixbuf_loader_get_pixbuf(loader
);
2502 width
= gdk_pixbuf_get_width(pixbuf
);
2503 height
= gdk_pixbuf_get_height(pixbuf
);
2504 format
= gdk_pixbuf_loader_get_format(loader
);
2505 g_object_unref(G_OBJECT(loader
));
2510 pixbuf_formats
= gdk_pixbuf_format_get_extensions(format
);
2511 prpl_formats
= g_strsplit(prpl_info
->icon_spec
.format
,",",0);
2512 if (str_array_match(pixbuf_formats
, prpl_formats
) && /* This is an acceptable format AND */
2513 (!(prpl_info
->icon_spec
.scale_rules
& PURPLE_ICON_SCALE_SEND
) || /* The prpl doesn't scale before it sends OR */
2514 (prpl_info
->icon_spec
.min_width
<= width
&&
2515 prpl_info
->icon_spec
.max_width
>= width
&&
2516 prpl_info
->icon_spec
.min_height
<= height
&&
2517 prpl_info
->icon_spec
.max_height
>= height
))) /* The icon is the correct size */
2520 #if GTK_CHECK_VERSION(2,2,0)
2521 g_strfreev(prpl_formats
);
2522 g_strfreev(pixbuf_formats
);
2524 /* We don't need to scale the image. */
2527 if (!g_file_get_contents(path
, &contents
, &length
, NULL
))
2530 #if GTK_CHECK_VERSION(2,2,0) && !GTK_CHECK_VERSION(2,4,0)
2531 g_object_unref(G_OBJECT(pixbuf
));
2536 #if GTK_CHECK_VERSION(2,2,0)
2540 GError
*error
= NULL
;
2542 gboolean success
= FALSE
;
2543 char *filename
= NULL
;
2545 g_strfreev(pixbuf_formats
);
2547 pixbuf
= gdk_pixbuf_new_from_file(path
, &error
);
2549 purple_debug_error("buddyicon", "Could not open icon for conversion: %s\n", error
->message
);
2550 g_error_free(error
);
2551 g_strfreev(prpl_formats
);
2555 if ((prpl_info
->icon_spec
.scale_rules
& PURPLE_ICON_SCALE_SEND
) &&
2556 (width
< prpl_info
->icon_spec
.min_width
||
2557 width
> prpl_info
->icon_spec
.max_width
||
2558 height
< prpl_info
->icon_spec
.min_height
||
2559 height
> prpl_info
->icon_spec
.max_height
))
2561 int new_width
= width
;
2562 int new_height
= height
;
2564 purple_buddy_icon_get_scale_size(&prpl_info
->icon_spec
, &new_width
, &new_height
);
2566 scale
= gdk_pixbuf_scale_simple(pixbuf
, new_width
, new_height
,
2568 g_object_unref(G_OBJECT(pixbuf
));
2572 for (i
= 0; prpl_formats
[i
]; i
++) {
2576 fp
= purple_mkstemp(&filename
, TRUE
);
2584 purple_debug_info("buddyicon", "Converting buddy icon to %s as %s\n", prpl_formats
[i
], filename
);
2585 /* The "compression" param wasn't supported until gdk-pixbuf 2.8.
2586 * Using it in previous versions causes the save to fail (and an assert message). */
2587 if ((gdk_pixbuf_major_version
> 2 || (gdk_pixbuf_major_version
== 2
2588 && gdk_pixbuf_minor_version
>= 8))
2589 && strcmp(prpl_formats
[i
], "png") == 0) {
2590 if (gdk_pixbuf_save(pixbuf
, filename
, prpl_formats
[i
],
2591 &error
, "compression", "9", NULL
)) {
2595 } else if (gdk_pixbuf_save(pixbuf
, filename
, prpl_formats
[i
],
2601 /* The NULL checking is necessary due to this bug:
2602 * http://bugzilla.gnome.org/show_bug.cgi?id=405539 */
2603 purple_debug_warning("buddyicon", "Could not convert to %s: %s\n", prpl_formats
[i
],
2604 (error
&& error
->message
) ? error
->message
: "Unknown error");
2605 g_error_free(error
);
2608 g_strfreev(prpl_formats
);
2609 g_object_unref(G_OBJECT(pixbuf
));
2611 purple_debug_error("buddyicon", "Could not convert icon to usable format.\n");
2616 if (!g_file_get_contents(filename
, &contents
, &length
, NULL
))
2618 purple_debug_error("buddyicon",
2619 "Could not read '%s', which we just wrote to disk.\n",
2631 /* Check the image size */
2633 * TODO: If the file is too big, it would be cool if we checked if
2634 * the prpl supported jpeg, and then we could convert to that
2635 * and use a lower quality setting.
2637 if ((prpl_info
->icon_spec
.max_filesize
!= 0) &&
2638 (length
> prpl_info
->icon_spec
.max_filesize
))
2641 tmp
= g_strdup_printf(_("The file '%s' is too large for %s. Please try a smaller image.\n"),
2642 path
, plugin
->info
->name
);
2643 purple_notify_error(NULL
, _("Icon Error"),
2644 _("Could not set icon"), tmp
);
2645 purple_debug_info("buddyicon",
2646 "'%s' was converted to an image which is %" G_GSIZE_FORMAT
2647 " bytes, but the maximum icon size for %s is %" G_GSIZE_FORMAT
2648 " bytes\n", path
, length
, plugin
->info
->name
,
2649 prpl_info
->icon_spec
.max_filesize
);
2659 * The chosen icon wasn't the right size, and we're using
2660 * GTK+ 2.0 so we can't scale it.
2666 #if !GTK_CHECK_VERSION(2,6,0)
2668 _gdk_file_scale_size_prepared_cb (GdkPixbufLoader
*loader
,
2676 gboolean preserve_aspect_ratio
;
2679 g_return_if_fail (width
> 0 && height
> 0);
2681 if (info
->preserve_aspect_ratio
&&
2682 (info
->width
> 0 || info
->height
> 0)) {
2683 if (info
->width
< 0)
2685 width
= width
* (double)info
->height
/(double)height
;
2686 height
= info
->height
;
2688 else if (info
->height
< 0)
2690 height
= height
* (double)info
->width
/(double)width
;
2691 width
= info
->width
;
2693 else if ((double)height
* (double)info
->width
>
2694 (double)width
* (double)info
->height
) {
2695 width
= 0.5 + (double)width
* (double)info
->height
/ (double)height
;
2696 height
= info
->height
;
2698 height
= 0.5 + (double)height
* (double)info
->width
/ (double)width
;
2699 width
= info
->width
;
2702 if (info
->width
> 0)
2703 width
= info
->width
;
2704 if (info
->height
> 0)
2705 height
= info
->height
;
2708 #if GTK_CHECK_VERSION(2,2,0) /* 2.0 users are going to have very strangely sized things */
2709 gdk_pixbuf_loader_set_size (loader
, width
, height
);
2711 #warning nosnilmot could not be bothered to fix this properly for you
2712 #warning ... good luck ... your images may end up strange sizes
2717 gdk_pixbuf_new_from_file_at_scale(const char *filename
, int width
, int height
,
2718 gboolean preserve_aspect_ratio
,
2721 GdkPixbufLoader
*loader
;
2723 guchar buffer
[4096];
2729 gboolean preserve_aspect_ratio
;
2731 GdkPixbufAnimation
*animation
;
2732 GdkPixbufAnimationIter
*iter
;
2735 g_return_val_if_fail (filename
!= NULL
, NULL
);
2736 g_return_val_if_fail (width
> 0 || width
== -1, NULL
);
2737 g_return_val_if_fail (height
> 0 || height
== -1, NULL
);
2739 f
= g_fopen (filename
, "rb");
2741 gint save_errno
= errno
;
2742 gchar
*display_name
= g_filename_to_utf8 (filename
, -1, NULL
, NULL
, NULL
);
2743 g_set_error (error
, G_FILE_ERROR
, g_file_error_from_errno (save_errno
),
2744 _("Failed to open file '%s': %s"),
2745 display_name
? display_name
: "(unknown)",
2746 g_strerror (save_errno
));
2747 g_free (display_name
);
2751 loader
= gdk_pixbuf_loader_new ();
2754 info
.height
= height
;
2755 info
.preserve_aspect_ratio
= preserve_aspect_ratio
;
2757 g_signal_connect (loader
, "size-prepared", G_CALLBACK (_gdk_file_scale_size_prepared_cb
), &info
);
2760 while (!has_frame
&& !feof (f
) && !ferror (f
)) {
2761 length
= fread (buffer
, 1, sizeof (buffer
), f
);
2763 if (!gdk_pixbuf_loader_write (loader
, buffer
, length
, error
)) {
2764 gdk_pixbuf_loader_close (loader
, NULL
);
2766 g_object_unref (loader
);
2770 animation
= gdk_pixbuf_loader_get_animation (loader
);
2772 iter
= gdk_pixbuf_animation_get_iter (animation
, 0);
2773 if (!gdk_pixbuf_animation_iter_on_currently_loading_frame (iter
)) {
2776 g_object_unref (iter
);
2782 if (!gdk_pixbuf_loader_close (loader
, error
) && !has_frame
) {
2783 g_object_unref (loader
);
2787 pixbuf
= gdk_pixbuf_loader_get_pixbuf (loader
);
2790 gchar
*display_name
= g_filename_to_utf8 (filename
, -1, NULL
, NULL
, NULL
);
2791 g_object_unref (loader
);
2792 g_set_error (error
, GDK_PIXBUF_ERROR
, GDK_PIXBUF_ERROR_FAILED
,
2793 _("Failed to load image '%s': reason not known, probably a corrupt image file"),
2794 display_name
? display_name
: "(unknown)");
2795 g_free (display_name
);
2799 g_object_ref (pixbuf
);
2801 g_object_unref (loader
);
2805 #endif /* ! Gtk 2.6.0 */
2807 void pidgin_set_custom_buddy_icon(PurpleAccount
*account
, const char *who
, const char *filename
)
2810 PurpleContact
*contact
;
2811 gpointer data
= NULL
;
2814 buddy
= purple_find_buddy(account
, who
);
2816 purple_debug_info("custom-icon", "You can only set custom icon for someone in your buddylist.\n");
2820 contact
= purple_buddy_get_contact(buddy
);
2823 const char *prpl_id
= purple_account_get_protocol_id(account
);
2824 PurplePlugin
*prpl
= purple_find_prpl(prpl_id
);
2826 data
= pidgin_convert_buddy_icon(prpl
, filename
, &len
);
2828 /* We don't want to delete the old icon if the new one didn't load. */
2833 purple_buddy_icons_set_custom_icon(contact
, data
, len
);
2836 char *pidgin_make_pretty_arrows(const char *str
)
2839 char **split
= g_strsplit(str
, "->", -1);
2840 ret
= g_strjoinv("\342\207\250", split
);
2843 split
= g_strsplit(ret
, "<-", -1);
2845 ret
= g_strjoinv("\342\207\246", split
);
2851 void pidgin_set_urgent(GtkWindow
*window
, gboolean urgent
)
2853 #if GTK_CHECK_VERSION(2,8,0)
2854 gtk_window_set_urgency_hint(window
, urgent
);
2855 #elif defined _WIN32
2856 winpidgin_window_flash(window
, urgent
);
2861 g_return_if_fail(window
!= NULL
);
2863 gdkwin
= GTK_WIDGET(window
)->window
;
2865 g_return_if_fail(gdkwin
!= NULL
);
2867 hints
= XGetWMHints(GDK_WINDOW_XDISPLAY(gdkwin
),
2868 GDK_WINDOW_XWINDOW(gdkwin
));
2870 hints
= XAllocWMHints();
2873 hints
->flags
|= XUrgencyHint
;
2875 hints
->flags
&= ~XUrgencyHint
;
2876 XSetWMHints(GDK_WINDOW_XDISPLAY(gdkwin
),
2877 GDK_WINDOW_XWINDOW(gdkwin
), hints
);
2882 GSList
*minidialogs
= NULL
;
2885 pidgin_utils_get_handle()
2892 static void connection_signed_off_cb(PurpleConnection
*gc
)
2895 for (list
= minidialogs
; list
; list
= list
->next
) {
2896 if (g_object_get_data(G_OBJECT(list
->data
), "gc") == gc
) {
2897 gtk_widget_destroy(GTK_WIDGET(list
->data
));
2902 static void alert_killed_cb(GtkWidget
*widget
)
2904 minidialogs
= g_slist_remove(minidialogs
, widget
);
2907 void *pidgin_make_mini_dialog(PurpleConnection
*gc
, const char *icon_name
,
2908 const char *primary
, const char *secondary
,
2909 void *user_data
, ...)
2916 GtkWidget
*img
= NULL
;
2917 GtkSizeGroup
*sg
= gtk_size_group_new(GTK_SIZE_GROUP_BOTH
);
2918 char label_text
[2048];
2919 const char *button_text
;
2921 char *primary_esc
, *secondary_esc
= NULL
;
2923 static gboolean first_call
= TRUE
;
2925 img
= gtk_image_new_from_stock(icon_name
, gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
));
2926 gtk_misc_set_alignment(GTK_MISC(img
), 0, 0);
2928 vbox
= gtk_vbox_new(FALSE
,0);
2929 gtk_container_set_border_width(GTK_CONTAINER(vbox
), PIDGIN_HIG_BOX_SPACE
);
2931 g_object_set_data(G_OBJECT(vbox
), "gc" ,gc
);
2932 minidialogs
= g_slist_prepend(minidialogs
, vbox
);
2933 g_signal_connect(G_OBJECT(vbox
), "destroy", G_CALLBACK(alert_killed_cb
), NULL
);
2937 purple_signal_connect(purple_connections_get_handle(), "signed-off",
2938 pidgin_utils_get_handle(),
2939 PURPLE_CALLBACK(connection_signed_off_cb
), NULL
);
2942 hbox
= gtk_hbox_new(FALSE
, 0);
2943 gtk_container_add(GTK_CONTAINER(vbox
), hbox
);
2946 gtk_box_pack_start(GTK_BOX(hbox
), img
, FALSE
, FALSE
, 0);
2948 primary_esc
= g_markup_escape_text(primary
, -1);
2951 secondary_esc
= g_markup_escape_text(secondary
, -1);
2952 g_snprintf(label_text
, sizeof(label_text
),
2953 "<span weight=\"bold\" size=\"smaller\">%s</span>%s<span size=\"smaller\">%s</span>",
2954 primary_esc
, secondary
? "\n" : "", secondary_esc
? secondary_esc
: "");
2955 g_free(primary_esc
);
2956 g_free(secondary_esc
);
2957 label
= gtk_label_new(NULL
);
2958 gtk_widget_set_size_request(label
, purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/blist/width")-25,-1);
2959 gtk_label_set_markup(GTK_LABEL(label
), label_text
);
2960 gtk_label_set_line_wrap(GTK_LABEL(label
), TRUE
);
2961 gtk_misc_set_alignment(GTK_MISC(label
), 0, 0);
2962 gtk_box_pack_start(GTK_BOX(hbox
), label
, TRUE
, TRUE
, 0);
2964 hbox2
= gtk_hbox_new(FALSE
, PIDGIN_HIG_BOX_SPACE
);
2965 gtk_box_pack_start(GTK_BOX(vbox
), hbox2
, FALSE
, FALSE
, 0);
2967 va_start(args
, user_data
);
2968 while ((button_text
= va_arg(args
, char*))) {
2969 callback
= va_arg(args
, GCallback
);
2970 button
= gtk_button_new();
2973 g_signal_connect_swapped(G_OBJECT(button
), "clicked", callback
, user_data
);
2974 g_signal_connect_swapped(G_OBJECT(button
), "clicked", G_CALLBACK(gtk_widget_destroy
), vbox
);
2975 hbox
= gtk_hbox_new(FALSE
, 0);
2976 gtk_container_add(GTK_CONTAINER(button
), hbox
);
2977 gtk_container_set_border_width(GTK_CONTAINER(hbox
), 3);
2978 g_snprintf(label_text
, sizeof(label_text
),
2979 "<span size=\"smaller\">%s</span>", button_text
);
2980 label
= gtk_label_new(NULL
);
2981 gtk_label_set_markup_with_mnemonic(GTK_LABEL(label
), label_text
);
2982 gtk_misc_set_alignment(GTK_MISC(label
), 0.5, 0.5);
2983 gtk_box_pack_start(GTK_BOX(hbox
), label
, TRUE
, TRUE
, 0);
2984 gtk_box_pack_end(GTK_BOX(hbox2
), button
, FALSE
, FALSE
, 0);
2985 gtk_size_group_add_widget(sg
, button
);
2993 * "This is so dead sexy."
2995 * "Best movie of the year."
2997 * This is the function that handles CTRL+F searching in the buddy list.
2998 * It finds the top-most buddy/group/chat/whatever containing the
3001 * It's somewhat ineffecient, because we strip all the HTML from the
3002 * "name" column of the buddy list (because the GtkTreeModel does not
3003 * contain the screen name in a non-markedup format). But the alternative
3004 * is to add an extra column to the GtkTreeModel. And this function is
3005 * used rarely, so it shouldn't matter TOO much.
3007 gboolean
pidgin_tree_view_search_equal_func(GtkTreeModel
*model
, gint column
,
3008 const gchar
*key
, GtkTreeIter
*iter
, gpointer data
)
3010 gchar
*enteredstring
;
3018 PangoLogAttr
*log_attrs
;
3021 if (g_ascii_strcasecmp(key
, "Global Thermonuclear War") == 0)
3023 purple_notify_info(NULL
, "WOPR",
3024 "Wouldn't you prefer a nice game of chess?", NULL
);
3028 gtk_tree_model_get(model
, iter
, column
, &withmarkup
, -1);
3029 if (withmarkup
== NULL
) /* This is probably a separator */
3032 tmp
= g_utf8_normalize(key
, -1, G_NORMALIZE_DEFAULT
);
3033 enteredstring
= g_utf8_casefold(tmp
, -1);
3036 nomarkup
= purple_markup_strip_html(withmarkup
);
3037 tmp
= g_utf8_normalize(nomarkup
, -1, G_NORMALIZE_DEFAULT
);
3039 normalized
= g_utf8_casefold(tmp
, -1);
3042 if (purple_str_has_prefix(normalized
, enteredstring
))
3045 g_free(enteredstring
);
3051 /* Use Pango to separate by words. */
3052 len
= g_utf8_strlen(normalized
, -1);
3053 log_attrs
= g_new(PangoLogAttr
, len
+ 1);
3055 pango_get_log_attrs(normalized
, strlen(normalized
), -1, NULL
, log_attrs
, len
+ 1);
3059 for (i
= 0; i
< (len
- 1) ; i
++)
3061 if (log_attrs
[i
].is_word_start
&&
3062 purple_str_has_prefix(word
, enteredstring
))
3067 word
= g_utf8_next_char(word
);
3071 /* The non-Pango version. */
3075 while (word
[0] != '\0')
3077 gunichar c
= g_utf8_get_char(word
);
3078 if (!g_unichar_isalnum(c
))
3080 word
= g_utf8_find_next_char(word
, NULL
);
3081 if (purple_str_has_prefix(word
, enteredstring
))
3088 word
= g_utf8_find_next_char(word
, NULL
);
3093 g_free(enteredstring
);
3100 gboolean
pidgin_gdk_pixbuf_is_opaque(GdkPixbuf
*pixbuf
) {
3101 int width
, height
, rowstride
, i
;
3102 unsigned char *pixels
;
3105 if (!gdk_pixbuf_get_has_alpha(pixbuf
))
3108 width
= gdk_pixbuf_get_width (pixbuf
);
3109 height
= gdk_pixbuf_get_height (pixbuf
);
3110 rowstride
= gdk_pixbuf_get_rowstride (pixbuf
);
3111 pixels
= gdk_pixbuf_get_pixels (pixbuf
);
3114 for (i
= 3; i
< rowstride
; i
+=4) {
3119 for (i
= 1; i
< height
- 1; i
++) {
3120 row
= pixels
+ (i
*rowstride
);
3121 if (row
[3] < 0xfe || row
[rowstride
-1] < 0xfe) {
3126 row
= pixels
+ ((height
-1) * rowstride
);
3127 for (i
= 3; i
< rowstride
; i
+=4) {
3135 void pidgin_gdk_pixbuf_make_round(GdkPixbuf
*pixbuf
) {
3136 int width
, height
, rowstride
;
3138 if (!gdk_pixbuf_get_has_alpha(pixbuf
))
3140 width
= gdk_pixbuf_get_width(pixbuf
);
3141 height
= gdk_pixbuf_get_height(pixbuf
);
3142 rowstride
= gdk_pixbuf_get_rowstride(pixbuf
);
3143 pixels
= gdk_pixbuf_get_pixels(pixbuf
);
3145 if (width
< 6 || height
< 6)
3151 pixels
[rowstride
+ 3] = 0x80;
3152 pixels
[rowstride
* 2 + 3] = 0xC0;
3155 pixels
[width
* 4 - 1] = 0;
3156 pixels
[width
* 4 - 5] = 0x80;
3157 pixels
[width
* 4 - 9] = 0xC0;
3158 pixels
[rowstride
+ (width
* 4) - 1] = 0x80;
3159 pixels
[(2 * rowstride
) + (width
* 4) - 1] = 0xC0;
3162 pixels
[(height
- 1) * rowstride
+ 3] = 0;
3163 pixels
[(height
- 1) * rowstride
+ 7] = 0x80;
3164 pixels
[(height
- 1) * rowstride
+ 11] = 0xC0;
3165 pixels
[(height
- 2) * rowstride
+ 3] = 0x80;
3166 pixels
[(height
- 3) * rowstride
+ 3] = 0xC0;
3169 pixels
[height
* rowstride
- 1] = 0;
3170 pixels
[(height
- 1) * rowstride
- 1] = 0x80;
3171 pixels
[(height
- 2) * rowstride
- 1] = 0xC0;
3172 pixels
[height
* rowstride
- 5] = 0x80;
3173 pixels
[height
* rowstride
- 9] = 0xC0;
3176 const char *pidgin_get_dim_grey_string(GtkWidget
*widget
) {
3177 static char dim_grey_string
[8] = "";
3183 style
= gtk_widget_get_style(widget
);
3187 snprintf(dim_grey_string
, sizeof(dim_grey_string
), "#%02x%02x%02x",
3188 style
->text_aa
[GTK_STATE_NORMAL
].red
>> 8,
3189 style
->text_aa
[GTK_STATE_NORMAL
].green
>> 8,
3190 style
->text_aa
[GTK_STATE_NORMAL
].blue
>> 8);
3191 return dim_grey_string
;
3194 #if !GTK_CHECK_VERSION(2,2,0)
3196 gtk_tree_path_new_from_indices (gint first_index
, ...)
3202 path
= gtk_tree_path_new ();
3204 va_start (args
, first_index
);
3209 gtk_tree_path_append_index (path
, arg
);
3210 arg
= va_arg (args
, gint
);
3220 combo_box_changed_cb(GtkComboBox
*combo_box
, GtkEntry
*entry
)
3222 char *text
= gtk_combo_box_get_active_text(combo_box
);
3223 gtk_entry_set_text(entry
, text
? text
: "");
3228 entry_key_pressed_cb(GtkWidget
*entry
, GdkEventKey
*key
, GtkComboBox
*combo
)
3230 if (key
->keyval
== GDK_Down
|| key
->keyval
== GDK_Up
) {
3231 gtk_combo_box_popup(combo
);
3238 pidgin_text_combo_box_entry_new(const char *default_item
, GList
*items
)
3240 GtkComboBox
*ret
= NULL
;
3241 GtkWidget
*the_entry
= NULL
;
3243 ret
= GTK_COMBO_BOX(gtk_combo_box_new_text());
3244 the_entry
= gtk_entry_new();
3245 gtk_container_add(GTK_CONTAINER(ret
), the_entry
);
3248 gtk_entry_set_text(GTK_ENTRY(the_entry
), default_item
);
3250 for (; items
!= NULL
; items
= items
->next
) {
3251 char *text
= items
->data
;
3253 gtk_combo_box_append_text(ret
, text
);
3256 g_signal_connect(G_OBJECT(ret
), "changed", (GCallback
)combo_box_changed_cb
, the_entry
);
3257 g_signal_connect_after(G_OBJECT(the_entry
), "key-press-event", G_CALLBACK(entry_key_pressed_cb
), ret
);
3259 return GTK_WIDGET(ret
);
3262 const char *pidgin_text_combo_box_entry_get_text(GtkWidget
*widget
)
3264 return gtk_entry_get_text(GTK_ENTRY(GTK_BIN((widget
))->child
));
3267 void pidgin_text_combo_box_entry_set_text(GtkWidget
*widget
, const char *text
)
3269 gtk_entry_set_text(GTK_ENTRY(GTK_BIN((widget
))->child
), (text
));