Don't allow multiple file choosers for custom buddy icons.
[pidgin-git.git] / pidgin / gtkblist.c
blob9d2c7ef78230bdf267043062fd1fcb9c94276460
1 /* pidgin
3 * Pidgin is the legal property of its developers, whose names are too numerous
4 * to list here. Please refer to the COPYRIGHT file distributed with this
5 * source distribution.
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
22 #include "internal.h"
23 #include "pidgin.h"
25 #include "account.h"
26 #include "action.h"
27 #include "connection.h"
28 #include "core.h"
29 #include "debug.h"
30 #include "notify.h"
31 #include "protocol.h"
32 #include "prefs.h"
33 #include "plugins.h"
34 #include "request.h"
35 #include "signals.h"
36 #include "pidginstock.h"
37 #include "theme-loader.h"
38 #include "theme-manager.h"
39 #include "util.h"
41 #include "gtkaccount.h"
42 #include "gtkblist.h"
43 #include "gtkcellrendererexpander.h"
44 #include "gtkconv.h"
45 #include "gtkdialogs.h"
46 #include "gtkxfer.h"
47 #include "gtkmenutray.h"
48 #include "gtkpounce.h"
49 #include "gtkplugin.h"
50 #include "gtkprefs.h"
51 #include "gtkprivacy.h"
52 #include "gtkroomlist.h"
53 #include "gtkstatusbox.h"
54 #include "gtkscrollbook.h"
55 #include "gtksmiley-manager.h"
56 #include "gtkstyle.h"
57 #include "gtkblist-theme.h"
58 #include "gtkblist-theme-loader.h"
59 #include "gtkutils.h"
60 #include "pidgin/minidialog.h"
61 #include "pidgin/pidginabout.h"
62 #include "pidgin/pidginaccountchooser.h"
63 #include "pidgin/pidgindebug.h"
64 #include "pidgin/pidgindebugplugininfo.h"
65 #include "pidgin/pidgingdkpixbuf.h"
66 #include "pidgin/pidginlog.h"
67 #include "pidgin/pidgintooltip.h"
69 #include <gdk/gdkkeysyms.h>
70 #include <gtk/gtk.h>
71 #include <gdk/gdk.h>
73 #include "gtk3compat.h"
75 typedef struct
77 PurpleAccount *account;
78 GtkWidget *window;
79 GtkBox *vbox;
80 GtkWidget *account_menu;
81 GtkSizeGroup *sg;
82 } PidginBlistRequestData;
84 typedef struct
86 PidginBlistRequestData rq_data;
87 GtkWidget *combo;
88 GtkWidget *entry;
89 GtkWidget *entry_for_alias;
90 GtkWidget *entry_for_invite;
92 } PidginAddBuddyData;
94 typedef struct
96 PidginBlistRequestData rq_data;
97 gchar *default_chat_name;
98 GList *entries;
99 } PidginChatData;
101 typedef struct
103 PidginChatData chat_data;
105 GtkWidget *alias_entry;
106 GtkWidget *group_combo;
107 GtkWidget *autojoin;
108 GtkWidget *persistent;
109 } PidginAddChatData;
111 typedef struct
113 /* GBoxed reference count */
114 int box_count;
116 /* Used to hold error minidialogs. Gets packed
117 * inside PidginBuddyList.error_buttons
119 PidginScrollBook *error_scrollbook;
121 /* Pointer to the mini-dialog about having signed on elsewhere, if one
122 * is showing; %NULL otherwise.
124 PidginMiniDialog *signed_on_elsewhere;
126 PidginBlistTheme *current_theme;
128 guint select_notebook_page_timeout;
131 } PidginBuddyListPrivate;
133 G_DEFINE_TYPE_WITH_PRIVATE(PidginBuddyList, pidgin_buddy_list,
134 PURPLE_TYPE_BUDDY_LIST)
136 #define PIDGIN_WINDOW_ICONIFIED(x) \
137 (gdk_window_get_state(gtk_widget_get_window(GTK_WIDGET(x))) & \
138 GDK_WINDOW_STATE_ICONIFIED)
140 #define PIDGIN_WINDOW_MAXIMIZED(x) \
141 (gdk_window_get_state(gtk_widget_get_window(GTK_WIDGET(x))) & \
142 GDK_WINDOW_STATE_MAXIMIZED)
144 static GtkWidget *accountmenu = NULL;
146 static guint visibility_manager_count = 0;
147 static GdkVisibilityState gtk_blist_visibility = GDK_VISIBILITY_UNOBSCURED;
148 static gboolean gtk_blist_focused = FALSE;
149 static gboolean editing_blist = FALSE;
151 static GList *pidgin_blist_sort_methods = NULL;
152 static struct _PidginBlistSortMethod *current_sort_method = NULL;
153 static void sort_method_none(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter);
155 static void sort_method_alphabetical(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter);
156 static void sort_method_status(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter);
157 static void sort_method_log_activity(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter);
158 static guint sort_merge_id;
159 static GtkActionGroup *sort_action_group = NULL;
161 static PidginBuddyList *gtkblist = NULL;
163 static GList *groups_tree(void);
164 static gboolean pidgin_blist_refresh_timer(PurpleBuddyList *list);
165 static void pidgin_blist_update_buddy(PurpleBuddyList *list, PurpleBlistNode *node, gboolean status_change);
166 static void pidgin_blist_selection_changed(GtkTreeSelection *selection, gpointer data);
167 static void pidgin_blist_update(PurpleBuddyList *list, PurpleBlistNode *node);
168 static void pidgin_blist_update_group(PurpleBuddyList *list, PurpleBlistNode *node);
169 static void pidgin_blist_update_contact(PurpleBuddyList *list, PurpleBlistNode *node);
170 static char *pidgin_get_tooltip_text(PurpleBlistNode *node, gboolean full);
171 static gboolean get_iter_from_node(PurpleBlistNode *node, GtkTreeIter *iter);
172 static gboolean buddy_is_displayable(PurpleBuddy *buddy);
173 static void redo_buddy_list(PurpleBuddyList *list, gboolean remove, gboolean rerender);
174 static void pidgin_blist_collapse_contact_cb(GtkWidget *w, PurpleBlistNode *node);
175 static char *pidgin_get_group_title(PurpleBlistNode *gnode, gboolean expanded);
176 static void pidgin_blist_expand_contact_cb(GtkWidget *w, PurpleBlistNode *node);
177 static void set_urgent(void);
179 typedef enum {
180 PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE = 1 << 0, /* Whether there's pending message in a conversation */
181 PIDGIN_BLIST_CHAT_HAS_PENDING_MESSAGE_WITH_NICK = 1 << 1, /* Whether there's a pending message in a chat that mentions our nick */
182 } PidginBlistNodeFlags;
184 typedef struct {
185 GtkTreeRowReference *row;
186 gboolean contact_expanded;
187 gboolean recent_signonoff;
188 gint recent_signonoff_timer;
189 struct {
190 PurpleConversation *conv;
191 PidginBlistNodeFlags flags;
192 } conv;
193 } PidginBlistNode;
195 /***************************************************
196 * Callbacks *
197 ***************************************************/
198 static gboolean gtk_blist_visibility_cb(GtkWidget *w, GdkEventVisibility *event, gpointer data)
200 GdkVisibilityState old_state = gtk_blist_visibility;
201 gtk_blist_visibility = event->state;
203 if (gtk_blist_visibility == GDK_VISIBILITY_FULLY_OBSCURED &&
204 old_state != GDK_VISIBILITY_FULLY_OBSCURED) {
206 /* no longer fully obscured */
207 pidgin_blist_refresh_timer(purple_blist_get_default());
210 /* continue to handle event normally */
211 return FALSE;
214 static gboolean gtk_blist_window_state_cb(GtkWidget *w, GdkEventWindowState *event, gpointer data)
216 if(event->changed_mask & GDK_WINDOW_STATE_WITHDRAWN) {
217 if(event->new_window_state & GDK_WINDOW_STATE_WITHDRAWN)
218 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/list_visible", FALSE);
219 else {
220 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/list_visible", TRUE);
221 pidgin_blist_refresh_timer(purple_blist_get_default());
225 if(event->changed_mask & GDK_WINDOW_STATE_MAXIMIZED) {
226 if(event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED)
227 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/list_maximized", TRUE);
228 else
229 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/list_maximized", FALSE);
232 /* Refresh gtkblist if un-iconifying */
233 if (event->changed_mask & GDK_WINDOW_STATE_ICONIFIED){
234 if (!(event->new_window_state & GDK_WINDOW_STATE_ICONIFIED))
235 pidgin_blist_refresh_timer(purple_blist_get_default());
238 return FALSE;
241 static gboolean gtk_blist_delete_cb(GtkWidget *w, GdkEventAny *event, gpointer data)
243 if(visibility_manager_count)
244 purple_blist_set_visible(FALSE);
245 else
246 purple_core_quit();
248 /* we handle everything, event should not propogate further */
249 return TRUE;
252 static void
253 gtk_blist_hide_cb(GtkWidget *widget, PidginBuddyList *gtkblist)
255 purple_signal_emit(pidgin_blist_get_handle(),
256 "gtkblist-hiding", gtkblist);
259 static void
260 gtk_blist_show_cb(GtkWidget *widget, PidginBuddyList *gtkblist)
262 purple_signal_emit(pidgin_blist_get_handle(),
263 "gtkblist-unhiding", gtkblist);
266 static void
267 gtk_blist_size_allocate_cb(GtkWidget *widget, GtkAllocation *allocation,
268 gpointer data)
270 int new_width;
271 int new_height;
273 /* ignore changes when maximized */
274 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/list_maximized")) {
275 return;
278 gtk_window_get_size(GTK_WINDOW(widget), &new_width, &new_height);
280 /* store the size */
281 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/blist/width", new_width);
282 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/blist/height", new_height);
285 static void gtk_blist_menu_info_cb(GtkWidget *w, PurpleBuddy *b)
287 PurpleAccount *account = purple_buddy_get_account(b);
289 pidgin_retrieve_user_info(purple_account_get_connection(account),
290 purple_buddy_get_name(b));
293 static void gtk_blist_menu_im_cb(GtkWidget *w, PurpleBuddy *b)
295 pidgin_dialogs_im_with_user(purple_buddy_get_account(b),
296 purple_buddy_get_name(b));
299 #ifdef USE_VV
300 static void gtk_blist_menu_audio_call_cb(GtkWidget *w, PurpleBuddy *b)
302 purple_protocol_initiate_media(purple_buddy_get_account(b),
303 purple_buddy_get_name(b), PURPLE_MEDIA_AUDIO);
306 static void gtk_blist_menu_video_call_cb(GtkWidget *w, PurpleBuddy *b)
308 /* if the buddy supports both audio and video, start a combined call,
309 otherwise start a pure video session */
310 if (purple_protocol_get_media_caps(purple_buddy_get_account(b),
311 purple_buddy_get_name(b)) &
312 PURPLE_MEDIA_CAPS_AUDIO_VIDEO) {
313 purple_protocol_initiate_media(purple_buddy_get_account(b),
314 purple_buddy_get_name(b), PURPLE_MEDIA_AUDIO | PURPLE_MEDIA_VIDEO);
315 } else {
316 purple_protocol_initiate_media(purple_buddy_get_account(b),
317 purple_buddy_get_name(b), PURPLE_MEDIA_VIDEO);
321 #endif
323 static void gtk_blist_menu_send_file_cb(GtkWidget *w, PurpleBuddy *b)
325 PurpleAccount *account = purple_buddy_get_account(b);
327 purple_serv_send_file(purple_account_get_connection(account),
328 purple_buddy_get_name(b), NULL);
331 static void gtk_blist_menu_move_to_cb(GtkWidget *w, PurpleBlistNode *node)
333 PurpleGroup *group = g_object_get_data(G_OBJECT(w), "groupnode");
334 purple_blist_add_contact((PurpleContact *)node, group, NULL);
338 static void gtk_blist_menu_autojoin_cb(GtkWidget *w, PurpleChat *chat)
340 purple_blist_node_set_bool(PURPLE_BLIST_NODE(chat), "gtk-autojoin",
341 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w)));
344 static void gtk_blist_menu_persistent_cb(GtkWidget *w, PurpleChat *chat)
346 purple_blist_node_set_bool(PURPLE_BLIST_NODE(chat), "gtk-persistent",
347 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w)));
350 static PurpleConversation *
351 find_conversation_with_buddy(PurpleBuddy *buddy)
353 PidginBlistNode *ui = purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(buddy));
354 if (ui)
355 return ui->conv.conv;
356 return PURPLE_CONVERSATION(purple_conversations_find_im_with_account(
357 purple_buddy_get_name(buddy), purple_buddy_get_account(buddy)));
360 static void gtk_blist_join_chat(PurpleChat *chat)
362 PurpleAccount *account;
363 PurpleConversation *conv;
364 PurpleProtocol *protocol;
365 GHashTable *components;
366 const char *name;
367 char *chat_name = NULL;
369 account = purple_chat_get_account(chat);
370 protocol = purple_protocols_find(purple_account_get_protocol_id(account));
372 components = purple_chat_get_components(chat);
374 if (protocol)
375 chat_name = purple_protocol_chat_iface_get_name(protocol, components);
377 if (chat_name)
378 name = chat_name;
379 else
380 name = purple_chat_get_name(chat);
382 conv = PURPLE_CONVERSATION(purple_conversations_find_chat_with_account(name,
383 account));
385 if (conv != NULL) {
386 pidgin_conv_attach_to_conversation(conv);
387 purple_conversation_present(conv);
390 purple_serv_join_chat(purple_account_get_connection(account), components);
391 g_free(chat_name);
394 static void gtk_blist_menu_join_cb(GtkWidget *w, PurpleChat *chat)
396 gtk_blist_join_chat(chat);
399 static void gtk_blist_renderer_editing_cancelled_cb(GtkCellRenderer *renderer, PurpleBuddyList *list)
401 editing_blist = FALSE;
402 g_object_set(G_OBJECT(renderer), "editable", FALSE, NULL);
403 pidgin_blist_refresh(list);
406 static void gtk_blist_renderer_editing_started_cb(GtkCellRenderer *renderer,
407 GtkCellEditable *editable,
408 gchar *path_str,
409 gpointer user_data)
411 PidginBuddyList *gtkblist = PIDGIN_BUDDY_LIST(user_data);
412 GtkTreeIter iter;
413 GtkTreePath *path = NULL;
414 PurpleBlistNode *node;
415 const char *text = NULL;
417 path = gtk_tree_path_new_from_string (path_str);
418 gtk_tree_model_get_iter (GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
419 gtk_tree_path_free (path);
420 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
422 if (PURPLE_IS_CONTACT(node))
423 text = purple_contact_get_alias(PURPLE_CONTACT(node));
424 else if (PURPLE_IS_BUDDY(node))
425 text = purple_buddy_get_alias(PURPLE_BUDDY(node));
426 else if (PURPLE_IS_GROUP(node))
427 text = purple_group_get_name(PURPLE_GROUP(node));
428 else if (PURPLE_IS_CHAT(node))
429 text = purple_chat_get_name(PURPLE_CHAT(node));
430 else
431 g_return_if_reached();
433 if (GTK_IS_ENTRY (editable)) {
434 GtkEntry *entry = GTK_ENTRY (editable);
435 gtk_entry_set_text(entry, text);
437 editing_blist = TRUE;
440 static void
441 gtk_blist_do_personize(GList *merges)
443 PurpleBlistNode *contact = NULL;
444 int max = 0;
445 GList *tmp;
447 /* First, we find the contact to merge the rest of the buddies into.
448 * This will be the contact with the most buddies in it; ties are broken
449 * by which contact is higher in the list
451 for (tmp = merges; tmp; tmp = tmp->next) {
452 PurpleBlistNode *node = tmp->data;
453 PurpleBlistNode *b;
454 int i = 0;
456 if (PURPLE_IS_BUDDY(node))
457 node = purple_blist_node_get_parent(node);
459 if (!PURPLE_IS_CONTACT(node))
460 continue;
462 for (b = purple_blist_node_get_first_child(node);
464 b = purple_blist_node_get_sibling_next(b))
466 i++;
469 if (i > max) {
470 contact = node;
471 max = i;
475 if (contact == NULL)
476 return;
478 /* Merge all those buddies into this contact */
479 for (tmp = merges; tmp; tmp = tmp->next) {
480 PurpleBlistNode *node = tmp->data;
481 if (PURPLE_IS_BUDDY(node))
482 node = purple_blist_node_get_parent(node);
484 if (node == contact)
485 continue;
487 purple_contact_merge((PurpleContact *)node, contact);
490 /* And show the expanded contact, so the people know what's going on */
491 pidgin_blist_expand_contact_cb(NULL, contact);
492 g_list_free(merges);
495 static void
496 gtk_blist_auto_personize(PurpleBlistNode *group, const char *alias)
498 PurpleBlistNode *contact;
499 PurpleBlistNode *buddy;
500 GList *merges = NULL;
501 int i = 0;
502 char *a = g_utf8_casefold(alias, -1);
504 for (contact = purple_blist_node_get_first_child(group);
505 contact != NULL;
506 contact = purple_blist_node_get_sibling_next(contact)) {
507 char *node_alias;
508 if (!PURPLE_IS_CONTACT(contact))
509 continue;
511 node_alias = g_utf8_casefold(purple_contact_get_alias((PurpleContact *)contact), -1);
512 if (node_alias && !g_utf8_collate(node_alias, a)) {
513 merges = g_list_append(merges, contact);
514 i++;
515 g_free(node_alias);
516 continue;
518 g_free(node_alias);
520 for (buddy = purple_blist_node_get_first_child(contact);
521 buddy;
522 buddy = purple_blist_node_get_sibling_next(buddy))
524 if (!PURPLE_IS_BUDDY(buddy))
525 continue;
527 node_alias = g_utf8_casefold(purple_buddy_get_alias(PURPLE_BUDDY(buddy)), -1);
528 if (node_alias && !g_utf8_collate(node_alias, a)) {
529 merges = g_list_append(merges, buddy);
530 i++;
531 g_free(node_alias);
532 break;
534 g_free(node_alias);
537 g_free(a);
539 if (i > 1)
541 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);
542 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. "
543 "You can separate them again by choosing 'Expand' from the contact's context menu"), 0, NULL,
544 merges, 2, _("_Yes"), PURPLE_CALLBACK(gtk_blist_do_personize), _("_No"), PURPLE_CALLBACK(g_list_free));
545 g_free(msg);
546 } else
547 g_list_free(merges);
550 static void gtk_blist_renderer_edited_cb(GtkCellRendererText *text_rend, char *arg1,
551 char *arg2, PurpleBuddyList *list)
553 PidginBuddyList *gtkblist = PIDGIN_BUDDY_LIST(list);
554 GtkTreeIter iter;
555 GtkTreePath *path;
556 PurpleBlistNode *node;
557 PurpleGroup *dest;
558 gchar *alias = NULL;
560 editing_blist = FALSE;
561 path = gtk_tree_path_new_from_string (arg1);
562 gtk_tree_model_get_iter (GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
563 gtk_tree_path_free (path);
564 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
565 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(gtkblist->treeview), TRUE);
566 g_object_set(G_OBJECT(gtkblist->text_rend), "editable", FALSE, NULL);
568 if (PURPLE_IS_CONTACT(node)) {
569 PurpleContact *contact = PURPLE_CONTACT(node);
570 PidginBlistNode *gtknode =
571 (PidginBlistNode *)purple_blist_node_get_ui_data(node);
574 * Using purple_contact_get_alias here breaks because we
575 * specifically want to check the contact alias only (i.e. not
576 * the priority buddy, which purple_contact_get_alias does).
577 * The "alias" GObject property gives us just the alias.
579 g_object_get(contact, "alias", &alias, NULL);
581 if (alias || gtknode->contact_expanded) {
582 purple_contact_set_alias(contact, arg2);
583 gtk_blist_auto_personize(purple_blist_node_get_parent(node), arg2);
584 } else {
585 PurpleBuddy *buddy = purple_contact_get_priority_buddy(contact);
586 purple_buddy_set_local_alias(buddy, arg2);
587 purple_serv_alias_buddy(buddy);
588 gtk_blist_auto_personize(purple_blist_node_get_parent(node), arg2);
590 } else if (PURPLE_IS_BUDDY(node)) {
591 PurpleGroup *group = purple_buddy_get_group(PURPLE_BUDDY(node));
593 purple_buddy_set_local_alias(PURPLE_BUDDY(node), arg2);
594 purple_serv_alias_buddy(PURPLE_BUDDY(node));
595 gtk_blist_auto_personize(PURPLE_BLIST_NODE(group), arg2);
596 } else if (PURPLE_IS_GROUP(node)) {
597 dest = purple_blist_find_group(arg2);
598 if (dest != NULL && purple_utf8_strcasecmp(arg2, purple_group_get_name(PURPLE_GROUP(node)))) {
599 pidgin_dialogs_merge_groups(PURPLE_GROUP(node), arg2);
600 } else {
601 purple_group_set_name(PURPLE_GROUP(node), arg2);
603 } else if (PURPLE_IS_CHAT(node)) {
604 purple_chat_set_alias(PURPLE_CHAT(node), arg2);
607 g_free(alias);
608 pidgin_blist_refresh(list);
611 static void
612 chat_components_edit_ok(PurpleChat *chat, PurpleRequestFields *allfields)
614 GList *groups, *fields;
616 for (groups = purple_request_fields_get_groups(allfields); groups; groups = groups->next) {
617 fields = purple_request_field_group_get_fields(groups->data);
618 for (; fields; fields = fields->next) {
619 PurpleRequestField *field = fields->data;
620 const char *id;
621 char *val;
623 id = purple_request_field_get_id(field);
624 if (purple_request_field_get_field_type(field) == PURPLE_REQUEST_FIELD_INTEGER)
625 val = g_strdup_printf("%d", purple_request_field_int_get_value(field));
626 else
627 val = g_strdup(purple_request_field_string_get_value(field));
629 if (!val) {
630 g_hash_table_remove(purple_chat_get_components(chat), id);
631 } else {
632 g_hash_table_replace(purple_chat_get_components(chat), g_strdup(id), val); /* val should not be free'd */
638 static void chat_components_edit(GtkWidget *w, PurpleBlistNode *node)
640 PurpleRequestFields *fields = purple_request_fields_new();
641 PurpleRequestFieldGroup *group = purple_request_field_group_new(NULL);
642 PurpleRequestField *field;
643 GList *parts, *iter;
644 PurpleProtocolChatEntry *pce;
645 PurpleConnection *gc;
646 PurpleChat *chat = (PurpleChat*)node;
648 purple_request_fields_add_group(fields, group);
650 gc = purple_account_get_connection(purple_chat_get_account(chat));
651 parts = purple_protocol_chat_iface_info(purple_connection_get_protocol(gc), gc);
653 for (iter = parts; iter; iter = iter->next) {
654 pce = iter->data;
655 if (pce->is_int) {
656 int val;
657 const char *str = g_hash_table_lookup(purple_chat_get_components(chat), pce->identifier);
658 if (!str || sscanf(str, "%d", &val) != 1)
659 val = pce->min;
660 field = purple_request_field_int_new(pce->identifier, pce->label, val, INT_MIN, INT_MAX);
661 } else {
662 field = purple_request_field_string_new(pce->identifier, pce->label,
663 g_hash_table_lookup(purple_chat_get_components(chat), pce->identifier), FALSE);
664 if (pce->secret)
665 purple_request_field_string_set_masked(field, TRUE);
668 if (pce->required)
669 purple_request_field_set_required(field, TRUE);
671 purple_request_field_group_add_field(group, field);
672 g_free(pce);
675 g_list_free(parts);
677 purple_request_fields(NULL, _("Edit Chat"), NULL, _("Please update the necessary fields."),
678 fields, _("Save"), G_CALLBACK(chat_components_edit_ok), _("Cancel"), NULL,
679 NULL, chat);
682 static void gtk_blist_menu_alias_cb(GtkWidget *w, PurpleBlistNode *node)
684 GtkTreeIter iter;
685 GtkTreePath *path;
687 if (!(get_iter_from_node(node, &iter))) {
688 /* This is either a bug, or the buddy is in a collapsed contact */
689 node = purple_blist_node_get_parent(node);
690 if (!get_iter_from_node(node, &iter))
691 /* Now it's definitely a bug */
692 return;
695 pidgin_blist_tooltip_destroy();
697 path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &iter);
698 g_object_set(G_OBJECT(gtkblist->text_rend), "editable", TRUE, NULL);
699 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(gtkblist->treeview), FALSE);
700 gtk_widget_grab_focus(gtkblist->treeview);
701 gtk_tree_view_set_cursor_on_cell(GTK_TREE_VIEW(gtkblist->treeview), path,
702 gtkblist->text_column, gtkblist->text_rend, TRUE);
703 gtk_tree_path_free(path);
706 static void gtk_blist_menu_bp_cb(GtkWidget *w, PurpleBuddy *b)
708 pidgin_pounce_editor_show(purple_buddy_get_account(b),
709 purple_buddy_get_name(b), NULL);
712 static void gtk_blist_menu_showlog_cb(GtkWidget *w, PurpleBlistNode *node)
714 PurpleLogType type;
715 PurpleAccount *account;
716 char *name = NULL;
718 pidgin_set_cursor(gtkblist->window, GDK_WATCH);
720 if (PURPLE_IS_BUDDY(node)) {
721 PurpleBuddy *b = (PurpleBuddy*) node;
722 type = PURPLE_LOG_IM;
723 name = g_strdup(purple_buddy_get_name(b));
724 account = purple_buddy_get_account(b);
725 } else if (PURPLE_IS_CHAT(node)) {
726 PurpleChat *c = PURPLE_CHAT(node);
727 PurpleProtocol *protocol = NULL;
728 type = PURPLE_LOG_CHAT;
729 account = purple_chat_get_account(c);
730 protocol = purple_protocols_find(purple_account_get_protocol_id(account));
731 if (protocol) {
732 name = purple_protocol_chat_iface_get_name(protocol, purple_chat_get_components(c));
734 } else if (PURPLE_IS_CONTACT(node)) {
735 pidgin_log_show_contact(PURPLE_CONTACT(node));
736 pidgin_clear_cursor(gtkblist->window);
737 return;
738 } else {
739 pidgin_clear_cursor(gtkblist->window);
741 /* This callback should not have been registered for a node
742 * that doesn't match the type of one of the blocks above. */
743 g_return_if_reached();
746 if (name && account) {
747 pidgin_log_show(type, name, account);
748 pidgin_clear_cursor(gtkblist->window);
751 g_free(name);
754 static void gtk_blist_menu_showoffline_cb(GtkWidget *w, PurpleBlistNode *node)
756 if (PURPLE_IS_BUDDY(node))
758 purple_blist_node_set_bool(node, "show_offline",
759 !purple_blist_node_get_bool(node, "show_offline"));
760 pidgin_blist_update(purple_blist_get_default(), node);
762 else if (PURPLE_IS_CONTACT(node))
764 PurpleBlistNode *bnode;
765 gboolean setting = !purple_blist_node_get_bool(node, "show_offline");
767 purple_blist_node_set_bool(node, "show_offline", setting);
768 for (bnode = purple_blist_node_get_first_child(node);
769 bnode != NULL;
770 bnode = purple_blist_node_get_sibling_next(bnode))
772 purple_blist_node_set_bool(bnode, "show_offline", setting);
773 pidgin_blist_update(purple_blist_get_default(), bnode);
775 } else if (PURPLE_IS_GROUP(node)) {
776 PurpleBlistNode *cnode, *bnode;
777 gboolean setting = !purple_blist_node_get_bool(node, "show_offline");
779 purple_blist_node_set_bool(node, "show_offline", setting);
780 for (cnode = purple_blist_node_get_first_child(node);
781 cnode != NULL;
782 cnode = purple_blist_node_get_sibling_next(cnode))
784 purple_blist_node_set_bool(cnode, "show_offline", setting);
785 for (bnode = purple_blist_node_get_first_child(cnode);
786 bnode != NULL;
787 bnode = purple_blist_node_get_sibling_next(bnode))
789 purple_blist_node_set_bool(bnode, "show_offline", setting);
790 pidgin_blist_update(purple_blist_get_default(),
791 bnode);
797 static void gtk_blist_show_systemlog_cb(void)
799 pidgin_syslog_show();
802 static void gtk_blist_show_onlinehelp_cb(void)
804 purple_notify_uri(NULL, PURPLE_WEBSITE "documentation");
807 static void
808 do_join_chat(PidginChatData *data)
810 if (data)
812 GHashTable *components =
813 g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
814 GList *tmp;
815 PurpleChat *chat;
817 for (tmp = data->entries; tmp != NULL; tmp = tmp->next)
819 if (g_object_get_data(tmp->data, "is_spin"))
821 g_hash_table_replace(components,
822 g_strdup(g_object_get_data(tmp->data, "identifier")),
823 g_strdup_printf("%d",
824 gtk_spin_button_get_value_as_int(tmp->data)));
826 else
828 g_hash_table_replace(components,
829 g_strdup(g_object_get_data(tmp->data, "identifier")),
830 g_strdup(gtk_entry_get_text(tmp->data)));
834 chat = purple_chat_new(data->rq_data.account, NULL, components);
835 gtk_blist_join_chat(chat);
836 purple_blist_remove_chat(chat);
840 static void
841 do_joinchat(GtkWidget *dialog, int id, PidginChatData *info)
843 switch(id)
845 case GTK_RESPONSE_OK:
846 do_join_chat(info);
847 break;
849 case 1:
850 pidgin_roomlist_dialog_show_with_account(info->rq_data.account);
851 return;
853 break;
856 gtk_widget_destroy(GTK_WIDGET(dialog));
857 g_list_free(info->entries);
858 g_free(info);
862 * Check the values of all the text entry boxes. If any required input
863 * strings are empty then don't allow the user to click on "OK."
865 static void
866 set_sensitive_if_input_chat_cb(GtkWidget *entry, gpointer user_data)
868 PurpleProtocol *protocol;
869 PurpleConnection *gc;
870 PidginChatData *data;
871 GList *tmp;
872 const char *text;
873 gboolean required;
874 gboolean sensitive = TRUE;
876 data = user_data;
878 for (tmp = data->entries; tmp != NULL; tmp = tmp->next)
880 if (!g_object_get_data(tmp->data, "is_spin"))
882 required = GPOINTER_TO_INT(g_object_get_data(tmp->data, "required"));
883 text = gtk_entry_get_text(tmp->data);
884 if (required && (*text == '\0'))
885 sensitive = FALSE;
889 gtk_dialog_set_response_sensitive(GTK_DIALOG(data->rq_data.window), GTK_RESPONSE_OK, sensitive);
891 gc = purple_account_get_connection(data->rq_data.account);
892 protocol = (gc != NULL) ? purple_connection_get_protocol(gc) : NULL;
893 sensitive = (protocol != NULL && PURPLE_PROTOCOL_IMPLEMENTS(protocol, ROOMLIST, get_list));
895 gtk_dialog_set_response_sensitive(GTK_DIALOG(data->rq_data.window), 1, sensitive);
898 static void
899 set_sensitive_if_input_buddy_cb(GtkWidget *entry, gpointer user_data)
901 PurpleProtocol *protocol;
902 PidginAddBuddyData *data = user_data;
903 const char *text;
905 protocol = purple_protocols_find(purple_account_get_protocol_id(
906 data->rq_data.account));
907 text = gtk_entry_get_text(GTK_ENTRY(entry));
909 gtk_dialog_set_response_sensitive(GTK_DIALOG(data->rq_data.window),
910 GTK_RESPONSE_OK, purple_validate(protocol, text));
913 static void
914 pidgin_blist_update_privacy_cb(PurpleBuddy *buddy)
916 PidginBlistNode *ui_data = purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(buddy));
917 if (ui_data == NULL || ui_data->row == NULL)
918 return;
919 pidgin_blist_update_buddy(purple_blist_get_default(),
920 PURPLE_BLIST_NODE(buddy), TRUE);
923 static gboolean
924 add_buddy_account_filter_func(PurpleAccount *account)
926 PurpleConnection *gc = purple_account_get_connection(account);
927 PurpleProtocol *protocol = purple_connection_get_protocol(gc);
929 return PURPLE_PROTOCOL_IMPLEMENTS(protocol, SERVER, add_buddy);
932 static gboolean
933 chat_account_filter_func(PurpleAccount *account)
935 PurpleConnection *gc = purple_account_get_connection(account);
936 PurpleProtocol *protocol = NULL;
938 if (gc == NULL)
939 return FALSE;
941 protocol = purple_connection_get_protocol(gc);
943 return (PURPLE_PROTOCOL_IMPLEMENTS(protocol, CHAT, info));
946 gboolean
947 pidgin_blist_joinchat_is_showable()
949 GList *c;
950 PurpleConnection *gc;
952 for (c = purple_connections_get_all(); c != NULL; c = c->next) {
953 gc = c->data;
955 if (chat_account_filter_func(purple_connection_get_account(gc)))
956 return TRUE;
959 return FALSE;
962 static GtkWidget *
963 make_blist_request_dialog(PidginBlistRequestData *data, PurpleAccount *account,
964 const char *title, const char *window_role, const char *label_text,
965 GCallback callback_func, PurpleFilterAccountFunc filter_func,
966 GCallback response_cb)
968 GtkWidget *label;
969 GtkWidget *img;
970 GtkWidget *hbox;
971 GtkWidget *vbox;
972 GtkWindow *blist_window;
973 PidginBuddyList *gtkblist;
975 data->account = account;
977 img = gtk_image_new_from_icon_name("dialog-question",
978 GTK_ICON_SIZE_DIALOG);
980 gtkblist = PIDGIN_BUDDY_LIST(purple_blist_get_default());
981 blist_window = gtkblist ? GTK_WINDOW(gtkblist->window) : NULL;
983 data->window = gtk_dialog_new();
984 gtk_window_set_title(GTK_WINDOW(data->window), title);
985 gtk_window_set_transient_for(GTK_WINDOW(data->window), blist_window);
986 gtk_dialog_set_default_response(GTK_DIALOG(data->window), GTK_RESPONSE_OK);
987 gtk_container_set_border_width(GTK_CONTAINER(data->window), PIDGIN_HIG_BOX_SPACE);
988 gtk_window_set_resizable(GTK_WINDOW(data->window), FALSE);
989 gtk_box_set_spacing(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(data->window))),
990 PIDGIN_HIG_BORDER);
991 gtk_container_set_border_width(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(data->window))),
992 PIDGIN_HIG_BOX_SPACE);
993 gtk_window_set_role(GTK_WINDOW(data->window), window_role);
995 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, PIDGIN_HIG_BORDER);
996 gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(data->window))),
997 hbox);
998 gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0);
999 gtk_widget_set_halign(img, GTK_ALIGN_START);
1000 gtk_widget_set_valign(img, GTK_ALIGN_START);
1002 vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
1003 gtk_container_add(GTK_CONTAINER(hbox), vbox);
1005 label = gtk_label_new(label_text);
1007 gtk_widget_set_size_request(label, 400, -1);
1008 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1009 gtk_label_set_xalign(GTK_LABEL(label), 0);
1010 gtk_label_set_yalign(GTK_LABEL(label), 0);
1011 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1013 data->sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
1015 data->account_menu = pidgin_account_chooser_new(account, FALSE);
1016 pidgin_account_chooser_set_filter_func(
1017 PIDGIN_ACCOUNT_CHOOSER(data->account_menu), filter_func);
1018 pidgin_add_widget_to_vbox(GTK_BOX(vbox), _("A_ccount"), data->sg, data->account_menu, TRUE, NULL);
1019 g_signal_connect(data->account_menu, "changed",
1020 G_CALLBACK(callback_func), data);
1022 data->vbox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 5));
1023 gtk_container_set_border_width(GTK_CONTAINER(data->vbox), 0);
1024 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(data->vbox), FALSE, FALSE, 0);
1026 g_signal_connect(G_OBJECT(data->window), "response", response_cb, data);
1028 g_object_unref(data->sg);
1030 return vbox;
1033 static void
1034 rebuild_chat_entries(PidginChatData *data, const char *default_chat_name)
1036 PurpleConnection *gc;
1037 PurpleProtocol *protocol;
1038 GList *list = NULL, *tmp;
1039 GHashTable *defaults = NULL;
1040 PurpleProtocolChatEntry *pce;
1041 gboolean focus = TRUE;
1043 g_return_if_fail(data->rq_data.account != NULL);
1045 gc = purple_account_get_connection(data->rq_data.account);
1046 protocol = purple_connection_get_protocol(gc);
1048 gtk_container_foreach(GTK_CONTAINER(data->rq_data.vbox), (GtkCallback)gtk_widget_destroy, NULL);
1050 g_list_free(data->entries);
1051 data->entries = NULL;
1053 list = purple_protocol_chat_iface_info(protocol, gc);
1054 defaults = purple_protocol_chat_iface_info_defaults(protocol, gc, default_chat_name);
1056 for (tmp = list; tmp; tmp = tmp->next)
1058 GtkWidget *input;
1060 pce = tmp->data;
1062 if (pce->is_int)
1064 GtkAdjustment *adjust;
1065 adjust = GTK_ADJUSTMENT(gtk_adjustment_new(pce->min,
1066 pce->min, pce->max,
1067 1, 10, 10));
1068 input = gtk_spin_button_new(adjust, 1, 0);
1069 gtk_widget_set_size_request(input, 50, -1);
1070 pidgin_add_widget_to_vbox(GTK_BOX(data->rq_data.vbox), pce->label, data->rq_data.sg, input, FALSE, NULL);
1072 else
1074 char *value;
1075 input = gtk_entry_new();
1076 gtk_entry_set_activates_default(GTK_ENTRY(input), TRUE);
1077 value = g_hash_table_lookup(defaults, pce->identifier);
1078 if (value != NULL)
1079 gtk_entry_set_text(GTK_ENTRY(input), value);
1080 if (pce->secret)
1082 gtk_entry_set_visibility(GTK_ENTRY(input), FALSE);
1084 pidgin_add_widget_to_vbox(data->rq_data.vbox, pce->label, data->rq_data.sg, input, TRUE, NULL);
1085 g_signal_connect(G_OBJECT(input), "changed",
1086 G_CALLBACK(set_sensitive_if_input_chat_cb), data);
1089 /* Do the following for any type of input widget */
1090 if (focus)
1092 gtk_widget_grab_focus(input);
1093 focus = FALSE;
1095 g_object_set_data(G_OBJECT(input), "identifier", (gpointer)pce->identifier);
1096 g_object_set_data(G_OBJECT(input), "is_spin", GINT_TO_POINTER(pce->is_int));
1097 g_object_set_data(G_OBJECT(input), "required", GINT_TO_POINTER(pce->required));
1098 data->entries = g_list_append(data->entries, input);
1100 g_free(pce);
1103 g_list_free(list);
1104 g_hash_table_destroy(defaults);
1106 /* Set whether the "OK" button should be clickable initially */
1107 set_sensitive_if_input_chat_cb(NULL, data);
1109 gtk_widget_show_all(GTK_WIDGET(data->rq_data.vbox));
1112 static void
1113 chat_select_account_cb(GObject *w, PidginChatData *data)
1115 PurpleAccount *account =
1116 pidgin_account_chooser_get_selected(GTK_WIDGET(w));
1118 g_return_if_fail(data != NULL);
1119 g_return_if_fail(account != NULL);
1121 if (purple_strequal(purple_account_get_protocol_id(data->rq_data.account),
1122 purple_account_get_protocol_id(account)))
1124 data->rq_data.account = account;
1126 else
1128 data->rq_data.account = account;
1129 rebuild_chat_entries(data, data->default_chat_name);
1133 void
1134 pidgin_blist_joinchat_show(void)
1136 PidginChatData *data = NULL;
1138 data = g_new0(PidginChatData, 1);
1140 make_blist_request_dialog((PidginBlistRequestData *)data, NULL,
1141 _("Join a Chat"), "join_chat",
1142 _("Please enter the appropriate information about the chat "
1143 "you would like to join.\n"),
1144 G_CALLBACK(chat_select_account_cb),
1145 chat_account_filter_func, (GCallback)do_joinchat);
1146 gtk_dialog_add_buttons(GTK_DIALOG(data->rq_data.window),
1147 _("Room _List"), 1,
1148 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1149 PIDGIN_STOCK_CHAT, GTK_RESPONSE_OK, NULL);
1150 gtk_dialog_set_default_response(GTK_DIALOG(data->rq_data.window),
1151 GTK_RESPONSE_OK);
1152 data->default_chat_name = NULL;
1153 data->rq_data.account =
1154 pidgin_account_chooser_get_selected(data->rq_data.account_menu);
1156 rebuild_chat_entries(data, NULL);
1158 gtk_widget_show_all(data->rq_data.window);
1161 static void gtk_blist_row_expanded_cb(GtkTreeView *tv, GtkTreeIter *iter, GtkTreePath *path, gpointer user_data)
1163 PidginBuddyList *gtkblist = PIDGIN_BUDDY_LIST(user_data);
1164 PurpleBlistNode *node;
1166 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), iter, NODE_COLUMN, &node, -1);
1168 if (PURPLE_IS_GROUP(node)) {
1169 char *title;
1171 title = pidgin_get_group_title(node, TRUE);
1173 gtk_tree_store_set(gtkblist->treemodel, iter,
1174 NAME_COLUMN, title,
1175 -1);
1177 g_free(title);
1179 purple_blist_node_set_bool(node, "collapsed", FALSE);
1180 pidgin_blist_tooltip_destroy();
1184 static void gtk_blist_row_collapsed_cb(GtkTreeView *tv, GtkTreeIter *iter, GtkTreePath *path, gpointer user_data)
1186 PidginBuddyList *gtkblist = PIDGIN_BUDDY_LIST(user_data);
1187 PurpleBlistNode *node;
1189 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), iter, NODE_COLUMN, &node, -1);
1191 if (PURPLE_IS_GROUP(node)) {
1192 char *title;
1193 PidginBlistNode *gtknode;
1194 PurpleBlistNode *cnode;
1196 title = pidgin_get_group_title(node, FALSE);
1198 gtk_tree_store_set(gtkblist->treemodel, iter,
1199 NAME_COLUMN, title,
1200 -1);
1202 g_free(title);
1204 purple_blist_node_set_bool(node, "collapsed", TRUE);
1206 for(cnode = purple_blist_node_get_first_child(node); cnode; cnode = purple_blist_node_get_sibling_next(cnode)) {
1207 if (PURPLE_IS_CONTACT(cnode)) {
1208 gtknode = purple_blist_node_get_ui_data(cnode);
1209 if (!gtknode->contact_expanded)
1210 continue;
1211 gtknode->contact_expanded = FALSE;
1212 pidgin_blist_update_contact(NULL, cnode);
1215 pidgin_blist_tooltip_destroy();
1216 } else if(PURPLE_IS_CONTACT(node)) {
1217 pidgin_blist_collapse_contact_cb(NULL, node);
1221 static void gtk_blist_row_activated_cb(GtkTreeView *tv, GtkTreePath *path, GtkTreeViewColumn *col, gpointer data) {
1222 PidginBuddyList *gtkblist = PIDGIN_BUDDY_LIST(data);
1223 PurpleBlistNode *node;
1224 GtkTreeIter iter;
1226 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
1227 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
1229 if(PURPLE_IS_CONTACT(node) || PURPLE_IS_BUDDY(node)) {
1230 PurpleBuddy *buddy;
1232 if(PURPLE_IS_CONTACT(node))
1233 buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
1234 else
1235 buddy = (PurpleBuddy*)node;
1237 pidgin_dialogs_im_with_user(purple_buddy_get_account(buddy), purple_buddy_get_name(buddy));
1238 } else if (PURPLE_IS_CHAT(node)) {
1239 gtk_blist_join_chat((PurpleChat *)node);
1240 } else if (PURPLE_IS_GROUP(node)) {
1241 /* if (gtk_tree_view_row_expanded(tv, path))
1242 gtk_tree_view_collapse_row(tv, path);
1243 else
1244 gtk_tree_view_expand_row(tv,path,FALSE);*/
1248 static void pidgin_blist_add_chat_cb(void)
1250 GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist->treeview));
1251 GtkTreeIter iter;
1252 PurpleBlistNode *node;
1254 if(gtk_tree_selection_get_selected(sel, NULL, &iter)){
1255 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
1256 if (PURPLE_IS_BUDDY(node))
1257 purple_blist_request_add_chat(NULL, purple_buddy_get_group(PURPLE_BUDDY(node)), NULL, NULL);
1258 if (PURPLE_IS_CONTACT(node) || PURPLE_IS_CHAT(node))
1259 purple_blist_request_add_chat(NULL, purple_contact_get_group(PURPLE_CONTACT(node)), NULL, NULL);
1260 else if (PURPLE_IS_GROUP(node))
1261 purple_blist_request_add_chat(NULL, (PurpleGroup*)node, NULL, NULL);
1263 else {
1264 purple_blist_request_add_chat(NULL, NULL, NULL, NULL);
1268 static void pidgin_blist_add_buddy_cb(void)
1270 GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist->treeview));
1271 GtkTreeIter iter;
1272 PurpleBlistNode *node;
1274 if(gtk_tree_selection_get_selected(sel, NULL, &iter)){
1275 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
1276 if (PURPLE_IS_BUDDY(node)) {
1277 PurpleGroup *group = purple_buddy_get_group(PURPLE_BUDDY(node));
1278 purple_blist_request_add_buddy(NULL, NULL, purple_group_get_name(group), NULL);
1279 } else if (PURPLE_IS_CONTACT(node) || PURPLE_IS_CHAT(node)) {
1280 PurpleGroup *group = purple_contact_get_group(PURPLE_CONTACT(node));
1281 purple_blist_request_add_buddy(NULL, NULL, purple_group_get_name(group), NULL);
1282 } else if (PURPLE_IS_GROUP(node)) {
1283 purple_blist_request_add_buddy(NULL, NULL, purple_group_get_name(PURPLE_GROUP(node)), NULL);
1286 else {
1287 purple_blist_request_add_buddy(NULL, NULL, NULL, NULL);
1291 static void
1292 pidgin_blist_remove_cb (GtkWidget *w, PurpleBlistNode *node)
1294 if (PURPLE_IS_BUDDY(node)) {
1295 pidgin_dialogs_remove_buddy((PurpleBuddy*)node);
1296 } else if (PURPLE_IS_CHAT(node)) {
1297 pidgin_dialogs_remove_chat((PurpleChat*)node);
1298 } else if (PURPLE_IS_GROUP(node)) {
1299 pidgin_dialogs_remove_group((PurpleGroup*)node);
1300 } else if (PURPLE_IS_CONTACT(node)) {
1301 pidgin_dialogs_remove_contact((PurpleContact*)node);
1305 struct _expand {
1306 GtkTreeView *treeview;
1307 GtkTreePath *path;
1308 PurpleBlistNode *node;
1311 static gboolean
1312 scroll_to_expanded_cell(gpointer data)
1314 struct _expand *ex = data;
1315 gtk_tree_view_scroll_to_cell(ex->treeview, ex->path, NULL, FALSE, 0, 0);
1316 pidgin_blist_update_contact(NULL, ex->node);
1318 gtk_tree_path_free(ex->path);
1319 g_free(ex);
1321 return FALSE;
1324 static void
1325 pidgin_blist_expand_contact_cb(GtkWidget *w, PurpleBlistNode *node)
1327 PidginBlistNode *gtknode;
1328 GtkTreeIter iter, parent;
1329 PurpleBlistNode *bnode;
1330 GtkTreePath *path;
1332 if(!PURPLE_IS_CONTACT(node))
1333 return;
1335 gtknode = purple_blist_node_get_ui_data(node);
1337 gtknode->contact_expanded = TRUE;
1339 for(bnode = purple_blist_node_get_first_child(node); bnode; bnode = purple_blist_node_get_sibling_next(bnode)) {
1340 pidgin_blist_update(NULL, bnode);
1343 /* This ensures that the bottom buddy is visible, i.e. not scrolled off the alignment */
1344 if (get_iter_from_node(node, &parent)) {
1345 struct _expand *ex = g_new0(struct _expand, 1);
1347 gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(gtkblist->treemodel), &iter, &parent,
1348 gtk_tree_model_iter_n_children(GTK_TREE_MODEL(gtkblist->treemodel), &parent) -1);
1349 path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &iter);
1351 /* Let the treeview draw so it knows where to scroll */
1352 ex->treeview = GTK_TREE_VIEW(gtkblist->treeview);
1353 ex->path = path;
1354 ex->node = purple_blist_node_get_first_child(node);
1355 g_idle_add(scroll_to_expanded_cell, ex);
1359 static void
1360 pidgin_blist_collapse_contact_cb(GtkWidget *w, PurpleBlistNode *node)
1362 PurpleBlistNode *bnode;
1363 PidginBlistNode *gtknode;
1365 if(!PURPLE_IS_CONTACT(node))
1366 return;
1368 gtknode = purple_blist_node_get_ui_data(node);
1370 gtknode->contact_expanded = FALSE;
1372 for(bnode = purple_blist_node_get_first_child(node); bnode; bnode = purple_blist_node_get_sibling_next(bnode)) {
1373 pidgin_blist_update(NULL, bnode);
1377 static void
1378 toggle_privacy(GtkWidget *widget, PurpleBlistNode *node)
1380 PurpleBuddy *buddy;
1381 PurpleAccount *account;
1382 gboolean permitted;
1383 const char *name;
1385 if (!PURPLE_IS_BUDDY(node))
1386 return;
1388 buddy = (PurpleBuddy *)node;
1389 account = purple_buddy_get_account(buddy);
1390 name = purple_buddy_get_name(buddy);
1392 permitted = purple_account_privacy_check(account, name);
1394 /* XXX: Perhaps ask whether to restore the previous lists where appropirate? */
1396 if (permitted)
1397 purple_account_privacy_deny(account, name);
1398 else
1399 purple_account_privacy_allow(account, name);
1401 pidgin_blist_update(purple_blist_get_default(), node);
1404 void pidgin_append_blist_node_privacy_menu(GtkWidget *menu, PurpleBlistNode *node)
1406 PurpleBuddy *buddy = (PurpleBuddy *)node;
1407 PurpleAccount *account;
1408 gboolean permitted;
1410 account = purple_buddy_get_account(buddy);
1411 permitted = purple_account_privacy_check(account, purple_buddy_get_name(buddy));
1413 pidgin_new_menu_item(menu, permitted ? _("_Block") : _("Un_block"),
1414 permitted ? PIDGIN_STOCK_TOOLBAR_BLOCK : PIDGIN_STOCK_TOOLBAR_UNBLOCK,
1415 G_CALLBACK(toggle_privacy), node);
1418 void
1419 pidgin_append_blist_node_proto_menu(GtkWidget *menu, PurpleConnection *gc,
1420 PurpleBlistNode *node)
1422 GList *l, *ll;
1423 PurpleProtocol *protocol = purple_connection_get_protocol(gc);
1425 if(!protocol || !PURPLE_PROTOCOL_IMPLEMENTS(protocol, CLIENT, blist_node_menu))
1426 return;
1428 for(l = ll = purple_protocol_client_iface_blist_node_menu(protocol, node); l; l = l->next) {
1429 PurpleActionMenu *act = (PurpleActionMenu *) l->data;
1430 pidgin_append_menu_action(menu, act, node);
1432 g_list_free(ll);
1435 void
1436 pidgin_append_blist_node_extended_menu(GtkWidget *menu, PurpleBlistNode *node)
1438 GList *l, *ll;
1440 for(l = ll = purple_blist_node_get_extended_menu(node); l; l = l->next) {
1441 PurpleActionMenu *act = (PurpleActionMenu *) l->data;
1442 pidgin_append_menu_action(menu, act, node);
1444 g_list_free(ll);
1449 static void
1450 pidgin_append_blist_node_move_to_menu(GtkWidget *menu, PurpleBlistNode *node)
1452 GtkWidget *submenu;
1453 GtkWidget *menuitem;
1454 PurpleBlistNode *group;
1456 menuitem = gtk_menu_item_new_with_label(_("Move to"));
1457 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1458 gtk_widget_show(menuitem);
1460 submenu = gtk_menu_new();
1461 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
1463 for (group = purple_blist_get_default_root(); group;
1464 group = purple_blist_node_get_sibling_next(group)) {
1465 if (!PURPLE_IS_GROUP(group))
1466 continue;
1467 if (group == purple_blist_node_get_parent(node))
1468 continue;
1469 menuitem = pidgin_new_menu_item(submenu,
1470 purple_group_get_name((PurpleGroup *)group), NULL,
1471 G_CALLBACK(gtk_blist_menu_move_to_cb), node);
1472 g_object_set_data(G_OBJECT(menuitem), "groupnode", group);
1474 gtk_widget_show_all(submenu);
1477 void
1478 pidgin_blist_make_buddy_menu(GtkWidget *menu, PurpleBuddy *buddy, gboolean sub) {
1479 PurpleAccount *account = NULL;
1480 PurpleConnection *pc = NULL;
1481 PurpleProtocol *protocol;
1482 PurpleContact *contact;
1483 PurpleBlistNode *node;
1484 gboolean contact_expanded = FALSE;
1486 g_return_if_fail(menu);
1487 g_return_if_fail(buddy);
1489 account = purple_buddy_get_account(buddy);
1490 pc = purple_account_get_connection(account);
1491 protocol = purple_connection_get_protocol(pc);
1493 node = PURPLE_BLIST_NODE(buddy);
1495 contact = purple_buddy_get_contact(buddy);
1496 if (contact) {
1497 PidginBlistNode *node = purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(contact));
1498 contact_expanded = node->contact_expanded;
1501 if (protocol && PURPLE_PROTOCOL_IMPLEMENTS(protocol, SERVER, get_info)) {
1502 pidgin_new_menu_item(menu, _("Get _Info"), PIDGIN_STOCK_TOOLBAR_USER_INFO,
1503 G_CALLBACK(gtk_blist_menu_info_cb), buddy);
1505 pidgin_new_menu_item(menu, _("I_M"), PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW,
1506 G_CALLBACK(gtk_blist_menu_im_cb), buddy);
1508 #ifdef USE_VV
1509 if (protocol && PURPLE_PROTOCOL_IMPLEMENTS(protocol, MEDIA, get_caps)) {
1510 PurpleAccount *account = purple_buddy_get_account(buddy);
1511 const gchar *who = purple_buddy_get_name(buddy);
1512 PurpleMediaCaps caps = purple_protocol_get_media_caps(account, who);
1513 if (caps & PURPLE_MEDIA_CAPS_AUDIO) {
1514 pidgin_new_menu_item(menu, _("_Audio Call"),
1515 PIDGIN_STOCK_TOOLBAR_AUDIO_CALL,
1516 G_CALLBACK(gtk_blist_menu_audio_call_cb), buddy);
1518 if (caps & PURPLE_MEDIA_CAPS_AUDIO_VIDEO) {
1519 pidgin_new_menu_item(menu, _("Audio/_Video Call"),
1520 PIDGIN_STOCK_TOOLBAR_VIDEO_CALL,
1521 G_CALLBACK(gtk_blist_menu_video_call_cb), buddy);
1522 } else if (caps & PURPLE_MEDIA_CAPS_VIDEO) {
1523 pidgin_new_menu_item(menu, _("_Video Call"),
1524 PIDGIN_STOCK_TOOLBAR_VIDEO_CALL,
1525 G_CALLBACK(gtk_blist_menu_video_call_cb), buddy);
1529 #endif
1531 if (protocol && PURPLE_IS_PROTOCOL_XFER(protocol)) {
1532 if (purple_protocol_xfer_can_receive(
1533 PURPLE_PROTOCOL_XFER(protocol),
1534 purple_account_get_connection(purple_buddy_get_account(buddy)), purple_buddy_get_name(buddy)
1535 )) {
1536 pidgin_new_menu_item(menu, _("_Send File..."),
1537 PIDGIN_STOCK_TOOLBAR_SEND_FILE,
1538 G_CALLBACK(gtk_blist_menu_send_file_cb),
1539 buddy);
1543 pidgin_new_menu_item(menu, _("Add Buddy _Pounce..."), NULL,
1544 G_CALLBACK(gtk_blist_menu_bp_cb), buddy);
1546 if (node->parent && node->parent->child->next &&
1547 !sub && !contact_expanded) {
1548 pidgin_new_menu_item(menu, _("View _Log"), NULL,
1549 G_CALLBACK(gtk_blist_menu_showlog_cb), contact);
1550 } else if (!sub) {
1551 pidgin_new_menu_item(menu, _("View _Log"), NULL,
1552 G_CALLBACK(gtk_blist_menu_showlog_cb), buddy);
1555 if (!purple_blist_node_is_transient(node)) {
1556 gboolean show_offline = purple_blist_node_get_bool(node, "show_offline");
1557 pidgin_new_menu_item(menu,
1558 show_offline ? _("Hide When Offline") : _("Show When Offline"),
1559 NULL, G_CALLBACK(gtk_blist_menu_showoffline_cb), node);
1562 pidgin_append_blist_node_proto_menu(menu, purple_account_get_connection(purple_buddy_get_account(buddy)), node);
1563 pidgin_append_blist_node_extended_menu(menu, node);
1565 if (!contact_expanded && contact != NULL)
1566 pidgin_append_blist_node_move_to_menu(menu, PURPLE_BLIST_NODE(contact));
1568 if (node->parent && node->parent->child->next &&
1569 !sub && !contact_expanded) {
1570 pidgin_separator(menu);
1571 pidgin_append_blist_node_privacy_menu(menu, node);
1572 pidgin_new_menu_item(menu, _("_Alias..."), PIDGIN_STOCK_ALIAS,
1573 G_CALLBACK(gtk_blist_menu_alias_cb), contact);
1574 pidgin_new_menu_item(menu, _("_Remove"), GTK_STOCK_REMOVE,
1575 G_CALLBACK(pidgin_blist_remove_cb), contact);
1576 } else if (!sub || contact_expanded) {
1577 pidgin_separator(menu);
1578 pidgin_append_blist_node_privacy_menu(menu, node);
1579 pidgin_new_menu_item(menu, _("_Alias..."), PIDGIN_STOCK_ALIAS,
1580 G_CALLBACK(gtk_blist_menu_alias_cb), buddy);
1581 pidgin_new_menu_item(menu, _("_Remove"), GTK_STOCK_REMOVE,
1582 G_CALLBACK(pidgin_blist_remove_cb), buddy);
1586 static gboolean
1587 gtk_blist_key_press_cb(GtkWidget *tv, GdkEventKey *event, gpointer data)
1589 PurpleBlistNode *node;
1590 GtkTreeIter iter, parent;
1591 GtkTreeSelection *sel;
1592 GtkTreePath *path;
1594 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv));
1595 if(!gtk_tree_selection_get_selected(sel, NULL, &iter))
1596 return FALSE;
1598 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
1600 if(event->state & GDK_CONTROL_MASK &&
1601 (event->keyval == 'o' || event->keyval == 'O')) {
1602 PurpleBuddy *buddy;
1604 if(PURPLE_IS_CONTACT(node)) {
1605 buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
1606 } else if(PURPLE_IS_BUDDY(node)) {
1607 buddy = (PurpleBuddy*)node;
1608 } else {
1609 return FALSE;
1611 if(buddy)
1612 pidgin_retrieve_user_info(purple_account_get_connection(purple_buddy_get_account(buddy)), purple_buddy_get_name(buddy));
1613 } else {
1614 switch (event->keyval) {
1615 case GDK_KEY_F2:
1616 gtk_blist_menu_alias_cb(tv, node);
1617 break;
1619 case GDK_KEY_Left:
1620 path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &iter);
1621 if (gtk_tree_view_row_expanded(GTK_TREE_VIEW(tv), path)) {
1622 /* Collapse the Group */
1623 gtk_tree_view_collapse_row(GTK_TREE_VIEW(tv), path);
1624 gtk_tree_path_free(path);
1625 return TRUE;
1626 } else {
1627 /* Select the Parent */
1628 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path)) {
1629 if (gtk_tree_model_iter_parent(GTK_TREE_MODEL(gtkblist->treemodel), &parent, &iter)) {
1630 gtk_tree_path_free(path);
1631 path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &parent);
1632 gtk_tree_view_set_cursor(GTK_TREE_VIEW(tv), path, NULL, FALSE);
1633 gtk_tree_path_free(path);
1634 return TRUE;
1638 gtk_tree_path_free(path);
1639 break;
1641 case GDK_KEY_Right:
1642 path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &iter);
1643 if (!gtk_tree_view_row_expanded(GTK_TREE_VIEW(tv), path)) {
1644 /* Expand the Group */
1645 if (PURPLE_IS_CONTACT(node)) {
1646 pidgin_blist_expand_contact_cb(NULL, node);
1647 gtk_tree_path_free(path);
1648 return TRUE;
1649 } else if (!PURPLE_IS_BUDDY(node)) {
1650 gtk_tree_view_expand_row(GTK_TREE_VIEW(tv), path, FALSE);
1651 gtk_tree_path_free(path);
1652 return TRUE;
1654 } else {
1655 /* Select the First Child */
1656 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &parent, path)) {
1657 if (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(gtkblist->treemodel), &iter, &parent, 0)) {
1658 gtk_tree_path_free(path);
1659 path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &iter);
1660 gtk_tree_view_set_cursor(GTK_TREE_VIEW(tv), path, NULL, FALSE);
1661 gtk_tree_path_free(path);
1662 return TRUE;
1666 gtk_tree_path_free(path);
1667 break;
1671 return FALSE;
1674 static void
1675 set_node_custom_icon_cb(const gchar *filename, gpointer data)
1677 if (filename) {
1678 PurpleBlistNode *node = (PurpleBlistNode*)data;
1680 purple_buddy_icons_node_set_custom_icon_from_file(node,
1681 filename);
1683 g_object_set_data(G_OBJECT(data), "buddy-icon-chooser", NULL);
1686 static void
1687 set_node_custom_icon(GtkWidget *w, PurpleBlistNode *node)
1689 GtkWidget *win = g_object_get_data(G_OBJECT(node), "buddy-icon-chooser");
1690 if (win == NULL) {
1691 win = pidgin_buddy_icon_chooser_new(NULL, set_node_custom_icon_cb,
1692 node);
1693 g_object_set_data_full(G_OBJECT(node), "buddy-icon-chooser", win,
1694 (GDestroyNotify)gtk_widget_destroy);
1696 gtk_widget_show_all(win);
1699 static void
1700 remove_node_custom_icon(GtkWidget *w, PurpleBlistNode *node)
1702 purple_buddy_icons_node_set_custom_icon(node, NULL, 0);
1705 static void
1706 add_buddy_icon_menu_items(GtkWidget *menu, PurpleBlistNode *node)
1708 GtkWidget *item;
1710 pidgin_new_menu_item(menu, _("Set Custom Icon"), NULL,
1711 G_CALLBACK(set_node_custom_icon), node);
1713 item = pidgin_new_menu_item(menu, _("Remove Custom Icon"), NULL,
1714 G_CALLBACK(remove_node_custom_icon), node);
1715 if (!purple_buddy_icons_node_has_custom_icon(node))
1716 gtk_widget_set_sensitive(item, FALSE);
1719 static GtkWidget *
1720 create_group_menu (PurpleBlistNode *node, PurpleGroup *g)
1722 GtkWidget *menu;
1723 GtkWidget *item;
1725 menu = gtk_menu_new();
1726 item = pidgin_new_menu_item(menu, _("Add _Buddy..."), GTK_STOCK_ADD,
1727 G_CALLBACK(pidgin_blist_add_buddy_cb), node);
1728 gtk_widget_set_sensitive(item, purple_connections_get_all() != NULL);
1729 item = pidgin_new_menu_item(menu, _("Add C_hat..."), GTK_STOCK_ADD,
1730 G_CALLBACK(pidgin_blist_add_chat_cb), node);
1731 gtk_widget_set_sensitive(item, pidgin_blist_joinchat_is_showable());
1732 pidgin_new_menu_item(menu, _("_Delete Group"), GTK_STOCK_REMOVE,
1733 G_CALLBACK(pidgin_blist_remove_cb), node);
1734 pidgin_new_menu_item(menu, _("_Rename"), NULL,
1735 G_CALLBACK(gtk_blist_menu_alias_cb), node);
1736 if (!purple_blist_node_is_transient(node)) {
1737 gboolean show_offline = purple_blist_node_get_bool(node, "show_offline");
1738 pidgin_new_menu_item(menu,
1739 show_offline ? _("Hide When Offline") : _("Show When Offline"),
1740 NULL, G_CALLBACK(gtk_blist_menu_showoffline_cb), node);
1743 add_buddy_icon_menu_items(menu, node);
1745 pidgin_append_blist_node_extended_menu(menu, node);
1747 return menu;
1750 static GtkWidget *
1751 create_chat_menu(PurpleBlistNode *node, PurpleChat *c)
1753 GtkWidget *menu;
1754 gboolean autojoin, persistent;
1756 menu = gtk_menu_new();
1757 autojoin = purple_blist_node_get_bool(node, "gtk-autojoin");
1758 persistent = purple_blist_node_get_bool(node, "gtk-persistent");
1760 pidgin_new_menu_item(menu, _("_Join"), PIDGIN_STOCK_CHAT,
1761 G_CALLBACK(gtk_blist_menu_join_cb), node);
1762 pidgin_new_check_item(menu, _("Auto-Join"),
1763 G_CALLBACK(gtk_blist_menu_autojoin_cb), node, autojoin);
1764 pidgin_new_check_item(menu, _("Persistent"),
1765 G_CALLBACK(gtk_blist_menu_persistent_cb), node, persistent);
1766 pidgin_new_menu_item(menu, _("View _Log"), NULL,
1767 G_CALLBACK(gtk_blist_menu_showlog_cb), node);
1769 pidgin_append_blist_node_proto_menu(menu, purple_account_get_connection(purple_chat_get_account(c)), node);
1770 pidgin_append_blist_node_extended_menu(menu, node);
1772 pidgin_separator(menu);
1774 pidgin_new_menu_item(menu, _("_Edit Settings..."), NULL,
1775 G_CALLBACK(chat_components_edit), node);
1776 pidgin_new_menu_item(menu, _("_Alias..."), PIDGIN_STOCK_ALIAS,
1777 G_CALLBACK(gtk_blist_menu_alias_cb), node);
1778 pidgin_new_menu_item(menu, _("_Remove"), GTK_STOCK_REMOVE,
1779 G_CALLBACK(pidgin_blist_remove_cb), node);
1781 add_buddy_icon_menu_items(menu, node);
1783 return menu;
1786 static GtkWidget *
1787 create_contact_menu (PurpleBlistNode *node)
1789 GtkWidget *menu;
1791 menu = gtk_menu_new();
1793 pidgin_new_menu_item(menu, _("View _Log"), NULL,
1794 G_CALLBACK(gtk_blist_menu_showlog_cb),
1795 node);
1797 pidgin_separator(menu);
1799 pidgin_new_menu_item(menu, _("_Alias..."), PIDGIN_STOCK_ALIAS,
1800 G_CALLBACK(gtk_blist_menu_alias_cb), node);
1801 pidgin_new_menu_item(menu, _("_Remove"), GTK_STOCK_REMOVE,
1802 G_CALLBACK(pidgin_blist_remove_cb), node);
1804 add_buddy_icon_menu_items(menu, node);
1806 pidgin_separator(menu);
1808 pidgin_new_menu_item(menu, _("_Collapse"), GTK_STOCK_ZOOM_OUT,
1809 G_CALLBACK(pidgin_blist_collapse_contact_cb),
1810 node);
1812 pidgin_append_blist_node_extended_menu(menu, node);
1813 return menu;
1816 static GtkWidget *
1817 create_buddy_menu(PurpleBlistNode *node, PurpleBuddy *b)
1819 PidginBlistNode *gtknode = purple_blist_node_get_ui_data(node);
1820 GtkWidget *menu;
1821 GtkWidget *menuitem;
1822 gboolean show_offline = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_offline_buddies");
1824 menu = gtk_menu_new();
1825 pidgin_blist_make_buddy_menu(menu, b, FALSE);
1827 if(PURPLE_IS_CONTACT(node)) {
1828 pidgin_separator(menu);
1830 add_buddy_icon_menu_items(menu, node);
1832 if(gtknode->contact_expanded) {
1833 pidgin_new_menu_item(menu, _("_Collapse"),
1834 GTK_STOCK_ZOOM_OUT,
1835 G_CALLBACK(pidgin_blist_collapse_contact_cb),
1836 node);
1837 } else {
1838 pidgin_new_menu_item(menu, _("_Expand"),
1839 GTK_STOCK_ZOOM_IN,
1840 G_CALLBACK(pidgin_blist_expand_contact_cb),
1841 node);
1843 if(node->child->next) {
1844 PurpleBlistNode *bnode;
1846 for(bnode = node->child; bnode; bnode = bnode->next) {
1847 PurpleBuddy *buddy = (PurpleBuddy*)bnode;
1848 GdkPixbuf *buf;
1849 GtkWidget *submenu;
1850 GtkWidget *image;
1852 if(buddy == b)
1853 continue;
1854 if(!purple_account_get_connection(purple_buddy_get_account(buddy)))
1855 continue;
1856 if(!show_offline && !PURPLE_BUDDY_IS_ONLINE(buddy))
1857 continue;
1859 menuitem = gtk_image_menu_item_new_with_label(purple_buddy_get_name(buddy));
1860 buf = pidgin_create_protocol_icon(purple_buddy_get_account(buddy), PIDGIN_PROTOCOL_ICON_SMALL);
1861 image = gtk_image_new_from_pixbuf(buf);
1862 g_object_unref(G_OBJECT(buf));
1863 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem),
1864 image);
1865 gtk_widget_show(image);
1866 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1867 gtk_widget_show(menuitem);
1869 submenu = gtk_menu_new();
1870 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
1871 gtk_widget_show(submenu);
1873 pidgin_blist_make_buddy_menu(submenu, buddy, TRUE);
1877 return menu;
1880 static gboolean
1881 pidgin_blist_show_context_menu(GtkWidget *tv, PurpleBlistNode *node, GdkEvent *event)
1883 PidginBlistNode *gtknode = purple_blist_node_get_ui_data(node);
1884 GtkWidget *menu = NULL;
1885 gboolean handled = FALSE;
1887 /* Create a menu based on the thing we right-clicked on */
1888 if (PURPLE_IS_GROUP(node)) {
1889 PurpleGroup *g = (PurpleGroup *)node;
1891 menu = create_group_menu(node, g);
1892 } else if (PURPLE_IS_CHAT(node)) {
1893 PurpleChat *c = (PurpleChat *)node;
1895 menu = create_chat_menu(node, c);
1896 } else if ((PURPLE_IS_CONTACT(node)) && (gtknode->contact_expanded)) {
1897 menu = create_contact_menu(node);
1898 } else if (PURPLE_IS_CONTACT(node) || PURPLE_IS_BUDDY(node)) {
1899 PurpleBuddy *b;
1901 if (PURPLE_IS_CONTACT(node))
1902 b = purple_contact_get_priority_buddy((PurpleContact*)node);
1903 else
1904 b = (PurpleBuddy *)node;
1906 menu = create_buddy_menu(node, b);
1909 #ifdef _WIN32
1910 pidgin_blist_tooltip_destroy();
1912 /* Unhook the tooltip-timeout since we don't want a tooltip
1913 * to appear and obscure the context menu we are about to show
1914 This is a workaround for GTK+ bug 107320. */
1915 if (gtkblist->timeout) {
1916 g_source_remove(gtkblist->timeout);
1917 gtkblist->timeout = 0;
1919 #endif
1921 /* Now display the menu */
1922 if (menu != NULL) {
1923 gtk_widget_show_all(menu);
1924 if (event != NULL) {
1925 /* Pointer event */
1926 gtk_menu_popup_at_pointer(GTK_MENU(menu), event);
1927 } else {
1928 /* Keyboard event */
1929 pidgin_menu_popup_at_treeview_selection(menu, tv);
1931 handled = TRUE;
1934 return handled;
1937 static gboolean
1938 gtk_blist_button_press_cb(GtkWidget *tv, GdkEventButton *event, gpointer user_data)
1940 GtkTreePath *path;
1941 PurpleBlistNode *node;
1942 GtkTreeIter iter;
1943 GtkTreeSelection *sel;
1944 PurpleProtocol *protocol = NULL;
1945 PidginBlistNode *gtknode;
1946 gboolean handled = FALSE;
1948 /* Here we figure out which node was clicked */
1949 if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), event->x, event->y, &path, NULL, NULL, NULL))
1950 return FALSE;
1951 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
1952 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
1953 gtknode = purple_blist_node_get_ui_data(node);
1955 /* Right click draws a context menu */
1956 if (gdk_event_triggers_context_menu((GdkEvent *)event)) {
1957 handled = pidgin_blist_show_context_menu(tv, node, (GdkEvent *)event);
1959 /* CTRL+middle click expands or collapse a contact */
1960 } else if ((event->button == GDK_BUTTON_MIDDLE) && (event->type == GDK_BUTTON_PRESS) &&
1961 (event->state & GDK_CONTROL_MASK) && (PURPLE_IS_CONTACT(node))) {
1962 if (gtknode->contact_expanded)
1963 pidgin_blist_collapse_contact_cb(NULL, node);
1964 else
1965 pidgin_blist_expand_contact_cb(NULL, node);
1966 handled = TRUE;
1968 /* Double middle click gets info */
1969 } else if ((event->button == GDK_BUTTON_MIDDLE) && (event->type == GDK_2BUTTON_PRESS) &&
1970 ((PURPLE_IS_CONTACT(node)) || (PURPLE_IS_BUDDY(node)))) {
1971 PurpleBuddy *b;
1972 if(PURPLE_IS_CONTACT(node))
1973 b = purple_contact_get_priority_buddy((PurpleContact*)node);
1974 else
1975 b = (PurpleBuddy *)node;
1977 protocol = purple_protocols_find(purple_account_get_protocol_id(purple_buddy_get_account(b)));
1979 if (protocol && PURPLE_PROTOCOL_IMPLEMENTS(protocol, SERVER, get_info))
1980 pidgin_retrieve_user_info(purple_account_get_connection(purple_buddy_get_account(b)), purple_buddy_get_name(b));
1981 handled = TRUE;
1984 #if (1)
1986 * This code only exists because GTK+ doesn't work. If we return
1987 * FALSE here, as would be normal the event propoagates down and
1988 * somehow gets interpreted as the start of a drag event.
1990 * Um, isn't it _normal_ to return TRUE here? Since the event
1991 * was handled? --Mark
1993 if(handled) {
1994 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv));
1995 gtk_tree_selection_select_path(sel, path);
1996 gtk_tree_path_free(path);
1997 return TRUE;
1999 #endif
2000 gtk_tree_path_free(path);
2002 return FALSE;
2005 static gboolean
2006 pidgin_blist_popup_menu_cb(GtkWidget *tv, void *user_data)
2008 PurpleBlistNode *node;
2009 GtkTreeIter iter;
2010 GtkTreeSelection *sel;
2011 gboolean handled = FALSE;
2013 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv));
2014 if (!gtk_tree_selection_get_selected(sel, NULL, &iter))
2015 return FALSE;
2017 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
2019 /* Shift+F10 draws a context menu */
2020 handled = pidgin_blist_show_context_menu(tv, node, NULL);
2022 return handled;
2025 static void gtk_blist_show_xfer_dialog_cb(GtkAction *item, gpointer data)
2027 pidgin_xfer_dialog_show(NULL);
2030 static void pidgin_blist_buddy_details_cb(GtkToggleAction *item, gpointer data)
2032 pidgin_set_cursor(gtkblist->window, GDK_WATCH);
2034 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons",
2035 gtk_toggle_action_get_active(item));
2037 pidgin_clear_cursor(gtkblist->window);
2040 static void pidgin_blist_show_idle_time_cb(GtkToggleAction *item, gpointer data)
2042 pidgin_set_cursor(gtkblist->window, GDK_WATCH);
2044 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/show_idle_time",
2045 gtk_toggle_action_get_active(item));
2047 pidgin_clear_cursor(gtkblist->window);
2050 static void pidgin_blist_show_protocol_icons_cb(GtkToggleAction *item, gpointer data)
2052 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons",
2053 gtk_toggle_action_get_active(item));
2056 static void pidgin_blist_show_empty_groups_cb(GtkToggleAction *item, gpointer data)
2058 pidgin_set_cursor(gtkblist->window, GDK_WATCH);
2060 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/show_empty_groups",
2061 gtk_toggle_action_get_active(item));
2063 pidgin_clear_cursor(gtkblist->window);
2066 static void pidgin_blist_edit_mode_cb(GtkToggleAction *checkitem, gpointer data)
2068 pidgin_set_cursor(gtkblist->window, GDK_WATCH);
2070 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/show_offline_buddies",
2071 gtk_toggle_action_get_active(checkitem));
2073 pidgin_clear_cursor(gtkblist->window);
2076 static void pidgin_blist_mute_sounds_cb(GtkToggleAction *item, gpointer data)
2078 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/sound/mute",
2079 gtk_toggle_action_get_active(item));
2083 static void
2084 pidgin_blist_mute_pref_cb(const char *name, PurplePrefType type,
2085 gconstpointer value, gpointer data)
2087 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(gtk_ui_manager_get_action(gtkblist->ui,
2088 "/BList/ToolsMenu/MuteSounds")), (gboolean)GPOINTER_TO_INT(value));
2091 static void
2092 pidgin_blist_sound_method_pref_cb(const char *name, PurplePrefType type,
2093 gconstpointer value, gpointer data)
2095 gboolean sensitive = TRUE;
2097 if(purple_strequal(value, "none"))
2098 sensitive = FALSE;
2100 gtk_action_set_sensitive(gtk_ui_manager_get_action(gtkblist->ui, "/BList/ToolsMenu/MuteSounds"), sensitive);
2103 static void
2104 add_buddies_from_vcard(const char *protocol_id, PurpleGroup *group, GList *list,
2105 const char *alias)
2107 GList *l;
2108 PurpleAccount *account = NULL;
2109 PurpleConnection *gc;
2111 if (list == NULL)
2112 return;
2114 for (l = purple_connections_get_all(); l != NULL; l = l->next)
2116 gc = (PurpleConnection *)l->data;
2117 account = purple_connection_get_account(gc);
2119 if (purple_strequal(purple_account_get_protocol_id(account), protocol_id))
2120 break;
2122 account = NULL;
2125 if (account != NULL)
2127 for (l = list; l != NULL; l = l->next)
2129 purple_blist_request_add_buddy(account, l->data,
2130 (group ? purple_group_get_name(group) : NULL),
2131 alias);
2135 g_list_foreach(list, (GFunc)g_free, NULL);
2136 g_list_free(list);
2139 static gboolean
2140 parse_vcard(const char *vcard, PurpleGroup *group)
2142 char *temp_vcard;
2143 char *s, *c;
2144 char *alias = NULL;
2145 GList *aims = NULL;
2146 GList *icqs = NULL;
2147 GList *jabbers = NULL;
2149 s = temp_vcard = g_strdup(vcard);
2151 while (*s != '\0' && strncmp(s, "END:vCard", strlen("END:vCard")))
2153 char *field, *value;
2155 field = s;
2157 /* Grab the field */
2158 while (*s != '\r' && *s != '\n' && *s != '\0' && *s != ':')
2159 s++;
2161 if (*s == '\r') s++;
2162 if (*s == '\n')
2164 s++;
2165 continue;
2168 if (*s != '\0') *s++ = '\0';
2170 if ((c = strchr(field, ';')) != NULL)
2171 *c = '\0';
2173 /* Proceed to the end of the line */
2174 value = s;
2176 while (*s != '\r' && *s != '\n' && *s != '\0')
2177 s++;
2179 if (*s == '\r') *s++ = '\0';
2180 if (*s == '\n') *s++ = '\0';
2182 /* We only want to worry about a few fields here. */
2183 if (purple_strequal(field, "FN"))
2184 alias = g_strdup(value);
2185 else if (purple_strequal(field, "X-AIM") || purple_strequal(field, "X-ICQ") ||
2186 purple_strequal(field, "X-JABBER"))
2188 char **values = g_strsplit(value, ":", 0);
2189 char **im;
2191 for (im = values; *im != NULL; im++)
2193 if (purple_strequal(field, "X-AIM"))
2194 aims = g_list_append(aims, g_strdup(*im));
2195 else if (purple_strequal(field, "X-ICQ"))
2196 icqs = g_list_append(icqs, g_strdup(*im));
2197 else if (purple_strequal(field, "X-JABBER"))
2198 jabbers = g_list_append(jabbers, g_strdup(*im));
2201 g_strfreev(values);
2205 g_free(temp_vcard);
2207 if (aims == NULL && icqs == NULL && jabbers == NULL)
2209 g_free(alias);
2211 return FALSE;
2214 add_buddies_from_vcard("prpl-aim", group, aims, alias);
2215 add_buddies_from_vcard("prpl-icq", group, icqs, alias);
2216 add_buddies_from_vcard("prpl-jabber", group, jabbers, alias);
2218 g_free(alias);
2220 return TRUE;
2223 #ifdef _WIN32
2224 static void pidgin_blist_drag_begin(GtkWidget *widget,
2225 GdkDragContext *drag_context, gpointer user_data)
2227 pidgin_blist_tooltip_destroy();
2230 /* Unhook the tooltip-timeout since we don't want a tooltip
2231 * to appear and obscure the dragging operation.
2232 * This is a workaround for GTK+ bug 107320. */
2233 if (gtkblist->timeout) {
2234 g_source_remove(gtkblist->timeout);
2235 gtkblist->timeout = 0;
2238 #endif
2240 static void pidgin_blist_drag_data_get_cb(GtkWidget *widget,
2241 GdkDragContext *dc,
2242 GtkSelectionData *data,
2243 guint info,
2244 guint time,
2245 gpointer null)
2247 GdkAtom target = gtk_selection_data_get_target(data);
2249 if (target == gdk_atom_intern("PURPLE_BLIST_NODE", FALSE)) {
2250 GtkTreeRowReference *ref = g_object_get_data(G_OBJECT(dc), "gtk-tree-view-source-row");
2251 GtkTreePath *sourcerow = gtk_tree_row_reference_get_path(ref);
2252 GtkTreeIter iter;
2253 PurpleBlistNode *node = NULL;
2254 if(!sourcerow)
2255 return;
2256 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, sourcerow);
2257 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
2258 gtk_selection_data_set (data,
2259 gdk_atom_intern ("PURPLE_BLIST_NODE", FALSE),
2260 8, /* bits */
2261 (void*)&node,
2262 sizeof (node));
2264 gtk_tree_path_free(sourcerow);
2265 } else if (target == gdk_atom_intern("application/x-im-contact", FALSE)) {
2266 GtkTreeRowReference *ref;
2267 GtkTreePath *sourcerow;
2268 GtkTreeIter iter;
2269 PurpleBlistNode *node = NULL;
2270 PurpleBuddy *buddy;
2271 PurpleConnection *gc;
2272 GString *str;
2273 const char *protocol;
2275 ref = g_object_get_data(G_OBJECT(dc), "gtk-tree-view-source-row");
2276 sourcerow = gtk_tree_row_reference_get_path(ref);
2278 if (!sourcerow)
2279 return;
2281 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter,
2282 sourcerow);
2283 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
2285 if (PURPLE_IS_CONTACT(node))
2287 buddy = purple_contact_get_priority_buddy((PurpleContact *)node);
2289 else if (!PURPLE_IS_BUDDY(node))
2291 gtk_tree_path_free(sourcerow);
2292 return;
2294 else
2296 buddy = (PurpleBuddy *)node;
2299 gc = purple_account_get_connection(purple_buddy_get_account(buddy));
2301 if (gc == NULL)
2303 gtk_tree_path_free(sourcerow);
2304 return;
2307 protocol =
2308 purple_protocol_class_list_icon(purple_connection_get_protocol(gc),
2309 purple_buddy_get_account(buddy), buddy);
2311 str = g_string_new(NULL);
2312 g_string_printf(str,
2313 "MIME-Version: 1.0\r\n"
2314 "Content-Type: application/x-im-contact\r\n"
2315 "X-IM-Protocol: %s\r\n"
2316 "X-IM-Username: %s\r\n",
2317 protocol,
2318 purple_buddy_get_name(buddy));
2320 if (purple_buddy_get_local_alias(buddy) != NULL)
2322 g_string_append_printf(str,
2323 "X-IM-Alias: %s\r\n",
2324 purple_buddy_get_local_alias(buddy));
2327 g_string_append(str, "\r\n");
2329 gtk_selection_data_set(data,
2330 gdk_atom_intern("application/x-im-contact", FALSE),
2331 8, /* bits */
2332 (const guchar *)str->str,
2333 strlen(str->str) + 1);
2335 g_string_free(str, TRUE);
2336 gtk_tree_path_free(sourcerow);
2340 static void pidgin_blist_drag_data_rcv_cb(GtkWidget *widget, GdkDragContext *dc, guint x, guint y,
2341 GtkSelectionData *sd, guint info, guint t)
2343 GdkAtom target = gtk_selection_data_get_target(sd);
2344 const guchar *data = gtk_selection_data_get_data(sd);
2346 if (gtkblist->drag_timeout) {
2347 g_source_remove(gtkblist->drag_timeout);
2348 gtkblist->drag_timeout = 0;
2351 if (target == gdk_atom_intern("PURPLE_BLIST_NODE", FALSE) && data) {
2352 PurpleBlistNode *n = NULL;
2353 GtkTreePath *path = NULL;
2354 GtkTreeViewDropPosition position;
2355 memcpy(&n, data, sizeof(n));
2356 if(gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget), x, y, &path, &position)) {
2357 /* if we're here, I think it means the drop is ok */
2358 GtkTreeIter iter;
2359 PurpleBlistNode *node;
2360 PidginBlistNode *gtknode;
2362 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel),
2363 &iter, path);
2364 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel),
2365 &iter, NODE_COLUMN, &node, -1);
2366 gtknode = purple_blist_node_get_ui_data(node);
2368 if (PURPLE_IS_CONTACT(n)) {
2369 PurpleContact *c = (PurpleContact*)n;
2370 if (PURPLE_IS_CONTACT(node) && gtknode->contact_expanded) {
2371 purple_contact_merge(c, node);
2372 } else if (PURPLE_IS_CONTACT(node) ||
2373 PURPLE_IS_CHAT(node)) {
2374 switch(position) {
2375 case GTK_TREE_VIEW_DROP_AFTER:
2376 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
2377 purple_blist_add_contact(c, (PurpleGroup*)node->parent,
2378 node);
2379 break;
2380 case GTK_TREE_VIEW_DROP_BEFORE:
2381 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
2382 purple_blist_add_contact(c, (PurpleGroup*)node->parent,
2383 node->prev);
2384 break;
2386 } else if(PURPLE_IS_GROUP(node)) {
2387 purple_blist_add_contact(c, (PurpleGroup*)node, NULL);
2388 } else if(PURPLE_IS_BUDDY(node)) {
2389 purple_contact_merge(c, node);
2391 } else if (PURPLE_IS_BUDDY(n)) {
2392 PurpleBuddy *b = (PurpleBuddy*)n;
2393 if (PURPLE_IS_BUDDY(node)) {
2394 switch(position) {
2395 case GTK_TREE_VIEW_DROP_AFTER:
2396 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
2397 purple_blist_add_buddy(b, (PurpleContact*)node->parent,
2398 (PurpleGroup*)node->parent->parent, node);
2399 break;
2400 case GTK_TREE_VIEW_DROP_BEFORE:
2401 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
2402 purple_blist_add_buddy(b, (PurpleContact*)node->parent,
2403 (PurpleGroup*)node->parent->parent,
2404 node->prev);
2405 break;
2407 } else if(PURPLE_IS_CHAT(node)) {
2408 purple_blist_add_buddy(b, NULL, (PurpleGroup*)node->parent,
2409 NULL);
2410 } else if (PURPLE_IS_GROUP(node)) {
2411 purple_blist_add_buddy(b, NULL, (PurpleGroup*)node, NULL);
2412 } else if (PURPLE_IS_CONTACT(node)) {
2413 if(gtknode->contact_expanded) {
2414 switch(position) {
2415 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
2416 case GTK_TREE_VIEW_DROP_AFTER:
2417 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
2418 purple_blist_add_buddy(b, (PurpleContact*)node,
2419 (PurpleGroup*)node->parent, NULL);
2420 break;
2421 case GTK_TREE_VIEW_DROP_BEFORE:
2422 purple_blist_add_buddy(b, NULL,
2423 (PurpleGroup*)node->parent, node->prev);
2424 break;
2426 } else {
2427 switch(position) {
2428 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
2429 case GTK_TREE_VIEW_DROP_AFTER:
2430 purple_blist_add_buddy(b, NULL,
2431 (PurpleGroup*)node->parent, NULL);
2432 break;
2433 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
2434 case GTK_TREE_VIEW_DROP_BEFORE:
2435 purple_blist_add_buddy(b, NULL,
2436 (PurpleGroup*)node->parent, node->prev);
2437 break;
2441 } else if (PURPLE_IS_CHAT(n)) {
2442 PurpleChat *chat = (PurpleChat *)n;
2443 if (PURPLE_IS_BUDDY(node)) {
2444 switch(position) {
2445 case GTK_TREE_VIEW_DROP_AFTER:
2446 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
2447 case GTK_TREE_VIEW_DROP_BEFORE:
2448 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
2449 purple_blist_add_chat(chat,
2450 (PurpleGroup*)node->parent->parent,
2451 node->parent);
2452 break;
2454 } else if(PURPLE_IS_CONTACT(node) ||
2455 PURPLE_IS_CHAT(node)) {
2456 switch(position) {
2457 case GTK_TREE_VIEW_DROP_AFTER:
2458 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
2459 purple_blist_add_chat(chat, (PurpleGroup*)node->parent, node);
2460 break;
2461 case GTK_TREE_VIEW_DROP_BEFORE:
2462 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
2463 purple_blist_add_chat(chat, (PurpleGroup*)node->parent, node->prev);
2464 break;
2466 } else if (PURPLE_IS_GROUP(node)) {
2467 purple_blist_add_chat(chat, (PurpleGroup*)node, NULL);
2469 } else if (PURPLE_IS_GROUP(n)) {
2470 PurpleGroup *g = (PurpleGroup*)n;
2471 if (PURPLE_IS_GROUP(node)) {
2472 switch (position) {
2473 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
2474 case GTK_TREE_VIEW_DROP_AFTER:
2475 purple_blist_add_group(g, node);
2476 break;
2477 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
2478 case GTK_TREE_VIEW_DROP_BEFORE:
2479 purple_blist_add_group(g, node->prev);
2480 break;
2482 } else if(PURPLE_IS_BUDDY(node)) {
2483 purple_blist_add_group(g, node->parent->parent);
2484 } else if(PURPLE_IS_CONTACT(node) ||
2485 PURPLE_IS_CHAT(node)) {
2486 purple_blist_add_group(g, node->parent);
2490 gtk_tree_path_free(path);
2491 gtk_drag_finish(dc, TRUE, (gdk_drag_context_get_actions(dc) == GDK_ACTION_MOVE), t);
2493 } else if (target == gdk_atom_intern("application/x-im-contact",
2494 FALSE) && data) {
2495 PurpleGroup *group = NULL;
2496 GtkTreePath *path = NULL;
2497 GtkTreeViewDropPosition position;
2498 PurpleAccount *account;
2499 char *protocol = NULL;
2500 char *username = NULL;
2501 char *alias = NULL;
2503 if (gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget),
2504 x, y, &path, &position))
2506 GtkTreeIter iter;
2507 PurpleBlistNode *node;
2509 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel),
2510 &iter, path);
2511 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel),
2512 &iter, NODE_COLUMN, &node, -1);
2514 if (PURPLE_IS_BUDDY(node))
2516 group = (PurpleGroup *)node->parent->parent;
2518 else if (PURPLE_IS_CHAT(node) ||
2519 PURPLE_IS_CONTACT(node))
2521 group = (PurpleGroup *)node->parent;
2523 else if (PURPLE_IS_GROUP(node))
2525 group = (PurpleGroup *)node;
2529 if (pidgin_parse_x_im_contact((const char *)data, FALSE, &account,
2530 &protocol, &username, &alias))
2532 if (account == NULL)
2534 purple_notify_error(NULL, NULL,
2535 _("You are not currently signed on with an account that "
2536 "can add that buddy."), NULL, NULL);
2538 else
2540 purple_blist_request_add_buddy(account, username,
2541 (group ? purple_group_get_name(group) : NULL),
2542 alias);
2546 g_free(username);
2547 g_free(protocol);
2548 g_free(alias);
2550 if (path != NULL)
2551 gtk_tree_path_free(path);
2553 gtk_drag_finish(dc, TRUE,
2554 gdk_drag_context_get_actions(dc) == GDK_ACTION_MOVE, t);
2556 else if (target == gdk_atom_intern("text/x-vcard", FALSE) && data)
2558 gboolean result;
2559 PurpleGroup *group = NULL;
2560 GtkTreePath *path = NULL;
2561 GtkTreeViewDropPosition position;
2563 if (gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget),
2564 x, y, &path, &position))
2566 GtkTreeIter iter;
2567 PurpleBlistNode *node;
2569 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel),
2570 &iter, path);
2571 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel),
2572 &iter, NODE_COLUMN, &node, -1);
2574 if (PURPLE_IS_BUDDY(node))
2576 group = (PurpleGroup *)node->parent->parent;
2578 else if (PURPLE_IS_CHAT(node) ||
2579 PURPLE_IS_CONTACT(node))
2581 group = (PurpleGroup *)node->parent;
2583 else if (PURPLE_IS_GROUP(node))
2585 group = (PurpleGroup *)node;
2589 result = parse_vcard((const gchar *)data, group);
2591 gtk_drag_finish(dc, result,
2592 gdk_drag_context_get_actions(dc) == GDK_ACTION_MOVE, t);
2593 } else if (target == gdk_atom_intern("text/uri-list", FALSE) && data) {
2594 GtkTreePath *path = NULL;
2595 GtkTreeViewDropPosition position;
2597 if (gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget),
2598 x, y, &path, &position))
2600 GtkTreeIter iter;
2601 PurpleBlistNode *node;
2603 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel),
2604 &iter, path);
2605 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel),
2606 &iter, NODE_COLUMN, &node, -1);
2608 if (PURPLE_IS_BUDDY(node) || PURPLE_IS_CONTACT(node)) {
2609 PurpleBuddy *b = PURPLE_IS_BUDDY(node) ? PURPLE_BUDDY(node) : purple_contact_get_priority_buddy(PURPLE_CONTACT(node));
2610 pidgin_dnd_file_manage(sd, purple_buddy_get_account(b), purple_buddy_get_name(b));
2611 gtk_drag_finish(dc, TRUE,
2612 gdk_drag_context_get_actions(dc) == GDK_ACTION_MOVE, t);
2613 } else {
2614 gtk_drag_finish(dc, FALSE, FALSE, t);
2620 /* Altered from do_colorshift in gnome-panel */
2621 static void
2622 do_alphashift(GdkPixbuf *pixbuf, int shift)
2624 gint i, j;
2625 gint width, height, padding;
2626 guchar *pixels;
2627 int val;
2629 if (!gdk_pixbuf_get_has_alpha(pixbuf))
2630 return;
2632 width = gdk_pixbuf_get_width(pixbuf);
2633 height = gdk_pixbuf_get_height(pixbuf);
2634 padding = gdk_pixbuf_get_rowstride(pixbuf) - width * 4;
2635 pixels = gdk_pixbuf_get_pixels(pixbuf);
2637 for (i = 0; i < height; i++) {
2638 for (j = 0; j < width; j++) {
2639 pixels++;
2640 pixels++;
2641 pixels++;
2642 val = *pixels - shift;
2643 *(pixels++) = CLAMP(val, 0, 255);
2645 pixels += padding;
2650 static GdkPixbuf *pidgin_blist_get_buddy_icon(PurpleBlistNode *node,
2651 gboolean scaled, gboolean greyed)
2653 gsize len;
2654 PurpleBuddy *buddy = NULL;
2655 PurpleGroup *group = NULL;
2656 const guchar *data = NULL;
2657 GdkPixbuf *buf, *ret = NULL;
2658 PurpleBuddyIcon *icon = NULL;
2659 PurpleAccount *account = NULL;
2660 PurpleContact *contact = NULL;
2661 PurpleImage *custom_img;
2662 PurpleProtocol *protocol = NULL;
2663 PurpleBuddyIconSpec *icon_spec = NULL;
2664 gint orig_width, orig_height, scale_width, scale_height;
2666 if (PURPLE_IS_CONTACT(node)) {
2667 buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
2668 contact = (PurpleContact*)node;
2669 } else if (PURPLE_IS_BUDDY(node)) {
2670 buddy = (PurpleBuddy*)node;
2671 contact = purple_buddy_get_contact(buddy);
2672 } else if (PURPLE_IS_GROUP(node)) {
2673 group = (PurpleGroup*)node;
2674 } else if (PURPLE_IS_CHAT(node)) {
2675 /* We don't need to do anything here. We just need to not fall
2676 * into the else block and return. */
2677 } else {
2678 return NULL;
2681 if (buddy) {
2682 account = purple_buddy_get_account(buddy);
2685 if(account && purple_account_get_connection(account)) {
2686 protocol = purple_connection_get_protocol(purple_account_get_connection(account));
2689 #if 0
2690 if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons"))
2691 return NULL;
2692 #endif
2694 /* If we have a contact then this is either a contact or a buddy and
2695 * we want to fetch the custom icon for the contact. If we don't have
2696 * a contact then this is a group or some other type of node and we
2697 * want to use that directly. */
2698 if (contact) {
2699 custom_img = purple_buddy_icons_node_find_custom_icon(PURPLE_BLIST_NODE(contact));
2700 } else {
2701 custom_img = purple_buddy_icons_node_find_custom_icon(node);
2704 if (custom_img) {
2705 data = purple_image_get_data(custom_img);
2706 len = purple_image_get_data_size(custom_img);
2709 if (data == NULL) {
2710 if (buddy) {
2711 /* Not sure I like this...*/
2712 if (!(icon = purple_buddy_icons_find(purple_buddy_get_account(buddy), purple_buddy_get_name(buddy))))
2713 return NULL;
2714 data = purple_buddy_icon_get_data(icon, &len);
2717 if(data == NULL)
2718 return NULL;
2721 buf = pidgin_pixbuf_from_data(data, len);
2722 purple_buddy_icon_unref(icon);
2723 if (!buf) {
2724 purple_debug_warning("gtkblist", "Couldn't load buddy icon on "
2725 "account %s (%s); buddyname=%s; custom_img_size=%" G_GSIZE_FORMAT,
2726 account ? purple_account_get_username(account) : "(no account)",
2727 account ? purple_account_get_protocol_id(account) : "(no account)",
2728 buddy ? purple_buddy_get_name(buddy) : "(no buddy)",
2729 custom_img ? purple_image_get_data_size(custom_img) : 0);
2730 if (custom_img)
2731 g_object_unref(custom_img);
2732 return NULL;
2734 if (custom_img)
2735 g_object_unref(custom_img);
2737 if (greyed) {
2738 gboolean offline = FALSE, idle = FALSE;
2740 if (buddy) {
2741 PurplePresence *presence = purple_buddy_get_presence(buddy);
2742 if (!PURPLE_BUDDY_IS_ONLINE(buddy))
2743 offline = TRUE;
2744 if (purple_presence_is_idle(presence))
2745 idle = TRUE;
2746 } else if (group) {
2747 if (purple_counting_node_get_online_count(PURPLE_COUNTING_NODE(group)) == 0)
2748 offline = TRUE;
2751 if (offline)
2752 gdk_pixbuf_saturate_and_pixelate(buf, buf, 0.0, FALSE);
2754 if (idle)
2755 gdk_pixbuf_saturate_and_pixelate(buf, buf, 0.25, FALSE);
2758 /* I'd use the pidgin_buddy_icon_get_scale_size() thing, but it won't
2759 * tell me the original size, which I need for scaling purposes. */
2760 scale_width = orig_width = gdk_pixbuf_get_width(buf);
2761 scale_height = orig_height = gdk_pixbuf_get_height(buf);
2763 if (protocol)
2764 icon_spec = purple_protocol_get_icon_spec(protocol);
2766 if (icon_spec && icon_spec->scale_rules & PURPLE_ICON_SCALE_DISPLAY)
2767 purple_buddy_icon_spec_get_scaled_size(purple_protocol_get_icon_spec(protocol), &scale_width, &scale_height);
2769 if (scaled || scale_height > 200 || scale_width > 200) {
2770 GdkPixbuf *tmpbuf;
2771 float scale_size = scaled ? 32.0 : 200.0;
2772 if(scale_height > scale_width) {
2773 scale_width = scale_size * (double)scale_width / (double)scale_height;
2774 scale_height = scale_size;
2775 } else {
2776 scale_height = scale_size * (double)scale_height / (double)scale_width;
2777 scale_width = scale_size;
2779 /* Scale & round before making square, so rectangular (but
2780 * non-square) images get rounded corners too. */
2781 tmpbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, scale_width, scale_height);
2782 gdk_pixbuf_fill(tmpbuf, 0x00000000);
2783 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);
2784 if (pidgin_gdk_pixbuf_is_opaque(tmpbuf))
2785 pidgin_gdk_pixbuf_make_round(tmpbuf);
2786 ret = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, scale_size, scale_size);
2787 gdk_pixbuf_fill(ret, 0x00000000);
2788 gdk_pixbuf_copy_area(tmpbuf, 0, 0, scale_width, scale_height, ret, (scale_size-scale_width)/2, (scale_size-scale_height)/2);
2789 g_object_unref(G_OBJECT(tmpbuf));
2790 } else {
2791 ret = gdk_pixbuf_scale_simple(buf,scale_width,scale_height, GDK_INTERP_BILINEAR);
2793 g_object_unref(G_OBJECT(buf));
2795 return ret;
2798 /* # - Status Icon
2799 * P - Protocol Icon
2800 * A - Buddy Icon
2801 * [ - SMALL_SPACE
2802 * = - LARGE_SPACE
2803 * +--- STATUS_SIZE +--- td->avatar_width
2804 * | +-- td->name_width |
2805 * +----+ +-------+ +---------+
2806 * | | | | | |
2807 * +-------------------------------------------+
2808 * | [ = [ |--- TOOLTIP_BORDER
2809 *name_height --+-| ######[BuddyName = PP [ AAAAAAAAAAA |--+
2810 * | | ######[ = PP [ AAAAAAAAAAA | |
2811 * STATUS SIZE -| | ######[[[[[[[[[[[[[[[[[[[[[ AAAAAAAAAAA | |
2812 * +--+-| ######[Account: So-and-so [ AAAAAAAAAAA | |-- td->avatar_height
2813 * | | [Idle: 4h 15m [ AAAAAAAAAAA | |
2814 * height --+ | [Foo: Bar, Baz [ AAAAAAAAAAA | |
2815 * | | [Status: Awesome [ AAAAAAAAAAA |--+
2816 * +----| [Stop: Hammer Time [ |
2817 * | [ [ |--- TOOLTIP_BORDER
2818 * +-------------------------------------------+
2819 * | | | |
2820 * | +----------------+ |
2821 * | | |
2822 * | +-- td->width |
2823 * | |
2824 * +---- TOOLTIP_BORDER +---- TOOLTIP_BORDER
2828 #define STATUS_SIZE 16
2829 #define TOOLTIP_BORDER 12
2830 #define SMALL_SPACE 6
2831 #define LARGE_SPACE 12
2832 #define PROTOCOL_SIZE 16
2833 struct tooltip_data {
2834 PangoLayout *layout;
2835 PangoLayout *name_layout;
2836 GdkPixbuf *protocol_icon;
2837 GdkPixbuf *status_icon;
2838 GdkPixbuf *avatar;
2839 gboolean avatar_is_protocol_icon;
2840 int avatar_width;
2841 int avatar_height;
2842 int name_height;
2843 int name_width;
2844 int width;
2845 int height;
2846 int padding;
2849 static PangoLayout * create_pango_layout(const char *markup, int *width, int *height)
2851 PangoLayout *layout;
2852 int w, h;
2854 layout = gtk_widget_create_pango_layout(gtkblist->tipwindow, NULL);
2855 pango_layout_set_markup(layout, markup, -1);
2856 pango_layout_set_wrap(layout, PANGO_WRAP_WORD);
2857 pango_layout_set_width(layout, 300000);
2859 pango_layout_get_size (layout, &w, &h);
2860 if (width)
2861 *width = PANGO_PIXELS(w);
2862 if (height)
2863 *height = PANGO_PIXELS(h);
2864 return layout;
2867 static struct tooltip_data * create_tip_for_account(PurpleAccount *account)
2869 struct tooltip_data *td = g_new0(struct tooltip_data, 1);
2870 td->status_icon = pidgin_create_protocol_icon(account, PIDGIN_PROTOCOL_ICON_SMALL);
2871 /* Yes, status_icon, not protocol_icon */
2872 if (purple_account_is_disconnected(account))
2873 gdk_pixbuf_saturate_and_pixelate(td->status_icon, td->status_icon, 0.0, FALSE);
2874 td->layout = create_pango_layout(purple_account_get_username(account), &td->width, &td->height);
2875 td->padding = SMALL_SPACE;
2876 return td;
2879 static struct tooltip_data * create_tip_for_node(PurpleBlistNode *node, gboolean full)
2881 struct tooltip_data *td = g_new0(struct tooltip_data, 1);
2882 PurpleAccount *account = NULL;
2883 char *tmp = NULL, *node_name = NULL, *tooltip_text = NULL;
2885 if (PURPLE_IS_BUDDY(node)) {
2886 account = purple_buddy_get_account((PurpleBuddy*)(node));
2887 } else if (PURPLE_IS_CHAT(node)) {
2888 account = purple_chat_get_account((PurpleChat*)(node));
2891 td->padding = TOOLTIP_BORDER;
2892 td->status_icon = pidgin_blist_get_status_icon(node, PIDGIN_STATUS_ICON_LARGE);
2893 td->avatar = pidgin_blist_get_buddy_icon(node, !full, FALSE);
2894 if (account != NULL) {
2895 td->protocol_icon = pidgin_create_protocol_icon(account, PIDGIN_PROTOCOL_ICON_SMALL);
2897 tooltip_text = pidgin_get_tooltip_text(node, full);
2898 if (tooltip_text && *tooltip_text) {
2899 td->layout = create_pango_layout(tooltip_text, &td->width, &td->height);
2902 if (PURPLE_IS_BUDDY(node)) {
2903 tmp = g_markup_escape_text(purple_buddy_get_name((PurpleBuddy*)node), -1);
2904 } else if (PURPLE_IS_CHAT(node)) {
2905 tmp = g_markup_escape_text(purple_chat_get_name((PurpleChat*)node), -1);
2906 } else if (PURPLE_IS_GROUP(node)) {
2907 tmp = g_markup_escape_text(purple_group_get_name((PurpleGroup*)node), -1);
2908 } else {
2909 /* I don't believe this can happen currently, I think
2910 * everything that calls this function checks for one of the
2911 * above node types first. */
2912 tmp = g_strdup(_("Unknown node type"));
2914 node_name = g_strdup_printf("<span size='x-large' weight='bold'>%s</span>",
2915 tmp ? tmp : "");
2916 g_free(tmp);
2918 td->name_layout = create_pango_layout(node_name, &td->name_width, &td->name_height);
2919 td->name_width += SMALL_SPACE + PROTOCOL_SIZE;
2920 td->name_height = MAX(td->name_height, PROTOCOL_SIZE + SMALL_SPACE);
2921 #if 0 /* Protocol Icon as avatar */
2922 if(!td->avatar && full) {
2923 td->avatar = pidgin_create_protocol_icon(account, PIDGIN_PROTOCOL_ICON_LARGE);
2924 td->avatar_is_protocol_icon = TRUE;
2926 #endif
2928 if (td->avatar) {
2929 td->avatar_width = gdk_pixbuf_get_width(td->avatar);
2930 td->avatar_height = gdk_pixbuf_get_height(td->avatar);
2933 g_free(node_name);
2934 g_free(tooltip_text);
2935 return td;
2938 static gboolean
2939 pidgin_blist_paint_tip(GtkWidget *widget, cairo_t *cr, gpointer null)
2941 GtkStyleContext *context;
2942 int current_height, max_width;
2943 int max_text_width;
2944 int max_avatar_width;
2945 GList *l;
2946 int protocol_col = 0;
2947 GtkTextDirection dir = gtk_widget_get_direction(widget);
2948 int status_size = 0;
2950 if(gtkblist->tooltipdata == NULL)
2951 return FALSE;
2953 context = gtk_widget_get_style_context(gtkblist->tipwindow);
2954 gtk_style_context_add_class(context, GTK_STYLE_CLASS_TOOLTIP);
2956 max_text_width = 0;
2957 max_avatar_width = 0;
2959 for(l = gtkblist->tooltipdata; l; l = l->next)
2961 struct tooltip_data *td = l->data;
2963 max_text_width = MAX(max_text_width,
2964 MAX(td->width, td->name_width));
2965 max_avatar_width = MAX(max_avatar_width, td->avatar_width);
2966 if (td->status_icon)
2967 status_size = STATUS_SIZE;
2970 max_width = TOOLTIP_BORDER + status_size + SMALL_SPACE + max_text_width + SMALL_SPACE + max_avatar_width + TOOLTIP_BORDER;
2971 if (dir == GTK_TEXT_DIR_RTL)
2972 protocol_col = TOOLTIP_BORDER + max_avatar_width + SMALL_SPACE;
2973 else
2974 protocol_col = TOOLTIP_BORDER + status_size + SMALL_SPACE + max_text_width - PROTOCOL_SIZE;
2976 current_height = 12;
2977 for(l = gtkblist->tooltipdata; l; l = l->next)
2979 struct tooltip_data *td = l->data;
2981 if (td->avatar && pidgin_gdk_pixbuf_is_opaque(td->avatar))
2983 gtk_style_context_save(context);
2984 gtk_style_context_add_class(context, GTK_STYLE_CLASS_FRAME);
2985 if (dir == GTK_TEXT_DIR_RTL) {
2986 gtk_render_frame(context, cr,
2987 TOOLTIP_BORDER - 1, current_height - 1,
2988 td->avatar_width + 2, td->avatar_height + 2);
2989 } else {
2990 gtk_render_frame(context, cr,
2991 max_width - (td->avatar_width + TOOLTIP_BORDER) - 1,
2992 current_height - 1,
2993 td->avatar_width + 2, td->avatar_height + 2);
2995 gtk_style_context_restore(context);
2998 if (td->status_icon) {
2999 if (dir == GTK_TEXT_DIR_RTL) {
3000 gdk_cairo_set_source_pixbuf(cr, td->status_icon,
3001 max_width - TOOLTIP_BORDER - status_size,
3002 current_height);
3003 cairo_paint(cr);
3004 } else {
3005 gdk_cairo_set_source_pixbuf(cr, td->status_icon,
3006 TOOLTIP_BORDER, current_height);
3007 cairo_paint(cr);
3011 if (td->avatar) {
3012 if (dir == GTK_TEXT_DIR_RTL) {
3013 gdk_cairo_set_source_pixbuf(cr, td->avatar,
3014 TOOLTIP_BORDER, current_height);
3015 cairo_paint(cr);
3016 } else {
3017 gdk_cairo_set_source_pixbuf(cr, td->avatar,
3018 max_width - (td->avatar_width + TOOLTIP_BORDER),
3019 current_height);
3020 cairo_paint(cr);
3024 if (!td->avatar_is_protocol_icon && td->protocol_icon) {
3025 gdk_cairo_set_source_pixbuf(cr, td->protocol_icon, protocol_col,
3026 current_height +
3027 (td->name_height - PROTOCOL_SIZE) / 2);
3028 cairo_paint(cr);
3031 if (td->name_layout) {
3032 if (dir == GTK_TEXT_DIR_RTL) {
3033 gtk_render_layout(context, cr,
3034 max_width - (TOOLTIP_BORDER + status_size + SMALL_SPACE) - PANGO_PIXELS(300000),
3035 current_height, td->name_layout);
3036 } else {
3037 gtk_render_layout(context, cr,
3038 TOOLTIP_BORDER + status_size + SMALL_SPACE,
3039 current_height, td->name_layout);
3043 if (td->layout) {
3044 if (dir != GTK_TEXT_DIR_RTL) {
3045 gtk_render_layout(context, cr,
3046 TOOLTIP_BORDER + status_size + SMALL_SPACE,
3047 current_height + td->name_height,
3048 td->layout);
3049 } else {
3050 gtk_render_layout(context, cr,
3051 max_width - (TOOLTIP_BORDER + status_size + SMALL_SPACE) - PANGO_PIXELS(300000),
3052 current_height + td->name_height,
3053 td->layout);
3057 current_height += MAX(td->name_height + td->height, td->avatar_height) + td->padding;
3060 return FALSE;
3063 static void
3064 pidgin_blist_destroy_tooltip_data(void)
3066 while(gtkblist->tooltipdata) {
3067 struct tooltip_data *td = gtkblist->tooltipdata->data;
3069 if(td->avatar)
3070 g_object_unref(td->avatar);
3071 if(td->status_icon)
3072 g_object_unref(td->status_icon);
3073 if(td->protocol_icon)
3074 g_object_unref(td->protocol_icon);
3075 if (td->layout)
3076 g_object_unref(td->layout);
3077 if (td->name_layout)
3078 g_object_unref(td->name_layout);
3079 g_free(td);
3080 gtkblist->tooltipdata = g_list_delete_link(gtkblist->tooltipdata, gtkblist->tooltipdata);
3084 void pidgin_blist_tooltip_destroy()
3086 pidgin_blist_destroy_tooltip_data();
3087 pidgin_tooltip_destroy();
3090 static void
3091 pidgin_blist_align_tooltip(struct tooltip_data *td, GtkWidget *widget)
3093 GtkTextDirection dir = gtk_widget_get_direction(widget);
3095 if (dir == GTK_TEXT_DIR_RTL)
3097 char* layout_name = purple_markup_strip_html(pango_layout_get_text(td->name_layout));
3098 PangoDirection dir = pango_find_base_dir(layout_name, -1);
3099 if (dir == PANGO_DIRECTION_RTL || dir == PANGO_DIRECTION_NEUTRAL)
3100 pango_layout_set_alignment(td->name_layout, PANGO_ALIGN_RIGHT);
3101 g_free(layout_name);
3102 pango_layout_set_alignment(td->layout, PANGO_ALIGN_RIGHT);
3106 static gboolean
3107 pidgin_blist_create_tooltip_for_node(GtkWidget *widget, gpointer data, int *w, int *h)
3109 PurpleBlistNode *node = data;
3110 int width, height;
3111 GList *list;
3112 int max_text_width = 0;
3113 int max_avatar_width = 0;
3114 int status_size = 0;
3116 if (gtkblist->tooltipdata) {
3117 gtkblist->tipwindow = NULL;
3118 pidgin_blist_destroy_tooltip_data();
3121 gtkblist->tipwindow = widget;
3122 if (PURPLE_IS_CHAT(node) ||
3123 PURPLE_IS_BUDDY(node)) {
3124 struct tooltip_data *td = create_tip_for_node(node, TRUE);
3125 pidgin_blist_align_tooltip(td, gtkblist->tipwindow);
3126 gtkblist->tooltipdata = g_list_append(gtkblist->tooltipdata, td);
3127 } else if (PURPLE_IS_GROUP(node)) {
3128 PurpleGroup *group = (PurpleGroup*)node;
3129 GSList *accounts;
3130 struct tooltip_data *td = create_tip_for_node(node, TRUE);
3131 pidgin_blist_align_tooltip(td, gtkblist->tipwindow);
3132 gtkblist->tooltipdata = g_list_append(gtkblist->tooltipdata, td);
3134 /* Accounts with buddies in group */
3135 accounts = purple_group_get_accounts(group);
3136 for (; accounts != NULL;
3137 accounts = g_slist_delete_link(accounts, accounts)) {
3138 PurpleAccount *account = accounts->data;
3139 td = create_tip_for_account(account);
3140 gtkblist->tooltipdata = g_list_append(gtkblist->tooltipdata, td);
3142 } else if (PURPLE_IS_CONTACT(node)) {
3143 PurpleBlistNode *child;
3144 PurpleBuddy *b = purple_contact_get_priority_buddy((PurpleContact *)node);
3146 for(child = node->child; child; child = child->next)
3148 if(PURPLE_IS_BUDDY(child) && buddy_is_displayable((PurpleBuddy*)child)) {
3149 struct tooltip_data *td = create_tip_for_node(child, (b == (PurpleBuddy*)child));
3150 pidgin_blist_align_tooltip(td, gtkblist->tipwindow);
3151 if (b == (PurpleBuddy *)child) {
3152 gtkblist->tooltipdata = g_list_prepend(gtkblist->tooltipdata, td);
3153 } else {
3154 gtkblist->tooltipdata = g_list_append(gtkblist->tooltipdata, td);
3158 } else {
3159 return FALSE;
3162 height = width = 0;
3163 for (list = gtkblist->tooltipdata; list; list = list->next) {
3164 struct tooltip_data *td = list->data;
3165 max_text_width = MAX(max_text_width, MAX(td->width, td->name_width));
3166 max_avatar_width = MAX(max_avatar_width, td->avatar_width);
3167 height += MAX(MAX(STATUS_SIZE, td->avatar_height), td->height + td->name_height) + td->padding;
3168 if (td->status_icon)
3169 status_size = MAX(status_size, STATUS_SIZE);
3171 height += TOOLTIP_BORDER;
3172 width = TOOLTIP_BORDER + status_size + SMALL_SPACE + max_text_width + SMALL_SPACE + max_avatar_width + TOOLTIP_BORDER;
3174 if (w)
3175 *w = width;
3176 if (h)
3177 *h = height;
3179 return TRUE;
3182 static gboolean pidgin_blist_expand_timeout(GtkWidget *tv)
3184 GtkTreePath *path;
3185 GtkTreeIter iter;
3186 PurpleBlistNode *node;
3187 PidginBlistNode *gtknode;
3189 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),
3190 &path, NULL, NULL, NULL))
3191 return FALSE;
3192 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
3193 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
3195 if(!PURPLE_IS_CONTACT(node)) {
3196 gtk_tree_path_free(path);
3197 return FALSE;
3200 gtknode = purple_blist_node_get_ui_data(node);
3202 if (!gtknode->contact_expanded) {
3203 GtkTreeIter i;
3205 pidgin_blist_expand_contact_cb(NULL, node);
3207 gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, &gtkblist->contact_rect);
3208 gtkblist->contact_rect.width =
3209 gdk_window_get_width(gtk_widget_get_window(tv));
3210 gtkblist->mouseover_contact = node;
3211 gtk_tree_path_down (path);
3212 while (gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &i, path)) {
3213 GdkRectangle rect;
3214 gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, &rect);
3215 gtkblist->contact_rect.height += rect.height;
3216 gtk_tree_path_next(path);
3219 gtk_tree_path_free(path);
3220 return FALSE;
3223 static gboolean buddy_is_displayable(PurpleBuddy *buddy)
3225 PidginBlistNode *gtknode;
3227 if(!buddy)
3228 return FALSE;
3230 gtknode = purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(buddy));
3232 return (purple_account_is_connected(purple_buddy_get_account(buddy)) &&
3233 (purple_presence_is_online(purple_buddy_get_presence(buddy)) ||
3234 (gtknode && gtknode->recent_signonoff) ||
3235 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_offline_buddies") ||
3236 purple_blist_node_get_bool(PURPLE_BLIST_NODE(buddy), "show_offline")));
3239 void pidgin_blist_draw_tooltip(PurpleBlistNode *node, GtkWidget *widget)
3241 pidgin_tooltip_show(widget, node, pidgin_blist_create_tooltip_for_node, pidgin_blist_paint_tip);
3244 static gboolean pidgin_blist_drag_motion_cb(GtkWidget *tv, GdkDragContext *drag_context,
3245 gint x, gint y, guint time, gpointer user_data)
3247 GtkTreePath *path;
3248 int delay;
3249 GdkRectangle rect;
3252 * When dragging a buddy into a contact, this is the delay before
3253 * the contact auto-expands.
3255 delay = 900;
3257 if (gtkblist->drag_timeout) {
3258 if ((y > gtkblist->tip_rect.y) && ((y - gtkblist->tip_rect.height) < gtkblist->tip_rect.y))
3259 return FALSE;
3260 /* We've left the cell. Remove the timeout and create a new one below */
3261 g_source_remove(gtkblist->drag_timeout);
3264 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), x, y, &path, NULL, NULL, NULL);
3265 gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, &rect);
3267 if (path)
3268 gtk_tree_path_free(path);
3270 /* Only autoexpand when in the middle of the cell to avoid annoying un-intended expands */
3271 if (y < rect.y + (rect.height / 3) ||
3272 y > rect.y + (2 * (rect.height /3)))
3273 return FALSE;
3275 rect.height = rect.height / 3;
3276 rect.y += rect.height;
3278 gtkblist->tip_rect = rect;
3280 gtkblist->drag_timeout = g_timeout_add(delay, (GSourceFunc)pidgin_blist_expand_timeout, tv);
3282 if (gtkblist->mouseover_contact) {
3283 if ((y < gtkblist->contact_rect.y) || ((y - gtkblist->contact_rect.height) > gtkblist->contact_rect.y)) {
3284 pidgin_blist_collapse_contact_cb(NULL, gtkblist->mouseover_contact);
3285 gtkblist->mouseover_contact = NULL;
3289 return FALSE;
3292 static gboolean
3293 pidgin_blist_create_tooltip(GtkWidget *widget, GtkTreePath *path,
3294 gpointer null, int *w, int *h)
3296 GtkTreeIter iter;
3297 PurpleBlistNode *node;
3298 gboolean editable = FALSE;
3300 /* If we're editing a cell (e.g. alias editing), don't show the tooltip */
3301 g_object_get(G_OBJECT(gtkblist->text_rend), "editable", &editable, NULL);
3302 if (editable)
3303 return FALSE;
3305 if (gtkblist->tooltipdata) {
3306 gtkblist->tipwindow = NULL;
3307 pidgin_blist_destroy_tooltip_data();
3310 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
3311 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
3313 return pidgin_blist_create_tooltip_for_node(widget, node, w, h);
3316 static gboolean pidgin_blist_motion_cb (GtkWidget *tv, GdkEventMotion *event, gpointer null)
3318 if (gtkblist->mouseover_contact) {
3319 if ((event->y < gtkblist->contact_rect.y) || ((event->y - gtkblist->contact_rect.height) > gtkblist->contact_rect.y)) {
3320 pidgin_blist_collapse_contact_cb(NULL, gtkblist->mouseover_contact);
3321 gtkblist->mouseover_contact = NULL;
3325 return FALSE;
3328 static gboolean pidgin_blist_leave_cb (GtkWidget *w, GdkEventCrossing *e, gpointer n)
3330 if (gtkblist->timeout) {
3331 g_source_remove(gtkblist->timeout);
3332 gtkblist->timeout = 0;
3335 if (gtkblist->drag_timeout) {
3336 g_source_remove(gtkblist->drag_timeout);
3337 gtkblist->drag_timeout = 0;
3340 if (gtkblist->mouseover_contact &&
3341 !((e->x > gtkblist->contact_rect.x) && (e->x < (gtkblist->contact_rect.x + gtkblist->contact_rect.width)) &&
3342 (e->y > gtkblist->contact_rect.y) && (e->y < (gtkblist->contact_rect.y + gtkblist->contact_rect.height)))) {
3343 pidgin_blist_collapse_contact_cb(NULL, gtkblist->mouseover_contact);
3344 gtkblist->mouseover_contact = NULL;
3346 return FALSE;
3349 static void
3350 toggle_debug(void)
3352 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/debug/enabled",
3353 !purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/debug/enabled"));
3356 static char *get_mood_icon_path(const char *mood)
3358 char *path;
3360 if (purple_strequal(mood, "busy")) {
3361 path = g_build_filename(PURPLE_DATADIR, "pidgin", "icons",
3362 "hicolor", "16x16", "status", "user-busy.png", NULL);
3363 } else if (purple_strequal(mood, "hiptop")) {
3364 path = g_build_filename(PURPLE_DATADIR, "pidgin", "icons",
3365 "hicolor", "16x16", "emblems", "emblem-hiptop.png",
3366 NULL);
3367 } else {
3368 char *filename = g_strdup_printf("%s.png", mood);
3369 path = g_build_filename(PURPLE_DATADIR, "pixmaps", "pidgin",
3370 "emotes", "small", filename, NULL);
3371 g_free(filename);
3373 return path;
3376 static void
3377 update_status_with_mood(PurpleAccount *account, const gchar *mood,
3378 const gchar *text)
3380 if (mood && *mood) {
3381 if (text) {
3382 purple_account_set_status(account, "mood", TRUE,
3383 PURPLE_MOOD_NAME, mood,
3384 PURPLE_MOOD_COMMENT, text,
3385 NULL);
3386 } else {
3387 purple_account_set_status(account, "mood", TRUE,
3388 PURPLE_MOOD_NAME, mood,
3389 NULL);
3391 } else {
3392 purple_account_set_status(account, "mood", FALSE, NULL);
3396 static void
3397 edit_mood_cb(PurpleConnection *gc, PurpleRequestFields *fields)
3399 PurpleRequestField *mood_field;
3400 GList *l;
3402 mood_field = purple_request_fields_get_field(fields, "mood");
3403 l = purple_request_field_list_get_selected(mood_field);
3405 if (l) {
3406 const char *mood = purple_request_field_list_get_data(mood_field, l->data);
3408 if (gc) {
3409 const char *text;
3410 PurpleAccount *account = purple_connection_get_account(gc);
3412 if (purple_connection_get_flags(gc) & PURPLE_CONNECTION_FLAG_SUPPORT_MOOD_MESSAGES) {
3413 PurpleRequestField *text_field;
3414 text_field = purple_request_fields_get_field(fields, "text");
3415 text = purple_request_field_string_get_value(text_field);
3416 } else {
3417 text = NULL;
3420 update_status_with_mood(account, mood, text);
3421 } else {
3422 GList *accounts = purple_accounts_get_all_active();
3424 for (; accounts ; accounts = g_list_delete_link(accounts, accounts)) {
3425 PurpleAccount *account = (PurpleAccount *) accounts->data;
3426 PurpleConnection *gc = purple_account_get_connection(account);
3428 if (gc && (purple_connection_get_flags(gc) & PURPLE_CONNECTION_FLAG_SUPPORT_MOODS)) {
3429 update_status_with_mood(account, mood, NULL);
3436 static void
3437 global_moods_for_each(gpointer key, gpointer value, gpointer user_data)
3439 GList **out_moods = (GList **) user_data;
3440 PurpleMood *mood = (PurpleMood *) value;
3442 *out_moods = g_list_append(*out_moods, mood);
3445 static PurpleMood *
3446 get_global_moods(void)
3448 GHashTable *global_moods =
3449 g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
3450 GHashTable *mood_counts =
3451 g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
3452 GList *accounts = purple_accounts_get_all_active();
3453 PurpleMood *result = NULL;
3454 GList *out_moods = NULL;
3455 int i = 0;
3456 int num_accounts = 0;
3458 for (; accounts ; accounts = g_list_delete_link(accounts, accounts)) {
3459 PurpleAccount *account = (PurpleAccount *) accounts->data;
3460 if (purple_account_is_connected(account)) {
3461 PurpleConnection *gc = purple_account_get_connection(account);
3463 if (purple_connection_get_flags(gc) & PURPLE_CONNECTION_FLAG_SUPPORT_MOODS) {
3464 PurpleProtocol *protocol = purple_connection_get_protocol(gc);
3465 PurpleMood *mood = NULL;
3467 for (mood = purple_protocol_client_iface_get_moods(protocol, account) ;
3468 mood->mood != NULL ; mood++) {
3469 int mood_count =
3470 GPOINTER_TO_INT(g_hash_table_lookup(mood_counts, mood->mood));
3472 if (!g_hash_table_lookup(global_moods, mood->mood)) {
3473 g_hash_table_insert(global_moods, (gpointer)mood->mood, mood);
3475 g_hash_table_insert(mood_counts, (gpointer)mood->mood,
3476 GINT_TO_POINTER(mood_count + 1));
3479 num_accounts++;
3484 g_hash_table_foreach(global_moods, global_moods_for_each, &out_moods);
3485 result = g_new0(PurpleMood, g_hash_table_size(global_moods) + 1);
3487 while (out_moods) {
3488 PurpleMood *mood = (PurpleMood *) out_moods->data;
3489 int in_num_accounts =
3490 GPOINTER_TO_INT(g_hash_table_lookup(mood_counts, mood->mood));
3492 if (in_num_accounts == num_accounts) {
3493 /* mood is present in all accounts supporting moods */
3494 result[i].mood = mood->mood;
3495 result[i].description = mood->description;
3496 i++;
3498 out_moods = g_list_delete_link(out_moods, out_moods);
3501 g_hash_table_destroy(global_moods);
3502 g_hash_table_destroy(mood_counts);
3504 return result;
3507 /* get current set mood for all mood-supporting accounts, or NULL if not set
3508 or not set to the same on all */
3509 static const gchar *
3510 get_global_mood_status(void)
3512 GList *accounts = purple_accounts_get_all_active();
3513 const gchar *found_mood = NULL;
3515 for (; accounts ; accounts = g_list_delete_link(accounts, accounts)) {
3516 PurpleAccount *account = (PurpleAccount *) accounts->data;
3518 if (purple_account_is_connected(account) &&
3519 (purple_connection_get_flags(purple_account_get_connection(account)) &
3520 PURPLE_CONNECTION_FLAG_SUPPORT_MOODS)) {
3521 PurplePresence *presence = purple_account_get_presence(account);
3522 PurpleStatus *status = purple_presence_get_status(presence, "mood");
3523 const gchar *curr_mood = purple_status_get_attr_string(status, PURPLE_MOOD_NAME);
3525 if (found_mood != NULL && !purple_strequal(curr_mood, found_mood)) {
3526 /* found a different mood */
3527 found_mood = NULL;
3528 break;
3529 } else {
3530 found_mood = curr_mood;
3535 return found_mood;
3538 static void
3539 set_mood_cb(GtkWidget *widget, PurpleAccount *account)
3541 const char *current_mood;
3542 PurpleRequestFields *fields;
3543 PurpleRequestFieldGroup *g;
3544 PurpleRequestField *f;
3545 PurpleConnection *gc = NULL;
3546 PurpleProtocol *protocol = NULL;
3547 PurpleMood *mood;
3548 PurpleMood *global_moods = get_global_moods();
3550 if (account) {
3551 PurplePresence *presence = purple_account_get_presence(account);
3552 PurpleStatus *status = purple_presence_get_status(presence, "mood");
3553 gc = purple_account_get_connection(account);
3554 g_return_if_fail(purple_connection_get_protocol(gc) != NULL);
3555 protocol = purple_connection_get_protocol(gc);
3556 current_mood = purple_status_get_attr_string(status, PURPLE_MOOD_NAME);
3557 } else {
3558 current_mood = get_global_mood_status();
3561 fields = purple_request_fields_new();
3562 g = purple_request_field_group_new(NULL);
3563 f = purple_request_field_list_new("mood", _("Please select your mood from the list"));
3565 purple_request_field_list_add_icon(f, _("None"), NULL, "");
3566 if (current_mood == NULL)
3567 purple_request_field_list_add_selected(f, _("None"));
3569 /* TODO: rlaager wants this sorted. */
3570 /* TODO: darkrain wants it sorted post-translation */
3571 if (account && PURPLE_PROTOCOL_IMPLEMENTS(protocol, CLIENT, get_moods))
3572 mood = purple_protocol_client_iface_get_moods(protocol, account);
3573 else
3574 mood = global_moods;
3575 for ( ; mood->mood != NULL ; mood++) {
3576 char *path;
3578 if (mood->mood == NULL || mood->description == NULL)
3579 continue;
3581 path = get_mood_icon_path(mood->mood);
3582 purple_request_field_list_add_icon(f, _(mood->description),
3583 path, (gpointer)mood->mood);
3584 g_free(path);
3586 if (current_mood && purple_strequal(current_mood, mood->mood))
3587 purple_request_field_list_add_selected(f, _(mood->description));
3589 purple_request_field_group_add_field(g, f);
3591 purple_request_fields_add_group(fields, g);
3593 /* if the connection allows setting a mood message */
3594 if (gc && (purple_connection_get_flags(gc) & PURPLE_CONNECTION_FLAG_SUPPORT_MOOD_MESSAGES)) {
3595 g = purple_request_field_group_new(NULL);
3596 f = purple_request_field_string_new("text",
3597 _("Message (optional)"), NULL, FALSE);
3598 purple_request_field_group_add_field(g, f);
3599 purple_request_fields_add_group(fields, g);
3602 purple_request_fields(gc, _("Edit User Mood"), _("Edit User Mood"),
3603 NULL, fields,
3604 _("OK"), G_CALLBACK(edit_mood_cb),
3605 _("Cancel"), NULL,
3606 purple_request_cpar_from_connection(gc), gc);
3608 g_free(global_moods);
3611 static void
3612 set_mood_show(void)
3614 set_mood_cb(NULL, NULL);
3617 /***************************************************
3618 * Crap *
3619 ***************************************************/
3620 static void
3621 _pidgin_about_cb(GtkAction *action, GtkWidget *window) {
3622 GtkWidget *about = pidgin_about_dialog_new();
3624 gtk_window_set_transient_for(GTK_WINDOW(about), GTK_WINDOW(window));
3626 gtk_widget_show_all(about);
3629 /* TODO: fill out tooltips... */
3630 static const GtkActionEntry blist_menu_entries[] = {
3631 /* NOTE: Do not set any accelerator to Control+O. It is mapped by
3632 gtk_blist_key_press_cb to "Get User Info" on the selected buddy. */
3633 /* Buddies menu */
3634 { "BuddiesMenu", NULL, N_("_Buddies"), NULL, NULL, NULL },
3635 { "NewInstantMessage", PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW, N_("New Instant _Message..."), "<control>M", NULL, pidgin_dialogs_im },
3636 { "JoinAChat", PIDGIN_STOCK_CHAT, N_("Join a _Chat..."), "<control>C", NULL, pidgin_blist_joinchat_show },
3637 { "GetUserInfo", PIDGIN_STOCK_TOOLBAR_USER_INFO, N_("Get User _Info..."), "<control>I", NULL, pidgin_dialogs_info },
3638 { "ViewUserLog", NULL, N_("View User _Log..."), "<control>L", NULL, pidgin_dialogs_log },
3639 { "ShowMenu", NULL, N_("Sh_ow"), NULL, NULL, NULL },
3640 { "SortMenu", NULL, N_("_Sort Buddies"), NULL, NULL, NULL },
3641 { "AddBuddy", GTK_STOCK_ADD, N_("_Add Buddy..."), "<control>B", NULL, pidgin_blist_add_buddy_cb },
3642 { "AddChat", GTK_STOCK_ADD, N_("Add C_hat..."), NULL, NULL, pidgin_blist_add_chat_cb },
3643 { "AddGroup", GTK_STOCK_ADD, N_("Add _Group..."), NULL, NULL, purple_blist_request_add_group },
3644 { "Quit", GTK_STOCK_QUIT, N_("_Quit"), "<control>Q", NULL, purple_core_quit },
3646 /* Accounts menu */
3647 { "AccountsMenu", NULL, N_("_Accounts"), NULL, NULL, NULL },
3648 { "ManageAccounts", NULL, N_("Manage Accounts"), "<control>A", NULL, pidgin_accounts_window_show },
3650 /* Tools */
3651 { "ToolsMenu", NULL, N_("_Tools"), NULL, NULL, NULL },
3652 { "BuddyPounces", NULL, N_("Buddy _Pounces"), NULL, NULL, pidgin_pounces_manager_show },
3653 { "CustomSmileys", PIDGIN_STOCK_TOOLBAR_SMILEY, N_("Custom Smile_ys"), "<control>Y", NULL, pidgin_smiley_manager_show },
3654 { "Plugins", PIDGIN_STOCK_TOOLBAR_PLUGINS, N_("Plu_gins"), "<control>U", NULL, pidgin_plugin_dialog_show },
3655 { "Preferences", GTK_STOCK_PREFERENCES, N_("Pr_eferences"), "<control>P", NULL, pidgin_prefs_show },
3656 { "Privacy", NULL, N_("Pr_ivacy"), NULL, NULL, pidgin_privacy_dialog_show },
3657 { "SetMood", NULL, N_("Set _Mood"), "<control>D", NULL, set_mood_show },
3658 { "FileTransfers", PIDGIN_STOCK_TOOLBAR_TRANSFER, N_("_File Transfers"), "<control>T", NULL, G_CALLBACK(gtk_blist_show_xfer_dialog_cb) },
3659 { "RoomList", NULL, N_("R_oom List"), NULL, NULL, pidgin_roomlist_dialog_show },
3660 { "SystemLog", NULL, N_("System _Log"), NULL, NULL, gtk_blist_show_systemlog_cb },
3662 /* Help */
3663 { "HelpMenu", NULL, N_("_Help"), NULL, NULL, NULL },
3664 { "OnlineHelp", GTK_STOCK_HELP, N_("Online _Help"), "F1", NULL, gtk_blist_show_onlinehelp_cb },
3665 { "DebugWindow", NULL, N_("_Debug Window"), NULL, NULL, toggle_debug },
3666 { "PluginInformation", NULL, N_("_Plugin Information"), NULL, NULL, pidgin_debug_plugin_info_show },
3667 { "About", GTK_STOCK_ABOUT, N_("_About"), NULL, NULL, G_CALLBACK(_pidgin_about_cb) },
3670 /* Toggle items */
3671 static const GtkToggleActionEntry blist_menu_toggle_entries[] = {
3672 /* Buddies->Show menu */
3673 { "ShowOffline", NULL, N_("_Offline Buddies"), NULL, NULL, G_CALLBACK(pidgin_blist_edit_mode_cb), FALSE },
3674 { "ShowEmptyGroups", NULL, N_("_Empty Groups"), NULL, NULL, G_CALLBACK(pidgin_blist_show_empty_groups_cb), FALSE },
3675 { "ShowBuddyDetails", NULL, N_("Buddy _Details"), NULL, NULL, G_CALLBACK(pidgin_blist_buddy_details_cb), FALSE },
3676 { "ShowIdleTimes", NULL, N_("Idle _Times"), NULL, NULL, G_CALLBACK(pidgin_blist_show_idle_time_cb), FALSE },
3677 { "ShowProtocolIcons", NULL, N_("_Protocol Icons"), NULL, NULL, G_CALLBACK(pidgin_blist_show_protocol_icons_cb), FALSE },
3679 /* Tools menu */
3680 { "MuteSounds", NULL, N_("Mute _Sounds"), NULL, NULL, G_CALLBACK(pidgin_blist_mute_sounds_cb), FALSE },
3683 static const char *blist_menu =
3684 "<ui>"
3685 "<menubar name='BList'>"
3686 "<menu action='BuddiesMenu'>"
3687 "<menuitem action='NewInstantMessage'/>"
3688 "<menuitem action='JoinAChat'/>"
3689 "<menuitem action='GetUserInfo'/>"
3690 "<menuitem action='ViewUserLog'/>"
3691 "<separator/>"
3692 "<menu action='ShowMenu'>"
3693 "<menuitem action='ShowOffline'/>"
3694 "<menuitem action='ShowEmptyGroups'/>"
3695 "<menuitem action='ShowBuddyDetails'/>"
3696 "<menuitem action='ShowIdleTimes'/>"
3697 "<menuitem action='ShowProtocolIcons'/>"
3698 "</menu>"
3699 "<menu action='SortMenu'/>"
3700 "<separator/>"
3701 "<menuitem action='AddBuddy'/>"
3702 "<menuitem action='AddChat'/>"
3703 "<menuitem action='AddGroup'/>"
3704 "<separator/>"
3705 "<menuitem action='Quit'/>"
3706 "</menu>"
3707 "<menu action='AccountsMenu'>"
3708 "<menuitem action='ManageAccounts'/>"
3709 "</menu>"
3710 "<menu action='ToolsMenu'>"
3711 "<menuitem action='BuddyPounces'/>"
3712 "<menuitem action='CustomSmileys'/>"
3713 "<menuitem action='Plugins'/>"
3714 "<menuitem action='Preferences'/>"
3715 "<menuitem action='Privacy'/>"
3716 "<menuitem action='SetMood'/>"
3717 "<separator/>"
3718 "<menuitem action='FileTransfers'/>"
3719 "<menuitem action='RoomList'/>"
3720 "<menuitem action='SystemLog'/>"
3721 "<separator/>"
3722 "<menuitem action='MuteSounds'/>"
3723 "<placeholder name='PluginActions'/>"
3724 "</menu>"
3725 "<menu action='HelpMenu'>"
3726 "<menuitem action='OnlineHelp'/>"
3727 "<separator/>"
3728 "<menuitem action='DebugWindow'/>"
3729 "<menuitem action='PluginInformation'/>"
3730 "<separator/>"
3731 "<menuitem action='About'/>"
3732 "</menu>"
3733 "</menubar>"
3734 "</ui>";
3736 /*********************************************************
3737 * Private Utility functions *
3738 *********************************************************/
3740 static char *pidgin_get_tooltip_text(PurpleBlistNode *node, gboolean full)
3742 GString *str = g_string_new("");
3743 PurpleProtocol *protocol = NULL;
3744 char *tmp;
3746 if (PURPLE_IS_CHAT(node))
3748 PurpleChat *chat;
3749 GList *connections;
3750 GList *cur = NULL;
3751 PurpleProtocolChatEntry *pce;
3752 char *name, *value;
3753 PurpleChatConversation *conv;
3754 PidginBlistNode *bnode = purple_blist_node_get_ui_data(node);
3756 chat = (PurpleChat *)node;
3757 protocol = purple_protocols_find(purple_account_get_protocol_id(purple_chat_get_account(chat)));
3759 connections = purple_connections_get_all();
3760 if (connections && connections->next)
3762 tmp = g_markup_escape_text(purple_account_get_username(purple_chat_get_account(chat)), -1);
3763 g_string_append_printf(str, _("<b>Account:</b> %s"), tmp);
3764 g_free(tmp);
3767 if (bnode && bnode->conv.conv) {
3768 conv = PURPLE_CHAT_CONVERSATION(bnode->conv.conv);
3769 } else {
3770 char *chat_name;
3771 if (protocol && PURPLE_PROTOCOL_IMPLEMENTS(protocol, CHAT, get_name))
3772 chat_name = purple_protocol_chat_iface_get_name(protocol, purple_chat_get_components(chat));
3773 else
3774 chat_name = g_strdup(purple_chat_get_name(chat));
3776 conv = purple_conversations_find_chat_with_account(chat_name,
3777 purple_chat_get_account(chat));
3778 g_free(chat_name);
3781 if (conv && !purple_chat_conversation_has_left(conv)) {
3782 g_string_append_printf(str, _("\n<b>Occupants:</b> %d"),
3783 purple_chat_conversation_get_users_count(conv));
3785 if (protocol && (purple_protocol_get_options(protocol) & OPT_PROTO_CHAT_TOPIC)) {
3786 const char *chattopic = purple_chat_conversation_get_topic(conv);
3787 char *topic = chattopic ? g_markup_escape_text(chattopic, -1) : NULL;
3788 g_string_append_printf(str, _("\n<b>Topic:</b> %s"), topic ? topic : _("(no topic set)"));
3789 g_free(topic);
3793 if (protocol)
3794 cur = purple_protocol_chat_iface_info(protocol, purple_account_get_connection(purple_chat_get_account(chat)));
3796 while (cur != NULL)
3798 pce = cur->data;
3800 if (!pce->secret)
3802 tmp = purple_text_strip_mnemonic(pce->label);
3803 name = g_markup_escape_text(tmp, -1);
3804 g_free(tmp);
3805 value = g_markup_escape_text(g_hash_table_lookup(
3806 purple_chat_get_components(chat), pce->identifier), -1);
3807 g_string_append_printf(str, "\n<b>%s</b> %s",
3808 name ? name : "",
3809 value ? value : "");
3810 g_free(name);
3811 g_free(value);
3814 g_free(pce);
3815 cur = g_list_delete_link(cur, cur);
3818 else if (PURPLE_IS_CONTACT(node) || PURPLE_IS_BUDDY(node))
3820 /* NOTE: THIS FUNCTION IS NO LONGER CALLED FOR CONTACTS.
3821 * It is only called by create_tip_for_node(), and create_tip_for_node() is never called for a contact.
3823 PurpleContact *c;
3824 PurpleBuddy *b;
3825 PurplePresence *presence;
3826 PurpleNotifyUserInfo *user_info;
3827 GList *connections;
3828 char *tmp;
3829 gchar *alias;
3830 time_t idle_secs, signon;
3832 if (PURPLE_IS_CONTACT(node))
3834 c = (PurpleContact *)node;
3835 b = purple_contact_get_priority_buddy(c);
3837 else
3839 b = (PurpleBuddy *)node;
3840 c = purple_buddy_get_contact(b);
3843 protocol = purple_protocols_find(purple_account_get_protocol_id(purple_buddy_get_account(b)));
3845 presence = purple_buddy_get_presence(b);
3846 user_info = purple_notify_user_info_new();
3848 /* Account */
3849 connections = purple_connections_get_all();
3850 if (full && connections && connections->next)
3852 purple_notify_user_info_add_pair_plaintext(user_info, _("Account"),
3853 purple_account_get_username(purple_buddy_get_account(b)));
3856 /* Alias */
3857 /* If there's not a contact alias, the node is being displayed with
3858 * this alias, so there's no point in showing it in the tooltip. */
3859 g_object_get(c, "alias", &alias, NULL);
3860 if (full && c && purple_buddy_get_local_alias(b) != NULL && purple_buddy_get_local_alias(b)[0] != '\0' &&
3861 (alias != NULL && alias[0] != '\0') &&
3862 !purple_strequal(alias, purple_buddy_get_local_alias(b)))
3864 purple_notify_user_info_add_pair_plaintext(user_info,
3865 _("Buddy Alias"), purple_buddy_get_local_alias(b));
3868 /* Nickname/Server Alias */
3869 /* I'd like to only show this if there's a contact or buddy
3870 * alias, but people often set long nicknames, which
3871 * get ellipsized, so the only way to see the whole thing is
3872 * to look at the tooltip. */
3873 if (full && purple_buddy_get_server_alias(b))
3875 purple_notify_user_info_add_pair_plaintext(user_info,
3876 _("Nickname"), purple_buddy_get_server_alias(b));
3879 /* Logged In */
3880 signon = purple_presence_get_login_time(presence);
3881 if (full && PURPLE_BUDDY_IS_ONLINE(b) && signon > 0)
3883 if (signon > time(NULL)) {
3885 * They signed on in the future?! Our local clock
3886 * must be wrong, show the actual date instead of
3887 * "4 days", etc.
3889 tmp = g_strdup(purple_date_format_long(localtime(&signon)));
3890 } else
3891 tmp = purple_str_seconds_to_string(time(NULL) - signon);
3892 purple_notify_user_info_add_pair_plaintext(user_info, _("Logged In"), tmp);
3893 g_free(tmp);
3896 /* Idle */
3897 if (purple_presence_is_idle(presence))
3899 idle_secs = purple_presence_get_idle_time(presence);
3900 if (idle_secs > 0)
3902 tmp = purple_str_seconds_to_string(time(NULL) - idle_secs);
3903 purple_notify_user_info_add_pair_plaintext(user_info, _("Idle"), tmp);
3904 g_free(tmp);
3908 /* Last Seen */
3909 if (full && c && !PURPLE_BUDDY_IS_ONLINE(b))
3911 PidginBlistNode *gtknode = purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(c));
3912 PurpleBlistNode *bnode;
3913 int lastseen = 0;
3915 if (gtknode && (!gtknode->contact_expanded || PURPLE_IS_CONTACT(node)))
3917 /* We're either looking at a buddy for a collapsed contact or
3918 * an expanded contact itself so we show the most recent
3919 * (largest) last_seen time for any of the buddies under
3920 * the contact. */
3921 for (bnode = ((PurpleBlistNode *)c)->child ; bnode != NULL ; bnode = bnode->next)
3923 int value = purple_blist_node_get_int(bnode, "last_seen");
3924 if (value > lastseen)
3925 lastseen = value;
3928 else
3930 /* We're dealing with a buddy under an expanded contact,
3931 * so we show the last_seen time for the buddy. */
3932 lastseen = purple_blist_node_get_int(&b->node, "last_seen");
3935 if (lastseen > 0)
3937 tmp = purple_str_seconds_to_string(time(NULL) - lastseen);
3938 purple_notify_user_info_add_pair_plaintext(user_info, _("Last Seen"), tmp);
3939 g_free(tmp);
3944 /* Offline? */
3945 /* FIXME: Why is this status special-cased by the core? --rlaager
3946 * FIXME: Alternatively, why not have the core do all of them? --rlaager */
3947 if (!PURPLE_BUDDY_IS_ONLINE(b)) {
3948 purple_notify_user_info_add_pair_plaintext(user_info, _("Status"), _("Offline"));
3951 if (purple_account_is_connected(purple_buddy_get_account(b)) &&
3952 protocol)
3954 /* Additional text from the protocol */
3955 purple_protocol_client_iface_tooltip_text(protocol, b, user_info, full);
3958 /* These are Easter Eggs. Patches to remove them will be rejected. */
3959 if (!g_ascii_strcasecmp(purple_buddy_get_name(b), "robflynn"))
3960 purple_notify_user_info_add_pair_plaintext(user_info, _("Description"), _("Spooky"));
3961 if (!g_ascii_strcasecmp(purple_buddy_get_name(b), "seanegn"))
3962 purple_notify_user_info_add_pair_plaintext(user_info, _("Status"), _("Awesome"));
3963 if (!g_ascii_strcasecmp(purple_buddy_get_name(b), "chipx86"))
3964 purple_notify_user_info_add_pair_plaintext(user_info, _("Status"), _("Rockin'"));
3966 tmp = purple_notify_user_info_get_text_with_newline(user_info, "\n");
3967 g_string_append(str, tmp);
3968 g_free(tmp);
3969 g_free(alias);
3971 purple_notify_user_info_destroy(user_info);
3972 } else if (PURPLE_IS_GROUP(node)) {
3973 gint count;
3974 PurpleGroup *group = (PurpleGroup*)node;
3975 PurpleNotifyUserInfo *user_info;
3977 user_info = purple_notify_user_info_new();
3979 count = purple_counting_node_get_online_count(PURPLE_COUNTING_NODE(group));
3980 if (count != 0) {
3981 /* Online buddies in group */
3982 char tmp2[12];
3983 sprintf(tmp2, "%d", count);
3984 purple_notify_user_info_add_pair_plaintext(user_info,
3985 _("Online Buddies"), tmp2);
3988 count = purple_counting_node_get_current_size(PURPLE_COUNTING_NODE(group));
3989 if (count != 0) {
3990 /* Total buddies (from online accounts) in group */
3991 char tmp2[12];
3992 sprintf(tmp2, "%d", count);
3993 purple_notify_user_info_add_pair_html(user_info,
3994 _("Total Buddies"), tmp2);
3997 tmp = purple_notify_user_info_get_text_with_newline(user_info, "\n");
3998 g_string_append(str, tmp);
3999 g_free(tmp);
4001 purple_notify_user_info_destroy(user_info);
4004 purple_signal_emit(pidgin_blist_get_handle(), "drawing-tooltip",
4005 node, str, full);
4007 return g_string_free(str, FALSE);
4010 static GHashTable *cached_emblems;
4012 static void _cleanup_cached_emblem(gpointer data, GObject *obj) {
4013 g_hash_table_remove(cached_emblems, data);
4016 static GdkPixbuf * _pidgin_blist_get_cached_emblem(gchar *path) {
4017 GdkPixbuf *pb = g_hash_table_lookup(cached_emblems, path);
4019 if (pb != NULL) {
4020 /* The caller gets a reference */
4021 g_object_ref(pb);
4022 g_free(path);
4023 } else {
4024 pb = pidgin_pixbuf_new_from_file(path);
4025 if (pb != NULL) {
4026 /* We don't want to own a ref to the pixbuf, but we need to keep clean up. */
4027 /* I'm not sure if it would be better to just keep our ref and not let the emblem ever be destroyed */
4028 g_object_weak_ref(G_OBJECT(pb), _cleanup_cached_emblem, path);
4029 g_hash_table_insert(cached_emblems, path, pb);
4030 } else
4031 g_free(path);
4034 return pb;
4037 GdkPixbuf *
4038 pidgin_blist_get_emblem(PurpleBlistNode *node)
4040 PurpleBuddy *buddy = NULL;
4041 PidginBlistNode *gtknode = purple_blist_node_get_ui_data(node);
4042 PurpleProtocol *protocol;
4043 const char *name = NULL;
4044 char *filename, *path;
4045 PurplePresence *p = NULL;
4046 PurpleStatus *tune;
4048 if(PURPLE_IS_CONTACT(node)) {
4049 if(!gtknode->contact_expanded) {
4050 buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
4052 } else if(PURPLE_IS_BUDDY(node)) {
4053 buddy = (PurpleBuddy*)node;
4054 p = purple_buddy_get_presence(buddy);
4055 if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_MOBILE)) {
4056 /* This emblem comes from the small emoticon set now,
4057 * to reduce duplication. */
4058 path = g_build_filename(PURPLE_DATADIR, "pixmaps",
4059 "pidgin", "emotes", "small", "mobile.png", NULL);
4060 return _pidgin_blist_get_cached_emblem(path);
4063 if (((PidginBlistNode*)purple_blist_node_get_ui_data(node->parent))->contact_expanded) {
4064 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons"))
4065 return NULL;
4066 return pidgin_create_protocol_icon(purple_buddy_get_account((PurpleBuddy*)node), PIDGIN_PROTOCOL_ICON_SMALL);
4068 } else {
4069 return NULL;
4072 g_return_val_if_fail(buddy != NULL, NULL);
4074 if (!purple_account_privacy_check(purple_buddy_get_account(buddy), purple_buddy_get_name(buddy))) {
4075 path = g_build_filename(PURPLE_DATADIR, "pidgin", "icons",
4076 "hicolor", "16x16", "emblems", "emblem-blocked.png",
4077 NULL);
4078 return _pidgin_blist_get_cached_emblem(path);
4081 /* If we came through the contact code flow above, we didn't need
4082 * to get the presence until now. */
4083 if (p == NULL)
4084 p = purple_buddy_get_presence(buddy);
4086 if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_MOBILE)) {
4087 /* This emblem comes from the small emoticon set now, to reduce duplication. */
4088 path = g_build_filename(PURPLE_DATADIR, "pixmaps", "pidgin",
4089 "emotes", "small", "mobile.png", NULL);
4090 return _pidgin_blist_get_cached_emblem(path);
4093 tune = purple_presence_get_status(p, "tune");
4094 if (tune && purple_status_is_active(tune)) {
4095 /* TODO: Replace "Tune" with generalized "Media" in 3.0. */
4096 if (purple_status_get_attr_string(tune, "game") != NULL) {
4097 path = g_build_filename(PURPLE_DATADIR, "pidgin",
4098 "icons", "hicolor", "16x16", "emblems",
4099 "emblem-game.png", NULL);
4100 return _pidgin_blist_get_cached_emblem(path);
4102 /* TODO: Replace "Tune" with generalized "Media" in 3.0. */
4103 if (purple_status_get_attr_string(tune, "office") != NULL) {
4104 path = g_build_filename(PURPLE_DATADIR, "pidgin",
4105 "icons", "hicolor", "16x16", "emblems",
4106 "emblem-office.png", NULL);
4107 return _pidgin_blist_get_cached_emblem(path);
4109 /* Regular old "tune" is the only one in all protocols. */
4110 /* This emblem comes from the small emoticon set now, to reduce duplication. */
4111 path = g_build_filename(PURPLE_DATADIR, "pixmaps", "pidgin",
4112 "emotes", "small", "music.png", NULL);
4113 return _pidgin_blist_get_cached_emblem(path);
4116 protocol = purple_protocols_find(purple_account_get_protocol_id(purple_buddy_get_account(buddy)));
4117 if (!protocol)
4118 return NULL;
4120 name = purple_protocol_client_iface_list_emblem(protocol, buddy);
4122 if (name == NULL) {
4123 PurpleStatus *status;
4125 if (!purple_presence_is_status_primitive_active(p, PURPLE_STATUS_MOOD))
4126 return NULL;
4128 status = purple_presence_get_status(p, "mood");
4129 name = purple_status_get_attr_string(status, PURPLE_MOOD_NAME);
4131 if (!(name && *name))
4132 return NULL;
4134 path = get_mood_icon_path(name);
4135 } else {
4136 filename = g_strdup_printf("emblem-%s.png", name);
4137 path = g_build_filename(PURPLE_DATADIR, "pidgin", "icons",
4138 "hicolor", "16x16", "emblems", filename, NULL);
4139 g_free(filename);
4142 /* _pidgin_blist_get_cached_emblem() assumes ownership of path */
4143 return _pidgin_blist_get_cached_emblem(path);
4147 GdkPixbuf *
4148 pidgin_blist_get_status_icon(PurpleBlistNode *node, PidginStatusIconSize size)
4150 GdkPixbuf *ret;
4151 const char *icon = NULL;
4152 PidginBlistNode *gtknode = purple_blist_node_get_ui_data(node);
4153 PidginBlistNode *gtkbuddynode = NULL;
4154 PurpleBuddy *buddy = NULL;
4155 PurpleChat *chat = NULL;
4156 GtkIconSize icon_size = gtk_icon_size_from_name((size == PIDGIN_STATUS_ICON_LARGE) ? PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL :
4157 PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC);
4159 if(PURPLE_IS_CONTACT(node)) {
4160 if(!gtknode->contact_expanded) {
4161 buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
4162 if (buddy != NULL)
4163 gtkbuddynode = purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(buddy));
4165 } else if(PURPLE_IS_BUDDY(node)) {
4166 buddy = (PurpleBuddy*)node;
4167 gtkbuddynode = purple_blist_node_get_ui_data(node);
4168 } else if(PURPLE_IS_CHAT(node)) {
4169 chat = (PurpleChat*)node;
4170 } else {
4171 return NULL;
4174 if(buddy || chat) {
4175 PurpleAccount *account;
4176 PurpleProtocol *protocol;
4178 if(buddy)
4179 account = purple_buddy_get_account(buddy);
4180 else
4181 account = purple_chat_get_account(chat);
4183 protocol = purple_protocols_find(purple_account_get_protocol_id(account));
4184 if(!protocol)
4185 return NULL;
4188 if(buddy) {
4189 PurpleConversation *conv = find_conversation_with_buddy(buddy);
4190 PurplePresence *p;
4191 gboolean trans;
4193 if(conv != NULL) {
4194 PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
4195 if (gtkconv == NULL && size == PIDGIN_STATUS_ICON_SMALL) {
4196 PidginBlistNode *ui = purple_blist_node_get_ui_data(&(buddy->node));
4197 if (ui == NULL || (ui->conv.flags & PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE))
4198 return gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview),
4199 PIDGIN_STOCK_STATUS_MESSAGE, icon_size, "GtkTreeView");
4203 p = purple_buddy_get_presence(buddy);
4204 trans = purple_presence_is_idle(p);
4206 if (PURPLE_BUDDY_IS_ONLINE(buddy) && gtkbuddynode && gtkbuddynode->recent_signonoff)
4207 icon = PIDGIN_STOCK_STATUS_LOGIN;
4208 else if (gtkbuddynode && gtkbuddynode->recent_signonoff)
4209 icon = PIDGIN_STOCK_STATUS_LOGOUT;
4210 else if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_UNAVAILABLE))
4211 if (trans)
4212 icon = PIDGIN_STOCK_STATUS_BUSY_I;
4213 else
4214 icon = PIDGIN_STOCK_STATUS_BUSY;
4215 else if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_AWAY))
4216 if (trans)
4217 icon = PIDGIN_STOCK_STATUS_AWAY_I;
4218 else
4219 icon = PIDGIN_STOCK_STATUS_AWAY;
4220 else if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_EXTENDED_AWAY))
4221 if (trans)
4222 icon = PIDGIN_STOCK_STATUS_XA_I;
4223 else
4224 icon = PIDGIN_STOCK_STATUS_XA;
4225 else if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_OFFLINE))
4226 icon = PIDGIN_STOCK_STATUS_OFFLINE;
4227 else if (trans)
4228 icon = PIDGIN_STOCK_STATUS_AVAILABLE_I;
4229 else if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_INVISIBLE))
4230 icon = PIDGIN_STOCK_STATUS_INVISIBLE;
4231 else
4232 icon = PIDGIN_STOCK_STATUS_AVAILABLE;
4233 } else if (chat) {
4234 icon = PIDGIN_STOCK_STATUS_CHAT;
4235 } else {
4236 icon = PIDGIN_STOCK_STATUS_PERSON;
4239 ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), icon,
4240 icon_size, "GtkTreeView");
4241 return ret;
4244 static const char *
4245 theme_font_get_color_default(PidginThemeFont *font, const char *def)
4247 const char *ret;
4248 if (!font || !(ret = pidgin_theme_font_get_color_describe(font)))
4249 ret = def;
4250 return ret;
4253 static const char *
4254 theme_font_get_face_default(PidginThemeFont *font, const char *def)
4256 const char *ret;
4257 if (!font || !(ret = pidgin_theme_font_get_font_face(font)))
4258 ret = def;
4259 return ret;
4262 gchar *
4263 pidgin_blist_get_name_markup(PurpleBuddy *b, gboolean selected, gboolean aliased)
4265 const char *name, *name_color, *name_font, *status_color, *status_font, *dim_grey;
4266 char *text = NULL;
4267 PurpleProtocol *protocol = NULL;
4268 PurpleContact *contact;
4269 PurplePresence *presence;
4270 PidginBlistNode *gtkcontactnode = NULL;
4271 char *idletime = NULL, *statustext = NULL, *nametext = NULL;
4272 PurpleConversation *conv = find_conversation_with_buddy(b);
4273 gboolean hidden_conv = FALSE;
4274 gboolean biglist = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons");
4275 PidginThemeFont *statusfont = NULL, *namefont = NULL;
4276 PidginBlistTheme *theme;
4277 gchar *contact_alias;
4279 if (conv != NULL) {
4280 PidginBlistNode *ui = purple_blist_node_get_ui_data(&(b->node));
4281 if (ui) {
4282 if (ui->conv.flags & PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE)
4283 hidden_conv = TRUE;
4284 } else {
4285 if (PIDGIN_CONVERSATION(conv) == NULL)
4286 hidden_conv = TRUE;
4290 /* XXX Good luck cleaning up this crap */
4291 contact = PURPLE_CONTACT(PURPLE_BLIST_NODE(b)->parent);
4292 if(contact)
4293 gtkcontactnode = purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(contact));
4295 g_object_get(contact, "alias", &contact_alias, NULL);
4297 /* Name */
4298 if (gtkcontactnode && !gtkcontactnode->contact_expanded && contact_alias)
4299 name = contact_alias;
4300 else
4301 name = purple_buddy_get_alias(b);
4303 /* Raise a contact pre-draw signal here. THe callback will return an
4304 * escaped version of the name. */
4305 nametext = purple_signal_emit_return_1(pidgin_blist_get_handle(), "drawing-buddy", b);
4307 if(!nametext)
4308 nametext = g_markup_escape_text(name, strlen(name));
4310 presence = purple_buddy_get_presence(b);
4312 /* Name is all that is needed */
4313 if (!aliased || biglist) {
4315 /* Status Info */
4316 protocol = purple_protocols_find(purple_account_get_protocol_id(purple_buddy_get_account(b)));
4318 if (protocol && PURPLE_PROTOCOL_IMPLEMENTS(protocol, CLIENT, status_text) &&
4319 purple_account_get_connection(purple_buddy_get_account(b))) {
4320 char *tmp = purple_protocol_client_iface_status_text(protocol, b);
4321 const char *end;
4323 if(tmp && !g_utf8_validate(tmp, -1, &end)) {
4324 char *new = g_strndup(tmp,
4325 g_utf8_pointer_to_offset(tmp, end));
4326 g_free(tmp);
4327 tmp = new;
4329 if(tmp) {
4330 g_strdelimit(tmp, "\n", ' ');
4331 purple_str_strip_char(tmp, '\r');
4333 statustext = tmp;
4336 if(!purple_presence_is_online(presence) && !statustext)
4337 statustext = g_strdup(_("Offline"));
4339 /* Idle Text */
4340 if (purple_presence_is_idle(presence) && purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_idle_time")) {
4341 time_t idle_secs = purple_presence_get_idle_time(presence);
4343 if (idle_secs > 0) {
4344 int iday, ihrs, imin;
4345 time_t t;
4347 time(&t);
4348 iday = (t - idle_secs) / (24 * 60 * 60);
4349 ihrs = ((t - idle_secs) / 60 / 60) % 24;
4350 imin = ((t - idle_secs) / 60) % 60;
4352 if (iday)
4353 idletime = g_strdup_printf(_("Idle %dd %dh %02dm"), iday, ihrs, imin);
4354 else if (ihrs)
4355 idletime = g_strdup_printf(_("Idle %dh %02dm"), ihrs, imin);
4356 else
4357 idletime = g_strdup_printf(_("Idle %dm"), imin);
4359 } else
4360 idletime = g_strdup(_("Idle"));
4364 /* choose the colors of the text */
4365 theme = pidgin_blist_get_theme();
4366 name_color = NULL;
4368 dim_grey = pidgin_style_is_dark(NULL) ? "light slate grey" : "dim grey";
4370 if (theme) {
4371 if (purple_presence_is_idle(presence)) {
4372 namefont = statusfont = pidgin_blist_theme_get_idle_text_info(theme);
4373 name_color = dim_grey;
4374 } else if (!purple_presence_is_online(presence)) {
4375 namefont = pidgin_blist_theme_get_offline_text_info(theme);
4376 name_color = dim_grey;
4377 statusfont = pidgin_blist_theme_get_status_text_info(theme);
4378 } else if (purple_presence_is_available(presence)) {
4379 namefont = pidgin_blist_theme_get_online_text_info(theme);
4380 statusfont = pidgin_blist_theme_get_status_text_info(theme);
4381 } else {
4382 namefont = pidgin_blist_theme_get_away_text_info(theme);
4383 statusfont = pidgin_blist_theme_get_status_text_info(theme);
4385 } else {
4386 if (!selected
4387 && (purple_presence_is_idle(presence)
4388 || !purple_presence_is_online(presence)))
4390 name_color = dim_grey;
4394 name_color = theme_font_get_color_default(namefont, name_color);
4395 name_font = theme_font_get_face_default(namefont, "");
4397 status_color = theme_font_get_color_default(statusfont, dim_grey);
4398 status_font = theme_font_get_face_default(statusfont, "");
4400 if (aliased && selected) {
4401 if (theme) {
4402 name_color = "black";
4403 status_color = "black";
4404 } else {
4405 name_color = NULL;
4406 status_color = NULL;
4410 if (hidden_conv) {
4411 char *tmp = nametext;
4412 nametext = g_strdup_printf("<b>%s</b>", tmp);
4413 g_free(tmp);
4416 /* Put it all together */
4417 if ((!aliased || biglist) && (statustext || idletime)) {
4418 /* using <span size='smaller'> breaks the status, so it must be seperated into <small><span>*/
4419 if (name_color) {
4420 text = g_strdup_printf("<span font_desc='%s' foreground='%s'>%s</span>\n"
4421 "<small><span font_desc='%s' foreground='%s'>%s%s%s</span></small>",
4422 name_font, name_color, nametext, status_font, status_color,
4423 idletime != NULL ? idletime : "",
4424 (idletime != NULL && statustext != NULL) ? " - " : "",
4425 statustext != NULL ? statustext : "");
4426 } else if (status_color) {
4427 text = g_strdup_printf("<span font_desc='%s'>%s</span>\n"
4428 "<small><span font_desc='%s' foreground='%s'>%s%s%s</span></small>",
4429 name_font, nametext, status_font, status_color,
4430 idletime != NULL ? idletime : "",
4431 (idletime != NULL && statustext != NULL) ? " - " : "",
4432 statustext != NULL ? statustext : "");
4433 } else {
4434 text = g_strdup_printf("<span font_desc='%s'>%s</span>\n"
4435 "<small><span font_desc='%s'>%s%s%s</span></small>",
4436 name_font, nametext, status_font,
4437 idletime != NULL ? idletime : "",
4438 (idletime != NULL && statustext != NULL) ? " - " : "",
4439 statustext != NULL ? statustext : "");
4441 } else {
4442 if (name_color) {
4443 text = g_strdup_printf("<span font_desc='%s' color='%s'>%s</span>",
4444 name_font, name_color, nametext);
4445 } else {
4446 text = g_strdup_printf("<span font_desc='%s'>%s</span>", name_font,
4447 nametext);
4450 g_free(nametext);
4451 g_free(statustext);
4452 g_free(idletime);
4453 g_free(contact_alias);
4455 if (hidden_conv) {
4456 char *tmp = text;
4457 text = g_strdup_printf("<b>%s</b>", tmp);
4458 g_free(tmp);
4461 return text;
4464 static void pidgin_blist_restore_window_state(void)
4466 int blist_width, blist_height;
4468 blist_width = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/width");
4470 /* if the window exists, is hidden, we're saving sizes, and the
4471 * size is sane... */
4472 if (gtkblist && gtkblist->window &&
4473 !gtk_widget_get_visible(gtkblist->window) && blist_width != 0) {
4475 blist_height = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/height");
4477 gtk_window_set_default_size(GTK_WINDOW(gtkblist->window),
4478 blist_width, blist_height);
4479 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/list_maximized"))
4480 gtk_window_maximize(GTK_WINDOW(gtkblist->window));
4484 static gboolean pidgin_blist_refresh_timer(PurpleBuddyList *list)
4486 PurpleBlistNode *gnode, *cnode;
4488 if (gtk_blist_visibility == GDK_VISIBILITY_FULLY_OBSCURED
4489 || !gtk_widget_get_visible(gtkblist->window))
4490 return TRUE;
4492 for (gnode = purple_blist_get_root(list); gnode; gnode = gnode->next) {
4493 if(!PURPLE_IS_GROUP(gnode))
4494 continue;
4495 for(cnode = gnode->child; cnode; cnode = cnode->next) {
4496 if(PURPLE_IS_CONTACT(cnode)) {
4497 PurpleBuddy *buddy;
4499 buddy = purple_contact_get_priority_buddy((PurpleContact*)cnode);
4501 if (buddy &&
4502 purple_presence_is_idle(purple_buddy_get_presence(buddy)))
4503 pidgin_blist_update_contact(list, PURPLE_BLIST_NODE(buddy));
4508 /* keep on going */
4509 return TRUE;
4512 static void pidgin_blist_hide_node(PurpleBuddyList *list, PurpleBlistNode *node, gboolean update)
4514 PidginBlistNode *gtknode = purple_blist_node_get_ui_data(node);
4515 GtkTreeIter iter;
4517 if (!gtknode || !gtknode->row || !gtkblist)
4518 return;
4520 if(gtkblist->selected_node == node)
4521 gtkblist->selected_node = NULL;
4522 if (get_iter_from_node(node, &iter)) {
4523 gtk_tree_store_remove(gtkblist->treemodel, &iter);
4524 if(update && (PURPLE_IS_CONTACT(node) ||
4525 PURPLE_IS_BUDDY(node) || PURPLE_IS_CHAT(node))) {
4526 pidgin_blist_update(list, node->parent);
4529 gtk_tree_row_reference_free(gtknode->row);
4530 gtknode->row = NULL;
4533 static const char *require_connection[] =
4535 "/BList/BuddiesMenu/NewInstantMessage",
4536 "/BList/BuddiesMenu/JoinAChat",
4537 "/BList/BuddiesMenu/GetUserInfo",
4538 "/BList/BuddiesMenu/AddBuddy",
4539 "/BList/BuddiesMenu/AddChat",
4540 "/BList/BuddiesMenu/AddGroup",
4541 "/BList/ToolsMenu/Privacy",
4544 static const int require_connection_size = sizeof(require_connection)
4545 / sizeof(*require_connection);
4548 * Rebuild dynamic menus and make menu items sensitive/insensitive
4549 * where appropriate.
4551 static void
4552 update_menu_bar(PidginBuddyList *gtkblist)
4554 GtkAction *action;
4555 gboolean sensitive;
4556 int i;
4558 g_return_if_fail(gtkblist != NULL);
4560 pidgin_blist_update_accounts_menu();
4562 sensitive = (purple_connections_get_all() != NULL);
4564 for (i = 0; i < require_connection_size; i++)
4566 action = gtk_ui_manager_get_action(gtkblist->ui, require_connection[i]);
4567 gtk_action_set_sensitive(action, sensitive);
4570 action = gtk_ui_manager_get_action(gtkblist->ui, "/BList/BuddiesMenu/JoinAChat");
4571 gtk_action_set_sensitive(action, pidgin_blist_joinchat_is_showable());
4573 action = gtk_ui_manager_get_action(gtkblist->ui, "/BList/BuddiesMenu/AddChat");
4574 gtk_action_set_sensitive(action, pidgin_blist_joinchat_is_showable());
4576 action = gtk_ui_manager_get_action(gtkblist->ui, "/BList/ToolsMenu/RoomList");
4577 gtk_action_set_sensitive(action, pidgin_roomlist_is_showable());
4580 static void
4581 sign_on_off_cb(PurpleConnection *gc, PurpleBuddyList *blist)
4583 PidginBuddyList *gtkblist = PIDGIN_BUDDY_LIST(blist);
4585 update_menu_bar(gtkblist);
4588 static void
4589 plugin_changed_cb(PurplePlugin *p, gpointer data)
4591 pidgin_blist_update_plugin_actions();
4594 static void
4595 unseen_conv_menu(GdkEvent *event)
4597 static GtkWidget *menu = NULL;
4598 GList *convs = NULL;
4599 GList *chats, *ims;
4601 if (menu) {
4602 gtk_widget_destroy(menu);
4603 menu = NULL;
4606 ims = pidgin_conversations_get_unseen_ims(PIDGIN_UNSEEN_TEXT, FALSE, 0);
4608 chats = pidgin_conversations_get_unseen_chats(PIDGIN_UNSEEN_NICK, FALSE, 0);
4610 if(ims && chats)
4611 convs = g_list_concat(ims, chats);
4612 else if(ims && !chats)
4613 convs = ims;
4614 else if(!ims && chats)
4615 convs = chats;
4617 if (!convs)
4618 /* no conversations added, don't show the menu */
4619 return;
4621 menu = gtk_menu_new();
4623 pidgin_conversations_fill_menu(menu, convs);
4624 g_list_free(convs);
4625 gtk_widget_show_all(menu);
4626 gtk_menu_popup_at_pointer(GTK_MENU(menu), event);
4629 static gboolean
4630 menutray_press_cb(GtkWidget *widget, GdkEventButton *event)
4632 GList *convs;
4634 if (event->button == GDK_BUTTON_PRIMARY) {
4635 convs = pidgin_conversations_get_unseen_ims(PIDGIN_UNSEEN_TEXT, FALSE, 1);
4636 if(!convs)
4637 convs = pidgin_conversations_get_unseen_chats(PIDGIN_UNSEEN_NICK, FALSE, 1);
4639 if (convs) {
4640 pidgin_conv_present_conversation((PurpleConversation*)convs->data);
4641 g_list_free(convs);
4644 } else if (gdk_event_triggers_context_menu((GdkEvent *)event)) {
4645 unseen_conv_menu((GdkEvent *)event);
4648 return TRUE;
4651 static void
4652 conversation_updated_cb(PurpleConversation *conv, PurpleConversationUpdateType type,
4653 PidginBuddyList *gtkblist)
4655 PurpleAccount *account = purple_conversation_get_account(conv);
4656 GList *convs = NULL;
4657 GList *ims, *chats;
4658 GList *l = NULL;
4660 if (type != PURPLE_CONVERSATION_UPDATE_UNSEEN)
4661 return;
4663 if(account != NULL && purple_conversation_get_name(conv) != NULL) {
4664 PurpleBuddy *buddy = purple_blist_find_buddy(account, purple_conversation_get_name(conv));
4665 if(buddy != NULL)
4666 pidgin_blist_update_buddy(NULL, PURPLE_BLIST_NODE(buddy), TRUE);
4669 if (gtkblist->menutrayicon) {
4670 gtk_widget_destroy(gtkblist->menutrayicon);
4671 gtkblist->menutrayicon = NULL;
4674 ims = pidgin_conversations_get_unseen_ims(PIDGIN_UNSEEN_TEXT, FALSE, 0);
4676 chats = pidgin_conversations_get_unseen_chats(PIDGIN_UNSEEN_NICK, FALSE, 0);
4678 if(ims && chats)
4679 convs = g_list_concat(ims, chats);
4680 else if(ims && !chats)
4681 convs = ims;
4682 else if(!ims && chats)
4683 convs = chats;
4685 if (convs) {
4686 GtkWidget *img = NULL;
4687 GString *tooltip_text = NULL;
4689 tooltip_text = g_string_new("");
4690 l = convs;
4691 while (l != NULL) {
4692 int count = 0;
4693 PidginConversation *gtkconv = PIDGIN_CONVERSATION((PurpleConversation *)l->data);
4695 if(gtkconv)
4696 count = gtkconv->unseen_count;
4697 else if(g_object_get_data(G_OBJECT(l->data), "unseen-count"))
4698 count = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(l->data), "unseen-count"));
4700 g_string_append_printf(tooltip_text,
4701 ngettext("%d unread message from %s\n", "%d unread messages from %s\n", count),
4702 count, purple_conversation_get_title(l->data));
4703 l = l->next;
4705 if(tooltip_text->len > 0) {
4706 /* get rid of the last newline */
4707 g_string_truncate(tooltip_text, tooltip_text->len -1);
4708 img = gtk_image_new_from_stock(PIDGIN_STOCK_TOOLBAR_PENDING,
4709 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL));
4711 gtkblist->menutrayicon = gtk_event_box_new();
4712 gtk_container_add(GTK_CONTAINER(gtkblist->menutrayicon), img);
4713 gtk_widget_show(img);
4714 gtk_widget_show(gtkblist->menutrayicon);
4715 g_signal_connect(G_OBJECT(gtkblist->menutrayicon), "button-press-event", G_CALLBACK(menutray_press_cb), NULL);
4717 pidgin_menu_tray_append(PIDGIN_MENU_TRAY(gtkblist->menutray), gtkblist->menutrayicon, tooltip_text->str);
4719 g_string_free(tooltip_text, TRUE);
4720 g_list_free(convs);
4724 static void
4725 conversation_deleting_cb(PurpleConversation *conv, PidginBuddyList *gtkblist)
4727 conversation_updated_cb(conv, PURPLE_CONVERSATION_UPDATE_UNSEEN, gtkblist);
4730 static void
4731 conversation_deleted_update_ui_cb(PurpleConversation *conv, PidginBlistNode *ui)
4733 if (ui->conv.conv != conv)
4734 return;
4735 ui->conv.conv = NULL;
4736 ui->conv.flags = 0;
4739 static void
4740 written_msg_update_ui_cb(PurpleConversation *conv, PurpleMessage *msg, PurpleBlistNode *node)
4742 PidginBlistNode *ui = purple_blist_node_get_ui_data(node);
4744 if (ui->conv.conv != conv)
4745 return;
4747 if (!pidgin_conv_is_hidden(PIDGIN_CONVERSATION(conv)))
4748 return;
4750 if (!(purple_message_get_flags(msg) & (PURPLE_MESSAGE_SEND | PURPLE_MESSAGE_RECV)))
4751 return;
4753 ui->conv.flags |= PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE;
4754 if (PURPLE_IS_CHAT_CONVERSATION(conv) && (purple_message_get_flags(msg) & PURPLE_MESSAGE_NICK))
4755 ui->conv.flags |= PIDGIN_BLIST_CHAT_HAS_PENDING_MESSAGE_WITH_NICK;
4757 pidgin_blist_update(purple_blist_get_default(), node);
4760 static void
4761 displayed_msg_update_ui_cb(PidginConversation *gtkconv, PurpleBlistNode *node)
4763 PidginBlistNode *ui = purple_blist_node_get_ui_data(node);
4764 if (ui->conv.conv != gtkconv->active_conv)
4765 return;
4766 ui->conv.flags &= ~(PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE |
4767 PIDGIN_BLIST_CHAT_HAS_PENDING_MESSAGE_WITH_NICK);
4768 pidgin_blist_update(purple_blist_get_default(), node);
4771 static void
4772 conversation_created_cb(PurpleConversation *conv, PidginBuddyList *gtkblist)
4774 PurpleAccount *account = purple_conversation_get_account(conv);
4776 if (PURPLE_IS_IM_CONVERSATION(conv)) {
4777 GSList *buddies = purple_blist_find_buddies(account, purple_conversation_get_name(conv));
4778 while (buddies) {
4779 PurpleBlistNode *buddy = buddies->data;
4780 PidginBlistNode *ui = purple_blist_node_get_ui_data(buddy);
4781 buddies = g_slist_delete_link(buddies, buddies);
4782 if (!ui)
4783 continue;
4784 ui->conv.conv = conv;
4785 ui->conv.flags = 0;
4786 purple_signal_connect(purple_conversations_get_handle(), "deleting-conversation",
4787 ui, PURPLE_CALLBACK(conversation_deleted_update_ui_cb), ui);
4788 purple_signal_connect(purple_conversations_get_handle(), "wrote-im-msg",
4789 ui, PURPLE_CALLBACK(written_msg_update_ui_cb), buddy);
4790 purple_signal_connect(pidgin_conversations_get_handle(), "conversation-displayed",
4791 ui, PURPLE_CALLBACK(displayed_msg_update_ui_cb), buddy);
4793 } else if (PURPLE_IS_CHAT_CONVERSATION(conv)) {
4794 PurpleChat *chat = purple_blist_find_chat(account, purple_conversation_get_name(conv));
4795 PidginBlistNode *ui;
4796 if (!chat)
4797 return;
4798 ui = purple_blist_node_get_ui_data(&(chat->node));
4799 if (!ui)
4800 return;
4801 ui->conv.conv = conv;
4802 ui->conv.flags = 0;
4803 purple_signal_connect(purple_conversations_get_handle(), "deleting-conversation",
4804 ui, PURPLE_CALLBACK(conversation_deleted_update_ui_cb), ui);
4805 purple_signal_connect(purple_conversations_get_handle(), "wrote-chat-msg",
4806 ui, PURPLE_CALLBACK(written_msg_update_ui_cb), chat);
4807 purple_signal_connect(pidgin_conversations_get_handle(), "conversation-displayed",
4808 ui, PURPLE_CALLBACK(displayed_msg_update_ui_cb), chat);
4812 /**********************************************************************************
4813 * Public API Functions *
4814 **********************************************************************************/
4816 static void
4817 pidgin_blist_new_node(PurpleBuddyList *list, PurpleBlistNode *node)
4819 purple_blist_node_set_ui_data(node, g_new0(PidginBlistNode, 1));
4822 gboolean pidgin_blist_node_is_contact_expanded(PurpleBlistNode *node)
4824 if (PURPLE_IS_BUDDY(node)) {
4825 node = node->parent;
4826 if (node == NULL)
4827 return FALSE;
4830 g_return_val_if_fail(PURPLE_IS_CONTACT(node), FALSE);
4832 return ((PidginBlistNode *)purple_blist_node_get_ui_data(node))->contact_expanded;
4835 enum {
4836 DRAG_BUDDY,
4837 DRAG_ROW,
4838 DRAG_VCARD,
4839 DRAG_TEXT,
4840 DRAG_URI,
4841 NUM_TARGETS
4844 void pidgin_blist_setup_sort_methods()
4846 const char *id;
4848 pidgin_blist_sort_method_reg("none", _("Manually"), sort_method_none);
4849 pidgin_blist_sort_method_reg("alphabetical", _("Alphabetically"), sort_method_alphabetical);
4850 pidgin_blist_sort_method_reg("status", _("By status"), sort_method_status);
4851 pidgin_blist_sort_method_reg("log_size", _("By recent log activity"), sort_method_log_activity);
4853 id = purple_prefs_get_string(PIDGIN_PREFS_ROOT "/blist/sort_type");
4854 if (id == NULL) {
4855 purple_debug_warning("gtkblist", "Sort method was NULL, resetting to alphabetical\n");
4856 id = "alphabetical";
4858 pidgin_blist_sort_method_set(id);
4861 static void _prefs_change_redo_list(const char *name, PurplePrefType type,
4862 gconstpointer val, gpointer data)
4864 GtkTreeSelection *sel;
4865 GtkTreeIter iter;
4866 PurpleBlistNode *node = NULL;
4868 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist->treeview));
4869 if (gtk_tree_selection_get_selected(sel, NULL, &iter))
4871 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
4874 redo_buddy_list(purple_blist_get_default(), FALSE, FALSE);
4875 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(gtkblist->treeview));
4877 if (node)
4879 PidginBlistNode *gtknode;
4880 GtkTreePath *path;
4882 gtknode = purple_blist_node_get_ui_data(node);
4883 if (gtknode && gtknode->row)
4885 path = gtk_tree_row_reference_get_path(gtknode->row);
4886 gtk_tree_selection_select_path(sel, path);
4887 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(gtkblist->treeview), path, NULL, FALSE, 0, 0);
4888 gtk_tree_path_free(path);
4893 static void _prefs_change_sort_method(const char *pref_name, PurplePrefType type,
4894 gconstpointer val, gpointer data)
4896 if(purple_strequal(pref_name, PIDGIN_PREFS_ROOT "/blist/sort_type"))
4897 pidgin_blist_sort_method_set(val);
4900 static gboolean pidgin_blist_select_notebook_page_cb(gpointer user_data)
4902 PidginBuddyList *gtkblist = (PidginBuddyList *)user_data;
4903 int errors = 0;
4904 GList *list = NULL;
4905 PidginBuddyListPrivate *priv;
4907 priv = pidgin_buddy_list_get_instance_private(gtkblist);
4909 priv->select_notebook_page_timeout = 0;
4911 /* this is far too ugly thanks to me not wanting to fix #3989 properly right now */
4912 if (priv->error_scrollbook != NULL) {
4913 errors = gtk_notebook_get_n_pages(GTK_NOTEBOOK(priv->error_scrollbook->notebook));
4915 if ((list = purple_accounts_get_all_active()) != NULL || errors) {
4916 gtk_notebook_set_current_page(GTK_NOTEBOOK(gtkblist->notebook), 1);
4917 g_list_free(list);
4918 } else
4919 gtk_notebook_set_current_page(GTK_NOTEBOOK(gtkblist->notebook), 0);
4921 priv->select_notebook_page_timeout = 0;
4922 return FALSE;
4925 static void pidgin_blist_select_notebook_page(PidginBuddyList *gtkblist)
4927 PidginBuddyListPrivate *priv =
4928 pidgin_buddy_list_get_instance_private(gtkblist);
4929 priv->select_notebook_page_timeout = g_timeout_add(0,
4930 pidgin_blist_select_notebook_page_cb, gtkblist);
4933 static void account_modified(PurpleAccount *account, PidginBuddyList *gtkblist)
4935 if (!gtkblist)
4936 return;
4938 pidgin_blist_select_notebook_page(gtkblist);
4939 update_menu_bar(gtkblist);
4942 static void
4943 account_actions_changed(PurpleAccount *account, gpointer data)
4945 pidgin_blist_update_accounts_menu();
4948 static void
4949 account_status_changed(PurpleAccount *account, PurpleStatus *old,
4950 PurpleStatus *new, PidginBuddyList *gtkblist)
4952 if (!gtkblist)
4953 return;
4955 account_modified(account, gtkblist);
4958 static gboolean
4959 gtk_blist_window_key_press_cb(GtkWidget *w, GdkEventKey *event, PidginBuddyList *gtkblist)
4961 /* clear any tooltips */
4962 pidgin_blist_tooltip_destroy();
4964 return FALSE;
4967 static void
4968 reset_headline(PidginBuddyList *gtkblist)
4970 gtkblist->headline_callback = NULL;
4971 gtkblist->headline_data = NULL;
4972 gtkblist->headline_destroy = NULL;
4973 pidgin_set_urgent(GTK_WINDOW(gtkblist->window), FALSE);
4976 static gboolean
4977 headline_click_callback(gpointer unused)
4979 if (gtkblist->headline_callback)
4980 ((GSourceFunc) gtkblist->headline_callback)(gtkblist->headline_data);
4981 reset_headline(gtkblist);
4982 return FALSE;
4985 static gboolean
4986 headline_response_cb(GtkInfoBar *infobar, int resp, PidginBuddyList *gtkblist)
4988 gtk_widget_hide(gtkblist->headline);
4990 if (resp == GTK_RESPONSE_OK) {
4991 if (gtkblist->headline_callback)
4992 g_idle_add(headline_click_callback, NULL);
4993 else {
4994 if (gtkblist->headline_destroy)
4995 gtkblist->headline_destroy(gtkblist->headline_data);
4996 reset_headline(gtkblist);
4998 } else {
4999 if (gtkblist->headline_destroy)
5000 gtkblist->headline_destroy(gtkblist->headline_data);
5001 reset_headline(gtkblist);
5004 return FALSE;
5007 static void
5008 headline_realize_cb(GtkWidget *widget, gpointer data)
5010 GdkWindow *window = gtk_widget_get_window(widget);
5011 GdkDisplay *display = gdk_window_get_display(window);
5012 GdkCursor *hand_cursor = gdk_cursor_new_for_display(display, GDK_HAND2);
5013 gdk_window_set_cursor(window, hand_cursor);
5014 g_object_unref(hand_cursor);
5017 static gboolean
5018 headline_press_cb(GtkWidget *widget, GdkEventButton *event, GtkInfoBar *infobar)
5020 gtk_info_bar_response(infobar, GTK_RESPONSE_OK);
5021 return TRUE;
5024 /***********************************/
5025 /* Connection error handling stuff */
5026 /***********************************/
5028 #define OBJECT_DATA_KEY_ACCOUNT "account"
5029 #define DO_NOT_CLEAR_ERROR "do-not-clear-error"
5031 static gboolean
5032 find_account_widget(GObject *widget,
5033 PurpleAccount *account)
5035 if (g_object_get_data(widget, OBJECT_DATA_KEY_ACCOUNT) == account)
5036 return 0; /* found */
5037 else
5038 return 1;
5041 static void
5042 pack_protocol_icon_start(GtkWidget *box,
5043 PurpleAccount *account)
5045 GdkPixbuf *pixbuf;
5046 GtkWidget *image;
5048 pixbuf = pidgin_create_protocol_icon(account, PIDGIN_PROTOCOL_ICON_SMALL);
5049 if (pixbuf != NULL) {
5050 image = gtk_image_new_from_pixbuf(pixbuf);
5051 g_object_unref(pixbuf);
5053 gtk_box_pack_start(GTK_BOX(box), image, FALSE, FALSE, 0);
5057 static void
5058 add_error_dialog(PidginBuddyList *gtkblist,
5059 GtkWidget *dialog)
5061 PidginBuddyListPrivate *priv =
5062 pidgin_buddy_list_get_instance_private(gtkblist);
5063 gtk_container_add(GTK_CONTAINER(priv->error_scrollbook), dialog);
5066 static GtkWidget *
5067 find_child_widget_by_account(GtkContainer *container,
5068 PurpleAccount *account)
5070 GList *l = NULL;
5071 GList *children = NULL;
5072 GtkWidget *ret = NULL;
5073 /* XXX: Workaround for the currently incomplete implementation of PidginScrollBook */
5074 if (PIDGIN_IS_SCROLL_BOOK(container))
5075 container = GTK_CONTAINER(PIDGIN_SCROLL_BOOK(container)->notebook);
5076 children = gtk_container_get_children(container);
5077 l = g_list_find_custom(children, account, (GCompareFunc) find_account_widget);
5078 if (l)
5079 ret = GTK_WIDGET(l->data);
5080 g_list_free(children);
5081 return ret;
5084 static void
5085 remove_child_widget_by_account(GtkContainer *container,
5086 PurpleAccount *account)
5088 GtkWidget *widget = find_child_widget_by_account(container, account);
5089 if(widget) {
5090 /* Since we are destroying the widget in response to a change in
5091 * error, we should not clear the error.
5093 g_object_set_data(G_OBJECT(widget), DO_NOT_CLEAR_ERROR,
5094 GINT_TO_POINTER(TRUE));
5095 gtk_widget_destroy(widget);
5099 /* Generic error buttons */
5101 static void
5102 generic_error_modify_cb(PurpleAccount *account)
5104 purple_account_clear_current_error(account);
5105 pidgin_account_dialog_show(PIDGIN_MODIFY_ACCOUNT_DIALOG, account);
5108 static void
5109 generic_error_enable_cb(PurpleAccount *account)
5111 purple_account_clear_current_error(account);
5112 purple_account_set_enabled(account, purple_core_get_ui(), TRUE);
5115 static void
5116 generic_error_destroy_cb(GtkWidget *dialog,
5117 PurpleAccount *account)
5119 /* If the error dialog is being destroyed in response to the
5120 * account-error-changed signal, we don't want to clear the current
5121 * error.
5123 if (g_object_get_data(G_OBJECT(dialog), DO_NOT_CLEAR_ERROR) == NULL)
5124 purple_account_clear_current_error(account);
5127 #define SSL_FAQ_URI "https://developer.pidgin.im/wiki/FAQssl"
5129 static void
5130 ssl_faq_clicked_cb(PidginMiniDialog *mini_dialog,
5131 GtkButton *button,
5132 gpointer ignored)
5134 purple_notify_uri(NULL, SSL_FAQ_URI);
5137 static void
5138 add_generic_error_dialog(PurpleAccount *account,
5139 const PurpleConnectionErrorInfo *err)
5141 GtkWidget *mini_dialog;
5142 const char *username = purple_account_get_username(account);
5143 gboolean enabled =
5144 purple_account_get_enabled(account, purple_core_get_ui());
5145 char *primary;
5147 if (enabled)
5148 primary = g_strdup_printf(_("%s disconnected"), username);
5149 else
5150 primary = g_strdup_printf(_("%s disabled"), username);
5152 mini_dialog = pidgin_make_mini_dialog(NULL, "dialog-error",
5153 primary, err->description, account,
5154 (enabled ? _("Reconnect") : _("Re-enable")),
5155 (enabled ? PURPLE_CALLBACK(purple_account_connect)
5156 : PURPLE_CALLBACK(generic_error_enable_cb)),
5157 _("Modify Account"), PURPLE_CALLBACK(generic_error_modify_cb),
5158 NULL);
5160 g_free(primary);
5162 g_object_set_data(G_OBJECT(mini_dialog), OBJECT_DATA_KEY_ACCOUNT,
5163 account);
5165 if(err->type == PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT)
5166 pidgin_mini_dialog_add_non_closing_button(PIDGIN_MINI_DIALOG(mini_dialog),
5167 _("SSL FAQs"), ssl_faq_clicked_cb, NULL);
5169 g_signal_connect_after(mini_dialog, "destroy",
5170 (GCallback)generic_error_destroy_cb,
5171 account);
5173 add_error_dialog(gtkblist, mini_dialog);
5176 static void
5177 remove_generic_error_dialog(PurpleAccount *account)
5179 PidginBuddyListPrivate *priv =
5180 pidgin_buddy_list_get_instance_private(gtkblist);
5181 remove_child_widget_by_account(
5182 GTK_CONTAINER(priv->error_scrollbook), account);
5186 static void
5187 update_generic_error_message(PurpleAccount *account,
5188 const char *description)
5190 PidginBuddyListPrivate *priv =
5191 pidgin_buddy_list_get_instance_private(gtkblist);
5192 GtkWidget *mini_dialog = find_child_widget_by_account(
5193 GTK_CONTAINER(priv->error_scrollbook), account);
5194 pidgin_mini_dialog_set_description(PIDGIN_MINI_DIALOG(mini_dialog),
5195 description);
5199 /* Notifications about accounts which were disconnected with
5200 * PURPLE_CONNECTION_ERROR_NAME_IN_USE
5203 typedef void (*AccountFunction)(PurpleAccount *);
5205 static void
5206 elsewhere_foreach_account(PidginMiniDialog *mini_dialog,
5207 AccountFunction f)
5209 PurpleAccount *account;
5210 GList *labels = gtk_container_get_children(
5211 GTK_CONTAINER(mini_dialog->contents));
5212 GList *l;
5214 for (l = labels; l; l = l->next) {
5215 account = g_object_get_data(G_OBJECT(l->data), OBJECT_DATA_KEY_ACCOUNT);
5216 if (account)
5217 f(account);
5218 else
5219 purple_debug_warning("gtkblist", "mini_dialog's child "
5220 "didn't have an account stored in it!");
5222 g_list_free(labels);
5225 static void
5226 enable_account(PurpleAccount *account)
5228 purple_account_set_enabled(account, purple_core_get_ui(), TRUE);
5231 static void
5232 reconnect_elsewhere_accounts(PidginMiniDialog *mini_dialog,
5233 GtkButton *button,
5234 gpointer unused)
5236 elsewhere_foreach_account(mini_dialog, enable_account);
5239 static void
5240 clear_elsewhere_errors(PidginMiniDialog *mini_dialog,
5241 gpointer unused)
5243 elsewhere_foreach_account(mini_dialog, purple_account_clear_current_error);
5246 static void
5247 ensure_signed_on_elsewhere_minidialog(PidginBuddyList *gtkblist)
5249 PidginBuddyListPrivate *priv =
5250 pidgin_buddy_list_get_instance_private(gtkblist);
5251 PidginMiniDialog *mini_dialog;
5253 if(priv->signed_on_elsewhere)
5254 return;
5256 mini_dialog = priv->signed_on_elsewhere =
5257 pidgin_mini_dialog_new(_("Welcome back!"), NULL, PIDGIN_STOCK_DISCONNECT);
5259 pidgin_mini_dialog_add_button(mini_dialog, _("Re-enable"),
5260 reconnect_elsewhere_accounts, NULL);
5262 /* Make dismissing the dialog clear the errors. The "destroy" signal
5263 * does not appear to fire at quit, which is fortunate!
5265 g_signal_connect(G_OBJECT(mini_dialog), "destroy",
5266 (GCallback) clear_elsewhere_errors, NULL);
5268 add_error_dialog(gtkblist, GTK_WIDGET(mini_dialog));
5270 /* Set priv->signed_on_elsewhere to NULL when the dialog is destroyed */
5271 g_signal_connect(G_OBJECT(mini_dialog), "destroy",
5272 (GCallback) gtk_widget_destroyed, &(priv->signed_on_elsewhere));
5275 static void
5276 update_signed_on_elsewhere_minidialog_title(void)
5278 PidginBuddyListPrivate *priv =
5279 pidgin_buddy_list_get_instance_private(gtkblist);
5280 PidginMiniDialog *mini_dialog = priv->signed_on_elsewhere;
5281 guint accounts;
5282 char *title;
5284 if (mini_dialog == NULL)
5285 return;
5287 accounts = pidgin_mini_dialog_get_num_children(mini_dialog);
5288 if (accounts == 0) {
5289 gtk_widget_destroy(GTK_WIDGET(mini_dialog));
5290 return;
5293 title = g_strdup_printf(
5294 ngettext("%d account was disabled because you signed on from another location:",
5295 "%d accounts were disabled because you signed on from another location:",
5296 accounts),
5297 accounts);
5298 pidgin_mini_dialog_set_description(mini_dialog, title);
5299 g_free(title);
5302 static GtkWidget *
5303 create_account_label(PurpleAccount *account)
5305 GtkWidget *hbox, *label;
5306 const char *username = purple_account_get_username(account);
5307 char *markup;
5308 char *description;
5310 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
5311 g_object_set_data(G_OBJECT(hbox), OBJECT_DATA_KEY_ACCOUNT, account);
5313 pack_protocol_icon_start(hbox, account);
5315 label = gtk_label_new(NULL);
5316 markup = g_strdup_printf("<span size=\"smaller\">%s</span>", username);
5317 gtk_label_set_markup(GTK_LABEL(label), markup);
5318 g_free(markup);
5319 gtk_label_set_xalign(GTK_LABEL(label), 0);
5320 gtk_label_set_yalign(GTK_LABEL(label), 0);
5321 g_object_set(G_OBJECT(label), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
5322 description = purple_account_get_current_error(account)->description;
5323 if (description != NULL && *description != '\0')
5324 gtk_widget_set_tooltip_text(label, description);
5325 gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
5327 return hbox;
5330 static void
5331 add_to_signed_on_elsewhere(PurpleAccount *account)
5333 PidginBuddyListPrivate *priv =
5334 pidgin_buddy_list_get_instance_private(gtkblist);
5335 PidginMiniDialog *mini_dialog;
5336 GtkWidget *account_label;
5338 ensure_signed_on_elsewhere_minidialog(gtkblist);
5339 mini_dialog = priv->signed_on_elsewhere;
5341 if(find_child_widget_by_account(GTK_CONTAINER(mini_dialog->contents), account))
5342 return;
5344 account_label = create_account_label(account);
5345 gtk_box_pack_start(mini_dialog->contents, account_label, FALSE, FALSE, 0);
5346 gtk_widget_show_all(account_label);
5348 update_signed_on_elsewhere_minidialog_title();
5351 static void
5352 remove_from_signed_on_elsewhere(PurpleAccount *account)
5354 PidginBuddyListPrivate *priv =
5355 pidgin_buddy_list_get_instance_private(gtkblist);
5356 PidginMiniDialog *mini_dialog = priv->signed_on_elsewhere;
5357 if(mini_dialog == NULL)
5358 return;
5360 remove_child_widget_by_account(GTK_CONTAINER(mini_dialog->contents), account);
5362 update_signed_on_elsewhere_minidialog_title();
5366 static void
5367 update_signed_on_elsewhere_tooltip(PurpleAccount *account,
5368 const char *description)
5370 PidginBuddyListPrivate *priv =
5371 pidgin_buddy_list_get_instance_private(gtkblist);
5372 GtkContainer *c = GTK_CONTAINER(priv->signed_on_elsewhere->contents);
5373 GtkWidget *label = find_child_widget_by_account(c, account);
5374 gtk_widget_set_tooltip_text(label, description);
5378 /* Call appropriate error notification code based on error types */
5379 static void
5380 update_account_error_state(PurpleAccount *account,
5381 const PurpleConnectionErrorInfo *old,
5382 const PurpleConnectionErrorInfo *new,
5383 PidginBuddyList *gtkblist)
5385 gboolean descriptions_differ;
5386 const char *desc;
5388 if (old == NULL && new == NULL)
5389 return;
5391 if (new != NULL)
5392 pidgin_blist_select_notebook_page(gtkblist);
5394 if (old != NULL && new == NULL) {
5395 if(old->type == PURPLE_CONNECTION_ERROR_NAME_IN_USE)
5396 remove_from_signed_on_elsewhere(account);
5397 else
5398 remove_generic_error_dialog(account);
5399 return;
5402 if (old == NULL && new != NULL) {
5403 if(new->type == PURPLE_CONNECTION_ERROR_NAME_IN_USE)
5404 add_to_signed_on_elsewhere(account);
5405 else
5406 add_generic_error_dialog(account, new);
5407 return;
5410 /* else, new and old are both non-NULL */
5412 descriptions_differ = !purple_strequal(old->description, new->description);
5413 desc = new->description;
5415 switch (new->type) {
5416 case PURPLE_CONNECTION_ERROR_NAME_IN_USE:
5417 if (old->type == PURPLE_CONNECTION_ERROR_NAME_IN_USE
5418 && descriptions_differ) {
5419 update_signed_on_elsewhere_tooltip(account, desc);
5420 } else {
5421 remove_generic_error_dialog(account);
5422 add_to_signed_on_elsewhere(account);
5424 break;
5425 default:
5426 if (old->type == PURPLE_CONNECTION_ERROR_NAME_IN_USE) {
5427 remove_from_signed_on_elsewhere(account);
5428 add_generic_error_dialog(account, new);
5429 } else if (descriptions_differ) {
5430 update_generic_error_message(account, desc);
5432 break;
5436 /* In case accounts are loaded before the blist (which they currently are),
5437 * let's call update_account_error_state ourselves on every account's current
5438 * state when the blist starts.
5440 static void
5441 show_initial_account_errors(PidginBuddyList *gtkblist)
5443 GList *l = purple_accounts_get_all();
5444 PurpleAccount *account;
5445 const PurpleConnectionErrorInfo *err;
5447 for (; l; l = l->next)
5449 account = l->data;
5450 err = purple_account_get_current_error(account);
5452 update_account_error_state(account, NULL, err, gtkblist);
5456 /* This assumes there are not things like groupless buddies or multi-leveled groups.
5457 * I'm sure other things in this code assumes that also.
5459 static void
5460 treeview_style_set(GtkWidget *widget,
5461 gpointer data)
5463 PurpleBuddyList *list = data;
5464 PurpleBlistNode *node = purple_blist_get_root(list);
5465 while (node) {
5466 pidgin_blist_update_group(list, node);
5467 node = node->next;
5471 /******************************************/
5472 /* End of connection error handling stuff */
5473 /******************************************/
5475 static int
5476 blist_focus_cb(GtkWidget *widget, GdkEventFocus *event, PidginBuddyList *gtkblist)
5478 if(event->in) {
5479 gtk_blist_focused = TRUE;
5480 pidgin_set_urgent(GTK_WINDOW(gtkblist->window), FALSE);
5481 } else {
5482 gtk_blist_focused = FALSE;
5484 return 0;
5487 #if 0
5488 static GtkWidget *
5489 kiosk_page()
5491 GtkWidget *ret = gtk_box_new(GTK_ORIENTATION_VERTICAL, PIDGIN_HIG_BOX_SPACE);
5492 GtkWidget *label;
5493 GtkWidget *entry;
5494 GtkWidget *bbox;
5495 GtkWidget *button;
5497 label = gtk_label_new(NULL);
5498 gtk_box_pack_start(GTK_BOX(ret), label, TRUE, TRUE, 0);
5500 label = gtk_label_new(NULL);
5501 gtk_label_set_markup(GTK_LABEL(label), _("<b>Username:</b>"));
5502 gtk_label_set_xalign(GTK_LABEL(label), 0.0);
5503 gtk_box_pack_start(GTK_BOX(ret), label, FALSE, FALSE, 0);
5504 entry = gtk_entry_new();
5505 gtk_box_pack_start(GTK_BOX(ret), entry, FALSE, FALSE, 0);
5507 label = gtk_label_new(NULL);
5508 gtk_label_set_markup(GTK_LABEL(label), _("<b>Password:</b>"));
5509 gtk_label_set_xalign(GTK_LABEL(label), 0.0);
5510 gtk_box_pack_start(GTK_BOX(ret), label, FALSE, FALSE, 0);
5511 entry = gtk_entry_new();
5512 gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
5513 gtk_box_pack_start(GTK_BOX(ret), entry, FALSE, FALSE, 0);
5515 label = gtk_label_new(" ");
5516 gtk_box_pack_start(GTK_BOX(ret), label, FALSE, FALSE, 0);
5518 bbox = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL);
5519 button = gtk_button_new_with_mnemonic(_("_Login"));
5520 gtk_box_pack_start(GTK_BOX(ret), bbox, FALSE, FALSE, 0);
5521 gtk_container_add(GTK_CONTAINER(bbox), button);
5524 label = gtk_label_new(NULL);
5525 gtk_box_pack_start(GTK_BOX(ret), label, TRUE, TRUE, 0);
5527 gtk_container_set_border_width(GTK_CONTAINER(ret), PIDGIN_HIG_BORDER);
5529 gtk_widget_show_all(ret);
5530 return ret;
5532 #endif
5534 /* builds the blist layout according to to the current theme */
5535 static void
5536 pidgin_blist_build_layout(PurpleBuddyList *list)
5538 GtkTreeViewColumn *column;
5539 PidginBlistLayout *layout;
5540 PidginBlistTheme *theme;
5541 GtkCellRenderer *rend;
5542 gint i, status_icon = 0, text = 1, emblem = 2, protocol_icon = 3, buddy_icon = 4;
5545 column = gtkblist->text_column;
5547 if ((theme = pidgin_blist_get_theme()) != NULL && (layout = pidgin_blist_theme_get_layout(theme)) != NULL) {
5548 status_icon = layout->status_icon ;
5549 text = layout->text;
5550 emblem = layout->emblem;
5551 protocol_icon = layout->protocol_icon;
5552 buddy_icon = layout->buddy_icon;
5555 gtk_tree_view_column_clear(column);
5557 /* group */
5558 rend = pidgin_cell_renderer_expander_new();
5559 gtk_tree_view_column_pack_start(column, rend, FALSE);
5560 gtk_tree_view_column_set_attributes(column, rend,
5561 "visible", GROUP_EXPANDER_VISIBLE_COLUMN,
5562 "expander-visible", GROUP_EXPANDER_COLUMN,
5563 "sensitive", GROUP_EXPANDER_COLUMN,
5564 "cell-background-rgba", BGCOLOR_COLUMN,
5565 NULL);
5567 /* contact */
5568 rend = pidgin_cell_renderer_expander_new();
5569 gtk_tree_view_column_pack_start(column, rend, FALSE);
5570 gtk_tree_view_column_set_attributes(column, rend,
5571 "visible", CONTACT_EXPANDER_VISIBLE_COLUMN,
5572 "expander-visible", CONTACT_EXPANDER_COLUMN,
5573 "sensitive", CONTACT_EXPANDER_COLUMN,
5574 "cell-background-rgba", BGCOLOR_COLUMN,
5575 NULL);
5577 for (i = 0; i < 5; i++) {
5579 if (status_icon == i) {
5580 /* status icons */
5581 rend = gtk_cell_renderer_pixbuf_new();
5582 gtk_tree_view_column_pack_start(column, rend, FALSE);
5583 gtk_tree_view_column_set_attributes(column, rend,
5584 "pixbuf", STATUS_ICON_COLUMN,
5585 "visible", STATUS_ICON_VISIBLE_COLUMN,
5586 "cell-background-rgba", BGCOLOR_COLUMN,
5587 NULL);
5588 g_object_set(rend, "xalign", 0.0, "xpad", 6, "ypad", 0, NULL);
5590 } else if (text == i) {
5591 /* name */
5592 gtkblist->text_rend = rend = gtk_cell_renderer_text_new();
5593 gtk_tree_view_column_pack_start(column, rend, TRUE);
5594 gtk_tree_view_column_set_attributes(column, rend,
5595 "cell-background-rgba", BGCOLOR_COLUMN,
5596 "markup", NAME_COLUMN,
5597 NULL);
5598 g_signal_connect(
5599 G_OBJECT(rend), "editing-started",
5600 G_CALLBACK(gtk_blist_renderer_editing_started_cb),
5601 list);
5602 g_signal_connect(G_OBJECT(rend), "editing-canceled", G_CALLBACK(gtk_blist_renderer_editing_cancelled_cb), list);
5603 g_signal_connect(G_OBJECT(rend), "edited", G_CALLBACK(gtk_blist_renderer_edited_cb), list);
5604 g_object_set(rend, "ypad", 0, "yalign", 0.5, NULL);
5605 g_object_set(rend, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
5607 /* idle */
5608 rend = gtk_cell_renderer_text_new();
5609 g_object_set(rend, "xalign", 1.0, "ypad", 0, NULL);
5610 gtk_tree_view_column_pack_start(column, rend, FALSE);
5611 gtk_tree_view_column_set_attributes(column, rend,
5612 "markup", IDLE_COLUMN,
5613 "visible", IDLE_VISIBLE_COLUMN,
5614 "cell-background-rgba", BGCOLOR_COLUMN,
5615 NULL);
5616 } else if (emblem == i) {
5617 /* emblem */
5618 rend = gtk_cell_renderer_pixbuf_new();
5619 g_object_set(rend, "xalign", 1.0, "yalign", 0.5, "ypad", 0, "xpad", 3, NULL);
5620 gtk_tree_view_column_pack_start(column, rend, FALSE);
5621 gtk_tree_view_column_set_attributes(column, rend, "pixbuf", EMBLEM_COLUMN,
5622 "cell-background-rgba", BGCOLOR_COLUMN,
5623 "visible", EMBLEM_VISIBLE_COLUMN, NULL);
5625 } else if (protocol_icon == i) {
5626 /* protocol icon */
5627 rend = gtk_cell_renderer_pixbuf_new();
5628 gtk_tree_view_column_pack_start(column, rend, FALSE);
5629 gtk_tree_view_column_set_attributes(column, rend,
5630 "pixbuf", PROTOCOL_ICON_COLUMN,
5631 "visible", PROTOCOL_ICON_VISIBLE_COLUMN,
5632 "cell-background-rgba", BGCOLOR_COLUMN,
5633 NULL);
5634 g_object_set(rend, "xalign", 0.0, "xpad", 3, "ypad", 0, NULL);
5636 } else if (buddy_icon == i) {
5637 /* buddy icon */
5638 rend = gtk_cell_renderer_pixbuf_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, "pixbuf", BUDDY_ICON_COLUMN,
5642 "cell-background-rgba", BGCOLOR_COLUMN,
5643 "visible", BUDDY_ICON_VISIBLE_COLUMN,
5644 NULL);
5647 }/* end for loop */
5651 static gboolean
5652 pidgin_blist_search_equal_func(GtkTreeModel *model, gint column,
5653 const gchar *key, GtkTreeIter *iter, gpointer data)
5655 PurpleBlistNode *node = NULL;
5656 gboolean res = TRUE;
5657 const char *compare = NULL;
5659 if (!pidgin_tree_view_search_equal_func(model, column, key, iter, data))
5660 return FALSE;
5662 /* If the search string does not match the displayed label, then look
5663 * at the alternate labels for the nodes and search in them. Currently,
5664 * alternate labels that make sense are usernames/email addresses for
5665 * buddies (but only for the ones who don't have a local alias).
5668 gtk_tree_model_get(model, iter, NODE_COLUMN, &node, -1);
5669 if (!node)
5670 return TRUE;
5672 compare = NULL;
5673 if (PURPLE_IS_CONTACT(node)) {
5674 PurpleBuddy *b = purple_contact_get_priority_buddy(PURPLE_CONTACT(node));
5675 if (!purple_buddy_get_local_alias(b))
5676 compare = purple_buddy_get_name(b);
5677 } else if (PURPLE_IS_BUDDY(node)) {
5678 if (!purple_buddy_get_local_alias(PURPLE_BUDDY(node)))
5679 compare = purple_buddy_get_name(PURPLE_BUDDY(node));
5682 if (compare) {
5683 char *tmp, *enteredstring;
5684 tmp = g_utf8_normalize(key, -1, G_NORMALIZE_DEFAULT);
5685 enteredstring = g_utf8_casefold(tmp, -1);
5686 g_free(tmp);
5688 if (purple_str_has_prefix(compare, enteredstring))
5689 res = FALSE;
5690 g_free(enteredstring);
5693 return res;
5696 static void pidgin_blist_show(PurpleBuddyList *list)
5698 PidginBuddyListPrivate *priv;
5699 void *handle;
5700 GtkTreeViewColumn *column;
5701 GtkWidget *menu;
5702 GtkWidget *sep;
5703 GtkWidget *infobar;
5704 GtkWidget *content_area;
5705 GtkWidget *label;
5706 GtkWidget *close;
5707 gchar *text;
5708 const char *theme_name;
5709 GtkActionGroup *action_group;
5710 GError *error;
5711 GtkAccelGroup *accel_group;
5712 GtkTreeSelection *selection;
5713 GtkTargetEntry dte[] = {{"PURPLE_BLIST_NODE", GTK_TARGET_SAME_APP, DRAG_ROW},
5714 {"application/x-im-contact", 0, DRAG_BUDDY},
5715 {"text/x-vcard", 0, DRAG_VCARD },
5716 {"text/uri-list", 0, DRAG_URI},
5717 {"text/plain", 0, DRAG_TEXT}};
5718 GtkTargetEntry ste[] = {{"PURPLE_BLIST_NODE", GTK_TARGET_SAME_APP, DRAG_ROW},
5719 {"application/x-im-contact", 0, DRAG_BUDDY},
5720 {"text/x-vcard", 0, DRAG_VCARD }};
5721 if (gtkblist && gtkblist->window) {
5722 purple_blist_set_visible(purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/list_visible"));
5723 return;
5726 gtkblist = PIDGIN_BUDDY_LIST(list);
5727 priv = pidgin_buddy_list_get_instance_private(gtkblist);
5729 if (priv->current_theme)
5730 g_object_unref(priv->current_theme);
5732 theme_name = purple_prefs_get_string(PIDGIN_PREFS_ROOT "/blist/theme");
5733 if (theme_name && *theme_name)
5734 priv->current_theme = g_object_ref(PIDGIN_BLIST_THEME(purple_theme_manager_find_theme(theme_name, "blist")));
5735 else
5736 priv->current_theme = NULL;
5738 gtkblist->empty_avatar = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, 32, 32);
5739 gdk_pixbuf_fill(gtkblist->empty_avatar, 0x00000000);
5741 gtkblist->window = pidgin_create_window(_("Buddy List"), 0, "buddy_list", TRUE);
5742 g_signal_connect(G_OBJECT(gtkblist->window), "focus-in-event",
5743 G_CALLBACK(blist_focus_cb), gtkblist);
5744 g_signal_connect(G_OBJECT(gtkblist->window), "focus-out-event",
5745 G_CALLBACK(blist_focus_cb), gtkblist);
5747 gtkblist->main_vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
5748 gtk_widget_show(gtkblist->main_vbox);
5749 gtk_container_add(GTK_CONTAINER(gtkblist->window), gtkblist->main_vbox);
5751 g_signal_connect(G_OBJECT(gtkblist->window), "delete_event", G_CALLBACK(gtk_blist_delete_cb), NULL);
5752 g_signal_connect(G_OBJECT(gtkblist->window), "hide",
5753 G_CALLBACK(gtk_blist_hide_cb), gtkblist);
5754 g_signal_connect(G_OBJECT(gtkblist->window), "show",
5755 G_CALLBACK(gtk_blist_show_cb), gtkblist);
5756 g_signal_connect(G_OBJECT(gtkblist->window), "size-allocate",
5757 G_CALLBACK(gtk_blist_size_allocate_cb), NULL);
5758 g_signal_connect(G_OBJECT(gtkblist->window), "visibility_notify_event", G_CALLBACK(gtk_blist_visibility_cb), NULL);
5759 g_signal_connect(G_OBJECT(gtkblist->window), "window_state_event", G_CALLBACK(gtk_blist_window_state_cb), NULL);
5760 g_signal_connect(G_OBJECT(gtkblist->window), "key_press_event", G_CALLBACK(gtk_blist_window_key_press_cb), gtkblist);
5761 gtk_widget_add_events(gtkblist->window, GDK_VISIBILITY_NOTIFY_MASK);
5763 /******************************* Menu bar *************************************/
5764 action_group = gtk_action_group_new("BListActions");
5765 gtk_action_group_set_translation_domain(action_group, PACKAGE);
5766 gtk_action_group_add_actions(action_group,
5767 blist_menu_entries,
5768 G_N_ELEMENTS(blist_menu_entries),
5769 GTK_WINDOW(gtkblist->window));
5770 gtk_action_group_add_toggle_actions(action_group,
5771 blist_menu_toggle_entries,
5772 G_N_ELEMENTS(blist_menu_toggle_entries),
5773 GTK_WINDOW(gtkblist->window));
5775 gtkblist->ui = gtk_ui_manager_new();
5776 gtk_ui_manager_insert_action_group(gtkblist->ui, action_group, 0);
5778 accel_group = gtk_ui_manager_get_accel_group(gtkblist->ui);
5779 gtk_window_add_accel_group(GTK_WINDOW(gtkblist->window), accel_group);
5780 pidgin_load_accels();
5781 g_signal_connect(G_OBJECT(accel_group), "accel-changed", G_CALLBACK(pidgin_save_accels_cb), NULL);
5783 error = NULL;
5784 if (!gtk_ui_manager_add_ui_from_string(gtkblist->ui, blist_menu, -1, &error))
5786 g_message("building menus failed: %s", error->message);
5787 g_error_free(error);
5788 exit(EXIT_FAILURE);
5791 menu = gtk_ui_manager_get_widget(gtkblist->ui, "/BList");
5792 gtkblist->menutray = pidgin_menu_tray_new();
5793 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtkblist->menutray);
5794 gtk_widget_show(gtkblist->menutray);
5795 gtk_widget_show(menu);
5796 gtk_box_pack_start(GTK_BOX(gtkblist->main_vbox), menu, FALSE, FALSE, 0);
5798 menu = gtk_ui_manager_get_widget(gtkblist->ui, "/BList/AccountsMenu");
5799 accountmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu));
5801 /****************************** Notebook *************************************/
5802 gtkblist->notebook = gtk_notebook_new();
5803 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(gtkblist->notebook), FALSE);
5804 gtk_notebook_set_show_border(GTK_NOTEBOOK(gtkblist->notebook), FALSE);
5805 gtk_box_pack_start(GTK_BOX(gtkblist->main_vbox), gtkblist->notebook, TRUE, TRUE, 0);
5807 #if 0
5808 gtk_notebook_append_page(GTK_NOTEBOOK(gtkblist->notebook), kiosk_page(), NULL);
5809 #endif
5811 /* Translators: Please maintain the use of ⇦ or ⇨ to refer to menu hierarchy */
5812 text = g_strdup_printf(_("<span weight='bold' size='larger'>Welcome to %s!</span>\n\n"
5814 "You have no accounts enabled. Enable your IM accounts from the "
5815 "<b>Accounts</b> window at <b>Accounts⇨Manage Accounts</b>. Once you "
5816 "enable accounts, you'll be able to sign on, set your status, "
5817 "and talk to your friends."), PIDGIN_NAME);
5818 label = gtk_label_new(NULL);
5819 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
5820 gtk_label_set_yalign(GTK_LABEL(label), 0.2);
5821 gtk_label_set_markup(GTK_LABEL(label), text);
5822 g_free(text);
5823 gtk_notebook_append_page(GTK_NOTEBOOK(gtkblist->notebook),label, NULL);
5824 gtkblist->vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
5825 gtk_notebook_append_page(GTK_NOTEBOOK(gtkblist->notebook), gtkblist->vbox, NULL);
5826 gtk_widget_show_all(gtkblist->notebook);
5827 pidgin_blist_select_notebook_page(gtkblist);
5829 /****************************** Headline **********************************/
5831 gtkblist->headline = gtk_event_box_new();
5832 gtk_box_pack_start(GTK_BOX(gtkblist->vbox), gtkblist->headline,
5833 FALSE, FALSE, 0);
5834 infobar = gtk_info_bar_new();
5835 gtk_container_add(GTK_CONTAINER(gtkblist->headline), infobar);
5836 gtk_info_bar_set_default_response(GTK_INFO_BAR(infobar), GTK_RESPONSE_OK);
5837 gtk_info_bar_set_message_type(GTK_INFO_BAR(infobar), GTK_MESSAGE_INFO);
5839 content_area = gtk_info_bar_get_content_area(GTK_INFO_BAR(infobar));
5840 gtkblist->headline_image = gtk_image_new_from_pixbuf(NULL);
5841 gtk_widget_set_halign(gtkblist->headline_image, GTK_ALIGN_CENTER);
5842 gtk_widget_set_valign(gtkblist->headline_image, GTK_ALIGN_CENTER);
5843 gtkblist->headline_label = gtk_label_new(NULL);
5844 gtk_label_set_line_wrap(GTK_LABEL(gtkblist->headline_label), TRUE);
5845 gtk_box_pack_start(GTK_BOX(content_area), gtkblist->headline_image,
5846 FALSE, FALSE, 0);
5847 gtk_box_pack_start(GTK_BOX(content_area), gtkblist->headline_label,
5848 TRUE, TRUE, 0);
5850 close = gtk_image_new_from_stock(GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU);
5851 close = pidgin_create_small_button(close);
5852 gtk_widget_set_tooltip_text(close, _("Close"));
5853 gtk_info_bar_add_action_widget(GTK_INFO_BAR(infobar), close,
5854 GTK_RESPONSE_CLOSE);
5856 g_signal_connect(infobar, "response", G_CALLBACK(headline_response_cb),
5857 gtkblist);
5858 g_signal_connect(infobar, "close", G_CALLBACK(gtk_info_bar_response),
5859 GINT_TO_POINTER(GTK_RESPONSE_CLOSE));
5860 g_signal_connect(gtkblist->headline, "realize",
5861 G_CALLBACK(headline_realize_cb), NULL);
5862 g_signal_connect(gtkblist->headline, "button-press-event",
5863 G_CALLBACK(headline_press_cb), infobar);
5865 /****************************** GtkTreeView **********************************/
5866 gtkblist->treemodel = gtk_tree_store_new(BLIST_COLUMNS,
5867 GDK_TYPE_PIXBUF, /* Status icon */
5868 G_TYPE_BOOLEAN, /* Status icon visible */
5869 G_TYPE_STRING, /* Name */
5870 G_TYPE_STRING, /* Idle */
5871 G_TYPE_BOOLEAN, /* Idle visible */
5872 GDK_TYPE_PIXBUF, /* Buddy icon */
5873 G_TYPE_BOOLEAN, /* Buddy icon visible */
5874 G_TYPE_POINTER, /* Node */
5875 GDK_TYPE_RGBA, /* bgcolor */
5876 G_TYPE_BOOLEAN, /* Group expander */
5877 G_TYPE_BOOLEAN, /* Group expander visible */
5878 G_TYPE_BOOLEAN, /* Contact expander */
5879 G_TYPE_BOOLEAN, /* Contact expander visible */
5880 GDK_TYPE_PIXBUF, /* Emblem */
5881 G_TYPE_BOOLEAN, /* Emblem visible */
5882 GDK_TYPE_PIXBUF, /* Protocol icon */
5883 G_TYPE_BOOLEAN /* Protocol visible */
5886 gtkblist->treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(gtkblist->treemodel));
5888 gtk_widget_show(gtkblist->treeview);
5889 gtk_widget_set_name(gtkblist->treeview, "pidgin_blist_treeview");
5891 g_signal_connect(gtkblist->treeview,
5892 "style-updated",
5893 G_CALLBACK(treeview_style_set), list);
5894 /* Set up selection stuff */
5895 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist->treeview));
5896 g_signal_connect(G_OBJECT(selection), "changed", G_CALLBACK(pidgin_blist_selection_changed), NULL);
5898 /* Set up dnd */
5899 gtk_tree_view_enable_model_drag_source(GTK_TREE_VIEW(gtkblist->treeview),
5900 GDK_BUTTON1_MASK, ste, 3,
5901 GDK_ACTION_COPY);
5902 gtk_tree_view_enable_model_drag_dest(GTK_TREE_VIEW(gtkblist->treeview),
5903 dte, 5,
5904 GDK_ACTION_COPY | GDK_ACTION_MOVE);
5906 g_signal_connect(G_OBJECT(gtkblist->treeview), "drag-data-received", G_CALLBACK(pidgin_blist_drag_data_rcv_cb), NULL);
5907 g_signal_connect(G_OBJECT(gtkblist->treeview), "drag-data-get", G_CALLBACK(pidgin_blist_drag_data_get_cb), NULL);
5908 #ifdef _WIN32
5909 g_signal_connect(G_OBJECT(gtkblist->treeview), "drag-begin", G_CALLBACK(pidgin_blist_drag_begin), NULL);
5910 #endif
5911 g_signal_connect(G_OBJECT(gtkblist->treeview), "drag-motion", G_CALLBACK(pidgin_blist_drag_motion_cb), NULL);
5912 g_signal_connect(G_OBJECT(gtkblist->treeview), "motion-notify-event", G_CALLBACK(pidgin_blist_motion_cb), NULL);
5913 g_signal_connect(G_OBJECT(gtkblist->treeview), "leave-notify-event", G_CALLBACK(pidgin_blist_leave_cb), NULL);
5915 /* Tooltips */
5916 pidgin_tooltip_setup_for_treeview(gtkblist->treeview, NULL,
5917 pidgin_blist_create_tooltip,
5918 pidgin_blist_paint_tip);
5920 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(gtkblist->treeview), FALSE);
5922 /* expander columns */
5923 column = gtk_tree_view_column_new();
5924 gtk_tree_view_append_column(GTK_TREE_VIEW(gtkblist->treeview), column);
5925 gtk_tree_view_column_set_visible(column, FALSE);
5926 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(gtkblist->treeview), column);
5928 /* everything else column */
5929 gtkblist->text_column = gtk_tree_view_column_new ();
5930 gtk_tree_view_append_column(GTK_TREE_VIEW(gtkblist->treeview), gtkblist->text_column);
5931 pidgin_blist_build_layout(list);
5933 g_signal_connect(G_OBJECT(gtkblist->treeview), "row-activated",
5934 G_CALLBACK(gtk_blist_row_activated_cb), gtkblist);
5935 g_signal_connect(G_OBJECT(gtkblist->treeview), "row-expanded",
5936 G_CALLBACK(gtk_blist_row_expanded_cb), gtkblist);
5937 g_signal_connect(G_OBJECT(gtkblist->treeview), "row-collapsed",
5938 G_CALLBACK(gtk_blist_row_collapsed_cb), gtkblist);
5939 g_signal_connect(G_OBJECT(gtkblist->treeview), "button-press-event", G_CALLBACK(gtk_blist_button_press_cb), NULL);
5940 g_signal_connect(G_OBJECT(gtkblist->treeview), "key-press-event", G_CALLBACK(gtk_blist_key_press_cb), NULL);
5941 g_signal_connect(G_OBJECT(gtkblist->treeview), "popup-menu", G_CALLBACK(pidgin_blist_popup_menu_cb), NULL);
5943 /* Enable CTRL+F searching */
5944 gtk_tree_view_set_search_column(GTK_TREE_VIEW(gtkblist->treeview), NAME_COLUMN);
5945 gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(gtkblist->treeview),
5946 pidgin_blist_search_equal_func, NULL, NULL);
5948 gtk_box_pack_start(GTK_BOX(gtkblist->vbox),
5949 pidgin_make_scrollable(gtkblist->treeview, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC, GTK_SHADOW_NONE, -1, -1),
5950 TRUE, TRUE, 0);
5952 sep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
5953 gtk_box_pack_start(GTK_BOX(gtkblist->vbox), sep, FALSE, FALSE, 0);
5955 gtkblist->scrollbook = pidgin_scroll_book_new();
5956 gtk_box_pack_start(GTK_BOX(gtkblist->vbox), gtkblist->scrollbook, FALSE, FALSE, 0);
5958 priv->error_scrollbook = PIDGIN_SCROLL_BOOK(pidgin_scroll_book_new());
5959 gtk_box_pack_start(GTK_BOX(gtkblist->vbox),
5960 GTK_WIDGET(priv->error_scrollbook), FALSE, FALSE, 0);
5962 /* Add the statusbox */
5963 gtkblist->statusbox = pidgin_status_box_new();
5964 gtk_box_pack_start(GTK_BOX(gtkblist->vbox), gtkblist->statusbox, FALSE, TRUE, 0);
5965 gtk_widget_set_name(gtkblist->statusbox, "pidgin_blist_statusbox");
5966 gtk_widget_show(gtkblist->statusbox);
5968 /* set the Show Offline Buddies option. must be done
5969 * after the treeview or faceprint gets mad. -Robot101
5971 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(gtk_ui_manager_get_action(gtkblist->ui, "/BList/BuddiesMenu/ShowMenu/ShowOffline")),
5972 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_offline_buddies"));
5974 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(gtk_ui_manager_get_action(gtkblist->ui, "/BList/BuddiesMenu/ShowMenu/ShowEmptyGroups")),
5975 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_empty_groups"));
5977 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(gtk_ui_manager_get_action(gtkblist->ui, "/BList/ToolsMenu/MuteSounds")),
5978 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/sound/mute"));
5980 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(gtk_ui_manager_get_action(gtkblist->ui, "/BList/BuddiesMenu/ShowMenu/ShowBuddyDetails")),
5981 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons"));
5983 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(gtk_ui_manager_get_action(gtkblist->ui, "/BList/BuddiesMenu/ShowMenu/ShowIdleTimes")),
5984 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_idle_time"));
5986 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(gtk_ui_manager_get_action(gtkblist->ui, "/BList/BuddiesMenu/ShowMenu/ShowProtocolIcons")),
5987 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons"));
5989 if(purple_strequal(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/sound/method"), "none"))
5990 gtk_action_set_sensitive(gtk_ui_manager_get_action(gtkblist->ui, "/BList/ToolsMenu/MuteSounds"), FALSE);
5992 /* Update some dynamic things */
5993 update_menu_bar(gtkblist);
5994 pidgin_blist_update_plugin_actions();
5995 pidgin_blist_update_sort_methods();
5997 /* OK... let's show this bad boy. */
5998 pidgin_blist_refresh(list);
5999 pidgin_blist_restore_window_state();
6000 gtk_widget_show_all(GTK_WIDGET(gtkblist->vbox));
6001 gtk_widget_realize(GTK_WIDGET(gtkblist->window));
6002 purple_blist_set_visible(purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/list_visible"));
6004 /* start the refresh timer */
6005 gtkblist->refresh_timer = g_timeout_add_seconds(30, (GSourceFunc)pidgin_blist_refresh_timer, list);
6007 handle = pidgin_blist_get_handle();
6009 /* things that affect how buddies are displayed */
6010 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/show_buddy_icons",
6011 _prefs_change_redo_list, NULL);
6012 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/show_idle_time",
6013 _prefs_change_redo_list, NULL);
6014 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/show_empty_groups",
6015 _prefs_change_redo_list, NULL);
6016 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/show_offline_buddies",
6017 _prefs_change_redo_list, NULL);
6018 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/show_protocol_icons",
6019 _prefs_change_redo_list, NULL);
6021 /* sorting */
6022 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/sort_type",
6023 _prefs_change_sort_method, NULL);
6025 /* menus */
6026 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/sound/mute",
6027 pidgin_blist_mute_pref_cb, NULL);
6028 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/sound/method",
6029 pidgin_blist_sound_method_pref_cb, NULL);
6031 /* Setup some purple signal handlers. */
6033 handle = purple_accounts_get_handle();
6034 purple_signal_connect(handle, "account-enabled", gtkblist,
6035 PURPLE_CALLBACK(account_modified), gtkblist);
6036 purple_signal_connect(handle, "account-disabled", gtkblist,
6037 PURPLE_CALLBACK(account_modified), gtkblist);
6038 purple_signal_connect(handle, "account-removed", gtkblist,
6039 PURPLE_CALLBACK(account_modified), gtkblist);
6040 purple_signal_connect(handle, "account-status-changed", gtkblist,
6041 PURPLE_CALLBACK(account_status_changed),
6042 gtkblist);
6043 purple_signal_connect(handle, "account-error-changed", gtkblist,
6044 PURPLE_CALLBACK(update_account_error_state),
6045 gtkblist);
6046 purple_signal_connect(handle, "account-actions-changed", gtkblist,
6047 PURPLE_CALLBACK(account_actions_changed), NULL);
6049 handle = pidgin_accounts_get_handle();
6050 purple_signal_connect(handle, "account-modified", gtkblist,
6051 PURPLE_CALLBACK(account_modified), gtkblist);
6053 handle = purple_connections_get_handle();
6054 purple_signal_connect(handle, "signed-on", gtkblist,
6055 PURPLE_CALLBACK(sign_on_off_cb), list);
6056 purple_signal_connect(handle, "signed-off", gtkblist,
6057 PURPLE_CALLBACK(sign_on_off_cb), list);
6059 handle = purple_plugins_get_handle();
6060 purple_signal_connect(handle, "plugin-load", gtkblist,
6061 PURPLE_CALLBACK(plugin_changed_cb), NULL);
6062 purple_signal_connect(handle, "plugin-unload", gtkblist,
6063 PURPLE_CALLBACK(plugin_changed_cb), NULL);
6065 handle = purple_conversations_get_handle();
6066 purple_signal_connect(handle, "conversation-updated", gtkblist,
6067 PURPLE_CALLBACK(conversation_updated_cb),
6068 gtkblist);
6069 purple_signal_connect(handle, "deleting-conversation", gtkblist,
6070 PURPLE_CALLBACK(conversation_deleting_cb),
6071 gtkblist);
6072 purple_signal_connect(handle, "conversation-created", gtkblist,
6073 PURPLE_CALLBACK(conversation_created_cb),
6074 gtkblist);
6075 purple_signal_connect(handle, "chat-joined", gtkblist,
6076 PURPLE_CALLBACK(conversation_created_cb),
6077 gtkblist);
6079 gtk_widget_hide(gtkblist->headline);
6081 show_initial_account_errors(gtkblist);
6083 /* emit our created signal */
6084 handle = pidgin_blist_get_handle();
6085 purple_signal_emit(handle, "gtkblist-created", list);
6088 static void redo_buddy_list(PurpleBuddyList *list, gboolean remove, gboolean rerender)
6090 PurpleBlistNode *node;
6092 gtkblist = PIDGIN_BUDDY_LIST(list);
6093 if(!gtkblist || !gtkblist->treeview)
6094 return;
6096 node = purple_blist_get_root(list);
6098 while (node)
6100 /* This is only needed when we're reverting to a non-GTK+ sorted
6101 * status. We shouldn't need to remove otherwise.
6103 if (remove && !PURPLE_IS_GROUP(node))
6104 pidgin_blist_hide_node(list, node, FALSE);
6106 if (PURPLE_IS_BUDDY(node))
6107 pidgin_blist_update_buddy(list, node, rerender);
6108 else if (PURPLE_IS_CHAT(node))
6109 pidgin_blist_update(list, node);
6110 else if (PURPLE_IS_GROUP(node))
6111 pidgin_blist_update(list, node);
6112 node = purple_blist_node_next(node, FALSE);
6117 void pidgin_blist_refresh(PurpleBuddyList *list)
6119 redo_buddy_list(list, FALSE, TRUE);
6122 void
6123 pidgin_blist_update_refresh_timeout()
6125 PurpleBuddyList *blist;
6126 PidginBuddyList *gtkblist;
6128 blist = purple_blist_get_default();
6129 gtkblist = PIDGIN_BUDDY_LIST(blist);
6131 gtkblist->refresh_timer = g_timeout_add_seconds(30,(GSourceFunc)pidgin_blist_refresh_timer, blist);
6134 static gboolean get_iter_from_node(PurpleBlistNode *node, GtkTreeIter *iter) {
6135 PidginBlistNode *gtknode = purple_blist_node_get_ui_data(node);
6136 GtkTreePath *path;
6138 if (!gtknode) {
6139 return FALSE;
6142 if (!gtkblist) {
6143 purple_debug_error("gtkblist", "get_iter_from_node was called, but we don't seem to have a blist\n");
6144 return FALSE;
6147 if (!gtknode->row)
6148 return FALSE;
6151 if ((path = gtk_tree_row_reference_get_path(gtknode->row)) == NULL)
6152 return FALSE;
6154 if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), iter, path)) {
6155 gtk_tree_path_free(path);
6156 return FALSE;
6158 gtk_tree_path_free(path);
6159 return TRUE;
6162 static void pidgin_blist_remove(PurpleBuddyList *list, PurpleBlistNode *node)
6164 PidginBlistNode *gtknode = purple_blist_node_get_ui_data(node);
6166 purple_request_close_with_handle(node);
6168 pidgin_blist_hide_node(list, node, TRUE);
6170 if(node->parent)
6171 pidgin_blist_update(list, node->parent);
6173 /* There's something I don't understand here - Ethan */
6174 /* Ethan said that back in 2003, but this g_free has been left commented
6175 * out ever since. I can't find any reason at all why this is bad and
6176 * valgrind found several reasons why it's good. If this causes problems
6177 * comment it out again. Stu */
6178 /* Of course it still causes problems - this breaks dragging buddies into
6179 * contacts, the dragged buddy mysteriously 'disappears'. Stu. */
6180 /* I think it's fixed now. Stu. */
6182 if(gtknode) {
6183 if(gtknode->recent_signonoff_timer > 0)
6184 g_source_remove(gtknode->recent_signonoff_timer);
6186 purple_signals_disconnect_by_handle(gtknode);
6187 g_free(gtknode);
6188 purple_blist_node_set_ui_data(node, NULL);
6192 static gboolean do_selection_changed(PurpleBlistNode *new_selection)
6194 PurpleBlistNode *old_selection = NULL;
6196 /* test for gtkblist because crazy timeout means we can be called after the blist is gone */
6197 if (gtkblist && new_selection != gtkblist->selected_node) {
6198 old_selection = gtkblist->selected_node;
6199 gtkblist->selected_node = new_selection;
6200 if(new_selection)
6201 pidgin_blist_update(NULL, new_selection);
6202 if(old_selection)
6203 pidgin_blist_update(NULL, old_selection);
6206 return FALSE;
6209 static void pidgin_blist_selection_changed(GtkTreeSelection *selection, gpointer data)
6211 PurpleBlistNode *new_selection = NULL;
6212 GtkTreeIter iter;
6214 if(gtk_tree_selection_get_selected(selection, NULL, &iter)){
6215 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter,
6216 NODE_COLUMN, &new_selection, -1);
6219 /* we set this up as a timeout, otherwise the blist flickers ...
6220 * but we don't do it for groups, because it causes total bizarness -
6221 * the previously selected buddy node might rendered at half height.
6223 if ((new_selection != NULL) && PURPLE_IS_GROUP(new_selection)) {
6224 do_selection_changed(new_selection);
6225 } else {
6226 g_timeout_add(0, (GSourceFunc)do_selection_changed, new_selection);
6230 static gboolean insert_node(PurpleBuddyList *list, PurpleBlistNode *node, GtkTreeIter *iter)
6232 GtkTreeIter parent_iter = {0, NULL, NULL, NULL}, cur, *curptr = NULL;
6233 PidginBlistNode *gtknode = purple_blist_node_get_ui_data(node);
6234 GtkTreePath *newpath;
6236 if(!iter)
6237 return FALSE;
6239 /* XXX: it's not necessary, but let's silence a warning*/
6240 memset(&parent_iter, 0, sizeof(parent_iter));
6242 if(node->parent && !get_iter_from_node(node->parent, &parent_iter))
6243 return FALSE;
6245 if(get_iter_from_node(node, &cur))
6246 curptr = &cur;
6248 if(PURPLE_IS_CONTACT(node) || PURPLE_IS_CHAT(node)) {
6249 current_sort_method->func(node, list, parent_iter, curptr, iter);
6250 } else {
6251 sort_method_none(node, list, parent_iter, curptr, iter);
6254 if(gtknode != NULL) {
6255 gtk_tree_row_reference_free(gtknode->row);
6256 } else {
6257 pidgin_blist_new_node(list, node);
6258 gtknode = purple_blist_node_get_ui_data(node);
6261 newpath = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel),
6262 iter);
6263 gtknode->row =
6264 gtk_tree_row_reference_new(GTK_TREE_MODEL(gtkblist->treemodel),
6265 newpath);
6267 gtk_tree_path_free(newpath);
6269 if (!editing_blist)
6270 gtk_tree_store_set(gtkblist->treemodel, iter,
6271 NODE_COLUMN, node,
6272 -1);
6274 if(node->parent) {
6275 GtkTreePath *expand = NULL;
6276 PidginBlistNode *gtkparentnode = purple_blist_node_get_ui_data(node->parent);
6278 if(PURPLE_IS_GROUP(node->parent)) {
6279 if(!purple_blist_node_get_bool(node->parent, "collapsed"))
6280 expand = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &parent_iter);
6281 } else if(PURPLE_IS_CONTACT(node->parent) &&
6282 gtkparentnode->contact_expanded) {
6283 expand = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &parent_iter);
6285 if(expand) {
6286 gtk_tree_view_expand_row(GTK_TREE_VIEW(gtkblist->treeview), expand, FALSE);
6287 gtk_tree_path_free(expand);
6291 return TRUE;
6294 static gboolean pidgin_blist_group_has_show_offline_buddy(PurpleGroup *group)
6296 PurpleBlistNode *gnode, *cnode, *bnode;
6298 gnode = PURPLE_BLIST_NODE(group);
6299 for(cnode = gnode->child; cnode; cnode = cnode->next) {
6300 if(PURPLE_IS_CONTACT(cnode)) {
6301 for(bnode = cnode->child; bnode; bnode = bnode->next) {
6302 PurpleBuddy *buddy = (PurpleBuddy *)bnode;
6303 if (purple_account_is_connected(purple_buddy_get_account(buddy)) &&
6304 purple_blist_node_get_bool(bnode, "show_offline"))
6305 return TRUE;
6309 return FALSE;
6312 /* This version of pidgin_blist_update_group can take the original buddy or a
6313 * group, but has much better algorithmic performance with a pre-known buddy.
6315 static void pidgin_blist_update_group(PurpleBuddyList *list,
6316 PurpleBlistNode *node)
6318 gint count;
6319 PurpleGroup *group;
6320 PurpleBlistNode* gnode;
6321 gboolean show = FALSE, show_offline = FALSE;
6323 g_return_if_fail(node != NULL);
6325 if (editing_blist)
6326 return;
6328 if (PURPLE_IS_GROUP(node))
6329 gnode = node;
6330 else if (PURPLE_IS_BUDDY(node))
6331 gnode = node->parent->parent;
6332 else if (PURPLE_IS_CONTACT(node) || PURPLE_IS_CHAT(node))
6333 gnode = node->parent;
6334 else
6335 return;
6337 group = (PurpleGroup*)gnode;
6339 show_offline = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_offline_buddies");
6341 if(show_offline)
6342 count = purple_counting_node_get_current_size(PURPLE_COUNTING_NODE(group));
6343 else
6344 count = purple_counting_node_get_online_count(PURPLE_COUNTING_NODE(group));
6346 if (count > 0 || purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_empty_groups"))
6347 show = TRUE;
6348 else if (PURPLE_IS_BUDDY(node) && buddy_is_displayable((PurpleBuddy*)node)) { /* Or chat? */
6349 show = TRUE;
6350 } else if (!show_offline) {
6351 show = pidgin_blist_group_has_show_offline_buddy(group);
6354 if (show) {
6355 gchar *title;
6356 gboolean biglist;
6357 GtkTreeIter iter;
6358 GtkTreePath *path;
6359 gboolean expanded;
6360 GdkRGBA *bgcolor = NULL;
6361 GdkPixbuf *avatar = NULL;
6362 PidginBlistTheme *theme = NULL;
6364 if(!insert_node(list, gnode, &iter))
6365 return;
6367 if ((theme = pidgin_blist_get_theme()) == NULL)
6368 bgcolor = NULL;
6369 else if (purple_blist_node_get_bool(gnode, "collapsed") || count <= 0)
6370 bgcolor = pidgin_blist_theme_get_collapsed_background_color(theme);
6371 else
6372 bgcolor = pidgin_blist_theme_get_expanded_background_color(theme);
6374 path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &iter);
6375 expanded = gtk_tree_view_row_expanded(GTK_TREE_VIEW(gtkblist->treeview), path);
6376 gtk_tree_path_free(path);
6378 title = pidgin_get_group_title(gnode, expanded);
6379 biglist = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons");
6381 if (biglist) {
6382 avatar = pidgin_blist_get_buddy_icon(gnode, TRUE, TRUE);
6385 gtk_tree_store_set(gtkblist->treemodel, &iter,
6386 STATUS_ICON_VISIBLE_COLUMN, FALSE,
6387 STATUS_ICON_COLUMN, NULL,
6388 NAME_COLUMN, title,
6389 NODE_COLUMN, gnode,
6390 BGCOLOR_COLUMN, bgcolor,
6391 GROUP_EXPANDER_COLUMN, TRUE,
6392 GROUP_EXPANDER_VISIBLE_COLUMN, TRUE,
6393 CONTACT_EXPANDER_VISIBLE_COLUMN, FALSE,
6394 BUDDY_ICON_COLUMN, avatar,
6395 BUDDY_ICON_VISIBLE_COLUMN, biglist,
6396 IDLE_VISIBLE_COLUMN, FALSE,
6397 EMBLEM_VISIBLE_COLUMN, FALSE,
6398 -1);
6399 g_free(title);
6400 } else {
6401 pidgin_blist_hide_node(list, gnode, TRUE);
6405 static char *pidgin_get_group_title(PurpleBlistNode *gnode, gboolean expanded)
6407 PurpleGroup *group;
6408 gboolean selected;
6409 char group_count[12] = "";
6410 char *mark, *esc;
6411 PurpleBlistNode *selected_node = NULL;
6412 GtkTreeIter iter;
6413 PidginThemeFont *pair;
6414 gchar const *text_color, *text_font;
6415 PidginBlistTheme *theme;
6417 group = (PurpleGroup*)gnode;
6419 if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist->treeview)), NULL, &iter)) {
6420 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter,
6421 NODE_COLUMN, &selected_node, -1);
6423 selected = (gnode == selected_node);
6425 if (!expanded) {
6426 g_snprintf(group_count, sizeof(group_count), "%d/%d",
6427 purple_counting_node_get_online_count(PURPLE_COUNTING_NODE(group)),
6428 purple_counting_node_get_current_size(PURPLE_COUNTING_NODE(group)));
6431 theme = pidgin_blist_get_theme();
6432 if (theme == NULL)
6433 pair = NULL;
6434 else if (expanded)
6435 pair = pidgin_blist_theme_get_expanded_text_info(theme);
6436 else
6437 pair = pidgin_blist_theme_get_collapsed_text_info(theme);
6440 text_color = selected ? NULL : theme_font_get_color_default(pair, NULL);
6441 text_font = theme_font_get_face_default(pair, "");
6443 esc = g_markup_escape_text(purple_group_get_name(group), -1);
6444 if (text_color) {
6445 mark = g_strdup_printf("<span foreground='%s' font_desc='%s'><b>%s</b>%s%s%s</span>",
6446 text_color, text_font,
6447 esc ? esc : "",
6448 !expanded ? " <span weight='light'>(</span>" : "",
6449 group_count,
6450 !expanded ? "<span weight='light'>)</span>" : "");
6451 } else {
6452 mark = g_strdup_printf("<span font_desc='%s'><b>%s</b>%s%s%s</span>",
6453 text_font, esc ? esc : "",
6454 !expanded ? " <span weight='light'>(</span>" : "",
6455 group_count,
6456 !expanded ? "<span weight='light'>)</span>" : "");
6459 g_free(esc);
6460 return mark;
6463 static void buddy_node(PurpleBuddy *buddy, GtkTreeIter *iter, PurpleBlistNode *node)
6465 PurplePresence *presence = purple_buddy_get_presence(buddy);
6466 GdkPixbuf *status, *avatar, *emblem, *protocol_icon;
6467 GdkRGBA *color = NULL;
6468 char *mark;
6469 char *idle = NULL;
6470 gboolean expanded = ((PidginBlistNode *)purple_blist_node_get_ui_data(node->parent))->contact_expanded;
6471 gboolean selected = (gtkblist->selected_node == node);
6472 gboolean biglist = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons");
6473 PidginBlistTheme *theme;
6475 if (editing_blist)
6476 return;
6478 status = pidgin_blist_get_status_icon(PURPLE_BLIST_NODE(buddy),
6479 biglist ? PIDGIN_STATUS_ICON_LARGE : PIDGIN_STATUS_ICON_SMALL);
6481 /* Speed it up if we don't want buddy icons. */
6482 if(biglist)
6483 avatar = pidgin_blist_get_buddy_icon(PURPLE_BLIST_NODE(buddy), TRUE, TRUE);
6484 else
6485 avatar = NULL;
6487 if (!avatar) {
6488 g_object_ref(G_OBJECT(gtkblist->empty_avatar));
6489 avatar = gtkblist->empty_avatar;
6490 } else if ((!PURPLE_BUDDY_IS_ONLINE(buddy) || purple_presence_is_idle(presence))) {
6491 do_alphashift(avatar, 77);
6494 emblem = pidgin_blist_get_emblem(PURPLE_BLIST_NODE(buddy));
6495 mark = pidgin_blist_get_name_markup(buddy, selected, TRUE);
6497 theme = pidgin_blist_get_theme();
6499 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_idle_time") &&
6500 purple_presence_is_idle(presence) && !biglist)
6502 time_t idle_secs = purple_presence_get_idle_time(presence);
6504 if (idle_secs > 0)
6506 PidginThemeFont *pair = NULL;
6507 const gchar *textcolor;
6508 time_t t;
6509 int ihrs, imin;
6510 time(&t);
6512 ihrs = (t - idle_secs) / 3600;
6513 imin = ((t - idle_secs) / 60) % 60;
6515 if (selected)
6516 textcolor = NULL;
6517 else if (theme != NULL && (pair = pidgin_blist_theme_get_idle_text_info(theme)) != NULL)
6518 textcolor = pidgin_theme_font_get_color_describe(pair);
6519 else
6520 /* If no theme them default to making idle buddy names grey */
6521 textcolor = pidgin_style_is_dark(NULL) ? "light slate grey" : "dim grey";
6523 if (textcolor) {
6524 idle = g_strdup_printf("<span color='%s' font_desc='%s'>%d:%02d</span>",
6525 textcolor, theme_font_get_face_default(pair, ""),
6526 ihrs, imin);
6527 } else {
6528 idle = g_strdup_printf("<span font_desc='%s'>%d:%02d</span>",
6529 theme_font_get_face_default(pair, ""),
6530 ihrs, imin);
6535 protocol_icon = pidgin_create_protocol_icon(purple_buddy_get_account(buddy), PIDGIN_PROTOCOL_ICON_SMALL);
6537 if (theme != NULL)
6538 color = pidgin_blist_theme_get_contact_color(theme);
6540 gtk_tree_store_set(gtkblist->treemodel, iter,
6541 STATUS_ICON_COLUMN, status,
6542 STATUS_ICON_VISIBLE_COLUMN, TRUE,
6543 NAME_COLUMN, mark,
6544 IDLE_COLUMN, idle,
6545 IDLE_VISIBLE_COLUMN, !biglist && idle,
6546 BUDDY_ICON_COLUMN, avatar,
6547 BUDDY_ICON_VISIBLE_COLUMN, biglist,
6548 EMBLEM_COLUMN, emblem,
6549 EMBLEM_VISIBLE_COLUMN, (emblem != NULL),
6550 PROTOCOL_ICON_COLUMN, protocol_icon,
6551 PROTOCOL_ICON_VISIBLE_COLUMN, purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons"),
6552 BGCOLOR_COLUMN, color,
6553 CONTACT_EXPANDER_COLUMN, NULL,
6554 CONTACT_EXPANDER_VISIBLE_COLUMN, expanded,
6555 GROUP_EXPANDER_VISIBLE_COLUMN, FALSE,
6556 -1);
6558 g_free(mark);
6559 g_free(idle);
6560 if(emblem)
6561 g_object_unref(emblem);
6562 if(status)
6563 g_object_unref(status);
6564 if(avatar)
6565 g_object_unref(avatar);
6566 if(protocol_icon)
6567 g_object_unref(protocol_icon);
6570 /* This is a variation on the original gtk_blist_update_contact. Here we
6571 can know in advance which buddy has changed so we can just update that */
6572 static void pidgin_blist_update_contact(PurpleBuddyList *list, PurpleBlistNode *node)
6574 PurpleBlistNode *cnode;
6575 PurpleContact *contact;
6576 PurpleBuddy *buddy;
6577 gboolean biglist = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons");
6578 PidginBlistNode *gtknode;
6580 if (editing_blist)
6581 return;
6583 if (PURPLE_IS_BUDDY(node))
6584 cnode = node->parent;
6585 else
6586 cnode = node;
6588 g_return_if_fail(PURPLE_IS_CONTACT(cnode));
6590 /* First things first, update the group */
6591 if (PURPLE_IS_BUDDY(node))
6592 pidgin_blist_update_group(list, node);
6593 else
6594 pidgin_blist_update_group(list, cnode->parent);
6596 contact = (PurpleContact*)cnode;
6597 buddy = purple_contact_get_priority_buddy(contact);
6599 if (buddy_is_displayable(buddy))
6601 GtkTreeIter iter;
6603 if(!insert_node(list, cnode, &iter))
6604 return;
6606 gtknode = purple_blist_node_get_ui_data(cnode);
6608 if(gtknode->contact_expanded) {
6609 GdkPixbuf *status;
6610 gchar *mark, *tmp;
6611 const gchar *fg_color, *font;
6612 GdkRGBA *color = NULL;
6613 PidginBlistTheme *theme;
6614 PidginThemeFont *pair;
6615 gboolean selected = (gtkblist->selected_node == cnode);
6617 mark = g_markup_escape_text(purple_contact_get_alias(contact), -1);
6619 theme = pidgin_blist_get_theme();
6620 if (theme == NULL)
6621 pair = NULL;
6622 else {
6623 pair = pidgin_blist_theme_get_contact_text_info(theme);
6624 color = pidgin_blist_theme_get_contact_color(theme);
6627 font = theme_font_get_face_default(pair, "");
6628 fg_color = selected ? NULL : theme_font_get_color_default(pair, NULL);
6630 if (fg_color) {
6631 tmp = g_strdup_printf("<span font_desc='%s' color='%s'>%s</span>",
6632 font, fg_color, mark);
6633 } else {
6634 tmp = g_strdup_printf("<span font_desc='%s'>%s</span>", font,
6635 mark);
6637 g_free(mark);
6638 mark = tmp;
6640 status = pidgin_blist_get_status_icon(cnode,
6641 biglist? PIDGIN_STATUS_ICON_LARGE : PIDGIN_STATUS_ICON_SMALL);
6643 gtk_tree_store_set(gtkblist->treemodel, &iter,
6644 STATUS_ICON_COLUMN, status,
6645 STATUS_ICON_VISIBLE_COLUMN, TRUE,
6646 NAME_COLUMN, mark,
6647 IDLE_COLUMN, NULL,
6648 IDLE_VISIBLE_COLUMN, FALSE,
6649 BGCOLOR_COLUMN, color,
6650 BUDDY_ICON_COLUMN, NULL,
6651 CONTACT_EXPANDER_COLUMN, TRUE,
6652 CONTACT_EXPANDER_VISIBLE_COLUMN, TRUE,
6653 GROUP_EXPANDER_VISIBLE_COLUMN, FALSE,
6654 -1);
6655 g_free(mark);
6656 if(status)
6657 g_object_unref(status);
6658 } else {
6659 buddy_node(buddy, &iter, cnode);
6661 } else {
6662 pidgin_blist_hide_node(list, cnode, TRUE);
6668 static void pidgin_blist_update_buddy(PurpleBuddyList *list, PurpleBlistNode *node, gboolean status_change)
6670 PurpleBuddy *buddy;
6671 PidginBlistNode *gtkparentnode;
6673 g_return_if_fail(PURPLE_IS_BUDDY(node));
6675 if (node->parent == NULL)
6676 return;
6678 buddy = (PurpleBuddy*)node;
6680 /* First things first, update the contact */
6681 pidgin_blist_update_contact(list, node);
6683 gtkparentnode = purple_blist_node_get_ui_data(node->parent);
6685 if (gtkparentnode->contact_expanded && buddy_is_displayable(buddy))
6687 GtkTreeIter iter;
6689 if (!insert_node(list, node, &iter))
6690 return;
6692 buddy_node(buddy, &iter, node);
6694 } else {
6695 pidgin_blist_hide_node(list, node, TRUE);
6700 static void pidgin_blist_update_chat(PurpleBuddyList *list, PurpleBlistNode *node)
6702 PurpleChat *chat;
6704 g_return_if_fail(PURPLE_IS_CHAT(node));
6706 if (editing_blist)
6707 return;
6709 /* First things first, update the group */
6710 pidgin_blist_update_group(list, node->parent);
6712 chat = (PurpleChat*)node;
6714 if(purple_account_is_connected(purple_chat_get_account(chat))) {
6715 GtkTreeIter iter;
6716 GdkPixbuf *status, *avatar, *emblem, *protocol_icon;
6717 const gchar *color, *font;
6718 gchar *mark, *tmp;
6719 gboolean showicons = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons");
6720 gboolean biglist = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons");
6721 PidginBlistNode *ui;
6722 PurpleConversation *conv;
6723 gboolean hidden = FALSE;
6724 GdkRGBA *bgcolor = NULL;
6725 PidginThemeFont *pair;
6726 PidginBlistTheme *theme;
6727 gboolean selected = (gtkblist->selected_node == node);
6728 gboolean nick_said = FALSE;
6730 if (!insert_node(list, node, &iter))
6731 return;
6733 ui = purple_blist_node_get_ui_data(node);
6734 conv = ui->conv.conv;
6735 if (conv && pidgin_conv_is_hidden(PIDGIN_CONVERSATION(conv))) {
6736 hidden = (ui->conv.flags & PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE);
6737 nick_said = (ui->conv.flags & PIDGIN_BLIST_CHAT_HAS_PENDING_MESSAGE_WITH_NICK);
6740 status = pidgin_blist_get_status_icon(node,
6741 biglist ? PIDGIN_STATUS_ICON_LARGE : PIDGIN_STATUS_ICON_SMALL);
6742 emblem = pidgin_blist_get_emblem(node);
6744 /* Speed it up if we don't want buddy icons. */
6745 if(showicons)
6746 avatar = pidgin_blist_get_buddy_icon(node, TRUE, FALSE);
6747 else
6748 avatar = NULL;
6750 mark = g_markup_escape_text(purple_chat_get_name(chat), -1);
6752 theme = pidgin_blist_get_theme();
6754 if (theme == NULL)
6755 pair = NULL;
6756 else if (nick_said)
6757 pair = pidgin_blist_theme_get_unread_message_nick_said_text_info(theme);
6758 else if (hidden)
6759 pair = pidgin_blist_theme_get_unread_message_text_info(theme);
6760 else pair = pidgin_blist_theme_get_online_text_info(theme);
6763 font = theme_font_get_face_default(pair, "");
6764 if (selected || !(color = theme_font_get_color_default(pair, NULL)))
6765 /* nick_said color is the same as gtkconv:tab-label-attention */
6766 color = (nick_said ? "#006aff" : NULL);
6768 if (color) {
6769 tmp = g_strdup_printf("<span font_desc='%s' color='%s' weight='%s'>%s</span>",
6770 font, color, hidden ? "bold" : "normal", mark);
6771 } else {
6772 tmp = g_strdup_printf("<span font_desc='%s' weight='%s'>%s</span>",
6773 font, hidden ? "bold" : "normal", mark);
6775 g_free(mark);
6776 mark = tmp;
6778 protocol_icon = pidgin_create_protocol_icon(purple_chat_get_account(chat), PIDGIN_PROTOCOL_ICON_SMALL);
6780 if (theme != NULL)
6781 bgcolor = pidgin_blist_theme_get_contact_color(theme);
6783 gtk_tree_store_set(gtkblist->treemodel, &iter,
6784 STATUS_ICON_COLUMN, status,
6785 STATUS_ICON_VISIBLE_COLUMN, TRUE,
6786 BUDDY_ICON_COLUMN, avatar ? avatar : gtkblist->empty_avatar,
6787 BUDDY_ICON_VISIBLE_COLUMN, showicons,
6788 EMBLEM_COLUMN, emblem,
6789 EMBLEM_VISIBLE_COLUMN, emblem != NULL,
6790 PROTOCOL_ICON_COLUMN, protocol_icon,
6791 PROTOCOL_ICON_VISIBLE_COLUMN, purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons"),
6792 NAME_COLUMN, mark,
6793 BGCOLOR_COLUMN, bgcolor,
6794 GROUP_EXPANDER_VISIBLE_COLUMN, FALSE,
6795 -1);
6797 g_free(mark);
6798 if(emblem)
6799 g_object_unref(emblem);
6800 if(status)
6801 g_object_unref(status);
6802 if(avatar)
6803 g_object_unref(avatar);
6804 if(protocol_icon)
6805 g_object_unref(protocol_icon);
6807 } else {
6808 pidgin_blist_hide_node(list, node, TRUE);
6812 static void pidgin_blist_update(PurpleBuddyList *list, PurpleBlistNode *node)
6814 if (list)
6815 gtkblist = PIDGIN_BUDDY_LIST(list);
6816 if(!gtkblist || !gtkblist->treeview || !node)
6817 return;
6819 if (purple_blist_node_get_ui_data(node) == NULL)
6820 pidgin_blist_new_node(list, node);
6822 if (PURPLE_IS_GROUP(node))
6823 pidgin_blist_update_group(list, node);
6824 else if (PURPLE_IS_CONTACT(node))
6825 pidgin_blist_update_contact(list, node);
6826 else if (PURPLE_IS_BUDDY(node))
6827 pidgin_blist_update_buddy(list, node, TRUE);
6828 else if (PURPLE_IS_CHAT(node))
6829 pidgin_blist_update_chat(list, node);
6832 static void pidgin_blist_set_visible(PurpleBuddyList *list, gboolean show)
6834 if (!(gtkblist && gtkblist->window))
6835 return;
6837 if (show) {
6838 gtk_widget_show(gtkblist->window);
6839 } else {
6840 if(visibility_manager_count) {
6841 gtk_widget_hide(gtkblist->window);
6842 } else {
6843 if (!gtk_widget_get_visible(gtkblist->window))
6844 gtk_widget_show(gtkblist->window);
6845 gtk_window_iconify(GTK_WINDOW(gtkblist->window));
6850 static GList *
6851 groups_tree(void)
6853 static GList *list = NULL;
6854 PurpleGroup *g;
6855 PurpleBlistNode *gnode;
6857 g_list_free(list);
6858 list = NULL;
6860 gnode = purple_blist_get_default_root();
6861 if (gnode == NULL) {
6862 list = g_list_append(list,
6863 (gpointer)PURPLE_BLIST_DEFAULT_GROUP_NAME);
6864 } else {
6865 for (; gnode != NULL; gnode = gnode->next) {
6866 if (PURPLE_IS_GROUP(gnode))
6868 g = (PurpleGroup *)gnode;
6869 list = g_list_append(list, (char *) purple_group_get_name(g));
6874 return list;
6877 static void
6878 add_buddy_select_account_cb(GObject *w, PidginAddBuddyData *data)
6880 PurpleAccount *account =
6881 pidgin_account_chooser_get_selected(GTK_WIDGET(w));
6882 PurpleConnection *pc = NULL;
6883 PurpleProtocol *protocol = NULL;
6884 gboolean invite_enabled = TRUE;
6886 /* Save our account */
6887 data->rq_data.account = account;
6889 if (account)
6890 pc = purple_account_get_connection(account);
6891 if (pc)
6892 protocol = purple_connection_get_protocol(pc);
6893 if (protocol && !(purple_protocol_get_options(protocol) & OPT_PROTO_INVITE_MESSAGE))
6894 invite_enabled = FALSE;
6896 gtk_widget_set_sensitive(data->entry_for_invite, invite_enabled);
6897 set_sensitive_if_input_buddy_cb(data->entry, data);
6900 static void
6901 destroy_add_buddy_dialog_cb(GtkWidget *win, PidginAddBuddyData *data)
6903 g_free(data);
6906 static void
6907 add_buddy_cb(GtkWidget *w, int resp, PidginAddBuddyData *data)
6909 const char *grp, *who, *whoalias, *invite;
6910 PurpleAccount *account;
6911 PurpleGroup *g;
6912 PurpleBuddy *b;
6913 PurpleIMConversation *im;
6914 PurpleBuddyIcon *icon;
6916 if (resp == GTK_RESPONSE_OK)
6918 who = gtk_entry_get_text(GTK_ENTRY(data->entry));
6919 grp = pidgin_text_combo_box_entry_get_text(data->combo);
6920 whoalias = gtk_entry_get_text(GTK_ENTRY(data->entry_for_alias));
6921 if (*whoalias == '\0')
6922 whoalias = NULL;
6923 invite = gtk_entry_get_text(GTK_ENTRY(data->entry_for_invite));
6924 if (*invite == '\0')
6925 invite = NULL;
6927 account = data->rq_data.account;
6929 g = NULL;
6930 if ((grp != NULL) && (*grp != '\0'))
6932 if ((g = purple_blist_find_group(grp)) == NULL)
6934 g = purple_group_new(grp);
6935 purple_blist_add_group(g, NULL);
6938 b = purple_blist_find_buddy_in_group(account, who, g);
6940 else if ((b = purple_blist_find_buddy(account, who)) != NULL)
6942 g = purple_buddy_get_group(b);
6945 if (b == NULL)
6947 b = purple_buddy_new(account, who, whoalias);
6948 purple_blist_add_buddy(b, NULL, g, NULL);
6951 purple_account_add_buddy(account, b, invite);
6953 /* Offer to merge people with the same alias. */
6954 if (whoalias != NULL && g != NULL)
6955 gtk_blist_auto_personize(PURPLE_BLIST_NODE(g), whoalias);
6958 * XXX
6959 * It really seems like it would be better if the call to
6960 * purple_account_add_buddy() and purple_conversation_update() were done in
6961 * blist.c, possibly in the purple_blist_add_buddy() function. Maybe
6962 * purple_account_add_buddy() should be renamed to
6963 * purple_blist_add_new_buddy() or something, and have it call
6964 * purple_blist_add_buddy() after it creates it. --Mark
6966 * No that's not good. blist.c should only deal with adding nodes to the
6967 * local list. We need a new, non-gtk file that calls both
6968 * purple_account_add_buddy and purple_blist_add_buddy().
6969 * Or something. --Mark
6972 im = purple_conversations_find_im_with_account(who, data->rq_data.account);
6973 if (im != NULL) {
6974 icon = purple_im_conversation_get_icon(im);
6975 if (icon != NULL)
6976 purple_buddy_icon_update(icon);
6980 gtk_widget_destroy(data->rq_data.window);
6983 static void
6984 pidgin_blist_request_add_buddy(PurpleBuddyList *list, PurpleAccount *account,
6985 const char *username, const char *group,
6986 const char *alias)
6988 PidginAddBuddyData *data = g_new0(PidginAddBuddyData, 1);
6989 PidginBlistRequestData *blist_req_data = &data->rq_data;
6991 if (account == NULL)
6992 account = purple_connection_get_account(purple_connections_get_all()->data);
6994 make_blist_request_dialog(blist_req_data,
6995 account,
6996 _("Add Buddy"), "add_buddy",
6997 _("Add a buddy.\n"),
6998 G_CALLBACK(add_buddy_select_account_cb), add_buddy_account_filter_func,
6999 G_CALLBACK(add_buddy_cb));
7000 gtk_dialog_add_buttons(GTK_DIALOG(data->rq_data.window),
7001 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
7002 GTK_STOCK_ADD, GTK_RESPONSE_OK,
7003 NULL);
7004 gtk_dialog_set_default_response(GTK_DIALOG(data->rq_data.window),
7005 GTK_RESPONSE_OK);
7007 g_signal_connect(G_OBJECT(data->rq_data.window), "destroy",
7008 G_CALLBACK(destroy_add_buddy_dialog_cb), data);
7010 data->entry = gtk_entry_new();
7012 pidgin_add_widget_to_vbox(data->rq_data.vbox, _("Buddy's _username:"),
7013 data->rq_data.sg, data->entry, TRUE, NULL);
7014 gtk_widget_grab_focus(data->entry);
7016 if (username != NULL)
7017 gtk_entry_set_text(GTK_ENTRY(data->entry), username);
7018 else
7019 gtk_dialog_set_response_sensitive(GTK_DIALOG(data->rq_data.window),
7020 GTK_RESPONSE_OK, FALSE);
7022 gtk_entry_set_activates_default (GTK_ENTRY(data->entry), TRUE);
7024 g_signal_connect(G_OBJECT(data->entry), "changed",
7025 G_CALLBACK(set_sensitive_if_input_buddy_cb),
7026 data);
7028 data->entry_for_alias = gtk_entry_new();
7029 pidgin_add_widget_to_vbox(data->rq_data.vbox, _("(Optional) A_lias:"),
7030 data->rq_data.sg, data->entry_for_alias, TRUE,
7031 NULL);
7033 if (alias != NULL)
7034 gtk_entry_set_text(GTK_ENTRY(data->entry_for_alias), alias);
7036 if (username != NULL)
7037 gtk_widget_grab_focus(GTK_WIDGET(data->entry_for_alias));
7039 data->entry_for_invite = gtk_entry_new();
7040 pidgin_add_widget_to_vbox(data->rq_data.vbox, _("(Optional) _Invite message:"),
7041 data->rq_data.sg, data->entry_for_invite, TRUE,
7042 NULL);
7044 data->combo = pidgin_text_combo_box_entry_new(group, groups_tree());
7045 pidgin_add_widget_to_vbox(data->rq_data.vbox, _("Add buddy to _group:"),
7046 data->rq_data.sg, data->combo, TRUE, NULL);
7048 gtk_widget_show_all(data->rq_data.window);
7050 /* Force update of invite message entry sensitivity */
7051 pidgin_account_chooser_set_selected(blist_req_data->account_menu,
7052 account);
7055 static void
7056 add_chat_cb(GtkWidget *w, PidginAddChatData *data)
7058 GList *tmp;
7059 PurpleChat *chat;
7060 GHashTable *components;
7062 components = g_hash_table_new_full(g_str_hash, g_str_equal,
7063 g_free, g_free);
7065 for (tmp = data->chat_data.entries; tmp; tmp = tmp->next)
7067 if (g_object_get_data(tmp->data, "is_spin"))
7069 g_hash_table_replace(components,
7070 g_strdup(g_object_get_data(tmp->data, "identifier")),
7071 g_strdup_printf("%d",
7072 gtk_spin_button_get_value_as_int(tmp->data)));
7074 else
7076 const char *value = gtk_entry_get_text(tmp->data);
7078 if (*value != '\0')
7079 g_hash_table_replace(components,
7080 g_strdup(g_object_get_data(tmp->data, "identifier")),
7081 g_strdup(value));
7085 chat = purple_chat_new(data->chat_data.rq_data.account,
7086 gtk_entry_get_text(GTK_ENTRY(data->alias_entry)),
7087 components);
7089 if (chat != NULL) {
7090 PurpleGroup *group;
7091 const char *group_name;
7093 group_name = pidgin_text_combo_box_entry_get_text(data->group_combo);
7095 group = NULL;
7096 if ((group_name != NULL) && (*group_name != '\0') &&
7097 ((group = purple_blist_find_group(group_name)) == NULL))
7099 group = purple_group_new(group_name);
7100 purple_blist_add_group(group, NULL);
7103 purple_blist_add_chat(chat, group, NULL);
7105 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(data->autojoin)))
7106 purple_blist_node_set_bool(PURPLE_BLIST_NODE(chat), "gtk-autojoin", TRUE);
7108 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(data->persistent)))
7109 purple_blist_node_set_bool(PURPLE_BLIST_NODE(chat), "gtk-persistent", TRUE);
7112 gtk_widget_destroy(data->chat_data.rq_data.window);
7113 g_free(data->chat_data.default_chat_name);
7114 g_list_free(data->chat_data.entries);
7115 g_free(data);
7118 static void
7119 add_chat_resp_cb(GtkWidget *w, int resp, PidginAddChatData *data)
7121 if (resp == GTK_RESPONSE_OK)
7123 add_chat_cb(NULL, data);
7125 else if (resp == 1)
7127 pidgin_roomlist_dialog_show_with_account(data->chat_data.rq_data.account);
7129 else
7131 gtk_widget_destroy(data->chat_data.rq_data.window);
7132 g_free(data->chat_data.default_chat_name);
7133 g_list_free(data->chat_data.entries);
7134 g_free(data);
7138 static void
7139 pidgin_blist_request_add_chat(PurpleBuddyList *list, PurpleAccount *account,
7140 PurpleGroup *group, const char *alias,
7141 const char *name)
7143 PidginAddChatData *data;
7144 GList *l;
7145 PurpleConnection *gc;
7146 GtkBox *vbox;
7147 PurpleProtocol *protocol = NULL;
7149 if (account != NULL) {
7150 gc = purple_account_get_connection(account);
7151 protocol = purple_connection_get_protocol(gc);
7153 if (!PURPLE_PROTOCOL_IMPLEMENTS(protocol, CHAT, join)) {
7154 purple_notify_error(gc, NULL, _("This protocol does not"
7155 " support chat rooms."), NULL,
7156 purple_request_cpar_from_account(account));
7157 return;
7159 } else {
7160 /* Find an account with chat capabilities */
7161 for (l = purple_connections_get_all(); l != NULL; l = l->next) {
7162 gc = (PurpleConnection *)l->data;
7163 protocol = purple_connection_get_protocol(gc);
7165 if (PURPLE_PROTOCOL_IMPLEMENTS(protocol, CHAT, join)) {
7166 account = purple_connection_get_account(gc);
7167 break;
7171 if (account == NULL) {
7172 purple_notify_error(NULL, NULL,
7173 _("You are not currently signed on with any "
7174 "protocols that have the ability to chat."), NULL, NULL);
7175 return;
7179 data = g_new0(PidginAddChatData, 1);
7180 vbox = GTK_BOX(make_blist_request_dialog((PidginBlistRequestData *)data, account,
7181 _("Add Chat"), "add_chat",
7182 _("Please enter an alias, and the appropriate information "
7183 "about the chat you would like to add to your buddy list.\n"),
7184 G_CALLBACK(chat_select_account_cb), chat_account_filter_func,
7185 G_CALLBACK(add_chat_resp_cb)));
7186 gtk_dialog_add_buttons(GTK_DIALOG(data->chat_data.rq_data.window),
7187 _("Room List"), 1,
7188 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
7189 GTK_STOCK_ADD, GTK_RESPONSE_OK,
7190 NULL);
7191 gtk_dialog_set_default_response(GTK_DIALOG(data->chat_data.rq_data.window),
7192 GTK_RESPONSE_OK);
7194 data->chat_data.default_chat_name = g_strdup(name);
7196 rebuild_chat_entries((PidginChatData *)data, name);
7198 data->alias_entry = gtk_entry_new();
7199 if (alias != NULL)
7200 gtk_entry_set_text(GTK_ENTRY(data->alias_entry), alias);
7201 gtk_entry_set_activates_default(GTK_ENTRY(data->alias_entry), TRUE);
7203 pidgin_add_widget_to_vbox(GTK_BOX(vbox), _("A_lias:"),
7204 data->chat_data.rq_data.sg, data->alias_entry,
7205 TRUE, NULL);
7206 if (name != NULL)
7207 gtk_widget_grab_focus(data->alias_entry);
7209 data->group_combo = pidgin_text_combo_box_entry_new(group ? purple_group_get_name(group) : NULL, groups_tree());
7210 pidgin_add_widget_to_vbox(GTK_BOX(vbox), _("_Group:"),
7211 data->chat_data.rq_data.sg, data->group_combo,
7212 TRUE, NULL);
7214 data->autojoin = gtk_check_button_new_with_mnemonic(_("Automatically _join when account connects"));
7215 data->persistent = gtk_check_button_new_with_mnemonic(_("_Remain in chat after window is closed"));
7216 gtk_box_pack_start(GTK_BOX(vbox), data->autojoin, FALSE, FALSE, 0);
7217 gtk_box_pack_start(GTK_BOX(vbox), data->persistent, FALSE, FALSE, 0);
7219 gtk_widget_show_all(data->chat_data.rq_data.window);
7222 static void
7223 add_group_cb(PurpleConnection *gc, const char *group_name)
7225 PurpleGroup *group;
7227 if ((group_name == NULL) || (*group_name == '\0'))
7228 return;
7230 group = purple_group_new(group_name);
7231 purple_blist_add_group(group, NULL);
7234 static void
7235 pidgin_blist_request_add_group(PurpleBuddyList *list)
7237 purple_request_input(NULL, _("Add Group"), NULL,
7238 _("Please enter the name of the group to be added."),
7239 NULL, FALSE, FALSE, NULL,
7240 _("Add"), G_CALLBACK(add_group_cb),
7241 _("Cancel"), NULL,
7242 NULL, NULL);
7245 void
7246 pidgin_blist_toggle_visibility()
7248 if (gtkblist && gtkblist->window) {
7249 if (gtk_widget_get_visible(gtkblist->window)) {
7250 /* make the buddy list visible if it is iconified or if it is
7251 * obscured and not currently focused (the focus part ensures
7252 * that we do something reasonable if the buddy list is obscured
7253 * by a window set to always be on top), otherwise hide the
7254 * buddy list
7256 purple_blist_set_visible(PIDGIN_WINDOW_ICONIFIED(gtkblist->window) ||
7257 ((gtk_blist_visibility != GDK_VISIBILITY_UNOBSCURED) &&
7258 !gtk_blist_focused));
7259 } else {
7260 purple_blist_set_visible(TRUE);
7265 void
7266 pidgin_blist_visibility_manager_add()
7268 visibility_manager_count++;
7269 purple_debug_info("gtkblist", "added visibility manager: %d\n", visibility_manager_count);
7272 void
7273 pidgin_blist_visibility_manager_remove()
7275 if (visibility_manager_count)
7276 visibility_manager_count--;
7277 if (!visibility_manager_count)
7278 purple_blist_set_visible(TRUE);
7279 purple_debug_info("gtkblist", "removed visibility manager: %d\n", visibility_manager_count);
7282 void pidgin_blist_add_alert(GtkWidget *widget)
7284 gtk_container_add(GTK_CONTAINER(gtkblist->scrollbook), widget);
7285 set_urgent();
7288 void
7289 pidgin_blist_set_headline(const char *text, const gchar *icon_name,
7290 GCallback callback, gpointer user_data, GDestroyNotify destroy)
7292 /* Destroy any existing headline first */
7293 if (gtkblist->headline_destroy)
7294 gtkblist->headline_destroy(gtkblist->headline_data);
7296 gtk_label_set_markup(GTK_LABEL(gtkblist->headline_label), text);
7297 gtk_image_set_from_icon_name(GTK_IMAGE(gtkblist->headline_image),
7298 icon_name, GTK_ICON_SIZE_SMALL_TOOLBAR);
7300 gtkblist->headline_callback = callback;
7301 gtkblist->headline_data = user_data;
7302 gtkblist->headline_destroy = destroy;
7303 if (text != NULL || icon_name != NULL) {
7304 set_urgent();
7305 gtk_widget_show_all(gtkblist->headline);
7306 } else {
7307 gtk_widget_hide(gtkblist->headline);
7312 static void
7313 set_urgent(void)
7315 if (gtkblist->window && !gtk_widget_has_focus(gtkblist->window))
7316 pidgin_set_urgent(GTK_WINDOW(gtkblist->window), TRUE);
7319 PidginBuddyList *
7320 pidgin_blist_get_default_gtk_blist(void)
7322 return gtkblist;
7325 static gboolean autojoin_cb(PurpleConnection *gc, gpointer data)
7327 PurpleAccount *account = purple_connection_get_account(gc);
7328 PurpleBlistNode *gnode, *cnode;
7329 for (gnode = purple_blist_get_default_root(); gnode;
7330 gnode = gnode->next) {
7331 if(!PURPLE_IS_GROUP(gnode))
7332 continue;
7333 for(cnode = gnode->child; cnode; cnode = cnode->next)
7335 PurpleChat *chat;
7337 if(!PURPLE_IS_CHAT(cnode))
7338 continue;
7340 chat = (PurpleChat *)cnode;
7342 if(purple_chat_get_account(chat) != account)
7343 continue;
7345 if (purple_blist_node_get_bool(PURPLE_BLIST_NODE(chat), "gtk-autojoin"))
7346 purple_serv_join_chat(gc, purple_chat_get_components(chat));
7350 /* Stop processing; we handled the autojoins. */
7351 return TRUE;
7354 void *
7355 pidgin_blist_get_handle() {
7356 static int handle;
7358 return &handle;
7361 static gboolean buddy_signonoff_timeout_cb(PurpleBuddy *buddy)
7363 PidginBlistNode *gtknode = purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(buddy));
7365 gtknode->recent_signonoff = FALSE;
7366 gtknode->recent_signonoff_timer = 0;
7368 pidgin_blist_update(NULL, PURPLE_BLIST_NODE(buddy));
7370 g_object_unref(buddy);
7371 return FALSE;
7374 static void buddy_signonoff_cb(PurpleBuddy *buddy)
7376 PidginBlistNode *gtknode = purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(buddy));
7378 if(!gtknode) {
7379 pidgin_blist_new_node(purple_blist_get_default(),
7380 PURPLE_BLIST_NODE(buddy));
7383 gtknode = purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(buddy));
7385 gtknode->recent_signonoff = TRUE;
7387 if(gtknode->recent_signonoff_timer > 0)
7388 g_source_remove(gtknode->recent_signonoff_timer);
7390 g_object_ref(buddy);
7391 gtknode->recent_signonoff_timer = g_timeout_add_seconds(10,
7392 (GSourceFunc)buddy_signonoff_timeout_cb, buddy);
7395 void
7396 pidgin_blist_set_theme(PidginBlistTheme *theme)
7398 PidginBuddyListPrivate *priv =
7399 pidgin_buddy_list_get_instance_private(gtkblist);
7400 PurpleBuddyList *list = purple_blist_get_default();
7402 if (theme != NULL)
7403 purple_prefs_set_string(PIDGIN_PREFS_ROOT "/blist/theme",
7404 purple_theme_get_name(PURPLE_THEME(theme)));
7405 else
7406 purple_prefs_set_string(PIDGIN_PREFS_ROOT "/blist/theme", "");
7408 if (priv->current_theme)
7409 g_object_unref(priv->current_theme);
7411 priv->current_theme = theme ? g_object_ref(theme) : NULL;
7413 pidgin_blist_build_layout(list);
7415 pidgin_blist_refresh(list);
7419 PidginBlistTheme *
7420 pidgin_blist_get_theme()
7422 PidginBuddyListPrivate *priv =
7423 pidgin_buddy_list_get_instance_private(gtkblist);
7425 return priv->current_theme;
7428 void pidgin_blist_init(void)
7430 void *gtk_blist_handle = pidgin_blist_get_handle();
7432 cached_emblems = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
7434 /* Initialize prefs */
7435 purple_prefs_add_none(PIDGIN_PREFS_ROOT "/blist");
7436 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons", TRUE);
7437 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/show_empty_groups", FALSE);
7438 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/show_idle_time", TRUE);
7439 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/show_offline_buddies", FALSE);
7440 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons", FALSE);
7441 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/list_visible", FALSE);
7442 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/list_maximized", FALSE);
7443 purple_prefs_add_string(PIDGIN_PREFS_ROOT "/blist/sort_type", "alphabetical");
7444 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/blist/width", 250); /* Golden ratio, baby */
7445 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/blist/height", 405); /* Golden ratio, baby */
7446 purple_prefs_add_string(PIDGIN_PREFS_ROOT "/blist/theme", "");
7448 purple_theme_manager_register_type(g_object_new(PIDGIN_TYPE_BLIST_THEME_LOADER, "type", "blist", NULL));
7450 /* Register our signals */
7451 purple_signal_register(gtk_blist_handle, "gtkblist-hiding",
7452 purple_marshal_VOID__POINTER, G_TYPE_NONE, 1,
7453 PURPLE_TYPE_BUDDY_LIST);
7455 purple_signal_register(gtk_blist_handle, "gtkblist-unhiding",
7456 purple_marshal_VOID__POINTER, G_TYPE_NONE, 1,
7457 PURPLE_TYPE_BUDDY_LIST);
7459 purple_signal_register(gtk_blist_handle, "gtkblist-created",
7460 purple_marshal_VOID__POINTER, G_TYPE_NONE, 1,
7461 PURPLE_TYPE_BUDDY_LIST);
7463 purple_signal_register(gtk_blist_handle, "drawing-tooltip",
7464 purple_marshal_VOID__POINTER_POINTER_UINT, G_TYPE_NONE,
7465 3, PURPLE_TYPE_BLIST_NODE,
7466 G_TYPE_POINTER, /* pointer to a (GString *) */
7467 G_TYPE_BOOLEAN);
7469 purple_signal_register(gtk_blist_handle, "drawing-buddy",
7470 purple_marshal_POINTER__POINTER,
7471 G_TYPE_STRING, 1, PURPLE_TYPE_BUDDY);
7473 purple_signal_connect(purple_blist_get_handle(), "buddy-signed-on",
7474 gtk_blist_handle, PURPLE_CALLBACK(buddy_signonoff_cb), NULL);
7475 purple_signal_connect(purple_blist_get_handle(), "buddy-signed-off",
7476 gtk_blist_handle, PURPLE_CALLBACK(buddy_signonoff_cb), NULL);
7477 purple_signal_connect(purple_blist_get_handle(), "buddy-privacy-changed",
7478 gtk_blist_handle, PURPLE_CALLBACK(pidgin_blist_update_privacy_cb), NULL);
7480 purple_signal_connect_priority(purple_connections_get_handle(), "autojoin",
7481 gtk_blist_handle, PURPLE_CALLBACK(autojoin_cb),
7482 NULL, PURPLE_SIGNAL_PRIORITY_HIGHEST);
7485 void
7486 pidgin_blist_uninit(void) {
7487 g_hash_table_destroy(cached_emblems);
7489 purple_signals_unregister_by_instance(pidgin_blist_get_handle());
7490 purple_signals_disconnect_by_handle(pidgin_blist_get_handle());
7492 accountmenu = NULL;
7493 gtkblist = NULL;
7496 /**************************************************************************
7497 * GTK Buddy list GObject code
7498 **************************************************************************/
7499 static void
7500 pidgin_buddy_list_init(PidginBuddyList *self)
7504 static void
7505 pidgin_buddy_list_finalize(GObject *obj)
7507 PidginBuddyList *gtkblist = PIDGIN_BUDDY_LIST(obj);
7508 PidginBuddyListPrivate *priv =
7509 pidgin_buddy_list_get_instance_private(gtkblist);
7511 purple_signals_disconnect_by_handle(gtkblist);
7513 gtk_widget_destroy(gtkblist->window);
7515 pidgin_blist_tooltip_destroy();
7517 if (gtkblist->refresh_timer) {
7518 g_source_remove(gtkblist->refresh_timer);
7519 gtkblist->refresh_timer = 0;
7521 if (gtkblist->timeout) {
7522 g_source_remove(gtkblist->timeout);
7523 gtkblist->timeout = 0;
7525 if (gtkblist->drag_timeout) {
7526 g_source_remove(gtkblist->drag_timeout);
7527 gtkblist->drag_timeout = 0;
7530 gtkblist->window = gtkblist->vbox = gtkblist->treeview = NULL;
7531 g_clear_object(&gtkblist->treemodel);
7532 g_object_unref(G_OBJECT(gtkblist->ui));
7533 g_object_unref(G_OBJECT(gtkblist->empty_avatar));
7535 g_clear_object(&priv->current_theme);
7536 if (priv->select_notebook_page_timeout) {
7537 g_source_remove(priv->select_notebook_page_timeout);
7540 purple_prefs_disconnect_by_handle(pidgin_blist_get_handle());
7542 G_OBJECT_CLASS(pidgin_buddy_list_parent_class)->finalize(obj);
7545 static void
7546 pidgin_buddy_list_class_init(PidginBuddyListClass *klass)
7548 GObjectClass *obj_class = G_OBJECT_CLASS(klass);
7549 PurpleBuddyListClass *purple_blist_class;
7551 obj_class->finalize = pidgin_buddy_list_finalize;
7553 purple_blist_class = PURPLE_BUDDY_LIST_CLASS(klass);
7554 purple_blist_class->new_node = pidgin_blist_new_node;
7555 purple_blist_class->show = pidgin_blist_show;
7556 purple_blist_class->update = pidgin_blist_update;
7557 purple_blist_class->remove = pidgin_blist_remove;
7558 purple_blist_class->set_visible = pidgin_blist_set_visible;
7559 purple_blist_class->request_add_buddy = pidgin_blist_request_add_buddy;
7560 purple_blist_class->request_add_chat = pidgin_blist_request_add_chat;
7561 purple_blist_class->request_add_group = pidgin_blist_request_add_group;
7564 /*********************************************************************
7565 * Buddy List sorting functions *
7566 *********************************************************************/
7568 GList *pidgin_blist_get_sort_methods()
7570 return pidgin_blist_sort_methods;
7573 void pidgin_blist_sort_method_reg(const char *id, const char *name, pidgin_blist_sort_function func)
7575 struct _PidginBlistSortMethod *method;
7577 g_return_if_fail(id != NULL);
7578 g_return_if_fail(name != NULL);
7579 g_return_if_fail(func != NULL);
7581 method = g_new0(struct _PidginBlistSortMethod, 1);
7582 method->id = g_strdup(id);
7583 method->name = g_strdup(name);
7584 method->func = func;
7585 pidgin_blist_sort_methods = g_list_append(pidgin_blist_sort_methods, method);
7586 pidgin_blist_update_sort_methods();
7589 void pidgin_blist_sort_method_unreg(const char *id)
7591 GList *l = pidgin_blist_sort_methods;
7593 g_return_if_fail(id != NULL);
7595 while(l) {
7596 struct _PidginBlistSortMethod *method = l->data;
7597 if(purple_strequal(method->id, id)) {
7598 pidgin_blist_sort_methods = g_list_delete_link(pidgin_blist_sort_methods, l);
7599 g_free(method->id);
7600 g_free(method->name);
7601 g_free(method);
7602 break;
7604 l = l->next;
7606 pidgin_blist_update_sort_methods();
7609 void pidgin_blist_sort_method_set(const char *id){
7610 GList *l = pidgin_blist_sort_methods;
7612 if(!id)
7613 id = "none";
7615 while (l && !purple_strequal(((struct _PidginBlistSortMethod*)l->data)->id, id))
7616 l = l->next;
7618 if (l) {
7619 current_sort_method = l->data;
7620 } else if (!current_sort_method) {
7621 pidgin_blist_sort_method_set("none");
7622 return;
7624 if (purple_strequal(id, "none")) {
7625 redo_buddy_list(purple_blist_get_default(), TRUE, FALSE);
7626 } else {
7627 redo_buddy_list(purple_blist_get_default(), FALSE, FALSE);
7631 /******************************************
7632 ** Sort Methods
7633 ******************************************/
7635 static void sort_method_none(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter parent_iter, GtkTreeIter *cur, GtkTreeIter *iter)
7637 PurpleBlistNode *sibling = node->prev;
7638 GtkTreeIter sibling_iter;
7640 if (cur != NULL) {
7641 *iter = *cur;
7642 return;
7645 while (sibling && !get_iter_from_node(sibling, &sibling_iter)) {
7646 sibling = sibling->prev;
7649 gtk_tree_store_insert_after(gtkblist->treemodel, iter,
7650 node->parent ? &parent_iter : NULL,
7651 sibling ? &sibling_iter : NULL);
7654 static void sort_method_alphabetical(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter)
7656 GtkTreeIter more_z;
7658 const char *my_name;
7660 if(PURPLE_IS_CONTACT(node)) {
7661 my_name = purple_contact_get_alias((PurpleContact*)node);
7662 } else if(PURPLE_IS_CHAT(node)) {
7663 my_name = purple_chat_get_name((PurpleChat*)node);
7664 } else {
7665 sort_method_none(node, blist, groupiter, cur, iter);
7666 return;
7669 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(gtkblist->treemodel), &more_z, &groupiter)) {
7670 gtk_tree_store_insert(gtkblist->treemodel, iter, &groupiter, 0);
7671 return;
7674 do {
7675 PurpleBlistNode *n;
7676 const char *this_name;
7677 int cmp;
7679 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &more_z, NODE_COLUMN, &n, -1);
7681 if(PURPLE_IS_CONTACT(n)) {
7682 this_name = purple_contact_get_alias((PurpleContact*)n);
7683 } else if(PURPLE_IS_CHAT(n)) {
7684 this_name = purple_chat_get_name((PurpleChat*)n);
7685 } else {
7686 this_name = NULL;
7689 cmp = purple_utf8_strcasecmp(my_name, this_name);
7691 if(this_name && (cmp < 0 || (cmp == 0 && node < n))) {
7692 if(cur) {
7693 gtk_tree_store_move_before(gtkblist->treemodel, cur, &more_z);
7694 *iter = *cur;
7695 return;
7696 } else {
7697 gtk_tree_store_insert_before(gtkblist->treemodel, iter,
7698 &groupiter, &more_z);
7699 return;
7702 } while (gtk_tree_model_iter_next (GTK_TREE_MODEL(gtkblist->treemodel), &more_z));
7704 if(cur) {
7705 gtk_tree_store_move_before(gtkblist->treemodel, cur, NULL);
7706 *iter = *cur;
7707 return;
7708 } else {
7709 gtk_tree_store_append(gtkblist->treemodel, iter, &groupiter);
7710 return;
7714 static void sort_method_status(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter)
7716 GtkTreeIter more_z;
7718 PurpleBuddy *my_buddy, *this_buddy;
7720 if(PURPLE_IS_CONTACT(node)) {
7721 my_buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
7722 } else if(PURPLE_IS_CHAT(node)) {
7723 if (cur != NULL) {
7724 *iter = *cur;
7725 return;
7728 gtk_tree_store_append(gtkblist->treemodel, iter, &groupiter);
7729 return;
7730 } else {
7731 sort_method_alphabetical(node, blist, groupiter, cur, iter);
7732 return;
7736 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(gtkblist->treemodel), &more_z, &groupiter)) {
7737 gtk_tree_store_insert(gtkblist->treemodel, iter, &groupiter, 0);
7738 return;
7741 do {
7742 PurpleBlistNode *n;
7743 gint name_cmp;
7744 gint presence_cmp;
7746 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &more_z, NODE_COLUMN, &n, -1);
7748 if(PURPLE_IS_CONTACT(n)) {
7749 this_buddy = purple_contact_get_priority_buddy((PurpleContact*)n);
7750 } else {
7751 this_buddy = NULL;
7754 name_cmp = purple_utf8_strcasecmp(
7755 purple_contact_get_alias(purple_buddy_get_contact(my_buddy)),
7756 (this_buddy
7757 ? purple_contact_get_alias(purple_buddy_get_contact(this_buddy))
7758 : NULL));
7760 presence_cmp = purple_buddy_presence_compare(
7761 PURPLE_BUDDY_PRESENCE(purple_buddy_get_presence(my_buddy)),
7762 this_buddy ? PURPLE_BUDDY_PRESENCE(purple_buddy_get_presence(this_buddy)) : NULL);
7764 if (this_buddy == NULL ||
7765 (presence_cmp < 0 ||
7766 (presence_cmp == 0 &&
7767 (name_cmp < 0 || (name_cmp == 0 && node < n)))))
7769 if (cur != NULL)
7771 gtk_tree_store_move_before(gtkblist->treemodel, cur, &more_z);
7772 *iter = *cur;
7773 return;
7775 else
7777 gtk_tree_store_insert_before(gtkblist->treemodel, iter,
7778 &groupiter, &more_z);
7779 return;
7783 while (gtk_tree_model_iter_next(GTK_TREE_MODEL(gtkblist->treemodel),
7784 &more_z));
7786 if (cur) {
7787 gtk_tree_store_move_before(gtkblist->treemodel, cur, NULL);
7788 *iter = *cur;
7789 return;
7790 } else {
7791 gtk_tree_store_append(gtkblist->treemodel, iter, &groupiter);
7792 return;
7796 static void sort_method_log_activity(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter)
7798 GtkTreeIter more_z;
7800 int activity_score = 0, this_log_activity_score = 0;
7801 const char *buddy_name, *this_buddy_name;
7803 if(cur && (gtk_tree_model_iter_n_children(GTK_TREE_MODEL(gtkblist->treemodel), &groupiter) == 1)) {
7804 *iter = *cur;
7805 return;
7808 if(PURPLE_IS_CONTACT(node)) {
7809 PurpleBlistNode *n;
7810 PurpleBuddy *buddy;
7811 for (n = node->child; n; n = n->next) {
7812 buddy = (PurpleBuddy*)n;
7813 activity_score += purple_log_get_activity_score(PURPLE_LOG_IM, purple_buddy_get_name(buddy), purple_buddy_get_account(buddy));
7815 buddy_name = purple_contact_get_alias((PurpleContact*)node);
7816 } else if(PURPLE_IS_CHAT(node)) {
7817 /* we don't have a reliable way of getting the log filename
7818 * from the chat info in the blist, yet */
7819 if (cur != NULL) {
7820 *iter = *cur;
7821 return;
7824 gtk_tree_store_append(gtkblist->treemodel, iter, &groupiter);
7825 return;
7826 } else {
7827 sort_method_none(node, blist, groupiter, cur, iter);
7828 return;
7832 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(gtkblist->treemodel), &more_z, &groupiter)) {
7833 gtk_tree_store_insert(gtkblist->treemodel, iter, &groupiter, 0);
7834 return;
7837 do {
7838 PurpleBlistNode *n;
7839 PurpleBlistNode *n2;
7840 PurpleBuddy *buddy;
7841 int cmp;
7843 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &more_z, NODE_COLUMN, &n, -1);
7844 this_log_activity_score = 0;
7846 if(PURPLE_IS_CONTACT(n)) {
7847 for (n2 = n->child; n2; n2 = n2->next) {
7848 buddy = (PurpleBuddy*)n2;
7849 this_log_activity_score += purple_log_get_activity_score(PURPLE_LOG_IM, purple_buddy_get_name(buddy), purple_buddy_get_account(buddy));
7851 this_buddy_name = purple_contact_get_alias((PurpleContact*)n);
7852 } else {
7853 this_buddy_name = NULL;
7856 cmp = purple_utf8_strcasecmp(buddy_name, this_buddy_name);
7858 if (!PURPLE_IS_CONTACT(n) || activity_score > this_log_activity_score ||
7859 ((activity_score == this_log_activity_score) &&
7860 (cmp < 0 || (cmp == 0 && node < n)))) {
7861 if (cur != NULL) {
7862 gtk_tree_store_move_before(gtkblist->treemodel, cur, &more_z);
7863 *iter = *cur;
7864 return;
7865 } else {
7866 gtk_tree_store_insert_before(gtkblist->treemodel, iter,
7867 &groupiter, &more_z);
7868 return;
7871 } while (gtk_tree_model_iter_next (GTK_TREE_MODEL(gtkblist->treemodel), &more_z));
7873 if (cur != NULL) {
7874 gtk_tree_store_move_before(gtkblist->treemodel, cur, NULL);
7875 *iter = *cur;
7876 return;
7877 } else {
7878 gtk_tree_store_append(gtkblist->treemodel, iter, &groupiter);
7879 return;
7883 static void
7884 plugin_act(GSimpleAction *action, GVariant *param, PurplePluginAction *pam)
7886 if (pam && pam->callback)
7887 pam->callback(pam);
7890 static GtkWidget *
7891 build_plugin_actions(GActionMap *action_map, const gchar *parent,
7892 PurplePlugin *plugin)
7894 GMenu *menu = NULL;
7895 GMenu *section = NULL;
7896 PurplePluginActionsCb actions_cb;
7897 PurplePluginAction *action = NULL;
7898 GList *actions, *l;
7899 char *name;
7900 int count = 0;
7901 GtkWidget *ret;
7903 actions_cb =
7904 purple_plugin_info_get_actions_cb(purple_plugin_get_info(plugin));
7906 actions = actions_cb(plugin);
7908 if (actions == NULL) {
7909 return NULL;
7912 menu = g_menu_new();
7914 for (l = actions; l != NULL; l = l->next) {
7915 GAction *menuaction;
7917 action = (PurplePluginAction *)l->data;
7919 if (action == NULL) {
7920 if (section != NULL) {
7921 /* Close and append section if any */
7922 g_menu_append_section(menu, NULL,
7923 G_MENU_MODEL(section));
7924 g_clear_object(&section);
7927 continue;
7930 action->plugin = plugin;
7932 name = g_strdup_printf("plugin.%s-action-%d", parent, count++);
7933 /* +7 to skip "plugin." prefix */
7934 menuaction = G_ACTION(g_simple_action_new(name + 7, NULL));
7935 g_signal_connect_data(G_OBJECT(menuaction), "activate",
7936 G_CALLBACK(plugin_act), action,
7937 (GClosureNotify)purple_plugin_action_free, 0);
7938 g_action_map_add_action(action_map, menuaction);
7939 g_object_unref(menuaction);
7941 if (section == NULL) {
7942 section = g_menu_new();
7945 g_menu_append(section, action->label, name);
7946 g_free(name);
7949 if (section != NULL) {
7950 /* Close and append final section if any */
7951 g_menu_append_section(menu, NULL, G_MENU_MODEL(section));
7952 g_clear_object(&section);
7955 g_list_free(actions);
7957 ret = gtk_menu_new_from_model(G_MENU_MODEL(menu));
7958 g_object_unref(menu);
7959 return ret;
7963 static void
7964 modify_account_cb(GtkWidget *widget, gpointer data)
7966 pidgin_account_dialog_show(PIDGIN_MODIFY_ACCOUNT_DIALOG, data);
7969 static void
7970 enable_account_cb(GtkCheckMenuItem *widget, gpointer data)
7972 PurpleAccount *account = data;
7973 const PurpleSavedStatus *saved_status;
7975 saved_status = purple_savedstatus_get_current();
7976 purple_savedstatus_activate_for_account(saved_status, account);
7978 purple_account_set_enabled(account, PIDGIN_UI, TRUE);
7981 static void
7982 disable_account_cb(GtkCheckMenuItem *widget, gpointer data)
7984 PurpleAccount *account = data;
7986 purple_account_set_enabled(account, PIDGIN_UI, FALSE);
7989 static void
7990 protocol_act(GtkWidget *obj, PurpleProtocolAction *pam)
7992 if (pam && pam->callback)
7993 pam->callback(pam);
7996 void
7997 pidgin_blist_update_accounts_menu(void)
7999 GtkWidget *menuitem, *submenu;
8000 GtkAccelGroup *accel_group;
8001 GList *l, *accounts;
8002 gboolean disabled_accounts = FALSE;
8003 gboolean enabled_accounts = FALSE;
8005 if (accountmenu == NULL)
8006 return;
8008 /* Clear the old Accounts menu */
8009 for (l = gtk_container_get_children(GTK_CONTAINER(accountmenu)); l; l = g_list_delete_link(l, l)) {
8010 menuitem = l->data;
8012 if (menuitem != gtk_ui_manager_get_widget(gtkblist->ui, "/BList/AccountsMenu/ManageAccounts"))
8013 gtk_widget_destroy(menuitem);
8016 accel_group = gtk_menu_get_accel_group(GTK_MENU(accountmenu));
8018 for (accounts = purple_accounts_get_all(); accounts; accounts = accounts->next) {
8019 char *buf = NULL;
8020 GtkWidget *image = NULL;
8021 PurpleAccount *account = NULL;
8022 GdkPixbuf *pixbuf = NULL;
8024 account = accounts->data;
8026 if (!purple_account_get_enabled(account, PIDGIN_UI)) {
8027 if (!disabled_accounts) {
8028 menuitem = gtk_menu_item_new_with_label(_("Enable Account"));
8029 gtk_menu_shell_append(GTK_MENU_SHELL(accountmenu), menuitem);
8031 submenu = gtk_menu_new();
8032 gtk_menu_set_accel_group(GTK_MENU(submenu), accel_group);
8033 gtk_menu_set_accel_path(GTK_MENU(submenu), "<Actions>/BListActions/EnableAccount");
8034 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
8036 disabled_accounts = TRUE;
8039 buf = g_strconcat(purple_account_get_username(account), " (",
8040 purple_account_get_protocol_name(account), ")", NULL);
8041 menuitem = gtk_image_menu_item_new_with_label(buf);
8042 g_free(buf);
8044 pixbuf = pidgin_create_protocol_icon(account, PIDGIN_PROTOCOL_ICON_SMALL);
8045 if (pixbuf != NULL) {
8046 if (!purple_account_is_connected(account))
8047 gdk_pixbuf_saturate_and_pixelate(pixbuf, pixbuf, 0.0, FALSE);
8048 image = gtk_image_new_from_pixbuf(pixbuf);
8049 g_object_unref(G_OBJECT(pixbuf));
8050 gtk_widget_show(image);
8051 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image);
8054 g_signal_connect(G_OBJECT(menuitem), "activate",
8055 G_CALLBACK(enable_account_cb), account);
8056 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), menuitem);
8058 } else {
8059 enabled_accounts = TRUE;
8063 if (!enabled_accounts) {
8064 gtk_widget_show_all(accountmenu);
8065 return;
8068 pidgin_separator(accountmenu);
8070 for (accounts = purple_accounts_get_all(); accounts; accounts = accounts->next) {
8071 char *buf = NULL;
8072 char *accel_path_buf = NULL;
8073 GtkWidget *image = NULL;
8074 PurpleConnection *gc = NULL;
8075 PurpleAccount *account = NULL;
8076 GdkPixbuf *pixbuf = NULL;
8077 PurpleProtocol *protocol;
8079 account = accounts->data;
8081 if (!purple_account_get_enabled(account, PIDGIN_UI))
8082 continue;
8084 buf = g_strconcat(purple_account_get_username(account), " (",
8085 purple_account_get_protocol_name(account), ")", NULL);
8086 menuitem = gtk_image_menu_item_new_with_label(buf);
8087 accel_path_buf = g_strconcat("<Actions>/AccountActions/", buf, NULL);
8088 g_free(buf);
8090 pixbuf = pidgin_create_protocol_icon(account, PIDGIN_PROTOCOL_ICON_SMALL);
8091 if (pixbuf != NULL) {
8092 if (!purple_account_is_connected(account))
8093 gdk_pixbuf_saturate_and_pixelate(pixbuf, pixbuf,
8094 0.0, FALSE);
8095 image = gtk_image_new_from_pixbuf(pixbuf);
8096 g_object_unref(G_OBJECT(pixbuf));
8097 gtk_widget_show(image);
8098 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image);
8101 gtk_menu_shell_append(GTK_MENU_SHELL(accountmenu), menuitem);
8103 submenu = gtk_menu_new();
8104 gtk_menu_set_accel_group(GTK_MENU(submenu), accel_group);
8105 gtk_menu_set_accel_path(GTK_MENU(submenu), accel_path_buf);
8106 g_free(accel_path_buf);
8107 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
8109 menuitem = gtk_menu_item_new_with_mnemonic(_("_Edit Account"));
8110 g_signal_connect(G_OBJECT(menuitem), "activate",
8111 G_CALLBACK(modify_account_cb), account);
8112 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), menuitem);
8114 pidgin_separator(submenu);
8116 gc = purple_account_get_connection(account);
8117 protocol = gc && PURPLE_CONNECTION_IS_CONNECTED(gc) ?
8118 purple_connection_get_protocol(gc) : NULL;
8120 if (protocol &&
8121 (PURPLE_PROTOCOL_IMPLEMENTS(protocol, CLIENT, get_moods) ||
8122 PURPLE_PROTOCOL_IMPLEMENTS(protocol, CLIENT, get_actions))) {
8123 if (PURPLE_PROTOCOL_IMPLEMENTS(protocol, CLIENT, get_moods) &&
8124 (purple_connection_get_flags(gc) & PURPLE_CONNECTION_FLAG_SUPPORT_MOODS)) {
8126 if (purple_account_get_status(account, "mood")) {
8127 menuitem = gtk_menu_item_new_with_mnemonic(_("Set _Mood..."));
8128 g_signal_connect(G_OBJECT(menuitem), "activate",
8129 G_CALLBACK(set_mood_cb), account);
8130 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), menuitem);
8134 if (PURPLE_PROTOCOL_IMPLEMENTS(protocol, CLIENT, get_actions)) {
8135 GtkWidget *menuitem;
8136 PurpleProtocolAction *action = NULL;
8137 GList *actions, *l;
8139 actions = purple_protocol_client_iface_get_actions(protocol, gc);
8141 for (l = actions; l != NULL; l = l->next)
8143 if (l->data)
8145 action = (PurpleProtocolAction *) l->data;
8146 action->connection = gc;
8148 menuitem = gtk_menu_item_new_with_label(action->label);
8149 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), menuitem);
8151 g_signal_connect(G_OBJECT(menuitem), "activate",
8152 G_CALLBACK(protocol_act), action);
8153 g_object_set_data_full(G_OBJECT(menuitem), "protocol_action",
8154 action,
8155 (GDestroyNotify)purple_protocol_action_free);
8156 gtk_widget_show(menuitem);
8158 else
8159 pidgin_separator(submenu);
8162 g_list_free(actions);
8164 } else {
8165 menuitem = gtk_menu_item_new_with_label(_("No actions available"));
8166 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), menuitem);
8167 gtk_widget_set_sensitive(menuitem, FALSE);
8170 pidgin_separator(submenu);
8172 menuitem = gtk_menu_item_new_with_mnemonic(_("_Disable"));
8173 g_signal_connect(G_OBJECT(menuitem), "activate",
8174 G_CALLBACK(disable_account_cb), account);
8175 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), menuitem);
8178 gtk_widget_show_all(accountmenu);
8181 static GSList *plugin_menu_items;
8183 void
8184 pidgin_blist_update_plugin_actions(void)
8186 GtkWidget *toolsmenu;
8187 GSimpleActionGroup *action_group;
8188 PurplePlugin *plugin = NULL;
8189 PurplePluginInfo *info;
8190 GList *l;
8192 int count = 0;
8194 if ((gtkblist == NULL) || (gtkblist->ui == NULL))
8195 return;
8197 /* Clear the old menu */
8198 g_slist_free_full(plugin_menu_items,
8199 (GDestroyNotify)gtk_widget_destroy);
8200 plugin_menu_items = NULL;
8202 toolsmenu = gtk_ui_manager_get_widget(gtkblist->ui,
8203 "/BList/ToolsMenu");
8204 toolsmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(toolsmenu));
8206 action_group = g_simple_action_group_new();
8208 /* Add a submenu for each plugin with custom actions */
8209 for (l = purple_plugins_get_loaded(); l; l = l->next) {
8210 char *name;
8211 GtkWidget *submenu;
8212 GtkWidget *menuitem;
8214 plugin = (PurplePlugin *)l->data;
8215 info = purple_plugin_get_info(plugin);
8217 if (!purple_plugin_info_get_actions_cb(info))
8218 continue;
8220 name = g_strdup_printf("plugin%d", count);
8221 submenu = build_plugin_actions(G_ACTION_MAP(action_group),
8222 name, plugin);
8223 g_free(name);
8225 if (submenu == NULL) {
8226 continue;
8229 menuitem = gtk_menu_item_new_with_mnemonic(
8230 _(gplugin_plugin_info_get_name(
8231 GPLUGIN_PLUGIN_INFO(info))));
8232 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
8233 gtk_widget_show(menuitem);
8234 plugin_menu_items = g_slist_prepend(plugin_menu_items,
8235 menuitem);
8236 gtk_menu_shell_append(GTK_MENU_SHELL(toolsmenu), menuitem);
8238 count++;
8241 /* Replaces existing "plugin" group if any */
8242 gtk_widget_insert_action_group(toolsmenu, "plugin",
8243 G_ACTION_GROUP(action_group));
8244 g_object_unref(action_group);
8247 static void
8248 sortmethod_act(GtkRadioAction *action, GtkRadioAction *current, char *id)
8250 if (action == current)
8252 pidgin_set_cursor(gtkblist->window, GDK_WATCH);
8253 /* This is redundant. I think. */
8254 /* pidgin_blist_sort_method_set(id); */
8255 purple_prefs_set_string(PIDGIN_PREFS_ROOT "/blist/sort_type", id);
8257 pidgin_clear_cursor(gtkblist->window);
8261 void
8262 pidgin_blist_update_sort_methods(void)
8264 PidginBlistSortMethod *method = NULL;
8265 GList *l;
8266 GSList *sl = NULL;
8267 const char *m = purple_prefs_get_string(PIDGIN_PREFS_ROOT "/blist/sort_type");
8269 GtkRadioAction *action;
8270 GString *ui_string;
8272 if ((gtkblist == NULL) || (gtkblist->ui == NULL))
8273 return;
8275 /* Clear the old menu */
8276 if (sort_action_group) {
8277 gtk_ui_manager_remove_ui(gtkblist->ui, sort_merge_id);
8278 gtk_ui_manager_remove_action_group(gtkblist->ui, sort_action_group);
8279 g_object_unref(G_OBJECT(sort_action_group));
8282 sort_action_group = gtk_action_group_new("SortMethods");
8283 gtk_action_group_set_translation_domain(sort_action_group, PACKAGE);
8285 ui_string = g_string_new("<ui><menubar name='BList'>"
8286 "<menu action='BuddiesMenu'><menu action='SortMenu'>");
8288 for (l = pidgin_blist_sort_methods; l; l = l->next) {
8289 method = (PidginBlistSortMethod *)l->data;
8291 g_string_append_printf(ui_string, "<menuitem action='%s'/>", method->id);
8292 action = gtk_radio_action_new(method->id,
8293 method->name,
8294 NULL,
8295 NULL,
8297 gtk_action_group_add_action_with_accel(sort_action_group, GTK_ACTION(action), NULL);
8299 gtk_radio_action_set_group(action, sl);
8300 sl = gtk_radio_action_get_group(action);
8302 if (purple_strequal(m, method->id))
8303 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action), TRUE);
8304 else
8305 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action), FALSE);
8307 g_signal_connect(G_OBJECT(action), "changed",
8308 G_CALLBACK(sortmethod_act), method->id);
8311 g_string_append(ui_string, "</menu></menu></menubar></ui>");
8312 gtk_ui_manager_insert_action_group(gtkblist->ui, sort_action_group, 1);
8313 sort_merge_id = gtk_ui_manager_add_ui_from_string(gtkblist->ui, ui_string->str, -1, NULL);
8315 g_string_free(ui_string, TRUE);