1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "findinfiles.h"
27 
28 #include <coreplugin/editormanager/editormanager.h>
29 #include <coreplugin/find/findplugin.h>
30 #include <coreplugin/icore.h>
31 
32 #include <utils/filesearch.h>
33 #include <utils/fileutils.h>
34 #include <utils/historycompleter.h>
35 #include <utils/pathchooser.h>
36 #include <utils/qtcassert.h>
37 #include <utils/stringutils.h>
38 
39 #include <QComboBox>
40 #include <QDebug>
41 #include <QDir>
42 #include <QFileDialog>
43 #include <QHBoxLayout>
44 #include <QLabel>
45 #include <QPushButton>
46 #include <QSettings>
47 #include <QStackedWidget>
48 
49 using namespace Core;
50 using namespace TextEditor;
51 using namespace Utils;
52 
53 static FindInFiles *m_instance = nullptr;
54 static const char HistoryKey[] = "FindInFiles.Directories.History";
55 
FindInFiles()56 FindInFiles::FindInFiles()
57 {
58     m_instance = this;
59     connect(EditorManager::instance(), &EditorManager::findOnFileSystemRequest,
60             this, &FindInFiles::findOnFileSystem);
61 }
62 
63 FindInFiles::~FindInFiles() = default;
64 
isValid() const65 bool FindInFiles::isValid() const
66 {
67     return m_isValid;
68 }
69 
id() const70 QString FindInFiles::id() const
71 {
72     return QLatin1String("Files on Disk");
73 }
74 
displayName() const75 QString FindInFiles::displayName() const
76 {
77     return tr("Files in File System");
78 }
79 
files(const QStringList & nameFilters,const QStringList & exclusionFilters,const QVariant & additionalParameters) const80 FileIterator *FindInFiles::files(const QStringList &nameFilters,
81                                  const QStringList &exclusionFilters,
82                                  const QVariant &additionalParameters) const
83 {
84     return new SubDirFileIterator({additionalParameters.toString()},
85                                   nameFilters,
86                                   exclusionFilters,
87                                   EditorManager::defaultTextCodec());
88 }
89 
additionalParameters() const90 QVariant FindInFiles::additionalParameters() const
91 {
92     return QVariant::fromValue(path().toString());
93 }
94 
label() const95 QString FindInFiles::label() const
96 {
97     QString title = currentSearchEngine()->title();
98 
99     const QChar slash = QLatin1Char('/');
100     const QStringList &nonEmptyComponents = path().toFileInfo().absoluteFilePath()
101             .split(slash, Qt::SkipEmptyParts);
102     return tr("%1 \"%2\":")
103             .arg(title)
104             .arg(nonEmptyComponents.isEmpty() ? QString(slash) : nonEmptyComponents.last());
105 }
106 
toolTip() const107 QString FindInFiles::toolTip() const
108 {
109     //: the last arg is filled by BaseFileFind::runNewSearch
110     QString tooltip = tr("Path: %1\nFilter: %2\nExcluding: %3\n%4")
111             .arg(path().toUserOutput())
112             .arg(fileNameFilters().join(','))
113             .arg(fileExclusionFilters().join(','));
114 
115     const QString searchEngineToolTip = currentSearchEngine()->toolTip();
116     if (!searchEngineToolTip.isEmpty())
117         tooltip = tooltip.arg(searchEngineToolTip);
118 
119     return tooltip;
120 }
121 
syncSearchEngineCombo(int selectedSearchEngineIndex)122 void FindInFiles::syncSearchEngineCombo(int selectedSearchEngineIndex)
123 {
124     QTC_ASSERT(m_searchEngineCombo && selectedSearchEngineIndex >= 0
125                && selectedSearchEngineIndex < searchEngines().size(), return);
126 
127     m_searchEngineCombo->setCurrentIndex(selectedSearchEngineIndex);
128 }
129 
setValid(bool valid)130 void FindInFiles::setValid(bool valid)
131 {
132     if (valid == m_isValid)
133         return;
134     m_isValid = valid;
135     emit validChanged(m_isValid);
136 }
137 
searchEnginesSelectionChanged(int index)138 void FindInFiles::searchEnginesSelectionChanged(int index)
139 {
140     setCurrentSearchEngine(index);
141     m_searchEngineWidget->setCurrentIndex(index);
142 }
143 
createConfigWidget()144 QWidget *FindInFiles::createConfigWidget()
145 {
146     if (!m_configWidget) {
147         m_configWidget = new QWidget;
148         auto gridLayout = new QGridLayout(m_configWidget);
149         gridLayout->setContentsMargins(0, 0, 0, 0);
150         m_configWidget->setLayout(gridLayout);
151 
152         int row = 0;
153         auto searchEngineLabel = new QLabel(tr("Search engine:"));
154         gridLayout->addWidget(searchEngineLabel, row, 0, Qt::AlignRight);
155         m_searchEngineCombo = new QComboBox;
156         auto cc = QOverload<int>::of(&QComboBox::currentIndexChanged);
157         connect(m_searchEngineCombo, cc, this, &FindInFiles::searchEnginesSelectionChanged);
158         searchEngineLabel->setBuddy(m_searchEngineCombo);
159         gridLayout->addWidget(m_searchEngineCombo, row, 1);
160 
161         m_searchEngineWidget = new QStackedWidget(m_configWidget);
162         foreach (SearchEngine *searchEngine, searchEngines()) {
163             m_searchEngineWidget->addWidget(searchEngine->widget());
164             m_searchEngineCombo->addItem(searchEngine->title());
165         }
166         gridLayout->addWidget(m_searchEngineWidget, row++, 2);
167 
168         QLabel *dirLabel = new QLabel(tr("Director&y:"));
169         gridLayout->addWidget(dirLabel, row, 0, Qt::AlignRight);
170         m_directory = new PathChooser;
171         m_directory->setExpectedKind(PathChooser::ExistingDirectory);
172         m_directory->setPromptDialogTitle(tr("Directory to Search"));
173         connect(m_directory.data(), &PathChooser::pathChanged,
174                 this, &FindInFiles::pathChanged);
175         m_directory->setHistoryCompleter(QLatin1String(HistoryKey),
176                                          /*restoreLastItemFromHistory=*/ true);
177         if (!HistoryCompleter::historyExistsFor(QLatin1String(HistoryKey))) {
178             auto completer = static_cast<HistoryCompleter *>(m_directory->lineEdit()->completer());
179             const QStringList legacyHistory = Core::ICore::settings()->value(
180                         QLatin1String("Find/FindInFiles/directories")).toStringList();
181             for (const QString &dir: legacyHistory)
182                 completer->addEntry(dir);
183         }
184         dirLabel->setBuddy(m_directory);
185         gridLayout->addWidget(m_directory, row++, 1, 1, 2);
186 
187         const QList<QPair<QWidget *, QWidget *>> patternWidgets = createPatternWidgets();
188         for (const QPair<QWidget *, QWidget *> &p : patternWidgets) {
189             gridLayout->addWidget(p.first, row, 0, Qt::AlignRight);
190             gridLayout->addWidget(p.second, row, 1, 1, 2);
191             ++row;
192         }
193         m_configWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
194 
195         // validity
196         auto updateValidity = [this]() {
197             setValid(currentSearchEngine()->isEnabled() && m_directory->isValid());
198         };
199         connect(this, &BaseFileFind::currentSearchEngineChanged, this, updateValidity);
200         foreach (SearchEngine *searchEngine, searchEngines())
201             connect(searchEngine, &SearchEngine::enabledChanged, this, updateValidity);
202         connect(m_directory.data(), &PathChooser::validChanged, this, updateValidity);
203         updateValidity();
204     }
205     return m_configWidget;
206 }
207 
path() const208 FilePath FindInFiles::path() const
209 {
210     return m_directory->filePath();
211 }
212 
writeSettings(QSettings * settings)213 void FindInFiles::writeSettings(QSettings *settings)
214 {
215     settings->beginGroup(QLatin1String("FindInFiles"));
216     writeCommonSettings(settings);
217     settings->endGroup();
218 }
219 
readSettings(QSettings * settings)220 void FindInFiles::readSettings(QSettings *settings)
221 {
222     settings->beginGroup(QLatin1String("FindInFiles"));
223     readCommonSettings(settings, "*.cpp,*.h", "*/.git/*,*/.cvs/*,*/.svn/*,*.autosave");
224     settings->endGroup();
225 }
226 
setDirectory(const FilePath & directory)227 void FindInFiles::setDirectory(const FilePath &directory)
228 {
229     m_directory->setFilePath(directory);
230 }
231 
setBaseDirectory(const FilePath & directory)232 void FindInFiles::setBaseDirectory(const FilePath &directory)
233 {
234     m_directory->setBaseDirectory(directory);
235 }
236 
directory() const237 FilePath FindInFiles::directory() const
238 {
239     return m_directory->filePath();
240 }
241 
findOnFileSystem(const QString & path)242 void FindInFiles::findOnFileSystem(const QString &path)
243 {
244     QTC_ASSERT(m_instance, return);
245     const QFileInfo fi(path);
246     const QString folder = fi.isDir() ? fi.absoluteFilePath() : fi.absolutePath();
247     m_instance->setDirectory(FilePath::fromString(folder));
248     Find::openFindDialog(m_instance);
249 }
250 
instance()251 FindInFiles *FindInFiles::instance()
252 {
253     return m_instance;
254 }
255