finch/gntblist.c

changeset 42678
0b9b81b6ff18
parent 42677
66b49e545c53
child 42679
192a8112562f
equal deleted inserted replaced
42677:66b49e545c53 42678:0b9b81b6ff18
1 /*
2 * finch
3 *
4 * Finch is the legal property of its developers, whose names are too numerous
5 * to list here. Please refer to the COPYRIGHT file distributed with this
6 * source distribution.
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
21 */
22
23 #include NCURSES_HEADER
24
25 #include <string.h>
26
27 #include <glib/gi18n-lib.h>
28
29 #include <gplugin.h>
30
31 #include <purple.h>
32
33 #include <gnt.h>
34
35 #include "gntblist.h"
36 #include "gntconv.h"
37 #include "gntmenuutil.h"
38 #include "gntstatus.h"
39
40 #define PREF_ROOT "/finch/blist"
41 #define TYPING_TIMEOUT_S 4
42
43 #define UI_DATA "ui-finch"
44
45 #define SHOW_EMPTY_GROUP_TIMEOUT 60
46
47 struct _FinchBuddyList {
48 PurpleBuddyList parent;
49
50 GntWidget *window;
51 GntWidget *tree;
52
53 GntWidget *tooltip;
54 PurpleBlistNode *tnode; /* Who is the tooltip being displayed for? */
55 GList *tagged; /* A list of tagged blistnodes */
56
57 GntWidget *context;
58 PurpleBlistNode *cnode;
59
60 /* XXX: I am KISSing */
61 GntWidget *status; /* Dropdown with the statuses */
62 GntWidget *statustext; /* Status message */
63 int typing;
64
65 GntWidget *menu;
66 /* These are the menuitems that get regenerated */
67 GntMenuItem *accounts;
68 GntMenuItem *plugins;
69 GntMenuItem *grouping;
70
71 /* When a new group is manually added, it is empty, but we still want to show it
72 * for a while (SHOW_EMPTY_GROUP_TIMEOUT seconds) even if 'show empty groups' is
73 * not selected.
74 */
75 GList *new_group;
76 guint new_group_timeout;
77
78 FinchBlistManager *manager;
79 };
80
81 typedef struct
82 {
83 gpointer row; /* the row in the GntTree */
84 guint signed_timer; /* used when 'recently' signed on/off */
85 } FinchBlistNode;
86
87 typedef enum
88 {
89 STATUS_PRIMITIVE = 0,
90 STATUS_SAVED_POPULAR,
91 STATUS_SAVED_ALL,
92 STATUS_SAVED_NEW
93 } StatusType;
94
95 typedef struct
96 {
97 StatusType type;
98 union
99 {
100 PurpleStatusPrimitive prim;
101 PurpleSavedStatus *saved;
102 } u;
103 } StatusBoxItem;
104
105 static FinchBuddyList *ggblist;
106
107 static void add_buddy(PurpleBuddy *buddy, FinchBuddyList *ggblist);
108 static void add_contact(PurpleMetaContact *contact, FinchBuddyList *ggblist);
109 static void add_group(PurpleGroup *group, FinchBuddyList *ggblist);
110 static void add_chat(PurpleChat *chat, FinchBuddyList *ggblist);
111 static void add_node(PurpleBlistNode *node, FinchBuddyList *ggblist);
112 static void node_update(PurpleBuddyList *list, PurpleBlistNode *node);
113 static void draw_tooltip(FinchBuddyList *ggblist);
114 static void tooltip_for_buddy(PurpleBuddy *buddy, GString *str, gboolean full);
115 static gboolean remove_typing_cb(gpointer data);
116 static void remove_peripherals(FinchBuddyList *ggblist);
117 static const char * get_display_name(PurpleBlistNode *node);
118 static void savedstatus_changed(PurpleSavedStatus *now, PurpleSavedStatus *old);
119 static void blist_show(PurpleBuddyList *list);
120 static void update_node_display(PurpleBlistNode *buddy,
121 FinchBuddyList *ggblist);
122 static void update_buddy_display(PurpleBuddy *buddy, FinchBuddyList *ggblist);
123 static gboolean account_autojoin_cb(PurpleConnection *pc, gpointer data);
124 static void finch_request_add_buddy(PurpleBuddyList *list,
125 PurpleAccount *account,
126 const char *username, const char *grp,
127 const char *alias);
128 static void menu_group_set_cb(GntMenuItem *item, gpointer data);
129
130 /* Sort functions */
131 static int blist_node_compare_position(PurpleBlistNode *n1, PurpleBlistNode *n2);
132 static int blist_node_compare_text(PurpleBlistNode *n1, PurpleBlistNode *n2);
133 static int blist_node_compare_status(PurpleBlistNode *n1, PurpleBlistNode *n2);
134
135 static int color_available;
136 static int color_away;
137 static int color_offline;
138 static int color_idle;
139
140 /*
141 * Buddy List Manager functions.
142 */
143
144 static gboolean default_can_add_node(PurpleBlistNode *node)
145 {
146 gboolean offline = purple_prefs_get_bool(PREF_ROOT "/showoffline");
147
148 if (PURPLE_IS_BUDDY(node)) {
149 PurpleBuddy *buddy = (PurpleBuddy*)node;
150 FinchBlistNode *fnode = g_object_get_data(G_OBJECT(node), UI_DATA);
151
152 if (!purple_buddy_get_contact(buddy))
153 return FALSE; /* When a new buddy is added and show-offline is set */
154 if (PURPLE_BUDDY_IS_ONLINE(buddy))
155 return TRUE; /* The buddy is online */
156 if (!purple_account_is_connected(purple_buddy_get_account(buddy)))
157 return FALSE; /* The account is disconnected. Do not show */
158 if (offline)
159 return TRUE; /* We want to see offline buddies too */
160 if (fnode && fnode->signed_timer)
161 return TRUE; /* Show if the buddy just signed off */
162 if (purple_blist_node_get_bool(node, "show_offline"))
163 return TRUE;
164 } else if (PURPLE_IS_META_CONTACT(node)) {
165 PurpleBlistNode *child;
166 for (child = purple_blist_node_get_first_child(node);
167 child; child = purple_blist_node_get_sibling_next(child)) {
168 if (default_can_add_node(child)) {
169 return TRUE;
170 }
171 }
172 } else if (PURPLE_IS_CHAT(node)) {
173 PurpleChat *chat = (PurpleChat*)node;
174 if (purple_account_is_connected(purple_chat_get_account(chat)))
175 return TRUE; /* Show whenever the account is online */
176 } else if (PURPLE_IS_GROUP(node)) {
177 PurpleBlistNode *child;
178 gboolean empty = purple_prefs_get_bool(PREF_ROOT "/emptygroups");
179 if (empty)
180 return TRUE; /* If we want to see empty groups, we can show any group */
181
182 for (child = purple_blist_node_get_first_child(node);
183 child; child = purple_blist_node_get_sibling_next(child)) {
184 if (default_can_add_node(child)) {
185 return TRUE;
186 }
187 }
188
189 if (ggblist && ggblist->new_group && g_list_find(ggblist->new_group, node))
190 return TRUE;
191 }
192
193 return FALSE;
194 }
195
196 static gpointer default_find_parent(PurpleBlistNode *node)
197 {
198 gpointer ret = NULL;
199
200 if (PURPLE_IS_BUDDY(node) || PURPLE_IS_META_CONTACT(node) || PURPLE_IS_CHAT(node))
201 ret = purple_blist_node_get_parent(node);
202
203 if (ret)
204 add_node(ret, ggblist);
205
206 return ret;
207 }
208
209 static gboolean default_create_tooltip(gpointer selected_row, GString **body, char **tool_title)
210 {
211 GString *str;
212 PurpleBlistNode *node = selected_row;
213 int lastseen = 0;
214 char *title;
215
216 str = g_string_new("");
217
218 if (PURPLE_IS_META_CONTACT(node)) {
219 PurpleBuddy *pr = purple_meta_contact_get_priority_buddy((PurpleMetaContact*)node);
220 gboolean offline = !PURPLE_BUDDY_IS_ONLINE(pr);
221 gboolean showoffline = purple_prefs_get_bool(PREF_ROOT "/showoffline");
222 const char *name = purple_buddy_get_name(pr);
223
224 title = g_strdup(name);
225 tooltip_for_buddy(pr, str, TRUE);
226 for (node = purple_blist_node_get_first_child(node); node; node = purple_blist_node_get_sibling_next(node)) {
227 PurpleBuddy *buddy = (PurpleBuddy*)node;
228 if (offline) {
229 int value = purple_blist_node_get_int(node, "last_seen");
230 if (value > lastseen)
231 lastseen = value;
232 }
233 if (node == (PurpleBlistNode*)pr)
234 continue;
235 if (!purple_account_is_connected(purple_buddy_get_account(buddy)))
236 continue;
237 if (!showoffline && !PURPLE_BUDDY_IS_ONLINE(buddy))
238 continue;
239 str = g_string_append(str, "\n----------\n");
240 tooltip_for_buddy(buddy, str, FALSE);
241 }
242 } else if (PURPLE_IS_BUDDY(node)) {
243 PurpleBuddy *buddy = (PurpleBuddy *)node;
244 tooltip_for_buddy(buddy, str, TRUE);
245 title = g_strdup(purple_buddy_get_name(buddy));
246 if (!PURPLE_BUDDY_IS_ONLINE((PurpleBuddy*)node))
247 lastseen = purple_blist_node_get_int(node, "last_seen");
248 } else if (PURPLE_IS_GROUP(node)) {
249 PurpleGroup *group = (PurpleGroup *)node;
250
251 g_string_append_printf(str, _("Online: %d\nTotal: %d"),
252 purple_counting_node_get_online_count(PURPLE_COUNTING_NODE(group)),
253 purple_counting_node_get_current_size(PURPLE_COUNTING_NODE(group)));
254
255 title = g_strdup(purple_group_get_name(group));
256 } else if (PURPLE_IS_CHAT(node)) {
257 PurpleAccount *account = NULL;
258 PurpleContactInfo *info = NULL;
259 PurpleChat *chat = NULL;
260
261 chat = PURPLE_CHAT(node);
262 account = purple_chat_get_account(chat);
263 info = PURPLE_CONTACT_INFO(account);
264
265 g_string_append_printf(str, _("Account: %s (%s)"),
266 purple_contact_info_get_username(info),
267 purple_account_get_protocol_name(account));
268
269 title = g_strdup(purple_chat_get_name(chat));
270 } else {
271 g_string_free(str, TRUE);
272 return FALSE;
273 }
274
275 if (lastseen > 0) {
276 char *tmp = purple_str_seconds_to_string(time(NULL) - lastseen);
277 g_string_append_printf(str, _("\nLast Seen: %s ago"), tmp);
278 g_free(tmp);
279 }
280
281 if (tool_title)
282 *tool_title = title;
283 else
284 g_free(title);
285
286 if (body)
287 *body = str;
288 else
289 g_string_free(str, TRUE);
290
291 return TRUE;
292 }
293
294 static FinchBlistManager default_manager =
295 {
296 "default",
297 N_("Default"),
298 NULL,
299 NULL,
300 default_can_add_node,
301 default_find_parent,
302 default_create_tooltip,
303 {NULL, NULL, NULL, NULL}
304 };
305 static GList *managers;
306
307 static void
308 finch_blist_node_free(FinchBlistNode *node) {
309 g_clear_handle_id(&node->signed_timer, g_source_remove);
310
311 g_free(node);
312 }
313
314 static FinchBlistNode *
315 create_finch_blist_node(PurpleBlistNode *node, gpointer row)
316 {
317 FinchBlistNode *fnode = g_object_get_data(G_OBJECT(node), UI_DATA);
318 if (!fnode) {
319 fnode = g_new0(FinchBlistNode, 1);
320 fnode->signed_timer = 0;
321
322 g_object_set_data_full(G_OBJECT(node), UI_DATA, fnode,
323 (GDestroyNotify)finch_blist_node_free);
324 }
325 fnode->row = row;
326 return fnode;
327 }
328
329 static int
330 get_display_color(PurpleBlistNode *node)
331 {
332 PurpleBuddy *buddy;
333 int color = 0;
334
335 if (PURPLE_IS_META_CONTACT(node))
336 node = PURPLE_BLIST_NODE(purple_meta_contact_get_priority_buddy(PURPLE_META_CONTACT(node)));
337 if (!PURPLE_IS_BUDDY(node))
338 return 0;
339
340 buddy = (PurpleBuddy*)node;
341 if (purple_presence_is_idle(purple_buddy_get_presence(buddy))) {
342 color = color_idle;
343 } else if (purple_presence_is_available(purple_buddy_get_presence(buddy))) {
344 color = color_available;
345 } else if (purple_presence_is_online(purple_buddy_get_presence(buddy)) &&
346 !purple_presence_is_available(purple_buddy_get_presence(buddy))) {
347 color = color_away;
348 } else if (!purple_presence_is_online(purple_buddy_get_presence(buddy))) {
349 color = color_offline;
350 }
351
352 return color;
353 }
354
355 static GntTextFormatFlags
356 get_blist_node_flag(FinchBuddyList *ggblist, PurpleBlistNode *node)
357 {
358 GntTextFormatFlags flag = 0;
359 FinchBlistNode *fnode = g_object_get_data(G_OBJECT(node), UI_DATA);
360
361 if (ggblist->tagged && g_list_find(ggblist->tagged, node))
362 flag |= GNT_TEXT_FLAG_BOLD;
363
364 if (fnode && fnode->signed_timer)
365 flag |= GNT_TEXT_FLAG_BLINK;
366 else if (PURPLE_IS_META_CONTACT(node)) {
367 node = PURPLE_BLIST_NODE(purple_meta_contact_get_priority_buddy(PURPLE_META_CONTACT(node)));
368 fnode = g_object_get_data(G_OBJECT(node), UI_DATA);
369 if (fnode && fnode->signed_timer)
370 flag |= GNT_TEXT_FLAG_BLINK;
371 }
372
373 return flag;
374 }
375
376 static void
377 blist_update_row_flags(FinchBuddyList *ggblist, PurpleBlistNode *node)
378 {
379 gnt_tree_set_row_flags(GNT_TREE(ggblist->tree), node,
380 get_blist_node_flag(ggblist, node));
381 gnt_tree_set_row_color(GNT_TREE(ggblist->tree), node, get_display_color(node));
382 }
383
384 static void
385 new_node(G_GNUC_UNUSED PurpleBuddyList *list,
386 G_GNUC_UNUSED PurpleBlistNode *node)
387 {
388 }
389
390 static void
391 add_node(PurpleBlistNode *node, FinchBuddyList *ggblist)
392 {
393 if(g_object_get_data(G_OBJECT(node), UI_DATA)) {
394 return;
395 }
396
397 if(!ggblist->manager->can_add_node(node)) {
398 return;
399 }
400
401 if(PURPLE_IS_BUDDY(node)) {
402 add_buddy((PurpleBuddy*)node, ggblist);
403 } else if (PURPLE_IS_META_CONTACT(node)) {
404 add_contact((PurpleMetaContact*)node, ggblist);
405 } else if (PURPLE_IS_GROUP(node)) {
406 add_group((PurpleGroup*)node, ggblist);
407 } else if (PURPLE_IS_CHAT(node)) {
408 add_chat((PurpleChat *)node, ggblist);
409 }
410
411 draw_tooltip(ggblist);
412 }
413
414 void finch_blist_manager_add_node(PurpleBlistNode *node)
415 {
416 add_node(node, ggblist);
417 }
418
419 static void
420 remove_tooltip(FinchBuddyList *ggblist)
421 {
422 gnt_widget_destroy(ggblist->tooltip);
423 ggblist->tooltip = NULL;
424 ggblist->tnode = NULL;
425 }
426
427 static void
428 node_remove(PurpleBuddyList *list, PurpleBlistNode *node)
429 {
430 FinchBuddyList *ggblist = FINCH_BUDDY_LIST(list);
431 PurpleBlistNode *parent;
432
433 if (ggblist == NULL || g_object_get_data(G_OBJECT(node), UI_DATA) == NULL)
434 return;
435
436 if (PURPLE_IS_GROUP(node) && ggblist->new_group) {
437 ggblist->new_group = g_list_remove(ggblist->new_group, node);
438 }
439
440 gnt_tree_remove(GNT_TREE(ggblist->tree), node);
441 if (ggblist->tagged)
442 ggblist->tagged = g_list_remove(ggblist->tagged, node);
443
444 parent = purple_blist_node_get_parent(node);
445 for (node = purple_blist_node_get_first_child(node); node;
446 node = purple_blist_node_get_sibling_next(node))
447 node_remove(list, node);
448
449 if (parent) {
450 if (!ggblist->manager->can_add_node(parent))
451 node_remove(list, parent);
452 else
453 node_update(list, parent);
454 }
455
456 draw_tooltip(ggblist);
457 }
458
459 static void
460 node_update(PurpleBuddyList *list, PurpleBlistNode *node)
461 {
462 FinchBuddyList *ggblist;
463
464 g_return_if_fail(FINCH_IS_BUDDY_LIST(list));
465 /* It really looks like this should never happen ... but it does.
466 This will at least emit a warning to the log when it
467 happens, so maybe someone will figure it out. */
468 g_return_if_fail(node != NULL);
469
470 ggblist = FINCH_BUDDY_LIST(list);
471 if (ggblist->window == NULL) {
472 return;
473 }
474
475 if(g_object_get_data(G_OBJECT(node), UI_DATA) != NULL) {
476 gnt_tree_change_text(GNT_TREE(ggblist->tree), node, 0,
477 get_display_name(node));
478 gnt_tree_sort_row(GNT_TREE(ggblist->tree), node);
479 blist_update_row_flags(ggblist, node);
480 if (gnt_tree_get_parent_key(GNT_TREE(ggblist->tree), node) !=
481 ggblist->manager->find_parent(node))
482 {
483 node_remove(list, node);
484 }
485 }
486
487 if (PURPLE_IS_BUDDY(node)) {
488 PurpleBuddy *buddy = (PurpleBuddy*)node;
489 add_node((PurpleBlistNode *)buddy, FINCH_BUDDY_LIST(list));
490 node_update(list, purple_blist_node_get_parent(node));
491 } else if (PURPLE_IS_CHAT(node)) {
492 add_node(node, FINCH_BUDDY_LIST(list));
493 } else if (PURPLE_IS_META_CONTACT(node)) {
494 if (g_object_get_data(G_OBJECT(node), UI_DATA) == NULL) {
495 /* The core seems to expect the UI to add the buddies. */
496 for (node = purple_blist_node_get_first_child(node); node; node = purple_blist_node_get_sibling_next(node))
497 add_node(node, FINCH_BUDDY_LIST(list));
498 }
499 } else if (PURPLE_IS_GROUP(node)) {
500 if (!ggblist->manager->can_add_node(node))
501 node_remove(list, node);
502 else
503 add_node(node, FINCH_BUDDY_LIST(list));
504 }
505 if (ggblist->tnode == node) {
506 draw_tooltip(ggblist);
507 }
508 }
509
510 static gboolean
511 remove_new_empty_group(G_GNUC_UNUSED gpointer data)
512 {
513 PurpleBuddyList *list;
514 FinchBuddyList *ggblist;
515
516 list = purple_blist_get_default();
517 g_return_val_if_fail(list, FALSE);
518 ggblist = FINCH_BUDDY_LIST(list);
519
520 ggblist->new_group_timeout = 0;
521 while (ggblist->new_group) {
522 PurpleBlistNode *group = ggblist->new_group->data;
523 ggblist->new_group = g_list_delete_link(ggblist->new_group, ggblist->new_group);
524 node_update(list, group);
525 }
526
527 return FALSE;
528 }
529
530 static void
531 add_buddy_cb(G_GNUC_UNUSED gpointer data, PurpleRequestPage *page) {
532 const char *username = purple_request_page_get_string(page, "screenname");
533 const char *alias = purple_request_page_get_string(page, "alias");
534 const char *group = purple_request_page_get_string(page, "group");
535 const char *invite = purple_request_page_get_string(page, "invite");
536 PurpleAccount *account = purple_request_page_get_account(page, "account");
537 const char *error = NULL;
538 PurpleGroup *grp;
539 PurpleBuddy *buddy;
540
541 if (!username)
542 error = _("You must provide a username for the buddy.");
543 else if (!group)
544 error = _("You must provide a group.");
545 else if (!account)
546 error = _("You must select an account.");
547 else if (!purple_account_is_connected(account))
548 error = _("The selected account is not online.");
549
550 if (error)
551 {
552 finch_request_add_buddy(purple_blist_get_default(), account,
553 username, group, alias);
554 purple_notify_error(NULL, _("Error"), _("Error adding buddy"),
555 error, purple_request_cpar_from_account(account));
556 return;
557 }
558
559 grp = purple_blist_find_group(group);
560 if (!grp)
561 {
562 grp = purple_group_new(group);
563 purple_blist_add_group(grp, NULL);
564 }
565
566 /* XXX: Ask to merge if there's already a buddy with the same alias in the same group (#4553) */
567
568 if ((buddy = purple_blist_find_buddy_in_group(account, username, grp)) == NULL)
569 {
570 buddy = purple_buddy_new(account, username, alias);
571 purple_blist_add_buddy(buddy, NULL, grp, NULL);
572 }
573
574 purple_account_add_buddy(account, buddy, invite);
575 }
576
577 static void
578 finch_request_add_buddy(G_GNUC_UNUSED PurpleBuddyList *list,
579 PurpleAccount *account, const char *username,
580 const char *grp, const char *alias)
581 {
582 PurpleRequestPage *page = purple_request_page_new();
583 PurpleRequestGroup *group = purple_request_group_new(NULL);
584 PurpleRequestField *field;
585
586 purple_request_page_add_group(page, group);
587
588 field = purple_request_field_string_new("screenname", _("Username"), username, FALSE);
589 purple_request_group_add_field(group, field);
590
591 field = purple_request_field_string_new("alias", _("Alias (optional)"), alias, FALSE);
592 purple_request_group_add_field(group, field);
593
594 field = purple_request_field_string_new("invite", _("Invite message (optional)"), NULL, FALSE);
595 purple_request_group_add_field(group, field);
596
597 field = purple_request_field_string_new("group", _("Add in group"), grp, FALSE);
598 purple_request_group_add_field(group, field);
599 purple_request_field_set_type_hint(field, "group");
600
601 field = purple_request_field_account_new("account", _("Account"), NULL);
602 purple_request_field_account_set_show_all(PURPLE_REQUEST_FIELD_ACCOUNT(field),
603 FALSE);
604 if(account) {
605 purple_request_field_account_set_value(PURPLE_REQUEST_FIELD_ACCOUNT(field),
606 account);
607 }
608 purple_request_group_add_field(group, field);
609
610 purple_request_fields(NULL, _("Add Buddy"), NULL, _("Please enter buddy information."),
611 page,
612 _("Add"), G_CALLBACK(add_buddy_cb),
613 _("Cancel"), NULL,
614 purple_request_cpar_from_account(account),
615 NULL);
616 }
617
618 static void
619 join_chat(PurpleChat *chat)
620 {
621 PurpleAccount *account = purple_chat_get_account(chat);
622 PurpleConversationManager *manager;
623 const char *name;
624 PurpleConversation *conv;
625
626 name = purple_chat_get_name_only(chat);
627 manager = purple_conversation_manager_get_default();
628 conv = purple_conversation_manager_find_chat(manager, account, name);
629
630 if (!conv || purple_chat_conversation_has_left(PURPLE_CHAT_CONVERSATION(conv))) {
631 purple_serv_join_chat(purple_account_get_connection(account),
632 purple_chat_get_components(chat));
633 } else if (conv) {
634 purple_conversation_present(conv);
635 }
636 }
637
638 static void
639 add_chat_cb(G_GNUC_UNUSED gpointer data, PurpleRequestPage *page) {
640 PurpleAccount *account;
641 const char *alias, *name, *group;
642 PurpleChat *chat;
643 PurpleGroup *grp;
644 GHashTable *hash = NULL;
645 PurpleConnection *gc;
646 gboolean autojoin;
647 PurpleProtocol *protocol;
648
649 account = purple_request_page_get_account(page, "account");
650 name = purple_request_page_get_string(page, "name");
651 alias = purple_request_page_get_string(page, "alias");
652 group = purple_request_page_get_string(page, "group");
653 autojoin = purple_request_page_get_bool(page, "autojoin");
654
655 if (!purple_account_is_connected(account) || !name || !*name)
656 return;
657
658 if (!group || !*group)
659 group = _("Chats");
660
661 gc = purple_account_get_connection(account);
662 protocol = purple_connection_get_protocol(gc);
663 hash = purple_protocol_chat_info_defaults(PURPLE_PROTOCOL_CHAT(protocol), gc, name);
664
665 chat = purple_chat_new(account, name, hash);
666
667 if (chat != NULL) {
668 if ((grp = purple_blist_find_group(group)) == NULL) {
669 grp = purple_group_new(group);
670 purple_blist_add_group(grp, NULL);
671 }
672 purple_blist_add_chat(chat, grp, NULL);
673 purple_chat_set_alias(chat, alias);
674 purple_blist_node_set_bool((PurpleBlistNode*)chat, "gnt-autojoin", autojoin);
675 if (autojoin) {
676 join_chat(chat);
677 }
678 }
679 }
680
681 static void
682 finch_request_add_chat(G_GNUC_UNUSED PurpleBuddyList *list,
683 PurpleAccount *account, PurpleGroup *grp,
684 const char *alias, const char *name)
685 {
686 PurpleRequestPage *page = purple_request_page_new();
687 PurpleRequestGroup *group = purple_request_group_new(NULL);
688 PurpleRequestField *field;
689
690 purple_request_page_add_group(page, group);
691
692 field = purple_request_field_account_new("account", _("Account"), NULL);
693 purple_request_field_account_set_show_all(PURPLE_REQUEST_FIELD_ACCOUNT(field),
694 FALSE);
695 if(account) {
696 purple_request_field_account_set_value(PURPLE_REQUEST_FIELD_ACCOUNT(field),
697 account);
698 }
699 purple_request_group_add_field(group, field);
700
701 field = purple_request_field_string_new("name", _("Name"), name, FALSE);
702 purple_request_group_add_field(group, field);
703
704 field = purple_request_field_string_new("alias", _("Alias"), alias, FALSE);
705 purple_request_group_add_field(group, field);
706
707 field = purple_request_field_string_new("group", _("Group"), grp ? purple_group_get_name(grp) : NULL, FALSE);
708 purple_request_group_add_field(group, field);
709 purple_request_field_set_type_hint(field, "group");
710
711 field = purple_request_field_bool_new("autojoin", _("Auto-join"), FALSE);
712 purple_request_group_add_field(group, field);
713
714 purple_request_fields(NULL, _("Add Chat"), NULL,
715 _("You can edit more information from the context menu later."),
716 page, _("Add"), G_CALLBACK(add_chat_cb), _("Cancel"), NULL,
717 NULL, NULL);
718 }
719
720 static void
721 add_group_cb(FinchBuddyList *ggblist, const char *group)
722 {
723 PurpleGroup *grp;
724
725 if (!group || !*group) {
726 purple_notify_error(NULL, _("Error"), _("Error adding group"),
727 _("You must give a name for the group to add."), NULL);
728 g_object_unref(ggblist);
729 return;
730 }
731
732 grp = purple_blist_find_group(group);
733 if (!grp) {
734 grp = purple_group_new(group);
735 purple_blist_add_group(grp, NULL);
736 }
737
738 /* Treat the group as a new group even if it had existed before. This should
739 * make things easier to add buddies to empty groups (new or old) without having
740 * to turn on 'show empty groups' setting */
741 ggblist->new_group = g_list_prepend(ggblist->new_group, grp);
742 g_clear_handle_id(&ggblist->new_group_timeout, g_source_remove);
743 ggblist->new_group_timeout = g_timeout_add_seconds(SHOW_EMPTY_GROUP_TIMEOUT,
744 remove_new_empty_group, NULL);
745
746 /* Select the group */
747 if (ggblist->tree) {
748 FinchBlistNode *fnode = g_object_get_data(G_OBJECT(grp), UI_DATA);
749 if (!fnode)
750 add_node((PurpleBlistNode*)grp, ggblist);
751 gnt_tree_set_selected(GNT_TREE(ggblist->tree), grp);
752 }
753
754 g_object_unref(ggblist);
755 }
756
757 static void
758 finch_request_add_group(PurpleBuddyList *list)
759 {
760 purple_request_input(NULL, _("Add Group"), NULL,
761 _("Enter the name of the group"), NULL, FALSE,
762 FALSE, NULL, _("Add"), G_CALLBACK(add_group_cb),
763 _("Cancel"), G_CALLBACK(g_object_unref), NULL,
764 g_object_ref(list));
765 }
766
767 static gpointer
768 finch_blist_get_handle(void)
769 {
770 static int handle;
771
772 return &handle;
773 }
774
775 static void
776 add_group(PurpleGroup *group, FinchBuddyList *ggblist)
777 {
778 gpointer parent;
779 PurpleBlistNode *node = (PurpleBlistNode *)group;
780 if(g_object_get_data(G_OBJECT(node), UI_DATA)) {
781 return;
782 }
783 parent = ggblist->manager->find_parent((PurpleBlistNode*)group);
784 create_finch_blist_node(node, gnt_tree_add_row_after(GNT_TREE(ggblist->tree), group,
785 gnt_tree_create_row(GNT_TREE(ggblist->tree), get_display_name(node)),
786 parent, NULL));
787 gnt_tree_set_expanded(GNT_TREE(ggblist->tree), node,
788 !purple_blist_node_get_bool(node, "collapsed"));
789 }
790
791 static const char *
792 get_display_name(PurpleBlistNode *node)
793 {
794 static char text[2096];
795 char status[8] = " ";
796 const char *name = NULL;
797
798 if (PURPLE_IS_META_CONTACT(node))
799 node = PURPLE_BLIST_NODE(purple_meta_contact_get_priority_buddy(PURPLE_META_CONTACT(node))); /* XXX: this can return NULL?! */
800
801 if (node == NULL)
802 return NULL;
803
804 if (PURPLE_IS_BUDDY(node))
805 {
806 PurpleBuddy *buddy = (PurpleBuddy *)node;
807 PurpleStatusPrimitive prim;
808 PurplePresence *presence;
809 PurpleStatus *now;
810 gboolean ascii = gnt_ascii_only();
811
812 presence = purple_buddy_get_presence(buddy);
813 if (purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_MOBILE))
814 strncpy(status, ascii ? ":" : "☎", sizeof(status) - 1);
815 else {
816 now = purple_presence_get_active_status(presence);
817
818 prim = purple_status_type_get_primitive(purple_status_get_status_type(now));
819
820 switch(prim) {
821 case PURPLE_STATUS_OFFLINE:
822 strncpy(status, ascii ? "x" : "⊗", sizeof(status) - 1);
823 break;
824 case PURPLE_STATUS_AVAILABLE:
825 strncpy(status, ascii ? "o" : "â—¯", sizeof(status) - 1);
826 break;
827 default:
828 strncpy(status, ascii ? "." : "⊖", sizeof(status) - 1);
829 break;
830 }
831 }
832 name = purple_buddy_get_alias(buddy);
833 }
834 else if (PURPLE_IS_CHAT(node))
835 {
836 PurpleChat *chat = (PurpleChat*)node;
837 name = purple_chat_get_name(chat);
838
839 strncpy(status, "~", sizeof(status) - 1);
840 }
841 else if (PURPLE_IS_GROUP(node))
842 return purple_group_get_name((PurpleGroup*)node);
843
844 g_snprintf(text, sizeof(text) - 1, "%s %s", status, name);
845
846 return text;
847 }
848
849 static void
850 add_chat(PurpleChat *chat, FinchBuddyList *ggblist)
851 {
852 gpointer parent;
853 PurpleBlistNode *node = (PurpleBlistNode *)chat;
854 if(g_object_get_data(G_OBJECT(node), UI_DATA)) {
855 return;
856 }
857 if(!purple_account_is_connected(purple_chat_get_account(chat))) {
858 return;
859 }
860
861 parent = ggblist->manager->find_parent((PurpleBlistNode*)chat);
862
863 create_finch_blist_node(node, gnt_tree_add_row_after(GNT_TREE(ggblist->tree), chat,
864 gnt_tree_create_row(GNT_TREE(ggblist->tree), get_display_name(node)),
865 parent, NULL));
866 }
867
868 static void
869 add_contact(PurpleMetaContact *contact, FinchBuddyList *ggblist)
870 {
871 gpointer parent;
872 PurpleBlistNode *node = (PurpleBlistNode*)contact;
873 const char *name;
874
875 if(g_object_get_data(G_OBJECT(node), UI_DATA)) {
876 return;
877 }
878
879 name = get_display_name(node);
880 if (name == NULL)
881 return;
882
883 parent = ggblist->manager->find_parent((PurpleBlistNode*)contact);
884
885 create_finch_blist_node(node, gnt_tree_add_row_after(GNT_TREE(ggblist->tree), contact,
886 gnt_tree_create_row(GNT_TREE(ggblist->tree), name),
887 parent, NULL));
888
889 gnt_tree_set_expanded(GNT_TREE(ggblist->tree), contact, FALSE);
890 }
891
892 static void
893 add_buddy(PurpleBuddy *buddy, FinchBuddyList *ggblist)
894 {
895 gpointer parent;
896 PurpleBlistNode *node = (PurpleBlistNode *)buddy;
897 PurpleMetaContact *contact;
898
899 if(g_object_get_data(G_OBJECT(node), UI_DATA)) {
900 return;
901 }
902
903 contact = purple_buddy_get_contact(buddy);
904 parent = ggblist->manager->find_parent((PurpleBlistNode*)buddy);
905
906 create_finch_blist_node(node, gnt_tree_add_row_after(GNT_TREE(ggblist->tree), buddy,
907 gnt_tree_create_row(GNT_TREE(ggblist->tree), get_display_name(node)),
908 parent, NULL));
909
910 blist_update_row_flags(ggblist, (PurpleBlistNode *)buddy);
911 if (buddy == purple_meta_contact_get_priority_buddy(contact)) {
912 blist_update_row_flags(ggblist, (PurpleBlistNode *)contact);
913 }
914 }
915
916 static void
917 selection_activate(G_GNUC_UNUSED GntWidget *widget, FinchBuddyList *ggblist)
918 {
919 GntTree *tree = GNT_TREE(ggblist->tree);
920 PurpleBlistNode *node = gnt_tree_get_selection_data(tree);
921
922 if (!node)
923 return;
924
925 if (PURPLE_IS_META_CONTACT(node))
926 node = PURPLE_BLIST_NODE(purple_meta_contact_get_priority_buddy(PURPLE_META_CONTACT(node)));
927
928 if (PURPLE_IS_BUDDY(node))
929 {
930 PurpleBuddy *buddy = (PurpleBuddy *)node;
931 PurpleConversation *im;
932 PurpleConversationManager *manager;
933
934 manager = purple_conversation_manager_get_default();
935 im = purple_conversation_manager_find_im(manager,
936 purple_buddy_get_account(buddy),
937 purple_buddy_get_name(buddy));
938
939 if(!PURPLE_IS_IM_CONVERSATION(im)) {
940 im = purple_im_conversation_new(purple_buddy_get_account(buddy),
941 purple_buddy_get_name(buddy));
942 } else {
943 FinchConv *ggconv = FINCH_CONV(im);
944 gnt_window_present(ggconv->window);
945 }
946 finch_conversation_set_active(im);
947 }
948 else if (PURPLE_IS_CHAT(node))
949 {
950 join_chat((PurpleChat*)node);
951 }
952 }
953
954 static void
955 append_proto_menu(GntMenu *menu, PurpleConnection *gc, PurpleBlistNode *node)
956 {
957 GList *list;
958 PurpleProtocol *protocol = purple_connection_get_protocol(gc);
959
960 if (!PURPLE_PROTOCOL_IMPLEMENTS(protocol, CLIENT, blist_node_menu)) {
961 return;
962 }
963
964 for(list = purple_protocol_client_blist_node_menu(PURPLE_PROTOCOL_CLIENT(protocol), node);
965 list; list = g_list_delete_link(list, list))
966 {
967 PurpleActionMenu *act = (PurpleActionMenu *) list->data;
968 if (!act)
969 continue;
970 purple_action_menu_set_data(act, node);
971 finch_append_menu_action(menu, act, node);
972 }
973 }
974
975 static void
976 add_custom_action(GntMenu *menu, const char *label, GCallback callback,
977 gpointer data)
978 {
979 PurpleActionMenu *action = purple_action_menu_new(label, callback, data, NULL);
980 finch_append_menu_action(menu, action, NULL);
981 }
982
983 static void
984 chat_components_edit_ok(PurpleChat *chat, PurpleRequestPage *page) {
985 guint n_groups;
986
987 n_groups = g_list_model_get_n_items(G_LIST_MODEL(page));
988 for(guint group_index = 0; group_index < n_groups; group_index++) {
989 GListModel *group = NULL;
990 guint n_fields = 0;
991
992 group = g_list_model_get_item(G_LIST_MODEL(page), group_index);
993 n_fields = g_list_model_get_n_items(group);
994 for(guint field_index = 0; field_index < n_fields; field_index++) {
995 PurpleRequestField *field = NULL;
996 const char *id;
997 char *val;
998
999 field = g_list_model_get_item(group, field_index);
1000 id = purple_request_field_get_id(field);
1001 if(PURPLE_IS_REQUEST_FIELD_INT(field)) {
1002 PurpleRequestFieldInt *ifield = PURPLE_REQUEST_FIELD_INT(field);
1003 val = g_strdup_printf("%d",
1004 purple_request_field_int_get_value(ifield));
1005 } else {
1006 val = g_strdup(purple_request_field_string_get_value(PURPLE_REQUEST_FIELD_STRING(field)));
1007 }
1008
1009 if (!val) {
1010 g_hash_table_remove(purple_chat_get_components(chat), id);
1011 } else {
1012 g_hash_table_replace(purple_chat_get_components(chat), g_strdup(id), val); /* val should not be free'd */
1013 }
1014
1015 g_object_unref(field);
1016 }
1017
1018 g_object_unref(group);
1019 }
1020 }
1021
1022 static void
1023 chat_components_edit(G_GNUC_UNUSED PurpleBlistNode *selected, PurpleChat *chat)
1024 {
1025 PurpleRequestPage *page = purple_request_page_new();
1026 PurpleRequestGroup *group = purple_request_group_new(NULL);
1027 PurpleRequestField *field;
1028 GList *parts, *iter;
1029 PurpleProtocol *protocol;
1030 PurpleProtocolChatEntry *pce;
1031 PurpleConnection *gc;
1032
1033 purple_request_page_add_group(page, group);
1034
1035 gc = purple_account_get_connection(purple_chat_get_account(chat));
1036 protocol = purple_connection_get_protocol(gc);
1037 parts = purple_protocol_chat_info(PURPLE_PROTOCOL_CHAT(protocol), gc);
1038
1039 for (iter = parts; iter; iter = iter->next) {
1040 pce = iter->data;
1041 if (pce->is_int) {
1042 int val;
1043 const char *str = g_hash_table_lookup(purple_chat_get_components(chat), pce->identifier);
1044 if (!str || sscanf(str, "%d", &val) != 1)
1045 val = pce->min;
1046 field = purple_request_field_int_new(pce->identifier, pce->label, val, INT_MIN, INT_MAX);
1047 } else {
1048 field = purple_request_field_string_new(pce->identifier, pce->label,
1049 g_hash_table_lookup(purple_chat_get_components(chat), pce->identifier), FALSE);
1050 if(pce->secret) {
1051 purple_request_field_string_set_masked(PURPLE_REQUEST_FIELD_STRING(field),
1052 TRUE);
1053 }
1054 }
1055
1056 if (pce->required)
1057 purple_request_field_set_required(field, TRUE);
1058
1059 purple_request_group_add_field(group, field);
1060 g_free(pce);
1061 }
1062
1063 g_list_free(parts);
1064
1065 purple_request_fields(NULL, _("Edit Chat"), NULL, _("Please Update the necessary fields."),
1066 page, _("Edit"), G_CALLBACK(chat_components_edit_ok), _("Cancel"), NULL,
1067 NULL, chat);
1068 }
1069
1070 static void
1071 autojoin_toggled(GntMenuItem *item, gpointer data)
1072 {
1073 PurpleActionMenu *action = data;
1074 purple_blist_node_set_bool(purple_action_menu_get_data(action), "gnt-autojoin",
1075 gnt_menuitem_check_get_checked(GNT_MENU_ITEM_CHECK(item)));
1076 }
1077
1078 static void
1079 create_chat_menu(GntMenu *menu, PurpleChat *chat)
1080 {
1081 PurpleActionMenu *action = purple_action_menu_new(_("Auto-join"), NULL, chat, NULL);
1082 GntMenuItem *check = gnt_menuitem_check_new(
1083 purple_action_menu_get_label(action));
1084 gnt_menuitem_check_set_checked(GNT_MENU_ITEM_CHECK(check),
1085 purple_blist_node_get_bool((PurpleBlistNode*)chat, "gnt-autojoin"));
1086 gnt_menu_add_item(menu, check);
1087 gnt_menuitem_set_callback(check, autojoin_toggled, action);
1088 g_signal_connect_swapped(G_OBJECT(menu), "destroy",
1089 G_CALLBACK(purple_action_menu_free), action);
1090
1091 /* Protocol actions */
1092 append_proto_menu(menu,
1093 purple_account_get_connection(purple_chat_get_account(chat)),
1094 (PurpleBlistNode*)chat);
1095
1096 add_custom_action(menu, _("Edit Settings"), (GCallback)chat_components_edit, chat);
1097 }
1098
1099 static void
1100 finch_add_buddy(G_GNUC_UNUSED PurpleBlistNode *selected, PurpleGroup *grp)
1101 {
1102 purple_blist_request_add_buddy(NULL, NULL, grp ? purple_group_get_name(grp) : NULL, NULL);
1103 }
1104
1105 static void
1106 finch_add_group(G_GNUC_UNUSED PurpleBlistNode *selected,
1107 G_GNUC_UNUSED PurpleGroup *grp)
1108 {
1109 purple_blist_request_add_group();
1110 }
1111
1112 static void
1113 finch_add_chat(G_GNUC_UNUSED PurpleBlistNode *selected, PurpleGroup *grp)
1114 {
1115 purple_blist_request_add_chat(NULL, grp, NULL, NULL);
1116 }
1117
1118 static void
1119 create_group_menu(GntMenu *menu, PurpleGroup *group)
1120 {
1121 add_custom_action(menu, _("Add Buddy"),
1122 G_CALLBACK(finch_add_buddy), group);
1123 add_custom_action(menu, _("Add Chat"),
1124 G_CALLBACK(finch_add_chat), group);
1125 add_custom_action(menu, _("Add Group"),
1126 G_CALLBACK(finch_add_group), group);
1127 }
1128
1129 gpointer finch_retrieve_user_info(PurpleConnection *conn, const char *name)
1130 {
1131 PurpleProtocol *protocol = NULL;
1132 PurpleNotifyUserInfo *info = NULL;
1133 gpointer uihandle;
1134
1135 protocol = purple_connection_get_protocol(conn);
1136
1137 if(!PURPLE_IS_PROTOCOL_SERVER(protocol)) {
1138 return NULL;
1139 }
1140
1141 purple_protocol_server_get_info(PURPLE_PROTOCOL_SERVER(protocol), conn,
1142 name);
1143
1144 info = purple_notify_user_info_new();
1145 purple_notify_user_info_add_pair_plaintext(info, _("Information"), _("Retrieving..."));
1146 uihandle = purple_notify_userinfo(conn, name, info, NULL, NULL);
1147 purple_notify_user_info_destroy(info);
1148
1149 return uihandle;
1150 }
1151
1152 static void
1153 finch_blist_get_buddy_info_cb(G_GNUC_UNUSED PurpleBlistNode *selected,
1154 PurpleBuddy *buddy)
1155 {
1156 finch_retrieve_user_info(purple_account_get_connection(purple_buddy_get_account(buddy)), purple_buddy_get_name(buddy));
1157 }
1158
1159 static void
1160 finch_blist_menu_send_file_cb(G_GNUC_UNUSED PurpleBlistNode *selected,
1161 PurpleBuddy *buddy)
1162 {
1163 purple_serv_send_file(purple_account_get_connection(purple_buddy_get_account(buddy)), purple_buddy_get_name(buddy), NULL);
1164 }
1165
1166 static void
1167 toggle_show_offline(G_GNUC_UNUSED GntMenuItem *item, gpointer buddy)
1168 {
1169 purple_blist_node_set_bool(buddy, "show_offline",
1170 !purple_blist_node_get_bool(buddy, "show_offline"));
1171 if (!ggblist->manager->can_add_node(buddy))
1172 node_remove(purple_blist_get_default(), buddy);
1173 else
1174 node_update(purple_blist_get_default(), buddy);
1175 }
1176
1177 static void
1178 create_buddy_menu(GntMenu *menu, PurpleBuddy *buddy)
1179 {
1180 GntMenuItem *item;
1181 PurpleProtocol *protocol;
1182 PurpleConnection *gc = purple_account_get_connection(purple_buddy_get_account(buddy));
1183
1184 protocol = purple_connection_get_protocol(gc);
1185 if (protocol && PURPLE_PROTOCOL_IMPLEMENTS(protocol, SERVER, get_info))
1186 {
1187 add_custom_action(menu, _("Get Info"),
1188 G_CALLBACK(finch_blist_get_buddy_info_cb), buddy);
1189 }
1190
1191 if (PURPLE_IS_PROTOCOL_XFER(protocol))
1192 {
1193 if (purple_protocol_xfer_can_receive(
1194 PURPLE_PROTOCOL_XFER(protocol),
1195 gc,
1196 purple_buddy_get_name(buddy))
1197 ) {
1198 add_custom_action(menu, _("Send File"),
1199 G_CALLBACK(finch_blist_menu_send_file_cb), buddy);
1200 }
1201 }
1202
1203 item = gnt_menuitem_check_new(_("Show when offline"));
1204 gnt_menuitem_check_set_checked(GNT_MENU_ITEM_CHECK(item), purple_blist_node_get_bool((PurpleBlistNode*)buddy, "show_offline"));
1205 gnt_menuitem_set_callback(item, toggle_show_offline, buddy);
1206 gnt_menu_add_item(menu, item);
1207
1208 /* Protocol actions */
1209 append_proto_menu(menu,
1210 purple_account_get_connection(purple_buddy_get_account(buddy)),
1211 (PurpleBlistNode*)buddy);
1212 }
1213
1214 static void
1215 append_extended_menu(GntMenu *menu, PurpleBlistNode *node)
1216 {
1217 GList *iter;
1218
1219 for (iter = purple_blist_node_get_extended_menu(node);
1220 iter; iter = g_list_delete_link(iter, iter))
1221 {
1222 finch_append_menu_action(menu, iter->data, node);
1223 }
1224 }
1225
1226 /* Xerox'd from gtkdialogs.c:purple_gtkdialogs_remove_contact_cb */
1227 static void
1228 remove_contact(PurpleMetaContact *contact)
1229 {
1230 PurpleBlistNode *bnode, *cnode;
1231 PurpleGroup *group;
1232
1233 cnode = (PurpleBlistNode *)contact;
1234 group = (PurpleGroup*)purple_blist_node_get_parent(cnode);
1235 for (bnode = purple_blist_node_get_first_child(cnode); bnode; bnode = purple_blist_node_get_sibling_next(bnode)) {
1236 PurpleBuddy *buddy = (PurpleBuddy*)bnode;
1237 PurpleAccount *account = purple_buddy_get_account(buddy);
1238 if (purple_account_is_connected(account))
1239 purple_account_remove_buddy(account, buddy, group);
1240 }
1241 purple_blist_remove_contact(contact);
1242 }
1243
1244 static void
1245 rename_blist_node(PurpleBlistNode *node, const char *newname)
1246 {
1247 const char *name = newname;
1248 if (name && !*name)
1249 name = NULL;
1250
1251 if (PURPLE_IS_META_CONTACT(node)) {
1252 PurpleMetaContact *contact = (PurpleMetaContact*)node;
1253 PurpleBuddy *buddy = purple_meta_contact_get_priority_buddy(contact);
1254 purple_meta_contact_set_alias(contact, name);
1255 purple_buddy_set_local_alias(buddy, name);
1256 purple_serv_alias_buddy(buddy);
1257 } else if (PURPLE_IS_BUDDY(node)) {
1258 purple_buddy_set_local_alias((PurpleBuddy*)node, name);
1259 purple_serv_alias_buddy((PurpleBuddy*)node);
1260 } else if (PURPLE_IS_CHAT(node))
1261 purple_chat_set_alias((PurpleChat*)node, name);
1262 else if (PURPLE_IS_GROUP(node) && (name != NULL))
1263 purple_group_set_name((PurpleGroup*)node, name);
1264 else
1265 g_return_if_reached();
1266 }
1267
1268 static void
1269 finch_blist_rename_node_cb(G_GNUC_UNUSED PurpleBlistNode *selected,
1270 PurpleBlistNode *node)
1271 {
1272 const char *name = NULL;
1273 char *prompt;
1274 const char *text;
1275
1276 if (PURPLE_IS_META_CONTACT(node))
1277 name = purple_meta_contact_get_alias((PurpleMetaContact*)node);
1278 else if (PURPLE_IS_BUDDY(node))
1279 name = purple_buddy_get_contact_alias((PurpleBuddy*)node);
1280 else if (PURPLE_IS_CHAT(node))
1281 name = purple_chat_get_name((PurpleChat*)node);
1282 else if (PURPLE_IS_GROUP(node))
1283 name = purple_group_get_name((PurpleGroup*)node);
1284 else
1285 g_return_if_reached();
1286
1287 prompt = g_strdup_printf(_("Please enter the new name for %s"), name);
1288
1289 text = PURPLE_IS_GROUP(node) ? _("Rename") : _("Set Alias");
1290 purple_request_input(node, text, prompt, _("Enter empty string to reset the name."),
1291 name, FALSE, FALSE, NULL, text, G_CALLBACK(rename_blist_node),
1292 _("Cancel"), NULL,
1293 NULL, node);
1294
1295 g_free(prompt);
1296 }
1297
1298 /* Xeroxed from gtkdialogs.c:purple_gtkdialogs_remove_group_cb*/
1299 static void
1300 remove_group(PurpleGroup *group)
1301 {
1302 PurpleBlistNode *cnode, *bnode;
1303
1304 cnode = purple_blist_node_get_first_child(((PurpleBlistNode*)group));
1305
1306 while (cnode) {
1307 if (PURPLE_IS_META_CONTACT(cnode)) {
1308 bnode = purple_blist_node_get_first_child(cnode);
1309 cnode = purple_blist_node_get_sibling_next(cnode);
1310 while (bnode) {
1311 PurpleBuddy *buddy;
1312 if (PURPLE_IS_BUDDY(bnode)) {
1313 PurpleAccount *account;
1314 buddy = (PurpleBuddy*)bnode;
1315 bnode = purple_blist_node_get_sibling_next(bnode);
1316 account = purple_buddy_get_account(buddy);
1317 if (purple_account_is_connected(account)) {
1318 purple_account_remove_buddy(account, buddy, group);
1319 purple_blist_remove_buddy(buddy);
1320 }
1321 } else {
1322 bnode = purple_blist_node_get_sibling_next(bnode);
1323 }
1324 }
1325 } else if (PURPLE_IS_CHAT(cnode)) {
1326 PurpleChat *chat = (PurpleChat *)cnode;
1327 cnode = purple_blist_node_get_sibling_next(cnode);
1328 if (purple_account_is_connected(purple_chat_get_account(chat)))
1329 purple_blist_remove_chat(chat);
1330 } else {
1331 cnode = purple_blist_node_get_sibling_next(cnode);
1332 }
1333 }
1334
1335 purple_blist_remove_group(group);
1336 }
1337
1338 static void
1339 finch_blist_remove_node(PurpleBlistNode *node)
1340 {
1341 if (PURPLE_IS_META_CONTACT(node)) {
1342 remove_contact((PurpleMetaContact*)node);
1343 } else if (PURPLE_IS_BUDDY(node)) {
1344 PurpleBuddy *buddy = (PurpleBuddy*)node;
1345 PurpleGroup *group = purple_buddy_get_group(buddy);
1346 purple_account_remove_buddy(purple_buddy_get_account(buddy), buddy, group);
1347 purple_blist_remove_buddy(buddy);
1348 } else if (PURPLE_IS_CHAT(node)) {
1349 purple_blist_remove_chat((PurpleChat*)node);
1350 } else if (PURPLE_IS_GROUP(node)) {
1351 remove_group((PurpleGroup*)node);
1352 }
1353 }
1354
1355 static void
1356 finch_blist_remove_node_cb(G_GNUC_UNUSED PurpleBlistNode *selected,
1357 PurpleBlistNode *node)
1358 {
1359 PurpleAccount *account = NULL;
1360 char *primary;
1361 const char *name, *sec = NULL;
1362
1363 if (PURPLE_IS_META_CONTACT(node)) {
1364 PurpleMetaContact *c = (PurpleMetaContact*)node;
1365 name = purple_meta_contact_get_alias(c);
1366 if (purple_counting_node_get_total_size(PURPLE_COUNTING_NODE(c)) > 1)
1367 sec = _("Removing this contact will also remove all the buddies in the contact");
1368 } else if (PURPLE_IS_BUDDY(node)) {
1369 name = purple_buddy_get_name((PurpleBuddy*)node);
1370 account = purple_buddy_get_account((PurpleBuddy*)node);
1371 } else if (PURPLE_IS_CHAT(node)) {
1372 name = purple_chat_get_name((PurpleChat*)node);
1373 } else if (PURPLE_IS_GROUP(node)) {
1374 name = purple_group_get_name((PurpleGroup*)node);
1375 sec = _("Removing this group will also remove all the buddies in the group");
1376 }
1377 else
1378 return;
1379
1380 primary = g_strdup_printf(_("Are you sure you want to remove %s?"), name);
1381
1382 /* XXX: anything to do with the returned ui-handle? */
1383 purple_request_action(node, _("Confirm Remove"),
1384 primary, sec,
1385 1,
1386 purple_request_cpar_from_account(account),
1387 node, 2,
1388 _("Remove"), finch_blist_remove_node,
1389 _("Cancel"), NULL);
1390 g_free(primary);
1391 }
1392
1393 static void
1394 finch_blist_toggle_tag_buddy(PurpleBlistNode *node)
1395 {
1396 GList *iter;
1397 if (node == NULL)
1398 return;
1399 if (ggblist->tagged && (iter = g_list_find(ggblist->tagged, node)) != NULL) {
1400 ggblist->tagged = g_list_delete_link(ggblist->tagged, iter);
1401 } else {
1402 ggblist->tagged = g_list_prepend(ggblist->tagged, node);
1403 }
1404 if (PURPLE_IS_META_CONTACT(node))
1405 update_buddy_display(purple_meta_contact_get_priority_buddy(PURPLE_META_CONTACT(node)), ggblist);
1406 else if (PURPLE_IS_BUDDY(node))
1407 update_buddy_display((PurpleBuddy*)node, ggblist);
1408 else
1409 update_node_display(node, ggblist);
1410 }
1411
1412 static void
1413 finch_blist_place_tagged(PurpleBlistNode *target)
1414 {
1415 PurpleGroup *tg = NULL;
1416 PurpleMetaContact *tc = NULL;
1417
1418 if (PURPLE_IS_GROUP(target))
1419 tg = (PurpleGroup*)target;
1420 else if (PURPLE_IS_BUDDY(target)) {
1421 tc = (PurpleMetaContact*)purple_blist_node_get_parent(target);
1422 tg = (PurpleGroup*)purple_blist_node_get_parent((PurpleBlistNode*)tc);
1423 } else if (PURPLE_IS_META_CONTACT(target)) {
1424 tc = (PurpleMetaContact *)target;
1425 tg = (PurpleGroup *)purple_blist_node_get_parent(target);
1426 } else if (PURPLE_IS_CHAT(target)) {
1427 tg = (PurpleGroup*)purple_blist_node_get_parent(target);
1428 } else {
1429 return;
1430 }
1431
1432 if (ggblist->tagged) {
1433 GList *list = ggblist->tagged;
1434 ggblist->tagged = NULL;
1435 while (list) {
1436 PurpleBlistNode *node = list->data;
1437 list = g_list_delete_link(list, list);
1438
1439 if (PURPLE_IS_GROUP(node)) {
1440 update_node_display(node, ggblist);
1441 /* Add the group after the current group */
1442 purple_blist_add_group((PurpleGroup*)node, (PurpleBlistNode*)tg);
1443 } else if (PURPLE_IS_META_CONTACT(node)) {
1444 update_buddy_display(purple_meta_contact_get_priority_buddy((PurpleMetaContact*)node), ggblist);
1445 if (PURPLE_BLIST_NODE(tg) == target) {
1446 /* The target is a group, just add the contact to the group. */
1447 purple_blist_add_contact((PurpleMetaContact*)node, tg, NULL);
1448 } else if (tc) {
1449 /* The target is either a buddy, or a contact. Merge with that contact. */
1450 purple_meta_contact_merge((PurpleMetaContact*)node, (PurpleBlistNode*)tc);
1451 } else {
1452 /* The target is a chat. Add the contact to the group after this chat. */
1453 purple_blist_add_contact((PurpleMetaContact*)node, NULL, target);
1454 }
1455 } else if (PURPLE_IS_BUDDY(node)) {
1456 update_buddy_display((PurpleBuddy*)node, ggblist);
1457 if (PURPLE_BLIST_NODE(tg) == target) {
1458 /* The target is a group. Add this buddy in a new contact under this group. */
1459 purple_blist_add_buddy((PurpleBuddy*)node, NULL, tg, NULL);
1460 } else if (PURPLE_IS_META_CONTACT(target)) {
1461 /* Add to the contact. */
1462 purple_blist_add_buddy((PurpleBuddy*)node, tc, NULL, NULL);
1463 } else if (PURPLE_IS_BUDDY(target)) {
1464 /* Add to the contact after the selected buddy. */
1465 purple_blist_add_buddy((PurpleBuddy*)node, NULL, NULL, target);
1466 } else if (PURPLE_IS_CHAT(target)) {
1467 /* Add to the selected chat's group. */
1468 purple_blist_add_buddy((PurpleBuddy*)node, NULL, tg, NULL);
1469 }
1470 } else if (PURPLE_IS_CHAT(node)) {
1471 update_node_display(node, ggblist);
1472 if (PURPLE_BLIST_NODE(tg) == target)
1473 purple_blist_add_chat((PurpleChat*)node, tg, NULL);
1474 else
1475 purple_blist_add_chat((PurpleChat*)node, NULL, target);
1476 }
1477 }
1478 }
1479 }
1480
1481 static void
1482 context_menu_destroyed(G_GNUC_UNUSED GntWidget *widget,
1483 FinchBuddyList *ggblist)
1484 {
1485 ggblist->context = NULL;
1486 }
1487
1488 static void
1489 draw_context_menu(FinchBuddyList *ggblist)
1490 {
1491 PurpleBlistNode *node = NULL;
1492 GntWidget *context = NULL;
1493 GntTree *tree = NULL;
1494 int x, y, top, width;
1495 char *title = NULL;
1496
1497 if (ggblist->context)
1498 return;
1499
1500 tree = GNT_TREE(ggblist->tree);
1501
1502 node = gnt_tree_get_selection_data(tree);
1503 if (node && !(PURPLE_IS_BUDDY(node) || PURPLE_IS_META_CONTACT(node) ||
1504 PURPLE_IS_GROUP(node) || PURPLE_IS_CHAT(node)))
1505 return;
1506
1507 if (ggblist->tooltip)
1508 remove_tooltip(ggblist);
1509
1510 ggblist->cnode = node;
1511
1512 ggblist->context = context = gnt_menu_new(GNT_MENU_POPUP);
1513 g_signal_connect(G_OBJECT(context), "destroy", G_CALLBACK(context_menu_destroyed), ggblist);
1514 g_signal_connect(G_OBJECT(context), "hide", G_CALLBACK(gnt_widget_destroy), NULL);
1515
1516 if (!node) {
1517 create_group_menu(GNT_MENU(context), NULL);
1518 title = g_strdup(_("Buddy List"));
1519 } else if (PURPLE_IS_META_CONTACT(node)) {
1520 ggblist->cnode = PURPLE_BLIST_NODE(purple_meta_contact_get_priority_buddy(PURPLE_META_CONTACT(node)));
1521 create_buddy_menu(GNT_MENU(context), (PurpleBuddy*)ggblist->cnode);
1522 title = g_strdup(purple_meta_contact_get_alias((PurpleMetaContact*)node));
1523 } else if (PURPLE_IS_BUDDY(node)) {
1524 PurpleBuddy *buddy = (PurpleBuddy *)node;
1525 create_buddy_menu(GNT_MENU(context), buddy);
1526 title = g_strdup(purple_buddy_get_name(buddy));
1527 } else if (PURPLE_IS_CHAT(node)) {
1528 PurpleChat *chat = (PurpleChat*)node;
1529 create_chat_menu(GNT_MENU(context), chat);
1530 title = g_strdup(purple_chat_get_name(chat));
1531 } else if (PURPLE_IS_GROUP(node)) {
1532 PurpleGroup *group = (PurpleGroup *)node;
1533 create_group_menu(GNT_MENU(context), group);
1534 title = g_strdup(purple_group_get_name(group));
1535 }
1536
1537 append_extended_menu(GNT_MENU(context), node);
1538
1539 /* These are common for everything */
1540 if (node) {
1541 add_custom_action(GNT_MENU(context),
1542 PURPLE_IS_GROUP(node) ? _("Rename") : _("Alias"),
1543 G_CALLBACK(finch_blist_rename_node_cb), node);
1544 add_custom_action(GNT_MENU(context), _("Remove"),
1545 G_CALLBACK(finch_blist_remove_node_cb), node);
1546
1547 if (ggblist->tagged && (PURPLE_IS_META_CONTACT(node)
1548 || PURPLE_IS_GROUP(node))) {
1549 add_custom_action(GNT_MENU(context), _("Place tagged"),
1550 G_CALLBACK(finch_blist_place_tagged), node);
1551 }
1552
1553 if (PURPLE_IS_BUDDY(node) || PURPLE_IS_META_CONTACT(node)) {
1554 add_custom_action(GNT_MENU(context), _("Toggle Tag"),
1555 G_CALLBACK(finch_blist_toggle_tag_buddy), node);
1556 }
1557 }
1558
1559 /* Set the position for the popup */
1560 gnt_widget_get_position(GNT_WIDGET(tree), &x, &y);
1561 gnt_widget_get_size(GNT_WIDGET(tree), &width, NULL);
1562 top = gnt_tree_get_selection_visible_line(tree);
1563
1564 x += width;
1565 y += top - 1;
1566
1567 gnt_widget_set_position(context, x, y);
1568 gnt_screen_menu_show(GNT_MENU(context));
1569 g_free(title);
1570 }
1571
1572 static void
1573 tooltip_for_buddy(PurpleBuddy *buddy, GString *str, gboolean full)
1574 {
1575 PurpleAccount *account = NULL;
1576 PurpleContactInfo *info = NULL;
1577 PurpleNotifyUserInfo *user_info;
1578 PurplePresence *presence = NULL;
1579 const char *alias = purple_buddy_get_alias(buddy);
1580 char *tmp, *strip;
1581
1582 user_info = purple_notify_user_info_new();
1583
1584 account = purple_buddy_get_account(buddy);
1585 info = PURPLE_CONTACT_INFO(account);
1586 presence = purple_buddy_get_presence(buddy);
1587
1588 if (!full || g_utf8_collate(purple_buddy_get_name(buddy), alias)) {
1589 purple_notify_user_info_add_pair_plaintext(user_info, _("Nickname"), alias);
1590 }
1591
1592 tmp = g_strdup_printf("%s (%s)",
1593 purple_contact_info_get_username(info),
1594 purple_account_get_protocol_name(account));
1595 purple_notify_user_info_add_pair_plaintext(user_info, _("Account"), tmp);
1596 g_free(tmp);
1597
1598 if (purple_prefs_get_bool("/finch/blist/idletime")) {
1599 PurplePresence *pre = purple_buddy_get_presence(buddy);
1600 if (purple_presence_is_idle(pre)) {
1601 GDateTime *idle = purple_presence_get_idle_time(pre);
1602
1603 if(idle != NULL) {
1604 GDateTime *now = NULL;
1605 GTimeSpan since = 0;
1606 char *st = NULL;
1607
1608 now = g_date_time_new_now_local();
1609 since = g_date_time_difference(now, idle);
1610 g_date_time_unref(now);
1611
1612 st = purple_str_seconds_to_string(since / G_TIME_SPAN_SECOND);
1613 purple_notify_user_info_add_pair_plaintext(user_info, _("Idle"), st);
1614 g_free(st);
1615 }
1616 }
1617 }
1618
1619 tmp = purple_notify_user_info_get_text_with_newline(user_info, "<BR>");
1620 purple_notify_user_info_destroy(user_info);
1621
1622 strip = purple_markup_strip_html(tmp);
1623 g_string_append(str, strip);
1624
1625 if (purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_MOBILE)) {
1626 g_string_append(str, "\n");
1627 g_string_append(str, _("On Mobile"));
1628 }
1629
1630 g_free(strip);
1631 g_free(tmp);
1632 }
1633
1634 static GString*
1635 make_sure_text_fits(GString *string)
1636 {
1637 int maxw = getmaxx(stdscr) - 3;
1638 char *str = gnt_util_onscreen_fit_string(string->str, maxw);
1639 string = g_string_assign(string, str);
1640 g_free(str);
1641 return string;
1642 }
1643
1644 static gboolean
1645 draw_tooltip_real(FinchBuddyList *ggblist)
1646 {
1647 PurpleBlistNode *node;
1648 int x, y, top, width, w, h;
1649 GString *str = NULL;
1650 GntTree *tree;
1651 GntWidget *widget, *box, *tv;
1652 char *title = NULL;
1653
1654 widget = ggblist->tree;
1655 tree = GNT_TREE(widget);
1656
1657 if (!gnt_widget_has_focus(ggblist->tree) ||
1658 (ggblist->context && gnt_widget_get_visible(ggblist->context)))
1659 return FALSE;
1660
1661 if (ggblist->tooltip)
1662 {
1663 /* XXX: Once we can properly redraw on expose events, this can be removed at the end
1664 * to avoid the blinking*/
1665 remove_tooltip(ggblist);
1666 }
1667
1668 node = gnt_tree_get_selection_data(tree);
1669 if (!node)
1670 return FALSE;
1671
1672 if (!ggblist->manager->create_tooltip(node, &str, &title))
1673 return FALSE;
1674
1675 gnt_widget_get_position(widget, &x, &y);
1676 gnt_widget_get_size(widget, &width, NULL);
1677 top = gnt_tree_get_selection_visible_line(tree);
1678
1679 x += width;
1680 y += top - 1;
1681
1682 box = gnt_box_new(FALSE, FALSE);
1683 gnt_box_set_toplevel(GNT_BOX(box), TRUE);
1684 gnt_widget_set_has_shadow(box, FALSE);
1685 gnt_box_set_title(GNT_BOX(box), title);
1686
1687 str = make_sure_text_fits(str);
1688 gnt_util_get_text_bound(str->str, &w, &h);
1689 h = MAX(1, h);
1690 tv = gnt_text_view_new();
1691 gnt_widget_set_size(tv, w + 1, h);
1692 gnt_text_view_set_flag(GNT_TEXT_VIEW(tv), GNT_TEXT_VIEW_NO_SCROLL);
1693 gnt_box_add_widget(GNT_BOX(box), tv);
1694
1695 if (x + w >= getmaxx(stdscr))
1696 x -= w + width + 2;
1697 gnt_widget_set_position(box, x, y);
1698 gnt_widget_set_take_focus(box, FALSE);
1699 gnt_widget_set_transient(box, TRUE);
1700 gnt_widget_draw(box);
1701
1702 gnt_text_view_append_text_with_flags(GNT_TEXT_VIEW(tv), str->str, GNT_TEXT_FLAG_NORMAL);
1703 gnt_text_view_scroll(GNT_TEXT_VIEW(tv), 0);
1704
1705 g_free(title);
1706 g_string_free(str, TRUE);
1707 ggblist->tooltip = box;
1708 ggblist->tnode = node;
1709
1710 gnt_widget_set_name(ggblist->tooltip, "tooltip");
1711 return FALSE;
1712 }
1713
1714 static void
1715 draw_tooltip(FinchBuddyList *ggblist)
1716 {
1717 /* When an account has signed off, it removes one buddy at a time.
1718 * Drawing the tooltip after removing each buddy is expensive. On
1719 * top of that, if the selected buddy belongs to the disconnected
1720 * account, then retrieving the tooltip for that causes crash. So
1721 * let's make sure we wait for all the buddies to be removed first.*/
1722 int id = g_timeout_add(0, G_SOURCE_FUNC(draw_tooltip_real), ggblist);
1723 g_object_set_data_full(G_OBJECT(ggblist->window), "draw_tooltip_calback",
1724 GINT_TO_POINTER(id), (GDestroyNotify)g_source_remove);
1725 }
1726
1727 static void
1728 selection_changed(G_GNUC_UNUSED GntWidget *widget, G_GNUC_UNUSED gpointer old,
1729 G_GNUC_UNUSED gpointer current, FinchBuddyList *ggblist)
1730 {
1731 remove_peripherals(ggblist);
1732 draw_tooltip(ggblist);
1733 }
1734
1735 static gboolean
1736 context_menu(G_GNUC_UNUSED GntWidget *widget, FinchBuddyList *ggblist)
1737 {
1738 draw_context_menu(ggblist);
1739 return TRUE;
1740 }
1741
1742 static gboolean
1743 key_pressed(G_GNUC_UNUSED GntWidget *widget, const char *text,
1744 FinchBuddyList *ggblist)
1745 {
1746 if (text[0] == 27 && text[1] == 0) {
1747 /* Escape was pressed */
1748 if (gnt_tree_is_searching(GNT_TREE(ggblist->tree)))
1749 gnt_bindable_perform_action_named(GNT_BINDABLE(ggblist->tree), "end-search", NULL);
1750 remove_peripherals(ggblist);
1751 } else if (purple_strequal(text, GNT_KEY_INS)) {
1752 PurpleBlistNode *node = gnt_tree_get_selection_data(GNT_TREE(ggblist->tree));
1753 purple_blist_request_add_buddy(NULL, NULL,
1754 node && PURPLE_IS_GROUP(node) ? purple_group_get_name(PURPLE_GROUP(node)) : NULL,
1755 NULL);
1756 } else if (!gnt_tree_is_searching(GNT_TREE(ggblist->tree))) {
1757 if (purple_strequal(text, "t")) {
1758 finch_blist_toggle_tag_buddy(gnt_tree_get_selection_data(GNT_TREE(ggblist->tree)));
1759 gnt_bindable_perform_action_named(GNT_BINDABLE(ggblist->tree), "move-down", NULL);
1760 } else if (purple_strequal(text, "a")) {
1761 finch_blist_place_tagged(gnt_tree_get_selection_data(GNT_TREE(ggblist->tree)));
1762 } else
1763 return FALSE;
1764 } else
1765 return FALSE;
1766
1767 return TRUE;
1768 }
1769
1770 static void
1771 update_node_display(PurpleBlistNode *node, FinchBuddyList *ggblist)
1772 {
1773 GntTextFormatFlags flag = get_blist_node_flag(ggblist, node);
1774 gnt_tree_set_row_flags(GNT_TREE(ggblist->tree), node, flag);
1775 }
1776
1777 static void
1778 update_buddy_display(PurpleBuddy *buddy, FinchBuddyList *ggblist)
1779 {
1780 PurpleMetaContact *contact;
1781
1782 contact = purple_buddy_get_contact(buddy);
1783
1784 gnt_tree_change_text(GNT_TREE(ggblist->tree), buddy, 0, get_display_name((PurpleBlistNode*)buddy));
1785 gnt_tree_change_text(GNT_TREE(ggblist->tree), contact, 0, get_display_name((PurpleBlistNode*)contact));
1786
1787 blist_update_row_flags(ggblist, (PurpleBlistNode *)buddy);
1788 if (buddy == purple_meta_contact_get_priority_buddy(contact))
1789 blist_update_row_flags(ggblist, (PurpleBlistNode *)contact);
1790
1791 if (ggblist->tnode == (PurpleBlistNode *)buddy) {
1792 draw_tooltip(ggblist);
1793 }
1794 }
1795
1796 static void
1797 buddy_status_changed(PurpleBuddy *buddy, G_GNUC_UNUSED PurpleStatus *old,
1798 G_GNUC_UNUSED PurpleStatus *now, FinchBuddyList *ggblist)
1799 {
1800 update_buddy_display(buddy, ggblist);
1801 }
1802
1803 static void
1804 buddy_idle_changed(PurpleBuddy *buddy, G_GNUC_UNUSED int old,
1805 G_GNUC_UNUSED int new, FinchBuddyList *ggblist)
1806 {
1807 update_buddy_display(buddy, ggblist);
1808 }
1809
1810 static void
1811 remove_peripherals(FinchBuddyList *ggblist)
1812 {
1813 if (ggblist->tooltip)
1814 remove_tooltip(ggblist);
1815 else if (ggblist->context)
1816 gnt_widget_destroy(ggblist->context);
1817 }
1818
1819 static void
1820 size_changed_cb(GntWidget *w, G_GNUC_UNUSED int wi, G_GNUC_UNUSED int h)
1821 {
1822 int width, height;
1823 gnt_widget_get_size(w, &width, &height);
1824 purple_prefs_set_int(PREF_ROOT "/size/width", width);
1825 purple_prefs_set_int(PREF_ROOT "/size/height", height);
1826 }
1827
1828 static void
1829 save_position_cb(G_GNUC_UNUSED GntWidget *w, int x, int y)
1830 {
1831 purple_prefs_set_int(PREF_ROOT "/position/x", x);
1832 purple_prefs_set_int(PREF_ROOT "/position/y", y);
1833 }
1834
1835 static void
1836 reset_blist_window(G_GNUC_UNUSED GntWidget *window,
1837 G_GNUC_UNUSED gpointer data)
1838 {
1839 purple_signals_disconnect_by_handle(finch_blist_get_handle());
1840
1841 g_clear_handle_id(&ggblist->typing, g_source_remove);
1842 remove_peripherals(ggblist);
1843 g_clear_list(&ggblist->tagged, NULL);
1844
1845 g_clear_handle_id(&ggblist->new_group_timeout, g_source_remove);
1846 g_clear_list(&ggblist->new_group, NULL);
1847
1848 ggblist = NULL;
1849 }
1850
1851 static void
1852 populate_buddylist(void)
1853 {
1854 PurpleBlistNode *node;
1855 PurpleBuddyList *list;
1856
1857 if (ggblist->manager->init)
1858 ggblist->manager->init();
1859
1860 if (purple_strequal(purple_prefs_get_string(PREF_ROOT "/sort_type"), "text")) {
1861 gnt_tree_set_compare_func(GNT_TREE(ggblist->tree),
1862 (GCompareFunc)blist_node_compare_text);
1863 } else if (purple_strequal(purple_prefs_get_string(PREF_ROOT "/sort_type"), "status")) {
1864 gnt_tree_set_compare_func(GNT_TREE(ggblist->tree),
1865 (GCompareFunc)blist_node_compare_status);
1866 }
1867
1868 list = purple_blist_get_default();
1869 node = purple_blist_get_root(list);
1870 while (node)
1871 {
1872 node_update(list, node);
1873 node = purple_blist_node_next(node, FALSE);
1874 }
1875 }
1876
1877 static void
1878 destroy_status_list(GList *list)
1879 {
1880 g_list_free_full(list, g_free);
1881 }
1882
1883 static void
1884 populate_status_dropdown(void)
1885 {
1886 int i;
1887 GList *iter;
1888 GList *items = NULL;
1889 StatusBoxItem *item = NULL;
1890
1891 /* First the primitives */
1892 PurpleStatusPrimitive prims[] = {PURPLE_STATUS_AVAILABLE, PURPLE_STATUS_AWAY,
1893 PURPLE_STATUS_INVISIBLE, PURPLE_STATUS_OFFLINE, PURPLE_STATUS_UNSET};
1894
1895 gnt_combo_box_remove_all(GNT_COMBO_BOX(ggblist->status));
1896
1897 for (i = 0; prims[i] != PURPLE_STATUS_UNSET; i++)
1898 {
1899 item = g_new0(StatusBoxItem, 1);
1900 item->type = STATUS_PRIMITIVE;
1901 item->u.prim = prims[i];
1902 items = g_list_prepend(items, item);
1903 gnt_combo_box_add_data(GNT_COMBO_BOX(ggblist->status), item,
1904 purple_primitive_get_name_from_type(prims[i]));
1905 }
1906
1907 /* Now the popular statuses */
1908 for (iter = purple_savedstatuses_get_popular(6); iter; iter = g_list_delete_link(iter, iter))
1909 {
1910 item = g_new0(StatusBoxItem, 1);
1911 item->type = STATUS_SAVED_POPULAR;
1912 item->u.saved = iter->data;
1913 items = g_list_prepend(items, item);
1914 gnt_combo_box_add_data(GNT_COMBO_BOX(ggblist->status), item,
1915 purple_savedstatus_get_title(iter->data));
1916 }
1917
1918 /* New savedstatus */
1919 item = g_new0(StatusBoxItem, 1);
1920 item->type = STATUS_SAVED_NEW;
1921 items = g_list_prepend(items, item);
1922 gnt_combo_box_add_data(GNT_COMBO_BOX(ggblist->status), item,
1923 _("New..."));
1924
1925 /* More savedstatuses */
1926 item = g_new0(StatusBoxItem, 1);
1927 item->type = STATUS_SAVED_ALL;
1928 items = g_list_prepend(items, item);
1929 gnt_combo_box_add_data(GNT_COMBO_BOX(ggblist->status), item,
1930 _("Saved..."));
1931
1932 /* The keys for the combobox are created here, and never used
1933 * anywhere else. So make sure the keys are freed when the widget
1934 * is destroyed. */
1935 g_object_set_data_full(G_OBJECT(ggblist->status), "list of statuses",
1936 items, (GDestroyNotify)destroy_status_list);
1937 }
1938
1939 static void
1940 redraw_blist(G_GNUC_UNUSED const char *name, G_GNUC_UNUSED PurplePrefType type,
1941 G_GNUC_UNUSED gconstpointer val, G_GNUC_UNUSED gpointer data)
1942 {
1943 PurpleBlistNode *sel;
1944 FinchBlistManager *manager;
1945
1946 if (ggblist == NULL)
1947 return;
1948
1949 manager = finch_blist_manager_find(purple_prefs_get_string(PREF_ROOT "/grouping"));
1950 if (manager == NULL)
1951 manager = &default_manager;
1952 if (ggblist->manager != manager) {
1953 if (ggblist->manager->uninit)
1954 ggblist->manager->uninit();
1955
1956 ggblist->manager = manager;
1957 if (manager->can_add_node == NULL)
1958 manager->can_add_node = default_can_add_node;
1959 if (manager->find_parent == NULL)
1960 manager->find_parent = default_find_parent;
1961 if (manager->create_tooltip == NULL)
1962 manager->create_tooltip = default_create_tooltip;
1963 }
1964
1965 if (ggblist->window == NULL)
1966 return;
1967
1968 sel = gnt_tree_get_selection_data(GNT_TREE(ggblist->tree));
1969 gnt_tree_remove_all(GNT_TREE(ggblist->tree));
1970
1971 populate_buddylist();
1972 gnt_tree_set_selected(GNT_TREE(ggblist->tree), sel);
1973 draw_tooltip(ggblist);
1974 }
1975
1976 void
1977 finch_blist_init(void)
1978 {
1979 color_available = gnt_style_get_color(NULL, "color-available");
1980 if (!color_available)
1981 color_available = gnt_color_add_pair(COLOR_GREEN, -1);
1982 color_away = gnt_style_get_color(NULL, "color-away");
1983 if (!color_away)
1984 color_away = gnt_color_add_pair(COLOR_BLUE, -1);
1985 color_idle = gnt_style_get_color(NULL, "color-idle");
1986 if (!color_idle)
1987 color_idle = gnt_color_add_pair(COLOR_CYAN, -1);
1988 color_offline = gnt_style_get_color(NULL, "color-offline");
1989 if (!color_offline)
1990 color_offline = gnt_color_add_pair(COLOR_RED, -1);
1991
1992 purple_prefs_add_none(PREF_ROOT);
1993 purple_prefs_add_none(PREF_ROOT "/size");
1994 purple_prefs_add_int(PREF_ROOT "/size/width", 20);
1995 purple_prefs_add_int(PREF_ROOT "/size/height", 17);
1996 purple_prefs_add_none(PREF_ROOT "/position");
1997 purple_prefs_add_int(PREF_ROOT "/position/x", 0);
1998 purple_prefs_add_int(PREF_ROOT "/position/y", 0);
1999 purple_prefs_add_bool(PREF_ROOT "/idletime", TRUE);
2000 purple_prefs_add_bool(PREF_ROOT "/showoffline", FALSE);
2001 purple_prefs_add_bool(PREF_ROOT "/emptygroups", FALSE);
2002 purple_prefs_add_string(PREF_ROOT "/sort_type", "text");
2003 purple_prefs_add_string(PREF_ROOT "/grouping", "default");
2004
2005 purple_prefs_connect_callback(finch_blist_get_handle(),
2006 PREF_ROOT "/emptygroups", redraw_blist, NULL);
2007 purple_prefs_connect_callback(finch_blist_get_handle(),
2008 PREF_ROOT "/showoffline", redraw_blist, NULL);
2009 purple_prefs_connect_callback(finch_blist_get_handle(),
2010 PREF_ROOT "/sort_type", redraw_blist, NULL);
2011 purple_prefs_connect_callback(finch_blist_get_handle(),
2012 PREF_ROOT "/grouping", redraw_blist, NULL);
2013
2014 purple_signal_connect_priority(purple_connections_get_handle(),
2015 "autojoin", purple_blist_get_handle(),
2016 G_CALLBACK(account_autojoin_cb), NULL,
2017 PURPLE_SIGNAL_PRIORITY_HIGHEST);
2018
2019 finch_blist_install_manager(&default_manager);
2020 }
2021
2022 static gboolean
2023 remove_typing_cb(G_GNUC_UNUSED gpointer data)
2024 {
2025 PurpleSavedStatus *current;
2026 const char *message, *newmessage;
2027 char *escnewmessage;
2028 PurpleStatusPrimitive prim, newprim;
2029 StatusBoxItem *item;
2030
2031 current = purple_savedstatus_get_current();
2032 message = purple_savedstatus_get_message(current);
2033 prim = purple_savedstatus_get_primitive_type(current);
2034
2035 newmessage = gnt_entry_get_text(GNT_ENTRY(ggblist->statustext));
2036 item = gnt_combo_box_get_selected_data(GNT_COMBO_BOX(ggblist->status));
2037 escnewmessage = newmessage ? g_markup_escape_text(newmessage, -1) : NULL;
2038
2039 switch (item->type) {
2040 case STATUS_PRIMITIVE:
2041 newprim = item->u.prim;
2042 break;
2043 case STATUS_SAVED_POPULAR:
2044 newprim = purple_savedstatus_get_primitive_type(item->u.saved);
2045 break;
2046 default:
2047 goto end; /* 'New' or 'Saved' is selected, but this should never happen. */
2048 }
2049
2050 if (newprim != prim || ((message && !escnewmessage) ||
2051 (!message && escnewmessage) ||
2052 (message && escnewmessage && g_utf8_collate(message, escnewmessage) != 0)))
2053 {
2054 PurpleSavedStatus *status = purple_savedstatus_find_transient_by_type_and_message(newprim, escnewmessage);
2055 /* Holy Crap! That's a LAWNG function name */
2056 if (status == NULL)
2057 {
2058 status = purple_savedstatus_new(NULL, newprim);
2059 purple_savedstatus_set_message(status, escnewmessage);
2060 }
2061
2062 purple_savedstatus_activate(status);
2063 }
2064
2065 gnt_box_give_focus_to_child(GNT_BOX(ggblist->window), ggblist->tree);
2066 end:
2067 g_free(escnewmessage);
2068 g_clear_handle_id(&ggblist->typing, g_source_remove);
2069 return FALSE;
2070 }
2071
2072 static void
2073 status_selection_changed(G_GNUC_UNUSED GntComboBox *box,
2074 G_GNUC_UNUSED StatusBoxItem *old,
2075 StatusBoxItem *now,
2076 G_GNUC_UNUSED gpointer data)
2077 {
2078 gnt_entry_set_text(GNT_ENTRY(ggblist->statustext), NULL);
2079 if (now->type == STATUS_SAVED_POPULAR)
2080 {
2081 /* Set the status immediately */
2082 purple_savedstatus_activate(now->u.saved);
2083 }
2084 else if (now->type == STATUS_PRIMITIVE)
2085 {
2086 /* Move the focus to the entry box */
2087 /* XXX: Make sure the selected status can have a message */
2088 gnt_box_move_focus(GNT_BOX(ggblist->window), 1);
2089 ggblist->typing = g_timeout_add_seconds(TYPING_TIMEOUT_S,
2090 G_SOURCE_FUNC(remove_typing_cb),
2091 NULL);
2092 }
2093 else if (now->type == STATUS_SAVED_ALL)
2094 {
2095 /* Restore the selection to reflect current status. */
2096 savedstatus_changed(purple_savedstatus_get_current(), NULL);
2097 gnt_box_give_focus_to_child(GNT_BOX(ggblist->window), ggblist->tree);
2098 finch_savedstatus_show_all();
2099 }
2100 else if (now->type == STATUS_SAVED_NEW)
2101 {
2102 savedstatus_changed(purple_savedstatus_get_current(), NULL);
2103 gnt_box_give_focus_to_child(GNT_BOX(ggblist->window), ggblist->tree);
2104 finch_savedstatus_edit(NULL);
2105 }
2106 else
2107 g_return_if_reached();
2108 }
2109
2110 static gboolean
2111 status_text_changed(G_GNUC_UNUSED GntEntry *entry, const char *text,
2112 G_GNUC_UNUSED gpointer data)
2113 {
2114 if ((text[0] == 27 || (text[0] == '\t' && text[1] == '\0')) && ggblist->typing == 0)
2115 return FALSE;
2116
2117 g_clear_handle_id(&ggblist->typing, g_source_remove);
2118
2119 if (text[0] == '\r' && text[1] == 0)
2120 {
2121 /* Set the status only after you press 'Enter' */
2122 remove_typing_cb(NULL);
2123 return TRUE;
2124 }
2125
2126 ggblist->typing = g_timeout_add_seconds(TYPING_TIMEOUT_S,
2127 G_SOURCE_FUNC(remove_typing_cb),
2128 NULL);
2129 return FALSE;
2130 }
2131
2132 static void
2133 savedstatus_changed(PurpleSavedStatus *now,
2134 G_GNUC_UNUSED PurpleSavedStatus *old)
2135 {
2136 GList *list;
2137 PurpleStatusPrimitive prim;
2138 const char *message;
2139 gboolean found = FALSE, saved = TRUE;
2140
2141 if (!ggblist)
2142 return;
2143
2144 /* Block the signals we don't want to emit */
2145 g_signal_handlers_block_matched(ggblist->status, G_SIGNAL_MATCH_FUNC,
2146 0, 0, NULL, status_selection_changed, NULL);
2147 g_signal_handlers_block_matched(ggblist->statustext, G_SIGNAL_MATCH_FUNC,
2148 0, 0, NULL, status_text_changed, NULL);
2149
2150 prim = purple_savedstatus_get_primitive_type(now);
2151 message = purple_savedstatus_get_message(now);
2152
2153 /* Rebuild the status dropdown */
2154 populate_status_dropdown();
2155
2156 while (!found) {
2157 list = g_object_get_data(G_OBJECT(ggblist->status), "list of statuses");
2158 for (; list; list = list->next)
2159 {
2160 StatusBoxItem *item = list->data;
2161 if ((saved && item->type != STATUS_PRIMITIVE && item->u.saved == now) ||
2162 (!saved && item->type == STATUS_PRIMITIVE && item->u.prim == prim))
2163 {
2164 char *mess = purple_unescape_html(message);
2165 gnt_combo_box_set_selected(GNT_COMBO_BOX(ggblist->status), item);
2166 gnt_entry_set_text(GNT_ENTRY(ggblist->statustext), mess);
2167 gnt_widget_draw(ggblist->status);
2168 g_free(mess);
2169 found = TRUE;
2170 break;
2171 }
2172 }
2173 if (!saved)
2174 break;
2175 saved = FALSE;
2176 }
2177
2178 g_signal_handlers_unblock_matched(ggblist->status, G_SIGNAL_MATCH_FUNC,
2179 0, 0, NULL, status_selection_changed, NULL);
2180 g_signal_handlers_unblock_matched(ggblist->statustext, G_SIGNAL_MATCH_FUNC,
2181 0, 0, NULL, status_text_changed, NULL);
2182 }
2183
2184 static int
2185 blist_node_compare_position(PurpleBlistNode *n1, PurpleBlistNode *n2)
2186 {
2187 while ((n1 = purple_blist_node_get_sibling_prev(n1)) != NULL)
2188 if (n1 == n2)
2189 return 1;
2190 return -1;
2191 }
2192
2193 static int
2194 blist_node_compare_text(PurpleBlistNode *n1, PurpleBlistNode *n2)
2195 {
2196 const char *s1, *s2;
2197 char *us1, *us2;
2198 int ret;
2199
2200 if (G_OBJECT_TYPE(n1) != G_OBJECT_TYPE(n2))
2201 return blist_node_compare_position(n1, n2);
2202
2203 if (PURPLE_IS_CHAT(n1)) {
2204 s1 = purple_chat_get_name((PurpleChat*)n1);
2205 s2 = purple_chat_get_name((PurpleChat*)n2);
2206 } else if (PURPLE_IS_BUDDY(n1)) {
2207 return purple_buddy_presence_compare(
2208 PURPLE_BUDDY_PRESENCE(purple_buddy_get_presence(PURPLE_BUDDY(n1))),
2209 PURPLE_BUDDY_PRESENCE(purple_buddy_get_presence(PURPLE_BUDDY(n2))));
2210 } else if (PURPLE_IS_META_CONTACT(n1)) {
2211 s1 = purple_meta_contact_get_alias((PurpleMetaContact*)n1);
2212 s2 = purple_meta_contact_get_alias((PurpleMetaContact*)n2);
2213 } else {
2214 return blist_node_compare_position(n1, n2);
2215 }
2216
2217 us1 = g_utf8_strup(s1, -1);
2218 us2 = g_utf8_strup(s2, -1);
2219 ret = g_utf8_collate(us1, us2);
2220 g_free(us1);
2221 g_free(us2);
2222
2223 return ret;
2224 }
2225
2226 static int
2227 blist_node_compare_status(PurpleBlistNode *n1, PurpleBlistNode *n2)
2228 {
2229 int ret;
2230
2231 if (G_OBJECT_TYPE(n1) != G_OBJECT_TYPE(n2))
2232 return blist_node_compare_position(n1, n2);
2233
2234 if (PURPLE_IS_META_CONTACT(n1))
2235 n1 = PURPLE_BLIST_NODE(purple_meta_contact_get_priority_buddy(PURPLE_META_CONTACT(n1)));
2236 if (PURPLE_IS_META_CONTACT(n2))
2237 n2 = PURPLE_BLIST_NODE(purple_meta_contact_get_priority_buddy(PURPLE_META_CONTACT(n2)));
2238
2239 if (PURPLE_IS_BUDDY(n1) && PURPLE_IS_BUDDY(n2)) {
2240 ret = purple_buddy_presence_compare(
2241 PURPLE_BUDDY_PRESENCE(purple_buddy_get_presence(PURPLE_BUDDY(n1))),
2242 PURPLE_BUDDY_PRESENCE(purple_buddy_get_presence(PURPLE_BUDDY(n2))));
2243 if (ret != 0)
2244 return ret;
2245 } else {
2246 return blist_node_compare_position(n1, n2);
2247 }
2248
2249 /* Sort alphabetically if presence is not comparable */
2250 ret = blist_node_compare_text(n1, n2);
2251
2252 return ret;
2253 }
2254
2255 static void
2256 plugin_action(G_GNUC_UNUSED GntMenuItem *item, G_GNUC_UNUSED gpointer data)
2257 {
2258 /* TODO: Convert to GAction/GMenu. */
2259 #if 0
2260 PurplePluginAction *action = data;
2261 if (action && action->callback)
2262 action->callback(action);
2263 #endif
2264 }
2265
2266 static void
2267 build_plugin_actions(G_GNUC_UNUSED GntMenuItem *item,
2268 G_GNUC_UNUSED PurplePlugin *plugin)
2269 {
2270 /* TODO: port to GAction/GMenu. */
2271 #if 0
2272 GntWidget *sub = gnt_menu_new(GNT_MENU_POPUP);
2273 PurplePluginActionsCb actions_cb;
2274 GList *actions;
2275 GntMenuItem *menuitem;
2276
2277 actions_cb =
2278 purple_plugin_info_get_actions_cb(purple_plugin_get_info(plugin));
2279
2280 gnt_menuitem_set_submenu(item, GNT_MENU(sub));
2281 for (actions = actions_cb(plugin); actions;
2282 actions = g_list_delete_link(actions, actions)) {
2283 if (actions->data) {
2284 PurplePluginAction *action = actions->data;
2285 action->plugin = plugin;
2286 menuitem = gnt_menuitem_new(action->label);
2287 gnt_menu_add_item(GNT_MENU(sub), menuitem);
2288
2289 gnt_menuitem_set_callback(menuitem, plugin_action, action);
2290 g_object_set_data_full(G_OBJECT(menuitem), "plugin_action",
2291 action, (GDestroyNotify)purple_plugin_action_free);
2292 }
2293 }
2294 #endif
2295 }
2296
2297 static void
2298 protocol_action(G_GNUC_UNUSED GntMenuItem *item, gpointer data) {
2299 PurpleProtocolAction *action = data;
2300 if (action && action->callback)
2301 action->callback(action);
2302 }
2303
2304 static void
2305 build_protocol_actions(G_GNUC_UNUSED GntMenuItem *item,
2306 G_GNUC_UNUSED PurpleProtocol *protocol,
2307 G_GNUC_UNUSED PurpleConnection *gc)
2308 {
2309 /* TODO: port to PurpleProtocolActions. */
2310 #if 0
2311 GntWidget *sub = gnt_menu_new(GNT_MENU_POPUP);
2312 GList *actions;
2313 GntMenuItem *menuitem;
2314
2315 gnt_menuitem_set_submenu(item, GNT_MENU(sub));
2316 for (actions = purple_protocol_client_get_actions(PURPLE_PROTOCOL_CLIENT(protocol), gc);
2317 actions; actions = g_list_delete_link(actions, actions)) {
2318 if (actions->data) {
2319 PurpleProtocolAction *action = actions->data;
2320 action->connection = gc;
2321 menuitem = gnt_menuitem_new(action->label);
2322 gnt_menu_add_item(GNT_MENU(sub), menuitem);
2323
2324 gnt_menuitem_set_callback(menuitem, protocol_action, action);
2325 g_object_set_data_full(G_OBJECT(menuitem), "protocol_action",
2326 action, (GDestroyNotify)purple_protocol_action_free);
2327 }
2328 }
2329 #endif
2330 }
2331
2332 static gboolean
2333 buddy_recent_signed_on_off(gpointer data)
2334 {
2335 PurpleBlistNode *node = data;
2336 FinchBlistNode *fnode = g_object_get_data(G_OBJECT(node), UI_DATA);
2337
2338 g_clear_handle_id(&fnode->signed_timer, g_source_remove);
2339
2340 if (!ggblist->manager->can_add_node(node)) {
2341 node_remove(purple_blist_get_default(), node);
2342 } else {
2343 update_node_display(node, ggblist);
2344 if (purple_blist_node_get_parent(node) && PURPLE_IS_META_CONTACT(purple_blist_node_get_parent(node)))
2345 update_node_display(purple_blist_node_get_parent(node), ggblist);
2346 }
2347
2348 g_object_unref(node);
2349 return FALSE;
2350 }
2351
2352 static gboolean
2353 buddy_signed_on_off_cb(gpointer data)
2354 {
2355 PurpleBlistNode *node = data;
2356 FinchBlistNode *fnode = g_object_get_data(G_OBJECT(node), UI_DATA);
2357 if(!ggblist || !fnode) {
2358 return FALSE;
2359 }
2360
2361 g_clear_handle_id(&fnode->signed_timer, g_source_remove);
2362
2363 g_object_ref(node);
2364 fnode->signed_timer = g_timeout_add_seconds(6,
2365 G_SOURCE_FUNC(buddy_recent_signed_on_off),
2366 data);
2367 update_node_display(node, ggblist);
2368 if (purple_blist_node_get_parent(node) && PURPLE_IS_META_CONTACT(purple_blist_node_get_parent(node)))
2369 update_node_display(purple_blist_node_get_parent(node), ggblist);
2370 return FALSE;
2371 }
2372
2373 static void
2374 buddy_signed_on_off(PurpleBuddy* buddy, G_GNUC_UNUSED gpointer data)
2375 {
2376 g_idle_add(buddy_signed_on_off_cb, buddy);
2377 }
2378
2379 static void
2380 reconstruct_plugins_menu(void)
2381 {
2382 GntWidget *sub;
2383 GntMenuItem *plg;
2384 GList *iter;
2385
2386 if (!ggblist)
2387 return;
2388
2389 if (ggblist->plugins == NULL)
2390 ggblist->plugins = gnt_menuitem_new(_("Plugins"));
2391
2392 plg = ggblist->plugins;
2393 sub = gnt_menu_new(GNT_MENU_POPUP);
2394 gnt_menuitem_set_submenu(plg, GNT_MENU(sub));
2395
2396 /* TODO: port to GAction/GMenu. */
2397 #if 0
2398 for (iter = purple_plugins_get_loaded(); iter; iter = iter->next) {
2399 PurplePlugin *plugin = iter->data;
2400 PurplePluginInfo *info = purple_plugin_get_info(plugin);
2401 GntMenuItem *item;
2402
2403 if (!purple_plugin_info_get_actions_cb(info))
2404 continue;
2405
2406 item = gnt_menuitem_new(_(gplugin_plugin_info_get_name(
2407 GPLUGIN_PLUGIN_INFO(info))));
2408 gnt_menu_add_item(GNT_MENU(sub), item);
2409 build_plugin_actions(item, plugin);
2410 }
2411 #endif
2412 }
2413
2414 static void
2415 reconstruct_plugins_menu_cb(G_GNUC_UNUSED GObject *plugin_manager,
2416 G_GNUC_UNUSED GPluginPlugin *plugin,
2417 G_GNUC_UNUSED gpointer data)
2418 {
2419 reconstruct_plugins_menu();
2420 }
2421
2422 static void
2423 reconstruct_accounts_menu(void) {
2424 #if 0
2425 PurpleAccountManager *manager = NULL;
2426 #endif
2427 GntWidget *sub;
2428 GntMenuItem *acc;
2429 #if 0
2430 GntMenuItem *item;
2431 GList *iter;
2432 #endif
2433
2434 if (!ggblist)
2435 return;
2436
2437 if (ggblist->accounts == NULL)
2438 ggblist->accounts = gnt_menuitem_new(_("Accounts"));
2439
2440 acc = ggblist->accounts;
2441 sub = gnt_menu_new(GNT_MENU_POPUP);
2442 gnt_menuitem_set_submenu(acc, GNT_MENU(sub));
2443
2444 #if 0
2445 manager = purple_account_manager_get_default();
2446 iter = purple_account_manager_get_enabled(manager);
2447
2448 for (; iter; iter = g_list_delete_link(iter, iter)) {
2449 PurpleAccount *account = iter->data;
2450 PurpleConnection *gc = purple_account_get_connection(account);
2451 PurpleProtocol *protocol;
2452
2453 if (!gc || !PURPLE_CONNECTION_IS_CONNECTED(gc))
2454 continue;
2455 protocol = purple_connection_get_protocol(gc);
2456
2457 /* TODO: port to PurpleProtocolActions */
2458 if (PURPLE_PROTOCOL_IMPLEMENTS(protocol, CLIENT, get_actions)) {
2459 PurpleContactInfo *info = PURPLE_CONTACT_INFO(account);
2460 item = gnt_menuitem_new(purple_contact_info_get_username(info));
2461 gnt_menu_add_item(GNT_MENU(sub), item);
2462 build_protocol_actions(item, protocol, gc);
2463 }
2464 }
2465 #endif
2466 }
2467
2468 static void
2469 reconstruct_grouping_menu(void)
2470 {
2471 GList *iter;
2472 GntWidget *subsub;
2473
2474 if (!ggblist || !ggblist->grouping)
2475 return;
2476
2477 subsub = gnt_menu_new(GNT_MENU_POPUP);
2478 gnt_menuitem_set_submenu(ggblist->grouping, GNT_MENU(subsub));
2479
2480 for (iter = managers; iter; iter = iter->next) {
2481 char menuid[128];
2482 FinchBlistManager *manager = iter->data;
2483 GntMenuItem *item = gnt_menuitem_new(_(manager->name));
2484 g_snprintf(menuid, sizeof(menuid), "grouping-%s", manager->id);
2485 gnt_menuitem_set_id(GNT_MENU_ITEM(item), menuid);
2486 gnt_menu_add_item(GNT_MENU(subsub), item);
2487 g_object_set_data_full(G_OBJECT(item), "grouping-id", g_strdup(manager->id), g_free);
2488 gnt_menuitem_set_callback(item, menu_group_set_cb, NULL);
2489 }
2490 }
2491
2492 static gboolean
2493 auto_join_chats(gpointer data)
2494 {
2495 PurpleBlistNode *node;
2496 PurpleConnection *pc = data;
2497 PurpleAccount *account = purple_connection_get_account(pc);
2498
2499 for (node = purple_blist_get_default_root(); node;
2500 node = purple_blist_node_next(node, FALSE)) {
2501 if (PURPLE_IS_CHAT(node)) {
2502 PurpleChat *chat = (PurpleChat*)node;
2503 if (purple_chat_get_account(chat) == account &&
2504 purple_blist_node_get_bool(node, "gnt-autojoin"))
2505 purple_serv_join_chat(purple_account_get_connection(account), purple_chat_get_components(chat));
2506 }
2507 }
2508 return FALSE;
2509 }
2510
2511 static gboolean
2512 account_autojoin_cb(PurpleConnection *gc, G_GNUC_UNUSED gpointer data)
2513 {
2514 g_idle_add(auto_join_chats, gc);
2515 return TRUE;
2516 }
2517
2518 static void toggle_pref_cb(G_GNUC_UNUSED GntMenuItem *item, gpointer n)
2519 {
2520 purple_prefs_set_bool(n, !purple_prefs_get_bool(n));
2521 }
2522
2523 static void sort_blist_change_cb(G_GNUC_UNUSED GntMenuItem *item, gpointer n)
2524 {
2525 purple_prefs_set_string(PREF_ROOT "/sort_type", n);
2526 }
2527
2528 /* send_im_select* -- Xerox */
2529 static void
2530 send_im_select_cb(G_GNUC_UNUSED gpointer data, PurpleRequestPage *page) {
2531 PurpleAccount *account;
2532 const char *username;
2533 PurpleConversation *im;
2534
2535 account = purple_request_page_get_account(page, "account");
2536 username = purple_request_page_get_string(page, "screenname");
2537
2538 im = purple_im_conversation_new(account, username);
2539 purple_conversation_present(im);
2540 }
2541
2542 static void
2543 send_im_select(G_GNUC_UNUSED GntMenuItem *item, G_GNUC_UNUSED gpointer n)
2544 {
2545 PurpleRequestPage *page;
2546 PurpleRequestGroup *group;
2547 PurpleRequestField *field;
2548
2549 page = purple_request_page_new();
2550
2551 group = purple_request_group_new(NULL);
2552 purple_request_page_add_group(page, group);
2553
2554 field = purple_request_field_string_new("screenname", _("Name"), NULL, FALSE);
2555 purple_request_field_set_type_hint(field, "screenname");
2556 purple_request_field_set_required(field, TRUE);
2557 purple_request_group_add_field(group, field);
2558
2559 field = purple_request_field_account_new("account", _("Account"), NULL);
2560 purple_request_field_set_type_hint(field, "account");
2561 purple_request_field_set_visible(field,
2562 (purple_connections_get_all() != NULL &&
2563 purple_connections_get_all()->next != NULL));
2564 purple_request_field_set_required(field, TRUE);
2565 purple_request_group_add_field(group, field);
2566
2567 purple_request_fields(
2568 purple_blist_get_default(), _("New Instant Message"), NULL,
2569 _("Please enter the username or alias of the person "
2570 "you would like to IM."),
2571 page, _("OK"), G_CALLBACK(send_im_select_cb), _("Cancel"),
2572 NULL, NULL, NULL);
2573 }
2574
2575 static void
2576 join_chat_select_cb(G_GNUC_UNUSED gpointer data, PurpleRequestPage *page) {
2577 PurpleAccount *account;
2578 const char *name;
2579 PurpleConnection *gc;
2580 PurpleConversationManager *manager;
2581 PurpleChat *chat;
2582 GHashTable *hash = NULL;
2583 PurpleConversation *conv;
2584
2585 account = purple_request_page_get_account(page, "account");
2586 name = purple_request_page_get_string(page, "chat");
2587
2588 if (!purple_account_is_connected(account))
2589 return;
2590
2591 gc = purple_account_get_connection(account);
2592 manager = purple_conversation_manager_get_default();
2593
2594 /* Create a new conversation now. This will give focus to the new window.
2595 * But it's necessary to pretend that we left the chat, because otherwise
2596 * a new conversation window will pop up when we finally join the chat. */
2597 conv = purple_conversation_manager_find_chat(manager, account, name);
2598 if(!PURPLE_IS_CHAT_CONVERSATION(conv)) {
2599 conv = purple_chat_conversation_new(account, name);
2600 purple_chat_conversation_leave(PURPLE_CHAT_CONVERSATION(conv));
2601 } else {
2602 purple_conversation_present(conv);
2603 }
2604
2605 chat = purple_blist_find_chat(account, name);
2606 if (chat == NULL) {
2607 PurpleProtocol *protocol = purple_connection_get_protocol(gc);
2608 hash = purple_protocol_chat_info_defaults(PURPLE_PROTOCOL_CHAT(protocol), gc, name);
2609 } else {
2610 hash = purple_chat_get_components(chat);
2611 }
2612 purple_serv_join_chat(gc, hash);
2613 if (chat == NULL && hash != NULL)
2614 g_hash_table_destroy(hash);
2615 }
2616
2617 static void
2618 join_chat_select(G_GNUC_UNUSED GntMenuItem *item, G_GNUC_UNUSED gpointer n)
2619 {
2620 PurpleRequestPage *page;
2621 PurpleRequestGroup *group;
2622 PurpleRequestField *field;
2623
2624 page = purple_request_page_new();
2625
2626 group = purple_request_group_new(NULL);
2627 purple_request_page_add_group(page, group);
2628
2629 field = purple_request_field_string_new("chat", _("Channel"), NULL, FALSE);
2630 purple_request_field_set_required(field, TRUE);
2631 purple_request_group_add_field(group, field);
2632
2633 field = purple_request_field_account_new("account", _("Account"), NULL);
2634 purple_request_field_set_type_hint(field, "account");
2635 purple_request_field_set_visible(field,
2636 (purple_connections_get_all() != NULL &&
2637 purple_connections_get_all()->next != NULL));
2638 purple_request_field_set_required(field, TRUE);
2639 purple_request_group_add_field(group, field);
2640
2641 purple_request_fields(
2642 purple_blist_get_default(), _("Join a Chat"), NULL,
2643 _("Please enter the name of the chat you want to join."),
2644 page, _("Join"), G_CALLBACK(join_chat_select_cb), _("Cancel"),
2645 NULL, NULL, NULL);
2646 }
2647
2648 static void
2649 menu_add_buddy_cb(G_GNUC_UNUSED GntMenuItem *item, G_GNUC_UNUSED gpointer data)
2650 {
2651 purple_blist_request_add_buddy(NULL, NULL, NULL, NULL);
2652 }
2653
2654 static void
2655 menu_add_chat_cb(G_GNUC_UNUSED GntMenuItem *item, G_GNUC_UNUSED gpointer data)
2656 {
2657 purple_blist_request_add_chat(NULL, NULL, NULL, NULL);
2658 }
2659
2660 static void
2661 menu_add_group_cb(G_GNUC_UNUSED GntMenuItem *item, G_GNUC_UNUSED gpointer data)
2662 {
2663 purple_blist_request_add_group();
2664 }
2665
2666 static void
2667 menu_group_set_cb(GntMenuItem *item, G_GNUC_UNUSED gpointer data)
2668 {
2669 const char *id = g_object_get_data(G_OBJECT(item), "grouping-id");
2670 purple_prefs_set_string(PREF_ROOT "/grouping", id);
2671 }
2672
2673 static void
2674 create_menu(void)
2675 {
2676 GntWidget *menu, *sub, *subsub;
2677 GntMenuItem *item;
2678 GntWindow *window;
2679
2680 if (!ggblist)
2681 return;
2682
2683 window = GNT_WINDOW(ggblist->window);
2684 ggblist->menu = menu = gnt_menu_new(GNT_MENU_TOPLEVEL);
2685 gnt_window_set_menu(window, GNT_MENU(menu));
2686
2687 item = gnt_menuitem_new(_("Options"));
2688 gnt_menu_add_item(GNT_MENU(menu), item);
2689
2690 sub = gnt_menu_new(GNT_MENU_POPUP);
2691 gnt_menuitem_set_submenu(item, GNT_MENU(sub));
2692
2693 item = gnt_menuitem_new(_("Send IM..."));
2694 gnt_menuitem_set_id(GNT_MENU_ITEM(item), "send-im");
2695 gnt_menu_add_item(GNT_MENU(sub), item);
2696 gnt_menuitem_set_callback(GNT_MENU_ITEM(item), send_im_select, NULL);
2697
2698 item = gnt_menuitem_new(_("Join Chat..."));
2699 gnt_menuitem_set_id(GNT_MENU_ITEM(item), "join-chat");
2700 gnt_menu_add_item(GNT_MENU(sub), item);
2701 gnt_menuitem_set_callback(GNT_MENU_ITEM(item), join_chat_select, NULL);
2702
2703 item = gnt_menuitem_new(_("Show"));
2704 gnt_menu_add_item(GNT_MENU(sub), item);
2705 subsub = gnt_menu_new(GNT_MENU_POPUP);
2706 gnt_menuitem_set_submenu(item, GNT_MENU(subsub));
2707
2708 item = gnt_menuitem_check_new(_("Empty groups"));
2709 gnt_menuitem_set_id(GNT_MENU_ITEM(item), "show-empty-groups");
2710 gnt_menuitem_check_set_checked(GNT_MENU_ITEM_CHECK(item),
2711 purple_prefs_get_bool(PREF_ROOT "/emptygroups"));
2712 gnt_menu_add_item(GNT_MENU(subsub), item);
2713 gnt_menuitem_set_callback(GNT_MENU_ITEM(item), toggle_pref_cb, PREF_ROOT "/emptygroups");
2714
2715 item = gnt_menuitem_check_new(_("Offline buddies"));
2716 gnt_menuitem_set_id(GNT_MENU_ITEM(item), "show-offline-buddies");
2717 gnt_menuitem_check_set_checked(GNT_MENU_ITEM_CHECK(item),
2718 purple_prefs_get_bool(PREF_ROOT "/showoffline"));
2719 gnt_menu_add_item(GNT_MENU(subsub), item);
2720 gnt_menuitem_set_callback(GNT_MENU_ITEM(item), toggle_pref_cb, PREF_ROOT "/showoffline");
2721
2722 item = gnt_menuitem_new(_("Sort"));
2723 gnt_menu_add_item(GNT_MENU(sub), item);
2724 subsub = gnt_menu_new(GNT_MENU_POPUP);
2725 gnt_menuitem_set_submenu(item, GNT_MENU(subsub));
2726
2727 item = gnt_menuitem_new(_("By Status"));
2728 gnt_menuitem_set_id(GNT_MENU_ITEM(item), "sort-status");
2729 gnt_menu_add_item(GNT_MENU(subsub), item);
2730 gnt_menuitem_set_callback(GNT_MENU_ITEM(item), sort_blist_change_cb, "status");
2731
2732 item = gnt_menuitem_new(_("Alphabetically"));
2733 gnt_menuitem_set_id(GNT_MENU_ITEM(item), "sort-alpha");
2734 gnt_menu_add_item(GNT_MENU(subsub), item);
2735 gnt_menuitem_set_callback(GNT_MENU_ITEM(item), sort_blist_change_cb, "text");
2736
2737 item = gnt_menuitem_new(_("By Log Size"));
2738 gnt_menuitem_set_id(GNT_MENU_ITEM(item), "sort-log");
2739 gnt_menu_add_item(GNT_MENU(subsub), item);
2740 gnt_menuitem_set_callback(GNT_MENU_ITEM(item), sort_blist_change_cb, "log");
2741
2742 item = gnt_menuitem_new(_("Add"));
2743 gnt_menu_add_item(GNT_MENU(sub), item);
2744
2745 subsub = gnt_menu_new(GNT_MENU_POPUP);
2746 gnt_menuitem_set_submenu(item, GNT_MENU(subsub));
2747
2748 item = gnt_menuitem_new(_("Buddy"));
2749 gnt_menuitem_set_id(GNT_MENU_ITEM(item), "add-buddy");
2750 gnt_menu_add_item(GNT_MENU(subsub), item);
2751 gnt_menuitem_set_callback(item, menu_add_buddy_cb, NULL);
2752
2753 item = gnt_menuitem_new(_("Chat"));
2754 gnt_menuitem_set_id(GNT_MENU_ITEM(item), "add-chat");
2755 gnt_menu_add_item(GNT_MENU(subsub), item);
2756 gnt_menuitem_set_callback(item, menu_add_chat_cb, NULL);
2757
2758 item = gnt_menuitem_new(_("Group"));
2759 gnt_menuitem_set_id(GNT_MENU_ITEM(item), "add-group");
2760 gnt_menu_add_item(GNT_MENU(subsub), item);
2761 gnt_menuitem_set_callback(item, menu_add_group_cb, NULL);
2762
2763 ggblist->grouping = item = gnt_menuitem_new(_("Grouping"));
2764 gnt_menu_add_item(GNT_MENU(sub), item);
2765 reconstruct_grouping_menu();
2766
2767 reconstruct_accounts_menu();
2768 gnt_menu_add_item(GNT_MENU(menu), ggblist->accounts);
2769
2770 reconstruct_plugins_menu();
2771 gnt_menu_add_item(GNT_MENU(menu), ggblist->plugins);
2772 }
2773
2774 void
2775 finch_blist_show(void)
2776 {
2777 blist_show(purple_blist_get_default());
2778 }
2779
2780 static void
2781 group_collapsed(G_GNUC_UNUSED GntWidget *widget, PurpleBlistNode *node,
2782 gboolean collapsed, G_GNUC_UNUSED gpointer data)
2783 {
2784 if (PURPLE_IS_GROUP(node))
2785 purple_blist_node_set_bool(node, "collapsed", collapsed);
2786 }
2787
2788 static void
2789 blist_show(G_GNUC_UNUSED PurpleBuddyList *list)
2790 {
2791 GPluginManager *plugin_manager = NULL;
2792
2793 if (ggblist->window) {
2794 gnt_window_present(ggblist->window);
2795 return;
2796 }
2797
2798 ggblist->window = gnt_vwindow_new(FALSE);
2799 gnt_widget_set_name(ggblist->window, "buddylist");
2800 gnt_box_set_toplevel(GNT_BOX(ggblist->window), TRUE);
2801 gnt_box_set_title(GNT_BOX(ggblist->window), _("Buddy List"));
2802 gnt_box_set_pad(GNT_BOX(ggblist->window), 0);
2803
2804 ggblist->tree = gnt_tree_new();
2805
2806 gnt_widget_set_has_border(ggblist->tree, FALSE);
2807 gnt_widget_set_size(ggblist->tree, purple_prefs_get_int(PREF_ROOT "/size/width"),
2808 purple_prefs_get_int(PREF_ROOT "/size/height"));
2809 gnt_widget_set_position(ggblist->window, purple_prefs_get_int(PREF_ROOT "/position/x"),
2810 purple_prefs_get_int(PREF_ROOT "/position/y"));
2811
2812 gnt_box_add_widget(GNT_BOX(ggblist->window), ggblist->tree);
2813
2814 ggblist->status = gnt_combo_box_new();
2815 gnt_box_add_widget(GNT_BOX(ggblist->window), ggblist->status);
2816 ggblist->statustext = gnt_entry_new(NULL);
2817 gnt_box_add_widget(GNT_BOX(ggblist->window), ggblist->statustext);
2818
2819 gnt_widget_show(ggblist->window);
2820
2821 purple_signal_connect(purple_connections_get_handle(), "signed-on", finch_blist_get_handle(),
2822 G_CALLBACK(reconstruct_accounts_menu), NULL);
2823 purple_signal_connect(purple_connections_get_handle(), "signed-off", finch_blist_get_handle(),
2824 G_CALLBACK(reconstruct_accounts_menu), NULL);
2825 purple_signal_connect(purple_accounts_get_handle(), "account-actions-changed", finch_blist_get_handle(),
2826 G_CALLBACK(reconstruct_accounts_menu), NULL);
2827 purple_signal_connect(purple_blist_get_handle(), "buddy-status-changed", finch_blist_get_handle(),
2828 G_CALLBACK(buddy_status_changed), ggblist);
2829 purple_signal_connect(purple_blist_get_handle(), "buddy-idle-changed", finch_blist_get_handle(),
2830 G_CALLBACK(buddy_idle_changed), ggblist);
2831
2832 plugin_manager = gplugin_manager_get_default();
2833 g_signal_connect_object(plugin_manager, "loaded-plugin",
2834 G_CALLBACK(reconstruct_plugins_menu_cb), ggblist, 0);
2835 g_signal_connect_object(plugin_manager, "unloaded-plugin",
2836 G_CALLBACK(reconstruct_plugins_menu_cb), ggblist, 0);
2837
2838 purple_signal_connect(purple_blist_get_handle(), "buddy-signed-on", finch_blist_get_handle(),
2839 G_CALLBACK(buddy_signed_on_off), ggblist);
2840 purple_signal_connect(purple_blist_get_handle(), "buddy-signed-off", finch_blist_get_handle(),
2841 G_CALLBACK(buddy_signed_on_off), ggblist);
2842
2843 g_signal_connect(G_OBJECT(ggblist->tree), "selection_changed", G_CALLBACK(selection_changed), ggblist);
2844 g_signal_connect(G_OBJECT(ggblist->tree), "key_pressed", G_CALLBACK(key_pressed), ggblist);
2845 g_signal_connect(G_OBJECT(ggblist->tree), "context-menu", G_CALLBACK(context_menu), ggblist);
2846 g_signal_connect(G_OBJECT(ggblist->tree), "collapse-toggled", G_CALLBACK(group_collapsed), NULL);
2847 g_signal_connect(G_OBJECT(ggblist->tree), "activate", G_CALLBACK(selection_activate), ggblist);
2848 g_signal_connect_data(G_OBJECT(ggblist->tree), "gained-focus", G_CALLBACK(draw_tooltip),
2849 ggblist, 0, G_CONNECT_AFTER | G_CONNECT_SWAPPED);
2850 g_signal_connect_data(G_OBJECT(ggblist->tree), "lost-focus", G_CALLBACK(remove_peripherals),
2851 ggblist, 0, G_CONNECT_AFTER | G_CONNECT_SWAPPED);
2852 g_signal_connect_data(G_OBJECT(ggblist->window), "workspace-hidden", G_CALLBACK(remove_peripherals),
2853 ggblist, 0, G_CONNECT_AFTER | G_CONNECT_SWAPPED);
2854 g_signal_connect(G_OBJECT(ggblist->tree), "size_changed", G_CALLBACK(size_changed_cb), NULL);
2855 g_signal_connect(G_OBJECT(ggblist->window), "position_set", G_CALLBACK(save_position_cb), NULL);
2856 g_signal_connect(G_OBJECT(ggblist->window), "destroy", G_CALLBACK(reset_blist_window), NULL);
2857
2858 /* Status signals */
2859 purple_signal_connect(purple_savedstatuses_get_handle(), "savedstatus-changed", finch_blist_get_handle(),
2860 G_CALLBACK(savedstatus_changed), NULL);
2861 g_signal_connect(G_OBJECT(ggblist->status), "selection_changed",
2862 G_CALLBACK(status_selection_changed), NULL);
2863 g_signal_connect(G_OBJECT(ggblist->statustext), "key_pressed",
2864 G_CALLBACK(status_text_changed), NULL);
2865
2866 create_menu();
2867
2868 populate_buddylist();
2869
2870 savedstatus_changed(purple_savedstatus_get_current(), NULL);
2871 }
2872
2873 void
2874 finch_blist_uninit(void)
2875 {
2876 }
2877
2878 gboolean finch_blist_get_position(int *x, int *y)
2879 {
2880 if (!ggblist || !ggblist->window)
2881 return FALSE;
2882 gnt_widget_get_position(ggblist->window, x, y);
2883 return TRUE;
2884 }
2885
2886 void finch_blist_set_position(int x, int y)
2887 {
2888 gnt_widget_set_position(ggblist->window, x, y);
2889 }
2890
2891 gboolean finch_blist_get_size(int *width, int *height)
2892 {
2893 if (!ggblist || !ggblist->window)
2894 return FALSE;
2895 gnt_widget_get_size(ggblist->window, width, height);
2896 return TRUE;
2897 }
2898
2899 void finch_blist_set_size(int width, int height)
2900 {
2901 gnt_widget_set_size(ggblist->window, width, height);
2902 }
2903
2904 void finch_blist_install_manager(const FinchBlistManager *manager)
2905 {
2906 if (!g_list_find(managers, manager)) {
2907 managers = g_list_append(managers, (gpointer)manager);
2908 reconstruct_grouping_menu();
2909 if (purple_strequal(manager->id, purple_prefs_get_string(PREF_ROOT "/grouping")))
2910 purple_prefs_trigger_callback(PREF_ROOT "/grouping");
2911 }
2912 }
2913
2914 void finch_blist_uninstall_manager(const FinchBlistManager *manager)
2915 {
2916 if (g_list_find(managers, manager)) {
2917 managers = g_list_remove(managers, manager);
2918 reconstruct_grouping_menu();
2919 if (purple_strequal(manager->id, purple_prefs_get_string(PREF_ROOT "/grouping")))
2920 purple_prefs_trigger_callback(PREF_ROOT "/grouping");
2921 }
2922 }
2923
2924 FinchBlistManager * finch_blist_manager_find(const char *id)
2925 {
2926 GList *iter = managers;
2927 if (!id)
2928 return NULL;
2929
2930 for (; iter; iter = iter->next) {
2931 FinchBlistManager *m = iter->data;
2932 if (purple_strequal(id, m->id))
2933 return m;
2934 }
2935 return NULL;
2936 }
2937
2938 GntTree * finch_blist_get_tree(void)
2939 {
2940 return ggblist ? GNT_TREE(ggblist->tree) : NULL;
2941 }
2942
2943 /**************************************************************************
2944 * GObject code
2945 **************************************************************************/
2946 G_DEFINE_FINAL_TYPE(FinchBuddyList, finch_buddy_list, PURPLE_TYPE_BUDDY_LIST)
2947
2948 static void
2949 finch_buddy_list_init(FinchBuddyList *self)
2950 {
2951 if (!ggblist) {
2952 /* The first buddy list object becomes the default. */
2953 ggblist = self;
2954 }
2955
2956 self->manager = finch_blist_manager_find(
2957 purple_prefs_get_string(PREF_ROOT "/grouping"));
2958 if (!self->manager) {
2959 self->manager = &default_manager;
2960 }
2961 }
2962
2963 static void
2964 finch_buddy_list_finalize(GObject *obj)
2965 {
2966 FinchBuddyList *ggblist = FINCH_BUDDY_LIST(obj);
2967
2968 gnt_widget_destroy(ggblist->window);
2969
2970 G_OBJECT_CLASS(finch_buddy_list_parent_class)->finalize(obj);
2971 }
2972
2973 static void
2974 finch_buddy_list_class_init(FinchBuddyListClass *klass)
2975 {
2976 GObjectClass *obj_class = G_OBJECT_CLASS(klass);
2977 PurpleBuddyListClass *purple_blist_class;
2978
2979 obj_class->finalize = finch_buddy_list_finalize;
2980
2981 purple_blist_class = PURPLE_BUDDY_LIST_CLASS(klass);
2982 purple_blist_class->new_node = new_node;
2983 purple_blist_class->show = blist_show;
2984 purple_blist_class->update = node_update;
2985 purple_blist_class->remove = node_remove;
2986 purple_blist_class->request_add_buddy = finch_request_add_buddy;
2987 purple_blist_class->request_add_chat = finch_request_add_chat;
2988 purple_blist_class->request_add_group = finch_request_add_group;
2989 }
2990
2991 /**************************************************************************
2992 * GBoxed code
2993 **************************************************************************/
2994 static FinchBlistManager *
2995 finch_blist_manager_copy(FinchBlistManager *manager)
2996 {
2997 FinchBlistManager *manager_new;
2998
2999 g_return_val_if_fail(manager != NULL, NULL);
3000
3001 manager_new = g_new(FinchBlistManager, 1);
3002 *manager_new = *manager;
3003
3004 return manager_new;
3005 }
3006
3007 static void
3008 finch_blist_manager_free(FinchBlistManager *manager)
3009 {
3010 g_return_if_fail(manager != NULL);
3011
3012 g_free(manager);
3013 }
3014
3015 GType
3016 finch_blist_manager_get_type(void)
3017 {
3018 static GType type = 0;
3019
3020 if (type == 0) {
3021 type = g_boxed_type_register_static("FinchBlistManager",
3022 (GBoxedCopyFunc)finch_blist_manager_copy,
3023 (GBoxedFreeFunc)finch_blist_manager_free);
3024 }
3025
3026 return type;
3027 }

mercurial