mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-25 11:56:11 -07:00
[App] Add onboarding wizard (#7064)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
* [App] Add onboarding wizard Took 10 minutes Took 3 minutes Took 7 minutes Took 9 minutes Took 2 minutes Took 1 minute Took 7 minutes * Adjust CI Took 14 minutes Took 56 seconds Took 2 seconds Took 3 seconds * Adjust CI again Took 14 minutes Took 2 seconds * Comments and fixes Took 9 seconds Took 1 minute * Rebase. Took 5 minutes Took 50 seconds Took 15 seconds * Comments. Took 7 minutes * CI lol Took 3 minutes * CI again lol Took 4 minutes * Drop some settings, add some new ones. Took 19 minutes * Resize when expanding section Took 4 minutes Took 3 minutes Took 7 minutes --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
parent
815c5987b4
commit
2b7b4e8168
45 changed files with 3207 additions and 336 deletions
|
|
@ -0,0 +1,56 @@
|
|||
#include "account_setup_page.h"
|
||||
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
AccountSetupPage::AccountSetupPage(QWidget *parent) : FirstRunWizardPage(parent)
|
||||
{
|
||||
bodyLabel = new QLabel(this);
|
||||
bodyLabel->setWordWrap(true);
|
||||
bodyLabel->setAlignment(Qt::AlignCenter);
|
||||
|
||||
registerButton = new QPushButton(this);
|
||||
connectButton = new QPushButton(this);
|
||||
skipHintLabel = new QLabel(this);
|
||||
skipHintLabel->setWordWrap(true);
|
||||
skipHintLabel->setAlignment(Qt::AlignCenter);
|
||||
|
||||
connect(registerButton, &QPushButton::clicked, this, &AccountSetupPage::registerRequested);
|
||||
connect(connectButton, &QPushButton::clicked, this, &AccountSetupPage::connectRequested);
|
||||
|
||||
auto *layout = new QVBoxLayout(this);
|
||||
layout->addStretch();
|
||||
layout->addWidget(bodyLabel);
|
||||
layout->addSpacing(16);
|
||||
layout->addWidget(registerButton, 0, Qt::AlignHCenter);
|
||||
layout->addWidget(connectButton, 0, Qt::AlignHCenter);
|
||||
layout->addSpacing(16);
|
||||
layout->addWidget(skipHintLabel);
|
||||
layout->addStretch();
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
bool AccountSetupPage::isSkippable() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
QString AccountSetupPage::stepTitle() const
|
||||
{
|
||||
return tr("Join a Server");
|
||||
}
|
||||
|
||||
QString AccountSetupPage::stepSubtitle() const
|
||||
{
|
||||
return tr("Optional — you can always do this later from the menu.");
|
||||
}
|
||||
|
||||
void AccountSetupPage::retranslateUi()
|
||||
{
|
||||
bodyLabel->setText(tr("Playing online needs a server account."));
|
||||
registerButton->setText(tr("Register a new account…"));
|
||||
connectButton->setText(tr("I already have one — Connect…"));
|
||||
skipHintLabel->setText(tr("Just want to play locally? Skip this and connect whenever you're ready."));
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
#ifndef ACCOUNT_SETUP_PAGE_H
|
||||
#define ACCOUNT_SETUP_PAGE_H
|
||||
|
||||
#include "../first_run_wizard_page.h"
|
||||
|
||||
class QLabel;
|
||||
class QPushButton;
|
||||
|
||||
/** @brief First-run account step. Does NOT embed DlgRegister's fields: they exist
|
||||
* to be handed to ConnectionController's network registration flow, which
|
||||
* this wizard has no visibility into. Reimplementing the fields here
|
||||
* without that wiring would look functional and silently do nothing --
|
||||
* worse than reuse. So: a friendly landing spot that opens the *existing*
|
||||
* DlgRegister / connect flow via signals FirstRunWizard forwards. */
|
||||
class AccountSetupPage : public FirstRunWizardPage
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit AccountSetupPage(QWidget *parent = nullptr);
|
||||
|
||||
bool isSkippable() const override;
|
||||
QString stepTitle() const override;
|
||||
QString stepSubtitle() const override;
|
||||
void retranslateUi() override;
|
||||
|
||||
signals:
|
||||
void registerRequested();
|
||||
void connectRequested();
|
||||
|
||||
private:
|
||||
QLabel *bodyLabel;
|
||||
QPushButton *registerButton;
|
||||
QPushButton *connectButton;
|
||||
QLabel *skipHintLabel;
|
||||
};
|
||||
|
||||
#endif // ACCOUNT_SETUP_PAGE_H
|
||||
|
|
@ -0,0 +1,314 @@
|
|||
#include "card_database_setup_page.h"
|
||||
|
||||
#include "../../client/settings/cache_settings.h"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QGridLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QMessageBox>
|
||||
#include <QProgressBar>
|
||||
#include <QPushButton>
|
||||
#include <QSettings>
|
||||
#include <QSpinBox>
|
||||
#include <QTimer>
|
||||
#include <QUrl>
|
||||
#include <QVBoxLayout>
|
||||
#include <libcockatrice/card/database/card_database_manager.h>
|
||||
#include <libcockatrice/settings/updates_settings.h>
|
||||
|
||||
CardDatabaseSetupPage::CardDatabaseSetupPage(QWidget *parent) : FirstRunWizardPage(parent)
|
||||
{
|
||||
statusLabel = new QLabel(this);
|
||||
statusLabel->setWordWrap(true);
|
||||
statusLabel->setAlignment(Qt::AlignCenter);
|
||||
|
||||
progressBar = new QProgressBar(this);
|
||||
progressBar->setRange(0, 0);
|
||||
progressBar->setTextVisible(false);
|
||||
progressBar->setFixedWidth(280);
|
||||
|
||||
retryButton = new QPushButton(this);
|
||||
manualButton = new QPushButton(this);
|
||||
|
||||
connect(retryButton, &QPushButton::clicked, this, [this] {
|
||||
setState(State::Running);
|
||||
emit updateRequested();
|
||||
});
|
||||
connect(manualButton, &QPushButton::clicked, this, &CardDatabaseSetupPage::manualSetupRequested);
|
||||
|
||||
// ── Advanced: custom download source ───────────────────────────────
|
||||
advancedToggleButton = new QPushButton(this);
|
||||
advancedToggleButton->setCheckable(true);
|
||||
advancedToggleButton->setChecked(false);
|
||||
advancedToggleButton->setFlat(true);
|
||||
advancedToggleButton->setStyleSheet("QPushButton { text-align: left; padding: 5px 12px; font-weight: bold; }"
|
||||
"QPushButton:checked { }");
|
||||
|
||||
advancedPanel = new QWidget(this);
|
||||
advancedPanel->setVisible(false);
|
||||
|
||||
urlLineEdit = new QLineEdit(advancedPanel);
|
||||
urlHintLabel = new QLabel(advancedPanel);
|
||||
urlHintLabel->setWordWrap(true);
|
||||
|
||||
restoreDefaultUrlButton = new QPushButton(advancedPanel);
|
||||
applyAndRetryButton = new QPushButton(advancedPanel);
|
||||
|
||||
connect(advancedToggleButton, &QPushButton::toggled, this, &CardDatabaseSetupPage::onToggleAdvanced);
|
||||
connect(restoreDefaultUrlButton, &QPushButton::clicked, this, &CardDatabaseSetupPage::onRestoreDefaultUrl);
|
||||
connect(applyAndRetryButton, &QPushButton::clicked, this, &CardDatabaseSetupPage::onApplyCustomUrl);
|
||||
|
||||
auto *advancedButtonRow = new QHBoxLayout;
|
||||
advancedButtonRow->addWidget(restoreDefaultUrlButton);
|
||||
advancedButtonRow->addStretch();
|
||||
advancedButtonRow->addWidget(applyAndRetryButton);
|
||||
|
||||
auto *advancedLayout = new QVBoxLayout(advancedPanel);
|
||||
advancedLayout->setContentsMargins(12, 4, 12, 4);
|
||||
advancedLayout->addWidget(urlLineEdit);
|
||||
advancedLayout->addWidget(urlHintLabel);
|
||||
advancedLayout->addLayout(advancedButtonRow);
|
||||
|
||||
// ── Startup card update check ───────────────────────────────────────
|
||||
auto &upd = SettingsCache::instance().updates();
|
||||
|
||||
const auto updateBehavior = [this] {
|
||||
auto &u = SettingsCache::instance().updates();
|
||||
int idx = startupBehaviorCombo->currentIndex();
|
||||
u.setStartupCardUpdateCheckPromptForUpdate(idx == 1);
|
||||
u.setStartupCardUpdateCheckAlwaysUpdate(idx == 2);
|
||||
};
|
||||
|
||||
startupBehaviorLabel = new QLabel(this);
|
||||
startupBehaviorCombo = new QComboBox(this);
|
||||
startupBehaviorCombo->addItem(QString()); // placeholder, filled in retranslateUi
|
||||
startupBehaviorCombo->addItem(QString());
|
||||
startupBehaviorCombo->addItem(QString());
|
||||
if (upd.getStartupCardUpdateCheckPromptForUpdate()) {
|
||||
startupBehaviorCombo->setCurrentIndex(1);
|
||||
} else if (upd.getStartupCardUpdateCheckAlwaysUpdate()) {
|
||||
startupBehaviorCombo->setCurrentIndex(2);
|
||||
} else {
|
||||
startupBehaviorCombo->setCurrentIndex(0);
|
||||
}
|
||||
connect(startupBehaviorCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, updateBehavior);
|
||||
|
||||
checkIntervalLabel = new QLabel(this);
|
||||
checkIntervalSpinBox = new QSpinBox(this);
|
||||
checkIntervalSpinBox->setMinimum(1);
|
||||
checkIntervalSpinBox->setMaximum(30);
|
||||
checkIntervalSpinBox->setValue(upd.getCardUpdateCheckInterval());
|
||||
connect(checkIntervalSpinBox, QOverload<int>::of(&QSpinBox::valueChanged), &upd,
|
||||
&UpdatesSettings::setCardUpdateCheckInterval);
|
||||
|
||||
auto *checkGrid = new QGridLayout;
|
||||
checkGrid->addWidget(startupBehaviorLabel, 0, 0);
|
||||
checkGrid->addWidget(startupBehaviorCombo, 0, 1);
|
||||
checkGrid->addWidget(checkIntervalLabel, 1, 0);
|
||||
checkGrid->addWidget(checkIntervalSpinBox, 1, 1);
|
||||
|
||||
auto *layout = new QVBoxLayout(this);
|
||||
layout->addStretch();
|
||||
layout->addWidget(statusLabel);
|
||||
layout->addSpacing(12);
|
||||
layout->addWidget(progressBar, 0, Qt::AlignHCenter);
|
||||
layout->addSpacing(12);
|
||||
layout->addWidget(retryButton, 0, Qt::AlignHCenter);
|
||||
layout->addWidget(manualButton, 0, Qt::AlignHCenter);
|
||||
layout->addSpacing(16);
|
||||
layout->addWidget(advancedToggleButton);
|
||||
layout->addWidget(advancedPanel);
|
||||
layout->addSpacing(8);
|
||||
layout->addLayout(checkGrid);
|
||||
layout->addStretch();
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
bool CardDatabaseSetupPage::alreadyHaveDatabase() const
|
||||
{
|
||||
return CardDatabaseManager::getInstance()->getCardList().count() > 0;
|
||||
}
|
||||
|
||||
QString CardDatabaseSetupPage::oracleSettingsFilePath() const
|
||||
{
|
||||
return SettingsCache::instance().getSettingsPath() + "oracle.ini";
|
||||
}
|
||||
|
||||
QString CardDatabaseSetupPage::readCustomUrl() const
|
||||
{
|
||||
QSettings oracleSettings(oracleSettingsFilePath(), QSettings::IniFormat);
|
||||
return oracleSettings.value("allsetsurl").toString();
|
||||
}
|
||||
|
||||
void CardDatabaseSetupPage::writeCustomUrl(const QString &url)
|
||||
{
|
||||
QSettings oracleSettings(oracleSettingsFilePath(), QSettings::IniFormat);
|
||||
if (url.isEmpty()) {
|
||||
oracleSettings.remove("allsetsurl");
|
||||
} else {
|
||||
oracleSettings.setValue("allsetsurl", url);
|
||||
}
|
||||
}
|
||||
|
||||
void CardDatabaseSetupPage::initializePage()
|
||||
{
|
||||
urlLineEdit->setText(readCustomUrl());
|
||||
|
||||
if (state != State::NotStarted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (alreadyHaveDatabase()) {
|
||||
setState(State::Succeeded);
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't auto-download — wait for the user to press "Download".
|
||||
setState(State::NotStarted);
|
||||
statusLabel->setText(tr("Press Download to fetch the card database, or Skip to do it later."));
|
||||
}
|
||||
|
||||
void CardDatabaseSetupPage::onUpdateFinished(bool success)
|
||||
{
|
||||
setState(success ? State::Succeeded : State::Failed);
|
||||
if (success) {
|
||||
emit advanceRequested();
|
||||
}
|
||||
}
|
||||
|
||||
QString CardDatabaseSetupPage::nextButtonText() const
|
||||
{
|
||||
return state == State::NotStarted ? tr("Download") : QString();
|
||||
}
|
||||
|
||||
bool CardDatabaseSetupPage::handleNextClick()
|
||||
{
|
||||
if (state == State::NotStarted) {
|
||||
setState(State::Running);
|
||||
emit updateRequested();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CardDatabaseSetupPage::onToggleAdvanced(bool open)
|
||||
{
|
||||
advancedToggleButton->setText(open ? tr("▼ Advanced: custom download source")
|
||||
: tr("▶ Advanced: custom download source"));
|
||||
advancedPanel->setVisible(open);
|
||||
|
||||
QWidget *wizardWindow = window();
|
||||
if (!wizardWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (open) {
|
||||
windowSizeBeforeExpansion = wizardWindow->size();
|
||||
QTimer::singleShot(0, this, [wizardWindow] {
|
||||
wizardWindow->resize(wizardWindow->size().expandedTo(wizardWindow->sizeHint()));
|
||||
});
|
||||
} else {
|
||||
QTimer::singleShot(0, this, [this, wizardWindow] {
|
||||
wizardWindow->resize(wizardWindow->size().boundedTo(windowSizeBeforeExpansion));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void CardDatabaseSetupPage::onApplyCustomUrl()
|
||||
{
|
||||
const QString text = urlLineEdit->text().trimmed();
|
||||
|
||||
if (!text.isEmpty()) {
|
||||
const QUrl url = QUrl::fromUserInput(text);
|
||||
if (!url.isValid()) {
|
||||
QMessageBox::warning(this, tr("Invalid URL"),
|
||||
tr("That doesn't look like a valid URL. Double-check it and try again, "
|
||||
"or clear the field to use the default source."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
writeCustomUrl(text);
|
||||
setState(State::Running);
|
||||
emit updateRequested();
|
||||
}
|
||||
|
||||
void CardDatabaseSetupPage::onRestoreDefaultUrl()
|
||||
{
|
||||
urlLineEdit->clear();
|
||||
writeCustomUrl(QString());
|
||||
}
|
||||
|
||||
void CardDatabaseSetupPage::setState(State newState)
|
||||
{
|
||||
state = newState;
|
||||
|
||||
progressBar->setVisible(state == State::Running);
|
||||
retryButton->setVisible(state == State::Failed);
|
||||
manualButton->setVisible(state == State::Failed);
|
||||
applyAndRetryButton->setEnabled(state != State::Running);
|
||||
|
||||
switch (state) {
|
||||
case State::NotStarted:
|
||||
statusLabel->setText(tr("Press Download to fetch the card database, or Skip to do it later."));
|
||||
break;
|
||||
case State::Running:
|
||||
statusLabel->setText(tr("Downloading the latest card database…"));
|
||||
break;
|
||||
case State::Succeeded:
|
||||
statusLabel->setText(tr("Card database ready ✓"));
|
||||
break;
|
||||
case State::Failed:
|
||||
statusLabel->setText(
|
||||
tr("Couldn't download the card database automatically. Check your connection and retry, "
|
||||
"set it up manually, or skip this for now — you can do it later from the Card Database menu."));
|
||||
break;
|
||||
}
|
||||
|
||||
emit completeChanged();
|
||||
}
|
||||
|
||||
bool CardDatabaseSetupPage::isComplete() const
|
||||
{
|
||||
return state != State::Running;
|
||||
}
|
||||
|
||||
bool CardDatabaseSetupPage::isSkippable() const
|
||||
{
|
||||
return state != State::Succeeded;
|
||||
}
|
||||
|
||||
QString CardDatabaseSetupPage::stepTitle() const
|
||||
{
|
||||
return tr("Card Database");
|
||||
}
|
||||
|
||||
QString CardDatabaseSetupPage::stepSubtitle() const
|
||||
{
|
||||
return tr("Cockatrice needs card data to know what you're playing with.");
|
||||
}
|
||||
|
||||
void CardDatabaseSetupPage::retranslateUi()
|
||||
{
|
||||
retryButton->setText(tr("Retry"));
|
||||
manualButton->setText(tr("Set up manually…"));
|
||||
|
||||
onToggleAdvanced(advancedToggleButton->isChecked());
|
||||
urlLineEdit->setPlaceholderText(tr("Leave blank to use the default source"));
|
||||
urlHintLabel->setText(tr("Only change this if you know you need a mirror or a custom card data source."));
|
||||
restoreDefaultUrlButton->setText(tr("Restore default"));
|
||||
applyAndRetryButton->setText(tr("Apply && retry"));
|
||||
|
||||
startupBehaviorLabel->setText(tr("Check for card database updates on startup"));
|
||||
startupBehaviorCombo->setItemText(0, tr("Don't check"));
|
||||
startupBehaviorCombo->setItemText(1, tr("Prompt for update"));
|
||||
startupBehaviorCombo->setItemText(2, tr("Always update in the background"));
|
||||
|
||||
checkIntervalLabel->setText(tr("Check for card database updates every"));
|
||||
checkIntervalSpinBox->setSuffix(tr(" days"));
|
||||
|
||||
setState(state);
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
#ifndef CARD_DATABASE_SETUP_PAGE_H
|
||||
#define CARD_DATABASE_SETUP_PAGE_H
|
||||
|
||||
#include "../first_run_wizard_page.h"
|
||||
|
||||
#include <QSize>
|
||||
|
||||
class QComboBox;
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
class QProgressBar;
|
||||
class QPushButton;
|
||||
class QSpinBox;
|
||||
class QWidget;
|
||||
|
||||
class CardDatabaseSetupPage : public FirstRunWizardPage
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CardDatabaseSetupPage(QWidget *parent = nullptr);
|
||||
|
||||
void initializePage() override;
|
||||
bool isComplete() const override;
|
||||
bool isSkippable() const override;
|
||||
QString stepTitle() const override;
|
||||
QString stepSubtitle() const override;
|
||||
QString nextButtonText() const override;
|
||||
bool handleNextClick() override;
|
||||
void retranslateUi() override;
|
||||
|
||||
void onUpdateFinished(bool success);
|
||||
|
||||
signals:
|
||||
void updateRequested();
|
||||
void manualSetupRequested();
|
||||
|
||||
private:
|
||||
enum class State
|
||||
{
|
||||
NotStarted,
|
||||
Running,
|
||||
Succeeded,
|
||||
Failed,
|
||||
};
|
||||
|
||||
void setState(State newState);
|
||||
bool alreadyHaveDatabase() const;
|
||||
|
||||
QString oracleSettingsFilePath() const;
|
||||
QString readCustomUrl() const;
|
||||
void writeCustomUrl(const QString &url);
|
||||
|
||||
void onToggleAdvanced(bool open);
|
||||
void onApplyCustomUrl();
|
||||
void onRestoreDefaultUrl();
|
||||
|
||||
QLabel *statusLabel;
|
||||
QProgressBar *progressBar;
|
||||
QPushButton *retryButton;
|
||||
QPushButton *manualButton;
|
||||
|
||||
QPushButton *advancedToggleButton;
|
||||
QWidget *advancedPanel;
|
||||
QLineEdit *urlLineEdit;
|
||||
QLabel *urlHintLabel;
|
||||
QPushButton *restoreDefaultUrlButton;
|
||||
QPushButton *applyAndRetryButton;
|
||||
|
||||
QLabel *startupBehaviorLabel;
|
||||
QComboBox *startupBehaviorCombo;
|
||||
QLabel *checkIntervalLabel;
|
||||
QSpinBox *checkIntervalSpinBox;
|
||||
|
||||
State state = State::NotStarted;
|
||||
QSize windowSizeBeforeExpansion;
|
||||
};
|
||||
|
||||
#endif // CARD_DATABASE_SETUP_PAGE_H
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
#include "finish_page.h"
|
||||
|
||||
#include <QLabel>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
FinishPage::FinishPage(QWidget *parent) : FirstRunWizardPage(parent)
|
||||
{
|
||||
bodyLabel = new QLabel(this);
|
||||
bodyLabel->setWordWrap(true);
|
||||
bodyLabel->setAlignment(Qt::AlignCenter);
|
||||
|
||||
auto *layout = new QVBoxLayout(this);
|
||||
layout->addStretch();
|
||||
layout->addWidget(bodyLabel);
|
||||
layout->addStretch();
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
QString FinishPage::stepTitle() const
|
||||
{
|
||||
return tr("You're All Set");
|
||||
}
|
||||
|
||||
void FinishPage::retranslateUi()
|
||||
{
|
||||
bodyLabel->setText(
|
||||
tr("That's everything for now. Jump into Settings any time to change your mind about any of this.\n\n"
|
||||
"Have fun!"));
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
#ifndef FINISH_PAGE_H
|
||||
#define FINISH_PAGE_H
|
||||
|
||||
#include "../first_run_wizard_page.h"
|
||||
|
||||
class QLabel;
|
||||
|
||||
class FinishPage : public FirstRunWizardPage
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit FinishPage(QWidget *parent = nullptr);
|
||||
|
||||
QString stepTitle() const override;
|
||||
void retranslateUi() override;
|
||||
|
||||
private:
|
||||
QLabel *bodyLabel;
|
||||
};
|
||||
|
||||
#endif // FINISH_PAGE_H
|
||||
|
|
@ -0,0 +1,173 @@
|
|||
#include "preferences_setup_page.h"
|
||||
|
||||
#include "../../client/settings/cache_settings.h"
|
||||
#include "../../client/sound_engine.h"
|
||||
#include "libcockatrice/settings/interface_settings.h"
|
||||
#include "libcockatrice/settings/sound_settings.h"
|
||||
#include "libcockatrice/settings/tabs_settings.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QFormLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QLabel>
|
||||
#include <QScrollArea>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
namespace
|
||||
{
|
||||
// The server destinations are omitted: during first run their tabs are not
|
||||
// open yet, and the wizard offers no way to fill in the server/room details.
|
||||
QList<StartupTab> wizardStartupTabOrder()
|
||||
{
|
||||
return {StartupTabHome, StartupTabVisualDeckStorage, StartupTabDeckStorage,
|
||||
StartupTabReplays, StartupTabDeckEditor, StartupTabVisualDeckEditor};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
PreferencesSetupPage::PreferencesSetupPage(QWidget *parent) : FirstRunWizardPage(parent)
|
||||
{
|
||||
auto *content = new QWidget;
|
||||
auto *contentLayout = new QVBoxLayout(content);
|
||||
|
||||
gameplayGroup = new QGroupBox(content);
|
||||
auto *gameplayLayout = new QVBoxLayout(gameplayGroup);
|
||||
contentLayout->addWidget(gameplayGroup);
|
||||
doubleClickToPlayCheckBox = new QCheckBox(gameplayGroup);
|
||||
horizontalHandCheckBox = new QCheckBox(gameplayGroup);
|
||||
playToStackCheckBox = new QCheckBox(gameplayGroup);
|
||||
gameplayLayout->addWidget(doubleClickToPlayCheckBox);
|
||||
gameplayLayout->addWidget(horizontalHandCheckBox);
|
||||
gameplayLayout->addWidget(playToStackCheckBox);
|
||||
|
||||
notificationsGroup = new QGroupBox(content);
|
||||
auto *notificationsLayout = new QVBoxLayout(notificationsGroup);
|
||||
contentLayout->addWidget(notificationsGroup);
|
||||
notificationsEnabledCheckBox = new QCheckBox(notificationsGroup);
|
||||
soundEnabledCheckBox = new QCheckBox(notificationsGroup);
|
||||
notificationsLayout->addWidget(notificationsEnabledCheckBox);
|
||||
notificationsLayout->addWidget(soundEnabledCheckBox);
|
||||
|
||||
startupGroup = new QGroupBox(content);
|
||||
auto *startupForm = new QFormLayout(startupGroup);
|
||||
contentLayout->addWidget(startupGroup);
|
||||
startupTabLabel = new QLabel(startupGroup);
|
||||
startupTabSelector = new QComboBox(startupGroup);
|
||||
startupTabSelector->setSizeAdjustPolicy(QComboBox::AdjustToContents);
|
||||
for (StartupTab tab : wizardStartupTabOrder()) {
|
||||
startupTabSelector->addItem(QString(), tab); // texts set in retranslateUi
|
||||
}
|
||||
startupForm->addRow(startupTabLabel, startupTabSelector);
|
||||
|
||||
contentLayout->addStretch();
|
||||
|
||||
auto *scrollArea = new QScrollArea(this);
|
||||
scrollArea->setWidget(content);
|
||||
scrollArea->setWidgetResizable(true);
|
||||
scrollArea->setFrameShape(QFrame::NoFrame);
|
||||
|
||||
auto *layout = new QVBoxLayout(this);
|
||||
layout->addWidget(scrollArea);
|
||||
|
||||
SettingsCache &settings = SettingsCache::instance();
|
||||
|
||||
connect(doubleClickToPlayCheckBox, &QCheckBox::toggled, &settings.userInterface(),
|
||||
&InterfaceSettings::setDoubleClickToPlay);
|
||||
connect(horizontalHandCheckBox, &QCheckBox::toggled, &settings.userInterface(),
|
||||
&InterfaceSettings::setHorizontalHand);
|
||||
connect(playToStackCheckBox, &QCheckBox::toggled, &settings.userInterface(), &InterfaceSettings::setPlayToStack);
|
||||
|
||||
connect(notificationsEnabledCheckBox, &QCheckBox::toggled, &settings.userInterface(),
|
||||
&InterfaceSettings::setNotificationsEnabled);
|
||||
connect(soundEnabledCheckBox, &QCheckBox::toggled, &settings.sound(), &SoundSettings::setSoundEnabled);
|
||||
connect(soundEnabledCheckBox, &QCheckBox::toggled, soundEngine, &SoundEngine::testSound);
|
||||
|
||||
connect(startupTabSelector, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index) {
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
SettingsCache::instance().tabs().setStartupTabIndex(startupTabSelector->itemData(index).toInt());
|
||||
});
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void PreferencesSetupPage::initializePage()
|
||||
{
|
||||
SettingsCache &settings = SettingsCache::instance();
|
||||
|
||||
doubleClickToPlayCheckBox->setChecked(settings.userInterface().getDoubleClickToPlay());
|
||||
horizontalHandCheckBox->setChecked(settings.userInterface().getHorizontalHand());
|
||||
playToStackCheckBox->setChecked(settings.userInterface().getPlayToStack());
|
||||
|
||||
notificationsEnabledCheckBox->setChecked(settings.userInterface().getNotificationsEnabled());
|
||||
soundEnabledCheckBox->setChecked(settings.sound().getSoundEnabled());
|
||||
|
||||
startupTabSelector->setCurrentIndex(startupTabSelector->findData(settings.tabs().getStartupTabIndex()));
|
||||
}
|
||||
|
||||
bool PreferencesSetupPage::isSkippable() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
QString PreferencesSetupPage::stepTitle() const
|
||||
{
|
||||
return tr("A Few Preferences");
|
||||
}
|
||||
|
||||
QString PreferencesSetupPage::stepSubtitle() const
|
||||
{
|
||||
return tr("Defaults are fine — tweak these now or from Settings anytime.");
|
||||
}
|
||||
|
||||
void PreferencesSetupPage::retranslateUi()
|
||||
{
|
||||
gameplayGroup->setTitle(tr("Gameplay"));
|
||||
doubleClickToPlayCheckBox->setText(tr("Double-click cards to play them"));
|
||||
doubleClickToPlayCheckBox->setToolTip(tr("When disabled, a single click plays the selected card onto the table."));
|
||||
horizontalHandCheckBox->setText(tr("Display hand horizontally"));
|
||||
horizontalHandCheckBox->setToolTip(
|
||||
tr("Shows your hand as a row along the bottom of the table instead of a column beside it."));
|
||||
playToStackCheckBox->setText(tr("Play all nonlands onto the stack by default"));
|
||||
playToStackCheckBox->setToolTip(
|
||||
tr("Cards you play appear on the stack so other players can respond to them, as in a tabletop game."));
|
||||
|
||||
notificationsGroup->setTitle(tr("Notifications && Sound"));
|
||||
notificationsEnabledCheckBox->setText(tr("Show desktop notifications"));
|
||||
soundEnabledCheckBox->setText(tr("Play sound effects"));
|
||||
|
||||
startupGroup->setTitle(tr("Startup"));
|
||||
startupTabLabel->setText(tr("Startup tab:"));
|
||||
const QList<StartupTab> tabs = wizardStartupTabOrder();
|
||||
for (int i = 0; i < tabs.size(); ++i) {
|
||||
QString name;
|
||||
switch (tabs[i]) {
|
||||
case StartupTabHome:
|
||||
name = tr("Home");
|
||||
break;
|
||||
case StartupTabVisualDeckStorage:
|
||||
name = tr("Visual Deck Storage");
|
||||
break;
|
||||
case StartupTabDeckStorage:
|
||||
name = tr("Deck Storage");
|
||||
break;
|
||||
case StartupTabReplays:
|
||||
name = tr("Game Replays");
|
||||
break;
|
||||
case StartupTabDeckEditor:
|
||||
name = tr("Deck Editor");
|
||||
break;
|
||||
case StartupTabVisualDeckEditor:
|
||||
name = tr("Visual Deck Editor");
|
||||
break;
|
||||
case StartupTabServer:
|
||||
name = tr("Server");
|
||||
break;
|
||||
case StartupTabServerRoom:
|
||||
name = tr("Server Room");
|
||||
break;
|
||||
}
|
||||
startupTabSelector->setItemText(i, name);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
#ifndef PREFERENCES_SETUP_PAGE_H
|
||||
#define PREFERENCES_SETUP_PAGE_H
|
||||
|
||||
#include "../first_run_wizard_page.h"
|
||||
|
||||
class QCheckBox;
|
||||
class QComboBox;
|
||||
class QGroupBox;
|
||||
class QLabel;
|
||||
|
||||
/** @brief A curated subset of settings for the user to adjust.
|
||||
**/
|
||||
class PreferencesSetupPage : public FirstRunWizardPage
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PreferencesSetupPage(QWidget *parent = nullptr);
|
||||
|
||||
void initializePage() override;
|
||||
bool isSkippable() const override;
|
||||
QString stepTitle() const override;
|
||||
QString stepSubtitle() const override;
|
||||
void retranslateUi() override;
|
||||
|
||||
private:
|
||||
QGroupBox *gameplayGroup;
|
||||
QCheckBox *doubleClickToPlayCheckBox;
|
||||
QCheckBox *horizontalHandCheckBox;
|
||||
QCheckBox *playToStackCheckBox;
|
||||
|
||||
QGroupBox *notificationsGroup;
|
||||
QCheckBox *notificationsEnabledCheckBox;
|
||||
QCheckBox *soundEnabledCheckBox;
|
||||
|
||||
QGroupBox *startupGroup;
|
||||
QLabel *startupTabLabel;
|
||||
QComboBox *startupTabSelector;
|
||||
};
|
||||
|
||||
#endif // PREFERENCES_SETUP_PAGE_H
|
||||
|
|
@ -0,0 +1,231 @@
|
|||
#include "theme_setup_page.h"
|
||||
|
||||
#include "../../client/settings/cache_settings.h"
|
||||
#include "../../interface/palette_editor/palette_generator.h"
|
||||
#include "../../interface/palette_editor/quick_setup_panel.h"
|
||||
#include "../../interface/theme_manager.h"
|
||||
#include "../../interface/widgets/general/background_sources.h"
|
||||
#include "libcockatrice/settings/appearance_settings.h"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFormLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QVBoxLayout>
|
||||
#include <libcockatrice/settings/paths_settings.h>
|
||||
#include <libcockatrice/settings/personal_settings.h>
|
||||
|
||||
ThemeSetupPage::ThemeSetupPage(QWidget *parent) : FirstRunWizardPage(parent)
|
||||
{
|
||||
themeCombo = new QComboBox(this);
|
||||
schemeCombo = new QComboBox(this);
|
||||
schemeCombo->addItem(tr("Light"), QStringLiteral("Light"));
|
||||
schemeCombo->addItem(tr("Dark"), QStringLiteral("Dark"));
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
||||
schemeCombo->addItem(tr("Match system"), QStringLiteral("System"));
|
||||
#endif
|
||||
|
||||
quickSetupPanel = new QuickSetupPanel(this);
|
||||
|
||||
connect(themeCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ThemeSetupPage::onThemeChanged);
|
||||
connect(schemeCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ThemeSetupPage::onSchemeChanged);
|
||||
connect(quickSetupPanel, &QuickSetupPanel::valueChanged, this, &ThemeSetupPage::onGenerateFromAccent);
|
||||
|
||||
homeTabBackgroundCombo = new QComboBox(this);
|
||||
for (const auto &entry : BackgroundSources::all()) {
|
||||
homeTabBackgroundCombo->addItem(QObject::tr(entry.trKey), QVariant::fromValue(entry.type));
|
||||
}
|
||||
connect(homeTabBackgroundCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
||||
&ThemeSetupPage::onHomeTabBackgroundChanged);
|
||||
|
||||
// Keep the scheme combo honest when the *theme* changes underneath it
|
||||
// (switching theme reloads that theme's own stored colorScheme), and
|
||||
// opportunistically seed a palette for themes that ship none at all.
|
||||
// Mirrors AppearanceSettingsPage's identical listener for the combo-sync
|
||||
// half of this.
|
||||
connect(themeManager, &ThemeManager::themeChanged, this, [this] {
|
||||
const QString newDir = themeManager->getAvailableThemes().value(SettingsCache::instance().getThemeName());
|
||||
const ThemeConfig cfg = ThemeConfig::fromThemeDir(newDir);
|
||||
const QString current = cfg.colorScheme;
|
||||
|
||||
schemeCombo->blockSignals(true);
|
||||
const int idx = schemeCombo->findData(current);
|
||||
schemeCombo->setCurrentIndex(idx >= 0 ? idx : 0);
|
||||
schemeCombo->blockSignals(false);
|
||||
|
||||
maybeAutoGeneratePalette();
|
||||
});
|
||||
|
||||
auto *form = new QFormLayout;
|
||||
form->addRow(tr("Theme:"), themeCombo);
|
||||
form->addRow(tr("Appearance:"), schemeCombo);
|
||||
form->addRow(tr("Home screen background:"), homeTabBackgroundCombo);
|
||||
|
||||
accentGroup = new QGroupBox(this);
|
||||
auto *accentLayout = new QVBoxLayout(accentGroup);
|
||||
accentLayout->addWidget(quickSetupPanel);
|
||||
|
||||
auto *layout = new QVBoxLayout(this);
|
||||
layout->addLayout(form);
|
||||
layout->addWidget(accentGroup);
|
||||
layout->addStretch();
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void ThemeSetupPage::initializePage()
|
||||
{
|
||||
themeCombo->blockSignals(true);
|
||||
themeCombo->clear();
|
||||
const QString currentTheme = SettingsCache::instance().getThemeName();
|
||||
for (const QString &name : themeManager->getAvailableThemes().keys()) {
|
||||
themeCombo->addItem(name);
|
||||
}
|
||||
const int idx = themeCombo->findText(currentTheme);
|
||||
themeCombo->setCurrentIndex(idx >= 0 ? idx : 0);
|
||||
themeCombo->blockSignals(false);
|
||||
|
||||
const QString dirPath = themeManager->getAvailableThemes().value(SettingsCache::instance().getThemeName());
|
||||
const ThemeConfig cfg = ThemeConfig::fromThemeDir(dirPath);
|
||||
schemeCombo->blockSignals(true);
|
||||
const int schemeIdx = schemeCombo->findData(cfg.colorScheme);
|
||||
schemeCombo->setCurrentIndex(schemeIdx >= 0 ? schemeIdx : 0);
|
||||
schemeCombo->blockSignals(false);
|
||||
|
||||
homeTabBackgroundCombo->blockSignals(true);
|
||||
QString homeTabSource = SettingsCache::instance().appearance().getHomeTabBackgroundSource();
|
||||
int homeTabIdx = homeTabBackgroundCombo->findData(BackgroundSources::fromId(homeTabSource));
|
||||
homeTabBackgroundCombo->setCurrentIndex(homeTabIdx >= 0 ? homeTabIdx : 0);
|
||||
homeTabBackgroundCombo->blockSignals(false);
|
||||
|
||||
// Opening the page must not touch the running application's palette:
|
||||
// previews and auto-generation only happen in response to the user
|
||||
// actually changing a control, never on mere page visibility.
|
||||
paletteDirty = false;
|
||||
}
|
||||
|
||||
QString ThemeSetupPage::currentScheme() const
|
||||
{
|
||||
return schemeCombo->currentData().toString();
|
||||
}
|
||||
|
||||
QString ThemeSetupPage::resolvedScheme() const
|
||||
{
|
||||
const QString scheme = currentScheme();
|
||||
if (scheme.isEmpty() || scheme == QStringLiteral("System")) {
|
||||
return themeManager->isDarkMode(themeManager->getCurrentThemePath()) ? "Dark" : "Light";
|
||||
}
|
||||
return scheme;
|
||||
}
|
||||
|
||||
void ThemeSetupPage::onThemeChanged(int index)
|
||||
{
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
paletteDirty = false;
|
||||
SettingsCache::instance().setThemeName(themeCombo->itemText(index));
|
||||
// Scheme-combo sync and auto-generation both happen via the
|
||||
// ThemeManager::themeChanged listener above, triggered by setThemeName.
|
||||
}
|
||||
|
||||
void ThemeSetupPage::onSchemeChanged()
|
||||
{
|
||||
themeManager->setColorScheme(currentScheme());
|
||||
}
|
||||
|
||||
void ThemeSetupPage::onHomeTabBackgroundChanged(int index)
|
||||
{
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
auto type = homeTabBackgroundCombo->currentData().value<BackgroundSources::Type>();
|
||||
SettingsCache::instance().appearance().setHomeTabBackgroundSource(BackgroundSources::toId(type));
|
||||
}
|
||||
|
||||
void ThemeSetupPage::onGenerateFromAccent(const QColor &accent, int intensity)
|
||||
{
|
||||
PaletteConfig cfg = PaletteGenerator::fromAccent(accent, intensity, resolvedScheme());
|
||||
themeManager->previewPalette(cfg, resolvedScheme());
|
||||
paletteDirty = true;
|
||||
}
|
||||
|
||||
void ThemeSetupPage::maybeAutoGeneratePalette()
|
||||
{
|
||||
const QString dirPath = themeManager->getAvailableThemes().value(SettingsCache::instance().getThemeName());
|
||||
const QString scheme = resolvedScheme();
|
||||
|
||||
if (PaletteConfig::fromScheme(dirPath, scheme).hasPalette() ||
|
||||
PaletteConfig::fromDefault(dirPath, scheme).hasPalette()) {
|
||||
return; // theme already has something real to show -- leave it alone
|
||||
}
|
||||
|
||||
// The theme+scheme combination has nothing saved and nothing shipped, and
|
||||
// the user just switched to it. Rather than leaving a flat, unstyled look,
|
||||
// seed one from whatever accent QuickSetupPanel currently holds and mark
|
||||
// it dirty so it's written to disk if the user moves on. Only ever reached
|
||||
// through user interaction (theme/scheme change, accent drag) -- never on
|
||||
// page open.
|
||||
PaletteConfig generated =
|
||||
PaletteGenerator::fromAccent(quickSetupPanel->accentColor(), quickSetupPanel->intensity(), scheme);
|
||||
themeManager->previewPalette(generated, scheme);
|
||||
paletteDirty = true;
|
||||
}
|
||||
|
||||
bool ThemeSetupPage::validatePage()
|
||||
{
|
||||
if (paletteDirty) {
|
||||
const QString scheme = resolvedScheme();
|
||||
PaletteConfig cfg =
|
||||
PaletteGenerator::fromAccent(quickSetupPanel->accentColor(), quickSetupPanel->intensity(), scheme);
|
||||
if (!ThemeManager::commitPalette(writableThemeDir(), scheme, cfg)) {
|
||||
QMessageBox::warning(this, tr("Save failed"),
|
||||
tr("Could not write the theme palette to:\n%1").arg(writableThemeDir()));
|
||||
return false;
|
||||
}
|
||||
themeManager->reloadCurrentTheme();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QString ThemeSetupPage::writableThemeDir() const
|
||||
{
|
||||
// Built-in themes resolve to the read-only system themes directory;
|
||||
// palette edits must go to the user themes directory instead, exactly
|
||||
// as PaletteEditorDialog does.
|
||||
const QString dirPath = themeManager->getAvailableThemes().value(SettingsCache::instance().getThemeName());
|
||||
if (!dirPath.isEmpty()) {
|
||||
const QString probe = QDir(dirPath).absoluteFilePath(".cockatrice_write_test");
|
||||
QFile f(probe);
|
||||
if (f.open(QIODevice::WriteOnly)) {
|
||||
f.close();
|
||||
f.remove();
|
||||
return dirPath;
|
||||
}
|
||||
}
|
||||
return QDir(SettingsCache::instance().paths().getThemesPath())
|
||||
.absoluteFilePath(SettingsCache::instance().getThemeName());
|
||||
}
|
||||
|
||||
bool ThemeSetupPage::isSkippable() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
QString ThemeSetupPage::stepTitle() const
|
||||
{
|
||||
return tr("Pick a Look");
|
||||
}
|
||||
|
||||
QString ThemeSetupPage::stepSubtitle() const
|
||||
{
|
||||
return tr("You can fine-tune every colour later from Settings → Appearance.");
|
||||
}
|
||||
|
||||
void ThemeSetupPage::retranslateUi()
|
||||
{
|
||||
accentGroup->setTitle(tr("Accent colour (optional)"));
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
#ifndef THEME_SETUP_PAGE_H
|
||||
#define THEME_SETUP_PAGE_H
|
||||
|
||||
#include "../first_run_wizard_page.h"
|
||||
|
||||
class QComboBox;
|
||||
class QGroupBox;
|
||||
class QuickSetupPanel;
|
||||
|
||||
/** @brief First-run theme step. Reuses the same building blocks as Appearance
|
||||
* settings and the Palette Editor (ThemeManager, PaletteConfig,
|
||||
* PaletteGenerator, and the QuickSetupPanel widget itself) rather than
|
||||
* reimplementing palette generation or preview here.
|
||||
*
|
||||
* Behavior specific to this page (deliberately not pushed down into
|
||||
* ThemeManager, to avoid changing app-wide behaviour for existing installs):
|
||||
* - Opening the page never changes the running palette; previews and
|
||||
* auto-generation only happen when the user actually changes a control.
|
||||
* - If a theme+scheme the user selects has no saved palette and no shipped
|
||||
* default, one is generated from the QuickSetupPanel's current accent so
|
||||
* the preview doesn't fall back to a flat, unstyled look. */
|
||||
class ThemeSetupPage : public FirstRunWizardPage
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ThemeSetupPage(QWidget *parent = nullptr);
|
||||
|
||||
void initializePage() override;
|
||||
bool validatePage() override;
|
||||
bool isSkippable() const override;
|
||||
QString stepTitle() const override;
|
||||
QString stepSubtitle() const override;
|
||||
void retranslateUi() override;
|
||||
|
||||
private slots:
|
||||
void onThemeChanged(int index);
|
||||
void onSchemeChanged();
|
||||
void onGenerateFromAccent(const QColor &accent, int intensity);
|
||||
void onHomeTabBackgroundChanged(int index);
|
||||
|
||||
private:
|
||||
QString currentScheme() const;
|
||||
QString resolvedScheme() const; // "System" -> actual Light/Dark
|
||||
void maybeAutoGeneratePalette();
|
||||
QString writableThemeDir() const;
|
||||
|
||||
QComboBox *themeCombo;
|
||||
QComboBox *schemeCombo;
|
||||
QGroupBox *accentGroup;
|
||||
QuickSetupPanel *quickSetupPanel;
|
||||
|
||||
QComboBox *homeTabBackgroundCombo;
|
||||
|
||||
bool paletteDirty = false;
|
||||
};
|
||||
|
||||
#endif // THEME_SETUP_PAGE_H
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
#include "welcome_page.h"
|
||||
|
||||
#include "../../../../main.h"
|
||||
#include "../../client/settings/cache_settings.h"
|
||||
#include "../../settings_page/general_settings_page.h"
|
||||
#include "libcockatrice/settings/personal_settings.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QComboBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QLocale>
|
||||
#include <QTranslator>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
WelcomePage::WelcomePage(QWidget *parent) : FirstRunWizardPage(parent)
|
||||
{
|
||||
bodyLabel = new QLabel(this);
|
||||
bodyLabel->setWordWrap(true);
|
||||
bodyLabel->setAlignment(Qt::AlignCenter);
|
||||
|
||||
languageLabel = new QLabel(this);
|
||||
langCombo = new QComboBox(this);
|
||||
for (const QString &code : GeneralSettingsPage::findQmFiles()) {
|
||||
langCombo->addItem(GeneralSettingsPage::languageName(code), code);
|
||||
}
|
||||
|
||||
QString current = SettingsCache::instance().personal().getLang();
|
||||
if (current.isEmpty()) {
|
||||
current = QLocale::system().name();
|
||||
}
|
||||
int index = langCombo->findData(current);
|
||||
if (index < 0) {
|
||||
index = langCombo->findData(current.section('_', 0, 0));
|
||||
}
|
||||
if (index >= 0) {
|
||||
langCombo->setCurrentIndex(index);
|
||||
}
|
||||
|
||||
connect(langCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &WelcomePage::languageChanged);
|
||||
|
||||
auto *languageRow = new QHBoxLayout;
|
||||
languageRow->addStretch();
|
||||
languageRow->addWidget(languageLabel);
|
||||
languageRow->addWidget(langCombo);
|
||||
languageRow->addStretch();
|
||||
|
||||
auto *layout = new QVBoxLayout(this);
|
||||
layout->addStretch();
|
||||
layout->addWidget(bodyLabel);
|
||||
layout->addStretch();
|
||||
layout->addLayout(languageRow);
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void WelcomePage::languageChanged(int index)
|
||||
{
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
SettingsCache::instance().personal().setLang(langCombo->itemData(index).toString());
|
||||
qApp->removeTranslator(translator); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast)
|
||||
installNewTranslator();
|
||||
}
|
||||
|
||||
QString WelcomePage::stepTitle() const
|
||||
{
|
||||
return tr("Welcome!");
|
||||
}
|
||||
|
||||
void WelcomePage::retranslateUi()
|
||||
{
|
||||
bodyLabel->setText(tr("Let's get you set up. This will only take a minute — "
|
||||
"we'll grab the card database, pick a look you like, "
|
||||
"and get you ready to connect to a server.\n\n"
|
||||
"You can change any of this later from Settings."));
|
||||
languageLabel->setText(tr("Language:"));
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
#ifndef WELCOME_PAGE_H
|
||||
#define WELCOME_PAGE_H
|
||||
|
||||
#include "../first_run_wizard_page.h"
|
||||
|
||||
class QComboBox;
|
||||
class QLabel;
|
||||
|
||||
class WelcomePage : public FirstRunWizardPage
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit WelcomePage(QWidget *parent = nullptr);
|
||||
|
||||
QString stepTitle() const override;
|
||||
void retranslateUi() override;
|
||||
|
||||
private slots:
|
||||
void languageChanged(int index);
|
||||
|
||||
private:
|
||||
QLabel *bodyLabel;
|
||||
QLabel *languageLabel;
|
||||
QComboBox *langCombo;
|
||||
};
|
||||
|
||||
#endif // WELCOME_PAGE_H
|
||||
Loading…
Add table
Add a link
Reference in a new issue