Stop trying to get "gtk-autojoin" as a string from chat rooms in the buddy
[pidgin-git.git] / pidgin / gtkblist.c
blob305dd28ccd16a07c293a26e2043f4949ef83f091
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;
89 } PidginAddBuddyData;
91 typedef struct
93 PidginBlistRequestData rq_data;
94 gchar *default_chat_name;
95 GList *entries;
96 } PidginChatData;
98 typedef struct
100 PidginChatData chat_data;
102 GtkWidget *alias_entry;
103 GtkWidget *group_combo;
104 GtkWidget *autojoin;
105 GtkWidget *persistent;
106 } PidginAddChatData;
108 typedef struct
110 /** Used to hold error minidialogs. Gets packed
111 * inside PidginBuddyList.error_buttons
113 PidginScrollBook *error_scrollbook;
115 /** Pointer to the mini-dialog about having signed on elsewhere, if one
116 * is showing; @c NULL otherwise.
118 PidginMiniDialog *signed_on_elsewhere;
120 PidginBlistTheme *current_theme;
121 } PidginBuddyListPrivate;
123 #define PIDGIN_BUDDY_LIST_GET_PRIVATE(list) \
124 ((PidginBuddyListPrivate *)((list)->priv))
126 static GtkWidget *accountmenu = NULL;
128 static guint visibility_manager_count = 0;
129 static GdkVisibilityState gtk_blist_visibility = GDK_VISIBILITY_UNOBSCURED;
130 static gboolean gtk_blist_focused = FALSE;
131 static gboolean editing_blist = FALSE;
133 static GList *pidgin_blist_sort_methods = NULL;
134 static struct pidgin_blist_sort_method *current_sort_method = NULL;
135 static void sort_method_none(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter);
137 static void sort_method_alphabetical(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter);
138 static void sort_method_status(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter);
139 static void sort_method_log_activity(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter);
140 static PidginBuddyList *gtkblist = NULL;
142 static GList *groups_tree(void);
143 static gboolean pidgin_blist_refresh_timer(PurpleBuddyList *list);
144 static void pidgin_blist_update_buddy(PurpleBuddyList *list, PurpleBlistNode *node, gboolean status_change);
145 static void pidgin_blist_selection_changed(GtkTreeSelection *selection, gpointer data);
146 static void pidgin_blist_update(PurpleBuddyList *list, PurpleBlistNode *node);
147 static void pidgin_blist_update_group(PurpleBuddyList *list, PurpleBlistNode *node);
148 static void pidgin_blist_update_contact(PurpleBuddyList *list, PurpleBlistNode *node);
149 static char *pidgin_get_tooltip_text(PurpleBlistNode *node, gboolean full);
150 static const char *item_factory_translate_func (const char *path, gpointer func_data);
151 static gboolean get_iter_from_node(PurpleBlistNode *node, GtkTreeIter *iter);
152 static gboolean buddy_is_displayable(PurpleBuddy *buddy);
153 static void redo_buddy_list(PurpleBuddyList *list, gboolean remove, gboolean rerender);
154 static void pidgin_blist_collapse_contact_cb(GtkWidget *w, PurpleBlistNode *node);
155 static char *pidgin_get_group_title(PurpleBlistNode *gnode, gboolean expanded);
156 static void pidgin_blist_expand_contact_cb(GtkWidget *w, PurpleBlistNode *node);
157 static void set_urgent(void);
159 typedef enum {
160 PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE = 1 << 0, /* Whether there's pending message in a conversation */
161 PIDGIN_BLIST_CHAT_HAS_PENDING_MESSAGE_WITH_NICK = 1 << 1, /* Whether there's a pending message in a chat that mentions our nick */
162 } PidginBlistNodeFlags;
164 typedef struct _pidgin_blist_node {
165 GtkTreeRowReference *row;
166 gboolean contact_expanded;
167 gboolean recent_signonoff;
168 gint recent_signonoff_timer;
169 struct {
170 PurpleConversation *conv;
171 time_t last_message; /* timestamp for last displayed message */
172 PidginBlistNodeFlags flags;
173 } conv;
174 } PidginBlistNode;
176 /***************************************************
177 * Callbacks *
178 ***************************************************/
179 static gboolean gtk_blist_visibility_cb(GtkWidget *w, GdkEventVisibility *event, gpointer data)
181 GdkVisibilityState old_state = gtk_blist_visibility;
182 gtk_blist_visibility = event->state;
184 if (gtk_blist_visibility == GDK_VISIBILITY_FULLY_OBSCURED &&
185 old_state != GDK_VISIBILITY_FULLY_OBSCURED) {
187 /* no longer fully obscured */
188 pidgin_blist_refresh_timer(purple_get_blist());
191 /* continue to handle event normally */
192 return FALSE;
195 static gboolean gtk_blist_window_state_cb(GtkWidget *w, GdkEventWindowState *event, gpointer data)
197 if(event->changed_mask & GDK_WINDOW_STATE_WITHDRAWN) {
198 if(event->new_window_state & GDK_WINDOW_STATE_WITHDRAWN)
199 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/list_visible", FALSE);
200 else {
201 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/list_visible", TRUE);
202 pidgin_blist_refresh_timer(purple_get_blist());
206 if(event->changed_mask & GDK_WINDOW_STATE_MAXIMIZED) {
207 if(event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED)
208 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/list_maximized", TRUE);
209 else
210 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/list_maximized", FALSE);
213 /* Refresh gtkblist if un-iconifying */
214 if (event->changed_mask & GDK_WINDOW_STATE_ICONIFIED){
215 if (!(event->new_window_state & GDK_WINDOW_STATE_ICONIFIED))
216 pidgin_blist_refresh_timer(purple_get_blist());
219 return FALSE;
222 static gboolean gtk_blist_delete_cb(GtkWidget *w, GdkEventAny *event, gpointer data)
224 if(visibility_manager_count)
225 purple_blist_set_visible(FALSE);
226 else
227 purple_core_quit();
229 /* we handle everything, event should not propogate further */
230 return TRUE;
233 static gboolean gtk_blist_configure_cb(GtkWidget *w, GdkEventConfigure *event, gpointer data)
235 /* unfortunately GdkEventConfigure ignores the window gravity, but *
236 * the only way we have of setting the position doesn't. we have to *
237 * call get_position because it does pay attention to the gravity. *
238 * this is inefficient and I agree it sucks, but it's more likely *
239 * to work correctly. - Robot101 */
240 gint x, y;
242 /* check for visibility because when we aren't visible, this will *
243 * give us bogus (0,0) coordinates. - xOr */
244 if (GTK_WIDGET_VISIBLE(w))
245 gtk_window_get_position(GTK_WINDOW(w), &x, &y);
246 else
247 return FALSE; /* carry on normally */
249 #ifdef _WIN32
250 /* Workaround for GTK+ bug # 169811 - "configure_event" is fired
251 * when the window is being maximized */
252 if (gdk_window_get_state(w->window)
253 & GDK_WINDOW_STATE_MAXIMIZED) {
254 return FALSE;
256 #endif
258 /* don't save if nothing changed */
259 if (x == purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/x") &&
260 y == purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/y") &&
261 event->width == purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/width") &&
262 event->height == purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/height")) {
264 return FALSE; /* carry on normally */
267 /* don't save off-screen positioning */
268 if (x + event->width < 0 ||
269 y + event->height < 0 ||
270 x > gdk_screen_width() ||
271 y > gdk_screen_height()) {
273 return FALSE; /* carry on normally */
276 /* ignore changes when maximized */
277 if(purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/list_maximized"))
278 return FALSE;
280 /* store the position */
281 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/blist/x", x);
282 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/blist/y", y);
283 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/blist/width", event->width);
284 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/blist/height", event->height);
286 /* continue to handle event normally */
287 return FALSE;
290 static void gtk_blist_menu_info_cb(GtkWidget *w, PurpleBuddy *b)
292 PurpleAccount *account = purple_buddy_get_account(b);
294 pidgin_retrieve_user_info(purple_account_get_connection(account),
295 purple_buddy_get_name(b));
298 static void gtk_blist_menu_im_cb(GtkWidget *w, PurpleBuddy *b)
300 pidgin_dialogs_im_with_user(purple_buddy_get_account(b),
301 purple_buddy_get_name(b));
304 #ifdef USE_VV
305 static void gtk_blist_menu_audio_call_cb(GtkWidget *w, PurpleBuddy *b)
307 purple_prpl_initiate_media(purple_buddy_get_account(b),
308 purple_buddy_get_name(b), PURPLE_MEDIA_AUDIO);
311 static void gtk_blist_menu_video_call_cb(GtkWidget *w, PurpleBuddy *b)
313 /* if the buddy supports both audio and video, start a combined call,
314 otherwise start a pure video session */
315 if (purple_prpl_get_media_caps(purple_buddy_get_account(b),
316 purple_buddy_get_name(b)) &
317 PURPLE_MEDIA_CAPS_AUDIO_VIDEO) {
318 purple_prpl_initiate_media(purple_buddy_get_account(b),
319 purple_buddy_get_name(b), PURPLE_MEDIA_AUDIO | PURPLE_MEDIA_VIDEO);
320 } else {
321 purple_prpl_initiate_media(purple_buddy_get_account(b),
322 purple_buddy_get_name(b), PURPLE_MEDIA_VIDEO);
326 #endif
328 static void gtk_blist_menu_send_file_cb(GtkWidget *w, PurpleBuddy *b)
330 PurpleAccount *account = purple_buddy_get_account(b);
332 serv_send_file(purple_account_get_connection(account),
333 purple_buddy_get_name(b), NULL);
336 static void gtk_blist_menu_move_to_cb(GtkWidget *w, PurpleBlistNode *node)
338 PurpleGroup *group = g_object_get_data(G_OBJECT(w), "groupnode");
339 purple_blist_add_contact((PurpleContact *)node, group, NULL);
343 static void gtk_blist_menu_autojoin_cb(GtkWidget *w, PurpleChat *chat)
345 purple_blist_node_set_bool((PurpleBlistNode*)chat, "gtk-autojoin",
346 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w)));
349 static void gtk_blist_menu_persistent_cb(GtkWidget *w, PurpleChat *chat)
351 purple_blist_node_set_bool((PurpleBlistNode*)chat, "gtk-persistent",
352 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w)));
355 static PurpleConversation *
356 find_conversation_with_buddy(PurpleBuddy *buddy)
358 PidginBlistNode *ui = purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(buddy));
359 if (ui)
360 return ui->conv.conv;
361 return purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
362 purple_buddy_get_name(buddy),
363 purple_buddy_get_account(buddy));
366 static void gtk_blist_join_chat(PurpleChat *chat)
368 PurpleAccount *account;
369 PurpleConversation *conv;
370 PurplePluginProtocolInfo *prpl_info;
371 GHashTable *components;
372 const char *name;
373 char *chat_name;
375 account = purple_chat_get_account(chat);
376 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_find_prpl(purple_account_get_protocol_id(account)));
378 components = purple_chat_get_components(chat);
380 if (prpl_info && prpl_info->get_chat_name)
381 chat_name = prpl_info->get_chat_name(components);
382 else
383 chat_name = NULL;
385 if (chat_name)
386 name = chat_name;
387 else
388 name = purple_chat_get_name(chat);
390 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, name,
391 account);
393 if (conv != NULL) {
394 pidgin_conv_attach_to_conversation(conv);
395 purple_conversation_present(conv);
398 serv_join_chat(purple_account_get_connection(account), components);
399 g_free(chat_name);
402 static void gtk_blist_menu_join_cb(GtkWidget *w, PurpleChat *chat)
404 gtk_blist_join_chat(chat);
407 static void gtk_blist_renderer_editing_cancelled_cb(GtkCellRenderer *renderer, PurpleBuddyList *list)
409 editing_blist = FALSE;
410 g_object_set(G_OBJECT(renderer), "editable", FALSE, NULL);
411 pidgin_blist_refresh(list);
414 static void gtk_blist_renderer_editing_started_cb(GtkCellRenderer *renderer,
415 GtkCellEditable *editable,
416 gchar *path_str,
417 gpointer user_data)
419 GtkTreeIter iter;
420 GtkTreePath *path = NULL;
421 PurpleBlistNode *node;
422 const char *text = NULL;
424 path = gtk_tree_path_new_from_string (path_str);
425 gtk_tree_model_get_iter (GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
426 gtk_tree_path_free (path);
427 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
429 switch (purple_blist_node_get_type(node)) {
430 case PURPLE_BLIST_CONTACT_NODE:
431 text = purple_contact_get_alias(PURPLE_CONTACT(node));
432 break;
433 case PURPLE_BLIST_BUDDY_NODE:
434 text = purple_buddy_get_alias(PURPLE_BUDDY(node));
435 break;
436 case PURPLE_BLIST_GROUP_NODE:
437 text = purple_group_get_name(PURPLE_GROUP(node));
438 break;
439 case PURPLE_BLIST_CHAT_NODE:
440 text = purple_chat_get_name(PURPLE_CHAT(node));
441 break;
442 default:
443 g_return_if_reached();
446 if (GTK_IS_ENTRY (editable)) {
447 GtkEntry *entry = GTK_ENTRY (editable);
448 gtk_entry_set_text(entry, text);
450 editing_blist = TRUE;
453 static void
454 gtk_blist_do_personize(GList *merges)
456 PurpleBlistNode *contact = NULL;
457 int max = 0;
458 GList *tmp;
460 /* First, we find the contact to merge the rest of the buddies into.
461 * This will be the contact with the most buddies in it; ties are broken
462 * by which contact is higher in the list
464 for (tmp = merges; tmp; tmp = tmp->next) {
465 PurpleBlistNode *node = tmp->data;
466 PurpleBlistNode *b;
467 PurpleBlistNodeType type;
468 int i = 0;
470 type = purple_blist_node_get_type(node);
472 if (type == PURPLE_BLIST_BUDDY_NODE) {
473 node = purple_blist_node_get_parent(node);
474 type = purple_blist_node_get_type(node);
477 if (type != PURPLE_BLIST_CONTACT_NODE)
478 continue;
480 for (b = purple_blist_node_get_first_child(node);
482 b = purple_blist_node_get_sibling_next(b))
484 i++;
487 if (i > max) {
488 contact = node;
489 max = i;
493 if (contact == NULL)
494 return;
496 /* Merge all those buddies into this contact */
497 for (tmp = merges; tmp; tmp = tmp->next) {
498 PurpleBlistNode *node = tmp->data;
499 if (purple_blist_node_get_type(node) == PURPLE_BLIST_BUDDY_NODE)
500 node = purple_blist_node_get_parent(node);
502 if (node == contact)
503 continue;
505 purple_blist_merge_contact((PurpleContact *)node, contact);
508 /* And show the expanded contact, so the people know what's going on */
509 pidgin_blist_expand_contact_cb(NULL, contact);
510 g_list_free(merges);
513 static void
514 gtk_blist_auto_personize(PurpleBlistNode *group, const char *alias)
516 PurpleBlistNode *contact;
517 PurpleBlistNode *buddy;
518 GList *merges = NULL;
519 int i = 0;
520 char *a = g_utf8_casefold(alias, -1);
522 for (contact = purple_blist_node_get_first_child(group);
523 contact != NULL;
524 contact = purple_blist_node_get_sibling_next(contact)) {
525 char *node_alias;
526 if (purple_blist_node_get_type(contact) != PURPLE_BLIST_CONTACT_NODE)
527 continue;
529 node_alias = g_utf8_casefold(purple_contact_get_alias((PurpleContact *)contact), -1);
530 if (node_alias && !g_utf8_collate(node_alias, a)) {
531 merges = g_list_append(merges, contact);
532 i++;
533 g_free(node_alias);
534 continue;
536 g_free(node_alias);
538 for (buddy = purple_blist_node_get_first_child(contact);
539 buddy;
540 buddy = purple_blist_node_get_sibling_next(buddy))
542 if (purple_blist_node_get_type(buddy) != PURPLE_BLIST_BUDDY_NODE)
543 continue;
545 node_alias = g_utf8_casefold(purple_buddy_get_alias(PURPLE_BUDDY(buddy)), -1);
546 if (node_alias && !g_utf8_collate(node_alias, a)) {
547 merges = g_list_append(merges, buddy);
548 i++;
549 g_free(node_alias);
550 break;
552 g_free(node_alias);
555 g_free(a);
557 if (i > 1)
559 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);
560 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. "
561 "You can separate them again by choosing 'Expand' from the contact's context menu"), 0, NULL, NULL, NULL,
562 merges, 2, _("_Yes"), PURPLE_CALLBACK(gtk_blist_do_personize), _("_No"), PURPLE_CALLBACK(g_list_free));
563 g_free(msg);
564 } else
565 g_list_free(merges);
568 static void gtk_blist_renderer_edited_cb(GtkCellRendererText *text_rend, char *arg1,
569 char *arg2, PurpleBuddyList *list)
571 GtkTreeIter iter;
572 GtkTreePath *path;
573 PurpleBlistNode *node;
574 PurpleGroup *dest;
576 editing_blist = FALSE;
577 path = gtk_tree_path_new_from_string (arg1);
578 gtk_tree_model_get_iter (GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
579 gtk_tree_path_free (path);
580 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
581 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(gtkblist->treeview), TRUE);
582 g_object_set(G_OBJECT(gtkblist->text_rend), "editable", FALSE, NULL);
584 switch (purple_blist_node_get_type(node))
586 case PURPLE_BLIST_CONTACT_NODE:
588 PurpleContact *contact = PURPLE_CONTACT(node);
589 struct _pidgin_blist_node *gtknode =
590 (struct _pidgin_blist_node *)purple_blist_node_get_ui_data(node);
593 * XXX Using purple_contact_get_alias here breaks because we
594 * specifically want to check the contact alias only (i.e. not
595 * the priority buddy, which purple_contact_get_alias does).
596 * Adding yet another get_alias is evil, so figure this out
597 * later :-P
599 if (contact->alias || gtknode->contact_expanded) {
600 purple_blist_alias_contact(contact, arg2);
601 gtk_blist_auto_personize(purple_blist_node_get_parent(node), arg2);
602 } else {
603 PurpleBuddy *buddy = purple_contact_get_priority_buddy(contact);
604 purple_blist_alias_buddy(buddy, arg2);
605 serv_alias_buddy(buddy);
606 gtk_blist_auto_personize(purple_blist_node_get_parent(node), arg2);
609 break;
611 case PURPLE_BLIST_BUDDY_NODE:
613 PurpleGroup *group = purple_buddy_get_group(PURPLE_BUDDY(node));
615 purple_blist_alias_buddy(PURPLE_BUDDY(node), arg2);
616 serv_alias_buddy(PURPLE_BUDDY(node));
617 gtk_blist_auto_personize(PURPLE_BLIST_NODE(group), arg2);
619 break;
620 case PURPLE_BLIST_GROUP_NODE:
621 dest = purple_find_group(arg2);
622 if (dest != NULL && purple_utf8_strcasecmp(arg2, purple_group_get_name(PURPLE_GROUP(node)))) {
623 pidgin_dialogs_merge_groups(PURPLE_GROUP(node), arg2);
624 } else {
625 purple_blist_rename_group(PURPLE_GROUP(node), arg2);
627 break;
628 case PURPLE_BLIST_CHAT_NODE:
629 purple_blist_alias_chat(PURPLE_CHAT(node), arg2);
630 break;
631 default:
632 break;
634 pidgin_blist_refresh(list);
637 static void
638 chat_components_edit_ok(PurpleChat *chat, PurpleRequestFields *allfields)
640 GList *groups, *fields;
642 for (groups = purple_request_fields_get_groups(allfields); groups; groups = groups->next) {
643 fields = purple_request_field_group_get_fields(groups->data);
644 for (; fields; fields = fields->next) {
645 PurpleRequestField *field = fields->data;
646 const char *id;
647 char *val;
649 id = purple_request_field_get_id(field);
650 if (purple_request_field_get_type(field) == PURPLE_REQUEST_FIELD_INTEGER)
651 val = g_strdup_printf("%d", purple_request_field_int_get_value(field));
652 else
653 val = g_strdup(purple_request_field_string_get_value(field));
655 if (!val) {
656 g_hash_table_remove(purple_chat_get_components(chat), id);
657 } else {
658 g_hash_table_replace(purple_chat_get_components(chat), g_strdup(id), val); /* val should not be free'd */
664 static void chat_components_edit(GtkWidget *w, PurpleBlistNode *node)
666 PurpleRequestFields *fields = purple_request_fields_new();
667 PurpleRequestFieldGroup *group = purple_request_field_group_new(NULL);
668 PurpleRequestField *field;
669 GList *parts, *iter;
670 struct proto_chat_entry *pce;
671 PurpleConnection *gc;
672 PurpleChat *chat = (PurpleChat*)node;
674 purple_request_fields_add_group(fields, group);
676 gc = purple_account_get_connection(purple_chat_get_account(chat));
677 parts = PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc))->chat_info(gc);
679 for (iter = parts; iter; iter = iter->next) {
680 pce = iter->data;
681 if (pce->is_int) {
682 int val;
683 const char *str = g_hash_table_lookup(purple_chat_get_components(chat), pce->identifier);
684 if (!str || sscanf(str, "%d", &val) != 1)
685 val = pce->min;
686 field = purple_request_field_int_new(pce->identifier, pce->label, val);
687 } else {
688 field = purple_request_field_string_new(pce->identifier, pce->label,
689 g_hash_table_lookup(purple_chat_get_components(chat), pce->identifier), FALSE);
690 if (pce->secret)
691 purple_request_field_string_set_masked(field, TRUE);
694 if (pce->required)
695 purple_request_field_set_required(field, TRUE);
697 purple_request_field_group_add_field(group, field);
698 g_free(pce);
701 g_list_free(parts);
703 purple_request_fields(NULL, _("Edit Chat"), NULL, _("Please update the necessary fields."),
704 fields, _("Save"), G_CALLBACK(chat_components_edit_ok), _("Cancel"), NULL,
705 NULL, NULL, NULL,
706 chat);
709 static void gtk_blist_menu_alias_cb(GtkWidget *w, PurpleBlistNode *node)
711 GtkTreeIter iter;
712 GtkTreePath *path;
714 if (!(get_iter_from_node(node, &iter))) {
715 /* This is either a bug, or the buddy is in a collapsed contact */
716 node = purple_blist_node_get_parent(node);
717 if (!get_iter_from_node(node, &iter))
718 /* Now it's definitely a bug */
719 return;
722 pidgin_blist_tooltip_destroy();
724 path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &iter);
725 g_object_set(G_OBJECT(gtkblist->text_rend), "editable", TRUE, NULL);
726 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(gtkblist->treeview), FALSE);
727 gtk_widget_grab_focus(gtkblist->treeview);
728 gtk_tree_view_set_cursor_on_cell(GTK_TREE_VIEW(gtkblist->treeview), path,
729 gtkblist->text_column, gtkblist->text_rend, TRUE);
730 gtk_tree_path_free(path);
733 static void gtk_blist_menu_bp_cb(GtkWidget *w, PurpleBuddy *b)
735 pidgin_pounce_editor_show(purple_buddy_get_account(b),
736 purple_buddy_get_name(b), NULL);
739 static void gtk_blist_menu_showlog_cb(GtkWidget *w, PurpleBlistNode *node)
741 PurpleLogType type;
742 PurpleAccount *account;
743 char *name = NULL;
745 pidgin_set_cursor(gtkblist->window, GDK_WATCH);
747 if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
748 PurpleBuddy *b = (PurpleBuddy*) node;
749 type = PURPLE_LOG_IM;
750 name = g_strdup(purple_buddy_get_name(b));
751 account = purple_buddy_get_account(b);
752 } else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
753 PurpleChat *c = PURPLE_CHAT(node);
754 PurplePluginProtocolInfo *prpl_info = NULL;
755 type = PURPLE_LOG_CHAT;
756 account = purple_chat_get_account(c);
757 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_find_prpl(purple_account_get_protocol_id(account)));
758 if (prpl_info && prpl_info->get_chat_name) {
759 name = prpl_info->get_chat_name(purple_chat_get_components(c));
761 } else if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
762 pidgin_log_show_contact(PURPLE_CONTACT(node));
763 pidgin_clear_cursor(gtkblist->window);
764 return;
765 } else {
766 pidgin_clear_cursor(gtkblist->window);
768 /* This callback should not have been registered for a node
769 * that doesn't match the type of one of the blocks above. */
770 g_return_if_reached();
773 if (name && account) {
774 pidgin_log_show(type, name, account);
775 pidgin_clear_cursor(gtkblist->window);
778 g_free(name);
781 static void gtk_blist_menu_showoffline_cb(GtkWidget *w, PurpleBlistNode *node)
783 if (PURPLE_BLIST_NODE_IS_BUDDY(node))
785 purple_blist_node_set_bool(node, "show_offline",
786 !purple_blist_node_get_bool(node, "show_offline"));
787 pidgin_blist_update(purple_get_blist(), node);
789 else if (PURPLE_BLIST_NODE_IS_CONTACT(node))
791 PurpleBlistNode *bnode;
792 gboolean setting = !purple_blist_node_get_bool(node, "show_offline");
794 purple_blist_node_set_bool(node, "show_offline", setting);
795 for (bnode = purple_blist_node_get_first_child(node);
796 bnode != NULL;
797 bnode = purple_blist_node_get_sibling_next(bnode))
799 purple_blist_node_set_bool(bnode, "show_offline", setting);
800 pidgin_blist_update(purple_get_blist(), bnode);
802 } else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
803 PurpleBlistNode *cnode, *bnode;
804 gboolean setting = !purple_blist_node_get_bool(node, "show_offline");
806 purple_blist_node_set_bool(node, "show_offline", setting);
807 for (cnode = purple_blist_node_get_first_child(node);
808 cnode != NULL;
809 cnode = purple_blist_node_get_sibling_next(cnode))
811 purple_blist_node_set_bool(cnode, "show_offline", setting);
812 for (bnode = purple_blist_node_get_first_child(cnode);
813 bnode != NULL;
814 bnode = purple_blist_node_get_sibling_next(bnode))
816 purple_blist_node_set_bool(bnode, "show_offline", setting);
817 pidgin_blist_update(purple_get_blist(), bnode);
823 static void gtk_blist_show_systemlog_cb(void)
825 pidgin_syslog_show();
828 static void gtk_blist_show_onlinehelp_cb(void)
830 purple_notify_uri(NULL, PURPLE_WEBSITE "documentation");
833 static void
834 do_join_chat(PidginChatData *data)
836 if (data)
838 GHashTable *components =
839 g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
840 GList *tmp;
841 PurpleChat *chat;
843 for (tmp = data->entries; tmp != NULL; tmp = tmp->next)
845 if (g_object_get_data(tmp->data, "is_spin"))
847 g_hash_table_replace(components,
848 g_strdup(g_object_get_data(tmp->data, "identifier")),
849 g_strdup_printf("%d",
850 gtk_spin_button_get_value_as_int(tmp->data)));
852 else
854 g_hash_table_replace(components,
855 g_strdup(g_object_get_data(tmp->data, "identifier")),
856 g_strdup(gtk_entry_get_text(tmp->data)));
860 chat = purple_chat_new(data->rq_data.account, NULL, components);
861 gtk_blist_join_chat(chat);
862 purple_blist_remove_chat(chat);
866 static void
867 do_joinchat(GtkWidget *dialog, int id, PidginChatData *info)
869 switch(id)
871 case GTK_RESPONSE_OK:
872 do_join_chat(info);
873 break;
875 case 1:
876 pidgin_roomlist_dialog_show_with_account(info->rq_data.account);
877 return;
879 break;
882 gtk_widget_destroy(GTK_WIDGET(dialog));
883 g_list_free(info->entries);
884 g_free(info);
888 * Check the values of all the text entry boxes. If any required input
889 * strings are empty then don't allow the user to click on "OK."
891 static void
892 set_sensitive_if_input_cb(GtkWidget *entry, gpointer user_data)
894 PurplePluginProtocolInfo *prpl_info;
895 PurpleConnection *gc;
896 PidginChatData *data;
897 GList *tmp;
898 const char *text;
899 gboolean required;
900 gboolean sensitive = TRUE;
902 data = user_data;
904 for (tmp = data->entries; tmp != NULL; tmp = tmp->next)
906 if (!g_object_get_data(tmp->data, "is_spin"))
908 required = GPOINTER_TO_INT(g_object_get_data(tmp->data, "required"));
909 text = gtk_entry_get_text(tmp->data);
910 if (required && (*text == '\0'))
911 sensitive = FALSE;
915 gtk_dialog_set_response_sensitive(GTK_DIALOG(data->rq_data.window), GTK_RESPONSE_OK, sensitive);
917 gc = purple_account_get_connection(data->rq_data.account);
918 prpl_info = (gc != NULL) ? PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl) : NULL;
919 sensitive = (prpl_info != NULL && prpl_info->roomlist_get_list != NULL);
921 gtk_dialog_set_response_sensitive(GTK_DIALOG(data->rq_data.window), 1, sensitive);
924 static void
925 pidgin_blist_update_privacy_cb(PurpleBuddy *buddy)
927 struct _pidgin_blist_node *ui_data = purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(buddy));
928 if (ui_data == NULL || ui_data->row == NULL)
929 return;
930 pidgin_blist_update_buddy(purple_get_blist(), PURPLE_BLIST_NODE(buddy), TRUE);
933 static gboolean
934 chat_account_filter_func(PurpleAccount *account)
936 PurpleConnection *gc = purple_account_get_connection(account);
937 PurplePluginProtocolInfo *prpl_info = NULL;
939 if (gc == NULL)
940 return FALSE;
942 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
944 return (prpl_info->chat_info != NULL);
947 gboolean
948 pidgin_blist_joinchat_is_showable()
950 GList *c;
951 PurpleConnection *gc;
953 for (c = purple_connections_get_all(); c != NULL; c = c->next) {
954 gc = c->data;
956 if (chat_account_filter_func(purple_connection_get_account(gc)))
957 return TRUE;
960 return FALSE;
963 static GtkWidget *
964 make_blist_request_dialog(PidginBlistRequestData *data, PurpleAccount *account,
965 const char *title, const char *window_role, const char *label_text,
966 GCallback callback_func, PurpleFilterAccountFunc filter_func,
967 GCallback response_cb)
969 GtkWidget *label;
970 GtkWidget *img;
971 GtkWidget *hbox;
972 GtkWidget *vbox;
973 GtkWindow *blist_window;
974 PidginBuddyList *gtkblist;
976 data->account = account;
978 img = gtk_image_new_from_stock(PIDGIN_STOCK_DIALOG_QUESTION,
979 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_HUGE));
981 gtkblist = PIDGIN_BLIST(purple_get_blist());
982 blist_window = gtkblist ? GTK_WINDOW(gtkblist->window) : NULL;
984 data->window = gtk_dialog_new_with_buttons(title,
985 blist_window, GTK_DIALOG_NO_SEPARATOR,
986 NULL);
988 gtk_window_set_transient_for(GTK_WINDOW(data->window), blist_window);
989 gtk_dialog_set_default_response(GTK_DIALOG(data->window), GTK_RESPONSE_OK);
990 gtk_container_set_border_width(GTK_CONTAINER(data->window), PIDGIN_HIG_BOX_SPACE);
991 gtk_window_set_resizable(GTK_WINDOW(data->window), FALSE);
992 gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(data->window)->vbox), PIDGIN_HIG_BORDER);
993 gtk_container_set_border_width(GTK_CONTAINER(GTK_DIALOG(data->window)->vbox), PIDGIN_HIG_BOX_SPACE);
994 gtk_window_set_role(GTK_WINDOW(data->window), window_role);
996 hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BORDER);
997 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(data->window)->vbox), hbox);
998 gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0);
999 gtk_misc_set_alignment(GTK_MISC(img), 0, 0);
1001 vbox = gtk_vbox_new(FALSE, 5);
1002 gtk_container_add(GTK_CONTAINER(hbox), vbox);
1004 label = gtk_label_new(label_text);
1006 gtk_widget_set_size_request(label, 400, -1);
1007 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1008 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
1009 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1011 data->sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
1013 data->account_menu = pidgin_account_option_menu_new(account, FALSE,
1014 callback_func, filter_func, data);
1015 pidgin_add_widget_to_vbox(GTK_BOX(vbox), _("A_ccount"), data->sg, data->account_menu, TRUE, NULL);
1017 data->vbox = GTK_BOX(gtk_vbox_new(FALSE, 5));
1018 gtk_container_set_border_width(GTK_CONTAINER(data->vbox), 0);
1019 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(data->vbox), FALSE, FALSE, 0);
1021 g_signal_connect(G_OBJECT(data->window), "response", response_cb, data);
1023 g_object_unref(data->sg);
1025 return vbox;
1028 static void
1029 rebuild_chat_entries(PidginChatData *data, const char *default_chat_name)
1031 PurpleConnection *gc;
1032 GList *list = NULL, *tmp;
1033 GHashTable *defaults = NULL;
1034 struct proto_chat_entry *pce;
1035 gboolean focus = TRUE;
1037 g_return_if_fail(data->rq_data.account != NULL);
1039 gc = purple_account_get_connection(data->rq_data.account);
1041 gtk_container_foreach(GTK_CONTAINER(data->rq_data.vbox), (GtkCallback)gtk_widget_destroy, NULL);
1043 g_list_free(data->entries);
1044 data->entries = NULL;
1046 if (PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info != NULL)
1047 list = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info(gc);
1049 if (PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info_defaults != NULL)
1050 defaults = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info_defaults(gc, default_chat_name);
1052 for (tmp = list; tmp; tmp = tmp->next)
1054 GtkWidget *input;
1056 pce = tmp->data;
1058 if (pce->is_int)
1060 GtkObject *adjust;
1061 adjust = gtk_adjustment_new(pce->min, pce->min, pce->max,
1062 1, 10, 10);
1063 input = gtk_spin_button_new(GTK_ADJUSTMENT(adjust), 1, 0);
1064 gtk_widget_set_size_request(input, 50, -1);
1065 pidgin_add_widget_to_vbox(GTK_BOX(data->rq_data.vbox), pce->label, data->rq_data.sg, input, FALSE, NULL);
1067 else
1069 char *value;
1070 input = gtk_entry_new();
1071 gtk_entry_set_activates_default(GTK_ENTRY(input), TRUE);
1072 value = g_hash_table_lookup(defaults, pce->identifier);
1073 if (value != NULL)
1074 gtk_entry_set_text(GTK_ENTRY(input), value);
1075 if (pce->secret)
1077 gtk_entry_set_visibility(GTK_ENTRY(input), FALSE);
1078 #if !GTK_CHECK_VERSION(2,16,0)
1079 if (gtk_entry_get_invisible_char(GTK_ENTRY(input)) == '*')
1080 gtk_entry_set_invisible_char(GTK_ENTRY(input), PIDGIN_INVISIBLE_CHAR);
1081 #endif /* Less than GTK+ 2.16 */
1083 pidgin_add_widget_to_vbox(data->rq_data.vbox, pce->label, data->rq_data.sg, input, TRUE, NULL);
1084 g_signal_connect(G_OBJECT(input), "changed",
1085 G_CALLBACK(set_sensitive_if_input_cb), data);
1088 /* Do the following for any type of input widget */
1089 if (focus)
1091 gtk_widget_grab_focus(input);
1092 focus = FALSE;
1094 g_object_set_data(G_OBJECT(input), "identifier", (gpointer)pce->identifier);
1095 g_object_set_data(G_OBJECT(input), "is_spin", GINT_TO_POINTER(pce->is_int));
1096 g_object_set_data(G_OBJECT(input), "required", GINT_TO_POINTER(pce->required));
1097 data->entries = g_list_append(data->entries, input);
1099 g_free(pce);
1102 g_list_free(list);
1103 g_hash_table_destroy(defaults);
1105 /* Set whether the "OK" button should be clickable initially */
1106 set_sensitive_if_input_cb(NULL, data);
1108 gtk_widget_show_all(GTK_WIDGET(data->rq_data.vbox));
1111 static void
1112 chat_select_account_cb(GObject *w, PurpleAccount *account,
1113 PidginChatData *data)
1115 if (strcmp(purple_account_get_protocol_id(data->rq_data.account),
1116 purple_account_get_protocol_id(account)) == 0)
1118 data->rq_data.account = account;
1120 else
1122 data->rq_data.account = account;
1123 rebuild_chat_entries(data, data->default_chat_name);
1127 void
1128 pidgin_blist_joinchat_show(void)
1130 PidginChatData *data = NULL;
1132 data = g_new0(PidginChatData, 1);
1134 make_blist_request_dialog((PidginBlistRequestData *)data, NULL,
1135 _("Join a Chat"), "join_chat",
1136 _("Please enter the appropriate information about the chat "
1137 "you would like to join.\n"),
1138 G_CALLBACK(chat_select_account_cb),
1139 chat_account_filter_func, (GCallback)do_joinchat);
1140 gtk_dialog_add_buttons(GTK_DIALOG(data->rq_data.window),
1141 _("Room _List"), 1,
1142 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1143 PIDGIN_STOCK_CHAT, GTK_RESPONSE_OK, NULL);
1144 gtk_dialog_set_default_response(GTK_DIALOG(data->rq_data.window),
1145 GTK_RESPONSE_OK);
1146 data->default_chat_name = NULL;
1147 data->rq_data.account = pidgin_account_option_menu_get_selected(data->rq_data.account_menu);
1149 rebuild_chat_entries(data, NULL);
1151 gtk_widget_show_all(data->rq_data.window);
1154 static void gtk_blist_row_expanded_cb(GtkTreeView *tv, GtkTreeIter *iter, GtkTreePath *path, gpointer user_data)
1156 PurpleBlistNode *node;
1158 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), iter, NODE_COLUMN, &node, -1);
1160 if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
1161 char *title;
1163 title = pidgin_get_group_title(node, TRUE);
1165 gtk_tree_store_set(gtkblist->treemodel, iter,
1166 NAME_COLUMN, title,
1167 -1);
1169 g_free(title);
1171 purple_blist_node_set_bool(node, "collapsed", FALSE);
1172 pidgin_blist_tooltip_destroy();
1176 static void gtk_blist_row_collapsed_cb(GtkTreeView *tv, GtkTreeIter *iter, GtkTreePath *path, gpointer user_data)
1178 PurpleBlistNode *node;
1180 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), iter, NODE_COLUMN, &node, -1);
1182 if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
1183 char *title;
1184 struct _pidgin_blist_node *gtknode;
1185 PurpleBlistNode *cnode;
1187 title = pidgin_get_group_title(node, FALSE);
1189 gtk_tree_store_set(gtkblist->treemodel, iter,
1190 NAME_COLUMN, title,
1191 -1);
1193 g_free(title);
1195 purple_blist_node_set_bool(node, "collapsed", TRUE);
1197 for(cnode = purple_blist_node_get_first_child(node); cnode; cnode = purple_blist_node_get_sibling_next(cnode)) {
1198 if (PURPLE_BLIST_NODE_IS_CONTACT(cnode)) {
1199 gtknode = purple_blist_node_get_ui_data(cnode);
1200 if (!gtknode->contact_expanded)
1201 continue;
1202 gtknode->contact_expanded = FALSE;
1203 pidgin_blist_update_contact(NULL, cnode);
1206 pidgin_blist_tooltip_destroy();
1207 } else if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
1208 pidgin_blist_collapse_contact_cb(NULL, node);
1212 static void gtk_blist_row_activated_cb(GtkTreeView *tv, GtkTreePath *path, GtkTreeViewColumn *col, gpointer data) {
1213 PurpleBlistNode *node;
1214 GtkTreeIter iter;
1216 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
1217 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
1219 if(PURPLE_BLIST_NODE_IS_CONTACT(node) || PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1220 PurpleBuddy *buddy;
1222 if(PURPLE_BLIST_NODE_IS_CONTACT(node))
1223 buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
1224 else
1225 buddy = (PurpleBuddy*)node;
1227 pidgin_dialogs_im_with_user(purple_buddy_get_account(buddy), purple_buddy_get_name(buddy));
1228 } else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
1229 gtk_blist_join_chat((PurpleChat *)node);
1230 } else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
1231 /* if (gtk_tree_view_row_expanded(tv, path))
1232 gtk_tree_view_collapse_row(tv, path);
1233 else
1234 gtk_tree_view_expand_row(tv,path,FALSE);*/
1238 static void pidgin_blist_add_chat_cb(void)
1240 GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist->treeview));
1241 GtkTreeIter iter;
1242 PurpleBlistNode *node;
1244 if(gtk_tree_selection_get_selected(sel, NULL, &iter)){
1245 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
1246 if (PURPLE_BLIST_NODE_IS_BUDDY(node))
1247 purple_blist_request_add_chat(NULL, purple_buddy_get_group(PURPLE_BUDDY(node)), NULL, NULL);
1248 if (PURPLE_BLIST_NODE_IS_CONTACT(node) || PURPLE_BLIST_NODE_IS_CHAT(node))
1249 purple_blist_request_add_chat(NULL, purple_contact_get_group(PURPLE_CONTACT(node)), NULL, NULL);
1250 else if (PURPLE_BLIST_NODE_IS_GROUP(node))
1251 purple_blist_request_add_chat(NULL, (PurpleGroup*)node, NULL, NULL);
1253 else {
1254 purple_blist_request_add_chat(NULL, NULL, NULL, NULL);
1258 static void pidgin_blist_add_buddy_cb(void)
1260 GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist->treeview));
1261 GtkTreeIter iter;
1262 PurpleBlistNode *node;
1264 if(gtk_tree_selection_get_selected(sel, NULL, &iter)){
1265 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
1266 if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1267 PurpleGroup *group = purple_buddy_get_group(PURPLE_BUDDY(node));
1268 purple_blist_request_add_buddy(NULL, NULL, purple_group_get_name(group), NULL);
1269 } else if (PURPLE_BLIST_NODE_IS_CONTACT(node) || PURPLE_BLIST_NODE_IS_CHAT(node)) {
1270 PurpleGroup *group = purple_contact_get_group(PURPLE_CONTACT(node));
1271 purple_blist_request_add_buddy(NULL, NULL, purple_group_get_name(group), NULL);
1272 } else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
1273 purple_blist_request_add_buddy(NULL, NULL, purple_group_get_name(PURPLE_GROUP(node)), NULL);
1276 else {
1277 purple_blist_request_add_buddy(NULL, NULL, NULL, NULL);
1281 static void
1282 pidgin_blist_remove_cb (GtkWidget *w, PurpleBlistNode *node)
1284 if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1285 pidgin_dialogs_remove_buddy((PurpleBuddy*)node);
1286 } else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
1287 pidgin_dialogs_remove_chat((PurpleChat*)node);
1288 } else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
1289 pidgin_dialogs_remove_group((PurpleGroup*)node);
1290 } else if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
1291 pidgin_dialogs_remove_contact((PurpleContact*)node);
1295 struct _expand {
1296 GtkTreeView *treeview;
1297 GtkTreePath *path;
1298 PurpleBlistNode *node;
1301 static gboolean
1302 scroll_to_expanded_cell(gpointer data)
1304 struct _expand *ex = data;
1305 gtk_tree_view_scroll_to_cell(ex->treeview, ex->path, NULL, FALSE, 0, 0);
1306 pidgin_blist_update_contact(NULL, ex->node);
1308 gtk_tree_path_free(ex->path);
1309 g_free(ex);
1311 return FALSE;
1314 static void
1315 pidgin_blist_expand_contact_cb(GtkWidget *w, PurpleBlistNode *node)
1317 struct _pidgin_blist_node *gtknode;
1318 GtkTreeIter iter, parent;
1319 PurpleBlistNode *bnode;
1320 GtkTreePath *path;
1322 if(!PURPLE_BLIST_NODE_IS_CONTACT(node))
1323 return;
1325 gtknode = purple_blist_node_get_ui_data(node);
1327 gtknode->contact_expanded = TRUE;
1329 for(bnode = purple_blist_node_get_first_child(node); bnode; bnode = purple_blist_node_get_sibling_next(bnode)) {
1330 pidgin_blist_update(NULL, bnode);
1333 /* This ensures that the bottom buddy is visible, i.e. not scrolled off the alignment */
1334 if (get_iter_from_node(node, &parent)) {
1335 struct _expand *ex = g_new0(struct _expand, 1);
1337 gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(gtkblist->treemodel), &iter, &parent,
1338 gtk_tree_model_iter_n_children(GTK_TREE_MODEL(gtkblist->treemodel), &parent) -1);
1339 path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &iter);
1341 /* Let the treeview draw so it knows where to scroll */
1342 ex->treeview = GTK_TREE_VIEW(gtkblist->treeview);
1343 ex->path = path;
1344 ex->node = purple_blist_node_get_first_child(node);
1345 g_idle_add(scroll_to_expanded_cell, ex);
1349 static void
1350 pidgin_blist_collapse_contact_cb(GtkWidget *w, PurpleBlistNode *node)
1352 PurpleBlistNode *bnode;
1353 struct _pidgin_blist_node *gtknode;
1355 if(!PURPLE_BLIST_NODE_IS_CONTACT(node))
1356 return;
1358 gtknode = purple_blist_node_get_ui_data(node);
1360 gtknode->contact_expanded = FALSE;
1362 for(bnode = purple_blist_node_get_first_child(node); bnode; bnode = purple_blist_node_get_sibling_next(bnode)) {
1363 pidgin_blist_update(NULL, bnode);
1367 static void
1368 toggle_privacy(GtkWidget *widget, PurpleBlistNode *node)
1370 PurpleBuddy *buddy;
1371 PurpleAccount *account;
1372 gboolean permitted;
1373 const char *name;
1375 if (!PURPLE_BLIST_NODE_IS_BUDDY(node))
1376 return;
1378 buddy = (PurpleBuddy *)node;
1379 account = purple_buddy_get_account(buddy);
1380 name = purple_buddy_get_name(buddy);
1382 permitted = purple_privacy_check(account, name);
1384 /* XXX: Perhaps ask whether to restore the previous lists where appropirate? */
1386 if (permitted)
1387 purple_privacy_deny(account, name, FALSE, FALSE);
1388 else
1389 purple_privacy_allow(account, name, FALSE, FALSE);
1391 pidgin_blist_update(purple_get_blist(), node);
1394 void pidgin_append_blist_node_privacy_menu(GtkWidget *menu, PurpleBlistNode *node)
1396 PurpleBuddy *buddy = (PurpleBuddy *)node;
1397 PurpleAccount *account;
1398 gboolean permitted;
1400 account = purple_buddy_get_account(buddy);
1401 permitted = purple_privacy_check(account, purple_buddy_get_name(buddy));
1403 pidgin_new_item_from_stock(menu, permitted ? _("_Block") : _("Un_block"),
1404 permitted ? PIDGIN_STOCK_TOOLBAR_BLOCK : PIDGIN_STOCK_TOOLBAR_UNBLOCK, G_CALLBACK(toggle_privacy),
1405 node, 0 ,0, NULL);
1408 void
1409 pidgin_append_blist_node_proto_menu(GtkWidget *menu, PurpleConnection *gc,
1410 PurpleBlistNode *node)
1412 GList *l, *ll;
1413 PurplePluginProtocolInfo *prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
1415 if(!prpl_info || !prpl_info->blist_node_menu)
1416 return;
1418 for(l = ll = prpl_info->blist_node_menu(node); l; l = l->next) {
1419 PurpleMenuAction *act = (PurpleMenuAction *) l->data;
1420 pidgin_append_menu_action(menu, act, node);
1422 g_list_free(ll);
1425 void
1426 pidgin_append_blist_node_extended_menu(GtkWidget *menu, PurpleBlistNode *node)
1428 GList *l, *ll;
1430 for(l = ll = purple_blist_node_get_extended_menu(node); l; l = l->next) {
1431 PurpleMenuAction *act = (PurpleMenuAction *) l->data;
1432 pidgin_append_menu_action(menu, act, node);
1434 g_list_free(ll);
1439 static void
1440 pidgin_append_blist_node_move_to_menu(GtkWidget *menu, PurpleBlistNode *node)
1442 GtkWidget *submenu;
1443 GtkWidget *menuitem;
1444 PurpleBlistNode *group;
1446 menuitem = gtk_menu_item_new_with_label(_("Move to"));
1447 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1448 gtk_widget_show(menuitem);
1450 submenu = gtk_menu_new();
1451 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
1453 for (group = purple_blist_get_root(); group; group = purple_blist_node_get_sibling_next(group)) {
1454 if (!PURPLE_BLIST_NODE_IS_GROUP(group))
1455 continue;
1456 if (group == purple_blist_node_get_parent(node))
1457 continue;
1458 menuitem = pidgin_new_item_from_stock(submenu, purple_group_get_name((PurpleGroup *)group), NULL,
1459 G_CALLBACK(gtk_blist_menu_move_to_cb), node, 0, 0, NULL);
1460 g_object_set_data(G_OBJECT(menuitem), "groupnode", group);
1462 gtk_widget_show_all(submenu);
1465 void
1466 pidgin_blist_make_buddy_menu(GtkWidget *menu, PurpleBuddy *buddy, gboolean sub) {
1467 PurpleAccount *account = NULL;
1468 PurpleConnection *pc = NULL;
1469 PurplePluginProtocolInfo *prpl_info;
1470 PurpleContact *contact;
1471 PurpleBlistNode *node;
1472 gboolean contact_expanded = FALSE;
1474 g_return_if_fail(menu);
1475 g_return_if_fail(buddy);
1477 account = purple_buddy_get_account(buddy);
1478 pc = purple_account_get_connection(account);
1479 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(pc));
1481 node = PURPLE_BLIST_NODE(buddy);
1483 contact = purple_buddy_get_contact(buddy);
1484 if (contact) {
1485 PidginBlistNode *node = purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(contact));
1486 contact_expanded = node->contact_expanded;
1489 if (prpl_info && prpl_info->get_info) {
1490 pidgin_new_item_from_stock(menu, _("Get _Info"), PIDGIN_STOCK_TOOLBAR_USER_INFO,
1491 G_CALLBACK(gtk_blist_menu_info_cb), buddy, 0, 0, NULL);
1493 pidgin_new_item_from_stock(menu, _("I_M"), PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW,
1494 G_CALLBACK(gtk_blist_menu_im_cb), buddy, 0, 0, NULL);
1496 #ifdef USE_VV
1497 if (prpl_info && prpl_info->get_media_caps) {
1498 PurpleAccount *account = purple_buddy_get_account(buddy);
1499 const gchar *who = purple_buddy_get_name(buddy);
1500 PurpleMediaCaps caps = purple_prpl_get_media_caps(account, who);
1501 if (caps & PURPLE_MEDIA_CAPS_AUDIO) {
1502 pidgin_new_item_from_stock(menu, _("_Audio Call"),
1503 PIDGIN_STOCK_TOOLBAR_AUDIO_CALL,
1504 G_CALLBACK(gtk_blist_menu_audio_call_cb), buddy, 0, 0, NULL);
1506 if (caps & PURPLE_MEDIA_CAPS_AUDIO_VIDEO) {
1507 pidgin_new_item_from_stock(menu, _("Audio/_Video Call"),
1508 PIDGIN_STOCK_TOOLBAR_VIDEO_CALL,
1509 G_CALLBACK(gtk_blist_menu_video_call_cb), buddy, 0, 0, NULL);
1510 } else if (caps & PURPLE_MEDIA_CAPS_VIDEO) {
1511 pidgin_new_item_from_stock(menu, _("_Video Call"),
1512 PIDGIN_STOCK_TOOLBAR_VIDEO_CALL,
1513 G_CALLBACK(gtk_blist_menu_video_call_cb), buddy, 0, 0, NULL);
1517 #endif
1519 if (prpl_info && prpl_info->send_file) {
1520 if (!prpl_info->can_receive_file ||
1521 prpl_info->can_receive_file(buddy->account->gc, buddy->name))
1523 pidgin_new_item_from_stock(menu, _("_Send File..."),
1524 PIDGIN_STOCK_TOOLBAR_SEND_FILE,
1525 G_CALLBACK(gtk_blist_menu_send_file_cb),
1526 buddy, 0, 0, NULL);
1530 pidgin_new_item_from_stock(menu, _("Add Buddy _Pounce..."), NULL,
1531 G_CALLBACK(gtk_blist_menu_bp_cb), buddy, 0, 0, NULL);
1533 if (node->parent && node->parent->child->next &&
1534 !sub && !contact_expanded) {
1535 pidgin_new_item_from_stock(menu, _("View _Log"), NULL,
1536 G_CALLBACK(gtk_blist_menu_showlog_cb),
1537 contact, 0, 0, NULL);
1538 } else if (!sub) {
1539 pidgin_new_item_from_stock(menu, _("View _Log"), NULL,
1540 G_CALLBACK(gtk_blist_menu_showlog_cb), buddy, 0, 0, NULL);
1543 if (!PURPLE_BLIST_NODE_HAS_FLAG(node, PURPLE_BLIST_NODE_FLAG_NO_SAVE)) {
1544 gboolean show_offline = purple_blist_node_get_bool(node, "show_offline");
1545 pidgin_new_item_from_stock(menu, show_offline ? _("Hide When Offline") : _("Show When Offline"),
1546 NULL, G_CALLBACK(gtk_blist_menu_showoffline_cb), node, 0, 0, NULL);
1549 pidgin_append_blist_node_proto_menu(menu, buddy->account->gc, node);
1550 pidgin_append_blist_node_extended_menu(menu, node);
1552 if (!contact_expanded && contact != NULL)
1553 pidgin_append_blist_node_move_to_menu(menu, (PurpleBlistNode *)contact);
1555 if (node->parent && node->parent->child->next &&
1556 !sub && !contact_expanded) {
1557 pidgin_separator(menu);
1558 pidgin_append_blist_node_privacy_menu(menu, node);
1559 pidgin_new_item_from_stock(menu, _("_Alias..."), PIDGIN_STOCK_ALIAS,
1560 G_CALLBACK(gtk_blist_menu_alias_cb),
1561 contact, 0, 0, NULL);
1562 pidgin_new_item_from_stock(menu, _("_Remove"), GTK_STOCK_REMOVE,
1563 G_CALLBACK(pidgin_blist_remove_cb),
1564 contact, 0, 0, NULL);
1565 } else if (!sub || contact_expanded) {
1566 pidgin_separator(menu);
1567 pidgin_append_blist_node_privacy_menu(menu, node);
1568 pidgin_new_item_from_stock(menu, _("_Alias..."), PIDGIN_STOCK_ALIAS,
1569 G_CALLBACK(gtk_blist_menu_alias_cb), buddy, 0, 0, NULL);
1570 pidgin_new_item_from_stock(menu, _("_Remove"), GTK_STOCK_REMOVE,
1571 G_CALLBACK(pidgin_blist_remove_cb), buddy,
1572 0, 0, NULL);
1576 static gboolean
1577 gtk_blist_key_press_cb(GtkWidget *tv, GdkEventKey *event, gpointer data)
1579 PurpleBlistNode *node;
1580 GtkTreeIter iter, parent;
1581 GtkTreeSelection *sel;
1582 GtkTreePath *path;
1584 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv));
1585 if(!gtk_tree_selection_get_selected(sel, NULL, &iter))
1586 return FALSE;
1588 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
1590 if(event->state & GDK_CONTROL_MASK &&
1591 (event->keyval == 'o' || event->keyval == 'O')) {
1592 PurpleBuddy *buddy;
1594 if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
1595 buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
1596 } else if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1597 buddy = (PurpleBuddy*)node;
1598 } else {
1599 return FALSE;
1601 if(buddy)
1602 pidgin_retrieve_user_info(buddy->account->gc, buddy->name);
1603 } else {
1604 switch (event->keyval) {
1605 case GDK_F2:
1606 gtk_blist_menu_alias_cb(tv, node);
1607 break;
1609 case GDK_Left:
1610 path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &iter);
1611 if (gtk_tree_view_row_expanded(GTK_TREE_VIEW(tv), path)) {
1612 /* Collapse the Group */
1613 gtk_tree_view_collapse_row(GTK_TREE_VIEW(tv), path);
1614 gtk_tree_path_free(path);
1615 return TRUE;
1616 } else {
1617 /* Select the Parent */
1618 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path)) {
1619 if (gtk_tree_model_iter_parent(GTK_TREE_MODEL(gtkblist->treemodel), &parent, &iter)) {
1620 gtk_tree_path_free(path);
1621 path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &parent);
1622 gtk_tree_view_set_cursor(GTK_TREE_VIEW(tv), path, NULL, FALSE);
1623 gtk_tree_path_free(path);
1624 return TRUE;
1628 gtk_tree_path_free(path);
1629 break;
1631 case GDK_Right:
1632 path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &iter);
1633 if (!gtk_tree_view_row_expanded(GTK_TREE_VIEW(tv), path)) {
1634 /* Expand the Group */
1635 if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
1636 pidgin_blist_expand_contact_cb(NULL, node);
1637 gtk_tree_path_free(path);
1638 return TRUE;
1639 } else if (!PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1640 gtk_tree_view_expand_row(GTK_TREE_VIEW(tv), path, FALSE);
1641 gtk_tree_path_free(path);
1642 return TRUE;
1644 } else {
1645 /* Select the First Child */
1646 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &parent, path)) {
1647 if (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(gtkblist->treemodel), &iter, &parent, 0)) {
1648 gtk_tree_path_free(path);
1649 path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &iter);
1650 gtk_tree_view_set_cursor(GTK_TREE_VIEW(tv), path, NULL, FALSE);
1651 gtk_tree_path_free(path);
1652 return TRUE;
1656 gtk_tree_path_free(path);
1657 break;
1661 return FALSE;
1664 static void
1665 set_node_custom_icon_cb(const gchar *filename, gpointer data)
1667 if (filename) {
1668 PurpleBlistNode *node = (PurpleBlistNode*)data;
1670 purple_buddy_icons_node_set_custom_icon_from_file(node,
1671 filename);
1675 static void
1676 set_node_custom_icon(GtkWidget *w, PurpleBlistNode *node)
1678 /* This doesn't keep track of the returned dialog (so that successive
1679 * calls could be made to re-display that dialog). Do we want that? */
1680 GtkWidget *win = pidgin_buddy_icon_chooser_new(NULL, set_node_custom_icon_cb, node);
1681 gtk_widget_show_all(win);
1684 static void
1685 remove_node_custom_icon(GtkWidget *w, PurpleBlistNode *node)
1687 purple_buddy_icons_node_set_custom_icon(node, NULL, 0);
1690 static void
1691 add_buddy_icon_menu_items(GtkWidget *menu, PurpleBlistNode *node)
1693 GtkWidget *item;
1695 pidgin_new_item_from_stock(menu, _("Set Custom Icon"), NULL,
1696 G_CALLBACK(set_node_custom_icon), node, 0,
1697 0, NULL);
1699 item = pidgin_new_item_from_stock(menu, _("Remove Custom Icon"), NULL,
1700 G_CALLBACK(remove_node_custom_icon), node,
1701 0, 0, NULL);
1702 if (!purple_buddy_icons_node_has_custom_icon(node))
1703 gtk_widget_set_sensitive(item, FALSE);
1706 static GtkWidget *
1707 create_group_menu (PurpleBlistNode *node, PurpleGroup *g)
1709 GtkWidget *menu;
1710 GtkWidget *item;
1712 menu = gtk_menu_new();
1713 item = pidgin_new_item_from_stock(menu, _("Add _Buddy..."), GTK_STOCK_ADD,
1714 G_CALLBACK(pidgin_blist_add_buddy_cb), node, 0, 0, NULL);
1715 gtk_widget_set_sensitive(item, purple_connections_get_all() != NULL);
1716 item = pidgin_new_item_from_stock(menu, _("Add C_hat..."), GTK_STOCK_ADD,
1717 G_CALLBACK(pidgin_blist_add_chat_cb), node, 0, 0, NULL);
1718 gtk_widget_set_sensitive(item, pidgin_blist_joinchat_is_showable());
1719 pidgin_new_item_from_stock(menu, _("_Delete Group"), GTK_STOCK_REMOVE,
1720 G_CALLBACK(pidgin_blist_remove_cb), node, 0, 0, NULL);
1721 pidgin_new_item_from_stock(menu, _("_Rename"), NULL,
1722 G_CALLBACK(gtk_blist_menu_alias_cb), node, 0, 0, NULL);
1723 if (!(purple_blist_node_get_flags(node) & PURPLE_BLIST_NODE_FLAG_NO_SAVE)) {
1724 gboolean show_offline = purple_blist_node_get_bool(node, "show_offline");
1725 pidgin_new_item_from_stock(menu, show_offline ? _("Hide When Offline") : _("Show When Offline"),
1726 NULL, G_CALLBACK(gtk_blist_menu_showoffline_cb), node, 0, 0, NULL);
1729 add_buddy_icon_menu_items(menu, node);
1731 pidgin_append_blist_node_extended_menu(menu, node);
1733 return menu;
1736 static GtkWidget *
1737 create_chat_menu(PurpleBlistNode *node, PurpleChat *c)
1739 GtkWidget *menu;
1740 gboolean autojoin, persistent;
1742 menu = gtk_menu_new();
1743 autojoin = purple_blist_node_get_bool(node, "gtk-autojoin");
1744 persistent = purple_blist_node_get_bool(node, "gtk-persistent");
1746 pidgin_new_item_from_stock(menu, _("_Join"), PIDGIN_STOCK_CHAT,
1747 G_CALLBACK(gtk_blist_menu_join_cb), node, 0, 0, NULL);
1748 pidgin_new_check_item(menu, _("Auto-Join"),
1749 G_CALLBACK(gtk_blist_menu_autojoin_cb), node, autojoin);
1750 pidgin_new_check_item(menu, _("Persistent"),
1751 G_CALLBACK(gtk_blist_menu_persistent_cb), node, persistent);
1752 pidgin_new_item_from_stock(menu, _("View _Log"), NULL,
1753 G_CALLBACK(gtk_blist_menu_showlog_cb), node, 0, 0, NULL);
1755 pidgin_append_blist_node_proto_menu(menu, c->account->gc, node);
1756 pidgin_append_blist_node_extended_menu(menu, node);
1758 pidgin_separator(menu);
1760 pidgin_new_item_from_stock(menu, _("_Edit Settings..."), NULL,
1761 G_CALLBACK(chat_components_edit), node, 0, 0, NULL);
1762 pidgin_new_item_from_stock(menu, _("_Alias..."), PIDGIN_STOCK_ALIAS,
1763 G_CALLBACK(gtk_blist_menu_alias_cb), node, 0, 0, NULL);
1764 pidgin_new_item_from_stock(menu, _("_Remove"), GTK_STOCK_REMOVE,
1765 G_CALLBACK(pidgin_blist_remove_cb), node, 0, 0, NULL);
1767 add_buddy_icon_menu_items(menu, node);
1769 return menu;
1772 static GtkWidget *
1773 create_contact_menu (PurpleBlistNode *node)
1775 GtkWidget *menu;
1777 menu = gtk_menu_new();
1779 pidgin_new_item_from_stock(menu, _("View _Log"), NULL,
1780 G_CALLBACK(gtk_blist_menu_showlog_cb),
1781 node, 0, 0, NULL);
1783 pidgin_separator(menu);
1785 pidgin_new_item_from_stock(menu, _("_Alias..."), PIDGIN_STOCK_ALIAS,
1786 G_CALLBACK(gtk_blist_menu_alias_cb), node, 0, 0, NULL);
1787 pidgin_new_item_from_stock(menu, _("_Remove"), GTK_STOCK_REMOVE,
1788 G_CALLBACK(pidgin_blist_remove_cb), node, 0, 0, NULL);
1790 add_buddy_icon_menu_items(menu, node);
1792 pidgin_separator(menu);
1794 pidgin_new_item_from_stock(menu, _("_Collapse"), GTK_STOCK_ZOOM_OUT,
1795 G_CALLBACK(pidgin_blist_collapse_contact_cb),
1796 node, 0, 0, NULL);
1798 pidgin_append_blist_node_extended_menu(menu, node);
1799 return menu;
1802 static GtkWidget *
1803 create_buddy_menu(PurpleBlistNode *node, PurpleBuddy *b)
1805 struct _pidgin_blist_node *gtknode = (struct _pidgin_blist_node *)node->ui_data;
1806 GtkWidget *menu;
1807 GtkWidget *menuitem;
1808 gboolean show_offline = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_offline_buddies");
1810 menu = gtk_menu_new();
1811 pidgin_blist_make_buddy_menu(menu, b, FALSE);
1813 if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
1814 pidgin_separator(menu);
1816 add_buddy_icon_menu_items(menu, node);
1818 if(gtknode->contact_expanded) {
1819 pidgin_new_item_from_stock(menu, _("_Collapse"),
1820 GTK_STOCK_ZOOM_OUT,
1821 G_CALLBACK(pidgin_blist_collapse_contact_cb),
1822 node, 0, 0, NULL);
1823 } else {
1824 pidgin_new_item_from_stock(menu, _("_Expand"),
1825 GTK_STOCK_ZOOM_IN,
1826 G_CALLBACK(pidgin_blist_expand_contact_cb), node,
1827 0, 0, NULL);
1829 if(node->child->next) {
1830 PurpleBlistNode *bnode;
1832 for(bnode = node->child; bnode; bnode = bnode->next) {
1833 PurpleBuddy *buddy = (PurpleBuddy*)bnode;
1834 GdkPixbuf *buf;
1835 GtkWidget *submenu;
1836 GtkWidget *image;
1838 if(buddy == b)
1839 continue;
1840 if(!buddy->account->gc)
1841 continue;
1842 if(!show_offline && !PURPLE_BUDDY_IS_ONLINE(buddy))
1843 continue;
1845 menuitem = gtk_image_menu_item_new_with_label(buddy->name);
1846 buf = pidgin_create_prpl_icon(buddy->account,PIDGIN_PRPL_ICON_SMALL);
1847 image = gtk_image_new_from_pixbuf(buf);
1848 g_object_unref(G_OBJECT(buf));
1849 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem),
1850 image);
1851 gtk_widget_show(image);
1852 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1853 gtk_widget_show(menuitem);
1855 submenu = gtk_menu_new();
1856 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
1857 gtk_widget_show(submenu);
1859 pidgin_blist_make_buddy_menu(submenu, buddy, TRUE);
1863 return menu;
1866 static gboolean
1867 pidgin_blist_show_context_menu(PurpleBlistNode *node,
1868 GtkMenuPositionFunc func,
1869 GtkWidget *tv,
1870 guint button,
1871 guint32 time)
1873 struct _pidgin_blist_node *gtknode;
1874 GtkWidget *menu = NULL;
1875 gboolean handled = FALSE;
1877 gtknode = (struct _pidgin_blist_node *)node->ui_data;
1879 /* Create a menu based on the thing we right-clicked on */
1880 if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
1881 PurpleGroup *g = (PurpleGroup *)node;
1883 menu = create_group_menu(node, g);
1884 } else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
1885 PurpleChat *c = (PurpleChat *)node;
1887 menu = create_chat_menu(node, c);
1888 } else if ((PURPLE_BLIST_NODE_IS_CONTACT(node)) && (gtknode->contact_expanded)) {
1889 menu = create_contact_menu(node);
1890 } else if (PURPLE_BLIST_NODE_IS_CONTACT(node) || PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1891 PurpleBuddy *b;
1893 if (PURPLE_BLIST_NODE_IS_CONTACT(node))
1894 b = purple_contact_get_priority_buddy((PurpleContact*)node);
1895 else
1896 b = (PurpleBuddy *)node;
1898 menu = create_buddy_menu(node, b);
1901 #ifdef _WIN32
1902 pidgin_blist_tooltip_destroy();
1904 /* Unhook the tooltip-timeout since we don't want a tooltip
1905 * to appear and obscure the context menu we are about to show
1906 This is a workaround for GTK+ bug 107320. */
1907 if (gtkblist->timeout) {
1908 g_source_remove(gtkblist->timeout);
1909 gtkblist->timeout = 0;
1911 #endif
1913 /* Now display the menu */
1914 if (menu != NULL) {
1915 gtk_widget_show_all(menu);
1916 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, func, tv, button, time);
1917 handled = TRUE;
1920 return handled;
1923 static gboolean
1924 gtk_blist_button_press_cb(GtkWidget *tv, GdkEventButton *event, gpointer user_data)
1926 GtkTreePath *path;
1927 PurpleBlistNode *node;
1928 GtkTreeIter iter;
1929 GtkTreeSelection *sel;
1930 PurplePlugin *prpl = NULL;
1931 PurplePluginProtocolInfo *prpl_info = NULL;
1932 struct _pidgin_blist_node *gtknode;
1933 gboolean handled = FALSE;
1935 /* Here we figure out which node was clicked */
1936 if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), event->x, event->y, &path, NULL, NULL, NULL))
1937 return FALSE;
1938 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
1939 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
1940 gtknode = (struct _pidgin_blist_node *)node->ui_data;
1942 /* Right click draws a context menu */
1943 if ((event->button == 3) && (event->type == GDK_BUTTON_PRESS)) {
1944 handled = pidgin_blist_show_context_menu(node, NULL, tv, 3, event->time);
1946 /* CTRL+middle click expands or collapse a contact */
1947 } else if ((event->button == 2) && (event->type == GDK_BUTTON_PRESS) &&
1948 (event->state & GDK_CONTROL_MASK) && (PURPLE_BLIST_NODE_IS_CONTACT(node))) {
1949 if (gtknode->contact_expanded)
1950 pidgin_blist_collapse_contact_cb(NULL, node);
1951 else
1952 pidgin_blist_expand_contact_cb(NULL, node);
1953 handled = TRUE;
1955 /* Double middle click gets info */
1956 } else if ((event->button == 2) && (event->type == GDK_2BUTTON_PRESS) &&
1957 ((PURPLE_BLIST_NODE_IS_CONTACT(node)) || (PURPLE_BLIST_NODE_IS_BUDDY(node)))) {
1958 PurpleBuddy *b;
1959 if(PURPLE_BLIST_NODE_IS_CONTACT(node))
1960 b = purple_contact_get_priority_buddy((PurpleContact*)node);
1961 else
1962 b = (PurpleBuddy *)node;
1964 prpl = purple_find_prpl(purple_account_get_protocol_id(b->account));
1965 if (prpl != NULL)
1966 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
1968 if (prpl && prpl_info->get_info)
1969 pidgin_retrieve_user_info(b->account->gc, b->name);
1970 handled = TRUE;
1973 #if (1)
1975 * This code only exists because GTK+ doesn't work. If we return
1976 * FALSE here, as would be normal the event propoagates down and
1977 * somehow gets interpreted as the start of a drag event.
1979 * Um, isn't it _normal_ to return TRUE here? Since the event
1980 * was handled? --Mark
1982 if(handled) {
1983 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv));
1984 gtk_tree_selection_select_path(sel, path);
1985 gtk_tree_path_free(path);
1986 return TRUE;
1988 #endif
1989 gtk_tree_path_free(path);
1991 return FALSE;
1994 static gboolean
1995 pidgin_blist_popup_menu_cb(GtkWidget *tv, void *user_data)
1997 PurpleBlistNode *node;
1998 GtkTreeIter iter;
1999 GtkTreeSelection *sel;
2000 gboolean handled = FALSE;
2002 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv));
2003 if (!gtk_tree_selection_get_selected(sel, NULL, &iter))
2004 return FALSE;
2006 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
2008 /* Shift+F10 draws a context menu */
2009 handled = pidgin_blist_show_context_menu(node, pidgin_treeview_popup_menu_position_func, tv, 0, GDK_CURRENT_TIME);
2011 return handled;
2014 static void pidgin_blist_buddy_details_cb(gpointer data, guint action, GtkWidget *item)
2016 pidgin_set_cursor(gtkblist->window, GDK_WATCH);
2018 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons",
2019 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item)));
2021 pidgin_clear_cursor(gtkblist->window);
2024 static void pidgin_blist_show_idle_time_cb(gpointer data, guint action, GtkWidget *item)
2026 pidgin_set_cursor(gtkblist->window, GDK_WATCH);
2028 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/show_idle_time",
2029 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item)));
2031 pidgin_clear_cursor(gtkblist->window);
2034 static void pidgin_blist_show_protocol_icons_cb(gpointer data, guint action, GtkWidget *item)
2036 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons",
2037 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item)));
2040 static void pidgin_blist_show_empty_groups_cb(gpointer data, guint action, GtkWidget *item)
2042 pidgin_set_cursor(gtkblist->window, GDK_WATCH);
2044 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/show_empty_groups",
2045 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item)));
2047 pidgin_clear_cursor(gtkblist->window);
2050 static void pidgin_blist_edit_mode_cb(gpointer callback_data, guint callback_action,
2051 GtkWidget *checkitem)
2053 pidgin_set_cursor(gtkblist->window, GDK_WATCH);
2055 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/show_offline_buddies",
2056 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(checkitem)));
2058 pidgin_clear_cursor(gtkblist->window);
2061 static void pidgin_blist_mute_sounds_cb(gpointer data, guint action, GtkWidget *item)
2063 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/sound/mute", GTK_CHECK_MENU_ITEM(item)->active);
2066 static void
2067 pidgin_blist_mute_pref_cb(const char *name, PurplePrefType type,
2068 gconstpointer value, gpointer data)
2070 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item(gtkblist->ift,
2071 N_("/Tools/Mute Sounds"))), (gboolean)GPOINTER_TO_INT(value));
2074 static void
2075 pidgin_blist_sound_method_pref_cb(const char *name, PurplePrefType type,
2076 gconstpointer value, gpointer data)
2078 gboolean sensitive = TRUE;
2080 if(!strcmp(value, "none"))
2081 sensitive = FALSE;
2083 gtk_widget_set_sensitive(gtk_item_factory_get_widget(gtkblist->ift, N_("/Tools/Mute Sounds")), sensitive);
2086 static void
2087 add_buddies_from_vcard(const char *prpl_id, PurpleGroup *group, GList *list,
2088 const char *alias)
2090 GList *l;
2091 PurpleAccount *account = NULL;
2092 PurpleConnection *gc;
2094 if (list == NULL)
2095 return;
2097 for (l = purple_connections_get_all(); l != NULL; l = l->next)
2099 gc = (PurpleConnection *)l->data;
2100 account = purple_connection_get_account(gc);
2102 if (!strcmp(purple_account_get_protocol_id(account), prpl_id))
2103 break;
2105 account = NULL;
2108 if (account != NULL)
2110 for (l = list; l != NULL; l = l->next)
2112 purple_blist_request_add_buddy(account, l->data,
2113 (group ? group->name : NULL),
2114 alias);
2118 g_list_foreach(list, (GFunc)g_free, NULL);
2119 g_list_free(list);
2122 static gboolean
2123 parse_vcard(const char *vcard, PurpleGroup *group)
2125 char *temp_vcard;
2126 char *s, *c;
2127 char *alias = NULL;
2128 GList *aims = NULL;
2129 GList *icqs = NULL;
2130 GList *yahoos = NULL;
2131 GList *msns = NULL;
2132 GList *jabbers = NULL;
2134 s = temp_vcard = g_strdup(vcard);
2136 while (*s != '\0' && strncmp(s, "END:vCard", strlen("END:vCard")))
2138 char *field, *value;
2140 field = s;
2142 /* Grab the field */
2143 while (*s != '\r' && *s != '\n' && *s != '\0' && *s != ':')
2144 s++;
2146 if (*s == '\r') s++;
2147 if (*s == '\n')
2149 s++;
2150 continue;
2153 if (*s != '\0') *s++ = '\0';
2155 if ((c = strchr(field, ';')) != NULL)
2156 *c = '\0';
2158 /* Proceed to the end of the line */
2159 value = s;
2161 while (*s != '\r' && *s != '\n' && *s != '\0')
2162 s++;
2164 if (*s == '\r') *s++ = '\0';
2165 if (*s == '\n') *s++ = '\0';
2167 /* We only want to worry about a few fields here. */
2168 if (!strcmp(field, "FN"))
2169 alias = g_strdup(value);
2170 else if (!strcmp(field, "X-AIM") || !strcmp(field, "X-ICQ") ||
2171 !strcmp(field, "X-YAHOO") || !strcmp(field, "X-MSN") ||
2172 !strcmp(field, "X-JABBER"))
2174 char **values = g_strsplit(value, ":", 0);
2175 char **im;
2177 for (im = values; *im != NULL; im++)
2179 if (!strcmp(field, "X-AIM"))
2180 aims = g_list_append(aims, g_strdup(*im));
2181 else if (!strcmp(field, "X-ICQ"))
2182 icqs = g_list_append(icqs, g_strdup(*im));
2183 else if (!strcmp(field, "X-YAHOO"))
2184 yahoos = g_list_append(yahoos, g_strdup(*im));
2185 else if (!strcmp(field, "X-MSN"))
2186 msns = g_list_append(msns, g_strdup(*im));
2187 else if (!strcmp(field, "X-JABBER"))
2188 jabbers = g_list_append(jabbers, g_strdup(*im));
2191 g_strfreev(values);
2195 g_free(temp_vcard);
2197 if (aims == NULL && icqs == NULL && yahoos == NULL &&
2198 msns == NULL && jabbers == NULL)
2200 g_free(alias);
2202 return FALSE;
2205 add_buddies_from_vcard("prpl-aim", group, aims, alias);
2206 add_buddies_from_vcard("prpl-icq", group, icqs, alias);
2207 add_buddies_from_vcard("prpl-yahoo", group, yahoos, alias);
2208 add_buddies_from_vcard("prpl-msn", group, msns, alias);
2209 add_buddies_from_vcard("prpl-jabber", group, jabbers, alias);
2211 g_free(alias);
2213 return TRUE;
2216 #ifdef _WIN32
2217 static void pidgin_blist_drag_begin(GtkWidget *widget,
2218 GdkDragContext *drag_context, gpointer user_data)
2220 pidgin_blist_tooltip_destroy();
2223 /* Unhook the tooltip-timeout since we don't want a tooltip
2224 * to appear and obscure the dragging operation.
2225 * This is a workaround for GTK+ bug 107320. */
2226 if (gtkblist->timeout) {
2227 g_source_remove(gtkblist->timeout);
2228 gtkblist->timeout = 0;
2231 #endif
2233 static void pidgin_blist_drag_data_get_cb(GtkWidget *widget,
2234 GdkDragContext *dc,
2235 GtkSelectionData *data,
2236 guint info,
2237 guint time,
2238 gpointer null)
2241 if (data->target == gdk_atom_intern("PURPLE_BLIST_NODE", FALSE))
2243 GtkTreeRowReference *ref = g_object_get_data(G_OBJECT(dc), "gtk-tree-view-source-row");
2244 GtkTreePath *sourcerow = gtk_tree_row_reference_get_path(ref);
2245 GtkTreeIter iter;
2246 PurpleBlistNode *node = NULL;
2247 if(!sourcerow)
2248 return;
2249 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, sourcerow);
2250 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
2251 gtk_selection_data_set (data,
2252 gdk_atom_intern ("PURPLE_BLIST_NODE", FALSE),
2253 8, /* bits */
2254 (void*)&node,
2255 sizeof (node));
2257 gtk_tree_path_free(sourcerow);
2259 else if (data->target == gdk_atom_intern("application/x-im-contact", FALSE))
2261 GtkTreeRowReference *ref;
2262 GtkTreePath *sourcerow;
2263 GtkTreeIter iter;
2264 PurpleBlistNode *node = NULL;
2265 PurpleBuddy *buddy;
2266 PurpleConnection *gc;
2267 GString *str;
2268 const char *protocol;
2270 ref = g_object_get_data(G_OBJECT(dc), "gtk-tree-view-source-row");
2271 sourcerow = gtk_tree_row_reference_get_path(ref);
2273 if (!sourcerow)
2274 return;
2276 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter,
2277 sourcerow);
2278 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
2280 if (PURPLE_BLIST_NODE_IS_CONTACT(node))
2282 buddy = purple_contact_get_priority_buddy((PurpleContact *)node);
2284 else if (!PURPLE_BLIST_NODE_IS_BUDDY(node))
2286 gtk_tree_path_free(sourcerow);
2287 return;
2289 else
2291 buddy = (PurpleBuddy *)node;
2294 gc = purple_account_get_connection(buddy->account);
2296 if (gc == NULL)
2298 gtk_tree_path_free(sourcerow);
2299 return;
2302 protocol =
2303 PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->list_icon(buddy->account,
2304 buddy);
2306 str = g_string_new(NULL);
2307 g_string_printf(str,
2308 "MIME-Version: 1.0\r\n"
2309 "Content-Type: application/x-im-contact\r\n"
2310 "X-IM-Protocol: %s\r\n"
2311 "X-IM-Username: %s\r\n",
2312 protocol,
2313 buddy->name);
2315 if (buddy->alias != NULL)
2317 g_string_append_printf(str,
2318 "X-IM-Alias: %s\r\n",
2319 buddy->alias);
2322 g_string_append(str, "\r\n");
2324 gtk_selection_data_set(data,
2325 gdk_atom_intern("application/x-im-contact", FALSE),
2326 8, /* bits */
2327 (const guchar *)str->str,
2328 strlen(str->str) + 1);
2330 g_string_free(str, TRUE);
2331 gtk_tree_path_free(sourcerow);
2335 static void pidgin_blist_drag_data_rcv_cb(GtkWidget *widget, GdkDragContext *dc, guint x, guint y,
2336 GtkSelectionData *sd, guint info, guint t)
2338 if (gtkblist->drag_timeout) {
2339 g_source_remove(gtkblist->drag_timeout);
2340 gtkblist->drag_timeout = 0;
2343 if (sd->target == gdk_atom_intern("PURPLE_BLIST_NODE", FALSE) && sd->data) {
2344 PurpleBlistNode *n = NULL;
2345 GtkTreePath *path = NULL;
2346 GtkTreeViewDropPosition position;
2347 memcpy(&n, sd->data, sizeof(n));
2348 if(gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget), x, y, &path, &position)) {
2349 /* if we're here, I think it means the drop is ok */
2350 GtkTreeIter iter;
2351 PurpleBlistNode *node;
2352 struct _pidgin_blist_node *gtknode;
2354 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel),
2355 &iter, path);
2356 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel),
2357 &iter, NODE_COLUMN, &node, -1);
2358 gtknode = node->ui_data;
2360 if (PURPLE_BLIST_NODE_IS_CONTACT(n)) {
2361 PurpleContact *c = (PurpleContact*)n;
2362 if (PURPLE_BLIST_NODE_IS_CONTACT(node) && gtknode->contact_expanded) {
2363 purple_blist_merge_contact(c, node);
2364 } else if (PURPLE_BLIST_NODE_IS_CONTACT(node) ||
2365 PURPLE_BLIST_NODE_IS_CHAT(node)) {
2366 switch(position) {
2367 case GTK_TREE_VIEW_DROP_AFTER:
2368 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
2369 purple_blist_add_contact(c, (PurpleGroup*)node->parent,
2370 node);
2371 break;
2372 case GTK_TREE_VIEW_DROP_BEFORE:
2373 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
2374 purple_blist_add_contact(c, (PurpleGroup*)node->parent,
2375 node->prev);
2376 break;
2378 } else if(PURPLE_BLIST_NODE_IS_GROUP(node)) {
2379 purple_blist_add_contact(c, (PurpleGroup*)node, NULL);
2380 } else if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
2381 purple_blist_merge_contact(c, node);
2383 } else if (PURPLE_BLIST_NODE_IS_BUDDY(n)) {
2384 PurpleBuddy *b = (PurpleBuddy*)n;
2385 if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
2386 switch(position) {
2387 case GTK_TREE_VIEW_DROP_AFTER:
2388 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
2389 purple_blist_add_buddy(b, (PurpleContact*)node->parent,
2390 (PurpleGroup*)node->parent->parent, node);
2391 break;
2392 case GTK_TREE_VIEW_DROP_BEFORE:
2393 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
2394 purple_blist_add_buddy(b, (PurpleContact*)node->parent,
2395 (PurpleGroup*)node->parent->parent,
2396 node->prev);
2397 break;
2399 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
2400 purple_blist_add_buddy(b, NULL, (PurpleGroup*)node->parent,
2401 NULL);
2402 } else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
2403 purple_blist_add_buddy(b, NULL, (PurpleGroup*)node, NULL);
2404 } else if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
2405 if(gtknode->contact_expanded) {
2406 switch(position) {
2407 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
2408 case GTK_TREE_VIEW_DROP_AFTER:
2409 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
2410 purple_blist_add_buddy(b, (PurpleContact*)node,
2411 (PurpleGroup*)node->parent, NULL);
2412 break;
2413 case GTK_TREE_VIEW_DROP_BEFORE:
2414 purple_blist_add_buddy(b, NULL,
2415 (PurpleGroup*)node->parent, node->prev);
2416 break;
2418 } else {
2419 switch(position) {
2420 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
2421 case GTK_TREE_VIEW_DROP_AFTER:
2422 purple_blist_add_buddy(b, NULL,
2423 (PurpleGroup*)node->parent, NULL);
2424 break;
2425 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
2426 case GTK_TREE_VIEW_DROP_BEFORE:
2427 purple_blist_add_buddy(b, NULL,
2428 (PurpleGroup*)node->parent, node->prev);
2429 break;
2433 } else if (PURPLE_BLIST_NODE_IS_CHAT(n)) {
2434 PurpleChat *chat = (PurpleChat *)n;
2435 if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
2436 switch(position) {
2437 case GTK_TREE_VIEW_DROP_AFTER:
2438 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
2439 case GTK_TREE_VIEW_DROP_BEFORE:
2440 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
2441 purple_blist_add_chat(chat,
2442 (PurpleGroup*)node->parent->parent,
2443 node->parent);
2444 break;
2446 } else if(PURPLE_BLIST_NODE_IS_CONTACT(node) ||
2447 PURPLE_BLIST_NODE_IS_CHAT(node)) {
2448 switch(position) {
2449 case GTK_TREE_VIEW_DROP_AFTER:
2450 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
2451 purple_blist_add_chat(chat, (PurpleGroup*)node->parent, node);
2452 break;
2453 case GTK_TREE_VIEW_DROP_BEFORE:
2454 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
2455 purple_blist_add_chat(chat, (PurpleGroup*)node->parent, node->prev);
2456 break;
2458 } else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
2459 purple_blist_add_chat(chat, (PurpleGroup*)node, NULL);
2461 } else if (PURPLE_BLIST_NODE_IS_GROUP(n)) {
2462 PurpleGroup *g = (PurpleGroup*)n;
2463 if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
2464 switch (position) {
2465 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
2466 case GTK_TREE_VIEW_DROP_AFTER:
2467 purple_blist_add_group(g, node);
2468 break;
2469 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
2470 case GTK_TREE_VIEW_DROP_BEFORE:
2471 purple_blist_add_group(g, node->prev);
2472 break;
2474 } else if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
2475 purple_blist_add_group(g, node->parent->parent);
2476 } else if(PURPLE_BLIST_NODE_IS_CONTACT(node) ||
2477 PURPLE_BLIST_NODE_IS_CHAT(node)) {
2478 purple_blist_add_group(g, node->parent);
2482 gtk_tree_path_free(path);
2483 gtk_drag_finish(dc, TRUE, (dc->action == GDK_ACTION_MOVE), t);
2486 else if (sd->target == gdk_atom_intern("application/x-im-contact",
2487 FALSE) && sd->data)
2489 PurpleGroup *group = NULL;
2490 GtkTreePath *path = NULL;
2491 GtkTreeViewDropPosition position;
2492 PurpleAccount *account;
2493 char *protocol = NULL;
2494 char *username = NULL;
2495 char *alias = NULL;
2497 if (gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget),
2498 x, y, &path, &position))
2500 GtkTreeIter iter;
2501 PurpleBlistNode *node;
2503 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel),
2504 &iter, path);
2505 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel),
2506 &iter, NODE_COLUMN, &node, -1);
2508 if (PURPLE_BLIST_NODE_IS_BUDDY(node))
2510 group = (PurpleGroup *)node->parent->parent;
2512 else if (PURPLE_BLIST_NODE_IS_CHAT(node) ||
2513 PURPLE_BLIST_NODE_IS_CONTACT(node))
2515 group = (PurpleGroup *)node->parent;
2517 else if (PURPLE_BLIST_NODE_IS_GROUP(node))
2519 group = (PurpleGroup *)node;
2523 if (pidgin_parse_x_im_contact((const char *)sd->data, FALSE, &account,
2524 &protocol, &username, &alias))
2526 if (account == NULL)
2528 purple_notify_error(NULL, NULL,
2529 _("You are not currently signed on with an account that "
2530 "can add that buddy."), NULL);
2532 else
2534 purple_blist_request_add_buddy(account, username,
2535 (group ? group->name : NULL),
2536 alias);
2540 g_free(username);
2541 g_free(protocol);
2542 g_free(alias);
2544 if (path != NULL)
2545 gtk_tree_path_free(path);
2547 gtk_drag_finish(dc, TRUE, (dc->action == GDK_ACTION_MOVE), t);
2549 else if (sd->target == gdk_atom_intern("text/x-vcard", FALSE) && sd->data)
2551 gboolean result;
2552 PurpleGroup *group = NULL;
2553 GtkTreePath *path = NULL;
2554 GtkTreeViewDropPosition position;
2556 if (gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget),
2557 x, y, &path, &position))
2559 GtkTreeIter iter;
2560 PurpleBlistNode *node;
2562 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel),
2563 &iter, path);
2564 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel),
2565 &iter, NODE_COLUMN, &node, -1);
2567 if (PURPLE_BLIST_NODE_IS_BUDDY(node))
2569 group = (PurpleGroup *)node->parent->parent;
2571 else if (PURPLE_BLIST_NODE_IS_CHAT(node) ||
2572 PURPLE_BLIST_NODE_IS_CONTACT(node))
2574 group = (PurpleGroup *)node->parent;
2576 else if (PURPLE_BLIST_NODE_IS_GROUP(node))
2578 group = (PurpleGroup *)node;
2582 result = parse_vcard((const gchar *)sd->data, group);
2584 gtk_drag_finish(dc, result, (dc->action == GDK_ACTION_MOVE), t);
2585 } else if (sd->target == gdk_atom_intern("text/uri-list", FALSE) && sd->data) {
2586 GtkTreePath *path = NULL;
2587 GtkTreeViewDropPosition position;
2589 if (gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget),
2590 x, y, &path, &position))
2592 GtkTreeIter iter;
2593 PurpleBlistNode *node;
2595 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel),
2596 &iter, path);
2597 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel),
2598 &iter, NODE_COLUMN, &node, -1);
2600 if (PURPLE_BLIST_NODE_IS_BUDDY(node) || PURPLE_BLIST_NODE_IS_CONTACT(node)) {
2601 PurpleBuddy *b = PURPLE_BLIST_NODE_IS_BUDDY(node) ? PURPLE_BUDDY(node) : purple_contact_get_priority_buddy(PURPLE_CONTACT(node));
2602 pidgin_dnd_file_manage(sd, b->account, b->name);
2603 gtk_drag_finish(dc, TRUE, (dc->action == GDK_ACTION_MOVE), t);
2604 } else {
2605 gtk_drag_finish(dc, FALSE, FALSE, t);
2611 /* Altered from do_colorshift in gnome-panel */
2612 static void
2613 do_alphashift(GdkPixbuf *pixbuf, int shift)
2615 gint i, j;
2616 gint width, height, padding;
2617 guchar *pixels;
2618 int val;
2620 if (!gdk_pixbuf_get_has_alpha(pixbuf))
2621 return;
2623 width = gdk_pixbuf_get_width(pixbuf);
2624 height = gdk_pixbuf_get_height(pixbuf);
2625 padding = gdk_pixbuf_get_rowstride(pixbuf) - width * 4;
2626 pixels = gdk_pixbuf_get_pixels(pixbuf);
2628 for (i = 0; i < height; i++) {
2629 for (j = 0; j < width; j++) {
2630 pixels++;
2631 pixels++;
2632 pixels++;
2633 val = *pixels - shift;
2634 *(pixels++) = CLAMP(val, 0, 255);
2636 pixels += padding;
2641 static GdkPixbuf *pidgin_blist_get_buddy_icon(PurpleBlistNode *node,
2642 gboolean scaled, gboolean greyed)
2644 gsize len;
2645 GdkPixbufLoader *loader;
2646 PurpleBuddy *buddy = NULL;
2647 PurpleGroup *group = NULL;
2648 const guchar *data = NULL;
2649 GdkPixbuf *buf, *ret = NULL;
2650 PurpleBuddyIcon *icon = NULL;
2651 PurpleAccount *account = NULL;
2652 PurpleContact *contact = NULL;
2653 PurpleStoredImage *custom_img;
2654 PurplePluginProtocolInfo *prpl_info = NULL;
2655 gint orig_width, orig_height, scale_width, scale_height;
2657 if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
2658 buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
2659 contact = (PurpleContact*)node;
2660 } else if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
2661 buddy = (PurpleBuddy*)node;
2662 contact = purple_buddy_get_contact(buddy);
2663 } else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
2664 group = (PurpleGroup*)node;
2665 } else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
2666 /* We don't need to do anything here. We just need to not fall
2667 * into the else block and return. */
2668 } else {
2669 return NULL;
2672 if (buddy) {
2673 account = purple_buddy_get_account(buddy);
2676 if(account && account->gc) {
2677 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(account->gc->prpl);
2680 #if 0
2681 if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons"))
2682 return NULL;
2683 #endif
2685 /* If we have a contact then this is either a contact or a buddy and
2686 * we want to fetch the custom icon for the contact. If we don't have
2687 * a contact then this is a group or some other type of node and we
2688 * want to use that directly. */
2689 if (contact) {
2690 custom_img = purple_buddy_icons_node_find_custom_icon((PurpleBlistNode*)contact);
2691 } else {
2692 custom_img = purple_buddy_icons_node_find_custom_icon(node);
2695 if (custom_img) {
2696 data = purple_imgstore_get_data(custom_img);
2697 len = purple_imgstore_get_size(custom_img);
2700 if (data == NULL) {
2701 if (buddy) {
2702 /* Not sure I like this...*/
2703 if (!(icon = purple_buddy_icons_find(buddy->account, buddy->name)))
2704 return NULL;
2705 data = purple_buddy_icon_get_data(icon, &len);
2708 if(data == NULL)
2709 return NULL;
2712 loader = gdk_pixbuf_loader_new();
2713 gdk_pixbuf_loader_write(loader, data, len, NULL);
2714 gdk_pixbuf_loader_close(loader, NULL);
2716 purple_imgstore_unref(custom_img);
2717 purple_buddy_icon_unref(icon);
2719 buf = gdk_pixbuf_loader_get_pixbuf(loader);
2720 if (buf)
2721 g_object_ref(G_OBJECT(buf));
2722 g_object_unref(G_OBJECT(loader));
2724 if (!buf) {
2725 return NULL;
2728 if (greyed) {
2729 gboolean offline = FALSE, idle = FALSE;
2731 if (buddy) {
2732 PurplePresence *presence = purple_buddy_get_presence(buddy);
2733 if (!PURPLE_BUDDY_IS_ONLINE(buddy))
2734 offline = TRUE;
2735 if (purple_presence_is_idle(presence))
2736 idle = TRUE;
2737 } else if (group) {
2738 if (purple_blist_get_group_online_count(group) == 0)
2739 offline = TRUE;
2742 if (offline)
2743 gdk_pixbuf_saturate_and_pixelate(buf, buf, 0.0, FALSE);
2745 if (idle)
2746 gdk_pixbuf_saturate_and_pixelate(buf, buf, 0.25, FALSE);
2749 /* I'd use the pidgin_buddy_icon_get_scale_size() thing, but it won't
2750 * tell me the original size, which I need for scaling purposes. */
2751 scale_width = orig_width = gdk_pixbuf_get_width(buf);
2752 scale_height = orig_height = gdk_pixbuf_get_height(buf);
2754 if (prpl_info && prpl_info->icon_spec.scale_rules & PURPLE_ICON_SCALE_DISPLAY)
2755 purple_buddy_icon_get_scale_size(&prpl_info->icon_spec, &scale_width, &scale_height);
2757 if (scaled || scale_height > 200 || scale_width > 200) {
2758 GdkPixbuf *tmpbuf;
2759 float scale_size = scaled ? 32.0 : 200.0;
2760 if(scale_height > scale_width) {
2761 scale_width = scale_size * (double)scale_width / (double)scale_height;
2762 scale_height = scale_size;
2763 } else {
2764 scale_height = scale_size * (double)scale_height / (double)scale_width;
2765 scale_width = scale_size;
2767 /* Scale & round before making square, so rectangular (but
2768 * non-square) images get rounded corners too. */
2769 tmpbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, scale_width, scale_height);
2770 gdk_pixbuf_fill(tmpbuf, 0x00000000);
2771 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);
2772 if (pidgin_gdk_pixbuf_is_opaque(tmpbuf))
2773 pidgin_gdk_pixbuf_make_round(tmpbuf);
2774 ret = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, scale_size, scale_size);
2775 gdk_pixbuf_fill(ret, 0x00000000);
2776 gdk_pixbuf_copy_area(tmpbuf, 0, 0, scale_width, scale_height, ret, (scale_size-scale_width)/2, (scale_size-scale_height)/2);
2777 g_object_unref(G_OBJECT(tmpbuf));
2778 } else {
2779 ret = gdk_pixbuf_scale_simple(buf,scale_width,scale_height, GDK_INTERP_BILINEAR);
2781 g_object_unref(G_OBJECT(buf));
2783 return ret;
2786 /* # - Status Icon
2787 * P - Protocol Icon
2788 * A - Buddy Icon
2789 * [ - SMALL_SPACE
2790 * = - LARGE_SPACE
2791 * +--- STATUS_SIZE +--- td->avatar_width
2792 * | +-- td->name_width |
2793 * +----+ +-------+ +---------+
2794 * | | | | | |
2795 * +-------------------------------------------+
2796 * | [ = [ |--- TOOLTIP_BORDER
2797 *name_height --+-| ######[BuddyName = PP [ AAAAAAAAAAA |--+
2798 * | | ######[ = PP [ AAAAAAAAAAA | |
2799 * STATUS SIZE -| | ######[[[[[[[[[[[[[[[[[[[[[ AAAAAAAAAAA | |
2800 * +--+-| ######[Account: So-and-so [ AAAAAAAAAAA | |-- td->avatar_height
2801 * | | [Idle: 4h 15m [ AAAAAAAAAAA | |
2802 * height --+ | [Foo: Bar, Baz [ AAAAAAAAAAA | |
2803 * | | [Status: Awesome [ AAAAAAAAAAA |--+
2804 * +----| [Stop: Hammer Time [ |
2805 * | [ [ |--- TOOLTIP_BORDER
2806 * +-------------------------------------------+
2807 * | | | |
2808 * | +----------------+ |
2809 * | | |
2810 * | +-- td->width |
2811 * | |
2812 * +---- TOOLTIP_BORDER +---- TOOLTIP_BORDER
2816 #define STATUS_SIZE 16
2817 #define TOOLTIP_BORDER 12
2818 #define SMALL_SPACE 6
2819 #define LARGE_SPACE 12
2820 #define PRPL_SIZE 16
2821 struct tooltip_data {
2822 PangoLayout *layout;
2823 PangoLayout *name_layout;
2824 GdkPixbuf *prpl_icon;
2825 GdkPixbuf *status_icon;
2826 GdkPixbuf *avatar;
2827 gboolean avatar_is_prpl_icon;
2828 int avatar_width;
2829 int avatar_height;
2830 int name_height;
2831 int name_width;
2832 int width;
2833 int height;
2834 int padding;
2837 static PangoLayout * create_pango_layout(const char *markup, int *width, int *height)
2839 PangoLayout *layout;
2840 int w, h;
2842 layout = gtk_widget_create_pango_layout(gtkblist->tipwindow, NULL);
2843 pango_layout_set_markup(layout, markup, -1);
2844 pango_layout_set_wrap(layout, PANGO_WRAP_WORD);
2845 pango_layout_set_width(layout, 300000);
2847 pango_layout_get_size (layout, &w, &h);
2848 if (width)
2849 *width = PANGO_PIXELS(w);
2850 if (height)
2851 *height = PANGO_PIXELS(h);
2852 return layout;
2855 static struct tooltip_data * create_tip_for_account(PurpleAccount *account)
2857 struct tooltip_data *td = g_new0(struct tooltip_data, 1);
2858 td->status_icon = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL);
2859 /* Yes, status_icon, not prpl_icon */
2860 if (purple_account_is_disconnected(account))
2861 gdk_pixbuf_saturate_and_pixelate(td->status_icon, td->status_icon, 0.0, FALSE);
2862 td->layout = create_pango_layout(purple_account_get_username(account), &td->width, &td->height);
2863 td->padding = SMALL_SPACE;
2864 return td;
2867 static struct tooltip_data * create_tip_for_node(PurpleBlistNode *node, gboolean full)
2869 struct tooltip_data *td = g_new0(struct tooltip_data, 1);
2870 PurpleAccount *account = NULL;
2871 char *tmp = NULL, *node_name = NULL, *tooltip_text = NULL;
2873 if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
2874 account = ((PurpleBuddy*)(node))->account;
2875 } else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
2876 account = ((PurpleChat*)(node))->account;
2879 td->padding = TOOLTIP_BORDER;
2880 td->status_icon = pidgin_blist_get_status_icon(node, PIDGIN_STATUS_ICON_LARGE);
2881 td->avatar = pidgin_blist_get_buddy_icon(node, !full, FALSE);
2882 if (account != NULL) {
2883 td->prpl_icon = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL);
2885 tooltip_text = pidgin_get_tooltip_text(node, full);
2886 if (tooltip_text && *tooltip_text) {
2887 td->layout = create_pango_layout(tooltip_text, &td->width, &td->height);
2890 if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
2891 tmp = g_markup_escape_text(purple_buddy_get_name((PurpleBuddy*)node), -1);
2892 } else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
2893 tmp = g_markup_escape_text(purple_chat_get_name((PurpleChat*)node), -1);
2894 } else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
2895 tmp = g_markup_escape_text(purple_group_get_name((PurpleGroup*)node), -1);
2896 } else {
2897 /* I don't believe this can happen currently, I think
2898 * everything that calls this function checks for one of the
2899 * above node types first. */
2900 tmp = g_strdup(_("Unknown node type"));
2902 node_name = g_strdup_printf("<span size='x-large' weight='bold'>%s</span>",
2903 tmp ? tmp : "");
2904 g_free(tmp);
2906 td->name_layout = create_pango_layout(node_name, &td->name_width, &td->name_height);
2907 td->name_width += SMALL_SPACE + PRPL_SIZE;
2908 td->name_height = MAX(td->name_height, PRPL_SIZE + SMALL_SPACE);
2909 #if 0 /* PRPL Icon as avatar */
2910 if(!td->avatar && full) {
2911 td->avatar = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_LARGE);
2912 td->avatar_is_prpl_icon = TRUE;
2914 #endif
2916 if (td->avatar) {
2917 td->avatar_width = gdk_pixbuf_get_width(td->avatar);
2918 td->avatar_height = gdk_pixbuf_get_height(td->avatar);
2921 g_free(node_name);
2922 g_free(tooltip_text);
2923 return td;
2926 static gboolean
2927 pidgin_blist_paint_tip(GtkWidget *widget, gpointer null)
2929 GtkStyle *style;
2930 int current_height, max_width;
2931 int max_text_width;
2932 int max_avatar_width;
2933 GList *l;
2934 int prpl_col = 0;
2935 GtkTextDirection dir = gtk_widget_get_direction(widget);
2936 int status_size = 0;
2938 if(gtkblist->tooltipdata == NULL)
2939 return FALSE;
2941 style = gtkblist->tipwindow->style;
2943 max_text_width = 0;
2944 max_avatar_width = 0;
2946 for(l = gtkblist->tooltipdata; l; l = l->next)
2948 struct tooltip_data *td = l->data;
2950 max_text_width = MAX(max_text_width,
2951 MAX(td->width, td->name_width));
2952 max_avatar_width = MAX(max_avatar_width, td->avatar_width);
2953 if (td->status_icon)
2954 status_size = STATUS_SIZE;
2957 max_width = TOOLTIP_BORDER + status_size + SMALL_SPACE + max_text_width + SMALL_SPACE + max_avatar_width + TOOLTIP_BORDER;
2958 if (dir == GTK_TEXT_DIR_RTL)
2959 prpl_col = TOOLTIP_BORDER + max_avatar_width + SMALL_SPACE;
2960 else
2961 prpl_col = TOOLTIP_BORDER + status_size + SMALL_SPACE + max_text_width - PRPL_SIZE;
2963 current_height = 12;
2964 for(l = gtkblist->tooltipdata; l; l = l->next)
2966 struct tooltip_data *td = l->data;
2968 if (td->avatar && pidgin_gdk_pixbuf_is_opaque(td->avatar))
2970 if (dir == GTK_TEXT_DIR_RTL)
2971 gtk_paint_flat_box(style, gtkblist->tipwindow->window, GTK_STATE_NORMAL, GTK_SHADOW_OUT,
2972 NULL, gtkblist->tipwindow, "tooltip",
2973 TOOLTIP_BORDER -1, current_height -1, td->avatar_width +2, td->avatar_height + 2);
2974 else
2975 gtk_paint_flat_box(style, gtkblist->tipwindow->window, GTK_STATE_NORMAL, GTK_SHADOW_OUT,
2976 NULL, gtkblist->tipwindow, "tooltip",
2977 max_width - (td->avatar_width+ TOOLTIP_BORDER)-1,
2978 current_height-1,td->avatar_width+2, td->avatar_height+2);
2981 if (td->status_icon) {
2982 if (dir == GTK_TEXT_DIR_RTL)
2983 gdk_draw_pixbuf(GDK_DRAWABLE(gtkblist->tipwindow->window), NULL, td->status_icon,
2984 0, 0, max_width - TOOLTIP_BORDER - status_size, current_height, -1, -1, GDK_RGB_DITHER_NONE, 0, 0);
2985 else
2986 gdk_draw_pixbuf(GDK_DRAWABLE(gtkblist->tipwindow->window), NULL, td->status_icon,
2987 0, 0, TOOLTIP_BORDER, current_height, -1 , -1, GDK_RGB_DITHER_NONE, 0, 0);
2990 if(td->avatar) {
2991 if (dir == GTK_TEXT_DIR_RTL)
2992 gdk_draw_pixbuf(GDK_DRAWABLE(gtkblist->tipwindow->window), NULL,
2993 td->avatar, 0, 0, TOOLTIP_BORDER, current_height, -1, -1, GDK_RGB_DITHER_NONE, 0, 0);
2994 else
2995 gdk_draw_pixbuf(GDK_DRAWABLE(gtkblist->tipwindow->window), NULL,
2996 td->avatar, 0, 0, max_width - (td->avatar_width + TOOLTIP_BORDER),
2997 current_height, -1 , -1, GDK_RGB_DITHER_NONE, 0, 0);
3000 if (!td->avatar_is_prpl_icon && td->prpl_icon)
3001 gdk_draw_pixbuf(GDK_DRAWABLE(gtkblist->tipwindow->window), NULL, td->prpl_icon,
3002 0, 0,
3003 prpl_col,
3004 current_height + ((td->name_height / 2) - (PRPL_SIZE / 2)),
3005 -1 , -1, GDK_RGB_DITHER_NONE, 0, 0);
3007 if (td->name_layout) {
3008 if (dir == GTK_TEXT_DIR_RTL) {
3009 gtk_paint_layout(style, gtkblist->tipwindow->window, GTK_STATE_NORMAL, FALSE,
3010 NULL, gtkblist->tipwindow, "tooltip",
3011 max_width -(TOOLTIP_BORDER + status_size + SMALL_SPACE) - PANGO_PIXELS(300000),
3012 current_height, td->name_layout);
3013 } else {
3014 gtk_paint_layout (style, gtkblist->tipwindow->window, GTK_STATE_NORMAL, FALSE,
3015 NULL, gtkblist->tipwindow, "tooltip",
3016 TOOLTIP_BORDER + status_size + SMALL_SPACE, current_height, td->name_layout);
3020 if (td->layout) {
3021 if (dir != GTK_TEXT_DIR_RTL) {
3022 gtk_paint_layout (style, gtkblist->tipwindow->window, GTK_STATE_NORMAL, FALSE,
3023 NULL, gtkblist->tipwindow, "tooltip",
3024 TOOLTIP_BORDER + status_size + SMALL_SPACE, current_height + td->name_height, td->layout);
3025 } else {
3026 gtk_paint_layout(style, gtkblist->tipwindow->window, GTK_STATE_NORMAL, FALSE,
3027 NULL, gtkblist->tipwindow, "tooltip",
3028 max_width - (TOOLTIP_BORDER + status_size + SMALL_SPACE) - PANGO_PIXELS(300000),
3029 current_height + td->name_height,
3030 td->layout);
3034 current_height += MAX(td->name_height + td->height, td->avatar_height) + td->padding;
3036 return FALSE;
3039 static void
3040 pidgin_blist_destroy_tooltip_data(void)
3042 while(gtkblist->tooltipdata) {
3043 struct tooltip_data *td = gtkblist->tooltipdata->data;
3045 if(td->avatar)
3046 g_object_unref(td->avatar);
3047 if(td->status_icon)
3048 g_object_unref(td->status_icon);
3049 if(td->prpl_icon)
3050 g_object_unref(td->prpl_icon);
3051 if (td->layout)
3052 g_object_unref(td->layout);
3053 if (td->name_layout)
3054 g_object_unref(td->name_layout);
3055 g_free(td);
3056 gtkblist->tooltipdata = g_list_delete_link(gtkblist->tooltipdata, gtkblist->tooltipdata);
3060 void pidgin_blist_tooltip_destroy()
3062 pidgin_blist_destroy_tooltip_data();
3063 pidgin_tooltip_destroy();
3066 static void
3067 pidgin_blist_align_tooltip(struct tooltip_data *td, GtkWidget *widget)
3069 GtkTextDirection dir = gtk_widget_get_direction(widget);
3071 if (dir == GTK_TEXT_DIR_RTL)
3073 char* layout_name = purple_markup_strip_html(pango_layout_get_text(td->name_layout));
3074 PangoDirection dir = pango_find_base_dir(layout_name, -1);
3075 if (dir == PANGO_DIRECTION_RTL || dir == PANGO_DIRECTION_NEUTRAL)
3076 pango_layout_set_alignment(td->name_layout, PANGO_ALIGN_RIGHT);
3077 g_free(layout_name);
3078 pango_layout_set_alignment(td->layout, PANGO_ALIGN_RIGHT);
3082 static gboolean
3083 pidgin_blist_create_tooltip_for_node(GtkWidget *widget, gpointer data, int *w, int *h)
3085 PurpleBlistNode *node = data;
3086 int width, height;
3087 GList *list;
3088 int max_text_width = 0;
3089 int max_avatar_width = 0;
3090 int status_size = 0;
3092 if (gtkblist->tooltipdata) {
3093 gtkblist->tipwindow = NULL;
3094 pidgin_blist_destroy_tooltip_data();
3097 gtkblist->tipwindow = widget;
3098 if (PURPLE_BLIST_NODE_IS_CHAT(node) ||
3099 PURPLE_BLIST_NODE_IS_BUDDY(node)) {
3100 struct tooltip_data *td = create_tip_for_node(node, TRUE);
3101 pidgin_blist_align_tooltip(td, gtkblist->tipwindow);
3102 gtkblist->tooltipdata = g_list_append(gtkblist->tooltipdata, td);
3103 } else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
3104 PurpleGroup *group = (PurpleGroup*)node;
3105 GSList *accounts;
3106 struct tooltip_data *td = create_tip_for_node(node, TRUE);
3107 pidgin_blist_align_tooltip(td, gtkblist->tipwindow);
3108 gtkblist->tooltipdata = g_list_append(gtkblist->tooltipdata, td);
3110 /* Accounts with buddies in group */
3111 accounts = purple_group_get_accounts(group);
3112 for (; accounts != NULL;
3113 accounts = g_slist_delete_link(accounts, accounts)) {
3114 PurpleAccount *account = accounts->data;
3115 td = create_tip_for_account(account);
3116 gtkblist->tooltipdata = g_list_append(gtkblist->tooltipdata, td);
3118 } else if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
3119 PurpleBlistNode *child;
3120 PurpleBuddy *b = purple_contact_get_priority_buddy((PurpleContact *)node);
3122 for(child = node->child; child; child = child->next)
3124 if(PURPLE_BLIST_NODE_IS_BUDDY(child) && buddy_is_displayable((PurpleBuddy*)child)) {
3125 struct tooltip_data *td = create_tip_for_node(child, (b == (PurpleBuddy*)child));
3126 pidgin_blist_align_tooltip(td, gtkblist->tipwindow);
3127 if (b == (PurpleBuddy *)child) {
3128 gtkblist->tooltipdata = g_list_prepend(gtkblist->tooltipdata, td);
3129 } else {
3130 gtkblist->tooltipdata = g_list_append(gtkblist->tooltipdata, td);
3134 } else {
3135 return FALSE;
3138 height = width = 0;
3139 for (list = gtkblist->tooltipdata; list; list = list->next) {
3140 struct tooltip_data *td = list->data;
3141 max_text_width = MAX(max_text_width, MAX(td->width, td->name_width));
3142 max_avatar_width = MAX(max_avatar_width, td->avatar_width);
3143 height += MAX(MAX(STATUS_SIZE, td->avatar_height), td->height + td->name_height) + td->padding;
3144 if (td->status_icon)
3145 status_size = MAX(status_size, STATUS_SIZE);
3147 height += TOOLTIP_BORDER;
3148 width = TOOLTIP_BORDER + status_size + SMALL_SPACE + max_text_width + SMALL_SPACE + max_avatar_width + TOOLTIP_BORDER;
3150 if (w)
3151 *w = width;
3152 if (h)
3153 *h = height;
3155 return TRUE;
3158 static gboolean pidgin_blist_expand_timeout(GtkWidget *tv)
3160 GtkTreePath *path;
3161 GtkTreeIter iter;
3162 PurpleBlistNode *node;
3163 struct _pidgin_blist_node *gtknode;
3165 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),
3166 &path, NULL, NULL, NULL))
3167 return FALSE;
3168 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
3169 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
3171 if(!PURPLE_BLIST_NODE_IS_CONTACT(node)) {
3172 gtk_tree_path_free(path);
3173 return FALSE;
3176 gtknode = node->ui_data;
3178 if (!gtknode->contact_expanded) {
3179 GtkTreeIter i;
3181 pidgin_blist_expand_contact_cb(NULL, node);
3183 gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, &gtkblist->contact_rect);
3184 gdk_drawable_get_size(GDK_DRAWABLE(tv->window), &(gtkblist->contact_rect.width), NULL);
3185 gtkblist->mouseover_contact = node;
3186 gtk_tree_path_down (path);
3187 while (gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &i, path)) {
3188 GdkRectangle rect;
3189 gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, &rect);
3190 gtkblist->contact_rect.height += rect.height;
3191 gtk_tree_path_next(path);
3194 gtk_tree_path_free(path);
3195 return FALSE;
3198 static gboolean buddy_is_displayable(PurpleBuddy *buddy)
3200 struct _pidgin_blist_node *gtknode;
3202 if(!buddy)
3203 return FALSE;
3205 gtknode = ((PurpleBlistNode*)buddy)->ui_data;
3207 return (purple_account_is_connected(buddy->account) &&
3208 (purple_presence_is_online(buddy->presence) ||
3209 (gtknode && gtknode->recent_signonoff) ||
3210 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_offline_buddies") ||
3211 purple_blist_node_get_bool((PurpleBlistNode*)buddy, "show_offline")));
3214 void pidgin_blist_draw_tooltip(PurpleBlistNode *node, GtkWidget *widget)
3216 pidgin_tooltip_show(widget, node, pidgin_blist_create_tooltip_for_node, pidgin_blist_paint_tip);
3219 static gboolean pidgin_blist_drag_motion_cb(GtkWidget *tv, GdkDragContext *drag_context,
3220 gint x, gint y, guint time, gpointer user_data)
3222 GtkTreePath *path;
3223 int delay;
3224 GdkRectangle rect;
3227 * When dragging a buddy into a contact, this is the delay before
3228 * the contact auto-expands.
3230 delay = 900;
3232 if (gtkblist->drag_timeout) {
3233 if ((y > gtkblist->tip_rect.y) && ((y - gtkblist->tip_rect.height) < gtkblist->tip_rect.y))
3234 return FALSE;
3235 /* We've left the cell. Remove the timeout and create a new one below */
3236 g_source_remove(gtkblist->drag_timeout);
3239 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), x, y, &path, NULL, NULL, NULL);
3240 gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, &rect);
3242 if (path)
3243 gtk_tree_path_free(path);
3245 /* Only autoexpand when in the middle of the cell to avoid annoying un-intended expands */
3246 if (y < rect.y + (rect.height / 3) ||
3247 y > rect.y + (2 * (rect.height /3)))
3248 return FALSE;
3250 rect.height = rect.height / 3;
3251 rect.y += rect.height;
3253 gtkblist->tip_rect = rect;
3255 gtkblist->drag_timeout = g_timeout_add(delay, (GSourceFunc)pidgin_blist_expand_timeout, tv);
3257 if (gtkblist->mouseover_contact) {
3258 if ((y < gtkblist->contact_rect.y) || ((y - gtkblist->contact_rect.height) > gtkblist->contact_rect.y)) {
3259 pidgin_blist_collapse_contact_cb(NULL, gtkblist->mouseover_contact);
3260 gtkblist->mouseover_contact = NULL;
3264 return FALSE;
3267 static gboolean
3268 pidgin_blist_create_tooltip(GtkWidget *widget, GtkTreePath *path,
3269 gpointer null, int *w, int *h)
3271 GtkTreeIter iter;
3272 PurpleBlistNode *node;
3273 gboolean editable = FALSE;
3275 /* If we're editing a cell (e.g. alias editing), don't show the tooltip */
3276 g_object_get(G_OBJECT(gtkblist->text_rend), "editable", &editable, NULL);
3277 if (editable)
3278 return FALSE;
3280 if (gtkblist->tooltipdata) {
3281 gtkblist->tipwindow = NULL;
3282 pidgin_blist_destroy_tooltip_data();
3285 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
3286 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
3288 return pidgin_blist_create_tooltip_for_node(widget, node, w, h);
3291 static gboolean pidgin_blist_motion_cb (GtkWidget *tv, GdkEventMotion *event, gpointer null)
3293 if (gtkblist->mouseover_contact) {
3294 if ((event->y < gtkblist->contact_rect.y) || ((event->y - gtkblist->contact_rect.height) > gtkblist->contact_rect.y)) {
3295 pidgin_blist_collapse_contact_cb(NULL, gtkblist->mouseover_contact);
3296 gtkblist->mouseover_contact = NULL;
3300 return FALSE;
3303 static gboolean pidgin_blist_leave_cb (GtkWidget *w, GdkEventCrossing *e, gpointer n)
3305 if (gtkblist->timeout) {
3306 g_source_remove(gtkblist->timeout);
3307 gtkblist->timeout = 0;
3310 if (gtkblist->drag_timeout) {
3311 g_source_remove(gtkblist->drag_timeout);
3312 gtkblist->drag_timeout = 0;
3315 if (gtkblist->mouseover_contact &&
3316 !((e->x > gtkblist->contact_rect.x) && (e->x < (gtkblist->contact_rect.x + gtkblist->contact_rect.width)) &&
3317 (e->y > gtkblist->contact_rect.y) && (e->y < (gtkblist->contact_rect.y + gtkblist->contact_rect.height)))) {
3318 pidgin_blist_collapse_contact_cb(NULL, gtkblist->mouseover_contact);
3319 gtkblist->mouseover_contact = NULL;
3321 return FALSE;
3324 static void
3325 toggle_debug(void)
3327 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/debug/enabled",
3328 !purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/debug/enabled"));
3331 static char *get_mood_icon_path(const char *mood)
3333 char *path;
3335 if (!strcmp(mood, "busy")) {
3336 path = g_build_filename(DATADIR, "pixmaps", "pidgin",
3337 "status", "16", "busy.png", NULL);
3338 } else if (!strcmp(mood, "hiptop")) {
3339 path = g_build_filename(DATADIR, "pixmaps", "pidgin",
3340 "emblems", "16", "hiptop.png", NULL);
3341 } else {
3342 char *filename = g_strdup_printf("%s.png", mood);
3343 path = g_build_filename(DATADIR, "pixmaps", "pidgin",
3344 "emotes", "small", filename, NULL);
3345 g_free(filename);
3347 return path;
3350 static void
3351 update_status_with_mood(PurpleAccount *account, const gchar *mood,
3352 const gchar *text)
3354 if (mood && *mood) {
3355 if (text) {
3356 purple_account_set_status(account, "mood", TRUE,
3357 PURPLE_MOOD_NAME, mood,
3358 PURPLE_MOOD_COMMENT, text,
3359 NULL);
3360 } else {
3361 purple_account_set_status(account, "mood", TRUE,
3362 PURPLE_MOOD_NAME, mood,
3363 NULL);
3365 } else {
3366 purple_account_set_status(account, "mood", FALSE, NULL);
3370 static void
3371 edit_mood_cb(PurpleConnection *gc, PurpleRequestFields *fields)
3373 PurpleRequestField *mood_field;
3374 GList *l;
3376 mood_field = purple_request_fields_get_field(fields, "mood");
3377 l = purple_request_field_list_get_selected(mood_field);
3379 if (l) {
3380 const char *mood = purple_request_field_list_get_data(mood_field, l->data);
3382 if (gc) {
3383 const char *text;
3384 PurpleAccount *account = purple_connection_get_account(gc);
3386 if (gc->flags & PURPLE_CONNECTION_SUPPORT_MOOD_MESSAGES) {
3387 PurpleRequestField *text_field;
3388 text_field = purple_request_fields_get_field(fields, "text");
3389 text = purple_request_field_string_get_value(text_field);
3390 } else {
3391 text = NULL;
3394 update_status_with_mood(account, mood, text);
3395 } else {
3396 GList *accounts = purple_accounts_get_all_active();
3398 for (; accounts ; accounts = g_list_delete_link(accounts, accounts)) {
3399 PurpleAccount *account = (PurpleAccount *) accounts->data;
3400 PurpleConnection *gc = purple_account_get_connection(account);
3402 if (gc && gc->flags & PURPLE_CONNECTION_SUPPORT_MOODS) {
3403 update_status_with_mood(account, mood, NULL);
3410 static void
3411 global_moods_for_each(gpointer key, gpointer value, gpointer user_data)
3413 GList **out_moods = (GList **) user_data;
3414 PurpleMood *mood = (PurpleMood *) value;
3416 *out_moods = g_list_append(*out_moods, mood);
3419 static PurpleMood *
3420 get_global_moods(void)
3422 GHashTable *global_moods =
3423 g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
3424 GHashTable *mood_counts =
3425 g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
3426 GList *accounts = purple_accounts_get_all_active();
3427 PurpleMood *result = NULL;
3428 GList *out_moods = NULL;
3429 int i = 0;
3430 int num_accounts = 0;
3432 for (; accounts ; accounts = g_list_delete_link(accounts, accounts)) {
3433 PurpleAccount *account = (PurpleAccount *) accounts->data;
3434 if (purple_account_is_connected(account)) {
3435 PurpleConnection *gc = purple_account_get_connection(account);
3437 if (gc->flags & PURPLE_CONNECTION_SUPPORT_MOODS) {
3438 PurplePluginProtocolInfo *prpl_info =
3439 PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
3440 PurpleMood *mood = NULL;
3442 /* PURPLE_CONNECTION_SUPPORT_MOODS would not be set if the prpl doesn't
3443 * have get_moods, so using PURPLE_PROTOCOL_PLUGIN_HAS_FUNC isn't necessary
3444 * here */
3445 for (mood = prpl_info->get_moods(account) ;
3446 mood->mood != NULL ; mood++) {
3447 int mood_count =
3448 GPOINTER_TO_INT(g_hash_table_lookup(mood_counts, mood->mood));
3450 if (!g_hash_table_lookup(global_moods, mood->mood)) {
3451 g_hash_table_insert(global_moods, (gpointer)mood->mood, mood);
3453 g_hash_table_insert(mood_counts, (gpointer)mood->mood,
3454 GINT_TO_POINTER(mood_count + 1));
3457 num_accounts++;
3462 g_hash_table_foreach(global_moods, global_moods_for_each, &out_moods);
3463 result = g_new0(PurpleMood, g_hash_table_size(global_moods) + 1);
3465 while (out_moods) {
3466 PurpleMood *mood = (PurpleMood *) out_moods->data;
3467 int in_num_accounts =
3468 GPOINTER_TO_INT(g_hash_table_lookup(mood_counts, mood->mood));
3470 if (in_num_accounts == num_accounts) {
3471 /* mood is present in all accounts supporting moods */
3472 result[i].mood = mood->mood;
3473 result[i].description = mood->description;
3474 i++;
3476 out_moods = g_list_delete_link(out_moods, out_moods);
3479 g_hash_table_destroy(global_moods);
3480 g_hash_table_destroy(mood_counts);
3482 return result;
3485 /* get current set mood for all mood-supporting accounts, or NULL if not set
3486 or not set to the same on all */
3487 static const gchar *
3488 get_global_mood_status(void)
3490 GList *accounts = purple_accounts_get_all_active();
3491 const gchar *found_mood = NULL;
3493 for (; accounts ; accounts = g_list_delete_link(accounts, accounts)) {
3494 PurpleAccount *account = (PurpleAccount *) accounts->data;
3496 if (purple_account_is_connected(account) &&
3497 (purple_account_get_connection(account)->flags &
3498 PURPLE_CONNECTION_SUPPORT_MOODS)) {
3499 PurplePresence *presence = purple_account_get_presence(account);
3500 PurpleStatus *status = purple_presence_get_status(presence, "mood");
3501 const gchar *curr_mood = purple_status_get_attr_string(status, PURPLE_MOOD_NAME);
3503 if (found_mood != NULL && !purple_strequal(curr_mood, found_mood)) {
3504 /* found a different mood */
3505 found_mood = NULL;
3506 break;
3507 } else {
3508 found_mood = curr_mood;
3513 return found_mood;
3516 static void
3517 set_mood_cb(GtkWidget *widget, PurpleAccount *account)
3519 const char *current_mood;
3520 PurpleRequestFields *fields;
3521 PurpleRequestFieldGroup *g;
3522 PurpleRequestField *f;
3523 PurpleConnection *gc = NULL;
3524 PurplePluginProtocolInfo *prpl_info = NULL;
3525 PurpleMood *mood;
3526 PurpleMood *global_moods = get_global_moods();
3528 if (account) {
3529 PurplePresence *presence = purple_account_get_presence(account);
3530 PurpleStatus *status = purple_presence_get_status(presence, "mood");
3531 gc = purple_account_get_connection(account);
3532 g_return_if_fail(gc->prpl != NULL);
3533 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
3534 current_mood = purple_status_get_attr_string(status, PURPLE_MOOD_NAME);
3535 } else {
3536 current_mood = get_global_mood_status();
3539 fields = purple_request_fields_new();
3540 g = purple_request_field_group_new(NULL);
3541 f = purple_request_field_list_new("mood", _("Please select your mood from the list"));
3543 purple_request_field_list_add(f, _("None"), "");
3544 if (current_mood == NULL)
3545 purple_request_field_list_add_selected(f, _("None"));
3547 /* TODO: rlaager wants this sorted. */
3548 /* TODO: darkrain wants it sorted post-translation */
3549 if (account && PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, get_moods))
3550 mood = prpl_info->get_moods(account);
3551 else
3552 mood = global_moods;
3553 for ( ; mood->mood != NULL ; mood++) {
3554 char *path;
3556 if (mood->mood == NULL || mood->description == NULL)
3557 continue;
3559 path = get_mood_icon_path(mood->mood);
3560 purple_request_field_list_add_icon(f, _(mood->description),
3561 path, (gpointer)mood->mood);
3562 g_free(path);
3564 if (current_mood && !strcmp(current_mood, mood->mood))
3565 purple_request_field_list_add_selected(f, _(mood->description));
3567 purple_request_field_group_add_field(g, f);
3569 purple_request_fields_add_group(fields, g);
3571 /* if the connection allows setting a mood message */
3572 if (gc && (gc->flags & PURPLE_CONNECTION_SUPPORT_MOOD_MESSAGES)) {
3573 g = purple_request_field_group_new(NULL);
3574 f = purple_request_field_string_new("text",
3575 _("Message (optional)"), NULL, FALSE);
3576 purple_request_field_group_add_field(g, f);
3577 purple_request_fields_add_group(fields, g);
3580 purple_request_fields(gc, _("Edit User Mood"), _("Edit User Mood"),
3581 NULL, fields,
3582 _("OK"), G_CALLBACK(edit_mood_cb),
3583 _("Cancel"), NULL,
3584 gc ? purple_connection_get_account(gc) : NULL,
3585 NULL, NULL, gc);
3587 g_free(global_moods);
3590 static void
3591 set_mood_show(void)
3593 set_mood_cb(NULL, NULL);
3596 /***************************************************
3597 * Crap *
3598 ***************************************************/
3599 static GtkItemFactoryEntry blist_menu[] =
3601 /* NOTE: Do not set any accelerator to Control+O. It is mapped by
3602 gtk_blist_key_press_cb to "Get User Info" on the selected buddy. */
3604 /* Buddies menu */
3605 { N_("/_Buddies"), NULL, NULL, 0, "<Branch>", NULL },
3606 { N_("/Buddies/New Instant _Message..."), "<CTL>M", pidgin_dialogs_im, 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW },
3607 { N_("/Buddies/Join a _Chat..."), "<CTL>C", pidgin_blist_joinchat_show, 0, "<StockItem>", PIDGIN_STOCK_CHAT },
3608 { N_("/Buddies/Get User _Info..."), "<CTL>I", pidgin_dialogs_info, 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_USER_INFO },
3609 { N_("/Buddies/View User _Log..."), "<CTL>L", pidgin_dialogs_log, 0, "<Item>", NULL },
3610 { "/Buddies/sep1", NULL, NULL, 0, "<Separator>", NULL },
3611 { N_("/Buddies/Sh_ow"), NULL, NULL, 0, "<Branch>", NULL},
3612 { N_("/Buddies/Show/_Offline Buddies"), NULL, pidgin_blist_edit_mode_cb, 1, "<CheckItem>", NULL },
3613 { N_("/Buddies/Show/_Empty Groups"), NULL, pidgin_blist_show_empty_groups_cb, 1, "<CheckItem>", NULL },
3614 { N_("/Buddies/Show/Buddy _Details"), NULL, pidgin_blist_buddy_details_cb, 1, "<CheckItem>", NULL },
3615 { N_("/Buddies/Show/Idle _Times"), NULL, pidgin_blist_show_idle_time_cb, 1, "<CheckItem>", NULL },
3616 { N_("/Buddies/Show/_Protocol Icons"), NULL, pidgin_blist_show_protocol_icons_cb, 1, "<CheckItem>", NULL },
3617 { N_("/Buddies/_Sort Buddies"), NULL, NULL, 0, "<Branch>", NULL },
3618 { "/Buddies/sep2", NULL, NULL, 0, "<Separator>", NULL },
3619 { N_("/Buddies/_Add Buddy..."), "<CTL>B", pidgin_blist_add_buddy_cb, 0, "<StockItem>", GTK_STOCK_ADD },
3620 { N_("/Buddies/Add C_hat..."), NULL, pidgin_blist_add_chat_cb, 0, "<StockItem>", GTK_STOCK_ADD },
3621 { N_("/Buddies/Add _Group..."), NULL, purple_blist_request_add_group, 0, "<StockItem>", GTK_STOCK_ADD },
3622 { "/Buddies/sep3", NULL, NULL, 0, "<Separator>", NULL },
3623 { N_("/Buddies/_Quit"), "<CTL>Q", purple_core_quit, 0, "<StockItem>", GTK_STOCK_QUIT },
3625 /* Accounts menu */
3626 { N_("/_Accounts"), NULL, NULL, 0, "<Branch>", NULL },
3627 { N_("/Accounts/Manage Accounts"), "<CTL>A", pidgin_accounts_window_show, 0, "<Item>", NULL },
3629 /* Tools */
3630 { N_("/_Tools"), NULL, NULL, 0, "<Branch>", NULL },
3631 { N_("/Tools/Buddy _Pounces"), NULL, pidgin_pounces_manager_show, 1, "<Item>", NULL },
3632 { N_("/Tools/_Certificates"), NULL, pidgin_certmgr_show, 0, "<Item>", NULL },
3633 { N_("/Tools/Custom Smile_ys"), "<CTL>Y", pidgin_smiley_manager_show, 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_SMILEY },
3634 { N_("/Tools/Plu_gins"), "<CTL>U", pidgin_plugin_dialog_show, 2, "<StockItem>", PIDGIN_STOCK_TOOLBAR_PLUGINS },
3635 { N_("/Tools/Pr_eferences"), "<CTL>P", pidgin_prefs_show, 0, "<StockItem>", GTK_STOCK_PREFERENCES },
3636 { N_("/Tools/Pr_ivacy"), NULL, pidgin_privacy_dialog_show, 0, "<Item>", NULL },
3637 { N_("/Tools/Set _Mood"), "<CTL>D", set_mood_show, 0, "<Item>", NULL },
3638 { "/Tools/sep2", NULL, NULL, 0, "<Separator>", NULL },
3639 { N_("/Tools/_File Transfers"), "<CTL>T", pidgin_xfer_dialog_show, 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_TRANSFER },
3640 { N_("/Tools/R_oom List"), NULL, pidgin_roomlist_dialog_show, 0, "<Item>", NULL },
3641 { N_("/Tools/System _Log"), NULL, gtk_blist_show_systemlog_cb, 3, "<Item>", NULL },
3642 { "/Tools/sep3", NULL, NULL, 0, "<Separator>", NULL },
3643 { N_("/Tools/Mute _Sounds"), NULL, pidgin_blist_mute_sounds_cb, 0, "<CheckItem>", NULL },
3644 /* Help */
3645 { N_("/_Help"), NULL, NULL, 0, "<Branch>", NULL },
3646 { N_("/Help/Online _Help"), "F1", gtk_blist_show_onlinehelp_cb, 0, "<StockItem>", GTK_STOCK_HELP },
3647 { "/Help/sep1", NULL, NULL, 0, "<Separator>", NULL },
3648 { N_("/Help/_Build Information"), NULL, pidgin_dialogs_buildinfo, 0, "<Item>", NULL },
3649 { N_("/Help/_Debug Window"), NULL, toggle_debug, 0, "<Item>", NULL },
3650 { N_("/Help/De_veloper Information"), NULL, pidgin_dialogs_developers, 0, "<Item>", NULL },
3651 { N_("/Help/_Translator Information"), NULL, pidgin_dialogs_translators, 0, "<Item>", NULL },
3652 { "/Help/sep2", NULL, NULL, 0, "<Separator>", NULL },
3653 { N_("/Help/_About"), NULL, pidgin_dialogs_about, 4, "<StockItem>", GTK_STOCK_ABOUT },
3656 /*********************************************************
3657 * Private Utility functions *
3658 *********************************************************/
3660 static char *pidgin_get_tooltip_text(PurpleBlistNode *node, gboolean full)
3662 GString *str = g_string_new("");
3663 PurplePlugin *prpl;
3664 PurplePluginProtocolInfo *prpl_info = NULL;
3665 char *tmp;
3667 if (PURPLE_BLIST_NODE_IS_CHAT(node))
3669 PurpleChat *chat;
3670 GList *connections;
3671 GList *cur;
3672 struct proto_chat_entry *pce;
3673 char *name, *value;
3674 PurpleConversation *conv;
3675 PidginBlistNode *bnode = node->ui_data;
3677 chat = (PurpleChat *)node;
3678 prpl = purple_find_prpl(purple_account_get_protocol_id(chat->account));
3679 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
3681 connections = purple_connections_get_all();
3682 if (connections && connections->next)
3684 tmp = g_markup_escape_text(chat->account->username, -1);
3685 g_string_append_printf(str, _("<b>Account:</b> %s"), tmp);
3686 g_free(tmp);
3689 if (bnode && bnode->conv.conv) {
3690 conv = bnode->conv.conv;
3691 } else {
3692 char *chat_name;
3693 if (prpl_info && prpl_info->get_chat_name)
3694 chat_name = prpl_info->get_chat_name(chat->components);
3695 else
3696 chat_name = g_strdup(purple_chat_get_name(chat));
3698 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, chat_name,
3699 chat->account);
3700 g_free(chat_name);
3703 if (conv && !purple_conv_chat_has_left(PURPLE_CONV_CHAT(conv))) {
3704 g_string_append_printf(str, _("\n<b>Occupants:</b> %d"),
3705 g_list_length(purple_conv_chat_get_users(PURPLE_CONV_CHAT(conv))));
3707 if (prpl_info && (prpl_info->options & OPT_PROTO_CHAT_TOPIC)) {
3708 const char *chattopic = purple_conv_chat_get_topic(PURPLE_CONV_CHAT(conv));
3709 char *topic = chattopic ? g_markup_escape_text(chattopic, -1) : NULL;
3710 g_string_append_printf(str, _("\n<b>Topic:</b> %s"), topic ? topic : _("(no topic set)"));
3711 g_free(topic);
3715 if (prpl_info && prpl_info->chat_info != NULL)
3716 cur = prpl_info->chat_info(chat->account->gc);
3717 else
3718 cur = NULL;
3720 while (cur != NULL)
3722 pce = cur->data;
3724 if (!pce->secret && (!pce->required &&
3725 g_hash_table_lookup(chat->components, pce->identifier) == NULL))
3727 tmp = purple_text_strip_mnemonic(pce->label);
3728 name = g_markup_escape_text(tmp, -1);
3729 g_free(tmp);
3730 value = g_markup_escape_text(g_hash_table_lookup(
3731 chat->components, pce->identifier), -1);
3732 g_string_append_printf(str, "\n<b>%s</b> %s",
3733 name ? name : "",
3734 value ? value : "");
3735 g_free(name);
3736 g_free(value);
3739 g_free(pce);
3740 cur = g_list_delete_link(cur, cur);
3743 else if (PURPLE_BLIST_NODE_IS_CONTACT(node) || PURPLE_BLIST_NODE_IS_BUDDY(node))
3745 /* NOTE: THIS FUNCTION IS NO LONGER CALLED FOR CONTACTS.
3746 * It is only called by create_tip_for_node(), and create_tip_for_node() is never called for a contact.
3748 PurpleContact *c;
3749 PurpleBuddy *b;
3750 PurplePresence *presence;
3751 PurpleNotifyUserInfo *user_info;
3752 GList *connections;
3753 char *tmp;
3754 time_t idle_secs, signon;
3756 if (PURPLE_BLIST_NODE_IS_CONTACT(node))
3758 c = (PurpleContact *)node;
3759 b = purple_contact_get_priority_buddy(c);
3761 else
3763 b = (PurpleBuddy *)node;
3764 c = purple_buddy_get_contact(b);
3767 prpl = purple_find_prpl(purple_account_get_protocol_id(b->account));
3768 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
3770 presence = purple_buddy_get_presence(b);
3771 user_info = purple_notify_user_info_new();
3773 /* Account */
3774 connections = purple_connections_get_all();
3775 if (full && connections && connections->next)
3777 tmp = g_markup_escape_text(purple_account_get_username(
3778 purple_buddy_get_account(b)), -1);
3779 purple_notify_user_info_add_pair(user_info, _("Account"), tmp);
3780 g_free(tmp);
3783 /* Alias */
3784 /* If there's not a contact alias, the node is being displayed with
3785 * this alias, so there's no point in showing it in the tooltip. */
3786 if (full && c && b->alias != NULL && b->alias[0] != '\0' &&
3787 (c->alias != NULL && c->alias[0] != '\0') &&
3788 strcmp(c->alias, b->alias) != 0)
3790 tmp = g_markup_escape_text(b->alias, -1);
3791 purple_notify_user_info_add_pair(user_info, _("Buddy Alias"), tmp);
3792 g_free(tmp);
3795 /* Nickname/Server Alias */
3796 /* I'd like to only show this if there's a contact or buddy
3797 * alias, but many people on MSN set long nicknames, which
3798 * get ellipsized, so the only way to see the whole thing is
3799 * to look at the tooltip. */
3800 if (full && b->server_alias != NULL && b->server_alias[0] != '\0')
3802 tmp = g_markup_escape_text(b->server_alias, -1);
3803 purple_notify_user_info_add_pair(user_info, _("Nickname"), tmp);
3804 g_free(tmp);
3807 /* Logged In */
3808 signon = purple_presence_get_login_time(presence);
3809 if (full && PURPLE_BUDDY_IS_ONLINE(b) && signon > 0)
3811 if (signon > time(NULL)) {
3813 * They signed on in the future?! Our local clock
3814 * must be wrong, show the actual date instead of
3815 * "4 days", etc.
3817 tmp = g_strdup(purple_date_format_long(localtime(&signon)));
3818 } else
3819 tmp = purple_str_seconds_to_string(time(NULL) - signon);
3820 purple_notify_user_info_add_pair(user_info, _("Logged In"), tmp);
3821 g_free(tmp);
3824 /* Idle */
3825 if (purple_presence_is_idle(presence))
3827 idle_secs = purple_presence_get_idle_time(presence);
3828 if (idle_secs > 0)
3830 tmp = purple_str_seconds_to_string(time(NULL) - idle_secs);
3831 purple_notify_user_info_add_pair(user_info, _("Idle"), tmp);
3832 g_free(tmp);
3836 /* Last Seen */
3837 if (full && c && !PURPLE_BUDDY_IS_ONLINE(b))
3839 struct _pidgin_blist_node *gtknode = ((PurpleBlistNode *)c)->ui_data;
3840 PurpleBlistNode *bnode;
3841 int lastseen = 0;
3843 if (gtknode && (!gtknode->contact_expanded || PURPLE_BLIST_NODE_IS_CONTACT(node)))
3845 /* We're either looking at a buddy for a collapsed contact or
3846 * an expanded contact itself so we show the most recent
3847 * (largest) last_seen time for any of the buddies under
3848 * the contact. */
3849 for (bnode = ((PurpleBlistNode *)c)->child ; bnode != NULL ; bnode = bnode->next)
3851 int value = purple_blist_node_get_int(bnode, "last_seen");
3852 if (value > lastseen)
3853 lastseen = value;
3856 else
3858 /* We're dealing with a buddy under an expanded contact,
3859 * so we show the last_seen time for the buddy. */
3860 lastseen = purple_blist_node_get_int(&b->node, "last_seen");
3863 if (lastseen > 0)
3865 tmp = purple_str_seconds_to_string(time(NULL) - lastseen);
3866 purple_notify_user_info_add_pair(user_info, _("Last Seen"), tmp);
3867 g_free(tmp);
3872 /* Offline? */
3873 /* FIXME: Why is this status special-cased by the core? --rlaager
3874 * FIXME: Alternatively, why not have the core do all of them? --rlaager */
3875 if (!PURPLE_BUDDY_IS_ONLINE(b)) {
3876 purple_notify_user_info_add_pair(user_info, _("Status"), _("Offline"));
3879 if (purple_account_is_connected(b->account) &&
3880 prpl_info && prpl_info->tooltip_text)
3882 /* Additional text from the PRPL */
3883 prpl_info->tooltip_text(b, user_info, full);
3886 /* These are Easter Eggs. Patches to remove them will be rejected. */
3887 if (!g_ascii_strcasecmp(b->name, "robflynn"))
3888 purple_notify_user_info_add_pair(user_info, _("Description"), _("Spooky"));
3889 if (!g_ascii_strcasecmp(b->name, "seanegn"))
3890 purple_notify_user_info_add_pair(user_info, _("Status"), _("Awesome"));
3891 if (!g_ascii_strcasecmp(b->name, "chipx86"))
3892 purple_notify_user_info_add_pair(user_info, _("Status"), _("Rockin'"));
3894 tmp = purple_notify_user_info_get_text_with_newline(user_info, "\n");
3895 g_string_append(str, tmp);
3896 g_free(tmp);
3898 purple_notify_user_info_destroy(user_info);
3899 } else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
3900 gint count;
3901 PurpleGroup *group = (PurpleGroup*)node;
3902 PurpleNotifyUserInfo *user_info;
3904 user_info = purple_notify_user_info_new();
3906 count = purple_blist_get_group_online_count(group);
3908 if (count != 0) {
3909 /* Online buddies in group */
3910 tmp = g_strdup_printf("%d", count);
3911 purple_notify_user_info_add_pair(user_info,
3912 _("Online Buddies"),
3913 tmp);
3914 g_free(tmp);
3917 count = purple_blist_get_group_size(group, FALSE);
3918 if (count != 0) {
3919 /* Total buddies (from online accounts) in group */
3920 tmp = g_strdup_printf("%d", count);
3921 purple_notify_user_info_add_pair(user_info,
3922 _("Total Buddies"),
3923 tmp);
3924 g_free(tmp);
3927 tmp = purple_notify_user_info_get_text_with_newline(user_info, "\n");
3928 g_string_append(str, tmp);
3929 g_free(tmp);
3931 purple_notify_user_info_destroy(user_info);
3934 purple_signal_emit(pidgin_blist_get_handle(), "drawing-tooltip",
3935 node, str, full);
3937 return g_string_free(str, FALSE);
3940 static GHashTable *cached_emblems;
3942 static void _cleanup_cached_emblem(gpointer data, GObject *obj) {
3943 g_hash_table_remove(cached_emblems, data);
3946 static GdkPixbuf * _pidgin_blist_get_cached_emblem(gchar *path) {
3947 GdkPixbuf *pb = g_hash_table_lookup(cached_emblems, path);
3949 if (pb != NULL) {
3950 /* The caller gets a reference */
3951 g_object_ref(pb);
3952 g_free(path);
3953 } else {
3954 pb = gdk_pixbuf_new_from_file(path, NULL);
3955 if (pb != NULL) {
3956 /* We don't want to own a ref to the pixbuf, but we need to keep clean up. */
3957 /* I'm not sure if it would be better to just keep our ref and not let the emblem ever be destroyed */
3958 g_object_weak_ref(G_OBJECT(pb), _cleanup_cached_emblem, path);
3959 g_hash_table_insert(cached_emblems, path, pb);
3960 } else
3961 g_free(path);
3964 return pb;
3967 GdkPixbuf *
3968 pidgin_blist_get_emblem(PurpleBlistNode *node)
3970 PurpleBuddy *buddy = NULL;
3971 struct _pidgin_blist_node *gtknode = node->ui_data;
3972 PurplePlugin *prpl;
3973 PurplePluginProtocolInfo *prpl_info;
3974 const char *name = NULL;
3975 char *filename, *path;
3976 PurplePresence *p = NULL;
3977 PurpleStatus *tune;
3979 if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
3980 if(!gtknode->contact_expanded) {
3981 buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
3983 } else if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
3984 buddy = (PurpleBuddy*)node;
3985 p = purple_buddy_get_presence(buddy);
3986 if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_MOBILE)) {
3987 /* This emblem comes from the small emoticon set now,
3988 * to reduce duplication. */
3989 path = g_build_filename(DATADIR, "pixmaps", "pidgin", "emotes",
3990 "small", "mobile.png", NULL);
3991 return _pidgin_blist_get_cached_emblem(path);
3994 if (((struct _pidgin_blist_node*)(node->parent->ui_data))->contact_expanded) {
3995 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons"))
3996 return NULL;
3997 return pidgin_create_prpl_icon(((PurpleBuddy*)node)->account, PIDGIN_PRPL_ICON_SMALL);
3999 } else {
4000 return NULL;
4003 g_return_val_if_fail(buddy != NULL, NULL);
4005 if (!purple_privacy_check(buddy->account, purple_buddy_get_name(buddy))) {
4006 path = g_build_filename(DATADIR, "pixmaps", "pidgin", "emblems", "16", "blocked.png", NULL);
4007 return _pidgin_blist_get_cached_emblem(path);
4010 /* If we came through the contact code flow above, we didn't need
4011 * to get the presence until now. */
4012 if (p == NULL)
4013 p = purple_buddy_get_presence(buddy);
4015 if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_MOBILE)) {
4016 /* This emblem comes from the small emoticon set now, to reduce duplication. */
4017 path = g_build_filename(DATADIR, "pixmaps", "pidgin", "emotes", "small", "mobile.png", NULL);
4018 return _pidgin_blist_get_cached_emblem(path);
4021 tune = purple_presence_get_status(p, "tune");
4022 if (tune && purple_status_is_active(tune)) {
4023 /* Only in MSN.
4024 * TODO: Replace "Tune" with generalized "Media" in 3.0. */
4025 if (purple_status_get_attr_string(tune, "game") != NULL) {
4026 path = g_build_filename(DATADIR, "pixmaps", "pidgin", "emblems", "16", "game.png", NULL);
4027 return _pidgin_blist_get_cached_emblem(path);
4029 /* Only in MSN.
4030 * TODO: Replace "Tune" with generalized "Media" in 3.0. */
4031 if (purple_status_get_attr_string(tune, "office") != NULL) {
4032 path = g_build_filename(DATADIR, "pixmaps", "pidgin", "emblems", "16", "office.png", NULL);
4033 return _pidgin_blist_get_cached_emblem(path);
4035 /* Regular old "tune" is the only one in all protocols. */
4036 /* This emblem comes from the small emoticon set now, to reduce duplication. */
4037 path = g_build_filename(DATADIR, "pixmaps", "pidgin", "emotes", "small", "music.png", NULL);
4038 return _pidgin_blist_get_cached_emblem(path);
4041 prpl = purple_find_prpl(purple_account_get_protocol_id(buddy->account));
4042 if (!prpl)
4043 return NULL;
4045 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
4046 if (prpl_info && prpl_info->list_emblem)
4047 name = prpl_info->list_emblem(buddy);
4049 if (name == NULL) {
4050 PurpleStatus *status;
4052 if (!purple_presence_is_status_primitive_active(p, PURPLE_STATUS_MOOD))
4053 return NULL;
4055 status = purple_presence_get_status(p, "mood");
4056 name = purple_status_get_attr_string(status, PURPLE_MOOD_NAME);
4058 if (!(name && *name))
4059 return NULL;
4061 path = get_mood_icon_path(name);
4062 } else {
4063 filename = g_strdup_printf("%s.png", name);
4064 path = g_build_filename(DATADIR, "pixmaps", "pidgin", "emblems", "16", filename, NULL);
4065 g_free(filename);
4068 /* _pidgin_blist_get_cached_emblem() assumes ownership of path */
4069 return _pidgin_blist_get_cached_emblem(path);
4073 GdkPixbuf *
4074 pidgin_blist_get_status_icon(PurpleBlistNode *node, PidginStatusIconSize size)
4076 GdkPixbuf *ret;
4077 const char *icon = NULL;
4078 struct _pidgin_blist_node *gtknode = node->ui_data;
4079 struct _pidgin_blist_node *gtkbuddynode = NULL;
4080 PurpleBuddy *buddy = NULL;
4081 PurpleChat *chat = NULL;
4082 GtkIconSize icon_size = gtk_icon_size_from_name((size == PIDGIN_STATUS_ICON_LARGE) ? PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL :
4083 PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC);
4085 if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
4086 if(!gtknode->contact_expanded) {
4087 buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
4088 if (buddy != NULL)
4089 gtkbuddynode = ((PurpleBlistNode*)buddy)->ui_data;
4091 } else if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
4092 buddy = (PurpleBuddy*)node;
4093 gtkbuddynode = node->ui_data;
4094 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
4095 chat = (PurpleChat*)node;
4096 } else {
4097 return NULL;
4100 if(buddy || chat) {
4101 PurpleAccount *account;
4102 PurplePlugin *prpl;
4104 if(buddy)
4105 account = buddy->account;
4106 else
4107 account = chat->account;
4109 prpl = purple_find_prpl(purple_account_get_protocol_id(account));
4110 if(!prpl)
4111 return NULL;
4114 if(buddy) {
4115 PurpleConversation *conv = find_conversation_with_buddy(buddy);
4116 PurplePresence *p;
4117 gboolean trans;
4119 if(conv != NULL) {
4120 PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
4121 if (gtkconv == NULL && size == PIDGIN_STATUS_ICON_SMALL) {
4122 PidginBlistNode *ui = buddy->node.ui_data;
4123 if (ui == NULL || (ui->conv.flags & PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE))
4124 return gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview),
4125 PIDGIN_STOCK_STATUS_MESSAGE, icon_size, "GtkTreeView");
4129 p = purple_buddy_get_presence(buddy);
4130 trans = purple_presence_is_idle(p);
4132 if (PURPLE_BUDDY_IS_ONLINE(buddy) && gtkbuddynode && gtkbuddynode->recent_signonoff)
4133 icon = PIDGIN_STOCK_STATUS_LOGIN;
4134 else if (gtkbuddynode && gtkbuddynode->recent_signonoff)
4135 icon = PIDGIN_STOCK_STATUS_LOGOUT;
4136 else if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_UNAVAILABLE))
4137 if (trans)
4138 icon = PIDGIN_STOCK_STATUS_BUSY_I;
4139 else
4140 icon = PIDGIN_STOCK_STATUS_BUSY;
4141 else if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_AWAY))
4142 if (trans)
4143 icon = PIDGIN_STOCK_STATUS_AWAY_I;
4144 else
4145 icon = PIDGIN_STOCK_STATUS_AWAY;
4146 else if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_EXTENDED_AWAY))
4147 if (trans)
4148 icon = PIDGIN_STOCK_STATUS_XA_I;
4149 else
4150 icon = PIDGIN_STOCK_STATUS_XA;
4151 else if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_OFFLINE))
4152 icon = PIDGIN_STOCK_STATUS_OFFLINE;
4153 else if (trans)
4154 icon = PIDGIN_STOCK_STATUS_AVAILABLE_I;
4155 else if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_INVISIBLE))
4156 icon = PIDGIN_STOCK_STATUS_INVISIBLE;
4157 else
4158 icon = PIDGIN_STOCK_STATUS_AVAILABLE;
4159 } else if (chat) {
4160 icon = PIDGIN_STOCK_STATUS_CHAT;
4161 } else {
4162 icon = PIDGIN_STOCK_STATUS_PERSON;
4165 ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), icon,
4166 icon_size, "GtkTreeView");
4167 return ret;
4170 static const char *
4171 theme_font_get_color_default(PidginThemeFont *font, const char *def)
4173 const char *ret;
4174 if (!font || !(ret = pidgin_theme_font_get_color_describe(font)))
4175 ret = def;
4176 return ret;
4179 static const char *
4180 theme_font_get_face_default(PidginThemeFont *font, const char *def)
4182 const char *ret;
4183 if (!font || !(ret = pidgin_theme_font_get_font_face(font)))
4184 ret = def;
4185 return ret;
4188 gchar *
4189 pidgin_blist_get_name_markup(PurpleBuddy *b, gboolean selected, gboolean aliased)
4191 const char *name, *name_color, *name_font, *status_color, *status_font;
4192 char *text = NULL;
4193 PurplePlugin *prpl;
4194 PurplePluginProtocolInfo *prpl_info = NULL;
4195 PurpleContact *contact;
4196 PurplePresence *presence;
4197 struct _pidgin_blist_node *gtkcontactnode = NULL;
4198 char *idletime = NULL, *statustext = NULL, *nametext = NULL;
4199 PurpleConversation *conv = find_conversation_with_buddy(b);
4200 gboolean hidden_conv = FALSE;
4201 gboolean biglist = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons");
4202 PidginThemeFont *statusfont = NULL, *namefont = NULL;
4203 PidginBlistTheme *theme;
4205 if (conv != NULL) {
4206 PidginBlistNode *ui = b->node.ui_data;
4207 if (ui) {
4208 if (ui->conv.flags & PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE)
4209 hidden_conv = TRUE;
4210 } else {
4211 if (PIDGIN_CONVERSATION(conv) == NULL)
4212 hidden_conv = TRUE;
4216 /* XXX Good luck cleaning up this crap */
4217 contact = PURPLE_CONTACT(PURPLE_BLIST_NODE(b)->parent);
4218 if(contact)
4219 gtkcontactnode = purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(contact));
4221 /* Name */
4222 if (gtkcontactnode && !gtkcontactnode->contact_expanded && contact->alias)
4223 name = contact->alias;
4224 else
4225 name = purple_buddy_get_alias(b);
4227 /* Raise a contact pre-draw signal here. THe callback will return an
4228 * escaped version of the name. */
4229 nametext = purple_signal_emit_return_1(pidgin_blist_get_handle(), "drawing-buddy", b);
4231 if(!nametext)
4232 nametext = g_markup_escape_text(name, strlen(name));
4234 presence = purple_buddy_get_presence(b);
4236 /* Name is all that is needed */
4237 if (!aliased || biglist) {
4239 /* Status Info */
4240 prpl = purple_find_prpl(purple_account_get_protocol_id(b->account));
4242 if (prpl != NULL)
4243 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
4245 if (prpl_info && prpl_info->status_text && b->account->gc) {
4246 char *tmp = prpl_info->status_text(b);
4247 const char *end;
4249 if(tmp && !g_utf8_validate(tmp, -1, &end)) {
4250 char *new = g_strndup(tmp,
4251 g_utf8_pointer_to_offset(tmp, end));
4252 g_free(tmp);
4253 tmp = new;
4255 if(tmp) {
4256 g_strdelimit(tmp, "\n", ' ');
4257 purple_str_strip_char(tmp, '\r');
4259 statustext = tmp;
4262 if(!purple_presence_is_online(presence) && !statustext)
4263 statustext = g_strdup(_("Offline"));
4265 /* Idle Text */
4266 if (purple_presence_is_idle(presence) && purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_idle_time")) {
4267 time_t idle_secs = purple_presence_get_idle_time(presence);
4269 if (idle_secs > 0) {
4270 int iday, ihrs, imin;
4271 time_t t;
4273 time(&t);
4274 iday = (t - idle_secs) / (24 * 60 * 60);
4275 ihrs = ((t - idle_secs) / 60 / 60) % 24;
4276 imin = ((t - idle_secs) / 60) % 60;
4278 if (iday)
4279 idletime = g_strdup_printf(_("Idle %dd %dh %02dm"), iday, ihrs, imin);
4280 else if (ihrs)
4281 idletime = g_strdup_printf(_("Idle %dh %02dm"), ihrs, imin);
4282 else
4283 idletime = g_strdup_printf(_("Idle %dm"), imin);
4285 } else
4286 idletime = g_strdup(_("Idle"));
4290 /* choose the colors of the text */
4291 theme = pidgin_blist_get_theme();
4292 name_color = NULL;
4294 if (theme) {
4295 if (purple_presence_is_idle(presence)) {
4296 namefont = statusfont = pidgin_blist_theme_get_idle_text_info(theme);
4297 name_color = "dim grey";
4298 } else if (!purple_presence_is_online(presence)) {
4299 namefont = pidgin_blist_theme_get_offline_text_info(theme);
4300 name_color = "dim grey";
4301 statusfont = pidgin_blist_theme_get_status_text_info(theme);
4302 } else if (purple_presence_is_available(presence)) {
4303 namefont = pidgin_blist_theme_get_online_text_info(theme);
4304 statusfont = pidgin_blist_theme_get_status_text_info(theme);
4305 } else {
4306 namefont = pidgin_blist_theme_get_away_text_info(theme);
4307 statusfont = pidgin_blist_theme_get_status_text_info(theme);
4309 } else {
4310 if (!selected
4311 && (purple_presence_is_idle(presence)
4312 || !purple_presence_is_online(presence)))
4314 name_color = "dim grey";
4318 name_color = theme_font_get_color_default(namefont, name_color);
4319 name_font = theme_font_get_face_default(namefont, "");
4321 status_color = theme_font_get_color_default(statusfont, "dim grey");
4322 status_font = theme_font_get_face_default(statusfont, "");
4324 if (aliased && selected) {
4325 if (theme) {
4326 name_color = "black";
4327 status_color = "black";
4328 } else {
4329 name_color = NULL;
4330 status_color = NULL;
4334 if (hidden_conv) {
4335 char *tmp = nametext;
4336 nametext = g_strdup_printf("<b>%s</b>", tmp);
4337 g_free(tmp);
4340 /* Put it all together */
4341 if ((!aliased || biglist) && (statustext || idletime)) {
4342 /* using <span size='smaller'> breaks the status, so it must be seperated into <small><span>*/
4343 if (name_color) {
4344 text = g_strdup_printf("<span font_desc='%s' foreground='%s'>%s</span>\n"
4345 "<small><span font_desc='%s' foreground='%s'>%s%s%s</span></small>",
4346 name_font, name_color, nametext, status_font, status_color,
4347 idletime != NULL ? idletime : "",
4348 (idletime != NULL && statustext != NULL) ? " - " : "",
4349 statustext != NULL ? statustext : "");
4350 } else if (status_color) {
4351 text = g_strdup_printf("<span font_desc='%s'>%s</span>\n"
4352 "<small><span font_desc='%s' foreground='%s'>%s%s%s</span></small>",
4353 name_font, nametext, status_font, status_color,
4354 idletime != NULL ? idletime : "",
4355 (idletime != NULL && statustext != NULL) ? " - " : "",
4356 statustext != NULL ? statustext : "");
4357 } else {
4358 text = g_strdup_printf("<span font_desc='%s'>%s</span>\n"
4359 "<small><span font_desc='%s'>%s%s%s</span></small>",
4360 name_font, nametext, status_font,
4361 idletime != NULL ? idletime : "",
4362 (idletime != NULL && statustext != NULL) ? " - " : "",
4363 statustext != NULL ? statustext : "");
4365 } else {
4366 if (name_color) {
4367 text = g_strdup_printf("<span font_desc='%s' color='%s'>%s</span>",
4368 name_font, name_color, nametext);
4369 } else {
4370 text = g_strdup_printf("<span font_desc='%s'>%s</span>", name_font,
4371 nametext);
4374 g_free(nametext);
4375 g_free(statustext);
4376 g_free(idletime);
4378 if (hidden_conv) {
4379 char *tmp = text;
4380 text = g_strdup_printf("<b>%s</b>", tmp);
4381 g_free(tmp);
4384 return text;
4387 static void pidgin_blist_restore_position(void)
4389 int blist_x, blist_y, blist_width, blist_height;
4391 blist_width = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/width");
4393 /* if the window exists, is hidden, we're saving positions, and the
4394 * position is sane... */
4395 if (gtkblist && gtkblist->window &&
4396 !GTK_WIDGET_VISIBLE(gtkblist->window) && blist_width != 0) {
4398 blist_x = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/x");
4399 blist_y = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/y");
4400 blist_height = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/height");
4402 /* ...check position is on screen... */
4403 if (blist_x >= gdk_screen_width())
4404 blist_x = gdk_screen_width() - 100;
4405 else if (blist_x + blist_width < 0)
4406 blist_x = 100;
4408 if (blist_y >= gdk_screen_height())
4409 blist_y = gdk_screen_height() - 100;
4410 else if (blist_y + blist_height < 0)
4411 blist_y = 100;
4413 /* ...and move it back. */
4414 gtk_window_move(GTK_WINDOW(gtkblist->window), blist_x, blist_y);
4415 gtk_window_resize(GTK_WINDOW(gtkblist->window), blist_width, blist_height);
4416 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/list_maximized"))
4417 gtk_window_maximize(GTK_WINDOW(gtkblist->window));
4421 static gboolean pidgin_blist_refresh_timer(PurpleBuddyList *list)
4423 PurpleBlistNode *gnode, *cnode;
4425 if (gtk_blist_visibility == GDK_VISIBILITY_FULLY_OBSCURED
4426 || !GTK_WIDGET_VISIBLE(gtkblist->window))
4427 return TRUE;
4429 for(gnode = list->root; gnode; gnode = gnode->next) {
4430 if(!PURPLE_BLIST_NODE_IS_GROUP(gnode))
4431 continue;
4432 for(cnode = gnode->child; cnode; cnode = cnode->next) {
4433 if(PURPLE_BLIST_NODE_IS_CONTACT(cnode)) {
4434 PurpleBuddy *buddy;
4436 buddy = purple_contact_get_priority_buddy((PurpleContact*)cnode);
4438 if (buddy &&
4439 purple_presence_is_idle(purple_buddy_get_presence(buddy)))
4440 pidgin_blist_update_contact(list, (PurpleBlistNode*)buddy);
4445 /* keep on going */
4446 return TRUE;
4449 static void pidgin_blist_hide_node(PurpleBuddyList *list, PurpleBlistNode *node, gboolean update)
4451 struct _pidgin_blist_node *gtknode = (struct _pidgin_blist_node *)node->ui_data;
4452 GtkTreeIter iter;
4454 if (!gtknode || !gtknode->row || !gtkblist)
4455 return;
4457 if(gtkblist->selected_node == node)
4458 gtkblist->selected_node = NULL;
4459 if (get_iter_from_node(node, &iter)) {
4460 gtk_tree_store_remove(gtkblist->treemodel, &iter);
4461 if(update && (PURPLE_BLIST_NODE_IS_CONTACT(node) ||
4462 PURPLE_BLIST_NODE_IS_BUDDY(node) || PURPLE_BLIST_NODE_IS_CHAT(node))) {
4463 pidgin_blist_update(list, node->parent);
4466 gtk_tree_row_reference_free(gtknode->row);
4467 gtknode->row = NULL;
4470 static const char *require_connection[] =
4472 N_("/Buddies/New Instant Message..."),
4473 N_("/Buddies/Join a Chat..."),
4474 N_("/Buddies/Get User Info..."),
4475 N_("/Buddies/Add Buddy..."),
4476 N_("/Buddies/Add Chat..."),
4477 N_("/Buddies/Add Group..."),
4480 static const int require_connection_size = sizeof(require_connection)
4481 / sizeof(*require_connection);
4484 * Rebuild dynamic menus and make menu items sensitive/insensitive
4485 * where appropriate.
4487 static void
4488 update_menu_bar(PidginBuddyList *gtkblist)
4490 GtkWidget *widget;
4491 gboolean sensitive;
4492 int i;
4494 g_return_if_fail(gtkblist != NULL);
4496 pidgin_blist_update_accounts_menu();
4498 sensitive = (purple_connections_get_all() != NULL);
4500 for (i = 0; i < require_connection_size; i++)
4502 widget = gtk_item_factory_get_widget(gtkblist->ift, require_connection[i]);
4503 gtk_widget_set_sensitive(widget, sensitive);
4506 widget = gtk_item_factory_get_widget(gtkblist->ift, N_("/Buddies/Join a Chat..."));
4507 gtk_widget_set_sensitive(widget, pidgin_blist_joinchat_is_showable());
4509 widget = gtk_item_factory_get_widget(gtkblist->ift, N_("/Buddies/Add Chat..."));
4510 gtk_widget_set_sensitive(widget, pidgin_blist_joinchat_is_showable());
4512 widget = gtk_item_factory_get_widget(gtkblist->ift, N_("/Tools/Privacy"));
4513 gtk_widget_set_sensitive(widget, sensitive);
4515 widget = gtk_item_factory_get_widget(gtkblist->ift, N_("/Tools/Room List"));
4516 gtk_widget_set_sensitive(widget, pidgin_roomlist_is_showable());
4519 static void
4520 sign_on_off_cb(PurpleConnection *gc, PurpleBuddyList *blist)
4522 PidginBuddyList *gtkblist = PIDGIN_BLIST(blist);
4524 update_menu_bar(gtkblist);
4527 static void
4528 plugin_changed_cb(PurplePlugin *p, gpointer data)
4530 pidgin_blist_update_plugin_actions();
4533 static void
4534 unseen_conv_menu(void)
4536 static GtkWidget *menu = NULL;
4537 GList *convs = NULL;
4538 GList *chats, *ims;
4540 if (menu) {
4541 gtk_widget_destroy(menu);
4542 menu = NULL;
4545 ims = pidgin_conversations_find_unseen_list(PURPLE_CONV_TYPE_IM,
4546 PIDGIN_UNSEEN_TEXT, FALSE, 0);
4548 chats = pidgin_conversations_find_unseen_list(PURPLE_CONV_TYPE_CHAT,
4549 PIDGIN_UNSEEN_NICK, FALSE, 0);
4551 if(ims && chats)
4552 convs = g_list_concat(ims, chats);
4553 else if(ims && !chats)
4554 convs = ims;
4555 else if(!ims && chats)
4556 convs = chats;
4558 if (!convs)
4559 /* no conversations added, don't show the menu */
4560 return;
4562 menu = gtk_menu_new();
4564 pidgin_conversations_fill_menu(menu, convs);
4565 g_list_free(convs);
4566 gtk_widget_show_all(menu);
4567 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 3,
4568 gtk_get_current_event_time());
4571 static gboolean
4572 menutray_press_cb(GtkWidget *widget, GdkEventButton *event)
4574 GList *convs;
4576 switch (event->button) {
4577 case 1:
4578 convs = pidgin_conversations_find_unseen_list(PURPLE_CONV_TYPE_IM,
4579 PIDGIN_UNSEEN_TEXT, FALSE, 1);
4581 if(!convs)
4582 convs = pidgin_conversations_find_unseen_list(PURPLE_CONV_TYPE_CHAT,
4583 PIDGIN_UNSEEN_NICK, FALSE, 1);
4584 if (convs) {
4585 pidgin_conv_present_conversation((PurpleConversation*)convs->data);
4586 g_list_free(convs);
4588 break;
4589 case 3:
4590 unseen_conv_menu();
4591 break;
4593 return TRUE;
4596 static void
4597 conversation_updated_cb(PurpleConversation *conv, PurpleConvUpdateType type,
4598 PidginBuddyList *gtkblist)
4600 GList *convs = NULL;
4601 GList *ims, *chats;
4602 GList *l = NULL;
4604 if (type != PURPLE_CONV_UPDATE_UNSEEN)
4605 return;
4607 if(conv->account != NULL && conv->name != NULL) {
4608 PurpleBuddy *buddy = purple_find_buddy(conv->account, conv->name);
4609 if(buddy != NULL)
4610 pidgin_blist_update_buddy(NULL, (PurpleBlistNode *)buddy, TRUE);
4613 if (gtkblist->menutrayicon) {
4614 gtk_widget_destroy(gtkblist->menutrayicon);
4615 gtkblist->menutrayicon = NULL;
4618 ims = pidgin_conversations_find_unseen_list(PURPLE_CONV_TYPE_IM,
4619 PIDGIN_UNSEEN_TEXT, FALSE, 0);
4621 chats = pidgin_conversations_find_unseen_list(PURPLE_CONV_TYPE_CHAT,
4622 PIDGIN_UNSEEN_NICK, FALSE, 0);
4624 if(ims && chats)
4625 convs = g_list_concat(ims, chats);
4626 else if(ims && !chats)
4627 convs = ims;
4628 else if(!ims && chats)
4629 convs = chats;
4631 if (convs) {
4632 GtkWidget *img = NULL;
4633 GString *tooltip_text = NULL;
4635 tooltip_text = g_string_new("");
4636 l = convs;
4637 while (l != NULL) {
4638 int count = 0;
4639 PidginConversation *gtkconv = PIDGIN_CONVERSATION((PurpleConversation *)l->data);
4641 if(gtkconv)
4642 count = gtkconv->unseen_count;
4643 else if(purple_conversation_get_data(l->data, "unseen-count"))
4644 count = GPOINTER_TO_INT(purple_conversation_get_data(l->data, "unseen-count"));
4646 g_string_append_printf(tooltip_text,
4647 ngettext("%d unread message from %s\n", "%d unread messages from %s\n", count),
4648 count, purple_conversation_get_title(l->data));
4649 l = l->next;
4651 if(tooltip_text->len > 0) {
4652 /* get rid of the last newline */
4653 g_string_truncate(tooltip_text, tooltip_text->len -1);
4654 img = gtk_image_new_from_stock(PIDGIN_STOCK_TOOLBAR_PENDING,
4655 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL));
4657 gtkblist->menutrayicon = gtk_event_box_new();
4658 gtk_container_add(GTK_CONTAINER(gtkblist->menutrayicon), img);
4659 gtk_widget_show(img);
4660 gtk_widget_show(gtkblist->menutrayicon);
4661 g_signal_connect(G_OBJECT(gtkblist->menutrayicon), "button-press-event", G_CALLBACK(menutray_press_cb), NULL);
4663 pidgin_menu_tray_append(PIDGIN_MENU_TRAY(gtkblist->menutray), gtkblist->menutrayicon, tooltip_text->str);
4665 g_string_free(tooltip_text, TRUE);
4666 g_list_free(convs);
4670 static void
4671 conversation_deleting_cb(PurpleConversation *conv, PidginBuddyList *gtkblist)
4673 conversation_updated_cb(conv, PURPLE_CONV_UPDATE_UNSEEN, gtkblist);
4676 static void
4677 conversation_deleted_update_ui_cb(PurpleConversation *conv, struct _pidgin_blist_node *ui)
4679 if (ui->conv.conv != conv)
4680 return;
4681 ui->conv.conv = NULL;
4682 ui->conv.flags = 0;
4683 ui->conv.last_message = 0;
4686 static void
4687 written_msg_update_ui_cb(PurpleAccount *account, const char *who, const char *message,
4688 PurpleConversation *conv, PurpleMessageFlags flag, PurpleBlistNode *node)
4690 PidginBlistNode *ui = node->ui_data;
4691 if (ui->conv.conv != conv || !pidgin_conv_is_hidden(PIDGIN_CONVERSATION(conv)) ||
4692 !(flag & (PURPLE_MESSAGE_SEND | PURPLE_MESSAGE_RECV)))
4693 return;
4694 ui->conv.flags |= PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE;
4695 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT
4696 && (flag & PURPLE_MESSAGE_NICK))
4697 ui->conv.flags |= PIDGIN_BLIST_CHAT_HAS_PENDING_MESSAGE_WITH_NICK;
4699 ui->conv.last_message = time(NULL); /* XXX: for lack of better data */
4700 pidgin_blist_update(purple_get_blist(), node);
4703 static void
4704 displayed_msg_update_ui_cb(PidginConversation *gtkconv, PurpleBlistNode *node)
4706 PidginBlistNode *ui = node->ui_data;
4707 if (ui->conv.conv != gtkconv->active_conv)
4708 return;
4709 ui->conv.flags &= ~(PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE |
4710 PIDGIN_BLIST_CHAT_HAS_PENDING_MESSAGE_WITH_NICK);
4711 pidgin_blist_update(purple_get_blist(), node);
4714 static void
4715 conversation_created_cb(PurpleConversation *conv, PidginBuddyList *gtkblist)
4717 switch (conv->type) {
4718 case PURPLE_CONV_TYPE_IM:
4720 GSList *buddies = purple_find_buddies(conv->account, conv->name);
4721 while (buddies) {
4722 PurpleBlistNode *buddy = buddies->data;
4723 struct _pidgin_blist_node *ui = buddy->ui_data;
4724 buddies = g_slist_delete_link(buddies, buddies);
4725 if (!ui)
4726 continue;
4727 ui->conv.conv = conv;
4728 ui->conv.flags = 0;
4729 ui->conv.last_message = 0;
4730 purple_signal_connect(purple_conversations_get_handle(), "deleting-conversation",
4731 ui, PURPLE_CALLBACK(conversation_deleted_update_ui_cb), ui);
4732 purple_signal_connect(purple_conversations_get_handle(), "wrote-im-msg",
4733 ui, PURPLE_CALLBACK(written_msg_update_ui_cb), buddy);
4734 purple_signal_connect(pidgin_conversations_get_handle(), "conversation-displayed",
4735 ui, PURPLE_CALLBACK(displayed_msg_update_ui_cb), buddy);
4738 break;
4739 case PURPLE_CONV_TYPE_CHAT:
4741 PurpleChat *chat = purple_blist_find_chat(conv->account, conv->name);
4742 struct _pidgin_blist_node *ui;
4743 if (!chat)
4744 break;
4745 ui = chat->node.ui_data;
4746 if (!ui)
4747 break;
4748 ui->conv.conv = conv;
4749 ui->conv.flags = 0;
4750 ui->conv.last_message = 0;
4751 purple_signal_connect(purple_conversations_get_handle(), "deleting-conversation",
4752 ui, PURPLE_CALLBACK(conversation_deleted_update_ui_cb), ui);
4753 purple_signal_connect(purple_conversations_get_handle(), "wrote-chat-msg",
4754 ui, PURPLE_CALLBACK(written_msg_update_ui_cb), chat);
4755 purple_signal_connect(pidgin_conversations_get_handle(), "conversation-displayed",
4756 ui, PURPLE_CALLBACK(displayed_msg_update_ui_cb), chat);
4758 break;
4759 default:
4760 break;
4764 /**********************************************************************************
4765 * Public API Functions *
4766 **********************************************************************************/
4768 static void pidgin_blist_new_list(PurpleBuddyList *blist)
4770 PidginBuddyList *gtkblist;
4772 gtkblist = g_new0(PidginBuddyList, 1);
4773 gtkblist->connection_errors = g_hash_table_new_full(g_direct_hash,
4774 g_direct_equal, NULL, g_free);
4775 gtkblist->priv = g_new0(PidginBuddyListPrivate, 1);
4777 blist->ui_data = gtkblist;
4780 static void pidgin_blist_new_node(PurpleBlistNode *node)
4782 node->ui_data = g_new0(struct _pidgin_blist_node, 1);
4785 gboolean pidgin_blist_node_is_contact_expanded(PurpleBlistNode *node)
4787 if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
4788 node = node->parent;
4789 if (node == NULL)
4790 return FALSE;
4793 g_return_val_if_fail(PURPLE_BLIST_NODE_IS_CONTACT(node), FALSE);
4795 return ((struct _pidgin_blist_node *)node->ui_data)->contact_expanded;
4798 enum {
4799 DRAG_BUDDY,
4800 DRAG_ROW,
4801 DRAG_VCARD,
4802 DRAG_TEXT,
4803 DRAG_URI,
4804 NUM_TARGETS
4807 static const char *
4808 item_factory_translate_func (const char *path, gpointer func_data)
4810 return _((char *)path);
4813 void pidgin_blist_setup_sort_methods()
4815 const char *id;
4817 pidgin_blist_sort_method_reg("none", _("Manually"), sort_method_none);
4818 pidgin_blist_sort_method_reg("alphabetical", _("Alphabetically"), sort_method_alphabetical);
4819 pidgin_blist_sort_method_reg("status", _("By status"), sort_method_status);
4820 pidgin_blist_sort_method_reg("log_size", _("By recent log activity"), sort_method_log_activity);
4822 id = purple_prefs_get_string(PIDGIN_PREFS_ROOT "/blist/sort_type");
4823 if (id == NULL) {
4824 purple_debug_warning("gtkblist", "Sort method was NULL, resetting to alphabetical\n");
4825 id = "alphabetical";
4827 pidgin_blist_sort_method_set(id);
4830 static void _prefs_change_redo_list(const char *name, PurplePrefType type,
4831 gconstpointer val, gpointer data)
4833 GtkTreeSelection *sel;
4834 GtkTreeIter iter;
4835 PurpleBlistNode *node = NULL;
4837 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist->treeview));
4838 if (gtk_tree_selection_get_selected(sel, NULL, &iter))
4840 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
4843 redo_buddy_list(purple_get_blist(), FALSE, FALSE);
4844 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(gtkblist->treeview));
4846 if (node)
4848 struct _pidgin_blist_node *gtknode;
4849 GtkTreePath *path;
4851 gtknode = node->ui_data;
4852 if (gtknode && gtknode->row)
4854 path = gtk_tree_row_reference_get_path(gtknode->row);
4855 gtk_tree_selection_select_path(sel, path);
4856 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(gtkblist->treeview), path, NULL, FALSE, 0, 0);
4857 gtk_tree_path_free(path);
4862 static void _prefs_change_sort_method(const char *pref_name, PurplePrefType type,
4863 gconstpointer val, gpointer data)
4865 if(!strcmp(pref_name, PIDGIN_PREFS_ROOT "/blist/sort_type"))
4866 pidgin_blist_sort_method_set(val);
4869 static gboolean pidgin_blist_select_notebook_page_cb(gpointer user_data)
4871 PidginBuddyList *gtkblist = (PidginBuddyList *)user_data;
4872 int errors = 0;
4873 GList *list = NULL;
4874 PidginBuddyListPrivate *priv;
4876 priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
4878 /* this is far too ugly thanks to me not wanting to fix #3989 properly right now */
4879 if (priv->error_scrollbook != NULL) {
4880 errors = gtk_notebook_get_n_pages(GTK_NOTEBOOK(priv->error_scrollbook->notebook));
4882 if ((list = purple_accounts_get_all_active()) != NULL || errors) {
4883 gtk_notebook_set_current_page(GTK_NOTEBOOK(gtkblist->notebook), 1);
4884 g_list_free(list);
4885 } else
4886 gtk_notebook_set_current_page(GTK_NOTEBOOK(gtkblist->notebook), 0);
4888 return FALSE;
4891 static void pidgin_blist_select_notebook_page(PidginBuddyList *gtkblist)
4893 purple_timeout_add(0, pidgin_blist_select_notebook_page_cb, gtkblist);
4896 static void account_modified(PurpleAccount *account, PidginBuddyList *gtkblist)
4898 if (!gtkblist)
4899 return;
4901 pidgin_blist_select_notebook_page(gtkblist);
4902 update_menu_bar(gtkblist);
4905 static void
4906 account_actions_changed(PurpleAccount *account, gpointer data)
4908 pidgin_blist_update_accounts_menu();
4911 static void
4912 account_status_changed(PurpleAccount *account, PurpleStatus *old,
4913 PurpleStatus *new, PidginBuddyList *gtkblist)
4915 if (!gtkblist)
4916 return;
4918 account_modified(account, gtkblist);
4921 static gboolean
4922 gtk_blist_window_key_press_cb(GtkWidget *w, GdkEventKey *event, PidginBuddyList *gtkblist)
4924 GtkWidget *widget;
4926 if (!gtkblist)
4927 return FALSE;
4929 /* clear any tooltips */
4930 pidgin_blist_tooltip_destroy();
4932 widget = gtk_window_get_focus(GTK_WINDOW(gtkblist->window));
4934 if (GTK_IS_IMHTML(widget) || GTK_IS_ENTRY(widget)) {
4935 if (gtk_bindings_activate(GTK_OBJECT(widget), event->keyval, event->state))
4936 return TRUE;
4938 return FALSE;
4941 static gboolean
4942 headline_box_enter_cb(GtkWidget *widget, GdkEventCrossing *event, PidginBuddyList *gtkblist)
4944 gdk_window_set_cursor(widget->window, gtkblist->hand_cursor);
4945 return FALSE;
4948 static gboolean
4949 headline_box_leave_cb(GtkWidget *widget, GdkEventCrossing *event, PidginBuddyList *gtkblist)
4951 gdk_window_set_cursor(widget->window, gtkblist->arrow_cursor);
4952 return FALSE;
4955 static void
4956 reset_headline(PidginBuddyList *gtkblist)
4958 gtkblist->headline_callback = NULL;
4959 gtkblist->headline_data = NULL;
4960 gtkblist->headline_destroy = NULL;
4961 pidgin_set_urgent(GTK_WINDOW(gtkblist->window), FALSE);
4964 static gboolean
4965 headline_click_callback(gpointer unused)
4967 if (gtkblist->headline_callback)
4968 ((GSourceFunc) gtkblist->headline_callback)(gtkblist->headline_data);
4969 reset_headline(gtkblist);
4970 return FALSE;
4973 static gboolean
4974 headline_close_press_cb(GtkButton *button, PidginBuddyList *gtkblist)
4976 gtk_widget_hide(gtkblist->headline_hbox);
4977 return FALSE;
4980 static gboolean
4981 headline_box_press_cb(GtkWidget *widget, GdkEventButton *event, PidginBuddyList *gtkblist)
4983 gtk_widget_hide(gtkblist->headline_hbox);
4984 if (gtkblist->headline_callback)
4985 g_idle_add(headline_click_callback, NULL);
4986 else {
4987 if (gtkblist->headline_destroy)
4988 gtkblist->headline_destroy(gtkblist->headline_data);
4989 reset_headline(gtkblist);
4991 return TRUE;
4994 /***********************************/
4995 /* Connection error handling stuff */
4996 /***********************************/
4998 #define OBJECT_DATA_KEY_ACCOUNT "account"
4999 #define DO_NOT_CLEAR_ERROR "do-not-clear-error"
5001 static gboolean
5002 find_account_widget(GObject *widget,
5003 PurpleAccount *account)
5005 if (g_object_get_data(widget, OBJECT_DATA_KEY_ACCOUNT) == account)
5006 return 0; /* found */
5007 else
5008 return 1;
5011 static void
5012 pack_prpl_icon_start(GtkWidget *box,
5013 PurpleAccount *account)
5015 GdkPixbuf *pixbuf;
5016 GtkWidget *image;
5018 pixbuf = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL);
5019 if (pixbuf != NULL) {
5020 image = gtk_image_new_from_pixbuf(pixbuf);
5021 g_object_unref(pixbuf);
5023 gtk_box_pack_start(GTK_BOX(box), image, FALSE, FALSE, 0);
5027 static void
5028 add_error_dialog(PidginBuddyList *gtkblist,
5029 GtkWidget *dialog)
5031 PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
5032 gtk_container_add(GTK_CONTAINER(priv->error_scrollbook), dialog);
5035 static GtkWidget *
5036 find_child_widget_by_account(GtkContainer *container,
5037 PurpleAccount *account)
5039 GList *l = NULL;
5040 GList *children = NULL;
5041 GtkWidget *ret = NULL;
5042 /* XXX: Workaround for the currently incomplete implementation of PidginScrollBook */
5043 if (PIDGIN_IS_SCROLL_BOOK(container))
5044 container = GTK_CONTAINER(PIDGIN_SCROLL_BOOK(container)->notebook);
5045 children = gtk_container_get_children(container);
5046 l = g_list_find_custom(children, account, (GCompareFunc) find_account_widget);
5047 if (l)
5048 ret = GTK_WIDGET(l->data);
5049 g_list_free(children);
5050 return ret;
5053 static void
5054 remove_child_widget_by_account(GtkContainer *container,
5055 PurpleAccount *account)
5057 GtkWidget *widget = find_child_widget_by_account(container, account);
5058 if(widget) {
5059 /* Since we are destroying the widget in response to a change in
5060 * error, we should not clear the error.
5062 g_object_set_data(G_OBJECT(widget), DO_NOT_CLEAR_ERROR,
5063 GINT_TO_POINTER(TRUE));
5064 gtk_widget_destroy(widget);
5068 /* Generic error buttons */
5070 static void
5071 generic_error_modify_cb(PurpleAccount *account)
5073 purple_account_clear_current_error(account);
5074 pidgin_account_dialog_show(PIDGIN_MODIFY_ACCOUNT_DIALOG, account);
5077 static void
5078 generic_error_enable_cb(PurpleAccount *account)
5080 purple_account_clear_current_error(account);
5081 purple_account_set_enabled(account, purple_core_get_ui(), TRUE);
5084 static void
5085 generic_error_destroy_cb(GtkObject *dialog,
5086 PurpleAccount *account)
5088 g_hash_table_remove(gtkblist->connection_errors, account);
5089 /* If the error dialog is being destroyed in response to the
5090 * account-error-changed signal, we don't want to clear the current
5091 * error.
5093 if (g_object_get_data(G_OBJECT(dialog), DO_NOT_CLEAR_ERROR) == NULL)
5094 purple_account_clear_current_error(account);
5097 #define SSL_FAQ_URI "http://d.pidgin.im/wiki/FAQssl"
5099 static void
5100 ssl_faq_clicked_cb(PidginMiniDialog *mini_dialog,
5101 GtkButton *button,
5102 gpointer ignored)
5104 purple_notify_uri(NULL, SSL_FAQ_URI);
5107 static void
5108 add_generic_error_dialog(PurpleAccount *account,
5109 const PurpleConnectionErrorInfo *err)
5111 GtkWidget *mini_dialog;
5112 const char *username = purple_account_get_username(account);
5113 gboolean enabled =
5114 purple_account_get_enabled(account, purple_core_get_ui());
5115 char *primary;
5117 if (enabled)
5118 primary = g_strdup_printf(_("%s disconnected"), username);
5119 else
5120 primary = g_strdup_printf(_("%s disabled"), username);
5122 mini_dialog = pidgin_make_mini_dialog(NULL, PIDGIN_STOCK_DIALOG_ERROR,
5123 primary, err->description, account,
5124 (enabled ? _("Reconnect") : _("Re-enable")),
5125 (enabled ? PURPLE_CALLBACK(purple_account_connect)
5126 : PURPLE_CALLBACK(generic_error_enable_cb)),
5127 _("Modify Account"), PURPLE_CALLBACK(generic_error_modify_cb),
5128 NULL);
5130 g_free(primary);
5132 g_object_set_data(G_OBJECT(mini_dialog), OBJECT_DATA_KEY_ACCOUNT,
5133 account);
5135 if(err->type == PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT)
5136 pidgin_mini_dialog_add_button(PIDGIN_MINI_DIALOG(mini_dialog),
5137 _("SSL FAQs"), ssl_faq_clicked_cb, NULL);
5139 g_signal_connect_after(mini_dialog, "destroy",
5140 (GCallback)generic_error_destroy_cb,
5141 account);
5143 add_error_dialog(gtkblist, mini_dialog);
5146 static void
5147 remove_generic_error_dialog(PurpleAccount *account)
5149 PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
5150 remove_child_widget_by_account(
5151 GTK_CONTAINER(priv->error_scrollbook), account);
5155 static void
5156 update_generic_error_message(PurpleAccount *account,
5157 const char *description)
5159 PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
5160 GtkWidget *mini_dialog = find_child_widget_by_account(
5161 GTK_CONTAINER(priv->error_scrollbook), account);
5162 pidgin_mini_dialog_set_description(PIDGIN_MINI_DIALOG(mini_dialog),
5163 description);
5167 /* Notifications about accounts which were disconnected with
5168 * PURPLE_CONNECTION_ERROR_NAME_IN_USE
5171 typedef void (*AccountFunction)(PurpleAccount *);
5173 static void
5174 elsewhere_foreach_account(PidginMiniDialog *mini_dialog,
5175 AccountFunction f)
5177 PurpleAccount *account;
5178 GList *labels = gtk_container_get_children(
5179 GTK_CONTAINER(mini_dialog->contents));
5180 GList *l;
5182 for (l = labels; l; l = l->next) {
5183 account = g_object_get_data(G_OBJECT(l->data), OBJECT_DATA_KEY_ACCOUNT);
5184 if (account)
5185 f(account);
5186 else
5187 purple_debug_warning("gtkblist", "mini_dialog's child "
5188 "didn't have an account stored in it!");
5190 g_list_free(labels);
5193 static void
5194 enable_account(PurpleAccount *account)
5196 purple_account_set_enabled(account, purple_core_get_ui(), TRUE);
5199 static void
5200 reconnect_elsewhere_accounts(PidginMiniDialog *mini_dialog,
5201 GtkButton *button,
5202 gpointer unused)
5204 elsewhere_foreach_account(mini_dialog, enable_account);
5207 static void
5208 clear_elsewhere_errors(PidginMiniDialog *mini_dialog,
5209 gpointer unused)
5211 elsewhere_foreach_account(mini_dialog, purple_account_clear_current_error);
5214 static void
5215 ensure_signed_on_elsewhere_minidialog(PidginBuddyList *gtkblist)
5217 PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
5218 PidginMiniDialog *mini_dialog;
5220 if(priv->signed_on_elsewhere)
5221 return;
5223 mini_dialog = priv->signed_on_elsewhere =
5224 pidgin_mini_dialog_new(_("Welcome back!"), NULL, PIDGIN_STOCK_DISCONNECT);
5226 pidgin_mini_dialog_add_button(mini_dialog, _("Re-enable"),
5227 reconnect_elsewhere_accounts, NULL);
5229 /* Make dismissing the dialog clear the errors. The "destroy" signal
5230 * does not appear to fire at quit, which is fortunate!
5232 g_signal_connect(G_OBJECT(mini_dialog), "destroy",
5233 (GCallback) clear_elsewhere_errors, NULL);
5235 add_error_dialog(gtkblist, GTK_WIDGET(mini_dialog));
5237 /* Set priv->signed_on_elsewhere to NULL when the dialog is destroyed */
5238 g_signal_connect(G_OBJECT(mini_dialog), "destroy",
5239 (GCallback) gtk_widget_destroyed, &(priv->signed_on_elsewhere));
5242 static void
5243 update_signed_on_elsewhere_minidialog_title(void)
5245 PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
5246 PidginMiniDialog *mini_dialog = priv->signed_on_elsewhere;
5247 guint accounts;
5248 char *title;
5250 if (mini_dialog == NULL)
5251 return;
5253 accounts = pidgin_mini_dialog_get_num_children(mini_dialog);
5254 if (accounts == 0) {
5255 gtk_widget_destroy(GTK_WIDGET(mini_dialog));
5256 return;
5259 title = g_strdup_printf(
5260 ngettext("%d account was disabled because you signed on from another location:",
5261 "%d accounts were disabled because you signed on from another location:",
5262 accounts),
5263 accounts);
5264 pidgin_mini_dialog_set_description(mini_dialog, title);
5265 g_free(title);
5268 static GtkWidget *
5269 create_account_label(PurpleAccount *account)
5271 GtkWidget *hbox, *label;
5272 const char *username = purple_account_get_username(account);
5273 char *markup;
5275 hbox = gtk_hbox_new(FALSE, 6);
5276 g_object_set_data(G_OBJECT(hbox), OBJECT_DATA_KEY_ACCOUNT, account);
5278 pack_prpl_icon_start(hbox, account);
5280 label = gtk_label_new(NULL);
5281 markup = g_strdup_printf("<span size=\"smaller\">%s</span>", username);
5282 gtk_label_set_markup(GTK_LABEL(label), markup);
5283 g_free(markup);
5284 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
5285 g_object_set(G_OBJECT(label), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
5286 #if GTK_CHECK_VERSION(2,12,0)
5287 { /* avoid unused variable warnings on pre-2.12 Gtk */
5288 char *description =
5289 purple_account_get_current_error(account)->description;
5290 if (description != NULL && *description != '\0')
5291 gtk_widget_set_tooltip_text(label, description);
5293 #endif
5294 gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
5296 return hbox;
5299 static void
5300 add_to_signed_on_elsewhere(PurpleAccount *account)
5302 PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
5303 PidginMiniDialog *mini_dialog;
5304 GtkWidget *account_label;
5306 ensure_signed_on_elsewhere_minidialog(gtkblist);
5307 mini_dialog = priv->signed_on_elsewhere;
5309 if(find_child_widget_by_account(GTK_CONTAINER(mini_dialog->contents), account))
5310 return;
5312 account_label = create_account_label(account);
5313 gtk_box_pack_start(mini_dialog->contents, account_label, FALSE, FALSE, 0);
5314 gtk_widget_show_all(account_label);
5316 update_signed_on_elsewhere_minidialog_title();
5319 static void
5320 remove_from_signed_on_elsewhere(PurpleAccount *account)
5322 PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
5323 PidginMiniDialog *mini_dialog = priv->signed_on_elsewhere;
5324 if(mini_dialog == NULL)
5325 return;
5327 remove_child_widget_by_account(GTK_CONTAINER(mini_dialog->contents), account);
5329 update_signed_on_elsewhere_minidialog_title();
5333 static void
5334 update_signed_on_elsewhere_tooltip(PurpleAccount *account,
5335 const char *description)
5337 #if GTK_CHECK_VERSION(2,12,0)
5338 PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
5339 GtkContainer *c = GTK_CONTAINER(priv->signed_on_elsewhere->contents);
5340 GtkWidget *label = find_child_widget_by_account(c, account);
5341 gtk_widget_set_tooltip_text(label, description);
5342 #endif
5346 /* Call appropriate error notification code based on error types */
5347 static void
5348 update_account_error_state(PurpleAccount *account,
5349 const PurpleConnectionErrorInfo *old,
5350 const PurpleConnectionErrorInfo *new,
5351 PidginBuddyList *gtkblist)
5353 gboolean descriptions_differ;
5354 const char *desc;
5356 if (old == NULL && new == NULL)
5357 return;
5359 /* For backwards compatibility: */
5360 if (new)
5361 pidgin_blist_update_account_error_state(account, new->description);
5362 else
5363 pidgin_blist_update_account_error_state(account, NULL);
5365 if (new != NULL)
5366 pidgin_blist_select_notebook_page(gtkblist);
5368 if (old != NULL && new == NULL) {
5369 if(old->type == PURPLE_CONNECTION_ERROR_NAME_IN_USE)
5370 remove_from_signed_on_elsewhere(account);
5371 else
5372 remove_generic_error_dialog(account);
5373 return;
5376 if (old == NULL && new != NULL) {
5377 if(new->type == PURPLE_CONNECTION_ERROR_NAME_IN_USE)
5378 add_to_signed_on_elsewhere(account);
5379 else
5380 add_generic_error_dialog(account, new);
5381 return;
5384 /* else, new and old are both non-NULL */
5386 descriptions_differ = strcmp(old->description, new->description);
5387 desc = new->description;
5389 switch (new->type) {
5390 case PURPLE_CONNECTION_ERROR_NAME_IN_USE:
5391 if (old->type == PURPLE_CONNECTION_ERROR_NAME_IN_USE
5392 && descriptions_differ) {
5393 update_signed_on_elsewhere_tooltip(account, desc);
5394 } else {
5395 remove_generic_error_dialog(account);
5396 add_to_signed_on_elsewhere(account);
5398 break;
5399 default:
5400 if (old->type == PURPLE_CONNECTION_ERROR_NAME_IN_USE) {
5401 remove_from_signed_on_elsewhere(account);
5402 add_generic_error_dialog(account, new);
5403 } else if (descriptions_differ) {
5404 update_generic_error_message(account, desc);
5406 break;
5410 /* In case accounts are loaded before the blist (which they currently are),
5411 * let's call update_account_error_state ourselves on every account's current
5412 * state when the blist starts.
5414 static void
5415 show_initial_account_errors(PidginBuddyList *gtkblist)
5417 GList *l = purple_accounts_get_all();
5418 PurpleAccount *account;
5419 const PurpleConnectionErrorInfo *err;
5421 for (; l; l = l->next)
5423 account = l->data;
5424 err = purple_account_get_current_error(account);
5426 update_account_error_state(account, NULL, err, gtkblist);
5430 void
5431 pidgin_blist_update_account_error_state(PurpleAccount *account, const char *text)
5433 /* connection_errors isn't actually used anywhere; it's just kept in
5434 * sync with reality in case a plugin uses it.
5436 if (text == NULL)
5437 g_hash_table_remove(gtkblist->connection_errors, account);
5438 else
5439 g_hash_table_insert(gtkblist->connection_errors, account, g_strdup(text));
5442 static gboolean
5443 paint_headline_hbox (GtkWidget *widget,
5444 GdkEventExpose *event,
5445 gpointer user_data)
5447 gtk_paint_flat_box (widget->style,
5448 widget->window,
5449 GTK_STATE_NORMAL,
5450 GTK_SHADOW_OUT,
5451 NULL,
5452 widget,
5453 "tooltip",
5454 widget->allocation.x + 1,
5455 widget->allocation.y + 1,
5456 widget->allocation.width - 2,
5457 widget->allocation.height - 2);
5458 return FALSE;
5461 /* This assumes there are not things like groupless buddies or multi-leveled groups.
5462 * I'm sure other things in this code assumes that also.
5464 static void
5465 treeview_style_set (GtkWidget *widget,
5466 GtkStyle *prev_style,
5467 gpointer data)
5469 PurpleBuddyList *list = data;
5470 PurpleBlistNode *node = list->root;
5471 while (node) {
5472 pidgin_blist_update_group(list, node);
5473 node = node->next;
5477 static void
5478 headline_style_set (GtkWidget *widget,
5479 GtkStyle *prev_style)
5481 GtkTooltips *tooltips;
5482 GtkStyle *style;
5484 if (gtkblist->changing_style)
5485 return;
5487 tooltips = gtk_tooltips_new ();
5488 g_object_ref_sink (tooltips);
5490 gtk_tooltips_force_window (tooltips);
5491 #if GTK_CHECK_VERSION(2, 12, 0)
5492 gtk_widget_set_name (tooltips->tip_window, "gtk-tooltips");
5493 #endif
5494 gtk_widget_ensure_style (tooltips->tip_window);
5495 style = gtk_widget_get_style (tooltips->tip_window);
5497 gtkblist->changing_style = TRUE;
5498 gtk_widget_set_style (gtkblist->headline_hbox, style);
5499 gtkblist->changing_style = FALSE;
5501 g_object_unref (tooltips);
5504 /******************************************/
5505 /* End of connection error handling stuff */
5506 /******************************************/
5508 static int
5509 blist_focus_cb(GtkWidget *widget, GdkEventFocus *event, PidginBuddyList *gtkblist)
5511 if(event->in) {
5512 gtk_blist_focused = TRUE;
5513 pidgin_set_urgent(GTK_WINDOW(gtkblist->window), FALSE);
5514 } else {
5515 gtk_blist_focused = FALSE;
5517 return 0;
5520 #if 0
5521 static GtkWidget *
5522 kiosk_page()
5524 GtkWidget *ret = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
5525 GtkWidget *label;
5526 GtkWidget *entry;
5527 GtkWidget *bbox;
5528 GtkWidget *button;
5530 label = gtk_label_new(NULL);
5531 gtk_box_pack_start(GTK_BOX(ret), label, TRUE, TRUE, 0);
5533 label = gtk_label_new(NULL);
5534 gtk_label_set_markup(GTK_LABEL(label), _("<b>Username:</b>"));
5535 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
5536 gtk_box_pack_start(GTK_BOX(ret), label, FALSE, FALSE, 0);
5537 entry = gtk_entry_new();
5538 gtk_box_pack_start(GTK_BOX(ret), entry, FALSE, FALSE, 0);
5540 label = gtk_label_new(NULL);
5541 gtk_label_set_markup(GTK_LABEL(label), _("<b>Password:</b>"));
5542 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
5543 gtk_box_pack_start(GTK_BOX(ret), label, FALSE, FALSE, 0);
5544 entry = gtk_entry_new();
5545 gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
5546 gtk_box_pack_start(GTK_BOX(ret), entry, FALSE, FALSE, 0);
5548 label = gtk_label_new(" ");
5549 gtk_box_pack_start(GTK_BOX(ret), label, FALSE, FALSE, 0);
5551 bbox = gtk_hbutton_box_new();
5552 button = gtk_button_new_with_mnemonic(_("_Login"));
5553 gtk_box_pack_start(GTK_BOX(ret), bbox, FALSE, FALSE, 0);
5554 gtk_container_add(GTK_CONTAINER(bbox), button);
5557 label = gtk_label_new(NULL);
5558 gtk_box_pack_start(GTK_BOX(ret), label, TRUE, TRUE, 0);
5560 gtk_container_set_border_width(GTK_CONTAINER(ret), PIDGIN_HIG_BORDER);
5562 gtk_widget_show_all(ret);
5563 return ret;
5565 #endif
5567 /* builds the blist layout according to to the current theme */
5568 static void
5569 pidgin_blist_build_layout(PurpleBuddyList *list)
5571 GtkTreeViewColumn *column;
5572 PidginBlistLayout *layout;
5573 PidginBlistTheme *theme;
5574 GtkCellRenderer *rend;
5575 gint i, status_icon = 0, text = 1, emblem = 2, protocol_icon = 3, buddy_icon = 4;
5578 column = gtkblist->text_column;
5580 if ((theme = pidgin_blist_get_theme()) != NULL && (layout = pidgin_blist_theme_get_layout(theme)) != NULL) {
5581 status_icon = layout->status_icon ;
5582 text = layout->text;
5583 emblem = layout->emblem;
5584 protocol_icon = layout->protocol_icon;
5585 buddy_icon = layout->buddy_icon;
5588 gtk_tree_view_column_clear(column);
5590 /* group */
5591 rend = pidgin_cell_renderer_expander_new();
5592 gtk_tree_view_column_pack_start(column, rend, FALSE);
5593 gtk_tree_view_column_set_attributes(column, rend,
5594 "visible", GROUP_EXPANDER_VISIBLE_COLUMN,
5595 "expander-visible", GROUP_EXPANDER_COLUMN,
5596 "sensitive", GROUP_EXPANDER_COLUMN,
5597 "cell-background-gdk", BGCOLOR_COLUMN,
5598 NULL);
5600 /* contact */
5601 rend = pidgin_cell_renderer_expander_new();
5602 gtk_tree_view_column_pack_start(column, rend, FALSE);
5603 gtk_tree_view_column_set_attributes(column, rend,
5604 "visible", CONTACT_EXPANDER_VISIBLE_COLUMN,
5605 "expander-visible", CONTACT_EXPANDER_COLUMN,
5606 "sensitive", CONTACT_EXPANDER_COLUMN,
5607 "cell-background-gdk", BGCOLOR_COLUMN,
5608 NULL);
5610 for (i = 0; i < 5; i++) {
5612 if (status_icon == i) {
5613 /* status icons */
5614 rend = gtk_cell_renderer_pixbuf_new();
5615 gtk_tree_view_column_pack_start(column, rend, FALSE);
5616 gtk_tree_view_column_set_attributes(column, rend,
5617 "pixbuf", STATUS_ICON_COLUMN,
5618 "visible", STATUS_ICON_VISIBLE_COLUMN,
5619 "cell-background-gdk", BGCOLOR_COLUMN,
5620 NULL);
5621 g_object_set(rend, "xalign", 0.0, "xpad", 6, "ypad", 0, NULL);
5623 } else if (text == i) {
5624 /* name */
5625 gtkblist->text_rend = rend = gtk_cell_renderer_text_new();
5626 gtk_tree_view_column_pack_start(column, rend, TRUE);
5627 gtk_tree_view_column_set_attributes(column, rend,
5628 "cell-background-gdk", BGCOLOR_COLUMN,
5629 "markup", NAME_COLUMN,
5630 NULL);
5631 g_signal_connect(G_OBJECT(rend), "editing-started", G_CALLBACK(gtk_blist_renderer_editing_started_cb), NULL);
5632 g_signal_connect(G_OBJECT(rend), "editing-canceled", G_CALLBACK(gtk_blist_renderer_editing_cancelled_cb), list);
5633 g_signal_connect(G_OBJECT(rend), "edited", G_CALLBACK(gtk_blist_renderer_edited_cb), list);
5634 g_object_set(rend, "ypad", 0, "yalign", 0.5, NULL);
5635 g_object_set(rend, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
5637 /* idle */
5638 rend = gtk_cell_renderer_text_new();
5639 g_object_set(rend, "xalign", 1.0, "ypad", 0, NULL);
5640 gtk_tree_view_column_pack_start(column, rend, FALSE);
5641 gtk_tree_view_column_set_attributes(column, rend,
5642 "markup", IDLE_COLUMN,
5643 "visible", IDLE_VISIBLE_COLUMN,
5644 "cell-background-gdk", BGCOLOR_COLUMN,
5645 NULL);
5646 } else if (emblem == i) {
5647 /* emblem */
5648 rend = gtk_cell_renderer_pixbuf_new();
5649 g_object_set(rend, "xalign", 1.0, "yalign", 0.5, "ypad", 0, "xpad", 3, NULL);
5650 gtk_tree_view_column_pack_start(column, rend, FALSE);
5651 gtk_tree_view_column_set_attributes(column, rend, "pixbuf", EMBLEM_COLUMN,
5652 "cell-background-gdk", BGCOLOR_COLUMN,
5653 "visible", EMBLEM_VISIBLE_COLUMN, NULL);
5655 } else if (protocol_icon == i) {
5656 /* protocol icon */
5657 rend = gtk_cell_renderer_pixbuf_new();
5658 gtk_tree_view_column_pack_start(column, rend, FALSE);
5659 gtk_tree_view_column_set_attributes(column, rend,
5660 "pixbuf", PROTOCOL_ICON_COLUMN,
5661 "visible", PROTOCOL_ICON_VISIBLE_COLUMN,
5662 "cell-background-gdk", BGCOLOR_COLUMN,
5663 NULL);
5664 g_object_set(rend, "xalign", 0.0, "xpad", 3, "ypad", 0, NULL);
5666 } else if (buddy_icon == i) {
5667 /* buddy icon */
5668 rend = gtk_cell_renderer_pixbuf_new();
5669 g_object_set(rend, "xalign", 1.0, "ypad", 0, NULL);
5670 gtk_tree_view_column_pack_start(column, rend, FALSE);
5671 gtk_tree_view_column_set_attributes(column, rend, "pixbuf", BUDDY_ICON_COLUMN,
5672 "cell-background-gdk", BGCOLOR_COLUMN,
5673 "visible", BUDDY_ICON_VISIBLE_COLUMN,
5674 NULL);
5677 }/* end for loop */
5681 static gboolean
5682 pidgin_blist_search_equal_func(GtkTreeModel *model, gint column,
5683 const gchar *key, GtkTreeIter *iter, gpointer data)
5685 PurpleBlistNode *node = NULL;
5686 gboolean res = TRUE;
5687 const char *compare = NULL;
5689 if (!pidgin_tree_view_search_equal_func(model, column, key, iter, data))
5690 return FALSE;
5692 /* If the search string does not match the displayed label, then look
5693 * at the alternate labels for the nodes and search in them. Currently,
5694 * alternate labels that make sense are usernames/email addresses for
5695 * buddies (but only for the ones who don't have a local alias).
5698 gtk_tree_model_get(model, iter, NODE_COLUMN, &node, -1);
5699 if (!node)
5700 return TRUE;
5702 compare = NULL;
5703 if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
5704 PurpleBuddy *b = purple_contact_get_priority_buddy(PURPLE_CONTACT(node));
5705 if (!purple_buddy_get_local_buddy_alias(b))
5706 compare = purple_buddy_get_name(b);
5707 } else if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
5708 if (!purple_buddy_get_local_buddy_alias(PURPLE_BUDDY(node)))
5709 compare = purple_buddy_get_name(PURPLE_BUDDY(node));
5712 if (compare) {
5713 char *tmp, *enteredstring;
5714 tmp = g_utf8_normalize(key, -1, G_NORMALIZE_DEFAULT);
5715 enteredstring = g_utf8_casefold(tmp, -1);
5716 g_free(tmp);
5718 if (purple_str_has_prefix(compare, enteredstring))
5719 res = FALSE;
5720 g_free(enteredstring);
5723 return res;
5726 static void pidgin_blist_show(PurpleBuddyList *list)
5728 PidginBuddyListPrivate *priv;
5729 void *handle;
5730 GtkTreeViewColumn *column;
5731 GtkWidget *menu;
5732 GtkWidget *ebox;
5733 GtkWidget *sw;
5734 GtkWidget *sep;
5735 GtkWidget *label;
5736 GtkWidget *close;
5737 char *pretty, *tmp;
5738 const char *theme_name;
5739 GtkAccelGroup *accel_group;
5740 GtkTreeSelection *selection;
5741 GtkTargetEntry dte[] = {{"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 {"text/uri-list", 0, DRAG_URI},
5745 {"text/plain", 0, DRAG_TEXT}};
5746 GtkTargetEntry ste[] = {{"PURPLE_BLIST_NODE", GTK_TARGET_SAME_APP, DRAG_ROW},
5747 {"application/x-im-contact", 0, DRAG_BUDDY},
5748 {"text/x-vcard", 0, DRAG_VCARD }};
5749 if (gtkblist && gtkblist->window) {
5750 purple_blist_set_visible(purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/list_visible"));
5751 return;
5754 gtkblist = PIDGIN_BLIST(list);
5755 priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
5757 if (priv->current_theme)
5758 g_object_unref(priv->current_theme);
5760 theme_name = purple_prefs_get_string(PIDGIN_PREFS_ROOT "/blist/theme");
5761 if (theme_name && *theme_name)
5762 priv->current_theme = g_object_ref(PIDGIN_BLIST_THEME(purple_theme_manager_find_theme(theme_name, "blist")));
5763 else
5764 priv->current_theme = NULL;
5766 gtkblist->empty_avatar = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, 32, 32);
5767 gdk_pixbuf_fill(gtkblist->empty_avatar, 0x00000000);
5769 gtkblist->window = pidgin_create_window(_("Buddy List"), 0, "buddy_list", TRUE);
5770 g_signal_connect(G_OBJECT(gtkblist->window), "focus-in-event",
5771 G_CALLBACK(blist_focus_cb), gtkblist);
5772 g_signal_connect(G_OBJECT(gtkblist->window), "focus-out-event",
5773 G_CALLBACK(blist_focus_cb), gtkblist);
5774 GTK_WINDOW(gtkblist->window)->allow_shrink = TRUE;
5776 gtkblist->main_vbox = gtk_vbox_new(FALSE, 0);
5777 gtk_widget_show(gtkblist->main_vbox);
5778 gtk_container_add(GTK_CONTAINER(gtkblist->window), gtkblist->main_vbox);
5780 g_signal_connect(G_OBJECT(gtkblist->window), "delete_event", G_CALLBACK(gtk_blist_delete_cb), NULL);
5781 g_signal_connect(G_OBJECT(gtkblist->window), "configure_event", G_CALLBACK(gtk_blist_configure_cb), NULL);
5782 g_signal_connect(G_OBJECT(gtkblist->window), "visibility_notify_event", G_CALLBACK(gtk_blist_visibility_cb), NULL);
5783 g_signal_connect(G_OBJECT(gtkblist->window), "window_state_event", G_CALLBACK(gtk_blist_window_state_cb), NULL);
5784 g_signal_connect(G_OBJECT(gtkblist->window), "key_press_event", G_CALLBACK(gtk_blist_window_key_press_cb), gtkblist);
5785 gtk_widget_add_events(gtkblist->window, GDK_VISIBILITY_NOTIFY_MASK);
5787 /******************************* Menu bar *************************************/
5788 accel_group = gtk_accel_group_new();
5789 gtk_window_add_accel_group(GTK_WINDOW (gtkblist->window), accel_group);
5790 g_object_unref(accel_group);
5791 gtkblist->ift = gtk_item_factory_new(GTK_TYPE_MENU_BAR, "<PurpleMain>", accel_group);
5792 gtk_item_factory_set_translate_func(gtkblist->ift,
5793 (GtkTranslateFunc)item_factory_translate_func,
5794 NULL, NULL);
5795 gtk_item_factory_create_items(gtkblist->ift, sizeof(blist_menu) / sizeof(*blist_menu),
5796 blist_menu, NULL);
5797 pidgin_load_accels();
5798 g_signal_connect(G_OBJECT(accel_group), "accel-changed", G_CALLBACK(pidgin_save_accels_cb), NULL);
5800 menu = gtk_item_factory_get_widget(gtkblist->ift, "<PurpleMain>");
5801 gtkblist->menutray = pidgin_menu_tray_new();
5802 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtkblist->menutray);
5803 gtk_widget_show(gtkblist->menutray);
5804 gtk_widget_show(menu);
5805 gtk_box_pack_start(GTK_BOX(gtkblist->main_vbox), menu, FALSE, FALSE, 0);
5807 accountmenu = gtk_item_factory_get_widget(gtkblist->ift, N_("/Accounts"));
5810 /****************************** Notebook *************************************/
5811 gtkblist->notebook = gtk_notebook_new();
5812 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(gtkblist->notebook), FALSE);
5813 gtk_notebook_set_show_border(GTK_NOTEBOOK(gtkblist->notebook), FALSE);
5814 gtk_box_pack_start(GTK_BOX(gtkblist->main_vbox), gtkblist->notebook, TRUE, TRUE, 0);
5816 #if 0
5817 gtk_notebook_append_page(GTK_NOTEBOOK(gtkblist->notebook), kiosk_page(), NULL);
5818 #endif
5820 /* Translators: Please maintain the use of -> and <- to refer to menu heirarchy */
5821 tmp = g_strdup_printf(_("<span weight='bold' size='larger'>Welcome to %s!</span>\n\n"
5823 "You have no accounts enabled. Enable your IM accounts from the "
5824 "<b>Accounts</b> window at <b>Accounts->Manage Accounts</b>. Once you "
5825 "enable accounts, you'll be able to sign on, set your status, "
5826 "and talk to your friends."), PIDGIN_NAME);
5827 pretty = pidgin_make_pretty_arrows(tmp);
5828 g_free(tmp);
5829 label = gtk_label_new(NULL);
5830 gtk_widget_set_size_request(label, purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/width") - 12, -1);
5831 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
5832 gtk_misc_set_alignment(GTK_MISC(label), 0.5, 0.2);
5833 gtk_label_set_markup(GTK_LABEL(label), pretty);
5834 g_free(pretty);
5835 gtk_notebook_append_page(GTK_NOTEBOOK(gtkblist->notebook),label, NULL);
5836 gtkblist->vbox = gtk_vbox_new(FALSE, 0);
5837 gtk_notebook_append_page(GTK_NOTEBOOK(gtkblist->notebook), gtkblist->vbox, NULL);
5838 gtk_widget_show_all(gtkblist->notebook);
5839 pidgin_blist_select_notebook_page(gtkblist);
5841 ebox = gtk_event_box_new();
5842 gtk_box_pack_start(GTK_BOX(gtkblist->vbox), ebox, FALSE, FALSE, 0);
5843 gtkblist->headline_hbox = gtk_hbox_new(FALSE, 3);
5844 gtk_container_set_border_width(GTK_CONTAINER(gtkblist->headline_hbox), 6);
5845 gtk_container_add(GTK_CONTAINER(ebox), gtkblist->headline_hbox);
5846 gtkblist->headline_image = gtk_image_new_from_pixbuf(NULL);
5847 gtk_misc_set_alignment(GTK_MISC(gtkblist->headline_image), 0.0, 0);
5848 gtkblist->headline_label = gtk_label_new(NULL);
5849 gtk_widget_set_size_request(gtkblist->headline_label,
5850 purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/width")-25,-1);
5851 gtk_label_set_line_wrap(GTK_LABEL(gtkblist->headline_label), TRUE);
5852 gtk_box_pack_start(GTK_BOX(gtkblist->headline_hbox), gtkblist->headline_image, FALSE, FALSE, 0);
5853 gtk_box_pack_start(GTK_BOX(gtkblist->headline_hbox), gtkblist->headline_label, TRUE, TRUE, 0);
5854 g_signal_connect(gtkblist->headline_label, /* connecting on headline_hbox doesn't work, because
5855 the signal is not emitted when theme is changed */
5856 "style-set",
5857 G_CALLBACK(headline_style_set),
5858 NULL);
5859 g_signal_connect (gtkblist->headline_hbox,
5860 "expose_event",
5861 G_CALLBACK (paint_headline_hbox),
5862 NULL);
5863 gtk_widget_set_name(gtkblist->headline_hbox, "gtk-tooltips");
5865 gtkblist->headline_close = gtk_widget_render_icon(ebox, GTK_STOCK_CLOSE,
5866 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC), NULL);
5867 gtkblist->hand_cursor = gdk_cursor_new (GDK_HAND2);
5868 gtkblist->arrow_cursor = gdk_cursor_new (GDK_LEFT_PTR);
5870 /* Close button. */
5871 close = gtk_image_new_from_stock(GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU);
5872 close = pidgin_create_small_button(close);
5873 gtk_box_pack_start(GTK_BOX(gtkblist->headline_hbox), close, FALSE, FALSE, 0);
5874 #if GTK_CHECK_VERSION(2,12,0)
5875 gtk_widget_set_tooltip_text(close, _("Close"));
5876 #endif
5877 g_signal_connect(close, "clicked", G_CALLBACK(headline_close_press_cb), gtkblist);
5879 g_signal_connect(G_OBJECT(ebox), "enter-notify-event", G_CALLBACK(headline_box_enter_cb), gtkblist);
5880 g_signal_connect(G_OBJECT(ebox), "leave-notify-event", G_CALLBACK(headline_box_leave_cb), gtkblist);
5881 g_signal_connect(G_OBJECT(ebox), "button-press-event", G_CALLBACK(headline_box_press_cb), gtkblist);
5883 /****************************** GtkTreeView **********************************/
5884 sw = gtk_scrolled_window_new(NULL,NULL);
5885 gtk_widget_show(sw);
5886 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_NONE);
5887 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
5889 gtkblist->treemodel = gtk_tree_store_new(BLIST_COLUMNS,
5890 GDK_TYPE_PIXBUF, /* Status icon */
5891 G_TYPE_BOOLEAN, /* Status icon visible */
5892 G_TYPE_STRING, /* Name */
5893 G_TYPE_STRING, /* Idle */
5894 G_TYPE_BOOLEAN, /* Idle visible */
5895 GDK_TYPE_PIXBUF, /* Buddy icon */
5896 G_TYPE_BOOLEAN, /* Buddy icon visible */
5897 G_TYPE_POINTER, /* Node */
5898 GDK_TYPE_COLOR, /* bgcolor */
5899 G_TYPE_BOOLEAN, /* Group expander */
5900 G_TYPE_BOOLEAN, /* Group expander visible */
5901 G_TYPE_BOOLEAN, /* Contact expander */
5902 G_TYPE_BOOLEAN, /* Contact expander visible */
5903 GDK_TYPE_PIXBUF, /* Emblem */
5904 G_TYPE_BOOLEAN, /* Emblem visible */
5905 GDK_TYPE_PIXBUF, /* Protocol icon */
5906 G_TYPE_BOOLEAN /* Protocol visible */
5909 gtkblist->treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(gtkblist->treemodel));
5911 gtk_widget_show(gtkblist->treeview);
5912 gtk_widget_set_name(gtkblist->treeview, "pidgin_blist_treeview");
5914 g_signal_connect(gtkblist->treeview,
5915 "style-set",
5916 G_CALLBACK(treeview_style_set), list);
5917 /* Set up selection stuff */
5918 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist->treeview));
5919 g_signal_connect(G_OBJECT(selection), "changed", G_CALLBACK(pidgin_blist_selection_changed), NULL);
5921 /* Set up dnd */
5922 gtk_tree_view_enable_model_drag_source(GTK_TREE_VIEW(gtkblist->treeview),
5923 GDK_BUTTON1_MASK, ste, 3,
5924 GDK_ACTION_COPY);
5925 gtk_tree_view_enable_model_drag_dest(GTK_TREE_VIEW(gtkblist->treeview),
5926 dte, 5,
5927 GDK_ACTION_COPY | GDK_ACTION_MOVE);
5929 g_signal_connect(G_OBJECT(gtkblist->treeview), "drag-data-received", G_CALLBACK(pidgin_blist_drag_data_rcv_cb), NULL);
5930 g_signal_connect(G_OBJECT(gtkblist->treeview), "drag-data-get", G_CALLBACK(pidgin_blist_drag_data_get_cb), NULL);
5931 #ifdef _WIN32
5932 g_signal_connect(G_OBJECT(gtkblist->treeview), "drag-begin", G_CALLBACK(pidgin_blist_drag_begin), NULL);
5933 #endif
5934 g_signal_connect(G_OBJECT(gtkblist->treeview), "drag-motion", G_CALLBACK(pidgin_blist_drag_motion_cb), NULL);
5935 g_signal_connect(G_OBJECT(gtkblist->treeview), "motion-notify-event", G_CALLBACK(pidgin_blist_motion_cb), NULL);
5936 g_signal_connect(G_OBJECT(gtkblist->treeview), "leave-notify-event", G_CALLBACK(pidgin_blist_leave_cb), NULL);
5938 /* Tooltips */
5939 pidgin_tooltip_setup_for_treeview(gtkblist->treeview, NULL,
5940 pidgin_blist_create_tooltip,
5941 pidgin_blist_paint_tip);
5943 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(gtkblist->treeview), FALSE);
5945 /* expander columns */
5946 column = gtk_tree_view_column_new();
5947 gtk_tree_view_append_column(GTK_TREE_VIEW(gtkblist->treeview), column);
5948 gtk_tree_view_column_set_visible(column, FALSE);
5949 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(gtkblist->treeview), column);
5951 /* everything else column */
5952 gtkblist->text_column = gtk_tree_view_column_new ();
5953 gtk_tree_view_append_column(GTK_TREE_VIEW(gtkblist->treeview), gtkblist->text_column);
5954 pidgin_blist_build_layout(list);
5956 g_signal_connect(G_OBJECT(gtkblist->treeview), "row-activated", G_CALLBACK(gtk_blist_row_activated_cb), NULL);
5957 g_signal_connect(G_OBJECT(gtkblist->treeview), "row-expanded", G_CALLBACK(gtk_blist_row_expanded_cb), NULL);
5958 g_signal_connect(G_OBJECT(gtkblist->treeview), "row-collapsed", G_CALLBACK(gtk_blist_row_collapsed_cb), NULL);
5959 g_signal_connect(G_OBJECT(gtkblist->treeview), "button-press-event", G_CALLBACK(gtk_blist_button_press_cb), NULL);
5960 g_signal_connect(G_OBJECT(gtkblist->treeview), "key-press-event", G_CALLBACK(gtk_blist_key_press_cb), NULL);
5961 g_signal_connect(G_OBJECT(gtkblist->treeview), "popup-menu", G_CALLBACK(pidgin_blist_popup_menu_cb), NULL);
5963 /* Enable CTRL+F searching */
5964 gtk_tree_view_set_search_column(GTK_TREE_VIEW(gtkblist->treeview), NAME_COLUMN);
5965 gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(gtkblist->treeview),
5966 pidgin_blist_search_equal_func, NULL, NULL);
5968 gtk_box_pack_start(GTK_BOX(gtkblist->vbox), sw, TRUE, TRUE, 0);
5969 gtk_container_add(GTK_CONTAINER(sw), gtkblist->treeview);
5971 sep = gtk_hseparator_new();
5972 gtk_box_pack_start(GTK_BOX(gtkblist->vbox), sep, FALSE, FALSE, 0);
5974 gtkblist->scrollbook = pidgin_scroll_book_new();
5975 gtk_box_pack_start(GTK_BOX(gtkblist->vbox), gtkblist->scrollbook, FALSE, FALSE, 0);
5977 /* Create an vbox which holds the scrollbook which is actually used to
5978 * display connection errors. The vbox needs to still exist for
5979 * backwards compatibility.
5981 gtkblist->error_buttons = gtk_vbox_new(FALSE, 0);
5982 gtk_box_pack_start(GTK_BOX(gtkblist->vbox), gtkblist->error_buttons, FALSE, FALSE, 0);
5983 gtk_container_set_border_width(GTK_CONTAINER(gtkblist->error_buttons), 0);
5985 priv->error_scrollbook = PIDGIN_SCROLL_BOOK(pidgin_scroll_book_new());
5986 gtk_box_pack_start(GTK_BOX(gtkblist->error_buttons),
5987 GTK_WIDGET(priv->error_scrollbook), FALSE, FALSE, 0);
5990 /* Add the statusbox */
5991 gtkblist->statusbox = pidgin_status_box_new();
5992 gtk_box_pack_start(GTK_BOX(gtkblist->vbox), gtkblist->statusbox, FALSE, TRUE, 0);
5993 gtk_widget_set_name(gtkblist->statusbox, "pidgin_blist_statusbox");
5994 gtk_container_set_border_width(GTK_CONTAINER(gtkblist->statusbox), 3);
5995 gtk_widget_show(gtkblist->statusbox);
5997 /* set the Show Offline Buddies option. must be done
5998 * after the treeview or faceprint gets mad. -Robot101
6000 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show/Offline Buddies"))),
6001 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_offline_buddies"));
6003 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show/Empty Groups"))),
6004 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_empty_groups"));
6006 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Tools/Mute Sounds"))),
6007 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/sound/mute"));
6009 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show/Buddy Details"))),
6010 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons"));
6012 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show/Idle Times"))),
6013 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_idle_time"));
6015 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show/Protocol Icons"))),
6016 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons"));
6018 if(!strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/sound/method"), "none"))
6019 gtk_widget_set_sensitive(gtk_item_factory_get_widget(gtkblist->ift, N_("/Tools/Mute Sounds")), FALSE);
6021 /* Update some dynamic things */
6022 update_menu_bar(gtkblist);
6023 pidgin_blist_update_plugin_actions();
6024 pidgin_blist_update_sort_methods();
6026 /* OK... let's show this bad boy. */
6027 pidgin_blist_refresh(list);
6028 pidgin_blist_restore_position();
6029 gtk_widget_show_all(GTK_WIDGET(gtkblist->vbox));
6030 gtk_widget_realize(GTK_WIDGET(gtkblist->window));
6031 purple_blist_set_visible(purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/list_visible"));
6033 /* start the refresh timer */
6034 gtkblist->refresh_timer = purple_timeout_add_seconds(30, (GSourceFunc)pidgin_blist_refresh_timer, list);
6036 handle = pidgin_blist_get_handle();
6038 /* things that affect how buddies are displayed */
6039 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/show_buddy_icons",
6040 _prefs_change_redo_list, NULL);
6041 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/show_idle_time",
6042 _prefs_change_redo_list, NULL);
6043 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/show_empty_groups",
6044 _prefs_change_redo_list, NULL);
6045 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/show_offline_buddies",
6046 _prefs_change_redo_list, NULL);
6047 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/show_protocol_icons",
6048 _prefs_change_redo_list, NULL);
6050 /* sorting */
6051 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/sort_type",
6052 _prefs_change_sort_method, NULL);
6054 /* menus */
6055 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/sound/mute",
6056 pidgin_blist_mute_pref_cb, NULL);
6057 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/sound/method",
6058 pidgin_blist_sound_method_pref_cb, NULL);
6060 /* Setup some purple signal handlers. */
6062 handle = purple_accounts_get_handle();
6063 purple_signal_connect(handle, "account-enabled", gtkblist,
6064 PURPLE_CALLBACK(account_modified), gtkblist);
6065 purple_signal_connect(handle, "account-disabled", gtkblist,
6066 PURPLE_CALLBACK(account_modified), gtkblist);
6067 purple_signal_connect(handle, "account-removed", gtkblist,
6068 PURPLE_CALLBACK(account_modified), gtkblist);
6069 purple_signal_connect(handle, "account-status-changed", gtkblist,
6070 PURPLE_CALLBACK(account_status_changed),
6071 gtkblist);
6072 purple_signal_connect(handle, "account-error-changed", gtkblist,
6073 PURPLE_CALLBACK(update_account_error_state),
6074 gtkblist);
6075 purple_signal_connect(handle, "account-actions-changed", gtkblist,
6076 PURPLE_CALLBACK(account_actions_changed), NULL);
6078 handle = pidgin_account_get_handle();
6079 purple_signal_connect(handle, "account-modified", gtkblist,
6080 PURPLE_CALLBACK(account_modified), gtkblist);
6082 handle = purple_connections_get_handle();
6083 purple_signal_connect(handle, "signed-on", gtkblist,
6084 PURPLE_CALLBACK(sign_on_off_cb), list);
6085 purple_signal_connect(handle, "signed-off", gtkblist,
6086 PURPLE_CALLBACK(sign_on_off_cb), list);
6088 handle = purple_plugins_get_handle();
6089 purple_signal_connect(handle, "plugin-load", gtkblist,
6090 PURPLE_CALLBACK(plugin_changed_cb), NULL);
6091 purple_signal_connect(handle, "plugin-unload", gtkblist,
6092 PURPLE_CALLBACK(plugin_changed_cb), NULL);
6094 handle = purple_conversations_get_handle();
6095 purple_signal_connect(handle, "conversation-updated", gtkblist,
6096 PURPLE_CALLBACK(conversation_updated_cb),
6097 gtkblist);
6098 purple_signal_connect(handle, "deleting-conversation", gtkblist,
6099 PURPLE_CALLBACK(conversation_deleting_cb),
6100 gtkblist);
6101 purple_signal_connect(handle, "conversation-created", gtkblist,
6102 PURPLE_CALLBACK(conversation_created_cb),
6103 gtkblist);
6105 gtk_widget_hide(gtkblist->headline_hbox);
6107 show_initial_account_errors(gtkblist);
6109 /* emit our created signal */
6110 handle = pidgin_blist_get_handle();
6111 purple_signal_emit(handle, "gtkblist-created", list);
6114 static void redo_buddy_list(PurpleBuddyList *list, gboolean remove, gboolean rerender)
6116 PurpleBlistNode *node;
6118 gtkblist = PIDGIN_BLIST(list);
6119 if(!gtkblist || !gtkblist->treeview)
6120 return;
6122 node = list->root;
6124 while (node)
6126 /* This is only needed when we're reverting to a non-GTK+ sorted
6127 * status. We shouldn't need to remove otherwise.
6129 if (remove && !PURPLE_BLIST_NODE_IS_GROUP(node))
6130 pidgin_blist_hide_node(list, node, FALSE);
6132 if (PURPLE_BLIST_NODE_IS_BUDDY(node))
6133 pidgin_blist_update_buddy(list, node, rerender);
6134 else if (PURPLE_BLIST_NODE_IS_CHAT(node))
6135 pidgin_blist_update(list, node);
6136 else if (PURPLE_BLIST_NODE_IS_GROUP(node))
6137 pidgin_blist_update(list, node);
6138 node = purple_blist_node_next(node, FALSE);
6143 void pidgin_blist_refresh(PurpleBuddyList *list)
6145 redo_buddy_list(list, FALSE, TRUE);
6148 void
6149 pidgin_blist_update_refresh_timeout()
6151 PurpleBuddyList *blist;
6152 PidginBuddyList *gtkblist;
6154 blist = purple_get_blist();
6155 gtkblist = PIDGIN_BLIST(purple_get_blist());
6157 gtkblist->refresh_timer = purple_timeout_add_seconds(30,(GSourceFunc)pidgin_blist_refresh_timer, blist);
6160 static gboolean get_iter_from_node(PurpleBlistNode *node, GtkTreeIter *iter) {
6161 struct _pidgin_blist_node *gtknode = (struct _pidgin_blist_node *)node->ui_data;
6162 GtkTreePath *path;
6164 if (!gtknode) {
6165 return FALSE;
6168 if (!gtkblist) {
6169 purple_debug_error("gtkblist", "get_iter_from_node was called, but we don't seem to have a blist\n");
6170 return FALSE;
6173 if (!gtknode->row)
6174 return FALSE;
6177 if ((path = gtk_tree_row_reference_get_path(gtknode->row)) == NULL)
6178 return FALSE;
6180 if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), iter, path)) {
6181 gtk_tree_path_free(path);
6182 return FALSE;
6184 gtk_tree_path_free(path);
6185 return TRUE;
6188 static void pidgin_blist_remove(PurpleBuddyList *list, PurpleBlistNode *node)
6190 struct _pidgin_blist_node *gtknode = node->ui_data;
6192 purple_request_close_with_handle(node);
6194 pidgin_blist_hide_node(list, node, TRUE);
6196 if(node->parent)
6197 pidgin_blist_update(list, node->parent);
6199 /* There's something I don't understand here - Ethan */
6200 /* Ethan said that back in 2003, but this g_free has been left commented
6201 * out ever since. I can't find any reason at all why this is bad and
6202 * valgrind found several reasons why it's good. If this causes problems
6203 * comment it out again. Stu */
6204 /* Of course it still causes problems - this breaks dragging buddies into
6205 * contacts, the dragged buddy mysteriously 'disappears'. Stu. */
6206 /* I think it's fixed now. Stu. */
6208 if(gtknode) {
6209 if(gtknode->recent_signonoff_timer > 0)
6210 purple_timeout_remove(gtknode->recent_signonoff_timer);
6212 purple_signals_disconnect_by_handle(node->ui_data);
6213 g_free(node->ui_data);
6214 node->ui_data = NULL;
6218 static gboolean do_selection_changed(PurpleBlistNode *new_selection)
6220 PurpleBlistNode *old_selection = NULL;
6222 /* test for gtkblist because crazy timeout means we can be called after the blist is gone */
6223 if (gtkblist && new_selection != gtkblist->selected_node) {
6224 old_selection = gtkblist->selected_node;
6225 gtkblist->selected_node = new_selection;
6226 if(new_selection)
6227 pidgin_blist_update(NULL, new_selection);
6228 if(old_selection)
6229 pidgin_blist_update(NULL, old_selection);
6232 return FALSE;
6235 static void pidgin_blist_selection_changed(GtkTreeSelection *selection, gpointer data)
6237 PurpleBlistNode *new_selection = NULL;
6238 GtkTreeIter iter;
6240 if(gtk_tree_selection_get_selected(selection, NULL, &iter)){
6241 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter,
6242 NODE_COLUMN, &new_selection, -1);
6245 /* we set this up as a timeout, otherwise the blist flickers ...
6246 * but we don't do it for groups, because it causes total bizarness -
6247 * the previously selected buddy node might rendered at half height.
6249 if ((new_selection != NULL) && PURPLE_BLIST_NODE_IS_GROUP(new_selection)) {
6250 do_selection_changed(new_selection);
6251 } else {
6252 g_timeout_add(0, (GSourceFunc)do_selection_changed, new_selection);
6256 static gboolean insert_node(PurpleBuddyList *list, PurpleBlistNode *node, GtkTreeIter *iter)
6258 GtkTreeIter parent_iter, cur, *curptr = NULL;
6259 struct _pidgin_blist_node *gtknode = node->ui_data;
6260 GtkTreePath *newpath;
6262 if(!iter)
6263 return FALSE;
6265 if(node->parent && !get_iter_from_node(node->parent, &parent_iter))
6266 return FALSE;
6268 if(get_iter_from_node(node, &cur))
6269 curptr = &cur;
6271 if(PURPLE_BLIST_NODE_IS_CONTACT(node) || PURPLE_BLIST_NODE_IS_CHAT(node)) {
6272 current_sort_method->func(node, list, parent_iter, curptr, iter);
6273 } else {
6274 sort_method_none(node, list, parent_iter, curptr, iter);
6277 if(gtknode != NULL) {
6278 gtk_tree_row_reference_free(gtknode->row);
6279 } else {
6280 pidgin_blist_new_node(node);
6281 gtknode = (struct _pidgin_blist_node *)node->ui_data;
6284 newpath = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel),
6285 iter);
6286 gtknode->row =
6287 gtk_tree_row_reference_new(GTK_TREE_MODEL(gtkblist->treemodel),
6288 newpath);
6290 gtk_tree_path_free(newpath);
6292 if (!editing_blist)
6293 gtk_tree_store_set(gtkblist->treemodel, iter,
6294 NODE_COLUMN, node,
6295 -1);
6297 if(node->parent) {
6298 GtkTreePath *expand = NULL;
6299 struct _pidgin_blist_node *gtkparentnode = node->parent->ui_data;
6301 if(PURPLE_BLIST_NODE_IS_GROUP(node->parent)) {
6302 if(!purple_blist_node_get_bool(node->parent, "collapsed"))
6303 expand = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &parent_iter);
6304 } else if(PURPLE_BLIST_NODE_IS_CONTACT(node->parent) &&
6305 gtkparentnode->contact_expanded) {
6306 expand = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &parent_iter);
6308 if(expand) {
6309 gtk_tree_view_expand_row(GTK_TREE_VIEW(gtkblist->treeview), expand, FALSE);
6310 gtk_tree_path_free(expand);
6314 return TRUE;
6317 static gboolean pidgin_blist_group_has_show_offline_buddy(PurpleGroup *group)
6319 PurpleBlistNode *gnode, *cnode, *bnode;
6321 gnode = (PurpleBlistNode *)group;
6322 for(cnode = gnode->child; cnode; cnode = cnode->next) {
6323 if(PURPLE_BLIST_NODE_IS_CONTACT(cnode)) {
6324 for(bnode = cnode->child; bnode; bnode = bnode->next) {
6325 PurpleBuddy *buddy = (PurpleBuddy *)bnode;
6326 if (purple_account_is_connected(buddy->account) &&
6327 purple_blist_node_get_bool(bnode, "show_offline"))
6328 return TRUE;
6332 return FALSE;
6335 /* This version of pidgin_blist_update_group can take the original buddy or a
6336 * group, but has much better algorithmic performance with a pre-known buddy.
6338 static void pidgin_blist_update_group(PurpleBuddyList *list,
6339 PurpleBlistNode *node)
6341 gint count;
6342 PurpleGroup *group;
6343 PurpleBlistNode* gnode;
6344 gboolean show = FALSE, show_offline = FALSE;
6346 g_return_if_fail(node != NULL);
6348 if (editing_blist)
6349 return;
6351 if (PURPLE_BLIST_NODE_IS_GROUP(node))
6352 gnode = node;
6353 else if (PURPLE_BLIST_NODE_IS_BUDDY(node))
6354 gnode = node->parent->parent;
6355 else if (PURPLE_BLIST_NODE_IS_CONTACT(node) || PURPLE_BLIST_NODE_IS_CHAT(node))
6356 gnode = node->parent;
6357 else
6358 return;
6360 group = (PurpleGroup*)gnode;
6362 show_offline = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_offline_buddies");
6364 if(show_offline)
6365 count = purple_blist_get_group_size(group, FALSE);
6366 else
6367 count = purple_blist_get_group_online_count(group);
6369 if (count > 0 || purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_empty_groups"))
6370 show = TRUE;
6371 else if (PURPLE_BLIST_NODE_IS_BUDDY(node) && buddy_is_displayable((PurpleBuddy*)node)) { /* Or chat? */
6372 show = TRUE;
6373 } else if (!show_offline) {
6374 show = pidgin_blist_group_has_show_offline_buddy(group);
6377 if (show) {
6378 gchar *title;
6379 gboolean biglist;
6380 GtkTreeIter iter;
6381 GtkTreePath *path;
6382 gboolean expanded;
6383 GdkColor *bgcolor = NULL;
6384 GdkPixbuf *avatar = NULL;
6385 PidginBlistTheme *theme = NULL;
6387 if(!insert_node(list, gnode, &iter))
6388 return;
6390 if ((theme = pidgin_blist_get_theme()) == NULL)
6391 bgcolor = NULL;
6392 else if (purple_blist_node_get_bool(gnode, "collapsed") || count <= 0)
6393 bgcolor = pidgin_blist_theme_get_collapsed_background_color(theme);
6394 else
6395 bgcolor = pidgin_blist_theme_get_expanded_background_color(theme);
6397 path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &iter);
6398 expanded = gtk_tree_view_row_expanded(GTK_TREE_VIEW(gtkblist->treeview), path);
6399 gtk_tree_path_free(path);
6401 title = pidgin_get_group_title(gnode, expanded);
6402 biglist = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons");
6404 if (biglist) {
6405 avatar = pidgin_blist_get_buddy_icon(gnode, TRUE, TRUE);
6408 gtk_tree_store_set(gtkblist->treemodel, &iter,
6409 STATUS_ICON_VISIBLE_COLUMN, FALSE,
6410 STATUS_ICON_COLUMN, NULL,
6411 NAME_COLUMN, title,
6412 NODE_COLUMN, gnode,
6413 BGCOLOR_COLUMN, bgcolor,
6414 GROUP_EXPANDER_COLUMN, TRUE,
6415 GROUP_EXPANDER_VISIBLE_COLUMN, TRUE,
6416 CONTACT_EXPANDER_VISIBLE_COLUMN, FALSE,
6417 BUDDY_ICON_COLUMN, avatar,
6418 BUDDY_ICON_VISIBLE_COLUMN, biglist,
6419 IDLE_VISIBLE_COLUMN, FALSE,
6420 EMBLEM_VISIBLE_COLUMN, FALSE,
6421 -1);
6422 g_free(title);
6423 } else {
6424 pidgin_blist_hide_node(list, gnode, TRUE);
6428 static char *pidgin_get_group_title(PurpleBlistNode *gnode, gboolean expanded)
6430 PurpleGroup *group;
6431 gboolean selected;
6432 char group_count[12] = "";
6433 char *mark, *esc;
6434 PurpleBlistNode *selected_node = NULL;
6435 GtkTreeIter iter;
6436 PidginThemeFont *pair;
6437 gchar const *text_color, *text_font;
6438 PidginBlistTheme *theme;
6440 group = (PurpleGroup*)gnode;
6442 if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist->treeview)), NULL, &iter)) {
6443 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter,
6444 NODE_COLUMN, &selected_node, -1);
6446 selected = (gnode == selected_node);
6448 if (!expanded) {
6449 g_snprintf(group_count, sizeof(group_count), "%d/%d",
6450 purple_blist_get_group_online_count(group),
6451 purple_blist_get_group_size(group, FALSE));
6454 theme = pidgin_blist_get_theme();
6455 if (theme == NULL)
6456 pair = NULL;
6457 else if (expanded)
6458 pair = pidgin_blist_theme_get_expanded_text_info(theme);
6459 else
6460 pair = pidgin_blist_theme_get_collapsed_text_info(theme);
6463 text_color = selected ? NULL : theme_font_get_color_default(pair, NULL);
6464 text_font = theme_font_get_face_default(pair, "");
6466 esc = g_markup_escape_text(group->name, -1);
6467 if (text_color) {
6468 mark = g_strdup_printf("<span foreground='%s' font_desc='%s'><b>%s</b>%s%s%s</span>",
6469 text_color, text_font,
6470 esc ? esc : "",
6471 !expanded ? " <span weight='light'>(</span>" : "",
6472 group_count,
6473 !expanded ? "<span weight='light'>)</span>" : "");
6474 } else {
6475 mark = g_strdup_printf("<span font_desc='%s'><b>%s</b>%s%s%s</span>",
6476 text_font, esc ? esc : "",
6477 !expanded ? " <span weight='light'>(</span>" : "",
6478 group_count,
6479 !expanded ? "<span weight='light'>)</span>" : "");
6482 g_free(esc);
6483 return mark;
6486 static void buddy_node(PurpleBuddy *buddy, GtkTreeIter *iter, PurpleBlistNode *node)
6488 PurplePresence *presence = purple_buddy_get_presence(buddy);
6489 GdkPixbuf *status, *avatar, *emblem, *prpl_icon;
6490 GdkColor *color = NULL;
6491 char *mark;
6492 char *idle = NULL;
6493 gboolean expanded = ((struct _pidgin_blist_node *)(node->parent->ui_data))->contact_expanded;
6494 gboolean selected = (gtkblist->selected_node == node);
6495 gboolean biglist = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons");
6496 PidginBlistTheme *theme;
6498 if (editing_blist)
6499 return;
6501 status = pidgin_blist_get_status_icon((PurpleBlistNode*)buddy,
6502 biglist ? PIDGIN_STATUS_ICON_LARGE : PIDGIN_STATUS_ICON_SMALL);
6504 /* Speed it up if we don't want buddy icons. */
6505 if(biglist)
6506 avatar = pidgin_blist_get_buddy_icon((PurpleBlistNode *)buddy, TRUE, TRUE);
6507 else
6508 avatar = NULL;
6510 if (!avatar) {
6511 g_object_ref(G_OBJECT(gtkblist->empty_avatar));
6512 avatar = gtkblist->empty_avatar;
6513 } else if ((!PURPLE_BUDDY_IS_ONLINE(buddy) || purple_presence_is_idle(presence))) {
6514 do_alphashift(avatar, 77);
6517 emblem = pidgin_blist_get_emblem((PurpleBlistNode*) buddy);
6518 mark = pidgin_blist_get_name_markup(buddy, selected, TRUE);
6520 theme = pidgin_blist_get_theme();
6522 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_idle_time") &&
6523 purple_presence_is_idle(presence) && !biglist)
6525 time_t idle_secs = purple_presence_get_idle_time(presence);
6527 if (idle_secs > 0)
6529 PidginThemeFont *pair = NULL;
6530 const gchar *textcolor;
6531 time_t t;
6532 int ihrs, imin;
6533 time(&t);
6535 ihrs = (t - idle_secs) / 3600;
6536 imin = ((t - idle_secs) / 60) % 60;
6538 if (selected)
6539 textcolor = NULL;
6540 else if (theme != NULL && (pair = pidgin_blist_theme_get_idle_text_info(theme)) != NULL)
6541 textcolor = pidgin_theme_font_get_color_describe(pair);
6542 else
6543 /* If no theme them default to making idle buddy names grey */
6544 textcolor = "dim grey";
6546 if (textcolor) {
6547 idle = g_strdup_printf("<span color='%s' font_desc='%s'>%d:%02d</span>",
6548 textcolor, theme_font_get_face_default(pair, ""),
6549 ihrs, imin);
6550 } else {
6551 idle = g_strdup_printf("<span font_desc='%s'>%d:%02d</span>",
6552 theme_font_get_face_default(pair, ""),
6553 ihrs, imin);
6558 prpl_icon = pidgin_create_prpl_icon(buddy->account, PIDGIN_PRPL_ICON_SMALL);
6560 if (theme != NULL)
6561 color = pidgin_blist_theme_get_contact_color(theme);
6563 gtk_tree_store_set(gtkblist->treemodel, iter,
6564 STATUS_ICON_COLUMN, status,
6565 STATUS_ICON_VISIBLE_COLUMN, TRUE,
6566 NAME_COLUMN, mark,
6567 IDLE_COLUMN, idle,
6568 IDLE_VISIBLE_COLUMN, !biglist && idle,
6569 BUDDY_ICON_COLUMN, avatar,
6570 BUDDY_ICON_VISIBLE_COLUMN, biglist,
6571 EMBLEM_COLUMN, emblem,
6572 EMBLEM_VISIBLE_COLUMN, (emblem != NULL),
6573 PROTOCOL_ICON_COLUMN, prpl_icon,
6574 PROTOCOL_ICON_VISIBLE_COLUMN, purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons"),
6575 BGCOLOR_COLUMN, color,
6576 CONTACT_EXPANDER_COLUMN, NULL,
6577 CONTACT_EXPANDER_VISIBLE_COLUMN, expanded,
6578 GROUP_EXPANDER_VISIBLE_COLUMN, FALSE,
6579 -1);
6581 g_free(mark);
6582 g_free(idle);
6583 if(emblem)
6584 g_object_unref(emblem);
6585 if(status)
6586 g_object_unref(status);
6587 if(avatar)
6588 g_object_unref(avatar);
6589 if(prpl_icon)
6590 g_object_unref(prpl_icon);
6593 /* This is a variation on the original gtk_blist_update_contact. Here we
6594 can know in advance which buddy has changed so we can just update that */
6595 static void pidgin_blist_update_contact(PurpleBuddyList *list, PurpleBlistNode *node)
6597 PurpleBlistNode *cnode;
6598 PurpleContact *contact;
6599 PurpleBuddy *buddy;
6600 gboolean biglist = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons");
6601 struct _pidgin_blist_node *gtknode;
6603 if (editing_blist)
6604 return;
6606 if (PURPLE_BLIST_NODE_IS_BUDDY(node))
6607 cnode = node->parent;
6608 else
6609 cnode = node;
6611 g_return_if_fail(PURPLE_BLIST_NODE_IS_CONTACT(cnode));
6613 /* First things first, update the group */
6614 if (PURPLE_BLIST_NODE_IS_BUDDY(node))
6615 pidgin_blist_update_group(list, node);
6616 else
6617 pidgin_blist_update_group(list, cnode->parent);
6619 contact = (PurpleContact*)cnode;
6620 buddy = purple_contact_get_priority_buddy(contact);
6622 if (buddy_is_displayable(buddy))
6624 GtkTreeIter iter;
6626 if(!insert_node(list, cnode, &iter))
6627 return;
6629 gtknode = (struct _pidgin_blist_node *)cnode->ui_data;
6631 if(gtknode->contact_expanded) {
6632 GdkPixbuf *status;
6633 gchar *mark, *tmp;
6634 const gchar *fg_color, *font;
6635 GdkColor *color = NULL;
6636 PidginBlistTheme *theme;
6637 PidginThemeFont *pair;
6638 gboolean selected = (gtkblist->selected_node == cnode);
6640 mark = g_markup_escape_text(purple_contact_get_alias(contact), -1);
6642 theme = pidgin_blist_get_theme();
6643 if (theme == NULL)
6644 pair = NULL;
6645 else {
6646 pair = pidgin_blist_theme_get_contact_text_info(theme);
6647 color = pidgin_blist_theme_get_contact_color(theme);
6650 font = theme_font_get_face_default(pair, "");
6651 fg_color = selected ? NULL : theme_font_get_color_default(pair, NULL);
6653 if (fg_color) {
6654 tmp = g_strdup_printf("<span font_desc='%s' color='%s'>%s</span>",
6655 font, fg_color, mark);
6656 } else {
6657 tmp = g_strdup_printf("<span font_desc='%s'>%s</span>", font,
6658 mark);
6660 g_free(mark);
6661 mark = tmp;
6663 status = pidgin_blist_get_status_icon(cnode,
6664 biglist? PIDGIN_STATUS_ICON_LARGE : PIDGIN_STATUS_ICON_SMALL);
6666 gtk_tree_store_set(gtkblist->treemodel, &iter,
6667 STATUS_ICON_COLUMN, status,
6668 STATUS_ICON_VISIBLE_COLUMN, TRUE,
6669 NAME_COLUMN, mark,
6670 IDLE_COLUMN, NULL,
6671 IDLE_VISIBLE_COLUMN, FALSE,
6672 BGCOLOR_COLUMN, color,
6673 BUDDY_ICON_COLUMN, NULL,
6674 CONTACT_EXPANDER_COLUMN, TRUE,
6675 CONTACT_EXPANDER_VISIBLE_COLUMN, TRUE,
6676 GROUP_EXPANDER_VISIBLE_COLUMN, FALSE,
6677 -1);
6678 g_free(mark);
6679 if(status)
6680 g_object_unref(status);
6681 } else {
6682 buddy_node(buddy, &iter, cnode);
6684 } else {
6685 pidgin_blist_hide_node(list, cnode, TRUE);
6691 static void pidgin_blist_update_buddy(PurpleBuddyList *list, PurpleBlistNode *node, gboolean status_change)
6693 PurpleBuddy *buddy;
6694 struct _pidgin_blist_node *gtkparentnode;
6696 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
6698 if (node->parent == NULL)
6699 return;
6701 buddy = (PurpleBuddy*)node;
6703 /* First things first, update the contact */
6704 pidgin_blist_update_contact(list, node);
6706 gtkparentnode = (struct _pidgin_blist_node *)node->parent->ui_data;
6708 if (gtkparentnode->contact_expanded && buddy_is_displayable(buddy))
6710 GtkTreeIter iter;
6712 if (!insert_node(list, node, &iter))
6713 return;
6715 buddy_node(buddy, &iter, node);
6717 } else {
6718 pidgin_blist_hide_node(list, node, TRUE);
6723 static void pidgin_blist_update_chat(PurpleBuddyList *list, PurpleBlistNode *node)
6725 PurpleChat *chat;
6727 g_return_if_fail(PURPLE_BLIST_NODE_IS_CHAT(node));
6729 if (editing_blist)
6730 return;
6732 /* First things first, update the group */
6733 pidgin_blist_update_group(list, node->parent);
6735 chat = (PurpleChat*)node;
6737 if(purple_account_is_connected(chat->account)) {
6738 GtkTreeIter iter;
6739 GdkPixbuf *status, *avatar, *emblem, *prpl_icon;
6740 const gchar *color, *font;
6741 gchar *mark, *tmp;
6742 gboolean showicons = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons");
6743 gboolean biglist = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons");
6744 PidginBlistNode *ui;
6745 PurpleConversation *conv;
6746 gboolean hidden = FALSE;
6747 GdkColor *bgcolor = NULL;
6748 PidginThemeFont *pair;
6749 PidginBlistTheme *theme;
6750 gboolean selected = (gtkblist->selected_node == node);
6751 gboolean nick_said = FALSE;
6753 if (!insert_node(list, node, &iter))
6754 return;
6756 ui = node->ui_data;
6757 conv = ui->conv.conv;
6758 if (conv && pidgin_conv_is_hidden(PIDGIN_CONVERSATION(conv))) {
6759 hidden = (ui->conv.flags & PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE);
6760 nick_said = (ui->conv.flags & PIDGIN_BLIST_CHAT_HAS_PENDING_MESSAGE_WITH_NICK);
6763 status = pidgin_blist_get_status_icon(node,
6764 biglist ? PIDGIN_STATUS_ICON_LARGE : PIDGIN_STATUS_ICON_SMALL);
6765 emblem = pidgin_blist_get_emblem(node);
6767 /* Speed it up if we don't want buddy icons. */
6768 if(showicons)
6769 avatar = pidgin_blist_get_buddy_icon(node, TRUE, FALSE);
6770 else
6771 avatar = NULL;
6773 mark = g_markup_escape_text(purple_chat_get_name(chat), -1);
6775 theme = pidgin_blist_get_theme();
6777 if (theme == NULL)
6778 pair = NULL;
6779 else if (nick_said)
6780 pair = pidgin_blist_theme_get_unread_message_nick_said_text_info(theme);
6781 else if (hidden)
6782 pair = pidgin_blist_theme_get_unread_message_text_info(theme);
6783 else pair = pidgin_blist_theme_get_online_text_info(theme);
6786 font = theme_font_get_face_default(pair, "");
6787 if (selected || !(color = theme_font_get_color_default(pair, NULL)))
6788 /* nick_said color is the same as gtkconv:tab-label-attention */
6789 color = (nick_said ? "#006aff" : NULL);
6791 if (color) {
6792 tmp = g_strdup_printf("<span font_desc='%s' color='%s' weight='%s'>%s</span>",
6793 font, color, hidden ? "bold" : "normal", mark);
6794 } else {
6795 tmp = g_strdup_printf("<span font_desc='%s' weight='%s'>%s</span>",
6796 font, hidden ? "bold" : "normal", mark);
6798 g_free(mark);
6799 mark = tmp;
6801 prpl_icon = pidgin_create_prpl_icon(chat->account, PIDGIN_PRPL_ICON_SMALL);
6803 if (theme != NULL)
6804 bgcolor = pidgin_blist_theme_get_contact_color(theme);
6806 gtk_tree_store_set(gtkblist->treemodel, &iter,
6807 STATUS_ICON_COLUMN, status,
6808 STATUS_ICON_VISIBLE_COLUMN, TRUE,
6809 BUDDY_ICON_COLUMN, avatar ? avatar : gtkblist->empty_avatar,
6810 BUDDY_ICON_VISIBLE_COLUMN, showicons,
6811 EMBLEM_COLUMN, emblem,
6812 EMBLEM_VISIBLE_COLUMN, emblem != NULL,
6813 PROTOCOL_ICON_COLUMN, prpl_icon,
6814 PROTOCOL_ICON_VISIBLE_COLUMN, purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons"),
6815 NAME_COLUMN, mark,
6816 BGCOLOR_COLUMN, bgcolor,
6817 GROUP_EXPANDER_VISIBLE_COLUMN, FALSE,
6818 -1);
6820 g_free(mark);
6821 if(emblem)
6822 g_object_unref(emblem);
6823 if(status)
6824 g_object_unref(status);
6825 if(avatar)
6826 g_object_unref(avatar);
6827 if(prpl_icon)
6828 g_object_unref(prpl_icon);
6830 } else {
6831 pidgin_blist_hide_node(list, node, TRUE);
6835 static void pidgin_blist_update(PurpleBuddyList *list, PurpleBlistNode *node)
6837 if (list)
6838 gtkblist = PIDGIN_BLIST(list);
6839 if(!gtkblist || !gtkblist->treeview || !node)
6840 return;
6842 if (node->ui_data == NULL)
6843 pidgin_blist_new_node(node);
6845 switch(node->type) {
6846 case PURPLE_BLIST_GROUP_NODE:
6847 pidgin_blist_update_group(list, node);
6848 break;
6849 case PURPLE_BLIST_CONTACT_NODE:
6850 pidgin_blist_update_contact(list, node);
6851 break;
6852 case PURPLE_BLIST_BUDDY_NODE:
6853 pidgin_blist_update_buddy(list, node, TRUE);
6854 break;
6855 case PURPLE_BLIST_CHAT_NODE:
6856 pidgin_blist_update_chat(list, node);
6857 break;
6858 case PURPLE_BLIST_OTHER_NODE:
6859 return;
6864 static void pidgin_blist_destroy(PurpleBuddyList *list)
6866 PidginBuddyListPrivate *priv;
6868 if (!list || !list->ui_data)
6869 return;
6871 g_return_if_fail(list->ui_data == gtkblist);
6873 purple_signals_disconnect_by_handle(gtkblist);
6875 if (gtkblist->headline_close)
6876 gdk_pixbuf_unref(gtkblist->headline_close);
6878 gtk_widget_destroy(gtkblist->window);
6880 pidgin_blist_tooltip_destroy();
6882 if (gtkblist->refresh_timer)
6883 purple_timeout_remove(gtkblist->refresh_timer);
6884 if (gtkblist->timeout)
6885 g_source_remove(gtkblist->timeout);
6886 if (gtkblist->drag_timeout)
6887 g_source_remove(gtkblist->drag_timeout);
6889 g_hash_table_destroy(gtkblist->connection_errors);
6890 gtkblist->refresh_timer = 0;
6891 gtkblist->timeout = 0;
6892 gtkblist->drag_timeout = 0;
6893 gtkblist->window = gtkblist->vbox = gtkblist->treeview = NULL;
6894 g_object_unref(G_OBJECT(gtkblist->treemodel));
6895 gtkblist->treemodel = NULL;
6896 g_object_unref(G_OBJECT(gtkblist->ift));
6897 g_object_unref(G_OBJECT(gtkblist->empty_avatar));
6899 gdk_cursor_unref(gtkblist->hand_cursor);
6900 gdk_cursor_unref(gtkblist->arrow_cursor);
6901 gtkblist->hand_cursor = NULL;
6902 gtkblist->arrow_cursor = NULL;
6904 priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
6905 if (priv->current_theme)
6906 g_object_unref(priv->current_theme);
6907 g_free(priv);
6909 g_free(gtkblist);
6910 accountmenu = NULL;
6911 gtkblist = NULL;
6912 purple_prefs_disconnect_by_handle(pidgin_blist_get_handle());
6915 static void pidgin_blist_set_visible(PurpleBuddyList *list, gboolean show)
6917 if (!(gtkblist && gtkblist->window))
6918 return;
6920 if (show) {
6921 if(!PIDGIN_WINDOW_ICONIFIED(gtkblist->window) && !GTK_WIDGET_VISIBLE(gtkblist->window))
6922 purple_signal_emit(pidgin_blist_get_handle(), "gtkblist-unhiding", gtkblist);
6923 pidgin_blist_restore_position();
6924 gtk_window_present(GTK_WINDOW(gtkblist->window));
6925 } else {
6926 if(visibility_manager_count) {
6927 purple_signal_emit(pidgin_blist_get_handle(), "gtkblist-hiding", gtkblist);
6928 gtk_widget_hide(gtkblist->window);
6929 } else {
6930 if (!GTK_WIDGET_VISIBLE(gtkblist->window))
6931 gtk_widget_show(gtkblist->window);
6932 gtk_window_iconify(GTK_WINDOW(gtkblist->window));
6937 static GList *
6938 groups_tree(void)
6940 static GList *list = NULL;
6941 char *tmp2;
6942 PurpleGroup *g;
6943 PurpleBlistNode *gnode;
6945 g_list_free(list);
6946 list = NULL;
6948 if (purple_get_blist()->root == NULL)
6950 list = g_list_append(list, (gpointer)_("Buddies"));
6952 else
6954 for (gnode = purple_get_blist()->root;
6955 gnode != NULL;
6956 gnode = gnode->next)
6958 if (PURPLE_BLIST_NODE_IS_GROUP(gnode))
6960 g = (PurpleGroup *)gnode;
6961 tmp2 = g->name;
6962 list = g_list_append(list, tmp2);
6967 return list;
6970 static void
6971 add_buddy_select_account_cb(GObject *w, PurpleAccount *account,
6972 PidginAddBuddyData *data)
6974 /* Save our account */
6975 data->rq_data.account = account;
6978 static void
6979 destroy_add_buddy_dialog_cb(GtkWidget *win, PidginAddBuddyData *data)
6981 g_free(data);
6984 static void
6985 add_buddy_cb(GtkWidget *w, int resp, PidginAddBuddyData *data)
6987 const char *grp, *who, *whoalias;
6988 PurpleAccount *account;
6989 PurpleGroup *g;
6990 PurpleBuddy *b;
6991 PurpleConversation *c;
6992 PurpleBuddyIcon *icon;
6994 if (resp == GTK_RESPONSE_OK)
6996 who = gtk_entry_get_text(GTK_ENTRY(data->entry));
6997 grp = pidgin_text_combo_box_entry_get_text(data->combo);
6998 whoalias = gtk_entry_get_text(GTK_ENTRY(data->entry_for_alias));
6999 if (*whoalias == '\0')
7000 whoalias = NULL;
7002 account = data->rq_data.account;
7004 g = NULL;
7005 if ((grp != NULL) && (*grp != '\0'))
7007 if ((g = purple_find_group(grp)) == NULL)
7009 g = purple_group_new(grp);
7010 purple_blist_add_group(g, NULL);
7013 b = purple_find_buddy_in_group(account, who, g);
7015 else if ((b = purple_find_buddy(account, who)) != NULL)
7017 g = purple_buddy_get_group(b);
7020 if (b == NULL)
7022 b = purple_buddy_new(account, who, whoalias);
7023 purple_blist_add_buddy(b, NULL, g, NULL);
7026 purple_account_add_buddy(account, b);
7028 /* Offer to merge people with the same alias. */
7029 if (whoalias != NULL && g != NULL)
7030 gtk_blist_auto_personize((PurpleBlistNode *)g, whoalias);
7033 * XXX
7034 * It really seems like it would be better if the call to
7035 * purple_account_add_buddy() and purple_conversation_update() were done in
7036 * blist.c, possibly in the purple_blist_add_buddy() function. Maybe
7037 * purple_account_add_buddy() should be renamed to
7038 * purple_blist_add_new_buddy() or something, and have it call
7039 * purple_blist_add_buddy() after it creates it. --Mark
7041 * No that's not good. blist.c should only deal with adding nodes to the
7042 * local list. We need a new, non-gtk file that calls both
7043 * purple_account_add_buddy and purple_blist_add_buddy().
7044 * Or something. --Mark
7047 c = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, who, data->rq_data.account);
7048 if (c != NULL) {
7049 icon = purple_conv_im_get_icon(PURPLE_CONV_IM(c));
7050 if (icon != NULL)
7051 purple_buddy_icon_update(icon);
7055 gtk_widget_destroy(data->rq_data.window);
7058 static void
7059 pidgin_blist_request_add_buddy(PurpleAccount *account, const char *username,
7060 const char *group, const char *alias)
7062 PidginAddBuddyData *data = g_new0(PidginAddBuddyData, 1);
7064 make_blist_request_dialog((PidginBlistRequestData *)data,
7065 (account != NULL
7066 ? account : purple_connection_get_account(purple_connections_get_all()->data)),
7067 _("Add Buddy"), "add_buddy",
7068 _("Add a buddy.\n"),
7069 G_CALLBACK(add_buddy_select_account_cb), NULL,
7070 G_CALLBACK(add_buddy_cb));
7071 gtk_dialog_add_buttons(GTK_DIALOG(data->rq_data.window),
7072 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
7073 GTK_STOCK_ADD, GTK_RESPONSE_OK,
7074 NULL);
7075 gtk_dialog_set_default_response(GTK_DIALOG(data->rq_data.window),
7076 GTK_RESPONSE_OK);
7078 g_signal_connect(G_OBJECT(data->rq_data.window), "destroy",
7079 G_CALLBACK(destroy_add_buddy_dialog_cb), data);
7081 data->entry = gtk_entry_new();
7083 pidgin_add_widget_to_vbox(data->rq_data.vbox, _("Buddy's _username:"),
7084 data->rq_data.sg, data->entry, TRUE, NULL);
7085 gtk_widget_grab_focus(data->entry);
7087 if (username != NULL)
7088 gtk_entry_set_text(GTK_ENTRY(data->entry), username);
7089 else
7090 gtk_dialog_set_response_sensitive(GTK_DIALOG(data->rq_data.window),
7091 GTK_RESPONSE_OK, FALSE);
7093 gtk_entry_set_activates_default (GTK_ENTRY(data->entry), TRUE);
7095 g_signal_connect(G_OBJECT(data->entry), "changed",
7096 G_CALLBACK(pidgin_set_sensitive_if_input),
7097 data->rq_data.window);
7099 data->entry_for_alias = gtk_entry_new();
7100 pidgin_add_widget_to_vbox(data->rq_data.vbox, _("(Optional) A_lias:"),
7101 data->rq_data.sg, data->entry_for_alias, TRUE,
7102 NULL);
7104 if (alias != NULL)
7105 gtk_entry_set_text(GTK_ENTRY(data->entry_for_alias), alias);
7107 if (username != NULL)
7108 gtk_widget_grab_focus(GTK_WIDGET(data->entry_for_alias));
7110 data->combo = pidgin_text_combo_box_entry_new(group, groups_tree());
7111 pidgin_add_widget_to_vbox(data->rq_data.vbox, _("Add buddy to _group:"),
7112 data->rq_data.sg, data->combo, TRUE, NULL);
7114 gtk_widget_show_all(data->rq_data.window);
7117 static void
7118 add_chat_cb(GtkWidget *w, PidginAddChatData *data)
7120 GList *tmp;
7121 PurpleChat *chat;
7122 GHashTable *components;
7124 components = g_hash_table_new_full(g_str_hash, g_str_equal,
7125 g_free, g_free);
7127 for (tmp = data->chat_data.entries; tmp; tmp = tmp->next)
7129 if (g_object_get_data(tmp->data, "is_spin"))
7131 g_hash_table_replace(components,
7132 g_strdup(g_object_get_data(tmp->data, "identifier")),
7133 g_strdup_printf("%d",
7134 gtk_spin_button_get_value_as_int(tmp->data)));
7136 else
7138 const char *value = gtk_entry_get_text(tmp->data);
7140 if (*value != '\0')
7141 g_hash_table_replace(components,
7142 g_strdup(g_object_get_data(tmp->data, "identifier")),
7143 g_strdup(value));
7147 chat = purple_chat_new(data->chat_data.rq_data.account,
7148 gtk_entry_get_text(GTK_ENTRY(data->alias_entry)),
7149 components);
7151 if (chat != NULL) {
7152 PurpleGroup *group;
7153 const char *group_name;
7155 group_name = pidgin_text_combo_box_entry_get_text(data->group_combo);
7157 group = NULL;
7158 if ((group_name != NULL) && (*group_name != '\0') &&
7159 ((group = purple_find_group(group_name)) == NULL))
7161 group = purple_group_new(group_name);
7162 purple_blist_add_group(group, NULL);
7165 purple_blist_add_chat(chat, group, NULL);
7167 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(data->autojoin)))
7168 purple_blist_node_set_bool((PurpleBlistNode*)chat, "gtk-autojoin", TRUE);
7170 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(data->persistent)))
7171 purple_blist_node_set_bool((PurpleBlistNode*)chat, "gtk-persistent", TRUE);
7174 gtk_widget_destroy(data->chat_data.rq_data.window);
7175 g_free(data->chat_data.default_chat_name);
7176 g_list_free(data->chat_data.entries);
7177 g_free(data);
7180 static void
7181 add_chat_resp_cb(GtkWidget *w, int resp, PidginAddChatData *data)
7183 if (resp == GTK_RESPONSE_OK)
7185 add_chat_cb(NULL, data);
7187 else if (resp == 1)
7189 pidgin_roomlist_dialog_show_with_account(data->chat_data.rq_data.account);
7191 else
7193 gtk_widget_destroy(data->chat_data.rq_data.window);
7194 g_free(data->chat_data.default_chat_name);
7195 g_list_free(data->chat_data.entries);
7196 g_free(data);
7200 static void
7201 pidgin_blist_request_add_chat(PurpleAccount *account, PurpleGroup *group,
7202 const char *alias, const char *name)
7204 PidginAddChatData *data;
7205 GList *l;
7206 PurpleConnection *gc;
7207 GtkBox *vbox;
7209 if (account != NULL) {
7210 gc = purple_account_get_connection(account);
7212 if (PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->join_chat == NULL) {
7213 purple_notify_error(gc, NULL, _("This protocol does not support chat rooms."), NULL);
7214 return;
7216 } else {
7217 /* Find an account with chat capabilities */
7218 for (l = purple_connections_get_all(); l != NULL; l = l->next) {
7219 gc = (PurpleConnection *)l->data;
7221 if (PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->join_chat != NULL) {
7222 account = purple_connection_get_account(gc);
7223 break;
7227 if (account == NULL) {
7228 purple_notify_error(NULL, NULL,
7229 _("You are not currently signed on with any "
7230 "protocols that have the ability to chat."), NULL);
7231 return;
7235 data = g_new0(PidginAddChatData, 1);
7236 vbox = GTK_BOX(make_blist_request_dialog((PidginBlistRequestData *)data, account,
7237 _("Add Chat"), "add_chat",
7238 _("Please enter an alias, and the appropriate information "
7239 "about the chat you would like to add to your buddy list.\n"),
7240 G_CALLBACK(chat_select_account_cb), chat_account_filter_func,
7241 G_CALLBACK(add_chat_resp_cb)));
7242 gtk_dialog_add_buttons(GTK_DIALOG(data->chat_data.rq_data.window),
7243 _("Room List"), 1,
7244 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
7245 GTK_STOCK_ADD, GTK_RESPONSE_OK,
7246 NULL);
7247 gtk_dialog_set_default_response(GTK_DIALOG(data->chat_data.rq_data.window),
7248 GTK_RESPONSE_OK);
7250 data->chat_data.default_chat_name = g_strdup(name);
7252 rebuild_chat_entries((PidginChatData *)data, name);
7254 data->alias_entry = gtk_entry_new();
7255 if (alias != NULL)
7256 gtk_entry_set_text(GTK_ENTRY(data->alias_entry), alias);
7257 gtk_entry_set_activates_default(GTK_ENTRY(data->alias_entry), TRUE);
7259 pidgin_add_widget_to_vbox(GTK_BOX(vbox), _("A_lias:"),
7260 data->chat_data.rq_data.sg, data->alias_entry,
7261 TRUE, NULL);
7262 if (name != NULL)
7263 gtk_widget_grab_focus(data->alias_entry);
7265 data->group_combo = pidgin_text_combo_box_entry_new(group ? group->name : NULL, groups_tree());
7266 pidgin_add_widget_to_vbox(GTK_BOX(vbox), _("_Group:"),
7267 data->chat_data.rq_data.sg, data->group_combo,
7268 TRUE, NULL);
7270 data->autojoin = gtk_check_button_new_with_mnemonic(_("Auto_join when account connects."));
7271 data->persistent = gtk_check_button_new_with_mnemonic(_("_Remain in chat after window is closed."));
7272 gtk_box_pack_start(GTK_BOX(vbox), data->autojoin, FALSE, FALSE, 0);
7273 gtk_box_pack_start(GTK_BOX(vbox), data->persistent, FALSE, FALSE, 0);
7275 gtk_widget_show_all(data->chat_data.rq_data.window);
7278 static void
7279 add_group_cb(PurpleConnection *gc, const char *group_name)
7281 PurpleGroup *group;
7283 if ((group_name == NULL) || (*group_name == '\0'))
7284 return;
7286 group = purple_group_new(group_name);
7287 purple_blist_add_group(group, NULL);
7290 static void
7291 pidgin_blist_request_add_group(void)
7293 purple_request_input(NULL, _("Add Group"), NULL,
7294 _("Please enter the name of the group to be added."),
7295 NULL, FALSE, FALSE, NULL,
7296 _("Add"), G_CALLBACK(add_group_cb),
7297 _("Cancel"), NULL,
7298 NULL, NULL, NULL,
7299 NULL);
7302 void
7303 pidgin_blist_toggle_visibility()
7305 if (gtkblist && gtkblist->window) {
7306 if (GTK_WIDGET_VISIBLE(gtkblist->window)) {
7307 /* make the buddy list visible if it is iconified or if it is
7308 * obscured and not currently focused (the focus part ensures
7309 * that we do something reasonable if the buddy list is obscured
7310 * by a window set to always be on top), otherwise hide the
7311 * buddy list
7313 purple_blist_set_visible(PIDGIN_WINDOW_ICONIFIED(gtkblist->window) ||
7314 ((gtk_blist_visibility != GDK_VISIBILITY_UNOBSCURED) &&
7315 !gtk_blist_focused));
7316 } else {
7317 purple_blist_set_visible(TRUE);
7322 void
7323 pidgin_blist_visibility_manager_add()
7325 visibility_manager_count++;
7326 purple_debug_info("gtkblist", "added visibility manager: %d\n", visibility_manager_count);
7329 void
7330 pidgin_blist_visibility_manager_remove()
7332 if (visibility_manager_count)
7333 visibility_manager_count--;
7334 if (!visibility_manager_count)
7335 purple_blist_set_visible(TRUE);
7336 purple_debug_info("gtkblist", "removed visibility manager: %d\n", visibility_manager_count);
7339 void pidgin_blist_add_alert(GtkWidget *widget)
7341 gtk_container_add(GTK_CONTAINER(gtkblist->scrollbook), widget);
7342 set_urgent();
7345 void
7346 pidgin_blist_set_headline(const char *text, GdkPixbuf *pixbuf, GCallback callback,
7347 gpointer user_data, GDestroyNotify destroy)
7349 /* Destroy any existing headline first */
7350 if (gtkblist->headline_destroy)
7351 gtkblist->headline_destroy(gtkblist->headline_data);
7353 gtk_label_set_markup(GTK_LABEL(gtkblist->headline_label), text);
7354 gtk_image_set_from_pixbuf(GTK_IMAGE(gtkblist->headline_image), pixbuf);
7356 gtkblist->headline_callback = callback;
7357 gtkblist->headline_data = user_data;
7358 gtkblist->headline_destroy = destroy;
7359 if (text != NULL || pixbuf != NULL) {
7360 set_urgent();
7361 gtk_widget_show_all(gtkblist->headline_hbox);
7362 } else {
7363 gtk_widget_hide(gtkblist->headline_hbox);
7368 static void
7369 set_urgent(void)
7371 if (gtkblist->window && !GTK_WIDGET_HAS_FOCUS(gtkblist->window))
7372 pidgin_set_urgent(GTK_WINDOW(gtkblist->window), TRUE);
7375 static PurpleBlistUiOps blist_ui_ops =
7377 pidgin_blist_new_list,
7378 pidgin_blist_new_node,
7379 pidgin_blist_show,
7380 pidgin_blist_update,
7381 pidgin_blist_remove,
7382 pidgin_blist_destroy,
7383 pidgin_blist_set_visible,
7384 pidgin_blist_request_add_buddy,
7385 pidgin_blist_request_add_chat,
7386 pidgin_blist_request_add_group,
7387 NULL,
7388 NULL,
7389 NULL,
7390 NULL
7394 PurpleBlistUiOps *
7395 pidgin_blist_get_ui_ops(void)
7397 return &blist_ui_ops;
7400 PidginBuddyList *pidgin_blist_get_default_gtk_blist()
7402 return gtkblist;
7405 static gboolean autojoin_cb(PurpleConnection *gc, gpointer data)
7407 PurpleAccount *account = purple_connection_get_account(gc);
7408 PurpleBlistNode *gnode, *cnode;
7409 for(gnode = purple_get_blist()->root; gnode; gnode = gnode->next)
7411 if(!PURPLE_BLIST_NODE_IS_GROUP(gnode))
7412 continue;
7413 for(cnode = gnode->child; cnode; cnode = cnode->next)
7415 PurpleChat *chat;
7417 if(!PURPLE_BLIST_NODE_IS_CHAT(cnode))
7418 continue;
7420 chat = (PurpleChat *)cnode;
7422 if(chat->account != account)
7423 continue;
7425 if (purple_blist_node_get_bool((PurpleBlistNode*)chat, "gtk-autojoin"))
7426 serv_join_chat(gc, chat->components);
7430 /* Stop processing; we handled the autojoins. */
7431 return TRUE;
7434 void *
7435 pidgin_blist_get_handle() {
7436 static int handle;
7438 return &handle;
7441 static gboolean buddy_signonoff_timeout_cb(PurpleBuddy *buddy)
7443 struct _pidgin_blist_node *gtknode = ((PurpleBlistNode*)buddy)->ui_data;
7445 gtknode->recent_signonoff = FALSE;
7446 gtknode->recent_signonoff_timer = 0;
7448 pidgin_blist_update(NULL, (PurpleBlistNode*)buddy);
7450 return FALSE;
7453 static void buddy_signonoff_cb(PurpleBuddy *buddy)
7455 struct _pidgin_blist_node *gtknode;
7457 if(!((PurpleBlistNode*)buddy)->ui_data) {
7458 pidgin_blist_new_node((PurpleBlistNode*)buddy);
7461 gtknode = ((PurpleBlistNode*)buddy)->ui_data;
7463 gtknode->recent_signonoff = TRUE;
7465 if(gtknode->recent_signonoff_timer > 0)
7466 purple_timeout_remove(gtknode->recent_signonoff_timer);
7467 gtknode->recent_signonoff_timer = purple_timeout_add_seconds(10,
7468 (GSourceFunc)buddy_signonoff_timeout_cb, buddy);
7471 void
7472 pidgin_blist_set_theme(PidginBlistTheme *theme)
7474 PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
7475 PurpleBuddyList *list = purple_get_blist();
7477 if (theme != NULL)
7478 purple_prefs_set_string(PIDGIN_PREFS_ROOT "/blist/theme",
7479 purple_theme_get_name(PURPLE_THEME(theme)));
7480 else
7481 purple_prefs_set_string(PIDGIN_PREFS_ROOT "/blist/theme", "");
7483 if (priv->current_theme)
7484 g_object_unref(priv->current_theme);
7486 priv->current_theme = theme ? g_object_ref(theme) : NULL;
7488 pidgin_blist_build_layout(list);
7490 pidgin_blist_refresh(list);
7494 PidginBlistTheme *
7495 pidgin_blist_get_theme()
7497 PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
7499 return priv->current_theme;
7502 void pidgin_blist_init(void)
7504 void *gtk_blist_handle = pidgin_blist_get_handle();
7506 cached_emblems = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
7508 /* Initialize prefs */
7509 purple_prefs_add_none(PIDGIN_PREFS_ROOT "/blist");
7510 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons", TRUE);
7511 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/show_empty_groups", FALSE);
7512 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/show_idle_time", TRUE);
7513 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/show_offline_buddies", FALSE);
7514 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons", FALSE);
7515 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/list_visible", FALSE);
7516 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/list_maximized", FALSE);
7517 purple_prefs_add_string(PIDGIN_PREFS_ROOT "/blist/sort_type", "alphabetical");
7518 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/blist/x", 0);
7519 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/blist/y", 0);
7520 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/blist/width", 250); /* Golden ratio, baby */
7521 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/blist/height", 405); /* Golden ratio, baby */
7522 #if !GTK_CHECK_VERSION(2,14,0)
7523 /* This pref is used in pidgintooltip.c. */
7524 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/blist/tooltip_delay", 500);
7525 #endif
7526 purple_prefs_add_string(PIDGIN_PREFS_ROOT "/blist/theme", "");
7528 purple_theme_manager_register_type(g_object_new(PIDGIN_TYPE_BLIST_THEME_LOADER, "type", "blist", NULL));
7530 /* Register our signals */
7531 purple_signal_register(gtk_blist_handle, "gtkblist-hiding",
7532 purple_marshal_VOID__POINTER, NULL, 1,
7533 purple_value_new(PURPLE_TYPE_SUBTYPE,
7534 PURPLE_SUBTYPE_BLIST));
7536 purple_signal_register(gtk_blist_handle, "gtkblist-unhiding",
7537 purple_marshal_VOID__POINTER, NULL, 1,
7538 purple_value_new(PURPLE_TYPE_SUBTYPE,
7539 PURPLE_SUBTYPE_BLIST));
7541 purple_signal_register(gtk_blist_handle, "gtkblist-created",
7542 purple_marshal_VOID__POINTER, NULL, 1,
7543 purple_value_new(PURPLE_TYPE_SUBTYPE,
7544 PURPLE_SUBTYPE_BLIST));
7546 purple_signal_register(gtk_blist_handle, "drawing-tooltip",
7547 purple_marshal_VOID__POINTER_POINTER_UINT, NULL, 3,
7548 purple_value_new(PURPLE_TYPE_SUBTYPE,
7549 PURPLE_SUBTYPE_BLIST_NODE),
7550 purple_value_new_outgoing(PURPLE_TYPE_BOXED, "GString *"),
7551 purple_value_new(PURPLE_TYPE_BOOLEAN));
7553 purple_signal_register(gtk_blist_handle, "drawing-buddy",
7554 purple_marshal_POINTER__POINTER,
7555 purple_value_new(PURPLE_TYPE_STRING), 1,
7556 purple_value_new(PURPLE_TYPE_SUBTYPE,
7557 PURPLE_SUBTYPE_BLIST_BUDDY));
7559 purple_signal_connect(purple_blist_get_handle(), "buddy-signed-on",
7560 gtk_blist_handle, PURPLE_CALLBACK(buddy_signonoff_cb), NULL);
7561 purple_signal_connect(purple_blist_get_handle(), "buddy-signed-off",
7562 gtk_blist_handle, PURPLE_CALLBACK(buddy_signonoff_cb), NULL);
7563 purple_signal_connect(purple_blist_get_handle(), "buddy-privacy-changed",
7564 gtk_blist_handle, PURPLE_CALLBACK(pidgin_blist_update_privacy_cb), NULL);
7566 purple_signal_connect_priority(purple_connections_get_handle(), "autojoin",
7567 gtk_blist_handle, PURPLE_CALLBACK(autojoin_cb),
7568 NULL, PURPLE_SIGNAL_PRIORITY_HIGHEST);
7571 void
7572 pidgin_blist_uninit(void) {
7573 g_hash_table_destroy(cached_emblems);
7575 purple_signals_unregister_by_instance(pidgin_blist_get_handle());
7576 purple_signals_disconnect_by_handle(pidgin_blist_get_handle());
7579 /*********************************************************************
7580 * Buddy List sorting functions *
7581 *********************************************************************/
7583 GList *pidgin_blist_get_sort_methods()
7585 return pidgin_blist_sort_methods;
7588 void pidgin_blist_sort_method_reg(const char *id, const char *name, pidgin_blist_sort_function func)
7590 struct pidgin_blist_sort_method *method;
7592 g_return_if_fail(id != NULL);
7593 g_return_if_fail(name != NULL);
7594 g_return_if_fail(func != NULL);
7596 method = g_new0(struct pidgin_blist_sort_method, 1);
7597 method->id = g_strdup(id);
7598 method->name = g_strdup(name);
7599 method->func = func;
7600 pidgin_blist_sort_methods = g_list_append(pidgin_blist_sort_methods, method);
7601 pidgin_blist_update_sort_methods();
7604 void pidgin_blist_sort_method_unreg(const char *id)
7606 GList *l = pidgin_blist_sort_methods;
7608 g_return_if_fail(id != NULL);
7610 while(l) {
7611 struct pidgin_blist_sort_method *method = l->data;
7612 if(!strcmp(method->id, id)) {
7613 pidgin_blist_sort_methods = g_list_delete_link(pidgin_blist_sort_methods, l);
7614 g_free(method->id);
7615 g_free(method->name);
7616 g_free(method);
7617 break;
7619 l = l->next;
7621 pidgin_blist_update_sort_methods();
7624 void pidgin_blist_sort_method_set(const char *id){
7625 GList *l = pidgin_blist_sort_methods;
7627 if(!id)
7628 id = "none";
7630 while (l && strcmp(((struct pidgin_blist_sort_method*)l->data)->id, id))
7631 l = l->next;
7633 if (l) {
7634 current_sort_method = l->data;
7635 } else if (!current_sort_method) {
7636 pidgin_blist_sort_method_set("none");
7637 return;
7639 if (!strcmp(id, "none")) {
7640 redo_buddy_list(purple_get_blist(), TRUE, FALSE);
7641 } else {
7642 redo_buddy_list(purple_get_blist(), FALSE, FALSE);
7646 /******************************************
7647 ** Sort Methods
7648 ******************************************/
7650 static void sort_method_none(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter parent_iter, GtkTreeIter *cur, GtkTreeIter *iter)
7652 PurpleBlistNode *sibling = node->prev;
7653 GtkTreeIter sibling_iter;
7655 if (cur != NULL) {
7656 *iter = *cur;
7657 return;
7660 while (sibling && !get_iter_from_node(sibling, &sibling_iter)) {
7661 sibling = sibling->prev;
7664 gtk_tree_store_insert_after(gtkblist->treemodel, iter,
7665 node->parent ? &parent_iter : NULL,
7666 sibling ? &sibling_iter : NULL);
7669 static void sort_method_alphabetical(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter)
7671 GtkTreeIter more_z;
7673 const char *my_name;
7675 if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
7676 my_name = purple_contact_get_alias((PurpleContact*)node);
7677 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
7678 my_name = purple_chat_get_name((PurpleChat*)node);
7679 } else {
7680 sort_method_none(node, blist, groupiter, cur, iter);
7681 return;
7684 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(gtkblist->treemodel), &more_z, &groupiter)) {
7685 gtk_tree_store_insert(gtkblist->treemodel, iter, &groupiter, 0);
7686 return;
7689 do {
7690 PurpleBlistNode *n;
7691 const char *this_name;
7692 int cmp;
7694 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &more_z, NODE_COLUMN, &n, -1);
7696 if(PURPLE_BLIST_NODE_IS_CONTACT(n)) {
7697 this_name = purple_contact_get_alias((PurpleContact*)n);
7698 } else if(PURPLE_BLIST_NODE_IS_CHAT(n)) {
7699 this_name = purple_chat_get_name((PurpleChat*)n);
7700 } else {
7701 this_name = NULL;
7704 cmp = purple_utf8_strcasecmp(my_name, this_name);
7706 if(this_name && (cmp < 0 || (cmp == 0 && node < n))) {
7707 if(cur) {
7708 gtk_tree_store_move_before(gtkblist->treemodel, cur, &more_z);
7709 *iter = *cur;
7710 return;
7711 } else {
7712 gtk_tree_store_insert_before(gtkblist->treemodel, iter,
7713 &groupiter, &more_z);
7714 return;
7717 } while (gtk_tree_model_iter_next (GTK_TREE_MODEL(gtkblist->treemodel), &more_z));
7719 if(cur) {
7720 gtk_tree_store_move_before(gtkblist->treemodel, cur, NULL);
7721 *iter = *cur;
7722 return;
7723 } else {
7724 gtk_tree_store_append(gtkblist->treemodel, iter, &groupiter);
7725 return;
7729 static void sort_method_status(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter)
7731 GtkTreeIter more_z;
7733 PurpleBuddy *my_buddy, *this_buddy;
7735 if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
7736 my_buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
7737 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
7738 if (cur != NULL) {
7739 *iter = *cur;
7740 return;
7743 gtk_tree_store_append(gtkblist->treemodel, iter, &groupiter);
7744 return;
7745 } else {
7746 sort_method_none(node, blist, groupiter, cur, iter);
7747 return;
7751 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(gtkblist->treemodel), &more_z, &groupiter)) {
7752 gtk_tree_store_insert(gtkblist->treemodel, iter, &groupiter, 0);
7753 return;
7756 do {
7757 PurpleBlistNode *n;
7758 gint name_cmp;
7759 gint presence_cmp;
7761 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &more_z, NODE_COLUMN, &n, -1);
7763 if(PURPLE_BLIST_NODE_IS_CONTACT(n)) {
7764 this_buddy = purple_contact_get_priority_buddy((PurpleContact*)n);
7765 } else {
7766 this_buddy = NULL;
7769 name_cmp = purple_utf8_strcasecmp(
7770 purple_contact_get_alias(purple_buddy_get_contact(my_buddy)),
7771 (this_buddy
7772 ? purple_contact_get_alias(purple_buddy_get_contact(this_buddy))
7773 : NULL));
7775 presence_cmp = purple_presence_compare(
7776 purple_buddy_get_presence(my_buddy),
7777 this_buddy ? purple_buddy_get_presence(this_buddy) : NULL);
7779 if (this_buddy == NULL ||
7780 (presence_cmp < 0 ||
7781 (presence_cmp == 0 &&
7782 (name_cmp < 0 || (name_cmp == 0 && node < n)))))
7784 if (cur != NULL)
7786 gtk_tree_store_move_before(gtkblist->treemodel, cur, &more_z);
7787 *iter = *cur;
7788 return;
7790 else
7792 gtk_tree_store_insert_before(gtkblist->treemodel, iter,
7793 &groupiter, &more_z);
7794 return;
7798 while (gtk_tree_model_iter_next(GTK_TREE_MODEL(gtkblist->treemodel),
7799 &more_z));
7801 if (cur) {
7802 gtk_tree_store_move_before(gtkblist->treemodel, cur, NULL);
7803 *iter = *cur;
7804 return;
7805 } else {
7806 gtk_tree_store_append(gtkblist->treemodel, iter, &groupiter);
7807 return;
7811 static void sort_method_log_activity(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter)
7813 GtkTreeIter more_z;
7815 int activity_score = 0, this_log_activity_score = 0;
7816 const char *buddy_name, *this_buddy_name;
7818 if(cur && (gtk_tree_model_iter_n_children(GTK_TREE_MODEL(gtkblist->treemodel), &groupiter) == 1)) {
7819 *iter = *cur;
7820 return;
7823 if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
7824 PurpleBlistNode *n;
7825 PurpleBuddy *buddy;
7826 for (n = node->child; n; n = n->next) {
7827 buddy = (PurpleBuddy*)n;
7828 activity_score += purple_log_get_activity_score(PURPLE_LOG_IM, buddy->name, buddy->account);
7830 buddy_name = purple_contact_get_alias((PurpleContact*)node);
7831 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
7832 /* we don't have a reliable way of getting the log filename
7833 * from the chat info in the blist, yet */
7834 if (cur != NULL) {
7835 *iter = *cur;
7836 return;
7839 gtk_tree_store_append(gtkblist->treemodel, iter, &groupiter);
7840 return;
7841 } else {
7842 sort_method_none(node, blist, groupiter, cur, iter);
7843 return;
7847 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(gtkblist->treemodel), &more_z, &groupiter)) {
7848 gtk_tree_store_insert(gtkblist->treemodel, iter, &groupiter, 0);
7849 return;
7852 do {
7853 PurpleBlistNode *n;
7854 PurpleBlistNode *n2;
7855 PurpleBuddy *buddy;
7856 int cmp;
7858 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &more_z, NODE_COLUMN, &n, -1);
7859 this_log_activity_score = 0;
7861 if(PURPLE_BLIST_NODE_IS_CONTACT(n)) {
7862 for (n2 = n->child; n2; n2 = n2->next) {
7863 buddy = (PurpleBuddy*)n2;
7864 this_log_activity_score += purple_log_get_activity_score(PURPLE_LOG_IM, buddy->name, buddy->account);
7866 this_buddy_name = purple_contact_get_alias((PurpleContact*)n);
7867 } else {
7868 this_buddy_name = NULL;
7871 cmp = purple_utf8_strcasecmp(buddy_name, this_buddy_name);
7873 if (!PURPLE_BLIST_NODE_IS_CONTACT(n) || activity_score > this_log_activity_score ||
7874 ((activity_score == this_log_activity_score) &&
7875 (cmp < 0 || (cmp == 0 && node < n)))) {
7876 if (cur != NULL) {
7877 gtk_tree_store_move_before(gtkblist->treemodel, cur, &more_z);
7878 *iter = *cur;
7879 return;
7880 } else {
7881 gtk_tree_store_insert_before(gtkblist->treemodel, iter,
7882 &groupiter, &more_z);
7883 return;
7886 } while (gtk_tree_model_iter_next (GTK_TREE_MODEL(gtkblist->treemodel), &more_z));
7888 if (cur != NULL) {
7889 gtk_tree_store_move_before(gtkblist->treemodel, cur, NULL);
7890 *iter = *cur;
7891 return;
7892 } else {
7893 gtk_tree_store_append(gtkblist->treemodel, iter, &groupiter);
7894 return;
7898 static void
7899 plugin_act(GtkObject *obj, PurplePluginAction *pam)
7901 if (pam && pam->callback)
7902 pam->callback(pam);
7905 static void
7906 build_plugin_actions(GtkWidget *menu, PurplePlugin *plugin,
7907 gpointer context)
7909 GtkWidget *menuitem;
7910 PurplePluginAction *action = NULL;
7911 GList *actions, *l;
7913 actions = PURPLE_PLUGIN_ACTIONS(plugin, context);
7915 for (l = actions; l != NULL; l = l->next)
7917 if (l->data)
7919 action = (PurplePluginAction *) l->data;
7920 action->plugin = plugin;
7921 action->context = context;
7923 menuitem = gtk_menu_item_new_with_label(action->label);
7924 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
7926 g_signal_connect(G_OBJECT(menuitem), "activate",
7927 G_CALLBACK(plugin_act), action);
7928 g_object_set_data_full(G_OBJECT(menuitem), "plugin_action",
7929 action,
7930 (GDestroyNotify)purple_plugin_action_free);
7931 gtk_widget_show(menuitem);
7933 else
7934 pidgin_separator(menu);
7937 g_list_free(actions);
7940 static void
7941 modify_account_cb(GtkWidget *widget, gpointer data)
7943 pidgin_account_dialog_show(PIDGIN_MODIFY_ACCOUNT_DIALOG, data);
7946 static void
7947 enable_account_cb(GtkCheckMenuItem *widget, gpointer data)
7949 PurpleAccount *account = data;
7950 const PurpleSavedStatus *saved_status;
7952 saved_status = purple_savedstatus_get_current();
7953 purple_savedstatus_activate_for_account(saved_status, account);
7955 purple_account_set_enabled(account, PIDGIN_UI, TRUE);
7958 static void
7959 disable_account_cb(GtkCheckMenuItem *widget, gpointer data)
7961 PurpleAccount *account = data;
7963 purple_account_set_enabled(account, PIDGIN_UI, FALSE);
7968 void
7969 pidgin_blist_update_accounts_menu(void)
7971 GtkWidget *menuitem = NULL, *submenu = NULL;
7972 GtkAccelGroup *accel_group = NULL;
7973 GList *l = NULL, *accounts = NULL;
7974 gboolean disabled_accounts = FALSE;
7975 gboolean enabled_accounts = FALSE;
7977 if (accountmenu == NULL)
7978 return;
7980 /* Clear the old Accounts menu */
7981 for (l = gtk_container_get_children(GTK_CONTAINER(accountmenu)); l; l = g_list_delete_link(l, l)) {
7982 menuitem = l->data;
7984 if (menuitem != gtk_item_factory_get_widget(gtkblist->ift, N_("/Accounts/Manage Accounts")))
7985 gtk_widget_destroy(menuitem);
7988 for (accounts = purple_accounts_get_all(); accounts; accounts = accounts->next) {
7989 char *buf = NULL;
7990 GtkWidget *image = NULL;
7991 PurpleAccount *account = NULL;
7992 GdkPixbuf *pixbuf = NULL;
7994 account = accounts->data;
7996 if(!purple_account_get_enabled(account, PIDGIN_UI)) {
7997 if (!disabled_accounts) {
7998 menuitem = gtk_menu_item_new_with_label(_("Enable Account"));
7999 gtk_menu_shell_append(GTK_MENU_SHELL(accountmenu), menuitem);
8001 submenu = gtk_menu_new();
8002 gtk_menu_set_accel_group(GTK_MENU(submenu), accel_group);
8003 gtk_menu_set_accel_path(GTK_MENU(submenu), N_("<PurpleMain>/Accounts/Enable Account"));
8004 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
8006 disabled_accounts = TRUE;
8009 buf = g_strconcat(purple_account_get_username(account), " (",
8010 purple_account_get_protocol_name(account), ")", NULL);
8011 menuitem = gtk_image_menu_item_new_with_label(buf);
8012 g_free(buf);
8013 pixbuf = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL);
8014 if (pixbuf != NULL)
8016 if (!purple_account_is_connected(account))
8017 gdk_pixbuf_saturate_and_pixelate(pixbuf, pixbuf, 0.0, FALSE);
8018 image = gtk_image_new_from_pixbuf(pixbuf);
8019 g_object_unref(G_OBJECT(pixbuf));
8020 gtk_widget_show(image);
8021 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image);
8023 g_signal_connect(G_OBJECT(menuitem), "activate",
8024 G_CALLBACK(enable_account_cb), account);
8025 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), menuitem);
8026 } else {
8027 enabled_accounts = TRUE;
8031 if (!enabled_accounts) {
8032 gtk_widget_show_all(accountmenu);
8033 return;
8036 pidgin_separator(accountmenu);
8037 accel_group = gtk_menu_get_accel_group(GTK_MENU(accountmenu));
8039 for (accounts = purple_accounts_get_all(); accounts; accounts = accounts->next) {
8040 char *buf = NULL;
8041 char *accel_path_buf = NULL;
8042 GtkWidget *image = NULL;
8043 PurpleConnection *gc = NULL;
8044 PurpleAccount *account = NULL;
8045 GdkPixbuf *pixbuf = NULL;
8046 PurplePlugin *plugin = NULL;
8047 PurplePluginProtocolInfo *prpl_info;
8049 account = accounts->data;
8051 if (!purple_account_get_enabled(account, PIDGIN_UI))
8052 continue;
8054 buf = g_strconcat(purple_account_get_username(account), " (",
8055 purple_account_get_protocol_name(account), ")", NULL);
8056 menuitem = gtk_image_menu_item_new_with_label(buf);
8057 accel_path_buf = g_strconcat(N_("<PurpleMain>/Accounts/"), buf, NULL);
8058 g_free(buf);
8059 pixbuf = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL);
8060 if (pixbuf != NULL) {
8061 if (!purple_account_is_connected(account))
8062 gdk_pixbuf_saturate_and_pixelate(pixbuf, pixbuf,
8063 0.0, FALSE);
8064 image = gtk_image_new_from_pixbuf(pixbuf);
8065 g_object_unref(G_OBJECT(pixbuf));
8066 gtk_widget_show(image);
8067 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image);
8069 gtk_menu_shell_append(GTK_MENU_SHELL(accountmenu), menuitem);
8071 submenu = gtk_menu_new();
8072 gtk_menu_set_accel_group(GTK_MENU(submenu), accel_group);
8073 gtk_menu_set_accel_path(GTK_MENU(submenu), accel_path_buf);
8074 g_free(accel_path_buf);
8075 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
8078 menuitem = gtk_menu_item_new_with_mnemonic(_("_Edit Account"));
8079 g_signal_connect(G_OBJECT(menuitem), "activate",
8080 G_CALLBACK(modify_account_cb), account);
8081 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), menuitem);
8083 pidgin_separator(submenu);
8085 gc = purple_account_get_connection(account);
8086 plugin = gc && PURPLE_CONNECTION_IS_CONNECTED(gc) ? gc->prpl : NULL;
8087 prpl_info = plugin ? PURPLE_PLUGIN_PROTOCOL_INFO(plugin) : NULL;
8089 if (prpl_info &&
8090 (PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, get_moods) ||
8091 PURPLE_PLUGIN_HAS_ACTIONS(plugin))) {
8092 if (PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, get_moods) &&
8093 gc->flags & PURPLE_CONNECTION_SUPPORT_MOODS) {
8095 if (purple_account_get_status(account, "mood")) {
8096 menuitem = gtk_menu_item_new_with_mnemonic(_("Set _Mood..."));
8097 g_signal_connect(G_OBJECT(menuitem), "activate",
8098 G_CALLBACK(set_mood_cb), account);
8099 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), menuitem);
8102 if (PURPLE_PLUGIN_HAS_ACTIONS(plugin)) {
8103 build_plugin_actions(submenu, plugin, gc);
8105 } else {
8106 menuitem = gtk_menu_item_new_with_label(_("No actions available"));
8107 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), menuitem);
8108 gtk_widget_set_sensitive(menuitem, FALSE);
8111 pidgin_separator(submenu);
8113 menuitem = gtk_menu_item_new_with_mnemonic(_("_Disable"));
8114 g_signal_connect(G_OBJECT(menuitem), "activate",
8115 G_CALLBACK(disable_account_cb), account);
8116 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), menuitem);
8118 gtk_widget_show_all(accountmenu);
8121 static GList *plugin_submenus = NULL;
8123 void
8124 pidgin_blist_update_plugin_actions(void)
8126 GtkWidget *menuitem, *submenu;
8127 PurplePlugin *plugin = NULL;
8128 GList *l;
8129 GtkAccelGroup *accel_group;
8131 GtkWidget *pluginmenu = gtk_item_factory_get_widget(gtkblist->ift, N_("/Tools"));
8133 g_return_if_fail(pluginmenu != NULL);
8135 /* Remove old plugin action submenus from the Tools menu */
8136 for (l = plugin_submenus; l; l = l->next)
8137 gtk_widget_destroy(GTK_WIDGET(l->data));
8138 g_list_free(plugin_submenus);
8139 plugin_submenus = NULL;
8141 accel_group = gtk_menu_get_accel_group(GTK_MENU(pluginmenu));
8143 /* Add a submenu for each plugin with custom actions */
8144 for (l = purple_plugins_get_loaded(); l; l = l->next) {
8145 char *path;
8147 plugin = (PurplePlugin *) l->data;
8149 if (PURPLE_IS_PROTOCOL_PLUGIN(plugin))
8150 continue;
8152 if (!PURPLE_PLUGIN_HAS_ACTIONS(plugin))
8153 continue;
8155 menuitem = gtk_image_menu_item_new_with_label(_(plugin->info->name));
8156 gtk_menu_shell_append(GTK_MENU_SHELL(pluginmenu), menuitem);
8158 plugin_submenus = g_list_append(plugin_submenus, menuitem);
8160 submenu = gtk_menu_new();
8161 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
8163 gtk_menu_set_accel_group(GTK_MENU(submenu), accel_group);
8164 path = g_strdup_printf("%s/Tools/%s", gtkblist->ift->path, plugin->info->name);
8165 gtk_menu_set_accel_path(GTK_MENU(submenu), path);
8166 g_free(path);
8168 build_plugin_actions(submenu, plugin, NULL);
8170 gtk_widget_show_all(pluginmenu);
8173 static void
8174 sortmethod_act(GtkCheckMenuItem *checkmenuitem, char *id)
8176 if (gtk_check_menu_item_get_active(checkmenuitem))
8178 pidgin_set_cursor(gtkblist->window, GDK_WATCH);
8179 /* This is redundant. I think. */
8180 /* pidgin_blist_sort_method_set(id); */
8181 purple_prefs_set_string(PIDGIN_PREFS_ROOT "/blist/sort_type", id);
8183 pidgin_clear_cursor(gtkblist->window);
8187 void
8188 pidgin_blist_update_sort_methods(void)
8190 GtkWidget *menuitem = NULL, *activeitem = NULL;
8191 PidginBlistSortMethod *method = NULL;
8192 GList *l;
8193 GSList *sl = NULL;
8194 GtkWidget *sortmenu;
8195 const char *m = purple_prefs_get_string(PIDGIN_PREFS_ROOT "/blist/sort_type");
8197 if ((gtkblist == NULL) || (gtkblist->ift == NULL))
8198 return;
8200 g_return_if_fail(m != NULL);
8202 sortmenu = gtk_item_factory_get_widget(gtkblist->ift, N_("/Buddies/Sort Buddies"));
8204 if (sortmenu == NULL)
8205 return;
8207 /* Clear the old menu */
8208 for (l = gtk_container_get_children(GTK_CONTAINER(sortmenu)); l; l = g_list_delete_link(l, l)) {
8209 menuitem = l->data;
8210 gtk_widget_destroy(GTK_WIDGET(menuitem));
8213 for (l = pidgin_blist_sort_methods; l; l = l->next) {
8214 method = (PidginBlistSortMethod *) l->data;
8215 menuitem = gtk_radio_menu_item_new_with_label(sl, _(method->name));
8216 if (g_str_equal(m, method->id))
8217 activeitem = menuitem;
8218 sl = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(menuitem));
8219 gtk_menu_shell_append(GTK_MENU_SHELL(sortmenu), menuitem);
8220 g_signal_connect(G_OBJECT(menuitem), "toggled",
8221 G_CALLBACK(sortmethod_act), method->id);
8222 gtk_widget_show(menuitem);
8224 if (activeitem)
8225 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(activeitem), TRUE);