25 #include "ui_filesearchoptions.h"
26 #include "ui_massreplaceoptions.h"
37 #include <QApplication>
38 #include <QDesktopWidget>
42 #include <QDragEnterEvent>
43 #include <QSortFilterProxyModel>
44 #include <QStyledItemDelegate>
45 #include <QStringBuilder>
47 #include <QTextDocument>
48 #include <QHeaderView>
49 #include <QStringListModel>
52 #include <KColorScheme>
53 #include <kactioncategory.h>
56 #include <kstandarddirs.h>
57 #include <kxmlguifactory.h>
58 #include <threadweaver/Job.h>
59 #include <threadweaver/JobCollection.h>
60 #include <threadweaver/ThreadWeaver.h>
61 #include <threadweaver/Thread.h>
72 QVariant data(
const QModelIndex& item,
int role=Qt::DisplayRole)
const;
75 QVariant FileListModel::data(
const QModelIndex& item,
int role)
const
77 if (role==Qt::DisplayRole)
83 :
QDockWidget( i18nc(
"@title:window",
"File List"), parent)
85 , m_background(new
QLabel(i18n(
"Drop translation files here..."), this))
86 , m_model(new FileListModel(this))
88 setWidget(m_background);
89 m_background->setMinimumWidth(QApplication::desktop()->width()/4);
90 m_background->setAlignment(Qt::AlignCenter);
92 m_browser->setModel(m_model);
93 m_browser->setRootIsDecorated(
false);
94 m_browser->setHeaderHidden(
true);
95 m_browser->setUniformRowHeights(
true);
96 m_browser->setAlternatingRowColors(
true);
99 m_browser->setContextMenuPolicy(Qt::ActionsContextMenu);
101 QAction* action=
new QAction(i18nc(
"@action:inmenu",
"Clear"), m_browser);
102 connect(action, SIGNAL(triggered()),
this, SLOT(
clear()));
103 m_browser->addAction(action);
111 m_background->hide();
112 setWidget(m_browser);
116 QMap<QString, bool> map;
117 foreach(
const QString& filepath, m_model->stringList())
119 foreach(
const QString& filepath, files)
122 m_model->setStringList(map.keys());
128 m_model->setStringList(m_model->stringList()+
files);
133 m_model->setStringList(QStringList());
139 return m_model->stringList();
146 m_browser->scrollToTop();
149 int idx=m_model->stringList().indexOf(file);
151 m_browser->scrollTo(m_model->index(idx, 0), QAbstractItemView::PositionAtCenter);
157 QRegExp sourcePattern;
158 QRegExp targetPattern;
159 QRegExp notesPattern;
163 bool isEmpty()
const;
166 bool SearchParams::isEmpty()
const
168 return sourcePattern.pattern().isEmpty()
169 && targetPattern.pattern().isEmpty();
175 class SearchJob:
public ThreadWeaver::Job
178 explicit SearchJob(
const QStringList& f,
179 const SearchParams& sp,
180 const QVector<Rule>& r,
189 SearchParams searchParams;
199 SearchJob::SearchJob(
const QStringList& f,
const SearchParams& sp,
const QVector<Rule>& r,
int sn,
QObject* parent)
200 : ThreadWeaver::Job(parent)
208 void SearchJob::run()
211 foreach(
const QString& path, files)
214 if (KDE_ISUNLIKELY(catalog.loadFromUrl(KUrl::fromPath(path), KUrl(), &m_size)!=0))
218 int numberOfEntries=catalog.numberOfEntries();
220 for (;pos.entry<numberOfEntries;pos.entry++)
224 int lim=catalog.isPlural(pos.entry)?catalog.numberOfPluralForms():1;
225 for (pos.form=0;pos.form<lim;pos.form++)
229 if (!searchParams.sourcePattern.isEmpty())
230 sp=searchParams.sourcePattern.indexIn(catalog.source(pos));
231 if (!searchParams.targetPattern.isEmpty())
232 tp=searchParams.targetPattern.indexIn(catalog.target(pos));
235 if (sp!=-1 && tp!=-1)
242 if (!searchParams.sourcePattern.isEmpty())
244 if (!searchParams.targetPattern.isEmpty())
246 r.
source=catalog.source(pos);
247 r.
target=catalog.target(pos);
248 r.
state=catalog.state(pos);
253 QVector<StartLen> positions(2);
255 if (matchedQaRule==-1)
257 if (positions.at(0).len)
259 if (positions.at(1).len)
273 qDebug()<<
"done in"<<a.elapsed();
277 class MassReplaceJob:
public ThreadWeaver::Job
297 MassReplaceJob::MassReplaceJob(
const SearchResults& srs,
int pos,
const QRegExp& s,
const QString& r,
QObject* parent)
298 : ThreadWeaver::Job(parent)
306 void MassReplaceJob::run()
308 QMultiHash<QString, int> map;
309 for (
int i=0;i<searchResults.count();++i)
310 map.insertMulti(searchResults.at(i).filepath, i);
312 foreach(
const QString& filepath, map.keys())
315 if (catalog.loadFromUrl(KUrl::fromPath(filepath), KUrl())!=0)
318 foreach(
int index, map.values(filepath))
325 s.
string.replace(replaceWhat, replaceWith);
342 if (role!=Qt::DisplayRole)
357 beginInsertRows(QModelIndex(), m_searchResults.size(), m_searchResults.size()+results.size()-1);
358 m_searchResults+=results;
365 m_searchResults.clear();;
373 role=Qt::DisplayRole;
375 if (role==Qt::DisplayRole)
379 if (item.column()==
Source)
381 if (item.column()==
Target)
388 if (result.isEmpty())
391 static QString startBld=
"_ST_";
392 static QString endBld=
"_END_";
393 static QString startBldTag=
"<b >";
394 static QString endBldTag=
"</b >";
398 result.replace(m_replaceWhat, m_replaceWith);
400 escaped.replace(startBld, startBldTag);
401 escaped.replace(endBld, endBldTag);
406 int occ=occurences.count();
409 const StartLen& sl=occurences.at(occ);
410 result.insert(sl.
start+sl.
len, endBld);
411 result.insert(sl.
start, startBld);
416 escaped.replace(startBld, startBldTag);
417 escaped.replace(endBld, endBldTag);
424 if (role==Qt::UserRole)
437 m_replaceWith=
"_ST_" % r %
"_END_";
450 , m_lastSearchNumber(0)
453 setWindowTitle(i18nc(
"@title:window",
"Search and replace in files"));
454 setAcceptDrops(
true);
457 ui_fileSearchOptions=
new Ui_FileSearchOptions;
458 ui_fileSearchOptions->setupUi(w);
462 QShortcut* sh=
new QShortcut(Qt::CTRL+Qt::Key_L,
this);
463 connect(sh,SIGNAL(activated()),ui_fileSearchOptions->querySource,SLOT(setFocus()));
464 setFocusProxy(ui_fileSearchOptions->querySource);
466 sh=
new QShortcut(Qt::Key_Escape,
this,SLOT(
stopSearch()),0,Qt::WidgetWithChildrenShortcut);
468 QTreeView* view=ui_fileSearchOptions->treeView;
479 connect(m_model,SIGNAL(modelReset()),view->itemDelegate(),SLOT(reset()));
480 connect(m_model,SIGNAL(dataChanged(QModelIndex,QModelIndex)),view->itemDelegate(),SLOT(reset()));
485 view->setContextMenuPolicy(Qt::ActionsContextMenu);
487 QAction* a=
new QAction(i18n(
"Copy source to clipboard"),view);
488 a->setShortcut(Qt::CTRL + Qt::Key_S);
489 a->setShortcutContext(Qt::WidgetWithChildrenShortcut);
490 connect(a,SIGNAL(activated()),
this, SLOT(
copySource()));
493 a=
new QAction(i18n(
"Copy target to clipboard"),view);
494 a->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Return));
495 a->setShortcutContext(Qt::WidgetWithChildrenShortcut);
496 connect(a,SIGNAL(activated()),
this, SLOT(
copyTarget()));
499 a=
new QAction(i18n(
"Open file"),view);
500 a->setShortcut(QKeySequence(Qt::Key_Return));
501 a->setShortcutContext(Qt::WidgetWithChildrenShortcut);
502 connect(a,SIGNAL(activated()),
this, SLOT(
openFile()));
503 connect(view,SIGNAL(activated(QModelIndex)),
this, SLOT(
openFile()));
506 connect(ui_fileSearchOptions->querySource,SIGNAL(returnPressed()),
this,SLOT(
performSearch()));
507 connect(ui_fileSearchOptions->queryTarget,SIGNAL(returnPressed()),
this,SLOT(
performSearch()));
508 connect(ui_fileSearchOptions->doFind,SIGNAL(clicked()),
this,SLOT(
performSearch()));
512 view->setModel(m_model);
524 static const int maxInitialWidths[]={QApplication::desktop()->availableGeometry().width()/3,QApplication::desktop()->availableGeometry().width()/3};
525 int column=
sizeof(maxInitialWidths)/
sizeof(
int);
527 view->setColumnWidth(column, maxInitialWidths[column]);
534 setXMLFile(
"filesearchtabui.rc",
true);
539 KActionCollection* ac=actionCollection();
540 KActionCategory* srf=
new KActionCategory(i18nc(
"@title actions category",
"Search and replace in files"), ac);
544 addDockWidget(Qt::RightDockWidgetArea, m_searchFileListView);
545 srf->addAction( QLatin1String(
"showfilelist_action"), m_searchFileListView->toggleViewAction() );
548 addDockWidget(Qt::RightDockWidgetArea, m_massReplaceView);
549 srf->addAction( QLatin1String(
"showmassreplace_action"), m_massReplaceView->toggleViewAction() );
550 connect(m_massReplaceView, SIGNAL(previewRequested(QRegExp,QString)), m_model, SLOT(setReplacePreview(QRegExp ,QString)));
551 connect(m_massReplaceView, SIGNAL(replaceRequested(QRegExp,QString)),
this, SLOT(
massReplace(QRegExp,QString)));
554 m_qaView =
new QaView(
this);
556 addDockWidget(Qt::RightDockWidgetArea, m_qaView);
557 srf->addAction( QLatin1String(
"showqa_action"), m_qaView->toggleViewAction() );
559 connect(m_qaView, SIGNAL(rulesChanged()),
this, SLOT(
performSearch()));
560 connect(m_qaView->toggleViewAction(), SIGNAL(toggled(
bool)),
this, SLOT(
performSearch()), Qt::QueuedConnection);
563 KConfigGroup cg(&config,
"MainWindow");
564 view->header()->restoreState(QByteArray::fromBase64( cg.readEntry(
"FileSearchResultsHeaderState", QByteArray()) ));
572 KConfigGroup cg(&config,
"MainWindow");
573 cg.writeEntry(
"FileSearchResultsHeaderState",ui_fileSearchOptions->treeView->header()->saveState().toBase64());
575 ids.removeAll(m_dbusId);
580 if (m_searchFileListView->
files().isEmpty())
583 if (m_searchFileListView->
files().isEmpty())
591 m_lastSearchNumber++;
594 sp.sourcePattern.setPattern(ui_fileSearchOptions->querySource->text());
595 sp.targetPattern.setPattern(ui_fileSearchOptions->queryTarget->text());
597 QVector<Rule> rules=m_qaView->isVisible()?m_qaView->
rules():QVector<Rule>();
599 if (sp.isEmpty() && rules.isEmpty())
602 if (!ui_fileSearchOptions->regEx->isChecked())
604 sp.sourcePattern.setPatternSyntax(QRegExp::FixedString);
605 sp.targetPattern.setPatternSyntax(QRegExp::FixedString);
618 QStringList files=m_searchFileListView->
files();
619 for(
int i=0; i<files.size();i+=100)
622 int lim=qMin(files.size(), i+100);
623 for(
int j=i; j<lim;j++)
624 batch.append(files.at(j));
626 SearchJob* job=
new SearchJob(batch, sp, rules, m_lastSearchNumber);
627 QObject::connect(job,SIGNAL(done(ThreadWeaver::Job*)),job,SLOT(deleteLater()));
628 QObject::connect(job,SIGNAL(done(ThreadWeaver::Job*)),
this,SLOT(searchJobDone(ThreadWeaver::Job*)));
629 ThreadWeaver::Weaver::instance()->enqueue(job);
630 m_runningJobs.append(job);
636 QVector<ThreadWeaver::Job*>::const_iterator it;
637 for (it = m_runningJobs.constBegin(); it != m_runningJobs.constEnd(); ++it)
638 ThreadWeaver::Weaver::instance()->dequeue(*it);
639 m_runningJobs.clear();
645 #define BATCH_SIZE 20
649 for (
int i=0;i<searchResults.count();i+=
BATCH_SIZE)
651 int last=qMin(i+
BATCH_SIZE, searchResults.count()-1);
652 QString filepath=searchResults.at(last).filepath;
653 while (last+1<searchResults.count() && filepath==searchResults.at(last+1).filepath)
656 MassReplaceJob* job=
new MassReplaceJob(searchResults.mid(i, last+1-i), i, what, with);
657 QObject::connect(job,SIGNAL(done(ThreadWeaver::Job*)),job,SLOT(deleteLater()));
658 QObject::connect(job,SIGNAL(done(ThreadWeaver::Job*)),
this,SLOT(replaceJobDone(ThreadWeaver::Job*)));
659 ThreadWeaver::Weaver::instance()->enqueue(job);
660 m_runningJobs.append(job);
667 QApplication::clipboard()->setText( view->currentIndex().sibling(view->currentIndex().row(),column).data().toString());
682 QModelIndex item=ui_fileSearchOptions->treeView->currentIndex();
691 kDebug()<<
"fileOpenRequest"<<docPos.
offset<<selection;
697 QModelIndex item=ui_fileSearchOptions->treeView->currentIndex();
704 ui_fileSearchOptions->treeView->setCurrentIndex(item.sibling(row, item.column()));
716 if (urls.at(i).isEmpty() || urls.at(i).path().isEmpty() )
718 QString path=urls.at(i).toLocalFile();
732 QStringList subDirs(dir.entryList(QDir::Dirs|QDir::NoDotAndDotDot|QDir::Readable));
733 int i=subDirs.size();
740 filters[i].prepend(
'*');
741 QStringList files(dir.entryList(filters,QDir::Files|QDir::NoDotAndDotDot|QDir::Readable));
745 result.append(dir.filePath(files.at(i)));
751 void FileSearchTab::dragEnterEvent(QDragEnterEvent* event)
754 event->acceptProposedAction();
757 void FileSearchTab::dropEvent(QDropEvent *event)
759 event->acceptProposedAction();
765 m_searchFileListView->
addFiles(files);
769 void FileSearchTab::searchJobDone(ThreadWeaver::Job* job)
771 SearchJob* j=
static_cast<SearchJob*
>(job);
772 if (j->searchNumber!=m_lastSearchNumber)
795 if (j->results.size())
796 m_searchFileListView->
scrollTo(j->results.last().filepath);
801 void FileSearchTab::replaceJobDone(ThreadWeaver::Job* job)
803 MassReplaceJob* j=
static_cast<MassReplaceJob*
>(job);
804 ui_fileSearchOptions->treeView->scrollTo(m_model->index(j->globalPos+j->searchResults.count(), 0));
814 :
QDockWidget(i18nc(
"@title:window",
"Mass replace"), parent)
815 , ui(new Ui_MassReplaceOptions)
821 connect(ui->doPreview, SIGNAL(toggled(
bool)),
this, SLOT(requestPreview(
bool)));
822 connect(ui->doReplace, SIGNAL(clicked(
bool)),
this, SLOT(requestReplace()));
850 void MassReplaceView::requestPreview(
bool enable)
855 s=ui->searchText->text();
856 r=ui->replaceText->text();
859 ui->doReplace->setEnabled(
true);
865 void MassReplaceView::requestReplace()
867 QString s=ui->searchText->text();
868 QString r=ui->replaceText->text();
878 ui->doPreview->setChecked(
false);
879 ui->doReplace->setEnabled(
false);
883 #include "filesearchadaptor.h"
884 #include <qdbusconnection.h>
886 QList<int> FileSearchTab::ids;
894 new FileSearchAdaptor(
this);
897 while(i<ids.size()&&i==ids.at(i))
901 QDBusConnection::sessionBus().registerObject(
"/ThisIsWhatYouWant/FileSearch/" + QString::number(m_dbusId),
this);
904 return "/ThisIsWhatYouWant/FileSearch/" + QString::number(m_dbusId);
910 #include "filesearchtab.moc"
QVector< Rule > rules() const
void massReplace(const QRegExp &what, const QString &with)
QVector< StartLen > targetPositions
bool dragIsAcceptable(const QList< QUrl > &urls)
SearchResult searchResult(const QModelIndex &item) const
void insert(int, const QString &)
static QStringList supportedExtensions()
QStringList files() const
static void copy(QTreeView *view, int column)
SearchResults searchResults() const
QVariant data(const QModelIndex &item, int role=Qt::DisplayRole) const
FileSearchTab(QWidget *parent)
static Project * instance()
void setReplacePreview(const QRegExp &, const QString &)
SearchFileListView(QWidget *)
static bool extIsSupported(const QString &path)
This struct represents a position in a catalog.
QVector< StartLen > sourcePositions
FileSearchModel(QObject *parent)
void addFilesFast(const QStringList &files)
void replaceRequested(const QRegExp &, const QString &)
int rowCount(const QModelIndex &parent=QModelIndex()) const
Q_SCRIPTABLE void addFilesToSearch(const QStringList &)
static const char *const states[]
remember to connect appropriate signals to reset slot for delegate to have actual cache ...
void scrollTo(const QString &file=QString())
Q_SCRIPTABLE void performSearch()
int findMatchingRule(const QVector< Rule > &rules, const QString &source, const QString &target, QVector< StartLen > &positions)
data structure used to pass info about inline elements a XLIFF tag is represented by a TAGRANGE_IMAGE...
QStringList scanRecursive(const QList< QUrl > &urls)
StatusBarProxy statusBarItems
void addFiles(const QStringList &files)
QString convertToHtml(QString str, bool italics)
#define ID_STATUS_PROGRESS
QVector< SearchResult > SearchResults
void previewRequested(const QRegExp &, const QString &)
This class represents a catalog It uses CatalogStorage interface to work with catalogs in different f...
QString shorterFilePath(const QString path)
void fileOpenRequested(const KUrl &url, DocPosition docPos, int selection)
QVariant headerData(int section, Qt::Orientation orientation, int role=Qt::DisplayRole) const
void appendSearchResults(const SearchResults &)
DocPosition toDocPosition()
MassReplaceView(QWidget *)
static QStringList doScanRecursive(const QDir &dir)