comparison finch/gntblist.c @ 15817:0e3a8505ebbe

renamed gaim-text to finch
author Sean Egan <seanegan@gmail.com>
date Sun, 18 Mar 2007 19:38:15 +0000
parents
children 32c366eeeb99
comparison
equal deleted inserted replaced
15816:317e7613e581 15817:0e3a8505ebbe
1 /**
2 * @file gntblist.c GNT BuddyList API
3 * @ingroup gntui
4 *
5 * gaim
6 *
7 * Gaim 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 #include <account.h>
26 #include <blist.h>
27 #include <notify.h>
28 #include <request.h>
29 #include <savedstatuses.h>
30 #include <server.h>
31 #include <signal.h>
32 #include <status.h>
33 #include <util.h>
34 #include "debug.h"
35
36 #include "gntgaim.h"
37 #include "gntbox.h"
38 #include "gntcombobox.h"
39 #include "gntentry.h"
40 #include "gntft.h"
41 #include "gntlabel.h"
42 #include "gntline.h"
43 #include "gntmenu.h"
44 #include "gntmenuitem.h"
45 #include "gntmenuitemcheck.h"
46 #include "gntpounce.h"
47 #include "gnttree.h"
48 #include "gntutils.h"
49 #include "gntwindow.h"
50
51 #include "gntblist.h"
52 #include "gntconv.h"
53 #include "gntstatus.h"
54 #include <string.h>
55
56 #define PREF_ROOT "/gaim/gnt/blist"
57 #define TYPING_TIMEOUT 4000
58
59 typedef struct
60 {
61 GntWidget *window;
62 GntWidget *tree;
63
64 GntWidget *tooltip;
65 GaimBlistNode *tnode; /* Who is the tooltip being displayed for? */
66 GList *tagged; /* A list of tagged blistnodes */
67
68 GntWidget *context;
69 GaimBlistNode *cnode;
70
71 /* XXX: I am KISSing */
72 GntWidget *status; /* Dropdown with the statuses */
73 GntWidget *statustext; /* Status message */
74 int typing;
75
76 GntWidget *menu;
77 /* These are the menuitems that get regenerated */
78 GntMenuItem *accounts;
79 GntMenuItem *plugins;
80 } FinchBlist;
81
82 typedef enum
83 {
84 STATUS_PRIMITIVE = 0,
85 STATUS_SAVED_POPULAR,
86 STATUS_SAVED_ALL,
87 STATUS_SAVED_NEW
88 } StatusType;
89
90 typedef struct
91 {
92 StatusType type;
93 union
94 {
95 GaimStatusPrimitive prim;
96 GaimSavedStatus *saved;
97 } u;
98 } StatusBoxItem;
99
100 FinchBlist *ggblist;
101
102 static void add_buddy(GaimBuddy *buddy, FinchBlist *ggblist);
103 static void add_contact(GaimContact *contact, FinchBlist *ggblist);
104 static void add_group(GaimGroup *group, FinchBlist *ggblist);
105 static void add_chat(GaimChat *chat, FinchBlist *ggblist);
106 static void add_node(GaimBlistNode *node, FinchBlist *ggblist);
107 static void draw_tooltip(FinchBlist *ggblist);
108 static gboolean remove_typing_cb(gpointer null);
109 static void remove_peripherals(FinchBlist *ggblist);
110 static const char * get_display_name(GaimBlistNode *node);
111 static void savedstatus_changed(GaimSavedStatus *now, GaimSavedStatus *old);
112 static void blist_show(GaimBuddyList *list);
113 static void update_buddy_display(GaimBuddy *buddy, FinchBlist *ggblist);
114 static void account_signed_on_cb(void);
115
116 /* Sort functions */
117 static int blist_node_compare_text(GaimBlistNode *n1, GaimBlistNode *n2);
118 static int blist_node_compare_status(GaimBlistNode *n1, GaimBlistNode *n2);
119 static int blist_node_compare_log(GaimBlistNode *n1, GaimBlistNode *n2);
120
121 static gboolean
122 is_contact_online(GaimContact *contact)
123 {
124 GaimBlistNode *node;
125 for (node = ((GaimBlistNode*)contact)->child; node; node = node->next) {
126 if (GAIM_BUDDY_IS_ONLINE((GaimBuddy*)node))
127 return TRUE;
128 }
129 return FALSE;
130 }
131
132 static gboolean
133 is_group_online(GaimGroup *group)
134 {
135 GaimBlistNode *node;
136 for (node = ((GaimBlistNode*)group)->child; node; node = node->next) {
137 if (GAIM_BLIST_NODE_IS_CHAT(node))
138 return TRUE;
139 else if (is_contact_online((GaimContact*)node))
140 return TRUE;
141 }
142 return FALSE;
143 }
144
145 static void
146 new_node(GaimBlistNode *node)
147 {
148 }
149
150 static void add_node(GaimBlistNode *node, FinchBlist *ggblist)
151 {
152 if (GAIM_BLIST_NODE_IS_BUDDY(node))
153 add_buddy((GaimBuddy*)node, ggblist);
154 else if (GAIM_BLIST_NODE_IS_CONTACT(node))
155 add_contact((GaimContact*)node, ggblist);
156 else if (GAIM_BLIST_NODE_IS_GROUP(node))
157 add_group((GaimGroup*)node, ggblist);
158 else if (GAIM_BLIST_NODE_IS_CHAT(node))
159 add_chat((GaimChat *)node, ggblist);
160 draw_tooltip(ggblist);
161 }
162
163 static void
164 remove_tooltip(FinchBlist *ggblist)
165 {
166 gnt_widget_destroy(ggblist->tooltip);
167 ggblist->tooltip = NULL;
168 ggblist->tnode = NULL;
169 }
170
171 static void
172 node_remove(GaimBuddyList *list, GaimBlistNode *node)
173 {
174 FinchBlist *ggblist = list->ui_data;
175
176 if (ggblist == NULL || node->ui_data == NULL)
177 return;
178
179 gnt_tree_remove(GNT_TREE(ggblist->tree), node);
180 node->ui_data = NULL;
181 if (ggblist->tagged)
182 ggblist->tagged = g_list_remove(ggblist->tagged, node);
183
184 if (GAIM_BLIST_NODE_IS_BUDDY(node)) {
185 GaimContact *contact = (GaimContact*)node->parent;
186 if ((!gaim_prefs_get_bool(PREF_ROOT "/showoffline") && !is_contact_online(contact)) ||
187 contact->currentsize < 1)
188 node_remove(list, (GaimBlistNode*)contact);
189 } else if (GAIM_BLIST_NODE_IS_CONTACT(node)) {
190 GaimGroup *group = (GaimGroup*)node->parent;
191 if ((!gaim_prefs_get_bool(PREF_ROOT "/showoffline") && !is_group_online(group)) ||
192 group->currentsize < 1)
193 node_remove(list, node->parent);
194 for (node = node->child; node; node = node->next)
195 node->ui_data = NULL;
196 }
197
198 draw_tooltip(ggblist);
199 }
200
201 static void
202 node_update(GaimBuddyList *list, GaimBlistNode *node)
203 {
204 /* It really looks like this should never happen ... but it does.
205 This will at least emit a warning to the log when it
206 happens, so maybe someone will figure it out. */
207 g_return_if_fail(node != NULL);
208
209 if (list->ui_data == NULL)
210 return; /* XXX: this is probably the place to auto-join chats */
211
212 if (node->ui_data != NULL) {
213 gnt_tree_change_text(GNT_TREE(ggblist->tree), node,
214 0, get_display_name(node));
215 gnt_tree_sort_row(GNT_TREE(ggblist->tree), node);
216 }
217
218 if (GAIM_BLIST_NODE_IS_BUDDY(node)) {
219 GaimBuddy *buddy = (GaimBuddy*)node;
220 if (gaim_account_is_connected(buddy->account) &&
221 (GAIM_BUDDY_IS_ONLINE(buddy) || gaim_prefs_get_bool(PREF_ROOT "/showoffline")))
222 add_node((GaimBlistNode*)buddy, list->ui_data);
223 else
224 node_remove(gaim_get_blist(), node);
225
226 node_update(list, node->parent);
227 } else if (GAIM_BLIST_NODE_IS_CHAT(node)) {
228 add_chat((GaimChat *)node, list->ui_data);
229 } else if (GAIM_BLIST_NODE_IS_CONTACT(node)) {
230 GaimContact *contact = (GaimContact*)node;
231 if ((!gaim_prefs_get_bool(PREF_ROOT "/showoffline") && !is_contact_online(contact)) ||
232 contact->currentsize < 1)
233 node_remove(gaim_get_blist(), node);
234 else
235 add_node(node, list->ui_data);
236 } else if (GAIM_BLIST_NODE_IS_GROUP(node)) {
237 GaimGroup *group = (GaimGroup*)node;
238 if ((!gaim_prefs_get_bool(PREF_ROOT "/showoffline") && !is_group_online(group)) ||
239 group->currentsize < 1)
240 node_remove(list, node);
241 }
242 }
243
244 static void
245 new_list(GaimBuddyList *list)
246 {
247 if (ggblist)
248 return;
249
250 ggblist = g_new0(FinchBlist, 1);
251 list->ui_data = ggblist;
252 }
253
254 static void
255 add_buddy_cb(void *data, GaimRequestFields *allfields)
256 {
257 const char *username = gaim_request_fields_get_string(allfields, "screenname");
258 const char *alias = gaim_request_fields_get_string(allfields, "alias");
259 const char *group = gaim_request_fields_get_string(allfields, "group");
260 GaimAccount *account = gaim_request_fields_get_account(allfields, "account");
261 const char *error = NULL;
262 GaimGroup *grp;
263 GaimBuddy *buddy;
264
265 if (!username)
266 error = _("You must provide a screename for the buddy.");
267 else if (!group)
268 error = _("You must provide a group.");
269 else if (!account)
270 error = _("You must select an account.");
271
272 if (error)
273 {
274 gaim_notify_error(NULL, _("Error"), _("Error adding buddy"), error);
275 return;
276 }
277
278 grp = gaim_find_group(group);
279 if (!grp)
280 {
281 grp = gaim_group_new(group);
282 gaim_blist_add_group(grp, NULL);
283 }
284
285 buddy = gaim_buddy_new(account, username, alias);
286 gaim_blist_add_buddy(buddy, NULL, grp, NULL);
287 gaim_account_add_buddy(account, buddy);
288 }
289
290 static void
291 finch_request_add_buddy(GaimAccount *account, const char *username, const char *grp, const char *alias)
292 {
293 GaimRequestFields *fields = gaim_request_fields_new();
294 GaimRequestFieldGroup *group = gaim_request_field_group_new(NULL);
295 GaimRequestField *field;
296
297 gaim_request_fields_add_group(fields, group);
298
299 field = gaim_request_field_string_new("screenname", _("Screen Name"), username, FALSE);
300 gaim_request_field_group_add_field(group, field);
301
302 field = gaim_request_field_string_new("alias", _("Alias"), alias, FALSE);
303 gaim_request_field_group_add_field(group, field);
304
305 field = gaim_request_field_string_new("group", _("Group"), grp, FALSE);
306 gaim_request_field_group_add_field(group, field);
307
308 field = gaim_request_field_account_new("account", _("Account"), NULL);
309 gaim_request_field_account_set_show_all(field, FALSE);
310 if (account)
311 gaim_request_field_account_set_value(field, account);
312 gaim_request_field_group_add_field(group, field);
313
314 gaim_request_fields(NULL, _("Add Buddy"), NULL, _("Please enter buddy information."),
315 fields, _("Add"), G_CALLBACK(add_buddy_cb), _("Cancel"), NULL, NULL);
316 }
317
318 static void
319 add_chat_cb(void *data, GaimRequestFields *allfields)
320 {
321 GaimAccount *account;
322 const char *alias, *name, *group;
323 GaimChat *chat;
324 GaimGroup *grp;
325 GHashTable *hash = NULL;
326 GaimConnection *gc;
327
328 account = gaim_request_fields_get_account(allfields, "account");
329 name = gaim_request_fields_get_string(allfields, "name");
330 alias = gaim_request_fields_get_string(allfields, "alias");
331 group = gaim_request_fields_get_string(allfields, "group");
332
333 if (!gaim_account_is_connected(account) || !name || !*name)
334 return;
335
336 if (!group || !*group)
337 group = _("Chats");
338
339 gc = gaim_account_get_connection(account);
340
341 if (GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info_defaults != NULL)
342 hash = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info_defaults(gc, name);
343
344 chat = gaim_chat_new(account, name, hash);
345
346 if (chat != NULL) {
347 if ((grp = gaim_find_group(group)) == NULL) {
348 grp = gaim_group_new(group);
349 gaim_blist_add_group(grp, NULL);
350 }
351 gaim_blist_add_chat(chat, grp, NULL);
352 gaim_blist_alias_chat(chat, alias);
353 }
354 }
355
356 static void
357 finch_request_add_chat(GaimAccount *account, GaimGroup *grp, const char *alias, const char *name)
358 {
359 GaimRequestFields *fields = gaim_request_fields_new();
360 GaimRequestFieldGroup *group = gaim_request_field_group_new(NULL);
361 GaimRequestField *field;
362
363 gaim_request_fields_add_group(fields, group);
364
365 field = gaim_request_field_account_new("account", _("Account"), NULL);
366 gaim_request_field_account_set_show_all(field, FALSE);
367 if (account)
368 gaim_request_field_account_set_value(field, account);
369 gaim_request_field_group_add_field(group, field);
370
371 field = gaim_request_field_string_new("name", _("Name"), name, FALSE);
372 gaim_request_field_group_add_field(group, field);
373
374 field = gaim_request_field_string_new("alias", _("Alias"), alias, FALSE);
375 gaim_request_field_group_add_field(group, field);
376
377 field = gaim_request_field_string_new("group", _("Group"), grp ? grp->name : NULL, FALSE);
378 gaim_request_field_group_add_field(group, field);
379
380 gaim_request_fields(NULL, _("Add Chat"), NULL,
381 _("You can edit more information from the context menu later."),
382 fields, _("Add"), G_CALLBACK(add_chat_cb), _("Cancel"), NULL, NULL);
383 }
384
385 static void
386 add_group_cb(gpointer null, const char *group)
387 {
388 GaimGroup *grp;
389
390 if (!group || !*group)
391 {
392 gaim_notify_error(NULL, _("Error"), _("Error adding group"),
393 _("You must give a name for the group to add."));
394 return;
395 }
396
397 grp = gaim_find_group(group);
398 if (!grp)
399 {
400 grp = gaim_group_new(group);
401 gaim_blist_add_group(grp, NULL);
402 }
403 else
404 {
405 gaim_notify_error(NULL, _("Error"), _("Error adding group"),
406 _("A group with the name already exists."));
407 }
408 }
409
410 static void
411 finch_request_add_group()
412 {
413 gaim_request_input(NULL, _("Add Group"), NULL, _("Enter the name of the group"),
414 NULL, FALSE, FALSE, NULL,
415 _("Add"), G_CALLBACK(add_group_cb), _("Cancel"), NULL, NULL);
416 }
417
418 static GaimBlistUiOps blist_ui_ops =
419 {
420 new_list,
421 new_node,
422 blist_show,
423 node_update,
424 node_remove,
425 NULL,
426 NULL,
427 .request_add_buddy = finch_request_add_buddy,
428 .request_add_chat = finch_request_add_chat,
429 .request_add_group = finch_request_add_group
430 };
431
432 static gpointer
433 finch_blist_get_handle()
434 {
435 static int handle;
436
437 return &handle;
438 }
439
440 static void
441 add_group(GaimGroup *group, FinchBlist *ggblist)
442 {
443 GaimBlistNode *node = (GaimBlistNode *)group;
444 if (node->ui_data)
445 return;
446 node->ui_data = gnt_tree_add_row_after(GNT_TREE(ggblist->tree), group,
447 gnt_tree_create_row(GNT_TREE(ggblist->tree), get_display_name(node)), NULL, NULL);
448 }
449
450 static const char *
451 get_display_name(GaimBlistNode *node)
452 {
453 static char text[2096];
454 char status[8] = " ";
455 const char *name = NULL;
456
457 if (GAIM_BLIST_NODE_IS_CONTACT(node))
458 node = (GaimBlistNode*)gaim_contact_get_priority_buddy((GaimContact*)node); /* XXX: this can return NULL?! */
459
460 if (node == NULL)
461 return NULL;
462
463 if (GAIM_BLIST_NODE_IS_BUDDY(node))
464 {
465 GaimBuddy *buddy = (GaimBuddy *)node;
466 GaimStatusPrimitive prim;
467 GaimPresence *presence;
468 GaimStatus *now;
469 gboolean ascii = gnt_ascii_only();
470
471 presence = gaim_buddy_get_presence(buddy);
472 now = gaim_presence_get_active_status(presence);
473
474 prim = gaim_status_type_get_primitive(gaim_status_get_type(now));
475
476 switch(prim)
477 {
478 case GAIM_STATUS_OFFLINE:
479 strncpy(status, ascii ? "x" : "⊗", sizeof(status) - 1);
480 break;
481 case GAIM_STATUS_AVAILABLE:
482 strncpy(status, ascii ? "o" : "â—¯", sizeof(status) - 1);
483 break;
484 default:
485 strncpy(status, ascii ? "." : "⊖", sizeof(status) - 1);
486 break;
487 }
488 name = gaim_buddy_get_alias(buddy);
489 }
490 else if (GAIM_BLIST_NODE_IS_CHAT(node))
491 {
492 GaimChat *chat = (GaimChat*)node;
493 name = gaim_chat_get_name(chat);
494
495 strncpy(status, "~", sizeof(status) - 1);
496 }
497 else if (GAIM_BLIST_NODE_IS_GROUP(node))
498 return ((GaimGroup*)node)->name;
499
500 snprintf(text, sizeof(text) - 1, "%s %s", status, name);
501
502 return text;
503 }
504
505 static void
506 add_chat(GaimChat *chat, FinchBlist *ggblist)
507 {
508 GaimGroup *group;
509 GaimBlistNode *node = (GaimBlistNode *)chat;
510 if (node->ui_data)
511 return;
512 if (!gaim_account_is_connected(chat->account))
513 return;
514
515 group = gaim_chat_get_group(chat);
516 add_node((GaimBlistNode*)group, ggblist);
517
518 node->ui_data = gnt_tree_add_row_after(GNT_TREE(ggblist->tree), chat,
519 gnt_tree_create_row(GNT_TREE(ggblist->tree), get_display_name(node)),
520 group, NULL);
521 }
522
523 static void
524 add_contact(GaimContact *contact, FinchBlist *ggblist)
525 {
526 GaimGroup *group;
527 GaimBlistNode *node = (GaimBlistNode*)contact;
528 const char *name;
529
530 if (node->ui_data)
531 return;
532
533 name = get_display_name(node);
534 if (name == NULL)
535 return;
536
537 group = (GaimGroup*)node->parent;
538 add_node((GaimBlistNode*)group, ggblist);
539
540 node->ui_data = gnt_tree_add_row_after(GNT_TREE(ggblist->tree), contact,
541 gnt_tree_create_row(GNT_TREE(ggblist->tree), name),
542 group, NULL);
543
544 gnt_tree_set_expanded(GNT_TREE(ggblist->tree), contact, FALSE);
545 }
546
547 static void
548 add_buddy(GaimBuddy *buddy, FinchBlist *ggblist)
549 {
550 GaimContact *contact;
551 GaimBlistNode *node = (GaimBlistNode *)buddy;
552 if (node->ui_data)
553 return;
554
555 contact = (GaimContact*)node->parent;
556 if (!contact) /* When a new buddy is added and show-offline is set */
557 return;
558 add_node((GaimBlistNode*)contact, ggblist);
559
560 node->ui_data = gnt_tree_add_row_after(GNT_TREE(ggblist->tree), buddy,
561 gnt_tree_create_row(GNT_TREE(ggblist->tree), get_display_name(node)),
562 contact, NULL);
563 if (gaim_presence_is_idle(gaim_buddy_get_presence(buddy))) {
564 gnt_tree_set_row_flags(GNT_TREE(ggblist->tree), buddy, GNT_TEXT_FLAG_DIM);
565 gnt_tree_set_row_flags(GNT_TREE(ggblist->tree), contact, GNT_TEXT_FLAG_DIM);
566 } else {
567 gnt_tree_set_row_flags(GNT_TREE(ggblist->tree), buddy, 0);
568 gnt_tree_set_row_flags(GNT_TREE(ggblist->tree), contact, 0);
569 }
570 }
571
572 #if 0
573 static void
574 buddy_signed_on(GaimBuddy *buddy, FinchBlist *ggblist)
575 {
576 add_node((GaimBlistNode*)buddy, ggblist);
577 }
578
579 static void
580 buddy_signed_off(GaimBuddy *buddy, FinchBlist *ggblist)
581 {
582 node_remove(gaim_get_blist(), (GaimBlistNode*)buddy);
583 }
584 #endif
585
586 GaimBlistUiOps *finch_blist_get_ui_ops()
587 {
588 return &blist_ui_ops;
589 }
590
591 static void
592 selection_activate(GntWidget *widget, FinchBlist *ggblist)
593 {
594 GntTree *tree = GNT_TREE(ggblist->tree);
595 GaimBlistNode *node = gnt_tree_get_selection_data(tree);
596
597 if (!node)
598 return;
599
600 if (GAIM_BLIST_NODE_IS_CONTACT(node))
601 node = (GaimBlistNode*)gaim_contact_get_priority_buddy((GaimContact*)node);
602
603 if (GAIM_BLIST_NODE_IS_BUDDY(node))
604 {
605 GaimBuddy *buddy = (GaimBuddy *)node;
606 GaimConversation *conv = gaim_conversation_new(GAIM_CONV_TYPE_IM,
607 gaim_buddy_get_account(buddy),
608 gaim_buddy_get_name(buddy));
609 finch_conversation_set_active(conv);
610 }
611 else if (GAIM_BLIST_NODE_IS_CHAT(node))
612 {
613 GaimChat *chat = (GaimChat*)node;
614 serv_join_chat(chat->account->gc, chat->components);
615 }
616 }
617
618 static void
619 context_menu_callback(GntMenuItem *item, gpointer data)
620 {
621 GaimMenuAction *action = data;
622 GaimBlistNode *node = ggblist->cnode;
623 if (action) {
624 void (*callback)(GaimBlistNode *, gpointer);
625 callback = (void (*)(GaimBlistNode *, gpointer))action->callback;
626 if (callback)
627 callback(action->data, node);
628 else
629 return;
630 }
631 }
632
633 static void
634 gnt_append_menu_action(GntMenu *menu, GaimMenuAction *action, gpointer parent)
635 {
636 GList *list;
637 GntMenuItem *item;
638
639 if (action == NULL)
640 return;
641
642 item = gnt_menuitem_new(action->label);
643 if (action->callback)
644 gnt_menuitem_set_callback(GNT_MENUITEM(item), context_menu_callback, action);
645 gnt_menu_add_item(menu, GNT_MENUITEM(item));
646
647 if (action->children) {
648 GntWidget *sub = gnt_menu_new(GNT_MENU_POPUP);
649 gnt_menuitem_set_submenu(item, GNT_MENU(sub));
650 for (list = action->children; list; list = list->next)
651 gnt_append_menu_action(GNT_MENU(sub), list->data, action);
652 }
653 }
654
655 static void
656 append_proto_menu(GntMenu *menu, GaimConnection *gc, GaimBlistNode *node)
657 {
658 GList *list;
659 GaimPluginProtocolInfo *prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl);
660
661 if(!prpl_info || !prpl_info->blist_node_menu)
662 return;
663
664 for(list = prpl_info->blist_node_menu(node); list;
665 list = g_list_delete_link(list, list))
666 {
667 GaimMenuAction *act = (GaimMenuAction *) list->data;
668 act->data = node;
669 gnt_append_menu_action(menu, act, NULL);
670 }
671 }
672
673 static void
674 add_custom_action(GntMenu *menu, const char *label, GaimCallback callback,
675 gpointer data)
676 {
677 GaimMenuAction *action = gaim_menu_action_new(label, callback, data, NULL);
678 gnt_append_menu_action(menu, action, NULL);
679 g_signal_connect_swapped(G_OBJECT(menu), "destroy",
680 G_CALLBACK(gaim_menu_action_free), action);
681 }
682
683 static void
684 chat_components_edit_ok(GaimChat *chat, GaimRequestFields *allfields)
685 {
686 GList *groups, *fields;
687
688 for (groups = gaim_request_fields_get_groups(allfields); groups; groups = groups->next) {
689 fields = gaim_request_field_group_get_fields(groups->data);
690 for (; fields; fields = fields->next) {
691 GaimRequestField *field = fields->data;
692 const char *id;
693 char *val;
694
695 id = gaim_request_field_get_id(field);
696 if (gaim_request_field_get_type(field) == GAIM_REQUEST_FIELD_INTEGER)
697 val = g_strdup_printf("%d", gaim_request_field_int_get_value(field));
698 else
699 val = g_strdup(gaim_request_field_string_get_value(field));
700
701 g_hash_table_replace(chat->components, g_strdup(id), val); /* val should not be free'd */
702 }
703 }
704 }
705
706 static void
707 chat_components_edit(GaimChat *chat, GaimBlistNode *selected)
708 {
709 GaimRequestFields *fields = gaim_request_fields_new();
710 GaimRequestFieldGroup *group = gaim_request_field_group_new(NULL);
711 GaimRequestField *field;
712 GList *parts, *iter;
713 struct proto_chat_entry *pce;
714
715 gaim_request_fields_add_group(fields, group);
716
717 parts = GAIM_PLUGIN_PROTOCOL_INFO(chat->account->gc->prpl)->chat_info(chat->account->gc);
718
719 for (iter = parts; iter; iter = iter->next) {
720 pce = iter->data;
721 if (pce->is_int) {
722 int val;
723 const char *str = g_hash_table_lookup(chat->components, pce->identifier);
724 if (!str || sscanf(str, "%d", &val) != 1)
725 val = pce->min;
726 field = gaim_request_field_int_new(pce->identifier, pce->label, val);
727 } else {
728 field = gaim_request_field_string_new(pce->identifier, pce->label,
729 g_hash_table_lookup(chat->components, pce->identifier), FALSE);
730 }
731
732 gaim_request_field_group_add_field(group, field);
733 g_free(pce);
734 }
735
736 g_list_free(parts);
737
738 gaim_request_fields(NULL, _("Edit Chat"), NULL, _("Please Update the necessary fields."),
739 fields, _("Edit"), G_CALLBACK(chat_components_edit_ok), _("Cancel"), NULL, chat);
740 }
741
742 static void
743 autojoin_toggled(GntMenuItem *item, gpointer data)
744 {
745 GaimMenuAction *action = data;
746 gaim_blist_node_set_bool(action->data, "gnt-autojoin",
747 gnt_menuitem_check_get_checked(GNT_MENUITEM_CHECK(item)));
748 }
749
750 static void
751 create_chat_menu(GntMenu *menu, GaimChat *chat)
752 {
753 GaimMenuAction *action = gaim_menu_action_new(_("Auto-join"), NULL, chat, NULL);
754 GntMenuItem *check = gnt_menuitem_check_new(action->label);
755 gnt_menuitem_check_set_checked(GNT_MENUITEM_CHECK(check),
756 gaim_blist_node_get_bool((GaimBlistNode*)chat, "gnt-autojoin"));
757 gnt_menu_add_item(menu, check);
758 gnt_menuitem_set_callback(check, autojoin_toggled, action);
759 g_signal_connect_swapped(G_OBJECT(menu), "destroy",
760 G_CALLBACK(gaim_menu_action_free), action);
761
762 add_custom_action(menu, _("Edit Settings"), (GaimCallback)chat_components_edit, chat);
763 }
764
765 static void
766 finch_add_buddy(GaimGroup *grp, GaimBlistNode *selected)
767 {
768 gaim_blist_request_add_buddy(NULL, NULL, grp ? grp->name : NULL, NULL);
769 }
770
771 static void
772 finch_add_group(GaimGroup *grp, GaimBlistNode *selected)
773 {
774 gaim_blist_request_add_group();
775 }
776
777 static void
778 finch_add_chat(GaimGroup *grp, GaimBlistNode *selected)
779 {
780 gaim_blist_request_add_chat(NULL, grp, NULL, NULL);
781 }
782
783 static void
784 create_group_menu(GntMenu *menu, GaimGroup *group)
785 {
786 add_custom_action(menu, _("Add Buddy"),
787 GAIM_CALLBACK(finch_add_buddy), group);
788 add_custom_action(menu, _("Add Chat"),
789 GAIM_CALLBACK(finch_add_chat), group);
790 add_custom_action(menu, _("Add Group"),
791 GAIM_CALLBACK(finch_add_group), group);
792 }
793
794 static void
795 finch_blist_get_buddy_info_cb(GaimBuddy *buddy, GaimBlistNode *selected)
796 {
797 serv_get_info(buddy->account->gc, gaim_buddy_get_name(buddy));
798 }
799
800 static void
801 finch_blist_menu_send_file_cb(GaimBuddy *buddy, GaimBlistNode *selected)
802 {
803 serv_send_file(buddy->account->gc, buddy->name, NULL);
804 }
805
806 static void
807 finch_blist_pounce_node_cb(GaimBlistNode *node, GaimBlistNode *selected)
808 {
809 GaimBuddy *b;
810 if (GAIM_BLIST_NODE_IS_CONTACT(node))
811 b = gaim_contact_get_priority_buddy((GaimContact *)node);
812 else
813 b = (GaimBuddy *)node;
814 finch_pounce_editor_show(b->account, b->name, NULL);
815 }
816
817
818 static void
819 create_buddy_menu(GntMenu *menu, GaimBuddy *buddy)
820 {
821 GaimPluginProtocolInfo *prpl_info;
822
823 prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(buddy->account->gc->prpl);
824 if (prpl_info && prpl_info->get_info)
825 {
826 add_custom_action(menu, _("Get Info"),
827 GAIM_CALLBACK(finch_blist_get_buddy_info_cb), buddy);
828 }
829
830 add_custom_action(menu, _("Add Buddy Pounce"),
831 GAIM_CALLBACK(finch_blist_pounce_node_cb), buddy);
832
833 if (prpl_info && prpl_info->send_file)
834 {
835 if (!prpl_info->can_receive_file ||
836 prpl_info->can_receive_file(buddy->account->gc, buddy->name))
837 add_custom_action(menu, _("Send File"),
838 GAIM_CALLBACK(finch_blist_menu_send_file_cb), buddy);
839 }
840 #if 0
841 add_custom_action(tree, _("View Log"),
842 GAIM_CALLBACK(finch_blist_view_log_cb)), buddy);
843 #endif
844
845 /* Protocol actions */
846 append_proto_menu(menu,
847 gaim_account_get_connection(gaim_buddy_get_account(buddy)),
848 (GaimBlistNode*)buddy);
849 }
850
851 static void
852 append_extended_menu(GntMenu *menu, GaimBlistNode *node)
853 {
854 GList *iter;
855
856 for (iter = gaim_blist_node_get_extended_menu(node);
857 iter; iter = g_list_delete_link(iter, iter))
858 {
859 gnt_append_menu_action(menu, iter->data, NULL);
860 }
861 }
862
863 /* Xerox'd from gtkdialogs.c:gaim_gtkdialogs_remove_contact_cb */
864 static void
865 remove_contact(GaimContact *contact)
866 {
867 GaimBlistNode *bnode, *cnode;
868 GaimGroup *group;
869
870 cnode = (GaimBlistNode *)contact;
871 group = (GaimGroup*)cnode->parent;
872 for (bnode = cnode->child; bnode; bnode = bnode->next) {
873 GaimBuddy *buddy = (GaimBuddy*)bnode;
874 if (gaim_account_is_connected(buddy->account))
875 gaim_account_remove_buddy(buddy->account, buddy, group);
876 }
877 gaim_blist_remove_contact(contact);
878 }
879
880 static void
881 rename_blist_node(GaimBlistNode *node, const char *newname)
882 {
883 const char *name = newname;
884 if (name && !*name)
885 name = NULL;
886
887 if (GAIM_BLIST_NODE_IS_CONTACT(node)) {
888 GaimContact *contact = (GaimContact*)node;
889 GaimBuddy *buddy = gaim_contact_get_priority_buddy(contact);
890 gaim_blist_alias_contact(contact, name);
891 gaim_blist_alias_buddy(buddy, name);
892 serv_alias_buddy(buddy);
893 } else if (GAIM_BLIST_NODE_IS_BUDDY(node)) {
894 gaim_blist_alias_buddy((GaimBuddy*)node, name);
895 serv_alias_buddy((GaimBuddy*)node);
896 } else if (GAIM_BLIST_NODE_IS_CHAT(node))
897 gaim_blist_alias_chat((GaimChat*)node, name);
898 else if (GAIM_BLIST_NODE_IS_GROUP(node) && (name != NULL))
899 gaim_blist_rename_group((GaimGroup*)node, name);
900 else
901 g_return_if_reached();
902 }
903
904 static void
905 finch_blist_rename_node_cb(GaimBlistNode *node, GaimBlistNode *selected)
906 {
907 const char *name = NULL;
908 char *prompt;
909
910 if (GAIM_BLIST_NODE_IS_CONTACT(node))
911 name = gaim_contact_get_alias((GaimContact*)node);
912 else if (GAIM_BLIST_NODE_IS_BUDDY(node))
913 name = gaim_buddy_get_contact_alias((GaimBuddy*)node);
914 else if (GAIM_BLIST_NODE_IS_CHAT(node))
915 name = gaim_chat_get_name((GaimChat*)node);
916 else if (GAIM_BLIST_NODE_IS_GROUP(node))
917 name = ((GaimGroup*)node)->name;
918 else
919 g_return_if_reached();
920
921 prompt = g_strdup_printf(_("Please enter the new name for %s"), name);
922
923 gaim_request_input(node, _("Rename"), prompt, _("Enter empty string to reset the name."),
924 name, FALSE, FALSE, NULL, _("Rename"), G_CALLBACK(rename_blist_node),
925 _("Cancel"), NULL, node);
926
927 g_free(prompt);
928 }
929
930 /* Xeroxed from gtkdialogs.c:gaim_gtkdialogs_remove_group_cb*/
931 static void
932 remove_group(GaimGroup *group)
933 {
934 GaimBlistNode *cnode, *bnode;
935
936 cnode = ((GaimBlistNode*)group)->child;
937
938 while (cnode) {
939 if (GAIM_BLIST_NODE_IS_CONTACT(cnode)) {
940 bnode = cnode->child;
941 cnode = cnode->next;
942 while (bnode) {
943 GaimBuddy *buddy;
944 if (GAIM_BLIST_NODE_IS_BUDDY(bnode)) {
945 buddy = (GaimBuddy*)bnode;
946 bnode = bnode->next;
947 if (gaim_account_is_connected(buddy->account)) {
948 gaim_account_remove_buddy(buddy->account, buddy, group);
949 gaim_blist_remove_buddy(buddy);
950 }
951 } else {
952 bnode = bnode->next;
953 }
954 }
955 } else if (GAIM_BLIST_NODE_IS_CHAT(cnode)) {
956 GaimChat *chat = (GaimChat *)cnode;
957 cnode = cnode->next;
958 if (gaim_account_is_connected(chat->account))
959 gaim_blist_remove_chat(chat);
960 } else {
961 cnode = cnode->next;
962 }
963 }
964
965 gaim_blist_remove_group(group);
966 }
967
968 static void
969 finch_blist_remove_node(GaimBlistNode *node)
970 {
971 if (GAIM_BLIST_NODE_IS_CONTACT(node)) {
972 remove_contact((GaimContact*)node);
973 } else if (GAIM_BLIST_NODE_IS_BUDDY(node)) {
974 GaimBuddy *buddy = (GaimBuddy*)node;
975 GaimGroup *group = gaim_buddy_get_group(buddy);
976 gaim_account_remove_buddy(gaim_buddy_get_account(buddy), buddy, group);
977 gaim_blist_remove_buddy(buddy);
978 } else if (GAIM_BLIST_NODE_IS_CHAT(node)) {
979 gaim_blist_remove_chat((GaimChat*)node);
980 } else if (GAIM_BLIST_NODE_IS_GROUP(node)) {
981 remove_group((GaimGroup*)node);
982 }
983 }
984
985 static void
986 finch_blist_remove_node_cb(GaimBlistNode *node, GaimBlistNode *selected)
987 {
988 char *primary;
989 const char *name, *sec = NULL;
990
991 /* XXX: could be a contact */
992 if (GAIM_BLIST_NODE_IS_CONTACT(node)) {
993 GaimContact *c = (GaimContact*)node;
994 name = gaim_contact_get_alias(c);
995 if (c->totalsize > 1)
996 sec = _("Removing this contact will also remove all the buddies in the contact");
997 } else if (GAIM_BLIST_NODE_IS_BUDDY(node))
998 name = gaim_buddy_get_name((GaimBuddy*)node);
999 else if (GAIM_BLIST_NODE_IS_CHAT(node))
1000 name = gaim_chat_get_name((GaimChat*)node);
1001 else if (GAIM_BLIST_NODE_IS_GROUP(node))
1002 {
1003 name = ((GaimGroup*)node)->name;
1004 sec = _("Removing this group will also remove all the buddies in the group");
1005 }
1006 else
1007 return;
1008
1009 primary = g_strdup_printf(_("Are you sure you want to remove %s?"), name);
1010
1011 /* XXX: anything to do with the returned ui-handle? */
1012 gaim_request_action(node, _("Confirm Remove"),
1013 primary, sec,
1014 1, node, 2,
1015 _("Remove"), finch_blist_remove_node,
1016 _("Cancel"), NULL);
1017 g_free(primary);
1018 }
1019
1020 static void
1021 finch_blist_toggle_tag_buddy(GaimBlistNode *node)
1022 {
1023 GList *iter;
1024 if (node == NULL)
1025 return;
1026 if (GAIM_BLIST_NODE_IS_CHAT(node) || GAIM_BLIST_NODE_IS_GROUP(node))
1027 return;
1028 if (ggblist->tagged && (iter = g_list_find(ggblist->tagged, node)) != NULL) {
1029 ggblist->tagged = g_list_delete_link(ggblist->tagged, iter);
1030 } else {
1031 ggblist->tagged = g_list_prepend(ggblist->tagged, node);
1032 }
1033 if (GAIM_BLIST_NODE_IS_CONTACT(node))
1034 node = (GaimBlistNode*)gaim_contact_get_priority_buddy((GaimContact*)node);
1035 update_buddy_display((GaimBuddy*)node, ggblist);
1036 }
1037
1038 static void
1039 finch_blist_place_tagged(GaimBlistNode *target)
1040 {
1041 GaimGroup *tg = NULL;
1042 GaimContact *tc = NULL;
1043
1044 if (target == NULL)
1045 return;
1046
1047 /* This target resolution probably needs more clarification; for
1048 * example, if I tag a buddy in a contact, then place on
1049 * another buddy in the same contact, I probably intend to
1050 * place the tagged buddy immediately after (before?) the
1051 * target buddy -- this will simply move the tagged buddy
1052 * within the same contact without reference to position. */
1053 if (GAIM_BLIST_NODE_IS_GROUP(target))
1054 tg = (GaimGroup*)target;
1055 else if (GAIM_BLIST_NODE_IS_CONTACT(target))
1056 tc = (GaimContact*)target;
1057 else /* Buddy or Chat */
1058 tc = (GaimContact*)target->parent;
1059
1060 if (ggblist->tagged) {
1061 GList *list = ggblist->tagged;
1062 ggblist->tagged = NULL;
1063
1064 while (list) {
1065 GaimBlistNode *node = list->data;
1066 list = g_list_delete_link(list, list);
1067 if (tg) {
1068 if (GAIM_BLIST_NODE_IS_CONTACT(node))
1069 gaim_blist_add_contact((GaimContact*)node, tg, NULL);
1070 else
1071 gaim_blist_add_buddy((GaimBuddy*)node, NULL, tg, NULL);
1072 } else {
1073 if (GAIM_BLIST_NODE_IS_BUDDY(node))
1074 gaim_blist_add_buddy((GaimBuddy*)node, tc,
1075 gaim_buddy_get_group(gaim_contact_get_priority_buddy(tc)), NULL);
1076 else if (GAIM_BLIST_NODE_IS_CONTACT(node))
1077 gaim_blist_merge_contact((GaimContact*)node, target);
1078 }
1079 }
1080 }
1081 }
1082
1083 static void
1084 context_menu_destroyed(GntWidget *widget, FinchBlist *ggblist)
1085 {
1086 ggblist->context = NULL;
1087 }
1088
1089 static void
1090 draw_context_menu(FinchBlist *ggblist)
1091 {
1092 GaimBlistNode *node = NULL;
1093 GntWidget *context = NULL;
1094 GntTree *tree = NULL;
1095 int x, y, top, width;
1096 char *title = NULL;
1097
1098 tree = GNT_TREE(ggblist->tree);
1099
1100 node = gnt_tree_get_selection_data(tree);
1101
1102 if (ggblist->tooltip)
1103 remove_tooltip(ggblist);
1104
1105 ggblist->cnode = node;
1106
1107 ggblist->context = context = gnt_menu_new(GNT_MENU_POPUP);
1108 g_signal_connect(G_OBJECT(context), "destroy", G_CALLBACK(context_menu_destroyed), ggblist);
1109
1110 if (!node) {
1111 create_group_menu(GNT_MENU(context), NULL);
1112 title = g_strdup(_("Buddy List"));
1113 } else if (GAIM_BLIST_NODE_IS_CONTACT(node)) {
1114 create_buddy_menu(GNT_MENU(context),
1115 gaim_contact_get_priority_buddy((GaimContact*)node));
1116 title = g_strdup(gaim_contact_get_alias((GaimContact*)node));
1117 } else if (GAIM_BLIST_NODE_IS_BUDDY(node)) {
1118 GaimBuddy *buddy = (GaimBuddy *)node;
1119 create_buddy_menu(GNT_MENU(context), buddy);
1120 title = g_strdup(gaim_buddy_get_name(buddy));
1121 } else if (GAIM_BLIST_NODE_IS_CHAT(node)) {
1122 GaimChat *chat = (GaimChat*)node;
1123 create_chat_menu(GNT_MENU(context), chat);
1124 title = g_strdup(gaim_chat_get_name(chat));
1125 } else if (GAIM_BLIST_NODE_IS_GROUP(node)) {
1126 GaimGroup *group = (GaimGroup *)node;
1127 create_group_menu(GNT_MENU(context), group);
1128 title = g_strdup(group->name);
1129 }
1130
1131 append_extended_menu(GNT_MENU(context), node);
1132
1133 /* These are common for everything */
1134 if (node) {
1135 add_custom_action(GNT_MENU(context), _("Rename"),
1136 GAIM_CALLBACK(finch_blist_rename_node_cb), node);
1137 add_custom_action(GNT_MENU(context), _("Remove"),
1138 GAIM_CALLBACK(finch_blist_remove_node_cb), node);
1139
1140 if (ggblist->tagged && (GAIM_BLIST_NODE_IS_CONTACT(node)
1141 || GAIM_BLIST_NODE_IS_GROUP(node))) {
1142 add_custom_action(GNT_MENU(context), _("Place tagged"),
1143 GAIM_CALLBACK(finch_blist_place_tagged), node);
1144 }
1145
1146 if (GAIM_BLIST_NODE_IS_BUDDY(node) || GAIM_BLIST_NODE_IS_CONTACT(node)) {
1147 add_custom_action(GNT_MENU(context), _("Toggle Tag"),
1148 GAIM_CALLBACK(finch_blist_toggle_tag_buddy), node);
1149 }
1150 }
1151
1152 /* Set the position for the popup */
1153 gnt_widget_get_position(GNT_WIDGET(tree), &x, &y);
1154 gnt_widget_get_size(GNT_WIDGET(tree), &width, NULL);
1155 top = gnt_tree_get_selection_visible_line(tree);
1156
1157 x += width;
1158 y += top - 1;
1159
1160 gnt_widget_set_position(context, x, y);
1161 gnt_screen_menu_show(GNT_MENU(context));
1162 g_free(title);
1163 }
1164
1165 static void
1166 tooltip_for_buddy(GaimBuddy *buddy, GString *str)
1167 {
1168 GaimPlugin *prpl;
1169 GaimPluginProtocolInfo *prpl_info;
1170 GaimAccount *account;
1171 GaimNotifyUserInfo *user_info;
1172 const char *alias = gaim_buddy_get_alias(buddy);
1173 char *tmp, *strip;
1174
1175 user_info = gaim_notify_user_info_new();
1176
1177 account = gaim_buddy_get_account(buddy);
1178
1179 if (g_utf8_collate(gaim_buddy_get_name(buddy), alias))
1180 gaim_notify_user_info_add_pair(user_info, _("Nickname"), alias);
1181
1182 tmp = g_strdup_printf("%s (%s)",
1183 gaim_account_get_username(account),
1184 gaim_account_get_protocol_name(account));
1185 gaim_notify_user_info_add_pair(user_info, _("Account"), tmp);
1186 g_free(tmp);
1187
1188 prpl = gaim_find_prpl(gaim_account_get_protocol_id(account));
1189 prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(prpl);
1190 if (prpl_info && prpl_info->tooltip_text) {
1191 prpl_info->tooltip_text(buddy, user_info, TRUE);
1192 }
1193
1194 if (gaim_prefs_get_bool("/gaim/gnt/blist/idletime")) {
1195 GaimPresence *pre = gaim_buddy_get_presence(buddy);
1196 if (gaim_presence_is_idle(pre)) {
1197 time_t idle = gaim_presence_get_idle_time(pre);
1198 if (idle > 0) {
1199 char *st = gaim_str_seconds_to_string(time(NULL) - idle);
1200 gaim_notify_user_info_add_pair(user_info, _("Idle"), st);
1201 g_free(st);
1202 }
1203 }
1204 }
1205
1206 tmp = gaim_notify_user_info_get_text_with_newline(user_info, "<BR>");
1207 gaim_notify_user_info_destroy(user_info);
1208
1209 strip = gaim_markup_strip_html(tmp);
1210 g_string_append(str, strip);
1211 g_free(strip);
1212 g_free(tmp);
1213 }
1214
1215 static GString*
1216 make_sure_text_fits(GString *string)
1217 {
1218 int maxw = getmaxx(stdscr) - 3;
1219 char *str = gnt_util_onscreen_fit_string(string->str, maxw);
1220 string = g_string_assign(string, str);
1221 g_free(str);
1222 return string;
1223 }
1224
1225 static gboolean
1226 draw_tooltip_real(FinchBlist *ggblist)
1227 {
1228 GaimBlistNode *node;
1229 int x, y, top, width, w, h;
1230 GString *str;
1231 GntTree *tree;
1232 GntWidget *widget, *box, *tv;
1233 char *title = NULL;
1234 int lastseen = 0;
1235
1236 widget = ggblist->tree;
1237 tree = GNT_TREE(widget);
1238
1239 if (!gnt_widget_has_focus(ggblist->tree) ||
1240 (ggblist->context && !GNT_WIDGET_IS_FLAG_SET(ggblist->context, GNT_WIDGET_INVISIBLE)))
1241 return FALSE;
1242
1243 if (ggblist->tooltip)
1244 {
1245 /* XXX: Once we can properly redraw on expose events, this can be removed at the end
1246 * to avoid the blinking*/
1247 remove_tooltip(ggblist);
1248 }
1249
1250 node = gnt_tree_get_selection_data(tree);
1251 if (!node)
1252 return FALSE;
1253
1254 str = g_string_new("");
1255
1256 if (GAIM_BLIST_NODE_IS_CONTACT(node)) {
1257 GaimBuddy *pr = gaim_contact_get_priority_buddy((GaimContact*)node);
1258 gboolean offline = !GAIM_BUDDY_IS_ONLINE(pr);
1259 gboolean showoffline = gaim_prefs_get_bool(PREF_ROOT "/showoffline");
1260 const char *name = gaim_buddy_get_name(pr);
1261
1262 title = g_strdup(name);
1263 tooltip_for_buddy(pr, str);
1264 for (node = node->child; node; node = node->next) {
1265 GaimBuddy *buddy = (GaimBuddy*)node;
1266 if (offline) {
1267 int value = gaim_blist_node_get_int(node, "last_seen");
1268 if (value > lastseen)
1269 lastseen = value;
1270 }
1271 if (node == (GaimBlistNode*)pr)
1272 continue;
1273 if (!gaim_account_is_connected(buddy->account))
1274 continue;
1275 if (!showoffline && !GAIM_BUDDY_IS_ONLINE(buddy))
1276 continue;
1277 str = g_string_append(str, "\n----------\n");
1278 tooltip_for_buddy(buddy, str);
1279 }
1280 } else if (GAIM_BLIST_NODE_IS_BUDDY(node)) {
1281 GaimBuddy *buddy = (GaimBuddy *)node;
1282 tooltip_for_buddy(buddy, str);
1283 title = g_strdup(gaim_buddy_get_name(buddy));
1284 if (!GAIM_BUDDY_IS_ONLINE((GaimBuddy*)node))
1285 lastseen = gaim_blist_node_get_int(node, "last_seen");
1286 } else if (GAIM_BLIST_NODE_IS_GROUP(node)) {
1287 GaimGroup *group = (GaimGroup *)node;
1288
1289 g_string_append_printf(str, _("Online: %d\nTotal: %d"),
1290 gaim_blist_get_group_online_count(group),
1291 gaim_blist_get_group_size(group, FALSE));
1292
1293 title = g_strdup(group->name);
1294 } else if (GAIM_BLIST_NODE_IS_CHAT(node)) {
1295 GaimChat *chat = (GaimChat *)node;
1296 GaimAccount *account = chat->account;
1297
1298 g_string_append_printf(str, _("Account: %s (%s)"),
1299 gaim_account_get_username(account),
1300 gaim_account_get_protocol_name(account));
1301
1302 title = g_strdup(gaim_chat_get_name(chat));
1303 } else {
1304 g_string_free(str, TRUE);
1305 return FALSE;
1306 }
1307
1308 if (lastseen > 0) {
1309 char *tmp = gaim_str_seconds_to_string(time(NULL) - lastseen);
1310 g_string_append_printf(str, _("\nLast Seen: %s ago"), tmp);
1311 g_free(tmp);
1312 }
1313
1314 gnt_widget_get_position(widget, &x, &y);
1315 gnt_widget_get_size(widget, &width, NULL);
1316 top = gnt_tree_get_selection_visible_line(tree);
1317
1318 x += width;
1319 y += top - 1;
1320
1321 box = gnt_box_new(FALSE, FALSE);
1322 gnt_box_set_toplevel(GNT_BOX(box), TRUE);
1323 GNT_WIDGET_SET_FLAGS(box, GNT_WIDGET_NO_SHADOW);
1324 gnt_box_set_title(GNT_BOX(box), title);
1325
1326 str = make_sure_text_fits(str);
1327 gnt_util_get_text_bound(str->str, &w, &h);
1328 h = MAX(2, h);
1329 tv = gnt_text_view_new();
1330 gnt_widget_set_size(tv, w + 1, h);
1331 gnt_box_add_widget(GNT_BOX(box), tv);
1332
1333 gnt_widget_set_position(box, x, y);
1334 GNT_WIDGET_UNSET_FLAGS(box, GNT_WIDGET_CAN_TAKE_FOCUS);
1335 GNT_WIDGET_SET_FLAGS(box, GNT_WIDGET_TRANSIENT);
1336 gnt_widget_draw(box);
1337
1338 gnt_text_view_append_text_with_flags(GNT_TEXT_VIEW(tv), str->str, GNT_TEXT_FLAG_NORMAL);
1339 gnt_text_view_scroll(GNT_TEXT_VIEW(tv), 0);
1340
1341 g_free(title);
1342 g_string_free(str, TRUE);
1343 ggblist->tooltip = box;
1344 ggblist->tnode = node;
1345
1346 gnt_widget_set_name(ggblist->tooltip, "tooltip");
1347 return FALSE;
1348 }
1349
1350 static void
1351 draw_tooltip(FinchBlist *ggblist)
1352 {
1353 /* When an account has signed off, it removes one buddy at a time.
1354 * Drawing the tooltip after removing each buddy is expensive. On
1355 * top of that, if the selected buddy belongs to the disconnected
1356 * account, then retreiving the tooltip for that causes crash. So
1357 * let's make sure we wait for all the buddies to be removed first.*/
1358 int id = g_timeout_add(0, (GSourceFunc)draw_tooltip_real, ggblist);
1359 g_object_set_data_full(G_OBJECT(ggblist->window), "draw_tooltip_calback",
1360 GINT_TO_POINTER(id), (GDestroyNotify)g_source_remove);
1361 }
1362
1363 static void
1364 selection_changed(GntWidget *widget, gpointer old, gpointer current, FinchBlist *ggblist)
1365 {
1366 draw_tooltip(ggblist);
1367 }
1368
1369 static gboolean
1370 context_menu(GntWidget *widget, FinchBlist *ggblist)
1371 {
1372 draw_context_menu(ggblist);
1373 return TRUE;
1374 }
1375
1376 static gboolean
1377 key_pressed(GntWidget *widget, const char *text, FinchBlist *ggblist)
1378 {
1379 if (text[0] == 27 && text[1] == 0) {
1380 /* Escape was pressed */
1381 remove_peripherals(ggblist);
1382 } else if (strcmp(text, GNT_KEY_CTRL_O) == 0) {
1383 gaim_prefs_set_bool(PREF_ROOT "/showoffline",
1384 !gaim_prefs_get_bool(PREF_ROOT "/showoffline"));
1385 } else if (GNT_TREE(ggblist->tree)->search == NULL) {
1386 if (strcmp(text, "t") == 0) {
1387 finch_blist_toggle_tag_buddy(gnt_tree_get_selection_data(GNT_TREE(ggblist->tree)));
1388 gnt_bindable_perform_action_named(GNT_BINDABLE(ggblist->tree), "move-down");
1389 } else if (strcmp(text, "a") == 0) {
1390 finch_blist_place_tagged(gnt_tree_get_selection_data(GNT_TREE(ggblist->tree)));
1391 } else
1392 return FALSE;
1393 } else
1394 return FALSE;
1395
1396 return TRUE;
1397 }
1398
1399 static void
1400 update_buddy_display(GaimBuddy *buddy, FinchBlist *ggblist)
1401 {
1402 GaimContact *contact;
1403 GntTextFormatFlags bflag = 0, cflag = 0;
1404
1405 contact = gaim_buddy_get_contact(buddy);
1406
1407 gnt_tree_change_text(GNT_TREE(ggblist->tree), buddy, 0, get_display_name((GaimBlistNode*)buddy));
1408 gnt_tree_change_text(GNT_TREE(ggblist->tree), contact, 0, get_display_name((GaimBlistNode*)contact));
1409
1410 if (ggblist->tagged && g_list_find(ggblist->tagged, buddy))
1411 bflag |= GNT_TEXT_FLAG_BOLD;
1412 if (ggblist->tagged && g_list_find(ggblist->tagged, contact))
1413 cflag |= GNT_TEXT_FLAG_BOLD;
1414
1415 if (ggblist->tnode == (GaimBlistNode*)buddy)
1416 draw_tooltip(ggblist);
1417
1418 if (gaim_presence_is_idle(gaim_buddy_get_presence(buddy))) {
1419 gnt_tree_set_row_flags(GNT_TREE(ggblist->tree), buddy, bflag | GNT_TEXT_FLAG_DIM);
1420 gnt_tree_set_row_flags(GNT_TREE(ggblist->tree), contact, cflag | GNT_TEXT_FLAG_DIM);
1421 } else {
1422 gnt_tree_set_row_flags(GNT_TREE(ggblist->tree), buddy, bflag);
1423 gnt_tree_set_row_flags(GNT_TREE(ggblist->tree), contact, cflag);
1424 }
1425 }
1426
1427 static void
1428 buddy_status_changed(GaimBuddy *buddy, GaimStatus *old, GaimStatus *now, FinchBlist *ggblist)
1429 {
1430 update_buddy_display(buddy, ggblist);
1431 }
1432
1433 static void
1434 buddy_idle_changed(GaimBuddy *buddy, int old, int new, FinchBlist *ggblist)
1435 {
1436 update_buddy_display(buddy, ggblist);
1437 }
1438
1439 static void
1440 remove_peripherals(FinchBlist *ggblist)
1441 {
1442 if (ggblist->tooltip)
1443 remove_tooltip(ggblist);
1444 else if (ggblist->context)
1445 gnt_widget_destroy(ggblist->context);
1446 }
1447
1448 static void
1449 size_changed_cb(GntWidget *w, int wi, int h)
1450 {
1451 int width, height;
1452 gnt_widget_get_size(w, &width, &height);
1453 gaim_prefs_set_int(PREF_ROOT "/size/width", width);
1454 gaim_prefs_set_int(PREF_ROOT "/size/height", height);
1455 }
1456
1457 static void
1458 save_position_cb(GntWidget *w, int x, int y)
1459 {
1460 gaim_prefs_set_int(PREF_ROOT "/position/x", x);
1461 gaim_prefs_set_int(PREF_ROOT "/position/y", y);
1462 }
1463
1464 static void
1465 reset_blist_window(GntWidget *window, gpointer null)
1466 {
1467 GaimBlistNode *node;
1468 gaim_signals_disconnect_by_handle(finch_blist_get_handle());
1469 gaim_get_blist()->ui_data = NULL;
1470
1471 node = gaim_blist_get_root();
1472 while (node) {
1473 node->ui_data = NULL;
1474 node = gaim_blist_node_next(node, TRUE);
1475 }
1476
1477 if (ggblist->typing)
1478 g_source_remove(ggblist->typing);
1479 remove_peripherals(ggblist);
1480 if (ggblist->tagged)
1481 g_list_free(ggblist->tagged);
1482 g_free(ggblist);
1483 ggblist = NULL;
1484 }
1485
1486 static void
1487 populate_buddylist()
1488 {
1489 GaimBlistNode *node;
1490 GaimBuddyList *list;
1491
1492 if (strcmp(gaim_prefs_get_string(PREF_ROOT "/sort_type"), "text") == 0) {
1493 gnt_tree_set_compare_func(GNT_TREE(ggblist->tree),
1494 (GCompareFunc)blist_node_compare_text);
1495 } else if (strcmp(gaim_prefs_get_string(PREF_ROOT "/sort_type"), "status") == 0) {
1496 gnt_tree_set_compare_func(GNT_TREE(ggblist->tree),
1497 (GCompareFunc)blist_node_compare_status);
1498 } else if (strcmp(gaim_prefs_get_string(PREF_ROOT "/sort_type"), "log") == 0) {
1499 gnt_tree_set_compare_func(GNT_TREE(ggblist->tree),
1500 (GCompareFunc)blist_node_compare_log);
1501 }
1502
1503 list = gaim_get_blist();
1504 node = gaim_blist_get_root();
1505 while (node)
1506 {
1507 node_update(list, node);
1508 node = gaim_blist_node_next(node, FALSE);
1509 }
1510 }
1511
1512 static void
1513 destroy_status_list(GList *list)
1514 {
1515 g_list_foreach(list, (GFunc)g_free, NULL);
1516 g_list_free(list);
1517 }
1518
1519 static void
1520 populate_status_dropdown()
1521 {
1522 int i;
1523 GList *iter;
1524 GList *items = NULL;
1525 StatusBoxItem *item = NULL;
1526
1527 /* First the primitives */
1528 GaimStatusPrimitive prims[] = {GAIM_STATUS_AVAILABLE, GAIM_STATUS_AWAY,
1529 GAIM_STATUS_INVISIBLE, GAIM_STATUS_OFFLINE, GAIM_STATUS_UNSET};
1530
1531 gnt_combo_box_remove_all(GNT_COMBO_BOX(ggblist->status));
1532
1533 for (i = 0; prims[i] != GAIM_STATUS_UNSET; i++)
1534 {
1535 item = g_new0(StatusBoxItem, 1);
1536 item->type = STATUS_PRIMITIVE;
1537 item->u.prim = prims[i];
1538 items = g_list_prepend(items, item);
1539 gnt_combo_box_add_data(GNT_COMBO_BOX(ggblist->status), item,
1540 gaim_primitive_get_name_from_type(prims[i]));
1541 }
1542
1543 /* Now the popular statuses */
1544 for (iter = gaim_savedstatuses_get_popular(6); iter; iter = iter->next)
1545 {
1546 item = g_new0(StatusBoxItem, 1);
1547 item->type = STATUS_SAVED_POPULAR;
1548 item->u.saved = iter->data;
1549 items = g_list_prepend(items, item);
1550 gnt_combo_box_add_data(GNT_COMBO_BOX(ggblist->status), item,
1551 gaim_savedstatus_get_title(iter->data));
1552 }
1553
1554 /* New savedstatus */
1555 item = g_new0(StatusBoxItem, 1);
1556 item->type = STATUS_SAVED_NEW;
1557 items = g_list_prepend(items, item);
1558 gnt_combo_box_add_data(GNT_COMBO_BOX(ggblist->status), item,
1559 _("New..."));
1560
1561 /* More savedstatuses */
1562 item = g_new0(StatusBoxItem, 1);
1563 item->type = STATUS_SAVED_ALL;
1564 items = g_list_prepend(items, item);
1565 gnt_combo_box_add_data(GNT_COMBO_BOX(ggblist->status), item,
1566 _("Saved..."));
1567
1568 /* The keys for the combobox are created here, and never used
1569 * anywhere else. So make sure the keys are freed when the widget
1570 * is destroyed. */
1571 g_object_set_data_full(G_OBJECT(ggblist->status), "list of statuses",
1572 items, (GDestroyNotify)destroy_status_list);
1573 }
1574
1575 static void
1576 redraw_blist(const char *name, GaimPrefType type, gconstpointer val, gpointer data)
1577 {
1578 GaimBlistNode *node, *sel;
1579 if (ggblist == NULL || ggblist->window == NULL)
1580 return;
1581
1582 sel = gnt_tree_get_selection_data(GNT_TREE(ggblist->tree));
1583 gnt_tree_remove_all(GNT_TREE(ggblist->tree));
1584 node = gaim_blist_get_root();
1585 for (; node; node = gaim_blist_node_next(node, TRUE))
1586 node->ui_data = NULL;
1587 populate_buddylist();
1588 gnt_tree_set_selected(GNT_TREE(ggblist->tree), sel);
1589 draw_tooltip(ggblist);
1590 }
1591
1592 void finch_blist_init()
1593 {
1594 gaim_prefs_add_none(PREF_ROOT);
1595 gaim_prefs_add_none(PREF_ROOT "/size");
1596 gaim_prefs_add_int(PREF_ROOT "/size/width", 20);
1597 gaim_prefs_add_int(PREF_ROOT "/size/height", 17);
1598 gaim_prefs_add_none(PREF_ROOT "/position");
1599 gaim_prefs_add_int(PREF_ROOT "/position/x", 0);
1600 gaim_prefs_add_int(PREF_ROOT "/position/y", 0);
1601 gaim_prefs_add_bool(PREF_ROOT "/idletime", TRUE);
1602 gaim_prefs_add_bool(PREF_ROOT "/showoffline", FALSE);
1603 gaim_prefs_add_string(PREF_ROOT "/sort_type", "text");
1604
1605 gaim_prefs_connect_callback(finch_blist_get_handle(),
1606 PREF_ROOT "/showoffline", redraw_blist, NULL);
1607 gaim_prefs_connect_callback(finch_blist_get_handle(),
1608 PREF_ROOT "/sort_type", redraw_blist, NULL);
1609
1610 gaim_signal_connect(gaim_connections_get_handle(), "signed-on", gaim_blist_get_handle(),
1611 G_CALLBACK(account_signed_on_cb), NULL);
1612 return;
1613 }
1614
1615 static gboolean
1616 remove_typing_cb(gpointer null)
1617 {
1618 GaimSavedStatus *current;
1619 const char *message, *newmessage;
1620 GaimStatusPrimitive prim, newprim;
1621 StatusBoxItem *item;
1622
1623 current = gaim_savedstatus_get_current();
1624 message = gaim_savedstatus_get_message(current);
1625 prim = gaim_savedstatus_get_type(current);
1626
1627 newmessage = gnt_entry_get_text(GNT_ENTRY(ggblist->statustext));
1628 item = gnt_combo_box_get_selected_data(GNT_COMBO_BOX(ggblist->status));
1629 g_return_val_if_fail(item->type == STATUS_PRIMITIVE, FALSE);
1630 newprim = item->u.prim;
1631
1632 if (newprim != prim || ((message && !newmessage) ||
1633 (!message && newmessage) ||
1634 (message && newmessage && g_utf8_collate(message, newmessage) != 0)))
1635 {
1636 GaimSavedStatus *status = gaim_savedstatus_find_transient_by_type_and_message(newprim, newmessage);
1637 /* Holy Crap! That's a LAWNG function name */
1638 if (status == NULL)
1639 {
1640 status = gaim_savedstatus_new(NULL, newprim);
1641 gaim_savedstatus_set_message(status, newmessage);
1642 }
1643
1644 gaim_savedstatus_activate(status);
1645 }
1646
1647 gnt_box_give_focus_to_child(GNT_BOX(ggblist->window), ggblist->tree);
1648 if (ggblist->typing)
1649 g_source_remove(ggblist->typing);
1650 ggblist->typing = 0;
1651 return FALSE;
1652 }
1653
1654 static void
1655 status_selection_changed(GntComboBox *box, StatusBoxItem *old, StatusBoxItem *now, gpointer null)
1656 {
1657 gnt_entry_set_text(GNT_ENTRY(ggblist->statustext), NULL);
1658 if (now->type == STATUS_SAVED_POPULAR)
1659 {
1660 /* Set the status immediately */
1661 gaim_savedstatus_activate(now->u.saved);
1662 }
1663 else if (now->type == STATUS_PRIMITIVE)
1664 {
1665 /* Move the focus to the entry box */
1666 /* XXX: Make sure the selected status can have a message */
1667 gnt_box_move_focus(GNT_BOX(ggblist->window), 1);
1668 ggblist->typing = g_timeout_add(TYPING_TIMEOUT, (GSourceFunc)remove_typing_cb, NULL);
1669 }
1670 else if (now->type == STATUS_SAVED_ALL)
1671 {
1672 /* Restore the selection to reflect current status. */
1673 savedstatus_changed(gaim_savedstatus_get_current(), NULL);
1674 gnt_box_give_focus_to_child(GNT_BOX(ggblist->window), ggblist->tree);
1675 finch_savedstatus_show_all();
1676 }
1677 else if (now->type == STATUS_SAVED_NEW)
1678 {
1679 savedstatus_changed(gaim_savedstatus_get_current(), NULL);
1680 gnt_box_give_focus_to_child(GNT_BOX(ggblist->window), ggblist->tree);
1681 finch_savedstatus_edit(NULL);
1682 }
1683 else
1684 g_return_if_reached();
1685 }
1686
1687 static gboolean
1688 status_text_changed(GntEntry *entry, const char *text, gpointer null)
1689 {
1690 if ((text[0] == 27 || (text[0] == '\t' && text[1] == '\0')) && ggblist->typing == 0)
1691 return FALSE;
1692
1693 if (ggblist->typing)
1694 g_source_remove(ggblist->typing);
1695 ggblist->typing = 0;
1696
1697 if (text[0] == '\r' && text[1] == 0)
1698 {
1699 /* Set the status only after you press 'Enter' */
1700 remove_typing_cb(NULL);
1701 return TRUE;
1702 }
1703
1704 ggblist->typing = g_timeout_add(TYPING_TIMEOUT, (GSourceFunc)remove_typing_cb, NULL);
1705 return FALSE;
1706 }
1707
1708 static void
1709 savedstatus_changed(GaimSavedStatus *now, GaimSavedStatus *old)
1710 {
1711 GList *list;
1712 GaimStatusPrimitive prim;
1713 const char *message;
1714 gboolean found = FALSE, saved = TRUE;
1715
1716 if (!ggblist)
1717 return;
1718
1719 /* Block the signals we don't want to emit */
1720 g_signal_handlers_block_matched(ggblist->status, G_SIGNAL_MATCH_FUNC,
1721 0, 0, NULL, status_selection_changed, NULL);
1722 g_signal_handlers_block_matched(ggblist->statustext, G_SIGNAL_MATCH_FUNC,
1723 0, 0, NULL, status_text_changed, NULL);
1724
1725 prim = gaim_savedstatus_get_type(now);
1726 message = gaim_savedstatus_get_message(now);
1727
1728 /* Rebuild the status dropdown */
1729 populate_status_dropdown();
1730
1731 while (!found) {
1732 list = g_object_get_data(G_OBJECT(ggblist->status), "list of statuses");
1733 for (; list; list = list->next)
1734 {
1735 StatusBoxItem *item = list->data;
1736 if ((saved && item->type != STATUS_PRIMITIVE && item->u.saved == now) ||
1737 (!saved && item->type == STATUS_PRIMITIVE && item->u.prim == prim))
1738 {
1739 char *mess = gaim_unescape_html(message);
1740 gnt_combo_box_set_selected(GNT_COMBO_BOX(ggblist->status), item);
1741 gnt_entry_set_text(GNT_ENTRY(ggblist->statustext), mess);
1742 gnt_widget_draw(ggblist->status);
1743 g_free(mess);
1744 found = TRUE;
1745 break;
1746 }
1747 }
1748 if (!saved)
1749 break;
1750 saved = FALSE;
1751 }
1752
1753 g_signal_handlers_unblock_matched(ggblist->status, G_SIGNAL_MATCH_FUNC,
1754 0, 0, NULL, status_selection_changed, NULL);
1755 g_signal_handlers_unblock_matched(ggblist->statustext, G_SIGNAL_MATCH_FUNC,
1756 0, 0, NULL, status_text_changed, NULL);
1757 }
1758
1759 static int
1760 blist_node_compare_text(GaimBlistNode *n1, GaimBlistNode *n2)
1761 {
1762 const char *s1, *s2;
1763 char *us1, *us2;
1764 int ret;
1765
1766 g_return_val_if_fail(n1->type == n2->type, -1);
1767
1768 switch (n1->type)
1769 {
1770 case GAIM_BLIST_GROUP_NODE:
1771 s1 = ((GaimGroup*)n1)->name;
1772 s2 = ((GaimGroup*)n2)->name;
1773 break;
1774 case GAIM_BLIST_CHAT_NODE:
1775 s1 = gaim_chat_get_name((GaimChat*)n1);
1776 s2 = gaim_chat_get_name((GaimChat*)n2);
1777 break;
1778 case GAIM_BLIST_BUDDY_NODE:
1779 return gaim_presence_compare(gaim_buddy_get_presence((GaimBuddy*)n1),
1780 gaim_buddy_get_presence((GaimBuddy*)n2));
1781 break;
1782 case GAIM_BLIST_CONTACT_NODE:
1783 s1 = gaim_contact_get_alias((GaimContact*)n1);
1784 s2 = gaim_contact_get_alias((GaimContact*)n2);
1785 break;
1786 default:
1787 return -1;
1788 }
1789
1790 us1 = g_utf8_strup(s1, -1);
1791 us2 = g_utf8_strup(s2, -1);
1792 ret = g_utf8_collate(us1, us2);
1793 g_free(us1);
1794 g_free(us2);
1795
1796 return ret;
1797 }
1798
1799 static int
1800 blist_node_compare_status(GaimBlistNode *n1, GaimBlistNode *n2)
1801 {
1802 int ret;
1803
1804 g_return_val_if_fail(n1->type == n2->type, -1);
1805
1806 switch (n1->type) {
1807 case GAIM_BLIST_CONTACT_NODE:
1808 n1 = (GaimBlistNode*)gaim_contact_get_priority_buddy((GaimContact*)n1);
1809 n2 = (GaimBlistNode*)gaim_contact_get_priority_buddy((GaimContact*)n2);
1810 /* now compare the presence of the priority buddies */
1811 case GAIM_BLIST_BUDDY_NODE:
1812 ret = gaim_presence_compare(gaim_buddy_get_presence((GaimBuddy*)n1),
1813 gaim_buddy_get_presence((GaimBuddy*)n2));
1814 if (ret != 0)
1815 return ret;
1816 break;
1817 default:
1818 break;
1819 }
1820
1821 /* Sort alphabetically if presence is not comparable */
1822 ret = blist_node_compare_text(n1, n2);
1823
1824 return ret;
1825 }
1826
1827 static int
1828 get_contact_log_size(GaimBlistNode *c)
1829 {
1830 int log = 0;
1831 GaimBlistNode *node;
1832
1833 for (node = c->child; node; node = node->next) {
1834 GaimBuddy *b = (GaimBuddy*)node;
1835 log += gaim_log_get_total_size(GAIM_LOG_IM, b->name, b->account);
1836 }
1837
1838 return log;
1839 }
1840
1841 static int
1842 blist_node_compare_log(GaimBlistNode *n1, GaimBlistNode *n2)
1843 {
1844 int ret;
1845 GaimBuddy *b1, *b2;
1846
1847 g_return_val_if_fail(n1->type == n2->type, -1);
1848
1849 switch (n1->type) {
1850 case GAIM_BLIST_BUDDY_NODE:
1851 b1 = (GaimBuddy*)n1;
1852 b2 = (GaimBuddy*)n2;
1853 ret = gaim_log_get_total_size(GAIM_LOG_IM, b2->name, b2->account) -
1854 gaim_log_get_total_size(GAIM_LOG_IM, b1->name, b1->account);
1855 if (ret != 0)
1856 return ret;
1857 break;
1858 case GAIM_BLIST_CONTACT_NODE:
1859 ret = get_contact_log_size(n2) - get_contact_log_size(n1);
1860 if (ret != 0)
1861 return ret;
1862 break;
1863 default:
1864 break;
1865 }
1866 ret = blist_node_compare_text(n1, n2);
1867 return ret;
1868 }
1869
1870 static gboolean
1871 blist_clicked(GntTree *tree, GntMouseEvent event, int x, int y, gpointer ggblist)
1872 {
1873 if (event == GNT_RIGHT_MOUSE_DOWN) {
1874 draw_context_menu(ggblist);
1875 }
1876 return FALSE;
1877 }
1878
1879 static void
1880 plugin_action(GntMenuItem *item, gpointer data)
1881 {
1882 GaimPluginAction *action = data;
1883 if (action && action->callback)
1884 action->callback(action);
1885 }
1886
1887 static void
1888 build_plugin_actions(GntMenuItem *item, GaimPlugin *plugin, gpointer context)
1889 {
1890 GntWidget *sub = gnt_menu_new(GNT_MENU_POPUP);
1891 GList *actions;
1892 GntMenuItem *menuitem;
1893
1894 gnt_menuitem_set_submenu(item, GNT_MENU(sub));
1895 for (actions = GAIM_PLUGIN_ACTIONS(plugin, context); actions;
1896 actions = g_list_delete_link(actions, actions)) {
1897 if (actions->data) {
1898 GaimPluginAction *action = actions->data;
1899 action->plugin = plugin;
1900 action->context = context;
1901 menuitem = gnt_menuitem_new(action->label);
1902 gnt_menu_add_item(GNT_MENU(sub), menuitem);
1903
1904 gnt_menuitem_set_callback(menuitem, plugin_action, action);
1905 g_object_set_data_full(G_OBJECT(menuitem), "plugin_action",
1906 action, (GDestroyNotify)gaim_plugin_action_free);
1907 }
1908 }
1909 }
1910
1911 static void
1912 reconstruct_plugins_menu()
1913 {
1914 GntWidget *sub;
1915 GntMenuItem *plg;
1916 GList *iter;
1917
1918 if (!ggblist)
1919 return;
1920
1921 if (ggblist->plugins == NULL)
1922 ggblist->plugins = gnt_menuitem_new(_("Plugins"));
1923
1924 plg = ggblist->plugins;
1925 sub = gnt_menu_new(GNT_MENU_POPUP);
1926 gnt_menuitem_set_submenu(plg, GNT_MENU(sub));
1927
1928 for (iter = gaim_plugins_get_loaded(); iter; iter = iter->next) {
1929 GaimPlugin *plugin = iter->data;
1930 GntMenuItem *item;
1931 if (GAIM_IS_PROTOCOL_PLUGIN(plugin))
1932 continue;
1933
1934 if (!GAIM_PLUGIN_HAS_ACTIONS(plugin))
1935 continue;
1936
1937 item = gnt_menuitem_new(_(plugin->info->name));
1938 gnt_menu_add_item(GNT_MENU(sub), item);
1939 build_plugin_actions(item, plugin, NULL);
1940 }
1941 }
1942
1943 static void
1944 reconstruct_accounts_menu()
1945 {
1946 GntWidget *sub;
1947 GntMenuItem *acc, *item;
1948 GList *iter;
1949
1950 if (!ggblist)
1951 return;
1952
1953 if (ggblist->accounts == NULL)
1954 ggblist->accounts = gnt_menuitem_new(_("Accounts"));
1955
1956 acc = ggblist->accounts;
1957 sub = gnt_menu_new(GNT_MENU_POPUP);
1958 gnt_menuitem_set_submenu(acc, GNT_MENU(sub));
1959
1960 for (iter = gaim_accounts_get_all_active(); iter;
1961 iter = g_list_delete_link(iter, iter)) {
1962 GaimAccount *account = iter->data;
1963 GaimConnection *gc = gaim_account_get_connection(account);
1964 GaimPlugin *prpl;
1965
1966 if (!gc || !GAIM_CONNECTION_IS_CONNECTED(gc))
1967 continue;
1968 prpl = gc->prpl;
1969
1970 if (GAIM_PLUGIN_HAS_ACTIONS(prpl)) {
1971 item = gnt_menuitem_new(gaim_account_get_username(account));
1972 gnt_menu_add_item(GNT_MENU(sub), item);
1973 build_plugin_actions(item, prpl, gc);
1974 }
1975 }
1976 }
1977
1978 static void
1979 account_signed_on_cb()
1980 {
1981 GaimBlistNode *node;
1982
1983 for (node = gaim_blist_get_root(); node;
1984 node = gaim_blist_node_next(node, FALSE)) {
1985 if (GAIM_BLIST_NODE_IS_CHAT(node)) {
1986 GaimChat *chat = (GaimChat*)node;
1987 if (gaim_blist_node_get_bool(node, "gnt-autojoin"))
1988 serv_join_chat(gaim_account_get_connection(chat->account), chat->components);
1989 }
1990 }
1991 }
1992
1993 static void show_offline_cb(GntMenuItem *item, gpointer n)
1994 {
1995 gaim_prefs_set_bool(PREF_ROOT "/showoffline",
1996 !gaim_prefs_get_bool(PREF_ROOT "/showoffline"));
1997 }
1998
1999 static void sort_blist_change_cb(GntMenuItem *item, gpointer n)
2000 {
2001 gaim_prefs_set_string(PREF_ROOT "/sort_type", n);
2002 }
2003
2004 /* XXX: send_im_select* -- Xerox */
2005 static void
2006 send_im_select_cb(gpointer data, GaimRequestFields *fields)
2007 {
2008 GaimAccount *account;
2009 const char *username;
2010
2011 account = gaim_request_fields_get_account(fields, "account");
2012 username = gaim_request_fields_get_string(fields, "screenname");
2013
2014 gaim_conversation_new(GAIM_CONV_TYPE_IM, account, username);
2015 }
2016
2017 static void
2018 send_im_select(GntMenuItem *item, gpointer n)
2019 {
2020 GaimRequestFields *fields;
2021 GaimRequestFieldGroup *group;
2022 GaimRequestField *field;
2023
2024 fields = gaim_request_fields_new();
2025
2026 group = gaim_request_field_group_new(NULL);
2027 gaim_request_fields_add_group(fields, group);
2028
2029 field = gaim_request_field_string_new("screenname", _("_Name"), NULL, FALSE);
2030 gaim_request_field_set_type_hint(field, "screenname");
2031 gaim_request_field_set_required(field, TRUE);
2032 gaim_request_field_group_add_field(group, field);
2033
2034 field = gaim_request_field_account_new("account", _("_Account"), NULL);
2035 gaim_request_field_set_type_hint(field, "account");
2036 gaim_request_field_set_visible(field,
2037 (gaim_connections_get_all() != NULL &&
2038 gaim_connections_get_all()->next != NULL));
2039 gaim_request_field_set_required(field, TRUE);
2040 gaim_request_field_group_add_field(group, field);
2041
2042 gaim_request_fields(gaim_get_blist(), _("New Instant Message"),
2043 NULL,
2044 _("Please enter the screen name or alias of the person "
2045 "you would like to IM."),
2046 fields,
2047 _("OK"), G_CALLBACK(send_im_select_cb),
2048 _("Cancel"), NULL,
2049 NULL);
2050 }
2051
2052 static void
2053 create_menu()
2054 {
2055 GntWidget *menu, *sub;
2056 GntMenuItem *item;
2057 GntWindow *window;
2058
2059 if (!ggblist)
2060 return;
2061
2062 window = GNT_WINDOW(ggblist->window);
2063 ggblist->menu = menu = gnt_menu_new(GNT_MENU_TOPLEVEL);
2064 gnt_window_set_menu(window, GNT_MENU(menu));
2065
2066 item = gnt_menuitem_new(_("Options"));
2067 gnt_menu_add_item(GNT_MENU(menu), item);
2068
2069 sub = gnt_menu_new(GNT_MENU_POPUP);
2070 gnt_menuitem_set_submenu(item, GNT_MENU(sub));
2071
2072 item = gnt_menuitem_new(_("Send IM..."));
2073 gnt_menu_add_item(GNT_MENU(sub), item);
2074 gnt_menuitem_set_callback(GNT_MENUITEM(item), send_im_select, NULL);
2075
2076 item = gnt_menuitem_check_new(_("Toggle offline buddies"));
2077 gnt_menuitem_check_set_checked(GNT_MENUITEM_CHECK(item),
2078 gaim_prefs_get_bool(PREF_ROOT "/showoffline"));
2079 gnt_menu_add_item(GNT_MENU(sub), item);
2080 gnt_menuitem_set_callback(GNT_MENUITEM(item), show_offline_cb, NULL);
2081
2082 item = gnt_menuitem_new(_("Sort by status"));
2083 gnt_menu_add_item(GNT_MENU(sub), item);
2084 gnt_menuitem_set_callback(GNT_MENUITEM(item), sort_blist_change_cb, "status");
2085
2086 item = gnt_menuitem_new(_("Sort alphabetically"));
2087 gnt_menu_add_item(GNT_MENU(sub), item);
2088 gnt_menuitem_set_callback(GNT_MENUITEM(item), sort_blist_change_cb, "text");
2089
2090 item = gnt_menuitem_new(_("Sort by log size"));
2091 gnt_menu_add_item(GNT_MENU(sub), item);
2092 gnt_menuitem_set_callback(GNT_MENUITEM(item), sort_blist_change_cb, "log");
2093
2094 reconstruct_accounts_menu();
2095 gnt_menu_add_item(GNT_MENU(menu), ggblist->accounts);
2096
2097 reconstruct_plugins_menu();
2098 gnt_menu_add_item(GNT_MENU(menu), ggblist->plugins);
2099 }
2100
2101 void finch_blist_show()
2102 {
2103 blist_show(gaim_get_blist());
2104 }
2105
2106 static void
2107 blist_show(GaimBuddyList *list)
2108 {
2109 if (ggblist == NULL)
2110 new_list(list);
2111 else if (ggblist->window)
2112 return;
2113
2114 ggblist->window = gnt_vwindow_new(FALSE);
2115 gnt_widget_set_name(ggblist->window, "buddylist");
2116 gnt_box_set_toplevel(GNT_BOX(ggblist->window), TRUE);
2117 gnt_box_set_title(GNT_BOX(ggblist->window), _("Buddy List"));
2118 gnt_box_set_pad(GNT_BOX(ggblist->window), 0);
2119
2120 ggblist->tree = gnt_tree_new();
2121
2122 GNT_WIDGET_SET_FLAGS(ggblist->tree, GNT_WIDGET_NO_BORDER);
2123 gnt_widget_set_size(ggblist->tree, gaim_prefs_get_int(PREF_ROOT "/size/width"),
2124 gaim_prefs_get_int(PREF_ROOT "/size/height"));
2125 gnt_widget_set_position(ggblist->window, gaim_prefs_get_int(PREF_ROOT "/position/x"),
2126 gaim_prefs_get_int(PREF_ROOT "/position/y"));
2127
2128 gnt_tree_set_col_width(GNT_TREE(ggblist->tree), 0,
2129 gaim_prefs_get_int(PREF_ROOT "/size/width") - 1);
2130
2131 gnt_box_add_widget(GNT_BOX(ggblist->window), ggblist->tree);
2132
2133 ggblist->status = gnt_combo_box_new();
2134 gnt_box_add_widget(GNT_BOX(ggblist->window), ggblist->status);
2135 ggblist->statustext = gnt_entry_new(NULL);
2136 gnt_box_add_widget(GNT_BOX(ggblist->window), ggblist->statustext);
2137
2138 gnt_widget_show(ggblist->window);
2139
2140 gaim_signal_connect(gaim_connections_get_handle(), "signed-on", finch_blist_get_handle(),
2141 GAIM_CALLBACK(reconstruct_accounts_menu), NULL);
2142 gaim_signal_connect(gaim_connections_get_handle(), "signed-off", finch_blist_get_handle(),
2143 GAIM_CALLBACK(reconstruct_accounts_menu), NULL);
2144 gaim_signal_connect(gaim_blist_get_handle(), "buddy-status-changed", finch_blist_get_handle(),
2145 GAIM_CALLBACK(buddy_status_changed), ggblist);
2146 gaim_signal_connect(gaim_blist_get_handle(), "buddy-idle-changed", finch_blist_get_handle(),
2147 GAIM_CALLBACK(buddy_idle_changed), ggblist);
2148
2149 gaim_signal_connect(gaim_plugins_get_handle(), "plugin-load", finch_blist_get_handle(),
2150 GAIM_CALLBACK(reconstruct_plugins_menu), NULL);
2151 gaim_signal_connect(gaim_plugins_get_handle(), "plugin-unload", finch_blist_get_handle(),
2152 GAIM_CALLBACK(reconstruct_plugins_menu), NULL);
2153
2154 #if 0
2155 gaim_signal_connect(gaim_blist_get_handle(), "buddy-signed-on", finch_blist_get_handle(),
2156 GAIM_CALLBACK(buddy_signed_on), ggblist);
2157 gaim_signal_connect(gaim_blist_get_handle(), "buddy-signed-off", finch_blist_get_handle(),
2158 GAIM_CALLBACK(buddy_signed_off), ggblist);
2159
2160 /* These I plan to use to indicate unread-messages etc. */
2161 gaim_signal_connect(gaim_conversations_get_handle(), "received-im-msg", finch_blist_get_handle(),
2162 GAIM_CALLBACK(received_im_msg), list);
2163 gaim_signal_connect(gaim_conversations_get_handle(), "sent-im-msg", finch_blist_get_handle(),
2164 GAIM_CALLBACK(sent_im_msg), NULL);
2165
2166 gaim_signal_connect(gaim_conversations_get_handle(), "received-chat-msg", finch_blist_get_handle(),
2167 GAIM_CALLBACK(received_chat_msg), list);
2168 #endif
2169
2170 g_signal_connect(G_OBJECT(ggblist->tree), "selection_changed", G_CALLBACK(selection_changed), ggblist);
2171 g_signal_connect(G_OBJECT(ggblist->tree), "key_pressed", G_CALLBACK(key_pressed), ggblist);
2172 g_signal_connect(G_OBJECT(ggblist->tree), "context-menu", G_CALLBACK(context_menu), ggblist);
2173 g_signal_connect_after(G_OBJECT(ggblist->tree), "clicked", G_CALLBACK(blist_clicked), ggblist);
2174 g_signal_connect(G_OBJECT(ggblist->tree), "activate", G_CALLBACK(selection_activate), ggblist);
2175 g_signal_connect_data(G_OBJECT(ggblist->tree), "gained-focus", G_CALLBACK(draw_tooltip),
2176 ggblist, 0, G_CONNECT_AFTER | G_CONNECT_SWAPPED);
2177 g_signal_connect_data(G_OBJECT(ggblist->tree), "lost-focus", G_CALLBACK(remove_peripherals),
2178 ggblist, 0, G_CONNECT_AFTER | G_CONNECT_SWAPPED);
2179 g_signal_connect(G_OBJECT(ggblist->tree), "size_changed", G_CALLBACK(size_changed_cb), NULL);
2180 g_signal_connect(G_OBJECT(ggblist->window), "position_set", G_CALLBACK(save_position_cb), NULL);
2181 g_signal_connect(G_OBJECT(ggblist->window), "destroy", G_CALLBACK(reset_blist_window), NULL);
2182
2183 /* Status signals */
2184 gaim_signal_connect(gaim_savedstatuses_get_handle(), "savedstatus-changed", finch_blist_get_handle(),
2185 GAIM_CALLBACK(savedstatus_changed), NULL);
2186 g_signal_connect(G_OBJECT(ggblist->status), "selection_changed",
2187 G_CALLBACK(status_selection_changed), NULL);
2188 g_signal_connect(G_OBJECT(ggblist->statustext), "key_pressed",
2189 G_CALLBACK(status_text_changed), NULL);
2190
2191 create_menu();
2192
2193 populate_buddylist();
2194
2195 savedstatus_changed(gaim_savedstatus_get_current(), NULL);
2196 }
2197
2198 void finch_blist_uninit()
2199 {
2200 if (ggblist == NULL)
2201 return;
2202
2203 gnt_widget_destroy(ggblist->window);
2204 g_free(ggblist);
2205 ggblist = NULL;
2206 }
2207
2208 gboolean finch_blist_get_position(int *x, int *y)
2209 {
2210 if (!ggblist || !ggblist->window)
2211 return FALSE;
2212 gnt_widget_get_position(ggblist->window, x, y);
2213 return TRUE;
2214 }
2215
2216 void finch_blist_set_position(int x, int y)
2217 {
2218 gnt_widget_set_position(ggblist->window, x, y);
2219 }
2220
2221 gboolean finch_blist_get_size(int *width, int *height)
2222 {
2223 if (!ggblist || !ggblist->window)
2224 return FALSE;
2225 gnt_widget_get_size(ggblist->window, width, height);
2226 return TRUE;
2227 }
2228
2229 void finch_blist_set_size(int width, int height)
2230 {
2231 gnt_widget_set_size(ggblist->window, width, height);
2232 }
2233