diff --git a/plugins/grepview/grepdialog.cpp b/plugins/grepview/grepdialog.cpp index 3b1d7e52af..88aa7fdb02 100644 --- a/plugins/grepview/grepdialog.cpp +++ b/plugins/grepview/grepdialog.cpp @@ -1,544 +1,556 @@ /*************************************************************************** * Copyright 1999-2001 Bernd Gehrmann and the KDevelop Team * * bernd@kdevelop.org * * Copyright 2007 Dukju Ahn * * Copyright 2010 Julien Desgats * * * * 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) any later version. * * * ***************************************************************************/ #include "grepdialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "grepviewplugin.h" #include "grepoutputview.h" #include "grepfindthread.h" #include "greputil.h" using namespace KDevelop; namespace { inline QString allOpenFilesString() { return i18n("All Open Files"); } inline QString allOpenProjectsString() { return i18n("All Open Projects"); } inline QStringList template_desc() { return QStringList() << QStringLiteral("verbatim") << QStringLiteral("word") << QStringLiteral("assignment") << QStringLiteral("->MEMBER(") << QStringLiteral("class::MEMBER(") << QStringLiteral("OBJECT->member("); } inline QStringList template_str() { return QStringList() << QStringLiteral("%s") << QStringLiteral("\\b%s\\b") << QStringLiteral("\\b%s\\b\\s*=[^=]") << QStringLiteral("\\->\\s*\\b%s\\b\\s*\\(") << QStringLiteral("([a-z0-9_$]+)\\s*::\\s*\\b%s\\b\\s*\\(") << QStringLiteral("\\b%s\\b\\s*\\->\\s*([a-z0-9_$]+)\\s*\\("); } inline QStringList repl_template() { return QStringList() << QStringLiteral("%s") << QStringLiteral("%s") << QStringLiteral("%s = ") << QStringLiteral("->%s(") << QStringLiteral("\\1::%s(") << QStringLiteral("%s->\\1("); } inline QStringList filepatterns() { return QStringList() << QStringLiteral("*.h,*.hxx,*.hpp,*.hh,*.h++,*.H,*.tlh,*.cpp,*.cc,*.C,*.c++,*.cxx,*.ocl,*.inl,*.idl,*.c,*.m,*.mm,*.M,*.y,*.ypp,*.yxx,*.y++,*.l") << QStringLiteral("*.cpp,*.cc,*.C,*.c++,*.cxx,*.ocl,*.inl,*.c,*.m,*.mm,*.M") << QStringLiteral("*.h,*.hxx,*.hpp,*.hh,*.h++,*.H,*.tlh,*.idl") << QStringLiteral("*.adb") << QStringLiteral("*.cs") << QStringLiteral("*.f") << QStringLiteral("*.html,*.htm") << QStringLiteral("*.hs") << QStringLiteral("*.java") << QStringLiteral("*.js") << QStringLiteral("*.php,*.php3,*.php4") << QStringLiteral("*.pl") << QStringLiteral("*.pp,*.pas") << QStringLiteral("*.py") << QStringLiteral("*.js,*.css,*.yml,*.rb,*.rhtml,*.html.erb,*.rjs,*.js.rjs,*.rxml,*.xml.builder") << QStringLiteral("CMakeLists.txt,*.cmake") << QStringLiteral("*"); } inline QStringList excludepatterns() { return QStringList() << QStringLiteral("/CVS/,/SCCS/,/.svn/,/_darcs/,/build/,/.git/") << QLatin1String(""); } ///Separator used to separate search paths. inline QString pathsSeparator() { return (QStringLiteral(";")); } ///Returns the chosen directories or files (only the top directories, not subfiles) QList getDirectoryChoice(const QString& text) { QList ret; if (text == allOpenFilesString()) { foreach(IDocument* doc, ICore::self()->documentController()->openDocuments()) ret << doc->url(); } else if (text == allOpenProjectsString()) { foreach(IProject* project, ICore::self()->projectController()->projects()) ret << project->path().toUrl(); } else { QStringList semicolonSeparatedFileList = text.split(pathsSeparator()); if (!semicolonSeparatedFileList.isEmpty() && QFileInfo::exists(semicolonSeparatedFileList[0])) { // We use QFileInfo to make sure this is really a semicolon-separated file list, not a file containing // a semicolon in the name. foreach(const QString& file, semicolonSeparatedFileList) ret << QUrl::fromLocalFile(file).adjusted(QUrl::StripTrailingSlash); } else { ret << QUrl::fromUserInput(text).adjusted(QUrl::StripTrailingSlash); } } return ret; } ///Check if all directories are part of a project bool directoriesInProject(const QString& dir) { foreach (const QUrl& url, getDirectoryChoice(dir)) { IProject *proj = ICore::self()->projectController()->findProjectForUrl(url); if (!proj || !proj->path().toUrl().isLocalFile()) { return false; } } return true; } ///Max number of items in paths combo box. const int pathsMaxCount = 25; } GrepDialog::GrepDialog(GrepViewPlugin *plugin, QWidget *parent, bool show) : QDialog(parent), Ui::GrepWidget(), m_plugin(plugin), m_show(show) { setAttribute(Qt::WA_DeleteOnClose); // if we don't intend on showing the dialog, we can skip all UI setup if (!m_show) { return; } setWindowTitle( i18n("Find/Replace in Files") ); setupUi(this); adjustSize(); auto searchButton = buttonBox->button(QDialogButtonBox::Ok); Q_ASSERT(searchButton); searchButton->setText(i18nc("@action:button", "Search...")); searchButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-find"))); connect(searchButton, &QPushButton::clicked, this, &GrepDialog::startSearch); connect(buttonBox, &QDialogButtonBox::rejected, this, &GrepDialog::reject); KConfigGroup cg = ICore::self()->activeSession()->config()->group( "GrepDialog" ); patternCombo->addItems( cg.readEntry("LastSearchItems", QStringList()) ); patternCombo->setInsertPolicy(QComboBox::InsertAtTop); templateTypeCombo->addItems(template_desc()); templateTypeCombo->setCurrentIndex( cg.readEntry("LastUsedTemplateIndex", 0) ); templateEdit->addItems( cg.readEntry("LastUsedTemplateString", template_str()) ); templateEdit->setEditable(true); templateEdit->setCompletionMode(KCompletion::CompletionPopup); KCompletion* comp = templateEdit->completionObject(); connect(templateEdit, static_cast(&KComboBox::returnPressed), comp, static_cast(&KCompletion::addItem)); for(int i=0; icount(); i++) comp->addItem(templateEdit->itemText(i)); replacementTemplateEdit->addItems( cg.readEntry("LastUsedReplacementTemplateString", repl_template()) ); replacementTemplateEdit->setEditable(true); replacementTemplateEdit->setCompletionMode(KCompletion::CompletionPopup); comp = replacementTemplateEdit->completionObject(); connect(replacementTemplateEdit, static_cast(&KComboBox::returnPressed), comp, static_cast(&KCompletion::addItem)); for(int i=0; icount(); i++) comp->addItem(replacementTemplateEdit->itemText(i)); regexCheck->setChecked(cg.readEntry("regexp", false )); caseSensitiveCheck->setChecked(cg.readEntry("case_sens", true)); searchPaths->setCompletionObject(new KUrlCompletion()); searchPaths->setAutoDeleteCompletionObject(true); QList projects = m_plugin->core()->projectController()->projects(); searchPaths->addItems(cg.readEntry("SearchPaths", QStringList(!projects.isEmpty() ? allOpenProjectsString() : QDir::homePath() ) )); searchPaths->setInsertPolicy(QComboBox::InsertAtTop); syncButton->setIcon(QIcon::fromTheme(QStringLiteral("dirsync"))); syncButton->setMenu(createSyncButtonMenu()); depthSpin->setValue(cg.readEntry("depth", -1)); limitToProjectCheck->setChecked(cg.readEntry("search_project_files", true)); filesCombo->addItems(cg.readEntry("file_patterns", filepatterns())); excludeCombo->addItems(cg.readEntry("exclude_patterns", excludepatterns()) ); connect(templateTypeCombo, static_cast(&KComboBox::activated), this, &GrepDialog::templateTypeComboActivated); connect(patternCombo, &QComboBox::editTextChanged, this, &GrepDialog::patternComboEditTextChanged); patternComboEditTextChanged( patternCombo->currentText() ); patternCombo->setFocus(); connect(searchPaths, static_cast(&KComboBox::activated), this, &GrepDialog::setSearchLocations); directorySelector->setIcon(QIcon::fromTheme(QStringLiteral("document-open"))); connect(directorySelector, &QPushButton::clicked, this, &GrepDialog::selectDirectoryDialog ); } void GrepDialog::selectDirectoryDialog() { const QString dirName = QFileDialog::getExistingDirectory( this, i18nc("@title:window", "Select directory to search in"), searchPaths->lineEdit()->text()); if (!dirName.isEmpty()) { setSearchLocations(dirName); } } void GrepDialog::addUrlToMenu(QMenu* menu, const QUrl& url) { QAction* action = menu->addAction(m_plugin->core()->projectController()->prettyFileName(url, KDevelop::IProjectController::FormatPlain)); action->setData(QVariant(url.toString(QUrl::PreferLocalFile))); connect(action, &QAction::triggered, this, &GrepDialog::synchronizeDirActionTriggered); } void GrepDialog::addStringToMenu(QMenu* menu, const QString& string) { QAction* action = menu->addAction(string); action->setData(QVariant(string)); connect(action, &QAction::triggered, this, &GrepDialog::synchronizeDirActionTriggered); } void GrepDialog::synchronizeDirActionTriggered(bool) { QAction* action = qobject_cast(sender()); Q_ASSERT(action); setSearchLocations(action->data().toString()); } QMenu* GrepDialog::createSyncButtonMenu() { QMenu* ret = new QMenu(this); QSet hadUrls; IDocument *doc = m_plugin->core()->documentController()->activeDocument(); if ( doc ) { Path url = Path(doc->url()).parent(); // always add the current file's parent directory hadUrls.insert(url); addUrlToMenu(ret, url.toUrl()); url = url.parent(); while(m_plugin->core()->projectController()->findProjectForUrl(url.toUrl())) { if(hadUrls.contains(url)) break; hadUrls.insert(url); addUrlToMenu(ret, url.toUrl()); url = url.parent(); } } QVector otherProjectUrls; foreach(IProject* project, m_plugin->core()->projectController()->projects()) { if (!hadUrls.contains(project->path())) { otherProjectUrls.append(project->path().toUrl()); } } // sort the remaining project URLs alphabetically std::sort(otherProjectUrls.begin(), otherProjectUrls.end()); foreach(const QUrl& url, otherProjectUrls) { addUrlToMenu(ret, url); } ret->addSeparator(); addStringToMenu(ret, allOpenFilesString()); addStringToMenu(ret, allOpenProjectsString()); return ret; } GrepDialog::~GrepDialog() { } void GrepDialog::setVisible(bool visible) { QDialog::setVisible(visible && m_show); } void GrepDialog::closeEvent(QCloseEvent* closeEvent) { Q_UNUSED(closeEvent); if (!m_show) { return; } KConfigGroup cg = ICore::self()->activeSession()->config()->group( "GrepDialog" ); // memorize the last patterns and paths cg.writeEntry("LastSearchItems", qCombo2StringList(patternCombo)); cg.writeEntry("regexp", regexCheck->isChecked()); cg.writeEntry("depth", depthSpin->value()); cg.writeEntry("search_project_files", limitToProjectCheck->isChecked()); cg.writeEntry("case_sens", caseSensitiveCheck->isChecked()); cg.writeEntry("exclude_patterns", qCombo2StringList(excludeCombo)); cg.writeEntry("file_patterns", qCombo2StringList(filesCombo)); cg.writeEntry("LastUsedTemplateIndex", templateTypeCombo->currentIndex()); cg.writeEntry("LastUsedTemplateString", qCombo2StringList(templateEdit)); cg.writeEntry("LastUsedReplacementTemplateString", qCombo2StringList(replacementTemplateEdit)); cg.writeEntry("SearchPaths", qCombo2StringList(searchPaths)); cg.sync(); } void GrepDialog::templateTypeComboActivated(int index) { templateEdit->setCurrentItem( template_str().at(index), true ); replacementTemplateEdit->setCurrentItem( repl_template().at(index), true ); } void GrepDialog::setSettings(const GrepJobSettings& settings) { patternCombo->setEditText(settings.pattern); patternComboEditTextChanged(settings.pattern); m_settings.pattern = settings.pattern; limitToProjectCheck->setEnabled(settings.projectFilesOnly); limitToProjectLabel->setEnabled(settings.projectFilesOnly); m_settings.projectFilesOnly = settings.projectFilesOnly; // Note: everything else is set by a user } GrepJobSettings GrepDialog::settings() const { return m_settings; } void GrepDialog::historySearch(QList &settingsHistory) { // clear the current settings history and pass it to a job list m_historyJobSettings.clear(); m_historyJobSettings.swap(settingsHistory); // check if anything is do be done and if all projects are loaded if (!m_historyJobSettings.empty() && !checkProjectsOpened()) { connect(KDevelop::ICore::self()->projectController(), &KDevelop::IProjectController::projectOpened, this, &GrepDialog::checkProjectsOpened); } } void GrepDialog::setSearchLocations(const QString& dir) { if (!dir.isEmpty()) { if (m_show) { if (QDir::isAbsolutePath(dir)) { static_cast(searchPaths->completionObject())->setDir( QUrl::fromLocalFile(dir) ); } if (searchPaths->contains(dir)) { searchPaths->removeItem(searchPaths->findText(dir)); } searchPaths->insertItem(0, dir); searchPaths->setCurrentItem(dir); if (searchPaths->count() > pathsMaxCount) { searchPaths->removeItem(searchPaths->count() - 1); } } else { m_settings.searchPaths = dir; } } m_settings.projectFilesOnly = directoriesInProject(dir); } void GrepDialog::patternComboEditTextChanged( const QString& text) { buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!text.isEmpty()); } bool GrepDialog::checkProjectsOpened() { // only proceed if all projects have been opened if (KDevelop::ICore::self()->activeSession()->config()->group("General Options").readEntry("Open Projects", QList()).count() != KDevelop::ICore::self()->projectController()->projects().count()) return false; foreach (IProject* p, KDevelop::ICore::self()->projectController()->projects()) { if (!p->isReady()) return false; } // do the grep jobs one by one connect(m_plugin, &GrepViewPlugin::grepJobFinished, this, &GrepDialog::nextHistory); QTimer::singleShot(0, this, [=]() {nextHistory(true);}); return true; } void GrepDialog::nextHistory(bool next) { if (next && !m_historyJobSettings.empty()) { m_settings = m_historyJobSettings.takeFirst(); startSearch(); } else { close(); } } bool GrepDialog::isPartOfChoice(const QUrl& url) const { foreach(const QUrl& choice, getDirectoryChoice(m_settings.searchPaths)) { if(choice.isParentOf(url) || choice == url) return true; } return false; } void GrepDialog::startSearch() { // if m_show is false, all settings are fixed in m_settings if (m_show) updateSettings(); const QStringList include = GrepFindFilesThread::parseInclude(m_settings.files); const QStringList exclude = GrepFindFilesThread::parseExclude(m_settings.exclude); // search for unsaved documents QList unsavedFiles; foreach(IDocument* doc, ICore::self()->documentController()->openDocuments()) { QUrl docUrl = doc->url(); if (doc->state() != IDocument::Clean && isPartOfChoice(docUrl) && QDir::match(include, docUrl.fileName()) && !QDir::match(exclude, docUrl.toLocalFile()) ) { unsavedFiles << doc; } } if(!ICore::self()->documentController()->saveSomeDocuments(unsavedFiles)) { close(); return; } const QString descriptionOrUrl(m_settings.searchPaths); QList choice = getDirectoryChoice(descriptionOrUrl); QString description = descriptionOrUrl; - GrepJob* job = m_plugin->newGrepJob(); // Shorten the description if(descriptionOrUrl != allOpenFilesString() && descriptionOrUrl != allOpenProjectsString()) { auto prettyFileName = [](const QUrl& url) { return ICore::self()->projectController()->prettyFileName(url, KDevelop::IProjectController::FormatPlain); }; if (choice.size() > 1) { description = i18np("%2, and %1 more item", "%2, and %1 more items", choice.size() - 1, prettyFileName(choice[0])); } else if (!choice.isEmpty()) { description = prettyFileName(choice[0]); } } - GrepOutputView *toolView = (GrepOutputView*)ICore::self()->uiController()-> - findToolView(i18n("Find/Replace in Files"), m_plugin->toolViewFactory(), IUiController::CreateAndRaise); - GrepOutputModel* outputModel = toolView->renewModel(m_settings, description); + GrepOutputView *toolView = + (GrepOutputView*)ICore::self()->uiController()->findToolView( + i18n("Find/Replace in Files"), m_plugin->toolViewFactory(), + m_settings.fromHistory ? IUiController::Create : IUiController::CreateAndRaise); - connect(job, &GrepJob::showErrorMessage, - toolView, &GrepOutputView::showErrorMessage); - //the GrepOutputModel gets the 'showMessage' signal to store it and forward - //it to toolView - connect(job, &GrepJob::showMessage, - outputModel, &GrepOutputModel::showMessageSlot); - connect(outputModel, &GrepOutputModel::showMessage, - toolView, &GrepOutputView::showMessage); + if (m_settings.fromHistory) { + // when restored from history, only display the parameters + toolView->renewModel(m_settings, i18n("Search \"%1\" in %2", m_settings.pattern, description)); + emit m_plugin->grepJobFinished(true); + } else { + GrepOutputModel* outputModel = + toolView->renewModel(m_settings, + i18n("Search \"%1\" in %2 (at time %3)", m_settings.pattern, description, + QTime::currentTime().toString(QStringLiteral("hh:mm")))); + + GrepJob* job = m_plugin->newGrepJob(); + connect(job, &GrepJob::showErrorMessage, + toolView, &GrepOutputView::showErrorMessage); + //the GrepOutputModel gets the 'showMessage' signal to store it and forward + //it to toolView + connect(job, &GrepJob::showMessage, + outputModel, &GrepOutputModel::showMessageSlot); + connect(outputModel, &GrepOutputModel::showMessage, + toolView, &GrepOutputView::showMessage); - connect(toolView, &GrepOutputView::outputViewIsClosed, job, [=]() {job->kill();}); + connect(toolView, &GrepOutputView::outputViewIsClosed, job, [=]() {job->kill();}); - job->setOutputModel(outputModel); - job->setDirectoryChoice(choice); + job->setOutputModel(outputModel); + job->setDirectoryChoice(choice); - job->setSettings(m_settings); + job->setSettings(m_settings); - ICore::self()->runController()->registerJob(job); + ICore::self()->runController()->registerJob(job); + } m_plugin->rememberSearchDirectory(descriptionOrUrl); // if m_show is false, the dialog is closed somewhere else if (m_show) close(); } void GrepDialog::updateSettings() { if (limitToProjectCheck->isEnabled()) m_settings.projectFilesOnly = limitToProjectCheck->isChecked(); m_settings.caseSensitive = caseSensitiveCheck->isChecked(); m_settings.regexp = regexCheck->isChecked(); m_settings.depth = depthSpin->value(); m_settings.pattern = patternCombo->currentText(); m_settings.searchTemplate = templateEdit->currentText().isEmpty() ? QStringLiteral("%s") : templateEdit->currentText(); m_settings.replacementTemplate = replacementTemplateEdit->currentText(); m_settings.files = filesCombo->currentText(); m_settings.exclude = excludeCombo->currentText(); m_settings.searchPaths = searchPaths->currentText(); } diff --git a/plugins/grepview/grepjob.h b/plugins/grepview/grepjob.h index a7dad6497b..5116a76e3e 100644 --- a/plugins/grepview/grepjob.h +++ b/plugins/grepview/grepjob.h @@ -1,123 +1,125 @@ /*************************************************************************** * Copyright 1999-2001 by Bernd Gehrmann * * bernd@kdevelop.org * * Copyright 2008 by Hamish Rodda * * rodda@kde.org * * Copyright 2010 Silvère Lestang * * Copyright 2010 Julien Desgats * * * * 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) any later version. * * * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_GREPJOB_H #define KDEVPLATFORM_PLUGIN_GREPJOB_H #include #include #include #include #include "grepfindthread.h" #include "grepoutputmodel.h" namespace KDevelop { class IProject; } class QRegExp; class GrepViewPlugin; class FindReplaceTest; //FIXME: this is useful only for tests struct GrepJobSettings { + bool fromHistory = false; + bool projectFilesOnly = false; bool caseSensitive = true; bool regexp = true; int depth = -1; QString pattern; QString searchTemplate; QString replacementTemplate; QString files; QString exclude; QString searchPaths; }; class GrepJob : public KJob, public KDevelop::IStatus { Q_OBJECT Q_INTERFACES( KDevelop::IStatus ) friend class GrepViewPlugin; friend class FindReplaceTest; private: ///Job can only be instanciated by plugin explicit GrepJob( QObject *parent = nullptr ); public: void setSettings(const GrepJobSettings& settings); GrepJobSettings settings() const; void setOutputModel(GrepOutputModel * model); void setDirectoryChoice(const QList &choice); void start() override; QString statusName() const override; protected: bool doKill() override; // GrepOutputModel* model() const; private Q_SLOTS: void slotFindFinished(); void testFinishState(KJob *job); Q_SIGNALS: void clearMessage( KDevelop::IStatus* ) override; void showMessage( KDevelop::IStatus*, const QString & message, int timeout = 0) override; void showErrorMessage(const QString & message, int timeout = 0) override; void hideProgress( KDevelop::IStatus* ) override; void showProgress( KDevelop::IStatus*, int minimum, int maximum, int value) override; void foundMatches( const QString& filename, const GrepOutputItem::List& matches); private: Q_INVOKABLE void slotWork(); QList m_directoryChoice; QString m_errorMessage; QRegExp m_regExp; QString m_regExpSimple; GrepOutputModel *m_outputModel; enum { WorkCollectFiles, WorkGrep, WorkIdle, WorkCancelled } m_workState; QList m_fileList; int m_fileIndex; QPointer m_findThread; GrepJobSettings m_settings; bool m_findSomething; }; //FIXME: this function is used externally only for tests, find a way to keep it // static for a regular compilation GrepOutputItem::List grepFile(const QString &filename, const QRegExp &re); #endif diff --git a/plugins/grepview/grepoutputview.cpp b/plugins/grepview/grepoutputview.cpp index a7cae38b2b..0569e0095a 100644 --- a/plugins/grepview/grepoutputview.cpp +++ b/plugins/grepview/grepoutputview.cpp @@ -1,469 +1,467 @@ /************************************************************************** * Copyright 2010 Silvère Lestang * * Copyright 2010 Julien Desgats * * * * 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) any later version. * * * ***************************************************************************/ #include "grepoutputview.h" #include "grepoutputmodel.h" #include "grepoutputdelegate.h" #include "ui_grepoutputview.h" #include "grepviewplugin.h" #include "grepdialog.h" #include "greputil.h" #include "grepjob.h" #include #include #include #include #include #include #include #include #include using namespace KDevelop; GrepOutputViewFactory::GrepOutputViewFactory(GrepViewPlugin* plugin) : m_plugin(plugin) {} QWidget* GrepOutputViewFactory::create(QWidget* parent) { return new GrepOutputView(parent, m_plugin); } Qt::DockWidgetArea GrepOutputViewFactory::defaultPosition() { return Qt::BottomDockWidgetArea; } QString GrepOutputViewFactory::id() const { return QStringLiteral("org.kdevelop.GrepOutputView"); } const int GrepOutputView::HISTORY_SIZE = 5; GrepOutputView::GrepOutputView(QWidget* parent, GrepViewPlugin* plugin) : QWidget(parent) , m_next(nullptr) , m_prev(nullptr) , m_collapseAll(nullptr) , m_expandAll(nullptr) , m_refresh(nullptr) , m_clearSearchHistory(nullptr) , m_statusLabel(nullptr) , m_plugin(plugin) { Ui::GrepOutputView::setupUi(this); setWindowTitle(i18nc("@title:window", "Find/Replace Output View")); setWindowIcon(QIcon::fromTheme(QStringLiteral("edit-find"), windowIcon())); m_prev = new QAction(QIcon::fromTheme(QStringLiteral("go-previous")), i18n("&Previous Item"), this); m_next = new QAction(QIcon::fromTheme(QStringLiteral("go-next")), i18n("&Next Item"), this); m_collapseAll = new QAction(QIcon::fromTheme(QStringLiteral("arrow-left-double")), i18n("C&ollapse All"), this); // TODO change icon m_expandAll = new QAction(QIcon::fromTheme(QStringLiteral("arrow-right-double")), i18n("&Expand All"), this); // TODO change icon updateButtonState(false); QAction *separator = new QAction(this); separator->setSeparator(true); QAction *newSearchAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-find")), i18n("New &Search"), this); m_refresh = new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18n("Refresh"), this); m_refresh->setEnabled(false); m_clearSearchHistory = new QAction(QIcon::fromTheme(QStringLiteral("edit-clear-list")), i18n("Clear Search History"), this); m_clearSearchHistory->setEnabled(false); addAction(m_prev); addAction(m_next); addAction(m_collapseAll); addAction(m_expandAll); addAction(separator); addAction(newSearchAction); addAction(m_refresh); addAction(m_clearSearchHistory); separator = new QAction(this); separator->setSeparator(true); addAction(separator); QWidgetAction *statusWidget = new QWidgetAction(this); m_statusLabel = new QLabel(this); statusWidget->setDefaultWidget(m_statusLabel); addAction(statusWidget); modelSelector->setEditable(false); modelSelector->setContextMenuPolicy(Qt::CustomContextMenu); connect(modelSelector, &KComboBox::customContextMenuRequested, this, &GrepOutputView::modelSelectorContextMenu); connect(modelSelector, static_cast(&KComboBox::currentIndexChanged), this, &GrepOutputView::changeModel); resultsTreeView->setItemDelegate(GrepOutputDelegate::self()); resultsTreeView->setRootIsDecorated(false); resultsTreeView->setHeaderHidden(true); resultsTreeView->setUniformRowHeights(false); resultsTreeView->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); connect(m_prev, &QAction::triggered, this, &GrepOutputView::selectPreviousItem); connect(m_next, &QAction::triggered, this, &GrepOutputView::selectNextItem); connect(m_collapseAll, &QAction::triggered, this, &GrepOutputView::collapseAllItems); connect(m_expandAll, &QAction::triggered, this, &GrepOutputView::expandAllItems); connect(applyButton, &QPushButton::clicked, this, &GrepOutputView::onApply); connect(m_refresh, &QAction::triggered, this, &GrepOutputView::refresh); connect(m_clearSearchHistory, &QAction::triggered, this, &GrepOutputView::clearSearchHistory); KConfigGroup cg = ICore::self()->activeSession()->config()->group( "GrepDialog" ); replacementCombo->addItems( cg.readEntry("LastReplacementItems", QStringList()) ); replacementCombo->setInsertPolicy(QComboBox::InsertAtTop); applyButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok-apply"))); connect(replacementCombo, &KComboBox::editTextChanged, this, &GrepOutputView::replacementTextChanged); connect(replacementCombo, static_cast(&KComboBox::returnPressed), this, &GrepOutputView::onApply); connect(newSearchAction, &QAction::triggered, this, &GrepOutputView::showDialog); resultsTreeView->header()->setStretchLastSection(true); resultsTreeView->header()->setStretchLastSection(true); // read Find/Replace settings history QStringList s = cg.readEntry("LastSettings", QStringList()); while (!s.empty() && s.count() % 10 == 0) { GrepJobSettings settings; settings.projectFilesOnly = s.takeFirst().toUInt(); settings.caseSensitive = s.takeFirst().toUInt(); settings.regexp = s.takeFirst().toUInt(); settings.depth = s.takeFirst().toInt(); settings.pattern = s.takeFirst(); settings.searchTemplate = s.takeFirst(); settings.replacementTemplate = s.takeFirst(); settings.files = s.takeFirst(); settings.exclude = s.takeFirst(); settings.searchPaths = s.takeFirst(); + settings.fromHistory = true; m_settingsHistory << settings; } // rerun the grep jobs with settings from the history GrepDialog* dlg = new GrepDialog(m_plugin, this, false); dlg->historySearch(m_settingsHistory); updateCheckable(); } void GrepOutputView::replacementTextChanged() { updateCheckable(); if (model()) { // see https://bugs.kde.org/show_bug.cgi?id=274902 - renewModel can trigger a call here without an active model updateApplyState(model()->index(0, 0), model()->index(0, 0)); } } GrepOutputView::~GrepOutputView() { KConfigGroup cg = ICore::self()->activeSession()->config()->group( "GrepDialog" ); cg.writeEntry("LastReplacementItems", qCombo2StringList(replacementCombo, true)); QStringList settingsStrings; foreach (const GrepJobSettings & s, m_settingsHistory) { settingsStrings << QStringList({ QString::number(s.projectFilesOnly), QString::number(s.caseSensitive), QString::number(s.regexp), QString::number(s.depth), s.pattern, s.searchTemplate, s.replacementTemplate, s.files, s.exclude, s.searchPaths }); } cg.writeEntry("LastSettings", settingsStrings); emit outputViewIsClosed(); } GrepOutputModel* GrepOutputView::renewModel(const GrepJobSettings& settings, const QString& description) { // clear oldest model while(modelSelector->count() >= GrepOutputView::HISTORY_SIZE) { QVariant var = modelSelector->itemData(GrepOutputView::HISTORY_SIZE - 1); qvariant_cast(var)->deleteLater(); modelSelector->removeItem(GrepOutputView::HISTORY_SIZE - 1); } while(m_settingsHistory.count() >= GrepOutputView::HISTORY_SIZE) { m_settingsHistory.removeFirst(); } replacementCombo->clearEditText(); GrepOutputModel* newModel = new GrepOutputModel(resultsTreeView); applyButton->setEnabled(false); // text may be already present newModel->setReplacement(replacementCombo->currentText()); connect(newModel, &GrepOutputModel::rowsRemoved, this, &GrepOutputView::rowsRemoved); connect(resultsTreeView, &QTreeView::activated, newModel, &GrepOutputModel::activate); connect(replacementCombo, &KComboBox::editTextChanged, newModel, &GrepOutputModel::setReplacement); connect(newModel, &GrepOutputModel::rowsInserted, this, &GrepOutputView::expandElements); connect(newModel, &GrepOutputModel::showErrorMessage, this, &GrepOutputView::showErrorMessage); connect(m_plugin, &GrepViewPlugin::grepJobFinished, this, &GrepOutputView::updateScrollArea); // appends new model to history - const QString displayName = i18n("Search \"%1\" in %2 (at time %3)", - settings.pattern, description, - QTime::currentTime().toString(QStringLiteral("hh:mm"))); - modelSelector->insertItem(0, displayName, qVariantFromValue(newModel)); - - modelSelector->setCurrentIndex(0);//setCurrentItem(displayName); + modelSelector->insertItem(0, description, qVariantFromValue(newModel)); + modelSelector->setCurrentIndex(0); m_settingsHistory.append(settings); updateCheckable(); return newModel; } GrepOutputModel* GrepOutputView::model() { return static_cast(resultsTreeView->model()); } void GrepOutputView::changeModel(int index) { if (model()) { disconnect(model(), &GrepOutputModel::showMessage, this, &GrepOutputView::showMessage); disconnect(model(), &GrepOutputModel::dataChanged, this, &GrepOutputView::updateApplyState); } replacementCombo->clearEditText(); //after deleting the whole search history, index is -1 if(index >= 0) { QVariant var = modelSelector->itemData(index); GrepOutputModel *resultModel = static_cast(qvariant_cast(var)); resultsTreeView->setModel(resultModel); resultsTreeView->expandAll(); connect(model(), &GrepOutputModel::showMessage, this, &GrepOutputView::showMessage); connect(model(), &GrepOutputModel::dataChanged, this, &GrepOutputView::updateApplyState); model()->showMessageEmit(); applyButton->setEnabled(model()->hasResults() && model()->getRootItem() && model()->getRootItem()->checkState() != Qt::Unchecked && !replacementCombo->currentText().isEmpty()); if(model()->hasResults()) expandElements(QModelIndex()); else { updateButtonState(false); } } updateCheckable(); updateApplyState(model()->index(0, 0), model()->index(0, 0)); m_refresh->setEnabled(true); m_clearSearchHistory->setEnabled(true); } void GrepOutputView::setMessage(const QString& msg, MessageType type) { if (type == Error) { QPalette palette = m_statusLabel->palette(); KColorScheme::adjustForeground(palette, KColorScheme::NegativeText, QPalette::WindowText); m_statusLabel->setPalette(palette); } else { m_statusLabel->setPalette(QPalette()); } m_statusLabel->setText(msg); } void GrepOutputView::showErrorMessage( const QString& errorMessage ) { setMessage(errorMessage, Error); } void GrepOutputView::showMessage( KDevelop::IStatus* , const QString& message ) { setMessage(message, Information); } void GrepOutputView::onApply() { if(model()) { Q_ASSERT(model()->rowCount()); // ask a confirmation before an empty string replacement if(replacementCombo->currentText().length() == 0 && KMessageBox::questionYesNo(this, i18n("Do you want to replace with an empty string?"), i18n("Start replacement")) == KMessageBox::No) { return; } setEnabled(false); model()->doReplacements(); setEnabled(true); } } void GrepOutputView::showDialog() { m_plugin->showDialog(true); } void GrepOutputView::refresh() { int index = modelSelector->currentIndex(); if (index >= 0) { QVariant var = modelSelector->currentData(); qvariant_cast(var)->deleteLater(); modelSelector->removeItem(index); QList refresh_history({ m_settingsHistory.takeAt(m_settingsHistory.count() - 1 - index) }); + refresh_history.first().fromHistory = false; GrepDialog* dlg = new GrepDialog(m_plugin, this, false); dlg->historySearch(refresh_history); } } void GrepOutputView::expandElements(const QModelIndex& index) { updateButtonState(true); resultsTreeView->expand(index); } void GrepOutputView::updateButtonState(bool enable) { m_prev->setEnabled(enable); m_next->setEnabled(enable); m_collapseAll->setEnabled(enable); m_expandAll->setEnabled(enable); } void GrepOutputView::selectPreviousItem() { if (!model()) { return; } QModelIndex prev_idx = model()->previousItemIndex(resultsTreeView->currentIndex()); if (prev_idx.isValid()) { resultsTreeView->setCurrentIndex(prev_idx); model()->activate(prev_idx); } } void GrepOutputView::selectNextItem() { if (!model()) { return; } QModelIndex next_idx = model()->nextItemIndex(resultsTreeView->currentIndex()); if (next_idx.isValid()) { resultsTreeView->setCurrentIndex(next_idx); model()->activate(next_idx); } } void GrepOutputView::collapseAllItems() { // Collapse everything resultsTreeView->collapseAll(); if (resultsTreeView->model()) { // Now reopen the first children, which correspond to the files. resultsTreeView->expand(resultsTreeView->model()->index(0, 0)); } } void GrepOutputView::expandAllItems() { resultsTreeView->expandAll(); } void GrepOutputView::rowsRemoved() { updateButtonState(model()->rowCount() > 0); } void GrepOutputView::updateApplyState(const QModelIndex& topLeft, const QModelIndex& bottomRight) { Q_UNUSED(bottomRight); if (!model() || !model()->hasResults()) { applyButton->setEnabled(false); return; } // we only care about the root item if(!topLeft.parent().isValid()) { applyButton->setEnabled(topLeft.data(Qt::CheckStateRole) != Qt::Unchecked && model()->itemsCheckable()); } } void GrepOutputView::updateCheckable() { if(model()) model()->makeItemsCheckable(!replacementCombo->currentText().isEmpty() || model()->itemsCheckable()); } void GrepOutputView::clearSearchHistory() { GrepJob *runningJob = m_plugin->grepJob(); if(runningJob) { connect(runningJob, &GrepJob::finished, this, [=]() {updateButtonState(false);}); runningJob->kill(); } while(modelSelector->count() > 0) { QVariant var = modelSelector->itemData(0); qvariant_cast(var)->deleteLater(); modelSelector->removeItem(0); } m_settingsHistory.clear(); applyButton->setEnabled(false); updateButtonState(false); m_refresh->setEnabled(false); m_clearSearchHistory->setEnabled(false); m_statusLabel->setText(QString()); } void GrepOutputView::modelSelectorContextMenu(const QPoint& pos) { QPoint globalPos = modelSelector->mapToGlobal(pos); QMenu myMenu(this); myMenu.addAction(m_clearSearchHistory); myMenu.exec(globalPos); } void GrepOutputView::updateScrollArea() { for (int col = 0; col < model()->columnCount(); ++col) resultsTreeView->resizeColumnToContents(col); }