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"<exec();
qCDebug(LOKALIZE_LOG) << "ExecQueryJob done" << query->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