/***************************************************************************
* 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 "question.h"
#include "catalog.h"
#include "tools.h"
#include
#include
#include
#include
void CDayStatistic::clear()
{
m_date = QDate();
m_uClickedWrong = 0;
m_uClickedCorrect = 0;
m_uTimeExpeditureCorrect = 0;
m_uTimeExpeditureWrong = 0;
m_dLevel = 0.0;
}
CDayStatistic& CDayStatistic::operator += (const CDayStatistic& ds)
{
m_date = ds.m_date;
m_uClickedWrong += ds.m_uClickedWrong;
m_uClickedCorrect += ds.m_uClickedCorrect;
m_uTimeExpeditureCorrect += ds.m_uTimeExpeditureCorrect;
m_uTimeExpeditureWrong += ds.m_uTimeExpeditureWrong;
m_dLevel += ds.m_dLevel;
return *this;
}
void CDayStatistic::debug() const
{
qDebug("-- Day statistic %s --", qPrintable(m_date.toString(Qt::LocalDate)));
if (m_uClickedCorrect + m_uClickedWrong == 0)
{
qDebug (" No clicks");
return;
}
qDebug(" %i correct + %i wrong = %i", m_uClickedCorrect, m_uClickedWrong, m_uClickedCorrect + m_uClickedWrong);
qDebug(" %i ms + %i ms = %i ms", m_uTimeExpeditureCorrect, m_uTimeExpeditureWrong, m_uTimeExpeditureCorrect + m_uTimeExpeditureWrong);
qDebug(" Level = %lf", m_dLevel);
}
QString CQuestion::tr (const char *sourceText, const char *comment)
{
return QCoreApplication::translate("CQuestion", sourceText, comment);
}
unsigned CQuestion::waitDaysForRepeat (const unsigned uLevel)
{
switch (uLevel)
{
case LEVEL_VERYOFTEN:
return 0;
case LEVEL_OFTEN:
return 1;
case LEVEL_NORMAL:
return 2;
case LEVEL_RARE:
return 4;
case LEVEL_VERYRARE:
return 8;
default:
case LEVEL_EXTREMERARE:
return 16;
}
}
void CQuestion::clear()
{
m_pParentChapter = 0;
m_strId.clear();
m_strText.clear();
m_listAnswer.clear();
m_strlGroups.clear();
m_uErrorPoints = 1;
m_uCorrectAnswers=0;
m_uCorrectSuccessive=0;
m_uWrongAnswers=0;
m_uWrongSuccessive=0;
m_uLevel=0;
}
QString CQuestion::plainText() const
{
QTextDocument doc;
doc.setHtml(m_strText);
return doc.toPlainText();
}
QString CQuestion::firstTextLine() const
{
QString str = plainText();
int i = str.indexOf('\n');
return str.left (i);
}
QString CQuestion::showText(CCatalog *pCatalog) const
{
QString str, strHints;
str +="Frage " + id() + "
";
str += "" + text() + "
";
str += "";
for (int i=0; i";
// Icon
str += " | ";
// Answer number
str += QString(" %1 | ").arg(QChar('A' + i));
// Text
str += "" + m_listAnswer[i].text() + " | ";
str += "";
}
str += "
";
strHints = pCatalog->hintText(id());
if (!strHints.isEmpty())
{
str += "Hilfestellung
" + strHints;
}
str += "Abfrageverlauf
";
str += lastClickedTextExtended();
str += " " + repeatDateTextExtended();
if (!m_listAnswerClicked.isEmpty())
{
str += "
Datum/Uhrzeit | Antwort | Richtig? | Benötigte Zeit |
";
for (int i=0; i" + ac.dateTime().toString(Qt::LocalDate) + " | " + ac.answerText() + " | ";
if (ac.isCorrect(m_listAnswer))
str += tr("richtig");
else
str += tr("falsch");
str += " | " + QString ("%1").arg(ac.neededTimeText()) + " | ";
}
str += "
";
}
//str +="Statistik
";
return str;
}
QString CQuestion::learnText(CCatalog *pCatalog, const bool bShowId, const bool bShowHints)
{
QString str;
if (bShowId) str += "" + id() + " ";
str += text() + "
";
str += "";
for (int i=0; i";
// Answer number
str += QString(" %1 | ").arg(QChar('A' + i));
// Text
str += "";
//CHEAT: if (mixedAnswerAt(i).isCorrect()) str += "OK ";
str += learnAnswerAt(i).text();
str += " | ";
}
str += "
";
if (bShowHints)
{
QString strHints = pCatalog->hintText(id());
if (!strHints.isEmpty()) str += "
" + strHints;
}
return str;
}
bool CQuestion::load (QDomElement elem)
{
QString str;
if (elem.tagName () != "question") return false;
if (!elem.hasAttribute ("id")) return false;
clear ();
m_strId = elem.attribute ("id");
m_uErrorPoints = elem.attribute("errorpoints", "1").toUInt();
str = elem.attribute("groups");
str = str.replace(' ', ';').replace(',', ';');
m_strlGroups = str.split(";", QString::SkipEmptyParts);
if (m_uErrorPoints == 0) return false;
QDomNode n = elem.firstChild();
while (!n.isNull())
{
if (n.isElement ())
{
QDomElement e = n.toElement ();
str = e.text();
if (e.tagName () == "textquestion")
m_strText = str;
else if (e.tagName () == "textanswer")
m_listAnswer.append(CAnswer(str, QVariant(e.attribute("correct", "false")).toBool()));
}
n = n.nextSibling();
}
return true;
}
QString CQuestion::removeTempPath(const QString& strText)
{
QString str=strText, strFileName, strBaseName;
int idxStart=-1, idxEnd=0;
while ((idxStart = str.indexOf(QRegExp("src\\s*=\\s*\""), idxEnd)) != -1)
{
idxStart = str.indexOf('"', idxStart)+1;
idxEnd = str.indexOf('"', idxStart);
strFileName = str.mid(idxStart, idxEnd - idxStart);
if (strFileName.isEmpty()) continue;
QFileInfo fi(strFileName);
strBaseName = fi.fileName();
strBaseName = strBaseName.left(strBaseName.lastIndexOf('.'));
str.replace(strFileName, strBaseName);
}
return str;
}
void CQuestion::save (QDomElement& parent, QDomDocument& doc)
{
QDomElement elemRoot = doc.createElement("question");
elemRoot.setAttribute("id", id());
if (m_uErrorPoints > 1)
elemRoot.setAttribute("errorpoints", QString("%1").arg(m_uErrorPoints));
if (!m_strlGroups.isEmpty())
elemRoot.setAttribute("groups", m_strlGroups.join("; "));
parent.appendChild(elemRoot);
QDomElement elemQuestion = doc.createElement("textquestion");
QDomText textQuestion = doc.createTextNode(removeTempPath(text()));
elemQuestion.appendChild(textQuestion);
elemRoot.appendChild(elemQuestion);
// save answers
for (int i=0; i";
iAnswerCount = countCorrectAnswer();
if (iAnswerCount != 1)
str += QObject::tr("Die Frage %1 hat nicht exakt eine richtige Antwort, sondern %2 Stück.").arg(id()).arg(iAnswerCount) + "
";
for (int j=0; j";
}
if (text().isEmpty())
str += QObject::tr("Die Frage %1 enthält keinen Text.").arg(id()) + "
";
return str;
}
void CQuestion::mixAnswers(const bool bMix)
{
unsigned u1=0, u2=0;
m_listMixedAnswer.clear();
if (!bMix) return;
for (int i=0; i>i) & 0x0001) << m_listMixedAnswer.at(i);
}
return u;
}
void CQuestion::registerAnswerClicked (const unsigned uAnswerMask, const unsigned uNeededTime)
{
m_listAnswerClicked.append(CAnswerClicked(orderedAnswerMask(uAnswerMask), uNeededTime));
if (correctAnswerMask() == uAnswerMask)
{
m_uCorrectAnswers++;
m_uCorrectSuccessive++;
m_uWrongSuccessive = 0;
//if (m_uLevel > 0 && m_uLevel < LEVEL_MAX || m_uLevel == 0 && m_uCorrectSuccessive == 2)
if (m_uLevel < LEVEL_MAX)
m_uLevel++;
}
else
{
m_uWrongAnswers++;
m_uCorrectSuccessive = 0;
m_uWrongSuccessive++;
if (m_uLevel > 0)
m_uLevel--;
}
/* if (m_pParentChapter)
m_pParentChapter->updateStatistic(true);
*/
}
QString CQuestion::levelIconName(const unsigned uLevel)
{
// if (uLevel <= LEVEL_VERYRARE)
return QString(":/icons/16x16/level%1.png").arg(uLevel);
// else
// return ":/icons/16x16/button_ok.png";
}
QIcon CQuestion::levelIcon(const unsigned uLevel)
{
return QIcon (levelIconName(uLevel));
}
QString CQuestion::levelText(const unsigned uLevel)
{
switch (uLevel)
{
case LEVEL_VERYOFTEN:
return QObject::tr("Ahnungslos");
case LEVEL_OFTEN:
return QObject::tr("Anfänger");
case LEVEL_NORMAL:
return QObject::tr("Fortgeschritten");
case LEVEL_RARE:
return QObject::tr("Experte");
case LEVEL_VERYRARE:
return QObject::tr("Freak");
case LEVEL_EXTREMERARE:
return QObject::tr("Professor");
default:
return QObject::tr("unbekannt");
}
}
QDateTime CQuestion::firstClicked() const
{
// Vorraussetzung: m_listAnswerClicked ist nach Datum aufsteigend sortiert
if (m_listAnswerClicked.size() > 0)
return m_listAnswerClicked.at(0).dateTime();
return QDateTime();
}
QDateTime CQuestion::lastClicked() const
{
// Vorraussetzung: m_listAnswerClicked ist nach Datum aufsteigend sortiert
if (m_listAnswerClicked.size() > 0)
return m_listAnswerClicked.at(m_listAnswerClicked.size()-1).dateTime();
return QDateTime();
}
QString CQuestion::lastClickedText() const
{
QDateTime dtLastClicked = lastClicked();
unsigned uSecs = dtLastClicked.secsTo (QDateTime::currentDateTime());
unsigned uDays = dtLastClicked.daysTo (QDateTime::currentDateTime());
if (!dtLastClicked.isValid())
return QString();
if (uSecs < 60)
return tr("vor < 1 min");
else if (uSecs < 3600)
return (tr("vor %1 min").arg(uSecs / 60));
else if (uDays == 1)
return (tr("gestern"));
else if (uDays > 1)
return (tr("vor %1 Tagen").arg(uDays));
else
return (tr("vor %1 h").arg(uSecs / 3600));
}
QString CQuestion::lastClickedTextExtended() const
{
QDateTime dtLastClicked = lastClicked();
QString strRet;
unsigned uLevelOld=0, uLevelNew=0;
if (!dtLastClicked.isValid())
return tr("Die Frage wurde noch nie beantwortet.");
strRet = tr("Die Frage wurde %1 (%2) zuletzt beantwortet. ", "%1=gestern, vor x Tagen, ... / %2 = exaktes Datum m. Uhrzeit").arg(lastClickedText(), dtLastClicked.toString(Qt::LocalDate));
uLevelOld = levelAtEndOfDay(dtLastClicked.date().addDays(-1));
uLevelNew = levelAtEndOfDay(dtLastClicked.date());
if (uLevelOld == uLevelNew)
strRet += tr("Die Einstufung des Lernfortschritts änderte sich an diesem Tag nicht.");
else
strRet += tr("Der Lernfortschritt änderte sich an diesem Tag von '%1' auf '%2'.").arg(levelText(uLevelOld), levelText(uLevelNew));
return strRet;
}
/*!
\return Datum, an dem die Frage wiederholt werden soll.
Gibt es kein solches Datum, so wird ein ungültiges Datum (QDate::isValid()) zurückgegeben.
Hinweis: Das Datum kann auch in der Vergangenheit liegen!
*/
QDate CQuestion::repeatDate() const
{
QDate dLastClicked = lastClicked().date();
unsigned uLevelOld=0, uLevelNew=0;
if (!dLastClicked.isValid()) return QDate();
uLevelOld = levelAtEndOfDay(dLastClicked.addDays(-1));
uLevelNew = levelAtEndOfDay(dLastClicked);
if (uLevelNew <= uLevelOld && uLevelNew != LEVEL_MAX)
return dLastClicked;
return dLastClicked.addDays(waitDaysForRepeat(m_uLevel));
}
bool CQuestion::isRepeatToday() const
{
QDate d = repeatDate();
if (d.isValid() && d <= QDate::currentDate()) return true;
return false;
}
QString CQuestion::repeatDateText() const
{
QDate d = repeatDate();
if (!d.isValid()) return QString();
if (d <= QDate::currentDate())
return tr("heute");
else if (QDate::currentDate().addDays(1) == d)
return tr("morgen");
else
return tr("in %1 Tagen").arg(QDate::currentDate().daysTo(d));
}
QString CQuestion::repeatDateTextExtended() const
{
QString strRet;
unsigned uLevelOld=0, uLevelNew=0, uLevelTarget=0;
QDate dLastClicked = lastClicked().date();
if (!dLastClicked.isValid()) return QString();
uLevelOld = levelAtEndOfDay(dLastClicked.addDays(-1));
uLevelNew = levelAtEndOfDay(dLastClicked);
if (uLevelNew > uLevelOld)
uLevelTarget = uLevelNew + 1;
else
uLevelTarget = uLevelOld + 1;
if (uLevelTarget > LEVEL_MAX) uLevelTarget = LEVEL_MAX;
if (uLevelNew == LEVEL_MAX)
strRet = tr("Es wird empfohlen, die Frage %1 zu wiederholen.").arg(repeatDateText());
else
strRet = tr("Es wird empfohlen, die Frage %1 zu wiederholen, um den Lernfortschritt '%2' zu erreichen.")
.arg(repeatDateText()).arg(levelText(uLevelTarget));
return strRet;
}
unsigned CQuestion::levelAtEndOfDay(const QDate& date) const
{
unsigned uLevel = 0, uCorrectSuccessive=0;
CAnswerClicked ac;
// Vorraussetzung: m_listAnswerClicked ist nach Datum aufsteigend sortiert
for (int i=0; i date) return uLevel;
if (ac.isCorrect(m_listAnswer))
{
uCorrectSuccessive++;
if (uLevel < LEVEL_MAX)
{
//if (uLevel == LEVEL_VERYOFTEN && uCorrectSuccessive == 2 || uLevel > LEVEL_VERYOFTEN)
uLevel++;
}
}
else
{
uCorrectSuccessive = 0;
if (uLevel > LEVEL_VERYOFTEN) uLevel--;
}
}
return uLevel;
}
CDayStatistic CQuestion::dayStatistic(const QDate& date) const
{
CAnswerClicked ac;
CDayStatistic ds;
ds.m_date = date;
if (date.isNull())
ds.m_dLevel = levelAtEndOfDay(QDate::currentDate());
else
ds.m_dLevel = levelAtEndOfDay(date);
// Vorraussetzung: m_listAnswerClicked ist nach Datum aufsteigend sortiert
for (int i=0; i date) break;
if (ac.dateTime().date() < date) continue;
//if (ac.dateTime().date() != date) continue;
}
if (ac.isCorrect(m_listAnswer))
{
ds.m_uClickedCorrect++;
ds.m_uTimeExpeditureCorrect += ac.neededTime();
}
else
{
ds.m_uClickedWrong++;
ds.m_uTimeExpeditureWrong += ac.neededTime();
}
}
return ds;
}
/*!
\return true: Wenn die Frage heute zum ersten Mal beantwortet wurde und noch in der Kategorie LEVEL_VERYOFTEN ist
*/
bool CQuestion::isLearningNew() const
{
if (m_uLevel != LEVEL_VERYOFTEN) return false;
if (m_listAnswerClicked.size() > 0
&& m_listAnswerClicked.at(0).dateTime().date() != QDate::currentDate()) return false;
return true;
}
/*!
\return true: Die Frage wurde gestern oder früher zum ersten Mal beantwortet
*/
bool CQuestion::isKnownQuestion() const
{
if (m_listAnswerClicked.size() > 0
&& m_listAnswerClicked.at(0).dateTime().date() < QDate::currentDate()) return true;
return false;
}