diff --git a/src/tm/jobs.cpp b/src/tm/jobs.cpp
index 54761e6..1a3f897 100644
--- a/src/tm/jobs.cpp
+++ b/src/tm/jobs.cpp
@@ -1,2130 +1,2133 @@
/* ****************************************************************************
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) << "doInsertEntry: 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) << "doInsertEntry: 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) << "doInsertEntry: 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"< there will be new record insertion and main table update below
}
//qCDebug(LOKALIZE_LOG)< 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 minScore = Settings::suggScore() * 100;
- int i = m_entries.size();
- while (--i >= limit || m_entries.last().score < minScore)
+ const int limit = qMin(Settings::suggCount(), m_entries.size());
+ const int minScore = Settings::suggScore() * 100;
+ int i = m_entries.size() - 1;
+ while (i >= 0 && (i >= limit || m_entries.last().score < minScore))
+ {
m_entries.removeLast();
+ i--;
+ }
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/tmview.cpp b/src/tm/tmview.cpp
index aa1e824..2500150 100644
--- a/src/tm/tmview.cpp
+++ b/src/tm/tmview.cpp
@@ -1,1021 +1,1024 @@
/* ****************************************************************************
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());
const int limit = qMin(Settings::suggCount(), job.m_entries.size());
const int minScore = Settings::suggScore() * 100;
- int i = job.m_entries.size();
- while (--i >= limit || job.m_entries.last().score < minScore)
+ int i = job.m_entries.size() - 1;
+ while (i >= 0 && (i >= limit || job.m_entries.last().score < minScore))
+ {
job.m_entries.removeLast();
+ i--;
+ }
} else if (job.m_entries.isEmpty() || job.m_entries.first().score < 8500) {
//be careful, as we switched to QDirModel!
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;
const 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 does not 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, 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();
}