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
27 #include "connection.h"
36 #include "pidginstock.h"
37 #include "theme-loader.h"
38 #include "theme-manager.h"
41 #include "gtkaccount.h"
43 #include "gtkcellrendererexpander.h"
45 #include "gtkdialogs.h"
47 #include "gtkmenutray.h"
48 #include "gtkpounce.h"
49 #include "gtkplugin.h"
51 #include "gtkprivacy.h"
52 #include "gtkroomlist.h"
53 #include "gtkstatusbox.h"
54 #include "gtkscrollbook.h"
55 #include "gtksmiley-manager.h"
57 #include "gtkblist-theme.h"
58 #include "gtkblist-theme-loader.h"
60 #include "pidgin/minidialog.h"
61 #include "pidgin/pidginabout.h"
62 #include "pidgin/pidginaccountchooser.h"
63 #include "pidgin/pidgindebug.h"
64 #include "pidgin/pidgindebugplugininfo.h"
65 #include "pidgin/pidgingdkpixbuf.h"
66 #include "pidgin/pidginlog.h"
67 #include "pidgin/pidgintooltip.h"
69 #include <gdk/gdkkeysyms.h>
73 #include "gtk3compat.h"
77 PurpleAccount
*account
;
80 GtkWidget
*account_menu
;
82 } PidginBlistRequestData
;
86 PidginBlistRequestData rq_data
;
89 GtkWidget
*entry_for_alias
;
90 GtkWidget
*entry_for_invite
;
96 PidginBlistRequestData rq_data
;
97 gchar
*default_chat_name
;
103 PidginChatData chat_data
;
105 GtkWidget
*alias_entry
;
106 GtkWidget
*group_combo
;
108 GtkWidget
*persistent
;
113 /* GBoxed reference count */
116 /* Used to hold error minidialogs. Gets packed
117 * inside PidginBuddyList.error_buttons
119 PidginScrollBook
*error_scrollbook
;
121 /* Pointer to the mini-dialog about having signed on elsewhere, if one
122 * is showing; %NULL otherwise.
124 PidginMiniDialog
*signed_on_elsewhere
;
126 PidginBlistTheme
*current_theme
;
128 guint select_notebook_page_timeout
;
131 } PidginBuddyListPrivate
;
133 G_DEFINE_TYPE_WITH_PRIVATE(PidginBuddyList
, pidgin_buddy_list
,
134 PURPLE_TYPE_BUDDY_LIST
)
136 #define PIDGIN_WINDOW_ICONIFIED(x) \
137 (gdk_window_get_state(gtk_widget_get_window(GTK_WIDGET(x))) & \
138 GDK_WINDOW_STATE_ICONIFIED)
140 #define PIDGIN_WINDOW_MAXIMIZED(x) \
141 (gdk_window_get_state(gtk_widget_get_window(GTK_WIDGET(x))) & \
142 GDK_WINDOW_STATE_MAXIMIZED)
144 static GtkWidget
*accountmenu
= NULL
;
146 static guint visibility_manager_count
= 0;
147 static GdkVisibilityState gtk_blist_visibility
= GDK_VISIBILITY_UNOBSCURED
;
148 static gboolean gtk_blist_focused
= FALSE
;
149 static gboolean editing_blist
= FALSE
;
151 static GList
*pidgin_blist_sort_methods
= NULL
;
152 static struct _PidginBlistSortMethod
*current_sort_method
= NULL
;
153 static void sort_method_none(PurpleBlistNode
*node
, PurpleBuddyList
*blist
, GtkTreeIter groupiter
, GtkTreeIter
*cur
, GtkTreeIter
*iter
);
155 static void sort_method_alphabetical(PurpleBlistNode
*node
, PurpleBuddyList
*blist
, GtkTreeIter groupiter
, GtkTreeIter
*cur
, GtkTreeIter
*iter
);
156 static void sort_method_status(PurpleBlistNode
*node
, PurpleBuddyList
*blist
, GtkTreeIter groupiter
, GtkTreeIter
*cur
, GtkTreeIter
*iter
);
157 static void sort_method_log_activity(PurpleBlistNode
*node
, PurpleBuddyList
*blist
, GtkTreeIter groupiter
, GtkTreeIter
*cur
, GtkTreeIter
*iter
);
158 static guint sort_merge_id
;
159 static GtkActionGroup
*sort_action_group
= NULL
;
161 static PidginBuddyList
*gtkblist
= NULL
;
163 static GList
*groups_tree(void);
164 static gboolean
pidgin_blist_refresh_timer(PurpleBuddyList
*list
);
165 static void pidgin_blist_update_buddy(PurpleBuddyList
*list
, PurpleBlistNode
*node
, gboolean status_change
);
166 static void pidgin_blist_selection_changed(GtkTreeSelection
*selection
, gpointer data
);
167 static void pidgin_blist_update(PurpleBuddyList
*list
, PurpleBlistNode
*node
);
168 static void pidgin_blist_update_group(PurpleBuddyList
*list
, PurpleBlistNode
*node
);
169 static void pidgin_blist_update_contact(PurpleBuddyList
*list
, PurpleBlistNode
*node
);
170 static char *pidgin_get_tooltip_text(PurpleBlistNode
*node
, gboolean full
);
171 static gboolean
get_iter_from_node(PurpleBlistNode
*node
, GtkTreeIter
*iter
);
172 static gboolean
buddy_is_displayable(PurpleBuddy
*buddy
);
173 static void redo_buddy_list(PurpleBuddyList
*list
, gboolean remove
, gboolean rerender
);
174 static void pidgin_blist_collapse_contact_cb(GtkWidget
*w
, PurpleBlistNode
*node
);
175 static char *pidgin_get_group_title(PurpleBlistNode
*gnode
, gboolean expanded
);
176 static void pidgin_blist_expand_contact_cb(GtkWidget
*w
, PurpleBlistNode
*node
);
177 static void set_urgent(void);
180 PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE
= 1 << 0, /* Whether there's pending message in a conversation */
181 PIDGIN_BLIST_CHAT_HAS_PENDING_MESSAGE_WITH_NICK
= 1 << 1, /* Whether there's a pending message in a chat that mentions our nick */
182 } PidginBlistNodeFlags
;
185 GtkTreeRowReference
*row
;
186 gboolean contact_expanded
;
187 gboolean recent_signonoff
;
188 gint recent_signonoff_timer
;
190 PurpleConversation
*conv
;
191 PidginBlistNodeFlags flags
;
195 /***************************************************
197 ***************************************************/
198 static gboolean
gtk_blist_visibility_cb(GtkWidget
*w
, GdkEventVisibility
*event
, gpointer data
)
200 GdkVisibilityState old_state
= gtk_blist_visibility
;
201 gtk_blist_visibility
= event
->state
;
203 if (gtk_blist_visibility
== GDK_VISIBILITY_FULLY_OBSCURED
&&
204 old_state
!= GDK_VISIBILITY_FULLY_OBSCURED
) {
206 /* no longer fully obscured */
207 pidgin_blist_refresh_timer(purple_blist_get_default());
210 /* continue to handle event normally */
214 static gboolean
gtk_blist_window_state_cb(GtkWidget
*w
, GdkEventWindowState
*event
, gpointer data
)
216 if(event
->changed_mask
& GDK_WINDOW_STATE_WITHDRAWN
) {
217 if(event
->new_window_state
& GDK_WINDOW_STATE_WITHDRAWN
)
218 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/blist/list_visible", FALSE
);
220 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/blist/list_visible", TRUE
);
221 pidgin_blist_refresh_timer(purple_blist_get_default());
225 if(event
->changed_mask
& GDK_WINDOW_STATE_MAXIMIZED
) {
226 if(event
->new_window_state
& GDK_WINDOW_STATE_MAXIMIZED
)
227 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/blist/list_maximized", TRUE
);
229 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/blist/list_maximized", FALSE
);
232 /* Refresh gtkblist if un-iconifying */
233 if (event
->changed_mask
& GDK_WINDOW_STATE_ICONIFIED
){
234 if (!(event
->new_window_state
& GDK_WINDOW_STATE_ICONIFIED
))
235 pidgin_blist_refresh_timer(purple_blist_get_default());
241 static gboolean
gtk_blist_delete_cb(GtkWidget
*w
, GdkEventAny
*event
, gpointer data
)
243 if(visibility_manager_count
)
244 purple_blist_set_visible(FALSE
);
248 /* we handle everything, event should not propogate further */
253 gtk_blist_hide_cb(GtkWidget
*widget
, PidginBuddyList
*gtkblist
)
255 purple_signal_emit(pidgin_blist_get_handle(),
256 "gtkblist-hiding", gtkblist
);
260 gtk_blist_show_cb(GtkWidget
*widget
, PidginBuddyList
*gtkblist
)
262 purple_signal_emit(pidgin_blist_get_handle(),
263 "gtkblist-unhiding", gtkblist
);
267 gtk_blist_size_allocate_cb(GtkWidget
*widget
, GtkAllocation
*allocation
,
273 /* ignore changes when maximized */
274 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/list_maximized")) {
278 gtk_window_get_size(GTK_WINDOW(widget
), &new_width
, &new_height
);
281 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/blist/width", new_width
);
282 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/blist/height", new_height
);
285 static void gtk_blist_menu_info_cb(GtkWidget
*w
, PurpleBuddy
*b
)
287 PurpleAccount
*account
= purple_buddy_get_account(b
);
289 pidgin_retrieve_user_info(purple_account_get_connection(account
),
290 purple_buddy_get_name(b
));
293 static void gtk_blist_menu_im_cb(GtkWidget
*w
, PurpleBuddy
*b
)
295 pidgin_dialogs_im_with_user(purple_buddy_get_account(b
),
296 purple_buddy_get_name(b
));
300 static void gtk_blist_menu_audio_call_cb(GtkWidget
*w
, PurpleBuddy
*b
)
302 purple_protocol_initiate_media(purple_buddy_get_account(b
),
303 purple_buddy_get_name(b
), PURPLE_MEDIA_AUDIO
);
306 static void gtk_blist_menu_video_call_cb(GtkWidget
*w
, PurpleBuddy
*b
)
308 /* if the buddy supports both audio and video, start a combined call,
309 otherwise start a pure video session */
310 if (purple_protocol_get_media_caps(purple_buddy_get_account(b
),
311 purple_buddy_get_name(b
)) &
312 PURPLE_MEDIA_CAPS_AUDIO_VIDEO
) {
313 purple_protocol_initiate_media(purple_buddy_get_account(b
),
314 purple_buddy_get_name(b
), PURPLE_MEDIA_AUDIO
| PURPLE_MEDIA_VIDEO
);
316 purple_protocol_initiate_media(purple_buddy_get_account(b
),
317 purple_buddy_get_name(b
), PURPLE_MEDIA_VIDEO
);
323 static void gtk_blist_menu_send_file_cb(GtkWidget
*w
, PurpleBuddy
*b
)
325 PurpleAccount
*account
= purple_buddy_get_account(b
);
327 purple_serv_send_file(purple_account_get_connection(account
),
328 purple_buddy_get_name(b
), NULL
);
331 static void gtk_blist_menu_move_to_cb(GtkWidget
*w
, PurpleBlistNode
*node
)
333 PurpleGroup
*group
= g_object_get_data(G_OBJECT(w
), "groupnode");
334 purple_blist_add_contact((PurpleContact
*)node
, group
, NULL
);
338 static void gtk_blist_menu_autojoin_cb(GtkWidget
*w
, PurpleChat
*chat
)
340 purple_blist_node_set_bool(PURPLE_BLIST_NODE(chat
), "gtk-autojoin",
341 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w
)));
344 static void gtk_blist_menu_persistent_cb(GtkWidget
*w
, PurpleChat
*chat
)
346 purple_blist_node_set_bool(PURPLE_BLIST_NODE(chat
), "gtk-persistent",
347 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w
)));
350 static PurpleConversation
*
351 find_conversation_with_buddy(PurpleBuddy
*buddy
)
353 PidginBlistNode
*ui
= purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(buddy
));
355 return ui
->conv
.conv
;
356 return PURPLE_CONVERSATION(purple_conversations_find_im_with_account(
357 purple_buddy_get_name(buddy
), purple_buddy_get_account(buddy
)));
360 static void gtk_blist_join_chat(PurpleChat
*chat
)
362 PurpleAccount
*account
;
363 PurpleConversation
*conv
;
364 PurpleProtocol
*protocol
;
365 GHashTable
*components
;
367 char *chat_name
= NULL
;
369 account
= purple_chat_get_account(chat
);
370 protocol
= purple_protocols_find(purple_account_get_protocol_id(account
));
372 components
= purple_chat_get_components(chat
);
375 chat_name
= purple_protocol_chat_iface_get_name(protocol
, components
);
380 name
= purple_chat_get_name(chat
);
382 conv
= PURPLE_CONVERSATION(purple_conversations_find_chat_with_account(name
,
386 pidgin_conv_attach_to_conversation(conv
);
387 purple_conversation_present(conv
);
390 purple_serv_join_chat(purple_account_get_connection(account
), components
);
394 static void gtk_blist_menu_join_cb(GtkWidget
*w
, PurpleChat
*chat
)
396 gtk_blist_join_chat(chat
);
399 static void gtk_blist_renderer_editing_cancelled_cb(GtkCellRenderer
*renderer
, PurpleBuddyList
*list
)
401 editing_blist
= FALSE
;
402 g_object_set(G_OBJECT(renderer
), "editable", FALSE
, NULL
);
403 pidgin_blist_refresh(list
);
406 static void gtk_blist_renderer_editing_started_cb(GtkCellRenderer
*renderer
,
407 GtkCellEditable
*editable
,
411 PidginBuddyList
*gtkblist
= PIDGIN_BUDDY_LIST(user_data
);
413 GtkTreePath
*path
= NULL
;
414 PurpleBlistNode
*node
;
415 const char *text
= NULL
;
417 path
= gtk_tree_path_new_from_string (path_str
);
418 gtk_tree_model_get_iter (GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, path
);
419 gtk_tree_path_free (path
);
420 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, NODE_COLUMN
, &node
, -1);
422 if (PURPLE_IS_CONTACT(node
))
423 text
= purple_contact_get_alias(PURPLE_CONTACT(node
));
424 else if (PURPLE_IS_BUDDY(node
))
425 text
= purple_buddy_get_alias(PURPLE_BUDDY(node
));
426 else if (PURPLE_IS_GROUP(node
))
427 text
= purple_group_get_name(PURPLE_GROUP(node
));
428 else if (PURPLE_IS_CHAT(node
))
429 text
= purple_chat_get_name(PURPLE_CHAT(node
));
431 g_return_if_reached();
433 if (GTK_IS_ENTRY (editable
)) {
434 GtkEntry
*entry
= GTK_ENTRY (editable
);
435 gtk_entry_set_text(entry
, text
);
437 editing_blist
= TRUE
;
441 gtk_blist_do_personize(GList
*merges
)
443 PurpleBlistNode
*contact
= NULL
;
447 /* First, we find the contact to merge the rest of the buddies into.
448 * This will be the contact with the most buddies in it; ties are broken
449 * by which contact is higher in the list
451 for (tmp
= merges
; tmp
; tmp
= tmp
->next
) {
452 PurpleBlistNode
*node
= tmp
->data
;
456 if (PURPLE_IS_BUDDY(node
))
457 node
= purple_blist_node_get_parent(node
);
459 if (!PURPLE_IS_CONTACT(node
))
462 for (b
= purple_blist_node_get_first_child(node
);
464 b
= purple_blist_node_get_sibling_next(b
))
478 /* Merge all those buddies into this contact */
479 for (tmp
= merges
; tmp
; tmp
= tmp
->next
) {
480 PurpleBlistNode
*node
= tmp
->data
;
481 if (PURPLE_IS_BUDDY(node
))
482 node
= purple_blist_node_get_parent(node
);
487 purple_contact_merge((PurpleContact
*)node
, contact
);
490 /* And show the expanded contact, so the people know what's going on */
491 pidgin_blist_expand_contact_cb(NULL
, contact
);
496 gtk_blist_auto_personize(PurpleBlistNode
*group
, const char *alias
)
498 PurpleBlistNode
*contact
;
499 PurpleBlistNode
*buddy
;
500 GList
*merges
= NULL
;
502 char *a
= g_utf8_casefold(alias
, -1);
504 for (contact
= purple_blist_node_get_first_child(group
);
506 contact
= purple_blist_node_get_sibling_next(contact
)) {
508 if (!PURPLE_IS_CONTACT(contact
))
511 node_alias
= g_utf8_casefold(purple_contact_get_alias((PurpleContact
*)contact
), -1);
512 if (node_alias
&& !g_utf8_collate(node_alias
, a
)) {
513 merges
= g_list_append(merges
, contact
);
520 for (buddy
= purple_blist_node_get_first_child(contact
);
522 buddy
= purple_blist_node_get_sibling_next(buddy
))
524 if (!PURPLE_IS_BUDDY(buddy
))
527 node_alias
= g_utf8_casefold(purple_buddy_get_alias(PURPLE_BUDDY(buddy
)), -1);
528 if (node_alias
&& !g_utf8_collate(node_alias
, a
)) {
529 merges
= g_list_append(merges
, buddy
);
541 char *msg
= g_strdup_printf(ngettext("You have %d contact named %s. Would you like to merge them?", "You currently have %d contacts named %s. Would you like to merge them?", i
), i
, alias
);
542 purple_request_action(NULL
, NULL
, msg
, _("Merging these contacts will cause them to share a single entry on the buddy list and use a single conversation window. "
543 "You can separate them again by choosing 'Expand' from the contact's context menu"), 0, NULL
,
544 merges
, 2, _("_Yes"), PURPLE_CALLBACK(gtk_blist_do_personize
), _("_No"), PURPLE_CALLBACK(g_list_free
));
550 static void gtk_blist_renderer_edited_cb(GtkCellRendererText
*text_rend
, char *arg1
,
551 char *arg2
, PurpleBuddyList
*list
)
553 PidginBuddyList
*gtkblist
= PIDGIN_BUDDY_LIST(list
);
556 PurpleBlistNode
*node
;
560 editing_blist
= FALSE
;
561 path
= gtk_tree_path_new_from_string (arg1
);
562 gtk_tree_model_get_iter (GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, path
);
563 gtk_tree_path_free (path
);
564 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, NODE_COLUMN
, &node
, -1);
565 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(gtkblist
->treeview
), TRUE
);
566 g_object_set(G_OBJECT(gtkblist
->text_rend
), "editable", FALSE
, NULL
);
568 if (PURPLE_IS_CONTACT(node
)) {
569 PurpleContact
*contact
= PURPLE_CONTACT(node
);
570 PidginBlistNode
*gtknode
=
571 (PidginBlistNode
*)purple_blist_node_get_ui_data(node
);
574 * Using purple_contact_get_alias here breaks because we
575 * specifically want to check the contact alias only (i.e. not
576 * the priority buddy, which purple_contact_get_alias does).
577 * The "alias" GObject property gives us just the alias.
579 g_object_get(contact
, "alias", &alias
, NULL
);
581 if (alias
|| gtknode
->contact_expanded
) {
582 purple_contact_set_alias(contact
, arg2
);
583 gtk_blist_auto_personize(purple_blist_node_get_parent(node
), arg2
);
585 PurpleBuddy
*buddy
= purple_contact_get_priority_buddy(contact
);
586 purple_buddy_set_local_alias(buddy
, arg2
);
587 purple_serv_alias_buddy(buddy
);
588 gtk_blist_auto_personize(purple_blist_node_get_parent(node
), arg2
);
590 } else if (PURPLE_IS_BUDDY(node
)) {
591 PurpleGroup
*group
= purple_buddy_get_group(PURPLE_BUDDY(node
));
593 purple_buddy_set_local_alias(PURPLE_BUDDY(node
), arg2
);
594 purple_serv_alias_buddy(PURPLE_BUDDY(node
));
595 gtk_blist_auto_personize(PURPLE_BLIST_NODE(group
), arg2
);
596 } else if (PURPLE_IS_GROUP(node
)) {
597 dest
= purple_blist_find_group(arg2
);
598 if (dest
!= NULL
&& purple_utf8_strcasecmp(arg2
, purple_group_get_name(PURPLE_GROUP(node
)))) {
599 pidgin_dialogs_merge_groups(PURPLE_GROUP(node
), arg2
);
601 purple_group_set_name(PURPLE_GROUP(node
), arg2
);
603 } else if (PURPLE_IS_CHAT(node
)) {
604 purple_chat_set_alias(PURPLE_CHAT(node
), arg2
);
608 pidgin_blist_refresh(list
);
612 chat_components_edit_ok(PurpleChat
*chat
, PurpleRequestFields
*allfields
)
614 GList
*groups
, *fields
;
616 for (groups
= purple_request_fields_get_groups(allfields
); groups
; groups
= groups
->next
) {
617 fields
= purple_request_field_group_get_fields(groups
->data
);
618 for (; fields
; fields
= fields
->next
) {
619 PurpleRequestField
*field
= fields
->data
;
623 id
= purple_request_field_get_id(field
);
624 if (purple_request_field_get_field_type(field
) == PURPLE_REQUEST_FIELD_INTEGER
)
625 val
= g_strdup_printf("%d", purple_request_field_int_get_value(field
));
627 val
= g_strdup(purple_request_field_string_get_value(field
));
630 g_hash_table_remove(purple_chat_get_components(chat
), id
);
632 g_hash_table_replace(purple_chat_get_components(chat
), g_strdup(id
), val
); /* val should not be free'd */
638 static void chat_components_edit(GtkWidget
*w
, PurpleBlistNode
*node
)
640 PurpleRequestFields
*fields
= purple_request_fields_new();
641 PurpleRequestFieldGroup
*group
= purple_request_field_group_new(NULL
);
642 PurpleRequestField
*field
;
644 PurpleProtocolChatEntry
*pce
;
645 PurpleConnection
*gc
;
646 PurpleChat
*chat
= (PurpleChat
*)node
;
648 purple_request_fields_add_group(fields
, group
);
650 gc
= purple_account_get_connection(purple_chat_get_account(chat
));
651 parts
= purple_protocol_chat_iface_info(purple_connection_get_protocol(gc
), gc
);
653 for (iter
= parts
; iter
; iter
= iter
->next
) {
657 const char *str
= g_hash_table_lookup(purple_chat_get_components(chat
), pce
->identifier
);
658 if (!str
|| sscanf(str
, "%d", &val
) != 1)
660 field
= purple_request_field_int_new(pce
->identifier
, pce
->label
, val
, INT_MIN
, INT_MAX
);
662 field
= purple_request_field_string_new(pce
->identifier
, pce
->label
,
663 g_hash_table_lookup(purple_chat_get_components(chat
), pce
->identifier
), FALSE
);
665 purple_request_field_string_set_masked(field
, TRUE
);
669 purple_request_field_set_required(field
, TRUE
);
671 purple_request_field_group_add_field(group
, field
);
677 purple_request_fields(NULL
, _("Edit Chat"), NULL
, _("Please update the necessary fields."),
678 fields
, _("Save"), G_CALLBACK(chat_components_edit_ok
), _("Cancel"), NULL
,
682 static void gtk_blist_menu_alias_cb(GtkWidget
*w
, PurpleBlistNode
*node
)
687 if (!(get_iter_from_node(node
, &iter
))) {
688 /* This is either a bug, or the buddy is in a collapsed contact */
689 node
= purple_blist_node_get_parent(node
);
690 if (!get_iter_from_node(node
, &iter
))
691 /* Now it's definitely a bug */
695 pidgin_blist_tooltip_destroy();
697 path
= gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
);
698 g_object_set(G_OBJECT(gtkblist
->text_rend
), "editable", TRUE
, NULL
);
699 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(gtkblist
->treeview
), FALSE
);
700 gtk_widget_grab_focus(gtkblist
->treeview
);
701 gtk_tree_view_set_cursor_on_cell(GTK_TREE_VIEW(gtkblist
->treeview
), path
,
702 gtkblist
->text_column
, gtkblist
->text_rend
, TRUE
);
703 gtk_tree_path_free(path
);
706 static void gtk_blist_menu_bp_cb(GtkWidget
*w
, PurpleBuddy
*b
)
708 pidgin_pounce_editor_show(purple_buddy_get_account(b
),
709 purple_buddy_get_name(b
), NULL
);
712 static void gtk_blist_menu_showlog_cb(GtkWidget
*w
, PurpleBlistNode
*node
)
715 PurpleAccount
*account
;
718 pidgin_set_cursor(gtkblist
->window
, GDK_WATCH
);
720 if (PURPLE_IS_BUDDY(node
)) {
721 PurpleBuddy
*b
= (PurpleBuddy
*) node
;
722 type
= PURPLE_LOG_IM
;
723 name
= g_strdup(purple_buddy_get_name(b
));
724 account
= purple_buddy_get_account(b
);
725 } else if (PURPLE_IS_CHAT(node
)) {
726 PurpleChat
*c
= PURPLE_CHAT(node
);
727 PurpleProtocol
*protocol
= NULL
;
728 type
= PURPLE_LOG_CHAT
;
729 account
= purple_chat_get_account(c
);
730 protocol
= purple_protocols_find(purple_account_get_protocol_id(account
));
732 name
= purple_protocol_chat_iface_get_name(protocol
, purple_chat_get_components(c
));
734 } else if (PURPLE_IS_CONTACT(node
)) {
735 pidgin_log_show_contact(PURPLE_CONTACT(node
));
736 pidgin_clear_cursor(gtkblist
->window
);
739 pidgin_clear_cursor(gtkblist
->window
);
741 /* This callback should not have been registered for a node
742 * that doesn't match the type of one of the blocks above. */
743 g_return_if_reached();
746 if (name
&& account
) {
747 pidgin_log_show(type
, name
, account
);
748 pidgin_clear_cursor(gtkblist
->window
);
754 static void gtk_blist_menu_showoffline_cb(GtkWidget
*w
, PurpleBlistNode
*node
)
756 if (PURPLE_IS_BUDDY(node
))
758 purple_blist_node_set_bool(node
, "show_offline",
759 !purple_blist_node_get_bool(node
, "show_offline"));
760 pidgin_blist_update(purple_blist_get_default(), node
);
762 else if (PURPLE_IS_CONTACT(node
))
764 PurpleBlistNode
*bnode
;
765 gboolean setting
= !purple_blist_node_get_bool(node
, "show_offline");
767 purple_blist_node_set_bool(node
, "show_offline", setting
);
768 for (bnode
= purple_blist_node_get_first_child(node
);
770 bnode
= purple_blist_node_get_sibling_next(bnode
))
772 purple_blist_node_set_bool(bnode
, "show_offline", setting
);
773 pidgin_blist_update(purple_blist_get_default(), bnode
);
775 } else if (PURPLE_IS_GROUP(node
)) {
776 PurpleBlistNode
*cnode
, *bnode
;
777 gboolean setting
= !purple_blist_node_get_bool(node
, "show_offline");
779 purple_blist_node_set_bool(node
, "show_offline", setting
);
780 for (cnode
= purple_blist_node_get_first_child(node
);
782 cnode
= purple_blist_node_get_sibling_next(cnode
))
784 purple_blist_node_set_bool(cnode
, "show_offline", setting
);
785 for (bnode
= purple_blist_node_get_first_child(cnode
);
787 bnode
= purple_blist_node_get_sibling_next(bnode
))
789 purple_blist_node_set_bool(bnode
, "show_offline", setting
);
790 pidgin_blist_update(purple_blist_get_default(),
797 static void gtk_blist_show_systemlog_cb(void)
799 pidgin_syslog_show();
802 static void gtk_blist_show_onlinehelp_cb(void)
804 purple_notify_uri(NULL
, PURPLE_WEBSITE
"documentation");
808 do_join_chat(PidginChatData
*data
)
812 GHashTable
*components
=
813 g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, g_free
);
817 for (tmp
= data
->entries
; tmp
!= NULL
; tmp
= tmp
->next
)
819 if (g_object_get_data(tmp
->data
, "is_spin"))
821 g_hash_table_replace(components
,
822 g_strdup(g_object_get_data(tmp
->data
, "identifier")),
823 g_strdup_printf("%d",
824 gtk_spin_button_get_value_as_int(tmp
->data
)));
828 g_hash_table_replace(components
,
829 g_strdup(g_object_get_data(tmp
->data
, "identifier")),
830 g_strdup(gtk_entry_get_text(tmp
->data
)));
834 chat
= purple_chat_new(data
->rq_data
.account
, NULL
, components
);
835 gtk_blist_join_chat(chat
);
836 purple_blist_remove_chat(chat
);
841 do_joinchat(GtkWidget
*dialog
, int id
, PidginChatData
*info
)
845 case GTK_RESPONSE_OK
:
850 pidgin_roomlist_dialog_show_with_account(info
->rq_data
.account
);
856 gtk_widget_destroy(GTK_WIDGET(dialog
));
857 g_list_free(info
->entries
);
862 * Check the values of all the text entry boxes. If any required input
863 * strings are empty then don't allow the user to click on "OK."
866 set_sensitive_if_input_chat_cb(GtkWidget
*entry
, gpointer user_data
)
868 PurpleProtocol
*protocol
;
869 PurpleConnection
*gc
;
870 PidginChatData
*data
;
874 gboolean sensitive
= TRUE
;
878 for (tmp
= data
->entries
; tmp
!= NULL
; tmp
= tmp
->next
)
880 if (!g_object_get_data(tmp
->data
, "is_spin"))
882 required
= GPOINTER_TO_INT(g_object_get_data(tmp
->data
, "required"));
883 text
= gtk_entry_get_text(tmp
->data
);
884 if (required
&& (*text
== '\0'))
889 gtk_dialog_set_response_sensitive(GTK_DIALOG(data
->rq_data
.window
), GTK_RESPONSE_OK
, sensitive
);
891 gc
= purple_account_get_connection(data
->rq_data
.account
);
892 protocol
= (gc
!= NULL
) ? purple_connection_get_protocol(gc
) : NULL
;
893 sensitive
= (protocol
!= NULL
&& PURPLE_PROTOCOL_IMPLEMENTS(protocol
, ROOMLIST
, get_list
));
895 gtk_dialog_set_response_sensitive(GTK_DIALOG(data
->rq_data
.window
), 1, sensitive
);
899 set_sensitive_if_input_buddy_cb(GtkWidget
*entry
, gpointer user_data
)
901 PurpleProtocol
*protocol
;
902 PidginAddBuddyData
*data
= user_data
;
905 protocol
= purple_protocols_find(purple_account_get_protocol_id(
906 data
->rq_data
.account
));
907 text
= gtk_entry_get_text(GTK_ENTRY(entry
));
909 gtk_dialog_set_response_sensitive(GTK_DIALOG(data
->rq_data
.window
),
910 GTK_RESPONSE_OK
, purple_validate(protocol
, text
));
914 pidgin_blist_update_privacy_cb(PurpleBuddy
*buddy
)
916 PidginBlistNode
*ui_data
= purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(buddy
));
917 if (ui_data
== NULL
|| ui_data
->row
== NULL
)
919 pidgin_blist_update_buddy(purple_blist_get_default(),
920 PURPLE_BLIST_NODE(buddy
), TRUE
);
924 add_buddy_account_filter_func(PurpleAccount
*account
)
926 PurpleConnection
*gc
= purple_account_get_connection(account
);
927 PurpleProtocol
*protocol
= purple_connection_get_protocol(gc
);
929 return PURPLE_PROTOCOL_IMPLEMENTS(protocol
, SERVER
, add_buddy
);
933 chat_account_filter_func(PurpleAccount
*account
)
935 PurpleConnection
*gc
= purple_account_get_connection(account
);
936 PurpleProtocol
*protocol
= NULL
;
941 protocol
= purple_connection_get_protocol(gc
);
943 return (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT
, info
));
947 pidgin_blist_joinchat_is_showable()
950 PurpleConnection
*gc
;
952 for (c
= purple_connections_get_all(); c
!= NULL
; c
= c
->next
) {
955 if (chat_account_filter_func(purple_connection_get_account(gc
)))
963 make_blist_request_dialog(PidginBlistRequestData
*data
, PurpleAccount
*account
,
964 const char *title
, const char *window_role
, const char *label_text
,
965 GCallback callback_func
, PurpleFilterAccountFunc filter_func
,
966 GCallback response_cb
)
972 GtkWindow
*blist_window
;
973 PidginBuddyList
*gtkblist
;
975 data
->account
= account
;
977 img
= gtk_image_new_from_icon_name("dialog-question",
978 GTK_ICON_SIZE_DIALOG
);
980 gtkblist
= PIDGIN_BUDDY_LIST(purple_blist_get_default());
981 blist_window
= gtkblist
? GTK_WINDOW(gtkblist
->window
) : NULL
;
983 data
->window
= gtk_dialog_new();
984 gtk_window_set_title(GTK_WINDOW(data
->window
), title
);
985 gtk_window_set_transient_for(GTK_WINDOW(data
->window
), blist_window
);
986 gtk_dialog_set_default_response(GTK_DIALOG(data
->window
), GTK_RESPONSE_OK
);
987 gtk_container_set_border_width(GTK_CONTAINER(data
->window
), PIDGIN_HIG_BOX_SPACE
);
988 gtk_window_set_resizable(GTK_WINDOW(data
->window
), FALSE
);
989 gtk_box_set_spacing(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(data
->window
))),
991 gtk_container_set_border_width(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(data
->window
))),
992 PIDGIN_HIG_BOX_SPACE
);
993 gtk_window_set_role(GTK_WINDOW(data
->window
), window_role
);
995 hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, PIDGIN_HIG_BORDER
);
996 gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(data
->window
))),
998 gtk_box_pack_start(GTK_BOX(hbox
), img
, FALSE
, FALSE
, 0);
999 gtk_widget_set_halign(img
, GTK_ALIGN_START
);
1000 gtk_widget_set_valign(img
, GTK_ALIGN_START
);
1002 vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 5);
1003 gtk_container_add(GTK_CONTAINER(hbox
), vbox
);
1005 label
= gtk_label_new(label_text
);
1007 gtk_widget_set_size_request(label
, 400, -1);
1008 gtk_label_set_line_wrap(GTK_LABEL(label
), TRUE
);
1009 gtk_label_set_xalign(GTK_LABEL(label
), 0);
1010 gtk_label_set_yalign(GTK_LABEL(label
), 0);
1011 gtk_box_pack_start(GTK_BOX(vbox
), label
, FALSE
, FALSE
, 0);
1013 data
->sg
= gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL
);
1015 data
->account_menu
= pidgin_account_chooser_new(account
, FALSE
);
1016 pidgin_account_chooser_set_filter_func(
1017 PIDGIN_ACCOUNT_CHOOSER(data
->account_menu
), filter_func
);
1018 pidgin_add_widget_to_vbox(GTK_BOX(vbox
), _("A_ccount"), data
->sg
, data
->account_menu
, TRUE
, NULL
);
1019 g_signal_connect(data
->account_menu
, "changed",
1020 G_CALLBACK(callback_func
), data
);
1022 data
->vbox
= GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL
, 5));
1023 gtk_container_set_border_width(GTK_CONTAINER(data
->vbox
), 0);
1024 gtk_box_pack_start(GTK_BOX(vbox
), GTK_WIDGET(data
->vbox
), FALSE
, FALSE
, 0);
1026 g_signal_connect(G_OBJECT(data
->window
), "response", response_cb
, data
);
1028 g_object_unref(data
->sg
);
1034 rebuild_chat_entries(PidginChatData
*data
, const char *default_chat_name
)
1036 PurpleConnection
*gc
;
1037 PurpleProtocol
*protocol
;
1038 GList
*list
= NULL
, *tmp
;
1039 GHashTable
*defaults
= NULL
;
1040 PurpleProtocolChatEntry
*pce
;
1041 gboolean focus
= TRUE
;
1043 g_return_if_fail(data
->rq_data
.account
!= NULL
);
1045 gc
= purple_account_get_connection(data
->rq_data
.account
);
1046 protocol
= purple_connection_get_protocol(gc
);
1048 gtk_container_foreach(GTK_CONTAINER(data
->rq_data
.vbox
), (GtkCallback
)gtk_widget_destroy
, NULL
);
1050 g_list_free(data
->entries
);
1051 data
->entries
= NULL
;
1053 list
= purple_protocol_chat_iface_info(protocol
, gc
);
1054 defaults
= purple_protocol_chat_iface_info_defaults(protocol
, gc
, default_chat_name
);
1056 for (tmp
= list
; tmp
; tmp
= tmp
->next
)
1064 GtkAdjustment
*adjust
;
1065 adjust
= GTK_ADJUSTMENT(gtk_adjustment_new(pce
->min
,
1068 input
= gtk_spin_button_new(adjust
, 1, 0);
1069 gtk_widget_set_size_request(input
, 50, -1);
1070 pidgin_add_widget_to_vbox(GTK_BOX(data
->rq_data
.vbox
), pce
->label
, data
->rq_data
.sg
, input
, FALSE
, NULL
);
1075 input
= gtk_entry_new();
1076 gtk_entry_set_activates_default(GTK_ENTRY(input
), TRUE
);
1077 value
= g_hash_table_lookup(defaults
, pce
->identifier
);
1079 gtk_entry_set_text(GTK_ENTRY(input
), value
);
1082 gtk_entry_set_visibility(GTK_ENTRY(input
), FALSE
);
1084 pidgin_add_widget_to_vbox(data
->rq_data
.vbox
, pce
->label
, data
->rq_data
.sg
, input
, TRUE
, NULL
);
1085 g_signal_connect(G_OBJECT(input
), "changed",
1086 G_CALLBACK(set_sensitive_if_input_chat_cb
), data
);
1089 /* Do the following for any type of input widget */
1092 gtk_widget_grab_focus(input
);
1095 g_object_set_data(G_OBJECT(input
), "identifier", (gpointer
)pce
->identifier
);
1096 g_object_set_data(G_OBJECT(input
), "is_spin", GINT_TO_POINTER(pce
->is_int
));
1097 g_object_set_data(G_OBJECT(input
), "required", GINT_TO_POINTER(pce
->required
));
1098 data
->entries
= g_list_append(data
->entries
, input
);
1104 g_hash_table_destroy(defaults
);
1106 /* Set whether the "OK" button should be clickable initially */
1107 set_sensitive_if_input_chat_cb(NULL
, data
);
1109 gtk_widget_show_all(GTK_WIDGET(data
->rq_data
.vbox
));
1113 chat_select_account_cb(GObject
*w
, PidginChatData
*data
)
1115 PurpleAccount
*account
=
1116 pidgin_account_chooser_get_selected(GTK_WIDGET(w
));
1118 g_return_if_fail(data
!= NULL
);
1119 g_return_if_fail(account
!= NULL
);
1121 if (purple_strequal(purple_account_get_protocol_id(data
->rq_data
.account
),
1122 purple_account_get_protocol_id(account
)))
1124 data
->rq_data
.account
= account
;
1128 data
->rq_data
.account
= account
;
1129 rebuild_chat_entries(data
, data
->default_chat_name
);
1134 pidgin_blist_joinchat_show(void)
1136 PidginChatData
*data
= NULL
;
1138 data
= g_new0(PidginChatData
, 1);
1140 make_blist_request_dialog((PidginBlistRequestData
*)data
, NULL
,
1141 _("Join a Chat"), "join_chat",
1142 _("Please enter the appropriate information about the chat "
1143 "you would like to join.\n"),
1144 G_CALLBACK(chat_select_account_cb
),
1145 chat_account_filter_func
, (GCallback
)do_joinchat
);
1146 gtk_dialog_add_buttons(GTK_DIALOG(data
->rq_data
.window
),
1148 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
1149 PIDGIN_STOCK_CHAT
, GTK_RESPONSE_OK
, NULL
);
1150 gtk_dialog_set_default_response(GTK_DIALOG(data
->rq_data
.window
),
1152 data
->default_chat_name
= NULL
;
1153 data
->rq_data
.account
=
1154 pidgin_account_chooser_get_selected(data
->rq_data
.account_menu
);
1156 rebuild_chat_entries(data
, NULL
);
1158 gtk_widget_show_all(data
->rq_data
.window
);
1161 static void gtk_blist_row_expanded_cb(GtkTreeView
*tv
, GtkTreeIter
*iter
, GtkTreePath
*path
, gpointer user_data
)
1163 PidginBuddyList
*gtkblist
= PIDGIN_BUDDY_LIST(user_data
);
1164 PurpleBlistNode
*node
;
1166 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), iter
, NODE_COLUMN
, &node
, -1);
1168 if (PURPLE_IS_GROUP(node
)) {
1171 title
= pidgin_get_group_title(node
, TRUE
);
1173 gtk_tree_store_set(gtkblist
->treemodel
, iter
,
1179 purple_blist_node_set_bool(node
, "collapsed", FALSE
);
1180 pidgin_blist_tooltip_destroy();
1184 static void gtk_blist_row_collapsed_cb(GtkTreeView
*tv
, GtkTreeIter
*iter
, GtkTreePath
*path
, gpointer user_data
)
1186 PidginBuddyList
*gtkblist
= PIDGIN_BUDDY_LIST(user_data
);
1187 PurpleBlistNode
*node
;
1189 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), iter
, NODE_COLUMN
, &node
, -1);
1191 if (PURPLE_IS_GROUP(node
)) {
1193 PidginBlistNode
*gtknode
;
1194 PurpleBlistNode
*cnode
;
1196 title
= pidgin_get_group_title(node
, FALSE
);
1198 gtk_tree_store_set(gtkblist
->treemodel
, iter
,
1204 purple_blist_node_set_bool(node
, "collapsed", TRUE
);
1206 for(cnode
= purple_blist_node_get_first_child(node
); cnode
; cnode
= purple_blist_node_get_sibling_next(cnode
)) {
1207 if (PURPLE_IS_CONTACT(cnode
)) {
1208 gtknode
= purple_blist_node_get_ui_data(cnode
);
1209 if (!gtknode
->contact_expanded
)
1211 gtknode
->contact_expanded
= FALSE
;
1212 pidgin_blist_update_contact(NULL
, cnode
);
1215 pidgin_blist_tooltip_destroy();
1216 } else if(PURPLE_IS_CONTACT(node
)) {
1217 pidgin_blist_collapse_contact_cb(NULL
, node
);
1221 static void gtk_blist_row_activated_cb(GtkTreeView
*tv
, GtkTreePath
*path
, GtkTreeViewColumn
*col
, gpointer data
) {
1222 PidginBuddyList
*gtkblist
= PIDGIN_BUDDY_LIST(data
);
1223 PurpleBlistNode
*node
;
1226 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, path
);
1227 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, NODE_COLUMN
, &node
, -1);
1229 if(PURPLE_IS_CONTACT(node
) || PURPLE_IS_BUDDY(node
)) {
1232 if(PURPLE_IS_CONTACT(node
))
1233 buddy
= purple_contact_get_priority_buddy((PurpleContact
*)node
);
1235 buddy
= (PurpleBuddy
*)node
;
1237 pidgin_dialogs_im_with_user(purple_buddy_get_account(buddy
), purple_buddy_get_name(buddy
));
1238 } else if (PURPLE_IS_CHAT(node
)) {
1239 gtk_blist_join_chat((PurpleChat
*)node
);
1240 } else if (PURPLE_IS_GROUP(node
)) {
1241 /* if (gtk_tree_view_row_expanded(tv, path))
1242 gtk_tree_view_collapse_row(tv, path);
1244 gtk_tree_view_expand_row(tv,path,FALSE);*/
1248 static void pidgin_blist_add_chat_cb(void)
1250 GtkTreeSelection
*sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist
->treeview
));
1252 PurpleBlistNode
*node
;
1254 if(gtk_tree_selection_get_selected(sel
, NULL
, &iter
)){
1255 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, NODE_COLUMN
, &node
, -1);
1256 if (PURPLE_IS_BUDDY(node
))
1257 purple_blist_request_add_chat(NULL
, purple_buddy_get_group(PURPLE_BUDDY(node
)), NULL
, NULL
);
1258 if (PURPLE_IS_CONTACT(node
) || PURPLE_IS_CHAT(node
))
1259 purple_blist_request_add_chat(NULL
, purple_contact_get_group(PURPLE_CONTACT(node
)), NULL
, NULL
);
1260 else if (PURPLE_IS_GROUP(node
))
1261 purple_blist_request_add_chat(NULL
, (PurpleGroup
*)node
, NULL
, NULL
);
1264 purple_blist_request_add_chat(NULL
, NULL
, NULL
, NULL
);
1268 static void pidgin_blist_add_buddy_cb(void)
1270 GtkTreeSelection
*sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist
->treeview
));
1272 PurpleBlistNode
*node
;
1274 if(gtk_tree_selection_get_selected(sel
, NULL
, &iter
)){
1275 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, NODE_COLUMN
, &node
, -1);
1276 if (PURPLE_IS_BUDDY(node
)) {
1277 PurpleGroup
*group
= purple_buddy_get_group(PURPLE_BUDDY(node
));
1278 purple_blist_request_add_buddy(NULL
, NULL
, purple_group_get_name(group
), NULL
);
1279 } else if (PURPLE_IS_CONTACT(node
) || PURPLE_IS_CHAT(node
)) {
1280 PurpleGroup
*group
= purple_contact_get_group(PURPLE_CONTACT(node
));
1281 purple_blist_request_add_buddy(NULL
, NULL
, purple_group_get_name(group
), NULL
);
1282 } else if (PURPLE_IS_GROUP(node
)) {
1283 purple_blist_request_add_buddy(NULL
, NULL
, purple_group_get_name(PURPLE_GROUP(node
)), NULL
);
1287 purple_blist_request_add_buddy(NULL
, NULL
, NULL
, NULL
);
1292 pidgin_blist_remove_cb (GtkWidget
*w
, PurpleBlistNode
*node
)
1294 if (PURPLE_IS_BUDDY(node
)) {
1295 pidgin_dialogs_remove_buddy((PurpleBuddy
*)node
);
1296 } else if (PURPLE_IS_CHAT(node
)) {
1297 pidgin_dialogs_remove_chat((PurpleChat
*)node
);
1298 } else if (PURPLE_IS_GROUP(node
)) {
1299 pidgin_dialogs_remove_group((PurpleGroup
*)node
);
1300 } else if (PURPLE_IS_CONTACT(node
)) {
1301 pidgin_dialogs_remove_contact((PurpleContact
*)node
);
1306 GtkTreeView
*treeview
;
1308 PurpleBlistNode
*node
;
1312 scroll_to_expanded_cell(gpointer data
)
1314 struct _expand
*ex
= data
;
1315 gtk_tree_view_scroll_to_cell(ex
->treeview
, ex
->path
, NULL
, FALSE
, 0, 0);
1316 pidgin_blist_update_contact(NULL
, ex
->node
);
1318 gtk_tree_path_free(ex
->path
);
1325 pidgin_blist_expand_contact_cb(GtkWidget
*w
, PurpleBlistNode
*node
)
1327 PidginBlistNode
*gtknode
;
1328 GtkTreeIter iter
, parent
;
1329 PurpleBlistNode
*bnode
;
1332 if(!PURPLE_IS_CONTACT(node
))
1335 gtknode
= purple_blist_node_get_ui_data(node
);
1337 gtknode
->contact_expanded
= TRUE
;
1339 for(bnode
= purple_blist_node_get_first_child(node
); bnode
; bnode
= purple_blist_node_get_sibling_next(bnode
)) {
1340 pidgin_blist_update(NULL
, bnode
);
1343 /* This ensures that the bottom buddy is visible, i.e. not scrolled off the alignment */
1344 if (get_iter_from_node(node
, &parent
)) {
1345 struct _expand
*ex
= g_new0(struct _expand
, 1);
1347 gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, &parent
,
1348 gtk_tree_model_iter_n_children(GTK_TREE_MODEL(gtkblist
->treemodel
), &parent
) -1);
1349 path
= gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
);
1351 /* Let the treeview draw so it knows where to scroll */
1352 ex
->treeview
= GTK_TREE_VIEW(gtkblist
->treeview
);
1354 ex
->node
= purple_blist_node_get_first_child(node
);
1355 g_idle_add(scroll_to_expanded_cell
, ex
);
1360 pidgin_blist_collapse_contact_cb(GtkWidget
*w
, PurpleBlistNode
*node
)
1362 PurpleBlistNode
*bnode
;
1363 PidginBlistNode
*gtknode
;
1365 if(!PURPLE_IS_CONTACT(node
))
1368 gtknode
= purple_blist_node_get_ui_data(node
);
1370 gtknode
->contact_expanded
= FALSE
;
1372 for(bnode
= purple_blist_node_get_first_child(node
); bnode
; bnode
= purple_blist_node_get_sibling_next(bnode
)) {
1373 pidgin_blist_update(NULL
, bnode
);
1378 toggle_privacy(GtkWidget
*widget
, PurpleBlistNode
*node
)
1381 PurpleAccount
*account
;
1385 if (!PURPLE_IS_BUDDY(node
))
1388 buddy
= (PurpleBuddy
*)node
;
1389 account
= purple_buddy_get_account(buddy
);
1390 name
= purple_buddy_get_name(buddy
);
1392 permitted
= purple_account_privacy_check(account
, name
);
1394 /* XXX: Perhaps ask whether to restore the previous lists where appropirate? */
1397 purple_account_privacy_deny(account
, name
);
1399 purple_account_privacy_allow(account
, name
);
1401 pidgin_blist_update(purple_blist_get_default(), node
);
1404 void pidgin_append_blist_node_privacy_menu(GtkWidget
*menu
, PurpleBlistNode
*node
)
1406 PurpleBuddy
*buddy
= (PurpleBuddy
*)node
;
1407 PurpleAccount
*account
;
1410 account
= purple_buddy_get_account(buddy
);
1411 permitted
= purple_account_privacy_check(account
, purple_buddy_get_name(buddy
));
1413 pidgin_new_menu_item(menu
, permitted
? _("_Block") : _("Un_block"),
1414 permitted
? PIDGIN_STOCK_TOOLBAR_BLOCK
: PIDGIN_STOCK_TOOLBAR_UNBLOCK
,
1415 G_CALLBACK(toggle_privacy
), node
);
1419 pidgin_append_blist_node_proto_menu(GtkWidget
*menu
, PurpleConnection
*gc
,
1420 PurpleBlistNode
*node
)
1423 PurpleProtocol
*protocol
= purple_connection_get_protocol(gc
);
1425 if(!protocol
|| !PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CLIENT
, blist_node_menu
))
1428 for(l
= ll
= purple_protocol_client_iface_blist_node_menu(protocol
, node
); l
; l
= l
->next
) {
1429 PurpleActionMenu
*act
= (PurpleActionMenu
*) l
->data
;
1430 pidgin_append_menu_action(menu
, act
, node
);
1436 pidgin_append_blist_node_extended_menu(GtkWidget
*menu
, PurpleBlistNode
*node
)
1440 for(l
= ll
= purple_blist_node_get_extended_menu(node
); l
; l
= l
->next
) {
1441 PurpleActionMenu
*act
= (PurpleActionMenu
*) l
->data
;
1442 pidgin_append_menu_action(menu
, act
, node
);
1450 pidgin_append_blist_node_move_to_menu(GtkWidget
*menu
, PurpleBlistNode
*node
)
1453 GtkWidget
*menuitem
;
1454 PurpleBlistNode
*group
;
1456 menuitem
= gtk_menu_item_new_with_label(_("Move to"));
1457 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
1458 gtk_widget_show(menuitem
);
1460 submenu
= gtk_menu_new();
1461 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem
), submenu
);
1463 for (group
= purple_blist_get_default_root(); group
;
1464 group
= purple_blist_node_get_sibling_next(group
)) {
1465 if (!PURPLE_IS_GROUP(group
))
1467 if (group
== purple_blist_node_get_parent(node
))
1469 menuitem
= pidgin_new_menu_item(submenu
,
1470 purple_group_get_name((PurpleGroup
*)group
), NULL
,
1471 G_CALLBACK(gtk_blist_menu_move_to_cb
), node
);
1472 g_object_set_data(G_OBJECT(menuitem
), "groupnode", group
);
1474 gtk_widget_show_all(submenu
);
1478 pidgin_blist_make_buddy_menu(GtkWidget
*menu
, PurpleBuddy
*buddy
, gboolean sub
) {
1479 PurpleAccount
*account
= NULL
;
1480 PurpleConnection
*pc
= NULL
;
1481 PurpleProtocol
*protocol
;
1482 PurpleContact
*contact
;
1483 PurpleBlistNode
*node
;
1484 gboolean contact_expanded
= FALSE
;
1486 g_return_if_fail(menu
);
1487 g_return_if_fail(buddy
);
1489 account
= purple_buddy_get_account(buddy
);
1490 pc
= purple_account_get_connection(account
);
1491 protocol
= purple_connection_get_protocol(pc
);
1493 node
= PURPLE_BLIST_NODE(buddy
);
1495 contact
= purple_buddy_get_contact(buddy
);
1497 PidginBlistNode
*node
= purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(contact
));
1498 contact_expanded
= node
->contact_expanded
;
1501 if (protocol
&& PURPLE_PROTOCOL_IMPLEMENTS(protocol
, SERVER
, get_info
)) {
1502 pidgin_new_menu_item(menu
, _("Get _Info"), PIDGIN_STOCK_TOOLBAR_USER_INFO
,
1503 G_CALLBACK(gtk_blist_menu_info_cb
), buddy
);
1505 pidgin_new_menu_item(menu
, _("I_M"), PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW
,
1506 G_CALLBACK(gtk_blist_menu_im_cb
), buddy
);
1509 if (protocol
&& PURPLE_PROTOCOL_IMPLEMENTS(protocol
, MEDIA
, get_caps
)) {
1510 PurpleAccount
*account
= purple_buddy_get_account(buddy
);
1511 const gchar
*who
= purple_buddy_get_name(buddy
);
1512 PurpleMediaCaps caps
= purple_protocol_get_media_caps(account
, who
);
1513 if (caps
& PURPLE_MEDIA_CAPS_AUDIO
) {
1514 pidgin_new_menu_item(menu
, _("_Audio Call"),
1515 PIDGIN_STOCK_TOOLBAR_AUDIO_CALL
,
1516 G_CALLBACK(gtk_blist_menu_audio_call_cb
), buddy
);
1518 if (caps
& PURPLE_MEDIA_CAPS_AUDIO_VIDEO
) {
1519 pidgin_new_menu_item(menu
, _("Audio/_Video Call"),
1520 PIDGIN_STOCK_TOOLBAR_VIDEO_CALL
,
1521 G_CALLBACK(gtk_blist_menu_video_call_cb
), buddy
);
1522 } else if (caps
& PURPLE_MEDIA_CAPS_VIDEO
) {
1523 pidgin_new_menu_item(menu
, _("_Video Call"),
1524 PIDGIN_STOCK_TOOLBAR_VIDEO_CALL
,
1525 G_CALLBACK(gtk_blist_menu_video_call_cb
), buddy
);
1531 if (protocol
&& PURPLE_IS_PROTOCOL_XFER(protocol
)) {
1532 if (purple_protocol_xfer_can_receive(
1533 PURPLE_PROTOCOL_XFER(protocol
),
1534 purple_account_get_connection(purple_buddy_get_account(buddy
)), purple_buddy_get_name(buddy
)
1536 pidgin_new_menu_item(menu
, _("_Send File..."),
1537 PIDGIN_STOCK_TOOLBAR_SEND_FILE
,
1538 G_CALLBACK(gtk_blist_menu_send_file_cb
),
1543 pidgin_new_menu_item(menu
, _("Add Buddy _Pounce..."), NULL
,
1544 G_CALLBACK(gtk_blist_menu_bp_cb
), buddy
);
1546 if (node
->parent
&& node
->parent
->child
->next
&&
1547 !sub
&& !contact_expanded
) {
1548 pidgin_new_menu_item(menu
, _("View _Log"), NULL
,
1549 G_CALLBACK(gtk_blist_menu_showlog_cb
), contact
);
1551 pidgin_new_menu_item(menu
, _("View _Log"), NULL
,
1552 G_CALLBACK(gtk_blist_menu_showlog_cb
), buddy
);
1555 if (!purple_blist_node_is_transient(node
)) {
1556 gboolean show_offline
= purple_blist_node_get_bool(node
, "show_offline");
1557 pidgin_new_menu_item(menu
,
1558 show_offline
? _("Hide When Offline") : _("Show When Offline"),
1559 NULL
, G_CALLBACK(gtk_blist_menu_showoffline_cb
), node
);
1562 pidgin_append_blist_node_proto_menu(menu
, purple_account_get_connection(purple_buddy_get_account(buddy
)), node
);
1563 pidgin_append_blist_node_extended_menu(menu
, node
);
1565 if (!contact_expanded
&& contact
!= NULL
)
1566 pidgin_append_blist_node_move_to_menu(menu
, PURPLE_BLIST_NODE(contact
));
1568 if (node
->parent
&& node
->parent
->child
->next
&&
1569 !sub
&& !contact_expanded
) {
1570 pidgin_separator(menu
);
1571 pidgin_append_blist_node_privacy_menu(menu
, node
);
1572 pidgin_new_menu_item(menu
, _("_Alias..."), PIDGIN_STOCK_ALIAS
,
1573 G_CALLBACK(gtk_blist_menu_alias_cb
), contact
);
1574 pidgin_new_menu_item(menu
, _("_Remove"), GTK_STOCK_REMOVE
,
1575 G_CALLBACK(pidgin_blist_remove_cb
), contact
);
1576 } else if (!sub
|| contact_expanded
) {
1577 pidgin_separator(menu
);
1578 pidgin_append_blist_node_privacy_menu(menu
, node
);
1579 pidgin_new_menu_item(menu
, _("_Alias..."), PIDGIN_STOCK_ALIAS
,
1580 G_CALLBACK(gtk_blist_menu_alias_cb
), buddy
);
1581 pidgin_new_menu_item(menu
, _("_Remove"), GTK_STOCK_REMOVE
,
1582 G_CALLBACK(pidgin_blist_remove_cb
), buddy
);
1587 gtk_blist_key_press_cb(GtkWidget
*tv
, GdkEventKey
*event
, gpointer data
)
1589 PurpleBlistNode
*node
;
1590 GtkTreeIter iter
, parent
;
1591 GtkTreeSelection
*sel
;
1594 sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(tv
));
1595 if(!gtk_tree_selection_get_selected(sel
, NULL
, &iter
))
1598 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, NODE_COLUMN
, &node
, -1);
1600 if(event
->state
& GDK_CONTROL_MASK
&&
1601 (event
->keyval
== 'o' || event
->keyval
== 'O')) {
1604 if(PURPLE_IS_CONTACT(node
)) {
1605 buddy
= purple_contact_get_priority_buddy((PurpleContact
*)node
);
1606 } else if(PURPLE_IS_BUDDY(node
)) {
1607 buddy
= (PurpleBuddy
*)node
;
1612 pidgin_retrieve_user_info(purple_account_get_connection(purple_buddy_get_account(buddy
)), purple_buddy_get_name(buddy
));
1614 switch (event
->keyval
) {
1616 gtk_blist_menu_alias_cb(tv
, node
);
1620 path
= gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
);
1621 if (gtk_tree_view_row_expanded(GTK_TREE_VIEW(tv
), path
)) {
1622 /* Collapse the Group */
1623 gtk_tree_view_collapse_row(GTK_TREE_VIEW(tv
), path
);
1624 gtk_tree_path_free(path
);
1627 /* Select the Parent */
1628 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, path
)) {
1629 if (gtk_tree_model_iter_parent(GTK_TREE_MODEL(gtkblist
->treemodel
), &parent
, &iter
)) {
1630 gtk_tree_path_free(path
);
1631 path
= gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist
->treemodel
), &parent
);
1632 gtk_tree_view_set_cursor(GTK_TREE_VIEW(tv
), path
, NULL
, FALSE
);
1633 gtk_tree_path_free(path
);
1638 gtk_tree_path_free(path
);
1642 path
= gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
);
1643 if (!gtk_tree_view_row_expanded(GTK_TREE_VIEW(tv
), path
)) {
1644 /* Expand the Group */
1645 if (PURPLE_IS_CONTACT(node
)) {
1646 pidgin_blist_expand_contact_cb(NULL
, node
);
1647 gtk_tree_path_free(path
);
1649 } else if (!PURPLE_IS_BUDDY(node
)) {
1650 gtk_tree_view_expand_row(GTK_TREE_VIEW(tv
), path
, FALSE
);
1651 gtk_tree_path_free(path
);
1655 /* Select the First Child */
1656 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
), &parent
, path
)) {
1657 if (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, &parent
, 0)) {
1658 gtk_tree_path_free(path
);
1659 path
= gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
);
1660 gtk_tree_view_set_cursor(GTK_TREE_VIEW(tv
), path
, NULL
, FALSE
);
1661 gtk_tree_path_free(path
);
1666 gtk_tree_path_free(path
);
1675 set_node_custom_icon_cb(const gchar
*filename
, gpointer data
)
1678 PurpleBlistNode
*node
= (PurpleBlistNode
*)data
;
1680 purple_buddy_icons_node_set_custom_icon_from_file(node
,
1683 g_object_set_data(G_OBJECT(data
), "buddy-icon-chooser", NULL
);
1687 set_node_custom_icon(GtkWidget
*w
, PurpleBlistNode
*node
)
1689 GtkWidget
*win
= g_object_get_data(G_OBJECT(node
), "buddy-icon-chooser");
1691 win
= pidgin_buddy_icon_chooser_new(NULL
, set_node_custom_icon_cb
,
1693 g_object_set_data_full(G_OBJECT(node
), "buddy-icon-chooser", win
,
1694 (GDestroyNotify
)gtk_widget_destroy
);
1696 gtk_widget_show_all(win
);
1700 remove_node_custom_icon(GtkWidget
*w
, PurpleBlistNode
*node
)
1702 purple_buddy_icons_node_set_custom_icon(node
, NULL
, 0);
1706 add_buddy_icon_menu_items(GtkWidget
*menu
, PurpleBlistNode
*node
)
1710 pidgin_new_menu_item(menu
, _("Set Custom Icon"), NULL
,
1711 G_CALLBACK(set_node_custom_icon
), node
);
1713 item
= pidgin_new_menu_item(menu
, _("Remove Custom Icon"), NULL
,
1714 G_CALLBACK(remove_node_custom_icon
), node
);
1715 if (!purple_buddy_icons_node_has_custom_icon(node
))
1716 gtk_widget_set_sensitive(item
, FALSE
);
1720 create_group_menu (PurpleBlistNode
*node
, PurpleGroup
*g
)
1725 menu
= gtk_menu_new();
1726 item
= pidgin_new_menu_item(menu
, _("Add _Buddy..."), GTK_STOCK_ADD
,
1727 G_CALLBACK(pidgin_blist_add_buddy_cb
), node
);
1728 gtk_widget_set_sensitive(item
, purple_connections_get_all() != NULL
);
1729 item
= pidgin_new_menu_item(menu
, _("Add C_hat..."), GTK_STOCK_ADD
,
1730 G_CALLBACK(pidgin_blist_add_chat_cb
), node
);
1731 gtk_widget_set_sensitive(item
, pidgin_blist_joinchat_is_showable());
1732 pidgin_new_menu_item(menu
, _("_Delete Group"), GTK_STOCK_REMOVE
,
1733 G_CALLBACK(pidgin_blist_remove_cb
), node
);
1734 pidgin_new_menu_item(menu
, _("_Rename"), NULL
,
1735 G_CALLBACK(gtk_blist_menu_alias_cb
), node
);
1736 if (!purple_blist_node_is_transient(node
)) {
1737 gboolean show_offline
= purple_blist_node_get_bool(node
, "show_offline");
1738 pidgin_new_menu_item(menu
,
1739 show_offline
? _("Hide When Offline") : _("Show When Offline"),
1740 NULL
, G_CALLBACK(gtk_blist_menu_showoffline_cb
), node
);
1743 add_buddy_icon_menu_items(menu
, node
);
1745 pidgin_append_blist_node_extended_menu(menu
, node
);
1751 create_chat_menu(PurpleBlistNode
*node
, PurpleChat
*c
)
1754 gboolean autojoin
, persistent
;
1756 menu
= gtk_menu_new();
1757 autojoin
= purple_blist_node_get_bool(node
, "gtk-autojoin");
1758 persistent
= purple_blist_node_get_bool(node
, "gtk-persistent");
1760 pidgin_new_menu_item(menu
, _("_Join"), PIDGIN_STOCK_CHAT
,
1761 G_CALLBACK(gtk_blist_menu_join_cb
), node
);
1762 pidgin_new_check_item(menu
, _("Auto-Join"),
1763 G_CALLBACK(gtk_blist_menu_autojoin_cb
), node
, autojoin
);
1764 pidgin_new_check_item(menu
, _("Persistent"),
1765 G_CALLBACK(gtk_blist_menu_persistent_cb
), node
, persistent
);
1766 pidgin_new_menu_item(menu
, _("View _Log"), NULL
,
1767 G_CALLBACK(gtk_blist_menu_showlog_cb
), node
);
1769 pidgin_append_blist_node_proto_menu(menu
, purple_account_get_connection(purple_chat_get_account(c
)), node
);
1770 pidgin_append_blist_node_extended_menu(menu
, node
);
1772 pidgin_separator(menu
);
1774 pidgin_new_menu_item(menu
, _("_Edit Settings..."), NULL
,
1775 G_CALLBACK(chat_components_edit
), node
);
1776 pidgin_new_menu_item(menu
, _("_Alias..."), PIDGIN_STOCK_ALIAS
,
1777 G_CALLBACK(gtk_blist_menu_alias_cb
), node
);
1778 pidgin_new_menu_item(menu
, _("_Remove"), GTK_STOCK_REMOVE
,
1779 G_CALLBACK(pidgin_blist_remove_cb
), node
);
1781 add_buddy_icon_menu_items(menu
, node
);
1787 create_contact_menu (PurpleBlistNode
*node
)
1791 menu
= gtk_menu_new();
1793 pidgin_new_menu_item(menu
, _("View _Log"), NULL
,
1794 G_CALLBACK(gtk_blist_menu_showlog_cb
),
1797 pidgin_separator(menu
);
1799 pidgin_new_menu_item(menu
, _("_Alias..."), PIDGIN_STOCK_ALIAS
,
1800 G_CALLBACK(gtk_blist_menu_alias_cb
), node
);
1801 pidgin_new_menu_item(menu
, _("_Remove"), GTK_STOCK_REMOVE
,
1802 G_CALLBACK(pidgin_blist_remove_cb
), node
);
1804 add_buddy_icon_menu_items(menu
, node
);
1806 pidgin_separator(menu
);
1808 pidgin_new_menu_item(menu
, _("_Collapse"), GTK_STOCK_ZOOM_OUT
,
1809 G_CALLBACK(pidgin_blist_collapse_contact_cb
),
1812 pidgin_append_blist_node_extended_menu(menu
, node
);
1817 create_buddy_menu(PurpleBlistNode
*node
, PurpleBuddy
*b
)
1819 PidginBlistNode
*gtknode
= purple_blist_node_get_ui_data(node
);
1821 GtkWidget
*menuitem
;
1822 gboolean show_offline
= purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_offline_buddies");
1824 menu
= gtk_menu_new();
1825 pidgin_blist_make_buddy_menu(menu
, b
, FALSE
);
1827 if(PURPLE_IS_CONTACT(node
)) {
1828 pidgin_separator(menu
);
1830 add_buddy_icon_menu_items(menu
, node
);
1832 if(gtknode
->contact_expanded
) {
1833 pidgin_new_menu_item(menu
, _("_Collapse"),
1835 G_CALLBACK(pidgin_blist_collapse_contact_cb
),
1838 pidgin_new_menu_item(menu
, _("_Expand"),
1840 G_CALLBACK(pidgin_blist_expand_contact_cb
),
1843 if(node
->child
->next
) {
1844 PurpleBlistNode
*bnode
;
1846 for(bnode
= node
->child
; bnode
; bnode
= bnode
->next
) {
1847 PurpleBuddy
*buddy
= (PurpleBuddy
*)bnode
;
1854 if(!purple_account_get_connection(purple_buddy_get_account(buddy
)))
1856 if(!show_offline
&& !PURPLE_BUDDY_IS_ONLINE(buddy
))
1859 menuitem
= gtk_image_menu_item_new_with_label(purple_buddy_get_name(buddy
));
1860 buf
= pidgin_create_protocol_icon(purple_buddy_get_account(buddy
), PIDGIN_PROTOCOL_ICON_SMALL
);
1861 image
= gtk_image_new_from_pixbuf(buf
);
1862 g_object_unref(G_OBJECT(buf
));
1863 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem
),
1865 gtk_widget_show(image
);
1866 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
1867 gtk_widget_show(menuitem
);
1869 submenu
= gtk_menu_new();
1870 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem
), submenu
);
1871 gtk_widget_show(submenu
);
1873 pidgin_blist_make_buddy_menu(submenu
, buddy
, TRUE
);
1881 pidgin_blist_show_context_menu(GtkWidget
*tv
, PurpleBlistNode
*node
, GdkEvent
*event
)
1883 PidginBlistNode
*gtknode
= purple_blist_node_get_ui_data(node
);
1884 GtkWidget
*menu
= NULL
;
1885 gboolean handled
= FALSE
;
1887 /* Create a menu based on the thing we right-clicked on */
1888 if (PURPLE_IS_GROUP(node
)) {
1889 PurpleGroup
*g
= (PurpleGroup
*)node
;
1891 menu
= create_group_menu(node
, g
);
1892 } else if (PURPLE_IS_CHAT(node
)) {
1893 PurpleChat
*c
= (PurpleChat
*)node
;
1895 menu
= create_chat_menu(node
, c
);
1896 } else if ((PURPLE_IS_CONTACT(node
)) && (gtknode
->contact_expanded
)) {
1897 menu
= create_contact_menu(node
);
1898 } else if (PURPLE_IS_CONTACT(node
) || PURPLE_IS_BUDDY(node
)) {
1901 if (PURPLE_IS_CONTACT(node
))
1902 b
= purple_contact_get_priority_buddy((PurpleContact
*)node
);
1904 b
= (PurpleBuddy
*)node
;
1906 menu
= create_buddy_menu(node
, b
);
1910 pidgin_blist_tooltip_destroy();
1912 /* Unhook the tooltip-timeout since we don't want a tooltip
1913 * to appear and obscure the context menu we are about to show
1914 This is a workaround for GTK+ bug 107320. */
1915 if (gtkblist
->timeout
) {
1916 g_source_remove(gtkblist
->timeout
);
1917 gtkblist
->timeout
= 0;
1921 /* Now display the menu */
1923 gtk_widget_show_all(menu
);
1924 if (event
!= NULL
) {
1926 gtk_menu_popup_at_pointer(GTK_MENU(menu
), event
);
1928 /* Keyboard event */
1929 pidgin_menu_popup_at_treeview_selection(menu
, tv
);
1938 gtk_blist_button_press_cb(GtkWidget
*tv
, GdkEventButton
*event
, gpointer user_data
)
1941 PurpleBlistNode
*node
;
1943 GtkTreeSelection
*sel
;
1944 PurpleProtocol
*protocol
= NULL
;
1945 PidginBlistNode
*gtknode
;
1946 gboolean handled
= FALSE
;
1948 /* Here we figure out which node was clicked */
1949 if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv
), event
->x
, event
->y
, &path
, NULL
, NULL
, NULL
))
1951 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, path
);
1952 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, NODE_COLUMN
, &node
, -1);
1953 gtknode
= purple_blist_node_get_ui_data(node
);
1955 /* Right click draws a context menu */
1956 if (gdk_event_triggers_context_menu((GdkEvent
*)event
)) {
1957 handled
= pidgin_blist_show_context_menu(tv
, node
, (GdkEvent
*)event
);
1959 /* CTRL+middle click expands or collapse a contact */
1960 } else if ((event
->button
== GDK_BUTTON_MIDDLE
) && (event
->type
== GDK_BUTTON_PRESS
) &&
1961 (event
->state
& GDK_CONTROL_MASK
) && (PURPLE_IS_CONTACT(node
))) {
1962 if (gtknode
->contact_expanded
)
1963 pidgin_blist_collapse_contact_cb(NULL
, node
);
1965 pidgin_blist_expand_contact_cb(NULL
, node
);
1968 /* Double middle click gets info */
1969 } else if ((event
->button
== GDK_BUTTON_MIDDLE
) && (event
->type
== GDK_2BUTTON_PRESS
) &&
1970 ((PURPLE_IS_CONTACT(node
)) || (PURPLE_IS_BUDDY(node
)))) {
1972 if(PURPLE_IS_CONTACT(node
))
1973 b
= purple_contact_get_priority_buddy((PurpleContact
*)node
);
1975 b
= (PurpleBuddy
*)node
;
1977 protocol
= purple_protocols_find(purple_account_get_protocol_id(purple_buddy_get_account(b
)));
1979 if (protocol
&& PURPLE_PROTOCOL_IMPLEMENTS(protocol
, SERVER
, get_info
))
1980 pidgin_retrieve_user_info(purple_account_get_connection(purple_buddy_get_account(b
)), purple_buddy_get_name(b
));
1986 * This code only exists because GTK+ doesn't work. If we return
1987 * FALSE here, as would be normal the event propoagates down and
1988 * somehow gets interpreted as the start of a drag event.
1990 * Um, isn't it _normal_ to return TRUE here? Since the event
1991 * was handled? --Mark
1994 sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(tv
));
1995 gtk_tree_selection_select_path(sel
, path
);
1996 gtk_tree_path_free(path
);
2000 gtk_tree_path_free(path
);
2006 pidgin_blist_popup_menu_cb(GtkWidget
*tv
, void *user_data
)
2008 PurpleBlistNode
*node
;
2010 GtkTreeSelection
*sel
;
2011 gboolean handled
= FALSE
;
2013 sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(tv
));
2014 if (!gtk_tree_selection_get_selected(sel
, NULL
, &iter
))
2017 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, NODE_COLUMN
, &node
, -1);
2019 /* Shift+F10 draws a context menu */
2020 handled
= pidgin_blist_show_context_menu(tv
, node
, NULL
);
2025 static void gtk_blist_show_xfer_dialog_cb(GtkAction
*item
, gpointer data
)
2027 pidgin_xfer_dialog_show(NULL
);
2030 static void pidgin_blist_buddy_details_cb(GtkToggleAction
*item
, gpointer data
)
2032 pidgin_set_cursor(gtkblist
->window
, GDK_WATCH
);
2034 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/blist/show_buddy_icons",
2035 gtk_toggle_action_get_active(item
));
2037 pidgin_clear_cursor(gtkblist
->window
);
2040 static void pidgin_blist_show_idle_time_cb(GtkToggleAction
*item
, gpointer data
)
2042 pidgin_set_cursor(gtkblist
->window
, GDK_WATCH
);
2044 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/blist/show_idle_time",
2045 gtk_toggle_action_get_active(item
));
2047 pidgin_clear_cursor(gtkblist
->window
);
2050 static void pidgin_blist_show_protocol_icons_cb(GtkToggleAction
*item
, gpointer data
)
2052 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/blist/show_protocol_icons",
2053 gtk_toggle_action_get_active(item
));
2056 static void pidgin_blist_show_empty_groups_cb(GtkToggleAction
*item
, gpointer data
)
2058 pidgin_set_cursor(gtkblist
->window
, GDK_WATCH
);
2060 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/blist/show_empty_groups",
2061 gtk_toggle_action_get_active(item
));
2063 pidgin_clear_cursor(gtkblist
->window
);
2066 static void pidgin_blist_edit_mode_cb(GtkToggleAction
*checkitem
, gpointer data
)
2068 pidgin_set_cursor(gtkblist
->window
, GDK_WATCH
);
2070 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/blist/show_offline_buddies",
2071 gtk_toggle_action_get_active(checkitem
));
2073 pidgin_clear_cursor(gtkblist
->window
);
2076 static void pidgin_blist_mute_sounds_cb(GtkToggleAction
*item
, gpointer data
)
2078 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/sound/mute",
2079 gtk_toggle_action_get_active(item
));
2084 pidgin_blist_mute_pref_cb(const char *name
, PurplePrefType type
,
2085 gconstpointer value
, gpointer data
)
2087 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(gtk_ui_manager_get_action(gtkblist
->ui
,
2088 "/BList/ToolsMenu/MuteSounds")), (gboolean
)GPOINTER_TO_INT(value
));
2092 pidgin_blist_sound_method_pref_cb(const char *name
, PurplePrefType type
,
2093 gconstpointer value
, gpointer data
)
2095 gboolean sensitive
= TRUE
;
2097 if(purple_strequal(value
, "none"))
2100 gtk_action_set_sensitive(gtk_ui_manager_get_action(gtkblist
->ui
, "/BList/ToolsMenu/MuteSounds"), sensitive
);
2104 add_buddies_from_vcard(const char *protocol_id
, PurpleGroup
*group
, GList
*list
,
2108 PurpleAccount
*account
= NULL
;
2109 PurpleConnection
*gc
;
2114 for (l
= purple_connections_get_all(); l
!= NULL
; l
= l
->next
)
2116 gc
= (PurpleConnection
*)l
->data
;
2117 account
= purple_connection_get_account(gc
);
2119 if (purple_strequal(purple_account_get_protocol_id(account
), protocol_id
))
2125 if (account
!= NULL
)
2127 for (l
= list
; l
!= NULL
; l
= l
->next
)
2129 purple_blist_request_add_buddy(account
, l
->data
,
2130 (group
? purple_group_get_name(group
) : NULL
),
2135 g_list_foreach(list
, (GFunc
)g_free
, NULL
);
2140 parse_vcard(const char *vcard
, PurpleGroup
*group
)
2147 GList
*jabbers
= NULL
;
2149 s
= temp_vcard
= g_strdup(vcard
);
2151 while (*s
!= '\0' && strncmp(s
, "END:vCard", strlen("END:vCard")))
2153 char *field
, *value
;
2157 /* Grab the field */
2158 while (*s
!= '\r' && *s
!= '\n' && *s
!= '\0' && *s
!= ':')
2161 if (*s
== '\r') s
++;
2168 if (*s
!= '\0') *s
++ = '\0';
2170 if ((c
= strchr(field
, ';')) != NULL
)
2173 /* Proceed to the end of the line */
2176 while (*s
!= '\r' && *s
!= '\n' && *s
!= '\0')
2179 if (*s
== '\r') *s
++ = '\0';
2180 if (*s
== '\n') *s
++ = '\0';
2182 /* We only want to worry about a few fields here. */
2183 if (purple_strequal(field
, "FN"))
2184 alias
= g_strdup(value
);
2185 else if (purple_strequal(field
, "X-AIM") || purple_strequal(field
, "X-ICQ") ||
2186 purple_strequal(field
, "X-JABBER"))
2188 char **values
= g_strsplit(value
, ":", 0);
2191 for (im
= values
; *im
!= NULL
; im
++)
2193 if (purple_strequal(field
, "X-AIM"))
2194 aims
= g_list_append(aims
, g_strdup(*im
));
2195 else if (purple_strequal(field
, "X-ICQ"))
2196 icqs
= g_list_append(icqs
, g_strdup(*im
));
2197 else if (purple_strequal(field
, "X-JABBER"))
2198 jabbers
= g_list_append(jabbers
, g_strdup(*im
));
2207 if (aims
== NULL
&& icqs
== NULL
&& jabbers
== NULL
)
2214 add_buddies_from_vcard("prpl-aim", group
, aims
, alias
);
2215 add_buddies_from_vcard("prpl-icq", group
, icqs
, alias
);
2216 add_buddies_from_vcard("prpl-jabber", group
, jabbers
, alias
);
2224 static void pidgin_blist_drag_begin(GtkWidget
*widget
,
2225 GdkDragContext
*drag_context
, gpointer user_data
)
2227 pidgin_blist_tooltip_destroy();
2230 /* Unhook the tooltip-timeout since we don't want a tooltip
2231 * to appear and obscure the dragging operation.
2232 * This is a workaround for GTK+ bug 107320. */
2233 if (gtkblist
->timeout
) {
2234 g_source_remove(gtkblist
->timeout
);
2235 gtkblist
->timeout
= 0;
2240 static void pidgin_blist_drag_data_get_cb(GtkWidget
*widget
,
2242 GtkSelectionData
*data
,
2247 GdkAtom target
= gtk_selection_data_get_target(data
);
2249 if (target
== gdk_atom_intern("PURPLE_BLIST_NODE", FALSE
)) {
2250 GtkTreeRowReference
*ref
= g_object_get_data(G_OBJECT(dc
), "gtk-tree-view-source-row");
2251 GtkTreePath
*sourcerow
= gtk_tree_row_reference_get_path(ref
);
2253 PurpleBlistNode
*node
= NULL
;
2256 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, sourcerow
);
2257 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, NODE_COLUMN
, &node
, -1);
2258 gtk_selection_data_set (data
,
2259 gdk_atom_intern ("PURPLE_BLIST_NODE", FALSE
),
2264 gtk_tree_path_free(sourcerow
);
2265 } else if (target
== gdk_atom_intern("application/x-im-contact", FALSE
)) {
2266 GtkTreeRowReference
*ref
;
2267 GtkTreePath
*sourcerow
;
2269 PurpleBlistNode
*node
= NULL
;
2271 PurpleConnection
*gc
;
2273 const char *protocol
;
2275 ref
= g_object_get_data(G_OBJECT(dc
), "gtk-tree-view-source-row");
2276 sourcerow
= gtk_tree_row_reference_get_path(ref
);
2281 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
,
2283 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, NODE_COLUMN
, &node
, -1);
2285 if (PURPLE_IS_CONTACT(node
))
2287 buddy
= purple_contact_get_priority_buddy((PurpleContact
*)node
);
2289 else if (!PURPLE_IS_BUDDY(node
))
2291 gtk_tree_path_free(sourcerow
);
2296 buddy
= (PurpleBuddy
*)node
;
2299 gc
= purple_account_get_connection(purple_buddy_get_account(buddy
));
2303 gtk_tree_path_free(sourcerow
);
2308 purple_protocol_class_list_icon(purple_connection_get_protocol(gc
),
2309 purple_buddy_get_account(buddy
), buddy
);
2311 str
= g_string_new(NULL
);
2312 g_string_printf(str
,
2313 "MIME-Version: 1.0\r\n"
2314 "Content-Type: application/x-im-contact\r\n"
2315 "X-IM-Protocol: %s\r\n"
2316 "X-IM-Username: %s\r\n",
2318 purple_buddy_get_name(buddy
));
2320 if (purple_buddy_get_local_alias(buddy
) != NULL
)
2322 g_string_append_printf(str
,
2323 "X-IM-Alias: %s\r\n",
2324 purple_buddy_get_local_alias(buddy
));
2327 g_string_append(str
, "\r\n");
2329 gtk_selection_data_set(data
,
2330 gdk_atom_intern("application/x-im-contact", FALSE
),
2332 (const guchar
*)str
->str
,
2333 strlen(str
->str
) + 1);
2335 g_string_free(str
, TRUE
);
2336 gtk_tree_path_free(sourcerow
);
2340 static void pidgin_blist_drag_data_rcv_cb(GtkWidget
*widget
, GdkDragContext
*dc
, guint x
, guint y
,
2341 GtkSelectionData
*sd
, guint info
, guint t
)
2343 GdkAtom target
= gtk_selection_data_get_target(sd
);
2344 const guchar
*data
= gtk_selection_data_get_data(sd
);
2346 if (gtkblist
->drag_timeout
) {
2347 g_source_remove(gtkblist
->drag_timeout
);
2348 gtkblist
->drag_timeout
= 0;
2351 if (target
== gdk_atom_intern("PURPLE_BLIST_NODE", FALSE
) && data
) {
2352 PurpleBlistNode
*n
= NULL
;
2353 GtkTreePath
*path
= NULL
;
2354 GtkTreeViewDropPosition position
;
2355 memcpy(&n
, data
, sizeof(n
));
2356 if(gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget
), x
, y
, &path
, &position
)) {
2357 /* if we're here, I think it means the drop is ok */
2359 PurpleBlistNode
*node
;
2360 PidginBlistNode
*gtknode
;
2362 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
),
2364 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
),
2365 &iter
, NODE_COLUMN
, &node
, -1);
2366 gtknode
= purple_blist_node_get_ui_data(node
);
2368 if (PURPLE_IS_CONTACT(n
)) {
2369 PurpleContact
*c
= (PurpleContact
*)n
;
2370 if (PURPLE_IS_CONTACT(node
) && gtknode
->contact_expanded
) {
2371 purple_contact_merge(c
, node
);
2372 } else if (PURPLE_IS_CONTACT(node
) ||
2373 PURPLE_IS_CHAT(node
)) {
2375 case GTK_TREE_VIEW_DROP_AFTER
:
2376 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER
:
2377 purple_blist_add_contact(c
, (PurpleGroup
*)node
->parent
,
2380 case GTK_TREE_VIEW_DROP_BEFORE
:
2381 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE
:
2382 purple_blist_add_contact(c
, (PurpleGroup
*)node
->parent
,
2386 } else if(PURPLE_IS_GROUP(node
)) {
2387 purple_blist_add_contact(c
, (PurpleGroup
*)node
, NULL
);
2388 } else if(PURPLE_IS_BUDDY(node
)) {
2389 purple_contact_merge(c
, node
);
2391 } else if (PURPLE_IS_BUDDY(n
)) {
2392 PurpleBuddy
*b
= (PurpleBuddy
*)n
;
2393 if (PURPLE_IS_BUDDY(node
)) {
2395 case GTK_TREE_VIEW_DROP_AFTER
:
2396 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER
:
2397 purple_blist_add_buddy(b
, (PurpleContact
*)node
->parent
,
2398 (PurpleGroup
*)node
->parent
->parent
, node
);
2400 case GTK_TREE_VIEW_DROP_BEFORE
:
2401 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE
:
2402 purple_blist_add_buddy(b
, (PurpleContact
*)node
->parent
,
2403 (PurpleGroup
*)node
->parent
->parent
,
2407 } else if(PURPLE_IS_CHAT(node
)) {
2408 purple_blist_add_buddy(b
, NULL
, (PurpleGroup
*)node
->parent
,
2410 } else if (PURPLE_IS_GROUP(node
)) {
2411 purple_blist_add_buddy(b
, NULL
, (PurpleGroup
*)node
, NULL
);
2412 } else if (PURPLE_IS_CONTACT(node
)) {
2413 if(gtknode
->contact_expanded
) {
2415 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER
:
2416 case GTK_TREE_VIEW_DROP_AFTER
:
2417 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE
:
2418 purple_blist_add_buddy(b
, (PurpleContact
*)node
,
2419 (PurpleGroup
*)node
->parent
, NULL
);
2421 case GTK_TREE_VIEW_DROP_BEFORE
:
2422 purple_blist_add_buddy(b
, NULL
,
2423 (PurpleGroup
*)node
->parent
, node
->prev
);
2428 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER
:
2429 case GTK_TREE_VIEW_DROP_AFTER
:
2430 purple_blist_add_buddy(b
, NULL
,
2431 (PurpleGroup
*)node
->parent
, NULL
);
2433 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE
:
2434 case GTK_TREE_VIEW_DROP_BEFORE
:
2435 purple_blist_add_buddy(b
, NULL
,
2436 (PurpleGroup
*)node
->parent
, node
->prev
);
2441 } else if (PURPLE_IS_CHAT(n
)) {
2442 PurpleChat
*chat
= (PurpleChat
*)n
;
2443 if (PURPLE_IS_BUDDY(node
)) {
2445 case GTK_TREE_VIEW_DROP_AFTER
:
2446 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER
:
2447 case GTK_TREE_VIEW_DROP_BEFORE
:
2448 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE
:
2449 purple_blist_add_chat(chat
,
2450 (PurpleGroup
*)node
->parent
->parent
,
2454 } else if(PURPLE_IS_CONTACT(node
) ||
2455 PURPLE_IS_CHAT(node
)) {
2457 case GTK_TREE_VIEW_DROP_AFTER
:
2458 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER
:
2459 purple_blist_add_chat(chat
, (PurpleGroup
*)node
->parent
, node
);
2461 case GTK_TREE_VIEW_DROP_BEFORE
:
2462 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE
:
2463 purple_blist_add_chat(chat
, (PurpleGroup
*)node
->parent
, node
->prev
);
2466 } else if (PURPLE_IS_GROUP(node
)) {
2467 purple_blist_add_chat(chat
, (PurpleGroup
*)node
, NULL
);
2469 } else if (PURPLE_IS_GROUP(n
)) {
2470 PurpleGroup
*g
= (PurpleGroup
*)n
;
2471 if (PURPLE_IS_GROUP(node
)) {
2473 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER
:
2474 case GTK_TREE_VIEW_DROP_AFTER
:
2475 purple_blist_add_group(g
, node
);
2477 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE
:
2478 case GTK_TREE_VIEW_DROP_BEFORE
:
2479 purple_blist_add_group(g
, node
->prev
);
2482 } else if(PURPLE_IS_BUDDY(node
)) {
2483 purple_blist_add_group(g
, node
->parent
->parent
);
2484 } else if(PURPLE_IS_CONTACT(node
) ||
2485 PURPLE_IS_CHAT(node
)) {
2486 purple_blist_add_group(g
, node
->parent
);
2490 gtk_tree_path_free(path
);
2491 gtk_drag_finish(dc
, TRUE
, (gdk_drag_context_get_actions(dc
) == GDK_ACTION_MOVE
), t
);
2493 } else if (target
== gdk_atom_intern("application/x-im-contact",
2495 PurpleGroup
*group
= NULL
;
2496 GtkTreePath
*path
= NULL
;
2497 GtkTreeViewDropPosition position
;
2498 PurpleAccount
*account
;
2499 char *protocol
= NULL
;
2500 char *username
= NULL
;
2503 if (gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget
),
2504 x
, y
, &path
, &position
))
2507 PurpleBlistNode
*node
;
2509 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
),
2511 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
),
2512 &iter
, NODE_COLUMN
, &node
, -1);
2514 if (PURPLE_IS_BUDDY(node
))
2516 group
= (PurpleGroup
*)node
->parent
->parent
;
2518 else if (PURPLE_IS_CHAT(node
) ||
2519 PURPLE_IS_CONTACT(node
))
2521 group
= (PurpleGroup
*)node
->parent
;
2523 else if (PURPLE_IS_GROUP(node
))
2525 group
= (PurpleGroup
*)node
;
2529 if (pidgin_parse_x_im_contact((const char *)data
, FALSE
, &account
,
2530 &protocol
, &username
, &alias
))
2532 if (account
== NULL
)
2534 purple_notify_error(NULL
, NULL
,
2535 _("You are not currently signed on with an account that "
2536 "can add that buddy."), NULL
, NULL
);
2540 purple_blist_request_add_buddy(account
, username
,
2541 (group
? purple_group_get_name(group
) : NULL
),
2551 gtk_tree_path_free(path
);
2553 gtk_drag_finish(dc
, TRUE
,
2554 gdk_drag_context_get_actions(dc
) == GDK_ACTION_MOVE
, t
);
2556 else if (target
== gdk_atom_intern("text/x-vcard", FALSE
) && data
)
2559 PurpleGroup
*group
= NULL
;
2560 GtkTreePath
*path
= NULL
;
2561 GtkTreeViewDropPosition position
;
2563 if (gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget
),
2564 x
, y
, &path
, &position
))
2567 PurpleBlistNode
*node
;
2569 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
),
2571 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
),
2572 &iter
, NODE_COLUMN
, &node
, -1);
2574 if (PURPLE_IS_BUDDY(node
))
2576 group
= (PurpleGroup
*)node
->parent
->parent
;
2578 else if (PURPLE_IS_CHAT(node
) ||
2579 PURPLE_IS_CONTACT(node
))
2581 group
= (PurpleGroup
*)node
->parent
;
2583 else if (PURPLE_IS_GROUP(node
))
2585 group
= (PurpleGroup
*)node
;
2589 result
= parse_vcard((const gchar
*)data
, group
);
2591 gtk_drag_finish(dc
, result
,
2592 gdk_drag_context_get_actions(dc
) == GDK_ACTION_MOVE
, t
);
2593 } else if (target
== gdk_atom_intern("text/uri-list", FALSE
) && data
) {
2594 GtkTreePath
*path
= NULL
;
2595 GtkTreeViewDropPosition position
;
2597 if (gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget
),
2598 x
, y
, &path
, &position
))
2601 PurpleBlistNode
*node
;
2603 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
),
2605 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
),
2606 &iter
, NODE_COLUMN
, &node
, -1);
2608 if (PURPLE_IS_BUDDY(node
) || PURPLE_IS_CONTACT(node
)) {
2609 PurpleBuddy
*b
= PURPLE_IS_BUDDY(node
) ? PURPLE_BUDDY(node
) : purple_contact_get_priority_buddy(PURPLE_CONTACT(node
));
2610 pidgin_dnd_file_manage(sd
, purple_buddy_get_account(b
), purple_buddy_get_name(b
));
2611 gtk_drag_finish(dc
, TRUE
,
2612 gdk_drag_context_get_actions(dc
) == GDK_ACTION_MOVE
, t
);
2614 gtk_drag_finish(dc
, FALSE
, FALSE
, t
);
2620 /* Altered from do_colorshift in gnome-panel */
2622 do_alphashift(GdkPixbuf
*pixbuf
, int shift
)
2625 gint width
, height
, padding
;
2629 if (!gdk_pixbuf_get_has_alpha(pixbuf
))
2632 width
= gdk_pixbuf_get_width(pixbuf
);
2633 height
= gdk_pixbuf_get_height(pixbuf
);
2634 padding
= gdk_pixbuf_get_rowstride(pixbuf
) - width
* 4;
2635 pixels
= gdk_pixbuf_get_pixels(pixbuf
);
2637 for (i
= 0; i
< height
; i
++) {
2638 for (j
= 0; j
< width
; j
++) {
2642 val
= *pixels
- shift
;
2643 *(pixels
++) = CLAMP(val
, 0, 255);
2650 static GdkPixbuf
*pidgin_blist_get_buddy_icon(PurpleBlistNode
*node
,
2651 gboolean scaled
, gboolean greyed
)
2654 PurpleBuddy
*buddy
= NULL
;
2655 PurpleGroup
*group
= NULL
;
2656 const guchar
*data
= NULL
;
2657 GdkPixbuf
*buf
, *ret
= NULL
;
2658 PurpleBuddyIcon
*icon
= NULL
;
2659 PurpleAccount
*account
= NULL
;
2660 PurpleContact
*contact
= NULL
;
2661 PurpleImage
*custom_img
;
2662 PurpleProtocol
*protocol
= NULL
;
2663 PurpleBuddyIconSpec
*icon_spec
= NULL
;
2664 gint orig_width
, orig_height
, scale_width
, scale_height
;
2666 if (PURPLE_IS_CONTACT(node
)) {
2667 buddy
= purple_contact_get_priority_buddy((PurpleContact
*)node
);
2668 contact
= (PurpleContact
*)node
;
2669 } else if (PURPLE_IS_BUDDY(node
)) {
2670 buddy
= (PurpleBuddy
*)node
;
2671 contact
= purple_buddy_get_contact(buddy
);
2672 } else if (PURPLE_IS_GROUP(node
)) {
2673 group
= (PurpleGroup
*)node
;
2674 } else if (PURPLE_IS_CHAT(node
)) {
2675 /* We don't need to do anything here. We just need to not fall
2676 * into the else block and return. */
2682 account
= purple_buddy_get_account(buddy
);
2685 if(account
&& purple_account_get_connection(account
)) {
2686 protocol
= purple_connection_get_protocol(purple_account_get_connection(account
));
2690 if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_buddy_icons"))
2694 /* If we have a contact then this is either a contact or a buddy and
2695 * we want to fetch the custom icon for the contact. If we don't have
2696 * a contact then this is a group or some other type of node and we
2697 * want to use that directly. */
2699 custom_img
= purple_buddy_icons_node_find_custom_icon(PURPLE_BLIST_NODE(contact
));
2701 custom_img
= purple_buddy_icons_node_find_custom_icon(node
);
2705 data
= purple_image_get_data(custom_img
);
2706 len
= purple_image_get_data_size(custom_img
);
2711 /* Not sure I like this...*/
2712 if (!(icon
= purple_buddy_icons_find(purple_buddy_get_account(buddy
), purple_buddy_get_name(buddy
))))
2714 data
= purple_buddy_icon_get_data(icon
, &len
);
2721 buf
= pidgin_pixbuf_from_data(data
, len
);
2722 purple_buddy_icon_unref(icon
);
2724 purple_debug_warning("gtkblist", "Couldn't load buddy icon on "
2725 "account %s (%s); buddyname=%s; custom_img_size=%" G_GSIZE_FORMAT
,
2726 account
? purple_account_get_username(account
) : "(no account)",
2727 account
? purple_account_get_protocol_id(account
) : "(no account)",
2728 buddy
? purple_buddy_get_name(buddy
) : "(no buddy)",
2729 custom_img
? purple_image_get_data_size(custom_img
) : 0);
2731 g_object_unref(custom_img
);
2735 g_object_unref(custom_img
);
2738 gboolean offline
= FALSE
, idle
= FALSE
;
2741 PurplePresence
*presence
= purple_buddy_get_presence(buddy
);
2742 if (!PURPLE_BUDDY_IS_ONLINE(buddy
))
2744 if (purple_presence_is_idle(presence
))
2747 if (purple_counting_node_get_online_count(PURPLE_COUNTING_NODE(group
)) == 0)
2752 gdk_pixbuf_saturate_and_pixelate(buf
, buf
, 0.0, FALSE
);
2755 gdk_pixbuf_saturate_and_pixelate(buf
, buf
, 0.25, FALSE
);
2758 /* I'd use the pidgin_buddy_icon_get_scale_size() thing, but it won't
2759 * tell me the original size, which I need for scaling purposes. */
2760 scale_width
= orig_width
= gdk_pixbuf_get_width(buf
);
2761 scale_height
= orig_height
= gdk_pixbuf_get_height(buf
);
2764 icon_spec
= purple_protocol_get_icon_spec(protocol
);
2766 if (icon_spec
&& icon_spec
->scale_rules
& PURPLE_ICON_SCALE_DISPLAY
)
2767 purple_buddy_icon_spec_get_scaled_size(purple_protocol_get_icon_spec(protocol
), &scale_width
, &scale_height
);
2769 if (scaled
|| scale_height
> 200 || scale_width
> 200) {
2771 float scale_size
= scaled
? 32.0 : 200.0;
2772 if(scale_height
> scale_width
) {
2773 scale_width
= scale_size
* (double)scale_width
/ (double)scale_height
;
2774 scale_height
= scale_size
;
2776 scale_height
= scale_size
* (double)scale_height
/ (double)scale_width
;
2777 scale_width
= scale_size
;
2779 /* Scale & round before making square, so rectangular (but
2780 * non-square) images get rounded corners too. */
2781 tmpbuf
= gdk_pixbuf_new(GDK_COLORSPACE_RGB
, TRUE
, 8, scale_width
, scale_height
);
2782 gdk_pixbuf_fill(tmpbuf
, 0x00000000);
2783 gdk_pixbuf_scale(buf
, tmpbuf
, 0, 0, scale_width
, scale_height
, 0, 0, (double)scale_width
/(double)orig_width
, (double)scale_height
/(double)orig_height
, GDK_INTERP_BILINEAR
);
2784 if (pidgin_gdk_pixbuf_is_opaque(tmpbuf
))
2785 pidgin_gdk_pixbuf_make_round(tmpbuf
);
2786 ret
= gdk_pixbuf_new(GDK_COLORSPACE_RGB
, TRUE
, 8, scale_size
, scale_size
);
2787 gdk_pixbuf_fill(ret
, 0x00000000);
2788 gdk_pixbuf_copy_area(tmpbuf
, 0, 0, scale_width
, scale_height
, ret
, (scale_size
-scale_width
)/2, (scale_size
-scale_height
)/2);
2789 g_object_unref(G_OBJECT(tmpbuf
));
2791 ret
= gdk_pixbuf_scale_simple(buf
,scale_width
,scale_height
, GDK_INTERP_BILINEAR
);
2793 g_object_unref(G_OBJECT(buf
));
2803 * +--- STATUS_SIZE +--- td->avatar_width
2804 * | +-- td->name_width |
2805 * +----+ +-------+ +---------+
2807 * +-------------------------------------------+
2808 * | [ = [ |--- TOOLTIP_BORDER
2809 *name_height --+-| ######[BuddyName = PP [ AAAAAAAAAAA |--+
2810 * | | ######[ = PP [ AAAAAAAAAAA | |
2811 * STATUS SIZE -| | ######[[[[[[[[[[[[[[[[[[[[[ AAAAAAAAAAA | |
2812 * +--+-| ######[Account: So-and-so [ AAAAAAAAAAA | |-- td->avatar_height
2813 * | | [Idle: 4h 15m [ AAAAAAAAAAA | |
2814 * height --+ | [Foo: Bar, Baz [ AAAAAAAAAAA | |
2815 * | | [Status: Awesome [ AAAAAAAAAAA |--+
2816 * +----| [Stop: Hammer Time [ |
2817 * | [ [ |--- TOOLTIP_BORDER
2818 * +-------------------------------------------+
2820 * | +----------------+ |
2824 * +---- TOOLTIP_BORDER +---- TOOLTIP_BORDER
2828 #define STATUS_SIZE 16
2829 #define TOOLTIP_BORDER 12
2830 #define SMALL_SPACE 6
2831 #define LARGE_SPACE 12
2832 #define PROTOCOL_SIZE 16
2833 struct tooltip_data
{
2834 PangoLayout
*layout
;
2835 PangoLayout
*name_layout
;
2836 GdkPixbuf
*protocol_icon
;
2837 GdkPixbuf
*status_icon
;
2839 gboolean avatar_is_protocol_icon
;
2849 static PangoLayout
* create_pango_layout(const char *markup
, int *width
, int *height
)
2851 PangoLayout
*layout
;
2854 layout
= gtk_widget_create_pango_layout(gtkblist
->tipwindow
, NULL
);
2855 pango_layout_set_markup(layout
, markup
, -1);
2856 pango_layout_set_wrap(layout
, PANGO_WRAP_WORD
);
2857 pango_layout_set_width(layout
, 300000);
2859 pango_layout_get_size (layout
, &w
, &h
);
2861 *width
= PANGO_PIXELS(w
);
2863 *height
= PANGO_PIXELS(h
);
2867 static struct tooltip_data
* create_tip_for_account(PurpleAccount
*account
)
2869 struct tooltip_data
*td
= g_new0(struct tooltip_data
, 1);
2870 td
->status_icon
= pidgin_create_protocol_icon(account
, PIDGIN_PROTOCOL_ICON_SMALL
);
2871 /* Yes, status_icon, not protocol_icon */
2872 if (purple_account_is_disconnected(account
))
2873 gdk_pixbuf_saturate_and_pixelate(td
->status_icon
, td
->status_icon
, 0.0, FALSE
);
2874 td
->layout
= create_pango_layout(purple_account_get_username(account
), &td
->width
, &td
->height
);
2875 td
->padding
= SMALL_SPACE
;
2879 static struct tooltip_data
* create_tip_for_node(PurpleBlistNode
*node
, gboolean full
)
2881 struct tooltip_data
*td
= g_new0(struct tooltip_data
, 1);
2882 PurpleAccount
*account
= NULL
;
2883 char *tmp
= NULL
, *node_name
= NULL
, *tooltip_text
= NULL
;
2885 if (PURPLE_IS_BUDDY(node
)) {
2886 account
= purple_buddy_get_account((PurpleBuddy
*)(node
));
2887 } else if (PURPLE_IS_CHAT(node
)) {
2888 account
= purple_chat_get_account((PurpleChat
*)(node
));
2891 td
->padding
= TOOLTIP_BORDER
;
2892 td
->status_icon
= pidgin_blist_get_status_icon(node
, PIDGIN_STATUS_ICON_LARGE
);
2893 td
->avatar
= pidgin_blist_get_buddy_icon(node
, !full
, FALSE
);
2894 if (account
!= NULL
) {
2895 td
->protocol_icon
= pidgin_create_protocol_icon(account
, PIDGIN_PROTOCOL_ICON_SMALL
);
2897 tooltip_text
= pidgin_get_tooltip_text(node
, full
);
2898 if (tooltip_text
&& *tooltip_text
) {
2899 td
->layout
= create_pango_layout(tooltip_text
, &td
->width
, &td
->height
);
2902 if (PURPLE_IS_BUDDY(node
)) {
2903 tmp
= g_markup_escape_text(purple_buddy_get_name((PurpleBuddy
*)node
), -1);
2904 } else if (PURPLE_IS_CHAT(node
)) {
2905 tmp
= g_markup_escape_text(purple_chat_get_name((PurpleChat
*)node
), -1);
2906 } else if (PURPLE_IS_GROUP(node
)) {
2907 tmp
= g_markup_escape_text(purple_group_get_name((PurpleGroup
*)node
), -1);
2909 /* I don't believe this can happen currently, I think
2910 * everything that calls this function checks for one of the
2911 * above node types first. */
2912 tmp
= g_strdup(_("Unknown node type"));
2914 node_name
= g_strdup_printf("<span size='x-large' weight='bold'>%s</span>",
2918 td
->name_layout
= create_pango_layout(node_name
, &td
->name_width
, &td
->name_height
);
2919 td
->name_width
+= SMALL_SPACE
+ PROTOCOL_SIZE
;
2920 td
->name_height
= MAX(td
->name_height
, PROTOCOL_SIZE
+ SMALL_SPACE
);
2921 #if 0 /* Protocol Icon as avatar */
2922 if(!td
->avatar
&& full
) {
2923 td
->avatar
= pidgin_create_protocol_icon(account
, PIDGIN_PROTOCOL_ICON_LARGE
);
2924 td
->avatar_is_protocol_icon
= TRUE
;
2929 td
->avatar_width
= gdk_pixbuf_get_width(td
->avatar
);
2930 td
->avatar_height
= gdk_pixbuf_get_height(td
->avatar
);
2934 g_free(tooltip_text
);
2939 pidgin_blist_paint_tip(GtkWidget
*widget
, cairo_t
*cr
, gpointer null
)
2941 GtkStyleContext
*context
;
2942 int current_height
, max_width
;
2944 int max_avatar_width
;
2946 int protocol_col
= 0;
2947 GtkTextDirection dir
= gtk_widget_get_direction(widget
);
2948 int status_size
= 0;
2950 if(gtkblist
->tooltipdata
== NULL
)
2953 context
= gtk_widget_get_style_context(gtkblist
->tipwindow
);
2954 gtk_style_context_add_class(context
, GTK_STYLE_CLASS_TOOLTIP
);
2957 max_avatar_width
= 0;
2959 for(l
= gtkblist
->tooltipdata
; l
; l
= l
->next
)
2961 struct tooltip_data
*td
= l
->data
;
2963 max_text_width
= MAX(max_text_width
,
2964 MAX(td
->width
, td
->name_width
));
2965 max_avatar_width
= MAX(max_avatar_width
, td
->avatar_width
);
2966 if (td
->status_icon
)
2967 status_size
= STATUS_SIZE
;
2970 max_width
= TOOLTIP_BORDER
+ status_size
+ SMALL_SPACE
+ max_text_width
+ SMALL_SPACE
+ max_avatar_width
+ TOOLTIP_BORDER
;
2971 if (dir
== GTK_TEXT_DIR_RTL
)
2972 protocol_col
= TOOLTIP_BORDER
+ max_avatar_width
+ SMALL_SPACE
;
2974 protocol_col
= TOOLTIP_BORDER
+ status_size
+ SMALL_SPACE
+ max_text_width
- PROTOCOL_SIZE
;
2976 current_height
= 12;
2977 for(l
= gtkblist
->tooltipdata
; l
; l
= l
->next
)
2979 struct tooltip_data
*td
= l
->data
;
2981 if (td
->avatar
&& pidgin_gdk_pixbuf_is_opaque(td
->avatar
))
2983 gtk_style_context_save(context
);
2984 gtk_style_context_add_class(context
, GTK_STYLE_CLASS_FRAME
);
2985 if (dir
== GTK_TEXT_DIR_RTL
) {
2986 gtk_render_frame(context
, cr
,
2987 TOOLTIP_BORDER
- 1, current_height
- 1,
2988 td
->avatar_width
+ 2, td
->avatar_height
+ 2);
2990 gtk_render_frame(context
, cr
,
2991 max_width
- (td
->avatar_width
+ TOOLTIP_BORDER
) - 1,
2993 td
->avatar_width
+ 2, td
->avatar_height
+ 2);
2995 gtk_style_context_restore(context
);
2998 if (td
->status_icon
) {
2999 if (dir
== GTK_TEXT_DIR_RTL
) {
3000 gdk_cairo_set_source_pixbuf(cr
, td
->status_icon
,
3001 max_width
- TOOLTIP_BORDER
- status_size
,
3005 gdk_cairo_set_source_pixbuf(cr
, td
->status_icon
,
3006 TOOLTIP_BORDER
, current_height
);
3012 if (dir
== GTK_TEXT_DIR_RTL
) {
3013 gdk_cairo_set_source_pixbuf(cr
, td
->avatar
,
3014 TOOLTIP_BORDER
, current_height
);
3017 gdk_cairo_set_source_pixbuf(cr
, td
->avatar
,
3018 max_width
- (td
->avatar_width
+ TOOLTIP_BORDER
),
3024 if (!td
->avatar_is_protocol_icon
&& td
->protocol_icon
) {
3025 gdk_cairo_set_source_pixbuf(cr
, td
->protocol_icon
, protocol_col
,
3027 (td
->name_height
- PROTOCOL_SIZE
) / 2);
3031 if (td
->name_layout
) {
3032 if (dir
== GTK_TEXT_DIR_RTL
) {
3033 gtk_render_layout(context
, cr
,
3034 max_width
- (TOOLTIP_BORDER
+ status_size
+ SMALL_SPACE
) - PANGO_PIXELS(300000),
3035 current_height
, td
->name_layout
);
3037 gtk_render_layout(context
, cr
,
3038 TOOLTIP_BORDER
+ status_size
+ SMALL_SPACE
,
3039 current_height
, td
->name_layout
);
3044 if (dir
!= GTK_TEXT_DIR_RTL
) {
3045 gtk_render_layout(context
, cr
,
3046 TOOLTIP_BORDER
+ status_size
+ SMALL_SPACE
,
3047 current_height
+ td
->name_height
,
3050 gtk_render_layout(context
, cr
,
3051 max_width
- (TOOLTIP_BORDER
+ status_size
+ SMALL_SPACE
) - PANGO_PIXELS(300000),
3052 current_height
+ td
->name_height
,
3057 current_height
+= MAX(td
->name_height
+ td
->height
, td
->avatar_height
) + td
->padding
;
3064 pidgin_blist_destroy_tooltip_data(void)
3066 while(gtkblist
->tooltipdata
) {
3067 struct tooltip_data
*td
= gtkblist
->tooltipdata
->data
;
3070 g_object_unref(td
->avatar
);
3072 g_object_unref(td
->status_icon
);
3073 if(td
->protocol_icon
)
3074 g_object_unref(td
->protocol_icon
);
3076 g_object_unref(td
->layout
);
3077 if (td
->name_layout
)
3078 g_object_unref(td
->name_layout
);
3080 gtkblist
->tooltipdata
= g_list_delete_link(gtkblist
->tooltipdata
, gtkblist
->tooltipdata
);
3084 void pidgin_blist_tooltip_destroy()
3086 pidgin_blist_destroy_tooltip_data();
3087 pidgin_tooltip_destroy();
3091 pidgin_blist_align_tooltip(struct tooltip_data
*td
, GtkWidget
*widget
)
3093 GtkTextDirection dir
= gtk_widget_get_direction(widget
);
3095 if (dir
== GTK_TEXT_DIR_RTL
)
3097 char* layout_name
= purple_markup_strip_html(pango_layout_get_text(td
->name_layout
));
3098 PangoDirection dir
= pango_find_base_dir(layout_name
, -1);
3099 if (dir
== PANGO_DIRECTION_RTL
|| dir
== PANGO_DIRECTION_NEUTRAL
)
3100 pango_layout_set_alignment(td
->name_layout
, PANGO_ALIGN_RIGHT
);
3101 g_free(layout_name
);
3102 pango_layout_set_alignment(td
->layout
, PANGO_ALIGN_RIGHT
);
3107 pidgin_blist_create_tooltip_for_node(GtkWidget
*widget
, gpointer data
, int *w
, int *h
)
3109 PurpleBlistNode
*node
= data
;
3112 int max_text_width
= 0;
3113 int max_avatar_width
= 0;
3114 int status_size
= 0;
3116 if (gtkblist
->tooltipdata
) {
3117 gtkblist
->tipwindow
= NULL
;
3118 pidgin_blist_destroy_tooltip_data();
3121 gtkblist
->tipwindow
= widget
;
3122 if (PURPLE_IS_CHAT(node
) ||
3123 PURPLE_IS_BUDDY(node
)) {
3124 struct tooltip_data
*td
= create_tip_for_node(node
, TRUE
);
3125 pidgin_blist_align_tooltip(td
, gtkblist
->tipwindow
);
3126 gtkblist
->tooltipdata
= g_list_append(gtkblist
->tooltipdata
, td
);
3127 } else if (PURPLE_IS_GROUP(node
)) {
3128 PurpleGroup
*group
= (PurpleGroup
*)node
;
3130 struct tooltip_data
*td
= create_tip_for_node(node
, TRUE
);
3131 pidgin_blist_align_tooltip(td
, gtkblist
->tipwindow
);
3132 gtkblist
->tooltipdata
= g_list_append(gtkblist
->tooltipdata
, td
);
3134 /* Accounts with buddies in group */
3135 accounts
= purple_group_get_accounts(group
);
3136 for (; accounts
!= NULL
;
3137 accounts
= g_slist_delete_link(accounts
, accounts
)) {
3138 PurpleAccount
*account
= accounts
->data
;
3139 td
= create_tip_for_account(account
);
3140 gtkblist
->tooltipdata
= g_list_append(gtkblist
->tooltipdata
, td
);
3142 } else if (PURPLE_IS_CONTACT(node
)) {
3143 PurpleBlistNode
*child
;
3144 PurpleBuddy
*b
= purple_contact_get_priority_buddy((PurpleContact
*)node
);
3146 for(child
= node
->child
; child
; child
= child
->next
)
3148 if(PURPLE_IS_BUDDY(child
) && buddy_is_displayable((PurpleBuddy
*)child
)) {
3149 struct tooltip_data
*td
= create_tip_for_node(child
, (b
== (PurpleBuddy
*)child
));
3150 pidgin_blist_align_tooltip(td
, gtkblist
->tipwindow
);
3151 if (b
== (PurpleBuddy
*)child
) {
3152 gtkblist
->tooltipdata
= g_list_prepend(gtkblist
->tooltipdata
, td
);
3154 gtkblist
->tooltipdata
= g_list_append(gtkblist
->tooltipdata
, td
);
3163 for (list
= gtkblist
->tooltipdata
; list
; list
= list
->next
) {
3164 struct tooltip_data
*td
= list
->data
;
3165 max_text_width
= MAX(max_text_width
, MAX(td
->width
, td
->name_width
));
3166 max_avatar_width
= MAX(max_avatar_width
, td
->avatar_width
);
3167 height
+= MAX(MAX(STATUS_SIZE
, td
->avatar_height
), td
->height
+ td
->name_height
) + td
->padding
;
3168 if (td
->status_icon
)
3169 status_size
= MAX(status_size
, STATUS_SIZE
);
3171 height
+= TOOLTIP_BORDER
;
3172 width
= TOOLTIP_BORDER
+ status_size
+ SMALL_SPACE
+ max_text_width
+ SMALL_SPACE
+ max_avatar_width
+ TOOLTIP_BORDER
;
3182 static gboolean
pidgin_blist_expand_timeout(GtkWidget
*tv
)
3186 PurpleBlistNode
*node
;
3187 PidginBlistNode
*gtknode
;
3189 if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv
), gtkblist
->tip_rect
.x
, gtkblist
->tip_rect
.y
+ (gtkblist
->tip_rect
.height
/2),
3190 &path
, NULL
, NULL
, NULL
))
3192 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, path
);
3193 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, NODE_COLUMN
, &node
, -1);
3195 if(!PURPLE_IS_CONTACT(node
)) {
3196 gtk_tree_path_free(path
);
3200 gtknode
= purple_blist_node_get_ui_data(node
);
3202 if (!gtknode
->contact_expanded
) {
3205 pidgin_blist_expand_contact_cb(NULL
, node
);
3207 gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv
), path
, NULL
, >kblist
->contact_rect
);
3208 gtkblist
->contact_rect
.width
=
3209 gdk_window_get_width(gtk_widget_get_window(tv
));
3210 gtkblist
->mouseover_contact
= node
;
3211 gtk_tree_path_down (path
);
3212 while (gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
), &i
, path
)) {
3214 gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv
), path
, NULL
, &rect
);
3215 gtkblist
->contact_rect
.height
+= rect
.height
;
3216 gtk_tree_path_next(path
);
3219 gtk_tree_path_free(path
);
3223 static gboolean
buddy_is_displayable(PurpleBuddy
*buddy
)
3225 PidginBlistNode
*gtknode
;
3230 gtknode
= purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(buddy
));
3232 return (purple_account_is_connected(purple_buddy_get_account(buddy
)) &&
3233 (purple_presence_is_online(purple_buddy_get_presence(buddy
)) ||
3234 (gtknode
&& gtknode
->recent_signonoff
) ||
3235 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_offline_buddies") ||
3236 purple_blist_node_get_bool(PURPLE_BLIST_NODE(buddy
), "show_offline")));
3239 void pidgin_blist_draw_tooltip(PurpleBlistNode
*node
, GtkWidget
*widget
)
3241 pidgin_tooltip_show(widget
, node
, pidgin_blist_create_tooltip_for_node
, pidgin_blist_paint_tip
);
3244 static gboolean
pidgin_blist_drag_motion_cb(GtkWidget
*tv
, GdkDragContext
*drag_context
,
3245 gint x
, gint y
, guint time
, gpointer user_data
)
3252 * When dragging a buddy into a contact, this is the delay before
3253 * the contact auto-expands.
3257 if (gtkblist
->drag_timeout
) {
3258 if ((y
> gtkblist
->tip_rect
.y
) && ((y
- gtkblist
->tip_rect
.height
) < gtkblist
->tip_rect
.y
))
3260 /* We've left the cell. Remove the timeout and create a new one below */
3261 g_source_remove(gtkblist
->drag_timeout
);
3264 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv
), x
, y
, &path
, NULL
, NULL
, NULL
);
3265 gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv
), path
, NULL
, &rect
);
3268 gtk_tree_path_free(path
);
3270 /* Only autoexpand when in the middle of the cell to avoid annoying un-intended expands */
3271 if (y
< rect
.y
+ (rect
.height
/ 3) ||
3272 y
> rect
.y
+ (2 * (rect
.height
/3)))
3275 rect
.height
= rect
.height
/ 3;
3276 rect
.y
+= rect
.height
;
3278 gtkblist
->tip_rect
= rect
;
3280 gtkblist
->drag_timeout
= g_timeout_add(delay
, (GSourceFunc
)pidgin_blist_expand_timeout
, tv
);
3282 if (gtkblist
->mouseover_contact
) {
3283 if ((y
< gtkblist
->contact_rect
.y
) || ((y
- gtkblist
->contact_rect
.height
) > gtkblist
->contact_rect
.y
)) {
3284 pidgin_blist_collapse_contact_cb(NULL
, gtkblist
->mouseover_contact
);
3285 gtkblist
->mouseover_contact
= NULL
;
3293 pidgin_blist_create_tooltip(GtkWidget
*widget
, GtkTreePath
*path
,
3294 gpointer null
, int *w
, int *h
)
3297 PurpleBlistNode
*node
;
3298 gboolean editable
= FALSE
;
3300 /* If we're editing a cell (e.g. alias editing), don't show the tooltip */
3301 g_object_get(G_OBJECT(gtkblist
->text_rend
), "editable", &editable
, NULL
);
3305 if (gtkblist
->tooltipdata
) {
3306 gtkblist
->tipwindow
= NULL
;
3307 pidgin_blist_destroy_tooltip_data();
3310 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, path
);
3311 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, NODE_COLUMN
, &node
, -1);
3313 return pidgin_blist_create_tooltip_for_node(widget
, node
, w
, h
);
3316 static gboolean
pidgin_blist_motion_cb (GtkWidget
*tv
, GdkEventMotion
*event
, gpointer null
)
3318 if (gtkblist
->mouseover_contact
) {
3319 if ((event
->y
< gtkblist
->contact_rect
.y
) || ((event
->y
- gtkblist
->contact_rect
.height
) > gtkblist
->contact_rect
.y
)) {
3320 pidgin_blist_collapse_contact_cb(NULL
, gtkblist
->mouseover_contact
);
3321 gtkblist
->mouseover_contact
= NULL
;
3328 static gboolean
pidgin_blist_leave_cb (GtkWidget
*w
, GdkEventCrossing
*e
, gpointer n
)
3330 if (gtkblist
->timeout
) {
3331 g_source_remove(gtkblist
->timeout
);
3332 gtkblist
->timeout
= 0;
3335 if (gtkblist
->drag_timeout
) {
3336 g_source_remove(gtkblist
->drag_timeout
);
3337 gtkblist
->drag_timeout
= 0;
3340 if (gtkblist
->mouseover_contact
&&
3341 !((e
->x
> gtkblist
->contact_rect
.x
) && (e
->x
< (gtkblist
->contact_rect
.x
+ gtkblist
->contact_rect
.width
)) &&
3342 (e
->y
> gtkblist
->contact_rect
.y
) && (e
->y
< (gtkblist
->contact_rect
.y
+ gtkblist
->contact_rect
.height
)))) {
3343 pidgin_blist_collapse_contact_cb(NULL
, gtkblist
->mouseover_contact
);
3344 gtkblist
->mouseover_contact
= NULL
;
3352 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/debug/enabled",
3353 !purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/debug/enabled"));
3356 static char *get_mood_icon_path(const char *mood
)
3360 if (purple_strequal(mood
, "busy")) {
3361 path
= g_build_filename(PURPLE_DATADIR
, "pidgin", "icons",
3362 "hicolor", "16x16", "status", "user-busy.png", NULL
);
3363 } else if (purple_strequal(mood
, "hiptop")) {
3364 path
= g_build_filename(PURPLE_DATADIR
, "pidgin", "icons",
3365 "hicolor", "16x16", "emblems", "emblem-hiptop.png",
3368 char *filename
= g_strdup_printf("%s.png", mood
);
3369 path
= g_build_filename(PURPLE_DATADIR
, "pixmaps", "pidgin",
3370 "emotes", "small", filename
, NULL
);
3377 update_status_with_mood(PurpleAccount
*account
, const gchar
*mood
,
3380 if (mood
&& *mood
) {
3382 purple_account_set_status(account
, "mood", TRUE
,
3383 PURPLE_MOOD_NAME
, mood
,
3384 PURPLE_MOOD_COMMENT
, text
,
3387 purple_account_set_status(account
, "mood", TRUE
,
3388 PURPLE_MOOD_NAME
, mood
,
3392 purple_account_set_status(account
, "mood", FALSE
, NULL
);
3397 edit_mood_cb(PurpleConnection
*gc
, PurpleRequestFields
*fields
)
3399 PurpleRequestField
*mood_field
;
3402 mood_field
= purple_request_fields_get_field(fields
, "mood");
3403 l
= purple_request_field_list_get_selected(mood_field
);
3406 const char *mood
= purple_request_field_list_get_data(mood_field
, l
->data
);
3410 PurpleAccount
*account
= purple_connection_get_account(gc
);
3412 if (purple_connection_get_flags(gc
) & PURPLE_CONNECTION_FLAG_SUPPORT_MOOD_MESSAGES
) {
3413 PurpleRequestField
*text_field
;
3414 text_field
= purple_request_fields_get_field(fields
, "text");
3415 text
= purple_request_field_string_get_value(text_field
);
3420 update_status_with_mood(account
, mood
, text
);
3422 GList
*accounts
= purple_accounts_get_all_active();
3424 for (; accounts
; accounts
= g_list_delete_link(accounts
, accounts
)) {
3425 PurpleAccount
*account
= (PurpleAccount
*) accounts
->data
;
3426 PurpleConnection
*gc
= purple_account_get_connection(account
);
3428 if (gc
&& (purple_connection_get_flags(gc
) & PURPLE_CONNECTION_FLAG_SUPPORT_MOODS
)) {
3429 update_status_with_mood(account
, mood
, NULL
);
3437 global_moods_for_each(gpointer key
, gpointer value
, gpointer user_data
)
3439 GList
**out_moods
= (GList
**) user_data
;
3440 PurpleMood
*mood
= (PurpleMood
*) value
;
3442 *out_moods
= g_list_append(*out_moods
, mood
);
3446 get_global_moods(void)
3448 GHashTable
*global_moods
=
3449 g_hash_table_new_full(g_str_hash
, g_str_equal
, NULL
, NULL
);
3450 GHashTable
*mood_counts
=
3451 g_hash_table_new_full(g_str_hash
, g_str_equal
, NULL
, NULL
);
3452 GList
*accounts
= purple_accounts_get_all_active();
3453 PurpleMood
*result
= NULL
;
3454 GList
*out_moods
= NULL
;
3456 int num_accounts
= 0;
3458 for (; accounts
; accounts
= g_list_delete_link(accounts
, accounts
)) {
3459 PurpleAccount
*account
= (PurpleAccount
*) accounts
->data
;
3460 if (purple_account_is_connected(account
)) {
3461 PurpleConnection
*gc
= purple_account_get_connection(account
);
3463 if (purple_connection_get_flags(gc
) & PURPLE_CONNECTION_FLAG_SUPPORT_MOODS
) {
3464 PurpleProtocol
*protocol
= purple_connection_get_protocol(gc
);
3465 PurpleMood
*mood
= NULL
;
3467 for (mood
= purple_protocol_client_iface_get_moods(protocol
, account
) ;
3468 mood
->mood
!= NULL
; mood
++) {
3470 GPOINTER_TO_INT(g_hash_table_lookup(mood_counts
, mood
->mood
));
3472 if (!g_hash_table_lookup(global_moods
, mood
->mood
)) {
3473 g_hash_table_insert(global_moods
, (gpointer
)mood
->mood
, mood
);
3475 g_hash_table_insert(mood_counts
, (gpointer
)mood
->mood
,
3476 GINT_TO_POINTER(mood_count
+ 1));
3484 g_hash_table_foreach(global_moods
, global_moods_for_each
, &out_moods
);
3485 result
= g_new0(PurpleMood
, g_hash_table_size(global_moods
) + 1);
3488 PurpleMood
*mood
= (PurpleMood
*) out_moods
->data
;
3489 int in_num_accounts
=
3490 GPOINTER_TO_INT(g_hash_table_lookup(mood_counts
, mood
->mood
));
3492 if (in_num_accounts
== num_accounts
) {
3493 /* mood is present in all accounts supporting moods */
3494 result
[i
].mood
= mood
->mood
;
3495 result
[i
].description
= mood
->description
;
3498 out_moods
= g_list_delete_link(out_moods
, out_moods
);
3501 g_hash_table_destroy(global_moods
);
3502 g_hash_table_destroy(mood_counts
);
3507 /* get current set mood for all mood-supporting accounts, or NULL if not set
3508 or not set to the same on all */
3509 static const gchar
*
3510 get_global_mood_status(void)
3512 GList
*accounts
= purple_accounts_get_all_active();
3513 const gchar
*found_mood
= NULL
;
3515 for (; accounts
; accounts
= g_list_delete_link(accounts
, accounts
)) {
3516 PurpleAccount
*account
= (PurpleAccount
*) accounts
->data
;
3518 if (purple_account_is_connected(account
) &&
3519 (purple_connection_get_flags(purple_account_get_connection(account
)) &
3520 PURPLE_CONNECTION_FLAG_SUPPORT_MOODS
)) {
3521 PurplePresence
*presence
= purple_account_get_presence(account
);
3522 PurpleStatus
*status
= purple_presence_get_status(presence
, "mood");
3523 const gchar
*curr_mood
= purple_status_get_attr_string(status
, PURPLE_MOOD_NAME
);
3525 if (found_mood
!= NULL
&& !purple_strequal(curr_mood
, found_mood
)) {
3526 /* found a different mood */
3530 found_mood
= curr_mood
;
3539 set_mood_cb(GtkWidget
*widget
, PurpleAccount
*account
)
3541 const char *current_mood
;
3542 PurpleRequestFields
*fields
;
3543 PurpleRequestFieldGroup
*g
;
3544 PurpleRequestField
*f
;
3545 PurpleConnection
*gc
= NULL
;
3546 PurpleProtocol
*protocol
= NULL
;
3548 PurpleMood
*global_moods
= get_global_moods();
3551 PurplePresence
*presence
= purple_account_get_presence(account
);
3552 PurpleStatus
*status
= purple_presence_get_status(presence
, "mood");
3553 gc
= purple_account_get_connection(account
);
3554 g_return_if_fail(purple_connection_get_protocol(gc
) != NULL
);
3555 protocol
= purple_connection_get_protocol(gc
);
3556 current_mood
= purple_status_get_attr_string(status
, PURPLE_MOOD_NAME
);
3558 current_mood
= get_global_mood_status();
3561 fields
= purple_request_fields_new();
3562 g
= purple_request_field_group_new(NULL
);
3563 f
= purple_request_field_list_new("mood", _("Please select your mood from the list"));
3565 purple_request_field_list_add_icon(f
, _("None"), NULL
, "");
3566 if (current_mood
== NULL
)
3567 purple_request_field_list_add_selected(f
, _("None"));
3569 /* TODO: rlaager wants this sorted. */
3570 /* TODO: darkrain wants it sorted post-translation */
3571 if (account
&& PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CLIENT
, get_moods
))
3572 mood
= purple_protocol_client_iface_get_moods(protocol
, account
);
3574 mood
= global_moods
;
3575 for ( ; mood
->mood
!= NULL
; mood
++) {
3578 if (mood
->mood
== NULL
|| mood
->description
== NULL
)
3581 path
= get_mood_icon_path(mood
->mood
);
3582 purple_request_field_list_add_icon(f
, _(mood
->description
),
3583 path
, (gpointer
)mood
->mood
);
3586 if (current_mood
&& purple_strequal(current_mood
, mood
->mood
))
3587 purple_request_field_list_add_selected(f
, _(mood
->description
));
3589 purple_request_field_group_add_field(g
, f
);
3591 purple_request_fields_add_group(fields
, g
);
3593 /* if the connection allows setting a mood message */
3594 if (gc
&& (purple_connection_get_flags(gc
) & PURPLE_CONNECTION_FLAG_SUPPORT_MOOD_MESSAGES
)) {
3595 g
= purple_request_field_group_new(NULL
);
3596 f
= purple_request_field_string_new("text",
3597 _("Message (optional)"), NULL
, FALSE
);
3598 purple_request_field_group_add_field(g
, f
);
3599 purple_request_fields_add_group(fields
, g
);
3602 purple_request_fields(gc
, _("Edit User Mood"), _("Edit User Mood"),
3604 _("OK"), G_CALLBACK(edit_mood_cb
),
3606 purple_request_cpar_from_connection(gc
), gc
);
3608 g_free(global_moods
);
3614 set_mood_cb(NULL
, NULL
);
3617 /***************************************************
3619 ***************************************************/
3621 _pidgin_about_cb(GtkAction
*action
, GtkWidget
*window
) {
3622 GtkWidget
*about
= pidgin_about_dialog_new();
3624 gtk_window_set_transient_for(GTK_WINDOW(about
), GTK_WINDOW(window
));
3626 gtk_widget_show_all(about
);
3629 /* TODO: fill out tooltips... */
3630 static const GtkActionEntry blist_menu_entries
[] = {
3631 /* NOTE: Do not set any accelerator to Control+O. It is mapped by
3632 gtk_blist_key_press_cb to "Get User Info" on the selected buddy. */
3634 { "BuddiesMenu", NULL
, N_("_Buddies"), NULL
, NULL
, NULL
},
3635 { "NewInstantMessage", PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW
, N_("New Instant _Message..."), "<control>M", NULL
, pidgin_dialogs_im
},
3636 { "JoinAChat", PIDGIN_STOCK_CHAT
, N_("Join a _Chat..."), "<control>C", NULL
, pidgin_blist_joinchat_show
},
3637 { "GetUserInfo", PIDGIN_STOCK_TOOLBAR_USER_INFO
, N_("Get User _Info..."), "<control>I", NULL
, pidgin_dialogs_info
},
3638 { "ViewUserLog", NULL
, N_("View User _Log..."), "<control>L", NULL
, pidgin_dialogs_log
},
3639 { "ShowMenu", NULL
, N_("Sh_ow"), NULL
, NULL
, NULL
},
3640 { "SortMenu", NULL
, N_("_Sort Buddies"), NULL
, NULL
, NULL
},
3641 { "AddBuddy", GTK_STOCK_ADD
, N_("_Add Buddy..."), "<control>B", NULL
, pidgin_blist_add_buddy_cb
},
3642 { "AddChat", GTK_STOCK_ADD
, N_("Add C_hat..."), NULL
, NULL
, pidgin_blist_add_chat_cb
},
3643 { "AddGroup", GTK_STOCK_ADD
, N_("Add _Group..."), NULL
, NULL
, purple_blist_request_add_group
},
3644 { "Quit", GTK_STOCK_QUIT
, N_("_Quit"), "<control>Q", NULL
, purple_core_quit
},
3647 { "AccountsMenu", NULL
, N_("_Accounts"), NULL
, NULL
, NULL
},
3648 { "ManageAccounts", NULL
, N_("Manage Accounts"), "<control>A", NULL
, pidgin_accounts_window_show
},
3651 { "ToolsMenu", NULL
, N_("_Tools"), NULL
, NULL
, NULL
},
3652 { "BuddyPounces", NULL
, N_("Buddy _Pounces"), NULL
, NULL
, pidgin_pounces_manager_show
},
3653 { "CustomSmileys", PIDGIN_STOCK_TOOLBAR_SMILEY
, N_("Custom Smile_ys"), "<control>Y", NULL
, pidgin_smiley_manager_show
},
3654 { "Plugins", PIDGIN_STOCK_TOOLBAR_PLUGINS
, N_("Plu_gins"), "<control>U", NULL
, pidgin_plugin_dialog_show
},
3655 { "Preferences", GTK_STOCK_PREFERENCES
, N_("Pr_eferences"), "<control>P", NULL
, pidgin_prefs_show
},
3656 { "Privacy", NULL
, N_("Pr_ivacy"), NULL
, NULL
, pidgin_privacy_dialog_show
},
3657 { "SetMood", NULL
, N_("Set _Mood"), "<control>D", NULL
, set_mood_show
},
3658 { "FileTransfers", PIDGIN_STOCK_TOOLBAR_TRANSFER
, N_("_File Transfers"), "<control>T", NULL
, G_CALLBACK(gtk_blist_show_xfer_dialog_cb
) },
3659 { "RoomList", NULL
, N_("R_oom List"), NULL
, NULL
, pidgin_roomlist_dialog_show
},
3660 { "SystemLog", NULL
, N_("System _Log"), NULL
, NULL
, gtk_blist_show_systemlog_cb
},
3663 { "HelpMenu", NULL
, N_("_Help"), NULL
, NULL
, NULL
},
3664 { "OnlineHelp", GTK_STOCK_HELP
, N_("Online _Help"), "F1", NULL
, gtk_blist_show_onlinehelp_cb
},
3665 { "DebugWindow", NULL
, N_("_Debug Window"), NULL
, NULL
, toggle_debug
},
3666 { "PluginInformation", NULL
, N_("_Plugin Information"), NULL
, NULL
, pidgin_debug_plugin_info_show
},
3667 { "About", GTK_STOCK_ABOUT
, N_("_About"), NULL
, NULL
, G_CALLBACK(_pidgin_about_cb
) },
3671 static const GtkToggleActionEntry blist_menu_toggle_entries
[] = {
3672 /* Buddies->Show menu */
3673 { "ShowOffline", NULL
, N_("_Offline Buddies"), NULL
, NULL
, G_CALLBACK(pidgin_blist_edit_mode_cb
), FALSE
},
3674 { "ShowEmptyGroups", NULL
, N_("_Empty Groups"), NULL
, NULL
, G_CALLBACK(pidgin_blist_show_empty_groups_cb
), FALSE
},
3675 { "ShowBuddyDetails", NULL
, N_("Buddy _Details"), NULL
, NULL
, G_CALLBACK(pidgin_blist_buddy_details_cb
), FALSE
},
3676 { "ShowIdleTimes", NULL
, N_("Idle _Times"), NULL
, NULL
, G_CALLBACK(pidgin_blist_show_idle_time_cb
), FALSE
},
3677 { "ShowProtocolIcons", NULL
, N_("_Protocol Icons"), NULL
, NULL
, G_CALLBACK(pidgin_blist_show_protocol_icons_cb
), FALSE
},
3680 { "MuteSounds", NULL
, N_("Mute _Sounds"), NULL
, NULL
, G_CALLBACK(pidgin_blist_mute_sounds_cb
), FALSE
},
3683 static const char *blist_menu
=
3685 "<menubar name='BList'>"
3686 "<menu action='BuddiesMenu'>"
3687 "<menuitem action='NewInstantMessage'/>"
3688 "<menuitem action='JoinAChat'/>"
3689 "<menuitem action='GetUserInfo'/>"
3690 "<menuitem action='ViewUserLog'/>"
3692 "<menu action='ShowMenu'>"
3693 "<menuitem action='ShowOffline'/>"
3694 "<menuitem action='ShowEmptyGroups'/>"
3695 "<menuitem action='ShowBuddyDetails'/>"
3696 "<menuitem action='ShowIdleTimes'/>"
3697 "<menuitem action='ShowProtocolIcons'/>"
3699 "<menu action='SortMenu'/>"
3701 "<menuitem action='AddBuddy'/>"
3702 "<menuitem action='AddChat'/>"
3703 "<menuitem action='AddGroup'/>"
3705 "<menuitem action='Quit'/>"
3707 "<menu action='AccountsMenu'>"
3708 "<menuitem action='ManageAccounts'/>"
3710 "<menu action='ToolsMenu'>"
3711 "<menuitem action='BuddyPounces'/>"
3712 "<menuitem action='CustomSmileys'/>"
3713 "<menuitem action='Plugins'/>"
3714 "<menuitem action='Preferences'/>"
3715 "<menuitem action='Privacy'/>"
3716 "<menuitem action='SetMood'/>"
3718 "<menuitem action='FileTransfers'/>"
3719 "<menuitem action='RoomList'/>"
3720 "<menuitem action='SystemLog'/>"
3722 "<menuitem action='MuteSounds'/>"
3723 "<placeholder name='PluginActions'/>"
3725 "<menu action='HelpMenu'>"
3726 "<menuitem action='OnlineHelp'/>"
3728 "<menuitem action='DebugWindow'/>"
3729 "<menuitem action='PluginInformation'/>"
3731 "<menuitem action='About'/>"
3736 /*********************************************************
3737 * Private Utility functions *
3738 *********************************************************/
3740 static char *pidgin_get_tooltip_text(PurpleBlistNode
*node
, gboolean full
)
3742 GString
*str
= g_string_new("");
3743 PurpleProtocol
*protocol
= NULL
;
3746 if (PURPLE_IS_CHAT(node
))
3751 PurpleProtocolChatEntry
*pce
;
3753 PurpleChatConversation
*conv
;
3754 PidginBlistNode
*bnode
= purple_blist_node_get_ui_data(node
);
3756 chat
= (PurpleChat
*)node
;
3757 protocol
= purple_protocols_find(purple_account_get_protocol_id(purple_chat_get_account(chat
)));
3759 connections
= purple_connections_get_all();
3760 if (connections
&& connections
->next
)
3762 tmp
= g_markup_escape_text(purple_account_get_username(purple_chat_get_account(chat
)), -1);
3763 g_string_append_printf(str
, _("<b>Account:</b> %s"), tmp
);
3767 if (bnode
&& bnode
->conv
.conv
) {
3768 conv
= PURPLE_CHAT_CONVERSATION(bnode
->conv
.conv
);
3771 if (protocol
&& PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT
, get_name
))
3772 chat_name
= purple_protocol_chat_iface_get_name(protocol
, purple_chat_get_components(chat
));
3774 chat_name
= g_strdup(purple_chat_get_name(chat
));
3776 conv
= purple_conversations_find_chat_with_account(chat_name
,
3777 purple_chat_get_account(chat
));
3781 if (conv
&& !purple_chat_conversation_has_left(conv
)) {
3782 g_string_append_printf(str
, _("\n<b>Occupants:</b> %d"),
3783 purple_chat_conversation_get_users_count(conv
));
3785 if (protocol
&& (purple_protocol_get_options(protocol
) & OPT_PROTO_CHAT_TOPIC
)) {
3786 const char *chattopic
= purple_chat_conversation_get_topic(conv
);
3787 char *topic
= chattopic
? g_markup_escape_text(chattopic
, -1) : NULL
;
3788 g_string_append_printf(str
, _("\n<b>Topic:</b> %s"), topic
? topic
: _("(no topic set)"));
3794 cur
= purple_protocol_chat_iface_info(protocol
, purple_account_get_connection(purple_chat_get_account(chat
)));
3802 tmp
= purple_text_strip_mnemonic(pce
->label
);
3803 name
= g_markup_escape_text(tmp
, -1);
3805 value
= g_markup_escape_text(g_hash_table_lookup(
3806 purple_chat_get_components(chat
), pce
->identifier
), -1);
3807 g_string_append_printf(str
, "\n<b>%s</b> %s",
3809 value
? value
: "");
3815 cur
= g_list_delete_link(cur
, cur
);
3818 else if (PURPLE_IS_CONTACT(node
) || PURPLE_IS_BUDDY(node
))
3820 /* NOTE: THIS FUNCTION IS NO LONGER CALLED FOR CONTACTS.
3821 * It is only called by create_tip_for_node(), and create_tip_for_node() is never called for a contact.
3825 PurplePresence
*presence
;
3826 PurpleNotifyUserInfo
*user_info
;
3830 time_t idle_secs
, signon
;
3832 if (PURPLE_IS_CONTACT(node
))
3834 c
= (PurpleContact
*)node
;
3835 b
= purple_contact_get_priority_buddy(c
);
3839 b
= (PurpleBuddy
*)node
;
3840 c
= purple_buddy_get_contact(b
);
3843 protocol
= purple_protocols_find(purple_account_get_protocol_id(purple_buddy_get_account(b
)));
3845 presence
= purple_buddy_get_presence(b
);
3846 user_info
= purple_notify_user_info_new();
3849 connections
= purple_connections_get_all();
3850 if (full
&& connections
&& connections
->next
)
3852 purple_notify_user_info_add_pair_plaintext(user_info
, _("Account"),
3853 purple_account_get_username(purple_buddy_get_account(b
)));
3857 /* If there's not a contact alias, the node is being displayed with
3858 * this alias, so there's no point in showing it in the tooltip. */
3859 g_object_get(c
, "alias", &alias
, NULL
);
3860 if (full
&& c
&& purple_buddy_get_local_alias(b
) != NULL
&& purple_buddy_get_local_alias(b
)[0] != '\0' &&
3861 (alias
!= NULL
&& alias
[0] != '\0') &&
3862 !purple_strequal(alias
, purple_buddy_get_local_alias(b
)))
3864 purple_notify_user_info_add_pair_plaintext(user_info
,
3865 _("Buddy Alias"), purple_buddy_get_local_alias(b
));
3868 /* Nickname/Server Alias */
3869 /* I'd like to only show this if there's a contact or buddy
3870 * alias, but people often set long nicknames, which
3871 * get ellipsized, so the only way to see the whole thing is
3872 * to look at the tooltip. */
3873 if (full
&& purple_buddy_get_server_alias(b
))
3875 purple_notify_user_info_add_pair_plaintext(user_info
,
3876 _("Nickname"), purple_buddy_get_server_alias(b
));
3880 signon
= purple_presence_get_login_time(presence
);
3881 if (full
&& PURPLE_BUDDY_IS_ONLINE(b
) && signon
> 0)
3883 if (signon
> time(NULL
)) {
3885 * They signed on in the future?! Our local clock
3886 * must be wrong, show the actual date instead of
3889 tmp
= g_strdup(purple_date_format_long(localtime(&signon
)));
3891 tmp
= purple_str_seconds_to_string(time(NULL
) - signon
);
3892 purple_notify_user_info_add_pair_plaintext(user_info
, _("Logged In"), tmp
);
3897 if (purple_presence_is_idle(presence
))
3899 idle_secs
= purple_presence_get_idle_time(presence
);
3902 tmp
= purple_str_seconds_to_string(time(NULL
) - idle_secs
);
3903 purple_notify_user_info_add_pair_plaintext(user_info
, _("Idle"), tmp
);
3909 if (full
&& c
&& !PURPLE_BUDDY_IS_ONLINE(b
))
3911 PidginBlistNode
*gtknode
= purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(c
));
3912 PurpleBlistNode
*bnode
;
3915 if (gtknode
&& (!gtknode
->contact_expanded
|| PURPLE_IS_CONTACT(node
)))
3917 /* We're either looking at a buddy for a collapsed contact or
3918 * an expanded contact itself so we show the most recent
3919 * (largest) last_seen time for any of the buddies under
3921 for (bnode
= ((PurpleBlistNode
*)c
)->child
; bnode
!= NULL
; bnode
= bnode
->next
)
3923 int value
= purple_blist_node_get_int(bnode
, "last_seen");
3924 if (value
> lastseen
)
3930 /* We're dealing with a buddy under an expanded contact,
3931 * so we show the last_seen time for the buddy. */
3932 lastseen
= purple_blist_node_get_int(&b
->node
, "last_seen");
3937 tmp
= purple_str_seconds_to_string(time(NULL
) - lastseen
);
3938 purple_notify_user_info_add_pair_plaintext(user_info
, _("Last Seen"), tmp
);
3945 /* FIXME: Why is this status special-cased by the core? --rlaager
3946 * FIXME: Alternatively, why not have the core do all of them? --rlaager */
3947 if (!PURPLE_BUDDY_IS_ONLINE(b
)) {
3948 purple_notify_user_info_add_pair_plaintext(user_info
, _("Status"), _("Offline"));
3951 if (purple_account_is_connected(purple_buddy_get_account(b
)) &&
3954 /* Additional text from the protocol */
3955 purple_protocol_client_iface_tooltip_text(protocol
, b
, user_info
, full
);
3958 /* These are Easter Eggs. Patches to remove them will be rejected. */
3959 if (!g_ascii_strcasecmp(purple_buddy_get_name(b
), "robflynn"))
3960 purple_notify_user_info_add_pair_plaintext(user_info
, _("Description"), _("Spooky"));
3961 if (!g_ascii_strcasecmp(purple_buddy_get_name(b
), "seanegn"))
3962 purple_notify_user_info_add_pair_plaintext(user_info
, _("Status"), _("Awesome"));
3963 if (!g_ascii_strcasecmp(purple_buddy_get_name(b
), "chipx86"))
3964 purple_notify_user_info_add_pair_plaintext(user_info
, _("Status"), _("Rockin'"));
3966 tmp
= purple_notify_user_info_get_text_with_newline(user_info
, "\n");
3967 g_string_append(str
, tmp
);
3971 purple_notify_user_info_destroy(user_info
);
3972 } else if (PURPLE_IS_GROUP(node
)) {
3974 PurpleGroup
*group
= (PurpleGroup
*)node
;
3975 PurpleNotifyUserInfo
*user_info
;
3977 user_info
= purple_notify_user_info_new();
3979 count
= purple_counting_node_get_online_count(PURPLE_COUNTING_NODE(group
));
3981 /* Online buddies in group */
3983 sprintf(tmp2
, "%d", count
);
3984 purple_notify_user_info_add_pair_plaintext(user_info
,
3985 _("Online Buddies"), tmp2
);
3988 count
= purple_counting_node_get_current_size(PURPLE_COUNTING_NODE(group
));
3990 /* Total buddies (from online accounts) in group */
3992 sprintf(tmp2
, "%d", count
);
3993 purple_notify_user_info_add_pair_html(user_info
,
3994 _("Total Buddies"), tmp2
);
3997 tmp
= purple_notify_user_info_get_text_with_newline(user_info
, "\n");
3998 g_string_append(str
, tmp
);
4001 purple_notify_user_info_destroy(user_info
);
4004 purple_signal_emit(pidgin_blist_get_handle(), "drawing-tooltip",
4007 return g_string_free(str
, FALSE
);
4010 static GHashTable
*cached_emblems
;
4012 static void _cleanup_cached_emblem(gpointer data
, GObject
*obj
) {
4013 g_hash_table_remove(cached_emblems
, data
);
4016 static GdkPixbuf
* _pidgin_blist_get_cached_emblem(gchar
*path
) {
4017 GdkPixbuf
*pb
= g_hash_table_lookup(cached_emblems
, path
);
4020 /* The caller gets a reference */
4024 pb
= pidgin_pixbuf_new_from_file(path
);
4026 /* We don't want to own a ref to the pixbuf, but we need to keep clean up. */
4027 /* I'm not sure if it would be better to just keep our ref and not let the emblem ever be destroyed */
4028 g_object_weak_ref(G_OBJECT(pb
), _cleanup_cached_emblem
, path
);
4029 g_hash_table_insert(cached_emblems
, path
, pb
);
4038 pidgin_blist_get_emblem(PurpleBlistNode
*node
)
4040 PurpleBuddy
*buddy
= NULL
;
4041 PidginBlistNode
*gtknode
= purple_blist_node_get_ui_data(node
);
4042 PurpleProtocol
*protocol
;
4043 const char *name
= NULL
;
4044 char *filename
, *path
;
4045 PurplePresence
*p
= NULL
;
4048 if(PURPLE_IS_CONTACT(node
)) {
4049 if(!gtknode
->contact_expanded
) {
4050 buddy
= purple_contact_get_priority_buddy((PurpleContact
*)node
);
4052 } else if(PURPLE_IS_BUDDY(node
)) {
4053 buddy
= (PurpleBuddy
*)node
;
4054 p
= purple_buddy_get_presence(buddy
);
4055 if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_MOBILE
)) {
4056 /* This emblem comes from the small emoticon set now,
4057 * to reduce duplication. */
4058 path
= g_build_filename(PURPLE_DATADIR
, "pixmaps",
4059 "pidgin", "emotes", "small", "mobile.png", NULL
);
4060 return _pidgin_blist_get_cached_emblem(path
);
4063 if (((PidginBlistNode
*)purple_blist_node_get_ui_data(node
->parent
))->contact_expanded
) {
4064 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_protocol_icons"))
4066 return pidgin_create_protocol_icon(purple_buddy_get_account((PurpleBuddy
*)node
), PIDGIN_PROTOCOL_ICON_SMALL
);
4072 g_return_val_if_fail(buddy
!= NULL
, NULL
);
4074 if (!purple_account_privacy_check(purple_buddy_get_account(buddy
), purple_buddy_get_name(buddy
))) {
4075 path
= g_build_filename(PURPLE_DATADIR
, "pidgin", "icons",
4076 "hicolor", "16x16", "emblems", "emblem-blocked.png",
4078 return _pidgin_blist_get_cached_emblem(path
);
4081 /* If we came through the contact code flow above, we didn't need
4082 * to get the presence until now. */
4084 p
= purple_buddy_get_presence(buddy
);
4086 if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_MOBILE
)) {
4087 /* This emblem comes from the small emoticon set now, to reduce duplication. */
4088 path
= g_build_filename(PURPLE_DATADIR
, "pixmaps", "pidgin",
4089 "emotes", "small", "mobile.png", NULL
);
4090 return _pidgin_blist_get_cached_emblem(path
);
4093 tune
= purple_presence_get_status(p
, "tune");
4094 if (tune
&& purple_status_is_active(tune
)) {
4095 /* TODO: Replace "Tune" with generalized "Media" in 3.0. */
4096 if (purple_status_get_attr_string(tune
, "game") != NULL
) {
4097 path
= g_build_filename(PURPLE_DATADIR
, "pidgin",
4098 "icons", "hicolor", "16x16", "emblems",
4099 "emblem-game.png", NULL
);
4100 return _pidgin_blist_get_cached_emblem(path
);
4102 /* TODO: Replace "Tune" with generalized "Media" in 3.0. */
4103 if (purple_status_get_attr_string(tune
, "office") != NULL
) {
4104 path
= g_build_filename(PURPLE_DATADIR
, "pidgin",
4105 "icons", "hicolor", "16x16", "emblems",
4106 "emblem-office.png", NULL
);
4107 return _pidgin_blist_get_cached_emblem(path
);
4109 /* Regular old "tune" is the only one in all protocols. */
4110 /* This emblem comes from the small emoticon set now, to reduce duplication. */
4111 path
= g_build_filename(PURPLE_DATADIR
, "pixmaps", "pidgin",
4112 "emotes", "small", "music.png", NULL
);
4113 return _pidgin_blist_get_cached_emblem(path
);
4116 protocol
= purple_protocols_find(purple_account_get_protocol_id(purple_buddy_get_account(buddy
)));
4120 name
= purple_protocol_client_iface_list_emblem(protocol
, buddy
);
4123 PurpleStatus
*status
;
4125 if (!purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_MOOD
))
4128 status
= purple_presence_get_status(p
, "mood");
4129 name
= purple_status_get_attr_string(status
, PURPLE_MOOD_NAME
);
4131 if (!(name
&& *name
))
4134 path
= get_mood_icon_path(name
);
4136 filename
= g_strdup_printf("emblem-%s.png", name
);
4137 path
= g_build_filename(PURPLE_DATADIR
, "pidgin", "icons",
4138 "hicolor", "16x16", "emblems", filename
, NULL
);
4142 /* _pidgin_blist_get_cached_emblem() assumes ownership of path */
4143 return _pidgin_blist_get_cached_emblem(path
);
4148 pidgin_blist_get_status_icon(PurpleBlistNode
*node
, PidginStatusIconSize size
)
4151 const char *icon
= NULL
;
4152 PidginBlistNode
*gtknode
= purple_blist_node_get_ui_data(node
);
4153 PidginBlistNode
*gtkbuddynode
= NULL
;
4154 PurpleBuddy
*buddy
= NULL
;
4155 PurpleChat
*chat
= NULL
;
4156 GtkIconSize icon_size
= gtk_icon_size_from_name((size
== PIDGIN_STATUS_ICON_LARGE
) ? PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
:
4157 PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC
);
4159 if(PURPLE_IS_CONTACT(node
)) {
4160 if(!gtknode
->contact_expanded
) {
4161 buddy
= purple_contact_get_priority_buddy((PurpleContact
*)node
);
4163 gtkbuddynode
= purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(buddy
));
4165 } else if(PURPLE_IS_BUDDY(node
)) {
4166 buddy
= (PurpleBuddy
*)node
;
4167 gtkbuddynode
= purple_blist_node_get_ui_data(node
);
4168 } else if(PURPLE_IS_CHAT(node
)) {
4169 chat
= (PurpleChat
*)node
;
4175 PurpleAccount
*account
;
4176 PurpleProtocol
*protocol
;
4179 account
= purple_buddy_get_account(buddy
);
4181 account
= purple_chat_get_account(chat
);
4183 protocol
= purple_protocols_find(purple_account_get_protocol_id(account
));
4189 PurpleConversation
*conv
= find_conversation_with_buddy(buddy
);
4194 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
4195 if (gtkconv
== NULL
&& size
== PIDGIN_STATUS_ICON_SMALL
) {
4196 PidginBlistNode
*ui
= purple_blist_node_get_ui_data(&(buddy
->node
));
4197 if (ui
== NULL
|| (ui
->conv
.flags
& PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE
))
4198 return gtk_widget_render_icon (GTK_WIDGET(gtkblist
->treeview
),
4199 PIDGIN_STOCK_STATUS_MESSAGE
, icon_size
, "GtkTreeView");
4203 p
= purple_buddy_get_presence(buddy
);
4204 trans
= purple_presence_is_idle(p
);
4206 if (PURPLE_BUDDY_IS_ONLINE(buddy
) && gtkbuddynode
&& gtkbuddynode
->recent_signonoff
)
4207 icon
= PIDGIN_STOCK_STATUS_LOGIN
;
4208 else if (gtkbuddynode
&& gtkbuddynode
->recent_signonoff
)
4209 icon
= PIDGIN_STOCK_STATUS_LOGOUT
;
4210 else if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_UNAVAILABLE
))
4212 icon
= PIDGIN_STOCK_STATUS_BUSY_I
;
4214 icon
= PIDGIN_STOCK_STATUS_BUSY
;
4215 else if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_AWAY
))
4217 icon
= PIDGIN_STOCK_STATUS_AWAY_I
;
4219 icon
= PIDGIN_STOCK_STATUS_AWAY
;
4220 else if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_EXTENDED_AWAY
))
4222 icon
= PIDGIN_STOCK_STATUS_XA_I
;
4224 icon
= PIDGIN_STOCK_STATUS_XA
;
4225 else if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_OFFLINE
))
4226 icon
= PIDGIN_STOCK_STATUS_OFFLINE
;
4228 icon
= PIDGIN_STOCK_STATUS_AVAILABLE_I
;
4229 else if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_INVISIBLE
))
4230 icon
= PIDGIN_STOCK_STATUS_INVISIBLE
;
4232 icon
= PIDGIN_STOCK_STATUS_AVAILABLE
;
4234 icon
= PIDGIN_STOCK_STATUS_CHAT
;
4236 icon
= PIDGIN_STOCK_STATUS_PERSON
;
4239 ret
= gtk_widget_render_icon (GTK_WIDGET(gtkblist
->treeview
), icon
,
4240 icon_size
, "GtkTreeView");
4245 theme_font_get_color_default(PidginThemeFont
*font
, const char *def
)
4248 if (!font
|| !(ret
= pidgin_theme_font_get_color_describe(font
)))
4254 theme_font_get_face_default(PidginThemeFont
*font
, const char *def
)
4257 if (!font
|| !(ret
= pidgin_theme_font_get_font_face(font
)))
4263 pidgin_blist_get_name_markup(PurpleBuddy
*b
, gboolean selected
, gboolean aliased
)
4265 const char *name
, *name_color
, *name_font
, *status_color
, *status_font
, *dim_grey
;
4267 PurpleProtocol
*protocol
= NULL
;
4268 PurpleContact
*contact
;
4269 PurplePresence
*presence
;
4270 PidginBlistNode
*gtkcontactnode
= NULL
;
4271 char *idletime
= NULL
, *statustext
= NULL
, *nametext
= NULL
;
4272 PurpleConversation
*conv
= find_conversation_with_buddy(b
);
4273 gboolean hidden_conv
= FALSE
;
4274 gboolean biglist
= purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_buddy_icons");
4275 PidginThemeFont
*statusfont
= NULL
, *namefont
= NULL
;
4276 PidginBlistTheme
*theme
;
4277 gchar
*contact_alias
;
4280 PidginBlistNode
*ui
= purple_blist_node_get_ui_data(&(b
->node
));
4282 if (ui
->conv
.flags
& PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE
)
4285 if (PIDGIN_CONVERSATION(conv
) == NULL
)
4290 /* XXX Good luck cleaning up this crap */
4291 contact
= PURPLE_CONTACT(PURPLE_BLIST_NODE(b
)->parent
);
4293 gtkcontactnode
= purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(contact
));
4295 g_object_get(contact
, "alias", &contact_alias
, NULL
);
4298 if (gtkcontactnode
&& !gtkcontactnode
->contact_expanded
&& contact_alias
)
4299 name
= contact_alias
;
4301 name
= purple_buddy_get_alias(b
);
4303 /* Raise a contact pre-draw signal here. THe callback will return an
4304 * escaped version of the name. */
4305 nametext
= purple_signal_emit_return_1(pidgin_blist_get_handle(), "drawing-buddy", b
);
4308 nametext
= g_markup_escape_text(name
, strlen(name
));
4310 presence
= purple_buddy_get_presence(b
);
4312 /* Name is all that is needed */
4313 if (!aliased
|| biglist
) {
4316 protocol
= purple_protocols_find(purple_account_get_protocol_id(purple_buddy_get_account(b
)));
4318 if (protocol
&& PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CLIENT
, status_text
) &&
4319 purple_account_get_connection(purple_buddy_get_account(b
))) {
4320 char *tmp
= purple_protocol_client_iface_status_text(protocol
, b
);
4323 if(tmp
&& !g_utf8_validate(tmp
, -1, &end
)) {
4324 char *new = g_strndup(tmp
,
4325 g_utf8_pointer_to_offset(tmp
, end
));
4330 g_strdelimit(tmp
, "\n", ' ');
4331 purple_str_strip_char(tmp
, '\r');
4336 if(!purple_presence_is_online(presence
) && !statustext
)
4337 statustext
= g_strdup(_("Offline"));
4340 if (purple_presence_is_idle(presence
) && purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_idle_time")) {
4341 time_t idle_secs
= purple_presence_get_idle_time(presence
);
4343 if (idle_secs
> 0) {
4344 int iday
, ihrs
, imin
;
4348 iday
= (t
- idle_secs
) / (24 * 60 * 60);
4349 ihrs
= ((t
- idle_secs
) / 60 / 60) % 24;
4350 imin
= ((t
- idle_secs
) / 60) % 60;
4353 idletime
= g_strdup_printf(_("Idle %dd %dh %02dm"), iday
, ihrs
, imin
);
4355 idletime
= g_strdup_printf(_("Idle %dh %02dm"), ihrs
, imin
);
4357 idletime
= g_strdup_printf(_("Idle %dm"), imin
);
4360 idletime
= g_strdup(_("Idle"));
4364 /* choose the colors of the text */
4365 theme
= pidgin_blist_get_theme();
4368 dim_grey
= pidgin_style_is_dark(NULL
) ? "light slate grey" : "dim grey";
4371 if (purple_presence_is_idle(presence
)) {
4372 namefont
= statusfont
= pidgin_blist_theme_get_idle_text_info(theme
);
4373 name_color
= dim_grey
;
4374 } else if (!purple_presence_is_online(presence
)) {
4375 namefont
= pidgin_blist_theme_get_offline_text_info(theme
);
4376 name_color
= dim_grey
;
4377 statusfont
= pidgin_blist_theme_get_status_text_info(theme
);
4378 } else if (purple_presence_is_available(presence
)) {
4379 namefont
= pidgin_blist_theme_get_online_text_info(theme
);
4380 statusfont
= pidgin_blist_theme_get_status_text_info(theme
);
4382 namefont
= pidgin_blist_theme_get_away_text_info(theme
);
4383 statusfont
= pidgin_blist_theme_get_status_text_info(theme
);
4387 && (purple_presence_is_idle(presence
)
4388 || !purple_presence_is_online(presence
)))
4390 name_color
= dim_grey
;
4394 name_color
= theme_font_get_color_default(namefont
, name_color
);
4395 name_font
= theme_font_get_face_default(namefont
, "");
4397 status_color
= theme_font_get_color_default(statusfont
, dim_grey
);
4398 status_font
= theme_font_get_face_default(statusfont
, "");
4400 if (aliased
&& selected
) {
4402 name_color
= "black";
4403 status_color
= "black";
4406 status_color
= NULL
;
4411 char *tmp
= nametext
;
4412 nametext
= g_strdup_printf("<b>%s</b>", tmp
);
4416 /* Put it all together */
4417 if ((!aliased
|| biglist
) && (statustext
|| idletime
)) {
4418 /* using <span size='smaller'> breaks the status, so it must be seperated into <small><span>*/
4420 text
= g_strdup_printf("<span font_desc='%s' foreground='%s'>%s</span>\n"
4421 "<small><span font_desc='%s' foreground='%s'>%s%s%s</span></small>",
4422 name_font
, name_color
, nametext
, status_font
, status_color
,
4423 idletime
!= NULL
? idletime
: "",
4424 (idletime
!= NULL
&& statustext
!= NULL
) ? " - " : "",
4425 statustext
!= NULL
? statustext
: "");
4426 } else if (status_color
) {
4427 text
= g_strdup_printf("<span font_desc='%s'>%s</span>\n"
4428 "<small><span font_desc='%s' foreground='%s'>%s%s%s</span></small>",
4429 name_font
, nametext
, status_font
, status_color
,
4430 idletime
!= NULL
? idletime
: "",
4431 (idletime
!= NULL
&& statustext
!= NULL
) ? " - " : "",
4432 statustext
!= NULL
? statustext
: "");
4434 text
= g_strdup_printf("<span font_desc='%s'>%s</span>\n"
4435 "<small><span font_desc='%s'>%s%s%s</span></small>",
4436 name_font
, nametext
, status_font
,
4437 idletime
!= NULL
? idletime
: "",
4438 (idletime
!= NULL
&& statustext
!= NULL
) ? " - " : "",
4439 statustext
!= NULL
? statustext
: "");
4443 text
= g_strdup_printf("<span font_desc='%s' color='%s'>%s</span>",
4444 name_font
, name_color
, nametext
);
4446 text
= g_strdup_printf("<span font_desc='%s'>%s</span>", name_font
,
4453 g_free(contact_alias
);
4457 text
= g_strdup_printf("<b>%s</b>", tmp
);
4464 static void pidgin_blist_restore_window_state(void)
4466 int blist_width
, blist_height
;
4468 blist_width
= purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/blist/width");
4470 /* if the window exists, is hidden, we're saving sizes, and the
4471 * size is sane... */
4472 if (gtkblist
&& gtkblist
->window
&&
4473 !gtk_widget_get_visible(gtkblist
->window
) && blist_width
!= 0) {
4475 blist_height
= purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/blist/height");
4477 gtk_window_set_default_size(GTK_WINDOW(gtkblist
->window
),
4478 blist_width
, blist_height
);
4479 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/list_maximized"))
4480 gtk_window_maximize(GTK_WINDOW(gtkblist
->window
));
4484 static gboolean
pidgin_blist_refresh_timer(PurpleBuddyList
*list
)
4486 PurpleBlistNode
*gnode
, *cnode
;
4488 if (gtk_blist_visibility
== GDK_VISIBILITY_FULLY_OBSCURED
4489 || !gtk_widget_get_visible(gtkblist
->window
))
4492 for (gnode
= purple_blist_get_root(list
); gnode
; gnode
= gnode
->next
) {
4493 if(!PURPLE_IS_GROUP(gnode
))
4495 for(cnode
= gnode
->child
; cnode
; cnode
= cnode
->next
) {
4496 if(PURPLE_IS_CONTACT(cnode
)) {
4499 buddy
= purple_contact_get_priority_buddy((PurpleContact
*)cnode
);
4502 purple_presence_is_idle(purple_buddy_get_presence(buddy
)))
4503 pidgin_blist_update_contact(list
, PURPLE_BLIST_NODE(buddy
));
4512 static void pidgin_blist_hide_node(PurpleBuddyList
*list
, PurpleBlistNode
*node
, gboolean update
)
4514 PidginBlistNode
*gtknode
= purple_blist_node_get_ui_data(node
);
4517 if (!gtknode
|| !gtknode
->row
|| !gtkblist
)
4520 if(gtkblist
->selected_node
== node
)
4521 gtkblist
->selected_node
= NULL
;
4522 if (get_iter_from_node(node
, &iter
)) {
4523 gtk_tree_store_remove(gtkblist
->treemodel
, &iter
);
4524 if(update
&& (PURPLE_IS_CONTACT(node
) ||
4525 PURPLE_IS_BUDDY(node
) || PURPLE_IS_CHAT(node
))) {
4526 pidgin_blist_update(list
, node
->parent
);
4529 gtk_tree_row_reference_free(gtknode
->row
);
4530 gtknode
->row
= NULL
;
4533 static const char *require_connection
[] =
4535 "/BList/BuddiesMenu/NewInstantMessage",
4536 "/BList/BuddiesMenu/JoinAChat",
4537 "/BList/BuddiesMenu/GetUserInfo",
4538 "/BList/BuddiesMenu/AddBuddy",
4539 "/BList/BuddiesMenu/AddChat",
4540 "/BList/BuddiesMenu/AddGroup",
4541 "/BList/ToolsMenu/Privacy",
4544 static const int require_connection_size
= sizeof(require_connection
)
4545 / sizeof(*require_connection
);
4548 * Rebuild dynamic menus and make menu items sensitive/insensitive
4549 * where appropriate.
4552 update_menu_bar(PidginBuddyList
*gtkblist
)
4558 g_return_if_fail(gtkblist
!= NULL
);
4560 pidgin_blist_update_accounts_menu();
4562 sensitive
= (purple_connections_get_all() != NULL
);
4564 for (i
= 0; i
< require_connection_size
; i
++)
4566 action
= gtk_ui_manager_get_action(gtkblist
->ui
, require_connection
[i
]);
4567 gtk_action_set_sensitive(action
, sensitive
);
4570 action
= gtk_ui_manager_get_action(gtkblist
->ui
, "/BList/BuddiesMenu/JoinAChat");
4571 gtk_action_set_sensitive(action
, pidgin_blist_joinchat_is_showable());
4573 action
= gtk_ui_manager_get_action(gtkblist
->ui
, "/BList/BuddiesMenu/AddChat");
4574 gtk_action_set_sensitive(action
, pidgin_blist_joinchat_is_showable());
4576 action
= gtk_ui_manager_get_action(gtkblist
->ui
, "/BList/ToolsMenu/RoomList");
4577 gtk_action_set_sensitive(action
, pidgin_roomlist_is_showable());
4581 sign_on_off_cb(PurpleConnection
*gc
, PurpleBuddyList
*blist
)
4583 PidginBuddyList
*gtkblist
= PIDGIN_BUDDY_LIST(blist
);
4585 update_menu_bar(gtkblist
);
4589 plugin_changed_cb(PurplePlugin
*p
, gpointer data
)
4591 pidgin_blist_update_plugin_actions();
4595 unseen_conv_menu(GdkEvent
*event
)
4597 static GtkWidget
*menu
= NULL
;
4598 GList
*convs
= NULL
;
4602 gtk_widget_destroy(menu
);
4606 ims
= pidgin_conversations_get_unseen_ims(PIDGIN_UNSEEN_TEXT
, FALSE
, 0);
4608 chats
= pidgin_conversations_get_unseen_chats(PIDGIN_UNSEEN_NICK
, FALSE
, 0);
4611 convs
= g_list_concat(ims
, chats
);
4612 else if(ims
&& !chats
)
4614 else if(!ims
&& chats
)
4618 /* no conversations added, don't show the menu */
4621 menu
= gtk_menu_new();
4623 pidgin_conversations_fill_menu(menu
, convs
);
4625 gtk_widget_show_all(menu
);
4626 gtk_menu_popup_at_pointer(GTK_MENU(menu
), event
);
4630 menutray_press_cb(GtkWidget
*widget
, GdkEventButton
*event
)
4634 if (event
->button
== GDK_BUTTON_PRIMARY
) {
4635 convs
= pidgin_conversations_get_unseen_ims(PIDGIN_UNSEEN_TEXT
, FALSE
, 1);
4637 convs
= pidgin_conversations_get_unseen_chats(PIDGIN_UNSEEN_NICK
, FALSE
, 1);
4640 pidgin_conv_present_conversation((PurpleConversation
*)convs
->data
);
4644 } else if (gdk_event_triggers_context_menu((GdkEvent
*)event
)) {
4645 unseen_conv_menu((GdkEvent
*)event
);
4652 conversation_updated_cb(PurpleConversation
*conv
, PurpleConversationUpdateType type
,
4653 PidginBuddyList
*gtkblist
)
4655 PurpleAccount
*account
= purple_conversation_get_account(conv
);
4656 GList
*convs
= NULL
;
4660 if (type
!= PURPLE_CONVERSATION_UPDATE_UNSEEN
)
4663 if(account
!= NULL
&& purple_conversation_get_name(conv
) != NULL
) {
4664 PurpleBuddy
*buddy
= purple_blist_find_buddy(account
, purple_conversation_get_name(conv
));
4666 pidgin_blist_update_buddy(NULL
, PURPLE_BLIST_NODE(buddy
), TRUE
);
4669 if (gtkblist
->menutrayicon
) {
4670 gtk_widget_destroy(gtkblist
->menutrayicon
);
4671 gtkblist
->menutrayicon
= NULL
;
4674 ims
= pidgin_conversations_get_unseen_ims(PIDGIN_UNSEEN_TEXT
, FALSE
, 0);
4676 chats
= pidgin_conversations_get_unseen_chats(PIDGIN_UNSEEN_NICK
, FALSE
, 0);
4679 convs
= g_list_concat(ims
, chats
);
4680 else if(ims
&& !chats
)
4682 else if(!ims
&& chats
)
4686 GtkWidget
*img
= NULL
;
4687 GString
*tooltip_text
= NULL
;
4689 tooltip_text
= g_string_new("");
4693 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION((PurpleConversation
*)l
->data
);
4696 count
= gtkconv
->unseen_count
;
4697 else if(g_object_get_data(G_OBJECT(l
->data
), "unseen-count"))
4698 count
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(l
->data
), "unseen-count"));
4700 g_string_append_printf(tooltip_text
,
4701 ngettext("%d unread message from %s\n", "%d unread messages from %s\n", count
),
4702 count
, purple_conversation_get_title(l
->data
));
4705 if(tooltip_text
->len
> 0) {
4706 /* get rid of the last newline */
4707 g_string_truncate(tooltip_text
, tooltip_text
->len
-1);
4708 img
= gtk_image_new_from_stock(PIDGIN_STOCK_TOOLBAR_PENDING
,
4709 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
));
4711 gtkblist
->menutrayicon
= gtk_event_box_new();
4712 gtk_container_add(GTK_CONTAINER(gtkblist
->menutrayicon
), img
);
4713 gtk_widget_show(img
);
4714 gtk_widget_show(gtkblist
->menutrayicon
);
4715 g_signal_connect(G_OBJECT(gtkblist
->menutrayicon
), "button-press-event", G_CALLBACK(menutray_press_cb
), NULL
);
4717 pidgin_menu_tray_append(PIDGIN_MENU_TRAY(gtkblist
->menutray
), gtkblist
->menutrayicon
, tooltip_text
->str
);
4719 g_string_free(tooltip_text
, TRUE
);
4725 conversation_deleting_cb(PurpleConversation
*conv
, PidginBuddyList
*gtkblist
)
4727 conversation_updated_cb(conv
, PURPLE_CONVERSATION_UPDATE_UNSEEN
, gtkblist
);
4731 conversation_deleted_update_ui_cb(PurpleConversation
*conv
, PidginBlistNode
*ui
)
4733 if (ui
->conv
.conv
!= conv
)
4735 ui
->conv
.conv
= NULL
;
4740 written_msg_update_ui_cb(PurpleConversation
*conv
, PurpleMessage
*msg
, PurpleBlistNode
*node
)
4742 PidginBlistNode
*ui
= purple_blist_node_get_ui_data(node
);
4744 if (ui
->conv
.conv
!= conv
)
4747 if (!pidgin_conv_is_hidden(PIDGIN_CONVERSATION(conv
)))
4750 if (!(purple_message_get_flags(msg
) & (PURPLE_MESSAGE_SEND
| PURPLE_MESSAGE_RECV
)))
4753 ui
->conv
.flags
|= PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE
;
4754 if (PURPLE_IS_CHAT_CONVERSATION(conv
) && (purple_message_get_flags(msg
) & PURPLE_MESSAGE_NICK
))
4755 ui
->conv
.flags
|= PIDGIN_BLIST_CHAT_HAS_PENDING_MESSAGE_WITH_NICK
;
4757 pidgin_blist_update(purple_blist_get_default(), node
);
4761 displayed_msg_update_ui_cb(PidginConversation
*gtkconv
, PurpleBlistNode
*node
)
4763 PidginBlistNode
*ui
= purple_blist_node_get_ui_data(node
);
4764 if (ui
->conv
.conv
!= gtkconv
->active_conv
)
4766 ui
->conv
.flags
&= ~(PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE
|
4767 PIDGIN_BLIST_CHAT_HAS_PENDING_MESSAGE_WITH_NICK
);
4768 pidgin_blist_update(purple_blist_get_default(), node
);
4772 conversation_created_cb(PurpleConversation
*conv
, PidginBuddyList
*gtkblist
)
4774 PurpleAccount
*account
= purple_conversation_get_account(conv
);
4776 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
4777 GSList
*buddies
= purple_blist_find_buddies(account
, purple_conversation_get_name(conv
));
4779 PurpleBlistNode
*buddy
= buddies
->data
;
4780 PidginBlistNode
*ui
= purple_blist_node_get_ui_data(buddy
);
4781 buddies
= g_slist_delete_link(buddies
, buddies
);
4784 ui
->conv
.conv
= conv
;
4786 purple_signal_connect(purple_conversations_get_handle(), "deleting-conversation",
4787 ui
, PURPLE_CALLBACK(conversation_deleted_update_ui_cb
), ui
);
4788 purple_signal_connect(purple_conversations_get_handle(), "wrote-im-msg",
4789 ui
, PURPLE_CALLBACK(written_msg_update_ui_cb
), buddy
);
4790 purple_signal_connect(pidgin_conversations_get_handle(), "conversation-displayed",
4791 ui
, PURPLE_CALLBACK(displayed_msg_update_ui_cb
), buddy
);
4793 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
4794 PurpleChat
*chat
= purple_blist_find_chat(account
, purple_conversation_get_name(conv
));
4795 PidginBlistNode
*ui
;
4798 ui
= purple_blist_node_get_ui_data(&(chat
->node
));
4801 ui
->conv
.conv
= conv
;
4803 purple_signal_connect(purple_conversations_get_handle(), "deleting-conversation",
4804 ui
, PURPLE_CALLBACK(conversation_deleted_update_ui_cb
), ui
);
4805 purple_signal_connect(purple_conversations_get_handle(), "wrote-chat-msg",
4806 ui
, PURPLE_CALLBACK(written_msg_update_ui_cb
), chat
);
4807 purple_signal_connect(pidgin_conversations_get_handle(), "conversation-displayed",
4808 ui
, PURPLE_CALLBACK(displayed_msg_update_ui_cb
), chat
);
4812 /**********************************************************************************
4813 * Public API Functions *
4814 **********************************************************************************/
4817 pidgin_blist_new_node(PurpleBuddyList
*list
, PurpleBlistNode
*node
)
4819 purple_blist_node_set_ui_data(node
, g_new0(PidginBlistNode
, 1));
4822 gboolean
pidgin_blist_node_is_contact_expanded(PurpleBlistNode
*node
)
4824 if (PURPLE_IS_BUDDY(node
)) {
4825 node
= node
->parent
;
4830 g_return_val_if_fail(PURPLE_IS_CONTACT(node
), FALSE
);
4832 return ((PidginBlistNode
*)purple_blist_node_get_ui_data(node
))->contact_expanded
;
4844 void pidgin_blist_setup_sort_methods()
4848 pidgin_blist_sort_method_reg("none", _("Manually"), sort_method_none
);
4849 pidgin_blist_sort_method_reg("alphabetical", _("Alphabetically"), sort_method_alphabetical
);
4850 pidgin_blist_sort_method_reg("status", _("By status"), sort_method_status
);
4851 pidgin_blist_sort_method_reg("log_size", _("By recent log activity"), sort_method_log_activity
);
4853 id
= purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/blist/sort_type");
4855 purple_debug_warning("gtkblist", "Sort method was NULL, resetting to alphabetical\n");
4856 id
= "alphabetical";
4858 pidgin_blist_sort_method_set(id
);
4861 static void _prefs_change_redo_list(const char *name
, PurplePrefType type
,
4862 gconstpointer val
, gpointer data
)
4864 GtkTreeSelection
*sel
;
4866 PurpleBlistNode
*node
= NULL
;
4868 sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist
->treeview
));
4869 if (gtk_tree_selection_get_selected(sel
, NULL
, &iter
))
4871 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, NODE_COLUMN
, &node
, -1);
4874 redo_buddy_list(purple_blist_get_default(), FALSE
, FALSE
);
4875 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(gtkblist
->treeview
));
4879 PidginBlistNode
*gtknode
;
4882 gtknode
= purple_blist_node_get_ui_data(node
);
4883 if (gtknode
&& gtknode
->row
)
4885 path
= gtk_tree_row_reference_get_path(gtknode
->row
);
4886 gtk_tree_selection_select_path(sel
, path
);
4887 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(gtkblist
->treeview
), path
, NULL
, FALSE
, 0, 0);
4888 gtk_tree_path_free(path
);
4893 static void _prefs_change_sort_method(const char *pref_name
, PurplePrefType type
,
4894 gconstpointer val
, gpointer data
)
4896 if(purple_strequal(pref_name
, PIDGIN_PREFS_ROOT
"/blist/sort_type"))
4897 pidgin_blist_sort_method_set(val
);
4900 static gboolean
pidgin_blist_select_notebook_page_cb(gpointer user_data
)
4902 PidginBuddyList
*gtkblist
= (PidginBuddyList
*)user_data
;
4905 PidginBuddyListPrivate
*priv
;
4907 priv
= pidgin_buddy_list_get_instance_private(gtkblist
);
4909 priv
->select_notebook_page_timeout
= 0;
4911 /* this is far too ugly thanks to me not wanting to fix #3989 properly right now */
4912 if (priv
->error_scrollbook
!= NULL
) {
4913 errors
= gtk_notebook_get_n_pages(GTK_NOTEBOOK(priv
->error_scrollbook
->notebook
));
4915 if ((list
= purple_accounts_get_all_active()) != NULL
|| errors
) {
4916 gtk_notebook_set_current_page(GTK_NOTEBOOK(gtkblist
->notebook
), 1);
4919 gtk_notebook_set_current_page(GTK_NOTEBOOK(gtkblist
->notebook
), 0);
4921 priv
->select_notebook_page_timeout
= 0;
4925 static void pidgin_blist_select_notebook_page(PidginBuddyList
*gtkblist
)
4927 PidginBuddyListPrivate
*priv
=
4928 pidgin_buddy_list_get_instance_private(gtkblist
);
4929 priv
->select_notebook_page_timeout
= g_timeout_add(0,
4930 pidgin_blist_select_notebook_page_cb
, gtkblist
);
4933 static void account_modified(PurpleAccount
*account
, PidginBuddyList
*gtkblist
)
4938 pidgin_blist_select_notebook_page(gtkblist
);
4939 update_menu_bar(gtkblist
);
4943 account_actions_changed(PurpleAccount
*account
, gpointer data
)
4945 pidgin_blist_update_accounts_menu();
4949 account_status_changed(PurpleAccount
*account
, PurpleStatus
*old
,
4950 PurpleStatus
*new, PidginBuddyList
*gtkblist
)
4955 account_modified(account
, gtkblist
);
4959 gtk_blist_window_key_press_cb(GtkWidget
*w
, GdkEventKey
*event
, PidginBuddyList
*gtkblist
)
4961 /* clear any tooltips */
4962 pidgin_blist_tooltip_destroy();
4968 reset_headline(PidginBuddyList
*gtkblist
)
4970 gtkblist
->headline_callback
= NULL
;
4971 gtkblist
->headline_data
= NULL
;
4972 gtkblist
->headline_destroy
= NULL
;
4973 pidgin_set_urgent(GTK_WINDOW(gtkblist
->window
), FALSE
);
4977 headline_click_callback(gpointer unused
)
4979 if (gtkblist
->headline_callback
)
4980 ((GSourceFunc
) gtkblist
->headline_callback
)(gtkblist
->headline_data
);
4981 reset_headline(gtkblist
);
4986 headline_response_cb(GtkInfoBar
*infobar
, int resp
, PidginBuddyList
*gtkblist
)
4988 gtk_widget_hide(gtkblist
->headline
);
4990 if (resp
== GTK_RESPONSE_OK
) {
4991 if (gtkblist
->headline_callback
)
4992 g_idle_add(headline_click_callback
, NULL
);
4994 if (gtkblist
->headline_destroy
)
4995 gtkblist
->headline_destroy(gtkblist
->headline_data
);
4996 reset_headline(gtkblist
);
4999 if (gtkblist
->headline_destroy
)
5000 gtkblist
->headline_destroy(gtkblist
->headline_data
);
5001 reset_headline(gtkblist
);
5008 headline_realize_cb(GtkWidget
*widget
, gpointer data
)
5010 GdkWindow
*window
= gtk_widget_get_window(widget
);
5011 GdkDisplay
*display
= gdk_window_get_display(window
);
5012 GdkCursor
*hand_cursor
= gdk_cursor_new_for_display(display
, GDK_HAND2
);
5013 gdk_window_set_cursor(window
, hand_cursor
);
5014 g_object_unref(hand_cursor
);
5018 headline_press_cb(GtkWidget
*widget
, GdkEventButton
*event
, GtkInfoBar
*infobar
)
5020 gtk_info_bar_response(infobar
, GTK_RESPONSE_OK
);
5024 /***********************************/
5025 /* Connection error handling stuff */
5026 /***********************************/
5028 #define OBJECT_DATA_KEY_ACCOUNT "account"
5029 #define DO_NOT_CLEAR_ERROR "do-not-clear-error"
5032 find_account_widget(GObject
*widget
,
5033 PurpleAccount
*account
)
5035 if (g_object_get_data(widget
, OBJECT_DATA_KEY_ACCOUNT
) == account
)
5036 return 0; /* found */
5042 pack_protocol_icon_start(GtkWidget
*box
,
5043 PurpleAccount
*account
)
5048 pixbuf
= pidgin_create_protocol_icon(account
, PIDGIN_PROTOCOL_ICON_SMALL
);
5049 if (pixbuf
!= NULL
) {
5050 image
= gtk_image_new_from_pixbuf(pixbuf
);
5051 g_object_unref(pixbuf
);
5053 gtk_box_pack_start(GTK_BOX(box
), image
, FALSE
, FALSE
, 0);
5058 add_error_dialog(PidginBuddyList
*gtkblist
,
5061 PidginBuddyListPrivate
*priv
=
5062 pidgin_buddy_list_get_instance_private(gtkblist
);
5063 gtk_container_add(GTK_CONTAINER(priv
->error_scrollbook
), dialog
);
5067 find_child_widget_by_account(GtkContainer
*container
,
5068 PurpleAccount
*account
)
5071 GList
*children
= NULL
;
5072 GtkWidget
*ret
= NULL
;
5073 /* XXX: Workaround for the currently incomplete implementation of PidginScrollBook */
5074 if (PIDGIN_IS_SCROLL_BOOK(container
))
5075 container
= GTK_CONTAINER(PIDGIN_SCROLL_BOOK(container
)->notebook
);
5076 children
= gtk_container_get_children(container
);
5077 l
= g_list_find_custom(children
, account
, (GCompareFunc
) find_account_widget
);
5079 ret
= GTK_WIDGET(l
->data
);
5080 g_list_free(children
);
5085 remove_child_widget_by_account(GtkContainer
*container
,
5086 PurpleAccount
*account
)
5088 GtkWidget
*widget
= find_child_widget_by_account(container
, account
);
5090 /* Since we are destroying the widget in response to a change in
5091 * error, we should not clear the error.
5093 g_object_set_data(G_OBJECT(widget
), DO_NOT_CLEAR_ERROR
,
5094 GINT_TO_POINTER(TRUE
));
5095 gtk_widget_destroy(widget
);
5099 /* Generic error buttons */
5102 generic_error_modify_cb(PurpleAccount
*account
)
5104 purple_account_clear_current_error(account
);
5105 pidgin_account_dialog_show(PIDGIN_MODIFY_ACCOUNT_DIALOG
, account
);
5109 generic_error_enable_cb(PurpleAccount
*account
)
5111 purple_account_clear_current_error(account
);
5112 purple_account_set_enabled(account
, purple_core_get_ui(), TRUE
);
5116 generic_error_destroy_cb(GtkWidget
*dialog
,
5117 PurpleAccount
*account
)
5119 /* If the error dialog is being destroyed in response to the
5120 * account-error-changed signal, we don't want to clear the current
5123 if (g_object_get_data(G_OBJECT(dialog
), DO_NOT_CLEAR_ERROR
) == NULL
)
5124 purple_account_clear_current_error(account
);
5127 #define SSL_FAQ_URI "https://developer.pidgin.im/wiki/FAQssl"
5130 ssl_faq_clicked_cb(PidginMiniDialog
*mini_dialog
,
5134 purple_notify_uri(NULL
, SSL_FAQ_URI
);
5138 add_generic_error_dialog(PurpleAccount
*account
,
5139 const PurpleConnectionErrorInfo
*err
)
5141 GtkWidget
*mini_dialog
;
5142 const char *username
= purple_account_get_username(account
);
5144 purple_account_get_enabled(account
, purple_core_get_ui());
5148 primary
= g_strdup_printf(_("%s disconnected"), username
);
5150 primary
= g_strdup_printf(_("%s disabled"), username
);
5152 mini_dialog
= pidgin_make_mini_dialog(NULL
, "dialog-error",
5153 primary
, err
->description
, account
,
5154 (enabled
? _("Reconnect") : _("Re-enable")),
5155 (enabled
? PURPLE_CALLBACK(purple_account_connect
)
5156 : PURPLE_CALLBACK(generic_error_enable_cb
)),
5157 _("Modify Account"), PURPLE_CALLBACK(generic_error_modify_cb
),
5162 g_object_set_data(G_OBJECT(mini_dialog
), OBJECT_DATA_KEY_ACCOUNT
,
5165 if(err
->type
== PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT
)
5166 pidgin_mini_dialog_add_non_closing_button(PIDGIN_MINI_DIALOG(mini_dialog
),
5167 _("SSL FAQs"), ssl_faq_clicked_cb
, NULL
);
5169 g_signal_connect_after(mini_dialog
, "destroy",
5170 (GCallback
)generic_error_destroy_cb
,
5173 add_error_dialog(gtkblist
, mini_dialog
);
5177 remove_generic_error_dialog(PurpleAccount
*account
)
5179 PidginBuddyListPrivate
*priv
=
5180 pidgin_buddy_list_get_instance_private(gtkblist
);
5181 remove_child_widget_by_account(
5182 GTK_CONTAINER(priv
->error_scrollbook
), account
);
5187 update_generic_error_message(PurpleAccount
*account
,
5188 const char *description
)
5190 PidginBuddyListPrivate
*priv
=
5191 pidgin_buddy_list_get_instance_private(gtkblist
);
5192 GtkWidget
*mini_dialog
= find_child_widget_by_account(
5193 GTK_CONTAINER(priv
->error_scrollbook
), account
);
5194 pidgin_mini_dialog_set_description(PIDGIN_MINI_DIALOG(mini_dialog
),
5199 /* Notifications about accounts which were disconnected with
5200 * PURPLE_CONNECTION_ERROR_NAME_IN_USE
5203 typedef void (*AccountFunction
)(PurpleAccount
*);
5206 elsewhere_foreach_account(PidginMiniDialog
*mini_dialog
,
5209 PurpleAccount
*account
;
5210 GList
*labels
= gtk_container_get_children(
5211 GTK_CONTAINER(mini_dialog
->contents
));
5214 for (l
= labels
; l
; l
= l
->next
) {
5215 account
= g_object_get_data(G_OBJECT(l
->data
), OBJECT_DATA_KEY_ACCOUNT
);
5219 purple_debug_warning("gtkblist", "mini_dialog's child "
5220 "didn't have an account stored in it!");
5222 g_list_free(labels
);
5226 enable_account(PurpleAccount
*account
)
5228 purple_account_set_enabled(account
, purple_core_get_ui(), TRUE
);
5232 reconnect_elsewhere_accounts(PidginMiniDialog
*mini_dialog
,
5236 elsewhere_foreach_account(mini_dialog
, enable_account
);
5240 clear_elsewhere_errors(PidginMiniDialog
*mini_dialog
,
5243 elsewhere_foreach_account(mini_dialog
, purple_account_clear_current_error
);
5247 ensure_signed_on_elsewhere_minidialog(PidginBuddyList
*gtkblist
)
5249 PidginBuddyListPrivate
*priv
=
5250 pidgin_buddy_list_get_instance_private(gtkblist
);
5251 PidginMiniDialog
*mini_dialog
;
5253 if(priv
->signed_on_elsewhere
)
5256 mini_dialog
= priv
->signed_on_elsewhere
=
5257 pidgin_mini_dialog_new(_("Welcome back!"), NULL
, PIDGIN_STOCK_DISCONNECT
);
5259 pidgin_mini_dialog_add_button(mini_dialog
, _("Re-enable"),
5260 reconnect_elsewhere_accounts
, NULL
);
5262 /* Make dismissing the dialog clear the errors. The "destroy" signal
5263 * does not appear to fire at quit, which is fortunate!
5265 g_signal_connect(G_OBJECT(mini_dialog
), "destroy",
5266 (GCallback
) clear_elsewhere_errors
, NULL
);
5268 add_error_dialog(gtkblist
, GTK_WIDGET(mini_dialog
));
5270 /* Set priv->signed_on_elsewhere to NULL when the dialog is destroyed */
5271 g_signal_connect(G_OBJECT(mini_dialog
), "destroy",
5272 (GCallback
) gtk_widget_destroyed
, &(priv
->signed_on_elsewhere
));
5276 update_signed_on_elsewhere_minidialog_title(void)
5278 PidginBuddyListPrivate
*priv
=
5279 pidgin_buddy_list_get_instance_private(gtkblist
);
5280 PidginMiniDialog
*mini_dialog
= priv
->signed_on_elsewhere
;
5284 if (mini_dialog
== NULL
)
5287 accounts
= pidgin_mini_dialog_get_num_children(mini_dialog
);
5288 if (accounts
== 0) {
5289 gtk_widget_destroy(GTK_WIDGET(mini_dialog
));
5293 title
= g_strdup_printf(
5294 ngettext("%d account was disabled because you signed on from another location:",
5295 "%d accounts were disabled because you signed on from another location:",
5298 pidgin_mini_dialog_set_description(mini_dialog
, title
);
5303 create_account_label(PurpleAccount
*account
)
5305 GtkWidget
*hbox
, *label
;
5306 const char *username
= purple_account_get_username(account
);
5310 hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 6);
5311 g_object_set_data(G_OBJECT(hbox
), OBJECT_DATA_KEY_ACCOUNT
, account
);
5313 pack_protocol_icon_start(hbox
, account
);
5315 label
= gtk_label_new(NULL
);
5316 markup
= g_strdup_printf("<span size=\"smaller\">%s</span>", username
);
5317 gtk_label_set_markup(GTK_LABEL(label
), markup
);
5319 gtk_label_set_xalign(GTK_LABEL(label
), 0);
5320 gtk_label_set_yalign(GTK_LABEL(label
), 0);
5321 g_object_set(G_OBJECT(label
), "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
5322 description
= purple_account_get_current_error(account
)->description
;
5323 if (description
!= NULL
&& *description
!= '\0')
5324 gtk_widget_set_tooltip_text(label
, description
);
5325 gtk_box_pack_start(GTK_BOX(hbox
), label
, TRUE
, TRUE
, 0);
5331 add_to_signed_on_elsewhere(PurpleAccount
*account
)
5333 PidginBuddyListPrivate
*priv
=
5334 pidgin_buddy_list_get_instance_private(gtkblist
);
5335 PidginMiniDialog
*mini_dialog
;
5336 GtkWidget
*account_label
;
5338 ensure_signed_on_elsewhere_minidialog(gtkblist
);
5339 mini_dialog
= priv
->signed_on_elsewhere
;
5341 if(find_child_widget_by_account(GTK_CONTAINER(mini_dialog
->contents
), account
))
5344 account_label
= create_account_label(account
);
5345 gtk_box_pack_start(mini_dialog
->contents
, account_label
, FALSE
, FALSE
, 0);
5346 gtk_widget_show_all(account_label
);
5348 update_signed_on_elsewhere_minidialog_title();
5352 remove_from_signed_on_elsewhere(PurpleAccount
*account
)
5354 PidginBuddyListPrivate
*priv
=
5355 pidgin_buddy_list_get_instance_private(gtkblist
);
5356 PidginMiniDialog
*mini_dialog
= priv
->signed_on_elsewhere
;
5357 if(mini_dialog
== NULL
)
5360 remove_child_widget_by_account(GTK_CONTAINER(mini_dialog
->contents
), account
);
5362 update_signed_on_elsewhere_minidialog_title();
5367 update_signed_on_elsewhere_tooltip(PurpleAccount
*account
,
5368 const char *description
)
5370 PidginBuddyListPrivate
*priv
=
5371 pidgin_buddy_list_get_instance_private(gtkblist
);
5372 GtkContainer
*c
= GTK_CONTAINER(priv
->signed_on_elsewhere
->contents
);
5373 GtkWidget
*label
= find_child_widget_by_account(c
, account
);
5374 gtk_widget_set_tooltip_text(label
, description
);
5378 /* Call appropriate error notification code based on error types */
5380 update_account_error_state(PurpleAccount
*account
,
5381 const PurpleConnectionErrorInfo
*old
,
5382 const PurpleConnectionErrorInfo
*new,
5383 PidginBuddyList
*gtkblist
)
5385 gboolean descriptions_differ
;
5388 if (old
== NULL
&& new == NULL
)
5392 pidgin_blist_select_notebook_page(gtkblist
);
5394 if (old
!= NULL
&& new == NULL
) {
5395 if(old
->type
== PURPLE_CONNECTION_ERROR_NAME_IN_USE
)
5396 remove_from_signed_on_elsewhere(account
);
5398 remove_generic_error_dialog(account
);
5402 if (old
== NULL
&& new != NULL
) {
5403 if(new->type
== PURPLE_CONNECTION_ERROR_NAME_IN_USE
)
5404 add_to_signed_on_elsewhere(account
);
5406 add_generic_error_dialog(account
, new);
5410 /* else, new and old are both non-NULL */
5412 descriptions_differ
= !purple_strequal(old
->description
, new->description
);
5413 desc
= new->description
;
5415 switch (new->type
) {
5416 case PURPLE_CONNECTION_ERROR_NAME_IN_USE
:
5417 if (old
->type
== PURPLE_CONNECTION_ERROR_NAME_IN_USE
5418 && descriptions_differ
) {
5419 update_signed_on_elsewhere_tooltip(account
, desc
);
5421 remove_generic_error_dialog(account
);
5422 add_to_signed_on_elsewhere(account
);
5426 if (old
->type
== PURPLE_CONNECTION_ERROR_NAME_IN_USE
) {
5427 remove_from_signed_on_elsewhere(account
);
5428 add_generic_error_dialog(account
, new);
5429 } else if (descriptions_differ
) {
5430 update_generic_error_message(account
, desc
);
5436 /* In case accounts are loaded before the blist (which they currently are),
5437 * let's call update_account_error_state ourselves on every account's current
5438 * state when the blist starts.
5441 show_initial_account_errors(PidginBuddyList
*gtkblist
)
5443 GList
*l
= purple_accounts_get_all();
5444 PurpleAccount
*account
;
5445 const PurpleConnectionErrorInfo
*err
;
5447 for (; l
; l
= l
->next
)
5450 err
= purple_account_get_current_error(account
);
5452 update_account_error_state(account
, NULL
, err
, gtkblist
);
5456 /* This assumes there are not things like groupless buddies or multi-leveled groups.
5457 * I'm sure other things in this code assumes that also.
5460 treeview_style_set(GtkWidget
*widget
,
5463 PurpleBuddyList
*list
= data
;
5464 PurpleBlistNode
*node
= purple_blist_get_root(list
);
5466 pidgin_blist_update_group(list
, node
);
5471 /******************************************/
5472 /* End of connection error handling stuff */
5473 /******************************************/
5476 blist_focus_cb(GtkWidget
*widget
, GdkEventFocus
*event
, PidginBuddyList
*gtkblist
)
5479 gtk_blist_focused
= TRUE
;
5480 pidgin_set_urgent(GTK_WINDOW(gtkblist
->window
), FALSE
);
5482 gtk_blist_focused
= FALSE
;
5491 GtkWidget
*ret
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, PIDGIN_HIG_BOX_SPACE
);
5497 label
= gtk_label_new(NULL
);
5498 gtk_box_pack_start(GTK_BOX(ret
), label
, TRUE
, TRUE
, 0);
5500 label
= gtk_label_new(NULL
);
5501 gtk_label_set_markup(GTK_LABEL(label
), _("<b>Username:</b>"));
5502 gtk_label_set_xalign(GTK_LABEL(label
), 0.0);
5503 gtk_box_pack_start(GTK_BOX(ret
), label
, FALSE
, FALSE
, 0);
5504 entry
= gtk_entry_new();
5505 gtk_box_pack_start(GTK_BOX(ret
), entry
, FALSE
, FALSE
, 0);
5507 label
= gtk_label_new(NULL
);
5508 gtk_label_set_markup(GTK_LABEL(label
), _("<b>Password:</b>"));
5509 gtk_label_set_xalign(GTK_LABEL(label
), 0.0);
5510 gtk_box_pack_start(GTK_BOX(ret
), label
, FALSE
, FALSE
, 0);
5511 entry
= gtk_entry_new();
5512 gtk_entry_set_visibility(GTK_ENTRY(entry
), FALSE
);
5513 gtk_box_pack_start(GTK_BOX(ret
), entry
, FALSE
, FALSE
, 0);
5515 label
= gtk_label_new(" ");
5516 gtk_box_pack_start(GTK_BOX(ret
), label
, FALSE
, FALSE
, 0);
5518 bbox
= gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL
);
5519 button
= gtk_button_new_with_mnemonic(_("_Login"));
5520 gtk_box_pack_start(GTK_BOX(ret
), bbox
, FALSE
, FALSE
, 0);
5521 gtk_container_add(GTK_CONTAINER(bbox
), button
);
5524 label
= gtk_label_new(NULL
);
5525 gtk_box_pack_start(GTK_BOX(ret
), label
, TRUE
, TRUE
, 0);
5527 gtk_container_set_border_width(GTK_CONTAINER(ret
), PIDGIN_HIG_BORDER
);
5529 gtk_widget_show_all(ret
);
5534 /* builds the blist layout according to to the current theme */
5536 pidgin_blist_build_layout(PurpleBuddyList
*list
)
5538 GtkTreeViewColumn
*column
;
5539 PidginBlistLayout
*layout
;
5540 PidginBlistTheme
*theme
;
5541 GtkCellRenderer
*rend
;
5542 gint i
, status_icon
= 0, text
= 1, emblem
= 2, protocol_icon
= 3, buddy_icon
= 4;
5545 column
= gtkblist
->text_column
;
5547 if ((theme
= pidgin_blist_get_theme()) != NULL
&& (layout
= pidgin_blist_theme_get_layout(theme
)) != NULL
) {
5548 status_icon
= layout
->status_icon
;
5549 text
= layout
->text
;
5550 emblem
= layout
->emblem
;
5551 protocol_icon
= layout
->protocol_icon
;
5552 buddy_icon
= layout
->buddy_icon
;
5555 gtk_tree_view_column_clear(column
);
5558 rend
= pidgin_cell_renderer_expander_new();
5559 gtk_tree_view_column_pack_start(column
, rend
, FALSE
);
5560 gtk_tree_view_column_set_attributes(column
, rend
,
5561 "visible", GROUP_EXPANDER_VISIBLE_COLUMN
,
5562 "expander-visible", GROUP_EXPANDER_COLUMN
,
5563 "sensitive", GROUP_EXPANDER_COLUMN
,
5564 "cell-background-rgba", BGCOLOR_COLUMN
,
5568 rend
= pidgin_cell_renderer_expander_new();
5569 gtk_tree_view_column_pack_start(column
, rend
, FALSE
);
5570 gtk_tree_view_column_set_attributes(column
, rend
,
5571 "visible", CONTACT_EXPANDER_VISIBLE_COLUMN
,
5572 "expander-visible", CONTACT_EXPANDER_COLUMN
,
5573 "sensitive", CONTACT_EXPANDER_COLUMN
,
5574 "cell-background-rgba", BGCOLOR_COLUMN
,
5577 for (i
= 0; i
< 5; i
++) {
5579 if (status_icon
== i
) {
5581 rend
= gtk_cell_renderer_pixbuf_new();
5582 gtk_tree_view_column_pack_start(column
, rend
, FALSE
);
5583 gtk_tree_view_column_set_attributes(column
, rend
,
5584 "pixbuf", STATUS_ICON_COLUMN
,
5585 "visible", STATUS_ICON_VISIBLE_COLUMN
,
5586 "cell-background-rgba", BGCOLOR_COLUMN
,
5588 g_object_set(rend
, "xalign", 0.0, "xpad", 6, "ypad", 0, NULL
);
5590 } else if (text
== i
) {
5592 gtkblist
->text_rend
= rend
= gtk_cell_renderer_text_new();
5593 gtk_tree_view_column_pack_start(column
, rend
, TRUE
);
5594 gtk_tree_view_column_set_attributes(column
, rend
,
5595 "cell-background-rgba", BGCOLOR_COLUMN
,
5596 "markup", NAME_COLUMN
,
5599 G_OBJECT(rend
), "editing-started",
5600 G_CALLBACK(gtk_blist_renderer_editing_started_cb
),
5602 g_signal_connect(G_OBJECT(rend
), "editing-canceled", G_CALLBACK(gtk_blist_renderer_editing_cancelled_cb
), list
);
5603 g_signal_connect(G_OBJECT(rend
), "edited", G_CALLBACK(gtk_blist_renderer_edited_cb
), list
);
5604 g_object_set(rend
, "ypad", 0, "yalign", 0.5, NULL
);
5605 g_object_set(rend
, "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
5608 rend
= gtk_cell_renderer_text_new();
5609 g_object_set(rend
, "xalign", 1.0, "ypad", 0, NULL
);
5610 gtk_tree_view_column_pack_start(column
, rend
, FALSE
);
5611 gtk_tree_view_column_set_attributes(column
, rend
,
5612 "markup", IDLE_COLUMN
,
5613 "visible", IDLE_VISIBLE_COLUMN
,
5614 "cell-background-rgba", BGCOLOR_COLUMN
,
5616 } else if (emblem
== i
) {
5618 rend
= gtk_cell_renderer_pixbuf_new();
5619 g_object_set(rend
, "xalign", 1.0, "yalign", 0.5, "ypad", 0, "xpad", 3, NULL
);
5620 gtk_tree_view_column_pack_start(column
, rend
, FALSE
);
5621 gtk_tree_view_column_set_attributes(column
, rend
, "pixbuf", EMBLEM_COLUMN
,
5622 "cell-background-rgba", BGCOLOR_COLUMN
,
5623 "visible", EMBLEM_VISIBLE_COLUMN
, NULL
);
5625 } else if (protocol_icon
== i
) {
5627 rend
= gtk_cell_renderer_pixbuf_new();
5628 gtk_tree_view_column_pack_start(column
, rend
, FALSE
);
5629 gtk_tree_view_column_set_attributes(column
, rend
,
5630 "pixbuf", PROTOCOL_ICON_COLUMN
,
5631 "visible", PROTOCOL_ICON_VISIBLE_COLUMN
,
5632 "cell-background-rgba", BGCOLOR_COLUMN
,
5634 g_object_set(rend
, "xalign", 0.0, "xpad", 3, "ypad", 0, NULL
);
5636 } else if (buddy_icon
== i
) {
5638 rend
= gtk_cell_renderer_pixbuf_new();
5639 g_object_set(rend
, "xalign", 1.0, "ypad", 0, NULL
);
5640 gtk_tree_view_column_pack_start(column
, rend
, FALSE
);
5641 gtk_tree_view_column_set_attributes(column
, rend
, "pixbuf", BUDDY_ICON_COLUMN
,
5642 "cell-background-rgba", BGCOLOR_COLUMN
,
5643 "visible", BUDDY_ICON_VISIBLE_COLUMN
,
5652 pidgin_blist_search_equal_func(GtkTreeModel
*model
, gint column
,
5653 const gchar
*key
, GtkTreeIter
*iter
, gpointer data
)
5655 PurpleBlistNode
*node
= NULL
;
5656 gboolean res
= TRUE
;
5657 const char *compare
= NULL
;
5659 if (!pidgin_tree_view_search_equal_func(model
, column
, key
, iter
, data
))
5662 /* If the search string does not match the displayed label, then look
5663 * at the alternate labels for the nodes and search in them. Currently,
5664 * alternate labels that make sense are usernames/email addresses for
5665 * buddies (but only for the ones who don't have a local alias).
5668 gtk_tree_model_get(model
, iter
, NODE_COLUMN
, &node
, -1);
5673 if (PURPLE_IS_CONTACT(node
)) {
5674 PurpleBuddy
*b
= purple_contact_get_priority_buddy(PURPLE_CONTACT(node
));
5675 if (!purple_buddy_get_local_alias(b
))
5676 compare
= purple_buddy_get_name(b
);
5677 } else if (PURPLE_IS_BUDDY(node
)) {
5678 if (!purple_buddy_get_local_alias(PURPLE_BUDDY(node
)))
5679 compare
= purple_buddy_get_name(PURPLE_BUDDY(node
));
5683 char *tmp
, *enteredstring
;
5684 tmp
= g_utf8_normalize(key
, -1, G_NORMALIZE_DEFAULT
);
5685 enteredstring
= g_utf8_casefold(tmp
, -1);
5688 if (purple_str_has_prefix(compare
, enteredstring
))
5690 g_free(enteredstring
);
5696 static void pidgin_blist_show(PurpleBuddyList
*list
)
5698 PidginBuddyListPrivate
*priv
;
5700 GtkTreeViewColumn
*column
;
5704 GtkWidget
*content_area
;
5708 const char *theme_name
;
5709 GtkActionGroup
*action_group
;
5711 GtkAccelGroup
*accel_group
;
5712 GtkTreeSelection
*selection
;
5713 GtkTargetEntry dte
[] = {{"PURPLE_BLIST_NODE", GTK_TARGET_SAME_APP
, DRAG_ROW
},
5714 {"application/x-im-contact", 0, DRAG_BUDDY
},
5715 {"text/x-vcard", 0, DRAG_VCARD
},
5716 {"text/uri-list", 0, DRAG_URI
},
5717 {"text/plain", 0, DRAG_TEXT
}};
5718 GtkTargetEntry ste
[] = {{"PURPLE_BLIST_NODE", GTK_TARGET_SAME_APP
, DRAG_ROW
},
5719 {"application/x-im-contact", 0, DRAG_BUDDY
},
5720 {"text/x-vcard", 0, DRAG_VCARD
}};
5721 if (gtkblist
&& gtkblist
->window
) {
5722 purple_blist_set_visible(purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/list_visible"));
5726 gtkblist
= PIDGIN_BUDDY_LIST(list
);
5727 priv
= pidgin_buddy_list_get_instance_private(gtkblist
);
5729 if (priv
->current_theme
)
5730 g_object_unref(priv
->current_theme
);
5732 theme_name
= purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/blist/theme");
5733 if (theme_name
&& *theme_name
)
5734 priv
->current_theme
= g_object_ref(PIDGIN_BLIST_THEME(purple_theme_manager_find_theme(theme_name
, "blist")));
5736 priv
->current_theme
= NULL
;
5738 gtkblist
->empty_avatar
= gdk_pixbuf_new(GDK_COLORSPACE_RGB
, TRUE
, 8, 32, 32);
5739 gdk_pixbuf_fill(gtkblist
->empty_avatar
, 0x00000000);
5741 gtkblist
->window
= pidgin_create_window(_("Buddy List"), 0, "buddy_list", TRUE
);
5742 g_signal_connect(G_OBJECT(gtkblist
->window
), "focus-in-event",
5743 G_CALLBACK(blist_focus_cb
), gtkblist
);
5744 g_signal_connect(G_OBJECT(gtkblist
->window
), "focus-out-event",
5745 G_CALLBACK(blist_focus_cb
), gtkblist
);
5747 gtkblist
->main_vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0);
5748 gtk_widget_show(gtkblist
->main_vbox
);
5749 gtk_container_add(GTK_CONTAINER(gtkblist
->window
), gtkblist
->main_vbox
);
5751 g_signal_connect(G_OBJECT(gtkblist
->window
), "delete_event", G_CALLBACK(gtk_blist_delete_cb
), NULL
);
5752 g_signal_connect(G_OBJECT(gtkblist
->window
), "hide",
5753 G_CALLBACK(gtk_blist_hide_cb
), gtkblist
);
5754 g_signal_connect(G_OBJECT(gtkblist
->window
), "show",
5755 G_CALLBACK(gtk_blist_show_cb
), gtkblist
);
5756 g_signal_connect(G_OBJECT(gtkblist
->window
), "size-allocate",
5757 G_CALLBACK(gtk_blist_size_allocate_cb
), NULL
);
5758 g_signal_connect(G_OBJECT(gtkblist
->window
), "visibility_notify_event", G_CALLBACK(gtk_blist_visibility_cb
), NULL
);
5759 g_signal_connect(G_OBJECT(gtkblist
->window
), "window_state_event", G_CALLBACK(gtk_blist_window_state_cb
), NULL
);
5760 g_signal_connect(G_OBJECT(gtkblist
->window
), "key_press_event", G_CALLBACK(gtk_blist_window_key_press_cb
), gtkblist
);
5761 gtk_widget_add_events(gtkblist
->window
, GDK_VISIBILITY_NOTIFY_MASK
);
5763 /******************************* Menu bar *************************************/
5764 action_group
= gtk_action_group_new("BListActions");
5765 gtk_action_group_set_translation_domain(action_group
, PACKAGE
);
5766 gtk_action_group_add_actions(action_group
,
5768 G_N_ELEMENTS(blist_menu_entries
),
5769 GTK_WINDOW(gtkblist
->window
));
5770 gtk_action_group_add_toggle_actions(action_group
,
5771 blist_menu_toggle_entries
,
5772 G_N_ELEMENTS(blist_menu_toggle_entries
),
5773 GTK_WINDOW(gtkblist
->window
));
5775 gtkblist
->ui
= gtk_ui_manager_new();
5776 gtk_ui_manager_insert_action_group(gtkblist
->ui
, action_group
, 0);
5778 accel_group
= gtk_ui_manager_get_accel_group(gtkblist
->ui
);
5779 gtk_window_add_accel_group(GTK_WINDOW(gtkblist
->window
), accel_group
);
5780 pidgin_load_accels();
5781 g_signal_connect(G_OBJECT(accel_group
), "accel-changed", G_CALLBACK(pidgin_save_accels_cb
), NULL
);
5784 if (!gtk_ui_manager_add_ui_from_string(gtkblist
->ui
, blist_menu
, -1, &error
))
5786 g_message("building menus failed: %s", error
->message
);
5787 g_error_free(error
);
5791 menu
= gtk_ui_manager_get_widget(gtkblist
->ui
, "/BList");
5792 gtkblist
->menutray
= pidgin_menu_tray_new();
5793 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), gtkblist
->menutray
);
5794 gtk_widget_show(gtkblist
->menutray
);
5795 gtk_widget_show(menu
);
5796 gtk_box_pack_start(GTK_BOX(gtkblist
->main_vbox
), menu
, FALSE
, FALSE
, 0);
5798 menu
= gtk_ui_manager_get_widget(gtkblist
->ui
, "/BList/AccountsMenu");
5799 accountmenu
= gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu
));
5801 /****************************** Notebook *************************************/
5802 gtkblist
->notebook
= gtk_notebook_new();
5803 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(gtkblist
->notebook
), FALSE
);
5804 gtk_notebook_set_show_border(GTK_NOTEBOOK(gtkblist
->notebook
), FALSE
);
5805 gtk_box_pack_start(GTK_BOX(gtkblist
->main_vbox
), gtkblist
->notebook
, TRUE
, TRUE
, 0);
5808 gtk_notebook_append_page(GTK_NOTEBOOK(gtkblist
->notebook
), kiosk_page(), NULL
);
5811 /* Translators: Please maintain the use of ⇦ or ⇨ to refer to menu hierarchy */
5812 text
= g_strdup_printf(_("<span weight='bold' size='larger'>Welcome to %s!</span>\n\n"
5814 "You have no accounts enabled. Enable your IM accounts from the "
5815 "<b>Accounts</b> window at <b>Accounts⇨Manage Accounts</b>. Once you "
5816 "enable accounts, you'll be able to sign on, set your status, "
5817 "and talk to your friends."), PIDGIN_NAME
);
5818 label
= gtk_label_new(NULL
);
5819 gtk_label_set_line_wrap(GTK_LABEL(label
), TRUE
);
5820 gtk_label_set_yalign(GTK_LABEL(label
), 0.2);
5821 gtk_label_set_markup(GTK_LABEL(label
), text
);
5823 gtk_notebook_append_page(GTK_NOTEBOOK(gtkblist
->notebook
),label
, NULL
);
5824 gtkblist
->vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0);
5825 gtk_notebook_append_page(GTK_NOTEBOOK(gtkblist
->notebook
), gtkblist
->vbox
, NULL
);
5826 gtk_widget_show_all(gtkblist
->notebook
);
5827 pidgin_blist_select_notebook_page(gtkblist
);
5829 /****************************** Headline **********************************/
5831 gtkblist
->headline
= gtk_event_box_new();
5832 gtk_box_pack_start(GTK_BOX(gtkblist
->vbox
), gtkblist
->headline
,
5834 infobar
= gtk_info_bar_new();
5835 gtk_container_add(GTK_CONTAINER(gtkblist
->headline
), infobar
);
5836 gtk_info_bar_set_default_response(GTK_INFO_BAR(infobar
), GTK_RESPONSE_OK
);
5837 gtk_info_bar_set_message_type(GTK_INFO_BAR(infobar
), GTK_MESSAGE_INFO
);
5839 content_area
= gtk_info_bar_get_content_area(GTK_INFO_BAR(infobar
));
5840 gtkblist
->headline_image
= gtk_image_new_from_pixbuf(NULL
);
5841 gtk_widget_set_halign(gtkblist
->headline_image
, GTK_ALIGN_CENTER
);
5842 gtk_widget_set_valign(gtkblist
->headline_image
, GTK_ALIGN_CENTER
);
5843 gtkblist
->headline_label
= gtk_label_new(NULL
);
5844 gtk_label_set_line_wrap(GTK_LABEL(gtkblist
->headline_label
), TRUE
);
5845 gtk_box_pack_start(GTK_BOX(content_area
), gtkblist
->headline_image
,
5847 gtk_box_pack_start(GTK_BOX(content_area
), gtkblist
->headline_label
,
5850 close
= gtk_image_new_from_stock(GTK_STOCK_CLOSE
, GTK_ICON_SIZE_MENU
);
5851 close
= pidgin_create_small_button(close
);
5852 gtk_widget_set_tooltip_text(close
, _("Close"));
5853 gtk_info_bar_add_action_widget(GTK_INFO_BAR(infobar
), close
,
5854 GTK_RESPONSE_CLOSE
);
5856 g_signal_connect(infobar
, "response", G_CALLBACK(headline_response_cb
),
5858 g_signal_connect(infobar
, "close", G_CALLBACK(gtk_info_bar_response
),
5859 GINT_TO_POINTER(GTK_RESPONSE_CLOSE
));
5860 g_signal_connect(gtkblist
->headline
, "realize",
5861 G_CALLBACK(headline_realize_cb
), NULL
);
5862 g_signal_connect(gtkblist
->headline
, "button-press-event",
5863 G_CALLBACK(headline_press_cb
), infobar
);
5865 /****************************** GtkTreeView **********************************/
5866 gtkblist
->treemodel
= gtk_tree_store_new(BLIST_COLUMNS
,
5867 GDK_TYPE_PIXBUF
, /* Status icon */
5868 G_TYPE_BOOLEAN
, /* Status icon visible */
5869 G_TYPE_STRING
, /* Name */
5870 G_TYPE_STRING
, /* Idle */
5871 G_TYPE_BOOLEAN
, /* Idle visible */
5872 GDK_TYPE_PIXBUF
, /* Buddy icon */
5873 G_TYPE_BOOLEAN
, /* Buddy icon visible */
5874 G_TYPE_POINTER
, /* Node */
5875 GDK_TYPE_RGBA
, /* bgcolor */
5876 G_TYPE_BOOLEAN
, /* Group expander */
5877 G_TYPE_BOOLEAN
, /* Group expander visible */
5878 G_TYPE_BOOLEAN
, /* Contact expander */
5879 G_TYPE_BOOLEAN
, /* Contact expander visible */
5880 GDK_TYPE_PIXBUF
, /* Emblem */
5881 G_TYPE_BOOLEAN
, /* Emblem visible */
5882 GDK_TYPE_PIXBUF
, /* Protocol icon */
5883 G_TYPE_BOOLEAN
/* Protocol visible */
5886 gtkblist
->treeview
= gtk_tree_view_new_with_model(GTK_TREE_MODEL(gtkblist
->treemodel
));
5888 gtk_widget_show(gtkblist
->treeview
);
5889 gtk_widget_set_name(gtkblist
->treeview
, "pidgin_blist_treeview");
5891 g_signal_connect(gtkblist
->treeview
,
5893 G_CALLBACK(treeview_style_set
), list
);
5894 /* Set up selection stuff */
5895 selection
= gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist
->treeview
));
5896 g_signal_connect(G_OBJECT(selection
), "changed", G_CALLBACK(pidgin_blist_selection_changed
), NULL
);
5899 gtk_tree_view_enable_model_drag_source(GTK_TREE_VIEW(gtkblist
->treeview
),
5900 GDK_BUTTON1_MASK
, ste
, 3,
5902 gtk_tree_view_enable_model_drag_dest(GTK_TREE_VIEW(gtkblist
->treeview
),
5904 GDK_ACTION_COPY
| GDK_ACTION_MOVE
);
5906 g_signal_connect(G_OBJECT(gtkblist
->treeview
), "drag-data-received", G_CALLBACK(pidgin_blist_drag_data_rcv_cb
), NULL
);
5907 g_signal_connect(G_OBJECT(gtkblist
->treeview
), "drag-data-get", G_CALLBACK(pidgin_blist_drag_data_get_cb
), NULL
);
5909 g_signal_connect(G_OBJECT(gtkblist
->treeview
), "drag-begin", G_CALLBACK(pidgin_blist_drag_begin
), NULL
);
5911 g_signal_connect(G_OBJECT(gtkblist
->treeview
), "drag-motion", G_CALLBACK(pidgin_blist_drag_motion_cb
), NULL
);
5912 g_signal_connect(G_OBJECT(gtkblist
->treeview
), "motion-notify-event", G_CALLBACK(pidgin_blist_motion_cb
), NULL
);
5913 g_signal_connect(G_OBJECT(gtkblist
->treeview
), "leave-notify-event", G_CALLBACK(pidgin_blist_leave_cb
), NULL
);
5916 pidgin_tooltip_setup_for_treeview(gtkblist
->treeview
, NULL
,
5917 pidgin_blist_create_tooltip
,
5918 pidgin_blist_paint_tip
);
5920 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(gtkblist
->treeview
), FALSE
);
5922 /* expander columns */
5923 column
= gtk_tree_view_column_new();
5924 gtk_tree_view_append_column(GTK_TREE_VIEW(gtkblist
->treeview
), column
);
5925 gtk_tree_view_column_set_visible(column
, FALSE
);
5926 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(gtkblist
->treeview
), column
);
5928 /* everything else column */
5929 gtkblist
->text_column
= gtk_tree_view_column_new ();
5930 gtk_tree_view_append_column(GTK_TREE_VIEW(gtkblist
->treeview
), gtkblist
->text_column
);
5931 pidgin_blist_build_layout(list
);
5933 g_signal_connect(G_OBJECT(gtkblist
->treeview
), "row-activated",
5934 G_CALLBACK(gtk_blist_row_activated_cb
), gtkblist
);
5935 g_signal_connect(G_OBJECT(gtkblist
->treeview
), "row-expanded",
5936 G_CALLBACK(gtk_blist_row_expanded_cb
), gtkblist
);
5937 g_signal_connect(G_OBJECT(gtkblist
->treeview
), "row-collapsed",
5938 G_CALLBACK(gtk_blist_row_collapsed_cb
), gtkblist
);
5939 g_signal_connect(G_OBJECT(gtkblist
->treeview
), "button-press-event", G_CALLBACK(gtk_blist_button_press_cb
), NULL
);
5940 g_signal_connect(G_OBJECT(gtkblist
->treeview
), "key-press-event", G_CALLBACK(gtk_blist_key_press_cb
), NULL
);
5941 g_signal_connect(G_OBJECT(gtkblist
->treeview
), "popup-menu", G_CALLBACK(pidgin_blist_popup_menu_cb
), NULL
);
5943 /* Enable CTRL+F searching */
5944 gtk_tree_view_set_search_column(GTK_TREE_VIEW(gtkblist
->treeview
), NAME_COLUMN
);
5945 gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(gtkblist
->treeview
),
5946 pidgin_blist_search_equal_func
, NULL
, NULL
);
5948 gtk_box_pack_start(GTK_BOX(gtkblist
->vbox
),
5949 pidgin_make_scrollable(gtkblist
->treeview
, GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
, GTK_SHADOW_NONE
, -1, -1),
5952 sep
= gtk_separator_new(GTK_ORIENTATION_HORIZONTAL
);
5953 gtk_box_pack_start(GTK_BOX(gtkblist
->vbox
), sep
, FALSE
, FALSE
, 0);
5955 gtkblist
->scrollbook
= pidgin_scroll_book_new();
5956 gtk_box_pack_start(GTK_BOX(gtkblist
->vbox
), gtkblist
->scrollbook
, FALSE
, FALSE
, 0);
5958 priv
->error_scrollbook
= PIDGIN_SCROLL_BOOK(pidgin_scroll_book_new());
5959 gtk_box_pack_start(GTK_BOX(gtkblist
->vbox
),
5960 GTK_WIDGET(priv
->error_scrollbook
), FALSE
, FALSE
, 0);
5962 /* Add the statusbox */
5963 gtkblist
->statusbox
= pidgin_status_box_new();
5964 gtk_box_pack_start(GTK_BOX(gtkblist
->vbox
), gtkblist
->statusbox
, FALSE
, TRUE
, 0);
5965 gtk_widget_set_name(gtkblist
->statusbox
, "pidgin_blist_statusbox");
5966 gtk_widget_show(gtkblist
->statusbox
);
5968 /* set the Show Offline Buddies option. must be done
5969 * after the treeview or faceprint gets mad. -Robot101
5971 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(gtk_ui_manager_get_action(gtkblist
->ui
, "/BList/BuddiesMenu/ShowMenu/ShowOffline")),
5972 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_offline_buddies"));
5974 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(gtk_ui_manager_get_action(gtkblist
->ui
, "/BList/BuddiesMenu/ShowMenu/ShowEmptyGroups")),
5975 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_empty_groups"));
5977 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(gtk_ui_manager_get_action(gtkblist
->ui
, "/BList/ToolsMenu/MuteSounds")),
5978 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/sound/mute"));
5980 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(gtk_ui_manager_get_action(gtkblist
->ui
, "/BList/BuddiesMenu/ShowMenu/ShowBuddyDetails")),
5981 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_buddy_icons"));
5983 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(gtk_ui_manager_get_action(gtkblist
->ui
, "/BList/BuddiesMenu/ShowMenu/ShowIdleTimes")),
5984 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_idle_time"));
5986 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(gtk_ui_manager_get_action(gtkblist
->ui
, "/BList/BuddiesMenu/ShowMenu/ShowProtocolIcons")),
5987 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_protocol_icons"));
5989 if(purple_strequal(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/sound/method"), "none"))
5990 gtk_action_set_sensitive(gtk_ui_manager_get_action(gtkblist
->ui
, "/BList/ToolsMenu/MuteSounds"), FALSE
);
5992 /* Update some dynamic things */
5993 update_menu_bar(gtkblist
);
5994 pidgin_blist_update_plugin_actions();
5995 pidgin_blist_update_sort_methods();
5997 /* OK... let's show this bad boy. */
5998 pidgin_blist_refresh(list
);
5999 pidgin_blist_restore_window_state();
6000 gtk_widget_show_all(GTK_WIDGET(gtkblist
->vbox
));
6001 gtk_widget_realize(GTK_WIDGET(gtkblist
->window
));
6002 purple_blist_set_visible(purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/list_visible"));
6004 /* start the refresh timer */
6005 gtkblist
->refresh_timer
= g_timeout_add_seconds(30, (GSourceFunc
)pidgin_blist_refresh_timer
, list
);
6007 handle
= pidgin_blist_get_handle();
6009 /* things that affect how buddies are displayed */
6010 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/blist/show_buddy_icons",
6011 _prefs_change_redo_list
, NULL
);
6012 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/blist/show_idle_time",
6013 _prefs_change_redo_list
, NULL
);
6014 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/blist/show_empty_groups",
6015 _prefs_change_redo_list
, NULL
);
6016 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/blist/show_offline_buddies",
6017 _prefs_change_redo_list
, NULL
);
6018 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/blist/show_protocol_icons",
6019 _prefs_change_redo_list
, NULL
);
6022 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/blist/sort_type",
6023 _prefs_change_sort_method
, NULL
);
6026 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/sound/mute",
6027 pidgin_blist_mute_pref_cb
, NULL
);
6028 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/sound/method",
6029 pidgin_blist_sound_method_pref_cb
, NULL
);
6031 /* Setup some purple signal handlers. */
6033 handle
= purple_accounts_get_handle();
6034 purple_signal_connect(handle
, "account-enabled", gtkblist
,
6035 PURPLE_CALLBACK(account_modified
), gtkblist
);
6036 purple_signal_connect(handle
, "account-disabled", gtkblist
,
6037 PURPLE_CALLBACK(account_modified
), gtkblist
);
6038 purple_signal_connect(handle
, "account-removed", gtkblist
,
6039 PURPLE_CALLBACK(account_modified
), gtkblist
);
6040 purple_signal_connect(handle
, "account-status-changed", gtkblist
,
6041 PURPLE_CALLBACK(account_status_changed
),
6043 purple_signal_connect(handle
, "account-error-changed", gtkblist
,
6044 PURPLE_CALLBACK(update_account_error_state
),
6046 purple_signal_connect(handle
, "account-actions-changed", gtkblist
,
6047 PURPLE_CALLBACK(account_actions_changed
), NULL
);
6049 handle
= pidgin_accounts_get_handle();
6050 purple_signal_connect(handle
, "account-modified", gtkblist
,
6051 PURPLE_CALLBACK(account_modified
), gtkblist
);
6053 handle
= purple_connections_get_handle();
6054 purple_signal_connect(handle
, "signed-on", gtkblist
,
6055 PURPLE_CALLBACK(sign_on_off_cb
), list
);
6056 purple_signal_connect(handle
, "signed-off", gtkblist
,
6057 PURPLE_CALLBACK(sign_on_off_cb
), list
);
6059 handle
= purple_plugins_get_handle();
6060 purple_signal_connect(handle
, "plugin-load", gtkblist
,
6061 PURPLE_CALLBACK(plugin_changed_cb
), NULL
);
6062 purple_signal_connect(handle
, "plugin-unload", gtkblist
,
6063 PURPLE_CALLBACK(plugin_changed_cb
), NULL
);
6065 handle
= purple_conversations_get_handle();
6066 purple_signal_connect(handle
, "conversation-updated", gtkblist
,
6067 PURPLE_CALLBACK(conversation_updated_cb
),
6069 purple_signal_connect(handle
, "deleting-conversation", gtkblist
,
6070 PURPLE_CALLBACK(conversation_deleting_cb
),
6072 purple_signal_connect(handle
, "conversation-created", gtkblist
,
6073 PURPLE_CALLBACK(conversation_created_cb
),
6075 purple_signal_connect(handle
, "chat-joined", gtkblist
,
6076 PURPLE_CALLBACK(conversation_created_cb
),
6079 gtk_widget_hide(gtkblist
->headline
);
6081 show_initial_account_errors(gtkblist
);
6083 /* emit our created signal */
6084 handle
= pidgin_blist_get_handle();
6085 purple_signal_emit(handle
, "gtkblist-created", list
);
6088 static void redo_buddy_list(PurpleBuddyList
*list
, gboolean remove
, gboolean rerender
)
6090 PurpleBlistNode
*node
;
6092 gtkblist
= PIDGIN_BUDDY_LIST(list
);
6093 if(!gtkblist
|| !gtkblist
->treeview
)
6096 node
= purple_blist_get_root(list
);
6100 /* This is only needed when we're reverting to a non-GTK+ sorted
6101 * status. We shouldn't need to remove otherwise.
6103 if (remove
&& !PURPLE_IS_GROUP(node
))
6104 pidgin_blist_hide_node(list
, node
, FALSE
);
6106 if (PURPLE_IS_BUDDY(node
))
6107 pidgin_blist_update_buddy(list
, node
, rerender
);
6108 else if (PURPLE_IS_CHAT(node
))
6109 pidgin_blist_update(list
, node
);
6110 else if (PURPLE_IS_GROUP(node
))
6111 pidgin_blist_update(list
, node
);
6112 node
= purple_blist_node_next(node
, FALSE
);
6117 void pidgin_blist_refresh(PurpleBuddyList
*list
)
6119 redo_buddy_list(list
, FALSE
, TRUE
);
6123 pidgin_blist_update_refresh_timeout()
6125 PurpleBuddyList
*blist
;
6126 PidginBuddyList
*gtkblist
;
6128 blist
= purple_blist_get_default();
6129 gtkblist
= PIDGIN_BUDDY_LIST(blist
);
6131 gtkblist
->refresh_timer
= g_timeout_add_seconds(30,(GSourceFunc
)pidgin_blist_refresh_timer
, blist
);
6134 static gboolean
get_iter_from_node(PurpleBlistNode
*node
, GtkTreeIter
*iter
) {
6135 PidginBlistNode
*gtknode
= purple_blist_node_get_ui_data(node
);
6143 purple_debug_error("gtkblist", "get_iter_from_node was called, but we don't seem to have a blist\n");
6151 if ((path
= gtk_tree_row_reference_get_path(gtknode
->row
)) == NULL
)
6154 if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
), iter
, path
)) {
6155 gtk_tree_path_free(path
);
6158 gtk_tree_path_free(path
);
6162 static void pidgin_blist_remove(PurpleBuddyList
*list
, PurpleBlistNode
*node
)
6164 PidginBlistNode
*gtknode
= purple_blist_node_get_ui_data(node
);
6166 purple_request_close_with_handle(node
);
6168 pidgin_blist_hide_node(list
, node
, TRUE
);
6171 pidgin_blist_update(list
, node
->parent
);
6173 /* There's something I don't understand here - Ethan */
6174 /* Ethan said that back in 2003, but this g_free has been left commented
6175 * out ever since. I can't find any reason at all why this is bad and
6176 * valgrind found several reasons why it's good. If this causes problems
6177 * comment it out again. Stu */
6178 /* Of course it still causes problems - this breaks dragging buddies into
6179 * contacts, the dragged buddy mysteriously 'disappears'. Stu. */
6180 /* I think it's fixed now. Stu. */
6183 if(gtknode
->recent_signonoff_timer
> 0)
6184 g_source_remove(gtknode
->recent_signonoff_timer
);
6186 purple_signals_disconnect_by_handle(gtknode
);
6188 purple_blist_node_set_ui_data(node
, NULL
);
6192 static gboolean
do_selection_changed(PurpleBlistNode
*new_selection
)
6194 PurpleBlistNode
*old_selection
= NULL
;
6196 /* test for gtkblist because crazy timeout means we can be called after the blist is gone */
6197 if (gtkblist
&& new_selection
!= gtkblist
->selected_node
) {
6198 old_selection
= gtkblist
->selected_node
;
6199 gtkblist
->selected_node
= new_selection
;
6201 pidgin_blist_update(NULL
, new_selection
);
6203 pidgin_blist_update(NULL
, old_selection
);
6209 static void pidgin_blist_selection_changed(GtkTreeSelection
*selection
, gpointer data
)
6211 PurpleBlistNode
*new_selection
= NULL
;
6214 if(gtk_tree_selection_get_selected(selection
, NULL
, &iter
)){
6215 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
,
6216 NODE_COLUMN
, &new_selection
, -1);
6219 /* we set this up as a timeout, otherwise the blist flickers ...
6220 * but we don't do it for groups, because it causes total bizarness -
6221 * the previously selected buddy node might rendered at half height.
6223 if ((new_selection
!= NULL
) && PURPLE_IS_GROUP(new_selection
)) {
6224 do_selection_changed(new_selection
);
6226 g_timeout_add(0, (GSourceFunc
)do_selection_changed
, new_selection
);
6230 static gboolean
insert_node(PurpleBuddyList
*list
, PurpleBlistNode
*node
, GtkTreeIter
*iter
)
6232 GtkTreeIter parent_iter
= {0, NULL
, NULL
, NULL
}, cur
, *curptr
= NULL
;
6233 PidginBlistNode
*gtknode
= purple_blist_node_get_ui_data(node
);
6234 GtkTreePath
*newpath
;
6239 /* XXX: it's not necessary, but let's silence a warning*/
6240 memset(&parent_iter
, 0, sizeof(parent_iter
));
6242 if(node
->parent
&& !get_iter_from_node(node
->parent
, &parent_iter
))
6245 if(get_iter_from_node(node
, &cur
))
6248 if(PURPLE_IS_CONTACT(node
) || PURPLE_IS_CHAT(node
)) {
6249 current_sort_method
->func(node
, list
, parent_iter
, curptr
, iter
);
6251 sort_method_none(node
, list
, parent_iter
, curptr
, iter
);
6254 if(gtknode
!= NULL
) {
6255 gtk_tree_row_reference_free(gtknode
->row
);
6257 pidgin_blist_new_node(list
, node
);
6258 gtknode
= purple_blist_node_get_ui_data(node
);
6261 newpath
= gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist
->treemodel
),
6264 gtk_tree_row_reference_new(GTK_TREE_MODEL(gtkblist
->treemodel
),
6267 gtk_tree_path_free(newpath
);
6270 gtk_tree_store_set(gtkblist
->treemodel
, iter
,
6275 GtkTreePath
*expand
= NULL
;
6276 PidginBlistNode
*gtkparentnode
= purple_blist_node_get_ui_data(node
->parent
);
6278 if(PURPLE_IS_GROUP(node
->parent
)) {
6279 if(!purple_blist_node_get_bool(node
->parent
, "collapsed"))
6280 expand
= gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist
->treemodel
), &parent_iter
);
6281 } else if(PURPLE_IS_CONTACT(node
->parent
) &&
6282 gtkparentnode
->contact_expanded
) {
6283 expand
= gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist
->treemodel
), &parent_iter
);
6286 gtk_tree_view_expand_row(GTK_TREE_VIEW(gtkblist
->treeview
), expand
, FALSE
);
6287 gtk_tree_path_free(expand
);
6294 static gboolean
pidgin_blist_group_has_show_offline_buddy(PurpleGroup
*group
)
6296 PurpleBlistNode
*gnode
, *cnode
, *bnode
;
6298 gnode
= PURPLE_BLIST_NODE(group
);
6299 for(cnode
= gnode
->child
; cnode
; cnode
= cnode
->next
) {
6300 if(PURPLE_IS_CONTACT(cnode
)) {
6301 for(bnode
= cnode
->child
; bnode
; bnode
= bnode
->next
) {
6302 PurpleBuddy
*buddy
= (PurpleBuddy
*)bnode
;
6303 if (purple_account_is_connected(purple_buddy_get_account(buddy
)) &&
6304 purple_blist_node_get_bool(bnode
, "show_offline"))
6312 /* This version of pidgin_blist_update_group can take the original buddy or a
6313 * group, but has much better algorithmic performance with a pre-known buddy.
6315 static void pidgin_blist_update_group(PurpleBuddyList
*list
,
6316 PurpleBlistNode
*node
)
6320 PurpleBlistNode
* gnode
;
6321 gboolean show
= FALSE
, show_offline
= FALSE
;
6323 g_return_if_fail(node
!= NULL
);
6328 if (PURPLE_IS_GROUP(node
))
6330 else if (PURPLE_IS_BUDDY(node
))
6331 gnode
= node
->parent
->parent
;
6332 else if (PURPLE_IS_CONTACT(node
) || PURPLE_IS_CHAT(node
))
6333 gnode
= node
->parent
;
6337 group
= (PurpleGroup
*)gnode
;
6339 show_offline
= purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_offline_buddies");
6342 count
= purple_counting_node_get_current_size(PURPLE_COUNTING_NODE(group
));
6344 count
= purple_counting_node_get_online_count(PURPLE_COUNTING_NODE(group
));
6346 if (count
> 0 || purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_empty_groups"))
6348 else if (PURPLE_IS_BUDDY(node
) && buddy_is_displayable((PurpleBuddy
*)node
)) { /* Or chat? */
6350 } else if (!show_offline
) {
6351 show
= pidgin_blist_group_has_show_offline_buddy(group
);
6360 GdkRGBA
*bgcolor
= NULL
;
6361 GdkPixbuf
*avatar
= NULL
;
6362 PidginBlistTheme
*theme
= NULL
;
6364 if(!insert_node(list
, gnode
, &iter
))
6367 if ((theme
= pidgin_blist_get_theme()) == NULL
)
6369 else if (purple_blist_node_get_bool(gnode
, "collapsed") || count
<= 0)
6370 bgcolor
= pidgin_blist_theme_get_collapsed_background_color(theme
);
6372 bgcolor
= pidgin_blist_theme_get_expanded_background_color(theme
);
6374 path
= gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
);
6375 expanded
= gtk_tree_view_row_expanded(GTK_TREE_VIEW(gtkblist
->treeview
), path
);
6376 gtk_tree_path_free(path
);
6378 title
= pidgin_get_group_title(gnode
, expanded
);
6379 biglist
= purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_buddy_icons");
6382 avatar
= pidgin_blist_get_buddy_icon(gnode
, TRUE
, TRUE
);
6385 gtk_tree_store_set(gtkblist
->treemodel
, &iter
,
6386 STATUS_ICON_VISIBLE_COLUMN
, FALSE
,
6387 STATUS_ICON_COLUMN
, NULL
,
6390 BGCOLOR_COLUMN
, bgcolor
,
6391 GROUP_EXPANDER_COLUMN
, TRUE
,
6392 GROUP_EXPANDER_VISIBLE_COLUMN
, TRUE
,
6393 CONTACT_EXPANDER_VISIBLE_COLUMN
, FALSE
,
6394 BUDDY_ICON_COLUMN
, avatar
,
6395 BUDDY_ICON_VISIBLE_COLUMN
, biglist
,
6396 IDLE_VISIBLE_COLUMN
, FALSE
,
6397 EMBLEM_VISIBLE_COLUMN
, FALSE
,
6401 pidgin_blist_hide_node(list
, gnode
, TRUE
);
6405 static char *pidgin_get_group_title(PurpleBlistNode
*gnode
, gboolean expanded
)
6409 char group_count
[12] = "";
6411 PurpleBlistNode
*selected_node
= NULL
;
6413 PidginThemeFont
*pair
;
6414 gchar
const *text_color
, *text_font
;
6415 PidginBlistTheme
*theme
;
6417 group
= (PurpleGroup
*)gnode
;
6419 if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist
->treeview
)), NULL
, &iter
)) {
6420 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
,
6421 NODE_COLUMN
, &selected_node
, -1);
6423 selected
= (gnode
== selected_node
);
6426 g_snprintf(group_count
, sizeof(group_count
), "%d/%d",
6427 purple_counting_node_get_online_count(PURPLE_COUNTING_NODE(group
)),
6428 purple_counting_node_get_current_size(PURPLE_COUNTING_NODE(group
)));
6431 theme
= pidgin_blist_get_theme();
6435 pair
= pidgin_blist_theme_get_expanded_text_info(theme
);
6437 pair
= pidgin_blist_theme_get_collapsed_text_info(theme
);
6440 text_color
= selected
? NULL
: theme_font_get_color_default(pair
, NULL
);
6441 text_font
= theme_font_get_face_default(pair
, "");
6443 esc
= g_markup_escape_text(purple_group_get_name(group
), -1);
6445 mark
= g_strdup_printf("<span foreground='%s' font_desc='%s'><b>%s</b>%s%s%s</span>",
6446 text_color
, text_font
,
6448 !expanded
? " <span weight='light'>(</span>" : "",
6450 !expanded
? "<span weight='light'>)</span>" : "");
6452 mark
= g_strdup_printf("<span font_desc='%s'><b>%s</b>%s%s%s</span>",
6453 text_font
, esc
? esc
: "",
6454 !expanded
? " <span weight='light'>(</span>" : "",
6456 !expanded
? "<span weight='light'>)</span>" : "");
6463 static void buddy_node(PurpleBuddy
*buddy
, GtkTreeIter
*iter
, PurpleBlistNode
*node
)
6465 PurplePresence
*presence
= purple_buddy_get_presence(buddy
);
6466 GdkPixbuf
*status
, *avatar
, *emblem
, *protocol_icon
;
6467 GdkRGBA
*color
= NULL
;
6470 gboolean expanded
= ((PidginBlistNode
*)purple_blist_node_get_ui_data(node
->parent
))->contact_expanded
;
6471 gboolean selected
= (gtkblist
->selected_node
== node
);
6472 gboolean biglist
= purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_buddy_icons");
6473 PidginBlistTheme
*theme
;
6478 status
= pidgin_blist_get_status_icon(PURPLE_BLIST_NODE(buddy
),
6479 biglist
? PIDGIN_STATUS_ICON_LARGE
: PIDGIN_STATUS_ICON_SMALL
);
6481 /* Speed it up if we don't want buddy icons. */
6483 avatar
= pidgin_blist_get_buddy_icon(PURPLE_BLIST_NODE(buddy
), TRUE
, TRUE
);
6488 g_object_ref(G_OBJECT(gtkblist
->empty_avatar
));
6489 avatar
= gtkblist
->empty_avatar
;
6490 } else if ((!PURPLE_BUDDY_IS_ONLINE(buddy
) || purple_presence_is_idle(presence
))) {
6491 do_alphashift(avatar
, 77);
6494 emblem
= pidgin_blist_get_emblem(PURPLE_BLIST_NODE(buddy
));
6495 mark
= pidgin_blist_get_name_markup(buddy
, selected
, TRUE
);
6497 theme
= pidgin_blist_get_theme();
6499 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_idle_time") &&
6500 purple_presence_is_idle(presence
) && !biglist
)
6502 time_t idle_secs
= purple_presence_get_idle_time(presence
);
6506 PidginThemeFont
*pair
= NULL
;
6507 const gchar
*textcolor
;
6512 ihrs
= (t
- idle_secs
) / 3600;
6513 imin
= ((t
- idle_secs
) / 60) % 60;
6517 else if (theme
!= NULL
&& (pair
= pidgin_blist_theme_get_idle_text_info(theme
)) != NULL
)
6518 textcolor
= pidgin_theme_font_get_color_describe(pair
);
6520 /* If no theme them default to making idle buddy names grey */
6521 textcolor
= pidgin_style_is_dark(NULL
) ? "light slate grey" : "dim grey";
6524 idle
= g_strdup_printf("<span color='%s' font_desc='%s'>%d:%02d</span>",
6525 textcolor
, theme_font_get_face_default(pair
, ""),
6528 idle
= g_strdup_printf("<span font_desc='%s'>%d:%02d</span>",
6529 theme_font_get_face_default(pair
, ""),
6535 protocol_icon
= pidgin_create_protocol_icon(purple_buddy_get_account(buddy
), PIDGIN_PROTOCOL_ICON_SMALL
);
6538 color
= pidgin_blist_theme_get_contact_color(theme
);
6540 gtk_tree_store_set(gtkblist
->treemodel
, iter
,
6541 STATUS_ICON_COLUMN
, status
,
6542 STATUS_ICON_VISIBLE_COLUMN
, TRUE
,
6545 IDLE_VISIBLE_COLUMN
, !biglist
&& idle
,
6546 BUDDY_ICON_COLUMN
, avatar
,
6547 BUDDY_ICON_VISIBLE_COLUMN
, biglist
,
6548 EMBLEM_COLUMN
, emblem
,
6549 EMBLEM_VISIBLE_COLUMN
, (emblem
!= NULL
),
6550 PROTOCOL_ICON_COLUMN
, protocol_icon
,
6551 PROTOCOL_ICON_VISIBLE_COLUMN
, purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_protocol_icons"),
6552 BGCOLOR_COLUMN
, color
,
6553 CONTACT_EXPANDER_COLUMN
, NULL
,
6554 CONTACT_EXPANDER_VISIBLE_COLUMN
, expanded
,
6555 GROUP_EXPANDER_VISIBLE_COLUMN
, FALSE
,
6561 g_object_unref(emblem
);
6563 g_object_unref(status
);
6565 g_object_unref(avatar
);
6567 g_object_unref(protocol_icon
);
6570 /* This is a variation on the original gtk_blist_update_contact. Here we
6571 can know in advance which buddy has changed so we can just update that */
6572 static void pidgin_blist_update_contact(PurpleBuddyList
*list
, PurpleBlistNode
*node
)
6574 PurpleBlistNode
*cnode
;
6575 PurpleContact
*contact
;
6577 gboolean biglist
= purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_buddy_icons");
6578 PidginBlistNode
*gtknode
;
6583 if (PURPLE_IS_BUDDY(node
))
6584 cnode
= node
->parent
;
6588 g_return_if_fail(PURPLE_IS_CONTACT(cnode
));
6590 /* First things first, update the group */
6591 if (PURPLE_IS_BUDDY(node
))
6592 pidgin_blist_update_group(list
, node
);
6594 pidgin_blist_update_group(list
, cnode
->parent
);
6596 contact
= (PurpleContact
*)cnode
;
6597 buddy
= purple_contact_get_priority_buddy(contact
);
6599 if (buddy_is_displayable(buddy
))
6603 if(!insert_node(list
, cnode
, &iter
))
6606 gtknode
= purple_blist_node_get_ui_data(cnode
);
6608 if(gtknode
->contact_expanded
) {
6611 const gchar
*fg_color
, *font
;
6612 GdkRGBA
*color
= NULL
;
6613 PidginBlistTheme
*theme
;
6614 PidginThemeFont
*pair
;
6615 gboolean selected
= (gtkblist
->selected_node
== cnode
);
6617 mark
= g_markup_escape_text(purple_contact_get_alias(contact
), -1);
6619 theme
= pidgin_blist_get_theme();
6623 pair
= pidgin_blist_theme_get_contact_text_info(theme
);
6624 color
= pidgin_blist_theme_get_contact_color(theme
);
6627 font
= theme_font_get_face_default(pair
, "");
6628 fg_color
= selected
? NULL
: theme_font_get_color_default(pair
, NULL
);
6631 tmp
= g_strdup_printf("<span font_desc='%s' color='%s'>%s</span>",
6632 font
, fg_color
, mark
);
6634 tmp
= g_strdup_printf("<span font_desc='%s'>%s</span>", font
,
6640 status
= pidgin_blist_get_status_icon(cnode
,
6641 biglist
? PIDGIN_STATUS_ICON_LARGE
: PIDGIN_STATUS_ICON_SMALL
);
6643 gtk_tree_store_set(gtkblist
->treemodel
, &iter
,
6644 STATUS_ICON_COLUMN
, status
,
6645 STATUS_ICON_VISIBLE_COLUMN
, TRUE
,
6648 IDLE_VISIBLE_COLUMN
, FALSE
,
6649 BGCOLOR_COLUMN
, color
,
6650 BUDDY_ICON_COLUMN
, NULL
,
6651 CONTACT_EXPANDER_COLUMN
, TRUE
,
6652 CONTACT_EXPANDER_VISIBLE_COLUMN
, TRUE
,
6653 GROUP_EXPANDER_VISIBLE_COLUMN
, FALSE
,
6657 g_object_unref(status
);
6659 buddy_node(buddy
, &iter
, cnode
);
6662 pidgin_blist_hide_node(list
, cnode
, TRUE
);
6668 static void pidgin_blist_update_buddy(PurpleBuddyList
*list
, PurpleBlistNode
*node
, gboolean status_change
)
6671 PidginBlistNode
*gtkparentnode
;
6673 g_return_if_fail(PURPLE_IS_BUDDY(node
));
6675 if (node
->parent
== NULL
)
6678 buddy
= (PurpleBuddy
*)node
;
6680 /* First things first, update the contact */
6681 pidgin_blist_update_contact(list
, node
);
6683 gtkparentnode
= purple_blist_node_get_ui_data(node
->parent
);
6685 if (gtkparentnode
->contact_expanded
&& buddy_is_displayable(buddy
))
6689 if (!insert_node(list
, node
, &iter
))
6692 buddy_node(buddy
, &iter
, node
);
6695 pidgin_blist_hide_node(list
, node
, TRUE
);
6700 static void pidgin_blist_update_chat(PurpleBuddyList
*list
, PurpleBlistNode
*node
)
6704 g_return_if_fail(PURPLE_IS_CHAT(node
));
6709 /* First things first, update the group */
6710 pidgin_blist_update_group(list
, node
->parent
);
6712 chat
= (PurpleChat
*)node
;
6714 if(purple_account_is_connected(purple_chat_get_account(chat
))) {
6716 GdkPixbuf
*status
, *avatar
, *emblem
, *protocol_icon
;
6717 const gchar
*color
, *font
;
6719 gboolean showicons
= purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_buddy_icons");
6720 gboolean biglist
= purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_buddy_icons");
6721 PidginBlistNode
*ui
;
6722 PurpleConversation
*conv
;
6723 gboolean hidden
= FALSE
;
6724 GdkRGBA
*bgcolor
= NULL
;
6725 PidginThemeFont
*pair
;
6726 PidginBlistTheme
*theme
;
6727 gboolean selected
= (gtkblist
->selected_node
== node
);
6728 gboolean nick_said
= FALSE
;
6730 if (!insert_node(list
, node
, &iter
))
6733 ui
= purple_blist_node_get_ui_data(node
);
6734 conv
= ui
->conv
.conv
;
6735 if (conv
&& pidgin_conv_is_hidden(PIDGIN_CONVERSATION(conv
))) {
6736 hidden
= (ui
->conv
.flags
& PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE
);
6737 nick_said
= (ui
->conv
.flags
& PIDGIN_BLIST_CHAT_HAS_PENDING_MESSAGE_WITH_NICK
);
6740 status
= pidgin_blist_get_status_icon(node
,
6741 biglist
? PIDGIN_STATUS_ICON_LARGE
: PIDGIN_STATUS_ICON_SMALL
);
6742 emblem
= pidgin_blist_get_emblem(node
);
6744 /* Speed it up if we don't want buddy icons. */
6746 avatar
= pidgin_blist_get_buddy_icon(node
, TRUE
, FALSE
);
6750 mark
= g_markup_escape_text(purple_chat_get_name(chat
), -1);
6752 theme
= pidgin_blist_get_theme();
6757 pair
= pidgin_blist_theme_get_unread_message_nick_said_text_info(theme
);
6759 pair
= pidgin_blist_theme_get_unread_message_text_info(theme
);
6760 else pair
= pidgin_blist_theme_get_online_text_info(theme
);
6763 font
= theme_font_get_face_default(pair
, "");
6764 if (selected
|| !(color
= theme_font_get_color_default(pair
, NULL
)))
6765 /* nick_said color is the same as gtkconv:tab-label-attention */
6766 color
= (nick_said
? "#006aff" : NULL
);
6769 tmp
= g_strdup_printf("<span font_desc='%s' color='%s' weight='%s'>%s</span>",
6770 font
, color
, hidden
? "bold" : "normal", mark
);
6772 tmp
= g_strdup_printf("<span font_desc='%s' weight='%s'>%s</span>",
6773 font
, hidden
? "bold" : "normal", mark
);
6778 protocol_icon
= pidgin_create_protocol_icon(purple_chat_get_account(chat
), PIDGIN_PROTOCOL_ICON_SMALL
);
6781 bgcolor
= pidgin_blist_theme_get_contact_color(theme
);
6783 gtk_tree_store_set(gtkblist
->treemodel
, &iter
,
6784 STATUS_ICON_COLUMN
, status
,
6785 STATUS_ICON_VISIBLE_COLUMN
, TRUE
,
6786 BUDDY_ICON_COLUMN
, avatar
? avatar
: gtkblist
->empty_avatar
,
6787 BUDDY_ICON_VISIBLE_COLUMN
, showicons
,
6788 EMBLEM_COLUMN
, emblem
,
6789 EMBLEM_VISIBLE_COLUMN
, emblem
!= NULL
,
6790 PROTOCOL_ICON_COLUMN
, protocol_icon
,
6791 PROTOCOL_ICON_VISIBLE_COLUMN
, purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_protocol_icons"),
6793 BGCOLOR_COLUMN
, bgcolor
,
6794 GROUP_EXPANDER_VISIBLE_COLUMN
, FALSE
,
6799 g_object_unref(emblem
);
6801 g_object_unref(status
);
6803 g_object_unref(avatar
);
6805 g_object_unref(protocol_icon
);
6808 pidgin_blist_hide_node(list
, node
, TRUE
);
6812 static void pidgin_blist_update(PurpleBuddyList
*list
, PurpleBlistNode
*node
)
6815 gtkblist
= PIDGIN_BUDDY_LIST(list
);
6816 if(!gtkblist
|| !gtkblist
->treeview
|| !node
)
6819 if (purple_blist_node_get_ui_data(node
) == NULL
)
6820 pidgin_blist_new_node(list
, node
);
6822 if (PURPLE_IS_GROUP(node
))
6823 pidgin_blist_update_group(list
, node
);
6824 else if (PURPLE_IS_CONTACT(node
))
6825 pidgin_blist_update_contact(list
, node
);
6826 else if (PURPLE_IS_BUDDY(node
))
6827 pidgin_blist_update_buddy(list
, node
, TRUE
);
6828 else if (PURPLE_IS_CHAT(node
))
6829 pidgin_blist_update_chat(list
, node
);
6832 static void pidgin_blist_set_visible(PurpleBuddyList
*list
, gboolean show
)
6834 if (!(gtkblist
&& gtkblist
->window
))
6838 gtk_widget_show(gtkblist
->window
);
6840 if(visibility_manager_count
) {
6841 gtk_widget_hide(gtkblist
->window
);
6843 if (!gtk_widget_get_visible(gtkblist
->window
))
6844 gtk_widget_show(gtkblist
->window
);
6845 gtk_window_iconify(GTK_WINDOW(gtkblist
->window
));
6853 static GList
*list
= NULL
;
6855 PurpleBlistNode
*gnode
;
6860 gnode
= purple_blist_get_default_root();
6861 if (gnode
== NULL
) {
6862 list
= g_list_append(list
,
6863 (gpointer
)PURPLE_BLIST_DEFAULT_GROUP_NAME
);
6865 for (; gnode
!= NULL
; gnode
= gnode
->next
) {
6866 if (PURPLE_IS_GROUP(gnode
))
6868 g
= (PurpleGroup
*)gnode
;
6869 list
= g_list_append(list
, (char *) purple_group_get_name(g
));
6878 add_buddy_select_account_cb(GObject
*w
, PidginAddBuddyData
*data
)
6880 PurpleAccount
*account
=
6881 pidgin_account_chooser_get_selected(GTK_WIDGET(w
));
6882 PurpleConnection
*pc
= NULL
;
6883 PurpleProtocol
*protocol
= NULL
;
6884 gboolean invite_enabled
= TRUE
;
6886 /* Save our account */
6887 data
->rq_data
.account
= account
;
6890 pc
= purple_account_get_connection(account
);
6892 protocol
= purple_connection_get_protocol(pc
);
6893 if (protocol
&& !(purple_protocol_get_options(protocol
) & OPT_PROTO_INVITE_MESSAGE
))
6894 invite_enabled
= FALSE
;
6896 gtk_widget_set_sensitive(data
->entry_for_invite
, invite_enabled
);
6897 set_sensitive_if_input_buddy_cb(data
->entry
, data
);
6901 destroy_add_buddy_dialog_cb(GtkWidget
*win
, PidginAddBuddyData
*data
)
6907 add_buddy_cb(GtkWidget
*w
, int resp
, PidginAddBuddyData
*data
)
6909 const char *grp
, *who
, *whoalias
, *invite
;
6910 PurpleAccount
*account
;
6913 PurpleIMConversation
*im
;
6914 PurpleBuddyIcon
*icon
;
6916 if (resp
== GTK_RESPONSE_OK
)
6918 who
= gtk_entry_get_text(GTK_ENTRY(data
->entry
));
6919 grp
= pidgin_text_combo_box_entry_get_text(data
->combo
);
6920 whoalias
= gtk_entry_get_text(GTK_ENTRY(data
->entry_for_alias
));
6921 if (*whoalias
== '\0')
6923 invite
= gtk_entry_get_text(GTK_ENTRY(data
->entry_for_invite
));
6924 if (*invite
== '\0')
6927 account
= data
->rq_data
.account
;
6930 if ((grp
!= NULL
) && (*grp
!= '\0'))
6932 if ((g
= purple_blist_find_group(grp
)) == NULL
)
6934 g
= purple_group_new(grp
);
6935 purple_blist_add_group(g
, NULL
);
6938 b
= purple_blist_find_buddy_in_group(account
, who
, g
);
6940 else if ((b
= purple_blist_find_buddy(account
, who
)) != NULL
)
6942 g
= purple_buddy_get_group(b
);
6947 b
= purple_buddy_new(account
, who
, whoalias
);
6948 purple_blist_add_buddy(b
, NULL
, g
, NULL
);
6951 purple_account_add_buddy(account
, b
, invite
);
6953 /* Offer to merge people with the same alias. */
6954 if (whoalias
!= NULL
&& g
!= NULL
)
6955 gtk_blist_auto_personize(PURPLE_BLIST_NODE(g
), whoalias
);
6959 * It really seems like it would be better if the call to
6960 * purple_account_add_buddy() and purple_conversation_update() were done in
6961 * blist.c, possibly in the purple_blist_add_buddy() function. Maybe
6962 * purple_account_add_buddy() should be renamed to
6963 * purple_blist_add_new_buddy() or something, and have it call
6964 * purple_blist_add_buddy() after it creates it. --Mark
6966 * No that's not good. blist.c should only deal with adding nodes to the
6967 * local list. We need a new, non-gtk file that calls both
6968 * purple_account_add_buddy and purple_blist_add_buddy().
6969 * Or something. --Mark
6972 im
= purple_conversations_find_im_with_account(who
, data
->rq_data
.account
);
6974 icon
= purple_im_conversation_get_icon(im
);
6976 purple_buddy_icon_update(icon
);
6980 gtk_widget_destroy(data
->rq_data
.window
);
6984 pidgin_blist_request_add_buddy(PurpleBuddyList
*list
, PurpleAccount
*account
,
6985 const char *username
, const char *group
,
6988 PidginAddBuddyData
*data
= g_new0(PidginAddBuddyData
, 1);
6989 PidginBlistRequestData
*blist_req_data
= &data
->rq_data
;
6991 if (account
== NULL
)
6992 account
= purple_connection_get_account(purple_connections_get_all()->data
);
6994 make_blist_request_dialog(blist_req_data
,
6996 _("Add Buddy"), "add_buddy",
6997 _("Add a buddy.\n"),
6998 G_CALLBACK(add_buddy_select_account_cb
), add_buddy_account_filter_func
,
6999 G_CALLBACK(add_buddy_cb
));
7000 gtk_dialog_add_buttons(GTK_DIALOG(data
->rq_data
.window
),
7001 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
7002 GTK_STOCK_ADD
, GTK_RESPONSE_OK
,
7004 gtk_dialog_set_default_response(GTK_DIALOG(data
->rq_data
.window
),
7007 g_signal_connect(G_OBJECT(data
->rq_data
.window
), "destroy",
7008 G_CALLBACK(destroy_add_buddy_dialog_cb
), data
);
7010 data
->entry
= gtk_entry_new();
7012 pidgin_add_widget_to_vbox(data
->rq_data
.vbox
, _("Buddy's _username:"),
7013 data
->rq_data
.sg
, data
->entry
, TRUE
, NULL
);
7014 gtk_widget_grab_focus(data
->entry
);
7016 if (username
!= NULL
)
7017 gtk_entry_set_text(GTK_ENTRY(data
->entry
), username
);
7019 gtk_dialog_set_response_sensitive(GTK_DIALOG(data
->rq_data
.window
),
7020 GTK_RESPONSE_OK
, FALSE
);
7022 gtk_entry_set_activates_default (GTK_ENTRY(data
->entry
), TRUE
);
7024 g_signal_connect(G_OBJECT(data
->entry
), "changed",
7025 G_CALLBACK(set_sensitive_if_input_buddy_cb
),
7028 data
->entry_for_alias
= gtk_entry_new();
7029 pidgin_add_widget_to_vbox(data
->rq_data
.vbox
, _("(Optional) A_lias:"),
7030 data
->rq_data
.sg
, data
->entry_for_alias
, TRUE
,
7034 gtk_entry_set_text(GTK_ENTRY(data
->entry_for_alias
), alias
);
7036 if (username
!= NULL
)
7037 gtk_widget_grab_focus(GTK_WIDGET(data
->entry_for_alias
));
7039 data
->entry_for_invite
= gtk_entry_new();
7040 pidgin_add_widget_to_vbox(data
->rq_data
.vbox
, _("(Optional) _Invite message:"),
7041 data
->rq_data
.sg
, data
->entry_for_invite
, TRUE
,
7044 data
->combo
= pidgin_text_combo_box_entry_new(group
, groups_tree());
7045 pidgin_add_widget_to_vbox(data
->rq_data
.vbox
, _("Add buddy to _group:"),
7046 data
->rq_data
.sg
, data
->combo
, TRUE
, NULL
);
7048 gtk_widget_show_all(data
->rq_data
.window
);
7050 /* Force update of invite message entry sensitivity */
7051 pidgin_account_chooser_set_selected(blist_req_data
->account_menu
,
7056 add_chat_cb(GtkWidget
*w
, PidginAddChatData
*data
)
7060 GHashTable
*components
;
7062 components
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
7065 for (tmp
= data
->chat_data
.entries
; tmp
; tmp
= tmp
->next
)
7067 if (g_object_get_data(tmp
->data
, "is_spin"))
7069 g_hash_table_replace(components
,
7070 g_strdup(g_object_get_data(tmp
->data
, "identifier")),
7071 g_strdup_printf("%d",
7072 gtk_spin_button_get_value_as_int(tmp
->data
)));
7076 const char *value
= gtk_entry_get_text(tmp
->data
);
7079 g_hash_table_replace(components
,
7080 g_strdup(g_object_get_data(tmp
->data
, "identifier")),
7085 chat
= purple_chat_new(data
->chat_data
.rq_data
.account
,
7086 gtk_entry_get_text(GTK_ENTRY(data
->alias_entry
)),
7091 const char *group_name
;
7093 group_name
= pidgin_text_combo_box_entry_get_text(data
->group_combo
);
7096 if ((group_name
!= NULL
) && (*group_name
!= '\0') &&
7097 ((group
= purple_blist_find_group(group_name
)) == NULL
))
7099 group
= purple_group_new(group_name
);
7100 purple_blist_add_group(group
, NULL
);
7103 purple_blist_add_chat(chat
, group
, NULL
);
7105 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(data
->autojoin
)))
7106 purple_blist_node_set_bool(PURPLE_BLIST_NODE(chat
), "gtk-autojoin", TRUE
);
7108 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(data
->persistent
)))
7109 purple_blist_node_set_bool(PURPLE_BLIST_NODE(chat
), "gtk-persistent", TRUE
);
7112 gtk_widget_destroy(data
->chat_data
.rq_data
.window
);
7113 g_free(data
->chat_data
.default_chat_name
);
7114 g_list_free(data
->chat_data
.entries
);
7119 add_chat_resp_cb(GtkWidget
*w
, int resp
, PidginAddChatData
*data
)
7121 if (resp
== GTK_RESPONSE_OK
)
7123 add_chat_cb(NULL
, data
);
7127 pidgin_roomlist_dialog_show_with_account(data
->chat_data
.rq_data
.account
);
7131 gtk_widget_destroy(data
->chat_data
.rq_data
.window
);
7132 g_free(data
->chat_data
.default_chat_name
);
7133 g_list_free(data
->chat_data
.entries
);
7139 pidgin_blist_request_add_chat(PurpleBuddyList
*list
, PurpleAccount
*account
,
7140 PurpleGroup
*group
, const char *alias
,
7143 PidginAddChatData
*data
;
7145 PurpleConnection
*gc
;
7147 PurpleProtocol
*protocol
= NULL
;
7149 if (account
!= NULL
) {
7150 gc
= purple_account_get_connection(account
);
7151 protocol
= purple_connection_get_protocol(gc
);
7153 if (!PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT
, join
)) {
7154 purple_notify_error(gc
, NULL
, _("This protocol does not"
7155 " support chat rooms."), NULL
,
7156 purple_request_cpar_from_account(account
));
7160 /* Find an account with chat capabilities */
7161 for (l
= purple_connections_get_all(); l
!= NULL
; l
= l
->next
) {
7162 gc
= (PurpleConnection
*)l
->data
;
7163 protocol
= purple_connection_get_protocol(gc
);
7165 if (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT
, join
)) {
7166 account
= purple_connection_get_account(gc
);
7171 if (account
== NULL
) {
7172 purple_notify_error(NULL
, NULL
,
7173 _("You are not currently signed on with any "
7174 "protocols that have the ability to chat."), NULL
, NULL
);
7179 data
= g_new0(PidginAddChatData
, 1);
7180 vbox
= GTK_BOX(make_blist_request_dialog((PidginBlistRequestData
*)data
, account
,
7181 _("Add Chat"), "add_chat",
7182 _("Please enter an alias, and the appropriate information "
7183 "about the chat you would like to add to your buddy list.\n"),
7184 G_CALLBACK(chat_select_account_cb
), chat_account_filter_func
,
7185 G_CALLBACK(add_chat_resp_cb
)));
7186 gtk_dialog_add_buttons(GTK_DIALOG(data
->chat_data
.rq_data
.window
),
7188 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
7189 GTK_STOCK_ADD
, GTK_RESPONSE_OK
,
7191 gtk_dialog_set_default_response(GTK_DIALOG(data
->chat_data
.rq_data
.window
),
7194 data
->chat_data
.default_chat_name
= g_strdup(name
);
7196 rebuild_chat_entries((PidginChatData
*)data
, name
);
7198 data
->alias_entry
= gtk_entry_new();
7200 gtk_entry_set_text(GTK_ENTRY(data
->alias_entry
), alias
);
7201 gtk_entry_set_activates_default(GTK_ENTRY(data
->alias_entry
), TRUE
);
7203 pidgin_add_widget_to_vbox(GTK_BOX(vbox
), _("A_lias:"),
7204 data
->chat_data
.rq_data
.sg
, data
->alias_entry
,
7207 gtk_widget_grab_focus(data
->alias_entry
);
7209 data
->group_combo
= pidgin_text_combo_box_entry_new(group
? purple_group_get_name(group
) : NULL
, groups_tree());
7210 pidgin_add_widget_to_vbox(GTK_BOX(vbox
), _("_Group:"),
7211 data
->chat_data
.rq_data
.sg
, data
->group_combo
,
7214 data
->autojoin
= gtk_check_button_new_with_mnemonic(_("Automatically _join when account connects"));
7215 data
->persistent
= gtk_check_button_new_with_mnemonic(_("_Remain in chat after window is closed"));
7216 gtk_box_pack_start(GTK_BOX(vbox
), data
->autojoin
, FALSE
, FALSE
, 0);
7217 gtk_box_pack_start(GTK_BOX(vbox
), data
->persistent
, FALSE
, FALSE
, 0);
7219 gtk_widget_show_all(data
->chat_data
.rq_data
.window
);
7223 add_group_cb(PurpleConnection
*gc
, const char *group_name
)
7227 if ((group_name
== NULL
) || (*group_name
== '\0'))
7230 group
= purple_group_new(group_name
);
7231 purple_blist_add_group(group
, NULL
);
7235 pidgin_blist_request_add_group(PurpleBuddyList
*list
)
7237 purple_request_input(NULL
, _("Add Group"), NULL
,
7238 _("Please enter the name of the group to be added."),
7239 NULL
, FALSE
, FALSE
, NULL
,
7240 _("Add"), G_CALLBACK(add_group_cb
),
7246 pidgin_blist_toggle_visibility()
7248 if (gtkblist
&& gtkblist
->window
) {
7249 if (gtk_widget_get_visible(gtkblist
->window
)) {
7250 /* make the buddy list visible if it is iconified or if it is
7251 * obscured and not currently focused (the focus part ensures
7252 * that we do something reasonable if the buddy list is obscured
7253 * by a window set to always be on top), otherwise hide the
7256 purple_blist_set_visible(PIDGIN_WINDOW_ICONIFIED(gtkblist
->window
) ||
7257 ((gtk_blist_visibility
!= GDK_VISIBILITY_UNOBSCURED
) &&
7258 !gtk_blist_focused
));
7260 purple_blist_set_visible(TRUE
);
7266 pidgin_blist_visibility_manager_add()
7268 visibility_manager_count
++;
7269 purple_debug_info("gtkblist", "added visibility manager: %d\n", visibility_manager_count
);
7273 pidgin_blist_visibility_manager_remove()
7275 if (visibility_manager_count
)
7276 visibility_manager_count
--;
7277 if (!visibility_manager_count
)
7278 purple_blist_set_visible(TRUE
);
7279 purple_debug_info("gtkblist", "removed visibility manager: %d\n", visibility_manager_count
);
7282 void pidgin_blist_add_alert(GtkWidget
*widget
)
7284 gtk_container_add(GTK_CONTAINER(gtkblist
->scrollbook
), widget
);
7289 pidgin_blist_set_headline(const char *text
, const gchar
*icon_name
,
7290 GCallback callback
, gpointer user_data
, GDestroyNotify destroy
)
7292 /* Destroy any existing headline first */
7293 if (gtkblist
->headline_destroy
)
7294 gtkblist
->headline_destroy(gtkblist
->headline_data
);
7296 gtk_label_set_markup(GTK_LABEL(gtkblist
->headline_label
), text
);
7297 gtk_image_set_from_icon_name(GTK_IMAGE(gtkblist
->headline_image
),
7298 icon_name
, GTK_ICON_SIZE_SMALL_TOOLBAR
);
7300 gtkblist
->headline_callback
= callback
;
7301 gtkblist
->headline_data
= user_data
;
7302 gtkblist
->headline_destroy
= destroy
;
7303 if (text
!= NULL
|| icon_name
!= NULL
) {
7305 gtk_widget_show_all(gtkblist
->headline
);
7307 gtk_widget_hide(gtkblist
->headline
);
7315 if (gtkblist
->window
&& !gtk_widget_has_focus(gtkblist
->window
))
7316 pidgin_set_urgent(GTK_WINDOW(gtkblist
->window
), TRUE
);
7320 pidgin_blist_get_default_gtk_blist(void)
7325 static gboolean
autojoin_cb(PurpleConnection
*gc
, gpointer data
)
7327 PurpleAccount
*account
= purple_connection_get_account(gc
);
7328 PurpleBlistNode
*gnode
, *cnode
;
7329 for (gnode
= purple_blist_get_default_root(); gnode
;
7330 gnode
= gnode
->next
) {
7331 if(!PURPLE_IS_GROUP(gnode
))
7333 for(cnode
= gnode
->child
; cnode
; cnode
= cnode
->next
)
7337 if(!PURPLE_IS_CHAT(cnode
))
7340 chat
= (PurpleChat
*)cnode
;
7342 if(purple_chat_get_account(chat
) != account
)
7345 if (purple_blist_node_get_bool(PURPLE_BLIST_NODE(chat
), "gtk-autojoin"))
7346 purple_serv_join_chat(gc
, purple_chat_get_components(chat
));
7350 /* Stop processing; we handled the autojoins. */
7355 pidgin_blist_get_handle() {
7361 static gboolean
buddy_signonoff_timeout_cb(PurpleBuddy
*buddy
)
7363 PidginBlistNode
*gtknode
= purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(buddy
));
7365 gtknode
->recent_signonoff
= FALSE
;
7366 gtknode
->recent_signonoff_timer
= 0;
7368 pidgin_blist_update(NULL
, PURPLE_BLIST_NODE(buddy
));
7370 g_object_unref(buddy
);
7374 static void buddy_signonoff_cb(PurpleBuddy
*buddy
)
7376 PidginBlistNode
*gtknode
= purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(buddy
));
7379 pidgin_blist_new_node(purple_blist_get_default(),
7380 PURPLE_BLIST_NODE(buddy
));
7383 gtknode
= purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(buddy
));
7385 gtknode
->recent_signonoff
= TRUE
;
7387 if(gtknode
->recent_signonoff_timer
> 0)
7388 g_source_remove(gtknode
->recent_signonoff_timer
);
7390 g_object_ref(buddy
);
7391 gtknode
->recent_signonoff_timer
= g_timeout_add_seconds(10,
7392 (GSourceFunc
)buddy_signonoff_timeout_cb
, buddy
);
7396 pidgin_blist_set_theme(PidginBlistTheme
*theme
)
7398 PidginBuddyListPrivate
*priv
=
7399 pidgin_buddy_list_get_instance_private(gtkblist
);
7400 PurpleBuddyList
*list
= purple_blist_get_default();
7403 purple_prefs_set_string(PIDGIN_PREFS_ROOT
"/blist/theme",
7404 purple_theme_get_name(PURPLE_THEME(theme
)));
7406 purple_prefs_set_string(PIDGIN_PREFS_ROOT
"/blist/theme", "");
7408 if (priv
->current_theme
)
7409 g_object_unref(priv
->current_theme
);
7411 priv
->current_theme
= theme
? g_object_ref(theme
) : NULL
;
7413 pidgin_blist_build_layout(list
);
7415 pidgin_blist_refresh(list
);
7420 pidgin_blist_get_theme()
7422 PidginBuddyListPrivate
*priv
=
7423 pidgin_buddy_list_get_instance_private(gtkblist
);
7425 return priv
->current_theme
;
7428 void pidgin_blist_init(void)
7430 void *gtk_blist_handle
= pidgin_blist_get_handle();
7432 cached_emblems
= g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, NULL
);
7434 /* Initialize prefs */
7435 purple_prefs_add_none(PIDGIN_PREFS_ROOT
"/blist");
7436 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/blist/show_buddy_icons", TRUE
);
7437 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/blist/show_empty_groups", FALSE
);
7438 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/blist/show_idle_time", TRUE
);
7439 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/blist/show_offline_buddies", FALSE
);
7440 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/blist/show_protocol_icons", FALSE
);
7441 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/blist/list_visible", FALSE
);
7442 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/blist/list_maximized", FALSE
);
7443 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/blist/sort_type", "alphabetical");
7444 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/blist/width", 250); /* Golden ratio, baby */
7445 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/blist/height", 405); /* Golden ratio, baby */
7446 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/blist/theme", "");
7448 purple_theme_manager_register_type(g_object_new(PIDGIN_TYPE_BLIST_THEME_LOADER
, "type", "blist", NULL
));
7450 /* Register our signals */
7451 purple_signal_register(gtk_blist_handle
, "gtkblist-hiding",
7452 purple_marshal_VOID__POINTER
, G_TYPE_NONE
, 1,
7453 PURPLE_TYPE_BUDDY_LIST
);
7455 purple_signal_register(gtk_blist_handle
, "gtkblist-unhiding",
7456 purple_marshal_VOID__POINTER
, G_TYPE_NONE
, 1,
7457 PURPLE_TYPE_BUDDY_LIST
);
7459 purple_signal_register(gtk_blist_handle
, "gtkblist-created",
7460 purple_marshal_VOID__POINTER
, G_TYPE_NONE
, 1,
7461 PURPLE_TYPE_BUDDY_LIST
);
7463 purple_signal_register(gtk_blist_handle
, "drawing-tooltip",
7464 purple_marshal_VOID__POINTER_POINTER_UINT
, G_TYPE_NONE
,
7465 3, PURPLE_TYPE_BLIST_NODE
,
7466 G_TYPE_POINTER
, /* pointer to a (GString *) */
7469 purple_signal_register(gtk_blist_handle
, "drawing-buddy",
7470 purple_marshal_POINTER__POINTER
,
7471 G_TYPE_STRING
, 1, PURPLE_TYPE_BUDDY
);
7473 purple_signal_connect(purple_blist_get_handle(), "buddy-signed-on",
7474 gtk_blist_handle
, PURPLE_CALLBACK(buddy_signonoff_cb
), NULL
);
7475 purple_signal_connect(purple_blist_get_handle(), "buddy-signed-off",
7476 gtk_blist_handle
, PURPLE_CALLBACK(buddy_signonoff_cb
), NULL
);
7477 purple_signal_connect(purple_blist_get_handle(), "buddy-privacy-changed",
7478 gtk_blist_handle
, PURPLE_CALLBACK(pidgin_blist_update_privacy_cb
), NULL
);
7480 purple_signal_connect_priority(purple_connections_get_handle(), "autojoin",
7481 gtk_blist_handle
, PURPLE_CALLBACK(autojoin_cb
),
7482 NULL
, PURPLE_SIGNAL_PRIORITY_HIGHEST
);
7486 pidgin_blist_uninit(void) {
7487 g_hash_table_destroy(cached_emblems
);
7489 purple_signals_unregister_by_instance(pidgin_blist_get_handle());
7490 purple_signals_disconnect_by_handle(pidgin_blist_get_handle());
7496 /**************************************************************************
7497 * GTK Buddy list GObject code
7498 **************************************************************************/
7500 pidgin_buddy_list_init(PidginBuddyList
*self
)
7505 pidgin_buddy_list_finalize(GObject
*obj
)
7507 PidginBuddyList
*gtkblist
= PIDGIN_BUDDY_LIST(obj
);
7508 PidginBuddyListPrivate
*priv
=
7509 pidgin_buddy_list_get_instance_private(gtkblist
);
7511 purple_signals_disconnect_by_handle(gtkblist
);
7513 gtk_widget_destroy(gtkblist
->window
);
7515 pidgin_blist_tooltip_destroy();
7517 if (gtkblist
->refresh_timer
) {
7518 g_source_remove(gtkblist
->refresh_timer
);
7519 gtkblist
->refresh_timer
= 0;
7521 if (gtkblist
->timeout
) {
7522 g_source_remove(gtkblist
->timeout
);
7523 gtkblist
->timeout
= 0;
7525 if (gtkblist
->drag_timeout
) {
7526 g_source_remove(gtkblist
->drag_timeout
);
7527 gtkblist
->drag_timeout
= 0;
7530 gtkblist
->window
= gtkblist
->vbox
= gtkblist
->treeview
= NULL
;
7531 g_clear_object(>kblist
->treemodel
);
7532 g_object_unref(G_OBJECT(gtkblist
->ui
));
7533 g_object_unref(G_OBJECT(gtkblist
->empty_avatar
));
7535 g_clear_object(&priv
->current_theme
);
7536 if (priv
->select_notebook_page_timeout
) {
7537 g_source_remove(priv
->select_notebook_page_timeout
);
7540 purple_prefs_disconnect_by_handle(pidgin_blist_get_handle());
7542 G_OBJECT_CLASS(pidgin_buddy_list_parent_class
)->finalize(obj
);
7546 pidgin_buddy_list_class_init(PidginBuddyListClass
*klass
)
7548 GObjectClass
*obj_class
= G_OBJECT_CLASS(klass
);
7549 PurpleBuddyListClass
*purple_blist_class
;
7551 obj_class
->finalize
= pidgin_buddy_list_finalize
;
7553 purple_blist_class
= PURPLE_BUDDY_LIST_CLASS(klass
);
7554 purple_blist_class
->new_node
= pidgin_blist_new_node
;
7555 purple_blist_class
->show
= pidgin_blist_show
;
7556 purple_blist_class
->update
= pidgin_blist_update
;
7557 purple_blist_class
->remove
= pidgin_blist_remove
;
7558 purple_blist_class
->set_visible
= pidgin_blist_set_visible
;
7559 purple_blist_class
->request_add_buddy
= pidgin_blist_request_add_buddy
;
7560 purple_blist_class
->request_add_chat
= pidgin_blist_request_add_chat
;
7561 purple_blist_class
->request_add_group
= pidgin_blist_request_add_group
;
7564 /*********************************************************************
7565 * Buddy List sorting functions *
7566 *********************************************************************/
7568 GList
*pidgin_blist_get_sort_methods()
7570 return pidgin_blist_sort_methods
;
7573 void pidgin_blist_sort_method_reg(const char *id
, const char *name
, pidgin_blist_sort_function func
)
7575 struct _PidginBlistSortMethod
*method
;
7577 g_return_if_fail(id
!= NULL
);
7578 g_return_if_fail(name
!= NULL
);
7579 g_return_if_fail(func
!= NULL
);
7581 method
= g_new0(struct _PidginBlistSortMethod
, 1);
7582 method
->id
= g_strdup(id
);
7583 method
->name
= g_strdup(name
);
7584 method
->func
= func
;
7585 pidgin_blist_sort_methods
= g_list_append(pidgin_blist_sort_methods
, method
);
7586 pidgin_blist_update_sort_methods();
7589 void pidgin_blist_sort_method_unreg(const char *id
)
7591 GList
*l
= pidgin_blist_sort_methods
;
7593 g_return_if_fail(id
!= NULL
);
7596 struct _PidginBlistSortMethod
*method
= l
->data
;
7597 if(purple_strequal(method
->id
, id
)) {
7598 pidgin_blist_sort_methods
= g_list_delete_link(pidgin_blist_sort_methods
, l
);
7600 g_free(method
->name
);
7606 pidgin_blist_update_sort_methods();
7609 void pidgin_blist_sort_method_set(const char *id
){
7610 GList
*l
= pidgin_blist_sort_methods
;
7615 while (l
&& !purple_strequal(((struct _PidginBlistSortMethod
*)l
->data
)->id
, id
))
7619 current_sort_method
= l
->data
;
7620 } else if (!current_sort_method
) {
7621 pidgin_blist_sort_method_set("none");
7624 if (purple_strequal(id
, "none")) {
7625 redo_buddy_list(purple_blist_get_default(), TRUE
, FALSE
);
7627 redo_buddy_list(purple_blist_get_default(), FALSE
, FALSE
);
7631 /******************************************
7633 ******************************************/
7635 static void sort_method_none(PurpleBlistNode
*node
, PurpleBuddyList
*blist
, GtkTreeIter parent_iter
, GtkTreeIter
*cur
, GtkTreeIter
*iter
)
7637 PurpleBlistNode
*sibling
= node
->prev
;
7638 GtkTreeIter sibling_iter
;
7645 while (sibling
&& !get_iter_from_node(sibling
, &sibling_iter
)) {
7646 sibling
= sibling
->prev
;
7649 gtk_tree_store_insert_after(gtkblist
->treemodel
, iter
,
7650 node
->parent
? &parent_iter
: NULL
,
7651 sibling
? &sibling_iter
: NULL
);
7654 static void sort_method_alphabetical(PurpleBlistNode
*node
, PurpleBuddyList
*blist
, GtkTreeIter groupiter
, GtkTreeIter
*cur
, GtkTreeIter
*iter
)
7658 const char *my_name
;
7660 if(PURPLE_IS_CONTACT(node
)) {
7661 my_name
= purple_contact_get_alias((PurpleContact
*)node
);
7662 } else if(PURPLE_IS_CHAT(node
)) {
7663 my_name
= purple_chat_get_name((PurpleChat
*)node
);
7665 sort_method_none(node
, blist
, groupiter
, cur
, iter
);
7669 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(gtkblist
->treemodel
), &more_z
, &groupiter
)) {
7670 gtk_tree_store_insert(gtkblist
->treemodel
, iter
, &groupiter
, 0);
7676 const char *this_name
;
7679 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &more_z
, NODE_COLUMN
, &n
, -1);
7681 if(PURPLE_IS_CONTACT(n
)) {
7682 this_name
= purple_contact_get_alias((PurpleContact
*)n
);
7683 } else if(PURPLE_IS_CHAT(n
)) {
7684 this_name
= purple_chat_get_name((PurpleChat
*)n
);
7689 cmp
= purple_utf8_strcasecmp(my_name
, this_name
);
7691 if(this_name
&& (cmp
< 0 || (cmp
== 0 && node
< n
))) {
7693 gtk_tree_store_move_before(gtkblist
->treemodel
, cur
, &more_z
);
7697 gtk_tree_store_insert_before(gtkblist
->treemodel
, iter
,
7698 &groupiter
, &more_z
);
7702 } while (gtk_tree_model_iter_next (GTK_TREE_MODEL(gtkblist
->treemodel
), &more_z
));
7705 gtk_tree_store_move_before(gtkblist
->treemodel
, cur
, NULL
);
7709 gtk_tree_store_append(gtkblist
->treemodel
, iter
, &groupiter
);
7714 static void sort_method_status(PurpleBlistNode
*node
, PurpleBuddyList
*blist
, GtkTreeIter groupiter
, GtkTreeIter
*cur
, GtkTreeIter
*iter
)
7718 PurpleBuddy
*my_buddy
, *this_buddy
;
7720 if(PURPLE_IS_CONTACT(node
)) {
7721 my_buddy
= purple_contact_get_priority_buddy((PurpleContact
*)node
);
7722 } else if(PURPLE_IS_CHAT(node
)) {
7728 gtk_tree_store_append(gtkblist
->treemodel
, iter
, &groupiter
);
7731 sort_method_alphabetical(node
, blist
, groupiter
, cur
, iter
);
7736 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(gtkblist
->treemodel
), &more_z
, &groupiter
)) {
7737 gtk_tree_store_insert(gtkblist
->treemodel
, iter
, &groupiter
, 0);
7746 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &more_z
, NODE_COLUMN
, &n
, -1);
7748 if(PURPLE_IS_CONTACT(n
)) {
7749 this_buddy
= purple_contact_get_priority_buddy((PurpleContact
*)n
);
7754 name_cmp
= purple_utf8_strcasecmp(
7755 purple_contact_get_alias(purple_buddy_get_contact(my_buddy
)),
7757 ? purple_contact_get_alias(purple_buddy_get_contact(this_buddy
))
7760 presence_cmp
= purple_buddy_presence_compare(
7761 PURPLE_BUDDY_PRESENCE(purple_buddy_get_presence(my_buddy
)),
7762 this_buddy
? PURPLE_BUDDY_PRESENCE(purple_buddy_get_presence(this_buddy
)) : NULL
);
7764 if (this_buddy
== NULL
||
7765 (presence_cmp
< 0 ||
7766 (presence_cmp
== 0 &&
7767 (name_cmp
< 0 || (name_cmp
== 0 && node
< n
)))))
7771 gtk_tree_store_move_before(gtkblist
->treemodel
, cur
, &more_z
);
7777 gtk_tree_store_insert_before(gtkblist
->treemodel
, iter
,
7778 &groupiter
, &more_z
);
7783 while (gtk_tree_model_iter_next(GTK_TREE_MODEL(gtkblist
->treemodel
),
7787 gtk_tree_store_move_before(gtkblist
->treemodel
, cur
, NULL
);
7791 gtk_tree_store_append(gtkblist
->treemodel
, iter
, &groupiter
);
7796 static void sort_method_log_activity(PurpleBlistNode
*node
, PurpleBuddyList
*blist
, GtkTreeIter groupiter
, GtkTreeIter
*cur
, GtkTreeIter
*iter
)
7800 int activity_score
= 0, this_log_activity_score
= 0;
7801 const char *buddy_name
, *this_buddy_name
;
7803 if(cur
&& (gtk_tree_model_iter_n_children(GTK_TREE_MODEL(gtkblist
->treemodel
), &groupiter
) == 1)) {
7808 if(PURPLE_IS_CONTACT(node
)) {
7811 for (n
= node
->child
; n
; n
= n
->next
) {
7812 buddy
= (PurpleBuddy
*)n
;
7813 activity_score
+= purple_log_get_activity_score(PURPLE_LOG_IM
, purple_buddy_get_name(buddy
), purple_buddy_get_account(buddy
));
7815 buddy_name
= purple_contact_get_alias((PurpleContact
*)node
);
7816 } else if(PURPLE_IS_CHAT(node
)) {
7817 /* we don't have a reliable way of getting the log filename
7818 * from the chat info in the blist, yet */
7824 gtk_tree_store_append(gtkblist
->treemodel
, iter
, &groupiter
);
7827 sort_method_none(node
, blist
, groupiter
, cur
, iter
);
7832 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(gtkblist
->treemodel
), &more_z
, &groupiter
)) {
7833 gtk_tree_store_insert(gtkblist
->treemodel
, iter
, &groupiter
, 0);
7839 PurpleBlistNode
*n2
;
7843 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &more_z
, NODE_COLUMN
, &n
, -1);
7844 this_log_activity_score
= 0;
7846 if(PURPLE_IS_CONTACT(n
)) {
7847 for (n2
= n
->child
; n2
; n2
= n2
->next
) {
7848 buddy
= (PurpleBuddy
*)n2
;
7849 this_log_activity_score
+= purple_log_get_activity_score(PURPLE_LOG_IM
, purple_buddy_get_name(buddy
), purple_buddy_get_account(buddy
));
7851 this_buddy_name
= purple_contact_get_alias((PurpleContact
*)n
);
7853 this_buddy_name
= NULL
;
7856 cmp
= purple_utf8_strcasecmp(buddy_name
, this_buddy_name
);
7858 if (!PURPLE_IS_CONTACT(n
) || activity_score
> this_log_activity_score
||
7859 ((activity_score
== this_log_activity_score
) &&
7860 (cmp
< 0 || (cmp
== 0 && node
< n
)))) {
7862 gtk_tree_store_move_before(gtkblist
->treemodel
, cur
, &more_z
);
7866 gtk_tree_store_insert_before(gtkblist
->treemodel
, iter
,
7867 &groupiter
, &more_z
);
7871 } while (gtk_tree_model_iter_next (GTK_TREE_MODEL(gtkblist
->treemodel
), &more_z
));
7874 gtk_tree_store_move_before(gtkblist
->treemodel
, cur
, NULL
);
7878 gtk_tree_store_append(gtkblist
->treemodel
, iter
, &groupiter
);
7884 plugin_act(GSimpleAction
*action
, GVariant
*param
, PurplePluginAction
*pam
)
7886 if (pam
&& pam
->callback
)
7891 build_plugin_actions(GActionMap
*action_map
, const gchar
*parent
,
7892 PurplePlugin
*plugin
)
7895 GMenu
*section
= NULL
;
7896 PurplePluginActionsCb actions_cb
;
7897 PurplePluginAction
*action
= NULL
;
7904 purple_plugin_info_get_actions_cb(purple_plugin_get_info(plugin
));
7906 actions
= actions_cb(plugin
);
7908 if (actions
== NULL
) {
7912 menu
= g_menu_new();
7914 for (l
= actions
; l
!= NULL
; l
= l
->next
) {
7915 GAction
*menuaction
;
7917 action
= (PurplePluginAction
*)l
->data
;
7919 if (action
== NULL
) {
7920 if (section
!= NULL
) {
7921 /* Close and append section if any */
7922 g_menu_append_section(menu
, NULL
,
7923 G_MENU_MODEL(section
));
7924 g_clear_object(§ion
);
7930 action
->plugin
= plugin
;
7932 name
= g_strdup_printf("plugin.%s-action-%d", parent
, count
++);
7933 /* +7 to skip "plugin." prefix */
7934 menuaction
= G_ACTION(g_simple_action_new(name
+ 7, NULL
));
7935 g_signal_connect_data(G_OBJECT(menuaction
), "activate",
7936 G_CALLBACK(plugin_act
), action
,
7937 (GClosureNotify
)purple_plugin_action_free
, 0);
7938 g_action_map_add_action(action_map
, menuaction
);
7939 g_object_unref(menuaction
);
7941 if (section
== NULL
) {
7942 section
= g_menu_new();
7945 g_menu_append(section
, action
->label
, name
);
7949 if (section
!= NULL
) {
7950 /* Close and append final section if any */
7951 g_menu_append_section(menu
, NULL
, G_MENU_MODEL(section
));
7952 g_clear_object(§ion
);
7955 g_list_free(actions
);
7957 ret
= gtk_menu_new_from_model(G_MENU_MODEL(menu
));
7958 g_object_unref(menu
);
7964 modify_account_cb(GtkWidget
*widget
, gpointer data
)
7966 pidgin_account_dialog_show(PIDGIN_MODIFY_ACCOUNT_DIALOG
, data
);
7970 enable_account_cb(GtkCheckMenuItem
*widget
, gpointer data
)
7972 PurpleAccount
*account
= data
;
7973 const PurpleSavedStatus
*saved_status
;
7975 saved_status
= purple_savedstatus_get_current();
7976 purple_savedstatus_activate_for_account(saved_status
, account
);
7978 purple_account_set_enabled(account
, PIDGIN_UI
, TRUE
);
7982 disable_account_cb(GtkCheckMenuItem
*widget
, gpointer data
)
7984 PurpleAccount
*account
= data
;
7986 purple_account_set_enabled(account
, PIDGIN_UI
, FALSE
);
7990 protocol_act(GtkWidget
*obj
, PurpleProtocolAction
*pam
)
7992 if (pam
&& pam
->callback
)
7997 pidgin_blist_update_accounts_menu(void)
7999 GtkWidget
*menuitem
, *submenu
;
8000 GtkAccelGroup
*accel_group
;
8001 GList
*l
, *accounts
;
8002 gboolean disabled_accounts
= FALSE
;
8003 gboolean enabled_accounts
= FALSE
;
8005 if (accountmenu
== NULL
)
8008 /* Clear the old Accounts menu */
8009 for (l
= gtk_container_get_children(GTK_CONTAINER(accountmenu
)); l
; l
= g_list_delete_link(l
, l
)) {
8012 if (menuitem
!= gtk_ui_manager_get_widget(gtkblist
->ui
, "/BList/AccountsMenu/ManageAccounts"))
8013 gtk_widget_destroy(menuitem
);
8016 accel_group
= gtk_menu_get_accel_group(GTK_MENU(accountmenu
));
8018 for (accounts
= purple_accounts_get_all(); accounts
; accounts
= accounts
->next
) {
8020 GtkWidget
*image
= NULL
;
8021 PurpleAccount
*account
= NULL
;
8022 GdkPixbuf
*pixbuf
= NULL
;
8024 account
= accounts
->data
;
8026 if (!purple_account_get_enabled(account
, PIDGIN_UI
)) {
8027 if (!disabled_accounts
) {
8028 menuitem
= gtk_menu_item_new_with_label(_("Enable Account"));
8029 gtk_menu_shell_append(GTK_MENU_SHELL(accountmenu
), menuitem
);
8031 submenu
= gtk_menu_new();
8032 gtk_menu_set_accel_group(GTK_MENU(submenu
), accel_group
);
8033 gtk_menu_set_accel_path(GTK_MENU(submenu
), "<Actions>/BListActions/EnableAccount");
8034 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem
), submenu
);
8036 disabled_accounts
= TRUE
;
8039 buf
= g_strconcat(purple_account_get_username(account
), " (",
8040 purple_account_get_protocol_name(account
), ")", NULL
);
8041 menuitem
= gtk_image_menu_item_new_with_label(buf
);
8044 pixbuf
= pidgin_create_protocol_icon(account
, PIDGIN_PROTOCOL_ICON_SMALL
);
8045 if (pixbuf
!= NULL
) {
8046 if (!purple_account_is_connected(account
))
8047 gdk_pixbuf_saturate_and_pixelate(pixbuf
, pixbuf
, 0.0, FALSE
);
8048 image
= gtk_image_new_from_pixbuf(pixbuf
);
8049 g_object_unref(G_OBJECT(pixbuf
));
8050 gtk_widget_show(image
);
8051 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem
), image
);
8054 g_signal_connect(G_OBJECT(menuitem
), "activate",
8055 G_CALLBACK(enable_account_cb
), account
);
8056 gtk_menu_shell_append(GTK_MENU_SHELL(submenu
), menuitem
);
8059 enabled_accounts
= TRUE
;
8063 if (!enabled_accounts
) {
8064 gtk_widget_show_all(accountmenu
);
8068 pidgin_separator(accountmenu
);
8070 for (accounts
= purple_accounts_get_all(); accounts
; accounts
= accounts
->next
) {
8072 char *accel_path_buf
= NULL
;
8073 GtkWidget
*image
= NULL
;
8074 PurpleConnection
*gc
= NULL
;
8075 PurpleAccount
*account
= NULL
;
8076 GdkPixbuf
*pixbuf
= NULL
;
8077 PurpleProtocol
*protocol
;
8079 account
= accounts
->data
;
8081 if (!purple_account_get_enabled(account
, PIDGIN_UI
))
8084 buf
= g_strconcat(purple_account_get_username(account
), " (",
8085 purple_account_get_protocol_name(account
), ")", NULL
);
8086 menuitem
= gtk_image_menu_item_new_with_label(buf
);
8087 accel_path_buf
= g_strconcat("<Actions>/AccountActions/", buf
, NULL
);
8090 pixbuf
= pidgin_create_protocol_icon(account
, PIDGIN_PROTOCOL_ICON_SMALL
);
8091 if (pixbuf
!= NULL
) {
8092 if (!purple_account_is_connected(account
))
8093 gdk_pixbuf_saturate_and_pixelate(pixbuf
, pixbuf
,
8095 image
= gtk_image_new_from_pixbuf(pixbuf
);
8096 g_object_unref(G_OBJECT(pixbuf
));
8097 gtk_widget_show(image
);
8098 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem
), image
);
8101 gtk_menu_shell_append(GTK_MENU_SHELL(accountmenu
), menuitem
);
8103 submenu
= gtk_menu_new();
8104 gtk_menu_set_accel_group(GTK_MENU(submenu
), accel_group
);
8105 gtk_menu_set_accel_path(GTK_MENU(submenu
), accel_path_buf
);
8106 g_free(accel_path_buf
);
8107 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem
), submenu
);
8109 menuitem
= gtk_menu_item_new_with_mnemonic(_("_Edit Account"));
8110 g_signal_connect(G_OBJECT(menuitem
), "activate",
8111 G_CALLBACK(modify_account_cb
), account
);
8112 gtk_menu_shell_append(GTK_MENU_SHELL(submenu
), menuitem
);
8114 pidgin_separator(submenu
);
8116 gc
= purple_account_get_connection(account
);
8117 protocol
= gc
&& PURPLE_CONNECTION_IS_CONNECTED(gc
) ?
8118 purple_connection_get_protocol(gc
) : NULL
;
8121 (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CLIENT
, get_moods
) ||
8122 PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CLIENT
, get_actions
))) {
8123 if (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CLIENT
, get_moods
) &&
8124 (purple_connection_get_flags(gc
) & PURPLE_CONNECTION_FLAG_SUPPORT_MOODS
)) {
8126 if (purple_account_get_status(account
, "mood")) {
8127 menuitem
= gtk_menu_item_new_with_mnemonic(_("Set _Mood..."));
8128 g_signal_connect(G_OBJECT(menuitem
), "activate",
8129 G_CALLBACK(set_mood_cb
), account
);
8130 gtk_menu_shell_append(GTK_MENU_SHELL(submenu
), menuitem
);
8134 if (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CLIENT
, get_actions
)) {
8135 GtkWidget
*menuitem
;
8136 PurpleProtocolAction
*action
= NULL
;
8139 actions
= purple_protocol_client_iface_get_actions(protocol
, gc
);
8141 for (l
= actions
; l
!= NULL
; l
= l
->next
)
8145 action
= (PurpleProtocolAction
*) l
->data
;
8146 action
->connection
= gc
;
8148 menuitem
= gtk_menu_item_new_with_label(action
->label
);
8149 gtk_menu_shell_append(GTK_MENU_SHELL(submenu
), menuitem
);
8151 g_signal_connect(G_OBJECT(menuitem
), "activate",
8152 G_CALLBACK(protocol_act
), action
);
8153 g_object_set_data_full(G_OBJECT(menuitem
), "protocol_action",
8155 (GDestroyNotify
)purple_protocol_action_free
);
8156 gtk_widget_show(menuitem
);
8159 pidgin_separator(submenu
);
8162 g_list_free(actions
);
8165 menuitem
= gtk_menu_item_new_with_label(_("No actions available"));
8166 gtk_menu_shell_append(GTK_MENU_SHELL(submenu
), menuitem
);
8167 gtk_widget_set_sensitive(menuitem
, FALSE
);
8170 pidgin_separator(submenu
);
8172 menuitem
= gtk_menu_item_new_with_mnemonic(_("_Disable"));
8173 g_signal_connect(G_OBJECT(menuitem
), "activate",
8174 G_CALLBACK(disable_account_cb
), account
);
8175 gtk_menu_shell_append(GTK_MENU_SHELL(submenu
), menuitem
);
8178 gtk_widget_show_all(accountmenu
);
8181 static GSList
*plugin_menu_items
;
8184 pidgin_blist_update_plugin_actions(void)
8186 GtkWidget
*toolsmenu
;
8187 GSimpleActionGroup
*action_group
;
8188 PurplePlugin
*plugin
= NULL
;
8189 PurplePluginInfo
*info
;
8194 if ((gtkblist
== NULL
) || (gtkblist
->ui
== NULL
))
8197 /* Clear the old menu */
8198 g_slist_free_full(plugin_menu_items
,
8199 (GDestroyNotify
)gtk_widget_destroy
);
8200 plugin_menu_items
= NULL
;
8202 toolsmenu
= gtk_ui_manager_get_widget(gtkblist
->ui
,
8203 "/BList/ToolsMenu");
8204 toolsmenu
= gtk_menu_item_get_submenu(GTK_MENU_ITEM(toolsmenu
));
8206 action_group
= g_simple_action_group_new();
8208 /* Add a submenu for each plugin with custom actions */
8209 for (l
= purple_plugins_get_loaded(); l
; l
= l
->next
) {
8212 GtkWidget
*menuitem
;
8214 plugin
= (PurplePlugin
*)l
->data
;
8215 info
= purple_plugin_get_info(plugin
);
8217 if (!purple_plugin_info_get_actions_cb(info
))
8220 name
= g_strdup_printf("plugin%d", count
);
8221 submenu
= build_plugin_actions(G_ACTION_MAP(action_group
),
8225 if (submenu
== NULL
) {
8229 menuitem
= gtk_menu_item_new_with_mnemonic(
8230 _(gplugin_plugin_info_get_name(
8231 GPLUGIN_PLUGIN_INFO(info
))));
8232 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem
), submenu
);
8233 gtk_widget_show(menuitem
);
8234 plugin_menu_items
= g_slist_prepend(plugin_menu_items
,
8236 gtk_menu_shell_append(GTK_MENU_SHELL(toolsmenu
), menuitem
);
8241 /* Replaces existing "plugin" group if any */
8242 gtk_widget_insert_action_group(toolsmenu
, "plugin",
8243 G_ACTION_GROUP(action_group
));
8244 g_object_unref(action_group
);
8248 sortmethod_act(GtkRadioAction
*action
, GtkRadioAction
*current
, char *id
)
8250 if (action
== current
)
8252 pidgin_set_cursor(gtkblist
->window
, GDK_WATCH
);
8253 /* This is redundant. I think. */
8254 /* pidgin_blist_sort_method_set(id); */
8255 purple_prefs_set_string(PIDGIN_PREFS_ROOT
"/blist/sort_type", id
);
8257 pidgin_clear_cursor(gtkblist
->window
);
8262 pidgin_blist_update_sort_methods(void)
8264 PidginBlistSortMethod
*method
= NULL
;
8267 const char *m
= purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/blist/sort_type");
8269 GtkRadioAction
*action
;
8272 if ((gtkblist
== NULL
) || (gtkblist
->ui
== NULL
))
8275 /* Clear the old menu */
8276 if (sort_action_group
) {
8277 gtk_ui_manager_remove_ui(gtkblist
->ui
, sort_merge_id
);
8278 gtk_ui_manager_remove_action_group(gtkblist
->ui
, sort_action_group
);
8279 g_object_unref(G_OBJECT(sort_action_group
));
8282 sort_action_group
= gtk_action_group_new("SortMethods");
8283 gtk_action_group_set_translation_domain(sort_action_group
, PACKAGE
);
8285 ui_string
= g_string_new("<ui><menubar name='BList'>"
8286 "<menu action='BuddiesMenu'><menu action='SortMenu'>");
8288 for (l
= pidgin_blist_sort_methods
; l
; l
= l
->next
) {
8289 method
= (PidginBlistSortMethod
*)l
->data
;
8291 g_string_append_printf(ui_string
, "<menuitem action='%s'/>", method
->id
);
8292 action
= gtk_radio_action_new(method
->id
,
8297 gtk_action_group_add_action_with_accel(sort_action_group
, GTK_ACTION(action
), NULL
);
8299 gtk_radio_action_set_group(action
, sl
);
8300 sl
= gtk_radio_action_get_group(action
);
8302 if (purple_strequal(m
, method
->id
))
8303 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action
), TRUE
);
8305 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action
), FALSE
);
8307 g_signal_connect(G_OBJECT(action
), "changed",
8308 G_CALLBACK(sortmethod_act
), method
->id
);
8311 g_string_append(ui_string
, "</menu></menu></menubar></ui>");
8312 gtk_ui_manager_insert_action_group(gtkblist
->ui
, sort_action_group
, 1);
8313 sort_merge_id
= gtk_ui_manager_add_ui_from_string(gtkblist
->ui
, ui_string
->str
, -1, NULL
);
8315 g_string_free(ui_string
, TRUE
);