1 /*
2 This file is part of Lokalize
3 This file contains parts of KBabel code
4
5 SPDX-FileCopyrightText: 1999-2000 Matthias Kiefer <matthias.kiefer@gmx.de>
6 SPDX-FileCopyrightText: 2001-2005 Stanislav Visnovsky <visnovsky@kde.org>
7 SPDX-FileCopyrightText: 2006 Nicolas Goutte <goutte@kde.org>
8 SPDX-FileCopyrightText: 2007-2014 Nick Shaforostoff <shafff@ukr.net>
9 SPDX-FileCopyrightText: 2018-2019 Simon Depiets <sdepiets@gmail.com>
10
11 SPDX-License-Identifier: GPL-2.0-or-later WITH LicenseRef-Qt-Commercial-exception-1.0
12 */
13
14 #include "catalog.h"
15 #include "catalog_private.h"
16 #include "project.h"
17 #include "projectmodel.h" //to notify about modification
18
19 #include "catalogstorage.h"
20 #include "gettextstorage.h"
21 #include "gettextimport.h"
22 #include "gettextexport.h"
23 #include "xliffstorage.h"
24 #include "tsstorage.h"
25
26 #include "mergecatalog.h"
27
28 #include "version.h"
29 #include "prefs_lokalize.h"
30 #include "jobs.h"
31 #include "dbfilesmodel.h"
32
33 #include <QString>
34 #include <QStringBuilder>
35 #include <QMap>
36 #include <QBuffer>
37 #include <QFileInfo>
38 #include <QDir>
39
40 #include <klocalizedstring.h>
41
42 #ifdef Q_OS_WIN
43 #define U QLatin1String
44 #else
45 #define U QStringLiteral
46 #endif
47
48 //QString Catalog::supportedMimeFilters("text/x-gettext-translation application/x-xliff application/x-linguist"); //" text/x-gettext-translation-template")
supportedFileTypes(bool includeTemplates)49 QString Catalog::supportedFileTypes(bool includeTemplates)
50 {
51 QString sep = QStringLiteral(";;");
52 QString all = i18n("All supported files (*.po *.pot *.xlf *.xliff *.ts)") + sep;
53 return all + (includeTemplates ? i18n("Gettext (*.po *.pot)") : i18n("Gettext (*.po)")) + sep + i18n("XLIFF (*.xlf *.xliff)") + sep + i18n("Linguist (*.ts)");
54 }
55
56 static const QString extensions[] = {U(".po"), U(".pot"), U(".xlf"), U(".xliff"), U(".ts")};
57
58 static const char* const xliff_states[] = {
59 I18N_NOOP("New"), I18N_NOOP("Needs translation"), I18N_NOOP("Needs full localization"), I18N_NOOP("Needs adaptation"), I18N_NOOP("Translated"),
60 I18N_NOOP("Needs translation review"), I18N_NOOP("Needs full localization review"), I18N_NOOP("Needs adaptation review"), I18N_NOOP("Final"),
61 I18N_NOOP("Signed-off")
62 };
63
states()64 const char* const* Catalog::states()
65 {
66 return xliff_states;
67 }
68
supportedExtensions()69 QStringList Catalog::supportedExtensions()
70 {
71 QStringList result;
72 int i = sizeof(extensions) / sizeof(QString);
73 while (--i >= 0)
74 result.append(extensions[i]);
75 return result;
76 }
77
extIsSupported(const QString & path)78 bool Catalog::extIsSupported(const QString& path)
79 {
80 QStringList ext = supportedExtensions();
81 int i = ext.size();
82 while (--i >= 0 && !path.endsWith(ext.at(i)))
83 ;
84 return i != -1;
85 }
86
Catalog(QObject * parent)87 Catalog::Catalog(QObject *parent)
88 : QUndoStack(parent)
89 , d(this)
90 , m_storage(nullptr)
91 {
92 //cause refresh events for files modified from lokalize itself aint delivered automatically
93 connect(this, QOverload<const QString &>::of(&Catalog::signalFileSaved), Project::instance()->model(), QOverload<const QString &>::of(&ProjectModel::slotFileSaved), Qt::QueuedConnection);
94
95 QTimer* t = &(d._autoSaveTimer);
96 t->setInterval(2 * 60 * 1000);
97 t->setSingleShot(false);
98 connect(t, &QTimer::timeout, this, &Catalog::doAutoSave);
99 connect(this, QOverload<>::of(&Catalog::signalFileSaved), t, QOverload<>::of(&QTimer::start));
100 connect(this, QOverload<>::of(&Catalog::signalFileLoaded), t, QOverload<>::of(&QTimer::start));
101 connect(this, &Catalog::indexChanged, this, &Catalog::setAutoSaveDirty);
102 connect(Project::local(), &Project::configChanged, this, &Catalog::projectConfigChanged);
103 }
104
~Catalog()105 Catalog::~Catalog()
106 {
107 clear();
108 //delete m_storage; //deleted in clear();
109 }
110
111
clear()112 void Catalog::clear()
113 {
114 setIndex(cleanIndex());//to keep TM in sync
115 QUndoStack::clear();
116 d._errorIndex.clear();
117 d._nonApprovedIndex.clear();
118 d._nonApprovedNonEmptyIndex.clear();
119 d._emptyIndex.clear();
120 delete m_storage; m_storage = nullptr;
121 d._filePath.clear();
122 d._lastModifiedPos = DocPosition();
123 d._modifiedEntries.clear();
124
125 while (!d._altTransCatalogs.isEmpty())
126 d._altTransCatalogs.takeFirst()->deleteLater();
127
128 d._altTranslations.clear();
129 /*
130 d.msgidDiffList.clear();
131 d.msgstr2MsgidDiffList.clear();
132 d.diffCache.clear();
133 */
134 }
135
136
137
push(QUndoCommand * cmd)138 void Catalog::push(QUndoCommand* cmd)
139 {
140 generatePhaseForCatalogIfNeeded(this);
141 QUndoStack::push(cmd);
142 }
143
144
145 //BEGIN STORAGE TRANSLATION
146
capabilities() const147 int Catalog::capabilities() const
148 {
149 if (Q_UNLIKELY(!m_storage)) return 0;
150
151 return m_storage->capabilities();
152 }
153
numberOfEntries() const154 int Catalog::numberOfEntries() const
155 {
156 if (Q_UNLIKELY(!m_storage)) return 0;
157
158 return m_storage->size();
159 }
160
161
alterForSinglePlural(const Catalog * th,DocPosition pos)162 static DocPosition alterForSinglePlural(const Catalog* th, DocPosition pos)
163 {
164 //if source lang is english (implied) and target lang has only 1 plural form (e.g. Chinese)
165 if (Q_UNLIKELY(th->numberOfPluralForms() == 1 && th->isPlural(pos)))
166 pos.form = 1;
167 return pos;
168 }
169
msgid(const DocPosition & pos) const170 QString Catalog::msgid(const DocPosition& pos) const
171 {
172 if (Q_UNLIKELY(!m_storage))
173 return QString();
174
175 return m_storage->source(alterForSinglePlural(this, pos));
176 }
177
msgidWithPlurals(const DocPosition & pos,bool truncateFirstLine) const178 QString Catalog::msgidWithPlurals(const DocPosition& pos, bool truncateFirstLine) const
179 {
180 if (Q_UNLIKELY(!m_storage))
181 return QString();
182 return m_storage->sourceWithPlurals(pos, truncateFirstLine);
183 }
184
msgstr(const DocPosition & pos) const185 QString Catalog::msgstr(const DocPosition& pos) const
186 {
187 if (Q_UNLIKELY(!m_storage))
188 return QString();
189
190 return m_storage->target(pos);
191 }
192
msgstrWithPlurals(const DocPosition & pos,bool truncateFirstLine) const193 QString Catalog::msgstrWithPlurals(const DocPosition& pos, bool truncateFirstLine) const
194 {
195 if (Q_UNLIKELY(!m_storage))
196 return QString();
197
198 return m_storage->targetWithPlurals(pos, truncateFirstLine);
199 }
200
201
sourceWithTags(const DocPosition & pos) const202 CatalogString Catalog::sourceWithTags(const DocPosition& pos) const
203 {
204 if (Q_UNLIKELY(!m_storage))
205 return CatalogString();
206
207 return m_storage->sourceWithTags(alterForSinglePlural(this, pos));
208
209 }
targetWithTags(const DocPosition & pos) const210 CatalogString Catalog::targetWithTags(const DocPosition& pos) const
211 {
212 if (Q_UNLIKELY(!m_storage))
213 return CatalogString();
214
215 return m_storage->targetWithTags(pos);
216 }
217
catalogString(const DocPosition & pos) const218 CatalogString Catalog::catalogString(const DocPosition& pos) const
219 {
220 if (Q_UNLIKELY(!m_storage))
221 return CatalogString();
222
223 return m_storage->catalogString(pos.part == DocPosition::Source ? alterForSinglePlural(this, pos) : pos);
224 }
225
226
notes(const DocPosition & pos) const227 QVector<Note> Catalog::notes(const DocPosition& pos) const
228 {
229 if (Q_UNLIKELY(!m_storage))
230 return QVector<Note>();
231
232 return m_storage->notes(pos);
233 }
234
developerNotes(const DocPosition & pos) const235 QVector<Note> Catalog::developerNotes(const DocPosition& pos) const
236 {
237 if (Q_UNLIKELY(!m_storage))
238 return QVector<Note>();
239
240 return m_storage->developerNotes(pos);
241 }
242
setNote(const DocPosition & pos,const Note & note)243 Note Catalog::setNote(const DocPosition& pos, const Note& note)
244 {
245 if (Q_UNLIKELY(!m_storage))
246 return Note();
247
248 return m_storage->setNote(pos, note);
249 }
250
noteAuthors() const251 QStringList Catalog::noteAuthors() const
252 {
253 if (Q_UNLIKELY(!m_storage))
254 return QStringList();
255
256 return m_storage->noteAuthors();
257 }
258
attachAltTransCatalog(Catalog * altCat)259 void Catalog::attachAltTransCatalog(Catalog* altCat)
260 {
261 d._altTransCatalogs.append(altCat);
262 if (numberOfEntries() != altCat->numberOfEntries())
263 qCWarning(LOKALIZE_LOG) << altCat->url() << "has different number of entries";
264 }
265
attachAltTrans(int entry,const AltTrans & trans)266 void Catalog::attachAltTrans(int entry, const AltTrans& trans)
267 {
268 d._altTranslations.insert(entry, trans);
269 }
270
altTrans(const DocPosition & pos) const271 QVector<AltTrans> Catalog::altTrans(const DocPosition& pos) const
272 {
273 QVector<AltTrans> result;
274 if (m_storage)
275 result = m_storage->altTrans(pos);
276
277 for (Catalog* altCat : d._altTransCatalogs) {
278 if (pos.entry >= altCat->numberOfEntries()) {
279 qCDebug(LOKALIZE_LOG) << "ignoring" << altCat->url() << "this time because" << pos.entry << "<" << altCat->numberOfEntries();
280 continue;
281 }
282
283 if (altCat->source(pos) != source(pos)) {
284 qCDebug(LOKALIZE_LOG) << "ignoring" << altCat->url() << "this time because <source>s don't match";
285 continue;
286 }
287
288 QString target = altCat->msgstr(pos);
289 if (!target.isEmpty() && altCat->isApproved(pos)) {
290 result << AltTrans();
291 result.last().target = target;
292 result.last().type = AltTrans::Reference;
293 result.last().origin = altCat->url();
294 }
295 }
296 if (d._altTranslations.contains(pos.entry))
297 result << d._altTranslations.value(pos.entry);
298 return result;
299 }
300
sourceFiles(const DocPosition & pos) const301 QStringList Catalog::sourceFiles(const DocPosition& pos) const
302 {
303 if (Q_UNLIKELY(!m_storage))
304 return QStringList();
305
306 return m_storage->sourceFiles(pos);
307 }
308
id(const DocPosition & pos) const309 QString Catalog::id(const DocPosition& pos) const
310 {
311 if (Q_UNLIKELY(!m_storage))
312 return QString();
313
314 return m_storage->id(pos);
315 }
316
context(const DocPosition & pos) const317 QStringList Catalog::context(const DocPosition& pos) const
318 {
319 if (Q_UNLIKELY(!m_storage))
320 return QStringList();
321
322 return m_storage->context(pos);
323 }
324
setPhase(const DocPosition & pos,const QString & phase)325 QString Catalog::setPhase(const DocPosition& pos, const QString& phase)
326 {
327 if (Q_UNLIKELY(!m_storage))
328 return QString();
329
330 return m_storage->setPhase(pos, phase);
331 }
332
333
setActivePhase(const QString & phase,ProjectLocal::PersonRole role)334 void Catalog::setActivePhase(const QString& phase, ProjectLocal::PersonRole role)
335 {
336 d._phase = phase;
337 d._phaseRole = role;
338 updateApprovedEmptyIndexCache();
339 Q_EMIT activePhaseChanged();
340 }
341
updateApprovedEmptyIndexCache()342 void Catalog::updateApprovedEmptyIndexCache()
343 {
344 if (Q_UNLIKELY(!m_storage))
345 return;
346
347 //index cache TODO profile?
348 d._nonApprovedIndex.clear();
349 d._nonApprovedNonEmptyIndex.clear();
350 d._emptyIndex.clear();
351
352 DocPosition pos(0);
353 const int limit = m_storage->size();
354 while (pos.entry < limit) {
355 if (m_storage->isEmpty(pos))
356 d._emptyIndex << pos.entry;
357 if (!isApproved(pos)) {
358 d._nonApprovedIndex << pos.entry;
359 if (!m_storage->isEmpty(pos)) {
360 d._nonApprovedNonEmptyIndex << pos.entry;
361 }
362 }
363 ++(pos.entry);
364 }
365
366 Q_EMIT signalNumberOfFuzziesChanged();
367 Q_EMIT signalNumberOfEmptyChanged();
368 }
369
phase(const DocPosition & pos) const370 QString Catalog::phase(const DocPosition& pos) const
371 {
372 if (Q_UNLIKELY(!m_storage))
373 return QString();
374
375 return m_storage->phase(pos);
376 }
377
phase(const QString & name) const378 Phase Catalog::phase(const QString& name) const
379 {
380 return m_storage->phase(name);
381 }
382
allPhases() const383 QList<Phase> Catalog::allPhases() const
384 {
385 return m_storage->allPhases();
386 }
387
phaseNotes(const QString & phase) const388 QVector<Note> Catalog::phaseNotes(const QString& phase) const
389 {
390 return m_storage->phaseNotes(phase);
391 }
392
setPhaseNotes(const QString & phase,QVector<Note> notes)393 QVector<Note> Catalog::setPhaseNotes(const QString& phase, QVector<Note> notes)
394 {
395 return m_storage->setPhaseNotes(phase, notes);
396 }
397
allTools() const398 QMap<QString, Tool> Catalog::allTools() const
399 {
400 return m_storage->allTools();
401 }
402
isPlural(uint index) const403 bool Catalog::isPlural(uint index) const
404 {
405 return m_storage && m_storage->isPlural(DocPosition(index));
406 }
407
isApproved(uint index) const408 bool Catalog::isApproved(uint index) const
409 {
410 if (Q_UNLIKELY(!m_storage))
411 return false;
412
413 bool extendedStates = m_storage->capabilities()&ExtendedStates;
414
415 return (extendedStates &&::isApproved(state(DocPosition(index)), activePhaseRole()))
416 || (!extendedStates && m_storage->isApproved(DocPosition(index)));
417 }
418
state(const DocPosition & pos) const419 TargetState Catalog::state(const DocPosition& pos) const
420 {
421 if (Q_UNLIKELY(!m_storage))
422 return NeedsTranslation;
423
424 if (m_storage->capabilities()&ExtendedStates)
425 return m_storage->state(pos);
426 else
427 return closestState(m_storage->isApproved(pos), activePhaseRole());
428 }
429
isEmpty(uint index) const430 bool Catalog::isEmpty(uint index) const
431 {
432 return m_storage && m_storage->isEmpty(DocPosition(index));
433 }
434
isEmpty(const DocPosition & pos) const435 bool Catalog::isEmpty(const DocPosition& pos) const
436 {
437 return m_storage && m_storage->isEmpty(pos);
438 }
439
440
isEquivTrans(const DocPosition & pos) const441 bool Catalog::isEquivTrans(const DocPosition& pos) const
442 {
443 return m_storage && m_storage->isEquivTrans(pos);
444 }
445
binUnitsCount() const446 int Catalog::binUnitsCount() const
447 {
448 return m_storage ? m_storage->binUnitsCount() : 0;
449 }
450
unitById(const QString & id) const451 int Catalog::unitById(const QString& id) const
452 {
453 return m_storage ? m_storage->unitById(id) : 0;
454 }
455
mimetype()456 QString Catalog::mimetype()
457 {
458 if (Q_UNLIKELY(!m_storage))
459 return QString();
460
461 return m_storage->mimetype();
462 }
463
fileType()464 QString Catalog::fileType()
465 {
466 if (Q_UNLIKELY(!m_storage))
467 return QString();
468
469 return m_storage->fileType();
470 }
471
type()472 CatalogType Catalog::type()
473 {
474 if (Q_UNLIKELY(!m_storage))
475 return Gettext;
476
477 return m_storage->type();
478 }
479
sourceLangCode() const480 QString Catalog::sourceLangCode() const
481 {
482 if (Q_UNLIKELY(!m_storage))
483 return QString();
484
485 return m_storage->sourceLangCode();
486 }
487
targetLangCode() const488 QString Catalog::targetLangCode() const
489 {
490 if (Q_UNLIKELY(!m_storage))
491 return QString();
492
493 return m_storage->targetLangCode();
494 }
495
setTargetLangCode(const QString & targetLangCode)496 void Catalog::setTargetLangCode(const QString& targetLangCode)
497 {
498 if (Q_UNLIKELY(!m_storage))
499 return;
500
501 bool notify = m_storage->targetLangCode() != targetLangCode;
502 m_storage->setTargetLangCode(targetLangCode);
503 if (notify) Q_EMIT signalFileLoaded();
504 }
505
506 //END STORAGE TRANSLATION
507
508 //BEGIN OPEN/SAVE
509 #define DOESNTEXIST -1
510 #define ISNTREADABLE -2
511 #define UNKNOWNFORMAT -3
512
checkAutoSave(const QString & url)513 KAutoSaveFile* Catalog::checkAutoSave(const QString& url)
514 {
515 KAutoSaveFile* autoSave = nullptr;
516 const QList<KAutoSaveFile*> staleFiles = KAutoSaveFile::staleFiles(QUrl::fromLocalFile(url));
517 for (KAutoSaveFile *stale : staleFiles) {
518 if (stale->open(QIODevice::ReadOnly) && !autoSave) {
519 autoSave = stale;
520 autoSave->setParent(this);
521 } else
522 stale->deleteLater();
523 }
524 if (autoSave)
525 qCInfo(LOKALIZE_LOG) << "autoSave" << autoSave->fileName();
526 return autoSave;
527 }
528
loadFromUrl(const QString & filePath,const QString & saidUrl,int * fileSize,bool fast)529 int Catalog::loadFromUrl(const QString& filePath, const QString& saidUrl, int* fileSize, bool fast)
530 {
531 QFileInfo info(filePath);
532 if (Q_UNLIKELY(!info.exists() || info.isDir()))
533 return DOESNTEXIST;
534 if (Q_UNLIKELY(!info.isReadable()))
535 return ISNTREADABLE;
536 bool readOnly = !info.isWritable();
537
538
539 QElapsedTimer a; a.start();
540
541 QFile file(filePath);
542 if (!file.open(QIODevice::ReadOnly))
543 return ISNTREADABLE;//TODO
544
545 CatalogStorage* storage = nullptr;
546 if (filePath.endsWith(QLatin1String(".po")) || filePath.endsWith(QLatin1String(".pot")))
547 storage = new GettextCatalog::GettextStorage;
548 else if (filePath.endsWith(QLatin1String(".xlf")) || filePath.endsWith(QLatin1String(".xliff")))
549 storage = new XliffStorage;
550 else if (filePath.endsWith(QLatin1String(".ts")))
551 storage = new TsStorage;
552 else {
553 //try harder
554 QTextStream in(&file);
555 int i = 0;
556 bool gettext = false;
557 while (!in.atEnd() && ++i < 64 && !gettext)
558 gettext = in.readLine().contains(QLatin1String("msgid"));
559 if (gettext) storage = new GettextCatalog::GettextStorage;
560 else return UNKNOWNFORMAT;
561 }
562
563 int line = storage->load(&file);
564
565 file.close();
566
567 if (Q_UNLIKELY(line != 0 || (!storage->size() && (line == -1)))) {
568 delete storage;
569 return line;
570 }
571
572 if (a.elapsed() > 100) qCDebug(LOKALIZE_LOG) << filePath << "opened in" << a.elapsed();
573
574 //ok...
575 clear();
576
577 //commit transaction
578 m_storage = storage;
579
580 updateApprovedEmptyIndexCache();
581
582 d._numberOfPluralForms = storage->numberOfPluralForms();
583 d._autoSaveDirty = true;
584 d._readOnly = readOnly;
585 d._filePath = saidUrl.isEmpty() ? filePath : saidUrl;
586
587 //set some sane role, a real phase with a nmae will be created later with the first edit command
588 setActivePhase(QString(), Project::local()->role());
589
590 if (!fast) {
591 KAutoSaveFile* autoSave = checkAutoSave(d._filePath);
592 d._autoSaveRecovered = autoSave;
593 if (autoSave) {
594 d._autoSave->deleteLater();
595 d._autoSave = autoSave;
596
597 //restore 'modified' status for entries
598 MergeCatalog* mergeCatalog = new MergeCatalog(this, this);
599 int errorLine = mergeCatalog->loadFromUrl(autoSave->fileName());
600 if (Q_LIKELY(errorLine == 0))
601 mergeCatalog->copyToBaseCatalog();
602 mergeCatalog->deleteLater();
603 d._autoSave->close();
604 } else
605 d._autoSave->setManagedFile(QUrl::fromLocalFile(d._filePath));
606 }
607
608 if (fileSize)
609 *fileSize = file.size();
610
611 Q_EMIT signalFileLoaded();
612 Q_EMIT signalFileLoaded(d._filePath);
613 return 0;
614 }
615
save()616 bool Catalog::save()
617 {
618 return saveToUrl(d._filePath);
619 }
620
621 //this function is not called if QUndoStack::isClean() !
saveToUrl(QString localFilePath)622 bool Catalog::saveToUrl(QString localFilePath)
623 {
624 if (Q_UNLIKELY(!m_storage))
625 return true;
626
627 bool nameChanged = localFilePath.length();
628 if (Q_LIKELY(!nameChanged))
629 localFilePath = d._filePath;
630
631 QString localPath = QFileInfo(localFilePath).absolutePath();
632 if (!QFileInfo::exists(localPath))
633 if (!QDir::root().mkpath(localPath))
634 return false;
635 QFile file(localFilePath);
636 if (Q_UNLIKELY(!file.open(QIODevice::WriteOnly))) //i18n("Wasn't able to open file %1",filename.ascii());
637 return false;
638
639 bool belongsToProject = localFilePath.contains(Project::instance()->poDir());
640 if (Q_UNLIKELY(!m_storage->save(&file, belongsToProject)))
641 return false;
642
643 file.close();
644
645 d._autoSave->remove();
646 d._autoSaveRecovered = false;
647 setClean(); //undo/redo
648 if (nameChanged) {
649 d._filePath = localFilePath;
650 d._autoSave->setManagedFile(QUrl::fromLocalFile(localFilePath));
651 }
652
653 //Settings::self()->setCurrentGroup("Bookmarks");
654 //Settings::self()->addItemIntList(d._filePath.url(),d._bookmarkIndex);
655
656 Q_EMIT signalFileSaved();
657 Q_EMIT signalFileSaved(localFilePath);
658 return true;
659 /*
660 else if (status==NO_PERMISSIONS)
661 {
662 if (KMessageBox::warningContinueCancel(this,
663 i18n("You do not have permission to write to file:\n%1\n"
664 "Do you want to save to another file or cancel?", _currentURL.prettyUrl()),
665 i18n("Error"),KStandardGuiItem::save())==KMessageBox::Continue)
666 return fileSaveAs();
667
668 }
669 */
670 }
671
doAutoSave()672 void Catalog::doAutoSave()
673 {
674 if (isClean() || !(d._autoSaveDirty))
675 return;
676 if (Q_UNLIKELY(!m_storage))
677 return;
678 if (!d._autoSave->open(QIODevice::WriteOnly)) {
679 Q_EMIT signalFileAutoSaveFailed(d._autoSave->fileName());
680 return;
681 }
682 qCInfo(LOKALIZE_LOG) << "doAutoSave" << d._autoSave->fileName();
683 m_storage->save(d._autoSave);
684 d._autoSave->close();
685 d._autoSaveDirty = false;
686 }
687
projectConfigChanged()688 void Catalog::projectConfigChanged()
689 {
690 setActivePhase(activePhase(), Project::local()->role());
691 }
692
contents()693 QByteArray Catalog::contents()
694 {
695 QBuffer buf;
696 buf.open(QIODevice::WriteOnly);
697 m_storage->save(&buf);
698 buf.close();
699 return buf.data();
700 }
701
702 //END OPEN/SAVE
703
704
705
706 /**
707 * helper method to keep db in a good shape :)
708 * called on
709 * 1) entry switch
710 * 2) automatic editing code like replace or undo/redo operation
711 **/
updateDB(const QString & filePath,const QString & ctxt,const CatalogString & english,const CatalogString & newTarget,int form,bool approved,const QString & dbName)712 static void updateDB(
713 const QString& filePath,
714 const QString& ctxt,
715 const CatalogString& english,
716 const CatalogString& newTarget,
717 int form,
718 bool approved,
719 const QString& dbName
720 //const DocPosition&,//for back tracking
721 )
722 {
723 TM::UpdateJob* j = new TM::UpdateJob(filePath, ctxt, english, newTarget, form, approved,
724 dbName);
725 TM::threadPool()->start(j);
726 }
727
728
729 //BEGIN UNDO/REDO
undo()730 const DocPosition& Catalog::undo()
731 {
732 QUndoStack::undo();
733 return d._lastModifiedPos;
734 }
735
redo()736 const DocPosition& Catalog::redo()
737 {
738 QUndoStack::redo();
739 return d._lastModifiedPos;
740 }
741
flushUpdateDBBuffer()742 void Catalog::flushUpdateDBBuffer()
743 {
744 if (!Settings::autoaddTM())
745 return;
746
747 DocPosition pos = d._lastModifiedPos;
748 if (pos.entry == -1 || pos.entry >= numberOfEntries()) {
749 //nothing to flush
750 //qCWarning(LOKALIZE_LOG)<<"nothing to flush or new file opened";
751 return;
752 }
753 QString dbName;
754 if (Project::instance()->targetLangCode() == targetLangCode()) {
755 dbName = Project::instance()->projectID();
756 } else {
757 dbName = sourceLangCode() + '-' + targetLangCode();
758 qCInfo(LOKALIZE_LOG) << "updating" << dbName << "because target language of project db does not match" << Project::instance()->targetLangCode() << targetLangCode();
759 if (!TM::DBFilesModel::instance()->m_configurations.contains(dbName)) {
760 TM::OpenDBJob* openDBJob = new TM::OpenDBJob(dbName, TM::Local, true);
761 connect(openDBJob, &TM::OpenDBJob::done, TM::DBFilesModel::instance(), &TM::DBFilesModel::updateProjectTmIndex);
762
763 openDBJob->m_setParams = true;
764 openDBJob->m_tmConfig.markup = Project::instance()->markup();
765 openDBJob->m_tmConfig.accel = Project::instance()->accel();
766 openDBJob->m_tmConfig.sourceLangCode = sourceLangCode();
767 openDBJob->m_tmConfig.targetLangCode = targetLangCode();
768
769 TM::DBFilesModel::instance()->openDB(openDBJob);
770 }
771 }
772 int form = -1;
773 if (isPlural(pos.entry))
774 form = pos.form;
775 updateDB(url(),
776 context(pos.entry).first(),
777 sourceWithTags(pos),
778 targetWithTags(pos),
779 form,
780 isApproved(pos.entry),
781 dbName);
782
783 d._lastModifiedPos = DocPosition();
784 }
785
setLastModifiedPos(const DocPosition & pos)786 void Catalog::setLastModifiedPos(const DocPosition& pos)
787 {
788 if (pos.entry >= numberOfEntries()) //bin-units
789 return;
790
791 bool entryChanged = DocPos(d._lastModifiedPos) != DocPos(pos);
792 if (entryChanged)
793 flushUpdateDBBuffer();
794
795 d._lastModifiedPos = pos;
796 }
797
addToEmptyIndexIfAppropriate(CatalogStorage * storage,const DocPosition & pos,bool alreadyEmpty)798 bool CatalogPrivate::addToEmptyIndexIfAppropriate(CatalogStorage* storage, const DocPosition& pos, bool alreadyEmpty)
799 {
800 if ((!pos.offset) && (storage->target(pos).isEmpty()) && (!alreadyEmpty)) {
801 insertInList(_emptyIndex, pos.entry);
802 return true;
803 }
804 return false;
805 }
806
targetDelete(const DocPosition & pos,int count)807 void Catalog::targetDelete(const DocPosition& pos, int count)
808 {
809 if (Q_UNLIKELY(!m_storage))
810 return;
811
812 bool alreadyEmpty = m_storage->isEmpty(pos);
813 m_storage->targetDelete(pos, count);
814
815 if (d.addToEmptyIndexIfAppropriate(m_storage, pos, alreadyEmpty))
816 Q_EMIT signalNumberOfEmptyChanged();
817 Q_EMIT signalEntryModified(pos);
818 }
819
removeFromUntransIndexIfAppropriate(CatalogStorage * storage,const DocPosition & pos)820 bool CatalogPrivate::removeFromUntransIndexIfAppropriate(CatalogStorage* storage, const DocPosition& pos)
821 {
822 if ((!pos.offset) && (storage->isEmpty(pos))) {
823 _emptyIndex.removeAll(pos.entry);
824 return true;
825 }
826 return false;
827 }
828
targetInsert(const DocPosition & pos,const QString & arg)829 void Catalog::targetInsert(const DocPosition& pos, const QString& arg)
830 {
831 if (Q_UNLIKELY(!m_storage))
832 return;
833
834 if (d.removeFromUntransIndexIfAppropriate(m_storage, pos))
835 Q_EMIT signalNumberOfEmptyChanged();
836
837 m_storage->targetInsert(pos, arg);
838 Q_EMIT signalEntryModified(pos);
839 }
840
targetInsertTag(const DocPosition & pos,const InlineTag & tag)841 void Catalog::targetInsertTag(const DocPosition& pos, const InlineTag& tag)
842 {
843 if (Q_UNLIKELY(!m_storage))
844 return;
845
846 if (d.removeFromUntransIndexIfAppropriate(m_storage, pos))
847 Q_EMIT signalNumberOfEmptyChanged();
848
849 m_storage->targetInsertTag(pos, tag);
850 Q_EMIT signalEntryModified(pos);
851 }
852
targetDeleteTag(const DocPosition & pos)853 InlineTag Catalog::targetDeleteTag(const DocPosition& pos)
854 {
855 if (Q_UNLIKELY(!m_storage))
856 return InlineTag();
857
858 bool alreadyEmpty = m_storage->isEmpty(pos);
859 InlineTag tag = m_storage->targetDeleteTag(pos);
860
861 if (d.addToEmptyIndexIfAppropriate(m_storage, pos, alreadyEmpty))
862 Q_EMIT signalNumberOfEmptyChanged();
863 Q_EMIT signalEntryModified(pos);
864 return tag;
865 }
866
setTarget(DocPosition pos,const CatalogString & s)867 void Catalog::setTarget(DocPosition pos, const CatalogString& s)
868 {
869 //TODO for case of markup present
870 m_storage->setTarget(pos, s.string);
871 }
872
setState(const DocPosition & pos,TargetState state)873 TargetState Catalog::setState(const DocPosition& pos, TargetState state)
874 {
875 bool extendedStates = m_storage && m_storage->capabilities()&ExtendedStates;
876 bool approved =::isApproved(state, activePhaseRole());
877 if (Q_UNLIKELY(!m_storage
878 || (extendedStates && m_storage->state(pos) == state)
879 || (!extendedStates && m_storage->isApproved(pos) == approved)))
880 return this->state(pos);
881
882 TargetState prevState;
883 if (extendedStates) {
884 prevState = m_storage->setState(pos, state);
885 d._statesIndex[prevState].removeAll(pos.entry);
886 insertInList(d._statesIndex[state], pos.entry);
887 } else {
888 prevState = closestState(!approved, activePhaseRole());
889 m_storage->setApproved(pos, approved);
890 }
891
892 if (!approved) {
893 insertInList(d._nonApprovedIndex, pos.entry);
894 if (!m_storage->isEmpty(pos))
895 insertInList(d._nonApprovedNonEmptyIndex, pos.entry);
896 } else {
897 d._nonApprovedIndex.removeAll(pos.entry);
898 d._nonApprovedNonEmptyIndex.removeAll(pos.entry);
899 }
900
901 Q_EMIT signalNumberOfFuzziesChanged();
902 Q_EMIT signalEntryModified(pos);
903
904 return prevState;
905 }
906
updatePhase(const Phase & phase)907 Phase Catalog::updatePhase(const Phase& phase)
908 {
909 return m_storage->updatePhase(phase);
910 }
911
setEquivTrans(const DocPosition & pos,bool equivTrans)912 void Catalog::setEquivTrans(const DocPosition& pos, bool equivTrans)
913 {
914 if (m_storage) m_storage->setEquivTrans(pos, equivTrans);
915 }
916
setModified(DocPos entry,bool modified)917 bool Catalog::setModified(DocPos entry, bool modified)
918 {
919 if (modified) {
920 if (d._modifiedEntries.contains(entry))
921 return false;
922 d._modifiedEntries.insert(entry);
923 } else
924 d._modifiedEntries.remove(entry);
925 return true;
926 }
927
isModified(DocPos entry) const928 bool Catalog::isModified(DocPos entry) const
929 {
930 return d._modifiedEntries.contains(entry);
931 }
932
isModified(int entry) const933 bool Catalog::isModified(int entry) const
934 {
935 if (!isPlural(entry))
936 return isModified(DocPos(entry, 0));
937
938 int f = numberOfPluralForms();
939 while (--f >= 0)
940 if (isModified(DocPos(entry, f)))
941 return true;
942 return false;
943 }
944
945 //END UNDO/REDO
946
947
948
949
findNextInList(const QLinkedList<int> & list,int index)950 int findNextInList(const QLinkedList<int>& list, int index)
951 {
952 int nextIndex = -1;
953 for (int key : list) {
954 if (Q_UNLIKELY(key > index)) {
955 nextIndex = key;
956 break;
957 }
958 }
959 return nextIndex;
960 }
961
findPrevInList(const QLinkedList<int> & list,int index)962 int findPrevInList(const QLinkedList<int>& list, int index)
963 {
964 int prevIndex = -1;
965 for (int key : list) {
966 if (Q_UNLIKELY(key >= index))
967 break;
968 prevIndex = key;
969 }
970 return prevIndex;
971 }
972
insertInList(QLinkedList<int> & list,int index)973 void insertInList(QLinkedList<int>& list, int index)
974 {
975 QLinkedList<int>::Iterator it = list.begin();
976 while (it != list.end() && index > *it)
977 ++it;
978 list.insert(it, index);
979 }
980
setBookmark(uint idx,bool set)981 void Catalog::setBookmark(uint idx, bool set)
982 {
983 if (set)
984 insertInList(d._bookmarkIndex, idx);
985 else
986 d._bookmarkIndex.removeAll(idx);
987 }
988
989
isApproved(TargetState state,ProjectLocal::PersonRole role)990 bool isApproved(TargetState state, ProjectLocal::PersonRole role)
991 {
992 static const TargetState marginStates[] = {Translated, Final, SignedOff};
993 return state >= marginStates[role];
994 }
995
isApproved(TargetState state)996 bool isApproved(TargetState state)
997 {
998 static const TargetState marginStates[] = {Translated, Final, SignedOff};
999 return state == marginStates[0] || state == marginStates[1] || state == marginStates[2];
1000 }
1001
closestState(bool approved,ProjectLocal::PersonRole role)1002 TargetState closestState(bool approved, ProjectLocal::PersonRole role)
1003 {
1004 Q_ASSERT(role != ProjectLocal::Undefined);
1005 static const TargetState approvementStates[][3] = {
1006 {NeedsTranslation, NeedsReviewTranslation, NeedsReviewTranslation},
1007 {Translated, Final, SignedOff}
1008 };
1009 return approvementStates[approved][role];
1010 }
1011
1012
isObsolete(int entry) const1013 bool Catalog::isObsolete(int entry) const
1014 {
1015 if (Q_UNLIKELY(!m_storage))
1016 return false;
1017
1018 return m_storage->isObsolete(entry);
1019 }
1020
originalOdfFilePath()1021 QString Catalog::originalOdfFilePath()
1022 {
1023 if (Q_UNLIKELY(!m_storage))
1024 return QString();
1025
1026 return m_storage->originalOdfFilePath();
1027 }
1028
setOriginalOdfFilePath(const QString & odfFilePath)1029 void Catalog::setOriginalOdfFilePath(const QString& odfFilePath)
1030 {
1031 if (Q_UNLIKELY(!m_storage))
1032 return;
1033
1034 m_storage->setOriginalOdfFilePath(odfFilePath);
1035 }
1036
1037
1038