From d24f813f3f2a05c112e803e4256b53535895fc98 Mon Sep 17 00:00:00 2001 From: Jedidiah Barber Date: Wed, 14 Jul 2021 11:49:10 +1200 Subject: Initial mirror commit --- src/study/SpacedRepetitionWindow.cpp | 380 +++++++++++++++++++++++++++++++++++ 1 file changed, 380 insertions(+) create mode 100644 src/study/SpacedRepetitionWindow.cpp (limited to 'src/study/SpacedRepetitionWindow.cpp') 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 += " "; + QString colorBoxPattern = "

" + + boxSpace + "  "; + QString pEnd = "

"; + + 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(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("

" + 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 visibleGrades = static_cast(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 visibleGrades = static_cast(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(m_model)->isNew()); +} + +void SpacedRepetitionWindow::displayAnswer() +{ + static_cast(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(m_model)->isNew()) + { + exactAnswerLabel->hide(); + return; + } + else + exactAnswerLabel->show(); + bool isCorrect = correctAnswers.first() == exactAnswerEdit->text().trimmed(); + QString beginning(""; + QString answer = beginning + exactAnswerEdit->text() + ""; + 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(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("

") + + tr("All cards are reviewed") + + "
" + "

" + + tr("You can go to the next pack or dictionary, or open the Word drill.") + + "

"); +} + +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; +} -- cgit