[education/minuet] src/app: Let the user decide about tests' next question autoplay (default=false)

Sandro S. Andrade <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit 297b80c86539624f99bcc643c99a7df0eee9ba97 by Sandro S. Andrade.
Committed on 10/08/2026 at 19:34.
Pushed by sandroandrade into branch 'master'.

Let the user decide about tests' next question autoplay (default=false)

M  +28   -0    src/app/autotests/settingscontrollertest.cpp
M  +2    -2    src/app/qml/exercises/ExerciseClappingView.qml
M  +2    -2    src/app/qml/exercises/ExerciseSingingView.qml
M  +5    -4    src/app/qml/exercises/ExerciseView.qml
M  +7    -1    src/app/qml/pages/SettingsPage.qml
M  +17   -0    src/app/settingscontroller.cpp
M  +6    -0    src/app/settingscontroller.h

https://invent.kde.org/education/minuet/-/commit/297b80c86539624f99bcc643c99a7df0eee9ba97

diff --git a/src/app/autotests/settingscontrollertest.cpp b/src/app/autotests/settingscontrollertest.cpp
index a175054..32c693e 100644
--- a/src/app/autotests/settingscontrollertest.cpp
+++ b/src/app/autotests/settingscontrollertest.cpp
@@ -6,6 +6,7 @@
 
 #include <QCoreApplication>
 #include <QSettings>
+#include <QSignalSpy>
 #include <QStandardPaths>
 #include <QTest>
 
@@ -21,6 +22,7 @@ private Q_SLOTS:
     void initTestCase();
     void init();
     void usesRhythmDefault();
+    void persistsAutomaticTestQuestionAdvancement();
     void routesTempoAndSubdivisionsByPlayMode();
     void clampsRhythmTempo();
 };
@@ -44,6 +46,32 @@ void SettingsControllerTest::usesRhythmDefault()
     QCOMPARE(controller.rhythmTempo(), 45);
 }
 
+void SettingsControllerTest::persistsAutomaticTestQuestionAdvancement()
+{
+    SettingsController controller;
+    QCOMPARE(controller.automaticallyAdvanceTestQuestions(), false);
+
+    QSignalSpy spy(&controller, &SettingsController::automaticallyAdvanceTestQuestionsChanged);
+    controller.setAutomaticallyAdvanceTestQuestions(true);
+    QCOMPARE(controller.automaticallyAdvanceTestQuestions(), true);
+    QCOMPARE(spy.count(), 1);
+    QCOMPARE(spy.at(0).at(0).toBool(), true);
+
+    controller.setAutomaticallyAdvanceTestQuestions(true);
+    QCOMPARE(spy.count(), 1);
+
+    SettingsController reloadedController;
+    QCOMPARE(reloadedController.automaticallyAdvanceTestQuestions(), true);
+
+    controller.setAutomaticallyAdvanceTestQuestions(false);
+    QCOMPARE(controller.automaticallyAdvanceTestQuestions(), false);
+    QCOMPARE(spy.count(), 2);
+    QCOMPARE(spy.at(1).at(0).toBool(), false);
+
+    SettingsController resetController;
+    QCOMPARE(resetController.automaticallyAdvanceTestQuestions(), false);
+}
+
 void SettingsControllerTest::routesTempoAndSubdivisionsByPlayMode()
 {
     SettingsController controller;
diff --git a/src/app/qml/exercises/ExerciseClappingView.qml b/src/app/qml/exercises/ExerciseClappingView.qml
index 219cb33..ee43413 100644
--- a/src/app/qml/exercises/ExerciseClappingView.qml
+++ b/src/app/qml/exercises/ExerciseClappingView.qml
@@ -157,7 +157,7 @@ ExerciseContent {
         const finalScore = Core.exerciseSessionController.recordTestScore(internal.score, Core.settingsController.testExerciseCount);
         if (finalScore >= 0) {
             internal.score = finalScore;
-        } else {
+        } else if (Core.settingsController.automaticallyAdvanceTestQuestions) {
             testNextQuestionTimer.restart();
         }
     }
@@ -461,7 +461,7 @@ ExerciseContent {
 
                 Layout.preferredWidth: exerciseHeader.actionButtonWidth
                 enabled: internal.microphoneReady && internal.viewState !== "counting" && internal.viewState !== "listening" && internal.viewState !== "analyzing"
-                text: internal.expectedOnsets.length === 0 || internal.viewState === "finished" ? i18n("New Question") : i18n("Start")
+                text: Core.exerciseSessionController.isTest && internal.viewState === "finished" ? i18n("Next Question") : internal.expectedOnsets.length === 0 || internal.viewState === "finished" ? i18n("New Question") : i18n("Start")
 
                 onClicked: {
                     if (internal.expectedOnsets.length === 0 || internal.viewState === "finished") {
diff --git a/src/app/qml/exercises/ExerciseSingingView.qml b/src/app/qml/exercises/ExerciseSingingView.qml
index ea2d3bf..6823fc2 100644
--- a/src/app/qml/exercises/ExerciseSingingView.qml
+++ b/src/app/qml/exercises/ExerciseSingingView.qml
@@ -190,7 +190,7 @@ ExerciseContent {
         const finalScore = Core.exerciseSessionController.recordTestScore(internal.score, Core.settingsController.testExerciseCount);
         if (finalScore >= 0) {
             internal.score = finalScore;
-        } else {
+        } else if (Core.settingsController.automaticallyAdvanceTestQuestions) {
             testNextQuestionTimer.restart();
         }
     }
@@ -687,7 +687,7 @@ ExerciseContent {
 
                 Layout.preferredWidth: exerciseHeader.actionButtonWidth
                 enabled: internal.microphoneReady && internal.viewState !== "counting" && internal.viewState !== "listening" && internal.viewState !== "analyzing"
-                text: internal.targetNotes.length === 0 || internal.viewState === "finished" ? i18n("New Question") : i18n("Start")
+                text: Core.exerciseSessionController.isTest && internal.viewState === "finished" ? i18n("Next Question") : internal.targetNotes.length === 0 || internal.viewState === "finished" ? i18n("New Question") : i18n("Start")
 
                 onClicked: {
                     if (internal.targetNotes.length === 0 || internal.viewState === "finished") {
diff --git a/src/app/qml/exercises/ExerciseView.qml b/src/app/qml/exercises/ExerciseView.qml
index faaf7ce..4dff992 100644
--- a/src/app/qml/exercises/ExerciseView.qml
+++ b/src/app/qml/exercises/ExerciseView.qml
@@ -27,7 +27,7 @@ ExerciseContent {
             highlightRightAnswer();
         } else {
             exerciseView.state = "waitingForNewQuestion";
-            if (Core.exerciseSessionController.isTest) {
+            if (Core.exerciseSessionController.isTest && Core.settingsController.automaticallyAdvanceTestQuestions) {
                 testFeedbackTimer.restart();
             }
         }
@@ -67,8 +67,8 @@ ExerciseContent {
         return Core.exerciseSessionController.colorForAnswerIndex(index, internal.colors);
     }
     function finishSingleAnswerFeedback(): void {
-        exerciseView.state = Core.exerciseSessionController.isTest ? "waitingForAnswer" : "waitingForNewQuestion";
-        if (Core.exerciseSessionController.isTest) {
+        exerciseView.state = "waitingForNewQuestion";
+        if (Core.exerciseSessionController.isTest && Core.settingsController.automaticallyAdvanceTestQuestions) {
             nextTestExercise();
             if (Core.exerciseSessionController.currentExercise === internal.maximumExercises + 1)
                 Core.exerciseSessionController.resetTest();
@@ -121,6 +121,7 @@ ExerciseContent {
                 answerItem.opacity = 1;
             }
         }
+        Core.soundController.stop();
         generateNewQuestion();
         Core.soundController.play();
     }
@@ -365,7 +366,7 @@ ExerciseContent {
 
                     Layout.preferredWidth: exerciseHeader.actionButtonWidth
                     enabled: !animation.running && !testFeedbackTimer.running && !internal.exercisePlaying
-                    text: exerciseView.state === "waitingForNewQuestion" ? i18n("New Question") : i18n("Play Question")
+                    text: exerciseView.state === "waitingForNewQuestion" ? Core.exerciseSessionController.isTest ? i18n("Next Question") : i18n("New Question") : i18n("Play Question")
 
                     onClicked: {
                         if (exerciseView.state === "waitingForNewQuestion") {
diff --git a/src/app/qml/pages/SettingsPage.qml b/src/app/qml/pages/SettingsPage.qml
index e87371a..0643caf 100644
--- a/src/app/qml/pages/SettingsPage.qml
+++ b/src/app/qml/pages/SettingsPage.qml
@@ -145,7 +145,7 @@ FormCard.FormCardPage {
 
             contentItem: SettingsSlider {
                 from: 5
-                label: i18n("Number of exercises")
+                label: i18n("Number of exercises in tests")
                 to: 20
                 value: Core.settingsController.testExerciseCount
 
@@ -154,6 +154,12 @@ FormCard.FormCardPage {
                 }
             }
         }
+        FormCard.FormSwitchDelegate {
+            checked: Core.settingsController.automaticallyAdvanceTestQuestions
+            text: i18n("Automatically advance to the next test question")
+
+            onToggled: Core.settingsController.automaticallyAdvanceTestQuestions = checked
+        }
     }
     FormCard.FormHeader {
         title: i18n("Sound")
diff --git a/src/app/settingscontroller.cpp b/src/app/settingscontroller.cpp
index fcaf63e..a079743 100644
--- a/src/app/settingscontroller.cpp
+++ b/src/app/settingscontroller.cpp
@@ -24,6 +24,7 @@ void SettingsController::load()
     settings.beginGroup(u"Settings"_s);
     m_rhythmPatternCount = std::clamp(settings.value(u"RhythmPatternCount"_s, m_rhythmPatternCount).toInt(), 4, 16);
     m_testExerciseCount = std::clamp(settings.value(u"TestExerciseCount"_s, m_testExerciseCount).toInt(), 5, 20);
+    m_automaticallyAdvanceTestQuestions = settings.value(u"AutomaticallyAdvanceTestQuestions"_s, m_automaticallyAdvanceTestQuestions).toBool();
     m_volume = std::clamp(settings.value(u"Volume"_s, m_volume).toInt(), 0, 200);
     m_pitch = std::clamp(settings.value(u"Pitch"_s, m_pitch).toInt(), -12, 12);
     m_tempo = std::clamp(settings.value(u"Tempo"_s, m_tempo).toInt(), 1, 255);
@@ -96,6 +97,11 @@ int SettingsController::testExerciseCount() const
     return m_testExerciseCount;
 }
 
+bool SettingsController::automaticallyAdvanceTestQuestions() const
+{
+    return m_automaticallyAdvanceTestQuestions;
+}
+
 int SettingsController::volume() const
 {
     return m_volume;
@@ -334,6 +340,17 @@ void SettingsController::setTestExerciseCount(int testExerciseCount)
     emit testExerciseCountChanged(m_testExerciseCount);
 }
 
+void SettingsController::setAutomaticallyAdvanceTestQuestions(bool automaticallyAdvance)
+{
+    if (m_automaticallyAdvanceTestQuestions == automaticallyAdvance) {
+        return;
+    }
+
+    m_automaticallyAdvanceTestQuestions = automaticallyAdvance;
+    write(u"AutomaticallyAdvanceTestQuestions"_s, m_automaticallyAdvanceTestQuestions);
+    emit automaticallyAdvanceTestQuestionsChanged(m_automaticallyAdvanceTestQuestions);
+}
+
 void SettingsController::setVolume(int volume)
 {
     volume = std::clamp(volume, 0, 200);
diff --git a/src/app/settingscontroller.h b/src/app/settingscontroller.h
index d297c74..7e2ad52 100644
--- a/src/app/settingscontroller.h
+++ b/src/app/settingscontroller.h
@@ -16,6 +16,8 @@ class SettingsController : public QObject
     Q_OBJECT
     Q_PROPERTY(int rhythmPatternCount READ rhythmPatternCount WRITE setRhythmPatternCount NOTIFY rhythmPatternCountChanged)
     Q_PROPERTY(int testExerciseCount READ testExerciseCount WRITE setTestExerciseCount NOTIFY testExerciseCountChanged)
+    Q_PROPERTY(bool automaticallyAdvanceTestQuestions READ automaticallyAdvanceTestQuestions WRITE setAutomaticallyAdvanceTestQuestions NOTIFY
+                   automaticallyAdvanceTestQuestionsChanged)
     Q_PROPERTY(int volume READ volume WRITE setVolume NOTIFY volumeChanged)
     Q_PROPERTY(int pitch READ pitch WRITE setPitch NOTIFY pitchChanged)
     Q_PROPERTY(int tempo READ tempo WRITE setTempo NOTIFY tempoChanged)
@@ -62,6 +64,7 @@ public:
 
     int rhythmPatternCount() const;
     int testExerciseCount() const;
+    bool automaticallyAdvanceTestQuestions() const;
     int volume() const;
     int pitch() const;
     int tempo() const;
@@ -100,6 +103,7 @@ public:
 public Q_SLOTS:
     void setRhythmPatternCount(int rhythmPatternCount);
     void setTestExerciseCount(int testExerciseCount);
+    void setAutomaticallyAdvanceTestQuestions(bool automaticallyAdvance);
     void setVolume(int volume);
     void setPitch(int pitch);
     void setTempo(int tempo);
@@ -133,6 +137,7 @@ public Q_SLOTS:
 Q_SIGNALS:
     void rhythmPatternCountChanged(int rhythmPatternCount);
     void testExerciseCountChanged(int testExerciseCount);
+    void automaticallyAdvanceTestQuestionsChanged(bool automaticallyAdvance);
     void volumeChanged(int volume);
     void pitchChanged(int pitch);
     void tempoChanged(int tempo);
@@ -191,6 +196,7 @@ private:
 
     int m_rhythmPatternCount = 4;
     int m_testExerciseCount = 10;
+    bool m_automaticallyAdvanceTestQuestions = false;
     int m_volume = 100;
     int m_pitch = 0;
     int m_tempo = 60;
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.