3 * Pidgin is the legal property of its developers, whose names are too numerous
4 * to list here. Please refer to the COPYRIGHT file distributed with this
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
26 #include "connection.h"
35 #include "pidginstock.h"
36 #include "theme-loader.h"
37 #include "theme-manager.h"
40 #include "gtkaccount.h"
42 #include "gtkcellrendererexpander.h"
43 #include "gtkcertmgr.h"
46 #include "gtkdialogs.h"
49 #include "gtkmenutray.h"
50 #include "gtkpounce.h"
51 #include "gtkplugin.h"
53 #include "gtkprivacy.h"
54 #include "gtkroomlist.h"
55 #include "gtkstatusbox.h"
56 #include "gtkscrollbook.h"
57 #include "gtksmiley-manager.h"
58 #include "gtkblist-theme.h"
59 #include "gtkblist-theme-loader.h"
61 #include "pidgin/minidialog.h"
62 #include "pidgin/pidgintooltip.h"
64 #include <gdk/gdkkeysyms.h>
68 #include "gtk3compat.h"
72 PurpleAccount
*account
;
75 GtkWidget
*account_menu
;
77 } PidginBlistRequestData
;
81 PidginBlistRequestData rq_data
;
84 GtkWidget
*entry_for_alias
;
85 GtkWidget
*entry_for_invite
;
91 PidginBlistRequestData rq_data
;
92 gchar
*default_chat_name
;
98 PidginChatData chat_data
;
100 GtkWidget
*alias_entry
;
101 GtkWidget
*group_combo
;
103 GtkWidget
*persistent
;
108 /* GBoxed reference count */
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; %NULL otherwise.
119 PidginMiniDialog
*signed_on_elsewhere
;
121 PidginBlistTheme
*current_theme
;
123 guint select_notebook_page_timeout
;
126 } PidginBuddyListPrivate
;
128 #define PIDGIN_BUDDY_LIST_GET_PRIVATE(list) \
129 ((PidginBuddyListPrivate *)((list)->priv))
131 #define PIDGIN_WINDOW_ICONIFIED(x) \
132 (gdk_window_get_state(gtk_widget_get_window(GTK_WIDGET(x))) & \
133 GDK_WINDOW_STATE_ICONIFIED)
135 #define PIDGIN_WINDOW_MAXIMIZED(x) \
136 (gdk_window_get_state(gtk_widget_get_window(GTK_WIDGET(x))) & \
137 GDK_WINDOW_STATE_MAXIMIZED)
139 static GtkWidget
*accountmenu
= NULL
;
141 static guint visibility_manager_count
= 0;
142 static GdkVisibilityState gtk_blist_visibility
= GDK_VISIBILITY_UNOBSCURED
;
143 static gboolean gtk_blist_focused
= FALSE
;
144 static gboolean editing_blist
= FALSE
;
146 static GList
*pidgin_blist_sort_methods
= NULL
;
147 static struct _PidginBlistSortMethod
*current_sort_method
= NULL
;
148 static void sort_method_none(PurpleBlistNode
*node
, PurpleBuddyList
*blist
, GtkTreeIter groupiter
, GtkTreeIter
*cur
, GtkTreeIter
*iter
);
150 static void sort_method_alphabetical(PurpleBlistNode
*node
, PurpleBuddyList
*blist
, GtkTreeIter groupiter
, GtkTreeIter
*cur
, GtkTreeIter
*iter
);
151 static void sort_method_status(PurpleBlistNode
*node
, PurpleBuddyList
*blist
, GtkTreeIter groupiter
, GtkTreeIter
*cur
, GtkTreeIter
*iter
);
152 static void sort_method_log_activity(PurpleBlistNode
*node
, PurpleBuddyList
*blist
, GtkTreeIter groupiter
, GtkTreeIter
*cur
, GtkTreeIter
*iter
);
153 static guint sort_merge_id
;
154 static GtkActionGroup
*sort_action_group
= NULL
;
156 static PidginBuddyList
*gtkblist
= NULL
;
158 static GList
*groups_tree(void);
159 static gboolean
pidgin_blist_refresh_timer(PurpleBuddyList
*list
);
160 static void pidgin_blist_update_buddy(PurpleBuddyList
*list
, PurpleBlistNode
*node
, gboolean status_change
);
161 static void pidgin_blist_selection_changed(GtkTreeSelection
*selection
, gpointer data
);
162 static void pidgin_blist_update(PurpleBuddyList
*list
, PurpleBlistNode
*node
);
163 static void pidgin_blist_update_group(PurpleBuddyList
*list
, PurpleBlistNode
*node
);
164 static void pidgin_blist_update_contact(PurpleBuddyList
*list
, PurpleBlistNode
*node
);
165 static char *pidgin_get_tooltip_text(PurpleBlistNode
*node
, gboolean full
);
166 static gboolean
get_iter_from_node(PurpleBlistNode
*node
, GtkTreeIter
*iter
);
167 static gboolean
buddy_is_displayable(PurpleBuddy
*buddy
);
168 static void redo_buddy_list(PurpleBuddyList
*list
, gboolean remove
, gboolean rerender
);
169 static void pidgin_blist_collapse_contact_cb(GtkWidget
*w
, PurpleBlistNode
*node
);
170 static char *pidgin_get_group_title(PurpleBlistNode
*gnode
, gboolean expanded
);
171 static void pidgin_blist_expand_contact_cb(GtkWidget
*w
, PurpleBlistNode
*node
);
172 static void set_urgent(void);
175 PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE
= 1 << 0, /* Whether there's pending message in a conversation */
176 PIDGIN_BLIST_CHAT_HAS_PENDING_MESSAGE_WITH_NICK
= 1 << 1, /* Whether there's a pending message in a chat that mentions our nick */
177 } PidginBlistNodeFlags
;
179 typedef struct _pidgin_blist_node
{
180 GtkTreeRowReference
*row
;
181 gboolean contact_expanded
;
182 gboolean recent_signonoff
;
183 gint recent_signonoff_timer
;
185 PurpleConversation
*conv
;
186 time_t last_message
; /* timestamp for last displayed message */
187 PidginBlistNodeFlags flags
;
191 /***************************************************
193 ***************************************************/
194 static gboolean
gtk_blist_visibility_cb(GtkWidget
*w
, GdkEventVisibility
*event
, gpointer data
)
196 GdkVisibilityState old_state
= gtk_blist_visibility
;
197 gtk_blist_visibility
= event
->state
;
199 if (gtk_blist_visibility
== GDK_VISIBILITY_FULLY_OBSCURED
&&
200 old_state
!= GDK_VISIBILITY_FULLY_OBSCURED
) {
202 /* no longer fully obscured */
203 pidgin_blist_refresh_timer(purple_blist_get_buddy_list());
206 /* continue to handle event normally */
210 static gboolean
gtk_blist_window_state_cb(GtkWidget
*w
, GdkEventWindowState
*event
, gpointer data
)
212 if(event
->changed_mask
& GDK_WINDOW_STATE_WITHDRAWN
) {
213 if(event
->new_window_state
& GDK_WINDOW_STATE_WITHDRAWN
)
214 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/blist/list_visible", FALSE
);
216 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/blist/list_visible", TRUE
);
217 pidgin_blist_refresh_timer(purple_blist_get_buddy_list());
221 if(event
->changed_mask
& GDK_WINDOW_STATE_MAXIMIZED
) {
222 if(event
->new_window_state
& GDK_WINDOW_STATE_MAXIMIZED
)
223 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/blist/list_maximized", TRUE
);
225 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/blist/list_maximized", FALSE
);
228 /* Refresh gtkblist if un-iconifying */
229 if (event
->changed_mask
& GDK_WINDOW_STATE_ICONIFIED
){
230 if (!(event
->new_window_state
& GDK_WINDOW_STATE_ICONIFIED
))
231 pidgin_blist_refresh_timer(purple_blist_get_buddy_list());
237 static gboolean
gtk_blist_delete_cb(GtkWidget
*w
, GdkEventAny
*event
, gpointer data
)
239 if(visibility_manager_count
)
240 purple_blist_set_visible(FALSE
);
244 /* we handle everything, event should not propogate further */
248 static gboolean
gtk_blist_configure_cb(GtkWidget
*w
, GdkEventConfigure
*event
, gpointer data
)
250 /* unfortunately GdkEventConfigure ignores the window gravity, but *
251 * the only way we have of setting the position doesn't. we have to *
252 * call get_position because it does pay attention to the gravity. *
253 * this is inefficient and I agree it sucks, but it's more likely *
254 * to work correctly. - Robot101 */
257 /* check for visibility because when we aren't visible, this will *
258 * give us bogus (0,0) coordinates. - xOr */
259 if (gtk_widget_get_visible(w
))
260 gtk_window_get_position(GTK_WINDOW(w
), &x
, &y
);
262 return FALSE
; /* carry on normally */
265 /* Workaround for GTK+ bug # 169811 - "configure_event" is fired
266 * when the window is being maximized */
267 if (PIDGIN_WINDOW_MAXIMIZED(w
))
271 /* don't save if nothing changed */
272 if (x
== purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/blist/x") &&
273 y
== purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/blist/y") &&
274 event
->width
== purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/blist/width") &&
275 event
->height
== purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/blist/height")) {
277 return FALSE
; /* carry on normally */
280 /* don't save off-screen positioning */
281 if (x
+ event
->width
< 0 ||
282 y
+ event
->height
< 0 ||
283 x
> gdk_screen_width() ||
284 y
> gdk_screen_height()) {
286 return FALSE
; /* carry on normally */
289 /* ignore changes when maximized */
290 if(purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/list_maximized"))
293 /* store the position */
294 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/blist/x", x
);
295 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/blist/y", y
);
296 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/blist/width", event
->width
);
297 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/blist/height", event
->height
);
299 /* continue to handle event normally */
303 static void gtk_blist_menu_info_cb(GtkWidget
*w
, PurpleBuddy
*b
)
305 PurpleAccount
*account
= purple_buddy_get_account(b
);
307 pidgin_retrieve_user_info(purple_account_get_connection(account
),
308 purple_buddy_get_name(b
));
311 static void gtk_blist_menu_im_cb(GtkWidget
*w
, PurpleBuddy
*b
)
313 pidgin_dialogs_im_with_user(purple_buddy_get_account(b
),
314 purple_buddy_get_name(b
));
318 static void gtk_blist_menu_audio_call_cb(GtkWidget
*w
, PurpleBuddy
*b
)
320 purple_protocol_initiate_media(purple_buddy_get_account(b
),
321 purple_buddy_get_name(b
), PURPLE_MEDIA_AUDIO
);
324 static void gtk_blist_menu_video_call_cb(GtkWidget
*w
, PurpleBuddy
*b
)
326 /* if the buddy supports both audio and video, start a combined call,
327 otherwise start a pure video session */
328 if (purple_protocol_get_media_caps(purple_buddy_get_account(b
),
329 purple_buddy_get_name(b
)) &
330 PURPLE_MEDIA_CAPS_AUDIO_VIDEO
) {
331 purple_protocol_initiate_media(purple_buddy_get_account(b
),
332 purple_buddy_get_name(b
), PURPLE_MEDIA_AUDIO
| PURPLE_MEDIA_VIDEO
);
334 purple_protocol_initiate_media(purple_buddy_get_account(b
),
335 purple_buddy_get_name(b
), PURPLE_MEDIA_VIDEO
);
341 static void gtk_blist_menu_send_file_cb(GtkWidget
*w
, PurpleBuddy
*b
)
343 PurpleAccount
*account
= purple_buddy_get_account(b
);
345 purple_serv_send_file(purple_account_get_connection(account
),
346 purple_buddy_get_name(b
), NULL
);
349 static void gtk_blist_menu_move_to_cb(GtkWidget
*w
, PurpleBlistNode
*node
)
351 PurpleGroup
*group
= g_object_get_data(G_OBJECT(w
), "groupnode");
352 purple_blist_add_contact((PurpleContact
*)node
, group
, NULL
);
356 static void gtk_blist_menu_autojoin_cb(GtkWidget
*w
, PurpleChat
*chat
)
358 purple_blist_node_set_bool(PURPLE_BLIST_NODE(chat
), "gtk-autojoin",
359 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w
)));
362 static void gtk_blist_menu_persistent_cb(GtkWidget
*w
, PurpleChat
*chat
)
364 purple_blist_node_set_bool(PURPLE_BLIST_NODE(chat
), "gtk-persistent",
365 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w
)));
368 static PurpleConversation
*
369 find_conversation_with_buddy(PurpleBuddy
*buddy
)
371 PidginBlistNode
*ui
= purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(buddy
));
373 return ui
->conv
.conv
;
374 return PURPLE_CONVERSATION(purple_conversations_find_im_with_account(
375 purple_buddy_get_name(buddy
), purple_buddy_get_account(buddy
)));
378 static void gtk_blist_join_chat(PurpleChat
*chat
)
380 PurpleAccount
*account
;
381 PurpleConversation
*conv
;
382 PurpleProtocol
*protocol
;
383 GHashTable
*components
;
385 char *chat_name
= NULL
;
387 account
= purple_chat_get_account(chat
);
388 protocol
= purple_protocols_find(purple_account_get_protocol_id(account
));
390 components
= purple_chat_get_components(chat
);
393 chat_name
= purple_protocol_chat_iface_get_name(protocol
, components
);
398 name
= purple_chat_get_name(chat
);
400 conv
= PURPLE_CONVERSATION(purple_conversations_find_chat_with_account(name
,
404 pidgin_conv_attach_to_conversation(conv
);
405 purple_conversation_present(conv
);
408 purple_serv_join_chat(purple_account_get_connection(account
), components
);
412 static void gtk_blist_menu_join_cb(GtkWidget
*w
, PurpleChat
*chat
)
414 gtk_blist_join_chat(chat
);
417 static void gtk_blist_renderer_editing_cancelled_cb(GtkCellRenderer
*renderer
, PurpleBuddyList
*list
)
419 editing_blist
= FALSE
;
420 g_object_set(G_OBJECT(renderer
), "editable", FALSE
, NULL
);
421 pidgin_blist_refresh(list
);
424 static void gtk_blist_renderer_editing_started_cb(GtkCellRenderer
*renderer
,
425 GtkCellEditable
*editable
,
430 GtkTreePath
*path
= NULL
;
431 PurpleBlistNode
*node
;
432 const char *text
= NULL
;
434 path
= gtk_tree_path_new_from_string (path_str
);
435 gtk_tree_model_get_iter (GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, path
);
436 gtk_tree_path_free (path
);
437 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, NODE_COLUMN
, &node
, -1);
439 if (PURPLE_IS_CONTACT(node
))
440 text
= purple_contact_get_alias(PURPLE_CONTACT(node
));
441 else if (PURPLE_IS_BUDDY(node
))
442 text
= purple_buddy_get_alias(PURPLE_BUDDY(node
));
443 else if (PURPLE_IS_GROUP(node
))
444 text
= purple_group_get_name(PURPLE_GROUP(node
));
445 else if (PURPLE_IS_CHAT(node
))
446 text
= purple_chat_get_name(PURPLE_CHAT(node
));
448 g_return_if_reached();
450 if (GTK_IS_ENTRY (editable
)) {
451 GtkEntry
*entry
= GTK_ENTRY (editable
);
452 gtk_entry_set_text(entry
, text
);
454 editing_blist
= TRUE
;
458 gtk_blist_do_personize(GList
*merges
)
460 PurpleBlistNode
*contact
= NULL
;
464 /* First, we find the contact to merge the rest of the buddies into.
465 * This will be the contact with the most buddies in it; ties are broken
466 * by which contact is higher in the list
468 for (tmp
= merges
; tmp
; tmp
= tmp
->next
) {
469 PurpleBlistNode
*node
= tmp
->data
;
473 if (PURPLE_IS_BUDDY(node
))
474 node
= purple_blist_node_get_parent(node
);
476 if (!PURPLE_IS_CONTACT(node
))
479 for (b
= purple_blist_node_get_first_child(node
);
481 b
= purple_blist_node_get_sibling_next(b
))
495 /* Merge all those buddies into this contact */
496 for (tmp
= merges
; tmp
; tmp
= tmp
->next
) {
497 PurpleBlistNode
*node
= tmp
->data
;
498 if (PURPLE_IS_BUDDY(node
))
499 node
= purple_blist_node_get_parent(node
);
504 purple_contact_merge((PurpleContact
*)node
, contact
);
507 /* And show the expanded contact, so the people know what's going on */
508 pidgin_blist_expand_contact_cb(NULL
, contact
);
513 gtk_blist_auto_personize(PurpleBlistNode
*group
, const char *alias
)
515 PurpleBlistNode
*contact
;
516 PurpleBlistNode
*buddy
;
517 GList
*merges
= NULL
;
519 char *a
= g_utf8_casefold(alias
, -1);
521 for (contact
= purple_blist_node_get_first_child(group
);
523 contact
= purple_blist_node_get_sibling_next(contact
)) {
525 if (!PURPLE_IS_CONTACT(contact
))
528 node_alias
= g_utf8_casefold(purple_contact_get_alias((PurpleContact
*)contact
), -1);
529 if (node_alias
&& !g_utf8_collate(node_alias
, a
)) {
530 merges
= g_list_append(merges
, contact
);
537 for (buddy
= purple_blist_node_get_first_child(contact
);
539 buddy
= purple_blist_node_get_sibling_next(buddy
))
541 if (!PURPLE_IS_BUDDY(buddy
))
544 node_alias
= g_utf8_casefold(purple_buddy_get_alias(PURPLE_BUDDY(buddy
)), -1);
545 if (node_alias
&& !g_utf8_collate(node_alias
, a
)) {
546 merges
= g_list_append(merges
, buddy
);
558 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
);
559 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. "
560 "You can separate them again by choosing 'Expand' from the contact's context menu"), 0, NULL
,
561 merges
, 2, _("_Yes"), PURPLE_CALLBACK(gtk_blist_do_personize
), _("_No"), PURPLE_CALLBACK(g_list_free
));
567 static void gtk_blist_renderer_edited_cb(GtkCellRendererText
*text_rend
, char *arg1
,
568 char *arg2
, PurpleBuddyList
*list
)
572 PurpleBlistNode
*node
;
576 editing_blist
= FALSE
;
577 path
= gtk_tree_path_new_from_string (arg1
);
578 gtk_tree_model_get_iter (GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, path
);
579 gtk_tree_path_free (path
);
580 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, NODE_COLUMN
, &node
, -1);
581 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(gtkblist
->treeview
), TRUE
);
582 g_object_set(G_OBJECT(gtkblist
->text_rend
), "editable", FALSE
, NULL
);
584 if (PURPLE_IS_CONTACT(node
)) {
585 PurpleContact
*contact
= PURPLE_CONTACT(node
);
586 struct _pidgin_blist_node
*gtknode
=
587 (struct _pidgin_blist_node
*)purple_blist_node_get_ui_data(node
);
590 * Using purple_contact_get_alias here breaks because we
591 * specifically want to check the contact alias only (i.e. not
592 * the priority buddy, which purple_contact_get_alias does).
593 * The "alias" GObject property gives us just the alias.
595 g_object_get(contact
, "alias", &alias
, NULL
);
597 if (alias
|| gtknode
->contact_expanded
) {
598 purple_contact_set_alias(contact
, arg2
);
599 gtk_blist_auto_personize(purple_blist_node_get_parent(node
), arg2
);
601 PurpleBuddy
*buddy
= purple_contact_get_priority_buddy(contact
);
602 purple_buddy_set_local_alias(buddy
, arg2
);
603 purple_serv_alias_buddy(buddy
);
604 gtk_blist_auto_personize(purple_blist_node_get_parent(node
), arg2
);
606 } else if (PURPLE_IS_BUDDY(node
)) {
607 PurpleGroup
*group
= purple_buddy_get_group(PURPLE_BUDDY(node
));
609 purple_buddy_set_local_alias(PURPLE_BUDDY(node
), arg2
);
610 purple_serv_alias_buddy(PURPLE_BUDDY(node
));
611 gtk_blist_auto_personize(PURPLE_BLIST_NODE(group
), arg2
);
612 } else if (PURPLE_IS_GROUP(node
)) {
613 dest
= purple_blist_find_group(arg2
);
614 if (dest
!= NULL
&& purple_utf8_strcasecmp(arg2
, purple_group_get_name(PURPLE_GROUP(node
)))) {
615 pidgin_dialogs_merge_groups(PURPLE_GROUP(node
), arg2
);
617 purple_group_set_name(PURPLE_GROUP(node
), arg2
);
619 } else if (PURPLE_IS_CHAT(node
)) {
620 purple_chat_set_alias(PURPLE_CHAT(node
), arg2
);
624 pidgin_blist_refresh(list
);
628 chat_components_edit_ok(PurpleChat
*chat
, PurpleRequestFields
*allfields
)
630 GList
*groups
, *fields
;
632 for (groups
= purple_request_fields_get_groups(allfields
); groups
; groups
= groups
->next
) {
633 fields
= purple_request_field_group_get_fields(groups
->data
);
634 for (; fields
; fields
= fields
->next
) {
635 PurpleRequestField
*field
= fields
->data
;
639 id
= purple_request_field_get_id(field
);
640 if (purple_request_field_get_field_type(field
) == PURPLE_REQUEST_FIELD_INTEGER
)
641 val
= g_strdup_printf("%d", purple_request_field_int_get_value(field
));
643 val
= g_strdup(purple_request_field_string_get_value(field
));
646 g_hash_table_remove(purple_chat_get_components(chat
), id
);
648 g_hash_table_replace(purple_chat_get_components(chat
), g_strdup(id
), val
); /* val should not be free'd */
654 static void chat_components_edit(GtkWidget
*w
, PurpleBlistNode
*node
)
656 PurpleRequestFields
*fields
= purple_request_fields_new();
657 PurpleRequestFieldGroup
*group
= purple_request_field_group_new(NULL
);
658 PurpleRequestField
*field
;
660 PurpleProtocolChatEntry
*pce
;
661 PurpleConnection
*gc
;
662 PurpleChat
*chat
= (PurpleChat
*)node
;
664 purple_request_fields_add_group(fields
, group
);
666 gc
= purple_account_get_connection(purple_chat_get_account(chat
));
667 parts
= purple_protocol_chat_iface_info(purple_connection_get_protocol(gc
), gc
);
669 for (iter
= parts
; iter
; iter
= iter
->next
) {
673 const char *str
= g_hash_table_lookup(purple_chat_get_components(chat
), pce
->identifier
);
674 if (!str
|| sscanf(str
, "%d", &val
) != 1)
676 field
= purple_request_field_int_new(pce
->identifier
, pce
->label
, val
, INT_MIN
, INT_MAX
);
678 field
= purple_request_field_string_new(pce
->identifier
, pce
->label
,
679 g_hash_table_lookup(purple_chat_get_components(chat
), pce
->identifier
), FALSE
);
681 purple_request_field_string_set_masked(field
, TRUE
);
685 purple_request_field_set_required(field
, TRUE
);
687 purple_request_field_group_add_field(group
, field
);
693 purple_request_fields(NULL
, _("Edit Chat"), NULL
, _("Please update the necessary fields."),
694 fields
, _("Save"), G_CALLBACK(chat_components_edit_ok
), _("Cancel"), NULL
,
698 static void gtk_blist_menu_alias_cb(GtkWidget
*w
, PurpleBlistNode
*node
)
703 if (!(get_iter_from_node(node
, &iter
))) {
704 /* This is either a bug, or the buddy is in a collapsed contact */
705 node
= purple_blist_node_get_parent(node
);
706 if (!get_iter_from_node(node
, &iter
))
707 /* Now it's definitely a bug */
711 pidgin_blist_tooltip_destroy();
713 path
= gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
);
714 g_object_set(G_OBJECT(gtkblist
->text_rend
), "editable", TRUE
, NULL
);
715 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(gtkblist
->treeview
), FALSE
);
716 gtk_widget_grab_focus(gtkblist
->treeview
);
717 gtk_tree_view_set_cursor_on_cell(GTK_TREE_VIEW(gtkblist
->treeview
), path
,
718 gtkblist
->text_column
, gtkblist
->text_rend
, TRUE
);
719 gtk_tree_path_free(path
);
722 static void gtk_blist_menu_bp_cb(GtkWidget
*w
, PurpleBuddy
*b
)
724 pidgin_pounce_editor_show(purple_buddy_get_account(b
),
725 purple_buddy_get_name(b
), NULL
);
728 static void gtk_blist_menu_showlog_cb(GtkWidget
*w
, PurpleBlistNode
*node
)
731 PurpleAccount
*account
;
734 pidgin_set_cursor(gtkblist
->window
, GDK_WATCH
);
736 if (PURPLE_IS_BUDDY(node
)) {
737 PurpleBuddy
*b
= (PurpleBuddy
*) node
;
738 type
= PURPLE_LOG_IM
;
739 name
= g_strdup(purple_buddy_get_name(b
));
740 account
= purple_buddy_get_account(b
);
741 } else if (PURPLE_IS_CHAT(node
)) {
742 PurpleChat
*c
= PURPLE_CHAT(node
);
743 PurpleProtocol
*protocol
= NULL
;
744 type
= PURPLE_LOG_CHAT
;
745 account
= purple_chat_get_account(c
);
746 protocol
= purple_protocols_find(purple_account_get_protocol_id(account
));
748 name
= purple_protocol_chat_iface_get_name(protocol
, purple_chat_get_components(c
));
750 } else if (PURPLE_IS_CONTACT(node
)) {
751 pidgin_log_show_contact(PURPLE_CONTACT(node
));
752 pidgin_clear_cursor(gtkblist
->window
);
755 pidgin_clear_cursor(gtkblist
->window
);
757 /* This callback should not have been registered for a node
758 * that doesn't match the type of one of the blocks above. */
759 g_return_if_reached();
762 if (name
&& account
) {
763 pidgin_log_show(type
, name
, account
);
764 pidgin_clear_cursor(gtkblist
->window
);
770 static void gtk_blist_menu_showoffline_cb(GtkWidget
*w
, PurpleBlistNode
*node
)
772 if (PURPLE_IS_BUDDY(node
))
774 purple_blist_node_set_bool(node
, "show_offline",
775 !purple_blist_node_get_bool(node
, "show_offline"));
776 pidgin_blist_update(purple_blist_get_buddy_list(), node
);
778 else if (PURPLE_IS_CONTACT(node
))
780 PurpleBlistNode
*bnode
;
781 gboolean setting
= !purple_blist_node_get_bool(node
, "show_offline");
783 purple_blist_node_set_bool(node
, "show_offline", setting
);
784 for (bnode
= purple_blist_node_get_first_child(node
);
786 bnode
= purple_blist_node_get_sibling_next(bnode
))
788 purple_blist_node_set_bool(bnode
, "show_offline", setting
);
789 pidgin_blist_update(purple_blist_get_buddy_list(), bnode
);
791 } else if (PURPLE_IS_GROUP(node
)) {
792 PurpleBlistNode
*cnode
, *bnode
;
793 gboolean setting
= !purple_blist_node_get_bool(node
, "show_offline");
795 purple_blist_node_set_bool(node
, "show_offline", setting
);
796 for (cnode
= purple_blist_node_get_first_child(node
);
798 cnode
= purple_blist_node_get_sibling_next(cnode
))
800 purple_blist_node_set_bool(cnode
, "show_offline", setting
);
801 for (bnode
= purple_blist_node_get_first_child(cnode
);
803 bnode
= purple_blist_node_get_sibling_next(bnode
))
805 purple_blist_node_set_bool(bnode
, "show_offline", setting
);
806 pidgin_blist_update(purple_blist_get_buddy_list(), bnode
);
812 static void gtk_blist_show_systemlog_cb(void)
814 pidgin_syslog_show();
817 static void gtk_blist_show_onlinehelp_cb(void)
819 purple_notify_uri(NULL
, PURPLE_WEBSITE
"documentation");
823 do_join_chat(PidginChatData
*data
)
827 GHashTable
*components
=
828 g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, g_free
);
832 for (tmp
= data
->entries
; tmp
!= NULL
; tmp
= tmp
->next
)
834 if (g_object_get_data(tmp
->data
, "is_spin"))
836 g_hash_table_replace(components
,
837 g_strdup(g_object_get_data(tmp
->data
, "identifier")),
838 g_strdup_printf("%d",
839 gtk_spin_button_get_value_as_int(tmp
->data
)));
843 g_hash_table_replace(components
,
844 g_strdup(g_object_get_data(tmp
->data
, "identifier")),
845 g_strdup(gtk_entry_get_text(tmp
->data
)));
849 chat
= purple_chat_new(data
->rq_data
.account
, NULL
, components
);
850 gtk_blist_join_chat(chat
);
851 purple_blist_remove_chat(chat
);
856 do_joinchat(GtkWidget
*dialog
, int id
, PidginChatData
*info
)
860 case GTK_RESPONSE_OK
:
865 pidgin_roomlist_dialog_show_with_account(info
->rq_data
.account
);
871 gtk_widget_destroy(GTK_WIDGET(dialog
));
872 g_list_free(info
->entries
);
877 * Check the values of all the text entry boxes. If any required input
878 * strings are empty then don't allow the user to click on "OK."
881 set_sensitive_if_input_chat_cb(GtkWidget
*entry
, gpointer user_data
)
883 PurpleProtocol
*protocol
;
884 PurpleConnection
*gc
;
885 PidginChatData
*data
;
889 gboolean sensitive
= TRUE
;
893 for (tmp
= data
->entries
; tmp
!= NULL
; tmp
= tmp
->next
)
895 if (!g_object_get_data(tmp
->data
, "is_spin"))
897 required
= GPOINTER_TO_INT(g_object_get_data(tmp
->data
, "required"));
898 text
= gtk_entry_get_text(tmp
->data
);
899 if (required
&& (*text
== '\0'))
904 gtk_dialog_set_response_sensitive(GTK_DIALOG(data
->rq_data
.window
), GTK_RESPONSE_OK
, sensitive
);
906 gc
= purple_account_get_connection(data
->rq_data
.account
);
907 protocol
= (gc
!= NULL
) ? purple_connection_get_protocol(gc
) : NULL
;
908 sensitive
= (protocol
!= NULL
&& PURPLE_PROTOCOL_IMPLEMENTS(protocol
, ROOMLIST_IFACE
, get_list
));
910 gtk_dialog_set_response_sensitive(GTK_DIALOG(data
->rq_data
.window
), 1, sensitive
);
914 set_sensitive_if_input_buddy_cb(GtkWidget
*entry
, gpointer user_data
)
916 PurpleProtocol
*protocol
;
917 PidginAddBuddyData
*data
= user_data
;
920 protocol
= purple_protocols_find(purple_account_get_protocol_id(
921 data
->rq_data
.account
));
922 text
= gtk_entry_get_text(GTK_ENTRY(entry
));
924 gtk_dialog_set_response_sensitive(GTK_DIALOG(data
->rq_data
.window
),
925 GTK_RESPONSE_OK
, purple_validate(protocol
, text
));
929 pidgin_blist_update_privacy_cb(PurpleBuddy
*buddy
)
931 struct _pidgin_blist_node
*ui_data
= purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(buddy
));
932 if (ui_data
== NULL
|| ui_data
->row
== NULL
)
934 pidgin_blist_update_buddy(purple_blist_get_buddy_list(), PURPLE_BLIST_NODE(buddy
), TRUE
);
938 chat_account_filter_func(PurpleAccount
*account
)
940 PurpleConnection
*gc
= purple_account_get_connection(account
);
941 PurpleProtocol
*protocol
= NULL
;
946 protocol
= purple_connection_get_protocol(gc
);
948 return (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT_IFACE
, info
));
952 pidgin_blist_joinchat_is_showable()
955 PurpleConnection
*gc
;
957 for (c
= purple_connections_get_all(); c
!= NULL
; c
= c
->next
) {
960 if (chat_account_filter_func(purple_connection_get_account(gc
)))
968 make_blist_request_dialog(PidginBlistRequestData
*data
, PurpleAccount
*account
,
969 const char *title
, const char *window_role
, const char *label_text
,
970 GCallback callback_func
, PurpleFilterAccountFunc filter_func
,
971 GCallback response_cb
)
977 GtkWindow
*blist_window
;
978 PidginBuddyList
*gtkblist
;
980 data
->account
= account
;
982 img
= gtk_image_new_from_stock(PIDGIN_STOCK_DIALOG_QUESTION
,
983 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_HUGE
));
985 gtkblist
= PIDGIN_BLIST(purple_blist_get_buddy_list());
986 blist_window
= gtkblist
? GTK_WINDOW(gtkblist
->window
) : NULL
;
988 data
->window
= gtk_dialog_new();
989 gtk_window_set_title(GTK_WINDOW(data
->window
), title
);
990 gtk_window_set_transient_for(GTK_WINDOW(data
->window
), blist_window
);
991 gtk_dialog_set_default_response(GTK_DIALOG(data
->window
), GTK_RESPONSE_OK
);
992 gtk_container_set_border_width(GTK_CONTAINER(data
->window
), PIDGIN_HIG_BOX_SPACE
);
993 gtk_window_set_resizable(GTK_WINDOW(data
->window
), FALSE
);
994 gtk_box_set_spacing(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(data
->window
))),
996 gtk_container_set_border_width(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(data
->window
))),
997 PIDGIN_HIG_BOX_SPACE
);
998 gtk_window_set_role(GTK_WINDOW(data
->window
), window_role
);
1000 hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, PIDGIN_HIG_BORDER
);
1001 gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(data
->window
))),
1003 gtk_box_pack_start(GTK_BOX(hbox
), img
, FALSE
, FALSE
, 0);
1004 gtk_widget_set_halign(img
, GTK_ALIGN_START
);
1005 gtk_widget_set_valign(img
, GTK_ALIGN_START
);
1007 vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 5);
1008 gtk_container_add(GTK_CONTAINER(hbox
), vbox
);
1010 label
= gtk_label_new(label_text
);
1012 gtk_widget_set_size_request(label
, 400, -1);
1013 gtk_label_set_line_wrap(GTK_LABEL(label
), TRUE
);
1014 gtk_label_set_xalign(GTK_LABEL(label
), 0);
1015 gtk_label_set_yalign(GTK_LABEL(label
), 0);
1016 gtk_box_pack_start(GTK_BOX(vbox
), label
, FALSE
, FALSE
, 0);
1018 data
->sg
= gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL
);
1020 data
->account_menu
= pidgin_account_option_menu_new(account
, FALSE
,
1021 callback_func
, filter_func
, data
);
1022 pidgin_add_widget_to_vbox(GTK_BOX(vbox
), _("A_ccount"), data
->sg
, data
->account_menu
, TRUE
, NULL
);
1024 data
->vbox
= GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL
, 5));
1025 gtk_container_set_border_width(GTK_CONTAINER(data
->vbox
), 0);
1026 gtk_box_pack_start(GTK_BOX(vbox
), GTK_WIDGET(data
->vbox
), FALSE
, FALSE
, 0);
1028 g_signal_connect(G_OBJECT(data
->window
), "response", response_cb
, data
);
1030 g_object_unref(data
->sg
);
1036 rebuild_chat_entries(PidginChatData
*data
, const char *default_chat_name
)
1038 PurpleConnection
*gc
;
1039 PurpleProtocol
*protocol
;
1040 GList
*list
= NULL
, *tmp
;
1041 GHashTable
*defaults
= NULL
;
1042 PurpleProtocolChatEntry
*pce
;
1043 gboolean focus
= TRUE
;
1045 g_return_if_fail(data
->rq_data
.account
!= NULL
);
1047 gc
= purple_account_get_connection(data
->rq_data
.account
);
1048 protocol
= purple_connection_get_protocol(gc
);
1050 gtk_container_foreach(GTK_CONTAINER(data
->rq_data
.vbox
), (GtkCallback
)gtk_widget_destroy
, NULL
);
1052 g_list_free(data
->entries
);
1053 data
->entries
= NULL
;
1055 list
= purple_protocol_chat_iface_info(protocol
, gc
);
1056 defaults
= purple_protocol_chat_iface_info_defaults(protocol
, gc
, default_chat_name
);
1058 for (tmp
= list
; tmp
; tmp
= tmp
->next
)
1066 GtkAdjustment
*adjust
;
1067 adjust
= GTK_ADJUSTMENT(gtk_adjustment_new(pce
->min
,
1070 input
= gtk_spin_button_new(adjust
, 1, 0);
1071 gtk_widget_set_size_request(input
, 50, -1);
1072 pidgin_add_widget_to_vbox(GTK_BOX(data
->rq_data
.vbox
), pce
->label
, data
->rq_data
.sg
, input
, FALSE
, NULL
);
1077 input
= gtk_entry_new();
1078 gtk_entry_set_activates_default(GTK_ENTRY(input
), TRUE
);
1079 value
= g_hash_table_lookup(defaults
, pce
->identifier
);
1081 gtk_entry_set_text(GTK_ENTRY(input
), value
);
1084 gtk_entry_set_visibility(GTK_ENTRY(input
), FALSE
);
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_chat_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_chat_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 g_return_if_fail(w
!= NULL
);
1119 g_return_if_fail(data
!= NULL
);
1120 g_return_if_fail(account
!= NULL
);
1122 if (strcmp(purple_account_get_protocol_id(data
->rq_data
.account
),
1123 purple_account_get_protocol_id(account
)) == 0)
1125 data
->rq_data
.account
= account
;
1129 data
->rq_data
.account
= account
;
1130 rebuild_chat_entries(data
, data
->default_chat_name
);
1135 pidgin_blist_joinchat_show(void)
1137 PidginChatData
*data
= NULL
;
1139 data
= g_new0(PidginChatData
, 1);
1141 make_blist_request_dialog((PidginBlistRequestData
*)data
, NULL
,
1142 _("Join a Chat"), "join_chat",
1143 _("Please enter the appropriate information about the chat "
1144 "you would like to join.\n"),
1145 G_CALLBACK(chat_select_account_cb
),
1146 chat_account_filter_func
, (GCallback
)do_joinchat
);
1147 gtk_dialog_add_buttons(GTK_DIALOG(data
->rq_data
.window
),
1149 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
1150 PIDGIN_STOCK_CHAT
, GTK_RESPONSE_OK
, NULL
);
1151 gtk_dialog_set_default_response(GTK_DIALOG(data
->rq_data
.window
),
1153 data
->default_chat_name
= NULL
;
1154 data
->rq_data
.account
= pidgin_account_option_menu_get_selected(data
->rq_data
.account_menu
);
1156 rebuild_chat_entries(data
, NULL
);
1158 gtk_widget_show_all(data
->rq_data
.window
);
1161 static void gtk_blist_row_expanded_cb(GtkTreeView
*tv
, GtkTreeIter
*iter
, GtkTreePath
*path
, gpointer user_data
)
1163 PurpleBlistNode
*node
;
1165 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), iter
, NODE_COLUMN
, &node
, -1);
1167 if (PURPLE_IS_GROUP(node
)) {
1170 title
= pidgin_get_group_title(node
, TRUE
);
1172 gtk_tree_store_set(gtkblist
->treemodel
, iter
,
1178 purple_blist_node_set_bool(node
, "collapsed", FALSE
);
1179 pidgin_blist_tooltip_destroy();
1183 static void gtk_blist_row_collapsed_cb(GtkTreeView
*tv
, GtkTreeIter
*iter
, GtkTreePath
*path
, gpointer user_data
)
1185 PurpleBlistNode
*node
;
1187 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), iter
, NODE_COLUMN
, &node
, -1);
1189 if (PURPLE_IS_GROUP(node
)) {
1191 struct _pidgin_blist_node
*gtknode
;
1192 PurpleBlistNode
*cnode
;
1194 title
= pidgin_get_group_title(node
, FALSE
);
1196 gtk_tree_store_set(gtkblist
->treemodel
, iter
,
1202 purple_blist_node_set_bool(node
, "collapsed", TRUE
);
1204 for(cnode
= purple_blist_node_get_first_child(node
); cnode
; cnode
= purple_blist_node_get_sibling_next(cnode
)) {
1205 if (PURPLE_IS_CONTACT(cnode
)) {
1206 gtknode
= purple_blist_node_get_ui_data(cnode
);
1207 if (!gtknode
->contact_expanded
)
1209 gtknode
->contact_expanded
= FALSE
;
1210 pidgin_blist_update_contact(NULL
, cnode
);
1213 pidgin_blist_tooltip_destroy();
1214 } else if(PURPLE_IS_CONTACT(node
)) {
1215 pidgin_blist_collapse_contact_cb(NULL
, node
);
1219 static void gtk_blist_row_activated_cb(GtkTreeView
*tv
, GtkTreePath
*path
, GtkTreeViewColumn
*col
, gpointer data
) {
1220 PurpleBlistNode
*node
;
1223 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, path
);
1224 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, NODE_COLUMN
, &node
, -1);
1226 if(PURPLE_IS_CONTACT(node
) || PURPLE_IS_BUDDY(node
)) {
1229 if(PURPLE_IS_CONTACT(node
))
1230 buddy
= purple_contact_get_priority_buddy((PurpleContact
*)node
);
1232 buddy
= (PurpleBuddy
*)node
;
1234 pidgin_dialogs_im_with_user(purple_buddy_get_account(buddy
), purple_buddy_get_name(buddy
));
1235 } else if (PURPLE_IS_CHAT(node
)) {
1236 gtk_blist_join_chat((PurpleChat
*)node
);
1237 } else if (PURPLE_IS_GROUP(node
)) {
1238 /* if (gtk_tree_view_row_expanded(tv, path))
1239 gtk_tree_view_collapse_row(tv, path);
1241 gtk_tree_view_expand_row(tv,path,FALSE);*/
1245 static void pidgin_blist_add_chat_cb(void)
1247 GtkTreeSelection
*sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist
->treeview
));
1249 PurpleBlistNode
*node
;
1251 if(gtk_tree_selection_get_selected(sel
, NULL
, &iter
)){
1252 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, NODE_COLUMN
, &node
, -1);
1253 if (PURPLE_IS_BUDDY(node
))
1254 purple_blist_request_add_chat(NULL
, purple_buddy_get_group(PURPLE_BUDDY(node
)), NULL
, NULL
);
1255 if (PURPLE_IS_CONTACT(node
) || PURPLE_IS_CHAT(node
))
1256 purple_blist_request_add_chat(NULL
, purple_contact_get_group(PURPLE_CONTACT(node
)), NULL
, NULL
);
1257 else if (PURPLE_IS_GROUP(node
))
1258 purple_blist_request_add_chat(NULL
, (PurpleGroup
*)node
, NULL
, NULL
);
1261 purple_blist_request_add_chat(NULL
, NULL
, NULL
, NULL
);
1265 static void pidgin_blist_add_buddy_cb(void)
1267 GtkTreeSelection
*sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist
->treeview
));
1269 PurpleBlistNode
*node
;
1271 if(gtk_tree_selection_get_selected(sel
, NULL
, &iter
)){
1272 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, NODE_COLUMN
, &node
, -1);
1273 if (PURPLE_IS_BUDDY(node
)) {
1274 PurpleGroup
*group
= purple_buddy_get_group(PURPLE_BUDDY(node
));
1275 purple_blist_request_add_buddy(NULL
, NULL
, purple_group_get_name(group
), NULL
);
1276 } else if (PURPLE_IS_CONTACT(node
) || PURPLE_IS_CHAT(node
)) {
1277 PurpleGroup
*group
= purple_contact_get_group(PURPLE_CONTACT(node
));
1278 purple_blist_request_add_buddy(NULL
, NULL
, purple_group_get_name(group
), NULL
);
1279 } else if (PURPLE_IS_GROUP(node
)) {
1280 purple_blist_request_add_buddy(NULL
, NULL
, purple_group_get_name(PURPLE_GROUP(node
)), NULL
);
1284 purple_blist_request_add_buddy(NULL
, NULL
, NULL
, NULL
);
1289 pidgin_blist_remove_cb (GtkWidget
*w
, PurpleBlistNode
*node
)
1291 if (PURPLE_IS_BUDDY(node
)) {
1292 pidgin_dialogs_remove_buddy((PurpleBuddy
*)node
);
1293 } else if (PURPLE_IS_CHAT(node
)) {
1294 pidgin_dialogs_remove_chat((PurpleChat
*)node
);
1295 } else if (PURPLE_IS_GROUP(node
)) {
1296 pidgin_dialogs_remove_group((PurpleGroup
*)node
);
1297 } else if (PURPLE_IS_CONTACT(node
)) {
1298 pidgin_dialogs_remove_contact((PurpleContact
*)node
);
1303 GtkTreeView
*treeview
;
1305 PurpleBlistNode
*node
;
1309 scroll_to_expanded_cell(gpointer data
)
1311 struct _expand
*ex
= data
;
1312 gtk_tree_view_scroll_to_cell(ex
->treeview
, ex
->path
, NULL
, FALSE
, 0, 0);
1313 pidgin_blist_update_contact(NULL
, ex
->node
);
1315 gtk_tree_path_free(ex
->path
);
1322 pidgin_blist_expand_contact_cb(GtkWidget
*w
, PurpleBlistNode
*node
)
1324 struct _pidgin_blist_node
*gtknode
;
1325 GtkTreeIter iter
, parent
;
1326 PurpleBlistNode
*bnode
;
1329 if(!PURPLE_IS_CONTACT(node
))
1332 gtknode
= purple_blist_node_get_ui_data(node
);
1334 gtknode
->contact_expanded
= TRUE
;
1336 for(bnode
= purple_blist_node_get_first_child(node
); bnode
; bnode
= purple_blist_node_get_sibling_next(bnode
)) {
1337 pidgin_blist_update(NULL
, bnode
);
1340 /* This ensures that the bottom buddy is visible, i.e. not scrolled off the alignment */
1341 if (get_iter_from_node(node
, &parent
)) {
1342 struct _expand
*ex
= g_new0(struct _expand
, 1);
1344 gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, &parent
,
1345 gtk_tree_model_iter_n_children(GTK_TREE_MODEL(gtkblist
->treemodel
), &parent
) -1);
1346 path
= gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
);
1348 /* Let the treeview draw so it knows where to scroll */
1349 ex
->treeview
= GTK_TREE_VIEW(gtkblist
->treeview
);
1351 ex
->node
= purple_blist_node_get_first_child(node
);
1352 g_idle_add(scroll_to_expanded_cell
, ex
);
1357 pidgin_blist_collapse_contact_cb(GtkWidget
*w
, PurpleBlistNode
*node
)
1359 PurpleBlistNode
*bnode
;
1360 struct _pidgin_blist_node
*gtknode
;
1362 if(!PURPLE_IS_CONTACT(node
))
1365 gtknode
= purple_blist_node_get_ui_data(node
);
1367 gtknode
->contact_expanded
= FALSE
;
1369 for(bnode
= purple_blist_node_get_first_child(node
); bnode
; bnode
= purple_blist_node_get_sibling_next(bnode
)) {
1370 pidgin_blist_update(NULL
, bnode
);
1375 toggle_privacy(GtkWidget
*widget
, PurpleBlistNode
*node
)
1378 PurpleAccount
*account
;
1382 if (!PURPLE_IS_BUDDY(node
))
1385 buddy
= (PurpleBuddy
*)node
;
1386 account
= purple_buddy_get_account(buddy
);
1387 name
= purple_buddy_get_name(buddy
);
1389 permitted
= purple_account_privacy_check(account
, name
);
1391 /* XXX: Perhaps ask whether to restore the previous lists where appropirate? */
1394 purple_account_privacy_deny(account
, name
);
1396 purple_account_privacy_allow(account
, name
);
1398 pidgin_blist_update(purple_blist_get_buddy_list(), node
);
1401 void pidgin_append_blist_node_privacy_menu(GtkWidget
*menu
, PurpleBlistNode
*node
)
1403 PurpleBuddy
*buddy
= (PurpleBuddy
*)node
;
1404 PurpleAccount
*account
;
1407 account
= purple_buddy_get_account(buddy
);
1408 permitted
= purple_account_privacy_check(account
, purple_buddy_get_name(buddy
));
1410 pidgin_new_menu_item(menu
, permitted
? _("_Block") : _("Un_block"),
1411 permitted
? PIDGIN_STOCK_TOOLBAR_BLOCK
: PIDGIN_STOCK_TOOLBAR_UNBLOCK
,
1412 G_CALLBACK(toggle_privacy
), node
);
1416 pidgin_append_blist_node_proto_menu(GtkWidget
*menu
, PurpleConnection
*gc
,
1417 PurpleBlistNode
*node
)
1420 PurpleProtocol
*protocol
= purple_connection_get_protocol(gc
);
1422 if(!protocol
|| !PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CLIENT_IFACE
, blist_node_menu
))
1425 for(l
= ll
= purple_protocol_client_iface_blist_node_menu(protocol
, node
); l
; l
= l
->next
) {
1426 PurpleMenuAction
*act
= (PurpleMenuAction
*) l
->data
;
1427 pidgin_append_menu_action(menu
, act
, node
);
1433 pidgin_append_blist_node_extended_menu(GtkWidget
*menu
, PurpleBlistNode
*node
)
1437 for(l
= ll
= purple_blist_node_get_extended_menu(node
); l
; l
= l
->next
) {
1438 PurpleMenuAction
*act
= (PurpleMenuAction
*) l
->data
;
1439 pidgin_append_menu_action(menu
, act
, node
);
1447 pidgin_append_blist_node_move_to_menu(GtkWidget
*menu
, PurpleBlistNode
*node
)
1450 GtkWidget
*menuitem
;
1451 PurpleBlistNode
*group
;
1453 menuitem
= gtk_menu_item_new_with_label(_("Move to"));
1454 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
1455 gtk_widget_show(menuitem
);
1457 submenu
= gtk_menu_new();
1458 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem
), submenu
);
1460 for (group
= purple_blist_get_root(); group
; group
= purple_blist_node_get_sibling_next(group
)) {
1461 if (!PURPLE_IS_GROUP(group
))
1463 if (group
== purple_blist_node_get_parent(node
))
1465 menuitem
= pidgin_new_menu_item(submenu
,
1466 purple_group_get_name((PurpleGroup
*)group
), NULL
,
1467 G_CALLBACK(gtk_blist_menu_move_to_cb
), node
);
1468 g_object_set_data(G_OBJECT(menuitem
), "groupnode", group
);
1470 gtk_widget_show_all(submenu
);
1474 pidgin_blist_make_buddy_menu(GtkWidget
*menu
, PurpleBuddy
*buddy
, gboolean sub
) {
1475 PurpleAccount
*account
= NULL
;
1476 PurpleConnection
*pc
= NULL
;
1477 PurpleProtocol
*protocol
;
1478 PurpleContact
*contact
;
1479 PurpleBlistNode
*node
;
1480 gboolean contact_expanded
= FALSE
;
1482 g_return_if_fail(menu
);
1483 g_return_if_fail(buddy
);
1485 account
= purple_buddy_get_account(buddy
);
1486 pc
= purple_account_get_connection(account
);
1487 protocol
= purple_connection_get_protocol(pc
);
1489 node
= PURPLE_BLIST_NODE(buddy
);
1491 contact
= purple_buddy_get_contact(buddy
);
1493 PidginBlistNode
*node
= purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(contact
));
1494 contact_expanded
= node
->contact_expanded
;
1497 if (protocol
&& PURPLE_PROTOCOL_IMPLEMENTS(protocol
, SERVER_IFACE
, get_info
)) {
1498 pidgin_new_menu_item(menu
, _("Get _Info"), PIDGIN_STOCK_TOOLBAR_USER_INFO
,
1499 G_CALLBACK(gtk_blist_menu_info_cb
), buddy
);
1501 pidgin_new_menu_item(menu
, _("I_M"), PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW
,
1502 G_CALLBACK(gtk_blist_menu_im_cb
), buddy
);
1505 if (protocol
&& PURPLE_PROTOCOL_IMPLEMENTS(protocol
, MEDIA_IFACE
, get_caps
)) {
1506 PurpleAccount
*account
= purple_buddy_get_account(buddy
);
1507 const gchar
*who
= purple_buddy_get_name(buddy
);
1508 PurpleMediaCaps caps
= purple_protocol_get_media_caps(account
, who
);
1509 if (caps
& PURPLE_MEDIA_CAPS_AUDIO
) {
1510 pidgin_new_menu_item(menu
, _("_Audio Call"),
1511 PIDGIN_STOCK_TOOLBAR_AUDIO_CALL
,
1512 G_CALLBACK(gtk_blist_menu_audio_call_cb
), buddy
);
1514 if (caps
& PURPLE_MEDIA_CAPS_AUDIO_VIDEO
) {
1515 pidgin_new_menu_item(menu
, _("Audio/_Video Call"),
1516 PIDGIN_STOCK_TOOLBAR_VIDEO_CALL
,
1517 G_CALLBACK(gtk_blist_menu_video_call_cb
), buddy
);
1518 } else if (caps
& PURPLE_MEDIA_CAPS_VIDEO
) {
1519 pidgin_new_menu_item(menu
, _("_Video Call"),
1520 PIDGIN_STOCK_TOOLBAR_VIDEO_CALL
,
1521 G_CALLBACK(gtk_blist_menu_video_call_cb
), buddy
);
1527 if (protocol
&& PURPLE_PROTOCOL_IMPLEMENTS(protocol
, XFER_IFACE
, send
)) {
1528 if (!PURPLE_PROTOCOL_IMPLEMENTS(protocol
, XFER_IFACE
, can_receive
) ||
1529 purple_protocol_xfer_iface_can_receive(protocol
,
1530 purple_account_get_connection(purple_buddy_get_account(buddy
)), purple_buddy_get_name(buddy
)))
1532 pidgin_new_menu_item(menu
, _("_Send File..."),
1533 PIDGIN_STOCK_TOOLBAR_SEND_FILE
,
1534 G_CALLBACK(gtk_blist_menu_send_file_cb
),
1539 pidgin_new_menu_item(menu
, _("Add Buddy _Pounce..."), NULL
,
1540 G_CALLBACK(gtk_blist_menu_bp_cb
), buddy
);
1542 if (node
->parent
&& node
->parent
->child
->next
&&
1543 !sub
&& !contact_expanded
) {
1544 pidgin_new_menu_item(menu
, _("View _Log"), NULL
,
1545 G_CALLBACK(gtk_blist_menu_showlog_cb
), contact
);
1547 pidgin_new_menu_item(menu
, _("View _Log"), NULL
,
1548 G_CALLBACK(gtk_blist_menu_showlog_cb
), buddy
);
1551 if (!purple_blist_node_is_transient(node
)) {
1552 gboolean show_offline
= purple_blist_node_get_bool(node
, "show_offline");
1553 pidgin_new_menu_item(menu
,
1554 show_offline
? _("Hide When Offline") : _("Show When Offline"),
1555 NULL
, G_CALLBACK(gtk_blist_menu_showoffline_cb
), node
);
1558 pidgin_append_blist_node_proto_menu(menu
, purple_account_get_connection(purple_buddy_get_account(buddy
)), node
);
1559 pidgin_append_blist_node_extended_menu(menu
, node
);
1561 if (!contact_expanded
&& contact
!= NULL
)
1562 pidgin_append_blist_node_move_to_menu(menu
, PURPLE_BLIST_NODE(contact
));
1564 if (node
->parent
&& node
->parent
->child
->next
&&
1565 !sub
&& !contact_expanded
) {
1566 pidgin_separator(menu
);
1567 pidgin_append_blist_node_privacy_menu(menu
, node
);
1568 pidgin_new_menu_item(menu
, _("_Alias..."), PIDGIN_STOCK_ALIAS
,
1569 G_CALLBACK(gtk_blist_menu_alias_cb
), contact
);
1570 pidgin_new_menu_item(menu
, _("_Remove"), GTK_STOCK_REMOVE
,
1571 G_CALLBACK(pidgin_blist_remove_cb
), contact
);
1572 } else if (!sub
|| contact_expanded
) {
1573 pidgin_separator(menu
);
1574 pidgin_append_blist_node_privacy_menu(menu
, node
);
1575 pidgin_new_menu_item(menu
, _("_Alias..."), PIDGIN_STOCK_ALIAS
,
1576 G_CALLBACK(gtk_blist_menu_alias_cb
), buddy
);
1577 pidgin_new_menu_item(menu
, _("_Remove"), GTK_STOCK_REMOVE
,
1578 G_CALLBACK(pidgin_blist_remove_cb
), buddy
);
1583 gtk_blist_key_press_cb(GtkWidget
*tv
, GdkEventKey
*event
, gpointer data
)
1585 PurpleBlistNode
*node
;
1586 GtkTreeIter iter
, parent
;
1587 GtkTreeSelection
*sel
;
1590 sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(tv
));
1591 if(!gtk_tree_selection_get_selected(sel
, NULL
, &iter
))
1594 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, NODE_COLUMN
, &node
, -1);
1596 if(event
->state
& GDK_CONTROL_MASK
&&
1597 (event
->keyval
== 'o' || event
->keyval
== 'O')) {
1600 if(PURPLE_IS_CONTACT(node
)) {
1601 buddy
= purple_contact_get_priority_buddy((PurpleContact
*)node
);
1602 } else if(PURPLE_IS_BUDDY(node
)) {
1603 buddy
= (PurpleBuddy
*)node
;
1608 pidgin_retrieve_user_info(purple_account_get_connection(purple_buddy_get_account(buddy
)), purple_buddy_get_name(buddy
));
1610 switch (event
->keyval
) {
1612 gtk_blist_menu_alias_cb(tv
, node
);
1616 path
= gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
);
1617 if (gtk_tree_view_row_expanded(GTK_TREE_VIEW(tv
), path
)) {
1618 /* Collapse the Group */
1619 gtk_tree_view_collapse_row(GTK_TREE_VIEW(tv
), path
);
1620 gtk_tree_path_free(path
);
1623 /* Select the Parent */
1624 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, path
)) {
1625 if (gtk_tree_model_iter_parent(GTK_TREE_MODEL(gtkblist
->treemodel
), &parent
, &iter
)) {
1626 gtk_tree_path_free(path
);
1627 path
= gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist
->treemodel
), &parent
);
1628 gtk_tree_view_set_cursor(GTK_TREE_VIEW(tv
), path
, NULL
, FALSE
);
1629 gtk_tree_path_free(path
);
1634 gtk_tree_path_free(path
);
1638 path
= gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
);
1639 if (!gtk_tree_view_row_expanded(GTK_TREE_VIEW(tv
), path
)) {
1640 /* Expand the Group */
1641 if (PURPLE_IS_CONTACT(node
)) {
1642 pidgin_blist_expand_contact_cb(NULL
, node
);
1643 gtk_tree_path_free(path
);
1645 } else if (!PURPLE_IS_BUDDY(node
)) {
1646 gtk_tree_view_expand_row(GTK_TREE_VIEW(tv
), path
, FALSE
);
1647 gtk_tree_path_free(path
);
1651 /* Select the First Child */
1652 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
), &parent
, path
)) {
1653 if (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, &parent
, 0)) {
1654 gtk_tree_path_free(path
);
1655 path
= gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
);
1656 gtk_tree_view_set_cursor(GTK_TREE_VIEW(tv
), path
, NULL
, FALSE
);
1657 gtk_tree_path_free(path
);
1662 gtk_tree_path_free(path
);
1671 set_node_custom_icon_cb(const gchar
*filename
, gpointer data
)
1674 PurpleBlistNode
*node
= (PurpleBlistNode
*)data
;
1676 purple_buddy_icons_node_set_custom_icon_from_file(node
,
1682 set_node_custom_icon(GtkWidget
*w
, PurpleBlistNode
*node
)
1684 /* This doesn't keep track of the returned dialog (so that successive
1685 * calls could be made to re-display that dialog). Do we want that? */
1686 GtkWidget
*win
= pidgin_buddy_icon_chooser_new(NULL
, set_node_custom_icon_cb
, node
);
1687 gtk_widget_show_all(win
);
1691 remove_node_custom_icon(GtkWidget
*w
, PurpleBlistNode
*node
)
1693 purple_buddy_icons_node_set_custom_icon(node
, NULL
, 0);
1697 add_buddy_icon_menu_items(GtkWidget
*menu
, PurpleBlistNode
*node
)
1701 pidgin_new_menu_item(menu
, _("Set Custom Icon"), NULL
,
1702 G_CALLBACK(set_node_custom_icon
), node
);
1704 item
= pidgin_new_menu_item(menu
, _("Remove Custom Icon"), NULL
,
1705 G_CALLBACK(remove_node_custom_icon
), node
);
1706 if (!purple_buddy_icons_node_has_custom_icon(node
))
1707 gtk_widget_set_sensitive(item
, FALSE
);
1711 create_group_menu (PurpleBlistNode
*node
, PurpleGroup
*g
)
1716 menu
= gtk_menu_new();
1717 item
= pidgin_new_menu_item(menu
, _("Add _Buddy..."), GTK_STOCK_ADD
,
1718 G_CALLBACK(pidgin_blist_add_buddy_cb
), node
);
1719 gtk_widget_set_sensitive(item
, purple_connections_get_all() != NULL
);
1720 item
= pidgin_new_menu_item(menu
, _("Add C_hat..."), GTK_STOCK_ADD
,
1721 G_CALLBACK(pidgin_blist_add_chat_cb
), node
);
1722 gtk_widget_set_sensitive(item
, pidgin_blist_joinchat_is_showable());
1723 pidgin_new_menu_item(menu
, _("_Delete Group"), GTK_STOCK_REMOVE
,
1724 G_CALLBACK(pidgin_blist_remove_cb
), node
);
1725 pidgin_new_menu_item(menu
, _("_Rename"), NULL
,
1726 G_CALLBACK(gtk_blist_menu_alias_cb
), node
);
1727 if (!purple_blist_node_is_transient(node
)) {
1728 gboolean show_offline
= purple_blist_node_get_bool(node
, "show_offline");
1729 pidgin_new_menu_item(menu
,
1730 show_offline
? _("Hide When Offline") : _("Show When Offline"),
1731 NULL
, G_CALLBACK(gtk_blist_menu_showoffline_cb
), node
);
1734 add_buddy_icon_menu_items(menu
, node
);
1736 pidgin_append_blist_node_extended_menu(menu
, node
);
1742 create_chat_menu(PurpleBlistNode
*node
, PurpleChat
*c
)
1745 gboolean autojoin
, persistent
;
1747 menu
= gtk_menu_new();
1748 autojoin
= purple_blist_node_get_bool(node
, "gtk-autojoin");
1749 persistent
= purple_blist_node_get_bool(node
, "gtk-persistent");
1751 pidgin_new_menu_item(menu
, _("_Join"), PIDGIN_STOCK_CHAT
,
1752 G_CALLBACK(gtk_blist_menu_join_cb
), node
);
1753 pidgin_new_check_item(menu
, _("Auto-Join"),
1754 G_CALLBACK(gtk_blist_menu_autojoin_cb
), node
, autojoin
);
1755 pidgin_new_check_item(menu
, _("Persistent"),
1756 G_CALLBACK(gtk_blist_menu_persistent_cb
), node
, persistent
);
1757 pidgin_new_menu_item(menu
, _("View _Log"), NULL
,
1758 G_CALLBACK(gtk_blist_menu_showlog_cb
), node
);
1760 pidgin_append_blist_node_proto_menu(menu
, purple_account_get_connection(purple_chat_get_account(c
)), node
);
1761 pidgin_append_blist_node_extended_menu(menu
, node
);
1763 pidgin_separator(menu
);
1765 pidgin_new_menu_item(menu
, _("_Edit Settings..."), NULL
,
1766 G_CALLBACK(chat_components_edit
), node
);
1767 pidgin_new_menu_item(menu
, _("_Alias..."), PIDGIN_STOCK_ALIAS
,
1768 G_CALLBACK(gtk_blist_menu_alias_cb
), node
);
1769 pidgin_new_menu_item(menu
, _("_Remove"), GTK_STOCK_REMOVE
,
1770 G_CALLBACK(pidgin_blist_remove_cb
), node
);
1772 add_buddy_icon_menu_items(menu
, node
);
1778 create_contact_menu (PurpleBlistNode
*node
)
1782 menu
= gtk_menu_new();
1784 pidgin_new_menu_item(menu
, _("View _Log"), NULL
,
1785 G_CALLBACK(gtk_blist_menu_showlog_cb
),
1788 pidgin_separator(menu
);
1790 pidgin_new_menu_item(menu
, _("_Alias..."), PIDGIN_STOCK_ALIAS
,
1791 G_CALLBACK(gtk_blist_menu_alias_cb
), node
);
1792 pidgin_new_menu_item(menu
, _("_Remove"), GTK_STOCK_REMOVE
,
1793 G_CALLBACK(pidgin_blist_remove_cb
), node
);
1795 add_buddy_icon_menu_items(menu
, node
);
1797 pidgin_separator(menu
);
1799 pidgin_new_menu_item(menu
, _("_Collapse"), GTK_STOCK_ZOOM_OUT
,
1800 G_CALLBACK(pidgin_blist_collapse_contact_cb
),
1803 pidgin_append_blist_node_extended_menu(menu
, node
);
1808 create_buddy_menu(PurpleBlistNode
*node
, PurpleBuddy
*b
)
1810 struct _pidgin_blist_node
*gtknode
= purple_blist_node_get_ui_data(node
);
1812 GtkWidget
*menuitem
;
1813 gboolean show_offline
= purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_offline_buddies");
1815 menu
= gtk_menu_new();
1816 pidgin_blist_make_buddy_menu(menu
, b
, FALSE
);
1818 if(PURPLE_IS_CONTACT(node
)) {
1819 pidgin_separator(menu
);
1821 add_buddy_icon_menu_items(menu
, node
);
1823 if(gtknode
->contact_expanded
) {
1824 pidgin_new_menu_item(menu
, _("_Collapse"),
1826 G_CALLBACK(pidgin_blist_collapse_contact_cb
),
1829 pidgin_new_menu_item(menu
, _("_Expand"),
1831 G_CALLBACK(pidgin_blist_expand_contact_cb
),
1834 if(node
->child
->next
) {
1835 PurpleBlistNode
*bnode
;
1837 for(bnode
= node
->child
; bnode
; bnode
= bnode
->next
) {
1838 PurpleBuddy
*buddy
= (PurpleBuddy
*)bnode
;
1845 if(!purple_account_get_connection(purple_buddy_get_account(buddy
)))
1847 if(!show_offline
&& !PURPLE_BUDDY_IS_ONLINE(buddy
))
1850 menuitem
= gtk_image_menu_item_new_with_label(purple_buddy_get_name(buddy
));
1851 buf
= pidgin_create_protocol_icon(purple_buddy_get_account(buddy
), PIDGIN_PROTOCOL_ICON_SMALL
);
1852 image
= gtk_image_new_from_pixbuf(buf
);
1853 g_object_unref(G_OBJECT(buf
));
1854 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem
),
1856 gtk_widget_show(image
);
1857 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
1858 gtk_widget_show(menuitem
);
1860 submenu
= gtk_menu_new();
1861 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem
), submenu
);
1862 gtk_widget_show(submenu
);
1864 pidgin_blist_make_buddy_menu(submenu
, buddy
, TRUE
);
1872 pidgin_blist_show_context_menu(PurpleBlistNode
*node
,
1873 GtkMenuPositionFunc func
,
1878 struct _pidgin_blist_node
*gtknode
= purple_blist_node_get_ui_data(node
);
1879 GtkWidget
*menu
= NULL
;
1880 gboolean handled
= FALSE
;
1882 /* Create a menu based on the thing we right-clicked on */
1883 if (PURPLE_IS_GROUP(node
)) {
1884 PurpleGroup
*g
= (PurpleGroup
*)node
;
1886 menu
= create_group_menu(node
, g
);
1887 } else if (PURPLE_IS_CHAT(node
)) {
1888 PurpleChat
*c
= (PurpleChat
*)node
;
1890 menu
= create_chat_menu(node
, c
);
1891 } else if ((PURPLE_IS_CONTACT(node
)) && (gtknode
->contact_expanded
)) {
1892 menu
= create_contact_menu(node
);
1893 } else if (PURPLE_IS_CONTACT(node
) || PURPLE_IS_BUDDY(node
)) {
1896 if (PURPLE_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 PurpleProtocol
*protocol
= NULL
;
1934 struct _pidgin_blist_node
*gtknode
;
1935 gboolean handled
= FALSE
;
1937 /* Here we figure out which node was clicked */
1938 if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv
), event
->x
, event
->y
, &path
, NULL
, NULL
, NULL
))
1940 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, path
);
1941 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, NODE_COLUMN
, &node
, -1);
1942 gtknode
= purple_blist_node_get_ui_data(node
);
1944 /* Right click draws a context menu */
1945 if ((event
->button
== 3) && (event
->type
== GDK_BUTTON_PRESS
)) {
1946 handled
= pidgin_blist_show_context_menu(node
, NULL
, tv
, 3, event
->time
);
1948 /* CTRL+middle click expands or collapse a contact */
1949 } else if ((event
->button
== 2) && (event
->type
== GDK_BUTTON_PRESS
) &&
1950 (event
->state
& GDK_CONTROL_MASK
) && (PURPLE_IS_CONTACT(node
))) {
1951 if (gtknode
->contact_expanded
)
1952 pidgin_blist_collapse_contact_cb(NULL
, node
);
1954 pidgin_blist_expand_contact_cb(NULL
, node
);
1957 /* Double middle click gets info */
1958 } else if ((event
->button
== 2) && (event
->type
== GDK_2BUTTON_PRESS
) &&
1959 ((PURPLE_IS_CONTACT(node
)) || (PURPLE_IS_BUDDY(node
)))) {
1961 if(PURPLE_IS_CONTACT(node
))
1962 b
= purple_contact_get_priority_buddy((PurpleContact
*)node
);
1964 b
= (PurpleBuddy
*)node
;
1966 protocol
= purple_protocols_find(purple_account_get_protocol_id(purple_buddy_get_account(b
)));
1968 if (protocol
&& PURPLE_PROTOCOL_IMPLEMENTS(protocol
, SERVER_IFACE
, get_info
))
1969 pidgin_retrieve_user_info(purple_account_get_connection(purple_buddy_get_account(b
)), purple_buddy_get_name(b
));
1975 * This code only exists because GTK+ doesn't work. If we return
1976 * FALSE here, as would be normal the event propoagates down and
1977 * somehow gets interpreted as the start of a drag event.
1979 * Um, isn't it _normal_ to return TRUE here? Since the event
1980 * was handled? --Mark
1983 sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(tv
));
1984 gtk_tree_selection_select_path(sel
, path
);
1985 gtk_tree_path_free(path
);
1989 gtk_tree_path_free(path
);
1995 pidgin_blist_popup_menu_cb(GtkWidget
*tv
, void *user_data
)
1997 PurpleBlistNode
*node
;
1999 GtkTreeSelection
*sel
;
2000 gboolean handled
= FALSE
;
2002 sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(tv
));
2003 if (!gtk_tree_selection_get_selected(sel
, NULL
, &iter
))
2006 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, NODE_COLUMN
, &node
, -1);
2008 /* Shift+F10 draws a context menu */
2009 handled
= pidgin_blist_show_context_menu(node
, pidgin_treeview_popup_menu_position_func
, tv
, 0, GDK_CURRENT_TIME
);
2014 static void gtk_blist_show_xfer_dialog_cb(GtkAction
*item
, gpointer data
)
2016 pidgin_xfer_dialog_show(NULL
);
2019 static void pidgin_blist_buddy_details_cb(GtkToggleAction
*item
, gpointer data
)
2021 pidgin_set_cursor(gtkblist
->window
, GDK_WATCH
);
2023 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/blist/show_buddy_icons",
2024 gtk_toggle_action_get_active(item
));
2026 pidgin_clear_cursor(gtkblist
->window
);
2029 static void pidgin_blist_show_idle_time_cb(GtkToggleAction
*item
, gpointer data
)
2031 pidgin_set_cursor(gtkblist
->window
, GDK_WATCH
);
2033 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/blist/show_idle_time",
2034 gtk_toggle_action_get_active(item
));
2036 pidgin_clear_cursor(gtkblist
->window
);
2039 static void pidgin_blist_show_protocol_icons_cb(GtkToggleAction
*item
, gpointer data
)
2041 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/blist/show_protocol_icons",
2042 gtk_toggle_action_get_active(item
));
2045 static void pidgin_blist_show_empty_groups_cb(GtkToggleAction
*item
, gpointer data
)
2047 pidgin_set_cursor(gtkblist
->window
, GDK_WATCH
);
2049 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/blist/show_empty_groups",
2050 gtk_toggle_action_get_active(item
));
2052 pidgin_clear_cursor(gtkblist
->window
);
2055 static void pidgin_blist_edit_mode_cb(GtkToggleAction
*checkitem
, gpointer data
)
2057 pidgin_set_cursor(gtkblist
->window
, GDK_WATCH
);
2059 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/blist/show_offline_buddies",
2060 gtk_toggle_action_get_active(checkitem
));
2062 pidgin_clear_cursor(gtkblist
->window
);
2065 static void pidgin_blist_mute_sounds_cb(GtkToggleAction
*item
, gpointer data
)
2067 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/sound/mute",
2068 gtk_toggle_action_get_active(item
));
2073 pidgin_blist_mute_pref_cb(const char *name
, PurplePrefType type
,
2074 gconstpointer value
, gpointer data
)
2076 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(gtk_ui_manager_get_action(gtkblist
->ui
,
2077 "/BList/ToolsMenu/MuteSounds")), (gboolean
)GPOINTER_TO_INT(value
));
2081 pidgin_blist_sound_method_pref_cb(const char *name
, PurplePrefType type
,
2082 gconstpointer value
, gpointer data
)
2084 gboolean sensitive
= TRUE
;
2086 if(!strcmp(value
, "none"))
2089 gtk_action_set_sensitive(gtk_ui_manager_get_action(gtkblist
->ui
, "/BList/ToolsMenu/MuteSounds"), sensitive
);
2093 add_buddies_from_vcard(const char *protocol_id
, PurpleGroup
*group
, GList
*list
,
2097 PurpleAccount
*account
= NULL
;
2098 PurpleConnection
*gc
;
2103 for (l
= purple_connections_get_all(); l
!= NULL
; l
= l
->next
)
2105 gc
= (PurpleConnection
*)l
->data
;
2106 account
= purple_connection_get_account(gc
);
2108 if (!strcmp(purple_account_get_protocol_id(account
), protocol_id
))
2114 if (account
!= NULL
)
2116 for (l
= list
; l
!= NULL
; l
= l
->next
)
2118 purple_blist_request_add_buddy(account
, l
->data
,
2119 (group
? purple_group_get_name(group
) : NULL
),
2124 g_list_foreach(list
, (GFunc
)g_free
, NULL
);
2129 parse_vcard(const char *vcard
, PurpleGroup
*group
)
2136 GList
*yahoos
= NULL
;
2138 GList
*jabbers
= NULL
;
2140 s
= temp_vcard
= g_strdup(vcard
);
2142 while (*s
!= '\0' && strncmp(s
, "END:vCard", strlen("END:vCard")))
2144 char *field
, *value
;
2148 /* Grab the field */
2149 while (*s
!= '\r' && *s
!= '\n' && *s
!= '\0' && *s
!= ':')
2152 if (*s
== '\r') s
++;
2159 if (*s
!= '\0') *s
++ = '\0';
2161 if ((c
= strchr(field
, ';')) != NULL
)
2164 /* Proceed to the end of the line */
2167 while (*s
!= '\r' && *s
!= '\n' && *s
!= '\0')
2170 if (*s
== '\r') *s
++ = '\0';
2171 if (*s
== '\n') *s
++ = '\0';
2173 /* We only want to worry about a few fields here. */
2174 if (!strcmp(field
, "FN"))
2175 alias
= g_strdup(value
);
2176 else if (!strcmp(field
, "X-AIM") || !strcmp(field
, "X-ICQ") ||
2177 !strcmp(field
, "X-YAHOO") || !strcmp(field
, "X-MSN") ||
2178 !strcmp(field
, "X-JABBER"))
2180 char **values
= g_strsplit(value
, ":", 0);
2183 for (im
= values
; *im
!= NULL
; im
++)
2185 if (!strcmp(field
, "X-AIM"))
2186 aims
= g_list_append(aims
, g_strdup(*im
));
2187 else if (!strcmp(field
, "X-ICQ"))
2188 icqs
= g_list_append(icqs
, g_strdup(*im
));
2189 else if (!strcmp(field
, "X-YAHOO"))
2190 yahoos
= g_list_append(yahoos
, g_strdup(*im
));
2191 else if (!strcmp(field
, "X-MSN"))
2192 msns
= g_list_append(msns
, g_strdup(*im
));
2193 else if (!strcmp(field
, "X-JABBER"))
2194 jabbers
= g_list_append(jabbers
, g_strdup(*im
));
2203 if (aims
== NULL
&& icqs
== NULL
&& yahoos
== NULL
&&
2204 msns
== NULL
&& jabbers
== NULL
)
2211 add_buddies_from_vcard("prpl-aim", group
, aims
, alias
);
2212 add_buddies_from_vcard("prpl-icq", group
, icqs
, alias
);
2213 add_buddies_from_vcard("prpl-yahoo", group
, yahoos
, alias
);
2214 add_buddies_from_vcard("prpl-msn", group
, msns
, alias
);
2215 add_buddies_from_vcard("prpl-jabber", group
, jabbers
, alias
);
2223 static void pidgin_blist_drag_begin(GtkWidget
*widget
,
2224 GdkDragContext
*drag_context
, gpointer user_data
)
2226 pidgin_blist_tooltip_destroy();
2229 /* Unhook the tooltip-timeout since we don't want a tooltip
2230 * to appear and obscure the dragging operation.
2231 * This is a workaround for GTK+ bug 107320. */
2232 if (gtkblist
->timeout
) {
2233 g_source_remove(gtkblist
->timeout
);
2234 gtkblist
->timeout
= 0;
2239 static void pidgin_blist_drag_data_get_cb(GtkWidget
*widget
,
2241 GtkSelectionData
*data
,
2246 GdkAtom target
= gtk_selection_data_get_target(data
);
2248 if (target
== gdk_atom_intern("PURPLE_BLIST_NODE", FALSE
)) {
2249 GtkTreeRowReference
*ref
= g_object_get_data(G_OBJECT(dc
), "gtk-tree-view-source-row");
2250 GtkTreePath
*sourcerow
= gtk_tree_row_reference_get_path(ref
);
2252 PurpleBlistNode
*node
= NULL
;
2255 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, sourcerow
);
2256 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, NODE_COLUMN
, &node
, -1);
2257 gtk_selection_data_set (data
,
2258 gdk_atom_intern ("PURPLE_BLIST_NODE", FALSE
),
2263 gtk_tree_path_free(sourcerow
);
2264 } else if (target
== gdk_atom_intern("application/x-im-contact", FALSE
)) {
2265 GtkTreeRowReference
*ref
;
2266 GtkTreePath
*sourcerow
;
2268 PurpleBlistNode
*node
= NULL
;
2270 PurpleConnection
*gc
;
2272 const char *protocol
;
2274 ref
= g_object_get_data(G_OBJECT(dc
), "gtk-tree-view-source-row");
2275 sourcerow
= gtk_tree_row_reference_get_path(ref
);
2280 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
,
2282 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, NODE_COLUMN
, &node
, -1);
2284 if (PURPLE_IS_CONTACT(node
))
2286 buddy
= purple_contact_get_priority_buddy((PurpleContact
*)node
);
2288 else if (!PURPLE_IS_BUDDY(node
))
2290 gtk_tree_path_free(sourcerow
);
2295 buddy
= (PurpleBuddy
*)node
;
2298 gc
= purple_account_get_connection(purple_buddy_get_account(buddy
));
2302 gtk_tree_path_free(sourcerow
);
2307 purple_protocol_class_list_icon(purple_connection_get_protocol(gc
),
2308 purple_buddy_get_account(buddy
), buddy
);
2310 str
= g_string_new(NULL
);
2311 g_string_printf(str
,
2312 "MIME-Version: 1.0\r\n"
2313 "Content-Type: application/x-im-contact\r\n"
2314 "X-IM-Protocol: %s\r\n"
2315 "X-IM-Username: %s\r\n",
2317 purple_buddy_get_name(buddy
));
2319 if (purple_buddy_get_local_alias(buddy
) != NULL
)
2321 g_string_append_printf(str
,
2322 "X-IM-Alias: %s\r\n",
2323 purple_buddy_get_local_alias(buddy
));
2326 g_string_append(str
, "\r\n");
2328 gtk_selection_data_set(data
,
2329 gdk_atom_intern("application/x-im-contact", FALSE
),
2331 (const guchar
*)str
->str
,
2332 strlen(str
->str
) + 1);
2334 g_string_free(str
, TRUE
);
2335 gtk_tree_path_free(sourcerow
);
2339 static void pidgin_blist_drag_data_rcv_cb(GtkWidget
*widget
, GdkDragContext
*dc
, guint x
, guint y
,
2340 GtkSelectionData
*sd
, guint info
, guint t
)
2342 GdkAtom target
= gtk_selection_data_get_target(sd
);
2343 const guchar
*data
= gtk_selection_data_get_data(sd
);
2345 if (gtkblist
->drag_timeout
) {
2346 g_source_remove(gtkblist
->drag_timeout
);
2347 gtkblist
->drag_timeout
= 0;
2350 if (target
== gdk_atom_intern("PURPLE_BLIST_NODE", FALSE
) && data
) {
2351 PurpleBlistNode
*n
= NULL
;
2352 GtkTreePath
*path
= NULL
;
2353 GtkTreeViewDropPosition position
;
2354 memcpy(&n
, data
, sizeof(n
));
2355 if(gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget
), x
, y
, &path
, &position
)) {
2356 /* if we're here, I think it means the drop is ok */
2358 PurpleBlistNode
*node
;
2359 struct _pidgin_blist_node
*gtknode
;
2361 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
),
2363 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
),
2364 &iter
, NODE_COLUMN
, &node
, -1);
2365 gtknode
= purple_blist_node_get_ui_data(node
);
2367 if (PURPLE_IS_CONTACT(n
)) {
2368 PurpleContact
*c
= (PurpleContact
*)n
;
2369 if (PURPLE_IS_CONTACT(node
) && gtknode
->contact_expanded
) {
2370 purple_contact_merge(c
, node
);
2371 } else if (PURPLE_IS_CONTACT(node
) ||
2372 PURPLE_IS_CHAT(node
)) {
2374 case GTK_TREE_VIEW_DROP_AFTER
:
2375 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER
:
2376 purple_blist_add_contact(c
, (PurpleGroup
*)node
->parent
,
2379 case GTK_TREE_VIEW_DROP_BEFORE
:
2380 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE
:
2381 purple_blist_add_contact(c
, (PurpleGroup
*)node
->parent
,
2385 } else if(PURPLE_IS_GROUP(node
)) {
2386 purple_blist_add_contact(c
, (PurpleGroup
*)node
, NULL
);
2387 } else if(PURPLE_IS_BUDDY(node
)) {
2388 purple_contact_merge(c
, node
);
2390 } else if (PURPLE_IS_BUDDY(n
)) {
2391 PurpleBuddy
*b
= (PurpleBuddy
*)n
;
2392 if (PURPLE_IS_BUDDY(node
)) {
2394 case GTK_TREE_VIEW_DROP_AFTER
:
2395 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER
:
2396 purple_blist_add_buddy(b
, (PurpleContact
*)node
->parent
,
2397 (PurpleGroup
*)node
->parent
->parent
, node
);
2399 case GTK_TREE_VIEW_DROP_BEFORE
:
2400 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE
:
2401 purple_blist_add_buddy(b
, (PurpleContact
*)node
->parent
,
2402 (PurpleGroup
*)node
->parent
->parent
,
2406 } else if(PURPLE_IS_CHAT(node
)) {
2407 purple_blist_add_buddy(b
, NULL
, (PurpleGroup
*)node
->parent
,
2409 } else if (PURPLE_IS_GROUP(node
)) {
2410 purple_blist_add_buddy(b
, NULL
, (PurpleGroup
*)node
, NULL
);
2411 } else if (PURPLE_IS_CONTACT(node
)) {
2412 if(gtknode
->contact_expanded
) {
2414 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER
:
2415 case GTK_TREE_VIEW_DROP_AFTER
:
2416 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE
:
2417 purple_blist_add_buddy(b
, (PurpleContact
*)node
,
2418 (PurpleGroup
*)node
->parent
, NULL
);
2420 case GTK_TREE_VIEW_DROP_BEFORE
:
2421 purple_blist_add_buddy(b
, NULL
,
2422 (PurpleGroup
*)node
->parent
, node
->prev
);
2427 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER
:
2428 case GTK_TREE_VIEW_DROP_AFTER
:
2429 purple_blist_add_buddy(b
, NULL
,
2430 (PurpleGroup
*)node
->parent
, NULL
);
2432 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE
:
2433 case GTK_TREE_VIEW_DROP_BEFORE
:
2434 purple_blist_add_buddy(b
, NULL
,
2435 (PurpleGroup
*)node
->parent
, node
->prev
);
2440 } else if (PURPLE_IS_CHAT(n
)) {
2441 PurpleChat
*chat
= (PurpleChat
*)n
;
2442 if (PURPLE_IS_BUDDY(node
)) {
2444 case GTK_TREE_VIEW_DROP_AFTER
:
2445 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER
:
2446 case GTK_TREE_VIEW_DROP_BEFORE
:
2447 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE
:
2448 purple_blist_add_chat(chat
,
2449 (PurpleGroup
*)node
->parent
->parent
,
2453 } else if(PURPLE_IS_CONTACT(node
) ||
2454 PURPLE_IS_CHAT(node
)) {
2456 case GTK_TREE_VIEW_DROP_AFTER
:
2457 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER
:
2458 purple_blist_add_chat(chat
, (PurpleGroup
*)node
->parent
, node
);
2460 case GTK_TREE_VIEW_DROP_BEFORE
:
2461 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE
:
2462 purple_blist_add_chat(chat
, (PurpleGroup
*)node
->parent
, node
->prev
);
2465 } else if (PURPLE_IS_GROUP(node
)) {
2466 purple_blist_add_chat(chat
, (PurpleGroup
*)node
, NULL
);
2468 } else if (PURPLE_IS_GROUP(n
)) {
2469 PurpleGroup
*g
= (PurpleGroup
*)n
;
2470 if (PURPLE_IS_GROUP(node
)) {
2472 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER
:
2473 case GTK_TREE_VIEW_DROP_AFTER
:
2474 purple_blist_add_group(g
, node
);
2476 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE
:
2477 case GTK_TREE_VIEW_DROP_BEFORE
:
2478 purple_blist_add_group(g
, node
->prev
);
2481 } else if(PURPLE_IS_BUDDY(node
)) {
2482 purple_blist_add_group(g
, node
->parent
->parent
);
2483 } else if(PURPLE_IS_CONTACT(node
) ||
2484 PURPLE_IS_CHAT(node
)) {
2485 purple_blist_add_group(g
, node
->parent
);
2489 gtk_tree_path_free(path
);
2490 gtk_drag_finish(dc
, TRUE
, (gdk_drag_context_get_actions(dc
) == GDK_ACTION_MOVE
), t
);
2492 } else if (target
== gdk_atom_intern("application/x-im-contact",
2494 PurpleGroup
*group
= NULL
;
2495 GtkTreePath
*path
= NULL
;
2496 GtkTreeViewDropPosition position
;
2497 PurpleAccount
*account
;
2498 char *protocol
= NULL
;
2499 char *username
= NULL
;
2502 if (gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget
),
2503 x
, y
, &path
, &position
))
2506 PurpleBlistNode
*node
;
2508 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
),
2510 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
),
2511 &iter
, NODE_COLUMN
, &node
, -1);
2513 if (PURPLE_IS_BUDDY(node
))
2515 group
= (PurpleGroup
*)node
->parent
->parent
;
2517 else if (PURPLE_IS_CHAT(node
) ||
2518 PURPLE_IS_CONTACT(node
))
2520 group
= (PurpleGroup
*)node
->parent
;
2522 else if (PURPLE_IS_GROUP(node
))
2524 group
= (PurpleGroup
*)node
;
2528 if (pidgin_parse_x_im_contact((const char *)data
, FALSE
, &account
,
2529 &protocol
, &username
, &alias
))
2531 if (account
== NULL
)
2533 purple_notify_error(NULL
, NULL
,
2534 _("You are not currently signed on with an account that "
2535 "can add that buddy."), NULL
, NULL
);
2539 purple_blist_request_add_buddy(account
, username
,
2540 (group
? purple_group_get_name(group
) : NULL
),
2550 gtk_tree_path_free(path
);
2552 gtk_drag_finish(dc
, TRUE
,
2553 gdk_drag_context_get_actions(dc
) == GDK_ACTION_MOVE
, t
);
2555 else if (target
== gdk_atom_intern("text/x-vcard", FALSE
) && data
)
2558 PurpleGroup
*group
= NULL
;
2559 GtkTreePath
*path
= NULL
;
2560 GtkTreeViewDropPosition position
;
2562 if (gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget
),
2563 x
, y
, &path
, &position
))
2566 PurpleBlistNode
*node
;
2568 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
),
2570 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
),
2571 &iter
, NODE_COLUMN
, &node
, -1);
2573 if (PURPLE_IS_BUDDY(node
))
2575 group
= (PurpleGroup
*)node
->parent
->parent
;
2577 else if (PURPLE_IS_CHAT(node
) ||
2578 PURPLE_IS_CONTACT(node
))
2580 group
= (PurpleGroup
*)node
->parent
;
2582 else if (PURPLE_IS_GROUP(node
))
2584 group
= (PurpleGroup
*)node
;
2588 result
= parse_vcard((const gchar
*)data
, group
);
2590 gtk_drag_finish(dc
, result
,
2591 gdk_drag_context_get_actions(dc
) == GDK_ACTION_MOVE
, t
);
2592 } else if (target
== gdk_atom_intern("text/uri-list", FALSE
) && data
) {
2593 GtkTreePath
*path
= NULL
;
2594 GtkTreeViewDropPosition position
;
2596 if (gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget
),
2597 x
, y
, &path
, &position
))
2600 PurpleBlistNode
*node
;
2602 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
),
2604 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
),
2605 &iter
, NODE_COLUMN
, &node
, -1);
2607 if (PURPLE_IS_BUDDY(node
) || PURPLE_IS_CONTACT(node
)) {
2608 PurpleBuddy
*b
= PURPLE_IS_BUDDY(node
) ? PURPLE_BUDDY(node
) : purple_contact_get_priority_buddy(PURPLE_CONTACT(node
));
2609 pidgin_dnd_file_manage(sd
, purple_buddy_get_account(b
), purple_buddy_get_name(b
));
2610 gtk_drag_finish(dc
, TRUE
,
2611 gdk_drag_context_get_actions(dc
) == GDK_ACTION_MOVE
, t
);
2613 gtk_drag_finish(dc
, FALSE
, FALSE
, t
);
2619 /* Altered from do_colorshift in gnome-panel */
2621 do_alphashift(GdkPixbuf
*pixbuf
, int shift
)
2624 gint width
, height
, padding
;
2628 if (!gdk_pixbuf_get_has_alpha(pixbuf
))
2631 width
= gdk_pixbuf_get_width(pixbuf
);
2632 height
= gdk_pixbuf_get_height(pixbuf
);
2633 padding
= gdk_pixbuf_get_rowstride(pixbuf
) - width
* 4;
2634 pixels
= gdk_pixbuf_get_pixels(pixbuf
);
2636 for (i
= 0; i
< height
; i
++) {
2637 for (j
= 0; j
< width
; j
++) {
2641 val
= *pixels
- shift
;
2642 *(pixels
++) = CLAMP(val
, 0, 255);
2649 static GdkPixbuf
*pidgin_blist_get_buddy_icon(PurpleBlistNode
*node
,
2650 gboolean scaled
, gboolean greyed
)
2653 PurpleBuddy
*buddy
= NULL
;
2654 PurpleGroup
*group
= NULL
;
2655 const guchar
*data
= NULL
;
2656 GdkPixbuf
*buf
, *ret
= NULL
;
2657 PurpleBuddyIcon
*icon
= NULL
;
2658 PurpleAccount
*account
= NULL
;
2659 PurpleContact
*contact
= NULL
;
2660 PurpleImage
*custom_img
;
2661 PurpleProtocol
*protocol
= NULL
;
2662 PurpleBuddyIconSpec
*icon_spec
= NULL
;
2663 gint orig_width
, orig_height
, scale_width
, scale_height
;
2665 if (PURPLE_IS_CONTACT(node
)) {
2666 buddy
= purple_contact_get_priority_buddy((PurpleContact
*)node
);
2667 contact
= (PurpleContact
*)node
;
2668 } else if (PURPLE_IS_BUDDY(node
)) {
2669 buddy
= (PurpleBuddy
*)node
;
2670 contact
= purple_buddy_get_contact(buddy
);
2671 } else if (PURPLE_IS_GROUP(node
)) {
2672 group
= (PurpleGroup
*)node
;
2673 } else if (PURPLE_IS_CHAT(node
)) {
2674 /* We don't need to do anything here. We just need to not fall
2675 * into the else block and return. */
2681 account
= purple_buddy_get_account(buddy
);
2684 if(account
&& purple_account_get_connection(account
)) {
2685 protocol
= purple_connection_get_protocol(purple_account_get_connection(account
));
2689 if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_buddy_icons"))
2693 /* If we have a contact then this is either a contact or a buddy and
2694 * we want to fetch the custom icon for the contact. If we don't have
2695 * a contact then this is a group or some other type of node and we
2696 * want to use that directly. */
2698 custom_img
= purple_buddy_icons_node_find_custom_icon(PURPLE_BLIST_NODE(contact
));
2700 custom_img
= purple_buddy_icons_node_find_custom_icon(node
);
2704 data
= purple_image_get_data(custom_img
);
2705 len
= purple_image_get_size(custom_img
);
2710 /* Not sure I like this...*/
2711 if (!(icon
= purple_buddy_icons_find(purple_buddy_get_account(buddy
), purple_buddy_get_name(buddy
))))
2713 data
= purple_buddy_icon_get_data(icon
, &len
);
2720 buf
= pidgin_pixbuf_from_data(data
, len
);
2721 purple_buddy_icon_unref(icon
);
2723 purple_debug_warning("gtkblist", "Couldn't load buddy icon on "
2724 "account %s (%s); buddyname=%s; custom_img_size=%" G_GSIZE_FORMAT
,
2725 account
? purple_account_get_username(account
) : "(no account)",
2726 account
? purple_account_get_protocol_id(account
) : "(no account)",
2727 buddy
? purple_buddy_get_name(buddy
) : "(no buddy)",
2728 custom_img
? purple_image_get_size(custom_img
) : 0);
2730 g_object_unref(custom_img
);
2734 g_object_unref(custom_img
);
2737 gboolean offline
= FALSE
, idle
= FALSE
;
2740 PurplePresence
*presence
= purple_buddy_get_presence(buddy
);
2741 if (!PURPLE_BUDDY_IS_ONLINE(buddy
))
2743 if (purple_presence_is_idle(presence
))
2746 if (purple_counting_node_get_online_count(PURPLE_COUNTING_NODE(group
)) == 0)
2751 gdk_pixbuf_saturate_and_pixelate(buf
, buf
, 0.0, FALSE
);
2754 gdk_pixbuf_saturate_and_pixelate(buf
, buf
, 0.25, FALSE
);
2757 /* I'd use the pidgin_buddy_icon_get_scale_size() thing, but it won't
2758 * tell me the original size, which I need for scaling purposes. */
2759 scale_width
= orig_width
= gdk_pixbuf_get_width(buf
);
2760 scale_height
= orig_height
= gdk_pixbuf_get_height(buf
);
2763 icon_spec
= purple_protocol_get_icon_spec(protocol
);
2765 if (icon_spec
&& icon_spec
->scale_rules
& PURPLE_ICON_SCALE_DISPLAY
)
2766 purple_buddy_icon_spec_get_scaled_size(purple_protocol_get_icon_spec(protocol
), &scale_width
, &scale_height
);
2768 if (scaled
|| scale_height
> 200 || scale_width
> 200) {
2770 float scale_size
= scaled
? 32.0 : 200.0;
2771 if(scale_height
> scale_width
) {
2772 scale_width
= scale_size
* (double)scale_width
/ (double)scale_height
;
2773 scale_height
= scale_size
;
2775 scale_height
= scale_size
* (double)scale_height
/ (double)scale_width
;
2776 scale_width
= scale_size
;
2778 /* Scale & round before making square, so rectangular (but
2779 * non-square) images get rounded corners too. */
2780 tmpbuf
= gdk_pixbuf_new(GDK_COLORSPACE_RGB
, TRUE
, 8, scale_width
, scale_height
);
2781 gdk_pixbuf_fill(tmpbuf
, 0x00000000);
2782 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
);
2783 if (pidgin_gdk_pixbuf_is_opaque(tmpbuf
))
2784 pidgin_gdk_pixbuf_make_round(tmpbuf
);
2785 ret
= gdk_pixbuf_new(GDK_COLORSPACE_RGB
, TRUE
, 8, scale_size
, scale_size
);
2786 gdk_pixbuf_fill(ret
, 0x00000000);
2787 gdk_pixbuf_copy_area(tmpbuf
, 0, 0, scale_width
, scale_height
, ret
, (scale_size
-scale_width
)/2, (scale_size
-scale_height
)/2);
2788 g_object_unref(G_OBJECT(tmpbuf
));
2790 ret
= gdk_pixbuf_scale_simple(buf
,scale_width
,scale_height
, GDK_INTERP_BILINEAR
);
2792 g_object_unref(G_OBJECT(buf
));
2802 * +--- STATUS_SIZE +--- td->avatar_width
2803 * | +-- td->name_width |
2804 * +----+ +-------+ +---------+
2806 * +-------------------------------------------+
2807 * | [ = [ |--- TOOLTIP_BORDER
2808 *name_height --+-| ######[BuddyName = PP [ AAAAAAAAAAA |--+
2809 * | | ######[ = PP [ AAAAAAAAAAA | |
2810 * STATUS SIZE -| | ######[[[[[[[[[[[[[[[[[[[[[ AAAAAAAAAAA | |
2811 * +--+-| ######[Account: So-and-so [ AAAAAAAAAAA | |-- td->avatar_height
2812 * | | [Idle: 4h 15m [ AAAAAAAAAAA | |
2813 * height --+ | [Foo: Bar, Baz [ AAAAAAAAAAA | |
2814 * | | [Status: Awesome [ AAAAAAAAAAA |--+
2815 * +----| [Stop: Hammer Time [ |
2816 * | [ [ |--- TOOLTIP_BORDER
2817 * +-------------------------------------------+
2819 * | +----------------+ |
2823 * +---- TOOLTIP_BORDER +---- TOOLTIP_BORDER
2827 #define STATUS_SIZE 16
2828 #define TOOLTIP_BORDER 12
2829 #define SMALL_SPACE 6
2830 #define LARGE_SPACE 12
2831 #define PROTOCOL_SIZE 16
2832 struct tooltip_data
{
2833 PangoLayout
*layout
;
2834 PangoLayout
*name_layout
;
2835 GdkPixbuf
*protocol_icon
;
2836 GdkPixbuf
*status_icon
;
2838 gboolean avatar_is_protocol_icon
;
2848 static PangoLayout
* create_pango_layout(const char *markup
, int *width
, int *height
)
2850 PangoLayout
*layout
;
2853 layout
= gtk_widget_create_pango_layout(gtkblist
->tipwindow
, NULL
);
2854 pango_layout_set_markup(layout
, markup
, -1);
2855 pango_layout_set_wrap(layout
, PANGO_WRAP_WORD
);
2856 pango_layout_set_width(layout
, 300000);
2858 pango_layout_get_size (layout
, &w
, &h
);
2860 *width
= PANGO_PIXELS(w
);
2862 *height
= PANGO_PIXELS(h
);
2866 static struct tooltip_data
* create_tip_for_account(PurpleAccount
*account
)
2868 struct tooltip_data
*td
= g_new0(struct tooltip_data
, 1);
2869 td
->status_icon
= pidgin_create_protocol_icon(account
, PIDGIN_PROTOCOL_ICON_SMALL
);
2870 /* Yes, status_icon, not protocol_icon */
2871 if (purple_account_is_disconnected(account
))
2872 gdk_pixbuf_saturate_and_pixelate(td
->status_icon
, td
->status_icon
, 0.0, FALSE
);
2873 td
->layout
= create_pango_layout(purple_account_get_username(account
), &td
->width
, &td
->height
);
2874 td
->padding
= SMALL_SPACE
;
2878 static struct tooltip_data
* create_tip_for_node(PurpleBlistNode
*node
, gboolean full
)
2880 struct tooltip_data
*td
= g_new0(struct tooltip_data
, 1);
2881 PurpleAccount
*account
= NULL
;
2882 char *tmp
= NULL
, *node_name
= NULL
, *tooltip_text
= NULL
;
2884 if (PURPLE_IS_BUDDY(node
)) {
2885 account
= purple_buddy_get_account((PurpleBuddy
*)(node
));
2886 } else if (PURPLE_IS_CHAT(node
)) {
2887 account
= purple_chat_get_account((PurpleChat
*)(node
));
2890 td
->padding
= TOOLTIP_BORDER
;
2891 td
->status_icon
= pidgin_blist_get_status_icon(node
, PIDGIN_STATUS_ICON_LARGE
);
2892 td
->avatar
= pidgin_blist_get_buddy_icon(node
, !full
, FALSE
);
2893 if (account
!= NULL
) {
2894 td
->protocol_icon
= pidgin_create_protocol_icon(account
, PIDGIN_PROTOCOL_ICON_SMALL
);
2896 tooltip_text
= pidgin_get_tooltip_text(node
, full
);
2897 if (tooltip_text
&& *tooltip_text
) {
2898 td
->layout
= create_pango_layout(tooltip_text
, &td
->width
, &td
->height
);
2901 if (PURPLE_IS_BUDDY(node
)) {
2902 tmp
= g_markup_escape_text(purple_buddy_get_name((PurpleBuddy
*)node
), -1);
2903 } else if (PURPLE_IS_CHAT(node
)) {
2904 tmp
= g_markup_escape_text(purple_chat_get_name((PurpleChat
*)node
), -1);
2905 } else if (PURPLE_IS_GROUP(node
)) {
2906 tmp
= g_markup_escape_text(purple_group_get_name((PurpleGroup
*)node
), -1);
2908 /* I don't believe this can happen currently, I think
2909 * everything that calls this function checks for one of the
2910 * above node types first. */
2911 tmp
= g_strdup(_("Unknown node type"));
2913 node_name
= g_strdup_printf("<span size='x-large' weight='bold'>%s</span>",
2917 td
->name_layout
= create_pango_layout(node_name
, &td
->name_width
, &td
->name_height
);
2918 td
->name_width
+= SMALL_SPACE
+ PROTOCOL_SIZE
;
2919 td
->name_height
= MAX(td
->name_height
, PROTOCOL_SIZE
+ SMALL_SPACE
);
2920 #if 0 /* Protocol Icon as avatar */
2921 if(!td
->avatar
&& full
) {
2922 td
->avatar
= pidgin_create_protocol_icon(account
, PIDGIN_PROTOCOL_ICON_LARGE
);
2923 td
->avatar_is_protocol_icon
= TRUE
;
2928 td
->avatar_width
= gdk_pixbuf_get_width(td
->avatar
);
2929 td
->avatar_height
= gdk_pixbuf_get_height(td
->avatar
);
2933 g_free(tooltip_text
);
2938 pidgin_blist_paint_tip(GtkWidget
*widget
, cairo_t
*cr
, gpointer null
)
2940 GtkStyleContext
*context
;
2941 int current_height
, max_width
;
2943 int max_avatar_width
;
2945 int protocol_col
= 0;
2946 GtkTextDirection dir
= gtk_widget_get_direction(widget
);
2947 int status_size
= 0;
2949 if(gtkblist
->tooltipdata
== NULL
)
2952 context
= gtk_widget_get_style_context(gtkblist
->tipwindow
);
2953 gtk_style_context_add_class(context
, GTK_STYLE_CLASS_TOOLTIP
);
2956 max_avatar_width
= 0;
2958 for(l
= gtkblist
->tooltipdata
; l
; l
= l
->next
)
2960 struct tooltip_data
*td
= l
->data
;
2962 max_text_width
= MAX(max_text_width
,
2963 MAX(td
->width
, td
->name_width
));
2964 max_avatar_width
= MAX(max_avatar_width
, td
->avatar_width
);
2965 if (td
->status_icon
)
2966 status_size
= STATUS_SIZE
;
2969 max_width
= TOOLTIP_BORDER
+ status_size
+ SMALL_SPACE
+ max_text_width
+ SMALL_SPACE
+ max_avatar_width
+ TOOLTIP_BORDER
;
2970 if (dir
== GTK_TEXT_DIR_RTL
)
2971 protocol_col
= TOOLTIP_BORDER
+ max_avatar_width
+ SMALL_SPACE
;
2973 protocol_col
= TOOLTIP_BORDER
+ status_size
+ SMALL_SPACE
+ max_text_width
- PROTOCOL_SIZE
;
2975 current_height
= 12;
2976 for(l
= gtkblist
->tooltipdata
; l
; l
= l
->next
)
2978 struct tooltip_data
*td
= l
->data
;
2980 if (td
->avatar
&& pidgin_gdk_pixbuf_is_opaque(td
->avatar
))
2982 gtk_style_context_save(context
);
2983 gtk_style_context_add_class(context
, GTK_STYLE_CLASS_FRAME
);
2984 if (dir
== GTK_TEXT_DIR_RTL
) {
2985 gtk_render_frame(context
, cr
,
2986 TOOLTIP_BORDER
- 1, current_height
- 1,
2987 td
->avatar_width
+ 2, td
->avatar_height
+ 2);
2989 gtk_render_frame(context
, cr
,
2990 max_width
- (td
->avatar_width
+ TOOLTIP_BORDER
) - 1,
2992 td
->avatar_width
+ 2, td
->avatar_height
+ 2);
2994 gtk_style_context_restore(context
);
2997 if (td
->status_icon
) {
2998 if (dir
== GTK_TEXT_DIR_RTL
) {
2999 gdk_cairo_set_source_pixbuf(cr
, td
->status_icon
,
3000 max_width
- TOOLTIP_BORDER
- status_size
,
3004 gdk_cairo_set_source_pixbuf(cr
, td
->status_icon
,
3005 TOOLTIP_BORDER
, current_height
);
3011 if (dir
== GTK_TEXT_DIR_RTL
) {
3012 gdk_cairo_set_source_pixbuf(cr
, td
->avatar
,
3013 TOOLTIP_BORDER
, current_height
);
3016 gdk_cairo_set_source_pixbuf(cr
, td
->avatar
,
3017 max_width
- (td
->avatar_width
+ TOOLTIP_BORDER
),
3023 if (!td
->avatar_is_protocol_icon
&& td
->protocol_icon
) {
3024 gdk_cairo_set_source_pixbuf(cr
, td
->protocol_icon
, protocol_col
,
3026 (td
->name_height
- PROTOCOL_SIZE
) / 2);
3030 if (td
->name_layout
) {
3031 if (dir
== GTK_TEXT_DIR_RTL
) {
3032 gtk_render_layout(context
, cr
,
3033 max_width
- (TOOLTIP_BORDER
+ status_size
+ SMALL_SPACE
) - PANGO_PIXELS(300000),
3034 current_height
, td
->name_layout
);
3036 gtk_render_layout(context
, cr
,
3037 TOOLTIP_BORDER
+ status_size
+ SMALL_SPACE
,
3038 current_height
, td
->name_layout
);
3043 if (dir
!= GTK_TEXT_DIR_RTL
) {
3044 gtk_render_layout(context
, cr
,
3045 TOOLTIP_BORDER
+ status_size
+ SMALL_SPACE
,
3046 current_height
+ td
->name_height
,
3049 gtk_render_layout(context
, cr
,
3050 max_width
- (TOOLTIP_BORDER
+ status_size
+ SMALL_SPACE
) - PANGO_PIXELS(300000),
3051 current_height
+ td
->name_height
,
3056 current_height
+= MAX(td
->name_height
+ td
->height
, td
->avatar_height
) + td
->padding
;
3063 pidgin_blist_destroy_tooltip_data(void)
3065 while(gtkblist
->tooltipdata
) {
3066 struct tooltip_data
*td
= gtkblist
->tooltipdata
->data
;
3069 g_object_unref(td
->avatar
);
3071 g_object_unref(td
->status_icon
);
3072 if(td
->protocol_icon
)
3073 g_object_unref(td
->protocol_icon
);
3075 g_object_unref(td
->layout
);
3076 if (td
->name_layout
)
3077 g_object_unref(td
->name_layout
);
3079 gtkblist
->tooltipdata
= g_list_delete_link(gtkblist
->tooltipdata
, gtkblist
->tooltipdata
);
3083 void pidgin_blist_tooltip_destroy()
3085 pidgin_blist_destroy_tooltip_data();
3086 pidgin_tooltip_destroy();
3090 pidgin_blist_align_tooltip(struct tooltip_data
*td
, GtkWidget
*widget
)
3092 GtkTextDirection dir
= gtk_widget_get_direction(widget
);
3094 if (dir
== GTK_TEXT_DIR_RTL
)
3096 char* layout_name
= purple_markup_strip_html(pango_layout_get_text(td
->name_layout
));
3097 PangoDirection dir
= pango_find_base_dir(layout_name
, -1);
3098 if (dir
== PANGO_DIRECTION_RTL
|| dir
== PANGO_DIRECTION_NEUTRAL
)
3099 pango_layout_set_alignment(td
->name_layout
, PANGO_ALIGN_RIGHT
);
3100 g_free(layout_name
);
3101 pango_layout_set_alignment(td
->layout
, PANGO_ALIGN_RIGHT
);
3106 pidgin_blist_create_tooltip_for_node(GtkWidget
*widget
, gpointer data
, int *w
, int *h
)
3108 PurpleBlistNode
*node
= data
;
3111 int max_text_width
= 0;
3112 int max_avatar_width
= 0;
3113 int status_size
= 0;
3115 if (gtkblist
->tooltipdata
) {
3116 gtkblist
->tipwindow
= NULL
;
3117 pidgin_blist_destroy_tooltip_data();
3120 gtkblist
->tipwindow
= widget
;
3121 if (PURPLE_IS_CHAT(node
) ||
3122 PURPLE_IS_BUDDY(node
)) {
3123 struct tooltip_data
*td
= create_tip_for_node(node
, TRUE
);
3124 pidgin_blist_align_tooltip(td
, gtkblist
->tipwindow
);
3125 gtkblist
->tooltipdata
= g_list_append(gtkblist
->tooltipdata
, td
);
3126 } else if (PURPLE_IS_GROUP(node
)) {
3127 PurpleGroup
*group
= (PurpleGroup
*)node
;
3129 struct tooltip_data
*td
= create_tip_for_node(node
, TRUE
);
3130 pidgin_blist_align_tooltip(td
, gtkblist
->tipwindow
);
3131 gtkblist
->tooltipdata
= g_list_append(gtkblist
->tooltipdata
, td
);
3133 /* Accounts with buddies in group */
3134 accounts
= purple_group_get_accounts(group
);
3135 for (; accounts
!= NULL
;
3136 accounts
= g_slist_delete_link(accounts
, accounts
)) {
3137 PurpleAccount
*account
= accounts
->data
;
3138 td
= create_tip_for_account(account
);
3139 gtkblist
->tooltipdata
= g_list_append(gtkblist
->tooltipdata
, td
);
3141 } else if (PURPLE_IS_CONTACT(node
)) {
3142 PurpleBlistNode
*child
;
3143 PurpleBuddy
*b
= purple_contact_get_priority_buddy((PurpleContact
*)node
);
3145 for(child
= node
->child
; child
; child
= child
->next
)
3147 if(PURPLE_IS_BUDDY(child
) && buddy_is_displayable((PurpleBuddy
*)child
)) {
3148 struct tooltip_data
*td
= create_tip_for_node(child
, (b
== (PurpleBuddy
*)child
));
3149 pidgin_blist_align_tooltip(td
, gtkblist
->tipwindow
);
3150 if (b
== (PurpleBuddy
*)child
) {
3151 gtkblist
->tooltipdata
= g_list_prepend(gtkblist
->tooltipdata
, td
);
3153 gtkblist
->tooltipdata
= g_list_append(gtkblist
->tooltipdata
, td
);
3162 for (list
= gtkblist
->tooltipdata
; list
; list
= list
->next
) {
3163 struct tooltip_data
*td
= list
->data
;
3164 max_text_width
= MAX(max_text_width
, MAX(td
->width
, td
->name_width
));
3165 max_avatar_width
= MAX(max_avatar_width
, td
->avatar_width
);
3166 height
+= MAX(MAX(STATUS_SIZE
, td
->avatar_height
), td
->height
+ td
->name_height
) + td
->padding
;
3167 if (td
->status_icon
)
3168 status_size
= MAX(status_size
, STATUS_SIZE
);
3170 height
+= TOOLTIP_BORDER
;
3171 width
= TOOLTIP_BORDER
+ status_size
+ SMALL_SPACE
+ max_text_width
+ SMALL_SPACE
+ max_avatar_width
+ TOOLTIP_BORDER
;
3181 static gboolean
pidgin_blist_expand_timeout(GtkWidget
*tv
)
3185 PurpleBlistNode
*node
;
3186 struct _pidgin_blist_node
*gtknode
;
3188 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),
3189 &path
, NULL
, NULL
, NULL
))
3191 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, path
);
3192 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, NODE_COLUMN
, &node
, -1);
3194 if(!PURPLE_IS_CONTACT(node
)) {
3195 gtk_tree_path_free(path
);
3199 gtknode
= purple_blist_node_get_ui_data(node
);
3201 if (!gtknode
->contact_expanded
) {
3204 pidgin_blist_expand_contact_cb(NULL
, node
);
3206 gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv
), path
, NULL
, >kblist
->contact_rect
);
3207 gtkblist
->contact_rect
.width
=
3208 gdk_window_get_width(gtk_widget_get_window(tv
));
3209 gtkblist
->mouseover_contact
= node
;
3210 gtk_tree_path_down (path
);
3211 while (gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
), &i
, path
)) {
3213 gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv
), path
, NULL
, &rect
);
3214 gtkblist
->contact_rect
.height
+= rect
.height
;
3215 gtk_tree_path_next(path
);
3218 gtk_tree_path_free(path
);
3222 static gboolean
buddy_is_displayable(PurpleBuddy
*buddy
)
3224 struct _pidgin_blist_node
*gtknode
;
3229 gtknode
= purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(buddy
));
3231 return (purple_account_is_connected(purple_buddy_get_account(buddy
)) &&
3232 (purple_presence_is_online(purple_buddy_get_presence(buddy
)) ||
3233 (gtknode
&& gtknode
->recent_signonoff
) ||
3234 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_offline_buddies") ||
3235 purple_blist_node_get_bool(PURPLE_BLIST_NODE(buddy
), "show_offline")));
3238 void pidgin_blist_draw_tooltip(PurpleBlistNode
*node
, GtkWidget
*widget
)
3240 pidgin_tooltip_show(widget
, node
, pidgin_blist_create_tooltip_for_node
, pidgin_blist_paint_tip
);
3243 static gboolean
pidgin_blist_drag_motion_cb(GtkWidget
*tv
, GdkDragContext
*drag_context
,
3244 gint x
, gint y
, guint time
, gpointer user_data
)
3251 * When dragging a buddy into a contact, this is the delay before
3252 * the contact auto-expands.
3256 if (gtkblist
->drag_timeout
) {
3257 if ((y
> gtkblist
->tip_rect
.y
) && ((y
- gtkblist
->tip_rect
.height
) < gtkblist
->tip_rect
.y
))
3259 /* We've left the cell. Remove the timeout and create a new one below */
3260 g_source_remove(gtkblist
->drag_timeout
);
3263 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv
), x
, y
, &path
, NULL
, NULL
, NULL
);
3264 gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv
), path
, NULL
, &rect
);
3267 gtk_tree_path_free(path
);
3269 /* Only autoexpand when in the middle of the cell to avoid annoying un-intended expands */
3270 if (y
< rect
.y
+ (rect
.height
/ 3) ||
3271 y
> rect
.y
+ (2 * (rect
.height
/3)))
3274 rect
.height
= rect
.height
/ 3;
3275 rect
.y
+= rect
.height
;
3277 gtkblist
->tip_rect
= rect
;
3279 gtkblist
->drag_timeout
= g_timeout_add(delay
, (GSourceFunc
)pidgin_blist_expand_timeout
, tv
);
3281 if (gtkblist
->mouseover_contact
) {
3282 if ((y
< gtkblist
->contact_rect
.y
) || ((y
- gtkblist
->contact_rect
.height
) > gtkblist
->contact_rect
.y
)) {
3283 pidgin_blist_collapse_contact_cb(NULL
, gtkblist
->mouseover_contact
);
3284 gtkblist
->mouseover_contact
= NULL
;
3292 pidgin_blist_create_tooltip(GtkWidget
*widget
, GtkTreePath
*path
,
3293 gpointer null
, int *w
, int *h
)
3296 PurpleBlistNode
*node
;
3297 gboolean editable
= FALSE
;
3299 /* If we're editing a cell (e.g. alias editing), don't show the tooltip */
3300 g_object_get(G_OBJECT(gtkblist
->text_rend
), "editable", &editable
, NULL
);
3304 if (gtkblist
->tooltipdata
) {
3305 gtkblist
->tipwindow
= NULL
;
3306 pidgin_blist_destroy_tooltip_data();
3309 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, path
);
3310 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, NODE_COLUMN
, &node
, -1);
3312 return pidgin_blist_create_tooltip_for_node(widget
, node
, w
, h
);
3315 static gboolean
pidgin_blist_motion_cb (GtkWidget
*tv
, GdkEventMotion
*event
, gpointer null
)
3317 if (gtkblist
->mouseover_contact
) {
3318 if ((event
->y
< gtkblist
->contact_rect
.y
) || ((event
->y
- gtkblist
->contact_rect
.height
) > gtkblist
->contact_rect
.y
)) {
3319 pidgin_blist_collapse_contact_cb(NULL
, gtkblist
->mouseover_contact
);
3320 gtkblist
->mouseover_contact
= NULL
;
3327 static gboolean
pidgin_blist_leave_cb (GtkWidget
*w
, GdkEventCrossing
*e
, gpointer n
)
3329 if (gtkblist
->timeout
) {
3330 g_source_remove(gtkblist
->timeout
);
3331 gtkblist
->timeout
= 0;
3334 if (gtkblist
->drag_timeout
) {
3335 g_source_remove(gtkblist
->drag_timeout
);
3336 gtkblist
->drag_timeout
= 0;
3339 if (gtkblist
->mouseover_contact
&&
3340 !((e
->x
> gtkblist
->contact_rect
.x
) && (e
->x
< (gtkblist
->contact_rect
.x
+ gtkblist
->contact_rect
.width
)) &&
3341 (e
->y
> gtkblist
->contact_rect
.y
) && (e
->y
< (gtkblist
->contact_rect
.y
+ gtkblist
->contact_rect
.height
)))) {
3342 pidgin_blist_collapse_contact_cb(NULL
, gtkblist
->mouseover_contact
);
3343 gtkblist
->mouseover_contact
= NULL
;
3351 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/debug/enabled",
3352 !purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/debug/enabled"));
3355 static char *get_mood_icon_path(const char *mood
)
3359 if (!strcmp(mood
, "busy")) {
3360 path
= g_build_filename(PURPLE_DATADIR
, "pixmaps", "pidgin",
3361 "status", "16", "busy.png", NULL
);
3362 } else if (!strcmp(mood
, "hiptop")) {
3363 path
= g_build_filename(PURPLE_DATADIR
, "pixmaps", "pidgin",
3364 "emblems", "16", "hiptop.png", NULL
);
3366 char *filename
= g_strdup_printf("%s.png", mood
);
3367 path
= g_build_filename(PURPLE_DATADIR
, "pixmaps", "pidgin",
3368 "emotes", "small", filename
, NULL
);
3375 update_status_with_mood(PurpleAccount
*account
, const gchar
*mood
,
3378 if (mood
&& *mood
) {
3380 purple_account_set_status(account
, "mood", TRUE
,
3381 PURPLE_MOOD_NAME
, mood
,
3382 PURPLE_MOOD_COMMENT
, text
,
3385 purple_account_set_status(account
, "mood", TRUE
,
3386 PURPLE_MOOD_NAME
, mood
,
3390 purple_account_set_status(account
, "mood", FALSE
, NULL
);
3395 edit_mood_cb(PurpleConnection
*gc
, PurpleRequestFields
*fields
)
3397 PurpleRequestField
*mood_field
;
3400 mood_field
= purple_request_fields_get_field(fields
, "mood");
3401 l
= purple_request_field_list_get_selected(mood_field
);
3404 const char *mood
= purple_request_field_list_get_data(mood_field
, l
->data
);
3408 PurpleAccount
*account
= purple_connection_get_account(gc
);
3410 if (purple_connection_get_flags(gc
) & PURPLE_CONNECTION_FLAG_SUPPORT_MOOD_MESSAGES
) {
3411 PurpleRequestField
*text_field
;
3412 text_field
= purple_request_fields_get_field(fields
, "text");
3413 text
= purple_request_field_string_get_value(text_field
);
3418 update_status_with_mood(account
, mood
, text
);
3420 GList
*accounts
= purple_accounts_get_all_active();
3422 for (; accounts
; accounts
= g_list_delete_link(accounts
, accounts
)) {
3423 PurpleAccount
*account
= (PurpleAccount
*) accounts
->data
;
3424 PurpleConnection
*gc
= purple_account_get_connection(account
);
3426 if (gc
&& (purple_connection_get_flags(gc
) & PURPLE_CONNECTION_FLAG_SUPPORT_MOODS
)) {
3427 update_status_with_mood(account
, mood
, NULL
);
3435 global_moods_for_each(gpointer key
, gpointer value
, gpointer user_data
)
3437 GList
**out_moods
= (GList
**) user_data
;
3438 PurpleMood
*mood
= (PurpleMood
*) value
;
3440 *out_moods
= g_list_append(*out_moods
, mood
);
3444 get_global_moods(void)
3446 GHashTable
*global_moods
=
3447 g_hash_table_new_full(g_str_hash
, g_str_equal
, NULL
, NULL
);
3448 GHashTable
*mood_counts
=
3449 g_hash_table_new_full(g_str_hash
, g_str_equal
, NULL
, NULL
);
3450 GList
*accounts
= purple_accounts_get_all_active();
3451 PurpleMood
*result
= NULL
;
3452 GList
*out_moods
= NULL
;
3454 int num_accounts
= 0;
3456 for (; accounts
; accounts
= g_list_delete_link(accounts
, accounts
)) {
3457 PurpleAccount
*account
= (PurpleAccount
*) accounts
->data
;
3458 if (purple_account_is_connected(account
)) {
3459 PurpleConnection
*gc
= purple_account_get_connection(account
);
3461 if (purple_connection_get_flags(gc
) & PURPLE_CONNECTION_FLAG_SUPPORT_MOODS
) {
3462 PurpleProtocol
*protocol
= purple_connection_get_protocol(gc
);
3463 PurpleMood
*mood
= NULL
;
3465 for (mood
= purple_protocol_client_iface_get_moods(protocol
, account
) ;
3466 mood
->mood
!= NULL
; mood
++) {
3468 GPOINTER_TO_INT(g_hash_table_lookup(mood_counts
, mood
->mood
));
3470 if (!g_hash_table_lookup(global_moods
, mood
->mood
)) {
3471 g_hash_table_insert(global_moods
, (gpointer
)mood
->mood
, mood
);
3473 g_hash_table_insert(mood_counts
, (gpointer
)mood
->mood
,
3474 GINT_TO_POINTER(mood_count
+ 1));
3482 g_hash_table_foreach(global_moods
, global_moods_for_each
, &out_moods
);
3483 result
= g_new0(PurpleMood
, g_hash_table_size(global_moods
) + 1);
3486 PurpleMood
*mood
= (PurpleMood
*) out_moods
->data
;
3487 int in_num_accounts
=
3488 GPOINTER_TO_INT(g_hash_table_lookup(mood_counts
, mood
->mood
));
3490 if (in_num_accounts
== num_accounts
) {
3491 /* mood is present in all accounts supporting moods */
3492 result
[i
].mood
= mood
->mood
;
3493 result
[i
].description
= mood
->description
;
3496 out_moods
= g_list_delete_link(out_moods
, out_moods
);
3499 g_hash_table_destroy(global_moods
);
3500 g_hash_table_destroy(mood_counts
);
3505 /* get current set mood for all mood-supporting accounts, or NULL if not set
3506 or not set to the same on all */
3507 static const gchar
*
3508 get_global_mood_status(void)
3510 GList
*accounts
= purple_accounts_get_all_active();
3511 const gchar
*found_mood
= NULL
;
3513 for (; accounts
; accounts
= g_list_delete_link(accounts
, accounts
)) {
3514 PurpleAccount
*account
= (PurpleAccount
*) accounts
->data
;
3516 if (purple_account_is_connected(account
) &&
3517 (purple_connection_get_flags(purple_account_get_connection(account
)) &
3518 PURPLE_CONNECTION_FLAG_SUPPORT_MOODS
)) {
3519 PurplePresence
*presence
= purple_account_get_presence(account
);
3520 PurpleStatus
*status
= purple_presence_get_status(presence
, "mood");
3521 const gchar
*curr_mood
= purple_status_get_attr_string(status
, PURPLE_MOOD_NAME
);
3523 if (found_mood
!= NULL
&& !purple_strequal(curr_mood
, found_mood
)) {
3524 /* found a different mood */
3528 found_mood
= curr_mood
;
3537 set_mood_cb(GtkWidget
*widget
, PurpleAccount
*account
)
3539 const char *current_mood
;
3540 PurpleRequestFields
*fields
;
3541 PurpleRequestFieldGroup
*g
;
3542 PurpleRequestField
*f
;
3543 PurpleConnection
*gc
= NULL
;
3544 PurpleProtocol
*protocol
= NULL
;
3546 PurpleMood
*global_moods
= get_global_moods();
3549 PurplePresence
*presence
= purple_account_get_presence(account
);
3550 PurpleStatus
*status
= purple_presence_get_status(presence
, "mood");
3551 gc
= purple_account_get_connection(account
);
3552 g_return_if_fail(purple_connection_get_protocol(gc
) != NULL
);
3553 protocol
= purple_connection_get_protocol(gc
);
3554 current_mood
= purple_status_get_attr_string(status
, PURPLE_MOOD_NAME
);
3556 current_mood
= get_global_mood_status();
3559 fields
= purple_request_fields_new();
3560 g
= purple_request_field_group_new(NULL
);
3561 f
= purple_request_field_list_new("mood", _("Please select your mood from the list"));
3563 purple_request_field_list_add_icon(f
, _("None"), NULL
, "");
3564 if (current_mood
== NULL
)
3565 purple_request_field_list_add_selected(f
, _("None"));
3567 /* TODO: rlaager wants this sorted. */
3568 /* TODO: darkrain wants it sorted post-translation */
3569 if (account
&& PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CLIENT_IFACE
, get_moods
))
3570 mood
= purple_protocol_client_iface_get_moods(protocol
, account
);
3572 mood
= global_moods
;
3573 for ( ; mood
->mood
!= NULL
; mood
++) {
3576 if (mood
->mood
== NULL
|| mood
->description
== NULL
)
3579 path
= get_mood_icon_path(mood
->mood
);
3580 purple_request_field_list_add_icon(f
, _(mood
->description
),
3581 path
, (gpointer
)mood
->mood
);
3584 if (current_mood
&& !strcmp(current_mood
, mood
->mood
))
3585 purple_request_field_list_add_selected(f
, _(mood
->description
));
3587 purple_request_field_group_add_field(g
, f
);
3589 purple_request_fields_add_group(fields
, g
);
3591 /* if the connection allows setting a mood message */
3592 if (gc
&& (purple_connection_get_flags(gc
) & PURPLE_CONNECTION_FLAG_SUPPORT_MOOD_MESSAGES
)) {
3593 g
= purple_request_field_group_new(NULL
);
3594 f
= purple_request_field_string_new("text",
3595 _("Message (optional)"), NULL
, FALSE
);
3596 purple_request_field_group_add_field(g
, f
);
3597 purple_request_fields_add_group(fields
, g
);
3600 purple_request_fields(gc
, _("Edit User Mood"), _("Edit User Mood"),
3602 _("OK"), G_CALLBACK(edit_mood_cb
),
3604 purple_request_cpar_from_connection(gc
), gc
);
3606 g_free(global_moods
);
3612 set_mood_cb(NULL
, NULL
);
3615 /***************************************************
3617 ***************************************************/
3618 /* TODO: fill out tooltips... */
3619 static const GtkActionEntry blist_menu_entries
[] = {
3620 /* NOTE: Do not set any accelerator to Control+O. It is mapped by
3621 gtk_blist_key_press_cb to "Get User Info" on the selected buddy. */
3623 { "BuddiesMenu", NULL
, N_("_Buddies"), NULL
, NULL
, NULL
},
3624 { "NewInstantMessage", PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW
, N_("New Instant _Message..."), "<control>M", NULL
, pidgin_dialogs_im
},
3625 { "JoinAChat", PIDGIN_STOCK_CHAT
, N_("Join a _Chat..."), "<control>C", NULL
, pidgin_blist_joinchat_show
},
3626 { "GetUserInfo", PIDGIN_STOCK_TOOLBAR_USER_INFO
, N_("Get User _Info..."), "<control>I", NULL
, pidgin_dialogs_info
},
3627 { "ViewUserLog", NULL
, N_("View User _Log..."), "<control>L", NULL
, pidgin_dialogs_log
},
3628 { "ShowMenu", NULL
, N_("Sh_ow"), NULL
, NULL
, NULL
},
3629 { "SortMenu", NULL
, N_("_Sort Buddies"), NULL
, NULL
, NULL
},
3630 { "AddBuddy", GTK_STOCK_ADD
, N_("_Add Buddy..."), "<control>B", NULL
, pidgin_blist_add_buddy_cb
},
3631 { "AddChat", GTK_STOCK_ADD
, N_("Add C_hat..."), NULL
, NULL
, pidgin_blist_add_chat_cb
},
3632 { "AddGroup", GTK_STOCK_ADD
, N_("Add _Group..."), NULL
, NULL
, purple_blist_request_add_group
},
3633 { "Quit", GTK_STOCK_QUIT
, N_("_Quit"), "<control>Q", NULL
, purple_core_quit
},
3636 { "AccountsMenu", NULL
, N_("_Accounts"), NULL
, NULL
, NULL
},
3637 { "ManageAccounts", NULL
, N_("Manage Accounts"), "<control>A", NULL
, pidgin_accounts_window_show
},
3640 { "ToolsMenu", NULL
, N_("_Tools"), NULL
, NULL
, NULL
},
3641 { "BuddyPounces", NULL
, N_("Buddy _Pounces"), NULL
, NULL
, pidgin_pounces_manager_show
},
3642 { "Certificates", NULL
, N_("_Certificates"), NULL
, NULL
, pidgin_certmgr_show
},
3643 { "CustomSmileys", PIDGIN_STOCK_TOOLBAR_SMILEY
, N_("Custom Smile_ys"), "<control>Y", NULL
, pidgin_smiley_manager_show
},
3644 { "Plugins", PIDGIN_STOCK_TOOLBAR_PLUGINS
, N_("Plu_gins"), "<control>U", NULL
, pidgin_plugin_dialog_show
},
3645 { "Preferences", GTK_STOCK_PREFERENCES
, N_("Pr_eferences"), "<control>P", NULL
, pidgin_prefs_show
},
3646 { "Privacy", NULL
, N_("Pr_ivacy"), NULL
, NULL
, pidgin_privacy_dialog_show
},
3647 { "SetMood", NULL
, N_("Set _Mood"), "<control>D", NULL
, set_mood_show
},
3648 { "FileTransfers", PIDGIN_STOCK_TOOLBAR_TRANSFER
, N_("_File Transfers"), "<control>T", NULL
, G_CALLBACK(gtk_blist_show_xfer_dialog_cb
) },
3649 { "RoomList", NULL
, N_("R_oom List"), NULL
, NULL
, pidgin_roomlist_dialog_show
},
3650 { "SystemLog", NULL
, N_("System _Log"), NULL
, NULL
, gtk_blist_show_systemlog_cb
},
3653 { "HelpMenu", NULL
, N_("_Help"), NULL
, NULL
, NULL
},
3654 { "OnlineHelp", GTK_STOCK_HELP
, N_("Online _Help"), "F1", NULL
, gtk_blist_show_onlinehelp_cb
},
3655 { "BuildInformation", NULL
, N_("_Build Information"), NULL
, NULL
, pidgin_dialogs_buildinfo
},
3656 { "DebugWindow", NULL
, N_("_Debug Window"), NULL
, NULL
, toggle_debug
},
3657 { "DeveloperInformation", NULL
, N_("De_veloper Information"), NULL
, NULL
, pidgin_dialogs_developers
},
3658 { "PluginInformation", NULL
, N_("_Plugin Information"), NULL
, NULL
, pidgin_dialogs_plugins_info
},
3659 { "TranslatorInformation", NULL
, N_("_Translator Information"), NULL
, NULL
, pidgin_dialogs_translators
},
3660 { "About", GTK_STOCK_ABOUT
, N_("_About"), NULL
, NULL
, pidgin_dialogs_about
},
3664 static const GtkToggleActionEntry blist_menu_toggle_entries
[] = {
3665 /* Buddies->Show menu */
3666 { "ShowOffline", NULL
, N_("_Offline Buddies"), NULL
, NULL
, G_CALLBACK(pidgin_blist_edit_mode_cb
), FALSE
},
3667 { "ShowEmptyGroups", NULL
, N_("_Empty Groups"), NULL
, NULL
, G_CALLBACK(pidgin_blist_show_empty_groups_cb
), FALSE
},
3668 { "ShowBuddyDetails", NULL
, N_("Buddy _Details"), NULL
, NULL
, G_CALLBACK(pidgin_blist_buddy_details_cb
), FALSE
},
3669 { "ShowIdleTimes", NULL
, N_("Idle _Times"), NULL
, NULL
, G_CALLBACK(pidgin_blist_show_idle_time_cb
), FALSE
},
3670 { "ShowProtocolIcons", NULL
, N_("_Protocol Icons"), NULL
, NULL
, G_CALLBACK(pidgin_blist_show_protocol_icons_cb
), FALSE
},
3673 { "MuteSounds", NULL
, N_("Mute _Sounds"), NULL
, NULL
, G_CALLBACK(pidgin_blist_mute_sounds_cb
), FALSE
},
3676 static const char *blist_menu
=
3678 "<menubar name='BList'>"
3679 "<menu action='BuddiesMenu'>"
3680 "<menuitem action='NewInstantMessage'/>"
3681 "<menuitem action='JoinAChat'/>"
3682 "<menuitem action='GetUserInfo'/>"
3683 "<menuitem action='ViewUserLog'/>"
3685 "<menu action='ShowMenu'>"
3686 "<menuitem action='ShowOffline'/>"
3687 "<menuitem action='ShowEmptyGroups'/>"
3688 "<menuitem action='ShowBuddyDetails'/>"
3689 "<menuitem action='ShowIdleTimes'/>"
3690 "<menuitem action='ShowProtocolIcons'/>"
3692 "<menu action='SortMenu'/>"
3694 "<menuitem action='AddBuddy'/>"
3695 "<menuitem action='AddChat'/>"
3696 "<menuitem action='AddGroup'/>"
3698 "<menuitem action='Quit'/>"
3700 "<menu action='AccountsMenu'>"
3701 "<menuitem action='ManageAccounts'/>"
3703 "<menu action='ToolsMenu'>"
3704 "<menuitem action='BuddyPounces'/>"
3705 "<menuitem action='Certificates'/>"
3706 "<menuitem action='CustomSmileys'/>"
3707 "<menuitem action='Plugins'/>"
3708 "<menuitem action='Preferences'/>"
3709 "<menuitem action='Privacy'/>"
3710 "<menuitem action='SetMood'/>"
3712 "<menuitem action='FileTransfers'/>"
3713 "<menuitem action='RoomList'/>"
3714 "<menuitem action='SystemLog'/>"
3716 "<menuitem action='MuteSounds'/>"
3717 "<placeholder name='PluginActions'/>"
3719 "<menu action='HelpMenu'>"
3720 "<menuitem action='OnlineHelp'/>"
3722 "<menuitem action='BuildInformation'/>"
3723 "<menuitem action='DebugWindow'/>"
3724 "<menuitem action='DeveloperInformation'/>"
3725 "<menuitem action='PluginInformation'/>"
3726 "<menuitem action='TranslatorInformation'/>"
3728 "<menuitem action='About'/>"
3733 /*********************************************************
3734 * Private Utility functions *
3735 *********************************************************/
3737 static char *pidgin_get_tooltip_text(PurpleBlistNode
*node
, gboolean full
)
3739 GString
*str
= g_string_new("");
3740 PurpleProtocol
*protocol
= NULL
;
3743 if (PURPLE_IS_CHAT(node
))
3748 PurpleProtocolChatEntry
*pce
;
3750 PurpleChatConversation
*conv
;
3751 PidginBlistNode
*bnode
= purple_blist_node_get_ui_data(node
);
3753 chat
= (PurpleChat
*)node
;
3754 protocol
= purple_protocols_find(purple_account_get_protocol_id(purple_chat_get_account(chat
)));
3756 connections
= purple_connections_get_all();
3757 if (connections
&& connections
->next
)
3759 tmp
= g_markup_escape_text(purple_account_get_username(purple_chat_get_account(chat
)), -1);
3760 g_string_append_printf(str
, _("<b>Account:</b> %s"), tmp
);
3764 if (bnode
&& bnode
->conv
.conv
) {
3765 conv
= PURPLE_CHAT_CONVERSATION(bnode
->conv
.conv
);
3768 if (protocol
&& PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT_IFACE
, get_name
))
3769 chat_name
= purple_protocol_chat_iface_get_name(protocol
, purple_chat_get_components(chat
));
3771 chat_name
= g_strdup(purple_chat_get_name(chat
));
3773 conv
= purple_conversations_find_chat_with_account(chat_name
,
3774 purple_chat_get_account(chat
));
3778 if (conv
&& !purple_chat_conversation_has_left(conv
)) {
3779 g_string_append_printf(str
, _("\n<b>Occupants:</b> %d"),
3780 purple_chat_conversation_get_users_count(conv
));
3782 if (protocol
&& (purple_protocol_get_options(protocol
) & OPT_PROTO_CHAT_TOPIC
)) {
3783 const char *chattopic
= purple_chat_conversation_get_topic(conv
);
3784 char *topic
= chattopic
? g_markup_escape_text(chattopic
, -1) : NULL
;
3785 g_string_append_printf(str
, _("\n<b>Topic:</b> %s"), topic
? topic
: _("(no topic set)"));
3791 cur
= purple_protocol_chat_iface_info(protocol
, purple_account_get_connection(purple_chat_get_account(chat
)));
3799 tmp
= purple_text_strip_mnemonic(pce
->label
);
3800 name
= g_markup_escape_text(tmp
, -1);
3802 value
= g_markup_escape_text(g_hash_table_lookup(
3803 purple_chat_get_components(chat
), pce
->identifier
), -1);
3804 g_string_append_printf(str
, "\n<b>%s</b> %s",
3806 value
? value
: "");
3812 cur
= g_list_delete_link(cur
, cur
);
3815 else if (PURPLE_IS_CONTACT(node
) || PURPLE_IS_BUDDY(node
))
3817 /* NOTE: THIS FUNCTION IS NO LONGER CALLED FOR CONTACTS.
3818 * It is only called by create_tip_for_node(), and create_tip_for_node() is never called for a contact.
3822 PurplePresence
*presence
;
3823 PurpleNotifyUserInfo
*user_info
;
3827 time_t idle_secs
, signon
;
3829 if (PURPLE_IS_CONTACT(node
))
3831 c
= (PurpleContact
*)node
;
3832 b
= purple_contact_get_priority_buddy(c
);
3836 b
= (PurpleBuddy
*)node
;
3837 c
= purple_buddy_get_contact(b
);
3840 protocol
= purple_protocols_find(purple_account_get_protocol_id(purple_buddy_get_account(b
)));
3842 presence
= purple_buddy_get_presence(b
);
3843 user_info
= purple_notify_user_info_new();
3846 connections
= purple_connections_get_all();
3847 if (full
&& connections
&& connections
->next
)
3849 purple_notify_user_info_add_pair_plaintext(user_info
, _("Account"),
3850 purple_account_get_username(purple_buddy_get_account(b
)));
3854 /* If there's not a contact alias, the node is being displayed with
3855 * this alias, so there's no point in showing it in the tooltip. */
3856 g_object_get(c
, "alias", &alias
, NULL
);
3857 if (full
&& c
&& purple_buddy_get_local_alias(b
) != NULL
&& purple_buddy_get_local_alias(b
)[0] != '\0' &&
3858 (alias
!= NULL
&& alias
[0] != '\0') &&
3859 strcmp(alias
, purple_buddy_get_local_alias(b
)) != 0)
3861 purple_notify_user_info_add_pair_plaintext(user_info
,
3862 _("Buddy Alias"), purple_buddy_get_local_alias(b
));
3865 /* Nickname/Server Alias */
3866 /* I'd like to only show this if there's a contact or buddy
3867 * alias, but many people on MSN set long nicknames, which
3868 * get ellipsized, so the only way to see the whole thing is
3869 * to look at the tooltip. */
3870 if (full
&& purple_buddy_get_server_alias(b
))
3872 purple_notify_user_info_add_pair_plaintext(user_info
,
3873 _("Nickname"), purple_buddy_get_server_alias(b
));
3877 signon
= purple_presence_get_login_time(presence
);
3878 if (full
&& PURPLE_BUDDY_IS_ONLINE(b
) && signon
> 0)
3880 if (signon
> time(NULL
)) {
3882 * They signed on in the future?! Our local clock
3883 * must be wrong, show the actual date instead of
3886 tmp
= g_strdup(purple_date_format_long(localtime(&signon
)));
3888 tmp
= purple_str_seconds_to_string(time(NULL
) - signon
);
3889 purple_notify_user_info_add_pair_plaintext(user_info
, _("Logged In"), tmp
);
3894 if (purple_presence_is_idle(presence
))
3896 idle_secs
= purple_presence_get_idle_time(presence
);
3899 tmp
= purple_str_seconds_to_string(time(NULL
) - idle_secs
);
3900 purple_notify_user_info_add_pair_plaintext(user_info
, _("Idle"), tmp
);
3906 if (full
&& c
&& !PURPLE_BUDDY_IS_ONLINE(b
))
3908 struct _pidgin_blist_node
*gtknode
= purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(c
));
3909 PurpleBlistNode
*bnode
;
3912 if (gtknode
&& (!gtknode
->contact_expanded
|| PURPLE_IS_CONTACT(node
)))
3914 /* We're either looking at a buddy for a collapsed contact or
3915 * an expanded contact itself so we show the most recent
3916 * (largest) last_seen time for any of the buddies under
3918 for (bnode
= ((PurpleBlistNode
*)c
)->child
; bnode
!= NULL
; bnode
= bnode
->next
)
3920 int value
= purple_blist_node_get_int(bnode
, "last_seen");
3921 if (value
> lastseen
)
3927 /* We're dealing with a buddy under an expanded contact,
3928 * so we show the last_seen time for the buddy. */
3929 lastseen
= purple_blist_node_get_int(&b
->node
, "last_seen");
3934 tmp
= purple_str_seconds_to_string(time(NULL
) - lastseen
);
3935 purple_notify_user_info_add_pair_plaintext(user_info
, _("Last Seen"), tmp
);
3942 /* FIXME: Why is this status special-cased by the core? --rlaager
3943 * FIXME: Alternatively, why not have the core do all of them? --rlaager */
3944 if (!PURPLE_BUDDY_IS_ONLINE(b
)) {
3945 purple_notify_user_info_add_pair_plaintext(user_info
, _("Status"), _("Offline"));
3948 if (purple_account_is_connected(purple_buddy_get_account(b
)) &&
3951 /* Additional text from the protocol */
3952 purple_protocol_client_iface_tooltip_text(protocol
, b
, user_info
, full
);
3955 /* These are Easter Eggs. Patches to remove them will be rejected. */
3956 if (!g_ascii_strcasecmp(purple_buddy_get_name(b
), "robflynn"))
3957 purple_notify_user_info_add_pair_plaintext(user_info
, _("Description"), _("Spooky"));
3958 if (!g_ascii_strcasecmp(purple_buddy_get_name(b
), "seanegn"))
3959 purple_notify_user_info_add_pair_plaintext(user_info
, _("Status"), _("Awesome"));
3960 if (!g_ascii_strcasecmp(purple_buddy_get_name(b
), "chipx86"))
3961 purple_notify_user_info_add_pair_plaintext(user_info
, _("Status"), _("Rockin'"));
3963 tmp
= purple_notify_user_info_get_text_with_newline(user_info
, "\n");
3964 g_string_append(str
, tmp
);
3968 purple_notify_user_info_destroy(user_info
);
3969 } else if (PURPLE_IS_GROUP(node
)) {
3971 PurpleGroup
*group
= (PurpleGroup
*)node
;
3972 PurpleNotifyUserInfo
*user_info
;
3974 user_info
= purple_notify_user_info_new();
3976 count
= purple_counting_node_get_online_count(PURPLE_COUNTING_NODE(group
));
3978 /* Online buddies in group */
3980 sprintf(tmp2
, "%d", count
);
3981 purple_notify_user_info_add_pair_plaintext(user_info
,
3982 _("Online Buddies"), tmp2
);
3985 count
= purple_counting_node_get_current_size(PURPLE_COUNTING_NODE(group
));
3987 /* Total buddies (from online accounts) in group */
3989 sprintf(tmp2
, "%d", count
);
3990 purple_notify_user_info_add_pair_html(user_info
,
3991 _("Total Buddies"), tmp2
);
3994 tmp
= purple_notify_user_info_get_text_with_newline(user_info
, "\n");
3995 g_string_append(str
, tmp
);
3998 purple_notify_user_info_destroy(user_info
);
4001 purple_signal_emit(pidgin_blist_get_handle(), "drawing-tooltip",
4004 return g_string_free(str
, FALSE
);
4007 static GHashTable
*cached_emblems
;
4009 static void _cleanup_cached_emblem(gpointer data
, GObject
*obj
) {
4010 g_hash_table_remove(cached_emblems
, data
);
4013 static GdkPixbuf
* _pidgin_blist_get_cached_emblem(gchar
*path
) {
4014 GdkPixbuf
*pb
= g_hash_table_lookup(cached_emblems
, path
);
4017 /* The caller gets a reference */
4021 pb
= pidgin_pixbuf_new_from_file(path
);
4023 /* We don't want to own a ref to the pixbuf, but we need to keep clean up. */
4024 /* I'm not sure if it would be better to just keep our ref and not let the emblem ever be destroyed */
4025 g_object_weak_ref(G_OBJECT(pb
), _cleanup_cached_emblem
, path
);
4026 g_hash_table_insert(cached_emblems
, path
, pb
);
4035 pidgin_blist_get_emblem(PurpleBlistNode
*node
)
4037 PurpleBuddy
*buddy
= NULL
;
4038 struct _pidgin_blist_node
*gtknode
= purple_blist_node_get_ui_data(node
);
4039 PurpleProtocol
*protocol
;
4040 const char *name
= NULL
;
4041 char *filename
, *path
;
4042 PurplePresence
*p
= NULL
;
4045 if(PURPLE_IS_CONTACT(node
)) {
4046 if(!gtknode
->contact_expanded
) {
4047 buddy
= purple_contact_get_priority_buddy((PurpleContact
*)node
);
4049 } else if(PURPLE_IS_BUDDY(node
)) {
4050 buddy
= (PurpleBuddy
*)node
;
4051 p
= purple_buddy_get_presence(buddy
);
4052 if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_MOBILE
)) {
4053 /* This emblem comes from the small emoticon set now,
4054 * to reduce duplication. */
4055 path
= g_build_filename(PURPLE_DATADIR
, "pixmaps",
4056 "pidgin", "emotes", "small", "mobile.png", NULL
);
4057 return _pidgin_blist_get_cached_emblem(path
);
4060 if (((struct _pidgin_blist_node
*)purple_blist_node_get_ui_data(node
->parent
))->contact_expanded
) {
4061 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_protocol_icons"))
4063 return pidgin_create_protocol_icon(purple_buddy_get_account((PurpleBuddy
*)node
), PIDGIN_PROTOCOL_ICON_SMALL
);
4069 g_return_val_if_fail(buddy
!= NULL
, NULL
);
4071 if (!purple_account_privacy_check(purple_buddy_get_account(buddy
), purple_buddy_get_name(buddy
))) {
4072 path
= g_build_filename(PURPLE_DATADIR
, "pixmaps", "pidgin",
4073 "emblems", "16", "blocked.png", NULL
);
4074 return _pidgin_blist_get_cached_emblem(path
);
4077 /* If we came through the contact code flow above, we didn't need
4078 * to get the presence until now. */
4080 p
= purple_buddy_get_presence(buddy
);
4082 if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_MOBILE
)) {
4083 /* This emblem comes from the small emoticon set now, to reduce duplication. */
4084 path
= g_build_filename(PURPLE_DATADIR
, "pixmaps", "pidgin",
4085 "emotes", "small", "mobile.png", NULL
);
4086 return _pidgin_blist_get_cached_emblem(path
);
4089 tune
= purple_presence_get_status(p
, "tune");
4090 if (tune
&& purple_status_is_active(tune
)) {
4092 * TODO: Replace "Tune" with generalized "Media" in 3.0. */
4093 if (purple_status_get_attr_string(tune
, "game") != NULL
) {
4094 path
= g_build_filename(PURPLE_DATADIR
, "pixmaps",
4095 "pidgin", "emblems", "16", "game.png", NULL
);
4096 return _pidgin_blist_get_cached_emblem(path
);
4099 * TODO: Replace "Tune" with generalized "Media" in 3.0. */
4100 if (purple_status_get_attr_string(tune
, "office") != NULL
) {
4101 path
= g_build_filename(PURPLE_DATADIR
, "pixmaps",
4102 "pidgin", "emblems", "16", "office.png", NULL
);
4103 return _pidgin_blist_get_cached_emblem(path
);
4105 /* Regular old "tune" is the only one in all protocols. */
4106 /* This emblem comes from the small emoticon set now, to reduce duplication. */
4107 path
= g_build_filename(PURPLE_DATADIR
, "pixmaps", "pidgin",
4108 "emotes", "small", "music.png", NULL
);
4109 return _pidgin_blist_get_cached_emblem(path
);
4112 protocol
= purple_protocols_find(purple_account_get_protocol_id(purple_buddy_get_account(buddy
)));
4116 name
= purple_protocol_client_iface_list_emblem(protocol
, buddy
);
4119 PurpleStatus
*status
;
4121 if (!purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_MOOD
))
4124 status
= purple_presence_get_status(p
, "mood");
4125 name
= purple_status_get_attr_string(status
, PURPLE_MOOD_NAME
);
4127 if (!(name
&& *name
))
4130 path
= get_mood_icon_path(name
);
4132 filename
= g_strdup_printf("%s.png", name
);
4133 path
= g_build_filename(PURPLE_DATADIR
, "pixmaps", "pidgin",
4134 "emblems", "16", filename
, NULL
);
4138 /* _pidgin_blist_get_cached_emblem() assumes ownership of path */
4139 return _pidgin_blist_get_cached_emblem(path
);
4144 pidgin_blist_get_status_icon(PurpleBlistNode
*node
, PidginStatusIconSize size
)
4147 const char *icon
= NULL
;
4148 struct _pidgin_blist_node
*gtknode
= purple_blist_node_get_ui_data(node
);
4149 struct _pidgin_blist_node
*gtkbuddynode
= NULL
;
4150 PurpleBuddy
*buddy
= NULL
;
4151 PurpleChat
*chat
= NULL
;
4152 GtkIconSize icon_size
= gtk_icon_size_from_name((size
== PIDGIN_STATUS_ICON_LARGE
) ? PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
:
4153 PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC
);
4155 if(PURPLE_IS_CONTACT(node
)) {
4156 if(!gtknode
->contact_expanded
) {
4157 buddy
= purple_contact_get_priority_buddy((PurpleContact
*)node
);
4159 gtkbuddynode
= purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(buddy
));
4161 } else if(PURPLE_IS_BUDDY(node
)) {
4162 buddy
= (PurpleBuddy
*)node
;
4163 gtkbuddynode
= purple_blist_node_get_ui_data(node
);
4164 } else if(PURPLE_IS_CHAT(node
)) {
4165 chat
= (PurpleChat
*)node
;
4171 PurpleAccount
*account
;
4172 PurpleProtocol
*protocol
;
4175 account
= purple_buddy_get_account(buddy
);
4177 account
= purple_chat_get_account(chat
);
4179 protocol
= purple_protocols_find(purple_account_get_protocol_id(account
));
4185 PurpleConversation
*conv
= find_conversation_with_buddy(buddy
);
4190 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
4191 if (gtkconv
== NULL
&& size
== PIDGIN_STATUS_ICON_SMALL
) {
4192 PidginBlistNode
*ui
= purple_blist_node_get_ui_data(&(buddy
->node
));
4193 if (ui
== NULL
|| (ui
->conv
.flags
& PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE
))
4194 return gtk_widget_render_icon (GTK_WIDGET(gtkblist
->treeview
),
4195 PIDGIN_STOCK_STATUS_MESSAGE
, icon_size
, "GtkTreeView");
4199 p
= purple_buddy_get_presence(buddy
);
4200 trans
= purple_presence_is_idle(p
);
4202 if (PURPLE_BUDDY_IS_ONLINE(buddy
) && gtkbuddynode
&& gtkbuddynode
->recent_signonoff
)
4203 icon
= PIDGIN_STOCK_STATUS_LOGIN
;
4204 else if (gtkbuddynode
&& gtkbuddynode
->recent_signonoff
)
4205 icon
= PIDGIN_STOCK_STATUS_LOGOUT
;
4206 else if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_UNAVAILABLE
))
4208 icon
= PIDGIN_STOCK_STATUS_BUSY_I
;
4210 icon
= PIDGIN_STOCK_STATUS_BUSY
;
4211 else if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_AWAY
))
4213 icon
= PIDGIN_STOCK_STATUS_AWAY_I
;
4215 icon
= PIDGIN_STOCK_STATUS_AWAY
;
4216 else if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_EXTENDED_AWAY
))
4218 icon
= PIDGIN_STOCK_STATUS_XA_I
;
4220 icon
= PIDGIN_STOCK_STATUS_XA
;
4221 else if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_OFFLINE
))
4222 icon
= PIDGIN_STOCK_STATUS_OFFLINE
;
4224 icon
= PIDGIN_STOCK_STATUS_AVAILABLE_I
;
4225 else if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_INVISIBLE
))
4226 icon
= PIDGIN_STOCK_STATUS_INVISIBLE
;
4228 icon
= PIDGIN_STOCK_STATUS_AVAILABLE
;
4230 icon
= PIDGIN_STOCK_STATUS_CHAT
;
4232 icon
= PIDGIN_STOCK_STATUS_PERSON
;
4235 ret
= gtk_widget_render_icon (GTK_WIDGET(gtkblist
->treeview
), icon
,
4236 icon_size
, "GtkTreeView");
4241 theme_font_get_color_default(PidginThemeFont
*font
, const char *def
)
4244 if (!font
|| !(ret
= pidgin_theme_font_get_color_describe(font
)))
4250 theme_font_get_face_default(PidginThemeFont
*font
, const char *def
)
4253 if (!font
|| !(ret
= pidgin_theme_font_get_font_face(font
)))
4259 pidgin_blist_get_name_markup(PurpleBuddy
*b
, gboolean selected
, gboolean aliased
)
4261 const char *name
, *name_color
, *name_font
, *status_color
, *status_font
;
4263 PurpleProtocol
*protocol
= NULL
;
4264 PurpleContact
*contact
;
4265 PurplePresence
*presence
;
4266 struct _pidgin_blist_node
*gtkcontactnode
= NULL
;
4267 char *idletime
= NULL
, *statustext
= NULL
, *nametext
= NULL
;
4268 PurpleConversation
*conv
= find_conversation_with_buddy(b
);
4269 gboolean hidden_conv
= FALSE
;
4270 gboolean biglist
= purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_buddy_icons");
4271 PidginThemeFont
*statusfont
= NULL
, *namefont
= NULL
;
4272 PidginBlistTheme
*theme
;
4273 gchar
*contact_alias
;
4276 PidginBlistNode
*ui
= purple_blist_node_get_ui_data(&(b
->node
));
4278 if (ui
->conv
.flags
& PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE
)
4281 if (PIDGIN_CONVERSATION(conv
) == NULL
)
4286 /* XXX Good luck cleaning up this crap */
4287 contact
= PURPLE_CONTACT(PURPLE_BLIST_NODE(b
)->parent
);
4289 gtkcontactnode
= purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(contact
));
4291 g_object_get(contact
, "alias", &contact_alias
, NULL
);
4294 if (gtkcontactnode
&& !gtkcontactnode
->contact_expanded
&& contact_alias
)
4295 name
= contact_alias
;
4297 name
= purple_buddy_get_alias(b
);
4299 /* Raise a contact pre-draw signal here. THe callback will return an
4300 * escaped version of the name. */
4301 nametext
= purple_signal_emit_return_1(pidgin_blist_get_handle(), "drawing-buddy", b
);
4304 nametext
= g_markup_escape_text(name
, strlen(name
));
4306 presence
= purple_buddy_get_presence(b
);
4308 /* Name is all that is needed */
4309 if (!aliased
|| biglist
) {
4312 protocol
= purple_protocols_find(purple_account_get_protocol_id(purple_buddy_get_account(b
)));
4314 if (protocol
&& PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CLIENT_IFACE
, status_text
) &&
4315 purple_account_get_connection(purple_buddy_get_account(b
))) {
4316 char *tmp
= purple_protocol_client_iface_status_text(protocol
, b
);
4319 if(tmp
&& !g_utf8_validate(tmp
, -1, &end
)) {
4320 char *new = g_strndup(tmp
,
4321 g_utf8_pointer_to_offset(tmp
, end
));
4326 g_strdelimit(tmp
, "\n", ' ');
4327 purple_str_strip_char(tmp
, '\r');
4332 if(!purple_presence_is_online(presence
) && !statustext
)
4333 statustext
= g_strdup(_("Offline"));
4336 if (purple_presence_is_idle(presence
) && purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_idle_time")) {
4337 time_t idle_secs
= purple_presence_get_idle_time(presence
);
4339 if (idle_secs
> 0) {
4340 int iday
, ihrs
, imin
;
4344 iday
= (t
- idle_secs
) / (24 * 60 * 60);
4345 ihrs
= ((t
- idle_secs
) / 60 / 60) % 24;
4346 imin
= ((t
- idle_secs
) / 60) % 60;
4349 idletime
= g_strdup_printf(_("Idle %dd %dh %02dm"), iday
, ihrs
, imin
);
4351 idletime
= g_strdup_printf(_("Idle %dh %02dm"), ihrs
, imin
);
4353 idletime
= g_strdup_printf(_("Idle %dm"), imin
);
4356 idletime
= g_strdup(_("Idle"));
4360 /* choose the colors of the text */
4361 theme
= pidgin_blist_get_theme();
4365 if (purple_presence_is_idle(presence
)) {
4366 namefont
= statusfont
= pidgin_blist_theme_get_idle_text_info(theme
);
4367 name_color
= "dim grey";
4368 } else if (!purple_presence_is_online(presence
)) {
4369 namefont
= pidgin_blist_theme_get_offline_text_info(theme
);
4370 name_color
= "dim grey";
4371 statusfont
= pidgin_blist_theme_get_status_text_info(theme
);
4372 } else if (purple_presence_is_available(presence
)) {
4373 namefont
= pidgin_blist_theme_get_online_text_info(theme
);
4374 statusfont
= pidgin_blist_theme_get_status_text_info(theme
);
4376 namefont
= pidgin_blist_theme_get_away_text_info(theme
);
4377 statusfont
= pidgin_blist_theme_get_status_text_info(theme
);
4381 && (purple_presence_is_idle(presence
)
4382 || !purple_presence_is_online(presence
)))
4384 name_color
= "dim grey";
4388 name_color
= theme_font_get_color_default(namefont
, name_color
);
4389 name_font
= theme_font_get_face_default(namefont
, "");
4391 status_color
= theme_font_get_color_default(statusfont
, "dim grey");
4392 status_font
= theme_font_get_face_default(statusfont
, "");
4394 if (aliased
&& selected
) {
4396 name_color
= "black";
4397 status_color
= "black";
4400 status_color
= NULL
;
4405 char *tmp
= nametext
;
4406 nametext
= g_strdup_printf("<b>%s</b>", tmp
);
4410 /* Put it all together */
4411 if ((!aliased
|| biglist
) && (statustext
|| idletime
)) {
4412 /* using <span size='smaller'> breaks the status, so it must be seperated into <small><span>*/
4414 text
= g_strdup_printf("<span font_desc='%s' foreground='%s'>%s</span>\n"
4415 "<small><span font_desc='%s' foreground='%s'>%s%s%s</span></small>",
4416 name_font
, name_color
, nametext
, status_font
, status_color
,
4417 idletime
!= NULL
? idletime
: "",
4418 (idletime
!= NULL
&& statustext
!= NULL
) ? " - " : "",
4419 statustext
!= NULL
? statustext
: "");
4420 } else if (status_color
) {
4421 text
= g_strdup_printf("<span font_desc='%s'>%s</span>\n"
4422 "<small><span font_desc='%s' foreground='%s'>%s%s%s</span></small>",
4423 name_font
, nametext
, status_font
, status_color
,
4424 idletime
!= NULL
? idletime
: "",
4425 (idletime
!= NULL
&& statustext
!= NULL
) ? " - " : "",
4426 statustext
!= NULL
? statustext
: "");
4428 text
= g_strdup_printf("<span font_desc='%s'>%s</span>\n"
4429 "<small><span font_desc='%s'>%s%s%s</span></small>",
4430 name_font
, nametext
, status_font
,
4431 idletime
!= NULL
? idletime
: "",
4432 (idletime
!= NULL
&& statustext
!= NULL
) ? " - " : "",
4433 statustext
!= NULL
? statustext
: "");
4437 text
= g_strdup_printf("<span font_desc='%s' color='%s'>%s</span>",
4438 name_font
, name_color
, nametext
);
4440 text
= g_strdup_printf("<span font_desc='%s'>%s</span>", name_font
,
4447 g_free(contact_alias
);
4451 text
= g_strdup_printf("<b>%s</b>", tmp
);
4458 static void pidgin_blist_restore_position(void)
4460 int blist_x
, blist_y
, blist_width
, blist_height
;
4462 blist_width
= purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/blist/width");
4464 /* if the window exists, is hidden, we're saving positions, and the
4465 * position is sane... */
4466 if (gtkblist
&& gtkblist
->window
&&
4467 !gtk_widget_get_visible(gtkblist
->window
) && blist_width
!= 0) {
4469 blist_x
= purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/blist/x");
4470 blist_y
= purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/blist/y");
4471 blist_height
= purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/blist/height");
4473 /* ...check position is on screen... */
4474 if (blist_x
>= gdk_screen_width())
4475 blist_x
= gdk_screen_width() - 100;
4476 else if (blist_x
+ blist_width
< 0)
4479 if (blist_y
>= gdk_screen_height())
4480 blist_y
= gdk_screen_height() - 100;
4481 else if (blist_y
+ blist_height
< 0)
4484 /* ...and move it back. */
4485 gtk_window_move(GTK_WINDOW(gtkblist
->window
), blist_x
, blist_y
);
4486 gtk_window_resize(GTK_WINDOW(gtkblist
->window
), blist_width
, blist_height
);
4487 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/list_maximized"))
4488 gtk_window_maximize(GTK_WINDOW(gtkblist
->window
));
4492 static gboolean
pidgin_blist_refresh_timer(PurpleBuddyList
*list
)
4494 PurpleBlistNode
*gnode
, *cnode
;
4496 if (gtk_blist_visibility
== GDK_VISIBILITY_FULLY_OBSCURED
4497 || !gtk_widget_get_visible(gtkblist
->window
))
4500 for(gnode
= list
->root
; gnode
; gnode
= gnode
->next
) {
4501 if(!PURPLE_IS_GROUP(gnode
))
4503 for(cnode
= gnode
->child
; cnode
; cnode
= cnode
->next
) {
4504 if(PURPLE_IS_CONTACT(cnode
)) {
4507 buddy
= purple_contact_get_priority_buddy((PurpleContact
*)cnode
);
4510 purple_presence_is_idle(purple_buddy_get_presence(buddy
)))
4511 pidgin_blist_update_contact(list
, PURPLE_BLIST_NODE(buddy
));
4520 static void pidgin_blist_hide_node(PurpleBuddyList
*list
, PurpleBlistNode
*node
, gboolean update
)
4522 struct _pidgin_blist_node
*gtknode
= purple_blist_node_get_ui_data(node
);
4525 if (!gtknode
|| !gtknode
->row
|| !gtkblist
)
4528 if(gtkblist
->selected_node
== node
)
4529 gtkblist
->selected_node
= NULL
;
4530 if (get_iter_from_node(node
, &iter
)) {
4531 gtk_tree_store_remove(gtkblist
->treemodel
, &iter
);
4532 if(update
&& (PURPLE_IS_CONTACT(node
) ||
4533 PURPLE_IS_BUDDY(node
) || PURPLE_IS_CHAT(node
))) {
4534 pidgin_blist_update(list
, node
->parent
);
4537 gtk_tree_row_reference_free(gtknode
->row
);
4538 gtknode
->row
= NULL
;
4541 static const char *require_connection
[] =
4543 "/BList/BuddiesMenu/NewInstantMessage",
4544 "/BList/BuddiesMenu/JoinAChat",
4545 "/BList/BuddiesMenu/GetUserInfo",
4546 "/BList/BuddiesMenu/AddBuddy",
4547 "/BList/BuddiesMenu/AddChat",
4548 "/BList/BuddiesMenu/AddGroup",
4549 "/BList/ToolsMenu/Privacy",
4552 static const int require_connection_size
= sizeof(require_connection
)
4553 / sizeof(*require_connection
);
4556 * Rebuild dynamic menus and make menu items sensitive/insensitive
4557 * where appropriate.
4560 update_menu_bar(PidginBuddyList
*gtkblist
)
4566 g_return_if_fail(gtkblist
!= NULL
);
4568 pidgin_blist_update_accounts_menu();
4570 sensitive
= (purple_connections_get_all() != NULL
);
4572 for (i
= 0; i
< require_connection_size
; i
++)
4574 action
= gtk_ui_manager_get_action(gtkblist
->ui
, require_connection
[i
]);
4575 gtk_action_set_sensitive(action
, sensitive
);
4578 action
= gtk_ui_manager_get_action(gtkblist
->ui
, "/BList/BuddiesMenu/JoinAChat");
4579 gtk_action_set_sensitive(action
, pidgin_blist_joinchat_is_showable());
4581 action
= gtk_ui_manager_get_action(gtkblist
->ui
, "/BList/BuddiesMenu/AddChat");
4582 gtk_action_set_sensitive(action
, pidgin_blist_joinchat_is_showable());
4584 action
= gtk_ui_manager_get_action(gtkblist
->ui
, "/BList/ToolsMenu/RoomList");
4585 gtk_action_set_sensitive(action
, pidgin_roomlist_is_showable());
4589 sign_on_off_cb(PurpleConnection
*gc
, PurpleBuddyList
*blist
)
4591 PidginBuddyList
*gtkblist
= PIDGIN_BLIST(blist
);
4593 update_menu_bar(gtkblist
);
4597 plugin_changed_cb(PurplePlugin
*p
, gpointer data
)
4599 pidgin_blist_update_plugin_actions();
4603 unseen_conv_menu(void)
4605 static GtkWidget
*menu
= NULL
;
4606 GList
*convs
= NULL
;
4610 gtk_widget_destroy(menu
);
4614 ims
= pidgin_conversations_get_unseen_ims(PIDGIN_UNSEEN_TEXT
, FALSE
, 0);
4616 chats
= pidgin_conversations_get_unseen_chats(PIDGIN_UNSEEN_NICK
, FALSE
, 0);
4619 convs
= g_list_concat(ims
, chats
);
4620 else if(ims
&& !chats
)
4622 else if(!ims
&& chats
)
4626 /* no conversations added, don't show the menu */
4629 menu
= gtk_menu_new();
4631 pidgin_conversations_fill_menu(menu
, convs
);
4633 gtk_widget_show_all(menu
);
4634 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
, NULL
, NULL
, 3,
4635 gtk_get_current_event_time());
4639 menutray_press_cb(GtkWidget
*widget
, GdkEventButton
*event
)
4643 switch (event
->button
) {
4645 convs
= pidgin_conversations_get_unseen_ims(PIDGIN_UNSEEN_TEXT
, FALSE
, 1);
4648 convs
= pidgin_conversations_get_unseen_chats(PIDGIN_UNSEEN_NICK
, FALSE
, 1);
4650 pidgin_conv_present_conversation((PurpleConversation
*)convs
->data
);
4662 conversation_updated_cb(PurpleConversation
*conv
, PurpleConversationUpdateType type
,
4663 PidginBuddyList
*gtkblist
)
4665 PurpleAccount
*account
= purple_conversation_get_account(conv
);
4666 GList
*convs
= NULL
;
4670 if (type
!= PURPLE_CONVERSATION_UPDATE_UNSEEN
)
4673 if(account
!= NULL
&& purple_conversation_get_name(conv
) != NULL
) {
4674 PurpleBuddy
*buddy
= purple_blist_find_buddy(account
, purple_conversation_get_name(conv
));
4676 pidgin_blist_update_buddy(NULL
, PURPLE_BLIST_NODE(buddy
), TRUE
);
4679 if (gtkblist
->menutrayicon
) {
4680 gtk_widget_destroy(gtkblist
->menutrayicon
);
4681 gtkblist
->menutrayicon
= NULL
;
4684 ims
= pidgin_conversations_get_unseen_ims(PIDGIN_UNSEEN_TEXT
, FALSE
, 0);
4686 chats
= pidgin_conversations_get_unseen_chats(PIDGIN_UNSEEN_NICK
, FALSE
, 0);
4689 convs
= g_list_concat(ims
, chats
);
4690 else if(ims
&& !chats
)
4692 else if(!ims
&& chats
)
4696 GtkWidget
*img
= NULL
;
4697 GString
*tooltip_text
= NULL
;
4699 tooltip_text
= g_string_new("");
4703 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION((PurpleConversation
*)l
->data
);
4706 count
= gtkconv
->unseen_count
;
4707 else if(g_object_get_data(G_OBJECT(l
->data
), "unseen-count"))
4708 count
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(l
->data
), "unseen-count"));
4710 g_string_append_printf(tooltip_text
,
4711 ngettext("%d unread message from %s\n", "%d unread messages from %s\n", count
),
4712 count
, purple_conversation_get_title(l
->data
));
4715 if(tooltip_text
->len
> 0) {
4716 /* get rid of the last newline */
4717 g_string_truncate(tooltip_text
, tooltip_text
->len
-1);
4718 img
= gtk_image_new_from_stock(PIDGIN_STOCK_TOOLBAR_PENDING
,
4719 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
));
4721 gtkblist
->menutrayicon
= gtk_event_box_new();
4722 gtk_container_add(GTK_CONTAINER(gtkblist
->menutrayicon
), img
);
4723 gtk_widget_show(img
);
4724 gtk_widget_show(gtkblist
->menutrayicon
);
4725 g_signal_connect(G_OBJECT(gtkblist
->menutrayicon
), "button-press-event", G_CALLBACK(menutray_press_cb
), NULL
);
4727 pidgin_menu_tray_append(PIDGIN_MENU_TRAY(gtkblist
->menutray
), gtkblist
->menutrayicon
, tooltip_text
->str
);
4729 g_string_free(tooltip_text
, TRUE
);
4735 conversation_deleting_cb(PurpleConversation
*conv
, PidginBuddyList
*gtkblist
)
4737 conversation_updated_cb(conv
, PURPLE_CONVERSATION_UPDATE_UNSEEN
, gtkblist
);
4741 conversation_deleted_update_ui_cb(PurpleConversation
*conv
, struct _pidgin_blist_node
*ui
)
4743 if (ui
->conv
.conv
!= conv
)
4745 ui
->conv
.conv
= NULL
;
4747 ui
->conv
.last_message
= 0;
4751 written_msg_update_ui_cb(PurpleConversation
*conv
, PurpleMessage
*msg
, PurpleBlistNode
*node
)
4753 PidginBlistNode
*ui
= purple_blist_node_get_ui_data(node
);
4755 if (ui
->conv
.conv
!= conv
)
4758 if (!pidgin_conv_is_hidden(PIDGIN_CONVERSATION(conv
)))
4761 if (!(purple_message_get_flags(msg
) & (PURPLE_MESSAGE_SEND
| PURPLE_MESSAGE_RECV
)))
4764 ui
->conv
.flags
|= PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE
;
4765 if (PURPLE_IS_CHAT_CONVERSATION(conv
) && (purple_message_get_flags(msg
) & PURPLE_MESSAGE_NICK
))
4766 ui
->conv
.flags
|= PIDGIN_BLIST_CHAT_HAS_PENDING_MESSAGE_WITH_NICK
;
4768 ui
->conv
.last_message
= time(NULL
); /* XXX: for lack of better data */
4769 pidgin_blist_update(purple_blist_get_buddy_list(), node
);
4773 displayed_msg_update_ui_cb(PidginConversation
*gtkconv
, PurpleBlistNode
*node
)
4775 PidginBlistNode
*ui
= purple_blist_node_get_ui_data(node
);
4776 if (ui
->conv
.conv
!= gtkconv
->active_conv
)
4778 ui
->conv
.flags
&= ~(PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE
|
4779 PIDGIN_BLIST_CHAT_HAS_PENDING_MESSAGE_WITH_NICK
);
4780 pidgin_blist_update(purple_blist_get_buddy_list(), node
);
4784 conversation_created_cb(PurpleConversation
*conv
, PidginBuddyList
*gtkblist
)
4786 PurpleAccount
*account
= purple_conversation_get_account(conv
);
4788 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
4789 GSList
*buddies
= purple_blist_find_buddies(account
, purple_conversation_get_name(conv
));
4791 PurpleBlistNode
*buddy
= buddies
->data
;
4792 struct _pidgin_blist_node
*ui
= purple_blist_node_get_ui_data(buddy
);
4793 buddies
= g_slist_delete_link(buddies
, buddies
);
4796 ui
->conv
.conv
= conv
;
4798 ui
->conv
.last_message
= 0;
4799 purple_signal_connect(purple_conversations_get_handle(), "deleting-conversation",
4800 ui
, PURPLE_CALLBACK(conversation_deleted_update_ui_cb
), ui
);
4801 purple_signal_connect(purple_conversations_get_handle(), "wrote-im-msg",
4802 ui
, PURPLE_CALLBACK(written_msg_update_ui_cb
), buddy
);
4803 purple_signal_connect(pidgin_conversations_get_handle(), "conversation-displayed",
4804 ui
, PURPLE_CALLBACK(displayed_msg_update_ui_cb
), buddy
);
4806 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
4807 PurpleChat
*chat
= purple_blist_find_chat(account
, purple_conversation_get_name(conv
));
4808 struct _pidgin_blist_node
*ui
;
4811 ui
= purple_blist_node_get_ui_data(&(chat
->node
));
4814 ui
->conv
.conv
= conv
;
4816 ui
->conv
.last_message
= 0;
4817 purple_signal_connect(purple_conversations_get_handle(), "deleting-conversation",
4818 ui
, PURPLE_CALLBACK(conversation_deleted_update_ui_cb
), ui
);
4819 purple_signal_connect(purple_conversations_get_handle(), "wrote-chat-msg",
4820 ui
, PURPLE_CALLBACK(written_msg_update_ui_cb
), chat
);
4821 purple_signal_connect(pidgin_conversations_get_handle(), "conversation-displayed",
4822 ui
, PURPLE_CALLBACK(displayed_msg_update_ui_cb
), chat
);
4826 /**************************************************************************
4827 * GTK Buddy list GBoxed code
4828 **************************************************************************/
4829 static PidginBuddyList
*
4830 pidgin_buddy_list_ref(PidginBuddyList
*gtkblist
)
4832 PidginBuddyListPrivate
*priv
;
4834 g_return_val_if_fail(gtkblist
!= NULL
, NULL
);
4836 priv
= PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist
);
4843 pidgin_buddy_list_unref(PidginBuddyList
*gtkblist
)
4845 PidginBuddyListPrivate
*priv
;
4847 g_return_if_fail(gtkblist
!= NULL
);
4849 priv
= PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist
);
4851 g_return_if_fail(priv
->box_count
>= 0);
4853 if (!priv
->box_count
--)
4858 pidgin_buddy_list_get_type(void)
4860 static GType type
= 0;
4863 type
= g_boxed_type_register_static("PidginBuddyList",
4864 (GBoxedCopyFunc
)pidgin_buddy_list_ref
,
4865 (GBoxedFreeFunc
)pidgin_buddy_list_unref
);
4871 /**********************************************************************************
4872 * Public API Functions *
4873 **********************************************************************************/
4875 static void pidgin_blist_new_list(PurpleBuddyList
*blist
)
4877 PidginBuddyList
*gtkblist
;
4879 gtkblist
= g_new0(PidginBuddyList
, 1);
4880 gtkblist
->priv
= g_new0(PidginBuddyListPrivate
, 1);
4882 blist
->ui_data
= gtkblist
;
4885 static void pidgin_blist_new_node(PurpleBlistNode
*node
)
4887 purple_blist_node_set_ui_data(node
, g_new0(struct _pidgin_blist_node
, 1));
4890 gboolean
pidgin_blist_node_is_contact_expanded(PurpleBlistNode
*node
)
4892 if (PURPLE_IS_BUDDY(node
)) {
4893 node
= node
->parent
;
4898 g_return_val_if_fail(PURPLE_IS_CONTACT(node
), FALSE
);
4900 return ((struct _pidgin_blist_node
*)purple_blist_node_get_ui_data(node
))->contact_expanded
;
4912 void pidgin_blist_setup_sort_methods()
4916 pidgin_blist_sort_method_reg("none", _("Manually"), sort_method_none
);
4917 pidgin_blist_sort_method_reg("alphabetical", _("Alphabetically"), sort_method_alphabetical
);
4918 pidgin_blist_sort_method_reg("status", _("By status"), sort_method_status
);
4919 pidgin_blist_sort_method_reg("log_size", _("By recent log activity"), sort_method_log_activity
);
4921 id
= purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/blist/sort_type");
4923 purple_debug_warning("gtkblist", "Sort method was NULL, resetting to alphabetical\n");
4924 id
= "alphabetical";
4926 pidgin_blist_sort_method_set(id
);
4929 static void _prefs_change_redo_list(const char *name
, PurplePrefType type
,
4930 gconstpointer val
, gpointer data
)
4932 GtkTreeSelection
*sel
;
4934 PurpleBlistNode
*node
= NULL
;
4936 sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist
->treeview
));
4937 if (gtk_tree_selection_get_selected(sel
, NULL
, &iter
))
4939 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
, NODE_COLUMN
, &node
, -1);
4942 redo_buddy_list(purple_blist_get_buddy_list(), FALSE
, FALSE
);
4943 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(gtkblist
->treeview
));
4947 struct _pidgin_blist_node
*gtknode
;
4950 gtknode
= purple_blist_node_get_ui_data(node
);
4951 if (gtknode
&& gtknode
->row
)
4953 path
= gtk_tree_row_reference_get_path(gtknode
->row
);
4954 gtk_tree_selection_select_path(sel
, path
);
4955 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(gtkblist
->treeview
), path
, NULL
, FALSE
, 0, 0);
4956 gtk_tree_path_free(path
);
4961 static void _prefs_change_sort_method(const char *pref_name
, PurplePrefType type
,
4962 gconstpointer val
, gpointer data
)
4964 if(!strcmp(pref_name
, PIDGIN_PREFS_ROOT
"/blist/sort_type"))
4965 pidgin_blist_sort_method_set(val
);
4968 static gboolean
pidgin_blist_select_notebook_page_cb(gpointer user_data
)
4970 PidginBuddyList
*gtkblist
= (PidginBuddyList
*)user_data
;
4973 PidginBuddyListPrivate
*priv
;
4975 priv
= PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist
);
4977 priv
->select_notebook_page_timeout
= 0;
4979 /* this is far too ugly thanks to me not wanting to fix #3989 properly right now */
4980 if (priv
->error_scrollbook
!= NULL
) {
4981 errors
= gtk_notebook_get_n_pages(GTK_NOTEBOOK(priv
->error_scrollbook
->notebook
));
4983 if ((list
= purple_accounts_get_all_active()) != NULL
|| errors
) {
4984 gtk_notebook_set_current_page(GTK_NOTEBOOK(gtkblist
->notebook
), 1);
4987 gtk_notebook_set_current_page(GTK_NOTEBOOK(gtkblist
->notebook
), 0);
4989 priv
->select_notebook_page_timeout
= 0;
4993 static void pidgin_blist_select_notebook_page(PidginBuddyList
*gtkblist
)
4995 PidginBuddyListPrivate
*priv
= PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist
);
4996 priv
->select_notebook_page_timeout
= purple_timeout_add(0,
4997 pidgin_blist_select_notebook_page_cb
, gtkblist
);
5000 static void account_modified(PurpleAccount
*account
, PidginBuddyList
*gtkblist
)
5005 pidgin_blist_select_notebook_page(gtkblist
);
5006 update_menu_bar(gtkblist
);
5010 account_actions_changed(PurpleAccount
*account
, gpointer data
)
5012 pidgin_blist_update_accounts_menu();
5016 account_status_changed(PurpleAccount
*account
, PurpleStatus
*old
,
5017 PurpleStatus
*new, PidginBuddyList
*gtkblist
)
5022 account_modified(account
, gtkblist
);
5026 gtk_blist_window_key_press_cb(GtkWidget
*w
, GdkEventKey
*event
, PidginBuddyList
*gtkblist
)
5028 /* clear any tooltips */
5029 pidgin_blist_tooltip_destroy();
5035 reset_headline(PidginBuddyList
*gtkblist
)
5037 gtkblist
->headline_callback
= NULL
;
5038 gtkblist
->headline_data
= NULL
;
5039 gtkblist
->headline_destroy
= NULL
;
5040 pidgin_set_urgent(GTK_WINDOW(gtkblist
->window
), FALSE
);
5044 headline_click_callback(gpointer unused
)
5046 if (gtkblist
->headline_callback
)
5047 ((GSourceFunc
) gtkblist
->headline_callback
)(gtkblist
->headline_data
);
5048 reset_headline(gtkblist
);
5053 headline_response_cb(GtkInfoBar
*infobar
, int resp
, PidginBuddyList
*gtkblist
)
5055 gtk_widget_hide(gtkblist
->headline
);
5057 if (resp
== GTK_RESPONSE_OK
) {
5058 if (gtkblist
->headline_callback
)
5059 g_idle_add(headline_click_callback
, NULL
);
5061 if (gtkblist
->headline_destroy
)
5062 gtkblist
->headline_destroy(gtkblist
->headline_data
);
5063 reset_headline(gtkblist
);
5066 if (gtkblist
->headline_destroy
)
5067 gtkblist
->headline_destroy(gtkblist
->headline_data
);
5068 reset_headline(gtkblist
);
5075 headline_realize_cb(GtkWidget
*widget
, gpointer data
)
5077 GdkWindow
*window
= gtk_widget_get_window(widget
);
5078 GdkDisplay
*display
= gdk_window_get_display(window
);
5079 GdkCursor
*hand_cursor
= gdk_cursor_new_for_display(display
, GDK_HAND2
);
5080 gdk_window_set_cursor(window
, hand_cursor
);
5081 g_object_unref(hand_cursor
);
5085 headline_press_cb(GtkWidget
*widget
, GdkEventButton
*event
, GtkInfoBar
*infobar
)
5087 gtk_info_bar_response(infobar
, GTK_RESPONSE_OK
);
5091 /***********************************/
5092 /* Connection error handling stuff */
5093 /***********************************/
5095 #define OBJECT_DATA_KEY_ACCOUNT "account"
5096 #define DO_NOT_CLEAR_ERROR "do-not-clear-error"
5099 find_account_widget(GObject
*widget
,
5100 PurpleAccount
*account
)
5102 if (g_object_get_data(widget
, OBJECT_DATA_KEY_ACCOUNT
) == account
)
5103 return 0; /* found */
5109 pack_protocol_icon_start(GtkWidget
*box
,
5110 PurpleAccount
*account
)
5115 pixbuf
= pidgin_create_protocol_icon(account
, PIDGIN_PROTOCOL_ICON_SMALL
);
5116 if (pixbuf
!= NULL
) {
5117 image
= gtk_image_new_from_pixbuf(pixbuf
);
5118 g_object_unref(pixbuf
);
5120 gtk_box_pack_start(GTK_BOX(box
), image
, FALSE
, FALSE
, 0);
5125 add_error_dialog(PidginBuddyList
*gtkblist
,
5128 PidginBuddyListPrivate
*priv
= PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist
);
5129 gtk_container_add(GTK_CONTAINER(priv
->error_scrollbook
), dialog
);
5133 find_child_widget_by_account(GtkContainer
*container
,
5134 PurpleAccount
*account
)
5137 GList
*children
= NULL
;
5138 GtkWidget
*ret
= NULL
;
5139 /* XXX: Workaround for the currently incomplete implementation of PidginScrollBook */
5140 if (PIDGIN_IS_SCROLL_BOOK(container
))
5141 container
= GTK_CONTAINER(PIDGIN_SCROLL_BOOK(container
)->notebook
);
5142 children
= gtk_container_get_children(container
);
5143 l
= g_list_find_custom(children
, account
, (GCompareFunc
) find_account_widget
);
5145 ret
= GTK_WIDGET(l
->data
);
5146 g_list_free(children
);
5151 remove_child_widget_by_account(GtkContainer
*container
,
5152 PurpleAccount
*account
)
5154 GtkWidget
*widget
= find_child_widget_by_account(container
, account
);
5156 /* Since we are destroying the widget in response to a change in
5157 * error, we should not clear the error.
5159 g_object_set_data(G_OBJECT(widget
), DO_NOT_CLEAR_ERROR
,
5160 GINT_TO_POINTER(TRUE
));
5161 gtk_widget_destroy(widget
);
5165 /* Generic error buttons */
5168 generic_error_modify_cb(PurpleAccount
*account
)
5170 purple_account_clear_current_error(account
);
5171 pidgin_account_dialog_show(PIDGIN_MODIFY_ACCOUNT_DIALOG
, account
);
5175 generic_error_enable_cb(PurpleAccount
*account
)
5177 purple_account_clear_current_error(account
);
5178 purple_account_set_enabled(account
, purple_core_get_ui(), TRUE
);
5182 generic_error_destroy_cb(GtkWidget
*dialog
,
5183 PurpleAccount
*account
)
5185 /* If the error dialog is being destroyed in response to the
5186 * account-error-changed signal, we don't want to clear the current
5189 if (g_object_get_data(G_OBJECT(dialog
), DO_NOT_CLEAR_ERROR
) == NULL
)
5190 purple_account_clear_current_error(account
);
5193 #define SSL_FAQ_URI "https://developer.pidgin.im/wiki/FAQssl"
5196 ssl_faq_clicked_cb(PidginMiniDialog
*mini_dialog
,
5200 purple_notify_uri(NULL
, SSL_FAQ_URI
);
5204 add_generic_error_dialog(PurpleAccount
*account
,
5205 const PurpleConnectionErrorInfo
*err
)
5207 GtkWidget
*mini_dialog
;
5208 const char *username
= purple_account_get_username(account
);
5210 purple_account_get_enabled(account
, purple_core_get_ui());
5214 primary
= g_strdup_printf(_("%s disconnected"), username
);
5216 primary
= g_strdup_printf(_("%s disabled"), username
);
5218 mini_dialog
= pidgin_make_mini_dialog(NULL
, PIDGIN_STOCK_DIALOG_ERROR
,
5219 primary
, err
->description
, account
,
5220 (enabled
? _("Reconnect") : _("Re-enable")),
5221 (enabled
? PURPLE_CALLBACK(purple_account_connect
)
5222 : PURPLE_CALLBACK(generic_error_enable_cb
)),
5223 _("Modify Account"), PURPLE_CALLBACK(generic_error_modify_cb
),
5228 g_object_set_data(G_OBJECT(mini_dialog
), OBJECT_DATA_KEY_ACCOUNT
,
5231 if(err
->type
== PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT
)
5232 pidgin_mini_dialog_add_non_closing_button(PIDGIN_MINI_DIALOG(mini_dialog
),
5233 _("SSL FAQs"), ssl_faq_clicked_cb
, NULL
);
5235 g_signal_connect_after(mini_dialog
, "destroy",
5236 (GCallback
)generic_error_destroy_cb
,
5239 add_error_dialog(gtkblist
, mini_dialog
);
5243 remove_generic_error_dialog(PurpleAccount
*account
)
5245 PidginBuddyListPrivate
*priv
= PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist
);
5246 remove_child_widget_by_account(
5247 GTK_CONTAINER(priv
->error_scrollbook
), account
);
5252 update_generic_error_message(PurpleAccount
*account
,
5253 const char *description
)
5255 PidginBuddyListPrivate
*priv
= PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist
);
5256 GtkWidget
*mini_dialog
= find_child_widget_by_account(
5257 GTK_CONTAINER(priv
->error_scrollbook
), account
);
5258 pidgin_mini_dialog_set_description(PIDGIN_MINI_DIALOG(mini_dialog
),
5263 /* Notifications about accounts which were disconnected with
5264 * PURPLE_CONNECTION_ERROR_NAME_IN_USE
5267 typedef void (*AccountFunction
)(PurpleAccount
*);
5270 elsewhere_foreach_account(PidginMiniDialog
*mini_dialog
,
5273 PurpleAccount
*account
;
5274 GList
*labels
= gtk_container_get_children(
5275 GTK_CONTAINER(mini_dialog
->contents
));
5278 for (l
= labels
; l
; l
= l
->next
) {
5279 account
= g_object_get_data(G_OBJECT(l
->data
), OBJECT_DATA_KEY_ACCOUNT
);
5283 purple_debug_warning("gtkblist", "mini_dialog's child "
5284 "didn't have an account stored in it!");
5286 g_list_free(labels
);
5290 enable_account(PurpleAccount
*account
)
5292 purple_account_set_enabled(account
, purple_core_get_ui(), TRUE
);
5296 reconnect_elsewhere_accounts(PidginMiniDialog
*mini_dialog
,
5300 elsewhere_foreach_account(mini_dialog
, enable_account
);
5304 clear_elsewhere_errors(PidginMiniDialog
*mini_dialog
,
5307 elsewhere_foreach_account(mini_dialog
, purple_account_clear_current_error
);
5311 ensure_signed_on_elsewhere_minidialog(PidginBuddyList
*gtkblist
)
5313 PidginBuddyListPrivate
*priv
= PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist
);
5314 PidginMiniDialog
*mini_dialog
;
5316 if(priv
->signed_on_elsewhere
)
5319 mini_dialog
= priv
->signed_on_elsewhere
=
5320 pidgin_mini_dialog_new(_("Welcome back!"), NULL
, PIDGIN_STOCK_DISCONNECT
);
5322 pidgin_mini_dialog_add_button(mini_dialog
, _("Re-enable"),
5323 reconnect_elsewhere_accounts
, NULL
);
5325 /* Make dismissing the dialog clear the errors. The "destroy" signal
5326 * does not appear to fire at quit, which is fortunate!
5328 g_signal_connect(G_OBJECT(mini_dialog
), "destroy",
5329 (GCallback
) clear_elsewhere_errors
, NULL
);
5331 add_error_dialog(gtkblist
, GTK_WIDGET(mini_dialog
));
5333 /* Set priv->signed_on_elsewhere to NULL when the dialog is destroyed */
5334 g_signal_connect(G_OBJECT(mini_dialog
), "destroy",
5335 (GCallback
) gtk_widget_destroyed
, &(priv
->signed_on_elsewhere
));
5339 update_signed_on_elsewhere_minidialog_title(void)
5341 PidginBuddyListPrivate
*priv
= PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist
);
5342 PidginMiniDialog
*mini_dialog
= priv
->signed_on_elsewhere
;
5346 if (mini_dialog
== NULL
)
5349 accounts
= pidgin_mini_dialog_get_num_children(mini_dialog
);
5350 if (accounts
== 0) {
5351 gtk_widget_destroy(GTK_WIDGET(mini_dialog
));
5355 title
= g_strdup_printf(
5356 ngettext("%d account was disabled because you signed on from another location:",
5357 "%d accounts were disabled because you signed on from another location:",
5360 pidgin_mini_dialog_set_description(mini_dialog
, title
);
5365 create_account_label(PurpleAccount
*account
)
5367 GtkWidget
*hbox
, *label
;
5368 const char *username
= purple_account_get_username(account
);
5372 hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 6);
5373 g_object_set_data(G_OBJECT(hbox
), OBJECT_DATA_KEY_ACCOUNT
, account
);
5375 pack_protocol_icon_start(hbox
, account
);
5377 label
= gtk_label_new(NULL
);
5378 markup
= g_strdup_printf("<span size=\"smaller\">%s</span>", username
);
5379 gtk_label_set_markup(GTK_LABEL(label
), markup
);
5381 gtk_label_set_xalign(GTK_LABEL(label
), 0);
5382 gtk_label_set_yalign(GTK_LABEL(label
), 0);
5383 g_object_set(G_OBJECT(label
), "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
5384 description
= purple_account_get_current_error(account
)->description
;
5385 if (description
!= NULL
&& *description
!= '\0')
5386 gtk_widget_set_tooltip_text(label
, description
);
5387 gtk_box_pack_start(GTK_BOX(hbox
), label
, TRUE
, TRUE
, 0);
5393 add_to_signed_on_elsewhere(PurpleAccount
*account
)
5395 PidginBuddyListPrivate
*priv
= PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist
);
5396 PidginMiniDialog
*mini_dialog
;
5397 GtkWidget
*account_label
;
5399 ensure_signed_on_elsewhere_minidialog(gtkblist
);
5400 mini_dialog
= priv
->signed_on_elsewhere
;
5402 if(find_child_widget_by_account(GTK_CONTAINER(mini_dialog
->contents
), account
))
5405 account_label
= create_account_label(account
);
5406 gtk_box_pack_start(mini_dialog
->contents
, account_label
, FALSE
, FALSE
, 0);
5407 gtk_widget_show_all(account_label
);
5409 update_signed_on_elsewhere_minidialog_title();
5413 remove_from_signed_on_elsewhere(PurpleAccount
*account
)
5415 PidginBuddyListPrivate
*priv
= PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist
);
5416 PidginMiniDialog
*mini_dialog
= priv
->signed_on_elsewhere
;
5417 if(mini_dialog
== NULL
)
5420 remove_child_widget_by_account(GTK_CONTAINER(mini_dialog
->contents
), account
);
5422 update_signed_on_elsewhere_minidialog_title();
5427 update_signed_on_elsewhere_tooltip(PurpleAccount
*account
,
5428 const char *description
)
5430 PidginBuddyListPrivate
*priv
= PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist
);
5431 GtkContainer
*c
= GTK_CONTAINER(priv
->signed_on_elsewhere
->contents
);
5432 GtkWidget
*label
= find_child_widget_by_account(c
, account
);
5433 gtk_widget_set_tooltip_text(label
, description
);
5437 /* Call appropriate error notification code based on error types */
5439 update_account_error_state(PurpleAccount
*account
,
5440 const PurpleConnectionErrorInfo
*old
,
5441 const PurpleConnectionErrorInfo
*new,
5442 PidginBuddyList
*gtkblist
)
5444 gboolean descriptions_differ
;
5447 if (old
== NULL
&& new == NULL
)
5451 pidgin_blist_select_notebook_page(gtkblist
);
5453 if (old
!= NULL
&& new == NULL
) {
5454 if(old
->type
== PURPLE_CONNECTION_ERROR_NAME_IN_USE
)
5455 remove_from_signed_on_elsewhere(account
);
5457 remove_generic_error_dialog(account
);
5461 if (old
== NULL
&& new != NULL
) {
5462 if(new->type
== PURPLE_CONNECTION_ERROR_NAME_IN_USE
)
5463 add_to_signed_on_elsewhere(account
);
5465 add_generic_error_dialog(account
, new);
5469 /* else, new and old are both non-NULL */
5471 descriptions_differ
= strcmp(old
->description
, new->description
);
5472 desc
= new->description
;
5474 switch (new->type
) {
5475 case PURPLE_CONNECTION_ERROR_NAME_IN_USE
:
5476 if (old
->type
== PURPLE_CONNECTION_ERROR_NAME_IN_USE
5477 && descriptions_differ
) {
5478 update_signed_on_elsewhere_tooltip(account
, desc
);
5480 remove_generic_error_dialog(account
);
5481 add_to_signed_on_elsewhere(account
);
5485 if (old
->type
== PURPLE_CONNECTION_ERROR_NAME_IN_USE
) {
5486 remove_from_signed_on_elsewhere(account
);
5487 add_generic_error_dialog(account
, new);
5488 } else if (descriptions_differ
) {
5489 update_generic_error_message(account
, desc
);
5495 /* In case accounts are loaded before the blist (which they currently are),
5496 * let's call update_account_error_state ourselves on every account's current
5497 * state when the blist starts.
5500 show_initial_account_errors(PidginBuddyList
*gtkblist
)
5502 GList
*l
= purple_accounts_get_all();
5503 PurpleAccount
*account
;
5504 const PurpleConnectionErrorInfo
*err
;
5506 for (; l
; l
= l
->next
)
5509 err
= purple_account_get_current_error(account
);
5511 update_account_error_state(account
, NULL
, err
, gtkblist
);
5515 /* This assumes there are not things like groupless buddies or multi-leveled groups.
5516 * I'm sure other things in this code assumes that also.
5519 treeview_style_set(GtkWidget
*widget
,
5522 PurpleBuddyList
*list
= data
;
5523 PurpleBlistNode
*node
= list
->root
;
5525 pidgin_blist_update_group(list
, node
);
5530 /******************************************/
5531 /* End of connection error handling stuff */
5532 /******************************************/
5535 blist_focus_cb(GtkWidget
*widget
, GdkEventFocus
*event
, PidginBuddyList
*gtkblist
)
5538 gtk_blist_focused
= TRUE
;
5539 pidgin_set_urgent(GTK_WINDOW(gtkblist
->window
), FALSE
);
5541 gtk_blist_focused
= FALSE
;
5550 GtkWidget
*ret
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, PIDGIN_HIG_BOX_SPACE
);
5556 label
= gtk_label_new(NULL
);
5557 gtk_box_pack_start(GTK_BOX(ret
), label
, TRUE
, TRUE
, 0);
5559 label
= gtk_label_new(NULL
);
5560 gtk_label_set_markup(GTK_LABEL(label
), _("<b>Username:</b>"));
5561 gtk_label_set_xalign(GTK_LABEL(label
), 0.0);
5562 gtk_box_pack_start(GTK_BOX(ret
), label
, FALSE
, FALSE
, 0);
5563 entry
= gtk_entry_new();
5564 gtk_box_pack_start(GTK_BOX(ret
), entry
, FALSE
, FALSE
, 0);
5566 label
= gtk_label_new(NULL
);
5567 gtk_label_set_markup(GTK_LABEL(label
), _("<b>Password:</b>"));
5568 gtk_label_set_xalign(GTK_LABEL(label
), 0.0);
5569 gtk_box_pack_start(GTK_BOX(ret
), label
, FALSE
, FALSE
, 0);
5570 entry
= gtk_entry_new();
5571 gtk_entry_set_visibility(GTK_ENTRY(entry
), FALSE
);
5572 gtk_box_pack_start(GTK_BOX(ret
), entry
, FALSE
, FALSE
, 0);
5574 label
= gtk_label_new(" ");
5575 gtk_box_pack_start(GTK_BOX(ret
), label
, FALSE
, FALSE
, 0);
5577 bbox
= gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL
);
5578 button
= gtk_button_new_with_mnemonic(_("_Login"));
5579 gtk_box_pack_start(GTK_BOX(ret
), bbox
, FALSE
, FALSE
, 0);
5580 gtk_container_add(GTK_CONTAINER(bbox
), button
);
5583 label
= gtk_label_new(NULL
);
5584 gtk_box_pack_start(GTK_BOX(ret
), label
, TRUE
, TRUE
, 0);
5586 gtk_container_set_border_width(GTK_CONTAINER(ret
), PIDGIN_HIG_BORDER
);
5588 gtk_widget_show_all(ret
);
5593 /* builds the blist layout according to to the current theme */
5595 pidgin_blist_build_layout(PurpleBuddyList
*list
)
5597 GtkTreeViewColumn
*column
;
5598 PidginBlistLayout
*layout
;
5599 PidginBlistTheme
*theme
;
5600 GtkCellRenderer
*rend
;
5601 gint i
, status_icon
= 0, text
= 1, emblem
= 2, protocol_icon
= 3, buddy_icon
= 4;
5604 column
= gtkblist
->text_column
;
5606 if ((theme
= pidgin_blist_get_theme()) != NULL
&& (layout
= pidgin_blist_theme_get_layout(theme
)) != NULL
) {
5607 status_icon
= layout
->status_icon
;
5608 text
= layout
->text
;
5609 emblem
= layout
->emblem
;
5610 protocol_icon
= layout
->protocol_icon
;
5611 buddy_icon
= layout
->buddy_icon
;
5614 gtk_tree_view_column_clear(column
);
5617 rend
= pidgin_cell_renderer_expander_new();
5618 gtk_tree_view_column_pack_start(column
, rend
, FALSE
);
5619 gtk_tree_view_column_set_attributes(column
, rend
,
5620 "visible", GROUP_EXPANDER_VISIBLE_COLUMN
,
5621 "expander-visible", GROUP_EXPANDER_COLUMN
,
5622 "sensitive", GROUP_EXPANDER_COLUMN
,
5623 "cell-background-rgba", BGCOLOR_COLUMN
,
5627 rend
= pidgin_cell_renderer_expander_new();
5628 gtk_tree_view_column_pack_start(column
, rend
, FALSE
);
5629 gtk_tree_view_column_set_attributes(column
, rend
,
5630 "visible", CONTACT_EXPANDER_VISIBLE_COLUMN
,
5631 "expander-visible", CONTACT_EXPANDER_COLUMN
,
5632 "sensitive", CONTACT_EXPANDER_COLUMN
,
5633 "cell-background-rgba", BGCOLOR_COLUMN
,
5636 for (i
= 0; i
< 5; i
++) {
5638 if (status_icon
== i
) {
5640 rend
= gtk_cell_renderer_pixbuf_new();
5641 gtk_tree_view_column_pack_start(column
, rend
, FALSE
);
5642 gtk_tree_view_column_set_attributes(column
, rend
,
5643 "pixbuf", STATUS_ICON_COLUMN
,
5644 "visible", STATUS_ICON_VISIBLE_COLUMN
,
5645 "cell-background-rgba", BGCOLOR_COLUMN
,
5647 g_object_set(rend
, "xalign", 0.0, "xpad", 6, "ypad", 0, NULL
);
5649 } else if (text
== i
) {
5651 gtkblist
->text_rend
= rend
= gtk_cell_renderer_text_new();
5652 gtk_tree_view_column_pack_start(column
, rend
, TRUE
);
5653 gtk_tree_view_column_set_attributes(column
, rend
,
5654 "cell-background-rgba", BGCOLOR_COLUMN
,
5655 "markup", NAME_COLUMN
,
5657 g_signal_connect(G_OBJECT(rend
), "editing-started", G_CALLBACK(gtk_blist_renderer_editing_started_cb
), NULL
);
5658 g_signal_connect(G_OBJECT(rend
), "editing-canceled", G_CALLBACK(gtk_blist_renderer_editing_cancelled_cb
), list
);
5659 g_signal_connect(G_OBJECT(rend
), "edited", G_CALLBACK(gtk_blist_renderer_edited_cb
), list
);
5660 g_object_set(rend
, "ypad", 0, "yalign", 0.5, NULL
);
5661 g_object_set(rend
, "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
5664 rend
= gtk_cell_renderer_text_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
,
5668 "markup", IDLE_COLUMN
,
5669 "visible", IDLE_VISIBLE_COLUMN
,
5670 "cell-background-rgba", BGCOLOR_COLUMN
,
5672 } else if (emblem
== i
) {
5674 rend
= gtk_cell_renderer_pixbuf_new();
5675 g_object_set(rend
, "xalign", 1.0, "yalign", 0.5, "ypad", 0, "xpad", 3, NULL
);
5676 gtk_tree_view_column_pack_start(column
, rend
, FALSE
);
5677 gtk_tree_view_column_set_attributes(column
, rend
, "pixbuf", EMBLEM_COLUMN
,
5678 "cell-background-rgba", BGCOLOR_COLUMN
,
5679 "visible", EMBLEM_VISIBLE_COLUMN
, NULL
);
5681 } else if (protocol_icon
== i
) {
5683 rend
= gtk_cell_renderer_pixbuf_new();
5684 gtk_tree_view_column_pack_start(column
, rend
, FALSE
);
5685 gtk_tree_view_column_set_attributes(column
, rend
,
5686 "pixbuf", PROTOCOL_ICON_COLUMN
,
5687 "visible", PROTOCOL_ICON_VISIBLE_COLUMN
,
5688 "cell-background-rgba", BGCOLOR_COLUMN
,
5690 g_object_set(rend
, "xalign", 0.0, "xpad", 3, "ypad", 0, NULL
);
5692 } else if (buddy_icon
== i
) {
5694 rend
= gtk_cell_renderer_pixbuf_new();
5695 g_object_set(rend
, "xalign", 1.0, "ypad", 0, NULL
);
5696 gtk_tree_view_column_pack_start(column
, rend
, FALSE
);
5697 gtk_tree_view_column_set_attributes(column
, rend
, "pixbuf", BUDDY_ICON_COLUMN
,
5698 "cell-background-rgba", BGCOLOR_COLUMN
,
5699 "visible", BUDDY_ICON_VISIBLE_COLUMN
,
5708 pidgin_blist_search_equal_func(GtkTreeModel
*model
, gint column
,
5709 const gchar
*key
, GtkTreeIter
*iter
, gpointer data
)
5711 PurpleBlistNode
*node
= NULL
;
5712 gboolean res
= TRUE
;
5713 const char *compare
= NULL
;
5715 if (!pidgin_tree_view_search_equal_func(model
, column
, key
, iter
, data
))
5718 /* If the search string does not match the displayed label, then look
5719 * at the alternate labels for the nodes and search in them. Currently,
5720 * alternate labels that make sense are usernames/email addresses for
5721 * buddies (but only for the ones who don't have a local alias).
5724 gtk_tree_model_get(model
, iter
, NODE_COLUMN
, &node
, -1);
5729 if (PURPLE_IS_CONTACT(node
)) {
5730 PurpleBuddy
*b
= purple_contact_get_priority_buddy(PURPLE_CONTACT(node
));
5731 if (!purple_buddy_get_local_alias(b
))
5732 compare
= purple_buddy_get_name(b
);
5733 } else if (PURPLE_IS_BUDDY(node
)) {
5734 if (!purple_buddy_get_local_alias(PURPLE_BUDDY(node
)))
5735 compare
= purple_buddy_get_name(PURPLE_BUDDY(node
));
5739 char *tmp
, *enteredstring
;
5740 tmp
= g_utf8_normalize(key
, -1, G_NORMALIZE_DEFAULT
);
5741 enteredstring
= g_utf8_casefold(tmp
, -1);
5744 if (purple_str_has_prefix(compare
, enteredstring
))
5746 g_free(enteredstring
);
5752 static void pidgin_blist_show(PurpleBuddyList
*list
)
5754 PidginBuddyListPrivate
*priv
;
5756 GtkTreeViewColumn
*column
;
5760 GtkWidget
*content_area
;
5764 const char *theme_name
;
5765 GtkActionGroup
*action_group
;
5767 GtkAccelGroup
*accel_group
;
5768 GtkTreeSelection
*selection
;
5769 GtkTargetEntry dte
[] = {{"PURPLE_BLIST_NODE", GTK_TARGET_SAME_APP
, DRAG_ROW
},
5770 {"application/x-im-contact", 0, DRAG_BUDDY
},
5771 {"text/x-vcard", 0, DRAG_VCARD
},
5772 {"text/uri-list", 0, DRAG_URI
},
5773 {"text/plain", 0, DRAG_TEXT
}};
5774 GtkTargetEntry ste
[] = {{"PURPLE_BLIST_NODE", GTK_TARGET_SAME_APP
, DRAG_ROW
},
5775 {"application/x-im-contact", 0, DRAG_BUDDY
},
5776 {"text/x-vcard", 0, DRAG_VCARD
}};
5777 if (gtkblist
&& gtkblist
->window
) {
5778 purple_blist_set_visible(purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/list_visible"));
5782 gtkblist
= PIDGIN_BLIST(list
);
5783 priv
= PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist
);
5785 if (priv
->current_theme
)
5786 g_object_unref(priv
->current_theme
);
5788 theme_name
= purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/blist/theme");
5789 if (theme_name
&& *theme_name
)
5790 priv
->current_theme
= g_object_ref(PIDGIN_BLIST_THEME(purple_theme_manager_find_theme(theme_name
, "blist")));
5792 priv
->current_theme
= NULL
;
5794 gtkblist
->empty_avatar
= gdk_pixbuf_new(GDK_COLORSPACE_RGB
, TRUE
, 8, 32, 32);
5795 gdk_pixbuf_fill(gtkblist
->empty_avatar
, 0x00000000);
5797 gtkblist
->window
= pidgin_create_window(_("Buddy List"), 0, "buddy_list", TRUE
);
5798 g_signal_connect(G_OBJECT(gtkblist
->window
), "focus-in-event",
5799 G_CALLBACK(blist_focus_cb
), gtkblist
);
5800 g_signal_connect(G_OBJECT(gtkblist
->window
), "focus-out-event",
5801 G_CALLBACK(blist_focus_cb
), gtkblist
);
5803 gtkblist
->main_vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0);
5804 gtk_widget_show(gtkblist
->main_vbox
);
5805 gtk_container_add(GTK_CONTAINER(gtkblist
->window
), gtkblist
->main_vbox
);
5807 g_signal_connect(G_OBJECT(gtkblist
->window
), "delete_event", G_CALLBACK(gtk_blist_delete_cb
), NULL
);
5808 g_signal_connect(G_OBJECT(gtkblist
->window
), "configure_event", G_CALLBACK(gtk_blist_configure_cb
), NULL
);
5809 g_signal_connect(G_OBJECT(gtkblist
->window
), "visibility_notify_event", G_CALLBACK(gtk_blist_visibility_cb
), NULL
);
5810 g_signal_connect(G_OBJECT(gtkblist
->window
), "window_state_event", G_CALLBACK(gtk_blist_window_state_cb
), NULL
);
5811 g_signal_connect(G_OBJECT(gtkblist
->window
), "key_press_event", G_CALLBACK(gtk_blist_window_key_press_cb
), gtkblist
);
5812 gtk_widget_add_events(gtkblist
->window
, GDK_VISIBILITY_NOTIFY_MASK
);
5814 /******************************* Menu bar *************************************/
5815 action_group
= gtk_action_group_new("BListActions");
5817 gtk_action_group_set_translation_domain(action_group
, PACKAGE
);
5819 gtk_action_group_add_actions(action_group
,
5821 G_N_ELEMENTS(blist_menu_entries
),
5822 GTK_WINDOW(gtkblist
->window
));
5823 gtk_action_group_add_toggle_actions(action_group
,
5824 blist_menu_toggle_entries
,
5825 G_N_ELEMENTS(blist_menu_toggle_entries
),
5826 GTK_WINDOW(gtkblist
->window
));
5828 gtkblist
->ui
= gtk_ui_manager_new();
5829 gtk_ui_manager_insert_action_group(gtkblist
->ui
, action_group
, 0);
5831 accel_group
= gtk_ui_manager_get_accel_group(gtkblist
->ui
);
5832 gtk_window_add_accel_group(GTK_WINDOW(gtkblist
->window
), accel_group
);
5833 pidgin_load_accels();
5834 g_signal_connect(G_OBJECT(accel_group
), "accel-changed", G_CALLBACK(pidgin_save_accels_cb
), NULL
);
5837 if (!gtk_ui_manager_add_ui_from_string(gtkblist
->ui
, blist_menu
, -1, &error
))
5839 g_message("building menus failed: %s", error
->message
);
5840 g_error_free(error
);
5844 menu
= gtk_ui_manager_get_widget(gtkblist
->ui
, "/BList");
5845 gtkblist
->menutray
= pidgin_menu_tray_new();
5846 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), gtkblist
->menutray
);
5847 gtk_widget_show(gtkblist
->menutray
);
5848 gtk_widget_show(menu
);
5849 gtk_box_pack_start(GTK_BOX(gtkblist
->main_vbox
), menu
, FALSE
, FALSE
, 0);
5851 menu
= gtk_ui_manager_get_widget(gtkblist
->ui
, "/BList/AccountsMenu");
5852 accountmenu
= gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu
));
5854 /****************************** Notebook *************************************/
5855 gtkblist
->notebook
= gtk_notebook_new();
5856 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(gtkblist
->notebook
), FALSE
);
5857 gtk_notebook_set_show_border(GTK_NOTEBOOK(gtkblist
->notebook
), FALSE
);
5858 gtk_box_pack_start(GTK_BOX(gtkblist
->main_vbox
), gtkblist
->notebook
, TRUE
, TRUE
, 0);
5861 gtk_notebook_append_page(GTK_NOTEBOOK(gtkblist
->notebook
), kiosk_page(), NULL
);
5864 /* Translators: Please maintain the use of -> and <- to refer to menu heirarchy */
5865 tmp
= g_strdup_printf(_("<span weight='bold' size='larger'>Welcome to %s!</span>\n\n"
5867 "You have no accounts enabled. Enable your IM accounts from the "
5868 "<b>Accounts</b> window at <b>Accounts->Manage Accounts</b>. Once you "
5869 "enable accounts, you'll be able to sign on, set your status, "
5870 "and talk to your friends."), PIDGIN_NAME
);
5871 pretty
= pidgin_make_pretty_arrows(tmp
);
5873 label
= gtk_label_new(NULL
);
5874 gtk_label_set_line_wrap(GTK_LABEL(label
), TRUE
);
5875 gtk_label_set_yalign(GTK_LABEL(label
), 0.2);
5876 gtk_label_set_markup(GTK_LABEL(label
), pretty
);
5878 gtk_notebook_append_page(GTK_NOTEBOOK(gtkblist
->notebook
),label
, NULL
);
5879 gtkblist
->vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0);
5880 gtk_notebook_append_page(GTK_NOTEBOOK(gtkblist
->notebook
), gtkblist
->vbox
, NULL
);
5881 gtk_widget_show_all(gtkblist
->notebook
);
5882 pidgin_blist_select_notebook_page(gtkblist
);
5884 /****************************** Headline **********************************/
5886 gtkblist
->headline
= gtk_event_box_new();
5887 gtk_box_pack_start(GTK_BOX(gtkblist
->vbox
), gtkblist
->headline
,
5889 infobar
= gtk_info_bar_new();
5890 gtk_container_add(GTK_CONTAINER(gtkblist
->headline
), infobar
);
5891 gtk_info_bar_set_default_response(GTK_INFO_BAR(infobar
), GTK_RESPONSE_OK
);
5892 gtk_info_bar_set_message_type(GTK_INFO_BAR(infobar
), GTK_MESSAGE_INFO
);
5894 content_area
= gtk_info_bar_get_content_area(GTK_INFO_BAR(infobar
));
5895 gtkblist
->headline_image
= gtk_image_new_from_pixbuf(NULL
);
5896 gtk_widget_set_halign(gtkblist
->headline_image
, GTK_ALIGN_CENTER
);
5897 gtk_widget_set_valign(gtkblist
->headline_image
, GTK_ALIGN_CENTER
);
5898 gtkblist
->headline_label
= gtk_label_new(NULL
);
5899 gtk_label_set_line_wrap(GTK_LABEL(gtkblist
->headline_label
), TRUE
);
5900 gtk_box_pack_start(GTK_BOX(content_area
), gtkblist
->headline_image
,
5902 gtk_box_pack_start(GTK_BOX(content_area
), gtkblist
->headline_label
,
5905 close
= gtk_image_new_from_stock(GTK_STOCK_CLOSE
, GTK_ICON_SIZE_MENU
);
5906 close
= pidgin_create_small_button(close
);
5907 gtk_widget_set_tooltip_text(close
, _("Close"));
5908 gtk_info_bar_add_action_widget(GTK_INFO_BAR(infobar
), close
,
5909 GTK_RESPONSE_CLOSE
);
5911 g_signal_connect(infobar
, "response", G_CALLBACK(headline_response_cb
),
5913 g_signal_connect(infobar
, "close", G_CALLBACK(gtk_info_bar_response
),
5914 GINT_TO_POINTER(GTK_RESPONSE_CLOSE
));
5915 g_signal_connect(gtkblist
->headline
, "realize",
5916 G_CALLBACK(headline_realize_cb
), NULL
);
5917 g_signal_connect(gtkblist
->headline
, "button-press-event",
5918 G_CALLBACK(headline_press_cb
), infobar
);
5920 /****************************** GtkTreeView **********************************/
5921 gtkblist
->treemodel
= gtk_tree_store_new(BLIST_COLUMNS
,
5922 GDK_TYPE_PIXBUF
, /* Status icon */
5923 G_TYPE_BOOLEAN
, /* Status icon visible */
5924 G_TYPE_STRING
, /* Name */
5925 G_TYPE_STRING
, /* Idle */
5926 G_TYPE_BOOLEAN
, /* Idle visible */
5927 GDK_TYPE_PIXBUF
, /* Buddy icon */
5928 G_TYPE_BOOLEAN
, /* Buddy icon visible */
5929 G_TYPE_POINTER
, /* Node */
5930 GDK_TYPE_RGBA
, /* bgcolor */
5931 G_TYPE_BOOLEAN
, /* Group expander */
5932 G_TYPE_BOOLEAN
, /* Group expander visible */
5933 G_TYPE_BOOLEAN
, /* Contact expander */
5934 G_TYPE_BOOLEAN
, /* Contact expander visible */
5935 GDK_TYPE_PIXBUF
, /* Emblem */
5936 G_TYPE_BOOLEAN
, /* Emblem visible */
5937 GDK_TYPE_PIXBUF
, /* Protocol icon */
5938 G_TYPE_BOOLEAN
/* Protocol visible */
5941 gtkblist
->treeview
= gtk_tree_view_new_with_model(GTK_TREE_MODEL(gtkblist
->treemodel
));
5943 gtk_widget_show(gtkblist
->treeview
);
5944 gtk_widget_set_name(gtkblist
->treeview
, "pidgin_blist_treeview");
5946 g_signal_connect(gtkblist
->treeview
,
5948 G_CALLBACK(treeview_style_set
), list
);
5949 /* Set up selection stuff */
5950 selection
= gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist
->treeview
));
5951 g_signal_connect(G_OBJECT(selection
), "changed", G_CALLBACK(pidgin_blist_selection_changed
), NULL
);
5954 gtk_tree_view_enable_model_drag_source(GTK_TREE_VIEW(gtkblist
->treeview
),
5955 GDK_BUTTON1_MASK
, ste
, 3,
5957 gtk_tree_view_enable_model_drag_dest(GTK_TREE_VIEW(gtkblist
->treeview
),
5959 GDK_ACTION_COPY
| GDK_ACTION_MOVE
);
5961 g_signal_connect(G_OBJECT(gtkblist
->treeview
), "drag-data-received", G_CALLBACK(pidgin_blist_drag_data_rcv_cb
), NULL
);
5962 g_signal_connect(G_OBJECT(gtkblist
->treeview
), "drag-data-get", G_CALLBACK(pidgin_blist_drag_data_get_cb
), NULL
);
5964 g_signal_connect(G_OBJECT(gtkblist
->treeview
), "drag-begin", G_CALLBACK(pidgin_blist_drag_begin
), NULL
);
5966 g_signal_connect(G_OBJECT(gtkblist
->treeview
), "drag-motion", G_CALLBACK(pidgin_blist_drag_motion_cb
), NULL
);
5967 g_signal_connect(G_OBJECT(gtkblist
->treeview
), "motion-notify-event", G_CALLBACK(pidgin_blist_motion_cb
), NULL
);
5968 g_signal_connect(G_OBJECT(gtkblist
->treeview
), "leave-notify-event", G_CALLBACK(pidgin_blist_leave_cb
), NULL
);
5971 pidgin_tooltip_setup_for_treeview(gtkblist
->treeview
, NULL
,
5972 pidgin_blist_create_tooltip
,
5973 pidgin_blist_paint_tip
);
5975 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(gtkblist
->treeview
), FALSE
);
5977 /* expander columns */
5978 column
= gtk_tree_view_column_new();
5979 gtk_tree_view_append_column(GTK_TREE_VIEW(gtkblist
->treeview
), column
);
5980 gtk_tree_view_column_set_visible(column
, FALSE
);
5981 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(gtkblist
->treeview
), column
);
5983 /* everything else column */
5984 gtkblist
->text_column
= gtk_tree_view_column_new ();
5985 gtk_tree_view_append_column(GTK_TREE_VIEW(gtkblist
->treeview
), gtkblist
->text_column
);
5986 pidgin_blist_build_layout(list
);
5988 g_signal_connect(G_OBJECT(gtkblist
->treeview
), "row-activated", G_CALLBACK(gtk_blist_row_activated_cb
), NULL
);
5989 g_signal_connect(G_OBJECT(gtkblist
->treeview
), "row-expanded", G_CALLBACK(gtk_blist_row_expanded_cb
), NULL
);
5990 g_signal_connect(G_OBJECT(gtkblist
->treeview
), "row-collapsed", G_CALLBACK(gtk_blist_row_collapsed_cb
), NULL
);
5991 g_signal_connect(G_OBJECT(gtkblist
->treeview
), "button-press-event", G_CALLBACK(gtk_blist_button_press_cb
), NULL
);
5992 g_signal_connect(G_OBJECT(gtkblist
->treeview
), "key-press-event", G_CALLBACK(gtk_blist_key_press_cb
), NULL
);
5993 g_signal_connect(G_OBJECT(gtkblist
->treeview
), "popup-menu", G_CALLBACK(pidgin_blist_popup_menu_cb
), NULL
);
5995 /* Enable CTRL+F searching */
5996 gtk_tree_view_set_search_column(GTK_TREE_VIEW(gtkblist
->treeview
), NAME_COLUMN
);
5997 gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(gtkblist
->treeview
),
5998 pidgin_blist_search_equal_func
, NULL
, NULL
);
6000 gtk_box_pack_start(GTK_BOX(gtkblist
->vbox
),
6001 pidgin_make_scrollable(gtkblist
->treeview
, GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
, GTK_SHADOW_NONE
, -1, -1),
6004 sep
= gtk_separator_new(GTK_ORIENTATION_HORIZONTAL
);
6005 gtk_box_pack_start(GTK_BOX(gtkblist
->vbox
), sep
, FALSE
, FALSE
, 0);
6007 gtkblist
->scrollbook
= pidgin_scroll_book_new();
6008 gtk_box_pack_start(GTK_BOX(gtkblist
->vbox
), gtkblist
->scrollbook
, FALSE
, FALSE
, 0);
6010 priv
->error_scrollbook
= PIDGIN_SCROLL_BOOK(pidgin_scroll_book_new());
6011 gtk_box_pack_start(GTK_BOX(gtkblist
->vbox
),
6012 GTK_WIDGET(priv
->error_scrollbook
), FALSE
, FALSE
, 0);
6014 /* Add the statusbox */
6015 gtkblist
->statusbox
= pidgin_status_box_new();
6016 gtk_box_pack_start(GTK_BOX(gtkblist
->vbox
), gtkblist
->statusbox
, FALSE
, TRUE
, 0);
6017 gtk_widget_set_name(gtkblist
->statusbox
, "pidgin_blist_statusbox");
6018 gtk_widget_show(gtkblist
->statusbox
);
6020 /* set the Show Offline Buddies option. must be done
6021 * after the treeview or faceprint gets mad. -Robot101
6023 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(gtk_ui_manager_get_action(gtkblist
->ui
, "/BList/BuddiesMenu/ShowMenu/ShowOffline")),
6024 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_offline_buddies"));
6026 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(gtk_ui_manager_get_action(gtkblist
->ui
, "/BList/BuddiesMenu/ShowMenu/ShowEmptyGroups")),
6027 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_empty_groups"));
6029 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(gtk_ui_manager_get_action(gtkblist
->ui
, "/BList/ToolsMenu/MuteSounds")),
6030 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/sound/mute"));
6032 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(gtk_ui_manager_get_action(gtkblist
->ui
, "/BList/BuddiesMenu/ShowMenu/ShowBuddyDetails")),
6033 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_buddy_icons"));
6035 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(gtk_ui_manager_get_action(gtkblist
->ui
, "/BList/BuddiesMenu/ShowMenu/ShowIdleTimes")),
6036 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_idle_time"));
6038 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(gtk_ui_manager_get_action(gtkblist
->ui
, "/BList/BuddiesMenu/ShowMenu/ShowProtocolIcons")),
6039 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_protocol_icons"));
6041 if(!strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/sound/method"), "none"))
6042 gtk_action_set_sensitive(gtk_ui_manager_get_action(gtkblist
->ui
, "/BList/ToolsMenu/MuteSounds"), FALSE
);
6044 /* Update some dynamic things */
6045 update_menu_bar(gtkblist
);
6046 pidgin_blist_update_plugin_actions();
6047 pidgin_blist_update_sort_methods();
6049 /* OK... let's show this bad boy. */
6050 pidgin_blist_refresh(list
);
6051 pidgin_blist_restore_position();
6052 gtk_widget_show_all(GTK_WIDGET(gtkblist
->vbox
));
6053 gtk_widget_realize(GTK_WIDGET(gtkblist
->window
));
6054 purple_blist_set_visible(purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/list_visible"));
6056 /* start the refresh timer */
6057 gtkblist
->refresh_timer
= purple_timeout_add_seconds(30, (GSourceFunc
)pidgin_blist_refresh_timer
, list
);
6059 handle
= pidgin_blist_get_handle();
6061 /* things that affect how buddies are displayed */
6062 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/blist/show_buddy_icons",
6063 _prefs_change_redo_list
, NULL
);
6064 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/blist/show_idle_time",
6065 _prefs_change_redo_list
, NULL
);
6066 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/blist/show_empty_groups",
6067 _prefs_change_redo_list
, NULL
);
6068 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/blist/show_offline_buddies",
6069 _prefs_change_redo_list
, NULL
);
6070 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/blist/show_protocol_icons",
6071 _prefs_change_redo_list
, NULL
);
6074 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/blist/sort_type",
6075 _prefs_change_sort_method
, NULL
);
6078 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/sound/mute",
6079 pidgin_blist_mute_pref_cb
, NULL
);
6080 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/sound/method",
6081 pidgin_blist_sound_method_pref_cb
, NULL
);
6083 /* Setup some purple signal handlers. */
6085 handle
= purple_accounts_get_handle();
6086 purple_signal_connect(handle
, "account-enabled", gtkblist
,
6087 PURPLE_CALLBACK(account_modified
), gtkblist
);
6088 purple_signal_connect(handle
, "account-disabled", gtkblist
,
6089 PURPLE_CALLBACK(account_modified
), gtkblist
);
6090 purple_signal_connect(handle
, "account-removed", gtkblist
,
6091 PURPLE_CALLBACK(account_modified
), gtkblist
);
6092 purple_signal_connect(handle
, "account-status-changed", gtkblist
,
6093 PURPLE_CALLBACK(account_status_changed
),
6095 purple_signal_connect(handle
, "account-error-changed", gtkblist
,
6096 PURPLE_CALLBACK(update_account_error_state
),
6098 purple_signal_connect(handle
, "account-actions-changed", gtkblist
,
6099 PURPLE_CALLBACK(account_actions_changed
), NULL
);
6101 handle
= pidgin_accounts_get_handle();
6102 purple_signal_connect(handle
, "account-modified", gtkblist
,
6103 PURPLE_CALLBACK(account_modified
), gtkblist
);
6105 handle
= purple_connections_get_handle();
6106 purple_signal_connect(handle
, "signed-on", gtkblist
,
6107 PURPLE_CALLBACK(sign_on_off_cb
), list
);
6108 purple_signal_connect(handle
, "signed-off", gtkblist
,
6109 PURPLE_CALLBACK(sign_on_off_cb
), list
);
6111 handle
= purple_plugins_get_handle();
6112 purple_signal_connect(handle
, "plugin-load", gtkblist
,
6113 PURPLE_CALLBACK(plugin_changed_cb
), NULL
);
6114 purple_signal_connect(handle
, "plugin-unload", gtkblist
,
6115 PURPLE_CALLBACK(plugin_changed_cb
), NULL
);
6117 handle
= purple_conversations_get_handle();
6118 purple_signal_connect(handle
, "conversation-updated", gtkblist
,
6119 PURPLE_CALLBACK(conversation_updated_cb
),
6121 purple_signal_connect(handle
, "deleting-conversation", gtkblist
,
6122 PURPLE_CALLBACK(conversation_deleting_cb
),
6124 purple_signal_connect(handle
, "conversation-created", gtkblist
,
6125 PURPLE_CALLBACK(conversation_created_cb
),
6128 gtk_widget_hide(gtkblist
->headline
);
6130 show_initial_account_errors(gtkblist
);
6132 /* emit our created signal */
6133 handle
= pidgin_blist_get_handle();
6134 purple_signal_emit(handle
, "gtkblist-created", list
);
6137 static void redo_buddy_list(PurpleBuddyList
*list
, gboolean remove
, gboolean rerender
)
6139 PurpleBlistNode
*node
;
6141 gtkblist
= PIDGIN_BLIST(list
);
6142 if(!gtkblist
|| !gtkblist
->treeview
)
6149 /* This is only needed when we're reverting to a non-GTK+ sorted
6150 * status. We shouldn't need to remove otherwise.
6152 if (remove
&& !PURPLE_IS_GROUP(node
))
6153 pidgin_blist_hide_node(list
, node
, FALSE
);
6155 if (PURPLE_IS_BUDDY(node
))
6156 pidgin_blist_update_buddy(list
, node
, rerender
);
6157 else if (PURPLE_IS_CHAT(node
))
6158 pidgin_blist_update(list
, node
);
6159 else if (PURPLE_IS_GROUP(node
))
6160 pidgin_blist_update(list
, node
);
6161 node
= purple_blist_node_next(node
, FALSE
);
6166 void pidgin_blist_refresh(PurpleBuddyList
*list
)
6168 redo_buddy_list(list
, FALSE
, TRUE
);
6172 pidgin_blist_update_refresh_timeout()
6174 PurpleBuddyList
*blist
;
6175 PidginBuddyList
*gtkblist
;
6177 blist
= purple_blist_get_buddy_list();
6178 gtkblist
= PIDGIN_BLIST(purple_blist_get_buddy_list());
6180 gtkblist
->refresh_timer
= purple_timeout_add_seconds(30,(GSourceFunc
)pidgin_blist_refresh_timer
, blist
);
6183 static gboolean
get_iter_from_node(PurpleBlistNode
*node
, GtkTreeIter
*iter
) {
6184 struct _pidgin_blist_node
*gtknode
= purple_blist_node_get_ui_data(node
);
6192 purple_debug_error("gtkblist", "get_iter_from_node was called, but we don't seem to have a blist\n");
6200 if ((path
= gtk_tree_row_reference_get_path(gtknode
->row
)) == NULL
)
6203 if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist
->treemodel
), iter
, path
)) {
6204 gtk_tree_path_free(path
);
6207 gtk_tree_path_free(path
);
6211 static void pidgin_blist_remove(PurpleBuddyList
*list
, PurpleBlistNode
*node
)
6213 struct _pidgin_blist_node
*gtknode
= purple_blist_node_get_ui_data(node
);
6215 purple_request_close_with_handle(node
);
6217 pidgin_blist_hide_node(list
, node
, TRUE
);
6220 pidgin_blist_update(list
, node
->parent
);
6222 /* There's something I don't understand here - Ethan */
6223 /* Ethan said that back in 2003, but this g_free has been left commented
6224 * out ever since. I can't find any reason at all why this is bad and
6225 * valgrind found several reasons why it's good. If this causes problems
6226 * comment it out again. Stu */
6227 /* Of course it still causes problems - this breaks dragging buddies into
6228 * contacts, the dragged buddy mysteriously 'disappears'. Stu. */
6229 /* I think it's fixed now. Stu. */
6232 if(gtknode
->recent_signonoff_timer
> 0)
6233 purple_timeout_remove(gtknode
->recent_signonoff_timer
);
6235 purple_signals_disconnect_by_handle(gtknode
);
6237 purple_blist_node_set_ui_data(node
, NULL
);
6241 static gboolean
do_selection_changed(PurpleBlistNode
*new_selection
)
6243 PurpleBlistNode
*old_selection
= NULL
;
6245 /* test for gtkblist because crazy timeout means we can be called after the blist is gone */
6246 if (gtkblist
&& new_selection
!= gtkblist
->selected_node
) {
6247 old_selection
= gtkblist
->selected_node
;
6248 gtkblist
->selected_node
= new_selection
;
6250 pidgin_blist_update(NULL
, new_selection
);
6252 pidgin_blist_update(NULL
, old_selection
);
6258 static void pidgin_blist_selection_changed(GtkTreeSelection
*selection
, gpointer data
)
6260 PurpleBlistNode
*new_selection
= NULL
;
6263 if(gtk_tree_selection_get_selected(selection
, NULL
, &iter
)){
6264 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
,
6265 NODE_COLUMN
, &new_selection
, -1);
6268 /* we set this up as a timeout, otherwise the blist flickers ...
6269 * but we don't do it for groups, because it causes total bizarness -
6270 * the previously selected buddy node might rendered at half height.
6272 if ((new_selection
!= NULL
) && PURPLE_IS_GROUP(new_selection
)) {
6273 do_selection_changed(new_selection
);
6275 g_timeout_add(0, (GSourceFunc
)do_selection_changed
, new_selection
);
6279 static gboolean
insert_node(PurpleBuddyList
*list
, PurpleBlistNode
*node
, GtkTreeIter
*iter
)
6281 GtkTreeIter parent_iter
= {0, NULL
, NULL
, NULL
}, cur
, *curptr
= NULL
;
6282 struct _pidgin_blist_node
*gtknode
= purple_blist_node_get_ui_data(node
);
6283 GtkTreePath
*newpath
;
6288 /* XXX: it's not necessary, but let's silence a warning*/
6289 memset(&parent_iter
, 0, sizeof(parent_iter
));
6291 if(node
->parent
&& !get_iter_from_node(node
->parent
, &parent_iter
))
6294 if(get_iter_from_node(node
, &cur
))
6297 if(PURPLE_IS_CONTACT(node
) || PURPLE_IS_CHAT(node
)) {
6298 current_sort_method
->func(node
, list
, parent_iter
, curptr
, iter
);
6300 sort_method_none(node
, list
, parent_iter
, curptr
, iter
);
6303 if(gtknode
!= NULL
) {
6304 gtk_tree_row_reference_free(gtknode
->row
);
6306 pidgin_blist_new_node(node
);
6307 gtknode
= purple_blist_node_get_ui_data(node
);
6310 newpath
= gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist
->treemodel
),
6313 gtk_tree_row_reference_new(GTK_TREE_MODEL(gtkblist
->treemodel
),
6316 gtk_tree_path_free(newpath
);
6319 gtk_tree_store_set(gtkblist
->treemodel
, iter
,
6324 GtkTreePath
*expand
= NULL
;
6325 struct _pidgin_blist_node
*gtkparentnode
= purple_blist_node_get_ui_data(node
->parent
);
6327 if(PURPLE_IS_GROUP(node
->parent
)) {
6328 if(!purple_blist_node_get_bool(node
->parent
, "collapsed"))
6329 expand
= gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist
->treemodel
), &parent_iter
);
6330 } else if(PURPLE_IS_CONTACT(node
->parent
) &&
6331 gtkparentnode
->contact_expanded
) {
6332 expand
= gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist
->treemodel
), &parent_iter
);
6335 gtk_tree_view_expand_row(GTK_TREE_VIEW(gtkblist
->treeview
), expand
, FALSE
);
6336 gtk_tree_path_free(expand
);
6343 static gboolean
pidgin_blist_group_has_show_offline_buddy(PurpleGroup
*group
)
6345 PurpleBlistNode
*gnode
, *cnode
, *bnode
;
6347 gnode
= PURPLE_BLIST_NODE(group
);
6348 for(cnode
= gnode
->child
; cnode
; cnode
= cnode
->next
) {
6349 if(PURPLE_IS_CONTACT(cnode
)) {
6350 for(bnode
= cnode
->child
; bnode
; bnode
= bnode
->next
) {
6351 PurpleBuddy
*buddy
= (PurpleBuddy
*)bnode
;
6352 if (purple_account_is_connected(purple_buddy_get_account(buddy
)) &&
6353 purple_blist_node_get_bool(bnode
, "show_offline"))
6361 /* This version of pidgin_blist_update_group can take the original buddy or a
6362 * group, but has much better algorithmic performance with a pre-known buddy.
6364 static void pidgin_blist_update_group(PurpleBuddyList
*list
,
6365 PurpleBlistNode
*node
)
6369 PurpleBlistNode
* gnode
;
6370 gboolean show
= FALSE
, show_offline
= FALSE
;
6372 g_return_if_fail(node
!= NULL
);
6377 if (PURPLE_IS_GROUP(node
))
6379 else if (PURPLE_IS_BUDDY(node
))
6380 gnode
= node
->parent
->parent
;
6381 else if (PURPLE_IS_CONTACT(node
) || PURPLE_IS_CHAT(node
))
6382 gnode
= node
->parent
;
6386 group
= (PurpleGroup
*)gnode
;
6388 show_offline
= purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_offline_buddies");
6391 count
= purple_counting_node_get_current_size(PURPLE_COUNTING_NODE(group
));
6393 count
= purple_counting_node_get_online_count(PURPLE_COUNTING_NODE(group
));
6395 if (count
> 0 || purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_empty_groups"))
6397 else if (PURPLE_IS_BUDDY(node
) && buddy_is_displayable((PurpleBuddy
*)node
)) { /* Or chat? */
6399 } else if (!show_offline
) {
6400 show
= pidgin_blist_group_has_show_offline_buddy(group
);
6409 GdkRGBA
*bgcolor
= NULL
;
6410 GdkPixbuf
*avatar
= NULL
;
6411 PidginBlistTheme
*theme
= NULL
;
6413 if(!insert_node(list
, gnode
, &iter
))
6416 if ((theme
= pidgin_blist_get_theme()) == NULL
)
6418 else if (purple_blist_node_get_bool(gnode
, "collapsed") || count
<= 0)
6419 bgcolor
= pidgin_blist_theme_get_collapsed_background_color(theme
);
6421 bgcolor
= pidgin_blist_theme_get_expanded_background_color(theme
);
6423 path
= gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
);
6424 expanded
= gtk_tree_view_row_expanded(GTK_TREE_VIEW(gtkblist
->treeview
), path
);
6425 gtk_tree_path_free(path
);
6427 title
= pidgin_get_group_title(gnode
, expanded
);
6428 biglist
= purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_buddy_icons");
6431 avatar
= pidgin_blist_get_buddy_icon(gnode
, TRUE
, TRUE
);
6434 gtk_tree_store_set(gtkblist
->treemodel
, &iter
,
6435 STATUS_ICON_VISIBLE_COLUMN
, FALSE
,
6436 STATUS_ICON_COLUMN
, NULL
,
6439 BGCOLOR_COLUMN
, bgcolor
,
6440 GROUP_EXPANDER_COLUMN
, TRUE
,
6441 GROUP_EXPANDER_VISIBLE_COLUMN
, TRUE
,
6442 CONTACT_EXPANDER_VISIBLE_COLUMN
, FALSE
,
6443 BUDDY_ICON_COLUMN
, avatar
,
6444 BUDDY_ICON_VISIBLE_COLUMN
, biglist
,
6445 IDLE_VISIBLE_COLUMN
, FALSE
,
6446 EMBLEM_VISIBLE_COLUMN
, FALSE
,
6450 pidgin_blist_hide_node(list
, gnode
, TRUE
);
6454 static char *pidgin_get_group_title(PurpleBlistNode
*gnode
, gboolean expanded
)
6458 char group_count
[12] = "";
6460 PurpleBlistNode
*selected_node
= NULL
;
6462 PidginThemeFont
*pair
;
6463 gchar
const *text_color
, *text_font
;
6464 PidginBlistTheme
*theme
;
6466 group
= (PurpleGroup
*)gnode
;
6468 if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist
->treeview
)), NULL
, &iter
)) {
6469 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &iter
,
6470 NODE_COLUMN
, &selected_node
, -1);
6472 selected
= (gnode
== selected_node
);
6475 g_snprintf(group_count
, sizeof(group_count
), "%d/%d",
6476 purple_counting_node_get_online_count(PURPLE_COUNTING_NODE(group
)),
6477 purple_counting_node_get_current_size(PURPLE_COUNTING_NODE(group
)));
6480 theme
= pidgin_blist_get_theme();
6484 pair
= pidgin_blist_theme_get_expanded_text_info(theme
);
6486 pair
= pidgin_blist_theme_get_collapsed_text_info(theme
);
6489 text_color
= selected
? NULL
: theme_font_get_color_default(pair
, NULL
);
6490 text_font
= theme_font_get_face_default(pair
, "");
6492 esc
= g_markup_escape_text(purple_group_get_name(group
), -1);
6494 mark
= g_strdup_printf("<span foreground='%s' font_desc='%s'><b>%s</b>%s%s%s</span>",
6495 text_color
, text_font
,
6497 !expanded
? " <span weight='light'>(</span>" : "",
6499 !expanded
? "<span weight='light'>)</span>" : "");
6501 mark
= g_strdup_printf("<span font_desc='%s'><b>%s</b>%s%s%s</span>",
6502 text_font
, esc
? esc
: "",
6503 !expanded
? " <span weight='light'>(</span>" : "",
6505 !expanded
? "<span weight='light'>)</span>" : "");
6512 static void buddy_node(PurpleBuddy
*buddy
, GtkTreeIter
*iter
, PurpleBlistNode
*node
)
6514 PurplePresence
*presence
= purple_buddy_get_presence(buddy
);
6515 GdkPixbuf
*status
, *avatar
, *emblem
, *protocol_icon
;
6516 GdkRGBA
*color
= NULL
;
6519 gboolean expanded
= ((struct _pidgin_blist_node
*)purple_blist_node_get_ui_data(node
->parent
))->contact_expanded
;
6520 gboolean selected
= (gtkblist
->selected_node
== node
);
6521 gboolean biglist
= purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_buddy_icons");
6522 PidginBlistTheme
*theme
;
6527 status
= pidgin_blist_get_status_icon(PURPLE_BLIST_NODE(buddy
),
6528 biglist
? PIDGIN_STATUS_ICON_LARGE
: PIDGIN_STATUS_ICON_SMALL
);
6530 /* Speed it up if we don't want buddy icons. */
6532 avatar
= pidgin_blist_get_buddy_icon(PURPLE_BLIST_NODE(buddy
), TRUE
, TRUE
);
6537 g_object_ref(G_OBJECT(gtkblist
->empty_avatar
));
6538 avatar
= gtkblist
->empty_avatar
;
6539 } else if ((!PURPLE_BUDDY_IS_ONLINE(buddy
) || purple_presence_is_idle(presence
))) {
6540 do_alphashift(avatar
, 77);
6543 emblem
= pidgin_blist_get_emblem(PURPLE_BLIST_NODE(buddy
));
6544 mark
= pidgin_blist_get_name_markup(buddy
, selected
, TRUE
);
6546 theme
= pidgin_blist_get_theme();
6548 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_idle_time") &&
6549 purple_presence_is_idle(presence
) && !biglist
)
6551 time_t idle_secs
= purple_presence_get_idle_time(presence
);
6555 PidginThemeFont
*pair
= NULL
;
6556 const gchar
*textcolor
;
6561 ihrs
= (t
- idle_secs
) / 3600;
6562 imin
= ((t
- idle_secs
) / 60) % 60;
6566 else if (theme
!= NULL
&& (pair
= pidgin_blist_theme_get_idle_text_info(theme
)) != NULL
)
6567 textcolor
= pidgin_theme_font_get_color_describe(pair
);
6569 /* If no theme them default to making idle buddy names grey */
6570 textcolor
= "dim grey";
6573 idle
= g_strdup_printf("<span color='%s' font_desc='%s'>%d:%02d</span>",
6574 textcolor
, theme_font_get_face_default(pair
, ""),
6577 idle
= g_strdup_printf("<span font_desc='%s'>%d:%02d</span>",
6578 theme_font_get_face_default(pair
, ""),
6584 protocol_icon
= pidgin_create_protocol_icon(purple_buddy_get_account(buddy
), PIDGIN_PROTOCOL_ICON_SMALL
);
6587 color
= pidgin_blist_theme_get_contact_color(theme
);
6589 gtk_tree_store_set(gtkblist
->treemodel
, iter
,
6590 STATUS_ICON_COLUMN
, status
,
6591 STATUS_ICON_VISIBLE_COLUMN
, TRUE
,
6594 IDLE_VISIBLE_COLUMN
, !biglist
&& idle
,
6595 BUDDY_ICON_COLUMN
, avatar
,
6596 BUDDY_ICON_VISIBLE_COLUMN
, biglist
,
6597 EMBLEM_COLUMN
, emblem
,
6598 EMBLEM_VISIBLE_COLUMN
, (emblem
!= NULL
),
6599 PROTOCOL_ICON_COLUMN
, protocol_icon
,
6600 PROTOCOL_ICON_VISIBLE_COLUMN
, purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_protocol_icons"),
6601 BGCOLOR_COLUMN
, color
,
6602 CONTACT_EXPANDER_COLUMN
, NULL
,
6603 CONTACT_EXPANDER_VISIBLE_COLUMN
, expanded
,
6604 GROUP_EXPANDER_VISIBLE_COLUMN
, FALSE
,
6610 g_object_unref(emblem
);
6612 g_object_unref(status
);
6614 g_object_unref(avatar
);
6616 g_object_unref(protocol_icon
);
6619 /* This is a variation on the original gtk_blist_update_contact. Here we
6620 can know in advance which buddy has changed so we can just update that */
6621 static void pidgin_blist_update_contact(PurpleBuddyList
*list
, PurpleBlistNode
*node
)
6623 PurpleBlistNode
*cnode
;
6624 PurpleContact
*contact
;
6626 gboolean biglist
= purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_buddy_icons");
6627 struct _pidgin_blist_node
*gtknode
;
6632 if (PURPLE_IS_BUDDY(node
))
6633 cnode
= node
->parent
;
6637 g_return_if_fail(PURPLE_IS_CONTACT(cnode
));
6639 /* First things first, update the group */
6640 if (PURPLE_IS_BUDDY(node
))
6641 pidgin_blist_update_group(list
, node
);
6643 pidgin_blist_update_group(list
, cnode
->parent
);
6645 contact
= (PurpleContact
*)cnode
;
6646 buddy
= purple_contact_get_priority_buddy(contact
);
6648 if (buddy_is_displayable(buddy
))
6652 if(!insert_node(list
, cnode
, &iter
))
6655 gtknode
= purple_blist_node_get_ui_data(cnode
);
6657 if(gtknode
->contact_expanded
) {
6660 const gchar
*fg_color
, *font
;
6661 GdkRGBA
*color
= NULL
;
6662 PidginBlistTheme
*theme
;
6663 PidginThemeFont
*pair
;
6664 gboolean selected
= (gtkblist
->selected_node
== cnode
);
6666 mark
= g_markup_escape_text(purple_contact_get_alias(contact
), -1);
6668 theme
= pidgin_blist_get_theme();
6672 pair
= pidgin_blist_theme_get_contact_text_info(theme
);
6673 color
= pidgin_blist_theme_get_contact_color(theme
);
6676 font
= theme_font_get_face_default(pair
, "");
6677 fg_color
= selected
? NULL
: theme_font_get_color_default(pair
, NULL
);
6680 tmp
= g_strdup_printf("<span font_desc='%s' color='%s'>%s</span>",
6681 font
, fg_color
, mark
);
6683 tmp
= g_strdup_printf("<span font_desc='%s'>%s</span>", font
,
6689 status
= pidgin_blist_get_status_icon(cnode
,
6690 biglist
? PIDGIN_STATUS_ICON_LARGE
: PIDGIN_STATUS_ICON_SMALL
);
6692 gtk_tree_store_set(gtkblist
->treemodel
, &iter
,
6693 STATUS_ICON_COLUMN
, status
,
6694 STATUS_ICON_VISIBLE_COLUMN
, TRUE
,
6697 IDLE_VISIBLE_COLUMN
, FALSE
,
6698 BGCOLOR_COLUMN
, color
,
6699 BUDDY_ICON_COLUMN
, NULL
,
6700 CONTACT_EXPANDER_COLUMN
, TRUE
,
6701 CONTACT_EXPANDER_VISIBLE_COLUMN
, TRUE
,
6702 GROUP_EXPANDER_VISIBLE_COLUMN
, FALSE
,
6706 g_object_unref(status
);
6708 buddy_node(buddy
, &iter
, cnode
);
6711 pidgin_blist_hide_node(list
, cnode
, TRUE
);
6717 static void pidgin_blist_update_buddy(PurpleBuddyList
*list
, PurpleBlistNode
*node
, gboolean status_change
)
6720 struct _pidgin_blist_node
*gtkparentnode
;
6722 g_return_if_fail(PURPLE_IS_BUDDY(node
));
6724 if (node
->parent
== NULL
)
6727 buddy
= (PurpleBuddy
*)node
;
6729 /* First things first, update the contact */
6730 pidgin_blist_update_contact(list
, node
);
6732 gtkparentnode
= purple_blist_node_get_ui_data(node
->parent
);
6734 if (gtkparentnode
->contact_expanded
&& buddy_is_displayable(buddy
))
6738 if (!insert_node(list
, node
, &iter
))
6741 buddy_node(buddy
, &iter
, node
);
6744 pidgin_blist_hide_node(list
, node
, TRUE
);
6749 static void pidgin_blist_update_chat(PurpleBuddyList
*list
, PurpleBlistNode
*node
)
6753 g_return_if_fail(PURPLE_IS_CHAT(node
));
6758 /* First things first, update the group */
6759 pidgin_blist_update_group(list
, node
->parent
);
6761 chat
= (PurpleChat
*)node
;
6763 if(purple_account_is_connected(purple_chat_get_account(chat
))) {
6765 GdkPixbuf
*status
, *avatar
, *emblem
, *protocol_icon
;
6766 const gchar
*color
, *font
;
6768 gboolean showicons
= purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_buddy_icons");
6769 gboolean biglist
= purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_buddy_icons");
6770 PidginBlistNode
*ui
;
6771 PurpleConversation
*conv
;
6772 gboolean hidden
= FALSE
;
6773 GdkRGBA
*bgcolor
= NULL
;
6774 PidginThemeFont
*pair
;
6775 PidginBlistTheme
*theme
;
6776 gboolean selected
= (gtkblist
->selected_node
== node
);
6777 gboolean nick_said
= FALSE
;
6779 if (!insert_node(list
, node
, &iter
))
6782 ui
= purple_blist_node_get_ui_data(node
);
6783 conv
= ui
->conv
.conv
;
6784 if (conv
&& pidgin_conv_is_hidden(PIDGIN_CONVERSATION(conv
))) {
6785 hidden
= (ui
->conv
.flags
& PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE
);
6786 nick_said
= (ui
->conv
.flags
& PIDGIN_BLIST_CHAT_HAS_PENDING_MESSAGE_WITH_NICK
);
6789 status
= pidgin_blist_get_status_icon(node
,
6790 biglist
? PIDGIN_STATUS_ICON_LARGE
: PIDGIN_STATUS_ICON_SMALL
);
6791 emblem
= pidgin_blist_get_emblem(node
);
6793 /* Speed it up if we don't want buddy icons. */
6795 avatar
= pidgin_blist_get_buddy_icon(node
, TRUE
, FALSE
);
6799 mark
= g_markup_escape_text(purple_chat_get_name(chat
), -1);
6801 theme
= pidgin_blist_get_theme();
6806 pair
= pidgin_blist_theme_get_unread_message_nick_said_text_info(theme
);
6808 pair
= pidgin_blist_theme_get_unread_message_text_info(theme
);
6809 else pair
= pidgin_blist_theme_get_online_text_info(theme
);
6812 font
= theme_font_get_face_default(pair
, "");
6813 if (selected
|| !(color
= theme_font_get_color_default(pair
, NULL
)))
6814 /* nick_said color is the same as gtkconv:tab-label-attention */
6815 color
= (nick_said
? "#006aff" : NULL
);
6818 tmp
= g_strdup_printf("<span font_desc='%s' color='%s' weight='%s'>%s</span>",
6819 font
, color
, hidden
? "bold" : "normal", mark
);
6821 tmp
= g_strdup_printf("<span font_desc='%s' weight='%s'>%s</span>",
6822 font
, hidden
? "bold" : "normal", mark
);
6827 protocol_icon
= pidgin_create_protocol_icon(purple_chat_get_account(chat
), PIDGIN_PROTOCOL_ICON_SMALL
);
6830 bgcolor
= pidgin_blist_theme_get_contact_color(theme
);
6832 gtk_tree_store_set(gtkblist
->treemodel
, &iter
,
6833 STATUS_ICON_COLUMN
, status
,
6834 STATUS_ICON_VISIBLE_COLUMN
, TRUE
,
6835 BUDDY_ICON_COLUMN
, avatar
? avatar
: gtkblist
->empty_avatar
,
6836 BUDDY_ICON_VISIBLE_COLUMN
, showicons
,
6837 EMBLEM_COLUMN
, emblem
,
6838 EMBLEM_VISIBLE_COLUMN
, emblem
!= NULL
,
6839 PROTOCOL_ICON_COLUMN
, protocol_icon
,
6840 PROTOCOL_ICON_VISIBLE_COLUMN
, purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_protocol_icons"),
6842 BGCOLOR_COLUMN
, bgcolor
,
6843 GROUP_EXPANDER_VISIBLE_COLUMN
, FALSE
,
6848 g_object_unref(emblem
);
6850 g_object_unref(status
);
6852 g_object_unref(avatar
);
6854 g_object_unref(protocol_icon
);
6857 pidgin_blist_hide_node(list
, node
, TRUE
);
6861 static void pidgin_blist_update(PurpleBuddyList
*list
, PurpleBlistNode
*node
)
6864 gtkblist
= PIDGIN_BLIST(list
);
6865 if(!gtkblist
|| !gtkblist
->treeview
|| !node
)
6868 if (purple_blist_node_get_ui_data(node
) == NULL
)
6869 pidgin_blist_new_node(node
);
6871 if (PURPLE_IS_GROUP(node
))
6872 pidgin_blist_update_group(list
, node
);
6873 else if (PURPLE_IS_CONTACT(node
))
6874 pidgin_blist_update_contact(list
, node
);
6875 else if (PURPLE_IS_BUDDY(node
))
6876 pidgin_blist_update_buddy(list
, node
, TRUE
);
6877 else if (PURPLE_IS_CHAT(node
))
6878 pidgin_blist_update_chat(list
, node
);
6881 static void pidgin_blist_destroy(PurpleBuddyList
*list
)
6883 PidginBuddyListPrivate
*priv
;
6885 if (!list
|| !list
->ui_data
)
6888 g_return_if_fail(list
->ui_data
== gtkblist
);
6890 purple_signals_disconnect_by_handle(gtkblist
);
6892 gtk_widget_destroy(gtkblist
->window
);
6894 pidgin_blist_tooltip_destroy();
6896 if (gtkblist
->refresh_timer
)
6897 purple_timeout_remove(gtkblist
->refresh_timer
);
6898 if (gtkblist
->timeout
)
6899 g_source_remove(gtkblist
->timeout
);
6900 if (gtkblist
->drag_timeout
)
6901 g_source_remove(gtkblist
->drag_timeout
);
6903 gtkblist
->refresh_timer
= 0;
6904 gtkblist
->timeout
= 0;
6905 gtkblist
->drag_timeout
= 0;
6906 gtkblist
->window
= gtkblist
->vbox
= gtkblist
->treeview
= NULL
;
6907 g_object_unref(G_OBJECT(gtkblist
->treemodel
));
6908 gtkblist
->treemodel
= NULL
;
6909 g_object_unref(G_OBJECT(gtkblist
->ui
));
6910 g_object_unref(G_OBJECT(gtkblist
->empty_avatar
));
6912 priv
= PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist
);
6914 if (priv
->current_theme
)
6915 g_object_unref(priv
->current_theme
);
6916 if (priv
->select_notebook_page_timeout
)
6917 purple_timeout_remove(priv
->select_notebook_page_timeout
);
6923 purple_prefs_disconnect_by_handle(pidgin_blist_get_handle());
6926 static void pidgin_blist_set_visible(PurpleBuddyList
*list
, gboolean show
)
6928 if (!(gtkblist
&& gtkblist
->window
))
6932 if(!PIDGIN_WINDOW_ICONIFIED(gtkblist
->window
) &&
6933 !gtk_widget_get_visible(gtkblist
->window
))
6934 purple_signal_emit(pidgin_blist_get_handle(), "gtkblist-unhiding", gtkblist
);
6935 pidgin_blist_restore_position();
6936 gtk_window_present(GTK_WINDOW(gtkblist
->window
));
6938 if(visibility_manager_count
) {
6939 purple_signal_emit(pidgin_blist_get_handle(), "gtkblist-hiding", gtkblist
);
6940 gtk_widget_hide(gtkblist
->window
);
6942 if (!gtk_widget_get_visible(gtkblist
->window
))
6943 gtk_widget_show(gtkblist
->window
);
6944 gtk_window_iconify(GTK_WINDOW(gtkblist
->window
));
6952 static GList
*list
= NULL
;
6954 PurpleBlistNode
*gnode
;
6959 if (purple_blist_get_buddy_list()->root
== NULL
)
6961 list
= g_list_append(list
,
6962 (gpointer
)PURPLE_BLIST_DEFAULT_GROUP_NAME
);
6966 for (gnode
= purple_blist_get_buddy_list()->root
;
6968 gnode
= gnode
->next
)
6970 if (PURPLE_IS_GROUP(gnode
))
6972 g
= (PurpleGroup
*)gnode
;
6973 list
= g_list_append(list
, (char *) purple_group_get_name(g
));
6982 add_buddy_select_account_cb(GObject
*w
, PurpleAccount
*account
,
6983 PidginAddBuddyData
*data
)
6985 PurpleConnection
*pc
= NULL
;
6986 PurpleProtocol
*protocol
= NULL
;
6987 gboolean invite_enabled
= TRUE
;
6989 /* Save our account */
6990 data
->rq_data
.account
= account
;
6993 pc
= purple_account_get_connection(account
);
6995 protocol
= purple_connection_get_protocol(pc
);
6996 if (protocol
&& !(purple_protocol_get_options(protocol
) & OPT_PROTO_INVITE_MESSAGE
))
6997 invite_enabled
= FALSE
;
6999 gtk_widget_set_sensitive(data
->entry_for_invite
, invite_enabled
);
7000 set_sensitive_if_input_buddy_cb(data
->entry
, data
);
7004 destroy_add_buddy_dialog_cb(GtkWidget
*win
, PidginAddBuddyData
*data
)
7010 add_buddy_cb(GtkWidget
*w
, int resp
, PidginAddBuddyData
*data
)
7012 const char *grp
, *who
, *whoalias
, *invite
;
7013 PurpleAccount
*account
;
7016 PurpleIMConversation
*im
;
7017 PurpleBuddyIcon
*icon
;
7019 if (resp
== GTK_RESPONSE_OK
)
7021 who
= gtk_entry_get_text(GTK_ENTRY(data
->entry
));
7022 grp
= pidgin_text_combo_box_entry_get_text(data
->combo
);
7023 whoalias
= gtk_entry_get_text(GTK_ENTRY(data
->entry_for_alias
));
7024 if (*whoalias
== '\0')
7026 invite
= gtk_entry_get_text(GTK_ENTRY(data
->entry_for_invite
));
7027 if (*invite
== '\0')
7030 account
= data
->rq_data
.account
;
7033 if ((grp
!= NULL
) && (*grp
!= '\0'))
7035 if ((g
= purple_blist_find_group(grp
)) == NULL
)
7037 g
= purple_group_new(grp
);
7038 purple_blist_add_group(g
, NULL
);
7041 b
= purple_blist_find_buddy_in_group(account
, who
, g
);
7043 else if ((b
= purple_blist_find_buddy(account
, who
)) != NULL
)
7045 g
= purple_buddy_get_group(b
);
7050 b
= purple_buddy_new(account
, who
, whoalias
);
7051 purple_blist_add_buddy(b
, NULL
, g
, NULL
);
7054 purple_account_add_buddy(account
, b
, invite
);
7056 /* Offer to merge people with the same alias. */
7057 if (whoalias
!= NULL
&& g
!= NULL
)
7058 gtk_blist_auto_personize(PURPLE_BLIST_NODE(g
), whoalias
);
7062 * It really seems like it would be better if the call to
7063 * purple_account_add_buddy() and purple_conversation_update() were done in
7064 * blist.c, possibly in the purple_blist_add_buddy() function. Maybe
7065 * purple_account_add_buddy() should be renamed to
7066 * purple_blist_add_new_buddy() or something, and have it call
7067 * purple_blist_add_buddy() after it creates it. --Mark
7069 * No that's not good. blist.c should only deal with adding nodes to the
7070 * local list. We need a new, non-gtk file that calls both
7071 * purple_account_add_buddy and purple_blist_add_buddy().
7072 * Or something. --Mark
7075 im
= purple_conversations_find_im_with_account(who
, data
->rq_data
.account
);
7077 icon
= purple_im_conversation_get_icon(im
);
7079 purple_buddy_icon_update(icon
);
7083 gtk_widget_destroy(data
->rq_data
.window
);
7087 pidgin_blist_request_add_buddy(PurpleAccount
*account
, const char *username
,
7088 const char *group
, const char *alias
)
7090 PidginAddBuddyData
*data
= g_new0(PidginAddBuddyData
, 1);
7092 if (account
== NULL
)
7093 account
= purple_connection_get_account(purple_connections_get_all()->data
);
7095 make_blist_request_dialog((PidginBlistRequestData
*)data
,
7097 _("Add Buddy"), "add_buddy",
7098 _("Add a buddy.\n"),
7099 G_CALLBACK(add_buddy_select_account_cb
), NULL
,
7100 G_CALLBACK(add_buddy_cb
));
7101 gtk_dialog_add_buttons(GTK_DIALOG(data
->rq_data
.window
),
7102 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
7103 GTK_STOCK_ADD
, GTK_RESPONSE_OK
,
7105 gtk_dialog_set_default_response(GTK_DIALOG(data
->rq_data
.window
),
7108 g_signal_connect(G_OBJECT(data
->rq_data
.window
), "destroy",
7109 G_CALLBACK(destroy_add_buddy_dialog_cb
), data
);
7111 data
->entry
= gtk_entry_new();
7113 pidgin_add_widget_to_vbox(data
->rq_data
.vbox
, _("Buddy's _username:"),
7114 data
->rq_data
.sg
, data
->entry
, TRUE
, NULL
);
7115 gtk_widget_grab_focus(data
->entry
);
7117 if (username
!= NULL
)
7118 gtk_entry_set_text(GTK_ENTRY(data
->entry
), username
);
7120 gtk_dialog_set_response_sensitive(GTK_DIALOG(data
->rq_data
.window
),
7121 GTK_RESPONSE_OK
, FALSE
);
7123 gtk_entry_set_activates_default (GTK_ENTRY(data
->entry
), TRUE
);
7125 g_signal_connect(G_OBJECT(data
->entry
), "changed",
7126 G_CALLBACK(set_sensitive_if_input_buddy_cb
),
7129 data
->entry_for_alias
= gtk_entry_new();
7130 pidgin_add_widget_to_vbox(data
->rq_data
.vbox
, _("(Optional) A_lias:"),
7131 data
->rq_data
.sg
, data
->entry_for_alias
, TRUE
,
7135 gtk_entry_set_text(GTK_ENTRY(data
->entry_for_alias
), alias
);
7137 if (username
!= NULL
)
7138 gtk_widget_grab_focus(GTK_WIDGET(data
->entry_for_alias
));
7140 data
->entry_for_invite
= gtk_entry_new();
7141 pidgin_add_widget_to_vbox(data
->rq_data
.vbox
, _("(Optional) _Invite message:"),
7142 data
->rq_data
.sg
, data
->entry_for_invite
, TRUE
,
7145 data
->combo
= pidgin_text_combo_box_entry_new(group
, groups_tree());
7146 pidgin_add_widget_to_vbox(data
->rq_data
.vbox
, _("Add buddy to _group:"),
7147 data
->rq_data
.sg
, data
->combo
, TRUE
, NULL
);
7149 gtk_widget_show_all(data
->rq_data
.window
);
7151 /* Force update of invite message entry sensitivity */
7152 add_buddy_select_account_cb(NULL
, account
, data
);
7156 add_chat_cb(GtkWidget
*w
, PidginAddChatData
*data
)
7160 GHashTable
*components
;
7162 components
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
7165 for (tmp
= data
->chat_data
.entries
; tmp
; tmp
= tmp
->next
)
7167 if (g_object_get_data(tmp
->data
, "is_spin"))
7169 g_hash_table_replace(components
,
7170 g_strdup(g_object_get_data(tmp
->data
, "identifier")),
7171 g_strdup_printf("%d",
7172 gtk_spin_button_get_value_as_int(tmp
->data
)));
7176 const char *value
= gtk_entry_get_text(tmp
->data
);
7179 g_hash_table_replace(components
,
7180 g_strdup(g_object_get_data(tmp
->data
, "identifier")),
7185 chat
= purple_chat_new(data
->chat_data
.rq_data
.account
,
7186 gtk_entry_get_text(GTK_ENTRY(data
->alias_entry
)),
7191 const char *group_name
;
7193 group_name
= pidgin_text_combo_box_entry_get_text(data
->group_combo
);
7196 if ((group_name
!= NULL
) && (*group_name
!= '\0') &&
7197 ((group
= purple_blist_find_group(group_name
)) == NULL
))
7199 group
= purple_group_new(group_name
);
7200 purple_blist_add_group(group
, NULL
);
7203 purple_blist_add_chat(chat
, group
, NULL
);
7205 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(data
->autojoin
)))
7206 purple_blist_node_set_bool(PURPLE_BLIST_NODE(chat
), "gtk-autojoin", TRUE
);
7208 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(data
->persistent
)))
7209 purple_blist_node_set_bool(PURPLE_BLIST_NODE(chat
), "gtk-persistent", TRUE
);
7212 gtk_widget_destroy(data
->chat_data
.rq_data
.window
);
7213 g_free(data
->chat_data
.default_chat_name
);
7214 g_list_free(data
->chat_data
.entries
);
7219 add_chat_resp_cb(GtkWidget
*w
, int resp
, PidginAddChatData
*data
)
7221 if (resp
== GTK_RESPONSE_OK
)
7223 add_chat_cb(NULL
, data
);
7227 pidgin_roomlist_dialog_show_with_account(data
->chat_data
.rq_data
.account
);
7231 gtk_widget_destroy(data
->chat_data
.rq_data
.window
);
7232 g_free(data
->chat_data
.default_chat_name
);
7233 g_list_free(data
->chat_data
.entries
);
7239 pidgin_blist_request_add_chat(PurpleAccount
*account
, PurpleGroup
*group
,
7240 const char *alias
, const char *name
)
7242 PidginAddChatData
*data
;
7244 PurpleConnection
*gc
;
7246 PurpleProtocol
*protocol
= NULL
;
7248 if (account
!= NULL
) {
7249 gc
= purple_account_get_connection(account
);
7250 protocol
= purple_connection_get_protocol(gc
);
7252 if (!PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT_IFACE
, join
)) {
7253 purple_notify_error(gc
, NULL
, _("This protocol does not"
7254 " support chat rooms."), NULL
,
7255 purple_request_cpar_from_account(account
));
7259 /* Find an account with chat capabilities */
7260 for (l
= purple_connections_get_all(); l
!= NULL
; l
= l
->next
) {
7261 gc
= (PurpleConnection
*)l
->data
;
7262 protocol
= purple_connection_get_protocol(gc
);
7264 if (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT_IFACE
, join
)) {
7265 account
= purple_connection_get_account(gc
);
7270 if (account
== NULL
) {
7271 purple_notify_error(NULL
, NULL
,
7272 _("You are not currently signed on with any "
7273 "protocols that have the ability to chat."), NULL
, NULL
);
7278 data
= g_new0(PidginAddChatData
, 1);
7279 vbox
= GTK_BOX(make_blist_request_dialog((PidginBlistRequestData
*)data
, account
,
7280 _("Add Chat"), "add_chat",
7281 _("Please enter an alias, and the appropriate information "
7282 "about the chat you would like to add to your buddy list.\n"),
7283 G_CALLBACK(chat_select_account_cb
), chat_account_filter_func
,
7284 G_CALLBACK(add_chat_resp_cb
)));
7285 gtk_dialog_add_buttons(GTK_DIALOG(data
->chat_data
.rq_data
.window
),
7287 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
7288 GTK_STOCK_ADD
, GTK_RESPONSE_OK
,
7290 gtk_dialog_set_default_response(GTK_DIALOG(data
->chat_data
.rq_data
.window
),
7293 data
->chat_data
.default_chat_name
= g_strdup(name
);
7295 rebuild_chat_entries((PidginChatData
*)data
, name
);
7297 data
->alias_entry
= gtk_entry_new();
7299 gtk_entry_set_text(GTK_ENTRY(data
->alias_entry
), alias
);
7300 gtk_entry_set_activates_default(GTK_ENTRY(data
->alias_entry
), TRUE
);
7302 pidgin_add_widget_to_vbox(GTK_BOX(vbox
), _("A_lias:"),
7303 data
->chat_data
.rq_data
.sg
, data
->alias_entry
,
7306 gtk_widget_grab_focus(data
->alias_entry
);
7308 data
->group_combo
= pidgin_text_combo_box_entry_new(group
? purple_group_get_name(group
) : NULL
, groups_tree());
7309 pidgin_add_widget_to_vbox(GTK_BOX(vbox
), _("_Group:"),
7310 data
->chat_data
.rq_data
.sg
, data
->group_combo
,
7313 data
->autojoin
= gtk_check_button_new_with_mnemonic(_("Automatically _join when account connects"));
7314 data
->persistent
= gtk_check_button_new_with_mnemonic(_("_Remain in chat after window is closed"));
7315 gtk_box_pack_start(GTK_BOX(vbox
), data
->autojoin
, FALSE
, FALSE
, 0);
7316 gtk_box_pack_start(GTK_BOX(vbox
), data
->persistent
, FALSE
, FALSE
, 0);
7318 gtk_widget_show_all(data
->chat_data
.rq_data
.window
);
7322 add_group_cb(PurpleConnection
*gc
, const char *group_name
)
7326 if ((group_name
== NULL
) || (*group_name
== '\0'))
7329 group
= purple_group_new(group_name
);
7330 purple_blist_add_group(group
, NULL
);
7334 pidgin_blist_request_add_group(void)
7336 purple_request_input(NULL
, _("Add Group"), NULL
,
7337 _("Please enter the name of the group to be added."),
7338 NULL
, FALSE
, FALSE
, NULL
,
7339 _("Add"), G_CALLBACK(add_group_cb
),
7345 pidgin_blist_toggle_visibility()
7347 if (gtkblist
&& gtkblist
->window
) {
7348 if (gtk_widget_get_visible(gtkblist
->window
)) {
7349 /* make the buddy list visible if it is iconified or if it is
7350 * obscured and not currently focused (the focus part ensures
7351 * that we do something reasonable if the buddy list is obscured
7352 * by a window set to always be on top), otherwise hide the
7355 purple_blist_set_visible(PIDGIN_WINDOW_ICONIFIED(gtkblist
->window
) ||
7356 ((gtk_blist_visibility
!= GDK_VISIBILITY_UNOBSCURED
) &&
7357 !gtk_blist_focused
));
7359 purple_blist_set_visible(TRUE
);
7365 pidgin_blist_visibility_manager_add()
7367 visibility_manager_count
++;
7368 purple_debug_info("gtkblist", "added visibility manager: %d\n", visibility_manager_count
);
7372 pidgin_blist_visibility_manager_remove()
7374 if (visibility_manager_count
)
7375 visibility_manager_count
--;
7376 if (!visibility_manager_count
)
7377 purple_blist_set_visible(TRUE
);
7378 purple_debug_info("gtkblist", "removed visibility manager: %d\n", visibility_manager_count
);
7381 void pidgin_blist_add_alert(GtkWidget
*widget
)
7383 gtk_container_add(GTK_CONTAINER(gtkblist
->scrollbook
), widget
);
7388 pidgin_blist_set_headline(const char *text
, GdkPixbuf
*pixbuf
, GCallback callback
,
7389 gpointer user_data
, GDestroyNotify destroy
)
7391 /* Destroy any existing headline first */
7392 if (gtkblist
->headline_destroy
)
7393 gtkblist
->headline_destroy(gtkblist
->headline_data
);
7395 gtk_label_set_markup(GTK_LABEL(gtkblist
->headline_label
), text
);
7396 gtk_image_set_from_pixbuf(GTK_IMAGE(gtkblist
->headline_image
), pixbuf
);
7398 gtkblist
->headline_callback
= callback
;
7399 gtkblist
->headline_data
= user_data
;
7400 gtkblist
->headline_destroy
= destroy
;
7401 if (text
!= NULL
|| pixbuf
!= NULL
) {
7403 gtk_widget_show_all(gtkblist
->headline
);
7405 gtk_widget_hide(gtkblist
->headline
);
7413 if (gtkblist
->window
&& !gtk_widget_has_focus(gtkblist
->window
))
7414 pidgin_set_urgent(GTK_WINDOW(gtkblist
->window
), TRUE
);
7417 static PurpleBlistUiOps blist_ui_ops
=
7419 pidgin_blist_new_list
,
7420 pidgin_blist_new_node
,
7422 pidgin_blist_update
,
7423 pidgin_blist_remove
,
7424 pidgin_blist_destroy
,
7425 pidgin_blist_set_visible
,
7426 pidgin_blist_request_add_buddy
,
7427 pidgin_blist_request_add_chat
,
7428 pidgin_blist_request_add_group
,
7432 NULL
, NULL
, NULL
, NULL
7437 pidgin_blist_get_ui_ops(void)
7439 return &blist_ui_ops
;
7442 PidginBuddyList
*pidgin_blist_get_default_gtk_blist()
7447 static gboolean
autojoin_cb(PurpleConnection
*gc
, gpointer data
)
7449 PurpleAccount
*account
= purple_connection_get_account(gc
);
7450 PurpleBlistNode
*gnode
, *cnode
;
7451 for(gnode
= purple_blist_get_buddy_list()->root
; gnode
; gnode
= gnode
->next
)
7453 if(!PURPLE_IS_GROUP(gnode
))
7455 for(cnode
= gnode
->child
; cnode
; cnode
= cnode
->next
)
7459 if(!PURPLE_IS_CHAT(cnode
))
7462 chat
= (PurpleChat
*)cnode
;
7464 if(purple_chat_get_account(chat
) != account
)
7467 if (purple_blist_node_get_bool(PURPLE_BLIST_NODE(chat
), "gtk-autojoin"))
7468 purple_serv_join_chat(gc
, purple_chat_get_components(chat
));
7472 /* Stop processing; we handled the autojoins. */
7477 pidgin_blist_get_handle() {
7483 static gboolean
buddy_signonoff_timeout_cb(PurpleBuddy
*buddy
)
7485 struct _pidgin_blist_node
*gtknode
= purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(buddy
));
7487 gtknode
->recent_signonoff
= FALSE
;
7488 gtknode
->recent_signonoff_timer
= 0;
7490 pidgin_blist_update(NULL
, PURPLE_BLIST_NODE(buddy
));
7492 g_object_unref(buddy
);
7496 static void buddy_signonoff_cb(PurpleBuddy
*buddy
)
7498 struct _pidgin_blist_node
*gtknode
= purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(buddy
));
7501 pidgin_blist_new_node(PURPLE_BLIST_NODE(buddy
));
7504 gtknode
= purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(buddy
));
7506 gtknode
->recent_signonoff
= TRUE
;
7508 if(gtknode
->recent_signonoff_timer
> 0)
7509 purple_timeout_remove(gtknode
->recent_signonoff_timer
);
7511 g_object_ref(buddy
);
7512 gtknode
->recent_signonoff_timer
= purple_timeout_add_seconds(10,
7513 (GSourceFunc
)buddy_signonoff_timeout_cb
, buddy
);
7517 pidgin_blist_set_theme(PidginBlistTheme
*theme
)
7519 PidginBuddyListPrivate
*priv
= PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist
);
7520 PurpleBuddyList
*list
= purple_blist_get_buddy_list();
7523 purple_prefs_set_string(PIDGIN_PREFS_ROOT
"/blist/theme",
7524 purple_theme_get_name(PURPLE_THEME(theme
)));
7526 purple_prefs_set_string(PIDGIN_PREFS_ROOT
"/blist/theme", "");
7528 if (priv
->current_theme
)
7529 g_object_unref(priv
->current_theme
);
7531 priv
->current_theme
= theme
? g_object_ref(theme
) : NULL
;
7533 pidgin_blist_build_layout(list
);
7535 pidgin_blist_refresh(list
);
7540 pidgin_blist_get_theme()
7542 PidginBuddyListPrivate
*priv
= PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist
);
7544 return priv
->current_theme
;
7547 void pidgin_blist_init(void)
7549 void *gtk_blist_handle
= pidgin_blist_get_handle();
7551 cached_emblems
= g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, NULL
);
7553 /* Initialize prefs */
7554 purple_prefs_add_none(PIDGIN_PREFS_ROOT
"/blist");
7555 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/blist/show_buddy_icons", TRUE
);
7556 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/blist/show_empty_groups", FALSE
);
7557 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/blist/show_idle_time", TRUE
);
7558 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/blist/show_offline_buddies", FALSE
);
7559 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/blist/show_protocol_icons", FALSE
);
7560 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/blist/list_visible", FALSE
);
7561 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/blist/list_maximized", FALSE
);
7562 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/blist/sort_type", "alphabetical");
7563 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/blist/x", 0);
7564 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/blist/y", 0);
7565 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/blist/width", 250); /* Golden ratio, baby */
7566 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/blist/height", 405); /* Golden ratio, baby */
7567 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/blist/theme", "");
7569 purple_theme_manager_register_type(g_object_new(PIDGIN_TYPE_BLIST_THEME_LOADER
, "type", "blist", NULL
));
7571 /* Register our signals */
7572 purple_signal_register(gtk_blist_handle
, "gtkblist-hiding",
7573 purple_marshal_VOID__POINTER
, G_TYPE_NONE
, 1,
7574 PURPLE_TYPE_BUDDY_LIST
);
7576 purple_signal_register(gtk_blist_handle
, "gtkblist-unhiding",
7577 purple_marshal_VOID__POINTER
, G_TYPE_NONE
, 1,
7578 PURPLE_TYPE_BUDDY_LIST
);
7580 purple_signal_register(gtk_blist_handle
, "gtkblist-created",
7581 purple_marshal_VOID__POINTER
, G_TYPE_NONE
, 1,
7582 PURPLE_TYPE_BUDDY_LIST
);
7584 purple_signal_register(gtk_blist_handle
, "drawing-tooltip",
7585 purple_marshal_VOID__POINTER_POINTER_UINT
, G_TYPE_NONE
,
7586 3, PURPLE_TYPE_BLIST_NODE
,
7587 G_TYPE_POINTER
, /* pointer to a (GString *) */
7590 purple_signal_register(gtk_blist_handle
, "drawing-buddy",
7591 purple_marshal_POINTER__POINTER
,
7592 G_TYPE_STRING
, 1, PURPLE_TYPE_BUDDY
);
7594 purple_signal_connect(purple_blist_get_handle(), "buddy-signed-on",
7595 gtk_blist_handle
, PURPLE_CALLBACK(buddy_signonoff_cb
), NULL
);
7596 purple_signal_connect(purple_blist_get_handle(), "buddy-signed-off",
7597 gtk_blist_handle
, PURPLE_CALLBACK(buddy_signonoff_cb
), NULL
);
7598 purple_signal_connect(purple_blist_get_handle(), "buddy-privacy-changed",
7599 gtk_blist_handle
, PURPLE_CALLBACK(pidgin_blist_update_privacy_cb
), NULL
);
7601 purple_signal_connect_priority(purple_connections_get_handle(), "autojoin",
7602 gtk_blist_handle
, PURPLE_CALLBACK(autojoin_cb
),
7603 NULL
, PURPLE_SIGNAL_PRIORITY_HIGHEST
);
7607 pidgin_blist_uninit(void) {
7608 g_hash_table_destroy(cached_emblems
);
7610 purple_signals_unregister_by_instance(pidgin_blist_get_handle());
7611 purple_signals_disconnect_by_handle(pidgin_blist_get_handle());
7614 /*********************************************************************
7615 * Buddy List sorting functions *
7616 *********************************************************************/
7618 GList
*pidgin_blist_get_sort_methods()
7620 return pidgin_blist_sort_methods
;
7623 void pidgin_blist_sort_method_reg(const char *id
, const char *name
, pidgin_blist_sort_function func
)
7625 struct _PidginBlistSortMethod
*method
;
7627 g_return_if_fail(id
!= NULL
);
7628 g_return_if_fail(name
!= NULL
);
7629 g_return_if_fail(func
!= NULL
);
7631 method
= g_new0(struct _PidginBlistSortMethod
, 1);
7632 method
->id
= g_strdup(id
);
7633 method
->name
= g_strdup(name
);
7634 method
->func
= func
;
7635 pidgin_blist_sort_methods
= g_list_append(pidgin_blist_sort_methods
, method
);
7636 pidgin_blist_update_sort_methods();
7639 void pidgin_blist_sort_method_unreg(const char *id
)
7641 GList
*l
= pidgin_blist_sort_methods
;
7643 g_return_if_fail(id
!= NULL
);
7646 struct _PidginBlistSortMethod
*method
= l
->data
;
7647 if(!strcmp(method
->id
, id
)) {
7648 pidgin_blist_sort_methods
= g_list_delete_link(pidgin_blist_sort_methods
, l
);
7650 g_free(method
->name
);
7656 pidgin_blist_update_sort_methods();
7659 void pidgin_blist_sort_method_set(const char *id
){
7660 GList
*l
= pidgin_blist_sort_methods
;
7665 while (l
&& strcmp(((struct _PidginBlistSortMethod
*)l
->data
)->id
, id
))
7669 current_sort_method
= l
->data
;
7670 } else if (!current_sort_method
) {
7671 pidgin_blist_sort_method_set("none");
7674 if (!strcmp(id
, "none")) {
7675 redo_buddy_list(purple_blist_get_buddy_list(), TRUE
, FALSE
);
7677 redo_buddy_list(purple_blist_get_buddy_list(), FALSE
, FALSE
);
7681 /******************************************
7683 ******************************************/
7685 static void sort_method_none(PurpleBlistNode
*node
, PurpleBuddyList
*blist
, GtkTreeIter parent_iter
, GtkTreeIter
*cur
, GtkTreeIter
*iter
)
7687 PurpleBlistNode
*sibling
= node
->prev
;
7688 GtkTreeIter sibling_iter
;
7695 while (sibling
&& !get_iter_from_node(sibling
, &sibling_iter
)) {
7696 sibling
= sibling
->prev
;
7699 gtk_tree_store_insert_after(gtkblist
->treemodel
, iter
,
7700 node
->parent
? &parent_iter
: NULL
,
7701 sibling
? &sibling_iter
: NULL
);
7704 static void sort_method_alphabetical(PurpleBlistNode
*node
, PurpleBuddyList
*blist
, GtkTreeIter groupiter
, GtkTreeIter
*cur
, GtkTreeIter
*iter
)
7708 const char *my_name
;
7710 if(PURPLE_IS_CONTACT(node
)) {
7711 my_name
= purple_contact_get_alias((PurpleContact
*)node
);
7712 } else if(PURPLE_IS_CHAT(node
)) {
7713 my_name
= purple_chat_get_name((PurpleChat
*)node
);
7715 sort_method_none(node
, blist
, groupiter
, cur
, iter
);
7719 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(gtkblist
->treemodel
), &more_z
, &groupiter
)) {
7720 gtk_tree_store_insert(gtkblist
->treemodel
, iter
, &groupiter
, 0);
7726 const char *this_name
;
7729 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &more_z
, NODE_COLUMN
, &n
, -1);
7731 if(PURPLE_IS_CONTACT(n
)) {
7732 this_name
= purple_contact_get_alias((PurpleContact
*)n
);
7733 } else if(PURPLE_IS_CHAT(n
)) {
7734 this_name
= purple_chat_get_name((PurpleChat
*)n
);
7739 cmp
= purple_utf8_strcasecmp(my_name
, this_name
);
7741 if(this_name
&& (cmp
< 0 || (cmp
== 0 && node
< n
))) {
7743 gtk_tree_store_move_before(gtkblist
->treemodel
, cur
, &more_z
);
7747 gtk_tree_store_insert_before(gtkblist
->treemodel
, iter
,
7748 &groupiter
, &more_z
);
7752 } while (gtk_tree_model_iter_next (GTK_TREE_MODEL(gtkblist
->treemodel
), &more_z
));
7755 gtk_tree_store_move_before(gtkblist
->treemodel
, cur
, NULL
);
7759 gtk_tree_store_append(gtkblist
->treemodel
, iter
, &groupiter
);
7764 static void sort_method_status(PurpleBlistNode
*node
, PurpleBuddyList
*blist
, GtkTreeIter groupiter
, GtkTreeIter
*cur
, GtkTreeIter
*iter
)
7768 PurpleBuddy
*my_buddy
, *this_buddy
;
7770 if(PURPLE_IS_CONTACT(node
)) {
7771 my_buddy
= purple_contact_get_priority_buddy((PurpleContact
*)node
);
7772 } else if(PURPLE_IS_CHAT(node
)) {
7778 gtk_tree_store_append(gtkblist
->treemodel
, iter
, &groupiter
);
7781 sort_method_alphabetical(node
, blist
, groupiter
, cur
, iter
);
7786 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(gtkblist
->treemodel
), &more_z
, &groupiter
)) {
7787 gtk_tree_store_insert(gtkblist
->treemodel
, iter
, &groupiter
, 0);
7796 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &more_z
, NODE_COLUMN
, &n
, -1);
7798 if(PURPLE_IS_CONTACT(n
)) {
7799 this_buddy
= purple_contact_get_priority_buddy((PurpleContact
*)n
);
7804 name_cmp
= purple_utf8_strcasecmp(
7805 purple_contact_get_alias(purple_buddy_get_contact(my_buddy
)),
7807 ? purple_contact_get_alias(purple_buddy_get_contact(this_buddy
))
7810 presence_cmp
= purple_buddy_presence_compare(
7811 PURPLE_BUDDY_PRESENCE(purple_buddy_get_presence(my_buddy
)),
7812 this_buddy
? PURPLE_BUDDY_PRESENCE(purple_buddy_get_presence(this_buddy
)) : NULL
);
7814 if (this_buddy
== NULL
||
7815 (presence_cmp
< 0 ||
7816 (presence_cmp
== 0 &&
7817 (name_cmp
< 0 || (name_cmp
== 0 && node
< n
)))))
7821 gtk_tree_store_move_before(gtkblist
->treemodel
, cur
, &more_z
);
7827 gtk_tree_store_insert_before(gtkblist
->treemodel
, iter
,
7828 &groupiter
, &more_z
);
7833 while (gtk_tree_model_iter_next(GTK_TREE_MODEL(gtkblist
->treemodel
),
7837 gtk_tree_store_move_before(gtkblist
->treemodel
, cur
, NULL
);
7841 gtk_tree_store_append(gtkblist
->treemodel
, iter
, &groupiter
);
7846 static void sort_method_log_activity(PurpleBlistNode
*node
, PurpleBuddyList
*blist
, GtkTreeIter groupiter
, GtkTreeIter
*cur
, GtkTreeIter
*iter
)
7850 int activity_score
= 0, this_log_activity_score
= 0;
7851 const char *buddy_name
, *this_buddy_name
;
7853 if(cur
&& (gtk_tree_model_iter_n_children(GTK_TREE_MODEL(gtkblist
->treemodel
), &groupiter
) == 1)) {
7858 if(PURPLE_IS_CONTACT(node
)) {
7861 for (n
= node
->child
; n
; n
= n
->next
) {
7862 buddy
= (PurpleBuddy
*)n
;
7863 activity_score
+= purple_log_get_activity_score(PURPLE_LOG_IM
, purple_buddy_get_name(buddy
), purple_buddy_get_account(buddy
));
7865 buddy_name
= purple_contact_get_alias((PurpleContact
*)node
);
7866 } else if(PURPLE_IS_CHAT(node
)) {
7867 /* we don't have a reliable way of getting the log filename
7868 * from the chat info in the blist, yet */
7874 gtk_tree_store_append(gtkblist
->treemodel
, iter
, &groupiter
);
7877 sort_method_none(node
, blist
, groupiter
, cur
, iter
);
7882 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(gtkblist
->treemodel
), &more_z
, &groupiter
)) {
7883 gtk_tree_store_insert(gtkblist
->treemodel
, iter
, &groupiter
, 0);
7889 PurpleBlistNode
*n2
;
7893 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist
->treemodel
), &more_z
, NODE_COLUMN
, &n
, -1);
7894 this_log_activity_score
= 0;
7896 if(PURPLE_IS_CONTACT(n
)) {
7897 for (n2
= n
->child
; n2
; n2
= n2
->next
) {
7898 buddy
= (PurpleBuddy
*)n2
;
7899 this_log_activity_score
+= purple_log_get_activity_score(PURPLE_LOG_IM
, purple_buddy_get_name(buddy
), purple_buddy_get_account(buddy
));
7901 this_buddy_name
= purple_contact_get_alias((PurpleContact
*)n
);
7903 this_buddy_name
= NULL
;
7906 cmp
= purple_utf8_strcasecmp(buddy_name
, this_buddy_name
);
7908 if (!PURPLE_IS_CONTACT(n
) || activity_score
> this_log_activity_score
||
7909 ((activity_score
== this_log_activity_score
) &&
7910 (cmp
< 0 || (cmp
== 0 && node
< n
)))) {
7912 gtk_tree_store_move_before(gtkblist
->treemodel
, cur
, &more_z
);
7916 gtk_tree_store_insert_before(gtkblist
->treemodel
, iter
,
7917 &groupiter
, &more_z
);
7921 } while (gtk_tree_model_iter_next (GTK_TREE_MODEL(gtkblist
->treemodel
), &more_z
));
7924 gtk_tree_store_move_before(gtkblist
->treemodel
, cur
, NULL
);
7928 gtk_tree_store_append(gtkblist
->treemodel
, iter
, &groupiter
);
7934 plugin_act(GtkWidget
*obj
, PurplePluginAction
*pam
)
7936 if (pam
&& pam
->callback
)
7941 build_plugin_actions(GtkActionGroup
*action_group
, GString
*ui
, char *parent
,
7942 PurplePlugin
*plugin
)
7944 GtkAction
*menuaction
;
7945 PurplePluginActionsCb actions_cb
;
7946 PurplePluginAction
*action
= NULL
;
7952 purple_plugin_info_get_actions_cb(purple_plugin_get_info(plugin
));
7954 actions
= actions_cb(plugin
);
7956 for (l
= actions
; l
!= NULL
; l
= l
->next
) {
7958 action
= (PurplePluginAction
*)l
->data
;
7959 action
->plugin
= plugin
;
7961 name
= g_strdup_printf("%s-action-%d", parent
, count
++);
7962 menuaction
= gtk_action_new(name
, action
->label
, NULL
, NULL
);
7963 gtk_action_group_add_action(action_group
, menuaction
);
7964 g_string_append_printf(ui
, "<menuitem action='%s'/>", name
);
7966 g_signal_connect(G_OBJECT(menuaction
), "activate",
7967 G_CALLBACK(plugin_act
), action
);
7968 g_object_set_data_full(G_OBJECT(menuaction
), "plugin_action",
7970 (GDestroyNotify
)purple_plugin_action_free
);
7974 g_string_append(ui
, "<separator/>");
7977 g_list_free(actions
);
7982 modify_account_cb(GtkWidget
*widget
, gpointer data
)
7984 pidgin_account_dialog_show(PIDGIN_MODIFY_ACCOUNT_DIALOG
, data
);
7988 enable_account_cb(GtkCheckMenuItem
*widget
, gpointer data
)
7990 PurpleAccount
*account
= data
;
7991 const PurpleSavedStatus
*saved_status
;
7993 saved_status
= purple_savedstatus_get_current();
7994 purple_savedstatus_activate_for_account(saved_status
, account
);
7996 purple_account_set_enabled(account
, PIDGIN_UI
, TRUE
);
8000 disable_account_cb(GtkCheckMenuItem
*widget
, gpointer data
)
8002 PurpleAccount
*account
= data
;
8004 purple_account_set_enabled(account
, PIDGIN_UI
, FALSE
);
8008 protocol_act(GtkWidget
*obj
, PurpleProtocolAction
*pam
)
8010 if (pam
&& pam
->callback
)
8015 pidgin_blist_update_accounts_menu(void)
8017 GtkWidget
*menuitem
, *submenu
;
8018 GtkAccelGroup
*accel_group
;
8019 GList
*l
, *accounts
;
8020 gboolean disabled_accounts
= FALSE
;
8021 gboolean enabled_accounts
= FALSE
;
8023 if (accountmenu
== NULL
)
8026 /* Clear the old Accounts menu */
8027 for (l
= gtk_container_get_children(GTK_CONTAINER(accountmenu
)); l
; l
= g_list_delete_link(l
, l
)) {
8030 if (menuitem
!= gtk_ui_manager_get_widget(gtkblist
->ui
, "/BList/AccountsMenu/ManageAccounts"))
8031 gtk_widget_destroy(menuitem
);
8034 accel_group
= gtk_menu_get_accel_group(GTK_MENU(accountmenu
));
8036 for (accounts
= purple_accounts_get_all(); accounts
; accounts
= accounts
->next
) {
8038 GtkWidget
*image
= NULL
;
8039 PurpleAccount
*account
= NULL
;
8040 GdkPixbuf
*pixbuf
= NULL
;
8042 account
= accounts
->data
;
8044 if (!purple_account_get_enabled(account
, PIDGIN_UI
)) {
8045 if (!disabled_accounts
) {
8046 menuitem
= gtk_menu_item_new_with_label(_("Enable Account"));
8047 gtk_menu_shell_append(GTK_MENU_SHELL(accountmenu
), menuitem
);
8049 submenu
= gtk_menu_new();
8050 gtk_menu_set_accel_group(GTK_MENU(submenu
), accel_group
);
8051 gtk_menu_set_accel_path(GTK_MENU(submenu
), "<Actions>/BListActions/EnableAccount");
8052 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem
), submenu
);
8054 disabled_accounts
= TRUE
;
8057 buf
= g_strconcat(purple_account_get_username(account
), " (",
8058 purple_account_get_protocol_name(account
), ")", NULL
);
8059 menuitem
= gtk_image_menu_item_new_with_label(buf
);
8062 pixbuf
= pidgin_create_protocol_icon(account
, PIDGIN_PROTOCOL_ICON_SMALL
);
8063 if (pixbuf
!= NULL
) {
8064 if (!purple_account_is_connected(account
))
8065 gdk_pixbuf_saturate_and_pixelate(pixbuf
, pixbuf
, 0.0, FALSE
);
8066 image
= gtk_image_new_from_pixbuf(pixbuf
);
8067 g_object_unref(G_OBJECT(pixbuf
));
8068 gtk_widget_show(image
);
8069 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem
), image
);
8072 g_signal_connect(G_OBJECT(menuitem
), "activate",
8073 G_CALLBACK(enable_account_cb
), account
);
8074 gtk_menu_shell_append(GTK_MENU_SHELL(submenu
), menuitem
);
8077 enabled_accounts
= TRUE
;
8081 if (!enabled_accounts
) {
8082 gtk_widget_show_all(accountmenu
);
8086 pidgin_separator(accountmenu
);
8088 for (accounts
= purple_accounts_get_all(); accounts
; accounts
= accounts
->next
) {
8090 char *accel_path_buf
= NULL
;
8091 GtkWidget
*image
= NULL
;
8092 PurpleConnection
*gc
= NULL
;
8093 PurpleAccount
*account
= NULL
;
8094 GdkPixbuf
*pixbuf
= NULL
;
8095 PurpleProtocol
*protocol
;
8097 account
= accounts
->data
;
8099 if (!purple_account_get_enabled(account
, PIDGIN_UI
))
8102 buf
= g_strconcat(purple_account_get_username(account
), " (",
8103 purple_account_get_protocol_name(account
), ")", NULL
);
8104 menuitem
= gtk_image_menu_item_new_with_label(buf
);
8105 accel_path_buf
= g_strconcat("<Actions>/AccountActions/", buf
, NULL
);
8108 pixbuf
= pidgin_create_protocol_icon(account
, PIDGIN_PROTOCOL_ICON_SMALL
);
8109 if (pixbuf
!= NULL
) {
8110 if (!purple_account_is_connected(account
))
8111 gdk_pixbuf_saturate_and_pixelate(pixbuf
, pixbuf
,
8113 image
= gtk_image_new_from_pixbuf(pixbuf
);
8114 g_object_unref(G_OBJECT(pixbuf
));
8115 gtk_widget_show(image
);
8116 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem
), image
);
8119 gtk_menu_shell_append(GTK_MENU_SHELL(accountmenu
), menuitem
);
8121 submenu
= gtk_menu_new();
8122 gtk_menu_set_accel_group(GTK_MENU(submenu
), accel_group
);
8123 gtk_menu_set_accel_path(GTK_MENU(submenu
), accel_path_buf
);
8124 g_free(accel_path_buf
);
8125 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem
), submenu
);
8127 menuitem
= gtk_menu_item_new_with_mnemonic(_("_Edit Account"));
8128 g_signal_connect(G_OBJECT(menuitem
), "activate",
8129 G_CALLBACK(modify_account_cb
), account
);
8130 gtk_menu_shell_append(GTK_MENU_SHELL(submenu
), menuitem
);
8132 pidgin_separator(submenu
);
8134 gc
= purple_account_get_connection(account
);
8135 protocol
= gc
&& PURPLE_CONNECTION_IS_CONNECTED(gc
) ?
8136 purple_connection_get_protocol(gc
) : NULL
;
8139 (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CLIENT_IFACE
, get_moods
) ||
8140 PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CLIENT_IFACE
, get_actions
))) {
8141 if (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CLIENT_IFACE
, get_moods
) &&
8142 (purple_connection_get_flags(gc
) & PURPLE_CONNECTION_FLAG_SUPPORT_MOODS
)) {
8144 if (purple_account_get_status(account
, "mood")) {
8145 menuitem
= gtk_menu_item_new_with_mnemonic(_("Set _Mood..."));
8146 g_signal_connect(G_OBJECT(menuitem
), "activate",
8147 G_CALLBACK(set_mood_cb
), account
);
8148 gtk_menu_shell_append(GTK_MENU_SHELL(submenu
), menuitem
);
8152 if (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CLIENT_IFACE
, get_actions
)) {
8153 GtkWidget
*menuitem
;
8154 PurpleProtocolAction
*action
= NULL
;
8157 actions
= purple_protocol_client_iface_get_actions(protocol
, gc
);
8159 for (l
= actions
; l
!= NULL
; l
= l
->next
)
8163 action
= (PurpleProtocolAction
*) l
->data
;
8164 action
->connection
= gc
;
8166 menuitem
= gtk_menu_item_new_with_label(action
->label
);
8167 gtk_menu_shell_append(GTK_MENU_SHELL(submenu
), menuitem
);
8169 g_signal_connect(G_OBJECT(menuitem
), "activate",
8170 G_CALLBACK(protocol_act
), action
);
8171 g_object_set_data_full(G_OBJECT(menuitem
), "protocol_action",
8173 (GDestroyNotify
)purple_protocol_action_free
);
8174 gtk_widget_show(menuitem
);
8177 pidgin_separator(submenu
);
8180 g_list_free(actions
);
8183 menuitem
= gtk_menu_item_new_with_label(_("No actions available"));
8184 gtk_menu_shell_append(GTK_MENU_SHELL(submenu
), menuitem
);
8185 gtk_widget_set_sensitive(menuitem
, FALSE
);
8188 pidgin_separator(submenu
);
8190 menuitem
= gtk_menu_item_new_with_mnemonic(_("_Disable"));
8191 g_signal_connect(G_OBJECT(menuitem
), "activate",
8192 G_CALLBACK(disable_account_cb
), account
);
8193 gtk_menu_shell_append(GTK_MENU_SHELL(submenu
), menuitem
);
8196 gtk_widget_show_all(accountmenu
);
8199 static guint plugins_merge_id
;
8200 static GtkActionGroup
*plugins_action_group
= NULL
;
8203 pidgin_blist_update_plugin_actions(void)
8205 PurplePlugin
*plugin
= NULL
;
8206 PurplePluginInfo
*info
;
8210 GString
*plugins_ui
;
8214 if ((gtkblist
== NULL
) || (gtkblist
->ui
== NULL
))
8217 /* Clear the old menu */
8218 if (plugins_action_group
) {
8219 gtk_ui_manager_remove_ui(gtkblist
->ui
, plugins_merge_id
);
8220 gtk_ui_manager_remove_action_group(gtkblist
->ui
, plugins_action_group
);
8221 g_object_unref(G_OBJECT(plugins_action_group
));
8224 plugins_action_group
= gtk_action_group_new("PluginActions");
8226 gtk_action_group_set_translation_domain(plugins_action_group
, PACKAGE
);
8228 plugins_ui
= g_string_new(NULL
);
8230 /* Add a submenu for each plugin with custom actions */
8231 for (l
= purple_plugins_get_loaded(); l
; l
= l
->next
) {
8234 plugin
= (PurplePlugin
*)l
->data
;
8235 info
= purple_plugin_get_info(plugin
);
8237 if (!purple_plugin_info_get_actions_cb(info
))
8240 name
= g_strdup_printf("plugin%d", count
);
8241 action
= gtk_action_new(name
, _(purple_plugin_info_get_name(info
)), NULL
, NULL
);
8242 gtk_action_group_add_action(plugins_action_group
, action
);
8243 g_string_append_printf(plugins_ui
, "<menu action='%s'>", name
);
8245 build_plugin_actions(plugins_action_group
, plugins_ui
, name
, plugin
);
8247 g_string_append(plugins_ui
, "</menu>");
8253 ui_string
= g_strconcat("<ui><menubar action='BList'><menu action='ToolsMenu'><placeholder name='PluginActions'>",
8255 "</placeholder></menu></menubar></ui>",
8257 gtk_ui_manager_insert_action_group(gtkblist
->ui
, plugins_action_group
, 1);
8258 plugins_merge_id
= gtk_ui_manager_add_ui_from_string(gtkblist
->ui
, ui_string
, -1, NULL
);
8260 g_string_free(plugins_ui
, TRUE
);
8265 sortmethod_act(GtkRadioAction
*action
, GtkRadioAction
*current
, char *id
)
8267 if (action
== current
)
8269 pidgin_set_cursor(gtkblist
->window
, GDK_WATCH
);
8270 /* This is redundant. I think. */
8271 /* pidgin_blist_sort_method_set(id); */
8272 purple_prefs_set_string(PIDGIN_PREFS_ROOT
"/blist/sort_type", id
);
8274 pidgin_clear_cursor(gtkblist
->window
);
8279 pidgin_blist_update_sort_methods(void)
8281 PidginBlistSortMethod
*method
= NULL
;
8284 const char *m
= purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/blist/sort_type");
8286 GtkRadioAction
*action
;
8289 if ((gtkblist
== NULL
) || (gtkblist
->ui
== NULL
))
8292 /* Clear the old menu */
8293 if (sort_action_group
) {
8294 gtk_ui_manager_remove_ui(gtkblist
->ui
, sort_merge_id
);
8295 gtk_ui_manager_remove_action_group(gtkblist
->ui
, sort_action_group
);
8296 g_object_unref(G_OBJECT(sort_action_group
));
8299 sort_action_group
= gtk_action_group_new("SortMethods");
8301 gtk_action_group_set_translation_domain(sort_action_group
, PACKAGE
);
8303 ui_string
= g_string_new("<ui><menubar name='BList'>"
8304 "<menu action='BuddiesMenu'><menu action='SortMenu'>");
8306 for (l
= pidgin_blist_sort_methods
; l
; l
= l
->next
) {
8307 method
= (PidginBlistSortMethod
*)l
->data
;
8309 g_string_append_printf(ui_string
, "<menuitem action='%s'/>", method
->id
);
8310 action
= gtk_radio_action_new(method
->id
,
8315 gtk_action_group_add_action_with_accel(sort_action_group
, GTK_ACTION(action
), NULL
);
8317 gtk_radio_action_set_group(action
, sl
);
8318 sl
= gtk_radio_action_get_group(action
);
8320 if (!strcmp(m
, method
->id
))
8321 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action
), TRUE
);
8323 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action
), FALSE
);
8325 g_signal_connect(G_OBJECT(action
), "changed",
8326 G_CALLBACK(sortmethod_act
), method
->id
);
8329 g_string_append(ui_string
, "</menu></menu></menubar></ui>");
8330 gtk_ui_manager_insert_action_group(gtkblist
->ui
, sort_action_group
, 1);
8331 sort_merge_id
= gtk_ui_manager_add_ui_from_string(gtkblist
->ui
, ui_string
->str
, -1, NULL
);
8333 g_string_free(ui_string
, TRUE
);