diff --git a/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp b/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp index 8b0e187f8..a0d7c8a4c 100644 --- a/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -634,7 +635,10 @@ UserListWidget::UserListWidget(TabSupervisor *_tabSupervisor, hidePopupTimer->setSingleShot(true); hidePopupTimer->setInterval(160); connect(hidePopupTimer, &QTimer::timeout, this, [this] { - if (!popupPinned && !userInfoPopup->underMouse() && !userTree->underMouse()) { + // 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(); } }); @@ -646,6 +650,11 @@ UserListWidget::UserListWidget(TabSupervisor *_tabSupervisor, userTree->viewport()->installEventFilter(this); userTree->installEventFilter(this); // keyboard handling for section dividers + // 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. + qApp->installEventFilter(this); + // Pin on item click connect(userTree, &QTreeWidget::itemClicked, this, [this](QTreeWidgetItem *item, int) { // Clicking a section divider toggles it @@ -733,6 +742,11 @@ UserListWidget::UserListWidget(TabSupervisor *_tabSupervisor, retranslateUi(); } +UserListWidget::~UserListWidget() +{ + qApp->removeEventFilter(this); +} + void UserListWidget::bind(UserListManager *mgr) { manager = mgr; @@ -918,6 +932,16 @@ void UserListWidget::connectPopupSignals() bool UserListWidget::eventFilter(QObject *obj, QEvent *event) { + // A press outside the tree, the popup and any open menu deselects the + // list and closes the popup. The filter is installed application-wide, so + // the target can be any widget in the window or another list. + if (event->type() == QEvent::MouseButtonPress) { + auto *pressTarget = qobject_cast(obj); + if (pressTarget && !isPressInsideListUi(pressTarget)) { + clearSelectionAndClosePopup(); + } + } + // Keyboard navigation of the section dividers. // The dividers are selectable so arrow keys land on them. When one is the // current item, Enter/Space toggle it (like a button) and Left/Right follow @@ -1111,6 +1135,25 @@ void UserListWidget::hidePopup(bool immediate) fade->start(QAbstractAnimation::DeleteWhenStopped); } +bool UserListWidget::isPressInsideListUi(const QWidget *widget) const +{ + const QWidget *w = widget; + while (w) { + if (w == userTree || w == userInfoPopup || qobject_cast(w)) { + return true; + } + w = w->parentWidget(); + } + return false; +} + +void UserListWidget::clearSelectionAndClosePopup() +{ + popupPinned = false; + hidePopup(true); + userTree->clearSelection(); +} + void UserListWidget::retranslateUi() { userContextMenu->retranslateUi(); diff --git a/cockatrice/src/interface/widgets/server/user/user_list_widget.h b/cockatrice/src/interface/widgets/server/user/user_list_widget.h index 3721892c5..d7aeb3762 100644 --- a/cockatrice/src/interface/widgets/server/user/user_list_widget.h +++ b/cockatrice/src/interface/widgets/server/user/user_list_widget.h @@ -173,6 +173,9 @@ private: void hidePopup(bool immediate = false); void positionPopup(UserListTWI *item); void connectPopupSignals(); + /** True when @p widget is the tree, the popup or an open menu. */ + bool isPressInsideListUi(const QWidget *widget) const; + void clearSelectionAndClosePopup(); bool isItemNearViewport(const UserListTWI *item) const; void requestAvatarsForVisibleItems(); @@ -235,6 +238,7 @@ public: AbstractClient *_client, UserListType _type, QWidget *parent = nullptr); + ~UserListWidget() override; void bind(UserListManager *mgr); void applyDisplayMode(); void beginBulkLoad();