File indexing completed on 2024-04-21 03:51:06
0001 /* 0002 SPDX-FileCopyrightText: 1999-2001 Ewald Arnold <kvoctrain@ewald-arnold.de> 0003 SPDX-FileCopyrightText: 2005-2007 Peter Hedlund <peter.hedlund@kdemail.net> 0004 SPDX-FileCopyrightText: 2007-2009 Frederik Gladhorn <gladhorn@kde.org> 0005 SPDX-FileCopyrightText: 2014 Inge Wallin <inge@lysator.liu.se> 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "sessionmanagerbase.h" 0010 0011 // Qt 0012 #include <QDateTime> 0013 #include <QRandomGenerator> 0014 0015 // kdelibs 0016 #include <KConfig> 0017 #include <KLocalizedString> 0018 #include <KMessageBox> 0019 #include <QDebug> 0020 0021 // kdeedulibs 0022 #include <KEduVocDocument> 0023 #include <KEduVocLesson> 0024 0025 // parley 0026 #include "entryfilter.h" 0027 #include <prefs.h> 0028 0029 using namespace Practice; 0030 0031 SessionManagerBase::SessionManagerBase(QWidget *parent) 0032 : m_parent(parent) 0033 { 0034 } 0035 0036 SessionManagerBase::~SessionManagerBase() 0037 { 0038 qDeleteAll(m_allTestEntries); 0039 } 0040 0041 void SessionManagerBase::setDocument(KEduVocDocument *doc) 0042 { 0043 qDeleteAll(m_allTestEntries); 0044 m_allTestEntries.clear(); 0045 m_notAskedTestEntries.clear(); 0046 m_currentEntries.clear(); 0047 0048 m_doc = doc; 0049 0050 // Make sure that there are at least 2 languages in the file before 0051 // starting the practice. If we don't do this, it will crash later. 0052 if (m_doc->identifierCount() < 2) { 0053 KMessageBox::error(nullptr, i18n("The vocabulary collection contains fewer than two languages."), i18n("Could not start practice")); 0054 return; 0055 } 0056 0057 if (Prefs::learningLanguage() >= m_doc->identifierCount() || Prefs::knownLanguage() >= m_doc->identifierCount()) { 0058 Prefs::setLearningLanguage(0); 0059 Prefs::setKnownLanguage(1); 0060 } 0061 0062 m_learningLanguageIndex = Prefs::learningLanguage(); 0063 m_knownLanguageIndex = Prefs::knownLanguage(); 0064 // qDebug() << "Practice: learning language:" << m_doc->identifier(m_learningLanguageIndex).name() 0065 // << " known language:" << m_doc->identifier(m_knownLanguageIndex).name(); 0066 0067 // Create the list of available entries for this training session. 0068 EntryFilter filter(m_doc, m_parent); 0069 m_allTestEntries = filter.entries(); 0070 0071 // qDebug() << "Entries: ----------------"; 0072 // qDebug() << "Found " << m_allTestEntries.count() << " entries after filtering."; 0073 // for (TestEntry *entry : qAsConst(m_allTestEntries)) { 0074 // qDebug() << "Entry: " << entry->languageFrom() << entry->entry()->translation(entry->languageFrom())->text() 0075 // << "to" << entry->languageTo() << entry->entry()->translation(entry->languageTo())->text(); 0076 // } 0077 0078 // Create the list actual entries in this training session. This 0079 // is a pure virtual function and must be implemented by the 0080 // concrete session managers. 0081 initializeTraining(); 0082 } 0083 0084 QString SessionManagerBase::title() const 0085 { 0086 return m_doc->title(); 0087 } 0088 0089 void SessionManagerBase::practiceStarted() 0090 { 0091 qDebug() << "start practice timer"; 0092 m_time.start(); 0093 } 0094 0095 void SessionManagerBase::practiceFinished() 0096 { 0097 m_totalTime = m_time.elapsed(); 0098 qDebug() << "stop practice timer" << m_totalTime; 0099 } 0100 0101 int SessionManagerBase::totalTime() 0102 { 0103 // seconds instead of ms 0104 return m_totalTime / (1000); 0105 } 0106 0107 TestEntry *SessionManagerBase::nextTrainingEntry() 0108 { 0109 // In some session types an entry will move out of the "current 0110 // entries" when they are correctly answered and then we need to 0111 // refill. 0112 while (m_currentEntries.count() < Prefs::testNumberOfEntries() && m_notAskedTestEntries.count() > 0) { 0113 m_currentEntries.append(m_notAskedTestEntries.takeAt(0)); 0114 } 0115 0116 // Return one of the current entries, but not the same as last 0117 // time if possible. 0118 int lastEntry = m_currentEntry; 0119 if (m_currentEntries.count() > 0) { 0120 // Choose one of the current entries randomly. 0121 m_currentEntry = QRandomGenerator::global()->bounded(m_currentEntries.count()); 0122 0123 // Do not allow to ask the same entry twice in a row. 0124 if (m_currentEntries.count() > 1) { 0125 while (m_currentEntry == lastEntry) { 0126 m_currentEntry = QRandomGenerator::global()->bounded(m_currentEntries.count()); 0127 } 0128 } 0129 0130 qDebug() << "nextTrainingEntry:" << m_currentEntry << " = " << m_currentEntries.value(m_currentEntry)->entry()->translation(0)->text() << " (" 0131 << m_currentEntries.count() + m_notAskedTestEntries.count() << "entries remaining)"; 0132 0133 return m_currentEntries.value(m_currentEntry); 0134 } else { 0135 return nullptr; 0136 } 0137 } 0138 0139 void SessionManagerBase::removeCurrentEntryFromPractice() 0140 { 0141 if (m_currentEntry >= 0) { 0142 m_currentEntries.takeAt(m_currentEntry); 0143 } 0144 } 0145 0146 QList<TestEntry *> SessionManagerBase::allTestEntries() const 0147 { 0148 return m_allTestEntries; 0149 } 0150 0151 int SessionManagerBase::allEntryCount() const 0152 { 0153 return m_allTestEntries.count(); 0154 } 0155 0156 int SessionManagerBase::activeEntryCount() 0157 { 0158 return m_notAskedTestEntries.count() + m_currentEntries.count(); 0159 } 0160 0161 QList<TestEntry *> SessionManagerBase::allUnansweredTestEntries() 0162 { 0163 QList<TestEntry *> result; 0164 0165 result.append(m_notAskedTestEntries); 0166 result.append(m_currentEntries); 0167 0168 return result; 0169 } 0170 0171 // ---------------------------------------------------------------- 0172 // Statistics 0173 0174 int SessionManagerBase::statisticTotalCorrectFirstAttempt() 0175 { 0176 int count = 0; 0177 for (TestEntry *entry : qAsConst(m_allTestEntries)) { 0178 if (entry->correctAtFirstAttempt()) { 0179 count++; 0180 } 0181 } 0182 return count; 0183 } 0184 0185 int SessionManagerBase::statisticTotalWrong() 0186 { 0187 int count = 0; 0188 for (TestEntry *entry : qAsConst(m_allTestEntries)) { 0189 if (entry->statisticBadCount()) { 0190 count++; 0191 } 0192 } 0193 return count; 0194 } 0195 0196 int SessionManagerBase::statisticTotalUnanswered() 0197 { 0198 int count = 0; 0199 for (TestEntry *entry : qAsConst(m_allTestEntries)) { 0200 if (entry->statisticCount() == 0) { 0201 count++; 0202 } 0203 } 0204 return count; 0205 } 0206 0207 void SessionManagerBase::printStatistics() 0208 { 0209 qDebug() << "Test statistics: "; 0210 for (TestEntry *entry : qAsConst(m_allTestEntries)) { 0211 qDebug() << " asked: " << entry->statisticCount() << " +" << entry->statisticGoodCount() << " -" << entry->statisticBadCount() 0212 << "Entry: " << entry->entry()->translation(0)->text(); 0213 } 0214 } 0215 0216 // ---------------------------------------------------------------- 0217 // Multiple choice questions 0218 0219 QStringList SessionManagerBase::multipleChoiceAnswers(int numberChoices) 0220 { 0221 QStringList choices; 0222 QList<KEduVocExpression *> allEntries = m_doc->lesson()->entries(KEduVocLesson::Recursive); 0223 int numValidEntries = 0; 0224 int count = numberChoices; 0225 0226 // if the current entry has predefined multiple choice entries defined, use them first 0227 TestEntry *currentEntry = m_currentEntries.at(m_currentEntry); 0228 QStringList predefinedChoices = currentEntry->entry()->translation(currentEntry->languageTo())->getMultipleChoice(); 0229 while (!predefinedChoices.isEmpty() && count > 0) { 0230 choices.append(predefinedChoices.takeAt(QRandomGenerator::global()->bounded(predefinedChoices.count()))); 0231 count--; 0232 } 0233 0234 // find out if we got enough valid entries to fill all the options 0235 for (int i = 0; i < allEntries.count(); ++i) { 0236 if (isValidMultipleChoiceAnswer(allEntries.value(i))) 0237 numValidEntries++; 0238 if (numValidEntries >= numberChoices) 0239 break; 0240 } 0241 0242 // if we don't have enough valid entries, just try everything and use what we can get 0243 if (numValidEntries < numberChoices) { 0244 for (int i = choices.count(); i < allEntries.count(); ++i) { 0245 KEduVocExpression *exp = allEntries.value(i); 0246 0247 // FIXME: Use trainingmode2 also here! 0248 if (isValidMultipleChoiceAnswer(exp)) { 0249 choices.append(exp->translation(currentEntry->languageTo())->text()); 0250 } 0251 } 0252 } else { 0253 QList<KEduVocExpression *> exprlist; 0254 while (count > 0) { 0255 int nr; 0256 // if there are enough non-empty fields, fill the options only with those 0257 do { 0258 nr = QRandomGenerator::global()->bounded(allEntries.count()); 0259 } while (!isValidMultipleChoiceAnswer(allEntries.value(nr))); 0260 // append if new entry found 0261 bool newex = true; 0262 for (int i = 0; newex && i < exprlist.count(); i++) { 0263 if (exprlist[i] == allEntries.value(nr)) 0264 newex = false; 0265 } 0266 if (newex) { 0267 count--; 0268 exprlist.append(allEntries.value(nr)); 0269 } 0270 } 0271 0272 // FIXME: Use trainingmode2 too here 0273 for (int i = 0; i < exprlist.count(); i++) { 0274 choices.append(exprlist[i]->translation(currentEntry->languageTo())->text()); 0275 } 0276 } 0277 0278 qDebug() << "choices:" << choices << " answerLang = " << currentEntry->languageTo(); 0279 return choices; 0280 } 0281 0282 // ---------------------------------------------------------------- 0283 // Protected methods 0284 0285 bool SessionManagerBase::isValidMultipleChoiceAnswer(KEduVocExpression *e) 0286 { 0287 // entry is empty 0288 if (e->translation(m_learningLanguageIndex)->text().trimmed().isEmpty()) 0289 return false; 0290 0291 // FIXME: Must test individual solution & question languages per 0292 // entry in mixed mode training. 0293 // 0294 // entry is a synonym of the solution 0295 if (e->translation(m_learningLanguageIndex)->synonyms().contains(m_currentEntries.at(m_currentEntry)->entry()->translation(m_learningLanguageIndex))) 0296 return false; 0297 0298 // Entry has the same text as the solution. 0299 if (e->translation(m_learningLanguageIndex)->text().simplified() 0300 == m_currentEntries.at(m_currentEntry)->entry()->translation(m_learningLanguageIndex)->text().simplified()) 0301 return false; 0302 0303 return true; 0304 }