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 }