1 /*
2 SPDX-FileCopyrightText: 2013-2019 Andreas Cord-Landwehr <cordlandwehr@kde.org>
3
4 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
5 */
6
7 #include "trainingsession.h"
8 #include "artikulate_debug.h"
9 #include "core/icourse.h"
10 #include "core/language.h"
11 #include "core/phrase.h"
12 #include "core/unit.h"
13 #include "learner.h"
14 #include "profilemanager.h"
15 #include "trainingaction.h"
16
TrainingSession(LearnerProfile::ProfileManager * manager,QObject * parent)17 TrainingSession::TrainingSession(LearnerProfile::ProfileManager *manager, QObject *parent)
18 : ISessionActions(parent)
19 , m_profileManager(manager)
20 , m_course(nullptr)
21 {
22 Q_ASSERT(m_profileManager != nullptr);
23 }
24
course() const25 ICourse *TrainingSession::course() const
26 {
27 return m_course;
28 }
29
setCourse(ICourse * course)30 void TrainingSession::setCourse(ICourse *course)
31 {
32 if (!course) {
33 updateTrainingActions();
34 return;
35 }
36 if (m_course == course) {
37 return;
38 }
39 m_course = course;
40 if (m_course && m_course->units().count() > 0) {
41 setUnit(m_course->units().constFirst().get());
42 }
43
44 // lazy loading of training data
45 LearnerProfile::LearningGoal *goal = m_profileManager->goal(LearnerProfile::LearningGoal::Language, m_course->id());
46 if (!goal) {
47 goal = m_profileManager->registerGoal(LearnerProfile::LearningGoal::Language, course->language()->id(), course->language()->i18nTitle());
48 }
49 auto data = m_profileManager->progressValues(m_profileManager->activeProfile(), goal, m_course->id());
50 const auto unitList = m_course->units();
51 for (auto unit : qAsConst(unitList)) {
52 const auto phrases = unit->phrases();
53 for (auto &phrase : phrases) {
54 auto iter = data.find(phrase->id());
55 if (iter != data.end()) {
56 // phrase->setProgress(iter.value()); //FIXME add a decorator?
57 }
58 }
59 }
60 updateTrainingActions();
61 emit courseChanged();
62 }
63
activeUnit() const64 IUnit *TrainingSession::activeUnit() const
65 {
66 if (auto phrase = activePhrase()) {
67 return phrase->unit().get();
68 }
69 return nullptr;
70 }
71
setUnit(IUnit * unit)72 void TrainingSession::setUnit(IUnit *unit)
73 {
74 // checking phrases in increasing order ensures that always the first phrase is selected
75 for (int i = 0; i < m_actions.count(); ++i) {
76 for (int j = 0; j < m_actions.at(i)->actions().count(); ++j) {
77 const auto testPhrase = qobject_cast<TrainingAction *>(m_actions.at(i)->actions().at(j))->phrase();
78 if (unit == testPhrase->unit().get()) {
79 if (auto action = activeAction()) {
80 action->setChecked(false);
81 }
82 m_indexUnit = i;
83 m_indexPhrase = j;
84 if (auto action = activeAction()) {
85 action->setChecked(true);
86 }
87 emit phraseChanged();
88 return;
89 }
90 }
91 }
92 }
93
activeAction() const94 TrainingAction *TrainingSession::activeAction() const
95 {
96 if (m_indexUnit < 0 || m_indexPhrase < 0) {
97 return nullptr;
98 }
99 return qobject_cast<TrainingAction *>(m_actions.at(m_indexUnit)->actions().at(m_indexPhrase));
100 }
101
activePhrase() const102 IPhrase *TrainingSession::activePhrase() const
103 {
104 if (const auto action = activeAction()) {
105 return action->phrase();
106 }
107 return nullptr;
108 }
109
setActivePhrase(IPhrase * phrase)110 void TrainingSession::setActivePhrase(IPhrase *phrase)
111 {
112 for (int i = 0; i < m_actions.count(); ++i) {
113 for (int j = 0; j < m_actions.at(i)->actions().count(); ++j) {
114 const auto testPhrase = qobject_cast<TrainingAction *>(m_actions.at(i)->actions().at(j))->phrase();
115 if (phrase == testPhrase) {
116 if (auto action = activeAction()) {
117 action->setChecked(false);
118 }
119 m_indexUnit = i;
120 m_indexPhrase = j;
121 if (auto action = activeAction()) {
122 action->setChecked(true);
123 }
124 emit phraseChanged();
125 return;
126 }
127 }
128 }
129 }
130
accept()131 void TrainingSession::accept()
132 {
133 Q_ASSERT(m_indexUnit >= 0);
134 Q_ASSERT(m_indexPhrase >= 0);
135 if (m_indexUnit < 0 || m_indexPhrase < 0) {
136 return;
137 }
138 auto phrase = activePhrase();
139
140 // possibly update goals of learner
141 updateGoal();
142 // phrase->updateProgress(Phrase::Progress::Done); //FIXME
143
144 // store training activity
145 LearnerProfile::LearningGoal *goal = m_profileManager->goal(LearnerProfile::LearningGoal::Language, m_course->language()->id());
146 // m_profileManager->recordProgress(m_profileManager->activeProfile(), //FIXME
147 // goal,
148 // m_course->id(),
149 // phrase->id(),
150 // static_cast<int>(LearnerProfile::ProfileManager::Skip),
151 // phrase->progress()
152 // );
153
154 selectNextPhrase();
155 }
156
skip()157 void TrainingSession::skip()
158 {
159 Q_ASSERT(m_indexUnit >= 0);
160 Q_ASSERT(m_indexPhrase >= 0);
161 if (m_indexUnit < 0 || m_indexPhrase < 0) {
162 return;
163 }
164
165 // possibly update goals of learner
166 updateGoal();
167 auto phrase = activePhrase();
168 // phrase->updateProgress(Phrase::Progress::Skip); //FIXME
169
170 // store training activity
171 LearnerProfile::LearningGoal *goal = m_profileManager->goal(LearnerProfile::LearningGoal::Language, m_course->language()->id());
172 // m_profileManager->recordProgress(m_profileManager->activeProfile(),
173 // goal,
174 // m_course->id(),
175 // phrase->id(),
176 // static_cast<int>(LearnerProfile::ProfileManager::Skip),
177 // phrase->progress()
178 // ); // FIXME
179
180 selectNextPhrase();
181 }
182
selectNextPhrase()183 void TrainingSession::selectNextPhrase()
184 {
185 if (auto action = activeAction()) {
186 action->setChecked(false);
187 }
188 // try to find next phrase, otherwise return completed
189 // 1. case: last phrase in unit
190 if (m_indexPhrase >= m_actions.at(m_indexUnit)->actions().count() - 1) {
191 // close current unit
192 emit closeUnit();
193 qDebug() << "switching to next unit";
194 if (m_indexUnit >= m_actions.count() - 1) {
195 emit completed();
196 } else {
197 ++m_indexUnit;
198 m_indexPhrase = 0;
199 // currently there is no way to automatically enter a submenu, at least inform user about new unit
200 m_actions.at(m_indexUnit)->setChecked(true);
201 }
202 // 2. case: next phrase inside current unit
203 } else {
204 ++m_indexPhrase;
205 }
206 if (auto action = activeAction()) {
207 action->setChecked(true);
208 }
209 emit phraseChanged();
210 }
211
hasPrevious() const212 bool TrainingSession::hasPrevious() const
213 {
214 return m_indexUnit > 0 || m_indexPhrase > 0;
215 }
216
hasNext() const217 bool TrainingSession::hasNext() const
218 {
219 if (m_indexUnit < m_actions.count() - 1) {
220 return true;
221 }
222 if (!m_actions.isEmpty() && m_actions.constLast()) {
223 if (m_indexPhrase < m_actions.constLast()->actions().count() - 1) {
224 return true;
225 }
226 }
227 return false;
228 }
229
updateGoal()230 void TrainingSession::updateGoal()
231 {
232 if (!m_profileManager) {
233 qCWarning(ARTIKULATE_LOG()) << "No ProfileManager registered, aborting operation";
234 return;
235 }
236 LearnerProfile::Learner *learner = m_profileManager->activeProfile();
237 if (!learner) {
238 qCWarning(ARTIKULATE_LOG()) << "No active Learner registered, aborting operation";
239 return;
240 }
241 LearnerProfile::LearningGoal *goal = m_profileManager->goal(LearnerProfile::LearningGoal::Language, m_course->language()->id());
242 learner->addGoal(goal);
243 learner->setActiveGoal(goal);
244 }
245
trainingActions() const246 QVector<TrainingAction *> TrainingSession::trainingActions() const
247 {
248 return m_actions;
249 }
250
updateTrainingActions()251 void TrainingSession::updateTrainingActions()
252 {
253 for (const auto &action : qAsConst(m_actions)) {
254 action->deleteLater();
255 }
256 m_actions.clear();
257
258 if (!m_course) {
259 m_indexUnit = -1;
260 m_indexPhrase = -1;
261 return;
262 }
263
264 const auto unitList = m_course->units();
265 for (const auto &unit : qAsConst(unitList)) {
266 auto action = new TrainingAction(unit->title(), this);
267 const auto phraseList = unit->phrases();
268 for (const auto &phrase : qAsConst(phraseList)) {
269 if (phrase->sound().isEmpty()) {
270 continue;
271 }
272 action->appendAction(new TrainingAction(phrase, this, unit.get()));
273 }
274 if (action->actions().count() > 0) {
275 m_actions.append(action);
276 } else {
277 action->deleteLater();
278 }
279 }
280
281 // update indices
282 m_indexUnit = -1;
283 m_indexPhrase = -1;
284 if (m_course->units().count() > 0) {
285 m_indexUnit = 0;
286 if (m_course->units().constFirst()->phrases().count() > 0) {
287 m_indexPhrase = 0;
288 }
289 }
290 }
291