Newer
Older
/**************************************************************************
** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
** Contact: Nokia Corporation (qt-info@nokia.com)
** 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.
** GNU Lesser General Public License Usage
** 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.
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://www.qtsoftware.com/contact.
**************************************************************************/
#include <qglobal.h>
namespace QuickOpen {
struct FilterEntry;
}
QT_BEGIN_NAMESPACE
unsigned int qHash(const QuickOpen::FilterEntry &entry);
QT_END_NAMESPACE
#include "quickopentoolwindow.h"
#include "quickopenplugin.h"
#include "quickopenconstants.h"
#include <extensionsystem/pluginmanager.h>
#include <coreplugin/icore.h>
#include <coreplugin/modemanager.h>
#include <coreplugin/coreconstants.h>
#include <coreplugin/fileiconprovider.h>
#include <utils/fancylineedit.h>
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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
110
111
112
113
114
115
116
117
118
119
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
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
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
#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>
Q_DECLARE_METATYPE(QuickOpen::IQuickOpenFilter*);
Q_DECLARE_METATYPE(QuickOpen::FilterEntry);
namespace QuickOpen {
namespace Internal {
/*! A model to represent the QuickOpen results. */
class QuickOpenModel : public QAbstractListModel
{
public:
QuickOpenModel(QObject *parent = 0)
: 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
} // namespace QuickOpen
using namespace QuickOpen;
using namespace QuickOpen::Internal;
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
// =========== QuickOpenModel ===========
int QuickOpenModel::rowCount(const QModelIndex & /* parent */) const
{
return mEntries.size();
}
int QuickOpenModel::columnCount(const QModelIndex &parent) const
{
return parent.isValid() ? 0 : 2;
}
/*!
* When asked for the icon via Qt::DecorationRole, the QuickOpenModel lazily
* resolves and caches the Greehouse-specific file icon when
* FilterEntry::resolveFileIcon is true. FilterEntry::internalData is assumed
* to be the filename.
*/
QVariant QuickOpenModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || index.row() >= mEntries.size())
return QVariant();
if (role == Qt::DisplayRole) {
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();
}
void QuickOpenModel::setEntries(const QList<FilterEntry> &entries)
{
mEntries = entries;
reset();
}
#if 0
void QuickOpenModel::setDisplayCount(int count)
{
// 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);
}
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
m_preferredSize = QSize(730, //qMax(600, preferredWidth),
shint.height() * 17 + frameWidth() * 2);
//header()->setStretchLastSection(true);
}
// =========== QuickOpenToolWindow ===========
QuickOpenToolWindow::QuickOpenToolWindow(QuickOpenPlugin *qop) :
m_quickOpenPlugin(qop),
m_quickOpenModel(new QuickOpenModel(this)),
m_completionList(new CompletionList(this)),
m_filterMenu(new QMenu(this)),
m_refreshAction(new QAction(tr("Refresh"), this)),
m_configureAction(new QAction(tr("Configure..."), this)),
m_fileLineEdit(new Core::Utils::FancyLineEdit)
{
// Explicitly hide the completion list popup.
setFocusProxy(m_fileLineEdit);
setWindowTitle(tr("Locate..."));
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);
setWindowIcon(QIcon(":/quickopen/images/quickopen.png"));
QPixmap image(Core::Constants::ICON_MAGNIFIER);
m_fileLineEdit->setPixmap(image);
m_fileLineEdit->setUseLayoutDirection(true);
m_fileLineEdit->setHintText(tr("Type to locate"));
m_fileLineEdit->setAttribute(Qt::WA_MacShowFocusRect, false);
m_fileLineEdit->installEventFilter(this);
this->installEventFilter(this);
m_completionList->setModel(m_quickOpenModel);
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);
connect(m_refreshAction, SIGNAL(triggered()), m_quickOpenPlugin, SLOT(refresh()));
connect(m_configureAction, SIGNAL(triggered()), this, SLOT(showConfigureDialog()));
connect(m_fileLineEdit, SIGNAL(textEdited(const QString&)),
this, SLOT(showPopup()));
connect(m_completionList, SIGNAL(activated(QModelIndex)),
this, SLOT(acceptCurrentEntry()));
}
bool QuickOpenToolWindow::isShowingTypeHereMessage() const
{
return m_fileLineEdit->isShowingHintText();
}
void QuickOpenToolWindow::updateFilterList()
{
m_filterMenu->clear();
foreach (IQuickOpenFilter *filter, m_quickOpenPlugin->filters()) {
if (!filter->shortcutString().isEmpty() && !filter->isHidden()) {
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
QAction *action = m_filterMenu->addAction(filter->trName(), this, SLOT(filterSelected()));
action->setData(qVariantFromValue(filter));
}
}
m_filterMenu->addSeparator();
m_filterMenu->addAction(m_refreshAction);
m_filterMenu->addAction(m_configureAction);
}
bool QuickOpenToolWindow::eventFilter(QObject *obj, QEvent *event)
{
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) {
} 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);
}
void QuickOpenToolWindow::showCompletionList()
{
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();
}
void QuickOpenToolWindow::showPopup()
updateCompletionList(m_fileLineEdit->typedText());
showCompletionList();
}
QList<IQuickOpenFilter*> QuickOpenToolWindow::filtersFor(const QString &text, QString &searchText)
{
QList<IQuickOpenFilter*> filters = m_quickOpenPlugin->filters();
int whiteSpace = text.indexOf(" ");
QString prefix;
if (whiteSpace >= 0)
prefix = text.left(whiteSpace);
if (!prefix.isEmpty()) {
prefix = prefix.toLower();
foreach (IQuickOpenFilter *filter, filters) {
if (prefix == filter->shortcutString()) {
searchText = text.mid(whiteSpace+1);
return QList<IQuickOpenFilter*>() << filter;
}
}
}
searchText = text;
QList<IQuickOpenFilter*> activeFilters;
foreach (IQuickOpenFilter *filter, filters)
if (filter->isIncludedByDefault())
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
activeFilters << filter;
return activeFilters;
}
void QuickOpenToolWindow::updateCompletionList(const QString &text)
{
QString searchText;
const QList<IQuickOpenFilter*> filters = filtersFor(text, searchText);
QSet<FilterEntry> alreadyAdded;
const bool checkDuplicates = (filters.size() > 1);
QList<FilterEntry> entries;
foreach (IQuickOpenFilter *filter, filters) {
foreach (const FilterEntry &entry, filter->matchesFor(searchText)) {
if (checkDuplicates && alreadyAdded.contains(entry))
continue;
entries.append(entry);
if (checkDuplicates)
alreadyAdded.insert(entry);
}
}
m_quickOpenModel->setEntries(entries);
if (m_quickOpenModel->rowCount() > 0) {
m_completionList->setCurrentIndex(m_quickOpenModel->index(0, 0));
}
#if 0
m_completionList->updatePreferredSize();
#endif
}
void QuickOpenToolWindow::acceptCurrentEntry()
{
if (!m_completionList->isVisible())
return;
const QModelIndex index = m_completionList->currentIndex();
if (!index.isValid())
return;
const FilterEntry entry = m_quickOpenModel->data(index, Qt::UserRole).value<FilterEntry>();
m_completionList->hide();
entry.filter->accept(entry);
}
void QuickOpenToolWindow::show(const QString &text, int selectionStart, int selectionLength)
{
m_fileLineEdit->hideHintText();
if (!text.isEmpty())
m_fileLineEdit->setText(text);
if (!m_fileLineEdit->hasFocus())
m_fileLineEdit->setFocus();
else
showPopup();
if (selectionLength == 0) // make sure the cursor is at the right position (Mac-vs.-rest difference)
m_fileLineEdit->setCursorPosition(selectionStart);
} else {
m_fileLineEdit->selectAll();
QString searchText = tr("<type here>");
IQuickOpenFilter *filter = action->data().value<IQuickOpenFilter*>();
QString currentText = m_fileLineEdit->text().trimmed();
// add shortcut string at front or replace existing shortcut string
if (!currentText.isEmpty()) {
searchText = currentText;
foreach (IQuickOpenFilter *otherfilter, m_quickOpenPlugin->filters()) {
if (currentText.startsWith(otherfilter->shortcutString() + " ")) {
searchText = currentText.mid(otherfilter->shortcutString().length()+1);
break;
}
}
}
show(filter->shortcutString() + " " + searchText,
searchText.length());
updateCompletionList(m_fileLineEdit->text());
m_fileLineEdit->setFocus();
}
void QuickOpenToolWindow::showEvent(QShowEvent *event)
{
QWidget::showEvent(event);
}
void QuickOpenToolWindow::showConfigureDialog()
{
Core::ICore::instance()->showOptionsDialog(Constants::QUICKOPEN_CATEGORY,