Update AUTHORS file
[centerim5.git] / src / BuddyListNode.cpp
blob369130835d0242eeade9965243381595338b3637
1 /*
2 * Copyright (C) 2007 Mark Pustjens <pustjens@dds.nl>
3 * Copyright (C) 2010-2015 Petr Pavlu <setup@dagobah.cz>
5 * This file is part of CenterIM.
7 * CenterIM is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * CenterIM is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22 #include "BuddyListNode.h"
24 #include "BuddyList.h"
25 #include "Log.h"
26 #include "Utils.h"
28 #include <cppconsui/ColorScheme.h>
29 #include "gettext.h"
31 BuddyListNode *BuddyListNode::createNode(PurpleBlistNode *node)
33 PurpleBlistNodeType type = purple_blist_node_get_type(node);
34 if (type == PURPLE_BLIST_BUDDY_NODE)
35 return new BuddyListBuddy(node);
36 if (type == PURPLE_BLIST_CHAT_NODE)
37 return new BuddyListChat(node);
38 if (type == PURPLE_BLIST_CONTACT_NODE)
39 return new BuddyListContact(node);
40 if (type == PURPLE_BLIST_GROUP_NODE)
41 return new BuddyListGroup(node);
43 LOG->error(_("Unhandled buddy list node '%d'."), type);
44 return NULL;
47 void BuddyListNode::setParent(CppConsUI::Container &parent)
49 Button::setParent(parent);
51 treeview = dynamic_cast<CppConsUI::TreeView *>(&parent);
52 g_assert(treeview);
55 void BuddyListNode::setRefNode(CppConsUI::TreeView::NodeReference n)
57 ref = n;
58 treeview->setCollapsed(ref, true);
61 void BuddyListNode::update()
63 // cache the last_activity time
64 last_activity = purple_blist_node_get_int(blist_node, "last_activity");
66 BuddyListNode *parent_node = getParentNode();
67 // the parent could have changed, so re-parent the node
68 if (parent_node)
69 treeview->setNodeParent(ref, parent_node->getRefNode());
72 void BuddyListNode::sortIn()
74 CppConsUI::TreeView::NodeReference parent_ref;
75 if (purple_blist_node_get_parent(blist_node)) {
76 /* This blist node has got a logical (libpurple) parent, check if it is
77 * possible to find also a cim node. */
78 BuddyListNode *parent_node = getParentNode();
79 if (parent_node)
80 parent_ref = parent_node->getRefNode();
81 else {
82 // there shouldn't be a cim node only if the flat mode is active
83 g_assert(BUDDYLIST->getListMode() == BuddyList::LIST_FLAT);
85 parent_ref = treeview->getRootNode();
88 else {
89 if (PURPLE_BLIST_NODE_IS_GROUP(blist_node)) {
90 // groups don't have parent nodes
91 parent_ref = treeview->getRootNode();
93 else {
94 /* When the new_node() callback is called for a contact/chat/buddy (and
95 * this method is called as a part of that callback) then the node
96 * doesn't have any parent set yet. In such a case, simply return. */
97 return;
101 /* Do the insertion sort. It should be fast enough here because nodes are
102 * usually already sorted and only one node is in a wrong position, so it
103 * kind of runs in O(n). */
104 CppConsUI::TreeView::SiblingIterator i = parent_ref.end();
105 i--;
106 while (true) {
107 // sref is a node that we want to sort in
108 CppConsUI::TreeView::SiblingIterator sref = i;
110 // calculate a stop condition
111 bool stop_flag;
112 if (i != parent_ref.begin()) {
113 stop_flag = false;
114 i--;
116 else
117 stop_flag = true;
119 BuddyListNode *swidget = dynamic_cast<BuddyListNode *>(sref->getWidget());
120 g_assert(swidget);
121 CppConsUI::TreeView::SiblingIterator j = sref;
122 j++;
123 while (j != parent_ref.end()) {
124 BuddyListNode *n = dynamic_cast<BuddyListNode *>(j->getWidget());
125 g_assert(n);
127 if (swidget->lessOrEqual(*n)) {
128 treeview->moveNodeBefore(sref, j);
129 break;
131 j++;
133 // the node is last in the list
134 if (j == parent_ref.end())
135 treeview->moveNodeAfter(sref, --j);
137 if (stop_flag)
138 break;
142 BuddyListNode *BuddyListNode::getParentNode() const
144 PurpleBlistNode *parent = purple_blist_node_get_parent(blist_node);
145 if (!parent)
146 return NULL;
148 return reinterpret_cast<BuddyListNode *>(
149 purple_blist_node_get_ui_data(parent));
152 BuddyListNode::ContextMenu::ContextMenu(BuddyListNode &parent_node_)
153 : MenuWindow(parent_node_, AUTOSIZE, AUTOSIZE), parent_node(&parent_node_)
157 void BuddyListNode::ContextMenu::onMenuAction(
158 Button & /*activator*/, PurpleCallback callback, void *data)
160 g_assert(callback);
162 typedef void (*TypedCallback)(void *, void *);
163 TypedCallback real_callback = reinterpret_cast<TypedCallback>(callback);
164 real_callback(parent_node->getPurpleBlistNode(), data);
166 close();
169 void BuddyListNode::ContextMenu::appendMenuAction(
170 MenuWindow &menu, PurpleMenuAction *act)
172 if (!act) {
173 menu.appendSeparator();
174 return;
177 if (!act->children) {
178 if (act->callback)
179 menu.appendItem(
180 act->label, sigc::bind(sigc::mem_fun(this, &ContextMenu::onMenuAction),
181 act->callback, act->data));
182 else {
183 // TODO display non-focusable widget?
186 else {
187 MenuWindow *submenu = new MenuWindow(0, 0, AUTOSIZE, AUTOSIZE);
188 menu.appendSubMenu(act->label, *submenu);
190 for (GList *l = act->children; l; l = l->next) {
191 PurpleMenuAction *act = reinterpret_cast<PurpleMenuAction *>(l->data);
192 appendMenuAction(*submenu, act);
195 // free memory associated with the children
196 g_list_free(act->children);
197 act->children = NULL;
200 // free the menu action
201 purple_menu_action_free(act);
204 void BuddyListNode::ContextMenu::appendProtocolMenu(PurpleConnection *gc)
206 PurplePluginProtocolInfo *prpl_info =
207 PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc));
208 if (!prpl_info || !prpl_info->blist_node_menu)
209 return;
211 GList *ll = prpl_info->blist_node_menu(parent_node->getPurpleBlistNode());
212 for (GList *l = ll; l; l = l->next) {
213 PurpleMenuAction *act = reinterpret_cast<PurpleMenuAction *>(l->data);
214 appendMenuAction(*this, act);
217 if (ll) {
218 // append a separator because there has been some items
219 appendSeparator();
222 g_list_free(ll);
225 void BuddyListNode::ContextMenu::appendExtendedMenu()
227 GList *ll =
228 purple_blist_node_get_extended_menu(parent_node->getPurpleBlistNode());
229 for (GList *l = ll; l; l = l->next) {
230 PurpleMenuAction *act = reinterpret_cast<PurpleMenuAction *>(l->data);
231 appendMenuAction(*this, act);
234 if (ll) {
235 // append a separator because there has been some items
236 appendSeparator();
239 g_list_free(ll);
242 BuddyListNode::BuddyListNode(PurpleBlistNode *node_)
243 : treeview(NULL), blist_node(node_), last_activity(0)
245 purple_blist_node_set_ui_data(blist_node, this);
246 signal_activate.connect(sigc::mem_fun(this, &BuddyListNode::onActivate));
247 declareBindables();
250 BuddyListNode::~BuddyListNode()
252 purple_blist_node_set_ui_data(blist_node, NULL);
255 bool BuddyListNode::lessOrEqualByType(const BuddyListNode &other) const
257 // group < contact < buddy < chat < other
258 PurpleBlistNodeType t1 = purple_blist_node_get_type(blist_node);
259 PurpleBlistNodeType t2 = purple_blist_node_get_type(other.blist_node);
260 return t1 <= t2;
263 bool BuddyListNode::lessOrEqualByBuddySort(
264 PurpleBuddy *left, PurpleBuddy *right) const
266 BuddyList::BuddySortMode mode = BUDDYLIST->getBuddySortMode();
267 int a, b;
269 switch (mode) {
270 case BuddyList::BUDDY_SORT_BY_NAME:
271 break;
272 case BuddyList::BUDDY_SORT_BY_STATUS:
273 a = getBuddyStatusWeight(left);
274 b = getBuddyStatusWeight(right);
275 if (a != b)
276 return a > b;
277 break;
278 case BuddyList::BUDDY_SORT_BY_ACTIVITY: {
279 /* Compare buddies according to their last activity.
281 * It is possible that a blist node will not have the ui_data set. For
282 * instance, this happens when libpurple informs the program that a blist
283 * node is about to be removed. At that point, an associated BuddyListNode
284 * is destroyed, a parent node is updated and the parent tries to update its
285 * position according to its priority buddy. This buddy will not have the
286 * ui_data set because the BuddyListNode has been already freed.
288 * In such a case, the cached value cannot be obtained and value 0 will be
289 * used instead. */
290 BuddyListNode *bnode_left = reinterpret_cast<BuddyListNode *>(
291 purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(left)));
292 BuddyListNode *bnode_right = reinterpret_cast<BuddyListNode *>(
293 purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(right)));
294 a = bnode_left ? bnode_left->last_activity : 0;
295 b = bnode_right ? bnode_right->last_activity : 0;
296 if (a != b)
297 return a > b;
298 } break;
300 return g_utf8_collate(
301 purple_buddy_get_alias(left), purple_buddy_get_alias(right)) <= 0;
304 const char *BuddyListNode::getBuddyStatus(PurpleBuddy *buddy) const
306 if (!purple_account_is_connected(purple_buddy_get_account(buddy)))
307 return "";
309 PurplePresence *presence = purple_buddy_get_presence(buddy);
310 PurpleStatus *status = purple_presence_get_active_status(presence);
311 return Utils::getStatusIndicator(status);
314 int BuddyListNode::getBuddyStatusWeight(PurpleBuddy *buddy) const
316 if (!purple_account_is_connected(purple_buddy_get_account(buddy)))
317 return 0;
319 PurplePresence *presence = purple_buddy_get_presence(buddy);
320 PurpleStatus *status = purple_presence_get_active_status(presence);
321 PurpleStatusType *status_type = purple_status_get_type(status);
322 PurpleStatusPrimitive prim = purple_status_type_get_primitive(status_type);
324 switch (prim) {
325 case PURPLE_STATUS_OFFLINE:
326 return 0;
327 default:
328 return 1;
329 case PURPLE_STATUS_UNSET:
330 return 2;
331 case PURPLE_STATUS_UNAVAILABLE:
332 return 3;
333 case PURPLE_STATUS_AWAY:
334 return 4;
335 case PURPLE_STATUS_EXTENDED_AWAY:
336 return 5;
337 case PURPLE_STATUS_MOBILE:
338 return 6;
339 case PURPLE_STATUS_MOOD:
340 return 7;
341 case PURPLE_STATUS_TUNE:
342 return 8;
343 case PURPLE_STATUS_INVISIBLE:
344 return 9;
345 case PURPLE_STATUS_AVAILABLE:
346 return 10;
350 void BuddyListNode::updateFilterVisibility(const char *name)
352 if (!isVisible())
353 return;
355 const char *filter = BUDDYLIST->getFilterString();
356 if (!filter[0])
357 return;
359 // filtering is active
360 setVisibility(purple_strcasestr(name, filter));
363 void BuddyListNode::retrieveUserInfoForName(
364 PurpleConnection *gc, const char *name) const
366 PurpleNotifyUserInfo *info = purple_notify_user_info_new();
367 purple_notify_user_info_add_pair(info, _("Information"), _("Retrieving..."));
368 purple_notify_userinfo(gc, name, info, NULL, NULL);
369 purple_notify_user_info_destroy(info);
370 serv_get_info(gc, name);
373 void BuddyListNode::actionOpenContextMenu()
375 openContextMenu();
378 void BuddyListNode::declareBindables()
380 declareBindable("buddylist", "contextmenu",
381 sigc::mem_fun(this, &BuddyListNode::actionOpenContextMenu),
382 InputProcessor::BINDABLE_NORMAL);
385 bool BuddyListBuddy::lessOrEqual(const BuddyListNode &other) const
387 const BuddyListBuddy *o = dynamic_cast<const BuddyListBuddy *>(&other);
388 if (o)
389 return lessOrEqualByBuddySort(buddy, o->buddy);
390 return lessOrEqualByType(other);
393 void BuddyListBuddy::update()
395 BuddyListNode::update();
397 const char *status = getBuddyStatus(buddy);
398 const char *alias = purple_buddy_get_alias(buddy);
399 if (status[0]) {
400 char *text = g_strdup_printf("%s %s", status, alias);
401 setText(text);
402 g_free(text);
404 else
405 setText(alias);
407 sortIn();
409 updateColorScheme();
411 if (!purple_account_is_connected(purple_buddy_get_account(buddy))) {
412 // hide if account is offline
413 setVisibility(false);
415 else
416 setVisibility(BUDDYLIST->getShowOfflineBuddiesPref() || status[0]);
418 updateFilterVisibility(alias);
421 void BuddyListBuddy::onActivate(Button & /*activator*/)
423 PurpleAccount *account = purple_buddy_get_account(buddy);
424 const char *name = purple_buddy_get_name(buddy);
425 PurpleConversation *conv =
426 purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, name, account);
428 if (!conv)
429 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, name);
430 purple_conversation_present(conv);
433 const char *BuddyListBuddy::toString() const
435 return purple_buddy_get_alias(buddy);
438 void BuddyListBuddy::retrieveUserInfo()
440 PurpleConnection *gc =
441 purple_account_get_connection(purple_buddy_get_account(buddy));
442 retrieveUserInfoForName(gc, purple_buddy_get_name(buddy));
445 BuddyListBuddy::BuddyContextMenu::BuddyContextMenu(
446 BuddyListBuddy &parent_buddy_)
447 : ContextMenu(parent_buddy_), parent_buddy(&parent_buddy_)
449 appendProtocolMenu(purple_account_get_connection(
450 purple_buddy_get_account(parent_buddy->getPurpleBuddy())));
451 appendExtendedMenu();
453 appendItem(
454 _("Information..."), sigc::mem_fun(this, &BuddyContextMenu::onInformation));
455 appendItem(
456 _("Alias..."), sigc::mem_fun(this, &BuddyContextMenu::onChangeAlias));
457 appendItem(_("Delete..."), sigc::mem_fun(this, &BuddyContextMenu::onRemove));
460 void BuddyListBuddy::BuddyContextMenu::onInformation(Button & /*activator*/)
462 parent_buddy->retrieveUserInfo();
463 close();
466 void BuddyListBuddy::BuddyContextMenu::changeAliasResponseHandler(
467 CppConsUI::InputDialog &activator,
468 CppConsUI::AbstractDialog::ResponseType response)
470 if (response != CppConsUI::AbstractDialog::RESPONSE_OK)
471 return;
473 PurpleBuddy *buddy = parent_buddy->getPurpleBuddy();
474 purple_blist_alias_buddy(buddy, activator.getText());
475 serv_alias_buddy(buddy);
477 // close context menu
478 close();
481 void BuddyListBuddy::BuddyContextMenu::onChangeAlias(Button & /*activator*/)
483 PurpleBuddy *buddy = parent_buddy->getPurpleBuddy();
484 CppConsUI::InputDialog *dialog =
485 new CppConsUI::InputDialog(_("Alias"), purple_buddy_get_alias(buddy));
486 dialog->signal_response.connect(
487 sigc::mem_fun(this, &BuddyContextMenu::changeAliasResponseHandler));
488 dialog->show();
491 void BuddyListBuddy::BuddyContextMenu::removeResponseHandler(
492 CppConsUI::MessageDialog & /*activator*/,
493 CppConsUI::AbstractDialog::ResponseType response)
495 if (response != CppConsUI::AbstractDialog::RESPONSE_OK)
496 return;
498 PurpleBuddy *buddy = parent_buddy->getPurpleBuddy();
499 purple_account_remove_buddy(
500 purple_buddy_get_account(buddy), buddy, purple_buddy_get_group(buddy));
502 /* Close the context menu before the buddy is deleted because its deletion
503 * can lead to destruction of this object. */
504 close();
506 purple_blist_remove_buddy(buddy);
509 void BuddyListBuddy::BuddyContextMenu::onRemove(Button & /*activator*/)
511 PurpleBuddy *buddy = parent_buddy->getPurpleBuddy();
512 char *msg = g_strdup_printf(
513 _("Are you sure you want to delete buddy %s from the list?"),
514 purple_buddy_get_alias(buddy));
515 CppConsUI::MessageDialog *dialog =
516 new CppConsUI::MessageDialog(_("Buddy deletion"), msg);
517 g_free(msg);
518 dialog->signal_response.connect(
519 sigc::mem_fun(this, &BuddyContextMenu::removeResponseHandler));
520 dialog->show();
523 int BuddyListBuddy::getColorPair(const char *widget, const char *property) const
525 if (BUDDYLIST->getColorizationMode() != BuddyList::COLOR_BY_ACCOUNT ||
526 strcmp(property, "normal"))
527 return Button::getColorPair(widget, property);
529 PurpleAccount *account = purple_buddy_get_account(buddy);
530 int fg = purple_account_get_ui_int(account, "centerim5",
531 "buddylist-foreground-color", CppConsUI::Curses::Color::DEFAULT);
532 int bg = purple_account_get_ui_int(account, "centerim5",
533 "buddylist-background-color", CppConsUI::Curses::Color::DEFAULT);
535 CppConsUI::ColorScheme::Color c(fg, bg);
536 return COLORSCHEME->getColorPair(c);
539 void BuddyListBuddy::openContextMenu()
541 ContextMenu *w = new BuddyContextMenu(*this);
542 w->show();
545 BuddyListBuddy::BuddyListBuddy(PurpleBlistNode *node_) : BuddyListNode(node_)
547 setColorScheme("buddylistbuddy");
549 buddy = PURPLE_BUDDY(blist_node);
552 void BuddyListBuddy::updateColorScheme()
554 char *new_scheme;
556 switch (BUDDYLIST->getColorizationMode()) {
557 case BuddyList::COLOR_BY_STATUS:
558 new_scheme = Utils::getColorSchemeString("buddylistbuddy", buddy);
559 setColorScheme(new_scheme);
560 g_free(new_scheme);
561 break;
562 default:
563 // note: COLOR_BY_ACCOUNT case is handled by BuddyListBuddy::draw()
564 setColorScheme("buddylistbuddy");
565 break;
569 bool BuddyListChat::lessOrEqual(const BuddyListNode &other) const
571 const BuddyListChat *o = dynamic_cast<const BuddyListChat *>(&other);
572 if (o)
573 return g_utf8_collate(
574 purple_chat_get_name(chat), purple_chat_get_name(o->chat)) <= 0;
575 return lessOrEqualByType(other);
578 void BuddyListChat::update()
580 BuddyListNode::update();
582 const char *name = purple_chat_get_name(chat);
583 setText(name);
585 sortIn();
587 // hide if account is offline
588 setVisibility(purple_account_is_connected(purple_chat_get_account(chat)));
590 updateFilterVisibility(name);
593 void BuddyListChat::onActivate(Button & /*activator*/)
595 PurpleAccount *account = purple_chat_get_account(chat);
596 PurplePluginProtocolInfo *prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(
597 purple_find_prpl(purple_account_get_protocol_id(account)));
598 GHashTable *components = purple_chat_get_components(chat);
600 char *chat_name = NULL;
601 if (prpl_info && prpl_info->get_chat_name)
602 chat_name = prpl_info->get_chat_name(components);
604 const char *name;
605 if (chat_name)
606 name = chat_name;
607 else
608 name = purple_chat_get_name(chat);
610 PurpleConversation *conv =
611 purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, name, account);
612 if (conv)
613 purple_conversation_present(conv);
615 serv_join_chat(purple_account_get_connection(account), components);
617 g_free(chat_name);
620 const char *BuddyListChat::toString() const
622 return purple_chat_get_name(chat);
625 BuddyListChat::ChatContextMenu::ChatContextMenu(BuddyListChat &parent_chat_)
626 : ContextMenu(parent_chat_), parent_chat(&parent_chat_)
628 appendProtocolMenu(purple_account_get_connection(
629 purple_chat_get_account(parent_chat->getPurpleChat())));
630 appendExtendedMenu();
632 appendItem(
633 _("Alias..."), sigc::mem_fun(this, &ChatContextMenu::onChangeAlias));
634 appendItem(_("Delete..."), sigc::mem_fun(this, &ChatContextMenu::onRemove));
637 void BuddyListChat::ChatContextMenu::changeAliasResponseHandler(
638 CppConsUI::InputDialog &activator,
639 CppConsUI::AbstractDialog::ResponseType response)
641 if (response != CppConsUI::AbstractDialog::RESPONSE_OK)
642 return;
644 PurpleChat *chat = parent_chat->getPurpleChat();
645 purple_blist_alias_chat(chat, activator.getText());
647 // close context menu
648 close();
651 void BuddyListChat::ChatContextMenu::onChangeAlias(Button & /*activator*/)
653 PurpleChat *chat = parent_chat->getPurpleChat();
654 CppConsUI::InputDialog *dialog =
655 new CppConsUI::InputDialog(_("Alias"), purple_chat_get_name(chat));
656 dialog->signal_response.connect(
657 sigc::mem_fun(this, &ChatContextMenu::changeAliasResponseHandler));
658 dialog->show();
661 void BuddyListChat::ChatContextMenu::removeResponseHandler(
662 CppConsUI::MessageDialog & /*activator*/,
663 CppConsUI::AbstractDialog::ResponseType response)
665 if (response != CppConsUI::AbstractDialog::RESPONSE_OK)
666 return;
668 PurpleChat *chat = parent_chat->getPurpleChat();
670 /* Close the context menu before the chat is deleted because its deletion
671 * can lead to destruction of this object. */
672 close();
674 purple_blist_remove_chat(chat);
677 void BuddyListChat::ChatContextMenu::onRemove(Button & /*activator*/)
679 PurpleChat *chat = parent_chat->getPurpleChat();
680 char *msg =
681 g_strdup_printf(_("Are you sure you want to delete chat %s from the list?"),
682 purple_chat_get_name(chat));
683 CppConsUI::MessageDialog *dialog =
684 new CppConsUI::MessageDialog(_("Chat deletion"), msg);
685 g_free(msg);
686 dialog->signal_response.connect(
687 sigc::mem_fun(this, &ChatContextMenu::removeResponseHandler));
688 dialog->show();
691 void BuddyListChat::openContextMenu()
693 ContextMenu *w = new ChatContextMenu(*this);
694 w->show();
697 BuddyListChat::BuddyListChat(PurpleBlistNode *node_) : BuddyListNode(node_)
699 setColorScheme("buddylistchat");
701 chat = PURPLE_CHAT(blist_node);
704 bool BuddyListContact::lessOrEqual(const BuddyListNode &other) const
706 const BuddyListContact *o = dynamic_cast<const BuddyListContact *>(&other);
707 if (o) {
708 PurpleBuddy *left = purple_contact_get_priority_buddy(contact);
709 PurpleBuddy *right = purple_contact_get_priority_buddy(o->contact);
710 return lessOrEqualByBuddySort(left, right);
712 return lessOrEqualByType(other);
715 void BuddyListContact::update()
717 BuddyListNode::update();
719 PurpleBuddy *buddy = purple_contact_get_priority_buddy(contact);
720 if (!buddy) {
721 /* The contact does not have any associated buddy, ignore it until it gets
722 * a buddy assigned. */
723 setText("*Contact*");
724 setVisibility(false);
725 return;
728 // format contact size
729 char *size;
730 if (contact->currentsize > 1)
731 size = g_strdup_printf(" (%d)", contact->currentsize);
732 else
733 size = NULL;
735 // format contact label
736 const char *alias = purple_contact_get_alias(contact);
737 const char *status = getBuddyStatus(buddy);
738 char *text;
739 if (status[0])
740 text = g_strdup_printf("%s %s%s", status, alias, size ? size : "");
741 else
742 text = g_strdup_printf("%s%s", alias, size ? size : "");
743 setText(text);
744 g_free(size);
745 g_free(text);
747 sortIn();
749 updateColorScheme();
751 if (!purple_account_is_connected(purple_buddy_get_account(buddy))) {
752 // hide if account is offline
753 setVisibility(false);
755 else
756 setVisibility(BUDDYLIST->getShowOfflineBuddiesPref() || status[0]);
758 updateFilterVisibility(alias);
761 void BuddyListContact::onActivate(Button &activator)
763 PurpleBuddy *buddy = purple_contact_get_priority_buddy(contact);
764 BuddyListNode *bnode = reinterpret_cast<BuddyListNode *>(
765 purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(buddy)));
766 if (bnode)
767 bnode->onActivate(activator);
770 const char *BuddyListContact::toString() const
772 return purple_contact_get_alias(contact);
775 void BuddyListContact::setRefNode(CppConsUI::TreeView::NodeReference n)
777 BuddyListNode::setRefNode(n);
778 treeview->setNodeStyle(n, CppConsUI::TreeView::STYLE_VOID);
781 void BuddyListContact::retrieveUserInfo()
783 PurpleBuddy *buddy = purple_contact_get_priority_buddy(contact);
784 PurpleConnection *gc =
785 purple_account_get_connection(purple_buddy_get_account(buddy));
786 retrieveUserInfoForName(gc, purple_buddy_get_name(buddy));
789 BuddyListContact::ContactContextMenu::ContactContextMenu(
790 BuddyListContact &parent_contact_)
791 : ContextMenu(parent_contact_), parent_contact(&parent_contact_)
793 appendExtendedMenu();
795 if (parent_contact->isCollapsed())
796 appendItem(_("Expand"),
797 sigc::bind(sigc::mem_fun(this, &ContactContextMenu::onExpandRequest),
798 true));
799 else
800 appendItem(_("Collapse"),
801 sigc::bind(sigc::mem_fun(this, &ContactContextMenu::onExpandRequest),
802 false));
804 appendItem(_("Information..."),
805 sigc::mem_fun(this, &ContactContextMenu::onInformation));
806 appendItem(
807 _("Alias..."), sigc::mem_fun(this, &ContactContextMenu::onChangeAlias));
808 appendItem(
809 _("Delete..."), sigc::mem_fun(this, &ContactContextMenu::onRemove));
811 CppConsUI::MenuWindow *groups =
812 new CppConsUI::MenuWindow(*this, AUTOSIZE, AUTOSIZE);
814 for (PurpleBlistNode *node = purple_blist_get_root(); node;
815 node = purple_blist_node_get_sibling_next(node)) {
816 if (!PURPLE_BLIST_NODE_IS_GROUP(node))
817 continue;
819 PurpleGroup *group = PURPLE_GROUP(node);
820 CppConsUI::Button *button = groups->appendItem(purple_group_get_name(group),
821 sigc::bind(sigc::mem_fun(this, &ContactContextMenu::onMoveTo), group));
822 if (purple_contact_get_group(parent_contact->getPurpleContact()) == group)
823 button->grabFocus();
826 appendSubMenu(_("Move to..."), *groups);
829 void BuddyListContact::ContactContextMenu::onExpandRequest(
830 Button & /*activator*/, bool expand)
832 parent_contact->setCollapsed(!expand);
833 close();
836 void BuddyListContact::ContactContextMenu::onInformation(Button & /*activator*/)
838 parent_contact->retrieveUserInfo();
839 close();
842 void BuddyListContact::ContactContextMenu::changeAliasResponseHandler(
843 CppConsUI::InputDialog &activator,
844 CppConsUI::AbstractDialog::ResponseType response)
846 if (response != CppConsUI::AbstractDialog::RESPONSE_OK)
847 return;
849 PurpleContact *contact = parent_contact->getPurpleContact();
850 if (contact->alias)
851 purple_blist_alias_contact(contact, activator.getText());
852 else {
853 PurpleBuddy *buddy = purple_contact_get_priority_buddy(contact);
854 purple_blist_alias_buddy(buddy, activator.getText());
855 serv_alias_buddy(buddy);
858 // close context menu
859 close();
862 void BuddyListContact::ContactContextMenu::onChangeAlias(Button & /*activator*/)
864 PurpleContact *contact = parent_contact->getPurpleContact();
865 CppConsUI::InputDialog *dialog =
866 new CppConsUI::InputDialog(_("Alias"), purple_contact_get_alias(contact));
867 dialog->signal_response.connect(
868 sigc::mem_fun(this, &ContactContextMenu::changeAliasResponseHandler));
869 dialog->show();
872 void BuddyListContact::ContactContextMenu::removeResponseHandler(
873 CppConsUI::MessageDialog & /*activator*/,
874 CppConsUI::AbstractDialog::ResponseType response)
876 if (response != CppConsUI::AbstractDialog::RESPONSE_OK)
877 return;
879 // based on gtkdialogs.c:pidgin_dialogs_remove_contact_cb()
880 PurpleContact *contact = parent_contact->getPurpleContact();
881 PurpleBlistNode *cnode = PURPLE_BLIST_NODE(contact);
882 PurpleGroup *group = purple_contact_get_group(contact);
884 for (PurpleBlistNode *bnode = purple_blist_node_get_first_child(cnode); bnode;
885 bnode = purple_blist_node_get_sibling_next(bnode)) {
886 PurpleBuddy *buddy = PURPLE_BUDDY(bnode);
887 PurpleAccount *account = purple_buddy_get_account(buddy);
888 if (purple_account_is_connected(account))
889 purple_account_remove_buddy(account, buddy, group);
892 /* Close the context menu before the contact is deleted because its deletion
893 * can lead to destruction of this object. */
894 close();
896 purple_blist_remove_contact(contact);
899 void BuddyListContact::ContactContextMenu::onRemove(Button & /*activator*/)
901 PurpleContact *contact = parent_contact->getPurpleContact();
902 char *msg = g_strdup_printf(
903 _("Are you sure you want to delete contact %s from the list?"),
904 purple_contact_get_alias(contact));
905 CppConsUI::MessageDialog *dialog =
906 new CppConsUI::MessageDialog(_("Contact deletion"), msg);
907 g_free(msg);
908 dialog->signal_response.connect(
909 sigc::mem_fun(this, &ContactContextMenu::removeResponseHandler));
910 dialog->show();
913 void BuddyListContact::ContactContextMenu::onMoveTo(
914 Button & /*activator*/, PurpleGroup *group)
916 PurpleContact *contact = parent_contact->getPurpleContact();
917 close();
919 purple_blist_add_contact(contact, group, NULL);
922 int BuddyListContact::getColorPair(
923 const char *widget, const char *property) const
925 if (BUDDYLIST->getColorizationMode() != BuddyList::COLOR_BY_ACCOUNT ||
926 strcmp(property, "normal"))
927 return Button::getColorPair(widget, property);
929 PurpleAccount *account =
930 purple_buddy_get_account(purple_contact_get_priority_buddy(contact));
931 int fg = purple_account_get_ui_int(account, "centerim5",
932 "buddylist-foreground-color", CppConsUI::Curses::Color::DEFAULT);
933 int bg = purple_account_get_ui_int(account, "centerim5",
934 "buddylist-background-color", CppConsUI::Curses::Color::DEFAULT);
936 CppConsUI::ColorScheme::Color c(fg, bg);
937 return COLORSCHEME->getColorPair(c);
940 void BuddyListContact::openContextMenu()
942 ContextMenu *w = new ContactContextMenu(*this);
943 w->show();
946 BuddyListContact::BuddyListContact(PurpleBlistNode *node_)
947 : BuddyListNode(node_)
949 setColorScheme("buddylistcontact");
951 contact = PURPLE_CONTACT(blist_node);
954 void BuddyListContact::updateColorScheme()
956 char *new_scheme;
957 PurpleBuddy *buddy;
959 switch (BUDDYLIST->getColorizationMode()) {
960 case BuddyList::COLOR_BY_STATUS:
961 buddy = purple_contact_get_priority_buddy(contact);
962 new_scheme = Utils::getColorSchemeString("buddylistcontact", buddy);
963 setColorScheme(new_scheme);
964 g_free(new_scheme);
965 break;
966 default:
967 // note: COLOR_BY_ACCOUNT case is handled by BuddyListContact::draw()
968 setColorScheme("buddylistcontact");
969 break;
973 bool BuddyListGroup::lessOrEqual(const BuddyListNode &other) const
975 /* If the groups aren't sorted but ordered manually then this method isn't
976 * used. */
978 const BuddyListGroup *o = dynamic_cast<const BuddyListGroup *>(&other);
979 if (o)
980 return g_utf8_collate(purple_group_get_name(group),
981 purple_group_get_name(o->group)) <= 0;
982 return lessOrEqualByType(other);
985 void BuddyListGroup::update()
987 BuddyListNode::update();
989 setText(purple_group_get_name(group));
991 // sort in the group
992 BuddyList::GroupSortMode mode = BUDDYLIST->getGroupSortMode();
993 switch (mode) {
994 case BuddyList::GROUP_SORT_BY_USER: {
995 /* Note that the sorting below works even if there was a contact/chat/buddy
996 * node that is attached at the root level of the blist treeview. This
997 * happens when such a node was just created (the new_node() callback was
998 * called) but the node doesn't have any parent yet. */
1000 PurpleBlistNode *prev = purple_blist_node_get_sibling_prev(blist_node);
1002 if (prev) {
1003 // it better be a group node
1004 g_assert(PURPLE_BLIST_NODE_IS_GROUP(prev));
1006 BuddyListNode *bnode =
1007 reinterpret_cast<BuddyListNode *>(purple_blist_node_get_ui_data(prev));
1008 // there has to be ui_data set for all group nodes!
1009 g_assert(bnode);
1011 treeview->moveNodeAfter(ref, bnode->getRefNode());
1013 else {
1014 // the group is the first one in the list
1015 CppConsUI::TreeView::NodeReference parent_ref = treeview->getRootNode();
1016 treeview->moveNodeBefore(ref, parent_ref.begin());
1018 } break;
1019 case BuddyList::GROUP_SORT_BY_NAME:
1020 sortIn();
1021 break;
1024 bool vis = true;
1025 if (!BUDDYLIST->getShowEmptyGroupsPref())
1026 vis = purple_blist_get_group_size(group, FALSE);
1027 setVisibility(vis);
1030 void BuddyListGroup::onActivate(Button & /*activator*/)
1032 treeview->toggleCollapsed(ref);
1033 purple_blist_node_set_bool(blist_node, "collapsed", ref->isCollapsed());
1036 const char *BuddyListGroup::toString() const
1038 return purple_group_get_name(group);
1041 void BuddyListGroup::setRefNode(CppConsUI::TreeView::NodeReference n)
1043 BuddyListNode::setRefNode(n);
1044 initCollapsedState();
1047 void BuddyListGroup::initCollapsedState()
1049 /* This can't be done when the purple_blist_load() function was called
1050 * because node settings are unavailable at that time. */
1051 treeview->setCollapsed(
1052 ref, purple_blist_node_get_bool(blist_node, "collapsed"));
1055 BuddyListGroup::GroupContextMenu::GroupContextMenu(
1056 BuddyListGroup &parent_group_)
1057 : ContextMenu(parent_group_), parent_group(&parent_group_)
1059 appendExtendedMenu();
1061 appendItem(_("Rename..."), sigc::mem_fun(this, &GroupContextMenu::onRename));
1062 appendItem(_("Delete..."), sigc::mem_fun(this, &GroupContextMenu::onRemove));
1064 if (BUDDYLIST->getGroupSortMode() == BuddyList::GROUP_SORT_BY_USER) {
1065 /* If the manual sorting is enabled then show a menu item and a submenu
1066 * for group moving. */
1067 CppConsUI::MenuWindow *groups =
1068 new CppConsUI::MenuWindow(*this, AUTOSIZE, AUTOSIZE);
1070 groups->appendItem(_("-Top-"),
1071 sigc::bind(sigc::mem_fun(this, &GroupContextMenu::onMoveAfter),
1072 static_cast<PurpleGroup *>(NULL)));
1073 for (PurpleBlistNode *node = purple_blist_get_root(); node;
1074 node = purple_blist_node_get_sibling_next(node)) {
1075 if (!PURPLE_BLIST_NODE_IS_GROUP(node))
1076 continue;
1078 PurpleGroup *group = PURPLE_GROUP(node);
1079 groups->appendItem(purple_group_get_name(group),
1080 sigc::bind(sigc::mem_fun(this, &GroupContextMenu::onMoveAfter), group));
1083 appendSubMenu(_("Move after..."), *groups);
1087 void BuddyListGroup::GroupContextMenu::renameResponseHandler(
1088 CppConsUI::InputDialog &activator,
1089 CppConsUI::AbstractDialog::ResponseType response)
1091 if (response != CppConsUI::AbstractDialog::RESPONSE_OK)
1092 return;
1094 const char *name = activator.getText();
1095 PurpleGroup *group = parent_group->getPurpleGroup();
1096 PurpleGroup *other = purple_find_group(name);
1097 if (other && !purple_utf8_strcasecmp(name, purple_group_get_name(group))) {
1098 LOG->message(_("Specified group is already in the list."));
1099 /* TODO Add group merging. Note that purple_blist_rename_group() can do
1100 * the merging. */
1102 else
1103 purple_blist_rename_group(group, name);
1105 // close context menu
1106 close();
1109 void BuddyListGroup::GroupContextMenu::onRename(Button & /*activator*/)
1111 PurpleGroup *group = parent_group->getPurpleGroup();
1112 CppConsUI::InputDialog *dialog =
1113 new CppConsUI::InputDialog(_("Rename"), purple_group_get_name(group));
1114 dialog->signal_response.connect(
1115 sigc::mem_fun(this, &GroupContextMenu::renameResponseHandler));
1116 dialog->show();
1119 void BuddyListGroup::GroupContextMenu::removeResponseHandler(
1120 CppConsUI::MessageDialog & /*activator*/,
1121 CppConsUI::AbstractDialog::ResponseType response)
1123 if (response != CppConsUI::AbstractDialog::RESPONSE_OK)
1124 return;
1126 // based on gtkdialogs.c:pidgin_dialogs_remove_group_cb()
1127 PurpleGroup *group = parent_group->getPurpleGroup();
1128 PurpleBlistNode *cnode =
1129 purple_blist_node_get_first_child(PURPLE_BLIST_NODE(group));
1130 while (cnode) {
1131 if (PURPLE_BLIST_NODE_IS_CONTACT(cnode)) {
1132 PurpleBlistNode *bnode = purple_blist_node_get_first_child(cnode);
1133 cnode = purple_blist_node_get_sibling_next(cnode);
1134 while (bnode)
1135 if (PURPLE_BLIST_NODE_IS_BUDDY(bnode)) {
1136 PurpleBuddy *buddy = PURPLE_BUDDY(bnode);
1137 PurpleAccount *account = purple_buddy_get_account(buddy);
1138 bnode = purple_blist_node_get_sibling_next(bnode);
1139 if (purple_account_is_connected(account)) {
1140 purple_account_remove_buddy(account, buddy, group);
1141 purple_blist_remove_buddy(buddy);
1144 else
1145 bnode = purple_blist_node_get_sibling_next(bnode);
1147 else if (PURPLE_BLIST_NODE_IS_CHAT(cnode)) {
1148 PurpleChat *chat = PURPLE_CHAT(cnode);
1149 cnode = purple_blist_node_get_sibling_next(cnode);
1150 purple_blist_remove_chat(chat);
1152 else
1153 cnode = purple_blist_node_get_sibling_next(cnode);
1156 /* Close the context menu before the group is deleted because its deletion
1157 * can lead to destruction of this object. */
1158 close();
1160 purple_blist_remove_group(group);
1163 void BuddyListGroup::GroupContextMenu::onRemove(Button & /*activator*/)
1165 PurpleGroup *group = parent_group->getPurpleGroup();
1166 char *msg = g_strdup_printf(
1167 _("Are you sure you want to delete group %s from the list?"),
1168 purple_group_get_name(group));
1169 CppConsUI::MessageDialog *dialog =
1170 new CppConsUI::MessageDialog(_("Group deletion"), msg);
1171 g_free(msg);
1172 dialog->signal_response.connect(
1173 sigc::mem_fun(this, &GroupContextMenu::removeResponseHandler));
1174 dialog->show();
1177 void BuddyListGroup::GroupContextMenu::onMoveAfter(
1178 Button & /*activator*/, PurpleGroup *group)
1180 PurpleGroup *moved_group = parent_group->getPurpleGroup();
1181 close();
1183 purple_blist_add_group(moved_group, PURPLE_BLIST_NODE(group));
1186 void BuddyListGroup::openContextMenu()
1188 ContextMenu *w = new GroupContextMenu(*this);
1189 w->show();
1192 BuddyListGroup::BuddyListGroup(PurpleBlistNode *node_) : BuddyListNode(node_)
1194 setColorScheme("buddylistgroup");
1196 group = PURPLE_GROUP(blist_node);
1199 /* vim: set tabstop=2 shiftwidth=2 textwidth=80 expandtab : */