Cockatrice/cockatrice/src/interface/widgets/settings_page/general_settings_page.cpp
2026-11-03 18:10:12 +01:00

696 lines
32 KiB
C++

#include "general_settings_page.h"
#include "../../../client/settings/cache_settings.h"
#include "../dialogs/path_safety_warning.h"
#include "../interface/card_picture_loader/card_picture_loader.h"
#include "../main.h"
#include "../server/user/user_info_connection.h"
#include "update/client/release_channel.h"
#include <QByteArray>
#include <QDate>
#include <QDebug>
#include <QFile>
#include <QFileDialog>
#include <QGridLayout>
#include <QGroupBox>
#include <QLineEdit>
#include <QList>
#include <QMap>
#include <QMessageBox>
#include <QPushButton>
#include <QSharedPointer>
#include <QSizePolicy>
#include <QVBoxLayout>
#include <QVariant>
#include <QVariantList>
#include <QtGlobal>
#include <libcockatrice/card/card_localization.h>
#include <libcockatrice/settings/cards_display_settings.h>
#include <libcockatrice/settings/deck_sync_settings.h>
#include <libcockatrice/settings/download_settings.h>
#include <libcockatrice/settings/paths_settings.h>
#include <libcockatrice/settings/personal_settings.h>
#include <libcockatrice/settings/tabs_settings.h>
#include <libcockatrice/settings/updates_settings.h>
#include <libcockatrice/utility/macros.h>
#include <libcockatrice/utility/qt_utils.h>
#include <libcockatrice/utility/translation_loader.h>
#include <qlogging.h>
#include <qnamespace.h>
#include <utility>
enum startupCardUpdateCheckBehaviorIndex
{
startupCardUpdateCheckBehaviorIndexNone,
startupCardUpdateCheckBehaviorIndexPrompt,
startupCardUpdateCheckBehaviorIndexAlways
};
GeneralSettingsPage::GeneralSettingsPage()
{
// language settings
for (const QString &code : findQmFiles()) {
languageBox.addItem(TranslationLoader::languageDisplayName(code), code);
}
// Select the language the client runs with: its own entry, the base language for a variant without a
// file of its own (de_CH -> de), and the default language for an unset setting, which names no file.
const QString configuredLanguage = SettingsCache::instance().personal().getLang();
int index = languageBox.findData(configuredLanguage);
if (index < 0) {
index = languageBox.findData(configuredLanguage.section('_', 0, 0));
}
if (index < 0) {
index = languageBox.findData(TranslationLoader::defaultLanguageCode());
}
if (index >= 0) {
languageBox.setCurrentIndex(index);
} else {
qCWarning(GeneralSettingsPageLog) << "could not find language" << configuredLanguage;
}
advertiseTranslationPageLabel.setTextInteractionFlags(Qt::LinksAccessibleByMouse);
advertiseTranslationPageLabel.setOpenExternalLinks(true);
connect(&languageBox, qOverload<int>(&QComboBox::currentIndexChanged), this,
&GeneralSettingsPage::languageBoxChanged);
// card text & images language, independent of the UI language
cardLanguageBox.addItem(tr("English"), "en");
for (const QString &code : CardLocalization::supportedLanguages()) {
cardLanguageBox.addItem(CardLocalization::languageDisplayName(code), code);
}
const int cardLangIndex = cardLanguageBox.findData(SettingsCache::instance().cardsDisplay().getCardLang());
cardLanguageBox.setCurrentIndex(cardLangIndex < 0 ? 0 : cardLangIndex);
connect(&cardLanguageBox, qOverload<int>(&QComboBox::currentIndexChanged), this,
&GeneralSettingsPage::cardLanguageBoxChanged);
// card search language, independent of the card display language
cardSearchLanguageBox.addItem(""); // texts set in retranslateUi
cardSearchLanguageBox.addItem("");
cardSearchLanguageBox.addItem("");
const int cardSearchLanguageIndex = SettingsCache::instance().cardsDisplay().getCardSearchLanguage();
cardSearchLanguageBox.setCurrentIndex(cardSearchLanguageIndex < 0 ? static_cast<int>(SearchLanguageMode::English)
: cardSearchLanguageIndex);
connect(&cardSearchLanguageBox, qOverload<int>(&QComboBox::currentIndexChanged), this,
&GeneralSettingsPage::cardSearchLanguageBoxChanged);
auto *languageGrid = new QGridLayout;
languageGrid->addWidget(&languageLabel, 0, 0);
languageGrid->addWidget(&languageBox, 0, 1);
languageGrid->addWidget(&advertiseTranslationPageLabel, 1, 1, Qt::AlignRight);
languageGrid->addWidget(&cardLanguageLabel, 2, 0);
languageGrid->addWidget(&cardLanguageBox, 2, 1);
languageGrid->addWidget(&cardLanguageNoteLabel, 3, 1);
languageGrid->addWidget(&cardSearchLanguageLabel, 4, 0);
languageGrid->addWidget(&cardSearchLanguageBox, 4, 1);
cardLanguageNoteLabel.setWordWrap(true);
cardLanguageNoteLabel.setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
languageGroupBox = new QGroupBox;
languageGroupBox->setLayout(languageGrid);
// version settings
SettingsCache &settings = SettingsCache::instance();
startupUpdateCheckCheckBox.setChecked(settings.updates().getCheckUpdatesOnStartup());
connect(&startupUpdateCheckCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings.updates(),
&UpdatesSettings::setCheckUpdatesOnStartup);
updateNotificationCheckBox.setChecked(settings.getNotifyAboutUpdates());
connect(&updateNotificationCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings.updates(),
&UpdatesSettings::setNotifyAboutUpdates);
connect(&newVersionOracleCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings.updates(),
&UpdatesSettings::setNotifyAboutNewVersion);
auto *versionGrid = new QGridLayout;
versionGrid->addWidget(&updateReleaseChannelLabel, 0, 0);
versionGrid->addWidget(&updateReleaseChannelBox, 0, 1);
versionGrid->addWidget(&startupUpdateCheckCheckBox, 1, 0, 1, 2);
versionGrid->addWidget(&updateNotificationCheckBox, 2, 0, 1, 2);
versionGrid->addWidget(&newVersionOracleCheckBox, 3, 0, 1, 2);
versionGroupBox = new QGroupBox;
versionGroupBox->setLayout(versionGrid);
// crash reporting consent (stored in personal.ini, not debug.ini)
crashReportingBox.addItem(""); // texts set in retranslateUi
crashReportingBox.addItem("");
crashReportingBox.addItem("");
crashReportingBox.setCurrentIndex(static_cast<int>(settings.personal().getCrashReportingMode()));
connect(&crashReportingBox, qOverload<int>(&QComboBox::currentIndexChanged), this, [](int index) {
if (index < 0 || index > static_cast<int>(PersonalSettings::CrashReportingMode::NeverSend)) {
return;
}
SettingsCache::instance().personal().setCrashReportingMode(
static_cast<PersonalSettings::CrashReportingMode>(index));
});
auto *crashReportingGrid = new QGridLayout;
crashReportingGrid->addWidget(&crashReportingLabel, 0, 0);
crashReportingGrid->addWidget(&crashReportingBox, 0, 1);
crashReportingGroupBox = new QGroupBox;
crashReportingGroupBox->setLayout(crashReportingGrid);
// card database settings
startupCardUpdateCheckBehaviorSelector.addItem(""); // these will be set in retranslateUI
startupCardUpdateCheckBehaviorSelector.addItem("");
startupCardUpdateCheckBehaviorSelector.addItem("");
if (SettingsCache::instance().updates().getStartupCardUpdateCheckPromptForUpdate()) {
startupCardUpdateCheckBehaviorSelector.setCurrentIndex(startupCardUpdateCheckBehaviorIndexPrompt);
} else if (SettingsCache::instance().updates().getStartupCardUpdateCheckAlwaysUpdate()) {
startupCardUpdateCheckBehaviorSelector.setCurrentIndex(startupCardUpdateCheckBehaviorIndexAlways);
} else {
startupCardUpdateCheckBehaviorSelector.setCurrentIndex(startupCardUpdateCheckBehaviorIndexNone);
}
connect(&startupCardUpdateCheckBehaviorSelector, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
[](int index) {
SettingsCache::instance().updates().setStartupCardUpdateCheckPromptForUpdate(
index == startupCardUpdateCheckBehaviorIndexPrompt);
SettingsCache::instance().updates().setStartupCardUpdateCheckAlwaysUpdate(
index == startupCardUpdateCheckBehaviorIndexAlways);
});
cardUpdateCheckIntervalSpinBox.setMinimum(1);
cardUpdateCheckIntervalSpinBox.setMaximum(30);
cardUpdateCheckIntervalSpinBox.setValue(settings.updates().getCardUpdateCheckInterval());
connect(&cardUpdateCheckIntervalSpinBox, qOverload<int>(&QSpinBox::valueChanged), &settings.updates(),
&UpdatesSettings::setCardUpdateCheckInterval);
newVersionOracleCheckBox.setChecked(settings.updates().getNotifyAboutNewVersion());
cardDatabaseUpdateTrayNotificationCheckBox.setChecked(settings.updates().getCardDatabaseUpdateTrayNotification());
connect(&cardDatabaseUpdateTrayNotificationCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings.updates(),
&UpdatesSettings::setCardDatabaseUpdateTrayNotification);
auto *cardDatabaseGrid = new QGridLayout;
cardDatabaseGrid->addWidget(&startupCardUpdateCheckBehaviorLabel, 0, 0);
cardDatabaseGrid->addWidget(&startupCardUpdateCheckBehaviorSelector, 0, 1);
cardDatabaseGrid->addWidget(&cardUpdateCheckIntervalLabel, 1, 0);
cardDatabaseGrid->addWidget(&cardUpdateCheckIntervalSpinBox, 1, 1);
cardDatabaseGrid->addWidget(&lastCardUpdateCheckDateLabel, 2, 1);
cardDatabaseGrid->addWidget(&cardDatabaseUpdateTrayNotificationCheckBox, 3, 0, 1, 2);
cardDatabaseGroupBox = new QGroupBox;
cardDatabaseGroupBox->setLayout(cardDatabaseGrid);
// startup settings
showTipsOnStartup.setChecked(settings.personal().getShowTipsOnStartup());
connect(&showTipsOnStartup, &QCheckBox::clicked, &settings.personal(), &PersonalSettings::setShowTipsOnStartup);
// startup destination
for (int i = 0; i < 8; ++i) {
startupTabSelector.addItem(""); // texts set in retranslateUi
}
startupTabSelector.setCurrentIndex(settings.tabs().getStartupTabIndex());
connect(&startupTabSelector, qOverload<int>(&QComboBox::currentIndexChanged), &settings.tabs(),
&TabsSettings::setStartupTabIndex);
connect(&startupTabSelector, qOverload<int>(&QComboBox::currentIndexChanged), this,
&GeneralSettingsPage::updateStartupServerControlsVisibility);
const QString savedHost = settings.tabs().getStartupServerHost();
const QString savedPort = settings.tabs().getStartupServerPort();
int startupServerIndex = -1;
UserConnection_Information uci;
for (const auto &savedServer : uci.getServerInfo()) {
const UserConnection_Information &info = savedServer.second;
const QString saveName = info.getSaveName();
if (saveName.isEmpty()) {
continue;
}
startupServerSelector.addItem(saveName, QVariantList{info.getServer(), info.getPort()});
if (startupServerIndex == -1 && info.getServer() == savedHost && info.getPort() == savedPort) {
startupServerIndex = startupServerSelector.count() - 1;
}
}
startupServerSelector.setCurrentIndex(startupServerIndex);
connect(&startupServerSelector, qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int index) {
const QVariantList serverInfo = startupServerSelector.itemData(index).toList();
if (serverInfo.size() != 2) {
return;
}
TabsSettings &tabs = SettingsCache::instance().tabs();
tabs.setStartupServerHost(serverInfo[0].toString());
tabs.setStartupServerPort(serverInfo[1].toString());
});
startupRoomNameEdit = new QLineEdit(settings.tabs().getStartupRoomName());
// Default (Expanding) would stretch the whole controls column when this row becomes visible,
// so size it like the combo boxes instead: fills the column, never widens it.
startupRoomNameEdit->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
connect(startupRoomNameEdit, &QLineEdit::editingFinished, this,
[this] { SettingsCache::instance().tabs().setStartupRoomName(startupRoomNameEdit->text().trimmed()); });
auto *startupGrid = new QGridLayout;
startupGrid->addWidget(&showTipsOnStartup, 0, 0, 1, 2);
startupGrid->addWidget(&startupTabLabel, 1, 0);
startupGrid->addWidget(&startupTabSelector, 1, 1);
startupGrid->addWidget(&startupServerLabel, 2, 0);
startupGrid->addWidget(&startupServerSelector, 2, 1);
startupGrid->addWidget(&startupRoomLabel, 3, 0);
startupGrid->addWidget(startupRoomNameEdit, 3, 1);
updateStartupServerControlsVisibility();
startupGroupBox = new QGroupBox;
startupGroupBox->setLayout(startupGrid);
// paths settings
deckPathEdit = new QLineEdit(settings.paths().getDeckPath());
deckPathEdit->setReadOnly(true);
auto *deckPathButton = new QPushButton("...");
connect(deckPathButton, &QPushButton::clicked, this, &GeneralSettingsPage::deckPathButtonClicked);
filtersPathEdit = new QLineEdit(settings.paths().getFiltersPath());
filtersPathEdit->setReadOnly(true);
auto *filtersPathButton = new QPushButton("...");
connect(filtersPathButton, &QPushButton::clicked, this, &GeneralSettingsPage::filtersPathButtonClicked);
replaysPathEdit = new QLineEdit(settings.paths().getReplaysPath());
replaysPathEdit->setReadOnly(true);
auto *replaysPathButton = new QPushButton("...");
connect(replaysPathButton, &QPushButton::clicked, this, &GeneralSettingsPage::replaysPathButtonClicked);
picsPathEdit = new QLineEdit(settings.paths().getPicsPath());
picsPathEdit->setReadOnly(true);
auto *picsPathButton = new QPushButton("...");
connect(picsPathButton, &QPushButton::clicked, this, &GeneralSettingsPage::picsPathButtonClicked);
cardDatabasePathEdit = new QLineEdit(settings.getCardDatabasePath());
cardDatabasePathEdit->setReadOnly(true);
auto *cardDatabasePathButton = new QPushButton("...");
connect(cardDatabasePathButton, &QPushButton::clicked, this, &GeneralSettingsPage::cardDatabasePathButtonClicked);
customCardDatabasePathEdit = new QLineEdit(settings.getCustomCardDatabasePath());
customCardDatabasePathEdit->setReadOnly(true);
auto *customCardDatabasePathButton = new QPushButton("...");
connect(customCardDatabasePathButton, &QPushButton::clicked, this,
&GeneralSettingsPage::customCardDatabaseButtonClicked);
tokenDatabasePathEdit = new QLineEdit(settings.getTokenDatabasePath());
tokenDatabasePathEdit->setReadOnly(true);
auto *tokenDatabasePathButton = new QPushButton("...");
connect(tokenDatabasePathButton, &QPushButton::clicked, this, &GeneralSettingsPage::tokenDatabasePathButtonClicked);
// Required init here to avoid crashing on Portable builds
resetAllPathsButton = new QPushButton;
bool isPortable = settings.getIsPortableBuild();
if (isPortable) {
deckPathEdit->setEnabled(false);
filtersPathEdit->setEnabled(false);
replaysPathEdit->setEnabled(false);
picsPathEdit->setEnabled(false);
cardDatabasePathEdit->setEnabled(false);
customCardDatabasePathEdit->setEnabled(false);
tokenDatabasePathEdit->setEnabled(false);
deckPathButton->setVisible(false);
replaysPathButton->setVisible(false);
picsPathButton->setVisible(false);
cardDatabasePathButton->setVisible(false);
customCardDatabasePathButton->setVisible(false);
tokenDatabasePathButton->setVisible(false);
} else {
connect(resetAllPathsButton, &QPushButton::clicked, this, &GeneralSettingsPage::resetAllPathsClicked);
allPathsResetLabel = new QLabel(tr("All paths have been reset"));
allPathsResetLabel->setVisible(false);
}
auto *pathsGrid = new QGridLayout;
pathsGrid->addWidget(&deckPathLabel, 0, 0);
pathsGrid->addWidget(deckPathEdit, 0, 1);
pathsGrid->addWidget(deckPathButton, 0, 2);
pathsGrid->addWidget(&filtersPathLabel, 1, 0);
pathsGrid->addWidget(filtersPathEdit, 1, 1);
pathsGrid->addWidget(filtersPathButton, 1, 2);
pathsGrid->addWidget(&replaysPathLabel, 2, 0);
pathsGrid->addWidget(replaysPathEdit, 2, 1);
pathsGrid->addWidget(replaysPathButton, 2, 2);
pathsGrid->addWidget(&picsPathLabel, 3, 0);
pathsGrid->addWidget(picsPathEdit, 3, 1);
pathsGrid->addWidget(picsPathButton, 3, 2);
pathsGrid->addWidget(&cardDatabasePathLabel, 4, 0);
pathsGrid->addWidget(cardDatabasePathEdit, 4, 1);
pathsGrid->addWidget(cardDatabasePathButton, 4, 2);
pathsGrid->addWidget(&customCardDatabasePathLabel, 5, 0);
pathsGrid->addWidget(customCardDatabasePathEdit, 5, 1);
pathsGrid->addWidget(customCardDatabasePathButton, 5, 2);
pathsGrid->addWidget(&tokenDatabasePathLabel, 6, 0);
pathsGrid->addWidget(tokenDatabasePathEdit, 6, 1);
pathsGrid->addWidget(tokenDatabasePathButton, 6, 2);
if (!isPortable) {
pathsGrid->addWidget(resetAllPathsButton, 7, 0);
pathsGrid->addWidget(allPathsResetLabel, 7, 1);
}
pathsGroupBox = new QGroupBox;
pathsGroupBox->setLayout(pathsGrid);
// deck sync settings
conflictDefaultSelector.addItem("", DeckSyncConflictAsk);
conflictDefaultSelector.addItem("", DeckSyncConflictKeepMine);
conflictDefaultSelector.addItem("", DeckSyncConflictKeepServer);
conflictDefaultSelector.addItem("", DeckSyncConflictKeepNewer);
int conflictIndex = conflictDefaultSelector.findData(settings.deckSync().getConflictDefault());
if (conflictIndex >= 0) {
conflictDefaultSelector.setCurrentIndex(conflictIndex);
}
connect(&conflictDefaultSelector, qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int index) {
SettingsCache::instance().deckSync().setConflictDefault(conflictDefaultSelector.itemData(index).toInt());
});
autoSyncCheckBox.setChecked(settings.deckSync().getAutoSync());
connect(&autoSyncCheckBox, &QCheckBox::toggled, &SettingsCache::instance().deckSync(),
&DeckSyncSettings::setAutoSync);
syncIntervalEdit.setMinimum(1);
syncIntervalEdit.setMaximum(60);
syncIntervalEdit.setValue(settings.deckSync().getSyncIntervalMinutes());
connect(&syncIntervalEdit, qOverload<int>(&QSpinBox::valueChanged), &SettingsCache::instance().deckSync(),
&DeckSyncSettings::setSyncIntervalMinutes);
auto *deckSyncGrid = new QGridLayout;
deckSyncGrid->addWidget(&conflictDefaultLabel, 0, 0);
deckSyncGrid->addWidget(&conflictDefaultSelector, 0, 1);
deckSyncGrid->addWidget(&autoSyncCheckBox, 1, 0, 1, 2);
deckSyncGrid->addWidget(&syncIntervalLabel, 2, 0);
deckSyncGrid->addWidget(&syncIntervalEdit, 2, 1);
deckSyncGroupBox = new QGroupBox;
deckSyncGroupBox->setLayout(deckSyncGrid);
auto *mainLayout = new QVBoxLayout;
mainLayout->addWidget(languageGroupBox);
mainLayout->addWidget(versionGroupBox);
mainLayout->addWidget(crashReportingGroupBox);
mainLayout->addWidget(cardDatabaseGroupBox);
mainLayout->addWidget(startupGroupBox);
mainLayout->addWidget(pathsGroupBox);
mainLayout->addWidget(deckSyncGroupBox);
mainLayout->addStretch();
GeneralSettingsPage::retranslateUi();
// connect the ReleaseChannel combo box only after the entries are inserted in retranslateUi
connect(&updateReleaseChannelBox, qOverload<int>(&QComboBox::currentIndexChanged), &settings.updates(),
&UpdatesSettings::setUpdateReleaseChannelIndex);
updateReleaseChannelBox.setCurrentIndex(settings.getUpdateReleaseChannelIndex());
setLayout(mainLayout);
connect(&SettingsCache::instance().personal(), &PersonalSettings::langChanged, this,
&GeneralSettingsPage::retranslateUi);
retranslateUi();
}
QStringList GeneralSettingsPage::findQmFiles()
{
return TranslationLoader::availableLanguages(translationPrefix,
TranslationLoader::applicationTranslationPaths(translationPath));
}
void GeneralSettingsPage::deckPathButtonClicked()
{
QString path = QFileDialog::getExistingDirectory(this, tr("Choose path"), deckPathEdit->text());
if (path.isEmpty()) {
return;
}
if (!PathSafetyWarning::confirmOutsideProgramDir(this, path)) {
return;
}
deckPathEdit->setText(path);
SettingsCache::instance().paths().setDeckPath(path);
}
void GeneralSettingsPage::filtersPathButtonClicked()
{
QString path = QFileDialog::getExistingDirectory(this, tr("Choose path"), filtersPathEdit->text());
if (path.isEmpty()) {
return;
}
if (!PathSafetyWarning::confirmOutsideProgramDir(this, path)) {
return;
}
filtersPathEdit->setText(path);
SettingsCache::instance().paths().setFiltersPath(path);
}
void GeneralSettingsPage::replaysPathButtonClicked()
{
QString path = QFileDialog::getExistingDirectory(this, tr("Choose path"), replaysPathEdit->text());
if (path.isEmpty()) {
return;
}
if (!PathSafetyWarning::confirmOutsideProgramDir(this, path)) {
return;
}
replaysPathEdit->setText(path);
SettingsCache::instance().paths().setReplaysPath(path);
}
void GeneralSettingsPage::picsPathButtonClicked()
{
QString path = QFileDialog::getExistingDirectory(this, tr("Choose path"), picsPathEdit->text());
if (path.isEmpty()) {
return;
}
if (!PathSafetyWarning::confirmOutsideProgramDir(this, path)) {
return;
}
picsPathEdit->setText(path);
SettingsCache::instance().paths().setPicsPath(path);
}
void GeneralSettingsPage::cardDatabasePathButtonClicked()
{
QString path = QFileDialog::getOpenFileName(this, tr("Choose path"), cardDatabasePathEdit->text());
if (path.isEmpty()) {
return;
}
if (!PathSafetyWarning::confirmOutsideProgramDir(this, path)) {
return;
}
cardDatabasePathEdit->setText(path);
SettingsCache::instance().paths().setCardDatabasePath(path);
}
void GeneralSettingsPage::customCardDatabaseButtonClicked()
{
QString path = QFileDialog::getExistingDirectory(this, tr("Choose path"), customCardDatabasePathEdit->text());
if (path.isEmpty()) {
return;
}
if (!PathSafetyWarning::confirmOutsideProgramDir(this, path)) {
return;
}
customCardDatabasePathEdit->setText(path);
SettingsCache::instance().paths().setCustomCardDatabasePath(path);
}
void GeneralSettingsPage::tokenDatabasePathButtonClicked()
{
QString path = QFileDialog::getOpenFileName(this, tr("Choose path"), tokenDatabasePathEdit->text());
if (path.isEmpty()) {
return;
}
if (!PathSafetyWarning::confirmOutsideProgramDir(this, path)) {
return;
}
tokenDatabasePathEdit->setText(path);
SettingsCache::instance().paths().setTokenDatabasePath(path);
}
void GeneralSettingsPage::resetAllPathsClicked()
{
SettingsCache &settings = SettingsCache::instance();
settings.resetPaths();
deckPathEdit->setText(settings.paths().getDeckPath());
replaysPathEdit->setText(settings.paths().getReplaysPath());
picsPathEdit->setText(settings.paths().getPicsPath());
cardDatabasePathEdit->setText(settings.getCardDatabasePath());
customCardDatabasePathEdit->setText(settings.getCustomCardDatabasePath());
tokenDatabasePathEdit->setText(settings.getTokenDatabasePath());
allPathsResetLabel->setVisible(true);
}
void GeneralSettingsPage::languageBoxChanged(int index)
{
SettingsCache::instance().personal().setLang(languageBox.itemData(index).toString());
}
void GeneralSettingsPage::cardLanguageBoxChanged(int index)
{
const QString lang = cardLanguageBox.itemData(index).toString();
SettingsCache::instance().cardsDisplay().setCardLang(lang);
// Switching to a non-default language only takes effect after the card
// database is re-imported with that language selected; English data is always
// present, so switching back to English needs no prompt.
if (lang == "en") {
return;
}
// The binary cache does not track the language its entries were imported in,
// and the downloaded pictures were fetched with English art names, so both are
// stale until Oracle re-imports the database in the new language: drop them.
QFile::remove(SettingsCache::instance().getCardDatabasePath() + ".cache");
CardPictureLoader::clearNetworkCache();
CardPictureLoader::clearPixmapCache();
// Art is resolved by the translated card name for non-English languages, so the
// matching Scryfall URL is added to the top of the download list. It stays
// visible in the deck editor settings, where it can be removed or reordered.
const bool localizedUrlAdded = SettingsCache::instance().downloads().addLocalizedScryfallUrl();
QString message = tr("<p>The card database only contains English card data. To see cards in <b>%1</b>, "
"<b>Oracle</b> must run once with this language selected and re-import the card "
"database.</p>"
"<p>The cached database and the downloaded card pictures have been cleared, so a "
"re-import is picked up without stale entries.</p>")
.arg(cardLanguageBox.itemText(index));
if (localizedUrlAdded) {
message += tr("<p>The Scryfall URL that resolves card art by translated name was added to the top of your "
"download list. You can remove or reorder it any time.</p>");
}
message += tr("<p>Run Oracle now?</p>");
// The answer only controls whether Oracle starts right away; the caches stay
// cleared so the next import or launch rebuilds them in the new language.
if (QtUtils::confirm(this, tr("Card text & images language changed"), message, QMessageBox::Yes)) {
emit cardDatabaseUpdateRequested();
}
}
void GeneralSettingsPage::cardSearchLanguageBoxChanged(int index)
{
SettingsCache::instance().cardsDisplay().setCardSearchLanguage(index);
}
void GeneralSettingsPage::updateStartupServerControlsVisibility()
{
const int index = startupTabSelector.currentIndex();
const bool serverNeeded = index == StartupTab::StartupTabServer || index == StartupTab::StartupTabServerRoom;
const bool roomNeeded = index == StartupTab::StartupTabServerRoom;
startupServerLabel.setVisible(serverNeeded);
startupServerSelector.setVisible(serverNeeded);
startupRoomLabel.setVisible(roomNeeded);
startupRoomNameEdit->setVisible(roomNeeded);
}
void GeneralSettingsPage::retranslateUi()
{
const auto &settings = SettingsCache::instance();
languageGroupBox->setTitle(tr("Language settings"));
languageLabel.setText(tr("Interface language:"));
cardLanguageBox.setItemText(0, tr("English"));
cardLanguageLabel.setText(tr("Card text & images language:"));
cardLanguageNoteLabel.setText(
tr("Foreign card names, text and art apply after you update the card database (Oracle)."));
cardSearchLanguageLabel.setText(tr("Language used in card search:"));
cardSearchLanguageBox.setItemText(static_cast<int>(SearchLanguageMode::English), tr("English"));
cardSearchLanguageBox.setItemText(static_cast<int>(SearchLanguageMode::Selected),
tr("Selected card language (untranslated cards still match in English)"));
cardSearchLanguageBox.setItemText(static_cast<int>(SearchLanguageMode::Both),
tr("English and selected card language"));
advertiseTranslationPageLabel.setText(
QString("<a href='%1'>%2</a>").arg(WIKI_TRANSLATION_FAQ).arg(tr("How to help with translations")));
versionGroupBox->setTitle(tr("Version settings"));
updateReleaseChannelLabel.setText(tr("Update channel"));
startupUpdateCheckCheckBox.setText(tr("Check for client updates on startup"));
updateNotificationCheckBox.setText(tr("Notify if a feature supported by the server is missing in my client"));
newVersionOracleCheckBox.setText(tr("Automatically run Oracle when running a new version of Cockatrice"));
crashReportingGroupBox->setTitle(tr("Crash reporting"));
crashReportingLabel.setText(tr("Send crash reports:"));
crashReportingBox.setItemText(static_cast<int>(PersonalSettings::CrashReportingMode::Ask), tr("Ask every time"));
crashReportingBox.setItemText(static_cast<int>(PersonalSettings::CrashReportingMode::AlwaysSend),
tr("Always send"));
crashReportingBox.setItemText(static_cast<int>(PersonalSettings::CrashReportingMode::NeverSend), tr("Never send"));
// We can't change the strings after they're put into the QComboBox, so this is our workaround
int oldIndex = updateReleaseChannelBox.currentIndex();
updateReleaseChannelBox.clear();
for (ReleaseChannel *chan : settings.getUpdateReleaseChannels()) {
updateReleaseChannelBox.addItem(tr(chan->getName().toUtf8()));
}
updateReleaseChannelBox.setCurrentIndex(oldIndex);
cardDatabaseGroupBox->setTitle(tr("Card database"));
startupCardUpdateCheckBehaviorLabel.setText(tr("Check for card database updates on startup"));
startupCardUpdateCheckBehaviorSelector.setItemText(startupCardUpdateCheckBehaviorIndexNone, tr("Don't check"));
startupCardUpdateCheckBehaviorSelector.setItemText(startupCardUpdateCheckBehaviorIndexPrompt,
tr("Prompt for update"));
startupCardUpdateCheckBehaviorSelector.setItemText(startupCardUpdateCheckBehaviorIndexAlways,
tr("Always update in the background"));
cardUpdateCheckIntervalLabel.setText(tr("Check for card database updates every"));
cardUpdateCheckIntervalSpinBox.setSuffix(tr(" days"));
cardDatabaseUpdateTrayNotificationCheckBox.setText(tr("Notify in the system tray while the card database updates"));
QDate lastCheckDate = settings.updates().getLastCardUpdateCheck();
int daysAgo = lastCheckDate.daysTo(QDate::currentDate());
lastCardUpdateCheckDateLabel.setText(
tr("Last update check on %1 (%2 days ago)").arg(lastCheckDate.toString()).arg(daysAgo));
startupGroupBox->setTitle(tr("Startup settings"));
showTipsOnStartup.setText(tr("Show tips on startup"));
startupTabLabel.setText(tr("Startup tab:"));
startupTabSelector.setItemText(StartupTab::StartupTabHome, tr("Home"));
startupTabSelector.setItemText(StartupTab::StartupTabVisualDeckStorage, tr("Visual Deck Storage"));
startupTabSelector.setItemText(StartupTab::StartupTabDeckStorage, tr("Deck Storage"));
startupTabSelector.setItemText(StartupTab::StartupTabReplays, tr("Game Replays"));
startupTabSelector.setItemText(StartupTab::StartupTabDeckEditor, tr("Deck Editor"));
startupTabSelector.setItemText(StartupTab::StartupTabVisualDeckEditor, tr("Visual Deck Editor"));
startupTabSelector.setItemText(StartupTab::StartupTabServer, tr("Server"));
startupTabSelector.setItemText(StartupTab::StartupTabServerRoom, tr("Server Room"));
startupTabSelector.setToolTip(
tr("The tab shown when Cockatrice starts. If the chosen tab is not open yet, it is opened."));
startupServerLabel.setText(tr("Server:"));
startupRoomLabel.setText(tr("Room:"));
startupRoomNameEdit->setPlaceholderText(tr("Room name"));
if (settings.getIsPortableBuild()) {
pathsGroupBox->setTitle(tr("Paths (editing disabled in portable mode)"));
} else {
pathsGroupBox->setTitle(tr("Paths"));
}
deckPathLabel.setText(tr("Decks directory:"));
filtersPathLabel.setText(tr("Filters directory:"));
replaysPathLabel.setText(tr("Replays directory:"));
picsPathLabel.setText(tr("Pictures directory:"));
cardDatabasePathLabel.setText(tr("Card database:"));
customCardDatabasePathLabel.setText(tr("Custom database directory:"));
tokenDatabasePathLabel.setText(tr("Token database:"));
deckSyncGroupBox->setTitle(tr("Deck sync"));
conflictDefaultLabel.setText(tr("When a deck changed on both sides:"));
conflictDefaultSelector.setItemText(conflictDefaultSelector.findData(DeckSyncConflictAsk), tr("Ask me every time"));
conflictDefaultSelector.setItemText(conflictDefaultSelector.findData(DeckSyncConflictKeepMine), tr("Keep my deck"));
conflictDefaultSelector.setItemText(conflictDefaultSelector.findData(DeckSyncConflictKeepServer),
tr("Keep the server deck"));
conflictDefaultSelector.setItemText(conflictDefaultSelector.findData(DeckSyncConflictKeepNewer),
tr("Keep the newer deck"));
autoSyncCheckBox.setText(tr("Sync decks automatically"));
syncIntervalLabel.setText(tr("Check every:"));
syncIntervalEdit.setSuffix(" " + tr("min"));
resetAllPathsButton->setText(tr("Reset all paths"));
}