[Client] Settings: open Settings as a tab

Add a singleton TabSettings hosting the shared SettingsView and retarget
the menu action, tray entry, shortcut and room chat deep-link to
TabSupervisor::openTabSettings. Close validation moves from
DlgSettings::closeEvent to TabSettings::closeRequest with tr() contexts
preserved as DlgSettings. The modal stays but is no longer referenced.
This commit is contained in:
Lukas Brübach 2026-10-11 11:49:42 +02:00
parent 77a0253242
commit 8572ac65e6
7 changed files with 214 additions and 12 deletions

View file

@ -3,7 +3,7 @@
#include "../../../client/settings/cache_settings.h"
#include "../../../client/settings/shortcuts_settings.h"
#include "../../pixel_map_generator.h"
#include "../interface/widgets/dialogs/dlg_settings.h"
#include "../interface/widgets/dialogs/settings_view.h"
#include "../interface/widgets/server/chat_view/chat_view.h"
#include "../interface/widgets/server/game_link.h"
#include "../interface/widgets/server/game_selector.h"
@ -272,9 +272,7 @@ void TabRoom::actClearChat()
void TabRoom::actOpenChatSettings()
{
DlgSettings settings(this);
settings.setTab(DlgSettings::MessagesPage);
settings.exec();
tabSupervisor->openTabSettings(SettingsView::MessagesPage);
}
void TabRoom::actCompleterChanged()

View file

@ -0,0 +1,129 @@
/**
* @file tab_settings.cpp
* @ingroup Tabs
*/
#include "tab_settings.h"
#include "../../client/settings/cache_settings.h"
#include "../dialogs/settings_view.h"
#include <QCoreApplication>
#include <QDir>
#include <QLoggingCategory>
#include <QMessageBox>
#include <libcockatrice/card/database/card_database.h>
#include <libcockatrice/card/database/card_database_loader.h>
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/settings/paths_settings.h>
inline Q_LOGGING_CATEGORY(TabSettingsLog, "tab_settings");
TabSettings::TabSettings(TabSupervisor *_tabSupervisor, int pageIndex) : Tab(_tabSupervisor)
{
settingsView = new SettingsView(this);
setCentralWidget(settingsView);
if (pageIndex >= 0) {
settingsView->setTab(pageIndex);
}
}
void TabSettings::setTab(int pageIndex)
{
settingsView->setTab(pageIndex);
}
void TabSettings::retranslateUi()
{
// Nothing to do: SettingsView retranslates itself via langChanged and the
// TabSupervisor refreshes the tab title from getTabText().
}
/**
* @brief Validates paths and card database before the tab closes
*
* Moved verbatim from DlgSettings::closeEvent. The "DlgSettings" translation
* context below is deliberate and preserves the existing translations.
*
* @return False when the user wants to go back and fix a setting, true otherwise
*/
bool TabSettings::closeRequest()
{
// NOTE: "DlgSettings" contexts are intentional (translation continuity, see SettingsView).
bool showLoadError = true;
QString loadErrorMessage = QCoreApplication::translate("DlgSettings", "Unknown Error loading card database");
LoadStatus loadStatus = CardDatabaseManager::getInstance()->getLoadStatus();
qCInfo(TabSettingsLog) << "Card Database load status: " << loadStatus;
switch (loadStatus) {
case Ok:
showLoadError = false;
break;
case Invalid:
loadErrorMessage = QCoreApplication::translate(
"DlgSettings", "Your card database is invalid.\n\n"
"Cockatrice may not function correctly with an invalid database\n\n"
"You may need to rerun Oracle to update your card database.\n\n"
"Would you like to change your database location setting?");
break;
case VersionTooOld:
loadErrorMessage = QCoreApplication::translate(
"DlgSettings", "Your card database version is too old.\n\n"
"This can cause problems loading card information or images\n\n"
"Usually this can be fixed by rerunning Oracle to to update your card database.\n\n"
"Would you like to change your database location setting?");
break;
case NotLoaded:
loadErrorMessage = QCoreApplication::translate(
"DlgSettings", "Your card database did not finish loading\n\n"
"Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your "
"cards.xml attached\n\n"
"Would you like to change your database location setting?");
break;
case FileError:
loadErrorMessage = QCoreApplication::translate(
"DlgSettings", "File Error loading your card database.\n\n"
"Would you like to change your database location setting?");
break;
case NoCards:
loadErrorMessage = QCoreApplication::translate(
"DlgSettings", "Your card database was loaded but contains no cards.\n\n"
"Would you like to change your database location setting?");
break;
default:
loadErrorMessage = QCoreApplication::translate(
"DlgSettings", "Unknown card database load status\n\n"
"Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues\n\n"
"Would you like to change your database location setting?");
break;
}
if (showLoadError) {
if (QMessageBox::critical(this, QCoreApplication::translate("DlgSettings", "Error"), loadErrorMessage,
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
return false;
}
}
if (!QDir(SettingsCache::instance().paths().getDeckPath()).exists() ||
SettingsCache::instance().paths().getDeckPath().isEmpty()) {
if (QMessageBox::critical(this, QCoreApplication::translate("DlgSettings", "Error"),
QCoreApplication::translate(
"DlgSettings", "The path to your deck directory is invalid. Would you like to "
"go back and set the correct path?"),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
return false;
}
}
if (!QDir(SettingsCache::instance().paths().getPicsPath()).exists() ||
SettingsCache::instance().paths().getPicsPath().isEmpty()) {
if (QMessageBox::critical(
this, QCoreApplication::translate("DlgSettings", "Error"),
QCoreApplication::translate("DlgSettings", "The path to your card pictures directory is invalid. "
"Would you like to go back and set the correct path?"),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
return false;
}
}
return close();
}

View file

@ -0,0 +1,46 @@
/**
* @file tab_settings.h
* @brief Settings tab hosting the shared SettingsView
* @ingroup Tabs
*/
#ifndef TAB_SETTINGS_H
#define TAB_SETTINGS_H
#include "tab.h"
#include <QString>
#include <qtmetamacros.h>
class SettingsView;
class TabSupervisor;
/**
* @brief Singleton settings tab owned by the TabSupervisor
*
* Thin Tab frame around SettingsView: page selection, tab title and the close
* validation live here. All settings content is owned by SettingsView.
*/
class TabSettings : public Tab
{
Q_OBJECT
private:
SettingsView *settingsView;
public:
explicit TabSettings(TabSupervisor *_tabSupervisor, int pageIndex = -1);
/** @brief Selects the given SettingsView page (range-checked, no-op when out of range) */
void setTab(int pageIndex);
/** @brief The hosted settings content */
[[nodiscard]] SettingsView *getSettingsView() const
{
return settingsView;
}
void retranslateUi() override;
[[nodiscard]] QString getTabText() const override
{
return tr("Settings");
}
bool closeRequest() override;
};
#endif // TAB_SETTINGS_H

View file

@ -39,6 +39,7 @@
#include "tab_report.h"
#include "tab_room.h"
#include "tab_server.h"
#include "tab_settings.h"
#include "tab_visual_database_display.h"
#include "user_level.h"
#include "visual_deck_editor/tab_deck_editor_visual.h"
@ -154,9 +155,9 @@ void CloseButton::paintEvent(QPaintEvent * /*event*/)
TabSupervisor::TabSupervisor(AbstractClient *_client, QMenu *tabsMenu, QWidget *parent)
: QTabWidget(parent), userInfo(nullptr), client(_client), tabsMenu(tabsMenu), tabHome(nullptr),
tabVisualDeckStorage(nullptr), tabServer(nullptr), tabAccount(nullptr), tabDeckStorage(nullptr),
tabReplays(nullptr), tabAdmin(nullptr), tabCardArtRules(nullptr), tabLog(nullptr), tabReport(nullptr),
tabModeration(nullptr), tabDeveloper(nullptr), isLocalGame(false)
tabVisualDeckStorage(nullptr), tabServer(nullptr), tabSettings(nullptr), tabAccount(nullptr),
tabDeckStorage(nullptr), tabReplays(nullptr), tabAdmin(nullptr), tabCardArtRules(nullptr), tabLog(nullptr),
tabReport(nullptr), tabModeration(nullptr), tabDeveloper(nullptr), isLocalGame(false)
{
setElideMode(Qt::ElideRight);
setMovable(true);
@ -293,6 +294,7 @@ void TabSupervisor::retranslateUi()
// tabs
QList<Tab *> tabs;
tabs.append(tabServer);
tabs.append(tabSettings);
tabs.append(tabReplays);
tabs.append(tabDeckStorage);
tabs.append(tabAdmin);
@ -753,6 +755,25 @@ void TabSupervisor::openTabServer()
aTabServer->setChecked(true);
}
/**
* @brief Opens the singleton settings tab, creating it on first use
*
* @param pageIndex Initial SettingsView page, or -1 to keep the current page
* @return The settings tab
*/
TabSettings *TabSupervisor::openTabSettings(int pageIndex)
{
if (!tabSettings) {
tabSettings = new TabSettings(this, pageIndex);
myAddTab(tabSettings, nullptr);
connect(tabSettings, &QObject::destroyed, this, [this] { tabSettings = nullptr; });
} else if (pageIndex >= 0) {
tabSettings->setTab(pageIndex);
}
setCurrentWidget(tabSettings);
return tabSettings;
}
void TabSupervisor::actTabAccount(bool checked)
{
SettingsCache::instance().tabs().setTabAccountOpen(checked);

View file

@ -41,6 +41,7 @@ class UserListManager;
class AbstractClient;
class Tab;
class TabServer;
class TabSettings;
class TabRoom;
class TabHome;
class TabGame;
@ -108,6 +109,7 @@ private:
TabHome *tabHome;
TabDeckStorageVisual *tabVisualDeckStorage;
TabServer *tabServer;
TabSettings *tabSettings;
TabAccount *tabAccount;
TabDeckStorage *tabDeckStorage;
TabReplays *tabReplays;
@ -214,6 +216,7 @@ public slots:
void actTabVisualDeckStorage(bool checked);
void actTabReplays(bool checked);
void openTabServer();
TabSettings *openTabSettings(int pageIndex = -1);
void addRoomTab(const ServerInfo_Room &info, bool setCurrent);
void openTabPublicDecks(const QString &userName);
private slots:

View file

@ -26,14 +26,15 @@
#include "../interface/widgets/dialogs/dlg_edit_tokens.h"
#include "../interface/widgets/dialogs/dlg_local_game_options.h"
#include "../interface/widgets/dialogs/dlg_manage_sets.h"
#include "../interface/widgets/dialogs/dlg_settings.h"
#include "../interface/widgets/dialogs/dlg_startup_card_check.h"
#include "../interface/widgets/dialogs/dlg_tip_of_the_day.h"
#include "../interface/widgets/dialogs/dlg_update.h"
#include "../interface/widgets/dialogs/dlg_view_log.h"
#include "../interface/widgets/dialogs/settings_view.h"
#include "../interface/widgets/onboarding/first_run_wizard.h"
#include "../interface/widgets/settings_page/general_settings_page.h"
#include "../interface/widgets/tabs/tab_server.h"
#include "../interface/widgets/tabs/tab_settings.h"
#include "../interface/widgets/tabs/tab_supervisor.h"
#include "../main.h"
#include "abstract_client.h"
@ -271,10 +272,13 @@ void MainWindow::actFullScreen(bool checked)
void MainWindow::actSettings()
{
DlgSettings dlg(this);
auto *generalPage = qobject_cast<GeneralSettingsPage *>(dlg.page(DlgSettings::GeneralPage));
connect(generalPage, &GeneralSettingsPage::cardDatabaseUpdateRequested, this, &MainWindow::actCheckCardUpdates);
dlg.exec();
TabSettings *settingsTab = tabSupervisor->openTabSettings();
auto *generalPage =
qobject_cast<GeneralSettingsPage *>(settingsTab->getSettingsView()->page(SettingsView::GeneralPage));
if (generalPage) {
connect(generalPage, &GeneralSettingsPage::cardDatabaseUpdateRequested, this, &MainWindow::actCheckCardUpdates,
Qt::UniqueConnection);
}
}
void MainWindow::actExit()