1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
16 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 ** In addition, as a special exception, Nokia gives you certain additional
26 ** rights. These rights are described in the Nokia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
32 **************************************************************************/
34 #include "searchresultwindow.h"
35 #include "searchresulttreemodel.h"
36 #include "searchresulttreeitems.h"
37 #include "searchresulttreeview.h"
38 #include "ifindsupport.h"
40 #include <aggregation/aggregate.h>
41 #include <coreplugin/icore.h>
42 #include <coreplugin/actionmanager/actionmanager.h>
43 #include <coreplugin/actionmanager/command.h>
44 #include <coreplugin/coreconstants.h>
45 #include <coreplugin/icontext.h>
46 #include <coreplugin/uniqueidmanager.h>
47 #include <utils/qtcassert.h>
49 #include <QtCore/QFile>
50 #include <QtCore/QTextStream>
51 #include <QtCore/QSettings>
52 #include <QtCore/QDebug>
53 #include <QtCore/QDir>
54 #include <QtGui/QListWidget>
55 #include <QtGui/QToolButton>
56 #include <QtGui/QLineEdit>
57 #include <QtGui/QStackedWidget>
58 #include <QtGui/QLabel>
59 #include <QtGui/QFont>
60 #include <QtGui/QAction>
62 static const char SETTINGSKEYSECTIONNAME[] = "SearchResults";
63 static const char SETTINGSKEYEXPANDRESULTS[] = "ExpandResults";
69 class WideEnoughLineEdit : public QLineEdit {
72 WideEnoughLineEdit(QWidget *parent):QLineEdit(parent){
73 connect(this, SIGNAL(textChanged(QString)),
74 this, SLOT(updateGeometry()));
76 ~WideEnoughLineEdit(){}
77 QSize sizeHint() const {
78 QSize sh = QLineEdit::minimumSizeHint();
79 sh.rwidth() += qMax(25 * fontMetrics().width(QLatin1Char('x')),
80 fontMetrics().width(text()));
84 void updateGeometry() { QLineEdit::updateGeometry(); }
87 class SearchResultFindSupport : public IFindSupport
91 SearchResultFindSupport(SearchResultTreeView *view)
96 bool supportsReplace() const { return false; }
98 Find::FindFlags supportedFindFlags() const
100 return Find::FindBackward | Find::FindCaseSensitively
101 | Find::FindRegularExpression | Find::FindWholeWords;
104 void resetIncrementalSearch()
106 m_incrementalFindStart = QModelIndex();
109 void clearResults() { }
111 QString currentFindString() const
116 QString completedFindString() const
121 void highlightAll(const QString &txt, Find::FindFlags findFlags)
128 IFindSupport::Result findIncremental(const QString &txt, Find::FindFlags findFlags)
130 if (!m_incrementalFindStart.isValid())
131 m_incrementalFindStart = m_view->currentIndex();
132 m_view->setCurrentIndex(m_incrementalFindStart);
133 return find(txt, findFlags);
136 IFindSupport::Result findStep(const QString &txt, Find::FindFlags findFlags)
138 IFindSupport::Result result = find(txt, findFlags);
139 if (result == IFindSupport::Found)
140 m_incrementalFindStart = m_view->currentIndex();
144 IFindSupport::Result find(const QString &txt, Find::FindFlags findFlags)
147 return IFindSupport::NotFound;
149 if (findFlags & Find::FindRegularExpression) {
150 bool sensitive = (findFlags & Find::FindCaseSensitively);
151 index = m_view->model()->find(QRegExp(txt, (sensitive ? Qt::CaseSensitive : Qt::CaseInsensitive)),
152 m_view->currentIndex(),
153 Find::textDocumentFlagsForFindFlags(findFlags));
155 index = m_view->model()->find(txt, m_view->currentIndex(),
156 Find::textDocumentFlagsForFindFlags(findFlags));
158 if (index.isValid()) {
159 m_view->setCurrentIndex(index);
160 m_view->scrollTo(index);
161 if (index.parent().isValid())
162 m_view->expand(index.parent());
163 return IFindSupport::Found;
165 return IFindSupport::NotFound;
168 void replace(const QString &before, const QString &after,
169 Find::FindFlags findFlags)
176 bool replaceStep(const QString &before, const QString &after,
177 Find::FindFlags findFlags)
185 int replaceAll(const QString &before, const QString &after,
186 Find::FindFlags findFlags)
195 SearchResultTreeView *m_view;
196 QModelIndex m_incrementalFindStart;
199 struct SearchResultWindowPrivate {
200 SearchResultWindowPrivate();
202 Internal::SearchResultTreeView *m_searchResultTreeView;
203 QListWidget *m_noMatchesFoundDisplay;
204 QToolButton *m_expandCollapseButton;
205 QAction *m_expandCollapseAction;
206 QLabel *m_replaceLabel;
207 QLineEdit *m_replaceTextEdit;
208 QToolButton *m_replaceButton;
209 static const bool m_initiallyExpand = false;
210 QStackedWidget *m_widget;
211 SearchResult *m_currentSearch;
213 bool m_isShowingReplaceUI;
214 bool m_focusReplaceEdit;
217 SearchResultWindowPrivate::SearchResultWindowPrivate()
218 : m_currentSearch(0),
220 m_isShowingReplaceUI(false),
221 m_focusReplaceEdit(false)
226 using namespace Find::Internal;
229 \enum Find::SearchResultWindow::SearchMode
230 Specifies if a search should show the replace UI or not.
233 The search doesn't support replace.
234 \value SearchAndReplace
235 The search supports replace, so show the UI for it.
239 \class Find::SearchResult
240 \brief Reports user interaction like activation of a search result item.
242 Whenever a new search is initiated via startNewSearch, an instance of this
243 class is returned to provide the initiator with the hooks for handling user
248 \fn void SearchResult::activated(const Find::SearchResultItem &item)
249 \brief Sent if the user activated (e.g. double-clicked) a search result
254 \fn void SearchResult::replaceButtonClicked(const QString &replaceText, const QList<Find::SearchResultItem> &checkedItems)
255 \brief Sent when the user initiated a replace, e.g. by pressing the replace
258 The signal reports the text to use for replacement in \a replaceText,
259 and the list of search result items that were selected by the user
261 The handler of this signal should apply the replace only on the selected
266 \class Find::SearchResultWindow
267 \brief The SearchResultWindow class is the implementation of a commonly
268 shared \gui{Search Results} output pane. Use it to show search results
271 Whenever you want to show the user a list of search results, or want
272 to present UI for a global search and replace, use the single instance
275 Except for being an implementation of a output pane, the
276 SearchResultWindow has a few methods and one enum that allows other
277 plugins to show their search results and hook into the user actions for
278 selecting an entry and performing a global replace.
280 Whenever you start a search, call startNewSearch(SearchMode) to initialize
281 the search result window. The parameter determines if the GUI for
282 replacing should be shown.
283 The method returns a SearchResult object that is your
284 hook into the signals from user interaction for this search.
285 When you produce search results, call addResults or addResult to add them
286 to the search result window.
287 After the search has finished call finishSearch to inform the search
288 result window about it.
290 After that you get activated signals via your SearchResult instance when
291 the user selects a search result item, and, if you started the search
292 with the SearchAndReplace option, the replaceButtonClicked signal
293 when the user requests a replace.
297 \fn QString SearchResultWindow::displayName() const
301 SearchResultWindow *SearchResultWindow::m_instance = 0;
304 \fn SearchResultWindow::SearchResultWindow()
307 SearchResultWindow::SearchResultWindow() : d(new SearchResultWindowPrivate)
310 d->m_widget = new QStackedWidget;
311 d->m_widget->setWindowTitle(displayName());
313 d->m_searchResultTreeView = new Internal::SearchResultTreeView(d->m_widget);
314 d->m_searchResultTreeView->setFrameStyle(QFrame::NoFrame);
315 d->m_searchResultTreeView->setAttribute(Qt::WA_MacShowFocusRect, false);
316 d->m_widget->addWidget(d->m_searchResultTreeView);
317 Aggregation::Aggregate * agg = new Aggregation::Aggregate;
318 agg->add(d->m_searchResultTreeView);
319 agg->add(new SearchResultFindSupport(d->m_searchResultTreeView));
321 d->m_noMatchesFoundDisplay = new QListWidget(d->m_widget);
322 d->m_noMatchesFoundDisplay->addItem(tr("No matches found!"));
323 d->m_noMatchesFoundDisplay->setFrameStyle(QFrame::NoFrame);
324 d->m_widget->addWidget(d->m_noMatchesFoundDisplay);
326 d->m_expandCollapseButton = new QToolButton(d->m_widget);
327 d->m_expandCollapseButton->setAutoRaise(true);
329 d->m_expandCollapseAction = new QAction(tr("Expand All"), this);
330 d->m_expandCollapseAction->setCheckable(true);
331 d->m_expandCollapseAction->setIcon(QIcon(QLatin1String(":/find/images/expand.png")));
332 Core::Command *cmd = Core::ICore::instance()->actionManager()->registerAction(
333 d->m_expandCollapseAction, "Find.ExpandAll",
334 Core::Context(Core::Constants::C_GLOBAL));
335 d->m_expandCollapseButton->setDefaultAction(cmd->action());
337 d->m_replaceLabel = new QLabel(tr("Replace with:"), d->m_widget);
338 d->m_replaceLabel->setContentsMargins(12, 0, 5, 0);
339 d->m_replaceTextEdit = new WideEnoughLineEdit(d->m_widget);
340 d->m_replaceButton = new QToolButton(d->m_widget);
341 d->m_replaceButton->setToolTip(tr("Replace all occurrences"));
342 d->m_replaceButton->setText(tr("Replace"));
343 d->m_replaceButton->setToolButtonStyle(Qt::ToolButtonTextOnly);
344 d->m_replaceButton->setAutoRaise(true);
345 d->m_replaceTextEdit->setTabOrder(d->m_replaceTextEdit, d->m_searchResultTreeView);
347 connect(d->m_searchResultTreeView, SIGNAL(jumpToSearchResult(SearchResultItem)),
348 this, SLOT(handleJumpToSearchResult(SearchResultItem)));
349 connect(d->m_expandCollapseAction, SIGNAL(toggled(bool)), this, SLOT(handleExpandCollapseToolButton(bool)));
350 connect(d->m_replaceTextEdit, SIGNAL(returnPressed()), this, SLOT(handleReplaceButton()));
351 connect(d->m_replaceButton, SIGNAL(clicked()), this, SLOT(handleReplaceButton()));
354 setShowReplaceUI(false);
358 \fn SearchResultWindow::~SearchResultWindow()
361 SearchResultWindow::~SearchResultWindow()
364 delete d->m_currentSearch;
365 d->m_currentSearch = 0;
373 \fn SearchResultWindow *SearchResultWindow::instance()
374 \brief Returns the single shared instance of the Search Results window.
376 SearchResultWindow *SearchResultWindow::instance()
382 \fn void SearchResultWindow::setTextToReplace(const QString &textToReplace)
383 \brief Sets the value in the UI element that allows the user to type
384 the text that should replace text in search results to \a textToReplace.
386 void SearchResultWindow::setTextToReplace(const QString &textToReplace)
388 d->m_replaceTextEdit->setText(textToReplace);
392 \fn QString SearchResultWindow::textToReplace() const
393 \brief Returns the text that should replace the text in search results.
395 QString SearchResultWindow::textToReplace() const
397 return d->m_replaceTextEdit->text();
401 \fn void SearchResultWindow::setShowReplaceUI(bool show)
404 void SearchResultWindow::setShowReplaceUI(bool show)
406 d->m_searchResultTreeView->model()->setShowReplaceUI(show);
407 d->m_replaceLabel->setVisible(show);
408 d->m_replaceTextEdit->setVisible(show);
409 d->m_replaceButton->setVisible(show);
410 d->m_isShowingReplaceUI = show;
414 \fn void SearchResultWindow::handleReplaceButton()
417 void SearchResultWindow::handleReplaceButton()
419 QTC_ASSERT(d->m_currentSearch, return);
420 // check if button is actually enabled, because this is also triggered
421 // by pressing return in replace line edit
422 if (d->m_replaceButton->isEnabled())
423 d->m_currentSearch->replaceButtonClicked(d->m_replaceTextEdit->text(), checkedItems());
427 \fn QList<SearchResultItem> SearchResultWindow::checkedItems() const
430 QList<SearchResultItem> SearchResultWindow::checkedItems() const
432 QList<SearchResultItem> result;
433 Internal::SearchResultTreeModel *model = d->m_searchResultTreeView->model();
434 const int fileCount = model->rowCount(QModelIndex());
435 for (int i = 0; i < fileCount; ++i) {
436 QModelIndex fileIndex = model->index(i, 0, QModelIndex());
437 Internal::SearchResultTreeItem *fileItem = static_cast<Internal::SearchResultTreeItem *>(fileIndex.internalPointer());
438 Q_ASSERT(fileItem != 0);
439 for (int rowIndex = 0; rowIndex < fileItem->childrenCount(); ++rowIndex) {
440 QModelIndex textIndex = model->index(rowIndex, 0, fileIndex);
441 Internal::SearchResultTreeItem *rowItem = static_cast<Internal::SearchResultTreeItem *>(textIndex.internalPointer());
442 if (rowItem->checkState())
443 result << rowItem->item;
450 \fn void SearchResultWindow::visibilityChanged(bool)
453 void SearchResultWindow::visibilityChanged(bool /*visible*/)
458 \fn QWidget *SearchResultWindow::outputWidget(QWidget *)
461 QWidget *SearchResultWindow::outputWidget(QWidget *)
467 \fn QList<QWidget*> SearchResultWindow::toolBarWidgets() const
470 QList<QWidget*> SearchResultWindow::toolBarWidgets() const
472 return QList<QWidget*>() << d->m_expandCollapseButton << d->m_replaceLabel << d->m_replaceTextEdit << d->m_replaceButton;
476 \fn SearchResult *SearchResultWindow::startNewSearch(SearchMode searchOrSearchAndReplace)
477 \brief Tells the search results window to start a new search.
479 This will clear the contents of the previous search and initialize the UI
480 with regard to showing the replace UI or not (depending on the search mode
481 in \a searchOrSearchAndReplace).
482 Returns a SearchResult object that is used for signaling user interaction
483 with the results of this search.
485 SearchResult *SearchResultWindow::startNewSearch(SearchMode searchOrSearchAndReplace)
488 setShowReplaceUI(searchOrSearchAndReplace != SearchOnly);
489 delete d->m_currentSearch;
490 d->m_currentSearch = new SearchResult;
491 return d->m_currentSearch;
495 \fn void SearchResultWindow::finishSearch()
496 \brief Notifies the search result window that the current search
497 has finished, and the UI should reflect that.
499 void SearchResultWindow::finishSearch()
501 if (d->m_itemCount > 0) {
502 d->m_replaceButton->setEnabled(true);
504 showNoMatchesFound();
509 \fn void SearchResultWindow::clearContents()
510 \brief Clears the current contents in the search result window.
512 void SearchResultWindow::clearContents()
514 d->m_replaceTextEdit->setEnabled(false);
515 d->m_replaceButton->setEnabled(false);
516 d->m_replaceTextEdit->clear();
517 d->m_searchResultTreeView->clear();
519 d->m_widget->setCurrentWidget(d->m_searchResultTreeView);
520 navigateStateChanged();
524 \fn void SearchResultWindow::showNoMatchesFound()
527 void SearchResultWindow::showNoMatchesFound()
529 d->m_replaceTextEdit->setEnabled(false);
530 d->m_replaceButton->setEnabled(false);
531 d->m_widget->setCurrentWidget(d->m_noMatchesFoundDisplay);
535 \fn bool SearchResultWindow::isEmpty() const
536 Returns if the search result window currently doesn't show any results.
538 bool SearchResultWindow::isEmpty() const
540 return (d->m_searchResultTreeView->model()->rowCount() < 1);
544 \fn int SearchResultWindow::numberOfResults() const
545 Returns the number of search results currently shown in the search
548 int SearchResultWindow::numberOfResults() const
550 return d->m_itemCount;
554 \fn bool SearchResultWindow::hasFocus()
557 bool SearchResultWindow::hasFocus()
559 return d->m_searchResultTreeView->hasFocus() || (d->m_isShowingReplaceUI && d->m_replaceTextEdit->hasFocus());
563 \fn bool SearchResultWindow::canFocus()
566 bool SearchResultWindow::canFocus()
568 return d->m_itemCount > 0;
572 \fn void SearchResultWindow::setFocus()
575 void SearchResultWindow::setFocus()
577 if (d->m_itemCount > 0) {
578 if (!d->m_isShowingReplaceUI) {
579 d->m_searchResultTreeView->setFocus();
581 if (!d->m_widget->focusWidget()
582 || d->m_widget->focusWidget() == d->m_replaceTextEdit
583 || d->m_focusReplaceEdit) {
584 d->m_replaceTextEdit->setFocus();
585 d->m_replaceTextEdit->selectAll();
587 d->m_searchResultTreeView->setFocus();
594 \fn void SearchResultWindow::setTextEditorFont(const QFont &font)
597 void SearchResultWindow::setTextEditorFont(const QFont &font)
599 d->m_searchResultTreeView->setTextEditorFont(font);
603 \fn void SearchResultWindow::handleJumpToSearchResult(int index, bool)
606 void SearchResultWindow::handleJumpToSearchResult(const SearchResultItem &item)
608 QTC_ASSERT(d->m_currentSearch, return);
609 d->m_currentSearch->activated(item);
613 \fn void SearchResultWindow::addResult(const QString &fileName, int lineNumber, const QString &rowText, int searchTermStart, int searchTermLength, const QVariant &userData)
614 \brief Adds a single result line to the search results.
616 The \a fileName, \a lineNumber and \a rowText are shown in the result line.
617 \a searchTermStart and \a searchTermLength specify the region that
618 should be visually marked (string position and length in \a rowText).
619 You can attach arbitrary \a userData to the search result, which can
620 be used e.g. when reacting to the signals of the SearchResult for your search.
624 void SearchResultWindow::addResult(const QString &fileName, int lineNumber, const QString &rowText,
625 int searchTermStart, int searchTermLength, const QVariant &userData)
627 SearchResultItem item;
628 item.path = QStringList() << QDir::toNativeSeparators(fileName);
629 item.lineNumber = lineNumber;
631 item.textMarkPos = searchTermStart;
632 item.textMarkLength = searchTermLength;
633 item.useTextEditorFont = true;
634 item.userData = userData;
635 addResults(QList<SearchResultItem>() << item, AddOrdered);
639 \fn void SearchResultWindow::addResults(QList<SearchResultItem> &items)
640 \brief Adds all of the given search result \a items to the search
645 void SearchResultWindow::addResults(QList<SearchResultItem> &items, AddMode mode)
647 bool firstItems = (d->m_itemCount == 0);
648 d->m_itemCount += items.size();
649 d->m_searchResultTreeView->addResults(items, mode);
651 d->m_replaceTextEdit->setEnabled(true);
652 // We didn't have an item before, set the focus to the search widget
653 d->m_focusReplaceEdit = true;
655 d->m_focusReplaceEdit = false;
656 d->m_searchResultTreeView->selectionModel()->select(d->m_searchResultTreeView->model()->index(0, 0, QModelIndex()), QItemSelectionModel::Select);
657 emit navigateStateChanged();
662 \fn void SearchResultWindow::handleExpandCollapseToolButton(bool checked)
665 void SearchResultWindow::handleExpandCollapseToolButton(bool checked)
667 d->m_searchResultTreeView->setAutoExpandResults(checked);
669 d->m_searchResultTreeView->expandAll();
671 d->m_searchResultTreeView->collapseAll();
675 \fn void SearchResultWindow::readSettings()
678 void SearchResultWindow::readSettings()
680 QSettings *s = Core::ICore::instance()->settings();
682 s->beginGroup(QLatin1String(SETTINGSKEYSECTIONNAME));
683 d->m_expandCollapseAction->setChecked(s->value(QLatin1String(SETTINGSKEYEXPANDRESULTS), d->m_initiallyExpand).toBool());
689 \fn void SearchResultWindow::writeSettings()
692 void SearchResultWindow::writeSettings()
694 QSettings *s = Core::ICore::instance()->settings();
696 s->beginGroup(QLatin1String(SETTINGSKEYSECTIONNAME));
697 s->setValue(QLatin1String(SETTINGSKEYEXPANDRESULTS), d->m_expandCollapseAction->isChecked());
703 \fn int SearchResultWindow::priorityInStatusBar() const
706 int SearchResultWindow::priorityInStatusBar() const
712 \fn bool SearchResultWindow::canNext()
715 bool SearchResultWindow::canNext()
717 return d->m_itemCount > 0;
721 \fn bool SearchResultWindow::canPrevious()
724 bool SearchResultWindow::canPrevious()
726 return d->m_itemCount > 0;
730 \fn void SearchResultWindow::goToNext()
733 void SearchResultWindow::goToNext()
735 if (d->m_itemCount == 0)
737 QModelIndex idx = d->m_searchResultTreeView->model()->next(d->m_searchResultTreeView->currentIndex());
739 d->m_searchResultTreeView->setCurrentIndex(idx);
740 d->m_searchResultTreeView->emitJumpToSearchResult(idx);
745 \fn void SearchResultWindow::goToPrev()
748 void SearchResultWindow::goToPrev()
750 if (!d->m_searchResultTreeView->model()->rowCount())
752 QModelIndex idx = d->m_searchResultTreeView->model()->prev(d->m_searchResultTreeView->currentIndex());
754 d->m_searchResultTreeView->setCurrentIndex(idx);
755 d->m_searchResultTreeView->emitJumpToSearchResult(idx);
760 \fn bool SearchResultWindow::canNavigate()
763 bool SearchResultWindow::canNavigate()
771 #include "searchresultwindow.moc"