[UserList] Use an enum for the list sections

The section identifiers were stringly-typed: eleven hardcoded
QStringLiteral comparisons scattered through user_list_widget.cpp, and
the display path (sectionTitle) maps every id through tr() anyway, so
the raw strings were never shown. A typo compiled fine and silently
broke a section.

- enum class Section { Buddy, Online, Ignore } replaces the section
  strings across the sectioned-list API (setSectioned, getSectionIds,
  setSectionExpanded, the sectionExpanded signal and all membership
  helpers), giving compile-time checks at every call site.
- sectionTitle becomes a switch over the enum and the dead raw-string
  fallback is gone.
- The expanded-section state persists the same stable keys via the
  panel widget boundary, so existing settings files survive unchanged.
- The divider reverse lookup in handleSectionExpansion no longer relies
  on an empty-string sentinel from QMap::key; it scans the three
  dividers and bails when the item is not one of them.

Took 12 minutes

# Commit time for manual adjustment:
# Took 2 minutes
This commit is contained in:
Lukas Brübach 2026-08-15 21:47:49 +02:00
parent 829f56ccf0
commit d400af5bc0
4 changed files with 126 additions and 95 deletions

View file

@ -9,6 +9,24 @@
#include <libcockatrice/network/client/abstract/abstract_client.h> #include <libcockatrice/network/client/abstract/abstract_client.h>
#include <libcockatrice/settings/interface_settings.h> #include <libcockatrice/settings/interface_settings.h>
namespace
{
// The persisted section keys are the serialization contract with the user's
// settings file, so the values must stay stable across versions.
QString sectionKey(UserListWidget::Section section)
{
switch (section) {
case UserListWidget::Section::Buddy:
return QStringLiteral("buddy");
case UserListWidget::Section::Online:
return QStringLiteral("online");
case UserListWidget::Section::Ignore:
return QStringLiteral("ignore");
}
return {};
}
} // namespace
UserListPanelWidget::UserListPanelWidget(TabSupervisor *_tabSupervisor, AbstractClient *_client, QWidget *parent) UserListPanelWidget::UserListPanelWidget(TabSupervisor *_tabSupervisor, AbstractClient *_client, QWidget *parent)
: QWidget(parent) : QWidget(parent)
{ {
@ -21,7 +39,8 @@ UserListPanelWidget::UserListPanelWidget(TabSupervisor *_tabSupervisor, Abstract
mainLayout->addWidget(searchBar); mainLayout->addWidget(searchBar);
userList = new UserListWidget(_tabSupervisor, _client, UserListWidget::RoomList, this); userList = new UserListWidget(_tabSupervisor, _client, UserListWidget::RoomList, this);
userList->setSectioned({QStringLiteral("buddy"), QStringLiteral("online"), QStringLiteral("ignore")}); userList->setSectioned(
{UserListWidget::Section::Buddy, UserListWidget::Section::Online, UserListWidget::Section::Ignore});
mainLayout->addWidget(userList, 1); mainLayout->addWidget(userList, 1);
connect(searchBar, &QLineEdit::textChanged, userList, &UserListWidget::setFilterText); connect(searchBar, &QLineEdit::textChanged, userList, &UserListWidget::setFilterText);
@ -31,8 +50,8 @@ UserListPanelWidget::UserListPanelWidget(TabSupervisor *_tabSupervisor, Abstract
// Restore the persisted expansion state, then apply it to the tree. // Restore the persisted expansion state, then apply it to the tree.
const QStringList expandedSections = SettingsCache::instance().userInterface().getUserListExpandedSections(); const QStringList expandedSections = SettingsCache::instance().userInterface().getUserListExpandedSections();
for (const QString &sectionId : userList->getSectionIds()) { for (const UserListWidget::Section section : userList->getSectionIds()) {
userList->setSectionExpanded(sectionId, expandedSections.contains(sectionId)); userList->setSectionExpanded(section, expandedSections.contains(sectionKey(section)));
} }
retranslateUi(); retranslateUi();
@ -43,15 +62,16 @@ void UserListPanelWidget::bind(UserListManager *manager)
userList->bind(manager); userList->bind(manager);
} }
void UserListPanelWidget::persistExpandedSections(const QString &sectionId, bool expanded) void UserListPanelWidget::persistExpandedSections(UserListWidget::Section section, bool expanded)
{ {
const QString key = sectionKey(section);
QStringList expandedSections = SettingsCache::instance().userInterface().getUserListExpandedSections(); QStringList expandedSections = SettingsCache::instance().userInterface().getUserListExpandedSections();
if (expanded) { if (expanded) {
if (!expandedSections.contains(sectionId)) { if (!expandedSections.contains(key)) {
expandedSections.append(sectionId); expandedSections.append(key);
} }
} else { } else {
expandedSections.removeAll(sectionId); expandedSections.removeAll(key);
} }
SettingsCache::instance().userInterface().setUserListExpandedSections(expandedSections); SettingsCache::instance().userInterface().setUserListExpandedSections(expandedSections);
} }

View file

@ -6,13 +6,14 @@
#ifndef COCKATRICE_USER_LIST_PANEL_WIDGET_H #ifndef COCKATRICE_USER_LIST_PANEL_WIDGET_H
#define COCKATRICE_USER_LIST_PANEL_WIDGET_H #define COCKATRICE_USER_LIST_PANEL_WIDGET_H
#include "user_list_widget.h"
#include <QWidget> #include <QWidget>
class AbstractClient; class AbstractClient;
class QLineEdit; class QLineEdit;
class TabSupervisor; class TabSupervisor;
class UserListManager; class UserListManager;
class UserListWidget;
/** /**
* A unified user list: a search bar above a single tree whose section headers * A unified user list: a search bar above a single tree whose section headers
@ -33,7 +34,7 @@ signals:
void openMessageDialog(const QString &userName, bool focus); void openMessageDialog(const QString &userName, bool focus);
private: private:
void persistExpandedSections(const QString &sectionId, bool expanded); void persistExpandedSections(UserListWidget::Section section, bool expanded);
QLineEdit *searchBar = nullptr; QLineEdit *searchBar = nullptr;
UserListWidget *userList = nullptr; UserListWidget *userList = nullptr;

View file

@ -805,13 +805,13 @@ void UserListWidget::bind(UserListManager *mgr)
connect(manager, &UserListManager::userLeftOnline, this, connect(manager, &UserListManager::userLeftOnline, this,
[this](const QString &name) { handleOnlineChangeLeft(name); }); [this](const QString &name) { handleOnlineChangeLeft(name); });
connect(manager, &UserListManager::addedToBuddyList, this, connect(manager, &UserListManager::addedToBuddyList, this,
[this](const ServerInfo_User &user) { handleListAdd(QStringLiteral("buddy"), user); }); [this](const ServerInfo_User &user) { handleListAdd(Section::Buddy, user); });
connect(manager, &UserListManager::removedFromBuddyList, this, connect(manager, &UserListManager::removedFromBuddyList, this,
[this](const QString &name) { handleListRemove(QStringLiteral("buddy"), name); }); [this](const QString &name) { handleListRemove(Section::Buddy, name); });
connect(manager, &UserListManager::addedToIgnoreList, this, connect(manager, &UserListManager::addedToIgnoreList, this,
[this](const ServerInfo_User &user) { handleListAdd(QStringLiteral("ignore"), user); }); [this](const ServerInfo_User &user) { handleListAdd(Section::Ignore, user); });
connect(manager, &UserListManager::removedFromIgnoreList, this, connect(manager, &UserListManager::removedFromIgnoreList, this,
[this](const QString &name) { handleListRemove(QStringLiteral("ignore"), name); }); [this](const QString &name) { handleListRemove(Section::Ignore, name); });
} }
// ── Popup button refresh ────────────────────────────────────────────────── // ── Popup button refresh ──────────────────────────────────────────────────
@ -1201,8 +1201,8 @@ void UserListWidget::requestAvatarsForVisibleItems()
{ {
if (sectioned) { if (sectioned) {
// Top level items are dividers, user rows hang below them. // Top level items are dividers, user rows hang below them.
for (const QString &sectionId : sectionIds) { for (const Section section : sectionIds) {
QTreeWidgetItem *divider = sectionItems.value(sectionId); QTreeWidgetItem *divider = sectionItems.value(section);
if (!divider) { if (!divider) {
continue; continue;
} }
@ -1247,15 +1247,15 @@ void UserListWidget::rebuild()
beginBulkLoad(); beginBulkLoad();
const auto &onlineUsers = manager->getAllUsersList(); const auto &onlineUsers = manager->getAllUsersList();
for (auto it = onlineUsers.cbegin(); it != onlineUsers.cend(); ++it) { for (auto it = onlineUsers.cbegin(); it != onlineUsers.cend(); ++it) {
processUserInfo(QStringLiteral("online"), it.value(), true); processUserInfo(Section::Online, it.value(), true);
} }
const auto &buddyUsers = manager->getBuddyList(); const auto &buddyUsers = manager->getBuddyList();
for (auto it = buddyUsers.cbegin(); it != buddyUsers.cend(); ++it) { for (auto it = buddyUsers.cbegin(); it != buddyUsers.cend(); ++it) {
processUserInfo(QStringLiteral("buddy"), it.value(), manager->getOnlineUser(it.key()) != nullptr); processUserInfo(Section::Buddy, it.value(), manager->getOnlineUser(it.key()) != nullptr);
} }
const auto &ignoreUsers = manager->getIgnoreList(); const auto &ignoreUsers = manager->getIgnoreList();
for (auto it = ignoreUsers.cbegin(); it != ignoreUsers.cend(); ++it) { for (auto it = ignoreUsers.cbegin(); it != ignoreUsers.cend(); ++it) {
processUserInfo(QStringLiteral("ignore"), it.value(), manager->getOnlineUser(it.key()) != nullptr); processUserInfo(Section::Ignore, it.value(), manager->getOnlineUser(it.key()) != nullptr);
} }
endBulkLoad(); endBulkLoad();
applyFilter(); applyFilter();
@ -1334,9 +1334,9 @@ void UserListWidget::processUserInfo(const ServerInfo_User &user, bool online)
} }
} }
void UserListWidget::processUserInfo(const QString &sectionId, const ServerInfo_User &user, bool online) void UserListWidget::processUserInfo(Section section, const ServerInfo_User &user, bool online)
{ {
ensureSectionMembership(sectionId, user, online); ensureSectionMembership(section, user, online);
if (!bulkLoading) { if (!bulkLoading) {
sortItems(); sortItems();
applyFilter(); applyFilter();
@ -1349,9 +1349,9 @@ bool UserListWidget::deleteUser(const QString &userName)
if (sectioned) { if (sectioned) {
// The user may own several rows (one per section). Drop them all. // The user may own several rows (one per section). Drop them all.
bool removed = false; bool removed = false;
const QStringList sections = sectionUsers.keys(); // snapshot: maps mutate const QList<Section> sections = sectionUsers.keys(); // snapshot: maps mutate
for (const QString &sectionId : sections) { for (const Section section : sections) {
removed = dropSectionMembership(sectionId, userName) || removed; removed = dropSectionMembership(section, userName) || removed;
} }
if (removed && !bulkLoading) { if (removed && !bulkLoading) {
sortItems(); sortItems();
@ -1430,8 +1430,8 @@ void UserListWidget::updateCount()
if (sectioned) { if (sectioned) {
// The dividers carry the section titles // The dividers carry the section titles
setTitle(QString()); setTitle(QString());
for (const QString &sectionId : sectionIds) { for (const Section section : sectionIds) {
updateSectionDivider(sectionId); updateSectionDivider(section);
} }
return; return;
} }
@ -1467,8 +1467,8 @@ void UserListWidget::applyFilter()
if (sectioned) { if (sectioned) {
const bool searching = !filterText.isEmpty(); const bool searching = !filterText.isEmpty();
const QString lower = filterText.toLower(); const QString lower = filterText.toLower();
for (const QString &sectionId : sectionIds) { for (const Section section : sectionIds) {
QTreeWidgetItem *divider = sectionItems.value(sectionId); QTreeWidgetItem *divider = sectionItems.value(section);
if (!divider) { if (!divider) {
continue; continue;
} }
@ -1490,9 +1490,9 @@ void UserListWidget::applyFilter()
setExpandedProgrammatically(divider, visible > 0); setExpandedProgrammatically(divider, visible > 0);
} else { } else {
divider->setHidden(false); divider->setHidden(false);
setExpandedProgrammatically(divider, expandedSections.contains(sectionId)); setExpandedProgrammatically(divider, expandedSections.contains(section));
} }
updateSectionDivider(sectionId); updateSectionDivider(section);
} }
requestAvatarsForVisibleItems(); requestAvatarsForVisibleItems();
userTree->viewport()->update(); userTree->viewport()->update();
@ -1552,7 +1552,7 @@ void UserListWidget::sortItems()
// Sectioned mode // Sectioned mode
void UserListWidget::setSectioned(const QStringList &ids) void UserListWidget::setSectioned(const QList<Section> &ids)
{ {
if (sectioned || ids.isEmpty()) { if (sectioned || ids.isEmpty()) {
return; return;
@ -1561,8 +1561,8 @@ void UserListWidget::setSectioned(const QStringList &ids)
sectioned = true; sectioned = true;
sectionIds = ids; sectionIds = ids;
expandedSections.clear(); expandedSections.clear();
for (const QString &sectionId : sectionIds) { for (const Section section : sectionIds) {
expandedSections.insert(sectionId); // everything starts expanded expandedSections.insert(section); // everything starts expanded
} }
// The single tree owns scrolling and the dividers carry the section titles, // The single tree owns scrolling and the dividers carry the section titles,
@ -1587,16 +1587,16 @@ void UserListWidget::createSectionItems()
{ {
sectionItems.clear(); sectionItems.clear();
QSignalBlocker blocker(userTree); // no expansion signals while building QSignalBlocker blocker(userTree); // no expansion signals while building
for (const QString &sectionId : sectionIds) { for (const Section section : sectionIds) {
QTreeWidgetItem *divider = createSectionItem(sectionId); QTreeWidgetItem *divider = createSectionItem(section);
sectionItems.insert(sectionId, divider); sectionItems.insert(section, divider);
divider->setExpanded(expandedSections.contains(sectionId)); divider->setExpanded(expandedSections.contains(section));
} }
} }
QTreeWidgetItem *UserListWidget::createSectionItem(const QString &sectionId) QTreeWidgetItem *UserListWidget::createSectionItem(Section section)
{ {
Q_UNUSED(sectionId); Q_UNUSED(section);
auto *divider = new QTreeWidgetItem(SectionItemType); auto *divider = new QTreeWidgetItem(SectionItemType);
// Selectable so keyboard navigation (Up/Down) can land on the dividers. // Selectable so keyboard navigation (Up/Down) can land on the dividers.
// They act as collapsible section headers once they have focus. // They act as collapsible section headers once they have focus.
@ -1618,23 +1618,22 @@ QTreeWidgetItem *UserListWidget::createSectionItem(const QString &sectionId)
return divider; return divider;
} }
QString UserListWidget::sectionTitle(const QString &sectionId) const QString UserListWidget::sectionTitle(Section section) const
{ {
if (sectionId == QLatin1String("buddy")) { switch (section) {
return tr("Buddies"); case Section::Buddy:
return tr("Buddies");
case Section::Online:
return tr("Online");
case Section::Ignore:
return tr("Ignored");
} }
if (sectionId == QLatin1String("online")) { return {};
return tr("Online");
}
if (sectionId == QLatin1String("ignore")) {
return tr("Ignored");
}
return sectionId;
} }
void UserListWidget::updateSectionDivider(const QString &sectionId) void UserListWidget::updateSectionDivider(Section section)
{ {
QTreeWidgetItem *divider = sectionItems.value(sectionId); QTreeWidgetItem *divider = sectionItems.value(section);
if (!divider) { if (!divider) {
return; return;
} }
@ -1647,7 +1646,7 @@ void UserListWidget::updateSectionDivider(const QString &sectionId)
// The tree draws no branches (rows are flush), so the divider carries its // The tree draws no branches (rows are flush), so the divider carries its
// own collapse arrow glyph. // own collapse arrow glyph.
const QString arrow = divider->isExpanded() ? QStringLiteral("\u25BE") : QStringLiteral("\u25B8"); const QString arrow = divider->isExpanded() ? QStringLiteral("\u25BE") : QStringLiteral("\u25B8");
divider->setText(0, tr("%1 %2 (%3)").arg(arrow, sectionTitle(sectionId)).arg(visible)); divider->setText(0, tr("%1 %2 (%3)").arg(arrow, sectionTitle(section)).arg(visible));
} }
void UserListWidget::handleSectionExpansion(QTreeWidgetItem *item, bool expanded) void UserListWidget::handleSectionExpansion(QTreeWidgetItem *item, bool expanded)
@ -1655,17 +1654,23 @@ void UserListWidget::handleSectionExpansion(QTreeWidgetItem *item, bool expanded
if (!sectioned || item->type() != SectionItemType) { if (!sectioned || item->type() != SectionItemType) {
return; return;
} }
const QString sectionId = sectionItems.key(item); // Reverse lookup. Only three dividers exist, so a linear scan over the
if (sectionId.isEmpty()) { // section map is cheaper than caching the section on each divider.
auto dividerIt = sectionItems.constBegin();
while (dividerIt != sectionItems.constEnd() && dividerIt.value() != item) {
++dividerIt;
}
if (dividerIt == sectionItems.constEnd()) {
return; return;
} }
const Section section = dividerIt.key();
if (expanded) { if (expanded) {
expandedSections.insert(sectionId); expandedSections.insert(section);
} else { } else {
expandedSections.remove(sectionId); expandedSections.remove(section);
} }
updateSectionDivider(sectionId); // the arrow glyph follows the state updateSectionDivider(section); // the arrow glyph follows the state
emit sectionExpanded(sectionId, expanded); emit sectionExpanded(section, expanded);
} }
void UserListWidget::setExpandedProgrammatically(QTreeWidgetItem *item, bool expanded) void UserListWidget::setExpandedProgrammatically(QTreeWidgetItem *item, bool expanded)
@ -1674,23 +1679,23 @@ void UserListWidget::setExpandedProgrammatically(QTreeWidgetItem *item, bool exp
item->setExpanded(expanded); item->setExpanded(expanded);
} }
void UserListWidget::setSectionExpanded(const QString &sectionId, bool expanded) void UserListWidget::setSectionExpanded(Section section, bool expanded)
{ {
if (!sectioned) { if (!sectioned) {
return; return;
} }
if (expanded) { if (expanded) {
expandedSections.insert(sectionId); expandedSections.insert(section);
} else { } else {
expandedSections.remove(sectionId); expandedSections.remove(section);
} }
QTreeWidgetItem *divider = sectionItems.value(sectionId); QTreeWidgetItem *divider = sectionItems.value(section);
if (!divider) { if (!divider) {
return; return;
} }
QSignalBlocker blocker(userTree); QSignalBlocker blocker(userTree);
divider->setExpanded(expanded); divider->setExpanded(expanded);
updateSectionDivider(sectionId); // the arrow glyph follows the state updateSectionDivider(section); // the arrow glyph follows the state
userTree->viewport()->update(); userTree->viewport()->update();
} }
@ -1699,12 +1704,12 @@ void UserListWidget::handleOnlineChange(const ServerInfo_User &user)
// A user came online: they get a row in the "Online" section, plus (if // A user came online: they get a row in the "Online" section, plus (if
// applicable) a row in the buddy/ignore sections, which flip to online. // applicable) a row in the buddy/ignore sections, which flip to online.
const QString name = QString::fromStdString(user.name()); const QString name = QString::fromStdString(user.name());
ensureSectionMembership(QStringLiteral("online"), user, true); ensureSectionMembership(Section::Online, user, true);
if (manager->isUserBuddy(name)) { if (manager->isUserBuddy(name)) {
ensureSectionMembership(QStringLiteral("buddy"), user, true); ensureSectionMembership(Section::Buddy, user, true);
} }
if (manager->isUserIgnored(name)) { if (manager->isUserIgnored(name)) {
ensureSectionMembership(QStringLiteral("ignore"), user, true); ensureSectionMembership(Section::Ignore, user, true);
} }
finishSectionedMutation(); finishSectionedMutation();
} }
@ -1714,7 +1719,7 @@ void UserListWidget::handleOnlineChangeLeft(const QString &userName)
// The user is no longer online: their "Online" row disappears. Buddies and // The user is no longer online: their "Online" row disappears. Buddies and
// ignored users keep their own section's row, marked offline. A plain user // ignored users keep their own section's row, marked offline. A plain user
// has no rows left. // has no rows left.
const bool dropped = dropSectionMembership(QStringLiteral("online"), userName); const bool dropped = dropSectionMembership(Section::Online, userName);
const bool kept = manager->isUserBuddy(userName) || manager->isUserIgnored(userName); const bool kept = manager->isUserBuddy(userName) || manager->isUserIgnored(userName);
if (kept) { if (kept) {
setUserOnline(userName, false); setUserOnline(userName, false);
@ -1724,40 +1729,40 @@ void UserListWidget::handleOnlineChangeLeft(const QString &userName)
} }
} }
void UserListWidget::handleListAdd(const QString &sectionId, const ServerInfo_User &user) void UserListWidget::handleListAdd(Section section, const ServerInfo_User &user)
{ {
const QString name = QString::fromStdString(user.name()); const QString name = QString::fromStdString(user.name());
const bool online = manager->getOnlineUser(name) != nullptr; const bool online = manager->getOnlineUser(name) != nullptr;
ensureSectionMembership(sectionId, user, online); ensureSectionMembership(section, user, online);
if (online) { if (online) {
// The user belongs to the "Online" section as well. Make sure the row // The user belongs to the "Online" section as well. Make sure the row
// exists even if the join event raced ahead of the list mutation. // exists even if the join event raced ahead of the list mutation.
ensureSectionMembership(QStringLiteral("online"), user, true); ensureSectionMembership(Section::Online, user, true);
} }
finishSectionedMutation(); finishSectionedMutation();
} }
void UserListWidget::handleListRemove(const QString &sectionId, const QString &userName) void UserListWidget::handleListRemove(Section section, const QString &userName)
{ {
// Only the row of the removed section disappears: an online user keeps // Only the row of the removed section disappears: an online user keeps
// their "Online" row, and other list memberships keep theirs. // their "Online" row, and other list memberships keep theirs.
if (dropSectionMembership(sectionId, userName)) { if (dropSectionMembership(section, userName)) {
finishSectionedMutation(); finishSectionedMutation();
} }
} }
UserListTWI *UserListWidget::ensureSectionMembership(const QString &sectionId, const ServerInfo_User &user, bool online) UserListTWI *UserListWidget::ensureSectionMembership(Section section, const ServerInfo_User &user, bool online)
{ {
const QString userName = QString::fromStdString(user.name()); const QString userName = QString::fromStdString(user.name());
updateCardArtParams(user, userName); updateCardArtParams(user, userName);
QTreeWidgetItem *divider = sectionItems.value(sectionId); QTreeWidgetItem *divider = sectionItems.value(section);
if (!divider) { if (!divider) {
return nullptr; return nullptr;
} }
QMap<QString, UserListTWI *> &sectionMap = sectionUsers[sectionId]; QMap<QString, UserListTWI *> &sectionMap = sectionUsers[section];
UserListTWI *item = sectionMap.value(userName); UserListTWI *item = sectionMap.value(userName);
if (!item) { if (!item) {
item = new UserListTWI(user); item = new UserListTWI(user);
@ -1781,9 +1786,9 @@ UserListTWI *UserListWidget::ensureSectionMembership(const QString &sectionId, c
return item; return item;
} }
bool UserListWidget::dropSectionMembership(const QString &sectionId, const QString &userName) bool UserListWidget::dropSectionMembership(Section section, const QString &userName)
{ {
QMap<QString, UserListTWI *> &sectionMap = sectionUsers[sectionId]; QMap<QString, UserListTWI *> &sectionMap = sectionUsers[section];
UserListTWI *item = sectionMap.take(userName); UserListTWI *item = sectionMap.take(userName);
if (!item) { if (!item) {
return false; return false;

View file

@ -19,7 +19,6 @@
#include <QGroupBox> #include <QGroupBox>
#include <QQueue> #include <QQueue>
#include <QSet> #include <QSet>
#include <QStringList>
#include <QStyledItemDelegate> #include <QStyledItemDelegate>
#include <QTextEdit> #include <QTextEdit>
#include <QTreeWidgetItem> #include <QTreeWidgetItem>
@ -150,6 +149,12 @@ public:
BuddyList, BuddyList,
IgnoreList IgnoreList
}; };
enum class Section
{
Buddy,
Online,
Ignore
};
private: private:
UserListManager *manager = nullptr; UserListManager *manager = nullptr;
@ -181,31 +186,31 @@ private:
// Sectioned mode (single tree with inline dividers) // Sectioned mode (single tree with inline dividers)
bool sectioned = false; bool sectioned = false;
QStringList sectionIds; QList<Section> sectionIds;
QMap<QString, QTreeWidgetItem *> sectionItems; QMap<Section, QTreeWidgetItem *> sectionItems;
// One row per (section, user): a user that is online AND a buddy appears in // One row per (section, user): a user that is online AND a buddy appears in
// both the "Online" and the "Buddies" sections, so the same user can own // both the "Online" and the "Buddies" sections, so the same user can own
// several rows, each hanging off its section's divider. // several rows, each hanging off its section's divider.
QMap<QString, QMap<QString, UserListTWI *>> sectionUsers; QMap<Section, QMap<QString, UserListTWI *>> sectionUsers;
QSet<QString> expandedSections; QSet<Section> expandedSections;
void createSectionItems(); void createSectionItems();
QTreeWidgetItem *createSectionItem(const QString &sectionId); QTreeWidgetItem *createSectionItem(Section section);
[[nodiscard]] QString sectionTitle(const QString &sectionId) const; [[nodiscard]] QString sectionTitle(Section section) const;
void updateSectionDivider(const QString &sectionId); void updateSectionDivider(Section section);
void handleSectionExpansion(QTreeWidgetItem *item, bool expanded); void handleSectionExpansion(QTreeWidgetItem *item, bool expanded);
void setExpandedProgrammatically(QTreeWidgetItem *item, bool expanded); void setExpandedProgrammatically(QTreeWidgetItem *item, bool expanded);
void handleOnlineChange(const ServerInfo_User &user); void handleOnlineChange(const ServerInfo_User &user);
void handleOnlineChangeLeft(const QString &userName); void handleOnlineChangeLeft(const QString &userName);
void handleListAdd(const QString &sectionId, const ServerInfo_User &user); void handleListAdd(Section section, const ServerInfo_User &user);
void handleListRemove(const QString &sectionId, const QString &userName); void handleListRemove(Section section, const QString &userName);
/** Creates or updates the row for @p user in @p sectionId. */ /** Creates or updates the row for @p user in @p section. */
UserListTWI *ensureSectionMembership(const QString &sectionId, const ServerInfo_User &user, bool online); UserListTWI *ensureSectionMembership(Section section, const ServerInfo_User &user, bool online);
/** Removes and deletes the row for @p userName in @p sectionId. */ /** Removes and deletes the row for @p userName in @p section. */
bool dropSectionMembership(const QString &sectionId, const QString &userName); bool dropSectionMembership(Section section, const QString &userName);
/** Sorts, refilters and repaints after a sectioned mode mutation. */ /** Sorts, refilters and repaints after a sectioned mode mutation. */
void finishSectionedMutation(); void finishSectionedMutation();
void updateCardArtParams(const ServerInfo_User &user, const QString &userName); void updateCardArtParams(const ServerInfo_User &user, const QString &userName);
void processUserInfo(const QString &sectionId, const ServerInfo_User &user, bool online); void processUserInfo(Section section, const ServerInfo_User &user, bool online);
QMap<QString, UserListTWI *> users; QMap<QString, UserListTWI *> users;
TabSupervisor *tabSupervisor; TabSupervisor *tabSupervisor;
@ -231,7 +236,7 @@ signals:
void addIgnore(const QString &userName); void addIgnore(const QString &userName);
void removeIgnore(const QString &userName); void removeIgnore(const QString &userName);
void joinGameRequested(int gameId, int roomId, bool asSpectator); void joinGameRequested(int gameId, int roomId, bool asSpectator);
void sectionExpanded(const QString &sectionId, bool expanded); void sectionExpanded(Section section, bool expanded);
public: public:
UserListWidget(TabSupervisor *_tabSupervisor, UserListWidget(TabSupervisor *_tabSupervisor,
@ -251,9 +256,9 @@ public:
void setUserOnline(const QString &userName, bool online); void setUserOnline(const QString &userName, bool online);
void setFilterText(const QString &text); void setFilterText(const QString &text);
void setShowTitle(bool showTitle); void setShowTitle(bool showTitle);
void setSectioned(const QStringList &ids); void setSectioned(const QList<Section> &ids);
void setSectionExpanded(const QString &sectionId, bool expanded); void setSectionExpanded(Section section, bool expanded);
[[nodiscard]] const QStringList &getSectionIds() const [[nodiscard]] const QList<Section> &getSectionIds() const
{ {
return sectionIds; return sectionIds;
} }