Use pkg-config to find ncursesw
[centerim5.git] / src / BuddyList.cpp
blobc846a76e71e80d71aa84845a5d7d7d29b2506380
1 // Copyright (C) 2007 Mark Pustjens <pustjens@dds.nl>
2 // Copyright (C) 2010-2015 Petr Pavlu <setup@dagobah.cz>
3 //
4 // This file is part of CenterIM.
5 //
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 "BuddyList.h"
21 #include "Footer.h"
22 #include "Log.h"
23 #include "Utils.h"
25 #include "gettext.h"
26 #include <cppconsui/Spacer.h>
27 #include <cstdlib>
28 #include <cstring>
30 BuddyList *BuddyList::my_instance_ = nullptr;
32 BuddyList *BuddyList::instance()
34 return my_instance_;
37 bool BuddyList::processInputText(const TermKeyKey &key)
39 if (!filter_->isVisible())
40 return false;
42 std::size_t input_len = strlen(key.utf8);
43 if (filter_buffer_length_ + input_len + 1 > sizeof(filter_buffer_))
44 return false;
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);
52 redraw();
54 return true;
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())
76 return;
78 filterHide();
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)
100 int printed;
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));
106 return 0;
109 int w = 0;
110 const char *cur =
111 parent_blist_->filter_buffer_ + parent_blist_->filter_buffer_length_;
112 while (true) {
113 const char *prev =
114 g_utf8_find_prev_char(parent_blist_->filter_buffer_, cur);
115 if (!prev)
116 break;
117 gunichar uc = g_utf8_get_char(prev);
118 int wc = CppConsUI::Curses::onScreenWidth(uc);
119 if (w + wc > real_width_ - printed)
120 break;
121 w += wc;
122 cur = prev;
124 DRAW(area.addString(real_width_ - w, 0, cur, error));
126 return 0;
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)));
142 onScreenResized();
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))
157 continue;
159 if (chat_only) {
160 PurpleConnection *gc = purple_account_get_connection(account);
161 if (PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->join_chat == nullptr)
162 continue;
165 char *label =
166 g_strdup_printf("[%s] %s", purple_account_get_protocol_name(account),
167 purple_account_get_username(account));
168 addOptionPtr(label, account);
169 g_free(label);
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)
184 setSelected(opt);
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));
205 dialog->show();
208 void BuddyList::AddWindow::StringOption::responseHandler(
209 CppConsUI::InputDialog &activator,
210 CppConsUI::AbstractDialog::ResponseType response)
212 if (response != AbstractDialog::RESPONSE_OK)
213 return;
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 = std::strtol(value, nullptr, 10);
225 i = CLAMP(i, min_value_, max_value_);
226 setValue(i);
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));
239 dialog->show();
242 void BuddyList::AddWindow::IntegerOption::responseHandler(
243 CppConsUI::InputDialog &activator,
244 CppConsUI::AbstractDialog::ResponseType response)
246 if (response != AbstractDialog::RESPONSE_OK)
247 return;
249 long num;
250 if (!Utils::stringToNumber(activator.getText(), min_value_, max_value_, &num))
251 return;
253 setValue(num);
256 BuddyList::AddWindow::BooleanOption::BooleanOption(
257 const char *text, bool checked)
258 : CheckBox(text, checked)
262 BuddyList::AddBuddyWindow::AddBuddyWindow(PurpleAccount *account,
263 const char *username, const char *group, const char *alias)
264 : AddWindow(_("Add buddy"))
266 CppConsUI::TreeView::NodeReference parent = treeview_->getRootNode();
268 account_option_ = new AccountOption(account);
269 treeview_->appendNode(parent, *account_option_);
270 account_option_->grabFocus();
272 name_option_ = new StringOption(_("Buddy name"), username);
273 treeview_->appendNode(parent, *name_option_);
275 alias_option_ = new StringOption(_("Alias"), alias);
276 treeview_->appendNode(parent, *alias_option_);
278 group_option_ = new GroupOption(group);
279 treeview_->appendNode(parent, *group_option_);
282 void BuddyList::AddBuddyWindow::onAddRequest(CppConsUI::Button & /*activator*/)
284 PurpleAccount *account =
285 static_cast<PurpleAccount *>(account_option_->getSelectedDataPtr());
286 const char *name = name_option_->getValue();
287 const char *alias = alias_option_->getValue();
288 const char *group = group_option_->getSelectedTitle();
290 if (!purple_account_is_connected(account)) {
291 LOG->message(_("Selected account is not connected."));
292 return;
294 if (name == nullptr || name[0] == '\0') {
295 LOG->message(_("No buddy name specified."));
296 return;
298 if (alias != nullptr && alias[0] == '\0')
299 alias = nullptr;
301 PurpleGroup *g = purple_find_group(group);
302 if (g == nullptr) {
303 g = purple_group_new(group);
304 purple_blist_add_group(g, nullptr);
306 PurpleBuddy *b = purple_find_buddy_in_group(account, name, g);
307 if (b != nullptr)
308 LOG->message(_("Specified buddy is already in the list."));
309 else {
310 // Add the specified buddy.
311 b = purple_buddy_new(account, name, alias);
312 purple_blist_add_buddy(b, nullptr, g, nullptr);
313 purple_account_add_buddy(account, b);
316 close();
319 BuddyList::AddChatWindow::AddChatWindow(PurpleAccount *account,
320 const char * /*name*/, const char *alias, const char *group)
321 : AddWindow(_("Add chat"))
323 CppConsUI::TreeView::NodeReference parent = treeview_->getRootNode();
325 account_option_ = new AccountOption(account, true);
326 account_option_->signal_selection_changed.connect(
327 sigc::mem_fun(this, &AddChatWindow::onAccountChanged));
328 account_option_ref_ = treeview_->appendNode(parent, *account_option_);
329 account_option_->grabFocus();
331 alias_option_ = new StringOption(_("Alias"), alias);
332 treeview_->appendNode(parent, *alias_option_);
334 group_option_ = new GroupOption(group);
335 treeview_->appendNode(parent, *group_option_);
337 autojoin_option_ = new BooleanOption(_("Auto-join"), false);
338 treeview_->appendNode(parent, *autojoin_option_);
340 // Add protocol-specific fields.
341 populateChatInfo(
342 static_cast<PurpleAccount *>(account_option_->getSelectedDataPtr()));
345 void BuddyList::AddChatWindow::onAddRequest(CppConsUI::Button & /*activator*/)
347 PurpleAccount *account =
348 static_cast<PurpleAccount *>(account_option_->getSelectedDataPtr());
349 const char *alias = alias_option_->getValue();
350 const char *group = group_option_->getSelectedTitle();
351 bool autojoin = autojoin_option_->isChecked();
353 if (!purple_account_is_connected(account)) {
354 LOG->message(_("Selected account is not connected."));
355 return;
358 GHashTable *components =
359 g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
360 for (ChatInfos::value_type &chat_info : chat_infos_) {
361 CppConsUI::Button *button =
362 dynamic_cast<CppConsUI::Button *>(chat_info.second->getWidget());
363 g_assert(button != nullptr);
364 const char *value = button->getValue();
365 if (value[0] != '\0')
366 g_hash_table_replace(components, g_strdup(chat_info.first.c_str()),
367 g_strdup(button->getValue()));
370 PurpleChat *chat = purple_chat_new(account, alias, components);
372 if (chat != nullptr) {
373 PurpleGroup *g = purple_find_group(group);
374 if (g == nullptr) {
375 g = purple_group_new(group);
376 purple_blist_add_group(g, nullptr);
378 purple_blist_add_chat(chat, g, nullptr);
379 purple_blist_node_set_bool(
380 PURPLE_BLIST_NODE(chat), PACKAGE_NAME "-autojoin", autojoin);
383 close();
386 void BuddyList::AddChatWindow::populateChatInfo(PurpleAccount *account)
388 // Remove old entries.
389 for (ChatInfos::value_type &chat_info : chat_infos_)
390 treeview_->deleteNode(chat_info.second, false);
391 chat_infos_.clear();
393 PurpleConnection *gc = purple_account_get_connection(account);
394 PurplePluginProtocolInfo *info =
395 PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc));
396 if (info->chat_info == nullptr)
397 return;
399 GHashTable *defaults = nullptr;
400 if (info->chat_info_defaults != nullptr)
401 defaults = info->chat_info_defaults(gc, "");
403 CppConsUI::TreeView::NodeReference ref = account_option_ref_;
404 for (GList *l = info->chat_info(gc); l != nullptr; l = l->next) {
405 struct proto_chat_entry *entry =
406 static_cast<struct proto_chat_entry *>(l->data);
408 // Remove any accelerator from the label:
409 char *label = Utils::stripAccelerator(entry->label);
411 // And strip any trailing colon.
412 std::size_t len = strlen(label);
413 if (label[len - 1] == ':')
414 label[len - 1] = '\0';
416 // Get default value.
417 const char *value = nullptr;
418 if (defaults != nullptr)
419 value = static_cast<const char *>(
420 g_hash_table_lookup(defaults, entry->identifier));
422 CppConsUI::Button *button;
423 if (entry->is_int)
424 button =
425 new IntegerOption(label, value, entry->secret, entry->min, entry->max);
426 else
427 button = new StringOption(label, value, entry->secret);
428 g_free(label);
430 ref = treeview_->insertNodeAfter(ref, *button);
431 chat_infos_[entry->identifier] = ref;
434 if (defaults != nullptr)
435 g_hash_table_destroy(defaults);
438 void BuddyList::AddChatWindow::onAccountChanged(
439 CppConsUI::Button & /*activator*/, std::size_t /*new_entry*/,
440 const char * /*title*/, intptr_t data)
442 populateChatInfo(reinterpret_cast<PurpleAccount *>(data));
445 BuddyList::AddGroupWindow::AddGroupWindow() : AddWindow(_("Add group"))
447 name_option_ = new StringOption(_("Name"), _("New group"));
448 treeview_->appendNode(treeview_->getRootNode(), *name_option_);
449 name_option_->grabFocus();
452 void BuddyList::AddGroupWindow::onAddRequest(CppConsUI::Button & /*activator*/)
454 const char *name = name_option_->getValue();
455 if (name == nullptr || name[0] == '\0') {
456 LOG->message(_("No group name specified."));
457 return;
460 PurpleGroup *group = purple_group_new(name);
461 purple_blist_add_group(group, nullptr);
463 close();
466 BuddyList::BuddyList() : Window(0, 0, 80, 24)
468 setColorScheme(CenterIM::SCHEME_BUDDYLIST);
470 auto lbox = new CppConsUI::ListBox(AUTOSIZE, AUTOSIZE);
471 addWidget(*lbox, 1, 1);
473 auto hbox = new CppConsUI::HorizontalListBox(AUTOSIZE, AUTOSIZE);
474 lbox->appendWidget(*hbox);
475 hbox->appendWidget(*(new CppConsUI::Spacer(1, AUTOSIZE)));
476 treeview_ = new CppConsUI::TreeView(AUTOSIZE, AUTOSIZE);
477 hbox->appendWidget(*treeview_);
478 hbox->appendWidget(*(new CppConsUI::Spacer(1, AUTOSIZE)));
480 filter_ = new Filter(this);
481 filterHide();
482 lbox->appendWidget(*filter_);
484 // TODO Check if this has been moved to purple_blist_init(). Remove these
485 // lines if it was as this will probably move to purple_init(), the buddylist
486 // object should be available a lot more early and the uiops should be set a
487 // lot more early. (All in all a lot of work.)
488 buddylist_ = purple_blist_new();
489 buddylist_->ui_data = this;
490 purple_set_blist(buddylist_);
492 // Load the pounces.
493 purple_pounces_load();
495 // Init prefs.
496 purple_prefs_add_none(CONF_PREFIX "/blist");
497 purple_prefs_add_bool(CONF_PREFIX "/blist/show_empty_groups", false);
498 purple_prefs_add_bool(CONF_PREFIX "/blist/show_offline_buddies", true);
499 purple_prefs_add_string(CONF_PREFIX "/blist/list_mode", "normal");
500 purple_prefs_add_string(CONF_PREFIX "/blist/group_sort_mode", "name");
501 purple_prefs_add_string(CONF_PREFIX "/blist/buddy_sort_mode", "status");
502 purple_prefs_add_string(CONF_PREFIX "/blist/colorization_mode", "none");
504 updateCachedPreference(CONF_PREFIX "/blist/show_empty_groups");
505 updateCachedPreference(CONF_PREFIX "/blist/show_offline_buddies");
506 updateCachedPreference(CONF_PREFIX "/blist/list_mode");
507 updateCachedPreference(CONF_PREFIX "/blist/group_sort_mode");
508 updateCachedPreference(CONF_PREFIX "/blist/buddy_sort_mode");
509 updateCachedPreference(CONF_PREFIX "/blist/colorization_mode");
511 // Connect callbacks.
512 purple_prefs_connect_callback(
513 this, CONF_PREFIX "/blist", blist_pref_change_, this);
515 // Setup the callbacks for the buddylist.
516 std::memset(&centerim_blist_ui_ops_, 0, sizeof(centerim_blist_ui_ops_));
517 centerim_blist_ui_ops_.new_list = new_list_;
518 centerim_blist_ui_ops_.new_node = new_node_;
519 // centerim_blist_ui_ops_.show = show_;
520 centerim_blist_ui_ops_.update = update_;
521 centerim_blist_ui_ops_.remove = remove_;
522 centerim_blist_ui_ops_.destroy = destroy_;
523 // centerim_blist_ui_ops_.set_visible = set_visible_;
524 centerim_blist_ui_ops_.request_add_buddy = request_add_buddy_;
525 centerim_blist_ui_ops_.request_add_chat = request_add_chat_;
526 centerim_blist_ui_ops_.request_add_group = request_add_group_;
527 // Since libpurple 2.6.0.
528 // centerim_blist_ui_ops_.save_node = save_node_;
529 // centerim_blist_ui_ops_.remove_node = remove_node_;
530 // centerim_blist_ui_ops_.save_account = save_account_;
531 purple_blist_set_ui_ops(&centerim_blist_ui_ops_);
533 CENTERIM->timeoutOnceConnect(sigc::mem_fun(this, &BuddyList::load), 0);
535 onScreenResized();
536 declareBindables();
539 BuddyList::~BuddyList()
541 purple_blist_set_ui_ops(nullptr);
542 purple_prefs_disconnect_by_handle(this);
545 void BuddyList::init()
547 g_assert(my_instance_ == nullptr);
549 my_instance_ = new BuddyList;
550 my_instance_->show();
553 void BuddyList::finalize()
555 g_assert(my_instance_ != nullptr);
557 delete my_instance_;
558 my_instance_ = nullptr;
561 void BuddyList::load()
563 // Load the buddy list from ~/.centerim5/blist.xml.
564 purple_blist_load();
566 delayedGroupNodesInit();
569 void BuddyList::rebuildList()
571 treeview_->clear();
573 for (PurpleBlistNode *node = purple_blist_get_root(); node != nullptr;
574 node = purple_blist_node_next(node, TRUE))
575 new_node(node);
578 void BuddyList::updateList(int flags)
580 for (PurpleBlistNode *node = purple_blist_get_root(); node != nullptr;
581 node = purple_blist_node_next(node, TRUE))
582 if (PURPLE_BLIST_NODE_IS_GROUP(node) ? (flags & UPDATE_GROUPS)
583 : (flags & UPDATE_OTHERS)) {
584 BuddyListNode *bnode =
585 reinterpret_cast<BuddyListNode *>(purple_blist_node_get_ui_data(node));
586 if (bnode != nullptr)
587 bnode->update();
591 void BuddyList::delayedGroupNodesInit()
593 // Delayed group nodes init.
594 for (PurpleBlistNode *node = purple_blist_get_root(); node != nullptr;
595 node = purple_blist_node_get_sibling_next(node))
596 if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
597 BuddyListGroup *gnode =
598 reinterpret_cast<BuddyListGroup *>(purple_blist_node_get_ui_data(node));
599 if (gnode != nullptr)
600 gnode->initCollapsedState();
604 void BuddyList::updateCachedPreference(const char *name)
606 if (std::strcmp(name, CONF_PREFIX "/blist/show_empty_groups") == 0)
607 show_empty_groups_ = purple_prefs_get_bool(name);
608 else if (std::strcmp(name, CONF_PREFIX "/blist/show_offline_buddies") == 0)
609 show_offline_buddies_ = purple_prefs_get_bool(name);
610 else if (std::strcmp(name, CONF_PREFIX "/blist/list_mode") == 0) {
611 const char *value = purple_prefs_get_string(name);
612 if (std::strcmp(value, "flat") == 0)
613 list_mode_ = LIST_FLAT;
614 else
615 list_mode_ = LIST_NORMAL;
617 else if (std::strcmp(name, CONF_PREFIX "/blist/group_sort_mode") == 0) {
618 const char *value = purple_prefs_get_string(name);
619 if (std::strcmp(value, "user") == 0)
620 group_sort_mode_ = GROUP_SORT_BY_USER;
621 else
622 group_sort_mode_ = GROUP_SORT_BY_NAME;
624 else if (std::strcmp(name, CONF_PREFIX "/blist/buddy_sort_mode") == 0) {
625 const char *value = purple_prefs_get_string(name);
626 if (std::strcmp(value, "status") == 0)
627 buddy_sort_mode_ = BUDDY_SORT_BY_STATUS;
628 else if (std::strcmp(value, "activity") == 0)
629 buddy_sort_mode_ = BUDDY_SORT_BY_ACTIVITY;
630 else
631 buddy_sort_mode_ = BUDDY_SORT_BY_NAME;
633 else if (std::strcmp(name, CONF_PREFIX "/blist/colorization_mode") == 0) {
634 const char *value = purple_prefs_get_string(name);
635 if (std::strcmp(value, "status") == 0)
636 colorization_mode_ = COLOR_BY_STATUS;
637 else if (std::strcmp(value, "account") != 0)
638 colorization_mode_ = COLOR_BY_ACCOUNT;
639 else
640 colorization_mode_ = COLOR_NONE;
644 bool BuddyList::isAnyAccountConnected()
646 for (GList *list = purple_accounts_get_all(); list != nullptr;
647 list = list->next) {
648 PurpleAccount *account = reinterpret_cast<PurpleAccount *>(list->data);
649 if (purple_account_is_connected(account))
650 return true;
653 LOG->message(_("There are no connected accounts."));
654 return false;
657 void BuddyList::filterHide()
659 filter_->setVisibility(false);
660 filter_buffer_[0] = '\0';
661 filter_buffer_length_ = 0;
662 filter_buffer_onscreen_width_ = 0;
665 void BuddyList::actionOpenFilter()
667 if (filter_->isVisible())
668 return;
670 filter_->setVisibility(true);
672 // Stay sane.
673 g_assert(filter_buffer_[0] == '\0');
674 g_assert(filter_buffer_length_ == 0);
675 g_assert(filter_buffer_onscreen_width_ == 0);
678 void BuddyList::actionDeleteChar()
680 if (!filter_->isVisible())
681 return;
683 const char *end = filter_buffer_ + filter_buffer_length_;
684 g_assert(*end == '\0');
685 char *prev = g_utf8_find_prev_char(filter_buffer_, end);
686 if (prev != nullptr) {
687 filter_buffer_length_ -= end - prev;
688 filter_buffer_onscreen_width_ -= CppConsUI::Curses::onScreenWidth(prev);
689 *prev = '\0';
691 else
692 filterHide();
694 updateList(UPDATE_OTHERS);
695 redraw();
698 void BuddyList::declareBindables()
700 declareBindable("buddylist", "filter",
701 sigc::mem_fun(this, &BuddyList::actionOpenFilter),
702 InputProcessor::BINDABLE_NORMAL);
703 declareBindable("textentry", "backspace",
704 sigc::mem_fun(this, &BuddyList::actionDeleteChar),
705 InputProcessor::BINDABLE_NORMAL);
708 void BuddyList::new_list(PurpleBuddyList *list)
710 if (buddylist_ != list)
711 LOG->error(_("Different buddylist detected!"));
714 void BuddyList::new_node(PurpleBlistNode *node)
716 g_return_if_fail(!purple_blist_node_get_ui_data(node));
718 if (PURPLE_BLIST_NODE_IS_GROUP(node) && list_mode_ == BuddyList::LIST_FLAT) {
719 // Flat mode = no groups.
720 return;
723 BuddyListNode *bnode = BuddyListNode::createNode(node);
724 if (bnode == nullptr)
725 return;
727 BuddyListNode *parent = bnode->getParentNode();
728 CppConsUI::TreeView::NodeReference nref = treeview_->appendNode(
729 parent ? parent->getRefNode() : treeview_->getRootNode(), *bnode);
730 bnode->setRefNode(nref);
731 bnode->update();
734 void BuddyList::update(PurpleBuddyList *list, PurpleBlistNode *node)
736 // Not cool, but necessary because libpurple does not always behave nice. Note
737 // that calling new_node() can modify node's ui_data.
738 if (purple_blist_node_get_ui_data(node) == nullptr)
739 new_node(node);
741 BuddyListNode *bnode =
742 reinterpret_cast<BuddyListNode *>(purple_blist_node_get_ui_data(node));
743 if (bnode == nullptr)
744 return;
746 // Update the node data.
747 bnode->update();
749 if (node->parent != nullptr)
750 update(list, node->parent);
753 void BuddyList::remove(PurpleBuddyList *list, PurpleBlistNode *node)
755 BuddyListNode *bnode =
756 reinterpret_cast<BuddyListNode *>(purple_blist_node_get_ui_data(node));
757 if (bnode == nullptr)
758 return;
760 treeview_->deleteNode(bnode->getRefNode(), false);
762 if (node->parent != nullptr)
763 update(list, node->parent);
766 void BuddyList::destroy(PurpleBuddyList * /*list*/)
770 void BuddyList::request_add_buddy(PurpleAccount *account, const char *username,
771 const char *group, const char *alias)
773 if (!isAnyAccountConnected())
774 return;
776 auto window = new AddBuddyWindow(account, username, group, alias);
777 window->show();
780 void BuddyList::request_add_chat(PurpleAccount *account, PurpleGroup *group,
781 const char *alias, const char *name)
783 if (!isAnyAccountConnected())
784 return;
786 // Find an account with chat capabilities.
787 bool chat_account_found = false;
788 for (GList *l = purple_connections_get_all(); l != nullptr; l = l->next) {
789 PurpleConnection *gc = reinterpret_cast<PurpleConnection *>(l->data);
791 if (PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->join_chat) {
792 chat_account_found = true;
793 break;
797 if (!chat_account_found) {
798 LOG->message(_("You are not currently signed on with any "
799 "protocols that have the ability to chat."));
800 return;
803 const char *group_name = nullptr;
804 if (group != nullptr)
805 group_name = purple_group_get_name(group);
806 auto window = new AddChatWindow(account, name, alias, group_name);
807 window->show();
810 void BuddyList::request_add_group()
812 if (!isAnyAccountConnected())
813 return;
815 auto window = new AddGroupWindow;
816 window->show();
819 void BuddyList::blist_pref_change(
820 const char *name, PurplePrefType /*type*/, gconstpointer /*val*/)
822 // Some blist/* preference changed.
823 updateCachedPreference(name);
825 if (std::strcmp(name, CONF_PREFIX "/blist/list_mode") == 0) {
826 rebuildList();
827 return;
830 bool groups_only = false;
831 if (std::strcmp(name, CONF_PREFIX "/blist/show_empty_groups") == 0 ||
832 std::strcmp(name, CONF_PREFIX "/blist/group_sort_mode") == 0)
833 groups_only = true;
835 updateList(UPDATE_GROUPS | (!groups_only ? UPDATE_OTHERS : 0));
838 // vim: set tabstop=2 shiftwidth=2 textwidth=80 expandtab: