Write this item more similarly to other items
[pidgin-git.git] / pidgin / gtkblist.c
blob6bcef4c88a7c459e765c41d5b0f6ce9bed3947dc
1 /*
2 * @file gtkblist.c GTK+ BuddyList API
3 * @ingroup pidgin
4 */
6 /* pidgin
8 * Pidgin is the legal property of its developers, whose names are too numerous
9 * to list here. Please refer to the COPYRIGHT file distributed with this
10 * source distribution.
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
27 #include "internal.h"
28 #include "pidgin.h"
30 #include "account.h"
31 #include "connection.h"
32 #include "core.h"
33 #include "debug.h"
34 #include "notify.h"
35 #include "prpl.h"
36 #include "prefs.h"
37 #include "plugin.h"
38 #include "request.h"
39 #include "signals.h"
40 #include "pidginstock.h"
41 #include "theme-loader.h"
42 #include "theme-manager.h"
43 #include "util.h"
45 #include "gtkaccount.h"
46 #include "gtkblist.h"
47 #include "gtkcellrendererexpander.h"
48 #include "gtkcertmgr.h"
49 #include "gtkconv.h"
50 #include "gtkdebug.h"
51 #include "gtkdialogs.h"
52 #include "gtkft.h"
53 #include "gtklog.h"
54 #include "gtkmenutray.h"
55 #include "gtkpounce.h"
56 #include "gtkplugin.h"
57 #include "gtkprefs.h"
58 #include "gtkprivacy.h"
59 #include "gtkroomlist.h"
60 #include "gtkstatusbox.h"
61 #include "gtkscrollbook.h"
62 #include "gtksmiley.h"
63 #include "gtkblist-theme.h"
64 #include "gtkblist-theme-loader.h"
65 #include "gtkutils.h"
66 #include "pidgin/minidialog.h"
67 #include "pidgin/pidgintooltip.h"
69 #include <gdk/gdkkeysyms.h>
70 #include <gtk/gtk.h>
71 #include <gdk/gdk.h>
73 typedef struct
75 PurpleAccount *account;
76 GtkWidget *window;
77 GtkBox *vbox;
78 GtkWidget *account_menu;
79 GtkSizeGroup *sg;
80 } PidginBlistRequestData;
82 typedef struct
84 PidginBlistRequestData rq_data;
85 GtkWidget *combo;
86 GtkWidget *entry;
87 GtkWidget *entry_for_alias;
88 GtkWidget *entry_for_invite;
90 } PidginAddBuddyData;
92 typedef struct
94 PidginBlistRequestData rq_data;
95 gchar *default_chat_name;
96 GList *entries;
97 } PidginChatData;
99 typedef struct
101 PidginChatData chat_data;
103 GtkWidget *alias_entry;
104 GtkWidget *group_combo;
105 GtkWidget *autojoin;
106 GtkWidget *persistent;
107 } PidginAddChatData;
109 typedef struct
111 /** Used to hold error minidialogs. Gets packed
112 * inside PidginBuddyList.error_buttons
114 PidginScrollBook *error_scrollbook;
116 /** Pointer to the mini-dialog about having signed on elsewhere, if one
117 * is showing; @c NULL otherwise.
119 PidginMiniDialog *signed_on_elsewhere;
121 PidginBlistTheme *current_theme;
122 } PidginBuddyListPrivate;
124 #define PIDGIN_BUDDY_LIST_GET_PRIVATE(list) \
125 ((PidginBuddyListPrivate *)((list)->priv))
127 static GtkWidget *accountmenu = NULL;
129 static guint visibility_manager_count = 0;
130 static GdkVisibilityState gtk_blist_visibility = GDK_VISIBILITY_UNOBSCURED;
131 static gboolean gtk_blist_focused = FALSE;
132 static gboolean editing_blist = FALSE;
134 static GList *pidgin_blist_sort_methods = NULL;
135 static struct pidgin_blist_sort_method *current_sort_method = NULL;
136 static void sort_method_none(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter);
138 static void sort_method_alphabetical(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter);
139 static void sort_method_status(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter);
140 static void sort_method_log_activity(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter);
141 static PidginBuddyList *gtkblist = NULL;
143 static GList *groups_tree(void);
144 static gboolean pidgin_blist_refresh_timer(PurpleBuddyList *list);
145 static void pidgin_blist_update_buddy(PurpleBuddyList *list, PurpleBlistNode *node, gboolean status_change);
146 static void pidgin_blist_selection_changed(GtkTreeSelection *selection, gpointer data);
147 static void pidgin_blist_update(PurpleBuddyList *list, PurpleBlistNode *node);
148 static void pidgin_blist_update_group(PurpleBuddyList *list, PurpleBlistNode *node);
149 static void pidgin_blist_update_contact(PurpleBuddyList *list, PurpleBlistNode *node);
150 static char *pidgin_get_tooltip_text(PurpleBlistNode *node, gboolean full);
151 static const char *item_factory_translate_func (const char *path, gpointer func_data);
152 static gboolean get_iter_from_node(PurpleBlistNode *node, GtkTreeIter *iter);
153 static gboolean buddy_is_displayable(PurpleBuddy *buddy);
154 static void redo_buddy_list(PurpleBuddyList *list, gboolean remove, gboolean rerender);
155 static void pidgin_blist_collapse_contact_cb(GtkWidget *w, PurpleBlistNode *node);
156 static char *pidgin_get_group_title(PurpleBlistNode *gnode, gboolean expanded);
157 static void pidgin_blist_expand_contact_cb(GtkWidget *w, PurpleBlistNode *node);
158 static void set_urgent(void);
160 typedef enum {
161 PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE = 1 << 0, /* Whether there's pending message in a conversation */
162 PIDGIN_BLIST_CHAT_HAS_PENDING_MESSAGE_WITH_NICK = 1 << 1, /* Whether there's a pending message in a chat that mentions our nick */
163 } PidginBlistNodeFlags;
165 typedef struct _pidgin_blist_node {
166 GtkTreeRowReference *row;
167 gboolean contact_expanded;
168 gboolean recent_signonoff;
169 gint recent_signonoff_timer;
170 struct {
171 PurpleConversation *conv;
172 time_t last_message; /* timestamp for last displayed message */
173 PidginBlistNodeFlags flags;
174 } conv;
175 } PidginBlistNode;
177 /***************************************************
178 * Callbacks *
179 ***************************************************/
180 static gboolean gtk_blist_visibility_cb(GtkWidget *w, GdkEventVisibility *event, gpointer data)
182 GdkVisibilityState old_state = gtk_blist_visibility;
183 gtk_blist_visibility = event->state;
185 if (gtk_blist_visibility == GDK_VISIBILITY_FULLY_OBSCURED &&
186 old_state != GDK_VISIBILITY_FULLY_OBSCURED) {
188 /* no longer fully obscured */
189 pidgin_blist_refresh_timer(purple_get_blist());
192 /* continue to handle event normally */
193 return FALSE;
196 static gboolean gtk_blist_window_state_cb(GtkWidget *w, GdkEventWindowState *event, gpointer data)
198 if(event->changed_mask & GDK_WINDOW_STATE_WITHDRAWN) {
199 if(event->new_window_state & GDK_WINDOW_STATE_WITHDRAWN)
200 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/list_visible", FALSE);
201 else {
202 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/list_visible", TRUE);
203 pidgin_blist_refresh_timer(purple_get_blist());
207 if(event->changed_mask & GDK_WINDOW_STATE_MAXIMIZED) {
208 if(event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED)
209 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/list_maximized", TRUE);
210 else
211 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/list_maximized", FALSE);
214 /* Refresh gtkblist if un-iconifying */
215 if (event->changed_mask & GDK_WINDOW_STATE_ICONIFIED){
216 if (!(event->new_window_state & GDK_WINDOW_STATE_ICONIFIED))
217 pidgin_blist_refresh_timer(purple_get_blist());
220 return FALSE;
223 static gboolean gtk_blist_delete_cb(GtkWidget *w, GdkEventAny *event, gpointer data)
225 if(visibility_manager_count)
226 purple_blist_set_visible(FALSE);
227 else
228 purple_core_quit();
230 /* we handle everything, event should not propogate further */
231 return TRUE;
234 static gboolean gtk_blist_configure_cb(GtkWidget *w, GdkEventConfigure *event, gpointer data)
236 /* unfortunately GdkEventConfigure ignores the window gravity, but *
237 * the only way we have of setting the position doesn't. we have to *
238 * call get_position because it does pay attention to the gravity. *
239 * this is inefficient and I agree it sucks, but it's more likely *
240 * to work correctly. - Robot101 */
241 gint x, y;
243 /* check for visibility because when we aren't visible, this will *
244 * give us bogus (0,0) coordinates. - xOr */
245 if (GTK_WIDGET_VISIBLE(w))
246 gtk_window_get_position(GTK_WINDOW(w), &x, &y);
247 else
248 return FALSE; /* carry on normally */
250 #ifdef _WIN32
251 /* Workaround for GTK+ bug # 169811 - "configure_event" is fired
252 * when the window is being maximized */
253 if (gdk_window_get_state(w->window)
254 & GDK_WINDOW_STATE_MAXIMIZED) {
255 return FALSE;
257 #endif
259 /* don't save if nothing changed */
260 if (x == purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/x") &&
261 y == purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/y") &&
262 event->width == purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/width") &&
263 event->height == purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/height")) {
265 return FALSE; /* carry on normally */
268 /* don't save off-screen positioning */
269 if (x + event->width < 0 ||
270 y + event->height < 0 ||
271 x > gdk_screen_width() ||
272 y > gdk_screen_height()) {
274 return FALSE; /* carry on normally */
277 /* ignore changes when maximized */
278 if(purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/list_maximized"))
279 return FALSE;
281 /* store the position */
282 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/blist/x", x);
283 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/blist/y", y);
284 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/blist/width", event->width);
285 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/blist/height", event->height);
287 /* continue to handle event normally */
288 return FALSE;
291 static void gtk_blist_menu_info_cb(GtkWidget *w, PurpleBuddy *b)
293 PurpleAccount *account = purple_buddy_get_account(b);
295 pidgin_retrieve_user_info(purple_account_get_connection(account),
296 purple_buddy_get_name(b));
299 static void gtk_blist_menu_im_cb(GtkWidget *w, PurpleBuddy *b)
301 pidgin_dialogs_im_with_user(purple_buddy_get_account(b),
302 purple_buddy_get_name(b));
305 #ifdef USE_VV
306 static void gtk_blist_menu_audio_call_cb(GtkWidget *w, PurpleBuddy *b)
308 purple_prpl_initiate_media(purple_buddy_get_account(b),
309 purple_buddy_get_name(b), PURPLE_MEDIA_AUDIO);
312 static void gtk_blist_menu_video_call_cb(GtkWidget *w, PurpleBuddy *b)
314 /* if the buddy supports both audio and video, start a combined call,
315 otherwise start a pure video session */
316 if (purple_prpl_get_media_caps(purple_buddy_get_account(b),
317 purple_buddy_get_name(b)) &
318 PURPLE_MEDIA_CAPS_AUDIO_VIDEO) {
319 purple_prpl_initiate_media(purple_buddy_get_account(b),
320 purple_buddy_get_name(b), PURPLE_MEDIA_AUDIO | PURPLE_MEDIA_VIDEO);
321 } else {
322 purple_prpl_initiate_media(purple_buddy_get_account(b),
323 purple_buddy_get_name(b), PURPLE_MEDIA_VIDEO);
327 #endif
329 static void gtk_blist_menu_send_file_cb(GtkWidget *w, PurpleBuddy *b)
331 PurpleAccount *account = purple_buddy_get_account(b);
333 serv_send_file(purple_account_get_connection(account),
334 purple_buddy_get_name(b), NULL);
337 static void gtk_blist_menu_move_to_cb(GtkWidget *w, PurpleBlistNode *node)
339 PurpleGroup *group = g_object_get_data(G_OBJECT(w), "groupnode");
340 purple_blist_add_contact((PurpleContact *)node, group, NULL);
344 static void gtk_blist_menu_autojoin_cb(GtkWidget *w, PurpleChat *chat)
346 purple_blist_node_set_bool((PurpleBlistNode*)chat, "gtk-autojoin",
347 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w)));
350 static void gtk_blist_menu_persistent_cb(GtkWidget *w, PurpleChat *chat)
352 purple_blist_node_set_bool((PurpleBlistNode*)chat, "gtk-persistent",
353 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w)));
356 static PurpleConversation *
357 find_conversation_with_buddy(PurpleBuddy *buddy)
359 PidginBlistNode *ui = purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(buddy));
360 if (ui)
361 return ui->conv.conv;
362 return purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
363 purple_buddy_get_name(buddy),
364 purple_buddy_get_account(buddy));
367 static void gtk_blist_join_chat(PurpleChat *chat)
369 PurpleAccount *account;
370 PurpleConversation *conv;
371 PurplePluginProtocolInfo *prpl_info;
372 GHashTable *components;
373 const char *name;
374 char *chat_name;
376 account = purple_chat_get_account(chat);
377 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_find_prpl(purple_account_get_protocol_id(account)));
379 components = purple_chat_get_components(chat);
381 if (prpl_info && prpl_info->get_chat_name)
382 chat_name = prpl_info->get_chat_name(components);
383 else
384 chat_name = NULL;
386 if (chat_name)
387 name = chat_name;
388 else
389 name = purple_chat_get_name(chat);
391 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, name,
392 account);
394 if (conv != NULL) {
395 pidgin_conv_attach_to_conversation(conv);
396 purple_conversation_present(conv);
399 serv_join_chat(purple_account_get_connection(account), components);
400 g_free(chat_name);
403 static void gtk_blist_menu_join_cb(GtkWidget *w, PurpleChat *chat)
405 gtk_blist_join_chat(chat);
408 static void gtk_blist_renderer_editing_cancelled_cb(GtkCellRenderer *renderer, PurpleBuddyList *list)
410 editing_blist = FALSE;
411 g_object_set(G_OBJECT(renderer), "editable", FALSE, NULL);
412 pidgin_blist_refresh(list);
415 static void gtk_blist_renderer_editing_started_cb(GtkCellRenderer *renderer,
416 GtkCellEditable *editable,
417 gchar *path_str,
418 gpointer user_data)
420 GtkTreeIter iter;
421 GtkTreePath *path = NULL;
422 PurpleBlistNode *node;
423 const char *text = NULL;
425 path = gtk_tree_path_new_from_string (path_str);
426 gtk_tree_model_get_iter (GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
427 gtk_tree_path_free (path);
428 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
430 switch (purple_blist_node_get_type(node)) {
431 case PURPLE_BLIST_CONTACT_NODE:
432 text = purple_contact_get_alias(PURPLE_CONTACT(node));
433 break;
434 case PURPLE_BLIST_BUDDY_NODE:
435 text = purple_buddy_get_alias(PURPLE_BUDDY(node));
436 break;
437 case PURPLE_BLIST_GROUP_NODE:
438 text = purple_group_get_name(PURPLE_GROUP(node));
439 break;
440 case PURPLE_BLIST_CHAT_NODE:
441 text = purple_chat_get_name(PURPLE_CHAT(node));
442 break;
443 default:
444 g_return_if_reached();
447 if (GTK_IS_ENTRY (editable)) {
448 GtkEntry *entry = GTK_ENTRY (editable);
449 gtk_entry_set_text(entry, text);
451 editing_blist = TRUE;
454 static void
455 gtk_blist_do_personize(GList *merges)
457 PurpleBlistNode *contact = NULL;
458 int max = 0;
459 GList *tmp;
461 /* First, we find the contact to merge the rest of the buddies into.
462 * This will be the contact with the most buddies in it; ties are broken
463 * by which contact is higher in the list
465 for (tmp = merges; tmp; tmp = tmp->next) {
466 PurpleBlistNode *node = tmp->data;
467 PurpleBlistNode *b;
468 PurpleBlistNodeType type;
469 int i = 0;
471 type = purple_blist_node_get_type(node);
473 if (type == PURPLE_BLIST_BUDDY_NODE) {
474 node = purple_blist_node_get_parent(node);
475 type = purple_blist_node_get_type(node);
478 if (type != PURPLE_BLIST_CONTACT_NODE)
479 continue;
481 for (b = purple_blist_node_get_first_child(node);
483 b = purple_blist_node_get_sibling_next(b))
485 i++;
488 if (i > max) {
489 contact = node;
490 max = i;
494 if (contact == NULL)
495 return;
497 /* Merge all those buddies into this contact */
498 for (tmp = merges; tmp; tmp = tmp->next) {
499 PurpleBlistNode *node = tmp->data;
500 if (purple_blist_node_get_type(node) == PURPLE_BLIST_BUDDY_NODE)
501 node = purple_blist_node_get_parent(node);
503 if (node == contact)
504 continue;
506 purple_blist_merge_contact((PurpleContact *)node, contact);
509 /* And show the expanded contact, so the people know what's going on */
510 pidgin_blist_expand_contact_cb(NULL, contact);
511 g_list_free(merges);
514 static void
515 gtk_blist_auto_personize(PurpleBlistNode *group, const char *alias)
517 PurpleBlistNode *contact;
518 PurpleBlistNode *buddy;
519 GList *merges = NULL;
520 int i = 0;
521 char *a = g_utf8_casefold(alias, -1);
523 for (contact = purple_blist_node_get_first_child(group);
524 contact != NULL;
525 contact = purple_blist_node_get_sibling_next(contact)) {
526 char *node_alias;
527 if (purple_blist_node_get_type(contact) != PURPLE_BLIST_CONTACT_NODE)
528 continue;
530 node_alias = g_utf8_casefold(purple_contact_get_alias((PurpleContact *)contact), -1);
531 if (node_alias && !g_utf8_collate(node_alias, a)) {
532 merges = g_list_append(merges, contact);
533 i++;
534 g_free(node_alias);
535 continue;
537 g_free(node_alias);
539 for (buddy = purple_blist_node_get_first_child(contact);
540 buddy;
541 buddy = purple_blist_node_get_sibling_next(buddy))
543 if (purple_blist_node_get_type(buddy) != PURPLE_BLIST_BUDDY_NODE)
544 continue;
546 node_alias = g_utf8_casefold(purple_buddy_get_alias(PURPLE_BUDDY(buddy)), -1);
547 if (node_alias && !g_utf8_collate(node_alias, a)) {
548 merges = g_list_append(merges, buddy);
549 i++;
550 g_free(node_alias);
551 break;
553 g_free(node_alias);
556 g_free(a);
558 if (i > 1)
560 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);
561 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. "
562 "You can separate them again by choosing 'Expand' from the contact's context menu"), 0, NULL, NULL, NULL,
563 merges, 2, _("_Yes"), PURPLE_CALLBACK(gtk_blist_do_personize), _("_No"), PURPLE_CALLBACK(g_list_free));
564 g_free(msg);
565 } else
566 g_list_free(merges);
569 static void gtk_blist_renderer_edited_cb(GtkCellRendererText *text_rend, char *arg1,
570 char *arg2, PurpleBuddyList *list)
572 GtkTreeIter iter;
573 GtkTreePath *path;
574 PurpleBlistNode *node;
575 PurpleGroup *dest;
577 editing_blist = FALSE;
578 path = gtk_tree_path_new_from_string (arg1);
579 gtk_tree_model_get_iter (GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
580 gtk_tree_path_free (path);
581 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
582 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(gtkblist->treeview), TRUE);
583 g_object_set(G_OBJECT(gtkblist->text_rend), "editable", FALSE, NULL);
585 switch (purple_blist_node_get_type(node))
587 case PURPLE_BLIST_CONTACT_NODE:
589 PurpleContact *contact = PURPLE_CONTACT(node);
590 struct _pidgin_blist_node *gtknode =
591 (struct _pidgin_blist_node *)purple_blist_node_get_ui_data(node);
594 * XXX Using purple_contact_get_alias here breaks because we
595 * specifically want to check the contact alias only (i.e. not
596 * the priority buddy, which purple_contact_get_alias does).
597 * Adding yet another get_alias is evil, so figure this out
598 * later :-P
600 if (contact->alias || gtknode->contact_expanded) {
601 purple_blist_alias_contact(contact, arg2);
602 gtk_blist_auto_personize(purple_blist_node_get_parent(node), arg2);
603 } else {
604 PurpleBuddy *buddy = purple_contact_get_priority_buddy(contact);
605 purple_blist_alias_buddy(buddy, arg2);
606 serv_alias_buddy(buddy);
607 gtk_blist_auto_personize(purple_blist_node_get_parent(node), arg2);
610 break;
612 case PURPLE_BLIST_BUDDY_NODE:
614 PurpleGroup *group = purple_buddy_get_group(PURPLE_BUDDY(node));
616 purple_blist_alias_buddy(PURPLE_BUDDY(node), arg2);
617 serv_alias_buddy(PURPLE_BUDDY(node));
618 gtk_blist_auto_personize(PURPLE_BLIST_NODE(group), arg2);
620 break;
621 case PURPLE_BLIST_GROUP_NODE:
622 dest = purple_find_group(arg2);
623 if (dest != NULL && purple_utf8_strcasecmp(arg2, purple_group_get_name(PURPLE_GROUP(node)))) {
624 pidgin_dialogs_merge_groups(PURPLE_GROUP(node), arg2);
625 } else {
626 purple_blist_rename_group(PURPLE_GROUP(node), arg2);
628 break;
629 case PURPLE_BLIST_CHAT_NODE:
630 purple_blist_alias_chat(PURPLE_CHAT(node), arg2);
631 break;
632 default:
633 break;
635 pidgin_blist_refresh(list);
638 static void
639 chat_components_edit_ok(PurpleChat *chat, PurpleRequestFields *allfields)
641 GList *groups, *fields;
643 for (groups = purple_request_fields_get_groups(allfields); groups; groups = groups->next) {
644 fields = purple_request_field_group_get_fields(groups->data);
645 for (; fields; fields = fields->next) {
646 PurpleRequestField *field = fields->data;
647 const char *id;
648 char *val;
650 id = purple_request_field_get_id(field);
651 if (purple_request_field_get_type(field) == PURPLE_REQUEST_FIELD_INTEGER)
652 val = g_strdup_printf("%d", purple_request_field_int_get_value(field));
653 else
654 val = g_strdup(purple_request_field_string_get_value(field));
656 if (!val) {
657 g_hash_table_remove(purple_chat_get_components(chat), id);
658 } else {
659 g_hash_table_replace(purple_chat_get_components(chat), g_strdup(id), val); /* val should not be free'd */
665 static void chat_components_edit(GtkWidget *w, PurpleBlistNode *node)
667 PurpleRequestFields *fields = purple_request_fields_new();
668 PurpleRequestFieldGroup *group = purple_request_field_group_new(NULL);
669 PurpleRequestField *field;
670 GList *parts, *iter;
671 struct proto_chat_entry *pce;
672 PurpleConnection *gc;
673 PurpleChat *chat = (PurpleChat*)node;
675 purple_request_fields_add_group(fields, group);
677 gc = purple_account_get_connection(purple_chat_get_account(chat));
678 parts = PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc))->chat_info(gc);
680 for (iter = parts; iter; iter = iter->next) {
681 pce = iter->data;
682 if (pce->is_int) {
683 int val;
684 const char *str = g_hash_table_lookup(purple_chat_get_components(chat), pce->identifier);
685 if (!str || sscanf(str, "%d", &val) != 1)
686 val = pce->min;
687 field = purple_request_field_int_new(pce->identifier, pce->label, val);
688 } else {
689 field = purple_request_field_string_new(pce->identifier, pce->label,
690 g_hash_table_lookup(purple_chat_get_components(chat), pce->identifier), FALSE);
691 if (pce->secret)
692 purple_request_field_string_set_masked(field, TRUE);
695 if (pce->required)
696 purple_request_field_set_required(field, TRUE);
698 purple_request_field_group_add_field(group, field);
699 g_free(pce);
702 g_list_free(parts);
704 purple_request_fields(NULL, _("Edit Chat"), NULL, _("Please update the necessary fields."),
705 fields, _("Save"), G_CALLBACK(chat_components_edit_ok), _("Cancel"), NULL,
706 NULL, NULL, NULL,
707 chat);
710 static void gtk_blist_menu_alias_cb(GtkWidget *w, PurpleBlistNode *node)
712 GtkTreeIter iter;
713 GtkTreePath *path;
715 if (!(get_iter_from_node(node, &iter))) {
716 /* This is either a bug, or the buddy is in a collapsed contact */
717 node = purple_blist_node_get_parent(node);
718 if (!get_iter_from_node(node, &iter))
719 /* Now it's definitely a bug */
720 return;
723 pidgin_blist_tooltip_destroy();
725 path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &iter);
726 g_object_set(G_OBJECT(gtkblist->text_rend), "editable", TRUE, NULL);
727 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(gtkblist->treeview), FALSE);
728 gtk_widget_grab_focus(gtkblist->treeview);
729 gtk_tree_view_set_cursor_on_cell(GTK_TREE_VIEW(gtkblist->treeview), path,
730 gtkblist->text_column, gtkblist->text_rend, TRUE);
731 gtk_tree_path_free(path);
734 static void gtk_blist_menu_bp_cb(GtkWidget *w, PurpleBuddy *b)
736 pidgin_pounce_editor_show(purple_buddy_get_account(b),
737 purple_buddy_get_name(b), NULL);
740 static void gtk_blist_menu_showlog_cb(GtkWidget *w, PurpleBlistNode *node)
742 PurpleLogType type;
743 PurpleAccount *account;
744 char *name = NULL;
746 pidgin_set_cursor(gtkblist->window, GDK_WATCH);
748 if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
749 PurpleBuddy *b = (PurpleBuddy*) node;
750 type = PURPLE_LOG_IM;
751 name = g_strdup(purple_buddy_get_name(b));
752 account = purple_buddy_get_account(b);
753 } else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
754 PurpleChat *c = PURPLE_CHAT(node);
755 PurplePluginProtocolInfo *prpl_info = NULL;
756 type = PURPLE_LOG_CHAT;
757 account = purple_chat_get_account(c);
758 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_find_prpl(purple_account_get_protocol_id(account)));
759 if (prpl_info && prpl_info->get_chat_name) {
760 name = prpl_info->get_chat_name(purple_chat_get_components(c));
762 } else if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
763 pidgin_log_show_contact(PURPLE_CONTACT(node));
764 pidgin_clear_cursor(gtkblist->window);
765 return;
766 } else {
767 pidgin_clear_cursor(gtkblist->window);
769 /* This callback should not have been registered for a node
770 * that doesn't match the type of one of the blocks above. */
771 g_return_if_reached();
774 if (name && account) {
775 pidgin_log_show(type, name, account);
776 pidgin_clear_cursor(gtkblist->window);
779 g_free(name);
782 static void gtk_blist_menu_showoffline_cb(GtkWidget *w, PurpleBlistNode *node)
784 if (PURPLE_BLIST_NODE_IS_BUDDY(node))
786 purple_blist_node_set_bool(node, "show_offline",
787 !purple_blist_node_get_bool(node, "show_offline"));
788 pidgin_blist_update(purple_get_blist(), node);
790 else if (PURPLE_BLIST_NODE_IS_CONTACT(node))
792 PurpleBlistNode *bnode;
793 gboolean setting = !purple_blist_node_get_bool(node, "show_offline");
795 purple_blist_node_set_bool(node, "show_offline", setting);
796 for (bnode = purple_blist_node_get_first_child(node);
797 bnode != NULL;
798 bnode = purple_blist_node_get_sibling_next(bnode))
800 purple_blist_node_set_bool(bnode, "show_offline", setting);
801 pidgin_blist_update(purple_get_blist(), bnode);
803 } else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
804 PurpleBlistNode *cnode, *bnode;
805 gboolean setting = !purple_blist_node_get_bool(node, "show_offline");
807 purple_blist_node_set_bool(node, "show_offline", setting);
808 for (cnode = purple_blist_node_get_first_child(node);
809 cnode != NULL;
810 cnode = purple_blist_node_get_sibling_next(cnode))
812 purple_blist_node_set_bool(cnode, "show_offline", setting);
813 for (bnode = purple_blist_node_get_first_child(cnode);
814 bnode != NULL;
815 bnode = purple_blist_node_get_sibling_next(bnode))
817 purple_blist_node_set_bool(bnode, "show_offline", setting);
818 pidgin_blist_update(purple_get_blist(), bnode);
824 static void gtk_blist_show_systemlog_cb(void)
826 pidgin_syslog_show();
829 static void gtk_blist_show_onlinehelp_cb(void)
831 purple_notify_uri(NULL, PURPLE_WEBSITE "documentation");
834 static void
835 do_join_chat(PidginChatData *data)
837 if (data)
839 GHashTable *components =
840 g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
841 GList *tmp;
842 PurpleChat *chat;
844 for (tmp = data->entries; tmp != NULL; tmp = tmp->next)
846 if (g_object_get_data(tmp->data, "is_spin"))
848 g_hash_table_replace(components,
849 g_strdup(g_object_get_data(tmp->data, "identifier")),
850 g_strdup_printf("%d",
851 gtk_spin_button_get_value_as_int(tmp->data)));
853 else
855 g_hash_table_replace(components,
856 g_strdup(g_object_get_data(tmp->data, "identifier")),
857 g_strdup(gtk_entry_get_text(tmp->data)));
861 chat = purple_chat_new(data->rq_data.account, NULL, components);
862 gtk_blist_join_chat(chat);
863 purple_blist_remove_chat(chat);
867 static void
868 do_joinchat(GtkWidget *dialog, int id, PidginChatData *info)
870 switch(id)
872 case GTK_RESPONSE_OK:
873 do_join_chat(info);
874 break;
876 case 1:
877 pidgin_roomlist_dialog_show_with_account(info->rq_data.account);
878 return;
880 break;
883 gtk_widget_destroy(GTK_WIDGET(dialog));
884 g_list_free(info->entries);
885 g_free(info);
889 * Check the values of all the text entry boxes. If any required input
890 * strings are empty then don't allow the user to click on "OK."
892 static void
893 set_sensitive_if_input_cb(GtkWidget *entry, gpointer user_data)
895 PurplePluginProtocolInfo *prpl_info;
896 PurpleConnection *gc;
897 PidginChatData *data;
898 GList *tmp;
899 const char *text;
900 gboolean required;
901 gboolean sensitive = TRUE;
903 data = user_data;
905 for (tmp = data->entries; tmp != NULL; tmp = tmp->next)
907 if (!g_object_get_data(tmp->data, "is_spin"))
909 required = GPOINTER_TO_INT(g_object_get_data(tmp->data, "required"));
910 text = gtk_entry_get_text(tmp->data);
911 if (required && (*text == '\0'))
912 sensitive = FALSE;
916 gtk_dialog_set_response_sensitive(GTK_DIALOG(data->rq_data.window), GTK_RESPONSE_OK, sensitive);
918 gc = purple_account_get_connection(data->rq_data.account);
919 prpl_info = (gc != NULL) ? PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl) : NULL;
920 sensitive = (prpl_info != NULL && prpl_info->roomlist_get_list != NULL);
922 gtk_dialog_set_response_sensitive(GTK_DIALOG(data->rq_data.window), 1, sensitive);
925 static void
926 pidgin_blist_update_privacy_cb(PurpleBuddy *buddy)
928 struct _pidgin_blist_node *ui_data = purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(buddy));
929 if (ui_data == NULL || ui_data->row == NULL)
930 return;
931 pidgin_blist_update_buddy(purple_get_blist(), PURPLE_BLIST_NODE(buddy), TRUE);
934 static gboolean
935 chat_account_filter_func(PurpleAccount *account)
937 PurpleConnection *gc = purple_account_get_connection(account);
938 PurplePluginProtocolInfo *prpl_info = NULL;
940 if (gc == NULL)
941 return FALSE;
943 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
945 return (prpl_info->chat_info != NULL);
948 gboolean
949 pidgin_blist_joinchat_is_showable()
951 GList *c;
952 PurpleConnection *gc;
954 for (c = purple_connections_get_all(); c != NULL; c = c->next) {
955 gc = c->data;
957 if (chat_account_filter_func(purple_connection_get_account(gc)))
958 return TRUE;
961 return FALSE;
964 static GtkWidget *
965 make_blist_request_dialog(PidginBlistRequestData *data, PurpleAccount *account,
966 const char *title, const char *window_role, const char *label_text,
967 GCallback callback_func, PurpleFilterAccountFunc filter_func,
968 GCallback response_cb)
970 GtkWidget *label;
971 GtkWidget *img;
972 GtkWidget *hbox;
973 GtkWidget *vbox;
974 GtkWindow *blist_window;
975 PidginBuddyList *gtkblist;
977 data->account = account;
979 img = gtk_image_new_from_stock(PIDGIN_STOCK_DIALOG_QUESTION,
980 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_HUGE));
982 gtkblist = PIDGIN_BLIST(purple_get_blist());
983 blist_window = gtkblist ? GTK_WINDOW(gtkblist->window) : NULL;
985 data->window = gtk_dialog_new_with_buttons(title,
986 blist_window, GTK_DIALOG_NO_SEPARATOR,
987 NULL);
989 gtk_window_set_transient_for(GTK_WINDOW(data->window), blist_window);
990 gtk_dialog_set_default_response(GTK_DIALOG(data->window), GTK_RESPONSE_OK);
991 gtk_container_set_border_width(GTK_CONTAINER(data->window), PIDGIN_HIG_BOX_SPACE);
992 gtk_window_set_resizable(GTK_WINDOW(data->window), FALSE);
993 gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(data->window)->vbox), PIDGIN_HIG_BORDER);
994 gtk_container_set_border_width(GTK_CONTAINER(GTK_DIALOG(data->window)->vbox), PIDGIN_HIG_BOX_SPACE);
995 gtk_window_set_role(GTK_WINDOW(data->window), window_role);
997 hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BORDER);
998 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(data->window)->vbox), hbox);
999 gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0);
1000 gtk_misc_set_alignment(GTK_MISC(img), 0, 0);
1002 vbox = gtk_vbox_new(FALSE, 5);
1003 gtk_container_add(GTK_CONTAINER(hbox), vbox);
1005 label = gtk_label_new(label_text);
1007 gtk_widget_set_size_request(label, 400, -1);
1008 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1009 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
1010 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1012 data->sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
1014 data->account_menu = pidgin_account_option_menu_new(account, FALSE,
1015 callback_func, filter_func, data);
1016 pidgin_add_widget_to_vbox(GTK_BOX(vbox), _("A_ccount"), data->sg, data->account_menu, TRUE, NULL);
1018 data->vbox = GTK_BOX(gtk_vbox_new(FALSE, 5));
1019 gtk_container_set_border_width(GTK_CONTAINER(data->vbox), 0);
1020 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(data->vbox), FALSE, FALSE, 0);
1022 g_signal_connect(G_OBJECT(data->window), "response", response_cb, data);
1024 g_object_unref(data->sg);
1026 return vbox;
1029 static void
1030 rebuild_chat_entries(PidginChatData *data, const char *default_chat_name)
1032 PurpleConnection *gc;
1033 GList *list = NULL, *tmp;
1034 GHashTable *defaults = NULL;
1035 struct proto_chat_entry *pce;
1036 gboolean focus = TRUE;
1038 g_return_if_fail(data->rq_data.account != NULL);
1040 gc = purple_account_get_connection(data->rq_data.account);
1042 gtk_container_foreach(GTK_CONTAINER(data->rq_data.vbox), (GtkCallback)gtk_widget_destroy, NULL);
1044 g_list_free(data->entries);
1045 data->entries = NULL;
1047 if (PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info != NULL)
1048 list = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info(gc);
1050 if (PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info_defaults != NULL)
1051 defaults = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info_defaults(gc, default_chat_name);
1053 for (tmp = list; tmp; tmp = tmp->next)
1055 GtkWidget *input;
1057 pce = tmp->data;
1059 if (pce->is_int)
1061 GtkObject *adjust;
1062 adjust = gtk_adjustment_new(pce->min, pce->min, pce->max,
1063 1, 10, 10);
1064 input = gtk_spin_button_new(GTK_ADJUSTMENT(adjust), 1, 0);
1065 gtk_widget_set_size_request(input, 50, -1);
1066 pidgin_add_widget_to_vbox(GTK_BOX(data->rq_data.vbox), pce->label, data->rq_data.sg, input, FALSE, NULL);
1068 else
1070 char *value;
1071 input = gtk_entry_new();
1072 gtk_entry_set_activates_default(GTK_ENTRY(input), TRUE);
1073 value = g_hash_table_lookup(defaults, pce->identifier);
1074 if (value != NULL)
1075 gtk_entry_set_text(GTK_ENTRY(input), value);
1076 if (pce->secret)
1078 gtk_entry_set_visibility(GTK_ENTRY(input), FALSE);
1079 #if !GTK_CHECK_VERSION(2,16,0)
1080 if (gtk_entry_get_invisible_char(GTK_ENTRY(input)) == '*')
1081 gtk_entry_set_invisible_char(GTK_ENTRY(input), PIDGIN_INVISIBLE_CHAR);
1082 #endif /* Less than GTK+ 2.16 */
1084 pidgin_add_widget_to_vbox(data->rq_data.vbox, pce->label, data->rq_data.sg, input, TRUE, NULL);
1085 g_signal_connect(G_OBJECT(input), "changed",
1086 G_CALLBACK(set_sensitive_if_input_cb), data);
1089 /* Do the following for any type of input widget */
1090 if (focus)
1092 gtk_widget_grab_focus(input);
1093 focus = FALSE;
1095 g_object_set_data(G_OBJECT(input), "identifier", (gpointer)pce->identifier);
1096 g_object_set_data(G_OBJECT(input), "is_spin", GINT_TO_POINTER(pce->is_int));
1097 g_object_set_data(G_OBJECT(input), "required", GINT_TO_POINTER(pce->required));
1098 data->entries = g_list_append(data->entries, input);
1100 g_free(pce);
1103 g_list_free(list);
1104 g_hash_table_destroy(defaults);
1106 /* Set whether the "OK" button should be clickable initially */
1107 set_sensitive_if_input_cb(NULL, data);
1109 gtk_widget_show_all(GTK_WIDGET(data->rq_data.vbox));
1112 static void
1113 chat_select_account_cb(GObject *w, PurpleAccount *account,
1114 PidginChatData *data)
1116 if (strcmp(purple_account_get_protocol_id(data->rq_data.account),
1117 purple_account_get_protocol_id(account)) == 0)
1119 data->rq_data.account = account;
1121 else
1123 data->rq_data.account = account;
1124 rebuild_chat_entries(data, data->default_chat_name);
1128 void
1129 pidgin_blist_joinchat_show(void)
1131 PidginChatData *data = NULL;
1133 data = g_new0(PidginChatData, 1);
1135 make_blist_request_dialog((PidginBlistRequestData *)data, NULL,
1136 _("Join a Chat"), "join_chat",
1137 _("Please enter the appropriate information about the chat "
1138 "you would like to join.\n"),
1139 G_CALLBACK(chat_select_account_cb),
1140 chat_account_filter_func, (GCallback)do_joinchat);
1141 gtk_dialog_add_buttons(GTK_DIALOG(data->rq_data.window),
1142 _("Room _List"), 1,
1143 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1144 PIDGIN_STOCK_CHAT, GTK_RESPONSE_OK, NULL);
1145 gtk_dialog_set_default_response(GTK_DIALOG(data->rq_data.window),
1146 GTK_RESPONSE_OK);
1147 data->default_chat_name = NULL;
1148 data->rq_data.account = pidgin_account_option_menu_get_selected(data->rq_data.account_menu);
1150 rebuild_chat_entries(data, NULL);
1152 gtk_widget_show_all(data->rq_data.window);
1155 static void gtk_blist_row_expanded_cb(GtkTreeView *tv, GtkTreeIter *iter, GtkTreePath *path, gpointer user_data)
1157 PurpleBlistNode *node;
1159 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), iter, NODE_COLUMN, &node, -1);
1161 if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
1162 char *title;
1164 title = pidgin_get_group_title(node, TRUE);
1166 gtk_tree_store_set(gtkblist->treemodel, iter,
1167 NAME_COLUMN, title,
1168 -1);
1170 g_free(title);
1172 purple_blist_node_set_bool(node, "collapsed", FALSE);
1173 pidgin_blist_tooltip_destroy();
1177 static void gtk_blist_row_collapsed_cb(GtkTreeView *tv, GtkTreeIter *iter, GtkTreePath *path, gpointer user_data)
1179 PurpleBlistNode *node;
1181 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), iter, NODE_COLUMN, &node, -1);
1183 if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
1184 char *title;
1185 struct _pidgin_blist_node *gtknode;
1186 PurpleBlistNode *cnode;
1188 title = pidgin_get_group_title(node, FALSE);
1190 gtk_tree_store_set(gtkblist->treemodel, iter,
1191 NAME_COLUMN, title,
1192 -1);
1194 g_free(title);
1196 purple_blist_node_set_bool(node, "collapsed", TRUE);
1198 for(cnode = purple_blist_node_get_first_child(node); cnode; cnode = purple_blist_node_get_sibling_next(cnode)) {
1199 if (PURPLE_BLIST_NODE_IS_CONTACT(cnode)) {
1200 gtknode = purple_blist_node_get_ui_data(cnode);
1201 if (!gtknode->contact_expanded)
1202 continue;
1203 gtknode->contact_expanded = FALSE;
1204 pidgin_blist_update_contact(NULL, cnode);
1207 pidgin_blist_tooltip_destroy();
1208 } else if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
1209 pidgin_blist_collapse_contact_cb(NULL, node);
1213 static void gtk_blist_row_activated_cb(GtkTreeView *tv, GtkTreePath *path, GtkTreeViewColumn *col, gpointer data) {
1214 PurpleBlistNode *node;
1215 GtkTreeIter iter;
1217 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
1218 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
1220 if(PURPLE_BLIST_NODE_IS_CONTACT(node) || PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1221 PurpleBuddy *buddy;
1223 if(PURPLE_BLIST_NODE_IS_CONTACT(node))
1224 buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
1225 else
1226 buddy = (PurpleBuddy*)node;
1228 pidgin_dialogs_im_with_user(purple_buddy_get_account(buddy), purple_buddy_get_name(buddy));
1229 } else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
1230 gtk_blist_join_chat((PurpleChat *)node);
1231 } else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
1232 /* if (gtk_tree_view_row_expanded(tv, path))
1233 gtk_tree_view_collapse_row(tv, path);
1234 else
1235 gtk_tree_view_expand_row(tv,path,FALSE);*/
1239 static void pidgin_blist_add_chat_cb(void)
1241 GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist->treeview));
1242 GtkTreeIter iter;
1243 PurpleBlistNode *node;
1245 if(gtk_tree_selection_get_selected(sel, NULL, &iter)){
1246 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
1247 if (PURPLE_BLIST_NODE_IS_BUDDY(node))
1248 purple_blist_request_add_chat(NULL, purple_buddy_get_group(PURPLE_BUDDY(node)), NULL, NULL);
1249 if (PURPLE_BLIST_NODE_IS_CONTACT(node) || PURPLE_BLIST_NODE_IS_CHAT(node))
1250 purple_blist_request_add_chat(NULL, purple_contact_get_group(PURPLE_CONTACT(node)), NULL, NULL);
1251 else if (PURPLE_BLIST_NODE_IS_GROUP(node))
1252 purple_blist_request_add_chat(NULL, (PurpleGroup*)node, NULL, NULL);
1254 else {
1255 purple_blist_request_add_chat(NULL, NULL, NULL, NULL);
1259 static void pidgin_blist_add_buddy_cb(void)
1261 GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist->treeview));
1262 GtkTreeIter iter;
1263 PurpleBlistNode *node;
1265 if(gtk_tree_selection_get_selected(sel, NULL, &iter)){
1266 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
1267 if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1268 PurpleGroup *group = purple_buddy_get_group(PURPLE_BUDDY(node));
1269 purple_blist_request_add_buddy(NULL, NULL, purple_group_get_name(group), NULL);
1270 } else if (PURPLE_BLIST_NODE_IS_CONTACT(node) || PURPLE_BLIST_NODE_IS_CHAT(node)) {
1271 PurpleGroup *group = purple_contact_get_group(PURPLE_CONTACT(node));
1272 purple_blist_request_add_buddy(NULL, NULL, purple_group_get_name(group), NULL);
1273 } else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
1274 purple_blist_request_add_buddy(NULL, NULL, purple_group_get_name(PURPLE_GROUP(node)), NULL);
1277 else {
1278 purple_blist_request_add_buddy(NULL, NULL, NULL, NULL);
1282 static void
1283 pidgin_blist_remove_cb (GtkWidget *w, PurpleBlistNode *node)
1285 if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1286 pidgin_dialogs_remove_buddy((PurpleBuddy*)node);
1287 } else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
1288 pidgin_dialogs_remove_chat((PurpleChat*)node);
1289 } else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
1290 pidgin_dialogs_remove_group((PurpleGroup*)node);
1291 } else if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
1292 pidgin_dialogs_remove_contact((PurpleContact*)node);
1296 struct _expand {
1297 GtkTreeView *treeview;
1298 GtkTreePath *path;
1299 PurpleBlistNode *node;
1302 static gboolean
1303 scroll_to_expanded_cell(gpointer data)
1305 struct _expand *ex = data;
1306 gtk_tree_view_scroll_to_cell(ex->treeview, ex->path, NULL, FALSE, 0, 0);
1307 pidgin_blist_update_contact(NULL, ex->node);
1309 gtk_tree_path_free(ex->path);
1310 g_free(ex);
1312 return FALSE;
1315 static void
1316 pidgin_blist_expand_contact_cb(GtkWidget *w, PurpleBlistNode *node)
1318 struct _pidgin_blist_node *gtknode;
1319 GtkTreeIter iter, parent;
1320 PurpleBlistNode *bnode;
1321 GtkTreePath *path;
1323 if(!PURPLE_BLIST_NODE_IS_CONTACT(node))
1324 return;
1326 gtknode = purple_blist_node_get_ui_data(node);
1328 gtknode->contact_expanded = TRUE;
1330 for(bnode = purple_blist_node_get_first_child(node); bnode; bnode = purple_blist_node_get_sibling_next(bnode)) {
1331 pidgin_blist_update(NULL, bnode);
1334 /* This ensures that the bottom buddy is visible, i.e. not scrolled off the alignment */
1335 if (get_iter_from_node(node, &parent)) {
1336 struct _expand *ex = g_new0(struct _expand, 1);
1338 gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(gtkblist->treemodel), &iter, &parent,
1339 gtk_tree_model_iter_n_children(GTK_TREE_MODEL(gtkblist->treemodel), &parent) -1);
1340 path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &iter);
1342 /* Let the treeview draw so it knows where to scroll */
1343 ex->treeview = GTK_TREE_VIEW(gtkblist->treeview);
1344 ex->path = path;
1345 ex->node = purple_blist_node_get_first_child(node);
1346 g_idle_add(scroll_to_expanded_cell, ex);
1350 static void
1351 pidgin_blist_collapse_contact_cb(GtkWidget *w, PurpleBlistNode *node)
1353 PurpleBlistNode *bnode;
1354 struct _pidgin_blist_node *gtknode;
1356 if(!PURPLE_BLIST_NODE_IS_CONTACT(node))
1357 return;
1359 gtknode = purple_blist_node_get_ui_data(node);
1361 gtknode->contact_expanded = FALSE;
1363 for(bnode = purple_blist_node_get_first_child(node); bnode; bnode = purple_blist_node_get_sibling_next(bnode)) {
1364 pidgin_blist_update(NULL, bnode);
1368 static void
1369 toggle_privacy(GtkWidget *widget, PurpleBlistNode *node)
1371 PurpleBuddy *buddy;
1372 PurpleAccount *account;
1373 gboolean permitted;
1374 const char *name;
1376 if (!PURPLE_BLIST_NODE_IS_BUDDY(node))
1377 return;
1379 buddy = (PurpleBuddy *)node;
1380 account = purple_buddy_get_account(buddy);
1381 name = purple_buddy_get_name(buddy);
1383 permitted = purple_privacy_check(account, name);
1385 /* XXX: Perhaps ask whether to restore the previous lists where appropirate? */
1387 if (permitted)
1388 purple_privacy_deny(account, name, FALSE, FALSE);
1389 else
1390 purple_privacy_allow(account, name, FALSE, FALSE);
1392 pidgin_blist_update(purple_get_blist(), node);
1395 void pidgin_append_blist_node_privacy_menu(GtkWidget *menu, PurpleBlistNode *node)
1397 PurpleBuddy *buddy = (PurpleBuddy *)node;
1398 PurpleAccount *account;
1399 gboolean permitted;
1401 account = purple_buddy_get_account(buddy);
1402 permitted = purple_privacy_check(account, purple_buddy_get_name(buddy));
1404 pidgin_new_item_from_stock(menu, permitted ? _("_Block") : _("Un_block"),
1405 permitted ? PIDGIN_STOCK_TOOLBAR_BLOCK : PIDGIN_STOCK_TOOLBAR_UNBLOCK, G_CALLBACK(toggle_privacy),
1406 node, 0 ,0, NULL);
1409 void
1410 pidgin_append_blist_node_proto_menu(GtkWidget *menu, PurpleConnection *gc,
1411 PurpleBlistNode *node)
1413 GList *l, *ll;
1414 PurplePluginProtocolInfo *prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
1416 if(!prpl_info || !prpl_info->blist_node_menu)
1417 return;
1419 for(l = ll = prpl_info->blist_node_menu(node); l; l = l->next) {
1420 PurpleMenuAction *act = (PurpleMenuAction *) l->data;
1421 pidgin_append_menu_action(menu, act, node);
1423 g_list_free(ll);
1426 void
1427 pidgin_append_blist_node_extended_menu(GtkWidget *menu, PurpleBlistNode *node)
1429 GList *l, *ll;
1431 for(l = ll = purple_blist_node_get_extended_menu(node); l; l = l->next) {
1432 PurpleMenuAction *act = (PurpleMenuAction *) l->data;
1433 pidgin_append_menu_action(menu, act, node);
1435 g_list_free(ll);
1440 static void
1441 pidgin_append_blist_node_move_to_menu(GtkWidget *menu, PurpleBlistNode *node)
1443 GtkWidget *submenu;
1444 GtkWidget *menuitem;
1445 PurpleBlistNode *group;
1447 menuitem = gtk_menu_item_new_with_label(_("Move to"));
1448 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1449 gtk_widget_show(menuitem);
1451 submenu = gtk_menu_new();
1452 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
1454 for (group = purple_blist_get_root(); group; group = purple_blist_node_get_sibling_next(group)) {
1455 if (!PURPLE_BLIST_NODE_IS_GROUP(group))
1456 continue;
1457 if (group == purple_blist_node_get_parent(node))
1458 continue;
1459 menuitem = pidgin_new_item_from_stock(submenu, purple_group_get_name((PurpleGroup *)group), NULL,
1460 G_CALLBACK(gtk_blist_menu_move_to_cb), node, 0, 0, NULL);
1461 g_object_set_data(G_OBJECT(menuitem), "groupnode", group);
1463 gtk_widget_show_all(submenu);
1466 void
1467 pidgin_blist_make_buddy_menu(GtkWidget *menu, PurpleBuddy *buddy, gboolean sub) {
1468 PurpleAccount *account = NULL;
1469 PurpleConnection *pc = NULL;
1470 PurplePluginProtocolInfo *prpl_info;
1471 PurpleContact *contact;
1472 PurpleBlistNode *node;
1473 gboolean contact_expanded = FALSE;
1475 g_return_if_fail(menu);
1476 g_return_if_fail(buddy);
1478 account = purple_buddy_get_account(buddy);
1479 pc = purple_account_get_connection(account);
1480 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(pc));
1482 node = PURPLE_BLIST_NODE(buddy);
1484 contact = purple_buddy_get_contact(buddy);
1485 if (contact) {
1486 PidginBlistNode *node = purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(contact));
1487 contact_expanded = node->contact_expanded;
1490 if (prpl_info && prpl_info->get_info) {
1491 pidgin_new_item_from_stock(menu, _("Get _Info"), PIDGIN_STOCK_TOOLBAR_USER_INFO,
1492 G_CALLBACK(gtk_blist_menu_info_cb), buddy, 0, 0, NULL);
1494 pidgin_new_item_from_stock(menu, _("I_M"), PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW,
1495 G_CALLBACK(gtk_blist_menu_im_cb), buddy, 0, 0, NULL);
1497 #ifdef USE_VV
1498 if (prpl_info && prpl_info->get_media_caps) {
1499 PurpleAccount *account = purple_buddy_get_account(buddy);
1500 const gchar *who = purple_buddy_get_name(buddy);
1501 PurpleMediaCaps caps = purple_prpl_get_media_caps(account, who);
1502 if (caps & PURPLE_MEDIA_CAPS_AUDIO) {
1503 pidgin_new_item_from_stock(menu, _("_Audio Call"),
1504 PIDGIN_STOCK_TOOLBAR_AUDIO_CALL,
1505 G_CALLBACK(gtk_blist_menu_audio_call_cb), buddy, 0, 0, NULL);
1507 if (caps & PURPLE_MEDIA_CAPS_AUDIO_VIDEO) {
1508 pidgin_new_item_from_stock(menu, _("Audio/_Video Call"),
1509 PIDGIN_STOCK_TOOLBAR_VIDEO_CALL,
1510 G_CALLBACK(gtk_blist_menu_video_call_cb), buddy, 0, 0, NULL);
1511 } else if (caps & PURPLE_MEDIA_CAPS_VIDEO) {
1512 pidgin_new_item_from_stock(menu, _("_Video Call"),
1513 PIDGIN_STOCK_TOOLBAR_VIDEO_CALL,
1514 G_CALLBACK(gtk_blist_menu_video_call_cb), buddy, 0, 0, NULL);
1518 #endif
1520 if (prpl_info && prpl_info->send_file) {
1521 if (!prpl_info->can_receive_file ||
1522 prpl_info->can_receive_file(buddy->account->gc, buddy->name))
1524 pidgin_new_item_from_stock(menu, _("_Send File..."),
1525 PIDGIN_STOCK_TOOLBAR_SEND_FILE,
1526 G_CALLBACK(gtk_blist_menu_send_file_cb),
1527 buddy, 0, 0, NULL);
1531 pidgin_new_item_from_stock(menu, _("Add Buddy _Pounce..."), NULL,
1532 G_CALLBACK(gtk_blist_menu_bp_cb), buddy, 0, 0, NULL);
1534 if (node->parent && node->parent->child->next &&
1535 !sub && !contact_expanded) {
1536 pidgin_new_item_from_stock(menu, _("View _Log"), NULL,
1537 G_CALLBACK(gtk_blist_menu_showlog_cb),
1538 contact, 0, 0, NULL);
1539 } else if (!sub) {
1540 pidgin_new_item_from_stock(menu, _("View _Log"), NULL,
1541 G_CALLBACK(gtk_blist_menu_showlog_cb), buddy, 0, 0, NULL);
1544 if (!PURPLE_BLIST_NODE_HAS_FLAG(node, PURPLE_BLIST_NODE_FLAG_NO_SAVE)) {
1545 gboolean show_offline = purple_blist_node_get_bool(node, "show_offline");
1546 pidgin_new_item_from_stock(menu, show_offline ? _("Hide When Offline") : _("Show When Offline"),
1547 NULL, G_CALLBACK(gtk_blist_menu_showoffline_cb), node, 0, 0, NULL);
1550 pidgin_append_blist_node_proto_menu(menu, buddy->account->gc, node);
1551 pidgin_append_blist_node_extended_menu(menu, node);
1553 if (!contact_expanded && contact != NULL)
1554 pidgin_append_blist_node_move_to_menu(menu, (PurpleBlistNode *)contact);
1556 if (node->parent && node->parent->child->next &&
1557 !sub && !contact_expanded) {
1558 pidgin_separator(menu);
1559 pidgin_append_blist_node_privacy_menu(menu, node);
1560 pidgin_new_item_from_stock(menu, _("_Alias..."), PIDGIN_STOCK_ALIAS,
1561 G_CALLBACK(gtk_blist_menu_alias_cb),
1562 contact, 0, 0, NULL);
1563 pidgin_new_item_from_stock(menu, _("_Remove"), GTK_STOCK_REMOVE,
1564 G_CALLBACK(pidgin_blist_remove_cb),
1565 contact, 0, 0, NULL);
1566 } else if (!sub || contact_expanded) {
1567 pidgin_separator(menu);
1568 pidgin_append_blist_node_privacy_menu(menu, node);
1569 pidgin_new_item_from_stock(menu, _("_Alias..."), PIDGIN_STOCK_ALIAS,
1570 G_CALLBACK(gtk_blist_menu_alias_cb), buddy, 0, 0, NULL);
1571 pidgin_new_item_from_stock(menu, _("_Remove"), GTK_STOCK_REMOVE,
1572 G_CALLBACK(pidgin_blist_remove_cb), buddy,
1573 0, 0, NULL);
1577 static gboolean
1578 gtk_blist_key_press_cb(GtkWidget *tv, GdkEventKey *event, gpointer data)
1580 PurpleBlistNode *node;
1581 GtkTreeIter iter, parent;
1582 GtkTreeSelection *sel;
1583 GtkTreePath *path;
1585 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv));
1586 if(!gtk_tree_selection_get_selected(sel, NULL, &iter))
1587 return FALSE;
1589 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
1591 if(event->state & GDK_CONTROL_MASK &&
1592 (event->keyval == 'o' || event->keyval == 'O')) {
1593 PurpleBuddy *buddy;
1595 if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
1596 buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
1597 } else if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1598 buddy = (PurpleBuddy*)node;
1599 } else {
1600 return FALSE;
1602 if(buddy)
1603 pidgin_retrieve_user_info(buddy->account->gc, buddy->name);
1604 } else {
1605 switch (event->keyval) {
1606 case GDK_F2:
1607 gtk_blist_menu_alias_cb(tv, node);
1608 break;
1610 case GDK_Left:
1611 path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &iter);
1612 if (gtk_tree_view_row_expanded(GTK_TREE_VIEW(tv), path)) {
1613 /* Collapse the Group */
1614 gtk_tree_view_collapse_row(GTK_TREE_VIEW(tv), path);
1615 gtk_tree_path_free(path);
1616 return TRUE;
1617 } else {
1618 /* Select the Parent */
1619 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path)) {
1620 if (gtk_tree_model_iter_parent(GTK_TREE_MODEL(gtkblist->treemodel), &parent, &iter)) {
1621 gtk_tree_path_free(path);
1622 path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &parent);
1623 gtk_tree_view_set_cursor(GTK_TREE_VIEW(tv), path, NULL, FALSE);
1624 gtk_tree_path_free(path);
1625 return TRUE;
1629 gtk_tree_path_free(path);
1630 break;
1632 case GDK_Right:
1633 path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &iter);
1634 if (!gtk_tree_view_row_expanded(GTK_TREE_VIEW(tv), path)) {
1635 /* Expand the Group */
1636 if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
1637 pidgin_blist_expand_contact_cb(NULL, node);
1638 gtk_tree_path_free(path);
1639 return TRUE;
1640 } else if (!PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1641 gtk_tree_view_expand_row(GTK_TREE_VIEW(tv), path, FALSE);
1642 gtk_tree_path_free(path);
1643 return TRUE;
1645 } else {
1646 /* Select the First Child */
1647 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &parent, path)) {
1648 if (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(gtkblist->treemodel), &iter, &parent, 0)) {
1649 gtk_tree_path_free(path);
1650 path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &iter);
1651 gtk_tree_view_set_cursor(GTK_TREE_VIEW(tv), path, NULL, FALSE);
1652 gtk_tree_path_free(path);
1653 return TRUE;
1657 gtk_tree_path_free(path);
1658 break;
1662 return FALSE;
1665 static void
1666 set_node_custom_icon_cb(const gchar *filename, gpointer data)
1668 if (filename) {
1669 PurpleBlistNode *node = (PurpleBlistNode*)data;
1671 purple_buddy_icons_node_set_custom_icon_from_file(node,
1672 filename);
1676 static void
1677 set_node_custom_icon(GtkWidget *w, PurpleBlistNode *node)
1679 /* This doesn't keep track of the returned dialog (so that successive
1680 * calls could be made to re-display that dialog). Do we want that? */
1681 GtkWidget *win = pidgin_buddy_icon_chooser_new(NULL, set_node_custom_icon_cb, node);
1682 gtk_widget_show_all(win);
1685 static void
1686 remove_node_custom_icon(GtkWidget *w, PurpleBlistNode *node)
1688 purple_buddy_icons_node_set_custom_icon(node, NULL, 0);
1691 static void
1692 add_buddy_icon_menu_items(GtkWidget *menu, PurpleBlistNode *node)
1694 GtkWidget *item;
1696 pidgin_new_item_from_stock(menu, _("Set Custom Icon"), NULL,
1697 G_CALLBACK(set_node_custom_icon), node, 0,
1698 0, NULL);
1700 item = pidgin_new_item_from_stock(menu, _("Remove Custom Icon"), NULL,
1701 G_CALLBACK(remove_node_custom_icon), node,
1702 0, 0, NULL);
1703 if (!purple_buddy_icons_node_has_custom_icon(node))
1704 gtk_widget_set_sensitive(item, FALSE);
1707 static GtkWidget *
1708 create_group_menu (PurpleBlistNode *node, PurpleGroup *g)
1710 GtkWidget *menu;
1711 GtkWidget *item;
1713 menu = gtk_menu_new();
1714 item = pidgin_new_item_from_stock(menu, _("Add _Buddy..."), GTK_STOCK_ADD,
1715 G_CALLBACK(pidgin_blist_add_buddy_cb), node, 0, 0, NULL);
1716 gtk_widget_set_sensitive(item, purple_connections_get_all() != NULL);
1717 item = pidgin_new_item_from_stock(menu, _("Add C_hat..."), GTK_STOCK_ADD,
1718 G_CALLBACK(pidgin_blist_add_chat_cb), node, 0, 0, NULL);
1719 gtk_widget_set_sensitive(item, pidgin_blist_joinchat_is_showable());
1720 pidgin_new_item_from_stock(menu, _("_Delete Group"), GTK_STOCK_REMOVE,
1721 G_CALLBACK(pidgin_blist_remove_cb), node, 0, 0, NULL);
1722 pidgin_new_item_from_stock(menu, _("_Rename"), NULL,
1723 G_CALLBACK(gtk_blist_menu_alias_cb), node, 0, 0, NULL);
1724 if (!(purple_blist_node_get_flags(node) & PURPLE_BLIST_NODE_FLAG_NO_SAVE)) {
1725 gboolean show_offline = purple_blist_node_get_bool(node, "show_offline");
1726 pidgin_new_item_from_stock(menu, show_offline ? _("Hide When Offline") : _("Show When Offline"),
1727 NULL, G_CALLBACK(gtk_blist_menu_showoffline_cb), node, 0, 0, NULL);
1730 add_buddy_icon_menu_items(menu, node);
1732 pidgin_append_blist_node_extended_menu(menu, node);
1734 return menu;
1737 static GtkWidget *
1738 create_chat_menu(PurpleBlistNode *node, PurpleChat *c)
1740 GtkWidget *menu;
1741 gboolean autojoin, persistent;
1743 menu = gtk_menu_new();
1744 autojoin = purple_blist_node_get_bool(node, "gtk-autojoin");
1745 persistent = purple_blist_node_get_bool(node, "gtk-persistent");
1747 pidgin_new_item_from_stock(menu, _("_Join"), PIDGIN_STOCK_CHAT,
1748 G_CALLBACK(gtk_blist_menu_join_cb), node, 0, 0, NULL);
1749 pidgin_new_check_item(menu, _("Auto-Join"),
1750 G_CALLBACK(gtk_blist_menu_autojoin_cb), node, autojoin);
1751 pidgin_new_check_item(menu, _("Persistent"),
1752 G_CALLBACK(gtk_blist_menu_persistent_cb), node, persistent);
1753 pidgin_new_item_from_stock(menu, _("View _Log"), NULL,
1754 G_CALLBACK(gtk_blist_menu_showlog_cb), node, 0, 0, NULL);
1756 pidgin_append_blist_node_proto_menu(menu, c->account->gc, node);
1757 pidgin_append_blist_node_extended_menu(menu, node);
1759 pidgin_separator(menu);
1761 pidgin_new_item_from_stock(menu, _("_Edit Settings..."), NULL,
1762 G_CALLBACK(chat_components_edit), node, 0, 0, NULL);
1763 pidgin_new_item_from_stock(menu, _("_Alias..."), PIDGIN_STOCK_ALIAS,
1764 G_CALLBACK(gtk_blist_menu_alias_cb), node, 0, 0, NULL);
1765 pidgin_new_item_from_stock(menu, _("_Remove"), GTK_STOCK_REMOVE,
1766 G_CALLBACK(pidgin_blist_remove_cb), node, 0, 0, NULL);
1768 add_buddy_icon_menu_items(menu, node);
1770 return menu;
1773 static GtkWidget *
1774 create_contact_menu (PurpleBlistNode *node)
1776 GtkWidget *menu;
1778 menu = gtk_menu_new();
1780 pidgin_new_item_from_stock(menu, _("View _Log"), NULL,
1781 G_CALLBACK(gtk_blist_menu_showlog_cb),
1782 node, 0, 0, NULL);
1784 pidgin_separator(menu);
1786 pidgin_new_item_from_stock(menu, _("_Alias..."), PIDGIN_STOCK_ALIAS,
1787 G_CALLBACK(gtk_blist_menu_alias_cb), node, 0, 0, NULL);
1788 pidgin_new_item_from_stock(menu, _("_Remove"), GTK_STOCK_REMOVE,
1789 G_CALLBACK(pidgin_blist_remove_cb), node, 0, 0, NULL);
1791 add_buddy_icon_menu_items(menu, node);
1793 pidgin_separator(menu);
1795 pidgin_new_item_from_stock(menu, _("_Collapse"), GTK_STOCK_ZOOM_OUT,
1796 G_CALLBACK(pidgin_blist_collapse_contact_cb),
1797 node, 0, 0, NULL);
1799 pidgin_append_blist_node_extended_menu(menu, node);
1800 return menu;
1803 static GtkWidget *
1804 create_buddy_menu(PurpleBlistNode *node, PurpleBuddy *b)
1806 struct _pidgin_blist_node *gtknode = (struct _pidgin_blist_node *)node->ui_data;
1807 GtkWidget *menu;
1808 GtkWidget *menuitem;
1809 gboolean show_offline = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_offline_buddies");
1811 menu = gtk_menu_new();
1812 pidgin_blist_make_buddy_menu(menu, b, FALSE);
1814 if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
1815 pidgin_separator(menu);
1817 add_buddy_icon_menu_items(menu, node);
1819 if(gtknode->contact_expanded) {
1820 pidgin_new_item_from_stock(menu, _("_Collapse"),
1821 GTK_STOCK_ZOOM_OUT,
1822 G_CALLBACK(pidgin_blist_collapse_contact_cb),
1823 node, 0, 0, NULL);
1824 } else {
1825 pidgin_new_item_from_stock(menu, _("_Expand"),
1826 GTK_STOCK_ZOOM_IN,
1827 G_CALLBACK(pidgin_blist_expand_contact_cb), node,
1828 0, 0, NULL);
1830 if(node->child->next) {
1831 PurpleBlistNode *bnode;
1833 for(bnode = node->child; bnode; bnode = bnode->next) {
1834 PurpleBuddy *buddy = (PurpleBuddy*)bnode;
1835 GdkPixbuf *buf;
1836 GtkWidget *submenu;
1837 GtkWidget *image;
1839 if(buddy == b)
1840 continue;
1841 if(!buddy->account->gc)
1842 continue;
1843 if(!show_offline && !PURPLE_BUDDY_IS_ONLINE(buddy))
1844 continue;
1846 menuitem = gtk_image_menu_item_new_with_label(buddy->name);
1847 buf = pidgin_create_prpl_icon(buddy->account,PIDGIN_PRPL_ICON_SMALL);
1848 image = gtk_image_new_from_pixbuf(buf);
1849 g_object_unref(G_OBJECT(buf));
1850 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem),
1851 image);
1852 gtk_widget_show(image);
1853 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1854 gtk_widget_show(menuitem);
1856 submenu = gtk_menu_new();
1857 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
1858 gtk_widget_show(submenu);
1860 pidgin_blist_make_buddy_menu(submenu, buddy, TRUE);
1864 return menu;
1867 static gboolean
1868 pidgin_blist_show_context_menu(PurpleBlistNode *node,
1869 GtkMenuPositionFunc func,
1870 GtkWidget *tv,
1871 guint button,
1872 guint32 time)
1874 struct _pidgin_blist_node *gtknode;
1875 GtkWidget *menu = NULL;
1876 gboolean handled = FALSE;
1878 gtknode = (struct _pidgin_blist_node *)node->ui_data;
1880 /* Create a menu based on the thing we right-clicked on */
1881 if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
1882 PurpleGroup *g = (PurpleGroup *)node;
1884 menu = create_group_menu(node, g);
1885 } else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
1886 PurpleChat *c = (PurpleChat *)node;
1888 menu = create_chat_menu(node, c);
1889 } else if ((PURPLE_BLIST_NODE_IS_CONTACT(node)) && (gtknode->contact_expanded)) {
1890 menu = create_contact_menu(node);
1891 } else if (PURPLE_BLIST_NODE_IS_CONTACT(node) || PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1892 PurpleBuddy *b;
1894 if (PURPLE_BLIST_NODE_IS_CONTACT(node))
1895 b = purple_contact_get_priority_buddy((PurpleContact*)node);
1896 else
1897 b = (PurpleBuddy *)node;
1899 menu = create_buddy_menu(node, b);
1902 #ifdef _WIN32
1903 pidgin_blist_tooltip_destroy();
1905 /* Unhook the tooltip-timeout since we don't want a tooltip
1906 * to appear and obscure the context menu we are about to show
1907 This is a workaround for GTK+ bug 107320. */
1908 if (gtkblist->timeout) {
1909 g_source_remove(gtkblist->timeout);
1910 gtkblist->timeout = 0;
1912 #endif
1914 /* Now display the menu */
1915 if (menu != NULL) {
1916 gtk_widget_show_all(menu);
1917 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, func, tv, button, time);
1918 handled = TRUE;
1921 return handled;
1924 static gboolean
1925 gtk_blist_button_press_cb(GtkWidget *tv, GdkEventButton *event, gpointer user_data)
1927 GtkTreePath *path;
1928 PurpleBlistNode *node;
1929 GtkTreeIter iter;
1930 GtkTreeSelection *sel;
1931 PurplePlugin *prpl = NULL;
1932 PurplePluginProtocolInfo *prpl_info = NULL;
1933 struct _pidgin_blist_node *gtknode;
1934 gboolean handled = FALSE;
1936 /* Here we figure out which node was clicked */
1937 if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), event->x, event->y, &path, NULL, NULL, NULL))
1938 return FALSE;
1939 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
1940 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
1941 gtknode = (struct _pidgin_blist_node *)node->ui_data;
1943 /* Right click draws a context menu */
1944 if ((event->button == 3) && (event->type == GDK_BUTTON_PRESS)) {
1945 handled = pidgin_blist_show_context_menu(node, NULL, tv, 3, event->time);
1947 /* CTRL+middle click expands or collapse a contact */
1948 } else if ((event->button == 2) && (event->type == GDK_BUTTON_PRESS) &&
1949 (event->state & GDK_CONTROL_MASK) && (PURPLE_BLIST_NODE_IS_CONTACT(node))) {
1950 if (gtknode->contact_expanded)
1951 pidgin_blist_collapse_contact_cb(NULL, node);
1952 else
1953 pidgin_blist_expand_contact_cb(NULL, node);
1954 handled = TRUE;
1956 /* Double middle click gets info */
1957 } else if ((event->button == 2) && (event->type == GDK_2BUTTON_PRESS) &&
1958 ((PURPLE_BLIST_NODE_IS_CONTACT(node)) || (PURPLE_BLIST_NODE_IS_BUDDY(node)))) {
1959 PurpleBuddy *b;
1960 if(PURPLE_BLIST_NODE_IS_CONTACT(node))
1961 b = purple_contact_get_priority_buddy((PurpleContact*)node);
1962 else
1963 b = (PurpleBuddy *)node;
1965 prpl = purple_find_prpl(purple_account_get_protocol_id(b->account));
1966 if (prpl != NULL)
1967 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
1969 if (prpl && prpl_info->get_info)
1970 pidgin_retrieve_user_info(b->account->gc, b->name);
1971 handled = TRUE;
1974 #if (1)
1976 * This code only exists because GTK+ doesn't work. If we return
1977 * FALSE here, as would be normal the event propoagates down and
1978 * somehow gets interpreted as the start of a drag event.
1980 * Um, isn't it _normal_ to return TRUE here? Since the event
1981 * was handled? --Mark
1983 if(handled) {
1984 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv));
1985 gtk_tree_selection_select_path(sel, path);
1986 gtk_tree_path_free(path);
1987 return TRUE;
1989 #endif
1990 gtk_tree_path_free(path);
1992 return FALSE;
1995 static gboolean
1996 pidgin_blist_popup_menu_cb(GtkWidget *tv, void *user_data)
1998 PurpleBlistNode *node;
1999 GtkTreeIter iter;
2000 GtkTreeSelection *sel;
2001 gboolean handled = FALSE;
2003 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv));
2004 if (!gtk_tree_selection_get_selected(sel, NULL, &iter))
2005 return FALSE;
2007 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
2009 /* Shift+F10 draws a context menu */
2010 handled = pidgin_blist_show_context_menu(node, pidgin_treeview_popup_menu_position_func, tv, 0, GDK_CURRENT_TIME);
2012 return handled;
2015 static void pidgin_blist_buddy_details_cb(gpointer data, guint action, GtkWidget *item)
2017 pidgin_set_cursor(gtkblist->window, GDK_WATCH);
2019 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons",
2020 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item)));
2022 pidgin_clear_cursor(gtkblist->window);
2025 static void pidgin_blist_show_idle_time_cb(gpointer data, guint action, GtkWidget *item)
2027 pidgin_set_cursor(gtkblist->window, GDK_WATCH);
2029 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/show_idle_time",
2030 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item)));
2032 pidgin_clear_cursor(gtkblist->window);
2035 static void pidgin_blist_show_protocol_icons_cb(gpointer data, guint action, GtkWidget *item)
2037 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons",
2038 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item)));
2041 static void pidgin_blist_show_empty_groups_cb(gpointer data, guint action, GtkWidget *item)
2043 pidgin_set_cursor(gtkblist->window, GDK_WATCH);
2045 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/show_empty_groups",
2046 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item)));
2048 pidgin_clear_cursor(gtkblist->window);
2051 static void pidgin_blist_edit_mode_cb(gpointer callback_data, guint callback_action,
2052 GtkWidget *checkitem)
2054 pidgin_set_cursor(gtkblist->window, GDK_WATCH);
2056 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/show_offline_buddies",
2057 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(checkitem)));
2059 pidgin_clear_cursor(gtkblist->window);
2062 static void pidgin_blist_mute_sounds_cb(gpointer data, guint action, GtkWidget *item)
2064 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/sound/mute", GTK_CHECK_MENU_ITEM(item)->active);
2067 static void
2068 pidgin_blist_mute_pref_cb(const char *name, PurplePrefType type,
2069 gconstpointer value, gpointer data)
2071 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item(gtkblist->ift,
2072 N_("/Tools/Mute Sounds"))), (gboolean)GPOINTER_TO_INT(value));
2075 static void
2076 pidgin_blist_sound_method_pref_cb(const char *name, PurplePrefType type,
2077 gconstpointer value, gpointer data)
2079 gboolean sensitive = TRUE;
2081 if(!strcmp(value, "none"))
2082 sensitive = FALSE;
2084 gtk_widget_set_sensitive(gtk_item_factory_get_widget(gtkblist->ift, N_("/Tools/Mute Sounds")), sensitive);
2087 static void
2088 add_buddies_from_vcard(const char *prpl_id, PurpleGroup *group, GList *list,
2089 const char *alias)
2091 GList *l;
2092 PurpleAccount *account = NULL;
2093 PurpleConnection *gc;
2095 if (list == NULL)
2096 return;
2098 for (l = purple_connections_get_all(); l != NULL; l = l->next)
2100 gc = (PurpleConnection *)l->data;
2101 account = purple_connection_get_account(gc);
2103 if (!strcmp(purple_account_get_protocol_id(account), prpl_id))
2104 break;
2106 account = NULL;
2109 if (account != NULL)
2111 for (l = list; l != NULL; l = l->next)
2113 purple_blist_request_add_buddy(account, l->data,
2114 (group ? group->name : NULL),
2115 alias);
2119 g_list_foreach(list, (GFunc)g_free, NULL);
2120 g_list_free(list);
2123 static gboolean
2124 parse_vcard(const char *vcard, PurpleGroup *group)
2126 char *temp_vcard;
2127 char *s, *c;
2128 char *alias = NULL;
2129 GList *aims = NULL;
2130 GList *icqs = NULL;
2131 GList *yahoos = NULL;
2132 GList *msns = NULL;
2133 GList *jabbers = NULL;
2135 s = temp_vcard = g_strdup(vcard);
2137 while (*s != '\0' && strncmp(s, "END:vCard", strlen("END:vCard")))
2139 char *field, *value;
2141 field = s;
2143 /* Grab the field */
2144 while (*s != '\r' && *s != '\n' && *s != '\0' && *s != ':')
2145 s++;
2147 if (*s == '\r') s++;
2148 if (*s == '\n')
2150 s++;
2151 continue;
2154 if (*s != '\0') *s++ = '\0';
2156 if ((c = strchr(field, ';')) != NULL)
2157 *c = '\0';
2159 /* Proceed to the end of the line */
2160 value = s;
2162 while (*s != '\r' && *s != '\n' && *s != '\0')
2163 s++;
2165 if (*s == '\r') *s++ = '\0';
2166 if (*s == '\n') *s++ = '\0';
2168 /* We only want to worry about a few fields here. */
2169 if (!strcmp(field, "FN"))
2170 alias = g_strdup(value);
2171 else if (!strcmp(field, "X-AIM") || !strcmp(field, "X-ICQ") ||
2172 !strcmp(field, "X-YAHOO") || !strcmp(field, "X-MSN") ||
2173 !strcmp(field, "X-JABBER"))
2175 char **values = g_strsplit(value, ":", 0);
2176 char **im;
2178 for (im = values; *im != NULL; im++)
2180 if (!strcmp(field, "X-AIM"))
2181 aims = g_list_append(aims, g_strdup(*im));
2182 else if (!strcmp(field, "X-ICQ"))
2183 icqs = g_list_append(icqs, g_strdup(*im));
2184 else if (!strcmp(field, "X-YAHOO"))
2185 yahoos = g_list_append(yahoos, g_strdup(*im));
2186 else if (!strcmp(field, "X-MSN"))
2187 msns = g_list_append(msns, g_strdup(*im));
2188 else if (!strcmp(field, "X-JABBER"))
2189 jabbers = g_list_append(jabbers, g_strdup(*im));
2192 g_strfreev(values);
2196 g_free(temp_vcard);
2198 if (aims == NULL && icqs == NULL && yahoos == NULL &&
2199 msns == NULL && jabbers == NULL)
2201 g_free(alias);
2203 return FALSE;
2206 add_buddies_from_vcard("prpl-aim", group, aims, alias);
2207 add_buddies_from_vcard("prpl-icq", group, icqs, alias);
2208 add_buddies_from_vcard("prpl-yahoo", group, yahoos, alias);
2209 add_buddies_from_vcard("prpl-msn", group, msns, alias);
2210 add_buddies_from_vcard("prpl-jabber", group, jabbers, alias);
2212 g_free(alias);
2214 return TRUE;
2217 #ifdef _WIN32
2218 static void pidgin_blist_drag_begin(GtkWidget *widget,
2219 GdkDragContext *drag_context, gpointer user_data)
2221 pidgin_blist_tooltip_destroy();
2224 /* Unhook the tooltip-timeout since we don't want a tooltip
2225 * to appear and obscure the dragging operation.
2226 * This is a workaround for GTK+ bug 107320. */
2227 if (gtkblist->timeout) {
2228 g_source_remove(gtkblist->timeout);
2229 gtkblist->timeout = 0;
2232 #endif
2234 static void pidgin_blist_drag_data_get_cb(GtkWidget *widget,
2235 GdkDragContext *dc,
2236 GtkSelectionData *data,
2237 guint info,
2238 guint time,
2239 gpointer null)
2242 if (data->target == gdk_atom_intern("PURPLE_BLIST_NODE", FALSE))
2244 GtkTreeRowReference *ref = g_object_get_data(G_OBJECT(dc), "gtk-tree-view-source-row");
2245 GtkTreePath *sourcerow = gtk_tree_row_reference_get_path(ref);
2246 GtkTreeIter iter;
2247 PurpleBlistNode *node = NULL;
2248 if(!sourcerow)
2249 return;
2250 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, sourcerow);
2251 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
2252 gtk_selection_data_set (data,
2253 gdk_atom_intern ("PURPLE_BLIST_NODE", FALSE),
2254 8, /* bits */
2255 (void*)&node,
2256 sizeof (node));
2258 gtk_tree_path_free(sourcerow);
2260 else if (data->target == gdk_atom_intern("application/x-im-contact", FALSE))
2262 GtkTreeRowReference *ref;
2263 GtkTreePath *sourcerow;
2264 GtkTreeIter iter;
2265 PurpleBlistNode *node = NULL;
2266 PurpleBuddy *buddy;
2267 PurpleConnection *gc;
2268 GString *str;
2269 const char *protocol;
2271 ref = g_object_get_data(G_OBJECT(dc), "gtk-tree-view-source-row");
2272 sourcerow = gtk_tree_row_reference_get_path(ref);
2274 if (!sourcerow)
2275 return;
2277 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter,
2278 sourcerow);
2279 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
2281 if (PURPLE_BLIST_NODE_IS_CONTACT(node))
2283 buddy = purple_contact_get_priority_buddy((PurpleContact *)node);
2285 else if (!PURPLE_BLIST_NODE_IS_BUDDY(node))
2287 gtk_tree_path_free(sourcerow);
2288 return;
2290 else
2292 buddy = (PurpleBuddy *)node;
2295 gc = purple_account_get_connection(buddy->account);
2297 if (gc == NULL)
2299 gtk_tree_path_free(sourcerow);
2300 return;
2303 protocol =
2304 PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->list_icon(buddy->account,
2305 buddy);
2307 str = g_string_new(NULL);
2308 g_string_printf(str,
2309 "MIME-Version: 1.0\r\n"
2310 "Content-Type: application/x-im-contact\r\n"
2311 "X-IM-Protocol: %s\r\n"
2312 "X-IM-Username: %s\r\n",
2313 protocol,
2314 buddy->name);
2316 if (buddy->alias != NULL)
2318 g_string_append_printf(str,
2319 "X-IM-Alias: %s\r\n",
2320 buddy->alias);
2323 g_string_append(str, "\r\n");
2325 gtk_selection_data_set(data,
2326 gdk_atom_intern("application/x-im-contact", FALSE),
2327 8, /* bits */
2328 (const guchar *)str->str,
2329 strlen(str->str) + 1);
2331 g_string_free(str, TRUE);
2332 gtk_tree_path_free(sourcerow);
2336 static void pidgin_blist_drag_data_rcv_cb(GtkWidget *widget, GdkDragContext *dc, guint x, guint y,
2337 GtkSelectionData *sd, guint info, guint t)
2339 if (gtkblist->drag_timeout) {
2340 g_source_remove(gtkblist->drag_timeout);
2341 gtkblist->drag_timeout = 0;
2344 if (sd->target == gdk_atom_intern("PURPLE_BLIST_NODE", FALSE) && sd->data) {
2345 PurpleBlistNode *n = NULL;
2346 GtkTreePath *path = NULL;
2347 GtkTreeViewDropPosition position;
2348 memcpy(&n, sd->data, sizeof(n));
2349 if(gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget), x, y, &path, &position)) {
2350 /* if we're here, I think it means the drop is ok */
2351 GtkTreeIter iter;
2352 PurpleBlistNode *node;
2353 struct _pidgin_blist_node *gtknode;
2355 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel),
2356 &iter, path);
2357 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel),
2358 &iter, NODE_COLUMN, &node, -1);
2359 gtknode = node->ui_data;
2361 if (PURPLE_BLIST_NODE_IS_CONTACT(n)) {
2362 PurpleContact *c = (PurpleContact*)n;
2363 if (PURPLE_BLIST_NODE_IS_CONTACT(node) && gtknode->contact_expanded) {
2364 purple_blist_merge_contact(c, node);
2365 } else if (PURPLE_BLIST_NODE_IS_CONTACT(node) ||
2366 PURPLE_BLIST_NODE_IS_CHAT(node)) {
2367 switch(position) {
2368 case GTK_TREE_VIEW_DROP_AFTER:
2369 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
2370 purple_blist_add_contact(c, (PurpleGroup*)node->parent,
2371 node);
2372 break;
2373 case GTK_TREE_VIEW_DROP_BEFORE:
2374 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
2375 purple_blist_add_contact(c, (PurpleGroup*)node->parent,
2376 node->prev);
2377 break;
2379 } else if(PURPLE_BLIST_NODE_IS_GROUP(node)) {
2380 purple_blist_add_contact(c, (PurpleGroup*)node, NULL);
2381 } else if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
2382 purple_blist_merge_contact(c, node);
2384 } else if (PURPLE_BLIST_NODE_IS_BUDDY(n)) {
2385 PurpleBuddy *b = (PurpleBuddy*)n;
2386 if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
2387 switch(position) {
2388 case GTK_TREE_VIEW_DROP_AFTER:
2389 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
2390 purple_blist_add_buddy(b, (PurpleContact*)node->parent,
2391 (PurpleGroup*)node->parent->parent, node);
2392 break;
2393 case GTK_TREE_VIEW_DROP_BEFORE:
2394 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
2395 purple_blist_add_buddy(b, (PurpleContact*)node->parent,
2396 (PurpleGroup*)node->parent->parent,
2397 node->prev);
2398 break;
2400 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
2401 purple_blist_add_buddy(b, NULL, (PurpleGroup*)node->parent,
2402 NULL);
2403 } else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
2404 purple_blist_add_buddy(b, NULL, (PurpleGroup*)node, NULL);
2405 } else if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
2406 if(gtknode->contact_expanded) {
2407 switch(position) {
2408 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
2409 case GTK_TREE_VIEW_DROP_AFTER:
2410 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
2411 purple_blist_add_buddy(b, (PurpleContact*)node,
2412 (PurpleGroup*)node->parent, NULL);
2413 break;
2414 case GTK_TREE_VIEW_DROP_BEFORE:
2415 purple_blist_add_buddy(b, NULL,
2416 (PurpleGroup*)node->parent, node->prev);
2417 break;
2419 } else {
2420 switch(position) {
2421 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
2422 case GTK_TREE_VIEW_DROP_AFTER:
2423 purple_blist_add_buddy(b, NULL,
2424 (PurpleGroup*)node->parent, NULL);
2425 break;
2426 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
2427 case GTK_TREE_VIEW_DROP_BEFORE:
2428 purple_blist_add_buddy(b, NULL,
2429 (PurpleGroup*)node->parent, node->prev);
2430 break;
2434 } else if (PURPLE_BLIST_NODE_IS_CHAT(n)) {
2435 PurpleChat *chat = (PurpleChat *)n;
2436 if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
2437 switch(position) {
2438 case GTK_TREE_VIEW_DROP_AFTER:
2439 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
2440 case GTK_TREE_VIEW_DROP_BEFORE:
2441 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
2442 purple_blist_add_chat(chat,
2443 (PurpleGroup*)node->parent->parent,
2444 node->parent);
2445 break;
2447 } else if(PURPLE_BLIST_NODE_IS_CONTACT(node) ||
2448 PURPLE_BLIST_NODE_IS_CHAT(node)) {
2449 switch(position) {
2450 case GTK_TREE_VIEW_DROP_AFTER:
2451 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
2452 purple_blist_add_chat(chat, (PurpleGroup*)node->parent, node);
2453 break;
2454 case GTK_TREE_VIEW_DROP_BEFORE:
2455 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
2456 purple_blist_add_chat(chat, (PurpleGroup*)node->parent, node->prev);
2457 break;
2459 } else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
2460 purple_blist_add_chat(chat, (PurpleGroup*)node, NULL);
2462 } else if (PURPLE_BLIST_NODE_IS_GROUP(n)) {
2463 PurpleGroup *g = (PurpleGroup*)n;
2464 if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
2465 switch (position) {
2466 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
2467 case GTK_TREE_VIEW_DROP_AFTER:
2468 purple_blist_add_group(g, node);
2469 break;
2470 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
2471 case GTK_TREE_VIEW_DROP_BEFORE:
2472 purple_blist_add_group(g, node->prev);
2473 break;
2475 } else if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
2476 purple_blist_add_group(g, node->parent->parent);
2477 } else if(PURPLE_BLIST_NODE_IS_CONTACT(node) ||
2478 PURPLE_BLIST_NODE_IS_CHAT(node)) {
2479 purple_blist_add_group(g, node->parent);
2483 gtk_tree_path_free(path);
2484 gtk_drag_finish(dc, TRUE, (dc->action == GDK_ACTION_MOVE), t);
2487 else if (sd->target == gdk_atom_intern("application/x-im-contact",
2488 FALSE) && sd->data)
2490 PurpleGroup *group = NULL;
2491 GtkTreePath *path = NULL;
2492 GtkTreeViewDropPosition position;
2493 PurpleAccount *account;
2494 char *protocol = NULL;
2495 char *username = NULL;
2496 char *alias = NULL;
2498 if (gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget),
2499 x, y, &path, &position))
2501 GtkTreeIter iter;
2502 PurpleBlistNode *node;
2504 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel),
2505 &iter, path);
2506 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel),
2507 &iter, NODE_COLUMN, &node, -1);
2509 if (PURPLE_BLIST_NODE_IS_BUDDY(node))
2511 group = (PurpleGroup *)node->parent->parent;
2513 else if (PURPLE_BLIST_NODE_IS_CHAT(node) ||
2514 PURPLE_BLIST_NODE_IS_CONTACT(node))
2516 group = (PurpleGroup *)node->parent;
2518 else if (PURPLE_BLIST_NODE_IS_GROUP(node))
2520 group = (PurpleGroup *)node;
2524 if (pidgin_parse_x_im_contact((const char *)sd->data, FALSE, &account,
2525 &protocol, &username, &alias))
2527 if (account == NULL)
2529 purple_notify_error(NULL, NULL,
2530 _("You are not currently signed on with an account that "
2531 "can add that buddy."), NULL);
2533 else
2535 purple_blist_request_add_buddy(account, username,
2536 (group ? group->name : NULL),
2537 alias);
2541 g_free(username);
2542 g_free(protocol);
2543 g_free(alias);
2545 if (path != NULL)
2546 gtk_tree_path_free(path);
2548 gtk_drag_finish(dc, TRUE, (dc->action == GDK_ACTION_MOVE), t);
2550 else if (sd->target == gdk_atom_intern("text/x-vcard", FALSE) && sd->data)
2552 gboolean result;
2553 PurpleGroup *group = NULL;
2554 GtkTreePath *path = NULL;
2555 GtkTreeViewDropPosition position;
2557 if (gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget),
2558 x, y, &path, &position))
2560 GtkTreeIter iter;
2561 PurpleBlistNode *node;
2563 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel),
2564 &iter, path);
2565 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel),
2566 &iter, NODE_COLUMN, &node, -1);
2568 if (PURPLE_BLIST_NODE_IS_BUDDY(node))
2570 group = (PurpleGroup *)node->parent->parent;
2572 else if (PURPLE_BLIST_NODE_IS_CHAT(node) ||
2573 PURPLE_BLIST_NODE_IS_CONTACT(node))
2575 group = (PurpleGroup *)node->parent;
2577 else if (PURPLE_BLIST_NODE_IS_GROUP(node))
2579 group = (PurpleGroup *)node;
2583 result = parse_vcard((const gchar *)sd->data, group);
2585 gtk_drag_finish(dc, result, (dc->action == GDK_ACTION_MOVE), t);
2586 } else if (sd->target == gdk_atom_intern("text/uri-list", FALSE) && sd->data) {
2587 GtkTreePath *path = NULL;
2588 GtkTreeViewDropPosition position;
2590 if (gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget),
2591 x, y, &path, &position))
2593 GtkTreeIter iter;
2594 PurpleBlistNode *node;
2596 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel),
2597 &iter, path);
2598 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel),
2599 &iter, NODE_COLUMN, &node, -1);
2601 if (PURPLE_BLIST_NODE_IS_BUDDY(node) || PURPLE_BLIST_NODE_IS_CONTACT(node)) {
2602 PurpleBuddy *b = PURPLE_BLIST_NODE_IS_BUDDY(node) ? PURPLE_BUDDY(node) : purple_contact_get_priority_buddy(PURPLE_CONTACT(node));
2603 pidgin_dnd_file_manage(sd, b->account, b->name);
2604 gtk_drag_finish(dc, TRUE, (dc->action == GDK_ACTION_MOVE), t);
2605 } else {
2606 gtk_drag_finish(dc, FALSE, FALSE, t);
2612 /* Altered from do_colorshift in gnome-panel */
2613 static void
2614 do_alphashift(GdkPixbuf *pixbuf, int shift)
2616 gint i, j;
2617 gint width, height, padding;
2618 guchar *pixels;
2619 int val;
2621 if (!gdk_pixbuf_get_has_alpha(pixbuf))
2622 return;
2624 width = gdk_pixbuf_get_width(pixbuf);
2625 height = gdk_pixbuf_get_height(pixbuf);
2626 padding = gdk_pixbuf_get_rowstride(pixbuf) - width * 4;
2627 pixels = gdk_pixbuf_get_pixels(pixbuf);
2629 for (i = 0; i < height; i++) {
2630 for (j = 0; j < width; j++) {
2631 pixels++;
2632 pixels++;
2633 pixels++;
2634 val = *pixels - shift;
2635 *(pixels++) = CLAMP(val, 0, 255);
2637 pixels += padding;
2642 static GdkPixbuf *pidgin_blist_get_buddy_icon(PurpleBlistNode *node,
2643 gboolean scaled, gboolean greyed)
2645 gsize len;
2646 PurpleBuddy *buddy = NULL;
2647 PurpleGroup *group = NULL;
2648 const guchar *data = NULL;
2649 GdkPixbuf *buf, *ret = NULL;
2650 PurpleBuddyIcon *icon = NULL;
2651 PurpleAccount *account = NULL;
2652 PurpleContact *contact = NULL;
2653 PurpleStoredImage *custom_img;
2654 PurplePluginProtocolInfo *prpl_info = NULL;
2655 gint orig_width, orig_height, scale_width, scale_height;
2657 if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
2658 buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
2659 contact = (PurpleContact*)node;
2660 } else if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
2661 buddy = (PurpleBuddy*)node;
2662 contact = purple_buddy_get_contact(buddy);
2663 } else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
2664 group = (PurpleGroup*)node;
2665 } else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
2666 /* We don't need to do anything here. We just need to not fall
2667 * into the else block and return. */
2668 } else {
2669 return NULL;
2672 if (buddy) {
2673 account = purple_buddy_get_account(buddy);
2676 if(account && account->gc) {
2677 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(account->gc->prpl);
2680 #if 0
2681 if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons"))
2682 return NULL;
2683 #endif
2685 /* If we have a contact then this is either a contact or a buddy and
2686 * we want to fetch the custom icon for the contact. If we don't have
2687 * a contact then this is a group or some other type of node and we
2688 * want to use that directly. */
2689 if (contact) {
2690 custom_img = purple_buddy_icons_node_find_custom_icon((PurpleBlistNode*)contact);
2691 } else {
2692 custom_img = purple_buddy_icons_node_find_custom_icon(node);
2695 if (custom_img) {
2696 data = purple_imgstore_get_data(custom_img);
2697 len = purple_imgstore_get_size(custom_img);
2700 if (data == NULL) {
2701 if (buddy) {
2702 /* Not sure I like this...*/
2703 if (!(icon = purple_buddy_icons_find(buddy->account, buddy->name)))
2704 return NULL;
2705 data = purple_buddy_icon_get_data(icon, &len);
2708 if(data == NULL)
2709 return NULL;
2712 buf = pidgin_pixbuf_from_data(data, len);
2713 purple_buddy_icon_unref(icon);
2714 if (!buf) {
2715 purple_debug_warning("gtkblist", "Couldn't load buddy icon "
2716 "on account %s (%s) buddyname=%s "
2717 "custom_img_data=%p\n",
2718 account ? purple_account_get_username(account) : "(no account)",
2719 account ? purple_account_get_protocol_id(account) : "(no account)",
2720 buddy ? purple_buddy_get_name(buddy) : "(no buddy)",
2721 custom_img ? purple_imgstore_get_data(custom_img) : NULL);
2722 purple_imgstore_unref(custom_img);
2723 return NULL;
2725 purple_imgstore_unref(custom_img);
2727 if (greyed) {
2728 gboolean offline = FALSE, idle = FALSE;
2730 if (buddy) {
2731 PurplePresence *presence = purple_buddy_get_presence(buddy);
2732 if (!PURPLE_BUDDY_IS_ONLINE(buddy))
2733 offline = TRUE;
2734 if (purple_presence_is_idle(presence))
2735 idle = TRUE;
2736 } else if (group) {
2737 if (purple_blist_get_group_online_count(group) == 0)
2738 offline = TRUE;
2741 if (offline)
2742 gdk_pixbuf_saturate_and_pixelate(buf, buf, 0.0, FALSE);
2744 if (idle)
2745 gdk_pixbuf_saturate_and_pixelate(buf, buf, 0.25, FALSE);
2748 /* I'd use the pidgin_buddy_icon_get_scale_size() thing, but it won't
2749 * tell me the original size, which I need for scaling purposes. */
2750 scale_width = orig_width = gdk_pixbuf_get_width(buf);
2751 scale_height = orig_height = gdk_pixbuf_get_height(buf);
2753 if (prpl_info && prpl_info->icon_spec.scale_rules & PURPLE_ICON_SCALE_DISPLAY)
2754 purple_buddy_icon_get_scale_size(&prpl_info->icon_spec, &scale_width, &scale_height);
2756 if (scaled || scale_height > 200 || scale_width > 200) {
2757 GdkPixbuf *tmpbuf;
2758 float scale_size = scaled ? 32.0 : 200.0;
2759 if(scale_height > scale_width) {
2760 scale_width = scale_size * (double)scale_width / (double)scale_height;
2761 scale_height = scale_size;
2762 } else {
2763 scale_height = scale_size * (double)scale_height / (double)scale_width;
2764 scale_width = scale_size;
2766 /* Scale & round before making square, so rectangular (but
2767 * non-square) images get rounded corners too. */
2768 tmpbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, scale_width, scale_height);
2769 gdk_pixbuf_fill(tmpbuf, 0x00000000);
2770 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);
2771 if (pidgin_gdk_pixbuf_is_opaque(tmpbuf))
2772 pidgin_gdk_pixbuf_make_round(tmpbuf);
2773 ret = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, scale_size, scale_size);
2774 gdk_pixbuf_fill(ret, 0x00000000);
2775 gdk_pixbuf_copy_area(tmpbuf, 0, 0, scale_width, scale_height, ret, (scale_size-scale_width)/2, (scale_size-scale_height)/2);
2776 g_object_unref(G_OBJECT(tmpbuf));
2777 } else {
2778 ret = gdk_pixbuf_scale_simple(buf,scale_width,scale_height, GDK_INTERP_BILINEAR);
2780 g_object_unref(G_OBJECT(buf));
2782 return ret;
2785 /* # - Status Icon
2786 * P - Protocol Icon
2787 * A - Buddy Icon
2788 * [ - SMALL_SPACE
2789 * = - LARGE_SPACE
2790 * +--- STATUS_SIZE +--- td->avatar_width
2791 * | +-- td->name_width |
2792 * +----+ +-------+ +---------+
2793 * | | | | | |
2794 * +-------------------------------------------+
2795 * | [ = [ |--- TOOLTIP_BORDER
2796 *name_height --+-| ######[BuddyName = PP [ AAAAAAAAAAA |--+
2797 * | | ######[ = PP [ AAAAAAAAAAA | |
2798 * STATUS SIZE -| | ######[[[[[[[[[[[[[[[[[[[[[ AAAAAAAAAAA | |
2799 * +--+-| ######[Account: So-and-so [ AAAAAAAAAAA | |-- td->avatar_height
2800 * | | [Idle: 4h 15m [ AAAAAAAAAAA | |
2801 * height --+ | [Foo: Bar, Baz [ AAAAAAAAAAA | |
2802 * | | [Status: Awesome [ AAAAAAAAAAA |--+
2803 * +----| [Stop: Hammer Time [ |
2804 * | [ [ |--- TOOLTIP_BORDER
2805 * +-------------------------------------------+
2806 * | | | |
2807 * | +----------------+ |
2808 * | | |
2809 * | +-- td->width |
2810 * | |
2811 * +---- TOOLTIP_BORDER +---- TOOLTIP_BORDER
2815 #define STATUS_SIZE 16
2816 #define TOOLTIP_BORDER 12
2817 #define SMALL_SPACE 6
2818 #define LARGE_SPACE 12
2819 #define PRPL_SIZE 16
2820 struct tooltip_data {
2821 PangoLayout *layout;
2822 PangoLayout *name_layout;
2823 GdkPixbuf *prpl_icon;
2824 GdkPixbuf *status_icon;
2825 GdkPixbuf *avatar;
2826 gboolean avatar_is_prpl_icon;
2827 int avatar_width;
2828 int avatar_height;
2829 int name_height;
2830 int name_width;
2831 int width;
2832 int height;
2833 int padding;
2836 static PangoLayout * create_pango_layout(const char *markup, int *width, int *height)
2838 PangoLayout *layout;
2839 int w, h;
2841 layout = gtk_widget_create_pango_layout(gtkblist->tipwindow, NULL);
2842 pango_layout_set_markup(layout, markup, -1);
2843 pango_layout_set_wrap(layout, PANGO_WRAP_WORD);
2844 pango_layout_set_width(layout, 300000);
2846 pango_layout_get_size (layout, &w, &h);
2847 if (width)
2848 *width = PANGO_PIXELS(w);
2849 if (height)
2850 *height = PANGO_PIXELS(h);
2851 return layout;
2854 static struct tooltip_data * create_tip_for_account(PurpleAccount *account)
2856 struct tooltip_data *td = g_new0(struct tooltip_data, 1);
2857 td->status_icon = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL);
2858 /* Yes, status_icon, not prpl_icon */
2859 if (purple_account_is_disconnected(account))
2860 gdk_pixbuf_saturate_and_pixelate(td->status_icon, td->status_icon, 0.0, FALSE);
2861 td->layout = create_pango_layout(purple_account_get_username(account), &td->width, &td->height);
2862 td->padding = SMALL_SPACE;
2863 return td;
2866 static struct tooltip_data * create_tip_for_node(PurpleBlistNode *node, gboolean full)
2868 struct tooltip_data *td = g_new0(struct tooltip_data, 1);
2869 PurpleAccount *account = NULL;
2870 char *tmp = NULL, *node_name = NULL, *tooltip_text = NULL;
2872 if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
2873 account = ((PurpleBuddy*)(node))->account;
2874 } else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
2875 account = ((PurpleChat*)(node))->account;
2878 td->padding = TOOLTIP_BORDER;
2879 td->status_icon = pidgin_blist_get_status_icon(node, PIDGIN_STATUS_ICON_LARGE);
2880 td->avatar = pidgin_blist_get_buddy_icon(node, !full, FALSE);
2881 if (account != NULL) {
2882 td->prpl_icon = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL);
2884 tooltip_text = pidgin_get_tooltip_text(node, full);
2885 if (tooltip_text && *tooltip_text) {
2886 td->layout = create_pango_layout(tooltip_text, &td->width, &td->height);
2889 if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
2890 tmp = g_markup_escape_text(purple_buddy_get_name((PurpleBuddy*)node), -1);
2891 } else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
2892 tmp = g_markup_escape_text(purple_chat_get_name((PurpleChat*)node), -1);
2893 } else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
2894 tmp = g_markup_escape_text(purple_group_get_name((PurpleGroup*)node), -1);
2895 } else {
2896 /* I don't believe this can happen currently, I think
2897 * everything that calls this function checks for one of the
2898 * above node types first. */
2899 tmp = g_strdup(_("Unknown node type"));
2901 node_name = g_strdup_printf("<span size='x-large' weight='bold'>%s</span>",
2902 tmp ? tmp : "");
2903 g_free(tmp);
2905 td->name_layout = create_pango_layout(node_name, &td->name_width, &td->name_height);
2906 td->name_width += SMALL_SPACE + PRPL_SIZE;
2907 td->name_height = MAX(td->name_height, PRPL_SIZE + SMALL_SPACE);
2908 #if 0 /* PRPL Icon as avatar */
2909 if(!td->avatar && full) {
2910 td->avatar = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_LARGE);
2911 td->avatar_is_prpl_icon = TRUE;
2913 #endif
2915 if (td->avatar) {
2916 td->avatar_width = gdk_pixbuf_get_width(td->avatar);
2917 td->avatar_height = gdk_pixbuf_get_height(td->avatar);
2920 g_free(node_name);
2921 g_free(tooltip_text);
2922 return td;
2925 static gboolean
2926 pidgin_blist_paint_tip(GtkWidget *widget, gpointer null)
2928 GtkStyle *style;
2929 int current_height, max_width;
2930 int max_text_width;
2931 int max_avatar_width;
2932 GList *l;
2933 int prpl_col = 0;
2934 GtkTextDirection dir = gtk_widget_get_direction(widget);
2935 int status_size = 0;
2937 if(gtkblist->tooltipdata == NULL)
2938 return FALSE;
2940 style = gtkblist->tipwindow->style;
2942 max_text_width = 0;
2943 max_avatar_width = 0;
2945 for(l = gtkblist->tooltipdata; l; l = l->next)
2947 struct tooltip_data *td = l->data;
2949 max_text_width = MAX(max_text_width,
2950 MAX(td->width, td->name_width));
2951 max_avatar_width = MAX(max_avatar_width, td->avatar_width);
2952 if (td->status_icon)
2953 status_size = STATUS_SIZE;
2956 max_width = TOOLTIP_BORDER + status_size + SMALL_SPACE + max_text_width + SMALL_SPACE + max_avatar_width + TOOLTIP_BORDER;
2957 if (dir == GTK_TEXT_DIR_RTL)
2958 prpl_col = TOOLTIP_BORDER + max_avatar_width + SMALL_SPACE;
2959 else
2960 prpl_col = TOOLTIP_BORDER + status_size + SMALL_SPACE + max_text_width - PRPL_SIZE;
2962 current_height = 12;
2963 for(l = gtkblist->tooltipdata; l; l = l->next)
2965 struct tooltip_data *td = l->data;
2967 if (td->avatar && pidgin_gdk_pixbuf_is_opaque(td->avatar))
2969 if (dir == GTK_TEXT_DIR_RTL)
2970 gtk_paint_flat_box(style, gtkblist->tipwindow->window, GTK_STATE_NORMAL, GTK_SHADOW_OUT,
2971 NULL, gtkblist->tipwindow, "tooltip",
2972 TOOLTIP_BORDER -1, current_height -1, td->avatar_width +2, td->avatar_height + 2);
2973 else
2974 gtk_paint_flat_box(style, gtkblist->tipwindow->window, GTK_STATE_NORMAL, GTK_SHADOW_OUT,
2975 NULL, gtkblist->tipwindow, "tooltip",
2976 max_width - (td->avatar_width+ TOOLTIP_BORDER)-1,
2977 current_height-1,td->avatar_width+2, td->avatar_height+2);
2980 if (td->status_icon) {
2981 if (dir == GTK_TEXT_DIR_RTL)
2982 gdk_draw_pixbuf(GDK_DRAWABLE(gtkblist->tipwindow->window), NULL, td->status_icon,
2983 0, 0, max_width - TOOLTIP_BORDER - status_size, current_height, -1, -1, GDK_RGB_DITHER_NONE, 0, 0);
2984 else
2985 gdk_draw_pixbuf(GDK_DRAWABLE(gtkblist->tipwindow->window), NULL, td->status_icon,
2986 0, 0, TOOLTIP_BORDER, current_height, -1 , -1, GDK_RGB_DITHER_NONE, 0, 0);
2989 if(td->avatar) {
2990 if (dir == GTK_TEXT_DIR_RTL)
2991 gdk_draw_pixbuf(GDK_DRAWABLE(gtkblist->tipwindow->window), NULL,
2992 td->avatar, 0, 0, TOOLTIP_BORDER, current_height, -1, -1, GDK_RGB_DITHER_NONE, 0, 0);
2993 else
2994 gdk_draw_pixbuf(GDK_DRAWABLE(gtkblist->tipwindow->window), NULL,
2995 td->avatar, 0, 0, max_width - (td->avatar_width + TOOLTIP_BORDER),
2996 current_height, -1 , -1, GDK_RGB_DITHER_NONE, 0, 0);
2999 if (!td->avatar_is_prpl_icon && td->prpl_icon)
3000 gdk_draw_pixbuf(GDK_DRAWABLE(gtkblist->tipwindow->window), NULL, td->prpl_icon,
3001 0, 0,
3002 prpl_col,
3003 current_height + ((td->name_height / 2) - (PRPL_SIZE / 2)),
3004 -1 , -1, GDK_RGB_DITHER_NONE, 0, 0);
3006 if (td->name_layout) {
3007 if (dir == GTK_TEXT_DIR_RTL) {
3008 gtk_paint_layout(style, gtkblist->tipwindow->window, GTK_STATE_NORMAL, FALSE,
3009 NULL, gtkblist->tipwindow, "tooltip",
3010 max_width -(TOOLTIP_BORDER + status_size + SMALL_SPACE) - PANGO_PIXELS(300000),
3011 current_height, td->name_layout);
3012 } else {
3013 gtk_paint_layout (style, gtkblist->tipwindow->window, GTK_STATE_NORMAL, FALSE,
3014 NULL, gtkblist->tipwindow, "tooltip",
3015 TOOLTIP_BORDER + status_size + SMALL_SPACE, current_height, td->name_layout);
3019 if (td->layout) {
3020 if (dir != GTK_TEXT_DIR_RTL) {
3021 gtk_paint_layout (style, gtkblist->tipwindow->window, GTK_STATE_NORMAL, FALSE,
3022 NULL, gtkblist->tipwindow, "tooltip",
3023 TOOLTIP_BORDER + status_size + SMALL_SPACE, current_height + td->name_height, td->layout);
3024 } else {
3025 gtk_paint_layout(style, gtkblist->tipwindow->window, GTK_STATE_NORMAL, FALSE,
3026 NULL, gtkblist->tipwindow, "tooltip",
3027 max_width - (TOOLTIP_BORDER + status_size + SMALL_SPACE) - PANGO_PIXELS(300000),
3028 current_height + td->name_height,
3029 td->layout);
3033 current_height += MAX(td->name_height + td->height, td->avatar_height) + td->padding;
3035 return FALSE;
3038 static void
3039 pidgin_blist_destroy_tooltip_data(void)
3041 while(gtkblist->tooltipdata) {
3042 struct tooltip_data *td = gtkblist->tooltipdata->data;
3044 if(td->avatar)
3045 g_object_unref(td->avatar);
3046 if(td->status_icon)
3047 g_object_unref(td->status_icon);
3048 if(td->prpl_icon)
3049 g_object_unref(td->prpl_icon);
3050 if (td->layout)
3051 g_object_unref(td->layout);
3052 if (td->name_layout)
3053 g_object_unref(td->name_layout);
3054 g_free(td);
3055 gtkblist->tooltipdata = g_list_delete_link(gtkblist->tooltipdata, gtkblist->tooltipdata);
3059 void pidgin_blist_tooltip_destroy()
3061 pidgin_blist_destroy_tooltip_data();
3062 pidgin_tooltip_destroy();
3065 static void
3066 pidgin_blist_align_tooltip(struct tooltip_data *td, GtkWidget *widget)
3068 GtkTextDirection dir = gtk_widget_get_direction(widget);
3070 if (dir == GTK_TEXT_DIR_RTL)
3072 char* layout_name = purple_markup_strip_html(pango_layout_get_text(td->name_layout));
3073 PangoDirection dir = pango_find_base_dir(layout_name, -1);
3074 if (dir == PANGO_DIRECTION_RTL || dir == PANGO_DIRECTION_NEUTRAL)
3075 pango_layout_set_alignment(td->name_layout, PANGO_ALIGN_RIGHT);
3076 g_free(layout_name);
3077 pango_layout_set_alignment(td->layout, PANGO_ALIGN_RIGHT);
3081 static gboolean
3082 pidgin_blist_create_tooltip_for_node(GtkWidget *widget, gpointer data, int *w, int *h)
3084 PurpleBlistNode *node = data;
3085 int width, height;
3086 GList *list;
3087 int max_text_width = 0;
3088 int max_avatar_width = 0;
3089 int status_size = 0;
3091 if (gtkblist->tooltipdata) {
3092 gtkblist->tipwindow = NULL;
3093 pidgin_blist_destroy_tooltip_data();
3096 gtkblist->tipwindow = widget;
3097 if (PURPLE_BLIST_NODE_IS_CHAT(node) ||
3098 PURPLE_BLIST_NODE_IS_BUDDY(node)) {
3099 struct tooltip_data *td = create_tip_for_node(node, TRUE);
3100 pidgin_blist_align_tooltip(td, gtkblist->tipwindow);
3101 gtkblist->tooltipdata = g_list_append(gtkblist->tooltipdata, td);
3102 } else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
3103 PurpleGroup *group = (PurpleGroup*)node;
3104 GSList *accounts;
3105 struct tooltip_data *td = create_tip_for_node(node, TRUE);
3106 pidgin_blist_align_tooltip(td, gtkblist->tipwindow);
3107 gtkblist->tooltipdata = g_list_append(gtkblist->tooltipdata, td);
3109 /* Accounts with buddies in group */
3110 accounts = purple_group_get_accounts(group);
3111 for (; accounts != NULL;
3112 accounts = g_slist_delete_link(accounts, accounts)) {
3113 PurpleAccount *account = accounts->data;
3114 td = create_tip_for_account(account);
3115 gtkblist->tooltipdata = g_list_append(gtkblist->tooltipdata, td);
3117 } else if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
3118 PurpleBlistNode *child;
3119 PurpleBuddy *b = purple_contact_get_priority_buddy((PurpleContact *)node);
3121 for(child = node->child; child; child = child->next)
3123 if(PURPLE_BLIST_NODE_IS_BUDDY(child) && buddy_is_displayable((PurpleBuddy*)child)) {
3124 struct tooltip_data *td = create_tip_for_node(child, (b == (PurpleBuddy*)child));
3125 pidgin_blist_align_tooltip(td, gtkblist->tipwindow);
3126 if (b == (PurpleBuddy *)child) {
3127 gtkblist->tooltipdata = g_list_prepend(gtkblist->tooltipdata, td);
3128 } else {
3129 gtkblist->tooltipdata = g_list_append(gtkblist->tooltipdata, td);
3133 } else {
3134 return FALSE;
3137 height = width = 0;
3138 for (list = gtkblist->tooltipdata; list; list = list->next) {
3139 struct tooltip_data *td = list->data;
3140 max_text_width = MAX(max_text_width, MAX(td->width, td->name_width));
3141 max_avatar_width = MAX(max_avatar_width, td->avatar_width);
3142 height += MAX(MAX(STATUS_SIZE, td->avatar_height), td->height + td->name_height) + td->padding;
3143 if (td->status_icon)
3144 status_size = MAX(status_size, STATUS_SIZE);
3146 height += TOOLTIP_BORDER;
3147 width = TOOLTIP_BORDER + status_size + SMALL_SPACE + max_text_width + SMALL_SPACE + max_avatar_width + TOOLTIP_BORDER;
3149 if (w)
3150 *w = width;
3151 if (h)
3152 *h = height;
3154 return TRUE;
3157 static gboolean pidgin_blist_expand_timeout(GtkWidget *tv)
3159 GtkTreePath *path;
3160 GtkTreeIter iter;
3161 PurpleBlistNode *node;
3162 struct _pidgin_blist_node *gtknode;
3164 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),
3165 &path, NULL, NULL, NULL))
3166 return FALSE;
3167 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
3168 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
3170 if(!PURPLE_BLIST_NODE_IS_CONTACT(node)) {
3171 gtk_tree_path_free(path);
3172 return FALSE;
3175 gtknode = node->ui_data;
3177 if (!gtknode->contact_expanded) {
3178 GtkTreeIter i;
3180 pidgin_blist_expand_contact_cb(NULL, node);
3182 gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, &gtkblist->contact_rect);
3183 gdk_drawable_get_size(GDK_DRAWABLE(tv->window), &(gtkblist->contact_rect.width), NULL);
3184 gtkblist->mouseover_contact = node;
3185 gtk_tree_path_down (path);
3186 while (gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &i, path)) {
3187 GdkRectangle rect;
3188 gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, &rect);
3189 gtkblist->contact_rect.height += rect.height;
3190 gtk_tree_path_next(path);
3193 gtk_tree_path_free(path);
3194 return FALSE;
3197 static gboolean buddy_is_displayable(PurpleBuddy *buddy)
3199 struct _pidgin_blist_node *gtknode;
3201 if(!buddy)
3202 return FALSE;
3204 gtknode = ((PurpleBlistNode*)buddy)->ui_data;
3206 return (purple_account_is_connected(buddy->account) &&
3207 (purple_presence_is_online(buddy->presence) ||
3208 (gtknode && gtknode->recent_signonoff) ||
3209 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_offline_buddies") ||
3210 purple_blist_node_get_bool((PurpleBlistNode*)buddy, "show_offline")));
3213 void pidgin_blist_draw_tooltip(PurpleBlistNode *node, GtkWidget *widget)
3215 pidgin_tooltip_show(widget, node, pidgin_blist_create_tooltip_for_node, pidgin_blist_paint_tip);
3218 static gboolean pidgin_blist_drag_motion_cb(GtkWidget *tv, GdkDragContext *drag_context,
3219 gint x, gint y, guint time, gpointer user_data)
3221 GtkTreePath *path;
3222 int delay;
3223 GdkRectangle rect;
3226 * When dragging a buddy into a contact, this is the delay before
3227 * the contact auto-expands.
3229 delay = 900;
3231 if (gtkblist->drag_timeout) {
3232 if ((y > gtkblist->tip_rect.y) && ((y - gtkblist->tip_rect.height) < gtkblist->tip_rect.y))
3233 return FALSE;
3234 /* We've left the cell. Remove the timeout and create a new one below */
3235 g_source_remove(gtkblist->drag_timeout);
3238 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), x, y, &path, NULL, NULL, NULL);
3239 gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, &rect);
3241 if (path)
3242 gtk_tree_path_free(path);
3244 /* Only autoexpand when in the middle of the cell to avoid annoying un-intended expands */
3245 if (y < rect.y + (rect.height / 3) ||
3246 y > rect.y + (2 * (rect.height /3)))
3247 return FALSE;
3249 rect.height = rect.height / 3;
3250 rect.y += rect.height;
3252 gtkblist->tip_rect = rect;
3254 gtkblist->drag_timeout = g_timeout_add(delay, (GSourceFunc)pidgin_blist_expand_timeout, tv);
3256 if (gtkblist->mouseover_contact) {
3257 if ((y < gtkblist->contact_rect.y) || ((y - gtkblist->contact_rect.height) > gtkblist->contact_rect.y)) {
3258 pidgin_blist_collapse_contact_cb(NULL, gtkblist->mouseover_contact);
3259 gtkblist->mouseover_contact = NULL;
3263 return FALSE;
3266 static gboolean
3267 pidgin_blist_create_tooltip(GtkWidget *widget, GtkTreePath *path,
3268 gpointer null, int *w, int *h)
3270 GtkTreeIter iter;
3271 PurpleBlistNode *node;
3272 gboolean editable = FALSE;
3274 /* If we're editing a cell (e.g. alias editing), don't show the tooltip */
3275 g_object_get(G_OBJECT(gtkblist->text_rend), "editable", &editable, NULL);
3276 if (editable)
3277 return FALSE;
3279 if (gtkblist->tooltipdata) {
3280 gtkblist->tipwindow = NULL;
3281 pidgin_blist_destroy_tooltip_data();
3284 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
3285 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
3287 return pidgin_blist_create_tooltip_for_node(widget, node, w, h);
3290 static gboolean pidgin_blist_motion_cb (GtkWidget *tv, GdkEventMotion *event, gpointer null)
3292 if (gtkblist->mouseover_contact) {
3293 if ((event->y < gtkblist->contact_rect.y) || ((event->y - gtkblist->contact_rect.height) > gtkblist->contact_rect.y)) {
3294 pidgin_blist_collapse_contact_cb(NULL, gtkblist->mouseover_contact);
3295 gtkblist->mouseover_contact = NULL;
3299 return FALSE;
3302 static gboolean pidgin_blist_leave_cb (GtkWidget *w, GdkEventCrossing *e, gpointer n)
3304 if (gtkblist->timeout) {
3305 g_source_remove(gtkblist->timeout);
3306 gtkblist->timeout = 0;
3309 if (gtkblist->drag_timeout) {
3310 g_source_remove(gtkblist->drag_timeout);
3311 gtkblist->drag_timeout = 0;
3314 if (gtkblist->mouseover_contact &&
3315 !((e->x > gtkblist->contact_rect.x) && (e->x < (gtkblist->contact_rect.x + gtkblist->contact_rect.width)) &&
3316 (e->y > gtkblist->contact_rect.y) && (e->y < (gtkblist->contact_rect.y + gtkblist->contact_rect.height)))) {
3317 pidgin_blist_collapse_contact_cb(NULL, gtkblist->mouseover_contact);
3318 gtkblist->mouseover_contact = NULL;
3320 return FALSE;
3323 static void
3324 toggle_debug(void)
3326 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/debug/enabled",
3327 !purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/debug/enabled"));
3330 static char *get_mood_icon_path(const char *mood)
3332 char *path;
3334 if (!strcmp(mood, "busy")) {
3335 path = g_build_filename(DATADIR, "pixmaps", "pidgin",
3336 "status", "16", "busy.png", NULL);
3337 } else if (!strcmp(mood, "hiptop")) {
3338 path = g_build_filename(DATADIR, "pixmaps", "pidgin",
3339 "emblems", "16", "hiptop.png", NULL);
3340 } else {
3341 char *filename = g_strdup_printf("%s.png", mood);
3342 path = g_build_filename(DATADIR, "pixmaps", "pidgin",
3343 "emotes", "small", filename, NULL);
3344 g_free(filename);
3346 return path;
3349 static void
3350 update_status_with_mood(PurpleAccount *account, const gchar *mood,
3351 const gchar *text)
3353 if (mood && *mood) {
3354 if (text) {
3355 purple_account_set_status(account, "mood", TRUE,
3356 PURPLE_MOOD_NAME, mood,
3357 PURPLE_MOOD_COMMENT, text,
3358 NULL);
3359 } else {
3360 purple_account_set_status(account, "mood", TRUE,
3361 PURPLE_MOOD_NAME, mood,
3362 NULL);
3364 } else {
3365 purple_account_set_status(account, "mood", FALSE, NULL);
3369 static void
3370 edit_mood_cb(PurpleConnection *gc, PurpleRequestFields *fields)
3372 PurpleRequestField *mood_field;
3373 GList *l;
3375 mood_field = purple_request_fields_get_field(fields, "mood");
3376 l = purple_request_field_list_get_selected(mood_field);
3378 if (l) {
3379 const char *mood = purple_request_field_list_get_data(mood_field, l->data);
3381 if (gc) {
3382 const char *text;
3383 PurpleAccount *account = purple_connection_get_account(gc);
3385 if (gc->flags & PURPLE_CONNECTION_SUPPORT_MOOD_MESSAGES) {
3386 PurpleRequestField *text_field;
3387 text_field = purple_request_fields_get_field(fields, "text");
3388 text = purple_request_field_string_get_value(text_field);
3389 } else {
3390 text = NULL;
3393 update_status_with_mood(account, mood, text);
3394 } else {
3395 GList *accounts = purple_accounts_get_all_active();
3397 for (; accounts ; accounts = g_list_delete_link(accounts, accounts)) {
3398 PurpleAccount *account = (PurpleAccount *) accounts->data;
3399 PurpleConnection *gc = purple_account_get_connection(account);
3401 if (gc && gc->flags & PURPLE_CONNECTION_SUPPORT_MOODS) {
3402 update_status_with_mood(account, mood, NULL);
3409 static void
3410 global_moods_for_each(gpointer key, gpointer value, gpointer user_data)
3412 GList **out_moods = (GList **) user_data;
3413 PurpleMood *mood = (PurpleMood *) value;
3415 *out_moods = g_list_append(*out_moods, mood);
3418 static PurpleMood *
3419 get_global_moods(void)
3421 GHashTable *global_moods =
3422 g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
3423 GHashTable *mood_counts =
3424 g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
3425 GList *accounts = purple_accounts_get_all_active();
3426 PurpleMood *result = NULL;
3427 GList *out_moods = NULL;
3428 int i = 0;
3429 int num_accounts = 0;
3431 for (; accounts ; accounts = g_list_delete_link(accounts, accounts)) {
3432 PurpleAccount *account = (PurpleAccount *) accounts->data;
3433 if (purple_account_is_connected(account)) {
3434 PurpleConnection *gc = purple_account_get_connection(account);
3436 if (gc->flags & PURPLE_CONNECTION_SUPPORT_MOODS) {
3437 PurplePluginProtocolInfo *prpl_info =
3438 PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
3439 PurpleMood *mood = NULL;
3441 /* PURPLE_CONNECTION_SUPPORT_MOODS would not be set if the prpl doesn't
3442 * have get_moods, so using PURPLE_PROTOCOL_PLUGIN_HAS_FUNC isn't necessary
3443 * here */
3444 for (mood = prpl_info->get_moods(account) ;
3445 mood->mood != NULL ; mood++) {
3446 int mood_count =
3447 GPOINTER_TO_INT(g_hash_table_lookup(mood_counts, mood->mood));
3449 if (!g_hash_table_lookup(global_moods, mood->mood)) {
3450 g_hash_table_insert(global_moods, (gpointer)mood->mood, mood);
3452 g_hash_table_insert(mood_counts, (gpointer)mood->mood,
3453 GINT_TO_POINTER(mood_count + 1));
3456 num_accounts++;
3461 g_hash_table_foreach(global_moods, global_moods_for_each, &out_moods);
3462 result = g_new0(PurpleMood, g_hash_table_size(global_moods) + 1);
3464 while (out_moods) {
3465 PurpleMood *mood = (PurpleMood *) out_moods->data;
3466 int in_num_accounts =
3467 GPOINTER_TO_INT(g_hash_table_lookup(mood_counts, mood->mood));
3469 if (in_num_accounts == num_accounts) {
3470 /* mood is present in all accounts supporting moods */
3471 result[i].mood = mood->mood;
3472 result[i].description = mood->description;
3473 i++;
3475 out_moods = g_list_delete_link(out_moods, out_moods);
3478 g_hash_table_destroy(global_moods);
3479 g_hash_table_destroy(mood_counts);
3481 return result;
3484 /* get current set mood for all mood-supporting accounts, or NULL if not set
3485 or not set to the same on all */
3486 static const gchar *
3487 get_global_mood_status(void)
3489 GList *accounts = purple_accounts_get_all_active();
3490 const gchar *found_mood = NULL;
3492 for (; accounts ; accounts = g_list_delete_link(accounts, accounts)) {
3493 PurpleAccount *account = (PurpleAccount *) accounts->data;
3495 if (purple_account_is_connected(account) &&
3496 (purple_account_get_connection(account)->flags &
3497 PURPLE_CONNECTION_SUPPORT_MOODS)) {
3498 PurplePresence *presence = purple_account_get_presence(account);
3499 PurpleStatus *status = purple_presence_get_status(presence, "mood");
3500 const gchar *curr_mood = purple_status_get_attr_string(status, PURPLE_MOOD_NAME);
3502 if (found_mood != NULL && !purple_strequal(curr_mood, found_mood)) {
3503 /* found a different mood */
3504 found_mood = NULL;
3505 break;
3506 } else {
3507 found_mood = curr_mood;
3512 return found_mood;
3515 static void
3516 set_mood_cb(GtkWidget *widget, PurpleAccount *account)
3518 const char *current_mood;
3519 PurpleRequestFields *fields;
3520 PurpleRequestFieldGroup *g;
3521 PurpleRequestField *f;
3522 PurpleConnection *gc = NULL;
3523 PurplePluginProtocolInfo *prpl_info = NULL;
3524 PurpleMood *mood;
3525 PurpleMood *global_moods = get_global_moods();
3527 if (account) {
3528 PurplePresence *presence = purple_account_get_presence(account);
3529 PurpleStatus *status = purple_presence_get_status(presence, "mood");
3530 gc = purple_account_get_connection(account);
3531 g_return_if_fail(gc->prpl != NULL);
3532 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
3533 current_mood = purple_status_get_attr_string(status, PURPLE_MOOD_NAME);
3534 } else {
3535 current_mood = get_global_mood_status();
3538 fields = purple_request_fields_new();
3539 g = purple_request_field_group_new(NULL);
3540 f = purple_request_field_list_new("mood", _("Please select your mood from the list"));
3542 purple_request_field_list_add(f, _("None"), "");
3543 if (current_mood == NULL)
3544 purple_request_field_list_add_selected(f, _("None"));
3546 /* TODO: rlaager wants this sorted. */
3547 /* TODO: darkrain wants it sorted post-translation */
3548 if (account && PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, get_moods))
3549 mood = prpl_info->get_moods(account);
3550 else
3551 mood = global_moods;
3552 for ( ; mood->mood != NULL ; mood++) {
3553 char *path;
3555 if (mood->mood == NULL || mood->description == NULL)
3556 continue;
3558 path = get_mood_icon_path(mood->mood);
3559 purple_request_field_list_add_icon(f, _(mood->description),
3560 path, (gpointer)mood->mood);
3561 g_free(path);
3563 if (current_mood && !strcmp(current_mood, mood->mood))
3564 purple_request_field_list_add_selected(f, _(mood->description));
3566 purple_request_field_group_add_field(g, f);
3568 purple_request_fields_add_group(fields, g);
3570 /* if the connection allows setting a mood message */
3571 if (gc && (gc->flags & PURPLE_CONNECTION_SUPPORT_MOOD_MESSAGES)) {
3572 g = purple_request_field_group_new(NULL);
3573 f = purple_request_field_string_new("text",
3574 _("Message (optional)"), NULL, FALSE);
3575 purple_request_field_group_add_field(g, f);
3576 purple_request_fields_add_group(fields, g);
3579 purple_request_fields(gc, _("Edit User Mood"), _("Edit User Mood"),
3580 NULL, fields,
3581 _("OK"), G_CALLBACK(edit_mood_cb),
3582 _("Cancel"), NULL,
3583 gc ? purple_connection_get_account(gc) : NULL,
3584 NULL, NULL, gc);
3586 g_free(global_moods);
3589 static void
3590 set_mood_show(void)
3592 set_mood_cb(NULL, NULL);
3595 /***************************************************
3596 * Crap *
3597 ***************************************************/
3598 static GtkItemFactoryEntry blist_menu[] =
3600 /* NOTE: Do not set any accelerator to Control+O. It is mapped by
3601 gtk_blist_key_press_cb to "Get User Info" on the selected buddy. */
3603 /* Buddies menu */
3604 { N_("/_Buddies"), NULL, NULL, 0, "<Branch>", NULL },
3605 { N_("/Buddies/New Instant _Message..."), "<CTL>M", pidgin_dialogs_im, 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW },
3606 { N_("/Buddies/Join a _Chat..."), "<CTL>C", pidgin_blist_joinchat_show, 0, "<StockItem>", PIDGIN_STOCK_CHAT },
3607 { N_("/Buddies/Get User _Info..."), "<CTL>I", pidgin_dialogs_info, 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_USER_INFO },
3608 { N_("/Buddies/View User _Log..."), "<CTL>L", pidgin_dialogs_log, 0, "<Item>", NULL },
3609 { "/Buddies/sep1", NULL, NULL, 0, "<Separator>", NULL },
3610 { N_("/Buddies/Sh_ow"), NULL, NULL, 0, "<Branch>", NULL},
3611 { N_("/Buddies/Show/_Offline Buddies"), NULL, pidgin_blist_edit_mode_cb, 1, "<CheckItem>", NULL },
3612 { N_("/Buddies/Show/_Empty Groups"), NULL, pidgin_blist_show_empty_groups_cb, 1, "<CheckItem>", NULL },
3613 { N_("/Buddies/Show/Buddy _Details"), NULL, pidgin_blist_buddy_details_cb, 1, "<CheckItem>", NULL },
3614 { N_("/Buddies/Show/Idle _Times"), NULL, pidgin_blist_show_idle_time_cb, 1, "<CheckItem>", NULL },
3615 { N_("/Buddies/Show/_Protocol Icons"), NULL, pidgin_blist_show_protocol_icons_cb, 1, "<CheckItem>", NULL },
3616 { N_("/Buddies/_Sort Buddies"), NULL, NULL, 0, "<Branch>", NULL },
3617 { "/Buddies/sep2", NULL, NULL, 0, "<Separator>", NULL },
3618 { N_("/Buddies/_Add Buddy..."), "<CTL>B", pidgin_blist_add_buddy_cb, 0, "<StockItem>", GTK_STOCK_ADD },
3619 { N_("/Buddies/Add C_hat..."), NULL, pidgin_blist_add_chat_cb, 0, "<StockItem>", GTK_STOCK_ADD },
3620 { N_("/Buddies/Add _Group..."), NULL, purple_blist_request_add_group, 0, "<StockItem>", GTK_STOCK_ADD },
3621 { "/Buddies/sep3", NULL, NULL, 0, "<Separator>", NULL },
3622 { N_("/Buddies/_Quit"), "<CTL>Q", purple_core_quit, 0, "<StockItem>", GTK_STOCK_QUIT },
3624 /* Accounts menu */
3625 { N_("/_Accounts"), NULL, NULL, 0, "<Branch>", NULL },
3626 { N_("/Accounts/Manage Accounts"), "<CTL>A", pidgin_accounts_window_show, 0, "<Item>", NULL },
3628 /* Tools */
3629 { N_("/_Tools"), NULL, NULL, 0, "<Branch>", NULL },
3630 { N_("/Tools/Buddy _Pounces"), NULL, pidgin_pounces_manager_show, 1, "<Item>", NULL },
3631 { N_("/Tools/_Certificates"), NULL, pidgin_certmgr_show, 0, "<Item>", NULL },
3632 { N_("/Tools/Custom Smile_ys"), "<CTL>Y", pidgin_smiley_manager_show, 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_SMILEY },
3633 { N_("/Tools/Plu_gins"), "<CTL>U", pidgin_plugin_dialog_show, 2, "<StockItem>", PIDGIN_STOCK_TOOLBAR_PLUGINS },
3634 { N_("/Tools/Pr_eferences"), "<CTL>P", pidgin_prefs_show, 0, "<StockItem>", GTK_STOCK_PREFERENCES },
3635 { N_("/Tools/Pr_ivacy"), NULL, pidgin_privacy_dialog_show, 0, "<Item>", NULL },
3636 { N_("/Tools/Set _Mood"), "<CTL>D", set_mood_show, 0, "<Item>", NULL },
3637 { "/Tools/sep2", NULL, NULL, 0, "<Separator>", NULL },
3638 { N_("/Tools/_File Transfers"), "<CTL>T", pidgin_xfer_dialog_show, 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_TRANSFER },
3639 { N_("/Tools/R_oom List"), NULL, pidgin_roomlist_dialog_show, 0, "<Item>", NULL },
3640 { N_("/Tools/System _Log"), NULL, gtk_blist_show_systemlog_cb, 3, "<Item>", NULL },
3641 { "/Tools/sep3", NULL, NULL, 0, "<Separator>", NULL },
3642 { N_("/Tools/Mute _Sounds"), NULL, pidgin_blist_mute_sounds_cb, 0, "<CheckItem>", NULL },
3643 /* Help */
3644 { N_("/_Help"), NULL, NULL, 0, "<Branch>", NULL },
3645 { N_("/Help/Online _Help"), "F1", gtk_blist_show_onlinehelp_cb, 0, "<StockItem>", GTK_STOCK_HELP },
3646 { "/Help/sep1", NULL, NULL, 0, "<Separator>", NULL },
3647 { N_("/Help/_Build Information"), NULL, pidgin_dialogs_buildinfo, 0, "<Item>", NULL },
3648 { N_("/Help/_Debug Window"), NULL, toggle_debug, 0, "<Item>", NULL },
3649 { N_("/Help/De_veloper Information"), NULL, pidgin_dialogs_developers, 0, "<Item>", NULL },
3650 { N_("/Help/_Plugin Information"), NULL, pidgin_dialogs_plugins_info, 0, "<Item>", NULL },
3651 { N_("/Help/_Translator Information"), NULL, pidgin_dialogs_translators, 0, "<Item>", NULL },
3652 { "/Help/sep2", NULL, NULL, 0, "<Separator>", NULL },
3653 { N_("/Help/_About"), NULL, pidgin_dialogs_about, 4, "<StockItem>", GTK_STOCK_ABOUT },
3656 /*********************************************************
3657 * Private Utility functions *
3658 *********************************************************/
3660 static char *pidgin_get_tooltip_text(PurpleBlistNode *node, gboolean full)
3662 GString *str = g_string_new("");
3663 PurplePlugin *prpl;
3664 PurplePluginProtocolInfo *prpl_info = NULL;
3665 char *tmp;
3667 if (PURPLE_BLIST_NODE_IS_CHAT(node))
3669 PurpleChat *chat;
3670 GList *connections;
3671 GList *cur;
3672 struct proto_chat_entry *pce;
3673 char *name, *value;
3674 PurpleConversation *conv;
3675 PidginBlistNode *bnode = node->ui_data;
3677 chat = (PurpleChat *)node;
3678 prpl = purple_find_prpl(purple_account_get_protocol_id(chat->account));
3679 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
3681 connections = purple_connections_get_all();
3682 if (connections && connections->next)
3684 tmp = g_markup_escape_text(chat->account->username, -1);
3685 g_string_append_printf(str, _("<b>Account:</b> %s"), tmp);
3686 g_free(tmp);
3689 if (bnode && bnode->conv.conv) {
3690 conv = bnode->conv.conv;
3691 } else {
3692 char *chat_name;
3693 if (prpl_info && prpl_info->get_chat_name)
3694 chat_name = prpl_info->get_chat_name(chat->components);
3695 else
3696 chat_name = g_strdup(purple_chat_get_name(chat));
3698 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, chat_name,
3699 chat->account);
3700 g_free(chat_name);
3703 if (conv && !purple_conv_chat_has_left(PURPLE_CONV_CHAT(conv))) {
3704 g_string_append_printf(str, _("\n<b>Occupants:</b> %d"),
3705 g_list_length(purple_conv_chat_get_users(PURPLE_CONV_CHAT(conv))));
3707 if (prpl_info && (prpl_info->options & OPT_PROTO_CHAT_TOPIC)) {
3708 const char *chattopic = purple_conv_chat_get_topic(PURPLE_CONV_CHAT(conv));
3709 char *topic = chattopic ? g_markup_escape_text(chattopic, -1) : NULL;
3710 g_string_append_printf(str, _("\n<b>Topic:</b> %s"), topic ? topic : _("(no topic set)"));
3711 g_free(topic);
3715 if (prpl_info && prpl_info->chat_info != NULL)
3716 cur = prpl_info->chat_info(chat->account->gc);
3717 else
3718 cur = NULL;
3720 while (cur != NULL)
3722 pce = cur->data;
3724 if (!pce->secret && (!pce->required &&
3725 g_hash_table_lookup(chat->components, pce->identifier) == NULL))
3727 tmp = purple_text_strip_mnemonic(pce->label);
3728 name = g_markup_escape_text(tmp, -1);
3729 g_free(tmp);
3730 value = g_markup_escape_text(g_hash_table_lookup(
3731 chat->components, pce->identifier), -1);
3732 g_string_append_printf(str, "\n<b>%s</b> %s",
3733 name ? name : "",
3734 value ? value : "");
3735 g_free(name);
3736 g_free(value);
3739 g_free(pce);
3740 cur = g_list_delete_link(cur, cur);
3743 else if (PURPLE_BLIST_NODE_IS_CONTACT(node) || PURPLE_BLIST_NODE_IS_BUDDY(node))
3745 /* NOTE: THIS FUNCTION IS NO LONGER CALLED FOR CONTACTS.
3746 * It is only called by create_tip_for_node(), and create_tip_for_node() is never called for a contact.
3748 PurpleContact *c;
3749 PurpleBuddy *b;
3750 PurplePresence *presence;
3751 PurpleNotifyUserInfo *user_info;
3752 GList *connections;
3753 char *tmp;
3754 time_t idle_secs, signon;
3756 if (PURPLE_BLIST_NODE_IS_CONTACT(node))
3758 c = (PurpleContact *)node;
3759 b = purple_contact_get_priority_buddy(c);
3761 else
3763 b = (PurpleBuddy *)node;
3764 c = purple_buddy_get_contact(b);
3767 prpl = purple_find_prpl(purple_account_get_protocol_id(b->account));
3768 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
3770 presence = purple_buddy_get_presence(b);
3771 user_info = purple_notify_user_info_new();
3773 /* Account */
3774 connections = purple_connections_get_all();
3775 if (full && connections && connections->next)
3777 tmp = g_markup_escape_text(purple_account_get_username(
3778 purple_buddy_get_account(b)), -1);
3779 purple_notify_user_info_add_pair(user_info, _("Account"), tmp);
3780 g_free(tmp);
3783 /* Alias */
3784 /* If there's not a contact alias, the node is being displayed with
3785 * this alias, so there's no point in showing it in the tooltip. */
3786 if (full && c && b->alias != NULL && b->alias[0] != '\0' &&
3787 (c->alias != NULL && c->alias[0] != '\0') &&
3788 strcmp(c->alias, b->alias) != 0)
3790 tmp = g_markup_escape_text(b->alias, -1);
3791 purple_notify_user_info_add_pair(user_info, _("Buddy Alias"), tmp);
3792 g_free(tmp);
3795 /* Nickname/Server Alias */
3796 /* I'd like to only show this if there's a contact or buddy
3797 * alias, but many people on MSN set long nicknames, which
3798 * get ellipsized, so the only way to see the whole thing is
3799 * to look at the tooltip. */
3800 if (full && b->server_alias != NULL && b->server_alias[0] != '\0')
3802 tmp = g_markup_escape_text(b->server_alias, -1);
3803 purple_notify_user_info_add_pair(user_info, _("Nickname"), tmp);
3804 g_free(tmp);
3807 /* Logged In */
3808 signon = purple_presence_get_login_time(presence);
3809 if (full && PURPLE_BUDDY_IS_ONLINE(b) && signon > 0)
3811 if (signon > time(NULL)) {
3813 * They signed on in the future?! Our local clock
3814 * must be wrong, show the actual date instead of
3815 * "4 days", etc.
3817 tmp = g_strdup(purple_date_format_long(localtime(&signon)));
3818 } else
3819 tmp = purple_str_seconds_to_string(time(NULL) - signon);
3820 purple_notify_user_info_add_pair(user_info, _("Logged In"), tmp);
3821 g_free(tmp);
3824 /* Idle */
3825 if (purple_presence_is_idle(presence))
3827 idle_secs = purple_presence_get_idle_time(presence);
3828 if (idle_secs > 0)
3830 tmp = purple_str_seconds_to_string(time(NULL) - idle_secs);
3831 purple_notify_user_info_add_pair(user_info, _("Idle"), tmp);
3832 g_free(tmp);
3836 /* Last Seen */
3837 if (full && c && !PURPLE_BUDDY_IS_ONLINE(b))
3839 struct _pidgin_blist_node *gtknode = ((PurpleBlistNode *)c)->ui_data;
3840 PurpleBlistNode *bnode;
3841 int lastseen = 0;
3843 if (gtknode && (!gtknode->contact_expanded || PURPLE_BLIST_NODE_IS_CONTACT(node)))
3845 /* We're either looking at a buddy for a collapsed contact or
3846 * an expanded contact itself so we show the most recent
3847 * (largest) last_seen time for any of the buddies under
3848 * the contact. */
3849 for (bnode = ((PurpleBlistNode *)c)->child ; bnode != NULL ; bnode = bnode->next)
3851 int value = purple_blist_node_get_int(bnode, "last_seen");
3852 if (value > lastseen)
3853 lastseen = value;
3856 else
3858 /* We're dealing with a buddy under an expanded contact,
3859 * so we show the last_seen time for the buddy. */
3860 lastseen = purple_blist_node_get_int(&b->node, "last_seen");
3863 if (lastseen > 0)
3865 tmp = purple_str_seconds_to_string(time(NULL) - lastseen);
3866 purple_notify_user_info_add_pair(user_info, _("Last Seen"), tmp);
3867 g_free(tmp);
3872 /* Offline? */
3873 /* FIXME: Why is this status special-cased by the core? --rlaager
3874 * FIXME: Alternatively, why not have the core do all of them? --rlaager */
3875 if (!PURPLE_BUDDY_IS_ONLINE(b)) {
3876 purple_notify_user_info_add_pair(user_info, _("Status"), _("Offline"));
3879 if (purple_account_is_connected(b->account) &&
3880 prpl_info && prpl_info->tooltip_text)
3882 /* Additional text from the PRPL */
3883 prpl_info->tooltip_text(b, user_info, full);
3886 /* These are Easter Eggs. Patches to remove them will be rejected. */
3887 if (!g_ascii_strcasecmp(b->name, "robflynn"))
3888 purple_notify_user_info_add_pair(user_info, _("Description"), _("Spooky"));
3889 if (!g_ascii_strcasecmp(b->name, "seanegn"))
3890 purple_notify_user_info_add_pair(user_info, _("Status"), _("Awesome"));
3891 if (!g_ascii_strcasecmp(b->name, "chipx86"))
3892 purple_notify_user_info_add_pair(user_info, _("Status"), _("Rockin'"));
3894 tmp = purple_notify_user_info_get_text_with_newline(user_info, "\n");
3895 g_string_append(str, tmp);
3896 g_free(tmp);
3898 purple_notify_user_info_destroy(user_info);
3899 } else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
3900 gint count;
3901 PurpleGroup *group = (PurpleGroup*)node;
3902 PurpleNotifyUserInfo *user_info;
3904 user_info = purple_notify_user_info_new();
3906 count = purple_blist_get_group_online_count(group);
3908 if (count != 0) {
3909 /* Online buddies in group */
3910 tmp = g_strdup_printf("%d", count);
3911 purple_notify_user_info_add_pair(user_info,
3912 _("Online Buddies"),
3913 tmp);
3914 g_free(tmp);
3917 count = purple_blist_get_group_size(group, FALSE);
3918 if (count != 0) {
3919 /* Total buddies (from online accounts) in group */
3920 tmp = g_strdup_printf("%d", count);
3921 purple_notify_user_info_add_pair(user_info,
3922 _("Total Buddies"),
3923 tmp);
3924 g_free(tmp);
3927 tmp = purple_notify_user_info_get_text_with_newline(user_info, "\n");
3928 g_string_append(str, tmp);
3929 g_free(tmp);
3931 purple_notify_user_info_destroy(user_info);
3934 purple_signal_emit(pidgin_blist_get_handle(), "drawing-tooltip",
3935 node, str, full);
3937 return g_string_free(str, FALSE);
3940 static GHashTable *cached_emblems;
3942 static void _cleanup_cached_emblem(gpointer data, GObject *obj) {
3943 g_hash_table_remove(cached_emblems, data);
3946 static GdkPixbuf * _pidgin_blist_get_cached_emblem(gchar *path) {
3947 GdkPixbuf *pb = g_hash_table_lookup(cached_emblems, path);
3949 if (pb != NULL) {
3950 /* The caller gets a reference */
3951 g_object_ref(pb);
3952 g_free(path);
3953 } else {
3954 pb = pidgin_pixbuf_new_from_file(path);
3955 if (pb != NULL) {
3956 /* We don't want to own a ref to the pixbuf, but we need to keep clean up. */
3957 /* I'm not sure if it would be better to just keep our ref and not let the emblem ever be destroyed */
3958 g_object_weak_ref(G_OBJECT(pb), _cleanup_cached_emblem, path);
3959 g_hash_table_insert(cached_emblems, path, pb);
3960 } else
3961 g_free(path);
3964 return pb;
3967 GdkPixbuf *
3968 pidgin_blist_get_emblem(PurpleBlistNode *node)
3970 PurpleBuddy *buddy = NULL;
3971 struct _pidgin_blist_node *gtknode = node->ui_data;
3972 PurplePlugin *prpl;
3973 PurplePluginProtocolInfo *prpl_info;
3974 const char *name = NULL;
3975 char *filename, *path;
3976 PurplePresence *p = NULL;
3977 PurpleStatus *tune;
3979 if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
3980 if(!gtknode->contact_expanded) {
3981 buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
3983 } else if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
3984 buddy = (PurpleBuddy*)node;
3985 p = purple_buddy_get_presence(buddy);
3986 if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_MOBILE)) {
3987 /* This emblem comes from the small emoticon set now,
3988 * to reduce duplication. */
3989 path = g_build_filename(DATADIR, "pixmaps", "pidgin", "emotes",
3990 "small", "mobile.png", NULL);
3991 return _pidgin_blist_get_cached_emblem(path);
3994 if (((struct _pidgin_blist_node*)(node->parent->ui_data))->contact_expanded) {
3995 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons"))
3996 return NULL;
3997 return pidgin_create_prpl_icon(((PurpleBuddy*)node)->account, PIDGIN_PRPL_ICON_SMALL);
3999 } else {
4000 return NULL;
4003 g_return_val_if_fail(buddy != NULL, NULL);
4005 if (!purple_privacy_check(buddy->account, purple_buddy_get_name(buddy))) {
4006 path = g_build_filename(DATADIR, "pixmaps", "pidgin", "emblems", "16", "blocked.png", NULL);
4007 return _pidgin_blist_get_cached_emblem(path);
4010 /* If we came through the contact code flow above, we didn't need
4011 * to get the presence until now. */
4012 if (p == NULL)
4013 p = purple_buddy_get_presence(buddy);
4015 if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_MOBILE)) {
4016 /* This emblem comes from the small emoticon set now, to reduce duplication. */
4017 path = g_build_filename(DATADIR, "pixmaps", "pidgin", "emotes", "small", "mobile.png", NULL);
4018 return _pidgin_blist_get_cached_emblem(path);
4021 tune = purple_presence_get_status(p, "tune");
4022 if (tune && purple_status_is_active(tune)) {
4023 /* Only in MSN.
4024 * TODO: Replace "Tune" with generalized "Media" in 3.0. */
4025 if (purple_status_get_attr_string(tune, "game") != NULL) {
4026 path = g_build_filename(DATADIR, "pixmaps", "pidgin", "emblems", "16", "game.png", NULL);
4027 return _pidgin_blist_get_cached_emblem(path);
4029 /* Only in MSN.
4030 * TODO: Replace "Tune" with generalized "Media" in 3.0. */
4031 if (purple_status_get_attr_string(tune, "office") != NULL) {
4032 path = g_build_filename(DATADIR, "pixmaps", "pidgin", "emblems", "16", "office.png", NULL);
4033 return _pidgin_blist_get_cached_emblem(path);
4035 /* Regular old "tune" is the only one in all protocols. */
4036 /* This emblem comes from the small emoticon set now, to reduce duplication. */
4037 path = g_build_filename(DATADIR, "pixmaps", "pidgin", "emotes", "small", "music.png", NULL);
4038 return _pidgin_blist_get_cached_emblem(path);
4041 prpl = purple_find_prpl(purple_account_get_protocol_id(buddy->account));
4042 if (!prpl)
4043 return NULL;
4045 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
4046 if (prpl_info && prpl_info->list_emblem)
4047 name = prpl_info->list_emblem(buddy);
4049 if (name == NULL) {
4050 PurpleStatus *status;
4052 if (!purple_presence_is_status_primitive_active(p, PURPLE_STATUS_MOOD))
4053 return NULL;
4055 status = purple_presence_get_status(p, "mood");
4056 name = purple_status_get_attr_string(status, PURPLE_MOOD_NAME);
4058 if (!(name && *name))
4059 return NULL;
4061 path = get_mood_icon_path(name);
4062 } else {
4063 filename = g_strdup_printf("%s.png", name);
4064 path = g_build_filename(DATADIR, "pixmaps", "pidgin", "emblems", "16", filename, NULL);
4065 g_free(filename);
4068 /* _pidgin_blist_get_cached_emblem() assumes ownership of path */
4069 return _pidgin_blist_get_cached_emblem(path);
4073 GdkPixbuf *
4074 pidgin_blist_get_status_icon(PurpleBlistNode *node, PidginStatusIconSize size)
4076 GdkPixbuf *ret;
4077 const char *icon = NULL;
4078 struct _pidgin_blist_node *gtknode = node->ui_data;
4079 struct _pidgin_blist_node *gtkbuddynode = NULL;
4080 PurpleBuddy *buddy = NULL;
4081 PurpleChat *chat = NULL;
4082 GtkIconSize icon_size = gtk_icon_size_from_name((size == PIDGIN_STATUS_ICON_LARGE) ? PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL :
4083 PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC);
4085 if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
4086 if(!gtknode->contact_expanded) {
4087 buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
4088 if (buddy != NULL)
4089 gtkbuddynode = ((PurpleBlistNode*)buddy)->ui_data;
4091 } else if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
4092 buddy = (PurpleBuddy*)node;
4093 gtkbuddynode = node->ui_data;
4094 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
4095 chat = (PurpleChat*)node;
4096 } else {
4097 return NULL;
4100 if(buddy || chat) {
4101 PurpleAccount *account;
4102 PurplePlugin *prpl;
4104 if(buddy)
4105 account = buddy->account;
4106 else
4107 account = chat->account;
4109 prpl = purple_find_prpl(purple_account_get_protocol_id(account));
4110 if(!prpl)
4111 return NULL;
4114 if(buddy) {
4115 PurpleConversation *conv = find_conversation_with_buddy(buddy);
4116 PurplePresence *p;
4117 gboolean trans;
4119 if(conv != NULL) {
4120 PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
4121 if (gtkconv == NULL && size == PIDGIN_STATUS_ICON_SMALL) {
4122 PidginBlistNode *ui = buddy->node.ui_data;
4123 if (ui == NULL || (ui->conv.flags & PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE))
4124 return gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview),
4125 PIDGIN_STOCK_STATUS_MESSAGE, icon_size, "GtkTreeView");
4129 p = purple_buddy_get_presence(buddy);
4130 trans = purple_presence_is_idle(p);
4132 if (PURPLE_BUDDY_IS_ONLINE(buddy) && gtkbuddynode && gtkbuddynode->recent_signonoff)
4133 icon = PIDGIN_STOCK_STATUS_LOGIN;
4134 else if (gtkbuddynode && gtkbuddynode->recent_signonoff)
4135 icon = PIDGIN_STOCK_STATUS_LOGOUT;
4136 else if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_UNAVAILABLE))
4137 if (trans)
4138 icon = PIDGIN_STOCK_STATUS_BUSY_I;
4139 else
4140 icon = PIDGIN_STOCK_STATUS_BUSY;
4141 else if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_AWAY))
4142 if (trans)
4143 icon = PIDGIN_STOCK_STATUS_AWAY_I;
4144 else
4145 icon = PIDGIN_STOCK_STATUS_AWAY;
4146 else if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_EXTENDED_AWAY))
4147 if (trans)
4148 icon = PIDGIN_STOCK_STATUS_XA_I;
4149 else
4150 icon = PIDGIN_STOCK_STATUS_XA;
4151 else if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_OFFLINE))
4152 icon = PIDGIN_STOCK_STATUS_OFFLINE;
4153 else if (trans)
4154 icon = PIDGIN_STOCK_STATUS_AVAILABLE_I;
4155 else if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_INVISIBLE))
4156 icon = PIDGIN_STOCK_STATUS_INVISIBLE;
4157 else
4158 icon = PIDGIN_STOCK_STATUS_AVAILABLE;
4159 } else if (chat) {
4160 icon = PIDGIN_STOCK_STATUS_CHAT;
4161 } else {
4162 icon = PIDGIN_STOCK_STATUS_PERSON;
4165 ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), icon,
4166 icon_size, "GtkTreeView");
4167 return ret;
4170 static const char *
4171 theme_font_get_color_default(PidginThemeFont *font, const char *def)
4173 const char *ret;
4174 if (!font || !(ret = pidgin_theme_font_get_color_describe(font)))
4175 ret = def;
4176 return ret;
4179 static const char *
4180 theme_font_get_face_default(PidginThemeFont *font, const char *def)
4182 const char *ret;
4183 if (!font || !(ret = pidgin_theme_font_get_font_face(font)))
4184 ret = def;
4185 return ret;
4188 gchar *
4189 pidgin_blist_get_name_markup(PurpleBuddy *b, gboolean selected, gboolean aliased)
4191 const char *name, *name_color, *name_font, *status_color, *status_font;
4192 char *text = NULL;
4193 PurplePlugin *prpl;
4194 PurplePluginProtocolInfo *prpl_info = NULL;
4195 PurpleContact *contact;
4196 PurplePresence *presence;
4197 struct _pidgin_blist_node *gtkcontactnode = NULL;
4198 char *idletime = NULL, *statustext = NULL, *nametext = NULL;
4199 PurpleConversation *conv = find_conversation_with_buddy(b);
4200 gboolean hidden_conv = FALSE;
4201 gboolean biglist = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons");
4202 PidginThemeFont *statusfont = NULL, *namefont = NULL;
4203 PidginBlistTheme *theme;
4205 if (conv != NULL) {
4206 PidginBlistNode *ui = b->node.ui_data;
4207 if (ui) {
4208 if (ui->conv.flags & PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE)
4209 hidden_conv = TRUE;
4210 } else {
4211 if (PIDGIN_CONVERSATION(conv) == NULL)
4212 hidden_conv = TRUE;
4216 /* XXX Good luck cleaning up this crap */
4217 contact = PURPLE_CONTACT(PURPLE_BLIST_NODE(b)->parent);
4218 if(contact)
4219 gtkcontactnode = purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(contact));
4221 /* Name */
4222 if (gtkcontactnode && !gtkcontactnode->contact_expanded && contact->alias)
4223 name = contact->alias;
4224 else
4225 name = purple_buddy_get_alias(b);
4227 /* Raise a contact pre-draw signal here. THe callback will return an
4228 * escaped version of the name. */
4229 nametext = purple_signal_emit_return_1(pidgin_blist_get_handle(), "drawing-buddy", b);
4231 if(!nametext)
4232 nametext = g_markup_escape_text(name, strlen(name));
4234 presence = purple_buddy_get_presence(b);
4236 /* Name is all that is needed */
4237 if (!aliased || biglist) {
4239 /* Status Info */
4240 prpl = purple_find_prpl(purple_account_get_protocol_id(b->account));
4242 if (prpl != NULL)
4243 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
4245 if (prpl_info && prpl_info->status_text && b->account->gc) {
4246 char *tmp = prpl_info->status_text(b);
4247 const char *end;
4249 if(tmp && !g_utf8_validate(tmp, -1, &end)) {
4250 char *new = g_strndup(tmp,
4251 g_utf8_pointer_to_offset(tmp, end));
4252 g_free(tmp);
4253 tmp = new;
4255 if(tmp) {
4256 g_strdelimit(tmp, "\n", ' ');
4257 purple_str_strip_char(tmp, '\r');
4259 statustext = tmp;
4262 if(!purple_presence_is_online(presence) && !statustext)
4263 statustext = g_strdup(_("Offline"));
4265 /* Idle Text */
4266 if (purple_presence_is_idle(presence) && purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_idle_time")) {
4267 time_t idle_secs = purple_presence_get_idle_time(presence);
4269 if (idle_secs > 0) {
4270 int iday, ihrs, imin;
4271 time_t t;
4273 time(&t);
4274 iday = (t - idle_secs) / (24 * 60 * 60);
4275 ihrs = ((t - idle_secs) / 60 / 60) % 24;
4276 imin = ((t - idle_secs) / 60) % 60;
4278 if (iday)
4279 idletime = g_strdup_printf(_("Idle %dd %dh %02dm"), iday, ihrs, imin);
4280 else if (ihrs)
4281 idletime = g_strdup_printf(_("Idle %dh %02dm"), ihrs, imin);
4282 else
4283 idletime = g_strdup_printf(_("Idle %dm"), imin);
4285 } else
4286 idletime = g_strdup(_("Idle"));
4290 /* choose the colors of the text */
4291 theme = pidgin_blist_get_theme();
4292 name_color = NULL;
4294 if (theme) {
4295 if (purple_presence_is_idle(presence)) {
4296 namefont = statusfont = pidgin_blist_theme_get_idle_text_info(theme);
4297 name_color = "dim grey";
4298 } else if (!purple_presence_is_online(presence)) {
4299 namefont = pidgin_blist_theme_get_offline_text_info(theme);
4300 name_color = "dim grey";
4301 statusfont = pidgin_blist_theme_get_status_text_info(theme);
4302 } else if (purple_presence_is_available(presence)) {
4303 namefont = pidgin_blist_theme_get_online_text_info(theme);
4304 statusfont = pidgin_blist_theme_get_status_text_info(theme);
4305 } else {
4306 namefont = pidgin_blist_theme_get_away_text_info(theme);
4307 statusfont = pidgin_blist_theme_get_status_text_info(theme);
4309 } else {
4310 if (!selected
4311 && (purple_presence_is_idle(presence)
4312 || !purple_presence_is_online(presence)))
4314 name_color = "dim grey";
4318 name_color = theme_font_get_color_default(namefont, name_color);
4319 name_font = theme_font_get_face_default(namefont, "");
4321 status_color = theme_font_get_color_default(statusfont, "dim grey");
4322 status_font = theme_font_get_face_default(statusfont, "");
4324 if (aliased && selected) {
4325 if (theme) {
4326 name_color = "black";
4327 status_color = "black";
4328 } else {
4329 name_color = NULL;
4330 status_color = NULL;
4334 if (hidden_conv) {
4335 char *tmp = nametext;
4336 nametext = g_strdup_printf("<b>%s</b>", tmp);
4337 g_free(tmp);
4340 /* Put it all together */
4341 if ((!aliased || biglist) && (statustext || idletime)) {
4342 /* using <span size='smaller'> breaks the status, so it must be seperated into <small><span>*/
4343 if (name_color) {
4344 text = g_strdup_printf("<span font_desc='%s' foreground='%s'>%s</span>\n"
4345 "<small><span font_desc='%s' foreground='%s'>%s%s%s</span></small>",
4346 name_font, name_color, nametext, status_font, status_color,
4347 idletime != NULL ? idletime : "",
4348 (idletime != NULL && statustext != NULL) ? " - " : "",
4349 statustext != NULL ? statustext : "");
4350 } else if (status_color) {
4351 text = g_strdup_printf("<span font_desc='%s'>%s</span>\n"
4352 "<small><span font_desc='%s' foreground='%s'>%s%s%s</span></small>",
4353 name_font, nametext, status_font, status_color,
4354 idletime != NULL ? idletime : "",
4355 (idletime != NULL && statustext != NULL) ? " - " : "",
4356 statustext != NULL ? statustext : "");
4357 } else {
4358 text = g_strdup_printf("<span font_desc='%s'>%s</span>\n"
4359 "<small><span font_desc='%s'>%s%s%s</span></small>",
4360 name_font, nametext, status_font,
4361 idletime != NULL ? idletime : "",
4362 (idletime != NULL && statustext != NULL) ? " - " : "",
4363 statustext != NULL ? statustext : "");
4365 } else {
4366 if (name_color) {
4367 text = g_strdup_printf("<span font_desc='%s' color='%s'>%s</span>",
4368 name_font, name_color, nametext);
4369 } else {
4370 text = g_strdup_printf("<span font_desc='%s'>%s</span>", name_font,
4371 nametext);
4374 g_free(nametext);
4375 g_free(statustext);
4376 g_free(idletime);
4378 if (hidden_conv) {
4379 char *tmp = text;
4380 text = g_strdup_printf("<b>%s</b>", tmp);
4381 g_free(tmp);
4384 return text;
4387 static void pidgin_blist_restore_position(void)
4389 int blist_x, blist_y, blist_width, blist_height;
4391 blist_width = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/width");
4393 /* if the window exists, is hidden, we're saving positions, and the
4394 * position is sane... */
4395 if (gtkblist && gtkblist->window &&
4396 !GTK_WIDGET_VISIBLE(gtkblist->window) && blist_width != 0) {
4398 blist_x = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/x");
4399 blist_y = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/y");
4400 blist_height = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/height");
4402 /* ...check position is on screen... */
4403 if (blist_x >= gdk_screen_width())
4404 blist_x = gdk_screen_width() - 100;
4405 else if (blist_x + blist_width < 0)
4406 blist_x = 100;
4408 if (blist_y >= gdk_screen_height())
4409 blist_y = gdk_screen_height() - 100;
4410 else if (blist_y + blist_height < 0)
4411 blist_y = 100;
4413 /* ...and move it back. */
4414 gtk_window_move(GTK_WINDOW(gtkblist->window), blist_x, blist_y);
4415 gtk_window_resize(GTK_WINDOW(gtkblist->window), blist_width, blist_height);
4416 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/list_maximized"))
4417 gtk_window_maximize(GTK_WINDOW(gtkblist->window));
4421 static gboolean pidgin_blist_refresh_timer(PurpleBuddyList *list)
4423 PurpleBlistNode *gnode, *cnode;
4425 if (gtk_blist_visibility == GDK_VISIBILITY_FULLY_OBSCURED
4426 || !GTK_WIDGET_VISIBLE(gtkblist->window))
4427 return TRUE;
4429 for(gnode = list->root; gnode; gnode = gnode->next) {
4430 if(!PURPLE_BLIST_NODE_IS_GROUP(gnode))
4431 continue;
4432 for(cnode = gnode->child; cnode; cnode = cnode->next) {
4433 if(PURPLE_BLIST_NODE_IS_CONTACT(cnode)) {
4434 PurpleBuddy *buddy;
4436 buddy = purple_contact_get_priority_buddy((PurpleContact*)cnode);
4438 if (buddy &&
4439 purple_presence_is_idle(purple_buddy_get_presence(buddy)))
4440 pidgin_blist_update_contact(list, (PurpleBlistNode*)buddy);
4445 /* keep on going */
4446 return TRUE;
4449 static void pidgin_blist_hide_node(PurpleBuddyList *list, PurpleBlistNode *node, gboolean update)
4451 struct _pidgin_blist_node *gtknode = (struct _pidgin_blist_node *)node->ui_data;
4452 GtkTreeIter iter;
4454 if (!gtknode || !gtknode->row || !gtkblist)
4455 return;
4457 if(gtkblist->selected_node == node)
4458 gtkblist->selected_node = NULL;
4459 if (get_iter_from_node(node, &iter)) {
4460 gtk_tree_store_remove(gtkblist->treemodel, &iter);
4461 if(update && (PURPLE_BLIST_NODE_IS_CONTACT(node) ||
4462 PURPLE_BLIST_NODE_IS_BUDDY(node) || PURPLE_BLIST_NODE_IS_CHAT(node))) {
4463 pidgin_blist_update(list, node->parent);
4466 gtk_tree_row_reference_free(gtknode->row);
4467 gtknode->row = NULL;
4470 static const char *require_connection[] =
4472 N_("/Buddies/New Instant Message..."),
4473 N_("/Buddies/Join a Chat..."),
4474 N_("/Buddies/Get User Info..."),
4475 N_("/Buddies/Add Buddy..."),
4476 N_("/Buddies/Add Chat..."),
4477 N_("/Buddies/Add Group..."),
4480 static const int require_connection_size = sizeof(require_connection)
4481 / sizeof(*require_connection);
4484 * Rebuild dynamic menus and make menu items sensitive/insensitive
4485 * where appropriate.
4487 static void
4488 update_menu_bar(PidginBuddyList *gtkblist)
4490 GtkWidget *widget;
4491 gboolean sensitive;
4492 int i;
4494 g_return_if_fail(gtkblist != NULL);
4496 pidgin_blist_update_accounts_menu();
4498 sensitive = (purple_connections_get_all() != NULL);
4500 for (i = 0; i < require_connection_size; i++)
4502 widget = gtk_item_factory_get_widget(gtkblist->ift, require_connection[i]);
4503 gtk_widget_set_sensitive(widget, sensitive);
4506 widget = gtk_item_factory_get_widget(gtkblist->ift, N_("/Buddies/Join a Chat..."));
4507 gtk_widget_set_sensitive(widget, pidgin_blist_joinchat_is_showable());
4509 widget = gtk_item_factory_get_widget(gtkblist->ift, N_("/Buddies/Add Chat..."));
4510 gtk_widget_set_sensitive(widget, pidgin_blist_joinchat_is_showable());
4512 widget = gtk_item_factory_get_widget(gtkblist->ift, N_("/Tools/Privacy"));
4513 gtk_widget_set_sensitive(widget, sensitive);
4515 widget = gtk_item_factory_get_widget(gtkblist->ift, N_("/Tools/Room List"));
4516 gtk_widget_set_sensitive(widget, pidgin_roomlist_is_showable());
4519 static void
4520 sign_on_off_cb(PurpleConnection *gc, PurpleBuddyList *blist)
4522 PidginBuddyList *gtkblist = PIDGIN_BLIST(blist);
4524 update_menu_bar(gtkblist);
4527 static void
4528 plugin_changed_cb(PurplePlugin *p, gpointer data)
4530 pidgin_blist_update_plugin_actions();
4533 static void
4534 unseen_conv_menu(void)
4536 static GtkWidget *menu = NULL;
4537 GList *convs = NULL;
4538 GList *chats, *ims;
4540 if (menu) {
4541 gtk_widget_destroy(menu);
4542 menu = NULL;
4545 ims = pidgin_conversations_find_unseen_list(PURPLE_CONV_TYPE_IM,
4546 PIDGIN_UNSEEN_TEXT, FALSE, 0);
4548 chats = pidgin_conversations_find_unseen_list(PURPLE_CONV_TYPE_CHAT,
4549 PIDGIN_UNSEEN_NICK, FALSE, 0);
4551 if(ims && chats)
4552 convs = g_list_concat(ims, chats);
4553 else if(ims && !chats)
4554 convs = ims;
4555 else if(!ims && chats)
4556 convs = chats;
4558 if (!convs)
4559 /* no conversations added, don't show the menu */
4560 return;
4562 menu = gtk_menu_new();
4564 pidgin_conversations_fill_menu(menu, convs);
4565 g_list_free(convs);
4566 gtk_widget_show_all(menu);
4567 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 3,
4568 gtk_get_current_event_time());
4571 static gboolean
4572 menutray_press_cb(GtkWidget *widget, GdkEventButton *event)
4574 GList *convs;
4576 switch (event->button) {
4577 case 1:
4578 convs = pidgin_conversations_find_unseen_list(PURPLE_CONV_TYPE_IM,
4579 PIDGIN_UNSEEN_TEXT, FALSE, 1);
4581 if(!convs)
4582 convs = pidgin_conversations_find_unseen_list(PURPLE_CONV_TYPE_CHAT,
4583 PIDGIN_UNSEEN_NICK, FALSE, 1);
4584 if (convs) {
4585 pidgin_conv_present_conversation((PurpleConversation*)convs->data);
4586 g_list_free(convs);
4588 break;
4589 case 3:
4590 unseen_conv_menu();
4591 break;
4593 return TRUE;
4596 static void
4597 conversation_updated_cb(PurpleConversation *conv, PurpleConvUpdateType type,
4598 PidginBuddyList *gtkblist)
4600 GList *convs = NULL;
4601 GList *ims, *chats;
4602 GList *l = NULL;
4604 if (type != PURPLE_CONV_UPDATE_UNSEEN)
4605 return;
4607 if(conv->account != NULL && conv->name != NULL) {
4608 PurpleBuddy *buddy = purple_find_buddy(conv->account, conv->name);
4609 if(buddy != NULL)
4610 pidgin_blist_update_buddy(NULL, (PurpleBlistNode *)buddy, TRUE);
4613 if (gtkblist->menutrayicon) {
4614 gtk_widget_destroy(gtkblist->menutrayicon);
4615 gtkblist->menutrayicon = NULL;
4618 ims = pidgin_conversations_find_unseen_list(PURPLE_CONV_TYPE_IM,
4619 PIDGIN_UNSEEN_TEXT, FALSE, 0);
4621 chats = pidgin_conversations_find_unseen_list(PURPLE_CONV_TYPE_CHAT,
4622 PIDGIN_UNSEEN_NICK, FALSE, 0);
4624 if(ims && chats)
4625 convs = g_list_concat(ims, chats);
4626 else if(ims && !chats)
4627 convs = ims;
4628 else if(!ims && chats)
4629 convs = chats;
4631 if (convs) {
4632 GtkWidget *img = NULL;
4633 GString *tooltip_text = NULL;
4635 tooltip_text = g_string_new("");
4636 l = convs;
4637 while (l != NULL) {
4638 int count = 0;
4639 PidginConversation *gtkconv = PIDGIN_CONVERSATION((PurpleConversation *)l->data);
4641 if(gtkconv)
4642 count = gtkconv->unseen_count;
4643 else if(purple_conversation_get_data(l->data, "unseen-count"))
4644 count = GPOINTER_TO_INT(purple_conversation_get_data(l->data, "unseen-count"));
4646 g_string_append_printf(tooltip_text,
4647 ngettext("%d unread message from %s\n", "%d unread messages from %s\n", count),
4648 count, purple_conversation_get_title(l->data));
4649 l = l->next;
4651 if(tooltip_text->len > 0) {
4652 /* get rid of the last newline */
4653 g_string_truncate(tooltip_text, tooltip_text->len -1);
4654 img = gtk_image_new_from_stock(PIDGIN_STOCK_TOOLBAR_PENDING,
4655 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL));
4657 gtkblist->menutrayicon = gtk_event_box_new();
4658 gtk_container_add(GTK_CONTAINER(gtkblist->menutrayicon), img);
4659 gtk_widget_show(img);
4660 gtk_widget_show(gtkblist->menutrayicon);
4661 g_signal_connect(G_OBJECT(gtkblist->menutrayicon), "button-press-event", G_CALLBACK(menutray_press_cb), NULL);
4663 pidgin_menu_tray_append(PIDGIN_MENU_TRAY(gtkblist->menutray), gtkblist->menutrayicon, tooltip_text->str);
4665 g_string_free(tooltip_text, TRUE);
4666 g_list_free(convs);
4670 static void
4671 conversation_deleting_cb(PurpleConversation *conv, PidginBuddyList *gtkblist)
4673 conversation_updated_cb(conv, PURPLE_CONV_UPDATE_UNSEEN, gtkblist);
4676 static void
4677 conversation_deleted_update_ui_cb(PurpleConversation *conv, struct _pidgin_blist_node *ui)
4679 if (ui->conv.conv != conv)
4680 return;
4681 ui->conv.conv = NULL;
4682 ui->conv.flags = 0;
4683 ui->conv.last_message = 0;
4686 static void
4687 written_msg_update_ui_cb(PurpleAccount *account, const char *who, const char *message,
4688 PurpleConversation *conv, PurpleMessageFlags flag, PurpleBlistNode *node)
4690 PidginBlistNode *ui = node->ui_data;
4691 if (ui->conv.conv != conv || !pidgin_conv_is_hidden(PIDGIN_CONVERSATION(conv)) ||
4692 !(flag & (PURPLE_MESSAGE_SEND | PURPLE_MESSAGE_RECV)))
4693 return;
4694 ui->conv.flags |= PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE;
4695 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT
4696 && (flag & PURPLE_MESSAGE_NICK))
4697 ui->conv.flags |= PIDGIN_BLIST_CHAT_HAS_PENDING_MESSAGE_WITH_NICK;
4699 ui->conv.last_message = time(NULL); /* XXX: for lack of better data */
4700 pidgin_blist_update(purple_get_blist(), node);
4703 static void
4704 displayed_msg_update_ui_cb(PidginConversation *gtkconv, PurpleBlistNode *node)
4706 PidginBlistNode *ui = node->ui_data;
4707 if (ui->conv.conv != gtkconv->active_conv)
4708 return;
4709 ui->conv.flags &= ~(PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE |
4710 PIDGIN_BLIST_CHAT_HAS_PENDING_MESSAGE_WITH_NICK);
4711 pidgin_blist_update(purple_get_blist(), node);
4714 static void
4715 conversation_created_cb(PurpleConversation *conv, PidginBuddyList *gtkblist)
4717 switch (conv->type) {
4718 case PURPLE_CONV_TYPE_IM:
4720 GSList *buddies = purple_find_buddies(conv->account, conv->name);
4721 while (buddies) {
4722 PurpleBlistNode *buddy = buddies->data;
4723 struct _pidgin_blist_node *ui = buddy->ui_data;
4724 buddies = g_slist_delete_link(buddies, buddies);
4725 if (!ui)
4726 continue;
4727 ui->conv.conv = conv;
4728 ui->conv.flags = 0;
4729 ui->conv.last_message = 0;
4730 purple_signal_connect(purple_conversations_get_handle(), "deleting-conversation",
4731 ui, PURPLE_CALLBACK(conversation_deleted_update_ui_cb), ui);
4732 purple_signal_connect(purple_conversations_get_handle(), "wrote-im-msg",
4733 ui, PURPLE_CALLBACK(written_msg_update_ui_cb), buddy);
4734 purple_signal_connect(pidgin_conversations_get_handle(), "conversation-displayed",
4735 ui, PURPLE_CALLBACK(displayed_msg_update_ui_cb), buddy);
4738 break;
4739 case PURPLE_CONV_TYPE_CHAT:
4741 PurpleChat *chat = purple_blist_find_chat(conv->account, conv->name);
4742 struct _pidgin_blist_node *ui;
4743 if (!chat)
4744 break;
4745 ui = chat->node.ui_data;
4746 if (!ui)
4747 break;
4748 ui->conv.conv = conv;
4749 ui->conv.flags = 0;
4750 ui->conv.last_message = 0;
4751 purple_signal_connect(purple_conversations_get_handle(), "deleting-conversation",
4752 ui, PURPLE_CALLBACK(conversation_deleted_update_ui_cb), ui);
4753 purple_signal_connect(purple_conversations_get_handle(), "wrote-chat-msg",
4754 ui, PURPLE_CALLBACK(written_msg_update_ui_cb), chat);
4755 purple_signal_connect(pidgin_conversations_get_handle(), "conversation-displayed",
4756 ui, PURPLE_CALLBACK(displayed_msg_update_ui_cb), chat);
4758 break;
4759 default:
4760 break;
4764 /**********************************************************************************
4765 * Public API Functions *
4766 **********************************************************************************/
4768 static void pidgin_blist_new_list(PurpleBuddyList *blist)
4770 PidginBuddyList *gtkblist;
4772 gtkblist = g_new0(PidginBuddyList, 1);
4773 gtkblist->connection_errors = g_hash_table_new_full(g_direct_hash,
4774 g_direct_equal, NULL, g_free);
4775 gtkblist->priv = g_new0(PidginBuddyListPrivate, 1);
4777 blist->ui_data = gtkblist;
4780 static void pidgin_blist_new_node(PurpleBlistNode *node)
4782 node->ui_data = g_new0(struct _pidgin_blist_node, 1);
4785 gboolean pidgin_blist_node_is_contact_expanded(PurpleBlistNode *node)
4787 if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
4788 node = node->parent;
4789 if (node == NULL)
4790 return FALSE;
4793 g_return_val_if_fail(PURPLE_BLIST_NODE_IS_CONTACT(node), FALSE);
4795 return ((struct _pidgin_blist_node *)node->ui_data)->contact_expanded;
4798 enum {
4799 DRAG_BUDDY,
4800 DRAG_ROW,
4801 DRAG_VCARD,
4802 DRAG_TEXT,
4803 DRAG_URI,
4804 NUM_TARGETS
4807 static const char *
4808 item_factory_translate_func (const char *path, gpointer func_data)
4810 return _((char *)path);
4813 void pidgin_blist_setup_sort_methods()
4815 const char *id;
4817 pidgin_blist_sort_method_reg("none", _("Manually"), sort_method_none);
4818 pidgin_blist_sort_method_reg("alphabetical", _("Alphabetically"), sort_method_alphabetical);
4819 pidgin_blist_sort_method_reg("status", _("By status"), sort_method_status);
4820 pidgin_blist_sort_method_reg("log_size", _("By recent log activity"), sort_method_log_activity);
4822 id = purple_prefs_get_string(PIDGIN_PREFS_ROOT "/blist/sort_type");
4823 if (id == NULL) {
4824 purple_debug_warning("gtkblist", "Sort method was NULL, resetting to alphabetical\n");
4825 id = "alphabetical";
4827 pidgin_blist_sort_method_set(id);
4830 static void _prefs_change_redo_list(const char *name, PurplePrefType type,
4831 gconstpointer val, gpointer data)
4833 GtkTreeSelection *sel;
4834 GtkTreeIter iter;
4835 PurpleBlistNode *node = NULL;
4837 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist->treeview));
4838 if (gtk_tree_selection_get_selected(sel, NULL, &iter))
4840 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
4843 redo_buddy_list(purple_get_blist(), FALSE, FALSE);
4844 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(gtkblist->treeview));
4846 if (node)
4848 struct _pidgin_blist_node *gtknode;
4849 GtkTreePath *path;
4851 gtknode = node->ui_data;
4852 if (gtknode && gtknode->row)
4854 path = gtk_tree_row_reference_get_path(gtknode->row);
4855 gtk_tree_selection_select_path(sel, path);
4856 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(gtkblist->treeview), path, NULL, FALSE, 0, 0);
4857 gtk_tree_path_free(path);
4862 static void _prefs_change_sort_method(const char *pref_name, PurplePrefType type,
4863 gconstpointer val, gpointer data)
4865 if(!strcmp(pref_name, PIDGIN_PREFS_ROOT "/blist/sort_type"))
4866 pidgin_blist_sort_method_set(val);
4869 static gboolean pidgin_blist_select_notebook_page_cb(gpointer user_data)
4871 PidginBuddyList *gtkblist = (PidginBuddyList *)user_data;
4872 int errors = 0;
4873 GList *list = NULL;
4874 PidginBuddyListPrivate *priv;
4876 priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
4878 /* this is far too ugly thanks to me not wanting to fix #3989 properly right now */
4879 if (priv->error_scrollbook != NULL) {
4880 errors = gtk_notebook_get_n_pages(GTK_NOTEBOOK(priv->error_scrollbook->notebook));
4882 if ((list = purple_accounts_get_all_active()) != NULL || errors) {
4883 gtk_notebook_set_current_page(GTK_NOTEBOOK(gtkblist->notebook), 1);
4884 g_list_free(list);
4885 } else
4886 gtk_notebook_set_current_page(GTK_NOTEBOOK(gtkblist->notebook), 0);
4888 return FALSE;
4891 static void pidgin_blist_select_notebook_page(PidginBuddyList *gtkblist)
4893 purple_timeout_add(0, pidgin_blist_select_notebook_page_cb, gtkblist);
4896 static void account_modified(PurpleAccount *account, PidginBuddyList *gtkblist)
4898 if (!gtkblist)
4899 return;
4901 pidgin_blist_select_notebook_page(gtkblist);
4902 update_menu_bar(gtkblist);
4905 static void
4906 account_actions_changed(PurpleAccount *account, gpointer data)
4908 pidgin_blist_update_accounts_menu();
4911 static void
4912 account_status_changed(PurpleAccount *account, PurpleStatus *old,
4913 PurpleStatus *new, PidginBuddyList *gtkblist)
4915 if (!gtkblist)
4916 return;
4918 account_modified(account, gtkblist);
4921 static gboolean
4922 gtk_blist_window_key_press_cb(GtkWidget *w, GdkEventKey *event, PidginBuddyList *gtkblist)
4924 GtkWidget *widget;
4926 if (!gtkblist)
4927 return FALSE;
4929 /* clear any tooltips */
4930 pidgin_blist_tooltip_destroy();
4932 widget = gtk_window_get_focus(GTK_WINDOW(gtkblist->window));
4934 if (GTK_IS_IMHTML(widget) || GTK_IS_ENTRY(widget)) {
4935 if (gtk_bindings_activate(GTK_OBJECT(widget), event->keyval, event->state))
4936 return TRUE;
4938 return FALSE;
4941 static gboolean
4942 headline_box_enter_cb(GtkWidget *widget, GdkEventCrossing *event, PidginBuddyList *gtkblist)
4944 gdk_window_set_cursor(widget->window, gtkblist->hand_cursor);
4945 return FALSE;
4948 static gboolean
4949 headline_box_leave_cb(GtkWidget *widget, GdkEventCrossing *event, PidginBuddyList *gtkblist)
4951 gdk_window_set_cursor(widget->window, gtkblist->arrow_cursor);
4952 return FALSE;
4955 static void
4956 reset_headline(PidginBuddyList *gtkblist)
4958 gtkblist->headline_callback = NULL;
4959 gtkblist->headline_data = NULL;
4960 gtkblist->headline_destroy = NULL;
4961 pidgin_set_urgent(GTK_WINDOW(gtkblist->window), FALSE);
4964 static gboolean
4965 headline_click_callback(gpointer unused)
4967 if (gtkblist->headline_callback)
4968 ((GSourceFunc) gtkblist->headline_callback)(gtkblist->headline_data);
4969 reset_headline(gtkblist);
4970 return FALSE;
4973 static gboolean
4974 headline_close_press_cb(GtkButton *button, PidginBuddyList *gtkblist)
4976 gtk_widget_hide(gtkblist->headline_hbox);
4977 return FALSE;
4980 static gboolean
4981 headline_box_press_cb(GtkWidget *widget, GdkEventButton *event, PidginBuddyList *gtkblist)
4983 gtk_widget_hide(gtkblist->headline_hbox);
4984 if (gtkblist->headline_callback)
4985 g_idle_add(headline_click_callback, NULL);
4986 else {
4987 if (gtkblist->headline_destroy)
4988 gtkblist->headline_destroy(gtkblist->headline_data);
4989 reset_headline(gtkblist);
4991 return TRUE;
4994 /***********************************/
4995 /* Connection error handling stuff */
4996 /***********************************/
4998 #define OBJECT_DATA_KEY_ACCOUNT "account"
4999 #define DO_NOT_CLEAR_ERROR "do-not-clear-error"
5001 static gboolean
5002 find_account_widget(GObject *widget,
5003 PurpleAccount *account)
5005 if (g_object_get_data(widget, OBJECT_DATA_KEY_ACCOUNT) == account)
5006 return 0; /* found */
5007 else
5008 return 1;
5011 static void
5012 pack_prpl_icon_start(GtkWidget *box,
5013 PurpleAccount *account)
5015 GdkPixbuf *pixbuf;
5016 GtkWidget *image;
5018 pixbuf = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL);
5019 if (pixbuf != NULL) {
5020 image = gtk_image_new_from_pixbuf(pixbuf);
5021 g_object_unref(pixbuf);
5023 gtk_box_pack_start(GTK_BOX(box), image, FALSE, FALSE, 0);
5027 static void
5028 add_error_dialog(PidginBuddyList *gtkblist,
5029 GtkWidget *dialog)
5031 PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
5032 gtk_container_add(GTK_CONTAINER(priv->error_scrollbook), dialog);
5035 static GtkWidget *
5036 find_child_widget_by_account(GtkContainer *container,
5037 PurpleAccount *account)
5039 GList *l = NULL;
5040 GList *children = NULL;
5041 GtkWidget *ret = NULL;
5042 /* XXX: Workaround for the currently incomplete implementation of PidginScrollBook */
5043 if (PIDGIN_IS_SCROLL_BOOK(container))
5044 container = GTK_CONTAINER(PIDGIN_SCROLL_BOOK(container)->notebook);
5045 children = gtk_container_get_children(container);
5046 l = g_list_find_custom(children, account, (GCompareFunc) find_account_widget);
5047 if (l)
5048 ret = GTK_WIDGET(l->data);
5049 g_list_free(children);
5050 return ret;
5053 static void
5054 remove_child_widget_by_account(GtkContainer *container,
5055 PurpleAccount *account)
5057 GtkWidget *widget = find_child_widget_by_account(container, account);
5058 if(widget) {
5059 /* Since we are destroying the widget in response to a change in
5060 * error, we should not clear the error.
5062 g_object_set_data(G_OBJECT(widget), DO_NOT_CLEAR_ERROR,
5063 GINT_TO_POINTER(TRUE));
5064 gtk_widget_destroy(widget);
5068 /* Generic error buttons */
5070 static void
5071 generic_error_modify_cb(PurpleAccount *account)
5073 purple_account_clear_current_error(account);
5074 pidgin_account_dialog_show(PIDGIN_MODIFY_ACCOUNT_DIALOG, account);
5077 static void
5078 generic_error_enable_cb(PurpleAccount *account)
5080 purple_account_clear_current_error(account);
5081 purple_account_set_enabled(account, purple_core_get_ui(), TRUE);
5084 static void
5085 generic_error_destroy_cb(GtkObject *dialog,
5086 PurpleAccount *account)
5088 g_hash_table_remove(gtkblist->connection_errors, account);
5089 /* If the error dialog is being destroyed in response to the
5090 * account-error-changed signal, we don't want to clear the current
5091 * error.
5093 if (g_object_get_data(G_OBJECT(dialog), DO_NOT_CLEAR_ERROR) == NULL)
5094 purple_account_clear_current_error(account);
5097 #define SSL_FAQ_URI "http://d.pidgin.im/wiki/FAQssl"
5099 static void
5100 ssl_faq_clicked_cb(PidginMiniDialog *mini_dialog,
5101 GtkButton *button,
5102 gpointer ignored)
5104 purple_notify_uri(NULL, SSL_FAQ_URI);
5107 static void
5108 add_generic_error_dialog(PurpleAccount *account,
5109 const PurpleConnectionErrorInfo *err)
5111 GtkWidget *mini_dialog;
5112 const char *username = purple_account_get_username(account);
5113 gboolean enabled =
5114 purple_account_get_enabled(account, purple_core_get_ui());
5115 char *primary;
5117 if (enabled)
5118 primary = g_strdup_printf(_("%s disconnected"), username);
5119 else
5120 primary = g_strdup_printf(_("%s disabled"), username);
5122 mini_dialog = pidgin_make_mini_dialog(NULL, PIDGIN_STOCK_DIALOG_ERROR,
5123 primary, err->description, account,
5124 (enabled ? _("Reconnect") : _("Re-enable")),
5125 (enabled ? PURPLE_CALLBACK(purple_account_connect)
5126 : PURPLE_CALLBACK(generic_error_enable_cb)),
5127 _("Modify Account"), PURPLE_CALLBACK(generic_error_modify_cb),
5128 NULL);
5130 g_free(primary);
5132 g_object_set_data(G_OBJECT(mini_dialog), OBJECT_DATA_KEY_ACCOUNT,
5133 account);
5135 if(err->type == PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT)
5136 pidgin_mini_dialog_add_non_closing_button(PIDGIN_MINI_DIALOG(mini_dialog),
5137 _("SSL FAQs"), ssl_faq_clicked_cb, NULL);
5139 g_signal_connect_after(mini_dialog, "destroy",
5140 (GCallback)generic_error_destroy_cb,
5141 account);
5143 add_error_dialog(gtkblist, mini_dialog);
5146 static void
5147 remove_generic_error_dialog(PurpleAccount *account)
5149 PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
5150 remove_child_widget_by_account(
5151 GTK_CONTAINER(priv->error_scrollbook), account);
5155 static void
5156 update_generic_error_message(PurpleAccount *account,
5157 const char *description)
5159 PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
5160 GtkWidget *mini_dialog = find_child_widget_by_account(
5161 GTK_CONTAINER(priv->error_scrollbook), account);
5162 pidgin_mini_dialog_set_description(PIDGIN_MINI_DIALOG(mini_dialog),
5163 description);
5167 /* Notifications about accounts which were disconnected with
5168 * PURPLE_CONNECTION_ERROR_NAME_IN_USE
5171 typedef void (*AccountFunction)(PurpleAccount *);
5173 static void
5174 elsewhere_foreach_account(PidginMiniDialog *mini_dialog,
5175 AccountFunction f)
5177 PurpleAccount *account;
5178 GList *labels = gtk_container_get_children(
5179 GTK_CONTAINER(mini_dialog->contents));
5180 GList *l;
5182 for (l = labels; l; l = l->next) {
5183 account = g_object_get_data(G_OBJECT(l->data), OBJECT_DATA_KEY_ACCOUNT);
5184 if (account)
5185 f(account);
5186 else
5187 purple_debug_warning("gtkblist", "mini_dialog's child "
5188 "didn't have an account stored in it!");
5190 g_list_free(labels);
5193 static void
5194 enable_account(PurpleAccount *account)
5196 purple_account_set_enabled(account, purple_core_get_ui(), TRUE);
5199 static void
5200 reconnect_elsewhere_accounts(PidginMiniDialog *mini_dialog,
5201 GtkButton *button,
5202 gpointer unused)
5204 elsewhere_foreach_account(mini_dialog, enable_account);
5207 static void
5208 clear_elsewhere_errors(PidginMiniDialog *mini_dialog,
5209 gpointer unused)
5211 elsewhere_foreach_account(mini_dialog, purple_account_clear_current_error);
5214 static void
5215 ensure_signed_on_elsewhere_minidialog(PidginBuddyList *gtkblist)
5217 PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
5218 PidginMiniDialog *mini_dialog;
5220 if(priv->signed_on_elsewhere)
5221 return;
5223 mini_dialog = priv->signed_on_elsewhere =
5224 pidgin_mini_dialog_new(_("Welcome back!"), NULL, PIDGIN_STOCK_DISCONNECT);
5226 pidgin_mini_dialog_add_button(mini_dialog, _("Re-enable"),
5227 reconnect_elsewhere_accounts, NULL);
5229 /* Make dismissing the dialog clear the errors. The "destroy" signal
5230 * does not appear to fire at quit, which is fortunate!
5232 g_signal_connect(G_OBJECT(mini_dialog), "destroy",
5233 (GCallback) clear_elsewhere_errors, NULL);
5235 add_error_dialog(gtkblist, GTK_WIDGET(mini_dialog));
5237 /* Set priv->signed_on_elsewhere to NULL when the dialog is destroyed */
5238 g_signal_connect(G_OBJECT(mini_dialog), "destroy",
5239 (GCallback) gtk_widget_destroyed, &(priv->signed_on_elsewhere));
5242 static void
5243 update_signed_on_elsewhere_minidialog_title(void)
5245 PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
5246 PidginMiniDialog *mini_dialog = priv->signed_on_elsewhere;
5247 guint accounts;
5248 char *title;
5250 if (mini_dialog == NULL)
5251 return;
5253 accounts = pidgin_mini_dialog_get_num_children(mini_dialog);
5254 if (accounts == 0) {
5255 gtk_widget_destroy(GTK_WIDGET(mini_dialog));
5256 return;
5259 title = g_strdup_printf(
5260 ngettext("%d account was disabled because you signed on from another location:",
5261 "%d accounts were disabled because you signed on from another location:",
5262 accounts),
5263 accounts);
5264 pidgin_mini_dialog_set_description(mini_dialog, title);
5265 g_free(title);
5268 static GtkWidget *
5269 create_account_label(PurpleAccount *account)
5271 GtkWidget *hbox, *label;
5272 const char *username = purple_account_get_username(account);
5273 char *markup;
5275 hbox = gtk_hbox_new(FALSE, 6);
5276 g_object_set_data(G_OBJECT(hbox), OBJECT_DATA_KEY_ACCOUNT, account);
5278 pack_prpl_icon_start(hbox, account);
5280 label = gtk_label_new(NULL);
5281 markup = g_strdup_printf("<span size=\"smaller\">%s</span>", username);
5282 gtk_label_set_markup(GTK_LABEL(label), markup);
5283 g_free(markup);
5284 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
5285 g_object_set(G_OBJECT(label), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
5286 #if GTK_CHECK_VERSION(2,12,0)
5287 { /* avoid unused variable warnings on pre-2.12 Gtk */
5288 char *description =
5289 purple_account_get_current_error(account)->description;
5290 if (description != NULL && *description != '\0')
5291 gtk_widget_set_tooltip_text(label, description);
5293 #endif
5294 gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
5296 return hbox;
5299 static void
5300 add_to_signed_on_elsewhere(PurpleAccount *account)
5302 PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
5303 PidginMiniDialog *mini_dialog;
5304 GtkWidget *account_label;
5306 ensure_signed_on_elsewhere_minidialog(gtkblist);
5307 mini_dialog = priv->signed_on_elsewhere;
5309 if(find_child_widget_by_account(GTK_CONTAINER(mini_dialog->contents), account))
5310 return;
5312 account_label = create_account_label(account);
5313 gtk_box_pack_start(mini_dialog->contents, account_label, FALSE, FALSE, 0);
5314 gtk_widget_show_all(account_label);
5316 update_signed_on_elsewhere_minidialog_title();
5319 static void
5320 remove_from_signed_on_elsewhere(PurpleAccount *account)
5322 PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
5323 PidginMiniDialog *mini_dialog = priv->signed_on_elsewhere;
5324 if(mini_dialog == NULL)
5325 return;
5327 remove_child_widget_by_account(GTK_CONTAINER(mini_dialog->contents), account);
5329 update_signed_on_elsewhere_minidialog_title();
5333 static void
5334 update_signed_on_elsewhere_tooltip(PurpleAccount *account,
5335 const char *description)
5337 #if GTK_CHECK_VERSION(2,12,0)
5338 PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
5339 GtkContainer *c = GTK_CONTAINER(priv->signed_on_elsewhere->contents);
5340 GtkWidget *label = find_child_widget_by_account(c, account);
5341 gtk_widget_set_tooltip_text(label, description);
5342 #endif
5346 /* Call appropriate error notification code based on error types */
5347 static void
5348 update_account_error_state(PurpleAccount *account,
5349 const PurpleConnectionErrorInfo *old,
5350 const PurpleConnectionErrorInfo *new,
5351 PidginBuddyList *gtkblist)
5353 gboolean descriptions_differ;
5354 const char *desc;
5356 if (old == NULL && new == NULL)
5357 return;
5359 /* For backwards compatibility: */
5360 if (new)
5361 pidgin_blist_update_account_error_state(account, new->description);
5362 else
5363 pidgin_blist_update_account_error_state(account, NULL);
5365 if (new != NULL)
5366 pidgin_blist_select_notebook_page(gtkblist);
5368 if (old != NULL && new == NULL) {
5369 if(old->type == PURPLE_CONNECTION_ERROR_NAME_IN_USE)
5370 remove_from_signed_on_elsewhere(account);
5371 else
5372 remove_generic_error_dialog(account);
5373 return;
5376 if (old == NULL && new != NULL) {
5377 if(new->type == PURPLE_CONNECTION_ERROR_NAME_IN_USE)
5378 add_to_signed_on_elsewhere(account);
5379 else
5380 add_generic_error_dialog(account, new);
5381 return;
5384 /* else, new and old are both non-NULL */
5386 descriptions_differ = strcmp(old->description, new->description);
5387 desc = new->description;
5389 switch (new->type) {
5390 case PURPLE_CONNECTION_ERROR_NAME_IN_USE:
5391 if (old->type == PURPLE_CONNECTION_ERROR_NAME_IN_USE
5392 && descriptions_differ) {
5393 update_signed_on_elsewhere_tooltip(account, desc);
5394 } else {
5395 remove_generic_error_dialog(account);
5396 add_to_signed_on_elsewhere(account);
5398 break;
5399 default:
5400 if (old->type == PURPLE_CONNECTION_ERROR_NAME_IN_USE) {
5401 remove_from_signed_on_elsewhere(account);
5402 add_generic_error_dialog(account, new);
5403 } else if (descriptions_differ) {
5404 update_generic_error_message(account, desc);
5406 break;
5410 /* In case accounts are loaded before the blist (which they currently are),
5411 * let's call update_account_error_state ourselves on every account's current
5412 * state when the blist starts.
5414 static void
5415 show_initial_account_errors(PidginBuddyList *gtkblist)
5417 GList *l = purple_accounts_get_all();
5418 PurpleAccount *account;
5419 const PurpleConnectionErrorInfo *err;
5421 for (; l; l = l->next)
5423 account = l->data;
5424 err = purple_account_get_current_error(account);
5426 update_account_error_state(account, NULL, err, gtkblist);
5430 void
5431 pidgin_blist_update_account_error_state(PurpleAccount *account, const char *text)
5433 /* connection_errors isn't actually used anywhere; it's just kept in
5434 * sync with reality in case a plugin uses it.
5436 if (text == NULL)
5437 g_hash_table_remove(gtkblist->connection_errors, account);
5438 else
5439 g_hash_table_insert(gtkblist->connection_errors, account, g_strdup(text));
5442 static gboolean
5443 paint_headline_hbox (GtkWidget *widget,
5444 GdkEventExpose *event,
5445 gpointer user_data)
5447 gtk_paint_flat_box (widget->style,
5448 widget->window,
5449 GTK_STATE_NORMAL,
5450 GTK_SHADOW_OUT,
5451 NULL,
5452 widget,
5453 "tooltip",
5454 widget->allocation.x + 1,
5455 widget->allocation.y + 1,
5456 widget->allocation.width - 2,
5457 widget->allocation.height - 2);
5458 return FALSE;
5461 /* This assumes there are not things like groupless buddies or multi-leveled groups.
5462 * I'm sure other things in this code assumes that also.
5464 static void
5465 treeview_style_set (GtkWidget *widget,
5466 GtkStyle *prev_style,
5467 gpointer data)
5469 PurpleBuddyList *list = data;
5470 PurpleBlistNode *node = list->root;
5471 while (node) {
5472 pidgin_blist_update_group(list, node);
5473 node = node->next;
5477 static void
5478 headline_style_set (GtkWidget *widget,
5479 GtkStyle *prev_style)
5481 GtkTooltips *tooltips;
5482 GtkStyle *style;
5484 if (gtkblist->changing_style)
5485 return;
5487 tooltips = gtk_tooltips_new ();
5488 g_object_ref_sink (tooltips);
5490 gtk_tooltips_force_window (tooltips);
5491 #if GTK_CHECK_VERSION(2, 12, 0)
5492 gtk_widget_set_name (tooltips->tip_window, "gtk-tooltips");
5493 #endif
5494 gtk_widget_ensure_style (tooltips->tip_window);
5495 style = gtk_widget_get_style (tooltips->tip_window);
5497 gtkblist->changing_style = TRUE;
5498 gtk_widget_set_style (gtkblist->headline_hbox, style);
5499 gtkblist->changing_style = FALSE;
5501 g_object_unref (tooltips);
5504 /******************************************/
5505 /* End of connection error handling stuff */
5506 /******************************************/
5508 static int
5509 blist_focus_cb(GtkWidget *widget, GdkEventFocus *event, PidginBuddyList *gtkblist)
5511 if(event->in) {
5512 gtk_blist_focused = TRUE;
5513 pidgin_set_urgent(GTK_WINDOW(gtkblist->window), FALSE);
5514 } else {
5515 gtk_blist_focused = FALSE;
5517 return 0;
5520 #if 0
5521 static GtkWidget *
5522 kiosk_page()
5524 GtkWidget *ret = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
5525 GtkWidget *label;
5526 GtkWidget *entry;
5527 GtkWidget *bbox;
5528 GtkWidget *button;
5530 label = gtk_label_new(NULL);
5531 gtk_box_pack_start(GTK_BOX(ret), label, TRUE, TRUE, 0);
5533 label = gtk_label_new(NULL);
5534 gtk_label_set_markup(GTK_LABEL(label), _("<b>Username:</b>"));
5535 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
5536 gtk_box_pack_start(GTK_BOX(ret), label, FALSE, FALSE, 0);
5537 entry = gtk_entry_new();
5538 gtk_box_pack_start(GTK_BOX(ret), entry, FALSE, FALSE, 0);
5540 label = gtk_label_new(NULL);
5541 gtk_label_set_markup(GTK_LABEL(label), _("<b>Password:</b>"));
5542 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
5543 gtk_box_pack_start(GTK_BOX(ret), label, FALSE, FALSE, 0);
5544 entry = gtk_entry_new();
5545 gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
5546 gtk_box_pack_start(GTK_BOX(ret), entry, FALSE, FALSE, 0);
5548 label = gtk_label_new(" ");
5549 gtk_box_pack_start(GTK_BOX(ret), label, FALSE, FALSE, 0);
5551 bbox = gtk_hbutton_box_new();
5552 button = gtk_button_new_with_mnemonic(_("_Login"));
5553 gtk_box_pack_start(GTK_BOX(ret), bbox, FALSE, FALSE, 0);
5554 gtk_container_add(GTK_CONTAINER(bbox), button);
5557 label = gtk_label_new(NULL);
5558 gtk_box_pack_start(GTK_BOX(ret), label, TRUE, TRUE, 0);
5560 gtk_container_set_border_width(GTK_CONTAINER(ret), PIDGIN_HIG_BORDER);
5562 gtk_widget_show_all(ret);
5563 return ret;
5565 #endif
5567 /* builds the blist layout according to to the current theme */
5568 static void
5569 pidgin_blist_build_layout(PurpleBuddyList *list)
5571 GtkTreeViewColumn *column;
5572 PidginBlistLayout *layout;
5573 PidginBlistTheme *theme;
5574 GtkCellRenderer *rend;
5575 gint i, status_icon = 0, text = 1, emblem = 2, protocol_icon = 3, buddy_icon = 4;
5578 column = gtkblist->text_column;
5580 if ((theme = pidgin_blist_get_theme()) != NULL && (layout = pidgin_blist_theme_get_layout(theme)) != NULL) {
5581 status_icon = layout->status_icon ;
5582 text = layout->text;
5583 emblem = layout->emblem;
5584 protocol_icon = layout->protocol_icon;
5585 buddy_icon = layout->buddy_icon;
5588 gtk_tree_view_column_clear(column);
5590 /* group */
5591 rend = pidgin_cell_renderer_expander_new();
5592 gtk_tree_view_column_pack_start(column, rend, FALSE);
5593 gtk_tree_view_column_set_attributes(column, rend,
5594 "visible", GROUP_EXPANDER_VISIBLE_COLUMN,
5595 "expander-visible", GROUP_EXPANDER_COLUMN,
5596 "sensitive", GROUP_EXPANDER_COLUMN,
5597 "cell-background-gdk", BGCOLOR_COLUMN,
5598 NULL);
5600 /* contact */
5601 rend = pidgin_cell_renderer_expander_new();
5602 gtk_tree_view_column_pack_start(column, rend, FALSE);
5603 gtk_tree_view_column_set_attributes(column, rend,
5604 "visible", CONTACT_EXPANDER_VISIBLE_COLUMN,
5605 "expander-visible", CONTACT_EXPANDER_COLUMN,
5606 "sensitive", CONTACT_EXPANDER_COLUMN,
5607 "cell-background-gdk", BGCOLOR_COLUMN,
5608 NULL);
5610 for (i = 0; i < 5; i++) {
5612 if (status_icon == i) {
5613 /* status icons */
5614 rend = gtk_cell_renderer_pixbuf_new();
5615 gtk_tree_view_column_pack_start(column, rend, FALSE);
5616 gtk_tree_view_column_set_attributes(column, rend,
5617 "pixbuf", STATUS_ICON_COLUMN,
5618 "visible", STATUS_ICON_VISIBLE_COLUMN,
5619 "cell-background-gdk", BGCOLOR_COLUMN,
5620 NULL);
5621 g_object_set(rend, "xalign", 0.0, "xpad", 6, "ypad", 0, NULL);
5623 } else if (text == i) {
5624 /* name */
5625 gtkblist->text_rend = rend = gtk_cell_renderer_text_new();
5626 gtk_tree_view_column_pack_start(column, rend, TRUE);
5627 gtk_tree_view_column_set_attributes(column, rend,
5628 "cell-background-gdk", BGCOLOR_COLUMN,
5629 "markup", NAME_COLUMN,
5630 NULL);
5631 g_signal_connect(G_OBJECT(rend), "editing-started", G_CALLBACK(gtk_blist_renderer_editing_started_cb), NULL);
5632 g_signal_connect(G_OBJECT(rend), "editing-canceled", G_CALLBACK(gtk_blist_renderer_editing_cancelled_cb), list);
5633 g_signal_connect(G_OBJECT(rend), "edited", G_CALLBACK(gtk_blist_renderer_edited_cb), list);
5634 g_object_set(rend, "ypad", 0, "yalign", 0.5, NULL);
5635 g_object_set(rend, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
5637 /* idle */
5638 rend = gtk_cell_renderer_text_new();
5639 g_object_set(rend, "xalign", 1.0, "ypad", 0, NULL);
5640 gtk_tree_view_column_pack_start(column, rend, FALSE);
5641 gtk_tree_view_column_set_attributes(column, rend,
5642 "markup", IDLE_COLUMN,
5643 "visible", IDLE_VISIBLE_COLUMN,
5644 "cell-background-gdk", BGCOLOR_COLUMN,
5645 NULL);
5646 } else if (emblem == i) {
5647 /* emblem */
5648 rend = gtk_cell_renderer_pixbuf_new();
5649 g_object_set(rend, "xalign", 1.0, "yalign", 0.5, "ypad", 0, "xpad", 3, NULL);
5650 gtk_tree_view_column_pack_start(column, rend, FALSE);
5651 gtk_tree_view_column_set_attributes(column, rend, "pixbuf", EMBLEM_COLUMN,
5652 "cell-background-gdk", BGCOLOR_COLUMN,
5653 "visible", EMBLEM_VISIBLE_COLUMN, NULL);
5655 } else if (protocol_icon == i) {
5656 /* protocol icon */
5657 rend = gtk_cell_renderer_pixbuf_new();
5658 gtk_tree_view_column_pack_start(column, rend, FALSE);
5659 gtk_tree_view_column_set_attributes(column, rend,
5660 "pixbuf", PROTOCOL_ICON_COLUMN,
5661 "visible", PROTOCOL_ICON_VISIBLE_COLUMN,
5662 "cell-background-gdk", BGCOLOR_COLUMN,
5663 NULL);
5664 g_object_set(rend, "xalign", 0.0, "xpad", 3, "ypad", 0, NULL);
5666 } else if (buddy_icon == i) {
5667 /* buddy icon */
5668 rend = gtk_cell_renderer_pixbuf_new();
5669 g_object_set(rend, "xalign", 1.0, "ypad", 0, NULL);
5670 gtk_tree_view_column_pack_start(column, rend, FALSE);
5671 gtk_tree_view_column_set_attributes(column, rend, "pixbuf", BUDDY_ICON_COLUMN,
5672 "cell-background-gdk", BGCOLOR_COLUMN,
5673 "visible", BUDDY_ICON_VISIBLE_COLUMN,
5674 NULL);
5677 }/* end for loop */
5681 static gboolean
5682 pidgin_blist_search_equal_func(GtkTreeModel *model, gint column,
5683 const gchar *key, GtkTreeIter *iter, gpointer data)
5685 PurpleBlistNode *node = NULL;
5686 gboolean res = TRUE;
5687 const char *compare = NULL;
5689 if (!pidgin_tree_view_search_equal_func(model, column, key, iter, data))
5690 return FALSE;
5692 /* If the search string does not match the displayed label, then look
5693 * at the alternate labels for the nodes and search in them. Currently,
5694 * alternate labels that make sense are usernames/email addresses for
5695 * buddies (but only for the ones who don't have a local alias).
5698 gtk_tree_model_get(model, iter, NODE_COLUMN, &node, -1);
5699 if (!node)
5700 return TRUE;
5702 compare = NULL;
5703 if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
5704 PurpleBuddy *b = purple_contact_get_priority_buddy(PURPLE_CONTACT(node));
5705 if (!purple_buddy_get_local_buddy_alias(b))
5706 compare = purple_buddy_get_name(b);
5707 } else if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
5708 if (!purple_buddy_get_local_buddy_alias(PURPLE_BUDDY(node)))
5709 compare = purple_buddy_get_name(PURPLE_BUDDY(node));
5712 if (compare) {
5713 char *tmp, *enteredstring;
5714 tmp = g_utf8_normalize(key, -1, G_NORMALIZE_DEFAULT);
5715 enteredstring = g_utf8_casefold(tmp, -1);
5716 g_free(tmp);
5718 if (purple_str_has_prefix(compare, enteredstring))
5719 res = FALSE;
5720 g_free(enteredstring);
5723 return res;
5726 static void pidgin_blist_show(PurpleBuddyList *list)
5728 PidginBuddyListPrivate *priv;
5729 void *handle;
5730 GtkTreeViewColumn *column;
5731 GtkWidget *menu;
5732 GtkWidget *ebox;
5733 GtkWidget *sep;
5734 GtkWidget *label;
5735 GtkWidget *close;
5736 char *pretty, *tmp;
5737 const char *theme_name;
5738 GtkAccelGroup *accel_group;
5739 GtkTreeSelection *selection;
5740 GtkTargetEntry dte[] = {{"PURPLE_BLIST_NODE", GTK_TARGET_SAME_APP, DRAG_ROW},
5741 {"application/x-im-contact", 0, DRAG_BUDDY},
5742 {"text/x-vcard", 0, DRAG_VCARD },
5743 {"text/uri-list", 0, DRAG_URI},
5744 {"text/plain", 0, DRAG_TEXT}};
5745 GtkTargetEntry ste[] = {{"PURPLE_BLIST_NODE", GTK_TARGET_SAME_APP, DRAG_ROW},
5746 {"application/x-im-contact", 0, DRAG_BUDDY},
5747 {"text/x-vcard", 0, DRAG_VCARD }};
5748 if (gtkblist && gtkblist->window) {
5749 purple_blist_set_visible(purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/list_visible"));
5750 return;
5753 gtkblist = PIDGIN_BLIST(list);
5754 priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
5756 if (priv->current_theme)
5757 g_object_unref(priv->current_theme);
5759 theme_name = purple_prefs_get_string(PIDGIN_PREFS_ROOT "/blist/theme");
5760 if (theme_name && *theme_name)
5761 priv->current_theme = g_object_ref(PIDGIN_BLIST_THEME(purple_theme_manager_find_theme(theme_name, "blist")));
5762 else
5763 priv->current_theme = NULL;
5765 gtkblist->empty_avatar = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, 32, 32);
5766 gdk_pixbuf_fill(gtkblist->empty_avatar, 0x00000000);
5768 gtkblist->window = pidgin_create_window(_("Buddy List"), 0, "buddy_list", TRUE);
5769 g_signal_connect(G_OBJECT(gtkblist->window), "focus-in-event",
5770 G_CALLBACK(blist_focus_cb), gtkblist);
5771 g_signal_connect(G_OBJECT(gtkblist->window), "focus-out-event",
5772 G_CALLBACK(blist_focus_cb), gtkblist);
5773 GTK_WINDOW(gtkblist->window)->allow_shrink = TRUE;
5775 gtkblist->main_vbox = gtk_vbox_new(FALSE, 0);
5776 gtk_widget_show(gtkblist->main_vbox);
5777 gtk_container_add(GTK_CONTAINER(gtkblist->window), gtkblist->main_vbox);
5779 g_signal_connect(G_OBJECT(gtkblist->window), "delete_event", G_CALLBACK(gtk_blist_delete_cb), NULL);
5780 g_signal_connect(G_OBJECT(gtkblist->window), "configure_event", G_CALLBACK(gtk_blist_configure_cb), NULL);
5781 g_signal_connect(G_OBJECT(gtkblist->window), "visibility_notify_event", G_CALLBACK(gtk_blist_visibility_cb), NULL);
5782 g_signal_connect(G_OBJECT(gtkblist->window), "window_state_event", G_CALLBACK(gtk_blist_window_state_cb), NULL);
5783 g_signal_connect(G_OBJECT(gtkblist->window), "key_press_event", G_CALLBACK(gtk_blist_window_key_press_cb), gtkblist);
5784 gtk_widget_add_events(gtkblist->window, GDK_VISIBILITY_NOTIFY_MASK);
5786 /******************************* Menu bar *************************************/
5787 accel_group = gtk_accel_group_new();
5788 gtk_window_add_accel_group(GTK_WINDOW (gtkblist->window), accel_group);
5789 g_object_unref(accel_group);
5790 gtkblist->ift = gtk_item_factory_new(GTK_TYPE_MENU_BAR, "<PurpleMain>", accel_group);
5791 gtk_item_factory_set_translate_func(gtkblist->ift,
5792 (GtkTranslateFunc)item_factory_translate_func,
5793 NULL, NULL);
5794 gtk_item_factory_create_items(gtkblist->ift, sizeof(blist_menu) / sizeof(*blist_menu),
5795 blist_menu, NULL);
5796 pidgin_load_accels();
5797 g_signal_connect(G_OBJECT(accel_group), "accel-changed", G_CALLBACK(pidgin_save_accels_cb), NULL);
5799 menu = gtk_item_factory_get_widget(gtkblist->ift, "<PurpleMain>");
5800 gtkblist->menutray = pidgin_menu_tray_new();
5801 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtkblist->menutray);
5802 gtk_widget_show(gtkblist->menutray);
5803 gtk_widget_show(menu);
5804 gtk_box_pack_start(GTK_BOX(gtkblist->main_vbox), menu, FALSE, FALSE, 0);
5806 accountmenu = gtk_item_factory_get_widget(gtkblist->ift, N_("/Accounts"));
5809 /****************************** Notebook *************************************/
5810 gtkblist->notebook = gtk_notebook_new();
5811 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(gtkblist->notebook), FALSE);
5812 gtk_notebook_set_show_border(GTK_NOTEBOOK(gtkblist->notebook), FALSE);
5813 gtk_box_pack_start(GTK_BOX(gtkblist->main_vbox), gtkblist->notebook, TRUE, TRUE, 0);
5815 #if 0
5816 gtk_notebook_append_page(GTK_NOTEBOOK(gtkblist->notebook), kiosk_page(), NULL);
5817 #endif
5819 /* Translators: Please maintain the use of -> and <- to refer to menu heirarchy */
5820 tmp = g_strdup_printf(_("<span weight='bold' size='larger'>Welcome to %s!</span>\n\n"
5822 "You have no accounts enabled. Enable your IM accounts from the "
5823 "<b>Accounts</b> window at <b>Accounts->Manage Accounts</b>. Once you "
5824 "enable accounts, you'll be able to sign on, set your status, "
5825 "and talk to your friends."), PIDGIN_NAME);
5826 pretty = pidgin_make_pretty_arrows(tmp);
5827 g_free(tmp);
5828 label = gtk_label_new(NULL);
5829 gtk_widget_set_size_request(label, purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/width") - 12, -1);
5830 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
5831 gtk_misc_set_alignment(GTK_MISC(label), 0.5, 0.2);
5832 gtk_label_set_markup(GTK_LABEL(label), pretty);
5833 g_free(pretty);
5834 gtk_notebook_append_page(GTK_NOTEBOOK(gtkblist->notebook),label, NULL);
5835 gtkblist->vbox = gtk_vbox_new(FALSE, 0);
5836 gtk_notebook_append_page(GTK_NOTEBOOK(gtkblist->notebook), gtkblist->vbox, NULL);
5837 gtk_widget_show_all(gtkblist->notebook);
5838 pidgin_blist_select_notebook_page(gtkblist);
5840 ebox = gtk_event_box_new();
5841 gtk_box_pack_start(GTK_BOX(gtkblist->vbox), ebox, FALSE, FALSE, 0);
5842 gtkblist->headline_hbox = gtk_hbox_new(FALSE, 3);
5843 gtk_container_set_border_width(GTK_CONTAINER(gtkblist->headline_hbox), 6);
5844 gtk_container_add(GTK_CONTAINER(ebox), gtkblist->headline_hbox);
5845 gtkblist->headline_image = gtk_image_new_from_pixbuf(NULL);
5846 gtk_misc_set_alignment(GTK_MISC(gtkblist->headline_image), 0.0, 0);
5847 gtkblist->headline_label = gtk_label_new(NULL);
5848 gtk_widget_set_size_request(gtkblist->headline_label,
5849 purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/width")-25,-1);
5850 gtk_label_set_line_wrap(GTK_LABEL(gtkblist->headline_label), TRUE);
5851 gtk_box_pack_start(GTK_BOX(gtkblist->headline_hbox), gtkblist->headline_image, FALSE, FALSE, 0);
5852 gtk_box_pack_start(GTK_BOX(gtkblist->headline_hbox), gtkblist->headline_label, TRUE, TRUE, 0);
5853 g_signal_connect(gtkblist->headline_label, /* connecting on headline_hbox doesn't work, because
5854 the signal is not emitted when theme is changed */
5855 "style-set",
5856 G_CALLBACK(headline_style_set),
5857 NULL);
5858 g_signal_connect (gtkblist->headline_hbox,
5859 "expose_event",
5860 G_CALLBACK (paint_headline_hbox),
5861 NULL);
5862 gtk_widget_set_name(gtkblist->headline_hbox, "gtk-tooltips");
5864 gtkblist->headline_close = gtk_widget_render_icon(ebox, GTK_STOCK_CLOSE,
5865 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC), NULL);
5866 gtkblist->hand_cursor = gdk_cursor_new (GDK_HAND2);
5867 gtkblist->arrow_cursor = gdk_cursor_new (GDK_LEFT_PTR);
5869 /* Close button. */
5870 close = gtk_image_new_from_stock(GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU);
5871 close = pidgin_create_small_button(close);
5872 gtk_box_pack_start(GTK_BOX(gtkblist->headline_hbox), close, FALSE, FALSE, 0);
5873 #if GTK_CHECK_VERSION(2,12,0)
5874 gtk_widget_set_tooltip_text(close, _("Close"));
5875 #endif
5876 g_signal_connect(close, "clicked", G_CALLBACK(headline_close_press_cb), gtkblist);
5878 g_signal_connect(G_OBJECT(ebox), "enter-notify-event", G_CALLBACK(headline_box_enter_cb), gtkblist);
5879 g_signal_connect(G_OBJECT(ebox), "leave-notify-event", G_CALLBACK(headline_box_leave_cb), gtkblist);
5880 g_signal_connect(G_OBJECT(ebox), "button-press-event", G_CALLBACK(headline_box_press_cb), gtkblist);
5882 /****************************** GtkTreeView **********************************/
5883 gtkblist->treemodel = gtk_tree_store_new(BLIST_COLUMNS,
5884 GDK_TYPE_PIXBUF, /* Status icon */
5885 G_TYPE_BOOLEAN, /* Status icon visible */
5886 G_TYPE_STRING, /* Name */
5887 G_TYPE_STRING, /* Idle */
5888 G_TYPE_BOOLEAN, /* Idle visible */
5889 GDK_TYPE_PIXBUF, /* Buddy icon */
5890 G_TYPE_BOOLEAN, /* Buddy icon visible */
5891 G_TYPE_POINTER, /* Node */
5892 GDK_TYPE_COLOR, /* bgcolor */
5893 G_TYPE_BOOLEAN, /* Group expander */
5894 G_TYPE_BOOLEAN, /* Group expander visible */
5895 G_TYPE_BOOLEAN, /* Contact expander */
5896 G_TYPE_BOOLEAN, /* Contact expander visible */
5897 GDK_TYPE_PIXBUF, /* Emblem */
5898 G_TYPE_BOOLEAN, /* Emblem visible */
5899 GDK_TYPE_PIXBUF, /* Protocol icon */
5900 G_TYPE_BOOLEAN /* Protocol visible */
5903 gtkblist->treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(gtkblist->treemodel));
5905 gtk_widget_show(gtkblist->treeview);
5906 gtk_widget_set_name(gtkblist->treeview, "pidgin_blist_treeview");
5908 g_signal_connect(gtkblist->treeview,
5909 "style-set",
5910 G_CALLBACK(treeview_style_set), list);
5911 /* Set up selection stuff */
5912 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist->treeview));
5913 g_signal_connect(G_OBJECT(selection), "changed", G_CALLBACK(pidgin_blist_selection_changed), NULL);
5915 /* Set up dnd */
5916 gtk_tree_view_enable_model_drag_source(GTK_TREE_VIEW(gtkblist->treeview),
5917 GDK_BUTTON1_MASK, ste, 3,
5918 GDK_ACTION_COPY);
5919 gtk_tree_view_enable_model_drag_dest(GTK_TREE_VIEW(gtkblist->treeview),
5920 dte, 5,
5921 GDK_ACTION_COPY | GDK_ACTION_MOVE);
5923 g_signal_connect(G_OBJECT(gtkblist->treeview), "drag-data-received", G_CALLBACK(pidgin_blist_drag_data_rcv_cb), NULL);
5924 g_signal_connect(G_OBJECT(gtkblist->treeview), "drag-data-get", G_CALLBACK(pidgin_blist_drag_data_get_cb), NULL);
5925 #ifdef _WIN32
5926 g_signal_connect(G_OBJECT(gtkblist->treeview), "drag-begin", G_CALLBACK(pidgin_blist_drag_begin), NULL);
5927 #endif
5928 g_signal_connect(G_OBJECT(gtkblist->treeview), "drag-motion", G_CALLBACK(pidgin_blist_drag_motion_cb), NULL);
5929 g_signal_connect(G_OBJECT(gtkblist->treeview), "motion-notify-event", G_CALLBACK(pidgin_blist_motion_cb), NULL);
5930 g_signal_connect(G_OBJECT(gtkblist->treeview), "leave-notify-event", G_CALLBACK(pidgin_blist_leave_cb), NULL);
5932 /* Tooltips */
5933 pidgin_tooltip_setup_for_treeview(gtkblist->treeview, NULL,
5934 pidgin_blist_create_tooltip,
5935 pidgin_blist_paint_tip);
5937 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(gtkblist->treeview), FALSE);
5939 /* expander columns */
5940 column = gtk_tree_view_column_new();
5941 gtk_tree_view_append_column(GTK_TREE_VIEW(gtkblist->treeview), column);
5942 gtk_tree_view_column_set_visible(column, FALSE);
5943 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(gtkblist->treeview), column);
5945 /* everything else column */
5946 gtkblist->text_column = gtk_tree_view_column_new ();
5947 gtk_tree_view_append_column(GTK_TREE_VIEW(gtkblist->treeview), gtkblist->text_column);
5948 pidgin_blist_build_layout(list);
5950 g_signal_connect(G_OBJECT(gtkblist->treeview), "row-activated", G_CALLBACK(gtk_blist_row_activated_cb), NULL);
5951 g_signal_connect(G_OBJECT(gtkblist->treeview), "row-expanded", G_CALLBACK(gtk_blist_row_expanded_cb), NULL);
5952 g_signal_connect(G_OBJECT(gtkblist->treeview), "row-collapsed", G_CALLBACK(gtk_blist_row_collapsed_cb), NULL);
5953 g_signal_connect(G_OBJECT(gtkblist->treeview), "button-press-event", G_CALLBACK(gtk_blist_button_press_cb), NULL);
5954 g_signal_connect(G_OBJECT(gtkblist->treeview), "key-press-event", G_CALLBACK(gtk_blist_key_press_cb), NULL);
5955 g_signal_connect(G_OBJECT(gtkblist->treeview), "popup-menu", G_CALLBACK(pidgin_blist_popup_menu_cb), NULL);
5957 /* Enable CTRL+F searching */
5958 gtk_tree_view_set_search_column(GTK_TREE_VIEW(gtkblist->treeview), NAME_COLUMN);
5959 gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(gtkblist->treeview),
5960 pidgin_blist_search_equal_func, NULL, NULL);
5962 gtk_box_pack_start(GTK_BOX(gtkblist->vbox),
5963 pidgin_make_scrollable(gtkblist->treeview, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC, GTK_SHADOW_NONE, -1, -1),
5964 TRUE, TRUE, 0);
5966 sep = gtk_hseparator_new();
5967 gtk_box_pack_start(GTK_BOX(gtkblist->vbox), sep, FALSE, FALSE, 0);
5969 gtkblist->scrollbook = pidgin_scroll_book_new();
5970 gtk_box_pack_start(GTK_BOX(gtkblist->vbox), gtkblist->scrollbook, FALSE, FALSE, 0);
5972 /* Create an vbox which holds the scrollbook which is actually used to
5973 * display connection errors. The vbox needs to still exist for
5974 * backwards compatibility.
5976 gtkblist->error_buttons = gtk_vbox_new(FALSE, 0);
5977 gtk_box_pack_start(GTK_BOX(gtkblist->vbox), gtkblist->error_buttons, FALSE, FALSE, 0);
5978 gtk_container_set_border_width(GTK_CONTAINER(gtkblist->error_buttons), 0);
5980 priv->error_scrollbook = PIDGIN_SCROLL_BOOK(pidgin_scroll_book_new());
5981 gtk_box_pack_start(GTK_BOX(gtkblist->error_buttons),
5982 GTK_WIDGET(priv->error_scrollbook), FALSE, FALSE, 0);
5985 /* Add the statusbox */
5986 gtkblist->statusbox = pidgin_status_box_new();
5987 gtk_box_pack_start(GTK_BOX(gtkblist->vbox), gtkblist->statusbox, FALSE, TRUE, 0);
5988 gtk_widget_set_name(gtkblist->statusbox, "pidgin_blist_statusbox");
5989 gtk_container_set_border_width(GTK_CONTAINER(gtkblist->statusbox), 3);
5990 gtk_widget_show(gtkblist->statusbox);
5992 /* set the Show Offline Buddies option. must be done
5993 * after the treeview or faceprint gets mad. -Robot101
5995 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show/Offline Buddies"))),
5996 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_offline_buddies"));
5998 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show/Empty Groups"))),
5999 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_empty_groups"));
6001 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Tools/Mute Sounds"))),
6002 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/sound/mute"));
6004 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show/Buddy Details"))),
6005 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons"));
6007 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show/Idle Times"))),
6008 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_idle_time"));
6010 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show/Protocol Icons"))),
6011 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons"));
6013 if(!strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/sound/method"), "none"))
6014 gtk_widget_set_sensitive(gtk_item_factory_get_widget(gtkblist->ift, N_("/Tools/Mute Sounds")), FALSE);
6016 /* Update some dynamic things */
6017 update_menu_bar(gtkblist);
6018 pidgin_blist_update_plugin_actions();
6019 pidgin_blist_update_sort_methods();
6021 /* OK... let's show this bad boy. */
6022 pidgin_blist_refresh(list);
6023 pidgin_blist_restore_position();
6024 gtk_widget_show_all(GTK_WIDGET(gtkblist->vbox));
6025 gtk_widget_realize(GTK_WIDGET(gtkblist->window));
6026 purple_blist_set_visible(purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/list_visible"));
6028 /* start the refresh timer */
6029 gtkblist->refresh_timer = purple_timeout_add_seconds(30, (GSourceFunc)pidgin_blist_refresh_timer, list);
6031 handle = pidgin_blist_get_handle();
6033 /* things that affect how buddies are displayed */
6034 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/show_buddy_icons",
6035 _prefs_change_redo_list, NULL);
6036 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/show_idle_time",
6037 _prefs_change_redo_list, NULL);
6038 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/show_empty_groups",
6039 _prefs_change_redo_list, NULL);
6040 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/show_offline_buddies",
6041 _prefs_change_redo_list, NULL);
6042 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/show_protocol_icons",
6043 _prefs_change_redo_list, NULL);
6045 /* sorting */
6046 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/sort_type",
6047 _prefs_change_sort_method, NULL);
6049 /* menus */
6050 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/sound/mute",
6051 pidgin_blist_mute_pref_cb, NULL);
6052 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/sound/method",
6053 pidgin_blist_sound_method_pref_cb, NULL);
6055 /* Setup some purple signal handlers. */
6057 handle = purple_accounts_get_handle();
6058 purple_signal_connect(handle, "account-enabled", gtkblist,
6059 PURPLE_CALLBACK(account_modified), gtkblist);
6060 purple_signal_connect(handle, "account-disabled", gtkblist,
6061 PURPLE_CALLBACK(account_modified), gtkblist);
6062 purple_signal_connect(handle, "account-removed", gtkblist,
6063 PURPLE_CALLBACK(account_modified), gtkblist);
6064 purple_signal_connect(handle, "account-status-changed", gtkblist,
6065 PURPLE_CALLBACK(account_status_changed),
6066 gtkblist);
6067 purple_signal_connect(handle, "account-error-changed", gtkblist,
6068 PURPLE_CALLBACK(update_account_error_state),
6069 gtkblist);
6070 purple_signal_connect(handle, "account-actions-changed", gtkblist,
6071 PURPLE_CALLBACK(account_actions_changed), NULL);
6073 handle = pidgin_account_get_handle();
6074 purple_signal_connect(handle, "account-modified", gtkblist,
6075 PURPLE_CALLBACK(account_modified), gtkblist);
6077 handle = purple_connections_get_handle();
6078 purple_signal_connect(handle, "signed-on", gtkblist,
6079 PURPLE_CALLBACK(sign_on_off_cb), list);
6080 purple_signal_connect(handle, "signed-off", gtkblist,
6081 PURPLE_CALLBACK(sign_on_off_cb), list);
6083 handle = purple_plugins_get_handle();
6084 purple_signal_connect(handle, "plugin-load", gtkblist,
6085 PURPLE_CALLBACK(plugin_changed_cb), NULL);
6086 purple_signal_connect(handle, "plugin-unload", gtkblist,
6087 PURPLE_CALLBACK(plugin_changed_cb), NULL);
6089 handle = purple_conversations_get_handle();
6090 purple_signal_connect(handle, "conversation-updated", gtkblist,
6091 PURPLE_CALLBACK(conversation_updated_cb),
6092 gtkblist);
6093 purple_signal_connect(handle, "deleting-conversation", gtkblist,
6094 PURPLE_CALLBACK(conversation_deleting_cb),
6095 gtkblist);
6096 purple_signal_connect(handle, "conversation-created", gtkblist,
6097 PURPLE_CALLBACK(conversation_created_cb),
6098 gtkblist);
6100 gtk_widget_hide(gtkblist->headline_hbox);
6102 show_initial_account_errors(gtkblist);
6104 /* emit our created signal */
6105 handle = pidgin_blist_get_handle();
6106 purple_signal_emit(handle, "gtkblist-created", list);
6109 static void redo_buddy_list(PurpleBuddyList *list, gboolean remove, gboolean rerender)
6111 PurpleBlistNode *node;
6113 gtkblist = PIDGIN_BLIST(list);
6114 if(!gtkblist || !gtkblist->treeview)
6115 return;
6117 node = list->root;
6119 while (node)
6121 /* This is only needed when we're reverting to a non-GTK+ sorted
6122 * status. We shouldn't need to remove otherwise.
6124 if (remove && !PURPLE_BLIST_NODE_IS_GROUP(node))
6125 pidgin_blist_hide_node(list, node, FALSE);
6127 if (PURPLE_BLIST_NODE_IS_BUDDY(node))
6128 pidgin_blist_update_buddy(list, node, rerender);
6129 else if (PURPLE_BLIST_NODE_IS_CHAT(node))
6130 pidgin_blist_update(list, node);
6131 else if (PURPLE_BLIST_NODE_IS_GROUP(node))
6132 pidgin_blist_update(list, node);
6133 node = purple_blist_node_next(node, FALSE);
6138 void pidgin_blist_refresh(PurpleBuddyList *list)
6140 redo_buddy_list(list, FALSE, TRUE);
6143 void
6144 pidgin_blist_update_refresh_timeout()
6146 PurpleBuddyList *blist;
6147 PidginBuddyList *gtkblist;
6149 blist = purple_get_blist();
6150 gtkblist = PIDGIN_BLIST(purple_get_blist());
6152 gtkblist->refresh_timer = purple_timeout_add_seconds(30,(GSourceFunc)pidgin_blist_refresh_timer, blist);
6155 static gboolean get_iter_from_node(PurpleBlistNode *node, GtkTreeIter *iter) {
6156 struct _pidgin_blist_node *gtknode = (struct _pidgin_blist_node *)node->ui_data;
6157 GtkTreePath *path;
6159 if (!gtknode) {
6160 return FALSE;
6163 if (!gtkblist) {
6164 purple_debug_error("gtkblist", "get_iter_from_node was called, but we don't seem to have a blist\n");
6165 return FALSE;
6168 if (!gtknode->row)
6169 return FALSE;
6172 if ((path = gtk_tree_row_reference_get_path(gtknode->row)) == NULL)
6173 return FALSE;
6175 if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), iter, path)) {
6176 gtk_tree_path_free(path);
6177 return FALSE;
6179 gtk_tree_path_free(path);
6180 return TRUE;
6183 static void pidgin_blist_remove(PurpleBuddyList *list, PurpleBlistNode *node)
6185 struct _pidgin_blist_node *gtknode = node->ui_data;
6187 purple_request_close_with_handle(node);
6189 pidgin_blist_hide_node(list, node, TRUE);
6191 if(node->parent)
6192 pidgin_blist_update(list, node->parent);
6194 /* There's something I don't understand here - Ethan */
6195 /* Ethan said that back in 2003, but this g_free has been left commented
6196 * out ever since. I can't find any reason at all why this is bad and
6197 * valgrind found several reasons why it's good. If this causes problems
6198 * comment it out again. Stu */
6199 /* Of course it still causes problems - this breaks dragging buddies into
6200 * contacts, the dragged buddy mysteriously 'disappears'. Stu. */
6201 /* I think it's fixed now. Stu. */
6203 if(gtknode) {
6204 if(gtknode->recent_signonoff_timer > 0)
6205 purple_timeout_remove(gtknode->recent_signonoff_timer);
6207 purple_signals_disconnect_by_handle(node->ui_data);
6208 g_free(node->ui_data);
6209 node->ui_data = NULL;
6213 static gboolean do_selection_changed(PurpleBlistNode *new_selection)
6215 PurpleBlistNode *old_selection = NULL;
6217 /* test for gtkblist because crazy timeout means we can be called after the blist is gone */
6218 if (gtkblist && new_selection != gtkblist->selected_node) {
6219 old_selection = gtkblist->selected_node;
6220 gtkblist->selected_node = new_selection;
6221 if(new_selection)
6222 pidgin_blist_update(NULL, new_selection);
6223 if(old_selection)
6224 pidgin_blist_update(NULL, old_selection);
6227 return FALSE;
6230 static void pidgin_blist_selection_changed(GtkTreeSelection *selection, gpointer data)
6232 PurpleBlistNode *new_selection = NULL;
6233 GtkTreeIter iter;
6235 if(gtk_tree_selection_get_selected(selection, NULL, &iter)){
6236 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter,
6237 NODE_COLUMN, &new_selection, -1);
6240 /* we set this up as a timeout, otherwise the blist flickers ...
6241 * but we don't do it for groups, because it causes total bizarness -
6242 * the previously selected buddy node might rendered at half height.
6244 if ((new_selection != NULL) && PURPLE_BLIST_NODE_IS_GROUP(new_selection)) {
6245 do_selection_changed(new_selection);
6246 } else {
6247 g_timeout_add(0, (GSourceFunc)do_selection_changed, new_selection);
6251 static gboolean insert_node(PurpleBuddyList *list, PurpleBlistNode *node, GtkTreeIter *iter)
6253 GtkTreeIter parent_iter, cur, *curptr = NULL;
6254 struct _pidgin_blist_node *gtknode = node->ui_data;
6255 GtkTreePath *newpath;
6257 if(!iter)
6258 return FALSE;
6260 if(node->parent && !get_iter_from_node(node->parent, &parent_iter))
6261 return FALSE;
6263 if(get_iter_from_node(node, &cur))
6264 curptr = &cur;
6266 if(PURPLE_BLIST_NODE_IS_CONTACT(node) || PURPLE_BLIST_NODE_IS_CHAT(node)) {
6267 current_sort_method->func(node, list, parent_iter, curptr, iter);
6268 } else {
6269 sort_method_none(node, list, parent_iter, curptr, iter);
6272 if(gtknode != NULL) {
6273 gtk_tree_row_reference_free(gtknode->row);
6274 } else {
6275 pidgin_blist_new_node(node);
6276 gtknode = (struct _pidgin_blist_node *)node->ui_data;
6279 newpath = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel),
6280 iter);
6281 gtknode->row =
6282 gtk_tree_row_reference_new(GTK_TREE_MODEL(gtkblist->treemodel),
6283 newpath);
6285 gtk_tree_path_free(newpath);
6287 if (!editing_blist)
6288 gtk_tree_store_set(gtkblist->treemodel, iter,
6289 NODE_COLUMN, node,
6290 -1);
6292 if(node->parent) {
6293 GtkTreePath *expand = NULL;
6294 struct _pidgin_blist_node *gtkparentnode = node->parent->ui_data;
6296 if(PURPLE_BLIST_NODE_IS_GROUP(node->parent)) {
6297 if(!purple_blist_node_get_bool(node->parent, "collapsed"))
6298 expand = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &parent_iter);
6299 } else if(PURPLE_BLIST_NODE_IS_CONTACT(node->parent) &&
6300 gtkparentnode->contact_expanded) {
6301 expand = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &parent_iter);
6303 if(expand) {
6304 gtk_tree_view_expand_row(GTK_TREE_VIEW(gtkblist->treeview), expand, FALSE);
6305 gtk_tree_path_free(expand);
6309 return TRUE;
6312 static gboolean pidgin_blist_group_has_show_offline_buddy(PurpleGroup *group)
6314 PurpleBlistNode *gnode, *cnode, *bnode;
6316 gnode = (PurpleBlistNode *)group;
6317 for(cnode = gnode->child; cnode; cnode = cnode->next) {
6318 if(PURPLE_BLIST_NODE_IS_CONTACT(cnode)) {
6319 for(bnode = cnode->child; bnode; bnode = bnode->next) {
6320 PurpleBuddy *buddy = (PurpleBuddy *)bnode;
6321 if (purple_account_is_connected(buddy->account) &&
6322 purple_blist_node_get_bool(bnode, "show_offline"))
6323 return TRUE;
6327 return FALSE;
6330 /* This version of pidgin_blist_update_group can take the original buddy or a
6331 * group, but has much better algorithmic performance with a pre-known buddy.
6333 static void pidgin_blist_update_group(PurpleBuddyList *list,
6334 PurpleBlistNode *node)
6336 gint count;
6337 PurpleGroup *group;
6338 PurpleBlistNode* gnode;
6339 gboolean show = FALSE, show_offline = FALSE;
6341 g_return_if_fail(node != NULL);
6343 if (editing_blist)
6344 return;
6346 if (PURPLE_BLIST_NODE_IS_GROUP(node))
6347 gnode = node;
6348 else if (PURPLE_BLIST_NODE_IS_BUDDY(node))
6349 gnode = node->parent->parent;
6350 else if (PURPLE_BLIST_NODE_IS_CONTACT(node) || PURPLE_BLIST_NODE_IS_CHAT(node))
6351 gnode = node->parent;
6352 else
6353 return;
6355 group = (PurpleGroup*)gnode;
6357 show_offline = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_offline_buddies");
6359 if(show_offline)
6360 count = purple_blist_get_group_size(group, FALSE);
6361 else
6362 count = purple_blist_get_group_online_count(group);
6364 if (count > 0 || purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_empty_groups"))
6365 show = TRUE;
6366 else if (PURPLE_BLIST_NODE_IS_BUDDY(node) && buddy_is_displayable((PurpleBuddy*)node)) { /* Or chat? */
6367 show = TRUE;
6368 } else if (!show_offline) {
6369 show = pidgin_blist_group_has_show_offline_buddy(group);
6372 if (show) {
6373 gchar *title;
6374 gboolean biglist;
6375 GtkTreeIter iter;
6376 GtkTreePath *path;
6377 gboolean expanded;
6378 GdkColor *bgcolor = NULL;
6379 GdkPixbuf *avatar = NULL;
6380 PidginBlistTheme *theme = NULL;
6382 if(!insert_node(list, gnode, &iter))
6383 return;
6385 if ((theme = pidgin_blist_get_theme()) == NULL)
6386 bgcolor = NULL;
6387 else if (purple_blist_node_get_bool(gnode, "collapsed") || count <= 0)
6388 bgcolor = pidgin_blist_theme_get_collapsed_background_color(theme);
6389 else
6390 bgcolor = pidgin_blist_theme_get_expanded_background_color(theme);
6392 path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &iter);
6393 expanded = gtk_tree_view_row_expanded(GTK_TREE_VIEW(gtkblist->treeview), path);
6394 gtk_tree_path_free(path);
6396 title = pidgin_get_group_title(gnode, expanded);
6397 biglist = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons");
6399 if (biglist) {
6400 avatar = pidgin_blist_get_buddy_icon(gnode, TRUE, TRUE);
6403 gtk_tree_store_set(gtkblist->treemodel, &iter,
6404 STATUS_ICON_VISIBLE_COLUMN, FALSE,
6405 STATUS_ICON_COLUMN, NULL,
6406 NAME_COLUMN, title,
6407 NODE_COLUMN, gnode,
6408 BGCOLOR_COLUMN, bgcolor,
6409 GROUP_EXPANDER_COLUMN, TRUE,
6410 GROUP_EXPANDER_VISIBLE_COLUMN, TRUE,
6411 CONTACT_EXPANDER_VISIBLE_COLUMN, FALSE,
6412 BUDDY_ICON_COLUMN, avatar,
6413 BUDDY_ICON_VISIBLE_COLUMN, biglist,
6414 IDLE_VISIBLE_COLUMN, FALSE,
6415 EMBLEM_VISIBLE_COLUMN, FALSE,
6416 -1);
6417 g_free(title);
6418 } else {
6419 pidgin_blist_hide_node(list, gnode, TRUE);
6423 static char *pidgin_get_group_title(PurpleBlistNode *gnode, gboolean expanded)
6425 PurpleGroup *group;
6426 gboolean selected;
6427 char group_count[12] = "";
6428 char *mark, *esc;
6429 PurpleBlistNode *selected_node = NULL;
6430 GtkTreeIter iter;
6431 PidginThemeFont *pair;
6432 gchar const *text_color, *text_font;
6433 PidginBlistTheme *theme;
6435 group = (PurpleGroup*)gnode;
6437 if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist->treeview)), NULL, &iter)) {
6438 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter,
6439 NODE_COLUMN, &selected_node, -1);
6441 selected = (gnode == selected_node);
6443 if (!expanded) {
6444 g_snprintf(group_count, sizeof(group_count), "%d/%d",
6445 purple_blist_get_group_online_count(group),
6446 purple_blist_get_group_size(group, FALSE));
6449 theme = pidgin_blist_get_theme();
6450 if (theme == NULL)
6451 pair = NULL;
6452 else if (expanded)
6453 pair = pidgin_blist_theme_get_expanded_text_info(theme);
6454 else
6455 pair = pidgin_blist_theme_get_collapsed_text_info(theme);
6458 text_color = selected ? NULL : theme_font_get_color_default(pair, NULL);
6459 text_font = theme_font_get_face_default(pair, "");
6461 esc = g_markup_escape_text(group->name, -1);
6462 if (text_color) {
6463 mark = g_strdup_printf("<span foreground='%s' font_desc='%s'><b>%s</b>%s%s%s</span>",
6464 text_color, text_font,
6465 esc ? esc : "",
6466 !expanded ? " <span weight='light'>(</span>" : "",
6467 group_count,
6468 !expanded ? "<span weight='light'>)</span>" : "");
6469 } else {
6470 mark = g_strdup_printf("<span font_desc='%s'><b>%s</b>%s%s%s</span>",
6471 text_font, esc ? esc : "",
6472 !expanded ? " <span weight='light'>(</span>" : "",
6473 group_count,
6474 !expanded ? "<span weight='light'>)</span>" : "");
6477 g_free(esc);
6478 return mark;
6481 static void buddy_node(PurpleBuddy *buddy, GtkTreeIter *iter, PurpleBlistNode *node)
6483 PurplePresence *presence = purple_buddy_get_presence(buddy);
6484 GdkPixbuf *status, *avatar, *emblem, *prpl_icon;
6485 GdkColor *color = NULL;
6486 char *mark;
6487 char *idle = NULL;
6488 gboolean expanded = ((struct _pidgin_blist_node *)(node->parent->ui_data))->contact_expanded;
6489 gboolean selected = (gtkblist->selected_node == node);
6490 gboolean biglist = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons");
6491 PidginBlistTheme *theme;
6493 if (editing_blist)
6494 return;
6496 status = pidgin_blist_get_status_icon((PurpleBlistNode*)buddy,
6497 biglist ? PIDGIN_STATUS_ICON_LARGE : PIDGIN_STATUS_ICON_SMALL);
6499 /* Speed it up if we don't want buddy icons. */
6500 if(biglist)
6501 avatar = pidgin_blist_get_buddy_icon((PurpleBlistNode *)buddy, TRUE, TRUE);
6502 else
6503 avatar = NULL;
6505 if (!avatar) {
6506 g_object_ref(G_OBJECT(gtkblist->empty_avatar));
6507 avatar = gtkblist->empty_avatar;
6508 } else if ((!PURPLE_BUDDY_IS_ONLINE(buddy) || purple_presence_is_idle(presence))) {
6509 do_alphashift(avatar, 77);
6512 emblem = pidgin_blist_get_emblem((PurpleBlistNode*) buddy);
6513 mark = pidgin_blist_get_name_markup(buddy, selected, TRUE);
6515 theme = pidgin_blist_get_theme();
6517 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_idle_time") &&
6518 purple_presence_is_idle(presence) && !biglist)
6520 time_t idle_secs = purple_presence_get_idle_time(presence);
6522 if (idle_secs > 0)
6524 PidginThemeFont *pair = NULL;
6525 const gchar *textcolor;
6526 time_t t;
6527 int ihrs, imin;
6528 time(&t);
6530 ihrs = (t - idle_secs) / 3600;
6531 imin = ((t - idle_secs) / 60) % 60;
6533 if (selected)
6534 textcolor = NULL;
6535 else if (theme != NULL && (pair = pidgin_blist_theme_get_idle_text_info(theme)) != NULL)
6536 textcolor = pidgin_theme_font_get_color_describe(pair);
6537 else
6538 /* If no theme them default to making idle buddy names grey */
6539 textcolor = "dim grey";
6541 if (textcolor) {
6542 idle = g_strdup_printf("<span color='%s' font_desc='%s'>%d:%02d</span>",
6543 textcolor, theme_font_get_face_default(pair, ""),
6544 ihrs, imin);
6545 } else {
6546 idle = g_strdup_printf("<span font_desc='%s'>%d:%02d</span>",
6547 theme_font_get_face_default(pair, ""),
6548 ihrs, imin);
6553 prpl_icon = pidgin_create_prpl_icon(buddy->account, PIDGIN_PRPL_ICON_SMALL);
6555 if (theme != NULL)
6556 color = pidgin_blist_theme_get_contact_color(theme);
6558 gtk_tree_store_set(gtkblist->treemodel, iter,
6559 STATUS_ICON_COLUMN, status,
6560 STATUS_ICON_VISIBLE_COLUMN, TRUE,
6561 NAME_COLUMN, mark,
6562 IDLE_COLUMN, idle,
6563 IDLE_VISIBLE_COLUMN, !biglist && idle,
6564 BUDDY_ICON_COLUMN, avatar,
6565 BUDDY_ICON_VISIBLE_COLUMN, biglist,
6566 EMBLEM_COLUMN, emblem,
6567 EMBLEM_VISIBLE_COLUMN, (emblem != NULL),
6568 PROTOCOL_ICON_COLUMN, prpl_icon,
6569 PROTOCOL_ICON_VISIBLE_COLUMN, purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons"),
6570 BGCOLOR_COLUMN, color,
6571 CONTACT_EXPANDER_COLUMN, NULL,
6572 CONTACT_EXPANDER_VISIBLE_COLUMN, expanded,
6573 GROUP_EXPANDER_VISIBLE_COLUMN, FALSE,
6574 -1);
6576 g_free(mark);
6577 g_free(idle);
6578 if(emblem)
6579 g_object_unref(emblem);
6580 if(status)
6581 g_object_unref(status);
6582 if(avatar)
6583 g_object_unref(avatar);
6584 if(prpl_icon)
6585 g_object_unref(prpl_icon);
6588 /* This is a variation on the original gtk_blist_update_contact. Here we
6589 can know in advance which buddy has changed so we can just update that */
6590 static void pidgin_blist_update_contact(PurpleBuddyList *list, PurpleBlistNode *node)
6592 PurpleBlistNode *cnode;
6593 PurpleContact *contact;
6594 PurpleBuddy *buddy;
6595 gboolean biglist = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons");
6596 struct _pidgin_blist_node *gtknode;
6598 if (editing_blist)
6599 return;
6601 if (PURPLE_BLIST_NODE_IS_BUDDY(node))
6602 cnode = node->parent;
6603 else
6604 cnode = node;
6606 g_return_if_fail(PURPLE_BLIST_NODE_IS_CONTACT(cnode));
6608 /* First things first, update the group */
6609 if (PURPLE_BLIST_NODE_IS_BUDDY(node))
6610 pidgin_blist_update_group(list, node);
6611 else
6612 pidgin_blist_update_group(list, cnode->parent);
6614 contact = (PurpleContact*)cnode;
6615 buddy = purple_contact_get_priority_buddy(contact);
6617 if (buddy_is_displayable(buddy))
6619 GtkTreeIter iter;
6621 if(!insert_node(list, cnode, &iter))
6622 return;
6624 gtknode = (struct _pidgin_blist_node *)cnode->ui_data;
6626 if(gtknode->contact_expanded) {
6627 GdkPixbuf *status;
6628 gchar *mark, *tmp;
6629 const gchar *fg_color, *font;
6630 GdkColor *color = NULL;
6631 PidginBlistTheme *theme;
6632 PidginThemeFont *pair;
6633 gboolean selected = (gtkblist->selected_node == cnode);
6635 mark = g_markup_escape_text(purple_contact_get_alias(contact), -1);
6637 theme = pidgin_blist_get_theme();
6638 if (theme == NULL)
6639 pair = NULL;
6640 else {
6641 pair = pidgin_blist_theme_get_contact_text_info(theme);
6642 color = pidgin_blist_theme_get_contact_color(theme);
6645 font = theme_font_get_face_default(pair, "");
6646 fg_color = selected ? NULL : theme_font_get_color_default(pair, NULL);
6648 if (fg_color) {
6649 tmp = g_strdup_printf("<span font_desc='%s' color='%s'>%s</span>",
6650 font, fg_color, mark);
6651 } else {
6652 tmp = g_strdup_printf("<span font_desc='%s'>%s</span>", font,
6653 mark);
6655 g_free(mark);
6656 mark = tmp;
6658 status = pidgin_blist_get_status_icon(cnode,
6659 biglist? PIDGIN_STATUS_ICON_LARGE : PIDGIN_STATUS_ICON_SMALL);
6661 gtk_tree_store_set(gtkblist->treemodel, &iter,
6662 STATUS_ICON_COLUMN, status,
6663 STATUS_ICON_VISIBLE_COLUMN, TRUE,
6664 NAME_COLUMN, mark,
6665 IDLE_COLUMN, NULL,
6666 IDLE_VISIBLE_COLUMN, FALSE,
6667 BGCOLOR_COLUMN, color,
6668 BUDDY_ICON_COLUMN, NULL,
6669 CONTACT_EXPANDER_COLUMN, TRUE,
6670 CONTACT_EXPANDER_VISIBLE_COLUMN, TRUE,
6671 GROUP_EXPANDER_VISIBLE_COLUMN, FALSE,
6672 -1);
6673 g_free(mark);
6674 if(status)
6675 g_object_unref(status);
6676 } else {
6677 buddy_node(buddy, &iter, cnode);
6679 } else {
6680 pidgin_blist_hide_node(list, cnode, TRUE);
6686 static void pidgin_blist_update_buddy(PurpleBuddyList *list, PurpleBlistNode *node, gboolean status_change)
6688 PurpleBuddy *buddy;
6689 struct _pidgin_blist_node *gtkparentnode;
6691 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
6693 if (node->parent == NULL)
6694 return;
6696 buddy = (PurpleBuddy*)node;
6698 /* First things first, update the contact */
6699 pidgin_blist_update_contact(list, node);
6701 gtkparentnode = (struct _pidgin_blist_node *)node->parent->ui_data;
6703 if (gtkparentnode->contact_expanded && buddy_is_displayable(buddy))
6705 GtkTreeIter iter;
6707 if (!insert_node(list, node, &iter))
6708 return;
6710 buddy_node(buddy, &iter, node);
6712 } else {
6713 pidgin_blist_hide_node(list, node, TRUE);
6718 static void pidgin_blist_update_chat(PurpleBuddyList *list, PurpleBlistNode *node)
6720 PurpleChat *chat;
6722 g_return_if_fail(PURPLE_BLIST_NODE_IS_CHAT(node));
6724 if (editing_blist)
6725 return;
6727 /* First things first, update the group */
6728 pidgin_blist_update_group(list, node->parent);
6730 chat = (PurpleChat*)node;
6732 if(purple_account_is_connected(chat->account)) {
6733 GtkTreeIter iter;
6734 GdkPixbuf *status, *avatar, *emblem, *prpl_icon;
6735 const gchar *color, *font;
6736 gchar *mark, *tmp;
6737 gboolean showicons = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons");
6738 gboolean biglist = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons");
6739 PidginBlistNode *ui;
6740 PurpleConversation *conv;
6741 gboolean hidden = FALSE;
6742 GdkColor *bgcolor = NULL;
6743 PidginThemeFont *pair;
6744 PidginBlistTheme *theme;
6745 gboolean selected = (gtkblist->selected_node == node);
6746 gboolean nick_said = FALSE;
6748 if (!insert_node(list, node, &iter))
6749 return;
6751 ui = node->ui_data;
6752 conv = ui->conv.conv;
6753 if (conv && pidgin_conv_is_hidden(PIDGIN_CONVERSATION(conv))) {
6754 hidden = (ui->conv.flags & PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE);
6755 nick_said = (ui->conv.flags & PIDGIN_BLIST_CHAT_HAS_PENDING_MESSAGE_WITH_NICK);
6758 status = pidgin_blist_get_status_icon(node,
6759 biglist ? PIDGIN_STATUS_ICON_LARGE : PIDGIN_STATUS_ICON_SMALL);
6760 emblem = pidgin_blist_get_emblem(node);
6762 /* Speed it up if we don't want buddy icons. */
6763 if(showicons)
6764 avatar = pidgin_blist_get_buddy_icon(node, TRUE, FALSE);
6765 else
6766 avatar = NULL;
6768 mark = g_markup_escape_text(purple_chat_get_name(chat), -1);
6770 theme = pidgin_blist_get_theme();
6772 if (theme == NULL)
6773 pair = NULL;
6774 else if (nick_said)
6775 pair = pidgin_blist_theme_get_unread_message_nick_said_text_info(theme);
6776 else if (hidden)
6777 pair = pidgin_blist_theme_get_unread_message_text_info(theme);
6778 else pair = pidgin_blist_theme_get_online_text_info(theme);
6781 font = theme_font_get_face_default(pair, "");
6782 if (selected || !(color = theme_font_get_color_default(pair, NULL)))
6783 /* nick_said color is the same as gtkconv:tab-label-attention */
6784 color = (nick_said ? "#006aff" : NULL);
6786 if (color) {
6787 tmp = g_strdup_printf("<span font_desc='%s' color='%s' weight='%s'>%s</span>",
6788 font, color, hidden ? "bold" : "normal", mark);
6789 } else {
6790 tmp = g_strdup_printf("<span font_desc='%s' weight='%s'>%s</span>",
6791 font, hidden ? "bold" : "normal", mark);
6793 g_free(mark);
6794 mark = tmp;
6796 prpl_icon = pidgin_create_prpl_icon(chat->account, PIDGIN_PRPL_ICON_SMALL);
6798 if (theme != NULL)
6799 bgcolor = pidgin_blist_theme_get_contact_color(theme);
6801 gtk_tree_store_set(gtkblist->treemodel, &iter,
6802 STATUS_ICON_COLUMN, status,
6803 STATUS_ICON_VISIBLE_COLUMN, TRUE,
6804 BUDDY_ICON_COLUMN, avatar ? avatar : gtkblist->empty_avatar,
6805 BUDDY_ICON_VISIBLE_COLUMN, showicons,
6806 EMBLEM_COLUMN, emblem,
6807 EMBLEM_VISIBLE_COLUMN, emblem != NULL,
6808 PROTOCOL_ICON_COLUMN, prpl_icon,
6809 PROTOCOL_ICON_VISIBLE_COLUMN, purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons"),
6810 NAME_COLUMN, mark,
6811 BGCOLOR_COLUMN, bgcolor,
6812 GROUP_EXPANDER_VISIBLE_COLUMN, FALSE,
6813 -1);
6815 g_free(mark);
6816 if(emblem)
6817 g_object_unref(emblem);
6818 if(status)
6819 g_object_unref(status);
6820 if(avatar)
6821 g_object_unref(avatar);
6822 if(prpl_icon)
6823 g_object_unref(prpl_icon);
6825 } else {
6826 pidgin_blist_hide_node(list, node, TRUE);
6830 static void pidgin_blist_update(PurpleBuddyList *list, PurpleBlistNode *node)
6832 if (list)
6833 gtkblist = PIDGIN_BLIST(list);
6834 if(!gtkblist || !gtkblist->treeview || !node)
6835 return;
6837 if (node->ui_data == NULL)
6838 pidgin_blist_new_node(node);
6840 switch(node->type) {
6841 case PURPLE_BLIST_GROUP_NODE:
6842 pidgin_blist_update_group(list, node);
6843 break;
6844 case PURPLE_BLIST_CONTACT_NODE:
6845 pidgin_blist_update_contact(list, node);
6846 break;
6847 case PURPLE_BLIST_BUDDY_NODE:
6848 pidgin_blist_update_buddy(list, node, TRUE);
6849 break;
6850 case PURPLE_BLIST_CHAT_NODE:
6851 pidgin_blist_update_chat(list, node);
6852 break;
6853 case PURPLE_BLIST_OTHER_NODE:
6854 return;
6859 static void pidgin_blist_destroy(PurpleBuddyList *list)
6861 PidginBuddyListPrivate *priv;
6863 if (!list || !list->ui_data)
6864 return;
6866 g_return_if_fail(list->ui_data == gtkblist);
6868 purple_signals_disconnect_by_handle(gtkblist);
6870 if (gtkblist->headline_close)
6871 gdk_pixbuf_unref(gtkblist->headline_close);
6873 gtk_widget_destroy(gtkblist->window);
6875 pidgin_blist_tooltip_destroy();
6877 if (gtkblist->refresh_timer)
6878 purple_timeout_remove(gtkblist->refresh_timer);
6879 if (gtkblist->timeout)
6880 g_source_remove(gtkblist->timeout);
6881 if (gtkblist->drag_timeout)
6882 g_source_remove(gtkblist->drag_timeout);
6884 g_hash_table_destroy(gtkblist->connection_errors);
6885 gtkblist->refresh_timer = 0;
6886 gtkblist->timeout = 0;
6887 gtkblist->drag_timeout = 0;
6888 gtkblist->window = gtkblist->vbox = gtkblist->treeview = NULL;
6889 g_object_unref(G_OBJECT(gtkblist->treemodel));
6890 gtkblist->treemodel = NULL;
6891 g_object_unref(G_OBJECT(gtkblist->ift));
6892 g_object_unref(G_OBJECT(gtkblist->empty_avatar));
6894 gdk_cursor_unref(gtkblist->hand_cursor);
6895 gdk_cursor_unref(gtkblist->arrow_cursor);
6896 gtkblist->hand_cursor = NULL;
6897 gtkblist->arrow_cursor = NULL;
6899 priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
6900 if (priv->current_theme)
6901 g_object_unref(priv->current_theme);
6902 g_free(priv);
6904 g_free(gtkblist);
6905 accountmenu = NULL;
6906 gtkblist = NULL;
6907 purple_prefs_disconnect_by_handle(pidgin_blist_get_handle());
6910 static void pidgin_blist_set_visible(PurpleBuddyList *list, gboolean show)
6912 if (!(gtkblist && gtkblist->window))
6913 return;
6915 if (show) {
6916 if(!PIDGIN_WINDOW_ICONIFIED(gtkblist->window) && !GTK_WIDGET_VISIBLE(gtkblist->window))
6917 purple_signal_emit(pidgin_blist_get_handle(), "gtkblist-unhiding", gtkblist);
6918 pidgin_blist_restore_position();
6919 gtk_window_present(GTK_WINDOW(gtkblist->window));
6920 } else {
6921 if(visibility_manager_count) {
6922 purple_signal_emit(pidgin_blist_get_handle(), "gtkblist-hiding", gtkblist);
6923 gtk_widget_hide(gtkblist->window);
6924 } else {
6925 if (!GTK_WIDGET_VISIBLE(gtkblist->window))
6926 gtk_widget_show(gtkblist->window);
6927 gtk_window_iconify(GTK_WINDOW(gtkblist->window));
6932 static GList *
6933 groups_tree(void)
6935 static GList *list = NULL;
6936 char *tmp2;
6937 PurpleGroup *g;
6938 PurpleBlistNode *gnode;
6940 g_list_free(list);
6941 list = NULL;
6943 if (purple_get_blist()->root == NULL)
6945 list = g_list_append(list, (gpointer)_("Buddies"));
6947 else
6949 for (gnode = purple_get_blist()->root;
6950 gnode != NULL;
6951 gnode = gnode->next)
6953 if (PURPLE_BLIST_NODE_IS_GROUP(gnode))
6955 g = (PurpleGroup *)gnode;
6956 tmp2 = g->name;
6957 list = g_list_append(list, tmp2);
6962 return list;
6965 static void
6966 add_buddy_select_account_cb(GObject *w, PurpleAccount *account,
6967 PidginAddBuddyData *data)
6969 PurpleConnection *pc = NULL;
6970 PurplePlugin *prpl = NULL;
6971 PurplePluginProtocolInfo *prpl_info = NULL;
6972 gboolean invite_enabled = TRUE;
6974 /* Save our account */
6975 data->rq_data.account = account;
6977 if (account)
6978 pc = purple_account_get_connection(account);
6979 if (pc)
6980 prpl = purple_connection_get_prpl(pc);
6981 if (prpl)
6982 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
6983 if (prpl_info && !(prpl_info->options & OPT_PROTO_INVITE_MESSAGE))
6984 invite_enabled = FALSE;
6986 gtk_widget_set_sensitive(data->entry_for_invite, invite_enabled);
6989 static void
6990 destroy_add_buddy_dialog_cb(GtkWidget *win, PidginAddBuddyData *data)
6992 g_free(data);
6995 static void
6996 add_buddy_cb(GtkWidget *w, int resp, PidginAddBuddyData *data)
6998 const char *grp, *who, *whoalias, *invite;
6999 PurpleAccount *account;
7000 PurpleGroup *g;
7001 PurpleBuddy *b;
7002 PurpleConversation *c;
7003 PurpleBuddyIcon *icon;
7005 if (resp == GTK_RESPONSE_OK)
7007 who = gtk_entry_get_text(GTK_ENTRY(data->entry));
7008 grp = pidgin_text_combo_box_entry_get_text(data->combo);
7009 whoalias = gtk_entry_get_text(GTK_ENTRY(data->entry_for_alias));
7010 if (*whoalias == '\0')
7011 whoalias = NULL;
7012 invite = gtk_entry_get_text(GTK_ENTRY(data->entry_for_invite));
7013 if (*invite == '\0')
7014 invite = NULL;
7016 account = data->rq_data.account;
7018 g = NULL;
7019 if ((grp != NULL) && (*grp != '\0'))
7021 if ((g = purple_find_group(grp)) == NULL)
7023 g = purple_group_new(grp);
7024 purple_blist_add_group(g, NULL);
7027 b = purple_find_buddy_in_group(account, who, g);
7029 else if ((b = purple_find_buddy(account, who)) != NULL)
7031 g = purple_buddy_get_group(b);
7034 if (b == NULL)
7036 b = purple_buddy_new(account, who, whoalias);
7037 purple_blist_add_buddy(b, NULL, g, NULL);
7040 purple_account_add_buddy_with_invite(account, b, invite);
7042 /* Offer to merge people with the same alias. */
7043 if (whoalias != NULL && g != NULL)
7044 gtk_blist_auto_personize((PurpleBlistNode *)g, whoalias);
7047 * XXX
7048 * It really seems like it would be better if the call to
7049 * purple_account_add_buddy() and purple_conversation_update() were done in
7050 * blist.c, possibly in the purple_blist_add_buddy() function. Maybe
7051 * purple_account_add_buddy() should be renamed to
7052 * purple_blist_add_new_buddy() or something, and have it call
7053 * purple_blist_add_buddy() after it creates it. --Mark
7055 * No that's not good. blist.c should only deal with adding nodes to the
7056 * local list. We need a new, non-gtk file that calls both
7057 * purple_account_add_buddy and purple_blist_add_buddy().
7058 * Or something. --Mark
7061 c = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, who, data->rq_data.account);
7062 if (c != NULL) {
7063 icon = purple_conv_im_get_icon(PURPLE_CONV_IM(c));
7064 if (icon != NULL)
7065 purple_buddy_icon_update(icon);
7069 gtk_widget_destroy(data->rq_data.window);
7072 static void
7073 pidgin_blist_request_add_buddy(PurpleAccount *account, const char *username,
7074 const char *group, const char *alias)
7076 PidginAddBuddyData *data = g_new0(PidginAddBuddyData, 1);
7078 if (account == NULL)
7079 account = purple_connection_get_account(purple_connections_get_all()->data);
7081 make_blist_request_dialog((PidginBlistRequestData *)data,
7082 account,
7083 _("Add Buddy"), "add_buddy",
7084 _("Add a buddy.\n"),
7085 G_CALLBACK(add_buddy_select_account_cb), NULL,
7086 G_CALLBACK(add_buddy_cb));
7087 gtk_dialog_add_buttons(GTK_DIALOG(data->rq_data.window),
7088 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
7089 GTK_STOCK_ADD, GTK_RESPONSE_OK,
7090 NULL);
7091 gtk_dialog_set_default_response(GTK_DIALOG(data->rq_data.window),
7092 GTK_RESPONSE_OK);
7094 g_signal_connect(G_OBJECT(data->rq_data.window), "destroy",
7095 G_CALLBACK(destroy_add_buddy_dialog_cb), data);
7097 data->entry = gtk_entry_new();
7099 pidgin_add_widget_to_vbox(data->rq_data.vbox, _("Buddy's _username:"),
7100 data->rq_data.sg, data->entry, TRUE, NULL);
7101 gtk_widget_grab_focus(data->entry);
7103 if (username != NULL)
7104 gtk_entry_set_text(GTK_ENTRY(data->entry), username);
7105 else
7106 gtk_dialog_set_response_sensitive(GTK_DIALOG(data->rq_data.window),
7107 GTK_RESPONSE_OK, FALSE);
7109 gtk_entry_set_activates_default (GTK_ENTRY(data->entry), TRUE);
7111 g_signal_connect(G_OBJECT(data->entry), "changed",
7112 G_CALLBACK(pidgin_set_sensitive_if_input),
7113 data->rq_data.window);
7115 data->entry_for_alias = gtk_entry_new();
7116 pidgin_add_widget_to_vbox(data->rq_data.vbox, _("(Optional) A_lias:"),
7117 data->rq_data.sg, data->entry_for_alias, TRUE,
7118 NULL);
7120 if (alias != NULL)
7121 gtk_entry_set_text(GTK_ENTRY(data->entry_for_alias), alias);
7123 if (username != NULL)
7124 gtk_widget_grab_focus(GTK_WIDGET(data->entry_for_alias));
7126 data->entry_for_invite = gtk_entry_new();
7127 pidgin_add_widget_to_vbox(data->rq_data.vbox, _("(Optional) _Invite message:"),
7128 data->rq_data.sg, data->entry_for_invite, TRUE,
7129 NULL);
7131 data->combo = pidgin_text_combo_box_entry_new(group, groups_tree());
7132 pidgin_add_widget_to_vbox(data->rq_data.vbox, _("Add buddy to _group:"),
7133 data->rq_data.sg, data->combo, TRUE, NULL);
7135 gtk_widget_show_all(data->rq_data.window);
7137 /* Force update of invite message entry sensitivity */
7138 add_buddy_select_account_cb(NULL, account, data);
7141 static void
7142 add_chat_cb(GtkWidget *w, PidginAddChatData *data)
7144 GList *tmp;
7145 PurpleChat *chat;
7146 GHashTable *components;
7148 components = g_hash_table_new_full(g_str_hash, g_str_equal,
7149 g_free, g_free);
7151 for (tmp = data->chat_data.entries; tmp; tmp = tmp->next)
7153 if (g_object_get_data(tmp->data, "is_spin"))
7155 g_hash_table_replace(components,
7156 g_strdup(g_object_get_data(tmp->data, "identifier")),
7157 g_strdup_printf("%d",
7158 gtk_spin_button_get_value_as_int(tmp->data)));
7160 else
7162 const char *value = gtk_entry_get_text(tmp->data);
7164 if (*value != '\0')
7165 g_hash_table_replace(components,
7166 g_strdup(g_object_get_data(tmp->data, "identifier")),
7167 g_strdup(value));
7171 chat = purple_chat_new(data->chat_data.rq_data.account,
7172 gtk_entry_get_text(GTK_ENTRY(data->alias_entry)),
7173 components);
7175 if (chat != NULL) {
7176 PurpleGroup *group;
7177 const char *group_name;
7179 group_name = pidgin_text_combo_box_entry_get_text(data->group_combo);
7181 group = NULL;
7182 if ((group_name != NULL) && (*group_name != '\0') &&
7183 ((group = purple_find_group(group_name)) == NULL))
7185 group = purple_group_new(group_name);
7186 purple_blist_add_group(group, NULL);
7189 purple_blist_add_chat(chat, group, NULL);
7191 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(data->autojoin)))
7192 purple_blist_node_set_bool((PurpleBlistNode*)chat, "gtk-autojoin", TRUE);
7194 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(data->persistent)))
7195 purple_blist_node_set_bool((PurpleBlistNode*)chat, "gtk-persistent", TRUE);
7198 gtk_widget_destroy(data->chat_data.rq_data.window);
7199 g_free(data->chat_data.default_chat_name);
7200 g_list_free(data->chat_data.entries);
7201 g_free(data);
7204 static void
7205 add_chat_resp_cb(GtkWidget *w, int resp, PidginAddChatData *data)
7207 if (resp == GTK_RESPONSE_OK)
7209 add_chat_cb(NULL, data);
7211 else if (resp == 1)
7213 pidgin_roomlist_dialog_show_with_account(data->chat_data.rq_data.account);
7215 else
7217 gtk_widget_destroy(data->chat_data.rq_data.window);
7218 g_free(data->chat_data.default_chat_name);
7219 g_list_free(data->chat_data.entries);
7220 g_free(data);
7224 static void
7225 pidgin_blist_request_add_chat(PurpleAccount *account, PurpleGroup *group,
7226 const char *alias, const char *name)
7228 PidginAddChatData *data;
7229 GList *l;
7230 PurpleConnection *gc;
7231 GtkBox *vbox;
7233 if (account != NULL) {
7234 gc = purple_account_get_connection(account);
7236 if (PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->join_chat == NULL) {
7237 purple_notify_error(gc, NULL, _("This protocol does not support chat rooms."), NULL);
7238 return;
7240 } else {
7241 /* Find an account with chat capabilities */
7242 for (l = purple_connections_get_all(); l != NULL; l = l->next) {
7243 gc = (PurpleConnection *)l->data;
7245 if (PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->join_chat != NULL) {
7246 account = purple_connection_get_account(gc);
7247 break;
7251 if (account == NULL) {
7252 purple_notify_error(NULL, NULL,
7253 _("You are not currently signed on with any "
7254 "protocols that have the ability to chat."), NULL);
7255 return;
7259 data = g_new0(PidginAddChatData, 1);
7260 vbox = GTK_BOX(make_blist_request_dialog((PidginBlistRequestData *)data, account,
7261 _("Add Chat"), "add_chat",
7262 _("Please enter an alias, and the appropriate information "
7263 "about the chat you would like to add to your buddy list.\n"),
7264 G_CALLBACK(chat_select_account_cb), chat_account_filter_func,
7265 G_CALLBACK(add_chat_resp_cb)));
7266 gtk_dialog_add_buttons(GTK_DIALOG(data->chat_data.rq_data.window),
7267 _("Room List"), 1,
7268 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
7269 GTK_STOCK_ADD, GTK_RESPONSE_OK,
7270 NULL);
7271 gtk_dialog_set_default_response(GTK_DIALOG(data->chat_data.rq_data.window),
7272 GTK_RESPONSE_OK);
7274 data->chat_data.default_chat_name = g_strdup(name);
7276 rebuild_chat_entries((PidginChatData *)data, name);
7278 data->alias_entry = gtk_entry_new();
7279 if (alias != NULL)
7280 gtk_entry_set_text(GTK_ENTRY(data->alias_entry), alias);
7281 gtk_entry_set_activates_default(GTK_ENTRY(data->alias_entry), TRUE);
7283 pidgin_add_widget_to_vbox(GTK_BOX(vbox), _("A_lias:"),
7284 data->chat_data.rq_data.sg, data->alias_entry,
7285 TRUE, NULL);
7286 if (name != NULL)
7287 gtk_widget_grab_focus(data->alias_entry);
7289 data->group_combo = pidgin_text_combo_box_entry_new(group ? group->name : NULL, groups_tree());
7290 pidgin_add_widget_to_vbox(GTK_BOX(vbox), _("_Group:"),
7291 data->chat_data.rq_data.sg, data->group_combo,
7292 TRUE, NULL);
7294 data->autojoin = gtk_check_button_new_with_mnemonic(_("Auto_join when account connects."));
7295 data->persistent = gtk_check_button_new_with_mnemonic(_("_Remain in chat after window is closed."));
7296 gtk_box_pack_start(GTK_BOX(vbox), data->autojoin, FALSE, FALSE, 0);
7297 gtk_box_pack_start(GTK_BOX(vbox), data->persistent, FALSE, FALSE, 0);
7299 gtk_widget_show_all(data->chat_data.rq_data.window);
7302 static void
7303 add_group_cb(PurpleConnection *gc, const char *group_name)
7305 PurpleGroup *group;
7307 if ((group_name == NULL) || (*group_name == '\0'))
7308 return;
7310 group = purple_group_new(group_name);
7311 purple_blist_add_group(group, NULL);
7314 static void
7315 pidgin_blist_request_add_group(void)
7317 purple_request_input(NULL, _("Add Group"), NULL,
7318 _("Please enter the name of the group to be added."),
7319 NULL, FALSE, FALSE, NULL,
7320 _("Add"), G_CALLBACK(add_group_cb),
7321 _("Cancel"), NULL,
7322 NULL, NULL, NULL,
7323 NULL);
7326 void
7327 pidgin_blist_toggle_visibility()
7329 if (gtkblist && gtkblist->window) {
7330 if (GTK_WIDGET_VISIBLE(gtkblist->window)) {
7331 /* make the buddy list visible if it is iconified or if it is
7332 * obscured and not currently focused (the focus part ensures
7333 * that we do something reasonable if the buddy list is obscured
7334 * by a window set to always be on top), otherwise hide the
7335 * buddy list
7337 purple_blist_set_visible(PIDGIN_WINDOW_ICONIFIED(gtkblist->window) ||
7338 ((gtk_blist_visibility != GDK_VISIBILITY_UNOBSCURED) &&
7339 !gtk_blist_focused));
7340 } else {
7341 purple_blist_set_visible(TRUE);
7346 void
7347 pidgin_blist_visibility_manager_add()
7349 visibility_manager_count++;
7350 purple_debug_info("gtkblist", "added visibility manager: %d\n", visibility_manager_count);
7353 void
7354 pidgin_blist_visibility_manager_remove()
7356 if (visibility_manager_count)
7357 visibility_manager_count--;
7358 if (!visibility_manager_count)
7359 purple_blist_set_visible(TRUE);
7360 purple_debug_info("gtkblist", "removed visibility manager: %d\n", visibility_manager_count);
7363 void pidgin_blist_add_alert(GtkWidget *widget)
7365 gtk_container_add(GTK_CONTAINER(gtkblist->scrollbook), widget);
7366 set_urgent();
7369 void
7370 pidgin_blist_set_headline(const char *text, GdkPixbuf *pixbuf, GCallback callback,
7371 gpointer user_data, GDestroyNotify destroy)
7373 /* Destroy any existing headline first */
7374 if (gtkblist->headline_destroy)
7375 gtkblist->headline_destroy(gtkblist->headline_data);
7377 gtk_label_set_markup(GTK_LABEL(gtkblist->headline_label), text);
7378 gtk_image_set_from_pixbuf(GTK_IMAGE(gtkblist->headline_image), pixbuf);
7380 gtkblist->headline_callback = callback;
7381 gtkblist->headline_data = user_data;
7382 gtkblist->headline_destroy = destroy;
7383 if (text != NULL || pixbuf != NULL) {
7384 set_urgent();
7385 gtk_widget_show_all(gtkblist->headline_hbox);
7386 } else {
7387 gtk_widget_hide(gtkblist->headline_hbox);
7392 static void
7393 set_urgent(void)
7395 if (gtkblist->window && !GTK_WIDGET_HAS_FOCUS(gtkblist->window))
7396 pidgin_set_urgent(GTK_WINDOW(gtkblist->window), TRUE);
7399 static PurpleBlistUiOps blist_ui_ops =
7401 pidgin_blist_new_list,
7402 pidgin_blist_new_node,
7403 pidgin_blist_show,
7404 pidgin_blist_update,
7405 pidgin_blist_remove,
7406 pidgin_blist_destroy,
7407 pidgin_blist_set_visible,
7408 pidgin_blist_request_add_buddy,
7409 pidgin_blist_request_add_chat,
7410 pidgin_blist_request_add_group,
7411 NULL,
7412 NULL,
7413 NULL,
7414 NULL
7418 PurpleBlistUiOps *
7419 pidgin_blist_get_ui_ops(void)
7421 return &blist_ui_ops;
7424 PidginBuddyList *pidgin_blist_get_default_gtk_blist()
7426 return gtkblist;
7429 static gboolean autojoin_cb(PurpleConnection *gc, gpointer data)
7431 PurpleAccount *account = purple_connection_get_account(gc);
7432 PurpleBlistNode *gnode, *cnode;
7433 for(gnode = purple_get_blist()->root; gnode; gnode = gnode->next)
7435 if(!PURPLE_BLIST_NODE_IS_GROUP(gnode))
7436 continue;
7437 for(cnode = gnode->child; cnode; cnode = cnode->next)
7439 PurpleChat *chat;
7441 if(!PURPLE_BLIST_NODE_IS_CHAT(cnode))
7442 continue;
7444 chat = (PurpleChat *)cnode;
7446 if(chat->account != account)
7447 continue;
7449 if (purple_blist_node_get_bool((PurpleBlistNode*)chat, "gtk-autojoin"))
7450 serv_join_chat(gc, chat->components);
7454 /* Stop processing; we handled the autojoins. */
7455 return TRUE;
7458 void *
7459 pidgin_blist_get_handle() {
7460 static int handle;
7462 return &handle;
7465 static gboolean buddy_signonoff_timeout_cb(PurpleBuddy *buddy)
7467 struct _pidgin_blist_node *gtknode = ((PurpleBlistNode*)buddy)->ui_data;
7469 gtknode->recent_signonoff = FALSE;
7470 gtknode->recent_signonoff_timer = 0;
7472 pidgin_blist_update(NULL, (PurpleBlistNode*)buddy);
7474 return FALSE;
7477 static void buddy_signonoff_cb(PurpleBuddy *buddy)
7479 struct _pidgin_blist_node *gtknode;
7481 if(!((PurpleBlistNode*)buddy)->ui_data) {
7482 pidgin_blist_new_node((PurpleBlistNode*)buddy);
7485 gtknode = ((PurpleBlistNode*)buddy)->ui_data;
7487 gtknode->recent_signonoff = TRUE;
7489 if(gtknode->recent_signonoff_timer > 0)
7490 purple_timeout_remove(gtknode->recent_signonoff_timer);
7491 gtknode->recent_signonoff_timer = purple_timeout_add_seconds(10,
7492 (GSourceFunc)buddy_signonoff_timeout_cb, buddy);
7495 void
7496 pidgin_blist_set_theme(PidginBlistTheme *theme)
7498 PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
7499 PurpleBuddyList *list = purple_get_blist();
7501 if (theme != NULL)
7502 purple_prefs_set_string(PIDGIN_PREFS_ROOT "/blist/theme",
7503 purple_theme_get_name(PURPLE_THEME(theme)));
7504 else
7505 purple_prefs_set_string(PIDGIN_PREFS_ROOT "/blist/theme", "");
7507 if (priv->current_theme)
7508 g_object_unref(priv->current_theme);
7510 priv->current_theme = theme ? g_object_ref(theme) : NULL;
7512 pidgin_blist_build_layout(list);
7514 pidgin_blist_refresh(list);
7518 PidginBlistTheme *
7519 pidgin_blist_get_theme()
7521 PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
7523 return priv->current_theme;
7526 void pidgin_blist_init(void)
7528 void *gtk_blist_handle = pidgin_blist_get_handle();
7530 cached_emblems = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
7532 /* Initialize prefs */
7533 purple_prefs_add_none(PIDGIN_PREFS_ROOT "/blist");
7534 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons", TRUE);
7535 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/show_empty_groups", FALSE);
7536 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/show_idle_time", TRUE);
7537 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/show_offline_buddies", FALSE);
7538 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons", FALSE);
7539 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/list_visible", FALSE);
7540 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/list_maximized", FALSE);
7541 purple_prefs_add_string(PIDGIN_PREFS_ROOT "/blist/sort_type", "alphabetical");
7542 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/blist/x", 0);
7543 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/blist/y", 0);
7544 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/blist/width", 250); /* Golden ratio, baby */
7545 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/blist/height", 405); /* Golden ratio, baby */
7546 #if !GTK_CHECK_VERSION(2,14,0)
7547 /* This pref is used in pidgintooltip.c. */
7548 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/blist/tooltip_delay", 500);
7549 #endif
7550 purple_prefs_add_string(PIDGIN_PREFS_ROOT "/blist/theme", "");
7552 purple_theme_manager_register_type(g_object_new(PIDGIN_TYPE_BLIST_THEME_LOADER, "type", "blist", NULL));
7554 /* Register our signals */
7555 purple_signal_register(gtk_blist_handle, "gtkblist-hiding",
7556 purple_marshal_VOID__POINTER, NULL, 1,
7557 purple_value_new(PURPLE_TYPE_SUBTYPE,
7558 PURPLE_SUBTYPE_BLIST));
7560 purple_signal_register(gtk_blist_handle, "gtkblist-unhiding",
7561 purple_marshal_VOID__POINTER, NULL, 1,
7562 purple_value_new(PURPLE_TYPE_SUBTYPE,
7563 PURPLE_SUBTYPE_BLIST));
7565 purple_signal_register(gtk_blist_handle, "gtkblist-created",
7566 purple_marshal_VOID__POINTER, NULL, 1,
7567 purple_value_new(PURPLE_TYPE_SUBTYPE,
7568 PURPLE_SUBTYPE_BLIST));
7570 purple_signal_register(gtk_blist_handle, "drawing-tooltip",
7571 purple_marshal_VOID__POINTER_POINTER_UINT, NULL, 3,
7572 purple_value_new(PURPLE_TYPE_SUBTYPE,
7573 PURPLE_SUBTYPE_BLIST_NODE),
7574 purple_value_new_outgoing(PURPLE_TYPE_BOXED, "GString *"),
7575 purple_value_new(PURPLE_TYPE_BOOLEAN));
7577 purple_signal_register(gtk_blist_handle, "drawing-buddy",
7578 purple_marshal_POINTER__POINTER,
7579 purple_value_new(PURPLE_TYPE_STRING), 1,
7580 purple_value_new(PURPLE_TYPE_SUBTYPE,
7581 PURPLE_SUBTYPE_BLIST_BUDDY));
7583 purple_signal_connect(purple_blist_get_handle(), "buddy-signed-on",
7584 gtk_blist_handle, PURPLE_CALLBACK(buddy_signonoff_cb), NULL);
7585 purple_signal_connect(purple_blist_get_handle(), "buddy-signed-off",
7586 gtk_blist_handle, PURPLE_CALLBACK(buddy_signonoff_cb), NULL);
7587 purple_signal_connect(purple_blist_get_handle(), "buddy-privacy-changed",
7588 gtk_blist_handle, PURPLE_CALLBACK(pidgin_blist_update_privacy_cb), NULL);
7590 purple_signal_connect_priority(purple_connections_get_handle(), "autojoin",
7591 gtk_blist_handle, PURPLE_CALLBACK(autojoin_cb),
7592 NULL, PURPLE_SIGNAL_PRIORITY_HIGHEST);
7595 void
7596 pidgin_blist_uninit(void) {
7597 g_hash_table_destroy(cached_emblems);
7599 purple_signals_unregister_by_instance(pidgin_blist_get_handle());
7600 purple_signals_disconnect_by_handle(pidgin_blist_get_handle());
7603 /*********************************************************************
7604 * Buddy List sorting functions *
7605 *********************************************************************/
7607 GList *pidgin_blist_get_sort_methods()
7609 return pidgin_blist_sort_methods;
7612 void pidgin_blist_sort_method_reg(const char *id, const char *name, pidgin_blist_sort_function func)
7614 struct pidgin_blist_sort_method *method;
7616 g_return_if_fail(id != NULL);
7617 g_return_if_fail(name != NULL);
7618 g_return_if_fail(func != NULL);
7620 method = g_new0(struct pidgin_blist_sort_method, 1);
7621 method->id = g_strdup(id);
7622 method->name = g_strdup(name);
7623 method->func = func;
7624 pidgin_blist_sort_methods = g_list_append(pidgin_blist_sort_methods, method);
7625 pidgin_blist_update_sort_methods();
7628 void pidgin_blist_sort_method_unreg(const char *id)
7630 GList *l = pidgin_blist_sort_methods;
7632 g_return_if_fail(id != NULL);
7634 while(l) {
7635 struct pidgin_blist_sort_method *method = l->data;
7636 if(!strcmp(method->id, id)) {
7637 pidgin_blist_sort_methods = g_list_delete_link(pidgin_blist_sort_methods, l);
7638 g_free(method->id);
7639 g_free(method->name);
7640 g_free(method);
7641 break;
7643 l = l->next;
7645 pidgin_blist_update_sort_methods();
7648 void pidgin_blist_sort_method_set(const char *id){
7649 GList *l = pidgin_blist_sort_methods;
7651 if(!id)
7652 id = "none";
7654 while (l && strcmp(((struct pidgin_blist_sort_method*)l->data)->id, id))
7655 l = l->next;
7657 if (l) {
7658 current_sort_method = l->data;
7659 } else if (!current_sort_method) {
7660 pidgin_blist_sort_method_set("none");
7661 return;
7663 if (!strcmp(id, "none")) {
7664 redo_buddy_list(purple_get_blist(), TRUE, FALSE);
7665 } else {
7666 redo_buddy_list(purple_get_blist(), FALSE, FALSE);
7670 /******************************************
7671 ** Sort Methods
7672 ******************************************/
7674 static void sort_method_none(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter parent_iter, GtkTreeIter *cur, GtkTreeIter *iter)
7676 PurpleBlistNode *sibling = node->prev;
7677 GtkTreeIter sibling_iter;
7679 if (cur != NULL) {
7680 *iter = *cur;
7681 return;
7684 while (sibling && !get_iter_from_node(sibling, &sibling_iter)) {
7685 sibling = sibling->prev;
7688 gtk_tree_store_insert_after(gtkblist->treemodel, iter,
7689 node->parent ? &parent_iter : NULL,
7690 sibling ? &sibling_iter : NULL);
7693 static void sort_method_alphabetical(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter)
7695 GtkTreeIter more_z;
7697 const char *my_name;
7699 if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
7700 my_name = purple_contact_get_alias((PurpleContact*)node);
7701 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
7702 my_name = purple_chat_get_name((PurpleChat*)node);
7703 } else {
7704 sort_method_none(node, blist, groupiter, cur, iter);
7705 return;
7708 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(gtkblist->treemodel), &more_z, &groupiter)) {
7709 gtk_tree_store_insert(gtkblist->treemodel, iter, &groupiter, 0);
7710 return;
7713 do {
7714 PurpleBlistNode *n;
7715 const char *this_name;
7716 int cmp;
7718 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &more_z, NODE_COLUMN, &n, -1);
7720 if(PURPLE_BLIST_NODE_IS_CONTACT(n)) {
7721 this_name = purple_contact_get_alias((PurpleContact*)n);
7722 } else if(PURPLE_BLIST_NODE_IS_CHAT(n)) {
7723 this_name = purple_chat_get_name((PurpleChat*)n);
7724 } else {
7725 this_name = NULL;
7728 cmp = purple_utf8_strcasecmp(my_name, this_name);
7730 if(this_name && (cmp < 0 || (cmp == 0 && node < n))) {
7731 if(cur) {
7732 gtk_tree_store_move_before(gtkblist->treemodel, cur, &more_z);
7733 *iter = *cur;
7734 return;
7735 } else {
7736 gtk_tree_store_insert_before(gtkblist->treemodel, iter,
7737 &groupiter, &more_z);
7738 return;
7741 } while (gtk_tree_model_iter_next (GTK_TREE_MODEL(gtkblist->treemodel), &more_z));
7743 if(cur) {
7744 gtk_tree_store_move_before(gtkblist->treemodel, cur, NULL);
7745 *iter = *cur;
7746 return;
7747 } else {
7748 gtk_tree_store_append(gtkblist->treemodel, iter, &groupiter);
7749 return;
7753 static void sort_method_status(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter)
7755 GtkTreeIter more_z;
7757 PurpleBuddy *my_buddy, *this_buddy;
7759 if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
7760 my_buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
7761 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
7762 if (cur != NULL) {
7763 *iter = *cur;
7764 return;
7767 gtk_tree_store_append(gtkblist->treemodel, iter, &groupiter);
7768 return;
7769 } else {
7770 sort_method_alphabetical(node, blist, groupiter, cur, iter);
7771 return;
7775 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(gtkblist->treemodel), &more_z, &groupiter)) {
7776 gtk_tree_store_insert(gtkblist->treemodel, iter, &groupiter, 0);
7777 return;
7780 do {
7781 PurpleBlistNode *n;
7782 gint name_cmp;
7783 gint presence_cmp;
7785 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &more_z, NODE_COLUMN, &n, -1);
7787 if(PURPLE_BLIST_NODE_IS_CONTACT(n)) {
7788 this_buddy = purple_contact_get_priority_buddy((PurpleContact*)n);
7789 } else {
7790 this_buddy = NULL;
7793 name_cmp = purple_utf8_strcasecmp(
7794 purple_contact_get_alias(purple_buddy_get_contact(my_buddy)),
7795 (this_buddy
7796 ? purple_contact_get_alias(purple_buddy_get_contact(this_buddy))
7797 : NULL));
7799 presence_cmp = purple_presence_compare(
7800 purple_buddy_get_presence(my_buddy),
7801 this_buddy ? purple_buddy_get_presence(this_buddy) : NULL);
7803 if (this_buddy == NULL ||
7804 (presence_cmp < 0 ||
7805 (presence_cmp == 0 &&
7806 (name_cmp < 0 || (name_cmp == 0 && node < n)))))
7808 if (cur != NULL)
7810 gtk_tree_store_move_before(gtkblist->treemodel, cur, &more_z);
7811 *iter = *cur;
7812 return;
7814 else
7816 gtk_tree_store_insert_before(gtkblist->treemodel, iter,
7817 &groupiter, &more_z);
7818 return;
7822 while (gtk_tree_model_iter_next(GTK_TREE_MODEL(gtkblist->treemodel),
7823 &more_z));
7825 if (cur) {
7826 gtk_tree_store_move_before(gtkblist->treemodel, cur, NULL);
7827 *iter = *cur;
7828 return;
7829 } else {
7830 gtk_tree_store_append(gtkblist->treemodel, iter, &groupiter);
7831 return;
7835 static void sort_method_log_activity(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter)
7837 GtkTreeIter more_z;
7839 int activity_score = 0, this_log_activity_score = 0;
7840 const char *buddy_name, *this_buddy_name;
7842 if(cur && (gtk_tree_model_iter_n_children(GTK_TREE_MODEL(gtkblist->treemodel), &groupiter) == 1)) {
7843 *iter = *cur;
7844 return;
7847 if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
7848 PurpleBlistNode *n;
7849 PurpleBuddy *buddy;
7850 for (n = node->child; n; n = n->next) {
7851 buddy = (PurpleBuddy*)n;
7852 activity_score += purple_log_get_activity_score(PURPLE_LOG_IM, buddy->name, buddy->account);
7854 buddy_name = purple_contact_get_alias((PurpleContact*)node);
7855 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
7856 /* we don't have a reliable way of getting the log filename
7857 * from the chat info in the blist, yet */
7858 if (cur != NULL) {
7859 *iter = *cur;
7860 return;
7863 gtk_tree_store_append(gtkblist->treemodel, iter, &groupiter);
7864 return;
7865 } else {
7866 sort_method_none(node, blist, groupiter, cur, iter);
7867 return;
7871 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(gtkblist->treemodel), &more_z, &groupiter)) {
7872 gtk_tree_store_insert(gtkblist->treemodel, iter, &groupiter, 0);
7873 return;
7876 do {
7877 PurpleBlistNode *n;
7878 PurpleBlistNode *n2;
7879 PurpleBuddy *buddy;
7880 int cmp;
7882 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &more_z, NODE_COLUMN, &n, -1);
7883 this_log_activity_score = 0;
7885 if(PURPLE_BLIST_NODE_IS_CONTACT(n)) {
7886 for (n2 = n->child; n2; n2 = n2->next) {
7887 buddy = (PurpleBuddy*)n2;
7888 this_log_activity_score += purple_log_get_activity_score(PURPLE_LOG_IM, buddy->name, buddy->account);
7890 this_buddy_name = purple_contact_get_alias((PurpleContact*)n);
7891 } else {
7892 this_buddy_name = NULL;
7895 cmp = purple_utf8_strcasecmp(buddy_name, this_buddy_name);
7897 if (!PURPLE_BLIST_NODE_IS_CONTACT(n) || activity_score > this_log_activity_score ||
7898 ((activity_score == this_log_activity_score) &&
7899 (cmp < 0 || (cmp == 0 && node < n)))) {
7900 if (cur != NULL) {
7901 gtk_tree_store_move_before(gtkblist->treemodel, cur, &more_z);
7902 *iter = *cur;
7903 return;
7904 } else {
7905 gtk_tree_store_insert_before(gtkblist->treemodel, iter,
7906 &groupiter, &more_z);
7907 return;
7910 } while (gtk_tree_model_iter_next (GTK_TREE_MODEL(gtkblist->treemodel), &more_z));
7912 if (cur != NULL) {
7913 gtk_tree_store_move_before(gtkblist->treemodel, cur, NULL);
7914 *iter = *cur;
7915 return;
7916 } else {
7917 gtk_tree_store_append(gtkblist->treemodel, iter, &groupiter);
7918 return;
7922 static void
7923 plugin_act(GtkObject *obj, PurplePluginAction *pam)
7925 if (pam && pam->callback)
7926 pam->callback(pam);
7929 static void
7930 build_plugin_actions(GtkWidget *menu, PurplePlugin *plugin,
7931 gpointer context)
7933 GtkWidget *menuitem;
7934 PurplePluginAction *action = NULL;
7935 GList *actions, *l;
7937 actions = PURPLE_PLUGIN_ACTIONS(plugin, context);
7939 for (l = actions; l != NULL; l = l->next)
7941 if (l->data)
7943 action = (PurplePluginAction *) l->data;
7944 action->plugin = plugin;
7945 action->context = context;
7947 menuitem = gtk_menu_item_new_with_label(action->label);
7948 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
7950 g_signal_connect(G_OBJECT(menuitem), "activate",
7951 G_CALLBACK(plugin_act), action);
7952 g_object_set_data_full(G_OBJECT(menuitem), "plugin_action",
7953 action,
7954 (GDestroyNotify)purple_plugin_action_free);
7955 gtk_widget_show(menuitem);
7957 else
7958 pidgin_separator(menu);
7961 g_list_free(actions);
7964 static void
7965 modify_account_cb(GtkWidget *widget, gpointer data)
7967 pidgin_account_dialog_show(PIDGIN_MODIFY_ACCOUNT_DIALOG, data);
7970 static void
7971 enable_account_cb(GtkCheckMenuItem *widget, gpointer data)
7973 PurpleAccount *account = data;
7974 const PurpleSavedStatus *saved_status;
7976 saved_status = purple_savedstatus_get_current();
7977 purple_savedstatus_activate_for_account(saved_status, account);
7979 purple_account_set_enabled(account, PIDGIN_UI, TRUE);
7982 static void
7983 disable_account_cb(GtkCheckMenuItem *widget, gpointer data)
7985 PurpleAccount *account = data;
7987 purple_account_set_enabled(account, PIDGIN_UI, FALSE);
7992 void
7993 pidgin_blist_update_accounts_menu(void)
7995 GtkWidget *menuitem = NULL, *submenu = NULL;
7996 GtkAccelGroup *accel_group = NULL;
7997 GList *l = NULL, *accounts = NULL;
7998 gboolean disabled_accounts = FALSE;
7999 gboolean enabled_accounts = FALSE;
8001 if (accountmenu == NULL)
8002 return;
8004 /* Clear the old Accounts menu */
8005 for (l = gtk_container_get_children(GTK_CONTAINER(accountmenu)); l; l = g_list_delete_link(l, l)) {
8006 menuitem = l->data;
8008 if (menuitem != gtk_item_factory_get_widget(gtkblist->ift, N_("/Accounts/Manage Accounts")))
8009 gtk_widget_destroy(menuitem);
8012 for (accounts = purple_accounts_get_all(); accounts; accounts = accounts->next) {
8013 char *buf = NULL;
8014 GtkWidget *image = NULL;
8015 PurpleAccount *account = NULL;
8016 GdkPixbuf *pixbuf = NULL;
8018 account = accounts->data;
8020 if(!purple_account_get_enabled(account, PIDGIN_UI)) {
8021 if (!disabled_accounts) {
8022 menuitem = gtk_menu_item_new_with_label(_("Enable Account"));
8023 gtk_menu_shell_append(GTK_MENU_SHELL(accountmenu), menuitem);
8025 submenu = gtk_menu_new();
8026 gtk_menu_set_accel_group(GTK_MENU(submenu), accel_group);
8027 gtk_menu_set_accel_path(GTK_MENU(submenu), N_("<PurpleMain>/Accounts/Enable Account"));
8028 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
8030 disabled_accounts = TRUE;
8033 buf = g_strconcat(purple_account_get_username(account), " (",
8034 purple_account_get_protocol_name(account), ")", NULL);
8035 menuitem = gtk_image_menu_item_new_with_label(buf);
8036 g_free(buf);
8037 pixbuf = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL);
8038 if (pixbuf != NULL)
8040 if (!purple_account_is_connected(account))
8041 gdk_pixbuf_saturate_and_pixelate(pixbuf, pixbuf, 0.0, FALSE);
8042 image = gtk_image_new_from_pixbuf(pixbuf);
8043 g_object_unref(G_OBJECT(pixbuf));
8044 gtk_widget_show(image);
8045 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image);
8047 g_signal_connect(G_OBJECT(menuitem), "activate",
8048 G_CALLBACK(enable_account_cb), account);
8049 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), menuitem);
8050 } else {
8051 enabled_accounts = TRUE;
8055 if (!enabled_accounts) {
8056 gtk_widget_show_all(accountmenu);
8057 return;
8060 pidgin_separator(accountmenu);
8061 accel_group = gtk_menu_get_accel_group(GTK_MENU(accountmenu));
8063 for (accounts = purple_accounts_get_all(); accounts; accounts = accounts->next) {
8064 char *buf = NULL;
8065 char *accel_path_buf = NULL;
8066 GtkWidget *image = NULL;
8067 PurpleConnection *gc = NULL;
8068 PurpleAccount *account = NULL;
8069 GdkPixbuf *pixbuf = NULL;
8070 PurplePlugin *plugin = NULL;
8071 PurplePluginProtocolInfo *prpl_info;
8073 account = accounts->data;
8075 if (!purple_account_get_enabled(account, PIDGIN_UI))
8076 continue;
8078 buf = g_strconcat(purple_account_get_username(account), " (",
8079 purple_account_get_protocol_name(account), ")", NULL);
8080 menuitem = gtk_image_menu_item_new_with_label(buf);
8081 accel_path_buf = g_strconcat(N_("<PurpleMain>/Accounts/"), buf, NULL);
8082 g_free(buf);
8083 pixbuf = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL);
8084 if (pixbuf != NULL) {
8085 if (!purple_account_is_connected(account))
8086 gdk_pixbuf_saturate_and_pixelate(pixbuf, pixbuf,
8087 0.0, FALSE);
8088 image = gtk_image_new_from_pixbuf(pixbuf);
8089 g_object_unref(G_OBJECT(pixbuf));
8090 gtk_widget_show(image);
8091 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image);
8093 gtk_menu_shell_append(GTK_MENU_SHELL(accountmenu), menuitem);
8095 submenu = gtk_menu_new();
8096 gtk_menu_set_accel_group(GTK_MENU(submenu), accel_group);
8097 gtk_menu_set_accel_path(GTK_MENU(submenu), accel_path_buf);
8098 g_free(accel_path_buf);
8099 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
8102 menuitem = gtk_menu_item_new_with_mnemonic(_("_Edit Account"));
8103 g_signal_connect(G_OBJECT(menuitem), "activate",
8104 G_CALLBACK(modify_account_cb), account);
8105 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), menuitem);
8107 pidgin_separator(submenu);
8109 gc = purple_account_get_connection(account);
8110 plugin = gc && PURPLE_CONNECTION_IS_CONNECTED(gc) ? gc->prpl : NULL;
8111 prpl_info = plugin ? PURPLE_PLUGIN_PROTOCOL_INFO(plugin) : NULL;
8113 if (prpl_info &&
8114 (PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, get_moods) ||
8115 PURPLE_PLUGIN_HAS_ACTIONS(plugin))) {
8116 if (PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, get_moods) &&
8117 gc->flags & PURPLE_CONNECTION_SUPPORT_MOODS) {
8119 if (purple_account_get_status(account, "mood")) {
8120 menuitem = gtk_menu_item_new_with_mnemonic(_("Set _Mood..."));
8121 g_signal_connect(G_OBJECT(menuitem), "activate",
8122 G_CALLBACK(set_mood_cb), account);
8123 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), menuitem);
8126 if (PURPLE_PLUGIN_HAS_ACTIONS(plugin)) {
8127 build_plugin_actions(submenu, plugin, gc);
8129 } else {
8130 menuitem = gtk_menu_item_new_with_label(_("No actions available"));
8131 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), menuitem);
8132 gtk_widget_set_sensitive(menuitem, FALSE);
8135 pidgin_separator(submenu);
8137 menuitem = gtk_menu_item_new_with_mnemonic(_("_Disable"));
8138 g_signal_connect(G_OBJECT(menuitem), "activate",
8139 G_CALLBACK(disable_account_cb), account);
8140 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), menuitem);
8142 gtk_widget_show_all(accountmenu);
8145 static GList *plugin_submenus = NULL;
8147 void
8148 pidgin_blist_update_plugin_actions(void)
8150 GtkWidget *menuitem, *submenu;
8151 PurplePlugin *plugin = NULL;
8152 GList *l;
8153 GtkAccelGroup *accel_group;
8155 GtkWidget *pluginmenu = gtk_item_factory_get_widget(gtkblist->ift, N_("/Tools"));
8157 g_return_if_fail(pluginmenu != NULL);
8159 /* Remove old plugin action submenus from the Tools menu */
8160 for (l = plugin_submenus; l; l = l->next)
8161 gtk_widget_destroy(GTK_WIDGET(l->data));
8162 g_list_free(plugin_submenus);
8163 plugin_submenus = NULL;
8165 accel_group = gtk_menu_get_accel_group(GTK_MENU(pluginmenu));
8167 /* Add a submenu for each plugin with custom actions */
8168 for (l = purple_plugins_get_loaded(); l; l = l->next) {
8169 char *path;
8171 plugin = (PurplePlugin *) l->data;
8173 if (PURPLE_IS_PROTOCOL_PLUGIN(plugin))
8174 continue;
8176 if (!PURPLE_PLUGIN_HAS_ACTIONS(plugin))
8177 continue;
8179 menuitem = gtk_image_menu_item_new_with_label(_(plugin->info->name));
8180 gtk_menu_shell_append(GTK_MENU_SHELL(pluginmenu), menuitem);
8182 plugin_submenus = g_list_append(plugin_submenus, menuitem);
8184 submenu = gtk_menu_new();
8185 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
8187 gtk_menu_set_accel_group(GTK_MENU(submenu), accel_group);
8188 path = g_strdup_printf("%s/Tools/%s", gtkblist->ift->path, plugin->info->name);
8189 gtk_menu_set_accel_path(GTK_MENU(submenu), path);
8190 g_free(path);
8192 build_plugin_actions(submenu, plugin, NULL);
8194 gtk_widget_show_all(pluginmenu);
8197 static void
8198 sortmethod_act(GtkCheckMenuItem *checkmenuitem, char *id)
8200 if (gtk_check_menu_item_get_active(checkmenuitem))
8202 pidgin_set_cursor(gtkblist->window, GDK_WATCH);
8203 /* This is redundant. I think. */
8204 /* pidgin_blist_sort_method_set(id); */
8205 purple_prefs_set_string(PIDGIN_PREFS_ROOT "/blist/sort_type", id);
8207 pidgin_clear_cursor(gtkblist->window);
8211 void
8212 pidgin_blist_update_sort_methods(void)
8214 GtkWidget *menuitem = NULL, *activeitem = NULL;
8215 PidginBlistSortMethod *method = NULL;
8216 GList *l;
8217 GSList *sl = NULL;
8218 GtkWidget *sortmenu;
8219 const char *m = purple_prefs_get_string(PIDGIN_PREFS_ROOT "/blist/sort_type");
8221 if ((gtkblist == NULL) || (gtkblist->ift == NULL))
8222 return;
8224 g_return_if_fail(m != NULL);
8226 sortmenu = gtk_item_factory_get_widget(gtkblist->ift, N_("/Buddies/Sort Buddies"));
8228 if (sortmenu == NULL)
8229 return;
8231 /* Clear the old menu */
8232 for (l = gtk_container_get_children(GTK_CONTAINER(sortmenu)); l; l = g_list_delete_link(l, l)) {
8233 menuitem = l->data;
8234 gtk_widget_destroy(GTK_WIDGET(menuitem));
8237 for (l = pidgin_blist_sort_methods; l; l = l->next) {
8238 method = (PidginBlistSortMethod *) l->data;
8239 menuitem = gtk_radio_menu_item_new_with_label(sl, _(method->name));
8240 if (g_str_equal(m, method->id))
8241 activeitem = menuitem;
8242 sl = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(menuitem));
8243 gtk_menu_shell_append(GTK_MENU_SHELL(sortmenu), menuitem);
8244 g_signal_connect(G_OBJECT(menuitem), "toggled",
8245 G_CALLBACK(sortmethod_act), method->id);
8246 gtk_widget_show(menuitem);
8248 if (activeitem)
8249 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(activeitem), TRUE);