summaryrefslogtreecommitdiff
path: root/src/study/SpacedRepetitionWindow.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/study/SpacedRepetitionWindow.cpp')
-rw-r--r--src/study/SpacedRepetitionWindow.cpp380
1 files changed, 380 insertions, 0 deletions
diff --git a/src/study/SpacedRepetitionWindow.cpp b/src/study/SpacedRepetitionWindow.cpp
new file mode 100644
index 0000000..977b1ec
--- /dev/null
+++ b/src/study/SpacedRepetitionWindow.cpp
@@ -0,0 +1,380 @@
+#include "SpacedRepetitionWindow.h"
+#include "CardsStatusBar.h"
+#include "NumberFrame.h"
+#include "WarningPanel.h"
+#include "../dictionary/Dictionary.h"
+#include "../dictionary/Card.h"
+#include "../dictionary/CardPack.h"
+
+SpacedRepetitionWindow::SpacedRepetitionWindow( SpacedRepetitionModel* aModel, QWidget* aParent ):
+ IStudyWindow( aModel, tr("Spaced repetition"), aParent ),
+ exactAnswerLabel(NULL), exactAnswerEdit(NULL)
+{
+ usesExactAnswer = m_model->getCardPack()->getUsesExactAnswer();
+ studyType = AppModel::SpacedRepetition;
+ if(usesExactAnswer)
+ createExactAnswerWidget();
+ createUI(); // Can't call from parent constructor
+ setWindowIcon( QIcon(":/images/spaced-rep.png") );
+ dayCardsLimitShown = false;
+ showNextCard();
+}
+
+SpacedRepetitionWindow::~SpacedRepetitionWindow()
+{
+ WriteSettings();
+}
+
+void SpacedRepetitionWindow::createExactAnswerWidget()
+{
+ exactAnswerEdit = new QLineEdit;
+
+ exactAnswerLabel = new CardSideView;
+ exactAnswerLabel->setMaximumHeight(40);
+ exactAnswerLabel->setShowMode(CardSideView::AnsMode);
+ exactAnswerLabel->setPack(m_model->getCardPack());
+}
+
+QVBoxLayout* SpacedRepetitionWindow::createLowerPanel()
+{
+ warningPanel = new WarningPanel;
+
+ controlLt = new QVBoxLayout;
+ controlLt->addWidget(warningPanel);
+ controlLt->addLayout(createProgressLayout());
+ controlLt->addLayout(new QVBoxLayout); // Grades layout
+
+ createGradeButtons();
+
+ return controlLt;
+}
+
+QBoxLayout* SpacedRepetitionWindow::createProgressLayout()
+{
+ QVBoxLayout* lt = new QVBoxLayout;
+ lt->setSpacing(3);
+ lt->addLayout(createStatsLayout());
+ lt->addLayout(createProgressBarLayout());
+ return lt;
+}
+
+QBoxLayout* SpacedRepetitionWindow::createStatsLayout()
+{
+ todayNewLabel = new NumberFrame;
+ todayNewLabel->setToolTip(tr("Today learned new cards"));
+ todayNewLabel->setColor("#c0d6ff");
+
+ learningCardsLabel = new NumberFrame;
+ learningCardsLabel->setToolTip(tr("Scheduled learning reviews:\n"
+ "new cards must be repeated today to learn"));
+ learningCardsLabel->setColor(CardsStatusBar::Colors[1]);
+
+ timeToNextLearningLabel = new QLabel;
+ timeToNextLearningLabel->setToolTip(tr("Time left to the next learning review"));
+
+ scheduledCardsLabel = new NumberFrame;
+ scheduledCardsLabel->setToolTip(tr("Scheduled cards for today"));
+ scheduledCardsLabel->setColor(CardsStatusBar::Colors[2]);
+
+ scheduledNewLabel = new NumberFrame;
+ scheduledNewLabel->setToolTip(tr("New scheduled cards for today:\n"
+ "new cards that will be shown between the scheduled ones"));
+ scheduledNewLabel->setColor("#bd8d71");
+
+ QHBoxLayout* lt = new QHBoxLayout;
+ lt->setSpacing(5);
+ lt->addWidget(todayNewLabel);
+ lt->addStretch();
+ lt->addWidget(learningCardsLabel);
+ lt->addWidget(timeToNextLearningLabel);
+ lt->addWidget(scheduledCardsLabel);
+ lt->addWidget(scheduledNewLabel);
+ return lt;
+}
+
+QBoxLayout* SpacedRepetitionWindow::createProgressBarLayout()
+{
+ progressLabel = new QLabel;
+ progressLabel->setToolTip(getProgressBarTooltip());
+
+ coloredProgressBar = new CardsStatusBar;
+ coloredProgressBar->setToolTip(progressLabel->toolTip());
+
+ QHBoxLayout* lt = new QHBoxLayout;
+ lt->setSpacing(5);
+ lt->addWidget(progressLabel);
+ lt->addWidget(coloredProgressBar);
+ return lt;
+}
+
+QString SpacedRepetitionWindow::getProgressBarTooltip()
+{
+ QString boxSpace;
+ for(int i = 0; i < 6; i++)
+ boxSpace += "&nbsp;";
+ QString colorBoxPattern = "<p><span style=\"background-color: %1;\">" +
+ boxSpace + "</span> &nbsp;";
+ QString pEnd = "</p>";
+
+ QStringList legendLines = {
+ tr("Reviewed cards"),
+ tr("Learning reviews"),
+ tr("Scheduled cards"),
+ tr("New cards for today")};
+ QString legend;
+ for(int i = 0; i < 4; i++)
+ legend += colorBoxPattern.arg(CardsStatusBar::Colors[i]) + legendLines[i] + pEnd;
+ return tr("Progress of reviews scheduled for today:") + legend;
+}
+
+void SpacedRepetitionWindow::createGradeButtons()
+{
+ cardGradedSM = new QSignalMapper(this);
+ connect( cardGradedSM, SIGNAL(mapped(int)),
+ qobject_cast<SpacedRepetitionModel*>(m_model), SLOT(scheduleCard(int)) );
+
+ gradeBtns[0] = createGradeButton(0, "question.png", tr("Unknown"),
+ tr("Completely forgotten card, couldn't recall the answer."));
+ gradeBtns[1] = createGradeButton(1, "red-stop.png", tr("Incorrect"),
+ tr("The answer is incorrect."));
+ gradeBtns[2] = createGradeButton(2, "blue-triangle-down.png", tr("Difficult"),
+ tr("It's difficult to recall the answer. The last interval was too long."));
+ gradeBtns[3] = createGradeButton(3, "green-tick.png", tr("Good"),
+ tr("The answer is recalled in couple of seconds. The last interval was good enough."));
+ gradeBtns[4] = createGradeButton(4, "green-triangle-up.png", tr("Easy"),
+ tr("The card is too easy, and recalled without any effort. The last interval was too short."));
+ goodBtn = gradeBtns[StudyRecord::Good - 1];
+}
+
+QPushButton* SpacedRepetitionWindow::createGradeButton(int i, const QString& iconName,
+ const QString& label, const QString& toolTip)
+{
+ QPushButton* btn = new QPushButton;
+ btn->setIcon(QIcon(":/images/" + iconName));
+ btn->setText(QString("%1 %2").arg(i + 1).arg(label));
+ btn->setToolTip("<p>" + toolTip);
+ btn->setShortcut( QString::number(i + 1) );
+ btn->setMinimumWidth(GradeButtonMinWidth);
+
+#if defined(Q_OS_WIN)
+ QFont font = btn->font();
+ font.setPointSize(12);
+ font.setFamily("Calibri");
+ btn->setFont(font);
+#endif
+
+ cardGradedSM->setMapping(btn, i + 1);
+ connect(btn, SIGNAL(clicked()), cardGradedSM, SLOT(map()));
+ return btn;
+}
+
+void SpacedRepetitionWindow::processState()
+{
+ setEnabledGradeButtons();
+ switch(state)
+ {
+ case StateAnswerHidden:
+ displayQuestion();
+ break;
+ case StateAnswerVisible:
+ displayAnswer();
+ break;
+ case StateNoCards:
+ updateCardsProgress();
+ displayNoRemainedCards();
+ break;
+ }
+}
+
+void SpacedRepetitionWindow::setEnabledGradeButtons()
+{
+ for(int i = 0; i < StudyRecord::GradesNum; i++)
+ gradeBtns[i]->setEnabled(state == StateAnswerVisible);
+}
+
+void SpacedRepetitionWindow::displayQuestion()
+{
+ questionLabel->setQuestion( m_model->getCurCard()->getQuestion() );
+ answerStackedLt->setCurrentIndex( AnsButtonPage );
+ updateCardsProgress();
+ layoutGradeButtons();
+ focusAnswerWidget();
+ m_answerTime.start();
+ processIsNewCard();
+}
+
+void SpacedRepetitionWindow::layoutGradeButtons()
+{
+ QHBoxLayout* higherLt = new QHBoxLayout;
+ higherLt->addStretch();
+ putGradeButtonsIntoLayouts(higherLt);
+ higherLt->addStretch();
+ replaceGradesLayout(higherLt);
+ setGoodButtonText();
+}
+
+void SpacedRepetitionWindow::putGradeButtonsIntoLayouts(QBoxLayout* higherLt)
+{
+ QList<int> visibleGrades = static_cast<SpacedRepetitionModel*>(m_model)
+ ->getAvailableGrades();
+ for(int i = 0; i < StudyRecord::GradesNum; i++)
+ {
+ if(!visibleGrades.contains(i + 1))
+ {
+ gradeBtns[i]->setParent(NULL);
+ continue;
+ }
+ higherLt->addWidget(gradeBtns[i]);
+ }
+}
+
+void SpacedRepetitionWindow::replaceGradesLayout(QBoxLayout* higherLt)
+{
+ const int GradesLtIndex = 2;
+ delete controlLt->takeAt(GradesLtIndex);
+ controlLt->insertLayout(GradesLtIndex, higherLt);
+}
+
+void SpacedRepetitionWindow::setGoodButtonText()
+{
+ QList<int> visibleGrades = static_cast<SpacedRepetitionModel*>(m_model)
+ ->getAvailableGrades();
+ QString goodLabel = visibleGrades.size() == StudyRecord::GradesNum?
+ tr("Good") : tr("OK");
+ goodBtn->setText(QString("%1 %2").arg(StudyRecord::Good).arg(goodLabel));
+ goodBtn->setShortcut(QString::number(StudyRecord::Good));
+}
+
+void SpacedRepetitionWindow::focusAnswerWidget()
+{
+ if(usesExactAnswer)
+ {
+ exactAnswerEdit->clear();
+ exactAnswerEdit->setFocus();
+ }
+ else
+ answerBtn->setFocus();
+}
+
+void SpacedRepetitionWindow::processIsNewCard()
+{
+ questionLabel->showNewIcon(static_cast<SpacedRepetitionModel*>(m_model)->isNew());
+}
+
+void SpacedRepetitionWindow::displayAnswer()
+{
+ static_cast<SpacedRepetitionModel*>(m_model)->setRecallTime(m_answerTime.elapsed());
+ Card* card = m_model->getCurCard();
+ QStringList correctAnswers = card->getAnswers();
+ answerStackedLt->setCurrentIndex(AnsLabelPage);
+ if(usesExactAnswer)
+ showExactAnswer(correctAnswers);
+ answerLabel->setQstAnsr(card->getQuestion(), correctAnswers);
+ goodBtn->setFocus();
+}
+
+void SpacedRepetitionWindow::showExactAnswer(const QStringList& correctAnswers)
+{
+ if(static_cast<SpacedRepetitionModel*>(m_model)->isNew())
+ {
+ exactAnswerLabel->hide();
+ return;
+ }
+ else
+ exactAnswerLabel->show();
+ bool isCorrect = correctAnswers.first() == exactAnswerEdit->text().trimmed();
+ QString beginning("<span style=\"");
+ beginning += QString("color:%1").arg(isCorrect ? "green" : "red");
+ beginning += "\">";
+ QString answer = beginning + exactAnswerEdit->text() + "</span>";
+ exactAnswerLabel->setQstAnsr("", {answer});
+}
+
+void SpacedRepetitionWindow::updateCardsProgress()
+ {
+ CardPack* pack = m_model->getCardPack();
+ int todayReviewed = pack->getTodayReviewedCardsNum();
+ int todayNewReviewed = pack->getTodayNewCardsNum();
+ int activeRepeating = pack->getActiveRepeatingCardsNum();
+ int learningReviews = pack->getLearningReviewsNum();
+ int timeToNextLearning = pack->getTimeToNextLearning();
+ int scheduledNew = static_cast<SpacedRepetitionModel*>(m_model)->
+ estimatedNewReviewedCardsToday();
+ int allTodaysCards = todayReviewed + learningReviews +
+ activeRepeating + scheduledNew;
+
+ todayNewLabel->setVisible(todayNewReviewed > 0);
+ todayNewLabel->setValue(todayNewReviewed);
+ learningCardsLabel->setValue(learningReviews);
+
+ int minsToNextLearning = timeToNextLearning / 60;
+ timeToNextLearningLabel->setVisible(minsToNextLearning > 0);
+ timeToNextLearningLabel->setText(tr("(%1 min)").
+ arg(minsToNextLearning));
+
+ scheduledCardsLabel->setValue(activeRepeating);
+ scheduledNewLabel->setValue(scheduledNew);
+
+ progressLabel->setText(QString("%1/%2").
+ arg(todayReviewed).arg(allTodaysCards));
+ coloredProgressBar->setValues({todayReviewed, learningReviews,
+ activeRepeating, scheduledNew});
+
+ showLimitWarnings();
+}
+
+void SpacedRepetitionWindow::showLimitWarnings()
+{
+ if(warningPanel->isVisible())
+ return;
+ CardPack* pack = m_model->getCardPack();
+ if(dayLimitReached(pack->dictionary()->countTodaysAllCards()))
+ {
+ warningPanel->setText(tr("Day cards limit is reached: %1 cards.\n"
+ "It is recommended to stop studying this dictionary.")
+ .arg(StudySettings::inst()->cardsDayLimit));
+ warningPanel->show();
+ dayCardsLimitShown = true;
+ }
+}
+
+bool SpacedRepetitionWindow::dayLimitReached(int todayReviewed)
+{
+ return todayReviewed >= StudySettings::inst()->cardsDayLimit &&
+ !dayCardsLimitShown;
+}
+
+void SpacedRepetitionWindow::displayNoRemainedCards()
+{
+ centralStackedLt->setCurrentIndex(MessagePage);
+ messageLabel->setText(
+ QString("<div align=\"center\" style=\"font-size: 22pt; font-weight: bold\">") +
+ tr("All cards are reviewed") +
+ "</div>" + "<p align=\"center\" style=\"font-size: 11pt\">" +
+ tr("You can go to the next pack or dictionary, or open the Word drill.") +
+ "</p>");
+}
+
+void SpacedRepetitionWindow::ReadSettings()
+{
+ QSettings settings;
+ move( settings.value("spacedrep-pos", QPoint(PosX, PosY)).toPoint() );
+ resize( settings.value("spacedrep-size", QSize(Width, Height)).toSize() );
+}
+
+void SpacedRepetitionWindow::WriteSettings()
+{
+ QSettings settings;
+ settings.setValue("spacedrep-pos", pos());
+ settings.setValue("spacedrep-size", size());
+}
+
+QWidget* SpacedRepetitionWindow::getAnswerEdit()
+{
+ return exactAnswerEdit;
+}
+
+QWidget* SpacedRepetitionWindow::getUserAnswerLabel()
+{
+ return exactAnswerLabel;
+}