AFUTrainer/chapter.cpp
2019-09-28 11:14:53 +02:00

808 lines
21 KiB
C++
Raw Permalink Blame History

/***************************************************************************
* Copyright (C) 2003-2007 by Oliver Saal *
* osaal@gmx.de *
* http://www.oliver-saal.de/software/afutrainer/ *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "catalog.h"
#include <qcoreapplication.h>
QString CChapter::tr (const char *sourceText, const char *comment)
{
return QCoreApplication::translate("CChapter", sourceText, comment);
}
void CChapter::clear()
{
m_pParentChapter=0;
m_strId.clear();
m_strText.clear();
m_strComment.clear();
m_recom = RecommendationNone;
m_recom2 = RecommendationNone;
qDeleteAll(m_listChapter);
qDeleteAll(m_listQuestion);
m_bHasLearningNew = false;
m_bHasKnownQuestions = false;
m_bHasKnownQuestionsRepeatToday = false;
m_uNeverAskedCount = 0;
for (int i=0; i<=LEVEL_MAX; i++) m_uLevelCount[i] = 0;
for (int i=0; i<=RecommendationMax; i++) m_uRecomCount[i] = 0;
// m_mapExam.clear();
}
int CChapter::countSubQuestion() const
{
int i=0, iRet=0;
for (i=0; i<m_listChapter.size(); i++)
iRet += m_listChapter.at(i)->countSubQuestion();
return (iRet + m_listQuestion.size());
}
bool CChapter::load (QDomElement elem)
{
CChapter *pChapter=0;
if (elem.tagName () != QString ("chapter")) return false;
if (!elem.hasAttribute ("name")) return false;
// if (pParent != NULL && !elem.hasAttribute ("id")) return false;
m_strId = elem.attribute ("id");
m_strText = elem.attribute ("name");
QDomNode n = elem.firstChild();
while (!n.isNull())
{
if (n.isElement ())
{
QDomElement e = n.toElement ();
if (e.tagName() == "comment")
m_strComment = e.text();
else if (e.tagName() == QString ("chapter"))
{
pChapter = new CChapter();
if (pChapter->load (e))
appendChapter(pChapter);
else
delete pChapter;
}
else if (e.tagName() == QString ("question"))
{
CQuestion *pQuestion = new CQuestion();
if (pQuestion->load (e))
appendQuestion(pQuestion);
else
delete pQuestion;
}
}
n = n.nextSibling();
}
updateStatistic();
return true;
}
void CChapter::save (QDomElement& parent, QDomDocument& doc)
{
QDomElement elem = doc.createElement("chapter");
elem.setAttribute("name", text());
elem.setAttribute("id", id());
if (!m_strComment.isEmpty())
{
QDomElement elemComment = doc.createElement("comment");
QDomText textComment = doc.createTextNode(text());
elemComment.appendChild(textComment);
elem.appendChild(elemComment);
}
parent.appendChild(elem);
// save exams
/* QStringList strl = m_mapExam.keys();
for (int i=0; i<strl.size(); i++)
{
QDomElement e = doc.createElement("exam");
e.setAttribute("id", strl.at(i));
e.setAttribute("questions", QString("%1").arg(m_mapExam[strl.at(i)]));
elem.appendChild(e);
}
*/
// save chapters
for (int i=0; i<m_listChapter.size(); i++)
{
m_listChapter[i]->save(elem, doc);
}
// save questions
for (int i=0; i<m_listQuestion.size(); i++)
{
m_listQuestion[i]->save(elem, doc);
}
}
bool CChapter::loadLearnStatistic (QDomElement elem)
{
QList<CQuestion*> listQuestionPool = questionPool();
if (elem.tagName () != QString ("learning")) return false;
QDomNode n = elem.firstChild();
while (!n.isNull())
{
if (n.isElement ())
{
QDomElement e = n.toElement ();
if (e.tagName() == QString ("question"))
{
QString strId = e.attribute("id");
for (int i=0; i<listQuestionPool.size(); i++)
{
if (listQuestionPool.at(i)->id() == strId)
listQuestionPool[i]->loadLearnStatistic(e);
}
}
}
n = n.nextSibling();
}
updateRecommendation();
return true;
}
bool CChapter::saveLearnStatistic (QDomElement& parent, QDomDocument& doc)
{
// questions from sub-chapters
for (int i=0; i<m_listChapter.size(); i++)
{
m_listChapter[i]->saveLearnStatistic(parent, doc);
}
// save questions
for (int i=0; i<m_listQuestion.size(); i++)
{
m_listQuestion[i]->saveLearnStatistic(parent, doc);
}
return true;
}
QString CChapter::checkForErrors() const
{
QString str;
// check chapters
for (int i=0; i<m_listChapter.size(); i++)
{
str += m_listChapter[i]->checkForErrors();
}
// check questions
for (int i=0; i<m_listQuestion.size(); i++)
{
str += m_listQuestion[i]->checkForErrors();
}
return str;
}
QList<CChapter*> CChapter::subChapters() const
{
QList<CChapter*> list;
for (int i=0; i<m_listChapter.size(); i++)
{
list << m_listChapter.at(i);
list << m_listChapter[i]->subChapters();
}
return list;
}
QList<CQuestion*> CChapter::questionPool() const
{
QList<CQuestion*> list;
for (int i=0; i<m_listChapter.size(); i++)
{
list << m_listChapter[i]->questionPool();
}
list << m_listQuestion;
return list;
}
QList<CQuestion*> CChapter::questionPoolLevel(const unsigned uLevel) const
{
QList<CQuestion*> list;
int i;
for (i=0; i<m_listChapter.size(); i++)
list << m_listChapter[i]->questionPoolLevel(uLevel);
for (i=0; i<m_listQuestion.size(); i++)
{
if (m_listQuestion.at(i)->level() == uLevel)
list.append (m_listQuestion.at(i));
}
return list;
}
QList<CQuestion*> CChapter::questionPoolDeepen() const
{
QList<CQuestion*> list;
int i;
for (i=0; i<m_listChapter.size(); i++)
list << m_listChapter[i]->questionPoolDeepen();
for (i=0; i<m_listQuestion.size(); i++)
{
CQuestion *q = m_listQuestion.at(i);
if (q->level() == LEVEL_VERYOFTEN && !q->isNeverAsked())
list.append (q);
}
return list;
}
QList<CQuestion*> CChapter::questionPoolRepeat(const QDate d) const
{
QList<CQuestion*> list;
int i;
for (i=0; i<m_listChapter.size(); i++)
list << m_listChapter[i]->questionPoolRepeat(d);
for (i=0; i<m_listQuestion.size(); i++)
{
CQuestion *q = m_listQuestion.at(i);
if (q->repeatDate() <= d)
list.append (q);
}
return list;
}
void CChapter::updateStatisticCount()
{
/* for (int i=0; i<m_listQuestion.size(); i++)
if (m_listQuestion.at(i)->isLearningNew()) return true;
for (int i=0; i<m_listChapter.size(); i++)
if (m_listChapter.at(i)->isLearningNew()) return true;
return false;
*/
// Alle Statistiken zur<75>cksetzen
m_uNeverAskedCount = 0;
m_bHasLearningNew = false;
m_bHasKnownQuestions = false;
m_bHasKnownQuestionsRepeatToday = false;
for (int i=0; i<=LEVEL_MAX; i++) m_uLevelCount[i] = 0;
// Zuerst Statistiken f<>r Unterkapitel berechnen und integrieren
for (int i=0; i<m_listChapter.size(); i++)
{
CChapter *p = m_listChapter.at(i);
p->updateStatisticCount();
for (int j=0; j<=LEVEL_MAX; j++)
m_uLevelCount[j] += p->m_uLevelCount[j];
m_uNeverAskedCount += p->m_uNeverAskedCount;
if (p->hasLearningNewQuestions()) m_bHasLearningNew = true;
if (p->hasKnownQuestions()) m_bHasKnownQuestions = true;
if (p->hasKnownQuestionsRepeatToday()) m_bHasKnownQuestionsRepeatToday = true;
}
// Anschlie<69>end Statistiken f<>r eigene Fragen neu berechnen und hinzuf<75>gen
for (int i=0; i<m_listQuestion.size(); i++)
{
CQuestion *q = m_listQuestion.at(i);
if (q->isNeverAsked()) m_uNeverAskedCount++;
m_uLevelCount[q->level()]++;
if (q->isLearningNew()) m_bHasLearningNew = true;
if (q->isKnownQuestion()) m_bHasKnownQuestions = true;
if (q->isKnownQuestion() && q->isRepeatToday()) m_bHasKnownQuestionsRepeatToday = true;
}
}
void CChapter::updateRecommendationStatistic()
{
for (int i=0; i<RecommendationMax; i++) m_uRecomCount[i] = 0;
m_uRecomCount[recommendation()]=1;
for (int i=0; i<m_listChapter.size(); i++)
{
CChapter *p = m_listChapter.at(i);
p->updateRecommendationStatistic();
for (int j=0; j<RecommendationMax; j++)
m_uRecomCount[j] += p->m_uRecomCount[j];
}
}
/*
Berechnet die Statistik f<>r das Kapitel inkl. aller Unterkapitel neu.
*/
void CChapter::updateStatistic()
{
updateStatisticCount();
updateRecommendation();
updateRecommendationStatistic();
}
/*
CRecommendation CChapter::recommendation() const
{
CRecommendation r;
r.create(this);
return r;
}
*/
/*
CRecommendationStatistic CChapter::recommendationStatistic() const
{
CRecommendationStatistic rs;
for (int i=0; i<m_listChapter.size(); i++)
{
rs.append(m_listChapter.at(i)->recommendationStatistic());
}
//TODO
// rs.append(recommendation());
return rs;
}
*/
QString CChapter::idWithParents() const
{
CChapter *pParent=m_pParentChapter;
QString str;
str = id();
while (pParent != 0)
{
str = pParent->id() + str;
pParent = pParent->m_pParentChapter;
}
return str;
}
/*!
Das empfohlene Wiederholdatum eines Kapitels das fr<66>hestete Datum,
an dem eine Frage des Kapitels oder Unterkapitels wiederholt werden sollte.
\return Datum, wann das Kapitel wiederholt werden soll.
Gibt es kein solches Datum, so wird ein ung<6E>ltiges Datum (QDate::isValid()) zur<75>ckgegeben.
Hinweis: Das Datum kann auch in der Vergangenheit liegen!
*/
QDate CChapter::repeatDate() const
{
QDate d;
QList<CQuestion*> list = questionPool();
for (int i=0; i<list.size(); i++)
{
QDate dq = list.at(i)->repeatDate();
if (!dq.isValid()) continue;
if (!d.isValid() || dq < d) d = dq;
}
return d;
}
/*!
Die Variable m_recomRepeatDate muss up to date sein!
\return Lernempfehlung dieses Kapitels ohne Ber<65>cksichtigung evt. vorhandener <20>nter- oder <20>bergeordneter Kapitel.
\sa m_recomRepeatDate
*/
CChapter::Recommendation CChapter::recommendationIndividual() const
{
if (!m_recomRepeatDate.isValid())
return RecommendationLearnNew;
else if (m_recomRepeatDate <= QDate::currentDate())
{
if (hasLearningNewQuestions() && !hasKnownQuestionsRepeatToday())
return RecommendationLearnNew;
else
return RecommendationRepeatToday;
}
else
{
if (countNeverAsked() > 0)
return RecommendationLearnNew;
else
return RecommendationRepeatLater;
}
}
/*!
Aktualisiert die Lernempfehlung f<>r dieses Kapitel und alle Unterkapitel
\sa m_recom
*/
void CChapter::updateRecommendation()
{
m_recom = RecommendationNone;
m_recom2 = RecommendationNone;
m_recomRepeatDate = repeatDate(); /*QDate();*/
if (m_listChapter.isEmpty() && m_listQuestion.isEmpty()) return;
if (m_pParentChapter && m_pParentChapter->recommendation() != RecommendationSubChapter)
m_recom = RecommendationParentChapter;
else
{
if (countSubQuestion() > 20)
{ // Nur wenn in den Unterkapiteln zusammen mehr als x Fragen sind, zuerst die Unterkapitel einzeln lernen lassen
for (int i=0; i<m_listChapter.size(); i++)
{
CChapter *p = m_listChapter.at(i);
if (p->levelAvg() < LEVEL_NORMAL || p->countNeverAsked() > 0)
m_recom = RecommendationSubChapter;
}
}
if (m_recom == RecommendationNone)
{
//m_recomRepeatDate = repeatDate();
m_recom = recommendationIndividual();
}
}
// Alternative Empfehlung
if (m_recom == RecommendationParentChapter || m_recom == RecommendationSubChapter)
{
m_recom2 = recommendationIndividual();
if (m_recom2 != RecommendationLearnNew && m_recom2 != RecommendationRepeatToday)
m_recom2 = RecommendationNone;
}
// find recommended questions
m_listQuestionRecommended.clear();
QList<CQuestion*> list = questionPool();
for (int i=0; i<list.size(); i++)
{
CQuestion *q = list.at(i);
if (m_recom == RecommendationLearnNew || m_recom2 == RecommendationLearnNew)
{
if (q->isNeverAsked() || q->isLearningNew())
m_listQuestionRecommended.append(q);
}
else if (m_recom == RecommendationRepeatToday || m_recom2 == RecommendationRepeatToday)
{
if (q->isRepeatToday())
m_listQuestionRecommended.append(q);
}
}
// update child chapters
for (int i=0; i<m_listChapter.size(); i++)
{
CChapter *p = m_listChapter.at(i);
p->updateRecommendation();
//m_listQuestionRecommended << p->m_listQuestionRecommended;
}
}
/*
bool CChapter::hasRecommendedQuestions() const
{
return (m_recom == RecommendationLearnNew || m_recom == RecommendationRepeatToday);
}
QList<CQuestion*> CChapter::recommendedQuestions() const
{
QList<CQuestion*> listRet, list = questionPool();
if (m_recom != RecommendationLearnNew && m_recom != RecommendationRepeatToday) return listRet;
for (int i=0; i<list.size(); i++)
{
CQuestion *q = list.at(i);
switch (m_recom)
{
case RecommendationLearnNew:
if (q->isNeverAsked() || q->isLearningNew())
listRet.append(q);
break;
case RecommendationRepeatToday:
if (q->isRepeatToday())
listRet.append(q);
break;
}
}
return listRet;
}
*/
QString CChapter::recommendationText(const Recommendation r, const QDate dRepeat)
{
unsigned uDays=0;
switch (r)
{
default:
case RecommendationNone:
return tr("Keine Lernempfehlung");
case RecommendationSubChapter:
return tr("Zuerst Unterkapitel lernen");
case RecommendationParentChapter:
return tr("<EFBFBD>bergeordnetes Kapitel lernen");
case RecommendationLearnNew:
return tr("Neue Fragen lernen");
case RecommendationRepeatToday:
return tr("Heute wiederholen");
case RecommendationRepeatLater:
uDays = QDate::currentDate().daysTo(dRepeat);
if (uDays == 1)
return tr("Morgen wiederholen");
else
return tr("In %1 Tagen wiederholen").arg(uDays);
}
return QString();
}
QString CChapter::recommendationText() const
{
return recommendationText(m_recom, m_recomRepeatDate);
}
QString CChapter::recommendationToolTip() const
{
QString str = recommendationText();
unsigned uCount = recommendedQuestionCount();
if (hasRecommendedQuestions() && m_recom2 == RecommendationNone)
{
str += " ";
if (uCount == 1)
str += tr("(1 Frage)");
else
str += tr("(%1 Fragen)").arg(uCount);
}
if (m_recom2 != RecommendationNone)
{
str += "\n" + tr("Alternativ: ");
if (m_recom2 == RecommendationLearnNew)
{
if (uCount == 1)
str += tr("1 neue Frage lernen");
else
str += tr("%1 neue Fragen lernen").arg(uCount);
}
else if (m_recom2 == RecommendationRepeatToday)
{
if (uCount == 1)
str += tr("1 Frage wiederholen");
else
str += tr("%1 Fragen wiederholen").arg(uCount);
}
else
str += recommendationText (m_recom2, m_recomRepeatDate);
}
return str;
}
QString CChapter::recommendationTextExtended(const CCatalog *pCatalog) const
{
unsigned uDays=0;
QString str;
switch (m_recom)
{
default:
case RecommendationNone:
str = tr("Keine Lernempfehlung");
break;
case RecommendationSubChapter:
str = tr("Dieses Kapitel enth<74>lt Unterkapitel, dessen Fragen Sie noch nicht ausreichend gelernt haben.\nEs wird empohlen in kleinen Etappen zu lernen und damit zuerst die Unterkapitel zu vertiefen.");
break;
case RecommendationParentChapter:
if (levelAvgRounded() >= LEVEL_NORMAL)
str = tr("Sie k<>nnen die Fragen dieses Kapitels gut beantworten.\n");
str += tr("Es wird empfohlen, alle Fragen des <20>bergeordneten Kapitels gemischt zusammen zu lernen.");
break;
case RecommendationLearnNew:
if (!isRecommendedNow(pCatalog))
str = tr("Es gibt andere Kapitel, deren Fragen heute wiederholt werden sollten. Bitte lernen Sie diese Kapitel zuerst.");
else
str = tr("Bitte beantworten Sie alle neuen Fragen mindestens einmal richtig.");
break;
case RecommendationRepeatToday:
str = tr("Bitte lernen Sie alle heute zu wiederholenden Fragen, bis sie eine Lernfortschritts-Stufe h<>her eingestuft sind.");
break;
case RecommendationRepeatLater:
uDays = QDate::currentDate().daysTo(m_recomRepeatDate);
if (uDays > 1)
str = tr("Die Wiederholung dieses Kapitels ist erst in %1 Tagen geplant.\n").arg(uDays);
else
str = tr("Die Wiederholung dieses Kapitels ist erst f<>r morgen geplant.\n");
if (pCatalog->m_uRecomCount[RecommendationRepeatToday] > 0)
str += tr("Es gibt andere Kapitel, deren Fragen heute wiederholt werden m<>ssen. Bitte lernen Sie diese Kapitel zuerst.");
else if (pCatalog->m_uRecomCount[RecommendationLearnNew] > 0)
str += tr("Bitte lernen Sie zuerst Kapitel mit neuen Fragen.");
break;
}
if (hasRecommendedQuestions() && isRecommendedNow(pCatalog))
str += tr("<p>Daf<61>r sind noch %1 Fragen zu lernen.").arg(recommendedQuestionCount());
return str;
}
QString CChapter::recommendationTextExtended2(const CCatalog *pCatalog) const
{
QString str;
if (m_recom2 == RecommendationLearnNew || (m_recom == RecommendationLearnNew && !isRecommendedNow(pCatalog)))
{
//str = tr("Bitte beantworten Sie alle neuen Fragen mindestens einmal richtig.");
str = tr("Alternativ k<>nnen Sie jetzt die neuen Fragen dieses Kapitels lernen (%1 Fragen).").arg(recommendedQuestionCount());
}
else if (m_recom2 == RecommendationRepeatToday)
{
if (m_recom == RecommendationSubChapter)
str = tr("Bitte lernen Sie alle heute zu wiederholenden Fragen, bis sie eine Lernfortschritts-Stufe h<>her eingestuft sind (%1 Fragen).").arg(recommendedQuestionCount());
else
str = tr("Alternativ k<>nnen Sie jetzt die heute zu wiederholenden Fragen dieses Kapitels lernen (%1 Fragen).").arg(recommendedQuestionCount());
}
return str;
}
QString CChapter::recommendationIconName(const Recommendation r, const CCatalog *pCatalog)
{
switch (r)
{
case RecommendationSubChapter:
return QString(":/icons/16x16/button_cancel.png");
case RecommendationParentChapter:
return QString(":/icons/16x16/button_ok.png");
case RecommendationLearnNew:
if (pCatalog->m_uRecomCount[RecommendationRepeatToday] > 0)
return QString(":/icons/16x16/idea_gray.png");
else
return QString(":/icons/16x16/idea.png");
case RecommendationRepeatToday:
return QString(":/icons/16x16/idea.png");
case RecommendationRepeatLater:
return QString(":/icons/16x16/idea_gray.png");
default:
return QString();
}
}
QString CChapter::recommendationIconName(const CCatalog *pCatalog) const
{
return recommendationIconName(m_recom, pCatalog);
}
/*!
\return true: Kapitel kann jetzt gelernt werden,
false: Kapitel sollte <20>berhaupt nicht oder erst sp<73>ter gelernt werden
*/
bool CChapter::isRecommendedNow(const CCatalog *pCatalog) const
{
switch (m_recom)
{
case RecommendationSubChapter:
if (pCatalog->m_uRecomCount[RecommendationRepeatToday] > 0)
return true;
break;
case RecommendationRepeatToday:
return true;
case RecommendationLearnNew:
if (pCatalog->m_uRecomCount[RecommendationRepeatToday] == 0)
return true;
break;
default:
break;
}
return false;
}
/*
\return true: Das Kapitel enth<74>lt noch neue Fragen, die gerade (="heute") gelernt werden
bool CChapter::isLearningNew() const
{
for (int i=0; i<m_listQuestion.size(); i++)
if (m_listQuestion.at(i)->isLearningNew()) return true;
for (int i=0; i<m_listChapter.size(); i++)
if (m_listChapter.at(i)->isLearningNew()) return true;
return false;
}
*/
CDayStatistic CChapter::dayStatistic (const QDate& date) const
{
CDayStatistic dsRet, ds;
QList<CQuestion*> listPool = questionPool();
for (int i=0; i<listPool.size(); i++)
{
ds = listPool.at(i)->dayStatistic(date);
dsRet += ds;
}
if (listPool.size() != 0) dsRet.m_dLevel /= listPool.size();
return dsRet;
}
CDayStatistic CChapter::completeStatistic() const
{
return dayStatistic(QDate());
}
QDateTime CChapter::firstAnswerClicked() const
{
QList<CQuestion*> listPool = questionPool();
QDateTime dtRet, dt;
for (int i=0; i<listPool.size(); i++)
{
dt = listPool.at(i)->firstClicked();
if (dt.isNull()) continue;
if (dtRet.isNull() || dt < dtRet) dtRet = dt;
}
return dtRet;
}
double CChapter::levelAvg() const
{
double d=0.0;
double dCount=0.0;
for (int i=0; i<=LEVEL_MAX; i++)
{
d += m_uLevelCount[i] * i;
dCount += m_uLevelCount[i];
}
if (dCount != 0.0) d /= dCount;
return d;
}
unsigned CChapter::levelAvgRounded() const
{
return ((unsigned) (levelAvg()+0.5));
}
QString CChapter::levelAvgText() const
{
return QString("%1").arg(CQuestion::levelText(levelAvgRounded()));
}
QIcon CChapter::levelAvgIcon() const
{
return QIcon(CQuestion::levelIconName(levelAvgRounded()));
}
QPixmap CChapter::levelAvgPixmap() const
{
return QPixmap(CQuestion::levelIconName(levelAvgRounded()));
}
static bool chapterLessThan(const CChapter *c1, const CChapter *c2)
{
return c1->id() < c2->id();
}
void CChapter::sortSubChapters(bool bSortQuestions)
{
if (bSortQuestions) sortQuestions();
qSort (m_listChapter.begin(), m_listChapter.end(), chapterLessThan);
for (int i=0; i<m_listChapter.size(); i++)
{
m_listChapter.at(i)->sortSubChapters(bSortQuestions);
}
}
static bool questionLessThan(const CQuestion *q1, const CQuestion *q2)
{
return q1->id() < q2->id();
}
void CChapter::sortQuestions()
{
qSort (m_listQuestion.begin(), m_listQuestion.end(), questionLessThan);
}