File indexing completed on 2024-04-28 07:36:29

0001 /*
0002  * Vocabulary Document for KDE Edu
0003  * SPDX-FileCopyrightText: 1999-2001 Ewald Arnold <kvoctrain@ewald-arnold.de>
0004  * SPDX-FileCopyrightText: 2005-2007 Peter Hedlund <peter.hedlund@kdemail.net>
0005  * SPDX-FileCopyrightText: 2007 Frederik Gladhorn <frederik.gladhorn@kdemail.net>
0006  * SPDX-License-Identifier: GPL-2.0-or-later
0007  */
0008 #include "keduvocdocument.h"
0009 
0010 #include <QCoreApplication>
0011 #include <QFileInfo>
0012 #include <QIODevice>
0013 #include <QTemporaryFile>
0014 #include <QTextStream>
0015 #include <QtAlgorithms>
0016 
0017 #include <KAutoSaveFile>
0018 #include <KCompressionDevice>
0019 #include <KLazyLocalizedString>
0020 #include <KLocalizedString>
0021 #include <QDebug>
0022 #include <kio/filecopyjob.h>
0023 
0024 #include "keduvoccsvwriter.h"
0025 #include "keduvocexpression.h"
0026 #include "keduvockvtml2writer.h"
0027 #include "keduvocleitnerbox.h"
0028 #include "keduvoclesson.h"
0029 #include "keduvocwordtype.h"
0030 #include "readerwriters/readerbase.h"
0031 #include "readerwriters/readermanager.h"
0032 
0033 #define WQL_IDENT "WordQuiz"
0034 
0035 #define KVTML_EXT "kvtml"
0036 #define CSV_EXT "csv"
0037 #define TXT_EXT "txt"
0038 #define WQL_EXT "wql"
0039 
0040 /** @details Private Data class for KEduVocDocument */
0041 class KEduVocDocument::KEduVocDocumentPrivate
0042 {
0043 public:
0044     /** constructor */
0045     KEduVocDocumentPrivate(KEduVocDocument *qq)
0046         : q(qq)
0047     {
0048         m_lessonContainer = nullptr;
0049         m_wordTypeContainer = nullptr;
0050         m_leitnerContainer = nullptr;
0051         m_autosave = new KAutoSaveFile;
0052         init();
0053     }
0054 
0055     /** destructor */
0056     ~KEduVocDocumentPrivate();
0057 
0058     /** initializer */
0059     void init();
0060 
0061     KEduVocDocument *q; ///< The Front End
0062 
0063     /** autosave file used to provide locking access to the underlying file
0064      * Note: It is a pointer to allow locking a new file, saving results and
0065      * then transferring the lock to m_autosave without risking loss of lock.
0066      * See saveAs for clarification*/
0067     KAutoSaveFile *m_autosave;
0068 
0069     bool m_dirty; ///< dirty bit
0070     bool m_isReadOnly; ///< FileOpenReadOnly was used for opening
0071 
0072     // save these to document
0073     QList<KEduVocIdentifier> m_identifiers; ///< list of identifiers
0074 
0075     QList<int> m_extraSizeHints; ///< unused
0076     QList<int> m_sizeHints; ///< unused
0077 
0078     QString m_generator; ///< name of generating application
0079     QString m_queryorg; ///< unused
0080     QString m_querytrans; ///< unused
0081 
0082     QStringList m_tenseDescriptions; ///< unused. Was used in merge
0083     QSet<QString> m_usages; ///< unused
0084 
0085     QString m_title; ///< Document title
0086     QString m_author; ///< Document author
0087     QString m_authorContact; ///< Author contact information
0088     QString m_license; ///< Document license
0089     QString m_comment; ///< Document comment
0090     QString m_version; ///< Document version
0091     QString m_csvDelimiter; ///< CSV delimiter
0092 
0093     /** Categories that can later be used to sork kvtml files:
0094      * language, music, children, anatomy
0095      */
0096     QString m_category; ///< Document category
0097 
0098     KEduVocLesson *m_lessonContainer; ///< Root lesson container
0099     KEduVocWordType *m_wordTypeContainer; ///< Root word types container
0100     /** Root Leitner container
0101      * (probably unused) This is not used in Parley
0102      * */
0103     KEduVocLeitnerBox *m_leitnerContainer;
0104 
0105     /** Creates an autosave file for fpath in order to lock the file
0106      *   @param autosave a reference to an allocated KAutosave
0107      *   @param fpath File path to the file to be locked
0108      *   @param flags Describes how to deal with locked file etc.
0109      *   @return ErrorCode where NoError is success
0110      *   */
0111     KEduVocDocument::ErrorCode initializeKAutoSave(KAutoSaveFile &autosave, QString const &fpath, FileHandlingFlags flags) const;
0112 };
0113 
0114 KEduVocDocument::KEduVocDocumentPrivate::~KEduVocDocumentPrivate()
0115 {
0116     delete m_lessonContainer;
0117     delete m_wordTypeContainer;
0118     delete m_leitnerContainer;
0119     delete m_autosave;
0120 }
0121 
0122 void KEduVocDocument::KEduVocDocumentPrivate::init()
0123 {
0124     delete m_lessonContainer;
0125     m_lessonContainer = new KEduVocLesson(i18nc("The top level lesson which contains all other lessons of the document.", "Document Lesson"), q);
0126     m_lessonContainer->setContainerType(KEduVocLesson::Lesson);
0127     delete m_wordTypeContainer;
0128     m_wordTypeContainer = new KEduVocWordType(i18n("Word types"));
0129 
0130     delete m_leitnerContainer;
0131     m_leitnerContainer = new KEduVocLeitnerBox(i18n("Leitner Box"));
0132 
0133     m_tenseDescriptions.clear();
0134     m_identifiers.clear();
0135     m_extraSizeHints.clear();
0136     m_sizeHints.clear();
0137     m_dirty = false;
0138     m_isReadOnly = false;
0139     m_queryorg = QLatin1String("");
0140     m_querytrans = QLatin1String("");
0141     m_autosave->setManagedFile(QUrl());
0142     m_author = QLatin1String("");
0143     m_title = QLatin1String("");
0144     m_comment = QLatin1String("");
0145     m_version = QLatin1String("");
0146     m_generator = QLatin1String("");
0147     m_csvDelimiter = QString('\t');
0148     m_usages.clear();
0149     m_license.clear();
0150     m_category.clear();
0151 }
0152 
0153 KEduVocDocument::ErrorCode
0154 KEduVocDocument::KEduVocDocumentPrivate::initializeKAutoSave(KAutoSaveFile &autosave, QString const &fpath, FileHandlingFlags flags) const
0155 {
0156     QList<KAutoSaveFile *> staleFiles = KAutoSaveFile::staleFiles(QUrl::fromLocalFile(fpath), QCoreApplication::instance()->applicationName());
0157     if (!staleFiles.isEmpty()) {
0158         if (flags & FileIgnoreLock) {
0159             foreach (KAutoSaveFile *f, staleFiles) {
0160                 f->open(QIODevice::ReadWrite);
0161                 f->remove();
0162                 delete f;
0163             }
0164         } else {
0165             qWarning() << i18n("Cannot lock file %1", fpath);
0166             return FileLocked;
0167         }
0168     }
0169 
0170     autosave.setManagedFile(QUrl::fromLocalFile(fpath));
0171     if (!autosave.open(QIODevice::ReadWrite)) {
0172         qWarning() << i18n("Cannot lock file %1", autosave.fileName());
0173         return FileCannotLock;
0174     }
0175 
0176     return NoError;
0177 }
0178 
0179 KEduVocDocument::KEduVocDocument(QObject *parent)
0180     : QObject(parent)
0181     , d(new KEduVocDocumentPrivate(this))
0182 {
0183 }
0184 
0185 KEduVocDocument::~KEduVocDocument()
0186 {
0187     delete d;
0188 }
0189 
0190 void KEduVocDocument::setModified(bool dirty)
0191 {
0192     d->m_dirty = dirty;
0193     Q_EMIT docModified(d->m_dirty);
0194 }
0195 
0196 KEduVocDocument::FileType KEduVocDocument::detectFileType(const QString &fileName)
0197 {
0198     KCompressionDevice f(fileName);
0199     f.open(QIODevice::ReadOnly);
0200 
0201     ReaderManager::ReaderPtr reader(ReaderManager::reader(f));
0202 
0203     f.close();
0204 
0205     return reader->fileTypeHandled();
0206 }
0207 
0208 KEduVocDocument::ErrorCode KEduVocDocument::open(const QUrl &url, FileHandlingFlags flags)
0209 {
0210     // save csv delimiter to preserve it in case this is a csv document
0211     QString csv = d->m_csvDelimiter;
0212     // clear all other properties
0213     d->init();
0214     if (!url.isEmpty()) {
0215         setUrl(url);
0216     }
0217     d->m_csvDelimiter = csv;
0218 
0219     KEduVocDocument::ErrorCode errStatus = Unknown;
0220     QString errorMessage = i18n("<qt>Cannot open file<br /><b>%1</b></qt>", url.toDisplayString());
0221 
0222     QString temporaryFile;
0223     QTemporaryFile tempFile;
0224     if (url.isLocalFile()) {
0225         temporaryFile = url.toLocalFile();
0226     } else {
0227         if (!tempFile.open()) {
0228             qWarning() << i18n("Cannot open tempfile %1", tempFile.fileName());
0229             return Unknown;
0230         }
0231         KIO::FileCopyJob *job = KIO::file_copy(url, QUrl::fromLocalFile(tempFile.fileName()), -1, KIO::Overwrite);
0232         if (!job->exec()) {
0233             qWarning() << i18n("Cannot download %1: %2", url.toDisplayString(), job->errorString());
0234             return FileDoesNotExist;
0235         }
0236         temporaryFile = tempFile.fileName();
0237     }
0238 
0239     if (flags & FileOpenReadOnly) {
0240         d->m_isReadOnly = true;
0241     }
0242 
0243     ErrorCode autosaveError = NoError;
0244 
0245     if (!d->m_isReadOnly) {
0246         autosaveError = d->initializeKAutoSave(*d->m_autosave, temporaryFile, flags);
0247         if (autosaveError != NoError) {
0248             return autosaveError;
0249         }
0250     }
0251 
0252     KCompressionDevice f(temporaryFile);
0253     if (f.open(QIODevice::ReadOnly)) {
0254         ReaderManager::ReaderPtr reader(ReaderManager::reader(f));
0255         errStatus = reader->read(*this);
0256 
0257         if (errStatus != KEduVocDocument::NoError) {
0258             errorMessage = i18n("Could not open or properly read \"%1\"\n(Error reported: %2)", url.toDisplayString(), reader->errorMessage());
0259         }
0260     } else {
0261         errStatus = FileCannotRead;
0262     }
0263 
0264     f.close();
0265 
0266     if (errStatus == KEduVocDocument::NoError) {
0267         setModified(false);
0268     } else {
0269         qWarning() << errorMessage;
0270     }
0271 
0272     return errStatus;
0273 }
0274 
0275 void KEduVocDocument::close()
0276 {
0277     if (!d->m_isReadOnly) {
0278         d->m_autosave->releaseLock();
0279     }
0280 }
0281 
0282 KEduVocDocument::ErrorCode KEduVocDocument::saveAs(const QUrl &url, FileType ft, FileHandlingFlags flags)
0283 {
0284     if (d->m_isReadOnly) {
0285         return FileIsReadOnly;
0286     }
0287 
0288     QUrl tmp(url);
0289 
0290     if (ft == Automatic) {
0291         if (tmp.path().right(strlen("." KVTML_EXT)) == "." KVTML_EXT)
0292             ft = Kvtml;
0293         else if (tmp.path().right(strlen("." CSV_EXT)) == "." CSV_EXT)
0294             ft = Csv;
0295         else {
0296             return FileTypeUnknown;
0297         }
0298     }
0299 
0300     QString errorMessage = i18n("Cannot write to file %1", tmp.toDisplayString());
0301 
0302     KAutoSaveFile *autosave;
0303 
0304     // If we don't care about the lock always create a new one
0305     // If we are changing files create a new lock
0306     if ((flags & FileIgnoreLock) || (d->m_autosave->managedFile() != tmp)) {
0307         autosave = new KAutoSaveFile;
0308         ErrorCode autosaveError = d->initializeKAutoSave(*autosave, tmp.toLocalFile(), flags);
0309         if (autosaveError != NoError) {
0310             delete autosave;
0311             return autosaveError;
0312         }
0313     }
0314 
0315     // We care about the lock and we think we still have it.
0316     else {
0317         autosave = d->m_autosave;
0318         // Is the lock still good?
0319         if (!autosave->exists()) {
0320             return FileCannotLock;
0321         }
0322     }
0323 
0324     QFile f(tmp.toLocalFile());
0325     if (!f.open(QIODevice::WriteOnly)) {
0326         qCritical() << i18n("Cannot write to file %1", f.fileName());
0327         return FileCannotWrite;
0328     }
0329 
0330     bool saved = false;
0331 
0332     switch (ft) {
0333     case Kvtml: {
0334         // write version 2 file
0335         KEduVocKvtml2Writer kvtmlWriter(&f);
0336         saved = kvtmlWriter.writeDoc(this, d->m_generator);
0337     } break;
0338         ///@todo port me
0339         //         case Kvtml1: {
0340         //             // write old version 1 file
0341         //             KEduVocKvtmlWriter kvtmlWriter( &f );
0342         //             saved = kvtmlWriter.writeDoc( this, d->m_generator );
0343         //         }
0344         //         break;
0345     case Csv: {
0346         KEduVocCsvWriter csvWriter(&f);
0347         saved = csvWriter.writeDoc(this, d->m_generator);
0348     } break;
0349     default: {
0350         qCritical() << "kvcotrainDoc::saveAs(): unknown filetype";
0351     } break;
0352     } // switch
0353 
0354     f.close();
0355 
0356     if (!saved) {
0357         qCritical() << "Error Saving File" << tmp.toDisplayString();
0358 
0359         if (autosave != d->m_autosave) {
0360             delete autosave;
0361         }
0362         return FileWriterFailed;
0363     }
0364 
0365     if (autosave != d->m_autosave) {
0366         // The order is important: release old lock, delete old locker and then claim new locker.
0367         d->m_autosave->releaseLock();
0368         delete d->m_autosave;
0369         d->m_autosave = autosave;
0370     }
0371 
0372     setModified(false);
0373     return NoError;
0374 }
0375 
0376 QByteArray KEduVocDocument::toByteArray(const QString &generator)
0377 {
0378     // no file needed
0379     KEduVocKvtml2Writer kvtmlWriter(nullptr);
0380     return kvtmlWriter.toByteArray(this, generator);
0381 }
0382 
0383 void KEduVocDocument::merge(KEduVocDocument *docToMerge, bool matchIdentifiers)
0384 {
0385     Q_UNUSED(docToMerge)
0386     Q_UNUSED(matchIdentifiers)
0387     qDebug() << "Merging of docs is not implemented"; /// @todo IMPLEMENT ME
0388     // This code was really horribly broken.
0389     // Now with the new classes we could attempt to reactivate it.
0390     // A rewrite might be easier.
0391 
0392     /*
0393       if (docToMerge) {
0394 
0395         QStringList new_names = docToMerge->lessonDescriptions();
0396 
0397         QStringList new_types = docToMerge->typeDescriptions();
0398 
0399         QStringList new_tenses = docToMerge->tenseDescriptions();
0400 
0401         QList<int> old_in_query = lessonsInPractice();
0402         QList<int> new_in_query = docToMerge->lessonsInPractice();
0403 
0404         QStringList new_usages = docToMerge->usageDescriptions();
0405 
0406         int lesson_offset = d->m_lessonDescriptions.count();
0407         for (int i = 0; i < new_names.count(); i++) {
0408           d->m_lessonDescriptions.append(new_names[i]);
0409         }
0410 
0411         for (int i = 0; i < new_in_query.count(); i++)
0412           old_in_query.append(new_in_query[i] + lesson_offset);
0413         setLessonsInPractice(old_in_query);
0414 
0415         int types_offset = d->m_typeDescriptions.count();
0416         for (int i = 0; i < new_types.count(); i++) {
0417           d->m_typeDescriptions.append(new_types[i]);
0418         }
0419 
0420         int tenses_offset = d->m_tenseDescriptions.count();
0421         for (int i = 0; i < new_tenses.count(); i++) {
0422           d->m_tenseDescriptions.append(new_tenses[i]);
0423         }
0424 
0425         int usages_offset = d->m_usageDescriptions.count();
0426         for (int i = 0; i < new_usages.count(); i++) {
0427           d->m_usageDescriptions.append(new_usages[i]);
0428         }
0429 
0430         bool equal = true;
0431         if (originalIdentifier() != docToMerge->originalIdentifier())
0432           equal = false;
0433         for (int i = 1; i < identifierCount(); i++)
0434           if (identifier(i) != docToMerge->identifier(i))
0435             equal = false;
0436 
0437         if (!matchIdentifiers)
0438           equal = true; ///@todo massive cheating, problem if docToMerge has more identifiers than this
0439 
0440         if (equal) {   // easy way: same language codes, just append
0441 
0442           for (int i = 0; i < docToMerge->entryCount(); i++) {
0443             KEduVocExpression *expr = docToMerge->entry(i);
0444 
0445             expr->setLesson(expr->lesson() + lesson_offset);
0446 
0447             for (int lang = 0; lang <= expr->translationCount(); lang++) {
0448               QString t = expr->translation(lang).type();
0449               // adjust type offset
0450               if (!t.isEmpty() && t.left(1) == QM_USER_TYPE) {
0451                 QString t2;
0452                 t.remove(0, 1);
0453                 t2.setNum(t.toInt() + types_offset);
0454                 t2.prepend(QM_USER_TYPE);
0455                 expr->translation(lang).setType (t2);
0456               }
0457 
0458               t = expr->translation(lang).usageLabel();
0459               // adjust usage offset
0460               QString tg;
0461               if (!t.isEmpty()) {
0462                 QString t2;
0463                 while (t.left(strlen(":")) == UL_USER_USAGE) {
0464                   QString n;
0465                   t.remove(0, 1);
0466                   int next;
0467                   if ((next = t.indexOf(":")) >= 0) {
0468                     n = t.left(next);
0469                     t.remove(0, next + 1);
0470                   }
0471                   else {
0472                     n = t;
0473                     t = "";
0474                   }
0475 
0476                   t2.setNum(n.toInt() + usages_offset);
0477                   t2.prepend(UL_USER_USAGE);
0478                   if (tg.length() == 0)
0479                     tg = t2;
0480                   else
0481                     tg += ':' + t2;
0482                 }
0483 
0484                 if (tg.length() == 0)
0485                   tg = t;
0486                 else if (t.length() != 0)
0487                   tg += ':' + t;
0488 
0489                 expr->translation(lang).setUsageLabel (tg);
0490               }
0491 
0492               KEduVocConjugation conj = expr->translation(lang).conjugation();
0493               bool condirty = false;
0494               for (int ci = 0; ci < conj.entryCount(); ci++) {
0495                 t = conj.getType(ci);
0496                 if (!t.isEmpty() && t.left(1) == UL_USER_TENSE) {
0497                   t.remove(0, strlen(UL_USER_TENSE));
0498                   QString t2;
0499                   t2.setNum(t.toInt() + tenses_offset);
0500                   t2.prepend(UL_USER_TENSE);
0501                   conj.setType(ci, t2);
0502                   condirty = true;
0503                 }
0504                 if (condirty)
0505                   expr->translation(lang).setConjugation(conj);
0506               }
0507             }
0508 
0509             appendEntry(expr);
0510           }
0511           setModified();
0512         }
0513         else { // hard way: move entries around, most attributes get lost
0514           QList<int> move_matrix;
0515           QList<bool> cs_equal;
0516           QString s;
0517 
0518           for (int i = 0; i < qMax (identifierCount(), docToMerge->identifierCount()); i++)
0519             cs_equal.append(false);
0520 
0521           move_matrix.append(docToMerge->indexOfIdentifier(originalIdentifier()));
0522           for (int i = 1; i < identifierCount(); i++)
0523             move_matrix.append(docToMerge->indexOfIdentifier(identifier(i)));
0524 
0525           for (int j = 0; j < docToMerge->entryCount(); j++) {
0526             KEduVocExpression new_expr;
0527             KEduVocExpression *expr = docToMerge->entry(j);
0528             new_expr.setLesson(expr->lesson()+lesson_offset);
0529 
0530             for (int i = 0; i < move_matrix.count(); i++) {
0531               int lpos = move_matrix[i];
0532               if (lpos >= 0) {
0533 
0534                 if (lpos == 0)
0535                   s = expr->original();
0536                 else
0537                   s = expr->translation(lpos);
0538 
0539                 if (!cs_equal[lpos]) {
0540                   cs_equal[lpos] = true;
0541                   QString id = lpos == 0 ? originalIdentifier() : identifier(lpos);
0542                 }
0543 
0544                 if (i == 0)
0545                   new_expr.setOriginal(s);
0546                 else
0547                   new_expr.setTranslation(i, s);
0548                 QString r = expr->remark(lpos);
0549                 new_expr.setRemark (i, r);
0550 
0551                 QString t = expr->type(lpos);
0552                 if (!t.isEmpty() && t.left(1) == QM_USER_TYPE) {
0553                   QString t2;
0554                   t.remove(0, 1);
0555                   t2.setNum(t.toInt() + types_offset);
0556                   t2.prepend(QM_USER_TYPE);
0557                   new_expr.setType(i, t2);
0558                 }
0559 
0560                 t = expr->usageLabel(lpos);
0561                 if (!t.isEmpty() && t.left(1) == QM_USER_TYPE) {
0562                   QString t2;
0563                   t.remove(0, 1);
0564                   t2.setNum(t.toInt() + usages_offset);
0565                   t2.prepend(QM_USER_TYPE);
0566                   new_expr.setUsageLabel(i, t2);
0567                 }
0568 
0569                 KEduVocConjugation conj = expr->conjugation(lpos);
0570                 for (int ci = 0; ci < conj.entryCount(); ci++) {
0571                   t = conj.getType(ci);
0572                   if (!t.isEmpty() && t.left(1) == QM_USER_TYPE) {
0573                     t.remove (0, strlen(QM_USER_TYPE));
0574                     QString t2;
0575                     t2.setNum(t.toInt() + tenses_offset);
0576                     t2.prepend(QM_USER_TYPE);
0577                     conj.setType(ci, t2);
0578                   }
0579                 }
0580 
0581               }
0582             }
0583             // only append if entries are used
0584             bool used = !new_expr.original().isEmpty();
0585             for (int i = 1; i < identifierCount(); i++)
0586               if (!new_expr.translation(i).isEmpty())
0587                 used = true;
0588 
0589             if (used) {
0590               appendEntry(&new_expr);
0591               setModified();
0592             }
0593           }
0594         }
0595       }
0596     */
0597 }
0598 
0599 const KEduVocIdentifier &KEduVocDocument::identifier(int index) const
0600 {
0601     if (index < 0 || index >= d->m_identifiers.size()) {
0602         qCritical() << " Error: Invalid identifier index: " << index;
0603     }
0604     return d->m_identifiers[index];
0605 }
0606 
0607 KEduVocIdentifier &KEduVocDocument::identifier(int index)
0608 {
0609     if (index < 0 || index >= d->m_identifiers.size()) {
0610         qCritical() << " Error: Invalid identifier index: " << index;
0611     }
0612     return d->m_identifiers[index];
0613 }
0614 
0615 void KEduVocDocument::setIdentifier(int idx, const KEduVocIdentifier &id)
0616 {
0617     if (idx >= 0 && idx < d->m_identifiers.size()) {
0618         d->m_identifiers[idx] = id;
0619     }
0620     setModified(true);
0621 }
0622 
0623 // works if const is removed
0624 int KEduVocDocument::indexOfIdentifier(const QString &name) const
0625 {
0626     for (int i = 0; i < identifierCount(); i++)
0627         if (identifier(i).locale() == name)
0628             return i;
0629     return -1;
0630 }
0631 
0632 void KEduVocDocument::removeIdentifier(int index)
0633 {
0634     if (index < d->m_identifiers.size() && index >= 0) {
0635         d->m_identifiers.removeAt(index);
0636         d->m_lessonContainer->removeTranslation(index);
0637     }
0638 }
0639 
0640 bool KEduVocDocument::isModified() const
0641 {
0642     return d->m_dirty;
0643 }
0644 
0645 int KEduVocDocument::identifierCount() const
0646 {
0647     return d->m_identifiers.count(); // number of translations
0648 }
0649 
0650 int KEduVocDocument::appendIdentifier(const KEduVocIdentifier &id)
0651 {
0652     int i = d->m_identifiers.size();
0653     // qDebug() << "appendIdentifier: " << i << id.name() << id.locale();
0654     d->m_identifiers.append(id);
0655     if (id.name().isEmpty()) {
0656         if (i == 0) {
0657             identifier(i).setName(i18nc("The name of the first language/column of vocabulary, if we have to guess it.", "Original"));
0658         } else {
0659             identifier(i).setName(i18nc("The name of the second, third ... language/column of vocabulary, if we have to guess it.", "Translation %1", i));
0660         }
0661     }
0662 
0663     return i;
0664 }
0665 
0666 KEduVocLesson *KEduVocDocument::lesson()
0667 {
0668     return d->m_lessonContainer;
0669 }
0670 
0671 KEduVocWordType *KEduVocDocument::wordTypeContainer()
0672 {
0673     return d->m_wordTypeContainer;
0674 }
0675 
0676 KEduVocLeitnerBox *KEduVocDocument::leitnerContainer()
0677 {
0678     return d->m_leitnerContainer;
0679 }
0680 
0681 QUrl KEduVocDocument::url() const
0682 {
0683     return d->m_autosave->managedFile();
0684 }
0685 
0686 void KEduVocDocument::setUrl(const QUrl &url)
0687 {
0688     d->m_autosave->setManagedFile(url);
0689 }
0690 
0691 QString KEduVocDocument::title() const
0692 {
0693     if (d->m_title.isEmpty())
0694         return d->m_autosave->managedFile().fileName();
0695     else
0696         return d->m_title;
0697 }
0698 
0699 void KEduVocDocument::setTitle(const QString &title)
0700 {
0701     d->m_title = title;
0702     ///@todo decouple document title and root lesson title
0703     d->m_lessonContainer->setName(title);
0704     setModified(true);
0705 }
0706 
0707 QString KEduVocDocument::author() const
0708 {
0709     return d->m_author;
0710 }
0711 
0712 void KEduVocDocument::setAuthor(const QString &s)
0713 {
0714     d->m_author = s.simplified();
0715     setModified(true);
0716 }
0717 
0718 QString KEduVocDocument::authorContact() const
0719 {
0720     return d->m_authorContact;
0721 }
0722 
0723 void KEduVocDocument::setAuthorContact(const QString &s)
0724 {
0725     d->m_authorContact = s.simplified();
0726     setModified(true);
0727 }
0728 
0729 QString KEduVocDocument::license() const
0730 {
0731     return d->m_license;
0732 }
0733 
0734 QString KEduVocDocument::documentComment() const
0735 {
0736     return d->m_comment;
0737 }
0738 
0739 void KEduVocDocument::setCategory(const QString &category)
0740 {
0741     d->m_category = category;
0742     setModified(true);
0743 }
0744 
0745 QString KEduVocDocument::category() const
0746 {
0747     return d->m_category;
0748     ///@todo make writer/reader use this
0749 }
0750 
0751 void KEduVocDocument::queryIdentifier(QString &org, QString &trans) const
0752 {
0753     org = d->m_queryorg;
0754     trans = d->m_querytrans;
0755 }
0756 
0757 void KEduVocDocument::setQueryIdentifier(const QString &org, const QString &trans)
0758 {
0759     d->m_queryorg = org;
0760     d->m_querytrans = trans;
0761     setModified(true);
0762 }
0763 
0764 void KEduVocDocument::setLicense(const QString &s)
0765 {
0766     d->m_license = s.simplified();
0767     setModified(true);
0768 }
0769 
0770 void KEduVocDocument::setDocumentComment(const QString &s)
0771 {
0772     d->m_comment = s.trimmed();
0773     setModified(true);
0774 }
0775 
0776 void KEduVocDocument::setGenerator(const QString &generator)
0777 {
0778     d->m_generator = generator;
0779     setModified(true);
0780 }
0781 
0782 QString KEduVocDocument::generator() const
0783 {
0784     return d->m_generator;
0785 }
0786 
0787 QString KEduVocDocument::version() const
0788 {
0789     return d->m_version;
0790 }
0791 
0792 void KEduVocDocument::setVersion(const QString &vers)
0793 {
0794     d->m_version = vers;
0795     setModified(true);
0796 }
0797 
0798 QString KEduVocDocument::csvDelimiter() const
0799 {
0800     return d->m_csvDelimiter;
0801 }
0802 
0803 void KEduVocDocument::setCsvDelimiter(const QString &delimiter)
0804 {
0805     d->m_csvDelimiter = delimiter;
0806     setModified(true);
0807 }
0808 
0809 QString KEduVocDocument::pattern(FileDialogMode mode)
0810 {
0811     static const struct SupportedFilter {
0812         bool reading;
0813         bool writing;
0814         const char *extensions;
0815         const KLazyLocalizedString description;
0816     } filters[] = {{true, true, "*.kvtml", kli18n("KDE Vocabulary Document")},
0817                    {true, false, "*.wql", kli18n("KWordQuiz Document")},
0818                    {true, false, "*.xml.qz *.pau.gz", kli18n("Pauker Lesson")},
0819                    {true, false, "*.voc", kli18n("Vokabeltrainer")},
0820                    {true, false, "*.xdxf", kli18n("XML Dictionary Exchange Format")},
0821                    {true, true, "*.csv", kli18n("Comma Separated Values (CSV)")},
0822                    // last is marker for the end, do not remove it
0823                    {false, false, nullptr, KLazyLocalizedString()}};
0824     QStringList newfilters;
0825     QStringList allext;
0826     for (int i = 0; filters[i].extensions; ++i) {
0827         if ((mode == Reading && filters[i].reading) || (mode == Writing && filters[i].writing)) {
0828             newfilters.append(KLocalizedString(filters[i].description).toString() + " (" + QLatin1String(filters[i].extensions) + ')');
0829             allext.append(QLatin1String(filters[i].extensions));
0830         }
0831     }
0832     if (mode == Reading) {
0833         newfilters.prepend(allext.join(QLatin1Char(' ')) + '|' + i18n("All supported documents"));
0834     }
0835     return newfilters.join(QLatin1String(";;"));
0836 }
0837 
0838 QString KEduVocDocument::errorDescription(int errorCode)
0839 {
0840     switch (errorCode) {
0841     case NoError:
0842         return i18n("No error found.");
0843 
0844     case InvalidXml:
0845         return i18n("Invalid XML in document.");
0846     case FileTypeUnknown:
0847         return i18n("Unknown file type.");
0848     case FileCannotWrite:
0849         return i18n("File is not writeable.");
0850     case FileWriterFailed:
0851         return i18n("File writer failed.");
0852     case FileCannotRead:
0853         return i18n("File is not readable.");
0854     case FileReaderFailed:
0855         return i18n("The file reader failed.");
0856     case FileDoesNotExist:
0857         return i18n("The file does not exist.");
0858     case FileLocked:
0859         return i18n("The file is locked by another process.");
0860     case FileCannotLock:
0861         return i18n("The lock file can't be created.");
0862     case Unknown:
0863     default:
0864         return i18n("Unknown error.");
0865     }
0866 }
0867 
0868 #include "moc_keduvocdocument.cpp"