kde-extraapps/kdevplatform/plugins/grepview/grepdialog.cpp

503 lines
16 KiB
C++
Raw Normal View History

/***************************************************************************
* Copyright 1999-2001 Bernd Gehrmann and the KDevelop Team *
* bernd@kdevelop.org *
* Copyright 2007 Dukju Ahn <dukjuahn@gmail.com> *
* Copyright 2010 Julien Desgats <julien.desgats@gmail.com> *
* *
* 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 <QDir>
#include <QLabel>
#include <QRegExp>
#include <QBoxLayout>
#include <QStringList>
#include <QMenu>
#include <kfiledialog.h>
#include <kpushbutton.h>
#include <kglobal.h>
#include <kiconloader.h>
#include <klocale.h>
#include <kconfig.h>
#include <kmessagebox.h>
#include <kdebug.h>
#include <kcombobox.h>
#include <kurlcompletion.h>
#include <kstandarddirs.h>
#include <klineedit.h>
#include <interfaces/icore.h>
#include <interfaces/idocument.h>
#include <interfaces/idocumentcontroller.h>
#include <interfaces/iruncontroller.h>
#include <interfaces/iproject.h>
#include <interfaces/iprojectcontroller.h>
#include <interfaces/isession.h>
#include "grepviewplugin.h"
#include "grepjob.h"
#include "grepoutputview.h"
#include "grepfindthread.h"
#include "greputil.h"
using namespace KDevelop;
namespace {
QString allOpenFilesString = i18n("All Open Files");
QString allOpenProjectsString = i18n("All Open Projects");
const QStringList template_desc = QStringList()
<< "verbatim"
<< "word"
<< "assignment"
<< "->MEMBER("
<< "class::MEMBER("
<< "OBJECT->member(";
const QStringList template_str = QStringList()
<< "%s"
<< "\\b%s\\b"
<< "\\b%s\\b\\s*=[^=]"
<< "\\->\\s*\\b%s\\b\\s*\\("
<< "([a-z0-9_$]+)\\s*::\\s*\\b%s\\b\\s*\\("
<< "\\b%s\\b\\s*\\->\\s*([a-z0-9_$]+)\\s*\\(";
const QStringList repl_template = QStringList()
<< "%s"
<< "%s"
<< "%s = "
<< "->%s("
<< "\\1::%s("
<< "%s->\\1(";
const QStringList filepatterns = QStringList()
<< "*.h,*.hxx,*.hpp,*.hh,*.h++,*.H,*.tlh,*.cpp,*.cc,*.C,*.c++,*.cxx,*.ocl,*.inl,*.idl,*.c,*.m,*.mm,*.M,*.y,*.ypp,*.yxx,*.y++,*.l"
<< "*.cpp,*.cc,*.C,*.c++,*.cxx,*.ocl,*.inl,*.c,*.m,*.mm,*.M"
<< "*.h,*.hxx,*.hpp,*.hh,*.h++,*.H,*.tlh,*.idl"
<< "*.adb"
<< "*.cs"
<< "*.f"
<< "*.html,*.htm"
<< "*.hs"
<< "*.java"
<< "*.js"
<< "*.php,*.php3,*.php4"
<< "*.pl"
<< "*.pp,*.pas"
<< "*.py"
<< "*.js,*.css,*.yml,*.rb,*.rhtml,*.html.erb,*.rjs,*.js.rjs,*.rxml,*.xml.builder"
<< "CMakeLists.txt,*.cmake"
<< "*";
const QStringList excludepatterns = QStringList()
<< "/CVS/,/SCCS/,/.svn/,/_darcs/,/build/,/.git/"
<< "";
///Separator used to separate search paths.
const QString pathsSeparator(";");
///Max number of items in paths combo box.
const int pathsMaxCount = 25;
}
const KDialog::ButtonCode GrepDialog::SearchButton = KDialog::User1;
GrepDialog::GrepDialog( GrepViewPlugin * plugin, QWidget *parent )
: KDialog(parent), Ui::GrepWidget(), m_plugin( plugin )
{
setAttribute(Qt::WA_DeleteOnClose);
setButtons( SearchButton | KDialog::Cancel );
setButtonText( SearchButton, i18n("Search...") );
setButtonIcon( SearchButton, KIcon("edit-find") );
setCaption( i18n("Find/Replace in Files") );
setDefaultButton( SearchButton );
setupUi(mainWidget());
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(KGlobalSettings::CompletionPopup);
KCompletion* comp = templateEdit->completionObject();
connect(templateEdit, SIGNAL(returnPressed(QString)), comp, SLOT(addItem(QString)));
for(int i=0; i<templateEdit->count(); i++)
comp->addItem(templateEdit->itemText(i));
replacementTemplateEdit->addItems( cg.readEntry("LastUsedReplacementTemplateString", repl_template) );
replacementTemplateEdit->setEditable(true);
replacementTemplateEdit->setCompletionMode(KGlobalSettings::CompletionPopup);
comp = replacementTemplateEdit->completionObject();
connect(replacementTemplateEdit, SIGNAL(returnPressed(QString)), comp, SLOT(addItem(QString)));
for(int i=0; i<replacementTemplateEdit->count(); 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<IProject*> projects = m_plugin->core()->projectController()->projects();
searchPaths->addItems(cg.readEntry("SearchPaths", QStringList(!projects.isEmpty() ? allOpenProjectsString : QDir::homePath() ) ));
searchPaths->setInsertPolicy(QComboBox::InsertAtTop);
syncButton->setIcon(KIcon("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(this, SIGNAL(buttonClicked(KDialog::ButtonCode)), this, SLOT(performAction(KDialog::ButtonCode)));
connect(templateTypeCombo, SIGNAL(activated(int)),
this, SLOT(templateTypeComboActivated(int)));
connect(patternCombo, SIGNAL(editTextChanged(QString)),
this, SLOT(patternComboEditTextChanged(QString)));
patternComboEditTextChanged( patternCombo->currentText() );
patternCombo->setFocus();
connect(searchPaths, SIGNAL(activated(QString)), this, SLOT(setSearchLocations(QString)));
directorySelector->setIcon(KIcon("document-open"));
connect(directorySelector, SIGNAL(clicked(bool)), this, SLOT(selectDirectoryDialog()) );
directoryChanged(directorySelector->text());
}
void GrepDialog::selectDirectoryDialog()
{
QString dirName = KFileDialog::getExistingDirectory(searchPaths->lineEdit()->text(), this, tr("Select directory to search in"));
if (!dirName.isEmpty()) {
setSearchLocations(dirName);
}
}
void GrepDialog::addUrlToMenu(QMenu* menu, KUrl url)
{
QAction* action = menu->addAction(m_plugin->core()->projectController()->prettyFileName(url, KDevelop::IProjectController::FormatPlain));
action->setData(QVariant(url.pathOrUrl()));
connect(action, SIGNAL(triggered(bool)), SLOT(synchronizeDirActionTriggered(bool)));
}
void GrepDialog::addStringToMenu(QMenu* menu, QString string)
{
QAction* action = menu->addAction(string);
action->setData(QVariant(string));
connect(action, SIGNAL(triggered(bool)), SLOT(synchronizeDirActionTriggered(bool)));
}
void GrepDialog::synchronizeDirActionTriggered(bool)
{
QAction* action = qobject_cast<QAction*>(sender());
Q_ASSERT(action);
setSearchLocations(action->data().value<QString>());
}
QMenu* GrepDialog::createSyncButtonMenu()
{
QMenu* ret = new QMenu;
QSet<KUrl> hadUrls;
IDocument *doc = m_plugin->core()->documentController()->activeDocument();
if ( doc )
{
KUrl url = doc->url();
url.cd("..");
while(m_plugin->core()->projectController()->findProjectForUrl(url))
{
url.adjustPath(KUrl::RemoveTrailingSlash);
if(hadUrls.contains(url))
break;
hadUrls.insert(url);
addUrlToMenu(ret, url);
if(!url.cd(".."))
break;
}
// if the current file's parent directory is not in the project, add it
url = doc->url().upUrl();
url.adjustPath(KUrl::RemoveTrailingSlash);
if(!hadUrls.contains(url))
{
hadUrls.insert(url);
addUrlToMenu(ret, url);
}
}
foreach(IProject* project, m_plugin->core()->projectController()->projects())
{
KUrl url = project->folder();
url.adjustPath(KUrl::RemoveTrailingSlash);
if(hadUrls.contains(url))
continue;
addUrlToMenu(ret, url);
}
addStringToMenu(ret, allOpenFilesString);
addStringToMenu(ret, allOpenProjectsString);
return ret;
}
void GrepDialog::directoryChanged(const QString& dir)
{
KUrl currentUrl = dir;
if( !currentUrl.isValid() ) {
setEnableProjectBox(false);
return;
}
bool projectAvailable = true;
foreach(KUrl url, getDirectoryChoice())
{
IProject *proj = ICore::self()->projectController()->findProjectForUrl( currentUrl );
if( !proj || !proj->folder().isLocalFile() )
projectAvailable = false;
}
setEnableProjectBox(projectAvailable);
}
GrepDialog::~GrepDialog()
{
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[index], true );
replacementTemplateEdit->setCurrentItem( repl_template[index], true );
}
void GrepDialog::setEnableProjectBox(bool enable)
{
limitToProjectCheck->setEnabled(enable);
limitToProjectLabel->setEnabled(enable);
}
void GrepDialog::setPattern(const QString &pattern)
{
patternCombo->setEditText(pattern);
}
void GrepDialog::setSearchLocations(const QString &dir)
{
if(!dir.isEmpty()) {
if(QDir::isAbsolutePath(dir))
{
static_cast<KUrlCompletion*>(searchPaths->completionObject())->setDir( 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);
}
}
directoryChanged(dir);
}
QString GrepDialog::patternString() const
{
return patternCombo->currentText();
}
QString GrepDialog::templateString() const
{
return templateEdit->currentText().isEmpty() ? "%s" : templateEdit->currentText();
}
QString GrepDialog::replacementTemplateString() const
{
return replacementTemplateEdit->currentText();
}
QString GrepDialog::filesString() const
{
return filesCombo->currentText();
}
QString GrepDialog::excludeString() const
{
return excludeCombo->currentText();
}
bool GrepDialog::useProjectFilesFlag() const
{
if (!limitToProjectCheck->isEnabled()) return false;
return limitToProjectCheck->isChecked();
}
bool GrepDialog::regexpFlag() const
{
return regexCheck->isChecked();
}
int GrepDialog::depthValue() const
{
return depthSpin->value();
}
bool GrepDialog::caseSensitiveFlag() const
{
return caseSensitiveCheck->isChecked();
}
void GrepDialog::patternComboEditTextChanged( const QString& text)
{
enableButton( SearchButton, !text.isEmpty() );
}
QList< KUrl > GrepDialog::getDirectoryChoice() const
{
QList< KUrl > ret;
QString text = searchPaths->currentText();
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->folder();
}else{
QStringList semicolonSeparatedFileList = text.split(pathsSeparator);
if(!semicolonSeparatedFileList.isEmpty() && QFileInfo(semicolonSeparatedFileList[0]).exists())
{
// We use QFileInfo to make sure this is really a semicolon-separated file list, not a file containing
// a semicolon in the name.
foreach(QString file, semicolonSeparatedFileList)
ret << KUrl::fromPath(file);
}else{
ret << KUrl(searchPaths->currentText());
}
}
return ret;
}
bool GrepDialog::isPartOfChoice(KUrl url) const
{
foreach(KUrl choice, getDirectoryChoice())
if(choice.isParentOf(url) || choice.equals(url))
return true;
return false;
}
void GrepDialog::start()
{
performAction(SearchButton);
}
void GrepDialog::performAction(KDialog::ButtonCode button)
{
// a click on cancel trigger this signal too
if( button != SearchButton ) return;
// search for unsaved documents
QList<IDocument*> unsavedFiles;
QStringList include = GrepFindFilesThread::parseInclude(filesString());
QStringList exclude = GrepFindFilesThread::parseExclude(excludeString());
foreach(IDocument* doc, ICore::self()->documentController()->openDocuments())
{
KUrl 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;
}
QList<KUrl> choice = getDirectoryChoice();
GrepJob* job = m_plugin->newGrepJob();
QString descriptionOrUrl(searchPaths->currentText());
QString description = descriptionOrUrl;
// Shorten the description
if(descriptionOrUrl != allOpenFilesString && descriptionOrUrl != allOpenProjectsString && choice.size() > 1)
description = i18np("%2, and %1 more item", "%2, and %1 more items", choice.size() - 1, choice[0].pathOrUrl());
GrepOutputView *toolView = (GrepOutputView*)ICore::self()->uiController()->
findToolView(i18n("Find/Replace in Files"), m_plugin->toolViewFactory(), IUiController::CreateAndRaise);
GrepOutputModel* outputModel = toolView->renewModel(patternString(), description);
connect(job, SIGNAL(showErrorMessage(QString,int)),
toolView, SLOT(showErrorMessage(QString)));
//the GrepOutputModel gets the 'showMessage' signal to store it and forward
//it to toolView
connect(job, SIGNAL(showMessage(KDevelop::IStatus*,QString,int)),
outputModel, SLOT(showMessageSlot(KDevelop::IStatus*,QString)));
connect(outputModel, SIGNAL(showMessage(KDevelop::IStatus*,QString)),
toolView, SLOT(showMessage(KDevelop::IStatus*,QString)));
connect(toolView, SIGNAL(outputViewIsClosed()),
job, SLOT(kill()));
job->setOutputModel(outputModel);
job->setPatternString(patternString());
job->setReplacementTemplateString(replacementTemplateString());
job->setTemplateString(templateString());
job->setFilesString(filesString());
job->setExcludeString(excludeString());
job->setDirectoryChoice(choice);
job->setProjectFilesFlag( useProjectFilesFlag() );
job->setRegexpFlag( regexpFlag() );
job->setDepth( depthValue() );
job->setCaseSensitive( caseSensitiveFlag() );
ICore::self()->runController()->registerJob(job);
m_plugin->rememberSearchDirectory(descriptionOrUrl);
close();
}
#include "moc_grepdialog.cpp"