pidgin/gtkblist.c

branch
cpw.khc.msnp14
changeset 20481
65485e2ed8a3
parent 20472
6a6d2ef151e6
parent 20478
46933dc62880
child 20488
dcc6125edcd6
equal deleted inserted replaced
20480:df9df972434f 20481:65485e2ed8a3
1 /*
2 * @file gtkblist.c GTK+ BuddyList API
3 * @ingroup gtkui
4 *
5 * pidgin
6 *
7 * Pidgin is the legal property of its developers, whose names are too numerous
8 * to list here. Please refer to the COPYRIGHT file distributed with this
9 * source distribution.
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 *
25 */
26 #include "internal.h"
27 #include "pidgin.h"
28
29 #include "account.h"
30 #include "connection.h"
31 #include "core.h"
32 #include "debug.h"
33 #include "notify.h"
34 #include "prpl.h"
35 #include "prefs.h"
36 #include "plugin.h"
37 #include "request.h"
38 #include "signals.h"
39 #include "pidginstock.h"
40 #include "util.h"
41
42 #include "gtkaccount.h"
43 #include "gtkblist.h"
44 #include "gtkcellrendererexpander.h"
45 #include "gtkconv.h"
46 #include "gtkdebug.h"
47 #include "gtkdialogs.h"
48 #include "gtkft.h"
49 #include "gtklog.h"
50 #include "gtkmenutray.h"
51 #include "gtkpounce.h"
52 #include "gtkplugin.h"
53 #include "gtkprefs.h"
54 #include "gtkprivacy.h"
55 #include "gtkroomlist.h"
56 #include "gtkstatusbox.h"
57 #include "gtkscrollbook.h"
58 #include "gtkutils.h"
59
60 #include <gdk/gdkkeysyms.h>
61 #include <gtk/gtk.h>
62 #include <gdk/gdk.h>
63
64 #define HEADLINE_CLOSE_SIZE 12
65
66 typedef struct
67 {
68 PurpleAccount *account;
69
70 GtkWidget *window;
71 GtkWidget *combo;
72 GtkWidget *entry;
73 GtkWidget *entry_for_alias;
74 GtkWidget *account_box;
75
76 } PidginAddBuddyData;
77
78 typedef struct
79 {
80 PurpleAccount *account;
81 gchar *default_chat_name;
82
83 GtkWidget *window;
84 GtkWidget *account_menu;
85 GtkWidget *alias_entry;
86 GtkWidget *group_combo;
87 GtkWidget *entries_box;
88 GtkSizeGroup *sg;
89
90 GList *entries;
91
92 } PidginAddChatData;
93
94 typedef struct
95 {
96 PurpleAccount *account;
97
98 GtkWidget *window;
99 GtkWidget *account_menu;
100 GtkWidget *entries_box;
101 GtkSizeGroup *sg;
102
103 GList *entries;
104 } PidginJoinChatData;
105
106
107 static GtkWidget *accountmenu = NULL;
108
109 static guint visibility_manager_count = 0;
110 static gboolean gtk_blist_obscured = FALSE;
111
112 static GList *pidgin_blist_sort_methods = NULL;
113 static struct pidgin_blist_sort_method *current_sort_method = NULL;
114 static void sort_method_none(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter);
115
116 /* The functions we use for sorting aren't available in gtk 2.0.x, and
117 * segfault in 2.2.0. 2.2.1 is known to work, so I'll require that */
118 #if GTK_CHECK_VERSION(2,2,1)
119 static void sort_method_alphabetical(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter);
120 static void sort_method_status(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter);
121 static void sort_method_log(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter);
122 #endif
123 static PidginBuddyList *gtkblist = NULL;
124
125 static gboolean pidgin_blist_refresh_timer(PurpleBuddyList *list);
126 static void pidgin_blist_update_buddy(PurpleBuddyList *list, PurpleBlistNode *node, gboolean statusChange);
127 static void pidgin_blist_selection_changed(GtkTreeSelection *selection, gpointer data);
128 static void pidgin_blist_update(PurpleBuddyList *list, PurpleBlistNode *node);
129 static void pidgin_blist_update_group(PurpleBuddyList *list, PurpleBlistNode *node);
130 static void pidgin_blist_update_contact(PurpleBuddyList *list, PurpleBlistNode *node);
131 static char *pidgin_get_tooltip_text(PurpleBlistNode *node, gboolean full);
132 static const char *item_factory_translate_func (const char *path, gpointer func_data);
133 static gboolean get_iter_from_node(PurpleBlistNode *node, GtkTreeIter *iter);
134 static void redo_buddy_list(PurpleBuddyList *list, gboolean remove, gboolean rerender);
135 static void pidgin_blist_collapse_contact_cb(GtkWidget *w, PurpleBlistNode *node);
136 static char *pidgin_get_group_title(PurpleBlistNode *gnode, gboolean expanded);
137
138 static void pidgin_blist_tooltip_destroy(void);
139
140 struct _pidgin_blist_node {
141 GtkTreeRowReference *row;
142 gboolean contact_expanded;
143 gboolean recent_signonoff;
144 gint recent_signonoff_timer;
145 };
146
147 static char dim_grey_string[8] = "";
148 static char *dim_grey()
149 {
150 if (!gtkblist)
151 return "dim grey";
152 if (!dim_grey_string[0]) {
153 GtkStyle *style = gtk_widget_get_style(gtkblist->treeview);
154 snprintf(dim_grey_string, sizeof(dim_grey_string), "#%02x%02x%02x",
155 style->text_aa[GTK_STATE_NORMAL].red >> 8,
156 style->text_aa[GTK_STATE_NORMAL].green >> 8,
157 style->text_aa[GTK_STATE_NORMAL].blue >> 8);
158 }
159 return dim_grey_string;
160 }
161
162 /***************************************************
163 * Callbacks *
164 ***************************************************/
165 static gboolean gtk_blist_visibility_cb(GtkWidget *w, GdkEventVisibility *event, gpointer data)
166 {
167 if (event->state == GDK_VISIBILITY_FULLY_OBSCURED)
168 gtk_blist_obscured = TRUE;
169 else if (gtk_blist_obscured) {
170 gtk_blist_obscured = FALSE;
171 pidgin_blist_refresh_timer(purple_get_blist());
172 }
173
174 /* continue to handle event normally */
175 return FALSE;
176 }
177
178 static gboolean gtk_blist_window_state_cb(GtkWidget *w, GdkEventWindowState *event, gpointer data)
179 {
180 if(event->changed_mask & GDK_WINDOW_STATE_WITHDRAWN) {
181 if(event->new_window_state & GDK_WINDOW_STATE_WITHDRAWN)
182 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/list_visible", FALSE);
183 else {
184 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/list_visible", TRUE);
185 pidgin_blist_refresh_timer(purple_get_blist());
186 }
187 }
188
189 if(event->changed_mask & GDK_WINDOW_STATE_MAXIMIZED) {
190 if(event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED)
191 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/list_maximized", TRUE);
192 else
193 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/list_maximized", FALSE);
194 }
195
196 /* Refresh gtkblist if un-iconifying */
197 if (event->changed_mask & GDK_WINDOW_STATE_ICONIFIED){
198 if (!(event->new_window_state & GDK_WINDOW_STATE_ICONIFIED))
199 pidgin_blist_refresh_timer(purple_get_blist());
200 }
201
202 return FALSE;
203 }
204
205 static gboolean gtk_blist_delete_cb(GtkWidget *w, GdkEventAny *event, gpointer data)
206 {
207 if(visibility_manager_count)
208 purple_blist_set_visible(FALSE);
209 else
210 purple_core_quit();
211
212 /* we handle everything, event should not propogate further */
213 return TRUE;
214 }
215
216 static gboolean gtk_blist_configure_cb(GtkWidget *w, GdkEventConfigure *event, gpointer data)
217 {
218 /* unfortunately GdkEventConfigure ignores the window gravity, but *
219 * the only way we have of setting the position doesn't. we have to *
220 * call get_position because it does pay attention to the gravity. *
221 * this is inefficient and I agree it sucks, but it's more likely *
222 * to work correctly. - Robot101 */
223 gint x, y;
224
225 /* check for visibility because when we aren't visible, this will *
226 * give us bogus (0,0) coordinates. - xOr */
227 if (GTK_WIDGET_VISIBLE(w))
228 gtk_window_get_position(GTK_WINDOW(w), &x, &y);
229 else
230 return FALSE; /* carry on normally */
231
232 #ifdef _WIN32
233 /* Workaround for GTK+ bug # 169811 - "configure_event" is fired
234 * when the window is being maximized */
235 if (gdk_window_get_state(w->window)
236 & GDK_WINDOW_STATE_MAXIMIZED) {
237 return FALSE;
238 }
239 #endif
240
241 /* don't save if nothing changed */
242 if (x == purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/x") &&
243 y == purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/y") &&
244 event->width == purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/width") &&
245 event->height == purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/height")) {
246
247 return FALSE; /* carry on normally */
248 }
249
250 /* don't save off-screen positioning */
251 if (x + event->width < 0 ||
252 y + event->height < 0 ||
253 x > gdk_screen_width() ||
254 y > gdk_screen_height()) {
255
256 return FALSE; /* carry on normally */
257 }
258
259 /* ignore changes when maximized */
260 if(purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/list_maximized"))
261 return FALSE;
262
263 /* store the position */
264 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/blist/x", x);
265 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/blist/y", y);
266 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/blist/width", event->width);
267 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/blist/height", event->height);
268
269 gtk_widget_set_size_request(gtkblist->headline_label,
270 purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/width")-25,-1);
271 /* continue to handle event normally */
272 return FALSE;
273 }
274
275 static void gtk_blist_menu_info_cb(GtkWidget *w, PurpleBuddy *b)
276 {
277 serv_get_info(b->account->gc, b->name);
278 }
279
280 static void gtk_blist_menu_im_cb(GtkWidget *w, PurpleBuddy *b)
281 {
282 pidgindialogs_im_with_user(b->account, b->name);
283 }
284
285 static void gtk_blist_menu_send_file_cb(GtkWidget *w, PurpleBuddy *b)
286 {
287 serv_send_file(b->account->gc, b->name, NULL);
288 }
289
290 static void gtk_blist_menu_autojoin_cb(GtkWidget *w, PurpleChat *chat)
291 {
292 purple_blist_node_set_bool((PurpleBlistNode*)chat, "gtk-autojoin",
293 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w)));
294 }
295
296 static void gtk_blist_join_chat(PurpleChat *chat)
297 {
298 PurpleConversation *conv;
299
300 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
301 purple_chat_get_name(chat),
302 chat->account);
303
304 if (conv != NULL)
305 purple_conversation_present(conv);
306
307 serv_join_chat(chat->account->gc, chat->components);
308 }
309
310 static void gtk_blist_menu_join_cb(GtkWidget *w, PurpleChat *chat)
311 {
312 gtk_blist_join_chat(chat);
313 }
314
315 static void gtk_blist_renderer_edited_cb(GtkCellRendererText *text_rend, char *arg1,
316 char *arg2, gpointer nada)
317 {
318 GtkTreeIter iter;
319 GtkTreePath *path;
320 GValue val;
321 PurpleBlistNode *node;
322 PurpleGroup *dest;
323
324 path = gtk_tree_path_new_from_string (arg1);
325 gtk_tree_model_get_iter (GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
326 gtk_tree_path_free (path);
327 val.g_type = 0;
328 gtk_tree_model_get_value (GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &val);
329 node = g_value_get_pointer(&val);
330 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(gtkblist->treeview), TRUE);
331 g_object_set(G_OBJECT(gtkblist->text_rend), "editable", FALSE, NULL);
332
333 switch (node->type)
334 {
335 case PURPLE_BLIST_CONTACT_NODE:
336 {
337 PurpleContact *contact = (PurpleContact *)node;
338 struct _pidgin_blist_node *gtknode = (struct _pidgin_blist_node *)node->ui_data;
339
340 if (contact->alias || gtknode->contact_expanded)
341 purple_blist_alias_contact(contact, arg2);
342 else
343 {
344 PurpleBuddy *buddy = purple_contact_get_priority_buddy(contact);
345 purple_blist_alias_buddy(buddy, arg2);
346 serv_alias_buddy(buddy);
347 }
348 }
349 break;
350
351 case PURPLE_BLIST_BUDDY_NODE:
352 purple_blist_alias_buddy((PurpleBuddy*)node, arg2);
353 serv_alias_buddy((PurpleBuddy *)node);
354 break;
355 case PURPLE_BLIST_GROUP_NODE:
356 dest = purple_find_group(arg2);
357 if (dest != NULL && strcmp(arg2, ((PurpleGroup*) node)->name)) {
358 pidgindialogs_merge_groups((PurpleGroup*) node, arg2);
359 } else
360 purple_blist_rename_group((PurpleGroup*)node, arg2);
361 break;
362 case PURPLE_BLIST_CHAT_NODE:
363 purple_blist_alias_chat((PurpleChat*)node, arg2);
364 break;
365 default:
366 break;
367 }
368 }
369
370 static void gtk_blist_menu_alias_cb(GtkWidget *w, PurpleBlistNode *node)
371 {
372 GtkTreeIter iter;
373 GtkTreePath *path;
374 const char *text = NULL;
375 char *esc;
376
377 if (!(get_iter_from_node(node, &iter))) {
378 /* This is either a bug, or the buddy is in a collapsed contact */
379 node = node->parent;
380 if (!get_iter_from_node(node, &iter))
381 /* Now it's definitely a bug */
382 return;
383 }
384
385 switch (node->type) {
386 case PURPLE_BLIST_BUDDY_NODE:
387 text = purple_buddy_get_alias((PurpleBuddy *)node);
388 break;
389 case PURPLE_BLIST_CONTACT_NODE:
390 text = purple_contact_get_alias((PurpleContact *)node);
391 break;
392 case PURPLE_BLIST_GROUP_NODE:
393 text = ((PurpleGroup *)node)->name;
394 break;
395 case PURPLE_BLIST_CHAT_NODE:
396 text = purple_chat_get_name((PurpleChat *)node);
397 break;
398 default:
399 g_return_if_reached();
400 }
401
402 esc = g_markup_escape_text(text, -1);
403 gtk_tree_store_set(gtkblist->treemodel, &iter, NAME_COLUMN, esc, -1);
404 g_free(esc);
405
406 path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &iter);
407 g_object_set(G_OBJECT(gtkblist->text_rend), "editable", TRUE, NULL);
408 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(gtkblist->treeview), FALSE);
409 gtk_widget_grab_focus(gtkblist->treeview);
410 #if GTK_CHECK_VERSION(2,2,0)
411 gtk_tree_view_set_cursor_on_cell(GTK_TREE_VIEW(gtkblist->treeview), path,
412 gtkblist->text_column, gtkblist->text_rend, TRUE);
413 #else
414 gtk_tree_view_set_cursor(GTK_TREE_VIEW(gtkblist->treeview), path, gtkblist->text_column, TRUE);
415 #endif
416 gtk_tree_path_free(path);
417 }
418
419 static void gtk_blist_menu_bp_cb(GtkWidget *w, PurpleBuddy *b)
420 {
421 pidgin_pounce_editor_show(b->account, b->name, NULL);
422 }
423
424 static void gtk_blist_menu_showlog_cb(GtkWidget *w, PurpleBlistNode *node)
425 {
426 PurpleLogType type;
427 PurpleAccount *account;
428 char *name = NULL;
429
430 pidgin_set_cursor(gtkblist->window, GDK_WATCH);
431
432 if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
433 PurpleBuddy *b = (PurpleBuddy*) node;
434 type = PURPLE_LOG_IM;
435 name = g_strdup(b->name);
436 account = b->account;
437 } else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
438 PurpleChat *c = (PurpleChat*) node;
439 PurplePluginProtocolInfo *prpl_info = NULL;
440 type = PURPLE_LOG_CHAT;
441 account = c->account;
442 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_find_prpl(purple_account_get_protocol_id(account)));
443 if (prpl_info && prpl_info->get_chat_name) {
444 name = prpl_info->get_chat_name(c->components);
445 }
446 } else if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
447 pidgin_log_show_contact((PurpleContact *)node);
448 pidgin_clear_cursor(gtkblist->window);
449 return;
450 } else {
451 pidgin_clear_cursor(gtkblist->window);
452
453 /* This callback should not have been registered for a node
454 * that doesn't match the type of one of the blocks above. */
455 g_return_if_reached();
456 }
457
458 if (name && account) {
459 pidgin_log_show(type, name, account);
460 g_free(name);
461
462 pidgin_clear_cursor(gtkblist->window);
463 }
464 }
465
466 static void gtk_blist_show_systemlog_cb()
467 {
468 pidgin_syslog_show();
469 }
470
471 static void gtk_blist_show_onlinehelp_cb()
472 {
473 purple_notify_uri(NULL, PURPLE_WEBSITE "documentation.php");
474 }
475
476 static void
477 do_join_chat(PidginJoinChatData *data)
478 {
479 if (data)
480 {
481 GHashTable *components =
482 g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
483 GList *tmp;
484 PurpleChat *chat;
485
486 for (tmp = data->entries; tmp != NULL; tmp = tmp->next)
487 {
488 if (g_object_get_data(tmp->data, "is_spin"))
489 {
490 g_hash_table_replace(components,
491 g_strdup(g_object_get_data(tmp->data, "identifier")),
492 g_strdup_printf("%d",
493 gtk_spin_button_get_value_as_int(tmp->data)));
494 }
495 else
496 {
497 g_hash_table_replace(components,
498 g_strdup(g_object_get_data(tmp->data, "identifier")),
499 g_strdup(gtk_entry_get_text(tmp->data)));
500 }
501 }
502
503 chat = purple_chat_new(data->account, NULL, components);
504 gtk_blist_join_chat(chat);
505 purple_blist_remove_chat(chat);
506 }
507 }
508
509 static void
510 do_joinchat(GtkWidget *dialog, int id, PidginJoinChatData *info)
511 {
512 switch(id)
513 {
514 case GTK_RESPONSE_OK:
515 do_join_chat(info);
516
517 break;
518 }
519
520 gtk_widget_destroy(GTK_WIDGET(dialog));
521 g_list_free(info->entries);
522 g_free(info);
523 }
524
525 /*
526 * Check the values of all the text entry boxes. If any required input
527 * strings are empty then don't allow the user to click on "OK."
528 */
529 static void
530 joinchat_set_sensitive_if_input_cb(GtkWidget *entry, gpointer user_data)
531 {
532 PidginJoinChatData *data;
533 GList *tmp;
534 const char *text;
535 gboolean required;
536 gboolean sensitive = TRUE;
537
538 data = user_data;
539
540 for (tmp = data->entries; tmp != NULL; tmp = tmp->next)
541 {
542 if (!g_object_get_data(tmp->data, "is_spin"))
543 {
544 required = GPOINTER_TO_INT(g_object_get_data(tmp->data, "required"));
545 text = gtk_entry_get_text(tmp->data);
546 if (required && (*text == '\0'))
547 sensitive = FALSE;
548 }
549 }
550
551 gtk_dialog_set_response_sensitive(GTK_DIALOG(data->window), GTK_RESPONSE_OK, sensitive);
552 }
553
554 static void
555 pidgin_blist_update_privacy_cb(PurpleBuddy *buddy)
556 {
557 pidgin_blist_update_buddy(purple_get_blist(), (PurpleBlistNode*)(buddy), TRUE);
558 }
559
560 static void
561 rebuild_joinchat_entries(PidginJoinChatData *data)
562 {
563 PurpleConnection *gc;
564 GList *list = NULL, *tmp;
565 GHashTable *defaults = NULL;
566 struct proto_chat_entry *pce;
567 gboolean focus = TRUE;
568
569 g_return_if_fail(data->account != NULL);
570
571 gc = purple_account_get_connection(data->account);
572
573 while ((tmp = gtk_container_get_children(GTK_CONTAINER(data->entries_box))))
574 gtk_widget_destroy(tmp->data);
575
576 g_list_free(data->entries);
577 data->entries = NULL;
578
579 if (PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info != NULL)
580 list = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info(gc);
581
582 if (PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info_defaults != NULL)
583 defaults = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info_defaults(gc, NULL);
584
585 for (tmp = list; tmp; tmp = tmp->next)
586 {
587 GtkWidget *label;
588 GtkWidget *rowbox;
589 GtkWidget *input;
590
591 pce = tmp->data;
592
593 rowbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BORDER);
594 gtk_box_pack_start(GTK_BOX(data->entries_box), rowbox, FALSE, FALSE, 0);
595
596 label = gtk_label_new_with_mnemonic(pce->label);
597 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
598 gtk_size_group_add_widget(data->sg, label);
599 gtk_box_pack_start(GTK_BOX(rowbox), label, FALSE, FALSE, 0);
600
601 if (pce->is_int)
602 {
603 GtkObject *adjust;
604 adjust = gtk_adjustment_new(pce->min, pce->min, pce->max,
605 1, 10, 10);
606 input = gtk_spin_button_new(GTK_ADJUSTMENT(adjust), 1, 0);
607 gtk_widget_set_size_request(input, 50, -1);
608 gtk_box_pack_end(GTK_BOX(rowbox), input, FALSE, FALSE, 0);
609 }
610 else
611 {
612 char *value;
613 input = gtk_entry_new();
614 gtk_entry_set_activates_default(GTK_ENTRY(input), TRUE);
615 value = g_hash_table_lookup(defaults, pce->identifier);
616 if (value != NULL)
617 gtk_entry_set_text(GTK_ENTRY(input), value);
618 if (pce->secret)
619 {
620 gtk_entry_set_visibility(GTK_ENTRY(input), FALSE);
621 if (gtk_entry_get_invisible_char(GTK_ENTRY(input)) == '*')
622 gtk_entry_set_invisible_char(GTK_ENTRY(input), PIDGIN_INVISIBLE_CHAR);
623 }
624 gtk_box_pack_end(GTK_BOX(rowbox), input, TRUE, TRUE, 0);
625 g_signal_connect(G_OBJECT(input), "changed",
626 G_CALLBACK(joinchat_set_sensitive_if_input_cb), data);
627 }
628
629 /* Do the following for any type of input widget */
630 if (focus)
631 {
632 gtk_widget_grab_focus(input);
633 focus = FALSE;
634 }
635 gtk_label_set_mnemonic_widget(GTK_LABEL(label), input);
636 pidgin_set_accessible_label(input, label);
637 g_object_set_data(G_OBJECT(input), "identifier", (gpointer)pce->identifier);
638 g_object_set_data(G_OBJECT(input), "is_spin", GINT_TO_POINTER(pce->is_int));
639 g_object_set_data(G_OBJECT(input), "required", GINT_TO_POINTER(pce->required));
640 data->entries = g_list_append(data->entries, input);
641
642 g_free(pce);
643 }
644
645 g_list_free(list);
646 g_hash_table_destroy(defaults);
647
648 /* Set whether the "OK" button should be clickable initially */
649 joinchat_set_sensitive_if_input_cb(NULL, data);
650
651 gtk_widget_show_all(data->entries_box);
652 }
653
654 static void
655 joinchat_select_account_cb(GObject *w, PurpleAccount *account,
656 PidginJoinChatData *data)
657 {
658 data->account = account;
659 rebuild_joinchat_entries(data);
660 }
661
662 static gboolean
663 chat_account_filter_func(PurpleAccount *account)
664 {
665 PurpleConnection *gc = purple_account_get_connection(account);
666 PurplePluginProtocolInfo *prpl_info = NULL;
667
668 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
669
670 return (prpl_info->chat_info != NULL);
671 }
672
673 gboolean
674 pidgin_blist_joinchat_is_showable()
675 {
676 GList *c;
677 PurpleConnection *gc;
678
679 for (c = purple_connections_get_all(); c != NULL; c = c->next) {
680 gc = c->data;
681
682 if (chat_account_filter_func(purple_connection_get_account(gc)))
683 return TRUE;
684 }
685
686 return FALSE;
687 }
688
689 void
690 pidgin_blist_joinchat_show(void)
691 {
692 GtkWidget *hbox, *vbox;
693 GtkWidget *rowbox;
694 GtkWidget *label;
695 PidginBuddyList *gtkblist;
696 GtkWidget *img = NULL;
697 PidginJoinChatData *data = NULL;
698
699 gtkblist = PIDGIN_BLIST(purple_get_blist());
700 img = gtk_image_new_from_stock(PIDGIN_STOCK_DIALOG_QUESTION,
701 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_HUGE));
702 data = g_new0(PidginJoinChatData, 1);
703
704 data->window = gtk_dialog_new_with_buttons(_("Join a Chat"),
705 NULL, GTK_DIALOG_NO_SEPARATOR,
706 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
707 PIDGIN_STOCK_CHAT, GTK_RESPONSE_OK, NULL);
708 gtk_dialog_set_default_response(GTK_DIALOG(data->window), GTK_RESPONSE_OK);
709 gtk_container_set_border_width(GTK_CONTAINER(data->window), PIDGIN_HIG_BOX_SPACE);
710 gtk_window_set_resizable(GTK_WINDOW(data->window), FALSE);
711 gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(data->window)->vbox), PIDGIN_HIG_BORDER);
712 gtk_container_set_border_width(
713 GTK_CONTAINER(GTK_DIALOG(data->window)->vbox), PIDGIN_HIG_BOX_SPACE);
714 gtk_window_set_role(GTK_WINDOW(data->window), "join_chat");
715
716 hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BORDER);
717 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(data->window)->vbox), hbox);
718 gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0);
719 gtk_misc_set_alignment(GTK_MISC(img), 0, 0);
720
721 vbox = gtk_vbox_new(FALSE, 5);
722 gtk_container_set_border_width(GTK_CONTAINER(vbox), 0);
723 gtk_container_add(GTK_CONTAINER(hbox), vbox);
724
725 label = gtk_label_new(_("Please enter the appropriate information "
726 "about the chat you would like to join.\n"));
727 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
728 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
729 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
730
731 rowbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BORDER);
732 gtk_box_pack_start(GTK_BOX(vbox), rowbox, TRUE, TRUE, 0);
733
734 data->sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
735
736 label = gtk_label_new_with_mnemonic(_("_Account:"));
737 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
738 gtk_box_pack_start(GTK_BOX(rowbox), label, FALSE, FALSE, 0);
739 gtk_size_group_add_widget(data->sg, label);
740
741 data->account_menu = pidgin_account_option_menu_new(NULL, FALSE,
742 G_CALLBACK(joinchat_select_account_cb),
743 chat_account_filter_func, data);
744 gtk_box_pack_start(GTK_BOX(rowbox), data->account_menu, TRUE, TRUE, 0);
745 gtk_label_set_mnemonic_widget(GTK_LABEL(label),
746 GTK_WIDGET(data->account_menu));
747 pidgin_set_accessible_label (data->account_menu, label);
748
749 data->entries_box = gtk_vbox_new(FALSE, 5);
750 gtk_container_add(GTK_CONTAINER(vbox), data->entries_box);
751 gtk_container_set_border_width(GTK_CONTAINER(data->entries_box), 0);
752
753 data->account = pidgin_account_option_menu_get_selected(data->account_menu);
754
755 rebuild_joinchat_entries(data);
756
757 g_signal_connect(G_OBJECT(data->window), "response",
758 G_CALLBACK(do_joinchat), data);
759
760 g_object_unref(data->sg);
761
762 gtk_widget_show_all(data->window);
763 }
764
765 static void gtk_blist_row_expanded_cb(GtkTreeView *tv, GtkTreeIter *iter, GtkTreePath *path, gpointer user_data) {
766 PurpleBlistNode *node;
767 GValue val;
768
769 val.g_type = 0;
770 gtk_tree_model_get_value(GTK_TREE_MODEL(gtkblist->treemodel), iter, NODE_COLUMN, &val);
771
772 node = g_value_get_pointer(&val);
773
774 if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
775 char *title;
776
777 title = pidgin_get_group_title(node, TRUE);
778
779 gtk_tree_store_set(gtkblist->treemodel, iter,
780 NAME_COLUMN, title,
781 -1);
782
783 g_free(title);
784
785 purple_blist_node_set_bool(node, "collapsed", FALSE);
786 }
787 }
788
789 static void gtk_blist_row_collapsed_cb(GtkTreeView *tv, GtkTreeIter *iter, GtkTreePath *path, gpointer user_data) {
790 PurpleBlistNode *node;
791 GValue val;
792
793 val.g_type = 0;
794 gtk_tree_model_get_value(GTK_TREE_MODEL(gtkblist->treemodel), iter, NODE_COLUMN, &val);
795
796 node = g_value_get_pointer(&val);
797
798 if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
799 char *title;
800
801 title = pidgin_get_group_title(node, FALSE);
802
803 gtk_tree_store_set(gtkblist->treemodel, iter,
804 NAME_COLUMN, title,
805 -1);
806
807 g_free(title);
808
809 purple_blist_node_set_bool(node, "collapsed", TRUE);
810 } else if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
811 pidgin_blist_collapse_contact_cb(NULL, node);
812 }
813 }
814
815 static void gtk_blist_row_activated_cb(GtkTreeView *tv, GtkTreePath *path, GtkTreeViewColumn *col, gpointer data) {
816 PurpleBlistNode *node;
817 GtkTreeIter iter;
818 GValue val;
819
820 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
821
822 val.g_type = 0;
823 gtk_tree_model_get_value (GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &val);
824 node = g_value_get_pointer(&val);
825
826 if(PURPLE_BLIST_NODE_IS_CONTACT(node) || PURPLE_BLIST_NODE_IS_BUDDY(node)) {
827 PurpleBuddy *buddy;
828
829 if(PURPLE_BLIST_NODE_IS_CONTACT(node))
830 buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
831 else
832 buddy = (PurpleBuddy*)node;
833
834 pidgindialogs_im_with_user(buddy->account, buddy->name);
835 } else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
836 gtk_blist_join_chat((PurpleChat *)node);
837 } else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
838 /* if (gtk_tree_view_row_expanded(tv, path))
839 gtk_tree_view_collapse_row(tv, path);
840 else
841 gtk_tree_view_expand_row(tv,path,FALSE);*/
842 }
843 }
844
845 static void pidgin_blist_add_chat_cb()
846 {
847 GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist->treeview));
848 GtkTreeIter iter;
849 PurpleBlistNode *node;
850
851 if(gtk_tree_selection_get_selected(sel, NULL, &iter)){
852 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
853 if (PURPLE_BLIST_NODE_IS_BUDDY(node))
854 purple_blist_request_add_chat(NULL, (PurpleGroup*)node->parent->parent, NULL, NULL);
855 if (PURPLE_BLIST_NODE_IS_CONTACT(node) || PURPLE_BLIST_NODE_IS_CHAT(node))
856 purple_blist_request_add_chat(NULL, (PurpleGroup*)node->parent, NULL, NULL);
857 else if (PURPLE_BLIST_NODE_IS_GROUP(node))
858 purple_blist_request_add_chat(NULL, (PurpleGroup*)node, NULL, NULL);
859 }
860 else {
861 purple_blist_request_add_chat(NULL, NULL, NULL, NULL);
862 }
863 }
864
865 static void pidgin_blist_add_buddy_cb()
866 {
867 GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist->treeview));
868 GtkTreeIter iter;
869 PurpleBlistNode *node;
870
871 if(gtk_tree_selection_get_selected(sel, NULL, &iter)){
872 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
873 if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
874 purple_blist_request_add_buddy(NULL, NULL, ((PurpleGroup*)node->parent->parent)->name,
875 NULL);
876 } else if (PURPLE_BLIST_NODE_IS_CONTACT(node)
877 || PURPLE_BLIST_NODE_IS_CHAT(node)) {
878 purple_blist_request_add_buddy(NULL, NULL, ((PurpleGroup*)node->parent)->name, NULL);
879 } else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
880 purple_blist_request_add_buddy(NULL, NULL, ((PurpleGroup*)node)->name, NULL);
881 }
882 }
883 else {
884 purple_blist_request_add_buddy(NULL, NULL, NULL, NULL);
885 }
886 }
887
888 static void
889 pidgin_blist_remove_cb (GtkWidget *w, PurpleBlistNode *node)
890 {
891 if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
892 pidgindialogs_remove_buddy((PurpleBuddy*)node);
893 } else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
894 pidgindialogs_remove_chat((PurpleChat*)node);
895 } else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
896 pidgindialogs_remove_group((PurpleGroup*)node);
897 } else if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
898 pidgindialogs_remove_contact((PurpleContact*)node);
899 }
900 }
901
902 struct _expand {
903 GtkTreeView *treeview;
904 GtkTreePath *path;
905 PurpleBlistNode *node;
906 };
907
908 static gboolean
909 scroll_to_expanded_cell(gpointer data)
910 {
911 struct _expand *ex = data;
912 gtk_tree_view_scroll_to_cell(ex->treeview, ex->path, NULL, FALSE, 0, 0);
913 pidgin_blist_update_contact(NULL, ex->node);
914
915 gtk_tree_path_free(ex->path);
916 g_free(ex);
917
918 return FALSE;
919 }
920
921 static void
922 pidgin_blist_expand_contact_cb(GtkWidget *w, PurpleBlistNode *node)
923 {
924 struct _pidgin_blist_node *gtknode;
925 GtkTreeIter iter, parent;
926 PurpleBlistNode *bnode;
927 GtkTreePath *path;
928
929 if(!PURPLE_BLIST_NODE_IS_CONTACT(node))
930 return;
931
932 gtknode = (struct _pidgin_blist_node *)node->ui_data;
933
934 gtknode->contact_expanded = TRUE;
935
936 for(bnode = node->child; bnode; bnode = bnode->next) {
937 pidgin_blist_update(NULL, bnode);
938 }
939
940 /* This ensures that the bottom buddy is visible, i.e. not scrolled off the alignment */
941 if (get_iter_from_node(node, &parent)) {
942 struct _expand *ex = g_new0(struct _expand, 1);
943
944 gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(gtkblist->treemodel), &iter, &parent,
945 gtk_tree_model_iter_n_children(GTK_TREE_MODEL(gtkblist->treemodel), &parent) -1);
946 path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &iter);
947
948 /* Let the treeview draw so it knows where to scroll */
949 ex->treeview = GTK_TREE_VIEW(gtkblist->treeview);
950 ex->path = path;
951 ex->node = node->child;
952 g_idle_add(scroll_to_expanded_cell, ex);
953 }
954 }
955
956 static void
957 pidgin_blist_collapse_contact_cb(GtkWidget *w, PurpleBlistNode *node)
958 {
959 PurpleBlistNode *bnode;
960 struct _pidgin_blist_node *gtknode;
961
962 if(!PURPLE_BLIST_NODE_IS_CONTACT(node))
963 return;
964
965 gtknode = (struct _pidgin_blist_node *)node->ui_data;
966
967 gtknode->contact_expanded = FALSE;
968
969 for(bnode = node->child; bnode; bnode = bnode->next) {
970 pidgin_blist_update(NULL, bnode);
971 }
972 }
973
974 static void
975 toggle_privacy(GtkWidget *widget, PurpleBlistNode *node)
976 {
977 PurpleBuddy *buddy;
978 PurpleAccount *account;
979 gboolean permitted;
980 const char *name;
981
982 if (!PURPLE_BLIST_NODE_IS_BUDDY(node))
983 return;
984
985 buddy = (PurpleBuddy *)node;
986 account = purple_buddy_get_account(buddy);
987 name = purple_buddy_get_name(buddy);
988
989 permitted = purple_privacy_check(account, name);
990
991 /* XXX: Perhaps ask whether to restore the previous lists where appropirate? */
992
993 if (permitted)
994 purple_privacy_deny(account, name, FALSE, FALSE);
995 else
996 purple_privacy_allow(account, name, FALSE, FALSE);
997
998 pidgin_blist_update(purple_get_blist(), node);
999 }
1000
1001 void pidgin_append_blist_node_privacy_menu(GtkWidget *menu, PurpleBlistNode *node)
1002 {
1003 PurpleBuddy *buddy = (PurpleBuddy *)node;
1004 PurpleAccount *account;
1005 gboolean permitted;
1006
1007 account = purple_buddy_get_account(buddy);
1008 permitted = purple_privacy_check(account, purple_buddy_get_name(buddy));
1009
1010 pidgin_new_item_from_stock(menu, permitted ? _("_Block") : _("Un_block"),
1011 permitted ? PIDGIN_STOCK_TOOLBAR_BLOCK : PIDGIN_STOCK_TOOLBAR_UNBLOCK, G_CALLBACK(toggle_privacy),
1012 node, 0 ,0, NULL);
1013 }
1014
1015 void
1016 pidgin_append_blist_node_proto_menu(GtkWidget *menu, PurpleConnection *gc,
1017 PurpleBlistNode *node)
1018 {
1019 GList *l, *ll;
1020 PurplePluginProtocolInfo *prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
1021
1022 if(!prpl_info || !prpl_info->blist_node_menu)
1023 return;
1024
1025 for(l = ll = prpl_info->blist_node_menu(node); l; l = l->next) {
1026 PurpleMenuAction *act = (PurpleMenuAction *) l->data;
1027 pidgin_append_menu_action(menu, act, node);
1028 }
1029 g_list_free(ll);
1030 }
1031
1032 void
1033 pidgin_append_blist_node_extended_menu(GtkWidget *menu, PurpleBlistNode *node)
1034 {
1035 GList *l, *ll;
1036
1037 for(l = ll = purple_blist_node_get_extended_menu(node); l; l = l->next) {
1038 PurpleMenuAction *act = (PurpleMenuAction *) l->data;
1039 pidgin_append_menu_action(menu, act, node);
1040 }
1041 g_list_free(ll);
1042 }
1043
1044 void
1045 pidgin_blist_make_buddy_menu(GtkWidget *menu, PurpleBuddy *buddy, gboolean sub) {
1046 PurplePluginProtocolInfo *prpl_info;
1047 PurpleContact *contact;
1048 gboolean contact_expanded = FALSE;
1049
1050 g_return_if_fail(menu);
1051 g_return_if_fail(buddy);
1052
1053 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(buddy->account->gc->prpl);
1054
1055 contact = purple_buddy_get_contact(buddy);
1056 if (contact) {
1057 contact_expanded = ((struct _pidgin_blist_node *)(((PurpleBlistNode*)contact)->ui_data))->contact_expanded;
1058 }
1059
1060 if (prpl_info && prpl_info->get_info) {
1061 pidgin_new_item_from_stock(menu, _("Get _Info"), PIDGIN_STOCK_TOOLBAR_USER_INFO,
1062 G_CALLBACK(gtk_blist_menu_info_cb), buddy, 0, 0, NULL);
1063 }
1064 pidgin_new_item_from_stock(menu, _("I_M"), PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW,
1065 G_CALLBACK(gtk_blist_menu_im_cb), buddy, 0, 0, NULL);
1066 if (prpl_info && prpl_info->send_file) {
1067 if (!prpl_info->can_receive_file ||
1068 prpl_info->can_receive_file(buddy->account->gc, buddy->name))
1069 {
1070 pidgin_new_item_from_stock(menu, _("_Send File"),
1071 PIDGIN_STOCK_FILE_TRANSFER,
1072 G_CALLBACK(gtk_blist_menu_send_file_cb),
1073 buddy, 0, 0, NULL);
1074 }
1075 }
1076
1077 pidgin_new_item_from_stock(menu, _("Add Buddy _Pounce"), NULL,
1078 G_CALLBACK(gtk_blist_menu_bp_cb), buddy, 0, 0, NULL);
1079
1080 if(((PurpleBlistNode*)buddy)->parent->child->next && !sub && !contact_expanded) {
1081 pidgin_new_item_from_stock(menu, _("View _Log"), NULL,
1082 G_CALLBACK(gtk_blist_menu_showlog_cb),
1083 contact, 0, 0, NULL);
1084 } else if (!sub) {
1085 pidgin_new_item_from_stock(menu, _("View _Log"), NULL,
1086 G_CALLBACK(gtk_blist_menu_showlog_cb), buddy, 0, 0, NULL);
1087 }
1088
1089
1090 pidgin_append_blist_node_proto_menu(menu, buddy->account->gc,
1091 (PurpleBlistNode *)buddy);
1092 pidgin_append_blist_node_extended_menu(menu, (PurpleBlistNode *)buddy);
1093
1094 if (((PurpleBlistNode*)buddy)->parent->child->next && !sub && !contact_expanded) {
1095 pidgin_separator(menu);
1096 pidgin_append_blist_node_privacy_menu(menu, (PurpleBlistNode *)buddy);
1097 pidgin_new_item_from_stock(menu, _("Alias..."), PIDGIN_STOCK_ALIAS,
1098 G_CALLBACK(gtk_blist_menu_alias_cb),
1099 contact, 0, 0, NULL);
1100 pidgin_new_item_from_stock(menu, _("Remove"), GTK_STOCK_REMOVE,
1101 G_CALLBACK(pidgin_blist_remove_cb),
1102 contact, 0, 0, NULL);
1103 } else if (!sub || contact_expanded) {
1104 pidgin_separator(menu);
1105 pidgin_append_blist_node_privacy_menu(menu, (PurpleBlistNode *)buddy);
1106 pidgin_new_item_from_stock(menu, _("_Alias..."), PIDGIN_STOCK_ALIAS,
1107 G_CALLBACK(gtk_blist_menu_alias_cb), buddy, 0, 0, NULL);
1108 pidgin_new_item_from_stock(menu, _("_Remove"), GTK_STOCK_REMOVE,
1109 G_CALLBACK(pidgin_blist_remove_cb), buddy,
1110 0, 0, NULL);
1111 }
1112 }
1113
1114 static gboolean
1115 gtk_blist_key_press_cb(GtkWidget *tv, GdkEventKey *event, gpointer data) {
1116 PurpleBlistNode *node;
1117 GValue val;
1118 GtkTreeIter iter;
1119 GtkTreeSelection *sel;
1120
1121 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv));
1122 if(!gtk_tree_selection_get_selected(sel, NULL, &iter))
1123 return FALSE;
1124
1125 val.g_type = 0;
1126 gtk_tree_model_get_value(GTK_TREE_MODEL(gtkblist->treemodel), &iter,
1127 NODE_COLUMN, &val);
1128 node = g_value_get_pointer(&val);
1129
1130 if(event->state & GDK_CONTROL_MASK &&
1131 (event->keyval == 'o' || event->keyval == 'O')) {
1132 PurpleBuddy *buddy;
1133
1134 if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
1135 buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
1136 } else if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1137 buddy = (PurpleBuddy*)node;
1138 } else {
1139 return FALSE;
1140 }
1141 if(buddy)
1142 serv_get_info(buddy->account->gc, buddy->name);
1143 }
1144
1145 return FALSE;
1146 }
1147
1148 static GtkWidget *
1149 create_group_menu (PurpleBlistNode *node, PurpleGroup *g)
1150 {
1151 GtkWidget *menu;
1152 GtkWidget *item;
1153
1154 menu = gtk_menu_new();
1155 pidgin_new_item_from_stock(menu, _("Add a _Buddy"), GTK_STOCK_ADD,
1156 G_CALLBACK(pidgin_blist_add_buddy_cb), node, 0, 0, NULL);
1157 item = pidgin_new_item_from_stock(menu, _("Add a C_hat"), GTK_STOCK_ADD,
1158 G_CALLBACK(pidgin_blist_add_chat_cb), node, 0, 0, NULL);
1159 gtk_widget_set_sensitive(item, pidgin_blist_joinchat_is_showable());
1160 pidgin_new_item_from_stock(menu, _("_Delete Group"), GTK_STOCK_REMOVE,
1161 G_CALLBACK(pidgin_blist_remove_cb), node, 0, 0, NULL);
1162 pidgin_new_item_from_stock(menu, _("_Rename"), NULL,
1163 G_CALLBACK(gtk_blist_menu_alias_cb), node, 0, 0, NULL);
1164
1165 pidgin_append_blist_node_extended_menu(menu, node);
1166
1167 return menu;
1168 }
1169
1170
1171 static GtkWidget *
1172 create_chat_menu(PurpleBlistNode *node, PurpleChat *c) {
1173 GtkWidget *menu;
1174 gboolean autojoin;
1175
1176 menu = gtk_menu_new();
1177 autojoin = (purple_blist_node_get_bool(node, "gtk-autojoin") ||
1178 (purple_blist_node_get_string(node, "gtk-autojoin") != NULL));
1179
1180 pidgin_new_item_from_stock(menu, _("_Join"), PIDGIN_STOCK_CHAT,
1181 G_CALLBACK(gtk_blist_menu_join_cb), node, 0, 0, NULL);
1182 pidgin_new_check_item(menu, _("Auto-Join"),
1183 G_CALLBACK(gtk_blist_menu_autojoin_cb), node, autojoin);
1184 pidgin_new_item_from_stock(menu, _("View _Log"), NULL,
1185 G_CALLBACK(gtk_blist_menu_showlog_cb), node, 0, 0, NULL);
1186
1187 pidgin_append_blist_node_proto_menu(menu, c->account->gc, node);
1188 pidgin_append_blist_node_extended_menu(menu, node);
1189
1190 pidgin_separator(menu);
1191
1192 pidgin_new_item_from_stock(menu, _("_Alias..."), PIDGIN_STOCK_ALIAS,
1193 G_CALLBACK(gtk_blist_menu_alias_cb), node, 0, 0, NULL);
1194 pidgin_new_item_from_stock(menu, _("_Remove"), GTK_STOCK_REMOVE,
1195 G_CALLBACK(pidgin_blist_remove_cb), node, 0, 0, NULL);
1196
1197 return menu;
1198 }
1199
1200 static GtkWidget *
1201 create_contact_menu (PurpleBlistNode *node)
1202 {
1203 GtkWidget *menu;
1204
1205 menu = gtk_menu_new();
1206
1207 pidgin_new_item_from_stock(menu, _("View _Log"), NULL,
1208 G_CALLBACK(gtk_blist_menu_showlog_cb),
1209 node, 0, 0, NULL);
1210
1211 pidgin_separator(menu);
1212
1213 pidgin_new_item_from_stock(menu, _("_Alias..."), PIDGIN_STOCK_ALIAS,
1214 G_CALLBACK(gtk_blist_menu_alias_cb), node, 0, 0, NULL);
1215 pidgin_new_item_from_stock(menu, _("_Remove"), GTK_STOCK_REMOVE,
1216 G_CALLBACK(pidgin_blist_remove_cb), node, 0, 0, NULL);
1217
1218 pidgin_separator(menu);
1219
1220 pidgin_new_item_from_stock(menu, _("_Collapse"), GTK_STOCK_ZOOM_OUT,
1221 G_CALLBACK(pidgin_blist_collapse_contact_cb),
1222 node, 0, 0, NULL);
1223
1224 pidgin_append_blist_node_extended_menu(menu, node);
1225
1226 return menu;
1227 }
1228
1229 static GtkWidget *
1230 create_buddy_menu(PurpleBlistNode *node, PurpleBuddy *b) {
1231 struct _pidgin_blist_node *gtknode = (struct _pidgin_blist_node *)node->ui_data;
1232 GtkWidget *menu;
1233 GtkWidget *menuitem;
1234 gboolean show_offline = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_offline_buddies");
1235
1236 menu = gtk_menu_new();
1237 pidgin_blist_make_buddy_menu(menu, b, FALSE);
1238
1239 if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
1240 pidgin_separator(menu);
1241
1242 if(gtknode->contact_expanded) {
1243 pidgin_new_item_from_stock(menu, _("_Collapse"),
1244 GTK_STOCK_ZOOM_OUT,
1245 G_CALLBACK(pidgin_blist_collapse_contact_cb),
1246 node, 0, 0, NULL);
1247 } else {
1248 pidgin_new_item_from_stock(menu, _("_Expand"),
1249 GTK_STOCK_ZOOM_IN,
1250 G_CALLBACK(pidgin_blist_expand_contact_cb), node,
1251 0, 0, NULL);
1252 }
1253 if(node->child->next) {
1254 PurpleBlistNode *bnode;
1255
1256 for(bnode = node->child; bnode; bnode = bnode->next) {
1257 PurpleBuddy *buddy = (PurpleBuddy*)bnode;
1258 GdkPixbuf *buf;
1259 GtkWidget *submenu;
1260 GtkWidget *image;
1261
1262 if(buddy == b)
1263 continue;
1264 if(!buddy->account->gc)
1265 continue;
1266 if(!show_offline && !PURPLE_BUDDY_IS_ONLINE(buddy))
1267 continue;
1268
1269 menuitem = gtk_image_menu_item_new_with_label(buddy->name);
1270 buf = pidgin_create_prpl_icon(buddy->account,PIDGIN_PRPL_ICON_SMALL);
1271 image = gtk_image_new_from_pixbuf(buf);
1272 g_object_unref(G_OBJECT(buf));
1273 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem),
1274 image);
1275 gtk_widget_show(image);
1276 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1277 gtk_widget_show(menuitem);
1278
1279 submenu = gtk_menu_new();
1280 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
1281 gtk_widget_show(submenu);
1282
1283 pidgin_blist_make_buddy_menu(submenu, buddy, TRUE);
1284 }
1285 }
1286 }
1287 return menu;
1288 }
1289
1290 static gboolean
1291 pidgin_blist_show_context_menu(PurpleBlistNode *node,
1292 GtkMenuPositionFunc func,
1293 GtkWidget *tv,
1294 guint button,
1295 guint32 time)
1296 {
1297 struct _pidgin_blist_node *gtknode;
1298 GtkWidget *menu = NULL;
1299 gboolean handled = FALSE;
1300
1301 gtknode = (struct _pidgin_blist_node *)node->ui_data;
1302
1303 /* Create a menu based on the thing we right-clicked on */
1304 if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
1305 PurpleGroup *g = (PurpleGroup *)node;
1306
1307 menu = create_group_menu(node, g);
1308 } else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
1309 PurpleChat *c = (PurpleChat *)node;
1310
1311 menu = create_chat_menu(node, c);
1312 } else if ((PURPLE_BLIST_NODE_IS_CONTACT(node)) && (gtknode->contact_expanded)) {
1313 menu = create_contact_menu(node);
1314 } else if (PURPLE_BLIST_NODE_IS_CONTACT(node) || PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1315 PurpleBuddy *b;
1316
1317 if (PURPLE_BLIST_NODE_IS_CONTACT(node))
1318 b = purple_contact_get_priority_buddy((PurpleContact*)node);
1319 else
1320 b = (PurpleBuddy *)node;
1321
1322 menu = create_buddy_menu(node, b);
1323 }
1324
1325 #ifdef _WIN32
1326 /* Unhook the tooltip-timeout since we don't want a tooltip
1327 * to appear and obscure the context menu we are about to show
1328 This is a workaround for GTK+ bug 107320. */
1329 if (gtkblist->timeout) {
1330 g_source_remove(gtkblist->timeout);
1331 gtkblist->timeout = 0;
1332 }
1333 #endif
1334
1335 /* Now display the menu */
1336 if (menu != NULL) {
1337 gtk_widget_show_all(menu);
1338 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, func, tv, button, time);
1339 handled = TRUE;
1340 }
1341
1342 return handled;
1343 }
1344
1345 static gboolean gtk_blist_button_press_cb(GtkWidget *tv, GdkEventButton *event, gpointer user_data)
1346 {
1347 GtkTreePath *path;
1348 PurpleBlistNode *node;
1349 GValue val;
1350 GtkTreeIter iter;
1351 GtkTreeSelection *sel;
1352 PurplePlugin *prpl = NULL;
1353 PurplePluginProtocolInfo *prpl_info = NULL;
1354 struct _pidgin_blist_node *gtknode;
1355 gboolean handled = FALSE;
1356
1357 /* Here we figure out which node was clicked */
1358 if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), event->x, event->y, &path, NULL, NULL, NULL))
1359 return FALSE;
1360 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
1361 val.g_type = 0;
1362 gtk_tree_model_get_value(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &val);
1363 node = g_value_get_pointer(&val);
1364 gtknode = (struct _pidgin_blist_node *)node->ui_data;
1365
1366 /* Right click draws a context menu */
1367 if ((event->button == 3) && (event->type == GDK_BUTTON_PRESS)) {
1368 handled = pidgin_blist_show_context_menu(node, NULL, tv, 3, event->time);
1369
1370 /* CTRL+middle click expands or collapse a contact */
1371 } else if ((event->button == 2) && (event->type == GDK_BUTTON_PRESS) &&
1372 (event->state & GDK_CONTROL_MASK) && (PURPLE_BLIST_NODE_IS_CONTACT(node))) {
1373 if (gtknode->contact_expanded)
1374 pidgin_blist_collapse_contact_cb(NULL, node);
1375 else
1376 pidgin_blist_expand_contact_cb(NULL, node);
1377 handled = TRUE;
1378
1379 /* Double middle click gets info */
1380 } else if ((event->button == 2) && (event->type == GDK_2BUTTON_PRESS) &&
1381 ((PURPLE_BLIST_NODE_IS_CONTACT(node)) || (PURPLE_BLIST_NODE_IS_BUDDY(node)))) {
1382 PurpleBuddy *b;
1383 if(PURPLE_BLIST_NODE_IS_CONTACT(node))
1384 b = purple_contact_get_priority_buddy((PurpleContact*)node);
1385 else
1386 b = (PurpleBuddy *)node;
1387
1388 prpl = purple_find_prpl(purple_account_get_protocol_id(b->account));
1389 if (prpl != NULL)
1390 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
1391
1392 if (prpl && prpl_info->get_info)
1393 serv_get_info(b->account->gc, b->name);
1394 handled = TRUE;
1395 }
1396
1397 #if (1)
1398 /*
1399 * This code only exists because GTK+ doesn't work. If we return
1400 * FALSE here, as would be normal the event propoagates down and
1401 * somehow gets interpreted as the start of a drag event.
1402 *
1403 * Um, isn't it _normal_ to return TRUE here? Since the event
1404 * was handled? --Mark
1405 */
1406 if(handled) {
1407 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv));
1408 gtk_tree_selection_select_path(sel, path);
1409 gtk_tree_path_free(path);
1410 return TRUE;
1411 }
1412 #endif
1413 gtk_tree_path_free(path);
1414
1415 return FALSE;
1416 }
1417
1418 static gboolean
1419 pidgin_blist_popup_menu_cb(GtkWidget *tv, void *user_data)
1420 {
1421 PurpleBlistNode *node;
1422 GValue val;
1423 GtkTreeIter iter;
1424 GtkTreeSelection *sel;
1425 gboolean handled = FALSE;
1426
1427 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv));
1428 if (!gtk_tree_selection_get_selected(sel, NULL, &iter))
1429 return FALSE;
1430
1431 val.g_type = 0;
1432 gtk_tree_model_get_value(GTK_TREE_MODEL(gtkblist->treemodel),
1433 &iter, NODE_COLUMN, &val);
1434 node = g_value_get_pointer(&val);
1435
1436 /* Shift+F10 draws a context menu */
1437 handled = pidgin_blist_show_context_menu(node, pidgin_treeview_popup_menu_position_func, tv, 0, GDK_CURRENT_TIME);
1438
1439 return handled;
1440 }
1441
1442 static void pidgin_blist_buddy_details_cb(gpointer data, guint action, GtkWidget *item)
1443 {
1444 pidgin_set_cursor(gtkblist->window, GDK_WATCH);
1445
1446 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons",
1447 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item)));
1448
1449 pidgin_clear_cursor(gtkblist->window);
1450 }
1451
1452 static void pidgin_blist_show_idle_time_cb(gpointer data, guint action, GtkWidget *item)
1453 {
1454 pidgin_set_cursor(gtkblist->window, GDK_WATCH);
1455
1456 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/show_idle_time",
1457 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item)));
1458
1459 pidgin_clear_cursor(gtkblist->window);
1460 }
1461
1462 static void pidgin_blist_show_empty_groups_cb(gpointer data, guint action, GtkWidget *item)
1463 {
1464 pidgin_set_cursor(gtkblist->window, GDK_WATCH);
1465
1466 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/show_empty_groups",
1467 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item)));
1468
1469 pidgin_clear_cursor(gtkblist->window);
1470 }
1471
1472 static void pidgin_blist_edit_mode_cb(gpointer callback_data, guint callback_action,
1473 GtkWidget *checkitem)
1474 {
1475 pidgin_set_cursor(gtkblist->window, GDK_WATCH);
1476
1477 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/show_offline_buddies",
1478 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(checkitem)));
1479
1480 pidgin_clear_cursor(gtkblist->window);
1481 }
1482
1483 static void pidgin_blist_mute_sounds_cb(gpointer data, guint action, GtkWidget *item)
1484 {
1485 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/sound/mute", GTK_CHECK_MENU_ITEM(item)->active);
1486 }
1487
1488 static void
1489 pidgin_blist_mute_pref_cb(const char *name, PurplePrefType type,
1490 gconstpointer value, gpointer data)
1491 {
1492 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item(gtkblist->ift,
1493 N_("/Tools/Mute Sounds"))), (gboolean)GPOINTER_TO_INT(value));
1494 }
1495
1496 static void
1497 pidgin_blist_sound_method_pref_cb(const char *name, PurplePrefType type,
1498 gconstpointer value, gpointer data)
1499 {
1500 gboolean sensitive = TRUE;
1501
1502 if(!strcmp(value, "none"))
1503 sensitive = FALSE;
1504
1505 gtk_widget_set_sensitive(gtk_item_factory_get_widget(gtkblist->ift, N_("/Tools/Mute Sounds")), sensitive);
1506 }
1507
1508 static void
1509 add_buddies_from_vcard(const char *prpl_id, PurpleGroup *group, GList *list,
1510 const char *alias)
1511 {
1512 GList *l;
1513 PurpleAccount *account = NULL;
1514 PurpleConnection *gc;
1515
1516 if (list == NULL)
1517 return;
1518
1519 for (l = purple_connections_get_all(); l != NULL; l = l->next)
1520 {
1521 gc = (PurpleConnection *)l->data;
1522 account = purple_connection_get_account(gc);
1523
1524 if (!strcmp(purple_account_get_protocol_id(account), prpl_id))
1525 break;
1526
1527 account = NULL;
1528 }
1529
1530 if (account != NULL)
1531 {
1532 for (l = list; l != NULL; l = l->next)
1533 {
1534 purple_blist_request_add_buddy(account, l->data,
1535 (group ? group->name : NULL),
1536 alias);
1537 }
1538 }
1539
1540 g_list_foreach(list, (GFunc)g_free, NULL);
1541 g_list_free(list);
1542 }
1543
1544 static gboolean
1545 parse_vcard(const char *vcard, PurpleGroup *group)
1546 {
1547 char *temp_vcard;
1548 char *s, *c;
1549 char *alias = NULL;
1550 GList *aims = NULL;
1551 GList *icqs = NULL;
1552 GList *yahoos = NULL;
1553 GList *msns = NULL;
1554 GList *jabbers = NULL;
1555
1556 s = temp_vcard = g_strdup(vcard);
1557
1558 while (*s != '\0' && strncmp(s, "END:vCard", strlen("END:vCard")))
1559 {
1560 char *field, *value;
1561
1562 field = s;
1563
1564 /* Grab the field */
1565 while (*s != '\r' && *s != '\n' && *s != '\0' && *s != ':')
1566 s++;
1567
1568 if (*s == '\r') s++;
1569 if (*s == '\n')
1570 {
1571 s++;
1572 continue;
1573 }
1574
1575 if (*s != '\0') *s++ = '\0';
1576
1577 if ((c = strchr(field, ';')) != NULL)
1578 *c = '\0';
1579
1580 /* Proceed to the end of the line */
1581 value = s;
1582
1583 while (*s != '\r' && *s != '\n' && *s != '\0')
1584 s++;
1585
1586 if (*s == '\r') *s++ = '\0';
1587 if (*s == '\n') *s++ = '\0';
1588
1589 /* We only want to worry about a few fields here. */
1590 if (!strcmp(field, "FN"))
1591 alias = g_strdup(value);
1592 else if (!strcmp(field, "X-AIM") || !strcmp(field, "X-ICQ") ||
1593 !strcmp(field, "X-YAHOO") || !strcmp(field, "X-MSN") ||
1594 !strcmp(field, "X-JABBER"))
1595 {
1596 char **values = g_strsplit(value, ":", 0);
1597 char **im;
1598
1599 for (im = values; *im != NULL; im++)
1600 {
1601 if (!strcmp(field, "X-AIM"))
1602 aims = g_list_append(aims, g_strdup(*im));
1603 else if (!strcmp(field, "X-ICQ"))
1604 icqs = g_list_append(icqs, g_strdup(*im));
1605 else if (!strcmp(field, "X-YAHOO"))
1606 yahoos = g_list_append(yahoos, g_strdup(*im));
1607 else if (!strcmp(field, "X-MSN"))
1608 msns = g_list_append(msns, g_strdup(*im));
1609 else if (!strcmp(field, "X-JABBER"))
1610 jabbers = g_list_append(jabbers, g_strdup(*im));
1611 }
1612
1613 g_strfreev(values);
1614 }
1615 }
1616
1617 g_free(temp_vcard);
1618
1619 if (aims == NULL && icqs == NULL && yahoos == NULL &&
1620 msns == NULL && jabbers == NULL)
1621 {
1622 g_free(alias);
1623
1624 return FALSE;
1625 }
1626
1627 add_buddies_from_vcard("prpl-oscar", group, aims, alias);
1628 add_buddies_from_vcard("prpl-oscar", group, icqs, alias);
1629 add_buddies_from_vcard("prpl-yahoo", group, yahoos, alias);
1630 add_buddies_from_vcard("prpl-msn", group, msns, alias);
1631 add_buddies_from_vcard("prpl-jabber", group, jabbers, alias);
1632
1633 g_free(alias);
1634
1635 return TRUE;
1636 }
1637
1638 #ifdef _WIN32
1639 static void pidgin_blist_drag_begin(GtkWidget *widget,
1640 GdkDragContext *drag_context, gpointer user_data)
1641 {
1642 pidgin_blist_tooltip_destroy();
1643
1644
1645 /* Unhook the tooltip-timeout since we don't want a tooltip
1646 * to appear and obscure the dragging operation.
1647 * This is a workaround for GTK+ bug 107320. */
1648 if (gtkblist->timeout) {
1649 g_source_remove(gtkblist->timeout);
1650 gtkblist->timeout = 0;
1651 }
1652 }
1653 #endif
1654
1655 static void pidgin_blist_drag_data_get_cb(GtkWidget *widget,
1656 GdkDragContext *dc,
1657 GtkSelectionData *data,
1658 guint info,
1659 guint time,
1660 gpointer null)
1661 {
1662
1663 if (data->target == gdk_atom_intern("PURPLE_BLIST_NODE", FALSE))
1664 {
1665 GtkTreeRowReference *ref = g_object_get_data(G_OBJECT(dc), "gtk-tree-view-source-row");
1666 GtkTreePath *sourcerow = gtk_tree_row_reference_get_path(ref);
1667 GtkTreeIter iter;
1668 PurpleBlistNode *node = NULL;
1669 GValue val;
1670 if(!sourcerow)
1671 return;
1672 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, sourcerow);
1673 val.g_type = 0;
1674 gtk_tree_model_get_value (GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &val);
1675 node = g_value_get_pointer(&val);
1676 gtk_selection_data_set (data,
1677 gdk_atom_intern ("PURPLE_BLIST_NODE", FALSE),
1678 8, /* bits */
1679 (void*)&node,
1680 sizeof (node));
1681
1682 gtk_tree_path_free(sourcerow);
1683 }
1684 else if (data->target == gdk_atom_intern("application/x-im-contact", FALSE))
1685 {
1686 GtkTreeRowReference *ref;
1687 GtkTreePath *sourcerow;
1688 GtkTreeIter iter;
1689 PurpleBlistNode *node = NULL;
1690 PurpleBuddy *buddy;
1691 PurpleConnection *gc;
1692 GValue val;
1693 GString *str;
1694 const char *protocol;
1695
1696 ref = g_object_get_data(G_OBJECT(dc), "gtk-tree-view-source-row");
1697 sourcerow = gtk_tree_row_reference_get_path(ref);
1698
1699 if (!sourcerow)
1700 return;
1701
1702 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter,
1703 sourcerow);
1704 val.g_type = 0;
1705 gtk_tree_model_get_value(GTK_TREE_MODEL(gtkblist->treemodel), &iter,
1706 NODE_COLUMN, &val);
1707
1708 node = g_value_get_pointer(&val);
1709
1710 if (PURPLE_BLIST_NODE_IS_CONTACT(node))
1711 {
1712 buddy = purple_contact_get_priority_buddy((PurpleContact *)node);
1713 }
1714 else if (!PURPLE_BLIST_NODE_IS_BUDDY(node))
1715 {
1716 gtk_tree_path_free(sourcerow);
1717 return;
1718 }
1719 else
1720 {
1721 buddy = (PurpleBuddy *)node;
1722 }
1723
1724 gc = purple_account_get_connection(buddy->account);
1725
1726 if (gc == NULL)
1727 {
1728 gtk_tree_path_free(sourcerow);
1729 return;
1730 }
1731
1732 protocol =
1733 PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->list_icon(buddy->account,
1734 buddy);
1735
1736 str = g_string_new(NULL);
1737 g_string_printf(str,
1738 "MIME-Version: 1.0\r\n"
1739 "Content-Type: application/x-im-contact\r\n"
1740 "X-IM-Protocol: %s\r\n"
1741 "X-IM-Username: %s\r\n",
1742 protocol,
1743 buddy->name);
1744
1745 if (buddy->alias != NULL)
1746 {
1747 g_string_append_printf(str,
1748 "X-IM-Alias: %s\r\n",
1749 buddy->alias);
1750 }
1751
1752 g_string_append(str, "\r\n");
1753
1754 gtk_selection_data_set(data,
1755 gdk_atom_intern("application/x-im-contact", FALSE),
1756 8, /* bits */
1757 (const guchar *)str->str,
1758 strlen(str->str) + 1);
1759
1760 g_string_free(str, TRUE);
1761 gtk_tree_path_free(sourcerow);
1762 }
1763 }
1764
1765 static void pidgin_blist_drag_data_rcv_cb(GtkWidget *widget, GdkDragContext *dc, guint x, guint y,
1766 GtkSelectionData *sd, guint info, guint t)
1767 {
1768 if (gtkblist->drag_timeout) {
1769 g_source_remove(gtkblist->drag_timeout);
1770 gtkblist->drag_timeout = 0;
1771 }
1772
1773 if (sd->target == gdk_atom_intern("PURPLE_BLIST_NODE", FALSE) && sd->data) {
1774 PurpleBlistNode *n = NULL;
1775 GtkTreePath *path = NULL;
1776 GtkTreeViewDropPosition position;
1777 memcpy(&n, sd->data, sizeof(n));
1778 if(gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget), x, y, &path, &position)) {
1779 /* if we're here, I think it means the drop is ok */
1780 GtkTreeIter iter;
1781 PurpleBlistNode *node;
1782 GValue val;
1783 struct _pidgin_blist_node *gtknode;
1784
1785 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel),
1786 &iter, path);
1787 val.g_type = 0;
1788 gtk_tree_model_get_value (GTK_TREE_MODEL(gtkblist->treemodel),
1789 &iter, NODE_COLUMN, &val);
1790 node = g_value_get_pointer(&val);
1791 gtknode = node->ui_data;
1792
1793 if (PURPLE_BLIST_NODE_IS_CONTACT(n)) {
1794 PurpleContact *c = (PurpleContact*)n;
1795 if (PURPLE_BLIST_NODE_IS_CONTACT(node) && gtknode->contact_expanded) {
1796 purple_blist_merge_contact(c, node);
1797 } else if (PURPLE_BLIST_NODE_IS_CONTACT(node) ||
1798 PURPLE_BLIST_NODE_IS_CHAT(node)) {
1799 switch(position) {
1800 case GTK_TREE_VIEW_DROP_AFTER:
1801 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
1802 purple_blist_add_contact(c, (PurpleGroup*)node->parent,
1803 node);
1804 break;
1805 case GTK_TREE_VIEW_DROP_BEFORE:
1806 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
1807 purple_blist_add_contact(c, (PurpleGroup*)node->parent,
1808 node->prev);
1809 break;
1810 }
1811 } else if(PURPLE_BLIST_NODE_IS_GROUP(node)) {
1812 purple_blist_add_contact(c, (PurpleGroup*)node, NULL);
1813 } else if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1814 purple_blist_merge_contact(c, node);
1815 }
1816 } else if (PURPLE_BLIST_NODE_IS_BUDDY(n)) {
1817 PurpleBuddy *b = (PurpleBuddy*)n;
1818 if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1819 switch(position) {
1820 case GTK_TREE_VIEW_DROP_AFTER:
1821 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
1822 purple_blist_add_buddy(b, (PurpleContact*)node->parent,
1823 (PurpleGroup*)node->parent->parent, node);
1824 break;
1825 case GTK_TREE_VIEW_DROP_BEFORE:
1826 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
1827 purple_blist_add_buddy(b, (PurpleContact*)node->parent,
1828 (PurpleGroup*)node->parent->parent,
1829 node->prev);
1830 break;
1831 }
1832 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
1833 purple_blist_add_buddy(b, NULL, (PurpleGroup*)node->parent,
1834 NULL);
1835 } else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
1836 purple_blist_add_buddy(b, NULL, (PurpleGroup*)node, NULL);
1837 } else if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
1838 if(gtknode->contact_expanded) {
1839 switch(position) {
1840 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
1841 case GTK_TREE_VIEW_DROP_AFTER:
1842 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
1843 purple_blist_add_buddy(b, (PurpleContact*)node,
1844 (PurpleGroup*)node->parent, NULL);
1845 break;
1846 case GTK_TREE_VIEW_DROP_BEFORE:
1847 purple_blist_add_buddy(b, NULL,
1848 (PurpleGroup*)node->parent, node->prev);
1849 break;
1850 }
1851 } else {
1852 switch(position) {
1853 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
1854 case GTK_TREE_VIEW_DROP_AFTER:
1855 purple_blist_add_buddy(b, NULL,
1856 (PurpleGroup*)node->parent, NULL);
1857 break;
1858 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
1859 case GTK_TREE_VIEW_DROP_BEFORE:
1860 purple_blist_add_buddy(b, NULL,
1861 (PurpleGroup*)node->parent, node->prev);
1862 break;
1863 }
1864 }
1865 }
1866 } else if (PURPLE_BLIST_NODE_IS_CHAT(n)) {
1867 PurpleChat *chat = (PurpleChat *)n;
1868 if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1869 switch(position) {
1870 case GTK_TREE_VIEW_DROP_AFTER:
1871 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
1872 case GTK_TREE_VIEW_DROP_BEFORE:
1873 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
1874 purple_blist_add_chat(chat,
1875 (PurpleGroup*)node->parent->parent,
1876 node->parent);
1877 break;
1878 }
1879 } else if(PURPLE_BLIST_NODE_IS_CONTACT(node) ||
1880 PURPLE_BLIST_NODE_IS_CHAT(node)) {
1881 switch(position) {
1882 case GTK_TREE_VIEW_DROP_AFTER:
1883 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
1884 purple_blist_add_chat(chat, (PurpleGroup*)node->parent, node);
1885 break;
1886 case GTK_TREE_VIEW_DROP_BEFORE:
1887 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
1888 purple_blist_add_chat(chat, (PurpleGroup*)node->parent, node->prev);
1889 break;
1890 }
1891 } else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
1892 purple_blist_add_chat(chat, (PurpleGroup*)node, NULL);
1893 }
1894 } else if (PURPLE_BLIST_NODE_IS_GROUP(n)) {
1895 PurpleGroup *g = (PurpleGroup*)n;
1896 if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
1897 switch (position) {
1898 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
1899 case GTK_TREE_VIEW_DROP_AFTER:
1900 purple_blist_add_group(g, node);
1901 break;
1902 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
1903 case GTK_TREE_VIEW_DROP_BEFORE:
1904 purple_blist_add_group(g, node->prev);
1905 break;
1906 }
1907 } else if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1908 purple_blist_add_group(g, node->parent->parent);
1909 } else if(PURPLE_BLIST_NODE_IS_CONTACT(node) ||
1910 PURPLE_BLIST_NODE_IS_CHAT(node)) {
1911 purple_blist_add_group(g, node->parent);
1912 }
1913 }
1914
1915 gtk_tree_path_free(path);
1916 gtk_drag_finish(dc, TRUE, (dc->action == GDK_ACTION_MOVE), t);
1917 }
1918 }
1919 else if (sd->target == gdk_atom_intern("application/x-im-contact",
1920 FALSE) && sd->data)
1921 {
1922 PurpleGroup *group = NULL;
1923 GtkTreePath *path = NULL;
1924 GtkTreeViewDropPosition position;
1925 PurpleAccount *account;
1926 char *protocol = NULL;
1927 char *username = NULL;
1928 char *alias = NULL;
1929
1930 if (gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget),
1931 x, y, &path, &position))
1932 {
1933 GtkTreeIter iter;
1934 PurpleBlistNode *node;
1935 GValue val;
1936
1937 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel),
1938 &iter, path);
1939 val.g_type = 0;
1940 gtk_tree_model_get_value (GTK_TREE_MODEL(gtkblist->treemodel),
1941 &iter, NODE_COLUMN, &val);
1942 node = g_value_get_pointer(&val);
1943
1944 if (PURPLE_BLIST_NODE_IS_BUDDY(node))
1945 {
1946 group = (PurpleGroup *)node->parent->parent;
1947 }
1948 else if (PURPLE_BLIST_NODE_IS_CHAT(node) ||
1949 PURPLE_BLIST_NODE_IS_CONTACT(node))
1950 {
1951 group = (PurpleGroup *)node->parent;
1952 }
1953 else if (PURPLE_BLIST_NODE_IS_GROUP(node))
1954 {
1955 group = (PurpleGroup *)node;
1956 }
1957 }
1958
1959 if (pidgin_parse_x_im_contact((const char *)sd->data, FALSE, &account,
1960 &protocol, &username, &alias))
1961 {
1962 if (account == NULL)
1963 {
1964 purple_notify_error(NULL, NULL,
1965 _("You are not currently signed on with an account that "
1966 "can add that buddy."), NULL);
1967 }
1968 else
1969 {
1970 purple_blist_request_add_buddy(account, username,
1971 (group ? group->name : NULL),
1972 alias);
1973 }
1974 }
1975
1976 g_free(username);
1977 g_free(protocol);
1978 g_free(alias);
1979
1980 if (path != NULL)
1981 gtk_tree_path_free(path);
1982
1983 gtk_drag_finish(dc, TRUE, (dc->action == GDK_ACTION_MOVE), t);
1984 }
1985 else if (sd->target == gdk_atom_intern("text/x-vcard", FALSE) && sd->data)
1986 {
1987 gboolean result;
1988 PurpleGroup *group = NULL;
1989 GtkTreePath *path = NULL;
1990 GtkTreeViewDropPosition position;
1991
1992 if (gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget),
1993 x, y, &path, &position))
1994 {
1995 GtkTreeIter iter;
1996 PurpleBlistNode *node;
1997 GValue val;
1998
1999 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel),
2000 &iter, path);
2001 val.g_type = 0;
2002 gtk_tree_model_get_value (GTK_TREE_MODEL(gtkblist->treemodel),
2003 &iter, NODE_COLUMN, &val);
2004 node = g_value_get_pointer(&val);
2005
2006 if (PURPLE_BLIST_NODE_IS_BUDDY(node))
2007 {
2008 group = (PurpleGroup *)node->parent->parent;
2009 }
2010 else if (PURPLE_BLIST_NODE_IS_CHAT(node) ||
2011 PURPLE_BLIST_NODE_IS_CONTACT(node))
2012 {
2013 group = (PurpleGroup *)node->parent;
2014 }
2015 else if (PURPLE_BLIST_NODE_IS_GROUP(node))
2016 {
2017 group = (PurpleGroup *)node;
2018 }
2019 }
2020
2021 result = parse_vcard((const gchar *)sd->data, group);
2022
2023 gtk_drag_finish(dc, result, (dc->action == GDK_ACTION_MOVE), t);
2024 } else if (sd->target == gdk_atom_intern("text/uri-list", FALSE) && sd->data) {
2025 GtkTreePath *path = NULL;
2026 GtkTreeViewDropPosition position;
2027
2028 if (gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget),
2029 x, y, &path, &position))
2030 {
2031 GtkTreeIter iter;
2032 PurpleBlistNode *node;
2033 GValue val;
2034
2035 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel),
2036 &iter, path);
2037 val.g_type = 0;
2038 gtk_tree_model_get_value (GTK_TREE_MODEL(gtkblist->treemodel),
2039 &iter, NODE_COLUMN, &val);
2040 node = g_value_get_pointer(&val);
2041
2042 if (PURPLE_BLIST_NODE_IS_BUDDY(node) || PURPLE_BLIST_NODE_IS_CONTACT(node)) {
2043 PurpleBuddy *b = PURPLE_BLIST_NODE_IS_BUDDY(node) ? (PurpleBuddy*)node : purple_contact_get_priority_buddy((PurpleContact*)node);
2044 pidgin_dnd_file_manage(sd, b->account, b->name);
2045 gtk_drag_finish(dc, TRUE, (dc->action == GDK_ACTION_MOVE), t);
2046 } else {
2047 gtk_drag_finish(dc, FALSE, FALSE, t);
2048 }
2049 }
2050 }
2051 }
2052
2053 static void
2054 roundify(GdkPixbuf *pixbuf) {
2055 int width, height, rowstride;
2056 guchar *pixels;
2057
2058 if (!gdk_pixbuf_get_has_alpha(pixbuf))
2059 return;
2060
2061 width = gdk_pixbuf_get_width(pixbuf);
2062 height = gdk_pixbuf_get_height(pixbuf);
2063 rowstride = gdk_pixbuf_get_rowstride(pixbuf);
2064 pixels = gdk_pixbuf_get_pixels(pixbuf);
2065
2066 if (width < 6 || height < 6)
2067 return;
2068
2069 /* Top left */
2070 pixels[3] = 0;
2071 pixels[7] = 0x80;
2072 pixels[11] = 0xC0;
2073 pixels[rowstride + 3] = 0x80;
2074 pixels[rowstride * 2 + 3] = 0xC0;
2075
2076 /* Top right */
2077 pixels[width * 4 - 1] = 0;
2078 pixels[width * 4 - 5] = 0x80;
2079 pixels[width * 4 - 9] = 0xC0;
2080 pixels[rowstride + (width * 4) - 1] = 0x80;
2081 pixels[(2 * rowstride) + (width * 4) - 1] = 0xC0;
2082
2083 /* Bottom left */
2084 pixels[(height - 1) * rowstride + 3] = 0;
2085 pixels[(height - 1) * rowstride + 7] = 0x80;
2086 pixels[(height - 1) * rowstride + 11] = 0xC0;
2087 pixels[(height - 2) * rowstride + 3] = 0x80;
2088 pixels[(height - 3) * rowstride + 3] = 0xC0;
2089
2090 /* Bottom right */
2091 pixels[height * rowstride - 1] = 0;
2092 pixels[(height - 1) * rowstride - 1] = 0x80;
2093 pixels[(height - 2) * rowstride - 1] = 0xC0;
2094 pixels[height * rowstride - 5] = 0x80;
2095 pixels[height * rowstride - 9] = 0xC0;
2096 }
2097
2098 /* Altered from do_colorshift in gnome-panel */
2099 static void
2100 do_alphashift (GdkPixbuf *dest, GdkPixbuf *src, int shift)
2101 {
2102 gint i, j;
2103 gint width, height, has_alpha, srcrowstride, destrowstride;
2104 guchar *target_pixels;
2105 guchar *original_pixels;
2106 guchar *pixsrc;
2107 guchar *pixdest;
2108 int val;
2109 guchar a;
2110
2111 has_alpha = gdk_pixbuf_get_has_alpha (src);
2112 if (!has_alpha)
2113 return;
2114
2115 width = gdk_pixbuf_get_width (src);
2116 height = gdk_pixbuf_get_height (src);
2117 srcrowstride = gdk_pixbuf_get_rowstride (src);
2118 destrowstride = gdk_pixbuf_get_rowstride (dest);
2119 target_pixels = gdk_pixbuf_get_pixels (dest);
2120 original_pixels = gdk_pixbuf_get_pixels (src);
2121
2122 for (i = 0; i < height; i++) {
2123 pixdest = target_pixels + i*destrowstride;
2124 pixsrc = original_pixels + i*srcrowstride;
2125 for (j = 0; j < width; j++) {
2126 *(pixdest++) = *(pixsrc++);
2127 *(pixdest++) = *(pixsrc++);
2128 *(pixdest++) = *(pixsrc++);
2129 a = *(pixsrc++);
2130 val = a - shift;
2131 *(pixdest++) = CLAMP(val, 0, 255);
2132 }
2133 }
2134 }
2135
2136
2137 static GdkPixbuf *pidgin_blist_get_buddy_icon(PurpleBlistNode *node,
2138 gboolean scaled, gboolean greyed, gboolean custom)
2139 {
2140 GdkPixbuf *buf, *ret = NULL;
2141 GdkPixbufLoader *loader;
2142 PurpleBuddyIcon *icon;
2143 const guchar *data = NULL;
2144 gsize len;
2145 PurpleBuddy *buddy = NULL;
2146 PurpleChat *chat = NULL;
2147 PurpleAccount *account = NULL;
2148 PurplePluginProtocolInfo *prpl_info = NULL;
2149
2150 if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
2151 buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
2152 } else if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
2153 buddy = (PurpleBuddy*)node;
2154 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
2155 chat = (PurpleChat*)node;
2156 } else {
2157 return NULL;
2158 }
2159
2160 if(buddy != NULL)
2161 account = purple_buddy_get_account(buddy);
2162 else if(chat != NULL)
2163 account = chat->account;
2164
2165 if(account && account->gc)
2166 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(account->gc->prpl);
2167
2168 #if 0
2169 if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons"))
2170 return NULL;
2171 #endif
2172
2173 if (custom) {
2174 const char *file = purple_blist_node_get_string((PurpleBlistNode*)purple_buddy_get_contact(buddy),
2175 "custom_buddy_icon");
2176 if (file && *file) {
2177 char *contents;
2178 GError *err = NULL;
2179 if (!g_file_get_contents(file, &contents, &len, &err)) {
2180 purple_debug_info("custom -icon", "Could not open custom-icon %s for %s\n",
2181 file, purple_buddy_get_name(buddy), err->message);
2182 g_error_free(err);
2183 } else
2184 data = (const guchar*)contents;
2185 }
2186 }
2187
2188 if (data == NULL) {
2189 if(buddy != NULL) {
2190 if (!(icon = purple_buddy_get_icon(buddy)))
2191 if (!(icon = purple_buddy_icons_find(buddy->account, buddy->name))) /* Not sure I like this...*/
2192 return NULL;
2193 data = purple_buddy_icon_get_data(icon, &len);
2194 }
2195 custom = FALSE; /* We are not using the custom icon */
2196 }
2197
2198 if(data == NULL)
2199 return NULL;
2200
2201 loader = gdk_pixbuf_loader_new();
2202 gdk_pixbuf_loader_write(loader, data, len, NULL);
2203 gdk_pixbuf_loader_close(loader, NULL);
2204 buf = gdk_pixbuf_loader_get_pixbuf(loader);
2205 if (buf)
2206 g_object_ref(G_OBJECT(buf));
2207 g_object_unref(G_OBJECT(loader));
2208
2209 if (custom)
2210 g_free((void*)data);
2211 if (buf) {
2212 int orig_width, orig_height;
2213 int scale_width, scale_height;
2214
2215 if (greyed) {
2216 PurplePresence *presence = purple_buddy_get_presence(buddy);
2217 if (!PURPLE_BUDDY_IS_ONLINE(buddy))
2218 gdk_pixbuf_saturate_and_pixelate(buf, buf, 0.0, FALSE);
2219 if (purple_presence_is_idle(presence))
2220 gdk_pixbuf_saturate_and_pixelate(buf, buf, 0.25, FALSE);
2221 }
2222
2223 /* i'd use the pidgin_buddy_icon_get_scale_size() thing,
2224 * but it won't tell me the original size, which I need for scaling
2225 * purposes */
2226 scale_width = orig_width = gdk_pixbuf_get_width(buf);
2227 scale_height = orig_height = gdk_pixbuf_get_height(buf);
2228
2229 if (prpl_info && prpl_info->icon_spec.scale_rules & PURPLE_ICON_SCALE_DISPLAY)
2230 purple_buddy_icon_get_scale_size(&prpl_info->icon_spec, &scale_width, &scale_height);
2231
2232 if (scaled) {
2233 if(scale_height > scale_width) {
2234 scale_width = 32.0 * (double)scale_width / (double)scale_height;
2235 scale_height = 32;
2236 } else {
2237 scale_height = 32.0 * (double)scale_height / (double)scale_width;
2238 scale_width = 32;
2239 }
2240
2241 ret = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, 32, 32);
2242 gdk_pixbuf_fill(ret, 0x00000000);
2243 gdk_pixbuf_scale(buf, ret, (32-scale_width)/2, (32-scale_height)/2, scale_width, scale_height, (32-scale_width)/2, (32-scale_height)/2, (double)scale_width/(double)orig_width, (double)scale_height/(double)orig_height, GDK_INTERP_BILINEAR);
2244 if (pidgin_gdk_pixbuf_is_opaque(ret))
2245 roundify(ret);
2246 } else {
2247 ret = gdk_pixbuf_scale_simple(buf,scale_width,scale_height, GDK_INTERP_BILINEAR);
2248 }
2249 g_object_unref(G_OBJECT(buf));
2250 }
2251
2252 return ret;
2253 }
2254 /* # - Status Icon
2255 * P - Protocol Icon
2256 * A - Buddy Icon
2257 * [ - SMALL_SPACE
2258 * = - LARGE_SPACE
2259 * +--- STATUS_SIZE +--- td->avatar_width
2260 * | +-- td->name_width |
2261 * +----+ +-------+ +---------+
2262 * | | | | | |
2263 * +-------------------------------------------+
2264 * | [ = [ |--- TOOLTIP_BORDER
2265 *name_height --+-| ######[BuddyName = PP [ AAAAAAAAAAA |--+
2266 * | | ######[ = PP [ AAAAAAAAAAA | |
2267 * STATUS SIZE -| | ######[[[[[[[[[[[[[[[[[[[[[ AAAAAAAAAAA | |
2268 * +--+-| ######[Account: So-and-so [ AAAAAAAAAAA | |-- td->avatar_height
2269 * | | [Idle: 4h 15m [ AAAAAAAAAAA | |
2270 * height --+ | [Foo: Bar, Baz [ AAAAAAAAAAA | |
2271 * | | [Status: Awesome [ AAAAAAAAAAA |--+
2272 * +----| [Stop: Hammer Time [ |
2273 * | [ [ |--- TOOLTIP_BORDER
2274 * +-------------------------------------------+
2275 * | | | |
2276 * | +----------------+ |
2277 * | | |
2278 * | +-- td->width |
2279 * | |
2280 * +---- TOOLTIP_BORDER +---- TOOLTIP_BORDER
2281 *
2282 *
2283 */
2284 #define STATUS_SIZE 22
2285 #define TOOLTIP_BORDER 12
2286 #define SMALL_SPACE 6
2287 #define LARGE_SPACE 12
2288 #define PRPL_SIZE 16
2289 struct tooltip_data {
2290 PangoLayout *layout;
2291 PangoLayout *name_layout;
2292 GdkPixbuf *prpl_icon;
2293 GdkPixbuf *status_icon;
2294 GdkPixbuf *avatar;
2295 gboolean avatar_is_prpl_icon;
2296 int avatar_width;
2297 int avatar_height;
2298 int name_height;
2299 int name_width;
2300 int width;
2301 int height;
2302 };
2303
2304 static struct tooltip_data * create_tip_for_node(PurpleBlistNode *node, gboolean full)
2305 {
2306 char *tooltip_text = NULL;
2307 struct tooltip_data *td = g_new0(struct tooltip_data, 1);
2308 PurpleAccount *account = NULL;
2309 char *tmp, *node_name;
2310
2311 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
2312 account = ((PurpleBuddy*)(node))->account;
2313 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
2314 account = ((PurpleChat*)(node))->account;
2315 }
2316
2317 td->status_icon = pidgin_blist_get_status_icon(node, PIDGIN_STATUS_ICON_LARGE);
2318 td->avatar = pidgin_blist_get_buddy_icon(node, !full, FALSE, TRUE);
2319 td->prpl_icon = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL);
2320 tooltip_text = pidgin_get_tooltip_text(node, full);
2321 td->layout = gtk_widget_create_pango_layout(gtkblist->tipwindow, NULL);
2322 td->name_layout = gtk_widget_create_pango_layout(gtkblist->tipwindow, NULL);
2323
2324 if (PURPLE_BLIST_NODE_IS_BUDDY(node))
2325 tmp = g_markup_escape_text(purple_buddy_get_name((PurpleBuddy*)node), -1);
2326 else
2327 tmp = g_markup_escape_text(purple_chat_get_name((PurpleChat*)node), -1);
2328 node_name = g_strdup_printf("<span size='x-large' weight='bold'>%s</span>", tmp);
2329 g_free(tmp);
2330
2331 pango_layout_set_markup(td->layout, tooltip_text, -1);
2332 pango_layout_set_wrap(td->layout, PANGO_WRAP_WORD);
2333 pango_layout_set_width(td->layout, 300000);
2334
2335 pango_layout_get_size (td->layout, &td->width, &td->height);
2336 td->width = PANGO_PIXELS(td->width);
2337 td->height = PANGO_PIXELS(td->height);
2338
2339 pango_layout_set_markup(td->name_layout, node_name, -1);
2340 pango_layout_set_wrap(td->name_layout, PANGO_WRAP_WORD);
2341 pango_layout_set_width(td->name_layout, 300000);
2342
2343 pango_layout_get_size (td->name_layout, &td->name_width, &td->name_height);
2344 td->name_width = PANGO_PIXELS(td->name_width) + SMALL_SPACE + PRPL_SIZE;
2345 td->name_height = MAX(PANGO_PIXELS(td->name_height), PRPL_SIZE + SMALL_SPACE);
2346 #if 0 /* PRPL Icon as avatar */
2347 if(!td->avatar && full) {
2348 td->avatar = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_LARGE);
2349 td->avatar_is_prpl_icon = TRUE;
2350 }
2351 #endif
2352
2353 if (td->avatar) {
2354 td->avatar_width = gdk_pixbuf_get_width(td->avatar);
2355 td->avatar_height = gdk_pixbuf_get_height(td->avatar);
2356 }
2357
2358 g_free(node_name);
2359 g_free(tooltip_text);
2360 return td;
2361 }
2362
2363 static void pidgin_blist_paint_tip(GtkWidget *widget, GdkEventExpose *event, PurpleBlistNode *node)
2364 {
2365 GtkStyle *style;
2366 int current_height, max_width;
2367 int max_text_width;
2368 int max_avatar_width;
2369 GList *l;
2370 int prpl_col = 0;
2371 GtkTextDirection dir = gtk_widget_get_direction(widget);
2372
2373 if(gtkblist->tooltipdata == NULL)
2374 return;
2375
2376 style = gtkblist->tipwindow->style;
2377 gtk_paint_flat_box(style, gtkblist->tipwindow->window, GTK_STATE_NORMAL, GTK_SHADOW_OUT,
2378 NULL, gtkblist->tipwindow, "tooltip", 0, 0, -1, -1);
2379
2380 max_text_width = 0;
2381 max_avatar_width = 0;
2382
2383 for(l = gtkblist->tooltipdata; l; l = l->next)
2384 {
2385 struct tooltip_data *td = l->data;
2386
2387 max_text_width = MAX(max_text_width,
2388 MAX(td->width, td->name_width));
2389 max_avatar_width = MAX(max_avatar_width, td->avatar_width);
2390 }
2391
2392 max_width = TOOLTIP_BORDER + STATUS_SIZE + SMALL_SPACE + max_text_width + SMALL_SPACE + max_avatar_width + TOOLTIP_BORDER;
2393 if (dir == GTK_TEXT_DIR_RTL)
2394 prpl_col = TOOLTIP_BORDER + max_avatar_width + SMALL_SPACE;
2395 else
2396 prpl_col = TOOLTIP_BORDER + STATUS_SIZE + SMALL_SPACE + max_text_width - PRPL_SIZE;
2397
2398 current_height = 12;
2399 for(l = gtkblist->tooltipdata; l; l = l->next)
2400 {
2401 struct tooltip_data *td = l->data;
2402
2403 if (td->avatar && pidgin_gdk_pixbuf_is_opaque(td->avatar))
2404 {
2405 if (dir == GTK_TEXT_DIR_RTL)
2406 gtk_paint_flat_box(style, gtkblist->tipwindow->window, GTK_STATE_NORMAL, GTK_SHADOW_OUT,
2407 NULL, gtkblist->tipwindow, "tooltip",
2408 TOOLTIP_BORDER -1, current_height -1, td->avatar_width +2, td->avatar_height + 2);
2409 else
2410 gtk_paint_flat_box(style, gtkblist->tipwindow->window, GTK_STATE_NORMAL, GTK_SHADOW_OUT,
2411 NULL, gtkblist->tipwindow, "tooltip",
2412 max_width - (td->avatar_width+ TOOLTIP_BORDER)-1,
2413 current_height-1,td->avatar_width+2, td->avatar_height+2);
2414 }
2415
2416 #if GTK_CHECK_VERSION(2,2,0)
2417 if (dir == GTK_TEXT_DIR_RTL)
2418 gdk_draw_pixbuf(GDK_DRAWABLE(gtkblist->tipwindow->window), NULL, td->status_icon,
2419 0, 0, max_width - TOOLTIP_BORDER - STATUS_SIZE, current_height, -1, -1, GDK_RGB_DITHER_NONE, 0, 0);
2420 else
2421 gdk_draw_pixbuf(GDK_DRAWABLE(gtkblist->tipwindow->window), NULL, td->status_icon,
2422 0, 0, TOOLTIP_BORDER, current_height, -1 , -1, GDK_RGB_DITHER_NONE, 0, 0);
2423 if(td->avatar)
2424 {
2425 if (dir == GTK_TEXT_DIR_RTL)
2426 gdk_draw_pixbuf(GDK_DRAWABLE(gtkblist->tipwindow->window), NULL,
2427 td->avatar, 0, 0, TOOLTIP_BORDER, current_height, -1, -1, GDK_RGB_DITHER_NONE, 0, 0);
2428 else
2429 gdk_draw_pixbuf(GDK_DRAWABLE(gtkblist->tipwindow->window), NULL,
2430 td->avatar, 0, 0, max_width - (td->avatar_width + TOOLTIP_BORDER),
2431 current_height, -1 , -1, GDK_RGB_DITHER_NONE, 0, 0);
2432 }
2433
2434 if (!td->avatar_is_prpl_icon)
2435 gdk_draw_pixbuf(GDK_DRAWABLE(gtkblist->tipwindow->window), NULL, td->prpl_icon,
2436 0, 0,
2437 prpl_col,
2438 current_height + ((td->name_height / 2) - (PRPL_SIZE / 2)),
2439 -1 , -1, GDK_RGB_DITHER_NONE, 0, 0);
2440
2441 #else
2442 gdk_pixbuf_render_to_drawable(td->status_icon, GDK_DRAWABLE(gtkblist->tipwindow->window), NULL, 0, 0, 12, current_height, -1, -1, GDK_RGB_DITHER_NONE, 0, 0);
2443 if(td->avatar)
2444 gdk_pixbuf_render_to_drawable(td->avatar,
2445 GDK_DRAWABLE(gtkblist->tipwindow->window), NULL, 0, 0,
2446 max_width - (td->avatar_width + TOOLTIP_BORDER),
2447 current_height, -1, -1, GDK_RGB_DITHER_NONE, 0, 0);
2448 #endif
2449 if (dir == GTK_TEXT_DIR_RTL) {
2450 gtk_paint_layout(style, gtkblist->tipwindow->window, GTK_STATE_NORMAL, FALSE,
2451 NULL, gtkblist->tipwindow, "tooltip",
2452 max_width -(TOOLTIP_BORDER + STATUS_SIZE +SMALL_SPACE) - PANGO_PIXELS(300000),
2453 current_height, td->name_layout);
2454 } else {
2455 gtk_paint_layout (style, gtkblist->tipwindow->window, GTK_STATE_NORMAL, FALSE,
2456 NULL, gtkblist->tipwindow, "tooltip",
2457 TOOLTIP_BORDER + STATUS_SIZE + SMALL_SPACE, current_height, td->name_layout);
2458 }
2459 if (dir != GTK_TEXT_DIR_RTL) {
2460 gtk_paint_layout (style, gtkblist->tipwindow->window, GTK_STATE_NORMAL, FALSE,
2461 NULL, gtkblist->tipwindow, "tooltip",
2462 TOOLTIP_BORDER + STATUS_SIZE + SMALL_SPACE, current_height + td->name_height, td->layout);
2463 } else {
2464 gtk_paint_layout(style, gtkblist->tipwindow->window, GTK_STATE_NORMAL, FALSE,
2465 NULL, gtkblist->tipwindow, "tooltip",
2466 max_width - (TOOLTIP_BORDER + STATUS_SIZE + SMALL_SPACE) - PANGO_PIXELS(300000),
2467 current_height + td->name_height,
2468 td->layout);
2469 }
2470
2471 current_height += MAX(td->name_height + td->height, td->avatar_height) + TOOLTIP_BORDER;
2472 }
2473 }
2474
2475
2476 static void pidgin_blist_tooltip_destroy()
2477 {
2478 while(gtkblist->tooltipdata) {
2479 struct tooltip_data *td = gtkblist->tooltipdata->data;
2480
2481 if(td->avatar)
2482 g_object_unref(td->avatar);
2483 if(td->status_icon)
2484 g_object_unref(td->status_icon);
2485 if(td->prpl_icon)
2486 g_object_unref(td->prpl_icon);
2487 g_object_unref(td->layout);
2488 g_object_unref(td->name_layout);
2489 g_free(td);
2490 gtkblist->tooltipdata = g_list_delete_link(gtkblist->tooltipdata, gtkblist->tooltipdata);
2491 }
2492
2493 if (gtkblist->tipwindow == NULL)
2494 return;
2495
2496 gtk_widget_destroy(gtkblist->tipwindow);
2497 gtkblist->tipwindow = NULL;
2498 }
2499
2500 static gboolean pidgin_blist_expand_timeout(GtkWidget *tv)
2501 {
2502 GtkTreePath *path;
2503 GtkTreeIter iter;
2504 PurpleBlistNode *node;
2505 GValue val;
2506 struct _pidgin_blist_node *gtknode;
2507
2508 if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), gtkblist->tip_rect.x, gtkblist->tip_rect.y, &path, NULL, NULL, NULL))
2509 return FALSE;
2510 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
2511 val.g_type = 0;
2512 gtk_tree_model_get_value (GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &val);
2513 node = g_value_get_pointer(&val);
2514
2515 if(!PURPLE_BLIST_NODE_IS_CONTACT(node)) {
2516 gtk_tree_path_free(path);
2517 return FALSE;
2518 }
2519
2520 gtknode = node->ui_data;
2521
2522 if (!gtknode->contact_expanded) {
2523 GtkTreeIter i;
2524
2525 pidgin_blist_expand_contact_cb(NULL, node);
2526
2527 gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, &gtkblist->contact_rect);
2528 gdk_drawable_get_size(GDK_DRAWABLE(tv->window), &(gtkblist->contact_rect.width), NULL);
2529 gtkblist->mouseover_contact = node;
2530 gtk_tree_path_down (path);
2531 while (gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &i, path)) {
2532 GdkRectangle rect;
2533 gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, &rect);
2534 gtkblist->contact_rect.height += rect.height;
2535 gtk_tree_path_next(path);
2536 }
2537 }
2538 gtk_tree_path_free(path);
2539 return FALSE;
2540 }
2541
2542 static gboolean buddy_is_displayable(PurpleBuddy *buddy)
2543 {
2544 struct _pidgin_blist_node *gtknode;
2545
2546 if(!buddy)
2547 return FALSE;
2548
2549 gtknode = ((PurpleBlistNode*)buddy)->ui_data;
2550
2551 return (purple_account_is_connected(buddy->account) &&
2552 (purple_presence_is_online(buddy->presence) ||
2553 (gtknode && gtknode->recent_signonoff) ||
2554 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_offline_buddies") ||
2555 purple_blist_node_get_bool((PurpleBlistNode*)buddy, "show_offline")));
2556 }
2557
2558 static gboolean pidgin_blist_tooltip_timeout(GtkWidget *tv)
2559 {
2560 GtkTreePath *path;
2561 GtkTreeIter iter;
2562 PurpleBlistNode *node;
2563 GValue val;
2564 int scr_w, scr_h, w, h, x, y;
2565 #if GTK_CHECK_VERSION(2,2,0)
2566 int mon_num;
2567 GdkScreen *screen = NULL;
2568 #endif
2569 gboolean tooltip_top = FALSE;
2570 struct _pidgin_blist_node *gtknode;
2571 GdkRectangle mon_size;
2572
2573 /*
2574 * Attempt to free the previous tooltip. I have a feeling
2575 * this is never needed... but just in case.
2576 */
2577 pidgin_blist_tooltip_destroy();
2578
2579 if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), gtkblist->tip_rect.x, gtkblist->tip_rect.y, &path, NULL, NULL, NULL))
2580 return FALSE;
2581 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
2582 val.g_type = 0;
2583 gtk_tree_model_get_value (GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &val);
2584 node = g_value_get_pointer(&val);
2585
2586 gtk_tree_path_free(path);
2587
2588 gtkblist->tipwindow = gtk_window_new(GTK_WINDOW_POPUP);
2589
2590 if(PURPLE_BLIST_NODE_IS_CHAT(node) || PURPLE_BLIST_NODE_IS_BUDDY(node)) {
2591 struct tooltip_data *td = create_tip_for_node(node, TRUE);
2592 gtkblist->tooltipdata = g_list_append(gtkblist->tooltipdata, td);
2593 w = TOOLTIP_BORDER + STATUS_SIZE + SMALL_SPACE +
2594 MAX(td->width, td->name_width) + SMALL_SPACE + td->avatar_width + TOOLTIP_BORDER;
2595 h = TOOLTIP_BORDER + MAX(td->height + td->name_height, MAX(STATUS_SIZE, td->avatar_height))
2596 + TOOLTIP_BORDER;
2597 } else if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
2598 PurpleBlistNode *child;
2599 PurpleBuddy *b = purple_contact_get_priority_buddy((PurpleContact *)node);
2600 int max_text_width = 0;
2601 int max_avatar_width = 0;
2602 w = h = 0;
2603
2604 for(child = node->child; child; child = child->next)
2605 {
2606 if(PURPLE_BLIST_NODE_IS_BUDDY(child) && buddy_is_displayable((PurpleBuddy*)child)) {
2607 struct tooltip_data *td = create_tip_for_node(child, (b == (PurpleBuddy*)child));
2608 if (b == (PurpleBuddy *)child) {
2609 gtkblist->tooltipdata = g_list_prepend(gtkblist->tooltipdata, td);
2610 } else {
2611 gtkblist->tooltipdata = g_list_append(gtkblist->tooltipdata, td);
2612 }
2613 max_text_width = MAX(max_text_width, MAX(td->width, td->name_width));
2614 max_avatar_width = MAX(max_avatar_width, td->avatar_width);
2615 h += MAX(TOOLTIP_BORDER + MAX(STATUS_SIZE,td->avatar_height),
2616 TOOLTIP_BORDER + td->height + td->name_height);
2617 }
2618 }
2619 h += TOOLTIP_BORDER;
2620 w = TOOLTIP_BORDER + STATUS_SIZE + SMALL_SPACE + max_text_width + SMALL_SPACE + max_avatar_width + TOOLTIP_BORDER;
2621 } else {
2622 gtk_widget_destroy(gtkblist->tipwindow);
2623 gtkblist->tipwindow = NULL;
2624 return FALSE;
2625 }
2626
2627 if (gtkblist->tooltipdata == NULL) {
2628 gtk_widget_destroy(gtkblist->tipwindow);
2629 gtkblist->tipwindow = NULL;
2630 return FALSE;
2631 }
2632
2633 gtknode = node->ui_data;
2634
2635 gtk_widget_set_app_paintable(gtkblist->tipwindow, TRUE);
2636 gtk_window_set_resizable(GTK_WINDOW(gtkblist->tipwindow), FALSE);
2637 gtk_widget_set_name(gtkblist->tipwindow, "gtk-tooltips");
2638 g_signal_connect(G_OBJECT(gtkblist->tipwindow), "expose_event",
2639 G_CALLBACK(pidgin_blist_paint_tip), NULL);
2640 gtk_widget_ensure_style (gtkblist->tipwindow);
2641
2642
2643 #if GTK_CHECK_VERSION(2,2,0)
2644 gdk_display_get_pointer(gdk_display_get_default(), &screen, &x, &y, NULL);
2645 mon_num = gdk_screen_get_monitor_at_point(screen, x, y);
2646 gdk_screen_get_monitor_geometry(screen, mon_num, &mon_size);
2647
2648 scr_w = mon_size.width + mon_size.x;
2649 scr_h = mon_size.height + mon_size.y;
2650 #else
2651 scr_w = gdk_screen_width();
2652 scr_h = gdk_screen_height();
2653 gdk_window_get_pointer(NULL, &x, &y, NULL);
2654 mon_size.x = 0;
2655 mon_size.y = 0;
2656 #endif
2657
2658 #if GTK_CHECK_VERSION(2,2,0)
2659 if (w > mon_size.width)
2660 w = mon_size.width - 10;
2661
2662 if (h > mon_size.height)
2663 h = mon_size.height - 10;
2664 #endif
2665
2666 if (GTK_WIDGET_NO_WINDOW(gtkblist->window))
2667 y+=gtkblist->window->allocation.y;
2668
2669 x -= ((w >> 1) + 4);
2670
2671 if ((y + h + 4) > scr_h || tooltip_top)
2672 y = y - h - 5;
2673 else
2674 y = y + 6;
2675
2676 if (y < mon_size.y)
2677 y = mon_size.y;
2678
2679 if (y != mon_size.y) {
2680 if ((x + w) > scr_w)
2681 x -= (x + w + 5) - scr_w;
2682 else if (x < mon_size.x)
2683 x = mon_size.x;
2684 } else {
2685 x -= (w / 2 + 10);
2686 if (x < mon_size.x)
2687 x = mon_size.x;
2688 }
2689
2690 gtk_widget_set_size_request(gtkblist->tipwindow, w, h);
2691 gtk_window_move(GTK_WINDOW(gtkblist->tipwindow), x, y);
2692 gtk_widget_show(gtkblist->tipwindow);
2693
2694 return FALSE;
2695 }
2696
2697 static gboolean pidgin_blist_drag_motion_cb(GtkWidget *tv, GdkDragContext *drag_context,
2698 gint x, gint y, guint time, gpointer user_data)
2699 {
2700 GtkTreePath *path;
2701 int delay;
2702
2703 /*
2704 * When dragging a buddy into a contact, this is the delay before
2705 * the contact auto-expands.
2706 */
2707 delay = 900;
2708
2709 if (gtkblist->drag_timeout) {
2710 if ((y > gtkblist->tip_rect.y) && ((y - gtkblist->tip_rect.height) < gtkblist->tip_rect.y))
2711 return FALSE;
2712 /* We've left the cell. Remove the timeout and create a new one below */
2713 g_source_remove(gtkblist->drag_timeout);
2714 }
2715
2716 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), x, y, &path, NULL, NULL, NULL);
2717 gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, &gtkblist->tip_rect);
2718
2719 if (path)
2720 gtk_tree_path_free(path);
2721 gtkblist->drag_timeout = g_timeout_add(delay, (GSourceFunc)pidgin_blist_expand_timeout, tv);
2722
2723 if (gtkblist->mouseover_contact) {
2724 if ((y < gtkblist->contact_rect.y) || ((y - gtkblist->contact_rect.height) > gtkblist->contact_rect.y)) {
2725 pidgin_blist_collapse_contact_cb(NULL, gtkblist->mouseover_contact);
2726 gtkblist->mouseover_contact = NULL;
2727 }
2728 }
2729
2730 return FALSE;
2731 }
2732
2733 static gboolean pidgin_blist_motion_cb (GtkWidget *tv, GdkEventMotion *event, gpointer null)
2734 {
2735 GtkTreePath *path;
2736 int delay;
2737
2738 delay = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/tooltip_delay");
2739
2740 if (delay == 0)
2741 return FALSE;
2742
2743 if (gtkblist->timeout) {
2744 if ((event->y > gtkblist->tip_rect.y) && ((event->y - gtkblist->tip_rect.height) < gtkblist->tip_rect.y))
2745 return FALSE;
2746 /* We've left the cell. Remove the timeout and create a new one below */
2747 pidgin_blist_tooltip_destroy();
2748 g_source_remove(gtkblist->timeout);
2749 }
2750
2751 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), event->x, event->y, &path, NULL, NULL, NULL);
2752 gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, &gtkblist->tip_rect);
2753
2754 if (path)
2755 gtk_tree_path_free(path);
2756 gtkblist->timeout = g_timeout_add(delay, (GSourceFunc)pidgin_blist_tooltip_timeout, tv);
2757
2758 if (gtkblist->mouseover_contact) {
2759 if ((event->y < gtkblist->contact_rect.y) || ((event->y - gtkblist->contact_rect.height) > gtkblist->contact_rect.y)) {
2760 pidgin_blist_collapse_contact_cb(NULL, gtkblist->mouseover_contact);
2761 gtkblist->mouseover_contact = NULL;
2762 }
2763 }
2764
2765 return FALSE;
2766 }
2767
2768 static void pidgin_blist_leave_cb (GtkWidget *w, GdkEventCrossing *e, gpointer n)
2769 {
2770
2771 if (gtkblist->timeout) {
2772 g_source_remove(gtkblist->timeout);
2773 gtkblist->timeout = 0;
2774 }
2775
2776 if (gtkblist->drag_timeout) {
2777 g_source_remove(gtkblist->drag_timeout);
2778 gtkblist->drag_timeout = 0;
2779 }
2780
2781 pidgin_blist_tooltip_destroy();
2782
2783 if (gtkblist->mouseover_contact &&
2784 !((e->x > gtkblist->contact_rect.x) && (e->x < (gtkblist->contact_rect.x + gtkblist->contact_rect.width)) &&
2785 (e->y > gtkblist->contact_rect.y) && (e->y < (gtkblist->contact_rect.y + gtkblist->contact_rect.height)))) {
2786 pidgin_blist_collapse_contact_cb(NULL, gtkblist->mouseover_contact);
2787 gtkblist->mouseover_contact = NULL;
2788 }
2789 }
2790
2791 static void
2792 toggle_debug(void)
2793 {
2794 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/debug/enabled",
2795 !purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/debug/enabled"));
2796 }
2797
2798
2799 /***************************************************
2800 * Crap *
2801 ***************************************************/
2802 static GtkItemFactoryEntry blist_menu[] =
2803 {
2804 /* Buddies menu */
2805 { N_("/_Buddies"), NULL, NULL, 0, "<Branch>", NULL },
2806 { N_("/Buddies/New Instant _Message..."), "<CTL>M", pidgindialogs_im, 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW },
2807 { N_("/Buddies/Join a _Chat..."), "<CTL>C", pidgin_blist_joinchat_show, 0, "<Item>", NULL },
2808 { N_("/Buddies/Get User _Info..."), "<CTL>I", pidgindialogs_info, 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_USER_INFO },
2809 { N_("/Buddies/View User _Log..."), "<CTL>L", pidgindialogs_log, 0, "<Item>", NULL },
2810 { "/Buddies/sep1", NULL, NULL, 0, "<Separator>", NULL },
2811 { N_("/Buddies/Show _Offline Buddies"), NULL, pidgin_blist_edit_mode_cb, 1, "<CheckItem>", NULL },
2812 { N_("/Buddies/Show _Empty Groups"), NULL, pidgin_blist_show_empty_groups_cb, 1, "<CheckItem>", NULL },
2813 { N_("/Buddies/Show Buddy _Details"), NULL, pidgin_blist_buddy_details_cb, 1, "<CheckItem>", NULL },
2814 { N_("/Buddies/Show Idle _Times"), NULL, pidgin_blist_show_idle_time_cb, 1, "<CheckItem>", NULL },
2815 { N_("/Buddies/_Sort Buddies"), NULL, NULL, 0, "<Branch>", NULL },
2816 { "/Buddies/sep2", NULL, NULL, 0, "<Separator>", NULL },
2817 { N_("/Buddies/_Add Buddy..."), "<CTL>B", pidgin_blist_add_buddy_cb, 0, "<StockItem>", GTK_STOCK_ADD },
2818 { N_("/Buddies/Add C_hat..."), NULL, pidgin_blist_add_chat_cb, 0, "<StockItem>", GTK_STOCK_ADD },
2819 { N_("/Buddies/Add _Group..."), NULL, purple_blist_request_add_group, 0, "<StockItem>", GTK_STOCK_ADD },
2820 { "/Buddies/sep3", NULL, NULL, 0, "<Separator>", NULL },
2821 { N_("/Buddies/_Quit"), "<CTL>Q", purple_core_quit, 0, "<StockItem>", GTK_STOCK_QUIT },
2822
2823 /* Accounts menu */
2824 { N_("/_Accounts"), NULL, NULL, 0, "<Branch>", NULL },
2825 { N_("/Accounts/Add\\/Edit"), "<CTL>A", pidgin_accounts_window_show, 0, "<Item>", NULL },
2826
2827 /* Tools */
2828 { N_("/_Tools"), NULL, NULL, 0, "<Branch>", NULL },
2829 { N_("/Tools/Buddy _Pounces"), NULL, pidgin_pounces_manager_show, 0, "<Item>", NULL },
2830 { N_("/Tools/Plu_gins"), "<CTL>U", pidgin_plugin_dialog_show, 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_PLUGINS },
2831 { N_("/Tools/Pr_eferences"), "<CTL>P", pidgin_prefs_show, 0, "<StockItem>", GTK_STOCK_PREFERENCES },
2832 { N_("/Tools/Pr_ivacy"), NULL, pidgin_privacy_dialog_show, 0, "<Item>", NULL },
2833 { "/Tools/sep2", NULL, NULL, 0, "<Separator>", NULL },
2834 { N_("/Tools/_File Transfers"), "<CTL>T", pidginxfer_dialog_show, 0, "<Item>", NULL },
2835 { N_("/Tools/R_oom List"), NULL, pidgin_roomlist_dialog_show, 0, "<Item>", NULL },
2836 { N_("/Tools/System _Log"), NULL, gtk_blist_show_systemlog_cb, 0, "<Item>", NULL },
2837 { "/Tools/sep3", NULL, NULL, 0, "<Separator>", NULL },
2838 { N_("/Tools/Mute _Sounds"), "<CTL>S", pidgin_blist_mute_sounds_cb, 0, "<CheckItem>", NULL },
2839
2840 /* Help */
2841 { N_("/_Help"), NULL, NULL, 0, "<Branch>", NULL },
2842 { N_("/Help/Online _Help"), "F1", gtk_blist_show_onlinehelp_cb, 0, "<StockItem>", GTK_STOCK_HELP },
2843 { N_("/Help/_Debug Window"), NULL, toggle_debug, 0, "<Item>", NULL },
2844 { N_("/Help/_About"), NULL, pidgindialogs_about, 0, "<StockItem>", PIDGIN_STOCK_ABOUT },
2845 };
2846
2847 /*********************************************************
2848 * Private Utility functions *
2849 *********************************************************/
2850
2851 static char *pidgin_get_tooltip_text(PurpleBlistNode *node, gboolean full)
2852 {
2853 GString *str = g_string_new("");
2854 PurplePlugin *prpl;
2855 PurplePluginProtocolInfo *prpl_info = NULL;
2856 char *tmp;
2857
2858 if (PURPLE_BLIST_NODE_IS_CHAT(node))
2859 {
2860 PurpleChat *chat;
2861 GList *cur;
2862 struct proto_chat_entry *pce;
2863 char *name, *value;
2864
2865 chat = (PurpleChat *)node;
2866 prpl = purple_find_prpl(purple_account_get_protocol_id(chat->account));
2867 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
2868
2869 if (g_list_length(purple_connections_get_all()) > 1)
2870 {
2871 tmp = g_markup_escape_text(chat->account->username, -1);
2872 g_string_append_printf(str, _("\n<b>Account:</b> %s"), tmp);
2873 g_free(tmp);
2874 }
2875
2876 if (prpl_info->chat_info != NULL)
2877 cur = prpl_info->chat_info(chat->account->gc);
2878 else
2879 cur = NULL;
2880
2881 while (cur != NULL)
2882 {
2883 pce = cur->data;
2884
2885 if (!pce->secret && (!pce->required &&
2886 g_hash_table_lookup(chat->components, pce->identifier) == NULL))
2887 {
2888 tmp = purple_text_strip_mnemonic(pce->label);
2889 name = g_markup_escape_text(tmp, -1);
2890 g_free(tmp);
2891 value = g_markup_escape_text(g_hash_table_lookup(
2892 chat->components, pce->identifier), -1);
2893 g_string_append_printf(str, "\n<b>%s</b> %s",
2894 name ? name : "",
2895 value ? value : "");
2896 g_free(name);
2897 g_free(value);
2898 }
2899
2900 g_free(pce);
2901 cur = g_list_remove(cur, pce);
2902 }
2903 }
2904 else if (PURPLE_BLIST_NODE_IS_CONTACT(node) || PURPLE_BLIST_NODE_IS_BUDDY(node))
2905 {
2906 /* NOTE: THIS FUNCTION IS NO LONGER CALLED FOR CONTACTS.
2907 * It is only called by create_tip_for_node(), and create_tip_for_node() is never called for a contact.
2908 */
2909 PurpleContact *c;
2910 PurpleBuddy *b;
2911 PurplePresence *presence;
2912 PurpleNotifyUserInfo *user_info;
2913 char *tmp;
2914 time_t idle_secs, signon;
2915
2916 if (PURPLE_BLIST_NODE_IS_CONTACT(node))
2917 {
2918 c = (PurpleContact *)node;
2919 b = purple_contact_get_priority_buddy(c);
2920 }
2921 else
2922 {
2923 b = (PurpleBuddy *)node;
2924 c = purple_buddy_get_contact(b);
2925 }
2926
2927 prpl = purple_find_prpl(purple_account_get_protocol_id(b->account));
2928 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
2929
2930 presence = purple_buddy_get_presence(b);
2931 user_info = purple_notify_user_info_new();
2932
2933 /* Account */
2934 if (full && g_list_length(purple_connections_get_all()) > 1)
2935 {
2936 tmp = g_markup_escape_text(purple_account_get_username(
2937 purple_buddy_get_account(b)), -1);
2938 purple_notify_user_info_add_pair(user_info, _("Account"), tmp);
2939 g_free(tmp);
2940 }
2941
2942 /* Alias */
2943 /* If there's not a contact alias, the node is being displayed with
2944 * this alias, so there's no point in showing it in the tooltip. */
2945 if (full && b->alias != NULL && b->alias[0] != '\0' &&
2946 (c->alias != NULL && c->alias[0] != '\0') &&
2947 strcmp(c->alias, b->alias) != 0)
2948 {
2949 tmp = g_markup_escape_text(b->alias, -1);
2950 purple_notify_user_info_add_pair(user_info, _("Buddy Alias"), tmp);
2951 g_free(tmp);
2952 }
2953
2954 /* Nickname/Server Alias */
2955 /* I'd like to only show this if there's a contact or buddy
2956 * alias, but many people on MSN set long nicknames, which
2957 * get ellipsized, so the only way to see the whole thing is
2958 * to look at the tooltip. */
2959 if (full && b->server_alias != NULL && b->server_alias[0] != '\0')
2960 {
2961 tmp = g_markup_escape_text(b->server_alias, -1);
2962 purple_notify_user_info_add_pair(user_info, _("Nickname"), tmp);
2963 g_free(tmp);
2964 }
2965
2966 /* Logged In */
2967 signon = purple_presence_get_login_time(presence);
2968 if (full && PURPLE_BUDDY_IS_ONLINE(b) && signon > 0)
2969 {
2970 tmp = purple_str_seconds_to_string(time(NULL) - signon);
2971 purple_notify_user_info_add_pair(user_info, _("Logged In"), tmp);
2972 g_free(tmp);
2973 }
2974
2975 /* Idle */
2976 if (purple_presence_is_idle(presence))
2977 {
2978 idle_secs = purple_presence_get_idle_time(presence);
2979 if (idle_secs > 0)
2980 {
2981 tmp = purple_str_seconds_to_string(time(NULL) - idle_secs);
2982 purple_notify_user_info_add_pair(user_info, _("Idle"), tmp);
2983 g_free(tmp);
2984 }
2985 }
2986
2987 /* Last Seen */
2988 if (full && !PURPLE_BUDDY_IS_ONLINE(b))
2989 {
2990 struct _pidgin_blist_node *gtknode = ((PurpleBlistNode *)c)->ui_data;
2991 PurpleBlistNode *bnode;
2992 int lastseen = 0;
2993
2994 if (!gtknode->contact_expanded || PURPLE_BLIST_NODE_IS_CONTACT(node))
2995 {
2996 /* We're either looking at a buddy for a collapsed contact or
2997 * an expanded contact itself so we show the most recent
2998 * (largest) last_seen time for any of the buddies under
2999 * the contact. */
3000 for (bnode = ((PurpleBlistNode *)c)->child ; bnode != NULL ; bnode = bnode->next)
3001 {
3002 int value = purple_blist_node_get_int(bnode, "last_seen");
3003 if (value > lastseen)
3004 lastseen = value;
3005 }
3006 }
3007 else
3008 {
3009 /* We're dealing with a buddy under an expanded contact,
3010 * so we show the last_seen time for the buddy. */
3011 lastseen = purple_blist_node_get_int(&b->node, "last_seen");
3012 }
3013
3014 if (lastseen > 0)
3015 {
3016 tmp = purple_str_seconds_to_string(time(NULL) - lastseen);
3017 purple_notify_user_info_add_pair(user_info, _("Last Seen"), tmp);
3018 g_free(tmp);
3019 }
3020 }
3021
3022
3023 /* Offline? */
3024 /* FIXME: Why is this status special-cased by the core? -- rlaager */
3025 if (!PURPLE_BUDDY_IS_ONLINE(b)) {
3026 purple_notify_user_info_add_pair(user_info, _("Status"), _("Offline"));
3027 }
3028
3029 if (prpl_info && prpl_info->tooltip_text)
3030 {
3031 /* Additional text from the PRPL */
3032 prpl_info->tooltip_text(b, user_info, full);
3033 }
3034
3035 /* These are Easter Eggs. Patches to remove them will be rejected. */
3036 if (!g_ascii_strcasecmp(b->name, "robflynn"))
3037 purple_notify_user_info_add_pair(user_info, _("Description"), _("Spooky"));
3038 if (!g_ascii_strcasecmp(b->name, "seanegn"))
3039 purple_notify_user_info_add_pair(user_info, _("Status"), _("Awesome"));
3040 if (!g_ascii_strcasecmp(b->name, "chipx86"))
3041 purple_notify_user_info_add_pair(user_info, _("Status"), _("Rockin'"));
3042
3043 tmp = purple_notify_user_info_get_text_with_newline(user_info, "\n");
3044 g_string_append(str, tmp);
3045 g_free(tmp);
3046
3047 purple_notify_user_info_destroy(user_info);
3048 }
3049
3050 purple_signal_emit(pidgin_blist_get_handle(),
3051 "drawing-tooltip", node, str, full);
3052
3053 return g_string_free(str, FALSE);
3054 }
3055
3056 GdkPixbuf *
3057 pidgin_blist_get_emblem(PurpleBlistNode *node)
3058 {
3059 PurpleBuddy *buddy = NULL;
3060 struct _pidgin_blist_node *gtknode = node->ui_data;
3061 struct _pidgin_blist_node *gtkbuddynode = NULL;
3062 PurplePlugin *prpl;
3063 PurplePluginProtocolInfo *prpl_info;
3064 const char *name = NULL;
3065 char *filename, *path;
3066 GdkPixbuf *ret;
3067 PurplePresence *p;
3068
3069
3070
3071 if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
3072 if(!gtknode->contact_expanded) {
3073 buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
3074 gtkbuddynode = ((PurpleBlistNode*)buddy)->ui_data;
3075 }
3076 } else if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
3077 buddy = (PurpleBuddy*)node;
3078 gtkbuddynode = node->ui_data;
3079 if (((struct _pidgin_blist_node*)(node->parent->ui_data))->contact_expanded)
3080 return pidgin_create_prpl_icon(((PurpleBuddy*)node)->account, PIDGIN_PRPL_ICON_SMALL);
3081 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
3082 return pidgin_create_prpl_icon(((PurpleChat*)node)->account, PIDGIN_PRPL_ICON_SMALL);
3083 } else {
3084 return NULL;
3085 }
3086
3087 if (!purple_privacy_check(buddy->account, purple_buddy_get_name(buddy))) {
3088 path = g_build_filename(DATADIR, "pixmaps", "pidgin", "emblems", "16", "blocked.png", NULL);
3089 ret = gdk_pixbuf_new_from_file(path, NULL);
3090 g_free(path);
3091 return ret;
3092 }
3093
3094 p = purple_buddy_get_presence(buddy);
3095 if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_MOBILE)) {
3096 path = g_build_filename(DATADIR, "pixmaps", "pidgin", "emblems", "16", "mobile.png", NULL);
3097 ret = gdk_pixbuf_new_from_file(path, NULL);
3098 g_free(path);
3099 return ret;
3100 }
3101
3102 prpl = purple_find_prpl(purple_account_get_protocol_id(buddy->account));
3103 if (!prpl)
3104 return NULL;
3105
3106 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
3107 if (prpl_info && prpl_info->list_emblem)
3108 name = prpl_info->list_emblem(buddy);
3109
3110 if (name == NULL)
3111 return NULL;
3112
3113 filename = g_strdup_printf("%s.png", name);
3114
3115 path = g_build_filename(DATADIR, "pixmaps", "pidgin", "emblems", "16", filename, NULL);
3116 ret = gdk_pixbuf_new_from_file(path, NULL);
3117
3118 g_free(filename);
3119 g_free(path);
3120
3121 return ret;
3122 }
3123
3124
3125 GdkPixbuf *
3126 pidgin_blist_get_status_icon(PurpleBlistNode *node, PidginStatusIconSize size)
3127 {
3128 GdkPixbuf *ret;
3129 const char *protoname = NULL;
3130 struct _pidgin_blist_node *gtknode = node->ui_data;
3131 struct _pidgin_blist_node *gtkbuddynode = NULL;
3132 PurpleBuddy *buddy = NULL;
3133 PurpleChat *chat = NULL;
3134 GtkIconSize icon_size = gtk_icon_size_from_name((size == PIDGIN_STATUS_ICON_LARGE) ? PIDGIN_ICON_SIZE_TANGO_SMALL :
3135 PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL);
3136
3137 if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
3138 if(!gtknode->contact_expanded) {
3139 buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
3140 gtkbuddynode = ((PurpleBlistNode*)buddy)->ui_data;
3141 }
3142 } else if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
3143 buddy = (PurpleBuddy*)node;
3144 gtkbuddynode = node->ui_data;
3145 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
3146 chat = (PurpleChat*)node;
3147 } else {
3148 return NULL;
3149 }
3150
3151 if(buddy || chat) {
3152 PurpleAccount *account;
3153 PurplePlugin *prpl;
3154 PurplePluginProtocolInfo *prpl_info;
3155
3156 if(buddy)
3157 account = buddy->account;
3158 else
3159 account = chat->account;
3160
3161 prpl = purple_find_prpl(purple_account_get_protocol_id(account));
3162 if(!prpl)
3163 return NULL;
3164
3165 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
3166
3167 if(prpl_info && prpl_info->list_icon) {
3168 protoname = prpl_info->list_icon(account, buddy);
3169 }
3170 }
3171
3172 if(buddy) {
3173 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
3174 purple_buddy_get_name(buddy),
3175 purple_buddy_get_account(buddy));
3176 PurplePresence *p;
3177 if(conv != NULL) {
3178 PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
3179 if(gtkconv != NULL && pidgin_conv_is_hidden(gtkconv) && size == PIDGIN_STATUS_ICON_SMALL) {
3180 return gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_MESSAGE,
3181 icon_size, "GtkTreeView");
3182 }
3183 }
3184 p = purple_buddy_get_presence(buddy);
3185
3186 if (PURPLE_BUDDY_IS_ONLINE(buddy) && gtkbuddynode && gtkbuddynode->recent_signonoff)
3187 ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_LOGIN,
3188 icon_size, "GtkTreeView");
3189 else if (gtkbuddynode && gtkbuddynode->recent_signonoff)
3190 ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_LOGOUT,
3191 icon_size, "GtkTreeView");
3192 else if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_UNAVAILABLE))
3193 if (purple_presence_is_idle(p) && size == PIDGIN_STATUS_ICON_SMALL)
3194 ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_BUSY_I,
3195 icon_size, "GtkTreeView");
3196 else
3197 ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_BUSY,
3198 icon_size, "GtkTreeView");
3199 else if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_AWAY))
3200 if (purple_presence_is_idle(p) && size == PIDGIN_STATUS_ICON_SMALL)
3201 ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_AWAY_I,
3202 icon_size, "GtkTreeView");
3203 else
3204 ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_AWAY,
3205 icon_size, "GtkTreeView");
3206 else if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_EXTENDED_AWAY))
3207 if (purple_presence_is_idle(p) && size == PIDGIN_STATUS_ICON_SMALL)
3208 ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_XA_I,
3209 icon_size, "GtkTreeView");
3210 else
3211 ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_XA,
3212 icon_size, "GtkTreeView");
3213 else if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_OFFLINE))
3214 ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_OFFLINE,
3215 icon_size, "GtkTreeView");
3216 else if (purple_presence_is_idle(p) && size == PIDGIN_STATUS_ICON_SMALL)
3217 ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_AVAILABLE_I,
3218 icon_size, "GtkTreeView");
3219 else
3220 ret = gtk_widget_render_icon(GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_AVAILABLE,
3221 icon_size, "GtkTreeView");
3222 } else if (chat) {
3223 ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_CHAT,
3224 icon_size, "GtkTreeView");
3225 } else {
3226 ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_PERSON,
3227 icon_size, "GtkTreeView");
3228 }
3229
3230 return ret;
3231 }
3232
3233 static gchar *pidgin_blist_get_name_markup(PurpleBuddy *b, gboolean selected)
3234 {
3235 const char *name;
3236 char *esc, *text = NULL;
3237 PurplePlugin *prpl;
3238 PurplePluginProtocolInfo *prpl_info = NULL;
3239 PurpleContact *contact;
3240 PurplePresence *presence;
3241 struct _pidgin_blist_node *gtkcontactnode = NULL;
3242 char *idletime = NULL, *statustext = NULL;
3243 time_t t;
3244 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
3245 purple_buddy_get_name(b),
3246 purple_buddy_get_account(b));
3247 PidginConversation *gtkconv;
3248 gboolean hidden_conv = FALSE;
3249
3250 if(conv != NULL) {
3251 gtkconv = PIDGIN_CONVERSATION(conv);
3252 if(gtkconv != NULL && pidgin_conv_is_hidden(gtkconv)) {
3253 hidden_conv = TRUE;
3254 }
3255 }
3256
3257 /* XXX Good luck cleaning up this crap */
3258
3259 contact = (PurpleContact*)((PurpleBlistNode*)b)->parent;
3260 if(contact)
3261 gtkcontactnode = ((PurpleBlistNode*)contact)->ui_data;
3262
3263 if(gtkcontactnode && !gtkcontactnode->contact_expanded && contact->alias)
3264 name = contact->alias;
3265 else
3266 name = purple_buddy_get_alias(b);
3267 esc = g_markup_escape_text(name, strlen(name));
3268
3269 presence = purple_buddy_get_presence(b);
3270
3271 if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons"))
3272 {
3273 if (!selected && purple_presence_is_idle(presence))
3274 {
3275 text = g_strdup_printf("<span color='%s'>%s</span>",
3276 dim_grey(), esc);
3277 g_free(esc);
3278 if (hidden_conv) {
3279 char *tmp = text;
3280 text = g_strdup_printf("<b>%s</b>", text);
3281 g_free(tmp);
3282 }
3283 return text;
3284 }
3285 else
3286 if (hidden_conv) {
3287 char *tmp = esc;
3288 esc = g_strdup_printf("<b>%s</b>", esc);
3289 g_free(tmp);
3290 }
3291 return esc;
3292 }
3293
3294 prpl = purple_find_prpl(purple_account_get_protocol_id(b->account));
3295
3296 if (prpl != NULL)
3297 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
3298
3299 if (prpl_info && prpl_info->status_text && b->account->gc) {
3300 char *tmp = prpl_info->status_text(b);
3301 const char *end;
3302
3303 if(tmp && !g_utf8_validate(tmp, -1, &end)) {
3304 char *new = g_strndup(tmp,
3305 g_utf8_pointer_to_offset(tmp, end));
3306 g_free(tmp);
3307 tmp = new;
3308 }
3309
3310 #if !GTK_CHECK_VERSION(2,6,0)
3311 if(tmp) {
3312 char buf[32];
3313 char *c = tmp;
3314 int length = 0, vis=0;
3315 gboolean inside = FALSE;
3316 g_strdelimit(tmp, "\n", ' ');
3317 purple_str_strip_char(tmp, '\r');
3318
3319 while(*c && vis < 20) {
3320 if(*c == '&')
3321 inside = TRUE;
3322 else if(*c == ';')
3323 inside = FALSE;
3324 if(!inside)
3325 vis++;
3326 c = g_utf8_next_char(c); /* this is fun */
3327 }
3328
3329 length = c - tmp;
3330
3331 if(vis == 20)
3332 g_snprintf(buf, sizeof(buf), "%%.%ds...", length);
3333 else
3334 g_snprintf(buf, sizeof(buf), "%%s ");
3335
3336 statustext = g_strdup_printf(buf, tmp);
3337
3338 g_free(tmp);
3339 }
3340 #else
3341 if(tmp) {
3342 g_strdelimit(tmp, "\n", ' ');
3343 purple_str_strip_char(tmp, '\r');
3344 }
3345 statustext = tmp;
3346 #endif
3347 }
3348
3349 if(!purple_presence_is_online(presence) && !statustext)
3350 statustext = g_strdup(_("Offline"));
3351 else if (!statustext)
3352 text = g_strdup(esc);
3353
3354 if (purple_presence_is_idle(presence)) {
3355 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_idle_time")) {
3356 time_t idle_secs = purple_presence_get_idle_time(presence);
3357
3358 if (idle_secs > 0) {
3359 int ihrs, imin;
3360
3361 time(&t);
3362 ihrs = (t - idle_secs) / 3600;
3363 imin = ((t - idle_secs) / 60) % 60;
3364
3365 if (ihrs)
3366 idletime = g_strdup_printf(_("Idle %dh %02dm"), ihrs, imin);
3367 else
3368 idletime = g_strdup_printf(_("Idle %dm"), imin);
3369 }
3370 else
3371 idletime = g_strdup(_("Idle"));
3372
3373 if (!selected)
3374 text = g_strdup_printf("<span color='%s'>%s</span>\n"
3375 "<span color='%s' size='smaller'>%s%s%s</span>",
3376 dim_grey(), esc, dim_grey(),
3377 idletime != NULL ? idletime : "",
3378 (idletime != NULL && statustext != NULL) ? " - " : "",
3379 statustext != NULL ? statustext : "");
3380 }
3381 else if (!selected && !statustext) /* We handle selected text later */
3382 text = g_strdup_printf("<span color='%s'>%s</span>", dim_grey(), esc);
3383 else if (!selected && !text)
3384 text = g_strdup_printf("<span color='%s'>%s</span>\n"
3385 "<span color='%s' size='smaller'>%s</span>",
3386 dim_grey(), esc, dim_grey(),
3387 statustext != NULL ? statustext : "");
3388 }
3389
3390 /* Not idle and not selected */
3391 else if (!selected && !text)
3392 {
3393 text = g_strdup_printf("%s\n"
3394 "<span color='%s' size='smaller'>%s</span>",
3395 esc, dim_grey(),
3396 statustext != NULL ? statustext : "");
3397 }
3398
3399 /* It is selected. */
3400 if ((selected && !text) || (selected && idletime))
3401 text = g_strdup_printf("%s\n"
3402 "<span size='smaller'>%s%s%s</span>",
3403 esc,
3404 idletime != NULL ? idletime : "",
3405 (idletime != NULL && statustext != NULL) ? " - " : "",
3406 statustext != NULL ? statustext : "");
3407
3408 g_free(idletime);
3409 g_free(statustext);
3410 g_free(esc);
3411
3412 if (hidden_conv) {
3413 char *tmp = text;
3414 text = g_strdup_printf("<b>%s</b>", tmp);
3415 g_free(tmp);
3416 }
3417
3418 return text;
3419 }
3420
3421 static void pidgin_blist_restore_position()
3422 {
3423 int blist_x, blist_y, blist_width, blist_height;
3424
3425 blist_width = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/width");
3426
3427 /* if the window exists, is hidden, we're saving positions, and the
3428 * position is sane... */
3429 if (gtkblist && gtkblist->window &&
3430 !GTK_WIDGET_VISIBLE(gtkblist->window) && blist_width != 0) {
3431
3432 blist_x = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/x");
3433 blist_y = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/y");
3434 blist_height = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/height");
3435
3436 /* ...check position is on screen... */
3437 if (blist_x >= gdk_screen_width())
3438 blist_x = gdk_screen_width() - 100;
3439 else if (blist_x + blist_width < 0)
3440 blist_x = 100;
3441
3442 if (blist_y >= gdk_screen_height())
3443 blist_y = gdk_screen_height() - 100;
3444 else if (blist_y + blist_height < 0)
3445 blist_y = 100;
3446
3447 /* ...and move it back. */
3448 gtk_window_move(GTK_WINDOW(gtkblist->window), blist_x, blist_y);
3449 gtk_window_resize(GTK_WINDOW(gtkblist->window), blist_width, blist_height);
3450 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/list_maximized"))
3451 gtk_window_maximize(GTK_WINDOW(gtkblist->window));
3452 }
3453 }
3454
3455 static gboolean pidgin_blist_refresh_timer(PurpleBuddyList *list)
3456 {
3457 PurpleBlistNode *gnode, *cnode;
3458
3459 if (gtk_blist_obscured || !GTK_WIDGET_VISIBLE(gtkblist->window))
3460 return TRUE;
3461
3462 for(gnode = list->root; gnode; gnode = gnode->next) {
3463 if(!PURPLE_BLIST_NODE_IS_GROUP(gnode))
3464 continue;
3465 for(cnode = gnode->child; cnode; cnode = cnode->next) {
3466 if(PURPLE_BLIST_NODE_IS_CONTACT(cnode)) {
3467 PurpleBuddy *buddy;
3468
3469 buddy = purple_contact_get_priority_buddy((PurpleContact*)cnode);
3470
3471 if (buddy &&
3472 purple_presence_is_idle(purple_buddy_get_presence(buddy)))
3473 pidgin_blist_update_contact(list, (PurpleBlistNode*)buddy);
3474 }
3475 }
3476 }
3477
3478 /* keep on going */
3479 return TRUE;
3480 }
3481
3482 static void pidgin_blist_hide_node(PurpleBuddyList *list, PurpleBlistNode *node, gboolean update)
3483 {
3484 struct _pidgin_blist_node *gtknode = (struct _pidgin_blist_node *)node->ui_data;
3485 GtkTreeIter iter;
3486
3487 if (!gtknode || !gtknode->row || !gtkblist)
3488 return;
3489
3490 if(gtkblist->selected_node == node)
3491 gtkblist->selected_node = NULL;
3492 if (get_iter_from_node(node, &iter)) {
3493 gtk_tree_store_remove(gtkblist->treemodel, &iter);
3494 if(update && (PURPLE_BLIST_NODE_IS_CONTACT(node) ||
3495 PURPLE_BLIST_NODE_IS_BUDDY(node) || PURPLE_BLIST_NODE_IS_CHAT(node))) {
3496 pidgin_blist_update(list, node->parent);
3497 }
3498 }
3499 gtk_tree_row_reference_free(gtknode->row);
3500 gtknode->row = NULL;
3501 }
3502
3503 static const char *require_connection[] =
3504 {
3505 N_("/Buddies/New Instant Message..."),
3506 N_("/Buddies/Join a Chat..."),
3507 N_("/Buddies/Get User Info..."),
3508 N_("/Buddies/Add Buddy..."),
3509 N_("/Buddies/Add Chat..."),
3510 N_("/Buddies/Add Group..."),
3511 };
3512
3513 static const int require_connection_size = sizeof(require_connection)
3514 / sizeof(*require_connection);
3515
3516 /**
3517 * Rebuild dynamic menus and make menu items sensitive/insensitive
3518 * where appropriate.
3519 */
3520 static void
3521 update_menu_bar(PidginBuddyList *gtkblist)
3522 {
3523 GtkWidget *widget;
3524 gboolean sensitive;
3525 int i;
3526
3527 g_return_if_fail(gtkblist != NULL);
3528
3529 pidgin_blist_update_accounts_menu();
3530
3531 sensitive = (purple_connections_get_all() != NULL);
3532
3533 for (i = 0; i < require_connection_size; i++)
3534 {
3535 widget = gtk_item_factory_get_widget(gtkblist->ift, require_connection[i]);
3536 gtk_widget_set_sensitive(widget, sensitive);
3537 }
3538
3539 widget = gtk_item_factory_get_widget(gtkblist->ift, N_("/Buddies/Join a Chat..."));
3540 gtk_widget_set_sensitive(widget, pidgin_blist_joinchat_is_showable());
3541
3542 widget = gtk_item_factory_get_widget(gtkblist->ift, N_("/Buddies/Add Chat..."));
3543 gtk_widget_set_sensitive(widget, pidgin_blist_joinchat_is_showable());
3544
3545 widget = gtk_item_factory_get_widget(gtkblist->ift, N_("/Tools/Buddy Pounces"));
3546 gtk_widget_set_sensitive(widget, (purple_accounts_get_all() != NULL));
3547
3548 widget = gtk_item_factory_get_widget(gtkblist->ift, N_("/Tools/Privacy"));
3549 gtk_widget_set_sensitive(widget, (purple_connections_get_all() != NULL));
3550
3551 widget = gtk_item_factory_get_widget(gtkblist->ift, N_("/Tools/Room List"));
3552 gtk_widget_set_sensitive(widget, pidgin_roomlist_is_showable());
3553 }
3554
3555 static void
3556 sign_on_off_cb(PurpleConnection *gc, PurpleBuddyList *blist)
3557 {
3558 PidginBuddyList *gtkblist = PIDGIN_BLIST(blist);
3559
3560 update_menu_bar(gtkblist);
3561 }
3562
3563 static void
3564 plugin_changed_cb(PurplePlugin *p, gpointer *data)
3565 {
3566 pidgin_blist_update_plugin_actions();
3567 }
3568
3569 static void
3570 unseen_conv_menu()
3571 {
3572 static GtkWidget *menu = NULL;
3573 GList *convs = NULL;
3574
3575 if (menu) {
3576 gtk_widget_destroy(menu);
3577 menu = NULL;
3578 }
3579
3580 convs = pidgin_conversations_find_unseen_list(PURPLE_CONV_TYPE_IM, PIDGIN_UNSEEN_TEXT, TRUE, 0);
3581 if (!convs)
3582 /* no conversations added, don't show the menu */
3583 return;
3584
3585 menu = gtk_menu_new();
3586
3587 pidgin_conversations_fill_menu(menu, convs);
3588 g_list_free(convs);
3589 gtk_widget_show_all(menu);
3590 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 3,
3591 gtk_get_current_event_time());
3592 }
3593
3594 static gboolean
3595 menutray_press_cb(GtkWidget *widget, GdkEventButton *event)
3596 {
3597 GList *convs;
3598
3599 switch (event->button) {
3600 case 1:
3601 convs = pidgin_conversations_find_unseen_list(PURPLE_CONV_TYPE_IM,
3602 PIDGIN_UNSEEN_TEXT, TRUE, 1);
3603 if (convs) {
3604 purple_conversation_present((PurpleConversation*)convs->data);
3605 g_list_free(convs);
3606 }
3607 break;
3608 case 3:
3609 unseen_conv_menu();
3610 break;
3611 }
3612 return TRUE;
3613 }
3614
3615 static void
3616 conversation_updated_cb(PurpleConversation *conv, PurpleConvUpdateType type,
3617 PidginBuddyList *gtkblist)
3618 {
3619 GList *convs = NULL;
3620 GList *l = NULL;
3621
3622 if (type != PURPLE_CONV_UPDATE_UNSEEN)
3623 return;
3624
3625 if(conv->account != NULL && conv->name != NULL) {
3626 PurpleBuddy *buddy = purple_find_buddy(conv->account, conv->name);
3627 if(buddy != NULL)
3628 pidgin_blist_update_buddy(NULL, (PurpleBlistNode *)buddy, TRUE);
3629 }
3630
3631 if (gtkblist->menutrayicon) {
3632 gtk_widget_destroy(gtkblist->menutrayicon);
3633 gtkblist->menutrayicon = NULL;
3634 }
3635
3636 convs = pidgin_conversations_find_unseen_list(PURPLE_CONV_TYPE_IM, PIDGIN_UNSEEN_TEXT, TRUE, 0);
3637 if (convs) {
3638 GtkWidget *img = NULL;
3639 GString *tooltip_text = NULL;
3640
3641 tooltip_text = g_string_new("");
3642 l = convs;
3643 while (l != NULL) {
3644 if (PIDGIN_IS_PIDGIN_CONVERSATION(l->data)) {
3645 PidginConversation *gtkconv = PIDGIN_CONVERSATION((PurpleConversation *)l->data);
3646
3647 g_string_append_printf(tooltip_text,
3648 ngettext("%d unread message from %s\n", "%d unread messages from %s\n", gtkconv->unseen_count),
3649 gtkconv->unseen_count,
3650 gtk_label_get_text(GTK_LABEL(gtkconv->tab_label)));
3651 }
3652 l = l->next;
3653 }
3654 if(tooltip_text->len > 0) {
3655 /* get rid of the last newline */
3656 g_string_truncate(tooltip_text, tooltip_text->len -1);
3657 img = gtk_image_new_from_stock(PIDGIN_STOCK_TOOLBAR_PENDING,
3658 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL));
3659
3660 gtkblist->menutrayicon = gtk_event_box_new();
3661 gtk_container_add(GTK_CONTAINER(gtkblist->menutrayicon), img);
3662 gtk_widget_show(img);
3663 gtk_widget_show(gtkblist->menutrayicon);
3664 g_signal_connect(G_OBJECT(gtkblist->menutrayicon), "button-press-event", G_CALLBACK(menutray_press_cb), NULL);
3665
3666 pidgin_menu_tray_append(PIDGIN_MENU_TRAY(gtkblist->menutray), gtkblist->menutrayicon, tooltip_text->str);
3667 }
3668 g_string_free(tooltip_text, TRUE);
3669 g_list_free(convs);
3670 }
3671 }
3672
3673 static void
3674 conversation_deleting_cb(PurpleConversation *conv, PidginBuddyList *gtkblist)
3675 {
3676 conversation_updated_cb(conv, PURPLE_CONV_UPDATE_UNSEEN, gtkblist);
3677 }
3678
3679 /**********************************************************************************
3680 * Public API Functions *
3681 **********************************************************************************/
3682
3683 static void pidgin_blist_new_list(PurpleBuddyList *blist)
3684 {
3685 PidginBuddyList *gtkblist;
3686
3687 gtkblist = g_new0(PidginBuddyList, 1);
3688 gtkblist->connection_errors = g_hash_table_new_full(g_direct_hash,
3689 g_direct_equal, NULL, g_free);
3690 blist->ui_data = gtkblist;
3691 }
3692
3693 static void pidgin_blist_new_node(PurpleBlistNode *node)
3694 {
3695 node->ui_data = g_new0(struct _pidgin_blist_node, 1);
3696 }
3697
3698 gboolean pidgin_blist_node_is_contact_expanded(PurpleBlistNode *node)
3699 {
3700 if PURPLE_BLIST_NODE_IS_BUDDY(node)
3701 node = node->parent;
3702
3703 g_return_val_if_fail(PURPLE_BLIST_NODE_IS_CONTACT(node), FALSE);
3704
3705 return ((struct _pidgin_blist_node *)node->ui_data)->contact_expanded;
3706 }
3707
3708 enum {
3709 DRAG_BUDDY,
3710 DRAG_ROW,
3711 DRAG_VCARD,
3712 DRAG_TEXT,
3713 DRAG_URI,
3714 NUM_TARGETS
3715 };
3716
3717 static const char *
3718 item_factory_translate_func (const char *path, gpointer func_data)
3719 {
3720 return _((char *)path);
3721 }
3722
3723 void pidgin_blist_setup_sort_methods()
3724 {
3725 pidgin_blist_sort_method_reg("none", _("Manually"), sort_method_none);
3726 #if GTK_CHECK_VERSION(2,2,1)
3727 pidgin_blist_sort_method_reg("alphabetical", _("Alphabetically"), sort_method_alphabetical);
3728 pidgin_blist_sort_method_reg("status", _("By status"), sort_method_status);
3729 pidgin_blist_sort_method_reg("log_size", _("By log size"), sort_method_log);
3730 #endif
3731 pidgin_blist_sort_method_set(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/blist/sort_type"));
3732 }
3733
3734 static void _prefs_change_redo_list()
3735 {
3736 GtkTreeSelection *sel;
3737 GtkTreeIter iter;
3738 PurpleBlistNode *node = NULL;
3739
3740 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist->treeview));
3741 if (gtk_tree_selection_get_selected(sel, NULL, &iter))
3742 {
3743 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
3744 }
3745
3746 redo_buddy_list(purple_get_blist(), FALSE, FALSE);
3747 #if GTK_CHECK_VERSION(2,6,0)
3748 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(gtkblist->treeview));
3749 #endif
3750
3751 if (node)
3752 {
3753 struct _pidgin_blist_node *gtknode;
3754 GtkTreePath *path;
3755
3756 gtknode = node->ui_data;
3757 if (gtknode && gtknode->row)
3758 {
3759 path = gtk_tree_row_reference_get_path(gtknode->row);
3760 gtk_tree_selection_select_path(sel, path);
3761 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(gtkblist->treeview), path, NULL, FALSE, 0, 0);
3762 gtk_tree_path_free(path);
3763 }
3764 }
3765 }
3766
3767 static void _prefs_change_sort_method(const char *pref_name, PurplePrefType type,
3768 gconstpointer val, gpointer data)
3769 {
3770 if(!strcmp(pref_name, PIDGIN_PREFS_ROOT "/blist/sort_type"))
3771 pidgin_blist_sort_method_set(val);
3772 }
3773
3774 static void account_modified(PurpleAccount *account, PidginBuddyList *gtkblist)
3775 {
3776 GList *list;
3777 if (!gtkblist)
3778 return;
3779
3780 if ((list = purple_accounts_get_all_active()) != NULL) {
3781 gtk_notebook_set_current_page(GTK_NOTEBOOK(gtkblist->notebook), 1);
3782 g_list_free(list);
3783 } else
3784 gtk_notebook_set_current_page(GTK_NOTEBOOK(gtkblist->notebook), 0);
3785
3786 update_menu_bar(gtkblist);
3787 }
3788
3789 static void
3790 account_status_changed(PurpleAccount *account, PurpleStatus *old,
3791 PurpleStatus *new, PidginBuddyList *gtkblist)
3792 {
3793 if (!gtkblist)
3794 return;
3795
3796 update_menu_bar(gtkblist);
3797 }
3798
3799 static gboolean
3800 gtk_blist_window_key_press_cb(GtkWidget *w, GdkEventKey *event, PidginBuddyList *gtkblist)
3801 {
3802 GtkWidget *imhtml;
3803
3804 if (!gtkblist)
3805 return FALSE;
3806
3807 imhtml = gtk_window_get_focus(GTK_WINDOW(gtkblist->window));
3808
3809 if (GTK_IS_IMHTML(imhtml) && gtk_bindings_activate(GTK_OBJECT(imhtml), event->keyval, event->state))
3810 return TRUE;
3811 return FALSE;
3812 }
3813
3814 static gboolean
3815 headline_hover_close(int x, int y)
3816 {
3817 GtkWidget *w = gtkblist->headline_hbox;
3818 if (x <= w->allocation.width && x >= w->allocation.width - HEADLINE_CLOSE_SIZE &&
3819 y >= 0 && y <= HEADLINE_CLOSE_SIZE)
3820 return TRUE;
3821 return FALSE;
3822 }
3823
3824 static gboolean
3825 headline_box_enter_cb(GtkWidget *widget, GdkEventCrossing *event, PidginBuddyList *gtkblist)
3826 {
3827 gdk_window_set_cursor(widget->window, gtkblist->hand_cursor);
3828
3829 if (gtkblist->headline_close) {
3830 #if GTK_CHECK_VERSION(2,2,0)
3831 gdk_draw_pixbuf(widget->window, NULL, gtkblist->headline_close,
3832 #else
3833 gdk_pixbuf_render_to_drawable(gtkblist->headline_close,
3834 GDK_DRAWABLE(widget->window), NULL,
3835 #endif
3836 0, 0,
3837 widget->allocation.width - 2 - HEADLINE_CLOSE_SIZE, 2,
3838 HEADLINE_CLOSE_SIZE, HEADLINE_CLOSE_SIZE,
3839 GDK_RGB_DITHER_NONE, 0, 0);
3840 gtk_paint_focus(widget->style, widget->window, GTK_STATE_PRELIGHT,
3841 NULL, widget, NULL,
3842 widget->allocation.width - HEADLINE_CLOSE_SIZE - 3, 1,
3843 HEADLINE_CLOSE_SIZE + 2, HEADLINE_CLOSE_SIZE + 2);
3844 }
3845
3846 return FALSE;
3847 }
3848
3849 #if 0
3850 static gboolean
3851 headline_box_motion_cb(GtkWidget *widget, GdkEventMotion *event, PidginBuddyList *gtkblist)
3852 {
3853 purple_debug_fatal("motion", "%d %d\n", (int)event->x, (int)event->y);
3854 if (headline_hover_close((int)event->x, (int)event->y))
3855 gtk_paint_focus(widget->style, widget->window, GTK_STATE_PRELIGHT,
3856 NULL, widget, NULL,
3857 widget->allocation.width - HEADLINE_CLOSE_SIZE - 3, 1,
3858 HEADLINE_CLOSE_SIZE + 2, HEADLINE_CLOSE_SIZE + 2);
3859 return FALSE;
3860 }
3861 #endif
3862
3863 static gboolean
3864 headline_box_leave_cb(GtkWidget *widget, GdkEventCrossing *event, PidginBuddyList *gtkblist)
3865 {
3866 gdk_window_set_cursor(widget->window, gtkblist->arrow_cursor);
3867 if (gtkblist->headline_close) {
3868 GdkRectangle rect = {widget->allocation.width - 3 - HEADLINE_CLOSE_SIZE, 1,
3869 HEADLINE_CLOSE_SIZE + 2, HEADLINE_CLOSE_SIZE + 2};
3870 gdk_window_invalidate_rect(widget->window, &rect, TRUE);
3871 }
3872 return FALSE;
3873 }
3874
3875 static void
3876 reset_headline(PidginBuddyList *gtkblist)
3877 {
3878 gtkblist->headline_callback = NULL;
3879 gtkblist->headline_data = NULL;
3880 gtkblist->headline_destroy = NULL;
3881 pidgin_set_urgent(GTK_WINDOW(gtkblist->window), FALSE);
3882 }
3883
3884 static gboolean
3885 headline_click_callback(gpointer data)
3886 {
3887 ((GSourceFunc)gtkblist->headline_callback)(gtkblist->headline_data);
3888 reset_headline(gtkblist);
3889 return FALSE;
3890 }
3891
3892 static gboolean
3893 headline_box_press_cb(GtkWidget *widget, GdkEventButton *event, PidginBuddyList *gtkblist)
3894 {
3895 gtk_widget_hide(gtkblist->headline_hbox);
3896 if (gtkblist->headline_callback && !headline_hover_close((int)event->x, (int)event->y))
3897 g_idle_add((GSourceFunc)headline_click_callback, gtkblist->headline_data);
3898 else {
3899 if (gtkblist->headline_destroy)
3900 gtkblist->headline_destroy(gtkblist->headline_data);
3901 reset_headline(gtkblist);
3902 }
3903 return TRUE;
3904 }
3905
3906 /***********************************/
3907 /* Connection error handling stuff */
3908 /***********************************/
3909
3910 static void
3911 ce_modify_account_cb(PurpleAccount *account)
3912 {
3913 pidgin_account_dialog_show(PIDGIN_MODIFY_ACCOUNT_DIALOG, account);
3914 }
3915
3916 static void
3917 ce_enable_account_cb(PurpleAccount *account)
3918 {
3919 purple_account_set_enabled(account, purple_core_get_ui(), TRUE);
3920 }
3921
3922 static void
3923 connection_error_button_clicked_cb(GtkButton *widget, gpointer user_data)
3924 {
3925 PurpleAccount *account;
3926 char *primary;
3927 const char *text;
3928 gboolean enabled;
3929
3930 account = user_data;
3931 primary = g_strdup_printf(_("%s disconnected"),
3932 purple_account_get_username(account));
3933 text = g_hash_table_lookup(gtkblist->connection_errors, account);
3934
3935 enabled = purple_account_get_enabled(account, purple_core_get_ui());
3936 purple_request_action(account, _("Connection Error"), primary, text, 2,
3937 account, 3,
3938 _("OK"), NULL,
3939 _("Modify Account"), PURPLE_CALLBACK(ce_modify_account_cb),
3940 enabled ? _("Connect") : _("Re-enable Account"),
3941 enabled ? PURPLE_CALLBACK(purple_account_connect) :
3942 PURPLE_CALLBACK(ce_enable_account_cb));
3943 g_free(primary);
3944 gtk_widget_destroy(GTK_WIDGET(widget));
3945 g_hash_table_remove(gtkblist->connection_errors, account);
3946 }
3947
3948 /* Add some buttons that show connection errors */
3949 static void
3950 create_connection_error_buttons(gpointer key, gpointer value,
3951 gpointer user_data)
3952 {
3953 PurpleAccount *account;
3954 PurpleStatusType *status_type;
3955 gchar *escaped, *text;
3956 GtkWidget *button, *label, *image, *hbox;
3957 GdkPixbuf *pixbuf;
3958
3959 account = key;
3960 escaped = g_markup_escape_text((const gchar *)value, -1);
3961 text = g_strdup_printf(_("<span color=\"red\">%s disconnected: %s</span>"),
3962 purple_account_get_username(account),
3963 escaped);
3964 g_free(escaped);
3965
3966 hbox = gtk_hbox_new(FALSE, 0);
3967
3968 /* Create the icon */
3969 if ((status_type = purple_account_get_status_type_with_primitive(account,
3970 PURPLE_STATUS_OFFLINE))) {
3971 pixbuf = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL);
3972 if (pixbuf != NULL) {
3973 image = gtk_image_new_from_pixbuf(pixbuf);
3974 g_object_unref(pixbuf);
3975
3976 gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE,
3977 PIDGIN_HIG_BOX_SPACE);
3978 }
3979 }
3980
3981 /* Create the text */
3982 label = gtk_label_new("");
3983 gtk_label_set_markup(GTK_LABEL(label), text);
3984 g_free(text);
3985 #if GTK_CHECK_VERSION(2,6,0)
3986 g_object_set(label, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
3987 #endif
3988 gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE,
3989 PIDGIN_HIG_BOX_SPACE);
3990
3991 /* Create the actual button and put the icon and text on it */
3992 button = gtk_button_new();
3993 gtk_container_add(GTK_CONTAINER(button), hbox);
3994 g_signal_connect(G_OBJECT(button), "clicked",
3995 G_CALLBACK(connection_error_button_clicked_cb),
3996 account);
3997 gtk_widget_show_all(button);
3998 gtk_box_pack_end(GTK_BOX(gtkblist->error_buttons), button,
3999 FALSE, FALSE, 0);
4000 }
4001
4002 void
4003 pidgin_blist_update_account_error_state(PurpleAccount *account, const char *text)
4004 {
4005 GList *l;
4006
4007 if (text == NULL)
4008 g_hash_table_remove(gtkblist->connection_errors, account);
4009 else
4010 g_hash_table_insert(gtkblist->connection_errors, account, g_strdup(text));
4011
4012 /* Remove the old error buttons */
4013 for (l = gtk_container_get_children(GTK_CONTAINER(gtkblist->error_buttons));
4014 l != NULL;
4015 l = l->next)
4016 {
4017 gtk_widget_destroy(GTK_WIDGET(l->data));
4018 }
4019
4020 /* Add new error buttons */
4021 g_hash_table_foreach(gtkblist->connection_errors,
4022 create_connection_error_buttons, NULL);
4023 }
4024
4025 static gboolean
4026 paint_headline_hbox (GtkWidget *widget,
4027 GdkEventExpose *event,
4028 gpointer user_data)
4029 {
4030 gtk_paint_flat_box (widget->style,
4031 widget->window,
4032 GTK_STATE_NORMAL,
4033 GTK_SHADOW_OUT,
4034 NULL,
4035 widget,
4036 "tooltip",
4037 widget->allocation.x + 1,
4038 widget->allocation.y + 1,
4039 widget->allocation.width - 2,
4040 widget->allocation.height - 2);
4041 return FALSE;
4042 }
4043
4044 /* This assumes there are not things like groupless buddies or multi-leveled groups.
4045 * I'm sure other things in this code assumes that also.
4046 */
4047 static void
4048 treeview_style_set (GtkWidget *widget,
4049 GtkStyle *prev_style,
4050 gpointer data)
4051 {
4052 PurpleBuddyList *list = data;
4053 PurpleBlistNode *node = list->root;
4054 while (node) {
4055 pidgin_blist_update_group(list, node);
4056 node = node->next;
4057 }
4058 }
4059
4060 static void
4061 headline_style_set (GtkWidget *widget,
4062 GtkStyle *prev_style)
4063 {
4064 GtkTooltips *tooltips;
4065 GtkStyle *style;
4066
4067 if (gtkblist->changing_style)
4068 return;
4069
4070 tooltips = gtk_tooltips_new ();
4071 #if GLIB_CHECK_VERSION(2,10,0)
4072 g_object_ref_sink (tooltips);
4073 #else
4074 g_object_ref(tooltips);
4075 gtk_object_sink(GTK_OBJECT(tooltips));
4076 #endif
4077
4078 gtk_tooltips_force_window (tooltips);
4079 gtk_widget_ensure_style (tooltips->tip_window);
4080 style = gtk_widget_get_style (tooltips->tip_window);
4081
4082 gtkblist->changing_style = TRUE;
4083 gtk_widget_set_style (gtkblist->headline_hbox, style);
4084 gtkblist->changing_style = FALSE;
4085
4086 g_object_unref (tooltips);
4087 }
4088
4089 /******************************************/
4090 /* End of connection error handling stuff */
4091 /******************************************/
4092
4093 static int
4094 blist_focus_cb(GtkWidget *widget, gpointer data, PidginBuddyList *gtkblist)
4095 {
4096 pidgin_set_urgent(GTK_WINDOW(gtkblist->window), FALSE);
4097 return 0;
4098 }
4099
4100 #if 0
4101 static GtkWidget *
4102 kiosk_page()
4103 {
4104 GtkWidget *ret = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
4105 GtkWidget *label;
4106 GtkWidget *entry;
4107 GtkWidget *bbox;
4108 GtkWidget *button;
4109
4110 label = gtk_label_new(NULL);
4111 gtk_box_pack_start(GTK_BOX(ret), label, TRUE, TRUE, 0);
4112
4113 label = gtk_label_new(NULL);
4114 gtk_label_set_markup(GTK_LABEL(label), _("<b>Username:</b>"));
4115 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
4116 gtk_box_pack_start(GTK_BOX(ret), label, FALSE, FALSE, 0);
4117 entry = gtk_entry_new();
4118 gtk_box_pack_start(GTK_BOX(ret), entry, FALSE, FALSE, 0);
4119
4120 label = gtk_label_new(NULL);
4121 gtk_label_set_markup(GTK_LABEL(label), _("<b>Password:</b>"));
4122 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
4123 gtk_box_pack_start(GTK_BOX(ret), label, FALSE, FALSE, 0);
4124 entry = gtk_entry_new();
4125 gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
4126 gtk_box_pack_start(GTK_BOX(ret), entry, FALSE, FALSE, 0);
4127
4128 label = gtk_label_new(" ");
4129 gtk_box_pack_start(GTK_BOX(ret), label, FALSE, FALSE, 0);
4130
4131 bbox = gtk_hbutton_box_new();
4132 button = gtk_button_new_with_mnemonic(_("_Login"));
4133 gtk_box_pack_start(GTK_BOX(ret), bbox, FALSE, FALSE, 0);
4134 gtk_container_add(GTK_CONTAINER(bbox), button);
4135
4136
4137 label = gtk_label_new(NULL);
4138 gtk_box_pack_start(GTK_BOX(ret), label, TRUE, TRUE, 0);
4139
4140 gtk_container_set_border_width(GTK_CONTAINER(ret), PIDGIN_HIG_BORDER);
4141
4142 gtk_widget_show_all(ret);
4143 return ret;
4144 }
4145 #endif
4146
4147 static void pidgin_blist_show(PurpleBuddyList *list)
4148 {
4149 void *handle;
4150 GtkCellRenderer *rend;
4151 GtkTreeViewColumn *column;
4152 GtkWidget *menu;
4153 GtkWidget *ebox;
4154 GtkWidget *sw;
4155 GtkWidget *sep;
4156 GtkWidget *label;
4157 GList *accounts;
4158 char *pretty, *tmp;
4159 GtkAccelGroup *accel_group;
4160 GtkTreeSelection *selection;
4161 GtkTargetEntry dte[] = {{"PURPLE_BLIST_NODE", GTK_TARGET_SAME_APP, DRAG_ROW},
4162 {"application/x-im-contact", 0, DRAG_BUDDY},
4163 {"text/x-vcard", 0, DRAG_VCARD },
4164 {"text/uri-list", 0, DRAG_URI},
4165 {"text/plain", 0, DRAG_TEXT}};
4166 GtkTargetEntry ste[] = {{"PURPLE_BLIST_NODE", GTK_TARGET_SAME_APP, DRAG_ROW},
4167 {"application/x-im-contact", 0, DRAG_BUDDY},
4168 {"text/x-vcard", 0, DRAG_VCARD }};
4169 if (gtkblist && gtkblist->window) {
4170 purple_blist_set_visible(purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/list_visible"));
4171 return;
4172 }
4173
4174 gtkblist = PIDGIN_BLIST(list);
4175
4176 gtkblist->empty_avatar = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, 32, 32);
4177 gdk_pixbuf_fill(gtkblist->empty_avatar, 0x00000000);
4178
4179 gtkblist->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
4180 gtk_window_set_role(GTK_WINDOW(gtkblist->window), "buddy_list");
4181 gtk_window_set_title(GTK_WINDOW(gtkblist->window), _("Buddy List"));
4182 gdk_window_set_decorations(gtkblist->window->window,
4183 GDK_DECOR_ALL | GDK_DECOR_MAXIMIZE);
4184 g_signal_connect(G_OBJECT(gtkblist->window), "focus-in-event",
4185 G_CALLBACK(blist_focus_cb), gtkblist);
4186 GTK_WINDOW(gtkblist->window)->allow_shrink = TRUE;
4187
4188 gtkblist->main_vbox = gtk_vbox_new(FALSE, 0);
4189 gtk_widget_show(gtkblist->main_vbox);
4190 gtk_container_add(GTK_CONTAINER(gtkblist->window), gtkblist->main_vbox);
4191
4192 g_signal_connect(G_OBJECT(gtkblist->window), "delete_event", G_CALLBACK(gtk_blist_delete_cb), NULL);
4193 g_signal_connect(G_OBJECT(gtkblist->window), "configure_event", G_CALLBACK(gtk_blist_configure_cb), NULL);
4194 g_signal_connect(G_OBJECT(gtkblist->window), "visibility_notify_event", G_CALLBACK(gtk_blist_visibility_cb), NULL);
4195 g_signal_connect(G_OBJECT(gtkblist->window), "window_state_event", G_CALLBACK(gtk_blist_window_state_cb), NULL);
4196 g_signal_connect(G_OBJECT(gtkblist->window), "key_press_event", G_CALLBACK(gtk_blist_window_key_press_cb), gtkblist);
4197 gtk_widget_add_events(gtkblist->window, GDK_VISIBILITY_NOTIFY_MASK);
4198
4199 /******************************* Menu bar *************************************/
4200 accel_group = gtk_accel_group_new();
4201 gtk_window_add_accel_group(GTK_WINDOW (gtkblist->window), accel_group);
4202 g_object_unref(accel_group);
4203 gtkblist->ift = gtk_item_factory_new(GTK_TYPE_MENU_BAR, "<PurpleMain>", accel_group);
4204 gtk_item_factory_set_translate_func(gtkblist->ift,
4205 (GtkTranslateFunc)item_factory_translate_func,
4206 NULL, NULL);
4207 gtk_item_factory_create_items(gtkblist->ift, sizeof(blist_menu) / sizeof(*blist_menu),
4208 blist_menu, NULL);
4209 pidgin_load_accels();
4210 g_signal_connect(G_OBJECT(accel_group), "accel-changed",
4211 G_CALLBACK(pidgin_save_accels_cb), NULL);
4212 menu = gtk_item_factory_get_widget(gtkblist->ift, "<PurpleMain>");
4213 gtkblist->menutray = pidgin_menu_tray_new();
4214 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtkblist->menutray);
4215 gtk_widget_show(gtkblist->menutray);
4216 gtk_widget_show(menu);
4217 gtk_box_pack_start(GTK_BOX(gtkblist->main_vbox), menu, FALSE, FALSE, 0);
4218
4219 accountmenu = gtk_item_factory_get_widget(gtkblist->ift, N_("/Accounts"));
4220
4221
4222 /****************************** Notebook *************************************/
4223 gtkblist->notebook = gtk_notebook_new();
4224 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(gtkblist->notebook), FALSE);
4225 gtk_notebook_set_show_border(GTK_NOTEBOOK(gtkblist->notebook), FALSE);
4226 gtk_box_pack_start(GTK_BOX(gtkblist->main_vbox), gtkblist->notebook, TRUE, TRUE, 0);
4227
4228 #if 0
4229 gtk_notebook_append_page(GTK_NOTEBOOK(gtkblist->notebook), kiosk_page(), NULL);
4230 #endif
4231
4232 /* Translators: Please maintain the use of -> and <- to refer to menu heirarchy */
4233 tmp = g_strdup_printf(_("<span weight='bold' size='larger'>Welcome to %s!</span>\n\n"
4234
4235 "You have no accounts enabled. Enable your IM accounts from the "
4236 "<b>Accounts</b> window at <b>Accounts->Add/Edit</b>. Once you "
4237 "enable accounts, you'll be able to sign on, set your status, "
4238 "and talk to your friends."), PIDGIN_NAME);
4239 pretty = pidgin_make_pretty_arrows(tmp);
4240 g_free(tmp);
4241 label = gtk_label_new(NULL);
4242 gtk_widget_set_size_request(label, purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/width") - 12, -1);
4243 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
4244 gtk_misc_set_alignment(GTK_MISC(label), 0.5, 0.2);
4245 gtk_label_set_markup(GTK_LABEL(label), pretty);
4246 g_free(pretty);
4247 gtk_notebook_append_page(GTK_NOTEBOOK(gtkblist->notebook),label, NULL);
4248 gtkblist->vbox = gtk_vbox_new(FALSE, 0);
4249 gtk_notebook_append_page(GTK_NOTEBOOK(gtkblist->notebook), gtkblist->vbox, NULL);
4250 gtk_widget_show_all(gtkblist->notebook);
4251 if ((accounts = purple_accounts_get_all_active())) {
4252 g_list_free(accounts);
4253 gtk_notebook_set_current_page(GTK_NOTEBOOK(gtkblist->notebook), 1);
4254 }
4255
4256 ebox = gtk_event_box_new();
4257 gtk_box_pack_start(GTK_BOX(gtkblist->vbox), ebox, FALSE, FALSE, 0);
4258 gtkblist->headline_hbox = gtk_hbox_new(FALSE, 3);
4259 gtk_container_set_border_width(GTK_CONTAINER(gtkblist->headline_hbox), 6);
4260 gtk_container_add(GTK_CONTAINER(ebox), gtkblist->headline_hbox);
4261 gtkblist->headline_image = gtk_image_new_from_pixbuf(NULL);
4262 gtk_misc_set_alignment(GTK_MISC(gtkblist->headline_image), 0.0, 0);
4263 gtkblist->headline_label = gtk_label_new(NULL);
4264 gtk_widget_set_size_request(gtkblist->headline_label,
4265 purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/width")-25,-1);
4266 gtk_label_set_line_wrap(GTK_LABEL(gtkblist->headline_label), TRUE);
4267 gtk_box_pack_start(GTK_BOX(gtkblist->headline_hbox), gtkblist->headline_image, FALSE, FALSE, 0);
4268 gtk_box_pack_start(GTK_BOX(gtkblist->headline_hbox), gtkblist->headline_label, TRUE, TRUE, 0);
4269 g_signal_connect(gtkblist->headline_hbox,
4270 "style-set",
4271 G_CALLBACK(headline_style_set),
4272 NULL);
4273 g_signal_connect (gtkblist->headline_hbox,
4274 "expose_event",
4275 G_CALLBACK (paint_headline_hbox),
4276 NULL);
4277 gtk_widget_set_name(gtkblist->headline_hbox, "gtk-tooltips");
4278
4279 gtkblist->headline_close = gtk_widget_render_icon(ebox, GTK_STOCK_CLOSE, -1, NULL);
4280 if (gtkblist->headline_close) {
4281 GdkPixbuf *scale = gdk_pixbuf_scale_simple(gtkblist->headline_close,
4282 HEADLINE_CLOSE_SIZE, HEADLINE_CLOSE_SIZE, GDK_INTERP_BILINEAR);
4283 gdk_pixbuf_unref(gtkblist->headline_close);
4284 gtkblist->headline_close = scale;
4285 }
4286
4287 gtkblist->hand_cursor = gdk_cursor_new (GDK_HAND2);
4288 gtkblist->arrow_cursor = gdk_cursor_new (GDK_LEFT_PTR);
4289
4290 g_signal_connect(G_OBJECT(ebox), "enter-notify-event", G_CALLBACK(headline_box_enter_cb), gtkblist);
4291 g_signal_connect(G_OBJECT(ebox), "leave-notify-event", G_CALLBACK(headline_box_leave_cb), gtkblist);
4292 g_signal_connect(G_OBJECT(ebox), "button-press-event", G_CALLBACK(headline_box_press_cb), gtkblist);
4293 #if 0
4294 /* I couldn't get this to work. The idea was to draw the focus-border only
4295 * when hovering over the close image. So for now, the focus-border is
4296 * always there. -- sad */
4297 gtk_widget_set_events(ebox, gtk_widget_get_events(ebox) | GDK_POINTER_MOTION_HINT_MASK);
4298 g_signal_connect(G_OBJECT(ebox), "motion-notify-event", G_CALLBACK(headline_box_motion_cb), gtkblist);
4299 #endif
4300
4301 /****************************** GtkTreeView **********************************/
4302 sw = gtk_scrolled_window_new(NULL,NULL);
4303 gtk_widget_show(sw);
4304 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_NONE);
4305 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
4306
4307 gtkblist->treemodel = gtk_tree_store_new(BLIST_COLUMNS,
4308 GDK_TYPE_PIXBUF, /* Status icon */
4309 G_TYPE_BOOLEAN, /* Status icon visible */
4310 G_TYPE_STRING, /* Name */
4311 G_TYPE_STRING, /* Idle */
4312 G_TYPE_BOOLEAN, /* Idle visible */
4313 GDK_TYPE_PIXBUF, /* Buddy icon */
4314 G_TYPE_BOOLEAN, /* Buddy icon visible */
4315 G_TYPE_POINTER, /* Node */
4316 GDK_TYPE_COLOR, /* bgcolor */
4317 G_TYPE_BOOLEAN, /* Group expander */
4318 G_TYPE_BOOLEAN, /* Group expander visible */
4319 G_TYPE_BOOLEAN, /* Contact expander */
4320 G_TYPE_BOOLEAN, /* Contact expander visible */
4321 GDK_TYPE_PIXBUF, /* Emblem */
4322 G_TYPE_BOOLEAN); /* Emblem visible */
4323
4324 gtkblist->treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(gtkblist->treemodel));
4325
4326 gtk_widget_show(gtkblist->treeview);
4327 gtk_widget_set_name(gtkblist->treeview, "pidginblist_treeview");
4328
4329 g_signal_connect(gtkblist->treeview,
4330 "style-set",
4331 G_CALLBACK(treeview_style_set), list);
4332 /* Set up selection stuff */
4333 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist->treeview));
4334 g_signal_connect(G_OBJECT(selection), "changed", G_CALLBACK(pidgin_blist_selection_changed), NULL);
4335
4336 /* Set up dnd */
4337 gtk_tree_view_enable_model_drag_source(GTK_TREE_VIEW(gtkblist->treeview),
4338 GDK_BUTTON1_MASK, ste, 3,
4339 GDK_ACTION_COPY);
4340 gtk_tree_view_enable_model_drag_dest(GTK_TREE_VIEW(gtkblist->treeview),
4341 dte, 5,
4342 GDK_ACTION_COPY | GDK_ACTION_MOVE);
4343
4344 g_signal_connect(G_OBJECT(gtkblist->treeview), "drag-data-received", G_CALLBACK(pidgin_blist_drag_data_rcv_cb), NULL);
4345 g_signal_connect(G_OBJECT(gtkblist->treeview), "drag-data-get", G_CALLBACK(pidgin_blist_drag_data_get_cb), NULL);
4346 #ifdef _WIN32
4347 g_signal_connect(G_OBJECT(gtkblist->treeview), "drag-begin", G_CALLBACK(pidgin_blist_drag_begin), NULL);
4348 #endif
4349
4350 g_signal_connect(G_OBJECT(gtkblist->treeview), "drag-motion", G_CALLBACK(pidgin_blist_drag_motion_cb), NULL);
4351
4352 /* Tooltips */
4353 g_signal_connect(G_OBJECT(gtkblist->treeview), "motion-notify-event", G_CALLBACK(pidgin_blist_motion_cb), NULL);
4354 g_signal_connect(G_OBJECT(gtkblist->treeview), "leave-notify-event", G_CALLBACK(pidgin_blist_leave_cb), NULL);
4355
4356 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(gtkblist->treeview), FALSE);
4357
4358 column = gtk_tree_view_column_new();
4359 gtk_tree_view_append_column(GTK_TREE_VIEW(gtkblist->treeview), column);
4360 gtk_tree_view_column_set_visible(column, FALSE);
4361 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(gtkblist->treeview), column);
4362
4363 gtkblist->text_column = column = gtk_tree_view_column_new ();
4364 rend = pidgin_cell_renderer_expander_new();
4365 gtk_tree_view_column_pack_start(column, rend, FALSE);
4366 gtk_tree_view_column_set_attributes(column, rend,
4367 "visible", GROUP_EXPANDER_VISIBLE_COLUMN,
4368 "expander-visible", GROUP_EXPANDER_COLUMN,
4369 #if GTK_CHECK_VERSION(2,6,0)
4370 "sensitive", GROUP_EXPANDER_COLUMN,
4371 "cell-background-gdk", BGCOLOR_COLUMN,
4372 #endif
4373 NULL);
4374
4375 rend = pidgin_cell_renderer_expander_new();
4376 gtk_tree_view_column_pack_start(column, rend, FALSE);
4377 gtk_tree_view_column_set_attributes(column, rend,
4378 "expander-visible", CONTACT_EXPANDER_COLUMN,
4379 #if GTK_CHECK_VERSION(2,6,0)
4380 "sensitive", CONTACT_EXPANDER_COLUMN,
4381 "cell-background-gdk", BGCOLOR_COLUMN,
4382 #endif
4383 "visible", CONTACT_EXPANDER_VISIBLE_COLUMN,
4384 NULL);
4385
4386 rend = gtk_cell_renderer_pixbuf_new();
4387 gtk_tree_view_column_pack_start(column, rend, FALSE);
4388 gtk_tree_view_column_set_attributes(column, rend,
4389 "pixbuf", STATUS_ICON_COLUMN,
4390 "visible", STATUS_ICON_VISIBLE_COLUMN,
4391 #if GTK_CHECK_VERSION(2,6,0)
4392 "cell-background-gdk", BGCOLOR_COLUMN,
4393 #endif
4394 NULL);
4395 g_object_set(rend, "xalign", 0.0, "xpad", 6, "ypad", 0, NULL);
4396
4397 gtkblist->text_rend = rend = gtk_cell_renderer_text_new();
4398 gtk_tree_view_column_pack_start (column, rend, TRUE);
4399 gtk_tree_view_column_set_attributes(column, rend,
4400 #if GTK_CHECK_VERSION(2,6,0)
4401 "cell-background-gdk", BGCOLOR_COLUMN,
4402 #endif
4403 "markup", NAME_COLUMN,
4404 NULL);
4405 g_signal_connect(G_OBJECT(rend), "edited", G_CALLBACK(gtk_blist_renderer_edited_cb), NULL);
4406 g_object_set(rend, "ypad", 0, "yalign", 0.5, NULL);
4407 #if GTK_CHECK_VERSION(2,6,0)
4408 g_object_set(rend, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
4409 #endif
4410 gtk_tree_view_append_column(GTK_TREE_VIEW(gtkblist->treeview), column);
4411
4412 rend = gtk_cell_renderer_text_new();
4413 g_object_set(rend, "xalign", 1.0, "ypad", 0, NULL);
4414 gtk_tree_view_column_pack_start(column, rend, FALSE);
4415 gtk_tree_view_column_set_attributes(column, rend,
4416 "markup", IDLE_COLUMN,
4417 "visible", IDLE_VISIBLE_COLUMN,
4418 #if GTK_CHECK_VERSION(2,6,0)
4419 "cell-background-gdk", BGCOLOR_COLUMN,
4420 #endif
4421 NULL);
4422
4423 rend = gtk_cell_renderer_pixbuf_new();
4424 g_object_set(rend, "xalign", 1.0, "yalign", 0.5, "ypad", 0, "xpad", 3, NULL);
4425 gtk_tree_view_column_pack_start(column, rend, FALSE);
4426 gtk_tree_view_column_set_attributes(column, rend, "pixbuf", EMBLEM_COLUMN,
4427 #if GTK_CHECK_VERSION(2,6,0)
4428 "cell-background-gdk", BGCOLOR_COLUMN,
4429 #endif
4430 "visible", EMBLEM_VISIBLE_COLUMN, NULL);
4431
4432 rend = gtk_cell_renderer_pixbuf_new();
4433 g_object_set(rend, "xalign", 1.0, "ypad", 0, NULL);
4434 gtk_tree_view_column_pack_start(column, rend, FALSE);
4435 gtk_tree_view_column_set_attributes(column, rend, "pixbuf", BUDDY_ICON_COLUMN,
4436 #if GTK_CHECK_VERSION(2,6,0)
4437 "cell-background-gdk", BGCOLOR_COLUMN,
4438 #endif
4439 "visible", BUDDY_ICON_VISIBLE_COLUMN,
4440 NULL);
4441
4442
4443 g_signal_connect(G_OBJECT(gtkblist->treeview), "row-activated", G_CALLBACK(gtk_blist_row_activated_cb), NULL);
4444 g_signal_connect(G_OBJECT(gtkblist->treeview), "row-expanded", G_CALLBACK(gtk_blist_row_expanded_cb), NULL);
4445 g_signal_connect(G_OBJECT(gtkblist->treeview), "row-collapsed", G_CALLBACK(gtk_blist_row_collapsed_cb), NULL);
4446 g_signal_connect(G_OBJECT(gtkblist->treeview), "button-press-event", G_CALLBACK(gtk_blist_button_press_cb), NULL);
4447 g_signal_connect(G_OBJECT(gtkblist->treeview), "key-press-event", G_CALLBACK(gtk_blist_key_press_cb), NULL);
4448 g_signal_connect(G_OBJECT(gtkblist->treeview), "popup-menu", G_CALLBACK(pidgin_blist_popup_menu_cb), NULL);
4449
4450 /* Enable CTRL+F searching */
4451 gtk_tree_view_set_search_column(GTK_TREE_VIEW(gtkblist->treeview), NAME_COLUMN);
4452 gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(gtkblist->treeview), pidgin_tree_view_search_equal_func, NULL, NULL);
4453
4454 gtk_box_pack_start(GTK_BOX(gtkblist->vbox), sw, TRUE, TRUE, 0);
4455 gtk_container_add(GTK_CONTAINER(sw), gtkblist->treeview);
4456
4457 sep = gtk_hseparator_new();
4458 gtk_box_pack_start(GTK_BOX(gtkblist->vbox), sep, FALSE, FALSE, 0);
4459
4460 gtkblist->scrollbook = pidgin_scroll_book_new();
4461 gtk_box_pack_start(GTK_BOX(gtkblist->vbox), gtkblist->scrollbook, FALSE, FALSE, 0);
4462
4463 /* Create an empty vbox used for showing connection errors */
4464 gtkblist->error_buttons = gtk_vbox_new(FALSE, 0);
4465 gtk_box_pack_start(GTK_BOX(gtkblist->vbox), gtkblist->error_buttons, FALSE, FALSE, 0);
4466
4467 /* Add the statusbox */
4468 gtkblist->statusbox = pidgin_status_box_new();
4469 gtk_box_pack_start(GTK_BOX(gtkblist->vbox), gtkblist->statusbox, FALSE, TRUE, 0);
4470 gtk_widget_set_name(gtkblist->statusbox, "pidginblist_statusbox");
4471 gtk_container_set_border_width(GTK_CONTAINER(gtkblist->statusbox), 3);
4472 gtk_widget_show(gtkblist->statusbox);
4473
4474 /* set the Show Offline Buddies option. must be done
4475 * after the treeview or faceprint gets mad. -Robot101
4476 */
4477 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show Offline Buddies"))),
4478 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_offline_buddies"));
4479
4480 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show Empty Groups"))),
4481 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_empty_groups"));
4482
4483 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Tools/Mute Sounds"))),
4484 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/sound/mute"));
4485
4486 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show Buddy Details"))),
4487 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons"));
4488
4489 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show Idle Times"))),
4490 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_idle_time"));
4491
4492 if(!strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/sound/method"), "none"))
4493 gtk_widget_set_sensitive(gtk_item_factory_get_widget(gtkblist->ift, N_("/Tools/Mute Sounds")), FALSE);
4494
4495 /* Update some dynamic things */
4496 update_menu_bar(gtkblist);
4497 pidgin_blist_update_plugin_actions();
4498 pidgin_blist_update_sort_methods();
4499
4500 /* OK... let's show this bad boy. */
4501 pidgin_blist_refresh(list);
4502 pidgin_blist_restore_position();
4503 gtk_widget_show_all(GTK_WIDGET(gtkblist->vbox));
4504 gtk_widget_realize(GTK_WIDGET(gtkblist->window));
4505 purple_blist_set_visible(purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/list_visible"));
4506
4507 /* start the refresh timer */
4508 gtkblist->refresh_timer = g_timeout_add(30000, (GSourceFunc)pidgin_blist_refresh_timer, list);
4509
4510 handle = pidgin_blist_get_handle();
4511
4512 /* things that affect how buddies are displayed */
4513 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/show_buddy_icons",
4514 _prefs_change_redo_list, NULL);
4515 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/show_idle_time",
4516 _prefs_change_redo_list, NULL);
4517 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/show_empty_groups",
4518 _prefs_change_redo_list, NULL);
4519 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/show_offline_buddies",
4520 _prefs_change_redo_list, NULL);
4521
4522 /* sorting */
4523 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/sort_type",
4524 _prefs_change_sort_method, NULL);
4525
4526 /* menus */
4527 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/sound/mute",
4528 pidgin_blist_mute_pref_cb, NULL);
4529 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/sound/method",
4530 pidgin_blist_sound_method_pref_cb, NULL);
4531
4532 /* Setup some purple signal handlers. */
4533 purple_signal_connect(purple_accounts_get_handle(), "account-enabled",
4534 gtkblist, PURPLE_CALLBACK(account_modified), gtkblist);
4535 purple_signal_connect(purple_accounts_get_handle(), "account-disabled",
4536 gtkblist, PURPLE_CALLBACK(account_modified), gtkblist);
4537 purple_signal_connect(purple_accounts_get_handle(), "account-removed",
4538 gtkblist, PURPLE_CALLBACK(account_modified), gtkblist);
4539 purple_signal_connect(purple_accounts_get_handle(), "account-status-changed",
4540 gtkblist, PURPLE_CALLBACK(account_status_changed), gtkblist);
4541
4542 purple_signal_connect(pidgin_account_get_handle(), "account-modified",
4543 gtkblist, PURPLE_CALLBACK(account_modified), gtkblist);
4544
4545 purple_signal_connect(purple_connections_get_handle(), "signed-on",
4546 gtkblist, PURPLE_CALLBACK(sign_on_off_cb), list);
4547 purple_signal_connect(purple_connections_get_handle(), "signed-off",
4548 gtkblist, PURPLE_CALLBACK(sign_on_off_cb), list);
4549
4550 purple_signal_connect(purple_plugins_get_handle(), "plugin-load",
4551 gtkblist, PURPLE_CALLBACK(plugin_changed_cb), NULL);
4552 purple_signal_connect(purple_plugins_get_handle(), "plugin-unload",
4553 gtkblist, PURPLE_CALLBACK(plugin_changed_cb), NULL);
4554
4555 purple_signal_connect(purple_conversations_get_handle(), "conversation-updated",
4556 gtkblist, PURPLE_CALLBACK(conversation_updated_cb),
4557 gtkblist);
4558 purple_signal_connect(purple_conversations_get_handle(), "deleting-conversation",
4559 gtkblist, PURPLE_CALLBACK(conversation_deleting_cb),
4560 gtkblist);
4561
4562 gtk_widget_hide(gtkblist->headline_hbox);
4563
4564 /* emit our created signal */
4565 purple_signal_emit(handle, "gtkblist-created", list);
4566 }
4567
4568 static void redo_buddy_list(PurpleBuddyList *list, gboolean remove, gboolean rerender)
4569 {
4570 PurpleBlistNode *node;
4571
4572 gtkblist = PIDGIN_BLIST(list);
4573 if(!gtkblist || !gtkblist->treeview)
4574 return;
4575
4576 node = list->root;
4577
4578 while (node)
4579 {
4580 /* This is only needed when we're reverting to a non-GTK+ sorted
4581 * status. We shouldn't need to remove otherwise.
4582 */
4583 if (remove && !PURPLE_BLIST_NODE_IS_GROUP(node))
4584 pidgin_blist_hide_node(list, node, FALSE);
4585
4586 if (PURPLE_BLIST_NODE_IS_BUDDY(node))
4587 pidgin_blist_update_buddy(list, node, rerender);
4588 else if (PURPLE_BLIST_NODE_IS_CHAT(node))
4589 pidgin_blist_update(list, node);
4590 else if (PURPLE_BLIST_NODE_IS_GROUP(node))
4591 pidgin_blist_update(list, node);
4592 node = purple_blist_node_next(node, FALSE);
4593 }
4594
4595 }
4596
4597 void pidgin_blist_refresh(PurpleBuddyList *list)
4598 {
4599 redo_buddy_list(list, FALSE, TRUE);
4600 }
4601
4602 void
4603 pidgin_blist_update_refresh_timeout()
4604 {
4605 PurpleBuddyList *blist;
4606 PidginBuddyList *gtkblist;
4607
4608 blist = purple_get_blist();
4609 gtkblist = PIDGIN_BLIST(purple_get_blist());
4610
4611 gtkblist->refresh_timer = g_timeout_add(30000,(GSourceFunc)pidgin_blist_refresh_timer, blist);
4612 }
4613
4614 static gboolean get_iter_from_node(PurpleBlistNode *node, GtkTreeIter *iter) {
4615 struct _pidgin_blist_node *gtknode = (struct _pidgin_blist_node *)node->ui_data;
4616 GtkTreePath *path;
4617
4618 if (!gtknode) {
4619 return FALSE;
4620 }
4621
4622 if (!gtkblist) {
4623 purple_debug_error("gtkblist", "get_iter_from_node was called, but we don't seem to have a blist\n");
4624 return FALSE;
4625 }
4626
4627 if (!gtknode->row)
4628 return FALSE;
4629
4630
4631 if ((path = gtk_tree_row_reference_get_path(gtknode->row)) == NULL)
4632 return FALSE;
4633
4634 if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), iter, path)) {
4635 gtk_tree_path_free(path);
4636 return FALSE;
4637 }
4638 gtk_tree_path_free(path);
4639 return TRUE;
4640 }
4641
4642 static void pidgin_blist_remove(PurpleBuddyList *list, PurpleBlistNode *node)
4643 {
4644 struct _pidgin_blist_node *gtknode = node->ui_data;
4645
4646 purple_request_close_with_handle(node);
4647
4648 pidgin_blist_hide_node(list, node, TRUE);
4649
4650 if(node->parent)
4651 pidgin_blist_update(list, node->parent);
4652
4653 /* There's something I don't understand here - Ethan */
4654 /* Ethan said that back in 2003, but this g_free has been left commented
4655 * out ever since. I can't find any reason at all why this is bad and
4656 * valgrind found several reasons why it's good. If this causes problems
4657 * comment it out again. Stu */
4658 /* Of course it still causes problems - this breaks dragging buddies into
4659 * contacts, the dragged buddy mysteriously 'disappears'. Stu. */
4660 /* I think it's fixed now. Stu. */
4661
4662 if(gtknode) {
4663 if(gtknode->recent_signonoff_timer > 0)
4664 purple_timeout_remove(gtknode->recent_signonoff_timer);
4665
4666 g_free(node->ui_data);
4667 node->ui_data = NULL;
4668 }
4669 }
4670
4671 static gboolean do_selection_changed(PurpleBlistNode *new_selection)
4672 {
4673 PurpleBlistNode *old_selection = NULL;
4674
4675 /* test for gtkblist because crazy timeout means we can be called after the blist is gone */
4676 if (gtkblist && new_selection != gtkblist->selected_node) {
4677 old_selection = gtkblist->selected_node;
4678 gtkblist->selected_node = new_selection;
4679 if(new_selection)
4680 pidgin_blist_update(NULL, new_selection);
4681 if(old_selection)
4682 pidgin_blist_update(NULL, old_selection);
4683 }
4684
4685 return FALSE;
4686 }
4687
4688 static void pidgin_blist_selection_changed(GtkTreeSelection *selection, gpointer data)
4689 {
4690 PurpleBlistNode *new_selection = NULL;
4691 GtkTreeIter iter;
4692
4693 if(gtk_tree_selection_get_selected(selection, NULL, &iter)){
4694 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter,
4695 NODE_COLUMN, &new_selection, -1);
4696 }
4697
4698 /* we set this up as a timeout, otherwise the blist flickers */
4699 g_timeout_add(0, (GSourceFunc)do_selection_changed, new_selection);
4700 }
4701
4702 static gboolean insert_node(PurpleBuddyList *list, PurpleBlistNode *node, GtkTreeIter *iter)
4703 {
4704 GtkTreeIter parent_iter, cur, *curptr = NULL;
4705 struct _pidgin_blist_node *gtknode = node->ui_data;
4706 GtkTreePath *newpath;
4707
4708 if(!iter)
4709 return FALSE;
4710
4711 if(node->parent && !get_iter_from_node(node->parent, &parent_iter))
4712 return FALSE;
4713
4714 if(get_iter_from_node(node, &cur))
4715 curptr = &cur;
4716
4717 if(PURPLE_BLIST_NODE_IS_CONTACT(node) || PURPLE_BLIST_NODE_IS_CHAT(node)) {
4718 current_sort_method->func(node, list, parent_iter, curptr, iter);
4719 } else {
4720 sort_method_none(node, list, parent_iter, curptr, iter);
4721 }
4722
4723 if(gtknode != NULL) {
4724 gtk_tree_row_reference_free(gtknode->row);
4725 } else {
4726 pidgin_blist_new_node(node);
4727 gtknode = (struct _pidgin_blist_node *)node->ui_data;
4728 }
4729
4730 newpath = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel),
4731 iter);
4732 gtknode->row =
4733 gtk_tree_row_reference_new(GTK_TREE_MODEL(gtkblist->treemodel),
4734 newpath);
4735
4736 gtk_tree_path_free(newpath);
4737
4738 gtk_tree_store_set(gtkblist->treemodel, iter,
4739 NODE_COLUMN, node,
4740 -1);
4741
4742 if(node->parent) {
4743 GtkTreePath *expand = NULL;
4744 struct _pidgin_blist_node *gtkparentnode = node->parent->ui_data;
4745
4746 if(PURPLE_BLIST_NODE_IS_GROUP(node->parent)) {
4747 if(!purple_blist_node_get_bool(node->parent, "collapsed"))
4748 expand = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &parent_iter);
4749 } else if(PURPLE_BLIST_NODE_IS_CONTACT(node->parent) &&
4750 gtkparentnode->contact_expanded) {
4751 expand = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &parent_iter);
4752 }
4753 if(expand) {
4754 gtk_tree_view_expand_row(GTK_TREE_VIEW(gtkblist->treeview), expand, FALSE);
4755 gtk_tree_path_free(expand);
4756 }
4757 }
4758
4759 return TRUE;
4760 }
4761
4762 /*This version of pidgin_blist_update_group can take the original buddy
4763 or a group, but has much better algorithmic performance with a pre-known buddy*/
4764 static void pidgin_blist_update_group(PurpleBuddyList *list, PurpleBlistNode *node)
4765 {
4766 PurpleGroup *group;
4767 int count;
4768 gboolean show = FALSE;
4769 PurpleBlistNode* gnode;
4770
4771 g_return_if_fail(node != NULL);
4772
4773 if (PURPLE_BLIST_NODE_IS_GROUP(node))
4774 gnode = node;
4775 else if (PURPLE_BLIST_NODE_IS_BUDDY(node))
4776 gnode = node->parent->parent;
4777 else if (PURPLE_BLIST_NODE_IS_CONTACT(node) || PURPLE_BLIST_NODE_IS_CHAT(node))
4778 gnode = node->parent;
4779 else
4780 return;
4781
4782 group = (PurpleGroup*)gnode;
4783
4784 if(purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_offline_buddies"))
4785 count = purple_blist_get_group_size(group, FALSE);
4786 else
4787 count = purple_blist_get_group_online_count(group);
4788
4789 if (count > 0 || purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_empty_groups"))
4790 show = TRUE;
4791 else if (PURPLE_BLIST_NODE_IS_BUDDY(node)){ /* Or chat? */
4792 if (buddy_is_displayable((PurpleBuddy*)node))
4793 show = TRUE;}
4794
4795 if (show) {
4796 GtkTreeIter iter;
4797 GtkTreePath *path;
4798 gboolean expanded;
4799 GdkColor bgcolor;
4800 char *title;
4801
4802
4803 if(!insert_node(list, gnode, &iter))
4804 return;
4805
4806 bgcolor = gtkblist->treeview->style->bg[GTK_STATE_ACTIVE];
4807
4808 path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &iter);
4809 expanded = gtk_tree_view_row_expanded(GTK_TREE_VIEW(gtkblist->treeview), path);
4810 gtk_tree_path_free(path);
4811
4812 title = pidgin_get_group_title(gnode, expanded);
4813
4814 gtk_tree_store_set(gtkblist->treemodel, &iter,
4815 STATUS_ICON_VISIBLE_COLUMN, FALSE,
4816 STATUS_ICON_COLUMN, NULL,
4817 NAME_COLUMN, title,
4818 NODE_COLUMN, gnode,
4819 BGCOLOR_COLUMN, &bgcolor,
4820 GROUP_EXPANDER_COLUMN, TRUE,
4821 GROUP_EXPANDER_VISIBLE_COLUMN, TRUE,
4822 CONTACT_EXPANDER_VISIBLE_COLUMN, FALSE,
4823 BUDDY_ICON_VISIBLE_COLUMN, FALSE,
4824 IDLE_VISIBLE_COLUMN, FALSE,
4825 EMBLEM_VISIBLE_COLUMN, FALSE,
4826 -1);
4827 g_free(title);
4828 } else {
4829 pidgin_blist_hide_node(list, gnode, TRUE);
4830 }
4831 }
4832
4833 static char *pidgin_get_group_title(PurpleBlistNode *gnode, gboolean expanded)
4834 {
4835 PurpleGroup *group;
4836 GdkColor textcolor;
4837 gboolean selected;
4838 char group_count[12] = "";
4839 char *mark, *esc;
4840
4841 group = (PurpleGroup*)gnode;
4842 textcolor = gtkblist->treeview->style->fg[GTK_STATE_ACTIVE];
4843 selected = gtkblist ? (gtkblist->selected_node == gnode) : FALSE;
4844
4845 if (!expanded) {
4846 g_snprintf(group_count, sizeof(group_count), " (%d/%d)",
4847 purple_blist_get_group_online_count(group),
4848 purple_blist_get_group_size(group, FALSE));
4849 }
4850
4851 esc = g_markup_escape_text(group->name, -1);
4852 if (selected)
4853 mark = g_strdup_printf("<span weight='bold'>%s</span>%s", esc, group_count);
4854 else
4855 mark = g_strdup_printf("<span color='#%02x%02x%02x' weight='bold'>%s</span>%s",
4856 textcolor.red>>8, textcolor.green>>8, textcolor.blue>>8,
4857 esc, group_count);
4858
4859 g_free(esc);
4860 return mark;
4861 }
4862
4863 static void buddy_node(PurpleBuddy *buddy, GtkTreeIter *iter, PurpleBlistNode *node)
4864 {
4865 PurplePresence *presence;
4866 GdkPixbuf *status, *avatar, *emblem;
4867 char *mark;
4868 char *idle = NULL;
4869 gboolean expanded = ((struct _pidgin_blist_node *)(node->parent->ui_data))->contact_expanded;
4870 gboolean selected = (gtkblist->selected_node == node);
4871 gboolean biglist = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons");
4872 presence = purple_buddy_get_presence(buddy);
4873
4874 status = pidgin_blist_get_status_icon((PurpleBlistNode*)buddy,
4875 PIDGIN_STATUS_ICON_SMALL);
4876
4877 avatar = pidgin_blist_get_buddy_icon((PurpleBlistNode *)buddy, TRUE, TRUE, TRUE);
4878 if (!avatar) {
4879 g_object_ref(G_OBJECT(gtkblist->empty_avatar));
4880 avatar = gtkblist->empty_avatar;
4881 } else if ((!PURPLE_BUDDY_IS_ONLINE(buddy) || purple_presence_is_idle(presence))) {
4882 do_alphashift(avatar, avatar, 77);
4883 }
4884
4885 emblem = pidgin_blist_get_emblem((PurpleBlistNode*) buddy);
4886 mark = pidgin_blist_get_name_markup(buddy, selected);
4887
4888 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_idle_time") &&
4889 purple_presence_is_idle(presence) &&
4890 !purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons"))
4891 {
4892 time_t idle_secs = purple_presence_get_idle_time(presence);
4893
4894 if (idle_secs > 0)
4895 {
4896 time_t t;
4897 int ihrs, imin;
4898 time(&t);
4899 ihrs = (t - idle_secs) / 3600;
4900 imin = ((t - idle_secs) / 60) % 60;
4901 idle = g_strdup_printf("%d:%02d", ihrs, imin);
4902 }
4903 }
4904
4905 if (purple_presence_is_idle(presence))
4906 {
4907 if (idle && !selected) {
4908 char *i2 = g_strdup_printf("<span color='%s'>%s</span>",
4909 dim_grey(), idle);
4910 g_free(idle);
4911 idle = i2;
4912 }
4913 }
4914
4915 gtk_tree_store_set(gtkblist->treemodel, iter,
4916 STATUS_ICON_COLUMN, status,
4917 STATUS_ICON_VISIBLE_COLUMN, TRUE,
4918 NAME_COLUMN, mark,
4919 IDLE_COLUMN, idle,
4920 IDLE_VISIBLE_COLUMN, !biglist && idle,
4921 BUDDY_ICON_COLUMN, avatar,
4922 BUDDY_ICON_VISIBLE_COLUMN, biglist,
4923 EMBLEM_COLUMN, emblem,
4924 EMBLEM_VISIBLE_COLUMN, emblem,
4925 BGCOLOR_COLUMN, NULL,
4926 CONTACT_EXPANDER_COLUMN, NULL,
4927 CONTACT_EXPANDER_VISIBLE_COLUMN, expanded,
4928 GROUP_EXPANDER_VISIBLE_COLUMN, FALSE,
4929 -1);
4930
4931 g_free(mark);
4932 g_free(idle);
4933 if(status)
4934 g_object_unref(status);
4935 if(avatar)
4936 g_object_unref(avatar);
4937 }
4938
4939 /* This is a variation on the original gtk_blist_update_contact. Here we
4940 can know in advance which buddy has changed so we can just update that */
4941 static void pidgin_blist_update_contact(PurpleBuddyList *list, PurpleBlistNode *node)
4942 {
4943 PurpleBlistNode *cnode;
4944 PurpleContact *contact;
4945 PurpleBuddy *buddy;
4946 struct _pidgin_blist_node *gtknode;
4947
4948 if (PURPLE_BLIST_NODE_IS_BUDDY(node))
4949 cnode = node->parent;
4950 else
4951 cnode = node;
4952
4953 g_return_if_fail(PURPLE_BLIST_NODE_IS_CONTACT(cnode));
4954
4955 /* First things first, update the group */
4956 if (PURPLE_BLIST_NODE_IS_BUDDY(node))
4957 pidgin_blist_update_group(list, node);
4958 else
4959 pidgin_blist_update_group(list, cnode->parent);
4960
4961 contact = (PurpleContact*)cnode;
4962 buddy = purple_contact_get_priority_buddy(contact);
4963
4964 if (buddy_is_displayable(buddy))
4965 {
4966 GtkTreeIter iter;
4967
4968 if(!insert_node(list, cnode, &iter))
4969 return;
4970
4971 gtknode = (struct _pidgin_blist_node *)cnode->ui_data;
4972
4973 if(gtknode->contact_expanded) {
4974 GdkPixbuf *status;
4975 char *mark;
4976
4977 status = pidgin_blist_get_status_icon(cnode,
4978 PIDGIN_STATUS_ICON_SMALL);
4979
4980 mark = g_markup_escape_text(purple_contact_get_alias(contact), -1);
4981 gtk_tree_store_set(gtkblist->treemodel, &iter,
4982 STATUS_ICON_COLUMN, status,
4983 STATUS_ICON_VISIBLE_COLUMN, TRUE,
4984 NAME_COLUMN, mark,
4985 IDLE_COLUMN, NULL,
4986 IDLE_VISIBLE_COLUMN, FALSE,
4987 BGCOLOR_COLUMN, NULL,
4988 BUDDY_ICON_COLUMN, NULL,
4989 CONTACT_EXPANDER_COLUMN, TRUE,
4990 CONTACT_EXPANDER_VISIBLE_COLUMN, TRUE,
4991 GROUP_EXPANDER_VISIBLE_COLUMN, FALSE,
4992 -1);
4993 g_free(mark);
4994 if(status)
4995 g_object_unref(status);
4996 } else {
4997 buddy_node(buddy, &iter, cnode);
4998 }
4999 } else {
5000 pidgin_blist_hide_node(list, cnode, TRUE);
5001 }
5002 }
5003
5004
5005
5006 static void pidgin_blist_update_buddy(PurpleBuddyList *list, PurpleBlistNode *node, gboolean statusChange)
5007 {
5008 PurpleBuddy *buddy;
5009 struct _pidgin_blist_node *gtkparentnode;
5010
5011 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
5012
5013 if (node->parent == NULL)
5014 return;
5015
5016 buddy = (PurpleBuddy*)node;
5017
5018 /* First things first, update the contact */
5019 pidgin_blist_update_contact(list, node);
5020
5021 gtkparentnode = (struct _pidgin_blist_node *)node->parent->ui_data;
5022
5023 if (gtkparentnode->contact_expanded && buddy_is_displayable(buddy))
5024 {
5025 GtkTreeIter iter;
5026
5027 if (!insert_node(list, node, &iter))
5028 return;
5029
5030 buddy_node(buddy, &iter, node);
5031
5032 } else {
5033 pidgin_blist_hide_node(list, node, TRUE);
5034 }
5035
5036 }
5037
5038 static void pidgin_blist_update_chat(PurpleBuddyList *list, PurpleBlistNode *node)
5039 {
5040 PurpleChat *chat;
5041
5042 g_return_if_fail(PURPLE_BLIST_NODE_IS_CHAT(node));
5043
5044 /* First things first, update the group */
5045 pidgin_blist_update_group(list, node->parent);
5046
5047 chat = (PurpleChat*)node;
5048
5049 if(purple_account_is_connected(chat->account)) {
5050 GtkTreeIter iter;
5051 GdkPixbuf *status;
5052 GdkPixbuf *avatar;
5053 GdkPixbuf *emblem;
5054 char *mark;
5055
5056 if(!insert_node(list, node, &iter))
5057 return;
5058
5059 status = pidgin_blist_get_status_icon(node,
5060 PIDGIN_STATUS_ICON_SMALL);
5061 emblem = pidgin_blist_get_emblem(node);
5062 avatar = pidgin_blist_get_buddy_icon(node, TRUE, FALSE, TRUE);
5063
5064 mark = g_markup_escape_text(purple_chat_get_name(chat), -1);
5065
5066 gtk_tree_store_set(gtkblist->treemodel, &iter,
5067 STATUS_ICON_COLUMN, status,
5068 STATUS_ICON_VISIBLE_COLUMN, TRUE,
5069 BUDDY_ICON_COLUMN, avatar ? avatar : gtkblist->empty_avatar,
5070 BUDDY_ICON_VISIBLE_COLUMN, purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons"),
5071 EMBLEM_COLUMN, emblem,
5072 EMBLEM_VISIBLE_COLUMN, emblem != NULL,
5073 NAME_COLUMN, mark,
5074 GROUP_EXPANDER_VISIBLE_COLUMN, FALSE,
5075 -1);
5076
5077 g_free(mark);
5078 if(status)
5079 g_object_unref(status);
5080 if(avatar)
5081 g_object_unref(avatar);
5082 } else {
5083 pidgin_blist_hide_node(list, node, TRUE);
5084 }
5085 }
5086
5087 static void pidgin_blist_update(PurpleBuddyList *list, PurpleBlistNode *node)
5088 {
5089 if (list)
5090 gtkblist = PIDGIN_BLIST(list);
5091 if(!gtkblist || !gtkblist->treeview || !node)
5092 return;
5093
5094 if (node->ui_data == NULL)
5095 pidgin_blist_new_node(node);
5096
5097 switch(node->type) {
5098 case PURPLE_BLIST_GROUP_NODE:
5099 pidgin_blist_update_group(list, node);
5100 break;
5101 case PURPLE_BLIST_CONTACT_NODE:
5102 pidgin_blist_update_contact(list, node);
5103 break;
5104 case PURPLE_BLIST_BUDDY_NODE:
5105 pidgin_blist_update_buddy(list, node, TRUE);
5106 break;
5107 case PURPLE_BLIST_CHAT_NODE:
5108 pidgin_blist_update_chat(list, node);
5109 break;
5110 case PURPLE_BLIST_OTHER_NODE:
5111 return;
5112 }
5113
5114 #if !GTK_CHECK_VERSION(2,6,0)
5115 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(gtkblist->treeview));
5116 #endif
5117 }
5118
5119
5120 static void pidgin_blist_destroy(PurpleBuddyList *list)
5121 {
5122 if (!gtkblist)
5123 return;
5124
5125 purple_signals_disconnect_by_handle(gtkblist);
5126
5127 if (gtkblist->headline_close)
5128 gdk_pixbuf_unref(gtkblist->headline_close);
5129
5130 gtk_widget_destroy(gtkblist->window);
5131
5132 pidgin_blist_tooltip_destroy();
5133
5134 if (gtkblist->refresh_timer)
5135 g_source_remove(gtkblist->refresh_timer);
5136 if (gtkblist->timeout)
5137 g_source_remove(gtkblist->timeout);
5138 if (gtkblist->drag_timeout)
5139 g_source_remove(gtkblist->drag_timeout);
5140
5141 g_hash_table_destroy(gtkblist->connection_errors);
5142 gtkblist->refresh_timer = 0;
5143 gtkblist->timeout = 0;
5144 gtkblist->drag_timeout = 0;
5145 gtkblist->window = gtkblist->vbox = gtkblist->treeview = NULL;
5146 gtkblist->treemodel = NULL;
5147 g_object_unref(G_OBJECT(gtkblist->ift));
5148 g_object_unref(G_OBJECT(gtkblist->empty_avatar));
5149
5150 gdk_cursor_unref(gtkblist->hand_cursor);
5151 gdk_cursor_unref(gtkblist->arrow_cursor);
5152 gtkblist->hand_cursor = NULL;
5153 gtkblist->arrow_cursor = NULL;
5154
5155 g_free(gtkblist);
5156 accountmenu = NULL;
5157 gtkblist = NULL;
5158 purple_prefs_disconnect_by_handle(pidgin_blist_get_handle());
5159 }
5160
5161 static void pidgin_blist_set_visible(PurpleBuddyList *list, gboolean show)
5162 {
5163 if (!(gtkblist && gtkblist->window))
5164 return;
5165
5166 if (show) {
5167 if(!PIDGIN_WINDOW_ICONIFIED(gtkblist->window) && !GTK_WIDGET_VISIBLE(gtkblist->window))
5168 purple_signal_emit(pidgin_blist_get_handle(), "gtkblist-unhiding", gtkblist);
5169 pidgin_blist_restore_position();
5170 gtk_window_present(GTK_WINDOW(gtkblist->window));
5171 } else {
5172 if(visibility_manager_count) {
5173 purple_signal_emit(pidgin_blist_get_handle(), "gtkblist-hiding", gtkblist);
5174 gtk_widget_hide(gtkblist->window);
5175 } else {
5176 if (!GTK_WIDGET_VISIBLE(gtkblist->window))
5177 gtk_widget_show(gtkblist->window);
5178 gtk_window_iconify(GTK_WINDOW(gtkblist->window));
5179 }
5180 }
5181 }
5182
5183 static GList *
5184 groups_tree(void)
5185 {
5186 GList *tmp = NULL;
5187 char *tmp2;
5188 PurpleGroup *g;
5189 PurpleBlistNode *gnode;
5190
5191 if (purple_get_blist()->root == NULL)
5192 {
5193 tmp2 = g_strdup(_("Buddies"));
5194 tmp = g_list_append(tmp, tmp2);
5195 }
5196 else
5197 {
5198 for (gnode = purple_get_blist()->root;
5199 gnode != NULL;
5200 gnode = gnode->next)
5201 {
5202 if (PURPLE_BLIST_NODE_IS_GROUP(gnode))
5203 {
5204 g = (PurpleGroup *)gnode;
5205 tmp2 = g->name;
5206 tmp = g_list_append(tmp, tmp2);
5207 }
5208 }
5209 }
5210
5211 return tmp;
5212 }
5213
5214 static void
5215 add_buddy_select_account_cb(GObject *w, PurpleAccount *account,
5216 PidginAddBuddyData *data)
5217 {
5218 /* Save our account */
5219 data->account = account;
5220 }
5221
5222 static void
5223 destroy_add_buddy_dialog_cb(GtkWidget *win, PidginAddBuddyData *data)
5224 {
5225 g_free(data);
5226 }
5227
5228 static void
5229 add_buddy_cb(GtkWidget *w, int resp, PidginAddBuddyData *data)
5230 {
5231 const char *grp, *who, *whoalias;
5232 PurpleGroup *g;
5233 PurpleBuddy *b;
5234 PurpleConversation *c;
5235 PurpleBuddyIcon *icon;
5236
5237 if (resp == GTK_RESPONSE_OK)
5238 {
5239 who = gtk_entry_get_text(GTK_ENTRY(data->entry));
5240 grp = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(data->combo)->entry));
5241 whoalias = gtk_entry_get_text(GTK_ENTRY(data->entry_for_alias));
5242 if (*whoalias == '\0')
5243 whoalias = NULL;
5244
5245 if ((g = purple_find_group(grp)) == NULL)
5246 {
5247 g = purple_group_new(grp);
5248 purple_blist_add_group(g, NULL);
5249 }
5250
5251 b = purple_buddy_new(data->account, who, whoalias);
5252 purple_blist_add_buddy(b, NULL, g, NULL);
5253 purple_account_add_buddy(data->account, b);
5254
5255 /*
5256 * XXX
5257 * It really seems like it would be better if the call to
5258 * purple_account_add_buddy() and purple_conversation_update() were done in
5259 * blist.c, possibly in the purple_blist_add_buddy() function. Maybe
5260 * purple_account_add_buddy() should be renamed to
5261 * purple_blist_add_new_buddy() or something, and have it call
5262 * purple_blist_add_buddy() after it creates it. --Mark
5263 *
5264 * No that's not good. blist.c should only deal with adding nodes to the
5265 * local list. We need a new, non-gtk file that calls both
5266 * purple_account_add_buddy and purple_blist_add_buddy().
5267 * Or something. --Mark
5268 */
5269
5270 c = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, who, data->account);
5271 if (c != NULL) {
5272 icon = purple_conv_im_get_icon(PURPLE_CONV_IM(c));
5273 if (icon != NULL)
5274 purple_buddy_icon_update(icon);
5275 }
5276 }
5277
5278 gtk_widget_destroy(data->window);
5279 }
5280
5281 static void
5282 pidgin_blist_request_add_buddy(PurpleAccount *account, const char *username,
5283 const char *group, const char *alias)
5284 {
5285 GtkWidget *table;
5286 GtkWidget *label;
5287 GtkWidget *hbox;
5288 GtkWidget *vbox;
5289 GtkWidget *img;
5290 PidginBuddyList *gtkblist;
5291 PidginAddBuddyData *data = g_new0(PidginAddBuddyData, 1);
5292
5293 data->account =
5294 (account != NULL
5295 ? account
5296 : purple_connection_get_account(purple_connections_get_all()->data));
5297
5298 img = gtk_image_new_from_stock(PIDGIN_STOCK_DIALOG_QUESTION,
5299 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_HUGE));
5300
5301 gtkblist = PIDGIN_BLIST(purple_get_blist());
5302
5303 data->window = gtk_dialog_new_with_buttons(_("Add Buddy"),
5304 NULL, GTK_DIALOG_NO_SEPARATOR,
5305 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
5306 GTK_STOCK_ADD, GTK_RESPONSE_OK,
5307 NULL);
5308
5309 gtk_dialog_set_default_response(GTK_DIALOG(data->window), GTK_RESPONSE_OK);
5310 gtk_container_set_border_width(GTK_CONTAINER(data->window), PIDGIN_HIG_BOX_SPACE);
5311 gtk_window_set_resizable(GTK_WINDOW(data->window), FALSE);
5312 gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(data->window)->vbox), PIDGIN_HIG_BORDER);
5313 gtk_container_set_border_width(GTK_CONTAINER(GTK_DIALOG(data->window)->vbox), PIDGIN_HIG_BOX_SPACE);
5314 gtk_window_set_role(GTK_WINDOW(data->window), "add_buddy");
5315 gtk_window_set_type_hint(GTK_WINDOW(data->window),
5316 GDK_WINDOW_TYPE_HINT_DIALOG);
5317
5318 hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BORDER);
5319 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(data->window)->vbox), hbox);
5320 gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0);
5321 gtk_misc_set_alignment(GTK_MISC(img), 0, 0);
5322
5323 vbox = gtk_vbox_new(FALSE, 0);
5324 gtk_container_add(GTK_CONTAINER(hbox), vbox);
5325
5326 label = gtk_label_new(
5327 _("Please enter the screen name of the person you would like "
5328 "to add to your buddy list. You may optionally enter an alias, "
5329 "or nickname, for the buddy. The alias will be displayed in "
5330 "place of the screen name whenever possible.\n"));
5331
5332 gtk_widget_set_size_request(GTK_WIDGET(label), 400, -1);
5333 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
5334 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
5335 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
5336
5337 hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
5338 gtk_container_add(GTK_CONTAINER(vbox), hbox);
5339
5340 g_signal_connect(G_OBJECT(data->window), "destroy",
5341 G_CALLBACK(destroy_add_buddy_dialog_cb), data);
5342
5343 table = gtk_table_new(4, 2, FALSE);
5344 gtk_table_set_row_spacings(GTK_TABLE(table), 5);
5345 gtk_table_set_col_spacings(GTK_TABLE(table), 5);
5346 gtk_container_set_border_width(GTK_CONTAINER(table), 0);
5347 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
5348
5349 label = gtk_label_new(_("Screen name:"));
5350 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
5351 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 0, 1);
5352
5353 data->entry = gtk_entry_new();
5354 gtk_table_attach_defaults(GTK_TABLE(table), data->entry, 1, 2, 0, 1);
5355 gtk_widget_grab_focus(data->entry);
5356
5357 if (username != NULL)
5358 gtk_entry_set_text(GTK_ENTRY(data->entry), username);
5359 else
5360 gtk_dialog_set_response_sensitive(GTK_DIALOG(data->window),
5361 GTK_RESPONSE_OK, FALSE);
5362
5363 gtk_entry_set_activates_default (GTK_ENTRY(data->entry), TRUE);
5364 pidgin_set_accessible_label (data->entry, label);
5365
5366 g_signal_connect(G_OBJECT(data->entry), "changed",
5367 G_CALLBACK(pidgin_set_sensitive_if_input),
5368 data->window);
5369
5370 label = gtk_label_new(_("Alias:"));
5371 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
5372 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 1, 2);
5373
5374 data->entry_for_alias = gtk_entry_new();
5375 gtk_table_attach_defaults(GTK_TABLE(table),
5376 data->entry_for_alias, 1, 2, 1, 2);
5377
5378 if (alias != NULL)
5379 gtk_entry_set_text(GTK_ENTRY(data->entry_for_alias), alias);
5380
5381 if (username != NULL)
5382 gtk_widget_grab_focus(GTK_WIDGET(data->entry_for_alias));
5383
5384 gtk_entry_set_activates_default (GTK_ENTRY(data->entry_for_alias), TRUE);
5385 pidgin_set_accessible_label (data->entry_for_alias, label);
5386
5387 label = gtk_label_new(_("Group:"));
5388 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
5389 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 2, 3);
5390
5391 data->combo = gtk_combo_new();
5392 gtk_combo_set_popdown_strings(GTK_COMBO(data->combo), groups_tree());
5393 gtk_table_attach_defaults(GTK_TABLE(table), data->combo, 1, 2, 2, 3);
5394 pidgin_set_accessible_label (data->combo, label);
5395
5396 /* Set up stuff for the account box */
5397 label = gtk_label_new(_("Account:"));
5398 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
5399 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 3, 4);
5400
5401 data->account_box = pidgin_account_option_menu_new(account, FALSE,
5402 G_CALLBACK(add_buddy_select_account_cb), NULL, data);
5403
5404 gtk_table_attach_defaults(GTK_TABLE(table), data->account_box, 1, 2, 3, 4);
5405 pidgin_set_accessible_label (data->account_box, label);
5406 /* End of account box */
5407
5408 g_signal_connect(G_OBJECT(data->window), "response",
5409 G_CALLBACK(add_buddy_cb), data);
5410
5411 gtk_widget_show_all(data->window);
5412
5413 if (group != NULL)
5414 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(data->combo)->entry), group);
5415 }
5416
5417 static void
5418 add_chat_cb(GtkWidget *w, PidginAddChatData *data)
5419 {
5420 GHashTable *components;
5421 GList *tmp;
5422 PurpleChat *chat;
5423 PurpleGroup *group;
5424 const char *group_name;
5425 const char *value;
5426
5427 components = g_hash_table_new_full(g_str_hash, g_str_equal,
5428 g_free, g_free);
5429
5430 for (tmp = data->entries; tmp; tmp = tmp->next)
5431 {
5432 if (g_object_get_data(tmp->data, "is_spin"))
5433 {
5434 g_hash_table_replace(components,
5435 g_strdup(g_object_get_data(tmp->data, "identifier")),
5436 g_strdup_printf("%d",
5437 gtk_spin_button_get_value_as_int(tmp->data)));
5438 }
5439 else
5440 {
5441 value = gtk_entry_get_text(tmp->data);
5442 if (*value != '\0')
5443 g_hash_table_replace(components,
5444 g_strdup(g_object_get_data(tmp->data, "identifier")),
5445 g_strdup(value));
5446 }
5447 }
5448
5449 chat = purple_chat_new(data->account,
5450 gtk_entry_get_text(GTK_ENTRY(data->alias_entry)),
5451 components);
5452
5453 group_name = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(data->group_combo)->entry));
5454
5455 if ((group = purple_find_group(group_name)) == NULL)
5456 {
5457 group = purple_group_new(group_name);
5458 purple_blist_add_group(group, NULL);
5459 }
5460
5461 if (chat != NULL)
5462 {
5463 purple_blist_add_chat(chat, group, NULL);
5464 }
5465
5466 gtk_widget_destroy(data->window);
5467 g_free(data->default_chat_name);
5468 g_list_free(data->entries);
5469 g_free(data);
5470 }
5471
5472 static void
5473 add_chat_resp_cb(GtkWidget *w, int resp, PidginAddChatData *data)
5474 {
5475 if (resp == GTK_RESPONSE_OK)
5476 {
5477 add_chat_cb(NULL, data);
5478 }
5479 else
5480 {
5481 gtk_widget_destroy(data->window);
5482 g_free(data->default_chat_name);
5483 g_list_free(data->entries);
5484 g_free(data);
5485 }
5486 }
5487
5488 /*
5489 * Check the values of all the text entry boxes. If any required input
5490 * strings are empty then don't allow the user to click on "OK."
5491 */
5492 static void
5493 addchat_set_sensitive_if_input_cb(GtkWidget *entry, gpointer user_data)
5494 {
5495 PidginAddChatData *data;
5496 GList *tmp;
5497 const char *text;
5498 gboolean required;
5499 gboolean sensitive = TRUE;
5500
5501 data = user_data;
5502
5503 for (tmp = data->entries; tmp != NULL; tmp = tmp->next)
5504 {
5505 if (!g_object_get_data(tmp->data, "is_spin"))
5506 {
5507 required = GPOINTER_TO_INT(g_object_get_data(tmp->data, "required"));
5508 text = gtk_entry_get_text(tmp->data);
5509 if (required && (*text == '\0'))
5510 sensitive = FALSE;
5511 }
5512 }
5513
5514 gtk_dialog_set_response_sensitive(GTK_DIALOG(data->window), GTK_RESPONSE_OK, sensitive);
5515 }
5516
5517 static void
5518 rebuild_addchat_entries(PidginAddChatData *data)
5519 {
5520 PurpleConnection *gc;
5521 GList *list = NULL, *tmp;
5522 GHashTable *defaults = NULL;
5523 struct proto_chat_entry *pce;
5524 gboolean focus = TRUE;
5525
5526 g_return_if_fail(data->account != NULL);
5527
5528 gc = purple_account_get_connection(data->account);
5529
5530 while ((tmp = gtk_container_get_children(GTK_CONTAINER(data->entries_box))))
5531 gtk_widget_destroy(tmp->data);
5532
5533 g_list_free(data->entries);
5534
5535 data->entries = NULL;
5536
5537 if (PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info != NULL)
5538 list = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info(gc);
5539
5540 if (PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info_defaults != NULL)
5541 defaults = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info_defaults(gc, data->default_chat_name);
5542
5543 for (tmp = list; tmp; tmp = tmp->next)
5544 {
5545 GtkWidget *label;
5546 GtkWidget *rowbox;
5547 GtkWidget *input;
5548
5549 pce = tmp->data;
5550
5551 rowbox = gtk_hbox_new(FALSE, 5);
5552 gtk_box_pack_start(GTK_BOX(data->entries_box), rowbox, FALSE, FALSE, 0);
5553
5554 label = gtk_label_new_with_mnemonic(pce->label);
5555 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
5556 gtk_size_group_add_widget(data->sg, label);
5557 gtk_box_pack_start(GTK_BOX(rowbox), label, FALSE, FALSE, 0);
5558
5559 if (pce->is_int)
5560 {
5561 GtkObject *adjust;
5562 adjust = gtk_adjustment_new(pce->min, pce->min, pce->max,
5563 1, 10, 10);
5564 input = gtk_spin_button_new(GTK_ADJUSTMENT(adjust), 1, 0);
5565 gtk_widget_set_size_request(input, 50, -1);
5566 gtk_box_pack_end(GTK_BOX(rowbox), input, FALSE, FALSE, 0);
5567 }
5568 else
5569 {
5570 char *value;
5571 input = gtk_entry_new();
5572 gtk_entry_set_activates_default(GTK_ENTRY(input), TRUE);
5573 value = g_hash_table_lookup(defaults, pce->identifier);
5574 if (value != NULL)
5575 gtk_entry_set_text(GTK_ENTRY(input), value);
5576 if (pce->secret)
5577 {
5578 gtk_entry_set_visibility(GTK_ENTRY(input), FALSE);
5579 if (gtk_entry_get_invisible_char(GTK_ENTRY(input)) == '*')
5580 gtk_entry_set_invisible_char(GTK_ENTRY(input), PIDGIN_INVISIBLE_CHAR);
5581 }
5582 gtk_box_pack_end(GTK_BOX(rowbox), input, TRUE, TRUE, 0);
5583 g_signal_connect(G_OBJECT(input), "changed",
5584 G_CALLBACK(addchat_set_sensitive_if_input_cb), data);
5585 }
5586
5587 /* Do the following for any type of input widget */
5588 if (focus)
5589 {
5590 gtk_widget_grab_focus(input);
5591 focus = FALSE;
5592 }
5593 gtk_label_set_mnemonic_widget(GTK_LABEL(label), input);
5594 pidgin_set_accessible_label(input, label);
5595 g_object_set_data(G_OBJECT(input), "identifier", (gpointer)pce->identifier);
5596 g_object_set_data(G_OBJECT(input), "is_spin", GINT_TO_POINTER(pce->is_int));
5597 g_object_set_data(G_OBJECT(input), "required", GINT_TO_POINTER(pce->required));
5598 data->entries = g_list_append(data->entries, input);
5599
5600 g_free(pce);
5601 }
5602
5603 g_list_free(list);
5604 g_hash_table_destroy(defaults);
5605
5606 /* Set whether the "OK" button should be clickable initially */
5607 addchat_set_sensitive_if_input_cb(NULL, data);
5608
5609 gtk_widget_show_all(data->entries_box);
5610 }
5611
5612 static void
5613 addchat_select_account_cb(GObject *w, PurpleAccount *account,
5614 PidginAddChatData *data)
5615 {
5616 if (strcmp(purple_account_get_protocol_id(data->account),
5617 purple_account_get_protocol_id(account)) == 0)
5618 {
5619 data->account = account;
5620 }
5621 else
5622 {
5623 data->account = account;
5624 rebuild_addchat_entries(data);
5625 }
5626 }
5627
5628 static void
5629 pidgin_blist_request_add_chat(PurpleAccount *account, PurpleGroup *group,
5630 const char *alias, const char *name)
5631 {
5632 PidginAddChatData *data;
5633 PidginBuddyList *gtkblist;
5634 GList *l;
5635 PurpleConnection *gc;
5636 GtkWidget *label;
5637 GtkWidget *rowbox;
5638 GtkWidget *hbox;
5639 GtkWidget *vbox;
5640 GtkWidget *img;
5641
5642 if (account != NULL) {
5643 gc = purple_account_get_connection(account);
5644
5645 if (PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->join_chat == NULL) {
5646 purple_notify_error(gc, NULL, _("This protocol does not support chat rooms."), NULL);
5647 return;
5648 }
5649 } else {
5650 /* Find an account with chat capabilities */
5651 for (l = purple_connections_get_all(); l != NULL; l = l->next) {
5652 gc = (PurpleConnection *)l->data;
5653
5654 if (PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->join_chat != NULL) {
5655 account = purple_connection_get_account(gc);
5656 break;
5657 }
5658 }
5659
5660 if (account == NULL) {
5661 purple_notify_error(NULL, NULL,
5662 _("You are not currently signed on with any "
5663 "protocols that have the ability to chat."), NULL);
5664 return;
5665 }
5666 }
5667
5668 data = g_new0(PidginAddChatData, 1);
5669 data->account = account;
5670 data->default_chat_name = g_strdup(name);
5671
5672 img = gtk_image_new_from_stock(PIDGIN_STOCK_DIALOG_QUESTION,
5673 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_HUGE));
5674
5675 gtkblist = PIDGIN_BLIST(purple_get_blist());
5676
5677 data->sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
5678
5679 data->window = gtk_dialog_new_with_buttons(_("Add Chat"),
5680 NULL, GTK_DIALOG_NO_SEPARATOR,
5681 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
5682 GTK_STOCK_ADD, GTK_RESPONSE_OK,
5683 NULL);
5684
5685 gtk_dialog_set_default_response(GTK_DIALOG(data->window), GTK_RESPONSE_OK);
5686 gtk_container_set_border_width(GTK_CONTAINER(data->window), PIDGIN_HIG_BOX_SPACE);
5687 gtk_window_set_resizable(GTK_WINDOW(data->window), FALSE);
5688 gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(data->window)->vbox), PIDGIN_HIG_BORDER);
5689 gtk_container_set_border_width(GTK_CONTAINER(GTK_DIALOG(data->window)->vbox), PIDGIN_HIG_BOX_SPACE);
5690 gtk_window_set_role(GTK_WINDOW(data->window), "add_chat");
5691 gtk_window_set_type_hint(GTK_WINDOW(data->window),
5692 GDK_WINDOW_TYPE_HINT_DIALOG);
5693
5694 hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BORDER);
5695 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(data->window)->vbox), hbox);
5696 gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0);
5697 gtk_misc_set_alignment(GTK_MISC(img), 0, 0);
5698
5699 vbox = gtk_vbox_new(FALSE, 5);
5700 gtk_container_add(GTK_CONTAINER(hbox), vbox);
5701
5702 label = gtk_label_new(
5703 _("Please enter an alias, and the appropriate information "
5704 "about the chat you would like to add to your buddy list.\n"));
5705 gtk_widget_set_size_request(label, 400, -1);
5706 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
5707 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
5708 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
5709
5710 rowbox = gtk_hbox_new(FALSE, 5);
5711 gtk_box_pack_start(GTK_BOX(vbox), rowbox, FALSE, FALSE, 0);
5712
5713 label = gtk_label_new(_("Account:"));
5714 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
5715 gtk_size_group_add_widget(data->sg, label);
5716 gtk_box_pack_start(GTK_BOX(rowbox), label, FALSE, FALSE, 0);
5717
5718 data->account_menu = pidgin_account_option_menu_new(account, FALSE,
5719 G_CALLBACK(addchat_select_account_cb),
5720 chat_account_filter_func, data);
5721 gtk_box_pack_start(GTK_BOX(rowbox), data->account_menu, TRUE, TRUE, 0);
5722 pidgin_set_accessible_label (data->account_menu, label);
5723
5724 data->entries_box = gtk_vbox_new(FALSE, 5);
5725 gtk_container_set_border_width(GTK_CONTAINER(data->entries_box), 0);
5726 gtk_box_pack_start(GTK_BOX(vbox), data->entries_box, TRUE, TRUE, 0);
5727
5728 rebuild_addchat_entries(data);
5729
5730 rowbox = gtk_hbox_new(FALSE, 5);
5731 gtk_box_pack_start(GTK_BOX(vbox), rowbox, FALSE, FALSE, 0);
5732
5733 label = gtk_label_new(_("Alias:"));
5734 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
5735 gtk_size_group_add_widget(data->sg, label);
5736 gtk_box_pack_start(GTK_BOX(rowbox), label, FALSE, FALSE, 0);
5737
5738 data->alias_entry = gtk_entry_new();
5739 if (alias != NULL)
5740 gtk_entry_set_text(GTK_ENTRY(data->alias_entry), alias);
5741 gtk_box_pack_end(GTK_BOX(rowbox), data->alias_entry, TRUE, TRUE, 0);
5742 gtk_entry_set_activates_default(GTK_ENTRY(data->alias_entry), TRUE);
5743 pidgin_set_accessible_label (data->alias_entry, label);
5744
5745 rowbox = gtk_hbox_new(FALSE, 5);
5746 gtk_box_pack_start(GTK_BOX(vbox), rowbox, FALSE, FALSE, 0);
5747
5748 label = gtk_label_new(_("Group:"));
5749 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
5750 gtk_size_group_add_widget(data->sg, label);
5751 gtk_box_pack_start(GTK_BOX(rowbox), label, FALSE, FALSE, 0);
5752
5753 data->group_combo = gtk_combo_new();
5754 gtk_combo_set_popdown_strings(GTK_COMBO(data->group_combo), groups_tree());
5755 gtk_box_pack_end(GTK_BOX(rowbox), data->group_combo, TRUE, TRUE, 0);
5756
5757 if (group)
5758 {
5759 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(data->group_combo)->entry),
5760 group->name);
5761 }
5762 pidgin_set_accessible_label (data->group_combo, label);
5763
5764 g_signal_connect(G_OBJECT(data->window), "response",
5765 G_CALLBACK(add_chat_resp_cb), data);
5766
5767 gtk_widget_show_all(data->window);
5768 }
5769
5770 static void
5771 add_group_cb(PurpleConnection *gc, const char *group_name)
5772 {
5773 PurpleGroup *group;
5774
5775 if ((group_name == NULL) || (*group_name == '\0'))
5776 return;
5777
5778 group = purple_group_new(group_name);
5779 purple_blist_add_group(group, NULL);
5780 }
5781
5782 static void
5783 pidgin_blist_request_add_group(void)
5784 {
5785 purple_request_input(NULL, _("Add Group"), NULL,
5786 _("Please enter the name of the group to be added."),
5787 NULL, FALSE, FALSE, NULL,
5788 _("Add"), G_CALLBACK(add_group_cb),
5789 _("Cancel"), NULL, NULL);
5790 }
5791
5792 void
5793 pidgin_blist_toggle_visibility()
5794 {
5795 if (gtkblist && gtkblist->window) {
5796 if (GTK_WIDGET_VISIBLE(gtkblist->window)) {
5797 purple_blist_set_visible(PIDGIN_WINDOW_ICONIFIED(gtkblist->window) || gtk_blist_obscured);
5798 } else {
5799 purple_blist_set_visible(TRUE);
5800 }
5801 }
5802 }
5803
5804 void
5805 pidgin_blist_visibility_manager_add()
5806 {
5807 visibility_manager_count++;
5808 purple_debug_info("gtkblist", "added visibility manager: %d\n", visibility_manager_count);
5809 }
5810
5811 void
5812 pidgin_blist_visibility_manager_remove()
5813 {
5814 if (visibility_manager_count)
5815 visibility_manager_count--;
5816 if (!visibility_manager_count)
5817 purple_blist_set_visible(TRUE);
5818 purple_debug_info("gtkblist", "removed visibility manager: %d\n", visibility_manager_count);
5819 }
5820
5821 void pidgin_blist_add_alert(GtkWidget *widget)
5822 {
5823 gtk_container_add(GTK_CONTAINER(gtkblist->scrollbook), widget);
5824 if (!GTK_WIDGET_HAS_FOCUS(gtkblist->window))
5825 pidgin_set_urgent(GTK_WINDOW(gtkblist->window), TRUE);
5826 }
5827
5828 void
5829 pidgin_blist_set_headline(const char *text, GdkPixbuf *pixbuf, GCallback callback,
5830 gpointer user_data, GDestroyNotify destroy)
5831 {
5832 /* Destroy any existing headline first */
5833 if (gtkblist->headline_destroy)
5834 gtkblist->headline_destroy(gtkblist->headline_data);
5835
5836 gtk_label_set_markup(GTK_LABEL(gtkblist->headline_label), text);
5837 gtk_image_set_from_pixbuf(GTK_IMAGE(gtkblist->headline_image), pixbuf);
5838
5839 gtkblist->headline_callback = callback;
5840 gtkblist->headline_data = user_data;
5841 gtkblist->headline_destroy = destroy;
5842 if (!GTK_WIDGET_HAS_FOCUS(gtkblist->window))
5843 pidgin_set_urgent(GTK_WINDOW(gtkblist->window), TRUE);
5844 gtk_widget_show_all(gtkblist->headline_hbox);
5845 }
5846
5847 static PurpleBlistUiOps blist_ui_ops =
5848 {
5849 pidgin_blist_new_list,
5850 pidgin_blist_new_node,
5851 pidgin_blist_show,
5852 pidgin_blist_update,
5853 pidgin_blist_remove,
5854 pidgin_blist_destroy,
5855 pidgin_blist_set_visible,
5856 pidgin_blist_request_add_buddy,
5857 pidgin_blist_request_add_chat,
5858 pidgin_blist_request_add_group
5859 };
5860
5861
5862 PurpleBlistUiOps *
5863 pidgin_blist_get_ui_ops(void)
5864 {
5865 return &blist_ui_ops;
5866 }
5867
5868 PidginBuddyList *pidgin_blist_get_default_gtk_blist()
5869 {
5870 return gtkblist;
5871 }
5872
5873 static void account_signon_cb(PurpleConnection *gc, gpointer z)
5874 {
5875 PurpleAccount *account = purple_connection_get_account(gc);
5876 PurpleBlistNode *gnode, *cnode;
5877 for(gnode = purple_get_blist()->root; gnode; gnode = gnode->next)
5878 {
5879 if(!PURPLE_BLIST_NODE_IS_GROUP(gnode))
5880 continue;
5881 for(cnode = gnode->child; cnode; cnode = cnode->next)
5882 {
5883 PurpleChat *chat;
5884
5885 if(!PURPLE_BLIST_NODE_IS_CHAT(cnode))
5886 continue;
5887
5888 chat = (PurpleChat *)cnode;
5889
5890 if(chat->account != account)
5891 continue;
5892
5893 if(purple_blist_node_get_bool((PurpleBlistNode*)chat, "gtk-autojoin") ||
5894 (purple_blist_node_get_string((PurpleBlistNode*)chat,
5895 "gtk-autojoin") != NULL))
5896 serv_join_chat(gc, chat->components);
5897 }
5898 }
5899 }
5900
5901 void *
5902 pidgin_blist_get_handle() {
5903 static int handle;
5904
5905 return &handle;
5906 }
5907
5908 static gboolean buddy_signonoff_timeout_cb(PurpleBuddy *buddy)
5909 {
5910 struct _pidgin_blist_node *gtknode = ((PurpleBlistNode*)buddy)->ui_data;
5911
5912 gtknode->recent_signonoff = FALSE;
5913 gtknode->recent_signonoff_timer = 0;
5914
5915 pidgin_blist_update(NULL, (PurpleBlistNode*)buddy);
5916
5917 return FALSE;
5918 }
5919
5920 static void buddy_signonoff_cb(PurpleBuddy *buddy)
5921 {
5922 struct _pidgin_blist_node *gtknode;
5923
5924 if(!((PurpleBlistNode*)buddy)->ui_data) {
5925 pidgin_blist_new_node((PurpleBlistNode*)buddy);
5926 }
5927
5928 gtknode = ((PurpleBlistNode*)buddy)->ui_data;
5929
5930 gtknode->recent_signonoff = TRUE;
5931
5932 if(gtknode->recent_signonoff_timer > 0)
5933 purple_timeout_remove(gtknode->recent_signonoff_timer);
5934 gtknode->recent_signonoff_timer = purple_timeout_add(10000,
5935 (GSourceFunc)buddy_signonoff_timeout_cb, buddy);
5936 }
5937
5938 void pidgin_blist_init(void)
5939 {
5940 void *gtk_blist_handle = pidgin_blist_get_handle();
5941
5942 purple_signal_connect(purple_connections_get_handle(), "signed-on",
5943 gtk_blist_handle, PURPLE_CALLBACK(account_signon_cb),
5944 NULL);
5945
5946 /* Initialize prefs */
5947 purple_prefs_add_none(PIDGIN_PREFS_ROOT "/blist");
5948 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons", TRUE);
5949 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/show_empty_groups", FALSE);
5950 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/show_idle_time", TRUE);
5951 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/show_offline_buddies", FALSE);
5952 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/list_visible", FALSE);
5953 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/list_maximized", FALSE);
5954 purple_prefs_add_string(PIDGIN_PREFS_ROOT "/blist/sort_type", "alphabetical");
5955 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/blist/x", 0);
5956 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/blist/y", 0);
5957 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/blist/width", 250); /* Golden ratio, baby */
5958 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/blist/height", 405); /* Golden ratio, baby */
5959 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/blist/tooltip_delay", 500);
5960
5961 /* Register our signals */
5962 purple_signal_register(gtk_blist_handle, "gtkblist-hiding",
5963 purple_marshal_VOID__POINTER, NULL, 1,
5964 purple_value_new(PURPLE_TYPE_SUBTYPE,
5965 PURPLE_SUBTYPE_BLIST));
5966
5967 purple_signal_register(gtk_blist_handle, "gtkblist-unhiding",
5968 purple_marshal_VOID__POINTER, NULL, 1,
5969 purple_value_new(PURPLE_TYPE_SUBTYPE,
5970 PURPLE_SUBTYPE_BLIST));
5971
5972 purple_signal_register(gtk_blist_handle, "gtkblist-created",
5973 purple_marshal_VOID__POINTER, NULL, 1,
5974 purple_value_new(PURPLE_TYPE_SUBTYPE,
5975 PURPLE_SUBTYPE_BLIST));
5976
5977 purple_signal_register(gtk_blist_handle, "drawing-tooltip",
5978 purple_marshal_VOID__POINTER_POINTER_UINT, NULL, 3,
5979 purple_value_new(PURPLE_TYPE_SUBTYPE,
5980 PURPLE_SUBTYPE_BLIST_NODE),
5981 purple_value_new_outgoing(PURPLE_TYPE_BOXED, "GString *"),
5982 purple_value_new(PURPLE_TYPE_BOOLEAN));
5983
5984
5985 purple_signal_connect(purple_blist_get_handle(), "buddy-signed-on", gtk_blist_handle, PURPLE_CALLBACK(buddy_signonoff_cb), NULL);
5986 purple_signal_connect(purple_blist_get_handle(), "buddy-signed-off", gtk_blist_handle, PURPLE_CALLBACK(buddy_signonoff_cb), NULL);
5987 purple_signal_connect(purple_blist_get_handle(), "buddy-privacy-changed", gtk_blist_handle, PURPLE_CALLBACK(pidgin_blist_update_privacy_cb), NULL);
5988 }
5989
5990 void
5991 pidgin_blist_uninit(void) {
5992 purple_signals_unregister_by_instance(pidgin_blist_get_handle());
5993 purple_signals_disconnect_by_handle(pidgin_blist_get_handle());
5994 }
5995
5996 /*********************************************************************
5997 * Buddy List sorting functions *
5998 *********************************************************************/
5999
6000 GList *pidgin_blist_get_sort_methods()
6001 {
6002 return pidgin_blist_sort_methods;
6003 }
6004
6005 void pidgin_blist_sort_method_reg(const char *id, const char *name, pidgin_blist_sort_function func)
6006 {
6007 struct pidgin_blist_sort_method *method = g_new0(struct pidgin_blist_sort_method, 1);
6008 method->id = g_strdup(id);
6009 method->name = g_strdup(name);
6010 method->func = func;
6011 pidgin_blist_sort_methods = g_list_append(pidgin_blist_sort_methods, method);
6012 pidgin_blist_update_sort_methods();
6013 }
6014
6015 void pidgin_blist_sort_method_unreg(const char *id){
6016 GList *l = pidgin_blist_sort_methods;
6017
6018 while(l) {
6019 struct pidgin_blist_sort_method *method = l->data;
6020 if(!strcmp(method->id, id)) {
6021 pidgin_blist_sort_methods = g_list_delete_link(pidgin_blist_sort_methods, l);
6022 g_free(method->id);
6023 g_free(method->name);
6024 g_free(method);
6025 break;
6026 }
6027 }
6028 pidgin_blist_update_sort_methods();
6029 }
6030
6031 void pidgin_blist_sort_method_set(const char *id){
6032 GList *l = pidgin_blist_sort_methods;
6033
6034 if(!id)
6035 id = "none";
6036
6037 while (l && strcmp(((struct pidgin_blist_sort_method*)l->data)->id, id))
6038 l = l->next;
6039
6040 if (l) {
6041 current_sort_method = l->data;
6042 } else if (!current_sort_method) {
6043 pidgin_blist_sort_method_set("none");
6044 return;
6045 }
6046 if (!strcmp(id, "none")) {
6047 redo_buddy_list(purple_get_blist(), TRUE, FALSE);
6048 } else {
6049 redo_buddy_list(purple_get_blist(), FALSE, FALSE);
6050 }
6051 }
6052
6053 /******************************************
6054 ** Sort Methods
6055 ******************************************/
6056
6057 static void sort_method_none(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter parent_iter, GtkTreeIter *cur, GtkTreeIter *iter)
6058 {
6059 PurpleBlistNode *sibling = node->prev;
6060 GtkTreeIter sibling_iter;
6061
6062 if (cur != NULL) {
6063 *iter = *cur;
6064 return;
6065 }
6066
6067 while (sibling && !get_iter_from_node(sibling, &sibling_iter)) {
6068 sibling = sibling->prev;
6069 }
6070
6071 gtk_tree_store_insert_after(gtkblist->treemodel, iter,
6072 node->parent ? &parent_iter : NULL,
6073 sibling ? &sibling_iter : NULL);
6074 }
6075
6076 #if GTK_CHECK_VERSION(2,2,1)
6077
6078 static void sort_method_alphabetical(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter)
6079 {
6080 GtkTreeIter more_z;
6081
6082 const char *my_name;
6083
6084 if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
6085 my_name = purple_contact_get_alias((PurpleContact*)node);
6086 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
6087 my_name = purple_chat_get_name((PurpleChat*)node);
6088 } else {
6089 sort_method_none(node, blist, groupiter, cur, iter);
6090 return;
6091 }
6092
6093
6094 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(gtkblist->treemodel), &more_z, &groupiter)) {
6095 gtk_tree_store_insert(gtkblist->treemodel, iter, &groupiter, 0);
6096 return;
6097 }
6098
6099 do {
6100 GValue val;
6101 PurpleBlistNode *n;
6102 const char *this_name;
6103 int cmp;
6104
6105 val.g_type = 0;
6106 gtk_tree_model_get_value (GTK_TREE_MODEL(gtkblist->treemodel), &more_z, NODE_COLUMN, &val);
6107 n = g_value_get_pointer(&val);
6108
6109 if(PURPLE_BLIST_NODE_IS_CONTACT(n)) {
6110 this_name = purple_contact_get_alias((PurpleContact*)n);
6111 } else if(PURPLE_BLIST_NODE_IS_CHAT(n)) {
6112 this_name = purple_chat_get_name((PurpleChat*)n);
6113 } else {
6114 this_name = NULL;
6115 }
6116
6117 cmp = purple_utf8_strcasecmp(my_name, this_name);
6118
6119 if(this_name && (cmp < 0 || (cmp == 0 && node < n))) {
6120 if(cur) {
6121 gtk_tree_store_move_before(gtkblist->treemodel, cur, &more_z);
6122 *iter = *cur;
6123 return;
6124 } else {
6125 gtk_tree_store_insert_before(gtkblist->treemodel, iter,
6126 &groupiter, &more_z);
6127 return;
6128 }
6129 }
6130 g_value_unset(&val);
6131 } while (gtk_tree_model_iter_next (GTK_TREE_MODEL(gtkblist->treemodel), &more_z));
6132
6133 if(cur) {
6134 gtk_tree_store_move_before(gtkblist->treemodel, cur, NULL);
6135 *iter = *cur;
6136 return;
6137 } else {
6138 gtk_tree_store_append(gtkblist->treemodel, iter, &groupiter);
6139 return;
6140 }
6141 }
6142
6143 static void sort_method_status(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter)
6144 {
6145 GtkTreeIter more_z;
6146
6147 PurpleBuddy *my_buddy, *this_buddy;
6148
6149 if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
6150 my_buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
6151 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
6152 if (cur != NULL) {
6153 *iter = *cur;
6154 return;
6155 }
6156
6157 gtk_tree_store_append(gtkblist->treemodel, iter, &groupiter);
6158 return;
6159 } else {
6160 sort_method_none(node, blist, groupiter, cur, iter);
6161 return;
6162 }
6163
6164
6165 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(gtkblist->treemodel), &more_z, &groupiter)) {
6166 gtk_tree_store_insert(gtkblist->treemodel, iter, &groupiter, 0);
6167 return;
6168 }
6169
6170 do {
6171 GValue val;
6172 PurpleBlistNode *n;
6173 gint name_cmp;
6174 gint presence_cmp;
6175
6176 val.g_type = 0;
6177 gtk_tree_model_get_value (GTK_TREE_MODEL(gtkblist->treemodel), &more_z, NODE_COLUMN, &val);
6178 n = g_value_get_pointer(&val);
6179
6180 if(PURPLE_BLIST_NODE_IS_CONTACT(n)) {
6181 this_buddy = purple_contact_get_priority_buddy((PurpleContact*)n);
6182 } else {
6183 this_buddy = NULL;
6184 }
6185
6186 name_cmp = purple_utf8_strcasecmp(
6187 purple_contact_get_alias(purple_buddy_get_contact(my_buddy)),
6188 (this_buddy
6189 ? purple_contact_get_alias(purple_buddy_get_contact(this_buddy))
6190 : NULL));
6191
6192 presence_cmp = purple_presence_compare(
6193 purple_buddy_get_presence(my_buddy),
6194 this_buddy ? purple_buddy_get_presence(this_buddy) : NULL);
6195
6196 if (this_buddy == NULL ||
6197 (presence_cmp < 0 ||
6198 (presence_cmp == 0 &&
6199 (name_cmp < 0 || (name_cmp == 0 && node < n)))))
6200 {
6201 if (cur != NULL)
6202 {
6203 gtk_tree_store_move_before(gtkblist->treemodel, cur, &more_z);
6204 *iter = *cur;
6205 return;
6206 }
6207 else
6208 {
6209 gtk_tree_store_insert_before(gtkblist->treemodel, iter,
6210 &groupiter, &more_z);
6211 return;
6212 }
6213 }
6214
6215 g_value_unset(&val);
6216 }
6217 while (gtk_tree_model_iter_next(GTK_TREE_MODEL(gtkblist->treemodel),
6218 &more_z));
6219
6220 if (cur) {
6221 gtk_tree_store_move_before(gtkblist->treemodel, cur, NULL);
6222 *iter = *cur;
6223 return;
6224 } else {
6225 gtk_tree_store_append(gtkblist->treemodel, iter, &groupiter);
6226 return;
6227 }
6228 }
6229
6230 static void sort_method_log(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter)
6231 {
6232 GtkTreeIter more_z;
6233
6234 int log_size = 0, this_log_size = 0;
6235 const char *buddy_name, *this_buddy_name;
6236
6237 if(cur && (gtk_tree_model_iter_n_children(GTK_TREE_MODEL(gtkblist->treemodel), &groupiter) == 1)) {
6238 *iter = *cur;
6239 return;
6240 }
6241
6242 if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
6243 PurpleBlistNode *n;
6244 for (n = node->child; n; n = n->next)
6245 log_size += purple_log_get_total_size(PURPLE_LOG_IM, ((PurpleBuddy*)(n))->name, ((PurpleBuddy*)(n))->account);
6246 buddy_name = purple_contact_get_alias((PurpleContact*)node);
6247 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
6248 /* we don't have a reliable way of getting the log filename
6249 * from the chat info in the blist, yet */
6250 if (cur != NULL) {
6251 *iter = *cur;
6252 return;
6253 }
6254
6255 gtk_tree_store_append(gtkblist->treemodel, iter, &groupiter);
6256 return;
6257 } else {
6258 sort_method_none(node, blist, groupiter, cur, iter);
6259 return;
6260 }
6261
6262
6263 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(gtkblist->treemodel), &more_z, &groupiter)) {
6264 gtk_tree_store_insert(gtkblist->treemodel, iter, &groupiter, 0);
6265 return;
6266 }
6267
6268 do {
6269 GValue val;
6270 PurpleBlistNode *n;
6271 PurpleBlistNode *n2;
6272 int cmp;
6273
6274 val.g_type = 0;
6275 gtk_tree_model_get_value (GTK_TREE_MODEL(gtkblist->treemodel), &more_z, NODE_COLUMN, &val);
6276 n = g_value_get_pointer(&val);
6277 this_log_size = 0;
6278
6279 if(PURPLE_BLIST_NODE_IS_CONTACT(n)) {
6280 for (n2 = n->child; n2; n2 = n2->next)
6281 this_log_size += purple_log_get_total_size(PURPLE_LOG_IM, ((PurpleBuddy*)(n2))->name, ((PurpleBuddy*)(n2))->account);
6282 this_buddy_name = purple_contact_get_alias((PurpleContact*)n);
6283 } else {
6284 this_buddy_name = NULL;
6285 }
6286
6287 cmp = purple_utf8_strcasecmp(buddy_name, this_buddy_name);
6288
6289 if (!PURPLE_BLIST_NODE_IS_CONTACT(n) || log_size > this_log_size ||
6290 ((log_size == this_log_size) &&
6291 (cmp < 0 || (cmp == 0 && node < n)))) {
6292 if (cur != NULL) {
6293 gtk_tree_store_move_before(gtkblist->treemodel, cur, &more_z);
6294 *iter = *cur;
6295 return;
6296 } else {
6297 gtk_tree_store_insert_before(gtkblist->treemodel, iter,
6298 &groupiter, &more_z);
6299 return;
6300 }
6301 }
6302 g_value_unset(&val);
6303 } while (gtk_tree_model_iter_next (GTK_TREE_MODEL(gtkblist->treemodel), &more_z));
6304
6305 if (cur != NULL) {
6306 gtk_tree_store_move_before(gtkblist->treemodel, cur, NULL);
6307 *iter = *cur;
6308 return;
6309 } else {
6310 gtk_tree_store_append(gtkblist->treemodel, iter, &groupiter);
6311 return;
6312 }
6313 }
6314
6315 #endif
6316
6317 static void
6318 plugin_act(GtkObject *obj, PurplePluginAction *pam)
6319 {
6320 if (pam && pam->callback)
6321 pam->callback(pam);
6322 }
6323
6324 static void
6325 build_plugin_actions(GtkWidget *menu, PurplePlugin *plugin,
6326 gpointer context)
6327 {
6328 GtkWidget *menuitem;
6329 PurplePluginAction *action = NULL;
6330 GList *actions, *l;
6331
6332 actions = PURPLE_PLUGIN_ACTIONS(plugin, context);
6333
6334 for (l = actions; l != NULL; l = l->next)
6335 {
6336 if (l->data)
6337 {
6338 action = (PurplePluginAction *) l->data;
6339 action->plugin = plugin;
6340 action->context = context;
6341
6342 menuitem = gtk_menu_item_new_with_label(action->label);
6343 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
6344
6345 g_signal_connect(G_OBJECT(menuitem), "activate",
6346 G_CALLBACK(plugin_act), action);
6347 g_object_set_data_full(G_OBJECT(menuitem), "plugin_action",
6348 action,
6349 (GDestroyNotify)purple_plugin_action_free);
6350 gtk_widget_show(menuitem);
6351 }
6352 else
6353 pidgin_separator(menu);
6354 }
6355
6356 g_list_free(actions);
6357 }
6358
6359 static void
6360 modify_account_cb(GtkWidget *widget, gpointer data)
6361 {
6362 pidgin_account_dialog_show(PIDGIN_MODIFY_ACCOUNT_DIALOG, data);
6363 }
6364
6365 static void
6366 enable_account_cb(GtkCheckMenuItem *widget, gpointer data)
6367 {
6368 PurpleAccount *account = data;
6369 const PurpleSavedStatus *saved_status;
6370
6371 saved_status = purple_savedstatus_get_current();
6372 purple_savedstatus_activate_for_account(saved_status, account);
6373
6374 purple_account_set_enabled(account, PIDGIN_UI, TRUE);
6375 }
6376
6377 static void
6378 disable_account_cb(GtkCheckMenuItem *widget, gpointer data)
6379 {
6380 PurpleAccount *account = data;
6381
6382 purple_account_set_enabled(account, PIDGIN_UI, FALSE);
6383 }
6384
6385 void
6386 pidgin_blist_update_accounts_menu(void)
6387 {
6388 GtkWidget *menuitem = NULL, *submenu = NULL;
6389 GtkAccelGroup *accel_group = NULL;
6390 GList *l = NULL, *accounts = NULL;
6391 gboolean disabled_accounts = FALSE;
6392
6393 if (accountmenu == NULL)
6394 return;
6395
6396 /* Clear the old Accounts menu */
6397 for (l = gtk_container_get_children(GTK_CONTAINER(accountmenu)); l; l = l->next) {
6398 menuitem = l->data;
6399
6400 if (menuitem != gtk_item_factory_get_widget(gtkblist->ift, N_("/Accounts/Add\\/Edit")))
6401 gtk_widget_destroy(menuitem);
6402 }
6403
6404 for (accounts = purple_accounts_get_all(); accounts; accounts = accounts->next) {
6405 char *buf = NULL;
6406 char *accel_path_buf = NULL;
6407 GtkWidget *image = NULL;
6408 PurpleConnection *gc = NULL;
6409 PurpleAccount *account = NULL;
6410 GdkPixbuf *pixbuf = NULL;
6411 PurplePlugin *plugin = NULL;
6412
6413 account = accounts->data;
6414 accel_group = gtk_menu_get_accel_group(GTK_MENU(accountmenu));
6415
6416 if(purple_account_get_enabled(account, PIDGIN_UI)) {
6417 buf = g_strconcat(purple_account_get_username(account), " (",
6418 purple_account_get_protocol_name(account), ")", NULL);
6419 menuitem = gtk_image_menu_item_new_with_label(buf);
6420 accel_path_buf = g_strconcat(N_("<PurpleMain>/Accounts/"), buf, NULL);
6421 g_free(buf);
6422 pixbuf = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL);
6423 if (pixbuf != NULL)
6424 {
6425 if (!purple_account_is_connected(account))
6426 gdk_pixbuf_saturate_and_pixelate(pixbuf, pixbuf,
6427 0.0, FALSE);
6428 image = gtk_image_new_from_pixbuf(pixbuf);
6429 g_object_unref(G_OBJECT(pixbuf));
6430 gtk_widget_show(image);
6431 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image);
6432 }
6433 gtk_menu_shell_append(GTK_MENU_SHELL(accountmenu), menuitem);
6434 gtk_widget_show(menuitem);
6435
6436 submenu = gtk_menu_new();
6437 gtk_menu_set_accel_group(GTK_MENU(submenu), accel_group);
6438 gtk_menu_set_accel_path(GTK_MENU(submenu), accel_path_buf);
6439 g_free(accel_path_buf);
6440 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
6441 gtk_widget_show(submenu);
6442
6443
6444 menuitem = gtk_menu_item_new_with_mnemonic(_("_Edit Account"));
6445 g_signal_connect(G_OBJECT(menuitem), "activate",
6446 G_CALLBACK(modify_account_cb), account);
6447 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), menuitem);
6448 gtk_widget_show(menuitem);
6449
6450 pidgin_separator(submenu);
6451
6452 gc = purple_account_get_connection(account);
6453 plugin = gc && PURPLE_CONNECTION_IS_CONNECTED(gc) ? gc->prpl : NULL;
6454 if (plugin && PURPLE_PLUGIN_HAS_ACTIONS(plugin)) {
6455 build_plugin_actions(submenu, plugin, gc);
6456 } else {
6457 menuitem = gtk_menu_item_new_with_label(_("No actions available"));
6458 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), menuitem);
6459 gtk_widget_set_sensitive(menuitem, FALSE);
6460 gtk_widget_show(menuitem);
6461 }
6462
6463 pidgin_separator(submenu);
6464
6465 menuitem = gtk_menu_item_new_with_mnemonic(_("_Disable"));
6466 g_signal_connect(G_OBJECT(menuitem), "activate",
6467 G_CALLBACK(disable_account_cb), account);
6468 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), menuitem);
6469 gtk_widget_show(menuitem);
6470 } else {
6471 disabled_accounts = TRUE;
6472 }
6473 }
6474
6475 if(disabled_accounts) {
6476 pidgin_separator(accountmenu);
6477 menuitem = gtk_menu_item_new_with_label(_("Enable Account"));
6478 gtk_menu_shell_append(GTK_MENU_SHELL(accountmenu), menuitem);
6479 gtk_widget_show(menuitem);
6480
6481 submenu = gtk_menu_new();
6482 gtk_menu_set_accel_group(GTK_MENU(submenu), accel_group);
6483 gtk_menu_set_accel_path(GTK_MENU(submenu), N_("<PurpleMain>/Accounts/Enable Account"));
6484 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
6485 gtk_widget_show(submenu);
6486
6487 for (accounts = purple_accounts_get_all(); accounts; accounts = accounts->next) {
6488 char *buf = NULL;
6489 GtkWidget *image = NULL;
6490 PurpleAccount *account = NULL;
6491 GdkPixbuf *pixbuf = NULL;
6492
6493 account = accounts->data;
6494
6495 if(!purple_account_get_enabled(account, PIDGIN_UI)) {
6496
6497 disabled_accounts = TRUE;
6498
6499 buf = g_strconcat(purple_account_get_username(account), " (",
6500 purple_account_get_protocol_name(account), ")", NULL);
6501 menuitem = gtk_image_menu_item_new_with_label(buf);
6502 g_free(buf);
6503 pixbuf = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL);
6504 if (pixbuf != NULL)
6505 {
6506 if (!purple_account_is_connected(account))
6507 gdk_pixbuf_saturate_and_pixelate(pixbuf, pixbuf, 0.0, FALSE);
6508 image = gtk_image_new_from_pixbuf(pixbuf);
6509 g_object_unref(G_OBJECT(pixbuf));
6510 gtk_widget_show(image);
6511 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image);
6512 }
6513 g_signal_connect(G_OBJECT(menuitem), "activate",
6514 G_CALLBACK(enable_account_cb), account);
6515 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), menuitem);
6516 gtk_widget_show(menuitem);
6517 }
6518 }
6519 }
6520 }
6521
6522 static GList *plugin_submenus = NULL;
6523
6524 void
6525 pidgin_blist_update_plugin_actions(void)
6526 {
6527 GtkWidget *menuitem, *submenu;
6528 PurplePlugin *plugin = NULL;
6529 GList *l;
6530 GtkAccelGroup *accel_group;
6531
6532 GtkWidget *pluginmenu = gtk_item_factory_get_widget(gtkblist->ift, N_("/Tools"));
6533
6534 g_return_if_fail(pluginmenu != NULL);
6535
6536 /* Remove old plugin action submenus from the Tools menu */
6537 for (l = plugin_submenus; l; l = l->next)
6538 gtk_widget_destroy(GTK_WIDGET(l->data));
6539 g_list_free(plugin_submenus);
6540 plugin_submenus = NULL;
6541
6542 accel_group = gtk_menu_get_accel_group(GTK_MENU(pluginmenu));
6543
6544 /* Add a submenu for each plugin with custom actions */
6545 for (l = purple_plugins_get_loaded(); l; l = l->next) {
6546 char *path;
6547
6548 plugin = (PurplePlugin *) l->data;
6549
6550 if (PURPLE_IS_PROTOCOL_PLUGIN(plugin))
6551 continue;
6552
6553 if (!PURPLE_PLUGIN_HAS_ACTIONS(plugin))
6554 continue;
6555
6556 menuitem = gtk_image_menu_item_new_with_label(_(plugin->info->name));
6557 gtk_menu_shell_append(GTK_MENU_SHELL(pluginmenu), menuitem);
6558 gtk_widget_show(menuitem);
6559
6560 plugin_submenus = g_list_append(plugin_submenus, menuitem);
6561
6562 submenu = gtk_menu_new();
6563 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
6564 gtk_widget_show(submenu);
6565
6566 gtk_menu_set_accel_group(GTK_MENU(submenu), accel_group);
6567 path = g_strdup_printf("%s/Tools/%s", gtkblist->ift->path, plugin->info->name);
6568 gtk_menu_set_accel_path(GTK_MENU(submenu), path);
6569 g_free(path);
6570
6571 build_plugin_actions(submenu, plugin, NULL);
6572 }
6573 }
6574
6575 static void
6576 sortmethod_act(GtkCheckMenuItem *checkmenuitem, char *id)
6577 {
6578 if (gtk_check_menu_item_get_active(checkmenuitem))
6579 {
6580 pidgin_set_cursor(gtkblist->window, GDK_WATCH);
6581 /* This is redundant. I think. */
6582 /* pidgin_blist_sort_method_set(id); */
6583 purple_prefs_set_string(PIDGIN_PREFS_ROOT "/blist/sort_type", id);
6584
6585 pidgin_clear_cursor(gtkblist->window);
6586 }
6587 }
6588
6589 void
6590 pidgin_blist_update_sort_methods(void)
6591 {
6592 GtkWidget *menuitem = NULL, *activeitem = NULL;
6593 PidginBlistSortMethod *method = NULL;
6594 GList *l;
6595 GSList *sl = NULL;
6596 GtkWidget *sortmenu;
6597 const char *m = purple_prefs_get_string(PIDGIN_PREFS_ROOT "/blist/sort_type");
6598
6599 if ((gtkblist == NULL) || (gtkblist->ift == NULL))
6600 return;
6601
6602 sortmenu = gtk_item_factory_get_widget(gtkblist->ift, N_("/Buddies/Sort Buddies"));
6603
6604 if (sortmenu == NULL)
6605 return;
6606
6607 /* Clear the old menu */
6608 for (l = gtk_container_get_children(GTK_CONTAINER(sortmenu)); l; l = l->next) {
6609 menuitem = l->data;
6610 gtk_widget_destroy(GTK_WIDGET(menuitem));
6611 }
6612
6613 for (l = pidgin_blist_sort_methods; l; l = l->next) {
6614 method = (PidginBlistSortMethod *) l->data;
6615 menuitem = gtk_radio_menu_item_new_with_label(sl, _(method->name));
6616 if (!strcmp(m, method->id))
6617 activeitem = menuitem;
6618 sl = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(menuitem));
6619 gtk_menu_shell_append(GTK_MENU_SHELL(sortmenu), menuitem);
6620 g_signal_connect(G_OBJECT(menuitem), "toggled",
6621 G_CALLBACK(sortmethod_act), method->id);
6622 gtk_widget_show(menuitem);
6623 }
6624 if (activeitem)
6625 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(activeitem), TRUE);
6626 }

mercurial