searchresultwindow.cpp 19.6 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 "searchresultwindow.h"
34
#include "searchresultwidget.h"
35
#include "findtoolwindow.h"
con's avatar
con committed
36

37
#include <coreplugin/icore.h>
38 39 40 41
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/actionmanager/command.h>
#include <coreplugin/coreconstants.h>
#include <coreplugin/icontext.h>
42
#include <utils/qtcassert.h>
43

con's avatar
con committed
44 45
#include <QtCore/QFile>
#include <QtCore/QSettings>
46
#include <QtCore/QDebug>
47
#include <QtGui/QVBoxLayout>
48
#include <QtGui/QFont>
49
#include <QtGui/QAction>
50
#include <QtGui/QComboBox>
51
#include <QtGui/QScrollArea>
52
#include <QtGui/QStackedWidget>
con's avatar
con committed
53

54 55
static const char SETTINGSKEYSECTIONNAME[] = "SearchResults";
static const char SETTINGSKEYEXPANDRESULTS[] = "ExpandResults";
con's avatar
con committed
56

57
namespace Find {
con's avatar
con committed
58

con's avatar
con committed
59 60
namespace Internal {

61 62 63 64 65 66
    class SearchResultWindowPrivate : public QObject {
        Q_OBJECT
    public:
        SearchResultWindowPrivate(SearchResultWindow *window);
        bool isSearchVisible() const;
        int visibleSearchIndex() const;
con's avatar
con committed
67

68 69
        SearchResultWindow *q;
        QList<Internal::SearchResultWidget *> m_searchResultWidgets;
70 71
        QToolButton *m_expandCollapseButton;
        QAction *m_expandCollapseAction;
con's avatar
con committed
72
        static const bool m_initiallyExpand = false;
73 74
        QWidget *m_spacer;
        QComboBox *m_recentSearchesBox;
75
        QStackedWidget *m_widget;
76 77
        QList<SearchResult *> m_searchResults;
        int m_currentIndex;
78
        QFont m_font;
79 80 81

    public slots:
        void setCurrentIndex(int index);
con's avatar
con committed
82 83
    };

84 85 86 87 88 89 90 91 92 93 94
    SearchResultWindowPrivate::SearchResultWindowPrivate(SearchResultWindow *window)
        : q(window)
    {
    }

    bool SearchResultWindowPrivate::isSearchVisible() const
    {
        return m_currentIndex > 0;
    }

    int SearchResultWindowPrivate::visibleSearchIndex() const
con's avatar
con committed
95
    {
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
        return m_currentIndex - 1;
    }

    void SearchResultWindowPrivate::setCurrentIndex(int index)
    {
        if (isSearchVisible())
            m_searchResultWidgets.at(visibleSearchIndex())->notifyVisibilityChanged(false);
        m_currentIndex = index;
        m_widget->setCurrentIndex(index);
        m_recentSearchesBox->setCurrentIndex(index);
        if (!isSearchVisible()) {
            m_widget->currentWidget()->setFocus();
            m_expandCollapseButton->setEnabled(false);
        } else {
            m_searchResultWidgets.at(visibleSearchIndex())->setFocusInternally();
            m_searchResultWidgets.at(visibleSearchIndex())->notifyVisibilityChanged(true);
            m_expandCollapseButton->setEnabled(true);
        }
        q->navigateStateChanged();
con's avatar
con committed
115
    }
116
}
117

con's avatar
con committed
118 119
using namespace Find::Internal;

120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
/*!
    \enum Find::SearchResultWindow::SearchMode
    Specifies if a search should show the replace UI or not.

    \value SearchOnly
           The search doesn't support replace.
    \value SearchAndReplace
           The search supports replace, so show the UI for it.
*/

/*!
    \class Find::SearchResult
    \brief Reports user interaction like activation of a search result item.

    Whenever a new search is initiated via startNewSearch, an instance of this
    class is returned to provide the initiator with the hooks for handling user
    interaction.
*/

/*!
    \fn void SearchResult::activated(const Find::SearchResultItem &item)
    \brief Sent if the user activated (e.g. double-clicked) a search result
    \a item.
*/

/*!
    \fn void SearchResult::replaceButtonClicked(const QString &replaceText, const QList<Find::SearchResultItem> &checkedItems)
    \brief Sent when the user initiated a replace, e.g. by pressing the replace
    all button.

    The signal reports the text to use for replacement in \a replaceText,
    and the list of search result items that were selected by the user
    in \a checkedItems.
    The handler of this signal should apply the replace only on the selected
    items.
*/

/*!
    \class Find::SearchResultWindow
    \brief The SearchResultWindow class is the implementation of a commonly
    shared \gui{Search Results} output pane. Use it to show search results
    to a user.

    Whenever you want to show the user a list of search results, or want
    to present UI for a global search and replace, use the single instance
    of this class.

    Except for being an implementation of a output pane, the
    SearchResultWindow has a few methods and one enum that allows other
    plugins to show their search results and hook into the user actions for
    selecting an entry and performing a global replace.

    Whenever you start a search, call startNewSearch(SearchMode) to initialize
    the search result window. The parameter determines if the GUI for
    replacing should be shown.
    The method returns a SearchResult object that is your
    hook into the signals from user interaction for this search.
    When you produce search results, call addResults or addResult to add them
    to the search result window.
    After the search has finished call finishSearch to inform the search
    result window about it.

182
    You will get activated signals via your SearchResult instance when
183 184 185 186 187 188 189 190 191 192
    the user selects a search result item, and, if you started the search
    with the SearchAndReplace option, the replaceButtonClicked signal
    when the user requests a replace.
*/

/*!
    \fn QString SearchResultWindow::displayName() const
    \internal
*/

193 194
SearchResultWindow *SearchResultWindow::m_instance = 0;

195 196 197 198
/*!
    \fn SearchResultWindow::SearchResultWindow()
    \internal
*/
199
SearchResultWindow::SearchResultWindow(QWidget *newSearchPanel)
200
    : d(new SearchResultWindowPrivate(this))
201
{
202
    m_instance = this;
203 204 205 206 207 208 209
    d->m_spacer = new QWidget;
    d->m_spacer->setMinimumWidth(30);
    d->m_recentSearchesBox = new QComboBox;
    d->m_recentSearchesBox->setSizeAdjustPolicy(QComboBox::AdjustToContents);
    d->m_recentSearchesBox->addItem(tr("New Search"));
    connect(d->m_recentSearchesBox, SIGNAL(activated(int)), d, SLOT(setCurrentIndex(int)));

210
    d->m_widget = new QStackedWidget;
211
    d->m_widget->setWindowTitle(displayName());
212

213 214 215 216
    QScrollArea *newSearchArea = new QScrollArea(d->m_widget);
    newSearchArea->setFrameStyle(QFrame::NoFrame);
    newSearchArea->setWidget(newSearchPanel);
    d->m_widget->addWidget(newSearchArea);
217
    d->m_currentIndex = 0;
218

219 220 221 222 223 224 225
    d->m_expandCollapseButton = new QToolButton(d->m_widget);
    d->m_expandCollapseButton->setAutoRaise(true);

    d->m_expandCollapseAction = new QAction(tr("Expand All"), this);
    d->m_expandCollapseAction->setCheckable(true);
    d->m_expandCollapseAction->setIcon(QIcon(QLatin1String(":/find/images/expand.png")));
    Core::Command *cmd = Core::ICore::instance()->actionManager()->registerAction(
hjk's avatar
hjk committed
226
            d->m_expandCollapseAction, "Find.ExpandAll",
227
            Core::Context(Core::Constants::C_GLOBAL));
228
    cmd->setAttribute(Core::Command::CA_UpdateText);
229
    d->m_expandCollapseButton->setDefaultAction(cmd->action());
230

231
    connect(d->m_expandCollapseAction, SIGNAL(toggled(bool)), this, SLOT(handleExpandCollapseToolButton(bool)));
con's avatar
con committed
232 233 234
    readSettings();
}

235 236 237 238
/*!
    \fn SearchResultWindow::~SearchResultWindow()
    \internal
*/
con's avatar
con committed
239 240 241
SearchResultWindow::~SearchResultWindow()
{
    writeSettings();
242
    qDeleteAll(d->m_searchResults);
243 244 245
    delete d->m_widget;
    d->m_widget = 0;
    delete d;
con's avatar
con committed
246 247
}

248 249 250 251
/*!
    \fn SearchResultWindow *SearchResultWindow::instance()
    \brief Returns the single shared instance of the Search Results window.
*/
252 253 254 255 256
SearchResultWindow *SearchResultWindow::instance()
{
    return m_instance;
}

257 258 259 260
/*!
    \fn void SearchResultWindow::visibilityChanged(bool)
    \internal
*/
261
void SearchResultWindow::visibilityChanged(bool visible)
con's avatar
con committed
262
{
263 264
    if (d->isSearchVisible())
        d->m_searchResultWidgets.at(d->visibleSearchIndex())->notifyVisibilityChanged(visible);
con's avatar
con committed
265 266
}

267 268 269 270
/*!
    \fn QWidget *SearchResultWindow::outputWidget(QWidget *)
    \internal
*/
con's avatar
con committed
271 272
QWidget *SearchResultWindow::outputWidget(QWidget *)
{
273
    return d->m_widget;
con's avatar
con committed
274 275
}

276 277 278 279
/*!
    \fn QList<QWidget*> SearchResultWindow::toolBarWidgets() const
    \internal
*/
Thorbjørn Lindeijer's avatar
Thorbjørn Lindeijer committed
280
QList<QWidget*> SearchResultWindow::toolBarWidgets() const
con's avatar
con committed
281
{
282
    return QList<QWidget*>() << d->m_expandCollapseButton << d->m_spacer << d->m_recentSearchesBox;
con's avatar
con committed
283 284
}

285 286 287
/*!
    \brief Tells the search results window to start a new search.

288 289 290 291 292 293
    The \a label should be a string that shortly describes the type of
    search, i.e. search filter and possibly a most relevant search option, followed by a colon ':'.
    E.g. \code{Project 'myproject':}
    The \a searchTerm will be shown behind the colon.
    The \a toolTip should elaborate on the search parameters, like file patterns that are searched and
    find flags.
294 295 296
    If \a cfgGroup is not empty, it will be used for storing the "do not ask again"
    setting of a "this change cannot be undone" warning (which is implicitly requested
    by passing a non-empty group).
297 298
    Returns a SearchResult object that is used for signaling user interaction
    with the results of this search.
299 300 301 302 303
    The search result window owns the returned SearchResult
    and might delete it any time, even while the search is running
    (e.g. when the user clears the search result pane, or if the user opens so many other searches
    that this search falls out of the history).

304
*/
305 306 307 308 309
SearchResult *SearchResultWindow::startNewSearch(const QString &label,
                                                 const QString &toolTip,
                                                 const QString &searchTerm,
                                                 SearchMode searchOrSearchAndReplace,
                                                 const QString &cfgGroup)
310
{
311 312 313 314 315
    if (d->m_searchResults.size() >= 5) {
        d->m_searchResultWidgets.last()->notifyVisibilityChanged(false);
        delete d->m_searchResults.takeLast();
        delete d->m_searchResultWidgets.takeLast();
        d->m_recentSearchesBox->removeItem(d->m_recentSearchesBox->count()-1);
316 317 318 319
        if (d->m_currentIndex >= d->m_recentSearchesBox->count()) {
            // temporarily set the index to the last existing
            d->m_currentIndex = d->m_recentSearchesBox->count() - 1;
        }
320 321 322 323 324
    }
    Internal::SearchResultWidget *widget = new Internal::SearchResultWidget;
    d->m_searchResultWidgets.prepend(widget);
    d->m_widget->insertWidget(1, widget);
    connect(widget, SIGNAL(navigateStateChanged()), this, SLOT(navigateStateChanged()));
325
    widget->setTextEditorFont(d->m_font);
326
    widget->setShowReplaceUI(searchOrSearchAndReplace != SearchOnly);
327
    widget->setAutoExpandResults(d->m_expandCollapseAction->isChecked());
328 329 330 331 332 333 334 335 336 337
    widget->setInfo(label, toolTip, searchTerm);
    if (searchOrSearchAndReplace == SearchAndReplace)
        widget->setDontAskAgainGroup(cfgGroup);
    SearchResult *result = new SearchResult(widget);
    d->m_searchResults.prepend(result);
    d->m_recentSearchesBox->insertItem(1, tr("%1 %2").arg(label, searchTerm));
    if (d->m_currentIndex > 0)
        ++d->m_currentIndex; // so setCurrentIndex still knows about the right "currentIndex" and its widget
    d->setCurrentIndex(1);
    return result;
338 339
}

340 341 342 343
/*!
    \fn void SearchResultWindow::clearContents()
    \brief Clears the current contents in the search result window.
*/
con's avatar
con committed
344 345
void SearchResultWindow::clearContents()
{
346 347 348 349 350 351 352 353 354 355 356 357
    for (int i = d->m_recentSearchesBox->count() - 1; i > 0 /* don't want i==0 */; --i)
        d->m_recentSearchesBox->removeItem(i);
    foreach (Internal::SearchResultWidget *widget, d->m_searchResultWidgets)
        widget->notifyVisibilityChanged(false);
    qDeleteAll(d->m_searchResultWidgets);
    d->m_searchResultWidgets.clear();
    qDeleteAll(d->m_searchResults);
    d->m_searchResults.clear();

    d->m_currentIndex = 0;
    d->m_widget->currentWidget()->setFocus();
    d->m_expandCollapseButton->setEnabled(false);
358
    navigateStateChanged();
con's avatar
con committed
359 360
}

361 362 363 364
/*!
    \fn bool SearchResultWindow::hasFocus()
    \internal
*/
365
bool SearchResultWindow::hasFocus() const
366
{
367
    return d->m_widget->focusWidget() && d->m_widget->focusWidget()->hasFocus();
368 369
}

370 371 372 373
/*!
    \fn bool SearchResultWindow::canFocus()
    \internal
*/
374
bool SearchResultWindow::canFocus() const
375
{
376 377 378
    if (d->isSearchVisible())
        return d->m_searchResultWidgets.at(d->visibleSearchIndex())->canFocusInternally();
    return true;
379 380
}

381 382 383 384
/*!
    \fn void SearchResultWindow::setFocus()
    \internal
*/
385 386
void SearchResultWindow::setFocus()
{
387
    if (!d->isSearchVisible())
388 389
        d->m_widget->currentWidget()->setFocus();
    else
390
        d->m_searchResultWidgets.at(d->visibleSearchIndex())->setFocusInternally();
391 392
}

393 394 395 396
/*!
    \fn void SearchResultWindow::setTextEditorFont(const QFont &font)
    \internal
*/
397 398
void SearchResultWindow::setTextEditorFont(const QFont &font)
{
399
    d->m_font = font;
400 401
    foreach (Internal::SearchResultWidget *widget, d->m_searchResultWidgets)
        widget->setTextEditorFont(font);
402 403
}

404 405
void SearchResultWindow::openNewSearchPanel()
{
406
    d->setCurrentIndex(0);
407
    popup(true/*focus*/, true/*sizeHint*/);
408 409
}

410 411 412 413
/*!
    \fn void SearchResultWindow::handleExpandCollapseToolButton(bool checked)
    \internal
*/
con's avatar
con committed
414 415
void SearchResultWindow::handleExpandCollapseToolButton(bool checked)
{
416 417 418
    if (!d->isSearchVisible())
        return;
    d->m_searchResultWidgets.at(d->visibleSearchIndex())->setAutoExpandResults(checked);
419 420
    if (checked) {
        d->m_expandCollapseAction->setText(tr("Collapse All"));
421
        d->m_searchResultWidgets.at(d->visibleSearchIndex())->expandAll();
422 423
    } else {
        d->m_expandCollapseAction->setText(tr("Expand All"));
424
        d->m_searchResultWidgets.at(d->visibleSearchIndex())->collapseAll();
425
    }
con's avatar
con committed
426 427
}

428 429 430 431
/*!
    \fn void SearchResultWindow::readSettings()
    \internal
*/
Thorbjørn Lindeijer's avatar
Thorbjørn Lindeijer committed
432
void SearchResultWindow::readSettings()
con's avatar
con committed
433
{
434 435
    QSettings *s = Core::ICore::instance()->settings();
    if (s) {
436
        s->beginGroup(QLatin1String(SETTINGSKEYSECTIONNAME));
437
        d->m_expandCollapseAction->setChecked(s->value(QLatin1String(SETTINGSKEYEXPANDRESULTS), d->m_initiallyExpand).toBool());
con's avatar
con committed
438 439 440 441
        s->endGroup();
    }
}

442 443 444 445
/*!
    \fn void SearchResultWindow::writeSettings()
    \internal
*/
Thorbjørn Lindeijer's avatar
Thorbjørn Lindeijer committed
446
void SearchResultWindow::writeSettings()
con's avatar
con committed
447
{
448 449
    QSettings *s = Core::ICore::instance()->settings();
    if (s) {
450
        s->beginGroup(QLatin1String(SETTINGSKEYSECTIONNAME));
451
        s->setValue(QLatin1String(SETTINGSKEYEXPANDRESULTS), d->m_expandCollapseAction->isChecked());
con's avatar
con committed
452 453 454 455
        s->endGroup();
    }
}

456 457 458 459
/*!
    \fn int SearchResultWindow::priorityInStatusBar() const
    \internal
*/
con's avatar
con committed
460 461 462 463
int SearchResultWindow::priorityInStatusBar() const
{
    return 80;
}
464

465 466 467 468
/*!
    \fn bool SearchResultWindow::canNext()
    \internal
*/
469
bool SearchResultWindow::canNext() const
470
{
471 472 473
    if (d->isSearchVisible())
        return d->m_searchResultWidgets.at(d->visibleSearchIndex())->count() > 0;
    return false;
474 475
}

476 477 478 479
/*!
    \fn bool SearchResultWindow::canPrevious()
    \internal
*/
480
bool SearchResultWindow::canPrevious() const
481
{
482
    return canNext();
483 484
}

485 486 487 488
/*!
    \fn void SearchResultWindow::goToNext()
    \internal
*/
489 490
void SearchResultWindow::goToNext()
{
491 492 493
    int index = d->m_widget->currentIndex();
    if (index != 0)
        d->m_searchResultWidgets.at(index-1)->goToNext();
494
}
495 496 497 498 499

/*!
    \fn void SearchResultWindow::goToPrev()
    \internal
*/
500 501
void SearchResultWindow::goToPrev()
{
502 503 504
    int index = d->m_widget->currentIndex();
    if (index != 0)
        d->m_searchResultWidgets.at(index-1)->goToPrevious();
505 506
}

507 508 509 510
/*!
    \fn bool SearchResultWindow::canNavigate()
    \internal
*/
511
bool SearchResultWindow::canNavigate() const
512 513 514
{
    return true;
}
515

516 517 518 519 520 521 522 523 524 525 526
/*!
    \fn SearchResult::SearchResult(SearchResultWidget *widget)
    \internal
*/
SearchResult::SearchResult(SearchResultWidget *widget)
    : m_widget(widget)
{
    connect(widget, SIGNAL(activated(Find::SearchResultItem)),
            this, SIGNAL(activated(Find::SearchResultItem)));
    connect(widget, SIGNAL(replaceButtonClicked(QString,QList<Find::SearchResultItem>)),
            this, SIGNAL(replaceButtonClicked(QString,QList<Find::SearchResultItem>)));
527 528
    connect(widget, SIGNAL(cancelled()),
            this, SIGNAL(cancelled()));
529 530 531 532
    connect(widget, SIGNAL(visibilityChanged(bool)),
            this, SIGNAL(visibilityChanged(bool)));
}

533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560
/*!
    \fn void SearchResult::setUserData(const QVariant &data)
    \brief Attach some random \a data to this search, that you can use later.

    \sa userData()
*/
void SearchResult::setUserData(const QVariant &data)
{
    m_userData = data;
}

/*!
    \fn void SearchResult::userData()
    \brief Return the data that was attached to this search by calling setUserData().

    \sa setUserData()
*/
QVariant SearchResult::userData() const
{
    return m_userData;
}

/*!
    \fn QString SearchResult::textToReplace() const
    \brief Returns the text that should replace the text in search results.
*/
QString SearchResult::textToReplace() const
{
561
    return m_widget->textToReplace();
562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578
}

/*!
    \fn void SearchResult::addResult(const QString &fileName, int lineNumber, const QString &rowText, int searchTermStart, int searchTermLength, const QVariant &userData)
    \brief Adds a single result line to the search results.

    The \a fileName, \a lineNumber and \a rowText are shown in the result line.
    \a searchTermStart and \a searchTermLength specify the region that
    should be visually marked (string position and length in \a rowText).
    You can attach arbitrary \a userData to the search result, which can
    be used e.g. when reacting to the signals of the SearchResult for your search.

    \sa addResults()
*/
void SearchResult::addResult(const QString &fileName, int lineNumber, const QString &lineText,
                             int searchTermStart, int searchTermLength, const QVariant &userData)
{
579 580
    m_widget->addResult(fileName, lineNumber, lineText,
                        searchTermStart, searchTermLength, userData);
581 582 583 584 585 586 587 588 589 590 591
}

/*!
    \fn void SearchResult::addResults(const QList<SearchResultItem> &items, SearchResult::AddMode mode)
    \brief Adds all of the given search result \a items to the search
    results window.

    \sa addResult()
*/
void SearchResult::addResults(const QList<SearchResultItem> &items, AddMode mode)
{
592
    m_widget->addResults(items, mode);
593 594 595 596 597 598 599 600 601
}

/*!
    \fn void SearchResult::finishSearch()
    \brief Notifies the search result window that the current search
    has finished, and the UI should reflect that.
*/
void SearchResult::finishSearch()
{
602
    m_widget->finishSearch();
603 604 605 606 607 608 609 610 611
}

/*!
    \fn void SearchResult::setTextToReplace(const QString &textToReplace)
    \brief Sets the value in the UI element that allows the user to type
    the text that should replace text in search results to \a textToReplace.
*/
void SearchResult::setTextToReplace(const QString &textToReplace)
{
612
    m_widget->setTextToReplace(textToReplace);
613 614
}

615
} // namespace Find
616 617

#include "searchresultwindow.moc"