Address comments.

This commit is contained in:
Lukas Brübach 2026-08-17 11:19:49 +02:00
parent 653e608dbe
commit 72843d1956
5 changed files with 47 additions and 60 deletions

View file

@ -33,7 +33,7 @@ DlgInviteToGame::DlgInviteToGame(TabSupervisor *_tabSupervisor,
// signals while the modal loop runs.
UserListManager *manager = tabSupervisor->getUserListManager();
userList = new UserListWidget(tabSupervisor, tabSupervisor->getClient(), UserListWidget::RoomList, this,
/*withUserInfoPopup=*/false);
/*hasUserInfoPopup=*/false);
userList->setUserFilter([this, manager](const QString &name, bool online) {
return !excludeUserNames.contains(name) && online && !manager->isUserIgnored(name);
});
@ -50,11 +50,6 @@ DlgInviteToGame::DlgInviteToGame(TabSupervisor *_tabSupervisor,
currentUserName = userName;
inviteButton->setEnabled(!userName.isEmpty());
});
connect(userList, &UserListWidget::userListChanged, this, &DlgInviteToGame::updateEmptyLabel);
emptyLabel = new QLabel(this);
emptyLabel->setAlignment(Qt::AlignCenter);
emptyLabel->setVisible(false);
inviteButton = new QPushButton(this);
inviteButton->setEnabled(false);
@ -72,10 +67,8 @@ DlgInviteToGame::DlgInviteToGame(TabSupervisor *_tabSupervisor,
auto *layout = new QVBoxLayout(this);
layout->addWidget(searchEdit);
layout->addWidget(userList, 1);
layout->addWidget(emptyLabel);
layout->addLayout(buttonRow);
updateEmptyLabel();
retranslateUi();
// Default to a comfortably tall dialog so the list has room to breathe,
@ -88,12 +81,6 @@ DlgInviteToGame::DlgInviteToGame(TabSupervisor *_tabSupervisor,
void DlgInviteToGame::searchTextChanged(const QString &text)
{
userList->setFilterText(text);
updateEmptyLabel();
}
void DlgInviteToGame::updateEmptyLabel()
{
emptyLabel->setVisible(userList->visibleUserRowCount() == 0);
}
void DlgInviteToGame::inviteCurrentUser(const QString &userName)
@ -122,5 +109,4 @@ void DlgInviteToGame::retranslateUi()
searchEdit->setPlaceholderText(tr("Search users..."));
inviteButton->setText(tr("Invite"));
cancelButton->setText(tr("Cancel"));
emptyLabel->setText(tr("No matching users."));
}

View file

@ -10,7 +10,6 @@
#include <QDialog>
#include <QStringList>
class QLabel;
class QLineEdit;
class QPushButton;
class TabSupervisor;
@ -38,11 +37,9 @@ private:
QString currentUserName;
QLineEdit *searchEdit;
UserListWidget *userList;
QLabel *emptyLabel;
QPushButton *inviteButton;
QPushButton *cancelButton;
void updateEmptyLabel();
void retranslateUi();
};

View file

@ -355,7 +355,7 @@ bool UserListItemDelegate::editorEvent(QEvent *event,
QMouseEvent *const mouseEvent = static_cast<QMouseEvent *>(event);
if (mouseEvent->button() == Qt::RightButton) {
// Dialog mode has no context menu: consume the press, show nothing.
if (owner->getWithUserInfoPopup()) {
if (owner->getHasUserInfoPopup()) {
owner->showContextMenu(mouseEvent->globalPosition().toPoint(), index);
}
return true;
@ -582,8 +582,8 @@ UserListWidget::UserListWidget(TabSupervisor *_tabSupervisor,
AbstractClient *_client,
UserListType _type,
QWidget *parent,
bool _withUserInfoPopup)
: QGroupBox(parent), withUserInfoPopup(_withUserInfoPopup), tabSupervisor(_tabSupervisor), client(_client),
bool _hasUserInfoPopup)
: QGroupBox(parent), hasUserInfoPopup(_hasUserInfoPopup), tabSupervisor(_tabSupervisor), client(_client),
type(_type), onlineCount(0)
{
avatarProvider = new UserAvatarProvider(client, this);
@ -610,7 +610,40 @@ UserListWidget::UserListWidget(TabSupervisor *_tabSupervisor,
userTree->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
userTree->header()->setStretchLastSection(true);
if (withUserInfoPopup) {
// Always create timers so callers never segfault on a null deref;
// showPopupForUser / hidePopup already guard against a null userInfoPopup.
showPopupTimer = new QTimer(this);
showPopupTimer->setSingleShot(true);
showPopupTimer->setInterval(280);
connect(showPopupTimer, &QTimer::timeout, this, [this] {
if (hoveredUser.isEmpty()) {
return;
}
// Resolve the row under the cursor again. In sectioned mode a user can
// own several rows (online + buddy), so the popup must anchor to the
// exact hovered row instead of a lookup by name.
const QPoint viewportPos = userTree->viewport()->mapFromGlobal(QCursor::pos());
QTreeWidgetItem *item = userTree->itemAt(viewportPos);
if (item && item->type() == QTreeWidgetItem::Type &&
QString::fromStdString(static_cast<UserListTWI *>(item)->getUserInfo().name()) == hoveredUser) {
showPopupForUser(static_cast<UserListTWI *>(item));
}
});
hidePopupTimer = new QTimer(this);
hidePopupTimer->setSingleShot(true);
hidePopupTimer->setInterval(160);
connect(hidePopupTimer, &QTimer::timeout, this, [this] {
// The hover ends when the cursor leaves the user row. Empty list
// space, a section divider and anything outside the tree all close
// the popup, while the popup itself keeps it alive.
if (!popupPinned && userInfoPopup && !userInfoPopup->underMouse() &&
(hoveredUser.isEmpty() || !userTree->underMouse())) {
hidePopup();
}
});
if (hasUserInfoPopup) {
// ── Hover popup ───────────────────────────────────────────────────────
userInfoPopup = new UserInfoPopup(tabSupervisor, tabSupervisor->getClient(), &avatarProvider->cache(),
&cardArtProvider->cache(), &cardArtParamsMap,
@ -620,36 +653,6 @@ UserListWidget::UserListWidget(TabSupervisor *_tabSupervisor,
userInfoPopup->setWindowOpacity(0.0);
userInfoPopup->installEventFilter(this);
showPopupTimer = new QTimer(this);
showPopupTimer->setSingleShot(true);
showPopupTimer->setInterval(280);
connect(showPopupTimer, &QTimer::timeout, this, [this] {
if (hoveredUser.isEmpty()) {
return;
}
// Resolve the row under the cursor again. In sectioned mode a user can
// own several rows (online + buddy), so the popup must anchor to the
// exact hovered row instead of a lookup by name.
const QPoint viewportPos = userTree->viewport()->mapFromGlobal(QCursor::pos());
QTreeWidgetItem *item = userTree->itemAt(viewportPos);
if (item && item->type() == QTreeWidgetItem::Type &&
QString::fromStdString(static_cast<UserListTWI *>(item)->getUserInfo().name()) == hoveredUser) {
showPopupForUser(static_cast<UserListTWI *>(item));
}
});
hidePopupTimer = new QTimer(this);
hidePopupTimer->setSingleShot(true);
hidePopupTimer->setInterval(160);
connect(hidePopupTimer, &QTimer::timeout, this, [this] {
// The hover ends when the cursor leaves the user row. Empty list
// space, a section divider and anything outside the tree all close
// the popup, while the popup itself keeps it alive.
if (!popupPinned && !userInfoPopup->underMouse() && (hoveredUser.isEmpty() || !userTree->underMouse())) {
hidePopup();
}
});
connectPopupSignals();
}
@ -658,7 +661,7 @@ UserListWidget::UserListWidget(TabSupervisor *_tabSupervisor,
userTree->viewport()->installEventFilter(this);
userTree->installEventFilter(this); // keyboard handling for section dividers
if (withUserInfoPopup) {
if (hasUserInfoPopup) {
// Clicking anywhere outside the list clears its selection and closes the
// popup. The filter watches all widgets because the press can land on any
// part of the window, on another list or on the popup itself.
@ -984,7 +987,7 @@ bool UserListWidget::eventFilter(QObject *obj, QEvent *event)
// Divider rows have no menu. Mouse-triggered context events are NOT handled
// here — the delegate's right-press path already pops the menu, and
// handling both would open two menus on one right-click.
if (withUserInfoPopup && (obj == userTree || obj == userTree->viewport()) && event->type() == QEvent::ContextMenu) {
if (hasUserInfoPopup && (obj == userTree || obj == userTree->viewport()) && event->type() == QEvent::ContextMenu) {
auto *contextEvent = static_cast<QContextMenuEvent *>(event);
if (contextEvent->reason() == QContextMenuEvent::Keyboard) {
QTreeWidgetItem *current = userTree->currentItem();
@ -1018,7 +1021,7 @@ bool UserListWidget::eventFilter(QObject *obj, QEvent *event)
}
}
if (withUserInfoPopup && obj == userTree->viewport()) {
if (hasUserInfoPopup && obj == userTree->viewport()) {
if (event->type() == QEvent::MouseMove) {
if (!SettingsCache::instance().appearance().getStyleUserList()) {
return QGroupBox::eventFilter(obj, event);
@ -1589,7 +1592,7 @@ void UserListWidget::userClicked(QTreeWidgetItem *item, int /*column*/)
return; // divider rows open no chat
}
const QString userName = item->data(2, Qt::UserRole).toString();
if (withUserInfoPopup) {
if (hasUserInfoPopup) {
emit openMessageDialog(userName, true);
} else {
emit userActivated(userName);

View file

@ -173,7 +173,7 @@ private:
QString hoveredUser;
bool popupPinned = false;
bool bulkLoading = false;
bool withUserInfoPopup = true;
bool hasUserInfoPopup = true;
std::function<bool(const QString &userName, bool online)> userFilter;
/**
@ -256,7 +256,7 @@ public:
AbstractClient *_client,
UserListType _type,
QWidget *parent = nullptr,
bool withUserInfoPopup = true);
bool hasUserInfoPopup = true);
~UserListWidget() override;
void bind(UserListManager *mgr);
void applyDisplayMode();
@ -278,9 +278,9 @@ public:
userFilter = std::move(filter);
}
[[nodiscard]] int visibleUserRowCount() const;
[[nodiscard]] bool getWithUserInfoPopup() const
[[nodiscard]] bool getHasUserInfoPopup() const
{
return withUserInfoPopup;
return hasUserInfoPopup;
}
[[nodiscard]] const QList<Section> &getSectionIds() const
{

View file

@ -1324,6 +1324,7 @@ void TabGame::createPlayerListDock(bool bReplay)
auto *playerListBox = new QWidget(this);
auto *vbox = new QVBoxLayout(playerListBox);
vbox->setContentsMargins(0, 0, 0, 0);
vbox->setSpacing(0);
vbox->addWidget(playerListWidget);