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 this program. If not, see <http://www.gnu.org/licenses/>.
19 #include "BuddyList.h"
25 #include <cppconsui/Spacer.h>
30 BuddyList
*BuddyList::my_instance_
= nullptr;
32 BuddyList
*BuddyList::instance()
37 bool BuddyList::processInputText(const TermKeyKey
&key
)
39 if (!filter_
->isVisible())
42 size_t input_len
= strlen(key
.utf8
);
43 if (filter_buffer_length_
+ input_len
+ 1 > sizeof(filter_buffer_
))
46 char *pos
= filter_buffer_
+ filter_buffer_length_
;
47 strcpy(pos
, key
.utf8
);
48 filter_buffer_onscreen_width_
+= CppConsUI::Curses::onScreenWidth(pos
);
49 filter_buffer_length_
+= input_len
;
51 updateList(UPDATE_OTHERS
);
57 bool BuddyList::restoreFocus()
59 FOOTER
->setText(_("%s act conv, %s main menu, %s context menu, %s filter"),
60 "centerim|conversation-active", "centerim|generalmenu",
61 "buddylist|contextmenu", "buddylist|filter");
63 return CppConsUI::Window::restoreFocus();
66 void BuddyList::ungrabFocus()
68 FOOTER
->setText(nullptr);
69 CppConsUI::Window::ungrabFocus();
72 void BuddyList::close()
74 // BuddyList cannot be closed, instead close the filter input if it is active.
75 if (!filter_
->isVisible())
79 updateList(UPDATE_OTHERS
);
82 void BuddyList::onScreenResized()
84 moveResizeRect(CENTERIM
->getScreenArea(CenterIM::BUDDY_LIST_AREA
));
87 void BuddyList::updateNode(PurpleBlistNode
*node
)
89 update(buddylist_
, node
);
92 BuddyList::Filter::Filter(BuddyList
*parent_blist
)
93 : Widget(AUTOSIZE
, 1), parent_blist_(parent_blist
)
97 int BuddyList::Filter::draw(
98 CppConsUI::Curses::ViewPort area
, CppConsUI::Error
&error
)
101 DRAW(area
.addString(0, 0, real_width_
, _("Filter: "), error
, &printed
));
102 if (static_cast<int>(parent_blist_
->filter_buffer_onscreen_width_
) <=
103 real_width_
- printed
) {
104 // Optimized simple case.
105 DRAW(area
.addString(printed
, 0, parent_blist_
->filter_buffer_
, error
));
111 parent_blist_
->filter_buffer_
+ parent_blist_
->filter_buffer_length_
;
114 g_utf8_find_prev_char(parent_blist_
->filter_buffer_
, cur
);
117 gunichar uc
= g_utf8_get_char(prev
);
118 int wc
= CppConsUI::Curses::onScreenWidth(uc
);
119 if (w
+ wc
> real_width_
- printed
)
124 DRAW(area
.addString(real_width_
- w
, 0, cur
, error
));
129 BuddyList::AddWindow::AddWindow(const char *title
)
130 : SplitDialog(0, 0, 80, 24, title
)
132 setColorScheme(CenterIM::SCHEME_GENERALWINDOW
);
134 treeview_
= new CppConsUI::TreeView(AUTOSIZE
, AUTOSIZE
);
135 setContainer(*treeview_
);
137 buttons_
->appendItem(_("Add"), sigc::mem_fun(this, &AddWindow::onAddRequest
));
138 buttons_
->appendSeparator();
139 buttons_
->appendItem(
140 _("Cancel"), sigc::hide(sigc::mem_fun(this, &AddWindow::close
)));
145 void BuddyList::AddWindow::onScreenResized()
147 moveResizeRect(CENTERIM
->getScreenArea(CenterIM::CHAT_AREA
));
150 BuddyList::AddWindow::AccountOption::AccountOption(
151 PurpleAccount
*default_account
, bool chat_only
)
152 : ComboBox(_("Account"))
154 for (GList
*l
= purple_accounts_get_all(); l
!= nullptr; l
= l
->next
) {
155 PurpleAccount
*account
= static_cast<PurpleAccount
*>(l
->data
);
156 if (!purple_account_is_connected(account
))
160 PurpleConnection
*gc
= purple_account_get_connection(account
);
161 if (PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
)->join_chat
== nullptr)
166 g_strdup_printf("[%s] %s", purple_account_get_protocol_name(account
),
167 purple_account_get_username(account
));
168 addOptionPtr(label
, account
);
171 setSelectedByDataPtr(default_account
);
174 BuddyList::AddWindow::GroupOption::GroupOption(const char *default_group
)
175 : ComboBox(_("Group"))
177 bool add_default_group
= true;
178 for (PurpleBlistNode
*node
= purple_blist_get_root(); node
!= nullptr;
179 node
= purple_blist_node_get_sibling_next(node
))
180 if (PURPLE_BLIST_NODE_IS_GROUP(node
)) {
181 const char *name
= purple_group_get_name(PURPLE_GROUP(node
));
182 int opt
= addOption(name
);
183 if (default_group
!= nullptr && std::strcmp(default_group
, name
) == 0)
185 add_default_group
= false;
187 if (add_default_group
)
188 addOption(_("Default"));
191 BuddyList::AddWindow::StringOption::StringOption(
192 const char *text
, const char *value
, bool masked
)
193 : Button(FLAG_VALUE
, text
, value
, nullptr, nullptr, masked
)
195 signal_activate
.connect(sigc::mem_fun(this, &StringOption::onActivate
));
198 void BuddyList::AddWindow::StringOption::onActivate(
199 CppConsUI::Button
& /*activator*/)
201 auto dialog
= new CppConsUI::InputDialog(getText(), getValue());
202 dialog
->setMasked(isMasked());
203 dialog
->signal_response
.connect(
204 sigc::mem_fun(this, &StringOption::responseHandler
));
208 void BuddyList::AddWindow::StringOption::responseHandler(
209 CppConsUI::InputDialog
&activator
,
210 CppConsUI::AbstractDialog::ResponseType response
)
212 if (response
!= AbstractDialog::RESPONSE_OK
)
215 setValue(activator
.getText());
218 BuddyList::AddWindow::IntegerOption::IntegerOption(
219 const char *text
, const char *value
, bool masked
, int min
, int max
)
220 : Button(FLAG_VALUE
, text
, nullptr, nullptr, nullptr, masked
),
221 min_value_(min
), max_value_(max
)
223 // Make sure that the default value is in the range.
224 long i
= strtol(value
, nullptr, 10);
225 i
= CLAMP(i
, min_value_
, max_value_
);
228 signal_activate
.connect(sigc::mem_fun(this, &IntegerOption::onActivate
));
231 void BuddyList::AddWindow::IntegerOption::onActivate(
232 CppConsUI::Button
& /*activator*/)
234 auto dialog
= new CppConsUI::InputDialog(getText(), getValue());
235 dialog
->setFlags(CppConsUI::TextEntry::FLAG_NUMERIC
);
236 dialog
->setMasked(isMasked());
237 dialog
->signal_response
.connect(
238 sigc::mem_fun(this, &IntegerOption::responseHandler
));
242 void BuddyList::AddWindow::IntegerOption::responseHandler(
243 CppConsUI::InputDialog
&activator
,
244 CppConsUI::AbstractDialog::ResponseType response
)
246 if (response
!= AbstractDialog::RESPONSE_OK
)
249 const char *text
= activator
.getText();
251 long i
= strtol(text
, nullptr, 10);
252 if (errno
== ERANGE
|| i
> max_value_
|| i
< min_value_
)
253 LOG
->warning(_("Value is out of range."));
254 i
= CLAMP(i
, min_value_
, max_value_
);
258 BuddyList::AddWindow::BooleanOption::BooleanOption(
259 const char *text
, bool checked
)
260 : CheckBox(text
, checked
)
264 BuddyList::AddBuddyWindow::AddBuddyWindow(PurpleAccount
*account
,
265 const char *username
, const char *group
, const char *alias
)
266 : AddWindow(_("Add buddy"))
268 CppConsUI::TreeView::NodeReference parent
= treeview_
->getRootNode();
270 account_option_
= new AccountOption(account
);
271 treeview_
->appendNode(parent
, *account_option_
);
272 account_option_
->grabFocus();
274 name_option_
= new StringOption(_("Buddy name"), username
);
275 treeview_
->appendNode(parent
, *name_option_
);
277 alias_option_
= new StringOption(_("Alias"), alias
);
278 treeview_
->appendNode(parent
, *alias_option_
);
280 group_option_
= new GroupOption(group
);
281 treeview_
->appendNode(parent
, *group_option_
);
284 void BuddyList::AddBuddyWindow::onAddRequest(CppConsUI::Button
& /*activator*/)
286 PurpleAccount
*account
=
287 static_cast<PurpleAccount
*>(account_option_
->getSelectedDataPtr());
288 const char *name
= name_option_
->getValue();
289 const char *alias
= alias_option_
->getValue();
290 const char *group
= group_option_
->getSelectedTitle();
292 if (!purple_account_is_connected(account
)) {
293 LOG
->message(_("Selected account is not connected."));
296 if (name
== nullptr || name
[0] == '\0') {
297 LOG
->message(_("No buddy name specified."));
300 if (alias
!= nullptr && alias
[0] == '\0')
303 PurpleGroup
*g
= purple_find_group(group
);
305 g
= purple_group_new(group
);
306 purple_blist_add_group(g
, nullptr);
308 PurpleBuddy
*b
= purple_find_buddy_in_group(account
, name
, g
);
310 LOG
->message(_("Specified buddy is already in the list."));
312 // Add the specified buddy.
313 b
= purple_buddy_new(account
, name
, alias
);
314 purple_blist_add_buddy(b
, nullptr, g
, nullptr);
315 purple_account_add_buddy(account
, b
);
321 BuddyList::AddChatWindow::AddChatWindow(PurpleAccount
*account
,
322 const char * /*name*/, const char *alias
, const char *group
)
323 : AddWindow(_("Add chat"))
325 CppConsUI::TreeView::NodeReference parent
= treeview_
->getRootNode();
327 account_option_
= new AccountOption(account
, true);
328 account_option_
->signal_selection_changed
.connect(
329 sigc::mem_fun(this, &AddChatWindow::onAccountChanged
));
330 account_option_ref_
= treeview_
->appendNode(parent
, *account_option_
);
331 account_option_
->grabFocus();
333 alias_option_
= new StringOption(_("Alias"), alias
);
334 treeview_
->appendNode(parent
, *alias_option_
);
336 group_option_
= new GroupOption(group
);
337 treeview_
->appendNode(parent
, *group_option_
);
339 autojoin_option_
= new BooleanOption(_("Auto-join"), false);
340 treeview_
->appendNode(parent
, *autojoin_option_
);
342 // Add protocol-specific fields.
344 static_cast<PurpleAccount
*>(account_option_
->getSelectedDataPtr()));
347 void BuddyList::AddChatWindow::onAddRequest(CppConsUI::Button
& /*activator*/)
349 PurpleAccount
*account
=
350 static_cast<PurpleAccount
*>(account_option_
->getSelectedDataPtr());
351 const char *alias
= alias_option_
->getValue();
352 const char *group
= group_option_
->getSelectedTitle();
353 bool autojoin
= autojoin_option_
->isChecked();
355 if (!purple_account_is_connected(account
)) {
356 LOG
->message(_("Selected account is not connected."));
360 GHashTable
*components
=
361 g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, g_free
);
362 for (ChatInfos::value_type
&chat_info
: chat_infos_
) {
363 CppConsUI::Button
*button
=
364 dynamic_cast<CppConsUI::Button
*>(chat_info
.second
->getWidget());
365 g_assert(button
!= nullptr);
366 const char *value
= button
->getValue();
367 if (value
[0] != '\0')
368 g_hash_table_replace(components
, g_strdup(chat_info
.first
.c_str()),
369 g_strdup(button
->getValue()));
372 PurpleChat
*chat
= purple_chat_new(account
, alias
, components
);
374 if (chat
!= nullptr) {
375 PurpleGroup
*g
= purple_find_group(group
);
377 g
= purple_group_new(group
);
378 purple_blist_add_group(g
, nullptr);
380 purple_blist_add_chat(chat
, g
, nullptr);
381 purple_blist_node_set_bool(
382 PURPLE_BLIST_NODE(chat
), PACKAGE_NAME
"-autojoin", autojoin
);
388 void BuddyList::AddChatWindow::populateChatInfo(PurpleAccount
*account
)
390 // Remove old entries.
391 for (ChatInfos::value_type
&chat_info
: chat_infos_
)
392 treeview_
->deleteNode(chat_info
.second
, false);
395 PurpleConnection
*gc
= purple_account_get_connection(account
);
396 PurplePluginProtocolInfo
*info
=
397 PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc
));
398 if (info
->chat_info
== nullptr)
401 GHashTable
*defaults
= nullptr;
402 if (info
->chat_info_defaults
!= nullptr)
403 defaults
= info
->chat_info_defaults(gc
, "");
405 CppConsUI::TreeView::NodeReference ref
= account_option_ref_
;
406 for (GList
*l
= info
->chat_info(gc
); l
!= nullptr; l
= l
->next
) {
407 struct proto_chat_entry
*entry
=
408 static_cast<struct proto_chat_entry
*>(l
->data
);
410 // Remove any accelerator from the label:
411 char *label
= Utils::stripAccelerator(entry
->label
);
413 // And strip any trailing colon.
414 size_t len
= strlen(label
);
415 if (label
[len
- 1] == ':')
416 label
[len
- 1] = '\0';
418 // Get default value.
419 const char *value
= nullptr;
420 if (defaults
!= nullptr)
421 value
= static_cast<const char *>(
422 g_hash_table_lookup(defaults
, entry
->identifier
));
424 CppConsUI::Button
*button
;
427 new IntegerOption(label
, value
, entry
->secret
, entry
->min
, entry
->max
);
429 button
= new StringOption(label
, value
, entry
->secret
);
432 ref
= treeview_
->insertNodeAfter(ref
, *button
);
433 chat_infos_
[entry
->identifier
] = ref
;
436 if (defaults
!= nullptr)
437 g_hash_table_destroy(defaults
);
440 void BuddyList::AddChatWindow::onAccountChanged(
441 CppConsUI::Button
& /*activator*/, size_t /*new_entry*/,
442 const char * /*title*/, intptr_t data
)
444 populateChatInfo(reinterpret_cast<PurpleAccount
*>(data
));
447 BuddyList::AddGroupWindow::AddGroupWindow() : AddWindow(_("Add group"))
449 name_option_
= new StringOption(_("Name"), _("New group"));
450 treeview_
->appendNode(treeview_
->getRootNode(), *name_option_
);
451 name_option_
->grabFocus();
454 void BuddyList::AddGroupWindow::onAddRequest(CppConsUI::Button
& /*activator*/)
456 const char *name
= name_option_
->getValue();
457 if (name
== nullptr || name
[0] == '\0') {
458 LOG
->message(_("No group name specified."));
462 PurpleGroup
*group
= purple_group_new(name
);
463 purple_blist_add_group(group
, nullptr);
468 BuddyList::BuddyList() : Window(0, 0, 80, 24)
470 setColorScheme(CenterIM::SCHEME_BUDDYLIST
);
472 auto lbox
= new CppConsUI::ListBox(AUTOSIZE
, AUTOSIZE
);
473 addWidget(*lbox
, 1, 1);
475 auto hbox
= new CppConsUI::HorizontalListBox(AUTOSIZE
, AUTOSIZE
);
476 lbox
->appendWidget(*hbox
);
477 hbox
->appendWidget(*(new CppConsUI::Spacer(1, AUTOSIZE
)));
478 treeview_
= new CppConsUI::TreeView(AUTOSIZE
, AUTOSIZE
);
479 hbox
->appendWidget(*treeview_
);
480 hbox
->appendWidget(*(new CppConsUI::Spacer(1, AUTOSIZE
)));
482 filter_
= new Filter(this);
484 lbox
->appendWidget(*filter_
);
486 // TODO Check if this has been moved to purple_blist_init(). Remove these
487 // lines if it was as this will probably move to purple_init(), the buddylist
488 // object should be available a lot more early and the uiops should be set a
489 // lot more early. (All in all a lot of work.)
490 buddylist_
= purple_blist_new();
491 buddylist_
->ui_data
= this;
492 purple_set_blist(buddylist_
);
495 purple_pounces_load();
498 purple_prefs_add_none(CONF_PREFIX
"/blist");
499 purple_prefs_add_bool(CONF_PREFIX
"/blist/show_empty_groups", false);
500 purple_prefs_add_bool(CONF_PREFIX
"/blist/show_offline_buddies", true);
501 purple_prefs_add_string(CONF_PREFIX
"/blist/list_mode", "normal");
502 purple_prefs_add_string(CONF_PREFIX
"/blist/group_sort_mode", "name");
503 purple_prefs_add_string(CONF_PREFIX
"/blist/buddy_sort_mode", "status");
504 purple_prefs_add_string(CONF_PREFIX
"/blist/colorization_mode", "none");
506 updateCachedPreference(CONF_PREFIX
"/blist/show_empty_groups");
507 updateCachedPreference(CONF_PREFIX
"/blist/show_offline_buddies");
508 updateCachedPreference(CONF_PREFIX
"/blist/list_mode");
509 updateCachedPreference(CONF_PREFIX
"/blist/group_sort_mode");
510 updateCachedPreference(CONF_PREFIX
"/blist/buddy_sort_mode");
511 updateCachedPreference(CONF_PREFIX
"/blist/colorization_mode");
513 // Connect callbacks.
514 purple_prefs_connect_callback(
515 this, CONF_PREFIX
"/blist", blist_pref_change_
, this);
517 // Setup the callbacks for the buddylist.
518 memset(¢erim_blist_ui_ops_
, 0, sizeof(centerim_blist_ui_ops_
));
519 centerim_blist_ui_ops_
.new_list
= new_list_
;
520 centerim_blist_ui_ops_
.new_node
= new_node_
;
521 // centerim_blist_ui_ops_.show = show_;
522 centerim_blist_ui_ops_
.update
= update_
;
523 centerim_blist_ui_ops_
.remove
= remove_
;
524 centerim_blist_ui_ops_
.destroy
= destroy_
;
525 // centerim_blist_ui_ops_.set_visible = set_visible_;
526 centerim_blist_ui_ops_
.request_add_buddy
= request_add_buddy_
;
527 centerim_blist_ui_ops_
.request_add_chat
= request_add_chat_
;
528 centerim_blist_ui_ops_
.request_add_group
= request_add_group_
;
529 // Since libpurple 2.6.0.
530 // centerim_blist_ui_ops_.save_node = save_node_;
531 // centerim_blist_ui_ops_.remove_node = remove_node_;
532 // centerim_blist_ui_ops_.save_account = save_account_;
533 purple_blist_set_ui_ops(¢erim_blist_ui_ops_
);
535 CENTERIM
->timeoutOnceConnect(sigc::mem_fun(this, &BuddyList::load
), 0);
541 BuddyList::~BuddyList()
543 purple_blist_set_ui_ops(nullptr);
544 purple_prefs_disconnect_by_handle(this);
547 void BuddyList::init()
549 g_assert(my_instance_
== nullptr);
551 my_instance_
= new BuddyList
;
552 my_instance_
->show();
555 void BuddyList::finalize()
557 g_assert(my_instance_
!= nullptr);
560 my_instance_
= nullptr;
563 void BuddyList::load()
565 // Load the buddy list from ~/.centerim5/blist.xml.
568 delayedGroupNodesInit();
571 void BuddyList::rebuildList()
575 for (PurpleBlistNode
*node
= purple_blist_get_root(); node
!= nullptr;
576 node
= purple_blist_node_next(node
, TRUE
))
580 void BuddyList::updateList(int flags
)
582 for (PurpleBlistNode
*node
= purple_blist_get_root(); node
!= nullptr;
583 node
= purple_blist_node_next(node
, TRUE
))
584 if (PURPLE_BLIST_NODE_IS_GROUP(node
) ? (flags
& UPDATE_GROUPS
)
585 : (flags
& UPDATE_OTHERS
)) {
586 BuddyListNode
*bnode
=
587 reinterpret_cast<BuddyListNode
*>(purple_blist_node_get_ui_data(node
));
588 if (bnode
!= nullptr)
593 void BuddyList::delayedGroupNodesInit()
595 // Delayed group nodes init.
596 for (PurpleBlistNode
*node
= purple_blist_get_root(); node
!= nullptr;
597 node
= purple_blist_node_get_sibling_next(node
))
598 if (PURPLE_BLIST_NODE_IS_GROUP(node
)) {
599 BuddyListGroup
*gnode
=
600 reinterpret_cast<BuddyListGroup
*>(purple_blist_node_get_ui_data(node
));
601 if (gnode
!= nullptr)
602 gnode
->initCollapsedState();
606 void BuddyList::updateCachedPreference(const char *name
)
608 if (std::strcmp(name
, CONF_PREFIX
"/blist/show_empty_groups") == 0)
609 show_empty_groups_
= purple_prefs_get_bool(name
);
610 else if (std::strcmp(name
, CONF_PREFIX
"/blist/show_offline_buddies") == 0)
611 show_offline_buddies_
= purple_prefs_get_bool(name
);
612 else if (std::strcmp(name
, CONF_PREFIX
"/blist/list_mode") == 0) {
613 const char *value
= purple_prefs_get_string(name
);
614 if (std::strcmp(value
, "flat") == 0)
615 list_mode_
= LIST_FLAT
;
617 list_mode_
= LIST_NORMAL
;
619 else if (std::strcmp(name
, CONF_PREFIX
"/blist/group_sort_mode") == 0) {
620 const char *value
= purple_prefs_get_string(name
);
621 if (std::strcmp(value
, "user") == 0)
622 group_sort_mode_
= GROUP_SORT_BY_USER
;
624 group_sort_mode_
= GROUP_SORT_BY_NAME
;
626 else if (std::strcmp(name
, CONF_PREFIX
"/blist/buddy_sort_mode") == 0) {
627 const char *value
= purple_prefs_get_string(name
);
628 if (std::strcmp(value
, "status") == 0)
629 buddy_sort_mode_
= BUDDY_SORT_BY_STATUS
;
630 else if (std::strcmp(value
, "activity") == 0)
631 buddy_sort_mode_
= BUDDY_SORT_BY_ACTIVITY
;
633 buddy_sort_mode_
= BUDDY_SORT_BY_NAME
;
635 else if (std::strcmp(name
, CONF_PREFIX
"/blist/colorization_mode") == 0) {
636 const char *value
= purple_prefs_get_string(name
);
637 if (std::strcmp(value
, "status") == 0)
638 colorization_mode_
= COLOR_BY_STATUS
;
639 else if (std::strcmp(value
, "account") != 0)
640 colorization_mode_
= COLOR_BY_ACCOUNT
;
642 colorization_mode_
= COLOR_NONE
;
646 bool BuddyList::isAnyAccountConnected()
648 for (GList
*list
= purple_accounts_get_all(); list
!= nullptr;
650 PurpleAccount
*account
= reinterpret_cast<PurpleAccount
*>(list
->data
);
651 if (purple_account_is_connected(account
))
655 LOG
->message(_("There are no connected accounts."));
659 void BuddyList::filterHide()
661 filter_
->setVisibility(false);
662 filter_buffer_
[0] = '\0';
663 filter_buffer_length_
= 0;
664 filter_buffer_onscreen_width_
= 0;
667 void BuddyList::actionOpenFilter()
669 if (filter_
->isVisible())
672 filter_
->setVisibility(true);
675 g_assert(filter_buffer_
[0] == '\0');
676 g_assert(filter_buffer_length_
== 0);
677 g_assert(filter_buffer_onscreen_width_
== 0);
680 void BuddyList::actionDeleteChar()
682 if (!filter_
->isVisible())
685 const char *end
= filter_buffer_
+ filter_buffer_length_
;
686 g_assert(*end
== '\0');
687 char *prev
= g_utf8_find_prev_char(filter_buffer_
, end
);
688 if (prev
!= nullptr) {
689 filter_buffer_length_
-= end
- prev
;
690 filter_buffer_onscreen_width_
-= CppConsUI::Curses::onScreenWidth(prev
);
696 updateList(UPDATE_OTHERS
);
700 void BuddyList::declareBindables()
702 declareBindable("buddylist", "filter",
703 sigc::mem_fun(this, &BuddyList::actionOpenFilter
),
704 InputProcessor::BINDABLE_NORMAL
);
705 declareBindable("textentry", "backspace",
706 sigc::mem_fun(this, &BuddyList::actionDeleteChar
),
707 InputProcessor::BINDABLE_NORMAL
);
710 void BuddyList::new_list(PurpleBuddyList
*list
)
712 if (buddylist_
!= list
)
713 LOG
->error(_("Different buddylist detected!"));
716 void BuddyList::new_node(PurpleBlistNode
*node
)
718 g_return_if_fail(!purple_blist_node_get_ui_data(node
));
720 if (PURPLE_BLIST_NODE_IS_GROUP(node
) && list_mode_
== BuddyList::LIST_FLAT
) {
721 // Flat mode = no groups.
725 BuddyListNode
*bnode
= BuddyListNode::createNode(node
);
726 if (bnode
== nullptr)
729 BuddyListNode
*parent
= bnode
->getParentNode();
730 CppConsUI::TreeView::NodeReference nref
= treeview_
->appendNode(
731 parent
? parent
->getRefNode() : treeview_
->getRootNode(), *bnode
);
732 bnode
->setRefNode(nref
);
736 void BuddyList::update(PurpleBuddyList
*list
, PurpleBlistNode
*node
)
738 // Not cool, but necessary because libpurple does not always behave nice. Note
739 // that calling new_node() can modify node's ui_data.
740 if (purple_blist_node_get_ui_data(node
) == nullptr)
743 BuddyListNode
*bnode
=
744 reinterpret_cast<BuddyListNode
*>(purple_blist_node_get_ui_data(node
));
745 if (bnode
== nullptr)
748 // Update the node data.
751 if (node
->parent
!= nullptr)
752 update(list
, node
->parent
);
755 void BuddyList::remove(PurpleBuddyList
*list
, PurpleBlistNode
*node
)
757 BuddyListNode
*bnode
=
758 reinterpret_cast<BuddyListNode
*>(purple_blist_node_get_ui_data(node
));
759 if (bnode
== nullptr)
762 treeview_
->deleteNode(bnode
->getRefNode(), false);
764 if (node
->parent
!= nullptr)
765 update(list
, node
->parent
);
768 void BuddyList::destroy(PurpleBuddyList
* /*list*/)
772 void BuddyList::request_add_buddy(PurpleAccount
*account
, const char *username
,
773 const char *group
, const char *alias
)
775 if (!isAnyAccountConnected())
778 auto window
= new AddBuddyWindow(account
, username
, group
, alias
);
782 void BuddyList::request_add_chat(PurpleAccount
*account
, PurpleGroup
*group
,
783 const char *alias
, const char *name
)
785 if (!isAnyAccountConnected())
788 // Find an account with chat capabilities.
789 bool chat_account_found
= false;
790 for (GList
*l
= purple_connections_get_all(); l
!= nullptr; l
= l
->next
) {
791 PurpleConnection
*gc
= reinterpret_cast<PurpleConnection
*>(l
->data
);
793 if (PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
)->join_chat
) {
794 chat_account_found
= true;
799 if (!chat_account_found
) {
800 LOG
->message(_("You are not currently signed on with any "
801 "protocols that have the ability to chat."));
805 const char *group_name
= nullptr;
806 if (group
!= nullptr)
807 group_name
= purple_group_get_name(group
);
808 auto window
= new AddChatWindow(account
, name
, alias
, group_name
);
812 void BuddyList::request_add_group()
814 if (!isAnyAccountConnected())
817 auto window
= new AddGroupWindow
;
821 void BuddyList::blist_pref_change(
822 const char *name
, PurplePrefType
/*type*/, gconstpointer
/*val*/)
824 // Some blist/* preference changed.
825 updateCachedPreference(name
);
827 if (std::strcmp(name
, CONF_PREFIX
"/blist/list_mode") == 0) {
832 bool groups_only
= false;
833 if (std::strcmp(name
, CONF_PREFIX
"/blist/show_empty_groups") == 0 ||
834 std::strcmp(name
, CONF_PREFIX
"/blist/group_sort_mode") == 0)
837 updateList(UPDATE_GROUPS
| (!groups_only
? UPDATE_OTHERS
: 0));
840 // vim: set tabstop=2 shiftwidth=2 textwidth=80 expandtab: