Various fixes

Took 14 minutes
This commit is contained in:
Lukas Brübach 2026-08-09 03:16:22 +02:00
parent 4779cc9742
commit 569b531113
15 changed files with 88 additions and 79 deletions

View file

@ -16,8 +16,8 @@
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/network/client/remote/remote_client.h>
#include <libcockatrice/settings/appearance_settings.h>
#include <libcockatrice/settings/interface_settings.h>
#include <libcockatrice/settings/paths_settings.h>
#include <libcockatrice/utility/qt_utils.h>
HomeWidget::HomeWidget(QWidget *parent, TabSupervisor *_tabSupervisor)
: QWidget(parent), tabSupervisor(_tabSupervisor), background("theme:backgrounds/home"), overlay("theme:cockatrice")
@ -50,13 +50,7 @@ HomeWidget::HomeWidget(QWidget *parent, TabSupervisor *_tabSupervisor)
connect(&SettingsCache::instance().appearance(), &AppearanceSettings::homeTabBackgroundShuffleFrequencyChanged,
this, &HomeWidget::onBackgroundShuffleFrequencyChanged);
auto mainWindow = QtUtils::findParentOfType<QMainWindow>(this);
if (mainWindow) {
tutorialController = new TutorialController(mainWindow);
} else {
tutorialController = new TutorialController(this);
}
tutorialController = new TutorialController(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!"};
@ -92,7 +86,7 @@ HomeWidget::HomeWidget(QWidget *parent, TabSupervisor *_tabSupervisor)
void HomeWidget::showEvent(QShowEvent *event)
{
QWidget::showEvent(event);
if (!tutorialStarted) {
if (!tutorialStarted && !SettingsCache::instance().userInterface().getTutorialCompleted()) {
tutorialStarted = true;
// Start on next event loop iteration so everything is fully painted
QTimer::singleShot(3, tutorialController, [this] { tutorialController->start(); });

View file

@ -15,6 +15,8 @@
#include <QWidget>
#include <libcockatrice/network/client/abstract/abstract_client.h>
class TutorialController;
class HomeWidget : public QWidget
{

View file

@ -1,12 +1,14 @@
#include "tutorial_controller.h"
#include "../../../../client/settings/cache_settings.h"
#include <QComboBox>
#include <QDebug>
#include <QLineEdit>
#include <QMainWindow>
#include <QPlainTextEdit>
#include <QTextEdit>
#include <QTimer>
#include <libcockatrice/settings/interface_settings.h>
TutorialController::TutorialController(QWidget *_tutorializedWidget)
: QObject(_tutorializedWidget), tutorializedWidget(_tutorializedWidget)
@ -24,6 +26,18 @@ TutorialController::TutorialController(QWidget *_tutorializedWidget)
connect(tutorialOverlay, &TutorialOverlay::targetClicked, this, &TutorialController::handleTargetClicked);
}
TutorialController::~TutorialController()
{
// 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().
if (tutorialOverlay) {
tutorialOverlay->hide();
tutorialOverlay->deleteLater();
}
tutorialOverlay = nullptr;
}
void TutorialController::addSequence(const TutorialSequence &seq)
{
sequences.append(seq);
@ -172,12 +186,15 @@ void TutorialController::prevSequence()
void TutorialController::exitTutorial()
{
cleanupValidationMonitoring();
tutorialOverlay->hide();
// TODO Maybe not the best idea:
tutorialOverlay->deleteLater();
if (tutorialOverlay) {
tutorialOverlay->hide();
tutorialOverlay->deleteLater();
}
tutorialOverlay = nullptr;
currentSequence = -1;
currentStep = -1;
tutorialCompleted = true;
SettingsCache::instance().userInterface().setTutorialCompleted(true);
deleteLater();
}
@ -237,6 +254,7 @@ void TutorialController::showStep()
}
cleanupValidationMonitoring();
advanceScheduled = false;
const auto &step = seq.steps[currentStep];
@ -278,13 +296,7 @@ void TutorialController::setupValidationMonitoring()
// Handle OnSignal validation - connect to any custom signal
if (step.validationTiming == ValidationTiming::OnSignal && step.validator) {
if (step.signalSource && step.signalName) {
qInfo() << "Setting up signal-based validation for signal:" << step.signalName;
validationConnection = connect(step.signalSource, step.signalName, this, SLOT(checkValidation()));
if (!validationConnection) {
qInfo() << "Warning: Failed to connect to signal" << step.signalName;
}
} else {
qInfo() << "Warning: OnSignal validation timing set but signalSource or signalName is null";
}
return;
}
@ -292,24 +304,17 @@ void TutorialController::setupValidationMonitoring()
// Handle OnChange validation - widget-specific
if (step.validationTiming == ValidationTiming::OnChange && step.validator) {
if (QLineEdit *lineEdit = qobject_cast<QLineEdit *>(step.targetWidget)) {
qInfo() << "Setting up validation monitoring for QLineEdit";
validationConnection =
connect(lineEdit, &QLineEdit::textChanged, this, &TutorialController::checkValidation);
} else if (QTextEdit *textEdit = qobject_cast<QTextEdit *>(step.targetWidget)) {
qInfo() << "Setting up validation monitoring for QTextEdit";
validationConnection =
connect(textEdit, &QTextEdit::textChanged, this, &TutorialController::checkValidation);
} else if (QPlainTextEdit *plainText = qobject_cast<QPlainTextEdit *>(step.targetWidget)) {
qInfo() << "Setting up validation monitoring for QPlainTextEdit";
validationConnection =
connect(plainText, &QPlainTextEdit::textChanged, this, &TutorialController::checkValidation);
} else if (QComboBox *combo = qobject_cast<QComboBox *>(step.targetWidget)) {
qInfo() << "Setting up validation monitoring for QComboBox";
validationConnection = connect(combo, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&TutorialController::checkValidation);
} else {
qInfo() << "Warning: OnChange validation timing set but widget type not supported:"
<< (step.targetWidget ? step.targetWidget->metaObject()->className() : "null");
}
}
}
@ -317,7 +322,6 @@ void TutorialController::setupValidationMonitoring()
void TutorialController::cleanupValidationMonitoring()
{
if (validationConnection) {
qInfo() << "Cleaning up validation connection";
disconnect(validationConnection);
validationConnection = QMetaObject::Connection();
}
@ -325,8 +329,6 @@ void TutorialController::cleanupValidationMonitoring()
void TutorialController::checkValidation()
{
qInfo() << "checkValidation() called";
if (currentSequence < 0 || currentSequence >= sequences.size()) {
return;
}
@ -338,16 +340,18 @@ void TutorialController::checkValidation()
if (step.validator) {
bool isValid = step.validator();
qInfo() << "Validation result:" << isValid;
if (isValid) {
// Clear any validation hints
tutorialOverlay->showValidationHint("");
// Auto-advance if enabled
if (step.autoAdvanceOnValid) {
qInfo() << "Auto-advancing to next step";
QTimer::singleShot(500, this, &TutorialController::nextStep);
if (step.autoAdvanceOnValid && !advanceScheduled) {
advanceScheduled = true;
QTimer::singleShot(500, this, [this]() {
advanceScheduled = false;
nextStep();
});
}
}
}

View file

@ -57,6 +57,7 @@ class TutorialController : public QObject
public:
explicit TutorialController(QWidget *_tutorializedWidget);
~TutorialController() override;
void addSequence(const TutorialSequence &seq);
void start();
@ -95,6 +96,10 @@ private:
// For OnChange validation monitoring
QMetaObject::Connection validationConnection;
// True while an auto-advance timer is pending, so repeated signal emissions
// can't queue more than one advance.
bool advanceScheduled = false;
};
#endif // COCKATRICE_TUTORIAL_CONTROLLER_H

View file

@ -300,7 +300,9 @@ void TutorialOverlay::recomputeLayout()
return;
}
resize(parentWidget()->window()->geometry().size());
// The overlay is parented to the top-level window; its client size (rect())
// is the visible area, whereas window()->geometry() includes the frame.
resize(parentWidget()->size());
bubble->adjustSize();