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
597 // BuddyListBuddy::getAttributes().
598 setColorScheme(CenterIM::SCHEME_BUDDYLISTBUDDY
);
603 bool BuddyListChat::lessOrEqual(const BuddyListNode
&other
) const
605 const BuddyListChat
*o
= dynamic_cast<const BuddyListChat
*>(&other
);
607 return g_utf8_collate(
608 purple_chat_get_name(chat_
), purple_chat_get_name(o
->chat_
)) <= 0;
609 return lessOrEqualByType(other
);
612 void BuddyListChat::update()
614 BuddyListNode::update();
616 const char *name
= purple_chat_get_name(chat_
);
621 // Hide if account is offline.
622 setVisibility(purple_account_is_connected(purple_chat_get_account(chat_
)));
624 updateFilterVisibility(name
);
627 void BuddyListChat::onActivate(Button
& /*activator*/)
629 PurpleAccount
*account
= purple_chat_get_account(chat_
);
630 PurplePluginProtocolInfo
*prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(
631 purple_find_prpl(purple_account_get_protocol_id(account
)));
632 GHashTable
*components
= purple_chat_get_components(chat_
);
634 char *chat_name
= nullptr;
635 if (prpl_info
!= nullptr && prpl_info
->get_chat_name
!= nullptr)
636 chat_name
= prpl_info
->get_chat_name(components
);
639 if (chat_name
!= nullptr)
642 name
= purple_chat_get_name(chat_
);
644 PurpleConversation
*conv
=
645 purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT
, name
, account
);
647 purple_conversation_present(conv
);
649 serv_join_chat(purple_account_get_connection(account
), components
);
654 const char *BuddyListChat::toString() const
656 return purple_chat_get_name(chat_
);
659 BuddyListChat::ChatContextMenu::ChatContextMenu(BuddyListChat
&parent_chat
)
660 : ContextMenu(parent_chat
), parent_chat_(&parent_chat
)
662 appendProtocolMenu(purple_account_get_connection(
663 purple_chat_get_account(parent_chat_
->getPurpleChat())));
664 appendExtendedMenu();
667 _("Alias..."), sigc::mem_fun(this, &ChatContextMenu::onChangeAlias
));
668 appendItem(_("Delete..."), sigc::mem_fun(this, &ChatContextMenu::onRemove
));
671 void BuddyListChat::ChatContextMenu::changeAliasResponseHandler(
672 CppConsUI::InputDialog
&activator
,
673 CppConsUI::AbstractDialog::ResponseType response
)
675 if (response
!= CppConsUI::AbstractDialog::RESPONSE_OK
)
678 PurpleChat
*chat
= parent_chat_
->getPurpleChat();
679 purple_blist_alias_chat(chat
, activator
.getText());
681 // Close context menu.
685 void BuddyListChat::ChatContextMenu::onChangeAlias(Button
& /*activator*/)
687 PurpleChat
*chat
= parent_chat_
->getPurpleChat();
689 new CppConsUI::InputDialog(_("Alias"), purple_chat_get_name(chat
));
690 dialog
->signal_response
.connect(
691 sigc::mem_fun(this, &ChatContextMenu::changeAliasResponseHandler
));
695 void BuddyListChat::ChatContextMenu::removeResponseHandler(
696 CppConsUI::MessageDialog
& /*activator*/,
697 CppConsUI::AbstractDialog::ResponseType response
)
699 if (response
!= CppConsUI::AbstractDialog::RESPONSE_OK
)
702 PurpleChat
*chat
= parent_chat_
->getPurpleChat();
704 // Close the context menu before the chat is deleted because its deletion can
705 // lead to destruction of this object.
708 purple_blist_remove_chat(chat
);
711 void BuddyListChat::ChatContextMenu::onRemove(Button
& /*activator*/)
713 PurpleChat
*chat
= parent_chat_
->getPurpleChat();
715 g_strdup_printf(_("Are you sure you want to delete chat %s from the list?"),
716 purple_chat_get_name(chat
));
717 auto dialog
= new CppConsUI::MessageDialog(_("Chat deletion"), msg
);
719 dialog
->signal_response
.connect(
720 sigc::mem_fun(this, &ChatContextMenu::removeResponseHandler
));
724 void BuddyListChat::openContextMenu()
726 ContextMenu
*w
= new ChatContextMenu(*this);
730 BuddyListChat::BuddyListChat(PurpleBlistNode
*node
) : BuddyListNode(node
)
732 setColorScheme(CenterIM::SCHEME_BUDDYLISTCHAT
);
734 chat_
= PURPLE_CHAT(blist_node_
);
737 bool BuddyListContact::lessOrEqual(const BuddyListNode
&other
) const
739 const BuddyListContact
*o
= dynamic_cast<const BuddyListContact
*>(&other
);
741 PurpleBuddy
*left
= purple_contact_get_priority_buddy(contact_
);
742 PurpleBuddy
*right
= purple_contact_get_priority_buddy(o
->contact_
);
743 return lessOrEqualByBuddySort(left
, right
);
745 return lessOrEqualByType(other
);
748 void BuddyListContact::update()
750 BuddyListNode::update();
752 PurpleBuddy
*buddy
= purple_contact_get_priority_buddy(contact_
);
753 if (buddy
== nullptr) {
754 // The contact does not have any associated buddy, ignore it until it gets a
756 setText("*Contact*");
757 setVisibility(false);
761 // Format contact size.
763 if (contact_
->currentsize
> 1)
764 size
= g_strdup_printf(" (%d)", contact_
->currentsize
);
768 // Format contact label.
769 const char *alias
= purple_contact_get_alias(contact_
);
770 const char *status
= getBuddyStatus(buddy
);
772 if (status
[0] != '\0')
773 text
= g_strdup_printf("%s %s%s", status
, alias
, size
? size
: "");
775 text
= g_strdup_printf("%s%s", alias
, size
? size
: "");
784 if (!purple_account_is_connected(purple_buddy_get_account(buddy
))) {
785 // Hide if account is offline.
786 setVisibility(false);
789 setVisibility(status
[0] != '\0' || BUDDYLIST
->getShowOfflineBuddiesPref());
791 updateFilterVisibility(alias
);
794 void BuddyListContact::onActivate(Button
&activator
)
796 PurpleBuddy
*buddy
= purple_contact_get_priority_buddy(contact_
);
797 BuddyListNode
*bnode
= reinterpret_cast<BuddyListNode
*>(
798 purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(buddy
)));
799 if (bnode
!= nullptr)
800 bnode
->onActivate(activator
);
803 const char *BuddyListContact::toString() const
805 return purple_contact_get_alias(contact_
);
808 void BuddyListContact::setRefNode(CppConsUI::TreeView::NodeReference n
)
810 BuddyListNode::setRefNode(n
);
811 treeview_
->setNodeStyle(n
, CppConsUI::TreeView::STYLE_VOID
);
814 void BuddyListContact::retrieveUserInfo()
816 PurpleBuddy
*buddy
= purple_contact_get_priority_buddy(contact_
);
817 PurpleConnection
*gc
=
818 purple_account_get_connection(purple_buddy_get_account(buddy
));
819 retrieveUserInfoForName(gc
, purple_buddy_get_name(buddy
));
822 BuddyListContact::ContactContextMenu::ContactContextMenu(
823 BuddyListContact
&parent_contact
)
824 : ContextMenu(parent_contact
), parent_contact_(&parent_contact
)
826 appendExtendedMenu();
828 if (parent_contact_
->isCollapsed())
829 appendItem(_("Expand"),
830 sigc::bind(sigc::mem_fun(this, &ContactContextMenu::onExpandRequest
),
833 appendItem(_("Collapse"),
834 sigc::bind(sigc::mem_fun(this, &ContactContextMenu::onExpandRequest
),
837 appendItem(_("Information..."),
838 sigc::mem_fun(this, &ContactContextMenu::onInformation
));
840 _("Alias..."), sigc::mem_fun(this, &ContactContextMenu::onChangeAlias
));
842 _("Delete..."), sigc::mem_fun(this, &ContactContextMenu::onRemove
));
844 auto groups
= new CppConsUI::MenuWindow(*this, AUTOSIZE
, AUTOSIZE
);
846 for (PurpleBlistNode
*node
= purple_blist_get_root(); node
!= nullptr;
847 node
= purple_blist_node_get_sibling_next(node
)) {
848 if (!PURPLE_BLIST_NODE_IS_GROUP(node
))
851 PurpleGroup
*group
= PURPLE_GROUP(node
);
852 CppConsUI::Button
*button
= groups
->appendItem(purple_group_get_name(group
),
853 sigc::bind(sigc::mem_fun(this, &ContactContextMenu::onMoveTo
), group
));
854 if (purple_contact_get_group(parent_contact_
->getPurpleContact()) == group
)
858 appendSubMenu(_("Move to..."), *groups
);
861 void BuddyListContact::ContactContextMenu::onExpandRequest(
862 Button
& /*activator*/, bool expand
)
864 parent_contact_
->setCollapsed(!expand
);
868 void BuddyListContact::ContactContextMenu::onInformation(Button
& /*activator*/)
870 parent_contact_
->retrieveUserInfo();
874 void BuddyListContact::ContactContextMenu::changeAliasResponseHandler(
875 CppConsUI::InputDialog
&activator
,
876 CppConsUI::AbstractDialog::ResponseType response
)
878 if (response
!= CppConsUI::AbstractDialog::RESPONSE_OK
)
881 PurpleContact
*contact
= parent_contact_
->getPurpleContact();
882 if (contact
->alias
!= nullptr)
883 purple_blist_alias_contact(contact
, activator
.getText());
885 PurpleBuddy
*buddy
= purple_contact_get_priority_buddy(contact
);
886 purple_blist_alias_buddy(buddy
, activator
.getText());
887 serv_alias_buddy(buddy
);
890 // Close context menu.
894 void BuddyListContact::ContactContextMenu::onChangeAlias(Button
& /*activator*/)
896 PurpleContact
*contact
= parent_contact_
->getPurpleContact();
898 new CppConsUI::InputDialog(_("Alias"), purple_contact_get_alias(contact
));
899 dialog
->signal_response
.connect(
900 sigc::mem_fun(this, &ContactContextMenu::changeAliasResponseHandler
));
904 void BuddyListContact::ContactContextMenu::removeResponseHandler(
905 CppConsUI::MessageDialog
& /*activator*/,
906 CppConsUI::AbstractDialog::ResponseType response
)
908 if (response
!= CppConsUI::AbstractDialog::RESPONSE_OK
)
911 // Based on gtkdialogs.c:pidgin_dialogs_remove_contact_cb().
912 PurpleContact
*contact
= parent_contact_
->getPurpleContact();
913 PurpleBlistNode
*cnode
= PURPLE_BLIST_NODE(contact
);
914 PurpleGroup
*group
= purple_contact_get_group(contact
);
916 for (PurpleBlistNode
*bnode
= purple_blist_node_get_first_child(cnode
);
917 bnode
!= nullptr; bnode
= purple_blist_node_get_sibling_next(bnode
)) {
918 PurpleBuddy
*buddy
= PURPLE_BUDDY(bnode
);
919 PurpleAccount
*account
= purple_buddy_get_account(buddy
);
920 if (purple_account_is_connected(account
))
921 purple_account_remove_buddy(account
, buddy
, group
);
924 // Close the context menu before the contact is deleted because its deletion
925 // can lead to destruction of this object.
928 purple_blist_remove_contact(contact
);
931 void BuddyListContact::ContactContextMenu::onRemove(Button
& /*activator*/)
933 PurpleContact
*contact
= parent_contact_
->getPurpleContact();
934 char *msg
= g_strdup_printf(
935 _("Are you sure you want to delete contact %s from the list?"),
936 purple_contact_get_alias(contact
));
937 auto dialog
= new CppConsUI::MessageDialog(_("Contact deletion"), msg
);
939 dialog
->signal_response
.connect(
940 sigc::mem_fun(this, &ContactContextMenu::removeResponseHandler
));
944 void BuddyListContact::ContactContextMenu::onMoveTo(
945 Button
& /*activator*/, PurpleGroup
*group
)
947 PurpleContact
*contact
= parent_contact_
->getPurpleContact();
950 purple_blist_add_contact(contact
, group
, nullptr);
953 int BuddyListContact::getAttributes(
954 int property
, int subproperty
, int *attrs
, CppConsUI::Error
&error
) const
956 if (BUDDYLIST
->getColorizationMode() != BuddyList::COLOR_BY_ACCOUNT
||
957 property
!= CppConsUI::ColorScheme::PROPERTY_BUTTON_NORMAL
)
958 return Button::getAttributes(property
, subproperty
, attrs
, error
);
960 // TODO Implement caching for these two properties.
961 PurpleAccount
*account
=
962 purple_buddy_get_account(purple_contact_get_priority_buddy(contact_
));
963 int fg
= purple_account_get_ui_int(account
, "centerim5",
964 "buddylist-foreground-color", CppConsUI::Curses::Color::DEFAULT
);
965 int bg
= purple_account_get_ui_int(account
, "centerim5",
966 "buddylist-background-color", CppConsUI::Curses::Color::DEFAULT
);
968 CppConsUI::ColorScheme::Color
c(fg
, bg
);
969 return COLORSCHEME
->getColorPair(c
, attrs
, error
);
972 void BuddyListContact::openContextMenu()
974 ContextMenu
*w
= new ContactContextMenu(*this);
978 BuddyListContact::BuddyListContact(PurpleBlistNode
*node
) : BuddyListNode(node
)
980 setColorScheme(CenterIM::SCHEME_BUDDYLISTCONTACT
);
982 contact_
= PURPLE_CONTACT(blist_node_
);
985 void BuddyListContact::updateColorScheme()
987 switch (BUDDYLIST
->getColorizationMode()) {
988 case BuddyList::COLOR_BY_STATUS
: {
989 PurpleBuddy
*buddy
= purple_contact_get_priority_buddy(contact_
);
991 getColorSchemeByBuddy(CenterIM::SCHEME_BUDDYLISTCONTACT
, buddy
));
995 // Note: COLOR_BY_ACCOUNT case is handled by
996 // BuddyListContact::getAttributes().
997 setColorScheme(CenterIM::SCHEME_BUDDYLISTCONTACT
);
1002 bool BuddyListGroup::lessOrEqual(const BuddyListNode
&other
) const
1004 // If the groups are not sorted but ordered manually then this method is not
1007 const BuddyListGroup
*o
= dynamic_cast<const BuddyListGroup
*>(&other
);
1009 return g_utf8_collate(purple_group_get_name(group_
),
1010 purple_group_get_name(o
->group_
)) <= 0;
1011 return lessOrEqualByType(other
);
1014 void BuddyListGroup::update()
1016 BuddyListNode::update();
1018 setText(purple_group_get_name(group_
));
1020 // Sort in the group.
1021 BuddyList::GroupSortMode mode
= BUDDYLIST
->getGroupSortMode();
1023 case BuddyList::GROUP_SORT_BY_USER
: {
1024 // Note that the sorting below works even if there was a contact/chat/buddy
1025 // node that is attached at the root level of the blist treeview. This
1026 // happens when such a node was just created (the new_node() callback was
1027 // called) but the node does not have any parent yet.
1029 PurpleBlistNode
*prev
= purple_blist_node_get_sibling_prev(blist_node_
);
1031 if (prev
!= nullptr) {
1032 // It better be a group node.
1033 g_assert(PURPLE_BLIST_NODE_IS_GROUP(prev
));
1035 BuddyListNode
*bnode
=
1036 reinterpret_cast<BuddyListNode
*>(purple_blist_node_get_ui_data(prev
));
1037 // There has to be ui_data set for all group nodes!
1038 g_assert(bnode
!= nullptr);
1040 treeview_
->moveNodeAfter(ref_
, bnode
->getRefNode());
1043 // The group is the first one in the list.
1044 CppConsUI::TreeView::NodeReference parent_ref
= treeview_
->getRootNode();
1045 treeview_
->moveNodeBefore(ref_
, parent_ref
.begin());
1048 case BuddyList::GROUP_SORT_BY_NAME
:
1054 if (!BUDDYLIST
->getShowEmptyGroupsPref())
1055 vis
= purple_blist_get_group_size(group_
, FALSE
);
1059 void BuddyListGroup::onActivate(Button
& /*activator*/)
1061 treeview_
->toggleCollapsed(ref_
);
1062 purple_blist_node_set_bool(blist_node_
, "collapsed", ref_
->isCollapsed());
1065 const char *BuddyListGroup::toString() const
1067 return purple_group_get_name(group_
);
1070 void BuddyListGroup::setRefNode(CppConsUI::TreeView::NodeReference n
)
1072 BuddyListNode::setRefNode(n
);
1073 initCollapsedState();
1076 void BuddyListGroup::initCollapsedState()
1078 // This cannot be done when the purple_blist_load() function was called
1079 // because node settings are unavailable at that time.
1080 treeview_
->setCollapsed(
1081 ref_
, purple_blist_node_get_bool(blist_node_
, "collapsed"));
1084 BuddyListGroup::GroupContextMenu::GroupContextMenu(BuddyListGroup
&parent_group
)
1085 : ContextMenu(parent_group
), parent_group_(&parent_group
)
1087 appendExtendedMenu();
1089 appendItem(_("Rename..."), sigc::mem_fun(this, &GroupContextMenu::onRename
));
1090 appendItem(_("Delete..."), sigc::mem_fun(this, &GroupContextMenu::onRemove
));
1092 if (BUDDYLIST
->getGroupSortMode() == BuddyList::GROUP_SORT_BY_USER
) {
1093 // If the manual sorting is enabled then show a menu item and a submenu for
1095 auto groups
= new CppConsUI::MenuWindow(*this, AUTOSIZE
, AUTOSIZE
);
1097 groups
->appendItem(_("-Top-"),
1098 sigc::bind(sigc::mem_fun(this, &GroupContextMenu::onMoveAfter
),
1099 static_cast<PurpleGroup
*>(nullptr)));
1100 for (PurpleBlistNode
*node
= purple_blist_get_root(); node
!= nullptr;
1101 node
= purple_blist_node_get_sibling_next(node
)) {
1102 if (!PURPLE_BLIST_NODE_IS_GROUP(node
))
1105 PurpleGroup
*group
= PURPLE_GROUP(node
);
1106 groups
->appendItem(purple_group_get_name(group
),
1107 sigc::bind(sigc::mem_fun(this, &GroupContextMenu::onMoveAfter
), group
));
1110 appendSubMenu(_("Move after..."), *groups
);
1114 void BuddyListGroup::GroupContextMenu::renameResponseHandler(
1115 CppConsUI::InputDialog
&activator
,
1116 CppConsUI::AbstractDialog::ResponseType response
)
1118 if (response
!= CppConsUI::AbstractDialog::RESPONSE_OK
)
1121 const char *name
= activator
.getText();
1122 PurpleGroup
*group
= parent_group_
->getPurpleGroup();
1123 PurpleGroup
*other
= purple_find_group(name
);
1124 if (other
!= nullptr &&
1125 !purple_utf8_strcasecmp(name
, purple_group_get_name(group
))) {
1126 LOG
->message(_("Specified group is already in the list."));
1127 // TODO Add group merging. Note that purple_blist_rename_group() can do the
1131 purple_blist_rename_group(group
, name
);
1133 // Close context menu.
1137 void BuddyListGroup::GroupContextMenu::onRename(Button
& /*activator*/)
1139 PurpleGroup
*group
= parent_group_
->getPurpleGroup();
1141 new CppConsUI::InputDialog(_("Rename"), purple_group_get_name(group
));
1142 dialog
->signal_response
.connect(
1143 sigc::mem_fun(this, &GroupContextMenu::renameResponseHandler
));
1147 void BuddyListGroup::GroupContextMenu::removeResponseHandler(
1148 CppConsUI::MessageDialog
& /*activator*/,
1149 CppConsUI::AbstractDialog::ResponseType response
)
1151 if (response
!= CppConsUI::AbstractDialog::RESPONSE_OK
)
1154 // Based on gtkdialogs.c:pidgin_dialogs_remove_group_cb().
1155 PurpleGroup
*group
= parent_group_
->getPurpleGroup();
1156 PurpleBlistNode
*cnode
=
1157 purple_blist_node_get_first_child(PURPLE_BLIST_NODE(group
));
1158 while (cnode
!= nullptr) {
1159 if (PURPLE_BLIST_NODE_IS_CONTACT(cnode
)) {
1160 PurpleBlistNode
*bnode
= purple_blist_node_get_first_child(cnode
);
1161 cnode
= purple_blist_node_get_sibling_next(cnode
);
1162 while (bnode
!= nullptr)
1163 if (PURPLE_BLIST_NODE_IS_BUDDY(bnode
)) {
1164 PurpleBuddy
*buddy
= PURPLE_BUDDY(bnode
);
1165 PurpleAccount
*account
= purple_buddy_get_account(buddy
);
1166 bnode
= purple_blist_node_get_sibling_next(bnode
);
1167 if (purple_account_is_connected(account
)) {
1168 purple_account_remove_buddy(account
, buddy
, group
);
1169 purple_blist_remove_buddy(buddy
);
1173 bnode
= purple_blist_node_get_sibling_next(bnode
);
1175 else if (PURPLE_BLIST_NODE_IS_CHAT(cnode
)) {
1176 PurpleChat
*chat
= PURPLE_CHAT(cnode
);
1177 cnode
= purple_blist_node_get_sibling_next(cnode
);
1178 purple_blist_remove_chat(chat
);
1181 cnode
= purple_blist_node_get_sibling_next(cnode
);
1184 // Close the context menu before the group is deleted because its deletion can
1185 // lead to destruction of this object.
1188 purple_blist_remove_group(group
);
1191 void BuddyListGroup::GroupContextMenu::onRemove(Button
& /*activator*/)
1193 PurpleGroup
*group
= parent_group_
->getPurpleGroup();
1194 char *msg
= g_strdup_printf(
1195 _("Are you sure you want to delete group %s from the list?"),
1196 purple_group_get_name(group
));
1197 auto dialog
= new CppConsUI::MessageDialog(_("Group deletion"), msg
);
1199 dialog
->signal_response
.connect(
1200 sigc::mem_fun(this, &GroupContextMenu::removeResponseHandler
));
1204 void BuddyListGroup::GroupContextMenu::onMoveAfter(
1205 Button
& /*activator*/, PurpleGroup
*group
)
1207 PurpleGroup
*moved_group
= parent_group_
->getPurpleGroup();
1210 purple_blist_add_group(moved_group
, PURPLE_BLIST_NODE(group
));
1213 void BuddyListGroup::openContextMenu()
1215 ContextMenu
*w
= new GroupContextMenu(*this);
1219 BuddyListGroup::BuddyListGroup(PurpleBlistNode
*node
) : BuddyListNode(node
)
1221 setColorScheme(CenterIM::SCHEME_BUDDYLISTGROUP
);
1223 group_
= PURPLE_GROUP(blist_node_
);
1226 // vim: set tabstop=2 shiftwidth=2 textwidth=80 expandtab: