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"<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(); }