locatorwidget.cpp 15.9 KB
Newer Older
1
/**************************************************************************
con's avatar
con committed
2
3
4
**
** This file is part of Qt Creator
**
5
** Copyright (c) 2009 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
** Commercial Usage
10
**
11
12
13
14
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
15
**
16
** GNU Lesser General Public License Usage
17
**
18
19
20
21
22
23
** Alternatively, 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.
24
**
25
** If you are unsure which license is appropriate for your use, please
hjk's avatar
hjk committed
26
** contact the sales department at http://qt.nokia.com/contact.
con's avatar
con committed
27
**
28
**************************************************************************/
con's avatar
con committed
29
30
31

#include <qglobal.h>

con's avatar
con committed
32
namespace Locator {
con's avatar
con committed
33
34
35
36
struct FilterEntry;
}

QT_BEGIN_NAMESPACE
con's avatar
con committed
37
unsigned int qHash(const Locator::FilterEntry &entry);
con's avatar
con committed
38
39
QT_END_NAMESPACE

40
#include "locatorwidget.h"
con's avatar
con committed
41
#include "locatorplugin.h"
con's avatar
con committed
42
#include "locatorconstants.h"
con's avatar
con committed
43
44
45
46
47
48
49

#include <extensionsystem/pluginmanager.h>
#include <coreplugin/icore.h>
#include <coreplugin/modemanager.h>
#include <coreplugin/coreconstants.h>
#include <coreplugin/fileiconprovider.h>
#include <utils/fancylineedit.h>
hjk's avatar
hjk committed
50
#include <utils/qtcassert.h>
con's avatar
con committed
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69

#include <QtCore/QFileInfo>
#include <QtCore/QFile>
#include <QtCore/QTimer>
#include <QtCore/QSettings>
#include <QtGui/QAction>
#include <QtGui/QApplication>
#include <QtGui/QContextMenuEvent>
#include <QtGui/QHBoxLayout>
#include <QtGui/QHeaderView>
#include <QtGui/QKeyEvent>
#include <QtGui/QLabel>
#include <QtGui/QLineEdit>
#include <QtGui/QMenu>
#include <QtGui/QMouseEvent>
#include <QtGui/QPushButton>
#include <QtGui/QScrollBar>
#include <QtGui/QTreeView>

con's avatar
con committed
70
71
Q_DECLARE_METATYPE(Locator::ILocatorFilter*);
Q_DECLARE_METATYPE(Locator::FilterEntry);
con's avatar
con committed
72

con's avatar
con committed
73
namespace Locator {
con's avatar
con committed
74
75
namespace Internal {

con's avatar
con committed
76
/*! A model to represent the Locator results. */
con's avatar
con committed
77
class LocatorModel : public QAbstractListModel
con's avatar
con committed
78
79
{
public:
con's avatar
con committed
80
    LocatorModel(QObject *parent = 0)
con's avatar
con committed
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
        : QAbstractListModel(parent)
//        , mDisplayCount(64)
    {}

    int rowCount(const QModelIndex &parent = QModelIndex()) const;
    int columnCount(const QModelIndex &parent = QModelIndex()) const;
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;

    void setEntries(const QList<FilterEntry> &entries);
    //void setDisplayCount(int count);

private:
    mutable QList<FilterEntry> mEntries;
    //int mDisplayCount;
};

class CompletionList : public QTreeView
{
public:
    CompletionList(QWidget *parent = 0);

    void updatePreferredSize();
    QSize preferredSize() const { return m_preferredSize; }

private:
    QSize m_preferredSize;
};

} // namespace Internal
con's avatar
con committed
110
} // namespace Locator
con's avatar
con committed
111

con's avatar
con committed
112
113
using namespace Locator;
using namespace Locator::Internal;
con's avatar
con committed
114
115
116
117
118
119
120
121
122
123
124

QT_BEGIN_NAMESPACE
uint qHash(const FilterEntry &entry)
{
    if (entry.internalData.canConvert(QVariant::String))
        return qHash(entry.internalData.toString());
    return qHash(entry.internalData.constData());
}
QT_END_NAMESPACE


con's avatar
con committed
125
// =========== LocatorModel ===========
con's avatar
con committed
126

con's avatar
con committed
127
int LocatorModel::rowCount(const QModelIndex & /* parent */) const
con's avatar
con committed
128
129
130
131
{
    return mEntries.size();
}

con's avatar
con committed
132
int LocatorModel::columnCount(const QModelIndex &parent) const
con's avatar
con committed
133
134
135
136
137
{
    return parent.isValid() ? 0 : 2;
}

/*!
con's avatar
con committed
138
 * When asked for the icon via Qt::DecorationRole, the LocatorModel lazily
con's avatar
con committed
139
140
141
142
 * resolves and caches the Greehouse-specific file icon when
 * FilterEntry::resolveFileIcon is true. FilterEntry::internalData is assumed
 * to be the filename.
 */
con's avatar
con committed
143
QVariant LocatorModel::data(const QModelIndex &index, int role) const
con's avatar
con committed
144
145
146
147
{
    if (!index.isValid() || index.row() >= mEntries.size())
        return QVariant();

148
    if (role == Qt::DisplayRole  || role == Qt::ToolTipRole) {
con's avatar
con committed
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
        if (index.column() == 0) {
            return mEntries.at(index.row()).displayName;
        } else if (index.column() == 1) {
            return mEntries.at(index.row()).extraInfo;
        }
    } else if (role == Qt::DecorationRole && index.column() == 0) {
        FilterEntry &entry = mEntries[index.row()];
        if (entry.resolveFileIcon && entry.displayIcon.isNull()) {
            entry.resolveFileIcon = false;
            entry.displayIcon =
                 Core::FileIconProvider::instance()->icon(QFileInfo(entry.internalData.toString()));
        }
        return entry.displayIcon;
    } else if (role == Qt::ForegroundRole && index.column() == 1) {
        return Qt::darkGray;
    } else if (role == Qt::UserRole) {
        return qVariantFromValue(mEntries.at(index.row()));
    }

    return QVariant();
}

con's avatar
con committed
171
void LocatorModel::setEntries(const QList<FilterEntry> &entries)
con's avatar
con committed
172
173
174
175
176
{
    mEntries = entries;
    reset();
}
#if 0
con's avatar
con committed
177
void LocatorModel::setDisplayCount(int count)
con's avatar
con committed
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
{
    // TODO: This method is meant to be used for increasing the number of items displayed at the
    // user's request. There is however no way yet for the user to request this.
    if (count == mDisplayCount)
        return;

    const int displayedOld = qMin(mDisplayCount, mEntries.size());
    const int displayedNew = qMin(count, mEntries.size());

    if (displayedNew < displayedOld)
        beginRemoveRows(QModelIndex(), displayedNew - 1, displayedOld - 1);
    else if (displayedNew > displayedOld)
        beginInsertRows(QModelIndex(), displayedOld - 1, displayedNew - 1);

    mDisplayCount = count;

    if (displayedNew < displayedOld)
        endRemoveRows();
    else if (displayedNew > displayedOld)
        endInsertRows();
}
#endif

// =========== CompletionList ===========

CompletionList::CompletionList(QWidget *parent)
    : QTreeView(parent)
{
    setRootIsDecorated(false);
    setUniformRowHeights(true);
    setMaximumWidth(900);
    header()->hide();
    header()->setStretchLastSection(true);
    // This is too slow when done on all results
    //header()->setResizeMode(QHeaderView::ResizeToContents);
    setWindowFlags(Qt::ToolTip);
con's avatar
con committed
214
215
216
217
218
219
#ifdef Q_WS_MAC
    if (horizontalScrollBar())
        horizontalScrollBar()->setAttribute(Qt::WA_MacMiniSize);
    if (verticalScrollBar())
        verticalScrollBar()->setAttribute(Qt::WA_MacMiniSize);
#endif
con's avatar
con committed
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
}

void CompletionList::updatePreferredSize()
{
    //header()->setStretchLastSection(false);
    //updateGeometries();

    const QStyleOptionViewItem &option = viewOptions();
    const QSize shint = itemDelegate()->sizeHint(option, model()->index(0, 0));
#if 0
    const int visibleItems = model()->rowCount();

    // TODO: Look into enabling preferred height as well. Current problem is that this doesn't
    // work nicely from the user perspective if the list is popping up instead of down.
    //const int h = shint.height() * visibleItems;

    const QScrollBar *vscroll = verticalScrollBar();
    int preferredWidth = header()->length() + frameWidth() * 2
                         + (vscroll->isVisibleTo(this) ? vscroll->width() : 0);
    const int diff = preferredWidth - width();

    // Avoid resizing the list widget when there are no results or when the preferred size is
    // only a little smaller than our current size
    if (visibleItems == 0 || (diff > -100 && diff < 0))
        preferredWidth = width();
#endif

247
    m_preferredSize = QSize(730, //qMax(600, preferredWidth),
con's avatar
con committed
248
249
250
251
252
                            shint.height() * 17 + frameWidth() * 2);
    //header()->setStretchLastSection(true);
}


253
// =========== LocatorWidget ===========
con's avatar
con committed
254

con's avatar
con committed
255
256
LocatorWidget::LocatorWidget(LocatorPlugin *qop) :
     m_locatorPlugin(qop),
con's avatar
con committed
257
     m_locatorModel(new LocatorModel(this)),
con's avatar
con committed
258
259
260
261
     m_completionList(new CompletionList(this)),
     m_filterMenu(new QMenu(this)),
     m_refreshAction(new QAction(tr("Refresh"), this)),
     m_configureAction(new QAction(tr("Configure..."), this)),
262
     m_fileLineEdit(new Utils::FancyLineEdit)
con's avatar
con committed
263
{
264
    // Explicitly hide the completion list popup.
con's avatar
con committed
265
266
    m_completionList->hide();

267
    setFocusProxy(m_fileLineEdit);
268
    setWindowTitle(tr("Locate..."));
con's avatar
con committed
269
270
271
272
273
274
275
276
277
278
279
280
    resize(200, 90);
    QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
    sizePolicy.setHorizontalStretch(0);
    sizePolicy.setVerticalStretch(0);
    setSizePolicy(sizePolicy);
    setMinimumSize(QSize(200, 0));

    QHBoxLayout *layout = new QHBoxLayout(this);
    setLayout(layout);
    layout->setMargin(0);
    layout->addWidget(m_fileLineEdit);

con's avatar
con committed
281
    setWindowIcon(QIcon(":/locator/images/locator.png"));
con's avatar
con committed
282
283
284
    QPixmap image(Core::Constants::ICON_MAGNIFIER);
    m_fileLineEdit->setPixmap(image);
    m_fileLineEdit->setUseLayoutDirection(true);
285
    m_fileLineEdit->setHintText(tr("Type to locate"));
con's avatar
con committed
286
    m_fileLineEdit->setFocusPolicy(Qt::ClickFocus);
287
    m_fileLineEdit->setAttribute(Qt::WA_MacShowFocusRect, false);
con's avatar
con committed
288
289
290
291

    m_fileLineEdit->installEventFilter(this);
    this->installEventFilter(this);

con's avatar
con committed
292
    m_completionList->setModel(m_locatorModel);
con's avatar
con committed
293
294
295
296
297
298
299
300
301
    m_completionList->header()->resizeSection(0, 300);
    m_completionList->updatePreferredSize();
    m_completionList->resize(m_completionList->preferredSize());

    m_filterMenu->addAction(m_refreshAction);
    m_filterMenu->addAction(m_configureAction);

    m_fileLineEdit->setMenu( m_filterMenu);

con's avatar
con committed
302
    connect(m_refreshAction, SIGNAL(triggered()), m_locatorPlugin, SLOT(refresh()));
con's avatar
con committed
303
304
    connect(m_configureAction, SIGNAL(triggered()), this, SLOT(showConfigureDialog()));
    connect(m_fileLineEdit, SIGNAL(textEdited(const QString&)),
305
        this, SLOT(showPopup()));
con's avatar
con committed
306
307
308
309
    connect(m_completionList, SIGNAL(activated(QModelIndex)),
            this, SLOT(acceptCurrentEntry()));
}

310
bool LocatorWidget::isShowingTypeHereMessage() const
con's avatar
con committed
311
312
313
314
{
    return m_fileLineEdit->isShowingHintText();
}

315
void LocatorWidget::updateFilterList()
con's avatar
con committed
316
317
{
    m_filterMenu->clear();
con's avatar
con committed
318
    foreach (ILocatorFilter *filter, m_locatorPlugin->filters()) {
319
        if (!filter->shortcutString().isEmpty() && !filter->isHidden()) {
320
            QAction *action = m_filterMenu->addAction(filter->displayName(), this, SLOT(filterSelected()));
con's avatar
con committed
321
322
323
324
325
326
327
328
            action->setData(qVariantFromValue(filter));
        }
    }
    m_filterMenu->addSeparator();
    m_filterMenu->addAction(m_refreshAction);
    m_filterMenu->addAction(m_configureAction);
}

329
bool LocatorWidget::eventFilter(QObject *obj, QEvent *event)
con's avatar
con committed
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
{
    if (obj == m_fileLineEdit && event->type() == QEvent::KeyPress) {
        QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
        switch (keyEvent->key()) {
        case Qt::Key_Up:
        case Qt::Key_Down:
        case Qt::Key_PageUp:
        case Qt::Key_PageDown:
            showCompletionList();
            QApplication::sendEvent(m_completionList, event);
            return true;
        case Qt::Key_Enter:
        case Qt::Key_Return:
            acceptCurrentEntry();
            return true;
        case Qt::Key_Escape:
            m_completionList->hide();
            return true;
        default:
            break;
        }
    } else if (obj == m_fileLineEdit && event->type() == QEvent::FocusOut) {
        m_completionList->hide();
    } else if (obj == m_fileLineEdit && event->type() == QEvent::FocusIn) {
354
        showPopup();
con's avatar
con committed
355
356
357
358
359
360
361
362
363
364
365
    } else if (obj == this && event->type() == QEvent::ShortcutOverride) {
        QKeyEvent *ke = static_cast<QKeyEvent *>(event);
        if (ke->key() == Qt::Key_Escape && !ke->modifiers()) {
            event->accept();
            QTimer::singleShot(0, Core::ModeManager::instance(), SLOT(setFocusToCurrentMode()));
            return true;
        }
    }
    return QWidget::eventFilter(obj, event);
}

366
void LocatorWidget::showCompletionList()
con's avatar
con committed
367
368
369
370
371
372
373
374
{
    const int border = m_completionList->frameWidth();
    const QSize size = m_completionList->preferredSize();
    const QRect rect(mapToGlobal(QPoint(-border, -size.height() - border)), size);
    m_completionList->setGeometry(rect);
    m_completionList->show();
}

375
void LocatorWidget::showPopup()
con's avatar
con committed
376
{
377
    updateCompletionList(m_fileLineEdit->typedText());
con's avatar
con committed
378
379
380
    showCompletionList();
}

381
QList<ILocatorFilter*> LocatorWidget::filtersFor(const QString &text, QString &searchText)
con's avatar
con committed
382
{
con's avatar
con committed
383
    QList<ILocatorFilter*> filters = m_locatorPlugin->filters();
384
    const int whiteSpace = text.indexOf(QLatin1Char(' '));
con's avatar
con committed
385
386
387
388
389
    QString prefix;
    if (whiteSpace >= 0)
        prefix = text.left(whiteSpace);
    if (!prefix.isEmpty()) {
        prefix = prefix.toLower();
390
        foreach (ILocatorFilter *filter, filters) {
con's avatar
con committed
391
392
            if (prefix == filter->shortcutString()) {
                searchText = text.mid(whiteSpace+1);
393
                return QList<ILocatorFilter*>() << filter;
con's avatar
con committed
394
395
396
397
            }
        }
    }
    searchText = text;
398
399
    QList<ILocatorFilter*> activeFilters;
    foreach (ILocatorFilter *filter, filters)
400
        if (filter->isIncludedByDefault())
con's avatar
con committed
401
402
403
404
            activeFilters << filter;
    return activeFilters;
}

405
void LocatorWidget::updateCompletionList(const QString &text)
con's avatar
con committed
406
407
{
    QString searchText;
408
    const QList<ILocatorFilter*> filters = filtersFor(text, searchText);
con's avatar
con committed
409
410
411
    QSet<FilterEntry> alreadyAdded;
    const bool checkDuplicates = (filters.size() > 1);
    QList<FilterEntry> entries;
412
    foreach (ILocatorFilter *filter, filters) {
con's avatar
con committed
413
414
415
416
417
418
419
420
        foreach (const FilterEntry &entry, filter->matchesFor(searchText)) {
            if (checkDuplicates && alreadyAdded.contains(entry))
                continue;
            entries.append(entry);
            if (checkDuplicates)
                alreadyAdded.insert(entry);
        }
    }
con's avatar
con committed
421
422
423
    m_locatorModel->setEntries(entries);
    if (m_locatorModel->rowCount() > 0) {
        m_completionList->setCurrentIndex(m_locatorModel->index(0, 0));
con's avatar
con committed
424
425
426
427
428
429
    }
#if 0
    m_completionList->updatePreferredSize();
#endif
}

430
void LocatorWidget::acceptCurrentEntry()
con's avatar
con committed
431
432
433
434
435
436
{
    if (!m_completionList->isVisible())
        return;
    const QModelIndex index = m_completionList->currentIndex();
    if (!index.isValid())
        return;
con's avatar
con committed
437
    const FilterEntry entry = m_locatorModel->data(index, Qt::UserRole).value<FilterEntry>();
con's avatar
con committed
438
439
440
441
    m_completionList->hide();
    entry.filter->accept(entry);
}

442
void LocatorWidget::show(const QString &text, int selectionStart, int selectionLength)
con's avatar
con committed
443
444
{
    m_fileLineEdit->hideHintText();
445
446
    if (!text.isEmpty())
        m_fileLineEdit->setText(text);
447
448
449
450
451
    if (!m_fileLineEdit->hasFocus())
        m_fileLineEdit->setFocus();
    else
        showPopup();

con's avatar
con committed
452
    if (selectionStart >= 0) {
con's avatar
con committed
453
        m_fileLineEdit->setSelection(selectionStart, selectionLength);
con's avatar
con committed
454
455
456
        if (selectionLength == 0) // make sure the cursor is at the right position (Mac-vs.-rest difference)
            m_fileLineEdit->setCursorPosition(selectionStart);
    } else {
457
        m_fileLineEdit->selectAll();
con's avatar
con committed
458
    }
con's avatar
con committed
459
460
}

461
void LocatorWidget::filterSelected()
con's avatar
con committed
462
{
463
    QString searchText = tr("<type here>");
con's avatar
con committed
464
    QAction *action = qobject_cast<QAction*>(sender());
hjk's avatar
hjk committed
465
    QTC_ASSERT(action, return);
466
    ILocatorFilter *filter = action->data().value<ILocatorFilter*>();
hjk's avatar
hjk committed
467
    QTC_ASSERT(filter, return);
468
469
470
471
    QString currentText = m_fileLineEdit->text().trimmed();
    // add shortcut string at front or replace existing shortcut string
    if (!currentText.isEmpty()) {
        searchText = currentText;
con's avatar
con committed
472
        foreach (ILocatorFilter *otherfilter, m_locatorPlugin->filters()) {
473
            if (currentText.startsWith(otherfilter->shortcutString() + QLatin1Char(' '))) {
474
475
476
477
478
                searchText = currentText.mid(otherfilter->shortcutString().length()+1);
                break;
            }
        }
    }
479
    show(filter->shortcutString() + QLatin1Char(' ') + searchText,
con's avatar
con committed
480
         filter->shortcutString().length() + 1,
481
         searchText.length());
con's avatar
con committed
482
483
484
485
    updateCompletionList(m_fileLineEdit->text());
    m_fileLineEdit->setFocus();
}

486
void LocatorWidget::showEvent(QShowEvent *event)
con's avatar
con committed
487
488
489
490
{
    QWidget::showEvent(event);
}

491
void LocatorWidget::showConfigureDialog()
con's avatar
con committed
492
{
con's avatar
con committed
493
    Core::ICore::instance()->showOptionsDialog(Constants::LOCATOR_CATEGORY,
con's avatar
con committed
494
495
          Constants::FILTER_OPTIONS_PAGE);
}