diff options
Diffstat (limited to 'src/dictionary/CardPack.cpp')
-rw-r--r-- | src/dictionary/CardPack.cpp | 432 |
1 files changed, 432 insertions, 0 deletions
diff --git a/src/dictionary/CardPack.cpp b/src/dictionary/CardPack.cpp new file mode 100644 index 0000000..75f5924 --- /dev/null +++ b/src/dictionary/CardPack.cpp @@ -0,0 +1,432 @@ +#include "CardPack.h" +#include "Field.h" +#include "DicRecord.h" +#include "Card.h" +#include "../study/StudyRecord.h" +#include "../study/StudySettings.h" +#include "../field-styles/FieldStyleFactory.h" + +const QString CardPack::SynonymDelimiter = ";"; +const QString CardPack::HomonymDelimiter = "; "; + +CardPack::CardPack(IDictionary* dict): + m_dictionary(dict), isReadingStudyFile(false), usesExactAnswer(false) +{ + enableDictRecordUpdates(); +} + +/// Copies fields and study data from another pack. +CardPack::CardPack(IDictionary* dict, const CardPack* otherPack ): + m_dictionary(dict) +{ + enableDictRecordUpdates(); + + // Copy fields by name + foreach( const Field* otherField, otherPack->fields ) + { + Q_ASSERT( otherField ); + if( !otherField ) + return; + const Field* field = m_dictionary->field( otherField->name() ); + fields << field; + } + + // Copy study data + m_curCardName = otherPack->m_curCardName; + studyRecords = otherPack->studyRecords; + usesExactAnswer = otherPack->usesExactAnswer; +} + +CardPack::~CardPack() + { + foreach( Card* card, cards ) + delete card; + } + +QList<DicRecord*> CardPack::getRecords() const + { + if(!m_dictionary) + return QList<DicRecord*>(); + return m_dictionary->getRecords(); + } + +const TreeItem* CardPack::parent() const + { + return dynamic_cast<const TreeItem*>(m_dictionary); + } + +QVariant CardPack::data( int aColumn ) const + { + switch( aColumn ) + { + case 0: + return id(); + case 1: + return getActiveCards().size(); + case 2: + return getNewCards().size(); + default: + return QVariant(); + } + } + +int CardPack::row() const + { + return m_dictionary->indexOfCardPack( const_cast<CardPack*>(this) ); + } + +int CardPack::topParentRow() const + { + return dynamic_cast<TreeItem*>(m_dictionary)->row(); + } + +/** Contains cards with at least 1 study record + */ +bool CardPack::containsReviewedCards() const + { + foreach( QString cardId, studyRecords.uniqueKeys() ) + if( studyRecords.values( cardId ).size() >= 1 ) + return true; + return false; + } + +StudyRecord CardPack::getStudyRecord(QString cardId) const +{ + QList<StudyRecord> recordList = studyRecords.values(cardId); + return recordList.isEmpty() ? StudyRecord() : recordList.first(); +} + +QString CardPack::findLastReviewedCard() const + { + QDateTime lastReview; + QString lastCardName; + foreach( QString cardName, cardQuestions ) + { + QDateTime reviewed = getStudyRecord( cardName ).date; + if( reviewed > lastReview ) + { + lastReview = reviewed; + lastCardName = cardName; + } + } + return lastCardName; + } + +QString CardPack::id() const +{ + if( !m_name.isEmpty() ) + return m_name; + if( fields.empty() || !getQuestionField() ) + return tr("(empty pack)"); + return getQuestionFieldName() + " - " + getAnswerFieldNames().join(", "); +} + +const Field* CardPack::getQuestionField() const +{ + if(fields.empty()) + return NULL; + return fields.first(); +} + +QList<const Field*> CardPack::getAnswerFields() const +{ + return fields.mid(1); +} + +// If the card is not yet created, it creates the card. +Card* CardPack::getCard(const QString& cardName) +{ + if(cardName.isEmpty()) + return NULL; + if(!cardQuestions.contains(cardName)) + return NULL; + if( cards.contains( cardName ) ) + return cards.value( cardName ); + else + { + Card* card = new Card( this, cardName ); + cards.insert( cardName, card ); + return card; + } +} + +void CardPack::setField(int aPos, const Field *aField) +{ + if( aPos >= fields.size() ) + return; + fields[aPos] = aField; +} + +void CardPack::setQstField(const Field *aField) +{ + if( !fields.empty() ) + fields[0] = aField; + else + fields << aField; +} + +void CardPack::setAnsFields(QList<const Field*> aFields) + { + const Field* questionField = NULL; + if(!fields.isEmpty()) + questionField = fields.first(); + fields.clear(); + fields << questionField << aFields; + } + +void CardPack::destroyCards() +{ + cardQuestions.clear(); + foreach(Card* card, cards) + delete card; + cards.clear(); +} + +void CardPack::addQuestionElementsForRecord(const DicRecord* record) +{ + foreach(QString qstElement, record->getFieldElements(getQuestionFieldName())) + { + if(!cardQuestions.contains(qstElement)) + cardQuestions << qstElement; + } +} + +void CardPack::removeAbsentCards(QStringList& cardQuestions) +{ + QMutableListIterator<QString> cardIt(cardQuestions); + while(cardIt.hasNext()) + if(!cardQuestions.contains(cardIt.next())) + cardIt.remove(); +} + +void CardPack::generateQuestions() + { + destroyCards(); + + if(fields.size() < MinRequiredFieldsNum) + return; + if(!getQuestionField()) + return; + + foreach(DicRecord* record, getRecords()) + if(record->isValid(getQuestionFieldName())) + addQuestionElementsForRecord(record); + + emit cardsGenerated(); + } + +QStringList CardPack::getNewCards() const +{ + QStringList list; + foreach(QString cardName, cardQuestions) + if(!studyRecords.contains(cardName)) + list << cardName; + return list; +} + +QStringList CardPack::getActiveCards() const +{ + QStringList list; + foreach(QString cardName, cardQuestions) + if(getStudyRecord(cardName).timeTriggered()) + list << cardName; + return list; +} + +int CardPack::getActiveRepeatingCardsNum() const +{ + int count = 0; + foreach(QString cardName, cardQuestions) + { + StudyRecord study = getStudyRecord(cardName); + if(study.timeTriggered() && study.level == StudyRecord::Repeating) + count++; + } + return count; +} + +int CardPack::countScheduledForTodayCards() const +{ + int count = 0; + foreach(QString cardName, cardQuestions) + { + StudyRecord study = getStudyRecord(cardName); + if(study.isActivatedToday()) + { + if(study.isLearning()) + count += study.getScheduledTodayReviews(); + else + count++; + } + } + return count; +} + +// With small intervals < 1 day +QStringList CardPack::getPriorityActiveCards() const +{ + QStringList list; + foreach(QString cardName, cardQuestions) + { + StudyRecord study = getStudyRecord(cardName); + if(study.timeTriggered() && study.interval < 1) + list << cardName; + } + return list; +} + +QStringList CardPack::getLearningCards() const +{ + QStringList list; + foreach(QString cardName, cardQuestions) + { + StudyRecord study = getStudyRecord(cardName); + if(study.isLearning()) + list << cardName; + } + return list; +} + +int CardPack::getLearningReviewsNum() const +{ + int count = 0; + foreach(QString cardName, cardQuestions) + { + StudyRecord study = getStudyRecord(cardName); + if(study.isLearning()) + count += study.getScheduledTodayReviews(); + } + return count; +} + +int CardPack::getTimeToNextLearning() const +{ + int minTime = 10000; + foreach(QString cardName, getLearningCards()) + { + StudyRecord study = getStudyRecord(cardName); + int left = study.getSecsToNextRepetition(); + if(left < minTime) + minTime = left; + } + if(minTime == 10000) + minTime = 0; + return minTime; +} + +int CardPack::getInactiveLearningReviewsNum() const +{ + int count = 0; + foreach(QString cardName, cardQuestions) + { + StudyRecord study = getStudyRecord(cardName); + if(study.isLearning() && !study.timeTriggered()) + count += study.getScheduledTodayReviews(); + } + return count; +} + +QStringList CardPack::getSmallestIntervalCards(const QStringList& priorityCards) +{ + QStringList smallestIntervals; + double smallest = 1; + foreach(QString name, priorityCards) + { + StudyRecord study = getStudyRecord(name); + if(study.interval < smallest) + { + smallest = study.interval; + smallestIntervals.clear(); + smallestIntervals << name; + } + else if(study.interval == smallest) + smallestIntervals << name; + } + return smallestIntervals; +} + +int CardPack::getTodayReviewedCardsNum() const +{ + const int MaxStudyDepth = 3; + int count = 0; + foreach(QString cardName, cardQuestions) + { + const QList<StudyRecord> studyList = studyRecords.values(cardName); + for(int j = 0; j < studyList.size(); j++) + { + if(j >= MaxStudyDepth) + break; + if(studyList[j].isReviewedToday()) + count++; + } + } + return count; +} + +int CardPack::getTodayNewCardsNum() const +{ + int count = 0; + foreach( QString cardName, cardQuestions ) + { + const QList<StudyRecord> studyList = studyRecords.values(cardName); + if(studyList.isEmpty()) + continue; + StudyRecord firstStudy = studyList.last(); + if(firstStudy.isReviewedToday()) + count++; + } + return count; +} + +void CardPack::addStudyRecord( const QString aCardId, const StudyRecord& aStudyRecord ) + { + if(!cardQuestions.contains(aCardId)) + return; + studyRecords.insert(aCardId, aStudyRecord); + if(!isReadingStudyFile) + emit studyRecordAdded(); + } + +void CardPack::setCurCard( const QString aCardId ) + { + if( aCardId == m_curCardName ) + return; + m_curCardName = aCardId; + emit studyRecordAdded(); // study is modified by the cur card + } + +QList<QDateTime> CardPack::getScheduledDates() const +{ + const int secsInDay = 24 * 60 * 60; + QList<QDateTime> scheduled; + foreach(QString cardName, studyRecords.uniqueKeys()) + { + StudyRecord record = getStudyRecord(cardName); + scheduled << record.date.addSecs((int)(record.interval * secsInDay)); + } + return scheduled; +} + +void CardPack::processEntryChangedEvent( int aEntryIx, int aFieldIx ) + { + Q_UNUSED( aEntryIx ) + if(aFieldIx != IDictionary::AllFields) + generateQuestions(); + } + +void CardPack::disableDictRecordUpdates() + { + disconnect(dynamic_cast<const TreeItem*>(m_dictionary), + SIGNAL(entryChanged(int,int)), this, SLOT(processEntryChangedEvent(int,int)) ); + disconnect(dynamic_cast<const TreeItem*>(m_dictionary), + SIGNAL(entriesRemoved(int,int)), this, SLOT(processEntryChangedEvent(int)) ); + } + +void CardPack::enableDictRecordUpdates() + { + connect(dynamic_cast<const TreeItem*>(m_dictionary), + SIGNAL(entryChanged(int,int)), SLOT(processEntryChangedEvent(int,int)) ); + connect(dynamic_cast<const TreeItem*>(m_dictionary), + SIGNAL(entriesRemoved(int,int)), SLOT(processEntryChangedEvent(int)) ); + // Inserting empty records doesn't regenerate cards + } |