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"
28 #include <cppconsui/ColorScheme.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
);
47 void BuddyListNode::setParent(CppConsUI::Container
&parent
)
49 Button::setParent(parent
);
51 treeview
= dynamic_cast<CppConsUI::TreeView
*>(&parent
);
55 void BuddyListNode::setRefNode(CppConsUI::TreeView::NodeReference 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
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();
80 parent_ref
= parent_node
->getRefNode();
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();
89 if (PURPLE_BLIST_NODE_IS_GROUP(blist_node
)) {
90 // groups don't have parent nodes
91 parent_ref
= treeview
->getRootNode();
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. */
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();
107 // sref is a node that we want to sort in
108 CppConsUI::TreeView::SiblingIterator sref
= i
;
110 // calculate a stop condition
112 if (i
!= parent_ref
.begin()) {
119 BuddyListNode
*swidget
= dynamic_cast<BuddyListNode
*>(sref
->getWidget());
121 CppConsUI::TreeView::SiblingIterator j
= sref
;
123 while (j
!= parent_ref
.end()) {
124 BuddyListNode
*n
= dynamic_cast<BuddyListNode
*>(j
->getWidget());
127 if (swidget
->lessOrEqual(*n
)) {
128 treeview
->moveNodeBefore(sref
, j
);
133 // the node is last in the list
134 if (j
== parent_ref
.end())
135 treeview
->moveNodeAfter(sref
, --j
);
142 BuddyListNode
*BuddyListNode::getParentNode() const
144 PurpleBlistNode
*parent
= purple_blist_node_get_parent(blist_node
);
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
)
162 typedef void (*TypedCallback
)(void *, void *);
163 TypedCallback real_callback
= reinterpret_cast<TypedCallback
>(callback
);
164 real_callback(parent_node
->getPurpleBlistNode(), data
);
169 void BuddyListNode::ContextMenu::appendMenuAction(
170 MenuWindow
&menu
, PurpleMenuAction
*act
)
173 menu
.appendSeparator();
177 if (!act
->children
) {
180 act
->label
, sigc::bind(sigc::mem_fun(this, &ContextMenu::onMenuAction
),
181 act
->callback
, act
->data
));
183 // TODO display non-focusable widget?
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
)
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
);
218 // append a separator because there has been some items
225 void BuddyListNode::ContextMenu::appendExtendedMenu()
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
);
235 // append a separator because there has been some items
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
));
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
);
263 bool BuddyListNode::lessOrEqualByBuddySort(
264 PurpleBuddy
*left
, PurpleBuddy
*right
) const
266 BuddyList::BuddySortMode mode
= BUDDYLIST
->getBuddySortMode();
270 case BuddyList::BUDDY_SORT_BY_NAME
:
272 case BuddyList::BUDDY_SORT_BY_STATUS
:
273 a
= getBuddyStatusWeight(left
);
274 b
= getBuddyStatusWeight(right
);
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
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;
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
)))
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
)))
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
);
325 case PURPLE_STATUS_OFFLINE
:
329 case PURPLE_STATUS_UNSET
:
331 case PURPLE_STATUS_UNAVAILABLE
:
333 case PURPLE_STATUS_AWAY
:
335 case PURPLE_STATUS_EXTENDED_AWAY
:
337 case PURPLE_STATUS_MOBILE
:
339 case PURPLE_STATUS_MOOD
:
341 case PURPLE_STATUS_TUNE
:
343 case PURPLE_STATUS_INVISIBLE
:
345 case PURPLE_STATUS_AVAILABLE
:
350 void BuddyListNode::updateFilterVisibility(const char *name
)
355 const char *filter
= BUDDYLIST
->getFilterString();
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()
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
);
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
);
400 char *text
= g_strdup_printf("%s %s", status
, alias
);
411 if (!purple_account_is_connected(purple_buddy_get_account(buddy
))) {
412 // hide if account is offline
413 setVisibility(false);
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
);
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();
454 _("Information..."), sigc::mem_fun(this, &BuddyContextMenu::onInformation
));
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();
466 void BuddyListBuddy::BuddyContextMenu::changeAliasResponseHandler(
467 CppConsUI::InputDialog
&activator
,
468 CppConsUI::AbstractDialog::ResponseType response
)
470 if (response
!= CppConsUI::AbstractDialog::RESPONSE_OK
)
473 PurpleBuddy
*buddy
= parent_buddy
->getPurpleBuddy();
474 purple_blist_alias_buddy(buddy
, activator
.getText());
475 serv_alias_buddy(buddy
);
477 // close context menu
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
));
491 void BuddyListBuddy::BuddyContextMenu::removeResponseHandler(
492 CppConsUI::MessageDialog
& /*activator*/,
493 CppConsUI::AbstractDialog::ResponseType response
)
495 if (response
!= CppConsUI::AbstractDialog::RESPONSE_OK
)
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. */
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
);
518 dialog
->signal_response
.connect(
519 sigc::mem_fun(this, &BuddyContextMenu::removeResponseHandler
));
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);
545 BuddyListBuddy::BuddyListBuddy(PurpleBlistNode
*node_
) : BuddyListNode(node_
)
547 setColorScheme("buddylistbuddy");
549 buddy
= PURPLE_BUDDY(blist_node
);
552 void BuddyListBuddy::updateColorScheme()
556 switch (BUDDYLIST
->getColorizationMode()) {
557 case BuddyList::COLOR_BY_STATUS
:
558 new_scheme
= Utils::getColorSchemeString("buddylistbuddy", buddy
);
559 setColorScheme(new_scheme
);
563 // note: COLOR_BY_ACCOUNT case is handled by BuddyListBuddy::draw()
564 setColorScheme("buddylistbuddy");
569 bool BuddyListChat::lessOrEqual(const BuddyListNode
&other
) const
571 const BuddyListChat
*o
= dynamic_cast<const BuddyListChat
*>(&other
);
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
);
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
);
608 name
= purple_chat_get_name(chat
);
610 PurpleConversation
*conv
=
611 purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT
, name
, account
);
613 purple_conversation_present(conv
);
615 serv_join_chat(purple_account_get_connection(account
), components
);
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();
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
)
644 PurpleChat
*chat
= parent_chat
->getPurpleChat();
645 purple_blist_alias_chat(chat
, activator
.getText());
647 // close context menu
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
));
661 void BuddyListChat::ChatContextMenu::removeResponseHandler(
662 CppConsUI::MessageDialog
& /*activator*/,
663 CppConsUI::AbstractDialog::ResponseType response
)
665 if (response
!= CppConsUI::AbstractDialog::RESPONSE_OK
)
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. */
674 purple_blist_remove_chat(chat
);
677 void BuddyListChat::ChatContextMenu::onRemove(Button
& /*activator*/)
679 PurpleChat
*chat
= parent_chat
->getPurpleChat();
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
);
686 dialog
->signal_response
.connect(
687 sigc::mem_fun(this, &ChatContextMenu::removeResponseHandler
));
691 void BuddyListChat::openContextMenu()
693 ContextMenu
*w
= new ChatContextMenu(*this);
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
);
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
);
721 /* The contact does not have any associated buddy, ignore it until it gets
722 * a buddy assigned. */
723 setText("*Contact*");
724 setVisibility(false);
728 // format contact size
730 if (contact
->currentsize
> 1)
731 size
= g_strdup_printf(" (%d)", contact
->currentsize
);
735 // format contact label
736 const char *alias
= purple_contact_get_alias(contact
);
737 const char *status
= getBuddyStatus(buddy
);
740 text
= g_strdup_printf("%s %s%s", status
, alias
, size
? size
: "");
742 text
= g_strdup_printf("%s%s", alias
, size
? size
: "");
751 if (!purple_account_is_connected(purple_buddy_get_account(buddy
))) {
752 // hide if account is offline
753 setVisibility(false);
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
)));
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
),
800 appendItem(_("Collapse"),
801 sigc::bind(sigc::mem_fun(this, &ContactContextMenu::onExpandRequest
),
804 appendItem(_("Information..."),
805 sigc::mem_fun(this, &ContactContextMenu::onInformation
));
807 _("Alias..."), sigc::mem_fun(this, &ContactContextMenu::onChangeAlias
));
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
))
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
)
826 appendSubMenu(_("Move to..."), *groups
);
829 void BuddyListContact::ContactContextMenu::onExpandRequest(
830 Button
& /*activator*/, bool expand
)
832 parent_contact
->setCollapsed(!expand
);
836 void BuddyListContact::ContactContextMenu::onInformation(Button
& /*activator*/)
838 parent_contact
->retrieveUserInfo();
842 void BuddyListContact::ContactContextMenu::changeAliasResponseHandler(
843 CppConsUI::InputDialog
&activator
,
844 CppConsUI::AbstractDialog::ResponseType response
)
846 if (response
!= CppConsUI::AbstractDialog::RESPONSE_OK
)
849 PurpleContact
*contact
= parent_contact
->getPurpleContact();
851 purple_blist_alias_contact(contact
, activator
.getText());
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
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
));
872 void BuddyListContact::ContactContextMenu::removeResponseHandler(
873 CppConsUI::MessageDialog
& /*activator*/,
874 CppConsUI::AbstractDialog::ResponseType response
)
876 if (response
!= CppConsUI::AbstractDialog::RESPONSE_OK
)
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. */
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
);
908 dialog
->signal_response
.connect(
909 sigc::mem_fun(this, &ContactContextMenu::removeResponseHandler
));
913 void BuddyListContact::ContactContextMenu::onMoveTo(
914 Button
& /*activator*/, PurpleGroup
*group
)
916 PurpleContact
*contact
= parent_contact
->getPurpleContact();
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);
946 BuddyListContact::BuddyListContact(PurpleBlistNode
*node_
)
947 : BuddyListNode(node_
)
949 setColorScheme("buddylistcontact");
951 contact
= PURPLE_CONTACT(blist_node
);
954 void BuddyListContact::updateColorScheme()
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
);
967 // note: COLOR_BY_ACCOUNT case is handled by BuddyListContact::draw()
968 setColorScheme("buddylistcontact");
973 bool BuddyListGroup::lessOrEqual(const BuddyListNode
&other
) const
975 /* If the groups aren't sorted but ordered manually then this method isn't
978 const BuddyListGroup
*o
= dynamic_cast<const BuddyListGroup
*>(&other
);
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
));
992 BuddyList::GroupSortMode mode
= BUDDYLIST
->getGroupSortMode();
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
);
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!
1011 treeview
->moveNodeAfter(ref
, bnode
->getRefNode());
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());
1019 case BuddyList::GROUP_SORT_BY_NAME
:
1025 if (!BUDDYLIST
->getShowEmptyGroupsPref())
1026 vis
= purple_blist_get_group_size(group
, FALSE
);
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
))
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
)
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
1103 purple_blist_rename_group(group
, name
);
1105 // close context menu
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
));
1119 void BuddyListGroup::GroupContextMenu::removeResponseHandler(
1120 CppConsUI::MessageDialog
& /*activator*/,
1121 CppConsUI::AbstractDialog::ResponseType response
)
1123 if (response
!= CppConsUI::AbstractDialog::RESPONSE_OK
)
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
));
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
);
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
);
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
);
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. */
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
);
1172 dialog
->signal_response
.connect(
1173 sigc::mem_fun(this, &GroupContextMenu::removeResponseHandler
));
1177 void BuddyListGroup::GroupContextMenu::onMoveAfter(
1178 Button
& /*activator*/, PurpleGroup
*group
)
1180 PurpleGroup
*moved_group
= parent_group
->getPurpleGroup();
1183 purple_blist_add_group(moved_group
, PURPLE_BLIST_NODE(group
));
1186 void BuddyListGroup::openContextMenu()
1188 ContextMenu
*w
= new GroupContextMenu(*this);
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 : */