3 * Pidgin is the legal property of its developers, whose names are too numerous
4 * to list here. Please refer to the COPYRIGHT file distributed with this
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
23 #include "glibcompat.h"
28 # include <shellapi.h>
31 #include <gdk/gdkkeysyms.h>
35 #include "conversation.h"
45 #include "gtkaccount.h"
49 #include "gtkdialogs.h"
50 #include "pidginstock.h"
51 #include "gtkrequest.h"
53 #include "pidgin/minidialog.h"
55 #include "gtk3compat.h"
58 /******************************************************************************
60 *****************************************************************************/
76 COMPLETION_DISPLAYED_COLUMN
, /* displayed completion value */
77 COMPLETION_BUDDY_COLUMN
, /* buddy name */
78 COMPLETION_NORMALIZED_COLUMN
, /* UTF-8 normalized & casefolded buddy name */
79 COMPLETION_COMPARISON_COLUMN
, /* UTF-8 normalized & casefolded value for comparison */
80 COMPLETION_ACCOUNT_COLUMN
, /* account */
81 COMPLETION_COLUMN_COUNT
84 /******************************************************************************
86 *****************************************************************************/
95 PurpleAccount
*account
;
102 GtkWidget
*accountopt
;
104 PidginFilterBuddyCompletionEntryFunc filter_func
;
105 gpointer filter_func_user_data
;
108 } PidginCompletionData
;
110 struct _icon_chooser
{
111 GtkWidget
*icon_filesel
;
112 GtkWidget
*icon_preview
;
113 GtkWidget
*icon_text
;
115 void (*callback
)(const char*,gpointer
);
119 struct _old_button_clicked_cb_data
121 PidginUtilMiniDialogCallback cb
;
125 /******************************************************************************
127 *****************************************************************************/
129 static guint accels_save_timer
= 0;
130 static GSList
*minidialogs
= NULL
;
132 /******************************************************************************
134 *****************************************************************************/
136 void pidgin_window_init(GtkWindow
*wnd
, const char *title
, guint border_width
, const char *role
, gboolean resizable
)
139 gtk_window_set_title(wnd
, title
);
142 gtk_window_set_title(wnd
, PIDGIN_ALERT_TITLE
);
144 gtk_container_set_border_width(GTK_CONTAINER(wnd
), border_width
);
146 gtk_window_set_role(wnd
, role
);
147 gtk_window_set_resizable(wnd
, resizable
);
151 pidgin_create_window(const char *title
, guint border_width
, const char *role
, gboolean resizable
)
153 GtkWindow
*wnd
= NULL
;
155 wnd
= GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL
));
156 pidgin_window_init(wnd
, title
, border_width
, role
, resizable
);
158 return GTK_WIDGET(wnd
);
162 pidgin_create_small_button(GtkWidget
*image
)
166 button
= gtk_button_new();
167 gtk_button_set_relief(GTK_BUTTON(button
), GTK_RELIEF_NONE
);
169 /* don't allow focus on the close button */
170 gtk_button_set_focus_on_click(GTK_BUTTON(button
), FALSE
);
172 gtk_widget_show(image
);
174 gtk_container_add(GTK_CONTAINER(button
), image
);
180 pidgin_create_dialog(const char *title
, guint border_width
, const char *role
, gboolean resizable
)
182 GtkWindow
*wnd
= NULL
;
184 wnd
= GTK_WINDOW(gtk_dialog_new());
185 pidgin_window_init(wnd
, title
, border_width
, role
, resizable
);
187 return GTK_WIDGET(wnd
);
191 pidgin_create_video_widget(void)
193 GtkWidget
*video
= NULL
;
194 GdkRGBA color
= {0.0, 0.0, 0.0, 1.0};
196 video
= gtk_drawing_area_new();
197 gtk_widget_override_background_color(video
, GTK_STATE_FLAG_NORMAL
, &color
);
199 /* In order to enable client shadow decorations, GtkDialog from GTK+ 3.0
200 * uses ARGB visual which by default gets inherited by its child widgets.
201 * XVideo adaptors on the other hand often support just depth 24 and
202 * rendering video through xvimagesink onto a widget inside a GtkDialog
203 * then results in no visible output.
205 * This ensures the default system visual of the drawing area doesn't get
206 * overridden by the widget's parent.
208 gtk_widget_set_visual(video
,
209 gdk_screen_get_system_visual(gtk_widget_get_screen(video
)));
215 pidgin_dialog_get_vbox_with_properties(GtkDialog
*dialog
, gboolean homogeneous
, gint spacing
)
217 GtkBox
*vbox
= GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog
)));
218 gtk_box_set_homogeneous(vbox
, homogeneous
);
219 gtk_box_set_spacing(vbox
, spacing
);
220 return GTK_WIDGET(vbox
);
223 GtkWidget
*pidgin_dialog_get_vbox(GtkDialog
*dialog
)
225 return gtk_dialog_get_content_area(GTK_DIALOG(dialog
));
228 GtkWidget
*pidgin_dialog_get_action_area(GtkDialog
*dialog
)
230 return gtk_dialog_get_action_area(GTK_DIALOG(dialog
));
233 GtkWidget
*pidgin_dialog_add_button(GtkDialog
*dialog
, const char *label
,
234 GCallback callback
, gpointer callbackdata
)
236 GtkWidget
*button
= gtk_button_new_with_mnemonic(label
);
237 GtkWidget
*bbox
= pidgin_dialog_get_action_area(dialog
);
239 /* Handle stock labels if passed in until nothing calls this
240 * expecting a GtkStock button */
243 if (gtk_stock_lookup(label
, &item
)) {
244 g_object_set(button
, "use-stock", TRUE
, NULL
);
248 gtk_box_pack_start(GTK_BOX(bbox
), button
, FALSE
, FALSE
, 0);
250 g_signal_connect(G_OBJECT(button
), "clicked", callback
, callbackdata
);
251 gtk_widget_show(button
);
256 pidgin_set_sensitive_if_input(GtkWidget
*entry
, GtkWidget
*dialog
)
258 const char *text
= gtk_entry_get_text(GTK_ENTRY(entry
));
259 gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog
), GTK_RESPONSE_OK
,
263 GtkWidget
*pidgin_separator(GtkWidget
*menu
)
267 menuitem
= gtk_separator_menu_item_new();
268 gtk_widget_show(menuitem
);
269 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
273 GtkWidget
*pidgin_new_check_item(GtkWidget
*menu
, const char *str
,
274 GCallback cb
, gpointer data
, gboolean checked
)
277 menuitem
= gtk_check_menu_item_new_with_mnemonic(str
);
280 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
282 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem
), checked
);
285 g_signal_connect(G_OBJECT(menuitem
), "activate", cb
, data
);
287 gtk_widget_show_all(menuitem
);
293 pidgin_pixbuf_toolbar_button_from_stock(const char *icon
)
295 GtkWidget
*button
, *image
, *bbox
;
297 button
= gtk_toggle_button_new();
298 gtk_button_set_relief(GTK_BUTTON(button
), GTK_RELIEF_NONE
);
300 bbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0);
302 gtk_container_add (GTK_CONTAINER(button
), bbox
);
304 image
= gtk_image_new_from_stock(icon
, gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
));
305 gtk_box_pack_start(GTK_BOX(bbox
), image
, FALSE
, FALSE
, 0);
307 gtk_widget_show_all(bbox
);
313 pidgin_pixbuf_button_from_stock(const char *text
, const char *icon
,
314 PidginButtonOrientation style
)
316 GtkWidget
*button
, *image
, *bbox
, *ibox
, *lbox
= NULL
;
318 button
= gtk_button_new();
320 if (style
== PIDGIN_BUTTON_HORIZONTAL
) {
321 bbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0);
322 ibox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0);
324 lbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0);
326 bbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0);
327 ibox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0);
329 lbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0);
332 gtk_container_add(GTK_CONTAINER(button
), bbox
);
335 gtk_box_pack_start(GTK_BOX(bbox
), ibox
, TRUE
, TRUE
, 0);
336 image
= gtk_image_new_from_stock(icon
, GTK_ICON_SIZE_BUTTON
);
337 gtk_box_pack_end(GTK_BOX(ibox
), image
, FALSE
, TRUE
, 0);
343 gtk_box_pack_start(GTK_BOX(bbox
), lbox
, TRUE
, TRUE
, 0);
344 label
= GTK_LABEL(gtk_label_new(NULL
));
345 gtk_label_set_text_with_mnemonic(label
, text
);
346 gtk_label_set_mnemonic_widget(label
, button
);
347 gtk_box_pack_start(GTK_BOX(lbox
), GTK_WIDGET(label
),
349 pidgin_set_accessible_label(button
, label
);
352 gtk_widget_show_all(bbox
);
358 GtkWidget
*pidgin_new_menu_item(GtkWidget
*menu
, const char *mnemonic
,
359 const char *icon
, GCallback cb
, gpointer data
)
365 box
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, PIDGIN_HIG_BOX_SPACE
);
367 menuitem
= gtk_menu_item_new();
370 g_signal_connect(G_OBJECT(menuitem
), "activate", cb
, data
);
374 image
= gtk_image_new_from_stock(icon
, GTK_ICON_SIZE_MENU
);
375 gtk_container_add(GTK_CONTAINER(box
), image
);
378 label
= gtk_label_new_with_mnemonic(mnemonic
);
379 gtk_container_add(GTK_CONTAINER(box
), label
);
381 gtk_container_add(GTK_CONTAINER(menuitem
), box
);
384 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
386 gtk_widget_show_all(menuitem
);
392 pidgin_make_frame(GtkWidget
*parent
, const char *title
)
394 GtkWidget
*vbox
, *vbox2
, *hbox
;
398 vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, PIDGIN_HIG_BOX_SPACE
);
399 gtk_box_pack_start(GTK_BOX(parent
), vbox
, FALSE
, FALSE
, 0);
400 gtk_widget_show(vbox
);
402 label
= GTK_LABEL(gtk_label_new(NULL
));
404 labeltitle
= g_strdup_printf("<span weight=\"bold\">%s</span>", title
);
405 gtk_label_set_markup(label
, labeltitle
);
408 gtk_label_set_xalign(GTK_LABEL(label
), 0);
409 gtk_label_set_yalign(GTK_LABEL(label
), 0);
410 gtk_box_pack_start(GTK_BOX(vbox
), GTK_WIDGET(label
), FALSE
, FALSE
, 0);
411 gtk_widget_show(GTK_WIDGET(label
));
412 pidgin_set_accessible_label(vbox
, label
);
414 hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, PIDGIN_HIG_BOX_SPACE
);
415 gtk_box_pack_start (GTK_BOX (vbox
), hbox
, FALSE
, FALSE
, 0);
416 gtk_widget_show(hbox
);
418 label
= GTK_LABEL(gtk_label_new(" "));
419 gtk_box_pack_start(GTK_BOX(hbox
), GTK_WIDGET(label
), FALSE
, FALSE
, 0);
420 gtk_widget_show(GTK_WIDGET(label
));
422 vbox2
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, PIDGIN_HIG_BOX_SPACE
);
423 gtk_box_pack_start(GTK_BOX(hbox
), vbox2
, FALSE
, FALSE
, 0);
424 gtk_widget_show(vbox2
);
426 g_object_set_data(G_OBJECT(vbox2
), "main-vbox", vbox
);
432 aop_option_menu_get_selected(GtkWidget
*optmenu
)
434 gpointer data
= NULL
;
437 g_return_val_if_fail(optmenu
!= NULL
, NULL
);
439 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(optmenu
), &iter
))
440 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu
)),
441 &iter
, AOP_DATA_COLUMN
, &data
, -1);
447 aop_menu_cb(GtkWidget
*optmenu
, GCallback cb
)
450 ((void (*)(GtkWidget
*, gpointer
, gpointer
))cb
)(optmenu
,
451 aop_option_menu_get_selected(optmenu
),
452 g_object_get_data(G_OBJECT(optmenu
), "user_data"));
457 aop_option_menu_replace_menu(GtkWidget
*optmenu
, AopMenu
*new_aop_menu
)
459 gtk_combo_box_set_model(GTK_COMBO_BOX(optmenu
), new_aop_menu
->model
);
460 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu
), new_aop_menu
->default_item
);
461 g_free(new_aop_menu
);
465 pidgin_create_icon_from_protocol(PurpleProtocol
*protocol
, PidginProtocolIconSize size
, PurpleAccount
*account
)
467 const char *protoname
= NULL
;
469 char *filename
= NULL
;
472 protoname
= purple_protocol_class_list_icon(protocol
, account
, NULL
);
473 if (protoname
== NULL
)
477 * Status icons will be themeable too, and then it will look up
478 * protoname from the theme
480 tmp
= g_strconcat("im-", protoname
, ".png", NULL
);
482 filename
= g_build_filename(PURPLE_DATADIR
,
483 "pidgin", "icons", "hicolor",
484 (size
== PIDGIN_PROTOCOL_ICON_SMALL
) ? "16x16" :
485 ((size
== PIDGIN_PROTOCOL_ICON_MEDIUM
) ? "22x22" :
490 pixbuf
= pidgin_pixbuf_new_from_file(filename
);
497 aop_option_menu_new(AopMenu
*aop_menu
, GCallback cb
, gpointer user_data
)
499 GtkWidget
*optmenu
= NULL
;
500 GtkCellRenderer
*cr
= NULL
;
502 optmenu
= gtk_combo_box_new();
503 gtk_widget_show(optmenu
);
504 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(optmenu
), cr
= gtk_cell_renderer_pixbuf_new(), FALSE
);
505 gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(optmenu
), cr
, "pixbuf", AOP_ICON_COLUMN
);
506 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(optmenu
), cr
= gtk_cell_renderer_text_new(), TRUE
);
507 gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(optmenu
), cr
, "text", AOP_NAME_COLUMN
);
509 aop_option_menu_replace_menu(optmenu
, aop_menu
);
510 g_object_set_data(G_OBJECT(optmenu
), "user_data", user_data
);
512 g_signal_connect(G_OBJECT(optmenu
), "changed", G_CALLBACK(aop_menu_cb
), cb
);
518 aop_option_menu_select_by_data(GtkWidget
*optmenu
, gpointer data
)
523 model
= gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu
));
524 if (gtk_tree_model_get_iter_first(model
, &iter
)) {
526 gtk_tree_model_get(model
, &iter
, AOP_DATA_COLUMN
, &iter_data
, -1);
527 if (iter_data
== data
) {
528 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(optmenu
), &iter
);
531 } while (gtk_tree_model_iter_next(model
, &iter
));
536 create_protocols_menu(const char *default_proto_id
)
538 AopMenu
*aop_menu
= NULL
;
539 PurpleProtocol
*protocol
;
540 GdkPixbuf
*pixbuf
= NULL
;
546 ls
= gtk_list_store_new(AOP_COLUMN_COUNT
, GDK_TYPE_PIXBUF
, G_TYPE_STRING
, G_TYPE_POINTER
);
548 aop_menu
= g_malloc0(sizeof(AopMenu
));
549 aop_menu
->default_item
= 0;
550 aop_menu
->model
= GTK_TREE_MODEL(ls
);
552 list
= purple_protocols_get_all();
554 for (p
= list
, i
= 0;
558 protocol
= PURPLE_PROTOCOL(p
->data
);
560 pixbuf
= pidgin_create_icon_from_protocol(protocol
, PIDGIN_PROTOCOL_ICON_SMALL
, NULL
);
562 gtk_list_store_append(ls
, &iter
);
563 gtk_list_store_set(ls
, &iter
,
564 AOP_ICON_COLUMN
, pixbuf
,
565 AOP_NAME_COLUMN
, purple_protocol_get_name(protocol
),
566 AOP_DATA_COLUMN
, purple_protocol_get_id(protocol
),
570 g_object_unref(pixbuf
);
572 if (default_proto_id
!= NULL
&& purple_strequal(purple_protocol_get_id(protocol
), default_proto_id
))
573 aop_menu
->default_item
= i
;
581 pidgin_protocol_option_menu_new(const char *id
, GCallback cb
,
584 return aop_option_menu_new(create_protocols_menu(id
), cb
, user_data
);
588 pidgin_protocol_option_menu_get_selected(GtkWidget
*optmenu
)
590 return (const char *)aop_option_menu_get_selected(optmenu
);
594 pidgin_save_accels_cb(GtkAccelGroup
*accel_group
, guint arg1
,
595 GdkModifierType arg2
, GClosure
*arg3
,
598 purple_debug(PURPLE_DEBUG_MISC
, "accels",
599 "accel changed, scheduling save.\n");
601 if (!accels_save_timer
)
602 accels_save_timer
= g_timeout_add_seconds(5, pidgin_save_accels
,
607 pidgin_save_accels(gpointer data
)
609 char *filename
= NULL
;
611 filename
= g_build_filename(purple_user_dir(), G_DIR_SEPARATOR_S
,
613 purple_debug(PURPLE_DEBUG_MISC
, "accels", "saving accels to %s\n", filename
);
614 gtk_accel_map_save(filename
);
617 accels_save_timer
= 0;
624 char *filename
= NULL
;
626 filename
= g_build_filename(purple_user_dir(), G_DIR_SEPARATOR_S
,
628 gtk_accel_map_load(filename
);
633 show_retrieveing_info(PurpleConnection
*conn
, const char *name
)
635 PurpleNotifyUserInfo
*info
= purple_notify_user_info_new();
636 purple_notify_user_info_add_pair_plaintext(info
, _("Information"), _("Retrieving..."));
637 purple_notify_userinfo(conn
, name
, info
, NULL
, NULL
);
638 purple_notify_user_info_destroy(info
);
641 void pidgin_retrieve_user_info(PurpleConnection
*conn
, const char *name
)
643 show_retrieveing_info(conn
, name
);
644 purple_serv_get_info(conn
, name
);
647 void pidgin_retrieve_user_info_in_chat(PurpleConnection
*conn
, const char *name
, int chat
)
650 PurpleProtocol
*protocol
= NULL
;
653 pidgin_retrieve_user_info(conn
, name
);
657 protocol
= purple_connection_get_protocol(conn
);
658 if (protocol
!= NULL
)
659 who
= purple_protocol_chat_iface_get_user_real_name(protocol
, conn
, chat
, name
);
661 pidgin_retrieve_user_info(conn
, who
? who
: name
);
666 pidgin_parse_x_im_contact(const char *msg
, gboolean all_accounts
,
667 PurpleAccount
**ret_account
, char **ret_protocol
,
668 char **ret_username
, char **ret_alias
)
670 char *protocol
= NULL
;
671 char *username
= NULL
;
677 g_return_val_if_fail(msg
!= NULL
, FALSE
);
678 g_return_val_if_fail(ret_protocol
!= NULL
, FALSE
);
679 g_return_val_if_fail(ret_username
!= NULL
, FALSE
);
681 s
= str
= g_strdup(msg
);
683 while (*s
!= '\r' && *s
!= '\n' && *s
!= '\0')
690 while (*s
!= '\r' && *s
!= '\n' && *s
!= '\0' && *s
!= ' ')
701 if (*s
!= '\0') *s
++ = '\0';
703 /* Clear past any whitespace */
704 while (*s
!= '\0' && *s
== ' ')
707 /* Now let's grab until the end of the line. */
710 while (*s
!= '\r' && *s
!= '\n' && *s
!= '\0')
713 if (*s
== '\r') *s
++ = '\0';
714 if (*s
== '\n') *s
++ = '\0';
716 if (strchr(key
, ':') != NULL
)
718 if (!g_ascii_strcasecmp(key
, "X-IM-Username:"))
719 username
= g_strdup(value
);
720 else if (!g_ascii_strcasecmp(key
, "X-IM-Protocol:"))
721 protocol
= g_strdup(value
);
722 else if (!g_ascii_strcasecmp(key
, "X-IM-Alias:"))
723 alias
= g_strdup(value
);
727 if (username
!= NULL
&& protocol
!= NULL
)
731 *ret_username
= username
;
732 *ret_protocol
= protocol
;
734 if (ret_alias
!= NULL
)
737 /* Check for a compatible account. */
738 if (ret_account
!= NULL
)
741 PurpleAccount
*account
= NULL
;
743 const char *protoname
;
746 list
= purple_accounts_get_all();
748 list
= purple_connections_get_all();
750 for (l
= list
; l
!= NULL
; l
= l
->next
)
752 PurpleConnection
*gc
;
753 PurpleProtocol
*proto
= NULL
;
757 account
= (PurpleAccount
*)l
->data
;
759 proto
= purple_protocols_find(
760 purple_account_get_protocol_id(account
));
771 gc
= (PurpleConnection
*)l
->data
;
772 account
= purple_connection_get_account(gc
);
774 proto
= purple_connection_get_protocol(gc
);
777 protoname
= purple_protocol_class_list_icon(proto
, account
, NULL
);
779 if (purple_strequal(protoname
, protocol
))
785 /* Special case for AIM and ICQ */
786 if (account
== NULL
&& (purple_strequal(protocol
, "aim") ||
787 purple_strequal(protocol
, "icq")))
789 for (l
= list
; l
!= NULL
; l
= l
->next
)
791 PurpleConnection
*gc
;
792 PurpleProtocol
*proto
= NULL
;
796 account
= (PurpleAccount
*)l
->data
;
798 proto
= purple_protocols_find(
799 purple_account_get_protocol_id(account
));
810 gc
= (PurpleConnection
*)l
->data
;
811 account
= purple_connection_get_account(gc
);
813 proto
= purple_connection_get_protocol(gc
);
816 protoname
= purple_protocol_class_list_icon(proto
, account
, NULL
);
818 if (purple_strequal(protoname
, "aim") || purple_strequal(protoname
, "icq"))
825 *ret_account
= account
;
843 pidgin_set_accessible_label(GtkWidget
*w
, GtkLabel
*l
)
846 const gchar
*label_text
;
847 const gchar
*existing_name
;
849 acc
= gtk_widget_get_accessible (w
);
851 /* If this object has no name, set it's name with the label text */
852 existing_name
= atk_object_get_name (acc
);
853 if (!existing_name
) {
854 label_text
= gtk_label_get_text(l
);
856 atk_object_set_name (acc
, label_text
);
859 pidgin_set_accessible_relations(w
, l
);
863 pidgin_set_accessible_relations (GtkWidget
*w
, GtkLabel
*l
)
865 AtkObject
*acc
, *label
;
866 AtkObject
*rel_obj
[1];
868 AtkRelation
*relation
;
870 acc
= gtk_widget_get_accessible (w
);
871 label
= gtk_widget_get_accessible(GTK_WIDGET(l
));
873 /* Make sure mnemonics work */
874 gtk_label_set_mnemonic_widget(l
, w
);
876 /* Create the labeled-by relation */
877 set
= atk_object_ref_relation_set (acc
);
879 relation
= atk_relation_new (rel_obj
, 1, ATK_RELATION_LABELLED_BY
);
880 atk_relation_set_add (set
, relation
);
881 g_object_unref (relation
);
884 /* Create the label-for relation */
885 set
= atk_object_ref_relation_set (label
);
887 relation
= atk_relation_new (rel_obj
, 1, ATK_RELATION_LABEL_FOR
);
888 atk_relation_set_add (set
, relation
);
889 g_object_unref (relation
);
894 pidgin_menu_position_func_helper(GtkMenu
*menu
,
901 GtkRequisition requisition
;
903 GdkRectangle monitor
;
905 gint space_left
, space_right
, space_above
, space_below
;
908 g_return_if_fail(GTK_IS_MENU(menu
));
910 widget
= GTK_WIDGET(menu
);
911 screen
= gtk_widget_get_screen(widget
);
912 rtl
= (gtk_widget_get_direction(widget
) == GTK_TEXT_DIR_RTL
);
915 * We need the requisition to figure out the right place to
916 * popup the menu. In fact, we always need to ask here, since
917 * if a size_request was queued while we weren't popped up,
918 * the requisition won't have been recomputed yet.
920 gtk_widget_get_preferred_size(widget
, NULL
, &requisition
);
922 monitor_num
= gdk_screen_get_monitor_at_point (screen
, *x
, *y
);
927 * The placement of popup menus horizontally works like this (with
928 * RTL in parentheses)
930 * - If there is enough room to the right (left) of the mouse cursor,
931 * position the menu there.
933 * - Otherwise, if if there is enough room to the left (right) of the
934 * mouse cursor, position the menu there.
936 * - Otherwise if the menu is smaller than the monitor, position it
937 * on the side of the mouse cursor that has the most space available
939 * - Otherwise (if there is simply not enough room for the menu on the
940 * monitor), position it as far left (right) as possible.
942 * Positioning in the vertical direction is similar: first try below
943 * mouse cursor, then above.
945 gdk_screen_get_monitor_geometry (screen
, monitor_num
, &monitor
);
947 space_left
= *x
- monitor
.x
;
948 space_right
= monitor
.x
+ monitor
.width
- *x
- 1;
949 space_above
= *y
- monitor
.y
;
950 space_below
= monitor
.y
+ monitor
.height
- *y
- 1;
952 /* position horizontally */
954 if (requisition
.width
<= space_left
||
955 requisition
.width
<= space_right
)
957 if ((rtl
&& requisition
.width
<= space_left
) ||
958 (!rtl
&& requisition
.width
> space_right
))
961 *x
= *x
- requisition
.width
+ 1;
964 /* x is clamped on-screen further down */
966 else if (requisition
.width
<= monitor
.width
)
968 /* the menu is too big to fit on either side of the mouse
969 * cursor, but smaller than the monitor. Position it on
970 * the side that has the most space
972 if (space_left
> space_right
)
980 *x
= monitor
.x
+ monitor
.width
- requisition
.width
;
983 else /* menu is simply too big for the monitor */
988 *x
= monitor
.x
+ monitor
.width
- requisition
.width
;
997 /* Position vertically. The algorithm is the same as above, but
998 * simpler because we don't have to take RTL into account.
1001 if (requisition
.height
<= space_above
||
1002 requisition
.height
<= space_below
)
1004 if (requisition
.height
> space_below
) {
1005 *y
= *y
- requisition
.height
+ 1;
1008 *y
= CLAMP (*y
, monitor
.y
,
1009 monitor
.y
+ monitor
.height
- requisition
.height
);
1011 else if (requisition
.height
> space_below
&&
1012 requisition
.height
> space_above
)
1014 if (space_below
>= space_above
)
1015 *y
= monitor
.y
+ monitor
.height
- requisition
.height
;
1026 #if !GTK_CHECK_VERSION(3,22,0)
1028 pidgin_treeview_popup_menu_position_func(GtkMenu
*menu
,
1034 GtkWidget
*widget
= GTK_WIDGET(data
);
1035 GtkTreeView
*tv
= GTK_TREE_VIEW(data
);
1037 GtkTreeViewColumn
*col
;
1040 gdk_window_get_origin (gtk_widget_get_window(widget
), x
, y
);
1041 gtk_tree_view_get_cursor (tv
, &path
, &col
);
1042 gtk_tree_view_get_cell_area (tv
, path
, col
, &rect
);
1044 *x
+= rect
.x
+rect
.width
;
1045 *y
+= rect
.y
+ rect
.height
;
1046 pidgin_menu_position_func_helper(menu
, x
, y
, push_in
, data
);
1052 pidgin_menu_popup_at_treeview_selection(GtkWidget
*menu
, GtkWidget
*treeview
)
1054 #if GTK_CHECK_VERSION(3,22,0)
1056 GtkTreeViewColumn
*column
;
1057 GdkWindow
*bin_window
;
1060 gtk_tree_view_get_cursor(GTK_TREE_VIEW(treeview
), &path
, &column
);
1061 g_return_if_fail(path
!= NULL
);
1063 column
= gtk_tree_view_get_column(GTK_TREE_VIEW(treeview
), 0);
1064 bin_window
= gtk_tree_view_get_bin_window(GTK_TREE_VIEW(treeview
));
1065 gtk_tree_view_get_cell_area(GTK_TREE_VIEW(treeview
), path
, column
, &rect
);
1066 gtk_menu_popup_at_rect(GTK_MENU(menu
), bin_window
, &rect
,
1067 GDK_GRAVITY_SOUTH_WEST
, GDK_GRAVITY_NORTH_WEST
,
1070 gtk_tree_path_free(path
);
1072 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
,
1073 pidgin_treeview_popup_menu_position_func
, treeview
,
1074 0, GDK_CURRENT_TIME
);
1079 static void dnd_image_ok_callback(_DndData
*data
, int choice
)
1081 const gchar
*shortname
;
1086 PurpleConversation
*conv
;
1088 PurpleContact
*contact
;
1092 case DND_BUDDY_ICON
:
1093 if (g_stat(data
->filename
, &st
)) {
1096 str
= g_strdup_printf(_("The following error has occurred loading %s: %s"),
1097 data
->filename
, g_strerror(errno
));
1098 purple_notify_error(NULL
, NULL
,
1099 _("Failed to load image"), str
, NULL
);
1105 buddy
= purple_blist_find_buddy(data
->account
, data
->who
);
1107 purple_debug_info("custom-icon", "You can only set custom icons for people on your buddylist.\n");
1110 contact
= purple_buddy_get_contact(buddy
);
1111 purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode
*)contact
, data
->filename
);
1113 case DND_FILE_TRANSFER
:
1114 purple_serv_send_file(purple_account_get_connection(data
->account
), data
->who
, data
->filename
);
1117 conv
= PURPLE_CONVERSATION(purple_im_conversation_new(data
->account
, data
->who
));
1119 if (!g_file_get_contents(data
->filename
, &filedata
, &size
,
1123 str
= g_strdup_printf(_("The following error has occurred loading %s: %s"), data
->filename
, err
->message
);
1124 purple_notify_error(NULL
, NULL
,
1125 _("Failed to load image"), str
, NULL
);
1132 shortname
= strrchr(data
->filename
, G_DIR_SEPARATOR
);
1133 shortname
= shortname
? shortname
+ 1 : data
->filename
;
1134 img
= purple_image_new_from_data((guint8
*)filedata
, size
);
1135 purple_image_set_friendly_filename(img
, shortname
);
1137 # warning fix this when talkatu has a way to programmatically insert an image
1138 // pidgin_webview_insert_image(PIDGIN_WEBVIEW(gtkconv->entry), img);
1139 g_object_unref(img
);
1143 g_free(data
->filename
);
1148 static void dnd_image_cancel_callback(_DndData
*data
, int choice
)
1150 g_free(data
->filename
);
1155 static void dnd_set_icon_ok_cb(_DndData
*data
)
1157 dnd_image_ok_callback(data
, DND_BUDDY_ICON
);
1160 static void dnd_set_icon_cancel_cb(_DndData
*data
)
1162 g_free(data
->filename
);
1168 pidgin_dnd_file_send_image(PurpleAccount
*account
, const gchar
*who
,
1169 const gchar
*filename
)
1171 PurpleConnection
*gc
= purple_account_get_connection(account
);
1172 PurpleProtocol
*protocol
= NULL
;
1173 _DndData
*data
= g_malloc(sizeof(_DndData
));
1174 gboolean ft
= FALSE
, im
= FALSE
;
1176 data
->who
= g_strdup(who
);
1177 data
->filename
= g_strdup(filename
);
1178 data
->account
= account
;
1181 protocol
= purple_connection_get_protocol(gc
);
1183 if (!(purple_connection_get_flags(gc
) & PURPLE_CONNECTION_FLAG_NO_IMAGES
))
1186 if (protocol
&& PURPLE_IS_PROTOCOL_XFER(protocol
)) {
1187 PurpleProtocolXferInterface
*iface
=
1188 PURPLE_PROTOCOL_XFER_GET_IFACE(protocol
);
1190 if(iface
->can_receive
) {
1191 ft
= purple_protocol_xfer_can_receive(
1192 PURPLE_PROTOCOL_XFER(protocol
),
1195 ft
= (iface
->send_file
) ? TRUE
: FALSE
;
1200 purple_request_choice(NULL
, NULL
,
1201 _("You have dragged an image"),
1202 _("You can send this image as a file "
1203 "transfer, embed it into this message, "
1204 "or use it as the buddy icon for this user."),
1205 (gpointer
)DND_FILE_TRANSFER
, _("OK"),
1206 (GCallback
)dnd_image_ok_callback
, _("Cancel"),
1207 (GCallback
)dnd_image_cancel_callback
,
1208 purple_request_cpar_from_account(account
), data
,
1209 _("Set as buddy icon"), DND_BUDDY_ICON
,
1210 _("Send image file"), DND_FILE_TRANSFER
,
1211 _("Insert in message"), DND_IM_IMAGE
,
1213 } else if (!(im
|| ft
)) {
1214 purple_request_yes_no(NULL
, NULL
, _("You have dragged an image"),
1215 _("Would you like to set it as the buddy icon for this user?"),
1216 PURPLE_DEFAULT_ACTION_NONE
,
1217 purple_request_cpar_from_account(account
),
1218 data
, (GCallback
)dnd_set_icon_ok_cb
, (GCallback
)dnd_set_icon_cancel_cb
);
1220 purple_request_choice(NULL
, NULL
,
1221 _("You have dragged an image"),
1222 (ft
? _("You can send this image as a file transfer, or use it as the buddy icon for this user.") :
1223 _("You can insert this image into this message, or use it as the buddy icon for this user")),
1224 GINT_TO_POINTER(ft
? DND_FILE_TRANSFER
: DND_IM_IMAGE
),
1225 _("OK"), (GCallback
)dnd_image_ok_callback
,
1226 _("Cancel"), (GCallback
)dnd_image_cancel_callback
,
1227 purple_request_cpar_from_account(account
),
1229 _("Set as buddy icon"), DND_BUDDY_ICON
,
1230 (ft
? _("Send image file") : _("Insert in message")), (ft
? DND_FILE_TRANSFER
: DND_IM_IMAGE
),
1238 pidgin_dnd_file_send_desktop(PurpleAccount
*account
, const gchar
*who
,
1239 const gchar
*filename
)
1244 GKeyFile
*desktop_file
;
1245 PurpleConversation
*conv
;
1246 PidginConversation
*gtkconv
;
1247 GError
*error
= NULL
;
1249 desktop_file
= g_key_file_new();
1251 if (!g_key_file_load_from_file(desktop_file
, filename
, G_KEY_FILE_NONE
, &error
)) {
1253 purple_debug_warning("D&D", "Failed to load %s: %s\n",
1254 filename
, error
->message
);
1255 g_error_free(error
);
1260 name
= g_key_file_get_string(desktop_file
, G_KEY_FILE_DESKTOP_GROUP
,
1261 G_KEY_FILE_DESKTOP_KEY_NAME
, &error
);
1263 purple_debug_warning("D&D", "Failed to read the Name from a desktop file: %s\n",
1265 g_error_free(error
);
1269 type
= g_key_file_get_string(desktop_file
, G_KEY_FILE_DESKTOP_GROUP
,
1270 G_KEY_FILE_DESKTOP_KEY_TYPE
, &error
);
1272 purple_debug_warning("D&D", "Failed to read the Type from a desktop file: %s\n",
1274 g_error_free(error
);
1278 url
= g_key_file_get_string(desktop_file
, G_KEY_FILE_DESKTOP_GROUP
,
1279 G_KEY_FILE_DESKTOP_KEY_URL
, &error
);
1281 purple_debug_warning("D&D", "Failed to read the Type from a desktop file: %s\n",
1283 g_error_free(error
);
1288 /* If any of this is null, do nothing. */
1289 if (!name
|| !type
|| url
) {
1297 /* I don't know if we really want to do anything here. Most of
1298 * the desktop item types are crap like "MIME Type" (I have no
1299 * clue how that would be a desktop item) and "Comment"...
1300 * nothing we can really send. The only logical one is
1301 * "Application," but do we really want to send a binary and
1302 * nothing else? Probably not. I'll just give an error and
1304 /* The original patch sent the icon used by the launcher. That's probably wrong */
1305 if (purple_strequal(type
, "Link")) {
1306 purple_notify_error(NULL
, NULL
, _("Cannot send launcher"),
1307 _("You dragged a desktop launcher. Most "
1308 "likely you wanted to send the target "
1309 "of this launcher instead of this "
1310 "launcher itself."), NULL
);
1313 GtkTextBuffer
*buffer
= NULL
;
1314 GtkTextMark
*mark
= NULL
;
1317 conv
= PURPLE_CONVERSATION(purple_im_conversation_new(account
, who
));
1318 gtkconv
= PIDGIN_CONVERSATION(conv
);
1320 buffer
= talkatu_editor_get_buffer(TALKATU_EDITOR(gtkconv
->editor
));
1321 mark
= gtk_text_buffer_get_insert(buffer
);
1323 gtk_text_buffer_get_iter_at_mark(buffer
, &iter
, mark
);
1325 talkatu_buffer_insert_link(TALKATU_BUFFER(buffer
), &iter
, name
, url
);
1335 pidgin_dnd_file_manage(GtkSelectionData
*sd
, PurpleAccount
*account
, const char *who
)
1338 GList
*files
= purple_uri_list_extract_filenames((const gchar
*) gtk_selection_data_get_data(sd
));
1339 PurpleConnection
*gc
= purple_account_get_connection(account
);
1340 gchar
*filename
= NULL
;
1341 gchar
*basename
= NULL
;
1343 g_return_if_fail(account
!= NULL
);
1344 g_return_if_fail(who
!= NULL
);
1346 for ( ; files
; files
= g_list_delete_link(files
, files
)) {
1350 filename
= files
->data
;
1351 basename
= g_path_get_basename(filename
);
1353 /* XXX - Make ft API support creating a transfer with more than one file */
1354 if (!g_file_test(filename
, G_FILE_TEST_EXISTS
)) {
1358 /* XXX - make ft api suupport sending a directory */
1359 /* Are we dealing with a directory? */
1360 if (g_file_test(filename
, G_FILE_TEST_IS_DIR
)) {
1363 str
= g_strdup_printf(_("Cannot send folder %s."), basename
);
1364 str2
= g_strdup_printf(_("%s cannot transfer a folder. You will need to send the files within individually."), PIDGIN_NAME
);
1366 purple_notify_error(NULL
, NULL
, str
, str2
,
1367 purple_request_cpar_from_connection(gc
));
1374 /* Are we dealing with an image? */
1375 pb
= pidgin_pixbuf_new_from_file(filename
);
1377 pidgin_dnd_file_send_image(account
, who
, filename
);
1379 g_object_unref(G_OBJECT(pb
));
1385 /* Are we trying to send a .desktop file? */
1386 else if (purple_str_has_suffix(basename
, ".desktop")) {
1387 pidgin_dnd_file_send_desktop(account
, who
, filename
);
1393 /* Everything is fine, let's send */
1394 purple_serv_send_file(gc
, who
, filename
);
1401 void pidgin_buddy_icon_get_scale_size(GdkPixbuf
*buf
, PurpleBuddyIconSpec
*spec
, PurpleBuddyIconScaleFlags rules
, int *width
, int *height
)
1403 *width
= gdk_pixbuf_get_width(buf
);
1404 *height
= gdk_pixbuf_get_height(buf
);
1406 if ((spec
== NULL
) || !(spec
->scale_rules
& rules
))
1409 purple_buddy_icon_spec_get_scaled_size(spec
, width
, height
);
1411 /* and now for some arbitrary sanity checks */
1418 GdkPixbuf
* pidgin_create_status_icon(PurpleStatusPrimitive prim
, GtkWidget
*w
, const char *size
)
1420 GtkIconSize icon_size
= gtk_icon_size_from_name(size
);
1421 GdkPixbuf
*pixbuf
= NULL
;
1422 const char *stock
= pidgin_stock_id_from_status_primitive(prim
);
1424 pixbuf
= gtk_widget_render_icon (w
, stock
? stock
: PIDGIN_STOCK_STATUS_AVAILABLE
,
1425 icon_size
, "GtkWidget");
1430 stock_id_from_status_primitive_idle(PurpleStatusPrimitive prim
, gboolean idle
)
1432 const char *stock
= NULL
;
1434 case PURPLE_STATUS_UNSET
:
1437 case PURPLE_STATUS_UNAVAILABLE
:
1438 stock
= idle
? PIDGIN_STOCK_STATUS_BUSY_I
: PIDGIN_STOCK_STATUS_BUSY
;
1440 case PURPLE_STATUS_AWAY
:
1441 stock
= idle
? PIDGIN_STOCK_STATUS_AWAY_I
: PIDGIN_STOCK_STATUS_AWAY
;
1443 case PURPLE_STATUS_EXTENDED_AWAY
:
1444 stock
= idle
? PIDGIN_STOCK_STATUS_XA_I
: PIDGIN_STOCK_STATUS_XA
;
1446 case PURPLE_STATUS_INVISIBLE
:
1447 stock
= PIDGIN_STOCK_STATUS_INVISIBLE
;
1449 case PURPLE_STATUS_OFFLINE
:
1450 stock
= idle
? PIDGIN_STOCK_STATUS_OFFLINE_I
: PIDGIN_STOCK_STATUS_OFFLINE
;
1453 stock
= idle
? PIDGIN_STOCK_STATUS_AVAILABLE_I
: PIDGIN_STOCK_STATUS_AVAILABLE
;
1460 pidgin_stock_id_from_status_primitive(PurpleStatusPrimitive prim
)
1462 return stock_id_from_status_primitive_idle(prim
, FALSE
);
1466 pidgin_stock_id_from_presence(PurplePresence
*presence
)
1468 PurpleStatus
*status
;
1469 PurpleStatusType
*type
;
1470 PurpleStatusPrimitive prim
;
1473 g_return_val_if_fail(presence
, NULL
);
1475 status
= purple_presence_get_active_status(presence
);
1476 type
= purple_status_get_status_type(status
);
1477 prim
= purple_status_type_get_primitive(type
);
1479 idle
= purple_presence_is_idle(presence
);
1481 return stock_id_from_status_primitive_idle(prim
, idle
);
1485 pidgin_create_protocol_icon(PurpleAccount
*account
, PidginProtocolIconSize size
)
1487 PurpleProtocol
*protocol
;
1489 g_return_val_if_fail(account
!= NULL
, NULL
);
1491 protocol
= purple_protocols_find(purple_account_get_protocol_id(account
));
1492 if (protocol
== NULL
)
1494 return pidgin_create_icon_from_protocol(protocol
, size
, account
);
1498 menu_action_cb(GtkMenuItem
*item
, gpointer object
)
1501 void (*callback
)(gpointer
, gpointer
);
1503 callback
= g_object_get_data(G_OBJECT(item
), "purplecallback");
1504 data
= g_object_get_data(G_OBJECT(item
), "purplecallbackdata");
1507 callback(object
, data
);
1511 pidgin_append_menu_action(GtkWidget
*menu
, PurpleActionMenu
*act
,
1514 GtkWidget
*menuitem
;
1518 return pidgin_separator(menu
);
1521 menuitem
= gtk_menu_item_new_with_mnemonic(
1522 purple_action_menu_get_label(act
));
1524 list
= purple_action_menu_get_children(act
);
1527 PurpleCallback callback
;
1529 callback
= purple_action_menu_get_callback(act
);
1531 if (callback
!= NULL
) {
1532 g_object_set_data(G_OBJECT(menuitem
),
1535 g_object_set_data(G_OBJECT(menuitem
),
1536 "purplecallbackdata",
1537 purple_action_menu_get_data(act
));
1538 g_signal_connect(G_OBJECT(menuitem
), "activate",
1539 G_CALLBACK(menu_action_cb
),
1542 gtk_widget_set_sensitive(menuitem
, FALSE
);
1545 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
1548 GtkWidget
*submenu
= NULL
;
1549 GtkAccelGroup
*group
;
1551 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
1553 submenu
= gtk_menu_new();
1554 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem
), submenu
);
1556 group
= gtk_menu_get_accel_group(GTK_MENU(menu
));
1558 char *path
= g_strdup_printf("%s/%s",
1559 gtk_menu_item_get_accel_path(GTK_MENU_ITEM(menuitem
)),
1560 purple_action_menu_get_label(act
));
1561 gtk_menu_set_accel_path(GTK_MENU(submenu
), path
);
1563 gtk_menu_set_accel_group(GTK_MENU(submenu
), group
);
1566 for (l
= list
; l
; l
= l
->next
) {
1567 PurpleActionMenu
*act
= (PurpleActionMenu
*)l
->data
;
1569 pidgin_append_menu_action(submenu
, act
, object
);
1572 purple_action_menu_set_children(act
, NULL
);
1574 purple_action_menu_free(act
);
1578 static gboolean
buddyname_completion_match_func(GtkEntryCompletion
*completion
,
1579 const gchar
*key
, GtkTreeIter
*iter
, gpointer user_data
)
1581 GtkTreeModel
*model
;
1586 model
= gtk_entry_completion_get_model(completion
);
1589 gtk_tree_model_get_value(model
, iter
, COMPLETION_NORMALIZED_COLUMN
, &val1
);
1590 tmp
= g_value_get_string(&val1
);
1591 if (tmp
!= NULL
&& purple_str_has_prefix(tmp
, key
))
1593 g_value_unset(&val1
);
1596 g_value_unset(&val1
);
1599 gtk_tree_model_get_value(model
, iter
, COMPLETION_COMPARISON_COLUMN
, &val2
);
1600 tmp
= g_value_get_string(&val2
);
1601 if (tmp
!= NULL
&& purple_str_has_prefix(tmp
, key
))
1603 g_value_unset(&val2
);
1606 g_value_unset(&val2
);
1611 static gboolean
buddyname_completion_match_selected_cb(GtkEntryCompletion
*completion
,
1612 GtkTreeModel
*model
, GtkTreeIter
*iter
, PidginCompletionData
*data
)
1615 GtkWidget
*optmenu
= data
->accountopt
;
1616 PurpleAccount
*account
;
1619 gtk_tree_model_get_value(model
, iter
, COMPLETION_BUDDY_COLUMN
, &val
);
1620 gtk_entry_set_text(GTK_ENTRY(data
->entry
), g_value_get_string(&val
));
1621 g_value_unset(&val
);
1623 gtk_tree_model_get_value(model
, iter
, COMPLETION_ACCOUNT_COLUMN
, &val
);
1624 account
= g_value_get_pointer(&val
);
1625 g_value_unset(&val
);
1627 if (account
== NULL
)
1630 if (optmenu
!= NULL
)
1631 aop_option_menu_select_by_data(optmenu
, account
);
1637 add_buddyname_autocomplete_entry(GtkListStore
*store
, const char *buddy_alias
, const char *contact_alias
,
1638 const PurpleAccount
*account
, const char *buddyname
)
1641 gboolean completion_added
= FALSE
;
1642 gchar
*normalized_buddyname
;
1645 tmp
= g_utf8_normalize(buddyname
, -1, G_NORMALIZE_DEFAULT
);
1646 normalized_buddyname
= g_utf8_casefold(tmp
, -1);
1649 /* There's no sense listing things like: 'xxx "xxx"'
1650 when the name and buddy alias match. */
1651 if (buddy_alias
&& !purple_strequal(buddy_alias
, buddyname
)) {
1652 char *completion_entry
= g_strdup_printf("%s \"%s\"", buddyname
, buddy_alias
);
1653 char *tmp2
= g_utf8_normalize(buddy_alias
, -1, G_NORMALIZE_DEFAULT
);
1655 tmp
= g_utf8_casefold(tmp2
, -1);
1658 gtk_list_store_append(store
, &iter
);
1659 gtk_list_store_set(store
, &iter
,
1660 COMPLETION_DISPLAYED_COLUMN
, completion_entry
,
1661 COMPLETION_BUDDY_COLUMN
, buddyname
,
1662 COMPLETION_NORMALIZED_COLUMN
, normalized_buddyname
,
1663 COMPLETION_COMPARISON_COLUMN
, tmp
,
1664 COMPLETION_ACCOUNT_COLUMN
, account
,
1666 g_free(completion_entry
);
1668 completion_added
= TRUE
;
1671 /* There's no sense listing things like: 'xxx "xxx"'
1672 when the name and contact alias match. */
1673 if (contact_alias
&& !purple_strequal(contact_alias
, buddyname
)) {
1674 /* We don't want duplicates when the contact and buddy alias match. */
1675 if (!purple_strequal(contact_alias
, buddy_alias
)) {
1676 char *completion_entry
= g_strdup_printf("%s \"%s\"",
1677 buddyname
, contact_alias
);
1678 char *tmp2
= g_utf8_normalize(contact_alias
, -1, G_NORMALIZE_DEFAULT
);
1680 tmp
= g_utf8_casefold(tmp2
, -1);
1683 gtk_list_store_append(store
, &iter
);
1684 gtk_list_store_set(store
, &iter
,
1685 COMPLETION_DISPLAYED_COLUMN
, completion_entry
,
1686 COMPLETION_BUDDY_COLUMN
, buddyname
,
1687 COMPLETION_NORMALIZED_COLUMN
, normalized_buddyname
,
1688 COMPLETION_COMPARISON_COLUMN
, tmp
,
1689 COMPLETION_ACCOUNT_COLUMN
, account
,
1691 g_free(completion_entry
);
1693 completion_added
= TRUE
;
1697 if (completion_added
== FALSE
) {
1698 /* Add the buddy's name. */
1699 gtk_list_store_append(store
, &iter
);
1700 gtk_list_store_set(store
, &iter
,
1701 COMPLETION_DISPLAYED_COLUMN
, buddyname
,
1702 COMPLETION_BUDDY_COLUMN
, buddyname
,
1703 COMPLETION_NORMALIZED_COLUMN
, normalized_buddyname
,
1704 COMPLETION_COMPARISON_COLUMN
, NULL
,
1705 COMPLETION_ACCOUNT_COLUMN
, account
,
1709 g_free(normalized_buddyname
);
1712 static void get_log_set_name(PurpleLogSet
*set
, gpointer value
, PidginCompletionData
*data
)
1714 PidginFilterBuddyCompletionEntryFunc filter_func
= data
->filter_func
;
1715 gpointer user_data
= data
->filter_func_user_data
;
1717 /* 1. Don't show buddies because we will have gotten them already.
1718 * 2. The boxes that use this autocomplete code handle only IMs. */
1719 if (!set
->buddy
&& set
->type
== PURPLE_LOG_IM
) {
1720 PidginBuddyCompletionEntry entry
;
1721 entry
.is_buddy
= FALSE
;
1722 entry
.entry
.logged_buddy
= set
;
1724 if (filter_func(&entry
, user_data
)) {
1725 add_buddyname_autocomplete_entry(data
->store
,
1726 NULL
, NULL
, set
->account
, set
->name
);
1732 add_completion_list(PidginCompletionData
*data
)
1734 PurpleBlistNode
*gnode
, *cnode
, *bnode
;
1735 PidginFilterBuddyCompletionEntryFunc filter_func
= data
->filter_func
;
1736 gpointer user_data
= data
->filter_func_user_data
;
1740 gtk_list_store_clear(data
->store
);
1742 for (gnode
= purple_blist_get_default_root(); gnode
!= NULL
;
1743 gnode
= gnode
->next
) {
1744 if (!PURPLE_IS_GROUP(gnode
))
1747 for (cnode
= gnode
->child
; cnode
!= NULL
; cnode
= cnode
->next
)
1749 if (!PURPLE_IS_CONTACT(cnode
))
1752 g_object_get(cnode
, "alias", &alias
, NULL
);
1754 for (bnode
= cnode
->child
; bnode
!= NULL
; bnode
= bnode
->next
)
1756 PidginBuddyCompletionEntry entry
;
1757 entry
.is_buddy
= TRUE
;
1758 entry
.entry
.buddy
= (PurpleBuddy
*) bnode
;
1760 if (filter_func(&entry
, user_data
)) {
1761 add_buddyname_autocomplete_entry(data
->store
,
1763 purple_buddy_get_contact_alias(entry
.entry
.buddy
),
1764 purple_buddy_get_account(entry
.entry
.buddy
),
1765 purple_buddy_get_name(entry
.entry
.buddy
)
1774 sets
= purple_log_get_log_sets();
1775 g_hash_table_foreach(sets
, (GHFunc
)get_log_set_name
, data
);
1776 g_hash_table_destroy(sets
);
1781 buddyname_autocomplete_destroyed_cb(GtkWidget
*widget
, gpointer data
)
1784 purple_signals_disconnect_by_handle(widget
);
1788 repopulate_autocomplete(gpointer something
, gpointer data
)
1790 add_completion_list(data
);
1794 pidgin_setup_screenname_autocomplete(
1795 GtkWidget
*entry
, GtkWidget
*chooser
,
1796 PidginFilterBuddyCompletionEntryFunc filter_func
, gpointer user_data
)
1798 PidginCompletionData
*data
;
1801 * Store the displayed completion value, the buddy name, the UTF-8
1802 * normalized & casefolded buddy name, the UTF-8 normalized &
1803 * casefolded value for comparison, and the account.
1805 GtkListStore
*store
;
1807 GtkEntryCompletion
*completion
;
1809 data
= g_new0(PidginCompletionData
, 1);
1810 store
= gtk_list_store_new(COMPLETION_COLUMN_COUNT
, G_TYPE_STRING
,
1811 G_TYPE_STRING
, G_TYPE_STRING
, G_TYPE_STRING
,
1814 data
->entry
= entry
;
1815 data
->accountopt
= chooser
;
1816 if (filter_func
== NULL
) {
1817 data
->filter_func
= pidgin_screenname_autocomplete_default_filter
;
1818 data
->filter_func_user_data
= NULL
;
1820 data
->filter_func
= filter_func
;
1821 data
->filter_func_user_data
= user_data
;
1823 data
->store
= store
;
1825 add_completion_list(data
);
1827 /* Sort the completion list by buddy name */
1828 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store
),
1829 COMPLETION_BUDDY_COLUMN
,
1830 GTK_SORT_ASCENDING
);
1832 completion
= gtk_entry_completion_new();
1833 gtk_entry_completion_set_match_func(completion
, buddyname_completion_match_func
, NULL
, NULL
);
1835 g_signal_connect(G_OBJECT(completion
), "match-selected",
1836 G_CALLBACK(buddyname_completion_match_selected_cb
), data
);
1838 gtk_entry_set_completion(GTK_ENTRY(entry
), completion
);
1839 g_object_unref(completion
);
1841 gtk_entry_completion_set_model(completion
, GTK_TREE_MODEL(store
));
1842 g_object_unref(store
);
1844 gtk_entry_completion_set_text_column(completion
, COMPLETION_DISPLAYED_COLUMN
);
1846 purple_signal_connect(purple_connections_get_handle(), "signed-on", entry
,
1847 PURPLE_CALLBACK(repopulate_autocomplete
), data
);
1848 purple_signal_connect(purple_connections_get_handle(), "signed-off", entry
,
1849 PURPLE_CALLBACK(repopulate_autocomplete
), data
);
1851 purple_signal_connect(purple_accounts_get_handle(), "account-added", entry
,
1852 PURPLE_CALLBACK(repopulate_autocomplete
), data
);
1853 purple_signal_connect(purple_accounts_get_handle(), "account-removed", entry
,
1854 PURPLE_CALLBACK(repopulate_autocomplete
), data
);
1856 g_signal_connect(G_OBJECT(entry
), "destroy", G_CALLBACK(buddyname_autocomplete_destroyed_cb
), data
);
1860 pidgin_screenname_autocomplete_default_filter(const PidginBuddyCompletionEntry
*completion_entry
, gpointer all_accounts
) {
1861 gboolean all
= GPOINTER_TO_INT(all_accounts
);
1863 if (completion_entry
->is_buddy
) {
1864 return all
|| purple_account_is_connected(purple_buddy_get_account(completion_entry
->entry
.buddy
));
1866 return all
|| (completion_entry
->entry
.logged_buddy
->account
!= NULL
&& purple_account_is_connected(completion_entry
->entry
.logged_buddy
->account
));
1870 void pidgin_set_cursor(GtkWidget
*widget
, GdkCursorType cursor_type
)
1872 GdkDisplay
*display
;
1875 g_return_if_fail(widget
!= NULL
);
1876 if (gtk_widget_get_window(widget
) == NULL
)
1879 display
= gtk_widget_get_display(widget
);
1880 cursor
= gdk_cursor_new_for_display(display
, cursor_type
);
1881 gdk_window_set_cursor(gtk_widget_get_window(widget
), cursor
);
1883 g_object_unref(cursor
);
1885 gdk_display_flush(gdk_window_get_display(gtk_widget_get_window(widget
)));
1888 void pidgin_clear_cursor(GtkWidget
*widget
)
1890 g_return_if_fail(widget
!= NULL
);
1891 if (gtk_widget_get_window(widget
) == NULL
)
1894 gdk_window_set_cursor(gtk_widget_get_window(widget
), NULL
);
1898 icon_filesel_choose_cb(GtkWidget
*widget
, gint response
, struct _icon_chooser
*dialog
)
1900 char *filename
, *current_folder
;
1902 if (response
!= GTK_RESPONSE_ACCEPT
) {
1903 if (response
== GTK_RESPONSE_CANCEL
) {
1904 gtk_widget_destroy(dialog
->icon_filesel
);
1906 dialog
->icon_filesel
= NULL
;
1907 if (dialog
->callback
)
1908 dialog
->callback(NULL
, dialog
->data
);
1913 filename
= gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog
->icon_filesel
));
1914 current_folder
= gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(dialog
->icon_filesel
));
1915 if (current_folder
!= NULL
) {
1916 purple_prefs_set_path(PIDGIN_PREFS_ROOT
"/filelocations/last_icon_folder", current_folder
);
1917 g_free(current_folder
);
1921 if (dialog
->callback
)
1922 dialog
->callback(filename
, dialog
->data
);
1923 gtk_widget_destroy(dialog
->icon_filesel
);
1930 icon_preview_change_cb(GtkFileChooser
*widget
, struct _icon_chooser
*dialog
)
1934 char *basename
, *markup
, *size
;
1938 filename
= gtk_file_chooser_get_preview_filename(
1939 GTK_FILE_CHOOSER(dialog
->icon_filesel
));
1941 if (!filename
|| g_stat(filename
, &st
) || !(pixbuf
= pidgin_pixbuf_new_from_file_at_size(filename
, 128, 128)))
1943 gtk_image_set_from_pixbuf(GTK_IMAGE(dialog
->icon_preview
), NULL
);
1944 gtk_label_set_markup(GTK_LABEL(dialog
->icon_text
), "");
1949 gdk_pixbuf_get_file_info(filename
, &width
, &height
);
1950 basename
= g_path_get_basename(filename
);
1951 size
= g_format_size(st
.st_size
);
1952 markup
= g_strdup_printf(_("<b>File:</b> %s\n"
1953 "<b>File size:</b> %s\n"
1954 "<b>Image size:</b> %dx%d"),
1955 basename
, size
, width
, height
);
1957 gtk_image_set_from_pixbuf(GTK_IMAGE(dialog
->icon_preview
), pixbuf
);
1958 gtk_label_set_markup(GTK_LABEL(dialog
->icon_text
), markup
);
1960 g_object_unref(G_OBJECT(pixbuf
));
1968 GtkWidget
*pidgin_buddy_icon_chooser_new(GtkWindow
*parent
, void(*callback
)(const char *, gpointer
), gpointer data
) {
1969 struct _icon_chooser
*dialog
= g_new0(struct _icon_chooser
, 1);
1972 const char *current_folder
;
1974 dialog
->callback
= callback
;
1975 dialog
->data
= data
;
1977 current_folder
= purple_prefs_get_path(PIDGIN_PREFS_ROOT
"/filelocations/last_icon_folder");
1979 dialog
->icon_filesel
= gtk_file_chooser_dialog_new(_("Buddy Icon"),
1981 GTK_FILE_CHOOSER_ACTION_OPEN
,
1982 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
1983 GTK_STOCK_OPEN
, GTK_RESPONSE_ACCEPT
,
1985 gtk_dialog_set_default_response(GTK_DIALOG(dialog
->icon_filesel
), GTK_RESPONSE_ACCEPT
);
1986 if ((current_folder
!= NULL
) && (*current_folder
!= '\0'))
1987 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog
->icon_filesel
),
1990 dialog
->icon_preview
= gtk_image_new();
1991 dialog
->icon_text
= gtk_label_new(NULL
);
1993 vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, PIDGIN_HIG_BOX_SPACE
);
1994 gtk_widget_set_size_request(GTK_WIDGET(vbox
), -1, 50);
1995 gtk_box_pack_start(GTK_BOX(vbox
), GTK_WIDGET(dialog
->icon_preview
), TRUE
, FALSE
, 0);
1996 gtk_box_pack_end(GTK_BOX(vbox
), GTK_WIDGET(dialog
->icon_text
), FALSE
, FALSE
, 0);
1997 gtk_widget_show_all(vbox
);
1999 gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog
->icon_filesel
), vbox
);
2000 gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(dialog
->icon_filesel
), TRUE
);
2001 gtk_file_chooser_set_use_preview_label(GTK_FILE_CHOOSER(dialog
->icon_filesel
), FALSE
);
2003 g_signal_connect(G_OBJECT(dialog
->icon_filesel
), "update-preview",
2004 G_CALLBACK(icon_preview_change_cb
), dialog
);
2005 g_signal_connect(G_OBJECT(dialog
->icon_filesel
), "response",
2006 G_CALLBACK(icon_filesel_choose_cb
), dialog
);
2007 icon_preview_change_cb(NULL
, dialog
);
2010 g_signal_connect(G_OBJECT(dialog
->icon_filesel
), "show",
2011 G_CALLBACK(winpidgin_ensure_onscreen
), dialog
->icon_filesel
);
2014 return dialog
->icon_filesel
;
2020 * Returns: %TRUE if any string from array @a exists in array @b.
2023 str_array_match(char **a
, char **b
)
2029 for (i
= 0; a
[i
] != NULL
; i
++)
2030 for (j
= 0; b
[j
] != NULL
; j
++)
2031 if (!g_ascii_strcasecmp(a
[i
], b
[j
]))
2037 pidgin_convert_buddy_icon(PurpleProtocol
*protocol
, const char *path
, size_t *len
)
2039 PurpleBuddyIconSpec
*spec
;
2040 int orig_width
, orig_height
, new_width
, new_height
;
2041 GdkPixbufFormat
*format
;
2042 char **pixbuf_formats
;
2043 char **protocol_formats
;
2044 GError
*error
= NULL
;
2047 GdkPixbuf
*pixbuf
, *original
;
2052 spec
= purple_protocol_get_icon_spec(protocol
);
2053 g_return_val_if_fail(spec
->format
!= NULL
, NULL
);
2055 format
= gdk_pixbuf_get_file_info(path
, &orig_width
, &orig_height
);
2056 if (format
== NULL
) {
2057 purple_debug_warning("buddyicon", "Could not get file info of %s\n", path
);
2061 pixbuf_formats
= gdk_pixbuf_format_get_extensions(format
);
2062 protocol_formats
= g_strsplit(spec
->format
, ",", 0);
2064 if (str_array_match(pixbuf_formats
, protocol_formats
) && /* This is an acceptable format AND */
2065 (!(spec
->scale_rules
& PURPLE_ICON_SCALE_SEND
) || /* The protocol doesn't scale before it sends OR */
2066 (spec
->min_width
<= orig_width
&& spec
->max_width
>= orig_width
&&
2067 spec
->min_height
<= orig_height
&& spec
->max_height
>= orig_height
))) /* The icon is the correct size */
2069 g_strfreev(pixbuf_formats
);
2071 if (!g_file_get_contents(path
, &contents
, &length
, &error
)) {
2072 purple_debug_warning("buddyicon", "Could not get file contents "
2073 "of %s: %s\n", path
, error
->message
);
2074 g_strfreev(protocol_formats
);
2078 if (spec
->max_filesize
== 0 || length
< spec
->max_filesize
) {
2079 /* The supplied image fits the file size, dimensions and type
2080 constraints. Great! Return it without making any changes. */
2083 g_strfreev(protocol_formats
);
2087 /* The image was too big. Fall-through and try scaling it down. */
2090 g_strfreev(pixbuf_formats
);
2093 /* The original image wasn't compatible. Scale it or convert file type. */
2094 pixbuf
= gdk_pixbuf_new_from_file(path
, &error
);
2096 purple_debug_warning("buddyicon", "Could not open icon '%s' for "
2097 "conversion: %s\n", path
, error
->message
);
2098 g_error_free(error
);
2099 g_strfreev(protocol_formats
);
2102 original
= g_object_ref(pixbuf
);
2104 new_width
= orig_width
;
2105 new_height
= orig_height
;
2107 /* Make sure the image is the correct dimensions */
2108 if (spec
->scale_rules
& PURPLE_ICON_SCALE_SEND
&&
2109 (orig_width
< spec
->min_width
|| orig_width
> spec
->max_width
||
2110 orig_height
< spec
->min_height
|| orig_height
> spec
->max_height
))
2112 purple_buddy_icon_spec_get_scaled_size(spec
, &new_width
, &new_height
);
2114 g_object_unref(G_OBJECT(pixbuf
));
2115 pixbuf
= gdk_pixbuf_scale_simple(original
, new_width
, new_height
, GDK_INTERP_HYPER
);
2120 for (i
= 0; protocol_formats
[i
]; i
++) {
2123 const char *key
= NULL
;
2124 const char *value
= NULL
;
2127 purple_debug_info("buddyicon", "Converting buddy icon to %s\n", protocol_formats
[i
]);
2129 if (purple_strequal(protocol_formats
[i
], "png")) {
2130 key
= "compression";
2132 } else if (purple_strequal(protocol_formats
[i
], "jpeg")) {
2133 sprintf(tmp_buf
, "%u", quality
);
2138 if (!gdk_pixbuf_save_to_buffer(pixbuf
, &contents
, &length
,
2139 protocol_formats
[i
], &error
, key
, value
, NULL
))
2141 /* The NULL checking of error is necessary due to this bug:
2142 * http://bugzilla.gnome.org/show_bug.cgi?id=405539 */
2143 purple_debug_warning("buddyicon",
2144 "Could not convert to %s: %s\n", protocol_formats
[i
],
2145 (error
&& error
->message
) ? error
->message
: "Unknown error");
2146 g_error_free(error
);
2149 /* We couldn't convert to this image type. Try the next
2154 if (spec
->max_filesize
== 0 || length
<= spec
->max_filesize
) {
2155 /* We were able to save the image as this image type and
2156 have it be within the size constraints. Great! Return
2158 purple_debug_info("buddyicon", "Converted image from "
2159 "%dx%d to %dx%d, format=%s, quality=%u, "
2160 "filesize=%" G_GSIZE_FORMAT
"\n",
2161 orig_width
, orig_height
, new_width
, new_height
,
2162 protocol_formats
[i
], quality
, length
);
2165 g_strfreev(protocol_formats
);
2166 g_object_unref(G_OBJECT(pixbuf
));
2167 g_object_unref(G_OBJECT(original
));
2173 if (!purple_strequal(protocol_formats
[i
], "jpeg")) {
2174 /* File size was too big and we can't lower the quality,
2175 so skip to the next image type. */
2179 /* File size was too big, but we're dealing with jpeg so try
2180 lowering the quality. */
2182 } while (quality
>= 70);
2185 /* We couldn't save the image in any format that was below the max
2186 file size. Maybe we can reduce the image dimensions? */
2187 scale_factor
*= 0.8;
2188 new_width
= orig_width
* scale_factor
;
2189 new_height
= orig_height
* scale_factor
;
2190 g_object_unref(G_OBJECT(pixbuf
));
2191 pixbuf
= gdk_pixbuf_scale_simple(original
, new_width
, new_height
, GDK_INTERP_HYPER
);
2192 } while ((new_width
> 10 || new_height
> 10) && new_width
> spec
->min_width
&& new_height
> spec
->min_height
);
2193 g_strfreev(protocol_formats
);
2194 g_object_unref(G_OBJECT(pixbuf
));
2195 g_object_unref(G_OBJECT(original
));
2197 tmp
= g_strdup_printf(_("The file '%s' is too large for %s. Please try a smaller image.\n"),
2198 path
, purple_protocol_get_name(protocol
));
2199 purple_notify_error(NULL
, _("Icon Error"), _("Could not set icon"), tmp
, NULL
);
2205 void pidgin_set_urgent(GtkWindow
*window
, gboolean urgent
)
2208 winpidgin_window_flash(window
, urgent
);
2210 gtk_window_set_urgency_hint(window
, urgent
);
2215 pidgin_utils_get_handle(void)
2222 static void connection_signed_off_cb(PurpleConnection
*gc
)
2224 GSList
*list
, *l_next
;
2225 for (list
= minidialogs
; list
; list
= l_next
) {
2226 l_next
= list
->next
;
2227 if (g_object_get_data(G_OBJECT(list
->data
), "gc") == gc
) {
2228 gtk_widget_destroy(GTK_WIDGET(list
->data
));
2233 static void alert_killed_cb(GtkWidget
*widget
)
2235 minidialogs
= g_slist_remove(minidialogs
, widget
);
2239 old_mini_dialog_button_clicked_cb(PidginMiniDialog
*mini_dialog
,
2243 struct _old_button_clicked_cb_data
*data
= user_data
;
2244 data
->cb(data
->data
, button
);
2248 old_mini_dialog_destroy_cb(GtkWidget
*dialog
,
2251 while (cb_datas
!= NULL
)
2253 g_free(cb_datas
->data
);
2254 cb_datas
= g_list_delete_link(cb_datas
, cb_datas
);
2259 mini_dialog_init(PidginMiniDialog
*mini_dialog
, PurpleConnection
*gc
, void *user_data
, va_list args
)
2261 const char *button_text
;
2262 GList
*cb_datas
= NULL
;
2263 static gboolean first_call
= TRUE
;
2267 purple_signal_connect(purple_connections_get_handle(), "signed-off",
2268 pidgin_utils_get_handle(),
2269 PURPLE_CALLBACK(connection_signed_off_cb
), NULL
);
2272 g_object_set_data(G_OBJECT(mini_dialog
), "gc" ,gc
);
2273 g_signal_connect(G_OBJECT(mini_dialog
), "destroy",
2274 G_CALLBACK(alert_killed_cb
), NULL
);
2276 while ((button_text
= va_arg(args
, char*))) {
2277 struct _old_button_clicked_cb_data
*data
= NULL
;
2278 PidginMiniDialogCallback wrapper_cb
= NULL
;
2279 PidginUtilMiniDialogCallback callback
=
2280 va_arg(args
, PidginUtilMiniDialogCallback
);
2282 if (callback
!= NULL
) {
2283 data
= g_new0(struct _old_button_clicked_cb_data
, 1);
2284 data
->cb
= callback
;
2285 data
->data
= user_data
;
2286 wrapper_cb
= old_mini_dialog_button_clicked_cb
;
2288 pidgin_mini_dialog_add_button(mini_dialog
, button_text
,
2290 cb_datas
= g_list_append(cb_datas
, data
);
2293 g_signal_connect(G_OBJECT(mini_dialog
), "destroy",
2294 G_CALLBACK(old_mini_dialog_destroy_cb
), cb_datas
);
2297 #define INIT_AND_RETURN_MINI_DIALOG(mini_dialog) \
2299 va_start(args, user_data); \
2300 mini_dialog_init(mini_dialog, gc, user_data, args); \
2302 return GTK_WIDGET(mini_dialog);
2305 pidgin_make_mini_dialog(PurpleConnection
*gc
,
2306 const char *icon_name
,
2307 const char *primary
,
2308 const char *secondary
,
2312 PidginMiniDialog
*mini_dialog
= pidgin_mini_dialog_new(primary
, secondary
, icon_name
);
2313 INIT_AND_RETURN_MINI_DIALOG(mini_dialog
);
2317 pidgin_make_mini_dialog_with_custom_icon(PurpleConnection
*gc
,
2318 GdkPixbuf
*custom_icon
,
2319 const char *primary
,
2320 const char *secondary
,
2324 PidginMiniDialog
*mini_dialog
= pidgin_mini_dialog_new_with_custom_icon(primary
, secondary
, custom_icon
);
2325 INIT_AND_RETURN_MINI_DIALOG(mini_dialog
);
2329 * "This is so dead sexy."
2331 * "Best movie of the year."
2333 * This is the function that handles CTRL+F searching in the buddy list.
2334 * It finds the top-most buddy/group/chat/whatever containing the
2337 * It's somewhat ineffecient, because we strip all the HTML from the
2338 * "name" column of the buddy list (because the GtkTreeModel does not
2339 * contain the screen name in a non-markedup format). But the alternative
2340 * is to add an extra column to the GtkTreeModel. And this function is
2341 * used rarely, so it shouldn't matter TOO much.
2343 gboolean
pidgin_tree_view_search_equal_func(GtkTreeModel
*model
, gint column
,
2344 const gchar
*key
, GtkTreeIter
*iter
, gpointer data
)
2346 gchar
*enteredstring
;
2354 PangoLogAttr
*log_attrs
;
2357 if (g_ascii_strcasecmp(key
, "Global Thermonuclear War") == 0)
2359 purple_notify_info(NULL
, "WOPR", "Wouldn't you prefer a nice "
2360 "game of chess?", NULL
, NULL
);
2364 gtk_tree_model_get(model
, iter
, column
, &withmarkup
, -1);
2365 if (withmarkup
== NULL
) /* This is probably a separator */
2368 tmp
= g_utf8_normalize(key
, -1, G_NORMALIZE_DEFAULT
);
2369 enteredstring
= g_utf8_casefold(tmp
, -1);
2372 nomarkup
= purple_markup_strip_html(withmarkup
);
2373 tmp
= g_utf8_normalize(nomarkup
, -1, G_NORMALIZE_DEFAULT
);
2375 normalized
= g_utf8_casefold(tmp
, -1);
2378 if (purple_str_has_prefix(normalized
, enteredstring
))
2381 g_free(enteredstring
);
2387 /* Use Pango to separate by words. */
2388 len
= g_utf8_strlen(normalized
, -1);
2389 log_attrs
= g_new(PangoLogAttr
, len
+ 1);
2391 pango_get_log_attrs(normalized
, strlen(normalized
), -1, NULL
, log_attrs
, len
+ 1);
2395 for (i
= 0; i
< (len
- 1) ; i
++)
2397 if (log_attrs
[i
].is_word_start
&&
2398 purple_str_has_prefix(word
, enteredstring
))
2403 word
= g_utf8_next_char(word
);
2407 /* The non-Pango version. */
2411 while (word
[0] != '\0')
2413 gunichar c
= g_utf8_get_char(word
);
2414 if (!g_unichar_isalnum(c
))
2416 word
= g_utf8_find_next_char(word
, NULL
);
2417 if (purple_str_has_prefix(word
, enteredstring
))
2424 word
= g_utf8_find_next_char(word
, NULL
);
2429 g_free(enteredstring
);
2435 const char *pidgin_get_dim_grey_string(GtkWidget
*widget
) {
2436 static char dim_grey_string
[8] = "";
2437 GtkStyleContext
*context
;
2443 context
= gtk_widget_get_style_context(widget
);
2447 gtk_style_context_get_color(context
, gtk_style_context_get_state(context
),
2449 gtk_style_context_get_background_color(context
,
2450 gtk_style_context_get_state(context
),
2452 snprintf(dim_grey_string
, sizeof(dim_grey_string
), "#%02x%02x%02x",
2453 (unsigned int)((fg
.red
+ bg
.red
) * 0.5 * 255),
2454 (unsigned int)((fg
.green
+ bg
.green
) * 0.5 * 255),
2455 (unsigned int)((fg
.blue
+ bg
.blue
) * 0.5 * 255));
2456 return dim_grey_string
;
2460 combo_box_changed_cb(GtkComboBoxText
*combo_box
, GtkEntry
*entry
)
2462 char *text
= gtk_combo_box_text_get_active_text(combo_box
);
2463 gtk_entry_set_text(entry
, text
? text
: "");
2468 entry_key_pressed_cb(GtkWidget
*entry
, GdkEventKey
*key
, GtkComboBoxText
*combo
)
2470 if (key
->keyval
== GDK_KEY_Down
|| key
->keyval
== GDK_KEY_Up
) {
2471 gtk_combo_box_popup(GTK_COMBO_BOX(combo
));
2478 pidgin_text_combo_box_entry_new(const char *default_item
, GList
*items
)
2480 GtkComboBoxText
*ret
= NULL
;
2481 GtkWidget
*the_entry
= NULL
;
2483 ret
= GTK_COMBO_BOX_TEXT(gtk_combo_box_text_new_with_entry());
2484 the_entry
= gtk_bin_get_child(GTK_BIN(ret
));
2487 gtk_entry_set_text(GTK_ENTRY(the_entry
), default_item
);
2489 for (; items
!= NULL
; items
= items
->next
) {
2490 char *text
= items
->data
;
2492 gtk_combo_box_text_append_text(ret
, text
);
2495 g_signal_connect(G_OBJECT(ret
), "changed", (GCallback
)combo_box_changed_cb
, the_entry
);
2496 g_signal_connect_after(G_OBJECT(the_entry
), "key-press-event", G_CALLBACK(entry_key_pressed_cb
), ret
);
2498 return GTK_WIDGET(ret
);
2501 const char *pidgin_text_combo_box_entry_get_text(GtkWidget
*widget
)
2503 return gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((widget
)))));
2506 void pidgin_text_combo_box_entry_set_text(GtkWidget
*widget
, const char *text
)
2508 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((widget
)))), (text
));
2512 pidgin_add_widget_to_vbox(GtkBox
*vbox
, const char *widget_label
, GtkSizeGroup
*sg
, GtkWidget
*widget
, gboolean expand
, GtkWidget
**p_label
)
2515 GtkWidget
*label
= NULL
;
2518 hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 5);
2519 gtk_widget_show(hbox
);
2520 gtk_box_pack_start(vbox
, hbox
, FALSE
, FALSE
, 0);
2522 label
= gtk_label_new_with_mnemonic(widget_label
);
2523 gtk_widget_show(label
);
2525 gtk_label_set_xalign(GTK_LABEL(label
), 0);
2526 gtk_size_group_add_widget(sg
, label
);
2528 gtk_box_pack_start(GTK_BOX(hbox
), label
, FALSE
, FALSE
, 0);
2530 hbox
= GTK_WIDGET(vbox
);
2533 gtk_widget_show(widget
);
2534 gtk_box_pack_start(GTK_BOX(hbox
), widget
, expand
, TRUE
, 0);
2536 gtk_label_set_mnemonic_widget(GTK_LABEL(label
), widget
);
2537 pidgin_set_accessible_label(widget
, GTK_LABEL(label
));
2545 gboolean
pidgin_auto_parent_window(GtkWidget
*widget
)
2548 /* This looks at the most recent window that received focus, and makes
2549 * that the parent window. */
2551 static GdkAtom _WindowTime
= GDK_NONE
;
2552 static GdkAtom _Cardinal
= GDK_NONE
;
2553 GList
*windows
= NULL
;
2554 GtkWidget
*parent
= NULL
;
2555 time_t window_time
= 0;
2557 windows
= gtk_window_list_toplevels();
2559 if (_WindowTime
== GDK_NONE
) {
2560 _WindowTime
= gdk_x11_xatom_to_atom(gdk_x11_get_xatom_by_name("_NET_WM_USER_TIME"));
2562 if (_Cardinal
== GDK_NONE
) {
2563 _Cardinal
= gdk_atom_intern("CARDINAL", FALSE
);
2567 GtkWidget
*window
= windows
->data
;
2568 guchar
*data
= NULL
;
2572 windows
= g_list_delete_link(windows
, windows
);
2574 if (window
== widget
||
2575 !gtk_widget_get_visible(window
))
2578 if (!gdk_property_get(window
->window
, _WindowTime
, _Cardinal
, 0, sizeof(time_t), FALSE
,
2579 NULL
, NULL
, &al
, &data
))
2581 value
= *(time_t *)data
;
2582 if (window_time
< value
) {
2583 window_time
= value
;
2589 g_list_free(windows
);
2591 if (!gtk_get_current_event() && gtk_window_has_toplevel_focus(GTK_WINDOW(parent
))) {
2592 /* The window is in focus, and the new window was not triggered by a keypress/click
2593 * event. So do not set it transient, to avoid focus stealing and all that.
2597 gtk_window_set_transient_for(GTK_WINDOW(widget
), GTK_WINDOW(parent
));
2603 /* This finds the currently active window and makes that the parent window. */
2604 GList
*windows
= NULL
;
2605 GtkWindow
*parent
= NULL
;
2606 GdkEvent
*event
= gtk_get_current_event();
2607 GdkWindow
*menu
= NULL
;
2608 gpointer parent_from
;
2609 PurpleNotifyType notify_type
;
2611 parent_from
= g_object_get_data(G_OBJECT(widget
), "pidgin-parent-from");
2612 if (purple_request_is_valid_ui_handle(parent_from
, NULL
)) {
2614 gtk_window_set_transient_for(GTK_WINDOW(widget
),
2615 gtk_window_get_transient_for(
2616 pidgin_request_get_dialog_window(parent_from
)));
2619 if (purple_notify_is_valid_ui_handle(parent_from
, ¬ify_type
) &&
2620 notify_type
== PURPLE_NOTIFY_MESSAGE
)
2622 gtk_window_set_transient_for(GTK_WINDOW(widget
),
2623 gtk_window_get_transient_for(GTK_WINDOW(parent_from
)));
2628 /* The window was not triggered by a user action. */
2631 /* We need to special case events from a popup menu. */
2632 if (event
->type
== GDK_BUTTON_RELEASE
) {
2633 /* XXX: Neither of the following works:
2634 menu = event->button.window;
2635 menu = gdk_window_get_parent(event->button.window);
2636 menu = gdk_window_get_toplevel(event->button.window);
2638 } else if (event
->type
== GDK_KEY_PRESS
)
2639 menu
= event
->key
.window
;
2641 windows
= gtk_window_list_toplevels();
2643 GtkWindow
*window
= GTK_WINDOW(windows
->data
);
2644 windows
= g_list_delete_link(windows
, windows
);
2646 if (GPOINTER_TO_INT(g_object_get_data(G_OBJECT(window
),
2647 "pidgin-window-is-closing")))
2649 parent
= gtk_window_get_transient_for(window
);
2653 if (GTK_WIDGET(window
) == widget
||
2654 !gtk_widget_get_visible(GTK_WIDGET(window
))) {
2658 if (gtk_window_has_toplevel_focus(window
) ||
2659 (menu
&& menu
== gtk_widget_get_window(GTK_WIDGET(window
)))) {
2665 g_list_free(windows
);
2667 gtk_window_set_transient_for(GTK_WINDOW(widget
), parent
);
2674 static GObject
*pidgin_pixbuf_from_data_helper(const guchar
*buf
, gsize count
, gboolean animated
)
2677 GdkPixbufLoader
*loader
;
2678 GError
*error
= NULL
;
2680 loader
= gdk_pixbuf_loader_new();
2682 if (!gdk_pixbuf_loader_write(loader
, buf
, count
, &error
) || error
) {
2683 purple_debug_warning("gtkutils", "gdk_pixbuf_loader_write() "
2684 "failed with size=%" G_GSIZE_FORMAT
": %s\n", count
,
2685 error
? error
->message
: "(no error message)");
2687 g_error_free(error
);
2688 g_object_unref(G_OBJECT(loader
));
2692 if (!gdk_pixbuf_loader_close(loader
, &error
) || error
) {
2693 purple_debug_warning("gtkutils", "gdk_pixbuf_loader_close() "
2694 "failed for image of size %" G_GSIZE_FORMAT
": %s\n", count
,
2695 error
? error
->message
: "(no error message)");
2697 g_error_free(error
);
2698 g_object_unref(G_OBJECT(loader
));
2703 pixbuf
= G_OBJECT(gdk_pixbuf_loader_get_animation(loader
));
2705 pixbuf
= G_OBJECT(gdk_pixbuf_loader_get_pixbuf(loader
));
2707 purple_debug_warning("gtkutils", "%s() returned NULL for image "
2708 "of size %" G_GSIZE_FORMAT
"\n",
2709 animated
? "gdk_pixbuf_loader_get_animation"
2710 : "gdk_pixbuf_loader_get_pixbuf", count
);
2711 g_object_unref(G_OBJECT(loader
));
2715 g_object_ref(pixbuf
);
2716 g_object_unref(G_OBJECT(loader
));
2721 GdkPixbuf
*pidgin_pixbuf_from_data(const guchar
*buf
, gsize count
)
2723 return GDK_PIXBUF(pidgin_pixbuf_from_data_helper(buf
, count
, FALSE
));
2726 GdkPixbufAnimation
*pidgin_pixbuf_anim_from_data(const guchar
*buf
, gsize count
)
2728 return GDK_PIXBUF_ANIMATION(pidgin_pixbuf_from_data_helper(buf
, count
, TRUE
));
2732 pidgin_pixbuf_from_image(PurpleImage
*image
)
2734 return pidgin_pixbuf_from_data(purple_image_get_data(image
),
2735 purple_image_get_data_size(image
));
2738 GdkPixbuf
*pidgin_pixbuf_new_from_file(const gchar
*filename
)
2741 GError
*error
= NULL
;
2743 g_return_val_if_fail(filename
!= NULL
, NULL
);
2744 g_return_val_if_fail(filename
[0] != '\0', NULL
);
2746 pixbuf
= gdk_pixbuf_new_from_file(filename
, &error
);
2747 if (!pixbuf
|| error
) {
2748 purple_debug_warning("gtkutils", "gdk_pixbuf_new_from_file() "
2749 "returned %s for file %s: %s\n",
2750 pixbuf
? "something" : "nothing",
2752 error
? error
->message
: "(no error message)");
2754 g_error_free(error
);
2756 g_object_unref(G_OBJECT(pixbuf
));
2763 GdkPixbuf
*pidgin_pixbuf_new_from_file_at_size(const char *filename
, int width
, int height
)
2766 GError
*error
= NULL
;
2768 g_return_val_if_fail(filename
!= NULL
, NULL
);
2769 g_return_val_if_fail(filename
[0] != '\0', NULL
);
2771 pixbuf
= gdk_pixbuf_new_from_file_at_size(filename
,
2772 width
, height
, &error
);
2773 if (!pixbuf
|| error
) {
2774 purple_debug_warning("gtkutils", "gdk_pixbuf_new_from_file_at_size() "
2775 "returned %s for file %s: %s\n",
2776 pixbuf
? "something" : "nothing",
2778 error
? error
->message
: "(no error message)");
2780 g_error_free(error
);
2782 g_object_unref(G_OBJECT(pixbuf
));
2789 GdkPixbuf
*pidgin_pixbuf_new_from_file_at_scale(const char *filename
, int width
, int height
, gboolean preserve_aspect_ratio
)
2792 GError
*error
= NULL
;
2794 g_return_val_if_fail(filename
!= NULL
, NULL
);
2795 g_return_val_if_fail(filename
[0] != '\0', NULL
);
2797 pixbuf
= gdk_pixbuf_new_from_file_at_scale(filename
,
2798 width
, height
, preserve_aspect_ratio
, &error
);
2799 if (!pixbuf
|| error
) {
2800 purple_debug_warning("gtkutils", "gdk_pixbuf_new_from_file_at_scale() "
2801 "returned %s for file %s: %s\n",
2802 pixbuf
? "something" : "nothing",
2804 error
? error
->message
: "(no error message)");
2806 g_error_free(error
);
2808 g_object_unref(G_OBJECT(pixbuf
));
2816 pidgin_pixbuf_scale_down(GdkPixbuf
*src
, guint max_width
, guint max_height
,
2817 GdkInterpType interp_type
, gboolean preserve_ratio
)
2822 g_return_val_if_fail(src
!= NULL
, NULL
);
2824 if (max_width
== 0 || max_height
== 0) {
2825 g_object_unref(src
);
2826 g_return_val_if_reached(NULL
);
2829 cur_w
= gdk_pixbuf_get_width(src
);
2830 cur_h
= gdk_pixbuf_get_height(src
);
2832 if (cur_w
<= max_width
&& cur_h
<= max_height
)
2835 /* cur_ratio = cur_w / cur_h
2836 * max_ratio = max_w / max_h
2839 if (!preserve_ratio
) {
2840 cur_w
= MIN(cur_w
, max_width
);
2841 cur_h
= MIN(cur_h
, max_height
);
2842 } else if ((guint64
)cur_w
* max_height
> (guint64
)max_width
* cur_h
) {
2843 /* cur_w / cur_h > max_width / max_height */
2844 cur_h
= (guint64
)max_width
* cur_h
/ cur_w
;
2847 cur_w
= (guint64
)max_height
* cur_w
/ cur_h
;
2856 dst
= gdk_pixbuf_scale_simple(src
, cur_w
, cur_h
, interp_type
);
2857 g_object_unref(src
);
2863 pidgin_make_scrollable(GtkWidget
*child
, GtkPolicyType hscrollbar_policy
, GtkPolicyType vscrollbar_policy
, GtkShadowType shadow_type
, int width
, int height
)
2865 GtkWidget
*sw
= gtk_scrolled_window_new(NULL
, NULL
);
2868 gtk_widget_show(sw
);
2869 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw
), hscrollbar_policy
, vscrollbar_policy
);
2870 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw
), shadow_type
);
2871 if (width
!= -1 || height
!= -1)
2872 gtk_widget_set_size_request(sw
, width
, height
);
2874 gtk_container_add(GTK_CONTAINER(sw
), child
);