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