1 // Copyright (C) 2007 Mark Pustjens <pustjens@dds.nl>
2 // Copyright (C) 2010-2015 Petr Pavlu <setup@dagobah.cz>
4 // This file is part of CenterIM.
6 // CenterIM is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 2 of the License, or
9 // (at your option) any later version.
11 // CenterIM is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with CenterIM. If not, see <http://www.gnu.org/licenses/>.
19 #include "BuddyListNode.h"
21 #include "BuddyList.h"
26 #include <cppconsui/ColorScheme.h>
28 BuddyListNode
*BuddyListNode::createNode(PurpleBlistNode
*node
)
30 PurpleBlistNodeType type
= purple_blist_node_get_type(node
);
31 if (type
== PURPLE_BLIST_BUDDY_NODE
)
32 return new BuddyListBuddy(node
);
33 if (type
== PURPLE_BLIST_CHAT_NODE
)
34 return new BuddyListChat(node
);
35 if (type
== PURPLE_BLIST_CONTACT_NODE
)
36 return new BuddyListContact(node
);
37 if (type
== PURPLE_BLIST_GROUP_NODE
)
38 return new BuddyListGroup(node
);
40 LOG
->error(_("Unhandled buddy list node '%d'."), type
);
44 void BuddyListNode::setParent(CppConsUI::Container
&parent
)
46 Button::setParent(parent
);
48 treeview_
= dynamic_cast<CppConsUI::TreeView
*>(&parent
);
49 g_assert(treeview_
!= nullptr);
52 void BuddyListNode::setRefNode(CppConsUI::TreeView::NodeReference n
)
55 treeview_
->setCollapsed(ref_
, true);
58 void BuddyListNode::update()
60 // Cache the last_activity time.
61 last_activity_
= purple_blist_node_get_int(blist_node_
, "last_activity");
63 BuddyListNode
*parent_node
= getParentNode();
64 // The parent could have changed, so re-parent the node.
65 if (parent_node
!= nullptr)
66 treeview_
->setNodeParent(ref_
, parent_node
->getRefNode());
69 void BuddyListNode::sortIn()
71 CppConsUI::TreeView::NodeReference parent_ref
;
72 if (purple_blist_node_get_parent(blist_node_
) != nullptr) {
73 // This blist node has got a logical (libpurple) parent, check if it is
74 // possible to find also a cim node.
75 BuddyListNode
*parent_node
= getParentNode();
76 if (parent_node
!= nullptr)
77 parent_ref
= parent_node
->getRefNode();
79 // there shouldn't be a cim node only if the flat mode is active
80 g_assert(BUDDYLIST
->getListMode() == BuddyList::LIST_FLAT
);
82 parent_ref
= treeview_
->getRootNode();
86 if (PURPLE_BLIST_NODE_IS_GROUP(blist_node_
)) {
87 // Groups do not have parent nodes.
88 parent_ref
= treeview_
->getRootNode();
91 // When the new_node() callback is called for a contact/chat/buddy (and
92 // this method is called as a part of that callback) then the node does
93 // not have any parent set yet. In such a case, simply return.
98 // Do the insertion sort. It should be fast enough here because nodes are
99 // usually already sorted and only one node is in a wrong position, so it kind
101 CppConsUI::TreeView::SiblingIterator i
= parent_ref
.end();
104 // sref is a node that we want to sort in.
105 CppConsUI::TreeView::SiblingIterator sref
= i
;
107 // Calculate a stop condition.
109 if (i
!= parent_ref
.begin()) {
116 BuddyListNode
*swidget
= dynamic_cast<BuddyListNode
*>(sref
->getWidget());
117 g_assert(swidget
!= nullptr);
118 CppConsUI::TreeView::SiblingIterator j
= sref
;
120 while (j
!= parent_ref
.end()) {
121 BuddyListNode
*n
= dynamic_cast<BuddyListNode
*>(j
->getWidget());
122 g_assert(n
!= nullptr);
124 if (swidget
->lessOrEqual(*n
)) {
125 treeview_
->moveNodeBefore(sref
, j
);
130 // the node is last in the list
131 if (j
== parent_ref
.end())
132 treeview_
->moveNodeAfter(sref
, --j
);
139 BuddyListNode
*BuddyListNode::getParentNode() const
141 PurpleBlistNode
*parent
= purple_blist_node_get_parent(blist_node_
);
142 if (parent
== nullptr)
145 return reinterpret_cast<BuddyListNode
*>(
146 purple_blist_node_get_ui_data(parent
));
149 BuddyListNode::ContextMenu::ContextMenu(BuddyListNode
&parent_node
)
150 : MenuWindow(parent_node
, AUTOSIZE
, AUTOSIZE
), parent_node_(&parent_node
)
154 void BuddyListNode::ContextMenu::onMenuAction(
155 Button
& /*activator*/, PurpleCallback callback
, void *data
)
157 g_assert(callback
!= nullptr);
159 typedef void (*TypedCallback
)(void *, void *);
160 TypedCallback real_callback
= reinterpret_cast<TypedCallback
>(callback
);
161 real_callback(parent_node_
->getPurpleBlistNode(), data
);
166 void BuddyListNode::ContextMenu::appendMenuAction(
167 MenuWindow
&menu
, PurpleMenuAction
*act
)
169 if (act
== nullptr) {
170 menu
.appendSeparator();
174 if (act
->children
== nullptr) {
175 if (act
->callback
!= nullptr)
177 act
->label
, sigc::bind(sigc::mem_fun(this, &ContextMenu::onMenuAction
),
178 act
->callback
, act
->data
));
180 // TODO display non-focusable widget?
184 auto submenu
= new MenuWindow(0, 0, AUTOSIZE
, AUTOSIZE
);
185 menu
.appendSubMenu(act
->label
, *submenu
);
187 for (GList
*l
= act
->children
; l
!= nullptr; l
= l
->next
) {
188 PurpleMenuAction
*act
= reinterpret_cast<PurpleMenuAction
*>(l
->data
);
189 appendMenuAction(*submenu
, act
);
192 // Free memory associated with the children.
193 g_list_free(act
->children
);
194 act
->children
= nullptr;
197 // Free the menu action.
198 purple_menu_action_free(act
);
201 void BuddyListNode::ContextMenu::appendProtocolMenu(PurpleConnection
*gc
)
203 PurplePluginProtocolInfo
*prpl_info
=
204 PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc
));
205 if (prpl_info
== nullptr || prpl_info
->blist_node_menu
== nullptr)
208 GList
*ll
= prpl_info
->blist_node_menu(parent_node_
->getPurpleBlistNode());
209 for (GList
*l
= ll
; l
!= nullptr; l
= l
->next
) {
210 PurpleMenuAction
*act
= reinterpret_cast<PurpleMenuAction
*>(l
->data
);
211 appendMenuAction(*this, act
);
215 // Append a separator because some items were added.
222 void BuddyListNode::ContextMenu::appendExtendedMenu()
225 purple_blist_node_get_extended_menu(parent_node_
->getPurpleBlistNode());
226 for (GList
*l
= ll
; l
!= nullptr; l
= l
->next
) {
227 PurpleMenuAction
*act
= reinterpret_cast<PurpleMenuAction
*>(l
->data
);
228 appendMenuAction(*this, act
);
232 // Append a separator because some items were added.
239 BuddyListNode::BuddyListNode(PurpleBlistNode
*node
)
240 : treeview_(nullptr), blist_node_(node
), last_activity_(0)
242 purple_blist_node_set_ui_data(blist_node_
, this);
243 signal_activate
.connect(sigc::mem_fun(this, &BuddyListNode::onActivate
));
247 BuddyListNode::~BuddyListNode()
249 purple_blist_node_set_ui_data(blist_node_
, nullptr);
252 bool BuddyListNode::lessOrEqualByType(const BuddyListNode
&other
) const
254 // group < contact < buddy < chat < other.
255 PurpleBlistNodeType t1
= purple_blist_node_get_type(blist_node_
);
256 PurpleBlistNodeType t2
= purple_blist_node_get_type(other
.blist_node_
);
260 bool BuddyListNode::lessOrEqualByBuddySort(
261 PurpleBuddy
*left
, PurpleBuddy
*right
) const
263 BuddyList::BuddySortMode mode
= BUDDYLIST
->getBuddySortMode();
267 case BuddyList::BUDDY_SORT_BY_NAME
:
269 case BuddyList::BUDDY_SORT_BY_STATUS
:
270 a
= getBuddyStatusWeight(left
);
271 b
= getBuddyStatusWeight(right
);
275 case BuddyList::BUDDY_SORT_BY_ACTIVITY
: {
276 // Compare buddies according to their last activity.
278 // It is possible that a blist node will not have the ui_data set. For
279 // instance, this happens when libpurple informs the program that a blist
280 // node is about to be removed. At that point, an associated BuddyListNode
281 // is destroyed, a parent node is updated and the parent tries to update its
282 // position according to its priority buddy. This buddy will not have the
283 // ui_data set because the BuddyListNode has been already freed.
285 // In such a case, the cached value cannot be obtained and value 0 will be
287 BuddyListNode
*bnode_left
= reinterpret_cast<BuddyListNode
*>(
288 purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(left
)));
289 BuddyListNode
*bnode_right
= reinterpret_cast<BuddyListNode
*>(
290 purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(right
)));
291 a
= bnode_left
? bnode_left
->last_activity_
: 0;
292 b
= bnode_right
? bnode_right
->last_activity_
: 0;
297 return g_utf8_collate(
298 purple_buddy_get_alias(left
), purple_buddy_get_alias(right
)) <= 0;
301 const char *BuddyListNode::getBuddyStatus(PurpleBuddy
*buddy
) const
303 if (!purple_account_is_connected(purple_buddy_get_account(buddy
)))
306 PurplePresence
*presence
= purple_buddy_get_presence(buddy
);
307 PurpleStatus
*status
= purple_presence_get_active_status(presence
);
308 return Utils::getStatusIndicator(status
);
311 int BuddyListNode::getBuddyStatusWeight(PurpleBuddy
*buddy
) const
313 if (!purple_account_is_connected(purple_buddy_get_account(buddy
)))
316 PurplePresence
*presence
= purple_buddy_get_presence(buddy
);
317 PurpleStatus
*status
= purple_presence_get_active_status(presence
);
318 PurpleStatusType
*status_type
= purple_status_get_type(status
);
319 PurpleStatusPrimitive prim
= purple_status_type_get_primitive(status_type
);
322 case PURPLE_STATUS_OFFLINE
:
326 case PURPLE_STATUS_UNSET
:
328 case PURPLE_STATUS_UNAVAILABLE
:
330 case PURPLE_STATUS_AWAY
:
332 case PURPLE_STATUS_EXTENDED_AWAY
:
334 case PURPLE_STATUS_MOBILE
:
336 case PURPLE_STATUS_MOOD
:
338 case PURPLE_STATUS_TUNE
:
340 case PURPLE_STATUS_INVISIBLE
:
342 case PURPLE_STATUS_AVAILABLE
:
347 int BuddyListNode::getColorSchemeByBuddy(int base_scheme
, PurpleBuddy
*buddy
)
349 g_assert(base_scheme
== CenterIM::SCHEME_BUDDYLISTBUDDY
||
350 base_scheme
== CenterIM::SCHEME_BUDDYLISTCONTACT
);
352 bool scheme_buddy
= (base_scheme
== CenterIM::SCHEME_BUDDYLISTBUDDY
);
354 if (!purple_account_is_connected(purple_buddy_get_account(buddy
)))
355 return scheme_buddy
? CenterIM::SCHEME_BUDDYLISTBUDDY_OFFLINE
356 : CenterIM::SCHEME_BUDDYLISTCONTACT_OFFLINE
;
358 PurplePresence
*presence
= purple_buddy_get_presence(buddy
);
359 PurpleStatus
*status
= purple_presence_get_active_status(presence
);
360 PurpleStatusType
*status_type
= purple_status_get_type(status
);
361 PurpleStatusPrimitive prim
= purple_status_type_get_primitive(status_type
);
364 case PURPLE_STATUS_UNSET
:
365 case PURPLE_STATUS_OFFLINE
:
367 return scheme_buddy
? CenterIM::SCHEME_BUDDYLISTBUDDY_OFFLINE
368 : CenterIM::SCHEME_BUDDYLISTCONTACT_OFFLINE
;
369 case PURPLE_STATUS_AVAILABLE
:
370 case PURPLE_STATUS_MOBILE
:
371 case PURPLE_STATUS_TUNE
:
372 case PURPLE_STATUS_MOOD
:
373 return scheme_buddy
? CenterIM::SCHEME_BUDDYLISTBUDDY_ONLINE
374 : CenterIM::SCHEME_BUDDYLISTCONTACT_ONLINE
;
375 case PURPLE_STATUS_AWAY
:
376 case PURPLE_STATUS_EXTENDED_AWAY
:
377 return scheme_buddy
? CenterIM::SCHEME_BUDDYLISTBUDDY_AWAY
378 : CenterIM::SCHEME_BUDDYLISTCONTACT_AWAY
;
379 case PURPLE_STATUS_UNAVAILABLE
:
380 case PURPLE_STATUS_INVISIBLE
:
381 return scheme_buddy
? CenterIM::SCHEME_BUDDYLISTBUDDY_NA
382 : CenterIM::SCHEME_BUDDYLISTCONTACT_NA
;
386 void BuddyListNode::updateFilterVisibility(const char *name
)
391 const char *filter
= BUDDYLIST
->getFilterString();
392 if (filter
[0] == '\0')
395 // Filtering is active.
396 setVisibility(purple_strcasestr(name
, filter
));
399 void BuddyListNode::retrieveUserInfoForName(
400 PurpleConnection
*gc
, const char *name
) const
402 PurpleNotifyUserInfo
*info
= purple_notify_user_info_new();
403 purple_notify_user_info_add_pair(info
, _("Information"), _("Retrieving..."));
404 purple_notify_userinfo(gc
, name
, info
, nullptr, nullptr);
405 purple_notify_user_info_destroy(info
);
406 serv_get_info(gc
, name
);
409 void BuddyListNode::actionOpenContextMenu()
414 void BuddyListNode::declareBindables()
416 declareBindable("buddylist", "contextmenu",
417 sigc::mem_fun(this, &BuddyListNode::actionOpenContextMenu
),
418 InputProcessor::BINDABLE_NORMAL
);
421 bool BuddyListBuddy::lessOrEqual(const BuddyListNode
&other
) const
423 const BuddyListBuddy
*o
= dynamic_cast<const BuddyListBuddy
*>(&other
);
425 return lessOrEqualByBuddySort(buddy_
, o
->buddy_
);
426 return lessOrEqualByType(other
);
429 void BuddyListBuddy::update()
431 BuddyListNode::update();
433 const char *status
= getBuddyStatus(buddy_
);
434 const char *alias
= purple_buddy_get_alias(buddy_
);
435 if (status
[0] != '\0') {
436 char *text
= g_strdup_printf("%s %s", status
, alias
);
447 if (!purple_account_is_connected(purple_buddy_get_account(buddy_
))) {
448 // Hide if account is offline.
449 setVisibility(false);
452 setVisibility(status
[0] != '\0' || BUDDYLIST
->getShowOfflineBuddiesPref());
454 updateFilterVisibility(alias
);
457 void BuddyListBuddy::onActivate(Button
& /*activator*/)
459 PurpleAccount
*account
= purple_buddy_get_account(buddy_
);
460 const char *name
= purple_buddy_get_name(buddy_
);
461 PurpleConversation
*conv
=
462 purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM
, name
, account
);
465 conv
= purple_conversation_new(PURPLE_CONV_TYPE_IM
, account
, name
);
466 purple_conversation_present(conv
);
469 const char *BuddyListBuddy::toString() const
471 return purple_buddy_get_alias(buddy_
);
474 void BuddyListBuddy::retrieveUserInfo()
476 PurpleConnection
*gc
=
477 purple_account_get_connection(purple_buddy_get_account(buddy_
));
478 retrieveUserInfoForName(gc
, purple_buddy_get_name(buddy_
));
481 BuddyListBuddy::BuddyContextMenu::BuddyContextMenu(BuddyListBuddy
&parent_buddy
)
482 : ContextMenu(parent_buddy
), parent_buddy_(&parent_buddy
)
484 appendProtocolMenu(purple_account_get_connection(
485 purple_buddy_get_account(parent_buddy_
->getPurpleBuddy())));
486 appendExtendedMenu();
489 _("Information..."), sigc::mem_fun(this, &BuddyContextMenu::onInformation
));
491 _("Alias..."), sigc::mem_fun(this, &BuddyContextMenu::onChangeAlias
));
492 appendItem(_("Delete..."), sigc::mem_fun(this, &BuddyContextMenu::onRemove
));
495 void BuddyListBuddy::BuddyContextMenu::onInformation(Button
& /*activator*/)
497 parent_buddy_
->retrieveUserInfo();
501 void BuddyListBuddy::BuddyContextMenu::changeAliasResponseHandler(
502 CppConsUI::InputDialog
&activator
,
503 CppConsUI::AbstractDialog::ResponseType response
)
505 if (response
!= CppConsUI::AbstractDialog::RESPONSE_OK
)
508 PurpleBuddy
*buddy
= parent_buddy_
->getPurpleBuddy();
509 purple_blist_alias_buddy(buddy
, activator
.getText());
510 serv_alias_buddy(buddy
);
512 // Close context menu.
516 void BuddyListBuddy::BuddyContextMenu::onChangeAlias(Button
& /*activator*/)
518 PurpleBuddy
*buddy
= parent_buddy_
->getPurpleBuddy();
520 new CppConsUI::InputDialog(_("Alias"), purple_buddy_get_alias(buddy
));
521 dialog
->signal_response
.connect(
522 sigc::mem_fun(this, &BuddyContextMenu::changeAliasResponseHandler
));
526 void BuddyListBuddy::BuddyContextMenu::removeResponseHandler(
527 CppConsUI::MessageDialog
& /*activator*/,
528 CppConsUI::AbstractDialog::ResponseType response
)
530 if (response
!= CppConsUI::AbstractDialog::RESPONSE_OK
)
533 PurpleBuddy
*buddy
= parent_buddy_
->getPurpleBuddy();
534 purple_account_remove_buddy(
535 purple_buddy_get_account(buddy
), buddy
, purple_buddy_get_group(buddy
));
537 // Close the context menu before the buddy is deleted because its deletion can
538 // lead to destruction of this object.
541 purple_blist_remove_buddy(buddy
);
544 void BuddyListBuddy::BuddyContextMenu::onRemove(Button
& /*activator*/)
546 PurpleBuddy
*buddy
= parent_buddy_
->getPurpleBuddy();
547 char *msg
= g_strdup_printf(
548 _("Are you sure you want to delete buddy %s from the list?"),
549 purple_buddy_get_alias(buddy
));
550 auto dialog
= new CppConsUI::MessageDialog(_("Buddy deletion"), msg
);
552 dialog
->signal_response
.connect(
553 sigc::mem_fun(this, &BuddyContextMenu::removeResponseHandler
));
557 int BuddyListBuddy::getAttributes(
558 int property
, int subproperty
, int *attrs
, CppConsUI::Error
&error
) const
560 if (BUDDYLIST
->getColorizationMode() != BuddyList::COLOR_BY_ACCOUNT
||
561 property
!= CppConsUI::ColorScheme::PROPERTY_BUTTON_NORMAL
)
562 return Button::getAttributes(property
, subproperty
, attrs
, error
);
564 // TODO Implement caching for these two properties.
565 PurpleAccount
*account
= purple_buddy_get_account(buddy_
);
566 int fg
= purple_account_get_ui_int(account
, "centerim5",
567 "buddylist-foreground-color", CppConsUI::Curses::Color::DEFAULT
);
568 int bg
= purple_account_get_ui_int(account
, "centerim5",
569 "buddylist-background-color", CppConsUI::Curses::Color::DEFAULT
);
571 CppConsUI::ColorScheme::Color
c(fg
, bg
);
572 return COLORSCHEME
->getColorPair(c
, attrs
, error
);
575 void BuddyListBuddy::openContextMenu()
577 ContextMenu
*w
= new BuddyContextMenu(*this);
581 BuddyListBuddy::BuddyListBuddy(PurpleBlistNode
*node
) : BuddyListNode(node
)
583 setColorScheme(CenterIM::SCHEME_BUDDYLISTBUDDY
);
585 buddy_
= PURPLE_BUDDY(blist_node_
);
588 void BuddyListBuddy::updateColorScheme()
590 switch (BUDDYLIST
->getColorizationMode()) {
591 case BuddyList::COLOR_BY_STATUS
:
593 getColorSchemeByBuddy(CenterIM::SCHEME_BUDDYLISTBUDDY
, buddy_
));
596 // note: COLOR_BY_ACCOUNT case is handled by BuddyListBuddy::draw()
597 setColorScheme(CenterIM::SCHEME_BUDDYLISTBUDDY
);
602 bool BuddyListChat::lessOrEqual(const BuddyListNode
&other
) const
604 const BuddyListChat
*o
= dynamic_cast<const BuddyListChat
*>(&other
);
606 return g_utf8_collate(
607 purple_chat_get_name(chat_
), purple_chat_get_name(o
->chat_
)) <= 0;
608 return lessOrEqualByType(other
);
611 void BuddyListChat::update()
613 BuddyListNode::update();
615 const char *name
= purple_chat_get_name(chat_
);
620 // Hide if account is offline.
621 setVisibility(purple_account_is_connected(purple_chat_get_account(chat_
)));
623 updateFilterVisibility(name
);
626 void BuddyListChat::onActivate(Button
& /*activator*/)
628 PurpleAccount
*account
= purple_chat_get_account(chat_
);
629 PurplePluginProtocolInfo
*prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(
630 purple_find_prpl(purple_account_get_protocol_id(account
)));
631 GHashTable
*components
= purple_chat_get_components(chat_
);
633 char *chat_name
= nullptr;
634 if (prpl_info
!= nullptr && prpl_info
->get_chat_name
!= nullptr)
635 chat_name
= prpl_info
->get_chat_name(components
);
638 if (chat_name
!= nullptr)
641 name
= purple_chat_get_name(chat_
);
643 PurpleConversation
*conv
=
644 purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT
, name
, account
);
646 purple_conversation_present(conv
);
648 serv_join_chat(purple_account_get_connection(account
), components
);
653 const char *BuddyListChat::toString() const
655 return purple_chat_get_name(chat_
);
658 BuddyListChat::ChatContextMenu::ChatContextMenu(BuddyListChat
&parent_chat
)
659 : ContextMenu(parent_chat
), parent_chat_(&parent_chat
)
661 appendProtocolMenu(purple_account_get_connection(
662 purple_chat_get_account(parent_chat_
->getPurpleChat())));
663 appendExtendedMenu();
666 _("Alias..."), sigc::mem_fun(this, &ChatContextMenu::onChangeAlias
));
667 appendItem(_("Delete..."), sigc::mem_fun(this, &ChatContextMenu::onRemove
));
670 void BuddyListChat::ChatContextMenu::changeAliasResponseHandler(
671 CppConsUI::InputDialog
&activator
,
672 CppConsUI::AbstractDialog::ResponseType response
)
674 if (response
!= CppConsUI::AbstractDialog::RESPONSE_OK
)
677 PurpleChat
*chat
= parent_chat_
->getPurpleChat();
678 purple_blist_alias_chat(chat
, activator
.getText());
680 // Close context menu.
684 void BuddyListChat::ChatContextMenu::onChangeAlias(Button
& /*activator*/)
686 PurpleChat
*chat
= parent_chat_
->getPurpleChat();
688 new CppConsUI::InputDialog(_("Alias"), purple_chat_get_name(chat
));
689 dialog
->signal_response
.connect(
690 sigc::mem_fun(this, &ChatContextMenu::changeAliasResponseHandler
));
694 void BuddyListChat::ChatContextMenu::removeResponseHandler(
695 CppConsUI::MessageDialog
& /*activator*/,
696 CppConsUI::AbstractDialog::ResponseType response
)
698 if (response
!= CppConsUI::AbstractDialog::RESPONSE_OK
)
701 PurpleChat
*chat
= parent_chat_
->getPurpleChat();
703 // Close the context menu before the chat is deleted because its deletion can
704 // lead to destruction of this object.
707 purple_blist_remove_chat(chat
);
710 void BuddyListChat::ChatContextMenu::onRemove(Button
& /*activator*/)
712 PurpleChat
*chat
= parent_chat_
->getPurpleChat();
714 g_strdup_printf(_("Are you sure you want to delete chat %s from the list?"),
715 purple_chat_get_name(chat
));
716 auto dialog
= new CppConsUI::MessageDialog(_("Chat deletion"), msg
);
718 dialog
->signal_response
.connect(
719 sigc::mem_fun(this, &ChatContextMenu::removeResponseHandler
));
723 void BuddyListChat::openContextMenu()
725 ContextMenu
*w
= new ChatContextMenu(*this);
729 BuddyListChat::BuddyListChat(PurpleBlistNode
*node
) : BuddyListNode(node
)
731 setColorScheme(CenterIM::SCHEME_BUDDYLISTCHAT
);
733 chat_
= PURPLE_CHAT(blist_node_
);
736 bool BuddyListContact::lessOrEqual(const BuddyListNode
&other
) const
738 const BuddyListContact
*o
= dynamic_cast<const BuddyListContact
*>(&other
);
740 PurpleBuddy
*left
= purple_contact_get_priority_buddy(contact_
);
741 PurpleBuddy
*right
= purple_contact_get_priority_buddy(o
->contact_
);
742 return lessOrEqualByBuddySort(left
, right
);
744 return lessOrEqualByType(other
);
747 void BuddyListContact::update()
749 BuddyListNode::update();
751 PurpleBuddy
*buddy
= purple_contact_get_priority_buddy(contact_
);
752 if (buddy
== nullptr) {
753 // The contact does not have any associated buddy, ignore it until it gets a
755 setText("*Contact*");
756 setVisibility(false);
760 // Format contact size.
762 if (contact_
->currentsize
> 1)
763 size
= g_strdup_printf(" (%d)", contact_
->currentsize
);
767 // Format contact label.
768 const char *alias
= purple_contact_get_alias(contact_
);
769 const char *status
= getBuddyStatus(buddy
);
771 if (status
[0] != '\0')
772 text
= g_strdup_printf("%s %s%s", status
, alias
, size
? size
: "");
774 text
= g_strdup_printf("%s%s", alias
, size
? size
: "");
783 if (!purple_account_is_connected(purple_buddy_get_account(buddy
))) {
784 // Hide if account is offline.
785 setVisibility(false);
788 setVisibility(status
[0] != '\0' || BUDDYLIST
->getShowOfflineBuddiesPref());
790 updateFilterVisibility(alias
);
793 void BuddyListContact::onActivate(Button
&activator
)
795 PurpleBuddy
*buddy
= purple_contact_get_priority_buddy(contact_
);
796 BuddyListNode
*bnode
= reinterpret_cast<BuddyListNode
*>(
797 purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(buddy
)));
798 if (bnode
!= nullptr)
799 bnode
->onActivate(activator
);
802 const char *BuddyListContact::toString() const
804 return purple_contact_get_alias(contact_
);
807 void BuddyListContact::setRefNode(CppConsUI::TreeView::NodeReference n
)
809 BuddyListNode::setRefNode(n
);
810 treeview_
->setNodeStyle(n
, CppConsUI::TreeView::STYLE_VOID
);
813 void BuddyListContact::retrieveUserInfo()
815 PurpleBuddy
*buddy
= purple_contact_get_priority_buddy(contact_
);
816 PurpleConnection
*gc
=
817 purple_account_get_connection(purple_buddy_get_account(buddy
));
818 retrieveUserInfoForName(gc
, purple_buddy_get_name(buddy
));
821 BuddyListContact::ContactContextMenu::ContactContextMenu(
822 BuddyListContact
&parent_contact
)
823 : ContextMenu(parent_contact
), parent_contact_(&parent_contact
)
825 appendExtendedMenu();
827 if (parent_contact_
->isCollapsed())
828 appendItem(_("Expand"),
829 sigc::bind(sigc::mem_fun(this, &ContactContextMenu::onExpandRequest
),
832 appendItem(_("Collapse"),
833 sigc::bind(sigc::mem_fun(this, &ContactContextMenu::onExpandRequest
),
836 appendItem(_("Information..."),
837 sigc::mem_fun(this, &ContactContextMenu::onInformation
));
839 _("Alias..."), sigc::mem_fun(this, &ContactContextMenu::onChangeAlias
));
841 _("Delete..."), sigc::mem_fun(this, &ContactContextMenu::onRemove
));
843 auto groups
= new CppConsUI::MenuWindow(*this, AUTOSIZE
, AUTOSIZE
);
845 for (PurpleBlistNode
*node
= purple_blist_get_root(); node
!= nullptr;
846 node
= purple_blist_node_get_sibling_next(node
)) {
847 if (!PURPLE_BLIST_NODE_IS_GROUP(node
))
850 PurpleGroup
*group
= PURPLE_GROUP(node
);
851 CppConsUI::Button
*button
= groups
->appendItem(purple_group_get_name(group
),
852 sigc::bind(sigc::mem_fun(this, &ContactContextMenu::onMoveTo
), group
));
853 if (purple_contact_get_group(parent_contact_
->getPurpleContact()) == group
)
857 appendSubMenu(_("Move to..."), *groups
);
860 void BuddyListContact::ContactContextMenu::onExpandRequest(
861 Button
& /*activator*/, bool expand
)
863 parent_contact_
->setCollapsed(!expand
);
867 void BuddyListContact::ContactContextMenu::onInformation(Button
& /*activator*/)
869 parent_contact_
->retrieveUserInfo();
873 void BuddyListContact::ContactContextMenu::changeAliasResponseHandler(
874 CppConsUI::InputDialog
&activator
,
875 CppConsUI::AbstractDialog::ResponseType response
)
877 if (response
!= CppConsUI::AbstractDialog::RESPONSE_OK
)
880 PurpleContact
*contact
= parent_contact_
->getPurpleContact();
881 if (contact
->alias
!= nullptr)
882 purple_blist_alias_contact(contact
, activator
.getText());
884 PurpleBuddy
*buddy
= purple_contact_get_priority_buddy(contact
);
885 purple_blist_alias_buddy(buddy
, activator
.getText());
886 serv_alias_buddy(buddy
);
889 // Close context menu.
893 void BuddyListContact::ContactContextMenu::onChangeAlias(Button
& /*activator*/)
895 PurpleContact
*contact
= parent_contact_
->getPurpleContact();
897 new CppConsUI::InputDialog(_("Alias"), purple_contact_get_alias(contact
));
898 dialog
->signal_response
.connect(
899 sigc::mem_fun(this, &ContactContextMenu::changeAliasResponseHandler
));
903 void BuddyListContact::ContactContextMenu::removeResponseHandler(
904 CppConsUI::MessageDialog
& /*activator*/,
905 CppConsUI::AbstractDialog::ResponseType response
)
907 if (response
!= CppConsUI::AbstractDialog::RESPONSE_OK
)
910 // Based on gtkdialogs.c:pidgin_dialogs_remove_contact_cb().
911 PurpleContact
*contact
= parent_contact_
->getPurpleContact();
912 PurpleBlistNode
*cnode
= PURPLE_BLIST_NODE(contact
);
913 PurpleGroup
*group
= purple_contact_get_group(contact
);
915 for (PurpleBlistNode
*bnode
= purple_blist_node_get_first_child(cnode
);
916 bnode
!= nullptr; bnode
= purple_blist_node_get_sibling_next(bnode
)) {
917 PurpleBuddy
*buddy
= PURPLE_BUDDY(bnode
);
918 PurpleAccount
*account
= purple_buddy_get_account(buddy
);
919 if (purple_account_is_connected(account
))
920 purple_account_remove_buddy(account
, buddy
, group
);
923 // Close the context menu before the contact is deleted because its deletion
924 // can lead to destruction of this object.
927 purple_blist_remove_contact(contact
);
930 void BuddyListContact::ContactContextMenu::onRemove(Button
& /*activator*/)
932 PurpleContact
*contact
= parent_contact_
->getPurpleContact();
933 char *msg
= g_strdup_printf(
934 _("Are you sure you want to delete contact %s from the list?"),
935 purple_contact_get_alias(contact
));
936 auto dialog
= new CppConsUI::MessageDialog(_("Contact deletion"), msg
);
938 dialog
->signal_response
.connect(
939 sigc::mem_fun(this, &ContactContextMenu::removeResponseHandler
));
943 void BuddyListContact::ContactContextMenu::onMoveTo(
944 Button
& /*activator*/, PurpleGroup
*group
)
946 PurpleContact
*contact
= parent_contact_
->getPurpleContact();
949 purple_blist_add_contact(contact
, group
, nullptr);
952 int BuddyListContact::getAttributes(
953 int property
, int subproperty
, int *attrs
, CppConsUI::Error
&error
) const
955 if (BUDDYLIST
->getColorizationMode() != BuddyList::COLOR_BY_ACCOUNT
||
956 property
!= CppConsUI::ColorScheme::PROPERTY_BUTTON_NORMAL
)
957 return Button::getAttributes(property
, subproperty
, attrs
, error
);
959 // TODO Implement caching for these two properties.
960 PurpleAccount
*account
=
961 purple_buddy_get_account(purple_contact_get_priority_buddy(contact_
));
962 int fg
= purple_account_get_ui_int(account
, "centerim5",
963 "buddylist-foreground-color", CppConsUI::Curses::Color::DEFAULT
);
964 int bg
= purple_account_get_ui_int(account
, "centerim5",
965 "buddylist-background-color", CppConsUI::Curses::Color::DEFAULT
);
967 CppConsUI::ColorScheme::Color
c(fg
, bg
);
968 return COLORSCHEME
->getColorPair(c
, attrs
, error
);
971 void BuddyListContact::openContextMenu()
973 ContextMenu
*w
= new ContactContextMenu(*this);
977 BuddyListContact::BuddyListContact(PurpleBlistNode
*node
) : BuddyListNode(node
)
979 setColorScheme(CenterIM::SCHEME_BUDDYLISTCONTACT
);
981 contact_
= PURPLE_CONTACT(blist_node_
);
984 void BuddyListContact::updateColorScheme()
986 switch (BUDDYLIST
->getColorizationMode()) {
987 case BuddyList::COLOR_BY_STATUS
: {
988 PurpleBuddy
*buddy
= purple_contact_get_priority_buddy(contact_
);
990 getColorSchemeByBuddy(CenterIM::SCHEME_BUDDYLISTCONTACT
, buddy
));
994 // Note: COLOR_BY_ACCOUNT case is handled by BuddyListContact::draw().
995 setColorScheme(CenterIM::SCHEME_BUDDYLISTCONTACT
);
1000 bool BuddyListGroup::lessOrEqual(const BuddyListNode
&other
) const
1002 // If the groups are not sorted but ordered manually then this method is not
1005 const BuddyListGroup
*o
= dynamic_cast<const BuddyListGroup
*>(&other
);
1007 return g_utf8_collate(purple_group_get_name(group_
),
1008 purple_group_get_name(o
->group_
)) <= 0;
1009 return lessOrEqualByType(other
);
1012 void BuddyListGroup::update()
1014 BuddyListNode::update();
1016 setText(purple_group_get_name(group_
));
1018 // Sort in the group.
1019 BuddyList::GroupSortMode mode
= BUDDYLIST
->getGroupSortMode();
1021 case BuddyList::GROUP_SORT_BY_USER
: {
1022 // Note that the sorting below works even if there was a contact/chat/buddy
1023 // node that is attached at the root level of the blist treeview. This
1024 // happens when such a node was just created (the new_node() callback was
1025 // called) but the node does not have any parent yet.
1027 PurpleBlistNode
*prev
= purple_blist_node_get_sibling_prev(blist_node_
);
1029 if (prev
!= nullptr) {
1030 // It better be a group node.
1031 g_assert(PURPLE_BLIST_NODE_IS_GROUP(prev
));
1033 BuddyListNode
*bnode
=
1034 reinterpret_cast<BuddyListNode
*>(purple_blist_node_get_ui_data(prev
));
1035 // There has to be ui_data set for all group nodes!
1036 g_assert(bnode
!= nullptr);
1038 treeview_
->moveNodeAfter(ref_
, bnode
->getRefNode());
1041 // The group is the first one in the list.
1042 CppConsUI::TreeView::NodeReference parent_ref
= treeview_
->getRootNode();
1043 treeview_
->moveNodeBefore(ref_
, parent_ref
.begin());
1046 case BuddyList::GROUP_SORT_BY_NAME
:
1052 if (!BUDDYLIST
->getShowEmptyGroupsPref())
1053 vis
= purple_blist_get_group_size(group_
, FALSE
);
1057 void BuddyListGroup::onActivate(Button
& /*activator*/)
1059 treeview_
->toggleCollapsed(ref_
);
1060 purple_blist_node_set_bool(blist_node_
, "collapsed", ref_
->isCollapsed());
1063 const char *BuddyListGroup::toString() const
1065 return purple_group_get_name(group_
);
1068 void BuddyListGroup::setRefNode(CppConsUI::TreeView::NodeReference n
)
1070 BuddyListNode::setRefNode(n
);
1071 initCollapsedState();
1074 void BuddyListGroup::initCollapsedState()
1076 // This cannot be done when the purple_blist_load() function was called
1077 // because node settings are unavailable at that time.
1078 treeview_
->setCollapsed(
1079 ref_
, purple_blist_node_get_bool(blist_node_
, "collapsed"));
1082 BuddyListGroup::GroupContextMenu::GroupContextMenu(BuddyListGroup
&parent_group
)
1083 : ContextMenu(parent_group
), parent_group_(&parent_group
)
1085 appendExtendedMenu();
1087 appendItem(_("Rename..."), sigc::mem_fun(this, &GroupContextMenu::onRename
));
1088 appendItem(_("Delete..."), sigc::mem_fun(this, &GroupContextMenu::onRemove
));
1090 if (BUDDYLIST
->getGroupSortMode() == BuddyList::GROUP_SORT_BY_USER
) {
1091 // If the manual sorting is enabled then show a menu item and a submenu for
1093 auto groups
= new CppConsUI::MenuWindow(*this, AUTOSIZE
, AUTOSIZE
);
1095 groups
->appendItem(_("-Top-"),
1096 sigc::bind(sigc::mem_fun(this, &GroupContextMenu::onMoveAfter
),
1097 static_cast<PurpleGroup
*>(nullptr)));
1098 for (PurpleBlistNode
*node
= purple_blist_get_root(); node
!= nullptr;
1099 node
= purple_blist_node_get_sibling_next(node
)) {
1100 if (!PURPLE_BLIST_NODE_IS_GROUP(node
))
1103 PurpleGroup
*group
= PURPLE_GROUP(node
);
1104 groups
->appendItem(purple_group_get_name(group
),
1105 sigc::bind(sigc::mem_fun(this, &GroupContextMenu::onMoveAfter
), group
));
1108 appendSubMenu(_("Move after..."), *groups
);
1112 void BuddyListGroup::GroupContextMenu::renameResponseHandler(
1113 CppConsUI::InputDialog
&activator
,
1114 CppConsUI::AbstractDialog::ResponseType response
)
1116 if (response
!= CppConsUI::AbstractDialog::RESPONSE_OK
)
1119 const char *name
= activator
.getText();
1120 PurpleGroup
*group
= parent_group_
->getPurpleGroup();
1121 PurpleGroup
*other
= purple_find_group(name
);
1122 if (other
!= nullptr &&
1123 !purple_utf8_strcasecmp(name
, purple_group_get_name(group
))) {
1124 LOG
->message(_("Specified group is already in the list."));
1125 // TODO Add group merging. Note that purple_blist_rename_group() can do the
1129 purple_blist_rename_group(group
, name
);
1131 // Close context menu.
1135 void BuddyListGroup::GroupContextMenu::onRename(Button
& /*activator*/)
1137 PurpleGroup
*group
= parent_group_
->getPurpleGroup();
1139 new CppConsUI::InputDialog(_("Rename"), purple_group_get_name(group
));
1140 dialog
->signal_response
.connect(
1141 sigc::mem_fun(this, &GroupContextMenu::renameResponseHandler
));
1145 void BuddyListGroup::GroupContextMenu::removeResponseHandler(
1146 CppConsUI::MessageDialog
& /*activator*/,
1147 CppConsUI::AbstractDialog::ResponseType response
)
1149 if (response
!= CppConsUI::AbstractDialog::RESPONSE_OK
)
1152 // Based on gtkdialogs.c:pidgin_dialogs_remove_group_cb().
1153 PurpleGroup
*group
= parent_group_
->getPurpleGroup();
1154 PurpleBlistNode
*cnode
=
1155 purple_blist_node_get_first_child(PURPLE_BLIST_NODE(group
));
1156 while (cnode
!= nullptr) {
1157 if (PURPLE_BLIST_NODE_IS_CONTACT(cnode
)) {
1158 PurpleBlistNode
*bnode
= purple_blist_node_get_first_child(cnode
);
1159 cnode
= purple_blist_node_get_sibling_next(cnode
);
1160 while (bnode
!= nullptr)
1161 if (PURPLE_BLIST_NODE_IS_BUDDY(bnode
)) {
1162 PurpleBuddy
*buddy
= PURPLE_BUDDY(bnode
);
1163 PurpleAccount
*account
= purple_buddy_get_account(buddy
);
1164 bnode
= purple_blist_node_get_sibling_next(bnode
);
1165 if (purple_account_is_connected(account
)) {
1166 purple_account_remove_buddy(account
, buddy
, group
);
1167 purple_blist_remove_buddy(buddy
);
1171 bnode
= purple_blist_node_get_sibling_next(bnode
);
1173 else if (PURPLE_BLIST_NODE_IS_CHAT(cnode
)) {
1174 PurpleChat
*chat
= PURPLE_CHAT(cnode
);
1175 cnode
= purple_blist_node_get_sibling_next(cnode
);
1176 purple_blist_remove_chat(chat
);
1179 cnode
= purple_blist_node_get_sibling_next(cnode
);
1182 // Close the context menu before the group is deleted because its deletion can
1183 // lead to destruction of this object.
1186 purple_blist_remove_group(group
);
1189 void BuddyListGroup::GroupContextMenu::onRemove(Button
& /*activator*/)
1191 PurpleGroup
*group
= parent_group_
->getPurpleGroup();
1192 char *msg
= g_strdup_printf(
1193 _("Are you sure you want to delete group %s from the list?"),
1194 purple_group_get_name(group
));
1195 auto dialog
= new CppConsUI::MessageDialog(_("Group deletion"), msg
);
1197 dialog
->signal_response
.connect(
1198 sigc::mem_fun(this, &GroupContextMenu::removeResponseHandler
));
1202 void BuddyListGroup::GroupContextMenu::onMoveAfter(
1203 Button
& /*activator*/, PurpleGroup
*group
)
1205 PurpleGroup
*moved_group
= parent_group_
->getPurpleGroup();
1208 purple_blist_add_group(moved_group
, PURPLE_BLIST_NODE(group
));
1211 void BuddyListGroup::openContextMenu()
1213 ContextMenu
*w
= new GroupContextMenu(*this);
1217 BuddyListGroup::BuddyListGroup(PurpleBlistNode
*node
) : BuddyListNode(node
)
1219 setColorScheme(CenterIM::SCHEME_BUDDYLISTGROUP
);
1221 group_
= PURPLE_GROUP(blist_node_
);
1224 // vim: set tabstop=2 shiftwidth=2 textwidth=80 expandtab: