diff options
author | Jedidiah Barber <contact@jedbarber.id.au> | 2021-07-14 11:49:10 +1200 |
---|---|---|
committer | Jedidiah Barber <contact@jedbarber.id.au> | 2021-07-14 11:49:10 +1200 |
commit | d24f813f3f2a05c112e803e4256b53535895fc98 (patch) | |
tree | 601e6ae9a1cd44bcfdcf91739a5ca36aedd827c9 /src/dictionary/Dictionary.cpp |
Diffstat (limited to 'src/dictionary/Dictionary.cpp')
-rw-r--r-- | src/dictionary/Dictionary.cpp | 601 |
1 files changed, 601 insertions, 0 deletions
diff --git a/src/dictionary/Dictionary.cpp b/src/dictionary/Dictionary.cpp new file mode 100644 index 0000000..0779651 --- /dev/null +++ b/src/dictionary/Dictionary.cpp @@ -0,0 +1,601 @@ +#include "Dictionary.h" +#include "CardPack.h" +#include "../version.h" +#include "DicRecord.h" +#include "DictionaryWriter.h" +#include "DictionaryReader.h" +#include "../study/StudyFileWriter.h" +#include "../study/StudyFileReader.h" +#include "../field-styles/FieldStyleFactory.h" +#include "../main-view/AppModel.h" + +#include <QTextStream> +#include <QStringList> +#include <QtAlgorithms> +#include <QDateTime> +#include <QSettings> +#include <QDir> + +const QString Dictionary::DictFileExtension(".fmd"); +const QString Dictionary::StudyFileExtension(".fms"); +const char* Dictionary::NoName( QT_TRANSLATE_NOOP("Dictionary", "noname") ); + +Dictionary::Dictionary(const QString& aFilePath, bool aNameIsTemp, const AppModel* aAppModel): + IDictionary(aFilePath), m_appModel(aAppModel), + obsoleteId(QUuid()), + m_contentModified(true), m_studyModified(false), m_nameIsTemp(aNameIsTemp) + { + } + +Dictionary::~Dictionary() + { + foreach( Field* field, m_fields ) + delete field; + foreach( CardPack* pack, m_cardPacks ) + delete pack; + } + +const TreeItem* Dictionary::child( int aRow ) const + { + return cardPack( aRow ); + } + +QVariant Dictionary::data( int aColumn ) const + { + if( aColumn == 0 ) + return shortName( false ); + else + return QVariant(); + } + +int Dictionary::row() const + { + if( m_appModel ) + return m_appModel->indexOfDictionary( const_cast<Dictionary*>(this) ); + else + return 0; + } + +void Dictionary::clearFieldPackConfig() + { + while( !m_fields.isEmpty() ) + delete m_fields.takeLast(); + while( !m_cardPacks.isEmpty() ) + delete m_cardPacks.takeLast(); + } + +/** Copies from another dictionary: + * - fields, packs, study data. + * The old configuration is deleted. + * The dic records are not changed. + */ +void Dictionary::setDictConfig( const Dictionary* aOtherDic ) + { + clearFieldPackConfig(); + + // Replace fields + foreach( Field* f, aOtherDic->fields() ) + { + m_fields << new Field( f->name(), f->style() ); + // Fix the renamed fields in the entries + // TODO: Refactor to remove the old name + foreach( DicRecord* entry, records ) + entry->renameField( f->oldName(), f->name() ); + } + + // Replace card packs + foreach( CardPack* otherPack, aOtherDic->cardPacks() ) + { + /* The packs are copied together with study data, because + * afterwards it's impossible to find correct modified pack + */ + CardPack* newPack = new CardPack( this, otherPack ); + addCardPack( newPack ); + } + + setContentModified(); + generateCards(); + } + +void Dictionary::setDefaultFields() +{ + m_fields << new Field( tr("Question"), FieldStyleFactory::DefaultStyle ); + m_fields << new Field( tr("Answer"), FieldStyleFactory::DefaultStyle ); + m_fields << new Field( tr("Example"), "Example" ); + + if( records.isEmpty() ) + records << new DicRecord(); + + CardPack* pack; + QList<const Field*> ansFields; + + // Question->Answer + pack = new CardPack( this ); + pack->setQstField( m_fields[0] ); + ansFields << m_fields[1] << m_fields[2]; + pack->setAnsFields( ansFields ); + addCardPack( pack ); + + // Answer->Question + ansFields.clear(); + pack = new CardPack( this ); + pack->setQstField( m_fields[1] ); + ansFields << m_fields[0] << m_fields[2]; + pack->setAnsFields( ansFields ); + addCardPack( pack ); +} + +bool Dictionary::load(const QString filePath) +{ + if(!loadDictFile(filePath)) + return false; + generateCards(); + cleanObsoleteId(); + QString studyFilePath = getStudyFilePath(); + if(!QFile::exists(studyFilePath)) + return true; + return loadStudyFile(studyFilePath); +} + +bool Dictionary::loadDictFile(const QString filePath) +{ + cleanRecords(); + QFile dicFile(filePath); + if(!dicFile.open(QIODevice::ReadOnly | QFile::Text)) + { + errorMessage = tr("Cannot open dictionary file:") + + QString("<p>%1</p>").arg(QDir::toNativeSeparators(filePath)); + return false; + } + this->filePath = filePath; + DictionaryReader dicReader(this); + bool ok = dicReader.read( &dicFile ); + dicFile.close(); + if(!ok) + { + errorMessage = dicReader.errorString(); + return false; + } + + if(m_contentModified) + { + if(save() != QFile::NoError) + return false; + } + return true; +} + +void Dictionary::cleanRecords() +{ + while (!records.isEmpty()) + delete records.takeFirst(); +} + +void Dictionary::cleanObsoleteId() +{ + if(!obsoleteId.isNull()) + obsoleteId = QUuid(); +} + +bool Dictionary::loadStudyFile(const QString filePath) +{ + QFile studyFile(filePath); + if(!studyFile.open(QIODevice::ReadOnly | QFile::Text)) + { + errorMessage = tr("Cannot open study file:") + + QString("<p>%1</p>").arg(QDir::toNativeSeparators(filePath)); + return false; + } + StudyFileReader studyReader( this ); + bool ok = studyReader.read( &studyFile ); + studyFile.close(); + if(!ok) + errorMessage = studyReader.errorString() + QString(" at %1:%2") + .arg( studyReader.lineNumber() ) + .arg( studyReader.columnNumber() ); + + if(m_studyModified) + { + if(saveStudy() != QFile::NoError) + return false; + } + return true; +} + +QFile::FileError Dictionary::save( const QString aFilePath, bool aChangeFilePath ) +{ + QFile::FileError error = saveContent( aFilePath ); + if(error != QFile::NoError) + return error; + + if(aChangeFilePath && aFilePath != filePath) + { + filePath = aFilePath; + m_studyModified = true; + emit filePathChanged(); + } + + if(m_studyModified) + { + error = saveStudy(); + if(error != QFile::NoError) + return error; + } + + return QFile::NoError; +} + +QFile::FileError Dictionary::saveContent( const QString aFilePath ) + { + QFile file( aFilePath ); + if( !file.open( QIODevice::WriteOnly | QFile::Text ) ) + return file.error(); + DictionaryWriter writer( this ); + writer.write( &file ); + file.close(); + setContentModified( false ); + m_nameIsTemp = false; + return QFile::NoError; + } + +QFile::FileError Dictionary::saveStudy() + { + if(!m_studyModified) + return QFile::NoError; + QFile file( getStudyFilePath() ); + if(!file.open(QIODevice::WriteOnly | QFile::Text)) + return file.error(); + StudyFileWriter writer(this); + writer.write(&file); + file.close(); + setStudyModified(false); + return QFile::NoError; + } + +QString Dictionary::shortName( bool aMarkModified ) const +{ +QString fileName; +if( !filePath.isEmpty() ) + fileName = QFileInfo(filePath).fileName(); +else + fileName = tr(NoName) + DictFileExtension; +if(aMarkModified && m_contentModified) + fileName += "*"; +return fileName; +} + +QString Dictionary::getStudyFilePath() const +{ +QString studyFilePath; +if(obsoleteId.isNull()) + { + // Take study file from the same dictionary directory + QFileInfo fileInfo(filePath); + studyFilePath = fileInfo.path() + "/" + fileInfo.completeBaseName() + StudyFileExtension; + } +else + { + // Old dictionary. Take study file from the user settings directory + QSettings settings; + QFileInfo settingsInfo( settings.fileName() ); + studyFilePath = settingsInfo.path() + "/study/" + obsoleteId.toString() + StudyFileExtension; + } +return studyFilePath; +} + +void Dictionary::setContentModified( bool aModified ) +{ +if( aModified != m_contentModified ) // The Content Modified state is changed + { + m_contentModified = aModified; + emit contentModifiedChanged( m_contentModified ); + } +} + +void Dictionary::setStudyModified(bool aModified) +{ +if(aModified != m_studyModified) + { + m_studyModified = aModified; + emit studyModifiedChanged(m_studyModified); + } +} + +const DicRecord* Dictionary::getRecord(int aIndex) const +{ + return records.value( aIndex ); +} + +const DicRecord* Dictionary::entry04( const QString aId04 ) const + { + if( aId04.isEmpty() ) + return NULL; + foreach( DicRecord* entry, records ) + if( entry->id04() == aId04 ) + return entry; + return NULL; + } + +void Dictionary::setFieldValue( int aEntryIx, int aFieldIx, QString aValue ) + { + DicRecord* entry = records.value(aEntryIx); + if( !entry ) + return; + const Field* field = m_fields[aFieldIx]; + if( field->name().isNull() ) + return; + entry->setField( field->name(), aValue ); + setContentModified(); + emit entryChanged( aEntryIx, aFieldIx ); + } + + /// @return -1, if the given ID 0.4 was not found +int Dictionary::fieldId04ToIx( const QString aId ) const + { + if( aId.isEmpty() ) + return -1; + int i=0; + foreach( Field* f, m_fields ) + { + if( f->id04() == aId ) + return i; + i++; + } + return -1; + } + +const Field* Dictionary::field( const QString aFieldName ) const + { + foreach( Field* f, m_fields ) + if( f->name() == aFieldName ) + return f; + return NULL; + } + +QStringList Dictionary::fieldNames() const + { + QStringList names; + foreach( Field* f, m_fields ) + names << f->name(); + return names; + } + +CardPack* Dictionary::cardPack( QString aId ) const + { + foreach( CardPack* pack, m_cardPacks ) + { + if( pack->id() == aId ) + return pack; + } + return NULL; + } + +void Dictionary::setFieldName( int aField, QString aName ) + { + if( aField >= m_fields.size() ) + return; + m_fields[aField]->setName(aName); + setContentModified(); + emit fieldChanged( aField ); + } + +void Dictionary::setFieldStyle( int aField, QString aStyle ) + { + if( aField >= m_fields.size() ) + return; + m_fields[aField]->setStyle(aStyle); + setContentModified(); + emit fieldChanged( aField ); + } + +void Dictionary::insertField( int aPos, QString aName ) + { + if( aPos > m_fields.size() ) + return; + m_fields.insert( aPos, new Field(aName) ); + setContentModified(); + emit fieldInserted( aPos ); + } + +void Dictionary::insertField( int aPos, Field* aFieldPtr ) + { + if( aPos > m_fields.size() ) + return; + m_fields.insert( aPos, aFieldPtr ); + setContentModified(); + emit fieldInserted( aPos ); + } + +void Dictionary::addField( QString aName, QString aStyle ) + { + m_fields << new Field( aName, aStyle ); + setContentModified(); + emit fieldInserted( m_fields.size()-1 ); + } + +// TODO: Make undo command +void Dictionary::addFields( QStringList aFieldNames ) + { + foreach( QString aName, aFieldNames ) + addField( aName ); + } + +/// Just removes field pointer from list +void Dictionary::removeField( int aPos ) + { + if( aPos >= m_fields.size() ) + return; + m_fields.removeAt( aPos ); + setContentModified(); + emit fieldRemoved( aPos ); + } + +/// Removes the field pointer and destroys the field itself! +void Dictionary::destroyField( int aPos ) + { + if( aPos >= m_fields.size() ) + return; + + Field* removedField = m_fields.takeAt( aPos ); + setContentModified(); + + // Remove this field in all packs + foreach( CardPack* pack, m_cardPacks ) + pack->removeField( removedField ); + + delete removedField; + emit fieldRemoved( aPos ); + emit fieldDestroyed( removedField ); + } + +void Dictionary::insertPack( int aPos, CardPack* aPack ) + { + if( aPos > m_cardPacks.size() ) + return; + m_cardPacks.insert( aPos, aPack ); + connect( aPack, SIGNAL(studyRecordAdded()), SLOT(setStudyModified()) ); + connect( aPack, SIGNAL(cardsGenerated()), SIGNAL(cardsGenerated()) ); + emit packInserted( aPos ); + } + + +void Dictionary::addCardPack( CardPack* aCardPack ) + { + insertPack( m_cardPacks.size(), aCardPack ); + } + +/// Just removes pack pointer from list +void Dictionary::removePack( int aPos ) + { + if( aPos >= m_cardPacks.size() ) + return; + m_cardPacks.removeAt( aPos ); + emit packRemoved( aPos ); + } + +/// Removes the field pointer and destroys the field itself! +void Dictionary::destroyPack( int aPos ) + { + if( aPos >= m_cardPacks.size() ) + return; + CardPack* removedPack = m_cardPacks.takeAt( aPos ); + delete removedPack; + emit packRemoved( aPos ); + emit packDestroyed( removedPack ); + } + +void Dictionary::setRecord( int aIndex, const DicRecord& aRecord ) + { + if( aIndex < 0 || aIndex >= records.size() ) + return; + DicRecord* newRecord = new DicRecord( aRecord ); + delete records.value( aIndex ); // delete old record + records.replace( aIndex, newRecord ); + setContentModified(); + emit entryChanged( aIndex, AllFields ); + } + +void Dictionary::insertEntry(int aIndex, DicRecord* aEntry) + { + records.insert( aIndex, aEntry ); + notifyRecordsInserted(aIndex, 1); + } + +void Dictionary::insertEntries( int aIndex, int aNum ) + { + for(int i=0; i < aNum; i++) + records.insert( aIndex + i, new DicRecord() ); + notifyRecordsInserted(aIndex, aNum); + } + +void Dictionary::insertEntries( int aIndex, QList<DicRecord*> aEntries ) + { + int i = 0; + foreach( DicRecord* entry, aEntries ) + { + records.insert( aIndex + i, entry ); + i++; + } + notifyRecordsInserted(aIndex, aEntries.size()); + } + +void Dictionary::notifyRecordsInserted(int index, int num) +{ + setContentModified(); + emit entriesInserted(index, num); +} + +void Dictionary::removeRecords(int aIndex, int aNum) + { + Q_ASSERT( aIndex + aNum <= records.size() ); + for( int i=0; i < aNum; i++ ) + delete records.takeAt( aIndex ); + setContentModified(); + emit entriesRemoved( aIndex, aNum ); + } + +void Dictionary::removeRecord( QString aQuestion ) + { + QMutableListIterator<DicRecord*> it( records ); + int i = 0; + int removedIndex = -1; // First removed record + while( it.hasNext() ) + { + DicRecord* record = it.next(); + foreach( QString fieldStr, record->getFields() ) + { + QStringList elements = fieldStr.split( CardPack::SynonymDelimiter, QString::SkipEmptyParts ); + if( elements.contains( aQuestion ) ) + { + it.remove(); + if( removedIndex < 0 ) + removedIndex = i; + break; + } + } + i++; + } + if( removedIndex >= 0 ) // if something was removed + { + setContentModified(); + emit entriesRemoved( removedIndex, 1 ); + } + } + +void Dictionary::generateCards() + { + if( m_cardPacks.isEmpty() ) + return; + foreach( CardPack* pack, m_cardPacks ) + pack->generateQuestions(); + } + +void Dictionary::disableRecordUpdates() + { + if( m_cardPacks.isEmpty() ) + return; + foreach( CardPack* pack, m_cardPacks ) + pack->disableDictRecordUpdates(); + } + +void Dictionary::enableRecordUpdates() + { + if( m_cardPacks.isEmpty() ) + return; + foreach( CardPack* pack, m_cardPacks ) + pack->enableDictRecordUpdates(); + generateCards(); + } + +QString Dictionary::shortenImagePaths(QString text) const +{ + QRegExp imgRx(QString("(<img\\s+src=\")%1([/\\\\])").arg(getImagesPath())); + text.replace(imgRx, "\\1%%\\2"); + return text; +} + +QString Dictionary::getFieldValue(int recordNum, int fieldNum) const +{ + QString fieldName = field(fieldNum)->name(); + return getRecord(recordNum)-> getFieldValue(fieldName); +} |