diff --git a/cockatrice/src/interface/widgets/server/user/user_info_popup.cpp b/cockatrice/src/interface/widgets/server/user/user_info_popup.cpp index 8be76eea0..967de2f2c 100644 --- a/cockatrice/src/interface/widgets/server/user/user_info_popup.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_info_popup.cpp @@ -634,9 +634,10 @@ void UserInfoPopup::onGamesContextMenu(const QPoint &pos) "QMenu::item:selected{background:%4;}") .arg(colorStr(theme.bg), colorStr(theme.text), colorStr(theme.border), colorStr(theme.buttonHover))); - const bool canJoin = !game.started() && game.player_count() < game.max_players(); + // Never grey the join action out: GameSelector::joinGame() asks whether to + // fall back to spectating when the game is full or already running, so a + // disabled entry would just read as a dead button. QAction *join = menu.addAction(tr("Join game")); - join->setEnabled(canJoin); QAction *spec = nullptr; if (game.spectators_allowed()) { 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 d9e1e1e7e..3b8fc08b7 100644 --- a/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp @@ -430,9 +430,6 @@ UserListWidget::UserListWidget(TabSupervisor *_tabSupervisor, hidePopup(true); requestVisibleItemResources(); }); - - // Forward join requests from popup upward - connect(userInfoPopup, &UserInfoPopup::joinGameRequested, this, &UserListWidget::joinGameRequested); } else { // Dialog mode: keyboard selection drives the Invite button. connect(userTree, &QTreeWidget::currentItemChanged, this, [this](QTreeWidgetItem *current, QTreeWidgetItem *) { @@ -692,6 +689,17 @@ void UserListWidget::connectPopupSignals() [this](const QString &n) { userContextMenu->execAdjustJudge(n, true); }); connect(userInfoPopup, &UserInfoPopup::demoteFromJudgeRequested, this, [this](const QString &n) { userContextMenu->execAdjustJudge(n, false); }); + + // Joining is a tab-level action, not a per-user one: the popup's games list + // spans every room the user is in, so the target room may not be open yet. + // Hand it to the supervisor, which owns the intent that joins the room if + // needed. The popup closes here because that intent switches tabs, which + // would otherwise strand it on a list that is no longer on screen. + connect(userInfoPopup, &UserInfoPopup::joinGameRequested, this, + [this](const int gameId, const int roomId, const bool asSpectator) { + hidePopup(true); + tabSupervisor->joinGameFromUserCard(gameId, roomId, asSpectator); + }); } bool UserListWidget::eventFilter(QObject *obj, QEvent *event) 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 412271160..9802387b2 100644 --- a/cockatrice/src/interface/widgets/server/user/user_list_widget.h +++ b/cockatrice/src/interface/widgets/server/user/user_list_widget.h @@ -172,7 +172,6 @@ signals: void removeBuddy(const QString &userName); void addIgnore(const QString &userName); void removeIgnore(const QString &userName); - void joinGameRequested(int gameId, int roomId, bool asSpectator); void sectionExpanded(Section section, bool expanded); /** Dialog mode: the user activated (Enter/double-click) the given row. */ void userActivated(const QString &userName); diff --git a/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp b/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp index c9bda7703..0dc246eaa 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp @@ -1100,10 +1100,27 @@ void TabSupervisor::replayLeft(TabGame *tab) } void TabSupervisor::joinReportGame(const int gameId, const int roomId) +{ + joinGameByIntent(gameId, roomId, true); +} + +void TabSupervisor::joinGameFromUserCard(const int gameId, const int roomId, const bool asSpectator) +{ + joinGameByIntent(gameId, roomId, asSpectator); +} + +/** + * Runs the join/spectate intent for @p gameId in @p roomId. + * + * The intent owns the whole flow: if the room tab is not open yet it joins the + * room first, then retries the game, so callers do not have to care whether the + * room happens to be on screen. + */ +void TabSupervisor::joinGameByIntent(const int gameId, const int roomId, const bool asSpectator) { auto *remoteClient = qobject_cast(client); if (!remoteClient) { - actShowPopup(tr("Report joins are only available on a remote server.")); + actShowPopup(tr("Game joins are only available on a remote server.")); return; } @@ -1112,7 +1129,7 @@ void TabSupervisor::joinReportGame(const int gameId, const int roomId) ctx->roomContext.serverContext.port = QString::number(remoteClient->peerPort()); ctx->roomContext.roomId = roomId; ctx->gameId = gameId; - ctx->asSpectator = true; + ctx->asSpectator = asSpectator; auto *joinGameIntent = new IntentJoinServerGame(this, remoteClient, std::move(ctx)); joinGameIntent->setParent(this); diff --git a/cockatrice/src/interface/widgets/tabs/tab_supervisor.h b/cockatrice/src/interface/widgets/tabs/tab_supervisor.h index 066c84a77..1a4f7f749 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_supervisor.h +++ b/cockatrice/src/interface/widgets/tabs/tab_supervisor.h @@ -128,6 +128,8 @@ private: static QString sanitizeTabName(QString dirty); static QString sanitizeHtml(QString dirty); void resetTabsMenu(); + /** Shared join/spectate driver behind the report tab and the profile hover card. */ + void joinGameByIntent(int gameId, int roomId, bool asSpectator); public: explicit TabSupervisor(AbstractClient *_client, QMenu *tabsMenu, QWidget *parent = nullptr); @@ -198,6 +200,8 @@ public slots: TabEdhRec *addEdhrecTab(const CardInfoPtr &cardToQuery, bool isCommander = false); void openReplay(GameReplay *replay); void joinReportGame(int gameId, int roomId); + /** Joins or spectates a game listed on a user's profile hover card. */ + void joinGameFromUserCard(int gameId, int roomId, bool asSpectator); void openTabModeration(const QString &userName = {}); void switchToFirstAvailableNetworkTab(); void maximizeMainWindow();