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 "gtkdialogs.h"
61 #include "gtkimhtml.h"
62 #include "gtkimhtmltoolbar.h"
63 #include "pidginstock.h"
64 #include "gtkthemes.h"
66 #include "pidgin/minidialog.h"
73 static guint accels_save_timer
= 0;
76 url_clicked_idle_cb(gpointer data
)
78 purple_notify_uri(NULL
, data
);
84 url_clicked_cb(GtkWidget
*w
, const char *uri
)
86 g_idle_add(url_clicked_idle_cb
, g_strdup(uri
));
89 static GtkIMHtmlFuncs gtkimhtml_cbs
= {
90 (GtkIMHtmlGetImageFunc
)purple_imgstore_find_by_id
,
91 (GtkIMHtmlGetImageDataFunc
)purple_imgstore_get_data
,
92 (GtkIMHtmlGetImageSizeFunc
)purple_imgstore_get_size
,
93 (GtkIMHtmlGetImageFilenameFunc
)purple_imgstore_get_filename
,
94 purple_imgstore_ref_by_id
,
95 purple_imgstore_unref_by_id
,
99 pidgin_setup_imhtml(GtkWidget
*imhtml
)
101 PangoFontDescription
*desc
= NULL
;
102 g_return_if_fail(imhtml
!= NULL
);
103 g_return_if_fail(GTK_IS_IMHTML(imhtml
));
105 g_signal_connect(G_OBJECT(imhtml
), "url_clicked",
106 G_CALLBACK(url_clicked_cb
), NULL
);
108 pidgin_themes_smiley_themeize(imhtml
);
110 gtk_imhtml_set_funcs(GTK_IMHTML(imhtml
), >kimhtml_cbs
);
112 if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/use_theme_font")) {
113 const char *font
= purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/custom_font");
114 desc
= pango_font_description_from_string(font
);
115 } else if (purple_running_gnome()) {
116 /* Use the GNOME "document" font, if applicable */
119 if ((path
= g_find_program_in_path("gconftool-2"))) {
123 if (g_spawn_command_line_sync(
124 "gconftool-2 -g /desktop/gnome/interface/document_font_name",
125 &font
, &err
, NULL
, NULL
)) {
126 desc
= pango_font_description_from_string(font
);
134 gtk_widget_modify_font(imhtml
, desc
);
135 pango_font_description_free(desc
);
140 void pidgin_window_init(GtkWindow
*wnd
, const char *title
, guint border_width
, const char *role
, gboolean resizable
)
143 gtk_window_set_title(wnd
, title
);
146 gtk_window_set_title(wnd
, PIDGIN_ALERT_TITLE
);
148 gtk_container_set_border_width(GTK_CONTAINER(wnd
), border_width
);
150 gtk_window_set_role(wnd
, role
);
151 gtk_window_set_resizable(wnd
, resizable
);
155 pidgin_create_window(const char *title
, guint border_width
, const char *role
, gboolean resizable
)
157 GtkWindow
*wnd
= NULL
;
159 wnd
= GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL
));
160 pidgin_window_init(wnd
, title
, border_width
, role
, resizable
);
162 return GTK_WIDGET(wnd
);
166 pidgin_create_dialog(const char *title
, guint border_width
, const char *role
, gboolean resizable
)
168 GtkWindow
*wnd
= NULL
;
170 wnd
= GTK_WINDOW(gtk_dialog_new());
171 pidgin_window_init(wnd
, title
, border_width
, role
, resizable
);
172 g_object_set(G_OBJECT(wnd
), "has-separator", FALSE
, NULL
);
174 return GTK_WIDGET(wnd
);
178 pidgin_dialog_get_vbox_with_properties(GtkDialog
*dialog
, gboolean homogeneous
, gint spacing
)
180 GtkBox
*vbox
= GTK_BOX(GTK_DIALOG(dialog
)->vbox
);
181 gtk_box_set_homogeneous(vbox
, homogeneous
);
182 gtk_box_set_spacing(vbox
, spacing
);
183 return GTK_WIDGET(vbox
);
186 GtkWidget
*pidgin_dialog_get_vbox(GtkDialog
*dialog
)
188 return GTK_DIALOG(dialog
)->vbox
;
191 GtkWidget
*pidgin_dialog_get_action_area(GtkDialog
*dialog
)
193 return GTK_DIALOG(dialog
)->action_area
;
196 GtkWidget
*pidgin_dialog_add_button(GtkDialog
*dialog
, const char *label
,
197 GCallback callback
, gpointer callbackdata
)
199 GtkWidget
*button
= gtk_button_new_from_stock(label
);
200 GtkWidget
*bbox
= pidgin_dialog_get_action_area(dialog
);
201 gtk_box_pack_start(GTK_BOX(bbox
), button
, FALSE
, FALSE
, 0);
203 g_signal_connect(G_OBJECT(button
), "clicked", callback
, callbackdata
);
204 gtk_widget_show(button
);
209 pidgin_create_imhtml(gboolean editable
, GtkWidget
**imhtml_ret
, GtkWidget
**toolbar_ret
, GtkWidget
**sw_ret
)
215 GtkWidget
*toolbar
= NULL
;
218 frame
= gtk_frame_new(NULL
);
219 gtk_frame_set_shadow_type(GTK_FRAME(frame
), GTK_SHADOW_IN
);
221 vbox
= gtk_vbox_new(FALSE
, 0);
222 gtk_container_add(GTK_CONTAINER(frame
), vbox
);
223 gtk_widget_show(vbox
);
226 toolbar
= gtk_imhtmltoolbar_new();
227 gtk_box_pack_start(GTK_BOX(vbox
), toolbar
, FALSE
, FALSE
, 0);
228 gtk_widget_show(toolbar
);
230 sep
= gtk_hseparator_new();
231 gtk_box_pack_start(GTK_BOX(vbox
), sep
, FALSE
, FALSE
, 0);
232 g_signal_connect_swapped(G_OBJECT(toolbar
), "show", G_CALLBACK(gtk_widget_show
), sep
);
233 g_signal_connect_swapped(G_OBJECT(toolbar
), "hide", G_CALLBACK(gtk_widget_hide
), sep
);
234 gtk_widget_show(sep
);
237 sw
= gtk_scrolled_window_new(NULL
, NULL
);
238 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw
),
239 GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
);
240 gtk_box_pack_start(GTK_BOX(vbox
), sw
, TRUE
, TRUE
, 0);
243 imhtml
= gtk_imhtml_new(NULL
, NULL
);
244 gtk_imhtml_set_editable(GTK_IMHTML(imhtml
), editable
);
245 gtk_imhtml_set_format_functions(GTK_IMHTML(imhtml
), GTK_IMHTML_ALL
^ GTK_IMHTML_IMAGE
);
246 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(imhtml
), GTK_WRAP_WORD_CHAR
);
248 if (editable
&& purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/spellcheck"))
249 pidgin_setup_gtkspell(GTK_TEXT_VIEW(imhtml
));
251 gtk_widget_show(imhtml
);
254 gtk_imhtmltoolbar_attach(GTK_IMHTMLTOOLBAR(toolbar
), imhtml
);
255 gtk_imhtmltoolbar_associate_smileys(GTK_IMHTMLTOOLBAR(toolbar
), "default");
257 pidgin_setup_imhtml(imhtml
);
259 gtk_container_add(GTK_CONTAINER(sw
), imhtml
);
261 if (imhtml_ret
!= NULL
)
262 *imhtml_ret
= imhtml
;
264 if (editable
&& (toolbar_ret
!= NULL
))
265 *toolbar_ret
= toolbar
;
274 pidgin_set_sensitive_if_input(GtkWidget
*entry
, GtkWidget
*dialog
)
276 const char *text
= gtk_entry_get_text(GTK_ENTRY(entry
));
277 gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog
), GTK_RESPONSE_OK
,
282 pidgin_toggle_sensitive(GtkWidget
*widget
, GtkWidget
*to_toggle
)
284 gboolean sensitivity
;
286 if (to_toggle
== NULL
)
289 sensitivity
= GTK_WIDGET_IS_SENSITIVE(to_toggle
);
291 gtk_widget_set_sensitive(to_toggle
, !sensitivity
);
295 pidgin_toggle_sensitive_array(GtkWidget
*w
, GPtrArray
*data
)
297 gboolean sensitivity
;
301 for (i
=0; i
< data
->len
; i
++) {
302 element
= g_ptr_array_index(data
,i
);
306 sensitivity
= GTK_WIDGET_IS_SENSITIVE(element
);
308 gtk_widget_set_sensitive(element
, !sensitivity
);
313 pidgin_toggle_showhide(GtkWidget
*widget
, GtkWidget
*to_toggle
)
315 if (to_toggle
== NULL
)
318 if (GTK_WIDGET_VISIBLE(to_toggle
))
319 gtk_widget_hide(to_toggle
);
321 gtk_widget_show(to_toggle
);
324 GtkWidget
*pidgin_separator(GtkWidget
*menu
)
328 menuitem
= gtk_separator_menu_item_new();
329 gtk_widget_show(menuitem
);
330 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
334 GtkWidget
*pidgin_new_item(GtkWidget
*menu
, const char *str
)
339 menuitem
= gtk_menu_item_new();
341 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
342 gtk_widget_show(menuitem
);
344 label
= gtk_label_new(str
);
345 gtk_misc_set_alignment(GTK_MISC(label
), 0, 0.5);
346 gtk_label_set_pattern(GTK_LABEL(label
), "_");
347 gtk_container_add(GTK_CONTAINER(menuitem
), label
);
348 gtk_widget_show(label
);
349 /* FIXME: Go back and fix this
350 gtk_widget_add_accelerator(menuitem, "activate", accel, str[0],
351 GDK_MOD1_MASK, GTK_ACCEL_LOCKED);
353 pidgin_set_accessible_label (menuitem
, label
);
357 GtkWidget
*pidgin_new_check_item(GtkWidget
*menu
, const char *str
,
358 GtkSignalFunc sf
, gpointer data
, gboolean checked
)
361 menuitem
= gtk_check_menu_item_new_with_mnemonic(str
);
364 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
366 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem
), checked
);
369 g_signal_connect(G_OBJECT(menuitem
), "activate", sf
, data
);
371 gtk_widget_show_all(menuitem
);
377 pidgin_pixbuf_toolbar_button_from_stock(const char *icon
)
379 GtkWidget
*button
, *image
, *bbox
;
381 button
= gtk_toggle_button_new();
382 gtk_button_set_relief(GTK_BUTTON(button
), GTK_RELIEF_NONE
);
384 bbox
= gtk_vbox_new(FALSE
, 0);
386 gtk_container_add (GTK_CONTAINER(button
), bbox
);
388 image
= gtk_image_new_from_stock(icon
, gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
));
389 gtk_box_pack_start(GTK_BOX(bbox
), image
, FALSE
, FALSE
, 0);
391 gtk_widget_show_all(bbox
);
397 pidgin_pixbuf_button_from_stock(const char *text
, const char *icon
,
398 PidginButtonOrientation style
)
400 GtkWidget
*button
, *image
, *label
, *bbox
, *ibox
, *lbox
= NULL
;
402 button
= gtk_button_new();
404 if (style
== PIDGIN_BUTTON_HORIZONTAL
) {
405 bbox
= gtk_hbox_new(FALSE
, 0);
406 ibox
= gtk_hbox_new(FALSE
, 0);
408 lbox
= gtk_hbox_new(FALSE
, 0);
410 bbox
= gtk_vbox_new(FALSE
, 0);
411 ibox
= gtk_vbox_new(FALSE
, 0);
413 lbox
= gtk_vbox_new(FALSE
, 0);
416 gtk_container_add(GTK_CONTAINER(button
), bbox
);
419 gtk_box_pack_start_defaults(GTK_BOX(bbox
), ibox
);
420 image
= gtk_image_new_from_stock(icon
, GTK_ICON_SIZE_BUTTON
);
421 gtk_box_pack_end(GTK_BOX(ibox
), image
, FALSE
, TRUE
, 0);
425 gtk_box_pack_start_defaults(GTK_BOX(bbox
), lbox
);
426 label
= gtk_label_new(NULL
);
427 gtk_label_set_text_with_mnemonic(GTK_LABEL(label
), text
);
428 gtk_label_set_mnemonic_widget(GTK_LABEL(label
), button
);
429 gtk_box_pack_start(GTK_BOX(lbox
), label
, FALSE
, TRUE
, 0);
430 pidgin_set_accessible_label (button
, label
);
433 gtk_widget_show_all(bbox
);
439 GtkWidget
*pidgin_new_item_from_stock(GtkWidget
*menu
, const char *str
, const char *icon
, GtkSignalFunc sf
, gpointer data
, guint accel_key
, guint accel_mods
, char *mod
)
449 menuitem
= gtk_menu_item_new_with_mnemonic(str
);
451 menuitem
= gtk_image_menu_item_new_with_mnemonic(str
);
454 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
457 g_signal_connect(G_OBJECT(menuitem
), "activate", sf
, data
);
460 image
= gtk_image_new_from_stock(icon
, gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
));
461 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem
), image
);
463 /* FIXME: this isn't right
465 label = gtk_label_new(mod);
466 gtk_box_pack_end(GTK_BOX(hbox), label, FALSE, FALSE, 2);
467 gtk_widget_show(label);
472 gtk_widget_add_accelerator(menuitem, "activate", accel, accel_key,
473 accel_mods, GTK_ACCEL_LOCKED);
477 gtk_widget_show_all(menuitem
);
483 pidgin_make_frame(GtkWidget
*parent
, const char *title
)
485 GtkWidget
*vbox
, *label
, *hbox
;
488 vbox
= gtk_vbox_new(FALSE
, PIDGIN_HIG_BOX_SPACE
);
489 gtk_box_pack_start(GTK_BOX(parent
), vbox
, FALSE
, FALSE
, 0);
490 gtk_widget_show(vbox
);
492 label
= gtk_label_new(NULL
);
494 labeltitle
= g_strdup_printf("<span weight=\"bold\">%s</span>", title
);
495 gtk_label_set_markup(GTK_LABEL(label
), labeltitle
);
498 gtk_misc_set_alignment(GTK_MISC(label
), 0, 0);
499 gtk_box_pack_start(GTK_BOX(vbox
), label
, FALSE
, FALSE
, 0);
500 gtk_widget_show(label
);
501 pidgin_set_accessible_label (vbox
, label
);
503 hbox
= gtk_hbox_new(FALSE
, PIDGIN_HIG_BOX_SPACE
);
504 gtk_box_pack_start (GTK_BOX (vbox
), hbox
, FALSE
, FALSE
, 0);
505 gtk_widget_show(hbox
);
507 label
= gtk_label_new(" ");
508 gtk_box_pack_start(GTK_BOX(hbox
), label
, FALSE
, FALSE
, 0);
509 gtk_widget_show(label
);
511 vbox
= gtk_vbox_new(FALSE
, PIDGIN_HIG_BOX_SPACE
);
512 gtk_box_pack_start(GTK_BOX(hbox
), vbox
, FALSE
, FALSE
, 0);
513 gtk_widget_show(vbox
);
519 aop_option_menu_get_selected(GtkWidget
*optmenu
, GtkWidget
**p_item
)
521 GtkWidget
*menu
= gtk_option_menu_get_menu(GTK_OPTION_MENU(optmenu
));
522 GtkWidget
*item
= gtk_menu_get_active(GTK_MENU(menu
));
525 return g_object_get_data(G_OBJECT(item
), "aop_per_item_data");
529 aop_menu_cb(GtkWidget
*optmenu
, GCallback cb
)
532 gpointer per_item_data
;
534 per_item_data
= aop_option_menu_get_selected(optmenu
, &item
);
537 ((void (*)(GtkWidget
*, gpointer
, gpointer
))cb
)(item
, per_item_data
, g_object_get_data(G_OBJECT(optmenu
), "user_data"));
542 aop_menu_item_new(GtkSizeGroup
*sg
, GdkPixbuf
*pixbuf
, const char *lbl
, gpointer per_item_data
, const char *data
)
549 item
= gtk_menu_item_new();
550 gtk_widget_show(item
);
552 hbox
= gtk_hbox_new(FALSE
, 4);
553 gtk_widget_show(hbox
);
555 /* Create the image */
557 image
= gtk_image_new();
559 image
= gtk_image_new_from_pixbuf(pixbuf
);
560 gtk_widget_show(image
);
563 gtk_size_group_add_widget(sg
, image
);
565 /* Create the label */
566 label
= gtk_label_new (lbl
);
567 gtk_widget_show (label
);
568 gtk_label_set_justify(GTK_LABEL(label
), GTK_JUSTIFY_LEFT
);
569 gtk_misc_set_alignment(GTK_MISC(label
), 0.0, 0.5);
571 gtk_container_add(GTK_CONTAINER(item
), hbox
);
572 gtk_box_pack_start(GTK_BOX(hbox
), image
, FALSE
, FALSE
, 0);
573 gtk_box_pack_start(GTK_BOX(hbox
), label
, TRUE
, TRUE
, 0);
575 g_object_set_data(G_OBJECT (item
), data
, per_item_data
);
576 g_object_set_data(G_OBJECT (item
), "aop_per_item_data", per_item_data
);
578 pidgin_set_accessible_label(item
, label
);
584 pidgin_create_prpl_icon_from_prpl(PurplePlugin
*prpl
, PidginPrplIconSize size
, PurpleAccount
*account
)
586 PurplePluginProtocolInfo
*prpl_info
;
587 const char *protoname
= NULL
;
589 char *filename
= NULL
;
592 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(prpl
);
593 if (prpl_info
->list_icon
== NULL
)
596 protoname
= prpl_info
->list_icon(account
, NULL
);
597 if (protoname
== NULL
)
601 * Status icons will be themeable too, and then it will look up
602 * protoname from the theme
604 tmp
= g_strconcat(protoname
, ".png", NULL
);
606 filename
= g_build_filename(DATADIR
, "pixmaps", "pidgin", "protocols",
607 size
== PIDGIN_PRPL_ICON_SMALL
? "16" :
608 size
== PIDGIN_PRPL_ICON_MEDIUM
? "22" : "48",
612 pixbuf
= gdk_pixbuf_new_from_file(filename
, NULL
);
619 aop_option_menu_new(AopMenu
*aop_menu
, GCallback cb
, gpointer user_data
)
623 optmenu
= gtk_option_menu_new();
624 gtk_widget_show(optmenu
);
625 gtk_option_menu_set_menu(GTK_OPTION_MENU(optmenu
), aop_menu
->menu
);
627 if (aop_menu
->default_item
!= -1)
628 gtk_option_menu_set_history(GTK_OPTION_MENU(optmenu
), aop_menu
->default_item
);
630 g_object_set_data_full(G_OBJECT(optmenu
), "aop_menu", aop_menu
, (GDestroyNotify
)g_free
);
631 g_object_set_data(G_OBJECT(optmenu
), "user_data", user_data
);
633 g_signal_connect(G_OBJECT(optmenu
), "changed", G_CALLBACK(aop_menu_cb
), cb
);
639 aop_option_menu_replace_menu(GtkWidget
*optmenu
, AopMenu
*new_aop_menu
)
641 if (gtk_option_menu_get_menu(GTK_OPTION_MENU(optmenu
)))
642 gtk_option_menu_remove_menu(GTK_OPTION_MENU(optmenu
));
644 gtk_option_menu_set_menu(GTK_OPTION_MENU(optmenu
), new_aop_menu
->menu
);
646 if (new_aop_menu
->default_item
!= -1)
647 gtk_option_menu_set_history(GTK_OPTION_MENU(optmenu
), new_aop_menu
->default_item
);
649 g_object_set_data_full(G_OBJECT(optmenu
), "aop_menu", new_aop_menu
, (GDestroyNotify
)g_free
);
653 aop_option_menu_select_by_data(GtkWidget
*optmenu
, gpointer data
)
658 for (idx
= 0, llItr
= GTK_MENU_SHELL(gtk_option_menu_get_menu(GTK_OPTION_MENU(optmenu
)))->children
;
660 llItr
= llItr
->next
, idx
++) {
661 if (data
== g_object_get_data(G_OBJECT(llItr
->data
), "aop_per_item_data")) {
662 gtk_option_menu_set_history(GTK_OPTION_MENU(optmenu
), idx
);
669 create_protocols_menu(const char *default_proto_id
)
671 AopMenu
*aop_menu
= NULL
;
672 PurplePluginProtocolInfo
*prpl_info
;
673 PurplePlugin
*plugin
;
674 GdkPixbuf
*pixbuf
= NULL
;
677 const char *gtalk_name
= NULL
;
680 aop_menu
= g_malloc0(sizeof(AopMenu
));
681 aop_menu
->default_item
= -1;
682 aop_menu
->menu
= gtk_menu_new();
683 gtk_widget_show(aop_menu
->menu
);
684 sg
= gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL
);
686 if (purple_find_prpl("prpl-jabber"))
687 gtalk_name
= _("Google Talk");
689 for (p
= purple_plugins_get_protocols(), i
= 0;
693 plugin
= (PurplePlugin
*)p
->data
;
694 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(plugin
);
696 if (gtalk_name
&& strcmp(gtalk_name
, plugin
->info
->name
) < 0) {
697 char *filename
= g_build_filename(DATADIR
, "pixmaps", "pidgin", "protocols",
698 "16", "google-talk.png", NULL
);
701 pixbuf
= gdk_pixbuf_new_from_file(filename
, NULL
);
704 gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu
->menu
),
705 item
= aop_menu_item_new(sg
, pixbuf
, gtalk_name
, "prpl-jabber", "protocol"));
706 g_object_set_data(G_OBJECT(item
), "fake", GINT_TO_POINTER(1));
709 g_object_unref(pixbuf
);
715 pixbuf
= pidgin_create_prpl_icon_from_prpl(plugin
, PIDGIN_PRPL_ICON_SMALL
, NULL
);
717 gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu
->menu
),
718 aop_menu_item_new(sg
, pixbuf
, plugin
->info
->name
, plugin
->info
->id
, "protocol"));
721 g_object_unref(pixbuf
);
723 if (default_proto_id
!= NULL
&& !strcmp(plugin
->info
->id
, default_proto_id
))
724 aop_menu
->default_item
= i
;
733 pidgin_protocol_option_menu_new(const char *id
, GCallback cb
,
736 return aop_option_menu_new(create_protocols_menu(id
), cb
, user_data
);
740 pidgin_protocol_option_menu_get_selected(GtkWidget
*optmenu
)
742 return (const char *)aop_option_menu_get_selected(optmenu
, NULL
);
746 pidgin_account_option_menu_get_selected(GtkWidget
*optmenu
)
748 return (PurpleAccount
*)aop_option_menu_get_selected(optmenu
, NULL
);
752 create_account_menu(PurpleAccount
*default_account
,
753 PurpleFilterAccountFunc filter_func
, gboolean show_all
)
755 AopMenu
*aop_menu
= NULL
;
756 PurpleAccount
*account
;
757 GdkPixbuf
*pixbuf
= NULL
;
765 list
= purple_accounts_get_all();
767 list
= purple_connections_get_all();
769 aop_menu
= g_malloc0(sizeof(AopMenu
));
770 aop_menu
->default_item
= -1;
771 aop_menu
->menu
= gtk_menu_new();
772 gtk_widget_show(aop_menu
->menu
);
773 sg
= gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL
);
775 for (p
= list
, i
= 0; p
!= NULL
; p
= p
->next
, i
++) {
776 PurplePlugin
*plugin
;
779 account
= (PurpleAccount
*)p
->data
;
781 PurpleConnection
*gc
= (PurpleConnection
*)p
->data
;
783 account
= purple_connection_get_account(gc
);
786 if (filter_func
&& !filter_func(account
)) {
791 plugin
= purple_find_prpl(purple_account_get_protocol_id(account
));
793 pixbuf
= pidgin_create_prpl_icon(account
, PIDGIN_PRPL_ICON_SMALL
);
796 if (purple_account_is_disconnected(account
) && show_all
&&
797 purple_connections_get_all())
798 gdk_pixbuf_saturate_and_pixelate(pixbuf
, pixbuf
, 0.0, FALSE
);
801 if (purple_account_get_alias(account
)) {
802 g_snprintf(buf
, sizeof(buf
), "%s (%s) (%s)",
803 purple_account_get_username(account
),
804 purple_account_get_alias(account
),
805 purple_account_get_protocol_name(account
));
807 g_snprintf(buf
, sizeof(buf
), "%s (%s)",
808 purple_account_get_username(account
),
809 purple_account_get_protocol_name(account
));
812 gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu
->menu
),
813 aop_menu_item_new(sg
, pixbuf
, buf
, account
, "account"));
816 g_object_unref(pixbuf
);
818 if (default_account
&& account
== default_account
)
819 aop_menu
->default_item
= i
;
828 regenerate_account_menu(GtkWidget
*optmenu
)
831 PurpleAccount
*account
;
832 PurpleFilterAccountFunc filter_func
;
834 account
= (PurpleAccount
*)aop_option_menu_get_selected(optmenu
, NULL
);
835 show_all
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(optmenu
), "show_all"));
836 filter_func
= g_object_get_data(G_OBJECT(optmenu
), "filter_func");
838 aop_option_menu_replace_menu(optmenu
, create_account_menu(account
, filter_func
, show_all
));
842 account_menu_sign_on_off_cb(PurpleConnection
*gc
, GtkWidget
*optmenu
)
844 regenerate_account_menu(optmenu
);
848 account_menu_added_removed_cb(PurpleAccount
*account
, GtkWidget
*optmenu
)
850 regenerate_account_menu(optmenu
);
854 account_menu_destroyed_cb(GtkWidget
*optmenu
, GdkEvent
*event
,
857 purple_signals_disconnect_by_handle(optmenu
);
863 pidgin_account_option_menu_set_selected(GtkWidget
*optmenu
, PurpleAccount
*account
)
865 aop_option_menu_select_by_data(optmenu
, account
);
869 pidgin_account_option_menu_new(PurpleAccount
*default_account
,
870 gboolean show_all
, GCallback cb
,
871 PurpleFilterAccountFunc filter_func
,
876 /* Create the option menu */
877 optmenu
= aop_option_menu_new(create_account_menu(default_account
, filter_func
, show_all
), cb
, user_data
);
879 g_signal_connect(G_OBJECT(optmenu
), "destroy",
880 G_CALLBACK(account_menu_destroyed_cb
), NULL
);
882 /* Register the purple sign on/off event callbacks. */
883 purple_signal_connect(purple_connections_get_handle(), "signed-on",
884 optmenu
, PURPLE_CALLBACK(account_menu_sign_on_off_cb
),
886 purple_signal_connect(purple_connections_get_handle(), "signed-off",
887 optmenu
, PURPLE_CALLBACK(account_menu_sign_on_off_cb
),
889 purple_signal_connect(purple_accounts_get_handle(), "account-added",
890 optmenu
, PURPLE_CALLBACK(account_menu_added_removed_cb
),
892 purple_signal_connect(purple_accounts_get_handle(), "account-removed",
893 optmenu
, PURPLE_CALLBACK(account_menu_added_removed_cb
),
897 g_object_set_data(G_OBJECT(optmenu
), "user_data", user_data
);
898 g_object_set_data(G_OBJECT(optmenu
), "show_all", GINT_TO_POINTER(show_all
));
899 g_object_set_data(G_OBJECT(optmenu
), "filter_func", filter_func
);
905 pidgin_check_if_dir(const char *path
, GtkFileSelection
*filesel
)
907 char *dirname
= NULL
;
909 if (g_file_test(path
, G_FILE_TEST_IS_DIR
)) {
910 /* append a / if needed */
911 if (path
[strlen(path
) - 1] != G_DIR_SEPARATOR
) {
912 dirname
= g_strconcat(path
, G_DIR_SEPARATOR_S
, NULL
);
914 gtk_file_selection_set_filename(filesel
, (dirname
!= NULL
) ? dirname
: path
);
923 pidgin_setup_gtkspell(GtkTextView
*textview
)
926 GError
*error
= NULL
;
929 g_return_if_fail(textview
!= NULL
);
930 g_return_if_fail(GTK_IS_TEXT_VIEW(textview
));
932 if (gtkspell_new_attach(textview
, locale
, &error
) == NULL
&& error
)
934 purple_debug_warning("gtkspell", "Failed to setup GtkSpell: %s\n",
938 #endif /* USE_GTKSPELL */
942 pidgin_save_accels_cb(GtkAccelGroup
*accel_group
, guint arg1
,
943 GdkModifierType arg2
, GClosure
*arg3
,
946 purple_debug(PURPLE_DEBUG_MISC
, "accels",
947 "accel changed, scheduling save.\n");
949 if (!accels_save_timer
)
950 accels_save_timer
= g_timeout_add(5000, pidgin_save_accels
,
955 pidgin_save_accels(gpointer data
)
957 char *filename
= NULL
;
959 filename
= g_build_filename(purple_user_dir(), G_DIR_SEPARATOR_S
,
961 purple_debug(PURPLE_DEBUG_MISC
, "accels", "saving accels to %s\n", filename
);
962 gtk_accel_map_save(filename
);
965 accels_save_timer
= 0;
972 char *filename
= NULL
;
974 filename
= g_build_filename(purple_user_dir(), G_DIR_SEPARATOR_S
,
976 gtk_accel_map_load(filename
);
981 show_retrieveing_info(PurpleConnection
*conn
, const char *name
)
983 PurpleNotifyUserInfo
*info
= purple_notify_user_info_new();
984 purple_notify_user_info_add_pair(info
, _("Information"), _("Retrieving..."));
985 purple_notify_userinfo(conn
, name
, info
, NULL
, NULL
);
986 purple_notify_user_info_destroy(info
);
989 void pidgin_retrieve_user_info(PurpleConnection
*conn
, const char *name
)
991 show_retrieveing_info(conn
, name
);
992 serv_get_info(conn
, name
);
995 void pidgin_retrieve_user_info_in_chat(PurpleConnection
*conn
, const char *name
, int chat
)
998 PurplePluginProtocolInfo
*prpl_info
= NULL
;
1001 pidgin_retrieve_user_info(conn
, name
);
1005 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(conn
->prpl
);
1006 if (prpl_info
!= NULL
&& prpl_info
->get_cb_real_name
)
1007 who
= prpl_info
->get_cb_real_name(conn
, chat
, name
);
1008 if (prpl_info
== NULL
|| prpl_info
->get_cb_info
== NULL
) {
1009 pidgin_retrieve_user_info(conn
, who
? who
: name
);
1014 show_retrieveing_info(conn
, who
? who
: name
);
1015 prpl_info
->get_cb_info(conn
, chat
, name
);
1020 pidgin_parse_x_im_contact(const char *msg
, gboolean all_accounts
,
1021 PurpleAccount
**ret_account
, char **ret_protocol
,
1022 char **ret_username
, char **ret_alias
)
1024 char *protocol
= NULL
;
1025 char *username
= NULL
;
1031 g_return_val_if_fail(msg
!= NULL
, FALSE
);
1032 g_return_val_if_fail(ret_protocol
!= NULL
, FALSE
);
1033 g_return_val_if_fail(ret_username
!= NULL
, FALSE
);
1035 s
= str
= g_strdup(msg
);
1037 while (*s
!= '\r' && *s
!= '\n' && *s
!= '\0')
1044 while (*s
!= '\r' && *s
!= '\n' && *s
!= '\0' && *s
!= ' ')
1047 if (*s
== '\r') s
++;
1055 if (*s
!= '\0') *s
++ = '\0';
1057 /* Clear past any whitespace */
1058 while (*s
!= '\0' && *s
== ' ')
1061 /* Now let's grab until the end of the line. */
1064 while (*s
!= '\r' && *s
!= '\n' && *s
!= '\0')
1067 if (*s
== '\r') *s
++ = '\0';
1068 if (*s
== '\n') *s
++ = '\0';
1070 if ((c
= strchr(key
, ':')) != NULL
)
1072 if (!g_ascii_strcasecmp(key
, "X-IM-Username:"))
1073 username
= g_strdup(value
);
1074 else if (!g_ascii_strcasecmp(key
, "X-IM-Protocol:"))
1075 protocol
= g_strdup(value
);
1076 else if (!g_ascii_strcasecmp(key
, "X-IM-Alias:"))
1077 alias
= g_strdup(value
);
1081 if (username
!= NULL
&& protocol
!= NULL
)
1085 *ret_username
= username
;
1086 *ret_protocol
= protocol
;
1088 if (ret_alias
!= NULL
)
1091 /* Check for a compatible account. */
1092 if (ret_account
!= NULL
)
1095 PurpleAccount
*account
= NULL
;
1097 const char *protoname
;
1100 list
= purple_accounts_get_all();
1102 list
= purple_connections_get_all();
1104 for (l
= list
; l
!= NULL
; l
= l
->next
)
1106 PurpleConnection
*gc
;
1107 PurplePluginProtocolInfo
*prpl_info
= NULL
;
1108 PurplePlugin
*plugin
;
1112 account
= (PurpleAccount
*)l
->data
;
1114 plugin
= purple_plugins_find_with_id(
1115 purple_account_get_protocol_id(account
));
1124 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(plugin
);
1128 gc
= (PurpleConnection
*)l
->data
;
1129 account
= purple_connection_get_account(gc
);
1131 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
);
1134 protoname
= prpl_info
->list_icon(account
, NULL
);
1136 if (!strcmp(protoname
, protocol
))
1142 /* Special case for AIM and ICQ */
1143 if (account
== NULL
&& (!strcmp(protocol
, "aim") ||
1144 !strcmp(protocol
, "icq")))
1146 for (l
= list
; l
!= NULL
; l
= l
->next
)
1148 PurpleConnection
*gc
;
1149 PurplePluginProtocolInfo
*prpl_info
= NULL
;
1150 PurplePlugin
*plugin
;
1154 account
= (PurpleAccount
*)l
->data
;
1156 plugin
= purple_plugins_find_with_id(
1157 purple_account_get_protocol_id(account
));
1166 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(plugin
);
1170 gc
= (PurpleConnection
*)l
->data
;
1171 account
= purple_connection_get_account(gc
);
1173 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
);
1176 protoname
= prpl_info
->list_icon(account
, NULL
);
1178 if (!strcmp(protoname
, "aim") || !strcmp(protoname
, "icq"))
1185 *ret_account
= account
;
1203 pidgin_set_accessible_label (GtkWidget
*w
, GtkWidget
*l
)
1206 const gchar
*label_text
;
1207 const gchar
*existing_name
;
1209 acc
= gtk_widget_get_accessible (w
);
1211 /* If this object has no name, set it's name with the label text */
1212 existing_name
= atk_object_get_name (acc
);
1213 if (!existing_name
) {
1214 label_text
= gtk_label_get_text (GTK_LABEL(l
));
1216 atk_object_set_name (acc
, label_text
);
1219 pidgin_set_accessible_relations(w
, l
);
1223 pidgin_set_accessible_relations (GtkWidget
*w
, GtkWidget
*l
)
1225 AtkObject
*acc
, *label
;
1226 AtkObject
*rel_obj
[1];
1227 AtkRelationSet
*set
;
1228 AtkRelation
*relation
;
1230 acc
= gtk_widget_get_accessible (w
);
1231 label
= gtk_widget_get_accessible (l
);
1233 /* Make sure mnemonics work */
1234 gtk_label_set_mnemonic_widget(GTK_LABEL(l
), w
);
1236 /* Create the labeled-by relation */
1237 set
= atk_object_ref_relation_set (acc
);
1239 relation
= atk_relation_new (rel_obj
, 1, ATK_RELATION_LABELLED_BY
);
1240 atk_relation_set_add (set
, relation
);
1241 g_object_unref (relation
);
1242 g_object_unref(set
);
1244 /* Create the label-for relation */
1245 set
= atk_object_ref_relation_set (label
);
1247 relation
= atk_relation_new (rel_obj
, 1, ATK_RELATION_LABEL_FOR
);
1248 atk_relation_set_add (set
, relation
);
1249 g_object_unref (relation
);
1250 g_object_unref(set
);
1254 pidgin_menu_position_func_helper(GtkMenu
*menu
,
1260 #if GTK_CHECK_VERSION(2,2,0)
1262 GtkRequisition requisition
;
1264 GdkRectangle monitor
;
1266 gint space_left
, space_right
, space_above
, space_below
;
1273 g_return_if_fail(GTK_IS_MENU(menu
));
1275 widget
= GTK_WIDGET(menu
);
1276 screen
= gtk_widget_get_screen(widget
);
1277 xthickness
= widget
->style
->xthickness
;
1278 ythickness
= widget
->style
->ythickness
;
1279 rtl
= (gtk_widget_get_direction(widget
) == GTK_TEXT_DIR_RTL
);
1282 * We need the requisition to figure out the right place to
1283 * popup the menu. In fact, we always need to ask here, since
1284 * if a size_request was queued while we weren't popped up,
1285 * the requisition won't have been recomputed yet.
1287 gtk_widget_size_request (widget
, &requisition
);
1289 monitor_num
= gdk_screen_get_monitor_at_point (screen
, *x
, *y
);
1294 * The placement of popup menus horizontally works like this (with
1295 * RTL in parentheses)
1297 * - If there is enough room to the right (left) of the mouse cursor,
1298 * position the menu there.
1300 * - Otherwise, if if there is enough room to the left (right) of the
1301 * mouse cursor, position the menu there.
1303 * - Otherwise if the menu is smaller than the monitor, position it
1304 * on the side of the mouse cursor that has the most space available
1306 * - Otherwise (if there is simply not enough room for the menu on the
1307 * monitor), position it as far left (right) as possible.
1309 * Positioning in the vertical direction is similar: first try below
1310 * mouse cursor, then above.
1312 gdk_screen_get_monitor_geometry (screen
, monitor_num
, &monitor
);
1314 space_left
= *x
- monitor
.x
;
1315 space_right
= monitor
.x
+ monitor
.width
- *x
- 1;
1316 space_above
= *y
- monitor
.y
;
1317 space_below
= monitor
.y
+ monitor
.height
- *y
- 1;
1319 /* position horizontally */
1321 /* the amount of space we need to position the menu. Note the
1322 * menu is offset "xthickness" pixels
1324 needed_width
= requisition
.width
- xthickness
;
1326 if (needed_width
<= space_left
||
1327 needed_width
<= space_right
)
1329 if ((rtl
&& needed_width
<= space_left
) ||
1330 (!rtl
&& needed_width
> space_right
))
1333 *x
= *x
+ xthickness
- requisition
.width
+ 1;
1337 /* position right */
1338 *x
= *x
- xthickness
;
1341 /* x is clamped on-screen further down */
1343 else if (requisition
.width
<= monitor
.width
)
1345 /* the menu is too big to fit on either side of the mouse
1346 * cursor, but smaller than the monitor. Position it on
1347 * the side that has the most space
1349 if (space_left
> space_right
)
1357 *x
= monitor
.x
+ monitor
.width
- requisition
.width
;
1360 else /* menu is simply too big for the monitor */
1365 *x
= monitor
.x
+ monitor
.width
- requisition
.width
;
1374 /* Position vertically. The algorithm is the same as above, but
1375 * simpler because we don't have to take RTL into account.
1377 needed_height
= requisition
.height
- ythickness
;
1379 if (needed_height
<= space_above
||
1380 needed_height
<= space_below
)
1382 if (needed_height
<= space_below
)
1383 *y
= *y
- ythickness
;
1385 *y
= *y
+ ythickness
- requisition
.height
+ 1;
1387 *y
= CLAMP (*y
, monitor
.y
,
1388 monitor
.y
+ monitor
.height
- requisition
.height
);
1390 else if (needed_height
> space_below
&& needed_height
> space_above
)
1392 if (space_below
>= space_above
)
1393 *y
= monitor
.y
+ monitor
.height
- requisition
.height
;
1406 pidgin_treeview_popup_menu_position_func(GtkMenu
*menu
,
1412 GtkWidget
*widget
= GTK_WIDGET(data
);
1413 GtkTreeView
*tv
= GTK_TREE_VIEW(data
);
1415 GtkTreeViewColumn
*col
;
1417 gint ythickness
= GTK_WIDGET(menu
)->style
->ythickness
;
1419 gdk_window_get_origin (widget
->window
, x
, y
);
1420 gtk_tree_view_get_cursor (tv
, &path
, &col
);
1421 gtk_tree_view_get_cell_area (tv
, path
, col
, &rect
);
1423 *x
+= rect
.x
+rect
.width
;
1424 *y
+= rect
.y
+rect
.height
+ythickness
;
1425 pidgin_menu_position_func_helper(menu
, x
, y
, push_in
, data
);
1436 PurpleAccount
*account
;
1440 static void dnd_image_ok_callback(_DndData
*data
, int choice
)
1446 PurpleConversation
*conv
;
1447 PidginConversation
*gtkconv
;
1451 PurpleContact
*contact
;
1453 case DND_BUDDY_ICON
:
1454 if (g_stat(data
->filename
, &st
)) {
1457 str
= g_strdup_printf(_("The following error has occurred loading %s: %s"),
1458 data
->filename
, g_strerror(errno
));
1459 purple_notify_error(NULL
, NULL
,
1460 _("Failed to load image"),
1467 buddy
= purple_find_buddy(data
->account
, data
->who
);
1469 purple_debug_info("custom-icon", "You can only set custom icons for people on your buddylist.\n");
1472 contact
= purple_buddy_get_contact(buddy
);
1473 purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode
*)contact
, data
->filename
);
1475 case DND_FILE_TRANSFER
:
1476 serv_send_file(purple_account_get_connection(data
->account
), data
->who
, data
->filename
);
1479 conv
= purple_conversation_new(PURPLE_CONV_TYPE_IM
, data
->account
, data
->who
);
1480 gtkconv
= PIDGIN_CONVERSATION(conv
);
1482 if (!g_file_get_contents(data
->filename
, &filedata
, &size
,
1486 str
= g_strdup_printf(_("The following error has occurred loading %s: %s"), data
->filename
, err
->message
);
1487 purple_notify_error(NULL
, NULL
,
1488 _("Failed to load image"),
1496 id
= purple_imgstore_add_with_id(filedata
, size
, data
->filename
);
1498 gtk_text_buffer_get_iter_at_mark(GTK_IMHTML(gtkconv
->entry
)->text_buffer
, &iter
,
1499 gtk_text_buffer_get_insert(GTK_IMHTML(gtkconv
->entry
)->text_buffer
));
1500 gtk_imhtml_insert_image_at_iter(GTK_IMHTML(gtkconv
->entry
), id
, &iter
);
1501 purple_imgstore_unref_by_id(id
);
1505 g_free(data
->filename
);
1510 static void dnd_image_cancel_callback(_DndData
*data
, int choice
)
1512 g_free(data
->filename
);
1517 static void dnd_set_icon_ok_cb(_DndData
*data
)
1519 dnd_image_ok_callback(data
, DND_BUDDY_ICON
);
1522 static void dnd_set_icon_cancel_cb(_DndData
*data
)
1524 g_free(data
->filename
);
1530 pidgin_dnd_file_manage(GtkSelectionData
*sd
, PurpleAccount
*account
, const char *who
)
1534 GList
*files
= purple_uri_list_extract_filenames((const gchar
*)sd
->data
);
1535 PurpleConnection
*gc
= purple_account_get_connection(account
);
1536 PurplePluginProtocolInfo
*prpl_info
= NULL
;
1537 gboolean file_send_ok
= FALSE
;
1539 PurpleDesktopItem
*item
;
1542 g_return_if_fail(account
!= NULL
);
1543 g_return_if_fail(who
!= NULL
);
1545 for(tmp
= files
; tmp
!= NULL
; tmp
= g_list_next(tmp
)) {
1546 gchar
*filename
= tmp
->data
;
1547 gchar
*basename
= g_path_get_basename(filename
);
1549 /* Set the default action: don't send anything */
1550 file_send_ok
= FALSE
;
1552 /* XXX - Make ft API support creating a transfer with more than one file */
1553 if (!g_file_test(filename
, G_FILE_TEST_EXISTS
)) {
1557 /* XXX - make ft api suupport sending a directory */
1558 /* Are we dealing with a directory? */
1559 if (g_file_test(filename
, G_FILE_TEST_IS_DIR
)) {
1562 str
= g_strdup_printf(_("Cannot send folder %s."), basename
);
1563 str2
= g_strdup_printf(_("%s cannot transfer a folder. You will need to send the files within individually."), PIDGIN_NAME
);
1565 purple_notify_error(NULL
, NULL
,
1574 /* Are we dealing with an image? */
1575 pb
= gdk_pixbuf_new_from_file(filename
, NULL
);
1577 _DndData
*data
= g_malloc(sizeof(_DndData
));
1578 gboolean ft
= FALSE
, im
= FALSE
;
1580 data
->who
= g_strdup(who
);
1581 data
->filename
= g_strdup(filename
);
1582 data
->account
= account
;
1585 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
);
1587 if (prpl_info
&& prpl_info
->options
& OPT_PROTO_IM_IMAGE
)
1590 if (prpl_info
&& prpl_info
->can_receive_file
)
1591 ft
= prpl_info
->can_receive_file(gc
, who
);
1592 else if (prpl_info
&& prpl_info
->send_file
)
1596 purple_request_choice(NULL
, NULL
,
1597 _("You have dragged an image"),
1598 _("You can send this image as a file transfer, "
1599 "embed it into this message, or use it as the buddy icon for this user."),
1600 DND_FILE_TRANSFER
, "OK", (GCallback
)dnd_image_ok_callback
,
1601 "Cancel", (GCallback
)dnd_image_cancel_callback
,
1604 _("Set as buddy icon"), DND_BUDDY_ICON
,
1605 _("Send image file"), DND_FILE_TRANSFER
,
1606 _("Insert in message"), DND_IM_IMAGE
,
1608 else if (!(im
|| ft
))
1609 purple_request_yes_no(NULL
, NULL
, _("You have dragged an image"),
1610 _("Would you like to set it as the buddy icon for this user?"),
1611 PURPLE_DEFAULT_ACTION_NONE
,
1613 data
, (GCallback
)dnd_set_icon_ok_cb
, (GCallback
)dnd_set_icon_cancel_cb
);
1615 purple_request_choice(NULL
, NULL
,
1616 _("You have dragged an image"),
1617 (ft
? _("You can send this image as a file transfer, or use it as the buddy icon for this user.") :
1618 _("You can insert this image into this message, or use it as the buddy icon for this user")),
1619 (ft
? DND_FILE_TRANSFER
: DND_IM_IMAGE
),
1620 "OK", (GCallback
)dnd_image_ok_callback
,
1621 "Cancel", (GCallback
)dnd_image_cancel_callback
,
1624 _("Set as buddy icon"), DND_BUDDY_ICON
,
1625 (ft
? _("Send image file") : _("Insert in message")), (ft
? DND_FILE_TRANSFER
: DND_IM_IMAGE
),
1627 gdk_pixbuf_unref(pb
);
1632 /* Are we trying to send a .desktop file? */
1633 else if (purple_str_has_suffix(basename
, ".desktop") && (item
= purple_desktop_item_new_from_file(filename
))) {
1634 PurpleDesktopItemType dtype
;
1636 const char *itemname
= NULL
;
1638 #if GTK_CHECK_VERSION(2,6,0)
1639 const char * const *langs
;
1641 langs
= g_get_language_names();
1642 for (i
= 0; langs
[i
]; i
++) {
1643 g_snprintf(key
, sizeof(key
), "Name[%s]", langs
[i
]);
1644 itemname
= purple_desktop_item_get_string(item
, key
);
1648 const char *lang
= g_getenv("LANG");
1650 dot
= strchr(lang
, '.');
1653 g_snprintf(key
, sizeof(key
), "Name[%s]", lang
);
1654 itemname
= purple_desktop_item_get_string(item
, key
);
1657 itemname
= purple_desktop_item_get_string(item
, "Name");
1659 dtype
= purple_desktop_item_get_entry_type(item
);
1661 PurpleConversation
*conv
;
1662 PidginConversation
*gtkconv
;
1664 case PURPLE_DESKTOP_ITEM_TYPE_LINK
:
1665 conv
= purple_conversation_new(PURPLE_CONV_TYPE_IM
, account
, who
);
1666 gtkconv
= PIDGIN_CONVERSATION(conv
);
1667 gtk_imhtml_insert_link(GTK_IMHTML(gtkconv
->entry
),
1668 gtk_text_buffer_get_insert(GTK_IMHTML(gtkconv
->entry
)->text_buffer
),
1669 purple_desktop_item_get_string(item
, "URL"), itemname
);
1672 /* I don't know if we really want to do anything here. Most of the desktop item types are crap like
1673 * "MIME Type" (I have no clue how that would be a desktop item) and "Comment"... nothing we can really
1674 * send. The only logical one is "Application," but do we really want to send a binary and nothing else?
1675 * Probably not. I'll just give an error and return. */
1676 /* The original patch sent the icon used by the launcher. That's probably wrong */
1677 purple_notify_error(NULL
, NULL
, _("Cannot send launcher"), _("You dragged a desktop launcher. "
1678 "Most likely you wanted to send whatever this launcher points to instead of this launcher"
1682 purple_desktop_item_unref(item
);
1687 /* Everything is fine, let's send */
1688 serv_send_file(gc
, who
, filename
);
1694 void pidgin_buddy_icon_get_scale_size(GdkPixbuf
*buf
, PurpleBuddyIconSpec
*spec
, PurpleIconScaleRules rules
, int *width
, int *height
)
1696 *width
= gdk_pixbuf_get_width(buf
);
1697 *height
= gdk_pixbuf_get_height(buf
);
1699 if ((spec
== NULL
) || !(spec
->scale_rules
& rules
))
1702 purple_buddy_icon_get_scale_size(spec
, width
, height
);
1704 /* and now for some arbitrary sanity checks */
1711 GdkPixbuf
* pidgin_create_status_icon(PurpleStatusPrimitive prim
, GtkWidget
*w
, const char *size
)
1713 GtkIconSize icon_size
= gtk_icon_size_from_name(size
);
1714 GdkPixbuf
*pixbuf
= NULL
;
1716 if (prim
== PURPLE_STATUS_UNAVAILABLE
)
1717 pixbuf
= gtk_widget_render_icon (w
, PIDGIN_STOCK_STATUS_BUSY
,
1718 icon_size
, "GtkWidget");
1719 else if (prim
== PURPLE_STATUS_AWAY
)
1720 pixbuf
= gtk_widget_render_icon (w
, PIDGIN_STOCK_STATUS_AWAY
,
1721 icon_size
, "GtkWidget");
1722 else if (prim
== PURPLE_STATUS_EXTENDED_AWAY
)
1723 pixbuf
= gtk_widget_render_icon (w
, PIDGIN_STOCK_STATUS_XA
,
1724 icon_size
, "GtkWidget");
1725 else if (prim
== PURPLE_STATUS_INVISIBLE
)
1726 pixbuf
= gtk_widget_render_icon (w
, PIDGIN_STOCK_STATUS_INVISIBLE
,
1727 icon_size
, "GtkWidget");
1728 else if (prim
== PURPLE_STATUS_OFFLINE
)
1729 pixbuf
= gtk_widget_render_icon (w
, PIDGIN_STOCK_STATUS_OFFLINE
,
1730 icon_size
, "GtkWidget");
1732 pixbuf
= gtk_widget_render_icon (w
, PIDGIN_STOCK_STATUS_AVAILABLE
,
1733 icon_size
, "GtkWidget");
1740 pidgin_create_prpl_icon(PurpleAccount
*account
, PidginPrplIconSize size
)
1744 g_return_val_if_fail(account
!= NULL
, NULL
);
1746 prpl
= purple_find_prpl(purple_account_get_protocol_id(account
));
1749 return pidgin_create_prpl_icon_from_prpl(prpl
, size
, account
);
1753 menu_action_cb(GtkMenuItem
*item
, gpointer object
)
1756 void (*callback
)(gpointer
, gpointer
);
1758 callback
= g_object_get_data(G_OBJECT(item
), "purplecallback");
1759 data
= g_object_get_data(G_OBJECT(item
), "purplecallbackdata");
1762 callback(object
, data
);
1766 pidgin_append_menu_action(GtkWidget
*menu
, PurpleMenuAction
*act
,
1769 GtkWidget
*menuitem
;
1772 return pidgin_separator(menu
);
1775 if (act
->children
== NULL
) {
1776 menuitem
= gtk_menu_item_new_with_mnemonic(act
->label
);
1778 if (act
->callback
!= NULL
) {
1779 g_object_set_data(G_OBJECT(menuitem
),
1782 g_object_set_data(G_OBJECT(menuitem
),
1783 "purplecallbackdata",
1785 g_signal_connect(G_OBJECT(menuitem
), "activate",
1786 G_CALLBACK(menu_action_cb
),
1789 gtk_widget_set_sensitive(menuitem
, FALSE
);
1792 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
1795 GtkWidget
*submenu
= NULL
;
1796 GtkAccelGroup
*group
;
1798 menuitem
= gtk_menu_item_new_with_mnemonic(act
->label
);
1799 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
1801 submenu
= gtk_menu_new();
1802 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem
), submenu
);
1804 group
= gtk_menu_get_accel_group(GTK_MENU(menu
));
1806 char *path
= g_strdup_printf("%s/%s", GTK_MENU_ITEM(menuitem
)->accel_path
, act
->label
);
1807 gtk_menu_set_accel_path(GTK_MENU(submenu
), path
);
1809 gtk_menu_set_accel_group(GTK_MENU(submenu
), group
);
1812 for (l
= act
->children
; l
; l
= l
->next
) {
1813 PurpleMenuAction
*act
= (PurpleMenuAction
*)l
->data
;
1815 pidgin_append_menu_action(submenu
, act
, object
);
1817 g_list_free(act
->children
);
1818 act
->children
= NULL
;
1820 purple_menu_action_free(act
);
1824 #if GTK_CHECK_VERSION(2,3,0)
1825 # define NEW_STYLE_COMPLETION
1831 GtkWidget
*accountopt
;
1833 PidginFilterBuddyCompletionEntryFunc filter_func
;
1834 gpointer filter_func_user_data
;
1836 #ifdef NEW_STYLE_COMPLETION
1837 GtkListStore
*store
;
1839 GCompletion
*completion
;
1840 gboolean completion_started
;
1842 #endif /* NEW_STYLE_COMPLETION */
1843 } PidginCompletionData
;
1845 #ifndef NEW_STYLE_COMPLETION
1847 completion_entry_event(GtkEditable
*entry
, GdkEventKey
*event
,
1848 PidginCompletionData
*data
)
1852 if (event
->type
== GDK_KEY_PRESS
&& event
->keyval
== GDK_Tab
)
1854 gtk_editable_get_selection_bounds(entry
, &pos
, &end_pos
);
1856 if (data
->completion_started
&&
1857 pos
!= end_pos
&& pos
> 1 &&
1858 end_pos
== strlen(gtk_entry_get_text(GTK_ENTRY(entry
))))
1860 gtk_editable_select_region(entry
, 0, 0);
1861 gtk_editable_set_position(entry
, -1);
1866 else if (event
->type
== GDK_KEY_PRESS
&& event
->length
> 0)
1868 char *prefix
, *nprefix
;
1870 gtk_editable_get_selection_bounds(entry
, &pos
, &end_pos
);
1872 if (data
->completion_started
&&
1873 pos
!= end_pos
&& pos
> 1 &&
1874 end_pos
== strlen(gtk_entry_get_text(GTK_ENTRY(entry
))))
1878 temp
= gtk_editable_get_chars(entry
, 0, pos
);
1879 prefix
= g_strconcat(temp
, event
->string
, NULL
);
1882 else if (pos
== end_pos
&& pos
> 1 &&
1883 end_pos
== strlen(gtk_entry_get_text(GTK_ENTRY(entry
))))
1885 prefix
= g_strconcat(gtk_entry_get_text(GTK_ENTRY(entry
)),
1886 event
->string
, NULL
);
1891 pos
= strlen(prefix
);
1894 g_completion_complete(data
->completion
, prefix
, &nprefix
);
1896 if (nprefix
!= NULL
)
1898 gtk_entry_set_text(GTK_ENTRY(entry
), nprefix
);
1899 gtk_editable_set_position(entry
, pos
);
1900 gtk_editable_select_region(entry
, pos
, -1);
1902 data
->completion_started
= TRUE
;
1917 destroy_completion_data(GtkWidget
*w
, PidginCompletionData
*data
)
1919 g_list_foreach(data
->completion
->items
, (GFunc
)g_free
, NULL
);
1920 g_completion_free(data
->completion
);
1924 #endif /* !NEW_STYLE_COMPLETION */
1926 #ifdef NEW_STYLE_COMPLETION
1927 static gboolean
screenname_completion_match_func(GtkEntryCompletion
*completion
,
1928 const gchar
*key
, GtkTreeIter
*iter
, gpointer user_data
)
1930 GtkTreeModel
*model
;
1935 model
= gtk_entry_completion_get_model (completion
);
1938 gtk_tree_model_get_value(model
, iter
, 2, &val1
);
1939 tmp
= g_value_get_string(&val1
);
1940 if (tmp
!= NULL
&& purple_str_has_prefix(tmp
, key
))
1942 g_value_unset(&val1
);
1945 g_value_unset(&val1
);
1948 gtk_tree_model_get_value(model
, iter
, 3, &val2
);
1949 tmp
= g_value_get_string(&val2
);
1950 if (tmp
!= NULL
&& purple_str_has_prefix(tmp
, key
))
1952 g_value_unset(&val2
);
1955 g_value_unset(&val2
);
1960 static gboolean
screenname_completion_match_selected_cb(GtkEntryCompletion
*completion
,
1961 GtkTreeModel
*model
, GtkTreeIter
*iter
, PidginCompletionData
*data
)
1964 GtkWidget
*optmenu
= data
->accountopt
;
1965 PurpleAccount
*account
;
1968 gtk_tree_model_get_value(model
, iter
, 1, &val
);
1969 gtk_entry_set_text(GTK_ENTRY(data
->entry
), g_value_get_string(&val
));
1970 g_value_unset(&val
);
1972 gtk_tree_model_get_value(model
, iter
, 4, &val
);
1973 account
= g_value_get_pointer(&val
);
1974 g_value_unset(&val
);
1976 if (account
== NULL
)
1979 if (optmenu
!= NULL
)
1980 aop_option_menu_select_by_data(optmenu
, account
);
1986 add_screenname_autocomplete_entry(GtkListStore
*store
, const char *buddy_alias
, const char *contact_alias
,
1987 const PurpleAccount
*account
, const char *screenname
)
1990 gboolean completion_added
= FALSE
;
1991 gchar
*normalized_screenname
;
1994 tmp
= g_utf8_normalize(screenname
, -1, G_NORMALIZE_DEFAULT
);
1995 normalized_screenname
= g_utf8_casefold(tmp
, -1);
1998 /* There's no sense listing things like: 'xxx "xxx"'
1999 when the screenname and buddy alias match. */
2000 if (buddy_alias
&& strcmp(buddy_alias
, screenname
)) {
2001 char *completion_entry
= g_strdup_printf("%s \"%s\"", screenname
, buddy_alias
);
2002 char *tmp2
= g_utf8_normalize(buddy_alias
, -1, G_NORMALIZE_DEFAULT
);
2004 tmp
= g_utf8_casefold(tmp2
, -1);
2007 gtk_list_store_append(store
, &iter
);
2008 gtk_list_store_set(store
, &iter
,
2009 0, completion_entry
,
2011 2, normalized_screenname
,
2015 g_free(completion_entry
);
2017 completion_added
= TRUE
;
2020 /* There's no sense listing things like: 'xxx "xxx"'
2021 when the screenname and contact alias match. */
2022 if (contact_alias
&& strcmp(contact_alias
, screenname
)) {
2023 /* We don't want duplicates when the contact and buddy alias match. */
2024 if (!buddy_alias
|| strcmp(contact_alias
, buddy_alias
)) {
2025 char *completion_entry
= g_strdup_printf("%s \"%s\"",
2026 screenname
, contact_alias
);
2027 char *tmp2
= g_utf8_normalize(contact_alias
, -1, G_NORMALIZE_DEFAULT
);
2029 tmp
= g_utf8_casefold(tmp2
, -1);
2032 gtk_list_store_append(store
, &iter
);
2033 gtk_list_store_set(store
, &iter
,
2034 0, completion_entry
,
2036 2, normalized_screenname
,
2040 g_free(completion_entry
);
2042 completion_added
= TRUE
;
2046 if (completion_added
== FALSE
) {
2047 /* Add the buddy's screenname. */
2048 gtk_list_store_append(store
, &iter
);
2049 gtk_list_store_set(store
, &iter
,
2052 2, normalized_screenname
,
2058 g_free(normalized_screenname
);
2060 #endif /* NEW_STYLE_COMPLETION */
2062 static void get_log_set_name(PurpleLogSet
*set
, gpointer value
, PidginCompletionData
*data
)
2064 PidginFilterBuddyCompletionEntryFunc filter_func
= data
->filter_func
;
2065 gpointer user_data
= data
->filter_func_user_data
;
2067 /* 1. Don't show buddies because we will have gotten them already.
2068 * 2. The boxes that use this autocomplete code handle only IMs. */
2069 if (!set
->buddy
&& set
->type
== PURPLE_LOG_IM
) {
2070 PidginBuddyCompletionEntry entry
;
2071 entry
.is_buddy
= FALSE
;
2072 entry
.entry
.logged_buddy
= set
;
2074 if (filter_func(&entry
, user_data
)) {
2075 #ifdef NEW_STYLE_COMPLETION
2076 add_screenname_autocomplete_entry(data
->store
,
2077 NULL
, NULL
, set
->account
, set
->name
);
2079 /* Steal the name for the GCompletion. */
2080 data
->log_items
= g_list_append(data
->log_items
, set
->name
);
2081 set
->name
= set
->normalized_name
= NULL
;
2082 #endif /* NEW_STYLE_COMPLETION */
2088 add_completion_list(PidginCompletionData
*data
)
2090 PurpleBlistNode
*gnode
, *cnode
, *bnode
;
2091 PidginFilterBuddyCompletionEntryFunc filter_func
= data
->filter_func
;
2092 gpointer user_data
= data
->filter_func_user_data
;
2095 #ifdef NEW_STYLE_COMPLETION
2096 gtk_list_store_clear(data
->store
);
2098 GList
*item
= g_list_append(NULL
, NULL
);
2100 g_list_foreach(data
->completion
->items
, (GFunc
)g_free
, NULL
);
2101 g_completion_clear_items(data
->completion
);
2102 #endif /* NEW_STYLE_COMPLETION */
2104 for (gnode
= purple_get_blist()->root
; gnode
!= NULL
; gnode
= gnode
->next
)
2106 if (!PURPLE_BLIST_NODE_IS_GROUP(gnode
))
2109 for (cnode
= gnode
->child
; cnode
!= NULL
; cnode
= cnode
->next
)
2111 if (!PURPLE_BLIST_NODE_IS_CONTACT(cnode
))
2114 for (bnode
= cnode
->child
; bnode
!= NULL
; bnode
= bnode
->next
)
2116 PidginBuddyCompletionEntry entry
;
2117 entry
.is_buddy
= TRUE
;
2118 entry
.entry
.buddy
= (PurpleBuddy
*) bnode
;
2120 if (filter_func(&entry
, user_data
)) {
2121 #ifdef NEW_STYLE_COMPLETION
2122 add_screenname_autocomplete_entry(data
->store
,
2123 ((PurpleContact
*)cnode
)->alias
,
2124 purple_buddy_get_contact_alias(entry
.entry
.buddy
),
2125 entry
.entry
.buddy
->account
,
2126 entry
.entry
.buddy
->name
2129 item
->data
= g_strdup(entry
.entry
.buddy
->name
);
2130 g_completion_add_items(data
->completion
, item
);
2131 #endif /* NEW_STYLE_COMPLETION */
2137 #ifndef NEW_STYLE_COMPLETION
2139 data
->log_items
= NULL
;
2140 #endif /* NEW_STYLE_COMPLETION */
2142 sets
= purple_log_get_log_sets();
2143 g_hash_table_foreach(sets
, (GHFunc
)get_log_set_name
, data
);
2144 g_hash_table_destroy(sets
);
2146 #ifndef NEW_STYLE_COMPLETION
2147 g_completion_add_items(data
->completion
, data
->log_items
);
2148 g_list_free(data
->log_items
);
2149 #endif /* NEW_STYLE_COMPLETION */
2153 screenname_autocomplete_destroyed_cb(GtkWidget
*widget
, gpointer data
)
2156 purple_signals_disconnect_by_handle(widget
);
2160 repopulate_autocomplete(gpointer something
, gpointer data
)
2162 add_completion_list(data
);
2167 pidgin_setup_screenname_autocomplete_with_filter(GtkWidget
*entry
, GtkWidget
*accountopt
, PidginFilterBuddyCompletionEntryFunc filter_func
, gpointer user_data
)
2169 PidginCompletionData
*data
;
2171 #ifdef NEW_STYLE_COMPLETION
2172 /* Store the displayed completion value, the screenname, the UTF-8 normalized & casefolded screenname,
2173 * the UTF-8 normalized & casefolded value for comparison, and the account. */
2174 GtkListStore
*store
;
2176 GtkEntryCompletion
*completion
;
2178 data
= g_new0(PidginCompletionData
, 1);
2179 store
= gtk_list_store_new(5, G_TYPE_STRING
, G_TYPE_STRING
, G_TYPE_STRING
, G_TYPE_STRING
, G_TYPE_POINTER
);
2181 data
->entry
= entry
;
2182 data
->accountopt
= accountopt
;
2183 if (filter_func
== NULL
) {
2184 data
->filter_func
= pidgin_screenname_autocomplete_default_filter
;
2185 data
->filter_func_user_data
= NULL
;
2187 data
->filter_func
= filter_func
;
2188 data
->filter_func_user_data
= user_data
;
2190 data
->store
= store
;
2192 add_completion_list(data
);
2194 /* Sort the completion list by screenname. */
2195 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store
),
2196 1, GTK_SORT_ASCENDING
);
2198 completion
= gtk_entry_completion_new();
2199 gtk_entry_completion_set_match_func(completion
, screenname_completion_match_func
, NULL
, NULL
);
2201 g_signal_connect(G_OBJECT(completion
), "match-selected",
2202 G_CALLBACK(screenname_completion_match_selected_cb
), data
);
2204 gtk_entry_set_completion(GTK_ENTRY(entry
), completion
);
2205 g_object_unref(completion
);
2207 gtk_entry_completion_set_model(completion
, GTK_TREE_MODEL(store
));
2208 g_object_unref(store
);
2210 gtk_entry_completion_set_text_column(completion
, 0);
2212 #else /* !NEW_STYLE_COMPLETION */
2214 data
= g_new0(PidginCompletionData
, 1);
2216 data
->entry
= entry
;
2217 data
->accountopt
= accountopt
;
2218 if (filter_func
== NULL
) {
2219 data
->filter_func
= pidgin_screenname_autocomplete_default_filter
;
2220 data
->filter_func_user_data
= NULL
;
2222 data
->filter_func
= filter_func
;
2223 data
->filter_func_user_data
= user_data
;
2225 data
->completion
= g_completion_new(NULL
);
2226 data
->completion_started
= FALSE
;
2228 add_completion_list(data
);
2230 g_completion_set_compare(data
->completion
, g_ascii_strncasecmp
);
2232 g_signal_connect(G_OBJECT(entry
), "event",
2233 G_CALLBACK(completion_entry_event
), data
);
2234 g_signal_connect(G_OBJECT(entry
), "destroy",
2235 G_CALLBACK(destroy_completion_data
), data
);
2237 #endif /* !NEW_STYLE_COMPLETION */
2239 purple_signal_connect(purple_connections_get_handle(), "signed-on", entry
,
2240 PURPLE_CALLBACK(repopulate_autocomplete
), data
);
2241 purple_signal_connect(purple_connections_get_handle(), "signed-off", entry
,
2242 PURPLE_CALLBACK(repopulate_autocomplete
), data
);
2244 purple_signal_connect(purple_accounts_get_handle(), "account-added", entry
,
2245 PURPLE_CALLBACK(repopulate_autocomplete
), data
);
2246 purple_signal_connect(purple_accounts_get_handle(), "account-removed", entry
,
2247 PURPLE_CALLBACK(repopulate_autocomplete
), data
);
2249 g_signal_connect(G_OBJECT(entry
), "destroy", G_CALLBACK(screenname_autocomplete_destroyed_cb
), data
);
2253 pidgin_screenname_autocomplete_default_filter(const PidginBuddyCompletionEntry
*completion_entry
, gpointer all_accounts
) {
2254 gboolean all
= GPOINTER_TO_INT(all_accounts
);
2256 if (completion_entry
->is_buddy
) {
2257 return all
|| purple_account_is_connected(completion_entry
->entry
.buddy
->account
);
2259 return all
|| (completion_entry
->entry
.logged_buddy
->account
!= NULL
&& purple_account_is_connected(completion_entry
->entry
.logged_buddy
->account
));
2264 pidgin_setup_screenname_autocomplete(GtkWidget
*entry
, GtkWidget
*accountopt
, gboolean all
) {
2265 pidgin_setup_screenname_autocomplete_with_filter(entry
, accountopt
, pidgin_screenname_autocomplete_default_filter
, GINT_TO_POINTER(all
));
2270 void pidgin_set_cursor(GtkWidget
*widget
, GdkCursorType cursor_type
)
2274 g_return_if_fail(widget
!= NULL
);
2275 if (widget
->window
== NULL
)
2278 cursor
= gdk_cursor_new(cursor_type
);
2279 gdk_window_set_cursor(widget
->window
, cursor
);
2280 gdk_cursor_unref(cursor
);
2282 #if GTK_CHECK_VERSION(2,4,0)
2283 gdk_display_flush(gdk_drawable_get_display(GDK_DRAWABLE(widget
->window
)));
2289 void pidgin_clear_cursor(GtkWidget
*widget
)
2291 g_return_if_fail(widget
!= NULL
);
2292 if (widget
->window
== NULL
)
2295 gdk_window_set_cursor(widget
->window
, NULL
);
2298 struct _icon_chooser
{
2299 GtkWidget
*icon_filesel
;
2300 GtkWidget
*icon_preview
;
2301 GtkWidget
*icon_text
;
2303 void (*callback
)(const char*,gpointer
);
2307 #if !GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */
2309 icon_filesel_delete_cb(GtkWidget
*w
, struct _icon_chooser
*dialog
)
2311 if (dialog
->icon_filesel
!= NULL
)
2312 gtk_widget_destroy(dialog
->icon_filesel
);
2314 if (dialog
->callback
)
2315 dialog
->callback(NULL
, dialog
->data
);
2319 #endif /* FILECHOOSER */
2323 #if GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */
2325 icon_filesel_choose_cb(GtkWidget
*widget
, gint response
, struct _icon_chooser
*dialog
)
2327 char *filename
, *current_folder
;
2329 if (response
!= GTK_RESPONSE_ACCEPT
) {
2330 if (response
== GTK_RESPONSE_CANCEL
) {
2331 gtk_widget_destroy(dialog
->icon_filesel
);
2333 dialog
->icon_filesel
= NULL
;
2334 if (dialog
->callback
)
2335 dialog
->callback(NULL
, dialog
->data
);
2340 filename
= gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog
->icon_filesel
));
2341 current_folder
= gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(dialog
->icon_filesel
));
2342 if (current_folder
!= NULL
) {
2343 purple_prefs_set_path(PIDGIN_PREFS_ROOT
"/filelocations/last_icon_folder", current_folder
);
2344 g_free(current_folder
);
2347 #else /* FILECHOOSER */
2349 icon_filesel_choose_cb(GtkWidget
*w
, struct _icon_chooser
*dialog
)
2351 char *filename
, *current_folder
;
2353 filename
= g_strdup(gtk_file_selection_get_filename(
2354 GTK_FILE_SELECTION(dialog
->icon_filesel
)));
2356 /* If they typed in a directory, change there */
2357 if (pidgin_check_if_dir(filename
,
2358 GTK_FILE_SELECTION(dialog
->icon_filesel
)))
2364 current_folder
= g_path_get_dirname(filename
);
2365 if (current_folder
!= NULL
) {
2366 purple_prefs_set_path(PIDGIN_PREFS_ROOT
"/filelocations/last_icon_folder", current_folder
);
2367 g_free(current_folder
);
2370 #endif /* FILECHOOSER */
2371 #if 0 /* mismatched curly braces */
2374 if (dialog
->callback
)
2375 dialog
->callback(filename
, dialog
->data
);
2376 gtk_widget_destroy(dialog
->icon_filesel
);
2383 #if GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */
2384 icon_preview_change_cb(GtkFileChooser
*widget
, struct _icon_chooser
*dialog
)
2385 #else /* FILECHOOSER */
2386 icon_preview_change_cb(GtkTreeSelection
*sel
, struct _icon_chooser
*dialog
)
2387 #endif /* FILECHOOSER */
2389 GdkPixbuf
*pixbuf
, *scale
;
2391 char *basename
, *markup
, *size
;
2395 #if GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */
2396 filename
= gtk_file_chooser_get_preview_filename(
2397 GTK_FILE_CHOOSER(dialog
->icon_filesel
));
2398 #else /* FILECHOOSER */
2399 filename
= g_strdup(gtk_file_selection_get_filename(
2400 GTK_FILE_SELECTION(dialog
->icon_filesel
)));
2401 #endif /* FILECHOOSER */
2403 if (!filename
|| g_stat(filename
, &st
) || !(pixbuf
= gdk_pixbuf_new_from_file(filename
, NULL
)))
2405 gtk_image_set_from_pixbuf(GTK_IMAGE(dialog
->icon_preview
), NULL
);
2406 gtk_label_set_markup(GTK_LABEL(dialog
->icon_text
), "");
2411 width
= gdk_pixbuf_get_width(pixbuf
);
2412 height
= gdk_pixbuf_get_height(pixbuf
);
2413 basename
= g_path_get_basename(filename
);
2414 size
= purple_str_size_to_units(st
.st_size
);
2415 markup
= g_strdup_printf(_("<b>File:</b> %s\n"
2416 "<b>File size:</b> %s\n"
2417 "<b>Image size:</b> %dx%d"),
2418 basename
, size
, width
, height
);
2420 scale
= gdk_pixbuf_scale_simple(pixbuf
, width
* 50 / height
,
2421 50, GDK_INTERP_BILINEAR
);
2422 gtk_image_set_from_pixbuf(GTK_IMAGE(dialog
->icon_preview
), scale
);
2423 gtk_label_set_markup(GTK_LABEL(dialog
->icon_text
), markup
);
2425 g_object_unref(G_OBJECT(pixbuf
));
2426 g_object_unref(G_OBJECT(scale
));
2434 GtkWidget
*pidgin_buddy_icon_chooser_new(GtkWindow
*parent
, void(*callback
)(const char *, gpointer
), gpointer data
) {
2435 struct _icon_chooser
*dialog
= g_new0(struct _icon_chooser
, 1);
2437 #if GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */
2442 GtkTreeSelection
*sel
;
2443 #endif /* FILECHOOSER */
2444 const char *current_folder
;
2446 dialog
->callback
= callback
;
2447 dialog
->data
= data
;
2449 if (dialog
->icon_filesel
!= NULL
) {
2450 gtk_window_present(GTK_WINDOW(dialog
->icon_filesel
));
2454 current_folder
= purple_prefs_get_path(PIDGIN_PREFS_ROOT
"/filelocations/last_icon_folder");
2455 #if GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */
2457 dialog
->icon_filesel
= gtk_file_chooser_dialog_new(_("Buddy Icon"),
2459 GTK_FILE_CHOOSER_ACTION_OPEN
,
2460 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
2461 GTK_STOCK_OPEN
, GTK_RESPONSE_ACCEPT
,
2463 gtk_dialog_set_default_response(GTK_DIALOG(dialog
->icon_filesel
), GTK_RESPONSE_ACCEPT
);
2464 if ((current_folder
!= NULL
) && (*current_folder
!= '\0'))
2465 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog
->icon_filesel
),
2468 dialog
->icon_preview
= gtk_image_new();
2469 dialog
->icon_text
= gtk_label_new(NULL
);
2471 vbox
= gtk_vbox_new(FALSE
, PIDGIN_HIG_BOX_SPACE
);
2472 gtk_widget_set_size_request(GTK_WIDGET(vbox
), -1, 50);
2473 gtk_box_pack_start(GTK_BOX(vbox
), GTK_WIDGET(dialog
->icon_preview
), TRUE
, FALSE
, 0);
2474 gtk_box_pack_end(GTK_BOX(vbox
), GTK_WIDGET(dialog
->icon_text
), FALSE
, FALSE
, 0);
2475 gtk_widget_show_all(vbox
);
2477 gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog
->icon_filesel
), vbox
);
2478 gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(dialog
->icon_filesel
), TRUE
);
2479 gtk_file_chooser_set_use_preview_label(GTK_FILE_CHOOSER(dialog
->icon_filesel
), FALSE
);
2481 g_signal_connect(G_OBJECT(dialog
->icon_filesel
), "update-preview",
2482 G_CALLBACK(icon_preview_change_cb
), dialog
);
2483 g_signal_connect(G_OBJECT(dialog
->icon_filesel
), "response",
2484 G_CALLBACK(icon_filesel_choose_cb
), dialog
);
2485 icon_preview_change_cb(NULL
, dialog
);
2486 #else /* FILECHOOSER */
2487 dialog
->icon_filesel
= gtk_file_selection_new(_("Buddy Icon"));
2488 dialog
->icon_preview
= gtk_image_new();
2489 dialog
->icon_text
= gtk_label_new(NULL
);
2490 if ((current_folder
!= NULL
) && (*current_folder
!= '\0'))
2491 gtk_file_selection_set_filename(GTK_FILE_SELECTION(dialog
->icon_filesel
),
2494 gtk_widget_set_size_request(GTK_WIDGET(dialog
->icon_preview
),-1, 50);
2495 hbox
= gtk_hbox_new(FALSE
, PIDGIN_HIG_BOX_SPACE
);
2497 GTK_BOX(GTK_FILE_SELECTION(dialog
->icon_filesel
)->main_vbox
),
2498 hbox
, FALSE
, FALSE
, 0);
2499 gtk_box_pack_end(GTK_BOX(hbox
), dialog
->icon_preview
,
2501 gtk_box_pack_end(GTK_BOX(hbox
), dialog
->icon_text
, FALSE
, FALSE
, 0);
2503 tv
= GTK_FILE_SELECTION(dialog
->icon_filesel
)->file_list
;
2504 sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(tv
));
2506 g_signal_connect(G_OBJECT(sel
), "changed",
2507 G_CALLBACK(icon_preview_change_cb
), dialog
);
2509 G_OBJECT(GTK_FILE_SELECTION(dialog
->icon_filesel
)->ok_button
),
2511 G_CALLBACK(icon_filesel_choose_cb
), dialog
);
2513 G_OBJECT(GTK_FILE_SELECTION(dialog
->icon_filesel
)->cancel_button
),
2515 G_CALLBACK(icon_filesel_delete_cb
), dialog
);
2516 g_signal_connect(G_OBJECT(dialog
->icon_filesel
), "destroy",
2517 G_CALLBACK(icon_filesel_delete_cb
), dialog
);
2518 #endif /* FILECHOOSER */
2521 g_signal_connect(G_OBJECT(dialog
->icon_filesel
), "show",
2522 G_CALLBACK(winpidgin_ensure_onscreen
), dialog
->icon_filesel
);
2525 return dialog
->icon_filesel
;
2529 #if GTK_CHECK_VERSION(2,2,0)
2531 str_array_match(char **a
, char **b
)
2537 for (i
= 0; a
[i
] != NULL
; i
++)
2538 for (j
= 0; b
[j
] != NULL
; j
++)
2539 if (!g_ascii_strcasecmp(a
[i
], b
[j
]))
2546 pidgin_convert_buddy_icon(PurplePlugin
*plugin
, const char *path
, size_t *len
)
2548 PurplePluginProtocolInfo
*prpl_info
;
2549 #if GTK_CHECK_VERSION(2,2,0)
2550 char **prpl_formats
;
2552 char **pixbuf_formats
= NULL
;
2553 GdkPixbufFormat
*format
;
2555 #if !GTK_CHECK_VERSION(2,4,0)
2556 GdkPixbufLoader
*loader
;
2562 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(plugin
);
2564 g_return_val_if_fail(prpl_info
->icon_spec
.format
!= NULL
, NULL
);
2567 #if GTK_CHECK_VERSION(2,2,0)
2568 #if GTK_CHECK_VERSION(2,4,0)
2569 format
= gdk_pixbuf_get_file_info(path
, &width
, &height
);
2571 loader
= gdk_pixbuf_loader_new();
2572 if (g_file_get_contents(path
, &contents
, &length
, NULL
)) {
2573 gdk_pixbuf_loader_write(loader
, contents
, length
, NULL
);
2576 gdk_pixbuf_loader_close(loader
, NULL
);
2577 pixbuf
= gdk_pixbuf_loader_get_pixbuf(loader
);
2578 width
= gdk_pixbuf_get_width(pixbuf
);
2579 height
= gdk_pixbuf_get_height(pixbuf
);
2580 format
= gdk_pixbuf_loader_get_format(loader
);
2581 g_object_unref(G_OBJECT(loader
));
2586 pixbuf_formats
= gdk_pixbuf_format_get_extensions(format
);
2587 prpl_formats
= g_strsplit(prpl_info
->icon_spec
.format
,",",0);
2588 if (str_array_match(pixbuf_formats
, prpl_formats
) && /* This is an acceptable format AND */
2589 (!(prpl_info
->icon_spec
.scale_rules
& PURPLE_ICON_SCALE_SEND
) || /* The prpl doesn't scale before it sends OR */
2590 (prpl_info
->icon_spec
.min_width
<= width
&&
2591 prpl_info
->icon_spec
.max_width
>= width
&&
2592 prpl_info
->icon_spec
.min_height
<= height
&&
2593 prpl_info
->icon_spec
.max_height
>= height
))) /* The icon is the correct size */
2596 #if GTK_CHECK_VERSION(2,2,0)
2597 g_strfreev(prpl_formats
);
2598 g_strfreev(pixbuf_formats
);
2600 /* We don't need to scale the image. */
2603 if (!g_file_get_contents(path
, &contents
, &length
, NULL
))
2606 #if GTK_CHECK_VERSION(2,2,0) && !GTK_CHECK_VERSION(2,4,0)
2607 g_object_unref(G_OBJECT(pixbuf
));
2612 #if GTK_CHECK_VERSION(2,2,0)
2616 GError
*error
= NULL
;
2618 gboolean success
= FALSE
;
2619 char *filename
= NULL
;
2621 g_strfreev(pixbuf_formats
);
2623 pixbuf
= gdk_pixbuf_new_from_file(path
, &error
);
2625 purple_debug_error("buddyicon", "Could not open icon for conversion: %s\n", error
->message
);
2626 g_error_free(error
);
2627 g_strfreev(prpl_formats
);
2631 if ((prpl_info
->icon_spec
.scale_rules
& PURPLE_ICON_SCALE_SEND
) &&
2632 (width
< prpl_info
->icon_spec
.min_width
||
2633 width
> prpl_info
->icon_spec
.max_width
||
2634 height
< prpl_info
->icon_spec
.min_height
||
2635 height
> prpl_info
->icon_spec
.max_height
))
2637 int new_width
= width
;
2638 int new_height
= height
;
2640 purple_buddy_icon_get_scale_size(&prpl_info
->icon_spec
, &new_width
, &new_height
);
2642 scale
= gdk_pixbuf_scale_simple(pixbuf
, new_width
, new_height
,
2644 g_object_unref(G_OBJECT(pixbuf
));
2648 for (i
= 0; prpl_formats
[i
]; i
++) {
2652 fp
= purple_mkstemp(&filename
, TRUE
);
2660 purple_debug_info("buddyicon", "Converting buddy icon to %s as %s\n", prpl_formats
[i
], filename
);
2661 /* The "compression" param wasn't supported until gdk-pixbuf 2.8.
2662 * Using it in previous versions causes the save to fail (and an assert message). */
2663 if ((gdk_pixbuf_major_version
> 2 || (gdk_pixbuf_major_version
== 2
2664 && gdk_pixbuf_minor_version
>= 8))
2665 && strcmp(prpl_formats
[i
], "png") == 0) {
2666 if (gdk_pixbuf_save(pixbuf
, filename
, prpl_formats
[i
],
2667 &error
, "compression", "9", NULL
)) {
2671 } else if (gdk_pixbuf_save(pixbuf
, filename
, prpl_formats
[i
],
2677 /* The NULL checking is necessary due to this bug:
2678 * http://bugzilla.gnome.org/show_bug.cgi?id=405539 */
2679 purple_debug_warning("buddyicon", "Could not convert to %s: %s\n", prpl_formats
[i
],
2680 (error
&& error
->message
) ? error
->message
: "Unknown error");
2681 g_error_free(error
);
2684 g_strfreev(prpl_formats
);
2685 g_object_unref(G_OBJECT(pixbuf
));
2687 purple_debug_error("buddyicon", "Could not convert icon to usable format.\n");
2692 if (!g_file_get_contents(filename
, &contents
, &length
, NULL
))
2694 purple_debug_error("buddyicon",
2695 "Could not read '%s', which we just wrote to disk.\n",
2707 /* Check the image size */
2709 * TODO: If the file is too big, it would be cool if we checked if
2710 * the prpl supported jpeg, and then we could convert to that
2711 * and use a lower quality setting.
2713 if ((prpl_info
->icon_spec
.max_filesize
!= 0) &&
2714 (length
> prpl_info
->icon_spec
.max_filesize
))
2717 tmp
= g_strdup_printf(_("The file '%s' is too large for %s. Please try a smaller image.\n"),
2718 path
, plugin
->info
->name
);
2719 purple_notify_error(NULL
, _("Icon Error"),
2720 _("Could not set icon"), tmp
);
2721 purple_debug_info("buddyicon",
2722 "'%s' was converted to an image which is %" G_GSIZE_FORMAT
2723 " bytes, but the maximum icon size for %s is %" G_GSIZE_FORMAT
2724 " bytes\n", path
, length
, plugin
->info
->name
,
2725 prpl_info
->icon_spec
.max_filesize
);
2735 * The chosen icon wasn't the right size, and we're using
2736 * GTK+ 2.0 so we can't scale it.
2742 #if !GTK_CHECK_VERSION(2,6,0)
2744 _gdk_file_scale_size_prepared_cb (GdkPixbufLoader
*loader
,
2752 gboolean preserve_aspect_ratio
;
2755 g_return_if_fail (width
> 0 && height
> 0);
2757 if (info
->preserve_aspect_ratio
&&
2758 (info
->width
> 0 || info
->height
> 0)) {
2759 if (info
->width
< 0)
2761 width
= width
* (double)info
->height
/(double)height
;
2762 height
= info
->height
;
2764 else if (info
->height
< 0)
2766 height
= height
* (double)info
->width
/(double)width
;
2767 width
= info
->width
;
2769 else if ((double)height
* (double)info
->width
>
2770 (double)width
* (double)info
->height
) {
2771 width
= 0.5 + (double)width
* (double)info
->height
/ (double)height
;
2772 height
= info
->height
;
2774 height
= 0.5 + (double)height
* (double)info
->width
/ (double)width
;
2775 width
= info
->width
;
2778 if (info
->width
> 0)
2779 width
= info
->width
;
2780 if (info
->height
> 0)
2781 height
= info
->height
;
2784 #if GTK_CHECK_VERSION(2,2,0) /* 2.0 users are going to have very strangely sized things */
2785 gdk_pixbuf_loader_set_size (loader
, width
, height
);
2787 #warning nosnilmot could not be bothered to fix this properly for you
2788 #warning ... good luck ... your images may end up strange sizes
2793 gdk_pixbuf_new_from_file_at_scale(const char *filename
, int width
, int height
,
2794 gboolean preserve_aspect_ratio
,
2797 GdkPixbufLoader
*loader
;
2799 guchar buffer
[4096];
2805 gboolean preserve_aspect_ratio
;
2807 GdkPixbufAnimation
*animation
;
2808 GdkPixbufAnimationIter
*iter
;
2811 g_return_val_if_fail (filename
!= NULL
, NULL
);
2812 g_return_val_if_fail (width
> 0 || width
== -1, NULL
);
2813 g_return_val_if_fail (height
> 0 || height
== -1, NULL
);
2815 f
= g_fopen (filename
, "rb");
2817 gint save_errno
= errno
;
2818 gchar
*display_name
= g_filename_to_utf8 (filename
, -1, NULL
, NULL
, NULL
);
2819 g_set_error (error
, G_FILE_ERROR
, g_file_error_from_errno (save_errno
),
2820 _("Failed to open file '%s': %s"),
2821 display_name
? display_name
: "(unknown)",
2822 g_strerror (save_errno
));
2823 g_free (display_name
);
2827 loader
= gdk_pixbuf_loader_new ();
2830 info
.height
= height
;
2831 info
.preserve_aspect_ratio
= preserve_aspect_ratio
;
2833 g_signal_connect (loader
, "size-prepared", G_CALLBACK (_gdk_file_scale_size_prepared_cb
), &info
);
2836 while (!has_frame
&& !feof (f
) && !ferror (f
)) {
2837 length
= fread (buffer
, 1, sizeof (buffer
), f
);
2839 if (!gdk_pixbuf_loader_write (loader
, buffer
, length
, error
)) {
2840 gdk_pixbuf_loader_close (loader
, NULL
);
2842 g_object_unref (loader
);
2846 animation
= gdk_pixbuf_loader_get_animation (loader
);
2848 iter
= gdk_pixbuf_animation_get_iter (animation
, 0);
2849 if (!gdk_pixbuf_animation_iter_on_currently_loading_frame (iter
)) {
2852 g_object_unref (iter
);
2858 if (!gdk_pixbuf_loader_close (loader
, error
) && !has_frame
) {
2859 g_object_unref (loader
);
2863 pixbuf
= gdk_pixbuf_loader_get_pixbuf (loader
);
2866 gchar
*display_name
= g_filename_to_utf8 (filename
, -1, NULL
, NULL
, NULL
);
2867 g_object_unref (loader
);
2868 g_set_error (error
, GDK_PIXBUF_ERROR
, GDK_PIXBUF_ERROR_FAILED
,
2869 _("Failed to load image '%s': reason not known, probably a corrupt image file"),
2870 display_name
? display_name
: "(unknown)");
2871 g_free (display_name
);
2875 g_object_ref (pixbuf
);
2877 g_object_unref (loader
);
2881 #endif /* ! Gtk 2.6.0 */
2883 void pidgin_set_custom_buddy_icon(PurpleAccount
*account
, const char *who
, const char *filename
)
2886 PurpleContact
*contact
;
2888 buddy
= purple_find_buddy(account
, who
);
2890 purple_debug_info("custom-icon", "You can only set custom icon for someone in your buddylist.\n");
2894 contact
= purple_buddy_get_contact(buddy
);
2895 purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode
*)contact
, filename
);
2898 char *pidgin_make_pretty_arrows(const char *str
)
2901 char **split
= g_strsplit(str
, "->", -1);
2902 ret
= g_strjoinv("\342\207\250", split
);
2905 split
= g_strsplit(ret
, "<-", -1);
2907 ret
= g_strjoinv("\342\207\246", split
);
2913 void pidgin_set_urgent(GtkWindow
*window
, gboolean urgent
)
2915 #if GTK_CHECK_VERSION(2,8,0)
2916 gtk_window_set_urgency_hint(window
, urgent
);
2917 #elif defined _WIN32
2918 winpidgin_window_flash(window
, urgent
);
2919 #elif defined GDK_WINDOWING_X11
2923 g_return_if_fail(window
!= NULL
);
2925 gdkwin
= GTK_WIDGET(window
)->window
;
2927 g_return_if_fail(gdkwin
!= NULL
);
2929 hints
= XGetWMHints(GDK_WINDOW_XDISPLAY(gdkwin
),
2930 GDK_WINDOW_XWINDOW(gdkwin
));
2932 hints
= XAllocWMHints();
2935 hints
->flags
|= XUrgencyHint
;
2937 hints
->flags
&= ~XUrgencyHint
;
2938 XSetWMHints(GDK_WINDOW_XDISPLAY(gdkwin
),
2939 GDK_WINDOW_XWINDOW(gdkwin
), hints
);
2942 /* do something else? */
2946 GSList
*minidialogs
= NULL
;
2949 pidgin_utils_get_handle(void)
2956 static void connection_signed_off_cb(PurpleConnection
*gc
)
2958 GSList
*list
, *l_next
;
2959 for (list
= minidialogs
; list
; list
= l_next
) {
2960 l_next
= list
->next
;
2961 if (g_object_get_data(G_OBJECT(list
->data
), "gc") == gc
) {
2962 gtk_widget_destroy(GTK_WIDGET(list
->data
));
2967 static void alert_killed_cb(GtkWidget
*widget
)
2969 minidialogs
= g_slist_remove(minidialogs
, widget
);
2972 struct _old_button_clicked_cb_data
2974 PidginUtilMiniDialogCallback cb
;
2979 old_mini_dialog_button_clicked_cb(PidginMiniDialog
*mini_dialog
,
2983 struct _old_button_clicked_cb_data
*data
= user_data
;
2984 data
->cb(data
->data
, button
);
2988 old_mini_dialog_destroy_cb(GtkWidget
*dialog
,
2991 while (cb_datas
!= NULL
)
2993 g_free(cb_datas
->data
);
2994 cb_datas
= g_list_delete_link(cb_datas
, cb_datas
);
2999 pidgin_make_mini_dialog(PurpleConnection
*gc
,
3000 const char *icon_name
,
3001 const char *primary
,
3002 const char *secondary
,
3006 PidginMiniDialog
*mini_dialog
;
3007 const char *button_text
;
3008 GList
*cb_datas
= NULL
;
3010 static gboolean first_call
= TRUE
;
3014 purple_signal_connect(purple_connections_get_handle(), "signed-off",
3015 pidgin_utils_get_handle(),
3016 PURPLE_CALLBACK(connection_signed_off_cb
), NULL
);
3019 mini_dialog
= pidgin_mini_dialog_new(primary
, secondary
, icon_name
);
3020 g_object_set_data(G_OBJECT(mini_dialog
), "gc" ,gc
);
3021 g_signal_connect(G_OBJECT(mini_dialog
), "destroy",
3022 G_CALLBACK(alert_killed_cb
), NULL
);
3024 va_start(args
, user_data
);
3025 while ((button_text
= va_arg(args
, char*))) {
3026 struct _old_button_clicked_cb_data
*data
= NULL
;
3027 PidginMiniDialogCallback wrapper_cb
= NULL
;
3028 PidginUtilMiniDialogCallback callback
=
3029 va_arg(args
, PidginUtilMiniDialogCallback
);
3031 if (callback
!= NULL
) {
3032 data
= g_new0(struct _old_button_clicked_cb_data
, 1);
3033 data
->cb
= callback
;
3034 data
->data
= user_data
;
3035 wrapper_cb
= old_mini_dialog_button_clicked_cb
;
3037 pidgin_mini_dialog_add_button(mini_dialog
, button_text
,
3039 cb_datas
= g_list_append(cb_datas
, data
);
3043 g_signal_connect(G_OBJECT(mini_dialog
), "destroy",
3044 G_CALLBACK(old_mini_dialog_destroy_cb
), cb_datas
);
3046 return GTK_WIDGET(mini_dialog
);
3050 * "This is so dead sexy."
3052 * "Best movie of the year."
3054 * This is the function that handles CTRL+F searching in the buddy list.
3055 * It finds the top-most buddy/group/chat/whatever containing the
3058 * It's somewhat ineffecient, because we strip all the HTML from the
3059 * "name" column of the buddy list (because the GtkTreeModel does not
3060 * contain the screen name in a non-markedup format). But the alternative
3061 * is to add an extra column to the GtkTreeModel. And this function is
3062 * used rarely, so it shouldn't matter TOO much.
3064 gboolean
pidgin_tree_view_search_equal_func(GtkTreeModel
*model
, gint column
,
3065 const gchar
*key
, GtkTreeIter
*iter
, gpointer data
)
3067 gchar
*enteredstring
;
3075 PangoLogAttr
*log_attrs
;
3078 if (g_ascii_strcasecmp(key
, "Global Thermonuclear War") == 0)
3080 purple_notify_info(NULL
, "WOPR",
3081 "Wouldn't you prefer a nice game of chess?", NULL
);
3085 gtk_tree_model_get(model
, iter
, column
, &withmarkup
, -1);
3086 if (withmarkup
== NULL
) /* This is probably a separator */
3089 tmp
= g_utf8_normalize(key
, -1, G_NORMALIZE_DEFAULT
);
3090 enteredstring
= g_utf8_casefold(tmp
, -1);
3093 nomarkup
= purple_markup_strip_html(withmarkup
);
3094 tmp
= g_utf8_normalize(nomarkup
, -1, G_NORMALIZE_DEFAULT
);
3096 normalized
= g_utf8_casefold(tmp
, -1);
3099 if (purple_str_has_prefix(normalized
, enteredstring
))
3102 g_free(enteredstring
);
3108 /* Use Pango to separate by words. */
3109 len
= g_utf8_strlen(normalized
, -1);
3110 log_attrs
= g_new(PangoLogAttr
, len
+ 1);
3112 pango_get_log_attrs(normalized
, strlen(normalized
), -1, NULL
, log_attrs
, len
+ 1);
3116 for (i
= 0; i
< (len
- 1) ; i
++)
3118 if (log_attrs
[i
].is_word_start
&&
3119 purple_str_has_prefix(word
, enteredstring
))
3124 word
= g_utf8_next_char(word
);
3128 /* The non-Pango version. */
3132 while (word
[0] != '\0')
3134 gunichar c
= g_utf8_get_char(word
);
3135 if (!g_unichar_isalnum(c
))
3137 word
= g_utf8_find_next_char(word
, NULL
);
3138 if (purple_str_has_prefix(word
, enteredstring
))
3145 word
= g_utf8_find_next_char(word
, NULL
);
3150 g_free(enteredstring
);
3157 gboolean
pidgin_gdk_pixbuf_is_opaque(GdkPixbuf
*pixbuf
) {
3158 int width
, height
, rowstride
, i
;
3159 unsigned char *pixels
;
3162 if (!gdk_pixbuf_get_has_alpha(pixbuf
))
3165 width
= gdk_pixbuf_get_width (pixbuf
);
3166 height
= gdk_pixbuf_get_height (pixbuf
);
3167 rowstride
= gdk_pixbuf_get_rowstride (pixbuf
);
3168 pixels
= gdk_pixbuf_get_pixels (pixbuf
);
3171 for (i
= 3; i
< rowstride
; i
+=4) {
3176 for (i
= 1; i
< height
- 1; i
++) {
3177 row
= pixels
+ (i
*rowstride
);
3178 if (row
[3] < 0xfe || row
[rowstride
-1] < 0xfe) {
3183 row
= pixels
+ ((height
-1) * rowstride
);
3184 for (i
= 3; i
< rowstride
; i
+=4) {
3192 void pidgin_gdk_pixbuf_make_round(GdkPixbuf
*pixbuf
) {
3193 int width
, height
, rowstride
;
3195 if (!gdk_pixbuf_get_has_alpha(pixbuf
))
3197 width
= gdk_pixbuf_get_width(pixbuf
);
3198 height
= gdk_pixbuf_get_height(pixbuf
);
3199 rowstride
= gdk_pixbuf_get_rowstride(pixbuf
);
3200 pixels
= gdk_pixbuf_get_pixels(pixbuf
);
3202 if (width
< 6 || height
< 6)
3208 pixels
[rowstride
+ 3] = 0x80;
3209 pixels
[rowstride
* 2 + 3] = 0xC0;
3212 pixels
[width
* 4 - 1] = 0;
3213 pixels
[width
* 4 - 5] = 0x80;
3214 pixels
[width
* 4 - 9] = 0xC0;
3215 pixels
[rowstride
+ (width
* 4) - 1] = 0x80;
3216 pixels
[(2 * rowstride
) + (width
* 4) - 1] = 0xC0;
3219 pixels
[(height
- 1) * rowstride
+ 3] = 0;
3220 pixels
[(height
- 1) * rowstride
+ 7] = 0x80;
3221 pixels
[(height
- 1) * rowstride
+ 11] = 0xC0;
3222 pixels
[(height
- 2) * rowstride
+ 3] = 0x80;
3223 pixels
[(height
- 3) * rowstride
+ 3] = 0xC0;
3226 pixels
[height
* rowstride
- 1] = 0;
3227 pixels
[(height
- 1) * rowstride
- 1] = 0x80;
3228 pixels
[(height
- 2) * rowstride
- 1] = 0xC0;
3229 pixels
[height
* rowstride
- 5] = 0x80;
3230 pixels
[height
* rowstride
- 9] = 0xC0;
3233 const char *pidgin_get_dim_grey_string(GtkWidget
*widget
) {
3234 static char dim_grey_string
[8] = "";
3240 style
= gtk_widget_get_style(widget
);
3244 snprintf(dim_grey_string
, sizeof(dim_grey_string
), "#%02x%02x%02x",
3245 style
->text_aa
[GTK_STATE_NORMAL
].red
>> 8,
3246 style
->text_aa
[GTK_STATE_NORMAL
].green
>> 8,
3247 style
->text_aa
[GTK_STATE_NORMAL
].blue
>> 8);
3248 return dim_grey_string
;
3251 #if !GTK_CHECK_VERSION(2,2,0)
3253 gtk_tree_path_new_from_indices (gint first_index
, ...)
3259 path
= gtk_tree_path_new ();
3261 va_start (args
, first_index
);
3266 gtk_tree_path_append_index (path
, arg
);
3267 arg
= va_arg (args
, gint
);
3277 combo_box_changed_cb(GtkComboBox
*combo_box
, GtkEntry
*entry
)
3279 char *text
= gtk_combo_box_get_active_text(combo_box
);
3280 gtk_entry_set_text(entry
, text
? text
: "");
3285 entry_key_pressed_cb(GtkWidget
*entry
, GdkEventKey
*key
, GtkComboBox
*combo
)
3287 if (key
->keyval
== GDK_Down
|| key
->keyval
== GDK_Up
) {
3288 gtk_combo_box_popup(combo
);
3295 pidgin_text_combo_box_entry_new(const char *default_item
, GList
*items
)
3297 GtkComboBox
*ret
= NULL
;
3298 GtkWidget
*the_entry
= NULL
;
3300 ret
= GTK_COMBO_BOX(gtk_combo_box_new_text());
3301 the_entry
= gtk_entry_new();
3302 gtk_container_add(GTK_CONTAINER(ret
), the_entry
);
3305 gtk_entry_set_text(GTK_ENTRY(the_entry
), default_item
);
3307 for (; items
!= NULL
; items
= items
->next
) {
3308 char *text
= items
->data
;
3310 gtk_combo_box_append_text(ret
, text
);
3313 g_signal_connect(G_OBJECT(ret
), "changed", (GCallback
)combo_box_changed_cb
, the_entry
);
3314 g_signal_connect_after(G_OBJECT(the_entry
), "key-press-event", G_CALLBACK(entry_key_pressed_cb
), ret
);
3316 return GTK_WIDGET(ret
);
3319 const char *pidgin_text_combo_box_entry_get_text(GtkWidget
*widget
)
3321 return gtk_entry_get_text(GTK_ENTRY(GTK_BIN((widget
))->child
));
3324 void pidgin_text_combo_box_entry_set_text(GtkWidget
*widget
, const char *text
)
3326 gtk_entry_set_text(GTK_ENTRY(GTK_BIN((widget
))->child
), (text
));
3330 pidgin_add_widget_to_vbox(GtkBox
*vbox
, const char *widget_label
, GtkSizeGroup
*sg
, GtkWidget
*widget
, gboolean expand
, GtkWidget
**p_label
)
3333 GtkWidget
*label
= NULL
;
3336 hbox
= gtk_hbox_new(FALSE
, 5);
3337 gtk_widget_show(hbox
);
3338 gtk_box_pack_start(vbox
, hbox
, FALSE
, FALSE
, 0);
3340 label
= gtk_label_new_with_mnemonic(widget_label
);
3341 gtk_widget_show(label
);
3343 gtk_misc_set_alignment(GTK_MISC(label
), 0, 0.5);
3344 gtk_size_group_add_widget(sg
, label
);
3346 gtk_box_pack_start(GTK_BOX(hbox
), label
, FALSE
, FALSE
, 0);
3348 hbox
= GTK_WIDGET(vbox
);
3351 gtk_widget_show(widget
);
3352 gtk_box_pack_start(GTK_BOX(hbox
), widget
, expand
, TRUE
, 0);
3354 gtk_label_set_mnemonic_widget(GTK_LABEL(label
), widget
);
3355 pidgin_set_accessible_label (widget
, label
);
3363 gboolean
pidgin_auto_parent_window(GtkWidget
*widget
)
3366 /* This looks at the most recent window that received focus, and makes
3367 * that the parent window. */
3369 static GdkAtom _WindowTime
= GDK_NONE
;
3370 static GdkAtom _Cardinal
= GDK_NONE
;
3371 GList
*windows
= NULL
;
3372 GtkWidget
*parent
= NULL
;
3373 time_t window_time
= 0;
3375 windows
= gtk_window_list_toplevels();
3377 if (_WindowTime
== GDK_NONE
) {
3378 _WindowTime
= gdk_x11_xatom_to_atom(gdk_x11_get_xatom_by_name("_NET_WM_USER_TIME"));
3380 if (_Cardinal
== GDK_NONE
) {
3381 _Cardinal
= gdk_atom_intern("CARDINAL", FALSE
);
3385 GtkWidget
*window
= windows
->data
;
3386 guchar
*data
= NULL
;
3390 windows
= g_list_delete_link(windows
, windows
);
3392 if (window
== widget
||
3393 !GTK_WIDGET_VISIBLE(window
))
3396 if (!gdk_property_get(window
->window
, _WindowTime
, _Cardinal
, 0, sizeof(time_t), FALSE
,
3397 NULL
, NULL
, &al
, &data
))
3399 value
= *(time_t *)data
;
3400 if (window_time
< value
) {
3401 window_time
= value
;
3407 g_list_free(windows
);
3409 if (!gtk_get_current_event() && gtk_window_has_toplevel_focus(GTK_WINDOW(parent
))) {
3410 /* The window is in focus, and the new window was not triggered by a keypress/click
3411 * event. So do not set it transient, to avoid focus stealing and all that.
3415 gtk_window_set_transient_for(GTK_WINDOW(widget
), GTK_WINDOW(parent
));
3421 #if GTK_CHECK_VERSION(2,4,0)
3422 /* This finds the currently active window and makes that the parent window. */
3423 GList
*windows
= NULL
;
3424 GtkWidget
*parent
= NULL
;
3425 GdkEvent
*event
= gtk_get_current_event();
3426 GdkWindow
*menu
= NULL
;
3429 /* The window was not triggered by a user action. */
3432 /* We need to special case events from a popup menu. */
3433 if (event
->type
== GDK_BUTTON_RELEASE
) {
3434 /* XXX: Neither of the following works:
3435 menu = event->button.window;
3436 menu = gdk_window_get_parent(event->button.window);
3437 menu = gdk_window_get_toplevel(event->button.window);
3439 } else if (event
->type
== GDK_KEY_PRESS
)
3440 menu
= event
->key
.window
;
3442 windows
= gtk_window_list_toplevels();
3444 GtkWidget
*window
= windows
->data
;
3445 windows
= g_list_delete_link(windows
, windows
);
3447 if (window
== widget
||
3448 !GTK_WIDGET_VISIBLE(window
)) {
3452 if (gtk_window_has_toplevel_focus(GTK_WINDOW(window
)) ||
3453 (menu
&& menu
== window
->window
)) {
3459 g_list_free(windows
);
3461 gtk_window_set_transient_for(GTK_WINDOW(widget
), GTK_WINDOW(parent
));
3469 GdkPixbuf
* pidgin_pixbuf_from_imgstore(PurpleStoredImage
*image
)
3472 GdkPixbufLoader
*loader
= gdk_pixbuf_loader_new();
3473 gdk_pixbuf_loader_write(loader
, purple_imgstore_get_data(image
),
3474 purple_imgstore_get_size(image
), NULL
);
3475 gdk_pixbuf_loader_close(loader
, NULL
);
3476 pixbuf
= gdk_pixbuf_loader_get_pixbuf(loader
);
3478 g_object_ref(pixbuf
);
3479 g_object_unref(loader
);