basefilefind.cpp 14.1 KB
Newer Older
1
/**************************************************************************
con's avatar
con committed
2
3
4
**
** This file is part of Qt Creator
**
hjk's avatar
hjk committed
5
** Copyright (c) 2012 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 =
hjk's avatar
hjk committed
161
        Core::ICore::progressManager()->addTask(watcher->future(),
Robert Loehning's avatar
Robert Loehning committed
162
                                                                        tr("Search"),
163
                                                                        QLatin1String(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
}

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

con's avatar
con committed
188
void BaseFileFind::displayResult(int index) {
189
190
191
192
193
194
    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();
195
196
        return;
    }
197
    Utils::FileSearchResultList results = watcher->resultAt(index);
198
    QList<Find::SearchResultItem> items;
199
200
    foreach (const Utils::FileSearchResult &result, results) {
        Find::SearchResultItem item;
201
        item.path = QStringList() << QDir::toNativeSeparators(result.fileName);
202
        item.lineNumber = result.lineNumber;
203
204
205
206
        item.text = result.matchingLine;
        item.textMarkLength = result.matchLength;
        item.textMarkPos = result.matchStart;
        item.useTextEditorFont = true;
207
208
209
        item.userData = result.regexpCapturedTexts;
        items << item;
    }
210
    search->addResults(items, Find::SearchResult::AddOrdered);
con's avatar
con committed
211
212
213
214
}

void BaseFileFind::searchFinished()
{
215
216
217
218
219
220
221
    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
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
}

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)
{
242
    settings->setValue(QLatin1String("filters"), m_filterStrings.stringList());
con's avatar
con committed
243
    if (m_filterCombo)
244
        settings->setValue(QLatin1String("currentFilter"), m_filterCombo->currentText());
con's avatar
con committed
245
246
247
248
}

void BaseFileFind::readCommonSettings(QSettings *settings, const QString &defaultFilter)
{
249
250
    QStringList filters = settings->value(QLatin1String("filters")).toStringList();
    m_filterSetting = settings->value(QLatin1String("currentFilter")).toString();
con's avatar
con committed
251
252
253
254
255
    if (filters.isEmpty())
        filters << defaultFilter;
    if (m_filterSetting.isEmpty())
        m_filterSetting = filters.first();
    m_filterStrings.setStringList(filters);
con's avatar
con committed
256
257
    if (m_filterCombo)
        syncComboWithSettings(m_filterCombo, m_filterSetting);
con's avatar
con committed
258
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
}

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()));
    }
}

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

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

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

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

332
    RefactoringChanges refactoring;
333

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

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

344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
        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);
363
        }
364
365
        file->setChangeSet(changeSet);
        file->apply();
366
367
368
369
    }

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

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));
}