diff --git a/src/nokde-stubs/prefs.cpp b/src/nokde-stubs/prefs.cpp index a093cec..4184ae7 100644 --- a/src/nokde-stubs/prefs.cpp +++ b/src/nokde-stubs/prefs.cpp @@ -1,362 +1,363 @@ #include "prefs.h" #include "prefs_lokalize.h" #include "projectbase.h" #include "projectlocal.h" #include "project.h" #include "tmtab.h" #include "filesearchtab.h" #include "kaboutdata.h" #include #include #include #include #include #include #include SettingsController* SettingsController::_instance = 0; void SettingsController::cleanupSettingsController() { delete SettingsController::_instance; SettingsController::_instance = 0; } SettingsController* SettingsController::instance() { if (_instance == 0) { _instance = new SettingsController; ///qAddPostRoutine(SettingsController::cleanupSettingsController); } return _instance; } bool SettingsController::ensureProjectIsLoaded() { Project::instance()->populateGlossary(); return true; } QString fullUserName();// defined in helpers.cpp Settings::Settings() : mAddColor(0x99, 0xCC, 0xFF) , mDelColor(0xFF, 0x99, 0x99) , mMsgFont() , mHighlightSpaces(true) , mLeds(false) // Editor , mAutoApprove(true) , mAutoSpellcheck(true) , mMouseWheelGo(false) , mAltTransViewEverShownWithData(false) // TM , mPrefetchTM(false) , mAutoaddTM(true) , mScanToTMOnOpen(false) + , mDeleteFromTMOnMissing(false) , mWordCompletionLength(3) , mSuggCount(10) { QSettings s; mAuthorName = s.value(QStringLiteral("Author/Name"), QString()).toString(); if (mAuthorName.isEmpty()) { mAuthorName = fullUserName(); if (mAuthorName.length()) mAuthorName[0] = mAuthorName.at(0).toUpper(); } mAuthorEmail = s.value(QStringLiteral("Author/Email"), QString()).toString(); mDefaultLangCode = s.value(QStringLiteral("Editor/TargetLangCode"), QLocale::system().name()).toString(); mAltTransViewEverShownWithData = s.value(QStringLiteral("Editor/AltTransViewEverShownWithData"), false).toBool(); } void Settings::save() { QSettings s; s.setValue(QStringLiteral("Author/Name"), mAuthorName); s.setValue(QStringLiteral("Author/Email"), mAuthorEmail); s.setValue(QStringLiteral("Editor/TargetLangCode"), mDefaultLangCode); s.setValue(QStringLiteral("Editor/AltTransViewEverShownWithData"), mAltTransViewEverShownWithData); } Settings *Settings::self() { static Settings* s = new Settings; return s; } void writeUiState(const char* elementName, const QByteArray& state) { QSettings s; s.setValue(QStringLiteral("UI/") + QLatin1String(elementName), state.toBase64()); } QByteArray readUiState(const char* elementName) { QSettings s; return QByteArray::fromBase64(s.value(QStringLiteral("UI/") + QLatin1String(elementName), QByteArray()).toByteArray()); } #include "editortab.h" ProjectBase::ProjectBase() : m_tmTab(0) , mProjectID(QStringLiteral("default")) , mKind() , mTargetLangCode(Settings::defaultLangCode()) , mSourceLangCode("en_US") , mPoBaseDir(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)) , mPotBaseDir() , mBranchDir() , mAltDir() , mGlossaryTbx(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/terms.tbx") , mMainQA(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/main.lqa") // RegExps , mAccel("&") , mMarkup("(<[^>]+>)+|(&[A-Za-z_:][A-Za-z0-9_\\.:-]*;)+") , mWordWrap(80) { QSettings s; mSourceLangCode = s.value(QStringLiteral("Project/SourceLangCode"), mSourceLangCode).toString(); mTargetLangCode = s.value(QStringLiteral("Project/TargetLangCode"), mTargetLangCode).toString(); } void ProjectBase::save() { QSettings s; s.setValue(QStringLiteral("Project/SourceLangCode"), mSourceLangCode); s.setValue(QStringLiteral("Project/TargetLangCode"), mTargetLangCode); } ProjectLocal::ProjectLocal() : mRole(Translator) , mFirstRun(true) { QSettings s; mRole = s.value("Project/AuthorRole", mRole).toInt(); mSourceDir = s.value("Project/SourceDir", mSourceDir).toString(); } void ProjectLocal::save() { QSettings s; s.setValue(QStringLiteral("Project/AuthorRole"), mRole); s.setValue(QStringLiteral("Project/SourceDir"), mSourceDir); } EditorTab* ProjectBase::fileOpen(QString filePath, int entry, bool setAsActive, const QString& mergeFile, bool silent) { if (filePath.length()) { FileToEditor::const_iterator it = m_fileToEditor.constFind(filePath); if (it != m_fileToEditor.constEnd()) { qCWarning(LOKALIZE_LOG) << "already opened:" << filePath; if (EditorTab* e = it.value()) { e->activateWindow(); return e; } } } QByteArray state = m_lastEditorState; EditorTab* w = new EditorTab(0); QString suggestedDirPath; if (EditorTab* e = qobject_cast(QApplication::activeWindow())) { QString fp = e->currentFilePath(); if (fp.length()) suggestedDirPath = QFileInfo(fp).absolutePath(); } if (!w->fileOpen(filePath, suggestedDirPath, silent)) { w->deleteLater(); return 0; } if (filePath.length()) { FileToEditor::const_iterator it = m_fileToEditor.constFind(filePath); if (it != m_fileToEditor.constEnd()) { qCWarning(LOKALIZE_LOG) << "already opened:" << filePath; if (EditorTab* e = it.value()) { e->activateWindow(); w->deleteLater(); return e; } } } w->show(); if (!state.isEmpty()) w->restoreState(QByteArray::fromBase64(state)); if (entry/* || offset*/) w->gotoEntry(DocPosition(entry/*, DocPosition::Target, 0, offset*/)); if (!mergeFile.isEmpty()) w->mergeOpen(mergeFile); // m_openRecentFileAction->addUrl(QUrl::fromLocalFile(filePath));//(w->currentUrl()); connect(w, SIGNAL(destroyed(QObject*)), this, SLOT(editorClosed(QObject*))); connect(w, SIGNAL(fileOpenRequested(QString, QString, QString)), this, SLOT(fileOpen(QString, QString, QString))); connect(w, SIGNAL(tmLookupRequested(QString, QString)), this, SLOT(lookupInTranslationMemory(QString, QString))); filePath = w->currentFilePath(); QStringRef fnSlashed = filePath.midRef(filePath.lastIndexOf('/')); FileToEditor::const_iterator i = m_fileToEditor.constBegin(); while (i != m_fileToEditor.constEnd()) { if (i.key().endsWith(fnSlashed)) { i.value()->setFullPathShown(true); w->setFullPathShown(true); } ++i; } m_fileToEditor.insert(filePath, w); //emit editorAdded(); return w; } EditorTab* ProjectBase::fileOpen(const QString& filePath, const QString& source, const QString& ctxt) { EditorTab* w = fileOpen(filePath, 0, true); if (!w) return 0;//TODO message w->findEntryBySourceContext(source, ctxt); return w; } EditorTab* ProjectBase::fileOpen(const QString& filePath, DocPosition docPos, int selection) { EditorTab* w = fileOpen(filePath, 0, true); if (!w) return 0;//TODO message w->gotoEntry(docPos, selection); return w; } void ProjectBase::editorClosed(QObject* obj) { m_fileToEditor.remove(m_fileToEditor.key(static_cast(obj))); } bool ProjectBase::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::FileOpen) { QFileOpenEvent *e = static_cast(event); fileOpen(e->file()); return true; } return QObject::eventFilter(obj, event); } void ProjectBase::lookupInTranslationMemory(const QString& source, const QString& target) { TM::TMTab* w = showTM(); w->lookup(source, target); } TM::TMTab* ProjectBase::showTM() { if (!m_tmTab) { m_tmTab = new TM::TMTab(0); connect(m_tmTab, SIGNAL(fileOpenRequested(QString, QString, QString)), this, SLOT(fileOpen(QString, QString, QString))); } m_tmTab->show(); m_tmTab->activateWindow(); return m_tmTab; } void ProjectBase::showFileSearch() { if (!m_fileSearchTab) { m_fileSearchTab = new FileSearchTab(0); connect(m_fileSearchTab, SIGNAL(fileOpenRequested(QString, DocPosition, int)), this, SLOT(fileOpen(QString, DocPosition, int))); connect(m_fileSearchTab, SIGNAL(fileOpenRequested(QString)), this, SLOT(fileOpen(QString))); } if (EditorTab* e = qobject_cast(QApplication::activeWindow())) { QString fp = e->currentFilePath(); if (fp.length()) { m_fileSearchTab->addFilesToSearch(QStringList(fp)); m_fileSearchTab->setSourceQuery(e->selectionInSource()); m_fileSearchTab->setTargetQuery(e->selectionInTarget()); } } m_fileSearchTab->show(); m_fileSearchTab->activateWindow(); } void ProjectBase::fileSearchNext() { if (!m_fileSearchTab) showFileSearch(); else m_fileSearchTab->fileSearchNext(); } KAboutData* KAboutData::instance = 0; KAboutData::KAboutData(const QString&, const QString& n, const QString& v, const QString& d, KAboutLicense::L, const QString& c) : name(n) , version(v) , description(d) , copyright(c) { KAboutData::instance = this; } void KAboutData::addAuthor(const QString& name, const QString&, const QString& mail) { // Credit c; // c.name=name; // c.mail=mail; // credits.append(c); } void KAboutData::addCredit(const QString& name, const QString& forwhat, const QString& mail, const QString& site) { Credit c; c.name = name; c.mail = mail; c.what = forwhat; c.site = site; credits.append(c); } void KAboutData::doAbout() { QString cs; foreach (const Credit& c, credits) { cs += c.name % ": " % c.what % "
"; } QMessageBox::about(0, name, "

" % name % ' ' % version % "

" % description % "

" % copyright.replace('\n', "
") % "


Credits:
" % cs % "
"); } namespace KLocalizedString { void setApplicationDomain(const char*) {} }; diff --git a/src/nokde-stubs/prefs_lokalize.h b/src/nokde-stubs/prefs_lokalize.h index 5fa3199..eb356ab 100644 --- a/src/nokde-stubs/prefs_lokalize.h +++ b/src/nokde-stubs/prefs_lokalize.h @@ -1,197 +1,204 @@ // This file is generated by kconfig_compiler_kf5 from lokalize.kcfg. #ifndef SETTINGS_H #define SETTINGS_H #include #include #include #include class Settings: public QObject { Q_OBJECT public: static Settings *self(); ~Settings() {} void save(); public slots: static void setAuthorName(const QString& v) { self()->mAuthorName = v; } static void setAuthorEmail(const QString& v) { self()->mAuthorEmail = v; } static void setDefaultLangCode(const QString& v) { self()->mDefaultLangCode = v; } public: static QString authorName() { return self()->mAuthorName; } static QString authorLocalizedName() { return self()->mAuthorLocalizedName; } static QString authorEmail() { return self()->mAuthorEmail; } static QString defaultLangCode() { return self()->mDefaultLangCode; } static QString defaultMailingList() { return self()->mDefaultMailingList; } static QColor addColor() { return self()->mAddColor; } static QColor delColor() { return self()->mDelColor; } static bool highlightSpaces() { return self()->mHighlightSpaces; } static QFont msgFont() { return self()->mMsgFont; } static void setLeds(bool v) { self()->mLeds = v; } static bool leds() { return self()->mLeds; } static bool autoApprove() { return self()->mAutoApprove; } static void setAutoSpellcheck(bool v) { self()->mAutoSpellcheck = v; } static bool autoSpellcheck() { return self()->mAutoSpellcheck; } static bool mouseWheelGo() { return self()->mMouseWheelGo; } static bool altTransViewEverShownWithData() { return self()->mAltTransViewEverShownWithData; } static void setAltTransViewEverShownWithData(bool v) { self()->mAltTransViewEverShownWithData = v; } static int wordCompletionLength() { return self()->mWordCompletionLength; } static bool prefetchTM() { return self()->mPrefetchTM; } static int suggCount() { return self()->mSuggCount; } static bool autoaddTM() { return self()->mAutoaddTM; } static bool scanToTMOnOpen() { return self()->mScanToTMOnOpen; } protected: + static + bool deleteFromTMOnMissing() + { + return self()->mDeleteFromTMOnMissing; + } + Settings(); friend class SettingsHelper; // Identity QString mAuthorName; QString mAuthorLocalizedName; QString mAuthorEmail; QString mDefaultLangCode; QString mDefaultMailingList; // Appearance QColor mAddColor; QColor mDelColor; QFont mMsgFont; bool mHighlightSpaces; bool mLeds; // Editor bool mAutoApprove; bool mAutoSpellcheck; bool mMouseWheelGo; bool mAltTransViewEverShownWithData; // TM bool mPrefetchTM; bool mAutoaddTM; bool mScanToTMOnOpen; + bool mDeleteFromTMOnMissing; int mWordCompletionLength; int mSuggCount; }; #endif diff --git a/src/prefs/lokalize.kcfg b/src/prefs/lokalize.kcfg index 07f2294..844a868 100644 --- a/src/prefs/lokalize.kcfg +++ b/src/prefs/lokalize.kcfg @@ -1,118 +1,121 @@ QLocale QFontDatabase kemailsettings.h kde-i18n-lists.h KEMailSettings().getSetting(KEMailSettings::RealName) KEMailSettings().getSetting(KEMailSettings::RealName) KEMailSettings().getSetting(KEMailSettings::EmailAddress) QLocale::system().name() getMailingList() #99CCFF #FF9999 true QFontDatabase::systemFont(QFontDatabase::GeneralFont) false true true false 4 false false 7 true false + + false + diff --git a/src/prefs/prefs_tm.ui b/src/prefs/prefs_tm.ui index b2a5fec..aff609f 100644 --- a/src/prefs/prefs_tm.ui +++ b/src/prefs/prefs_tm.ui @@ -1,91 +1,98 @@ prefs_tm 0 0 612 375 11 6 Qt::Vertical 20 40 If checked, get translation memory suggestions If this is checked, the program will fetch translation memories as soon as you open a file. Prefetch translation memory suggestions on file open Maximum number of suggestions: Set the maximum number of suggestions You can change the maximum number of suggestions, default is 7. Qt::Horizontal Update/Add edited entries to translation memory Add opened files to translation memory automatically + + + + Delete missing files from translation memory on Rescan or when clicking a missing entry + + + diff --git a/src/project/project.cpp b/src/project/project.cpp index 5c4159a..a969144 100644 --- a/src/project/project.cpp +++ b/src/project/project.cpp @@ -1,506 +1,519 @@ /* **************************************************************************** This file is part of Lokalize Copyright (C) 2007-2014 by Nick Shaforostoff This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . **************************************************************************** */ #include "project.h" #include "lokalize_debug.h" #include "projectlocal.h" #include "prefs.h" #include "jobs.h" #include "glossary.h" #include "tmmanager.h" #include "glossarywindow.h" #include "editortab.h" #include "dbfilesmodel.h" #include "qamodel.h" #include #include #include #include #include #include #include #include #ifndef NOKDE #include "projectmodel.h" #include "webquerycontroller.h" #include #include #include #include #include #include #include #include using namespace Kross; #endif QString getMailingList() { QString lang = QLocale::system().name(); if (lang.startsWith(QLatin1String("ca"))) return QLatin1String("kde-i18n-ca@kde.org"); if (lang.startsWith(QLatin1String("de"))) return QLatin1String("kde-i18n-de@kde.org"); if (lang.startsWith(QLatin1String("hu"))) return QLatin1String("kde-l10n-hu@kde.org"); if (lang.startsWith(QLatin1String("tr"))) return QLatin1String("kde-l10n-tr@kde.org"); if (lang.startsWith(QLatin1String("it"))) return QLatin1String("kde-i18n-it@kde.org"); if (lang.startsWith(QLatin1String("lt"))) return QLatin1String("kde-i18n-lt@kde.org"); if (lang.startsWith(QLatin1String("nb"))) return QLatin1String("i18n-nb@lister.ping.uio.no"); if (lang.startsWith(QLatin1String("nl"))) return QLatin1String("kde-i18n-nl@kde.org"); if (lang.startsWith(QLatin1String("nn"))) return QLatin1String("i18n-nn@lister.ping.uio.no"); if (lang.startsWith(QLatin1String("pt_BR"))) return QLatin1String("kde-i18n-pt_BR@kde.org"); if (lang.startsWith(QLatin1String("ru"))) return QLatin1String("kde-russian@lists.kde.ru"); if (lang.startsWith(QLatin1String("se"))) return QLatin1String("i18n-sme@lister.ping.uio.no"); if (lang.startsWith(QLatin1String("sl"))) return QLatin1String("lugos-slo@lugos.si"); return QLatin1String("kde-i18n-doc@kde.org"); } Project* Project::_instance = 0; void Project::cleanupProject() { delete Project::_instance; Project::_instance = 0; } Project* Project::instance() { if (_instance == 0) { _instance = new Project(); qAddPostRoutine(Project::cleanupProject); } return _instance; } Project::Project() : ProjectBase() , m_localConfig(new ProjectLocal()) , m_model(0) , m_glossary(new GlossaryNS::Glossary(this)) , m_glossaryWindow(0) , m_tmManagerWindow(0) { setDefaults(); /* qRegisterMetaType("DocPosition"); qDBusRegisterMetaType(); */ //QTimer::singleShot(66,this,SLOT(initLater())); } /* void Project::initLater() { if (isLoaded()) return; KConfig cfg; KConfigGroup gr(&cfg,"State"); QString file=gr.readEntry("Project"); if (!file.isEmpty()) load(file); } */ Project::~Project() { delete m_localConfig; //Project::save() } void Project::load(const QString &newProjectPath, const QString& forcedTargetLangCode, const QString& forcedProjectId) { QTime a; a.start(); TM::threadPool()->clear(); qCDebug(LOKALIZE_LOG) << "loading" << newProjectPath << "finishing tm jobs..."; if (!m_path.isEmpty()) { TM::CloseDBJob* closeDBJob = new TM::CloseDBJob(projectID()); closeDBJob->setAutoDelete(true); TM::threadPool()->start(closeDBJob, CLOSEDB); } TM::threadPool()->waitForDone(500);//more safety #ifndef NOKDE setSharedConfig(KSharedConfig::openConfig(newProjectPath, KConfig::NoGlobals)); if (!QFileInfo::exists(newProjectPath)) Project::instance()->setDefaults(); ProjectBase::load(); #else #endif m_path = newProjectPath; m_desirablePath.clear(); //cache: m_projectDir = QFileInfo(m_path).absolutePath(); #ifndef NOKDE m_localConfig->setSharedConfig(KSharedConfig::openConfig(projectID() + QStringLiteral(".local"), KConfig::NoGlobals, QStandardPaths::DataLocation)); m_localConfig->load(); #endif if (forcedTargetLangCode.length()) setLangCode(forcedTargetLangCode); else if (langCode().isEmpty()) setLangCode(QLocale::system().name()); if (forcedProjectId.length()) setProjectID(forcedProjectId); //KConfig config; //delete m_localConfig; m_localConfig=new KConfigGroup(&config,"Project-"+path()); populateDirModel(); //put 'em into thread? //QTimer::singleShot(0,this,SLOT(populateGlossary())); populateGlossary();//we cant postpone it because project load can be called from define new term function m_sourceFilePaths.clear(); if (newProjectPath.isEmpty()) return; if (!isTmSupported()) qCWarning(LOKALIZE_LOG) << "no sqlite module available"; //NOTE do we need to explicitly call it when project id changes? TM::DBFilesModel::instance()->openDB(projectID(), TM::Undefined, true); if (QaModel::isInstantiated()) { QaModel::instance()->saveRules(); QaModel::instance()->loadRules(qaPath()); } //qCDebug(LOKALIZE_LOG)<<"until emitting signal"<setAutoDelete(true); TM::threadPool()->start(closeDBJob, CLOSEDB); populateDirModel(); populateGlossary(); TM::threadPool()->waitForDone(500);//more safety TM::DBFilesModel::instance()->openDB(projectID(), TM::Undefined, true); } QString Project::absolutePath(const QString& possiblyRelPath) const { if (QFileInfo(possiblyRelPath).isRelative()) return QDir::cleanPath(m_projectDir % QLatin1Char('/') % possiblyRelPath); return possiblyRelPath; } void Project::populateDirModel() { #ifndef NOKDE if (Q_UNLIKELY(m_path.isEmpty() || !QFileInfo::exists(poDir()))) return; QUrl potUrl; if (QFileInfo::exists(potDir())) potUrl = QUrl::fromLocalFile(potDir()); model()->setUrl(QUrl::fromLocalFile(poDir()), potUrl); #endif } void Project::populateGlossary() { m_glossary->load(glossaryPath()); } GlossaryNS::GlossaryWindow* Project::showGlossary() { return defineNewTerm(); } GlossaryNS::GlossaryWindow* Project::defineNewTerm(QString en, QString target) { if (!SettingsController::instance()->ensureProjectIsLoaded()) return 0; if (!m_glossaryWindow) m_glossaryWindow = new GlossaryNS::GlossaryWindow(SettingsController::instance()->mainWindowPtr()); m_glossaryWindow->show(); m_glossaryWindow->activateWindow(); if (!en.isEmpty() || !target.isEmpty()) m_glossaryWindow->newTermEntry(en, target); return m_glossaryWindow; } bool Project::queryCloseForAuxiliaryWindows() { if (m_glossaryWindow && m_glossaryWindow->isVisible()) return m_glossaryWindow->queryClose(); return true; } bool Project::isTmSupported() const { QStringList drivers = QSqlDatabase::drivers(); return drivers.contains(QLatin1String("QSQLITE")); } void Project::showTMManager() { if (!m_tmManagerWindow) { if (!isTmSupported()) { KMessageBox::information(0, i18n("TM facility requires SQLite Qt module."), i18n("No SQLite module available")); return; } m_tmManagerWindow = new TM::TMManagerWin(SettingsController::instance()->mainWindowPtr()); } m_tmManagerWindow->show(); m_tmManagerWindow->activateWindow(); } +bool Project::isFileMissing(const QString& filePath) const +{ + if (!QFile::exists(filePath) && isLoaded()) { + //check if we are opening template + QString newPath = filePath; + newPath.replace(poDir(), potDir()); + if (!QFile::exists(newPath) && !QFile::exists(newPath += 't')) { + return true; + } + } + return false; +} + void Project::save() { m_localConfig->setFirstRun(false); ProjectBase::setTargetLangCode(langCode()); ProjectBase::save(); m_localConfig->save(); } ProjectModel* Project::model() { #ifndef NOKDE if (Q_UNLIKELY(!m_model)) m_model = new ProjectModel(this); return m_model; #else return 0; #endif } void Project::setDefaults() { ProjectBase::setDefaults(); setLangCode(QLocale::system().name()); } void Project::init(const QString& path, const QString& kind, const QString& id, const QString& sourceLang, const QString& targetLang) { setDefaults(); bool stop = false; while (true) { setKind(kind); setSourceLangCode(sourceLang); setLangCode(targetLang); setProjectID(id); if (stop) break; else { load(path); stop = true; } } save(); } static void fillFilePathsRecursive(const QDir& dir, QMultiMap& sourceFilePaths) { QStringList subDirs(dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable)); int i = subDirs.size(); while (--i >= 0) fillFilePathsRecursive(QDir(dir.filePath(subDirs.at(i))), sourceFilePaths); static QStringList filters = QStringList(QStringLiteral("*.cpp")) << QStringLiteral("*.c") << QStringLiteral("*.cc") << QStringLiteral("*.mm") << QStringLiteral("*.ui") << QStringLiteral("*rc"); QStringList files(dir.entryList(filters, QDir::Files | QDir::NoDotAndDotDot | QDir::Readable)); i = files.size(); QByteArray absDirPath = dir.absolutePath().toUtf8(); absDirPath.squeeze(); while (--i >= 0) { //qCDebug(LOKALIZE_LOG)<sourceFilePathsAreReady(); } protected: bool doKill(); private: QString m_folderName; }; SourceFilesSearchJob::SourceFilesSearchJob(const QString& folderName, QObject* parent) : KJob(parent) , m_folderName(folderName) { setCapabilities(KJob::Killable); } bool SourceFilesSearchJob::doKill() { //TODO return true; } class FillSourceFilePathsJob: public QRunnable { public: explicit FillSourceFilePathsJob(const QDir& dir, SourceFilesSearchJob* j): startingDir(dir), kj(j) {} protected: void run() { QMultiMap sourceFilePaths; fillFilePathsRecursive(startingDir, sourceFilePaths); Project::instance()->m_sourceFilePaths = sourceFilePaths; QTimer::singleShot(0, kj, &SourceFilesSearchJob::finish); } public: QDir startingDir; SourceFilesSearchJob* kj; }; void SourceFilesSearchJob::start() { QThreadPool::globalInstance()->start(new FillSourceFilePathsJob(QDir(m_folderName), this)); emit description(this, i18n("Scanning folders with source files"), qMakePair(i18n("Editor"), m_folderName)); } #endif const QMultiMap& Project::sourceFilePaths() { if (m_sourceFilePaths.isEmpty()) { QDir dir(local()->sourceDir()); if (dir.exists()) { #ifndef NOKDE SourceFilesSearchJob* metaJob = new SourceFilesSearchJob(local()->sourceDir()); KIO::getJobTracker()->registerJob(metaJob); metaJob->start(); //KNotification* notification=new KNotification("SourceFileScan", 0); //notification->setText( i18nc("@info","Please wait while %1 is being scanned for source files.", local()->sourceDir()) ); //notification->sendEvent(); #else QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); fillFilePathsRecursive(dir, m_sourceFilePaths); QApplication::restoreOverrideCursor(); #endif } } return m_sourceFilePaths; } #include #include #include "languagelistmodel.h" void Project::projectOdfCreate() { QString odf2xliff = QStringLiteral("odf2xliff"); if (QProcess::execute(odf2xliff, QStringList(QLatin1String("--version"))) == -2) { KMessageBox::error(SettingsController::instance()->mainWindowPtr(), i18n("Install translate-toolkit package and retry")); return; } QString odfPath = QFileDialog::getOpenFileName(SettingsController::instance()->mainWindowPtr(), QString(), QDir::homePath()/*_catalog->url().directory()*/, i18n("OpenDocument files (*.odt *.ods)")/*"text/x-lokalize-project"*/); if (odfPath.isEmpty()) return; QString targetLangCode = getTargetLangCode(QString(), true); QFileInfo fi(odfPath); QString trFolderName = i18nc("project folder name. %2 is targetLangCode", "%1 %2 Translation", fi.baseName(), targetLangCode); fi.absoluteDir().mkdir(trFolderName); QStringList args(odfPath); args.append(fi.absoluteDir().absoluteFilePath(trFolderName) % '/' % fi.baseName() % QLatin1String(".xlf")); qCDebug(LOKALIZE_LOG) << args; QProcess::execute(odf2xliff, args); if (!QFile::exists(args.at(1))) return; emit closed(); Project::instance()->load(fi.absoluteDir().absoluteFilePath(trFolderName) + QLatin1String("/index.lokalize"), targetLangCode, fi.baseName() % '-' % targetLangCode); emit fileOpenRequested(args.at(1)); } diff --git a/src/project/project.h b/src/project/project.h index 783fd65..6e37f25 100644 --- a/src/project/project.h +++ b/src/project/project.h @@ -1,215 +1,216 @@ /* **************************************************************************** This file is part of Lokalize Copyright (C) 2007-2009 by Nick Shaforostoff This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . **************************************************************************** */ #ifndef PROJECT_H #define PROJECT_H #include #include #include "projectbase.h" #define WEBQUERY_ENABLE class ProjectModel; class ProjectLocal; namespace GlossaryNS { class Glossary; } namespace GlossaryNS { class GlossaryWindow; } namespace TM { class TMManagerWin; } /** * Singleton object that represents project. * It is shared between EditorWindow 'mainwindows' that use the same project file. * Keeps project's KDirModel, Glossary and kross::actions * * GUI for config handling is implemented in prefs.cpp * * @short Singleton object that represents project */ ///////// * Also provides list of web-query scripts class Project: public ProjectBase { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.Lokalize.Project") //qdbuscpp2xml -m -s project.h -o org.kde.lokalize.Project.xml public: explicit Project(); virtual ~Project(); bool isLoaded()const { return !m_path.isEmpty(); } ProjectModel* model(); //void setPath(const QString& p){m_path=p;} QString path()const { return m_path; } QString projectDir()const { return m_projectDir; } QString poDir()const { return absolutePath(poBaseDir()); } QString potDir()const { return absolutePath(potBaseDir()); } QString branchDir()const { return absolutePath(ProjectBase::branchDir()); } QString glossaryPath()const { return absolutePath(glossaryTbx()); } QString qaPath()const { return absolutePath(mainQA()); } GlossaryNS::Glossary* glossary()const { return m_glossary; } QString altTransDir()const { return absolutePath(altDir()); } bool queryCloseForAuxiliaryWindows(); + bool isFileMissing(const QString& filePath) const; void setDefaults(); // private slots: // void initLater(); public slots: Q_SCRIPTABLE void load(const QString& newProjectPath, const QString& defaultTargetLangCode = QString(), const QString& defaultProjectId = QString()); Q_SCRIPTABLE void reinit(); Q_SCRIPTABLE void save(); Q_SCRIPTABLE QString translationsRoot()const { return poDir(); } Q_SCRIPTABLE QString templatesRoot()const { return potDir(); } Q_SCRIPTABLE QString targetLangCode()const { return ProjectBase::langCode(); } Q_SCRIPTABLE QString sourceLangCode()const { return ProjectBase::sourceLangCode(); } Q_SCRIPTABLE void init(const QString& path, const QString& kind, const QString& id, const QString& sourceLang, const QString& targetLang); Q_SCRIPTABLE QString kind()const { return ProjectBase::kind(); } Q_SCRIPTABLE QString absolutePath(const QString&) const; Q_SCRIPTABLE void setDesirablePath(const QString& path) { m_desirablePath = path; } Q_SCRIPTABLE QString desirablePath() const { return m_desirablePath; } Q_SCRIPTABLE bool isTmSupported() const; signals: Q_SCRIPTABLE void loaded(); void fileOpenRequested(const QString&); void closed(); public slots: void populateDirModel(); void populateGlossary(); void showTMManager(); GlossaryNS::GlossaryWindow* showGlossary(); GlossaryNS::GlossaryWindow* defineNewTerm(QString en = QString(), QString target = QString()); void projectOdfCreate(); private: static Project* _instance; static void cleanupProject(); public: static Project* instance(); static ProjectLocal* local() { return instance()->m_localConfig; } const QMultiMap& sourceFilePaths(); void resetSourceFilePaths() { m_sourceFilePaths.clear(); } friend class FillSourceFilePathsJob; signals: void sourceFilePathsAreReady(); private: QString m_path; QString m_desirablePath; ProjectLocal* m_localConfig; ProjectModel* m_model; GlossaryNS::Glossary* m_glossary; GlossaryNS::GlossaryWindow* m_glossaryWindow; TM::TMManagerWin* m_tmManagerWindow; QMultiMap m_sourceFilePaths; //cache QString m_projectDir; }; #endif diff --git a/src/tm/jobs.cpp b/src/tm/jobs.cpp index 5249cc4..0351b38 100644 --- a/src/tm/jobs.cpp +++ b/src/tm/jobs.cpp @@ -1,2028 +1,2125 @@ /* **************************************************************************** This file is part of Lokalize Copyright (C) 2007-2014 by Nick Shaforostoff This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . **************************************************************************** */ #include "jobs.h" #include "lokalize_debug.h" #include "catalog.h" #include "project.h" #include "diff.h" #include "prefs_lokalize.h" #include "version.h" #include "stemming.h" #include #include #include #include #include #include #include #include #include #include #include using namespace TM; QThreadPool* TM::threadPool() { static QThreadPool* inst = new QThreadPool; return inst; } #ifdef Q_OS_WIN #define U QLatin1String #else #define U QStringLiteral #endif #define TM_DELIMITER '\v' #define TM_SEPARATOR '\b' #define TM_NOTAPPROVED 0x04 static bool stop = false; void TM::cancelAllJobs() { stop = true; } static qlonglong newTMSourceEntryCount = 0; static qlonglong reusedTMSourceEntryCount = 0; /** * splits string into words, removing any markup * * TODO segmentation by sentences... **/ static void doSplit(QString& cleanEn, QStringList& words, QRegExp& rxClean1, const QString& accel ) { static QRegExp rxSplit(QStringLiteral("\\W+|\\d+")); if (!rxClean1.pattern().isEmpty()) cleanEn.replace(rxClean1, QStringLiteral(" ")); cleanEn.remove(accel); words = cleanEn.toLower().split(rxSplit, QString::SkipEmptyParts); if (words.size() > 4) { int i = 0; for (; i < words.size(); ++i) { if (words.at(i).size() < 4) words.removeAt(i--); else if (words.at(i).startsWith('t') && words.at(i).size() == 4) { if (words.at(i) == QLatin1String("then") || words.at(i) == QLatin1String("than") || words.at(i) == QLatin1String("that") || words.at(i) == QLatin1String("this") ) words.removeAt(i--); } } } } - - static qlonglong getFileId(const QString& path, QSqlDatabase& db) { QSqlQuery query1(db); QString escapedPath = path; escapedPath.replace(QLatin1Char('\''), QLatin1String("''")); QString pathExpr = QStringLiteral("path='") % escapedPath % '\''; if (path.isEmpty()) pathExpr = QStringLiteral("path ISNULL"); if (Q_UNLIKELY(!query1.exec(U("SELECT id FROM files WHERE " "path='") % escapedPath % '\''))) qCWarning(LOKALIZE_LOG) << "select db error: " << query1.lastError().text(); if (Q_LIKELY(query1.next())) { //this is translation of en string that is already present in db qlonglong id = query1.value(0).toLongLong(); query1.clear(); return id; } query1.clear(); //nope, this is new file bool qpsql = (db.driverName() == QLatin1String("QPSQL")); QString sql = QStringLiteral("INSERT INTO files (path) VALUES (?)"); if (qpsql) sql += QLatin1String(" RETURNING id"); query1.prepare(sql); query1.bindValue(0, path); if (Q_LIKELY(query1.exec())) return qpsql ? (query1.next(), query1.value(0).toLongLong()) : query1.lastInsertId().toLongLong(); else qCWarning(LOKALIZE_LOG) << "insert db error: " << query1.lastError().text(); return -1; } static void addToIndex(qlonglong sourceId, QString sourceString, QRegExp& rxClean1, const QString& accel, QSqlDatabase& db) { QStringList words; doSplit(sourceString, words, rxClean1, accel); if (Q_UNLIKELY(words.isEmpty())) return; QSqlQuery query1(db); QByteArray sourceIdStr = QByteArray::number(sourceId, 36); bool isShort = words.size() < 20; int j = words.size(); while (--j >= 0) { // insert word (if we do not have it) if (Q_UNLIKELY(!query1.exec(U("SELECT word, ids_short, ids_long FROM words WHERE " "word='") % words.at(j) % '\''))) qCWarning(LOKALIZE_LOG) << "select error 3: " << query1.lastError().text(); //we _have_ it bool weHaveIt = query1.next(); if (weHaveIt) { //just add new id QByteArray arr; QString field; if (isShort) { arr = query1.value(1).toByteArray(); field = QStringLiteral("ids_short"); } else { arr = query1.value(2).toByteArray(); field = QStringLiteral("ids_long"); } query1.clear(); if (arr.contains(' ' % sourceIdStr % ' ') || arr.startsWith(sourceIdStr + ' ') || arr.endsWith(' ' + sourceIdStr) || arr == sourceIdStr) return;//this string is already indexed query1.prepare(QStringLiteral("UPDATE words SET ") % field % QStringLiteral("=? WHERE word='") % words.at(j) % '\''); if (!arr.isEmpty()) arr += ' '; arr += sourceIdStr; query1.bindValue(0, arr); if (Q_UNLIKELY(!query1.exec())) qCWarning(LOKALIZE_LOG) << "update error 4: " << query1.lastError().text(); } else { query1.clear(); query1.prepare(QStringLiteral("INSERT INTO words (word, ids_short, ids_long) VALUES (?, ?, ?)")); QByteArray idsShort; QByteArray idsLong; if (isShort) idsShort = sourceIdStr; else idsLong = sourceIdStr; query1.bindValue(0, words.at(j)); query1.bindValue(1, idsShort); query1.bindValue(2, idsLong); if (Q_UNLIKELY(!query1.exec())) qCWarning(LOKALIZE_LOG) << "insert error 2: " << query1.lastError().text() ; } } } /** * remove source string from index if there are no other * 'good' entries using it but the entry specified with mainId */ static void removeFromIndex(qlonglong mainId, qlonglong sourceId, QString sourceString, QRegExp& rxClean1, const QString& accel, QSqlDatabase& db) { QStringList words; doSplit(sourceString, words, rxClean1, accel); if (Q_UNLIKELY(words.isEmpty())) return; QSqlQuery query1(db); QByteArray sourceIdStr = QByteArray::number(sourceId, 36); //BEGIN check //TM_NOTAPPROVED=4 if (Q_UNLIKELY(!query1.exec(U("SELECT count(*) FROM main, target_strings WHERE " "main.source=") % QString::number(sourceId) % U(" AND " "main.target=target_strings.id AND " "target_strings.target NOTNULL AND " "main.id!=") % QString::number(mainId) % U(" AND " "(main.bits&4)!=4")))) { qCWarning(LOKALIZE_LOG) << "select error 500: " << query1.lastError().text(); return; } bool exit = query1.next() && (query1.value(0).toLongLong() > 0); query1.clear(); if (exit) return; //END check bool isShort = words.size() < 20; int j = words.size(); while (--j >= 0) { // remove from record for the word (if we do not have it) if (Q_UNLIKELY(!query1.exec(U("SELECT word, ids_short, ids_long FROM words WHERE " "word='") % words.at(j) % '\''))) { qCWarning(LOKALIZE_LOG) << "select error 3: " << query1.lastError().text(); return; } if (!query1.next()) { qCWarning(LOKALIZE_LOG) << "exit here 1"; //we don't have record for the word, so nothing to remove query1.clear(); return; } QByteArray arr; QString field; if (isShort) { arr = query1.value(1).toByteArray(); field = QStringLiteral("ids_short"); } else { arr = query1.value(2).toByteArray(); field = QStringLiteral("ids_long"); } query1.clear(); if (arr.contains(' ' + sourceIdStr + ' ')) arr.replace(' ' + sourceIdStr + ' ', " "); else if (arr.startsWith(sourceIdStr + ' ')) arr.remove(0, sourceIdStr.size() + 1); else if (arr.endsWith(' ' + sourceIdStr)) arr.chop(sourceIdStr.size() + 1); else if (arr == sourceIdStr) arr.clear(); query1.prepare(U("UPDATE words " "SET ") % field % U("=? " "WHERE word='") % words.at(j) % '\''); query1.bindValue(0, arr); if (Q_UNLIKELY(!query1.exec())) qCWarning(LOKALIZE_LOG) << "update error 504: " << query1.lastError().text(); } } static bool doRemoveEntry(qlonglong mainId, QRegExp& rxClean1, const QString& accel, QSqlDatabase& db) { QSqlQuery query1(db); if (Q_UNLIKELY(!query1.exec(U("SELECT source_strings.id, source_strings.source FROM source_strings, main WHERE " "source_strings.id=main.source AND main.id=") + QString::number(mainId)))) return false; if (!query1.next()) return false; qlonglong sourceId = query1.value(0).toLongLong(); QString source_string = query1.value(1).toString(); query1.clear(); if (!query1.exec(QStringLiteral("SELECT count(*) FROM main WHERE source=") + QString::number(sourceId)) || !query1.next()) return false; bool theOnly = query1.value(0).toInt() == 1; query1.clear(); if (theOnly) { removeFromIndex(mainId, sourceId, source_string, rxClean1, accel, db); qCWarning(LOKALIZE_LOG) << "ok delete?" << query1.exec(QStringLiteral("DELETE FROM source_strings WHERE id=") + QString::number(sourceId)); } if (Q_UNLIKELY(!query1.exec(U("SELECT target FROM main WHERE " "main.id=") + QString::number(mainId)) || !query1.next())) return false; qlonglong targetId = query1.value(0).toLongLong(); query1.clear(); if (!query1.exec(QStringLiteral("SELECT count(*) FROM main WHERE target=") + QString::number(targetId)) || ! query1.next()) return false; theOnly = query1.value(0).toInt() == 1; query1.clear(); if (theOnly) query1.exec(QStringLiteral("DELETE FROM target_strings WHERE id=") + QString::number(targetId)); return query1.exec(QStringLiteral("DELETE FROM main WHERE id=") + QString::number(mainId)); } + +static bool doRemoveFile(const QString& filePath, QSqlDatabase& db) +{ + qlonglong fileId = getFileId(filePath, db); + QSqlQuery query1(db); + + if (Q_UNLIKELY(!query1.exec(U("SELECT id FROM files WHERE " + "id=") + QString::number(fileId)))) + return false; + + if (!query1.next()) + return false; + + query1.clear(); + + query1.exec(QStringLiteral("DELETE source_strings FROM source_strings, main WHERE source_strings.id = main.source AND main.file =") + QString::number(fileId)); + query1.exec(QStringLiteral("DELETE target_strings FROM target_strings, main WHERE target_strings.id = main.target AND main.file =") + QString::number(fileId)); + query1.exec(QStringLiteral("DELETE FROM main WHERE file = ") + QString::number(fileId)); + return query1.exec(QStringLiteral("DELETE FROM files WHERE id=") + QString::number(fileId)); +} + +static int doRemoveMissingFiles(QSqlDatabase& db, const QString& dbName, QObject *job) +{ + int deletedFiles = 0; + QSqlQuery query1(db); + + if (Q_UNLIKELY(!query1.exec(U("SELECT files.path FROM files")))) + return false; + + if (!query1.next()) + return false; + + do { + QString filePath = query1.value(0).toString(); + if (Project::instance()->isFileMissing(filePath)) { + qCWarning(LOKALIZE_LOG) << "Removing file " << filePath << " from translation memory"; + RemoveFileJob* job_removefile = new RemoveFileJob(filePath, dbName, job); + TM::threadPool()->start(job_removefile, REMOVEFILE); + deletedFiles++; + } + } while (query1.next()); + + return deletedFiles; +} + static QString escape(QString str) { return str.replace(QLatin1Char('\''), QStringLiteral("''")); } static bool doInsertEntry(CatalogString source, CatalogString target, const QString& ctxt, //TODO QStringList -- after XLIFF bool approved, qlonglong fileId, QSqlDatabase& db, QRegExp& rxClean1,//cleaning regexps for word index update const QString& accel, qlonglong priorId, qlonglong& mainId ) { QTime a; a.start(); mainId = -1; if (Q_UNLIKELY(source.isEmpty())) { qCWarning(LOKALIZE_LOG) << "source empty"; return false; } bool qpsql = (db.driverName() == QLatin1String("QPSQL")); //we store non-entranslaed entries to make search over all source parts possible bool untranslated = target.isEmpty(); bool shouldBeInIndex = !untranslated && approved; //remove first occurrence of accel character so that search returns words containing accel mark int sourceAccelPos = source.string.indexOf(accel); if (sourceAccelPos != -1) source.string.remove(sourceAccelPos, accel.size()); int targetAccelPos = target.string.indexOf(accel); if (targetAccelPos != -1) target.string.remove(targetAccelPos, accel.size()); //check if we already have record with the same en string QSqlQuery query1(db); QString escapedCtxt = escape(ctxt); QByteArray sourceTags = source.tagsAsByteArray(); QByteArray targetTags = target.tagsAsByteArray(); //BEGIN get sourceId query1.prepare(QString(U("SELECT id FROM source_strings WHERE " "source=? AND (source_accel%1) AND source_markup%2")).arg (sourceAccelPos != -1 ? QStringLiteral("=?") : QStringLiteral("=-1 OR source_accel ISNULL"), sourceTags.isEmpty() ? QStringLiteral(" ISNULL") : QStringLiteral("=?"))); int paranum = 0; query1.bindValue(paranum++, source.string); if (sourceAccelPos != -1) query1.bindValue(paranum++, sourceAccelPos); if (!sourceTags.isEmpty()) query1.bindValue(paranum++, sourceTags); if (Q_UNLIKELY(!query1.exec())) { qCWarning(LOKALIZE_LOG) << "select db source_strings error: " << query1.lastError().text(); return false; } qlonglong sourceId; if (!query1.next()) { //BEGIN insert source anew //qCDebug(LOKALIZE_LOG) <<"insert source anew";; ++newTMSourceEntryCount; QString sql = QStringLiteral("INSERT INTO source_strings (source, source_markup, source_accel) VALUES (?, ?, ?)"); if (qpsql) sql += QLatin1String(" RETURNING id"); query1.clear(); query1.prepare(sql); query1.bindValue(0, source.string); query1.bindValue(1, sourceTags); query1.bindValue(2, sourceAccelPos != -1 ? QVariant(sourceAccelPos) : QVariant()); if (Q_UNLIKELY(!query1.exec())) { qCWarning(LOKALIZE_LOG) << "select db source_strings error: " << query1.lastError().text(); return false; } sourceId = qpsql ? (query1.next(), query1.value(0).toLongLong()) : query1.lastInsertId().toLongLong(); query1.clear(); //update index if (shouldBeInIndex) addToIndex(sourceId, source.string, rxClean1, accel, db); //END insert source anew } else { sourceId = query1.value(0).toLongLong(); ++reusedTMSourceEntryCount; //qCDebug(LOKALIZE_LOG)<<"SOURCE ALREADY PRESENT"< tmConfigCache; static void setConfig(QSqlDatabase& db, const TMConfig& c) { QSqlQuery query(db); query.prepare(QStringLiteral("INSERT INTO tm_config (key, value) VALUES (?, ?)")); query.addBindValue(0); query.addBindValue(c.markup); //qCDebug(LOKALIZE_LOG)<<"setting tm db config:"<setPriority(QThread::IdlePriority); if (m_type == TM::Local) { QSqlDatabase db = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), m_dbName); QString dbFolder = QStandardPaths::writableLocation(QStandardPaths::DataLocation); QFileInfo fileInfo(dbFolder); if (!fileInfo.exists(dbFolder)) fileInfo.absoluteDir().mkpath(fileInfo.fileName()); db.setDatabaseName(dbFolder % QLatin1Char('/') % m_dbName % TM_DATABASE_EXTENSION); m_connectionSuccessful = db.open(); if (Q_UNLIKELY(!m_connectionSuccessful)) { qCDebug(LOKALIZE_LOG) << "failed to open db" << db.databaseName() << db.lastError().text(); QSqlDatabase::removeDatabase(m_dbName); emit done(this); return; } if (!initSqliteDb(db)) { //need to recreate db ;( QString filename = db.databaseName(); db.close(); QSqlDatabase::removeDatabase(m_dbName); QFile::remove(filename); db = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), m_dbName); db.setDatabaseName(filename); m_connectionSuccessful = db.open() && initSqliteDb(db); if (!m_connectionSuccessful) { QSqlDatabase::removeDatabase(m_dbName); emit done(this); return; } } } else { if (QSqlDatabase::contains(m_dbName)) { //reconnect is true QSqlDatabase::database(m_dbName).close(); QSqlDatabase::removeDatabase(m_dbName); } if (!m_connParams.isFilled()) { QFile rdb(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1Char('/') + m_dbName % REMOTETM_DATABASE_EXTENSION); if (!rdb.open(QIODevice::ReadOnly | QIODevice::Text)) { emit done(this); return; } QTextStream rdbParams(&rdb); m_connParams.driver = rdbParams.readLine(); m_connParams.host = rdbParams.readLine(); m_connParams.db = rdbParams.readLine(); m_connParams.user = rdbParams.readLine(); m_connParams.passwd = rdbParams.readLine(); } QSqlDatabase db = QSqlDatabase::addDatabase(m_connParams.driver, m_dbName); db.setHostName(m_connParams.host); db.setDatabaseName(m_connParams.db); db.setUserName(m_connParams.user); db.setPassword(m_connParams.passwd); m_connectionSuccessful = db.open(); if (Q_UNLIKELY(!m_connectionSuccessful)) { QSqlDatabase::removeDatabase(m_dbName); emit done(this); return; } m_connParams.user = db.userName(); initPgDb(db); } } QSqlDatabase db = QSqlDatabase::database(m_dbName); //if (!m_markup.isEmpty()||!m_accel.isEmpty()) if (m_setParams) setConfig(db, m_tmConfig); else m_tmConfig = getConfig(db); qCDebug(LOKALIZE_LOG) << "db" << m_dbName << "opened" << a.elapsed() << m_tmConfig.targetLangCode; getStats(db, m_stat.pairsCount, m_stat.uniqueSourcesCount, m_stat.uniqueTranslationsCount); if (m_type == TM::Local) { db.close(); db.open(); } emit done(this); } CloseDBJob::CloseDBJob(const QString& name) : QObject(), QRunnable() , m_dbName(name) { setAutoDelete(false); } CloseDBJob::~CloseDBJob() { qCDebug(LOKALIZE_LOG) << "closedb dtor" << m_dbName; } void CloseDBJob::run() { if (m_dbName.length()) QSqlDatabase::removeDatabase(m_dbName); emit done(this); } static QString makeAcceledString(QString source, const QString& accel, const QVariant& accelPos) { if (accelPos.isNull()) return source; int accelPosInt = accelPos.toInt(); if (accelPosInt != -1) source.insert(accelPosInt, accel); return source; } SelectJob* TM::initSelectJob(Catalog* catalog, DocPosition pos, QString db, int opt) { SelectJob* job = new SelectJob(catalog->sourceWithTags(pos), catalog->context(pos.entry).first(), catalog->url(), pos, db.isEmpty() ? Project::instance()->projectID() : db); if (opt & Enqueue) { //deletion should be done by receiver, e.g. slotSuggestionsCame() threadPool()->start(job, SELECT); } return job; } SelectJob::SelectJob(const CatalogString& source, const QString& ctxt, const QString& file, const DocPosition& pos, const QString& dbName) : QObject(), QRunnable() , m_source(source) , m_ctxt(ctxt) , m_file(file) , m_dequeued(false) , m_pos(pos) , m_dbName(dbName) { setAutoDelete(false); //qCDebug(LOKALIZE_LOG)<<"selectjob"< invertMap(const QMap& source) { //uses the fact that map has its keys always sorted QMap sortingMap; for (QMap::const_iterator i = source.constBegin(); i != source.constEnd(); ++i) { sortingMap.insertMulti(i.value(), i.key()); } return sortingMap; } //returns true if seen translation with >85% bool SelectJob::doSelect(QSqlDatabase& db, QStringList& words, //QList& entries, bool isShort) { bool qpsql = (db.driverName() == QLatin1String("QPSQL")); QMap occurencies; QVector idsForWord; QSqlQuery queryWords(db); //TODO ??? not sure. make another loop before to create QList< QList > then reorder it by size static const QString queryC[] = {U("SELECT ids_long FROM words WHERE word='%1'"), U("SELECT ids_short FROM words WHERE word='%1'") }; QString queryString = queryC[isShort]; //for each word... int o = words.size(); while (--o >= 0) { //if this is not the first word occurrence, just readd ids for it if (!(!idsForWord.isEmpty() && words.at(o) == words.at(o + 1))) { idsForWord.clear(); queryWords.exec(queryString.arg(words.at(o))); if (Q_UNLIKELY(!queryWords.exec(queryString.arg(words.at(o))))) qCWarning(LOKALIZE_LOG) << "select error: " << queryWords.lastError().text() << endl; if (queryWords.next()) { QByteArray arr(queryWords.value(0).toByteArray()); queryWords.clear(); QList ids(arr.split(' ')); int p = ids.size(); idsForWord.reserve(p); while (--p >= 0) idsForWord.append(ids.at(p).toLongLong(/*bool ok*/0, 36)); } else { queryWords.clear(); continue; } } //qCWarning(LOKALIZE_LOG) <<"SelectJob: idsForWord.size() "<::const_iterator i = idsForWord.constBegin(); i != idsForWord.constEnd(); i++) occurencies[*i]++; //0 is default value } //accels are removed TMConfig c = getConfig(db); QString tmp = c.markup; if (!c.markup.isEmpty()) tmp += '|'; QRegExp rxSplit(QLatin1Char('(') % tmp % QStringLiteral("\\W+|\\d+)+")); QString sourceClean(m_source.string); sourceClean.remove(c.accel); //split m_english for use in wordDiff later--all words are needed so we cant use list we already have QStringList englishList(sourceClean.toLower().split(rxSplit, QString::SkipEmptyParts)); static QRegExp delPart(QStringLiteral("*"), Qt::CaseSensitive, QRegExp::Wildcard); static QRegExp addPart(QStringLiteral("*"), Qt::CaseSensitive, QRegExp::Wildcard); delPart.setMinimal(true); addPart.setMinimal(true); //QList concordanceLevels=sortedUniqueValues(occurencies); //we start from entries with higher word-concordance level QMap concordanceLevelToIds = invertMap(occurencies); if (concordanceLevelToIds.isEmpty()) return false; bool seen85 = false; int limit = 200; auto clit = concordanceLevelToIds.constEnd(); if (concordanceLevelToIds.size()) --clit; if (concordanceLevelToIds.size()) while (--limit >= 0) { if (Q_UNLIKELY(m_dequeued)) break; //for every concordance level qlonglong level = clit.key(); QString joined; while (level == clit.key()) { joined += QString::number(clit.value()) + ','; if (clit == concordanceLevelToIds.constBegin() || --limit < 0) break; --clit; } joined.chop(1); //get records containing current word QSqlQuery queryFetch(U( "SELECT id, source, source_accel, source_markup FROM source_strings WHERE " "source_strings.id IN (") % joined % ')', db); TMEntry e; while (queryFetch.next()) { e.id = queryFetch.value(0).toLongLong(); if (queryFetch.value(3).toByteArray().size()) qCDebug(LOKALIZE_LOG) << "BA" << queryFetch.value(3).toByteArray(); e.source = CatalogString(makeAcceledString(queryFetch.value(1).toString(), c.accel, queryFetch.value(2)), queryFetch.value(3).toByteArray()); if (e.source.string.contains(TAGRANGE_IMAGE_SYMBOL)) { if (!e.source.tags.size()) qCWarning(LOKALIZE_LOG) << "problem:" << queryFetch.value(3).toByteArray().size() << queryFetch.value(3).toByteArray(); } //e.target=queryFetch.value(2).toString(); //QStringList e_ctxt=queryFetch.value(3).toString().split('\b',QString::SkipEmptyParts); //e.date=queryFetch.value(4).toString(); e.markupExpr = c.markup; e.accelExpr = c.accel; e.dbName = db.connectionName(); //BEGIN calc score QString str = e.source.string; str.remove(c.accel); QStringList englishSuggList(str.toLower().split(rxSplit, QString::SkipEmptyParts)); if (englishSuggList.size() > 10 * englishList.size()) continue; //sugg is 'old' --translator has to adapt its translation to 'new'--current QString result = wordDiff(englishSuggList, englishList); //qCWarning(LOKALIZE_LOG) <<"SelectJob: doin "< 1 so we have decreased it, and increased result: / exp(0.014 * float(addLen) * log10(3.0f + addSubStrCount)); if (delLen) { //qCWarning(LOKALIZE_LOG) <<"SelectJob: delLen:"< 8500; if (seen85 && e.score < 6000) continue; //BEGIN fetch rest of the data QString change_author_str; QString authors_table_str; if (qpsql) { //change_author_str=", main.change_author "; change_author_str = QStringLiteral(", pg_user.usename "); authors_table_str = QStringLiteral(" JOIN pg_user ON (pg_user.usesysid=main.change_author) "); } QSqlQuery queryRest(U( "SELECT main.id, main.date, main.ctxt, main.bits, " "target_strings.target, target_strings.target_accel, target_strings.target_markup, " "files.path, main.change_date ") % change_author_str % U( "FROM main JOIN target_strings ON (target_strings.id=main.target) JOIN files ON (files.id=main.file) ") % authors_table_str % U("WHERE " "main.source=") % QString::number(e.id) % U(" AND " "(main.bits&4)!=4 AND " "target_strings.target NOTNULL") , db); //ORDER BY tm_main.id ? queryRest.exec(); //qCDebug(LOKALIZE_LOG)<<"main select error"< sortedEntryList; //to eliminate same targets from different files while (queryRest.next()) { e.id = queryRest.value(0).toLongLong(); e.date = queryRest.value(1).toDate(); e.ctxt = queryRest.value(2).toString(); e.target = CatalogString(makeAcceledString(queryRest.value(4).toString(), c.accel, queryRest.value(5)), queryRest.value(6).toByteArray()); QStringList matchData = queryRest.value(2).toString().split(TM_DELIMITER, QString::KeepEmptyParts); //context|plural e.file = queryRest.value(7).toString(); if (e.target.isEmpty()) continue; e.obsolete = queryRest.value(3).toInt() & 1; e.changeDate = queryRest.value(8).toDate(); if (qpsql) e.changeAuthor = queryRest.value(9).toString(); //BEGIN exact match score++ if (possibleExactMatch) { //"exact" match (case insensitive+w/o non-word characters!) if (m_source.string == e.source.string) e.score = 10000; else e.score = 9900; } if (!m_ctxt.isEmpty() && matchData.size() > 0) { //check not needed? if (matchData.at(0) == m_ctxt) e.score += 33; } //qCWarning(LOKALIZE_LOG)<<"m_pos"< 1) { int form = matchData.at(1).toInt(); //pluralMatches=(form&&form==m_pos.form); if (form && form == (int)m_pos.form) { //qCWarning(LOKALIZE_LOG)<<"this"< hash; int oldCount = m_entries.size(); QMap::const_iterator it = sortedEntryList.constEnd(); if (sortedEntryList.size()) while (true) { --it; const TMEntry& e = it.key(); int& hits = hash[e.target.string]; if (!hits) //0 was default value m_entries.append(e); hits++; if (it == sortedEntryList.constBegin()) break; } for (int i = oldCount; i < m_entries.size(); ++i) m_entries[i].hits = hash.value(m_entries.at(i).target.string); //END fetch rest of the data } queryFetch.clear(); if (clit == concordanceLevelToIds.constBegin()) break; if (seen85) limit = qMin(limit, 100); //be more restrictive for the next concordance levels } return seen85; } void SelectJob::run() { //qCDebug(LOKALIZE_LOG)<<"select started"<setPriority(QThread::IdlePriority); QTime a; a.start(); if (Q_UNLIKELY(!QSqlDatabase::contains(m_dbName))) { emit done(this); return; } QSqlDatabase db = QSqlDatabase::database(m_dbName); if (Q_UNLIKELY(!db.isValid() || !db.isOpen())) { emit done(this); return; } //qCDebug(LOKALIZE_LOG)<<"select started 2"<()); int limit = qMin(Settings::suggCount(), m_entries.size()); int i = m_entries.size(); while (--i >= limit) m_entries.removeLast(); if (Q_UNLIKELY(m_dequeued)) { emit done(this); return; } ++i; while (--i >= 0) { m_entries[i].accelExpr = c.accel; m_entries[i].markupExpr = c.markup; m_entries[i].diff = userVisibleWordDiff(m_entries.at(i).source.string, m_source.string, m_entries.at(i).accelExpr, m_entries.at(i).markupExpr); } emit done(this); } ScanJob::ScanJob(const QString& filePath, const QString& dbName) : QRunnable() , m_filePath(filePath) , m_time(0) , m_added(0) , m_newVersions(0) , m_size(0) , m_dbName(dbName) { qCDebug(LOKALIZE_LOG) << m_dbName << m_filePath; } ScanJob::~ScanJob() { } void ScanJob::run() { if (stop || !QSqlDatabase::contains(m_dbName)) { return; } qCWarning(LOKALIZE_LOG) << "scan job started for" << m_filePath << m_dbName << stop << m_dbName; //QThread::currentThread()->setPriority(QThread::IdlePriority); QTime a; a.start(); QSqlDatabase db = QSqlDatabase::database(m_dbName); if (!db.isOpen()) return; //initSqliteDb(db); TMConfig c = getConfig(db, true); QRegExp rxClean1(c.markup); rxClean1.setMinimal(true); Catalog catalog(0); if (Q_LIKELY(catalog.loadFromUrl(m_filePath, QString(), &m_size, /*no auto save*/true) == 0)) { if (c.targetLangCode != catalog.targetLangCode()) { qCWarning(LOKALIZE_LOG) << "not indexing file because target languages don't match:" << c.targetLangCode << "in TM vs" << catalog.targetLangCode() << "in file"; return; } qlonglong priorId = -1; QSqlQuery queryBegin(QStringLiteral("BEGIN"), db); //qCWarning(LOKALIZE_LOG) <<"queryBegin error: " < #include /** @author Nick Shaforostoff */ class TmxParser : public QXmlDefaultHandler { enum State { //localstate for getting chars into right place null = 0, seg, propContext, propFile, propPluralForm, propApproved }; enum Lang { Source, Target, Null }; public: TmxParser(const QString& dbName); ~TmxParser(); private: bool startDocument(); bool startElement(const QString&, const QString&, const QString&, const QXmlAttributes&); bool endElement(const QString&, const QString&, const QString&); bool characters(const QString&); private: QSqlDatabase db; QRegExp rxClean1; QString accel; int m_hits; CatalogString m_segment[3]; //Lang enum QList m_inlineTags; QString m_context; QString m_pluralForm; QString m_filePath; QString m_approvedString; State m_state: 8; Lang m_lang: 8; ushort m_added; QMap m_fileIds; QString m_dbLangCode; }; TmxParser::TmxParser(const QString& dbName) : m_hits(0) , m_state(null) , m_lang(Null) , m_added(0) , m_dbLangCode(Project::instance()->langCode().toLower()) { db = QSqlDatabase::database(dbName); TMConfig c = getConfig(db); rxClean1.setPattern(c.markup); rxClean1.setMinimal(true); accel = c.accel; } bool TmxParser::startDocument() { //initSqliteDb(db); m_fileIds.clear(); QSqlQuery queryBegin(QLatin1String("BEGIN"), db); m_state = null; m_lang = Null; return true; } TmxParser::~TmxParser() { QSqlQuery queryEnd(QLatin1String("END"), db); } bool TmxParser::startElement(const QString&, const QString&, const QString& qName, const QXmlAttributes& attr) { if (qName == QLatin1String("tu")) { bool ok; m_hits = attr.value(QLatin1String("usagecount")).toInt(&ok); if (!ok) m_hits = -1; m_segment[Source].clear(); m_segment[Target].clear(); m_context.clear(); m_pluralForm.clear(); m_filePath.clear(); m_approvedString.clear(); } else if (qName == QLatin1String("tuv")) { QString attrLang = attr.value(QStringLiteral("xml:lang")).toLower(); if (attrLang == QLatin1String("en")) //TODO startsWith? m_lang = Source; else if (attrLang == m_dbLangCode) m_lang = Target; else { qCWarning(LOKALIZE_LOG) << "skipping lang" << attr.value("xml:lang"); m_lang = Null; } } else if (qName == QLatin1String("prop")) { QString attrType = attr.value(QStringLiteral("type")).toLower(); if (attrType == QLatin1String("x-context")) m_state = propContext; else if (attrType == QLatin1String("x-file")) m_state = propFile; else if (attrType == QLatin1String("x-pluralform")) m_state = propPluralForm; else if (attrType == QLatin1String("x-approved")) m_state = propApproved; else m_state = null; } else if (qName == QLatin1String("seg")) { m_state = seg; } else if (m_state == seg && m_lang != Null) { InlineTag::InlineElement t = InlineTag::getElementType(qName.toLatin1()); if (t != InlineTag::_unknown) { m_segment[m_lang].string += QChar(TAGRANGE_IMAGE_SYMBOL); int pos = m_segment[m_lang].string.size(); m_inlineTags.append(InlineTag(pos, pos, t, attr.value(QStringLiteral("id")))); } } return true; } bool TmxParser::endElement(const QString&, const QString&, const QString& qName) { if (qName == QLatin1String("tu")) { if (m_filePath.isEmpty()) m_filePath = QLatin1String("tmx-import"); if (!m_fileIds.contains(m_filePath)) m_fileIds.insert(m_filePath, getFileId(m_filePath, db)); qlonglong fileId = m_fileIds.value(m_filePath); if (!m_pluralForm.isEmpty()) m_context += TM_DELIMITER + m_pluralForm; qlonglong priorId = -1; bool ok = doInsertEntry(m_segment[Source], m_segment[Target], m_context, m_approvedString != QLatin1String("no"), fileId, db, rxClean1, accel, priorId, priorId); if (Q_LIKELY(ok)) ++m_added; } else if (m_state == seg && m_lang != Null) { InlineTag::InlineElement t = InlineTag::getElementType(qName.toLatin1()); if (t != InlineTag::_unknown) { InlineTag tag = m_inlineTags.takeLast(); qCWarning(LOKALIZE_LOG) << qName << tag.getElementName(); if (tag.isPaired()) { tag.end = m_segment[m_lang].string.size(); m_segment[m_lang].string += QChar(TAGRANGE_IMAGE_SYMBOL); } m_segment[m_lang].tags.append(tag); } } m_state = null; return true; } bool TmxParser::characters(const QString& ch) { if (m_state == seg && m_lang != Null) m_segment[m_lang].string += ch; else if (m_state == propFile) m_filePath += ch; else if (m_state == propContext) m_context += ch; else if (m_state == propPluralForm) m_pluralForm += ch; else if (m_state == propApproved) m_approvedString += ch; return true; } ImportTmxJob::ImportTmxJob(const QString& filename, const QString& dbName) : QRunnable() , m_filename(filename) , m_time(0) , m_dbName(dbName) { } ImportTmxJob::~ImportTmxJob() { qCWarning(LOKALIZE_LOG) << "ImportTmxJob dtor"; } void ImportTmxJob::run() { QTime a; a.start(); QFile file(m_filename); if (!file.open(QFile::ReadOnly | QFile::Text)) return; TmxParser parser(m_dbName); QXmlSimpleReader reader; reader.setContentHandler(&parser); QXmlInputSource xmlInputSource(&file); if (!reader.parse(xmlInputSource)) qCWarning(LOKALIZE_LOG) << "failed to load" << m_filename; //qCWarning(LOKALIZE_LOG) <<"Done scanning "< ExportTmxJob::ExportTmxJob(const QString& filename, const QString& dbName) : QRunnable() , m_filename(filename) , m_time(0) , m_dbName(dbName) { } ExportTmxJob::~ExportTmxJob() { qCDebug(LOKALIZE_LOG) << "ExportTmxJob dtor"; } void ExportTmxJob::run() { QTime a; a.start(); QFile out(m_filename); if (!out.open(QFile::WriteOnly | QFile::Text)) return; QXmlStreamWriter xmlOut(&out); xmlOut.setAutoFormatting(true); xmlOut.writeStartDocument(QStringLiteral("1.0")); xmlOut.writeStartElement(QStringLiteral("tmx")); xmlOut.writeAttribute(QStringLiteral("version"), QStringLiteral("2.0")); xmlOut.writeStartElement(QStringLiteral("header")); xmlOut.writeAttribute(QStringLiteral("creationtool"), QStringLiteral("lokalize")); xmlOut.writeAttribute(QStringLiteral("creationtoolversion"), QStringLiteral(LOKALIZE_VERSION)); xmlOut.writeAttribute(QStringLiteral("segtype"), QStringLiteral("paragraph")); xmlOut.writeAttribute(QStringLiteral("o-encoding"), QStringLiteral("UTF-8")); xmlOut.writeEndElement(); xmlOut.writeStartElement(QStringLiteral("body")); QString dbLangCode = Project::instance()->langCode(); QSqlDatabase db = QSqlDatabase::database(m_dbName); QSqlQuery query1(db); if (Q_UNLIKELY(!query1.exec(U( "SELECT main.id, main.ctxt, main.date, main.bits, " "source_strings.source, source_strings.source_accel, " "target_strings.target, target_strings.target_accel, " "files.path, main.change_date " "FROM main, source_strings, target_strings, files " "WHERE source_strings.id=main.source AND " "target_strings.id=main.target AND " "files.id=main.file")))) qCWarning(LOKALIZE_LOG) << "select error: " << query1.lastError().text(); TMConfig c = getConfig(db); const QString DATE_FORMAT = QStringLiteral("yyyyMMdd"); const QString PROP = QStringLiteral("prop"); const QString TYPE = QStringLiteral("type"); while (query1.next()) { QString source = makeAcceledString(query1.value(4).toString(), c.accel, query1.value(5)); QString target = makeAcceledString(query1.value(6).toString(), c.accel, query1.value(7)); xmlOut.writeStartElement(QStringLiteral("tu")); xmlOut.writeAttribute(QStringLiteral("tuid"), QString::number(query1.value(0).toLongLong())); xmlOut.writeStartElement(QStringLiteral("tuv")); xmlOut.writeAttribute(QStringLiteral("xml:lang"), QStringLiteral("en")); xmlOut.writeStartElement(QStringLiteral("seg")); xmlOut.writeCharacters(source); xmlOut.writeEndElement(); xmlOut.writeEndElement(); xmlOut.writeStartElement(QStringLiteral("tuv")); xmlOut.writeAttribute(QStringLiteral("xml:lang"), dbLangCode); xmlOut.writeAttribute(QStringLiteral("creationdate"), QDate::fromString(query1.value(2).toString(), Qt::ISODate).toString(DATE_FORMAT)); xmlOut.writeAttribute(QStringLiteral("changedate"), QDate::fromString(query1.value(9).toString(), Qt::ISODate).toString(DATE_FORMAT)); QString ctxt = query1.value(1).toString(); if (!ctxt.isEmpty()) { int pos = ctxt.indexOf(TM_DELIMITER); if (pos != -1) { QString plural = ctxt; plural.remove(0, pos + 1); ctxt.remove(pos, plural.size()); xmlOut.writeStartElement(PROP); xmlOut.writeAttribute(TYPE, "x-pluralform"); xmlOut.writeCharacters(plural); xmlOut.writeEndElement(); } if (!ctxt.isEmpty()) { xmlOut.writeStartElement(PROP); xmlOut.writeAttribute(TYPE, "x-context"); xmlOut.writeCharacters(ctxt); xmlOut.writeEndElement(); } } QString filePath = query1.value(8).toString(); if (!filePath.isEmpty()) { xmlOut.writeStartElement(PROP); xmlOut.writeAttribute(TYPE, "x-file"); xmlOut.writeCharacters(filePath); xmlOut.writeEndElement(); } qlonglong bits = query1.value(8).toLongLong(); if (bits & TM_NOTAPPROVED) if (!filePath.isEmpty()) { xmlOut.writeStartElement(PROP); xmlOut.writeAttribute(TYPE, "x-approved"); xmlOut.writeCharacters("no"); xmlOut.writeEndElement(); } xmlOut.writeStartElement(QStringLiteral("seg")); xmlOut.writeCharacters(target); xmlOut.writeEndElement(); xmlOut.writeEndElement(); xmlOut.writeEndElement(); } query1.clear(); xmlOut.writeEndDocument(); out.close(); qCWarning(LOKALIZE_LOG) << "ExportTmxJob done exporting:" << a.elapsed(); m_time = a.elapsed(); } //END TMX ExecQueryJob::ExecQueryJob(const QString& queryString, const QString& dbName) : QObject(), QRunnable() , query(0) , m_dbName(dbName) , m_query(queryString) { setAutoDelete(false); //qCDebug(LOKALIZE_LOG)<<"ExecQueryJob"<lastError().text(); emit done(this); } diff --git a/src/tm/jobs.h b/src/tm/jobs.h index cbe368f..a5efef8 100644 --- a/src/tm/jobs.h +++ b/src/tm/jobs.h @@ -1,450 +1,493 @@ /* **************************************************************************** This file is part of Lokalize Copyright (C) 2007-2014 by Nick Shaforostoff This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . **************************************************************************** */ #ifndef JOBS_H #define JOBS_H #include "pos.h" #include "tmentry.h" #include #include #include #include class QSqlQuery; /** * Translation Memory classes. see initDb() function for the database scheme */ namespace TM { #define TM_DATABASE_EXTENSION ".db" #define REMOTETM_DATABASE_EXTENSION ".remotedb" enum DbType {Local, Remote, Undefined}; //is needed only on opening #define TM_AREA 8111 QThreadPool* threadPool(); #define CLOSEDB 10001 #define OPENDB 10000 #define TMTABSELECT 100 #define UPDATE 80 #define REMOVE 70 +#define REMOVEFILE 69 #define INSERT 60 #define SELECT 50 #define BATCHSELECTFINISHED 49 #define IMPORT 30 #define EXPORT 25 +#define REMOVEMISSINGFILES 11 #define SCAN 10 #define SCANFINISHED 9 - struct TMConfig { QString markup; QString accel; QString sourceLangCode; QString targetLangCode; }; void cancelAllJobs(); //HACK because threadweaver's dequeue is not workin' //called on startup class OpenDBJob: public QObject, public QRunnable { Q_OBJECT public: struct ConnectionParams { QString driver, host, db, user, passwd; bool isFilled() { return !host.isEmpty() && !db.isEmpty() && !user.isEmpty(); } }; explicit OpenDBJob(const QString& dbName, DbType type = TM::Local, bool reconnect = false, const ConnectionParams& connParams = ConnectionParams()); ~OpenDBJob(); - int priority()const + int priority() const { return OPENDB; } struct DBStat { int pairsCount, uniqueSourcesCount, uniqueTranslationsCount; DBStat(): pairsCount(0), uniqueSourcesCount(0), uniqueTranslationsCount(0) {} }; protected: void run(); signals: void done(OpenDBJob*); public: QString m_dbName; DbType m_type; //statistics DBStat m_stat; //for the new DB creation TMConfig m_tmConfig; bool m_setParams; bool m_connectionSuccessful; bool m_reconnect; ConnectionParams m_connParams; }; //called on startup class CloseDBJob: public QObject, public QRunnable { Q_OBJECT public: explicit CloseDBJob(const QString& dbName); ~CloseDBJob(); - int priority()const + int priority() const { return CLOSEDB; } QString dbName() { return m_dbName; } signals: void done(CloseDBJob*); protected: void run(); QString m_dbName; //statistics? }; class SelectJob: public QObject, public QRunnable { Q_OBJECT public: SelectJob(const CatalogString& source, const QString& ctxt, const QString& file, const DocPosition&,//for back tracking const QString& dbName); ~SelectJob(); - int priority()const + int priority() const { return SELECT; } signals: void done(SelectJob*); protected: void run(); //void aboutToBeDequeued(ThreadWeaver::WeaverInterface*); KDE5PORT private: //returns true if seen translation with >85% bool doSelect(QSqlDatabase&, QStringList& words, bool isShort); public: CatalogString m_source; private: QString m_ctxt; QString m_file; bool m_dequeued; public: DocPosition m_pos; QList m_entries; QString m_dbName; }; enum {Enqueue = 1}; SelectJob* initSelectJob(Catalog*, DocPosition pos, QString db = QString(), int opt = Enqueue); +class RemoveMissingFilesJob: public QObject, public QRunnable +{ + Q_OBJECT +public: + explicit RemoveMissingFilesJob(const QString& dbName); + ~RemoveMissingFilesJob(); + int priority() const + { + return REMOVEMISSINGFILES; + } + +protected: + void run(); + + QString m_dbName; + +signals: + void done(); +}; + +class RemoveFileJob: public QObject, public QRunnable +{ + Q_OBJECT +public: + explicit RemoveFileJob(const QString& filePath, const QString& dbName, QObject *parent = nullptr); + ~RemoveFileJob(); + int priority() const + { + return REMOVEFILE; + } + +protected: + void run(); + + QString m_filePath; + QString m_dbName; + QObject m_parent; + +signals: + void done(); +}; + class RemoveJob: public QObject, public QRunnable { Q_OBJECT public: explicit RemoveJob(const TMEntry& entry); ~RemoveJob(); - int priority()const + int priority() const { return REMOVE; } protected: void run(); TMEntry m_entry; signals: void done(); }; /** * used to eliminate a lot of duplicate entries * * it is supposed to run on entry switch/file close in Editor **/ //TODO a mechanism to get rid of dead dups (use strigi?). //also, display usage of different translations and suggest user //to use only one of them (listview, checkboxes) class UpdateJob: public QRunnable { public: explicit UpdateJob(const QString& filePath, const QString& ctxt, const CatalogString& en, const CatalogString& newTarget, int form, bool approved, //const DocPosition&,//for back tracking const QString& dbName); ~UpdateJob() {} - int priority()const + int priority() const { return UPDATE; } protected: void run(); private: QString m_filePath; QString m_ctxt; CatalogString m_english; CatalogString m_newTarget; int m_form; bool m_approved; QString m_dbName; }; //scan one file class ScanJob: public QRunnable { public: explicit ScanJob(const QString& filePath, const QString& dbName); ~ScanJob(); - int priority()const + int priority() const { return SCAN; } protected: void run(); public: QString m_filePath; //statistics ushort m_time; ushort m_added; ushort m_newVersions;//e1.english==e2.english, e1.target!=e2.target int m_size; QString m_dbName; }; class ScanJobFeedingBack: public QObject, public ScanJob { Q_OBJECT public: explicit ScanJobFeedingBack(const QString& filePath, const QString& dbName) : QObject(), ScanJob(filePath, dbName) { setAutoDelete(false); } protected: void run() { ScanJob::run(); emit done(this); } signals: void done(ScanJobFeedingBack*); }; //helper class BatchSelectFinishedJob: public QObject, public QRunnable { Q_OBJECT public: explicit BatchSelectFinishedJob(QWidget* view) : QObject(), QRunnable() , m_view(view) {} ~BatchSelectFinishedJob() {}; - int priority()const + int priority() const { return BATCHSELECTFINISHED; } signals: void done(); protected: void run() { emit done(); }; public: QWidget* m_view; }; #if 0 we use index stored in db now... //create index --called on startup class IndexWordsJob: public QRunnable { Q_OBJECT public: IndexWordsJob(QObject* parent = 0); ~IndexWordsJob(); - int priority()const + int priority() const { return 100; } protected: void run(); public: TMWordHash m_tmWordHash; //statistics? }; #endif class ImportTmxJob: public QRunnable { public: explicit ImportTmxJob(const QString& url, const QString& dbName); ~ImportTmxJob(); - int priority()const + int priority() const { return IMPORT; } protected: void run(); public: QString m_filename; //statistics ushort m_time; QString m_dbName; }; // #if 0 class ExportTmxJob: public QRunnable { public: explicit ExportTmxJob(const QString& url, const QString& dbName); ~ExportTmxJob(); - int priority()const + int priority() const { return IMPORT; } protected: void run(); public: QString m_filename; //statistics ushort m_time; QString m_dbName; }; // #endif class ExecQueryJob: public QObject, public QRunnable { Q_OBJECT public: explicit ExecQueryJob(const QString& queryString, const QString& dbName); ~ExecQueryJob(); - int priority()const + int priority() const { return TMTABSELECT; } QSqlQuery* query; signals: void done(ExecQueryJob*); protected: void run(); QString m_dbName; QString m_query; //statistics? }; } #endif diff --git a/src/tm/tmscanapi.h b/src/tm/tmscanapi.h index c5f3094..2259f12 100644 --- a/src/tm/tmscanapi.h +++ b/src/tm/tmscanapi.h @@ -1,71 +1,73 @@ /* **************************************************************************** This file is part of Lokalize Copyright (C) 2007-2009 by Nick Shaforostoff This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . **************************************************************************** */ #ifndef SCANAPI_H #define SCANAPI_H #include #include #include #include #ifndef NOKDE #include #endif bool dragIsAcceptable(const QList& urls); QString shorterFilePath(const QString path); namespace TM { class ScanJob; class ScanJobFeedingBack; +void purgeMissingFilesFromTM(const QStringList& urls, const QString& dbName); + ///wrapper. returns gross number of jobs started int scanRecursive(const QStringList& urls, const QString& dbName); #ifndef NOKDE class RecursiveScanJob: public KJob { Q_OBJECT public: RecursiveScanJob(const QString& dbName, QObject* parent = 0); void setJobs(const QVector& jobs); void start(); public slots: void scanJobFinished(ScanJobFeedingBack*); protected: bool doKill(); private: QString m_dbName; QTime m_time; QVector m_jobs; }; #endif } #endif diff --git a/src/tm/tmtab.cpp b/src/tm/tmtab.cpp index f403670..4178672 100644 --- a/src/tm/tmtab.cpp +++ b/src/tm/tmtab.cpp @@ -1,773 +1,790 @@ /* **************************************************************************** This file is part of Lokalize Copyright (C) 2007-2014 by Nick Shaforostoff This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . **************************************************************************** */ #include "tmtab.h" #include "lokalize_debug.h" #include "ui_queryoptions.h" #include "project.h" #include "dbfilesmodel.h" #include "tmscanapi.h" #include "qaview.h" +#include "prefs_lokalize.h" #include "jobs.h" #include "fastsizehintitemdelegate.h" #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include -#include - #ifndef NOKDE #include #include #include +#include +#include #endif #if defined(Q_OS_WIN) && defined(QStringLiteral) #undef QStringLiteral #define QStringLiteral QLatin1String #endif using namespace TM; //static int BIG_COUNTER=0; //TODO do things for case when user explicitly wants to find & accel mark //BEGIN TMDBModel TMDBModel::TMDBModel(QObject* parent) : QSqlQueryModel(parent) , m_queryType(WordOrder) , m_totalResultCount(0) { setHeaderData(TMDBModel::Source, Qt::Horizontal, i18nc("@title:column Original text", "Source")); setHeaderData(TMDBModel::Target, Qt::Horizontal, i18nc("@title:column Text in target language", "Target")); setHeaderData(TMDBModel::Context, Qt::Horizontal, i18nc("@title:column", "Context")); setHeaderData(TMDBModel::Filepath, Qt::Horizontal, i18nc("@title:column", "File")); setHeaderData(TMDBModel::TransationStatus, Qt::Horizontal, i18nc("@title:column", "Translation Status")); } void TMDBModel::setDB(const QString& str) { m_dbName = str; QString sourceLangCode = DBFilesModel::instance()->m_configurations.value(str).sourceLangCode; QString targetLangCode = DBFilesModel::instance()->m_configurations.value(str).targetLangCode; if (sourceLangCode.length()) setHeaderData(TMDBModel::Source, Qt::Horizontal, QString(i18nc("@title:column Original text", "Source") % QStringLiteral(": ") % sourceLangCode)); if (targetLangCode.length()) setHeaderData(TMDBModel::Target, Qt::Horizontal, QString(i18nc("@title:column Text in target language", "Target") % QStringLiteral(": ") % targetLangCode)); } void TMDBModel::setQueryType(int type) { m_queryType = (QueryType)type; } void TMDBModel::setFilter(const QString& source, const QString& target, bool invertSource, bool invertTarget, const QString& filemask ) { QString escapedSource(source); escapedSource.replace('\'', QStringLiteral("''")); QString escapedTarget(target); escapedTarget.replace('\'', QStringLiteral("''")); QString invertSourceStr; if (invertSource) invertSourceStr = QStringLiteral("NOT "); QString invertTargetStr; if (invertTarget) invertTargetStr = QStringLiteral("NOT "); QString escapedFilemask(filemask); escapedFilemask.replace('\'', QStringLiteral("''")); QString sourceQuery; QString targetQuery; QString fileQuery; if (m_queryType == SubStr) { escapedSource.replace('%', QStringLiteral("\b%")); escapedSource.replace('_', QStringLiteral("\b_")); escapedTarget.replace('%', QStringLiteral("\b%")); escapedTarget.replace('_', QStringLiteral("\b_")); if (!escapedSource.isEmpty()) sourceQuery = QStringLiteral("AND source_strings.source ") % invertSourceStr % QStringLiteral("LIKE '%") % escapedSource % QStringLiteral("%' ESCAPE '\b' "); if (!escapedTarget.isEmpty()) targetQuery = QStringLiteral("AND target_strings.target ") % invertTargetStr % QStringLiteral("LIKE '%") % escapedTarget % QStringLiteral("%' ESCAPE '\b' "); } else if (m_queryType == WordOrder) { /*escapedSource.replace('%',"\b%");escapedSource.replace('_',"\b_"); escapedTarget.replace('%',"\b%");escapedTarget.replace('_',"\b_");*/ QRegExp wre(QStringLiteral("\\W")); QStringList sourceList = escapedSource.split(wre, QString::SkipEmptyParts); QStringList targetList = escapedTarget.split(wre, QString::SkipEmptyParts); if (!sourceList.isEmpty()) sourceQuery = QStringLiteral("AND source_strings.source ") % invertSourceStr % QStringLiteral("LIKE '%") % sourceList.join(QStringLiteral("%' AND source_strings.source ") % invertSourceStr % QStringLiteral("LIKE '%")) % QStringLiteral("%' "); if (!targetList.isEmpty()) targetQuery = QStringLiteral("AND target_strings.target ") % invertTargetStr % QStringLiteral("LIKE '%") % targetList.join(QStringLiteral("%' AND target_strings.target ") % invertTargetStr % QStringLiteral("LIKE '%")) % QStringLiteral("%' "); } else { if (!escapedSource.isEmpty()) sourceQuery = QStringLiteral("AND source_strings.source ") % invertSourceStr % QStringLiteral("GLOB '") % escapedSource % QStringLiteral("' "); if (!escapedTarget.isEmpty()) targetQuery = QStringLiteral("AND target_strings.target ") % invertTargetStr % QStringLiteral("GLOB '") % escapedTarget % QStringLiteral("' "); } if (!filemask.isEmpty()) fileQuery = QStringLiteral("AND files.path GLOB '") % escapedFilemask % QStringLiteral("' "); QString fromPart = QStringLiteral("FROM main JOIN source_strings ON (source_strings.id=main.source) " "JOIN target_strings ON (target_strings.id=main.target), files " "WHERE files.id=main.file ") % sourceQuery % targetQuery % fileQuery; ExecQueryJob* job = new ExecQueryJob(QStringLiteral( "SELECT source_strings.source, target_strings.target, " "main.ctxt, files.path, " "source_strings.source_accel, target_strings.target_accel, main.bits ") + fromPart, m_dbName); connect(job, &ExecQueryJob::done, this, &TMDBModel::slotQueryExecuted); threadPool()->start(job); job = new ExecQueryJob(QStringLiteral("SELECT count(*) ") + fromPart, m_dbName); connect(job, &ExecQueryJob::done, this, &TMDBModel::slotQueryExecuted); threadPool()->start(job); m_totalResultCount = 0; } void TMDBModel::slotQueryExecuted(ExecQueryJob* job) { job->deleteLater(); if (job->query->lastQuery().startsWith(QLatin1String("SELECT count(*) "))) { m_totalResultCount = job->query->next() ? job->query->value(0).toInt() : -1; emit finalResultCountFetched(m_totalResultCount); return; } setQuery(*(job->query)); emit resultsFetched(); } bool TMDBModel::rowIsApproved(int row) const { bool ok; qlonglong bits = record(row).value(TMDBModel::_Bits).toLongLong(&ok); return !(ok && bits & 4); } int TMDBModel::translationStatus(const QModelIndex& item) const { if (QSqlQueryModel::data(item.sibling(item.row(), Target), Qt::DisplayRole).toString().isEmpty()) return 2; return int(!rowIsApproved(item.row())); } #define TM_DELIMITER '\v' QVariant TMDBModel::data(const QModelIndex& item, int role) const { bool doHtml = (role == FastSizeHintItemDelegate::HtmlDisplayRole); if (doHtml) role = Qt::DisplayRole; else if (role == Qt::FontRole && item.column() == TMDBModel::Target) { //TODO Qt::ForegroundRole -- brush for orphaned entries QFont font = QApplication::font(); font.setItalic(!rowIsApproved(item.row())); return font; } else if (role == FullPathRole && item.column() == TMDBModel::Filepath) return QSqlQueryModel::data(item, Qt::DisplayRole); else if (role == TransStateRole) return translationStatus(item); QVariant result = QSqlQueryModel::data(item, role); /* if (role==Qt::SizeHintRole && !result.isValid()) BIG_COUNTER++;*/ if (role != Qt::DisplayRole) return result; if (item.column() == TMDBModel::Context) { //context QString r = result.toString(); int pos = r.indexOf(TM_DELIMITER); if (pos != -1) result = r.remove(pos, r.size()); } else if (item.column() < TMDBModel::Context && !record(item.row()).isNull(TMDBModel::_SourceAccel + item.column())) { //source, target const QVariant& posVar = record(item.row()).value(TMDBModel::_SourceAccel + item.column()); int pos = -1; bool ok = false; if (posVar.isValid()) pos = posVar.toInt(&ok); if (ok && pos != -1) { QString r = result.toString(); r.insert(pos, Project::instance()->accel()); result = r; } } else if (item.column() == TMDBModel::Filepath) { return shorterFilePath(result.toString()); } else if (item.column() == TMDBModel::TransationStatus) { static QString statuses[] = {i18nc("@info:status 'non-fuzzy' in gettext terminology", "Ready"), i18nc("@info:status 'fuzzy' in gettext terminology", "Needs review"), i18nc("@info:status", "Untranslated") }; return statuses[translationStatus(item)]; } if (doHtml && item.column() < TMDBModel::Context) return convertToHtml(result.toString(), item.column() == TMDBModel::Target && !rowIsApproved(item.row())); else return result; } //END TMDBModel //BEGIN TMResultsSortFilterProxyModel class TMResultsSortFilterProxyModel: public QSortFilterProxyModel { public: TMResultsSortFilterProxyModel(QObject *parent) : QSortFilterProxyModel(parent) {} void setRules(const QVector& rules); void fetchMore(const QModelIndex& parent); QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; protected: bool lessThan(const QModelIndex& left, const QModelIndex& right) const; bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const; private: QVector m_rules; mutable QMap m_matchingRulesForSourceRow; //mutable QMap > m_highlightDataForSourceRow; }; bool TMResultsSortFilterProxyModel::lessThan(const QModelIndex& left, const QModelIndex& right) const { if (left.column() == TMDBModel::TransationStatus) { int l = left.data(TMDBModel::TransStateRole).toInt(); int r = right.data(TMDBModel::TransStateRole).toInt(); return l < r; } return QSortFilterProxyModel::lessThan(left, right); } void TMResultsSortFilterProxyModel::fetchMore(const QModelIndex& parent) { int oldSourceRowCount = sourceModel()->rowCount(); int oldRowCount = rowCount(); QSortFilterProxyModel::fetchMore(parent); if (m_rules.isEmpty()) return; while (oldRowCount == rowCount()) { QSortFilterProxyModel::fetchMore(parent); if (sourceModel()->rowCount() == oldSourceRowCount) break; oldSourceRowCount = sourceModel()->rowCount(); } qCDebug(LOKALIZE_LOG) << "row count" << sourceModel()->rowCount() << " filtered:" << rowCount(); emit layoutChanged(); } void TMResultsSortFilterProxyModel::setRules(const QVector& rules) { m_rules = rules; m_matchingRulesForSourceRow.clear(); invalidateFilter(); } QVariant TMResultsSortFilterProxyModel::data(const QModelIndex& index, int role) const { QVariant result = QSortFilterProxyModel::data(index, role); if (m_rules.isEmpty() || role != FastSizeHintItemDelegate::HtmlDisplayRole) return result; if (index.column() != TMDBModel::Source && index.column() != TMDBModel::Target) return result; int source_row = mapToSource(index).row(); QString string = result.toString(); QVector regExps; if (index.column() == TMDBModel::Source) regExps = m_rules[m_matchingRulesForSourceRow[source_row]].sources; else regExps = m_rules[m_matchingRulesForSourceRow[source_row]].falseFriends; foreach (const QRegExp& re, regExps) { int pos = re.indexIn(string); if (pos != -1) return string.replace(pos, re.matchedLength(), QStringLiteral("") % re.cap(0) % QStringLiteral("")); } //StartLen sl=m_highlightDataForSourceRow.value(source_row).at(index.column()); return result; } bool TMResultsSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const { if (m_rules.isEmpty()) return true; QString source = sourceModel()->index(source_row, TMDBModel::Source, source_parent).data().toString(); QString target = sourceModel()->index(source_row, TMDBModel::Target, source_parent).data().toString(); static QVector dummy_positions; int i = findMatchingRule(m_rules, source, target, dummy_positions); bool accept = (i != -1); if (accept) m_matchingRulesForSourceRow[source_row] = i; return accept; } //END TMResultsSortFilterProxyModel class QueryStylesModel: public QStringListModel { public: explicit QueryStylesModel(QObject* parent = 0); QVariant data(const QModelIndex& item, int role) const; }; QueryStylesModel::QueryStylesModel(QObject* parent): QStringListModel(parent) { setStringList(QStringList(i18n("Substring")) << i18n("Google-like") << i18n("Wildcard")); } QVariant QueryStylesModel::data(const QModelIndex& item, int role) const { if (role == Qt::ToolTipRole) { static QString tooltips[] = {i18n("Case insensitive"), i18n("Space is AND operator. Case insensitive."), i18n("Shell globs (* and ?). Case sensitive.") }; return tooltips[item.row()]; } return QStringListModel::data(item, role); } //BEGIN TMWindow TMTab::TMTab(QWidget *parent) : LokalizeSubwindowBase2(parent) , m_proxyModel(new TMResultsSortFilterProxyModel(this)) , m_partToAlsoTryLater(DocPosition::UndefPart) , m_dbusId(-1) { //setCaption(i18nc("@title:window","Translation Memory"),false); setWindowTitle(i18nc("@title:window", "Translation Memory")); setAcceptDrops(true); ui_queryOptions = new Ui_QueryOptions; QWidget* w = new QWidget(this); ui_queryOptions->setupUi(w); setCentralWidget(w); ui_queryOptions->queryLayout->setStretchFactor(ui_queryOptions->mainQueryLayout, 42); connect(ui_queryOptions->querySource, &QLineEdit::returnPressed, this, &TMTab::performQuery); connect(ui_queryOptions->queryTarget, &QLineEdit::returnPressed, this, &TMTab::performQuery); connect(ui_queryOptions->filemask, &QLineEdit::returnPressed, this, &TMTab::performQuery); connect(ui_queryOptions->doFind, &QPushButton::clicked, this, &TMTab::performQuery); connect(ui_queryOptions->doUpdateTM, &QPushButton::clicked, this, &TMTab::updateTM); QShortcut* sh = new QShortcut(Qt::CTRL + Qt::Key_L, this); connect(sh, &QShortcut::activated, ui_queryOptions->querySource, QOverload<>::of(&QLineEdit::setFocus)); setFocusProxy(ui_queryOptions->querySource); QTreeView* view = ui_queryOptions->treeView; view->setContextMenuPolicy(Qt::ActionsContextMenu); QAction* a = new QAction(i18n("Copy source to clipboard"), view); a->setShortcut(Qt::CTRL + Qt::Key_S); a->setShortcutContext(Qt::WidgetWithChildrenShortcut); connect(a, &QAction::triggered, this, &TMTab::copySource); view->addAction(a); a = new QAction(i18n("Copy target to clipboard"), view); a->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Return)); a->setShortcutContext(Qt::WidgetWithChildrenShortcut); connect(a, &QAction::triggered, this, &TMTab::copyTarget); view->addAction(a); a = new QAction(i18n("Open file"), view); a->setShortcut(QKeySequence(Qt::Key_Return)); a->setShortcutContext(Qt::WidgetWithChildrenShortcut); connect(a, &QAction::triggered, this, &TMTab::openFile); connect(view, &QTreeView::activated, this, &TMTab::openFile); view->addAction(a); //view->addAction(KStandardAction::copy(this),this,SLOT(),this); //QKeySequence::Copy? //QShortcut* shortcut = new QShortcut(Qt::CTRL + Qt::Key_P,view,0,0,Qt::WidgetWithChildrenShortcut); //connect(shortcut,SIGNAL(activated()), this, SLOT(copyText())); m_model = new TMDBModel(this); m_model->setDB(Project::instance()->projectID()); m_proxyModel->setDynamicSortFilter(true); m_proxyModel->setSourceModel(m_model); view->setModel(m_proxyModel); view->sortByColumn(TMDBModel::Filepath, Qt::AscendingOrder); view->setSortingEnabled(true); view->setColumnHidden(TMDBModel::_SourceAccel, true); view->setColumnHidden(TMDBModel::_TargetAccel, true); view->setColumnHidden(TMDBModel::_Bits, true); QVector singleLineColumns(TMDBModel::ColumnCount, false); singleLineColumns[TMDBModel::Filepath] = true; singleLineColumns[TMDBModel::TransationStatus] = true; singleLineColumns[TMDBModel::Context] = true; QVector richTextColumns(TMDBModel::ColumnCount, false); richTextColumns[TMDBModel::Source] = true; richTextColumns[TMDBModel::Target] = true; view->setItemDelegate(new FastSizeHintItemDelegate(this, singleLineColumns, richTextColumns)); connect(m_model, &TMDBModel::resultsFetched, (FastSizeHintItemDelegate*)view->itemDelegate(), &FastSizeHintItemDelegate::reset); connect(m_model, &TMDBModel::modelReset, (FastSizeHintItemDelegate*)view->itemDelegate(), &FastSizeHintItemDelegate::reset); //connect(m_model,SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)),view->itemDelegate(),SLOT(reset())); connect(m_proxyModel, &TMResultsSortFilterProxyModel::layoutChanged, (FastSizeHintItemDelegate*)view->itemDelegate(), &FastSizeHintItemDelegate::reset); connect(m_proxyModel, &TMResultsSortFilterProxyModel::layoutChanged, this, &TMTab::displayTotalResultCount); connect(m_model, &TMDBModel::resultsFetched, this, &TMTab::handleResults); connect(m_model, &TMDBModel::finalResultCountFetched, this, &TMTab::displayTotalResultCount); ui_queryOptions->queryStyle->setModel(new QueryStylesModel(this)); connect(ui_queryOptions->queryStyle, QOverload::of(&KComboBox::currentIndexChanged), m_model, &TMDBModel::setQueryType); ui_queryOptions->dbName->setModel(DBFilesModel::instance()); ui_queryOptions->dbName->setRootModelIndex(DBFilesModel::instance()->rootIndex()); int pos = ui_queryOptions->dbName->findData(Project::instance()->projectID(), DBFilesModel::NameRole); if (pos >= 0) ui_queryOptions->dbName->setCurrentIndex(pos); connect(ui_queryOptions->dbName, QOverload::of(&QComboBox::currentIndexChanged), m_model, &TMDBModel::setDB); //connect(ui_queryOptions->dbName, SIGNAL(activated(QString)), this, SLOT(performQuery())); //BEGIN resizeColumnToContents static const int maxInitialWidths[4] = {QApplication::desktop()->availableGeometry().width() / 3, QApplication::desktop()->availableGeometry().width() / 3, 50, 200}; int column = sizeof(maxInitialWidths) / sizeof(int); while (--column >= 0) view->setColumnWidth(column, maxInitialWidths[column]); //END resizeColumnToContents int i = 6; while (--i > ID_STATUS_PROGRESS) statusBarItems.insert(i, QString()); setXMLFile(QStringLiteral("translationmemoryrui.rc"), true); dbusObjectPath(); QAction *action; KActionCollection* ac = actionCollection(); KActionCategory* tm = new KActionCategory(i18nc("@title actions category", "Translation Memory"), ac); action = tm->addAction(QStringLiteral("tools_tm_manage"), Project::instance(), SLOT(showTMManager())); action->setText(i18nc("@action:inmenu", "Manage translation memories")); m_qaView = new QaView(this); m_qaView->hide(); addDockWidget(Qt::RightDockWidgetArea, m_qaView); tm->addAction(QStringLiteral("showqa_action"), m_qaView->toggleViewAction()); connect(m_qaView, &QaView::rulesChanged, this, QOverload<>::of(&TMTab::setQAMode)); connect(m_qaView->toggleViewAction(), &QAction::toggled, this, QOverload::of(&TMTab::setQAMode)); #ifndef NOKDE KConfig config; KConfigGroup cg(&config, "MainWindow"); view->header()->restoreState(QByteArray::fromBase64(cg.readEntry("TMSearchResultsHeaderState", QByteArray()))); #endif } TMTab::~TMTab() { #ifndef NOKDE KConfig config; KConfigGroup cg(&config, "MainWindow"); cg.writeEntry("TMSearchResultsHeaderState", ui_queryOptions->treeView->header()->saveState().toBase64()); ids.removeAll(m_dbusId); #endif delete ui_queryOptions; } void TMTab::updateTM() { scanRecursive(QStringList(Project::instance()->poDir()), Project::instance()->projectID()); + if (Settings::deleteFromTMOnMissing()) { + RemoveMissingFilesJob* job = new RemoveMissingFilesJob(Project::instance()->projectID()); + TM::threadPool()->start(job, REMOVEMISSINGFILES); + } } void TMTab::performQuery() { if (ui_queryOptions->dbName->currentText().isEmpty()) { int pos = ui_queryOptions->dbName->findData(Project::instance()->projectID(), DBFilesModel::NameRole); if (pos >= 0) ui_queryOptions->dbName->setCurrentIndex(pos); //m_model->setDB(Project::instance()->projectID()); } m_model->setFilter(ui_queryOptions->querySource->text(), ui_queryOptions->queryTarget->text(), ui_queryOptions->invertSource->isChecked(), ui_queryOptions->invertTarget->isChecked(), ui_queryOptions->filemask->text() ); QApplication::setOverrideCursor(Qt::BusyCursor); } void TMTab::handleResults() { QApplication::restoreOverrideCursor(); QString filemask = ui_queryOptions->filemask->text(); //ui_queryOptions->regexSource->text(),ui_queryOptions->regexTarget->text() int rowCount = m_model->rowCount(); if (rowCount == 0) { qCDebug(LOKALIZE_LOG) << "m_model->rowCount()==0"; //try harder if (m_partToAlsoTryLater != DocPosition::UndefPart) { if (m_partToAlsoTryLater == DocPosition::Comment) { QString text = ui_queryOptions->queryTarget->text(); if (text.isEmpty()) text = ui_queryOptions->querySource->text(); if (text.isEmpty()) m_partToAlsoTryLater = DocPosition::UndefPart; else findGuiText(text); return; } QLineEdit* const source_target_query[] = {ui_queryOptions->queryTarget, ui_queryOptions->querySource}; source_target_query[m_partToAlsoTryLater == DocPosition::Source]->setText(source_target_query[m_partToAlsoTryLater != DocPosition::Source]->text()); source_target_query[m_partToAlsoTryLater != DocPosition::Source]->clear(); m_partToAlsoTryLater = ui_queryOptions->filemask->text().isEmpty() ? DocPosition::UndefPart : DocPosition::Comment; //leave a note that we should also try w/o package if the current one doesn't succeed return performQuery(); } if (!filemask.isEmpty() && !filemask.contains('*')) { ui_queryOptions->filemask->setText('*' % filemask % '*'); return performQuery(); } } qCDebug(LOKALIZE_LOG) << "=DocPosition::UndefPart"; m_partToAlsoTryLater = DocPosition::UndefPart; ui_queryOptions->treeView->setFocus(); } void TMTab::displayTotalResultCount() { int total = m_model->totalResultCount(); int filtered = m_proxyModel->rowCount(); if (filtered == m_model->rowCount()) statusBarItems.insert(1, i18nc("@info:status message entries", "Total: %1", total)); else statusBarItems.insert(1, i18nc("@info:status message entries", "Total: %1 (%2)", filtered, total)); } static void copy(Ui_QueryOptions* ui_queryOptions, int column) { QApplication::clipboard()->setText(ui_queryOptions->treeView->currentIndex().sibling(ui_queryOptions->treeView->currentIndex().row(), column).data().toString()); } void TMTab::copySource() { copy(ui_queryOptions, TMDBModel::Source); } void TMTab::copyTarget() { copy(ui_queryOptions, TMDBModel::Target); } void TMTab::openFile() { QModelIndex item = ui_queryOptions->treeView->currentIndex(); + if (Settings::deleteFromTMOnMissing()) { + //Check if the file exists and delete it if it doesn't + QString filePath = item.sibling(item.row(), TMDBModel::Filepath).data(Qt::UserRole).toString(); + if (Project::instance()->isFileMissing(filePath)) { + //File doesn't exist + RemoveFileJob* job = new RemoveFileJob(filePath, ui_queryOptions->dbName->currentText()); + TM::threadPool()->start(job, REMOVEFILE); + KMessageBox::information(this, i18nc("@info", "The file %1 doesn't exist, it has been removed from the Translation Memory.", filePath)); + return performQuery();//We relaunch the query + } + } emit fileOpenRequested(item.sibling(item.row(), TMDBModel::Filepath).data(Qt::UserRole).toString(), item.sibling(item.row(), TMDBModel::Source).data().toString(), item.sibling(item.row(), TMDBModel::Context).data().toString()); } void TMTab::setQAMode() { return setQAMode(true); } void TMTab::setQAMode(bool enable) { static_cast(ui_queryOptions->treeView->itemDelegate())->reset(); if (!enable) { m_proxyModel->setRules(QVector()); return; } m_proxyModel->setRules(m_qaView->rules()); /*QDomElement docElem = m_categories.at(0).toElement(); QDomNode n = docElem.firstChildElement(); while(!n.isNull()) { QDomElement e = n.toElement(); qCDebug(LOKALIZE_LOG) << e.tagName(); n = n.nextSiblingElement(); }*/ performQuery(); } //END TMWindow #if 0 bool QueryResultDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& /*option*/, const QModelIndex& index) { qCWarning(LOKALIZE_LOG) << "QEvent" << event; if (event->type() == QEvent::Shortcut) { qCWarning(LOKALIZE_LOG) << "QEvent::Shortcut" << index.data().canConvert(QVariant::String); if (static_cast(event)->key().matches(QKeySequence::Copy) && index.data().canConvert(QVariant::String)) { QApplication::clipboard()->setText(index.data().toString()); qCWarning(LOKALIZE_LOG) << "index.data().toString()"; } } else if (event->type() == QEvent::MouseButtonRelease) { QMouseEvent* mEvent = static_cast(event); if (mEvent->button() == Qt::MidButton) { } } else if (event->type() == QEvent::KeyPress) { QKeyEvent* kEvent = static_cast(event); if (kEvent->key() == Qt::Key_Return) { if (kEvent->modifiers() == Qt::NoModifier) { } } } else return false; event->accept(); return true; } #endif void TMTab::dragEnterEvent(QDragEnterEvent* event) { if (dragIsAcceptable(event->mimeData()->urls())) event->acceptProposedAction(); } void TMTab::dropEvent(QDropEvent *event) { QStringList files; foreach (const QUrl& url, event->mimeData()->urls()) files.append(url.toLocalFile()); if (scanRecursive(files, Project::instance()->projectID())) event->acceptProposedAction(); } #ifndef NOKDE #include "translationmemoryadaptor.h" #endif //BEGIN DBus interface QList TMTab::ids; QString TMTab::dbusObjectPath() { #ifndef NOKDE const QString TM_PATH = QStringLiteral("/ThisIsWhatYouWant/TranslationMemory/"); if (m_dbusId == -1) { new TranslationMemoryAdaptor(this); int i = 0; while (i < ids.size() && i == ids.at(i)) ++i; ids.insert(i, i); m_dbusId = i; QDBusConnection::sessionBus().registerObject(TM_PATH + QString::number(m_dbusId), this); } return TM_PATH + QString::number(m_dbusId); #else return QString(); #endif } void TMTab::lookup(QString source, QString target) { source.remove(Project::instance()->accel()); target.remove(Project::instance()->accel()); ui_queryOptions->querySource->setText(source); ui_queryOptions->queryTarget->setText(target); ui_queryOptions->invertSource->setChecked(false); ui_queryOptions->invertTarget->setChecked(false); ui_queryOptions->queryStyle->setCurrentIndex(TMDBModel::SubStr); performQuery(); } // void TMTab::lookup(DocPosition::Part part, QString text) // { // lookup(part==DocPosition::Source?text:QString(),part==DocPosition::Target?text:QString()); // } bool TMTab::findGuiTextPackage(QString text, QString package) { qCWarning(LOKALIZE_LOG) << package << text; QLineEdit* const source_target_query[] = {ui_queryOptions->queryTarget, ui_queryOptions->querySource}; static const DocPosition::Part source_target[] = {DocPosition::Target, DocPosition::Source}; QTextCodec* latin1 = QTextCodec::codecForMib(4); DocPosition::Part tryNowPart = source_target[latin1->canEncode(text)]; m_partToAlsoTryLater = source_target[tryNowPart == DocPosition::Target]; text.remove(Project::instance()->accel()); ui_queryOptions->querySource->clear(); ui_queryOptions->queryTarget->clear(); source_target_query[tryNowPart == DocPosition::Source]->setText(text); ui_queryOptions->invertSource->setChecked(false); ui_queryOptions->invertTarget->setChecked(false); if (!package.isEmpty()) package = '*' % package % '*'; ui_queryOptions->filemask->setText(package); ui_queryOptions->queryStyle->setCurrentIndex(TMDBModel::Glob); performQuery(); return true; } //END DBus interface diff --git a/src/tm/tmtab.h b/src/tm/tmtab.h index dcfeee2..f913a05 100644 --- a/src/tm/tmtab.h +++ b/src/tm/tmtab.h @@ -1,186 +1,190 @@ /* **************************************************************************** This file is part of Lokalize Copyright (C) 2007-2011 by Nick Shaforostoff This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . **************************************************************************** */ #ifndef TMTAB_H #define TMTAB_H #include "lokalizesubwindowbase.h" #include "pos.h" #include #include #include class KXMLGUIClient; class QComboBox; class QTreeView; class QSortFilterProxyModel; class QCheckBox; class QaView; class Ui_QueryOptions; class TMResultsSortFilterProxyModel; namespace TM { class TMDBModel; class ExecQueryJob; /** * Translation Memory tab */ class TMTab: public LokalizeSubwindowBase2 { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.Lokalize.TranslationMemory") //qdbuscpp2xml -m -s tm/tmtab.h -o tm/org.kde.lokalize.TranslationMemory.xml public: TMTab(QWidget *parent); ~TMTab(); void hideDocks() {}; void showDocks() {}; KXMLGUIClient* guiClient() { return (KXMLGUIClient*)this; } QString dbusObjectPath(); int dbusId() { return m_dbusId; } public slots: Q_SCRIPTABLE bool findGuiText(QString text) { return findGuiTextPackage(text, QString()); } Q_SCRIPTABLE bool findGuiTextPackage(QString text, QString package); Q_SCRIPTABLE void lookup(QString source, QString target); //void lookup(DocPosition::Part, QString text); public slots: void performQuery(); void updateTM(); void copySource(); void copyTarget(); void openFile(); void handleResults(); void displayTotalResultCount(); void setQAMode(); void setQAMode(bool enabled); signals: void fileOpenRequested(const QString& url, const QString& source, const QString& ctxt); private: void dragEnterEvent(QDragEnterEvent* event); void dropEvent(QDropEvent*); private: Ui_QueryOptions* ui_queryOptions; TMDBModel* m_model; TMResultsSortFilterProxyModel *m_proxyModel; QaView* m_qaView; DocPosition::Part m_partToAlsoTryLater; int m_dbusId; static QList ids; }; class TMDBModel: public QSqlQueryModel { Q_OBJECT public: enum TMDBModelColumns { Source = 0, Target, Context, Filepath, _SourceAccel, _TargetAccel, _Bits, TransationStatus, ColumnCount }; enum QueryType { SubStr = 0, WordOrder, Glob }; enum Roles { FullPathRole = Qt::UserRole, TransStateRole = Qt::UserRole + 1, //HtmlDisplayRole=FastSizeHintItemDelegate::HtmlDisplayRole }; TMDBModel(QObject* parent); ~TMDBModel() {} QVariant data(const QModelIndex& item, int role = Qt::DisplayRole) const; int columnCount(const QModelIndex& parent = QModelIndex()) const { Q_UNUSED(parent); return ColumnCount; } int totalResultCount()const { return m_totalResultCount; } + QString dbName()const + { + return m_dbName; + } public slots: void setFilter(const QString& source, const QString& target, bool invertSource, bool invertTarget, const QString& filemask ); void setQueryType(int); void setDB(const QString&); void slotQueryExecuted(ExecQueryJob*); signals: void resultsFetched(); void finalResultCountFetched(int); private: bool rowIsApproved(int row) const; int translationStatus(const QModelIndex& item) const; private: QueryType m_queryType; QString m_dbName; int m_totalResultCount; }; //const QString& sourceRefine, const QString& targetRefine } #endif diff --git a/src/tm/tmview.cpp b/src/tm/tmview.cpp index 4878d62..8b3e01c 100644 --- a/src/tm/tmview.cpp +++ b/src/tm/tmview.cpp @@ -1,989 +1,1018 @@ /* **************************************************************************** This file is part of Lokalize Copyright (C) 2007-2014 by Nick Shaforostoff This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . **************************************************************************** */ #include "tmview.h" #include "lokalize_debug.h" #include "jobs.h" #include "tmscanapi.h" #include "catalog.h" #include "cmd.h" #include "project.h" #include "prefs_lokalize.h" #include "dbfilesmodel.h" #include "diff.h" #include "xlifftextedit.h" #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #ifdef NDEBUG #undef NDEBUG #endif #define DEBUG using namespace TM; struct DiffInfo { DiffInfo(int reserveSize); QString diffClean; QString old; //Formatting info: QByteArray diffIndex; //Map old string-->d.diffClean QVector old2DiffClean; }; DiffInfo::DiffInfo(int reserveSize) { diffClean.reserve(reserveSize); old.reserve(reserveSize); diffIndex.reserve(reserveSize); old2DiffClean.reserve(reserveSize); } /** * 0 - common + - add - - del M - modified so the string is like 00000MM00+++---000 (M appears afterwards) */ static DiffInfo getDiffInfo(const QString& diff) { DiffInfo d(diff.size()); QChar sep('{'); char state = '0'; //walk through diff string char-by-char //calculate old and others int pos = -1; while (++pos < diff.size()) { if (diff.at(pos) == sep) { if (diff.indexOf(QLatin1String("{KBABELDEL}"), pos) == pos) { state = '-'; pos += 10; } else if (diff.indexOf(QLatin1String("{KBABELADD}"), pos) == pos) { state = '+'; pos += 10; } else if (diff.indexOf(QLatin1String("{/KBABEL"), pos) == pos) { state = '0'; pos += 11; } } else { if (state != '+') { d.old.append(diff.at(pos)); d.old2DiffClean.append(d.diffIndex.count()); } d.diffIndex.append(state); d.diffClean.append(diff.at(pos)); } } return d; } void TextBrowser::mouseDoubleClickEvent(QMouseEvent* event) { QTextBrowser::mouseDoubleClickEvent(event); QString sel = textCursor().selectedText(); if (!(sel.isEmpty() || sel.contains(' '))) emit textInsertRequested(sel); } TMView::TMView(QWidget* parent, Catalog* catalog, const QVector& actions_insert, const QVector& actions_remove) : QDockWidget(i18nc("@title:window", "Translation Memory"), parent) , m_browser(new TextBrowser(this)) , m_catalog(catalog) , m_currentSelectJob(0) , m_actions_insert(actions_insert) , m_actions_remove(actions_remove) , m_normTitle(i18nc("@title:window", "Translation Memory")) , m_hasInfoTitle(m_normTitle + QStringLiteral(" [*]")) , m_hasInfo(false) , m_isBatching(false) , m_markAsFuzzy(false) { setObjectName(QStringLiteral("TMView")); setWidget(m_browser); m_browser->document()->setDefaultStyleSheet(QStringLiteral("p.close_match { font-weight:bold; }")); m_browser->viewport()->setBackgroundRole(QPalette::Background); QTimer::singleShot(0, this, &TMView::initLater); connect(m_catalog, QOverload::of(&Catalog::signalFileLoaded), this, &TMView::slotFileLoaded); } TMView::~TMView() { #if QT_VERSION >= 0x050500 int i = m_jobs.size(); while (--i >= 0) TM::threadPool()->cancel(m_jobs.at(i)); #endif } void TMView::initLater() { setAcceptDrops(true); QSignalMapper* signalMapper_insert = new QSignalMapper(this); QSignalMapper* signalMapper_remove = new QSignalMapper(this); int i = m_actions_insert.size(); while (--i >= 0) { connect(m_actions_insert.at(i), &QAction::triggered, signalMapper_insert, QOverload<>::of(&QSignalMapper::map)); signalMapper_insert->setMapping(m_actions_insert.at(i), i); } i = m_actions_remove.size(); while (--i >= 0) { connect(m_actions_remove.at(i), &QAction::triggered, signalMapper_remove, QOverload<>::of(&QSignalMapper::map)); signalMapper_remove->setMapping(m_actions_remove.at(i), i); } connect(signalMapper_insert, QOverload::of(&QSignalMapper::mapped), this, &TMView::slotUseSuggestion); connect(signalMapper_remove, QOverload::of(&QSignalMapper::mapped), this, &TMView::slotRemoveSuggestion); setToolTip(i18nc("@info:tooltip", "Double-click any word to insert it into translation")); DBFilesModel::instance(); connect(m_browser, &TM::TextBrowser::textInsertRequested, this, &TMView::textInsertRequested); connect(m_browser, &TM::TextBrowser::customContextMenuRequested, this, &TMView::contextMenu); //TODO ? kdisplayPaletteChanged // connect(KGlobalSettings::self(),,SIGNAL(kdisplayPaletteChanged()),this,SLOT(slotPaletteChanged())); } void TMView::dragEnterEvent(QDragEnterEvent* event) { if (dragIsAcceptable(event->mimeData()->urls())) event->acceptProposedAction(); } void TMView::dropEvent(QDropEvent *event) { QStringList files; foreach (const QUrl& url, event->mimeData()->urls()) files.append(url.toLocalFile()); if (scanRecursive(files, Project::instance()->projectID())) event->acceptProposedAction(); } void TMView::slotFileLoaded(const QString& filePath) { const QString& pID = Project::instance()->projectID(); if (Settings::scanToTMOnOpen()) TM::threadPool()->start(new ScanJob(filePath, pID), SCAN); if (!Settings::prefetchTM() && !m_isBatching) return; m_cache.clear(); #if QT_VERSION >= 0x050500 int i = m_jobs.size(); while (--i >= 0) TM::threadPool()->cancel(m_jobs.at(i)); #endif m_jobs.clear(); DocPosition pos; while (switchNext(m_catalog, pos)) { if (!m_catalog->isEmpty(pos.entry) && m_catalog->isApproved(pos.entry)) continue; SelectJob* j = initSelectJob(m_catalog, pos, pID); connect(j, &SelectJob::done, this, &TMView::slotCacheSuggestions); m_jobs.append(j); } //dummy job for the finish indication BatchSelectFinishedJob* m_seq = new BatchSelectFinishedJob(this); connect(m_seq, &BatchSelectFinishedJob::done, this, &TMView::slotBatchSelectDone); TM::threadPool()->start(m_seq, BATCHSELECTFINISHED); m_jobs.append(m_seq); } void TMView::slotCacheSuggestions(SelectJob* job) { m_jobs.removeAll(job); qCDebug(LOKALIZE_LOG) << job->m_pos.entry; if (job->m_pos.entry == m_pos.entry) slotSuggestionsCame(job); m_cache[DocPos(job->m_pos)] = job->m_entries.toVector(); } void TMView::slotBatchSelectDone() { m_jobs.clear(); if (!m_isBatching) return; bool insHappened = false; DocPosition pos; while (switchNext(m_catalog, pos)) { if (!(m_catalog->isEmpty(pos.entry) || !m_catalog->isApproved(pos.entry)) ) continue; const QVector& suggList = m_cache.value(DocPos(pos)); if (suggList.isEmpty()) continue; const TMEntry& entry = suggList.first(); if (entry.score < 9900) //hacky continue; { bool forceFuzzy = (suggList.size() > 1 && suggList.at(1).score >= 10000) || entry.score < 10000; bool ctxtMatches = entry.score == 1001; if (!m_catalog->isApproved(pos.entry)) { ///m_catalog->push(new DelTextCmd(m_catalog,pos,m_catalog->msgstr(pos))); removeTargetSubstring(m_catalog, pos, 0, m_catalog->targetWithTags(pos).string.size()); if (ctxtMatches || !(m_markAsFuzzy || forceFuzzy)) SetStateCmd::push(m_catalog, pos, true); } else if ((m_markAsFuzzy && !ctxtMatches) || forceFuzzy) { SetStateCmd::push(m_catalog, pos, false); } ///m_catalog->push(new InsTextCmd(m_catalog,pos,entry.target)); insertCatalogString(m_catalog, pos, entry.target, 0); if (Q_UNLIKELY(m_pos.entry == pos.entry && pos.form == m_pos.form)) emit refreshRequested(); } if (!insHappened) { insHappened = true; m_catalog->beginMacro(i18nc("@item Undo action", "Batch translation memory filling")); } } QString msg = i18nc("@info", "Batch translation has been completed."); if (insHappened) m_catalog->endMacro(); else { // xgettext: no-c-format msg += ' '; msg += i18nc("@info", "No suggestions with exact matches were found."); } KPassivePopup::message(KPassivePopup::Balloon, i18nc("@title", "Batch translation complete"), msg, this); } void TMView::slotBatchTranslate() { m_isBatching = true; m_markAsFuzzy = false; if (!Settings::prefetchTM()) slotFileLoaded(m_catalog->url()); else if (m_jobs.isEmpty()) return slotBatchSelectDone(); KPassivePopup::message(KPassivePopup::Balloon, i18nc("@title", "Batch translation"), i18nc("@info", "Batch translation has been scheduled."), this); } void TMView::slotBatchTranslateFuzzy() { m_isBatching = true; m_markAsFuzzy = true; if (!Settings::prefetchTM()) slotFileLoaded(m_catalog->url()); else if (m_jobs.isEmpty()) slotBatchSelectDone(); KPassivePopup::message(KPassivePopup::Balloon, i18nc("@title", "Batch translation"), i18nc("@info", "Batch translation has been scheduled."), this); } void TMView::slotNewEntryDisplayed() { return slotNewEntryDisplayed(DocPosition()); } void TMView::slotNewEntryDisplayed(const DocPosition& pos) { if (m_catalog->numberOfEntries() <= pos.entry) return;//because of Qt::QueuedConnection #if QT_VERSION >= 0x050500 int i = m_jobs.size(); while (--i >= 0) TM::threadPool()->cancel(m_currentSelectJob); #endif //update DB //m_catalog->flushUpdateDBBuffer(); //this is called via subscribtion if (pos.entry != -1) m_pos = pos; m_browser->clear(); if (Settings::prefetchTM() && m_cache.contains(DocPos(m_pos))) { QTimer::singleShot(0, this, &TMView::displayFromCache); } m_currentSelectJob = initSelectJob(m_catalog, m_pos); connect(m_currentSelectJob, &TM::SelectJob::done, this, &TMView::slotSuggestionsCame); } void TMView::displayFromCache() { if (m_prevCachePos.entry == m_pos.entry && m_prevCachePos.form == m_pos.form) return; SelectJob* temp = initSelectJob(m_catalog, m_pos, QString(), 0); temp->m_entries = m_cache.value(DocPos(m_pos)).toList(); slotSuggestionsCame(temp); temp->deleteLater(); m_prevCachePos = m_pos; } void TMView::slotSuggestionsCame(SelectJob* j) { QTime time; time.start(); SelectJob& job = *j; job.deleteLater(); if (job.m_pos.entry != m_pos.entry) return; Catalog& catalog = *m_catalog; if (catalog.numberOfEntries() <= m_pos.entry) return;//because of Qt::QueuedConnection //BEGIN query other DBs handling Project* project = Project::instance(); const QString& projectID = project->projectID(); //check if this is an additional query, from secondary DBs if (job.m_dbName != projectID) { job.m_entries += m_entries; qSort(job.m_entries.begin(), job.m_entries.end(), qGreater()); int limit = qMin(Settings::suggCount(), job.m_entries.size()); int i = job.m_entries.size(); while (--i >= limit) job.m_entries.removeLast(); } else if (job.m_entries.isEmpty() || job.m_entries.first().score < 8500) { //be careful, as we switched to QDirModel! const DBFilesModel& dbFilesModel = *(DBFilesModel::instance()); QModelIndex root = dbFilesModel.rootIndex(); int i = dbFilesModel.rowCount(root); //qCWarning(LOKALIZE_LOG)<<"query other DBs,"<= 0) { const QString& dbName = dbFilesModel.data(dbFilesModel.index(i, 0, root), DBFilesModel::NameRole).toString(); if (projectID != dbName && dbFilesModel.m_configurations.value(dbName).targetLangCode == catalog.targetLangCode()) { SelectJob* j = initSelectJob(m_catalog, m_pos, dbName); connect(j, &SelectJob::done, this, &TMView::slotSuggestionsCame); m_jobs.append(j); } } } //END query other DBs handling m_entries = job.m_entries; int limit = job.m_entries.size(); if (!limit) { if (m_hasInfo) { m_hasInfo = false; setWindowTitle(m_normTitle); } return; } if (!m_hasInfo) { m_hasInfo = true; setWindowTitle(m_hasInfoTitle); } setUpdatesEnabled(false); m_browser->clear(); m_entryPositions.clear(); //m_entries=job.m_entries; //m_browser->insertHtml(""); int i = 0; QTextBlockFormat blockFormatBase; QTextBlockFormat blockFormatAlternate; blockFormatAlternate.setBackground(QPalette().alternateBase()); QTextCharFormat noncloseMatchCharFormat; QTextCharFormat closeMatchCharFormat; closeMatchCharFormat.setFontWeight(QFont::Bold); forever { QTextCursor cur = m_browser->textCursor(); QString html; html.reserve(1024); const TMEntry& entry = job.m_entries.at(i); html += (entry.score > 9500) ? QStringLiteral("

") : QStringLiteral("

"); //qCDebug(LOKALIZE_LOG)< 10000 ? 100 : float(entry.score) / 100); //int sourceStartPos=cur.position(); QString result = entry.diff.toHtmlEscaped(); //result.replace("&","&"); //result.replace("<","<"); //result.replace(">",">"); result.replace(QLatin1String("{KBABELADD}"), QStringLiteral("")); result.replace(QLatin1String("{/KBABELADD}"), QLatin1String("")); result.replace(QLatin1String("{KBABELDEL}"), QStringLiteral("")); result.replace(QLatin1String("{/KBABELDEL}"), QLatin1String("")); result.replace(QLatin1String("\\n"), QLatin1String("\\n
")); result.replace(QLatin1String("\\n"), QLatin1String("\\n
")); html += result; #if 0 cur.insertHtml(result); cur.movePosition(QTextCursor::PreviousCharacter, QTextCursor::MoveAnchor, cur.position() - sourceStartPos); CatalogString catStr(entry.diff); catStr.string.remove("{KBABELDEL}"); catStr.string.remove("{/KBABELDEL}"); catStr.string.remove("{KBABELADD}"); catStr.string.remove("{/KBABELADD}"); catStr.tags = entry.source.tags; DiffInfo d = getDiffInfo(entry.diff); int j = catStr.tags.size(); while (--j >= 0) { catStr.tags[j].start = d.old2DiffClean.at(catStr.tags.at(j).start); catStr.tags[j].end = d.old2DiffClean.at(catStr.tags.at(j).end); } insertContent(cur, catStr, job.m_source, false); #endif //str.replace('&',"&"); TODO check html += QLatin1String("
"); if (Q_LIKELY(i < m_actions_insert.size())) { m_actions_insert.at(i)->setStatusTip(entry.target.string); html += QStringLiteral("[%1] ").arg(m_actions_insert.at(i)->shortcut().toString(QKeySequence::NativeText)); } else html += QLatin1String("[ - ] "); /* QString str(entry.target.string); str.replace('<',"<"); str.replace('>',">"); html+=str; */ cur.insertHtml(html); html.clear(); cur.setCharFormat((entry.score > 9500) ? closeMatchCharFormat : noncloseMatchCharFormat); insertContent(cur, entry.target); m_entryPositions.insert(cur.anchor(), i); html += i ? QStringLiteral("

") : QStringLiteral("

"); cur.insertHtml(html); if (Q_UNLIKELY(++i >= limit)) break; cur.insertBlock(i % 2 ? blockFormatAlternate : blockFormatBase); } m_browser->insertHtml(QStringLiteral("")); setUpdatesEnabled(true); // qCWarning(LOKALIZE_LOG)<<"ELA "<document()->blockCount(); } /* void TMView::slotPaletteChanged() { }*/ bool TMView::event(QEvent *event) { if (event->type() == QEvent::ToolTip) { QHelpEvent *helpEvent = static_cast(event); //int block1=m_browser->cursorForPosition(m_browser->viewport()->mapFromGlobal(helpEvent->globalPos())).blockNumber(); QMap::iterator block = m_entryPositions.lowerBound(m_browser->cursorForPosition(m_browser->viewport()->mapFromGlobal(helpEvent->globalPos())).anchor()); if (block != m_entryPositions.end() && *block < m_entries.size()) { const TMEntry& tmEntry = m_entries.at(*block); QString file = tmEntry.file; if (file == m_catalog->url()) file = i18nc("File argument in tooltip, when file is current file", "this"); QString tooltip = i18nc("@info:tooltip", "File: %1
Addition date: %2", file, tmEntry.date.toString(Qt::ISODate)); if (!tmEntry.changeDate.isNull() && tmEntry.changeDate != tmEntry.date) tooltip += i18nc("@info:tooltip on TM entry continues", "
Last change date: %1", tmEntry.changeDate.toString(Qt::ISODate)); if (!tmEntry.changeAuthor.isEmpty()) tooltip += i18nc("@info:tooltip on TM entry continues", "
Last change author: %1", tmEntry.changeAuthor); tooltip += i18nc("@info:tooltip on TM entry continues", "
TM: %1", tmEntry.dbName); if (tmEntry.obsolete) tooltip += i18nc("@info:tooltip on TM entry continues", "
Is not present in the file anymore"); QToolTip::showText(helpEvent->globalPos(), tooltip); return true; } } return QWidget::event(event); } void TMView::removeEntry(const TMEntry& e) { if (KMessageBox::Yes == KMessageBox::questionYesNo(this, i18n("Do you really want to remove this entry:
%1
from translation memory %2?", e.target.string.toHtmlEscaped(), e.dbName), i18nc("@title:window", "Translation Memory Entry Removal"))) { RemoveJob* job = new RemoveJob(e); connect(job, SIGNAL(done()), this, SLOT(slotNewEntryDisplayed())); TM::threadPool()->start(job, REMOVE); } } +void TMView::deleteFile(const TMEntry& e, const bool showPopUp) +{ + QString filePath = e.file; + if (Project::instance()->isFileMissing(filePath)) { + //File doesn't exist + RemoveFileJob* job = new RemoveFileJob(e.file, e.dbName); + connect(job, SIGNAL(done()), this, SLOT(slotNewEntryDisplayed())); + TM::threadPool()->start(job, REMOVEFILE); + if (showPopUp) { + KMessageBox::information(this, i18nc("@info", "The file %1 doesn't exist, it has been removed from the Translation Memory.", e.file)); + } + return; + } +} + void TMView::contextMenu(const QPoint& pos) { int block = *m_entryPositions.lowerBound(m_browser->cursorForPosition(pos).anchor()); qCWarning(LOKALIZE_LOG) << block; if (block >= m_entries.size()) return; const TMEntry& e = m_entries.at(block); - enum {Remove, Open}; + enum {Remove, RemoveFile, Open}; QMenu popup; popup.addAction(i18nc("@action:inmenu", "Remove this entry"))->setData(Remove); if (e.file != m_catalog->url() && QFile::exists(e.file)) popup.addAction(i18nc("@action:inmenu", "Open file containing this entry"))->setData(Open); + else { + if (Settings::deleteFromTMOnMissing()) { + //Automatic deletion + deleteFile(e, true); + } else if (!QFile::exists(e.file)) { + //Still offer manual deletion if this is not the current file + popup.addAction(i18nc("@action:inmenu", "Remove this missing file from TM"))->setData(RemoveFile); + } + } QAction* r = popup.exec(m_browser->mapToGlobal(pos)); if (!r) return; if (r->data().toInt() == Remove) { removeEntry(e); } else if (r->data().toInt() == Open) { emit fileOpenRequested(e.file, e.source.string, e.ctxt); + } else if ((r->data().toInt() == RemoveFile) && + KMessageBox::Yes == KMessageBox::questionYesNo(this, i18n("Do you really want to remove this missing file:
%1
from translation memory %2?", e.file, e.dbName), + i18nc("@title:window", "Translation Memory Missing File Removal"))) { + deleteFile(e, false); } } /** * helper function: * searches to th nearest rxNum or ABBR * clears rxNum if ABBR is found before rxNum */ static int nextPlacableIn(const QString& old, int start, QString& cap) { static QRegExp rxNum(QStringLiteral("[\\d\\.\\%]+")); static QRegExp rxAbbr(QStringLiteral("\\w+")); int numPos = rxNum.indexIn(old, start); // int abbrPos=rxAbbr.indexIn(old,start); int abbrPos = start; //qCWarning(LOKALIZE_LOG)<<"seeing"<= 0) { if ((c++)->isUpper()) break; } abbrPos += rxAbbr.matchedLength(); } int pos = qMin(numPos, abbrPos); if (pos == -1) pos = qMax(numPos, abbrPos); // if (pos==numPos) // cap=rxNum.cap(0); // else // cap=rxAbbr.cap(0); cap = (pos == numPos ? rxNum : rxAbbr).cap(0); //qCWarning(LOKALIZE_LOG)<]*") % Settings::addColor().name() % QLatin1String("[^>]*\">([^>]*)")); QRegExp rxDel(QLatin1String("]*") % Settings::delColor().name() % QLatin1String("[^>]*\">([^>]*)")); //rxAdd.setMinimal(true); //rxDel.setMinimal(true); //first things first int pos = 0; while ((pos = rxDel.indexIn(diff, pos)) != -1) diff.replace(pos, rxDel.matchedLength(), "\tKBABELDEL\t" % rxDel.cap(1) % "\t/KBABELDEL\t"); pos = 0; while ((pos = rxAdd.indexIn(diff, pos)) != -1) diff.replace(pos, rxAdd.matchedLength(), "\tKBABELADD\t" % rxAdd.cap(1) % "\t/KBABELADD\t"); diff.replace(QStringLiteral("<"), QStringLiteral("<")); diff.replace(QStringLiteral(">"), QStringLiteral(">")); //possible enhancement: search for non-translated words in removedSubstrings... //QStringList removedSubstrings; //QStringList addedSubstrings; /* 0 - common + - add - - del M - modified so the string is like 00000MM00+++---000 */ DiffInfo d = getDiffInfo(diff); bool sameMarkup = Project::instance()->markup() == entry.markupExpr && !entry.markupExpr.isEmpty(); bool tryMarkup = !entry.target.tags.size() && sameMarkup; //search for changed markup if (tryMarkup) { QRegExp rxMarkup(entry.markupExpr); rxMarkup.setMinimal(true); pos = 0; int replacingPos = 0; while ((pos = rxMarkup.indexIn(d.old, pos)) != -1) { //qCWarning(LOKALIZE_LOG)<<"size"<= d.old2DiffClean.at(pos)) d.diffIndex[tmp] = 'M'; //qCWarning(LOKALIZE_LOG)<<"M"< 0) { QByteArray diffMPart(d.diffIndex.left(len)); int m = diffMPart.indexOf('M'); if (m != -1) diffMPart.truncate(m); #if 0 nono //first goes del, then add. so stop on second del sequence bool seenAdd = false; int j = -1; while (++j < diffMPart.size()) { if (diffMPart.at(j) == '+') seenAdd = true; else if (seenAdd && diffMPart.at(j) == '-') { diffMPart.truncate(j); break; } } #endif //form 'oldMarkup' QString oldMarkup; oldMarkup.reserve(diffMPart.size()); int j = -1; while (++j < diffMPart.size()) { if (diffMPart.at(j) != '+') oldMarkup.append(d.diffClean.at(j)); } //qCWarning(LOKALIZE_LOG)<<"old"<= 0) d.diffIndex[j] = 'M'; //qCWarning(LOKALIZE_LOG)<<"M"<= 0) d.diffIndex[len + j] = 'M'; //qCWarning(LOKALIZE_LOG)<<"M"< 500 cases while ((++endPos < d.diffIndex.size()) && (d.diffIndex.at(endPos) == '+') && (-1 != nextPlacableIn(QString(d.diffClean.at(endPos)), 0, _)) ) diffMPart.append('+'); qCWarning(LOKALIZE_LOG) << "diffMPart extended 1" << diffMPart; // if ((pos-1>=0) && (d.old2DiffClean.at(pos)>=0)) // { // qCWarning(LOKALIZE_LOG)<<"d.diffIndex"<= 0) && (d.diffIndex.at(startPos) == '+') //&&(-1!=nextPlacableIn(QString(d.diffClean.at(d.old2DiffClean.at(pos))),0,_)) ) diffMPart.prepend('+'); ++startPos; qCWarning(LOKALIZE_LOG) << "diffMPart extended 2" << diffMPart; if ((diffMPart.contains('-') || diffMPart.contains('+')) && (!diffMPart.contains('M'))) { //form newMarkup QString newMarkup; newMarkup.reserve(diffMPart.size()); int j = -1; while (++j < diffMPart.size()) { if (diffMPart.at(j) != '-') newMarkup.append(d.diffClean.at(startPos + j)); } if (newMarkup.endsWith(' ')) newMarkup.chop(1); //qCWarning(LOKALIZE_LOG)<<"d.old"<= d.old2DiffClean.at(pos)) d.diffIndex[tmp] = 'M'; //qCWarning(LOKALIZE_LOG)<<"M"<= m_entries.size())) return; const TMEntry& e = m_entries.at(i); removeEntry(e); } void TMView::slotUseSuggestion(int i) { if (Q_UNLIKELY(i >= m_entries.size())) return; CatalogString target = targetAdapted(m_entries.at(i), m_catalog->sourceWithTags(m_pos)); #if 0 QString tmp = target.string; tmp.replace(TAGRANGE_IMAGE_SYMBOL, '*'); qCWarning(LOKALIZE_LOG) << "targetAdapted" << tmp; foreach (InlineTag tag, target.tags) qCWarning(LOKALIZE_LOG) << "tag" << tag.start << tag.end; #endif if (Q_UNLIKELY(target.isEmpty())) return; m_catalog->beginMacro(i18nc("@item Undo action", "Use translation memory suggestion")); QString old = m_catalog->targetWithTags(m_pos).string; if (!old.isEmpty()) { m_pos.offset = 0; //FIXME test! removeTargetSubstring(m_catalog, m_pos, 0, old.size()); //m_catalog->push(new DelTextCmd(m_catalog,m_pos,m_catalog->msgstr(m_pos))); } qCWarning(LOKALIZE_LOG) << "1" << target.string; //m_catalog->push(new InsTextCmd(m_catalog,m_pos,target)/*,true*/); insertCatalogString(m_catalog, m_pos, target, 0); if (m_entries.at(i).score > 9900 && !m_catalog->isApproved(m_pos.entry)) SetStateCmd::push(m_catalog, m_pos, true); m_catalog->endMacro(); emit refreshRequested(); } diff --git a/src/tm/tmview.h b/src/tm/tmview.h index b57497e..8df287a 100644 --- a/src/tm/tmview.h +++ b/src/tm/tmview.h @@ -1,133 +1,134 @@ /* **************************************************************************** This file is part of Lokalize Copyright (C) 2007-2014 by Nick Shaforostoff This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . **************************************************************************** */ #ifndef TMVIEW_H #define TMVIEW_H #include "pos.h" #include "tmentry.h" #include #include #include #include class QRunnable; class Catalog; class QDropEvent; class QDragEnterEvent; #define TM_SHORTCUTS 10 namespace TM { class TextBrowser; class SelectJob; class TMView: public QDockWidget { Q_OBJECT public: TMView(QWidget*, Catalog*, const QVector&, const QVector&); ~TMView(); void dragEnterEvent(QDragEnterEvent* event); void dropEvent(QDropEvent*); QSize sizeHint() const { return QSize(300, 100); } signals: // void textReplaceRequested(const QString&); void refreshRequested(); void textInsertRequested(const QString&); void fileOpenRequested(const QString& filePath, const QString& str, const QString& ctxt); public slots: void slotNewEntryDisplayed(); void slotNewEntryDisplayed(const DocPosition& pos); void slotSuggestionsCame(SelectJob*); void slotUseSuggestion(int); void slotRemoveSuggestion(int); void slotFileLoaded(const QString& url); void displayFromCache(); void slotBatchTranslate(); void slotBatchTranslateFuzzy(); private slots: //i think we do not wanna cache suggestions: //what if good sugg may be generated //from the entry user translated 1 minute ago? void slotBatchSelectDone(); void slotCacheSuggestions(SelectJob*); void initLater(); void contextMenu(const QPoint & pos); void removeEntry(const TMEntry & e); private: bool event(QEvent *event); + void deleteFile(const TMEntry& e, const bool showPopUp); private: TextBrowser* m_browser; Catalog* m_catalog; DocPosition m_pos; SelectJob* m_currentSelectJob; QVector m_actions_insert;//need them to get insertion shortcuts QVector m_actions_remove;//need them to get deletion shortcuts QList m_entries; QMap m_entryPositions; QString m_normTitle; QString m_hasInfoTitle; bool m_hasInfo; bool m_isBatching; bool m_markAsFuzzy; QMap > m_cache; DocPosition m_prevCachePos;//hacky hacky QVector m_jobs;//holds pointers to all the jobs for the current file }; class TextBrowser: public QTextBrowser { Q_OBJECT public: TextBrowser(QWidget* parent): QTextBrowser(parent) { setContextMenuPolicy(Qt::CustomContextMenu); } void mouseDoubleClickEvent(QMouseEvent* event); signals: void textInsertRequested(const QString&); }; CatalogString targetAdapted(const TMEntry& entry, const CatalogString& ref); } #endif