1 /*
2     SPDX-FileCopyrightText: 2009-2010 Frederik Gladhorn <gladhorn@kde.org>
3     SPDX-FileCopyrightText: 2009 Daniel Laidig <d.laidig@gmx.de>
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "practicestatemachine.h"
8 
9 #include "parleydocument.h"
10 
11 #include "comparisonbackendmode.h"
12 #include "conjugationbackendmode.h"
13 #include "examplesentencebackendmode.h"
14 #include "flashcardbackendmode.h"
15 #include "genderbackendmode.h"
16 #include "multiplechoicebackendmode.h"
17 #include "prefs.h"
18 #include "writtenbackendmode.h"
19 
20 using namespace Practice;
21 
PracticeStateMachine(AbstractFrontend * frontend,ParleyDocument * doc,SessionManagerBase * sessionManager,QObject * parent)22 PracticeStateMachine::PracticeStateMachine(AbstractFrontend *frontend, ParleyDocument *doc, SessionManagerBase *sessionManager, QObject *parent)
23     : QObject(parent)
24     , m_frontend(frontend)
25     , m_document(doc)
26     , m_sessionManager(sessionManager)
27 {
28     createPracticeMode();
29 
30     // To allow to skip an an entry
31     connect(m_frontend, &AbstractFrontend::skipAction, this, &PracticeStateMachine::nextEntry);
32     connect(m_frontend, &AbstractFrontend::stopPractice, this, &PracticeStateMachine::slotPracticeFinished);
33     connect(m_frontend, &AbstractFrontend::hintAction, m_mode, &AbstractBackendMode::hintAction);
34 
35     connect(m_frontend, &AbstractFrontend::continueAction, this, &PracticeStateMachine::continueAction);
36 
37     connect(m_mode, &AbstractBackendMode::answerRight, this, &PracticeStateMachine::answerRight);
38     connect(m_mode, &AbstractBackendMode::answerWrongRetry, this, &PracticeStateMachine::answerWrongRetry);
39     connect(m_mode, &AbstractBackendMode::answerWrongShowSolution, this, &PracticeStateMachine::answerWrongShowSolution);
40     connect(m_mode, &AbstractBackendMode::showSolution, this, &PracticeStateMachine::showSolution);
41 }
42 
createPracticeMode()43 void PracticeStateMachine::createPracticeMode()
44 {
45     switch (Prefs::practiceMode()) {
46     case Prefs::EnumPracticeMode::FlashCardsPractice:
47         qDebug() << "Create Flash Card Practice backend";
48         m_frontend->setMode(AbstractFrontend::FlashCard);
49         m_mode = new FlashCardBackendMode(m_frontend, this);
50         break;
51     case Prefs::EnumPracticeMode::MultipleChoicePractice:
52         qDebug() << "Create MultipleChoice Practice backend";
53         m_frontend->setMode(AbstractFrontend::MultipleChoice);
54         m_mode = new MultipleChoiceBackendMode(m_frontend, this, m_sessionManager);
55         break;
56     case Prefs::EnumPracticeMode::MixedLettersPractice:
57         qDebug() << "Create Mixed Letters Practice backend";
58         m_frontend->setMode(AbstractFrontend::MixedLetters);
59         m_mode = new WrittenBackendMode(m_frontend, this, m_sessionManager, m_document->document().get());
60         break;
61     case Prefs::EnumPracticeMode::WrittenPractice:
62         qDebug() << "Create Written Practice backend";
63         m_frontend->setMode(AbstractFrontend::Written);
64         m_mode = new WrittenBackendMode(m_frontend, this, m_sessionManager, m_document->document().get());
65         break;
66     case Prefs::EnumPracticeMode::ExampleSentencesPractice:
67         qDebug() << "Create Written Practice backend";
68         m_frontend->setMode(AbstractFrontend::ExampleSentence);
69         m_mode = new ExampleSentenceBackendMode(m_frontend, this, m_sessionManager, m_document->document().get());
70         break;
71     case Prefs::EnumPracticeMode::GenderPractice:
72         m_frontend->setMode(AbstractFrontend::MultipleChoice);
73         m_mode = new GenderBackendMode(m_frontend, this, m_sessionManager, m_document->document().get());
74         break;
75     case Prefs::EnumPracticeMode::ConjugationPractice:
76         m_frontend->setMode(AbstractFrontend::Conjugation);
77         m_mode = new ConjugationBackendMode(m_frontend, this, m_sessionManager, m_document->document().get());
78         break;
79     case Prefs::EnumPracticeMode::ComparisonPractice:
80         m_frontend->setMode(AbstractFrontend::Comparison);
81         m_mode = new ComparisonBackendMode(m_frontend, this, m_sessionManager, m_document->document().get());
82         break;
83 
84     default:
85         Q_ASSERT("Implement selected practice mode" == 0);
86     }
87 }
88 
start()89 void Practice::PracticeStateMachine::start()
90 {
91     qDebug() << "Start practice";
92     m_sessionManager->practiceStarted();
93     nextEntry();
94 }
95 
nextEntry()96 void PracticeStateMachine::nextEntry()
97 {
98     m_state = NotAnswered;
99     m_current = m_sessionManager->nextTrainingEntry();
100 
101     // qDebug() << "GETTING ENTRY - " << m_current;
102 
103     // after going through all words, or at the start of practice
104     if (m_current == 0) {
105         slotPracticeFinished();
106         return;
107     }
108     if (!m_mode->setTestEntry(m_current)) {
109         // this is just a fall back, if an invalid entry slipped through
110         currentEntryFinished();
111         nextEntry();
112     }
113     updateFrontend();
114 }
115 
slotPracticeFinished()116 void PracticeStateMachine::slotPracticeFinished()
117 {
118     qDebug() << "Stop practice";
119     m_sessionManager->practiceFinished();
120     emit practiceFinished();
121 }
122 
currentEntryFinished()123 void PracticeStateMachine::currentEntryFinished()
124 {
125     m_sessionManager->removeCurrentEntryFromPractice();
126 }
127 
continueAction()128 void PracticeStateMachine::continueAction()
129 {
130     // qDebug() << "continue" << m_state;
131     switch (m_state) {
132         // on continue, we check the answer, if in NotAnsweredState or AnswerWasWrongState
133     case NotAnswered:
134     case AnswerWasWrong:
135         m_mode->checkAnswer();
136         break;
137 
138     case SolutionShown:
139         gradeEntryAndContinue();
140         break;
141     }
142 }
143 
answerRight()144 void PracticeStateMachine::answerRight()
145 {
146     // qDebug() << "ans right";
147 
148     m_frontend->setFeedbackState(AbstractFrontend::AnswerCorrect);
149     if (m_state == NotAnswered) {
150         m_frontend->setResultState(AbstractFrontend::AnswerCorrect);
151     } else {
152         m_frontend->setResultState(AbstractFrontend::AnswerWrong);
153     }
154 
155     m_state = SolutionShown;
156     m_frontend->showSolution();
157 }
158 
answerWrongRetry()159 void PracticeStateMachine::answerWrongRetry()
160 {
161     // qDebug() << "wrong retr";
162     m_frontend->setFeedbackState(AbstractFrontend::AnswerWrong);
163     m_state = AnswerWasWrong;
164 }
165 
answerWrongShowSolution()166 void PracticeStateMachine::answerWrongShowSolution()
167 {
168     // qDebug() << "wrong sol";
169     m_frontend->setFeedbackState(AbstractFrontend::AnswerWrong);
170     // User gave an empty answer or the same answer for a second time so we want to drop out.
171     m_frontend->setResultState(AbstractFrontend::AnswerWrong);
172     m_state = SolutionShown;
173     m_frontend->showSolution();
174 }
175 
showSolution()176 void PracticeStateMachine::showSolution()
177 {
178     // qDebug() << "show solution";
179     m_state = SolutionShown;
180     m_frontend->showSolution();
181 }
182 
updateFrontend()183 void PracticeStateMachine::updateFrontend()
184 {
185     m_frontend->setFeedbackState(AbstractFrontend::QuestionState);
186     m_frontend->setResultState(AbstractFrontend::QuestionState);
187     m_frontend->setLessonName(m_current->entry()->lesson()->name());
188     grade_t grade = m_mode->currentGradeForEntry();
189     m_frontend->showGrade(m_mode->currentPreGradeForEntry(), grade);
190 
191     // show the word that is currently practiced in the progress bar
192     m_frontend->setFinishedWordsTotalWords(m_sessionManager->allEntryCount() - m_sessionManager->activeEntryCount(), m_sessionManager->allEntryCount());
193 
194     // Set fonts
195     m_frontend->setQuestionFont((m_current->languageFrom() == Prefs::learningLanguage()) ? m_frontend->learningLangFont() : m_frontend->knownLangFont());
196     m_frontend->setSolutionFont((m_current->languageTo() == Prefs::learningLanguage()) ? m_frontend->learningLangFont() : m_frontend->knownLangFont());
197 
198     grade_t goodGrade = qMax(grade, grade_t(KV_LEV1_GRADE)); // if the word hasn't been practiced yet, use grade 1 as a base
199 
200     if (m_current->statisticBadCount() == 0) {
201         goodGrade = qMax(KV_LEV2_GRADE, qMin(grade + 1, KV_MAX_GRADE));
202     }
203 
204     m_frontend->setBoxes(grade, goodGrade, KV_LEV1_GRADE);
205 
206     QUrl imgFrom = m_current->entry()->translation(m_current->languageFrom())->imageUrl();
207     QUrl imgTo = m_current->entry()->translation(m_current->languageTo())->imageUrl();
208     if (imgFrom.isEmpty()) {
209         imgFrom = imgTo;
210     }
211     if (imgTo.isEmpty()) {
212         imgTo = imgFrom;
213     }
214     if (Prefs::flashcardsFrontImage()) {
215         m_frontend->setQuestionImage(imgFrom);
216     } else {
217         m_frontend->setQuestionImage(QUrl());
218     }
219     if (Prefs::flashcardsBackImage()) {
220         m_frontend->setSolutionImage(imgTo);
221     } else {
222         m_frontend->setSolutionImage(QUrl());
223     }
224     m_frontend->showQuestion();
225 }
226 
gradeEntryAndContinue()227 void PracticeStateMachine::gradeEntryAndContinue()
228 {
229     grade_t currentPreGrade = m_mode->currentPreGradeForEntry();
230     grade_t currentGrade = m_mode->currentGradeForEntry();
231 
232     if (m_frontend->resultState() == AbstractFrontend::AnswerCorrect) {
233         m_current->updateStatisticsRightAnswer(currentPreGrade, currentGrade);
234     } else {
235         m_current->updateStatisticsWrongAnswer(currentPreGrade, currentGrade);
236     }
237 
238     if (m_current->shouldChangeGrades()) {
239         m_mode->updateGrades();
240         if (m_frontend->resultState() == AbstractFrontend::AnswerCorrect) {
241             currentEntryFinished();
242         }
243     }
244     nextEntry();
245 }
246