Improve error reporting from the curses wrapper
[centerim5.git] / src / BuddyList.cpp
blob49b4e9875e669b91eea8b2b9d7ce9f050a51e1ca
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 this program. 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 <cppconsui/Spacer.h>
26 #include <cstring>
27 #include <errno.h>
28 #include "gettext.h"
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 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 = 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 const char *text = activator.getText();
250 errno = 0;
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_);
255 setValue(i);
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."));
294 return;
296 if (name == nullptr || name[0] == '\0') {
297 LOG->message(_("No buddy name specified."));
298 return;
300 if (alias != nullptr && alias[0] == '\0')
301 alias = nullptr;
303 PurpleGroup *g = purple_find_group(group);
304 if (g == nullptr) {
305 g = purple_group_new(group);
306 purple_blist_add_group(g, nullptr);
308 PurpleBuddy *b = purple_find_buddy_in_group(account, name, g);
309 if (b != nullptr)
310 LOG->message(_("Specified buddy is already in the list."));
311 else {
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);
318 close();
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.
343 populateChatInfo(
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."));
357 return;
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);
376 if (g == nullptr) {
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);
385 close();
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);
393 chat_infos_.clear();
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)
399 return;
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;
425 if (entry->is_int)
426 button =
427 new IntegerOption(label, value, entry->secret, entry->min, entry->max);
428 else
429 button = new StringOption(label, value, entry->secret);
430 g_free(label);
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."));
459 return;
462 PurpleGroup *group = purple_group_new(name);
463 purple_blist_add_group(group, nullptr);
465 close();
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);
483 filterHide();
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_);
494 // Load the pounces.
495 purple_pounces_load();
497 // Init prefs.
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(&centerim_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(&centerim_blist_ui_ops_);
535 CENTERIM->timeoutOnceConnect(sigc::mem_fun(this, &BuddyList::load), 0);
537 onScreenResized();
538 declareBindables();
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);
559 delete my_instance_;
560 my_instance_ = nullptr;
563 void BuddyList::load()
565 // Load the buddy list from ~/.centerim5/blist.xml.
566 purple_blist_load();
568 delayedGroupNodesInit();
571 void BuddyList::rebuildList()
573 treeview_->clear();
575 for (PurpleBlistNode *node = purple_blist_get_root(); node != nullptr;
576 node = purple_blist_node_next(node, TRUE))
577 new_node(node);
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)
589 bnode->update();
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;
616 else
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;
623 else
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;
632 else
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;
641 else
642 colorization_mode_ = COLOR_NONE;
646 bool BuddyList::isAnyAccountConnected()
648 for (GList *list = purple_accounts_get_all(); list != nullptr;
649 list = list->next) {
650 PurpleAccount *account = reinterpret_cast<PurpleAccount *>(list->data);
651 if (purple_account_is_connected(account))
652 return true;
655 LOG->message(_("There are no connected accounts."));
656 return false;
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())
670 return;
672 filter_->setVisibility(true);
674 // Stay sane.
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())
683 return;
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);
691 *prev = '\0';
693 else
694 filterHide();
696 updateList(UPDATE_OTHERS);
697 redraw();
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.
722 return;
725 BuddyListNode *bnode = BuddyListNode::createNode(node);
726 if (bnode == nullptr)
727 return;
729 BuddyListNode *parent = bnode->getParentNode();
730 CppConsUI::TreeView::NodeReference nref = treeview_->appendNode(
731 parent ? parent->getRefNode() : treeview_->getRootNode(), *bnode);
732 bnode->setRefNode(nref);
733 bnode->update();
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)
741 new_node(node);
743 BuddyListNode *bnode =
744 reinterpret_cast<BuddyListNode *>(purple_blist_node_get_ui_data(node));
745 if (bnode == nullptr)
746 return;
748 // Update the node data.
749 bnode->update();
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)
760 return;
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())
776 return;
778 auto window = new AddBuddyWindow(account, username, group, alias);
779 window->show();
782 void BuddyList::request_add_chat(PurpleAccount *account, PurpleGroup *group,
783 const char *alias, const char *name)
785 if (!isAnyAccountConnected())
786 return;
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;
795 break;
799 if (!chat_account_found) {
800 LOG->message(_("You are not currently signed on with any "
801 "protocols that have the ability to chat."));
802 return;
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);
809 window->show();
812 void BuddyList::request_add_group()
814 if (!isAnyAccountConnected())
815 return;
817 auto window = new AddGroupWindow;
818 window->show();
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) {
828 rebuildList();
829 return;
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)
835 groups_only = true;
837 updateList(UPDATE_GROUPS | (!groups_only ? UPDATE_OTHERS : 0));
840 // vim: set tabstop=2 shiftwidth=2 textwidth=80 expandtab: