From 39220a2ea9f163f194fe0506732c609b26528042 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Sun, 8 Nov 2026 16:22:31 +0100 Subject: [PATCH] [Refactor] Split CreateGameFormWidget out of DlgCreateGame (#7801) * Extract CreateGameFormWidget from DlgCreateGame * Address review findings --- cockatrice/CMakeLists.txt | 1 + .../widgets/dialogs/dlg_create_game.cpp | 378 ++++-------------- .../widgets/dialogs/dlg_create_game.h | 34 +- .../game_hosting/create_game_form_widget.cpp | 260 ++++++++++++ .../game_hosting/create_game_form_widget.h | 69 ++++ .../game_hosting/game_creation_options.cpp | 64 +++ .../game_hosting/game_creation_options.h | 20 + tests/CMakeLists.txt | 14 + tests/create_game_form_widget_test.cpp | 132 ++++++ tests/game_creation_options_test.cpp | 217 ++++++++++ 10 files changed, 864 insertions(+), 325 deletions(-) create mode 100644 cockatrice/src/interface/widgets/game_hosting/create_game_form_widget.cpp create mode 100644 cockatrice/src/interface/widgets/game_hosting/create_game_form_widget.h create mode 100644 tests/create_game_form_widget_test.cpp diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 46e6702d9..77cb7f014 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -216,6 +216,7 @@ set(cockatrice_SOURCES src/interface/widgets/deck_editor/deck_state_manager.cpp src/interface/widgets/deck_editor/deck_zone_dialog.cpp src/interface/widgets/deck_editor/printing_disabled_info_widget.cpp + src/interface/widgets/game_hosting/create_game_form_widget.cpp src/interface/widgets/game_hosting/game_creation_options.cpp src/interface/widgets/general/background_sources.cpp src/interface/widgets/general/display/background_plate_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 2030882fd..f5ba5458c 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_create_game.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_create_game.cpp @@ -1,341 +1,109 @@ #include "dlg_create_game.h" #include "../../../client/settings/cache_settings.h" -#include "../interface/widgets/game_hosting/game_creation_options.h" -#include "../interface/widgets/tabs/tab_room.h" +#include "../game_hosting/create_game_form_widget.h" +#include "../game_hosting/game_creation_options.h" +#include "../tabs/tab_room.h" #include "libcockatrice/protocol/pb/response.pb.h" #include "libcockatrice/protocol/pb/room_commands.pb.h" +#include "libcockatrice/protocol/pb/serverinfo_game.pb.h" #include "libcockatrice/protocol/pb/serverinfo_user.pb.h" -#include "libcockatrice/utility/macros.h" +#include "libcockatrice/protocol/pending_command.h" +#include "libcockatrice/settings/game_settings.h" -#include -#include #include -#include -#include #include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include -#include - -class QWidget; - -void DlgCreateGame::sharedCtor() -{ - rememberGameSettings = new QCheckBox(tr("Re&member settings")); - descriptionLabel = new QLabel(tr("&Description:")); - descriptionEdit = new QLineEdit; - descriptionEdit->setMaxLength(MAX_NAME_LENGTH); - descriptionLabel->setBuddy(descriptionEdit); - - maxPlayersLabel = new QLabel(tr("P&layers:")); - maxPlayersEdit = new QSpinBox(); - maxPlayersEdit->setMinimum(1); - maxPlayersEdit->setMaximum(100); - maxPlayersEdit->setValue(2); - maxPlayersLabel->setBuddy(maxPlayersEdit); - - auto *generalGrid = new QGridLayout; - generalGrid->addWidget(descriptionLabel, 0, 0); - generalGrid->addWidget(descriptionEdit, 0, 1); - generalGrid->addWidget(maxPlayersLabel, 1, 0); - generalGrid->addWidget(maxPlayersEdit, 1, 1); - generalGroupBox = new QGroupBox(tr("General")); - generalGroupBox->setLayout(generalGrid); - - auto *gameTypeLayout = new QVBoxLayout; - QMapIterator gameTypeIterator(gameTypes); - while (gameTypeIterator.hasNext()) { - gameTypeIterator.next(); - auto *gameTypeRadioButton = new QRadioButton(gameTypeIterator.value(), this); - gameTypeLayout->addWidget(gameTypeRadioButton); - gameTypeCheckBoxes.insert(gameTypeIterator.key(), gameTypeRadioButton); - bool isChecked = SettingsCache::instance().game().getGameTypes().contains(gameTypeIterator.value() + ", "); - gameTypeCheckBoxes[gameTypeIterator.key()]->setChecked(isChecked); - } - auto *gameTypeGroupBox = new QGroupBox(tr("Game type")); - gameTypeGroupBox->setLayout(gameTypeLayout); - - passwordLabel = new QLabel(tr("&Password:")); - passwordEdit = new QLineEdit; - passwordEdit->setMaxLength(MAX_NAME_LENGTH); - passwordLabel->setBuddy(passwordEdit); - - onlyBuddiesCheckBox = new QCheckBox(tr("Only &buddies can join")); - onlyRegisteredCheckBox = new QCheckBox(tr("Only ®istered users can join")); - if (room && room->getUserInfo()->user_level() & ServerInfo_User::IsRegistered) { - onlyRegisteredCheckBox->setChecked(true); - } else { - onlyBuddiesCheckBox->setEnabled(false); - onlyRegisteredCheckBox->setEnabled(false); - } - - auto *joinRestrictionsLayout = new QGridLayout; - joinRestrictionsLayout->addWidget(passwordLabel, 0, 0); - joinRestrictionsLayout->addWidget(passwordEdit, 0, 1); - joinRestrictionsLayout->addWidget(onlyBuddiesCheckBox, 1, 0, 1, 2); - joinRestrictionsLayout->addWidget(onlyRegisteredCheckBox, 2, 0, 1, 2); - - auto *joinRestrictionsGroupBox = new QGroupBox(tr("Joining restrictions")); - joinRestrictionsGroupBox->setLayout(joinRestrictionsLayout); - - spectatorsAllowedCheckBox = new QCheckBox(tr("&Spectators can watch")); - spectatorsAllowedCheckBox->setChecked(true); - connect(spectatorsAllowedCheckBox, &QCheckBox::QT_STATE_CHANGED, this, &DlgCreateGame::spectatorsAllowedChanged); - spectatorsNeedPasswordCheckBox = new QCheckBox(tr("Spectators &need a password to watch")); - spectatorsCanTalkCheckBox = new QCheckBox(tr("Spectators can &chat")); - spectatorsSeeEverythingCheckBox = new QCheckBox(tr("Spectators can see &hands")); - createGameAsSpectatorCheckBox = new QCheckBox(tr("Create game as spectator")); - auto *spectatorsLayout = new QVBoxLayout; - spectatorsLayout->addWidget(spectatorsAllowedCheckBox); - spectatorsLayout->addWidget(spectatorsNeedPasswordCheckBox); - spectatorsLayout->addWidget(spectatorsCanTalkCheckBox); - spectatorsLayout->addWidget(spectatorsSeeEverythingCheckBox); - spectatorsLayout->addWidget(createGameAsSpectatorCheckBox); - spectatorsGroupBox = new QGroupBox(tr("Spectators")); - spectatorsGroupBox->setLayout(spectatorsLayout); - - startingLifeTotalLabel = new QLabel(tr("Starting life total:")); - startingLifeTotalEdit = new QSpinBox(); - startingLifeTotalEdit->setMinimum(1); - startingLifeTotalEdit->setMaximum(99999); ///< Arbitrary but we can raise this when people start complaining. - startingLifeTotalEdit->setValue(20); - startingLifeTotalLabel->setBuddy(startingLifeTotalEdit); - - shareDecklistsOnLoadCheckBox = new QCheckBox(tr("Open decklists in lobby")); - - shufflePlayersCheckBox = new QCheckBox(tr("Randomize turn order (seats) on each game start")); - - createGameAsJudgeCheckBox = new QCheckBox(tr("Create game as judge")); - - auto *gameSetupOptionsLayout = new QGridLayout; - gameSetupOptionsLayout->addWidget(startingLifeTotalLabel, 0, 0); - gameSetupOptionsLayout->addWidget(startingLifeTotalEdit, 0, 1); - gameSetupOptionsLayout->addWidget(shareDecklistsOnLoadCheckBox, 1, 0, 1, 2); - gameSetupOptionsLayout->addWidget(shufflePlayersCheckBox, 2, 0, 1, 2); - if (room && room->getUserInfo()->user_level() & ServerInfo_User::IsJudge) { - gameSetupOptionsLayout->addWidget(createGameAsJudgeCheckBox, 3, 0, 1, 2); - } else { - createGameAsJudgeCheckBox->setChecked(false); - createGameAsJudgeCheckBox->setHidden(true); - } - gameSetupOptionsGroupBox = new QGroupBox(tr("Game setup options")); - gameSetupOptionsGroupBox->setLayout(gameSetupOptionsLayout); - - auto *grid = new QGridLayout; - - // Top row - grid->addWidget(generalGroupBox, 0, 0); - grid->addWidget(joinRestrictionsGroupBox, 0, 1); - - // Middle row: left column - grid->addWidget(gameTypeGroupBox, 1, 0); - - // Middle row: right column (game setup + spectators) - auto *rightLayout = new QVBoxLayout; - rightLayout->addWidget(spectatorsGroupBox, Qt::AlignTop); // top - rightLayout->addWidget(gameSetupOptionsGroupBox); // bottom - - grid->addLayout(rightLayout, 1, 1); - - // Bottom row - grid->addWidget(rememberGameSettings, 3, 0, 1, 2); // span both columns if needed - - buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok); - connect(buttonBox, &QDialogButtonBox::rejected, this, &DlgCreateGame::reject); - - auto *mainLayout = new QVBoxLayout; - mainLayout->addLayout(grid); - mainLayout->addWidget(buttonBox); - - setLayout(mainLayout); - - setFixedHeight(sizeHint().height()); -} DlgCreateGame::DlgCreateGame(TabRoom *_room, const QMap &_gameTypes, QWidget *parent) - : QDialog(parent), room(_room), gameTypes(_gameTypes) + : QDialog(parent), room(_room) { - sharedCtor(); + const bool registered = room && (room->getUserInfo()->user_level() & ServerInfo_User::IsRegistered); + const bool judge = room && (room->getUserInfo()->user_level() & ServerInfo_User::IsJudge); - rememberGameSettings->setChecked(SettingsCache::instance().game().getRememberGameSettings()); - descriptionEdit->setText(SettingsCache::instance().game().getGameDescription()); - maxPlayersEdit->setValue(SettingsCache::instance().game().getMaxPlayers()); - if (room && room->getUserInfo()->user_level() & ServerInfo_User::IsRegistered) { - onlyBuddiesCheckBox->setChecked(SettingsCache::instance().game().getOnlyBuddies()); - onlyRegisteredCheckBox->setChecked(SettingsCache::instance().game().getOnlyRegistered()); + form = new CreateGameFormWidget(_gameTypes, registered, judge, this); + const bool rememberSettings = SettingsCache::instance().game().getRememberGameSettings(); + form->setRememberSettingsChecked(rememberSettings); + if (rememberSettings) { + form->setOptions(GameCreationOptions::fromSettings(SettingsCache::instance().game(), _gameTypes, registered)); } else { - onlyBuddiesCheckBox->setEnabled(false); - onlyRegisteredCheckBox->setEnabled(false); + form->resetToDefaults(); } - spectatorsAllowedCheckBox->setChecked(SettingsCache::instance().game().getSpectatorsAllowed()); - spectatorsNeedPasswordCheckBox->setChecked(SettingsCache::instance().game().getSpectatorsNeedPassword()); - spectatorsCanTalkCheckBox->setChecked(SettingsCache::instance().game().getSpectatorsCanTalk()); - spectatorsSeeEverythingCheckBox->setChecked(SettingsCache::instance().game().getSpectatorsCanSeeEverything()); - createGameAsSpectatorCheckBox->setChecked(SettingsCache::instance().game().getCreateGameAsSpectator()); - startingLifeTotalEdit->setValue(SettingsCache::instance().game().getDefaultStartingLifeTotal()); - shareDecklistsOnLoadCheckBox->setChecked(SettingsCache::instance().game().getShareDecklistsOnLoad()); - shufflePlayersCheckBox->setChecked(SettingsCache::instance().game().getShufflePlayers()); + form->focusDescription(); - if (!rememberGameSettings->isChecked()) { - actReset(); - } + errorLabel = new QLabel(this); + errorLabel->setObjectName("createGameErrorLabel"); + errorLabel->setWordWrap(true); + errorLabel->setVisible(false); - descriptionEdit->setFocus(); - clearButton = new QPushButton(tr("&Clear")); + buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok, this); + connect(buttonBox, &QDialogButtonBox::rejected, this, &DlgCreateGame::reject); + clearButton = new QPushButton(tr("&Clear"), this); buttonBox->addButton(QDialogButtonBox::Cancel); buttonBox->addButton(clearButton, QDialogButtonBox::ActionRole); connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgCreateGame::actOK); connect(clearButton, &QPushButton::clicked, this, &DlgCreateGame::actReset); + auto *mainLayout = new QVBoxLayout; + mainLayout->addWidget(form); + mainLayout->addWidget(errorLabel); + mainLayout->addWidget(buttonBox); + + setLayout(mainLayout); setWindowTitle(tr("Create game")); } DlgCreateGame::DlgCreateGame(const ServerInfo_Game &gameInfo, const QMap &_gameTypes, QWidget *parent) - : QDialog(parent), room(0), gameTypes(_gameTypes) + : QDialog(parent), room(nullptr) { - sharedCtor(); - - rememberGameSettings->setEnabled(false); - descriptionEdit->setEnabled(false); - maxPlayersEdit->setEnabled(false); - passwordEdit->setEnabled(false); - onlyBuddiesCheckBox->setEnabled(false); - onlyRegisteredCheckBox->setEnabled(false); - spectatorsAllowedCheckBox->setEnabled(false); - spectatorsNeedPasswordCheckBox->setEnabled(false); - spectatorsCanTalkCheckBox->setEnabled(false); - spectatorsSeeEverythingCheckBox->setEnabled(false); - createGameAsSpectatorCheckBox->setEnabled(false); - startingLifeTotalEdit->setEnabled(false); - shareDecklistsOnLoadCheckBox->setEnabled(false); - shufflePlayersCheckBox->setEnabled(false); - - descriptionEdit->setText(QString::fromStdString(gameInfo.description())); - maxPlayersEdit->setValue(gameInfo.max_players()); - onlyBuddiesCheckBox->setChecked(gameInfo.only_buddies()); - onlyRegisteredCheckBox->setChecked(gameInfo.only_registered()); - spectatorsAllowedCheckBox->setChecked(gameInfo.spectators_allowed()); - spectatorsNeedPasswordCheckBox->setChecked(gameInfo.spectators_need_password()); - spectatorsCanTalkCheckBox->setChecked(gameInfo.spectators_can_chat()); - spectatorsSeeEverythingCheckBox->setChecked(gameInfo.spectators_omniscient()); - shareDecklistsOnLoadCheckBox->setChecked(gameInfo.share_decklists_on_load()); - shufflePlayersCheckBox->setChecked(gameInfo.shuffle_players()); - - QSet types; - for (int i = 0; i < gameInfo.game_types_size(); ++i) { - types.insert(gameInfo.game_types(i)); - } - - QMapIterator gameTypeIterator(gameTypes); - while (gameTypeIterator.hasNext()) { - gameTypeIterator.next(); - - QRadioButton *gameTypeCheckBox = gameTypeCheckBoxes.value(gameTypeIterator.key()); - gameTypeCheckBox->setEnabled(false); - gameTypeCheckBox->setChecked(types.contains(gameTypeIterator.key())); - } + form = new CreateGameFormWidget(_gameTypes, false, false, this); + form->setOptions(GameCreationOptions::fromGameInfo(gameInfo, _gameTypes)); + form->setReadOnly(true); + buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok, this); connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgCreateGame::accept); + auto *mainLayout = new QVBoxLayout; + mainLayout->addWidget(form); + mainLayout->addWidget(buttonBox); + + setLayout(mainLayout); setWindowTitle(tr("Game information")); } void DlgCreateGame::actReset() { - descriptionEdit->setText(""); - maxPlayersEdit->setValue(2); - - passwordEdit->setText(""); - onlyBuddiesCheckBox->setChecked(false); - onlyRegisteredCheckBox->setChecked(room && room->getUserInfo()->user_level() & ServerInfo_User::IsRegistered); - - spectatorsAllowedCheckBox->setChecked(true); - spectatorsNeedPasswordCheckBox->setChecked(false); - spectatorsCanTalkCheckBox->setChecked(false); - spectatorsSeeEverythingCheckBox->setChecked(false); - createGameAsSpectatorCheckBox->setChecked(false); - - startingLifeTotalEdit->setValue(20); - shareDecklistsOnLoadCheckBox->setChecked(false); - shufflePlayersCheckBox->setChecked(false); - createGameAsJudgeCheckBox->setChecked(false); - - QMapIterator gameTypeCheckBoxIterator(gameTypeCheckBoxes); - while (gameTypeCheckBoxIterator.hasNext()) { - gameTypeCheckBoxIterator.next(); - // must set auto enclusive to false to be able to set the check to false - gameTypeCheckBoxIterator.value()->setAutoExclusive(false); - gameTypeCheckBoxIterator.value()->setChecked(false); - gameTypeCheckBoxIterator.value()->setAutoExclusive(true); - } - - descriptionEdit->setFocus(); + form->resetToDefaults(); + clearError(); } void DlgCreateGame::actOK() { - auto _gameTypes = QString(); - QList gameTypeIds; - QMapIterator gameTypeCheckBoxIterator(gameTypeCheckBoxes); - while (gameTypeCheckBoxIterator.hasNext()) { - gameTypeCheckBoxIterator.next(); - if (gameTypeCheckBoxIterator.value()->isChecked()) { - gameTypeIds.append(gameTypeCheckBoxIterator.key()); - _gameTypes += gameTypeCheckBoxIterator.value()->text() + ", "; - } - } - - GameCreationOptions options{ - .description = descriptionEdit->text(), - .password = passwordEdit->text(), - .maxPlayers = maxPlayersEdit->value(), - .onlyBuddies = onlyBuddiesCheckBox->isChecked(), - .onlyRegistered = onlyRegisteredCheckBox->isChecked(), - .spectatorsAllowed = spectatorsAllowedCheckBox->isChecked(), - .spectatorsNeedPassword = spectatorsNeedPasswordCheckBox->isChecked(), - .spectatorsCanTalk = spectatorsCanTalkCheckBox->isChecked(), - .spectatorsSeeEverything = spectatorsSeeEverythingCheckBox->isChecked(), - .joinAsJudge = createGameAsJudgeCheckBox->isChecked(), - .joinAsSpectator = createGameAsSpectatorCheckBox->isChecked(), - .startingLifeTotal = startingLifeTotalEdit->value(), - .shareDecklistsOnLoad = shareDecklistsOnLoadCheckBox->isChecked(), - .shufflePlayers = shufflePlayersCheckBox->isChecked(), - .gameTypeIds = gameTypeIds, - }; + clearError(); + const GameCreationOptions options = form->options(); Command_CreateGame cmd = options.toCreateGameCommand(); - SettingsCache::instance().game().setRememberGameSettings(rememberGameSettings->isChecked()); - if (rememberGameSettings->isChecked()) { - SettingsCache::instance().game().setGameDescription(descriptionEdit->text()); - SettingsCache::instance().game().setMaxPlayers(maxPlayersEdit->value()); - SettingsCache::instance().game().setOnlyBuddies(onlyBuddiesCheckBox->isChecked()); - SettingsCache::instance().game().setOnlyRegistered(onlyRegisteredCheckBox->isChecked()); - SettingsCache::instance().game().setSpectatorsAllowed(spectatorsAllowedCheckBox->isChecked()); - SettingsCache::instance().game().setSpectatorsNeedPassword(spectatorsNeedPasswordCheckBox->isChecked()); - SettingsCache::instance().game().setSpectatorsCanTalk(spectatorsCanTalkCheckBox->isChecked()); - SettingsCache::instance().game().setSpectatorsCanSeeEverything(spectatorsSeeEverythingCheckBox->isChecked()); - SettingsCache::instance().game().setCreateGameAsSpectator(createGameAsSpectatorCheckBox->isChecked()); - SettingsCache::instance().game().setDefaultStartingLifeTotal(startingLifeTotalEdit->value()); - SettingsCache::instance().game().setShareDecklistsOnLoad(shareDecklistsOnLoadCheckBox->isChecked()); - SettingsCache::instance().game().setShufflePlayers(shufflePlayersCheckBox->isChecked()); - SettingsCache::instance().game().setGameTypes(_gameTypes); + const bool rememberSettings = form->isRememberSettingsChecked(); + auto &gameSettings = SettingsCache::instance().game(); + gameSettings.setRememberGameSettings(rememberSettings); + if (rememberSettings) { + gameSettings.setGameDescription(options.description); + gameSettings.setMaxPlayers(options.maxPlayers); + gameSettings.setOnlyBuddies(options.onlyBuddies); + gameSettings.setOnlyRegistered(options.onlyRegistered); + gameSettings.setSpectatorsAllowed(options.spectatorsAllowed); + gameSettings.setSpectatorsNeedPassword(options.spectatorsNeedPassword); + gameSettings.setSpectatorsCanTalk(options.spectatorsCanTalk); + gameSettings.setSpectatorsCanSeeEverything(options.spectatorsSeeEverything); + gameSettings.setCreateGameAsSpectator(options.joinAsSpectator); + gameSettings.setDefaultStartingLifeTotal(options.startingLifeTotal); + gameSettings.setShareDecklistsOnLoad(options.shareDecklistsOnLoad); + gameSettings.setShufflePlayers(options.shufflePlayers); + gameSettings.setGameTypes(form->selectedGameTypesSettingString()); } + PendingCommand *pend = room->prepareRoomCommand(cmd); connect(pend, &PendingCommand::finished, this, &DlgCreateGame::checkResponse); room->sendRoomCommand(pend); @@ -350,14 +118,26 @@ void DlgCreateGame::checkResponse(const Response &response) if (response.response_code() == Response::RespOk) { accept(); } else { - QMessageBox::critical(this, tr("Error"), tr("Server error.")); - return; + showError(tr("Server error.")); } } -void DlgCreateGame::spectatorsAllowedChanged(QT_STATE_CHANGED_T state) +void DlgCreateGame::showError(const QString &message) { - spectatorsNeedPasswordCheckBox->setEnabled(state); - spectatorsCanTalkCheckBox->setEnabled(state); - spectatorsSeeEverythingCheckBox->setEnabled(state); + if (!errorLabel) { + return; + } + errorLabel->setText(message); + errorLabel->setVisible(true); + adjustSize(); +} + +void DlgCreateGame::clearError() +{ + if (!errorLabel) { + return; + } + errorLabel->clear(); + errorLabel->setVisible(false); + adjustSize(); } diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_create_game.h b/cockatrice/src/interface/widgets/dialogs/dlg_create_game.h index 58356c9b6..414c9398d 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_create_game.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_create_game.h @@ -2,7 +2,6 @@ * @file dlg_create_game.h * @ingroup RoomDialogs */ -//! \todo Document this file. #ifndef DLG_CREATEGAME_H #define DLG_CREATEGAME_H @@ -10,21 +9,15 @@ #include #include #include -#include #include -class QCheckBox; +class CreateGameFormWidget; class QDialogButtonBox; -class QGroupBox; class QLabel; -class QLineEdit; class QPushButton; -class QRadioButton; -class QSpinBox; class Response; class ServerInfo_Game; class TabRoom; -class QWidget; class DlgCreateGame : public QDialog { @@ -36,27 +29,16 @@ private slots: void actOK(); void actReset(); void checkResponse(const Response &response); - void spectatorsAllowedChanged(QT_STATE_CHANGED_T state); private: TabRoom *room; - QMap gameTypes; - QMap gameTypeCheckBoxes; + CreateGameFormWidget *form = nullptr; + QDialogButtonBox *buttonBox = nullptr; + QPushButton *clearButton = nullptr; + QLabel *errorLabel = nullptr; - QGroupBox *generalGroupBox, *spectatorsGroupBox, *gameSetupOptionsGroupBox; - QLabel *descriptionLabel, *passwordLabel, *maxPlayersLabel, *startingLifeTotalLabel, *shareDecklistsOnLoadLabel; - QLineEdit *descriptionEdit, *passwordEdit; - QSpinBox *maxPlayersEdit, *startingLifeTotalEdit; - QCheckBox *onlyBuddiesCheckBox, *onlyRegisteredCheckBox; - QCheckBox *spectatorsAllowedCheckBox, *spectatorsNeedPasswordCheckBox, *spectatorsCanTalkCheckBox, - *spectatorsSeeEverythingCheckBox, *createGameAsJudgeCheckBox, *createGameAsSpectatorCheckBox; - QCheckBox *shareDecklistsOnLoadCheckBox; - QCheckBox *shufflePlayersCheckBox; - QDialogButtonBox *buttonBox; - QPushButton *clearButton; - QCheckBox *rememberGameSettings; - - void sharedCtor(); + void showError(const QString &message); + void clearError(); }; -#endif +#endif // DLG_CREATEGAME_H diff --git a/cockatrice/src/interface/widgets/game_hosting/create_game_form_widget.cpp b/cockatrice/src/interface/widgets/game_hosting/create_game_form_widget.cpp new file mode 100644 index 000000000..7d9e8e694 --- /dev/null +++ b/cockatrice/src/interface/widgets/game_hosting/create_game_form_widget.cpp @@ -0,0 +1,260 @@ +#include "create_game_form_widget.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +CreateGameFormWidget::CreateGameFormWidget(const QMap &gameTypes, + bool _registeredUser, + bool judge, + QWidget *parent) + : QWidget(parent), registeredUser(_registeredUser) +{ + setObjectName("createGameForm"); + + rememberGameSettings = new QCheckBox(tr("Re&member settings")); + auto *descriptionLabel = new QLabel(tr("&Description:")); + descriptionEdit = new QLineEdit; + descriptionEdit->setMaxLength(MAX_NAME_LENGTH); + descriptionLabel->setBuddy(descriptionEdit); + + auto *maxPlayersLabel = new QLabel(tr("P&layers:")); + maxPlayersEdit = new QSpinBox(); + maxPlayersEdit->setMinimum(1); + maxPlayersEdit->setMaximum(100); + maxPlayersEdit->setValue(2); + maxPlayersLabel->setBuddy(maxPlayersEdit); + + auto *generalGrid = new QGridLayout; + generalGrid->addWidget(descriptionLabel, 0, 0); + generalGrid->addWidget(descriptionEdit, 0, 1); + generalGrid->addWidget(maxPlayersLabel, 1, 0); + generalGrid->addWidget(maxPlayersEdit, 1, 1); + auto *generalGroupBox = new QGroupBox(tr("General")); + generalGroupBox->setLayout(generalGrid); + + auto *gameTypeLayout = new QVBoxLayout; + for (auto it = gameTypes.cbegin(); it != gameTypes.cend(); ++it) { + auto *gameTypeRadioButton = new QRadioButton(it.value(), this); + gameTypeLayout->addWidget(gameTypeRadioButton); + gameTypeCheckBoxes.insert(it.key(), gameTypeRadioButton); + } + auto *gameTypeGroupBox = new QGroupBox(tr("Game type")); + gameTypeGroupBox->setLayout(gameTypeLayout); + + auto *passwordLabel = new QLabel(tr("&Password:")); + passwordEdit = new QLineEdit; + passwordEdit->setMaxLength(MAX_NAME_LENGTH); + passwordLabel->setBuddy(passwordEdit); + + onlyBuddiesCheckBox = new QCheckBox(tr("Only &buddies can join")); + onlyRegisteredCheckBox = new QCheckBox(tr("Only ®istered users can join")); + if (registeredUser) { + onlyRegisteredCheckBox->setChecked(true); + } else { + onlyBuddiesCheckBox->setEnabled(false); + onlyRegisteredCheckBox->setEnabled(false); + } + + auto *joinRestrictionsLayout = new QGridLayout; + joinRestrictionsLayout->addWidget(passwordLabel, 0, 0); + joinRestrictionsLayout->addWidget(passwordEdit, 0, 1); + joinRestrictionsLayout->addWidget(onlyBuddiesCheckBox, 1, 0, 1, 2); + joinRestrictionsLayout->addWidget(onlyRegisteredCheckBox, 2, 0, 1, 2); + + auto *joinRestrictionsGroupBox = new QGroupBox(tr("Joining restrictions")); + joinRestrictionsGroupBox->setLayout(joinRestrictionsLayout); + + spectatorsAllowedCheckBox = new QCheckBox(tr("&Spectators can watch")); + spectatorsAllowedCheckBox->setChecked(true); + connect(spectatorsAllowedCheckBox, &QCheckBox::QT_STATE_CHANGED, this, + &CreateGameFormWidget::spectatorsAllowedChanged); + spectatorsNeedPasswordCheckBox = new QCheckBox(tr("Spectators &need a password to watch")); + spectatorsCanTalkCheckBox = new QCheckBox(tr("Spectators can &chat")); + spectatorsSeeEverythingCheckBox = new QCheckBox(tr("Spectators can see &hands")); + createGameAsSpectatorCheckBox = new QCheckBox(tr("Create game as spectator")); + auto *spectatorsLayout = new QVBoxLayout; + spectatorsLayout->addWidget(spectatorsAllowedCheckBox); + spectatorsLayout->addWidget(spectatorsNeedPasswordCheckBox); + spectatorsLayout->addWidget(spectatorsCanTalkCheckBox); + spectatorsLayout->addWidget(spectatorsSeeEverythingCheckBox); + spectatorsLayout->addWidget(createGameAsSpectatorCheckBox); + auto *spectatorsGroupBox = new QGroupBox(tr("Spectators")); + spectatorsGroupBox->setLayout(spectatorsLayout); + + auto *startingLifeTotalLabel = new QLabel(tr("Starting life total:")); + startingLifeTotalEdit = new QSpinBox(); + startingLifeTotalEdit->setMinimum(1); + startingLifeTotalEdit->setMaximum(99999); ///< Arbitrary but we can raise this when people start complaining. + startingLifeTotalEdit->setValue(20); + startingLifeTotalLabel->setBuddy(startingLifeTotalEdit); + + shareDecklistsOnLoadCheckBox = new QCheckBox(tr("Open decklists in lobby")); + + shufflePlayersCheckBox = new QCheckBox(tr("Randomize turn order (seats) on each game start")); + + createGameAsJudgeCheckBox = new QCheckBox(tr("Create game as judge")); + + auto *gameSetupOptionsLayout = new QGridLayout; + gameSetupOptionsLayout->addWidget(startingLifeTotalLabel, 0, 0); + gameSetupOptionsLayout->addWidget(startingLifeTotalEdit, 0, 1); + gameSetupOptionsLayout->addWidget(shareDecklistsOnLoadCheckBox, 1, 0, 1, 2); + gameSetupOptionsLayout->addWidget(shufflePlayersCheckBox, 2, 0, 1, 2); + if (judge) { + gameSetupOptionsLayout->addWidget(createGameAsJudgeCheckBox, 3, 0, 1, 2); + } else { + createGameAsJudgeCheckBox->setChecked(false); + createGameAsJudgeCheckBox->setHidden(true); + } + auto *gameSetupOptionsGroupBox = new QGroupBox(tr("Game setup options")); + gameSetupOptionsGroupBox->setLayout(gameSetupOptionsLayout); + + auto *grid = new QGridLayout; + + // Top row + grid->addWidget(generalGroupBox, 0, 0); + grid->addWidget(joinRestrictionsGroupBox, 0, 1); + + // Middle row: left column + grid->addWidget(gameTypeGroupBox, 1, 0); + + // Middle row: right column (game setup + spectators) + auto *rightLayout = new QVBoxLayout; + rightLayout->addWidget(spectatorsGroupBox, Qt::AlignTop); // top + rightLayout->addWidget(gameSetupOptionsGroupBox); // bottom + + grid->addLayout(rightLayout, 1, 1); + + // Bottom row + grid->addWidget(rememberGameSettings, 3, 0, 1, 2); // span both columns if needed + + setLayout(grid); +} + +void CreateGameFormWidget::setOptions(const GameCreationOptions &options) +{ + descriptionEdit->setText(options.description); + maxPlayersEdit->setValue(options.maxPlayers); + passwordEdit->setText(options.password); + onlyBuddiesCheckBox->setChecked(options.onlyBuddies); + onlyRegisteredCheckBox->setChecked(options.onlyRegistered); + spectatorsAllowedCheckBox->setChecked(options.spectatorsAllowed); + spectatorsNeedPasswordCheckBox->setChecked(options.spectatorsNeedPassword); + spectatorsCanTalkCheckBox->setChecked(options.spectatorsCanTalk); + spectatorsSeeEverythingCheckBox->setChecked(options.spectatorsSeeEverything); + createGameAsJudgeCheckBox->setChecked(options.joinAsJudge); + createGameAsSpectatorCheckBox->setChecked(options.joinAsSpectator); + startingLifeTotalEdit->setValue(options.startingLifeTotal); + shareDecklistsOnLoadCheckBox->setChecked(options.shareDecklistsOnLoad); + shufflePlayersCheckBox->setChecked(options.shufflePlayers); + + for (auto it = gameTypeCheckBoxes.cbegin(); it != gameTypeCheckBoxes.cend(); ++it) { + it.value()->setChecked(options.gameTypeIds.contains(it.key())); + } +} + +GameCreationOptions CreateGameFormWidget::options() const +{ + GameCreationOptions options; + options.description = descriptionEdit->text(); + options.password = passwordEdit->text(); + options.maxPlayers = maxPlayersEdit->value(); + options.onlyBuddies = onlyBuddiesCheckBox->isChecked(); + options.onlyRegistered = onlyRegisteredCheckBox->isChecked(); + options.spectatorsAllowed = spectatorsAllowedCheckBox->isChecked(); + options.spectatorsNeedPassword = spectatorsNeedPasswordCheckBox->isChecked(); + options.spectatorsCanTalk = spectatorsCanTalkCheckBox->isChecked(); + options.spectatorsSeeEverything = spectatorsSeeEverythingCheckBox->isChecked(); + options.joinAsJudge = createGameAsJudgeCheckBox->isChecked(); + options.joinAsSpectator = createGameAsSpectatorCheckBox->isChecked(); + options.startingLifeTotal = startingLifeTotalEdit->value(); + options.shareDecklistsOnLoad = shareDecklistsOnLoadCheckBox->isChecked(); + options.shufflePlayers = shufflePlayersCheckBox->isChecked(); + + for (auto it = gameTypeCheckBoxes.cbegin(); it != gameTypeCheckBoxes.cend(); ++it) { + if (it.value()->isChecked()) { + options.gameTypeIds.append(it.key()); + } + } + + return options; +} + +QString CreateGameFormWidget::selectedGameTypesSettingString() const +{ + QString gameTypes; + for (auto it = gameTypeCheckBoxes.cbegin(); it != gameTypeCheckBoxes.cend(); ++it) { + if (it.value()->isChecked()) { + gameTypes += it.value()->text() + ", "; + } + } + return gameTypes; +} + +void CreateGameFormWidget::resetToDefaults() +{ + GameCreationOptions defaults; + defaults.onlyRegistered = registeredUser; + setOptions(defaults); + + // An exclusive button group refuses to uncheck its selected button, which setOptions + // cannot do, so clear the exclusive flag while resetting the game type selection. + for (auto *gameTypeCheckBox : gameTypeCheckBoxes) { + gameTypeCheckBox->setAutoExclusive(false); + gameTypeCheckBox->setChecked(false); + gameTypeCheckBox->setAutoExclusive(true); + } + + descriptionEdit->setFocus(); +} + +void CreateGameFormWidget::setReadOnly(bool readOnly) +{ + const bool editable = !readOnly; + rememberGameSettings->setEnabled(editable); + descriptionEdit->setEnabled(editable); + maxPlayersEdit->setEnabled(editable); + passwordEdit->setEnabled(editable); + onlyBuddiesCheckBox->setEnabled(editable && registeredUser); + onlyRegisteredCheckBox->setEnabled(editable && registeredUser); + spectatorsAllowedCheckBox->setEnabled(editable); + spectatorsNeedPasswordCheckBox->setEnabled(editable); + spectatorsCanTalkCheckBox->setEnabled(editable); + spectatorsSeeEverythingCheckBox->setEnabled(editable); + createGameAsSpectatorCheckBox->setEnabled(editable); + startingLifeTotalEdit->setEnabled(editable); + shareDecklistsOnLoadCheckBox->setEnabled(editable); + shufflePlayersCheckBox->setEnabled(editable); + for (auto *gameTypeCheckBox : gameTypeCheckBoxes) { + gameTypeCheckBox->setEnabled(editable); + } +} + +void CreateGameFormWidget::focusDescription() +{ + descriptionEdit->setFocus(); +} + +bool CreateGameFormWidget::isRememberSettingsChecked() const +{ + return rememberGameSettings->isChecked(); +} + +void CreateGameFormWidget::setRememberSettingsChecked(bool checked) +{ + rememberGameSettings->setChecked(checked); +} + +void CreateGameFormWidget::spectatorsAllowedChanged(QT_STATE_CHANGED_T state) +{ + spectatorsNeedPasswordCheckBox->setEnabled(state); + spectatorsCanTalkCheckBox->setEnabled(state); + spectatorsSeeEverythingCheckBox->setEnabled(state); +} diff --git a/cockatrice/src/interface/widgets/game_hosting/create_game_form_widget.h b/cockatrice/src/interface/widgets/game_hosting/create_game_form_widget.h new file mode 100644 index 000000000..43e56006e --- /dev/null +++ b/cockatrice/src/interface/widgets/game_hosting/create_game_form_widget.h @@ -0,0 +1,69 @@ +/** + * @file create_game_form_widget.h + * @ingroup RoomWidgets + */ + +#ifndef CREATE_GAME_FORM_WIDGET_H +#define CREATE_GAME_FORM_WIDGET_H + +#include "game_creation_options.h" + +#include +#include +#include +#include + +class QCheckBox; +class QLineEdit; +class QRadioButton; +class QSpinBox; + +/** + * @brief The editable create-game form, independent of settings, the network, and the room. + * + * The dialog loads @ref GameCreationOptions into it and reads them back; a future host + * activity can do the same without duplicating the widget layout. + */ +class CreateGameFormWidget : public QWidget +{ + Q_OBJECT +public: + CreateGameFormWidget(const QMap &gameTypes, + bool registeredUser, + bool judge, + QWidget *parent = nullptr); + + void setOptions(const GameCreationOptions &options); + [[nodiscard]] GameCreationOptions options() const; + [[nodiscard]] QString selectedGameTypesSettingString() const; + void resetToDefaults(); + void setReadOnly(bool readOnly); + void focusDescription(); + [[nodiscard]] bool isRememberSettingsChecked() const; + void setRememberSettingsChecked(bool checked); + +private slots: + void spectatorsAllowedChanged(QT_STATE_CHANGED_T state); + +private: + bool registeredUser; + QMap gameTypeCheckBoxes; + + QLineEdit *descriptionEdit; + QLineEdit *passwordEdit; + QSpinBox *maxPlayersEdit; + QSpinBox *startingLifeTotalEdit; + QCheckBox *onlyBuddiesCheckBox; + QCheckBox *onlyRegisteredCheckBox; + QCheckBox *spectatorsAllowedCheckBox; + QCheckBox *spectatorsNeedPasswordCheckBox; + QCheckBox *spectatorsCanTalkCheckBox; + QCheckBox *spectatorsSeeEverythingCheckBox; + QCheckBox *createGameAsJudgeCheckBox; + QCheckBox *createGameAsSpectatorCheckBox; + QCheckBox *shareDecklistsOnLoadCheckBox; + QCheckBox *shufflePlayersCheckBox; + QCheckBox *rememberGameSettings; +}; + +#endif // CREATE_GAME_FORM_WIDGET_H diff --git a/cockatrice/src/interface/widgets/game_hosting/game_creation_options.cpp b/cockatrice/src/interface/widgets/game_hosting/game_creation_options.cpp index 33e30adb1..048899266 100644 --- a/cockatrice/src/interface/widgets/game_hosting/game_creation_options.cpp +++ b/cockatrice/src/interface/widgets/game_hosting/game_creation_options.cpp @@ -1,5 +1,69 @@ #include "game_creation_options.h" +#include +#include +#include + +GameCreationOptions GameCreationOptions::fromSettings(const IGameSettingsProvider &settings, + const QMap &gameTypes, + bool registeredUser) +{ + GameCreationOptions options; + options.description = settings.getGameDescription(); + options.maxPlayers = settings.getMaxPlayers(); + if (registeredUser) { + options.onlyBuddies = settings.getOnlyBuddies(); + options.onlyRegistered = settings.getOnlyRegistered(); + } + options.spectatorsAllowed = settings.getSpectatorsAllowed(); + options.spectatorsNeedPassword = settings.getSpectatorsNeedPassword(); + options.spectatorsCanTalk = settings.getSpectatorsCanTalk(); + options.spectatorsSeeEverything = settings.getSpectatorsCanSeeEverything(); + options.joinAsSpectator = settings.getCreateGameAsSpectator(); + options.startingLifeTotal = settings.getDefaultStartingLifeTotal(); + options.shareDecklistsOnLoad = settings.getShareDecklistsOnLoad(); + options.shufflePlayers = settings.getShufflePlayers(); + + // The old dialog stored game type names as "Name, " in one string; keep the exact + // substring test so an existing game.ini selects the same types after the split. + const QString savedGameTypes = settings.getGameTypes(); + for (auto it = gameTypes.cbegin(); it != gameTypes.cend(); ++it) { + if (savedGameTypes.contains(it.value() + ", ")) { + options.gameTypeIds.append(it.key()); + } + } + + return options; +} + +GameCreationOptions GameCreationOptions::fromGameInfo(const ServerInfo_Game &gameInfo, + const QMap &gameTypes) +{ + GameCreationOptions options; + options.description = QString::fromStdString(gameInfo.description()); + options.maxPlayers = gameInfo.max_players(); + options.onlyBuddies = gameInfo.only_buddies(); + options.onlyRegistered = gameInfo.only_registered(); + options.spectatorsAllowed = gameInfo.spectators_allowed(); + options.spectatorsNeedPassword = gameInfo.spectators_need_password(); + options.spectatorsCanTalk = gameInfo.spectators_can_chat(); + options.spectatorsSeeEverything = gameInfo.spectators_omniscient(); + options.shareDecklistsOnLoad = gameInfo.share_decklists_on_load(); + options.shufflePlayers = gameInfo.shuffle_players(); + + QSet selectedTypes; + for (int i = 0; i < gameInfo.game_types_size(); ++i) { + selectedTypes.insert(gameInfo.game_types(i)); + } + for (auto it = gameTypes.cbegin(); it != gameTypes.cend(); ++it) { + if (selectedTypes.contains(it.key())) { + options.gameTypeIds.append(it.key()); + } + } + + return options; +} + Command_CreateGame GameCreationOptions::toCreateGameCommand() const { Command_CreateGame cmd; diff --git a/cockatrice/src/interface/widgets/game_hosting/game_creation_options.h b/cockatrice/src/interface/widgets/game_hosting/game_creation_options.h index 15284eee0..cbc941815 100644 --- a/cockatrice/src/interface/widgets/game_hosting/game_creation_options.h +++ b/cockatrice/src/interface/widgets/game_hosting/game_creation_options.h @@ -9,8 +9,12 @@ #include "libcockatrice/protocol/pb/room_commands.pb.h" #include +#include #include +class IGameSettingsProvider; +class ServerInfo_Game; + /** * @brief The values needed to create a game, independent of the widgets that edit them. * @@ -35,6 +39,22 @@ struct GameCreationOptions bool shufflePlayers = false; QList gameTypeIds; + /** + * @brief Reads the remembered create-game settings. + * + * Join restrictions are only meaningful for registered users, so they are left + * unset for an unregistered host. Game type ids are resolved from the room's map + * by matching the remembered names. + */ + [[nodiscard]] static GameCreationOptions + fromSettings(const IGameSettingsProvider &settings, const QMap &gameTypes, bool registeredUser); + + /** + * @brief Reads the read-only fields shown in the game information view. + */ + [[nodiscard]] static GameCreationOptions fromGameInfo(const ServerInfo_Game &gameInfo, + const QMap &gameTypes); + /** * @brief Builds the room command, simplifying the description's whitespace. */ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7595706b2..62523f456 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -469,6 +469,20 @@ cockatrice_add_test( ${CMAKE_SOURCE_DIR}/cockatrice/src/interface/widgets/game_hosting/game_creation_options.cpp game_creation_options_test.cpp LIBS + libcockatrice_interfaces + libcockatrice_protocol + INCLUDE_DIRS + ${CMAKE_SOURCE_DIR}/cockatrice/src +) +cockatrice_add_test( + NAME + create_game_form_widget_test + SOURCES + ${CMAKE_SOURCE_DIR}/cockatrice/src/interface/widgets/game_hosting/create_game_form_widget.cpp + ${CMAKE_SOURCE_DIR}/cockatrice/src/interface/widgets/game_hosting/game_creation_options.cpp + create_game_form_widget_test.cpp + LIBS + libcockatrice_interfaces libcockatrice_protocol INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/cockatrice/src diff --git a/tests/create_game_form_widget_test.cpp b/tests/create_game_form_widget_test.cpp new file mode 100644 index 000000000..f919e3443 --- /dev/null +++ b/tests/create_game_form_widget_test.cpp @@ -0,0 +1,132 @@ +#include "interface/widgets/game_hosting/create_game_form_widget.h" + +#include +#include +#include + +namespace +{ +const QMap gameTypes{{1, "Draft"}, {2, "Commander"}}; + +void expectSameOptions(const GameCreationOptions &actual, const GameCreationOptions &expected) +{ + EXPECT_EQ(actual.description, expected.description); + EXPECT_EQ(actual.password, expected.password); + EXPECT_EQ(actual.maxPlayers, expected.maxPlayers); + EXPECT_EQ(actual.onlyBuddies, expected.onlyBuddies); + EXPECT_EQ(actual.onlyRegistered, expected.onlyRegistered); + EXPECT_EQ(actual.spectatorsAllowed, expected.spectatorsAllowed); + EXPECT_EQ(actual.spectatorsNeedPassword, expected.spectatorsNeedPassword); + EXPECT_EQ(actual.spectatorsCanTalk, expected.spectatorsCanTalk); + EXPECT_EQ(actual.spectatorsSeeEverything, expected.spectatorsSeeEverything); + EXPECT_EQ(actual.joinAsJudge, expected.joinAsJudge); + EXPECT_EQ(actual.joinAsSpectator, expected.joinAsSpectator); + EXPECT_EQ(actual.startingLifeTotal, expected.startingLifeTotal); + EXPECT_EQ(actual.shareDecklistsOnLoad, expected.shareDecklistsOnLoad); + EXPECT_EQ(actual.shufflePlayers, expected.shufflePlayers); + EXPECT_EQ(actual.gameTypeIds, expected.gameTypeIds); +} +} // namespace + +TEST(CreateGameFormWidget, RegisteredHostDefaultsToRegisteredOnly) +{ + const CreateGameFormWidget form(gameTypes, true, false); + + const GameCreationOptions options = form.options(); + + EXPECT_TRUE(options.onlyRegistered); + EXPECT_FALSE(options.onlyBuddies); + EXPECT_EQ(options.maxPlayers, 2); + EXPECT_TRUE(options.spectatorsAllowed); + EXPECT_EQ(options.startingLifeTotal, 20); + EXPECT_TRUE(options.gameTypeIds.isEmpty()); +} + +TEST(CreateGameFormWidget, UnregisteredHostDefaultsToOpenJoin) +{ + const CreateGameFormWidget form(gameTypes, false, false); + + const GameCreationOptions options = form.options(); + + EXPECT_FALSE(options.onlyRegistered); + EXPECT_FALSE(options.onlyBuddies); +} + +TEST(CreateGameFormWidget, SetOptionsRoundTripsEveryField) +{ + CreateGameFormWidget form(gameTypes, true, true); + + GameCreationOptions expected; + expected.description = "Some game"; + expected.password = "hunter2"; + expected.maxPlayers = 5; + expected.onlyBuddies = true; + expected.onlyRegistered = false; + expected.spectatorsAllowed = false; + expected.spectatorsNeedPassword = true; + expected.spectatorsCanTalk = true; + expected.spectatorsSeeEverything = true; + expected.joinAsJudge = true; + expected.joinAsSpectator = true; + expected.startingLifeTotal = 40; + expected.shareDecklistsOnLoad = true; + expected.shufflePlayers = true; + expected.gameTypeIds = {2}; + + form.setOptions(expected); + + expectSameOptions(form.options(), expected); +} + +TEST(CreateGameFormWidget, ResetRestoresDefaultsAndClearsTheGameType) +{ + CreateGameFormWidget form(gameTypes, true, false); + GameCreationOptions configured; + configured.description = "Configured"; + configured.maxPlayers = 8; + configured.onlyBuddies = true; + configured.joinAsJudge = true; + configured.gameTypeIds = {2}; + form.setOptions(configured); + + form.resetToDefaults(); + + const GameCreationOptions options = form.options(); + EXPECT_TRUE(options.description.isEmpty()); + EXPECT_EQ(options.maxPlayers, 2); + EXPECT_FALSE(options.onlyBuddies); + EXPECT_TRUE(options.onlyRegistered); + EXPECT_TRUE(options.spectatorsAllowed); + EXPECT_EQ(options.startingLifeTotal, 20); + EXPECT_FALSE(options.joinAsJudge); + EXPECT_TRUE(options.gameTypeIds.isEmpty()); +} + +TEST(CreateGameFormWidget, RememberSettingsFlagRoundTrips) +{ + CreateGameFormWidget form(gameTypes, true, false); + + EXPECT_FALSE(form.isRememberSettingsChecked()); + + form.setRememberSettingsChecked(true); + + EXPECT_TRUE(form.isRememberSettingsChecked()); +} + +TEST(CreateGameFormWidget, SelectedGameTypesSettingStringUsesTheLegacyEncoding) +{ + CreateGameFormWidget form(gameTypes, true, false); + GameCreationOptions configured; + configured.gameTypeIds = {1}; + form.setOptions(configured); + + EXPECT_EQ(form.selectedGameTypesSettingString(), "Draft, "); +} + +int main(int argc, char **argv) +{ + qputenv("QT_QPA_PLATFORM", "offscreen"); + QApplication app(argc, argv); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/game_creation_options_test.cpp b/tests/game_creation_options_test.cpp index e182e4e91..e4a7996cb 100644 --- a/tests/game_creation_options_test.cpp +++ b/tests/game_creation_options_test.cpp @@ -1,7 +1,100 @@ #include "interface/widgets/game_hosting/game_creation_options.h" #include +#include #include +#include + +namespace +{ +/** + * @brief The remembered settings a test hands to fromSettings. + * + * The fake cannot itself be an aggregate (it derives from a polymorphic interface), so the + * remembered values live in a GameCreationOptions member and the getters read them from + * there. That keeps the test data in one designated initializer. + */ +class FakeGameSettings : public IGameSettingsProvider +{ +public: + explicit FakeGameSettings(const GameCreationOptions &_options = {}) : options(_options) + { + } + + [[nodiscard]] QString getGameDescription() const override + { + return options.description; + } + [[nodiscard]] int getMaxPlayers() const override + { + return options.maxPlayers; + } + [[nodiscard]] QString getGameTypes() const override + { + return gameTypes; + } + [[nodiscard]] bool getOnlyBuddies() const override + { + return options.onlyBuddies; + } + [[nodiscard]] bool getOnlyRegistered() const override + { + return options.onlyRegistered; + } + [[nodiscard]] bool getSpectatorsAllowed() const override + { + return options.spectatorsAllowed; + } + [[nodiscard]] bool getSpectatorsNeedPassword() const override + { + return options.spectatorsNeedPassword; + } + [[nodiscard]] bool getSpectatorsCanTalk() const override + { + return options.spectatorsCanTalk; + } + [[nodiscard]] bool getSpectatorsCanSeeEverything() const override + { + return options.spectatorsSeeEverything; + } + [[nodiscard]] bool getCreateGameAsSpectator() const override + { + return options.joinAsSpectator; + } + [[nodiscard]] int getDefaultStartingLifeTotal() const override + { + return options.startingLifeTotal; + } + [[nodiscard]] bool getShareDecklistsOnLoad() const override + { + return options.shareDecklistsOnLoad; + } + [[nodiscard]] bool getShufflePlayers() const override + { + return options.shufflePlayers; + } + [[nodiscard]] bool getRememberGameSettings() const override + { + return false; + } + [[nodiscard]] bool getLocalGameRememberSettings() const override + { + return false; + } + [[nodiscard]] int getLocalGameMaxPlayers() const override + { + return 0; + } + [[nodiscard]] int getLocalGameStartingLifeTotal() const override + { + return 0; + } + + GameCreationOptions options; + /// The legacy "Name, " list; it has no GameCreationOptions counterpart. + QString gameTypes; +}; +} // namespace TEST(GameCreationOptions, DefaultsMatchTheDialog) { @@ -91,3 +184,127 @@ TEST(GameCreationOptions, DescriptionWhitespaceIsSimplified) EXPECT_EQ(cmd.description(), "a b"); } + +TEST(GameCreationOptions, FromSettingsCopiesEveryRememberedField) +{ + const FakeGameSettings settings{GameCreationOptions{.description = "Remembered game", + .password = {}, + .maxPlayers = 6, + .onlyBuddies = true, + .onlyRegistered = true, + .spectatorsAllowed = false, + .spectatorsNeedPassword = true, + .spectatorsCanTalk = true, + .spectatorsSeeEverything = true, + .joinAsJudge = false, + .joinAsSpectator = true, + .startingLifeTotal = 40, + .shareDecklistsOnLoad = true, + .shufflePlayers = true, + .gameTypeIds = {}}}; + + const GameCreationOptions options = GameCreationOptions::fromSettings(settings, {}, true); + + EXPECT_EQ(options.description, "Remembered game"); + EXPECT_EQ(options.maxPlayers, 6); + EXPECT_TRUE(options.onlyBuddies); + EXPECT_TRUE(options.onlyRegistered); + EXPECT_FALSE(options.spectatorsAllowed); + EXPECT_TRUE(options.spectatorsNeedPassword); + EXPECT_TRUE(options.spectatorsCanTalk); + EXPECT_TRUE(options.spectatorsSeeEverything); + EXPECT_TRUE(options.joinAsSpectator); + EXPECT_EQ(options.startingLifeTotal, 40); + EXPECT_TRUE(options.shareDecklistsOnLoad); + EXPECT_TRUE(options.shufflePlayers); +} + +TEST(GameCreationOptions, FromSettingsDropsJoinRestrictionsForUnregisteredHosts) +{ + FakeGameSettings settings; + settings.options.onlyBuddies = true; + settings.options.onlyRegistered = true; + + const GameCreationOptions options = GameCreationOptions::fromSettings(settings, {}, false); + + EXPECT_FALSE(options.onlyBuddies); + EXPECT_FALSE(options.onlyRegistered); +} + +TEST(GameCreationOptions, FromSettingsMatchesGameTypesByTheLegacyNameList) +{ + FakeGameSettings settings; + settings.gameTypes = "Draft, Commander, "; + const QMap gameTypes{{1, "Draft"}, {2, "Commander"}, {3, "Standard"}}; + + const GameCreationOptions options = GameCreationOptions::fromSettings(settings, gameTypes, true); + + EXPECT_EQ(options.gameTypeIds, QList({1, 2})); +} + +TEST(GameCreationOptions, FromSettingsDoesNotMatchGameTypesWithoutTheLegacySeparator) +{ + FakeGameSettings settings; + settings.gameTypes = "Draft Commander"; + const QMap gameTypes{{1, "Draft"}, {2, "Commander"}}; + + const GameCreationOptions options = GameCreationOptions::fromSettings(settings, gameTypes, true); + + EXPECT_TRUE(options.gameTypeIds.isEmpty()); +} + +TEST(GameCreationOptions, FromGameInfoCopiesTheVisibleFields) +{ + ServerInfo_Game gameInfo; + gameInfo.set_description("Some game"); + gameInfo.set_max_players(4); + gameInfo.set_only_buddies(true); + gameInfo.set_only_registered(true); + gameInfo.set_spectators_allowed(false); + gameInfo.set_spectators_need_password(true); + gameInfo.set_spectators_can_chat(true); + gameInfo.set_spectators_omniscient(true); + gameInfo.set_share_decklists_on_load(true); + gameInfo.set_shuffle_players(true); + gameInfo.add_game_types(2); + + const QMap gameTypes{{1, "Draft"}, {2, "Commander"}}; + const GameCreationOptions options = GameCreationOptions::fromGameInfo(gameInfo, gameTypes); + + EXPECT_EQ(options.description, "Some game"); + EXPECT_EQ(options.maxPlayers, 4); + EXPECT_TRUE(options.onlyBuddies); + EXPECT_TRUE(options.onlyRegistered); + EXPECT_FALSE(options.spectatorsAllowed); + EXPECT_TRUE(options.spectatorsNeedPassword); + EXPECT_TRUE(options.spectatorsCanTalk); + EXPECT_TRUE(options.spectatorsSeeEverything); + EXPECT_TRUE(options.shareDecklistsOnLoad); + EXPECT_TRUE(options.shufflePlayers); + EXPECT_EQ(options.gameTypeIds, QList({2})); +} + +TEST(GameCreationOptions, FromGameInfoLeavesUnshownFieldsAtDefaults) +{ + ServerInfo_Game gameInfo; + gameInfo.set_max_players(3); + + const GameCreationOptions options = GameCreationOptions::fromGameInfo(gameInfo, {}); + + EXPECT_TRUE(options.password.isEmpty()); + EXPECT_EQ(options.startingLifeTotal, 20); + EXPECT_FALSE(options.joinAsJudge); + EXPECT_FALSE(options.joinAsSpectator); +} + +TEST(GameCreationOptions, FromGameInfoIgnoresUnknownGameTypeIds) +{ + ServerInfo_Game gameInfo; + gameInfo.add_game_types(7); + gameInfo.add_game_types(9); + + const QMap gameTypes{{1, "Draft"}, {7, "Commander"}}; + const GameCreationOptions options = GameCreationOptions::fromGameInfo(gameInfo, gameTypes); + + EXPECT_EQ(options.gameTypeIds, QList({7})); +}