/*************************************************************************** * 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 %1 ").arg(QChar('A' + i)); // Text str += ""; str += ""; } str += "
" + m_listAnswer[i].text() + "

"; strHints = pCatalog->hintText(id()); if (!strHints.isEmpty()) { str += "

Hilfestellung

" + strHints; } str += "

Abfrageverlauf

"; str += lastClickedTextExtended(); str += " " + repeatDateTextExtended(); if (!m_listAnswerClicked.isEmpty()) { str += "

"; for (int i=0; i"; } str += "
Datum/Uhrzeit Antwort Richtig? Benötigte Zeit
" + ac.answerText() + ""; if (ac.isCorrect(m_listAnswer)) str += tr("richtig"); else str += tr("falsch"); str += "" + QString ("%1").arg(ac.neededTimeText()) + "
"; } //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 %1 ").arg(QChar('A' + i)); // Text str += ""; } str += "
"; //CHEAT: if (mixedAnswerAt(i).isCorrect()) str += "OK "; str += learnAnswerAt(i).text(); 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; }