diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 995b55258..d77d70d36 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -180,6 +180,7 @@ set(cockatrice_SOURCES src/interface/widgets/replay/replay_timeline_widget.cpp src/interface/widgets/server/chat_view/chat_view.cpp src/interface/widgets/server/game_selector.cpp + src/interface/widgets/server/game_selector_quick_filter_toolbar.cpp src/interface/widgets/server/games_model.cpp src/interface/widgets/server/handle_public_servers.cpp src/interface/widgets/server/remote/remote_decklist_tree_widget.cpp diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_create_game.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_create_game.cpp index 2b1b63065..629b23787 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_create_game.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_create_game.cpp @@ -102,15 +102,20 @@ void DlgCreateGame::sharedCtor() startingLifeTotalEdit->setValue(20); startingLifeTotalLabel->setBuddy(startingLifeTotalEdit); - shareDecklistsOnLoadLabel = new QLabel(tr("Open decklists in lobby")); - shareDecklistsOnLoadCheckBox = new QCheckBox(); - shareDecklistsOnLoadLabel->setBuddy(shareDecklistsOnLoadCheckBox); + shareDecklistsOnLoadCheckBox = new QCheckBox(tr("Open decklists in lobby")); + + createGameAsJudgeCheckBox = new QCheckBox(tr("Create game as judge")); auto *gameSetupOptionsLayout = new QGridLayout; gameSetupOptionsLayout->addWidget(startingLifeTotalLabel, 0, 0); gameSetupOptionsLayout->addWidget(startingLifeTotalEdit, 0, 1); - gameSetupOptionsLayout->addWidget(shareDecklistsOnLoadLabel, 1, 0); - gameSetupOptionsLayout->addWidget(shareDecklistsOnLoadCheckBox, 1, 1); + gameSetupOptionsLayout->addWidget(shareDecklistsOnLoadCheckBox, 1, 0); + if (room->getUserInfo()->user_level() & ServerInfo_User::IsJudge) { + gameSetupOptionsLayout->addWidget(createGameAsJudgeCheckBox, 2, 0); + } else { + createGameAsJudgeCheckBox->setChecked(false); + createGameAsJudgeCheckBox->setHidden(true); + } gameSetupOptionsGroupBox = new QGroupBox(tr("Game setup options")); gameSetupOptionsGroupBox->setLayout(gameSetupOptionsLayout); @@ -245,6 +250,7 @@ void DlgCreateGame::actReset() startingLifeTotalEdit->setValue(20); shareDecklistsOnLoadCheckBox->setChecked(false); + createGameAsJudgeCheckBox->setChecked(false); QMapIterator gameTypeCheckBoxIterator(gameTypeCheckBoxes); while (gameTypeCheckBoxIterator.hasNext()) { @@ -270,7 +276,7 @@ void DlgCreateGame::actOK() cmd.set_spectators_need_password(spectatorsNeedPasswordCheckBox->isChecked()); cmd.set_spectators_can_talk(spectatorsCanTalkCheckBox->isChecked()); cmd.set_spectators_see_everything(spectatorsSeeEverythingCheckBox->isChecked()); - cmd.set_join_as_judge(QApplication::keyboardModifiers() & Qt::ShiftModifier); + cmd.set_join_as_judge(createGameAsJudgeCheckBox->isChecked()); cmd.set_join_as_spectator(createGameAsSpectatorCheckBox->isChecked()); cmd.set_starting_life_total(startingLifeTotalEdit->value()); cmd.set_share_decklists_on_load(shareDecklistsOnLoadCheckBox->isChecked()); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_create_game.h b/cockatrice/src/interface/widgets/dialogs/dlg_create_game.h index 71359a758..e28363311 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_create_game.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_create_game.h @@ -46,7 +46,7 @@ private: QSpinBox *maxPlayersEdit, *startingLifeTotalEdit; QCheckBox *onlyBuddiesCheckBox, *onlyRegisteredCheckBox; QCheckBox *spectatorsAllowedCheckBox, *spectatorsNeedPasswordCheckBox, *spectatorsCanTalkCheckBox, - *spectatorsSeeEverythingCheckBox, *createGameAsSpectatorCheckBox; + *spectatorsSeeEverythingCheckBox, *createGameAsJudgeCheckBox, *createGameAsSpectatorCheckBox; QCheckBox *shareDecklistsOnLoadCheckBox; QDialogButtonBox *buttonBox; QPushButton *clearButton; diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_filter_games.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_filter_games.cpp index 0c5191f99..1bf98822c 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_filter_games.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_filter_games.cpp @@ -37,7 +37,7 @@ DlgFilterGames::DlgFilterGames(const QMap &_allGameTypes, hideIgnoredUserGames = new QCheckBox(tr("Hide 'ignored user' games")); hideIgnoredUserGames->setChecked(gamesProxyModel->getHideIgnoredUserGames()); - hideNotBuddyCreatedGames = new QCheckBox(tr("Hide games not created by buddy")); + hideNotBuddyCreatedGames = new QCheckBox(tr("Hide games not created by buddies")); hideNotBuddyCreatedGames->setChecked(gamesProxyModel->getHideNotBuddyCreatedGames()); hideOpenDecklistGames = new QCheckBox(tr("Hide games with forced open decklists")); @@ -56,7 +56,7 @@ DlgFilterGames::DlgFilterGames(const QMap &_allGameTypes, auto *gameNameFilterLabel = new QLabel(tr("Game &description:")); gameNameFilterLabel->setBuddy(gameNameFilterEdit); creatorNameFilterEdit = new QLineEdit; - creatorNameFilterEdit->setText(gamesProxyModel->getCreatorNameFilter()); + creatorNameFilterEdit->setText(gamesProxyModel->getCreatorNameFilters().join(", ")); auto *creatorNameFilterLabel = new QLabel(tr("&Creator name:")); creatorNameFilterLabel->setBuddy(creatorNameFilterEdit); @@ -232,9 +232,9 @@ QString DlgFilterGames::getGameNameFilter() const return gameNameFilterEdit->text(); } -QString DlgFilterGames::getCreatorNameFilter() const +QStringList DlgFilterGames::getCreatorNameFilters() const { - return creatorNameFilterEdit->text(); + return creatorNameFilterEdit->text().split(",", Qt::SkipEmptyParts); } QSet DlgFilterGames::getGameTypeFilter() const diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_filter_games.h b/cockatrice/src/interface/widgets/dialogs/dlg_filter_games.h index ab07a02d4..37b208749 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_filter_games.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_filter_games.h @@ -71,7 +71,7 @@ public: bool getHideNotBuddyCreatedGames() const; QString getGameNameFilter() const; void setGameNameFilter(const QString &_gameNameFilter); - QString getCreatorNameFilter() const; + QStringList getCreatorNameFilters() const; void setCreatorNameFilter(const QString &_creatorNameFilter); QSet getGameTypeFilter() const; void setGameTypeFilter(const QSet &_gameTypeFilter); diff --git a/cockatrice/src/interface/widgets/server/game_selector.cpp b/cockatrice/src/interface/widgets/server/game_selector.cpp index 8aa3211bd..f4366d8ab 100644 --- a/cockatrice/src/interface/widgets/server/game_selector.cpp +++ b/cockatrice/src/interface/widgets/server/game_selector.cpp @@ -76,6 +76,12 @@ GameSelector::GameSelector(AbstractClient *_client, gameListView->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); + if (showFilters && restoresettings) { + quickFilterToolBar = new GameSelectorQuickFilterToolBar(this, tabSupervisor, gameListProxyModel, gameTypeMap); + } else { + quickFilterToolBar = nullptr; + } + filterButton = new QPushButton; filterButton->setIcon(QPixmap("theme:icons/search")); connect(filterButton, &QPushButton::clicked, this, &GameSelector::actSetFilter); @@ -92,7 +98,9 @@ GameSelector::GameSelector(AbstractClient *_client, createButton = nullptr; } joinButton = new QPushButton; + joinAsJudgeButton = new QPushButton; spectateButton = new QPushButton; + joinAsJudgeSpectatorButton = new QPushButton; QHBoxLayout *buttonLayout = new QHBoxLayout; if (showFilters) { @@ -103,10 +111,23 @@ GameSelector::GameSelector(AbstractClient *_client, if (room) buttonLayout->addWidget(createButton); buttonLayout->addWidget(joinButton); + if (tabSupervisor->getUserInfo()->user_level() & ServerInfo_User::IsJudge) { + buttonLayout->addWidget(joinAsJudgeButton); + } else { + joinAsJudgeButton->setHidden(true); + } buttonLayout->addWidget(spectateButton); + if (tabSupervisor->getUserInfo()->user_level() & ServerInfo_User::IsJudge) { + buttonLayout->addWidget(joinAsJudgeSpectatorButton); + } else { + joinAsJudgeSpectatorButton->setHidden(true); + } buttonLayout->setAlignment(Qt::AlignTop); QVBoxLayout *mainLayout = new QVBoxLayout; + if (showFilters && restoresettings) { + mainLayout->addWidget(quickFilterToolBar); + } mainLayout->addWidget(gameListView); mainLayout->addLayout(buttonLayout); @@ -117,7 +138,9 @@ GameSelector::GameSelector(AbstractClient *_client, setMinimumHeight(200); connect(joinButton, &QPushButton::clicked, this, &GameSelector::actJoin); - connect(spectateButton, &QPushButton::clicked, this, &GameSelector::actSpectate); + connect(joinAsJudgeButton, &QPushButton::clicked, this, &GameSelector::actJoinAsJudge); + connect(spectateButton, &QPushButton::clicked, this, &GameSelector::actJoinAsSpectator); + connect(joinAsJudgeSpectatorButton, &QPushButton::clicked, this, &GameSelector::actJoinAsJudgeSpectator); connect(gameListView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &GameSelector::actSelectedGameChanged); connect(gameListView, &QTreeView::activated, this, &GameSelector::actJoin); @@ -158,7 +181,7 @@ void GameSelector::actSetFilter() gameListProxyModel->setGameFilters( dlg.getHideBuddiesOnlyGames(), dlg.getHideIgnoredUserGames(), dlg.getHideFullGames(), dlg.getHideGamesThatStarted(), dlg.getHidePasswordProtectedGames(), dlg.getHideNotBuddyCreatedGames(), - dlg.getHideOpenDecklistGames(), dlg.getGameNameFilter(), dlg.getCreatorNameFilter(), dlg.getGameTypeFilter(), + dlg.getHideOpenDecklistGames(), dlg.getGameNameFilter(), dlg.getCreatorNameFilters(), dlg.getGameTypeFilter(), dlg.getMaxPlayersFilterMin(), dlg.getMaxPlayersFilterMax(), dlg.getMaxGameAge(), dlg.getShowOnlyIfSpectatorsCanWatch(), dlg.getShowSpectatorPasswordProtected(), dlg.getShowOnlyIfSpectatorsCanChat(), dlg.getShowOnlyIfSpectatorsCanSeeHands()); @@ -238,12 +261,30 @@ void GameSelector::checkResponse(const Response &response) void GameSelector::actJoin() { - return joinGame(false); + joinGame(); } -void GameSelector::actSpectate() +void GameSelector::actJoinAsJudge() { - return joinGame(true); + if (!(tabSupervisor->getUserInfo()->user_level() & ServerInfo_User::IsJudge)) { + joinGame(); + } else { + joinGame(false, true); + } +} + +void GameSelector::actJoinAsSpectator() +{ + joinGame(true); +} + +void GameSelector::actJoinAsJudgeSpectator() +{ + if (!(tabSupervisor->getUserInfo()->user_level() & ServerInfo_User::IsJudge)) { + joinGame(true); + } else { + joinGame(true, true); + } } void GameSelector::customContextMenu(const QPoint &point) @@ -257,7 +298,7 @@ void GameSelector::customContextMenu(const QPoint &point) connect(&joinGame, &QAction::triggered, this, &GameSelector::actJoin); QAction spectateGame(tr("Spectate Game")); - connect(&spectateGame, &QAction::triggered, this, &GameSelector::actSpectate); + connect(&spectateGame, &QAction::triggered, this, &GameSelector::actJoinAsSpectator); QAction getGameInfo(tr("Game Information")); connect(&getGameInfo, &QAction::triggered, this, [=, this]() { @@ -270,12 +311,25 @@ void GameSelector::customContextMenu(const QPoint &point) QMenu menu; menu.addAction(&joinGame); + + if (tabSupervisor->getUserInfo()->user_level() & ServerInfo_User::IsJudge) { + QAction joinGameAsJudge(tr("Join Game as Judge")); + connect(&joinGameAsJudge, &QAction::triggered, this, &GameSelector::actJoinAsJudge); + + menu.addAction(&joinGameAsJudge); + + QAction spectateGameAsJudge(tr("Spectate Game as Judge")); + connect(&spectateGameAsJudge, &QAction::triggered, this, &GameSelector::actJoinAsJudgeSpectator); + + menu.addAction(&spectateGameAsJudge); + } + menu.addAction(&spectateGame); menu.addAction(&getGameInfo); menu.exec(gameListView->mapToGlobal(point)); } -void GameSelector::joinGame(const bool isSpectator) +void GameSelector::joinGame(const bool asSpectator, const bool asJudge) { QModelIndex ind = gameListView->currentIndex(); if (!ind.isValid()) { @@ -287,7 +341,7 @@ void GameSelector::joinGame(const bool isSpectator) return; } - bool spectator = isSpectator || game.player_count() == game.max_players(); + bool spectator = asSpectator || game.player_count() == game.max_players(); bool overrideRestrictions = !tabSupervisor->getAdminLocked(); QString password; @@ -304,7 +358,7 @@ void GameSelector::joinGame(const bool isSpectator) cmd.set_password(password.toStdString()); cmd.set_spectator(spectator); cmd.set_override_restrictions(overrideRestrictions); - cmd.set_join_as_judge((QApplication::keyboardModifiers() & Qt::ShiftModifier) != 0); + cmd.set_join_as_judge(asJudge); TabRoom *r = tabSupervisor->getRoomTabs().value(game.room_id()); if (!r) { @@ -356,7 +410,9 @@ void GameSelector::retranslateUi() if (createButton) createButton->setText(tr("C&reate")); joinButton->setText(tr("&Join")); + joinAsJudgeButton->setText(tr("Join as judge")); spectateButton->setText(tr("J&oin as spectator")); + joinAsJudgeSpectatorButton->setText(tr("Join as judge spectator")); updateTitle(); } diff --git a/cockatrice/src/interface/widgets/server/game_selector.h b/cockatrice/src/interface/widgets/server/game_selector.h index 0ca477fa9..ea0a4feb0 100644 --- a/cockatrice/src/interface/widgets/server/game_selector.h +++ b/cockatrice/src/interface/widgets/server/game_selector.h @@ -1,12 +1,7 @@ -/** - * @file game_selector.h - * @ingroup Lobby - * @brief TODO: Document this. - */ - #ifndef GAMESELECTOR_H #define GAMESELECTOR_H +#include "game_selector_quick_filter_toolbar.h" #include "game_type_map.h" #include @@ -26,46 +21,167 @@ class TabRoom; class ServerInfo_Game; class Response; +/** + * @class GameSelector + * @ingroup Lobby + * @brief Provides a widget for displaying, filtering, joining, spectating, and creating games in a room. + * + * The GameSelector displays all available games in a QTreeView. It supports filtering, + * creating, joining, spectating, and viewing game details. Integrates with TabSupervisor + * and TabRoom for room and game management. + */ class GameSelector : public QGroupBox { Q_OBJECT private slots: + /** + * @brief Opens a dialog to set filters for the game list. + * + * Updates the proxy model with selected filter parameters and refreshes the displayed game list. + */ void actSetFilter(); + + /** + * @brief Clears all filters applied to the game list. + * + * Resets the proxy model to show all games. + */ void actClearFilter(); + + /** + * @brief Opens the dialog to create a new game in the current room. + */ void actCreate(); + /** + * @brief Joins the currently selected game as a player. + */ void actJoin(); - void actSpectate(); + /** + * @brief Joins the currently selected game as a judge. + */ + void actJoinAsJudge(); + + /** + * @brief Joins the currently selected game as a spectator. + */ + void actJoinAsSpectator(); + void actJoinAsJudgeSpectator(); + + /** + * @brief Shows the custom context menu for a game when right-clicked. + * @param point The point at which the context menu is requested. + */ void customContextMenu(const QPoint &point); + /** + * @brief Slot called when the selected game changes. + * @param current The currently selected index. + * @param previous The previously selected index. + * + * Updates the enabled/disabled state of buttons depending on the selected game. + */ void actSelectedGameChanged(const QModelIndex ¤t, const QModelIndex &previous); + + /** + * @brief Processes server responses for join or spectate commands. + * @param response The response from the server. + * + * Displays error messages for failed join/spectate attempts. + */ void checkResponse(const Response &response); + /** + * @brief Refreshes the game list when the ignore list is received from the server. + * @param _ignoreList The list of users being ignored. + */ void ignoreListReceived(const QList &_ignoreList); + + /** + * @brief Processes events where a user is added to a list (e.g., ignore or buddy). + * @param event The event information. + */ void processAddToListEvent(const Event_AddToList &event); + + /** + * @brief Processes events where a user is removed from a list (e.g., ignore or buddy). + * @param event The event information. + */ void processRemoveFromListEvent(const Event_RemoveFromList &event); + signals: + /** + * @brief Emitted when a game has been successfully joined. + * @param gameId The ID of the joined game. + */ void gameJoined(int gameId); private: - AbstractClient *client; - TabSupervisor *tabSupervisor; - TabRoom *room; + AbstractClient *client; /**< The network client used to communicate with the server. */ + TabSupervisor *tabSupervisor; /**< Reference to TabSupervisor for managing tabs and rooms. */ + TabRoom *room; /**< The current room. */ - QTreeView *gameListView; - GamesModel *gameListModel; - GamesProxyModel *gameListProxyModel; - QPushButton *filterButton, *clearFilterButton, *createButton, *joinButton, *spectateButton; - const bool showFilters; - GameTypeMap gameTypeMap; + QTreeView *gameListView; /**< View widget for displaying the game list. */ + GamesModel *gameListModel; /**< Model containing all games. */ + GamesProxyModel *gameListProxyModel; /**< Proxy model for filtering and sorting the game list. */ + GameSelectorQuickFilterToolBar *quickFilterToolBar; + + QPushButton *filterButton; /**< Button to open the filter dialog. */ + QPushButton *clearFilterButton; /**< Button to clear active filters. */ + QPushButton *createButton; /**< Button to create a new game (only if room is set). */ + QPushButton *joinButton; /**< Button to join the selected game. */ + QPushButton *joinAsJudgeButton; /**< Button to join the selected game as a judge. */ + QPushButton *spectateButton; /**< Button to spectate the selected game. */ + QPushButton *joinAsJudgeSpectatorButton; /**< Button to join the selected game as a spectating judge. */ + + const bool showFilters; /**< Determines whether filter buttons are displayed. */ + GameTypeMap gameTypeMap; /**< Mapping of game types for the current room. */ + + /** + * @brief Updates the widget title to reflect the current number of displayed games. + * + * Shows the number of visible games versus total games if filters are enabled. + */ void updateTitle(); + + /** + * @brief Disables create/join/spectate buttons. + */ void disableButtons(); + + /** + * @brief Enables buttons for the currently selected game. + */ void enableButtons(); + + /** + * @brief Enables buttons for a specific game index. + * @param current The index of the currently selected game. + */ void enableButtonsForIndex(const QModelIndex ¤t); - void joinGame(const bool isSpectator); + + /** + * @brief Performs the join or spectate action for the currently selected game. + * @param asSpectator True to join as a spectator, false to join as a player. + * @param asJudge True to join as a judge, false to join as a player. + * + * Handles password prompts, overrides, and sending the join command to the server. + */ + void joinGame(bool asSpectator = false, bool asJudge = false); public: + /** + * @brief Constructs a GameSelector widget. + * @param _client The network client used to communicate with the server. + * @param _tabSupervisor Reference to TabSupervisor for managing tabs and rooms. + * @param _room Pointer to the current room; nullptr if no room is selected. + * @param _rooms Map of room IDs to room names. + * @param _gameTypes Map of room IDs to their available game types. + * @param restoresettings Whether to restore filter settings from previous sessions. + * @param _showfilters Whether to display filter buttons. + * @param parent Parent QWidget. + */ GameSelector(AbstractClient *_client, TabSupervisor *_tabSupervisor, TabRoom *_room, @@ -74,7 +190,16 @@ public: const bool restoresettings, const bool _showfilters, QWidget *parent = nullptr); + + /** + * @brief Updates UI text for translation/localization. + */ void retranslateUi(); + + /** + * @brief Updates or adds a game entry in the list. + * @param info The ServerInfo_Game object containing information about the game to update. + */ void processGameInfo(const ServerInfo_Game &info); }; diff --git a/cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.cpp b/cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.cpp new file mode 100644 index 000000000..c63b52450 --- /dev/null +++ b/cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.cpp @@ -0,0 +1,110 @@ +#include "game_selector_quick_filter_toolbar.h" + +#include "games_model.h" +#include "user/user_list_manager.h" + +#include +#include +#include +#include +#include + +GameSelectorQuickFilterToolBar::GameSelectorQuickFilterToolBar(QWidget *parent, + TabSupervisor *_tabSupervisor, + GamesProxyModel *_model, + const QMap &allGameTypes) + : QWidget(parent), tabSupervisor(_tabSupervisor), model(_model) +{ + mainLayout = new QHBoxLayout(this); + mainLayout->setContentsMargins(0, 0, 0, 0); + mainLayout->setSpacing(5); + + searchBar = new QLineEdit(this); + searchBar->setText(model->getCreatorNameFilters().join(", ")); + connect(searchBar, &QLineEdit::textChanged, this, [this](const QString &text) { model->setGameNameFilter(text); }); + + hideGamesNotCreatedByBuddiesCheckBox = new QCheckBox(this); + hideGamesNotCreatedByBuddiesCheckBox->setChecked(model->getHideBuddiesOnlyGames()); + connect(hideGamesNotCreatedByBuddiesCheckBox, &QCheckBox::toggled, this, [this](bool checked) { + if (checked) { + QStringList buddyNames; + for (auto buddy : tabSupervisor->getUserListManager()->getBuddyList().values()) { + buddyNames << QString::fromStdString(buddy.name()); + } + model->setCreatorNameFilters(buddyNames); + } else { + model->setCreatorNameFilters({}); + } + }); + + hideFullGamesCheckBox = new QCheckBox(this); + hideFullGamesCheckBox->setChecked(model->getHideFullGames()); + connect(hideFullGamesCheckBox, &QCheckBox::toggled, this, + [this](bool checked) { model->setHideFullGames(checked); }); + + hideStartedGamesCheckBox = new QCheckBox(this); + hideStartedGamesCheckBox->setChecked(model->getHideGamesThatStarted()); + connect(hideStartedGamesCheckBox, &QCheckBox::toggled, this, + [this](bool checked) { model->setHideGamesThatStarted(checked); }); + + filterToFormatComboBox = new QComboBox(this); + + // Add a "No filter" / "All types" option first + filterToFormatComboBox->addItem(tr("All types"), QVariant()); // empty QVariant = no filter + + QMapIterator i(allGameTypes); + while (i.hasNext()) { + i.next(); + filterToFormatComboBox->addItem(i.value(), i.key()); // text = name, data = type ID + } + + QSet currentTypes = model->getGameTypeFilter(); + if (currentTypes.size() == 1) { + int typeId = *currentTypes.begin(); + int index = filterToFormatComboBox->findData(typeId); + if (index >= 0) + filterToFormatComboBox->setCurrentIndex(index); + } else { + filterToFormatComboBox->setCurrentIndex(0); // "All types" by default + } + + // Update proxy model on selection change + connect(filterToFormatComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index) { + QVariant data = filterToFormatComboBox->itemData(index); + if (!data.isValid()) { + model->setGameTypeFilter({}); // empty = no filter + } else { + int typeId = data.toInt(); + model->setGameTypeFilter({typeId}); + } + }); + + hideGamesNotCreatedByBuddiesCheckBox->setMinimumSize(20, 20); + hideFullGamesCheckBox->setMinimumSize(20, 20); + hideStartedGamesCheckBox->setMinimumSize(20, 20); + +#if defined(Q_OS_MAC) + mainLayout->setSpacing(6); +#endif + + mainLayout->addWidget(searchBar); + mainLayout->addWidget(filterToFormatComboBox); + mainLayout->addWidget(hideGamesNotCreatedByBuddiesCheckBox); + mainLayout->addSpacing(5); + mainLayout->addWidget(hideFullGamesCheckBox); + mainLayout->addSpacing(5); + mainLayout->addWidget(hideStartedGamesCheckBox); + + setLayout(mainLayout); + + retranslateUi(); +} + +void GameSelectorQuickFilterToolBar::retranslateUi() +{ + searchBar->setPlaceholderText(tr("Filter by game name...")); + filterToFormatComboBox->setToolTip(tr("Filter by game type/format")); + hideGamesNotCreatedByBuddiesCheckBox->setText(tr("Hide games not created by buddies")); + hideFullGamesCheckBox->setText(tr("Hide full games")); + hideStartedGamesCheckBox->setText(tr("Hide started games")); +} diff --git a/cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.h b/cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.h new file mode 100644 index 000000000..f7e0c6fb1 --- /dev/null +++ b/cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.h @@ -0,0 +1,39 @@ +#ifndef COCKATRICE_GAME_SELECTOR_QUICK_FILTER_TOOLBAR_H +#define COCKATRICE_GAME_SELECTOR_QUICK_FILTER_TOOLBAR_H + +#include "../tabs/tab_supervisor.h" +#include "games_model.h" + +#include +#include +#include +#include +#include +#include + +class GameSelectorQuickFilterToolBar : public QWidget +{ + + Q_OBJECT + +public: + explicit GameSelectorQuickFilterToolBar(QWidget *parent, + TabSupervisor *tabSupervisor, + GamesProxyModel *model, + const QMap &allGameTypes); + void retranslateUi(); + +private: + TabSupervisor *tabSupervisor; + GamesProxyModel *model; + + QHBoxLayout *mainLayout; + + QLineEdit *searchBar; + QCheckBox *hideGamesNotCreatedByBuddiesCheckBox; + QCheckBox *hideFullGamesCheckBox; + QCheckBox *hideStartedGamesCheckBox; + QComboBox *filterToFormatComboBox; +}; + +#endif // COCKATRICE_GAME_SELECTOR_QUICK_FILTER_TOOLBAR_H diff --git a/cockatrice/src/interface/widgets/server/games_model.cpp b/cockatrice/src/interface/widgets/server/games_model.cpp index 4c68390c9..dfb41fd29 100644 --- a/cockatrice/src/interface/widgets/server/games_model.cpp +++ b/cockatrice/src/interface/widgets/server/games_model.cpp @@ -294,7 +294,7 @@ void GamesProxyModel::setGameFilters(bool _hideBuddiesOnlyGames, bool _hideNotBuddyCreatedGames, bool _hideOpenDecklistGames, const QString &_gameNameFilter, - const QString &_creatorNameFilter, + const QStringList &_creatorNameFilters, const QSet &_gameTypeFilter, int _maxPlayersFilterMin, int _maxPlayersFilterMax, @@ -315,7 +315,7 @@ void GamesProxyModel::setGameFilters(bool _hideBuddiesOnlyGames, hideNotBuddyCreatedGames = _hideNotBuddyCreatedGames; hideOpenDecklistGames = _hideOpenDecklistGames; gameNameFilter = _gameNameFilter; - creatorNameFilter = _creatorNameFilter; + creatorNameFilters = _creatorNameFilters; gameTypeFilter = _gameTypeFilter; maxPlayersFilterMin = _maxPlayersFilterMin; maxPlayersFilterMax = _maxPlayersFilterMax; @@ -348,15 +348,15 @@ int GamesProxyModel::getNumFilteredGames() const void GamesProxyModel::resetFilterParameters() { - setGameFilters(false, false, false, false, false, false, false, QString(), QString(), {}, DEFAULT_MAX_PLAYERS_MIN, - DEFAULT_MAX_PLAYERS_MAX, DEFAULT_MAX_GAME_AGE, false, false, false, false); + setGameFilters(false, false, false, false, false, false, false, QString(), QStringList(), {}, + DEFAULT_MAX_PLAYERS_MIN, DEFAULT_MAX_PLAYERS_MAX, DEFAULT_MAX_GAME_AGE, false, false, false, false); } bool GamesProxyModel::areFilterParametersSetToDefaults() const { return !hideFullGames && !hideGamesThatStarted && !hidePasswordProtectedGames && !hideBuddiesOnlyGames && !hideOpenDecklistGames && !hideIgnoredUserGames && !hideNotBuddyCreatedGames && gameNameFilter.isEmpty() && - creatorNameFilter.isEmpty() && gameTypeFilter.isEmpty() && maxPlayersFilterMin == DEFAULT_MAX_PLAYERS_MIN && + creatorNameFilters.isEmpty() && gameTypeFilter.isEmpty() && maxPlayersFilterMin == DEFAULT_MAX_PLAYERS_MIN && maxPlayersFilterMax == DEFAULT_MAX_PLAYERS_MAX && maxGameAge == DEFAULT_MAX_GAME_AGE && !showOnlyIfSpectatorsCanWatch && !showSpectatorPasswordProtected && !showOnlyIfSpectatorsCanChat && !showOnlyIfSpectatorsCanSeeHands; @@ -379,7 +379,7 @@ void GamesProxyModel::loadFilterParameters(const QMap &allGameType gameFilters.isHideFullGames(), gameFilters.isHideGamesThatStarted(), gameFilters.isHidePasswordProtectedGames(), gameFilters.isHideNotBuddyCreatedGames(), gameFilters.isHideOpenDecklistGames(), gameFilters.getGameNameFilter(), - gameFilters.getCreatorNameFilter(), newGameTypeFilter, gameFilters.getMinPlayers(), + gameFilters.getCreatorNameFilters(), newGameTypeFilter, gameFilters.getMinPlayers(), gameFilters.getMaxPlayers(), gameFilters.getMaxGameAge(), gameFilters.isShowOnlyIfSpectatorsCanWatch(), gameFilters.isShowSpectatorPasswordProtected(), gameFilters.isShowOnlyIfSpectatorsCanChat(), gameFilters.isShowOnlyIfSpectatorsCanSeeHands()); @@ -396,7 +396,7 @@ void GamesProxyModel::saveFilterParameters(const QMap &allGameType gameFilters.setHideNotBuddyCreatedGames(hideNotBuddyCreatedGames); gameFilters.setHideOpenDecklistGames(hideOpenDecklistGames); gameFilters.setGameNameFilter(gameNameFilter); - gameFilters.setCreatorNameFilter(creatorNameFilter); + gameFilters.setCreatorNameFilters(creatorNameFilters); QMapIterator gameTypeIterator(allGameTypes); while (gameTypeIterator.hasNext()) { @@ -459,9 +459,17 @@ bool GamesProxyModel::filterAcceptsRow(int sourceRow) const if (!gameNameFilter.isEmpty()) if (!QString::fromStdString(game.description()).contains(gameNameFilter, Qt::CaseInsensitive)) return false; - if (!creatorNameFilter.isEmpty()) - if (!QString::fromStdString(game.creator_info().name()).contains(creatorNameFilter, Qt::CaseInsensitive)) + if (!creatorNameFilters.isEmpty()) { + bool found = false; + for (const auto &createNameFilter : creatorNameFilters) { + if (QString::fromStdString(game.creator_info().name()).contains(createNameFilter, Qt::CaseInsensitive)) { + found = true; + } + } + if (!found) { return false; + } + } QSet gameTypes; for (int i = 0; i < game.game_types_size(); ++i) diff --git a/cockatrice/src/interface/widgets/server/games_model.h b/cockatrice/src/interface/widgets/server/games_model.h index d29800f66..c704befcc 100644 --- a/cockatrice/src/interface/widgets/server/games_model.h +++ b/cockatrice/src/interface/widgets/server/games_model.h @@ -1,9 +1,3 @@ -/** - * @file games_model.h - * @ingroup Lobby - * @brief TODO: Document this. - */ - #ifndef GAMESMODEL_H #define GAMESMODEL_H @@ -19,60 +13,107 @@ class UserListProxy; +/** + * @class GamesModel + * @ingroup Lobby + * @brief Model storing all available games for display in a QTreeView or QTableView. + * + * Provides access to game information, supports sorting by different columns, + * and updates when new game data is received from the server. + */ class GamesModel : public QAbstractTableModel { Q_OBJECT private: - QList gameList; - QMap rooms; - QMap gameTypes; + QList gameList; /**< List of games currently displayed. */ + QMap rooms; /**< Map of room IDs to room names. */ + QMap gameTypes; /**< Map of room IDs to available game types. */ - static const int NUM_COLS = 8; + static const int NUM_COLS = 8; /**< Number of columns in the table. */ public: - static const int SORT_ROLE = Qt::UserRole + 1; + static const int SORT_ROLE = Qt::UserRole + 1; /**< Role used for sorting. */ + /** + * @brief Constructs a GamesModel. + * @param _rooms Mapping of room IDs to room names. + * @param _gameTypes Mapping of room IDs to their available game types. + * @param parent Parent QObject. + */ GamesModel(const QMap &_rooms, const QMap &_gameTypes, QObject *parent = nullptr); + int rowCount(const QModelIndex &parent = QModelIndex()) const override { return parent.isValid() ? 0 : gameList.size(); } + int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override { return NUM_COLS; } + QVariant data(const QModelIndex &index, int role) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + /** + * @brief Formats the game creation time into a human-readable string. + * @param secs Number of seconds since the game started. + * @return Short string representing game age (e.g., "new", ">5 min", ">2 hr"). + */ static const QString getGameCreatedString(const int secs); + + /** + * @brief Returns a reference to a specific game by row index. + * @param row Row index in the table. + * @return Reference to the ServerInfo_Game at the given row. + */ const ServerInfo_Game &getGame(int row); /** - * Update game list with a (possibly new) game. + * @brief Updates the game list with a new or updated game. + * @param game The ServerInfo_Game object to add or update. */ void updateGameList(const ServerInfo_Game &game); + /** + * @brief Returns the index of the room column. + */ int roomColIndex() { return 0; } + + /** + * @brief Returns the index of the start time column. + */ int startTimeColIndex() { return 1; } + /** + * @brief Returns the map of game types per room. + */ const QMap &getGameTypes() { return gameTypes; } }; -class ServerInfo_User; - +/** + * @class GamesProxyModel + * @ingroup Lobby + * @brief Proxy model for filtering and sorting the GamesModel based on user preferences. + * + * Supports filtering games based on buddies-only, ignored users, password protection, + * game types, creator, age, player count, and spectator permissions. + */ class GamesProxyModel : public QSortFilterProxyModel { Q_OBJECT private: - const UserListProxy *userListProxy; + const UserListProxy *userListProxy; /**< Proxy for checking user ignore/buddy lists. */ // If adding any additional filters, make sure to update: // - GamesProxyModel() @@ -88,16 +129,25 @@ private: bool hidePasswordProtectedGames; bool hideNotBuddyCreatedGames; bool hideOpenDecklistGames; - QString gameNameFilter, creatorNameFilter; + QString gameNameFilter; + QStringList creatorNameFilters; QSet gameTypeFilter; quint32 maxPlayersFilterMin, maxPlayersFilterMax; QTime maxGameAge; - bool showOnlyIfSpectatorsCanWatch, showSpectatorPasswordProtected, showOnlyIfSpectatorsCanChat, - showOnlyIfSpectatorsCanSeeHands; + bool showOnlyIfSpectatorsCanWatch; + bool showSpectatorPasswordProtected; + bool showOnlyIfSpectatorsCanChat; + bool showOnlyIfSpectatorsCanSeeHands; public: + /** + * @brief Constructs a GamesProxyModel. + * @param parent Parent QObject. + * @param _userListProxy Proxy for accessing ignore/buddy lists. + */ explicit GamesProxyModel(QObject *parent = nullptr, const UserListProxy *_userListProxy = nullptr); + // Getters for filter parameters bool getHideBuddiesOnlyGames() const { return hideBuddiesOnlyGames; @@ -130,9 +180,9 @@ public: { return gameNameFilter; } - QString getCreatorNameFilter() const + QStringList getCreatorNameFilters() const { - return creatorNameFilter; + return creatorNameFilters; } QSet getGameTypeFilter() const { @@ -166,6 +216,10 @@ public: { return showOnlyIfSpectatorsCanSeeHands; } + + /** + * @brief Sets all game filters at once. + */ void setGameFilters(bool _hideBuddiesOnlyGames, bool _hideIgnoredUserGames, bool _hideFullGames, @@ -174,7 +228,7 @@ public: bool _hideNotBuddyCreatedGames, bool _hideOpenDecklistGames, const QString &_gameNameFilter, - const QString &_creatorNameFilter, + const QStringList &_creatorNameFilter, const QSet &_gameTypeFilter, int _maxPlayersFilterMin, int _maxPlayersFilterMax, @@ -184,11 +238,123 @@ public: bool _showOnlyIfSpectatorsCanChat, bool _showOnlyIfSpectatorsCanSeeHands); + // Individual setters + void setHideBuddiesOnlyGames(bool value) + { + hideBuddiesOnlyGames = value; + refresh(); + } + void setHideIgnoredUserGames(bool value) + { + hideIgnoredUserGames = value; + refresh(); + } + void setHideFullGames(bool value) + { + hideFullGames = value; + refresh(); + } + void setHideGamesThatStarted(bool value) + { + hideGamesThatStarted = value; + refresh(); + } + void setHidePasswordProtectedGames(bool value) + { + hidePasswordProtectedGames = value; + refresh(); + } + void setHideNotBuddyCreatedGames(bool value) + { + hideNotBuddyCreatedGames = value; + refresh(); + } + void setHideOpenDecklistGames(bool value) + { + hideOpenDecklistGames = value; + refresh(); + } + void setGameNameFilter(const QString &value) + { + gameNameFilter = value; + refresh(); + } + void setCreatorNameFilters(const QStringList &values) + { + creatorNameFilters = values; + refresh(); + } + void setGameTypeFilter(const QSet &value) + { + gameTypeFilter = value; + refresh(); + } + void setMaxPlayersFilterMin(int value) + { + maxPlayersFilterMin = value; + refresh(); + } + void setMaxPlayersFilterMax(int value) + { + maxPlayersFilterMax = value; + refresh(); + } + void setMaxGameAge(const QTime &value) + { + maxGameAge = value; + refresh(); + } + void setShowOnlyIfSpectatorsCanWatch(bool value) + { + showOnlyIfSpectatorsCanWatch = value; + refresh(); + } + void setShowSpectatorPasswordProtected(bool value) + { + showSpectatorPasswordProtected = value; + refresh(); + } + void setShowOnlyIfSpectatorsCanChat(bool value) + { + showOnlyIfSpectatorsCanChat = value; + refresh(); + } + void setShowOnlyIfSpectatorsCanSeeHands(bool value) + { + showOnlyIfSpectatorsCanSeeHands = value; + refresh(); + } + + /** + * @brief Returns the number of games filtered out by the current filter. + */ int getNumFilteredGames() const; + + /** + * @brief Resets all filter parameters to default values. + */ void resetFilterParameters(); + + /** + * @brief Returns true if all filter parameters are set to their defaults. + */ bool areFilterParametersSetToDefaults() const; + + /** + * @brief Loads filter parameters from persistent settings. + * @param allGameTypes Mapping of all game types by room ID. + */ void loadFilterParameters(const QMap &allGameTypes); + + /** + * @brief Saves filter parameters to persistent settings. + * @param allGameTypes Mapping of all game types by room ID. + */ void saveFilterParameters(const QMap &allGameTypes); + + /** + * @brief Refreshes the proxy model (re-applies filters). + */ void refresh(); protected: diff --git a/libcockatrice_settings/libcockatrice/settings/game_filters_settings.cpp b/libcockatrice_settings/libcockatrice/settings/game_filters_settings.cpp index 2da9b9bc3..e5db3010d 100644 --- a/libcockatrice_settings/libcockatrice/settings/game_filters_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/game_filters_settings.cpp @@ -25,7 +25,7 @@ void GameFiltersSettings::setHideBuddiesOnlyGames(bool hide) bool GameFiltersSettings::isHideBuddiesOnlyGames() { QVariant previous = getValue("hide_buddies_only_games"); - return previous == QVariant() || previous.toBool(); + return previous == QVariant() ? false : previous.toBool(); } void GameFiltersSettings::setHideFullGames(bool hide) @@ -36,7 +36,7 @@ void GameFiltersSettings::setHideFullGames(bool hide) bool GameFiltersSettings::isHideFullGames() { QVariant previous = getValue("hide_full_games"); - return !(previous == QVariant()) && previous.toBool(); + return previous == QVariant() ? false : previous.toBool(); } void GameFiltersSettings::setHideGamesThatStarted(bool hide) @@ -47,7 +47,7 @@ void GameFiltersSettings::setHideGamesThatStarted(bool hide) bool GameFiltersSettings::isHideGamesThatStarted() { QVariant previous = getValue("hide_games_that_started"); - return !(previous == QVariant()) && previous.toBool(); + return previous == QVariant() ? false : previous.toBool(); } void GameFiltersSettings::setHidePasswordProtectedGames(bool hide) @@ -58,7 +58,7 @@ void GameFiltersSettings::setHidePasswordProtectedGames(bool hide) bool GameFiltersSettings::isHidePasswordProtectedGames() { QVariant previous = getValue("hide_password_protected_games"); - return previous == QVariant() || previous.toBool(); + return previous == QVariant() ? false : previous.toBool(); } void GameFiltersSettings::setHideIgnoredUserGames(bool hide) @@ -69,7 +69,7 @@ void GameFiltersSettings::setHideIgnoredUserGames(bool hide) bool GameFiltersSettings::isHideIgnoredUserGames() { QVariant previous = getValue("hide_ignored_user_games"); - return !(previous == QVariant()) && previous.toBool(); + return previous == QVariant() ? true : previous.toBool(); } void GameFiltersSettings::setHideNotBuddyCreatedGames(bool hide) @@ -80,7 +80,7 @@ void GameFiltersSettings::setHideNotBuddyCreatedGames(bool hide) bool GameFiltersSettings::isHideNotBuddyCreatedGames() { QVariant previous = getValue("hide_not_buddy_created_games"); - return !(previous == QVariant()) && previous.toBool(); + return previous == QVariant() ? false : previous.toBool(); } void GameFiltersSettings::setHideOpenDecklistGames(bool hide) @@ -91,7 +91,7 @@ void GameFiltersSettings::setHideOpenDecklistGames(bool hide) bool GameFiltersSettings::isHideOpenDecklistGames() { QVariant previous = getValue("hide_open_decklist_games"); - return !(previous == QVariant()) && previous.toBool(); + return previous == QVariant() ? false : previous.toBool(); } void GameFiltersSettings::setGameNameFilter(QString gameName) @@ -104,14 +104,14 @@ QString GameFiltersSettings::getGameNameFilter() return getValue("game_name_filter").toString(); } -void GameFiltersSettings::setCreatorNameFilter(QString creatorName) +void GameFiltersSettings::setCreatorNameFilters(QStringList creatorName) { setValue(creatorName, "creator_name_filter"); } -QString GameFiltersSettings::getCreatorNameFilter() +QStringList GameFiltersSettings::getCreatorNameFilters() { - return getValue("creator_name_filter").toString(); + return getValue("creator_name_filter").toStringList(); } void GameFiltersSettings::setMinPlayers(int min) @@ -182,7 +182,7 @@ void GameFiltersSettings::setShowSpectatorPasswordProtected(bool show) bool GameFiltersSettings::isShowSpectatorPasswordProtected() { QVariant previous = getValue("show_spectator_password_protected"); - return previous == QVariant() ? true : previous.toBool(); + return previous == QVariant() ? false : previous.toBool(); } void GameFiltersSettings::setShowOnlyIfSpectatorsCanChat(bool show) @@ -193,7 +193,7 @@ void GameFiltersSettings::setShowOnlyIfSpectatorsCanChat(bool show) bool GameFiltersSettings::isShowOnlyIfSpectatorsCanChat() { QVariant previous = getValue("show_only_if_spectators_can_chat"); - return previous == QVariant() ? true : previous.toBool(); + return previous == QVariant() ? false : previous.toBool(); } void GameFiltersSettings::setShowOnlyIfSpectatorsCanSeeHands(bool show) @@ -204,5 +204,5 @@ void GameFiltersSettings::setShowOnlyIfSpectatorsCanSeeHands(bool show) bool GameFiltersSettings::isShowOnlyIfSpectatorsCanSeeHands() { QVariant previous = getValue("show_only_if_spectators_can_see_hands"); - return previous == QVariant() ? true : previous.toBool(); + return previous == QVariant() ? false : previous.toBool(); } \ No newline at end of file diff --git a/libcockatrice_settings/libcockatrice/settings/game_filters_settings.h b/libcockatrice_settings/libcockatrice/settings/game_filters_settings.h index ef2d802fd..d62fea03d 100644 --- a/libcockatrice_settings/libcockatrice/settings/game_filters_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/game_filters_settings.h @@ -24,7 +24,7 @@ public: bool isHideNotBuddyCreatedGames(); bool isHideOpenDecklistGames(); QString getGameNameFilter(); - QString getCreatorNameFilter(); + QStringList getCreatorNameFilters(); int getMinPlayers(); int getMaxPlayers(); QTime getMaxGameAge(); @@ -42,7 +42,7 @@ public: void setHidePasswordProtectedGames(bool hide); void setHideNotBuddyCreatedGames(bool hide); void setGameNameFilter(QString gameName); - void setCreatorNameFilter(QString creatorName); + void setCreatorNameFilters(QStringList creatorName); void setMinPlayers(int min); void setMaxPlayers(int max); void setMaxGameAge(const QTime &maxGameAge);