1 /*
2  * @file gtkblist.c GTK+ BuddyList API
3  * @ingroup pidgin
4  */
5 
6 /* pidgin
7  *
8  * Pidgin is the legal property of its developers, whose names are too numerous
9  * to list here.  Please refer to the COPYRIGHT file distributed with this
10  * source distribution.
11  *
12  * This program is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation; either version 2 of the License, or
15  * (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, write to the Free Software
24  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
25  *
26  */
27 #include "internal.h"
28 #include "pidgin.h"
29 
30 #include "account.h"
31 #include "connection.h"
32 #include "core.h"
33 #include "debug.h"
34 #include "glibcompat.h"
35 #include "notify.h"
36 #include "prpl.h"
37 #include "prefs.h"
38 #include "plugin.h"
39 #include "request.h"
40 #include "signals.h"
41 #include "pidginstock.h"
42 #include "theme-loader.h"
43 #include "theme-manager.h"
44 #include "util.h"
45 
46 #include "gtkaccount.h"
47 #include "gtkblist.h"
48 #include "gtkcellrendererexpander.h"
49 #include "gtkcertmgr.h"
50 #include "gtkconv.h"
51 #include "gtkdebug.h"
52 #include "gtkdialogs.h"
53 #include "gtkft.h"
54 #include "gtklog.h"
55 #include "gtkmenutray.h"
56 #include "gtkpounce.h"
57 #include "gtkplugin.h"
58 #include "gtkprefs.h"
59 #include "gtkprivacy.h"
60 #include "gtkroomlist.h"
61 #include "gtkstatusbox.h"
62 #include "gtkscrollbook.h"
63 #include "gtksmiley.h"
64 #include "gtkstyle.h"
65 #include "gtkblist-theme.h"
66 #include "gtkblist-theme-loader.h"
67 #include "gtkutils.h"
68 #include "pidgin/minidialog.h"
69 #include "pidgin/pidgintooltip.h"
70 
71 #include <gdk/gdkkeysyms.h>
72 #include <gtk/gtk.h>
73 #include <gdk/gdk.h>
74 
75 typedef struct
76 {
77 	PurpleAccount *account;
78 	GtkWidget *window;
79 	GtkBox *vbox;
80 	GtkWidget *account_menu;
81 	GtkSizeGroup *sg;
82 } PidginBlistRequestData;
83 
84 typedef struct
85 {
86 	PidginBlistRequestData rq_data;
87 	GtkWidget *combo;
88 	GtkWidget *entry;
89 	GtkWidget *entry_for_alias;
90 	GtkWidget *entry_for_invite;
91 
92 } PidginAddBuddyData;
93 
94 typedef struct
95 {
96 	PidginBlistRequestData rq_data;
97 	gchar *default_chat_name;
98 	GList *entries;
99 } PidginChatData;
100 
101 typedef struct
102 {
103 	PidginChatData chat_data;
104 
105 	GtkWidget *alias_entry;
106 	GtkWidget *group_combo;
107 	GtkWidget *autojoin;
108 	GtkWidget *persistent;
109 } PidginAddChatData;
110 
111 typedef struct
112 {
113 	/** Used to hold error minidialogs.  Gets packed
114 	 *  inside PidginBuddyList.error_buttons
115 	 */
116 	PidginScrollBook *error_scrollbook;
117 
118 	/** Pointer to the mini-dialog about having signed on elsewhere, if one
119 	 *  is showing; @c NULL otherwise.
120 	 */
121 	PidginMiniDialog *signed_on_elsewhere;
122 
123 	PidginBlistTheme *current_theme;
124 
125 	guint select_page_timeout;       /**< The timeout for pidgin_blist_select_notebook_page_cb */
126 } PidginBuddyListPrivate;
127 
128 #define PIDGIN_BUDDY_LIST_GET_PRIVATE(list) \
129 	((PidginBuddyListPrivate *)((list)->priv))
130 
131 static GtkWidget *accountmenu = NULL;
132 
133 static guint visibility_manager_count = 0;
134 static GdkVisibilityState gtk_blist_visibility = GDK_VISIBILITY_UNOBSCURED;
135 static gboolean gtk_blist_focused = FALSE;
136 static gboolean editing_blist = FALSE;
137 
138 static GList *pidgin_blist_sort_methods = NULL;
139 static struct pidgin_blist_sort_method *current_sort_method = NULL;
140 static void sort_method_none(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter);
141 
142 static void sort_method_alphabetical(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter);
143 static void sort_method_status(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter);
144 static void sort_method_log_activity(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter);
145 static PidginBuddyList *gtkblist = NULL;
146 
147 static GList *groups_tree(void);
148 static gboolean pidgin_blist_refresh_timer(PurpleBuddyList *list);
149 static void pidgin_blist_update_buddy(PurpleBuddyList *list, PurpleBlistNode *node, gboolean status_change);
150 static void pidgin_blist_selection_changed(GtkTreeSelection *selection, gpointer data);
151 static void pidgin_blist_update(PurpleBuddyList *list, PurpleBlistNode *node);
152 static void pidgin_blist_update_group(PurpleBuddyList *list, PurpleBlistNode *node);
153 static void pidgin_blist_update_contact(PurpleBuddyList *list, PurpleBlistNode *node);
154 static char *pidgin_get_tooltip_text(PurpleBlistNode *node, gboolean full);
155 static const char *item_factory_translate_func (const char *path, gpointer func_data);
156 static gboolean get_iter_from_node(PurpleBlistNode *node, GtkTreeIter *iter);
157 static gboolean buddy_is_displayable(PurpleBuddy *buddy);
158 static void redo_buddy_list(PurpleBuddyList *list, gboolean remove, gboolean rerender);
159 static void pidgin_blist_collapse_contact_cb(GtkWidget *w, PurpleBlistNode *node);
160 static char *pidgin_get_group_title(PurpleBlistNode *gnode, gboolean expanded);
161 static void pidgin_blist_expand_contact_cb(GtkWidget *w, PurpleBlistNode *node);
162 static void set_urgent(void);
163 
164 typedef enum {
165 	PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE            =  1 << 0,  /* Whether there's pending message in a conversation */
166 	PIDGIN_BLIST_CHAT_HAS_PENDING_MESSAGE_WITH_NICK	 =  1 << 1,  /* Whether there's a pending message in a chat that mentions our nick */
167 } PidginBlistNodeFlags;
168 
169 typedef struct _pidgin_blist_node {
170 	GtkTreeRowReference *row;
171 	gboolean contact_expanded;
172 	gboolean recent_signonoff;
173 	gint recent_signonoff_timer;
174 	struct {
175 		PurpleConversation *conv;
176 		time_t last_message;          /* timestamp for last displayed message */
177 		PidginBlistNodeFlags flags;
178 	} conv;
179 } PidginBlistNode;
180 
181 /***************************************************
182  *              Callbacks                          *
183  ***************************************************/
gtk_blist_visibility_cb(GtkWidget * w,GdkEventVisibility * event,gpointer data)184 static gboolean gtk_blist_visibility_cb(GtkWidget *w, GdkEventVisibility *event, gpointer data)
185 {
186 	GdkVisibilityState old_state = gtk_blist_visibility;
187 	gtk_blist_visibility = event->state;
188 
189 	if (gtk_blist_visibility == GDK_VISIBILITY_FULLY_OBSCURED &&
190 		old_state != GDK_VISIBILITY_FULLY_OBSCURED) {
191 
192 		/* no longer fully obscured */
193 		pidgin_blist_refresh_timer(purple_get_blist());
194 	}
195 
196 	/* continue to handle event normally */
197 	return FALSE;
198 }
199 
gtk_blist_window_state_cb(GtkWidget * w,GdkEventWindowState * event,gpointer data)200 static gboolean gtk_blist_window_state_cb(GtkWidget *w, GdkEventWindowState *event, gpointer data)
201 {
202 	if(event->changed_mask & GDK_WINDOW_STATE_WITHDRAWN) {
203 		if(event->new_window_state & GDK_WINDOW_STATE_WITHDRAWN)
204 			purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/list_visible", FALSE);
205 		else {
206 			purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/list_visible", TRUE);
207 			pidgin_blist_refresh_timer(purple_get_blist());
208 		}
209 	}
210 
211 	if(event->changed_mask & GDK_WINDOW_STATE_MAXIMIZED) {
212 		if(event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED)
213 			purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/list_maximized", TRUE);
214 		else
215 			purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/list_maximized", FALSE);
216 	}
217 
218 	/* Refresh gtkblist if un-iconifying */
219 	if (event->changed_mask & GDK_WINDOW_STATE_ICONIFIED){
220 		if (!(event->new_window_state & GDK_WINDOW_STATE_ICONIFIED))
221 			pidgin_blist_refresh_timer(purple_get_blist());
222 	}
223 
224 	return FALSE;
225 }
226 
gtk_blist_delete_cb(GtkWidget * w,GdkEventAny * event,gpointer data)227 static gboolean gtk_blist_delete_cb(GtkWidget *w, GdkEventAny *event, gpointer data)
228 {
229 	if(visibility_manager_count)
230 		purple_blist_set_visible(FALSE);
231 	else
232 		purple_core_quit();
233 
234 	/* we handle everything, event should not propogate further */
235 	return TRUE;
236 }
237 
gtk_blist_configure_cb(GtkWidget * w,GdkEventConfigure * event,gpointer data)238 static gboolean gtk_blist_configure_cb(GtkWidget *w, GdkEventConfigure *event, gpointer data)
239 {
240 	/* unfortunately GdkEventConfigure ignores the window gravity, but  *
241 	 * the only way we have of setting the position doesn't. we have to *
242 	 * call get_position because it does pay attention to the gravity.  *
243 	 * this is inefficient and I agree it sucks, but it's more likely   *
244 	 * to work correctly.                                    - Robot101 */
245 	gint x, y;
246 
247 	/* check for visibility because when we aren't visible, this will   *
248 	 * give us bogus (0,0) coordinates.                      - xOr      */
249 	if (GTK_WIDGET_VISIBLE(w))
250 		gtk_window_get_position(GTK_WINDOW(w), &x, &y);
251 	else
252 		return FALSE; /* carry on normally */
253 
254 #ifdef _WIN32
255 	/* Workaround for GTK+ bug # 169811 - "configure_event" is fired
256 	 * when the window is being maximized */
257 	if (gdk_window_get_state(w->window)
258 			& GDK_WINDOW_STATE_MAXIMIZED) {
259 		return FALSE;
260 	}
261 #endif
262 
263 	/* don't save if nothing changed */
264 	if (x == purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/x") &&
265 		y == purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/y") &&
266 		event->width  == purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/width") &&
267 		event->height == purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/height")) {
268 
269 		return FALSE; /* carry on normally */
270 	}
271 
272 	/* don't save off-screen positioning */
273 	if (x + event->width < 0 ||
274 		y + event->height < 0 ||
275 		x > gdk_screen_width() ||
276 		y > gdk_screen_height()) {
277 
278 		return FALSE; /* carry on normally */
279 	}
280 
281 	/* ignore changes when maximized */
282 	if(purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/list_maximized"))
283 		return FALSE;
284 
285 	/* store the position */
286 	purple_prefs_set_int(PIDGIN_PREFS_ROOT "/blist/x",      x);
287 	purple_prefs_set_int(PIDGIN_PREFS_ROOT "/blist/y",      y);
288 	purple_prefs_set_int(PIDGIN_PREFS_ROOT "/blist/width",  event->width);
289 	purple_prefs_set_int(PIDGIN_PREFS_ROOT "/blist/height", event->height);
290 
291 	/* continue to handle event normally */
292 	return FALSE;
293 }
294 
gtk_blist_menu_info_cb(GtkWidget * w,PurpleBuddy * b)295 static void gtk_blist_menu_info_cb(GtkWidget *w, PurpleBuddy *b)
296 {
297 	PurpleAccount *account = purple_buddy_get_account(b);
298 
299 	pidgin_retrieve_user_info(purple_account_get_connection(account),
300 	                          purple_buddy_get_name(b));
301 }
302 
gtk_blist_menu_im_cb(GtkWidget * w,PurpleBuddy * b)303 static void gtk_blist_menu_im_cb(GtkWidget *w, PurpleBuddy *b)
304 {
305 	pidgin_dialogs_im_with_user(purple_buddy_get_account(b),
306 	                            purple_buddy_get_name(b));
307 }
308 
309 #ifdef USE_VV
gtk_blist_menu_audio_call_cb(GtkWidget * w,PurpleBuddy * b)310 static void gtk_blist_menu_audio_call_cb(GtkWidget *w, PurpleBuddy *b)
311 {
312 	purple_prpl_initiate_media(purple_buddy_get_account(b),
313 		purple_buddy_get_name(b), PURPLE_MEDIA_AUDIO);
314 }
315 
gtk_blist_menu_video_call_cb(GtkWidget * w,PurpleBuddy * b)316 static void gtk_blist_menu_video_call_cb(GtkWidget *w, PurpleBuddy *b)
317 {
318 	/* if the buddy supports both audio and video, start a combined call,
319 	 otherwise start a pure video session */
320 	if (purple_prpl_get_media_caps(purple_buddy_get_account(b),
321 			purple_buddy_get_name(b)) &
322 			PURPLE_MEDIA_CAPS_AUDIO_VIDEO) {
323 		purple_prpl_initiate_media(purple_buddy_get_account(b),
324 			purple_buddy_get_name(b), PURPLE_MEDIA_AUDIO | PURPLE_MEDIA_VIDEO);
325 	} else {
326 		purple_prpl_initiate_media(purple_buddy_get_account(b),
327 			purple_buddy_get_name(b), PURPLE_MEDIA_VIDEO);
328 	}
329 }
330 
331 #endif
332 
gtk_blist_menu_send_file_cb(GtkWidget * w,PurpleBuddy * b)333 static void gtk_blist_menu_send_file_cb(GtkWidget *w, PurpleBuddy *b)
334 {
335 	PurpleAccount *account = purple_buddy_get_account(b);
336 
337 	serv_send_file(purple_account_get_connection(account),
338 	               purple_buddy_get_name(b), NULL);
339 }
340 
gtk_blist_menu_move_to_cb(GtkWidget * w,PurpleBlistNode * node)341 static void gtk_blist_menu_move_to_cb(GtkWidget *w, PurpleBlistNode *node)
342 {
343 	PurpleGroup *group = g_object_get_data(G_OBJECT(w), "groupnode");
344 	purple_blist_add_contact((PurpleContact *)node, group, NULL);
345 
346 }
347 
gtk_blist_menu_autojoin_cb(GtkWidget * w,PurpleChat * chat)348 static void gtk_blist_menu_autojoin_cb(GtkWidget *w, PurpleChat *chat)
349 {
350 	purple_blist_node_set_bool((PurpleBlistNode*)chat, "gtk-autojoin",
351 			gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w)));
352 }
353 
gtk_blist_menu_persistent_cb(GtkWidget * w,PurpleChat * chat)354 static void gtk_blist_menu_persistent_cb(GtkWidget *w, PurpleChat *chat)
355 {
356 	purple_blist_node_set_bool((PurpleBlistNode*)chat, "gtk-persistent",
357 			gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w)));
358 }
359 
360 static PurpleConversation *
find_conversation_with_buddy(PurpleBuddy * buddy)361 find_conversation_with_buddy(PurpleBuddy *buddy)
362 {
363 	PidginBlistNode *ui = purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(buddy));
364 	if (ui)
365 		return ui->conv.conv;
366 	return purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
367 									     purple_buddy_get_name(buddy),
368 									     purple_buddy_get_account(buddy));
369 }
370 
gtk_blist_join_chat(PurpleChat * chat)371 static void gtk_blist_join_chat(PurpleChat *chat)
372 {
373 	PurpleAccount *account;
374 	PurpleConversation *conv;
375 	PurplePluginProtocolInfo *prpl_info;
376 	GHashTable *components;
377 	const char *name;
378 	char *chat_name;
379 
380 	account = purple_chat_get_account(chat);
381 	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_find_prpl(purple_account_get_protocol_id(account)));
382 
383 	components = purple_chat_get_components(chat);
384 
385 	if (prpl_info && prpl_info->get_chat_name)
386 		chat_name = prpl_info->get_chat_name(components);
387 	else
388 		chat_name = NULL;
389 
390 	if (chat_name)
391 		name = chat_name;
392 	else
393 		name = purple_chat_get_name(chat);
394 
395 	conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, name,
396 	                                             account);
397 
398 	if (conv != NULL) {
399 		pidgin_conv_attach_to_conversation(conv);
400 		purple_conversation_present(conv);
401 	}
402 
403 	serv_join_chat(purple_account_get_connection(account), components);
404 	g_free(chat_name);
405 }
406 
gtk_blist_menu_join_cb(GtkWidget * w,PurpleChat * chat)407 static void gtk_blist_menu_join_cb(GtkWidget *w, PurpleChat *chat)
408 {
409 	gtk_blist_join_chat(chat);
410 }
411 
gtk_blist_renderer_editing_cancelled_cb(GtkCellRenderer * renderer,PurpleBuddyList * list)412 static void gtk_blist_renderer_editing_cancelled_cb(GtkCellRenderer *renderer, PurpleBuddyList *list)
413 {
414 	editing_blist = FALSE;
415 	g_object_set(G_OBJECT(renderer), "editable", FALSE, NULL);
416 	pidgin_blist_refresh(list);
417 }
418 
gtk_blist_renderer_editing_started_cb(GtkCellRenderer * renderer,GtkCellEditable * editable,gchar * path_str,gpointer user_data)419 static void gtk_blist_renderer_editing_started_cb(GtkCellRenderer *renderer,
420 		GtkCellEditable *editable,
421 		gchar *path_str,
422 		gpointer user_data)
423 {
424 	GtkTreeIter iter;
425 	GtkTreePath *path = NULL;
426 	PurpleBlistNode *node;
427 	const char *text = NULL;
428 
429 	path = gtk_tree_path_new_from_string (path_str);
430 	gtk_tree_model_get_iter (GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
431 	gtk_tree_path_free (path);
432 	gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
433 
434 	switch (purple_blist_node_get_type(node)) {
435 	case PURPLE_BLIST_CONTACT_NODE:
436 		text = purple_contact_get_alias(PURPLE_CONTACT(node));
437 		break;
438 	case PURPLE_BLIST_BUDDY_NODE:
439 		text = purple_buddy_get_alias(PURPLE_BUDDY(node));
440 		break;
441 	case PURPLE_BLIST_GROUP_NODE:
442 		text = purple_group_get_name(PURPLE_GROUP(node));
443 		break;
444 	case PURPLE_BLIST_CHAT_NODE:
445 		text = purple_chat_get_name(PURPLE_CHAT(node));
446 		break;
447 	default:
448 		g_return_if_reached();
449 	}
450 
451 	if (GTK_IS_ENTRY (editable)) {
452 		GtkEntry *entry = GTK_ENTRY (editable);
453 		gtk_entry_set_text(entry, text);
454 	}
455 	editing_blist = TRUE;
456 }
457 
458 static void
gtk_blist_do_personize(GList * merges)459 gtk_blist_do_personize(GList *merges)
460 {
461 	PurpleBlistNode *contact = NULL;
462 	int max = 0;
463 	GList *tmp;
464 
465 	/* First, we find the contact to merge the rest of the buddies into.
466  	 * This will be the contact with the most buddies in it; ties are broken
467  	 * by which contact is higher in the list
468  	 */
469 	for (tmp = merges; tmp; tmp = tmp->next) {
470 		PurpleBlistNode *node = tmp->data;
471 		PurpleBlistNode *b;
472 		PurpleBlistNodeType type;
473 		int i = 0;
474 
475 		type = purple_blist_node_get_type(node);
476 
477 		if (type == PURPLE_BLIST_BUDDY_NODE) {
478 			node = purple_blist_node_get_parent(node);
479 			type = purple_blist_node_get_type(node);
480 		}
481 
482 		if (type != PURPLE_BLIST_CONTACT_NODE)
483 			continue;
484 
485 		for (b = purple_blist_node_get_first_child(node);
486 		     b;
487 		     b = purple_blist_node_get_sibling_next(b))
488 		{
489 			i++;
490 		}
491 
492 		if (i > max) {
493 			contact = node;
494 			max = i;
495 		}
496 	}
497 
498 	if (contact == NULL)
499 		return;
500 
501 	/* Merge all those buddies into this contact */
502 	for (tmp = merges; tmp; tmp = tmp->next) {
503 		PurpleBlistNode *node = tmp->data;
504 		if (purple_blist_node_get_type(node) == PURPLE_BLIST_BUDDY_NODE)
505 			node = purple_blist_node_get_parent(node);
506 
507 		if (node == contact)
508 			continue;
509 
510 		purple_blist_merge_contact((PurpleContact *)node, contact);
511 	}
512 
513 	/* And show the expanded contact, so the people know what's going on */
514 	pidgin_blist_expand_contact_cb(NULL, contact);
515 	g_list_free(merges);
516 }
517 
518 static void
gtk_blist_auto_personize(PurpleBlistNode * group,const char * alias)519 gtk_blist_auto_personize(PurpleBlistNode *group, const char *alias)
520 {
521 	PurpleBlistNode *contact;
522 	PurpleBlistNode *buddy;
523 	GList *merges = NULL;
524 	int i = 0;
525 	char *a = g_utf8_casefold(alias, -1);
526 
527 	for (contact = purple_blist_node_get_first_child(group);
528 	     contact != NULL;
529 	     contact = purple_blist_node_get_sibling_next(contact)) {
530 		char *node_alias;
531 		if (purple_blist_node_get_type(contact) != PURPLE_BLIST_CONTACT_NODE)
532 			continue;
533 
534 		node_alias = g_utf8_casefold(purple_contact_get_alias((PurpleContact *)contact), -1);
535 		if (node_alias && !g_utf8_collate(node_alias, a)) {
536 			merges = g_list_append(merges, contact);
537 			i++;
538 			g_free(node_alias);
539 			continue;
540 		}
541 		g_free(node_alias);
542 
543 		for (buddy = purple_blist_node_get_first_child(contact);
544 		     buddy;
545 		     buddy = purple_blist_node_get_sibling_next(buddy))
546 		{
547 			if (purple_blist_node_get_type(buddy) != PURPLE_BLIST_BUDDY_NODE)
548 				continue;
549 
550 			node_alias = g_utf8_casefold(purple_buddy_get_alias(PURPLE_BUDDY(buddy)), -1);
551 			if (node_alias && !g_utf8_collate(node_alias, a)) {
552 				merges = g_list_append(merges, buddy);
553 				i++;
554 				g_free(node_alias);
555 				break;
556 			}
557 			g_free(node_alias);
558 		}
559 	}
560 	g_free(a);
561 
562 	if (i > 1)
563 	{
564 		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);
565 		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. "
566 							 "You can separate them again by choosing 'Expand' from the contact's context menu"), 0, NULL, NULL, NULL,
567 				      merges, 2, _("_Yes"), PURPLE_CALLBACK(gtk_blist_do_personize), _("_No"), PURPLE_CALLBACK(g_list_free));
568 		g_free(msg);
569 	} else
570 		g_list_free(merges);
571 }
572 
gtk_blist_renderer_edited_cb(GtkCellRendererText * text_rend,char * arg1,char * arg2,PurpleBuddyList * list)573 static void gtk_blist_renderer_edited_cb(GtkCellRendererText *text_rend, char *arg1,
574 					 char *arg2, PurpleBuddyList *list)
575 {
576 	GtkTreeIter iter;
577 	GtkTreePath *path;
578 	PurpleBlistNode *node;
579 	PurpleGroup *dest;
580 
581 	editing_blist = FALSE;
582 	path = gtk_tree_path_new_from_string (arg1);
583 	gtk_tree_model_get_iter (GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
584 	gtk_tree_path_free (path);
585 	gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
586 	gtk_tree_view_set_enable_search (GTK_TREE_VIEW(gtkblist->treeview), TRUE);
587 	g_object_set(G_OBJECT(gtkblist->text_rend), "editable", FALSE, NULL);
588 
589 	switch (purple_blist_node_get_type(node))
590 	{
591 		case PURPLE_BLIST_CONTACT_NODE:
592 			{
593 				PurpleContact *contact = PURPLE_CONTACT(node);
594 				struct _pidgin_blist_node *gtknode =
595 					(struct _pidgin_blist_node *)purple_blist_node_get_ui_data(node);
596 
597 				/*
598 				 * XXX Using purple_contact_get_alias here breaks because we
599 				 * specifically want to check the contact alias only (i.e. not
600 				 * the priority buddy, which purple_contact_get_alias does).
601 				 * Adding yet another get_alias is evil, so figure this out
602 				 * later :-P
603 				 */
604 				if (contact->alias || gtknode->contact_expanded) {
605 					purple_blist_alias_contact(contact, arg2);
606 					gtk_blist_auto_personize(purple_blist_node_get_parent(node), arg2);
607 				} else {
608 					PurpleBuddy *buddy = purple_contact_get_priority_buddy(contact);
609 					purple_blist_alias_buddy(buddy, arg2);
610 					serv_alias_buddy(buddy);
611 					gtk_blist_auto_personize(purple_blist_node_get_parent(node), arg2);
612 				}
613 			}
614 			break;
615 
616 		case PURPLE_BLIST_BUDDY_NODE:
617 			{
618 				PurpleGroup *group = purple_buddy_get_group(PURPLE_BUDDY(node));
619 
620 				purple_blist_alias_buddy(PURPLE_BUDDY(node), arg2);
621 				serv_alias_buddy(PURPLE_BUDDY(node));
622 				gtk_blist_auto_personize(PURPLE_BLIST_NODE(group), arg2);
623 			}
624 			break;
625 		case PURPLE_BLIST_GROUP_NODE:
626 			dest = purple_find_group(arg2);
627 			if (dest != NULL && purple_utf8_strcasecmp(arg2, purple_group_get_name(PURPLE_GROUP(node)))) {
628 				pidgin_dialogs_merge_groups(PURPLE_GROUP(node), arg2);
629 			} else {
630 				purple_blist_rename_group(PURPLE_GROUP(node), arg2);
631 			}
632 			break;
633 		case PURPLE_BLIST_CHAT_NODE:
634 			purple_blist_alias_chat(PURPLE_CHAT(node), arg2);
635 			break;
636 		default:
637 			break;
638 	}
639 	pidgin_blist_refresh(list);
640 }
641 
642 static void
chat_components_edit_ok(PurpleChat * chat,PurpleRequestFields * allfields)643 chat_components_edit_ok(PurpleChat *chat, PurpleRequestFields *allfields)
644 {
645 	GList *groups, *fields;
646 
647 	for (groups = purple_request_fields_get_groups(allfields); groups; groups = groups->next) {
648 		fields = purple_request_field_group_get_fields(groups->data);
649 		for (; fields; fields = fields->next) {
650 			PurpleRequestField *field = fields->data;
651 			const char *id;
652 			char *val;
653 
654 			id = purple_request_field_get_id(field);
655 			if (purple_request_field_get_type(field) == PURPLE_REQUEST_FIELD_INTEGER)
656 				val = g_strdup_printf("%d", purple_request_field_int_get_value(field));
657 			else
658 				val = g_strdup(purple_request_field_string_get_value(field));
659 
660 			if (!val) {
661 				g_hash_table_remove(purple_chat_get_components(chat), id);
662 			} else {
663 				g_hash_table_replace(purple_chat_get_components(chat), g_strdup(id), val);  /* val should not be free'd */
664 			}
665 		}
666 	}
667 }
668 
chat_components_edit(GtkWidget * w,PurpleBlistNode * node)669 static void chat_components_edit(GtkWidget *w, PurpleBlistNode *node)
670 {
671 	PurpleRequestFields *fields = purple_request_fields_new();
672 	PurpleRequestFieldGroup *group = purple_request_field_group_new(NULL);
673 	PurpleRequestField *field;
674 	GList *parts, *iter;
675 	struct proto_chat_entry *pce;
676 	PurpleConnection *gc;
677 	PurpleChat *chat = (PurpleChat*)node;
678 
679 	purple_request_fields_add_group(fields, group);
680 
681 	gc = purple_account_get_connection(purple_chat_get_account(chat));
682 	parts = PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc))->chat_info(gc);
683 
684 	for (iter = parts; iter; iter = iter->next) {
685 		pce = iter->data;
686 		if (pce->is_int) {
687 			int val;
688 			const char *str = g_hash_table_lookup(purple_chat_get_components(chat), pce->identifier);
689 			if (!str || sscanf(str, "%d", &val) != 1)
690 				val = pce->min;
691 			field = purple_request_field_int_new(pce->identifier, pce->label, val);
692 		} else {
693 			field = purple_request_field_string_new(pce->identifier, pce->label,
694 					g_hash_table_lookup(purple_chat_get_components(chat), pce->identifier), FALSE);
695 			if (pce->secret)
696 				purple_request_field_string_set_masked(field, TRUE);
697 		}
698 
699 		if (pce->required)
700 			purple_request_field_set_required(field, TRUE);
701 
702 		purple_request_field_group_add_field(group, field);
703 		g_free(pce);
704 	}
705 
706 	g_list_free(parts);
707 
708 	purple_request_fields(NULL, _("Edit Chat"), NULL, _("Please update the necessary fields."),
709 			fields, _("Save"), G_CALLBACK(chat_components_edit_ok), _("Cancel"), NULL,
710 			NULL, NULL, NULL,
711 			chat);
712 }
713 
gtk_blist_menu_alias_cb(GtkWidget * w,PurpleBlistNode * node)714 static void gtk_blist_menu_alias_cb(GtkWidget *w, PurpleBlistNode *node)
715 {
716 	GtkTreeIter iter;
717 	GtkTreePath *path;
718 
719 	if (!(get_iter_from_node(node, &iter))) {
720 		/* This is either a bug, or the buddy is in a collapsed contact */
721 		node = purple_blist_node_get_parent(node);
722 		if (!get_iter_from_node(node, &iter))
723 			/* Now it's definitely a bug */
724 			return;
725 	}
726 
727 	pidgin_blist_tooltip_destroy();
728 
729 	path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &iter);
730 	g_object_set(G_OBJECT(gtkblist->text_rend), "editable", TRUE, NULL);
731 	gtk_tree_view_set_enable_search (GTK_TREE_VIEW(gtkblist->treeview), FALSE);
732 	gtk_widget_grab_focus(gtkblist->treeview);
733 	gtk_tree_view_set_cursor_on_cell(GTK_TREE_VIEW(gtkblist->treeview), path,
734 			gtkblist->text_column, gtkblist->text_rend, TRUE);
735 	gtk_tree_path_free(path);
736 }
737 
gtk_blist_menu_bp_cb(GtkWidget * w,PurpleBuddy * b)738 static void gtk_blist_menu_bp_cb(GtkWidget *w, PurpleBuddy *b)
739 {
740 	pidgin_pounce_editor_show(purple_buddy_get_account(b),
741 	                          purple_buddy_get_name(b), NULL);
742 }
743 
gtk_blist_menu_showlog_cb(GtkWidget * w,PurpleBlistNode * node)744 static void gtk_blist_menu_showlog_cb(GtkWidget *w, PurpleBlistNode *node)
745 {
746 	PurpleLogType type;
747 	PurpleAccount *account;
748 	char *name = NULL;
749 
750 	pidgin_set_cursor(gtkblist->window, GDK_WATCH);
751 
752 	if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
753 		PurpleBuddy *b = (PurpleBuddy*) node;
754 		type = PURPLE_LOG_IM;
755 		name = g_strdup(purple_buddy_get_name(b));
756 		account = purple_buddy_get_account(b);
757 	} else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
758 		PurpleChat *c = PURPLE_CHAT(node);
759 		PurplePluginProtocolInfo *prpl_info = NULL;
760 		type = PURPLE_LOG_CHAT;
761 		account = purple_chat_get_account(c);
762 		prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_find_prpl(purple_account_get_protocol_id(account)));
763 		if (prpl_info && prpl_info->get_chat_name) {
764 			name = prpl_info->get_chat_name(purple_chat_get_components(c));
765 		}
766 	} else if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
767 		pidgin_log_show_contact(PURPLE_CONTACT(node));
768 		pidgin_clear_cursor(gtkblist->window);
769 		return;
770 	} else {
771 		pidgin_clear_cursor(gtkblist->window);
772 
773 		/* This callback should not have been registered for a node
774 		 * that doesn't match the type of one of the blocks above. */
775 		g_return_if_reached();
776 	}
777 
778 	if (name && account) {
779 		pidgin_log_show(type, name, account);
780 		pidgin_clear_cursor(gtkblist->window);
781 	}
782 
783 	g_free(name);
784 }
785 
gtk_blist_menu_showoffline_cb(GtkWidget * w,PurpleBlistNode * node)786 static void gtk_blist_menu_showoffline_cb(GtkWidget *w, PurpleBlistNode *node)
787 {
788 	if (PURPLE_BLIST_NODE_IS_BUDDY(node))
789 	{
790 		purple_blist_node_set_bool(node, "show_offline",
791 		                           !purple_blist_node_get_bool(node, "show_offline"));
792 		pidgin_blist_update(purple_get_blist(), node);
793 	}
794 	else if (PURPLE_BLIST_NODE_IS_CONTACT(node))
795 	{
796 		PurpleBlistNode *bnode;
797 		gboolean setting = !purple_blist_node_get_bool(node, "show_offline");
798 
799 		purple_blist_node_set_bool(node, "show_offline", setting);
800 		for (bnode = purple_blist_node_get_first_child(node);
801 		     bnode != NULL;
802 		     bnode = purple_blist_node_get_sibling_next(bnode))
803 		{
804 			purple_blist_node_set_bool(bnode, "show_offline", setting);
805 			pidgin_blist_update(purple_get_blist(), bnode);
806 		}
807 	} else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
808 		PurpleBlistNode *cnode, *bnode;
809 		gboolean setting = !purple_blist_node_get_bool(node, "show_offline");
810 
811 		purple_blist_node_set_bool(node, "show_offline", setting);
812 		for (cnode = purple_blist_node_get_first_child(node);
813 		     cnode != NULL;
814 		     cnode = purple_blist_node_get_sibling_next(cnode))
815 		{
816 			purple_blist_node_set_bool(cnode, "show_offline", setting);
817 			for (bnode = purple_blist_node_get_first_child(cnode);
818 			     bnode != NULL;
819 			     bnode = purple_blist_node_get_sibling_next(bnode))
820 			{
821 				purple_blist_node_set_bool(bnode, "show_offline", setting);
822 				pidgin_blist_update(purple_get_blist(), bnode);
823 			}
824 		}
825 	}
826 }
827 
gtk_blist_show_systemlog_cb(void)828 static void gtk_blist_show_systemlog_cb(void)
829 {
830 	pidgin_syslog_show();
831 }
832 
gtk_blist_show_onlinehelp_cb(void)833 static void gtk_blist_show_onlinehelp_cb(void)
834 {
835 	purple_notify_uri(NULL, PURPLE_WEBSITE "documentation");
836 }
837 
838 static void
gtk_blist_donate_cb(void)839 gtk_blist_donate_cb(void) {
840 	purple_notify_uri(NULL, "https://imfreedom.org/donate/");
841 }
842 
843 static void
do_join_chat(PidginChatData * data)844 do_join_chat(PidginChatData *data)
845 {
846 	if (data)
847 	{
848 		GHashTable *components =
849 			g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
850 		GList *tmp;
851 		PurpleChat *chat;
852 
853 		for (tmp = data->entries; tmp != NULL; tmp = tmp->next)
854 		{
855 			if (g_object_get_data(tmp->data, "is_spin"))
856 			{
857 				g_hash_table_replace(components,
858 					g_strdup(g_object_get_data(tmp->data, "identifier")),
859 					g_strdup_printf("%d",
860 							gtk_spin_button_get_value_as_int(tmp->data)));
861 			}
862 			else
863 			{
864 				g_hash_table_replace(components,
865 					g_strdup(g_object_get_data(tmp->data, "identifier")),
866 					g_strdup(gtk_entry_get_text(tmp->data)));
867 			}
868 		}
869 
870 		chat = purple_chat_new(data->rq_data.account, NULL, components);
871 		gtk_blist_join_chat(chat);
872 		purple_blist_remove_chat(chat);
873 	}
874 }
875 
876 static void
do_joinchat(GtkWidget * dialog,int id,PidginChatData * info)877 do_joinchat(GtkWidget *dialog, int id, PidginChatData *info)
878 {
879 	switch(id)
880 	{
881 		case GTK_RESPONSE_OK:
882 			do_join_chat(info);
883 			break;
884 
885 		case 1:
886 			pidgin_roomlist_dialog_show_with_account(info->rq_data.account);
887 			return;
888 
889 		break;
890 	}
891 
892 	gtk_widget_destroy(GTK_WIDGET(dialog));
893 	g_list_free(info->entries);
894 	g_free(info);
895 }
896 
897 /*
898  * Check the values of all the text entry boxes.  If any required input
899  * strings are empty then don't allow the user to click on "OK."
900  */
901 static void
set_sensitive_if_input_cb(GtkWidget * entry,gpointer user_data)902 set_sensitive_if_input_cb(GtkWidget *entry, gpointer user_data)
903 {
904 	PurplePluginProtocolInfo *prpl_info;
905 	PurpleConnection *gc;
906 	PidginChatData *data;
907 	GList *tmp;
908 	const char *text;
909 	gboolean required;
910 	gboolean sensitive = TRUE;
911 
912 	data = user_data;
913 
914 	for (tmp = data->entries; tmp != NULL; tmp = tmp->next)
915 	{
916 		if (!g_object_get_data(tmp->data, "is_spin"))
917 		{
918 			required = GPOINTER_TO_INT(g_object_get_data(tmp->data, "required"));
919 			text = gtk_entry_get_text(tmp->data);
920 			if (required && (*text == '\0'))
921 				sensitive = FALSE;
922 		}
923 	}
924 
925 	gtk_dialog_set_response_sensitive(GTK_DIALOG(data->rq_data.window), GTK_RESPONSE_OK, sensitive);
926 
927 	gc = purple_account_get_connection(data->rq_data.account);
928 	prpl_info = (gc != NULL) ? PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl) : NULL;
929 	sensitive = (prpl_info != NULL && prpl_info->roomlist_get_list != NULL);
930 
931 	gtk_dialog_set_response_sensitive(GTK_DIALOG(data->rq_data.window), 1, sensitive);
932 }
933 
934 static void
pidgin_blist_update_privacy_cb(PurpleBuddy * buddy)935 pidgin_blist_update_privacy_cb(PurpleBuddy *buddy)
936 {
937 	struct _pidgin_blist_node *ui_data = purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(buddy));
938 	if (ui_data == NULL || ui_data->row == NULL)
939 		return;
940 	pidgin_blist_update_buddy(purple_get_blist(), PURPLE_BLIST_NODE(buddy), TRUE);
941 }
942 
943 static gboolean
add_buddy_account_filter_func(PurpleAccount * account)944 add_buddy_account_filter_func(PurpleAccount *account)
945 {
946 	PurpleConnection *gc = purple_account_get_connection(account);
947 	PurplePluginProtocolInfo *prpl_info = NULL;
948 
949 	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
950 
951 	return (prpl_info->add_buddy != NULL);
952 }
953 
954 static gboolean
chat_account_filter_func(PurpleAccount * account)955 chat_account_filter_func(PurpleAccount *account)
956 {
957 	PurpleConnection *gc = purple_account_get_connection(account);
958 	PurplePluginProtocolInfo *prpl_info = NULL;
959 
960 	if (gc == NULL)
961 		return FALSE;
962 
963 	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
964 
965 	return (prpl_info->chat_info != NULL);
966 }
967 
968 gboolean
pidgin_blist_joinchat_is_showable()969 pidgin_blist_joinchat_is_showable()
970 {
971 	GList *c;
972 	PurpleConnection *gc;
973 
974 	for (c = purple_connections_get_all(); c != NULL; c = c->next) {
975 		gc = c->data;
976 
977 		if (chat_account_filter_func(purple_connection_get_account(gc)))
978 			return TRUE;
979 	}
980 
981 	return FALSE;
982 }
983 
984 static GtkWidget *
make_blist_request_dialog(PidginBlistRequestData * data,PurpleAccount * account,const char * title,const char * window_role,const char * label_text,GCallback callback_func,PurpleFilterAccountFunc filter_func,GCallback response_cb)985 make_blist_request_dialog(PidginBlistRequestData *data, PurpleAccount *account,
986 	const char *title, const char *window_role, const char *label_text,
987 	GCallback callback_func, PurpleFilterAccountFunc filter_func,
988 	GCallback response_cb)
989 {
990 	GtkWidget *label;
991 	GtkWidget *img;
992 	GtkWidget *hbox;
993 	GtkWidget *vbox;
994 	GtkWindow *blist_window;
995 	PidginBuddyList *gtkblist;
996 
997 	data->account = account;
998 
999 	img = gtk_image_new_from_stock(PIDGIN_STOCK_DIALOG_QUESTION,
1000 		gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_HUGE));
1001 
1002 	gtkblist = PIDGIN_BLIST(purple_get_blist());
1003 	blist_window = gtkblist ? GTK_WINDOW(gtkblist->window) : NULL;
1004 
1005 	data->window = gtk_dialog_new_with_buttons(title,
1006 		blist_window, GTK_DIALOG_NO_SEPARATOR,
1007 		NULL);
1008 
1009 	gtk_window_set_transient_for(GTK_WINDOW(data->window), blist_window);
1010 	gtk_dialog_set_default_response(GTK_DIALOG(data->window), GTK_RESPONSE_OK);
1011 	gtk_container_set_border_width(GTK_CONTAINER(data->window), PIDGIN_HIG_BOX_SPACE);
1012 	gtk_window_set_resizable(GTK_WINDOW(data->window), FALSE);
1013 	gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(data->window)->vbox), PIDGIN_HIG_BORDER);
1014 	gtk_container_set_border_width(GTK_CONTAINER(GTK_DIALOG(data->window)->vbox), PIDGIN_HIG_BOX_SPACE);
1015 	gtk_window_set_role(GTK_WINDOW(data->window), window_role);
1016 
1017 	hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BORDER);
1018 	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(data->window)->vbox), hbox);
1019 	gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0);
1020 	gtk_misc_set_alignment(GTK_MISC(img), 0, 0);
1021 
1022 	vbox = gtk_vbox_new(FALSE, 5);
1023 	gtk_container_add(GTK_CONTAINER(hbox), vbox);
1024 
1025 	label = gtk_label_new(label_text);
1026 
1027 	gtk_widget_set_size_request(label, 400, -1);
1028 	gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1029 	gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
1030 	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1031 
1032 	data->sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
1033 
1034 	data->account_menu = pidgin_account_option_menu_new(account, FALSE,
1035 			callback_func, filter_func, data);
1036 	pidgin_add_widget_to_vbox(GTK_BOX(vbox), _("A_ccount"), data->sg, data->account_menu, TRUE, NULL);
1037 
1038 	data->vbox = GTK_BOX(gtk_vbox_new(FALSE, 5));
1039 	gtk_container_set_border_width(GTK_CONTAINER(data->vbox), 0);
1040 	gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(data->vbox), FALSE, FALSE, 0);
1041 
1042 	g_signal_connect(G_OBJECT(data->window), "response", response_cb, data);
1043 
1044 	g_object_unref(data->sg);
1045 
1046 	return vbox;
1047 }
1048 
1049 static void
rebuild_chat_entries(PidginChatData * data,const char * default_chat_name)1050 rebuild_chat_entries(PidginChatData *data, const char *default_chat_name)
1051 {
1052 	PurpleConnection *gc;
1053 	GList *list = NULL, *tmp;
1054 	GHashTable *defaults = NULL;
1055 	struct proto_chat_entry *pce;
1056 	gboolean focus = TRUE;
1057 
1058 	g_return_if_fail(data->rq_data.account != NULL);
1059 
1060 	gc = purple_account_get_connection(data->rq_data.account);
1061 
1062 	gtk_container_foreach(GTK_CONTAINER(data->rq_data.vbox), (GtkCallback)gtk_widget_destroy, NULL);
1063 
1064 	g_list_free(data->entries);
1065 	data->entries = NULL;
1066 
1067 	if (PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info != NULL)
1068 		list = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info(gc);
1069 
1070 	if (PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info_defaults != NULL)
1071 		defaults = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info_defaults(gc, default_chat_name);
1072 
1073 	for (tmp = list; tmp; tmp = tmp->next)
1074 	{
1075 		GtkWidget *input;
1076 
1077 		pce = tmp->data;
1078 
1079 		if (pce->is_int)
1080 		{
1081 			GtkObject *adjust;
1082 			adjust = gtk_adjustment_new(pce->min, pce->min, pce->max,
1083 										1, 10, 10);
1084 			input = gtk_spin_button_new(GTK_ADJUSTMENT(adjust), 1, 0);
1085 			gtk_widget_set_size_request(input, 50, -1);
1086 			pidgin_add_widget_to_vbox(GTK_BOX(data->rq_data.vbox), pce->label, data->rq_data.sg, input, FALSE, NULL);
1087 		}
1088 		else
1089 		{
1090 			char *value;
1091 			input = gtk_entry_new();
1092 			gtk_entry_set_activates_default(GTK_ENTRY(input), TRUE);
1093 			value = g_hash_table_lookup(defaults, pce->identifier);
1094 			if (value != NULL)
1095 				gtk_entry_set_text(GTK_ENTRY(input), value);
1096 			if (pce->secret)
1097 			{
1098 				gtk_entry_set_visibility(GTK_ENTRY(input), FALSE);
1099 #if !GTK_CHECK_VERSION(2,16,0)
1100 				if (gtk_entry_get_invisible_char(GTK_ENTRY(input)) == '*')
1101 					gtk_entry_set_invisible_char(GTK_ENTRY(input), PIDGIN_INVISIBLE_CHAR);
1102 #endif /* Less than GTK+ 2.16 */
1103 			}
1104 			pidgin_add_widget_to_vbox(data->rq_data.vbox, pce->label, data->rq_data.sg, input, TRUE, NULL);
1105 			g_signal_connect(G_OBJECT(input), "changed",
1106 							 G_CALLBACK(set_sensitive_if_input_cb), data);
1107 		}
1108 
1109 		/* Do the following for any type of input widget */
1110 		if (focus)
1111 		{
1112 			gtk_widget_grab_focus(input);
1113 			focus = FALSE;
1114 		}
1115 		g_object_set_data(G_OBJECT(input), "identifier", (gpointer)pce->identifier);
1116 		g_object_set_data(G_OBJECT(input), "is_spin", GINT_TO_POINTER(pce->is_int));
1117 		g_object_set_data(G_OBJECT(input), "required", GINT_TO_POINTER(pce->required));
1118 		data->entries = g_list_append(data->entries, input);
1119 
1120 		g_free(pce);
1121 	}
1122 
1123 	g_list_free(list);
1124 	g_hash_table_destroy(defaults);
1125 
1126 	/* Set whether the "OK" button should be clickable initially */
1127 	set_sensitive_if_input_cb(NULL, data);
1128 
1129 	gtk_widget_show_all(GTK_WIDGET(data->rq_data.vbox));
1130 }
1131 
1132 static void
chat_select_account_cb(GObject * w,PurpleAccount * account,PidginChatData * data)1133 chat_select_account_cb(GObject *w, PurpleAccount *account,
1134                        PidginChatData *data)
1135 {
1136 	if (purple_strequal(purple_account_get_protocol_id(data->rq_data.account),
1137 	                    purple_account_get_protocol_id(account)))
1138 	{
1139 		data->rq_data.account = account;
1140 	}
1141 	else
1142 	{
1143 		data->rq_data.account = account;
1144 		rebuild_chat_entries(data, data->default_chat_name);
1145 	}
1146 }
1147 
1148 void
pidgin_blist_joinchat_show(void)1149 pidgin_blist_joinchat_show(void)
1150 {
1151 	PidginChatData *data = NULL;
1152 
1153 	data = g_new0(PidginChatData, 1);
1154 
1155 	make_blist_request_dialog((PidginBlistRequestData *)data, NULL,
1156 		_("Join a Chat"), "join_chat",
1157 		_("Please enter the appropriate information about the chat "
1158 			"you would like to join.\n"),
1159 		G_CALLBACK(chat_select_account_cb),
1160 		chat_account_filter_func, (GCallback)do_joinchat);
1161 	gtk_dialog_add_buttons(GTK_DIALOG(data->rq_data.window),
1162 		_("Room _List"), 1,
1163 		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1164 		PIDGIN_STOCK_CHAT, GTK_RESPONSE_OK, NULL);
1165 	gtk_dialog_set_default_response(GTK_DIALOG(data->rq_data.window),
1166 		GTK_RESPONSE_OK);
1167 	data->default_chat_name = NULL;
1168 	data->rq_data.account = pidgin_account_option_menu_get_selected(data->rq_data.account_menu);
1169 
1170 	rebuild_chat_entries(data, NULL);
1171 
1172 	gtk_widget_show_all(data->rq_data.window);
1173 }
1174 
gtk_blist_row_expanded_cb(GtkTreeView * tv,GtkTreeIter * iter,GtkTreePath * path,gpointer user_data)1175 static void gtk_blist_row_expanded_cb(GtkTreeView *tv, GtkTreeIter *iter, GtkTreePath *path, gpointer user_data)
1176 {
1177 	PurpleBlistNode *node;
1178 
1179 	gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), iter, NODE_COLUMN, &node, -1);
1180 
1181 	if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
1182 		char *title;
1183 
1184 		title = pidgin_get_group_title(node, TRUE);
1185 
1186 		gtk_tree_store_set(gtkblist->treemodel, iter,
1187 		   NAME_COLUMN, title,
1188 		   -1);
1189 
1190 		g_free(title);
1191 
1192 		purple_blist_node_set_bool(node, "collapsed", FALSE);
1193 		pidgin_blist_tooltip_destroy();
1194 	}
1195 }
1196 
gtk_blist_row_collapsed_cb(GtkTreeView * tv,GtkTreeIter * iter,GtkTreePath * path,gpointer user_data)1197 static void gtk_blist_row_collapsed_cb(GtkTreeView *tv, GtkTreeIter *iter, GtkTreePath *path, gpointer user_data)
1198 {
1199 	PurpleBlistNode *node;
1200 
1201 	gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), iter, NODE_COLUMN, &node, -1);
1202 
1203 	if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
1204 		char *title;
1205 		struct _pidgin_blist_node *gtknode;
1206 		PurpleBlistNode *cnode;
1207 
1208 		title = pidgin_get_group_title(node, FALSE);
1209 
1210 		gtk_tree_store_set(gtkblist->treemodel, iter,
1211 		   NAME_COLUMN, title,
1212 		   -1);
1213 
1214 		g_free(title);
1215 
1216 		purple_blist_node_set_bool(node, "collapsed", TRUE);
1217 
1218 		for(cnode = purple_blist_node_get_first_child(node); cnode; cnode = purple_blist_node_get_sibling_next(cnode)) {
1219 			if (PURPLE_BLIST_NODE_IS_CONTACT(cnode)) {
1220 				gtknode = purple_blist_node_get_ui_data(cnode);
1221 				if (!gtknode->contact_expanded)
1222 					continue;
1223 				gtknode->contact_expanded = FALSE;
1224 				pidgin_blist_update_contact(NULL, cnode);
1225 			}
1226 		}
1227 		pidgin_blist_tooltip_destroy();
1228 	} else if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
1229 		pidgin_blist_collapse_contact_cb(NULL, node);
1230 	}
1231 }
1232 
gtk_blist_row_activated_cb(GtkTreeView * tv,GtkTreePath * path,GtkTreeViewColumn * col,gpointer data)1233 static void gtk_blist_row_activated_cb(GtkTreeView *tv, GtkTreePath *path, GtkTreeViewColumn *col, gpointer data) {
1234 	PurpleBlistNode *node;
1235 	GtkTreeIter iter;
1236 
1237 	gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
1238 	gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
1239 
1240 	if(PURPLE_BLIST_NODE_IS_CONTACT(node) || PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1241 		PurpleBuddy *buddy;
1242 
1243 		if(PURPLE_BLIST_NODE_IS_CONTACT(node))
1244 			buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
1245 		else
1246 			buddy = (PurpleBuddy*)node;
1247 
1248 		pidgin_dialogs_im_with_user(purple_buddy_get_account(buddy), purple_buddy_get_name(buddy));
1249 	} else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
1250 		gtk_blist_join_chat((PurpleChat *)node);
1251 	} else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
1252 /*		if (gtk_tree_view_row_expanded(tv, path))
1253 			gtk_tree_view_collapse_row(tv, path);
1254 		else
1255 			gtk_tree_view_expand_row(tv,path,FALSE);*/
1256 	}
1257 }
1258 
pidgin_blist_add_chat_cb(void)1259 static void pidgin_blist_add_chat_cb(void)
1260 {
1261 	GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist->treeview));
1262 	GtkTreeIter iter;
1263 	PurpleBlistNode *node;
1264 
1265 	if(gtk_tree_selection_get_selected(sel, NULL, &iter)){
1266 		gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
1267 		if (PURPLE_BLIST_NODE_IS_BUDDY(node))
1268 			purple_blist_request_add_chat(NULL, purple_buddy_get_group(PURPLE_BUDDY(node)), NULL, NULL);
1269 		if (PURPLE_BLIST_NODE_IS_CONTACT(node) || PURPLE_BLIST_NODE_IS_CHAT(node))
1270 			purple_blist_request_add_chat(NULL, purple_contact_get_group(PURPLE_CONTACT(node)), NULL, NULL);
1271 		else if (PURPLE_BLIST_NODE_IS_GROUP(node))
1272 			purple_blist_request_add_chat(NULL, (PurpleGroup*)node, NULL, NULL);
1273 	}
1274 	else {
1275 		purple_blist_request_add_chat(NULL, NULL, NULL, NULL);
1276 	}
1277 }
1278 
pidgin_blist_add_buddy_cb(void)1279 static void pidgin_blist_add_buddy_cb(void)
1280 {
1281 	GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist->treeview));
1282 	GtkTreeIter iter;
1283 	PurpleBlistNode *node;
1284 
1285 	if(gtk_tree_selection_get_selected(sel, NULL, &iter)){
1286 		gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
1287 		if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1288 			PurpleGroup *group = purple_buddy_get_group(PURPLE_BUDDY(node));
1289 			purple_blist_request_add_buddy(NULL, NULL, purple_group_get_name(group), NULL);
1290 		} else if (PURPLE_BLIST_NODE_IS_CONTACT(node) || PURPLE_BLIST_NODE_IS_CHAT(node)) {
1291 			PurpleGroup *group = purple_contact_get_group(PURPLE_CONTACT(node));
1292 			purple_blist_request_add_buddy(NULL, NULL, purple_group_get_name(group), NULL);
1293 		} else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
1294 			purple_blist_request_add_buddy(NULL, NULL, purple_group_get_name(PURPLE_GROUP(node)), NULL);
1295 		}
1296 	}
1297 	else {
1298 		purple_blist_request_add_buddy(NULL, NULL, NULL, NULL);
1299 	}
1300 }
1301 
1302 static void
pidgin_blist_remove_cb(GtkWidget * w,PurpleBlistNode * node)1303 pidgin_blist_remove_cb (GtkWidget *w, PurpleBlistNode *node)
1304 {
1305 	if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1306 		pidgin_dialogs_remove_buddy((PurpleBuddy*)node);
1307 	} else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
1308 		pidgin_dialogs_remove_chat((PurpleChat*)node);
1309 	} else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
1310 		pidgin_dialogs_remove_group((PurpleGroup*)node);
1311 	} else if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
1312 		pidgin_dialogs_remove_contact((PurpleContact*)node);
1313 	}
1314 }
1315 
1316 struct _expand {
1317 	GtkTreeView *treeview;
1318 	GtkTreePath *path;
1319 	PurpleBlistNode *node;
1320 };
1321 
1322 static gboolean
scroll_to_expanded_cell(gpointer data)1323 scroll_to_expanded_cell(gpointer data)
1324 {
1325 	struct _expand *ex = data;
1326 	gtk_tree_view_scroll_to_cell(ex->treeview, ex->path, NULL, FALSE, 0, 0);
1327 	pidgin_blist_update_contact(NULL, ex->node);
1328 
1329 	gtk_tree_path_free(ex->path);
1330 	g_free(ex);
1331 
1332 	return FALSE;
1333 }
1334 
1335 static void
pidgin_blist_expand_contact_cb(GtkWidget * w,PurpleBlistNode * node)1336 pidgin_blist_expand_contact_cb(GtkWidget *w, PurpleBlistNode *node)
1337 {
1338 	struct _pidgin_blist_node *gtknode;
1339 	GtkTreeIter iter, parent;
1340 	PurpleBlistNode *bnode;
1341 	GtkTreePath *path;
1342 
1343 	if(!PURPLE_BLIST_NODE_IS_CONTACT(node))
1344 		return;
1345 
1346 	gtknode = purple_blist_node_get_ui_data(node);
1347 
1348 	gtknode->contact_expanded = TRUE;
1349 
1350 	for(bnode = purple_blist_node_get_first_child(node); bnode; bnode = purple_blist_node_get_sibling_next(bnode)) {
1351 		pidgin_blist_update(NULL, bnode);
1352 	}
1353 
1354 	/* This ensures that the bottom buddy is visible, i.e. not scrolled off the alignment */
1355 	if (get_iter_from_node(node, &parent)) {
1356 		struct _expand *ex = g_new0(struct _expand, 1);
1357 
1358 		gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(gtkblist->treemodel), &iter, &parent,
1359 				  gtk_tree_model_iter_n_children(GTK_TREE_MODEL(gtkblist->treemodel), &parent) -1);
1360 		path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &iter);
1361 
1362 		/* Let the treeview draw so it knows where to scroll */
1363 		ex->treeview = GTK_TREE_VIEW(gtkblist->treeview);
1364 		ex->path = path;
1365 		ex->node = purple_blist_node_get_first_child(node);
1366 		g_idle_add(scroll_to_expanded_cell, ex);
1367 	}
1368 }
1369 
1370 static void
pidgin_blist_collapse_contact_cb(GtkWidget * w,PurpleBlistNode * node)1371 pidgin_blist_collapse_contact_cb(GtkWidget *w, PurpleBlistNode *node)
1372 {
1373 	PurpleBlistNode *bnode;
1374 	struct _pidgin_blist_node *gtknode;
1375 
1376 	if(!PURPLE_BLIST_NODE_IS_CONTACT(node))
1377 		return;
1378 
1379 	gtknode = purple_blist_node_get_ui_data(node);
1380 
1381 	gtknode->contact_expanded = FALSE;
1382 
1383 	for(bnode = purple_blist_node_get_first_child(node); bnode; bnode = purple_blist_node_get_sibling_next(bnode)) {
1384 		pidgin_blist_update(NULL, bnode);
1385 	}
1386 }
1387 
1388 static void
toggle_privacy(GtkWidget * widget,PurpleBlistNode * node)1389 toggle_privacy(GtkWidget *widget, PurpleBlistNode *node)
1390 {
1391 	PurpleBuddy *buddy;
1392 	PurpleAccount *account;
1393 	gboolean permitted;
1394 	const char *name;
1395 
1396 	if (!PURPLE_BLIST_NODE_IS_BUDDY(node))
1397 		return;
1398 
1399 	buddy = (PurpleBuddy *)node;
1400 	account = purple_buddy_get_account(buddy);
1401 	name = purple_buddy_get_name(buddy);
1402 
1403 	permitted = purple_privacy_check(account, name);
1404 
1405 	/* XXX: Perhaps ask whether to restore the previous lists where appropirate? */
1406 
1407 	if (permitted)
1408 		purple_privacy_deny(account, name, FALSE, FALSE);
1409 	else
1410 		purple_privacy_allow(account, name, FALSE, FALSE);
1411 
1412 	pidgin_blist_update(purple_get_blist(), node);
1413 }
1414 
pidgin_append_blist_node_privacy_menu(GtkWidget * menu,PurpleBlistNode * node)1415 void pidgin_append_blist_node_privacy_menu(GtkWidget *menu, PurpleBlistNode *node)
1416 {
1417 	PurpleBuddy *buddy = (PurpleBuddy *)node;
1418 	PurpleAccount *account;
1419 	gboolean permitted;
1420 
1421 	account = purple_buddy_get_account(buddy);
1422 	permitted = purple_privacy_check(account, purple_buddy_get_name(buddy));
1423 
1424 	pidgin_new_item_from_stock(menu, permitted ? _("_Block") : _("Un_block"),
1425 						permitted ? PIDGIN_STOCK_TOOLBAR_BLOCK : PIDGIN_STOCK_TOOLBAR_UNBLOCK, G_CALLBACK(toggle_privacy),
1426 						node, 0 ,0, NULL);
1427 }
1428 
1429 void
pidgin_append_blist_node_proto_menu(GtkWidget * menu,PurpleConnection * gc,PurpleBlistNode * node)1430 pidgin_append_blist_node_proto_menu(GtkWidget *menu, PurpleConnection *gc,
1431                                       PurpleBlistNode *node)
1432 {
1433 	GList *l, *ll;
1434 	PurplePluginProtocolInfo *prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
1435 
1436 	if(!prpl_info || !prpl_info->blist_node_menu)
1437 		return;
1438 
1439 	for(l = ll = prpl_info->blist_node_menu(node); l; l = l->next) {
1440 		PurpleMenuAction *act = (PurpleMenuAction *) l->data;
1441 		pidgin_append_menu_action(menu, act, node);
1442 	}
1443 	g_list_free(ll);
1444 }
1445 
1446 void
pidgin_append_blist_node_extended_menu(GtkWidget * menu,PurpleBlistNode * node)1447 pidgin_append_blist_node_extended_menu(GtkWidget *menu, PurpleBlistNode *node)
1448 {
1449 	GList *l, *ll;
1450 
1451 	for(l = ll = purple_blist_node_get_extended_menu(node); l; l = l->next) {
1452 		PurpleMenuAction *act = (PurpleMenuAction *) l->data;
1453 		pidgin_append_menu_action(menu, act, node);
1454 	}
1455 	g_list_free(ll);
1456 }
1457 
1458 
1459 
1460 static void
pidgin_append_blist_node_move_to_menu(GtkWidget * menu,PurpleBlistNode * node)1461 pidgin_append_blist_node_move_to_menu(GtkWidget *menu, PurpleBlistNode *node)
1462 {
1463 	GtkWidget *submenu;
1464 	GtkWidget *menuitem;
1465 	PurpleBlistNode *group;
1466 
1467 	menuitem = gtk_menu_item_new_with_label(_("Move to"));
1468 	gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1469 	gtk_widget_show(menuitem);
1470 
1471 	submenu = gtk_menu_new();
1472 	gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
1473 
1474 	for (group = purple_blist_get_root(); group; group = purple_blist_node_get_sibling_next(group)) {
1475 		if (!PURPLE_BLIST_NODE_IS_GROUP(group))
1476 			continue;
1477 		if (group == purple_blist_node_get_parent(node))
1478 			continue;
1479 		menuitem = pidgin_new_item_from_stock(submenu, purple_group_get_name((PurpleGroup *)group), NULL,
1480 						      G_CALLBACK(gtk_blist_menu_move_to_cb), node, 0, 0, NULL);
1481 		g_object_set_data(G_OBJECT(menuitem), "groupnode", group);
1482 	}
1483 	gtk_widget_show_all(submenu);
1484 }
1485 
1486 void
pidgin_blist_make_buddy_menu(GtkWidget * menu,PurpleBuddy * buddy,gboolean sub)1487 pidgin_blist_make_buddy_menu(GtkWidget *menu, PurpleBuddy *buddy, gboolean sub) {
1488 	PurpleAccount *account = NULL;
1489 	PurpleConnection *pc = NULL;
1490 	PurplePluginProtocolInfo *prpl_info;
1491 	PurpleContact *contact;
1492 	PurpleBlistNode *node;
1493 	gboolean contact_expanded = FALSE;
1494 
1495 	g_return_if_fail(menu);
1496 	g_return_if_fail(buddy);
1497 
1498 	account = purple_buddy_get_account(buddy);
1499 	pc = purple_account_get_connection(account);
1500 	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(pc));
1501 
1502 	node = PURPLE_BLIST_NODE(buddy);
1503 
1504 	contact = purple_buddy_get_contact(buddy);
1505 	if (contact) {
1506 		PidginBlistNode *node = purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(contact));
1507 		contact_expanded = node->contact_expanded;
1508 	}
1509 
1510 	if (prpl_info && prpl_info->get_info) {
1511 		pidgin_new_item_from_stock(menu, _("Get _Info"), PIDGIN_STOCK_TOOLBAR_USER_INFO,
1512 				G_CALLBACK(gtk_blist_menu_info_cb), buddy, 0, 0, NULL);
1513 	}
1514 	pidgin_new_item_from_stock(menu, _("I_M"), PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW,
1515 			G_CALLBACK(gtk_blist_menu_im_cb), buddy, 0, 0, NULL);
1516 
1517 #ifdef USE_VV
1518 	if (prpl_info && prpl_info->get_media_caps) {
1519 		PurpleAccount *account = purple_buddy_get_account(buddy);
1520 		const gchar *who = purple_buddy_get_name(buddy);
1521 		PurpleMediaCaps caps = purple_prpl_get_media_caps(account, who);
1522 		if (caps & PURPLE_MEDIA_CAPS_AUDIO) {
1523 			pidgin_new_item_from_stock(menu, _("_Audio Call"),
1524 				PIDGIN_STOCK_TOOLBAR_AUDIO_CALL,
1525 				G_CALLBACK(gtk_blist_menu_audio_call_cb), buddy, 0, 0, NULL);
1526 		}
1527 		if (caps & PURPLE_MEDIA_CAPS_AUDIO_VIDEO) {
1528 			pidgin_new_item_from_stock(menu, _("Audio/_Video Call"),
1529 				PIDGIN_STOCK_TOOLBAR_VIDEO_CALL,
1530 				G_CALLBACK(gtk_blist_menu_video_call_cb), buddy, 0, 0, NULL);
1531 		} else if (caps & PURPLE_MEDIA_CAPS_VIDEO) {
1532 			pidgin_new_item_from_stock(menu, _("_Video Call"),
1533 				PIDGIN_STOCK_TOOLBAR_VIDEO_CALL,
1534 				G_CALLBACK(gtk_blist_menu_video_call_cb), buddy, 0, 0, NULL);
1535 		}
1536 	}
1537 
1538 #endif
1539 
1540 	if (prpl_info && prpl_info->send_file) {
1541 		if (!prpl_info->can_receive_file ||
1542 			prpl_info->can_receive_file(buddy->account->gc, buddy->name))
1543 		{
1544 			pidgin_new_item_from_stock(menu, _("_Send File..."),
1545 									 PIDGIN_STOCK_TOOLBAR_SEND_FILE,
1546 									 G_CALLBACK(gtk_blist_menu_send_file_cb),
1547 									 buddy, 0, 0, NULL);
1548 		}
1549 	}
1550 
1551 	pidgin_new_item_from_stock(menu, _("Add Buddy _Pounce..."), NULL,
1552 			G_CALLBACK(gtk_blist_menu_bp_cb), buddy, 0, 0, NULL);
1553 
1554 	if (node->parent && node->parent->child->next &&
1555 	      !sub && !contact_expanded) {
1556 		pidgin_new_item_from_stock(menu, _("View _Log"), NULL,
1557 				G_CALLBACK(gtk_blist_menu_showlog_cb),
1558 				contact, 0, 0, NULL);
1559 	} else if (!sub) {
1560 		pidgin_new_item_from_stock(menu, _("View _Log"), NULL,
1561 				G_CALLBACK(gtk_blist_menu_showlog_cb), buddy, 0, 0, NULL);
1562 	}
1563 
1564 	if (!PURPLE_BLIST_NODE_HAS_FLAG(node, PURPLE_BLIST_NODE_FLAG_NO_SAVE)) {
1565 		gboolean show_offline = purple_blist_node_get_bool(node, "show_offline");
1566 		pidgin_new_item_from_stock(menu, show_offline ? _("Hide When Offline") : _("Show When Offline"),
1567 				NULL, G_CALLBACK(gtk_blist_menu_showoffline_cb), node, 0, 0, NULL);
1568 	}
1569 
1570 	pidgin_append_blist_node_proto_menu(menu, buddy->account->gc, node);
1571 	pidgin_append_blist_node_extended_menu(menu, node);
1572 
1573 	if (!contact_expanded && contact != NULL)
1574 		pidgin_append_blist_node_move_to_menu(menu, (PurpleBlistNode *)contact);
1575 
1576 	if (node->parent && node->parent->child->next &&
1577               !sub && !contact_expanded) {
1578 		pidgin_separator(menu);
1579 		pidgin_append_blist_node_privacy_menu(menu, node);
1580 		pidgin_new_item_from_stock(menu, _("_Alias..."), PIDGIN_STOCK_ALIAS,
1581 				G_CALLBACK(gtk_blist_menu_alias_cb),
1582 				contact, 0, 0, NULL);
1583 		pidgin_new_item_from_stock(menu, _("_Remove"), GTK_STOCK_REMOVE,
1584 				G_CALLBACK(pidgin_blist_remove_cb),
1585 				contact, 0, 0, NULL);
1586 	} else if (!sub || contact_expanded) {
1587 		pidgin_separator(menu);
1588 		pidgin_append_blist_node_privacy_menu(menu, node);
1589 		pidgin_new_item_from_stock(menu, _("_Alias..."), PIDGIN_STOCK_ALIAS,
1590 				G_CALLBACK(gtk_blist_menu_alias_cb), buddy, 0, 0, NULL);
1591 		pidgin_new_item_from_stock(menu, _("_Remove"), GTK_STOCK_REMOVE,
1592 				G_CALLBACK(pidgin_blist_remove_cb), buddy,
1593 				0, 0, NULL);
1594 	}
1595 }
1596 
1597 static gboolean
gtk_blist_key_press_cb(GtkWidget * tv,GdkEventKey * event,gpointer data)1598 gtk_blist_key_press_cb(GtkWidget *tv, GdkEventKey *event, gpointer data)
1599 {
1600 	PurpleBlistNode *node;
1601 	GtkTreeIter iter, parent;
1602 	GtkTreeSelection *sel;
1603 	GtkTreePath *path;
1604 
1605 	sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv));
1606 	if(!gtk_tree_selection_get_selected(sel, NULL, &iter))
1607 		return FALSE;
1608 
1609 	gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
1610 
1611 	if(event->state & GDK_CONTROL_MASK &&
1612 			(event->keyval == 'o' || event->keyval == 'O')) {
1613 		PurpleBuddy *buddy;
1614 
1615 		if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
1616 			buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
1617 		} else if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1618 			buddy = (PurpleBuddy*)node;
1619 		} else {
1620 			return FALSE;
1621 		}
1622 		if(buddy)
1623 			pidgin_retrieve_user_info(buddy->account->gc, buddy->name);
1624 	} else {
1625 		switch (event->keyval) {
1626 			case GDK_F2:
1627 				gtk_blist_menu_alias_cb(tv, node);
1628 				break;
1629 
1630 			case GDK_Left:
1631 				path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &iter);
1632 				if (gtk_tree_view_row_expanded(GTK_TREE_VIEW(tv), path)) {
1633 					/* Collapse the Group */
1634 					gtk_tree_view_collapse_row(GTK_TREE_VIEW(tv), path);
1635 					gtk_tree_path_free(path);
1636 					return TRUE;
1637 				} else {
1638 					/* Select the Parent */
1639 					if (gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path)) {
1640 						if (gtk_tree_model_iter_parent(GTK_TREE_MODEL(gtkblist->treemodel), &parent, &iter)) {
1641 							gtk_tree_path_free(path);
1642 							path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &parent);
1643 							gtk_tree_view_set_cursor(GTK_TREE_VIEW(tv), path, NULL, FALSE);
1644 							gtk_tree_path_free(path);
1645 							return TRUE;
1646 						}
1647 					}
1648 				}
1649 				gtk_tree_path_free(path);
1650 				break;
1651 
1652 			case GDK_Right:
1653 				path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &iter);
1654 				if (!gtk_tree_view_row_expanded(GTK_TREE_VIEW(tv), path)) {
1655 					/* Expand the Group */
1656 					if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
1657 						pidgin_blist_expand_contact_cb(NULL, node);
1658 						gtk_tree_path_free(path);
1659 						return TRUE;
1660 					} else if (!PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1661 						gtk_tree_view_expand_row(GTK_TREE_VIEW(tv), path, FALSE);
1662 						gtk_tree_path_free(path);
1663 						return TRUE;
1664 					}
1665 				} else {
1666 					/* Select the First Child */
1667 					if (gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &parent, path)) {
1668 						if (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(gtkblist->treemodel), &iter, &parent, 0)) {
1669 							gtk_tree_path_free(path);
1670 							path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &iter);
1671 							gtk_tree_view_set_cursor(GTK_TREE_VIEW(tv), path, NULL, FALSE);
1672 							gtk_tree_path_free(path);
1673 							return TRUE;
1674 						}
1675 					}
1676 				}
1677 				gtk_tree_path_free(path);
1678 				break;
1679 		}
1680 	}
1681 
1682 	return FALSE;
1683 }
1684 
1685 static void
set_node_custom_icon_cb(const gchar * filename,gpointer data)1686 set_node_custom_icon_cb(const gchar *filename, gpointer data)
1687 {
1688 	if (filename) {
1689 		PurpleBlistNode *node = (PurpleBlistNode*)data;
1690 
1691 		purple_buddy_icons_node_set_custom_icon_from_file(node,
1692 		                                                  filename);
1693 	}
1694 }
1695 
1696 static void
set_node_custom_icon(GtkWidget * w,PurpleBlistNode * node)1697 set_node_custom_icon(GtkWidget *w, PurpleBlistNode *node)
1698 {
1699 	/* This doesn't keep track of the returned dialog (so that successive
1700 	 * calls could be made to re-display that dialog). Do we want that? */
1701 	GtkWidget *win = pidgin_buddy_icon_chooser_new(NULL, set_node_custom_icon_cb, node);
1702 	gtk_widget_show_all(win);
1703 }
1704 
1705 static void
remove_node_custom_icon(GtkWidget * w,PurpleBlistNode * node)1706 remove_node_custom_icon(GtkWidget *w, PurpleBlistNode *node)
1707 {
1708 	purple_buddy_icons_node_set_custom_icon(node, NULL, 0);
1709 }
1710 
1711 static void
add_buddy_icon_menu_items(GtkWidget * menu,PurpleBlistNode * node)1712 add_buddy_icon_menu_items(GtkWidget *menu, PurpleBlistNode *node)
1713 {
1714 	GtkWidget *item;
1715 
1716 	pidgin_new_item_from_stock(menu, _("Set Custom Icon"), NULL,
1717 	                           G_CALLBACK(set_node_custom_icon), node, 0,
1718 	                           0, NULL);
1719 
1720 	item = pidgin_new_item_from_stock(menu, _("Remove Custom Icon"), NULL,
1721 	                           G_CALLBACK(remove_node_custom_icon), node,
1722 	                           0, 0, NULL);
1723 	if (!purple_buddy_icons_node_has_custom_icon(node))
1724 		gtk_widget_set_sensitive(item, FALSE);
1725 }
1726 
1727 static GtkWidget *
create_group_menu(PurpleBlistNode * node,PurpleGroup * g)1728 create_group_menu (PurpleBlistNode *node, PurpleGroup *g)
1729 {
1730 	GtkWidget *menu;
1731 	GtkWidget *item;
1732 
1733 	menu = gtk_menu_new();
1734 	item = pidgin_new_item_from_stock(menu, _("Add _Buddy..."), GTK_STOCK_ADD,
1735 				 G_CALLBACK(pidgin_blist_add_buddy_cb), node, 0, 0, NULL);
1736 	gtk_widget_set_sensitive(item, purple_connections_get_all() != NULL);
1737 	item = pidgin_new_item_from_stock(menu, _("Add C_hat..."), GTK_STOCK_ADD,
1738 				 G_CALLBACK(pidgin_blist_add_chat_cb), node, 0, 0, NULL);
1739 	gtk_widget_set_sensitive(item, pidgin_blist_joinchat_is_showable());
1740 	pidgin_new_item_from_stock(menu, _("_Delete Group"), GTK_STOCK_REMOVE,
1741 				 G_CALLBACK(pidgin_blist_remove_cb), node, 0, 0, NULL);
1742 	pidgin_new_item_from_stock(menu, _("_Rename"), NULL,
1743 				 G_CALLBACK(gtk_blist_menu_alias_cb), node, 0, 0, NULL);
1744 	if (!(purple_blist_node_get_flags(node) & PURPLE_BLIST_NODE_FLAG_NO_SAVE)) {
1745 		gboolean show_offline = purple_blist_node_get_bool(node, "show_offline");
1746 		pidgin_new_item_from_stock(menu, show_offline ? _("Hide When Offline") : _("Show When Offline"),
1747 				NULL, G_CALLBACK(gtk_blist_menu_showoffline_cb), node, 0, 0, NULL);
1748 	}
1749 
1750 	add_buddy_icon_menu_items(menu, node);
1751 
1752 	pidgin_append_blist_node_extended_menu(menu, node);
1753 
1754 	return menu;
1755 }
1756 
1757 static GtkWidget *
create_chat_menu(PurpleBlistNode * node,PurpleChat * c)1758 create_chat_menu(PurpleBlistNode *node, PurpleChat *c)
1759 {
1760 	GtkWidget *menu;
1761 	gboolean autojoin, persistent;
1762 
1763 	menu = gtk_menu_new();
1764 	autojoin = purple_blist_node_get_bool(node, "gtk-autojoin");
1765 	persistent = purple_blist_node_get_bool(node, "gtk-persistent");
1766 
1767 	pidgin_new_item_from_stock(menu, _("_Join"), PIDGIN_STOCK_CHAT,
1768 			G_CALLBACK(gtk_blist_menu_join_cb), node, 0, 0, NULL);
1769 	pidgin_new_check_item(menu, _("Auto-Join"),
1770 			G_CALLBACK(gtk_blist_menu_autojoin_cb), node, autojoin);
1771 	pidgin_new_check_item(menu, _("Persistent"),
1772 			G_CALLBACK(gtk_blist_menu_persistent_cb), node, persistent);
1773 	pidgin_new_item_from_stock(menu, _("View _Log"), NULL,
1774 			G_CALLBACK(gtk_blist_menu_showlog_cb), node, 0, 0, NULL);
1775 
1776 	pidgin_append_blist_node_proto_menu(menu, c->account->gc, node);
1777 	pidgin_append_blist_node_extended_menu(menu, node);
1778 
1779 	pidgin_separator(menu);
1780 
1781 	pidgin_new_item_from_stock(menu, _("_Edit Settings..."), NULL,
1782 				 G_CALLBACK(chat_components_edit), node, 0, 0, NULL);
1783 	pidgin_new_item_from_stock(menu, _("_Alias..."), PIDGIN_STOCK_ALIAS,
1784 				 G_CALLBACK(gtk_blist_menu_alias_cb), node, 0, 0, NULL);
1785 	pidgin_new_item_from_stock(menu, _("_Remove"), GTK_STOCK_REMOVE,
1786 				 G_CALLBACK(pidgin_blist_remove_cb), node, 0, 0, NULL);
1787 
1788 	add_buddy_icon_menu_items(menu, node);
1789 
1790 	return menu;
1791 }
1792 
1793 static GtkWidget *
create_contact_menu(PurpleBlistNode * node)1794 create_contact_menu (PurpleBlistNode *node)
1795 {
1796 	GtkWidget *menu;
1797 
1798 	menu = gtk_menu_new();
1799 
1800 	pidgin_new_item_from_stock(menu, _("View _Log"), NULL,
1801 				 G_CALLBACK(gtk_blist_menu_showlog_cb),
1802 				 node, 0, 0, NULL);
1803 
1804 	pidgin_separator(menu);
1805 
1806 	pidgin_new_item_from_stock(menu, _("_Alias..."), PIDGIN_STOCK_ALIAS,
1807 				 G_CALLBACK(gtk_blist_menu_alias_cb), node, 0, 0, NULL);
1808 	pidgin_new_item_from_stock(menu, _("_Remove"), GTK_STOCK_REMOVE,
1809 				 G_CALLBACK(pidgin_blist_remove_cb), node, 0, 0, NULL);
1810 
1811 	add_buddy_icon_menu_items(menu, node);
1812 
1813 	pidgin_separator(menu);
1814 
1815 	pidgin_new_item_from_stock(menu, _("_Collapse"), GTK_STOCK_ZOOM_OUT,
1816 				 G_CALLBACK(pidgin_blist_collapse_contact_cb),
1817 				 node, 0, 0, NULL);
1818 
1819 	pidgin_append_blist_node_extended_menu(menu, node);
1820 	return menu;
1821 }
1822 
1823 static GtkWidget *
create_buddy_menu(PurpleBlistNode * node,PurpleBuddy * b)1824 create_buddy_menu(PurpleBlistNode *node, PurpleBuddy *b)
1825 {
1826 	struct _pidgin_blist_node *gtknode = (struct _pidgin_blist_node *)node->ui_data;
1827 	GtkWidget *menu;
1828 	GtkWidget *menuitem;
1829 	gboolean show_offline = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_offline_buddies");
1830 
1831 	menu = gtk_menu_new();
1832 	pidgin_blist_make_buddy_menu(menu, b, FALSE);
1833 
1834 	if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
1835 		pidgin_separator(menu);
1836 
1837 		add_buddy_icon_menu_items(menu, node);
1838 
1839 		if(gtknode->contact_expanded) {
1840 			pidgin_new_item_from_stock(menu, _("_Collapse"),
1841 						 GTK_STOCK_ZOOM_OUT,
1842 						 G_CALLBACK(pidgin_blist_collapse_contact_cb),
1843 						 node, 0, 0, NULL);
1844 		} else {
1845 			pidgin_new_item_from_stock(menu, _("_Expand"),
1846 						 GTK_STOCK_ZOOM_IN,
1847 						 G_CALLBACK(pidgin_blist_expand_contact_cb), node,
1848 						 0, 0, NULL);
1849 		}
1850 		if(node->child->next) {
1851 			PurpleBlistNode *bnode;
1852 
1853 			for(bnode = node->child; bnode; bnode = bnode->next) {
1854 				PurpleBuddy *buddy = (PurpleBuddy*)bnode;
1855 				GdkPixbuf *buf;
1856 				GtkWidget *submenu;
1857 				GtkWidget *image;
1858 
1859 				if(buddy == b)
1860 					continue;
1861 				if(!buddy->account->gc)
1862 					continue;
1863 				if(!show_offline && !PURPLE_BUDDY_IS_ONLINE(buddy))
1864 					continue;
1865 
1866 				menuitem = gtk_image_menu_item_new_with_label(buddy->name);
1867 				buf = pidgin_create_prpl_icon(buddy->account,PIDGIN_PRPL_ICON_SMALL);
1868 				image = gtk_image_new_from_pixbuf(buf);
1869 				g_object_unref(G_OBJECT(buf));
1870 				gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem),
1871 											  image);
1872 				gtk_widget_show(image);
1873 				gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1874 				gtk_widget_show(menuitem);
1875 
1876 				submenu = gtk_menu_new();
1877 				gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
1878 				gtk_widget_show(submenu);
1879 
1880 				pidgin_blist_make_buddy_menu(submenu, buddy, TRUE);
1881 			}
1882 		}
1883 	}
1884 	return menu;
1885 }
1886 
1887 static gboolean
pidgin_blist_show_context_menu(PurpleBlistNode * node,GtkMenuPositionFunc func,GtkWidget * tv,guint button,guint32 time)1888 pidgin_blist_show_context_menu(PurpleBlistNode *node,
1889 								 GtkMenuPositionFunc func,
1890 								 GtkWidget *tv,
1891 								 guint button,
1892 								 guint32 time)
1893 {
1894 	struct _pidgin_blist_node *gtknode;
1895 	GtkWidget *menu = NULL;
1896 	gboolean handled = FALSE;
1897 
1898 	gtknode = (struct _pidgin_blist_node *)node->ui_data;
1899 
1900 	/* Create a menu based on the thing we right-clicked on */
1901 	if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
1902 		PurpleGroup *g = (PurpleGroup *)node;
1903 
1904 		menu = create_group_menu(node, g);
1905 	} else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
1906 		PurpleChat *c = (PurpleChat *)node;
1907 
1908 		menu = create_chat_menu(node, c);
1909 	} else if ((PURPLE_BLIST_NODE_IS_CONTACT(node)) && (gtknode->contact_expanded)) {
1910 		menu = create_contact_menu(node);
1911 	} else if (PURPLE_BLIST_NODE_IS_CONTACT(node) || PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1912 		PurpleBuddy *b;
1913 
1914 		if (PURPLE_BLIST_NODE_IS_CONTACT(node))
1915 			b = purple_contact_get_priority_buddy((PurpleContact*)node);
1916 		else
1917 			b = (PurpleBuddy *)node;
1918 
1919 		menu = create_buddy_menu(node, b);
1920 	}
1921 
1922 #ifdef _WIN32
1923 	pidgin_blist_tooltip_destroy();
1924 
1925 	/* Unhook the tooltip-timeout since we don't want a tooltip
1926 	 * to appear and obscure the context menu we are about to show
1927 	   This is a workaround for GTK+ bug 107320. */
1928 	if (gtkblist->timeout) {
1929 		g_source_remove(gtkblist->timeout);
1930 		gtkblist->timeout = 0;
1931 	}
1932 #endif
1933 
1934 	/* Now display the menu */
1935 	if (menu != NULL) {
1936 		gtk_widget_show_all(menu);
1937 		gtk_menu_popup(GTK_MENU(menu), NULL, NULL, func, tv, button, time);
1938 		handled = TRUE;
1939 	}
1940 
1941 	return handled;
1942 }
1943 
1944 static gboolean
gtk_blist_button_press_cb(GtkWidget * tv,GdkEventButton * event,gpointer user_data)1945 gtk_blist_button_press_cb(GtkWidget *tv, GdkEventButton *event, gpointer user_data)
1946 {
1947 	GtkTreePath *path;
1948 	PurpleBlistNode *node;
1949 	GtkTreeIter iter;
1950 	GtkTreeSelection *sel;
1951 	PurplePlugin *prpl = NULL;
1952 	PurplePluginProtocolInfo *prpl_info = NULL;
1953 	struct _pidgin_blist_node *gtknode;
1954 	gboolean handled = FALSE;
1955 
1956 	/* Here we figure out which node was clicked */
1957 	if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), event->x, event->y, &path, NULL, NULL, NULL))
1958 		return FALSE;
1959 	gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
1960 	gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
1961 	gtknode = (struct _pidgin_blist_node *)node->ui_data;
1962 
1963 	/* Right click draws a context menu */
1964 	if ((event->button == 3) && (event->type == GDK_BUTTON_PRESS)) {
1965 		handled = pidgin_blist_show_context_menu(node, NULL, tv, 3, event->time);
1966 
1967 	/* CTRL+middle click expands or collapse a contact */
1968 	} else if ((event->button == 2) && (event->type == GDK_BUTTON_PRESS) &&
1969 			   (event->state & GDK_CONTROL_MASK) && (PURPLE_BLIST_NODE_IS_CONTACT(node))) {
1970 		if (gtknode->contact_expanded)
1971 			pidgin_blist_collapse_contact_cb(NULL, node);
1972 		else
1973 			pidgin_blist_expand_contact_cb(NULL, node);
1974 		handled = TRUE;
1975 
1976 	/* Double middle click gets info */
1977 	} else if ((event->button == 2) && (event->type == GDK_2BUTTON_PRESS) &&
1978 			   ((PURPLE_BLIST_NODE_IS_CONTACT(node)) || (PURPLE_BLIST_NODE_IS_BUDDY(node)))) {
1979 		PurpleBuddy *b;
1980 		if(PURPLE_BLIST_NODE_IS_CONTACT(node))
1981 			b = purple_contact_get_priority_buddy((PurpleContact*)node);
1982 		else
1983 			b = (PurpleBuddy *)node;
1984 
1985 		prpl = purple_find_prpl(purple_account_get_protocol_id(b->account));
1986 		if (prpl != NULL)
1987 			prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
1988 
1989 		if (prpl && prpl_info->get_info)
1990 			pidgin_retrieve_user_info(b->account->gc, b->name);
1991 		handled = TRUE;
1992 	}
1993 
1994 #if (1)
1995 	/*
1996 	 * This code only exists because GTK+ doesn't work.  If we return
1997 	 * FALSE here, as would be normal the event propoagates down and
1998 	 * somehow gets interpreted as the start of a drag event.
1999 	 *
2000 	 * Um, isn't it _normal_ to return TRUE here?  Since the event
2001 	 * was handled?  --Mark
2002 	 */
2003 	if(handled) {
2004 		sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv));
2005 		gtk_tree_selection_select_path(sel, path);
2006 		gtk_tree_path_free(path);
2007 		return TRUE;
2008 	}
2009 #endif
2010 	gtk_tree_path_free(path);
2011 
2012 	return FALSE;
2013 }
2014 
2015 static gboolean
pidgin_blist_popup_menu_cb(GtkWidget * tv,void * user_data)2016 pidgin_blist_popup_menu_cb(GtkWidget *tv, void *user_data)
2017 {
2018 	PurpleBlistNode *node;
2019 	GtkTreeIter iter;
2020 	GtkTreeSelection *sel;
2021 	gboolean handled = FALSE;
2022 
2023 	sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv));
2024 	if (!gtk_tree_selection_get_selected(sel, NULL, &iter))
2025 		return FALSE;
2026 
2027 	gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
2028 
2029 	/* Shift+F10 draws a context menu */
2030 	handled = pidgin_blist_show_context_menu(node, pidgin_treeview_popup_menu_position_func, tv, 0, GDK_CURRENT_TIME);
2031 
2032 	return handled;
2033 }
2034 
pidgin_blist_buddy_details_cb(gpointer data,guint action,GtkWidget * item)2035 static void pidgin_blist_buddy_details_cb(gpointer data, guint action, GtkWidget *item)
2036 {
2037 	pidgin_set_cursor(gtkblist->window, GDK_WATCH);
2038 
2039 	purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons",
2040 			    gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item)));
2041 
2042 	pidgin_clear_cursor(gtkblist->window);
2043 }
2044 
pidgin_blist_show_idle_time_cb(gpointer data,guint action,GtkWidget * item)2045 static void pidgin_blist_show_idle_time_cb(gpointer data, guint action, GtkWidget *item)
2046 {
2047 	pidgin_set_cursor(gtkblist->window, GDK_WATCH);
2048 
2049 	purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/show_idle_time",
2050 			    gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item)));
2051 
2052 	pidgin_clear_cursor(gtkblist->window);
2053 }
2054 
pidgin_blist_show_protocol_icons_cb(gpointer data,guint action,GtkWidget * item)2055 static void pidgin_blist_show_protocol_icons_cb(gpointer data, guint action, GtkWidget *item)
2056 {
2057 	purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons",
2058 			      gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item)));
2059 }
2060 
pidgin_blist_show_empty_groups_cb(gpointer data,guint action,GtkWidget * item)2061 static void pidgin_blist_show_empty_groups_cb(gpointer data, guint action, GtkWidget *item)
2062 {
2063 	pidgin_set_cursor(gtkblist->window, GDK_WATCH);
2064 
2065 	purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/show_empty_groups",
2066 			gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item)));
2067 
2068 	pidgin_clear_cursor(gtkblist->window);
2069 }
2070 
pidgin_blist_edit_mode_cb(gpointer callback_data,guint callback_action,GtkWidget * checkitem)2071 static void pidgin_blist_edit_mode_cb(gpointer callback_data, guint callback_action,
2072 		GtkWidget *checkitem)
2073 {
2074 	pidgin_set_cursor(gtkblist->window, GDK_WATCH);
2075 
2076 	purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/show_offline_buddies",
2077 			gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(checkitem)));
2078 
2079 	pidgin_clear_cursor(gtkblist->window);
2080 }
2081 
pidgin_blist_mute_sounds_cb(gpointer data,guint action,GtkWidget * item)2082 static void pidgin_blist_mute_sounds_cb(gpointer data, guint action, GtkWidget *item)
2083 {
2084 	purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/sound/mute", GTK_CHECK_MENU_ITEM(item)->active);
2085 }
2086 
2087 static void
pidgin_blist_mute_pref_cb(const char * name,PurplePrefType type,gconstpointer value,gpointer data)2088 pidgin_blist_mute_pref_cb(const char *name, PurplePrefType type,
2089 							gconstpointer value, gpointer data)
2090 {
2091 	gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item(gtkblist->ift,
2092 						N_("/Tools/Mute Sounds"))),	(gboolean)GPOINTER_TO_INT(value));
2093 }
2094 
2095 static void
pidgin_blist_sound_method_pref_cb(const char * name,PurplePrefType type,gconstpointer value,gpointer data)2096 pidgin_blist_sound_method_pref_cb(const char *name, PurplePrefType type,
2097 									gconstpointer value, gpointer data)
2098 {
2099 	gboolean sensitive = TRUE;
2100 
2101 	if(purple_strequal(value, "none"))
2102 		sensitive = FALSE;
2103 
2104 	gtk_widget_set_sensitive(gtk_item_factory_get_widget(gtkblist->ift, N_("/Tools/Mute Sounds")), sensitive);
2105 }
2106 
2107 static void
add_buddies_from_vcard(const char * prpl_id,PurpleGroup * group,GList * list,const char * alias)2108 add_buddies_from_vcard(const char *prpl_id, PurpleGroup *group, GList *list,
2109 					   const char *alias)
2110 {
2111 	GList *l;
2112 	PurpleAccount *account = NULL;
2113 	PurpleConnection *gc;
2114 
2115 	if (list == NULL)
2116 		return;
2117 
2118 	for (l = purple_connections_get_all(); l != NULL; l = l->next)
2119 	{
2120 		gc = (PurpleConnection *)l->data;
2121 		account = purple_connection_get_account(gc);
2122 
2123 		if (purple_strequal(purple_account_get_protocol_id(account), prpl_id))
2124 			break;
2125 
2126 		account = NULL;
2127 	}
2128 
2129 	if (account != NULL)
2130 	{
2131 		for (l = list; l != NULL; l = l->next)
2132 		{
2133 			purple_blist_request_add_buddy(account, l->data,
2134 										 (group ? group->name : NULL),
2135 										 alias);
2136 		}
2137 	}
2138 
2139 	g_list_free_full(list, (GDestroyNotify)g_free);
2140 }
2141 
2142 static gboolean
parse_vcard(const char * vcard,PurpleGroup * group)2143 parse_vcard(const char *vcard, PurpleGroup *group)
2144 {
2145 	char *temp_vcard;
2146 	char *s, *c;
2147 	char *alias    = NULL;
2148 	GList *aims    = NULL;
2149 	GList *icqs    = NULL;
2150 	GList *jabbers = NULL;
2151 
2152 	s = temp_vcard = g_strdup(vcard);
2153 
2154 	while (*s != '\0' && strncmp(s, "END:vCard", strlen("END:vCard")))
2155 	{
2156 		char *field, *value;
2157 
2158 		field = s;
2159 
2160 		/* Grab the field */
2161 		while (*s != '\r' && *s != '\n' && *s != '\0' && *s != ':')
2162 			s++;
2163 
2164 		if (*s == '\r') s++;
2165 		if (*s == '\n')
2166 		{
2167 			s++;
2168 			continue;
2169 		}
2170 
2171 		if (*s != '\0') *s++ = '\0';
2172 
2173 		if ((c = strchr(field, ';')) != NULL)
2174 			*c = '\0';
2175 
2176 		/* Proceed to the end of the line */
2177 		value = s;
2178 
2179 		while (*s != '\r' && *s != '\n' && *s != '\0')
2180 			s++;
2181 
2182 		if (*s == '\r') *s++ = '\0';
2183 		if (*s == '\n') *s++ = '\0';
2184 
2185 		/* We only want to worry about a few fields here. */
2186 		if (purple_strequal(field, "FN"))
2187 			alias = g_strdup(value);
2188 		else if (purple_strequal(field, "X-AIM") || purple_strequal(field, "X-ICQ") ||
2189 				 purple_strequal(field, "X-JABBER"))
2190 		{
2191 			char **values = g_strsplit(value, ":", 0);
2192 			char **im;
2193 
2194 			for (im = values; *im != NULL; im++)
2195 			{
2196 				if (purple_strequal(field, "X-AIM"))
2197 					aims = g_list_append(aims, g_strdup(*im));
2198 				else if (purple_strequal(field, "X-ICQ"))
2199 					icqs = g_list_append(icqs, g_strdup(*im));
2200 				else if (purple_strequal(field, "X-JABBER"))
2201 					jabbers = g_list_append(jabbers, g_strdup(*im));
2202 			}
2203 
2204 			g_strfreev(values);
2205 		}
2206 	}
2207 
2208 	g_free(temp_vcard);
2209 
2210 	if (aims == NULL && icqs == NULL && jabbers == NULL)
2211 	{
2212 		g_free(alias);
2213 
2214 		return FALSE;
2215 	}
2216 
2217 	add_buddies_from_vcard("prpl-aim",    group, aims,    alias);
2218 	add_buddies_from_vcard("prpl-icq",    group, icqs,    alias);
2219 	add_buddies_from_vcard("prpl-jabber", group, jabbers, alias);
2220 
2221 	g_free(alias);
2222 
2223 	return TRUE;
2224 }
2225 
2226 #ifdef _WIN32
pidgin_blist_drag_begin(GtkWidget * widget,GdkDragContext * drag_context,gpointer user_data)2227 static void pidgin_blist_drag_begin(GtkWidget *widget,
2228 		GdkDragContext *drag_context, gpointer user_data)
2229 {
2230 	pidgin_blist_tooltip_destroy();
2231 
2232 
2233 	/* Unhook the tooltip-timeout since we don't want a tooltip
2234 	 * to appear and obscure the dragging operation.
2235 	 * This is a workaround for GTK+ bug 107320. */
2236 	if (gtkblist->timeout) {
2237 		g_source_remove(gtkblist->timeout);
2238 		gtkblist->timeout = 0;
2239 	}
2240 }
2241 #endif
2242 
pidgin_blist_drag_data_get_cb(GtkWidget * widget,GdkDragContext * dc,GtkSelectionData * data,guint info,guint time,gpointer null)2243 static void pidgin_blist_drag_data_get_cb(GtkWidget *widget,
2244 											GdkDragContext *dc,
2245 											GtkSelectionData *data,
2246 											guint info,
2247 											guint time,
2248 											gpointer null)
2249 {
2250 
2251 	if (data->target == gdk_atom_intern("PURPLE_BLIST_NODE", FALSE))
2252 	{
2253 		GtkTreeRowReference *ref = g_object_get_data(G_OBJECT(dc), "gtk-tree-view-source-row");
2254 		GtkTreePath *sourcerow = gtk_tree_row_reference_get_path(ref);
2255 		GtkTreeIter iter;
2256 		PurpleBlistNode *node = NULL;
2257 		if(!sourcerow)
2258 			return;
2259 		gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, sourcerow);
2260 		gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
2261 		gtk_selection_data_set (data,
2262 					gdk_atom_intern ("PURPLE_BLIST_NODE", FALSE),
2263 					8, /* bits */
2264 					(void*)&node,
2265 					sizeof (node));
2266 
2267 		gtk_tree_path_free(sourcerow);
2268 	}
2269 	else if (data->target == gdk_atom_intern("application/x-im-contact", FALSE))
2270 	{
2271 		GtkTreeRowReference *ref;
2272 		GtkTreePath *sourcerow;
2273 		GtkTreeIter iter;
2274 		PurpleBlistNode *node = NULL;
2275 		PurpleBuddy *buddy;
2276 		PurpleConnection *gc;
2277 		GString *str;
2278 		const char *protocol;
2279 
2280 		ref = g_object_get_data(G_OBJECT(dc), "gtk-tree-view-source-row");
2281 		sourcerow = gtk_tree_row_reference_get_path(ref);
2282 
2283 		if (!sourcerow)
2284 			return;
2285 
2286 		gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter,
2287 								sourcerow);
2288 		gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
2289 
2290 		if (PURPLE_BLIST_NODE_IS_CONTACT(node))
2291 		{
2292 			buddy = purple_contact_get_priority_buddy((PurpleContact *)node);
2293 		}
2294 		else if (!PURPLE_BLIST_NODE_IS_BUDDY(node))
2295 		{
2296 			gtk_tree_path_free(sourcerow);
2297 			return;
2298 		}
2299 		else
2300 		{
2301 			buddy = (PurpleBuddy *)node;
2302 		}
2303 
2304 		gc = purple_account_get_connection(buddy->account);
2305 
2306 		if (gc == NULL)
2307 		{
2308 			gtk_tree_path_free(sourcerow);
2309 			return;
2310 		}
2311 
2312 		protocol =
2313 			PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->list_icon(buddy->account,
2314 														   buddy);
2315 
2316 		str = g_string_new(NULL);
2317 		g_string_printf(str,
2318 			"MIME-Version: 1.0\r\n"
2319 			"Content-Type: application/x-im-contact\r\n"
2320 			"X-IM-Protocol: %s\r\n"
2321 			"X-IM-Username: %s\r\n",
2322 			protocol,
2323 			buddy->name);
2324 
2325 		if (buddy->alias != NULL)
2326 		{
2327 			g_string_append_printf(str,
2328 				"X-IM-Alias: %s\r\n",
2329 				buddy->alias);
2330 		}
2331 
2332 		g_string_append(str, "\r\n");
2333 
2334 		gtk_selection_data_set(data,
2335 					gdk_atom_intern("application/x-im-contact", FALSE),
2336 					8, /* bits */
2337 					(const guchar *)str->str,
2338 					strlen(str->str) + 1);
2339 
2340 		g_string_free(str, TRUE);
2341 		gtk_tree_path_free(sourcerow);
2342 	}
2343 }
2344 
pidgin_blist_drag_data_rcv_cb(GtkWidget * widget,GdkDragContext * dc,guint x,guint y,GtkSelectionData * sd,guint info,guint t)2345 static void pidgin_blist_drag_data_rcv_cb(GtkWidget *widget, GdkDragContext *dc, guint x, guint y,
2346 			  GtkSelectionData *sd, guint info, guint t)
2347 {
2348 	if (gtkblist->drag_timeout) {
2349 		g_source_remove(gtkblist->drag_timeout);
2350 		gtkblist->drag_timeout = 0;
2351 	}
2352 
2353 	if (sd->target == gdk_atom_intern("PURPLE_BLIST_NODE", FALSE) && sd->data) {
2354 		PurpleBlistNode *n = NULL;
2355 		GtkTreePath *path = NULL;
2356 		GtkTreeViewDropPosition position;
2357 		memcpy(&n, sd->data, sizeof(n));
2358 		if(gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget), x, y, &path, &position)) {
2359 			/* if we're here, I think it means the drop is ok */
2360 			GtkTreeIter iter;
2361 			PurpleBlistNode *node;
2362 			struct _pidgin_blist_node *gtknode;
2363 
2364 			gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel),
2365 					&iter, path);
2366 			gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel),
2367 					&iter, NODE_COLUMN, &node, -1);
2368 			gtknode = node->ui_data;
2369 
2370 			if (PURPLE_BLIST_NODE_IS_CONTACT(n)) {
2371 				PurpleContact *c = (PurpleContact*)n;
2372 				if (PURPLE_BLIST_NODE_IS_CONTACT(node) && gtknode->contact_expanded) {
2373 					purple_blist_merge_contact(c, node);
2374 				} else if (PURPLE_BLIST_NODE_IS_CONTACT(node) ||
2375 						PURPLE_BLIST_NODE_IS_CHAT(node)) {
2376 					switch(position) {
2377 						case GTK_TREE_VIEW_DROP_AFTER:
2378 						case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
2379 							purple_blist_add_contact(c, (PurpleGroup*)node->parent,
2380 									node);
2381 							break;
2382 						case GTK_TREE_VIEW_DROP_BEFORE:
2383 						case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
2384 							purple_blist_add_contact(c, (PurpleGroup*)node->parent,
2385 									node->prev);
2386 							break;
2387 					}
2388 				} else if(PURPLE_BLIST_NODE_IS_GROUP(node)) {
2389 					purple_blist_add_contact(c, (PurpleGroup*)node, NULL);
2390 				} else if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
2391 					purple_blist_merge_contact(c, node);
2392 				}
2393 			} else if (PURPLE_BLIST_NODE_IS_BUDDY(n)) {
2394 				PurpleBuddy *b = (PurpleBuddy*)n;
2395 				if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
2396 					switch(position) {
2397 						case GTK_TREE_VIEW_DROP_AFTER:
2398 						case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
2399 							purple_blist_add_buddy(b, (PurpleContact*)node->parent,
2400 									(PurpleGroup*)node->parent->parent, node);
2401 							break;
2402 						case GTK_TREE_VIEW_DROP_BEFORE:
2403 						case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
2404 							purple_blist_add_buddy(b, (PurpleContact*)node->parent,
2405 									(PurpleGroup*)node->parent->parent,
2406 									node->prev);
2407 							break;
2408 					}
2409 				} else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
2410 					purple_blist_add_buddy(b, NULL, (PurpleGroup*)node->parent,
2411 							NULL);
2412 				} else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
2413 					purple_blist_add_buddy(b, NULL, (PurpleGroup*)node, NULL);
2414 				} else if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
2415 					if(gtknode->contact_expanded) {
2416 						switch(position) {
2417 							case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
2418 							case GTK_TREE_VIEW_DROP_AFTER:
2419 							case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
2420 								purple_blist_add_buddy(b, (PurpleContact*)node,
2421 										(PurpleGroup*)node->parent, NULL);
2422 								break;
2423 							case GTK_TREE_VIEW_DROP_BEFORE:
2424 								purple_blist_add_buddy(b, NULL,
2425 										(PurpleGroup*)node->parent, node->prev);
2426 								break;
2427 						}
2428 					} else {
2429 						switch(position) {
2430 							case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
2431 							case GTK_TREE_VIEW_DROP_AFTER:
2432 								purple_blist_add_buddy(b, NULL,
2433 										(PurpleGroup*)node->parent, NULL);
2434 								break;
2435 							case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
2436 							case GTK_TREE_VIEW_DROP_BEFORE:
2437 								purple_blist_add_buddy(b, NULL,
2438 										(PurpleGroup*)node->parent, node->prev);
2439 								break;
2440 						}
2441 					}
2442 				}
2443 			} else if (PURPLE_BLIST_NODE_IS_CHAT(n)) {
2444 				PurpleChat *chat = (PurpleChat *)n;
2445 				if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
2446 					switch(position) {
2447 						case GTK_TREE_VIEW_DROP_AFTER:
2448 						case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
2449 						case GTK_TREE_VIEW_DROP_BEFORE:
2450 						case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
2451 							purple_blist_add_chat(chat,
2452 									(PurpleGroup*)node->parent->parent,
2453 									node->parent);
2454 							break;
2455 					}
2456 				} else if(PURPLE_BLIST_NODE_IS_CONTACT(node) ||
2457 						PURPLE_BLIST_NODE_IS_CHAT(node)) {
2458 					switch(position) {
2459 						case GTK_TREE_VIEW_DROP_AFTER:
2460 						case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
2461 							purple_blist_add_chat(chat, (PurpleGroup*)node->parent, node);
2462 							break;
2463 						case GTK_TREE_VIEW_DROP_BEFORE:
2464 						case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
2465 							purple_blist_add_chat(chat, (PurpleGroup*)node->parent, node->prev);
2466 							break;
2467 					}
2468 				} else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
2469 					purple_blist_add_chat(chat, (PurpleGroup*)node, NULL);
2470 				}
2471 			} else if (PURPLE_BLIST_NODE_IS_GROUP(n)) {
2472 				PurpleGroup *g = (PurpleGroup*)n;
2473 				if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
2474 					switch (position) {
2475 					case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
2476 					case GTK_TREE_VIEW_DROP_AFTER:
2477 						purple_blist_add_group(g, node);
2478 						break;
2479 					case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
2480 					case GTK_TREE_VIEW_DROP_BEFORE:
2481 						purple_blist_add_group(g, node->prev);
2482 						break;
2483 					}
2484 				} else if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
2485 					purple_blist_add_group(g, node->parent->parent);
2486 				} else if(PURPLE_BLIST_NODE_IS_CONTACT(node) ||
2487 						PURPLE_BLIST_NODE_IS_CHAT(node)) {
2488 					purple_blist_add_group(g, node->parent);
2489 				}
2490 			}
2491 
2492 			gtk_tree_path_free(path);
2493 			gtk_drag_finish(dc, TRUE, (dc->action == GDK_ACTION_MOVE), t);
2494 		}
2495 	}
2496 	else if (sd->target == gdk_atom_intern("application/x-im-contact",
2497 										   FALSE) && sd->data)
2498 	{
2499 		PurpleGroup *group = NULL;
2500 		GtkTreePath *path = NULL;
2501 		GtkTreeViewDropPosition position;
2502 		PurpleAccount *account;
2503 		char *protocol = NULL;
2504 		char *username = NULL;
2505 		char *alias = NULL;
2506 
2507 		if (gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget),
2508 											  x, y, &path, &position))
2509 		{
2510 			GtkTreeIter iter;
2511 			PurpleBlistNode *node;
2512 
2513 			gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel),
2514 									&iter, path);
2515 			gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel),
2516 				&iter, NODE_COLUMN, &node, -1);
2517 
2518 			if (PURPLE_BLIST_NODE_IS_BUDDY(node))
2519 			{
2520 				group = (PurpleGroup *)node->parent->parent;
2521 			}
2522 			else if (PURPLE_BLIST_NODE_IS_CHAT(node) ||
2523 					 PURPLE_BLIST_NODE_IS_CONTACT(node))
2524 			{
2525 				group = (PurpleGroup *)node->parent;
2526 			}
2527 			else if (PURPLE_BLIST_NODE_IS_GROUP(node))
2528 			{
2529 				group = (PurpleGroup *)node;
2530 			}
2531 		}
2532 
2533 		if (pidgin_parse_x_im_contact((const char *)sd->data, FALSE, &account,
2534 										&protocol, &username, &alias))
2535 		{
2536 			if (account == NULL)
2537 			{
2538 				purple_notify_error(NULL, NULL,
2539 					_("You are not currently signed on with an account that "
2540 					  "can add that buddy."), NULL);
2541 			}
2542 			else
2543 			{
2544 				purple_blist_request_add_buddy(account, username,
2545 											 (group ? group->name : NULL),
2546 											 alias);
2547 			}
2548 		}
2549 
2550 		g_free(username);
2551 		g_free(protocol);
2552 		g_free(alias);
2553 
2554 		if (path != NULL)
2555 			gtk_tree_path_free(path);
2556 
2557 		gtk_drag_finish(dc, TRUE, (dc->action == GDK_ACTION_MOVE), t);
2558 	}
2559 	else if (sd->target == gdk_atom_intern("text/x-vcard", FALSE) && sd->data)
2560 	{
2561 		gboolean result;
2562 		PurpleGroup *group = NULL;
2563 		GtkTreePath *path = NULL;
2564 		GtkTreeViewDropPosition position;
2565 
2566 		if (gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget),
2567 											  x, y, &path, &position))
2568 		{
2569 			GtkTreeIter iter;
2570 			PurpleBlistNode *node;
2571 
2572 			gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel),
2573 									&iter, path);
2574 			gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel),
2575 				&iter, NODE_COLUMN, &node, -1);
2576 
2577 			if (PURPLE_BLIST_NODE_IS_BUDDY(node))
2578 			{
2579 				group = (PurpleGroup *)node->parent->parent;
2580 			}
2581 			else if (PURPLE_BLIST_NODE_IS_CHAT(node) ||
2582 					 PURPLE_BLIST_NODE_IS_CONTACT(node))
2583 			{
2584 				group = (PurpleGroup *)node->parent;
2585 			}
2586 			else if (PURPLE_BLIST_NODE_IS_GROUP(node))
2587 			{
2588 				group = (PurpleGroup *)node;
2589 			}
2590 		}
2591 
2592 		result = parse_vcard((const gchar *)sd->data, group);
2593 
2594 		gtk_drag_finish(dc, result, (dc->action == GDK_ACTION_MOVE), t);
2595 	} else if (sd->target == gdk_atom_intern("text/uri-list", FALSE) && sd->data) {
2596 		GtkTreePath *path = NULL;
2597 		GtkTreeViewDropPosition position;
2598 
2599 		if (gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget),
2600 											  x, y, &path, &position))
2601 			{
2602 				GtkTreeIter iter;
2603 				PurpleBlistNode *node;
2604 
2605 				gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel),
2606 							&iter, path);
2607 				gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel),
2608 					&iter, NODE_COLUMN, &node, -1);
2609 
2610 				if (PURPLE_BLIST_NODE_IS_BUDDY(node) || PURPLE_BLIST_NODE_IS_CONTACT(node)) {
2611 					PurpleBuddy *b = PURPLE_BLIST_NODE_IS_BUDDY(node) ? PURPLE_BUDDY(node) : purple_contact_get_priority_buddy(PURPLE_CONTACT(node));
2612 					pidgin_dnd_file_manage(sd, b->account, b->name);
2613 					gtk_drag_finish(dc, TRUE, (dc->action == GDK_ACTION_MOVE), t);
2614 				} else {
2615 					gtk_drag_finish(dc, FALSE, FALSE, t);
2616 				}
2617 			}
2618 	}
2619 }
2620 
2621 /* Altered from do_colorshift in gnome-panel */
2622 static void
do_alphashift(GdkPixbuf * pixbuf,int shift)2623 do_alphashift(GdkPixbuf *pixbuf, int shift)
2624 {
2625 	gint i, j;
2626 	gint width, height, padding;
2627 	guchar *pixels;
2628 	int val;
2629 
2630 	if (!gdk_pixbuf_get_has_alpha(pixbuf))
2631 	  return;
2632 
2633 	width = gdk_pixbuf_get_width(pixbuf);
2634 	height = gdk_pixbuf_get_height(pixbuf);
2635 	padding = gdk_pixbuf_get_rowstride(pixbuf) - width * 4;
2636 	pixels = gdk_pixbuf_get_pixels(pixbuf);
2637 
2638 	for (i = 0; i < height; i++) {
2639 		for (j = 0; j < width; j++) {
2640 			pixels++;
2641 			pixels++;
2642 			pixels++;
2643 			val = *pixels - shift;
2644 			*(pixels++) = CLAMP(val, 0, 255);
2645 		}
2646 		pixels += padding;
2647 	}
2648 }
2649 
2650 
pidgin_blist_get_buddy_icon(PurpleBlistNode * node,gboolean scaled,gboolean greyed)2651 static GdkPixbuf *pidgin_blist_get_buddy_icon(PurpleBlistNode *node,
2652                                               gboolean scaled, gboolean greyed)
2653 {
2654 	gsize len;
2655 	PurpleBuddy *buddy = NULL;
2656 	PurpleGroup *group = NULL;
2657 	const guchar *data = NULL;
2658 	GdkPixbuf *buf, *ret = NULL;
2659 	PurpleBuddyIcon *icon = NULL;
2660 	PurpleAccount *account = NULL;
2661 	PurpleContact *contact = NULL;
2662 	PurpleStoredImage *custom_img;
2663 	PurplePluginProtocolInfo *prpl_info = NULL;
2664 	gint orig_width, orig_height, scale_width, scale_height;
2665 
2666 	if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
2667 		buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
2668 		contact = (PurpleContact*)node;
2669 	} else if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
2670 		buddy = (PurpleBuddy*)node;
2671 		contact = purple_buddy_get_contact(buddy);
2672 	} else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
2673 		group = (PurpleGroup*)node;
2674 	} else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
2675 		/* We don't need to do anything here. We just need to not fall
2676 		 * into the else block and return. */
2677 	} else {
2678 		return NULL;
2679 	}
2680 
2681 	if (buddy) {
2682 		account = purple_buddy_get_account(buddy);
2683 	}
2684 
2685 	if(account && account->gc) {
2686 		prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(account->gc->prpl);
2687 	}
2688 
2689 #if 0
2690 	if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons"))
2691 		return NULL;
2692 #endif
2693 
2694 	/* If we have a contact then this is either a contact or a buddy and
2695 	 * we want to fetch the custom icon for the contact. If we don't have
2696 	 * a contact then this is a group or some other type of node and we
2697 	 * want to use that directly. */
2698 	if (contact) {
2699 		custom_img = purple_buddy_icons_node_find_custom_icon((PurpleBlistNode*)contact);
2700 	} else {
2701 		custom_img = purple_buddy_icons_node_find_custom_icon(node);
2702 	}
2703 
2704 	if (custom_img) {
2705 		data = purple_imgstore_get_data(custom_img);
2706 		len = purple_imgstore_get_size(custom_img);
2707 	}
2708 
2709 	if (data == NULL) {
2710 		if (buddy) {
2711 			/* Not sure I like this...*/
2712 			if (!(icon = purple_buddy_icons_find(buddy->account, buddy->name)))
2713 				return NULL;
2714 			data = purple_buddy_icon_get_data(icon, &len);
2715 		}
2716 
2717 		if(data == NULL)
2718 			return NULL;
2719 	}
2720 
2721 	buf = pidgin_pixbuf_from_data(data, len);
2722 	purple_buddy_icon_unref(icon);
2723 	if (!buf) {
2724 		purple_debug_warning("gtkblist", "Couldn't load buddy icon "
2725 				"on account %s (%s)  buddyname=%s  "
2726 				"custom_img_data=%p\n",
2727 				account ? purple_account_get_username(account) : "(no account)",
2728 				account ? purple_account_get_protocol_id(account) : "(no account)",
2729 				buddy ? purple_buddy_get_name(buddy) : "(no buddy)",
2730 				custom_img ? purple_imgstore_get_data(custom_img) : NULL);
2731 		purple_imgstore_unref(custom_img);
2732 		return NULL;
2733 	}
2734 	purple_imgstore_unref(custom_img);
2735 
2736 	if (greyed) {
2737 		gboolean offline = FALSE, idle = FALSE;
2738 
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_blist_get_group_online_count(group) == 0)
2747 				offline = TRUE;
2748 		}
2749 
2750 		if (offline)
2751 			gdk_pixbuf_saturate_and_pixelate(buf, buf, 0.0, FALSE);
2752 
2753 		if (idle)
2754 			gdk_pixbuf_saturate_and_pixelate(buf, buf, 0.25, FALSE);
2755 	}
2756 
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);
2761 
2762 	if (prpl_info && prpl_info->icon_spec.scale_rules & PURPLE_ICON_SCALE_DISPLAY)
2763 		purple_buddy_icon_get_scale_size(&prpl_info->icon_spec, &scale_width, &scale_height);
2764 
2765 	if (scaled || scale_height > 200 || scale_width > 200) {
2766 		GdkPixbuf *tmpbuf;
2767 		float scale_size = scaled ? 32.0 : 200.0;
2768 		if(scale_height > scale_width) {
2769 			scale_width = scale_size * (double)scale_width / (double)scale_height;
2770 			scale_height = scale_size;
2771 		} else {
2772 			scale_height = scale_size * (double)scale_height / (double)scale_width;
2773 			scale_width = scale_size;
2774 		}
2775 		/* Scale & round before making square, so rectangular (but
2776 		 * non-square) images get rounded corners too. */
2777 		tmpbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, scale_width, scale_height);
2778 		gdk_pixbuf_fill(tmpbuf, 0x00000000);
2779 		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);
2780 		if (pidgin_gdk_pixbuf_is_opaque(tmpbuf))
2781 			pidgin_gdk_pixbuf_make_round(tmpbuf);
2782 		ret = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, scale_size, scale_size);
2783 		gdk_pixbuf_fill(ret, 0x00000000);
2784 		gdk_pixbuf_copy_area(tmpbuf, 0, 0, scale_width, scale_height, ret, (scale_size-scale_width)/2, (scale_size-scale_height)/2);
2785 		g_object_unref(G_OBJECT(tmpbuf));
2786 	} else {
2787 		ret = gdk_pixbuf_scale_simple(buf,scale_width,scale_height, GDK_INTERP_BILINEAR);
2788 	}
2789 	g_object_unref(G_OBJECT(buf));
2790 
2791 	return ret;
2792 }
2793 
2794 /* # - Status Icon
2795  * P - Protocol Icon
2796  * A - Buddy Icon
2797  * [ - SMALL_SPACE
2798  * = - LARGE_SPACE
2799  *                   +--- STATUS_SIZE                +--- td->avatar_width
2800  *                   |         +-- td->name_width    |
2801  *                +----+   +-------+            +---------+
2802  *                |    |   |       |            |         |
2803  *                +-------------------------------------------+
2804  *                |       [          =        [               |--- TOOLTIP_BORDER
2805  *name_height --+-| ######[BuddyName = PP     [   AAAAAAAAAAA |--+
2806  *              | | ######[          = PP     [   AAAAAAAAAAA |  |
2807  * STATUS SIZE -| | ######[[[[[[[[[[[[[[[[[[[[[   AAAAAAAAAAA |  |
2808  *           +--+-| ######[Account: So-and-so [   AAAAAAAAAAA |  |-- td->avatar_height
2809  *           |    |       [Idle: 4h 15m       [   AAAAAAAAAAA |  |
2810  *  height --+    |       [Foo: Bar, Baz      [   AAAAAAAAAAA |  |
2811  *           |    |       [Status: Awesome    [   AAAAAAAAAAA |--+
2812  *           +----|       [Stop: Hammer Time  [               |
2813  *                |       [                   [               |--- TOOLTIP_BORDER
2814  *                +-------------------------------------------+
2815  *                 |       |                |                |
2816  *                 |       +----------------+                |
2817  *                 |               |                         |
2818  *                 |               +-- td->width             |
2819  *                 |                                         |
2820  *                 +---- TOOLTIP_BORDER                      +---- TOOLTIP_BORDER
2821  *
2822  *
2823  */
2824 #define STATUS_SIZE 16
2825 #define TOOLTIP_BORDER 12
2826 #define SMALL_SPACE 6
2827 #define LARGE_SPACE 12
2828 #define PRPL_SIZE 16
2829 struct tooltip_data {
2830 	PangoLayout *layout;
2831 	PangoLayout *name_layout;
2832 	GdkPixbuf *prpl_icon;
2833 	GdkPixbuf *status_icon;
2834 	GdkPixbuf *avatar;
2835 	gboolean avatar_is_prpl_icon;
2836 	int avatar_width;
2837 	int avatar_height;
2838 	int name_height;
2839 	int name_width;
2840 	int width;
2841 	int height;
2842 	int padding;
2843 };
2844 
create_pango_layout(const char * markup,int * width,int * height)2845 static PangoLayout * create_pango_layout(const char *markup, int *width, int *height)
2846 {
2847 	PangoLayout *layout;
2848 	int w, h;
2849 
2850 	layout = gtk_widget_create_pango_layout(gtkblist->tipwindow, NULL);
2851 	pango_layout_set_markup(layout, markup, -1);
2852 	pango_layout_set_wrap(layout, PANGO_WRAP_WORD);
2853 	pango_layout_set_width(layout, 300000);
2854 
2855 	pango_layout_get_size (layout, &w, &h);
2856 	if (width)
2857 		*width = PANGO_PIXELS(w);
2858 	if (height)
2859 		*height = PANGO_PIXELS(h);
2860 	return layout;
2861 }
2862 
create_tip_for_account(PurpleAccount * account)2863 static struct tooltip_data * create_tip_for_account(PurpleAccount *account)
2864 {
2865 	struct tooltip_data *td = g_new0(struct tooltip_data, 1);
2866 	td->status_icon = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL);
2867 		/* Yes, status_icon, not prpl_icon */
2868 	if (purple_account_is_disconnected(account))
2869 		gdk_pixbuf_saturate_and_pixelate(td->status_icon, td->status_icon, 0.0, FALSE);
2870 	td->layout = create_pango_layout(purple_account_get_username(account), &td->width, &td->height);
2871 	td->padding = SMALL_SPACE;
2872 	return td;
2873 }
2874 
create_tip_for_node(PurpleBlistNode * node,gboolean full)2875 static struct tooltip_data * create_tip_for_node(PurpleBlistNode *node, gboolean full)
2876 {
2877 	struct tooltip_data *td = g_new0(struct tooltip_data, 1);
2878 	PurpleAccount *account = NULL;
2879 	char *tmp = NULL, *node_name = NULL, *tooltip_text = NULL;
2880 
2881 	if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
2882 		account = ((PurpleBuddy*)(node))->account;
2883 	} else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
2884 		account = ((PurpleChat*)(node))->account;
2885 	}
2886 
2887 	td->padding = TOOLTIP_BORDER;
2888 	td->status_icon = pidgin_blist_get_status_icon(node, PIDGIN_STATUS_ICON_LARGE);
2889 	td->avatar = pidgin_blist_get_buddy_icon(node, !full, FALSE);
2890 	if (account != NULL) {
2891 		td->prpl_icon = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL);
2892 	}
2893 	tooltip_text = pidgin_get_tooltip_text(node, full);
2894 	if (tooltip_text && *tooltip_text) {
2895 		td->layout = create_pango_layout(tooltip_text, &td->width, &td->height);
2896 	}
2897 
2898 	if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
2899 		tmp = g_markup_escape_text(purple_buddy_get_name((PurpleBuddy*)node), -1);
2900 	} else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
2901 		tmp = g_markup_escape_text(purple_chat_get_name((PurpleChat*)node), -1);
2902 	} else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
2903 		tmp = g_markup_escape_text(purple_group_get_name((PurpleGroup*)node), -1);
2904 	} else {
2905 		/* I don't believe this can happen currently, I think
2906 		 * everything that calls this function checks for one of the
2907 		 * above node types first. */
2908 		tmp = g_strdup(_("Unknown node type"));
2909 	}
2910 	node_name = g_strdup_printf("<span size='x-large' weight='bold'>%s</span>",
2911 								tmp ? tmp : "");
2912 	g_free(tmp);
2913 
2914 	td->name_layout = create_pango_layout(node_name, &td->name_width, &td->name_height);
2915 	td->name_width += SMALL_SPACE + PRPL_SIZE;
2916 	td->name_height = MAX(td->name_height, PRPL_SIZE + SMALL_SPACE);
2917 #if 0  /* PRPL Icon as avatar */
2918 	if(!td->avatar && full) {
2919 		td->avatar = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_LARGE);
2920 		td->avatar_is_prpl_icon = TRUE;
2921 	}
2922 #endif
2923 
2924 	if (td->avatar) {
2925 		td->avatar_width = gdk_pixbuf_get_width(td->avatar);
2926 		td->avatar_height = gdk_pixbuf_get_height(td->avatar);
2927 	}
2928 
2929 	g_free(node_name);
2930 	g_free(tooltip_text);
2931 	return td;
2932 }
2933 
2934 static gboolean
pidgin_blist_paint_tip(GtkWidget * widget,gpointer null)2935 pidgin_blist_paint_tip(GtkWidget *widget, gpointer null)
2936 {
2937 	GtkStyle *style;
2938 	int current_height, max_width;
2939 	int max_text_width;
2940 	int max_avatar_width;
2941 	GList *l;
2942 	int prpl_col = 0;
2943 	GtkTextDirection dir = gtk_widget_get_direction(widget);
2944 	int status_size = 0;
2945 
2946 	if(gtkblist->tooltipdata == NULL)
2947 		return FALSE;
2948 
2949 	style = gtkblist->tipwindow->style;
2950 
2951 	max_text_width = 0;
2952 	max_avatar_width = 0;
2953 
2954 	for(l = gtkblist->tooltipdata; l; l = l->next)
2955 	{
2956 		struct tooltip_data *td = l->data;
2957 
2958 		max_text_width = MAX(max_text_width,
2959 				MAX(td->width, td->name_width));
2960 		max_avatar_width = MAX(max_avatar_width, td->avatar_width);
2961 		if (td->status_icon)
2962 			status_size = STATUS_SIZE;
2963 	}
2964 
2965 	max_width = TOOLTIP_BORDER + status_size + SMALL_SPACE + max_text_width + SMALL_SPACE + max_avatar_width + TOOLTIP_BORDER;
2966 	if (dir == GTK_TEXT_DIR_RTL)
2967 		prpl_col = TOOLTIP_BORDER + max_avatar_width + SMALL_SPACE;
2968 	else
2969 		prpl_col = TOOLTIP_BORDER + status_size + SMALL_SPACE + max_text_width - PRPL_SIZE;
2970 
2971 	current_height = 12;
2972 	for(l = gtkblist->tooltipdata; l; l = l->next)
2973 	{
2974 		struct tooltip_data *td = l->data;
2975 
2976 		if (td->avatar && pidgin_gdk_pixbuf_is_opaque(td->avatar))
2977 		{
2978 			if (dir == GTK_TEXT_DIR_RTL)
2979 				gtk_paint_flat_box(style, gtkblist->tipwindow->window, GTK_STATE_NORMAL, GTK_SHADOW_OUT,
2980 						NULL, gtkblist->tipwindow, "tooltip",
2981 						TOOLTIP_BORDER -1, current_height -1, td->avatar_width +2, td->avatar_height + 2);
2982 			else
2983 				gtk_paint_flat_box(style, gtkblist->tipwindow->window, GTK_STATE_NORMAL, GTK_SHADOW_OUT,
2984 						NULL, gtkblist->tipwindow, "tooltip",
2985 						max_width - (td->avatar_width+ TOOLTIP_BORDER)-1,
2986 						current_height-1,td->avatar_width+2, td->avatar_height+2);
2987 		}
2988 
2989 		if (td->status_icon) {
2990 			if (dir == GTK_TEXT_DIR_RTL)
2991 				gdk_draw_pixbuf(GDK_DRAWABLE(gtkblist->tipwindow->window), NULL, td->status_icon,
2992 				                0, 0, max_width - TOOLTIP_BORDER - status_size, current_height, -1, -1, GDK_RGB_DITHER_NONE, 0, 0);
2993 			else
2994 				gdk_draw_pixbuf(GDK_DRAWABLE(gtkblist->tipwindow->window), NULL, td->status_icon,
2995 				                0, 0, TOOLTIP_BORDER, current_height, -1 , -1, GDK_RGB_DITHER_NONE, 0, 0);
2996 		}
2997 
2998 		if(td->avatar) {
2999 			if (dir == GTK_TEXT_DIR_RTL)
3000 				gdk_draw_pixbuf(GDK_DRAWABLE(gtkblist->tipwindow->window), NULL,
3001 						td->avatar, 0, 0, TOOLTIP_BORDER, current_height, -1, -1, GDK_RGB_DITHER_NONE, 0, 0);
3002 			else
3003 				gdk_draw_pixbuf(GDK_DRAWABLE(gtkblist->tipwindow->window), NULL,
3004 						td->avatar, 0, 0, max_width - (td->avatar_width + TOOLTIP_BORDER),
3005 						current_height, -1 , -1, GDK_RGB_DITHER_NONE, 0, 0);
3006 		}
3007 
3008 		if (!td->avatar_is_prpl_icon && td->prpl_icon)
3009 			gdk_draw_pixbuf(GDK_DRAWABLE(gtkblist->tipwindow->window), NULL, td->prpl_icon,
3010 					0, 0,
3011 					prpl_col,
3012 					current_height + ((td->name_height / 2) - (PRPL_SIZE / 2)),
3013 					-1 , -1, GDK_RGB_DITHER_NONE, 0, 0);
3014 
3015 		if (td->name_layout) {
3016 			if (dir == GTK_TEXT_DIR_RTL) {
3017 				gtk_paint_layout(style, gtkblist->tipwindow->window, GTK_STATE_NORMAL, FALSE,
3018 						NULL, gtkblist->tipwindow, "tooltip",
3019 						max_width  -(TOOLTIP_BORDER + status_size + SMALL_SPACE) - PANGO_PIXELS(300000),
3020 						current_height, td->name_layout);
3021 			} else {
3022 				gtk_paint_layout (style, gtkblist->tipwindow->window, GTK_STATE_NORMAL, FALSE,
3023 						NULL, gtkblist->tipwindow, "tooltip",
3024 						TOOLTIP_BORDER + status_size + SMALL_SPACE, current_height, td->name_layout);
3025 			}
3026 		}
3027 
3028 		if (td->layout) {
3029 			if (dir != GTK_TEXT_DIR_RTL) {
3030 				gtk_paint_layout (style, gtkblist->tipwindow->window, GTK_STATE_NORMAL, FALSE,
3031 						NULL, gtkblist->tipwindow, "tooltip",
3032 						TOOLTIP_BORDER + status_size + SMALL_SPACE, current_height + td->name_height, td->layout);
3033 			} else {
3034 				gtk_paint_layout(style, gtkblist->tipwindow->window, GTK_STATE_NORMAL, FALSE,
3035 						NULL, gtkblist->tipwindow, "tooltip",
3036 						max_width - (TOOLTIP_BORDER + status_size + SMALL_SPACE) - PANGO_PIXELS(300000),
3037 						current_height + td->name_height,
3038 						td->layout);
3039 			}
3040 		}
3041 
3042 		current_height += MAX(td->name_height + td->height, td->avatar_height) + td->padding;
3043 	}
3044 	return FALSE;
3045 }
3046 
3047 static void
pidgin_blist_destroy_tooltip_data(void)3048 pidgin_blist_destroy_tooltip_data(void)
3049 {
3050 	while(gtkblist->tooltipdata) {
3051 		struct tooltip_data *td = gtkblist->tooltipdata->data;
3052 
3053 		if(td->avatar)
3054 			g_object_unref(td->avatar);
3055 		if(td->status_icon)
3056 			g_object_unref(td->status_icon);
3057 		if(td->prpl_icon)
3058 			g_object_unref(td->prpl_icon);
3059 		if (td->layout)
3060 			g_object_unref(td->layout);
3061 		if (td->name_layout)
3062 			g_object_unref(td->name_layout);
3063 		g_free(td);
3064 		gtkblist->tooltipdata = g_list_delete_link(gtkblist->tooltipdata, gtkblist->tooltipdata);
3065 	}
3066 }
3067 
pidgin_blist_tooltip_destroy()3068 void pidgin_blist_tooltip_destroy()
3069 {
3070 	pidgin_blist_destroy_tooltip_data();
3071 	pidgin_tooltip_destroy();
3072 }
3073 
3074 static void
pidgin_blist_align_tooltip(struct tooltip_data * td,GtkWidget * widget)3075 pidgin_blist_align_tooltip(struct tooltip_data *td, GtkWidget *widget)
3076 {
3077 	GtkTextDirection dir = gtk_widget_get_direction(widget);
3078 
3079 	if (dir == GTK_TEXT_DIR_RTL)
3080 	{
3081 		char* layout_name = purple_markup_strip_html(pango_layout_get_text(td->name_layout));
3082 		PangoDirection dir = pango_find_base_dir(layout_name, -1);
3083 		if (dir == PANGO_DIRECTION_RTL || dir == PANGO_DIRECTION_NEUTRAL)
3084 			pango_layout_set_alignment(td->name_layout, PANGO_ALIGN_RIGHT);
3085 		g_free(layout_name);
3086 		pango_layout_set_alignment(td->layout, PANGO_ALIGN_RIGHT);
3087 	}
3088 }
3089 
3090 static gboolean
pidgin_blist_create_tooltip_for_node(GtkWidget * widget,gpointer data,int * w,int * h)3091 pidgin_blist_create_tooltip_for_node(GtkWidget *widget, gpointer data, int *w, int *h)
3092 {
3093 	PurpleBlistNode *node = data;
3094 	int width, height;
3095 	GList *list;
3096 	int max_text_width = 0;
3097 	int max_avatar_width = 0;
3098 	int status_size = 0;
3099 
3100 	if (gtkblist->tooltipdata) {
3101 		gtkblist->tipwindow = NULL;
3102 		pidgin_blist_destroy_tooltip_data();
3103 	}
3104 
3105 	gtkblist->tipwindow = widget;
3106 	if (PURPLE_BLIST_NODE_IS_CHAT(node) ||
3107 	   PURPLE_BLIST_NODE_IS_BUDDY(node)) {
3108 		struct tooltip_data *td = create_tip_for_node(node, TRUE);
3109 		pidgin_blist_align_tooltip(td, gtkblist->tipwindow);
3110 		gtkblist->tooltipdata = g_list_append(gtkblist->tooltipdata, td);
3111 	} else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
3112 		PurpleGroup *group = (PurpleGroup*)node;
3113 		GSList *accounts;
3114 		struct tooltip_data *td = create_tip_for_node(node, TRUE);
3115 		pidgin_blist_align_tooltip(td, gtkblist->tipwindow);
3116 		gtkblist->tooltipdata = g_list_append(gtkblist->tooltipdata, td);
3117 
3118 		/* Accounts with buddies in group */
3119 		accounts = purple_group_get_accounts(group);
3120 		for (; accounts != NULL;
3121 		     accounts = g_slist_delete_link(accounts, accounts)) {
3122 			PurpleAccount *account = accounts->data;
3123 			td = create_tip_for_account(account);
3124 			gtkblist->tooltipdata = g_list_append(gtkblist->tooltipdata, td);
3125 		}
3126 	} else if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
3127 		PurpleBlistNode *child;
3128 		PurpleBuddy *b = purple_contact_get_priority_buddy((PurpleContact *)node);
3129 
3130 		for(child = node->child; child; child = child->next)
3131 		{
3132 			if(PURPLE_BLIST_NODE_IS_BUDDY(child) && buddy_is_displayable((PurpleBuddy*)child)) {
3133 				struct tooltip_data *td = create_tip_for_node(child, (b == (PurpleBuddy*)child));
3134 				pidgin_blist_align_tooltip(td, gtkblist->tipwindow);
3135 				if (b == (PurpleBuddy *)child) {
3136 					gtkblist->tooltipdata = g_list_prepend(gtkblist->tooltipdata, td);
3137 				} else {
3138 					gtkblist->tooltipdata = g_list_append(gtkblist->tooltipdata, td);
3139 				}
3140 			}
3141 		}
3142 	} else {
3143 		return FALSE;
3144 	}
3145 
3146 	height = 0;
3147 	for (list = gtkblist->tooltipdata; list; list = list->next) {
3148 		struct tooltip_data *td = list->data;
3149 		max_text_width = MAX(max_text_width, MAX(td->width, td->name_width));
3150 		max_avatar_width = MAX(max_avatar_width, td->avatar_width);
3151 		height += MAX(MAX(STATUS_SIZE, td->avatar_height), td->height + td->name_height) + td->padding;
3152 		if (td->status_icon)
3153 			status_size = MAX(status_size, STATUS_SIZE);
3154 	}
3155 	height += TOOLTIP_BORDER;
3156 	width = TOOLTIP_BORDER + status_size + SMALL_SPACE + max_text_width + SMALL_SPACE + max_avatar_width + TOOLTIP_BORDER;
3157 
3158 	if (w)
3159 		*w = width;
3160 	if (h)
3161 		*h = height;
3162 
3163 	return TRUE;
3164 }
3165 
pidgin_blist_expand_timeout(GtkWidget * tv)3166 static gboolean pidgin_blist_expand_timeout(GtkWidget *tv)
3167 {
3168 	GtkTreePath *path;
3169 	GtkTreeIter iter;
3170 	PurpleBlistNode *node;
3171 	struct _pidgin_blist_node *gtknode;
3172 
3173 	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),
3174 		&path, NULL, NULL, NULL))
3175 		return FALSE;
3176 	gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
3177 	gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
3178 
3179 	if(!PURPLE_BLIST_NODE_IS_CONTACT(node)) {
3180 		gtk_tree_path_free(path);
3181 		return FALSE;
3182 	}
3183 
3184 	gtknode = node->ui_data;
3185 
3186 	if (!gtknode->contact_expanded) {
3187 		GtkTreeIter i;
3188 
3189 		pidgin_blist_expand_contact_cb(NULL, node);
3190 
3191 		gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, &gtkblist->contact_rect);
3192 		gdk_drawable_get_size(GDK_DRAWABLE(tv->window), &(gtkblist->contact_rect.width), NULL);
3193 		gtkblist->mouseover_contact = node;
3194 		gtk_tree_path_down (path);
3195 		while (gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &i, path)) {
3196 			GdkRectangle rect;
3197 			gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, &rect);
3198 			gtkblist->contact_rect.height += rect.height;
3199 			gtk_tree_path_next(path);
3200 		}
3201 	}
3202 	gtk_tree_path_free(path);
3203 	return FALSE;
3204 }
3205 
buddy_is_displayable(PurpleBuddy * buddy)3206 static gboolean buddy_is_displayable(PurpleBuddy *buddy)
3207 {
3208 	struct _pidgin_blist_node *gtknode;
3209 
3210 	if(!buddy || !PURPLE_BLIST_NODE_IS_VISIBLE(buddy))
3211 		return FALSE;
3212 
3213 	gtknode = ((PurpleBlistNode*)buddy)->ui_data;
3214 
3215 	return (purple_account_is_connected(buddy->account) &&
3216 			(purple_presence_is_online(buddy->presence) ||
3217 			 (gtknode && gtknode->recent_signonoff) ||
3218 			 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_offline_buddies") ||
3219 			 purple_blist_node_get_bool((PurpleBlistNode*)buddy, "show_offline")));
3220 }
3221 
pidgin_blist_draw_tooltip(PurpleBlistNode * node,GtkWidget * widget)3222 void pidgin_blist_draw_tooltip(PurpleBlistNode *node, GtkWidget *widget)
3223 {
3224 	pidgin_tooltip_show(widget, node, pidgin_blist_create_tooltip_for_node, pidgin_blist_paint_tip);
3225 }
3226 
pidgin_blist_drag_motion_cb(GtkWidget * tv,GdkDragContext * drag_context,gint x,gint y,guint time,gpointer user_data)3227 static gboolean pidgin_blist_drag_motion_cb(GtkWidget *tv, GdkDragContext *drag_context,
3228 					      gint x, gint y, guint time, gpointer user_data)
3229 {
3230 	GtkTreePath *path;
3231 	int delay;
3232 	GdkRectangle rect;
3233 
3234 	/*
3235 	 * When dragging a buddy into a contact, this is the delay before
3236 	 * the contact auto-expands.
3237 	 */
3238 	delay = 900;
3239 
3240 	if (gtkblist->drag_timeout) {
3241 		if ((y > gtkblist->tip_rect.y) && ((y - gtkblist->tip_rect.height) < gtkblist->tip_rect.y))
3242 			return FALSE;
3243 		/* We've left the cell.  Remove the timeout and create a new one below */
3244 		g_source_remove(gtkblist->drag_timeout);
3245 	}
3246 
3247 	gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), x, y, &path, NULL, NULL, NULL);
3248 	gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, &rect);
3249 
3250 	if (path)
3251 		gtk_tree_path_free(path);
3252 
3253 	/* Only autoexpand when in the middle of the cell to avoid annoying un-intended expands */
3254 	if (y < rect.y + (rect.height / 3) ||
3255 	    y > rect.y + (2 * (rect.height /3)))
3256 		return FALSE;
3257 
3258 	rect.height = rect.height / 3;
3259 	rect.y += rect.height;
3260 
3261 	gtkblist->tip_rect = rect;
3262 
3263 	gtkblist->drag_timeout = g_timeout_add(delay, (GSourceFunc)pidgin_blist_expand_timeout, tv);
3264 
3265 	if (gtkblist->mouseover_contact) {
3266 		if ((y < gtkblist->contact_rect.y) || ((y - gtkblist->contact_rect.height) > gtkblist->contact_rect.y)) {
3267 			pidgin_blist_collapse_contact_cb(NULL, gtkblist->mouseover_contact);
3268 			gtkblist->mouseover_contact = NULL;
3269 		}
3270 	}
3271 
3272 	return FALSE;
3273 }
3274 
3275 static gboolean
pidgin_blist_create_tooltip(GtkWidget * widget,GtkTreePath * path,gpointer null,int * w,int * h)3276 pidgin_blist_create_tooltip(GtkWidget *widget, GtkTreePath *path,
3277 		gpointer null, int *w, int *h)
3278 {
3279 	GtkTreeIter iter;
3280 	PurpleBlistNode *node;
3281 	gboolean editable = FALSE;
3282 
3283 	/* If we're editing a cell (e.g. alias editing), don't show the tooltip */
3284 	g_object_get(G_OBJECT(gtkblist->text_rend), "editable", &editable, NULL);
3285 	if (editable)
3286 		return FALSE;
3287 
3288 	if (gtkblist->tooltipdata) {
3289 		gtkblist->tipwindow = NULL;
3290 		pidgin_blist_destroy_tooltip_data();
3291 	}
3292 
3293 	gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
3294 	gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
3295 
3296 	return pidgin_blist_create_tooltip_for_node(widget, node, w, h);
3297 }
3298 
pidgin_blist_motion_cb(GtkWidget * tv,GdkEventMotion * event,gpointer null)3299 static gboolean pidgin_blist_motion_cb (GtkWidget *tv, GdkEventMotion *event, gpointer null)
3300 {
3301 	if (gtkblist->mouseover_contact) {
3302 		if ((event->y < gtkblist->contact_rect.y) || ((event->y - gtkblist->contact_rect.height) > gtkblist->contact_rect.y)) {
3303 			pidgin_blist_collapse_contact_cb(NULL, gtkblist->mouseover_contact);
3304 			gtkblist->mouseover_contact = NULL;
3305 		}
3306 	}
3307 
3308 	return FALSE;
3309 }
3310 
pidgin_blist_leave_cb(GtkWidget * w,GdkEventCrossing * e,gpointer n)3311 static gboolean pidgin_blist_leave_cb (GtkWidget *w, GdkEventCrossing *e, gpointer n)
3312 {
3313 	if (gtkblist->timeout) {
3314 		g_source_remove(gtkblist->timeout);
3315 		gtkblist->timeout = 0;
3316 	}
3317 
3318 	if (gtkblist->drag_timeout) {
3319 		g_source_remove(gtkblist->drag_timeout);
3320 		gtkblist->drag_timeout = 0;
3321 	}
3322 
3323 	if (gtkblist->mouseover_contact &&
3324 		!((e->x > gtkblist->contact_rect.x) && (e->x < (gtkblist->contact_rect.x + gtkblist->contact_rect.width)) &&
3325 		 (e->y > gtkblist->contact_rect.y) && (e->y < (gtkblist->contact_rect.y + gtkblist->contact_rect.height)))) {
3326 			pidgin_blist_collapse_contact_cb(NULL, gtkblist->mouseover_contact);
3327 		gtkblist->mouseover_contact = NULL;
3328 	}
3329 	return FALSE;
3330 }
3331 
3332 static void
toggle_debug(void)3333 toggle_debug(void)
3334 {
3335 	purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/debug/enabled",
3336 			!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/debug/enabled"));
3337 }
3338 
get_mood_icon_path(const char * mood)3339 static char *get_mood_icon_path(const char *mood)
3340 {
3341 	char *path;
3342 
3343 	if (purple_strequal(mood, "busy")) {
3344 		path = g_build_filename(DATADIR, "pixmaps", "pidgin",
3345 		                        "status", "16", "busy.png", NULL);
3346 	} else if (purple_strequal(mood, "hiptop")) {
3347 		path = g_build_filename(DATADIR, "pixmaps", "pidgin",
3348 		                        "emblems", "16", "hiptop.png", NULL);
3349 	} else {
3350 		char *filename = g_strdup_printf("%s.png", mood);
3351 		path = g_build_filename(DATADIR, "pixmaps", "pidgin",
3352 		                        "emotes", "small", filename, NULL);
3353 		g_free(filename);
3354 	}
3355 	return path;
3356 }
3357 
3358 static void
update_status_with_mood(PurpleAccount * account,const gchar * mood,const gchar * text)3359 update_status_with_mood(PurpleAccount *account, const gchar *mood,
3360     const gchar *text)
3361 {
3362 	if (mood && *mood) {
3363 		if (text) {
3364 			purple_account_set_status(account, "mood", TRUE,
3365 			                          PURPLE_MOOD_NAME, mood,
3366 				    				  PURPLE_MOOD_COMMENT, text,
3367 			                          NULL);
3368 		} else {
3369 			purple_account_set_status(account, "mood", TRUE,
3370 			                          PURPLE_MOOD_NAME, mood,
3371 			                          NULL);
3372 		}
3373 	} else {
3374 		purple_account_set_status(account, "mood", FALSE, NULL);
3375 	}
3376 }
3377 
3378 static void
edit_mood_cb(PurpleConnection * gc,PurpleRequestFields * fields)3379 edit_mood_cb(PurpleConnection *gc, PurpleRequestFields *fields)
3380 {
3381 	PurpleRequestField *mood_field;
3382 	GList *l;
3383 
3384 	mood_field = purple_request_fields_get_field(fields, "mood");
3385 	l = purple_request_field_list_get_selected(mood_field);
3386 
3387 	if (l) {
3388 		const char *mood = purple_request_field_list_get_data(mood_field, l->data);
3389 
3390 		if (gc) {
3391 			const char *text;
3392 			PurpleAccount *account = purple_connection_get_account(gc);
3393 
3394 			if (gc->flags & PURPLE_CONNECTION_SUPPORT_MOOD_MESSAGES) {
3395 				PurpleRequestField *text_field;
3396 				text_field = purple_request_fields_get_field(fields, "text");
3397 				text = purple_request_field_string_get_value(text_field);
3398 			} else {
3399 				text = NULL;
3400 			}
3401 
3402 			update_status_with_mood(account, mood, text);
3403 		} else {
3404 			GList *accounts = purple_accounts_get_all_active();
3405 
3406 			for (; accounts ; accounts = g_list_delete_link(accounts, accounts)) {
3407 				PurpleAccount *account = (PurpleAccount *) accounts->data;
3408 				PurpleConnection *gc = purple_account_get_connection(account);
3409 
3410 				if (gc && gc->flags & PURPLE_CONNECTION_SUPPORT_MOODS) {
3411 					update_status_with_mood(account, mood, NULL);
3412 				}
3413 			}
3414 		}
3415 	}
3416 }
3417 
3418 static void
global_moods_for_each(gpointer key,gpointer value,gpointer user_data)3419 global_moods_for_each(gpointer key, gpointer value, gpointer user_data)
3420 {
3421 	GList **out_moods = (GList **) user_data;
3422 	PurpleMood *mood = (PurpleMood *) value;
3423 
3424 	*out_moods = g_list_append(*out_moods, mood);
3425 }
3426 
3427 static PurpleMood *
get_global_moods(void)3428 get_global_moods(void)
3429 {
3430 	GHashTable *global_moods =
3431 		g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
3432 	GHashTable *mood_counts =
3433 		g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
3434 	GList *accounts = purple_accounts_get_all_active();
3435 	PurpleMood *result = NULL;
3436 	GList *out_moods = NULL;
3437 	int i = 0;
3438 	int num_accounts = 0;
3439 
3440 	for (; accounts ; accounts = g_list_delete_link(accounts, accounts)) {
3441 		PurpleAccount *account = (PurpleAccount *) accounts->data;
3442 		if (purple_account_is_connected(account)) {
3443 			PurpleConnection *gc = purple_account_get_connection(account);
3444 
3445 			if (gc->flags & PURPLE_CONNECTION_SUPPORT_MOODS) {
3446 				PurplePluginProtocolInfo *prpl_info =
3447 					PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
3448 				PurpleMood *mood = NULL;
3449 
3450 				/* PURPLE_CONNECTION_SUPPORT_MOODS would not be set if the prpl doesn't
3451 				 * have get_moods, so using PURPLE_PROTOCOL_PLUGIN_HAS_FUNC isn't necessary
3452 				 * here */
3453 				for (mood = prpl_info->get_moods(account) ;
3454 				    mood->mood != NULL ; mood++) {
3455 					int mood_count =
3456 							GPOINTER_TO_INT(g_hash_table_lookup(mood_counts, mood->mood));
3457 
3458 					if (!g_hash_table_lookup(global_moods, mood->mood)) {
3459 						g_hash_table_insert(global_moods, (gpointer)mood->mood, mood);
3460 					}
3461 					g_hash_table_insert(mood_counts, (gpointer)mood->mood,
3462 					    GINT_TO_POINTER(mood_count + 1));
3463 				}
3464 
3465 				num_accounts++;
3466 			}
3467 		}
3468 	}
3469 
3470 	g_hash_table_foreach(global_moods, global_moods_for_each, &out_moods);
3471 	result = g_new0(PurpleMood, g_hash_table_size(global_moods) + 1);
3472 
3473 	while (out_moods) {
3474 		PurpleMood *mood = (PurpleMood *) out_moods->data;
3475 		int in_num_accounts =
3476 			GPOINTER_TO_INT(g_hash_table_lookup(mood_counts, mood->mood));
3477 
3478 		if (in_num_accounts == num_accounts) {
3479 			/* mood is present in all accounts supporting moods */
3480 			result[i].mood = mood->mood;
3481 			result[i].description = mood->description;
3482 			i++;
3483 		}
3484 		out_moods = g_list_delete_link(out_moods, out_moods);
3485 	}
3486 
3487 	g_hash_table_destroy(global_moods);
3488 	g_hash_table_destroy(mood_counts);
3489 
3490 	return result;
3491 }
3492 
3493 /* get current set mood for all mood-supporting accounts, or NULL if not set
3494  or not set to the same on all */
3495 static const gchar *
get_global_mood_status(void)3496 get_global_mood_status(void)
3497 {
3498 	GList *accounts = purple_accounts_get_all_active();
3499 	const gchar *found_mood = NULL;
3500 
3501 	for (; accounts ; accounts = g_list_delete_link(accounts, accounts)) {
3502 		PurpleAccount *account = (PurpleAccount *) accounts->data;
3503 
3504 		if (purple_account_is_connected(account) &&
3505 		    (purple_account_get_connection(account)->flags &
3506 		     PURPLE_CONNECTION_SUPPORT_MOODS)) {
3507 			PurplePresence *presence = purple_account_get_presence(account);
3508 			PurpleStatus *status = purple_presence_get_status(presence, "mood");
3509 			const gchar *curr_mood = purple_status_get_attr_string(status, PURPLE_MOOD_NAME);
3510 
3511 			if (found_mood != NULL && !purple_strequal(curr_mood, found_mood)) {
3512 				/* found a different mood */
3513 				found_mood = NULL;
3514 				break;
3515 			} else {
3516 				found_mood = curr_mood;
3517 			}
3518 		}
3519 	}
3520 
3521 	return found_mood;
3522 }
3523 
3524 static void
set_mood_cb(GtkWidget * widget,PurpleAccount * account)3525 set_mood_cb(GtkWidget *widget, PurpleAccount *account)
3526 {
3527 	const char *current_mood;
3528 	PurpleRequestFields *fields;
3529 	PurpleRequestFieldGroup *g;
3530 	PurpleRequestField *f;
3531 	PurpleConnection *gc = NULL;
3532 	PurplePluginProtocolInfo *prpl_info = NULL;
3533 	PurpleMood *mood;
3534 	PurpleMood *global_moods = get_global_moods();
3535 
3536 	if (account) {
3537 		PurplePresence *presence = purple_account_get_presence(account);
3538 		PurpleStatus *status = purple_presence_get_status(presence, "mood");
3539 		gc = purple_account_get_connection(account);
3540 		g_return_if_fail(gc->prpl != NULL);
3541 		prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
3542 		current_mood = purple_status_get_attr_string(status, PURPLE_MOOD_NAME);
3543 	} else {
3544 		current_mood = get_global_mood_status();
3545 	}
3546 
3547 	fields = purple_request_fields_new();
3548 	g = purple_request_field_group_new(NULL);
3549 	f = purple_request_field_list_new("mood", _("Please select your mood from the list"));
3550 
3551 	purple_request_field_list_add(f, _("None"), "");
3552 	if (current_mood == NULL)
3553 		purple_request_field_list_add_selected(f, _("None"));
3554 
3555 	/* TODO: rlaager wants this sorted. */
3556 	/* TODO: darkrain wants it sorted post-translation */
3557 	if (account && PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, get_moods))
3558 		mood = prpl_info->get_moods(account);
3559 	else
3560 		mood = global_moods;
3561 	for ( ; mood->mood != NULL ; mood++) {
3562 		char *path;
3563 
3564 		if (mood->mood == NULL || mood->description == NULL)
3565 			continue;
3566 
3567 		path = get_mood_icon_path(mood->mood);
3568 		purple_request_field_list_add_icon(f, _(mood->description),
3569 				path, (gpointer)mood->mood);
3570 		g_free(path);
3571 
3572 		if (current_mood && purple_strequal(current_mood, mood->mood))
3573 			purple_request_field_list_add_selected(f, _(mood->description));
3574 	}
3575 	purple_request_field_group_add_field(g, f);
3576 
3577 	purple_request_fields_add_group(fields, g);
3578 
3579 	/* if the connection allows setting a mood message */
3580 	if (gc && (gc->flags & PURPLE_CONNECTION_SUPPORT_MOOD_MESSAGES)) {
3581 		g = purple_request_field_group_new(NULL);
3582 		f = purple_request_field_string_new("text",
3583 		    _("Message (optional)"), NULL, FALSE);
3584 		purple_request_field_group_add_field(g, f);
3585 		purple_request_fields_add_group(fields, g);
3586 	}
3587 
3588 	purple_request_fields(gc, _("Edit User Mood"), _("Edit User Mood"),
3589                               NULL, fields,
3590                               _("OK"), G_CALLBACK(edit_mood_cb),
3591                               _("Cancel"), NULL,
3592                               gc ? purple_connection_get_account(gc) : NULL,
3593                               NULL, NULL, gc);
3594 
3595 	g_free(global_moods);
3596 }
3597 
3598 static void
set_mood_show(void)3599 set_mood_show(void)
3600 {
3601 	set_mood_cb(NULL, NULL);
3602 }
3603 
3604 /***************************************************
3605  *            Crap                                 *
3606  ***************************************************/
3607 static GtkItemFactoryEntry blist_menu[] =
3608 {
3609 /* NOTE: Do not set any accelerator to Control+O. It is mapped by
3610    gtk_blist_key_press_cb to "Get User Info" on the selected buddy. */
3611 
3612 	/* Buddies menu */
3613 	{ N_("/_Buddies"), NULL, NULL, 0, "<Branch>", NULL },
3614 	{ N_("/Buddies/New Instant _Message..."), "<CTL>M", pidgin_dialogs_im, 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW },
3615 	{ N_("/Buddies/Join a _Chat..."), "<CTL>C", pidgin_blist_joinchat_show, 0, "<StockItem>", PIDGIN_STOCK_CHAT },
3616 	{ N_("/Buddies/Get User _Info..."), "<CTL>I", pidgin_dialogs_info, 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_USER_INFO },
3617 	{ N_("/Buddies/View User _Log..."), "<CTL>L", pidgin_dialogs_log, 0, "<Item>", NULL },
3618 	{ "/Buddies/sep1", NULL, NULL, 0, "<Separator>", NULL },
3619 	{ N_("/Buddies/Sh_ow"), NULL, NULL, 0, "<Branch>", NULL},
3620 	{ N_("/Buddies/Show/_Offline Buddies"), NULL, pidgin_blist_edit_mode_cb, 1, "<CheckItem>", NULL },
3621 	{ N_("/Buddies/Show/_Empty Groups"), NULL, pidgin_blist_show_empty_groups_cb, 1, "<CheckItem>", NULL },
3622 	{ N_("/Buddies/Show/Buddy _Details"), NULL, pidgin_blist_buddy_details_cb, 1, "<CheckItem>", NULL },
3623 	{ N_("/Buddies/Show/Idle _Times"), NULL, pidgin_blist_show_idle_time_cb, 1, "<CheckItem>", NULL },
3624 	{ N_("/Buddies/Show/_Protocol Icons"), NULL, pidgin_blist_show_protocol_icons_cb, 1, "<CheckItem>", NULL },
3625 	{ N_("/Buddies/_Sort Buddies"), NULL, NULL, 0, "<Branch>", NULL },
3626 	{ "/Buddies/sep2", NULL, NULL, 0, "<Separator>", NULL },
3627 	{ N_("/Buddies/_Add Buddy..."), "<CTL>B", pidgin_blist_add_buddy_cb, 0, "<StockItem>", GTK_STOCK_ADD },
3628 	{ N_("/Buddies/Add C_hat..."), NULL, pidgin_blist_add_chat_cb, 0, "<StockItem>", GTK_STOCK_ADD },
3629 	{ N_("/Buddies/Add _Group..."), NULL, purple_blist_request_add_group, 0, "<StockItem>", GTK_STOCK_ADD },
3630 	{ "/Buddies/sep3", NULL, NULL, 0, "<Separator>", NULL },
3631 	{ N_("/Buddies/_Quit"), "<CTL>Q", purple_core_quit, 0, "<StockItem>", GTK_STOCK_QUIT },
3632 
3633 	/* Accounts menu */
3634 	{ N_("/_Accounts"), NULL, NULL, 0, "<Branch>", NULL },
3635 	{ N_("/Accounts/Manage Accounts"), "<CTL>A", pidgin_accounts_window_show, 0, "<Item>", NULL },
3636 
3637 	/* Tools */
3638 	{ N_("/_Tools"), NULL, NULL, 0, "<Branch>", NULL },
3639 	{ N_("/Tools/Buddy _Pounces"), NULL, pidgin_pounces_manager_show, 1, "<Item>", NULL },
3640 	{ N_("/Tools/_Certificates"), NULL, pidgin_certmgr_show, 0, "<Item>", NULL },
3641 	{ N_("/Tools/Custom Smile_ys"), "<CTL>Y", pidgin_smiley_manager_show, 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_SMILEY },
3642 	{ N_("/Tools/Plu_gins"), "<CTL>U", pidgin_plugin_dialog_show, 2, "<StockItem>", PIDGIN_STOCK_TOOLBAR_PLUGINS },
3643 	{ N_("/Tools/Pr_eferences"), "<CTL>P", pidgin_prefs_show, 0, "<StockItem>", GTK_STOCK_PREFERENCES },
3644 	{ N_("/Tools/Pr_ivacy"), NULL, pidgin_privacy_dialog_show, 0, "<Item>", NULL },
3645 	{ N_("/Tools/Set _Mood"), "<CTL>D", set_mood_show, 0, "<Item>", NULL },
3646 	{ "/Tools/sep2", NULL, NULL, 0, "<Separator>", NULL },
3647 	{ N_("/Tools/_File Transfers"), "<CTL>T", pidgin_xfer_dialog_show, 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_TRANSFER },
3648 	{ N_("/Tools/R_oom List"), NULL, pidgin_roomlist_dialog_show, 0, "<Item>", NULL },
3649 	{ N_("/Tools/System _Log"), NULL, gtk_blist_show_systemlog_cb, 3, "<Item>", NULL },
3650 	{ "/Tools/sep3", NULL, NULL, 0, "<Separator>", NULL },
3651 	{ N_("/Tools/Mute _Sounds"), NULL, pidgin_blist_mute_sounds_cb, 0, "<CheckItem>", NULL },
3652 	/* Help */
3653 	{ N_("/_Help"), NULL, NULL, 0, "<Branch>", NULL },
3654 	{ N_("/Help/Online _Help"), "F1", gtk_blist_show_onlinehelp_cb, 0, "<StockItem>", GTK_STOCK_HELP },
3655 	{ N_("/Help/D_onate"), NULL, gtk_blist_donate_cb, 0, "<Item>", NULL },
3656 	{ "/Help/sep1", NULL, NULL, 0, "<Separator>", NULL },
3657 	{ N_("/Help/_Build Information"), NULL, pidgin_dialogs_buildinfo, 0, "<Item>", NULL },
3658 	{ N_("/Help/_Debug Window"), NULL, toggle_debug, 0, "<Item>", NULL },
3659 	{ N_("/Help/De_veloper Information"), NULL, pidgin_dialogs_developers, 0, "<Item>", NULL },
3660 	{ N_("/Help/_Plugin Information"), NULL, pidgin_dialogs_plugins_info, 0, "<Item>", NULL },
3661 	{ N_("/Help/_Translator Information"), NULL, pidgin_dialogs_translators, 0, "<Item>", NULL },
3662 	{ "/Help/sep2", NULL, NULL, 0, "<Separator>", NULL },
3663 	{ N_("/Help/_About"), NULL, pidgin_dialogs_about, 4,  "<StockItem>", GTK_STOCK_ABOUT },
3664 };
3665 
3666 /*********************************************************
3667  * Private Utility functions                             *
3668  *********************************************************/
3669 
pidgin_get_tooltip_text(PurpleBlistNode * node,gboolean full)3670 static char *pidgin_get_tooltip_text(PurpleBlistNode *node, gboolean full)
3671 {
3672 	GString *str = g_string_new("");
3673 	PurplePlugin *prpl;
3674 	PurplePluginProtocolInfo *prpl_info = NULL;
3675 	char *tmp;
3676 
3677 	if (PURPLE_BLIST_NODE_IS_CHAT(node))
3678 	{
3679 		PurpleChat *chat;
3680 		GList *connections;
3681 		GList *cur;
3682 		struct proto_chat_entry *pce;
3683 		char *name, *value;
3684 		PurpleConversation *conv;
3685 		PidginBlistNode *bnode = node->ui_data;
3686 
3687 		chat = (PurpleChat *)node;
3688 		prpl = purple_find_prpl(purple_account_get_protocol_id(chat->account));
3689 		prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
3690 
3691 		connections = purple_connections_get_all();
3692 		if (connections && connections->next)
3693 		{
3694 			tmp = g_markup_escape_text(chat->account->username, -1);
3695 			g_string_append_printf(str, _("<b>Account:</b> %s"), tmp);
3696 			g_free(tmp);
3697 		}
3698 
3699 		if (bnode && bnode->conv.conv) {
3700 			conv = bnode->conv.conv;
3701 		} else {
3702 			char *chat_name;
3703 			if (prpl_info && prpl_info->get_chat_name)
3704 				chat_name = prpl_info->get_chat_name(chat->components);
3705 			else
3706 				chat_name = g_strdup(purple_chat_get_name(chat));
3707 
3708 			conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, chat_name,
3709 					chat->account);
3710 			g_free(chat_name);
3711 		}
3712 
3713 		if (conv && !purple_conv_chat_has_left(PURPLE_CONV_CHAT(conv))) {
3714 			g_string_append_printf(str, _("\n<b>Occupants:</b> %d"),
3715 					g_list_length(purple_conv_chat_get_users(PURPLE_CONV_CHAT(conv))));
3716 
3717 			if (prpl_info && (prpl_info->options & OPT_PROTO_CHAT_TOPIC)) {
3718 				const char *chattopic = purple_conv_chat_get_topic(PURPLE_CONV_CHAT(conv));
3719 				char *topic = chattopic ? g_markup_escape_text(chattopic, -1) : NULL;
3720 				g_string_append_printf(str, _("\n<b>Topic:</b> %s"), topic ? topic : _("(no topic set)"));
3721 				g_free(topic);
3722 			}
3723 		}
3724 
3725 		if (prpl_info && prpl_info->chat_info != NULL)
3726 			cur = prpl_info->chat_info(chat->account->gc);
3727 		else
3728 			cur = NULL;
3729 
3730 		while (cur != NULL)
3731 		{
3732 			pce = cur->data;
3733 
3734 			if (!pce->secret && (!pce->required &&
3735 				g_hash_table_lookup(chat->components, pce->identifier) == NULL))
3736 			{
3737 				tmp = purple_text_strip_mnemonic(pce->label);
3738 				name = g_markup_escape_text(tmp, -1);
3739 				g_free(tmp);
3740 				value = g_markup_escape_text(g_hash_table_lookup(
3741 										chat->components, pce->identifier), -1);
3742 				g_string_append_printf(str, "\n<b>%s</b> %s",
3743 							name ? name : "",
3744 							value ? value : "");
3745 				g_free(name);
3746 				g_free(value);
3747 			}
3748 
3749 			g_free(pce);
3750 			cur = g_list_delete_link(cur, cur);
3751 		}
3752 	}
3753 	else if (PURPLE_BLIST_NODE_IS_CONTACT(node) || PURPLE_BLIST_NODE_IS_BUDDY(node))
3754 	{
3755 		/* NOTE: THIS FUNCTION IS NO LONGER CALLED FOR CONTACTS.
3756 		 * It is only called by create_tip_for_node(), and create_tip_for_node() is never called for a contact.
3757 		 */
3758 		PurpleContact *c;
3759 		PurpleBuddy *b;
3760 		PurplePresence *presence;
3761 		PurpleNotifyUserInfo *user_info;
3762 		GList *connections;
3763 		char *tmp;
3764 		time_t idle_secs, signon;
3765 
3766 		if (PURPLE_BLIST_NODE_IS_CONTACT(node))
3767 		{
3768 			c = (PurpleContact *)node;
3769 			b = purple_contact_get_priority_buddy(c);
3770 		}
3771 		else
3772 		{
3773 			b = (PurpleBuddy *)node;
3774 			c = purple_buddy_get_contact(b);
3775 		}
3776 
3777 		prpl = purple_find_prpl(purple_account_get_protocol_id(b->account));
3778 		prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
3779 
3780 		presence = purple_buddy_get_presence(b);
3781 		user_info = purple_notify_user_info_new();
3782 
3783 		/* Account */
3784 		connections = purple_connections_get_all();
3785 		if (full && connections && connections->next)
3786 		{
3787 			tmp = g_markup_escape_text(purple_account_get_username(
3788 									   purple_buddy_get_account(b)), -1);
3789 			purple_notify_user_info_add_pair(user_info, _("Account"), tmp);
3790 			g_free(tmp);
3791 		}
3792 
3793 		/* Alias */
3794 		/* If there's not a contact alias, the node is being displayed with
3795 		 * this alias, so there's no point in showing it in the tooltip. */
3796 		if (full && c && b->alias != NULL && b->alias[0] != '\0' &&
3797 		    (c->alias != NULL && c->alias[0] != '\0') &&
3798 		    !purple_strequal(c->alias, b->alias))
3799 		{
3800 			tmp = g_markup_escape_text(b->alias, -1);
3801 			purple_notify_user_info_add_pair(user_info, _("Buddy Alias"), tmp);
3802 			g_free(tmp);
3803 		}
3804 
3805 		/* Nickname/Server Alias */
3806 		/* I'd like to only show this if there's a contact or buddy
3807 		 * alias, but people often set long nicknames, which
3808 		 * get ellipsized, so the only way to see the whole thing is
3809 		 * to look at the tooltip. */
3810 		if (full && b->server_alias != NULL && b->server_alias[0] != '\0')
3811 		{
3812 			tmp = g_markup_escape_text(b->server_alias, -1);
3813 			purple_notify_user_info_add_pair(user_info, _("Nickname"), tmp);
3814 			g_free(tmp);
3815 		}
3816 
3817 		/* Logged In */
3818 		signon = purple_presence_get_login_time(presence);
3819 		if (full && PURPLE_BUDDY_IS_ONLINE(b) && signon > 0)
3820 		{
3821 			if (signon > time(NULL)) {
3822 				/*
3823 				 * They signed on in the future?!  Our local clock
3824 				 * must be wrong, show the actual date instead of
3825 				 * "4 days", etc.
3826 				 */
3827 				tmp = g_strdup(purple_date_format_long(localtime(&signon)));
3828 			} else
3829 				tmp = purple_str_seconds_to_string(time(NULL) - signon);
3830 			purple_notify_user_info_add_pair(user_info, _("Logged In"), tmp);
3831 			g_free(tmp);
3832 		}
3833 
3834 		/* Idle */
3835 		if (purple_presence_is_idle(presence))
3836 		{
3837 			idle_secs = purple_presence_get_idle_time(presence);
3838 			if (idle_secs > 0)
3839 			{
3840 				tmp = purple_str_seconds_to_string(time(NULL) - idle_secs);
3841 				purple_notify_user_info_add_pair(user_info, _("Idle"), tmp);
3842 				g_free(tmp);
3843 			}
3844 		}
3845 
3846 		/* Last Seen */
3847 		if (full && c && !PURPLE_BUDDY_IS_ONLINE(b))
3848 		{
3849 			struct _pidgin_blist_node *gtknode = ((PurpleBlistNode *)c)->ui_data;
3850 			PurpleBlistNode *bnode;
3851 			int lastseen = 0;
3852 
3853 			if (gtknode && (!gtknode->contact_expanded || PURPLE_BLIST_NODE_IS_CONTACT(node)))
3854 			{
3855 				/* We're either looking at a buddy for a collapsed contact or
3856 				 * an expanded contact itself so we show the most recent
3857 				 * (largest) last_seen time for any of the buddies under
3858 				 * the contact. */
3859 				for (bnode = ((PurpleBlistNode *)c)->child ; bnode != NULL ; bnode = bnode->next)
3860 				{
3861 					int value = purple_blist_node_get_int(bnode, "last_seen");
3862 					if (value > lastseen)
3863 						lastseen = value;
3864 				}
3865 			}
3866 			else
3867 			{
3868 				/* We're dealing with a buddy under an expanded contact,
3869 				 * so we show the last_seen time for the buddy. */
3870 				lastseen = purple_blist_node_get_int(&b->node, "last_seen");
3871 			}
3872 
3873 			if (lastseen > 0)
3874 			{
3875 				tmp = purple_str_seconds_to_string(time(NULL) - lastseen);
3876 				purple_notify_user_info_add_pair(user_info, _("Last Seen"), tmp);
3877 				g_free(tmp);
3878 			}
3879 		}
3880 
3881 
3882 		/* Offline? */
3883 		/* FIXME: Why is this status special-cased by the core? --rlaager
3884 		 * FIXME: Alternatively, why not have the core do all of them? --rlaager */
3885 		if (!PURPLE_BUDDY_IS_ONLINE(b)) {
3886 			purple_notify_user_info_add_pair(user_info, _("Status"), _("Offline"));
3887 		}
3888 
3889 		if (purple_account_is_connected(b->account) &&
3890 				prpl_info && prpl_info->tooltip_text)
3891 		{
3892 			/* Additional text from the PRPL */
3893 			prpl_info->tooltip_text(b, user_info, full);
3894 		}
3895 
3896 		/* These are Easter Eggs.  Patches to remove them will be rejected. */
3897 		if (!g_ascii_strcasecmp(b->name, "robflynn"))
3898 			purple_notify_user_info_add_pair(user_info, _("Description"), _("Spooky"));
3899 		if (!g_ascii_strcasecmp(b->name, "seanegn"))
3900 			purple_notify_user_info_add_pair(user_info, _("Status"), _("Awesome"));
3901 		if (!g_ascii_strcasecmp(b->name, "chipx86"))
3902 			purple_notify_user_info_add_pair(user_info, _("Status"), _("Rockin'"));
3903 
3904 		tmp = purple_notify_user_info_get_text_with_newline(user_info, "\n");
3905 		g_string_append(str, tmp);
3906 		g_free(tmp);
3907 
3908 		purple_notify_user_info_destroy(user_info);
3909 	} else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
3910 		gint count;
3911 		PurpleGroup *group = (PurpleGroup*)node;
3912 		PurpleNotifyUserInfo *user_info;
3913 
3914 		user_info = purple_notify_user_info_new();
3915 
3916 		count = purple_blist_get_group_online_count(group);
3917 
3918 		if (count != 0) {
3919 			/* Online buddies in group */
3920 			tmp = g_strdup_printf("%d", count);
3921 			purple_notify_user_info_add_pair(user_info,
3922 			                                 _("Online Buddies"),
3923 			                                 tmp);
3924 			g_free(tmp);
3925 		}
3926 
3927 		count = purple_blist_get_group_size(group, FALSE);
3928 		if (count != 0) {
3929 			/* Total buddies (from online accounts) in group */
3930 			tmp = g_strdup_printf("%d", count);
3931 			purple_notify_user_info_add_pair(user_info,
3932 			                                 _("Total Buddies"),
3933 			                                 tmp);
3934 			g_free(tmp);
3935 		}
3936 
3937 		tmp = purple_notify_user_info_get_text_with_newline(user_info, "\n");
3938 		g_string_append(str, tmp);
3939 		g_free(tmp);
3940 
3941 		purple_notify_user_info_destroy(user_info);
3942 	}
3943 
3944 	purple_signal_emit(pidgin_blist_get_handle(), "drawing-tooltip",
3945 	                   node, str, full);
3946 
3947 	return g_string_free(str, FALSE);
3948 }
3949 
3950 static GHashTable *cached_emblems;
3951 
_cleanup_cached_emblem(gpointer data,GObject * obj)3952 static void _cleanup_cached_emblem(gpointer data, GObject *obj) {
3953 	g_hash_table_remove(cached_emblems, data);
3954 }
3955 
_pidgin_blist_get_cached_emblem(gchar * path)3956 static GdkPixbuf * _pidgin_blist_get_cached_emblem(gchar *path) {
3957 	GdkPixbuf *pb = g_hash_table_lookup(cached_emblems, path);
3958 
3959 	if (pb != NULL) {
3960 		/* The caller gets a reference */
3961 		g_object_ref(pb);
3962 		g_free(path);
3963 	} else {
3964 		pb = pidgin_pixbuf_new_from_file(path);
3965 		if (pb != NULL) {
3966 			/* We don't want to own a ref to the pixbuf, but we need to keep clean up. */
3967 			/* I'm not sure if it would be better to just keep our ref and not let the emblem ever be destroyed */
3968 			g_object_weak_ref(G_OBJECT(pb), _cleanup_cached_emblem, path);
3969 			g_hash_table_insert(cached_emblems, path, pb);
3970 		} else
3971 			g_free(path);
3972 	}
3973 
3974 	return pb;
3975 }
3976 
3977 GdkPixbuf *
pidgin_blist_get_emblem(PurpleBlistNode * node)3978 pidgin_blist_get_emblem(PurpleBlistNode *node)
3979 {
3980 	PurpleBuddy *buddy = NULL;
3981 	struct _pidgin_blist_node *gtknode = node->ui_data;
3982 	PurplePlugin *prpl;
3983 	PurplePluginProtocolInfo *prpl_info;
3984 	const char *name = NULL;
3985 	char *filename, *path;
3986 	PurplePresence *p = NULL;
3987 	PurpleStatus *tune;
3988 
3989 	if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
3990 		if(!gtknode->contact_expanded) {
3991 			buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
3992 		}
3993 	} else if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
3994 		buddy = (PurpleBuddy*)node;
3995 		p = purple_buddy_get_presence(buddy);
3996 		if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_MOBILE)) {
3997 			/* This emblem comes from the small emoticon set now,
3998 			 * to reduce duplication. */
3999 			path = g_build_filename(DATADIR, "pixmaps", "pidgin", "emotes",
4000 						"small", "mobile.png", NULL);
4001 			return _pidgin_blist_get_cached_emblem(path);
4002 		}
4003 
4004 		if (((struct _pidgin_blist_node*)(node->parent->ui_data))->contact_expanded) {
4005 			if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons"))
4006 				return NULL;
4007 			return pidgin_create_prpl_icon(((PurpleBuddy*)node)->account, PIDGIN_PRPL_ICON_SMALL);
4008 		}
4009 	} else {
4010 		return NULL;
4011 	}
4012 
4013 	g_return_val_if_fail(buddy != NULL, NULL);
4014 
4015 	if (!purple_privacy_check(buddy->account, purple_buddy_get_name(buddy))) {
4016 		path = g_build_filename(DATADIR, "pixmaps", "pidgin", "emblems", "16", "blocked.png", NULL);
4017 		return _pidgin_blist_get_cached_emblem(path);
4018 	}
4019 
4020 	/* If we came through the contact code flow above, we didn't need
4021 	 * to get the presence until now. */
4022 	if (p == NULL)
4023 		p = purple_buddy_get_presence(buddy);
4024 
4025 	if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_MOBILE)) {
4026 		/* This emblem comes from the small emoticon set now, to reduce duplication. */
4027 		path = g_build_filename(DATADIR, "pixmaps", "pidgin", "emotes", "small", "mobile.png", NULL);
4028 		return _pidgin_blist_get_cached_emblem(path);
4029 	}
4030 
4031 	tune = purple_presence_get_status(p, "tune");
4032 	if (tune && purple_status_is_active(tune)) {
4033 		/* TODO: Replace "Tune" with generalized "Media" in 3.0. */
4034 		if (purple_status_get_attr_string(tune, "game") != NULL) {
4035 			path = g_build_filename(DATADIR, "pixmaps", "pidgin", "emblems", "16", "game.png", NULL);
4036 			return _pidgin_blist_get_cached_emblem(path);
4037 		}
4038 		/* TODO: Replace "Tune" with generalized "Media" in 3.0. */
4039 		if (purple_status_get_attr_string(tune, "office") != NULL) {
4040 			path = g_build_filename(DATADIR, "pixmaps", "pidgin", "emblems", "16", "office.png", NULL);
4041 			return _pidgin_blist_get_cached_emblem(path);
4042 		}
4043 		/* Regular old "tune" is the only one in all protocols. */
4044 		/* This emblem comes from the small emoticon set now, to reduce duplication. */
4045 		path = g_build_filename(DATADIR, "pixmaps", "pidgin", "emotes", "small", "music.png", NULL);
4046 		return _pidgin_blist_get_cached_emblem(path);
4047 	}
4048 
4049 	prpl = purple_find_prpl(purple_account_get_protocol_id(buddy->account));
4050 	if (!prpl)
4051 		return NULL;
4052 
4053 	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
4054 	if (prpl_info && prpl_info->list_emblem)
4055 		name = prpl_info->list_emblem(buddy);
4056 
4057 	if (name == NULL) {
4058 		PurpleStatus *status;
4059 
4060 		if (!purple_presence_is_status_primitive_active(p, PURPLE_STATUS_MOOD))
4061 			return NULL;
4062 
4063 		status = purple_presence_get_status(p, "mood");
4064 		name = purple_status_get_attr_string(status, PURPLE_MOOD_NAME);
4065 
4066 		if (!(name && *name))
4067 			return NULL;
4068 
4069 		path = get_mood_icon_path(name);
4070 	} else {
4071 		filename = g_strdup_printf("%s.png", name);
4072 		path = g_build_filename(DATADIR, "pixmaps", "pidgin", "emblems", "16", filename, NULL);
4073 		g_free(filename);
4074 	}
4075 
4076 	/* _pidgin_blist_get_cached_emblem() assumes ownership of path */
4077 	return _pidgin_blist_get_cached_emblem(path);
4078 }
4079 
4080 
4081 GdkPixbuf *
pidgin_blist_get_status_icon(PurpleBlistNode * node,PidginStatusIconSize size)4082 pidgin_blist_get_status_icon(PurpleBlistNode *node, PidginStatusIconSize size)
4083 {
4084 	GdkPixbuf *ret;
4085 	const char *icon = NULL;
4086 	struct _pidgin_blist_node *gtknode = node->ui_data;
4087 	struct _pidgin_blist_node *gtkbuddynode = NULL;
4088 	PurpleBuddy *buddy = NULL;
4089 	PurpleChat *chat = NULL;
4090 	GtkIconSize icon_size = gtk_icon_size_from_name((size == PIDGIN_STATUS_ICON_LARGE) ? PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL :
4091 											 PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC);
4092 
4093 	if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
4094 		if(!gtknode->contact_expanded) {
4095 			buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
4096 			if (buddy != NULL)
4097 				gtkbuddynode = ((PurpleBlistNode*)buddy)->ui_data;
4098 		}
4099 	} else if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
4100 		buddy = (PurpleBuddy*)node;
4101 		gtkbuddynode = node->ui_data;
4102 	} else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
4103 		chat = (PurpleChat*)node;
4104 	} else {
4105 		return NULL;
4106 	}
4107 
4108 	if(buddy || chat) {
4109 		PurpleAccount *account;
4110 		PurplePlugin *prpl;
4111 
4112 		if(buddy)
4113 			account = buddy->account;
4114 		else
4115 			account = chat->account;
4116 
4117 		prpl = purple_find_prpl(purple_account_get_protocol_id(account));
4118 		if(!prpl)
4119 			return NULL;
4120 	}
4121 
4122 	if(buddy) {
4123 	  	PurpleConversation *conv = find_conversation_with_buddy(buddy);
4124 		PurplePresence *p;
4125 		gboolean trans;
4126 
4127 		if(conv != NULL) {
4128 			PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
4129 			if (gtkconv == NULL && size == PIDGIN_STATUS_ICON_SMALL) {
4130 				PidginBlistNode *ui = buddy->node.ui_data;
4131 				if (ui == NULL || (ui->conv.flags & PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE))
4132 					return gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview),
4133 							PIDGIN_STOCK_STATUS_MESSAGE, icon_size, "GtkTreeView");
4134 			}
4135 		}
4136 
4137 		p = purple_buddy_get_presence(buddy);
4138 		trans = purple_presence_is_idle(p);
4139 
4140 		if (PURPLE_BUDDY_IS_ONLINE(buddy) && gtkbuddynode && gtkbuddynode->recent_signonoff)
4141 			icon = PIDGIN_STOCK_STATUS_LOGIN;
4142 		else if (gtkbuddynode && gtkbuddynode->recent_signonoff)
4143 			icon = PIDGIN_STOCK_STATUS_LOGOUT;
4144 		else if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_UNAVAILABLE))
4145 			if (trans)
4146 				icon = PIDGIN_STOCK_STATUS_BUSY_I;
4147 			else
4148 				icon = PIDGIN_STOCK_STATUS_BUSY;
4149 		else if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_AWAY))
4150 			if (trans)
4151 				icon = PIDGIN_STOCK_STATUS_AWAY_I;
4152 		 	else
4153 				icon = PIDGIN_STOCK_STATUS_AWAY;
4154 		else if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_EXTENDED_AWAY))
4155 			if (trans)
4156 				icon = PIDGIN_STOCK_STATUS_XA_I;
4157 			else
4158 				icon = PIDGIN_STOCK_STATUS_XA;
4159 		else if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_OFFLINE))
4160 			icon = PIDGIN_STOCK_STATUS_OFFLINE;
4161 		else if (trans)
4162 			icon = PIDGIN_STOCK_STATUS_AVAILABLE_I;
4163 		else if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_INVISIBLE))
4164 			icon = PIDGIN_STOCK_STATUS_INVISIBLE;
4165 		else
4166 			icon = PIDGIN_STOCK_STATUS_AVAILABLE;
4167 	} else if (chat) {
4168 		icon = PIDGIN_STOCK_STATUS_CHAT;
4169 	} else {
4170 		icon = PIDGIN_STOCK_STATUS_PERSON;
4171 	}
4172 
4173 	ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), icon,
4174 			icon_size, "GtkTreeView");
4175 	return ret;
4176 }
4177 
4178 static const char *
theme_font_get_color_default(PidginThemeFont * font,const char * def)4179 theme_font_get_color_default(PidginThemeFont *font, const char *def)
4180 {
4181 	const char *ret;
4182 	if (!font || !(ret = pidgin_theme_font_get_color_describe(font)))
4183 		ret = def;
4184 	return ret;
4185 }
4186 
4187 static const char *
theme_font_get_face_default(PidginThemeFont * font,const char * def)4188 theme_font_get_face_default(PidginThemeFont *font, const char *def)
4189 {
4190 	const char *ret;
4191 	if (!font || !(ret = pidgin_theme_font_get_font_face(font)))
4192 		ret = def;
4193 	return ret;
4194 }
4195 
4196 gchar *
pidgin_blist_get_name_markup(PurpleBuddy * b,gboolean selected,gboolean aliased)4197 pidgin_blist_get_name_markup(PurpleBuddy *b, gboolean selected, gboolean aliased)
4198 {
4199 	const char *name, *name_color, *name_font, *status_color, *status_font, *dim_grey;
4200 	char *text = NULL;
4201 	PurplePlugin *prpl;
4202 	PurplePluginProtocolInfo *prpl_info = NULL;
4203 	PurpleContact *contact;
4204 	PurplePresence *presence;
4205 	struct _pidgin_blist_node *gtkcontactnode = NULL;
4206 	char *idletime = NULL, *statustext = NULL, *nametext = NULL;
4207 	PurpleConversation *conv = find_conversation_with_buddy(b);
4208 	gboolean hidden_conv = FALSE;
4209 	gboolean biglist = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons");
4210 	PidginThemeFont *statusfont = NULL, *namefont = NULL;
4211 	PidginBlistTheme *theme;
4212 
4213 	if (conv != NULL) {
4214 		PidginBlistNode *ui = b->node.ui_data;
4215 		if (ui) {
4216 			if (ui->conv.flags & PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE)
4217 				hidden_conv = TRUE;
4218 		} else {
4219 			if (PIDGIN_CONVERSATION(conv) == NULL)
4220 				hidden_conv = TRUE;
4221 		}
4222 	}
4223 
4224 	/* XXX Good luck cleaning up this crap */
4225 	contact = PURPLE_CONTACT(PURPLE_BLIST_NODE(b)->parent);
4226 	if(contact)
4227 		gtkcontactnode = purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(contact));
4228 
4229 	/* Name */
4230 	if (gtkcontactnode && !gtkcontactnode->contact_expanded && contact->alias)
4231 		name = contact->alias;
4232 	else
4233 		name = purple_buddy_get_alias(b);
4234 
4235 	/* Raise a contact pre-draw signal here.  THe callback will return an
4236 	 * escaped version of the name. */
4237 	nametext = purple_signal_emit_return_1(pidgin_blist_get_handle(), "drawing-buddy", b);
4238 
4239 	if(!nametext)
4240 		nametext = g_markup_escape_text(name, strlen(name));
4241 
4242 	presence = purple_buddy_get_presence(b);
4243 
4244 	/* Name is all that is needed */
4245 	if (!aliased || biglist) {
4246 
4247 		/* Status Info */
4248 		prpl = purple_find_prpl(purple_account_get_protocol_id(b->account));
4249 
4250 		if (prpl != NULL)
4251 			prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
4252 
4253 		if (prpl_info && prpl_info->status_text && b->account->gc) {
4254 			char *tmp = prpl_info->status_text(b);
4255 			const char *end;
4256 
4257 			if(tmp && !g_utf8_validate(tmp, -1, &end)) {
4258 				char *new = g_strndup(tmp,
4259 						g_utf8_pointer_to_offset(tmp, end));
4260 				g_free(tmp);
4261 				tmp = new;
4262 			}
4263 			if(tmp) {
4264 				g_strdelimit(tmp, "\n", ' ');
4265 				purple_str_strip_char(tmp, '\r');
4266 			}
4267 			statustext = tmp;
4268 		}
4269 
4270 		if(!purple_presence_is_online(presence) && !statustext)
4271 				statustext = g_strdup(_("Offline"));
4272 
4273 		/* Idle Text */
4274 		if (purple_presence_is_idle(presence) && purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_idle_time")) {
4275 			time_t idle_secs = purple_presence_get_idle_time(presence);
4276 
4277 			if (idle_secs > 0) {
4278 				int iday, ihrs, imin;
4279 				time_t t;
4280 
4281 				time(&t);
4282 				iday = (t - idle_secs) / (24 * 60 * 60);
4283 				ihrs = ((t - idle_secs) / 60 / 60) % 24;
4284 				imin = ((t - idle_secs) / 60) % 60;
4285 
4286 				if (iday)
4287 					idletime = g_strdup_printf(_("Idle %dd %dh %02dm"), iday, ihrs, imin);
4288 				else if (ihrs)
4289 					idletime = g_strdup_printf(_("Idle %dh %02dm"), ihrs, imin);
4290 				else
4291 					idletime = g_strdup_printf(_("Idle %dm"), imin);
4292 
4293 			} else
4294 				idletime = g_strdup(_("Idle"));
4295 		}
4296 	}
4297 
4298 	/* choose the colors of the text */
4299 	theme = pidgin_blist_get_theme();
4300 	name_color = NULL;
4301 
4302 	dim_grey = pidgin_style_is_dark(NULL) ? "light slate grey" : "dim grey";
4303 
4304 	if (theme) {
4305 		if (purple_presence_is_idle(presence)) {
4306 			namefont = statusfont = pidgin_blist_theme_get_idle_text_info(theme);
4307 			name_color = dim_grey;
4308 		} else if (!purple_presence_is_online(presence)) {
4309 			namefont = pidgin_blist_theme_get_offline_text_info(theme);
4310 			name_color = dim_grey;
4311 			statusfont = pidgin_blist_theme_get_status_text_info(theme);
4312 		} else if (purple_presence_is_available(presence)) {
4313 			namefont = pidgin_blist_theme_get_online_text_info(theme);
4314 			statusfont = pidgin_blist_theme_get_status_text_info(theme);
4315 		} else {
4316 			namefont = pidgin_blist_theme_get_away_text_info(theme);
4317 			statusfont = pidgin_blist_theme_get_status_text_info(theme);
4318 		}
4319 	} else {
4320 		if (!selected
4321 				&& (purple_presence_is_idle(presence)
4322 							|| !purple_presence_is_online(presence)))
4323 		{
4324 			name_color = dim_grey;
4325 		}
4326 	}
4327 
4328 	name_color = theme_font_get_color_default(namefont, name_color);
4329 	name_font = theme_font_get_face_default(namefont, "");
4330 
4331 	status_color = theme_font_get_color_default(statusfont, dim_grey);
4332 	status_font = theme_font_get_face_default(statusfont, "");
4333 
4334 	if (aliased && selected) {
4335 		if (theme) {
4336 			name_color = "black";
4337 			status_color = "black";
4338 		} else {
4339 			name_color = NULL;
4340 			status_color = NULL;
4341 		}
4342 	}
4343 
4344 	if (hidden_conv) {
4345 		char *tmp = nametext;
4346 		nametext = g_strdup_printf("<b>%s</b>", tmp);
4347 		g_free(tmp);
4348 	}
4349 
4350 	/* Put it all together */
4351 	if ((!aliased || biglist) && (statustext || idletime)) {
4352 		/* using <span size='smaller'> breaks the status, so it must be seperated into <small><span>*/
4353 		if (name_color) {
4354 			text = g_strdup_printf("<span font_desc='%s' foreground='%s'>%s</span>\n"
4355 				 		"<small><span font_desc='%s' foreground='%s'>%s%s%s</span></small>",
4356 						name_font, name_color, nametext, status_font, status_color,
4357 						idletime != NULL ? idletime : "",
4358 				    		(idletime != NULL && statustext != NULL) ? " - " : "",
4359 				    		statustext != NULL ? statustext : "");
4360 		} else if (status_color) {
4361 			text = g_strdup_printf("<span font_desc='%s'>%s</span>\n"
4362 				 		"<small><span font_desc='%s' foreground='%s'>%s%s%s</span></small>",
4363 						name_font, nametext, status_font, status_color,
4364 						idletime != NULL ? idletime : "",
4365 				    		(idletime != NULL && statustext != NULL) ? " - " : "",
4366 				    		statustext != NULL ? statustext : "");
4367 		} else {
4368 			text = g_strdup_printf("<span font_desc='%s'>%s</span>\n"
4369 				 		"<small><span font_desc='%s'>%s%s%s</span></small>",
4370 						name_font, nametext, status_font,
4371 						idletime != NULL ? idletime : "",
4372 				    		(idletime != NULL && statustext != NULL) ? " - " : "",
4373 				    		statustext != NULL ? statustext : "");
4374 		}
4375 	} else {
4376 		if (name_color) {
4377 			text = g_strdup_printf("<span font_desc='%s' color='%s'>%s</span>",
4378 				name_font, name_color, nametext);
4379 		} else {
4380 			text = g_strdup_printf("<span font_desc='%s'>%s</span>", name_font,
4381 				nametext);
4382 		}
4383 	}
4384 	g_free(nametext);
4385 	g_free(statustext);
4386 	g_free(idletime);
4387 
4388 	if (hidden_conv) {
4389 		char *tmp = text;
4390 		text = g_strdup_printf("<b>%s</b>", tmp);
4391 		g_free(tmp);
4392 	}
4393 
4394 	return text;
4395 }
4396 
pidgin_blist_restore_position(void)4397 static void pidgin_blist_restore_position(void)
4398 {
4399 	int blist_x, blist_y, blist_width, blist_height;
4400 
4401 	blist_width = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/width");
4402 
4403 	/* if the window exists, is hidden, we're saving positions, and the
4404 	 * position is sane... */
4405 	if (gtkblist && gtkblist->window &&
4406 		!GTK_WIDGET_VISIBLE(gtkblist->window) && blist_width != 0) {
4407 
4408 		blist_x      = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/x");
4409 		blist_y      = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/y");
4410 		blist_height = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/height");
4411 
4412 		/* ...check position is on screen... */
4413 		if (blist_x >= gdk_screen_width())
4414 			blist_x = gdk_screen_width() - 100;
4415 		else if (blist_x + blist_width < 0)
4416 			blist_x = 100;
4417 
4418 		if (blist_y >= gdk_screen_height())
4419 			blist_y = gdk_screen_height() - 100;
4420 		else if (blist_y + blist_height < 0)
4421 			blist_y = 100;
4422 
4423 		/* ...and move it back. */
4424 		gtk_window_move(GTK_WINDOW(gtkblist->window), blist_x, blist_y);
4425 		gtk_window_resize(GTK_WINDOW(gtkblist->window), blist_width, blist_height);
4426 		if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/list_maximized"))
4427 			gtk_window_maximize(GTK_WINDOW(gtkblist->window));
4428 	}
4429 }
4430 
pidgin_blist_refresh_timer(PurpleBuddyList * list)4431 static gboolean pidgin_blist_refresh_timer(PurpleBuddyList *list)
4432 {
4433 	PurpleBlistNode *gnode, *cnode;
4434 
4435 	if (gtk_blist_visibility == GDK_VISIBILITY_FULLY_OBSCURED
4436 			|| !GTK_WIDGET_VISIBLE(gtkblist->window))
4437 		return TRUE;
4438 
4439 	for(gnode = list->root; gnode; gnode = gnode->next) {
4440 		if(!PURPLE_BLIST_NODE_IS_GROUP(gnode))
4441 			continue;
4442 		for(cnode = gnode->child; cnode; cnode = cnode->next) {
4443 			if(PURPLE_BLIST_NODE_IS_CONTACT(cnode)) {
4444 				PurpleBuddy *buddy;
4445 
4446 				buddy = purple_contact_get_priority_buddy((PurpleContact*)cnode);
4447 
4448 				if (buddy &&
4449 						purple_presence_is_idle(purple_buddy_get_presence(buddy)))
4450 					pidgin_blist_update_contact(list, (PurpleBlistNode*)buddy);
4451 			}
4452 		}
4453 	}
4454 
4455 	/* keep on going */
4456 	return TRUE;
4457 }
4458 
pidgin_blist_hide_node(PurpleBuddyList * list,PurpleBlistNode * node,gboolean update)4459 static void pidgin_blist_hide_node(PurpleBuddyList *list, PurpleBlistNode *node, gboolean update)
4460 {
4461 	struct _pidgin_blist_node *gtknode = (struct _pidgin_blist_node *)node->ui_data;
4462 	GtkTreeIter iter;
4463 
4464 	if (!gtknode || !gtknode->row || !gtkblist)
4465 		return;
4466 
4467 	if(gtkblist->selected_node == node)
4468 		gtkblist->selected_node = NULL;
4469 	if (get_iter_from_node(node, &iter)) {
4470 		gtk_tree_store_remove(gtkblist->treemodel, &iter);
4471 		if(update && (PURPLE_BLIST_NODE_IS_CONTACT(node) ||
4472 			PURPLE_BLIST_NODE_IS_BUDDY(node) || PURPLE_BLIST_NODE_IS_CHAT(node))) {
4473 			pidgin_blist_update(list, node->parent);
4474 		}
4475 	}
4476 	gtk_tree_row_reference_free(gtknode->row);
4477 	gtknode->row = NULL;
4478 }
4479 
4480 static const char *require_connection[] =
4481 {
4482 	N_("/Buddies/New Instant Message..."),
4483 	N_("/Buddies/Join a Chat..."),
4484 	N_("/Buddies/Get User Info..."),
4485 	N_("/Buddies/Add Buddy..."),
4486 	N_("/Buddies/Add Chat..."),
4487 	N_("/Buddies/Add Group..."),
4488 };
4489 
4490 static const int require_connection_size = sizeof(require_connection)
4491 											/ sizeof(*require_connection);
4492 
4493 /**
4494  * Rebuild dynamic menus and make menu items sensitive/insensitive
4495  * where appropriate.
4496  */
4497 static void
update_menu_bar(PidginBuddyList * gtkblist)4498 update_menu_bar(PidginBuddyList *gtkblist)
4499 {
4500 	GtkWidget *widget;
4501 	gboolean sensitive;
4502 	int i;
4503 
4504 	g_return_if_fail(gtkblist != NULL);
4505 
4506 	pidgin_blist_update_accounts_menu();
4507 
4508 	sensitive = (purple_connections_get_all() != NULL);
4509 
4510 	for (i = 0; i < require_connection_size; i++)
4511 	{
4512 		widget = gtk_item_factory_get_widget(gtkblist->ift, require_connection[i]);
4513 		gtk_widget_set_sensitive(widget, sensitive);
4514 	}
4515 
4516 	widget = gtk_item_factory_get_widget(gtkblist->ift, N_("/Buddies/Join a Chat..."));
4517 	gtk_widget_set_sensitive(widget, pidgin_blist_joinchat_is_showable());
4518 
4519 	widget = gtk_item_factory_get_widget(gtkblist->ift, N_("/Buddies/Add Chat..."));
4520 	gtk_widget_set_sensitive(widget, pidgin_blist_joinchat_is_showable());
4521 
4522 	widget = gtk_item_factory_get_widget(gtkblist->ift, N_("/Tools/Privacy"));
4523 	gtk_widget_set_sensitive(widget, sensitive);
4524 
4525 	widget = gtk_item_factory_get_widget(gtkblist->ift, N_("/Tools/Room List"));
4526 	gtk_widget_set_sensitive(widget, pidgin_roomlist_is_showable());
4527 }
4528 
4529 static void
sign_on_off_cb(PurpleConnection * gc,PurpleBuddyList * blist)4530 sign_on_off_cb(PurpleConnection *gc, PurpleBuddyList *blist)
4531 {
4532 	PidginBuddyList *gtkblist = PIDGIN_BLIST(blist);
4533 
4534 	update_menu_bar(gtkblist);
4535 }
4536 
4537 static void
plugin_changed_cb(PurplePlugin * p,gpointer data)4538 plugin_changed_cb(PurplePlugin *p, gpointer data)
4539 {
4540 	pidgin_blist_update_plugin_actions();
4541 }
4542 
4543 static void
unseen_conv_menu(void)4544 unseen_conv_menu(void)
4545 {
4546 	static GtkWidget *menu = NULL;
4547 	GList *convs = NULL;
4548 	GList *chats, *ims;
4549 
4550 	if (menu) {
4551 		gtk_widget_destroy(menu);
4552 		menu = NULL;
4553 	}
4554 
4555 	ims = pidgin_conversations_find_unseen_list(PURPLE_CONV_TYPE_IM,
4556 				PIDGIN_UNSEEN_TEXT, FALSE, 0);
4557 
4558 	chats = pidgin_conversations_find_unseen_list(PURPLE_CONV_TYPE_CHAT,
4559 				PIDGIN_UNSEEN_NICK, FALSE, 0);
4560 
4561 	if(ims && chats)
4562 		convs = g_list_concat(ims, chats);
4563 	else if(ims && !chats)
4564 		convs = ims;
4565 	else if(!ims && chats)
4566 		convs = chats;
4567 
4568 	if (!convs)
4569 		/* no conversations added, don't show the menu */
4570 		return;
4571 
4572 	menu = gtk_menu_new();
4573 
4574 	pidgin_conversations_fill_menu(menu, convs);
4575 	g_list_free(convs);
4576 	gtk_widget_show_all(menu);
4577 	gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 3,
4578 			gtk_get_current_event_time());
4579 }
4580 
4581 static gboolean
menutray_press_cb(GtkWidget * widget,GdkEventButton * event)4582 menutray_press_cb(GtkWidget *widget, GdkEventButton *event)
4583 {
4584 	GList *convs;
4585 
4586 	switch (event->button) {
4587 		case 1:
4588 			convs = pidgin_conversations_find_unseen_list(PURPLE_CONV_TYPE_IM,
4589 							PIDGIN_UNSEEN_TEXT, FALSE, 1);
4590 
4591 			if(!convs)
4592 				convs = pidgin_conversations_find_unseen_list(PURPLE_CONV_TYPE_CHAT,
4593 								PIDGIN_UNSEEN_NICK, FALSE, 1);
4594 			if (convs) {
4595 				pidgin_conv_present_conversation((PurpleConversation*)convs->data);
4596 				g_list_free(convs);
4597 			}
4598 			break;
4599 		case 3:
4600 			unseen_conv_menu();
4601 			break;
4602 	}
4603 	return TRUE;
4604 }
4605 
4606 static void
conversation_updated_cb(PurpleConversation * conv,PurpleConvUpdateType type,PidginBuddyList * gtkblist)4607 conversation_updated_cb(PurpleConversation *conv, PurpleConvUpdateType type,
4608                         PidginBuddyList *gtkblist)
4609 {
4610 	GList *convs = NULL;
4611 	GList *ims, *chats;
4612 	GList *l = NULL;
4613 
4614 	if (type != PURPLE_CONV_UPDATE_UNSEEN)
4615 		return;
4616 
4617 	if(conv->account != NULL && conv->name != NULL) {
4618 		PurpleBuddy *buddy = purple_find_buddy(conv->account, conv->name);
4619 		if(buddy != NULL)
4620 			pidgin_blist_update_buddy(NULL, (PurpleBlistNode *)buddy, TRUE);
4621 	}
4622 
4623 	if (gtkblist->menutrayicon) {
4624 		gtk_widget_destroy(gtkblist->menutrayicon);
4625 		gtkblist->menutrayicon = NULL;
4626 	}
4627 
4628 	ims = pidgin_conversations_find_unseen_list(PURPLE_CONV_TYPE_IM,
4629 				PIDGIN_UNSEEN_TEXT, FALSE, 0);
4630 
4631 	chats = pidgin_conversations_find_unseen_list(PURPLE_CONV_TYPE_CHAT,
4632 				PIDGIN_UNSEEN_NICK, FALSE, 0);
4633 
4634 	if(ims && chats)
4635 		convs = g_list_concat(ims, chats);
4636 	else if(ims && !chats)
4637 		convs = ims;
4638 	else if(!ims && chats)
4639 		convs = chats;
4640 
4641 	if (convs) {
4642 		GtkWidget *img = NULL;
4643 		GString *tooltip_text = NULL;
4644 
4645 		tooltip_text = g_string_new("");
4646 		l = convs;
4647 		while (l != NULL) {
4648 			int count = 0;
4649 			PidginConversation *gtkconv = PIDGIN_CONVERSATION((PurpleConversation *)l->data);
4650 
4651 			if(gtkconv)
4652 				count = gtkconv->unseen_count;
4653 			else if(purple_conversation_get_data(l->data, "unseen-count"))
4654 				count = GPOINTER_TO_INT(purple_conversation_get_data(l->data, "unseen-count"));
4655 
4656 			g_string_append_printf(tooltip_text,
4657 					ngettext("%d unread message from %s\n", "%d unread messages from %s\n", count),
4658 					count, purple_conversation_get_title(l->data));
4659 			l = l->next;
4660 		}
4661 		if(tooltip_text->len > 0) {
4662 			/* get rid of the last newline */
4663 			g_string_truncate(tooltip_text, tooltip_text->len -1);
4664 			img = gtk_image_new_from_stock(PIDGIN_STOCK_TOOLBAR_PENDING,
4665 							gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL));
4666 
4667 			gtkblist->menutrayicon = gtk_event_box_new();
4668 			gtk_container_add(GTK_CONTAINER(gtkblist->menutrayicon), img);
4669 			gtk_widget_show(img);
4670 			gtk_widget_show(gtkblist->menutrayicon);
4671 			g_signal_connect(G_OBJECT(gtkblist->menutrayicon), "button-press-event", G_CALLBACK(menutray_press_cb), NULL);
4672 
4673 			pidgin_menu_tray_append(PIDGIN_MENU_TRAY(gtkblist->menutray), gtkblist->menutrayicon, tooltip_text->str);
4674 		}
4675 		g_string_free(tooltip_text, TRUE);
4676 		g_list_free(convs);
4677 	}
4678 }
4679 
4680 static void
conversation_deleting_cb(PurpleConversation * conv,PidginBuddyList * gtkblist)4681 conversation_deleting_cb(PurpleConversation *conv, PidginBuddyList *gtkblist)
4682 {
4683 	conversation_updated_cb(conv, PURPLE_CONV_UPDATE_UNSEEN, gtkblist);
4684 }
4685 
4686 static void
conversation_deleted_update_ui_cb(PurpleConversation * conv,struct _pidgin_blist_node * ui)4687 conversation_deleted_update_ui_cb(PurpleConversation *conv, struct _pidgin_blist_node *ui)
4688 {
4689 	if (ui->conv.conv != conv)
4690 		return;
4691 	ui->conv.conv = NULL;
4692 	ui->conv.flags = 0;
4693 	ui->conv.last_message = 0;
4694 }
4695 
4696 static void
written_msg_update_ui_cb(PurpleAccount * account,const char * who,const char * message,PurpleConversation * conv,PurpleMessageFlags flag,PurpleBlistNode * node)4697 written_msg_update_ui_cb(PurpleAccount *account, const char *who, const char *message,
4698 		PurpleConversation *conv, PurpleMessageFlags flag, PurpleBlistNode *node)
4699 {
4700 	PidginBlistNode *ui = node->ui_data;
4701 	if (ui->conv.conv != conv || !pidgin_conv_is_hidden(PIDGIN_CONVERSATION(conv)) ||
4702 			!(flag & (PURPLE_MESSAGE_SEND | PURPLE_MESSAGE_RECV)))
4703 		return;
4704 	ui->conv.flags |= PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE;
4705 	if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT
4706 			&& (flag & PURPLE_MESSAGE_NICK))
4707 		ui->conv.flags |= PIDGIN_BLIST_CHAT_HAS_PENDING_MESSAGE_WITH_NICK;
4708 
4709 	ui->conv.last_message = time(NULL);    /* XXX: for lack of better data */
4710 	pidgin_blist_update(purple_get_blist(), node);
4711 }
4712 
4713 static void
displayed_msg_update_ui_cb(PidginConversation * gtkconv,PurpleBlistNode * node)4714 displayed_msg_update_ui_cb(PidginConversation *gtkconv, PurpleBlistNode *node)
4715 {
4716 	PidginBlistNode *ui = node->ui_data;
4717 	if (ui->conv.conv != gtkconv->active_conv)
4718 		return;
4719 	ui->conv.flags &= ~(PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE |
4720 	                    PIDGIN_BLIST_CHAT_HAS_PENDING_MESSAGE_WITH_NICK);
4721 	pidgin_blist_update(purple_get_blist(), node);
4722 }
4723 
4724 static void
conversation_created_cb(PurpleConversation * conv,PidginBuddyList * gtkblist)4725 conversation_created_cb(PurpleConversation *conv, PidginBuddyList *gtkblist)
4726 {
4727 	switch (conv->type) {
4728 		case PURPLE_CONV_TYPE_IM:
4729 			{
4730 				GSList *buddies = purple_find_buddies(conv->account, conv->name);
4731 				while (buddies) {
4732 					PurpleBlistNode *buddy = buddies->data;
4733 					struct _pidgin_blist_node *ui = buddy->ui_data;
4734 					buddies = g_slist_delete_link(buddies, buddies);
4735 					if (!ui)
4736 						continue;
4737 					ui->conv.conv = conv;
4738 					ui->conv.flags = 0;
4739 					ui->conv.last_message = 0;
4740 					purple_signal_connect(purple_conversations_get_handle(), "deleting-conversation",
4741 							ui, PURPLE_CALLBACK(conversation_deleted_update_ui_cb), ui);
4742 					purple_signal_connect(purple_conversations_get_handle(), "wrote-im-msg",
4743 							ui, PURPLE_CALLBACK(written_msg_update_ui_cb), buddy);
4744 					purple_signal_connect(pidgin_conversations_get_handle(), "conversation-displayed",
4745 							ui, PURPLE_CALLBACK(displayed_msg_update_ui_cb), buddy);
4746 				}
4747 			}
4748 			break;
4749 		default:
4750 			break;
4751 	}
4752 }
4753 
4754 static void
chat_joined_cb(PurpleConversation * conv,PidginBuddyList * gtkblist)4755 chat_joined_cb(PurpleConversation *conv, PidginBuddyList *gtkblist)
4756 {
4757 	switch (conv->type) {
4758 		case PURPLE_CONV_TYPE_CHAT:
4759 			{
4760 				PurpleChat *chat = purple_blist_find_chat(conv->account, conv->name);
4761 				struct _pidgin_blist_node *ui;
4762 				if (!chat)
4763 					break;
4764 				ui = chat->node.ui_data;
4765 				if (!ui)
4766 					break;
4767 				ui->conv.conv = conv;
4768 				ui->conv.flags = 0;
4769 				ui->conv.last_message = 0;
4770 				purple_signal_connect(purple_conversations_get_handle(), "deleting-conversation",
4771 						ui, PURPLE_CALLBACK(conversation_deleted_update_ui_cb), ui);
4772 				purple_signal_connect(purple_conversations_get_handle(), "wrote-chat-msg",
4773 						ui, PURPLE_CALLBACK(written_msg_update_ui_cb), chat);
4774 				purple_signal_connect(pidgin_conversations_get_handle(), "conversation-displayed",
4775 						ui, PURPLE_CALLBACK(displayed_msg_update_ui_cb), chat);
4776 			}
4777 			break;
4778 		default:
4779 			break;
4780 	}
4781 }
4782 
4783 /**********************************************************************************
4784  * Public API Functions                                                           *
4785  **********************************************************************************/
4786 
pidgin_blist_new_list(PurpleBuddyList * blist)4787 static void pidgin_blist_new_list(PurpleBuddyList *blist)
4788 {
4789 	PidginBuddyList *gtkblist;
4790 
4791 	gtkblist = g_new0(PidginBuddyList, 1);
4792 	gtkblist->connection_errors = g_hash_table_new_full(g_direct_hash,
4793 												g_direct_equal, NULL, g_free);
4794 	gtkblist->priv = g_new0(PidginBuddyListPrivate, 1);
4795 
4796 	blist->ui_data = gtkblist;
4797 }
4798 
pidgin_blist_new_node(PurpleBlistNode * node)4799 static void pidgin_blist_new_node(PurpleBlistNode *node)
4800 {
4801 	node->ui_data = g_new0(struct _pidgin_blist_node, 1);
4802 }
4803 
pidgin_blist_node_is_contact_expanded(PurpleBlistNode * node)4804 gboolean pidgin_blist_node_is_contact_expanded(PurpleBlistNode *node)
4805 {
4806 	if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
4807 		node = node->parent;
4808 		if (node == NULL)
4809 			return FALSE;
4810 	}
4811 
4812 	g_return_val_if_fail(PURPLE_BLIST_NODE_IS_CONTACT(node), FALSE);
4813 
4814 	return ((struct _pidgin_blist_node *)node->ui_data)->contact_expanded;
4815 }
4816 
4817 enum {
4818 	DRAG_BUDDY,
4819 	DRAG_ROW,
4820 	DRAG_VCARD,
4821 	DRAG_TEXT,
4822 	DRAG_URI,
4823 	NUM_TARGETS
4824 };
4825 
4826 static const char *
item_factory_translate_func(const char * path,gpointer func_data)4827 item_factory_translate_func (const char *path, gpointer func_data)
4828 {
4829 	return _((char *)path);
4830 }
4831 
pidgin_blist_setup_sort_methods()4832 void pidgin_blist_setup_sort_methods()
4833 {
4834 	const char *id;
4835 
4836 	pidgin_blist_sort_method_reg("none", _("Manually"), sort_method_none);
4837 	pidgin_blist_sort_method_reg("alphabetical", _("Alphabetically"), sort_method_alphabetical);
4838 	pidgin_blist_sort_method_reg("status", _("By status"), sort_method_status);
4839 	pidgin_blist_sort_method_reg("log_size", _("By recent log activity"), sort_method_log_activity);
4840 
4841 	id = purple_prefs_get_string(PIDGIN_PREFS_ROOT "/blist/sort_type");
4842 	if (id == NULL) {
4843 		purple_debug_warning("gtkblist", "Sort method was NULL, resetting to alphabetical\n");
4844 		id = "alphabetical";
4845 	}
4846 	pidgin_blist_sort_method_set(id);
4847 }
4848 
_prefs_change_redo_list(const char * name,PurplePrefType type,gconstpointer val,gpointer data)4849 static void _prefs_change_redo_list(const char *name, PurplePrefType type,
4850                                     gconstpointer val, gpointer data)
4851 {
4852 	GtkTreeSelection *sel;
4853 	GtkTreeIter iter;
4854 	PurpleBlistNode *node = NULL;
4855 
4856 	sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist->treeview));
4857 	if (gtk_tree_selection_get_selected(sel, NULL, &iter))
4858 	{
4859 		gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
4860 	}
4861 
4862 	redo_buddy_list(purple_get_blist(), FALSE, FALSE);
4863 	gtk_tree_view_columns_autosize(GTK_TREE_VIEW(gtkblist->treeview));
4864 
4865 	if (node)
4866 	{
4867 		struct _pidgin_blist_node *gtknode;
4868 		GtkTreePath *path;
4869 
4870 		gtknode = node->ui_data;
4871 		if (gtknode && gtknode->row)
4872 		{
4873 			path = gtk_tree_row_reference_get_path(gtknode->row);
4874 			gtk_tree_selection_select_path(sel, path);
4875 			gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(gtkblist->treeview), path, NULL, FALSE, 0, 0);
4876 			gtk_tree_path_free(path);
4877 		}
4878 	}
4879 }
4880 
_prefs_change_sort_method(const char * pref_name,PurplePrefType type,gconstpointer val,gpointer data)4881 static void _prefs_change_sort_method(const char *pref_name, PurplePrefType type,
4882 									  gconstpointer val, gpointer data)
4883 {
4884 	if(purple_strequal(pref_name, PIDGIN_PREFS_ROOT "/blist/sort_type"))
4885 		pidgin_blist_sort_method_set(val);
4886 }
4887 
pidgin_blist_select_notebook_page_cb(gpointer user_data)4888 static gboolean pidgin_blist_select_notebook_page_cb(gpointer user_data)
4889 {
4890 	PidginBuddyList *gtkblist = (PidginBuddyList *)user_data;
4891 	int errors = 0;
4892 	GList *list = NULL;
4893 	PidginBuddyListPrivate *priv;
4894 
4895 	priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
4896 
4897 	priv->select_page_timeout = 0;
4898 
4899 	/* this is far too ugly thanks to me not wanting to fix #3989 properly right now */
4900 	if (priv->error_scrollbook != NULL) {
4901 		errors = gtk_notebook_get_n_pages(GTK_NOTEBOOK(priv->error_scrollbook->notebook));
4902 	}
4903 	if ((list = purple_accounts_get_all_active()) != NULL || errors) {
4904 		gtk_notebook_set_current_page(GTK_NOTEBOOK(gtkblist->notebook), 1);
4905 		g_list_free(list);
4906 	} else
4907 		gtk_notebook_set_current_page(GTK_NOTEBOOK(gtkblist->notebook), 0);
4908 
4909 	return FALSE;
4910 }
4911 
pidgin_blist_select_notebook_page(PidginBuddyList * gtkblist)4912 static void pidgin_blist_select_notebook_page(PidginBuddyList *gtkblist)
4913 {
4914 	PidginBuddyListPrivate *priv;
4915 
4916 	priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
4917 
4918 	priv->select_page_timeout = purple_timeout_add(0, pidgin_blist_select_notebook_page_cb, gtkblist);
4919 }
4920 
account_modified(PurpleAccount * account,PidginBuddyList * gtkblist)4921 static void account_modified(PurpleAccount *account, PidginBuddyList *gtkblist)
4922 {
4923 	if (!gtkblist)
4924 		return;
4925 
4926 	pidgin_blist_select_notebook_page(gtkblist);
4927 	update_menu_bar(gtkblist);
4928 }
4929 
4930 static void
account_actions_changed(PurpleAccount * account,gpointer data)4931 account_actions_changed(PurpleAccount *account, gpointer data)
4932 {
4933 	pidgin_blist_update_accounts_menu();
4934 }
4935 
4936 static void
account_status_changed(PurpleAccount * account,PurpleStatus * old,PurpleStatus * new,PidginBuddyList * gtkblist)4937 account_status_changed(PurpleAccount *account, PurpleStatus *old,
4938 					   PurpleStatus *new, PidginBuddyList *gtkblist)
4939 {
4940 	if (!gtkblist)
4941 		return;
4942 
4943 	account_modified(account, gtkblist);
4944 }
4945 
4946 static gboolean
gtk_blist_window_key_press_cb(GtkWidget * w,GdkEventKey * event,PidginBuddyList * gtkblist)4947 gtk_blist_window_key_press_cb(GtkWidget *w, GdkEventKey *event, PidginBuddyList *gtkblist)
4948 {
4949 	GtkWidget *widget;
4950 
4951 	if (!gtkblist)
4952 		return FALSE;
4953 
4954 	/* clear any tooltips */
4955 	pidgin_blist_tooltip_destroy();
4956 
4957 	widget = gtk_window_get_focus(GTK_WINDOW(gtkblist->window));
4958 
4959 	if (GTK_IS_IMHTML(widget) || GTK_IS_ENTRY(widget)) {
4960 		if (gtk_bindings_activate(GTK_OBJECT(widget), event->keyval, event->state))
4961 			return TRUE;
4962 	}
4963 	return FALSE;
4964 }
4965 
4966 static gboolean
headline_box_enter_cb(GtkWidget * widget,GdkEventCrossing * event,PidginBuddyList * gtkblist)4967 headline_box_enter_cb(GtkWidget *widget, GdkEventCrossing *event, PidginBuddyList *gtkblist)
4968 {
4969 	gdk_window_set_cursor(widget->window, gtkblist->hand_cursor);
4970 	return FALSE;
4971 }
4972 
4973 static gboolean
headline_box_leave_cb(GtkWidget * widget,GdkEventCrossing * event,PidginBuddyList * gtkblist)4974 headline_box_leave_cb(GtkWidget *widget, GdkEventCrossing *event, PidginBuddyList *gtkblist)
4975 {
4976 	gdk_window_set_cursor(widget->window, gtkblist->arrow_cursor);
4977 	return FALSE;
4978 }
4979 
4980 static void
reset_headline(PidginBuddyList * gtkblist)4981 reset_headline(PidginBuddyList *gtkblist)
4982 {
4983 	gtkblist->headline_callback = NULL;
4984 	gtkblist->headline_data = NULL;
4985 	gtkblist->headline_destroy = NULL;
4986 	pidgin_set_urgent(GTK_WINDOW(gtkblist->window), FALSE);
4987 }
4988 
4989 static gboolean
headline_click_callback(gpointer unused)4990 headline_click_callback(gpointer unused)
4991 {
4992 	if (gtkblist->headline_callback)
4993 		((GSourceFunc) gtkblist->headline_callback)(gtkblist->headline_data);
4994 	reset_headline(gtkblist);
4995 	return FALSE;
4996 }
4997 
4998 static gboolean
headline_close_press_cb(GtkButton * button,PidginBuddyList * gtkblist)4999 headline_close_press_cb(GtkButton *button, PidginBuddyList *gtkblist)
5000 {
5001 	gtk_widget_hide(gtkblist->headline_hbox);
5002 	return FALSE;
5003 }
5004 
5005 static gboolean
headline_box_press_cb(GtkWidget * widget,GdkEventButton * event,PidginBuddyList * gtkblist)5006 headline_box_press_cb(GtkWidget *widget, GdkEventButton *event, PidginBuddyList *gtkblist)
5007 {
5008 	gtk_widget_hide(gtkblist->headline_hbox);
5009 	if (gtkblist->headline_callback)
5010 		g_idle_add(headline_click_callback, NULL);
5011 	else {
5012 		if (gtkblist->headline_destroy)
5013 			gtkblist->headline_destroy(gtkblist->headline_data);
5014 		reset_headline(gtkblist);
5015 	}
5016 	return TRUE;
5017 }
5018 
5019 /***********************************/
5020 /* Connection error handling stuff */
5021 /***********************************/
5022 
5023 #define OBJECT_DATA_KEY_ACCOUNT "account"
5024 #define DO_NOT_CLEAR_ERROR "do-not-clear-error"
5025 
5026 static gboolean
find_account_widget(GObject * widget,PurpleAccount * account)5027 find_account_widget(GObject *widget,
5028                     PurpleAccount *account)
5029 {
5030 	if (g_object_get_data(widget, OBJECT_DATA_KEY_ACCOUNT) == account)
5031 		return 0; /* found */
5032 	else
5033 		return 1;
5034 }
5035 
5036 static void
pack_prpl_icon_start(GtkWidget * box,PurpleAccount * account)5037 pack_prpl_icon_start(GtkWidget *box,
5038                      PurpleAccount *account)
5039 {
5040 	GdkPixbuf *pixbuf;
5041 	GtkWidget *image;
5042 
5043 	pixbuf = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL);
5044 	if (pixbuf != NULL) {
5045 		image = gtk_image_new_from_pixbuf(pixbuf);
5046 		g_object_unref(pixbuf);
5047 
5048 		gtk_box_pack_start(GTK_BOX(box), image, FALSE, FALSE, 0);
5049 	}
5050 }
5051 
5052 static void
add_error_dialog(PidginBuddyList * gtkblist,GtkWidget * dialog)5053 add_error_dialog(PidginBuddyList *gtkblist,
5054                  GtkWidget *dialog)
5055 {
5056 	PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
5057 	gtk_container_add(GTK_CONTAINER(priv->error_scrollbook), dialog);
5058 }
5059 
5060 static GtkWidget *
find_child_widget_by_account(GtkContainer * container,PurpleAccount * account)5061 find_child_widget_by_account(GtkContainer *container,
5062                              PurpleAccount *account)
5063 {
5064 	GList *l = NULL;
5065 	GList *children = NULL;
5066 	GtkWidget *ret = NULL;
5067 	/* XXX: Workaround for the currently incomplete implementation of PidginScrollBook */
5068 	if (PIDGIN_IS_SCROLL_BOOK(container))
5069 		container = GTK_CONTAINER(PIDGIN_SCROLL_BOOK(container)->notebook);
5070 	children = gtk_container_get_children(container);
5071 	l = g_list_find_custom(children, account, (GCompareFunc) find_account_widget);
5072 	if (l)
5073 		ret = GTK_WIDGET(l->data);
5074 	g_list_free(children);
5075 	return ret;
5076 }
5077 
5078 static void
remove_child_widget_by_account(GtkContainer * container,PurpleAccount * account)5079 remove_child_widget_by_account(GtkContainer *container,
5080                                PurpleAccount *account)
5081 {
5082 	GtkWidget *widget = find_child_widget_by_account(container, account);
5083 	if(widget) {
5084 		/* Since we are destroying the widget in response to a change in
5085 		 * error, we should not clear the error.
5086 		 */
5087 		g_object_set_data(G_OBJECT(widget), DO_NOT_CLEAR_ERROR,
5088 			GINT_TO_POINTER(TRUE));
5089 		gtk_widget_destroy(widget);
5090 	}
5091 }
5092 
5093 /* Generic error buttons */
5094 
5095 static void
generic_error_modify_cb(PurpleAccount * account)5096 generic_error_modify_cb(PurpleAccount *account)
5097 {
5098 	purple_account_clear_current_error(account);
5099 	pidgin_account_dialog_show(PIDGIN_MODIFY_ACCOUNT_DIALOG, account);
5100 }
5101 
5102 static void
generic_error_enable_cb(PurpleAccount * account)5103 generic_error_enable_cb(PurpleAccount *account)
5104 {
5105 	purple_account_clear_current_error(account);
5106 	purple_account_set_enabled(account, purple_core_get_ui(), TRUE);
5107 }
5108 
5109 static void
generic_error_destroy_cb(GtkObject * dialog,PurpleAccount * account)5110 generic_error_destroy_cb(GtkObject *dialog,
5111                          PurpleAccount *account)
5112 {
5113 	g_hash_table_remove(gtkblist->connection_errors, account);
5114 	/* If the error dialog is being destroyed in response to the
5115 	 * account-error-changed signal, we don't want to clear the current
5116 	 * error.
5117 	 */
5118 	if (g_object_get_data(G_OBJECT(dialog), DO_NOT_CLEAR_ERROR) == NULL)
5119 		purple_account_clear_current_error(account);
5120 }
5121 
5122 #define SSL_FAQ_URI "https://developer.pidgin.im/wiki/FAQssl"
5123 
5124 static void
ssl_faq_clicked_cb(PidginMiniDialog * mini_dialog,GtkButton * button,gpointer ignored)5125 ssl_faq_clicked_cb(PidginMiniDialog *mini_dialog,
5126                    GtkButton *button,
5127                    gpointer ignored)
5128 {
5129 	purple_notify_uri(NULL, SSL_FAQ_URI);
5130 }
5131 
5132 static void
add_generic_error_dialog(PurpleAccount * account,const PurpleConnectionErrorInfo * err)5133 add_generic_error_dialog(PurpleAccount *account,
5134                          const PurpleConnectionErrorInfo *err)
5135 {
5136 	GtkWidget *mini_dialog;
5137 	const char *username = purple_account_get_username(account);
5138 	gboolean enabled =
5139 		purple_account_get_enabled(account, purple_core_get_ui());
5140 	char *primary;
5141 
5142 	if (enabled)
5143 		primary = g_strdup_printf(_("%s disconnected"), username);
5144 	else
5145 		primary = g_strdup_printf(_("%s disabled"), username);
5146 
5147 	mini_dialog = pidgin_make_mini_dialog(NULL, PIDGIN_STOCK_DIALOG_ERROR,
5148 		primary, err->description, account,
5149 		(enabled ? _("Reconnect") : _("Re-enable")),
5150 		(enabled ? PURPLE_CALLBACK(purple_account_connect)
5151 		         : PURPLE_CALLBACK(generic_error_enable_cb)),
5152 		_("Modify Account"), PURPLE_CALLBACK(generic_error_modify_cb),
5153 		NULL);
5154 
5155 	g_free(primary);
5156 
5157 	g_object_set_data(G_OBJECT(mini_dialog), OBJECT_DATA_KEY_ACCOUNT,
5158 		account);
5159 
5160 	 if(err->type == PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT)
5161 		pidgin_mini_dialog_add_non_closing_button(PIDGIN_MINI_DIALOG(mini_dialog),
5162 				_("SSL FAQs"), ssl_faq_clicked_cb, NULL);
5163 
5164 	g_signal_connect_after(mini_dialog, "destroy",
5165 		(GCallback)generic_error_destroy_cb,
5166 		account);
5167 
5168 	add_error_dialog(gtkblist, mini_dialog);
5169 }
5170 
5171 static void
remove_generic_error_dialog(PurpleAccount * account)5172 remove_generic_error_dialog(PurpleAccount *account)
5173 {
5174 	PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
5175 	remove_child_widget_by_account(
5176 		GTK_CONTAINER(priv->error_scrollbook), account);
5177 }
5178 
5179 
5180 static void
update_generic_error_message(PurpleAccount * account,const char * description)5181 update_generic_error_message(PurpleAccount *account,
5182                              const char *description)
5183 {
5184 	PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
5185 	GtkWidget *mini_dialog = find_child_widget_by_account(
5186 		GTK_CONTAINER(priv->error_scrollbook), account);
5187 	pidgin_mini_dialog_set_description(PIDGIN_MINI_DIALOG(mini_dialog),
5188 		description);
5189 }
5190 
5191 
5192 /* Notifications about accounts which were disconnected with
5193  * PURPLE_CONNECTION_ERROR_NAME_IN_USE
5194  */
5195 
5196 typedef void (*AccountFunction)(PurpleAccount *);
5197 
5198 static void
elsewhere_foreach_account(PidginMiniDialog * mini_dialog,AccountFunction f)5199 elsewhere_foreach_account(PidginMiniDialog *mini_dialog,
5200                           AccountFunction f)
5201 {
5202 	PurpleAccount *account;
5203 	GList *labels = gtk_container_get_children(
5204 		GTK_CONTAINER(mini_dialog->contents));
5205 	GList *l;
5206 
5207 	for (l = labels; l; l = l->next) {
5208 		account = g_object_get_data(G_OBJECT(l->data), OBJECT_DATA_KEY_ACCOUNT);
5209 		if (account)
5210 			f(account);
5211 		else
5212 			purple_debug_warning("gtkblist", "mini_dialog's child "
5213 				"didn't have an account stored in it!");
5214 	}
5215 	g_list_free(labels);
5216 }
5217 
5218 static void
enable_account(PurpleAccount * account)5219 enable_account(PurpleAccount *account)
5220 {
5221 	purple_account_set_enabled(account, purple_core_get_ui(), TRUE);
5222 }
5223 
5224 static void
reconnect_elsewhere_accounts(PidginMiniDialog * mini_dialog,GtkButton * button,gpointer unused)5225 reconnect_elsewhere_accounts(PidginMiniDialog *mini_dialog,
5226                              GtkButton *button,
5227                              gpointer unused)
5228 {
5229 	elsewhere_foreach_account(mini_dialog, enable_account);
5230 }
5231 
5232 static void
clear_elsewhere_errors(PidginMiniDialog * mini_dialog,gpointer unused)5233 clear_elsewhere_errors(PidginMiniDialog *mini_dialog,
5234                        gpointer unused)
5235 {
5236 	elsewhere_foreach_account(mini_dialog, purple_account_clear_current_error);
5237 }
5238 
5239 static void
ensure_signed_on_elsewhere_minidialog(PidginBuddyList * gtkblist)5240 ensure_signed_on_elsewhere_minidialog(PidginBuddyList *gtkblist)
5241 {
5242 	PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
5243 	PidginMiniDialog *mini_dialog;
5244 
5245 	if(priv->signed_on_elsewhere)
5246 		return;
5247 
5248 	mini_dialog = priv->signed_on_elsewhere =
5249 		pidgin_mini_dialog_new(_("Welcome back!"), NULL, PIDGIN_STOCK_DISCONNECT);
5250 
5251 	pidgin_mini_dialog_add_button(mini_dialog, _("Re-enable"),
5252 		reconnect_elsewhere_accounts, NULL);
5253 
5254 	/* Make dismissing the dialog clear the errors.  The "destroy" signal
5255 	 * does not appear to fire at quit, which is fortunate!
5256 	 */
5257 	g_signal_connect(G_OBJECT(mini_dialog), "destroy",
5258 		(GCallback) clear_elsewhere_errors, NULL);
5259 
5260 	add_error_dialog(gtkblist, GTK_WIDGET(mini_dialog));
5261 
5262 	/* Set priv->signed_on_elsewhere to NULL when the dialog is destroyed */
5263 	g_signal_connect(G_OBJECT(mini_dialog), "destroy",
5264 		(GCallback) gtk_widget_destroyed, &(priv->signed_on_elsewhere));
5265 }
5266 
5267 static void
update_signed_on_elsewhere_minidialog_title(void)5268 update_signed_on_elsewhere_minidialog_title(void)
5269 {
5270 	PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
5271 	PidginMiniDialog *mini_dialog = priv->signed_on_elsewhere;
5272 	guint accounts;
5273 	char *title;
5274 
5275 	if (mini_dialog == NULL)
5276 		return;
5277 
5278 	accounts = pidgin_mini_dialog_get_num_children(mini_dialog);
5279 	if (accounts == 0) {
5280 		gtk_widget_destroy(GTK_WIDGET(mini_dialog));
5281 		return;
5282 	}
5283 
5284 	title = g_strdup_printf(
5285 		ngettext("%d account was disabled because you signed on from another location:",
5286 			 "%d accounts were disabled because you signed on from another location:",
5287 			 accounts),
5288 		accounts);
5289 	pidgin_mini_dialog_set_description(mini_dialog, title);
5290 	g_free(title);
5291 }
5292 
5293 static GtkWidget *
create_account_label(PurpleAccount * account)5294 create_account_label(PurpleAccount *account)
5295 {
5296 	GtkWidget *hbox, *label;
5297 	const char *username = purple_account_get_username(account);
5298 	char *markup;
5299 
5300 	hbox = gtk_hbox_new(FALSE, 6);
5301 	g_object_set_data(G_OBJECT(hbox), OBJECT_DATA_KEY_ACCOUNT, account);
5302 
5303 	pack_prpl_icon_start(hbox, account);
5304 
5305 	label = gtk_label_new(NULL);
5306 	markup = g_strdup_printf("<span size=\"smaller\">%s</span>", username);
5307 	gtk_label_set_markup(GTK_LABEL(label), markup);
5308 	g_free(markup);
5309 	gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
5310 	g_object_set(G_OBJECT(label), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
5311 #if GTK_CHECK_VERSION(2,12,0)
5312 	{ /* avoid unused variable warnings on pre-2.12 Gtk */
5313 		char *description =
5314 			purple_account_get_current_error(account)->description;
5315 		if (description != NULL && *description != '\0')
5316 			gtk_widget_set_tooltip_text(label, description);
5317 	}
5318 #endif
5319 	gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
5320 
5321 	return hbox;
5322 }
5323 
5324 static void
add_to_signed_on_elsewhere(PurpleAccount * account)5325 add_to_signed_on_elsewhere(PurpleAccount *account)
5326 {
5327 	PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
5328 	PidginMiniDialog *mini_dialog;
5329 	GtkWidget *account_label;
5330 
5331 	ensure_signed_on_elsewhere_minidialog(gtkblist);
5332 	mini_dialog = priv->signed_on_elsewhere;
5333 
5334 	if(find_child_widget_by_account(GTK_CONTAINER(mini_dialog->contents), account))
5335 		return;
5336 
5337 	account_label = create_account_label(account);
5338 	gtk_box_pack_start(mini_dialog->contents, account_label, FALSE, FALSE, 0);
5339 	gtk_widget_show_all(account_label);
5340 
5341 	update_signed_on_elsewhere_minidialog_title();
5342 }
5343 
5344 static void
remove_from_signed_on_elsewhere(PurpleAccount * account)5345 remove_from_signed_on_elsewhere(PurpleAccount *account)
5346 {
5347 	PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
5348 	PidginMiniDialog *mini_dialog = priv->signed_on_elsewhere;
5349 	if(mini_dialog == NULL)
5350 		return;
5351 
5352 	remove_child_widget_by_account(GTK_CONTAINER(mini_dialog->contents), account);
5353 
5354 	update_signed_on_elsewhere_minidialog_title();
5355 }
5356 
5357 
5358 static void
update_signed_on_elsewhere_tooltip(PurpleAccount * account,const char * description)5359 update_signed_on_elsewhere_tooltip(PurpleAccount *account,
5360                                    const char *description)
5361 {
5362 #if GTK_CHECK_VERSION(2,12,0)
5363 	PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
5364 	GtkContainer *c = GTK_CONTAINER(priv->signed_on_elsewhere->contents);
5365 	GtkWidget *label = find_child_widget_by_account(c, account);
5366 	gtk_widget_set_tooltip_text(label, description);
5367 #endif
5368 }
5369 
5370 
5371 /* Call appropriate error notification code based on error types */
5372 static void
update_account_error_state(PurpleAccount * account,const PurpleConnectionErrorInfo * old,const PurpleConnectionErrorInfo * new,PidginBuddyList * gtkblist)5373 update_account_error_state(PurpleAccount *account,
5374                            const PurpleConnectionErrorInfo *old,
5375                            const PurpleConnectionErrorInfo *new,
5376                            PidginBuddyList *gtkblist)
5377 {
5378 	gboolean descriptions_differ;
5379 	const char *desc;
5380 
5381 	if (old == NULL && new == NULL)
5382 		return;
5383 
5384 	/* For backwards compatibility: */
5385 	if (new)
5386 		pidgin_blist_update_account_error_state(account, new->description);
5387 	else
5388 		pidgin_blist_update_account_error_state(account, NULL);
5389 
5390 	if (new != NULL)
5391 		pidgin_blist_select_notebook_page(gtkblist);
5392 
5393 	if (old != NULL && new == NULL) {
5394 		if(old->type == PURPLE_CONNECTION_ERROR_NAME_IN_USE)
5395 			remove_from_signed_on_elsewhere(account);
5396 		else
5397 			remove_generic_error_dialog(account);
5398 		return;
5399 	}
5400 
5401 	if (old == NULL && new != NULL) {
5402 		if(new->type == PURPLE_CONNECTION_ERROR_NAME_IN_USE)
5403 			add_to_signed_on_elsewhere(account);
5404 		else
5405 			add_generic_error_dialog(account, new);
5406 		return;
5407 	}
5408 
5409 	/* else, new and old are both non-NULL */
5410 
5411 	descriptions_differ = !purple_strequal(old->description, new->description);
5412 	desc = new->description;
5413 
5414 	switch (new->type) {
5415 	case PURPLE_CONNECTION_ERROR_NAME_IN_USE:
5416 		if (old->type == PURPLE_CONNECTION_ERROR_NAME_IN_USE
5417 		    && descriptions_differ) {
5418 			update_signed_on_elsewhere_tooltip(account, desc);
5419 		} else {
5420 			remove_generic_error_dialog(account);
5421 			add_to_signed_on_elsewhere(account);
5422 		}
5423 		break;
5424 	default:
5425 		if (old->type == PURPLE_CONNECTION_ERROR_NAME_IN_USE) {
5426 			remove_from_signed_on_elsewhere(account);
5427 			add_generic_error_dialog(account, new);
5428 		} else if (descriptions_differ) {
5429 			update_generic_error_message(account, desc);
5430 		}
5431 		break;
5432 	}
5433 }
5434 
5435 /* In case accounts are loaded before the blist (which they currently are),
5436  * let's call update_account_error_state ourselves on every account's current
5437  * state when the blist starts.
5438  */
5439 static void
show_initial_account_errors(PidginBuddyList * gtkblist)5440 show_initial_account_errors(PidginBuddyList *gtkblist)
5441 {
5442 	GList *l = purple_accounts_get_all();
5443 	PurpleAccount *account;
5444 	const PurpleConnectionErrorInfo *err;
5445 
5446 	for (; l; l = l->next)
5447 	{
5448 		account = l->data;
5449 		err = purple_account_get_current_error(account);
5450 
5451 		update_account_error_state(account, NULL, err, gtkblist);
5452 	}
5453 }
5454 
5455 void
pidgin_blist_update_account_error_state(PurpleAccount * account,const char * text)5456 pidgin_blist_update_account_error_state(PurpleAccount *account, const char *text)
5457 {
5458 	/* connection_errors isn't actually used anywhere; it's just kept in
5459 	 * sync with reality in case a plugin uses it.
5460 	 */
5461 	if (text == NULL)
5462 		g_hash_table_remove(gtkblist->connection_errors, account);
5463 	else
5464 		g_hash_table_insert(gtkblist->connection_errors, account, g_strdup(text));
5465 }
5466 
5467 static gboolean
paint_headline_hbox(GtkWidget * widget,GdkEventExpose * event,gpointer user_data)5468 paint_headline_hbox  (GtkWidget      *widget,
5469 		      GdkEventExpose *event,
5470 		      gpointer user_data)
5471 {
5472 	gtk_paint_flat_box (widget->style,
5473 		      widget->window,
5474 		      GTK_STATE_NORMAL,
5475 		      GTK_SHADOW_OUT,
5476 		      NULL,
5477 		      widget,
5478 		      "tooltip",
5479 		      widget->allocation.x + 1,
5480 		      widget->allocation.y + 1,
5481 		      widget->allocation.width - 2,
5482 		      widget->allocation.height - 2);
5483 	return FALSE;
5484 }
5485 
5486 /* This assumes there are not things like groupless buddies or multi-leveled groups.
5487  * I'm sure other things in this code assumes that also.
5488  */
5489 static void
treeview_style_set(GtkWidget * widget,GtkStyle * prev_style,gpointer data)5490 treeview_style_set (GtkWidget *widget,
5491 		    GtkStyle *prev_style,
5492 		    gpointer data)
5493 {
5494 	PurpleBuddyList *list = data;
5495 	PurpleBlistNode *node = list->root;
5496 	while (node) {
5497 		pidgin_blist_update_group(list, node);
5498 		node = node->next;
5499 	}
5500 }
5501 
5502 static void
headline_style_set(GtkWidget * widget,GtkStyle * prev_style)5503 headline_style_set (GtkWidget *widget,
5504 		    GtkStyle  *prev_style)
5505 {
5506 	GtkTooltips *tooltips;
5507 	GtkStyle *style;
5508 
5509 	if (gtkblist->changing_style)
5510 		return;
5511 
5512 	tooltips = gtk_tooltips_new ();
5513 	g_object_ref_sink (tooltips);
5514 
5515 	gtk_tooltips_force_window (tooltips);
5516 #if GTK_CHECK_VERSION(2, 12, 0)
5517 	gtk_widget_set_name (tooltips->tip_window, "gtk-tooltips");
5518 #endif
5519 	gtk_widget_ensure_style (tooltips->tip_window);
5520 	style = gtk_widget_get_style (tooltips->tip_window);
5521 
5522 	gtkblist->changing_style = TRUE;
5523 	gtk_widget_set_style (gtkblist->headline_hbox, style);
5524 	gtkblist->changing_style = FALSE;
5525 
5526 	g_object_unref (tooltips);
5527 }
5528 
5529 /******************************************/
5530 /* End of connection error handling stuff */
5531 /******************************************/
5532 
5533 static int
blist_focus_cb(GtkWidget * widget,GdkEventFocus * event,PidginBuddyList * gtkblist)5534 blist_focus_cb(GtkWidget *widget, GdkEventFocus *event, PidginBuddyList *gtkblist)
5535 {
5536 	if(event->in) {
5537 		gtk_blist_focused = TRUE;
5538 		pidgin_set_urgent(GTK_WINDOW(gtkblist->window), FALSE);
5539 	} else {
5540 		gtk_blist_focused = FALSE;
5541 	}
5542 	return 0;
5543 }
5544 
5545 #if 0
5546 static GtkWidget *
5547 kiosk_page()
5548 {
5549 	GtkWidget *ret = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
5550 	GtkWidget *label;
5551 	GtkWidget *entry;
5552 	GtkWidget *bbox;
5553 	GtkWidget *button;
5554 
5555 	label = gtk_label_new(NULL);
5556 	gtk_box_pack_start(GTK_BOX(ret), label, TRUE, TRUE, 0);
5557 
5558 	label = gtk_label_new(NULL);
5559 	gtk_label_set_markup(GTK_LABEL(label), _("<b>Username:</b>"));
5560 	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
5561 	gtk_box_pack_start(GTK_BOX(ret), label, FALSE, FALSE, 0);
5562 	entry = gtk_entry_new();
5563 	gtk_box_pack_start(GTK_BOX(ret), entry, FALSE, FALSE, 0);
5564 
5565 	label = gtk_label_new(NULL);
5566 	gtk_label_set_markup(GTK_LABEL(label), _("<b>Password:</b>"));
5567 	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
5568 	gtk_box_pack_start(GTK_BOX(ret), label, FALSE, FALSE, 0);
5569 	entry = gtk_entry_new();
5570 	gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
5571 	gtk_box_pack_start(GTK_BOX(ret), entry, FALSE, FALSE, 0);
5572 
5573 	label = gtk_label_new(" ");
5574 	gtk_box_pack_start(GTK_BOX(ret), label, FALSE, FALSE, 0);
5575 
5576 	bbox = gtk_hbutton_box_new();
5577 	button = gtk_button_new_with_mnemonic(_("_Login"));
5578 	gtk_box_pack_start(GTK_BOX(ret), bbox, FALSE, FALSE, 0);
5579 	gtk_container_add(GTK_CONTAINER(bbox), button);
5580 
5581 
5582 	label = gtk_label_new(NULL);
5583 	gtk_box_pack_start(GTK_BOX(ret), label, TRUE, TRUE, 0);
5584 
5585 	gtk_container_set_border_width(GTK_CONTAINER(ret), PIDGIN_HIG_BORDER);
5586 
5587 	gtk_widget_show_all(ret);
5588 	return ret;
5589 }
5590 #endif
5591 
5592 /* builds the blist layout according to to the current theme */
5593 static void
pidgin_blist_build_layout(PurpleBuddyList * list)5594 pidgin_blist_build_layout(PurpleBuddyList *list)
5595 {
5596 	GtkTreeViewColumn *column;
5597 	PidginBlistLayout *layout;
5598 	PidginBlistTheme *theme;
5599 	GtkCellRenderer *rend;
5600 	gint i, status_icon = 0, text = 1, emblem = 2, protocol_icon = 3, buddy_icon = 4;
5601 
5602 
5603 	column = gtkblist->text_column;
5604 
5605 	if ((theme = pidgin_blist_get_theme()) != NULL && (layout = pidgin_blist_theme_get_layout(theme)) != NULL) {
5606 		status_icon = layout->status_icon ;
5607 		text = layout->text;
5608 		emblem = layout->emblem;
5609 		protocol_icon = layout->protocol_icon;
5610 		buddy_icon = layout->buddy_icon;
5611 	}
5612 
5613 	gtk_tree_view_column_clear(column);
5614 
5615 	/* group */
5616 	rend = pidgin_cell_renderer_expander_new();
5617 	gtk_tree_view_column_pack_start(column, rend, FALSE);
5618 	gtk_tree_view_column_set_attributes(column, rend,
5619 					    "visible", GROUP_EXPANDER_VISIBLE_COLUMN,
5620 					    "expander-visible", GROUP_EXPANDER_COLUMN,
5621 					    "sensitive", GROUP_EXPANDER_COLUMN,
5622 					    "cell-background-gdk", BGCOLOR_COLUMN,
5623 					    NULL);
5624 
5625 	/* contact */
5626 	rend = pidgin_cell_renderer_expander_new();
5627 	gtk_tree_view_column_pack_start(column, rend, FALSE);
5628 	gtk_tree_view_column_set_attributes(column, rend,
5629 					    "visible", CONTACT_EXPANDER_VISIBLE_COLUMN,
5630 					    "expander-visible", CONTACT_EXPANDER_COLUMN,
5631 					    "sensitive", CONTACT_EXPANDER_COLUMN,
5632 					    "cell-background-gdk", BGCOLOR_COLUMN,
5633 					    NULL);
5634 
5635 	for (i = 0; i < 5; i++) {
5636 
5637 		if (status_icon == i) {
5638 			/* status icons */
5639 			rend = gtk_cell_renderer_pixbuf_new();
5640 			gtk_tree_view_column_pack_start(column, rend, FALSE);
5641 			gtk_tree_view_column_set_attributes(column, rend,
5642 							    "pixbuf", STATUS_ICON_COLUMN,
5643 							    "visible", STATUS_ICON_VISIBLE_COLUMN,
5644 							    "cell-background-gdk", BGCOLOR_COLUMN,
5645 							    NULL);
5646 			g_object_set(rend, "xalign", 0.0, "xpad", 6, "ypad", 0, NULL);
5647 
5648 		} else if (text == i) {
5649 			/* name */
5650 			gtkblist->text_rend = rend = gtk_cell_renderer_text_new();
5651 			gtk_tree_view_column_pack_start(column, rend, TRUE);
5652 			gtk_tree_view_column_set_attributes(column, rend,
5653 							    "cell-background-gdk", BGCOLOR_COLUMN,
5654 							    "markup", NAME_COLUMN,
5655 							    NULL);
5656 			g_signal_connect(G_OBJECT(rend), "editing-started", G_CALLBACK(gtk_blist_renderer_editing_started_cb), NULL);
5657 			g_signal_connect(G_OBJECT(rend), "editing-canceled", G_CALLBACK(gtk_blist_renderer_editing_cancelled_cb), list);
5658 			g_signal_connect(G_OBJECT(rend), "edited", G_CALLBACK(gtk_blist_renderer_edited_cb), list);
5659 			g_object_set(rend, "ypad", 0, "yalign", 0.5, NULL);
5660 			g_object_set(rend, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
5661 
5662 			/* idle */
5663 			rend = gtk_cell_renderer_text_new();
5664 			g_object_set(rend, "xalign", 1.0, "ypad", 0, NULL);
5665 			gtk_tree_view_column_pack_start(column, rend, FALSE);
5666 			gtk_tree_view_column_set_attributes(column, rend,
5667 							    "markup", IDLE_COLUMN,
5668 							    "visible", IDLE_VISIBLE_COLUMN,
5669 							    "cell-background-gdk", BGCOLOR_COLUMN,
5670 							    NULL);
5671 		} else if (emblem == i) {
5672 			/* emblem */
5673 			rend = gtk_cell_renderer_pixbuf_new();
5674 			g_object_set(rend, "xalign", 1.0, "yalign", 0.5, "ypad", 0, "xpad", 3, NULL);
5675 			gtk_tree_view_column_pack_start(column, rend, FALSE);
5676 			gtk_tree_view_column_set_attributes(column, rend, "pixbuf", EMBLEM_COLUMN,
5677 									  "cell-background-gdk", BGCOLOR_COLUMN,
5678 									  "visible", EMBLEM_VISIBLE_COLUMN, NULL);
5679 
5680 		} else if (protocol_icon == i) {
5681 			/* protocol icon */
5682 			rend = gtk_cell_renderer_pixbuf_new();
5683 			gtk_tree_view_column_pack_start(column, rend, FALSE);
5684 			gtk_tree_view_column_set_attributes(column, rend,
5685 							   "pixbuf", PROTOCOL_ICON_COLUMN,
5686 							   "visible", PROTOCOL_ICON_VISIBLE_COLUMN,
5687 							   "cell-background-gdk", BGCOLOR_COLUMN,
5688 							  NULL);
5689 			g_object_set(rend, "xalign", 0.0, "xpad", 3, "ypad", 0, NULL);
5690 
5691 		} else if (buddy_icon == i) {
5692 			/* buddy icon */
5693 			rend = gtk_cell_renderer_pixbuf_new();
5694 			g_object_set(rend, "xalign", 1.0, "ypad", 0, NULL);
5695 			gtk_tree_view_column_pack_start(column, rend, FALSE);
5696 			gtk_tree_view_column_set_attributes(column, rend, "pixbuf", BUDDY_ICON_COLUMN,
5697 							    "cell-background-gdk", BGCOLOR_COLUMN,
5698 							    "visible", BUDDY_ICON_VISIBLE_COLUMN,
5699 							    NULL);
5700 		}
5701 
5702 	}/* end for loop */
5703 
5704 }
5705 
5706 static gboolean
pidgin_blist_search_equal_func(GtkTreeModel * model,gint column,const gchar * key,GtkTreeIter * iter,gpointer data)5707 pidgin_blist_search_equal_func(GtkTreeModel *model, gint column,
5708 			const gchar *key, GtkTreeIter *iter, gpointer data)
5709 {
5710 	PurpleBlistNode *node = NULL;
5711 	gboolean res = TRUE;
5712 	const char *compare = NULL;
5713 
5714 	if (!pidgin_tree_view_search_equal_func(model, column, key, iter, data))
5715 		return FALSE;
5716 
5717 	/* If the search string does not match the displayed label, then look
5718 	 * at the alternate labels for the nodes and search in them. Currently,
5719 	 * alternate labels that make sense are usernames/email addresses for
5720 	 * buddies (but only for the ones who don't have a local alias).
5721 	 */
5722 
5723 	gtk_tree_model_get(model, iter, NODE_COLUMN, &node, -1);
5724 	if (!node)
5725 		return TRUE;
5726 
5727 	compare = NULL;
5728 	if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
5729 		PurpleBuddy *b = purple_contact_get_priority_buddy(PURPLE_CONTACT(node));
5730 		if (!purple_buddy_get_local_buddy_alias(b))
5731 			compare = purple_buddy_get_name(b);
5732 	} else if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
5733 		if (!purple_buddy_get_local_buddy_alias(PURPLE_BUDDY(node)))
5734 			compare = purple_buddy_get_name(PURPLE_BUDDY(node));
5735 	}
5736 
5737 	if (compare) {
5738 		char *tmp, *enteredstring;
5739 		tmp = g_utf8_normalize(key, -1, G_NORMALIZE_DEFAULT);
5740 		enteredstring = g_utf8_casefold(tmp, -1);
5741 		g_free(tmp);
5742 
5743 		if (purple_str_has_prefix(compare, enteredstring))
5744 			res = FALSE;
5745 		g_free(enteredstring);
5746 	}
5747 
5748 	return res;
5749 }
5750 
pidgin_blist_show(PurpleBuddyList * list)5751 static void pidgin_blist_show(PurpleBuddyList *list)
5752 {
5753 	PidginBuddyListPrivate *priv;
5754 	void *handle;
5755 	GtkTreeViewColumn *column;
5756 	GtkWidget *menu;
5757 	GtkWidget *ebox;
5758 	GtkWidget *sep;
5759 	GtkWidget *label;
5760 	GtkWidget *close;
5761 	char *pretty, *tmp;
5762 	const char *theme_name;
5763 	GtkAccelGroup *accel_group;
5764 	GtkTreeSelection *selection;
5765 	GtkTargetEntry dte[] = {{"PURPLE_BLIST_NODE", GTK_TARGET_SAME_APP, DRAG_ROW},
5766 				{"application/x-im-contact", 0, DRAG_BUDDY},
5767 				{"text/x-vcard", 0, DRAG_VCARD },
5768 				{"text/uri-list", 0, DRAG_URI},
5769 				{"text/plain", 0, DRAG_TEXT}};
5770 	GtkTargetEntry ste[] = {{"PURPLE_BLIST_NODE", GTK_TARGET_SAME_APP, DRAG_ROW},
5771 				{"application/x-im-contact", 0, DRAG_BUDDY},
5772 				{"text/x-vcard", 0, DRAG_VCARD }};
5773 	if (gtkblist && gtkblist->window) {
5774 		purple_blist_set_visible(purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/list_visible"));
5775 		return;
5776 	}
5777 
5778 	gtkblist = PIDGIN_BLIST(list);
5779 	priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
5780 
5781 	if (priv->current_theme)
5782 		g_object_unref(priv->current_theme);
5783 
5784 	theme_name = purple_prefs_get_string(PIDGIN_PREFS_ROOT "/blist/theme");
5785 	if (theme_name && *theme_name)
5786 		priv->current_theme = g_object_ref(PIDGIN_BLIST_THEME(purple_theme_manager_find_theme(theme_name, "blist")));
5787 	else
5788 		priv->current_theme = NULL;
5789 
5790 	gtkblist->empty_avatar = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, 32, 32);
5791 	gdk_pixbuf_fill(gtkblist->empty_avatar, 0x00000000);
5792 
5793 	gtkblist->window = pidgin_create_window(_("Buddy List"), 0, "buddy_list", TRUE);
5794 	g_signal_connect(G_OBJECT(gtkblist->window), "focus-in-event",
5795 			 G_CALLBACK(blist_focus_cb), gtkblist);
5796 	g_signal_connect(G_OBJECT(gtkblist->window), "focus-out-event",
5797 			 G_CALLBACK(blist_focus_cb), gtkblist);
5798 	GTK_WINDOW(gtkblist->window)->allow_shrink = TRUE;
5799 
5800 	gtkblist->main_vbox = gtk_vbox_new(FALSE, 0);
5801 	gtk_widget_show(gtkblist->main_vbox);
5802 	gtk_container_add(GTK_CONTAINER(gtkblist->window), gtkblist->main_vbox);
5803 
5804 	g_signal_connect(G_OBJECT(gtkblist->window), "delete_event", G_CALLBACK(gtk_blist_delete_cb), NULL);
5805 	g_signal_connect(G_OBJECT(gtkblist->window), "configure_event", G_CALLBACK(gtk_blist_configure_cb), NULL);
5806 	g_signal_connect(G_OBJECT(gtkblist->window), "visibility_notify_event", G_CALLBACK(gtk_blist_visibility_cb), NULL);
5807 	g_signal_connect(G_OBJECT(gtkblist->window), "window_state_event", G_CALLBACK(gtk_blist_window_state_cb), NULL);
5808 	g_signal_connect(G_OBJECT(gtkblist->window), "key_press_event", G_CALLBACK(gtk_blist_window_key_press_cb), gtkblist);
5809 	gtk_widget_add_events(gtkblist->window, GDK_VISIBILITY_NOTIFY_MASK);
5810 
5811 	/******************************* Menu bar *************************************/
5812 	accel_group = gtk_accel_group_new();
5813 	gtk_window_add_accel_group(GTK_WINDOW (gtkblist->window), accel_group);
5814 	g_object_unref(accel_group);
5815 	gtkblist->ift = gtk_item_factory_new(GTK_TYPE_MENU_BAR, "<PurpleMain>", accel_group);
5816 	gtk_item_factory_set_translate_func(gtkblist->ift,
5817 										(GtkTranslateFunc)item_factory_translate_func,
5818 										NULL, NULL);
5819 	gtk_item_factory_create_items(gtkblist->ift, sizeof(blist_menu) / sizeof(*blist_menu),
5820 								  blist_menu, NULL);
5821 	pidgin_load_accels();
5822 	g_signal_connect(G_OBJECT(accel_group), "accel-changed", G_CALLBACK(pidgin_save_accels_cb), NULL);
5823 
5824 	menu = gtk_item_factory_get_widget(gtkblist->ift, "<PurpleMain>");
5825 	gtkblist->menutray = pidgin_menu_tray_new();
5826 	gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtkblist->menutray);
5827 	gtk_widget_show(gtkblist->menutray);
5828 	gtk_widget_show(menu);
5829 	gtk_box_pack_start(GTK_BOX(gtkblist->main_vbox), menu, FALSE, FALSE, 0);
5830 
5831 	accountmenu = gtk_item_factory_get_widget(gtkblist->ift, N_("/Accounts"));
5832 
5833 
5834 	/****************************** Notebook *************************************/
5835 	gtkblist->notebook = gtk_notebook_new();
5836 	gtk_notebook_set_show_tabs(GTK_NOTEBOOK(gtkblist->notebook), FALSE);
5837 	gtk_notebook_set_show_border(GTK_NOTEBOOK(gtkblist->notebook), FALSE);
5838 	gtk_box_pack_start(GTK_BOX(gtkblist->main_vbox), gtkblist->notebook, TRUE, TRUE, 0);
5839 
5840 #if 0
5841 	gtk_notebook_append_page(GTK_NOTEBOOK(gtkblist->notebook), kiosk_page(), NULL);
5842 #endif
5843 
5844 	/* Translators: Please maintain the use of -> and <- to refer to menu heirarchy */
5845 	tmp = g_strdup_printf(_("<span weight='bold' size='larger'>Welcome to %s!</span>\n\n"
5846 
5847 					       "You have no accounts enabled. Enable your IM accounts from the "
5848 					       "<b>Accounts</b> window at <b>Accounts->Manage Accounts</b>. Once you "
5849 					       "enable accounts, you'll be able to sign on, set your status, "
5850 					       "and talk to your friends."), PIDGIN_NAME);
5851 	pretty = pidgin_make_pretty_arrows(tmp);
5852 	g_free(tmp);
5853 	label = gtk_label_new(NULL);
5854 	gtk_widget_set_size_request(label, purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/width") - 12, -1);
5855 	gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
5856 	gtk_misc_set_alignment(GTK_MISC(label), 0.5, 0.2);
5857 	gtk_label_set_markup(GTK_LABEL(label), pretty);
5858 	g_free(pretty);
5859 	gtk_notebook_append_page(GTK_NOTEBOOK(gtkblist->notebook),label, NULL);
5860 	gtkblist->vbox = gtk_vbox_new(FALSE, 0);
5861 	gtk_notebook_append_page(GTK_NOTEBOOK(gtkblist->notebook), gtkblist->vbox, NULL);
5862 	gtk_widget_show_all(gtkblist->notebook);
5863 	pidgin_blist_select_notebook_page(gtkblist);
5864 
5865 	ebox = gtk_event_box_new();
5866 	gtk_box_pack_start(GTK_BOX(gtkblist->vbox), ebox, FALSE, FALSE, 0);
5867 	gtkblist->headline_hbox = gtk_hbox_new(FALSE, 3);
5868 	gtk_container_set_border_width(GTK_CONTAINER(gtkblist->headline_hbox), 6);
5869 	gtk_container_add(GTK_CONTAINER(ebox), gtkblist->headline_hbox);
5870 	gtkblist->headline_image = gtk_image_new_from_pixbuf(NULL);
5871 	gtk_misc_set_alignment(GTK_MISC(gtkblist->headline_image), 0.0, 0);
5872 	gtkblist->headline_label = gtk_label_new(NULL);
5873 	gtk_widget_set_size_request(gtkblist->headline_label,
5874 				    purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/width")-25,-1);
5875 	gtk_label_set_line_wrap(GTK_LABEL(gtkblist->headline_label), TRUE);
5876 	gtk_box_pack_start(GTK_BOX(gtkblist->headline_hbox), gtkblist->headline_image, FALSE, FALSE, 0);
5877 	gtk_box_pack_start(GTK_BOX(gtkblist->headline_hbox), gtkblist->headline_label, TRUE, TRUE, 0);
5878 	g_signal_connect(gtkblist->headline_label,   /* connecting on headline_hbox doesn't work, because
5879 	                                                the signal is not emitted when theme is changed */
5880 			"style-set",
5881 			 G_CALLBACK(headline_style_set),
5882 			 NULL);
5883 	g_signal_connect (gtkblist->headline_hbox,
5884 			  "expose_event",
5885 			  G_CALLBACK (paint_headline_hbox),
5886 			  NULL);
5887 	gtk_widget_set_name(gtkblist->headline_hbox, "gtk-tooltips");
5888 
5889 	gtkblist->headline_close = gtk_widget_render_icon(ebox, GTK_STOCK_CLOSE,
5890 		gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC), NULL);
5891 	gtkblist->hand_cursor = gdk_cursor_new (GDK_HAND2);
5892 	gtkblist->arrow_cursor = gdk_cursor_new (GDK_LEFT_PTR);
5893 
5894 	/* Close button. */
5895 	close = gtk_image_new_from_stock(GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU);
5896 	close = pidgin_create_small_button(close);
5897 	gtk_box_pack_start(GTK_BOX(gtkblist->headline_hbox), close, FALSE, FALSE, 0);
5898 #if GTK_CHECK_VERSION(2,12,0)
5899 	gtk_widget_set_tooltip_text(close, _("Close"));
5900 #endif
5901 	g_signal_connect(close, "clicked", G_CALLBACK(headline_close_press_cb), gtkblist);
5902 
5903 	g_signal_connect(G_OBJECT(ebox), "enter-notify-event", G_CALLBACK(headline_box_enter_cb), gtkblist);
5904 	g_signal_connect(G_OBJECT(ebox), "leave-notify-event", G_CALLBACK(headline_box_leave_cb), gtkblist);
5905 	g_signal_connect(G_OBJECT(ebox), "button-press-event", G_CALLBACK(headline_box_press_cb), gtkblist);
5906 
5907 	/****************************** GtkTreeView **********************************/
5908 	gtkblist->treemodel = gtk_tree_store_new(BLIST_COLUMNS,
5909 						 GDK_TYPE_PIXBUF, /* Status icon */
5910 						 G_TYPE_BOOLEAN,  /* Status icon visible */
5911 						 G_TYPE_STRING,   /* Name */
5912 						 G_TYPE_STRING,   /* Idle */
5913 						 G_TYPE_BOOLEAN,  /* Idle visible */
5914 						 GDK_TYPE_PIXBUF, /* Buddy icon */
5915 						 G_TYPE_BOOLEAN,  /* Buddy icon visible */
5916 						 G_TYPE_POINTER,  /* Node */
5917 						 GDK_TYPE_COLOR,  /* bgcolor */
5918 						 G_TYPE_BOOLEAN,  /* Group expander */
5919 						 G_TYPE_BOOLEAN,  /* Group expander visible */
5920 						 G_TYPE_BOOLEAN,  /* Contact expander */
5921 						 G_TYPE_BOOLEAN,  /* Contact expander visible */
5922 						 GDK_TYPE_PIXBUF, /* Emblem */
5923 						 G_TYPE_BOOLEAN,  /* Emblem visible */
5924 						 GDK_TYPE_PIXBUF, /* Protocol icon */
5925 						 G_TYPE_BOOLEAN   /* Protocol visible */
5926 						);
5927 
5928 	gtkblist->treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(gtkblist->treemodel));
5929 
5930 	gtk_widget_show(gtkblist->treeview);
5931 	gtk_widget_set_name(gtkblist->treeview, "pidgin_blist_treeview");
5932 
5933 	g_signal_connect(gtkblist->treeview,
5934 			 "style-set",
5935 			 G_CALLBACK(treeview_style_set), list);
5936 	/* Set up selection stuff */
5937 	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist->treeview));
5938 	g_signal_connect(G_OBJECT(selection), "changed", G_CALLBACK(pidgin_blist_selection_changed), NULL);
5939 
5940 	/* Set up dnd */
5941 	gtk_tree_view_enable_model_drag_source(GTK_TREE_VIEW(gtkblist->treeview),
5942 										   GDK_BUTTON1_MASK, ste, 3,
5943 										   GDK_ACTION_COPY);
5944 	gtk_tree_view_enable_model_drag_dest(GTK_TREE_VIEW(gtkblist->treeview),
5945 										 dte, 5,
5946 										 GDK_ACTION_COPY | GDK_ACTION_MOVE);
5947 
5948 	g_signal_connect(G_OBJECT(gtkblist->treeview), "drag-data-received", G_CALLBACK(pidgin_blist_drag_data_rcv_cb), NULL);
5949 	g_signal_connect(G_OBJECT(gtkblist->treeview), "drag-data-get", G_CALLBACK(pidgin_blist_drag_data_get_cb), NULL);
5950 #ifdef _WIN32
5951 	g_signal_connect(G_OBJECT(gtkblist->treeview), "drag-begin", G_CALLBACK(pidgin_blist_drag_begin), NULL);
5952 #endif
5953 	g_signal_connect(G_OBJECT(gtkblist->treeview), "drag-motion", G_CALLBACK(pidgin_blist_drag_motion_cb), NULL);
5954 	g_signal_connect(G_OBJECT(gtkblist->treeview), "motion-notify-event", G_CALLBACK(pidgin_blist_motion_cb), NULL);
5955 	g_signal_connect(G_OBJECT(gtkblist->treeview), "leave-notify-event", G_CALLBACK(pidgin_blist_leave_cb), NULL);
5956 
5957 	/* Tooltips */
5958 	pidgin_tooltip_setup_for_treeview(gtkblist->treeview, NULL,
5959 			pidgin_blist_create_tooltip,
5960 			pidgin_blist_paint_tip);
5961 
5962 	gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(gtkblist->treeview), FALSE);
5963 
5964 	/* expander columns */
5965 	column = gtk_tree_view_column_new();
5966 	gtk_tree_view_append_column(GTK_TREE_VIEW(gtkblist->treeview), column);
5967 	gtk_tree_view_column_set_visible(column, FALSE);
5968 	gtk_tree_view_set_expander_column(GTK_TREE_VIEW(gtkblist->treeview), column);
5969 
5970 	/* everything else column */
5971 	gtkblist->text_column = gtk_tree_view_column_new ();
5972 	gtk_tree_view_append_column(GTK_TREE_VIEW(gtkblist->treeview), gtkblist->text_column);
5973 	pidgin_blist_build_layout(list);
5974 
5975 	g_signal_connect(G_OBJECT(gtkblist->treeview), "row-activated", G_CALLBACK(gtk_blist_row_activated_cb), NULL);
5976 	g_signal_connect(G_OBJECT(gtkblist->treeview), "row-expanded", G_CALLBACK(gtk_blist_row_expanded_cb), NULL);
5977 	g_signal_connect(G_OBJECT(gtkblist->treeview), "row-collapsed", G_CALLBACK(gtk_blist_row_collapsed_cb), NULL);
5978 	g_signal_connect(G_OBJECT(gtkblist->treeview), "button-press-event", G_CALLBACK(gtk_blist_button_press_cb), NULL);
5979 	g_signal_connect(G_OBJECT(gtkblist->treeview), "key-press-event", G_CALLBACK(gtk_blist_key_press_cb), NULL);
5980 	g_signal_connect(G_OBJECT(gtkblist->treeview), "popup-menu", G_CALLBACK(pidgin_blist_popup_menu_cb), NULL);
5981 
5982 	/* Enable CTRL+F searching */
5983 	gtk_tree_view_set_search_column(GTK_TREE_VIEW(gtkblist->treeview), NAME_COLUMN);
5984 	gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(gtkblist->treeview),
5985 			pidgin_blist_search_equal_func, NULL, NULL);
5986 
5987 	gtk_box_pack_start(GTK_BOX(gtkblist->vbox),
5988 		pidgin_make_scrollable(gtkblist->treeview, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC, GTK_SHADOW_NONE, -1, -1),
5989 		TRUE, TRUE, 0);
5990 
5991 	sep = gtk_hseparator_new();
5992 	gtk_box_pack_start(GTK_BOX(gtkblist->vbox), sep, FALSE, FALSE, 0);
5993 
5994 	gtkblist->scrollbook = pidgin_scroll_book_new();
5995 	gtk_box_pack_start(GTK_BOX(gtkblist->vbox), gtkblist->scrollbook, FALSE, FALSE, 0);
5996 
5997 	/* Create an vbox which holds the scrollbook which is actually used to
5998 	 * display connection errors.  The vbox needs to still exist for
5999 	 * backwards compatibility.
6000 	 */
6001 	gtkblist->error_buttons = gtk_vbox_new(FALSE, 0);
6002 	gtk_box_pack_start(GTK_BOX(gtkblist->vbox), gtkblist->error_buttons, FALSE, FALSE, 0);
6003 	gtk_container_set_border_width(GTK_CONTAINER(gtkblist->error_buttons), 0);
6004 
6005 	priv->error_scrollbook = PIDGIN_SCROLL_BOOK(pidgin_scroll_book_new());
6006 	gtk_box_pack_start(GTK_BOX(gtkblist->error_buttons),
6007 		GTK_WIDGET(priv->error_scrollbook), FALSE, FALSE, 0);
6008 
6009 
6010 	/* Add the statusbox */
6011 	gtkblist->statusbox = pidgin_status_box_new();
6012 	gtk_box_pack_start(GTK_BOX(gtkblist->vbox), gtkblist->statusbox, FALSE, TRUE, 0);
6013 	gtk_widget_set_name(gtkblist->statusbox, "pidgin_blist_statusbox");
6014 	gtk_container_set_border_width(GTK_CONTAINER(gtkblist->statusbox), 3);
6015 	gtk_widget_show(gtkblist->statusbox);
6016 
6017 	/* set the Show Offline Buddies option. must be done
6018 	 * after the treeview or faceprint gets mad. -Robot101
6019 	 */
6020 	gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show/Offline Buddies"))),
6021 			purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_offline_buddies"));
6022 
6023 	gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show/Empty Groups"))),
6024 			purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_empty_groups"));
6025 
6026 	gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Tools/Mute Sounds"))),
6027 			purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/sound/mute"));
6028 
6029 	gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show/Buddy Details"))),
6030 			purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons"));
6031 
6032 	gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show/Idle Times"))),
6033 			purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_idle_time"));
6034 
6035 	gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show/Protocol Icons"))),
6036 			purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons"));
6037 
6038 	if(purple_strequal(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/sound/method"), "none"))
6039 		gtk_widget_set_sensitive(gtk_item_factory_get_widget(gtkblist->ift, N_("/Tools/Mute Sounds")), FALSE);
6040 
6041 	/* Update some dynamic things */
6042 	update_menu_bar(gtkblist);
6043 	pidgin_blist_update_plugin_actions();
6044 	pidgin_blist_update_sort_methods();
6045 
6046 	/* OK... let's show this bad boy. */
6047 	pidgin_blist_refresh(list);
6048 	pidgin_blist_restore_position();
6049 	gtk_widget_show_all(GTK_WIDGET(gtkblist->vbox));
6050 	gtk_widget_realize(GTK_WIDGET(gtkblist->window));
6051 	purple_blist_set_visible(purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/list_visible"));
6052 
6053 	/* start the refresh timer */
6054 	gtkblist->refresh_timer = purple_timeout_add_seconds(30, (GSourceFunc)pidgin_blist_refresh_timer, list);
6055 
6056 	handle = pidgin_blist_get_handle();
6057 
6058 	/* things that affect how buddies are displayed */
6059 	purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/show_buddy_icons",
6060 			_prefs_change_redo_list, NULL);
6061 	purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/show_idle_time",
6062 			_prefs_change_redo_list, NULL);
6063 	purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/show_empty_groups",
6064 			_prefs_change_redo_list, NULL);
6065 	purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/show_offline_buddies",
6066 			_prefs_change_redo_list, NULL);
6067 	purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/show_protocol_icons",
6068 			_prefs_change_redo_list, NULL);
6069 
6070 	/* sorting */
6071 	purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/sort_type",
6072 			_prefs_change_sort_method, NULL);
6073 
6074 	/* menus */
6075 	purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/sound/mute",
6076 			pidgin_blist_mute_pref_cb, NULL);
6077 	purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/sound/method",
6078 			pidgin_blist_sound_method_pref_cb, NULL);
6079 
6080 	/* Setup some purple signal handlers. */
6081 
6082 	handle = purple_accounts_get_handle();
6083 	purple_signal_connect(handle, "account-enabled", gtkblist,
6084 	                      PURPLE_CALLBACK(account_modified), gtkblist);
6085 	purple_signal_connect(handle, "account-disabled", gtkblist,
6086 	                      PURPLE_CALLBACK(account_modified), gtkblist);
6087 	purple_signal_connect(handle, "account-removed", gtkblist,
6088 	                      PURPLE_CALLBACK(account_modified), gtkblist);
6089 	purple_signal_connect(handle, "account-status-changed", gtkblist,
6090 	                      PURPLE_CALLBACK(account_status_changed),
6091 	                      gtkblist);
6092 	purple_signal_connect(handle, "account-error-changed", gtkblist,
6093 	                      PURPLE_CALLBACK(update_account_error_state),
6094 	                      gtkblist);
6095 	purple_signal_connect(handle, "account-actions-changed", gtkblist,
6096 	                      PURPLE_CALLBACK(account_actions_changed), NULL);
6097 
6098 	handle = pidgin_account_get_handle();
6099 	purple_signal_connect(handle, "account-modified", gtkblist,
6100 	                      PURPLE_CALLBACK(account_modified), gtkblist);
6101 
6102 	handle = purple_connections_get_handle();
6103 	purple_signal_connect(handle, "signed-on", gtkblist,
6104 	                      PURPLE_CALLBACK(sign_on_off_cb), list);
6105 	purple_signal_connect(handle, "signed-off", gtkblist,
6106 	                      PURPLE_CALLBACK(sign_on_off_cb), list);
6107 
6108 	handle = purple_plugins_get_handle();
6109 	purple_signal_connect(handle, "plugin-load", gtkblist,
6110 	                      PURPLE_CALLBACK(plugin_changed_cb), NULL);
6111 	purple_signal_connect(handle, "plugin-unload", gtkblist,
6112 	                      PURPLE_CALLBACK(plugin_changed_cb), NULL);
6113 
6114 	handle = purple_conversations_get_handle();
6115 	purple_signal_connect(handle, "conversation-updated", gtkblist,
6116 	                      PURPLE_CALLBACK(conversation_updated_cb),
6117 	                      gtkblist);
6118 	purple_signal_connect(handle, "deleting-conversation", gtkblist,
6119 	                      PURPLE_CALLBACK(conversation_deleting_cb),
6120 	                      gtkblist);
6121 	purple_signal_connect(handle, "conversation-created", gtkblist,
6122 	                      PURPLE_CALLBACK(conversation_created_cb),
6123 	                      gtkblist);
6124 	purple_signal_connect(handle, "chat-joined", gtkblist,
6125 	                      PURPLE_CALLBACK(chat_joined_cb),
6126 	                      gtkblist);
6127 
6128 	gtk_widget_hide(gtkblist->headline_hbox);
6129 
6130 	show_initial_account_errors(gtkblist);
6131 
6132 	/* emit our created signal */
6133 	handle = pidgin_blist_get_handle();
6134 	purple_signal_emit(handle, "gtkblist-created", list);
6135 }
6136 
redo_buddy_list(PurpleBuddyList * list,gboolean remove,gboolean rerender)6137 static void redo_buddy_list(PurpleBuddyList *list, gboolean remove, gboolean rerender)
6138 {
6139 	PurpleBlistNode *node;
6140 
6141 	gtkblist = PIDGIN_BLIST(list);
6142 	if(!gtkblist || !gtkblist->treeview)
6143 		return;
6144 
6145 	node = list->root;
6146 
6147 	while (node)
6148 	{
6149 		/* This is only needed when we're reverting to a non-GTK+ sorted
6150 		 * status.  We shouldn't need to remove otherwise.
6151 		 */
6152 		if (remove && !PURPLE_BLIST_NODE_IS_GROUP(node))
6153 			pidgin_blist_hide_node(list, node, FALSE);
6154 
6155 		if (PURPLE_BLIST_NODE_IS_BUDDY(node))
6156 			pidgin_blist_update_buddy(list, node, rerender);
6157 		else if (PURPLE_BLIST_NODE_IS_CHAT(node))
6158 			pidgin_blist_update(list, node);
6159 		else if (PURPLE_BLIST_NODE_IS_GROUP(node))
6160 			pidgin_blist_update(list, node);
6161 		node = purple_blist_node_next(node, FALSE);
6162 	}
6163 
6164 }
6165 
pidgin_blist_refresh(PurpleBuddyList * list)6166 void pidgin_blist_refresh(PurpleBuddyList *list)
6167 {
6168 	redo_buddy_list(list, FALSE, TRUE);
6169 }
6170 
6171 void
pidgin_blist_update_refresh_timeout()6172 pidgin_blist_update_refresh_timeout()
6173 {
6174 	PurpleBuddyList *blist;
6175 	PidginBuddyList *gtkblist;
6176 
6177 	blist = purple_get_blist();
6178 	gtkblist = PIDGIN_BLIST(purple_get_blist());
6179 
6180 	gtkblist->refresh_timer = purple_timeout_add_seconds(30,(GSourceFunc)pidgin_blist_refresh_timer, blist);
6181 }
6182 
get_iter_from_node(PurpleBlistNode * node,GtkTreeIter * iter)6183 static gboolean get_iter_from_node(PurpleBlistNode *node, GtkTreeIter *iter) {
6184 	struct _pidgin_blist_node *gtknode = (struct _pidgin_blist_node *)node->ui_data;
6185 	GtkTreePath *path;
6186 
6187 	if (!gtknode) {
6188 		return FALSE;
6189 	}
6190 
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;
6194 	}
6195 
6196 	if (!gtknode->row)
6197 		return FALSE;
6198 
6199 
6200 	if ((path = gtk_tree_row_reference_get_path(gtknode->row)) == NULL)
6201 		return FALSE;
6202 
6203 	if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), iter, path)) {
6204 		gtk_tree_path_free(path);
6205 		return FALSE;
6206 	}
6207 	gtk_tree_path_free(path);
6208 	return TRUE;
6209 }
6210 
pidgin_blist_remove(PurpleBuddyList * list,PurpleBlistNode * node)6211 static void pidgin_blist_remove(PurpleBuddyList *list, PurpleBlistNode *node)
6212 {
6213 	struct _pidgin_blist_node *gtknode = node->ui_data;
6214 
6215 	purple_request_close_with_handle(node);
6216 
6217 	pidgin_blist_hide_node(list, node, TRUE);
6218 
6219 	if(node->parent)
6220 		pidgin_blist_update(list, node->parent);
6221 
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. */
6230 
6231 	if(gtknode) {
6232 		if(gtknode->recent_signonoff_timer > 0)
6233 			purple_timeout_remove(gtknode->recent_signonoff_timer);
6234 
6235 		purple_signals_disconnect_by_handle(node->ui_data);
6236 		g_free(node->ui_data);
6237 		node->ui_data = NULL;
6238 	}
6239 }
6240 
do_selection_changed(PurpleBlistNode * new_selection)6241 static gboolean do_selection_changed(PurpleBlistNode *new_selection)
6242 {
6243 	PurpleBlistNode *old_selection = NULL;
6244 
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);
6253 	}
6254 
6255 	return FALSE;
6256 }
6257 
pidgin_blist_selection_changed(GtkTreeSelection * selection,gpointer data)6258 static void pidgin_blist_selection_changed(GtkTreeSelection *selection, gpointer data)
6259 {
6260 	PurpleBlistNode *new_selection = NULL;
6261 	GtkTreeIter iter;
6262 
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);
6266 	}
6267 
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.
6271 	 */
6272 	if ((new_selection != NULL) && PURPLE_BLIST_NODE_IS_GROUP(new_selection)) {
6273 		do_selection_changed(new_selection);
6274 	} else {
6275 		g_timeout_add(0, (GSourceFunc)do_selection_changed, new_selection);
6276 	}
6277 }
6278 
insert_node(PurpleBuddyList * list,PurpleBlistNode * node,GtkTreeIter * iter)6279 static gboolean insert_node(PurpleBuddyList *list, PurpleBlistNode *node, GtkTreeIter *iter)
6280 {
6281 	GtkTreeIter parent_iter, cur, *curptr = NULL;
6282 	struct _pidgin_blist_node *gtknode = node->ui_data;
6283 	GtkTreePath *newpath;
6284 
6285 	if(!iter)
6286 		return FALSE;
6287 
6288 	/* XXX: it's not necessary, but let's silence a warning*/
6289 	memset(&parent_iter, 0, sizeof(parent_iter));
6290 
6291 	if(node->parent && !get_iter_from_node(node->parent, &parent_iter))
6292 		return FALSE;
6293 
6294 	if(get_iter_from_node(node, &cur))
6295 		curptr = &cur;
6296 
6297 	if(PURPLE_BLIST_NODE_IS_CONTACT(node) || PURPLE_BLIST_NODE_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);
6301 	}
6302 
6303 	if(gtknode != NULL) {
6304 		gtk_tree_row_reference_free(gtknode->row);
6305 	} else {
6306 		pidgin_blist_new_node(node);
6307 		gtknode = (struct _pidgin_blist_node *)node->ui_data;
6308 	}
6309 
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);
6315 
6316 	gtk_tree_path_free(newpath);
6317 
6318 	if (!editing_blist)
6319 		gtk_tree_store_set(gtkblist->treemodel, iter,
6320 				NODE_COLUMN, node,
6321 				-1);
6322 
6323 	if(node->parent) {
6324 		GtkTreePath *expand = NULL;
6325 		struct _pidgin_blist_node *gtkparentnode = node->parent->ui_data;
6326 
6327 		if(PURPLE_BLIST_NODE_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_BLIST_NODE_IS_CONTACT(node->parent) &&
6331 				gtkparentnode->contact_expanded) {
6332 			expand = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &parent_iter);
6333 		}
6334 		if(expand) {
6335 			gtk_tree_view_expand_row(GTK_TREE_VIEW(gtkblist->treeview), expand, FALSE);
6336 			gtk_tree_path_free(expand);
6337 		}
6338 	}
6339 
6340 	return TRUE;
6341 }
6342 
pidgin_blist_group_has_show_offline_buddy(PurpleGroup * group)6343 static gboolean pidgin_blist_group_has_show_offline_buddy(PurpleGroup *group)
6344 {
6345 	PurpleBlistNode *gnode, *cnode, *bnode;
6346 
6347 	gnode = (PurpleBlistNode *)group;
6348 	for(cnode = gnode->child; cnode; cnode = cnode->next) {
6349 		if(PURPLE_BLIST_NODE_IS_CONTACT(cnode)) {
6350 			for(bnode = cnode->child; bnode; bnode = bnode->next) {
6351 				PurpleBuddy *buddy = (PurpleBuddy *)bnode;
6352 				if (purple_account_is_connected(buddy->account) &&
6353 					purple_blist_node_get_bool(bnode, "show_offline"))
6354 					return TRUE;
6355 			}
6356 		}
6357 	}
6358 	return FALSE;
6359 }
6360 
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.
6363  */
pidgin_blist_update_group(PurpleBuddyList * list,PurpleBlistNode * node)6364 static void pidgin_blist_update_group(PurpleBuddyList *list,
6365                                       PurpleBlistNode *node)
6366 {
6367 	gint count;
6368 	PurpleGroup *group;
6369 	PurpleBlistNode* gnode;
6370 	gboolean show = FALSE, show_offline = FALSE;
6371 
6372 	g_return_if_fail(node != NULL);
6373 
6374 	if (editing_blist)
6375 		return;
6376 
6377 	if (PURPLE_BLIST_NODE_IS_GROUP(node))
6378 		gnode = node;
6379 	else if (PURPLE_BLIST_NODE_IS_BUDDY(node))
6380 		gnode = node->parent->parent;
6381 	else if (PURPLE_BLIST_NODE_IS_CONTACT(node) || PURPLE_BLIST_NODE_IS_CHAT(node))
6382 		gnode = node->parent;
6383 	else
6384 		return;
6385 
6386 	group = (PurpleGroup*)gnode;
6387 
6388 	show_offline = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_offline_buddies");
6389 
6390 	if(show_offline)
6391 		count = purple_blist_get_group_size(group, FALSE);
6392 	else
6393 		count = purple_blist_get_group_online_count(group);
6394 
6395 	if (!PURPLE_BLIST_NODE_IS_VISIBLE(gnode) || !PURPLE_BLIST_NODE_IS_VISIBLE(node))
6396 		show = FALSE;
6397 	else if (count > 0 || purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_empty_groups"))
6398 		show = TRUE;
6399 	else if (PURPLE_BLIST_NODE_IS_BUDDY(node) && buddy_is_displayable((PurpleBuddy*)node)) { /* Or chat? */
6400 		show = TRUE;
6401 	} else if (!show_offline) {
6402 		show = pidgin_blist_group_has_show_offline_buddy(group);
6403 	}
6404 	if (show) {
6405 		gchar *title;
6406 		gboolean biglist;
6407 		GtkTreeIter iter;
6408 		GtkTreePath *path;
6409 		gboolean expanded;
6410 		GdkColor *bgcolor = NULL;
6411 		GdkPixbuf *avatar = NULL;
6412 		PidginBlistTheme *theme = NULL;
6413 
6414 		if(!insert_node(list, gnode, &iter))
6415 			return;
6416 
6417 		if ((theme = pidgin_blist_get_theme()) == NULL)
6418 			bgcolor = NULL;
6419 		else if (purple_blist_node_get_bool(gnode, "collapsed") || count <= 0)
6420 			bgcolor = pidgin_blist_theme_get_collapsed_background_color(theme);
6421 		else
6422 			bgcolor = pidgin_blist_theme_get_expanded_background_color(theme);
6423 
6424 		path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &iter);
6425 		expanded = gtk_tree_view_row_expanded(GTK_TREE_VIEW(gtkblist->treeview), path);
6426 		gtk_tree_path_free(path);
6427 
6428 		title = pidgin_get_group_title(gnode, expanded);
6429 		biglist = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons");
6430 
6431 		if (biglist) {
6432 			avatar = pidgin_blist_get_buddy_icon(gnode, TRUE, TRUE);
6433 		}
6434 
6435 		gtk_tree_store_set(gtkblist->treemodel, &iter,
6436 				   STATUS_ICON_VISIBLE_COLUMN, FALSE,
6437 				   STATUS_ICON_COLUMN, NULL,
6438 				   NAME_COLUMN, title,
6439 				   NODE_COLUMN, gnode,
6440 				   BGCOLOR_COLUMN, bgcolor,
6441 				   GROUP_EXPANDER_COLUMN, TRUE,
6442 				   GROUP_EXPANDER_VISIBLE_COLUMN, TRUE,
6443 				   CONTACT_EXPANDER_VISIBLE_COLUMN, FALSE,
6444 				   BUDDY_ICON_COLUMN, avatar,
6445 				   BUDDY_ICON_VISIBLE_COLUMN, biglist,
6446 				   IDLE_VISIBLE_COLUMN, FALSE,
6447 				   EMBLEM_VISIBLE_COLUMN, FALSE,
6448 				   -1);
6449 		g_free(title);
6450 	} else {
6451 		pidgin_blist_hide_node(list, gnode, TRUE);
6452 	}
6453 }
6454 
pidgin_get_group_title(PurpleBlistNode * gnode,gboolean expanded)6455 static char *pidgin_get_group_title(PurpleBlistNode *gnode, gboolean expanded)
6456 {
6457 	PurpleGroup *group;
6458 	gboolean selected;
6459 	char group_count[12] = "";
6460 	char *mark, *esc = NULL;
6461 	PurpleBlistNode *selected_node = NULL;
6462 	GtkTreeIter iter;
6463 	PidginThemeFont *pair;
6464 	gchar const *text_color, *text_font;
6465 	PidginBlistTheme *theme;
6466 
6467 	group = (PurpleGroup*)gnode;
6468 
6469 	if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist->treeview)), NULL, &iter)) {
6470 		gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter,
6471 				NODE_COLUMN, &selected_node, -1);
6472 	}
6473 	selected = (gnode == selected_node);
6474 
6475 	if (!expanded) {
6476 		g_snprintf(group_count, sizeof(group_count), "%d/%d",
6477 		           purple_blist_get_group_online_count(group),
6478 		           purple_blist_get_group_size(group, FALSE));
6479 	}
6480 
6481 	theme = pidgin_blist_get_theme();
6482 	if (theme == NULL)
6483 		pair = NULL;
6484 	else if (expanded)
6485 		pair = pidgin_blist_theme_get_expanded_text_info(theme);
6486 	else
6487 		pair = pidgin_blist_theme_get_collapsed_text_info(theme);
6488 
6489 
6490 	text_color = selected ? NULL : theme_font_get_color_default(pair, NULL);
6491 	text_font = theme_font_get_face_default(pair, "");
6492 
6493 	if(group != NULL) {
6494 		esc = g_markup_escape_text(group->name, -1);
6495 	}
6496 
6497 	if (text_color) {
6498 		mark = g_strdup_printf("<span foreground='%s' font_desc='%s'><b>%s</b>%s%s%s</span>",
6499 		                       text_color, text_font,
6500 		                       esc ? esc : "",
6501 		                       !expanded ? " <span weight='light'>(</span>" : "",
6502 		                       group_count,
6503 		                       !expanded ? "<span weight='light'>)</span>" : "");
6504 	} else {
6505 		mark = g_strdup_printf("<span font_desc='%s'><b>%s</b>%s%s%s</span>",
6506 		                       text_font, esc ? esc : "",
6507 		                       !expanded ? " <span weight='light'>(</span>" : "",
6508 		                       group_count,
6509 		                       !expanded ? "<span weight='light'>)</span>" : "");
6510 	}
6511 
6512 	g_free(esc);
6513 	return mark;
6514 }
6515 
buddy_node(PurpleBuddy * buddy,GtkTreeIter * iter,PurpleBlistNode * node)6516 static void buddy_node(PurpleBuddy *buddy, GtkTreeIter *iter, PurpleBlistNode *node)
6517 {
6518 	PurplePresence *presence = purple_buddy_get_presence(buddy);
6519 	GdkPixbuf *status, *avatar, *emblem, *prpl_icon;
6520 	GdkColor *color = NULL;
6521 	char *mark;
6522 	char *idle = NULL;
6523 	gboolean expanded = ((struct _pidgin_blist_node *)(node->parent->ui_data))->contact_expanded;
6524 	gboolean selected = (gtkblist->selected_node == node);
6525 	gboolean biglist = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons");
6526 	PidginBlistTheme *theme;
6527 
6528 	if (editing_blist)
6529 		return;
6530 
6531 	status = pidgin_blist_get_status_icon((PurpleBlistNode*)buddy,
6532 						biglist ? PIDGIN_STATUS_ICON_LARGE : PIDGIN_STATUS_ICON_SMALL);
6533 
6534 	/* Speed it up if we don't want buddy icons. */
6535 	if(biglist)
6536 		avatar = pidgin_blist_get_buddy_icon((PurpleBlistNode *)buddy, TRUE, TRUE);
6537 	else
6538 		avatar = NULL;
6539 
6540 	if (!avatar) {
6541 		g_object_ref(G_OBJECT(gtkblist->empty_avatar));
6542 		avatar = gtkblist->empty_avatar;
6543 	} else if ((!PURPLE_BUDDY_IS_ONLINE(buddy) || purple_presence_is_idle(presence))) {
6544 		do_alphashift(avatar, 77);
6545 	}
6546 
6547 	emblem = pidgin_blist_get_emblem((PurpleBlistNode*) buddy);
6548 	mark = pidgin_blist_get_name_markup(buddy, selected, TRUE);
6549 
6550 	theme = pidgin_blist_get_theme();
6551 
6552 	if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_idle_time") &&
6553 		purple_presence_is_idle(presence) && !biglist)
6554 	{
6555 		time_t idle_secs = purple_presence_get_idle_time(presence);
6556 
6557 		if (idle_secs > 0)
6558 		{
6559 			PidginThemeFont *pair = NULL;
6560 			const gchar *textcolor;
6561 			time_t t;
6562 			int ihrs, imin;
6563 			time(&t);
6564 
6565 			ihrs = (t - idle_secs) / 3600;
6566 			imin = ((t - idle_secs) / 60) % 60;
6567 
6568 			if (selected)
6569 				textcolor = NULL;
6570 			else if (theme != NULL && (pair = pidgin_blist_theme_get_idle_text_info(theme)) != NULL)
6571 				textcolor = pidgin_theme_font_get_color_describe(pair);
6572 			else
6573 				/* If no theme them default to making idle buddy names grey */
6574 				textcolor = pidgin_style_is_dark(NULL) ? "light slate grey" : "dim grey";
6575 
6576 			if (textcolor) {
6577 				idle = g_strdup_printf("<span color='%s' font_desc='%s'>%d:%02d</span>",
6578 					textcolor, theme_font_get_face_default(pair, ""),
6579 					ihrs, imin);
6580 			} else {
6581 				idle = g_strdup_printf("<span font_desc='%s'>%d:%02d</span>",
6582 					theme_font_get_face_default(pair, ""),
6583 					ihrs, imin);
6584 			}
6585 		}
6586 	}
6587 
6588 	prpl_icon = pidgin_create_prpl_icon(buddy->account, PIDGIN_PRPL_ICON_SMALL);
6589 
6590 	if (theme != NULL)
6591 		color = pidgin_blist_theme_get_contact_color(theme);
6592 
6593 	gtk_tree_store_set(gtkblist->treemodel, iter,
6594 			   STATUS_ICON_COLUMN, status,
6595 			   STATUS_ICON_VISIBLE_COLUMN, TRUE,
6596 			   NAME_COLUMN, mark,
6597 			   IDLE_COLUMN, idle,
6598 			   IDLE_VISIBLE_COLUMN, !biglist && idle,
6599 			   BUDDY_ICON_COLUMN, avatar,
6600 			   BUDDY_ICON_VISIBLE_COLUMN, biglist,
6601 			   EMBLEM_COLUMN, emblem,
6602 			   EMBLEM_VISIBLE_COLUMN, (emblem != NULL),
6603 			   PROTOCOL_ICON_COLUMN, prpl_icon,
6604 			   PROTOCOL_ICON_VISIBLE_COLUMN, purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons"),
6605 			   BGCOLOR_COLUMN, color,
6606 			   CONTACT_EXPANDER_COLUMN, NULL,
6607 			   CONTACT_EXPANDER_VISIBLE_COLUMN, expanded,
6608 			   GROUP_EXPANDER_VISIBLE_COLUMN, FALSE,
6609 			-1);
6610 
6611 	g_free(mark);
6612 	g_free(idle);
6613 	if(emblem)
6614 		g_object_unref(emblem);
6615 	if(status)
6616 		g_object_unref(status);
6617 	if(avatar)
6618 		g_object_unref(avatar);
6619 	if(prpl_icon)
6620 		g_object_unref(prpl_icon);
6621 }
6622 
6623 /* This is a variation on the original gtk_blist_update_contact. Here we
6624 	can know in advance which buddy has changed so we can just update that */
pidgin_blist_update_contact(PurpleBuddyList * list,PurpleBlistNode * node)6625 static void pidgin_blist_update_contact(PurpleBuddyList *list, PurpleBlistNode *node)
6626 {
6627 	PurpleBlistNode *cnode;
6628 	PurpleContact *contact;
6629 	PurpleBuddy *buddy;
6630 	gboolean biglist = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons");
6631 	struct _pidgin_blist_node *gtknode;
6632 
6633 	if (editing_blist)
6634 		return;
6635 
6636 	if (PURPLE_BLIST_NODE_IS_BUDDY(node))
6637 		cnode = node->parent;
6638 	else
6639 		cnode = node;
6640 
6641 	g_return_if_fail(PURPLE_BLIST_NODE_IS_CONTACT(cnode));
6642 
6643 	/* First things first, update the group */
6644 	if (PURPLE_BLIST_NODE_IS_BUDDY(node))
6645 		pidgin_blist_update_group(list, node);
6646 	else
6647 		pidgin_blist_update_group(list, cnode->parent);
6648 
6649 	contact = (PurpleContact*)cnode;
6650 	buddy = purple_contact_get_priority_buddy(contact);
6651 
6652 	if (buddy_is_displayable(buddy))
6653 	{
6654 		GtkTreeIter iter;
6655 
6656 		if(!insert_node(list, cnode, &iter))
6657 			return;
6658 
6659 		gtknode = (struct _pidgin_blist_node *)cnode->ui_data;
6660 
6661 		if(gtknode->contact_expanded) {
6662 			GdkPixbuf *status;
6663 			gchar *mark, *tmp;
6664 			const gchar *fg_color, *font;
6665 			GdkColor *color = NULL;
6666 			PidginBlistTheme *theme;
6667 			PidginThemeFont *pair;
6668 			gboolean selected = (gtkblist->selected_node == cnode);
6669 
6670 			mark = g_markup_escape_text(purple_contact_get_alias(contact), -1);
6671 
6672 			theme = pidgin_blist_get_theme();
6673 			if (theme == NULL)
6674 				pair = NULL;
6675 			else {
6676 				pair = pidgin_blist_theme_get_contact_text_info(theme);
6677 				color = pidgin_blist_theme_get_contact_color(theme);
6678 			}
6679 
6680 			font = theme_font_get_face_default(pair, "");
6681 			fg_color = selected ? NULL : theme_font_get_color_default(pair, NULL);
6682 
6683 			if (fg_color) {
6684 				tmp = g_strdup_printf("<span font_desc='%s' color='%s'>%s</span>",
6685 						font, fg_color, mark);
6686 			} else {
6687 				tmp = g_strdup_printf("<span font_desc='%s'>%s</span>", font,
6688 					mark);
6689 			}
6690 			g_free(mark);
6691 			mark = tmp;
6692 
6693 			status = pidgin_blist_get_status_icon(cnode,
6694 					 biglist? PIDGIN_STATUS_ICON_LARGE : PIDGIN_STATUS_ICON_SMALL);
6695 
6696 			gtk_tree_store_set(gtkblist->treemodel, &iter,
6697 					   STATUS_ICON_COLUMN, status,
6698 					   STATUS_ICON_VISIBLE_COLUMN, TRUE,
6699 					   NAME_COLUMN, mark,
6700 					   IDLE_COLUMN, NULL,
6701 					   IDLE_VISIBLE_COLUMN, FALSE,
6702 					   BGCOLOR_COLUMN, color,
6703 					   BUDDY_ICON_COLUMN, NULL,
6704 					   CONTACT_EXPANDER_COLUMN, TRUE,
6705 					   CONTACT_EXPANDER_VISIBLE_COLUMN, TRUE,
6706 				  	   GROUP_EXPANDER_VISIBLE_COLUMN, FALSE,
6707 					-1);
6708 			g_free(mark);
6709 			if(status)
6710 				g_object_unref(status);
6711 		} else {
6712 			buddy_node(buddy, &iter, cnode);
6713 		}
6714 	} else {
6715 		pidgin_blist_hide_node(list, cnode, TRUE);
6716 	}
6717 }
6718 
6719 
6720 
pidgin_blist_update_buddy(PurpleBuddyList * list,PurpleBlistNode * node,gboolean status_change)6721 static void pidgin_blist_update_buddy(PurpleBuddyList *list, PurpleBlistNode *node, gboolean status_change)
6722 {
6723 	PurpleBuddy *buddy;
6724 	struct _pidgin_blist_node *gtkparentnode;
6725 
6726 	g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
6727 
6728 	if (node->parent == NULL)
6729 		return;
6730 
6731 	buddy = (PurpleBuddy*)node;
6732 
6733 	/* First things first, update the contact */
6734 	pidgin_blist_update_contact(list, node);
6735 
6736 	gtkparentnode = (struct _pidgin_blist_node *)node->parent->ui_data;
6737 
6738 	if (gtkparentnode->contact_expanded && buddy_is_displayable(buddy))
6739 	{
6740 		GtkTreeIter iter;
6741 
6742 		if (!insert_node(list, node, &iter))
6743 			return;
6744 
6745 		buddy_node(buddy, &iter, node);
6746 
6747 	} else {
6748 		pidgin_blist_hide_node(list, node, TRUE);
6749 	}
6750 
6751 }
6752 
pidgin_blist_update_chat(PurpleBuddyList * list,PurpleBlistNode * node)6753 static void pidgin_blist_update_chat(PurpleBuddyList *list, PurpleBlistNode *node)
6754 {
6755 	PurpleChat *chat;
6756 
6757 	g_return_if_fail(PURPLE_BLIST_NODE_IS_CHAT(node));
6758 
6759 	if (editing_blist)
6760 		return;
6761 
6762 	/* First things first, update the group */
6763 	pidgin_blist_update_group(list, node->parent);
6764 
6765 	chat = (PurpleChat*)node;
6766 
6767 	if(purple_account_is_connected(chat->account)) {
6768 		GtkTreeIter iter;
6769 		GdkPixbuf *status, *avatar, *emblem, *prpl_icon;
6770 		const gchar *color, *font;
6771 		gchar *mark, *tmp;
6772 		gboolean showicons = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons");
6773 		gboolean biglist = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons");
6774 		PidginBlistNode *ui;
6775 		PurpleConversation *conv;
6776 		gboolean hidden = FALSE;
6777 		GdkColor *bgcolor = NULL;
6778 		PidginThemeFont *pair;
6779 		PidginBlistTheme *theme;
6780 		gboolean selected = (gtkblist->selected_node == node);
6781 		gboolean nick_said = FALSE;
6782 
6783 		if (!insert_node(list, node, &iter))
6784 			return;
6785 
6786 		ui = node->ui_data;
6787 		conv = ui->conv.conv;
6788 		if (conv && pidgin_conv_is_hidden(PIDGIN_CONVERSATION(conv))) {
6789 			hidden = (ui->conv.flags & PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE);
6790 			nick_said = (ui->conv.flags & PIDGIN_BLIST_CHAT_HAS_PENDING_MESSAGE_WITH_NICK);
6791 		}
6792 
6793 		status = pidgin_blist_get_status_icon(node,
6794 				 biglist ? PIDGIN_STATUS_ICON_LARGE : PIDGIN_STATUS_ICON_SMALL);
6795 		emblem = pidgin_blist_get_emblem(node);
6796 
6797 		/* Speed it up if we don't want buddy icons. */
6798 		if(showicons)
6799 			avatar = pidgin_blist_get_buddy_icon(node, TRUE, FALSE);
6800 		else
6801 			avatar = NULL;
6802 
6803 		mark = g_markup_escape_text(purple_chat_get_name(chat), -1);
6804 
6805 		theme = pidgin_blist_get_theme();
6806 
6807 		if (theme == NULL)
6808 			pair = NULL;
6809 		else if (nick_said)
6810 			pair = pidgin_blist_theme_get_unread_message_nick_said_text_info(theme);
6811 		else if (hidden)
6812 			pair = pidgin_blist_theme_get_unread_message_text_info(theme);
6813 		else pair = pidgin_blist_theme_get_online_text_info(theme);
6814 
6815 
6816 		font = theme_font_get_face_default(pair, "");
6817 		if (selected || !(color = theme_font_get_color_default(pair, NULL)))
6818 			/* nick_said color is the same as gtkconv:tab-label-attention */
6819 			color = (nick_said ? "#006aff" : NULL);
6820 
6821 		if (color) {
6822 			tmp = g_strdup_printf("<span font_desc='%s' color='%s' weight='%s'>%s</span>",
6823 				  	  font, color, hidden ? "bold" : "normal", mark);
6824 		} else {
6825 			tmp = g_strdup_printf("<span font_desc='%s' weight='%s'>%s</span>",
6826 				  	  font, hidden ? "bold" : "normal", mark);
6827 		}
6828 		g_free(mark);
6829 		mark = tmp;
6830 
6831 		prpl_icon = pidgin_create_prpl_icon(chat->account, PIDGIN_PRPL_ICON_SMALL);
6832 
6833 		if (theme != NULL)
6834 			bgcolor = pidgin_blist_theme_get_contact_color(theme);
6835 
6836 		gtk_tree_store_set(gtkblist->treemodel, &iter,
6837 				STATUS_ICON_COLUMN, status,
6838 				STATUS_ICON_VISIBLE_COLUMN, TRUE,
6839 				BUDDY_ICON_COLUMN, avatar ? avatar : gtkblist->empty_avatar,
6840 				BUDDY_ICON_VISIBLE_COLUMN, showicons,
6841 				EMBLEM_COLUMN, emblem,
6842 				EMBLEM_VISIBLE_COLUMN, emblem != NULL,
6843 				PROTOCOL_ICON_COLUMN, prpl_icon,
6844 				PROTOCOL_ICON_VISIBLE_COLUMN, purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons"),
6845 				NAME_COLUMN, mark,
6846 				BGCOLOR_COLUMN, bgcolor,
6847 				GROUP_EXPANDER_VISIBLE_COLUMN, FALSE,
6848 				-1);
6849 
6850 		g_free(mark);
6851 		if(emblem)
6852 			g_object_unref(emblem);
6853 		if(status)
6854 			g_object_unref(status);
6855 		if(avatar)
6856 			g_object_unref(avatar);
6857 		if(prpl_icon)
6858 			g_object_unref(prpl_icon);
6859 
6860 	} else {
6861 		pidgin_blist_hide_node(list, node, TRUE);
6862 	}
6863 }
6864 
pidgin_blist_update(PurpleBuddyList * list,PurpleBlistNode * node)6865 static void pidgin_blist_update(PurpleBuddyList *list, PurpleBlistNode *node)
6866 {
6867 	if (list)
6868 		gtkblist = PIDGIN_BLIST(list);
6869 	if(!gtkblist || !gtkblist->treeview || !node)
6870 		return;
6871 
6872 	if (node->ui_data == NULL)
6873 		pidgin_blist_new_node(node);
6874 
6875 	switch(node->type) {
6876 		case PURPLE_BLIST_GROUP_NODE:
6877 			pidgin_blist_update_group(list, node);
6878 			break;
6879 		case PURPLE_BLIST_CONTACT_NODE:
6880 			pidgin_blist_update_contact(list, node);
6881 			break;
6882 		case PURPLE_BLIST_BUDDY_NODE:
6883 			pidgin_blist_update_buddy(list, node, TRUE);
6884 			break;
6885 		case PURPLE_BLIST_CHAT_NODE:
6886 			pidgin_blist_update_chat(list, node);
6887 			break;
6888 		case PURPLE_BLIST_OTHER_NODE:
6889 			return;
6890 	}
6891 
6892 }
6893 
pidgin_blist_destroy(PurpleBuddyList * list)6894 static void pidgin_blist_destroy(PurpleBuddyList *list)
6895 {
6896 	PidginBuddyListPrivate *priv;
6897 
6898 	if (!list || !list->ui_data)
6899 		return;
6900 
6901 	g_return_if_fail(list->ui_data == gtkblist);
6902 
6903 	purple_signals_disconnect_by_handle(gtkblist);
6904 
6905 	if (gtkblist->headline_close)
6906 		g_object_unref(gtkblist->headline_close);
6907 
6908 	gtk_widget_destroy(gtkblist->window);
6909 
6910 	pidgin_blist_tooltip_destroy();
6911 
6912 	if (gtkblist->refresh_timer)
6913 		purple_timeout_remove(gtkblist->refresh_timer);
6914 	if (gtkblist->timeout)
6915 		g_source_remove(gtkblist->timeout);
6916 	if (gtkblist->drag_timeout)
6917 		g_source_remove(gtkblist->drag_timeout);
6918 
6919 	g_hash_table_destroy(gtkblist->connection_errors);
6920 	gtkblist->refresh_timer = 0;
6921 	gtkblist->timeout = 0;
6922 	gtkblist->drag_timeout = 0;
6923 	gtkblist->window = gtkblist->vbox = gtkblist->treeview = NULL;
6924 	g_object_unref(G_OBJECT(gtkblist->treemodel));
6925 	gtkblist->treemodel = NULL;
6926 	g_object_unref(G_OBJECT(gtkblist->ift));
6927 	g_object_unref(G_OBJECT(gtkblist->empty_avatar));
6928 
6929 	gdk_cursor_unref(gtkblist->hand_cursor);
6930 	gdk_cursor_unref(gtkblist->arrow_cursor);
6931 	gtkblist->hand_cursor = NULL;
6932 	gtkblist->arrow_cursor = NULL;
6933 
6934 	priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
6935 	if (priv->current_theme)
6936 		g_object_unref(priv->current_theme);
6937 	if (priv->select_page_timeout)
6938 		purple_timeout_remove(priv->select_page_timeout);
6939 	g_free(priv);
6940 
6941 	g_free(gtkblist);
6942 	accountmenu = NULL;
6943 	gtkblist = NULL;
6944 	purple_prefs_disconnect_by_handle(pidgin_blist_get_handle());
6945 }
6946 
pidgin_blist_set_visible(PurpleBuddyList * list,gboolean show)6947 static void pidgin_blist_set_visible(PurpleBuddyList *list, gboolean show)
6948 {
6949 	if (!(gtkblist && gtkblist->window))
6950 		return;
6951 
6952 	if (show) {
6953 		if(!PIDGIN_WINDOW_ICONIFIED(gtkblist->window) && !GTK_WIDGET_VISIBLE(gtkblist->window))
6954 			purple_signal_emit(pidgin_blist_get_handle(), "gtkblist-unhiding", gtkblist);
6955 		pidgin_blist_restore_position();
6956 		gtk_window_present(GTK_WINDOW(gtkblist->window));
6957 	} else {
6958 		if(visibility_manager_count) {
6959 			purple_signal_emit(pidgin_blist_get_handle(), "gtkblist-hiding", gtkblist);
6960 			gtk_widget_hide(gtkblist->window);
6961 		} else {
6962 			if (!GTK_WIDGET_VISIBLE(gtkblist->window))
6963 				gtk_widget_show(gtkblist->window);
6964 			gtk_window_iconify(GTK_WINDOW(gtkblist->window));
6965 		}
6966 	}
6967 }
6968 
6969 static GList *
groups_tree(void)6970 groups_tree(void)
6971 {
6972 	static GList *list = NULL;
6973 	char *tmp2;
6974 	PurpleGroup *g;
6975 	PurpleBlistNode *gnode;
6976 
6977 	g_list_free(list);
6978 	list = NULL;
6979 
6980 	if (purple_get_blist()->root == NULL)
6981 	{
6982 		list  = g_list_append(list, (gpointer)_("Buddies"));
6983 	}
6984 	else
6985 	{
6986 		for (gnode = purple_get_blist()->root;
6987 			 gnode != NULL;
6988 			 gnode = gnode->next)
6989 		{
6990 			if (PURPLE_BLIST_NODE_IS_GROUP(gnode))
6991 			{
6992 				g    = (PurpleGroup *)gnode;
6993 				tmp2 = g->name;
6994 				list  = g_list_append(list, tmp2);
6995 			}
6996 		}
6997 	}
6998 
6999 	return list;
7000 }
7001 
7002 static void
add_buddy_select_account_cb(GObject * w,PurpleAccount * account,PidginAddBuddyData * data)7003 add_buddy_select_account_cb(GObject *w, PurpleAccount *account,
7004 							PidginAddBuddyData *data)
7005 {
7006 	PurpleConnection *pc = NULL;
7007 	PurplePlugin *prpl = NULL;
7008 	PurplePluginProtocolInfo *prpl_info = NULL;
7009 	gboolean invite_enabled = TRUE;
7010 
7011 	/* Save our account */
7012 	data->rq_data.account = account;
7013 
7014 	if (account)
7015 		pc = purple_account_get_connection(account);
7016 	if (pc)
7017 		prpl = purple_connection_get_prpl(pc);
7018 	if (prpl)
7019 		prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
7020 	if (prpl_info && !(prpl_info->options & OPT_PROTO_INVITE_MESSAGE))
7021 		invite_enabled = FALSE;
7022 
7023 	gtk_widget_set_sensitive(data->entry_for_invite, invite_enabled);
7024 }
7025 
7026 static void
destroy_add_buddy_dialog_cb(GtkWidget * win,PidginAddBuddyData * data)7027 destroy_add_buddy_dialog_cb(GtkWidget *win, PidginAddBuddyData *data)
7028 {
7029 	g_free(data);
7030 }
7031 
7032 static void
add_buddy_cb(GtkWidget * w,int resp,PidginAddBuddyData * data)7033 add_buddy_cb(GtkWidget *w, int resp, PidginAddBuddyData *data)
7034 {
7035 	const char *grp, *who, *whoalias, *invite;
7036 	PurpleAccount *account;
7037 	PurpleGroup *g;
7038 	PurpleBuddy *b;
7039 	PurpleConversation *c;
7040 	PurpleBuddyIcon *icon;
7041 
7042 	if (resp == GTK_RESPONSE_OK)
7043 	{
7044 		who = gtk_entry_get_text(GTK_ENTRY(data->entry));
7045 		grp = pidgin_text_combo_box_entry_get_text(data->combo);
7046 		whoalias = gtk_entry_get_text(GTK_ENTRY(data->entry_for_alias));
7047 		if (*whoalias == '\0')
7048 			whoalias = NULL;
7049 		invite = gtk_entry_get_text(GTK_ENTRY(data->entry_for_invite));
7050 		if (*invite == '\0')
7051 			invite = NULL;
7052 
7053 		account = data->rq_data.account;
7054 
7055 		g = NULL;
7056 		if ((grp != NULL) && (*grp != '\0'))
7057 		{
7058 			if ((g = purple_find_group(grp)) == NULL)
7059 			{
7060 				g = purple_group_new(grp);
7061 				purple_blist_add_group(g, NULL);
7062 			}
7063 
7064 			b = purple_find_buddy_in_group(account, who, g);
7065 		}
7066 		else if ((b = purple_find_buddy(account, who)) != NULL)
7067 		{
7068 			g = purple_buddy_get_group(b);
7069 		}
7070 
7071 		if (b == NULL)
7072 		{
7073 			b = purple_buddy_new(account, who, whoalias);
7074 			purple_blist_add_buddy(b, NULL, g, NULL);
7075 		}
7076 
7077 		purple_account_add_buddy_with_invite(account, b, invite);
7078 
7079 		/* Offer to merge people with the same alias. */
7080 		if (whoalias != NULL && g != NULL)
7081 			gtk_blist_auto_personize((PurpleBlistNode *)g, whoalias);
7082 
7083 		/*
7084 		 * XXX
7085 		 * It really seems like it would be better if the call to
7086 		 * purple_account_add_buddy() and purple_conversation_update() were done in
7087 		 * blist.c, possibly in the purple_blist_add_buddy() function.  Maybe
7088 		 * purple_account_add_buddy() should be renamed to
7089 		 * purple_blist_add_new_buddy() or something, and have it call
7090 		 * purple_blist_add_buddy() after it creates it.  --Mark
7091 		 *
7092 		 * No that's not good.  blist.c should only deal with adding nodes to the
7093 		 * local list.  We need a new, non-gtk file that calls both
7094 		 * purple_account_add_buddy and purple_blist_add_buddy().
7095 		 * Or something.  --Mark
7096 		 */
7097 
7098 		c = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, who, data->rq_data.account);
7099 		if (c != NULL) {
7100 			icon = purple_conv_im_get_icon(PURPLE_CONV_IM(c));
7101 			if (icon != NULL)
7102 				purple_buddy_icon_update(icon);
7103 		}
7104 	}
7105 
7106 	gtk_widget_destroy(data->rq_data.window);
7107 }
7108 
7109 static void
pidgin_blist_request_add_buddy(PurpleAccount * account,const char * username,const char * group,const char * alias)7110 pidgin_blist_request_add_buddy(PurpleAccount *account, const char *username,
7111 								 const char *group, const char *alias)
7112 {
7113 	PidginAddBuddyData *data = g_new0(PidginAddBuddyData, 1);
7114 
7115 	if (account == NULL)
7116 		account = purple_connection_get_account(purple_connections_get_all()->data);
7117 
7118 	make_blist_request_dialog((PidginBlistRequestData *)data,
7119 		account,
7120 		_("Add Buddy"), "add_buddy",
7121 		_("Add a buddy.\n"),
7122 		G_CALLBACK(add_buddy_select_account_cb), add_buddy_account_filter_func,
7123 		G_CALLBACK(add_buddy_cb));
7124 	gtk_dialog_add_buttons(GTK_DIALOG(data->rq_data.window),
7125 			GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
7126 			GTK_STOCK_ADD, GTK_RESPONSE_OK,
7127 			NULL);
7128 	gtk_dialog_set_default_response(GTK_DIALOG(data->rq_data.window),
7129 			GTK_RESPONSE_OK);
7130 
7131 	g_signal_connect(G_OBJECT(data->rq_data.window), "destroy",
7132 	                 G_CALLBACK(destroy_add_buddy_dialog_cb), data);
7133 
7134 	data->entry = gtk_entry_new();
7135 
7136 	pidgin_add_widget_to_vbox(data->rq_data.vbox, _("Buddy's _username:"),
7137 		data->rq_data.sg, data->entry, TRUE, NULL);
7138 	gtk_widget_grab_focus(data->entry);
7139 
7140 	if (username != NULL)
7141 		gtk_entry_set_text(GTK_ENTRY(data->entry), username);
7142 	else
7143 		gtk_dialog_set_response_sensitive(GTK_DIALOG(data->rq_data.window),
7144 		                                  GTK_RESPONSE_OK, FALSE);
7145 
7146 	gtk_entry_set_activates_default (GTK_ENTRY(data->entry), TRUE);
7147 
7148 	g_signal_connect(G_OBJECT(data->entry), "changed",
7149 	                 G_CALLBACK(pidgin_set_sensitive_if_input),
7150 	                 data->rq_data.window);
7151 
7152 	data->entry_for_alias = gtk_entry_new();
7153 	pidgin_add_widget_to_vbox(data->rq_data.vbox, _("(Optional) A_lias:"),
7154 	                          data->rq_data.sg, data->entry_for_alias, TRUE,
7155 	                          NULL);
7156 
7157 	if (alias != NULL)
7158 		gtk_entry_set_text(GTK_ENTRY(data->entry_for_alias), alias);
7159 
7160 	if (username != NULL)
7161 		gtk_widget_grab_focus(GTK_WIDGET(data->entry_for_alias));
7162 
7163 	data->entry_for_invite = gtk_entry_new();
7164 	pidgin_add_widget_to_vbox(data->rq_data.vbox, _("(Optional) _Invite message:"),
7165 	                          data->rq_data.sg, data->entry_for_invite, TRUE,
7166 	                          NULL);
7167 
7168 	data->combo = pidgin_text_combo_box_entry_new(group, groups_tree());
7169 	pidgin_add_widget_to_vbox(data->rq_data.vbox, _("Add buddy to _group:"),
7170 	                          data->rq_data.sg, data->combo, TRUE, NULL);
7171 
7172 	gtk_widget_show_all(data->rq_data.window);
7173 
7174 	/* Force update of invite message entry sensitivity */
7175 	add_buddy_select_account_cb(NULL, account, data);
7176 }
7177 
7178 static void
add_chat_cb(GtkWidget * w,PidginAddChatData * data)7179 add_chat_cb(GtkWidget *w, PidginAddChatData *data)
7180 {
7181 	GList *tmp;
7182 	PurpleChat *chat;
7183 	GHashTable *components;
7184 
7185 	components = g_hash_table_new_full(g_str_hash, g_str_equal,
7186 									   g_free, g_free);
7187 
7188 	for (tmp = data->chat_data.entries; tmp; tmp = tmp->next)
7189 	{
7190 		if (g_object_get_data(tmp->data, "is_spin"))
7191 		{
7192 			g_hash_table_replace(components,
7193 					g_strdup(g_object_get_data(tmp->data, "identifier")),
7194 					g_strdup_printf("%d",
7195 						gtk_spin_button_get_value_as_int(tmp->data)));
7196 		}
7197 		else
7198 		{
7199 			const char *value = gtk_entry_get_text(tmp->data);
7200 
7201 			if (*value != '\0')
7202 				g_hash_table_replace(components,
7203 						g_strdup(g_object_get_data(tmp->data, "identifier")),
7204 						g_strdup(value));
7205 		}
7206 	}
7207 
7208 	chat = purple_chat_new(data->chat_data.rq_data.account,
7209 	                       gtk_entry_get_text(GTK_ENTRY(data->alias_entry)),
7210 	                       components);
7211 
7212 	if (chat != NULL) {
7213 		PurpleGroup *group;
7214 		const char *group_name;
7215 
7216 		group_name = pidgin_text_combo_box_entry_get_text(data->group_combo);
7217 
7218 		group = NULL;
7219 		if ((group_name != NULL) && (*group_name != '\0') &&
7220 		    ((group = purple_find_group(group_name)) == NULL))
7221 		{
7222 			group = purple_group_new(group_name);
7223 			purple_blist_add_group(group, NULL);
7224 		}
7225 
7226 		purple_blist_add_chat(chat, group, NULL);
7227 
7228 		if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(data->autojoin)))
7229 			purple_blist_node_set_bool((PurpleBlistNode*)chat, "gtk-autojoin", TRUE);
7230 
7231 		if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(data->persistent)))
7232 			purple_blist_node_set_bool((PurpleBlistNode*)chat, "gtk-persistent", TRUE);
7233 	}
7234 
7235 	gtk_widget_destroy(data->chat_data.rq_data.window);
7236 	g_free(data->chat_data.default_chat_name);
7237 	g_list_free(data->chat_data.entries);
7238 	g_free(data);
7239 }
7240 
7241 static void
add_chat_resp_cb(GtkWidget * w,int resp,PidginAddChatData * data)7242 add_chat_resp_cb(GtkWidget *w, int resp, PidginAddChatData *data)
7243 {
7244 	if (resp == GTK_RESPONSE_OK)
7245 	{
7246 		add_chat_cb(NULL, data);
7247 	}
7248 	else if (resp == 1)
7249 	{
7250 		pidgin_roomlist_dialog_show_with_account(data->chat_data.rq_data.account);
7251 	}
7252 	else
7253 	{
7254 		gtk_widget_destroy(data->chat_data.rq_data.window);
7255 		g_free(data->chat_data.default_chat_name);
7256 		g_list_free(data->chat_data.entries);
7257 		g_free(data);
7258 	}
7259 }
7260 
7261 static void
pidgin_blist_request_add_chat(PurpleAccount * account,PurpleGroup * group,const char * alias,const char * name)7262 pidgin_blist_request_add_chat(PurpleAccount *account, PurpleGroup *group,
7263 								const char *alias, const char *name)
7264 {
7265 	PidginAddChatData *data;
7266 	GList *l;
7267 	PurpleConnection *gc;
7268 	GtkBox *vbox;
7269 
7270 	if (account != NULL) {
7271 		gc = purple_account_get_connection(account);
7272 
7273 		if (PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->join_chat == NULL) {
7274 			purple_notify_error(gc, NULL, _("This protocol does not support chat rooms."), NULL);
7275 			return;
7276 		}
7277 	} else {
7278 		/* Find an account with chat capabilities */
7279 		for (l = purple_connections_get_all(); l != NULL; l = l->next) {
7280 			gc = (PurpleConnection *)l->data;
7281 
7282 			if (PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->join_chat != NULL) {
7283 				account = purple_connection_get_account(gc);
7284 				break;
7285 			}
7286 		}
7287 
7288 		if (account == NULL) {
7289 			purple_notify_error(NULL, NULL,
7290 							  _("You are not currently signed on with any "
7291 								"protocols that have the ability to chat."), NULL);
7292 			return;
7293 		}
7294 	}
7295 
7296 	data = g_new0(PidginAddChatData, 1);
7297 	vbox = GTK_BOX(make_blist_request_dialog((PidginBlistRequestData *)data, account,
7298 			_("Add Chat"), "add_chat",
7299 			_("Please enter an alias, and the appropriate information "
7300 			  "about the chat you would like to add to your buddy list.\n"),
7301 			G_CALLBACK(chat_select_account_cb), chat_account_filter_func,
7302 			G_CALLBACK(add_chat_resp_cb)));
7303 	gtk_dialog_add_buttons(GTK_DIALOG(data->chat_data.rq_data.window),
7304 		_("Room List"), 1,
7305 		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
7306 		GTK_STOCK_ADD, GTK_RESPONSE_OK,
7307 		NULL);
7308 	gtk_dialog_set_default_response(GTK_DIALOG(data->chat_data.rq_data.window),
7309 			GTK_RESPONSE_OK);
7310 
7311 	data->chat_data.default_chat_name = g_strdup(name);
7312 
7313 	rebuild_chat_entries((PidginChatData *)data, name);
7314 
7315 	data->alias_entry = gtk_entry_new();
7316 	if (alias != NULL)
7317 		gtk_entry_set_text(GTK_ENTRY(data->alias_entry), alias);
7318 	gtk_entry_set_activates_default(GTK_ENTRY(data->alias_entry), TRUE);
7319 
7320 	pidgin_add_widget_to_vbox(GTK_BOX(vbox), _("A_lias:"),
7321 	                          data->chat_data.rq_data.sg, data->alias_entry,
7322 	                          TRUE, NULL);
7323 	if (name != NULL)
7324 		gtk_widget_grab_focus(data->alias_entry);
7325 
7326 	data->group_combo = pidgin_text_combo_box_entry_new(group ? group->name : NULL, groups_tree());
7327 	pidgin_add_widget_to_vbox(GTK_BOX(vbox), _("_Group:"),
7328 	                          data->chat_data.rq_data.sg, data->group_combo,
7329 	                          TRUE, NULL);
7330 
7331 	data->autojoin = gtk_check_button_new_with_mnemonic(_("Automatically _join when account connects"));
7332 	data->persistent = gtk_check_button_new_with_mnemonic(_("_Remain in chat after window is closed"));
7333 	gtk_box_pack_start(GTK_BOX(vbox), data->autojoin, FALSE, FALSE, 0);
7334 	gtk_box_pack_start(GTK_BOX(vbox), data->persistent, FALSE, FALSE, 0);
7335 
7336 	gtk_widget_show_all(data->chat_data.rq_data.window);
7337 }
7338 
7339 static void
add_group_cb(PurpleConnection * gc,const char * group_name)7340 add_group_cb(PurpleConnection *gc, const char *group_name)
7341 {
7342 	PurpleGroup *group;
7343 
7344 	if ((group_name == NULL) || (*group_name == '\0'))
7345 		return;
7346 
7347 	group = purple_group_new(group_name);
7348 	purple_blist_add_group(group, NULL);
7349 }
7350 
7351 static void
pidgin_blist_request_add_group(void)7352 pidgin_blist_request_add_group(void)
7353 {
7354 	purple_request_input(NULL, _("Add Group"), NULL,
7355 					   _("Please enter the name of the group to be added."),
7356 					   NULL, FALSE, FALSE, NULL,
7357 					   _("Add"), G_CALLBACK(add_group_cb),
7358 					   _("Cancel"), NULL,
7359 					   NULL, NULL, NULL,
7360 					   NULL);
7361 }
7362 
7363 void
pidgin_blist_toggle_visibility()7364 pidgin_blist_toggle_visibility()
7365 {
7366 	if (gtkblist && gtkblist->window) {
7367 		if (GTK_WIDGET_VISIBLE(gtkblist->window)) {
7368 			/* make the buddy list visible if it is iconified or if it is
7369 			 * obscured and not currently focused (the focus part ensures
7370 			 * that we do something reasonable if the buddy list is obscured
7371 			 * by a window set to always be on top), otherwise hide the
7372 			 * buddy list
7373 			 */
7374 			purple_blist_set_visible(PIDGIN_WINDOW_ICONIFIED(gtkblist->window) ||
7375 					((gtk_blist_visibility != GDK_VISIBILITY_UNOBSCURED) &&
7376 					!gtk_blist_focused));
7377 		} else {
7378 			purple_blist_set_visible(TRUE);
7379 		}
7380 	}
7381 }
7382 
7383 void
pidgin_blist_visibility_manager_add()7384 pidgin_blist_visibility_manager_add()
7385 {
7386 	visibility_manager_count++;
7387 	purple_debug_info("gtkblist", "added visibility manager: %d\n", visibility_manager_count);
7388 }
7389 
7390 void
pidgin_blist_visibility_manager_remove()7391 pidgin_blist_visibility_manager_remove()
7392 {
7393 	if (visibility_manager_count)
7394 		visibility_manager_count--;
7395 	if (!visibility_manager_count)
7396 		purple_blist_set_visible(TRUE);
7397 	purple_debug_info("gtkblist", "removed visibility manager: %d\n", visibility_manager_count);
7398 }
7399 
pidgin_blist_add_alert(GtkWidget * widget)7400 void pidgin_blist_add_alert(GtkWidget *widget)
7401 {
7402 	gtk_container_add(GTK_CONTAINER(gtkblist->scrollbook), widget);
7403 	set_urgent();
7404 }
7405 
7406 void
pidgin_blist_set_headline(const char * text,GdkPixbuf * pixbuf,GCallback callback,gpointer user_data,GDestroyNotify destroy)7407 pidgin_blist_set_headline(const char *text, GdkPixbuf *pixbuf, GCallback callback,
7408 			gpointer user_data, GDestroyNotify destroy)
7409 {
7410 	/* Destroy any existing headline first */
7411 	if (gtkblist->headline_destroy)
7412 		gtkblist->headline_destroy(gtkblist->headline_data);
7413 
7414 	gtk_label_set_markup(GTK_LABEL(gtkblist->headline_label), text);
7415 	gtk_image_set_from_pixbuf(GTK_IMAGE(gtkblist->headline_image), pixbuf);
7416 
7417 	gtkblist->headline_callback = callback;
7418 	gtkblist->headline_data = user_data;
7419 	gtkblist->headline_destroy = destroy;
7420 	if (text != NULL || pixbuf != NULL) {
7421 		set_urgent();
7422 		gtk_widget_show_all(gtkblist->headline_hbox);
7423 	} else {
7424 		gtk_widget_hide(gtkblist->headline_hbox);
7425 	}
7426 }
7427 
7428 
7429 static void
set_urgent(void)7430 set_urgent(void)
7431 {
7432 	if (gtkblist->window && !GTK_WIDGET_HAS_FOCUS(gtkblist->window))
7433 		pidgin_set_urgent(GTK_WINDOW(gtkblist->window), TRUE);
7434 }
7435 
7436 static PurpleBlistUiOps blist_ui_ops =
7437 {
7438 	pidgin_blist_new_list,
7439 	pidgin_blist_new_node,
7440 	pidgin_blist_show,
7441 	pidgin_blist_update,
7442 	pidgin_blist_remove,
7443 	pidgin_blist_destroy,
7444 	pidgin_blist_set_visible,
7445 	pidgin_blist_request_add_buddy,
7446 	pidgin_blist_request_add_chat,
7447 	pidgin_blist_request_add_group,
7448 	NULL,
7449 	NULL,
7450 	NULL,
7451 	NULL
7452 };
7453 
7454 
7455 PurpleBlistUiOps *
pidgin_blist_get_ui_ops(void)7456 pidgin_blist_get_ui_ops(void)
7457 {
7458 	return &blist_ui_ops;
7459 }
7460 
pidgin_blist_get_default_gtk_blist()7461 PidginBuddyList *pidgin_blist_get_default_gtk_blist()
7462 {
7463 	return gtkblist;
7464 }
7465 
autojoin_cb(PurpleConnection * gc,gpointer data)7466 static gboolean autojoin_cb(PurpleConnection *gc, gpointer data)
7467 {
7468 	PurpleAccount *account = purple_connection_get_account(gc);
7469 	PurpleBlistNode *gnode, *cnode;
7470 	for(gnode = purple_get_blist()->root; gnode; gnode = gnode->next)
7471 	{
7472 		if(!PURPLE_BLIST_NODE_IS_GROUP(gnode))
7473 			continue;
7474 		for(cnode = gnode->child; cnode; cnode = cnode->next)
7475 		{
7476 			PurpleChat *chat;
7477 
7478 			if(!PURPLE_BLIST_NODE_IS_CHAT(cnode))
7479 				continue;
7480 
7481 			chat = (PurpleChat *)cnode;
7482 
7483 			if(chat->account != account)
7484 				continue;
7485 
7486 			if (purple_blist_node_get_bool((PurpleBlistNode*)chat, "gtk-autojoin"))
7487 				serv_join_chat(gc, chat->components);
7488 		}
7489 	}
7490 
7491 	/* Stop processing; we handled the autojoins. */
7492 	return TRUE;
7493 }
7494 
7495 void *
pidgin_blist_get_handle()7496 pidgin_blist_get_handle() {
7497 	static int handle;
7498 
7499 	return &handle;
7500 }
7501 
buddy_signonoff_timeout_cb(PurpleBuddy * buddy)7502 static gboolean buddy_signonoff_timeout_cb(PurpleBuddy *buddy)
7503 {
7504 	struct _pidgin_blist_node *gtknode = ((PurpleBlistNode*)buddy)->ui_data;
7505 
7506 	gtknode->recent_signonoff = FALSE;
7507 	gtknode->recent_signonoff_timer = 0;
7508 
7509 	pidgin_blist_update(NULL, (PurpleBlistNode*)buddy);
7510 
7511 	return FALSE;
7512 }
7513 
buddy_signonoff_cb(PurpleBuddy * buddy)7514 static void buddy_signonoff_cb(PurpleBuddy *buddy)
7515 {
7516 	struct _pidgin_blist_node *gtknode;
7517 
7518 	if(!((PurpleBlistNode*)buddy)->ui_data) {
7519 		pidgin_blist_new_node((PurpleBlistNode*)buddy);
7520 	}
7521 
7522 	gtknode = ((PurpleBlistNode*)buddy)->ui_data;
7523 
7524 	gtknode->recent_signonoff = TRUE;
7525 
7526 	if(gtknode->recent_signonoff_timer > 0)
7527 		purple_timeout_remove(gtknode->recent_signonoff_timer);
7528 	gtknode->recent_signonoff_timer = purple_timeout_add_seconds(10,
7529 			(GSourceFunc)buddy_signonoff_timeout_cb, buddy);
7530 }
7531 
7532 void
pidgin_blist_set_theme(PidginBlistTheme * theme)7533 pidgin_blist_set_theme(PidginBlistTheme *theme)
7534 {
7535 	PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
7536 	PurpleBuddyList *list = purple_get_blist();
7537 
7538 	if (theme != NULL)
7539 		purple_prefs_set_string(PIDGIN_PREFS_ROOT "/blist/theme",
7540 				purple_theme_get_name(PURPLE_THEME(theme)));
7541 	else
7542 		purple_prefs_set_string(PIDGIN_PREFS_ROOT "/blist/theme", "");
7543 
7544 	if (priv->current_theme)
7545 		g_object_unref(priv->current_theme);
7546 
7547 	priv->current_theme = theme ? g_object_ref(theme) : NULL;
7548 
7549 	pidgin_blist_build_layout(list);
7550 
7551 	pidgin_blist_refresh(list);
7552 }
7553 
7554 
7555 PidginBlistTheme *
pidgin_blist_get_theme()7556 pidgin_blist_get_theme()
7557 {
7558 	PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
7559 
7560 	return priv->current_theme;
7561 }
7562 
pidgin_blist_init(void)7563 void pidgin_blist_init(void)
7564 {
7565 	void *gtk_blist_handle = pidgin_blist_get_handle();
7566 
7567 	cached_emblems = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
7568 
7569 	/* Initialize prefs */
7570 	purple_prefs_add_none(PIDGIN_PREFS_ROOT "/blist");
7571 	purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons", TRUE);
7572 	purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/show_empty_groups", FALSE);
7573 	purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/show_idle_time", TRUE);
7574 	purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/show_offline_buddies", FALSE);
7575 	purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons", FALSE);
7576 	purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/list_visible", FALSE);
7577 	purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/list_maximized", FALSE);
7578 	purple_prefs_add_string(PIDGIN_PREFS_ROOT "/blist/sort_type", "alphabetical");
7579 	purple_prefs_add_int(PIDGIN_PREFS_ROOT "/blist/x", 0);
7580 	purple_prefs_add_int(PIDGIN_PREFS_ROOT "/blist/y", 0);
7581 	purple_prefs_add_int(PIDGIN_PREFS_ROOT "/blist/width", 250); /* Golden ratio, baby */
7582 	purple_prefs_add_int(PIDGIN_PREFS_ROOT "/blist/height", 405); /* Golden ratio, baby */
7583 #if !GTK_CHECK_VERSION(2,14,0)
7584 	/* This pref is used in pidgintooltip.c. */
7585 	purple_prefs_add_int(PIDGIN_PREFS_ROOT "/blist/tooltip_delay", 500);
7586 #endif
7587 	purple_prefs_add_string(PIDGIN_PREFS_ROOT "/blist/theme", "");
7588 
7589 	purple_theme_manager_register_type(g_object_new(PIDGIN_TYPE_BLIST_THEME_LOADER, "type", "blist", NULL));
7590 
7591 	/* Register our signals */
7592 	purple_signal_register(gtk_blist_handle, "gtkblist-hiding",
7593 	                     purple_marshal_VOID__POINTER, NULL, 1,
7594 	                     purple_value_new(PURPLE_TYPE_SUBTYPE,
7595 	                                    PURPLE_SUBTYPE_BLIST));
7596 
7597 	purple_signal_register(gtk_blist_handle, "gtkblist-unhiding",
7598 	                     purple_marshal_VOID__POINTER, NULL, 1,
7599 	                     purple_value_new(PURPLE_TYPE_SUBTYPE,
7600 	                                    PURPLE_SUBTYPE_BLIST));
7601 
7602 	purple_signal_register(gtk_blist_handle, "gtkblist-created",
7603 	                     purple_marshal_VOID__POINTER, NULL, 1,
7604 	                     purple_value_new(PURPLE_TYPE_SUBTYPE,
7605 	                                    PURPLE_SUBTYPE_BLIST));
7606 
7607 	purple_signal_register(gtk_blist_handle, "drawing-tooltip",
7608 	                     purple_marshal_VOID__POINTER_POINTER_UINT, NULL, 3,
7609 	                     purple_value_new(PURPLE_TYPE_SUBTYPE,
7610 	                                    PURPLE_SUBTYPE_BLIST_NODE),
7611 	                     purple_value_new_outgoing(PURPLE_TYPE_BOXED, "GString *"),
7612 	                     purple_value_new(PURPLE_TYPE_BOOLEAN));
7613 
7614 	purple_signal_register(gtk_blist_handle, "drawing-buddy",
7615 						purple_marshal_POINTER__POINTER,
7616 						purple_value_new(PURPLE_TYPE_STRING), 1,
7617 						purple_value_new(PURPLE_TYPE_SUBTYPE,
7618 										PURPLE_SUBTYPE_BLIST_BUDDY));
7619 
7620 	purple_signal_connect(purple_blist_get_handle(), "buddy-signed-on",
7621 			gtk_blist_handle, PURPLE_CALLBACK(buddy_signonoff_cb), NULL);
7622 	purple_signal_connect(purple_blist_get_handle(), "buddy-signed-off",
7623 			gtk_blist_handle, PURPLE_CALLBACK(buddy_signonoff_cb), NULL);
7624 	purple_signal_connect(purple_blist_get_handle(), "buddy-privacy-changed",
7625 			gtk_blist_handle, PURPLE_CALLBACK(pidgin_blist_update_privacy_cb), NULL);
7626 
7627 	purple_signal_connect_priority(purple_connections_get_handle(), "autojoin",
7628 	                               gtk_blist_handle, PURPLE_CALLBACK(autojoin_cb),
7629 	                               NULL, PURPLE_SIGNAL_PRIORITY_HIGHEST);
7630 }
7631 
7632 void
pidgin_blist_uninit(void)7633 pidgin_blist_uninit(void) {
7634 	g_hash_table_destroy(cached_emblems);
7635 
7636 	purple_signals_unregister_by_instance(pidgin_blist_get_handle());
7637 	purple_signals_disconnect_by_handle(pidgin_blist_get_handle());
7638 }
7639 
7640 /*********************************************************************
7641  * Buddy List sorting functions                                      *
7642  *********************************************************************/
7643 
pidgin_blist_get_sort_methods()7644 GList *pidgin_blist_get_sort_methods()
7645 {
7646 	return pidgin_blist_sort_methods;
7647 }
7648 
pidgin_blist_sort_method_reg(const char * id,const char * name,pidgin_blist_sort_function func)7649 void pidgin_blist_sort_method_reg(const char *id, const char *name, pidgin_blist_sort_function func)
7650 {
7651 	struct pidgin_blist_sort_method *method;
7652 
7653 	g_return_if_fail(id != NULL);
7654 	g_return_if_fail(name != NULL);
7655 	g_return_if_fail(func != NULL);
7656 
7657 	method = g_new0(struct pidgin_blist_sort_method, 1);
7658 	method->id = g_strdup(id);
7659 	method->name = g_strdup(name);
7660 	method->func = func;
7661 	pidgin_blist_sort_methods = g_list_append(pidgin_blist_sort_methods, method);
7662 	pidgin_blist_update_sort_methods();
7663 }
7664 
pidgin_blist_sort_method_unreg(const char * id)7665 void pidgin_blist_sort_method_unreg(const char *id)
7666 {
7667 	GList *l = pidgin_blist_sort_methods;
7668 
7669 	g_return_if_fail(id != NULL);
7670 
7671 	while(l) {
7672 		struct pidgin_blist_sort_method *method = l->data;
7673 		if(purple_strequal(method->id, id)) {
7674 			pidgin_blist_sort_methods = g_list_delete_link(pidgin_blist_sort_methods, l);
7675 			g_free(method->id);
7676 			g_free(method->name);
7677 			g_free(method);
7678 			break;
7679 		}
7680 		l = l->next;
7681 	}
7682 	pidgin_blist_update_sort_methods();
7683 }
7684 
pidgin_blist_sort_method_set(const char * id)7685 void pidgin_blist_sort_method_set(const char *id){
7686 	GList *l = pidgin_blist_sort_methods;
7687 
7688 	if(!id)
7689 		id = "none";
7690 
7691 	while (l && !purple_strequal(((struct pidgin_blist_sort_method*)l->data)->id, id))
7692 		l = l->next;
7693 
7694 	if (l) {
7695 		current_sort_method = l->data;
7696 	} else if (!current_sort_method) {
7697 		pidgin_blist_sort_method_set("none");
7698 		return;
7699 	}
7700 	if (purple_strequal(id, "none")) {
7701 		redo_buddy_list(purple_get_blist(), TRUE, FALSE);
7702 	} else {
7703 		redo_buddy_list(purple_get_blist(), FALSE, FALSE);
7704 	}
7705 }
7706 
7707 /******************************************
7708  ** Sort Methods
7709  ******************************************/
7710 
sort_method_none(PurpleBlistNode * node,PurpleBuddyList * blist,GtkTreeIter parent_iter,GtkTreeIter * cur,GtkTreeIter * iter)7711 static void sort_method_none(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter parent_iter, GtkTreeIter *cur, GtkTreeIter *iter)
7712 {
7713 	PurpleBlistNode *sibling = node->prev;
7714 	GtkTreeIter sibling_iter;
7715 
7716 	if (cur != NULL) {
7717 		*iter = *cur;
7718 		return;
7719 	}
7720 
7721 	while (sibling && !get_iter_from_node(sibling, &sibling_iter)) {
7722 		sibling = sibling->prev;
7723 	}
7724 
7725 	gtk_tree_store_insert_after(gtkblist->treemodel, iter,
7726 			node->parent ? &parent_iter : NULL,
7727 			sibling ? &sibling_iter : NULL);
7728 }
7729 
sort_method_alphabetical(PurpleBlistNode * node,PurpleBuddyList * blist,GtkTreeIter groupiter,GtkTreeIter * cur,GtkTreeIter * iter)7730 static void sort_method_alphabetical(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter)
7731 {
7732 	GtkTreeIter more_z;
7733 
7734 	const char *my_name;
7735 
7736 	if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
7737 		my_name = purple_contact_get_alias((PurpleContact*)node);
7738 	} else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
7739 		my_name = purple_chat_get_name((PurpleChat*)node);
7740 	} else {
7741 		sort_method_none(node, blist, groupiter, cur, iter);
7742 		return;
7743 	}
7744 
7745 	if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(gtkblist->treemodel), &more_z, &groupiter)) {
7746 		gtk_tree_store_insert(gtkblist->treemodel, iter, &groupiter, 0);
7747 		return;
7748 	}
7749 
7750 	do {
7751 		PurpleBlistNode *n;
7752 		const char *this_name;
7753 		int cmp;
7754 
7755 		gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &more_z, NODE_COLUMN, &n, -1);
7756 
7757 		if(PURPLE_BLIST_NODE_IS_CONTACT(n)) {
7758 			this_name = purple_contact_get_alias((PurpleContact*)n);
7759 		} else if(PURPLE_BLIST_NODE_IS_CHAT(n)) {
7760 			this_name = purple_chat_get_name((PurpleChat*)n);
7761 		} else {
7762 			this_name = NULL;
7763 		}
7764 
7765 		cmp = purple_utf8_strcasecmp(my_name, this_name);
7766 
7767 		if(this_name && (cmp < 0 || (cmp == 0 && node < n))) {
7768 			if(cur) {
7769 				gtk_tree_store_move_before(gtkblist->treemodel, cur, &more_z);
7770 				*iter = *cur;
7771 				return;
7772 			} else {
7773 				gtk_tree_store_insert_before(gtkblist->treemodel, iter,
7774 						&groupiter, &more_z);
7775 				return;
7776 			}
7777 		}
7778 	} while (gtk_tree_model_iter_next (GTK_TREE_MODEL(gtkblist->treemodel), &more_z));
7779 
7780 	if(cur) {
7781 		gtk_tree_store_move_before(gtkblist->treemodel, cur, NULL);
7782 		*iter = *cur;
7783 		return;
7784 	} else {
7785 		gtk_tree_store_append(gtkblist->treemodel, iter, &groupiter);
7786 		return;
7787 	}
7788 }
7789 
sort_method_status(PurpleBlistNode * node,PurpleBuddyList * blist,GtkTreeIter groupiter,GtkTreeIter * cur,GtkTreeIter * iter)7790 static void sort_method_status(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter)
7791 {
7792 	GtkTreeIter more_z;
7793 
7794 	PurpleBuddy *my_buddy, *this_buddy;
7795 
7796 	if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
7797 		my_buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
7798 	} else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
7799 		if (cur != NULL) {
7800 			*iter = *cur;
7801 			return;
7802 		}
7803 
7804 		gtk_tree_store_append(gtkblist->treemodel, iter, &groupiter);
7805 		return;
7806 	} else {
7807 		sort_method_alphabetical(node, blist, groupiter, cur, iter);
7808 		return;
7809 	}
7810 
7811 
7812 	if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(gtkblist->treemodel), &more_z, &groupiter)) {
7813 		gtk_tree_store_insert(gtkblist->treemodel, iter, &groupiter, 0);
7814 		return;
7815 	}
7816 
7817 	do {
7818 		PurpleBlistNode *n;
7819 		gint name_cmp;
7820 		gint presence_cmp;
7821 
7822 		gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &more_z, NODE_COLUMN, &n, -1);
7823 
7824 		if(PURPLE_BLIST_NODE_IS_CONTACT(n)) {
7825 			this_buddy = purple_contact_get_priority_buddy((PurpleContact*)n);
7826 		} else {
7827 			this_buddy = NULL;
7828 		}
7829 
7830 		name_cmp = purple_utf8_strcasecmp(
7831 			purple_contact_get_alias(purple_buddy_get_contact(my_buddy)),
7832 			(this_buddy
7833 			 ? purple_contact_get_alias(purple_buddy_get_contact(this_buddy))
7834 			 : NULL));
7835 
7836 		presence_cmp = purple_presence_compare(
7837 			purple_buddy_get_presence(my_buddy),
7838 			this_buddy ? purple_buddy_get_presence(this_buddy) : NULL);
7839 
7840 		if (this_buddy == NULL ||
7841 			(presence_cmp < 0 ||
7842 			 (presence_cmp == 0 &&
7843 			  (name_cmp < 0 || (name_cmp == 0 && node < n)))))
7844 		{
7845 			if (cur != NULL)
7846 			{
7847 				gtk_tree_store_move_before(gtkblist->treemodel, cur, &more_z);
7848 				*iter = *cur;
7849 				return;
7850 			}
7851 			else
7852 			{
7853 				gtk_tree_store_insert_before(gtkblist->treemodel, iter,
7854 											 &groupiter, &more_z);
7855 				return;
7856 			}
7857 		}
7858 	}
7859 	while (gtk_tree_model_iter_next(GTK_TREE_MODEL(gtkblist->treemodel),
7860 									&more_z));
7861 
7862 	if (cur) {
7863 		gtk_tree_store_move_before(gtkblist->treemodel, cur, NULL);
7864 		*iter = *cur;
7865 		return;
7866 	} else {
7867 		gtk_tree_store_append(gtkblist->treemodel, iter, &groupiter);
7868 		return;
7869 	}
7870 }
7871 
sort_method_log_activity(PurpleBlistNode * node,PurpleBuddyList * blist,GtkTreeIter groupiter,GtkTreeIter * cur,GtkTreeIter * iter)7872 static void sort_method_log_activity(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter)
7873 {
7874 	GtkTreeIter more_z;
7875 
7876 	int activity_score = 0, this_log_activity_score = 0;
7877 	const char *buddy_name, *this_buddy_name;
7878 
7879 	if(cur && (gtk_tree_model_iter_n_children(GTK_TREE_MODEL(gtkblist->treemodel), &groupiter) == 1)) {
7880 		*iter = *cur;
7881 		return;
7882 	}
7883 
7884 	if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
7885 		PurpleBlistNode *n;
7886 		PurpleBuddy *buddy;
7887 		for (n = node->child; n; n = n->next) {
7888 			buddy = (PurpleBuddy*)n;
7889 			activity_score += purple_log_get_activity_score(PURPLE_LOG_IM, buddy->name, buddy->account);
7890 		}
7891 		buddy_name = purple_contact_get_alias((PurpleContact*)node);
7892 	} else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
7893 		/* we don't have a reliable way of getting the log filename
7894 		 * from the chat info in the blist, yet */
7895 		if (cur != NULL) {
7896 			*iter = *cur;
7897 			return;
7898 		}
7899 
7900 		gtk_tree_store_append(gtkblist->treemodel, iter, &groupiter);
7901 		return;
7902 	} else {
7903 		sort_method_none(node, blist, groupiter, cur, iter);
7904 		return;
7905 	}
7906 
7907 
7908 	if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(gtkblist->treemodel), &more_z, &groupiter)) {
7909 		gtk_tree_store_insert(gtkblist->treemodel, iter, &groupiter, 0);
7910 		return;
7911 	}
7912 
7913 	do {
7914 		PurpleBlistNode *n;
7915 		PurpleBlistNode *n2;
7916 		PurpleBuddy *buddy;
7917 		int cmp;
7918 
7919 		gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &more_z, NODE_COLUMN, &n, -1);
7920 		this_log_activity_score = 0;
7921 
7922 		if(PURPLE_BLIST_NODE_IS_CONTACT(n)) {
7923 			for (n2 = n->child; n2; n2 = n2->next) {
7924                         	buddy = (PurpleBuddy*)n2;
7925 				this_log_activity_score += purple_log_get_activity_score(PURPLE_LOG_IM, buddy->name, buddy->account);
7926 			}
7927 			this_buddy_name = purple_contact_get_alias((PurpleContact*)n);
7928 		} else {
7929 			this_buddy_name = NULL;
7930 		}
7931 
7932 		cmp = purple_utf8_strcasecmp(buddy_name, this_buddy_name);
7933 
7934 		if (!PURPLE_BLIST_NODE_IS_CONTACT(n) || activity_score > this_log_activity_score ||
7935 				((activity_score == this_log_activity_score) &&
7936 				 (cmp < 0 || (cmp == 0 && node < n)))) {
7937 			if (cur != NULL) {
7938 				gtk_tree_store_move_before(gtkblist->treemodel, cur, &more_z);
7939 				*iter = *cur;
7940 				return;
7941 			} else {
7942 				gtk_tree_store_insert_before(gtkblist->treemodel, iter,
7943 						&groupiter, &more_z);
7944 				return;
7945 			}
7946 		}
7947 	} while (gtk_tree_model_iter_next (GTK_TREE_MODEL(gtkblist->treemodel), &more_z));
7948 
7949 	if (cur != NULL) {
7950 		gtk_tree_store_move_before(gtkblist->treemodel, cur, NULL);
7951 		*iter = *cur;
7952 		return;
7953 	} else {
7954 		gtk_tree_store_append(gtkblist->treemodel, iter, &groupiter);
7955 		return;
7956 	}
7957 }
7958 
7959 static void
plugin_act(GtkObject * obj,PurplePluginAction * pam)7960 plugin_act(GtkObject *obj, PurplePluginAction *pam)
7961 {
7962 	if (pam && pam->callback)
7963 		pam->callback(pam);
7964 }
7965 
7966 static void
build_plugin_actions(GtkWidget * menu,PurplePlugin * plugin,gpointer context)7967 build_plugin_actions(GtkWidget *menu, PurplePlugin *plugin,
7968 		gpointer context)
7969 {
7970 	GtkWidget *menuitem;
7971 	PurplePluginAction *action = NULL;
7972 	GList *actions, *l;
7973 
7974 	actions = PURPLE_PLUGIN_ACTIONS(plugin, context);
7975 
7976 	for (l = actions; l != NULL; l = l->next)
7977 	{
7978 		if (l->data)
7979 		{
7980 			action = (PurplePluginAction *) l->data;
7981 			action->plugin = plugin;
7982 			action->context = context;
7983 
7984 			menuitem = gtk_menu_item_new_with_label(action->label);
7985 			gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
7986 
7987 			g_signal_connect(G_OBJECT(menuitem), "activate",
7988 					G_CALLBACK(plugin_act), action);
7989 			g_object_set_data_full(G_OBJECT(menuitem), "plugin_action",
7990 								   action,
7991 								   (GDestroyNotify)purple_plugin_action_free);
7992 			gtk_widget_show(menuitem);
7993 		}
7994 		else
7995 			pidgin_separator(menu);
7996 	}
7997 
7998 	g_list_free(actions);
7999 }
8000 
8001 static void
modify_account_cb(GtkWidget * widget,gpointer data)8002 modify_account_cb(GtkWidget *widget, gpointer data)
8003 {
8004 	pidgin_account_dialog_show(PIDGIN_MODIFY_ACCOUNT_DIALOG, data);
8005 }
8006 
8007 static void
enable_account_cb(GtkCheckMenuItem * widget,gpointer data)8008 enable_account_cb(GtkCheckMenuItem *widget, gpointer data)
8009 {
8010 	PurpleAccount *account = data;
8011 	const PurpleSavedStatus *saved_status;
8012 
8013 	saved_status = purple_savedstatus_get_current();
8014 	purple_savedstatus_activate_for_account(saved_status, account);
8015 
8016 	purple_account_set_enabled(account, PIDGIN_UI, TRUE);
8017 }
8018 
8019 static void
disable_account_cb(GtkCheckMenuItem * widget,gpointer data)8020 disable_account_cb(GtkCheckMenuItem *widget, gpointer data)
8021 {
8022 	PurpleAccount *account = data;
8023 
8024 	purple_account_set_enabled(account, PIDGIN_UI, FALSE);
8025 }
8026 
8027 
8028 
8029 void
pidgin_blist_update_accounts_menu(void)8030 pidgin_blist_update_accounts_menu(void)
8031 {
8032 	GtkWidget *menuitem = NULL, *submenu = NULL;
8033 	GtkAccelGroup *accel_group = NULL;
8034 	GList *l = NULL, *accounts = NULL;
8035 	gboolean disabled_accounts = FALSE;
8036 	gboolean enabled_accounts = FALSE;
8037 
8038 	if (accountmenu == NULL)
8039 		return;
8040 
8041 	/* Clear the old Accounts menu */
8042 	for (l = gtk_container_get_children(GTK_CONTAINER(accountmenu)); l; l = g_list_delete_link(l, l)) {
8043 		menuitem = l->data;
8044 
8045 		if (menuitem != gtk_item_factory_get_widget(gtkblist->ift, N_("/Accounts/Manage Accounts")))
8046 			gtk_widget_destroy(menuitem);
8047 	}
8048 
8049 	for (accounts = purple_accounts_get_all(); accounts; accounts = accounts->next) {
8050 		char *buf = NULL;
8051 		GtkWidget *image = NULL;
8052 		PurpleAccount *account = NULL;
8053 		GdkPixbuf *pixbuf = NULL;
8054 
8055 		account = accounts->data;
8056 
8057 		if(!purple_account_get_enabled(account, PIDGIN_UI)) {
8058 			if (!disabled_accounts) {
8059 				menuitem = gtk_menu_item_new_with_label(_("Enable Account"));
8060 				gtk_menu_shell_append(GTK_MENU_SHELL(accountmenu), menuitem);
8061 
8062 				submenu = gtk_menu_new();
8063 				gtk_menu_set_accel_group(GTK_MENU(submenu), accel_group);
8064 				gtk_menu_set_accel_path(GTK_MENU(submenu), N_("<PurpleMain>/Accounts/Enable Account"));
8065 				gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
8066 
8067 				disabled_accounts = TRUE;
8068 			}
8069 
8070 			buf = g_strconcat(purple_account_get_username(account), " (",
8071 				purple_account_get_protocol_name(account), ")", NULL);
8072 			menuitem = gtk_image_menu_item_new_with_label(buf);
8073 			g_free(buf);
8074 			pixbuf = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL);
8075 			if (pixbuf != NULL)
8076 			{
8077 				if (!purple_account_is_connected(account))
8078 					gdk_pixbuf_saturate_and_pixelate(pixbuf, pixbuf, 0.0, FALSE);
8079 				image = gtk_image_new_from_pixbuf(pixbuf);
8080 				g_object_unref(G_OBJECT(pixbuf));
8081 				gtk_widget_show(image);
8082 				gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image);
8083 			}
8084 			g_signal_connect(G_OBJECT(menuitem), "activate",
8085 				G_CALLBACK(enable_account_cb), account);
8086 			gtk_menu_shell_append(GTK_MENU_SHELL(submenu), menuitem);
8087 		} else {
8088 			enabled_accounts = TRUE;
8089 		}
8090 	}
8091 
8092 	if (!enabled_accounts) {
8093 		gtk_widget_show_all(accountmenu);
8094 		return;
8095 	}
8096 
8097 	pidgin_separator(accountmenu);
8098 	accel_group = gtk_menu_get_accel_group(GTK_MENU(accountmenu));
8099 
8100 	for (accounts = purple_accounts_get_all(); accounts; accounts = accounts->next) {
8101 		char *buf = NULL;
8102 		char *accel_path_buf = NULL;
8103 		GtkWidget *image = NULL;
8104 		PurpleConnection *gc = NULL;
8105 		PurpleAccount *account = NULL;
8106 		GdkPixbuf *pixbuf = NULL;
8107 		PurplePlugin *plugin = NULL;
8108 		PurplePluginProtocolInfo *prpl_info;
8109 
8110 		account = accounts->data;
8111 
8112 		if (!purple_account_get_enabled(account, PIDGIN_UI))
8113 			continue;
8114 
8115 		buf = g_strconcat(purple_account_get_username(account), " (",
8116 				purple_account_get_protocol_name(account), ")", NULL);
8117 		menuitem = gtk_image_menu_item_new_with_label(buf);
8118 		accel_path_buf = g_strconcat(N_("<PurpleMain>/Accounts/"), buf, NULL);
8119 		g_free(buf);
8120 		pixbuf = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL);
8121 		if (pixbuf != NULL) {
8122 			if (!purple_account_is_connected(account))
8123 				gdk_pixbuf_saturate_and_pixelate(pixbuf, pixbuf,
8124 						0.0, FALSE);
8125 			image = gtk_image_new_from_pixbuf(pixbuf);
8126 			g_object_unref(G_OBJECT(pixbuf));
8127 			gtk_widget_show(image);
8128 			gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image);
8129 		}
8130 		gtk_menu_shell_append(GTK_MENU_SHELL(accountmenu), menuitem);
8131 
8132 		submenu = gtk_menu_new();
8133 		gtk_menu_set_accel_group(GTK_MENU(submenu), accel_group);
8134 		gtk_menu_set_accel_path(GTK_MENU(submenu), accel_path_buf);
8135 		g_free(accel_path_buf);
8136 		gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
8137 
8138 
8139 		menuitem = gtk_menu_item_new_with_mnemonic(_("_Edit Account"));
8140 		g_signal_connect(G_OBJECT(menuitem), "activate",
8141 				G_CALLBACK(modify_account_cb), account);
8142 		gtk_menu_shell_append(GTK_MENU_SHELL(submenu), menuitem);
8143 
8144 		pidgin_separator(submenu);
8145 
8146 		gc = purple_account_get_connection(account);
8147 		plugin = gc && PURPLE_CONNECTION_IS_CONNECTED(gc) ? gc->prpl : NULL;
8148 		prpl_info = plugin ? PURPLE_PLUGIN_PROTOCOL_INFO(plugin) : NULL;
8149 
8150 		if (prpl_info &&
8151 		    (PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, get_moods) ||
8152 			 PURPLE_PLUGIN_HAS_ACTIONS(plugin))) {
8153 			if (PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, get_moods) &&
8154 			    gc->flags & PURPLE_CONNECTION_SUPPORT_MOODS) {
8155 
8156 				if (purple_account_get_status(account, "mood")) {
8157 					menuitem = gtk_menu_item_new_with_mnemonic(_("Set _Mood..."));
8158 					g_signal_connect(G_OBJECT(menuitem), "activate",
8159 					         	G_CALLBACK(set_mood_cb), account);
8160 					gtk_menu_shell_append(GTK_MENU_SHELL(submenu), menuitem);
8161 				}
8162 			}
8163 			if (PURPLE_PLUGIN_HAS_ACTIONS(plugin)) {
8164 				build_plugin_actions(submenu, plugin, gc);
8165 			}
8166 		} else {
8167 			menuitem = gtk_menu_item_new_with_label(_("No actions available"));
8168 			gtk_menu_shell_append(GTK_MENU_SHELL(submenu), menuitem);
8169 			gtk_widget_set_sensitive(menuitem, FALSE);
8170 		}
8171 
8172 		pidgin_separator(submenu);
8173 
8174 		menuitem = gtk_menu_item_new_with_mnemonic(_("_Disable"));
8175 		g_signal_connect(G_OBJECT(menuitem), "activate",
8176 				G_CALLBACK(disable_account_cb), account);
8177 		gtk_menu_shell_append(GTK_MENU_SHELL(submenu), menuitem);
8178 	}
8179 	gtk_widget_show_all(accountmenu);
8180 }
8181 
8182 static GList *plugin_submenus = NULL;
8183 
8184 void
pidgin_blist_update_plugin_actions(void)8185 pidgin_blist_update_plugin_actions(void)
8186 {
8187 	GtkWidget *menuitem, *submenu;
8188 	PurplePlugin *plugin = NULL;
8189 	GList *l;
8190 	GtkAccelGroup *accel_group;
8191 
8192 	GtkWidget *pluginmenu = gtk_item_factory_get_widget(gtkblist->ift, N_("/Tools"));
8193 
8194 	g_return_if_fail(pluginmenu != NULL);
8195 
8196 	/* Remove old plugin action submenus from the Tools menu */
8197 	for (l = plugin_submenus; l; l = l->next)
8198 		gtk_widget_destroy(GTK_WIDGET(l->data));
8199 	g_list_free(plugin_submenus);
8200 	plugin_submenus = NULL;
8201 
8202 	accel_group = gtk_menu_get_accel_group(GTK_MENU(pluginmenu));
8203 
8204 	/* Add a submenu for each plugin with custom actions */
8205 	for (l = purple_plugins_get_loaded(); l; l = l->next) {
8206 		char *path;
8207 
8208 		plugin = (PurplePlugin *) l->data;
8209 
8210 		if (PURPLE_IS_PROTOCOL_PLUGIN(plugin))
8211 			continue;
8212 
8213 		if (!PURPLE_PLUGIN_HAS_ACTIONS(plugin))
8214 			continue;
8215 
8216 		menuitem = gtk_image_menu_item_new_with_label(_(plugin->info->name));
8217 		gtk_menu_shell_append(GTK_MENU_SHELL(pluginmenu), menuitem);
8218 
8219 		plugin_submenus = g_list_append(plugin_submenus, menuitem);
8220 
8221 		submenu = gtk_menu_new();
8222 		gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
8223 
8224 		gtk_menu_set_accel_group(GTK_MENU(submenu), accel_group);
8225 		path = g_strdup_printf("%s/Tools/%s", gtkblist->ift->path, plugin->info->name);
8226 		gtk_menu_set_accel_path(GTK_MENU(submenu), path);
8227 		g_free(path);
8228 
8229 		build_plugin_actions(submenu, plugin, NULL);
8230 	}
8231 	gtk_widget_show_all(pluginmenu);
8232 }
8233 
8234 static void
sortmethod_act(GtkCheckMenuItem * checkmenuitem,char * id)8235 sortmethod_act(GtkCheckMenuItem *checkmenuitem, char *id)
8236 {
8237 	if (gtk_check_menu_item_get_active(checkmenuitem))
8238 	{
8239 		pidgin_set_cursor(gtkblist->window, GDK_WATCH);
8240 		/* This is redundant. I think. */
8241 		/* pidgin_blist_sort_method_set(id); */
8242 		purple_prefs_set_string(PIDGIN_PREFS_ROOT "/blist/sort_type", id);
8243 
8244 		pidgin_clear_cursor(gtkblist->window);
8245 	}
8246 }
8247 
8248 void
pidgin_blist_update_sort_methods(void)8249 pidgin_blist_update_sort_methods(void)
8250 {
8251 	GtkWidget *menuitem = NULL, *activeitem = NULL;
8252 	PidginBlistSortMethod *method = NULL;
8253 	GList *l;
8254 	GSList *sl = NULL;
8255 	GtkWidget *sortmenu;
8256 	const char *m = purple_prefs_get_string(PIDGIN_PREFS_ROOT "/blist/sort_type");
8257 
8258 	if ((gtkblist == NULL) || (gtkblist->ift == NULL))
8259 		return;
8260 
8261 	g_return_if_fail(m != NULL);
8262 
8263 	sortmenu = gtk_item_factory_get_widget(gtkblist->ift, N_("/Buddies/Sort Buddies"));
8264 
8265 	if (sortmenu == NULL)
8266 		return;
8267 
8268 	/* Clear the old menu */
8269 	for (l = gtk_container_get_children(GTK_CONTAINER(sortmenu)); l; l = g_list_delete_link(l, l)) {
8270 		menuitem = l->data;
8271 		gtk_widget_destroy(GTK_WIDGET(menuitem));
8272 	}
8273 
8274 	for (l = pidgin_blist_sort_methods; l; l = l->next) {
8275 		method = (PidginBlistSortMethod *) l->data;
8276 		menuitem = gtk_radio_menu_item_new_with_label(sl, _(method->name));
8277 		if (purple_strequal(m, method->id))
8278 			activeitem = menuitem;
8279 		sl = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(menuitem));
8280 		gtk_menu_shell_append(GTK_MENU_SHELL(sortmenu), menuitem);
8281 		g_signal_connect(G_OBJECT(menuitem), "toggled",
8282 				 G_CALLBACK(sortmethod_act), method->id);
8283 		gtk_widget_show(menuitem);
8284 	}
8285 	if (activeitem)
8286 		gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(activeitem), TRUE);
8287 }
8288