diff --git a/cockatrice/src/client/settings/cache_settings.cpp b/cockatrice/src/client/settings/cache_settings.cpp index 49af0b5f5..8e5e0878e 100644 --- a/cockatrice/src/client/settings/cache_settings.cpp +++ b/cockatrice/src/client/settings/cache_settings.cpp @@ -169,10 +169,28 @@ SettingsCache::SettingsCache() cardsDisplaySettings = new CardsDisplaySettings(settingsPath, this); interfaceSettings = new InterfaceSettings(settingsPath, this); if (hadExistingSettings) { + // Installs that predate the tutorial have no tutorial keys at all. Seed each + // per-tutorial key from the legacy flag, but never overwrite a key that is already + // there. Fresh installs keep the false defaults so every tutorial runs. QSettings iface(settingsPath + "interface.ini", QSettings::IniFormat); - if (!iface.contains("interface/tutorialCompleted")) { + const bool hasLegacyCompleted = iface.contains("interface/tutorialCompleted"); + const bool hasHomeCompleted = iface.contains("interface/tutorialHomeCompleted"); + const bool hasDeckEditorCompleted = iface.contains("interface/tutorialDeckEditorCompleted"); + const bool hasGameCompleted = iface.contains("interface/tutorialGameCompleted"); + if (!hasLegacyCompleted) { + // No legacy key means an install from before the tutorial existed: treat it as done. interfaceSettings->setTutorialCompleted(true); } + const bool legacyCompleted = interfaceSettings->getTutorialCompleted(); + if (!hasHomeCompleted) { + interfaceSettings->setHomeTutorialCompleted(legacyCompleted); + } + if (!hasDeckEditorCompleted) { + interfaceSettings->setDeckEditorTutorialCompleted(legacyCompleted); + } + if (!hasGameCompleted) { + interfaceSettings->setGameTutorialCompleted(legacyCompleted); + } } deckEditorSettings = new DeckEditorSettings(settingsPath, this); pathsSettings = new PathsSettings(settingsPath, this); diff --git a/cockatrice/src/interface/widgets/general/home_widget.cpp b/cockatrice/src/interface/widgets/general/home_widget.cpp index 9c46aa039..569911fc6 100644 --- a/cockatrice/src/interface/widgets/general/home_widget.cpp +++ b/cockatrice/src/interface/widgets/general/home_widget.cpp @@ -98,13 +98,13 @@ HomeWidget::HomeWidget(QWidget *parent, TabSupervisor *_tabSupervisor) connect(&SettingsCache::instance().appearance(), &AppearanceSettings::homeTabBackgroundShuffleFrequencyChanged, this, &HomeWidget::onBackgroundShuffleFrequencyChanged); - tutorialController = new TutorialController(this); + tutorialController = new TutorialController(TutorialId::Home, this); auto sequence = TutorialSequence(); - sequence.addStep({connectButton, "Connect to a server to play here!"}); - auto vdeStep = TutorialStep{visualDeckEditorButton, "Create a new deck from cards in the database here!"}; + sequence.addStep({connectButton, tr("Connect to a server to play here!")}); + auto vdeStep = TutorialStep{visualDeckEditorButton, tr("Create a new deck from cards in the database here!")}; vdeStep.requiresInteraction = true; vdeStep.allowClickThrough = true; - vdeStep.validationHint = "Open the deck editor to try it out!"; + vdeStep.validationHint = tr("Open the deck editor to try it out!"); vdeStep.validationTiming = ValidationTiming::OnSignal; vdeStep.autoAdvanceOnValid = true; vdeStep.validator = []() { return true; }; @@ -114,14 +114,14 @@ HomeWidget::HomeWidget(QWidget *parent, TabSupervisor *_tabSupervisor) }; sequence.addStep(vdeStep); - sequence.addStep({visualDeckStorageButton, "Browse the decks in your local collection."}); - sequence.addStep({visualDatabaseDisplayButton, "View the card database here."}); + sequence.addStep({visualDeckStorageButton, tr("Browse the decks in your local collection.")}); + sequence.addStep({visualDatabaseDisplayButton, tr("View the card database here.")}); sequence.addStep( - {edhrecButton, "Browse EDHRec, an external service designed to provide card recommendations for decks."}); - sequence.addStep({archidektButton, "Browse Archidekt, an external service that allows users to store " - "decklists and import them to your local collection."}); - sequence.addStep({replayButton, "View replays of your past games here."}); - sequence.addStep({exitButton, "Exit the application."}); + {edhrecButton, tr("Browse EDHRec, an external service designed to provide card recommendations for decks.")}); + sequence.addStep({archidektButton, tr("Browse Archidekt, an external service that allows users to store " + "decklists and import them to your local collection.")}); + sequence.addStep({replayButton, tr("View replays of your past games here.")}); + sequence.addStep({exitButton, tr("Exit the application.")}); tutorialController->addSequence(sequence); // Lambda is cleaner to read than overloading this @@ -171,7 +171,7 @@ void HomeWidget::retranslateUi() void HomeWidget::showEvent(QShowEvent *event) { QWidget::showEvent(event); - if (!tutorialStarted && !SettingsCache::instance().userInterface().getTutorialCompleted()) { + if (!tutorialStarted && !SettingsCache::instance().userInterface().getHomeTutorialCompleted()) { tutorialStarted = true; // Start on next event loop iteration so everything is fully painted QTimer::singleShot(3, tutorialController, [this] { tutorialController->start(); }); diff --git a/cockatrice/src/interface/widgets/general/tutorial/tutorial_bubble_widget.cpp b/cockatrice/src/interface/widgets/general/tutorial/tutorial_bubble_widget.cpp index 288b5cb24..f936492f4 100644 --- a/cockatrice/src/interface/widgets/general/tutorial/tutorial_bubble_widget.cpp +++ b/cockatrice/src/interface/widgets/general/tutorial/tutorial_bubble_widget.cpp @@ -68,10 +68,10 @@ void BubbleWidget::setText(const QString &text) void BubbleWidget::setProgress(int stepNum, int totalSteps, int overallStep, int overallTotal) { // Per-sequence progress - counterLabel->setText(QString("Step %1 of %2").arg(stepNum).arg(totalSteps)); + counterLabel->setText(tr("Step %1 of %2").arg(stepNum).arg(totalSteps)); // Overall progress across all sequences - progressLabel->setText(QString("(%1 of %2 total)").arg(overallStep).arg(overallTotal)); + progressLabel->setText(tr("(%1 of %2 total)").arg(overallStep).arg(overallTotal)); progressLabel->show(); } diff --git a/cockatrice/src/interface/widgets/general/tutorial/tutorial_controller.cpp b/cockatrice/src/interface/widgets/general/tutorial/tutorial_controller.cpp index 2b50804ee..a2c726cf1 100644 --- a/cockatrice/src/interface/widgets/general/tutorial/tutorial_controller.cpp +++ b/cockatrice/src/interface/widgets/general/tutorial/tutorial_controller.cpp @@ -10,9 +10,21 @@ #include #include -TutorialController::TutorialController(QWidget *_tutorializedWidget) - : QObject(_tutorializedWidget), tutorializedWidget(_tutorializedWidget) +namespace { +/// Every controller that currently exists, across all screens, so the overlay's X can end +/// all of them at once. Only touched from the GUI thread, which creates and destroys them. +QList liveControllers; + +/// True while skipTutorials() closes the controllers, so a nested call is a no-op. +bool skippingTutorials = false; +} // namespace + +TutorialController::TutorialController(TutorialId _tutorialId, QWidget *_tutorializedWidget) + : QObject(_tutorializedWidget), tutorialId(_tutorialId), tutorializedWidget(_tutorializedWidget) +{ + liveControllers.append(this); + tutorialOverlay = new TutorialOverlay(tutorializedWidget->window()); tutorialOverlay->setWindowFlags(tutorialOverlay->windowFlags() | Qt::FramelessWindowHint); @@ -22,12 +34,14 @@ TutorialController::TutorialController(QWidget *_tutorializedWidget) connect(tutorialOverlay, &TutorialOverlay::prevStep, this, &TutorialController::prevStep); connect(tutorialOverlay, &TutorialOverlay::nextSequence, this, &TutorialController::nextSequence); connect(tutorialOverlay, &TutorialOverlay::prevSequence, this, &TutorialController::prevSequence); - connect(tutorialOverlay, &TutorialOverlay::skipTutorial, this, &TutorialController::exitTutorial); + connect(tutorialOverlay, &TutorialOverlay::skipTutorial, this, &TutorialController::skipTutorials); connect(tutorialOverlay, &TutorialOverlay::targetClicked, this, &TutorialController::handleTargetClicked); } TutorialController::~TutorialController() { + liveControllers.removeAll(this); + // The overlay is parented to the top-level window, which outlives the widget // this controller is attached to (e.g. a closed tab), so it must be cleaned // up explicitly when the controller goes away without exitTutorial(). @@ -185,6 +199,9 @@ void TutorialController::prevSequence() void TutorialController::exitTutorial() { + if (tutorialCompleted) { + return; + } cleanupValidationMonitoring(); if (tutorialOverlay) { tutorialOverlay->hide(); @@ -194,10 +211,46 @@ void TutorialController::exitTutorial() currentSequence = -1; currentStep = -1; tutorialCompleted = true; - SettingsCache::instance().userInterface().setTutorialCompleted(true); + + // Only this screen's tutorial is done; the others keep their own state. + auto &iface = SettingsCache::instance().userInterface(); + switch (tutorialId) { + case TutorialId::Home: + iface.setHomeTutorialCompleted(true); + break; + case TutorialId::DeckEditor: + iface.setDeckEditorTutorialCompleted(true); + break; + case TutorialId::Game: + iface.setGameTutorialCompleted(true); + break; + } deleteLater(); } +void TutorialController::skipTutorials() +{ + if (skippingTutorials) { + return; + } + skippingTutorials = true; + + // The X means "I am done with the tutorial", not "close this one window", so every + // screen is marked done — including screens with no live controller right now. + auto &iface = SettingsCache::instance().userInterface(); + iface.setHomeTutorialCompleted(true); + iface.setDeckEditorTutorialCompleted(true); + iface.setGameTutorialCompleted(true); + + // exitTutorial() only schedules deletion, so iterating a copy cannot dangle. + const QList controllers = liveControllers; + for (TutorialController *controller : controllers) { + controller->exitTutorial(); + } + + skippingTutorials = false; +} + void TutorialController::runExitForCurrentStep() { if (currentSequence < 0 || currentSequence >= sequences.size()) { @@ -269,7 +322,7 @@ void TutorialController::showStep() if (!step.customInteractionHint.isEmpty()) { tutorialOverlay->setInteractionHint(step.customInteractionHint); } else if (step.requiresInteraction) { - tutorialOverlay->setInteractionHint("👆 Click the highlighted area to continue"); + tutorialOverlay->setInteractionHint(tr("👆 Click the highlighted area to continue")); } else { tutorialOverlay->setInteractionHint(""); } diff --git a/cockatrice/src/interface/widgets/general/tutorial/tutorial_controller.h b/cockatrice/src/interface/widgets/general/tutorial/tutorial_controller.h index 617a2972d..8aebe79ee 100644 --- a/cockatrice/src/interface/widgets/general/tutorial/tutorial_controller.h +++ b/cockatrice/src/interface/widgets/general/tutorial/tutorial_controller.h @@ -16,6 +16,14 @@ enum class ValidationTiming Manual // Only validate when explicitly triggered }; +/// Which screen a controller belongs to; each tutorial tracks its own completion flag. +enum class TutorialId +{ + Home, + DeckEditor, + Game +}; + class TutorialController; struct TutorialStep @@ -67,7 +75,7 @@ class TutorialController : public QObject Q_OBJECT public: - explicit TutorialController(QWidget *_tutorializedWidget); + TutorialController(TutorialId _tutorialId, QWidget *_tutorializedWidget); ~TutorialController() override; void addSequence(const TutorialSequence &seq); @@ -85,6 +93,7 @@ public slots: void prevSequence(); void runExitForCurrentStep(); void exitTutorial(); + void skipTutorials(); // Close the tutorial on every live screen and mark all three done void handleTargetClicked(); // Handle clicks on highlighted widget void attemptAdvance(); // Try to advance with validation void checkValidation(); // Check validation for OnChange timing @@ -96,6 +105,7 @@ private: void setupValidationMonitoring(); // Setup automatic validation checking void cleanupValidationMonitoring(); // Cleanup validation watchers + TutorialId tutorialId; QWidget *tutorializedWidget; TutorialOverlay *tutorialOverlay; QList sequences; diff --git a/cockatrice/src/interface/widgets/general/tutorial/tutorial_overlay.cpp b/cockatrice/src/interface/widgets/general/tutorial/tutorial_overlay.cpp index 271556fad..8684c5db6 100644 --- a/cockatrice/src/interface/widgets/general/tutorial/tutorial_overlay.cpp +++ b/cockatrice/src/interface/widgets/general/tutorial/tutorial_overlay.cpp @@ -41,7 +41,7 @@ TutorialOverlay::TutorialOverlay(QWidget *parent) : QWidget(parent) QHBoxLayout *barLayout = new QHBoxLayout(controlBar); barLayout->setContentsMargins(8, 4, 8, 4); - titleLabel = new QLabel("Tutorial", controlBar); + titleLabel = new QLabel(tr("Tutorial"), controlBar); titleLabel->setStyleSheet("color:white; font-weight:bold;"); barLayout->addWidget(titleLabel); barLayout->addStretch(); @@ -52,11 +52,11 @@ TutorialOverlay::TutorialOverlay(QWidget *parent) : QWidget(parent) return b; }; - QPushButton *prevSeq = mkBtn("⏮", "Previous chapter"); - QPushButton *prev = mkBtn("◀", "Previous step"); - nextButton = mkBtn("▶", "Next step"); - nextSeqButton = mkBtn("⏭", "Next chapter"); - QPushButton *close = mkBtn("✕", "Exit tutorial"); + QPushButton *prevSeq = mkBtn("⏮", tr("Previous chapter")); + QPushButton *prev = mkBtn("◀", tr("Previous step")); + nextButton = mkBtn("▶", tr("Next step")); + nextSeqButton = mkBtn("⏭", tr("Next chapter")); + QPushButton *close = mkBtn("✕", tr("Exit tutorial")); barLayout->addWidget(prevSeq); barLayout->addWidget(prev); @@ -121,12 +121,12 @@ void TutorialOverlay::setInteractive(bool interactive, bool clickThrough) if (nextButton) { nextButton->setEnabled(!interactive); - nextButton->setToolTip(interactive ? "Complete the highlighted action to continue" : "Next step"); + nextButton->setToolTip(interactive ? tr("Complete the highlighted action to continue") : tr("Next step")); } if (nextSeqButton) { nextSeqButton->setEnabled(!interactive); - nextSeqButton->setToolTip(interactive ? "Complete the highlighted action to continue" : "Next chapter"); + nextSeqButton->setToolTip(interactive ? tr("Complete the highlighted action to continue") : tr("Next chapter")); } updateMask(); diff --git a/cockatrice/src/interface/widgets/tabs/tab_game.cpp b/cockatrice/src/interface/widgets/tabs/tab_game.cpp index 71b7ed673..0d6c49d13 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_game.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_game.cpp @@ -189,7 +189,7 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor, QTimer::singleShot(0, this, &TabGame::loadLayout); - tutorialController = new TutorialController(this); + tutorialController = new TutorialController(TutorialId::Game, this); TutorialSequence lobbySequence; @@ -202,7 +202,8 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor, void TabGame::showEvent(QShowEvent *event) { QWidget::showEvent(event); - if (!tutorialController || tutorialStarted || SettingsCache::instance().userInterface().getTutorialCompleted()) { + if (!tutorialController || tutorialStarted || + SettingsCache::instance().userInterface().getGameTutorialCompleted()) { return; } // Only start once all sequences are registered so advancing can't run past the @@ -223,7 +224,7 @@ void TabGame::finishTutorialInitialization() } // No sequence building (and no widget access) once the tutorial is done. - if (SettingsCache::instance().userInterface().getTutorialCompleted()) { + if (SettingsCache::instance().userInterface().getGameTutorialCompleted()) { return; } diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp index f141f2bd2..155683a06 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp @@ -76,7 +76,7 @@ TabDeckEditorVisual::TabDeckEditorVisual(TabSupervisor *_tabSupervisor) : Abstra loadLayout(); filterDockWidget->setHidden(true); cardDatabaseDockWidget->setHidden(true); - tutorialController = new TutorialController(this); + tutorialController = new TutorialController(TutorialId::DeckEditor, this); createTutorialSequences(); } @@ -88,31 +88,31 @@ void TabDeckEditorVisual::createTutorialSequences() auto sequence = TutorialSequence(); - sequence.addStep({tabContainer->tabBar(), - "The Visual Deck Editor has multiple different functionalities.\n\nYou can cycle " - "through them by using these tabs.\n\nLet's start with the Visual Deck View."}); + sequence.addStep( + {tabContainer->tabBar(), tr("The Visual Deck Editor has multiple different functionalities.\n\nYou can cycle " + "through them by using these tabs.\n\nLet's start with the Visual Deck View.")}); sequence.addStep({tabContainer->visualDeckView, - "The cards in your deck will be displayed here, allowing for an easy overview.\n\nLet's try " - "adding some now, so you can see it in action!", + tr("The cards in your deck will be displayed here, allowing for an easy overview.\n\nLet's try " + "adding some now, so you can see it in action!"), [this]() { tabContainer->setCurrentWidget(tabContainer->visualDeckView); }}); tutorialController->addSequence(sequence); auto vdeSequence = tabContainer->visualDeckView->addTutorialSteps(); - vdeSequence.addStep({tabContainer->tabBar(), "Let's look at the database tab now."}); + vdeSequence.addStep({tabContainer->tabBar(), tr("Let's look at the database tab now.")}); tutorialController->addSequence(vdeSequence); auto vddSequence = tabContainer->visualDatabaseDisplay->addTutorialSteps(); vddSequence.steps.prepend( {tabContainer->visualDatabaseDisplay, - "You can view the database here, either as card images or in the old table display " - "style.\n\nAdditionally, there are many powerful and easy to use filters available.\n\nLet's dive in!", + tr("You can view the database here, either as card images or in the old table display " + "style.\n\nAdditionally, there are many powerful and easy to use filters available.\n\nLet's dive in!"), [this]() { tabContainer->setCurrentWidget(tabContainer->visualDatabaseDisplay); }}); tutorialController->addSequence(vddSequence); auto analyticsSequence = tabContainer->deckAnalytics->generateTutorialSequence(); - analyticsSequence.steps.prepend({tabContainer->tabBar(), "Let's look at the analytics tab now."}); + analyticsSequence.steps.prepend({tabContainer->tabBar(), tr("Let's look at the analytics tab now.")}); TutorialStep analyticsConclusionStep; analyticsConclusionStep.targetWidget = tabContainer->tabBar(); @@ -150,7 +150,7 @@ void TabDeckEditorVisual::createTutorialSequences() void TabDeckEditorVisual::showEvent(QShowEvent *ev) { QWidget::showEvent(ev); - if (!tutorialStarted && !SettingsCache::instance().userInterface().getTutorialCompleted()) { + if (!tutorialStarted && !SettingsCache::instance().userInterface().getDeckEditorTutorialCompleted()) { tutorialStarted = true; // Start on next event loop iteration so everything is fully painted QTimer::singleShot(0, tutorialController, [this] { tutorialController->start(); }); diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.cpp index 80a03bec4..0ce6f72df 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.cpp @@ -194,13 +194,13 @@ VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent, TutorialSequence VisualDatabaseDisplayWidget::addTutorialSteps() { auto sequence = TutorialSequence(); - sequence.addStep({colorFilterWidget, "Filter the database by colors with these controls"}); + sequence.addStep({colorFilterWidget, tr("Filter the database by colors with these controls")}); TutorialStep displayModeStep; displayModeStep.targetWidget = displayModeButton; displayModeStep.text = tr("You can change back to the old table display-style with this button."); displayModeStep.allowClickThrough = true; sequence.addStep(displayModeStep); - sequence.addStep({filterContainer, "Use these controls for quick access to common filters."}); + sequence.addStep({filterContainer, tr("Use these controls for quick access to common filters.")}); TutorialStep setFilterStep; setFilterStep.targetWidget = filterContainer->getSetFilterWidget(); diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.cpp index 5a63ea79e..02e4db52b 100644 --- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.cpp @@ -464,20 +464,20 @@ void VisualDeckEditorWidget::onSelectionChanged(const QItemSelection &selected, TutorialSequence VisualDeckEditorWidget::addTutorialSteps() { TutorialSequence sequence; - sequence.name = "Adding Cards to Your Deck"; + sequence.name = tr("Adding Cards to Your Deck"); TutorialStep introStep; introStep.targetWidget = displayOptionsAndSearch; - introStep.text = "There are two ways of adding cards to your deck:\n\n" - "The first is by using the quick search bar in the deck view tab.\n" - "This is helpful if you already know which card you would like to add " - "and will provide name suggestions as you type.\n\n" - "We'll look at the second way, through the database tab, later."; + introStep.text = tr("There are two ways of adding cards to your deck:\n\n" + "The first is by using the quick search bar in the deck view tab.\n" + "This is helpful if you already know which card you would like to add " + "and will provide name suggestions as you type.\n\n" + "We'll look at the second way, through the database tab, later."); sequence.addStep(introStep); TutorialStep searchStep; searchStep.targetWidget = searchBar; - searchStep.text = "Let's try it out now!\nType the name of a card into the search bar."; + searchStep.text = tr("Let's try it out now!\nType the name of a card into the search bar."); searchStep.allowClickThrough = true; searchStep.requiresInteraction = true; searchStep.autoAdvanceOnValid = true; @@ -485,14 +485,14 @@ TutorialSequence VisualDeckEditorWidget::addTutorialSteps() searchStep.validator = [this]() { return CardDatabaseManager::query()->getCard({searchBar->text()}) != ExactCard(); }; - searchStep.validationHint = "Please enter a valid card name."; - searchStep.customInteractionHint = "✏️ Type a valid card name to continue"; + searchStep.validationHint = tr("Please enter a valid card name."); + searchStep.customInteractionHint = tr("✏️ Type a valid card name to continue"); sequence.addStep(searchStep); TutorialStep addStep; addStep.targetWidget = searchPushButton; - addStep.text = "Click this button to add the card to your deck."; + addStep.text = tr("Click this button to add the card to your deck."); addStep.allowClickThrough = true; addStep.requiresInteraction = true; addStep.autoAdvanceOnValid = true; @@ -506,8 +506,8 @@ TutorialSequence VisualDeckEditorWidget::addTutorialSteps() TutorialStep organizationStep; organizationStep.targetWidget = this; - organizationStep.text = "Let's look at how cards are organized and displayed now.\n\nWe'll add some random cards " - "from the database to your deck, so you can see it in action properly."; + organizationStep.text = tr("Let's look at how cards are organized and displayed now.\n\nWe'll add some random " + "cards from the database to your deck, so you can see it in action properly."); organizationStep.onExit = [this]() { // getCardList() counts distinct card nodes, not copies, so once every random pick only bumps an // existing node's count the size never grows. With fewer than ~60 distinct cards in the database @@ -524,11 +524,12 @@ TutorialSequence VisualDeckEditorWidget::addTutorialSteps() TutorialStep hoverStep; hoverStep.targetWidget = this; - hoverStep.text = "Great! Take some time to explore these new cards in the current display mode.\n\nYou can select " - "a card by clicking on it with the left mouse button.\nYou can select multiple cards by holding " - "down CTRL or Shift.\nYou can clear the current selection by clicking on an area without a " - "card.\nDouble-clicking a card will move it between main and sideboard.\nRight-clicking a card " - "will remove it from the deck.\n\nYou can hover over a card to see a zoomed version of it."; + hoverStep.text = tr("Great! Take some time to explore these new cards in the current display mode.\n\nYou can " + "select a card by clicking on it with the left mouse button.\nYou can select multiple cards " + "by holding down CTRL or Shift.\nYou can clear the current selection by clicking on an area " + "without a card.\nDouble-clicking a card will move it between main and sideboard.\n" + "Right-clicking a card will remove it from the deck.\n\nYou can hover over a card to see a " + "zoomed version of it."); hoverStep.allowClickThrough = true; sequence.addStep(hoverStep); @@ -538,9 +539,9 @@ TutorialSequence VisualDeckEditorWidget::addTutorialSteps() TutorialStep conclusionStep; conclusionStep.targetWidget = this; conclusionStep.text = - "Great!\n\nNow that you've learned about all the different ways of displaying the cards in " - "your deck, it's time to move on to searching for new cards for your deck in style and ease.\n\nYou can stay " - "on this screen to play around with the display options and advance when you are ready."; + tr("Great!\n\nNow that you've learned about all the different ways of displaying the cards in " + "your deck, it's time to move on to searching for new cards for your deck in style and ease.\n\nYou can " + "stay on this screen to play around with the display options and advance when you are ready."); conclusionStep.allowClickThrough = true; sequence.addStep(conclusionStep); diff --git a/libcockatrice_settings/libcockatrice/settings/interface_settings.cpp b/libcockatrice_settings/libcockatrice/settings/interface_settings.cpp index a1a1e57d1..4691bf0c4 100644 --- a/libcockatrice_settings/libcockatrice/settings/interface_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/interface_settings.cpp @@ -5,8 +5,6 @@ #include "libcockatrice/utility/playmat_params.h" #include -#include -#include #include #include #include @@ -253,6 +251,21 @@ bool InterfaceSettings::getTutorialCompleted() const return getValue("tutorialCompleted", QString(), QString(), false).toBool(); } +bool InterfaceSettings::getHomeTutorialCompleted() const +{ + return getValue("tutorialHomeCompleted", QString(), QString(), false).toBool(); +} + +bool InterfaceSettings::getDeckEditorTutorialCompleted() const +{ + return getValue("tutorialDeckEditorCompleted", QString(), QString(), false).toBool(); +} + +bool InterfaceSettings::getGameTutorialCompleted() const +{ + return getValue("tutorialGameCompleted", QString(), QString(), false).toBool(); +} + void InterfaceSettings::setUseTearOffMenus(bool _useTearOffMenus) { setValue(_useTearOffMenus, "useTearOffMenus"); @@ -481,3 +494,18 @@ void InterfaceSettings::setTutorialCompleted(bool value) { setValue(value, "tutorialCompleted"); } + +void InterfaceSettings::setHomeTutorialCompleted(bool value) +{ + setValue(value, "tutorialHomeCompleted"); +} + +void InterfaceSettings::setDeckEditorTutorialCompleted(bool value) +{ + setValue(value, "tutorialDeckEditorCompleted"); +} + +void InterfaceSettings::setGameTutorialCompleted(bool value) +{ + setValue(value, "tutorialGameCompleted"); +} diff --git a/libcockatrice_settings/libcockatrice/settings/interface_settings.h b/libcockatrice_settings/libcockatrice/settings/interface_settings.h index 8bede58d9..e1d282e75 100644 --- a/libcockatrice_settings/libcockatrice/settings/interface_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/interface_settings.h @@ -58,6 +58,9 @@ public: [[nodiscard]] bool getBattlefieldFlashEnabled() const override; [[nodiscard]] QStringList getUserListExpandedSections() const override; [[nodiscard]] bool getTutorialCompleted() const; + [[nodiscard]] bool getHomeTutorialCompleted() const; + [[nodiscard]] bool getDeckEditorTutorialCompleted() const; + [[nodiscard]] bool getGameTutorialCompleted() const; void setUseTearOffMenus(bool _useTearOffMenus); void setCardViewInitialRowsMax(int _cardViewInitialRowsMax); @@ -98,6 +101,9 @@ public: void setBattlefieldFlashEnabled(bool _battlefieldFlashEnabled); void setUserListExpandedSections(const QStringList §ions); void setTutorialCompleted(bool value); + void setHomeTutorialCompleted(bool value); + void setDeckEditorTutorialCompleted(bool value); + void setGameTutorialCompleted(bool value); signals: void useTearOffMenusChanged(bool state); diff --git a/tests/settings/settings_defaults_test.cpp b/tests/settings/settings_defaults_test.cpp index aea7f1d54..08c99e23c 100644 --- a/tests/settings/settings_defaults_test.cpp +++ b/tests/settings/settings_defaults_test.cpp @@ -543,6 +543,51 @@ TEST_F(SettingsDefaultsTest, Interface_BuddyConnectNotificationsEnabled_Default) ASSERT_EQ(s.getBuddyConnectNotificationsEnabled(), true); } +TEST_F(SettingsDefaultsTest, Interface_HomeTutorialCompleted_Default) +{ + InterfaceSettings s(settingsPath, nullptr); + ASSERT_EQ(s.getHomeTutorialCompleted(), false); +} + +TEST_F(SettingsDefaultsTest, Interface_HomeTutorialCompleted_SetAndGet) +{ + InterfaceSettings s(settingsPath, nullptr); + s.setHomeTutorialCompleted(true); + ASSERT_EQ(s.getHomeTutorialCompleted(), true); + s.setHomeTutorialCompleted(false); + ASSERT_EQ(s.getHomeTutorialCompleted(), false); +} + +TEST_F(SettingsDefaultsTest, Interface_DeckEditorTutorialCompleted_Default) +{ + InterfaceSettings s(settingsPath, nullptr); + ASSERT_EQ(s.getDeckEditorTutorialCompleted(), false); +} + +TEST_F(SettingsDefaultsTest, Interface_DeckEditorTutorialCompleted_SetAndGet) +{ + InterfaceSettings s(settingsPath, nullptr); + s.setDeckEditorTutorialCompleted(true); + ASSERT_EQ(s.getDeckEditorTutorialCompleted(), true); + s.setDeckEditorTutorialCompleted(false); + ASSERT_EQ(s.getDeckEditorTutorialCompleted(), false); +} + +TEST_F(SettingsDefaultsTest, Interface_GameTutorialCompleted_Default) +{ + InterfaceSettings s(settingsPath, nullptr); + ASSERT_EQ(s.getGameTutorialCompleted(), false); +} + +TEST_F(SettingsDefaultsTest, Interface_GameTutorialCompleted_SetAndGet) +{ + InterfaceSettings s(settingsPath, nullptr); + s.setGameTutorialCompleted(true); + ASSERT_EQ(s.getGameTutorialCompleted(), true); + s.setGameTutorialCompleted(false); + ASSERT_EQ(s.getGameTutorialCompleted(), false); +} + // --- DeckEditorSettings --- TEST_F(SettingsDefaultsTest, DeckEditor_OpenDeckInNewTab_Default)