summaryrefslogtreecommitdiff
path: root/src/dictionary/Dictionary.cpp
diff options
context:
space:
mode:
authorJedidiah Barber <contact@jedbarber.id.au>2021-07-14 11:49:10 +1200
committerJedidiah Barber <contact@jedbarber.id.au>2021-07-14 11:49:10 +1200
commitd24f813f3f2a05c112e803e4256b53535895fc98 (patch)
tree601e6ae9a1cd44bcfdcf91739a5ca36aedd827c9 /src/dictionary/Dictionary.cpp
Initial mirror commitHEADmaster
Diffstat (limited to 'src/dictionary/Dictionary.cpp')
-rw-r--r--src/dictionary/Dictionary.cpp601
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);
+}