#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 CardPack::getRecords() const { if(!m_dictionary) return QList(); return m_dictionary->getRecords(); } const TreeItem* CardPack::parent() const { return dynamic_cast(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(this) ); } int CardPack::topParentRow() const { return dynamic_cast(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 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 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 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 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 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 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 CardPack::getScheduledDates() const { const int secsInDay = 24 * 60 * 60; QList 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(m_dictionary), SIGNAL(entryChanged(int,int)), this, SLOT(processEntryChangedEvent(int,int)) ); disconnect(dynamic_cast(m_dictionary), SIGNAL(entriesRemoved(int,int)), this, SLOT(processEntryChangedEvent(int)) ); } void CardPack::enableDictRecordUpdates() { connect(dynamic_cast(m_dictionary), SIGNAL(entryChanged(int,int)), SLOT(processEntryChangedEvent(int,int)) ); connect(dynamic_cast(m_dictionary), SIGNAL(entriesRemoved(int,int)), SLOT(processEntryChangedEvent(int)) ); // Inserting empty records doesn't regenerate cards }