basefilefind.cpp 14.1 KB
Newer Older
1
/**************************************************************************
con's avatar
con committed
2
3
4
**
** This file is part of Qt Creator
**
con's avatar
con committed
5
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
con's avatar
con committed
6
**
7
** Contact: Nokia Corporation (qt-info@nokia.com)
con's avatar
con committed
8
**
9
**
10
** GNU Lesser General Public License Usage
11
**
hjk's avatar
hjk committed
12
13
14
15
16
17
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this file.
** Please review the following information to ensure the GNU Lesser General
** Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18
**
con's avatar
con committed
19
** In addition, as a special exception, Nokia gives you certain additional
hjk's avatar
hjk committed
20
** rights. These rights are described in the Nokia Qt LGPL Exception
con's avatar
con committed
21
22
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
23
24
25
26
27
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
con's avatar
con committed
28
** If you have questions regarding the use of this file, please contact
29
** Nokia at qt-info@nokia.com.
con's avatar
con committed
30
**
31
**************************************************************************/
hjk's avatar
hjk committed
32

con's avatar
con committed
33
#include "basefilefind.h"
34
#include "basefilefind_p.h"
con's avatar
con committed
35

36
#include <aggregation/aggregate.h>
37
#include <coreplugin/icore.h>
38
#include <coreplugin/progressmanager/progressmanager.h>
39
#include <coreplugin/progressmanager/futureprogress.h>
con's avatar
con committed
40
#include <coreplugin/editormanager/editormanager.h>
41
#include <coreplugin/editormanager/ieditor.h>
42
#include <coreplugin/filemanager.h>
con's avatar
con committed
43
44
45
#include <find/textfindconstants.h>
#include <texteditor/itexteditor.h>
#include <texteditor/basetexteditor.h>
46
#include <texteditor/refactoringchanges.h>
47
#include <utils/stylehelper.h>
48
#include <utils/fileutils.h>
49
#include <utils/qtcassert.h>
con's avatar
con committed
50

51
#include <QtCore/QDebug>
con's avatar
con committed
52
#include <QtCore/QDirIterator>
53
#include <QtCore/QSettings>
54
55
#include <QtCore/QHash>
#include <QtCore/QPair>
56
#include <QtGui/QFileDialog>
57
#include <QtGui/QCheckBox>
58
#include <QtGui/QComboBox>
59
#include <QtGui/QHBoxLayout>
60
#include <QtGui/QLabel>
61
#include <QtGui/QMainWindow>
con's avatar
con committed
62
#include <QtGui/QPushButton>
63
#include <QtGui/QTextBlock>
con's avatar
con committed
64

65
using namespace Utils;
con's avatar
con committed
66
67
using namespace Find;
using namespace TextEditor;
68
using namespace TextEditor::Internal;
con's avatar
con committed
69

70
BaseFileFind::BaseFileFind()
71
  : m_resultLabel(0),
72
    m_filterCombo(0)
con's avatar
con committed
73
74
75
{
}

76
77
78
79
BaseFileFind::~BaseFileFind()
{
}

con's avatar
con committed
80
81
bool BaseFileFind::isEnabled() const
{
82
    return true;
con's avatar
con committed
83
84
}

dt's avatar
dt committed
85
86
void BaseFileFind::cancel()
{
87
88
    SearchResult *search = qobject_cast<SearchResult *>(sender());
    QTC_ASSERT(search, return);
Eike Ziller's avatar
Eike Ziller committed
89
    QFutureWatcher<FileSearchResultList> *watcher = m_watchers.key(search);
90
91
    QTC_ASSERT(watcher, return);
    watcher->cancel();
dt's avatar
dt committed
92
93
}

con's avatar
con committed
94
95
96
97
QStringList BaseFileFind::fileNameFilters() const
{
    QStringList filters;
    if (m_filterCombo && !m_filterCombo->currentText().isEmpty()) {
98
        const QStringList parts = m_filterCombo->currentText().split(QLatin1Char(','));
con's avatar
con committed
99
        foreach (const QString &part, parts) {
100
            const QString filter = part.trimmed();
con's avatar
con committed
101
102
103
104
105
106
107
108
            if (!filter.isEmpty()) {
                filters << filter;
            }
        }
    }
    return filters;
}

109
110
void BaseFileFind::runNewSearch(const QString &txt, Find::FindFlags findFlags,
                                    SearchResultWindow::SearchMode searchMode)
con's avatar
con committed
111
{
112
    m_currentFindSupport = 0;
con's avatar
con committed
113
    if (m_filterCombo)
114
        updateComboEntries(m_filterCombo, true);
115
    SearchResult *search = Find::SearchResultWindow::instance()->startNewSearch(label(),
116
117
                           toolTip().arg(Find::IFindFilter::descriptionForFindFlags(findFlags)),
                           txt, searchMode, QString::fromLatin1("TextEditor"));
118
    search->setTextToReplace(txt);
119
120
121
122
123
124
125
    search->setSearchAgainSupported(true);
    FileFindParameters parameters;
    parameters.text = txt;
    parameters.flags = findFlags;
    parameters.nameFilters = fileNameFilters();
    parameters.additionalParameters = additionalParameters();
    search->setUserData(qVariantFromValue(parameters));
126
    connect(search, SIGNAL(activated(Find::SearchResultItem)), this, SLOT(openEditor(Find::SearchResultItem)));
127
    if (searchMode == SearchResultWindow::SearchAndReplace) {
128
        connect(search, SIGNAL(replaceButtonClicked(QString,QList<Find::SearchResultItem>)),
129
130
                this, SLOT(doReplace(QString,QList<Find::SearchResultItem>)));
    }
131
    connect(search, SIGNAL(visibilityChanged(bool)), this, SLOT(hideHighlightAll(bool)));
132
133
    connect(search, SIGNAL(cancelled()), this, SLOT(cancel()));
    connect(search, SIGNAL(searchAgainRequested()), this, SLOT(searchAgain()));
134
    connect(this, SIGNAL(enabledChanged(bool)), search, SLOT(setSearchAgainEnabled(bool)));
135
136
137
138
139
140
    runSearch(search);
}

void BaseFileFind::runSearch(Find::SearchResult *search)
{
    FileFindParameters parameters = search->userData().value<FileFindParameters>();
141
142
    CountingLabel *label = new CountingLabel;
    connect(search, SIGNAL(countChanged(int)), label, SLOT(updateCount(int)));
143
    Find::SearchResultWindow::instance()->popup(true);
144
145
146
147
148
149
150
151
152
153
    QFutureWatcher<FileSearchResultList> *watcher = new QFutureWatcher<FileSearchResultList>();
    m_watchers.insert(watcher, search);
    watcher->setPendingResultsLimit(1);
    connect(watcher, SIGNAL(resultReadyAt(int)), this, SLOT(displayResult(int)));
    connect(watcher, SIGNAL(finished()), this, SLOT(searchFinished()));
    if (parameters.flags & Find::FindRegularExpression) {
        watcher->setFuture(Utils::findInFilesRegExp(parameters.text,
            files(parameters.nameFilters, parameters.additionalParameters),
            textDocumentFlagsForFindFlags(parameters.flags),
            ITextEditor::openedTextEditorsContents()));
154
    } else {
155
156
157
158
        watcher->setFuture(Utils::findInFiles(parameters.text,
            files(parameters.nameFilters, parameters.additionalParameters),
            textDocumentFlagsForFindFlags(parameters.flags),
            ITextEditor::openedTextEditorsContents()));
159
    }
160
    Core::FutureProgress *progress =
161
        Core::ICore::instance()->progressManager()->addTask(watcher->future(),
Robert Loehning's avatar
Robert Loehning committed
162
                                                                        tr("Search"),
163
                                                                        Constants::TASK_SEARCH);
164
    progress->setWidget(label);
165
    connect(progress, SIGNAL(clicked()), Find::SearchResultWindow::instance(), SLOT(popup()));
con's avatar
con committed
166
167
}

168
169
170
171
172
void BaseFileFind::findAll(const QString &txt, Find::FindFlags findFlags)
{
    runNewSearch(txt, findFlags, SearchResultWindow::SearchOnly);
}

173
void BaseFileFind::replaceAll(const QString &txt, Find::FindFlags findFlags)
174
{
175
    runNewSearch(txt, findFlags, SearchResultWindow::SearchAndReplace);
176
177
178
179
180
181
182
183
184
}

void BaseFileFind::doReplace(const QString &text,
                               const QList<Find::SearchResultItem> &items)
{
    QStringList files = replaceAll(text, items);
    Core::FileManager *fileManager = Core::ICore::instance()->fileManager();
    if (!files.isEmpty()) {
        fileManager->notifyFilesChangedInternally(files);
185
        Find::SearchResultWindow::instance()->hide();
186
187
188
    }
}

con's avatar
con committed
189
void BaseFileFind::displayResult(int index) {
190
191
192
193
194
195
    QFutureWatcher<FileSearchResultList> *watcher =
            static_cast<QFutureWatcher<FileSearchResultList> *>(sender());
    SearchResult *search = m_watchers.value(watcher);
    if (!search) {
        // search was removed from search history while the search is running
        watcher->cancel();
196
197
        return;
    }
198
    Utils::FileSearchResultList results = watcher->resultAt(index);
199
    QList<Find::SearchResultItem> items;
200
201
    foreach (const Utils::FileSearchResult &result, results) {
        Find::SearchResultItem item;
202
        item.path = QStringList() << QDir::toNativeSeparators(result.fileName);
203
        item.lineNumber = result.lineNumber;
204
205
206
207
        item.text = result.matchingLine;
        item.textMarkLength = result.matchLength;
        item.textMarkPos = result.matchStart;
        item.useTextEditorFont = true;
208
209
210
        item.userData = result.regexpCapturedTexts;
        items << item;
    }
211
    search->addResults(items, Find::SearchResult::AddOrdered);
con's avatar
con committed
212
213
214
215
}

void BaseFileFind::searchFinished()
{
216
217
218
219
220
221
222
    QFutureWatcher<FileSearchResultList> *watcher =
            static_cast<QFutureWatcher<FileSearchResultList> *>(sender());
    SearchResult *search = m_watchers.value(watcher);
    if (search)
        search->finishSearch();
    m_watchers.remove(watcher);
    watcher->deleteLater();
con's avatar
con committed
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
}

QWidget *BaseFileFind::createPatternWidget()
{
    QString filterToolTip = tr("List of comma separated wildcard filters");
    m_filterCombo = new QComboBox;
    m_filterCombo->setEditable(true);
    m_filterCombo->setModel(&m_filterStrings);
    m_filterCombo->setMaxCount(10);
    m_filterCombo->setMinimumContentsLength(10);
    m_filterCombo->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon);
    m_filterCombo->setInsertPolicy(QComboBox::InsertAtBottom);
    m_filterCombo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
    m_filterCombo->setToolTip(filterToolTip);
    syncComboWithSettings(m_filterCombo, m_filterSetting);
    return m_filterCombo;
}

void BaseFileFind::writeCommonSettings(QSettings *settings)
{
    settings->setValue("filters", m_filterStrings.stringList());
    if (m_filterCombo)
        settings->setValue("currentFilter", m_filterCombo->currentText());
}

void BaseFileFind::readCommonSettings(QSettings *settings, const QString &defaultFilter)
{
    QStringList filters = settings->value("filters").toStringList();
    m_filterSetting = settings->value("currentFilter").toString();
    if (filters.isEmpty())
        filters << defaultFilter;
    if (m_filterSetting.isEmpty())
        m_filterSetting = filters.first();
    m_filterStrings.setStringList(filters);
con's avatar
con committed
257
258
    if (m_filterCombo)
        syncComboWithSettings(m_filterCombo, m_filterSetting);
con's avatar
con committed
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
}

void BaseFileFind::syncComboWithSettings(QComboBox *combo, const QString &setting)
{
    if (!combo)
        return;
    int index = combo->findText(setting);
    if (index < 0)
        combo->setEditText(setting);
    else
        combo->setCurrentIndex(index);
}

void BaseFileFind::updateComboEntries(QComboBox *combo, bool onTop)
{
    int index = combo->findText(combo->currentText());
    if (index < 0) {
        if (onTop) {
            combo->insertItem(0, combo->currentText());
        } else {
            combo->addItem(combo->currentText());
        }
        combo->setCurrentIndex(combo->findText(combo->currentText()));
    }
}

285
void BaseFileFind::openEditor(const Find::SearchResultItem &item)
con's avatar
con committed
286
{
287
288
    SearchResult *result = qobject_cast<SearchResult *>(sender());
    Core::IEditor *openedEditor = 0;
289
    if (item.path.size() > 0) {
290
291
292
        openedEditor = TextEditor::BaseTextEditorWidget::openEditorAt(QDir::fromNativeSeparators(item.path.first()),
                                                                      item.lineNumber,
                                                                      item.textMarkPos,
hjk's avatar
hjk committed
293
                                                                      Core::Id(),
294
                                                                      Core::EditorManager::ModeSwitch);
295
    } else {
hjk's avatar
hjk committed
296
        openedEditor = Core::EditorManager::instance()->openEditor(item.text, Core::Id(),
297
298
299
300
301
302
303
304
305
306
                                                                   Core::EditorManager::ModeSwitch);
    }
    if (m_currentFindSupport)
        m_currentFindSupport->clearResults();
    m_currentFindSupport = 0;
    if (!openedEditor)
        return;
    // highlight results
    if (IFindSupport *findSupport = Aggregation::query<IFindSupport>(openedEditor->widget())) {
        if (result) {
307
            FileFindParameters parameters = result->userData().value<FileFindParameters>();
308
            m_currentFindSupport = findSupport;
309
            m_currentFindSupport->highlightAll(parameters.text, parameters.flags);
310
        }
311
    }
con's avatar
con committed
312
}
313

314
315
316
317
318
319
void BaseFileFind::hideHighlightAll(bool visible)
{
    if (!visible && m_currentFindSupport)
        m_currentFindSupport->clearResults();
}

320
321
322
323
324
325
326
void BaseFileFind::searchAgain()
{
    SearchResult *search = qobject_cast<SearchResult *>(sender());
    search->reset();
    runSearch(search);
}

327
328
329
QStringList BaseFileFind::replaceAll(const QString &text,
                               const QList<Find::SearchResultItem> &items)
{
330
    if (items.isEmpty())
331
332
        return QStringList();

333
    RefactoringChanges refactoring;
334

335
    QHash<QString, QList<Find::SearchResultItem> > changes;
336
    foreach (const Find::SearchResultItem &item, items)
337
        changes[QDir::fromNativeSeparators(item.path.first())].append(item);
338
339
340
341
342

    QHashIterator<QString, QList<Find::SearchResultItem> > it(changes);
    while (it.hasNext()) {
        it.next();
        const QString fileName = it.key();
343
        const QList<Find::SearchResultItem> changeItems = it.value();
344

345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
        ChangeSet changeSet;
        RefactoringFilePtr file = refactoring.file(fileName);
        QSet<QPair<int, int> > processed;
        foreach (const Find::SearchResultItem &item, changeItems) {
            const QPair<int, int> &p = qMakePair(item.lineNumber, item.textMarkPos);
            if (processed.contains(p))
                continue;
            processed.insert(p);

            QString replacement;
            if (item.userData.canConvert<QStringList>() && !item.userData.toStringList().isEmpty())
                replacement = Utils::expandRegExpReplacement(text, item.userData.toStringList());
            else
                replacement = text;

            const int start = file->position(item.lineNumber, item.textMarkPos + 1);
            const int end = file->position(item.lineNumber,
                                           item.textMarkPos + item.textMarkLength + 1);
            changeSet.replace(start, end, replacement);
364
        }
365
366
        file->setChangeSet(changeSet);
        file->apply();
367
368
369
370
    }

    return changes.keys();
}
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387

CountingLabel::CountingLabel()
{
    setAlignment(Qt::AlignCenter);
    // ### TODO this setup should be done by style
    QFont f = font();
    f.setBold(true);
    f.setPointSizeF(StyleHelper::sidebarFontSize());
    setFont(f);
    setPalette(StyleHelper::sidebarFontPalette(palette()));
    updateCount(0);
}

void CountingLabel::updateCount(int count)
{
    setText(tr("%1 found").arg(count));
}