summaryrefslogtreecommitdiff
path: root/src/dictionary/CardPack.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/dictionary/CardPack.cpp')
-rw-r--r--src/dictionary/CardPack.cpp432
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
+ }