diff --git a/resources/styles/app.qss b/resources/styles/app.qss index 3e65e1f2..1181458b 100644 --- a/resources/styles/app.qss +++ b/resources/styles/app.qss @@ -418,6 +418,12 @@ QPushButton[role="primary"]:pressed { background-color: #007F86; } +QPushButton[role="primary"]:disabled { + background-color: rgba(17, 179, 188, 0.24); + color: rgba(255, 255, 255, 0.55); + border: none; +} + QPushButton[role="danger"] { background-color: #D9534F; color: #FFFFFF; @@ -513,55 +519,102 @@ QTextBrowser#infoDialogContent a { color: #36C5CC; } -QWizard[wizardTheme="slr"] * { - background-color: #2B2B2B; - color: #E0E0E0; +QDialog[guidedDialog="slr"] { + background-color: #1F1F1F; + border: none; } -QWizard[wizardTheme="slr"] { - background-color: #1F1F1F; - color: #E0E0E0; +QDialog[guidedDialog="slr"] QFrame#slrWizardPanel { + background: transparent; + border: none; + border-radius: 0; } -QWizard[wizardTheme="slr"] QWizardPage { - background-color: #1F1F1F; - color: #E0E0E0; +QDialog[guidedDialog="slr"] QLabel#slrWizardTitle { + color: #F4F7F8; + font-size: 28px; + font-weight: 700; } -QWizard[wizardTheme="slr"] QWizard::title { - color: #FFFFFF; - font-size: 18px; +QDialog[guidedDialog="slr"] QLabel#slrWizardStepCounter { + color: #9AA5A8; + font-size: 12px; + font-weight: 600; } -QWizard[wizardTheme="slr"] QLabel { - color: #E0E0E0; +QDialog[guidedDialog="slr"] QProgressBar#slrWizardProgress { + background-color: rgba(255, 255, 255, 0.05); + border: none; + border-radius: 2px; } -QWizard[wizardTheme="slr"] QLabel#slrWizardFeedbackLabel { - color: #CCCCCC; - min-height: 20px; +QDialog[guidedDialog="slr"] QProgressBar#slrWizardProgress::chunk { + background-color: #11B3BC; + border-radius: 2px; } -QWizard[wizardTheme="slr"] QLineEdit { - background-color: #3C3F41; - color: #E0E0E0; - border: 1px solid #555555; - border-radius: 4px; - padding: 4px; +QDialog[guidedDialog="slr"] QPushButton#slrWizardCloseButton { + background: transparent; + color: #AAB5B8; + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 8px; + padding: 6px 11px; + font-size: 12px; + font-weight: 600; } -QWizard[wizardTheme="slr"] QWizard::WizardButton { - background-color: #444444; +QDialog[guidedDialog="slr"] QPushButton#slrWizardCloseButton:hover { + background-color: rgba(255, 255, 255, 0.03); color: #FFFFFF; - border: none; - padding: 6px 12px; - border-radius: 4px; + border: 1px solid rgba(255, 255, 255, 0.12); +} + +QDialog[guidedDialog="slr"] QPushButton#slrWizardCloseButton:pressed { + background-color: rgba(255, 255, 255, 0.06); +} + +QDialog[guidedDialog="slr"] QLabel#slrWizardSectionTitle { + color: #9FCFD2; + font-size: 12px; + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +QDialog[guidedDialog="slr"] QLabel#slrWizardExplanationLabel { + color: #DEE5E7; + font-size: 15px; + line-height: 1.5; +} + +QDialog[guidedDialog="slr"] QLineEdit#slrWizardAnswerEdit { + background-color: #2B3133; + color: #F1F4F5; + border: 1px solid rgba(255, 255, 255, 0.10); + border-radius: 12px; + padding: 12px 14px; + font-size: 15px; + selection-background-color: #35515A; } -QWizard[wizardTheme="slr"] QWizard::WizardButton:hover { - background-color: #555555; +QDialog[guidedDialog="slr"] QLineEdit#slrWizardAnswerEdit:focus { + border: 1px solid #11B3BC; } -QWizard[wizardTheme="slr"] QWizard::WizardButton:pressed { - background-color: #333333; +QDialog[guidedDialog="slr"] QLabel#slrWizardFeedbackLabel { + color: #AEB9BC; + min-height: 40px; + padding-top: 2px; +} + +QDialog[guidedDialog="slr"] QPushButton#slrWizardNextButton { + min-width: 136px; + padding: 11px 18px; + border-radius: 10px; +} + +QDialog[guidedDialog="slr"] QPushButton#slrWizardNextButton:disabled { + background-color: #2A2E30; + color: #6F7A7E; + border: 1px solid #353D40; } diff --git a/src/gui/slrtabledialog.cpp b/src/gui/slrtabledialog.cpp index 4859d627..7f8f8416 100644 --- a/src/gui/slrtabledialog.cpp +++ b/src/gui/slrtabledialog.cpp @@ -169,6 +169,11 @@ void SLRTableDialog::setInitialData(const QVector>& data) { } } +void SLRTableDialog::setGuidedModeActive(bool active) { + guidedButton->setEnabled(!active); + submitButton->setEnabled(!active); +} + void SLRTableDialog::highlightIncorrectCells( const QList>& coords) { for (int r = 0; r < table->rowCount(); ++r) { diff --git a/src/gui/slrtabledialog.h b/src/gui/slrtabledialog.h index 16eaf749..c66e14d7 100644 --- a/src/gui/slrtabledialog.h +++ b/src/gui/slrtabledialog.h @@ -70,6 +70,7 @@ class SLRTableDialog : public QDialog { * @param data 2D vector containing the table data to display. */ void setInitialData(const QVector>& data); + void setGuidedModeActive(bool active); void highlightIncorrectCells(const QList>& coords); void highlightInvalidCells(const QList>& coords); diff --git a/src/gui/slrtutorwindow.cpp b/src/gui/slrtutorwindow.cpp index 7a8e11c6..f976571f 100644 --- a/src/gui/slrtutorwindow.cpp +++ b/src/gui/slrtutorwindow.cpp @@ -599,12 +599,13 @@ void SLRTutorWindow::showTable() { const QVector> snapshot = data; auto* wizard = new SLRWizard(slr1, snapshot, colHeaders, sortedGrammar, dialog); - wizard->setProperty("wizardTheme", "slr"); wizard->setAttribute(Qt::WA_DeleteOnClose); wizard->setWindowModality(Qt::WindowModal); + dialog->setGuidedModeActive(true); - connect(wizard, &QWizard::finished, dialog, + connect(wizard, &QDialog::finished, dialog, [dialog, snapshot](int) { + dialog->setGuidedModeActive(false); dialog->setInitialData(snapshot); }); wizard->show(); diff --git a/src/gui/slrwizard.h b/src/gui/slrwizard.h index f1628fa3..e41c38e5 100644 --- a/src/gui/slrwizard.h +++ b/src/gui/slrwizard.h @@ -21,33 +21,33 @@ #include "slr1_parser.hpp" #include "slrwizardpage.h" -#include + +#include +#include +#include #include -#include +#include +#include +#include +#include #include -#include -#include /** * @class SLRWizard * @brief Interactive assistant that guides the student step-by-step through the * SLR(1) parsing table. * - * This wizard-based dialog presents the user with one cell of the SLR(1) - * parsing table at a time, asking them to deduce the correct ACTION or GOTO - * entry based on the LR(0) automaton and FOLLOW sets. It is designed as an - * educational aid to explain the reasoning behind each parsing decision. - * - * Each page includes: - * - The current state and symbol (terminal or non-terminal). - * - A guided explanation based on the grammar and LR(0) state. - * - The expected entry (e.g., s3, r1, acc, or a state number). + * This dialog presents one cell of the SLR(1) parsing table at a time, asking + * the user to deduce the correct ACTION or GOTO entry based on the LR(0) + * automaton and FOLLOW sets. It intentionally avoids the native wizard widget + * to keep a consistent look across platforms. */ -class SLRWizard : public QWizard { +class SLRWizard : public QDialog { Q_OBJECT public: /** - * @brief Constructs the SLR(1) wizard with all necessary parsing context. + * @brief Constructs the SLR(1) guided dialog with all necessary parsing + * context. * * @param parser The SLR(1) parser instance containing the LR(0) states and * transitions. @@ -61,32 +61,84 @@ class SLRWizard : public QWizard { const QStringList& colHeaders, const QVector>>& sortedGrammar, QWidget* parent = nullptr) - : QWizard(parent) { - setWizardStyle(QWizard::ModernStyle); + : QDialog(parent) { + setProperty("guidedDialog", "slr"); + setWindowTitle(tr("Completar tabla SLR")); setWindowFlag(Qt::WindowCloseButtonHint, true); - setButtonText(QWizard::CancelButton, tr("Salir")); - setButtonText(QWizard::NextButton, tr("Continuar")); - setButtonText(QWizard::FinishButton, tr("Finalizar")); - setButtonLayout({QWizard::Stretch, QWizard::CancelButton, - QWizard::NextButton, QWizard::FinishButton}); - - if (auto* backButton = button(QWizard::BackButton)) { - backButton->hide(); - backButton->setEnabled(false); - } + setModal(true); + resize(640, 440); + setMinimumSize(560, 400); + + auto* rootLayout = new QVBoxLayout(this); + rootLayout->setContentsMargins(0, 0, 0, 0); + rootLayout->setSpacing(0); + + auto* panel = new QFrame(this); + panel->setObjectName("slrWizardPanel"); + + auto* panelLayout = new QVBoxLayout(panel); + panelLayout->setContentsMargins(28, 24, 28, 24); + panelLayout->setSpacing(20); + + auto* headerLayout = new QHBoxLayout(); + headerLayout->setSpacing(12); + + auto* titleColumn = new QVBoxLayout(); + titleColumn->setContentsMargins(0, 0, 0, 0); + titleColumn->setSpacing(6); + + m_titleLabel = new QLabel(panel); + m_titleLabel->setObjectName("slrWizardTitle"); + titleColumn->addWidget(m_titleLabel); + + m_stepCounterLabel = new QLabel(panel); + m_stepCounterLabel->setObjectName("slrWizardStepCounter"); + titleColumn->addWidget(m_stepCounterLabel); + + m_progressBar = new QProgressBar(panel); + m_progressBar->setObjectName("slrWizardProgress"); + m_progressBar->setTextVisible(false); + m_progressBar->setRange(0, 100); + m_progressBar->setFixedHeight(4); + titleColumn->addWidget(m_progressBar); + + headerLayout->addLayout(titleColumn, 1); + + m_closeButton = new QPushButton(tr("Salir"), panel); + m_closeButton->setObjectName("slrWizardCloseButton"); + m_closeButton->setAutoDefault(false); + m_closeButton->setDefault(false); + m_closeButton->setCursor(Qt::PointingHandCursor); + headerLayout->addWidget(m_closeButton, 0, Qt::AlignTop); + + panelLayout->addLayout(headerLayout); + + m_stack = new QStackedWidget(panel); + m_stack->setObjectName("slrWizardStack"); + panelLayout->addWidget(m_stack, 1); + + auto* footerLayout = new QHBoxLayout(); + footerLayout->setContentsMargins(0, 0, 0, 0); + footerLayout->setSpacing(12); + footerLayout->addStretch(1); + + m_nextButton = new QPushButton(panel); + m_nextButton->setObjectName("slrWizardNextButton"); + m_nextButton->setProperty("role", "primary"); + m_nextButton->setAutoDefault(false); + m_nextButton->setDefault(false); + footerLayout->addWidget(m_nextButton); + panelLayout->addLayout(footerLayout); + + rootLayout->addWidget(panel); + + const int nTerm = parser.gr_.st_.terminals_.size(); + for (int i = 0; i < rawTable.size(); ++i) { + for (int j = 0; j < colHeaders.size(); ++j) { + const QString sym = colHeaders[j]; + QString expected; + QString explanation; - setWindowTitle(tr("Ayuda interactiva: Tabla SLR(1)")); - - const int nTerm = parser.gr_.st_.terminals_.size(); - SLRWizardPage* last = nullptr; - // Generar explicación y páginas - int rows = rawTable.size(); - int cols = colHeaders.size(); - for (int i = 0; i < rows; ++i) { - for (int j = 0; j < cols; ++j) { - QString sym = colHeaders[j]; - QString expected; - QString explanation; if (j < nTerm) { auto itAct = parser.actions_.at(i).find(sym.toStdString()); SLR1Parser::s_action act = @@ -94,14 +146,14 @@ class SLRWizard : public QWizard { ? itAct->second : SLR1Parser::s_action{nullptr, SLR1Parser::Action::Empty}); + switch (act.action) { case SLR1Parser::Action::Shift: { - unsigned to = + const unsigned to = parser.transitions_.at(i).at(sym.toStdString()); expected = QString("s%1").arg(to); explanation = tr("Estado %1: existe transición δ(%1, " - "'%2'). ¿A qué " - "estado harías shift?") + "'%2'). ¿A qué estado harías shift?") .arg(i) .arg(sym); break; @@ -119,16 +171,9 @@ class SLRWizard : public QWizard { } } expected = QString("r%1").arg(idx); - // explicación con FOLLOW - std::unordered_set F; - F = parser.Follow(act.item->antecedent_); - QStringList followList; - for (auto& t : F) - followList << QString::fromStdString(t); explanation = tr("Estado %1: contiene el ítem [%2 → " - "...·] y '%3' ∈ " - "SIG(%2). ¿Qué regla usas para " - "reducir (0, 1, ...)?") + "...·] y '%3' ∈ SIG(%2). ¿Qué regla " + "usas para reducir (0, 1, ...)?") .arg(i) .arg(QString::fromStdString( act.item->antecedent_)) @@ -138,8 +183,8 @@ class SLRWizard : public QWizard { case SLR1Parser::Action::Accept: expected = "acc"; explanation = tr("Estado %1: contiene [S → A · $]. " - "¿Qué palabra clave " - "usas para aceptar?") + "¿Qué palabra clave usas para " + "aceptar?") .arg(i); break; case SLR1Parser::Action::Empty: @@ -147,33 +192,57 @@ class SLRWizard : public QWizard { continue; } } else { - // GOTO sobre no terminal - auto nonT = sym.toStdString(); + const auto nonT = sym.toStdString(); if (!parser.transitions_.contains(i)) { continue; } + auto itGo = parser.transitions_.at(i).find(nonT); - if (itGo != parser.transitions_.at(i).end()) { - expected = QString::number(itGo->second); - explanation = tr("Estado %1: δ(%1, '%2') existe. ¿A " - "qué estado va " - "la transición? (pon solo el número)") - .arg(i) - .arg(sym); - } else { + if (itGo == parser.transitions_.at(i).end()) { continue; } + + expected = QString::number(itGo->second); + explanation = tr("Estado %1: δ(%1, '%2') existe. ¿A qué " + "estado va la transición? (pon solo el " + "número)") + .arg(i) + .arg(sym); } - SLRWizardPage* page = - new SLRWizardPage(i, sym, explanation, expected, this); - last = page; - addPage(page); + auto* page = new SLRWizardPage(i, sym, explanation, expected, + m_stack); + connect(page, &SLRWizardPage::completionChanged, this, + &SLRWizard::updateCurrentPage); + connect(page, &SLRWizardPage::submitRequested, this, + &SLRWizard::advance); + m_stack->addWidget(page); } } - if (last) { - last->setFinalPage(true); - } + + connect(m_closeButton, &QPushButton::clicked, this, + &QDialog::reject); + connect(m_nextButton, &QPushButton::clicked, this, + &SLRWizard::advance); + + updateCurrentPage(); + QTimer::singleShot(0, this, [this]() { + if (auto* page = currentPage()) { + page->focusAnswerField(); + } + }); + } + + SLRWizardPage* currentPage() const { + return qobject_cast(m_stack->currentWidget()); + } + + QPushButton* nextButton() const { return m_nextButton; } + QPushButton* closeButton() const { return m_closeButton; } + + bool isOnLastPage() const { + return m_stack->count() > 0 && + m_stack->currentIndex() == m_stack->count() - 1; } /** @@ -190,6 +259,57 @@ class SLRWizard : public QWizard { } return result; } + + private slots: + void advance() { + auto* page = currentPage(); + if (page == nullptr || !page->isComplete()) { + return; + } + + if (isOnLastPage()) { + accept(); + return; + } + + m_stack->setCurrentIndex(m_stack->currentIndex() + 1); + updateCurrentPage(); + if (auto* nextPage = currentPage()) { + nextPage->focusAnswerField(); + } + } + + void updateCurrentPage() { + auto* page = currentPage(); + if (page == nullptr) { + m_titleLabel->setText(tr("Modo guiado")); + m_stepCounterLabel->clear(); + m_progressBar->setValue(0); + m_nextButton->setEnabled(false); + m_nextButton->setText(tr("Finalizar")); + return; + } + + const int currentStep = m_stack->currentIndex() + 1; + const int totalSteps = m_stack->count(); + m_titleLabel->setText(page->titleText()); + m_stepCounterLabel->setText( + tr("Paso %1 de %2").arg(currentStep).arg(totalSteps)); + m_progressBar->setValue((100 * currentStep) / qMax(1, totalSteps)); + m_nextButton->setEnabled(page->isComplete()); + m_nextButton->setCursor(page->isComplete() ? Qt::PointingHandCursor + : Qt::ArrowCursor); + m_nextButton->setText(isOnLastPage() ? tr("Finalizar") + : tr("Continuar")); + } + + private: + QLabel* m_titleLabel = nullptr; + QLabel* m_stepCounterLabel = nullptr; + QProgressBar* m_progressBar = nullptr; + QPushButton* m_closeButton = nullptr; + QStackedWidget* m_stack = nullptr; + QPushButton* m_nextButton = nullptr; }; #endif // SLRWIZARD_H diff --git a/src/gui/slrwizardpage.h b/src/gui/slrwizardpage.h index 4526f889..91a56707 100644 --- a/src/gui/slrwizardpage.h +++ b/src/gui/slrwizardpage.h @@ -19,26 +19,21 @@ #ifndef SLRWIZARDPAGE_H #define SLRWIZARDPAGE_H -#include #include #include +#include #include -#include -#include +#include /** * @class SLRWizardPage - * @brief A single step in the SLR(1) guided assistant for table construction. + * @brief A single guided step for constructing the SLR(1) table. * - * This wizard page presents a specific (state, symbol) cell in the SLR(1) - * parsing table, and prompts the student to enter the correct ACTION or GOTO - * value. - * - * The page checks the user's input against the expected answer and provides - * immediate feedback, disabling the "Next" button until the correct response is - * entered. + * This widget presents one (state, symbol) question at a time with a compact + * explanation card and an answer card. It validates the current input and + * emits a signal whenever the completion state changes. */ -class SLRWizardPage : public QWizardPage { +class SLRWizardPage : public QWidget { Q_OBJECT public: /** @@ -47,90 +42,97 @@ class SLRWizardPage : public QWizardPage { * @param state The state ID (row index in the table). * @param symbol The grammar symbol (column header). * @param explanation A pedagogical explanation shown to the user. - * @param expected The expected answer (e.g., "s2", "r1", "acc", or a state - * number). + * @param expected The expected answer (e.g., "s2", "r1", "acc", or a + * state number). * @param parent The parent widget. */ SLRWizardPage(int state, const QString& symbol, const QString& explanation, const QString& expected, QWidget* parent = nullptr) - : QWizardPage(parent), m_state(state), m_symbol(symbol), + : QWidget(parent), m_title(tr("Estado %1, símbolo '%2'").arg(state).arg(symbol)), m_expected(expected) { - setTitle(tr("Estado %1, símbolo '%2'").arg(state).arg(symbol)); - setSubTitle(QString()); + auto* rootLayout = new QVBoxLayout(this); + rootLayout->setContentsMargins(0, 0, 0, 0); + rootLayout->setSpacing(22); - QLabel* lbl = new QLabel(explanation, this); - lbl->setWordWrap(true); + auto* explanationTitle = new QLabel(tr("Pista"), this); + explanationTitle->setObjectName("slrWizardSectionTitle"); + rootLayout->addWidget(explanationTitle); - m_feedback = new QLabel(this); - m_feedback->setObjectName("slrWizardFeedbackLabel"); - m_feedback->setWordWrap(true); - m_feedback->hide(); + auto* explanationLabel = new QLabel(explanation, this); + explanationLabel->setObjectName("slrWizardExplanationLabel"); + explanationLabel->setWordWrap(true); + rootLayout->addWidget(explanationLabel); + + auto* answerTitle = new QLabel(tr("Tu respuesta"), this); + answerTitle->setObjectName("slrWizardSectionTitle"); + rootLayout->addWidget(answerTitle); m_edit = new QLineEdit(this); m_edit->setObjectName("slrWizardAnswerEdit"); + m_edit->setMinimumHeight(48); + m_edit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); m_edit->setPlaceholderText( tr("Escribe tu respuesta (p.ej. s3, r2, acc, 5)")); + rootLayout->addWidget(m_edit); - QVBoxLayout* layout = new QVBoxLayout(this); - layout->addWidget(lbl); - layout->addWidget(m_feedback); - layout->addWidget(m_edit); - setLayout(layout); + m_feedback = new QLabel(this); + m_feedback->setObjectName("slrWizardFeedbackLabel"); + m_feedback->setWordWrap(true); + m_feedback->setMinimumHeight(40); + m_feedback->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + rootLayout->addWidget(m_feedback); + + rootLayout->addStretch(1); connect(m_edit, &QLineEdit::textChanged, this, &SLRWizardPage::onTextChanged); + connect(m_edit, &QLineEdit::returnPressed, this, + &SLRWizardPage::submitRequested); } + + QString titleText() const { return m_title; } + bool isComplete() const { return m_isComplete; } + void focusAnswerField() { m_edit->setFocus(); } + #ifdef SYNTAXTUTOR_TESTING QString expectedForTest() const { return m_expected; } #endif + + signals: + void completionChanged(bool complete); + void submitRequested(); + private slots: /** - * @brief Checks the user's input and enables the "Next" button only if - * correct. + * @brief Checks the user's input and updates inline feedback. * @param text The current user input. */ void onTextChanged(const QString& text) { const QString trimmed = text.trimmed(); - bool correct = (trimmed == m_expected); - setComplete(correct); + const bool correct = (trimmed == m_expected); if (trimmed.isEmpty()) { m_feedback->clear(); - m_feedback->hide(); } else if (correct) { m_feedback->setText( tr("✔ Respuesta correcta, pasa a la siguiente pregunta")); - m_feedback->show(); } else { m_feedback->setText(tr("✘ Incorrecto, revisa el enunciado. " "Consulta los estados que has " "construido.")); - m_feedback->show(); } - wizard()->button(QWizard::NextButton)->setEnabled(correct); - } + if (m_isComplete == correct) { + return; + } - private: - /** - * @brief Marks the page as complete or incomplete. - * @param complete Whether the current answer is correct. - */ - void setComplete(bool complete) { - m_isComplete = complete; - emit completeChanged(); + m_isComplete = correct; + emit completionChanged(m_isComplete); } - /** - * @brief Returns whether the page is currently complete (required for - * QWizard). - * @return true if the correct answer has been entered. - */ - bool isComplete() const override { return m_isComplete; } - - int m_state; ///< The state index of the cell. - QString m_symbol; ///< The symbol (terminal or non-terminal). - QString m_expected; ///< The expected user response. + private: + QString m_title; ///< Header title for this guided step. + QString m_expected; ///< Expected user response. QLabel* m_feedback; ///< Inline feedback label for answer validation. QLineEdit* m_edit; ///< Input field for the user's answer. bool m_isComplete = diff --git a/tests/helpers/slr_tutor_test_utils.h b/tests/helpers/slr_tutor_test_utils.h index f7ce71ba..d76c33d2 100644 --- a/tests/helpers/slr_tutor_test_utils.h +++ b/tests/helpers/slr_tutor_test_utils.h @@ -4,6 +4,7 @@ #include "qt_modal_test_utils.h" #include "slr1_parser.hpp" #include "slrtutorwindow.h" +#include "slrwizard.h" #include "slrwizardpage.h" #include @@ -15,8 +16,6 @@ #include #include #include -#include - #include #include #include @@ -301,22 +300,19 @@ inline QVector> buildWrongTable(const Grammar& grammar, return raw; } -inline void finishWizard(QWizard* wizard) { - QPointer wizardGuard(wizard); +inline void finishWizard(SLRWizard* wizard) { + QPointer wizardGuard(wizard); QVERIFY(wizardGuard != nullptr); while (wizardGuard != nullptr && wizardGuard->isVisible()) { - auto* page = qobject_cast(wizardGuard->currentPage()); + auto* page = wizardGuard->currentPage(); QVERIFY(page != nullptr); auto* edit = page->findChild("slrWizardAnswerEdit"); QVERIFY(edit != nullptr); edit->setText(page->expectedForTest()); QApplication::processEvents(); - const bool isFinalPage = page->isFinalPage(); - QPushButton* nextButton = qobject_cast( - wizardGuard->button(isFinalPage - ? QWizard::FinishButton - : QWizard::NextButton)); + const bool isFinalPage = wizardGuard->isOnLastPage(); + QPushButton* nextButton = wizardGuard->nextButton(); QVERIFY(nextButton != nullptr); QTest::mouseClick(nextButton, Qt::LeftButton); QApplication::processEvents(); diff --git a/tests/tutor/slr_tutor_window_test.cpp b/tests/tutor/slr_tutor_window_test.cpp index d1f4d89b..9c368611 100644 --- a/tests/tutor/slr_tutor_window_test.cpp +++ b/tests/tutor/slr_tutor_window_test.cpp @@ -3,6 +3,7 @@ #include "qt_modal_test_utils.h" #include "slr_tutor_test_utils.h" #include "slrtutorwindow.h" +#include "slrwizard.h" #include "tutor_grammar_fixtures.h" #include @@ -15,8 +16,6 @@ #include #include #include -#include - #include namespace { @@ -112,8 +111,8 @@ SLRTableDialog* waitForSlrTableDialog() { return QtModalTestUtils::waitForVisibleTopLevelWidget(); } -QWizard* waitForWizard() { - return QtModalTestUtils::waitForVisibleTopLevelWidget(); +SLRWizard* waitForWizard() { + return QtModalTestUtils::waitForVisibleTopLevelWidget(); } void driveSlrTutorToState(SLRTutorWindow& tutor, const QString& state) { @@ -694,8 +693,8 @@ void TutorWindowTest::slrStateHIncorrectTableKeepsDialogOpen() { // The user opens guided mode and then exits through the wizard cancel button. // // Expected: -// The wizard uses the configured non-native style, does not expose a back -// button, and closes back to the table dialog. +// The guided dialog uses the custom step layout and closes back to the table +// dialog. // ----------------------------------------------------------------------------- void TutorWindowTest::slrGuidedModeWizardUsesCustomNavigationAndAllowsExit() { const Grammar grammar = TutorGrammarFixtures::makeSlrSimpleGrammar(); @@ -706,45 +705,60 @@ void TutorWindowTest::slrGuidedModeWizardUsesCustomNavigationAndAllowsExit() { SLRTableDialog* dialog = waitForSlrTableDialog(); auto* table = dialog->findChild("slrTableWidget"); + auto* guidedButton = + dialog->findChild("slrTableGuidedButton"); + auto* submitButton = + dialog->findChild("slrTableSubmitButton"); QVERIFY(table != nullptr); + QVERIFY(guidedButton != nullptr); + QVERIFY(submitButton != nullptr); QtModalTestUtils::requestSlrGuidedMode( dialog, QVector>(table->rowCount(), QVector(table->columnCount()))); - QWizard* wizard = waitForWizard(); + SLRWizard* wizard = waitForWizard(); QVERIFY(wizard != nullptr); - QPointer wizardGuard(wizard); - auto* page = qobject_cast(wizard->currentPage()); + QPointer wizardGuard(wizard); + auto* page = wizard->currentPage(); QVERIFY(page != nullptr); - QCOMPARE(wizard->wizardStyle(), QWizard::ModernStyle); - QCOMPARE(page->subTitle(), QString()); + QVERIFY(!guidedButton->isEnabled()); + QVERIFY(!submitButton->isEnabled()); + + auto* titleLabel = wizard->findChild("slrWizardTitle"); + QVERIFY(titleLabel != nullptr); + QVERIFY(!titleLabel->text().isEmpty()); - auto* backButton = wizard->button(QWizard::BackButton); - QVERIFY(backButton != nullptr); - QVERIFY(!backButton->isVisibleTo(wizard)); - QVERIFY(!backButton->isEnabled()); + auto* stepCounter = wizard->findChild("slrWizardStepCounter"); + QVERIFY(stepCounter != nullptr); + QVERIFY(!stepCounter->text().isEmpty()); auto* feedbackLabel = page->findChild("slrWizardFeedbackLabel"); QVERIFY(feedbackLabel != nullptr); - QVERIFY(!feedbackLabel->isVisible()); + QVERIFY(feedbackLabel->text().isEmpty()); auto* edit = page->findChild("slrWizardAnswerEdit"); QVERIFY(edit != nullptr); edit->setText(QStringLiteral("s")); QApplication::processEvents(); - QCOMPARE(page->subTitle(), QString()); QVERIFY(feedbackLabel->isVisible()); QVERIFY(!feedbackLabel->text().isEmpty()); - auto* cancelButton = qobject_cast( - wizard->button(QWizard::CancelButton)); + edit->setText(page->expectedForTest()); + QApplication::processEvents(); + QPointer firstPage(page); + QTest::keyClick(edit, Qt::Key_Return); + QTRY_VERIFY(firstPage == nullptr || wizard->currentPage() != firstPage); + + auto* cancelButton = wizard->closeButton(); QVERIFY(cancelButton != nullptr); QVERIFY(cancelButton->isVisibleTo(wizard)); QTest::mouseClick(cancelButton, Qt::LeftButton); QTRY_VERIFY(wizardGuard == nullptr || !wizardGuard->isVisible()); + QVERIFY(guidedButton->isEnabled()); + QVERIFY(submitButton->isEnabled()); QVERIFY(dialog->isVisible()); QCOMPARE(tutor.currentStateForTest(), QString("H")); } @@ -773,17 +787,28 @@ void TutorWindowTest::slrGuidedModeWizardCompletesAndReturnsToTable() { SLRTableDialog* dialog = waitForSlrTableDialog(); auto* table = dialog->findChild("slrTableWidget"); + auto* guidedButton = + dialog->findChild("slrTableGuidedButton"); + auto* submitButton = + dialog->findChild("slrTableSubmitButton"); QVERIFY(table != nullptr); + QVERIFY(guidedButton != nullptr); + QVERIFY(submitButton != nullptr); QtModalTestUtils::requestSlrGuidedMode( dialog, QVector>(table->rowCount(), QVector(table->columnCount()))); - QWizard* wizard = waitForWizard(); + SLRWizard* wizard = waitForWizard(); QVERIFY(wizard != nullptr); - QPointer wizardGuard(wizard); + QPointer wizardGuard(wizard); + + QVERIFY(!guidedButton->isEnabled()); + QVERIFY(!submitButton->isEnabled()); SlrTutorTestUtils::finishWizard(wizard); QTRY_VERIFY(wizardGuard == nullptr || !wizardGuard->isVisible()); + QVERIFY(guidedButton->isEnabled()); + QVERIFY(submitButton->isEnabled()); QVERIFY(dialog->isVisible()); QCOMPARE(tutor.currentStateForTest(), QString("H")); }