#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 #include #include #include #include #include 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(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 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("

%1

").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("

%1

").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 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 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("(name(); return getRecord(recordNum)-> getFieldValue(fieldName); }