diff --git a/src/filesearch/filesearchtab.cpp b/src/filesearch/filesearchtab.cpp
index acb12f1..11058a6 100644
--- a/src/filesearch/filesearchtab.cpp
+++ b/src/filesearch/filesearchtab.cpp
@@ -1,922 +1,922 @@
/* ****************************************************************************
This file is part of Lokalize
Copyright (C) 2007-2014 by Nick Shaforostoff
2018-2019 by Simon Depiets
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 "filesearchtab.h"
#include "lokalize_debug.h"
#include "ui_filesearchoptions.h"
#include "ui_massreplaceoptions.h"
#include "project.h"
#include "prefs.h"
#include "tmscanapi.h" //TODO separate some decls into new header
#include "state.h"
#include "qaview.h"
#include "catalog.h"
#include "fastsizehintitemdelegate.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static QStringList doScanRecursive(const QDir& dir);
class FileListModel: public QStringListModel
{
public:
FileListModel(QObject* parent): QStringListModel(parent) {}
QVariant data(const QModelIndex& item, int role = Qt::DisplayRole) const override;
Qt::ItemFlags flags(const QModelIndex&) const override
{
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
};
QVariant FileListModel::data(const QModelIndex& item, int role) const
{
if (role == Qt::DisplayRole)
return shorterFilePath(stringList().at(item.row()));
if (role == Qt::UserRole)
return stringList().at(item.row());
return QVariant();
}
SearchFileListView::SearchFileListView(QWidget* parent)
: QDockWidget(i18nc("@title:window", "File List"), parent)
, m_browser(new QTreeView(this))
, m_background(new QLabel(i18n("Drop translation files here..."), this))
, m_model(new FileListModel(this))
{
setWidget(m_background);
m_background->setMinimumWidth(QFontMetrics(font()).averageCharWidth() * 30);
m_background->setAlignment(Qt::AlignCenter);
m_browser->hide();
m_browser->setModel(m_model);
m_browser->setRootIsDecorated(false);
m_browser->setHeaderHidden(true);
m_browser->setUniformRowHeights(true);
m_browser->setAlternatingRowColors(true);
m_browser->setContextMenuPolicy(Qt::ActionsContextMenu);
QAction* action = new QAction(i18nc("@action:inmenu", "Clear"), m_browser);
connect(action, &QAction::triggered, this, &SearchFileListView::clear);
m_browser->addAction(action);
connect(m_browser, &QTreeView::activated, this, &SearchFileListView::requestFileOpen);
}
void SearchFileListView::requestFileOpen(const QModelIndex& item)
{
emit fileOpenRequested(item.data(Qt::UserRole).toString(), true);
}
void SearchFileListView::addFiles(const QStringList& files)
{
if (files.isEmpty())
return;
m_background->hide();
setWidget(m_browser);
m_browser->show();
//ensure unquiness, sorting the list along the way
QMap map;
foreach (const QString& filepath, m_model->stringList())
map[filepath] = true;
foreach (const QString& filepath, files)
map[filepath] = true;
m_model->setStringList(map.keys());
}
void SearchFileListView::addFilesFast(const QStringList& files)
{
if (files.size())
m_model->setStringList(m_model->stringList() + files);
}
void SearchFileListView::clear()
{
m_model->setStringList(QStringList());
}
QStringList SearchFileListView::files() const
{
return m_model->stringList();
}
void SearchFileListView::scrollTo(const QString& file)
{
if (file.isEmpty()) {
m_browser->scrollToTop();
return;
}
int idx = m_model->stringList().indexOf(file);
if (idx != -1)
m_browser->scrollTo(m_model->index(idx, 0), QAbstractItemView::PositionAtCenter);
}
bool SearchParams::isEmpty() const
{
return sourcePattern.pattern().isEmpty()
&& targetPattern.pattern().isEmpty();
}
SearchJob::SearchJob(const QStringList& f, const SearchParams& sp, const QVector& r, int sn, QObject*)
: QRunnable()
, files(f)
, searchParams(sp)
, rules(r)
, searchNumber(sn)
, m_size(0)
{
setAutoDelete(false);
}
void SearchJob::run()
{
QTime a; a.start();
bool removeAmpFromSource = searchParams.sourcePattern.patternSyntax() == QRegExp::FixedString
&& !searchParams.sourcePattern.pattern().contains(QLatin1Char('&'));
bool removeAmpFromTarget = searchParams.targetPattern.patternSyntax() == QRegExp::FixedString
&& !searchParams.targetPattern.pattern().contains(QLatin1Char('&'));
foreach (const QString& filePath, files) {
Catalog catalog(0);
if (Q_UNLIKELY(catalog.loadFromUrl(filePath, QString(), &m_size, true) != 0))
continue;
//QVector catalogResults;
int numberOfEntries = catalog.numberOfEntries();
DocPosition pos(0);
for (; pos.entry < numberOfEntries; pos.entry++) {
//if (!searchParams.states[catalog.state(pos)])
// return false;
int lim = catalog.isPlural(pos.entry) ? catalog.numberOfPluralForms() : 1;
for (pos.form = 0; pos.form < lim; pos.form++) {
int sp = 0;
int tp = 0;
if (!searchParams.sourcePattern.isEmpty())
sp = searchParams.sourcePattern.indexIn(removeAmpFromSource ? catalog.source(pos).remove(QLatin1Char('&')) : catalog.source(pos));
if (!searchParams.targetPattern.isEmpty())
tp = searchParams.targetPattern.indexIn(removeAmpFromTarget ? catalog.target(pos).remove(QLatin1Char('&')) : catalog.target(pos));
//int np=searchParams.notesPattern.indexIn(catalog.notes(pos));
if ((sp != -1) != searchParams.invertSource && (tp != -1) != searchParams.invertTarget) {
//TODO handle multiple results in same column
//FileSearchResult r;
SearchResult r;
r.filepath = filePath;
r.docPos = pos;
if (!searchParams.sourcePattern.isEmpty() && !searchParams.invertSource)
r.sourcePositions << StartLen(searchParams.sourcePattern.pos(), searchParams.sourcePattern.matchedLength());
if (!searchParams.targetPattern.isEmpty() && !searchParams.invertTarget)
r.targetPositions << StartLen(searchParams.targetPattern.pos(), searchParams.targetPattern.matchedLength());
r.source = catalog.source(pos);
r.target = catalog.target(pos);
r.state = catalog.state(pos);
r.isApproved = catalog.isApproved(pos);
//r.activePhase=catalog.activePhase();
if (rules.size()) {
QVector positions(2);
int matchedQaRule = findMatchingRule(rules, r.source, r.target, positions);
if (matchedQaRule == -1)
continue;
if (positions.at(0).len)
r.sourcePositions << positions.at(0);
if (positions.at(1).len)
r.targetPositions << positions.at(1);
}
r.sourcePositions.squeeze();
r.targetPositions.squeeze();
//catalogResults< map;
for (int i = 0; i < searchResults.count(); ++i)
map.insertMulti(searchResults.at(i).filepath, i);
foreach (const QString& filepath, map.keys()) {
Catalog catalog(QThread::currentThread());
if (catalog.loadFromUrl(filepath, QString()) != 0)
continue;
foreach (int index, map.values(filepath)) {
SearchResult& sr = searchResults[index];
DocPosition docPos = sr.docPos.toDocPosition();
if (catalog.target(docPos) != sr.target) {
qCWarning(LOKALIZE_LOG) << "skipping replace because" << catalog.target(docPos) << "!=" << sr.target;
continue;
}
CatalogString s = catalog.targetWithTags(docPos);
int pos = replaceWhat.indexIn(s.string);
while (pos != -1) {
if (!s.string.midRef(pos, replaceWhat.matchedLength()).contains(TAGRANGE_IMAGE_SYMBOL)) {
docPos.offset = pos;
catalog.targetDelete(docPos, replaceWhat.matchedLength());
catalog.targetInsert(docPos, replaceWith);
s.string.replace(pos, replaceWhat.matchedLength(), replaceWith);
pos += replaceWith.length();
} else {
pos += replaceWhat.matchedLength();
qCWarning(LOKALIZE_LOG) << "skipping replace because matched text contains markup" << s.string;
}
if (pos > s.string.length() || replaceWhat.pattern().startsWith('^'))
break;
pos = replaceWhat.indexIn(s.string, pos);
}
}
catalog.save();
}
}
//BEGIN FileSearchModel
FileSearchModel::FileSearchModel(QObject* parent)
: QAbstractListModel(parent)
{
}
QVariant FileSearchModel::headerData(int section, Qt::Orientation, int role) const
{
if (role != Qt::DisplayRole)
return QVariant();
switch (section) {
case FileSearchModel::Source: return i18nc("@title:column Original text", "Source");
case FileSearchModel::Target: return i18nc("@title:column Text in target language", "Target");
//case FileSearchModel::Context: return i18nc("@title:column","Context");
case FileSearchModel::Filepath: return i18nc("@title:column", "File");
case FileSearchModel::TranslationStatus: return i18nc("@title:column", "Translation Status");
}
return QVariant();
}
void FileSearchModel::appendSearchResults(const SearchResults& results)
{
beginInsertRows(QModelIndex(), m_searchResults.size(), m_searchResults.size() + results.size() - 1);
m_searchResults += results;
endInsertRows();
}
void FileSearchModel::clear()
{
beginResetModel();
m_searchResults.clear();;
endResetModel();
}
QVariant FileSearchModel::data(const QModelIndex& item, int role) const
{
bool doHtml = (role == FastSizeHintItemDelegate::HtmlDisplayRole);
if (doHtml)
role = Qt::DisplayRole;
if (role == Qt::DisplayRole) {
QString result;
const SearchResult& sr = m_searchResults.at(item.row());
if (item.column() == Source)
result = sr.source;
if (item.column() == Target)
result = sr.target;
if (item.column() == Filepath)
result = shorterFilePath(sr.filepath);
if (doHtml && item.column() <= FileSearchModel::Target) {
if (result.isEmpty())
return result;
const QString startBld = QStringLiteral("_ST_");
const QString endBld = QStringLiteral("_END_");
const QString startBldTag = QStringLiteral("");
const QString endBldTag = QStringLiteral("");
if (item.column() == FileSearchModel::Target && !m_replaceWhat.isEmpty()) {
result.replace(m_replaceWhat, m_replaceWith);
QString escaped = convertToHtml(result, !sr.isApproved);
escaped.replace(startBld, startBldTag);
escaped.replace(endBld, endBldTag);
return escaped;
}
const QVector& occurrences = item.column() == FileSearchModel::Source ? sr.sourcePositions : sr.targetPositions;
int occ = occurrences.count();
while (--occ >= 0) {
const StartLen& sl = occurrences.at(occ);
result.insert(sl.start + sl.len, endBld);
result.insert(sl.start, startBld);
}
/* !isApproved(sr.state, Project::instance()->local()->role())*/
QString escaped = convertToHtml(result, item.column() == FileSearchModel::Target && !sr.isApproved);
escaped.replace(startBld, startBldTag);
escaped.replace(endBld, endBldTag);
return escaped;
}
return result;
}
if (role == Qt::UserRole) {
const SearchResult& sr = m_searchResults.at(item.row());
if (item.column() == Filepath)
return sr.filepath;
}
return QVariant();
}
void FileSearchModel::setReplacePreview(const QRegExp& s, const QString& r)
{
m_replaceWhat = s;
m_replaceWith = QLatin1String("_ST_") + r + QLatin1String("_END_");
emit dataChanged(index(0, Target), index(rowCount() - 1, Target));
}
//END FileSearchModel
//BEGIN FileSearchTab
FileSearchTab::FileSearchTab(QWidget *parent)
: LokalizeSubwindowBase2(parent)
// , m_proxyModel(new TMResultsSortFilterProxyModel(this))
, m_model(new FileSearchModel(this))
, m_lastSearchNumber(0)
, m_dbusId(-1)
{
setWindowTitle(i18nc("@title:window", "Search and replace in files"));
setAcceptDrops(true);
QWidget* w = new QWidget(this);
ui_fileSearchOptions = new Ui_FileSearchOptions;
ui_fileSearchOptions->setupUi(w);
setCentralWidget(w);
QShortcut* sh = new QShortcut(Qt::CTRL + Qt::Key_L, this);
connect(sh, &QShortcut::activated, ui_fileSearchOptions->querySource, QOverload<>::of(&QLineEdit::setFocus));
setFocusProxy(ui_fileSearchOptions->querySource);
sh = new QShortcut(Qt::Key_Escape, this, SLOT(stopSearch()), 0, Qt::WidgetWithChildrenShortcut);
QTreeView* view = ui_fileSearchOptions->treeView;
QVector singleLineColumns(FileSearchModel::ColumnCount, false);
singleLineColumns[FileSearchModel::Filepath] = true;
singleLineColumns[FileSearchModel::TranslationStatus] = true;
//singleLineColumns[TMDBModel::Context]=true;
QVector richTextColumns(FileSearchModel::ColumnCount, false);
richTextColumns[FileSearchModel::Source] = true;
richTextColumns[FileSearchModel::Target] = true;
view->setItemDelegate(new FastSizeHintItemDelegate(this, singleLineColumns, richTextColumns));
connect(m_model, &FileSearchModel::modelReset, (FastSizeHintItemDelegate*)view->itemDelegate(), &FastSizeHintItemDelegate::reset);
connect(m_model, &FileSearchModel::dataChanged, (FastSizeHintItemDelegate*)view->itemDelegate(), &FastSizeHintItemDelegate::reset);
//connect(m_model,SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)),view->itemDelegate(),SLOT(reset()));
//connect(m_proxyModel,SIGNAL(layoutChanged()),view->itemDelegate(),SLOT(reset()));
//connect(m_proxyModel,SIGNAL(layoutChanged()),this,SLOT(displayTotalResultCount()));
view->setContextMenuPolicy(Qt::ActionsContextMenu);
QAction* a = new QAction(i18n("Copy source to clipboard"), view);
a->setShortcut(Qt::CTRL + Qt::Key_S);
a->setShortcutContext(Qt::WidgetWithChildrenShortcut);
connect(a, &QAction::triggered, this, &FileSearchTab::copySourceToClipboard);
view->addAction(a);
a = new QAction(i18n("Copy target to clipboard"), view);
a->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Return));
a->setShortcutContext(Qt::WidgetWithChildrenShortcut);
connect(a, &QAction::triggered, this, &FileSearchTab::copyTargetToClipboard);
view->addAction(a);
a = new QAction(i18n("Open file"), view);
a->setShortcut(QKeySequence(Qt::Key_Return));
a->setShortcutContext(Qt::WidgetWithChildrenShortcut);
connect(a, &QAction::triggered, this, &FileSearchTab::openFile);
connect(view, &QTreeView::activated, this, &FileSearchTab::openFile);
view->addAction(a);
connect(ui_fileSearchOptions->querySource, &QLineEdit::returnPressed, this, &FileSearchTab::performSearch);
connect(ui_fileSearchOptions->queryTarget, &QLineEdit::returnPressed, this, &FileSearchTab::performSearch);
connect(ui_fileSearchOptions->doFind, &QPushButton::clicked, this, &FileSearchTab::performSearch);
// m_proxyModel->setDynamicSortFilter(true);
// m_proxyModel->setSourceModel(m_model);
view->setModel(m_model);
// view->setModel(m_proxyModel);
// view->sortByColumn(FileSearchModel::Filepath,Qt::AscendingOrder);
// view->setSortingEnabled(true);
// view->setItemDelegate(new FastSizeHintItemDelegate(this));
// connect(m_model,SIGNAL(resultsFetched()),view->itemDelegate(),SLOT(reset()));
// connect(m_model,SIGNAL(modelReset()),view->itemDelegate(),SLOT(reset()));
// connect(m_proxyModel,SIGNAL(layoutChanged()),view->itemDelegate(),SLOT(reset()));
// connect(m_proxyModel,SIGNAL(layoutChanged()),this,SLOT(displayTotalResultCount()));
//BEGIN resizeColumnToContents
static const int maxInitialWidths[] = {QGuiApplication::primaryScreen()->availableGeometry().width() / 3, QGuiApplication::primaryScreen()->availableGeometry().width() / 3};
int column = sizeof(maxInitialWidths) / sizeof(int);
while (--column >= 0)
view->setColumnWidth(column, maxInitialWidths[column]);
//END resizeColumnToContents
int i = 6;
while (--i > ID_STATUS_PROGRESS)
statusBarItems.insert(i, QString());
setXMLFile(QStringLiteral("filesearchtabui.rc"), true);
dbusObjectPath();
KActionCollection* ac = actionCollection();
KActionCategory* srf = new KActionCategory(i18nc("@title actions category", "Search and replace in files"), ac);
m_searchFileListView = new SearchFileListView(this);
//m_searchFileListView->hide();
addDockWidget(Qt::RightDockWidgetArea, m_searchFileListView);
srf->addAction(QStringLiteral("showfilelist_action"), m_searchFileListView->toggleViewAction());
connect(m_searchFileListView, &SearchFileListView::fileOpenRequested, this, QOverload::of(&FileSearchTab::fileOpenRequested));
m_massReplaceView = new MassReplaceView(this);
addDockWidget(Qt::RightDockWidgetArea, m_massReplaceView);
srf->addAction(QStringLiteral("showmassreplace_action"), m_massReplaceView->toggleViewAction());
connect(m_massReplaceView, &MassReplaceView::previewRequested, m_model, &FileSearchModel::setReplacePreview);
connect(m_massReplaceView, &MassReplaceView::replaceRequested, this, &FileSearchTab::massReplace);
//m_massReplaceView->hide();
m_qaView = new QaView(this);
m_qaView->hide();
addDockWidget(Qt::RightDockWidgetArea, m_qaView);
srf->addAction(QStringLiteral("showqa_action"), m_qaView->toggleViewAction());
connect(m_qaView, &QaView::rulesChanged, this, &FileSearchTab::performSearch);
connect(m_qaView->toggleViewAction(), &QAction::toggled, this, &FileSearchTab::performSearch, Qt::QueuedConnection);
view->header()->restoreState(readUiState("FileSearchResultsHeaderState"));
}
FileSearchTab::~FileSearchTab()
{
stopSearch();
writeUiState("FileSearchResultsHeaderState", ui_fileSearchOptions->treeView->header()->saveState());
ids.removeAll(m_dbusId);
}
void FileSearchTab::performSearch()
{
if (m_searchFileListView->files().isEmpty()) {
addFilesToSearch(doScanRecursive(QDir(Project::instance()->poDir())));
if (m_searchFileListView->files().isEmpty())
return;
}
m_model->clear();
statusBarItems.insert(1, QString());
m_searchFileListView->scrollTo();
m_lastSearchNumber++;
SearchParams sp;
sp.sourcePattern.setPattern(ui_fileSearchOptions->querySource->text());
sp.targetPattern.setPattern(ui_fileSearchOptions->queryTarget->text());
sp.invertSource = ui_fileSearchOptions->invertSource->isChecked();
sp.invertTarget = ui_fileSearchOptions->invertTarget->isChecked();
QVector rules = m_qaView->isVisible() ? m_qaView->rules() : QVector();
if (sp.isEmpty() && rules.isEmpty())
return;
if (!ui_fileSearchOptions->regEx->isChecked()) {
sp.sourcePattern.setPatternSyntax(QRegExp::FixedString);
sp.targetPattern.setPatternSyntax(QRegExp::FixedString);
}
/*
else
{
sp.sourcePattern.setMinimal(true);
sp.targetPattern.setMinimal(true);
}
*/
stopSearch();
m_massReplaceView->deactivatePreview();
QStringList files = m_searchFileListView->files();
for (int i = 0; i < files.size(); i += 100) {
QStringList batch;
int lim = qMin(files.size(), i + 100);
for (int j = i; j < lim; j++)
batch.append(files.at(j));
SearchJob* job = new SearchJob(batch, sp, rules, m_lastSearchNumber);
QObject::connect(job, &SearchJob::done, this, &FileSearchTab::searchJobDone);
QThreadPool::globalInstance()->start(job);
m_runningJobs.append(job);
}
}
void FileSearchTab::stopSearch()
{
#if QT_VERSION >= 0x050500
int i = m_runningJobs.size();
while (--i >= 0)
- QThreadPool::globalInstance()->cancel(m_runningJobs.at(i));
+ QThreadPool::globalInstance()->tryTake(m_runningJobs.at(i));
#endif
m_runningJobs.clear();
}
void FileSearchTab::massReplace(const QRegExp &what, const QString& with)
{
#define BATCH_SIZE 20
SearchResults searchResults = m_model->searchResults();
for (int i = 0; i < searchResults.count(); i += BATCH_SIZE) {
int last = qMin(i + BATCH_SIZE, searchResults.count() - 1);
QString filepath = searchResults.at(last).filepath;
while (last + 1 < searchResults.count() && filepath == searchResults.at(last + 1).filepath)
++last;
MassReplaceJob* job = new MassReplaceJob(searchResults.mid(i, last + 1 - i), i, what, with);
QObject::connect(job, &MassReplaceJob::done, this, &FileSearchTab::replaceJobDone);
QThreadPool::globalInstance()->start(job);
m_runningJobs.append(job);
}
}
static void copy(QTreeView* view, int column)
{
QApplication::clipboard()->setText(view->currentIndex().sibling(view->currentIndex().row(), column).data().toString());
}
void FileSearchTab::copySourceToClipboard()
{
copy(ui_fileSearchOptions->treeView, FileSearchModel::Source);
}
void FileSearchTab::copyTargetToClipboard()
{
copy(ui_fileSearchOptions->treeView, FileSearchModel::Target);
}
void FileSearchTab::openFile()
{
QModelIndex item = ui_fileSearchOptions->treeView->currentIndex();
SearchResult sr = m_model->searchResult(item);
DocPosition docPos = sr.docPos.toDocPosition();
int selection = 0;
if (sr.targetPositions.size()) {
docPos.offset = sr.targetPositions.first().start;
selection = sr.targetPositions.first().len;
}
qCDebug(LOKALIZE_LOG) << "fileOpenRequest" << docPos.offset << selection;
emit fileOpenRequested(sr.filepath, docPos, selection, true);
}
void FileSearchTab::fileSearchNext()
{
QModelIndex item = ui_fileSearchOptions->treeView->currentIndex();
int row = item.row();
int rowCount = m_model->rowCount();
if (++row >= rowCount) //ok if row was -1 (no solection)
return;
ui_fileSearchOptions->treeView->setCurrentIndex(item.sibling(row, item.column()));
openFile();
}
QStringList scanRecursive(const QList& urls)
{
QStringList result;
int i = urls.size();
while (--i >= 0) {
if (urls.at(i).isEmpty() || urls.at(i).path().isEmpty()) //NOTE is this a Qt bug?
continue;
QString path = urls.at(i).toLocalFile();
if (Catalog::extIsSupported(path))
result.append(path);
else
result += doScanRecursive(QDir(path));
}
return result;
}
//returns gross number of jobs started
static QStringList doScanRecursive(const QDir& dir)
{
QStringList result;
QStringList subDirs(dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable));
int i = subDirs.size();
while (--i >= 0)
result += doScanRecursive(QDir(dir.filePath(subDirs.at(i))));
QStringList filters = Catalog::supportedExtensions();
i = filters.size();
while (--i >= 0)
filters[i].prepend('*');
QStringList files(dir.entryList(filters, QDir::Files | QDir::NoDotAndDotDot | QDir::Readable));
i = files.size();
while (--i >= 0)
result.append(dir.filePath(files.at(i)));
return result;
}
void FileSearchTab::dragEnterEvent(QDragEnterEvent* event)
{
if (dragIsAcceptable(event->mimeData()->urls()))
event->acceptProposedAction();
}
void FileSearchTab::dropEvent(QDropEvent *event)
{
event->acceptProposedAction();
addFilesToSearch(scanRecursive(event->mimeData()->urls()));
}
void FileSearchTab::addFilesToSearch(const QStringList& files)
{
m_searchFileListView->addFiles(files);
performSearch();
}
void FileSearchTab::setSourceQuery(const QString& query)
{
ui_fileSearchOptions->querySource->setText(query);
}
void FileSearchTab::setTargetQuery(const QString& query)
{
ui_fileSearchOptions->queryTarget->setText(query);
}
void FileSearchTab::searchJobDone(SearchJob* j)
{
j->deleteLater();
if (j->searchNumber != m_lastSearchNumber)
return;
/*
SearchResults searchResults;
FileSearchResults::const_iterator i = j->results.constBegin();
while (i != j->results.constEnd())
{
foreach(const FileSearchResult& fsr, i.value())
{
SearchResult sr(fsr);
sr.filepath=i.key();
searchResults<appendSearchResults(searchResults);
*/
if (j->results.size()) {
m_model->appendSearchResults(j->results);
m_searchFileListView->scrollTo(j->results.last().filepath);
}
statusBarItems.insert(1, i18nc("@info:status message entries", "Total: %1", m_model->rowCount()));
//ui_fileSearchOptions->treeView->setFocus();
}
void FileSearchTab::replaceJobDone(MassReplaceJob* j)
{
j->deleteLater();
ui_fileSearchOptions->treeView->scrollTo(m_model->index(j->globalPos + j->searchResults.count(), 0));
}
//END FileSearchTab
//BEGIN MASS REPLACE
MassReplaceView::MassReplaceView(QWidget* parent)
: QDockWidget(i18nc("@title:window", "Mass replace"), parent)
, ui(new Ui_MassReplaceOptions)
{
QWidget* base = new QWidget(this);
setWidget(base);
ui->setupUi(base);
connect(ui->doPreview, &QPushButton::toggled, this, &MassReplaceView::requestPreview);
connect(ui->doReplace, &QPushButton::clicked, this, &MassReplaceView::requestReplace);
/*
QLabel* rl=new QLabel(i18n("Replace:"), base);
QLineEdit* searchEdit=new QLineEdit(base);
QHBoxLayout* searchL=new QHBoxLayout();
searchL->addWidget(rl);
searchL->addWidget(searchEdit);
QLabel* wl=new QLabel(i18n("With:"), base);
wl->setAlignment(Qt::AlignRight);
wl->setMinimumSize(rl->minimumSizeHint());
QLineEdit* replacementEdit=new QLineEdit(base);
QHBoxLayout* replacementL=new QHBoxLayout();
replacementL->addWidget(wl);
replacementL->addWidget(replacementEdit);
FlowLayout* fl=new FlowLayout();
fl->addItem(searchL);
fl->addItem(replacementL);
base->setLayout(fl);
*/
}
MassReplaceView::~MassReplaceView()
{
delete ui;
}
static QRegExp regExpFromUi(const QString& s, Ui_MassReplaceOptions* ui)
{
return QRegExp(s, ui->matchCase->isChecked() ? Qt::CaseSensitive : Qt::CaseInsensitive,
ui->useRegExps->isChecked() ? QRegExp::RegExp : QRegExp::FixedString);
}
void MassReplaceView::requestPreviewUpdate()
{
QString s = ui->searchText->text();
QString r = ui->replaceText->text();
if (s.length())
ui->doReplace->setEnabled(true);
emit previewRequested(regExpFromUi(s, ui), r);
}
void MassReplaceView::requestPreview(bool enable)
{
if (enable) {
connect(ui->searchText, &QLineEdit::textEdited, this, &MassReplaceView::requestPreviewUpdate);
connect(ui->replaceText, &QLineEdit::textEdited, this, &MassReplaceView::requestPreviewUpdate);
connect(ui->useRegExps, &QCheckBox::toggled, this, &MassReplaceView::requestPreviewUpdate);
connect(ui->matchCase, &QCheckBox::toggled, this, &MassReplaceView::requestPreviewUpdate);
requestPreviewUpdate();
} else {
disconnect(ui->searchText, &QLineEdit::textEdited, this, &MassReplaceView::requestPreviewUpdate);
disconnect(ui->replaceText, &QLineEdit::textEdited, this, &MassReplaceView::requestPreviewUpdate);
disconnect(ui->useRegExps, &QCheckBox::toggled, this, &MassReplaceView::requestPreviewUpdate);
disconnect(ui->matchCase, &QCheckBox::toggled, this, &MassReplaceView::requestPreviewUpdate);
emit previewRequested(QRegExp(), QString());
}
}
void MassReplaceView::requestReplace()
{
QString s = ui->searchText->text();
QString r = ui->replaceText->text();
if (s.isEmpty())
return;
emit replaceRequested(regExpFromUi(s, ui), r);
}
void MassReplaceView::deactivatePreview()
{
ui->doPreview->setChecked(false);
ui->doReplace->setEnabled(false);
}
#include "filesearchadaptor.h"
#include
QList FileSearchTab::ids;
//BEGIN DBus interface
QString FileSearchTab::dbusObjectPath()
{
QString FILESEARCH_PATH = QStringLiteral("/ThisIsWhatYouWant/FileSearch/");
if (m_dbusId == -1) {
new FileSearchAdaptor(this);
int i = 0;
while (i < ids.size() && i == ids.at(i))
++i;
ids.insert(i, i);
m_dbusId = i;
QDBusConnection::sessionBus().registerObject(FILESEARCH_PATH + QString::number(m_dbusId), this);
}
return FILESEARCH_PATH + QString::number(m_dbusId);
}
bool FileSearchTab::findGuiTextPackage(QString text, QString package)
{
setSourceQuery(text);
performSearch();
return true;
}
//END DBus interface
diff --git a/src/tm/jobs.h b/src/tm/jobs.h
index 2a42baf..0bfba38 100644
--- a/src/tm/jobs.h
+++ b/src/tm/jobs.h
@@ -1,494 +1,496 @@
/* ****************************************************************************
This file is part of Lokalize
Copyright (C) 2007-2014 by Nick Shaforostoff
2018-2019 by Simon Depiets
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License or (at your option) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
**************************************************************************** */
#ifndef JOBS_H
#define JOBS_H
#include "pos.h"
#include "tmentry.h"
#include
#include
#include
#include
#include
class QSqlQuery;
/**
* Translation Memory classes. see initDb() function for the database scheme
*/
namespace TM
{
#define TM_DATABASE_EXTENSION ".db"
#define REMOTETM_DATABASE_EXTENSION ".remotedb"
enum DbType {Local, Remote, Undefined}; //is needed only on opening
#define TM_AREA 8111
QThreadPool* threadPool();
#define CLOSEDB 10001
#define OPENDB 10000
#define TMTABSELECT 100
#define UPDATE 80
#define REMOVE 70
#define REMOVEFILE 69
#define INSERT 60
#define SELECT 50
#define BATCHSELECTFINISHED 49
#define IMPORT 30
#define EXPORT 25
#define REMOVEMISSINGFILES 11
#define SCAN 10
#define SCANFINISHED 9
struct TMConfig {
QString markup;
QString accel;
QString sourceLangCode;
QString targetLangCode;
};
void cancelAllJobs(); //HACK because threadweaver's dequeue is not workin'
//called on startup
class OpenDBJob: public QObject, public QRunnable
{
Q_OBJECT
public:
struct ConnectionParams {
QString driver, host, db, user, passwd;
bool isFilled()
{
return !host.isEmpty() && !db.isEmpty() && !user.isEmpty();
}
};
explicit OpenDBJob(const QString& dbName, DbType type = TM::Local, bool reconnect = false, const ConnectionParams& connParams = ConnectionParams());
~OpenDBJob() override = default;
int priority() const
{
return OPENDB;
}
struct DBStat {
int pairsCount, uniqueSourcesCount, uniqueTranslationsCount;
DBStat(): pairsCount(0), uniqueSourcesCount(0), uniqueTranslationsCount(0) {}
};
protected:
void run() override;
signals:
void done(OpenDBJob*);
public:
QString m_dbName;
DbType m_type;
//statistics
DBStat m_stat;
//for the new DB creation
TMConfig m_tmConfig;
bool m_setParams;
bool m_connectionSuccessful;
bool m_reconnect;
ConnectionParams m_connParams;
};
//called on startup
class CloseDBJob: public QObject, public QRunnable
{
Q_OBJECT
public:
explicit CloseDBJob(const QString& dbName);
~CloseDBJob();
int priority() const
{
return CLOSEDB;
}
QString dbName()
{
return m_dbName;
}
signals:
void done(CloseDBJob*);
protected:
void run() override;
QString m_dbName;
//statistics?
};
class SelectJob: public QObject, public QRunnable
{
Q_OBJECT
public:
SelectJob(const CatalogString& source,
const QString& ctxt,
const QString& file,
const DocPosition&,//for back tracking
const QString& dbName);
~SelectJob();
int priority() const
{
return SELECT;
}
signals:
void done(SelectJob*);
protected:
void run() override;
//void aboutToBeDequeued(ThreadWeaver::WeaverInterface*); KDE5PORT
private:
//returns true if seen translation with >85%
bool doSelect(QSqlDatabase&,
QStringList& words,
bool isShort);
public:
CatalogString m_source;
private:
QString m_ctxt;
QString m_file;
bool m_dequeued;
public:
DocPosition m_pos;
QList m_entries;
QString m_dbName;
};
enum {Enqueue = 1};
SelectJob* initSelectJob(Catalog*, DocPosition pos, QString db = QString(), int opt = Enqueue);
class RemoveMissingFilesJob: public QObject, public QRunnable
{
Q_OBJECT
public:
explicit RemoveMissingFilesJob(const QString& dbName);
~RemoveMissingFilesJob();
int priority() const
{
return REMOVEMISSINGFILES;
}
protected:
void run() override;
QString m_dbName;
signals:
void done();
};
class RemoveFileJob: public QObject, public QRunnable
{
Q_OBJECT
public:
explicit RemoveFileJob(const QString& filePath, const QString& dbName, QObject *parent = nullptr);
~RemoveFileJob();
int priority() const
{
return REMOVEFILE;
}
protected:
void run() override;
QString m_filePath;
QString m_dbName;
QObject m_parent;
signals:
void done();
};
class RemoveJob: public QObject, public QRunnable
{
Q_OBJECT
public:
explicit RemoveJob(const TMEntry& entry);
~RemoveJob();
int priority() const
{
return REMOVE;
}
protected:
void run() override;
TMEntry m_entry;
signals:
void done();
};
/**
* used to eliminate a lot of duplicate entries
*
* it is supposed to run on entry switch/file close in Editor
**/
//TODO a mechanism to get rid of dead dups (use strigi?).
//also, display usage of different translations and suggest user
//to use only one of them (listview, checkboxes)
class UpdateJob: public QRunnable
{
public:
explicit UpdateJob(const QString& filePath,
const QString& ctxt,
const CatalogString& en,
const CatalogString& newTarget,
int form,
bool approved,
//const DocPosition&,//for back tracking
const QString& dbName);
~UpdateJob() {}
int priority() const
{
return UPDATE;
}
protected:
void run() override;
private:
QString m_filePath;
QString m_ctxt;
CatalogString m_english;
CatalogString m_newTarget;
int m_form;
bool m_approved;
QString m_dbName;
};
//scan one file
class ScanJob: public QRunnable
{
public:
explicit ScanJob(const QString& filePath, const QString& dbName);
~ScanJob() override = default;
int priority() const
{
return SCAN;
}
protected:
void run() override;
public:
QString m_filePath;
//statistics
ushort m_time;
ushort m_added;
ushort m_newVersions;//e1.english==e2.english, e1.target!=e2.target
int m_size;
QString m_dbName;
};
class ScanJobFeedingBack: public QObject, public ScanJob
{
Q_OBJECT
public:
explicit ScanJobFeedingBack(const QString& filePath,
const QString& dbName)
: QObject(), ScanJob(filePath, dbName)
- {}
+ {
+ setAutoDelete(false);
+ }
protected:
void run() override
{
ScanJob::run();
emit done(this);
}
signals:
void done(ScanJobFeedingBack*);
};
//helper
class BatchSelectFinishedJob: public QObject, public QRunnable
{
Q_OBJECT
public:
explicit BatchSelectFinishedJob(QWidget* view)
: QObject(), QRunnable()
, m_view(view)
{}
~BatchSelectFinishedJob() override = default;
int priority() const
{
return BATCHSELECTFINISHED;
}
signals:
void done();
protected:
void run() override
{
emit done();
}
public:
QWidget* m_view;
};
#if 0
we use index stored in db now...
//create index --called on startup
class IndexWordsJob: public QRunnable
{
Q_OBJECT
public:
IndexWordsJob(QObject* parent = nullptr);
~IndexWordsJob();
int priority() const
{
return 100;
}
protected:
void run();
public:
TMWordHash m_tmWordHash;
//statistics?
};
#endif
class ImportTmxJob: public QRunnable
{
public:
explicit ImportTmxJob(const QString& url,
const QString& dbName);
~ImportTmxJob();
int priority() const
{
return IMPORT;
}
protected:
void run() override;
public:
QString m_filename;
//statistics
ushort m_time;
QString m_dbName;
};
// #if 0
class ExportTmxJob: public QRunnable
{
public:
explicit ExportTmxJob(const QString& url,
const QString& dbName);
~ExportTmxJob();
int priority() const
{
return IMPORT;
}
protected:
void run() override;
public:
QString m_filename;
//statistics
ushort m_time;
QString m_dbName;
};
// #endif
class ExecQueryJob: public QObject, public QRunnable
{
Q_OBJECT
public:
explicit ExecQueryJob(const QString& queryString, const QString& dbName, QMutex *dbOperation);
~ExecQueryJob();
int priority() const
{
return TMTABSELECT;
}
QSqlQuery* query;
signals:
void done(ExecQueryJob*);
protected:
void run() override;
QString m_dbName;
QString m_query;
QMutex* m_dbOperationMutex;
//statistics?
};
}
#endif
diff --git a/src/tm/tmscanapi.cpp b/src/tm/tmscanapi.cpp
index 7616c28..baeeea7 100644
--- a/src/tm/tmscanapi.cpp
+++ b/src/tm/tmscanapi.cpp
@@ -1,184 +1,184 @@
/* ****************************************************************************
This file is part of Lokalize
Copyright (C) 2007-2014 by Nick Shaforostoff
2018-2019 by Simon Depiets
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 "tmscanapi.h"
#include "lokalize_debug.h"
#include "jobs.h"
#include "catalog.h"
#include "prefs_lokalize.h"
#include "gettextheader.h"
#include "dbfilesmodel.h"
#include "project.h"
#include
#include
#include
#include
namespace TM
{
static QVector doScanRecursive(const QDir& dir, const QString& dbName, KJob* metaJob);
}
using namespace TM;
RecursiveScanJob::RecursiveScanJob(const QString& dbName, QObject* parent)
: KJob(parent)
, m_dbName(dbName)
{
setCapabilities(KJob::Killable);
}
bool RecursiveScanJob::doKill()
{
#if QT_VERSION >= 0x050500
foreach (ScanJob* job, m_jobs)
- TM::threadPool()->cancel(job);
+ TM::threadPool()->tryTake(job);
#endif
return true;
}
void RecursiveScanJob::setJobs(const QVector& jobs)
{
m_jobs = jobs;
setTotalAmount(KJob::Files, jobs.size());
if (!jobs.size())
kill(KJob::EmitResult);
}
void RecursiveScanJob::scanJobFinished(ScanJobFeedingBack* j)
{
ScanJob* job = static_cast(j);
setProcessedAmount(KJob::Files, processedAmount(KJob::Files) + 1);
emitPercent(processedAmount(KJob::Files), totalAmount(KJob::Files));
setProcessedAmount(KJob::Bytes, processedAmount(KJob::Bytes) + job->m_size);
if (m_time.elapsed()) emitSpeed(1000 * processedAmount(KJob::Bytes) / m_time.elapsed());
}
void RecursiveScanJob::scanJobDestroyed()
{
m_destroyedJobs += 1;
if (m_destroyedJobs == totalAmount(KJob::Files)) {
emitResult();
}
}
void RecursiveScanJob::start()
{
m_time.start();
emit description(this,
i18n("Adding files to Lokalize translation memory"),
qMakePair(i18n("TM"), m_dbName));
}
int TM::scanRecursive(const QStringList& filePaths, const QString& dbName)
{
RecursiveScanJob* metaJob = new RecursiveScanJob(dbName);
KIO::getJobTracker()->registerJob(metaJob);
metaJob->start();
if (!askAuthorInfoIfEmpty())
return 0;
QVector result;
int i = filePaths.size();
while (--i >= 0) {
const QString& filePath = filePaths.at(i);
if (filePath.isEmpty())
continue;
if (Catalog::extIsSupported(filePath)) {
ScanJobFeedingBack* job = new ScanJobFeedingBack(filePath, dbName);
QObject::connect(job, &ScanJobFeedingBack::done, metaJob, &RecursiveScanJob::scanJobFinished);
QObject::connect(job, &QObject::destroyed, metaJob, &RecursiveScanJob::scanJobDestroyed);
TM::threadPool()->start(job, SCAN);
result.append(job);
} else
result += doScanRecursive(QDir(filePath), dbName, metaJob);
}
metaJob->setJobs(result);
DBFilesModel::instance()->openDB(dbName); //update stats after it finishes
return result.size();
}
//returns gross number of jobs started
static QVector TM::doScanRecursive(const QDir& dir, const QString& dbName, KJob* metaJob)
{
QVector result;
QStringList subDirs(dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable));
int i = subDirs.size();
while (--i >= 0)
result += TM::doScanRecursive(QDir(dir.filePath(subDirs.at(i))), dbName, metaJob);
QStringList filters = Catalog::supportedExtensions();
i = filters.size();
while (--i >= 0)
filters[i].prepend('*');
QStringList files(dir.entryList(filters, QDir::Files | QDir::NoDotAndDotDot | QDir::Readable));
i = files.size();
while (--i >= 0) {
ScanJobFeedingBack* job = new ScanJobFeedingBack(dir.filePath(files.at(i)), dbName);
QObject::connect(job, &ScanJobFeedingBack::done, (RecursiveScanJob*)metaJob, &RecursiveScanJob::scanJobFinished);
TM::threadPool()->start(job, SCAN);
result.append(job);
}
return result;
}
bool dragIsAcceptable(const QList& urls)
{
int i = urls.size();
while (--i >= 0) {
bool ok = Catalog::extIsSupported(urls.at(i).path());
if (!ok) {
QFileInfo info(urls.at(i).toLocalFile());
ok = info.exists() && info.isDir();
}
if (ok)
return true;
}
return false;
}
QString shorterFilePath(const QString path)
{
if (!Project::instance()->isLoaded())
return path;
QString pDir = Project::instance()->projectDir();
if (path.startsWith(pDir))//TODO cache projectDir?
return QDir(pDir).relativeFilePath(path);
return path;
}
diff --git a/src/tm/tmview.cpp b/src/tm/tmview.cpp
index 6e019f9..1e3735a 100644
--- a/src/tm/tmview.cpp
+++ b/src/tm/tmview.cpp
@@ -1,1022 +1,1022 @@
/* ****************************************************************************
This file is part of Lokalize
Copyright (C) 2007-2014 by Nick Shaforostoff
2018-2019 by Simon Depiets
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
#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::Window);
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));
+ TM::threadPool()->tryTake(m_jobs.at(i));
#endif
}
void TMView::initLater()
{
setAcceptDrops(true);
int i = m_actions_insert.size();
while (--i >= 0) {
connect(m_actions_insert.at(i), &QAction::triggered, this, [this, i] { slotUseSuggestion(i); });
}
i = m_actions_remove.size();
while (--i >= 0) {
connect(m_actions_remove.at(i), &QAction::triggered, this, [this, i] { slotRemoveSuggestion(i); });
}
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));
+ TM::threadPool()->tryTake(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);
+ TM::threadPool()->tryTake(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;
std::sort(job.m_entries.begin(), job.m_entries.end(), std::greater());
const int limit = qMin(Settings::suggCount(), job.m_entries.size());
const int minScore = Settings::suggScore() * 100;
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));
html += QStringLiteral(" ");
html += QString(i18ncp("%1 is the number of times this TM entry has been found", "(1 time)", "(%1 times)", entry.hits));
html += QStringLiteral("/ ");
//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, true);
} 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('+');
qCDebug(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;
qCDebug(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();
}