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