2 * @file gtkblist.c GTK+ BuddyList API
8 * Pidgin is the legal property of its developers, whose names are too numerous
9 * to list here. Please refer to the COPYRIGHT file distributed with this
10 * source distribution.
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
31 #include "connection.h"
40 #include "pidginstock.h"
41 #include "theme-loader.h"
42 #include "theme-manager.h"
45 #include "gtkaccount.h"
47 #include "gtkcellrendererexpander.h"
48 #include "gtkcertmgr.h"
51 #include "gtkdialogs.h"
54 #include "gtkmenutray.h"
55 #include "gtkpounce.h"
56 #include "gtkplugin.h"
58 #include "gtkprivacy.h"
59 #include "gtkroomlist.h"
60 #include "gtkstatusbox.h"
61 #include "gtkscrollbook.h"
62 #include "gtksmiley.h"
63 #include "gtkblist-theme.h"
64 #include "gtkblist-theme-loader.h"
66 #include "pidgin/minidialog.h"
67 #include "pidgin/pidgintooltip.h"
69 #include <gdk/gdkkeysyms.h>
75 PurpleAccount
*account
;
78 GtkWidget
*account_menu
;
80 } PidginBlistRequestData
;
84 PidginBlistRequestData rq_data
;
87 GtkWidget
*entry_for_alias
;
88 GtkWidget
*entry_for_invite
;
94 PidginBlistRequestData rq_data
;
95 gchar
*default_chat_name
;
101 PidginChatData chat_data
;
103 GtkWidget
*alias_entry
;
104 GtkWidget
*group_combo
;
106 GtkWidget
*persistent
;
111 /** Used to hold error minidialogs. Gets packed
112 * inside PidginBuddyList.error_buttons
114 PidginScrollBook
*error_scrollbook
;
116 /** Pointer to the mini-dialog about having signed on elsewhere, if one
117 * is showing; @c NULL otherwise.
119 PidginMiniDialog
*signed_on_elsewhere
;
121 PidginBlistTheme
*current_theme
;
123 guint select_page_timeout
; /**< The timeout for pidgin_blist_select_notebook_page_cb */
124 } PidginBuddyListPrivate
;
126 #define PIDGIN_BUDDY_LIST_GET_PRIVATE(list) \
127 ((PidginBuddyListPrivate *)((list)->priv))
129 static GtkWidget
*accountmenu
= NULL
;
131 static guint visibility_manager_count
= 0;
132 static GdkVisibilityState gtk_blist_visibility
= GDK_VISIBILITY_UNOBSCURED
;
133 static gboolean gtk_blist_focused
= FALSE
;
134 static gboolean editing_blist
= FALSE
;
136 static GList
*pidgin_blist_sort_methods
= NULL
;
137 static struct pidgin_blist_sort_method
*current_sort_method
= NULL
;
138 static void sort_method_none(PurpleBlistNode
*node
, PurpleBuddyList
*blist
, GtkTreeIter groupiter
, GtkTreeIter
*cur
, GtkTreeIter
*iter
);
140 static void sort_method_alphabetical(PurpleBlistNode
*node
, PurpleBuddyList
*blist
, GtkTreeIter groupiter
, GtkTreeIter
*cur
, GtkTreeIter
*iter
);
141 static void sort_method_status(PurpleBlistNode
*node
, PurpleBuddyList
*blist
, GtkTreeIter groupiter
, GtkTreeIter
*cur
, GtkTreeIter
*iter
);
142 static void sort_method_log_activity(PurpleBlistNode
*node
, PurpleBuddyList
*blist
, GtkTreeIter groupiter
, GtkTreeIter
*cur
, GtkTreeIter
*iter
);
143 static PidginBuddyList
*gtkblist
= NULL
;
145 static GList
*groups_tree(void);
146 static gboolean
pidgin_blist_refresh_timer(PurpleBuddyList
*list
);
147 static void pidgin_blist_update_buddy(PurpleBuddyList
*list
, PurpleBlistNode
*node
, gboolean status_change
);
148 static void pidgin_blist_selection_changed(GtkTreeSelection
*selection
, gpointer data
);
149 static void pidgin_blist_update(PurpleBuddyList
*list
, PurpleBlistNode
*node
);
150 static void pidgin_blist_update_group(PurpleBuddyList
*list
, PurpleBlistNode
*node
);
151 static void pidgin_blist_update_contact(PurpleBuddyList
*list
, PurpleBlistNode
*node
);
152 static char *pidgin_get_tooltip_text(PurpleBlistNode
*node
, gboolean full
);
153 static const char *item_factory_translate_func (const char *path
, gpointer func_data
);
154 static gboolean
get_iter_from_node(PurpleBlistNode
*node
, GtkTreeIter
*iter
);
155 static gboolean
buddy_is_displayable(PurpleBuddy
*buddy
);
156 static void redo_buddy_list(PurpleBuddyList
*list
, gboolean remove
, gboolean rerender
);
157 static void pidgin_blist_collapse_contact_cb(GtkWidget
*w
, PurpleBlistNode
*node
);
158 static char *pidgin_get_group_title(PurpleBlistNode
*gnode
, gboolean expanded
);
159 static void pidgin_blist_expand_contact_cb(GtkWidget
*w
, PurpleBlistNode
*node
);
160 static void set_urgent(void);
163 PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE
= 1 << 0, /* Whether there's pending message in a conversation */
164 PIDGIN_BLIST_CHAT_HAS_PENDING_MESSAGE_WITH_NICK
= 1 << 1, /* Whether there's a pending message in a chat that mentions our nick */
165 } PidginBlistNodeFlags
;
167 typedef struct _pidgin_blist_node
{
168 GtkTreeRowReference
*row
;
169 gboolean contact_expanded
;
170 gboolean recent_signonoff
;
171 gint recent_signonoff_timer
;
173 PurpleConversation
*conv
;
174 time_t last_message
; /* timestamp for last displayed message */
175 PidginBlistNodeFlags flags
;
179 /***************************************************
181 ***************************************************/
182 static gboolean
gtk_blist_visibility_cb(GtkWidget
*w
, GdkEventVisibility
*event
, gpointer data
)
184 GdkVisibilityState old_state
= gtk_blist_visibility
;
185 gtk_blist_visibility
= event
->state
;
187 if (gtk_blist_visibility
== GDK_VISIBILITY_FULLY_OBSCURED
&&
188 old_state
!= GDK_VISIBILITY_FULLY_OBSCURED
) {
190 /* no longer fully obscured */
191 pidgin_blist_refresh_timer(purple_get_blist());
194 /* continue to handle event normally */
198 static gboolean
gtk_blist_window_state_cb(GtkWidget
*w
, GdkEventWindowState
*event
, gpointer data
)
200 if(event
->changed_mask
& GDK_WINDOW_STATE_WITHDRAWN
) {
201 if(event
->new_window_state
& GDK_WINDOW_STATE_WITHDRAWN
)
202 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/blist/list_visible", FALSE
);
204 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/blist/list_visible", TRUE
);
205 pidgin_blist_refresh_timer(purple_get_blist());
209 if(event
->changed_mask
& GDK_WINDOW_STATE_MAXIMIZED
) {
210 if(event
->new_window_state
& GDK_WINDOW_STATE_MAXIMIZED
)
211 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/blist/list_maximized", TRUE
);
213 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/blist/list_maximized", FALSE
);
216 /* Refresh gtkblist if un-iconifying */
217 if (event
->changed_mask
& GDK_WINDOW_STATE_ICONIFIED
){
218 if (!(event
->new_window_state
& GDK_WINDOW_STATE_ICONIFIED
))
219 pidgin_blist_refresh_timer(purple_get_blist());
225 static gboolean
gtk_blist_delete_cb(GtkWidget
*w
, GdkEventAny
*event
, gpointer data
)
227 if(visibility_manager_count
)
228 purple_blist_set_visible(FALSE
);
232 /* we handle everything, event should not propogate further */
236 static gboolean
gtk_blist_configure_cb(GtkWidget
*w
, GdkEventConfigure
*event
, gpointer data
)
238 /* unfortunately GdkEventConfigure ignores the window gravity, but *
239 * the only way we have of setting the position doesn't. we have to *
240 * call get_position because it does pay attention to the gravity. *
241 * this is inefficient and I agree it sucks, but it's more likely *
242 * to work correctly. - Robot101 */
245 /* check for visibility because when we aren't visible, this will *
246 * give us bogus (0,0) coordinates. - xOr */
247 if (GTK_WIDGET_VISIBLE(w
))
248 gtk_window_get_position(GTK_WINDOW(w
), &x
, &y
);
250 return FALSE
; /* carry on normally */
253 /* Workaround for GTK+ bug # 169811 - "configure_event" is fired
254 * when the window is being maximized */
255 if (gdk_window_get_state(w
->window
)
256 & GDK_WINDOW_STATE_MAXIMIZED
) {
261 /* don't save if nothing changed */
262 if (x
== purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/blist/x") &&
263 y
== purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/blist/y") &&
264 event
->width
== purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/blist/width") &&
265 event
->height
== purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/blist/height")) {
267 return FALSE
; /* carry on normally */
270 /* don't save off-screen positioning */
271 if (x
+ event
->width
< 0 ||
272 y
+ event
->height
< 0 ||
273 x
> gdk_screen_width() ||
274 y
> gdk_screen_height()) {
276 return FALSE
; /* carry on normally */
279 /* ignore changes when maximized */
280 if(purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/list_maximized"))
283 /* store the position */
284 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/blist/x", x
);
285 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/blist/y", y
);
286 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/blist/width", event
->width
);
287 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/blist/height", event
->height
);
289 /* continue to handle event normally */
293 static void gtk_blist_menu_info_cb(GtkWidget
*w
, PurpleBuddy
*b
)
295 PurpleAccount
*account
= purple_buddy_get_account(b
);
297 pidgin_retrieve_user_info(purple_account_get_connection(account
),
298 purple_buddy_get_name(b
));
301 static void gtk_blist_menu_im_cb(GtkWidget
*w
, PurpleBuddy
*b
)
303 pidgin_dialogs_im_with_user(purple_buddy_get_account(b
),
304 purple_buddy_get_name(b
));
308 static void gtk_blist_menu_audio_call_cb(GtkWidget
*w
, PurpleBuddy
*b
)
310 purple_prpl_initiate_media(purple_buddy_get_account(b
),
311 purple_buddy_get_name(b
), PURPLE_MEDIA_AUDIO
);
314 static void gtk_blist_menu_video_call_cb(GtkWidget
*w
, PurpleBuddy
*b
)
316 /* if the buddy supports both audio and video, start a combined call,
317 otherwise start a pure video session */
318 if (purple_prpl_get_media_caps(purple_buddy_get_account(b
),
319 purple_buddy_get_name(b
)) &
320 PURPLE_MEDIA_CAPS_AUDIO_VIDEO
) {
321 purple_prpl_initiate_media(purple_buddy_get_account(b
),
322 purple_buddy_get_name(b
), PURPLE_MEDIA_AUDIO
| PURPLE_MEDIA_VIDEO
);
324 purple_prpl_initiate_media(purple_buddy_get_account(b
),
325 purple_buddy_get_name(b
), PURPLE_MEDIA_VIDEO
);
331 static void gtk_blist_menu_send_file_cb(GtkWidget
*w
, PurpleBuddy
*b
)
333 PurpleAccount
*account
= purple_buddy_get_account(b
);
335 serv_send_file(purple_account_get_connection(account
),
336 purple_buddy_get_name(b
), NULL
);
339 static void gtk_blist_menu_move_to_cb(GtkWidget
*w
, PurpleBlistNode
*node
)
341 PurpleGroup
*group
= g_object_get_data(G_OBJECT(w
), "groupnode");
342 purple_blist_add_contact((PurpleContact
*)node
, group
, NULL
);
346 static void gtk_blist_menu_autojoin_cb(GtkWidget
*w
, PurpleChat
*chat
)
348 purple_blist_node_set_bool((PurpleBlistNode
*)chat
, "gtk-autojoin",
349 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w
)));
352 static void gtk_blist_menu_persistent_cb(GtkWidget
*w
, PurpleChat
*chat
)
354 purple_blist_node_set_bool((PurpleBlistNode
*)chat
, "gtk-persistent",
355 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w
)));
358 static PurpleConversation
*
359 find_conversation_with_buddy(PurpleBuddy
*buddy
)
361 PidginBlistNode
*ui
= purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(buddy
));
363 return ui
->conv
.conv
;
364 return purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM
,
365 purple_buddy_get_name(buddy
),
366 purple_buddy_get_account(buddy
));
369 static void gtk_blist_join_chat(PurpleChat
*chat
)
371 PurpleAccount
*account
;
372 PurpleConversation
*conv
;
373 PurplePluginProtocolInfo
*prpl_info
;
374 GHashTable
*components
;
378 account
= purple_chat_get_account(chat
);
379 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(purple_find_prpl(purple_account_get_protocol_id(account
)));
381 components
= purple_chat_get_components(chat
);
383 if (prpl_info
&& prpl_info
->get_chat_name
)
384 chat_name
= prpl_info
->get_chat_name(components
);
391 name
= purple_chat_get_name(chat
);
393 conv
= purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT
, name
,
397 pidgin_conv_attach_to_conversation(conv
);
398 purple_conversation_present(conv
);
401 serv_join_chat(purple_account_get_connection(account
), components
);
405 static void gtk_blist_menu_join_cb(GtkWidget
*w
, PurpleChat
*chat
)
407 gtk_blist_join_chat(chat
);
410 static void gtk_blist_renderer_editing_cancelled_cb(GtkCellRenderer
*renderer
, PurpleBuddyList
*list
)
412 editing_blist
= FALSE
;
413 g_object_set(G_OBJECT(renderer
), "editable", FALSE
, NULL
);
414 pidgin_blist_refresh(list
);
417 static void gtk_blist_renderer_editing_started_cb(GtkCellRenderer
*renderer
,
418 GtkCellEditable
*editable
,
423 GtkTreePath
*path
= NULL
;
424 PurpleBlistNode
*node
;
425 const char *text
= NULL
;
427 path
= gtk_tree_path_new_from_string (path_str
);
428 gtk_tree_model_get_iter (GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, path
);
429 gtk_tree_path_free (path
);
430 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, NODE_COLUMN
, &node
, -1);
432 switch (purple_blist_node_get_type(node
)) {
433 case PURPLE_BLIST_CONTACT_NODE
:
434 text
= purple_contact_get_alias(PURPLE_CONTACT(node
));
436 case PURPLE_BLIST_BUDDY_NODE
:
437 text
= purple_buddy_get_alias(PURPLE_BUDDY(node
));
439 case PURPLE_BLIST_GROUP_NODE
:
440 text
= purple_group_get_name(PURPLE_GROUP(node
));
442 case PURPLE_BLIST_CHAT_NODE
:
443 text
= purple_chat_get_name(PURPLE_CHAT(node
));
446 g_return_if_reached();
449 if (GTK_IS_ENTRY (editable
)) {
450 GtkEntry
*entry
= GTK_ENTRY (editable
);
451 gtk_entry_set_text(entry
, text
);
453 editing_blist
= TRUE
;
457 gtk_blist_do_personize(GList
*merges
)
459 PurpleBlistNode
*contact
= NULL
;
463 /* First, we find the contact to merge the rest of the buddies into.
464 * This will be the contact with the most buddies in it; ties are broken
465 * by which contact is higher in the list
467 for (tmp
= merges
; tmp
; tmp
= tmp
->next
) {
468 PurpleBlistNode
*node
= tmp
->data
;
470 PurpleBlistNodeType type
;
473 type
= purple_blist_node_get_type(node
);
475 if (type
== PURPLE_BLIST_BUDDY_NODE
) {
476 node
= purple_blist_node_get_parent(node
);
477 type
= purple_blist_node_get_type(node
);
480 if (type
!= PURPLE_BLIST_CONTACT_NODE
)
483 for (b
= purple_blist_node_get_first_child(node
);
485 b
= purple_blist_node_get_sibling_next(b
))
499 /* Merge all those buddies into this contact */
500 for (tmp
= merges
; tmp
; tmp
= tmp
->next
) {
501 PurpleBlistNode
*node
= tmp
->data
;
502 if (purple_blist_node_get_type(node
) == PURPLE_BLIST_BUDDY_NODE
)
503 node
= purple_blist_node_get_parent(node
);
508 purple_blist_merge_contact((PurpleContact
*)node
, contact
);
511 /* And show the expanded contact, so the people know what's going on */
512 pidgin_blist_expand_contact_cb(NULL
, contact
);
517 gtk_blist_auto_personize(PurpleBlistNode
*group
, const char *alias
)
519 PurpleBlistNode
*contact
;
520 PurpleBlistNode
*buddy
;
521 GList
*merges
= NULL
;
523 char *a
= g_utf8_casefold(alias
, -1);
525 for (contact
= purple_blist_node_get_first_child(group
);
527 contact
= purple_blist_node_get_sibling_next(contact
)) {
529 if (purple_blist_node_get_type(contact
) != PURPLE_BLIST_CONTACT_NODE
)
532 node_alias
= g_utf8_casefold(purple_contact_get_alias((PurpleContact
*)contact
), -1);
533 if (node_alias
&& !g_utf8_collate(node_alias
, a
)) {
534 merges
= g_list_append(merges
, contact
);
541 for (buddy
= purple_blist_node_get_first_child(contact
);
543 buddy
= purple_blist_node_get_sibling_next(buddy
))
545 if (purple_blist_node_get_type(buddy
) != PURPLE_BLIST_BUDDY_NODE
)
548 node_alias
= g_utf8_casefold(purple_buddy_get_alias(PURPLE_BUDDY(buddy
)), -1);
549 if (node_alias
&& !g_utf8_collate(node_alias
, a
)) {
550 merges
= g_list_append(merges
, buddy
);
562 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
);
563 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. "
564 "You can separate them again by choosing 'Expand' from the contact's context menu"), 0, NULL
, NULL
, NULL
,
565 merges
, 2, _("_Yes"), PURPLE_CALLBACK(gtk_blist_do_personize
), _("_No"), PURPLE_CALLBACK(g_list_free
));
571 static void gtk_blist_renderer_edited_cb(GtkCellRendererText
*text_rend
, char *arg1
,
572 char *arg2
, PurpleBuddyList
*list
)
576 PurpleBlistNode
*node
;
579 editing_blist
= FALSE
;
580 path
= gtk_tree_path_new_from_string (arg1
);
581 gtk_tree_model_get_iter (GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, path
);
582 gtk_tree_path_free (path
);
583 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, NODE_COLUMN
, &node
, -1);
584 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(gtkblist
->treeview
), TRUE
);
585 g_object_set(G_OBJECT(gtkblist
->text_rend
), "editable", FALSE
, NULL
);
587 switch (purple_blist_node_get_type(node
))
589 case PURPLE_BLIST_CONTACT_NODE
:
591 PurpleContact
*contact
= PURPLE_CONTACT(node
);
592 struct _pidgin_blist_node
*gtknode
=
593 (struct _pidgin_blist_node
*)purple_blist_node_get_ui_data(node
);
596 * XXX Using purple_contact_get_alias here breaks because we
597 * specifically want to check the contact alias only (i.e. not
598 * the priority buddy, which purple_contact_get_alias does).
599 * Adding yet another get_alias is evil, so figure this out
602 if (contact
->alias
|| gtknode
->contact_expanded
) {
603 purple_blist_alias_contact(contact
, arg2
);
604 gtk_blist_auto_personize(purple_blist_node_get_parent(node
), arg2
);
606 PurpleBuddy
*buddy
= purple_contact_get_priority_buddy(contact
);
607 purple_blist_alias_buddy(buddy
, arg2
);
608 serv_alias_buddy(buddy
);
609 gtk_blist_auto_personize(purple_blist_node_get_parent(node
), arg2
);
614 case PURPLE_BLIST_BUDDY_NODE
:
616 PurpleGroup
*group
= purple_buddy_get_group(PURPLE_BUDDY(node
));
618 purple_blist_alias_buddy(PURPLE_BUDDY(node
), arg2
);
619 serv_alias_buddy(PURPLE_BUDDY(node
));
620 gtk_blist_auto_personize(PURPLE_BLIST_NODE(group
), arg2
);
623 case PURPLE_BLIST_GROUP_NODE
:
624 dest
= purple_find_group(arg2
);
625 if (dest
!= NULL
&& purple_utf8_strcasecmp(arg2
, purple_group_get_name(PURPLE_GROUP(node
)))) {
626 pidgin_dialogs_merge_groups(PURPLE_GROUP(node
), arg2
);
628 purple_blist_rename_group(PURPLE_GROUP(node
), arg2
);
631 case PURPLE_BLIST_CHAT_NODE
:
632 purple_blist_alias_chat(PURPLE_CHAT(node
), arg2
);
637 pidgin_blist_refresh(list
);
641 chat_components_edit_ok(PurpleChat
*chat
, PurpleRequestFields
*allfields
)
643 GList
*groups
, *fields
;
645 for (groups
= purple_request_fields_get_groups(allfields
); groups
; groups
= groups
->next
) {
646 fields
= purple_request_field_group_get_fields(groups
->data
);
647 for (; fields
; fields
= fields
->next
) {
648 PurpleRequestField
*field
= fields
->data
;
652 id
= purple_request_field_get_id(field
);
653 if (purple_request_field_get_type(field
) == PURPLE_REQUEST_FIELD_INTEGER
)
654 val
= g_strdup_printf("%d", purple_request_field_int_get_value(field
));
656 val
= g_strdup(purple_request_field_string_get_value(field
));
659 g_hash_table_remove(purple_chat_get_components(chat
), id
);
661 g_hash_table_replace(purple_chat_get_components(chat
), g_strdup(id
), val
); /* val should not be free'd */
667 static void chat_components_edit(GtkWidget
*w
, PurpleBlistNode
*node
)
669 PurpleRequestFields
*fields
= purple_request_fields_new();
670 PurpleRequestFieldGroup
*group
= purple_request_field_group_new(NULL
);
671 PurpleRequestField
*field
;
673 struct proto_chat_entry
*pce
;
674 PurpleConnection
*gc
;
675 PurpleChat
*chat
= (PurpleChat
*)node
;
677 purple_request_fields_add_group(fields
, group
);
679 gc
= purple_account_get_connection(purple_chat_get_account(chat
));
680 parts
= PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc
))->chat_info(gc
);
682 for (iter
= parts
; iter
; iter
= iter
->next
) {
686 const char *str
= g_hash_table_lookup(purple_chat_get_components(chat
), pce
->identifier
);
687 if (!str
|| sscanf(str
, "%d", &val
) != 1)
689 field
= purple_request_field_int_new(pce
->identifier
, pce
->label
, val
);
691 field
= purple_request_field_string_new(pce
->identifier
, pce
->label
,
692 g_hash_table_lookup(purple_chat_get_components(chat
), pce
->identifier
), FALSE
);
694 purple_request_field_string_set_masked(field
, TRUE
);
698 purple_request_field_set_required(field
, TRUE
);
700 purple_request_field_group_add_field(group
, field
);
706 purple_request_fields(NULL
, _("Edit Chat"), NULL
, _("Please update the necessary fields."),
707 fields
, _("Save"), G_CALLBACK(chat_components_edit_ok
), _("Cancel"), NULL
,
712 static void gtk_blist_menu_alias_cb(GtkWidget
*w
, PurpleBlistNode
*node
)
717 if (!(get_iter_from_node(node
, &iter
))) {
718 /* This is either a bug, or the buddy is in a collapsed contact */
719 node
= purple_blist_node_get_parent(node
);
720 if (!get_iter_from_node(node
, &iter
))
721 /* Now it's definitely a bug */
725 pidgin_blist_tooltip_destroy();
727 path
= gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
);
728 g_object_set(G_OBJECT(gtkblist
->text_rend
), "editable", TRUE
, NULL
);
729 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(gtkblist
->treeview
), FALSE
);
730 gtk_widget_grab_focus(gtkblist
->treeview
);
731 gtk_tree_view_set_cursor_on_cell(GTK_TREE_VIEW(gtkblist
->treeview
), path
,
732 gtkblist
->text_column
, gtkblist
->text_rend
, TRUE
);
733 gtk_tree_path_free(path
);
736 static void gtk_blist_menu_bp_cb(GtkWidget
*w
, PurpleBuddy
*b
)
738 pidgin_pounce_editor_show(purple_buddy_get_account(b
),
739 purple_buddy_get_name(b
), NULL
);
742 static void gtk_blist_menu_showlog_cb(GtkWidget
*w
, PurpleBlistNode
*node
)
745 PurpleAccount
*account
;
748 pidgin_set_cursor(gtkblist
->window
, GDK_WATCH
);
750 if (PURPLE_BLIST_NODE_IS_BUDDY(node
)) {
751 PurpleBuddy
*b
= (PurpleBuddy
*) node
;
752 type
= PURPLE_LOG_IM
;
753 name
= g_strdup(purple_buddy_get_name(b
));
754 account
= purple_buddy_get_account(b
);
755 } else if (PURPLE_BLIST_NODE_IS_CHAT(node
)) {
756 PurpleChat
*c
= PURPLE_CHAT(node
);
757 PurplePluginProtocolInfo
*prpl_info
= NULL
;
758 type
= PURPLE_LOG_CHAT
;
759 account
= purple_chat_get_account(c
);
760 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(purple_find_prpl(purple_account_get_protocol_id(account
)));
761 if (prpl_info
&& prpl_info
->get_chat_name
) {
762 name
= prpl_info
->get_chat_name(purple_chat_get_components(c
));
764 } else if (PURPLE_BLIST_NODE_IS_CONTACT(node
)) {
765 pidgin_log_show_contact(PURPLE_CONTACT(node
));
766 pidgin_clear_cursor(gtkblist
->window
);
769 pidgin_clear_cursor(gtkblist
->window
);
771 /* This callback should not have been registered for a node
772 * that doesn't match the type of one of the blocks above. */
773 g_return_if_reached();
776 if (name
&& account
) {
777 pidgin_log_show(type
, name
, account
);
778 pidgin_clear_cursor(gtkblist
->window
);
784 static void gtk_blist_menu_showoffline_cb(GtkWidget
*w
, PurpleBlistNode
*node
)
786 if (PURPLE_BLIST_NODE_IS_BUDDY(node
))
788 purple_blist_node_set_bool(node
, "show_offline",
789 !purple_blist_node_get_bool(node
, "show_offline"));
790 pidgin_blist_update(purple_get_blist(), node
);
792 else if (PURPLE_BLIST_NODE_IS_CONTACT(node
))
794 PurpleBlistNode
*bnode
;
795 gboolean setting
= !purple_blist_node_get_bool(node
, "show_offline");
797 purple_blist_node_set_bool(node
, "show_offline", setting
);
798 for (bnode
= purple_blist_node_get_first_child(node
);
800 bnode
= purple_blist_node_get_sibling_next(bnode
))
802 purple_blist_node_set_bool(bnode
, "show_offline", setting
);
803 pidgin_blist_update(purple_get_blist(), bnode
);
805 } else if (PURPLE_BLIST_NODE_IS_GROUP(node
)) {
806 PurpleBlistNode
*cnode
, *bnode
;
807 gboolean setting
= !purple_blist_node_get_bool(node
, "show_offline");
809 purple_blist_node_set_bool(node
, "show_offline", setting
);
810 for (cnode
= purple_blist_node_get_first_child(node
);
812 cnode
= purple_blist_node_get_sibling_next(cnode
))
814 purple_blist_node_set_bool(cnode
, "show_offline", setting
);
815 for (bnode
= purple_blist_node_get_first_child(cnode
);
817 bnode
= purple_blist_node_get_sibling_next(bnode
))
819 purple_blist_node_set_bool(bnode
, "show_offline", setting
);
820 pidgin_blist_update(purple_get_blist(), bnode
);
826 static void gtk_blist_show_systemlog_cb(void)
828 pidgin_syslog_show();
831 static void gtk_blist_show_onlinehelp_cb(void)
833 purple_notify_uri(NULL
, PURPLE_WEBSITE
"documentation");
837 do_join_chat(PidginChatData
*data
)
841 GHashTable
*components
=
842 g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, g_free
);
846 for (tmp
= data
->entries
; tmp
!= NULL
; tmp
= tmp
->next
)
848 if (g_object_get_data(tmp
->data
, "is_spin"))
850 g_hash_table_replace(components
,
851 g_strdup(g_object_get_data(tmp
->data
, "identifier")),
852 g_strdup_printf("%d",
853 gtk_spin_button_get_value_as_int(tmp
->data
)));
857 g_hash_table_replace(components
,
858 g_strdup(g_object_get_data(tmp
->data
, "identifier")),
859 g_strdup(gtk_entry_get_text(tmp
->data
)));
863 chat
= purple_chat_new(data
->rq_data
.account
, NULL
, components
);
864 gtk_blist_join_chat(chat
);
865 purple_blist_remove_chat(chat
);
870 do_joinchat(GtkWidget
*dialog
, int id
, PidginChatData
*info
)
874 case GTK_RESPONSE_OK
:
879 pidgin_roomlist_dialog_show_with_account(info
->rq_data
.account
);
885 gtk_widget_destroy(GTK_WIDGET(dialog
));
886 g_list_free(info
->entries
);
891 * Check the values of all the text entry boxes. If any required input
892 * strings are empty then don't allow the user to click on "OK."
895 set_sensitive_if_input_cb(GtkWidget
*entry
, gpointer user_data
)
897 PurplePluginProtocolInfo
*prpl_info
;
898 PurpleConnection
*gc
;
899 PidginChatData
*data
;
903 gboolean sensitive
= TRUE
;
907 for (tmp
= data
->entries
; tmp
!= NULL
; tmp
= tmp
->next
)
909 if (!g_object_get_data(tmp
->data
, "is_spin"))
911 required
= GPOINTER_TO_INT(g_object_get_data(tmp
->data
, "required"));
912 text
= gtk_entry_get_text(tmp
->data
);
913 if (required
&& (*text
== '\0'))
918 gtk_dialog_set_response_sensitive(GTK_DIALOG(data
->rq_data
.window
), GTK_RESPONSE_OK
, sensitive
);
920 gc
= purple_account_get_connection(data
->rq_data
.account
);
921 prpl_info
= (gc
!= NULL
) ? PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
) : NULL
;
922 sensitive
= (prpl_info
!= NULL
&& prpl_info
->roomlist_get_list
!= NULL
);
924 gtk_dialog_set_response_sensitive(GTK_DIALOG(data
->rq_data
.window
), 1, sensitive
);
928 pidgin_blist_update_privacy_cb(PurpleBuddy
*buddy
)
930 struct _pidgin_blist_node
*ui_data
= purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(buddy
));
931 if (ui_data
== NULL
|| ui_data
->row
== NULL
)
933 pidgin_blist_update_buddy(purple_get_blist(), PURPLE_BLIST_NODE(buddy
), TRUE
);
937 chat_account_filter_func(PurpleAccount
*account
)
939 PurpleConnection
*gc
= purple_account_get_connection(account
);
940 PurplePluginProtocolInfo
*prpl_info
= NULL
;
945 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
);
947 return (prpl_info
->chat_info
!= NULL
);
951 pidgin_blist_joinchat_is_showable()
954 PurpleConnection
*gc
;
956 for (c
= purple_connections_get_all(); c
!= NULL
; c
= c
->next
) {
959 if (chat_account_filter_func(purple_connection_get_account(gc
)))
967 make_blist_request_dialog(PidginBlistRequestData
*data
, PurpleAccount
*account
,
968 const char *title
, const char *window_role
, const char *label_text
,
969 GCallback callback_func
, PurpleFilterAccountFunc filter_func
,
970 GCallback response_cb
)
976 GtkWindow
*blist_window
;
977 PidginBuddyList
*gtkblist
;
979 data
->account
= account
;
981 img
= gtk_image_new_from_stock(PIDGIN_STOCK_DIALOG_QUESTION
,
982 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_HUGE
));
984 gtkblist
= PIDGIN_BLIST(purple_get_blist());
985 blist_window
= gtkblist
? GTK_WINDOW(gtkblist
->window
) : NULL
;
987 data
->window
= gtk_dialog_new_with_buttons(title
,
988 blist_window
, GTK_DIALOG_NO_SEPARATOR
,
991 gtk_window_set_transient_for(GTK_WINDOW(data
->window
), blist_window
);
992 gtk_dialog_set_default_response(GTK_DIALOG(data
->window
), GTK_RESPONSE_OK
);
993 gtk_container_set_border_width(GTK_CONTAINER(data
->window
), PIDGIN_HIG_BOX_SPACE
);
994 gtk_window_set_resizable(GTK_WINDOW(data
->window
), FALSE
);
995 gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(data
->window
)->vbox
), PIDGIN_HIG_BORDER
);
996 gtk_container_set_border_width(GTK_CONTAINER(GTK_DIALOG(data
->window
)->vbox
), PIDGIN_HIG_BOX_SPACE
);
997 gtk_window_set_role(GTK_WINDOW(data
->window
), window_role
);
999 hbox
= gtk_hbox_new(FALSE
, PIDGIN_HIG_BORDER
);
1000 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(data
->window
)->vbox
), hbox
);
1001 gtk_box_pack_start(GTK_BOX(hbox
), img
, FALSE
, FALSE
, 0);
1002 gtk_misc_set_alignment(GTK_MISC(img
), 0, 0);
1004 vbox
= gtk_vbox_new(FALSE
, 5);
1005 gtk_container_add(GTK_CONTAINER(hbox
), vbox
);
1007 label
= gtk_label_new(label_text
);
1009 gtk_widget_set_size_request(label
, 400, -1);
1010 gtk_label_set_line_wrap(GTK_LABEL(label
), TRUE
);
1011 gtk_misc_set_alignment(GTK_MISC(label
), 0, 0);
1012 gtk_box_pack_start(GTK_BOX(vbox
), label
, FALSE
, FALSE
, 0);
1014 data
->sg
= gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL
);
1016 data
->account_menu
= pidgin_account_option_menu_new(account
, FALSE
,
1017 callback_func
, filter_func
, data
);
1018 pidgin_add_widget_to_vbox(GTK_BOX(vbox
), _("A_ccount"), data
->sg
, data
->account_menu
, TRUE
, NULL
);
1020 data
->vbox
= GTK_BOX(gtk_vbox_new(FALSE
, 5));
1021 gtk_container_set_border_width(GTK_CONTAINER(data
->vbox
), 0);
1022 gtk_box_pack_start(GTK_BOX(vbox
), GTK_WIDGET(data
->vbox
), FALSE
, FALSE
, 0);
1024 g_signal_connect(G_OBJECT(data
->window
), "response", response_cb
, data
);
1026 g_object_unref(data
->sg
);
1032 rebuild_chat_entries(PidginChatData
*data
, const char *default_chat_name
)
1034 PurpleConnection
*gc
;
1035 GList
*list
= NULL
, *tmp
;
1036 GHashTable
*defaults
= NULL
;
1037 struct proto_chat_entry
*pce
;
1038 gboolean focus
= TRUE
;
1040 g_return_if_fail(data
->rq_data
.account
!= NULL
);
1042 gc
= purple_account_get_connection(data
->rq_data
.account
);
1044 gtk_container_foreach(GTK_CONTAINER(data
->rq_data
.vbox
), (GtkCallback
)gtk_widget_destroy
, NULL
);
1046 g_list_free(data
->entries
);
1047 data
->entries
= NULL
;
1049 if (PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
)->chat_info
!= NULL
)
1050 list
= PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
)->chat_info(gc
);
1052 if (PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
)->chat_info_defaults
!= NULL
)
1053 defaults
= PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
)->chat_info_defaults(gc
, default_chat_name
);
1055 for (tmp
= list
; tmp
; tmp
= tmp
->next
)
1064 adjust
= gtk_adjustment_new(pce
->min
, pce
->min
, pce
->max
,
1066 input
= gtk_spin_button_new(GTK_ADJUSTMENT(adjust
), 1, 0);
1067 gtk_widget_set_size_request(input
, 50, -1);
1068 pidgin_add_widget_to_vbox(GTK_BOX(data
->rq_data
.vbox
), pce
->label
, data
->rq_data
.sg
, input
, FALSE
, NULL
);
1073 input
= gtk_entry_new();
1074 gtk_entry_set_activates_default(GTK_ENTRY(input
), TRUE
);
1075 value
= g_hash_table_lookup(defaults
, pce
->identifier
);
1077 gtk_entry_set_text(GTK_ENTRY(input
), value
);
1080 gtk_entry_set_visibility(GTK_ENTRY(input
), FALSE
);
1081 #if !GTK_CHECK_VERSION(2,16,0)
1082 if (gtk_entry_get_invisible_char(GTK_ENTRY(input
)) == '*')
1083 gtk_entry_set_invisible_char(GTK_ENTRY(input
), PIDGIN_INVISIBLE_CHAR
);
1084 #endif /* Less than GTK+ 2.16 */
1086 pidgin_add_widget_to_vbox(data
->rq_data
.vbox
, pce
->label
, data
->rq_data
.sg
, input
, TRUE
, NULL
);
1087 g_signal_connect(G_OBJECT(input
), "changed",
1088 G_CALLBACK(set_sensitive_if_input_cb
), data
);
1091 /* Do the following for any type of input widget */
1094 gtk_widget_grab_focus(input
);
1097 g_object_set_data(G_OBJECT(input
), "identifier", (gpointer
)pce
->identifier
);
1098 g_object_set_data(G_OBJECT(input
), "is_spin", GINT_TO_POINTER(pce
->is_int
));
1099 g_object_set_data(G_OBJECT(input
), "required", GINT_TO_POINTER(pce
->required
));
1100 data
->entries
= g_list_append(data
->entries
, input
);
1106 g_hash_table_destroy(defaults
);
1108 /* Set whether the "OK" button should be clickable initially */
1109 set_sensitive_if_input_cb(NULL
, data
);
1111 gtk_widget_show_all(GTK_WIDGET(data
->rq_data
.vbox
));
1115 chat_select_account_cb(GObject
*w
, PurpleAccount
*account
,
1116 PidginChatData
*data
)
1118 if (purple_strequal(purple_account_get_protocol_id(data
->rq_data
.account
),
1119 purple_account_get_protocol_id(account
)))
1121 data
->rq_data
.account
= account
;
1125 data
->rq_data
.account
= account
;
1126 rebuild_chat_entries(data
, data
->default_chat_name
);
1131 pidgin_blist_joinchat_show(void)
1133 PidginChatData
*data
= NULL
;
1135 data
= g_new0(PidginChatData
, 1);
1137 make_blist_request_dialog((PidginBlistRequestData
*)data
, NULL
,
1138 _("Join a Chat"), "join_chat",
1139 _("Please enter the appropriate information about the chat "
1140 "you would like to join.\n"),
1141 G_CALLBACK(chat_select_account_cb
),
1142 chat_account_filter_func
, (GCallback
)do_joinchat
);
1143 gtk_dialog_add_buttons(GTK_DIALOG(data
->rq_data
.window
),
1145 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
1146 PIDGIN_STOCK_CHAT
, GTK_RESPONSE_OK
, NULL
);
1147 gtk_dialog_set_default_response(GTK_DIALOG(data
->rq_data
.window
),
1149 data
->default_chat_name
= NULL
;
1150 data
->rq_data
.account
= pidgin_account_option_menu_get_selected(data
->rq_data
.account_menu
);
1152 rebuild_chat_entries(data
, NULL
);
1154 gtk_widget_show_all(data
->rq_data
.window
);
1157 static void gtk_blist_row_expanded_cb(GtkTreeView
*tv
, GtkTreeIter
*iter
, GtkTreePath
*path
, gpointer user_data
)
1159 PurpleBlistNode
*node
;
1161 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), iter
, NODE_COLUMN
, &node
, -1);
1163 if (PURPLE_BLIST_NODE_IS_GROUP(node
)) {
1166 title
= pidgin_get_group_title(node
, TRUE
);
1168 gtk_tree_store_set(gtkblist
->treemodel
, iter
,
1174 purple_blist_node_set_bool(node
, "collapsed", FALSE
);
1175 pidgin_blist_tooltip_destroy();
1179 static void gtk_blist_row_collapsed_cb(GtkTreeView
*tv
, GtkTreeIter
*iter
, GtkTreePath
*path
, gpointer user_data
)
1181 PurpleBlistNode
*node
;
1183 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), iter
, NODE_COLUMN
, &node
, -1);
1185 if (PURPLE_BLIST_NODE_IS_GROUP(node
)) {
1187 struct _pidgin_blist_node
*gtknode
;
1188 PurpleBlistNode
*cnode
;
1190 title
= pidgin_get_group_title(node
, FALSE
);
1192 gtk_tree_store_set(gtkblist
->treemodel
, iter
,
1198 purple_blist_node_set_bool(node
, "collapsed", TRUE
);
1200 for(cnode
= purple_blist_node_get_first_child(node
); cnode
; cnode
= purple_blist_node_get_sibling_next(cnode
)) {
1201 if (PURPLE_BLIST_NODE_IS_CONTACT(cnode
)) {
1202 gtknode
= purple_blist_node_get_ui_data(cnode
);
1203 if (!gtknode
->contact_expanded
)
1205 gtknode
->contact_expanded
= FALSE
;
1206 pidgin_blist_update_contact(NULL
, cnode
);
1209 pidgin_blist_tooltip_destroy();
1210 } else if(PURPLE_BLIST_NODE_IS_CONTACT(node
)) {
1211 pidgin_blist_collapse_contact_cb(NULL
, node
);
1215 static void gtk_blist_row_activated_cb(GtkTreeView
*tv
, GtkTreePath
*path
, GtkTreeViewColumn
*col
, gpointer data
) {
1216 PurpleBlistNode
*node
;
1219 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, path
);
1220 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, NODE_COLUMN
, &node
, -1);
1222 if(PURPLE_BLIST_NODE_IS_CONTACT(node
) || PURPLE_BLIST_NODE_IS_BUDDY(node
)) {
1225 if(PURPLE_BLIST_NODE_IS_CONTACT(node
))
1226 buddy
= purple_contact_get_priority_buddy((PurpleContact
*)node
);
1228 buddy
= (PurpleBuddy
*)node
;
1230 pidgin_dialogs_im_with_user(purple_buddy_get_account(buddy
), purple_buddy_get_name(buddy
));
1231 } else if (PURPLE_BLIST_NODE_IS_CHAT(node
)) {
1232 gtk_blist_join_chat((PurpleChat
*)node
);
1233 } else if (PURPLE_BLIST_NODE_IS_GROUP(node
)) {
1234 /* if (gtk_tree_view_row_expanded(tv, path))
1235 gtk_tree_view_collapse_row(tv, path);
1237 gtk_tree_view_expand_row(tv,path,FALSE);*/
1241 static void pidgin_blist_add_chat_cb(void)
1243 GtkTreeSelection
*sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist
->treeview
));
1245 PurpleBlistNode
*node
;
1247 if(gtk_tree_selection_get_selected(sel
, NULL
, &iter
)){
1248 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, NODE_COLUMN
, &node
, -1);
1249 if (PURPLE_BLIST_NODE_IS_BUDDY(node
))
1250 purple_blist_request_add_chat(NULL
, purple_buddy_get_group(PURPLE_BUDDY(node
)), NULL
, NULL
);
1251 if (PURPLE_BLIST_NODE_IS_CONTACT(node
) || PURPLE_BLIST_NODE_IS_CHAT(node
))
1252 purple_blist_request_add_chat(NULL
, purple_contact_get_group(PURPLE_CONTACT(node
)), NULL
, NULL
);
1253 else if (PURPLE_BLIST_NODE_IS_GROUP(node
))
1254 purple_blist_request_add_chat(NULL
, (PurpleGroup
*)node
, NULL
, NULL
);
1257 purple_blist_request_add_chat(NULL
, NULL
, NULL
, NULL
);
1261 static void pidgin_blist_add_buddy_cb(void)
1263 GtkTreeSelection
*sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist
->treeview
));
1265 PurpleBlistNode
*node
;
1267 if(gtk_tree_selection_get_selected(sel
, NULL
, &iter
)){
1268 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, NODE_COLUMN
, &node
, -1);
1269 if (PURPLE_BLIST_NODE_IS_BUDDY(node
)) {
1270 PurpleGroup
*group
= purple_buddy_get_group(PURPLE_BUDDY(node
));
1271 purple_blist_request_add_buddy(NULL
, NULL
, purple_group_get_name(group
), NULL
);
1272 } else if (PURPLE_BLIST_NODE_IS_CONTACT(node
) || PURPLE_BLIST_NODE_IS_CHAT(node
)) {
1273 PurpleGroup
*group
= purple_contact_get_group(PURPLE_CONTACT(node
));
1274 purple_blist_request_add_buddy(NULL
, NULL
, purple_group_get_name(group
), NULL
);
1275 } else if (PURPLE_BLIST_NODE_IS_GROUP(node
)) {
1276 purple_blist_request_add_buddy(NULL
, NULL
, purple_group_get_name(PURPLE_GROUP(node
)), NULL
);
1280 purple_blist_request_add_buddy(NULL
, NULL
, NULL
, NULL
);
1285 pidgin_blist_remove_cb (GtkWidget
*w
, PurpleBlistNode
*node
)
1287 if (PURPLE_BLIST_NODE_IS_BUDDY(node
)) {
1288 pidgin_dialogs_remove_buddy((PurpleBuddy
*)node
);
1289 } else if (PURPLE_BLIST_NODE_IS_CHAT(node
)) {
1290 pidgin_dialogs_remove_chat((PurpleChat
*)node
);
1291 } else if (PURPLE_BLIST_NODE_IS_GROUP(node
)) {
1292 pidgin_dialogs_remove_group((PurpleGroup
*)node
);
1293 } else if (PURPLE_BLIST_NODE_IS_CONTACT(node
)) {
1294 pidgin_dialogs_remove_contact((PurpleContact
*)node
);
1299 GtkTreeView
*treeview
;
1301 PurpleBlistNode
*node
;
1305 scroll_to_expanded_cell(gpointer data
)
1307 struct _expand
*ex
= data
;
1308 gtk_tree_view_scroll_to_cell(ex
->treeview
, ex
->path
, NULL
, FALSE
, 0, 0);
1309 pidgin_blist_update_contact(NULL
, ex
->node
);
1311 gtk_tree_path_free(ex
->path
);
1318 pidgin_blist_expand_contact_cb(GtkWidget
*w
, PurpleBlistNode
*node
)
1320 struct _pidgin_blist_node
*gtknode
;
1321 GtkTreeIter iter
, parent
;
1322 PurpleBlistNode
*bnode
;
1325 if(!PURPLE_BLIST_NODE_IS_CONTACT(node
))
1328 gtknode
= purple_blist_node_get_ui_data(node
);
1330 gtknode
->contact_expanded
= TRUE
;
1332 for(bnode
= purple_blist_node_get_first_child(node
); bnode
; bnode
= purple_blist_node_get_sibling_next(bnode
)) {
1333 pidgin_blist_update(NULL
, bnode
);
1336 /* This ensures that the bottom buddy is visible, i.e. not scrolled off the alignment */
1337 if (get_iter_from_node(node
, &parent
)) {
1338 struct _expand
*ex
= g_new0(struct _expand
, 1);
1340 gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, &parent
,
1341 gtk_tree_model_iter_n_children(GTK_TREE_MODEL(gtkblist
->treemodel
), &parent
) -1);
1342 path
= gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
);
1344 /* Let the treeview draw so it knows where to scroll */
1345 ex
->treeview
= GTK_TREE_VIEW(gtkblist
->treeview
);
1347 ex
->node
= purple_blist_node_get_first_child(node
);
1348 g_idle_add(scroll_to_expanded_cell
, ex
);
1353 pidgin_blist_collapse_contact_cb(GtkWidget
*w
, PurpleBlistNode
*node
)
1355 PurpleBlistNode
*bnode
;
1356 struct _pidgin_blist_node
*gtknode
;
1358 if(!PURPLE_BLIST_NODE_IS_CONTACT(node
))
1361 gtknode
= purple_blist_node_get_ui_data(node
);
1363 gtknode
->contact_expanded
= FALSE
;
1365 for(bnode
= purple_blist_node_get_first_child(node
); bnode
; bnode
= purple_blist_node_get_sibling_next(bnode
)) {
1366 pidgin_blist_update(NULL
, bnode
);
1371 toggle_privacy(GtkWidget
*widget
, PurpleBlistNode
*node
)
1374 PurpleAccount
*account
;
1378 if (!PURPLE_BLIST_NODE_IS_BUDDY(node
))
1381 buddy
= (PurpleBuddy
*)node
;
1382 account
= purple_buddy_get_account(buddy
);
1383 name
= purple_buddy_get_name(buddy
);
1385 permitted
= purple_privacy_check(account
, name
);
1387 /* XXX: Perhaps ask whether to restore the previous lists where appropirate? */
1390 purple_privacy_deny(account
, name
, FALSE
, FALSE
);
1392 purple_privacy_allow(account
, name
, FALSE
, FALSE
);
1394 pidgin_blist_update(purple_get_blist(), node
);
1397 void pidgin_append_blist_node_privacy_menu(GtkWidget
*menu
, PurpleBlistNode
*node
)
1399 PurpleBuddy
*buddy
= (PurpleBuddy
*)node
;
1400 PurpleAccount
*account
;
1403 account
= purple_buddy_get_account(buddy
);
1404 permitted
= purple_privacy_check(account
, purple_buddy_get_name(buddy
));
1406 pidgin_new_item_from_stock(menu
, permitted
? _("_Block") : _("Un_block"),
1407 permitted
? PIDGIN_STOCK_TOOLBAR_BLOCK
: PIDGIN_STOCK_TOOLBAR_UNBLOCK
, G_CALLBACK(toggle_privacy
),
1412 pidgin_append_blist_node_proto_menu(GtkWidget
*menu
, PurpleConnection
*gc
,
1413 PurpleBlistNode
*node
)
1416 PurplePluginProtocolInfo
*prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
);
1418 if(!prpl_info
|| !prpl_info
->blist_node_menu
)
1421 for(l
= ll
= prpl_info
->blist_node_menu(node
); l
; l
= l
->next
) {
1422 PurpleMenuAction
*act
= (PurpleMenuAction
*) l
->data
;
1423 pidgin_append_menu_action(menu
, act
, node
);
1429 pidgin_append_blist_node_extended_menu(GtkWidget
*menu
, PurpleBlistNode
*node
)
1433 for(l
= ll
= purple_blist_node_get_extended_menu(node
); l
; l
= l
->next
) {
1434 PurpleMenuAction
*act
= (PurpleMenuAction
*) l
->data
;
1435 pidgin_append_menu_action(menu
, act
, node
);
1443 pidgin_append_blist_node_move_to_menu(GtkWidget
*menu
, PurpleBlistNode
*node
)
1446 GtkWidget
*menuitem
;
1447 PurpleBlistNode
*group
;
1449 menuitem
= gtk_menu_item_new_with_label(_("Move to"));
1450 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
1451 gtk_widget_show(menuitem
);
1453 submenu
= gtk_menu_new();
1454 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem
), submenu
);
1456 for (group
= purple_blist_get_root(); group
; group
= purple_blist_node_get_sibling_next(group
)) {
1457 if (!PURPLE_BLIST_NODE_IS_GROUP(group
))
1459 if (group
== purple_blist_node_get_parent(node
))
1461 menuitem
= pidgin_new_item_from_stock(submenu
, purple_group_get_name((PurpleGroup
*)group
), NULL
,
1462 G_CALLBACK(gtk_blist_menu_move_to_cb
), node
, 0, 0, NULL
);
1463 g_object_set_data(G_OBJECT(menuitem
), "groupnode", group
);
1465 gtk_widget_show_all(submenu
);
1469 pidgin_blist_make_buddy_menu(GtkWidget
*menu
, PurpleBuddy
*buddy
, gboolean sub
) {
1470 PurpleAccount
*account
= NULL
;
1471 PurpleConnection
*pc
= NULL
;
1472 PurplePluginProtocolInfo
*prpl_info
;
1473 PurpleContact
*contact
;
1474 PurpleBlistNode
*node
;
1475 gboolean contact_expanded
= FALSE
;
1477 g_return_if_fail(menu
);
1478 g_return_if_fail(buddy
);
1480 account
= purple_buddy_get_account(buddy
);
1481 pc
= purple_account_get_connection(account
);
1482 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(pc
));
1484 node
= PURPLE_BLIST_NODE(buddy
);
1486 contact
= purple_buddy_get_contact(buddy
);
1488 PidginBlistNode
*node
= purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(contact
));
1489 contact_expanded
= node
->contact_expanded
;
1492 if (prpl_info
&& prpl_info
->get_info
) {
1493 pidgin_new_item_from_stock(menu
, _("Get _Info"), PIDGIN_STOCK_TOOLBAR_USER_INFO
,
1494 G_CALLBACK(gtk_blist_menu_info_cb
), buddy
, 0, 0, NULL
);
1496 pidgin_new_item_from_stock(menu
, _("I_M"), PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW
,
1497 G_CALLBACK(gtk_blist_menu_im_cb
), buddy
, 0, 0, NULL
);
1500 if (prpl_info
&& prpl_info
->get_media_caps
) {
1501 PurpleAccount
*account
= purple_buddy_get_account(buddy
);
1502 const gchar
*who
= purple_buddy_get_name(buddy
);
1503 PurpleMediaCaps caps
= purple_prpl_get_media_caps(account
, who
);
1504 if (caps
& PURPLE_MEDIA_CAPS_AUDIO
) {
1505 pidgin_new_item_from_stock(menu
, _("_Audio Call"),
1506 PIDGIN_STOCK_TOOLBAR_AUDIO_CALL
,
1507 G_CALLBACK(gtk_blist_menu_audio_call_cb
), buddy
, 0, 0, NULL
);
1509 if (caps
& PURPLE_MEDIA_CAPS_AUDIO_VIDEO
) {
1510 pidgin_new_item_from_stock(menu
, _("Audio/_Video Call"),
1511 PIDGIN_STOCK_TOOLBAR_VIDEO_CALL
,
1512 G_CALLBACK(gtk_blist_menu_video_call_cb
), buddy
, 0, 0, NULL
);
1513 } else if (caps
& PURPLE_MEDIA_CAPS_VIDEO
) {
1514 pidgin_new_item_from_stock(menu
, _("_Video Call"),
1515 PIDGIN_STOCK_TOOLBAR_VIDEO_CALL
,
1516 G_CALLBACK(gtk_blist_menu_video_call_cb
), buddy
, 0, 0, NULL
);
1522 if (prpl_info
&& prpl_info
->send_file
) {
1523 if (!prpl_info
->can_receive_file
||
1524 prpl_info
->can_receive_file(buddy
->account
->gc
, buddy
->name
))
1526 pidgin_new_item_from_stock(menu
, _("_Send File..."),
1527 PIDGIN_STOCK_TOOLBAR_SEND_FILE
,
1528 G_CALLBACK(gtk_blist_menu_send_file_cb
),
1533 pidgin_new_item_from_stock(menu
, _("Add Buddy _Pounce..."), NULL
,
1534 G_CALLBACK(gtk_blist_menu_bp_cb
), buddy
, 0, 0, NULL
);
1536 if (node
->parent
&& node
->parent
->child
->next
&&
1537 !sub
&& !contact_expanded
) {
1538 pidgin_new_item_from_stock(menu
, _("View _Log"), NULL
,
1539 G_CALLBACK(gtk_blist_menu_showlog_cb
),
1540 contact
, 0, 0, NULL
);
1542 pidgin_new_item_from_stock(menu
, _("View _Log"), NULL
,
1543 G_CALLBACK(gtk_blist_menu_showlog_cb
), buddy
, 0, 0, NULL
);
1546 if (!PURPLE_BLIST_NODE_HAS_FLAG(node
, PURPLE_BLIST_NODE_FLAG_NO_SAVE
)) {
1547 gboolean show_offline
= purple_blist_node_get_bool(node
, "show_offline");
1548 pidgin_new_item_from_stock(menu
, show_offline
? _("Hide When Offline") : _("Show When Offline"),
1549 NULL
, G_CALLBACK(gtk_blist_menu_showoffline_cb
), node
, 0, 0, NULL
);
1552 pidgin_append_blist_node_proto_menu(menu
, buddy
->account
->gc
, node
);
1553 pidgin_append_blist_node_extended_menu(menu
, node
);
1555 if (!contact_expanded
&& contact
!= NULL
)
1556 pidgin_append_blist_node_move_to_menu(menu
, (PurpleBlistNode
*)contact
);
1558 if (node
->parent
&& node
->parent
->child
->next
&&
1559 !sub
&& !contact_expanded
) {
1560 pidgin_separator(menu
);
1561 pidgin_append_blist_node_privacy_menu(menu
, node
);
1562 pidgin_new_item_from_stock(menu
, _("_Alias..."), PIDGIN_STOCK_ALIAS
,
1563 G_CALLBACK(gtk_blist_menu_alias_cb
),
1564 contact
, 0, 0, NULL
);
1565 pidgin_new_item_from_stock(menu
, _("_Remove"), GTK_STOCK_REMOVE
,
1566 G_CALLBACK(pidgin_blist_remove_cb
),
1567 contact
, 0, 0, NULL
);
1568 } else if (!sub
|| contact_expanded
) {
1569 pidgin_separator(menu
);
1570 pidgin_append_blist_node_privacy_menu(menu
, node
);
1571 pidgin_new_item_from_stock(menu
, _("_Alias..."), PIDGIN_STOCK_ALIAS
,
1572 G_CALLBACK(gtk_blist_menu_alias_cb
), buddy
, 0, 0, NULL
);
1573 pidgin_new_item_from_stock(menu
, _("_Remove"), GTK_STOCK_REMOVE
,
1574 G_CALLBACK(pidgin_blist_remove_cb
), buddy
,
1580 gtk_blist_key_press_cb(GtkWidget
*tv
, GdkEventKey
*event
, gpointer data
)
1582 PurpleBlistNode
*node
;
1583 GtkTreeIter iter
, parent
;
1584 GtkTreeSelection
*sel
;
1587 sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(tv
));
1588 if(!gtk_tree_selection_get_selected(sel
, NULL
, &iter
))
1591 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, NODE_COLUMN
, &node
, -1);
1593 if(event
->state
& GDK_CONTROL_MASK
&&
1594 (event
->keyval
== 'o' || event
->keyval
== 'O')) {
1597 if(PURPLE_BLIST_NODE_IS_CONTACT(node
)) {
1598 buddy
= purple_contact_get_priority_buddy((PurpleContact
*)node
);
1599 } else if(PURPLE_BLIST_NODE_IS_BUDDY(node
)) {
1600 buddy
= (PurpleBuddy
*)node
;
1605 pidgin_retrieve_user_info(buddy
->account
->gc
, buddy
->name
);
1607 switch (event
->keyval
) {
1609 gtk_blist_menu_alias_cb(tv
, node
);
1613 path
= gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
);
1614 if (gtk_tree_view_row_expanded(GTK_TREE_VIEW(tv
), path
)) {
1615 /* Collapse the Group */
1616 gtk_tree_view_collapse_row(GTK_TREE_VIEW(tv
), path
);
1617 gtk_tree_path_free(path
);
1620 /* Select the Parent */
1621 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, path
)) {
1622 if (gtk_tree_model_iter_parent(GTK_TREE_MODEL(gtkblist
->treemodel
), &parent
, &iter
)) {
1623 gtk_tree_path_free(path
);
1624 path
= gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist
->treemodel
), &parent
);
1625 gtk_tree_view_set_cursor(GTK_TREE_VIEW(tv
), path
, NULL
, FALSE
);
1626 gtk_tree_path_free(path
);
1631 gtk_tree_path_free(path
);
1635 path
= gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
);
1636 if (!gtk_tree_view_row_expanded(GTK_TREE_VIEW(tv
), path
)) {
1637 /* Expand the Group */
1638 if (PURPLE_BLIST_NODE_IS_CONTACT(node
)) {
1639 pidgin_blist_expand_contact_cb(NULL
, node
);
1640 gtk_tree_path_free(path
);
1642 } else if (!PURPLE_BLIST_NODE_IS_BUDDY(node
)) {
1643 gtk_tree_view_expand_row(GTK_TREE_VIEW(tv
), path
, FALSE
);
1644 gtk_tree_path_free(path
);
1648 /* Select the First Child */
1649 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
), &parent
, path
)) {
1650 if (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, &parent
, 0)) {
1651 gtk_tree_path_free(path
);
1652 path
= gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
);
1653 gtk_tree_view_set_cursor(GTK_TREE_VIEW(tv
), path
, NULL
, FALSE
);
1654 gtk_tree_path_free(path
);
1659 gtk_tree_path_free(path
);
1668 set_node_custom_icon_cb(const gchar
*filename
, gpointer data
)
1671 PurpleBlistNode
*node
= (PurpleBlistNode
*)data
;
1673 purple_buddy_icons_node_set_custom_icon_from_file(node
,
1679 set_node_custom_icon(GtkWidget
*w
, PurpleBlistNode
*node
)
1681 /* This doesn't keep track of the returned dialog (so that successive
1682 * calls could be made to re-display that dialog). Do we want that? */
1683 GtkWidget
*win
= pidgin_buddy_icon_chooser_new(NULL
, set_node_custom_icon_cb
, node
);
1684 gtk_widget_show_all(win
);
1688 remove_node_custom_icon(GtkWidget
*w
, PurpleBlistNode
*node
)
1690 purple_buddy_icons_node_set_custom_icon(node
, NULL
, 0);
1694 add_buddy_icon_menu_items(GtkWidget
*menu
, PurpleBlistNode
*node
)
1698 pidgin_new_item_from_stock(menu
, _("Set Custom Icon"), NULL
,
1699 G_CALLBACK(set_node_custom_icon
), node
, 0,
1702 item
= pidgin_new_item_from_stock(menu
, _("Remove Custom Icon"), NULL
,
1703 G_CALLBACK(remove_node_custom_icon
), node
,
1705 if (!purple_buddy_icons_node_has_custom_icon(node
))
1706 gtk_widget_set_sensitive(item
, FALSE
);
1710 create_group_menu (PurpleBlistNode
*node
, PurpleGroup
*g
)
1715 menu
= gtk_menu_new();
1716 item
= pidgin_new_item_from_stock(menu
, _("Add _Buddy..."), GTK_STOCK_ADD
,
1717 G_CALLBACK(pidgin_blist_add_buddy_cb
), node
, 0, 0, NULL
);
1718 gtk_widget_set_sensitive(item
, purple_connections_get_all() != NULL
);
1719 item
= pidgin_new_item_from_stock(menu
, _("Add C_hat..."), GTK_STOCK_ADD
,
1720 G_CALLBACK(pidgin_blist_add_chat_cb
), node
, 0, 0, NULL
);
1721 gtk_widget_set_sensitive(item
, pidgin_blist_joinchat_is_showable());
1722 pidgin_new_item_from_stock(menu
, _("_Delete Group"), GTK_STOCK_REMOVE
,
1723 G_CALLBACK(pidgin_blist_remove_cb
), node
, 0, 0, NULL
);
1724 pidgin_new_item_from_stock(menu
, _("_Rename"), NULL
,
1725 G_CALLBACK(gtk_blist_menu_alias_cb
), node
, 0, 0, NULL
);
1726 if (!(purple_blist_node_get_flags(node
) & PURPLE_BLIST_NODE_FLAG_NO_SAVE
)) {
1727 gboolean show_offline
= purple_blist_node_get_bool(node
, "show_offline");
1728 pidgin_new_item_from_stock(menu
, show_offline
? _("Hide When Offline") : _("Show When Offline"),
1729 NULL
, G_CALLBACK(gtk_blist_menu_showoffline_cb
), node
, 0, 0, NULL
);
1732 add_buddy_icon_menu_items(menu
, node
);
1734 pidgin_append_blist_node_extended_menu(menu
, node
);
1740 create_chat_menu(PurpleBlistNode
*node
, PurpleChat
*c
)
1743 gboolean autojoin
, persistent
;
1745 menu
= gtk_menu_new();
1746 autojoin
= purple_blist_node_get_bool(node
, "gtk-autojoin");
1747 persistent
= purple_blist_node_get_bool(node
, "gtk-persistent");
1749 pidgin_new_item_from_stock(menu
, _("_Join"), PIDGIN_STOCK_CHAT
,
1750 G_CALLBACK(gtk_blist_menu_join_cb
), node
, 0, 0, NULL
);
1751 pidgin_new_check_item(menu
, _("Auto-Join"),
1752 G_CALLBACK(gtk_blist_menu_autojoin_cb
), node
, autojoin
);
1753 pidgin_new_check_item(menu
, _("Persistent"),
1754 G_CALLBACK(gtk_blist_menu_persistent_cb
), node
, persistent
);
1755 pidgin_new_item_from_stock(menu
, _("View _Log"), NULL
,
1756 G_CALLBACK(gtk_blist_menu_showlog_cb
), node
, 0, 0, NULL
);
1758 pidgin_append_blist_node_proto_menu(menu
, c
->account
->gc
, node
);
1759 pidgin_append_blist_node_extended_menu(menu
, node
);
1761 pidgin_separator(menu
);
1763 pidgin_new_item_from_stock(menu
, _("_Edit Settings..."), NULL
,
1764 G_CALLBACK(chat_components_edit
), node
, 0, 0, NULL
);
1765 pidgin_new_item_from_stock(menu
, _("_Alias..."), PIDGIN_STOCK_ALIAS
,
1766 G_CALLBACK(gtk_blist_menu_alias_cb
), node
, 0, 0, NULL
);
1767 pidgin_new_item_from_stock(menu
, _("_Remove"), GTK_STOCK_REMOVE
,
1768 G_CALLBACK(pidgin_blist_remove_cb
), node
, 0, 0, NULL
);
1770 add_buddy_icon_menu_items(menu
, node
);
1776 create_contact_menu (PurpleBlistNode
*node
)
1780 menu
= gtk_menu_new();
1782 pidgin_new_item_from_stock(menu
, _("View _Log"), NULL
,
1783 G_CALLBACK(gtk_blist_menu_showlog_cb
),
1786 pidgin_separator(menu
);
1788 pidgin_new_item_from_stock(menu
, _("_Alias..."), PIDGIN_STOCK_ALIAS
,
1789 G_CALLBACK(gtk_blist_menu_alias_cb
), node
, 0, 0, NULL
);
1790 pidgin_new_item_from_stock(menu
, _("_Remove"), GTK_STOCK_REMOVE
,
1791 G_CALLBACK(pidgin_blist_remove_cb
), node
, 0, 0, NULL
);
1793 add_buddy_icon_menu_items(menu
, node
);
1795 pidgin_separator(menu
);
1797 pidgin_new_item_from_stock(menu
, _("_Collapse"), GTK_STOCK_ZOOM_OUT
,
1798 G_CALLBACK(pidgin_blist_collapse_contact_cb
),
1801 pidgin_append_blist_node_extended_menu(menu
, node
);
1806 create_buddy_menu(PurpleBlistNode
*node
, PurpleBuddy
*b
)
1808 struct _pidgin_blist_node
*gtknode
= (struct _pidgin_blist_node
*)node
->ui_data
;
1810 GtkWidget
*menuitem
;
1811 gboolean show_offline
= purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_offline_buddies");
1813 menu
= gtk_menu_new();
1814 pidgin_blist_make_buddy_menu(menu
, b
, FALSE
);
1816 if(PURPLE_BLIST_NODE_IS_CONTACT(node
)) {
1817 pidgin_separator(menu
);
1819 add_buddy_icon_menu_items(menu
, node
);
1821 if(gtknode
->contact_expanded
) {
1822 pidgin_new_item_from_stock(menu
, _("_Collapse"),
1824 G_CALLBACK(pidgin_blist_collapse_contact_cb
),
1827 pidgin_new_item_from_stock(menu
, _("_Expand"),
1829 G_CALLBACK(pidgin_blist_expand_contact_cb
), node
,
1832 if(node
->child
->next
) {
1833 PurpleBlistNode
*bnode
;
1835 for(bnode
= node
->child
; bnode
; bnode
= bnode
->next
) {
1836 PurpleBuddy
*buddy
= (PurpleBuddy
*)bnode
;
1843 if(!buddy
->account
->gc
)
1845 if(!show_offline
&& !PURPLE_BUDDY_IS_ONLINE(buddy
))
1848 menuitem
= gtk_image_menu_item_new_with_label(buddy
->name
);
1849 buf
= pidgin_create_prpl_icon(buddy
->account
,PIDGIN_PRPL_ICON_SMALL
);
1850 image
= gtk_image_new_from_pixbuf(buf
);
1851 g_object_unref(G_OBJECT(buf
));
1852 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem
),
1854 gtk_widget_show(image
);
1855 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
1856 gtk_widget_show(menuitem
);
1858 submenu
= gtk_menu_new();
1859 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem
), submenu
);
1860 gtk_widget_show(submenu
);
1862 pidgin_blist_make_buddy_menu(submenu
, buddy
, TRUE
);
1870 pidgin_blist_show_context_menu(PurpleBlistNode
*node
,
1871 GtkMenuPositionFunc func
,
1876 struct _pidgin_blist_node
*gtknode
;
1877 GtkWidget
*menu
= NULL
;
1878 gboolean handled
= FALSE
;
1880 gtknode
= (struct _pidgin_blist_node
*)node
->ui_data
;
1882 /* Create a menu based on the thing we right-clicked on */
1883 if (PURPLE_BLIST_NODE_IS_GROUP(node
)) {
1884 PurpleGroup
*g
= (PurpleGroup
*)node
;
1886 menu
= create_group_menu(node
, g
);
1887 } else if (PURPLE_BLIST_NODE_IS_CHAT(node
)) {
1888 PurpleChat
*c
= (PurpleChat
*)node
;
1890 menu
= create_chat_menu(node
, c
);
1891 } else if ((PURPLE_BLIST_NODE_IS_CONTACT(node
)) && (gtknode
->contact_expanded
)) {
1892 menu
= create_contact_menu(node
);
1893 } else if (PURPLE_BLIST_NODE_IS_CONTACT(node
) || PURPLE_BLIST_NODE_IS_BUDDY(node
)) {
1896 if (PURPLE_BLIST_NODE_IS_CONTACT(node
))
1897 b
= purple_contact_get_priority_buddy((PurpleContact
*)node
);
1899 b
= (PurpleBuddy
*)node
;
1901 menu
= create_buddy_menu(node
, b
);
1905 pidgin_blist_tooltip_destroy();
1907 /* Unhook the tooltip-timeout since we don't want a tooltip
1908 * to appear and obscure the context menu we are about to show
1909 This is a workaround for GTK+ bug 107320. */
1910 if (gtkblist
->timeout
) {
1911 g_source_remove(gtkblist
->timeout
);
1912 gtkblist
->timeout
= 0;
1916 /* Now display the menu */
1918 gtk_widget_show_all(menu
);
1919 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
, func
, tv
, button
, time
);
1927 gtk_blist_button_press_cb(GtkWidget
*tv
, GdkEventButton
*event
, gpointer user_data
)
1930 PurpleBlistNode
*node
;
1932 GtkTreeSelection
*sel
;
1933 PurplePlugin
*prpl
= NULL
;
1934 PurplePluginProtocolInfo
*prpl_info
= NULL
;
1935 struct _pidgin_blist_node
*gtknode
;
1936 gboolean handled
= FALSE
;
1938 /* Here we figure out which node was clicked */
1939 if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv
), event
->x
, event
->y
, &path
, NULL
, NULL
, NULL
))
1941 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, path
);
1942 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, NODE_COLUMN
, &node
, -1);
1943 gtknode
= (struct _pidgin_blist_node
*)node
->ui_data
;
1945 /* Right click draws a context menu */
1946 if ((event
->button
== 3) && (event
->type
== GDK_BUTTON_PRESS
)) {
1947 handled
= pidgin_blist_show_context_menu(node
, NULL
, tv
, 3, event
->time
);
1949 /* CTRL+middle click expands or collapse a contact */
1950 } else if ((event
->button
== 2) && (event
->type
== GDK_BUTTON_PRESS
) &&
1951 (event
->state
& GDK_CONTROL_MASK
) && (PURPLE_BLIST_NODE_IS_CONTACT(node
))) {
1952 if (gtknode
->contact_expanded
)
1953 pidgin_blist_collapse_contact_cb(NULL
, node
);
1955 pidgin_blist_expand_contact_cb(NULL
, node
);
1958 /* Double middle click gets info */
1959 } else if ((event
->button
== 2) && (event
->type
== GDK_2BUTTON_PRESS
) &&
1960 ((PURPLE_BLIST_NODE_IS_CONTACT(node
)) || (PURPLE_BLIST_NODE_IS_BUDDY(node
)))) {
1962 if(PURPLE_BLIST_NODE_IS_CONTACT(node
))
1963 b
= purple_contact_get_priority_buddy((PurpleContact
*)node
);
1965 b
= (PurpleBuddy
*)node
;
1967 prpl
= purple_find_prpl(purple_account_get_protocol_id(b
->account
));
1969 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(prpl
);
1971 if (prpl
&& prpl_info
->get_info
)
1972 pidgin_retrieve_user_info(b
->account
->gc
, b
->name
);
1978 * This code only exists because GTK+ doesn't work. If we return
1979 * FALSE here, as would be normal the event propoagates down and
1980 * somehow gets interpreted as the start of a drag event.
1982 * Um, isn't it _normal_ to return TRUE here? Since the event
1983 * was handled? --Mark
1986 sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(tv
));
1987 gtk_tree_selection_select_path(sel
, path
);
1988 gtk_tree_path_free(path
);
1992 gtk_tree_path_free(path
);
1998 pidgin_blist_popup_menu_cb(GtkWidget
*tv
, void *user_data
)
2000 PurpleBlistNode
*node
;
2002 GtkTreeSelection
*sel
;
2003 gboolean handled
= FALSE
;
2005 sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(tv
));
2006 if (!gtk_tree_selection_get_selected(sel
, NULL
, &iter
))
2009 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, NODE_COLUMN
, &node
, -1);
2011 /* Shift+F10 draws a context menu */
2012 handled
= pidgin_blist_show_context_menu(node
, pidgin_treeview_popup_menu_position_func
, tv
, 0, GDK_CURRENT_TIME
);
2017 static void pidgin_blist_buddy_details_cb(gpointer data
, guint action
, GtkWidget
*item
)
2019 pidgin_set_cursor(gtkblist
->window
, GDK_WATCH
);
2021 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/blist/show_buddy_icons",
2022 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item
)));
2024 pidgin_clear_cursor(gtkblist
->window
);
2027 static void pidgin_blist_show_idle_time_cb(gpointer data
, guint action
, GtkWidget
*item
)
2029 pidgin_set_cursor(gtkblist
->window
, GDK_WATCH
);
2031 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/blist/show_idle_time",
2032 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item
)));
2034 pidgin_clear_cursor(gtkblist
->window
);
2037 static void pidgin_blist_show_protocol_icons_cb(gpointer data
, guint action
, GtkWidget
*item
)
2039 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/blist/show_protocol_icons",
2040 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item
)));
2043 static void pidgin_blist_show_empty_groups_cb(gpointer data
, guint action
, GtkWidget
*item
)
2045 pidgin_set_cursor(gtkblist
->window
, GDK_WATCH
);
2047 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/blist/show_empty_groups",
2048 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item
)));
2050 pidgin_clear_cursor(gtkblist
->window
);
2053 static void pidgin_blist_edit_mode_cb(gpointer callback_data
, guint callback_action
,
2054 GtkWidget
*checkitem
)
2056 pidgin_set_cursor(gtkblist
->window
, GDK_WATCH
);
2058 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/blist/show_offline_buddies",
2059 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(checkitem
)));
2061 pidgin_clear_cursor(gtkblist
->window
);
2064 static void pidgin_blist_mute_sounds_cb(gpointer data
, guint action
, GtkWidget
*item
)
2066 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/sound/mute", GTK_CHECK_MENU_ITEM(item
)->active
);
2070 pidgin_blist_mute_pref_cb(const char *name
, PurplePrefType type
,
2071 gconstpointer value
, gpointer data
)
2073 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item(gtkblist
->ift
,
2074 N_("/Tools/Mute Sounds"))), (gboolean
)GPOINTER_TO_INT(value
));
2078 pidgin_blist_sound_method_pref_cb(const char *name
, PurplePrefType type
,
2079 gconstpointer value
, gpointer data
)
2081 gboolean sensitive
= TRUE
;
2083 if(purple_strequal(value
, "none"))
2086 gtk_widget_set_sensitive(gtk_item_factory_get_widget(gtkblist
->ift
, N_("/Tools/Mute Sounds")), sensitive
);
2090 add_buddies_from_vcard(const char *prpl_id
, PurpleGroup
*group
, GList
*list
,
2094 PurpleAccount
*account
= NULL
;
2095 PurpleConnection
*gc
;
2100 for (l
= purple_connections_get_all(); l
!= NULL
; l
= l
->next
)
2102 gc
= (PurpleConnection
*)l
->data
;
2103 account
= purple_connection_get_account(gc
);
2105 if (purple_strequal(purple_account_get_protocol_id(account
), prpl_id
))
2111 if (account
!= NULL
)
2113 for (l
= list
; l
!= NULL
; l
= l
->next
)
2115 purple_blist_request_add_buddy(account
, l
->data
,
2116 (group
? group
->name
: NULL
),
2121 g_list_foreach(list
, (GFunc
)g_free
, NULL
);
2126 parse_vcard(const char *vcard
, PurpleGroup
*group
)
2133 GList
*jabbers
= NULL
;
2135 s
= temp_vcard
= g_strdup(vcard
);
2137 while (*s
!= '\0' && strncmp(s
, "END:vCard", strlen("END:vCard")))
2139 char *field
, *value
;
2143 /* Grab the field */
2144 while (*s
!= '\r' && *s
!= '\n' && *s
!= '\0' && *s
!= ':')
2147 if (*s
== '\r') s
++;
2154 if (*s
!= '\0') *s
++ = '\0';
2156 if ((c
= strchr(field
, ';')) != NULL
)
2159 /* Proceed to the end of the line */
2162 while (*s
!= '\r' && *s
!= '\n' && *s
!= '\0')
2165 if (*s
== '\r') *s
++ = '\0';
2166 if (*s
== '\n') *s
++ = '\0';
2168 /* We only want to worry about a few fields here. */
2169 if (purple_strequal(field
, "FN"))
2170 alias
= g_strdup(value
);
2171 else if (purple_strequal(field
, "X-AIM") || purple_strequal(field
, "X-ICQ") ||
2172 purple_strequal(field
, "X-JABBER"))
2174 char **values
= g_strsplit(value
, ":", 0);
2177 for (im
= values
; *im
!= NULL
; im
++)
2179 if (purple_strequal(field
, "X-AIM"))
2180 aims
= g_list_append(aims
, g_strdup(*im
));
2181 else if (purple_strequal(field
, "X-ICQ"))
2182 icqs
= g_list_append(icqs
, g_strdup(*im
));
2183 else if (purple_strequal(field
, "X-JABBER"))
2184 jabbers
= g_list_append(jabbers
, g_strdup(*im
));
2193 if (aims
== NULL
&& icqs
== NULL
&& jabbers
== NULL
)
2200 add_buddies_from_vcard("prpl-aim", group
, aims
, alias
);
2201 add_buddies_from_vcard("prpl-icq", group
, icqs
, alias
);
2202 add_buddies_from_vcard("prpl-jabber", group
, jabbers
, alias
);
2210 static void pidgin_blist_drag_begin(GtkWidget
*widget
,
2211 GdkDragContext
*drag_context
, gpointer user_data
)
2213 pidgin_blist_tooltip_destroy();
2216 /* Unhook the tooltip-timeout since we don't want a tooltip
2217 * to appear and obscure the dragging operation.
2218 * This is a workaround for GTK+ bug 107320. */
2219 if (gtkblist
->timeout
) {
2220 g_source_remove(gtkblist
->timeout
);
2221 gtkblist
->timeout
= 0;
2226 static void pidgin_blist_drag_data_get_cb(GtkWidget
*widget
,
2228 GtkSelectionData
*data
,
2234 if (data
->target
== gdk_atom_intern("PURPLE_BLIST_NODE", FALSE
))
2236 GtkTreeRowReference
*ref
= g_object_get_data(G_OBJECT(dc
), "gtk-tree-view-source-row");
2237 GtkTreePath
*sourcerow
= gtk_tree_row_reference_get_path(ref
);
2239 PurpleBlistNode
*node
= NULL
;
2242 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, sourcerow
);
2243 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, NODE_COLUMN
, &node
, -1);
2244 gtk_selection_data_set (data
,
2245 gdk_atom_intern ("PURPLE_BLIST_NODE", FALSE
),
2250 gtk_tree_path_free(sourcerow
);
2252 else if (data
->target
== gdk_atom_intern("application/x-im-contact", FALSE
))
2254 GtkTreeRowReference
*ref
;
2255 GtkTreePath
*sourcerow
;
2257 PurpleBlistNode
*node
= NULL
;
2259 PurpleConnection
*gc
;
2261 const char *protocol
;
2263 ref
= g_object_get_data(G_OBJECT(dc
), "gtk-tree-view-source-row");
2264 sourcerow
= gtk_tree_row_reference_get_path(ref
);
2269 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
,
2271 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, NODE_COLUMN
, &node
, -1);
2273 if (PURPLE_BLIST_NODE_IS_CONTACT(node
))
2275 buddy
= purple_contact_get_priority_buddy((PurpleContact
*)node
);
2277 else if (!PURPLE_BLIST_NODE_IS_BUDDY(node
))
2279 gtk_tree_path_free(sourcerow
);
2284 buddy
= (PurpleBuddy
*)node
;
2287 gc
= purple_account_get_connection(buddy
->account
);
2291 gtk_tree_path_free(sourcerow
);
2296 PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
)->list_icon(buddy
->account
,
2299 str
= g_string_new(NULL
);
2300 g_string_printf(str
,
2301 "MIME-Version: 1.0\r\n"
2302 "Content-Type: application/x-im-contact\r\n"
2303 "X-IM-Protocol: %s\r\n"
2304 "X-IM-Username: %s\r\n",
2308 if (buddy
->alias
!= NULL
)
2310 g_string_append_printf(str
,
2311 "X-IM-Alias: %s\r\n",
2315 g_string_append(str
, "\r\n");
2317 gtk_selection_data_set(data
,
2318 gdk_atom_intern("application/x-im-contact", FALSE
),
2320 (const guchar
*)str
->str
,
2321 strlen(str
->str
) + 1);
2323 g_string_free(str
, TRUE
);
2324 gtk_tree_path_free(sourcerow
);
2328 static void pidgin_blist_drag_data_rcv_cb(GtkWidget
*widget
, GdkDragContext
*dc
, guint x
, guint y
,
2329 GtkSelectionData
*sd
, guint info
, guint t
)
2331 if (gtkblist
->drag_timeout
) {
2332 g_source_remove(gtkblist
->drag_timeout
);
2333 gtkblist
->drag_timeout
= 0;
2336 if (sd
->target
== gdk_atom_intern("PURPLE_BLIST_NODE", FALSE
) && sd
->data
) {
2337 PurpleBlistNode
*n
= NULL
;
2338 GtkTreePath
*path
= NULL
;
2339 GtkTreeViewDropPosition position
;
2340 memcpy(&n
, sd
->data
, sizeof(n
));
2341 if(gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget
), x
, y
, &path
, &position
)) {
2342 /* if we're here, I think it means the drop is ok */
2344 PurpleBlistNode
*node
;
2345 struct _pidgin_blist_node
*gtknode
;
2347 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
),
2349 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
),
2350 &iter
, NODE_COLUMN
, &node
, -1);
2351 gtknode
= node
->ui_data
;
2353 if (PURPLE_BLIST_NODE_IS_CONTACT(n
)) {
2354 PurpleContact
*c
= (PurpleContact
*)n
;
2355 if (PURPLE_BLIST_NODE_IS_CONTACT(node
) && gtknode
->contact_expanded
) {
2356 purple_blist_merge_contact(c
, node
);
2357 } else if (PURPLE_BLIST_NODE_IS_CONTACT(node
) ||
2358 PURPLE_BLIST_NODE_IS_CHAT(node
)) {
2360 case GTK_TREE_VIEW_DROP_AFTER
:
2361 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER
:
2362 purple_blist_add_contact(c
, (PurpleGroup
*)node
->parent
,
2365 case GTK_TREE_VIEW_DROP_BEFORE
:
2366 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE
:
2367 purple_blist_add_contact(c
, (PurpleGroup
*)node
->parent
,
2371 } else if(PURPLE_BLIST_NODE_IS_GROUP(node
)) {
2372 purple_blist_add_contact(c
, (PurpleGroup
*)node
, NULL
);
2373 } else if(PURPLE_BLIST_NODE_IS_BUDDY(node
)) {
2374 purple_blist_merge_contact(c
, node
);
2376 } else if (PURPLE_BLIST_NODE_IS_BUDDY(n
)) {
2377 PurpleBuddy
*b
= (PurpleBuddy
*)n
;
2378 if (PURPLE_BLIST_NODE_IS_BUDDY(node
)) {
2380 case GTK_TREE_VIEW_DROP_AFTER
:
2381 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER
:
2382 purple_blist_add_buddy(b
, (PurpleContact
*)node
->parent
,
2383 (PurpleGroup
*)node
->parent
->parent
, node
);
2385 case GTK_TREE_VIEW_DROP_BEFORE
:
2386 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE
:
2387 purple_blist_add_buddy(b
, (PurpleContact
*)node
->parent
,
2388 (PurpleGroup
*)node
->parent
->parent
,
2392 } else if(PURPLE_BLIST_NODE_IS_CHAT(node
)) {
2393 purple_blist_add_buddy(b
, NULL
, (PurpleGroup
*)node
->parent
,
2395 } else if (PURPLE_BLIST_NODE_IS_GROUP(node
)) {
2396 purple_blist_add_buddy(b
, NULL
, (PurpleGroup
*)node
, NULL
);
2397 } else if (PURPLE_BLIST_NODE_IS_CONTACT(node
)) {
2398 if(gtknode
->contact_expanded
) {
2400 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER
:
2401 case GTK_TREE_VIEW_DROP_AFTER
:
2402 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE
:
2403 purple_blist_add_buddy(b
, (PurpleContact
*)node
,
2404 (PurpleGroup
*)node
->parent
, NULL
);
2406 case GTK_TREE_VIEW_DROP_BEFORE
:
2407 purple_blist_add_buddy(b
, NULL
,
2408 (PurpleGroup
*)node
->parent
, node
->prev
);
2413 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER
:
2414 case GTK_TREE_VIEW_DROP_AFTER
:
2415 purple_blist_add_buddy(b
, NULL
,
2416 (PurpleGroup
*)node
->parent
, NULL
);
2418 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE
:
2419 case GTK_TREE_VIEW_DROP_BEFORE
:
2420 purple_blist_add_buddy(b
, NULL
,
2421 (PurpleGroup
*)node
->parent
, node
->prev
);
2426 } else if (PURPLE_BLIST_NODE_IS_CHAT(n
)) {
2427 PurpleChat
*chat
= (PurpleChat
*)n
;
2428 if (PURPLE_BLIST_NODE_IS_BUDDY(node
)) {
2430 case GTK_TREE_VIEW_DROP_AFTER
:
2431 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER
:
2432 case GTK_TREE_VIEW_DROP_BEFORE
:
2433 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE
:
2434 purple_blist_add_chat(chat
,
2435 (PurpleGroup
*)node
->parent
->parent
,
2439 } else if(PURPLE_BLIST_NODE_IS_CONTACT(node
) ||
2440 PURPLE_BLIST_NODE_IS_CHAT(node
)) {
2442 case GTK_TREE_VIEW_DROP_AFTER
:
2443 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER
:
2444 purple_blist_add_chat(chat
, (PurpleGroup
*)node
->parent
, node
);
2446 case GTK_TREE_VIEW_DROP_BEFORE
:
2447 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE
:
2448 purple_blist_add_chat(chat
, (PurpleGroup
*)node
->parent
, node
->prev
);
2451 } else if (PURPLE_BLIST_NODE_IS_GROUP(node
)) {
2452 purple_blist_add_chat(chat
, (PurpleGroup
*)node
, NULL
);
2454 } else if (PURPLE_BLIST_NODE_IS_GROUP(n
)) {
2455 PurpleGroup
*g
= (PurpleGroup
*)n
;
2456 if (PURPLE_BLIST_NODE_IS_GROUP(node
)) {
2458 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER
:
2459 case GTK_TREE_VIEW_DROP_AFTER
:
2460 purple_blist_add_group(g
, node
);
2462 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE
:
2463 case GTK_TREE_VIEW_DROP_BEFORE
:
2464 purple_blist_add_group(g
, node
->prev
);
2467 } else if(PURPLE_BLIST_NODE_IS_BUDDY(node
)) {
2468 purple_blist_add_group(g
, node
->parent
->parent
);
2469 } else if(PURPLE_BLIST_NODE_IS_CONTACT(node
) ||
2470 PURPLE_BLIST_NODE_IS_CHAT(node
)) {
2471 purple_blist_add_group(g
, node
->parent
);
2475 gtk_tree_path_free(path
);
2476 gtk_drag_finish(dc
, TRUE
, (dc
->action
== GDK_ACTION_MOVE
), t
);
2479 else if (sd
->target
== gdk_atom_intern("application/x-im-contact",
2482 PurpleGroup
*group
= NULL
;
2483 GtkTreePath
*path
= NULL
;
2484 GtkTreeViewDropPosition position
;
2485 PurpleAccount
*account
;
2486 char *protocol
= NULL
;
2487 char *username
= NULL
;
2490 if (gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget
),
2491 x
, y
, &path
, &position
))
2494 PurpleBlistNode
*node
;
2496 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
),
2498 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
),
2499 &iter
, NODE_COLUMN
, &node
, -1);
2501 if (PURPLE_BLIST_NODE_IS_BUDDY(node
))
2503 group
= (PurpleGroup
*)node
->parent
->parent
;
2505 else if (PURPLE_BLIST_NODE_IS_CHAT(node
) ||
2506 PURPLE_BLIST_NODE_IS_CONTACT(node
))
2508 group
= (PurpleGroup
*)node
->parent
;
2510 else if (PURPLE_BLIST_NODE_IS_GROUP(node
))
2512 group
= (PurpleGroup
*)node
;
2516 if (pidgin_parse_x_im_contact((const char *)sd
->data
, FALSE
, &account
,
2517 &protocol
, &username
, &alias
))
2519 if (account
== NULL
)
2521 purple_notify_error(NULL
, NULL
,
2522 _("You are not currently signed on with an account that "
2523 "can add that buddy."), NULL
);
2527 purple_blist_request_add_buddy(account
, username
,
2528 (group
? group
->name
: NULL
),
2538 gtk_tree_path_free(path
);
2540 gtk_drag_finish(dc
, TRUE
, (dc
->action
== GDK_ACTION_MOVE
), t
);
2542 else if (sd
->target
== gdk_atom_intern("text/x-vcard", FALSE
) && sd
->data
)
2545 PurpleGroup
*group
= NULL
;
2546 GtkTreePath
*path
= NULL
;
2547 GtkTreeViewDropPosition position
;
2549 if (gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget
),
2550 x
, y
, &path
, &position
))
2553 PurpleBlistNode
*node
;
2555 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
),
2557 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
),
2558 &iter
, NODE_COLUMN
, &node
, -1);
2560 if (PURPLE_BLIST_NODE_IS_BUDDY(node
))
2562 group
= (PurpleGroup
*)node
->parent
->parent
;
2564 else if (PURPLE_BLIST_NODE_IS_CHAT(node
) ||
2565 PURPLE_BLIST_NODE_IS_CONTACT(node
))
2567 group
= (PurpleGroup
*)node
->parent
;
2569 else if (PURPLE_BLIST_NODE_IS_GROUP(node
))
2571 group
= (PurpleGroup
*)node
;
2575 result
= parse_vcard((const gchar
*)sd
->data
, group
);
2577 gtk_drag_finish(dc
, result
, (dc
->action
== GDK_ACTION_MOVE
), t
);
2578 } else if (sd
->target
== gdk_atom_intern("text/uri-list", FALSE
) && sd
->data
) {
2579 GtkTreePath
*path
= NULL
;
2580 GtkTreeViewDropPosition position
;
2582 if (gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget
),
2583 x
, y
, &path
, &position
))
2586 PurpleBlistNode
*node
;
2588 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
),
2590 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
),
2591 &iter
, NODE_COLUMN
, &node
, -1);
2593 if (PURPLE_BLIST_NODE_IS_BUDDY(node
) || PURPLE_BLIST_NODE_IS_CONTACT(node
)) {
2594 PurpleBuddy
*b
= PURPLE_BLIST_NODE_IS_BUDDY(node
) ? PURPLE_BUDDY(node
) : purple_contact_get_priority_buddy(PURPLE_CONTACT(node
));
2595 pidgin_dnd_file_manage(sd
, b
->account
, b
->name
);
2596 gtk_drag_finish(dc
, TRUE
, (dc
->action
== GDK_ACTION_MOVE
), t
);
2598 gtk_drag_finish(dc
, FALSE
, FALSE
, t
);
2604 /* Altered from do_colorshift in gnome-panel */
2606 do_alphashift(GdkPixbuf
*pixbuf
, int shift
)
2609 gint width
, height
, padding
;
2613 if (!gdk_pixbuf_get_has_alpha(pixbuf
))
2616 width
= gdk_pixbuf_get_width(pixbuf
);
2617 height
= gdk_pixbuf_get_height(pixbuf
);
2618 padding
= gdk_pixbuf_get_rowstride(pixbuf
) - width
* 4;
2619 pixels
= gdk_pixbuf_get_pixels(pixbuf
);
2621 for (i
= 0; i
< height
; i
++) {
2622 for (j
= 0; j
< width
; j
++) {
2626 val
= *pixels
- shift
;
2627 *(pixels
++) = CLAMP(val
, 0, 255);
2634 static GdkPixbuf
*pidgin_blist_get_buddy_icon(PurpleBlistNode
*node
,
2635 gboolean scaled
, gboolean greyed
)
2638 PurpleBuddy
*buddy
= NULL
;
2639 PurpleGroup
*group
= NULL
;
2640 const guchar
*data
= NULL
;
2641 GdkPixbuf
*buf
, *ret
= NULL
;
2642 PurpleBuddyIcon
*icon
= NULL
;
2643 PurpleAccount
*account
= NULL
;
2644 PurpleContact
*contact
= NULL
;
2645 PurpleStoredImage
*custom_img
;
2646 PurplePluginProtocolInfo
*prpl_info
= NULL
;
2647 gint orig_width
, orig_height
, scale_width
, scale_height
;
2649 if (PURPLE_BLIST_NODE_IS_CONTACT(node
)) {
2650 buddy
= purple_contact_get_priority_buddy((PurpleContact
*)node
);
2651 contact
= (PurpleContact
*)node
;
2652 } else if (PURPLE_BLIST_NODE_IS_BUDDY(node
)) {
2653 buddy
= (PurpleBuddy
*)node
;
2654 contact
= purple_buddy_get_contact(buddy
);
2655 } else if (PURPLE_BLIST_NODE_IS_GROUP(node
)) {
2656 group
= (PurpleGroup
*)node
;
2657 } else if (PURPLE_BLIST_NODE_IS_CHAT(node
)) {
2658 /* We don't need to do anything here. We just need to not fall
2659 * into the else block and return. */
2665 account
= purple_buddy_get_account(buddy
);
2668 if(account
&& account
->gc
) {
2669 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(account
->gc
->prpl
);
2673 if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_buddy_icons"))
2677 /* If we have a contact then this is either a contact or a buddy and
2678 * we want to fetch the custom icon for the contact. If we don't have
2679 * a contact then this is a group or some other type of node and we
2680 * want to use that directly. */
2682 custom_img
= purple_buddy_icons_node_find_custom_icon((PurpleBlistNode
*)contact
);
2684 custom_img
= purple_buddy_icons_node_find_custom_icon(node
);
2688 data
= purple_imgstore_get_data(custom_img
);
2689 len
= purple_imgstore_get_size(custom_img
);
2694 /* Not sure I like this...*/
2695 if (!(icon
= purple_buddy_icons_find(buddy
->account
, buddy
->name
)))
2697 data
= purple_buddy_icon_get_data(icon
, &len
);
2704 buf
= pidgin_pixbuf_from_data(data
, len
);
2705 purple_buddy_icon_unref(icon
);
2707 purple_debug_warning("gtkblist", "Couldn't load buddy icon "
2708 "on account %s (%s) buddyname=%s "
2709 "custom_img_data=%p\n",
2710 account
? purple_account_get_username(account
) : "(no account)",
2711 account
? purple_account_get_protocol_id(account
) : "(no account)",
2712 buddy
? purple_buddy_get_name(buddy
) : "(no buddy)",
2713 custom_img
? purple_imgstore_get_data(custom_img
) : NULL
);
2714 purple_imgstore_unref(custom_img
);
2717 purple_imgstore_unref(custom_img
);
2720 gboolean offline
= FALSE
, idle
= FALSE
;
2723 PurplePresence
*presence
= purple_buddy_get_presence(buddy
);
2724 if (!PURPLE_BUDDY_IS_ONLINE(buddy
))
2726 if (purple_presence_is_idle(presence
))
2729 if (purple_blist_get_group_online_count(group
) == 0)
2734 gdk_pixbuf_saturate_and_pixelate(buf
, buf
, 0.0, FALSE
);
2737 gdk_pixbuf_saturate_and_pixelate(buf
, buf
, 0.25, FALSE
);
2740 /* I'd use the pidgin_buddy_icon_get_scale_size() thing, but it won't
2741 * tell me the original size, which I need for scaling purposes. */
2742 scale_width
= orig_width
= gdk_pixbuf_get_width(buf
);
2743 scale_height
= orig_height
= gdk_pixbuf_get_height(buf
);
2745 if (prpl_info
&& prpl_info
->icon_spec
.scale_rules
& PURPLE_ICON_SCALE_DISPLAY
)
2746 purple_buddy_icon_get_scale_size(&prpl_info
->icon_spec
, &scale_width
, &scale_height
);
2748 if (scaled
|| scale_height
> 200 || scale_width
> 200) {
2750 float scale_size
= scaled
? 32.0 : 200.0;
2751 if(scale_height
> scale_width
) {
2752 scale_width
= scale_size
* (double)scale_width
/ (double)scale_height
;
2753 scale_height
= scale_size
;
2755 scale_height
= scale_size
* (double)scale_height
/ (double)scale_width
;
2756 scale_width
= scale_size
;
2758 /* Scale & round before making square, so rectangular (but
2759 * non-square) images get rounded corners too. */
2760 tmpbuf
= gdk_pixbuf_new(GDK_COLORSPACE_RGB
, TRUE
, 8, scale_width
, scale_height
);
2761 gdk_pixbuf_fill(tmpbuf
, 0x00000000);
2762 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
);
2763 if (pidgin_gdk_pixbuf_is_opaque(tmpbuf
))
2764 pidgin_gdk_pixbuf_make_round(tmpbuf
);
2765 ret
= gdk_pixbuf_new(GDK_COLORSPACE_RGB
, TRUE
, 8, scale_size
, scale_size
);
2766 gdk_pixbuf_fill(ret
, 0x00000000);
2767 gdk_pixbuf_copy_area(tmpbuf
, 0, 0, scale_width
, scale_height
, ret
, (scale_size
-scale_width
)/2, (scale_size
-scale_height
)/2);
2768 g_object_unref(G_OBJECT(tmpbuf
));
2770 ret
= gdk_pixbuf_scale_simple(buf
,scale_width
,scale_height
, GDK_INTERP_BILINEAR
);
2772 g_object_unref(G_OBJECT(buf
));
2782 * +--- STATUS_SIZE +--- td->avatar_width
2783 * | +-- td->name_width |
2784 * +----+ +-------+ +---------+
2786 * +-------------------------------------------+
2787 * | [ = [ |--- TOOLTIP_BORDER
2788 *name_height --+-| ######[BuddyName = PP [ AAAAAAAAAAA |--+
2789 * | | ######[ = PP [ AAAAAAAAAAA | |
2790 * STATUS SIZE -| | ######[[[[[[[[[[[[[[[[[[[[[ AAAAAAAAAAA | |
2791 * +--+-| ######[Account: So-and-so [ AAAAAAAAAAA | |-- td->avatar_height
2792 * | | [Idle: 4h 15m [ AAAAAAAAAAA | |
2793 * height --+ | [Foo: Bar, Baz [ AAAAAAAAAAA | |
2794 * | | [Status: Awesome [ AAAAAAAAAAA |--+
2795 * +----| [Stop: Hammer Time [ |
2796 * | [ [ |--- TOOLTIP_BORDER
2797 * +-------------------------------------------+
2799 * | +----------------+ |
2803 * +---- TOOLTIP_BORDER +---- TOOLTIP_BORDER
2807 #define STATUS_SIZE 16
2808 #define TOOLTIP_BORDER 12
2809 #define SMALL_SPACE 6
2810 #define LARGE_SPACE 12
2811 #define PRPL_SIZE 16
2812 struct tooltip_data
{
2813 PangoLayout
*layout
;
2814 PangoLayout
*name_layout
;
2815 GdkPixbuf
*prpl_icon
;
2816 GdkPixbuf
*status_icon
;
2818 gboolean avatar_is_prpl_icon
;
2828 static PangoLayout
* create_pango_layout(const char *markup
, int *width
, int *height
)
2830 PangoLayout
*layout
;
2833 layout
= gtk_widget_create_pango_layout(gtkblist
->tipwindow
, NULL
);
2834 pango_layout_set_markup(layout
, markup
, -1);
2835 pango_layout_set_wrap(layout
, PANGO_WRAP_WORD
);
2836 pango_layout_set_width(layout
, 300000);
2838 pango_layout_get_size (layout
, &w
, &h
);
2840 *width
= PANGO_PIXELS(w
);
2842 *height
= PANGO_PIXELS(h
);
2846 static struct tooltip_data
* create_tip_for_account(PurpleAccount
*account
)
2848 struct tooltip_data
*td
= g_new0(struct tooltip_data
, 1);
2849 td
->status_icon
= pidgin_create_prpl_icon(account
, PIDGIN_PRPL_ICON_SMALL
);
2850 /* Yes, status_icon, not prpl_icon */
2851 if (purple_account_is_disconnected(account
))
2852 gdk_pixbuf_saturate_and_pixelate(td
->status_icon
, td
->status_icon
, 0.0, FALSE
);
2853 td
->layout
= create_pango_layout(purple_account_get_username(account
), &td
->width
, &td
->height
);
2854 td
->padding
= SMALL_SPACE
;
2858 static struct tooltip_data
* create_tip_for_node(PurpleBlistNode
*node
, gboolean full
)
2860 struct tooltip_data
*td
= g_new0(struct tooltip_data
, 1);
2861 PurpleAccount
*account
= NULL
;
2862 char *tmp
= NULL
, *node_name
= NULL
, *tooltip_text
= NULL
;
2864 if (PURPLE_BLIST_NODE_IS_BUDDY(node
)) {
2865 account
= ((PurpleBuddy
*)(node
))->account
;
2866 } else if (PURPLE_BLIST_NODE_IS_CHAT(node
)) {
2867 account
= ((PurpleChat
*)(node
))->account
;
2870 td
->padding
= TOOLTIP_BORDER
;
2871 td
->status_icon
= pidgin_blist_get_status_icon(node
, PIDGIN_STATUS_ICON_LARGE
);
2872 td
->avatar
= pidgin_blist_get_buddy_icon(node
, !full
, FALSE
);
2873 if (account
!= NULL
) {
2874 td
->prpl_icon
= pidgin_create_prpl_icon(account
, PIDGIN_PRPL_ICON_SMALL
);
2876 tooltip_text
= pidgin_get_tooltip_text(node
, full
);
2877 if (tooltip_text
&& *tooltip_text
) {
2878 td
->layout
= create_pango_layout(tooltip_text
, &td
->width
, &td
->height
);
2881 if (PURPLE_BLIST_NODE_IS_BUDDY(node
)) {
2882 tmp
= g_markup_escape_text(purple_buddy_get_name((PurpleBuddy
*)node
), -1);
2883 } else if (PURPLE_BLIST_NODE_IS_CHAT(node
)) {
2884 tmp
= g_markup_escape_text(purple_chat_get_name((PurpleChat
*)node
), -1);
2885 } else if (PURPLE_BLIST_NODE_IS_GROUP(node
)) {
2886 tmp
= g_markup_escape_text(purple_group_get_name((PurpleGroup
*)node
), -1);
2888 /* I don't believe this can happen currently, I think
2889 * everything that calls this function checks for one of the
2890 * above node types first. */
2891 tmp
= g_strdup(_("Unknown node type"));
2893 node_name
= g_strdup_printf("<span size='x-large' weight='bold'>%s</span>",
2897 td
->name_layout
= create_pango_layout(node_name
, &td
->name_width
, &td
->name_height
);
2898 td
->name_width
+= SMALL_SPACE
+ PRPL_SIZE
;
2899 td
->name_height
= MAX(td
->name_height
, PRPL_SIZE
+ SMALL_SPACE
);
2900 #if 0 /* PRPL Icon as avatar */
2901 if(!td
->avatar
&& full
) {
2902 td
->avatar
= pidgin_create_prpl_icon(account
, PIDGIN_PRPL_ICON_LARGE
);
2903 td
->avatar_is_prpl_icon
= TRUE
;
2908 td
->avatar_width
= gdk_pixbuf_get_width(td
->avatar
);
2909 td
->avatar_height
= gdk_pixbuf_get_height(td
->avatar
);
2913 g_free(tooltip_text
);
2918 pidgin_blist_paint_tip(GtkWidget
*widget
, gpointer null
)
2921 int current_height
, max_width
;
2923 int max_avatar_width
;
2926 GtkTextDirection dir
= gtk_widget_get_direction(widget
);
2927 int status_size
= 0;
2929 if(gtkblist
->tooltipdata
== NULL
)
2932 style
= gtkblist
->tipwindow
->style
;
2935 max_avatar_width
= 0;
2937 for(l
= gtkblist
->tooltipdata
; l
; l
= l
->next
)
2939 struct tooltip_data
*td
= l
->data
;
2941 max_text_width
= MAX(max_text_width
,
2942 MAX(td
->width
, td
->name_width
));
2943 max_avatar_width
= MAX(max_avatar_width
, td
->avatar_width
);
2944 if (td
->status_icon
)
2945 status_size
= STATUS_SIZE
;
2948 max_width
= TOOLTIP_BORDER
+ status_size
+ SMALL_SPACE
+ max_text_width
+ SMALL_SPACE
+ max_avatar_width
+ TOOLTIP_BORDER
;
2949 if (dir
== GTK_TEXT_DIR_RTL
)
2950 prpl_col
= TOOLTIP_BORDER
+ max_avatar_width
+ SMALL_SPACE
;
2952 prpl_col
= TOOLTIP_BORDER
+ status_size
+ SMALL_SPACE
+ max_text_width
- PRPL_SIZE
;
2954 current_height
= 12;
2955 for(l
= gtkblist
->tooltipdata
; l
; l
= l
->next
)
2957 struct tooltip_data
*td
= l
->data
;
2959 if (td
->avatar
&& pidgin_gdk_pixbuf_is_opaque(td
->avatar
))
2961 if (dir
== GTK_TEXT_DIR_RTL
)
2962 gtk_paint_flat_box(style
, gtkblist
->tipwindow
->window
, GTK_STATE_NORMAL
, GTK_SHADOW_OUT
,
2963 NULL
, gtkblist
->tipwindow
, "tooltip",
2964 TOOLTIP_BORDER
-1, current_height
-1, td
->avatar_width
+2, td
->avatar_height
+ 2);
2966 gtk_paint_flat_box(style
, gtkblist
->tipwindow
->window
, GTK_STATE_NORMAL
, GTK_SHADOW_OUT
,
2967 NULL
, gtkblist
->tipwindow
, "tooltip",
2968 max_width
- (td
->avatar_width
+ TOOLTIP_BORDER
)-1,
2969 current_height
-1,td
->avatar_width
+2, td
->avatar_height
+2);
2972 if (td
->status_icon
) {
2973 if (dir
== GTK_TEXT_DIR_RTL
)
2974 gdk_draw_pixbuf(GDK_DRAWABLE(gtkblist
->tipwindow
->window
), NULL
, td
->status_icon
,
2975 0, 0, max_width
- TOOLTIP_BORDER
- status_size
, current_height
, -1, -1, GDK_RGB_DITHER_NONE
, 0, 0);
2977 gdk_draw_pixbuf(GDK_DRAWABLE(gtkblist
->tipwindow
->window
), NULL
, td
->status_icon
,
2978 0, 0, TOOLTIP_BORDER
, current_height
, -1 , -1, GDK_RGB_DITHER_NONE
, 0, 0);
2982 if (dir
== GTK_TEXT_DIR_RTL
)
2983 gdk_draw_pixbuf(GDK_DRAWABLE(gtkblist
->tipwindow
->window
), NULL
,
2984 td
->avatar
, 0, 0, TOOLTIP_BORDER
, current_height
, -1, -1, GDK_RGB_DITHER_NONE
, 0, 0);
2986 gdk_draw_pixbuf(GDK_DRAWABLE(gtkblist
->tipwindow
->window
), NULL
,
2987 td
->avatar
, 0, 0, max_width
- (td
->avatar_width
+ TOOLTIP_BORDER
),
2988 current_height
, -1 , -1, GDK_RGB_DITHER_NONE
, 0, 0);
2991 if (!td
->avatar_is_prpl_icon
&& td
->prpl_icon
)
2992 gdk_draw_pixbuf(GDK_DRAWABLE(gtkblist
->tipwindow
->window
), NULL
, td
->prpl_icon
,
2995 current_height
+ ((td
->name_height
/ 2) - (PRPL_SIZE
/ 2)),
2996 -1 , -1, GDK_RGB_DITHER_NONE
, 0, 0);
2998 if (td
->name_layout
) {
2999 if (dir
== GTK_TEXT_DIR_RTL
) {
3000 gtk_paint_layout(style
, gtkblist
->tipwindow
->window
, GTK_STATE_NORMAL
, FALSE
,
3001 NULL
, gtkblist
->tipwindow
, "tooltip",
3002 max_width
-(TOOLTIP_BORDER
+ status_size
+ SMALL_SPACE
) - PANGO_PIXELS(300000),
3003 current_height
, td
->name_layout
);
3005 gtk_paint_layout (style
, gtkblist
->tipwindow
->window
, GTK_STATE_NORMAL
, FALSE
,
3006 NULL
, gtkblist
->tipwindow
, "tooltip",
3007 TOOLTIP_BORDER
+ status_size
+ SMALL_SPACE
, current_height
, td
->name_layout
);
3012 if (dir
!= GTK_TEXT_DIR_RTL
) {
3013 gtk_paint_layout (style
, gtkblist
->tipwindow
->window
, GTK_STATE_NORMAL
, FALSE
,
3014 NULL
, gtkblist
->tipwindow
, "tooltip",
3015 TOOLTIP_BORDER
+ status_size
+ SMALL_SPACE
, current_height
+ td
->name_height
, td
->layout
);
3017 gtk_paint_layout(style
, gtkblist
->tipwindow
->window
, GTK_STATE_NORMAL
, FALSE
,
3018 NULL
, gtkblist
->tipwindow
, "tooltip",
3019 max_width
- (TOOLTIP_BORDER
+ status_size
+ SMALL_SPACE
) - PANGO_PIXELS(300000),
3020 current_height
+ td
->name_height
,
3025 current_height
+= MAX(td
->name_height
+ td
->height
, td
->avatar_height
) + td
->padding
;
3031 pidgin_blist_destroy_tooltip_data(void)
3033 while(gtkblist
->tooltipdata
) {
3034 struct tooltip_data
*td
= gtkblist
->tooltipdata
->data
;
3037 g_object_unref(td
->avatar
);
3039 g_object_unref(td
->status_icon
);
3041 g_object_unref(td
->prpl_icon
);
3043 g_object_unref(td
->layout
);
3044 if (td
->name_layout
)
3045 g_object_unref(td
->name_layout
);
3047 gtkblist
->tooltipdata
= g_list_delete_link(gtkblist
->tooltipdata
, gtkblist
->tooltipdata
);
3051 void pidgin_blist_tooltip_destroy()
3053 pidgin_blist_destroy_tooltip_data();
3054 pidgin_tooltip_destroy();
3058 pidgin_blist_align_tooltip(struct tooltip_data
*td
, GtkWidget
*widget
)
3060 GtkTextDirection dir
= gtk_widget_get_direction(widget
);
3062 if (dir
== GTK_TEXT_DIR_RTL
)
3064 char* layout_name
= purple_markup_strip_html(pango_layout_get_text(td
->name_layout
));
3065 PangoDirection dir
= pango_find_base_dir(layout_name
, -1);
3066 if (dir
== PANGO_DIRECTION_RTL
|| dir
== PANGO_DIRECTION_NEUTRAL
)
3067 pango_layout_set_alignment(td
->name_layout
, PANGO_ALIGN_RIGHT
);
3068 g_free(layout_name
);
3069 pango_layout_set_alignment(td
->layout
, PANGO_ALIGN_RIGHT
);
3074 pidgin_blist_create_tooltip_for_node(GtkWidget
*widget
, gpointer data
, int *w
, int *h
)
3076 PurpleBlistNode
*node
= data
;
3079 int max_text_width
= 0;
3080 int max_avatar_width
= 0;
3081 int status_size
= 0;
3083 if (gtkblist
->tooltipdata
) {
3084 gtkblist
->tipwindow
= NULL
;
3085 pidgin_blist_destroy_tooltip_data();
3088 gtkblist
->tipwindow
= widget
;
3089 if (PURPLE_BLIST_NODE_IS_CHAT(node
) ||
3090 PURPLE_BLIST_NODE_IS_BUDDY(node
)) {
3091 struct tooltip_data
*td
= create_tip_for_node(node
, TRUE
);
3092 pidgin_blist_align_tooltip(td
, gtkblist
->tipwindow
);
3093 gtkblist
->tooltipdata
= g_list_append(gtkblist
->tooltipdata
, td
);
3094 } else if (PURPLE_BLIST_NODE_IS_GROUP(node
)) {
3095 PurpleGroup
*group
= (PurpleGroup
*)node
;
3097 struct tooltip_data
*td
= create_tip_for_node(node
, TRUE
);
3098 pidgin_blist_align_tooltip(td
, gtkblist
->tipwindow
);
3099 gtkblist
->tooltipdata
= g_list_append(gtkblist
->tooltipdata
, td
);
3101 /* Accounts with buddies in group */
3102 accounts
= purple_group_get_accounts(group
);
3103 for (; accounts
!= NULL
;
3104 accounts
= g_slist_delete_link(accounts
, accounts
)) {
3105 PurpleAccount
*account
= accounts
->data
;
3106 td
= create_tip_for_account(account
);
3107 gtkblist
->tooltipdata
= g_list_append(gtkblist
->tooltipdata
, td
);
3109 } else if (PURPLE_BLIST_NODE_IS_CONTACT(node
)) {
3110 PurpleBlistNode
*child
;
3111 PurpleBuddy
*b
= purple_contact_get_priority_buddy((PurpleContact
*)node
);
3113 for(child
= node
->child
; child
; child
= child
->next
)
3115 if(PURPLE_BLIST_NODE_IS_BUDDY(child
) && buddy_is_displayable((PurpleBuddy
*)child
)) {
3116 struct tooltip_data
*td
= create_tip_for_node(child
, (b
== (PurpleBuddy
*)child
));
3117 pidgin_blist_align_tooltip(td
, gtkblist
->tipwindow
);
3118 if (b
== (PurpleBuddy
*)child
) {
3119 gtkblist
->tooltipdata
= g_list_prepend(gtkblist
->tooltipdata
, td
);
3121 gtkblist
->tooltipdata
= g_list_append(gtkblist
->tooltipdata
, td
);
3130 for (list
= gtkblist
->tooltipdata
; list
; list
= list
->next
) {
3131 struct tooltip_data
*td
= list
->data
;
3132 max_text_width
= MAX(max_text_width
, MAX(td
->width
, td
->name_width
));
3133 max_avatar_width
= MAX(max_avatar_width
, td
->avatar_width
);
3134 height
+= MAX(MAX(STATUS_SIZE
, td
->avatar_height
), td
->height
+ td
->name_height
) + td
->padding
;
3135 if (td
->status_icon
)
3136 status_size
= MAX(status_size
, STATUS_SIZE
);
3138 height
+= TOOLTIP_BORDER
;
3139 width
= TOOLTIP_BORDER
+ status_size
+ SMALL_SPACE
+ max_text_width
+ SMALL_SPACE
+ max_avatar_width
+ TOOLTIP_BORDER
;
3149 static gboolean
pidgin_blist_expand_timeout(GtkWidget
*tv
)
3153 PurpleBlistNode
*node
;
3154 struct _pidgin_blist_node
*gtknode
;
3156 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),
3157 &path
, NULL
, NULL
, NULL
))
3159 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, path
);
3160 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, NODE_COLUMN
, &node
, -1);
3162 if(!PURPLE_BLIST_NODE_IS_CONTACT(node
)) {
3163 gtk_tree_path_free(path
);
3167 gtknode
= node
->ui_data
;
3169 if (!gtknode
->contact_expanded
) {
3172 pidgin_blist_expand_contact_cb(NULL
, node
);
3174 gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv
), path
, NULL
, >kblist
->contact_rect
);
3175 gdk_drawable_get_size(GDK_DRAWABLE(tv
->window
), &(gtkblist
->contact_rect
.width
), NULL
);
3176 gtkblist
->mouseover_contact
= node
;
3177 gtk_tree_path_down (path
);
3178 while (gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
), &i
, path
)) {
3180 gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv
), path
, NULL
, &rect
);
3181 gtkblist
->contact_rect
.height
+= rect
.height
;
3182 gtk_tree_path_next(path
);
3185 gtk_tree_path_free(path
);
3189 static gboolean
buddy_is_displayable(PurpleBuddy
*buddy
)
3191 struct _pidgin_blist_node
*gtknode
;
3196 gtknode
= ((PurpleBlistNode
*)buddy
)->ui_data
;
3198 return (purple_account_is_connected(buddy
->account
) &&
3199 (purple_presence_is_online(buddy
->presence
) ||
3200 (gtknode
&& gtknode
->recent_signonoff
) ||
3201 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_offline_buddies") ||
3202 purple_blist_node_get_bool((PurpleBlistNode
*)buddy
, "show_offline")));
3205 void pidgin_blist_draw_tooltip(PurpleBlistNode
*node
, GtkWidget
*widget
)
3207 pidgin_tooltip_show(widget
, node
, pidgin_blist_create_tooltip_for_node
, pidgin_blist_paint_tip
);
3210 static gboolean
pidgin_blist_drag_motion_cb(GtkWidget
*tv
, GdkDragContext
*drag_context
,
3211 gint x
, gint y
, guint time
, gpointer user_data
)
3218 * When dragging a buddy into a contact, this is the delay before
3219 * the contact auto-expands.
3223 if (gtkblist
->drag_timeout
) {
3224 if ((y
> gtkblist
->tip_rect
.y
) && ((y
- gtkblist
->tip_rect
.height
) < gtkblist
->tip_rect
.y
))
3226 /* We've left the cell. Remove the timeout and create a new one below */
3227 g_source_remove(gtkblist
->drag_timeout
);
3230 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv
), x
, y
, &path
, NULL
, NULL
, NULL
);
3231 gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv
), path
, NULL
, &rect
);
3234 gtk_tree_path_free(path
);
3236 /* Only autoexpand when in the middle of the cell to avoid annoying un-intended expands */
3237 if (y
< rect
.y
+ (rect
.height
/ 3) ||
3238 y
> rect
.y
+ (2 * (rect
.height
/3)))
3241 rect
.height
= rect
.height
/ 3;
3242 rect
.y
+= rect
.height
;
3244 gtkblist
->tip_rect
= rect
;
3246 gtkblist
->drag_timeout
= g_timeout_add(delay
, (GSourceFunc
)pidgin_blist_expand_timeout
, tv
);
3248 if (gtkblist
->mouseover_contact
) {
3249 if ((y
< gtkblist
->contact_rect
.y
) || ((y
- gtkblist
->contact_rect
.height
) > gtkblist
->contact_rect
.y
)) {
3250 pidgin_blist_collapse_contact_cb(NULL
, gtkblist
->mouseover_contact
);
3251 gtkblist
->mouseover_contact
= NULL
;
3259 pidgin_blist_create_tooltip(GtkWidget
*widget
, GtkTreePath
*path
,
3260 gpointer null
, int *w
, int *h
)
3263 PurpleBlistNode
*node
;
3264 gboolean editable
= FALSE
;
3266 /* If we're editing a cell (e.g. alias editing), don't show the tooltip */
3267 g_object_get(G_OBJECT(gtkblist
->text_rend
), "editable", &editable
, NULL
);
3271 if (gtkblist
->tooltipdata
) {
3272 gtkblist
->tipwindow
= NULL
;
3273 pidgin_blist_destroy_tooltip_data();
3276 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, path
);
3277 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, NODE_COLUMN
, &node
, -1);
3279 return pidgin_blist_create_tooltip_for_node(widget
, node
, w
, h
);
3282 static gboolean
pidgin_blist_motion_cb (GtkWidget
*tv
, GdkEventMotion
*event
, gpointer null
)
3284 if (gtkblist
->mouseover_contact
) {
3285 if ((event
->y
< gtkblist
->contact_rect
.y
) || ((event
->y
- gtkblist
->contact_rect
.height
) > gtkblist
->contact_rect
.y
)) {
3286 pidgin_blist_collapse_contact_cb(NULL
, gtkblist
->mouseover_contact
);
3287 gtkblist
->mouseover_contact
= NULL
;
3294 static gboolean
pidgin_blist_leave_cb (GtkWidget
*w
, GdkEventCrossing
*e
, gpointer n
)
3296 if (gtkblist
->timeout
) {
3297 g_source_remove(gtkblist
->timeout
);
3298 gtkblist
->timeout
= 0;
3301 if (gtkblist
->drag_timeout
) {
3302 g_source_remove(gtkblist
->drag_timeout
);
3303 gtkblist
->drag_timeout
= 0;
3306 if (gtkblist
->mouseover_contact
&&
3307 !((e
->x
> gtkblist
->contact_rect
.x
) && (e
->x
< (gtkblist
->contact_rect
.x
+ gtkblist
->contact_rect
.width
)) &&
3308 (e
->y
> gtkblist
->contact_rect
.y
) && (e
->y
< (gtkblist
->contact_rect
.y
+ gtkblist
->contact_rect
.height
)))) {
3309 pidgin_blist_collapse_contact_cb(NULL
, gtkblist
->mouseover_contact
);
3310 gtkblist
->mouseover_contact
= NULL
;
3318 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/debug/enabled",
3319 !purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/debug/enabled"));
3322 static char *get_mood_icon_path(const char *mood
)
3326 if (purple_strequal(mood
, "busy")) {
3327 path
= g_build_filename(DATADIR
, "pixmaps", "pidgin",
3328 "status", "16", "busy.png", NULL
);
3329 } else if (purple_strequal(mood
, "hiptop")) {
3330 path
= g_build_filename(DATADIR
, "pixmaps", "pidgin",
3331 "emblems", "16", "hiptop.png", NULL
);
3333 char *filename
= g_strdup_printf("%s.png", mood
);
3334 path
= g_build_filename(DATADIR
, "pixmaps", "pidgin",
3335 "emotes", "small", filename
, NULL
);
3342 update_status_with_mood(PurpleAccount
*account
, const gchar
*mood
,
3345 if (mood
&& *mood
) {
3347 purple_account_set_status(account
, "mood", TRUE
,
3348 PURPLE_MOOD_NAME
, mood
,
3349 PURPLE_MOOD_COMMENT
, text
,
3352 purple_account_set_status(account
, "mood", TRUE
,
3353 PURPLE_MOOD_NAME
, mood
,
3357 purple_account_set_status(account
, "mood", FALSE
, NULL
);
3362 edit_mood_cb(PurpleConnection
*gc
, PurpleRequestFields
*fields
)
3364 PurpleRequestField
*mood_field
;
3367 mood_field
= purple_request_fields_get_field(fields
, "mood");
3368 l
= purple_request_field_list_get_selected(mood_field
);
3371 const char *mood
= purple_request_field_list_get_data(mood_field
, l
->data
);
3375 PurpleAccount
*account
= purple_connection_get_account(gc
);
3377 if (gc
->flags
& PURPLE_CONNECTION_SUPPORT_MOOD_MESSAGES
) {
3378 PurpleRequestField
*text_field
;
3379 text_field
= purple_request_fields_get_field(fields
, "text");
3380 text
= purple_request_field_string_get_value(text_field
);
3385 update_status_with_mood(account
, mood
, text
);
3387 GList
*accounts
= purple_accounts_get_all_active();
3389 for (; accounts
; accounts
= g_list_delete_link(accounts
, accounts
)) {
3390 PurpleAccount
*account
= (PurpleAccount
*) accounts
->data
;
3391 PurpleConnection
*gc
= purple_account_get_connection(account
);
3393 if (gc
&& gc
->flags
& PURPLE_CONNECTION_SUPPORT_MOODS
) {
3394 update_status_with_mood(account
, mood
, NULL
);
3402 global_moods_for_each(gpointer key
, gpointer value
, gpointer user_data
)
3404 GList
**out_moods
= (GList
**) user_data
;
3405 PurpleMood
*mood
= (PurpleMood
*) value
;
3407 *out_moods
= g_list_append(*out_moods
, mood
);
3411 get_global_moods(void)
3413 GHashTable
*global_moods
=
3414 g_hash_table_new_full(g_str_hash
, g_str_equal
, NULL
, NULL
);
3415 GHashTable
*mood_counts
=
3416 g_hash_table_new_full(g_str_hash
, g_str_equal
, NULL
, NULL
);
3417 GList
*accounts
= purple_accounts_get_all_active();
3418 PurpleMood
*result
= NULL
;
3419 GList
*out_moods
= NULL
;
3421 int num_accounts
= 0;
3423 for (; accounts
; accounts
= g_list_delete_link(accounts
, accounts
)) {
3424 PurpleAccount
*account
= (PurpleAccount
*) accounts
->data
;
3425 if (purple_account_is_connected(account
)) {
3426 PurpleConnection
*gc
= purple_account_get_connection(account
);
3428 if (gc
->flags
& PURPLE_CONNECTION_SUPPORT_MOODS
) {
3429 PurplePluginProtocolInfo
*prpl_info
=
3430 PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
);
3431 PurpleMood
*mood
= NULL
;
3433 /* PURPLE_CONNECTION_SUPPORT_MOODS would not be set if the prpl doesn't
3434 * have get_moods, so using PURPLE_PROTOCOL_PLUGIN_HAS_FUNC isn't necessary
3436 for (mood
= prpl_info
->get_moods(account
) ;
3437 mood
->mood
!= NULL
; mood
++) {
3439 GPOINTER_TO_INT(g_hash_table_lookup(mood_counts
, mood
->mood
));
3441 if (!g_hash_table_lookup(global_moods
, mood
->mood
)) {
3442 g_hash_table_insert(global_moods
, (gpointer
)mood
->mood
, mood
);
3444 g_hash_table_insert(mood_counts
, (gpointer
)mood
->mood
,
3445 GINT_TO_POINTER(mood_count
+ 1));
3453 g_hash_table_foreach(global_moods
, global_moods_for_each
, &out_moods
);
3454 result
= g_new0(PurpleMood
, g_hash_table_size(global_moods
) + 1);
3457 PurpleMood
*mood
= (PurpleMood
*) out_moods
->data
;
3458 int in_num_accounts
=
3459 GPOINTER_TO_INT(g_hash_table_lookup(mood_counts
, mood
->mood
));
3461 if (in_num_accounts
== num_accounts
) {
3462 /* mood is present in all accounts supporting moods */
3463 result
[i
].mood
= mood
->mood
;
3464 result
[i
].description
= mood
->description
;
3467 out_moods
= g_list_delete_link(out_moods
, out_moods
);
3470 g_hash_table_destroy(global_moods
);
3471 g_hash_table_destroy(mood_counts
);
3476 /* get current set mood for all mood-supporting accounts, or NULL if not set
3477 or not set to the same on all */
3478 static const gchar
*
3479 get_global_mood_status(void)
3481 GList
*accounts
= purple_accounts_get_all_active();
3482 const gchar
*found_mood
= NULL
;
3484 for (; accounts
; accounts
= g_list_delete_link(accounts
, accounts
)) {
3485 PurpleAccount
*account
= (PurpleAccount
*) accounts
->data
;
3487 if (purple_account_is_connected(account
) &&
3488 (purple_account_get_connection(account
)->flags
&
3489 PURPLE_CONNECTION_SUPPORT_MOODS
)) {
3490 PurplePresence
*presence
= purple_account_get_presence(account
);
3491 PurpleStatus
*status
= purple_presence_get_status(presence
, "mood");
3492 const gchar
*curr_mood
= purple_status_get_attr_string(status
, PURPLE_MOOD_NAME
);
3494 if (found_mood
!= NULL
&& !purple_strequal(curr_mood
, found_mood
)) {
3495 /* found a different mood */
3499 found_mood
= curr_mood
;
3508 set_mood_cb(GtkWidget
*widget
, PurpleAccount
*account
)
3510 const char *current_mood
;
3511 PurpleRequestFields
*fields
;
3512 PurpleRequestFieldGroup
*g
;
3513 PurpleRequestField
*f
;
3514 PurpleConnection
*gc
= NULL
;
3515 PurplePluginProtocolInfo
*prpl_info
= NULL
;
3517 PurpleMood
*global_moods
= get_global_moods();
3520 PurplePresence
*presence
= purple_account_get_presence(account
);
3521 PurpleStatus
*status
= purple_presence_get_status(presence
, "mood");
3522 gc
= purple_account_get_connection(account
);
3523 g_return_if_fail(gc
->prpl
!= NULL
);
3524 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
);
3525 current_mood
= purple_status_get_attr_string(status
, PURPLE_MOOD_NAME
);
3527 current_mood
= get_global_mood_status();
3530 fields
= purple_request_fields_new();
3531 g
= purple_request_field_group_new(NULL
);
3532 f
= purple_request_field_list_new("mood", _("Please select your mood from the list"));
3534 purple_request_field_list_add(f
, _("None"), "");
3535 if (current_mood
== NULL
)
3536 purple_request_field_list_add_selected(f
, _("None"));
3538 /* TODO: rlaager wants this sorted. */
3539 /* TODO: darkrain wants it sorted post-translation */
3540 if (account
&& PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info
, get_moods
))
3541 mood
= prpl_info
->get_moods(account
);
3543 mood
= global_moods
;
3544 for ( ; mood
->mood
!= NULL
; mood
++) {
3547 if (mood
->mood
== NULL
|| mood
->description
== NULL
)
3550 path
= get_mood_icon_path(mood
->mood
);
3551 purple_request_field_list_add_icon(f
, _(mood
->description
),
3552 path
, (gpointer
)mood
->mood
);
3555 if (current_mood
&& purple_strequal(current_mood
, mood
->mood
))
3556 purple_request_field_list_add_selected(f
, _(mood
->description
));
3558 purple_request_field_group_add_field(g
, f
);
3560 purple_request_fields_add_group(fields
, g
);
3562 /* if the connection allows setting a mood message */
3563 if (gc
&& (gc
->flags
& PURPLE_CONNECTION_SUPPORT_MOOD_MESSAGES
)) {
3564 g
= purple_request_field_group_new(NULL
);
3565 f
= purple_request_field_string_new("text",
3566 _("Message (optional)"), NULL
, FALSE
);
3567 purple_request_field_group_add_field(g
, f
);
3568 purple_request_fields_add_group(fields
, g
);
3571 purple_request_fields(gc
, _("Edit User Mood"), _("Edit User Mood"),
3573 _("OK"), G_CALLBACK(edit_mood_cb
),
3575 gc
? purple_connection_get_account(gc
) : NULL
,
3578 g_free(global_moods
);
3584 set_mood_cb(NULL
, NULL
);
3587 /***************************************************
3589 ***************************************************/
3590 static GtkItemFactoryEntry blist_menu
[] =
3592 /* NOTE: Do not set any accelerator to Control+O. It is mapped by
3593 gtk_blist_key_press_cb to "Get User Info" on the selected buddy. */
3596 { N_("/_Buddies"), NULL
, NULL
, 0, "<Branch>", NULL
},
3597 { N_("/Buddies/New Instant _Message..."), "<CTL>M", pidgin_dialogs_im
, 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW
},
3598 { N_("/Buddies/Join a _Chat..."), "<CTL>C", pidgin_blist_joinchat_show
, 0, "<StockItem>", PIDGIN_STOCK_CHAT
},
3599 { N_("/Buddies/Get User _Info..."), "<CTL>I", pidgin_dialogs_info
, 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_USER_INFO
},
3600 { N_("/Buddies/View User _Log..."), "<CTL>L", pidgin_dialogs_log
, 0, "<Item>", NULL
},
3601 { "/Buddies/sep1", NULL
, NULL
, 0, "<Separator>", NULL
},
3602 { N_("/Buddies/Sh_ow"), NULL
, NULL
, 0, "<Branch>", NULL
},
3603 { N_("/Buddies/Show/_Offline Buddies"), NULL
, pidgin_blist_edit_mode_cb
, 1, "<CheckItem>", NULL
},
3604 { N_("/Buddies/Show/_Empty Groups"), NULL
, pidgin_blist_show_empty_groups_cb
, 1, "<CheckItem>", NULL
},
3605 { N_("/Buddies/Show/Buddy _Details"), NULL
, pidgin_blist_buddy_details_cb
, 1, "<CheckItem>", NULL
},
3606 { N_("/Buddies/Show/Idle _Times"), NULL
, pidgin_blist_show_idle_time_cb
, 1, "<CheckItem>", NULL
},
3607 { N_("/Buddies/Show/_Protocol Icons"), NULL
, pidgin_blist_show_protocol_icons_cb
, 1, "<CheckItem>", NULL
},
3608 { N_("/Buddies/_Sort Buddies"), NULL
, NULL
, 0, "<Branch>", NULL
},
3609 { "/Buddies/sep2", NULL
, NULL
, 0, "<Separator>", NULL
},
3610 { N_("/Buddies/_Add Buddy..."), "<CTL>B", pidgin_blist_add_buddy_cb
, 0, "<StockItem>", GTK_STOCK_ADD
},
3611 { N_("/Buddies/Add C_hat..."), NULL
, pidgin_blist_add_chat_cb
, 0, "<StockItem>", GTK_STOCK_ADD
},
3612 { N_("/Buddies/Add _Group..."), NULL
, purple_blist_request_add_group
, 0, "<StockItem>", GTK_STOCK_ADD
},
3613 { "/Buddies/sep3", NULL
, NULL
, 0, "<Separator>", NULL
},
3614 { N_("/Buddies/_Quit"), "<CTL>Q", purple_core_quit
, 0, "<StockItem>", GTK_STOCK_QUIT
},
3617 { N_("/_Accounts"), NULL
, NULL
, 0, "<Branch>", NULL
},
3618 { N_("/Accounts/Manage Accounts"), "<CTL>A", pidgin_accounts_window_show
, 0, "<Item>", NULL
},
3621 { N_("/_Tools"), NULL
, NULL
, 0, "<Branch>", NULL
},
3622 { N_("/Tools/Buddy _Pounces"), NULL
, pidgin_pounces_manager_show
, 1, "<Item>", NULL
},
3623 { N_("/Tools/_Certificates"), NULL
, pidgin_certmgr_show
, 0, "<Item>", NULL
},
3624 { N_("/Tools/Custom Smile_ys"), "<CTL>Y", pidgin_smiley_manager_show
, 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_SMILEY
},
3625 { N_("/Tools/Plu_gins"), "<CTL>U", pidgin_plugin_dialog_show
, 2, "<StockItem>", PIDGIN_STOCK_TOOLBAR_PLUGINS
},
3626 { N_("/Tools/Pr_eferences"), "<CTL>P", pidgin_prefs_show
, 0, "<StockItem>", GTK_STOCK_PREFERENCES
},
3627 { N_("/Tools/Pr_ivacy"), NULL
, pidgin_privacy_dialog_show
, 0, "<Item>", NULL
},
3628 { N_("/Tools/Set _Mood"), "<CTL>D", set_mood_show
, 0, "<Item>", NULL
},
3629 { "/Tools/sep2", NULL
, NULL
, 0, "<Separator>", NULL
},
3630 { N_("/Tools/_File Transfers"), "<CTL>T", pidgin_xfer_dialog_show
, 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_TRANSFER
},
3631 { N_("/Tools/R_oom List"), NULL
, pidgin_roomlist_dialog_show
, 0, "<Item>", NULL
},
3632 { N_("/Tools/System _Log"), NULL
, gtk_blist_show_systemlog_cb
, 3, "<Item>", NULL
},
3633 { "/Tools/sep3", NULL
, NULL
, 0, "<Separator>", NULL
},
3634 { N_("/Tools/Mute _Sounds"), NULL
, pidgin_blist_mute_sounds_cb
, 0, "<CheckItem>", NULL
},
3636 { N_("/_Help"), NULL
, NULL
, 0, "<Branch>", NULL
},
3637 { N_("/Help/Online _Help"), "F1", gtk_blist_show_onlinehelp_cb
, 0, "<StockItem>", GTK_STOCK_HELP
},
3638 { "/Help/sep1", NULL
, NULL
, 0, "<Separator>", NULL
},
3639 { N_("/Help/_Build Information"), NULL
, pidgin_dialogs_buildinfo
, 0, "<Item>", NULL
},
3640 { N_("/Help/_Debug Window"), NULL
, toggle_debug
, 0, "<Item>", NULL
},
3641 { N_("/Help/De_veloper Information"), NULL
, pidgin_dialogs_developers
, 0, "<Item>", NULL
},
3642 { N_("/Help/_Plugin Information"), NULL
, pidgin_dialogs_plugins_info
, 0, "<Item>", NULL
},
3643 { N_("/Help/_Translator Information"), NULL
, pidgin_dialogs_translators
, 0, "<Item>", NULL
},
3644 { "/Help/sep2", NULL
, NULL
, 0, "<Separator>", NULL
},
3645 { N_("/Help/_About"), NULL
, pidgin_dialogs_about
, 4, "<StockItem>", GTK_STOCK_ABOUT
},
3648 /*********************************************************
3649 * Private Utility functions *
3650 *********************************************************/
3652 static char *pidgin_get_tooltip_text(PurpleBlistNode
*node
, gboolean full
)
3654 GString
*str
= g_string_new("");
3656 PurplePluginProtocolInfo
*prpl_info
= NULL
;
3659 if (PURPLE_BLIST_NODE_IS_CHAT(node
))
3664 struct proto_chat_entry
*pce
;
3666 PurpleConversation
*conv
;
3667 PidginBlistNode
*bnode
= node
->ui_data
;
3669 chat
= (PurpleChat
*)node
;
3670 prpl
= purple_find_prpl(purple_account_get_protocol_id(chat
->account
));
3671 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(prpl
);
3673 connections
= purple_connections_get_all();
3674 if (connections
&& connections
->next
)
3676 tmp
= g_markup_escape_text(chat
->account
->username
, -1);
3677 g_string_append_printf(str
, _("<b>Account:</b> %s"), tmp
);
3681 if (bnode
&& bnode
->conv
.conv
) {
3682 conv
= bnode
->conv
.conv
;
3685 if (prpl_info
&& prpl_info
->get_chat_name
)
3686 chat_name
= prpl_info
->get_chat_name(chat
->components
);
3688 chat_name
= g_strdup(purple_chat_get_name(chat
));
3690 conv
= purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT
, chat_name
,
3695 if (conv
&& !purple_conv_chat_has_left(PURPLE_CONV_CHAT(conv
))) {
3696 g_string_append_printf(str
, _("\n<b>Occupants:</b> %d"),
3697 g_list_length(purple_conv_chat_get_users(PURPLE_CONV_CHAT(conv
))));
3699 if (prpl_info
&& (prpl_info
->options
& OPT_PROTO_CHAT_TOPIC
)) {
3700 const char *chattopic
= purple_conv_chat_get_topic(PURPLE_CONV_CHAT(conv
));
3701 char *topic
= chattopic
? g_markup_escape_text(chattopic
, -1) : NULL
;
3702 g_string_append_printf(str
, _("\n<b>Topic:</b> %s"), topic
? topic
: _("(no topic set)"));
3707 if (prpl_info
&& prpl_info
->chat_info
!= NULL
)
3708 cur
= prpl_info
->chat_info(chat
->account
->gc
);
3716 if (!pce
->secret
&& (!pce
->required
&&
3717 g_hash_table_lookup(chat
->components
, pce
->identifier
) == NULL
))
3719 tmp
= purple_text_strip_mnemonic(pce
->label
);
3720 name
= g_markup_escape_text(tmp
, -1);
3722 value
= g_markup_escape_text(g_hash_table_lookup(
3723 chat
->components
, pce
->identifier
), -1);
3724 g_string_append_printf(str
, "\n<b>%s</b> %s",
3726 value
? value
: "");
3732 cur
= g_list_delete_link(cur
, cur
);
3735 else if (PURPLE_BLIST_NODE_IS_CONTACT(node
) || PURPLE_BLIST_NODE_IS_BUDDY(node
))
3737 /* NOTE: THIS FUNCTION IS NO LONGER CALLED FOR CONTACTS.
3738 * It is only called by create_tip_for_node(), and create_tip_for_node() is never called for a contact.
3742 PurplePresence
*presence
;
3743 PurpleNotifyUserInfo
*user_info
;
3746 time_t idle_secs
, signon
;
3748 if (PURPLE_BLIST_NODE_IS_CONTACT(node
))
3750 c
= (PurpleContact
*)node
;
3751 b
= purple_contact_get_priority_buddy(c
);
3755 b
= (PurpleBuddy
*)node
;
3756 c
= purple_buddy_get_contact(b
);
3759 prpl
= purple_find_prpl(purple_account_get_protocol_id(b
->account
));
3760 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(prpl
);
3762 presence
= purple_buddy_get_presence(b
);
3763 user_info
= purple_notify_user_info_new();
3766 connections
= purple_connections_get_all();
3767 if (full
&& connections
&& connections
->next
)
3769 tmp
= g_markup_escape_text(purple_account_get_username(
3770 purple_buddy_get_account(b
)), -1);
3771 purple_notify_user_info_add_pair(user_info
, _("Account"), tmp
);
3776 /* If there's not a contact alias, the node is being displayed with
3777 * this alias, so there's no point in showing it in the tooltip. */
3778 if (full
&& c
&& b
->alias
!= NULL
&& b
->alias
[0] != '\0' &&
3779 (c
->alias
!= NULL
&& c
->alias
[0] != '\0') &&
3780 !purple_strequal(c
->alias
, b
->alias
))
3782 tmp
= g_markup_escape_text(b
->alias
, -1);
3783 purple_notify_user_info_add_pair(user_info
, _("Buddy Alias"), tmp
);
3787 /* Nickname/Server Alias */
3788 /* I'd like to only show this if there's a contact or buddy
3789 * alias, but people often set long nicknames, which
3790 * get ellipsized, so the only way to see the whole thing is
3791 * to look at the tooltip. */
3792 if (full
&& b
->server_alias
!= NULL
&& b
->server_alias
[0] != '\0')
3794 tmp
= g_markup_escape_text(b
->server_alias
, -1);
3795 purple_notify_user_info_add_pair(user_info
, _("Nickname"), tmp
);
3800 signon
= purple_presence_get_login_time(presence
);
3801 if (full
&& PURPLE_BUDDY_IS_ONLINE(b
) && signon
> 0)
3803 if (signon
> time(NULL
)) {
3805 * They signed on in the future?! Our local clock
3806 * must be wrong, show the actual date instead of
3809 tmp
= g_strdup(purple_date_format_long(localtime(&signon
)));
3811 tmp
= purple_str_seconds_to_string(time(NULL
) - signon
);
3812 purple_notify_user_info_add_pair(user_info
, _("Logged In"), tmp
);
3817 if (purple_presence_is_idle(presence
))
3819 idle_secs
= purple_presence_get_idle_time(presence
);
3822 tmp
= purple_str_seconds_to_string(time(NULL
) - idle_secs
);
3823 purple_notify_user_info_add_pair(user_info
, _("Idle"), tmp
);
3829 if (full
&& c
&& !PURPLE_BUDDY_IS_ONLINE(b
))
3831 struct _pidgin_blist_node
*gtknode
= ((PurpleBlistNode
*)c
)->ui_data
;
3832 PurpleBlistNode
*bnode
;
3835 if (gtknode
&& (!gtknode
->contact_expanded
|| PURPLE_BLIST_NODE_IS_CONTACT(node
)))
3837 /* We're either looking at a buddy for a collapsed contact or
3838 * an expanded contact itself so we show the most recent
3839 * (largest) last_seen time for any of the buddies under
3841 for (bnode
= ((PurpleBlistNode
*)c
)->child
; bnode
!= NULL
; bnode
= bnode
->next
)
3843 int value
= purple_blist_node_get_int(bnode
, "last_seen");
3844 if (value
> lastseen
)
3850 /* We're dealing with a buddy under an expanded contact,
3851 * so we show the last_seen time for the buddy. */
3852 lastseen
= purple_blist_node_get_int(&b
->node
, "last_seen");
3857 tmp
= purple_str_seconds_to_string(time(NULL
) - lastseen
);
3858 purple_notify_user_info_add_pair(user_info
, _("Last Seen"), tmp
);
3865 /* FIXME: Why is this status special-cased by the core? --rlaager
3866 * FIXME: Alternatively, why not have the core do all of them? --rlaager */
3867 if (!PURPLE_BUDDY_IS_ONLINE(b
)) {
3868 purple_notify_user_info_add_pair(user_info
, _("Status"), _("Offline"));
3871 if (purple_account_is_connected(b
->account
) &&
3872 prpl_info
&& prpl_info
->tooltip_text
)
3874 /* Additional text from the PRPL */
3875 prpl_info
->tooltip_text(b
, user_info
, full
);
3878 /* These are Easter Eggs. Patches to remove them will be rejected. */
3879 if (!g_ascii_strcasecmp(b
->name
, "robflynn"))
3880 purple_notify_user_info_add_pair(user_info
, _("Description"), _("Spooky"));
3881 if (!g_ascii_strcasecmp(b
->name
, "seanegn"))
3882 purple_notify_user_info_add_pair(user_info
, _("Status"), _("Awesome"));
3883 if (!g_ascii_strcasecmp(b
->name
, "chipx86"))
3884 purple_notify_user_info_add_pair(user_info
, _("Status"), _("Rockin'"));
3886 tmp
= purple_notify_user_info_get_text_with_newline(user_info
, "\n");
3887 g_string_append(str
, tmp
);
3890 purple_notify_user_info_destroy(user_info
);
3891 } else if (PURPLE_BLIST_NODE_IS_GROUP(node
)) {
3893 PurpleGroup
*group
= (PurpleGroup
*)node
;
3894 PurpleNotifyUserInfo
*user_info
;
3896 user_info
= purple_notify_user_info_new();
3898 count
= purple_blist_get_group_online_count(group
);
3901 /* Online buddies in group */
3902 tmp
= g_strdup_printf("%d", count
);
3903 purple_notify_user_info_add_pair(user_info
,
3904 _("Online Buddies"),
3909 count
= purple_blist_get_group_size(group
, FALSE
);
3911 /* Total buddies (from online accounts) in group */
3912 tmp
= g_strdup_printf("%d", count
);
3913 purple_notify_user_info_add_pair(user_info
,
3919 tmp
= purple_notify_user_info_get_text_with_newline(user_info
, "\n");
3920 g_string_append(str
, tmp
);
3923 purple_notify_user_info_destroy(user_info
);
3926 purple_signal_emit(pidgin_blist_get_handle(), "drawing-tooltip",
3929 return g_string_free(str
, FALSE
);
3932 static GHashTable
*cached_emblems
;
3934 static void _cleanup_cached_emblem(gpointer data
, GObject
*obj
) {
3935 g_hash_table_remove(cached_emblems
, data
);
3938 static GdkPixbuf
* _pidgin_blist_get_cached_emblem(gchar
*path
) {
3939 GdkPixbuf
*pb
= g_hash_table_lookup(cached_emblems
, path
);
3942 /* The caller gets a reference */
3946 pb
= pidgin_pixbuf_new_from_file(path
);
3948 /* We don't want to own a ref to the pixbuf, but we need to keep clean up. */
3949 /* I'm not sure if it would be better to just keep our ref and not let the emblem ever be destroyed */
3950 g_object_weak_ref(G_OBJECT(pb
), _cleanup_cached_emblem
, path
);
3951 g_hash_table_insert(cached_emblems
, path
, pb
);
3960 pidgin_blist_get_emblem(PurpleBlistNode
*node
)
3962 PurpleBuddy
*buddy
= NULL
;
3963 struct _pidgin_blist_node
*gtknode
= node
->ui_data
;
3965 PurplePluginProtocolInfo
*prpl_info
;
3966 const char *name
= NULL
;
3967 char *filename
, *path
;
3968 PurplePresence
*p
= NULL
;
3971 if(PURPLE_BLIST_NODE_IS_CONTACT(node
)) {
3972 if(!gtknode
->contact_expanded
) {
3973 buddy
= purple_contact_get_priority_buddy((PurpleContact
*)node
);
3975 } else if(PURPLE_BLIST_NODE_IS_BUDDY(node
)) {
3976 buddy
= (PurpleBuddy
*)node
;
3977 p
= purple_buddy_get_presence(buddy
);
3978 if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_MOBILE
)) {
3979 /* This emblem comes from the small emoticon set now,
3980 * to reduce duplication. */
3981 path
= g_build_filename(DATADIR
, "pixmaps", "pidgin", "emotes",
3982 "small", "mobile.png", NULL
);
3983 return _pidgin_blist_get_cached_emblem(path
);
3986 if (((struct _pidgin_blist_node
*)(node
->parent
->ui_data
))->contact_expanded
) {
3987 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_protocol_icons"))
3989 return pidgin_create_prpl_icon(((PurpleBuddy
*)node
)->account
, PIDGIN_PRPL_ICON_SMALL
);
3995 g_return_val_if_fail(buddy
!= NULL
, NULL
);
3997 if (!purple_privacy_check(buddy
->account
, purple_buddy_get_name(buddy
))) {
3998 path
= g_build_filename(DATADIR
, "pixmaps", "pidgin", "emblems", "16", "blocked.png", NULL
);
3999 return _pidgin_blist_get_cached_emblem(path
);
4002 /* If we came through the contact code flow above, we didn't need
4003 * to get the presence until now. */
4005 p
= purple_buddy_get_presence(buddy
);
4007 if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_MOBILE
)) {
4008 /* This emblem comes from the small emoticon set now, to reduce duplication. */
4009 path
= g_build_filename(DATADIR
, "pixmaps", "pidgin", "emotes", "small", "mobile.png", NULL
);
4010 return _pidgin_blist_get_cached_emblem(path
);
4013 tune
= purple_presence_get_status(p
, "tune");
4014 if (tune
&& purple_status_is_active(tune
)) {
4015 /* TODO: Replace "Tune" with generalized "Media" in 3.0. */
4016 if (purple_status_get_attr_string(tune
, "game") != NULL
) {
4017 path
= g_build_filename(DATADIR
, "pixmaps", "pidgin", "emblems", "16", "game.png", NULL
);
4018 return _pidgin_blist_get_cached_emblem(path
);
4020 /* TODO: Replace "Tune" with generalized "Media" in 3.0. */
4021 if (purple_status_get_attr_string(tune
, "office") != NULL
) {
4022 path
= g_build_filename(DATADIR
, "pixmaps", "pidgin", "emblems", "16", "office.png", NULL
);
4023 return _pidgin_blist_get_cached_emblem(path
);
4025 /* Regular old "tune" is the only one in all protocols. */
4026 /* This emblem comes from the small emoticon set now, to reduce duplication. */
4027 path
= g_build_filename(DATADIR
, "pixmaps", "pidgin", "emotes", "small", "music.png", NULL
);
4028 return _pidgin_blist_get_cached_emblem(path
);
4031 prpl
= purple_find_prpl(purple_account_get_protocol_id(buddy
->account
));
4035 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(prpl
);
4036 if (prpl_info
&& prpl_info
->list_emblem
)
4037 name
= prpl_info
->list_emblem(buddy
);
4040 PurpleStatus
*status
;
4042 if (!purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_MOOD
))
4045 status
= purple_presence_get_status(p
, "mood");
4046 name
= purple_status_get_attr_string(status
, PURPLE_MOOD_NAME
);
4048 if (!(name
&& *name
))
4051 path
= get_mood_icon_path(name
);
4053 filename
= g_strdup_printf("%s.png", name
);
4054 path
= g_build_filename(DATADIR
, "pixmaps", "pidgin", "emblems", "16", filename
, NULL
);
4058 /* _pidgin_blist_get_cached_emblem() assumes ownership of path */
4059 return _pidgin_blist_get_cached_emblem(path
);
4064 pidgin_blist_get_status_icon(PurpleBlistNode
*node
, PidginStatusIconSize size
)
4067 const char *icon
= NULL
;
4068 struct _pidgin_blist_node
*gtknode
= node
->ui_data
;
4069 struct _pidgin_blist_node
*gtkbuddynode
= NULL
;
4070 PurpleBuddy
*buddy
= NULL
;
4071 PurpleChat
*chat
= NULL
;
4072 GtkIconSize icon_size
= gtk_icon_size_from_name((size
== PIDGIN_STATUS_ICON_LARGE
) ? PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
:
4073 PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC
);
4075 if(PURPLE_BLIST_NODE_IS_CONTACT(node
)) {
4076 if(!gtknode
->contact_expanded
) {
4077 buddy
= purple_contact_get_priority_buddy((PurpleContact
*)node
);
4079 gtkbuddynode
= ((PurpleBlistNode
*)buddy
)->ui_data
;
4081 } else if(PURPLE_BLIST_NODE_IS_BUDDY(node
)) {
4082 buddy
= (PurpleBuddy
*)node
;
4083 gtkbuddynode
= node
->ui_data
;
4084 } else if(PURPLE_BLIST_NODE_IS_CHAT(node
)) {
4085 chat
= (PurpleChat
*)node
;
4091 PurpleAccount
*account
;
4095 account
= buddy
->account
;
4097 account
= chat
->account
;
4099 prpl
= purple_find_prpl(purple_account_get_protocol_id(account
));
4105 PurpleConversation
*conv
= find_conversation_with_buddy(buddy
);
4110 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
4111 if (gtkconv
== NULL
&& size
== PIDGIN_STATUS_ICON_SMALL
) {
4112 PidginBlistNode
*ui
= buddy
->node
.ui_data
;
4113 if (ui
== NULL
|| (ui
->conv
.flags
& PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE
))
4114 return gtk_widget_render_icon (GTK_WIDGET(gtkblist
->treeview
),
4115 PIDGIN_STOCK_STATUS_MESSAGE
, icon_size
, "GtkTreeView");
4119 p
= purple_buddy_get_presence(buddy
);
4120 trans
= purple_presence_is_idle(p
);
4122 if (PURPLE_BUDDY_IS_ONLINE(buddy
) && gtkbuddynode
&& gtkbuddynode
->recent_signonoff
)
4123 icon
= PIDGIN_STOCK_STATUS_LOGIN
;
4124 else if (gtkbuddynode
&& gtkbuddynode
->recent_signonoff
)
4125 icon
= PIDGIN_STOCK_STATUS_LOGOUT
;
4126 else if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_UNAVAILABLE
))
4128 icon
= PIDGIN_STOCK_STATUS_BUSY_I
;
4130 icon
= PIDGIN_STOCK_STATUS_BUSY
;
4131 else if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_AWAY
))
4133 icon
= PIDGIN_STOCK_STATUS_AWAY_I
;
4135 icon
= PIDGIN_STOCK_STATUS_AWAY
;
4136 else if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_EXTENDED_AWAY
))
4138 icon
= PIDGIN_STOCK_STATUS_XA_I
;
4140 icon
= PIDGIN_STOCK_STATUS_XA
;
4141 else if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_OFFLINE
))
4142 icon
= PIDGIN_STOCK_STATUS_OFFLINE
;
4144 icon
= PIDGIN_STOCK_STATUS_AVAILABLE_I
;
4145 else if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_INVISIBLE
))
4146 icon
= PIDGIN_STOCK_STATUS_INVISIBLE
;
4148 icon
= PIDGIN_STOCK_STATUS_AVAILABLE
;
4150 icon
= PIDGIN_STOCK_STATUS_CHAT
;
4152 icon
= PIDGIN_STOCK_STATUS_PERSON
;
4155 ret
= gtk_widget_render_icon (GTK_WIDGET(gtkblist
->treeview
), icon
,
4156 icon_size
, "GtkTreeView");
4161 theme_font_get_color_default(PidginThemeFont
*font
, const char *def
)
4164 if (!font
|| !(ret
= pidgin_theme_font_get_color_describe(font
)))
4170 theme_font_get_face_default(PidginThemeFont
*font
, const char *def
)
4173 if (!font
|| !(ret
= pidgin_theme_font_get_font_face(font
)))
4179 pidgin_blist_get_name_markup(PurpleBuddy
*b
, gboolean selected
, gboolean aliased
)
4181 const char *name
, *name_color
, *name_font
, *status_color
, *status_font
;
4184 PurplePluginProtocolInfo
*prpl_info
= NULL
;
4185 PurpleContact
*contact
;
4186 PurplePresence
*presence
;
4187 struct _pidgin_blist_node
*gtkcontactnode
= NULL
;
4188 char *idletime
= NULL
, *statustext
= NULL
, *nametext
= NULL
;
4189 PurpleConversation
*conv
= find_conversation_with_buddy(b
);
4190 gboolean hidden_conv
= FALSE
;
4191 gboolean biglist
= purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_buddy_icons");
4192 PidginThemeFont
*statusfont
= NULL
, *namefont
= NULL
;
4193 PidginBlistTheme
*theme
;
4196 PidginBlistNode
*ui
= b
->node
.ui_data
;
4198 if (ui
->conv
.flags
& PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE
)
4201 if (PIDGIN_CONVERSATION(conv
) == NULL
)
4206 /* XXX Good luck cleaning up this crap */
4207 contact
= PURPLE_CONTACT(PURPLE_BLIST_NODE(b
)->parent
);
4209 gtkcontactnode
= purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(contact
));
4212 if (gtkcontactnode
&& !gtkcontactnode
->contact_expanded
&& contact
->alias
)
4213 name
= contact
->alias
;
4215 name
= purple_buddy_get_alias(b
);
4217 /* Raise a contact pre-draw signal here. THe callback will return an
4218 * escaped version of the name. */
4219 nametext
= purple_signal_emit_return_1(pidgin_blist_get_handle(), "drawing-buddy", b
);
4222 nametext
= g_markup_escape_text(name
, strlen(name
));
4224 presence
= purple_buddy_get_presence(b
);
4226 /* Name is all that is needed */
4227 if (!aliased
|| biglist
) {
4230 prpl
= purple_find_prpl(purple_account_get_protocol_id(b
->account
));
4233 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(prpl
);
4235 if (prpl_info
&& prpl_info
->status_text
&& b
->account
->gc
) {
4236 char *tmp
= prpl_info
->status_text(b
);
4239 if(tmp
&& !g_utf8_validate(tmp
, -1, &end
)) {
4240 char *new = g_strndup(tmp
,
4241 g_utf8_pointer_to_offset(tmp
, end
));
4246 g_strdelimit(tmp
, "\n", ' ');
4247 purple_str_strip_char(tmp
, '\r');
4252 if(!purple_presence_is_online(presence
) && !statustext
)
4253 statustext
= g_strdup(_("Offline"));
4256 if (purple_presence_is_idle(presence
) && purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_idle_time")) {
4257 time_t idle_secs
= purple_presence_get_idle_time(presence
);
4259 if (idle_secs
> 0) {
4260 int iday
, ihrs
, imin
;
4264 iday
= (t
- idle_secs
) / (24 * 60 * 60);
4265 ihrs
= ((t
- idle_secs
) / 60 / 60) % 24;
4266 imin
= ((t
- idle_secs
) / 60) % 60;
4269 idletime
= g_strdup_printf(_("Idle %dd %dh %02dm"), iday
, ihrs
, imin
);
4271 idletime
= g_strdup_printf(_("Idle %dh %02dm"), ihrs
, imin
);
4273 idletime
= g_strdup_printf(_("Idle %dm"), imin
);
4276 idletime
= g_strdup(_("Idle"));
4280 /* choose the colors of the text */
4281 theme
= pidgin_blist_get_theme();
4285 if (purple_presence_is_idle(presence
)) {
4286 namefont
= statusfont
= pidgin_blist_theme_get_idle_text_info(theme
);
4287 name_color
= "dim grey";
4288 } else if (!purple_presence_is_online(presence
)) {
4289 namefont
= pidgin_blist_theme_get_offline_text_info(theme
);
4290 name_color
= "dim grey";
4291 statusfont
= pidgin_blist_theme_get_status_text_info(theme
);
4292 } else if (purple_presence_is_available(presence
)) {
4293 namefont
= pidgin_blist_theme_get_online_text_info(theme
);
4294 statusfont
= pidgin_blist_theme_get_status_text_info(theme
);
4296 namefont
= pidgin_blist_theme_get_away_text_info(theme
);
4297 statusfont
= pidgin_blist_theme_get_status_text_info(theme
);
4301 && (purple_presence_is_idle(presence
)
4302 || !purple_presence_is_online(presence
)))
4304 name_color
= "dim grey";
4308 name_color
= theme_font_get_color_default(namefont
, name_color
);
4309 name_font
= theme_font_get_face_default(namefont
, "");
4311 status_color
= theme_font_get_color_default(statusfont
, "dim grey");
4312 status_font
= theme_font_get_face_default(statusfont
, "");
4314 if (aliased
&& selected
) {
4316 name_color
= "black";
4317 status_color
= "black";
4320 status_color
= NULL
;
4325 char *tmp
= nametext
;
4326 nametext
= g_strdup_printf("<b>%s</b>", tmp
);
4330 /* Put it all together */
4331 if ((!aliased
|| biglist
) && (statustext
|| idletime
)) {
4332 /* using <span size='smaller'> breaks the status, so it must be seperated into <small><span>*/
4334 text
= g_strdup_printf("<span font_desc='%s' foreground='%s'>%s</span>\n"
4335 "<small><span font_desc='%s' foreground='%s'>%s%s%s</span></small>",
4336 name_font
, name_color
, nametext
, status_font
, status_color
,
4337 idletime
!= NULL
? idletime
: "",
4338 (idletime
!= NULL
&& statustext
!= NULL
) ? " - " : "",
4339 statustext
!= NULL
? statustext
: "");
4340 } else if (status_color
) {
4341 text
= g_strdup_printf("<span font_desc='%s'>%s</span>\n"
4342 "<small><span font_desc='%s' foreground='%s'>%s%s%s</span></small>",
4343 name_font
, nametext
, status_font
, status_color
,
4344 idletime
!= NULL
? idletime
: "",
4345 (idletime
!= NULL
&& statustext
!= NULL
) ? " - " : "",
4346 statustext
!= NULL
? statustext
: "");
4348 text
= g_strdup_printf("<span font_desc='%s'>%s</span>\n"
4349 "<small><span font_desc='%s'>%s%s%s</span></small>",
4350 name_font
, nametext
, status_font
,
4351 idletime
!= NULL
? idletime
: "",
4352 (idletime
!= NULL
&& statustext
!= NULL
) ? " - " : "",
4353 statustext
!= NULL
? statustext
: "");
4357 text
= g_strdup_printf("<span font_desc='%s' color='%s'>%s</span>",
4358 name_font
, name_color
, nametext
);
4360 text
= g_strdup_printf("<span font_desc='%s'>%s</span>", name_font
,
4370 text
= g_strdup_printf("<b>%s</b>", tmp
);
4377 static void pidgin_blist_restore_position(void)
4379 int blist_x
, blist_y
, blist_width
, blist_height
;
4381 blist_width
= purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/blist/width");
4383 /* if the window exists, is hidden, we're saving positions, and the
4384 * position is sane... */
4385 if (gtkblist
&& gtkblist
->window
&&
4386 !GTK_WIDGET_VISIBLE(gtkblist
->window
) && blist_width
!= 0) {
4388 blist_x
= purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/blist/x");
4389 blist_y
= purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/blist/y");
4390 blist_height
= purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/blist/height");
4392 /* ...check position is on screen... */
4393 if (blist_x
>= gdk_screen_width())
4394 blist_x
= gdk_screen_width() - 100;
4395 else if (blist_x
+ blist_width
< 0)
4398 if (blist_y
>= gdk_screen_height())
4399 blist_y
= gdk_screen_height() - 100;
4400 else if (blist_y
+ blist_height
< 0)
4403 /* ...and move it back. */
4404 gtk_window_move(GTK_WINDOW(gtkblist
->window
), blist_x
, blist_y
);
4405 gtk_window_resize(GTK_WINDOW(gtkblist
->window
), blist_width
, blist_height
);
4406 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/list_maximized"))
4407 gtk_window_maximize(GTK_WINDOW(gtkblist
->window
));
4411 static gboolean
pidgin_blist_refresh_timer(PurpleBuddyList
*list
)
4413 PurpleBlistNode
*gnode
, *cnode
;
4415 if (gtk_blist_visibility
== GDK_VISIBILITY_FULLY_OBSCURED
4416 || !GTK_WIDGET_VISIBLE(gtkblist
->window
))
4419 for(gnode
= list
->root
; gnode
; gnode
= gnode
->next
) {
4420 if(!PURPLE_BLIST_NODE_IS_GROUP(gnode
))
4422 for(cnode
= gnode
->child
; cnode
; cnode
= cnode
->next
) {
4423 if(PURPLE_BLIST_NODE_IS_CONTACT(cnode
)) {
4426 buddy
= purple_contact_get_priority_buddy((PurpleContact
*)cnode
);
4429 purple_presence_is_idle(purple_buddy_get_presence(buddy
)))
4430 pidgin_blist_update_contact(list
, (PurpleBlistNode
*)buddy
);
4439 static void pidgin_blist_hide_node(PurpleBuddyList
*list
, PurpleBlistNode
*node
, gboolean update
)
4441 struct _pidgin_blist_node
*gtknode
= (struct _pidgin_blist_node
*)node
->ui_data
;
4444 if (!gtknode
|| !gtknode
->row
|| !gtkblist
)
4447 if(gtkblist
->selected_node
== node
)
4448 gtkblist
->selected_node
= NULL
;
4449 if (get_iter_from_node(node
, &iter
)) {
4450 gtk_tree_store_remove(gtkblist
->treemodel
, &iter
);
4451 if(update
&& (PURPLE_BLIST_NODE_IS_CONTACT(node
) ||
4452 PURPLE_BLIST_NODE_IS_BUDDY(node
) || PURPLE_BLIST_NODE_IS_CHAT(node
))) {
4453 pidgin_blist_update(list
, node
->parent
);
4456 gtk_tree_row_reference_free(gtknode
->row
);
4457 gtknode
->row
= NULL
;
4460 static const char *require_connection
[] =
4462 N_("/Buddies/New Instant Message..."),
4463 N_("/Buddies/Join a Chat..."),
4464 N_("/Buddies/Get User Info..."),
4465 N_("/Buddies/Add Buddy..."),
4466 N_("/Buddies/Add Chat..."),
4467 N_("/Buddies/Add Group..."),
4470 static const int require_connection_size
= sizeof(require_connection
)
4471 / sizeof(*require_connection
);
4474 * Rebuild dynamic menus and make menu items sensitive/insensitive
4475 * where appropriate.
4478 update_menu_bar(PidginBuddyList
*gtkblist
)
4484 g_return_if_fail(gtkblist
!= NULL
);
4486 pidgin_blist_update_accounts_menu();
4488 sensitive
= (purple_connections_get_all() != NULL
);
4490 for (i
= 0; i
< require_connection_size
; i
++)
4492 widget
= gtk_item_factory_get_widget(gtkblist
->ift
, require_connection
[i
]);
4493 gtk_widget_set_sensitive(widget
, sensitive
);
4496 widget
= gtk_item_factory_get_widget(gtkblist
->ift
, N_("/Buddies/Join a Chat..."));
4497 gtk_widget_set_sensitive(widget
, pidgin_blist_joinchat_is_showable());
4499 widget
= gtk_item_factory_get_widget(gtkblist
->ift
, N_("/Buddies/Add Chat..."));
4500 gtk_widget_set_sensitive(widget
, pidgin_blist_joinchat_is_showable());
4502 widget
= gtk_item_factory_get_widget(gtkblist
->ift
, N_("/Tools/Privacy"));
4503 gtk_widget_set_sensitive(widget
, sensitive
);
4505 widget
= gtk_item_factory_get_widget(gtkblist
->ift
, N_("/Tools/Room List"));
4506 gtk_widget_set_sensitive(widget
, pidgin_roomlist_is_showable());
4510 sign_on_off_cb(PurpleConnection
*gc
, PurpleBuddyList
*blist
)
4512 PidginBuddyList
*gtkblist
= PIDGIN_BLIST(blist
);
4514 update_menu_bar(gtkblist
);
4518 plugin_changed_cb(PurplePlugin
*p
, gpointer data
)
4520 pidgin_blist_update_plugin_actions();
4524 unseen_conv_menu(void)
4526 static GtkWidget
*menu
= NULL
;
4527 GList
*convs
= NULL
;
4531 gtk_widget_destroy(menu
);
4535 ims
= pidgin_conversations_find_unseen_list(PURPLE_CONV_TYPE_IM
,
4536 PIDGIN_UNSEEN_TEXT
, FALSE
, 0);
4538 chats
= pidgin_conversations_find_unseen_list(PURPLE_CONV_TYPE_CHAT
,
4539 PIDGIN_UNSEEN_NICK
, FALSE
, 0);
4542 convs
= g_list_concat(ims
, chats
);
4543 else if(ims
&& !chats
)
4545 else if(!ims
&& chats
)
4549 /* no conversations added, don't show the menu */
4552 menu
= gtk_menu_new();
4554 pidgin_conversations_fill_menu(menu
, convs
);
4556 gtk_widget_show_all(menu
);
4557 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
, NULL
, NULL
, 3,
4558 gtk_get_current_event_time());
4562 menutray_press_cb(GtkWidget
*widget
, GdkEventButton
*event
)
4566 switch (event
->button
) {
4568 convs
= pidgin_conversations_find_unseen_list(PURPLE_CONV_TYPE_IM
,
4569 PIDGIN_UNSEEN_TEXT
, FALSE
, 1);
4572 convs
= pidgin_conversations_find_unseen_list(PURPLE_CONV_TYPE_CHAT
,
4573 PIDGIN_UNSEEN_NICK
, FALSE
, 1);
4575 pidgin_conv_present_conversation((PurpleConversation
*)convs
->data
);
4587 conversation_updated_cb(PurpleConversation
*conv
, PurpleConvUpdateType type
,
4588 PidginBuddyList
*gtkblist
)
4590 GList
*convs
= NULL
;
4594 if (type
!= PURPLE_CONV_UPDATE_UNSEEN
)
4597 if(conv
->account
!= NULL
&& conv
->name
!= NULL
) {
4598 PurpleBuddy
*buddy
= purple_find_buddy(conv
->account
, conv
->name
);
4600 pidgin_blist_update_buddy(NULL
, (PurpleBlistNode
*)buddy
, TRUE
);
4603 if (gtkblist
->menutrayicon
) {
4604 gtk_widget_destroy(gtkblist
->menutrayicon
);
4605 gtkblist
->menutrayicon
= NULL
;
4608 ims
= pidgin_conversations_find_unseen_list(PURPLE_CONV_TYPE_IM
,
4609 PIDGIN_UNSEEN_TEXT
, FALSE
, 0);
4611 chats
= pidgin_conversations_find_unseen_list(PURPLE_CONV_TYPE_CHAT
,
4612 PIDGIN_UNSEEN_NICK
, FALSE
, 0);
4615 convs
= g_list_concat(ims
, chats
);
4616 else if(ims
&& !chats
)
4618 else if(!ims
&& chats
)
4622 GtkWidget
*img
= NULL
;
4623 GString
*tooltip_text
= NULL
;
4625 tooltip_text
= g_string_new("");
4629 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION((PurpleConversation
*)l
->data
);
4632 count
= gtkconv
->unseen_count
;
4633 else if(purple_conversation_get_data(l
->data
, "unseen-count"))
4634 count
= GPOINTER_TO_INT(purple_conversation_get_data(l
->data
, "unseen-count"));
4636 g_string_append_printf(tooltip_text
,
4637 ngettext("%d unread message from %s\n", "%d unread messages from %s\n", count
),
4638 count
, purple_conversation_get_title(l
->data
));
4641 if(tooltip_text
->len
> 0) {
4642 /* get rid of the last newline */
4643 g_string_truncate(tooltip_text
, tooltip_text
->len
-1);
4644 img
= gtk_image_new_from_stock(PIDGIN_STOCK_TOOLBAR_PENDING
,
4645 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
));
4647 gtkblist
->menutrayicon
= gtk_event_box_new();
4648 gtk_container_add(GTK_CONTAINER(gtkblist
->menutrayicon
), img
);
4649 gtk_widget_show(img
);
4650 gtk_widget_show(gtkblist
->menutrayicon
);
4651 g_signal_connect(G_OBJECT(gtkblist
->menutrayicon
), "button-press-event", G_CALLBACK(menutray_press_cb
), NULL
);
4653 pidgin_menu_tray_append(PIDGIN_MENU_TRAY(gtkblist
->menutray
), gtkblist
->menutrayicon
, tooltip_text
->str
);
4655 g_string_free(tooltip_text
, TRUE
);
4661 conversation_deleting_cb(PurpleConversation
*conv
, PidginBuddyList
*gtkblist
)
4663 conversation_updated_cb(conv
, PURPLE_CONV_UPDATE_UNSEEN
, gtkblist
);
4667 conversation_deleted_update_ui_cb(PurpleConversation
*conv
, struct _pidgin_blist_node
*ui
)
4669 if (ui
->conv
.conv
!= conv
)
4671 ui
->conv
.conv
= NULL
;
4673 ui
->conv
.last_message
= 0;
4677 written_msg_update_ui_cb(PurpleAccount
*account
, const char *who
, const char *message
,
4678 PurpleConversation
*conv
, PurpleMessageFlags flag
, PurpleBlistNode
*node
)
4680 PidginBlistNode
*ui
= node
->ui_data
;
4681 if (ui
->conv
.conv
!= conv
|| !pidgin_conv_is_hidden(PIDGIN_CONVERSATION(conv
)) ||
4682 !(flag
& (PURPLE_MESSAGE_SEND
| PURPLE_MESSAGE_RECV
)))
4684 ui
->conv
.flags
|= PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE
;
4685 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
4686 && (flag
& PURPLE_MESSAGE_NICK
))
4687 ui
->conv
.flags
|= PIDGIN_BLIST_CHAT_HAS_PENDING_MESSAGE_WITH_NICK
;
4689 ui
->conv
.last_message
= time(NULL
); /* XXX: for lack of better data */
4690 pidgin_blist_update(purple_get_blist(), node
);
4694 displayed_msg_update_ui_cb(PidginConversation
*gtkconv
, PurpleBlistNode
*node
)
4696 PidginBlistNode
*ui
= node
->ui_data
;
4697 if (ui
->conv
.conv
!= gtkconv
->active_conv
)
4699 ui
->conv
.flags
&= ~(PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE
|
4700 PIDGIN_BLIST_CHAT_HAS_PENDING_MESSAGE_WITH_NICK
);
4701 pidgin_blist_update(purple_get_blist(), node
);
4705 conversation_created_cb(PurpleConversation
*conv
, PidginBuddyList
*gtkblist
)
4707 switch (conv
->type
) {
4708 case PURPLE_CONV_TYPE_IM
:
4710 GSList
*buddies
= purple_find_buddies(conv
->account
, conv
->name
);
4712 PurpleBlistNode
*buddy
= buddies
->data
;
4713 struct _pidgin_blist_node
*ui
= buddy
->ui_data
;
4714 buddies
= g_slist_delete_link(buddies
, buddies
);
4717 ui
->conv
.conv
= conv
;
4719 ui
->conv
.last_message
= 0;
4720 purple_signal_connect(purple_conversations_get_handle(), "deleting-conversation",
4721 ui
, PURPLE_CALLBACK(conversation_deleted_update_ui_cb
), ui
);
4722 purple_signal_connect(purple_conversations_get_handle(), "wrote-im-msg",
4723 ui
, PURPLE_CALLBACK(written_msg_update_ui_cb
), buddy
);
4724 purple_signal_connect(pidgin_conversations_get_handle(), "conversation-displayed",
4725 ui
, PURPLE_CALLBACK(displayed_msg_update_ui_cb
), buddy
);
4729 case PURPLE_CONV_TYPE_CHAT
:
4731 PurpleChat
*chat
= purple_blist_find_chat(conv
->account
, conv
->name
);
4732 struct _pidgin_blist_node
*ui
;
4735 ui
= chat
->node
.ui_data
;
4738 ui
->conv
.conv
= conv
;
4740 ui
->conv
.last_message
= 0;
4741 purple_signal_connect(purple_conversations_get_handle(), "deleting-conversation",
4742 ui
, PURPLE_CALLBACK(conversation_deleted_update_ui_cb
), ui
);
4743 purple_signal_connect(purple_conversations_get_handle(), "wrote-chat-msg",
4744 ui
, PURPLE_CALLBACK(written_msg_update_ui_cb
), chat
);
4745 purple_signal_connect(pidgin_conversations_get_handle(), "conversation-displayed",
4746 ui
, PURPLE_CALLBACK(displayed_msg_update_ui_cb
), chat
);
4754 /**********************************************************************************
4755 * Public API Functions *
4756 **********************************************************************************/
4758 static void pidgin_blist_new_list(PurpleBuddyList
*blist
)
4760 PidginBuddyList
*gtkblist
;
4762 gtkblist
= g_new0(PidginBuddyList
, 1);
4763 gtkblist
->connection_errors
= g_hash_table_new_full(g_direct_hash
,
4764 g_direct_equal
, NULL
, g_free
);
4765 gtkblist
->priv
= g_new0(PidginBuddyListPrivate
, 1);
4767 blist
->ui_data
= gtkblist
;
4770 static void pidgin_blist_new_node(PurpleBlistNode
*node
)
4772 node
->ui_data
= g_new0(struct _pidgin_blist_node
, 1);
4775 gboolean
pidgin_blist_node_is_contact_expanded(PurpleBlistNode
*node
)
4777 if (PURPLE_BLIST_NODE_IS_BUDDY(node
)) {
4778 node
= node
->parent
;
4783 g_return_val_if_fail(PURPLE_BLIST_NODE_IS_CONTACT(node
), FALSE
);
4785 return ((struct _pidgin_blist_node
*)node
->ui_data
)->contact_expanded
;
4798 item_factory_translate_func (const char *path
, gpointer func_data
)
4800 return _((char *)path
);
4803 void pidgin_blist_setup_sort_methods()
4807 pidgin_blist_sort_method_reg("none", _("Manually"), sort_method_none
);
4808 pidgin_blist_sort_method_reg("alphabetical", _("Alphabetically"), sort_method_alphabetical
);
4809 pidgin_blist_sort_method_reg("status", _("By status"), sort_method_status
);
4810 pidgin_blist_sort_method_reg("log_size", _("By recent log activity"), sort_method_log_activity
);
4812 id
= purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/blist/sort_type");
4814 purple_debug_warning("gtkblist", "Sort method was NULL, resetting to alphabetical\n");
4815 id
= "alphabetical";
4817 pidgin_blist_sort_method_set(id
);
4820 static void _prefs_change_redo_list(const char *name
, PurplePrefType type
,
4821 gconstpointer val
, gpointer data
)
4823 GtkTreeSelection
*sel
;
4825 PurpleBlistNode
*node
= NULL
;
4827 sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist
->treeview
));
4828 if (gtk_tree_selection_get_selected(sel
, NULL
, &iter
))
4830 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, NODE_COLUMN
, &node
, -1);
4833 redo_buddy_list(purple_get_blist(), FALSE
, FALSE
);
4834 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(gtkblist
->treeview
));
4838 struct _pidgin_blist_node
*gtknode
;
4841 gtknode
= node
->ui_data
;
4842 if (gtknode
&& gtknode
->row
)
4844 path
= gtk_tree_row_reference_get_path(gtknode
->row
);
4845 gtk_tree_selection_select_path(sel
, path
);
4846 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(gtkblist
->treeview
), path
, NULL
, FALSE
, 0, 0);
4847 gtk_tree_path_free(path
);
4852 static void _prefs_change_sort_method(const char *pref_name
, PurplePrefType type
,
4853 gconstpointer val
, gpointer data
)
4855 if(purple_strequal(pref_name
, PIDGIN_PREFS_ROOT
"/blist/sort_type"))
4856 pidgin_blist_sort_method_set(val
);
4859 static gboolean
pidgin_blist_select_notebook_page_cb(gpointer user_data
)
4861 PidginBuddyList
*gtkblist
= (PidginBuddyList
*)user_data
;
4864 PidginBuddyListPrivate
*priv
;
4866 priv
= PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist
);
4868 priv
->select_page_timeout
= 0;
4870 /* this is far too ugly thanks to me not wanting to fix #3989 properly right now */
4871 if (priv
->error_scrollbook
!= NULL
) {
4872 errors
= gtk_notebook_get_n_pages(GTK_NOTEBOOK(priv
->error_scrollbook
->notebook
));
4874 if ((list
= purple_accounts_get_all_active()) != NULL
|| errors
) {
4875 gtk_notebook_set_current_page(GTK_NOTEBOOK(gtkblist
->notebook
), 1);
4878 gtk_notebook_set_current_page(GTK_NOTEBOOK(gtkblist
->notebook
), 0);
4883 static void pidgin_blist_select_notebook_page(PidginBuddyList
*gtkblist
)
4885 PidginBuddyListPrivate
*priv
;
4887 priv
= PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist
);
4889 priv
->select_page_timeout
= purple_timeout_add(0, pidgin_blist_select_notebook_page_cb
, gtkblist
);
4892 static void account_modified(PurpleAccount
*account
, PidginBuddyList
*gtkblist
)
4897 pidgin_blist_select_notebook_page(gtkblist
);
4898 update_menu_bar(gtkblist
);
4902 account_actions_changed(PurpleAccount
*account
, gpointer data
)
4904 pidgin_blist_update_accounts_menu();
4908 account_status_changed(PurpleAccount
*account
, PurpleStatus
*old
,
4909 PurpleStatus
*new, PidginBuddyList
*gtkblist
)
4914 account_modified(account
, gtkblist
);
4918 gtk_blist_window_key_press_cb(GtkWidget
*w
, GdkEventKey
*event
, PidginBuddyList
*gtkblist
)
4925 /* clear any tooltips */
4926 pidgin_blist_tooltip_destroy();
4928 widget
= gtk_window_get_focus(GTK_WINDOW(gtkblist
->window
));
4930 if (GTK_IS_IMHTML(widget
) || GTK_IS_ENTRY(widget
)) {
4931 if (gtk_bindings_activate(GTK_OBJECT(widget
), event
->keyval
, event
->state
))
4938 headline_box_enter_cb(GtkWidget
*widget
, GdkEventCrossing
*event
, PidginBuddyList
*gtkblist
)
4940 gdk_window_set_cursor(widget
->window
, gtkblist
->hand_cursor
);
4945 headline_box_leave_cb(GtkWidget
*widget
, GdkEventCrossing
*event
, PidginBuddyList
*gtkblist
)
4947 gdk_window_set_cursor(widget
->window
, gtkblist
->arrow_cursor
);
4952 reset_headline(PidginBuddyList
*gtkblist
)
4954 gtkblist
->headline_callback
= NULL
;
4955 gtkblist
->headline_data
= NULL
;
4956 gtkblist
->headline_destroy
= NULL
;
4957 pidgin_set_urgent(GTK_WINDOW(gtkblist
->window
), FALSE
);
4961 headline_click_callback(gpointer unused
)
4963 if (gtkblist
->headline_callback
)
4964 ((GSourceFunc
) gtkblist
->headline_callback
)(gtkblist
->headline_data
);
4965 reset_headline(gtkblist
);
4970 headline_close_press_cb(GtkButton
*button
, PidginBuddyList
*gtkblist
)
4972 gtk_widget_hide(gtkblist
->headline_hbox
);
4977 headline_box_press_cb(GtkWidget
*widget
, GdkEventButton
*event
, PidginBuddyList
*gtkblist
)
4979 gtk_widget_hide(gtkblist
->headline_hbox
);
4980 if (gtkblist
->headline_callback
)
4981 g_idle_add(headline_click_callback
, NULL
);
4983 if (gtkblist
->headline_destroy
)
4984 gtkblist
->headline_destroy(gtkblist
->headline_data
);
4985 reset_headline(gtkblist
);
4990 /***********************************/
4991 /* Connection error handling stuff */
4992 /***********************************/
4994 #define OBJECT_DATA_KEY_ACCOUNT "account"
4995 #define DO_NOT_CLEAR_ERROR "do-not-clear-error"
4998 find_account_widget(GObject
*widget
,
4999 PurpleAccount
*account
)
5001 if (g_object_get_data(widget
, OBJECT_DATA_KEY_ACCOUNT
) == account
)
5002 return 0; /* found */
5008 pack_prpl_icon_start(GtkWidget
*box
,
5009 PurpleAccount
*account
)
5014 pixbuf
= pidgin_create_prpl_icon(account
, PIDGIN_PRPL_ICON_SMALL
);
5015 if (pixbuf
!= NULL
) {
5016 image
= gtk_image_new_from_pixbuf(pixbuf
);
5017 g_object_unref(pixbuf
);
5019 gtk_box_pack_start(GTK_BOX(box
), image
, FALSE
, FALSE
, 0);
5024 add_error_dialog(PidginBuddyList
*gtkblist
,
5027 PidginBuddyListPrivate
*priv
= PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist
);
5028 gtk_container_add(GTK_CONTAINER(priv
->error_scrollbook
), dialog
);
5032 find_child_widget_by_account(GtkContainer
*container
,
5033 PurpleAccount
*account
)
5036 GList
*children
= NULL
;
5037 GtkWidget
*ret
= NULL
;
5038 /* XXX: Workaround for the currently incomplete implementation of PidginScrollBook */
5039 if (PIDGIN_IS_SCROLL_BOOK(container
))
5040 container
= GTK_CONTAINER(PIDGIN_SCROLL_BOOK(container
)->notebook
);
5041 children
= gtk_container_get_children(container
);
5042 l
= g_list_find_custom(children
, account
, (GCompareFunc
) find_account_widget
);
5044 ret
= GTK_WIDGET(l
->data
);
5045 g_list_free(children
);
5050 remove_child_widget_by_account(GtkContainer
*container
,
5051 PurpleAccount
*account
)
5053 GtkWidget
*widget
= find_child_widget_by_account(container
, account
);
5055 /* Since we are destroying the widget in response to a change in
5056 * error, we should not clear the error.
5058 g_object_set_data(G_OBJECT(widget
), DO_NOT_CLEAR_ERROR
,
5059 GINT_TO_POINTER(TRUE
));
5060 gtk_widget_destroy(widget
);
5064 /* Generic error buttons */
5067 generic_error_modify_cb(PurpleAccount
*account
)
5069 purple_account_clear_current_error(account
);
5070 pidgin_account_dialog_show(PIDGIN_MODIFY_ACCOUNT_DIALOG
, account
);
5074 generic_error_enable_cb(PurpleAccount
*account
)
5076 purple_account_clear_current_error(account
);
5077 purple_account_set_enabled(account
, purple_core_get_ui(), TRUE
);
5081 generic_error_destroy_cb(GtkObject
*dialog
,
5082 PurpleAccount
*account
)
5084 g_hash_table_remove(gtkblist
->connection_errors
, account
);
5085 /* If the error dialog is being destroyed in response to the
5086 * account-error-changed signal, we don't want to clear the current
5089 if (g_object_get_data(G_OBJECT(dialog
), DO_NOT_CLEAR_ERROR
) == NULL
)
5090 purple_account_clear_current_error(account
);
5093 #define SSL_FAQ_URI "https://developer.pidgin.im/wiki/FAQssl"
5096 ssl_faq_clicked_cb(PidginMiniDialog
*mini_dialog
,
5100 purple_notify_uri(NULL
, SSL_FAQ_URI
);
5104 add_generic_error_dialog(PurpleAccount
*account
,
5105 const PurpleConnectionErrorInfo
*err
)
5107 GtkWidget
*mini_dialog
;
5108 const char *username
= purple_account_get_username(account
);
5110 purple_account_get_enabled(account
, purple_core_get_ui());
5114 primary
= g_strdup_printf(_("%s disconnected"), username
);
5116 primary
= g_strdup_printf(_("%s disabled"), username
);
5118 mini_dialog
= pidgin_make_mini_dialog(NULL
, PIDGIN_STOCK_DIALOG_ERROR
,
5119 primary
, err
->description
, account
,
5120 (enabled
? _("Reconnect") : _("Re-enable")),
5121 (enabled
? PURPLE_CALLBACK(purple_account_connect
)
5122 : PURPLE_CALLBACK(generic_error_enable_cb
)),
5123 _("Modify Account"), PURPLE_CALLBACK(generic_error_modify_cb
),
5128 g_object_set_data(G_OBJECT(mini_dialog
), OBJECT_DATA_KEY_ACCOUNT
,
5131 if(err
->type
== PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT
)
5132 pidgin_mini_dialog_add_non_closing_button(PIDGIN_MINI_DIALOG(mini_dialog
),
5133 _("SSL FAQs"), ssl_faq_clicked_cb
, NULL
);
5135 g_signal_connect_after(mini_dialog
, "destroy",
5136 (GCallback
)generic_error_destroy_cb
,
5139 add_error_dialog(gtkblist
, mini_dialog
);
5143 remove_generic_error_dialog(PurpleAccount
*account
)
5145 PidginBuddyListPrivate
*priv
= PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist
);
5146 remove_child_widget_by_account(
5147 GTK_CONTAINER(priv
->error_scrollbook
), account
);
5152 update_generic_error_message(PurpleAccount
*account
,
5153 const char *description
)
5155 PidginBuddyListPrivate
*priv
= PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist
);
5156 GtkWidget
*mini_dialog
= find_child_widget_by_account(
5157 GTK_CONTAINER(priv
->error_scrollbook
), account
);
5158 pidgin_mini_dialog_set_description(PIDGIN_MINI_DIALOG(mini_dialog
),
5163 /* Notifications about accounts which were disconnected with
5164 * PURPLE_CONNECTION_ERROR_NAME_IN_USE
5167 typedef void (*AccountFunction
)(PurpleAccount
*);
5170 elsewhere_foreach_account(PidginMiniDialog
*mini_dialog
,
5173 PurpleAccount
*account
;
5174 GList
*labels
= gtk_container_get_children(
5175 GTK_CONTAINER(mini_dialog
->contents
));
5178 for (l
= labels
; l
; l
= l
->next
) {
5179 account
= g_object_get_data(G_OBJECT(l
->data
), OBJECT_DATA_KEY_ACCOUNT
);
5183 purple_debug_warning("gtkblist", "mini_dialog's child "
5184 "didn't have an account stored in it!");
5186 g_list_free(labels
);
5190 enable_account(PurpleAccount
*account
)
5192 purple_account_set_enabled(account
, purple_core_get_ui(), TRUE
);
5196 reconnect_elsewhere_accounts(PidginMiniDialog
*mini_dialog
,
5200 elsewhere_foreach_account(mini_dialog
, enable_account
);
5204 clear_elsewhere_errors(PidginMiniDialog
*mini_dialog
,
5207 elsewhere_foreach_account(mini_dialog
, purple_account_clear_current_error
);
5211 ensure_signed_on_elsewhere_minidialog(PidginBuddyList
*gtkblist
)
5213 PidginBuddyListPrivate
*priv
= PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist
);
5214 PidginMiniDialog
*mini_dialog
;
5216 if(priv
->signed_on_elsewhere
)
5219 mini_dialog
= priv
->signed_on_elsewhere
=
5220 pidgin_mini_dialog_new(_("Welcome back!"), NULL
, PIDGIN_STOCK_DISCONNECT
);
5222 pidgin_mini_dialog_add_button(mini_dialog
, _("Re-enable"),
5223 reconnect_elsewhere_accounts
, NULL
);
5225 /* Make dismissing the dialog clear the errors. The "destroy" signal
5226 * does not appear to fire at quit, which is fortunate!
5228 g_signal_connect(G_OBJECT(mini_dialog
), "destroy",
5229 (GCallback
) clear_elsewhere_errors
, NULL
);
5231 add_error_dialog(gtkblist
, GTK_WIDGET(mini_dialog
));
5233 /* Set priv->signed_on_elsewhere to NULL when the dialog is destroyed */
5234 g_signal_connect(G_OBJECT(mini_dialog
), "destroy",
5235 (GCallback
) gtk_widget_destroyed
, &(priv
->signed_on_elsewhere
));
5239 update_signed_on_elsewhere_minidialog_title(void)
5241 PidginBuddyListPrivate
*priv
= PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist
);
5242 PidginMiniDialog
*mini_dialog
= priv
->signed_on_elsewhere
;
5246 if (mini_dialog
== NULL
)
5249 accounts
= pidgin_mini_dialog_get_num_children(mini_dialog
);
5250 if (accounts
== 0) {
5251 gtk_widget_destroy(GTK_WIDGET(mini_dialog
));
5255 title
= g_strdup_printf(
5256 ngettext("%d account was disabled because you signed on from another location:",
5257 "%d accounts were disabled because you signed on from another location:",
5260 pidgin_mini_dialog_set_description(mini_dialog
, title
);
5265 create_account_label(PurpleAccount
*account
)
5267 GtkWidget
*hbox
, *label
;
5268 const char *username
= purple_account_get_username(account
);
5271 hbox
= gtk_hbox_new(FALSE
, 6);
5272 g_object_set_data(G_OBJECT(hbox
), OBJECT_DATA_KEY_ACCOUNT
, account
);
5274 pack_prpl_icon_start(hbox
, account
);
5276 label
= gtk_label_new(NULL
);
5277 markup
= g_strdup_printf("<span size=\"smaller\">%s</span>", username
);
5278 gtk_label_set_markup(GTK_LABEL(label
), markup
);
5280 gtk_misc_set_alignment(GTK_MISC(label
), 0, 0);
5281 g_object_set(G_OBJECT(label
), "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
5282 #if GTK_CHECK_VERSION(2,12,0)
5283 { /* avoid unused variable warnings on pre-2.12 Gtk */
5285 purple_account_get_current_error(account
)->description
;
5286 if (description
!= NULL
&& *description
!= '\0')
5287 gtk_widget_set_tooltip_text(label
, description
);
5290 gtk_box_pack_start(GTK_BOX(hbox
), label
, TRUE
, TRUE
, 0);
5296 add_to_signed_on_elsewhere(PurpleAccount
*account
)
5298 PidginBuddyListPrivate
*priv
= PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist
);
5299 PidginMiniDialog
*mini_dialog
;
5300 GtkWidget
*account_label
;
5302 ensure_signed_on_elsewhere_minidialog(gtkblist
);
5303 mini_dialog
= priv
->signed_on_elsewhere
;
5305 if(find_child_widget_by_account(GTK_CONTAINER(mini_dialog
->contents
), account
))
5308 account_label
= create_account_label(account
);
5309 gtk_box_pack_start(mini_dialog
->contents
, account_label
, FALSE
, FALSE
, 0);
5310 gtk_widget_show_all(account_label
);
5312 update_signed_on_elsewhere_minidialog_title();
5316 remove_from_signed_on_elsewhere(PurpleAccount
*account
)
5318 PidginBuddyListPrivate
*priv
= PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist
);
5319 PidginMiniDialog
*mini_dialog
= priv
->signed_on_elsewhere
;
5320 if(mini_dialog
== NULL
)
5323 remove_child_widget_by_account(GTK_CONTAINER(mini_dialog
->contents
), account
);
5325 update_signed_on_elsewhere_minidialog_title();
5330 update_signed_on_elsewhere_tooltip(PurpleAccount
*account
,
5331 const char *description
)
5333 #if GTK_CHECK_VERSION(2,12,0)
5334 PidginBuddyListPrivate
*priv
= PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist
);
5335 GtkContainer
*c
= GTK_CONTAINER(priv
->signed_on_elsewhere
->contents
);
5336 GtkWidget
*label
= find_child_widget_by_account(c
, account
);
5337 gtk_widget_set_tooltip_text(label
, description
);
5342 /* Call appropriate error notification code based on error types */
5344 update_account_error_state(PurpleAccount
*account
,
5345 const PurpleConnectionErrorInfo
*old
,
5346 const PurpleConnectionErrorInfo
*new,
5347 PidginBuddyList
*gtkblist
)
5349 gboolean descriptions_differ
;
5352 if (old
== NULL
&& new == NULL
)
5355 /* For backwards compatibility: */
5357 pidgin_blist_update_account_error_state(account
, new->description
);
5359 pidgin_blist_update_account_error_state(account
, NULL
);
5362 pidgin_blist_select_notebook_page(gtkblist
);
5364 if (old
!= NULL
&& new == NULL
) {
5365 if(old
->type
== PURPLE_CONNECTION_ERROR_NAME_IN_USE
)
5366 remove_from_signed_on_elsewhere(account
);
5368 remove_generic_error_dialog(account
);
5372 if (old
== NULL
&& new != NULL
) {
5373 if(new->type
== PURPLE_CONNECTION_ERROR_NAME_IN_USE
)
5374 add_to_signed_on_elsewhere(account
);
5376 add_generic_error_dialog(account
, new);
5380 /* else, new and old are both non-NULL */
5382 descriptions_differ
= !purple_strequal(old
->description
, new->description
);
5383 desc
= new->description
;
5385 switch (new->type
) {
5386 case PURPLE_CONNECTION_ERROR_NAME_IN_USE
:
5387 if (old
->type
== PURPLE_CONNECTION_ERROR_NAME_IN_USE
5388 && descriptions_differ
) {
5389 update_signed_on_elsewhere_tooltip(account
, desc
);
5391 remove_generic_error_dialog(account
);
5392 add_to_signed_on_elsewhere(account
);
5396 if (old
->type
== PURPLE_CONNECTION_ERROR_NAME_IN_USE
) {
5397 remove_from_signed_on_elsewhere(account
);
5398 add_generic_error_dialog(account
, new);
5399 } else if (descriptions_differ
) {
5400 update_generic_error_message(account
, desc
);
5406 /* In case accounts are loaded before the blist (which they currently are),
5407 * let's call update_account_error_state ourselves on every account's current
5408 * state when the blist starts.
5411 show_initial_account_errors(PidginBuddyList
*gtkblist
)
5413 GList
*l
= purple_accounts_get_all();
5414 PurpleAccount
*account
;
5415 const PurpleConnectionErrorInfo
*err
;
5417 for (; l
; l
= l
->next
)
5420 err
= purple_account_get_current_error(account
);
5422 update_account_error_state(account
, NULL
, err
, gtkblist
);
5427 pidgin_blist_update_account_error_state(PurpleAccount
*account
, const char *text
)
5429 /* connection_errors isn't actually used anywhere; it's just kept in
5430 * sync with reality in case a plugin uses it.
5433 g_hash_table_remove(gtkblist
->connection_errors
, account
);
5435 g_hash_table_insert(gtkblist
->connection_errors
, account
, g_strdup(text
));
5439 paint_headline_hbox (GtkWidget
*widget
,
5440 GdkEventExpose
*event
,
5443 gtk_paint_flat_box (widget
->style
,
5450 widget
->allocation
.x
+ 1,
5451 widget
->allocation
.y
+ 1,
5452 widget
->allocation
.width
- 2,
5453 widget
->allocation
.height
- 2);
5457 /* This assumes there are not things like groupless buddies or multi-leveled groups.
5458 * I'm sure other things in this code assumes that also.
5461 treeview_style_set (GtkWidget
*widget
,
5462 GtkStyle
*prev_style
,
5465 PurpleBuddyList
*list
= data
;
5466 PurpleBlistNode
*node
= list
->root
;
5468 pidgin_blist_update_group(list
, node
);
5474 headline_style_set (GtkWidget
*widget
,
5475 GtkStyle
*prev_style
)
5477 GtkTooltips
*tooltips
;
5480 if (gtkblist
->changing_style
)
5483 tooltips
= gtk_tooltips_new ();
5484 g_object_ref_sink (tooltips
);
5486 gtk_tooltips_force_window (tooltips
);
5487 #if GTK_CHECK_VERSION(2, 12, 0)
5488 gtk_widget_set_name (tooltips
->tip_window
, "gtk-tooltips");
5490 gtk_widget_ensure_style (tooltips
->tip_window
);
5491 style
= gtk_widget_get_style (tooltips
->tip_window
);
5493 gtkblist
->changing_style
= TRUE
;
5494 gtk_widget_set_style (gtkblist
->headline_hbox
, style
);
5495 gtkblist
->changing_style
= FALSE
;
5497 g_object_unref (tooltips
);
5500 /******************************************/
5501 /* End of connection error handling stuff */
5502 /******************************************/
5505 blist_focus_cb(GtkWidget
*widget
, GdkEventFocus
*event
, PidginBuddyList
*gtkblist
)
5508 gtk_blist_focused
= TRUE
;
5509 pidgin_set_urgent(GTK_WINDOW(gtkblist
->window
), FALSE
);
5511 gtk_blist_focused
= FALSE
;
5520 GtkWidget
*ret
= gtk_vbox_new(FALSE
, PIDGIN_HIG_BOX_SPACE
);
5526 label
= gtk_label_new(NULL
);
5527 gtk_box_pack_start(GTK_BOX(ret
), label
, TRUE
, TRUE
, 0);
5529 label
= gtk_label_new(NULL
);
5530 gtk_label_set_markup(GTK_LABEL(label
), _("<b>Username:</b>"));
5531 gtk_misc_set_alignment(GTK_MISC(label
), 0.0, 0.5);
5532 gtk_box_pack_start(GTK_BOX(ret
), label
, FALSE
, FALSE
, 0);
5533 entry
= gtk_entry_new();
5534 gtk_box_pack_start(GTK_BOX(ret
), entry
, FALSE
, FALSE
, 0);
5536 label
= gtk_label_new(NULL
);
5537 gtk_label_set_markup(GTK_LABEL(label
), _("<b>Password:</b>"));
5538 gtk_misc_set_alignment(GTK_MISC(label
), 0.0, 0.5);
5539 gtk_box_pack_start(GTK_BOX(ret
), label
, FALSE
, FALSE
, 0);
5540 entry
= gtk_entry_new();
5541 gtk_entry_set_visibility(GTK_ENTRY(entry
), FALSE
);
5542 gtk_box_pack_start(GTK_BOX(ret
), entry
, FALSE
, FALSE
, 0);
5544 label
= gtk_label_new(" ");
5545 gtk_box_pack_start(GTK_BOX(ret
), label
, FALSE
, FALSE
, 0);
5547 bbox
= gtk_hbutton_box_new();
5548 button
= gtk_button_new_with_mnemonic(_("_Login"));
5549 gtk_box_pack_start(GTK_BOX(ret
), bbox
, FALSE
, FALSE
, 0);
5550 gtk_container_add(GTK_CONTAINER(bbox
), button
);
5553 label
= gtk_label_new(NULL
);
5554 gtk_box_pack_start(GTK_BOX(ret
), label
, TRUE
, TRUE
, 0);
5556 gtk_container_set_border_width(GTK_CONTAINER(ret
), PIDGIN_HIG_BORDER
);
5558 gtk_widget_show_all(ret
);
5563 /* builds the blist layout according to to the current theme */
5565 pidgin_blist_build_layout(PurpleBuddyList
*list
)
5567 GtkTreeViewColumn
*column
;
5568 PidginBlistLayout
*layout
;
5569 PidginBlistTheme
*theme
;
5570 GtkCellRenderer
*rend
;
5571 gint i
, status_icon
= 0, text
= 1, emblem
= 2, protocol_icon
= 3, buddy_icon
= 4;
5574 column
= gtkblist
->text_column
;
5576 if ((theme
= pidgin_blist_get_theme()) != NULL
&& (layout
= pidgin_blist_theme_get_layout(theme
)) != NULL
) {
5577 status_icon
= layout
->status_icon
;
5578 text
= layout
->text
;
5579 emblem
= layout
->emblem
;
5580 protocol_icon
= layout
->protocol_icon
;
5581 buddy_icon
= layout
->buddy_icon
;
5584 gtk_tree_view_column_clear(column
);
5587 rend
= pidgin_cell_renderer_expander_new();
5588 gtk_tree_view_column_pack_start(column
, rend
, FALSE
);
5589 gtk_tree_view_column_set_attributes(column
, rend
,
5590 "visible", GROUP_EXPANDER_VISIBLE_COLUMN
,
5591 "expander-visible", GROUP_EXPANDER_COLUMN
,
5592 "sensitive", GROUP_EXPANDER_COLUMN
,
5593 "cell-background-gdk", BGCOLOR_COLUMN
,
5597 rend
= pidgin_cell_renderer_expander_new();
5598 gtk_tree_view_column_pack_start(column
, rend
, FALSE
);
5599 gtk_tree_view_column_set_attributes(column
, rend
,
5600 "visible", CONTACT_EXPANDER_VISIBLE_COLUMN
,
5601 "expander-visible", CONTACT_EXPANDER_COLUMN
,
5602 "sensitive", CONTACT_EXPANDER_COLUMN
,
5603 "cell-background-gdk", BGCOLOR_COLUMN
,
5606 for (i
= 0; i
< 5; i
++) {
5608 if (status_icon
== i
) {
5610 rend
= gtk_cell_renderer_pixbuf_new();
5611 gtk_tree_view_column_pack_start(column
, rend
, FALSE
);
5612 gtk_tree_view_column_set_attributes(column
, rend
,
5613 "pixbuf", STATUS_ICON_COLUMN
,
5614 "visible", STATUS_ICON_VISIBLE_COLUMN
,
5615 "cell-background-gdk", BGCOLOR_COLUMN
,
5617 g_object_set(rend
, "xalign", 0.0, "xpad", 6, "ypad", 0, NULL
);
5619 } else if (text
== i
) {
5621 gtkblist
->text_rend
= rend
= gtk_cell_renderer_text_new();
5622 gtk_tree_view_column_pack_start(column
, rend
, TRUE
);
5623 gtk_tree_view_column_set_attributes(column
, rend
,
5624 "cell-background-gdk", BGCOLOR_COLUMN
,
5625 "markup", NAME_COLUMN
,
5627 g_signal_connect(G_OBJECT(rend
), "editing-started", G_CALLBACK(gtk_blist_renderer_editing_started_cb
), NULL
);
5628 g_signal_connect(G_OBJECT(rend
), "editing-canceled", G_CALLBACK(gtk_blist_renderer_editing_cancelled_cb
), list
);
5629 g_signal_connect(G_OBJECT(rend
), "edited", G_CALLBACK(gtk_blist_renderer_edited_cb
), list
);
5630 g_object_set(rend
, "ypad", 0, "yalign", 0.5, NULL
);
5631 g_object_set(rend
, "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
5634 rend
= gtk_cell_renderer_text_new();
5635 g_object_set(rend
, "xalign", 1.0, "ypad", 0, NULL
);
5636 gtk_tree_view_column_pack_start(column
, rend
, FALSE
);
5637 gtk_tree_view_column_set_attributes(column
, rend
,
5638 "markup", IDLE_COLUMN
,
5639 "visible", IDLE_VISIBLE_COLUMN
,
5640 "cell-background-gdk", BGCOLOR_COLUMN
,
5642 } else if (emblem
== i
) {
5644 rend
= gtk_cell_renderer_pixbuf_new();
5645 g_object_set(rend
, "xalign", 1.0, "yalign", 0.5, "ypad", 0, "xpad", 3, NULL
);
5646 gtk_tree_view_column_pack_start(column
, rend
, FALSE
);
5647 gtk_tree_view_column_set_attributes(column
, rend
, "pixbuf", EMBLEM_COLUMN
,
5648 "cell-background-gdk", BGCOLOR_COLUMN
,
5649 "visible", EMBLEM_VISIBLE_COLUMN
, NULL
);
5651 } else if (protocol_icon
== i
) {
5653 rend
= gtk_cell_renderer_pixbuf_new();
5654 gtk_tree_view_column_pack_start(column
, rend
, FALSE
);
5655 gtk_tree_view_column_set_attributes(column
, rend
,
5656 "pixbuf", PROTOCOL_ICON_COLUMN
,
5657 "visible", PROTOCOL_ICON_VISIBLE_COLUMN
,
5658 "cell-background-gdk", BGCOLOR_COLUMN
,
5660 g_object_set(rend
, "xalign", 0.0, "xpad", 3, "ypad", 0, NULL
);
5662 } else if (buddy_icon
== i
) {
5664 rend
= gtk_cell_renderer_pixbuf_new();
5665 g_object_set(rend
, "xalign", 1.0, "ypad", 0, NULL
);
5666 gtk_tree_view_column_pack_start(column
, rend
, FALSE
);
5667 gtk_tree_view_column_set_attributes(column
, rend
, "pixbuf", BUDDY_ICON_COLUMN
,
5668 "cell-background-gdk", BGCOLOR_COLUMN
,
5669 "visible", BUDDY_ICON_VISIBLE_COLUMN
,
5678 pidgin_blist_search_equal_func(GtkTreeModel
*model
, gint column
,
5679 const gchar
*key
, GtkTreeIter
*iter
, gpointer data
)
5681 PurpleBlistNode
*node
= NULL
;
5682 gboolean res
= TRUE
;
5683 const char *compare
= NULL
;
5685 if (!pidgin_tree_view_search_equal_func(model
, column
, key
, iter
, data
))
5688 /* If the search string does not match the displayed label, then look
5689 * at the alternate labels for the nodes and search in them. Currently,
5690 * alternate labels that make sense are usernames/email addresses for
5691 * buddies (but only for the ones who don't have a local alias).
5694 gtk_tree_model_get(model
, iter
, NODE_COLUMN
, &node
, -1);
5699 if (PURPLE_BLIST_NODE_IS_CONTACT(node
)) {
5700 PurpleBuddy
*b
= purple_contact_get_priority_buddy(PURPLE_CONTACT(node
));
5701 if (!purple_buddy_get_local_buddy_alias(b
))
5702 compare
= purple_buddy_get_name(b
);
5703 } else if (PURPLE_BLIST_NODE_IS_BUDDY(node
)) {
5704 if (!purple_buddy_get_local_buddy_alias(PURPLE_BUDDY(node
)))
5705 compare
= purple_buddy_get_name(PURPLE_BUDDY(node
));
5709 char *tmp
, *enteredstring
;
5710 tmp
= g_utf8_normalize(key
, -1, G_NORMALIZE_DEFAULT
);
5711 enteredstring
= g_utf8_casefold(tmp
, -1);
5714 if (purple_str_has_prefix(compare
, enteredstring
))
5716 g_free(enteredstring
);
5722 static void pidgin_blist_show(PurpleBuddyList
*list
)
5724 PidginBuddyListPrivate
*priv
;
5726 GtkTreeViewColumn
*column
;
5733 const char *theme_name
;
5734 GtkAccelGroup
*accel_group
;
5735 GtkTreeSelection
*selection
;
5736 GtkTargetEntry dte
[] = {{"PURPLE_BLIST_NODE", GTK_TARGET_SAME_APP
, DRAG_ROW
},
5737 {"application/x-im-contact", 0, DRAG_BUDDY
},
5738 {"text/x-vcard", 0, DRAG_VCARD
},
5739 {"text/uri-list", 0, DRAG_URI
},
5740 {"text/plain", 0, DRAG_TEXT
}};
5741 GtkTargetEntry ste
[] = {{"PURPLE_BLIST_NODE", GTK_TARGET_SAME_APP
, DRAG_ROW
},
5742 {"application/x-im-contact", 0, DRAG_BUDDY
},
5743 {"text/x-vcard", 0, DRAG_VCARD
}};
5744 if (gtkblist
&& gtkblist
->window
) {
5745 purple_blist_set_visible(purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/list_visible"));
5749 gtkblist
= PIDGIN_BLIST(list
);
5750 priv
= PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist
);
5752 if (priv
->current_theme
)
5753 g_object_unref(priv
->current_theme
);
5755 theme_name
= purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/blist/theme");
5756 if (theme_name
&& *theme_name
)
5757 priv
->current_theme
= g_object_ref(PIDGIN_BLIST_THEME(purple_theme_manager_find_theme(theme_name
, "blist")));
5759 priv
->current_theme
= NULL
;
5761 gtkblist
->empty_avatar
= gdk_pixbuf_new(GDK_COLORSPACE_RGB
, TRUE
, 8, 32, 32);
5762 gdk_pixbuf_fill(gtkblist
->empty_avatar
, 0x00000000);
5764 gtkblist
->window
= pidgin_create_window(_("Buddy List"), 0, "buddy_list", TRUE
);
5765 g_signal_connect(G_OBJECT(gtkblist
->window
), "focus-in-event",
5766 G_CALLBACK(blist_focus_cb
), gtkblist
);
5767 g_signal_connect(G_OBJECT(gtkblist
->window
), "focus-out-event",
5768 G_CALLBACK(blist_focus_cb
), gtkblist
);
5769 GTK_WINDOW(gtkblist
->window
)->allow_shrink
= TRUE
;
5771 gtkblist
->main_vbox
= gtk_vbox_new(FALSE
, 0);
5772 gtk_widget_show(gtkblist
->main_vbox
);
5773 gtk_container_add(GTK_CONTAINER(gtkblist
->window
), gtkblist
->main_vbox
);
5775 g_signal_connect(G_OBJECT(gtkblist
->window
), "delete_event", G_CALLBACK(gtk_blist_delete_cb
), NULL
);
5776 g_signal_connect(G_OBJECT(gtkblist
->window
), "configure_event", G_CALLBACK(gtk_blist_configure_cb
), NULL
);
5777 g_signal_connect(G_OBJECT(gtkblist
->window
), "visibility_notify_event", G_CALLBACK(gtk_blist_visibility_cb
), NULL
);
5778 g_signal_connect(G_OBJECT(gtkblist
->window
), "window_state_event", G_CALLBACK(gtk_blist_window_state_cb
), NULL
);
5779 g_signal_connect(G_OBJECT(gtkblist
->window
), "key_press_event", G_CALLBACK(gtk_blist_window_key_press_cb
), gtkblist
);
5780 gtk_widget_add_events(gtkblist
->window
, GDK_VISIBILITY_NOTIFY_MASK
);
5782 /******************************* Menu bar *************************************/
5783 accel_group
= gtk_accel_group_new();
5784 gtk_window_add_accel_group(GTK_WINDOW (gtkblist
->window
), accel_group
);
5785 g_object_unref(accel_group
);
5786 gtkblist
->ift
= gtk_item_factory_new(GTK_TYPE_MENU_BAR
, "<PurpleMain>", accel_group
);
5787 gtk_item_factory_set_translate_func(gtkblist
->ift
,
5788 (GtkTranslateFunc
)item_factory_translate_func
,
5790 gtk_item_factory_create_items(gtkblist
->ift
, sizeof(blist_menu
) / sizeof(*blist_menu
),
5792 pidgin_load_accels();
5793 g_signal_connect(G_OBJECT(accel_group
), "accel-changed", G_CALLBACK(pidgin_save_accels_cb
), NULL
);
5795 menu
= gtk_item_factory_get_widget(gtkblist
->ift
, "<PurpleMain>");
5796 gtkblist
->menutray
= pidgin_menu_tray_new();
5797 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), gtkblist
->menutray
);
5798 gtk_widget_show(gtkblist
->menutray
);
5799 gtk_widget_show(menu
);
5800 gtk_box_pack_start(GTK_BOX(gtkblist
->main_vbox
), menu
, FALSE
, FALSE
, 0);
5802 accountmenu
= gtk_item_factory_get_widget(gtkblist
->ift
, N_("/Accounts"));
5805 /****************************** Notebook *************************************/
5806 gtkblist
->notebook
= gtk_notebook_new();
5807 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(gtkblist
->notebook
), FALSE
);
5808 gtk_notebook_set_show_border(GTK_NOTEBOOK(gtkblist
->notebook
), FALSE
);
5809 gtk_box_pack_start(GTK_BOX(gtkblist
->main_vbox
), gtkblist
->notebook
, TRUE
, TRUE
, 0);
5812 gtk_notebook_append_page(GTK_NOTEBOOK(gtkblist
->notebook
), kiosk_page(), NULL
);
5815 /* Translators: Please maintain the use of -> and <- to refer to menu heirarchy */
5816 tmp
= g_strdup_printf(_("<span weight='bold' size='larger'>Welcome to %s!</span>\n\n"
5818 "You have no accounts enabled. Enable your IM accounts from the "
5819 "<b>Accounts</b> window at <b>Accounts->Manage Accounts</b>. Once you "
5820 "enable accounts, you'll be able to sign on, set your status, "
5821 "and talk to your friends."), PIDGIN_NAME
);
5822 pretty
= pidgin_make_pretty_arrows(tmp
);
5824 label
= gtk_label_new(NULL
);
5825 gtk_widget_set_size_request(label
, purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/blist/width") - 12, -1);
5826 gtk_label_set_line_wrap(GTK_LABEL(label
), TRUE
);
5827 gtk_misc_set_alignment(GTK_MISC(label
), 0.5, 0.2);
5828 gtk_label_set_markup(GTK_LABEL(label
), pretty
);
5830 gtk_notebook_append_page(GTK_NOTEBOOK(gtkblist
->notebook
),label
, NULL
);
5831 gtkblist
->vbox
= gtk_vbox_new(FALSE
, 0);
5832 gtk_notebook_append_page(GTK_NOTEBOOK(gtkblist
->notebook
), gtkblist
->vbox
, NULL
);
5833 gtk_widget_show_all(gtkblist
->notebook
);
5834 pidgin_blist_select_notebook_page(gtkblist
);
5836 ebox
= gtk_event_box_new();
5837 gtk_box_pack_start(GTK_BOX(gtkblist
->vbox
), ebox
, FALSE
, FALSE
, 0);
5838 gtkblist
->headline_hbox
= gtk_hbox_new(FALSE
, 3);
5839 gtk_container_set_border_width(GTK_CONTAINER(gtkblist
->headline_hbox
), 6);
5840 gtk_container_add(GTK_CONTAINER(ebox
), gtkblist
->headline_hbox
);
5841 gtkblist
->headline_image
= gtk_image_new_from_pixbuf(NULL
);
5842 gtk_misc_set_alignment(GTK_MISC(gtkblist
->headline_image
), 0.0, 0);
5843 gtkblist
->headline_label
= gtk_label_new(NULL
);
5844 gtk_widget_set_size_request(gtkblist
->headline_label
,
5845 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/blist/width")-25,-1);
5846 gtk_label_set_line_wrap(GTK_LABEL(gtkblist
->headline_label
), TRUE
);
5847 gtk_box_pack_start(GTK_BOX(gtkblist
->headline_hbox
), gtkblist
->headline_image
, FALSE
, FALSE
, 0);
5848 gtk_box_pack_start(GTK_BOX(gtkblist
->headline_hbox
), gtkblist
->headline_label
, TRUE
, TRUE
, 0);
5849 g_signal_connect(gtkblist
->headline_label
, /* connecting on headline_hbox doesn't work, because
5850 the signal is not emitted when theme is changed */
5852 G_CALLBACK(headline_style_set
),
5854 g_signal_connect (gtkblist
->headline_hbox
,
5856 G_CALLBACK (paint_headline_hbox
),
5858 gtk_widget_set_name(gtkblist
->headline_hbox
, "gtk-tooltips");
5860 gtkblist
->headline_close
= gtk_widget_render_icon(ebox
, GTK_STOCK_CLOSE
,
5861 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC
), NULL
);
5862 gtkblist
->hand_cursor
= gdk_cursor_new (GDK_HAND2
);
5863 gtkblist
->arrow_cursor
= gdk_cursor_new (GDK_LEFT_PTR
);
5866 close
= gtk_image_new_from_stock(GTK_STOCK_CLOSE
, GTK_ICON_SIZE_MENU
);
5867 close
= pidgin_create_small_button(close
);
5868 gtk_box_pack_start(GTK_BOX(gtkblist
->headline_hbox
), close
, FALSE
, FALSE
, 0);
5869 #if GTK_CHECK_VERSION(2,12,0)
5870 gtk_widget_set_tooltip_text(close
, _("Close"));
5872 g_signal_connect(close
, "clicked", G_CALLBACK(headline_close_press_cb
), gtkblist
);
5874 g_signal_connect(G_OBJECT(ebox
), "enter-notify-event", G_CALLBACK(headline_box_enter_cb
), gtkblist
);
5875 g_signal_connect(G_OBJECT(ebox
), "leave-notify-event", G_CALLBACK(headline_box_leave_cb
), gtkblist
);
5876 g_signal_connect(G_OBJECT(ebox
), "button-press-event", G_CALLBACK(headline_box_press_cb
), gtkblist
);
5878 /****************************** GtkTreeView **********************************/
5879 gtkblist
->treemodel
= gtk_tree_store_new(BLIST_COLUMNS
,
5880 GDK_TYPE_PIXBUF
, /* Status icon */
5881 G_TYPE_BOOLEAN
, /* Status icon visible */
5882 G_TYPE_STRING
, /* Name */
5883 G_TYPE_STRING
, /* Idle */
5884 G_TYPE_BOOLEAN
, /* Idle visible */
5885 GDK_TYPE_PIXBUF
, /* Buddy icon */
5886 G_TYPE_BOOLEAN
, /* Buddy icon visible */
5887 G_TYPE_POINTER
, /* Node */
5888 GDK_TYPE_COLOR
, /* bgcolor */
5889 G_TYPE_BOOLEAN
, /* Group expander */
5890 G_TYPE_BOOLEAN
, /* Group expander visible */
5891 G_TYPE_BOOLEAN
, /* Contact expander */
5892 G_TYPE_BOOLEAN
, /* Contact expander visible */
5893 GDK_TYPE_PIXBUF
, /* Emblem */
5894 G_TYPE_BOOLEAN
, /* Emblem visible */
5895 GDK_TYPE_PIXBUF
, /* Protocol icon */
5896 G_TYPE_BOOLEAN
/* Protocol visible */
5899 gtkblist
->treeview
= gtk_tree_view_new_with_model(GTK_TREE_MODEL(gtkblist
->treemodel
));
5901 gtk_widget_show(gtkblist
->treeview
);
5902 gtk_widget_set_name(gtkblist
->treeview
, "pidgin_blist_treeview");
5904 g_signal_connect(gtkblist
->treeview
,
5906 G_CALLBACK(treeview_style_set
), list
);
5907 /* Set up selection stuff */
5908 selection
= gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist
->treeview
));
5909 g_signal_connect(G_OBJECT(selection
), "changed", G_CALLBACK(pidgin_blist_selection_changed
), NULL
);
5912 gtk_tree_view_enable_model_drag_source(GTK_TREE_VIEW(gtkblist
->treeview
),
5913 GDK_BUTTON1_MASK
, ste
, 3,
5915 gtk_tree_view_enable_model_drag_dest(GTK_TREE_VIEW(gtkblist
->treeview
),
5917 GDK_ACTION_COPY
| GDK_ACTION_MOVE
);
5919 g_signal_connect(G_OBJECT(gtkblist
->treeview
), "drag-data-received", G_CALLBACK(pidgin_blist_drag_data_rcv_cb
), NULL
);
5920 g_signal_connect(G_OBJECT(gtkblist
->treeview
), "drag-data-get", G_CALLBACK(pidgin_blist_drag_data_get_cb
), NULL
);
5922 g_signal_connect(G_OBJECT(gtkblist
->treeview
), "drag-begin", G_CALLBACK(pidgin_blist_drag_begin
), NULL
);
5924 g_signal_connect(G_OBJECT(gtkblist
->treeview
), "drag-motion", G_CALLBACK(pidgin_blist_drag_motion_cb
), NULL
);
5925 g_signal_connect(G_OBJECT(gtkblist
->treeview
), "motion-notify-event", G_CALLBACK(pidgin_blist_motion_cb
), NULL
);
5926 g_signal_connect(G_OBJECT(gtkblist
->treeview
), "leave-notify-event", G_CALLBACK(pidgin_blist_leave_cb
), NULL
);
5929 pidgin_tooltip_setup_for_treeview(gtkblist
->treeview
, NULL
,
5930 pidgin_blist_create_tooltip
,
5931 pidgin_blist_paint_tip
);
5933 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(gtkblist
->treeview
), FALSE
);
5935 /* expander columns */
5936 column
= gtk_tree_view_column_new();
5937 gtk_tree_view_append_column(GTK_TREE_VIEW(gtkblist
->treeview
), column
);
5938 gtk_tree_view_column_set_visible(column
, FALSE
);
5939 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(gtkblist
->treeview
), column
);
5941 /* everything else column */
5942 gtkblist
->text_column
= gtk_tree_view_column_new ();
5943 gtk_tree_view_append_column(GTK_TREE_VIEW(gtkblist
->treeview
), gtkblist
->text_column
);
5944 pidgin_blist_build_layout(list
);
5946 g_signal_connect(G_OBJECT(gtkblist
->treeview
), "row-activated", G_CALLBACK(gtk_blist_row_activated_cb
), NULL
);
5947 g_signal_connect(G_OBJECT(gtkblist
->treeview
), "row-expanded", G_CALLBACK(gtk_blist_row_expanded_cb
), NULL
);
5948 g_signal_connect(G_OBJECT(gtkblist
->treeview
), "row-collapsed", G_CALLBACK(gtk_blist_row_collapsed_cb
), NULL
);
5949 g_signal_connect(G_OBJECT(gtkblist
->treeview
), "button-press-event", G_CALLBACK(gtk_blist_button_press_cb
), NULL
);
5950 g_signal_connect(G_OBJECT(gtkblist
->treeview
), "key-press-event", G_CALLBACK(gtk_blist_key_press_cb
), NULL
);
5951 g_signal_connect(G_OBJECT(gtkblist
->treeview
), "popup-menu", G_CALLBACK(pidgin_blist_popup_menu_cb
), NULL
);
5953 /* Enable CTRL+F searching */
5954 gtk_tree_view_set_search_column(GTK_TREE_VIEW(gtkblist
->treeview
), NAME_COLUMN
);
5955 gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(gtkblist
->treeview
),
5956 pidgin_blist_search_equal_func
, NULL
, NULL
);
5958 gtk_box_pack_start(GTK_BOX(gtkblist
->vbox
),
5959 pidgin_make_scrollable(gtkblist
->treeview
, GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
, GTK_SHADOW_NONE
, -1, -1),
5962 sep
= gtk_hseparator_new();
5963 gtk_box_pack_start(GTK_BOX(gtkblist
->vbox
), sep
, FALSE
, FALSE
, 0);
5965 gtkblist
->scrollbook
= pidgin_scroll_book_new();
5966 gtk_box_pack_start(GTK_BOX(gtkblist
->vbox
), gtkblist
->scrollbook
, FALSE
, FALSE
, 0);
5968 /* Create an vbox which holds the scrollbook which is actually used to
5969 * display connection errors. The vbox needs to still exist for
5970 * backwards compatibility.
5972 gtkblist
->error_buttons
= gtk_vbox_new(FALSE
, 0);
5973 gtk_box_pack_start(GTK_BOX(gtkblist
->vbox
), gtkblist
->error_buttons
, FALSE
, FALSE
, 0);
5974 gtk_container_set_border_width(GTK_CONTAINER(gtkblist
->error_buttons
), 0);
5976 priv
->error_scrollbook
= PIDGIN_SCROLL_BOOK(pidgin_scroll_book_new());
5977 gtk_box_pack_start(GTK_BOX(gtkblist
->error_buttons
),
5978 GTK_WIDGET(priv
->error_scrollbook
), FALSE
, FALSE
, 0);
5981 /* Add the statusbox */
5982 gtkblist
->statusbox
= pidgin_status_box_new();
5983 gtk_box_pack_start(GTK_BOX(gtkblist
->vbox
), gtkblist
->statusbox
, FALSE
, TRUE
, 0);
5984 gtk_widget_set_name(gtkblist
->statusbox
, "pidgin_blist_statusbox");
5985 gtk_container_set_border_width(GTK_CONTAINER(gtkblist
->statusbox
), 3);
5986 gtk_widget_show(gtkblist
->statusbox
);
5988 /* set the Show Offline Buddies option. must be done
5989 * after the treeview or faceprint gets mad. -Robot101
5991 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist
->ift
, N_("/Buddies/Show/Offline Buddies"))),
5992 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_offline_buddies"));
5994 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist
->ift
, N_("/Buddies/Show/Empty Groups"))),
5995 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_empty_groups"));
5997 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist
->ift
, N_("/Tools/Mute Sounds"))),
5998 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/sound/mute"));
6000 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist
->ift
, N_("/Buddies/Show/Buddy Details"))),
6001 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_buddy_icons"));
6003 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist
->ift
, N_("/Buddies/Show/Idle Times"))),
6004 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_idle_time"));
6006 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist
->ift
, N_("/Buddies/Show/Protocol Icons"))),
6007 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_protocol_icons"));
6009 if(purple_strequal(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/sound/method"), "none"))
6010 gtk_widget_set_sensitive(gtk_item_factory_get_widget(gtkblist
->ift
, N_("/Tools/Mute Sounds")), FALSE
);
6012 /* Update some dynamic things */
6013 update_menu_bar(gtkblist
);
6014 pidgin_blist_update_plugin_actions();
6015 pidgin_blist_update_sort_methods();
6017 /* OK... let's show this bad boy. */
6018 pidgin_blist_refresh(list
);
6019 pidgin_blist_restore_position();
6020 gtk_widget_show_all(GTK_WIDGET(gtkblist
->vbox
));
6021 gtk_widget_realize(GTK_WIDGET(gtkblist
->window
));
6022 purple_blist_set_visible(purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/list_visible"));
6024 /* start the refresh timer */
6025 gtkblist
->refresh_timer
= purple_timeout_add_seconds(30, (GSourceFunc
)pidgin_blist_refresh_timer
, list
);
6027 handle
= pidgin_blist_get_handle();
6029 /* things that affect how buddies are displayed */
6030 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/blist/show_buddy_icons",
6031 _prefs_change_redo_list
, NULL
);
6032 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/blist/show_idle_time",
6033 _prefs_change_redo_list
, NULL
);
6034 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/blist/show_empty_groups",
6035 _prefs_change_redo_list
, NULL
);
6036 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/blist/show_offline_buddies",
6037 _prefs_change_redo_list
, NULL
);
6038 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/blist/show_protocol_icons",
6039 _prefs_change_redo_list
, NULL
);
6042 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/blist/sort_type",
6043 _prefs_change_sort_method
, NULL
);
6046 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/sound/mute",
6047 pidgin_blist_mute_pref_cb
, NULL
);
6048 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/sound/method",
6049 pidgin_blist_sound_method_pref_cb
, NULL
);
6051 /* Setup some purple signal handlers. */
6053 handle
= purple_accounts_get_handle();
6054 purple_signal_connect(handle
, "account-enabled", gtkblist
,
6055 PURPLE_CALLBACK(account_modified
), gtkblist
);
6056 purple_signal_connect(handle
, "account-disabled", gtkblist
,
6057 PURPLE_CALLBACK(account_modified
), gtkblist
);
6058 purple_signal_connect(handle
, "account-removed", gtkblist
,
6059 PURPLE_CALLBACK(account_modified
), gtkblist
);
6060 purple_signal_connect(handle
, "account-status-changed", gtkblist
,
6061 PURPLE_CALLBACK(account_status_changed
),
6063 purple_signal_connect(handle
, "account-error-changed", gtkblist
,
6064 PURPLE_CALLBACK(update_account_error_state
),
6066 purple_signal_connect(handle
, "account-actions-changed", gtkblist
,
6067 PURPLE_CALLBACK(account_actions_changed
), NULL
);
6069 handle
= pidgin_account_get_handle();
6070 purple_signal_connect(handle
, "account-modified", gtkblist
,
6071 PURPLE_CALLBACK(account_modified
), gtkblist
);
6073 handle
= purple_connections_get_handle();
6074 purple_signal_connect(handle
, "signed-on", gtkblist
,
6075 PURPLE_CALLBACK(sign_on_off_cb
), list
);
6076 purple_signal_connect(handle
, "signed-off", gtkblist
,
6077 PURPLE_CALLBACK(sign_on_off_cb
), list
);
6079 handle
= purple_plugins_get_handle();
6080 purple_signal_connect(handle
, "plugin-load", gtkblist
,
6081 PURPLE_CALLBACK(plugin_changed_cb
), NULL
);
6082 purple_signal_connect(handle
, "plugin-unload", gtkblist
,
6083 PURPLE_CALLBACK(plugin_changed_cb
), NULL
);
6085 handle
= purple_conversations_get_handle();
6086 purple_signal_connect(handle
, "conversation-updated", gtkblist
,
6087 PURPLE_CALLBACK(conversation_updated_cb
),
6089 purple_signal_connect(handle
, "deleting-conversation", gtkblist
,
6090 PURPLE_CALLBACK(conversation_deleting_cb
),
6092 purple_signal_connect(handle
, "conversation-created", gtkblist
,
6093 PURPLE_CALLBACK(conversation_created_cb
),
6096 gtk_widget_hide(gtkblist
->headline_hbox
);
6098 show_initial_account_errors(gtkblist
);
6100 /* emit our created signal */
6101 handle
= pidgin_blist_get_handle();
6102 purple_signal_emit(handle
, "gtkblist-created", list
);
6105 static void redo_buddy_list(PurpleBuddyList
*list
, gboolean remove
, gboolean rerender
)
6107 PurpleBlistNode
*node
;
6109 gtkblist
= PIDGIN_BLIST(list
);
6110 if(!gtkblist
|| !gtkblist
->treeview
)
6117 /* This is only needed when we're reverting to a non-GTK+ sorted
6118 * status. We shouldn't need to remove otherwise.
6120 if (remove
&& !PURPLE_BLIST_NODE_IS_GROUP(node
))
6121 pidgin_blist_hide_node(list
, node
, FALSE
);
6123 if (PURPLE_BLIST_NODE_IS_BUDDY(node
))
6124 pidgin_blist_update_buddy(list
, node
, rerender
);
6125 else if (PURPLE_BLIST_NODE_IS_CHAT(node
))
6126 pidgin_blist_update(list
, node
);
6127 else if (PURPLE_BLIST_NODE_IS_GROUP(node
))
6128 pidgin_blist_update(list
, node
);
6129 node
= purple_blist_node_next(node
, FALSE
);
6134 void pidgin_blist_refresh(PurpleBuddyList
*list
)
6136 redo_buddy_list(list
, FALSE
, TRUE
);
6140 pidgin_blist_update_refresh_timeout()
6142 PurpleBuddyList
*blist
;
6143 PidginBuddyList
*gtkblist
;
6145 blist
= purple_get_blist();
6146 gtkblist
= PIDGIN_BLIST(purple_get_blist());
6148 gtkblist
->refresh_timer
= purple_timeout_add_seconds(30,(GSourceFunc
)pidgin_blist_refresh_timer
, blist
);
6151 static gboolean
get_iter_from_node(PurpleBlistNode
*node
, GtkTreeIter
*iter
) {
6152 struct _pidgin_blist_node
*gtknode
= (struct _pidgin_blist_node
*)node
->ui_data
;
6160 purple_debug_error("gtkblist", "get_iter_from_node was called, but we don't seem to have a blist\n");
6168 if ((path
= gtk_tree_row_reference_get_path(gtknode
->row
)) == NULL
)
6171 if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
), iter
, path
)) {
6172 gtk_tree_path_free(path
);
6175 gtk_tree_path_free(path
);
6179 static void pidgin_blist_remove(PurpleBuddyList
*list
, PurpleBlistNode
*node
)
6181 struct _pidgin_blist_node
*gtknode
= node
->ui_data
;
6183 purple_request_close_with_handle(node
);
6185 pidgin_blist_hide_node(list
, node
, TRUE
);
6188 pidgin_blist_update(list
, node
->parent
);
6190 /* There's something I don't understand here - Ethan */
6191 /* Ethan said that back in 2003, but this g_free has been left commented
6192 * out ever since. I can't find any reason at all why this is bad and
6193 * valgrind found several reasons why it's good. If this causes problems
6194 * comment it out again. Stu */
6195 /* Of course it still causes problems - this breaks dragging buddies into
6196 * contacts, the dragged buddy mysteriously 'disappears'. Stu. */
6197 /* I think it's fixed now. Stu. */
6200 if(gtknode
->recent_signonoff_timer
> 0)
6201 purple_timeout_remove(gtknode
->recent_signonoff_timer
);
6203 purple_signals_disconnect_by_handle(node
->ui_data
);
6204 g_free(node
->ui_data
);
6205 node
->ui_data
= NULL
;
6209 static gboolean
do_selection_changed(PurpleBlistNode
*new_selection
)
6211 PurpleBlistNode
*old_selection
= NULL
;
6213 /* test for gtkblist because crazy timeout means we can be called after the blist is gone */
6214 if (gtkblist
&& new_selection
!= gtkblist
->selected_node
) {
6215 old_selection
= gtkblist
->selected_node
;
6216 gtkblist
->selected_node
= new_selection
;
6218 pidgin_blist_update(NULL
, new_selection
);
6220 pidgin_blist_update(NULL
, old_selection
);
6226 static void pidgin_blist_selection_changed(GtkTreeSelection
*selection
, gpointer data
)
6228 PurpleBlistNode
*new_selection
= NULL
;
6231 if(gtk_tree_selection_get_selected(selection
, NULL
, &iter
)){
6232 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
,
6233 NODE_COLUMN
, &new_selection
, -1);
6236 /* we set this up as a timeout, otherwise the blist flickers ...
6237 * but we don't do it for groups, because it causes total bizarness -
6238 * the previously selected buddy node might rendered at half height.
6240 if ((new_selection
!= NULL
) && PURPLE_BLIST_NODE_IS_GROUP(new_selection
)) {
6241 do_selection_changed(new_selection
);
6243 g_timeout_add(0, (GSourceFunc
)do_selection_changed
, new_selection
);
6247 static gboolean
insert_node(PurpleBuddyList
*list
, PurpleBlistNode
*node
, GtkTreeIter
*iter
)
6249 GtkTreeIter parent_iter
, cur
, *curptr
= NULL
;
6250 struct _pidgin_blist_node
*gtknode
= node
->ui_data
;
6251 GtkTreePath
*newpath
;
6256 /* XXX: it's not necessary, but let's silence a warning*/
6257 memset(&parent_iter
, 0, sizeof(parent_iter
));
6259 if(node
->parent
&& !get_iter_from_node(node
->parent
, &parent_iter
))
6262 if(get_iter_from_node(node
, &cur
))
6265 if(PURPLE_BLIST_NODE_IS_CONTACT(node
) || PURPLE_BLIST_NODE_IS_CHAT(node
)) {
6266 current_sort_method
->func(node
, list
, parent_iter
, curptr
, iter
);
6268 sort_method_none(node
, list
, parent_iter
, curptr
, iter
);
6271 if(gtknode
!= NULL
) {
6272 gtk_tree_row_reference_free(gtknode
->row
);
6274 pidgin_blist_new_node(node
);
6275 gtknode
= (struct _pidgin_blist_node
*)node
->ui_data
;
6278 newpath
= gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist
->treemodel
),
6281 gtk_tree_row_reference_new(GTK_TREE_MODEL(gtkblist
->treemodel
),
6284 gtk_tree_path_free(newpath
);
6287 gtk_tree_store_set(gtkblist
->treemodel
, iter
,
6292 GtkTreePath
*expand
= NULL
;
6293 struct _pidgin_blist_node
*gtkparentnode
= node
->parent
->ui_data
;
6295 if(PURPLE_BLIST_NODE_IS_GROUP(node
->parent
)) {
6296 if(!purple_blist_node_get_bool(node
->parent
, "collapsed"))
6297 expand
= gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist
->treemodel
), &parent_iter
);
6298 } else if(PURPLE_BLIST_NODE_IS_CONTACT(node
->parent
) &&
6299 gtkparentnode
->contact_expanded
) {
6300 expand
= gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist
->treemodel
), &parent_iter
);
6303 gtk_tree_view_expand_row(GTK_TREE_VIEW(gtkblist
->treeview
), expand
, FALSE
);
6304 gtk_tree_path_free(expand
);
6311 static gboolean
pidgin_blist_group_has_show_offline_buddy(PurpleGroup
*group
)
6313 PurpleBlistNode
*gnode
, *cnode
, *bnode
;
6315 gnode
= (PurpleBlistNode
*)group
;
6316 for(cnode
= gnode
->child
; cnode
; cnode
= cnode
->next
) {
6317 if(PURPLE_BLIST_NODE_IS_CONTACT(cnode
)) {
6318 for(bnode
= cnode
->child
; bnode
; bnode
= bnode
->next
) {
6319 PurpleBuddy
*buddy
= (PurpleBuddy
*)bnode
;
6320 if (purple_account_is_connected(buddy
->account
) &&
6321 purple_blist_node_get_bool(bnode
, "show_offline"))
6329 /* This version of pidgin_blist_update_group can take the original buddy or a
6330 * group, but has much better algorithmic performance with a pre-known buddy.
6332 static void pidgin_blist_update_group(PurpleBuddyList
*list
,
6333 PurpleBlistNode
*node
)
6337 PurpleBlistNode
* gnode
;
6338 gboolean show
= FALSE
, show_offline
= FALSE
;
6340 g_return_if_fail(node
!= NULL
);
6345 if (PURPLE_BLIST_NODE_IS_GROUP(node
))
6347 else if (PURPLE_BLIST_NODE_IS_BUDDY(node
))
6348 gnode
= node
->parent
->parent
;
6349 else if (PURPLE_BLIST_NODE_IS_CONTACT(node
) || PURPLE_BLIST_NODE_IS_CHAT(node
))
6350 gnode
= node
->parent
;
6354 group
= (PurpleGroup
*)gnode
;
6356 show_offline
= purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_offline_buddies");
6359 count
= purple_blist_get_group_size(group
, FALSE
);
6361 count
= purple_blist_get_group_online_count(group
);
6363 if (count
> 0 || purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_empty_groups"))
6365 else if (PURPLE_BLIST_NODE_IS_BUDDY(node
) && buddy_is_displayable((PurpleBuddy
*)node
)) { /* Or chat? */
6367 } else if (!show_offline
) {
6368 show
= pidgin_blist_group_has_show_offline_buddy(group
);
6377 GdkColor
*bgcolor
= NULL
;
6378 GdkPixbuf
*avatar
= NULL
;
6379 PidginBlistTheme
*theme
= NULL
;
6381 if(!insert_node(list
, gnode
, &iter
))
6384 if ((theme
= pidgin_blist_get_theme()) == NULL
)
6386 else if (purple_blist_node_get_bool(gnode
, "collapsed") || count
<= 0)
6387 bgcolor
= pidgin_blist_theme_get_collapsed_background_color(theme
);
6389 bgcolor
= pidgin_blist_theme_get_expanded_background_color(theme
);
6391 path
= gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
);
6392 expanded
= gtk_tree_view_row_expanded(GTK_TREE_VIEW(gtkblist
->treeview
), path
);
6393 gtk_tree_path_free(path
);
6395 title
= pidgin_get_group_title(gnode
, expanded
);
6396 biglist
= purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_buddy_icons");
6399 avatar
= pidgin_blist_get_buddy_icon(gnode
, TRUE
, TRUE
);
6402 gtk_tree_store_set(gtkblist
->treemodel
, &iter
,
6403 STATUS_ICON_VISIBLE_COLUMN
, FALSE
,
6404 STATUS_ICON_COLUMN
, NULL
,
6407 BGCOLOR_COLUMN
, bgcolor
,
6408 GROUP_EXPANDER_COLUMN
, TRUE
,
6409 GROUP_EXPANDER_VISIBLE_COLUMN
, TRUE
,
6410 CONTACT_EXPANDER_VISIBLE_COLUMN
, FALSE
,
6411 BUDDY_ICON_COLUMN
, avatar
,
6412 BUDDY_ICON_VISIBLE_COLUMN
, biglist
,
6413 IDLE_VISIBLE_COLUMN
, FALSE
,
6414 EMBLEM_VISIBLE_COLUMN
, FALSE
,
6418 pidgin_blist_hide_node(list
, gnode
, TRUE
);
6422 static char *pidgin_get_group_title(PurpleBlistNode
*gnode
, gboolean expanded
)
6426 char group_count
[12] = "";
6428 PurpleBlistNode
*selected_node
= NULL
;
6430 PidginThemeFont
*pair
;
6431 gchar
const *text_color
, *text_font
;
6432 PidginBlistTheme
*theme
;
6434 group
= (PurpleGroup
*)gnode
;
6436 if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist
->treeview
)), NULL
, &iter
)) {
6437 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
,
6438 NODE_COLUMN
, &selected_node
, -1);
6440 selected
= (gnode
== selected_node
);
6443 g_snprintf(group_count
, sizeof(group_count
), "%d/%d",
6444 purple_blist_get_group_online_count(group
),
6445 purple_blist_get_group_size(group
, FALSE
));
6448 theme
= pidgin_blist_get_theme();
6452 pair
= pidgin_blist_theme_get_expanded_text_info(theme
);
6454 pair
= pidgin_blist_theme_get_collapsed_text_info(theme
);
6457 text_color
= selected
? NULL
: theme_font_get_color_default(pair
, NULL
);
6458 text_font
= theme_font_get_face_default(pair
, "");
6460 esc
= g_markup_escape_text(group
->name
, -1);
6462 mark
= g_strdup_printf("<span foreground='%s' font_desc='%s'><b>%s</b>%s%s%s</span>",
6463 text_color
, text_font
,
6465 !expanded
? " <span weight='light'>(</span>" : "",
6467 !expanded
? "<span weight='light'>)</span>" : "");
6469 mark
= g_strdup_printf("<span font_desc='%s'><b>%s</b>%s%s%s</span>",
6470 text_font
, esc
? esc
: "",
6471 !expanded
? " <span weight='light'>(</span>" : "",
6473 !expanded
? "<span weight='light'>)</span>" : "");
6480 static void buddy_node(PurpleBuddy
*buddy
, GtkTreeIter
*iter
, PurpleBlistNode
*node
)
6482 PurplePresence
*presence
= purple_buddy_get_presence(buddy
);
6483 GdkPixbuf
*status
, *avatar
, *emblem
, *prpl_icon
;
6484 GdkColor
*color
= NULL
;
6487 gboolean expanded
= ((struct _pidgin_blist_node
*)(node
->parent
->ui_data
))->contact_expanded
;
6488 gboolean selected
= (gtkblist
->selected_node
== node
);
6489 gboolean biglist
= purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_buddy_icons");
6490 PidginBlistTheme
*theme
;
6495 status
= pidgin_blist_get_status_icon((PurpleBlistNode
*)buddy
,
6496 biglist
? PIDGIN_STATUS_ICON_LARGE
: PIDGIN_STATUS_ICON_SMALL
);
6498 /* Speed it up if we don't want buddy icons. */
6500 avatar
= pidgin_blist_get_buddy_icon((PurpleBlistNode
*)buddy
, TRUE
, TRUE
);
6505 g_object_ref(G_OBJECT(gtkblist
->empty_avatar
));
6506 avatar
= gtkblist
->empty_avatar
;
6507 } else if ((!PURPLE_BUDDY_IS_ONLINE(buddy
) || purple_presence_is_idle(presence
))) {
6508 do_alphashift(avatar
, 77);
6511 emblem
= pidgin_blist_get_emblem((PurpleBlistNode
*) buddy
);
6512 mark
= pidgin_blist_get_name_markup(buddy
, selected
, TRUE
);
6514 theme
= pidgin_blist_get_theme();
6516 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_idle_time") &&
6517 purple_presence_is_idle(presence
) && !biglist
)
6519 time_t idle_secs
= purple_presence_get_idle_time(presence
);
6523 PidginThemeFont
*pair
= NULL
;
6524 const gchar
*textcolor
;
6529 ihrs
= (t
- idle_secs
) / 3600;
6530 imin
= ((t
- idle_secs
) / 60) % 60;
6534 else if (theme
!= NULL
&& (pair
= pidgin_blist_theme_get_idle_text_info(theme
)) != NULL
)
6535 textcolor
= pidgin_theme_font_get_color_describe(pair
);
6537 /* If no theme them default to making idle buddy names grey */
6538 textcolor
= "dim grey";
6541 idle
= g_strdup_printf("<span color='%s' font_desc='%s'>%d:%02d</span>",
6542 textcolor
, theme_font_get_face_default(pair
, ""),
6545 idle
= g_strdup_printf("<span font_desc='%s'>%d:%02d</span>",
6546 theme_font_get_face_default(pair
, ""),
6552 prpl_icon
= pidgin_create_prpl_icon(buddy
->account
, PIDGIN_PRPL_ICON_SMALL
);
6555 color
= pidgin_blist_theme_get_contact_color(theme
);
6557 gtk_tree_store_set(gtkblist
->treemodel
, iter
,
6558 STATUS_ICON_COLUMN
, status
,
6559 STATUS_ICON_VISIBLE_COLUMN
, TRUE
,
6562 IDLE_VISIBLE_COLUMN
, !biglist
&& idle
,
6563 BUDDY_ICON_COLUMN
, avatar
,
6564 BUDDY_ICON_VISIBLE_COLUMN
, biglist
,
6565 EMBLEM_COLUMN
, emblem
,
6566 EMBLEM_VISIBLE_COLUMN
, (emblem
!= NULL
),
6567 PROTOCOL_ICON_COLUMN
, prpl_icon
,
6568 PROTOCOL_ICON_VISIBLE_COLUMN
, purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_protocol_icons"),
6569 BGCOLOR_COLUMN
, color
,
6570 CONTACT_EXPANDER_COLUMN
, NULL
,
6571 CONTACT_EXPANDER_VISIBLE_COLUMN
, expanded
,
6572 GROUP_EXPANDER_VISIBLE_COLUMN
, FALSE
,
6578 g_object_unref(emblem
);
6580 g_object_unref(status
);
6582 g_object_unref(avatar
);
6584 g_object_unref(prpl_icon
);
6587 /* This is a variation on the original gtk_blist_update_contact. Here we
6588 can know in advance which buddy has changed so we can just update that */
6589 static void pidgin_blist_update_contact(PurpleBuddyList
*list
, PurpleBlistNode
*node
)
6591 PurpleBlistNode
*cnode
;
6592 PurpleContact
*contact
;
6594 gboolean biglist
= purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_buddy_icons");
6595 struct _pidgin_blist_node
*gtknode
;
6600 if (PURPLE_BLIST_NODE_IS_BUDDY(node
))
6601 cnode
= node
->parent
;
6605 g_return_if_fail(PURPLE_BLIST_NODE_IS_CONTACT(cnode
));
6607 /* First things first, update the group */
6608 if (PURPLE_BLIST_NODE_IS_BUDDY(node
))
6609 pidgin_blist_update_group(list
, node
);
6611 pidgin_blist_update_group(list
, cnode
->parent
);
6613 contact
= (PurpleContact
*)cnode
;
6614 buddy
= purple_contact_get_priority_buddy(contact
);
6616 if (buddy_is_displayable(buddy
))
6620 if(!insert_node(list
, cnode
, &iter
))
6623 gtknode
= (struct _pidgin_blist_node
*)cnode
->ui_data
;
6625 if(gtknode
->contact_expanded
) {
6628 const gchar
*fg_color
, *font
;
6629 GdkColor
*color
= NULL
;
6630 PidginBlistTheme
*theme
;
6631 PidginThemeFont
*pair
;
6632 gboolean selected
= (gtkblist
->selected_node
== cnode
);
6634 mark
= g_markup_escape_text(purple_contact_get_alias(contact
), -1);
6636 theme
= pidgin_blist_get_theme();
6640 pair
= pidgin_blist_theme_get_contact_text_info(theme
);
6641 color
= pidgin_blist_theme_get_contact_color(theme
);
6644 font
= theme_font_get_face_default(pair
, "");
6645 fg_color
= selected
? NULL
: theme_font_get_color_default(pair
, NULL
);
6648 tmp
= g_strdup_printf("<span font_desc='%s' color='%s'>%s</span>",
6649 font
, fg_color
, mark
);
6651 tmp
= g_strdup_printf("<span font_desc='%s'>%s</span>", font
,
6657 status
= pidgin_blist_get_status_icon(cnode
,
6658 biglist
? PIDGIN_STATUS_ICON_LARGE
: PIDGIN_STATUS_ICON_SMALL
);
6660 gtk_tree_store_set(gtkblist
->treemodel
, &iter
,
6661 STATUS_ICON_COLUMN
, status
,
6662 STATUS_ICON_VISIBLE_COLUMN
, TRUE
,
6665 IDLE_VISIBLE_COLUMN
, FALSE
,
6666 BGCOLOR_COLUMN
, color
,
6667 BUDDY_ICON_COLUMN
, NULL
,
6668 CONTACT_EXPANDER_COLUMN
, TRUE
,
6669 CONTACT_EXPANDER_VISIBLE_COLUMN
, TRUE
,
6670 GROUP_EXPANDER_VISIBLE_COLUMN
, FALSE
,
6674 g_object_unref(status
);
6676 buddy_node(buddy
, &iter
, cnode
);
6679 pidgin_blist_hide_node(list
, cnode
, TRUE
);
6685 static void pidgin_blist_update_buddy(PurpleBuddyList
*list
, PurpleBlistNode
*node
, gboolean status_change
)
6688 struct _pidgin_blist_node
*gtkparentnode
;
6690 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node
));
6692 if (node
->parent
== NULL
)
6695 buddy
= (PurpleBuddy
*)node
;
6697 /* First things first, update the contact */
6698 pidgin_blist_update_contact(list
, node
);
6700 gtkparentnode
= (struct _pidgin_blist_node
*)node
->parent
->ui_data
;
6702 if (gtkparentnode
->contact_expanded
&& buddy_is_displayable(buddy
))
6706 if (!insert_node(list
, node
, &iter
))
6709 buddy_node(buddy
, &iter
, node
);
6712 pidgin_blist_hide_node(list
, node
, TRUE
);
6717 static void pidgin_blist_update_chat(PurpleBuddyList
*list
, PurpleBlistNode
*node
)
6721 g_return_if_fail(PURPLE_BLIST_NODE_IS_CHAT(node
));
6726 /* First things first, update the group */
6727 pidgin_blist_update_group(list
, node
->parent
);
6729 chat
= (PurpleChat
*)node
;
6731 if(purple_account_is_connected(chat
->account
)) {
6733 GdkPixbuf
*status
, *avatar
, *emblem
, *prpl_icon
;
6734 const gchar
*color
, *font
;
6736 gboolean showicons
= purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_buddy_icons");
6737 gboolean biglist
= purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_buddy_icons");
6738 PidginBlistNode
*ui
;
6739 PurpleConversation
*conv
;
6740 gboolean hidden
= FALSE
;
6741 GdkColor
*bgcolor
= NULL
;
6742 PidginThemeFont
*pair
;
6743 PidginBlistTheme
*theme
;
6744 gboolean selected
= (gtkblist
->selected_node
== node
);
6745 gboolean nick_said
= FALSE
;
6747 if (!insert_node(list
, node
, &iter
))
6751 conv
= ui
->conv
.conv
;
6752 if (conv
&& pidgin_conv_is_hidden(PIDGIN_CONVERSATION(conv
))) {
6753 hidden
= (ui
->conv
.flags
& PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE
);
6754 nick_said
= (ui
->conv
.flags
& PIDGIN_BLIST_CHAT_HAS_PENDING_MESSAGE_WITH_NICK
);
6757 status
= pidgin_blist_get_status_icon(node
,
6758 biglist
? PIDGIN_STATUS_ICON_LARGE
: PIDGIN_STATUS_ICON_SMALL
);
6759 emblem
= pidgin_blist_get_emblem(node
);
6761 /* Speed it up if we don't want buddy icons. */
6763 avatar
= pidgin_blist_get_buddy_icon(node
, TRUE
, FALSE
);
6767 mark
= g_markup_escape_text(purple_chat_get_name(chat
), -1);
6769 theme
= pidgin_blist_get_theme();
6774 pair
= pidgin_blist_theme_get_unread_message_nick_said_text_info(theme
);
6776 pair
= pidgin_blist_theme_get_unread_message_text_info(theme
);
6777 else pair
= pidgin_blist_theme_get_online_text_info(theme
);
6780 font
= theme_font_get_face_default(pair
, "");
6781 if (selected
|| !(color
= theme_font_get_color_default(pair
, NULL
)))
6782 /* nick_said color is the same as gtkconv:tab-label-attention */
6783 color
= (nick_said
? "#006aff" : NULL
);
6786 tmp
= g_strdup_printf("<span font_desc='%s' color='%s' weight='%s'>%s</span>",
6787 font
, color
, hidden
? "bold" : "normal", mark
);
6789 tmp
= g_strdup_printf("<span font_desc='%s' weight='%s'>%s</span>",
6790 font
, hidden
? "bold" : "normal", mark
);
6795 prpl_icon
= pidgin_create_prpl_icon(chat
->account
, PIDGIN_PRPL_ICON_SMALL
);
6798 bgcolor
= pidgin_blist_theme_get_contact_color(theme
);
6800 gtk_tree_store_set(gtkblist
->treemodel
, &iter
,
6801 STATUS_ICON_COLUMN
, status
,
6802 STATUS_ICON_VISIBLE_COLUMN
, TRUE
,
6803 BUDDY_ICON_COLUMN
, avatar
? avatar
: gtkblist
->empty_avatar
,
6804 BUDDY_ICON_VISIBLE_COLUMN
, showicons
,
6805 EMBLEM_COLUMN
, emblem
,
6806 EMBLEM_VISIBLE_COLUMN
, emblem
!= NULL
,
6807 PROTOCOL_ICON_COLUMN
, prpl_icon
,
6808 PROTOCOL_ICON_VISIBLE_COLUMN
, purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_protocol_icons"),
6810 BGCOLOR_COLUMN
, bgcolor
,
6811 GROUP_EXPANDER_VISIBLE_COLUMN
, FALSE
,
6816 g_object_unref(emblem
);
6818 g_object_unref(status
);
6820 g_object_unref(avatar
);
6822 g_object_unref(prpl_icon
);
6825 pidgin_blist_hide_node(list
, node
, TRUE
);
6829 static void pidgin_blist_update(PurpleBuddyList
*list
, PurpleBlistNode
*node
)
6832 gtkblist
= PIDGIN_BLIST(list
);
6833 if(!gtkblist
|| !gtkblist
->treeview
|| !node
)
6836 if (node
->ui_data
== NULL
)
6837 pidgin_blist_new_node(node
);
6839 switch(node
->type
) {
6840 case PURPLE_BLIST_GROUP_NODE
:
6841 pidgin_blist_update_group(list
, node
);
6843 case PURPLE_BLIST_CONTACT_NODE
:
6844 pidgin_blist_update_contact(list
, node
);
6846 case PURPLE_BLIST_BUDDY_NODE
:
6847 pidgin_blist_update_buddy(list
, node
, TRUE
);
6849 case PURPLE_BLIST_CHAT_NODE
:
6850 pidgin_blist_update_chat(list
, node
);
6852 case PURPLE_BLIST_OTHER_NODE
:
6858 static void pidgin_blist_destroy(PurpleBuddyList
*list
)
6860 PidginBuddyListPrivate
*priv
;
6862 if (!list
|| !list
->ui_data
)
6865 g_return_if_fail(list
->ui_data
== gtkblist
);
6867 purple_signals_disconnect_by_handle(gtkblist
);
6869 if (gtkblist
->headline_close
)
6870 g_object_unref(gtkblist
->headline_close
);
6872 gtk_widget_destroy(gtkblist
->window
);
6874 pidgin_blist_tooltip_destroy();
6876 if (gtkblist
->refresh_timer
)
6877 purple_timeout_remove(gtkblist
->refresh_timer
);
6878 if (gtkblist
->timeout
)
6879 g_source_remove(gtkblist
->timeout
);
6880 if (gtkblist
->drag_timeout
)
6881 g_source_remove(gtkblist
->drag_timeout
);
6883 g_hash_table_destroy(gtkblist
->connection_errors
);
6884 gtkblist
->refresh_timer
= 0;
6885 gtkblist
->timeout
= 0;
6886 gtkblist
->drag_timeout
= 0;
6887 gtkblist
->window
= gtkblist
->vbox
= gtkblist
->treeview
= NULL
;
6888 g_object_unref(G_OBJECT(gtkblist
->treemodel
));
6889 gtkblist
->treemodel
= NULL
;
6890 g_object_unref(G_OBJECT(gtkblist
->ift
));
6891 g_object_unref(G_OBJECT(gtkblist
->empty_avatar
));
6893 gdk_cursor_unref(gtkblist
->hand_cursor
);
6894 gdk_cursor_unref(gtkblist
->arrow_cursor
);
6895 gtkblist
->hand_cursor
= NULL
;
6896 gtkblist
->arrow_cursor
= NULL
;
6898 priv
= PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist
);
6899 if (priv
->current_theme
)
6900 g_object_unref(priv
->current_theme
);
6901 if (priv
->select_page_timeout
)
6902 purple_timeout_remove(priv
->select_page_timeout
);
6908 purple_prefs_disconnect_by_handle(pidgin_blist_get_handle());
6911 static void pidgin_blist_set_visible(PurpleBuddyList
*list
, gboolean show
)
6913 if (!(gtkblist
&& gtkblist
->window
))
6917 if(!PIDGIN_WINDOW_ICONIFIED(gtkblist
->window
) && !GTK_WIDGET_VISIBLE(gtkblist
->window
))
6918 purple_signal_emit(pidgin_blist_get_handle(), "gtkblist-unhiding", gtkblist
);
6919 pidgin_blist_restore_position();
6920 gtk_window_present(GTK_WINDOW(gtkblist
->window
));
6922 if(visibility_manager_count
) {
6923 purple_signal_emit(pidgin_blist_get_handle(), "gtkblist-hiding", gtkblist
);
6924 gtk_widget_hide(gtkblist
->window
);
6926 if (!GTK_WIDGET_VISIBLE(gtkblist
->window
))
6927 gtk_widget_show(gtkblist
->window
);
6928 gtk_window_iconify(GTK_WINDOW(gtkblist
->window
));
6936 static GList
*list
= NULL
;
6939 PurpleBlistNode
*gnode
;
6944 if (purple_get_blist()->root
== NULL
)
6946 list
= g_list_append(list
, (gpointer
)_("Buddies"));
6950 for (gnode
= purple_get_blist()->root
;
6952 gnode
= gnode
->next
)
6954 if (PURPLE_BLIST_NODE_IS_GROUP(gnode
))
6956 g
= (PurpleGroup
*)gnode
;
6958 list
= g_list_append(list
, tmp2
);
6967 add_buddy_select_account_cb(GObject
*w
, PurpleAccount
*account
,
6968 PidginAddBuddyData
*data
)
6970 PurpleConnection
*pc
= NULL
;
6971 PurplePlugin
*prpl
= NULL
;
6972 PurplePluginProtocolInfo
*prpl_info
= NULL
;
6973 gboolean invite_enabled
= TRUE
;
6975 /* Save our account */
6976 data
->rq_data
.account
= account
;
6979 pc
= purple_account_get_connection(account
);
6981 prpl
= purple_connection_get_prpl(pc
);
6983 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(prpl
);
6984 if (prpl_info
&& !(prpl_info
->options
& OPT_PROTO_INVITE_MESSAGE
))
6985 invite_enabled
= FALSE
;
6987 gtk_widget_set_sensitive(data
->entry_for_invite
, invite_enabled
);
6991 destroy_add_buddy_dialog_cb(GtkWidget
*win
, PidginAddBuddyData
*data
)
6997 add_buddy_cb(GtkWidget
*w
, int resp
, PidginAddBuddyData
*data
)
6999 const char *grp
, *who
, *whoalias
, *invite
;
7000 PurpleAccount
*account
;
7003 PurpleConversation
*c
;
7004 PurpleBuddyIcon
*icon
;
7006 if (resp
== GTK_RESPONSE_OK
)
7008 who
= gtk_entry_get_text(GTK_ENTRY(data
->entry
));
7009 grp
= pidgin_text_combo_box_entry_get_text(data
->combo
);
7010 whoalias
= gtk_entry_get_text(GTK_ENTRY(data
->entry_for_alias
));
7011 if (*whoalias
== '\0')
7013 invite
= gtk_entry_get_text(GTK_ENTRY(data
->entry_for_invite
));
7014 if (*invite
== '\0')
7017 account
= data
->rq_data
.account
;
7020 if ((grp
!= NULL
) && (*grp
!= '\0'))
7022 if ((g
= purple_find_group(grp
)) == NULL
)
7024 g
= purple_group_new(grp
);
7025 purple_blist_add_group(g
, NULL
);
7028 b
= purple_find_buddy_in_group(account
, who
, g
);
7030 else if ((b
= purple_find_buddy(account
, who
)) != NULL
)
7032 g
= purple_buddy_get_group(b
);
7037 b
= purple_buddy_new(account
, who
, whoalias
);
7038 purple_blist_add_buddy(b
, NULL
, g
, NULL
);
7041 purple_account_add_buddy_with_invite(account
, b
, invite
);
7043 /* Offer to merge people with the same alias. */
7044 if (whoalias
!= NULL
&& g
!= NULL
)
7045 gtk_blist_auto_personize((PurpleBlistNode
*)g
, whoalias
);
7049 * It really seems like it would be better if the call to
7050 * purple_account_add_buddy() and purple_conversation_update() were done in
7051 * blist.c, possibly in the purple_blist_add_buddy() function. Maybe
7052 * purple_account_add_buddy() should be renamed to
7053 * purple_blist_add_new_buddy() or something, and have it call
7054 * purple_blist_add_buddy() after it creates it. --Mark
7056 * No that's not good. blist.c should only deal with adding nodes to the
7057 * local list. We need a new, non-gtk file that calls both
7058 * purple_account_add_buddy and purple_blist_add_buddy().
7059 * Or something. --Mark
7062 c
= purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM
, who
, data
->rq_data
.account
);
7064 icon
= purple_conv_im_get_icon(PURPLE_CONV_IM(c
));
7066 purple_buddy_icon_update(icon
);
7070 gtk_widget_destroy(data
->rq_data
.window
);
7074 pidgin_blist_request_add_buddy(PurpleAccount
*account
, const char *username
,
7075 const char *group
, const char *alias
)
7077 PidginAddBuddyData
*data
= g_new0(PidginAddBuddyData
, 1);
7079 if (account
== NULL
)
7080 account
= purple_connection_get_account(purple_connections_get_all()->data
);
7082 make_blist_request_dialog((PidginBlistRequestData
*)data
,
7084 _("Add Buddy"), "add_buddy",
7085 _("Add a buddy.\n"),
7086 G_CALLBACK(add_buddy_select_account_cb
), NULL
,
7087 G_CALLBACK(add_buddy_cb
));
7088 gtk_dialog_add_buttons(GTK_DIALOG(data
->rq_data
.window
),
7089 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
7090 GTK_STOCK_ADD
, GTK_RESPONSE_OK
,
7092 gtk_dialog_set_default_response(GTK_DIALOG(data
->rq_data
.window
),
7095 g_signal_connect(G_OBJECT(data
->rq_data
.window
), "destroy",
7096 G_CALLBACK(destroy_add_buddy_dialog_cb
), data
);
7098 data
->entry
= gtk_entry_new();
7100 pidgin_add_widget_to_vbox(data
->rq_data
.vbox
, _("Buddy's _username:"),
7101 data
->rq_data
.sg
, data
->entry
, TRUE
, NULL
);
7102 gtk_widget_grab_focus(data
->entry
);
7104 if (username
!= NULL
)
7105 gtk_entry_set_text(GTK_ENTRY(data
->entry
), username
);
7107 gtk_dialog_set_response_sensitive(GTK_DIALOG(data
->rq_data
.window
),
7108 GTK_RESPONSE_OK
, FALSE
);
7110 gtk_entry_set_activates_default (GTK_ENTRY(data
->entry
), TRUE
);
7112 g_signal_connect(G_OBJECT(data
->entry
), "changed",
7113 G_CALLBACK(pidgin_set_sensitive_if_input
),
7114 data
->rq_data
.window
);
7116 data
->entry_for_alias
= gtk_entry_new();
7117 pidgin_add_widget_to_vbox(data
->rq_data
.vbox
, _("(Optional) A_lias:"),
7118 data
->rq_data
.sg
, data
->entry_for_alias
, TRUE
,
7122 gtk_entry_set_text(GTK_ENTRY(data
->entry_for_alias
), alias
);
7124 if (username
!= NULL
)
7125 gtk_widget_grab_focus(GTK_WIDGET(data
->entry_for_alias
));
7127 data
->entry_for_invite
= gtk_entry_new();
7128 pidgin_add_widget_to_vbox(data
->rq_data
.vbox
, _("(Optional) _Invite message:"),
7129 data
->rq_data
.sg
, data
->entry_for_invite
, TRUE
,
7132 data
->combo
= pidgin_text_combo_box_entry_new(group
, groups_tree());
7133 pidgin_add_widget_to_vbox(data
->rq_data
.vbox
, _("Add buddy to _group:"),
7134 data
->rq_data
.sg
, data
->combo
, TRUE
, NULL
);
7136 gtk_widget_show_all(data
->rq_data
.window
);
7138 /* Force update of invite message entry sensitivity */
7139 add_buddy_select_account_cb(NULL
, account
, data
);
7143 add_chat_cb(GtkWidget
*w
, PidginAddChatData
*data
)
7147 GHashTable
*components
;
7149 components
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
7152 for (tmp
= data
->chat_data
.entries
; tmp
; tmp
= tmp
->next
)
7154 if (g_object_get_data(tmp
->data
, "is_spin"))
7156 g_hash_table_replace(components
,
7157 g_strdup(g_object_get_data(tmp
->data
, "identifier")),
7158 g_strdup_printf("%d",
7159 gtk_spin_button_get_value_as_int(tmp
->data
)));
7163 const char *value
= gtk_entry_get_text(tmp
->data
);
7166 g_hash_table_replace(components
,
7167 g_strdup(g_object_get_data(tmp
->data
, "identifier")),
7172 chat
= purple_chat_new(data
->chat_data
.rq_data
.account
,
7173 gtk_entry_get_text(GTK_ENTRY(data
->alias_entry
)),
7178 const char *group_name
;
7180 group_name
= pidgin_text_combo_box_entry_get_text(data
->group_combo
);
7183 if ((group_name
!= NULL
) && (*group_name
!= '\0') &&
7184 ((group
= purple_find_group(group_name
)) == NULL
))
7186 group
= purple_group_new(group_name
);
7187 purple_blist_add_group(group
, NULL
);
7190 purple_blist_add_chat(chat
, group
, NULL
);
7192 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(data
->autojoin
)))
7193 purple_blist_node_set_bool((PurpleBlistNode
*)chat
, "gtk-autojoin", TRUE
);
7195 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(data
->persistent
)))
7196 purple_blist_node_set_bool((PurpleBlistNode
*)chat
, "gtk-persistent", TRUE
);
7199 gtk_widget_destroy(data
->chat_data
.rq_data
.window
);
7200 g_free(data
->chat_data
.default_chat_name
);
7201 g_list_free(data
->chat_data
.entries
);
7206 add_chat_resp_cb(GtkWidget
*w
, int resp
, PidginAddChatData
*data
)
7208 if (resp
== GTK_RESPONSE_OK
)
7210 add_chat_cb(NULL
, data
);
7214 pidgin_roomlist_dialog_show_with_account(data
->chat_data
.rq_data
.account
);
7218 gtk_widget_destroy(data
->chat_data
.rq_data
.window
);
7219 g_free(data
->chat_data
.default_chat_name
);
7220 g_list_free(data
->chat_data
.entries
);
7226 pidgin_blist_request_add_chat(PurpleAccount
*account
, PurpleGroup
*group
,
7227 const char *alias
, const char *name
)
7229 PidginAddChatData
*data
;
7231 PurpleConnection
*gc
;
7234 if (account
!= NULL
) {
7235 gc
= purple_account_get_connection(account
);
7237 if (PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
)->join_chat
== NULL
) {
7238 purple_notify_error(gc
, NULL
, _("This protocol does not support chat rooms."), NULL
);
7242 /* Find an account with chat capabilities */
7243 for (l
= purple_connections_get_all(); l
!= NULL
; l
= l
->next
) {
7244 gc
= (PurpleConnection
*)l
->data
;
7246 if (PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
)->join_chat
!= NULL
) {
7247 account
= purple_connection_get_account(gc
);
7252 if (account
== NULL
) {
7253 purple_notify_error(NULL
, NULL
,
7254 _("You are not currently signed on with any "
7255 "protocols that have the ability to chat."), NULL
);
7260 data
= g_new0(PidginAddChatData
, 1);
7261 vbox
= GTK_BOX(make_blist_request_dialog((PidginBlistRequestData
*)data
, account
,
7262 _("Add Chat"), "add_chat",
7263 _("Please enter an alias, and the appropriate information "
7264 "about the chat you would like to add to your buddy list.\n"),
7265 G_CALLBACK(chat_select_account_cb
), chat_account_filter_func
,
7266 G_CALLBACK(add_chat_resp_cb
)));
7267 gtk_dialog_add_buttons(GTK_DIALOG(data
->chat_data
.rq_data
.window
),
7269 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
7270 GTK_STOCK_ADD
, GTK_RESPONSE_OK
,
7272 gtk_dialog_set_default_response(GTK_DIALOG(data
->chat_data
.rq_data
.window
),
7275 data
->chat_data
.default_chat_name
= g_strdup(name
);
7277 rebuild_chat_entries((PidginChatData
*)data
, name
);
7279 data
->alias_entry
= gtk_entry_new();
7281 gtk_entry_set_text(GTK_ENTRY(data
->alias_entry
), alias
);
7282 gtk_entry_set_activates_default(GTK_ENTRY(data
->alias_entry
), TRUE
);
7284 pidgin_add_widget_to_vbox(GTK_BOX(vbox
), _("A_lias:"),
7285 data
->chat_data
.rq_data
.sg
, data
->alias_entry
,
7288 gtk_widget_grab_focus(data
->alias_entry
);
7290 data
->group_combo
= pidgin_text_combo_box_entry_new(group
? group
->name
: NULL
, groups_tree());
7291 pidgin_add_widget_to_vbox(GTK_BOX(vbox
), _("_Group:"),
7292 data
->chat_data
.rq_data
.sg
, data
->group_combo
,
7295 data
->autojoin
= gtk_check_button_new_with_mnemonic(_("Automatically _join when account connects"));
7296 data
->persistent
= gtk_check_button_new_with_mnemonic(_("_Remain in chat after window is closed"));
7297 gtk_box_pack_start(GTK_BOX(vbox
), data
->autojoin
, FALSE
, FALSE
, 0);
7298 gtk_box_pack_start(GTK_BOX(vbox
), data
->persistent
, FALSE
, FALSE
, 0);
7300 gtk_widget_show_all(data
->chat_data
.rq_data
.window
);
7304 add_group_cb(PurpleConnection
*gc
, const char *group_name
)
7308 if ((group_name
== NULL
) || (*group_name
== '\0'))
7311 group
= purple_group_new(group_name
);
7312 purple_blist_add_group(group
, NULL
);
7316 pidgin_blist_request_add_group(void)
7318 purple_request_input(NULL
, _("Add Group"), NULL
,
7319 _("Please enter the name of the group to be added."),
7320 NULL
, FALSE
, FALSE
, NULL
,
7321 _("Add"), G_CALLBACK(add_group_cb
),
7328 pidgin_blist_toggle_visibility()
7330 if (gtkblist
&& gtkblist
->window
) {
7331 if (GTK_WIDGET_VISIBLE(gtkblist
->window
)) {
7332 /* make the buddy list visible if it is iconified or if it is
7333 * obscured and not currently focused (the focus part ensures
7334 * that we do something reasonable if the buddy list is obscured
7335 * by a window set to always be on top), otherwise hide the
7338 purple_blist_set_visible(PIDGIN_WINDOW_ICONIFIED(gtkblist
->window
) ||
7339 ((gtk_blist_visibility
!= GDK_VISIBILITY_UNOBSCURED
) &&
7340 !gtk_blist_focused
));
7342 purple_blist_set_visible(TRUE
);
7348 pidgin_blist_visibility_manager_add()
7350 visibility_manager_count
++;
7351 purple_debug_info("gtkblist", "added visibility manager: %d\n", visibility_manager_count
);
7355 pidgin_blist_visibility_manager_remove()
7357 if (visibility_manager_count
)
7358 visibility_manager_count
--;
7359 if (!visibility_manager_count
)
7360 purple_blist_set_visible(TRUE
);
7361 purple_debug_info("gtkblist", "removed visibility manager: %d\n", visibility_manager_count
);
7364 void pidgin_blist_add_alert(GtkWidget
*widget
)
7366 gtk_container_add(GTK_CONTAINER(gtkblist
->scrollbook
), widget
);
7371 pidgin_blist_set_headline(const char *text
, GdkPixbuf
*pixbuf
, GCallback callback
,
7372 gpointer user_data
, GDestroyNotify destroy
)
7374 /* Destroy any existing headline first */
7375 if (gtkblist
->headline_destroy
)
7376 gtkblist
->headline_destroy(gtkblist
->headline_data
);
7378 gtk_label_set_markup(GTK_LABEL(gtkblist
->headline_label
), text
);
7379 gtk_image_set_from_pixbuf(GTK_IMAGE(gtkblist
->headline_image
), pixbuf
);
7381 gtkblist
->headline_callback
= callback
;
7382 gtkblist
->headline_data
= user_data
;
7383 gtkblist
->headline_destroy
= destroy
;
7384 if (text
!= NULL
|| pixbuf
!= NULL
) {
7386 gtk_widget_show_all(gtkblist
->headline_hbox
);
7388 gtk_widget_hide(gtkblist
->headline_hbox
);
7396 if (gtkblist
->window
&& !GTK_WIDGET_HAS_FOCUS(gtkblist
->window
))
7397 pidgin_set_urgent(GTK_WINDOW(gtkblist
->window
), TRUE
);
7400 static PurpleBlistUiOps blist_ui_ops
=
7402 pidgin_blist_new_list
,
7403 pidgin_blist_new_node
,
7405 pidgin_blist_update
,
7406 pidgin_blist_remove
,
7407 pidgin_blist_destroy
,
7408 pidgin_blist_set_visible
,
7409 pidgin_blist_request_add_buddy
,
7410 pidgin_blist_request_add_chat
,
7411 pidgin_blist_request_add_group
,
7420 pidgin_blist_get_ui_ops(void)
7422 return &blist_ui_ops
;
7425 PidginBuddyList
*pidgin_blist_get_default_gtk_blist()
7430 static gboolean
autojoin_cb(PurpleConnection
*gc
, gpointer data
)
7432 PurpleAccount
*account
= purple_connection_get_account(gc
);
7433 PurpleBlistNode
*gnode
, *cnode
;
7434 for(gnode
= purple_get_blist()->root
; gnode
; gnode
= gnode
->next
)
7436 if(!PURPLE_BLIST_NODE_IS_GROUP(gnode
))
7438 for(cnode
= gnode
->child
; cnode
; cnode
= cnode
->next
)
7442 if(!PURPLE_BLIST_NODE_IS_CHAT(cnode
))
7445 chat
= (PurpleChat
*)cnode
;
7447 if(chat
->account
!= account
)
7450 if (purple_blist_node_get_bool((PurpleBlistNode
*)chat
, "gtk-autojoin"))
7451 serv_join_chat(gc
, chat
->components
);
7455 /* Stop processing; we handled the autojoins. */
7460 pidgin_blist_get_handle() {
7466 static gboolean
buddy_signonoff_timeout_cb(PurpleBuddy
*buddy
)
7468 struct _pidgin_blist_node
*gtknode
= ((PurpleBlistNode
*)buddy
)->ui_data
;
7470 gtknode
->recent_signonoff
= FALSE
;
7471 gtknode
->recent_signonoff_timer
= 0;
7473 pidgin_blist_update(NULL
, (PurpleBlistNode
*)buddy
);
7478 static void buddy_signonoff_cb(PurpleBuddy
*buddy
)
7480 struct _pidgin_blist_node
*gtknode
;
7482 if(!((PurpleBlistNode
*)buddy
)->ui_data
) {
7483 pidgin_blist_new_node((PurpleBlistNode
*)buddy
);
7486 gtknode
= ((PurpleBlistNode
*)buddy
)->ui_data
;
7488 gtknode
->recent_signonoff
= TRUE
;
7490 if(gtknode
->recent_signonoff_timer
> 0)
7491 purple_timeout_remove(gtknode
->recent_signonoff_timer
);
7492 gtknode
->recent_signonoff_timer
= purple_timeout_add_seconds(10,
7493 (GSourceFunc
)buddy_signonoff_timeout_cb
, buddy
);
7497 pidgin_blist_set_theme(PidginBlistTheme
*theme
)
7499 PidginBuddyListPrivate
*priv
= PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist
);
7500 PurpleBuddyList
*list
= purple_get_blist();
7503 purple_prefs_set_string(PIDGIN_PREFS_ROOT
"/blist/theme",
7504 purple_theme_get_name(PURPLE_THEME(theme
)));
7506 purple_prefs_set_string(PIDGIN_PREFS_ROOT
"/blist/theme", "");
7508 if (priv
->current_theme
)
7509 g_object_unref(priv
->current_theme
);
7511 priv
->current_theme
= theme
? g_object_ref(theme
) : NULL
;
7513 pidgin_blist_build_layout(list
);
7515 pidgin_blist_refresh(list
);
7520 pidgin_blist_get_theme()
7522 PidginBuddyListPrivate
*priv
= PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist
);
7524 return priv
->current_theme
;
7527 void pidgin_blist_init(void)
7529 void *gtk_blist_handle
= pidgin_blist_get_handle();
7531 cached_emblems
= g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, NULL
);
7533 /* Initialize prefs */
7534 purple_prefs_add_none(PIDGIN_PREFS_ROOT
"/blist");
7535 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/blist/show_buddy_icons", TRUE
);
7536 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/blist/show_empty_groups", FALSE
);
7537 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/blist/show_idle_time", TRUE
);
7538 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/blist/show_offline_buddies", FALSE
);
7539 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/blist/show_protocol_icons", FALSE
);
7540 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/blist/list_visible", FALSE
);
7541 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/blist/list_maximized", FALSE
);
7542 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/blist/sort_type", "alphabetical");
7543 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/blist/x", 0);
7544 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/blist/y", 0);
7545 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/blist/width", 250); /* Golden ratio, baby */
7546 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/blist/height", 405); /* Golden ratio, baby */
7547 #if !GTK_CHECK_VERSION(2,14,0)
7548 /* This pref is used in pidgintooltip.c. */
7549 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/blist/tooltip_delay", 500);
7551 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/blist/theme", "");
7553 purple_theme_manager_register_type(g_object_new(PIDGIN_TYPE_BLIST_THEME_LOADER
, "type", "blist", NULL
));
7555 /* Register our signals */
7556 purple_signal_register(gtk_blist_handle
, "gtkblist-hiding",
7557 purple_marshal_VOID__POINTER
, NULL
, 1,
7558 purple_value_new(PURPLE_TYPE_SUBTYPE
,
7559 PURPLE_SUBTYPE_BLIST
));
7561 purple_signal_register(gtk_blist_handle
, "gtkblist-unhiding",
7562 purple_marshal_VOID__POINTER
, NULL
, 1,
7563 purple_value_new(PURPLE_TYPE_SUBTYPE
,
7564 PURPLE_SUBTYPE_BLIST
));
7566 purple_signal_register(gtk_blist_handle
, "gtkblist-created",
7567 purple_marshal_VOID__POINTER
, NULL
, 1,
7568 purple_value_new(PURPLE_TYPE_SUBTYPE
,
7569 PURPLE_SUBTYPE_BLIST
));
7571 purple_signal_register(gtk_blist_handle
, "drawing-tooltip",
7572 purple_marshal_VOID__POINTER_POINTER_UINT
, NULL
, 3,
7573 purple_value_new(PURPLE_TYPE_SUBTYPE
,
7574 PURPLE_SUBTYPE_BLIST_NODE
),
7575 purple_value_new_outgoing(PURPLE_TYPE_BOXED
, "GString *"),
7576 purple_value_new(PURPLE_TYPE_BOOLEAN
));
7578 purple_signal_register(gtk_blist_handle
, "drawing-buddy",
7579 purple_marshal_POINTER__POINTER
,
7580 purple_value_new(PURPLE_TYPE_STRING
), 1,
7581 purple_value_new(PURPLE_TYPE_SUBTYPE
,
7582 PURPLE_SUBTYPE_BLIST_BUDDY
));
7584 purple_signal_connect(purple_blist_get_handle(), "buddy-signed-on",
7585 gtk_blist_handle
, PURPLE_CALLBACK(buddy_signonoff_cb
), NULL
);
7586 purple_signal_connect(purple_blist_get_handle(), "buddy-signed-off",
7587 gtk_blist_handle
, PURPLE_CALLBACK(buddy_signonoff_cb
), NULL
);
7588 purple_signal_connect(purple_blist_get_handle(), "buddy-privacy-changed",
7589 gtk_blist_handle
, PURPLE_CALLBACK(pidgin_blist_update_privacy_cb
), NULL
);
7591 purple_signal_connect_priority(purple_connections_get_handle(), "autojoin",
7592 gtk_blist_handle
, PURPLE_CALLBACK(autojoin_cb
),
7593 NULL
, PURPLE_SIGNAL_PRIORITY_HIGHEST
);
7597 pidgin_blist_uninit(void) {
7598 g_hash_table_destroy(cached_emblems
);
7600 purple_signals_unregister_by_instance(pidgin_blist_get_handle());
7601 purple_signals_disconnect_by_handle(pidgin_blist_get_handle());
7604 /*********************************************************************
7605 * Buddy List sorting functions *
7606 *********************************************************************/
7608 GList
*pidgin_blist_get_sort_methods()
7610 return pidgin_blist_sort_methods
;
7613 void pidgin_blist_sort_method_reg(const char *id
, const char *name
, pidgin_blist_sort_function func
)
7615 struct pidgin_blist_sort_method
*method
;
7617 g_return_if_fail(id
!= NULL
);
7618 g_return_if_fail(name
!= NULL
);
7619 g_return_if_fail(func
!= NULL
);
7621 method
= g_new0(struct pidgin_blist_sort_method
, 1);
7622 method
->id
= g_strdup(id
);
7623 method
->name
= g_strdup(name
);
7624 method
->func
= func
;
7625 pidgin_blist_sort_methods
= g_list_append(pidgin_blist_sort_methods
, method
);
7626 pidgin_blist_update_sort_methods();
7629 void pidgin_blist_sort_method_unreg(const char *id
)
7631 GList
*l
= pidgin_blist_sort_methods
;
7633 g_return_if_fail(id
!= NULL
);
7636 struct pidgin_blist_sort_method
*method
= l
->data
;
7637 if(purple_strequal(method
->id
, id
)) {
7638 pidgin_blist_sort_methods
= g_list_delete_link(pidgin_blist_sort_methods
, l
);
7640 g_free(method
->name
);
7646 pidgin_blist_update_sort_methods();
7649 void pidgin_blist_sort_method_set(const char *id
){
7650 GList
*l
= pidgin_blist_sort_methods
;
7655 while (l
&& !purple_strequal(((struct pidgin_blist_sort_method
*)l
->data
)->id
, id
))
7659 current_sort_method
= l
->data
;
7660 } else if (!current_sort_method
) {
7661 pidgin_blist_sort_method_set("none");
7664 if (purple_strequal(id
, "none")) {
7665 redo_buddy_list(purple_get_blist(), TRUE
, FALSE
);
7667 redo_buddy_list(purple_get_blist(), FALSE
, FALSE
);
7671 /******************************************
7673 ******************************************/
7675 static void sort_method_none(PurpleBlistNode
*node
, PurpleBuddyList
*blist
, GtkTreeIter parent_iter
, GtkTreeIter
*cur
, GtkTreeIter
*iter
)
7677 PurpleBlistNode
*sibling
= node
->prev
;
7678 GtkTreeIter sibling_iter
;
7685 while (sibling
&& !get_iter_from_node(sibling
, &sibling_iter
)) {
7686 sibling
= sibling
->prev
;
7689 gtk_tree_store_insert_after(gtkblist
->treemodel
, iter
,
7690 node
->parent
? &parent_iter
: NULL
,
7691 sibling
? &sibling_iter
: NULL
);
7694 static void sort_method_alphabetical(PurpleBlistNode
*node
, PurpleBuddyList
*blist
, GtkTreeIter groupiter
, GtkTreeIter
*cur
, GtkTreeIter
*iter
)
7698 const char *my_name
;
7700 if(PURPLE_BLIST_NODE_IS_CONTACT(node
)) {
7701 my_name
= purple_contact_get_alias((PurpleContact
*)node
);
7702 } else if(PURPLE_BLIST_NODE_IS_CHAT(node
)) {
7703 my_name
= purple_chat_get_name((PurpleChat
*)node
);
7705 sort_method_none(node
, blist
, groupiter
, cur
, iter
);
7709 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(gtkblist
->treemodel
), &more_z
, &groupiter
)) {
7710 gtk_tree_store_insert(gtkblist
->treemodel
, iter
, &groupiter
, 0);
7716 const char *this_name
;
7719 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &more_z
, NODE_COLUMN
, &n
, -1);
7721 if(PURPLE_BLIST_NODE_IS_CONTACT(n
)) {
7722 this_name
= purple_contact_get_alias((PurpleContact
*)n
);
7723 } else if(PURPLE_BLIST_NODE_IS_CHAT(n
)) {
7724 this_name
= purple_chat_get_name((PurpleChat
*)n
);
7729 cmp
= purple_utf8_strcasecmp(my_name
, this_name
);
7731 if(this_name
&& (cmp
< 0 || (cmp
== 0 && node
< n
))) {
7733 gtk_tree_store_move_before(gtkblist
->treemodel
, cur
, &more_z
);
7737 gtk_tree_store_insert_before(gtkblist
->treemodel
, iter
,
7738 &groupiter
, &more_z
);
7742 } while (gtk_tree_model_iter_next (GTK_TREE_MODEL(gtkblist
->treemodel
), &more_z
));
7745 gtk_tree_store_move_before(gtkblist
->treemodel
, cur
, NULL
);
7749 gtk_tree_store_append(gtkblist
->treemodel
, iter
, &groupiter
);
7754 static void sort_method_status(PurpleBlistNode
*node
, PurpleBuddyList
*blist
, GtkTreeIter groupiter
, GtkTreeIter
*cur
, GtkTreeIter
*iter
)
7758 PurpleBuddy
*my_buddy
, *this_buddy
;
7760 if(PURPLE_BLIST_NODE_IS_CONTACT(node
)) {
7761 my_buddy
= purple_contact_get_priority_buddy((PurpleContact
*)node
);
7762 } else if(PURPLE_BLIST_NODE_IS_CHAT(node
)) {
7768 gtk_tree_store_append(gtkblist
->treemodel
, iter
, &groupiter
);
7771 sort_method_alphabetical(node
, blist
, groupiter
, cur
, iter
);
7776 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(gtkblist
->treemodel
), &more_z
, &groupiter
)) {
7777 gtk_tree_store_insert(gtkblist
->treemodel
, iter
, &groupiter
, 0);
7786 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &more_z
, NODE_COLUMN
, &n
, -1);
7788 if(PURPLE_BLIST_NODE_IS_CONTACT(n
)) {
7789 this_buddy
= purple_contact_get_priority_buddy((PurpleContact
*)n
);
7794 name_cmp
= purple_utf8_strcasecmp(
7795 purple_contact_get_alias(purple_buddy_get_contact(my_buddy
)),
7797 ? purple_contact_get_alias(purple_buddy_get_contact(this_buddy
))
7800 presence_cmp
= purple_presence_compare(
7801 purple_buddy_get_presence(my_buddy
),
7802 this_buddy
? purple_buddy_get_presence(this_buddy
) : NULL
);
7804 if (this_buddy
== NULL
||
7805 (presence_cmp
< 0 ||
7806 (presence_cmp
== 0 &&
7807 (name_cmp
< 0 || (name_cmp
== 0 && node
< n
)))))
7811 gtk_tree_store_move_before(gtkblist
->treemodel
, cur
, &more_z
);
7817 gtk_tree_store_insert_before(gtkblist
->treemodel
, iter
,
7818 &groupiter
, &more_z
);
7823 while (gtk_tree_model_iter_next(GTK_TREE_MODEL(gtkblist
->treemodel
),
7827 gtk_tree_store_move_before(gtkblist
->treemodel
, cur
, NULL
);
7831 gtk_tree_store_append(gtkblist
->treemodel
, iter
, &groupiter
);
7836 static void sort_method_log_activity(PurpleBlistNode
*node
, PurpleBuddyList
*blist
, GtkTreeIter groupiter
, GtkTreeIter
*cur
, GtkTreeIter
*iter
)
7840 int activity_score
= 0, this_log_activity_score
= 0;
7841 const char *buddy_name
, *this_buddy_name
;
7843 if(cur
&& (gtk_tree_model_iter_n_children(GTK_TREE_MODEL(gtkblist
->treemodel
), &groupiter
) == 1)) {
7848 if(PURPLE_BLIST_NODE_IS_CONTACT(node
)) {
7851 for (n
= node
->child
; n
; n
= n
->next
) {
7852 buddy
= (PurpleBuddy
*)n
;
7853 activity_score
+= purple_log_get_activity_score(PURPLE_LOG_IM
, buddy
->name
, buddy
->account
);
7855 buddy_name
= purple_contact_get_alias((PurpleContact
*)node
);
7856 } else if(PURPLE_BLIST_NODE_IS_CHAT(node
)) {
7857 /* we don't have a reliable way of getting the log filename
7858 * from the chat info in the blist, yet */
7864 gtk_tree_store_append(gtkblist
->treemodel
, iter
, &groupiter
);
7867 sort_method_none(node
, blist
, groupiter
, cur
, iter
);
7872 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(gtkblist
->treemodel
), &more_z
, &groupiter
)) {
7873 gtk_tree_store_insert(gtkblist
->treemodel
, iter
, &groupiter
, 0);
7879 PurpleBlistNode
*n2
;
7883 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &more_z
, NODE_COLUMN
, &n
, -1);
7884 this_log_activity_score
= 0;
7886 if(PURPLE_BLIST_NODE_IS_CONTACT(n
)) {
7887 for (n2
= n
->child
; n2
; n2
= n2
->next
) {
7888 buddy
= (PurpleBuddy
*)n2
;
7889 this_log_activity_score
+= purple_log_get_activity_score(PURPLE_LOG_IM
, buddy
->name
, buddy
->account
);
7891 this_buddy_name
= purple_contact_get_alias((PurpleContact
*)n
);
7893 this_buddy_name
= NULL
;
7896 cmp
= purple_utf8_strcasecmp(buddy_name
, this_buddy_name
);
7898 if (!PURPLE_BLIST_NODE_IS_CONTACT(n
) || activity_score
> this_log_activity_score
||
7899 ((activity_score
== this_log_activity_score
) &&
7900 (cmp
< 0 || (cmp
== 0 && node
< n
)))) {
7902 gtk_tree_store_move_before(gtkblist
->treemodel
, cur
, &more_z
);
7906 gtk_tree_store_insert_before(gtkblist
->treemodel
, iter
,
7907 &groupiter
, &more_z
);
7911 } while (gtk_tree_model_iter_next (GTK_TREE_MODEL(gtkblist
->treemodel
), &more_z
));
7914 gtk_tree_store_move_before(gtkblist
->treemodel
, cur
, NULL
);
7918 gtk_tree_store_append(gtkblist
->treemodel
, iter
, &groupiter
);
7924 plugin_act(GtkObject
*obj
, PurplePluginAction
*pam
)
7926 if (pam
&& pam
->callback
)
7931 build_plugin_actions(GtkWidget
*menu
, PurplePlugin
*plugin
,
7934 GtkWidget
*menuitem
;
7935 PurplePluginAction
*action
= NULL
;
7938 actions
= PURPLE_PLUGIN_ACTIONS(plugin
, context
);
7940 for (l
= actions
; l
!= NULL
; l
= l
->next
)
7944 action
= (PurplePluginAction
*) l
->data
;
7945 action
->plugin
= plugin
;
7946 action
->context
= context
;
7948 menuitem
= gtk_menu_item_new_with_label(action
->label
);
7949 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
7951 g_signal_connect(G_OBJECT(menuitem
), "activate",
7952 G_CALLBACK(plugin_act
), action
);
7953 g_object_set_data_full(G_OBJECT(menuitem
), "plugin_action",
7955 (GDestroyNotify
)purple_plugin_action_free
);
7956 gtk_widget_show(menuitem
);
7959 pidgin_separator(menu
);
7962 g_list_free(actions
);
7966 modify_account_cb(GtkWidget
*widget
, gpointer data
)
7968 pidgin_account_dialog_show(PIDGIN_MODIFY_ACCOUNT_DIALOG
, data
);
7972 enable_account_cb(GtkCheckMenuItem
*widget
, gpointer data
)
7974 PurpleAccount
*account
= data
;
7975 const PurpleSavedStatus
*saved_status
;
7977 saved_status
= purple_savedstatus_get_current();
7978 purple_savedstatus_activate_for_account(saved_status
, account
);
7980 purple_account_set_enabled(account
, PIDGIN_UI
, TRUE
);
7984 disable_account_cb(GtkCheckMenuItem
*widget
, gpointer data
)
7986 PurpleAccount
*account
= data
;
7988 purple_account_set_enabled(account
, PIDGIN_UI
, FALSE
);
7994 pidgin_blist_update_accounts_menu(void)
7996 GtkWidget
*menuitem
= NULL
, *submenu
= NULL
;
7997 GtkAccelGroup
*accel_group
= NULL
;
7998 GList
*l
= NULL
, *accounts
= NULL
;
7999 gboolean disabled_accounts
= FALSE
;
8000 gboolean enabled_accounts
= FALSE
;
8002 if (accountmenu
== NULL
)
8005 /* Clear the old Accounts menu */
8006 for (l
= gtk_container_get_children(GTK_CONTAINER(accountmenu
)); l
; l
= g_list_delete_link(l
, l
)) {
8009 if (menuitem
!= gtk_item_factory_get_widget(gtkblist
->ift
, N_("/Accounts/Manage Accounts")))
8010 gtk_widget_destroy(menuitem
);
8013 for (accounts
= purple_accounts_get_all(); accounts
; accounts
= accounts
->next
) {
8015 GtkWidget
*image
= NULL
;
8016 PurpleAccount
*account
= NULL
;
8017 GdkPixbuf
*pixbuf
= NULL
;
8019 account
= accounts
->data
;
8021 if(!purple_account_get_enabled(account
, PIDGIN_UI
)) {
8022 if (!disabled_accounts
) {
8023 menuitem
= gtk_menu_item_new_with_label(_("Enable Account"));
8024 gtk_menu_shell_append(GTK_MENU_SHELL(accountmenu
), menuitem
);
8026 submenu
= gtk_menu_new();
8027 gtk_menu_set_accel_group(GTK_MENU(submenu
), accel_group
);
8028 gtk_menu_set_accel_path(GTK_MENU(submenu
), N_("<PurpleMain>/Accounts/Enable Account"));
8029 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem
), submenu
);
8031 disabled_accounts
= TRUE
;
8034 buf
= g_strconcat(purple_account_get_username(account
), " (",
8035 purple_account_get_protocol_name(account
), ")", NULL
);
8036 menuitem
= gtk_image_menu_item_new_with_label(buf
);
8038 pixbuf
= pidgin_create_prpl_icon(account
, PIDGIN_PRPL_ICON_SMALL
);
8041 if (!purple_account_is_connected(account
))
8042 gdk_pixbuf_saturate_and_pixelate(pixbuf
, pixbuf
, 0.0, FALSE
);
8043 image
= gtk_image_new_from_pixbuf(pixbuf
);
8044 g_object_unref(G_OBJECT(pixbuf
));
8045 gtk_widget_show(image
);
8046 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem
), image
);
8048 g_signal_connect(G_OBJECT(menuitem
), "activate",
8049 G_CALLBACK(enable_account_cb
), account
);
8050 gtk_menu_shell_append(GTK_MENU_SHELL(submenu
), menuitem
);
8052 enabled_accounts
= TRUE
;
8056 if (!enabled_accounts
) {
8057 gtk_widget_show_all(accountmenu
);
8061 pidgin_separator(accountmenu
);
8062 accel_group
= gtk_menu_get_accel_group(GTK_MENU(accountmenu
));
8064 for (accounts
= purple_accounts_get_all(); accounts
; accounts
= accounts
->next
) {
8066 char *accel_path_buf
= NULL
;
8067 GtkWidget
*image
= NULL
;
8068 PurpleConnection
*gc
= NULL
;
8069 PurpleAccount
*account
= NULL
;
8070 GdkPixbuf
*pixbuf
= NULL
;
8071 PurplePlugin
*plugin
= NULL
;
8072 PurplePluginProtocolInfo
*prpl_info
;
8074 account
= accounts
->data
;
8076 if (!purple_account_get_enabled(account
, PIDGIN_UI
))
8079 buf
= g_strconcat(purple_account_get_username(account
), " (",
8080 purple_account_get_protocol_name(account
), ")", NULL
);
8081 menuitem
= gtk_image_menu_item_new_with_label(buf
);
8082 accel_path_buf
= g_strconcat(N_("<PurpleMain>/Accounts/"), buf
, NULL
);
8084 pixbuf
= pidgin_create_prpl_icon(account
, PIDGIN_PRPL_ICON_SMALL
);
8085 if (pixbuf
!= NULL
) {
8086 if (!purple_account_is_connected(account
))
8087 gdk_pixbuf_saturate_and_pixelate(pixbuf
, pixbuf
,
8089 image
= gtk_image_new_from_pixbuf(pixbuf
);
8090 g_object_unref(G_OBJECT(pixbuf
));
8091 gtk_widget_show(image
);
8092 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem
), image
);
8094 gtk_menu_shell_append(GTK_MENU_SHELL(accountmenu
), menuitem
);
8096 submenu
= gtk_menu_new();
8097 gtk_menu_set_accel_group(GTK_MENU(submenu
), accel_group
);
8098 gtk_menu_set_accel_path(GTK_MENU(submenu
), accel_path_buf
);
8099 g_free(accel_path_buf
);
8100 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem
), submenu
);
8103 menuitem
= gtk_menu_item_new_with_mnemonic(_("_Edit Account"));
8104 g_signal_connect(G_OBJECT(menuitem
), "activate",
8105 G_CALLBACK(modify_account_cb
), account
);
8106 gtk_menu_shell_append(GTK_MENU_SHELL(submenu
), menuitem
);
8108 pidgin_separator(submenu
);
8110 gc
= purple_account_get_connection(account
);
8111 plugin
= gc
&& PURPLE_CONNECTION_IS_CONNECTED(gc
) ? gc
->prpl
: NULL
;
8112 prpl_info
= plugin
? PURPLE_PLUGIN_PROTOCOL_INFO(plugin
) : NULL
;
8115 (PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info
, get_moods
) ||
8116 PURPLE_PLUGIN_HAS_ACTIONS(plugin
))) {
8117 if (PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info
, get_moods
) &&
8118 gc
->flags
& PURPLE_CONNECTION_SUPPORT_MOODS
) {
8120 if (purple_account_get_status(account
, "mood")) {
8121 menuitem
= gtk_menu_item_new_with_mnemonic(_("Set _Mood..."));
8122 g_signal_connect(G_OBJECT(menuitem
), "activate",
8123 G_CALLBACK(set_mood_cb
), account
);
8124 gtk_menu_shell_append(GTK_MENU_SHELL(submenu
), menuitem
);
8127 if (PURPLE_PLUGIN_HAS_ACTIONS(plugin
)) {
8128 build_plugin_actions(submenu
, plugin
, gc
);
8131 menuitem
= gtk_menu_item_new_with_label(_("No actions available"));
8132 gtk_menu_shell_append(GTK_MENU_SHELL(submenu
), menuitem
);
8133 gtk_widget_set_sensitive(menuitem
, FALSE
);
8136 pidgin_separator(submenu
);
8138 menuitem
= gtk_menu_item_new_with_mnemonic(_("_Disable"));
8139 g_signal_connect(G_OBJECT(menuitem
), "activate",
8140 G_CALLBACK(disable_account_cb
), account
);
8141 gtk_menu_shell_append(GTK_MENU_SHELL(submenu
), menuitem
);
8143 gtk_widget_show_all(accountmenu
);
8146 static GList
*plugin_submenus
= NULL
;
8149 pidgin_blist_update_plugin_actions(void)
8151 GtkWidget
*menuitem
, *submenu
;
8152 PurplePlugin
*plugin
= NULL
;
8154 GtkAccelGroup
*accel_group
;
8156 GtkWidget
*pluginmenu
= gtk_item_factory_get_widget(gtkblist
->ift
, N_("/Tools"));
8158 g_return_if_fail(pluginmenu
!= NULL
);
8160 /* Remove old plugin action submenus from the Tools menu */
8161 for (l
= plugin_submenus
; l
; l
= l
->next
)
8162 gtk_widget_destroy(GTK_WIDGET(l
->data
));
8163 g_list_free(plugin_submenus
);
8164 plugin_submenus
= NULL
;
8166 accel_group
= gtk_menu_get_accel_group(GTK_MENU(pluginmenu
));
8168 /* Add a submenu for each plugin with custom actions */
8169 for (l
= purple_plugins_get_loaded(); l
; l
= l
->next
) {
8172 plugin
= (PurplePlugin
*) l
->data
;
8174 if (PURPLE_IS_PROTOCOL_PLUGIN(plugin
))
8177 if (!PURPLE_PLUGIN_HAS_ACTIONS(plugin
))
8180 menuitem
= gtk_image_menu_item_new_with_label(_(plugin
->info
->name
));
8181 gtk_menu_shell_append(GTK_MENU_SHELL(pluginmenu
), menuitem
);
8183 plugin_submenus
= g_list_append(plugin_submenus
, menuitem
);
8185 submenu
= gtk_menu_new();
8186 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem
), submenu
);
8188 gtk_menu_set_accel_group(GTK_MENU(submenu
), accel_group
);
8189 path
= g_strdup_printf("%s/Tools/%s", gtkblist
->ift
->path
, plugin
->info
->name
);
8190 gtk_menu_set_accel_path(GTK_MENU(submenu
), path
);
8193 build_plugin_actions(submenu
, plugin
, NULL
);
8195 gtk_widget_show_all(pluginmenu
);
8199 sortmethod_act(GtkCheckMenuItem
*checkmenuitem
, char *id
)
8201 if (gtk_check_menu_item_get_active(checkmenuitem
))
8203 pidgin_set_cursor(gtkblist
->window
, GDK_WATCH
);
8204 /* This is redundant. I think. */
8205 /* pidgin_blist_sort_method_set(id); */
8206 purple_prefs_set_string(PIDGIN_PREFS_ROOT
"/blist/sort_type", id
);
8208 pidgin_clear_cursor(gtkblist
->window
);
8213 pidgin_blist_update_sort_methods(void)
8215 GtkWidget
*menuitem
= NULL
, *activeitem
= NULL
;
8216 PidginBlistSortMethod
*method
= NULL
;
8219 GtkWidget
*sortmenu
;
8220 const char *m
= purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/blist/sort_type");
8222 if ((gtkblist
== NULL
) || (gtkblist
->ift
== NULL
))
8225 g_return_if_fail(m
!= NULL
);
8227 sortmenu
= gtk_item_factory_get_widget(gtkblist
->ift
, N_("/Buddies/Sort Buddies"));
8229 if (sortmenu
== NULL
)
8232 /* Clear the old menu */
8233 for (l
= gtk_container_get_children(GTK_CONTAINER(sortmenu
)); l
; l
= g_list_delete_link(l
, l
)) {
8235 gtk_widget_destroy(GTK_WIDGET(menuitem
));
8238 for (l
= pidgin_blist_sort_methods
; l
; l
= l
->next
) {
8239 method
= (PidginBlistSortMethod
*) l
->data
;
8240 menuitem
= gtk_radio_menu_item_new_with_label(sl
, _(method
->name
));
8241 if (purple_strequal(m
, method
->id
))
8242 activeitem
= menuitem
;
8243 sl
= gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(menuitem
));
8244 gtk_menu_shell_append(GTK_MENU_SHELL(sortmenu
), menuitem
);
8245 g_signal_connect(G_OBJECT(menuitem
), "toggled",
8246 G_CALLBACK(sortmethod_act
), method
->id
);
8247 gtk_widget_show(menuitem
);
8250 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(activeitem
), TRUE
);