OSDN Git Service

8def1b26220d90ef5986649955df1e9f48b5015b
[qt-creator-jp/qt-creator-jp.git] / src / plugins / locator / locatorwidget.cpp
1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
8 **
9 ** No Commercial Usage
10 **
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
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 **
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.
24 **
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.
28 **
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
31 **
32 **************************************************************************/
33
34 #include <qglobal.h>
35
36 #include "locatorwidget.h"
37 #include "locatorplugin.h"
38 #include "locatorconstants.h"
39 #include "ilocatorfilter.h"
40
41 #include <extensionsystem/pluginmanager.h>
42 #include <coreplugin/icore.h>
43 #include <coreplugin/modemanager.h>
44 #include <coreplugin/actionmanager/actionmanager.h>
45 #include <coreplugin/actionmanager/command.h>
46 #include <coreplugin/coreconstants.h>
47 #include <coreplugin/fileiconprovider.h>
48 #include <utils/filterlineedit.h>
49 #include <utils/qtcassert.h>
50 #include <qtconcurrent/runextensions.h>
51
52 #include <QtCore/QtConcurrentRun>
53 #include <QtCore/QFileInfo>
54 #include <QtCore/QFile>
55 #include <QtCore/QTimer>
56 #include <QtCore/QSettings>
57 #include <QtCore/QEvent>
58 #include <QtGui/QAction>
59 #include <QtGui/QApplication>
60 #include <QtGui/QContextMenuEvent>
61 #include <QtGui/QHBoxLayout>
62 #include <QtGui/QHeaderView>
63 #include <QtGui/QKeyEvent>
64 #include <QtGui/QLabel>
65 #include <QtGui/QLineEdit>
66 #include <QtGui/QMenu>
67 #include <QtGui/QMouseEvent>
68 #include <QtGui/QPushButton>
69 #include <QtGui/QScrollBar>
70 #include <QtGui/QTreeView>
71
72 Q_DECLARE_METATYPE(Locator::ILocatorFilter*)
73 Q_DECLARE_METATYPE(Locator::FilterEntry)
74
75 namespace Locator {
76 namespace Internal {
77
78 /* A model to represent the Locator results. */
79 class LocatorModel : public QAbstractListModel
80 {
81 public:
82     LocatorModel(QObject *parent = 0)
83         : QAbstractListModel(parent)
84 //        , mDisplayCount(64)
85     {}
86
87     int rowCount(const QModelIndex &parent = QModelIndex()) const;
88     int columnCount(const QModelIndex &parent = QModelIndex()) const;
89     QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
90
91     void setEntries(const QList<FilterEntry> &entries);
92     //void setDisplayCount(int count);
93
94 private:
95     mutable QList<FilterEntry> mEntries;
96     //int mDisplayCount;
97 };
98
99 class CompletionList : public QTreeView
100 {
101 public:
102     CompletionList(QWidget *parent = 0);
103
104     void updatePreferredSize();
105     QSize preferredSize() const { return m_preferredSize; }
106
107 #if defined(Q_OS_WIN)
108     void focusOutEvent (QFocusEvent * event)  {
109         if (event->reason() == Qt::ActiveWindowFocusReason)
110             hide();
111     }
112 #endif
113
114     void next() {
115         int index = currentIndex().row();
116         ++index;
117         if (index >= model()->rowCount(QModelIndex())) {
118             // wrap
119             index = 0;
120         }
121         setCurrentIndex(model()->index(index, 0));
122     }
123
124     void previous() {
125         int index = currentIndex().row();
126         --index;
127         if (index < 0) {
128             // wrap
129             index = model()->rowCount(QModelIndex()) - 1;
130         }
131         setCurrentIndex(model()->index(index, 0));
132     }
133
134 private:
135     QSize m_preferredSize;
136 };
137
138 } // namespace Internal
139
140 uint qHash(const FilterEntry &entry)
141 {
142     if (entry.internalData.canConvert(QVariant::String))
143         return QT_PREPEND_NAMESPACE(qHash)(entry.internalData.toString());
144     return QT_PREPEND_NAMESPACE(qHash)(entry.internalData.constData());
145 }
146
147 } // namespace Locator
148
149 using namespace Locator;
150 using namespace Locator::Internal;
151
152 // =========== LocatorModel ===========
153
154 int LocatorModel::rowCount(const QModelIndex & /* parent */) const
155 {
156     return mEntries.size();
157 }
158
159 int LocatorModel::columnCount(const QModelIndex &parent) const
160 {
161     return parent.isValid() ? 0 : 2;
162 }
163
164 /*!
165  * When asked for the icon via Qt::DecorationRole, the LocatorModel lazily
166  * resolves and caches the Greehouse-specific file icon when
167  * FilterEntry::resolveFileIcon is true. FilterEntry::internalData is assumed
168  * to be the filename.
169  */
170 QVariant LocatorModel::data(const QModelIndex &index, int role) const
171 {
172     if (!index.isValid() || index.row() >= mEntries.size())
173         return QVariant();
174
175     if (role == Qt::DisplayRole  || role == Qt::ToolTipRole) {
176         if (index.column() == 0) {
177             return mEntries.at(index.row()).displayName;
178         } else if (index.column() == 1) {
179             return mEntries.at(index.row()).extraInfo;
180         }
181     } else if (role == Qt::DecorationRole && index.column() == 0) {
182         FilterEntry &entry = mEntries[index.row()];
183         if (entry.resolveFileIcon && entry.displayIcon.isNull()) {
184             entry.resolveFileIcon = false;
185             entry.displayIcon =
186                  Core::FileIconProvider::instance()->icon(QFileInfo(entry.internalData.toString()));
187         }
188         return entry.displayIcon;
189     } else if (role == Qt::ForegroundRole && index.column() == 1) {
190         return Qt::darkGray;
191     } else if (role == Qt::UserRole) {
192         return qVariantFromValue(mEntries.at(index.row()));
193     }
194
195     return QVariant();
196 }
197
198 void LocatorModel::setEntries(const QList<FilterEntry> &entries)
199 {
200     mEntries = entries;
201     reset();
202 }
203 #if 0
204 void LocatorModel::setDisplayCount(int count)
205 {
206     // TODO: This method is meant to be used for increasing the number of items displayed at the
207     // user's request. There is however no way yet for the user to request this.
208     if (count == mDisplayCount)
209         return;
210
211     const int displayedOld = qMin(mDisplayCount, mEntries.size());
212     const int displayedNew = qMin(count, mEntries.size());
213
214     if (displayedNew < displayedOld)
215         beginRemoveRows(QModelIndex(), displayedNew - 1, displayedOld - 1);
216     else if (displayedNew > displayedOld)
217         beginInsertRows(QModelIndex(), displayedOld - 1, displayedNew - 1);
218
219     mDisplayCount = count;
220
221     if (displayedNew < displayedOld)
222         endRemoveRows();
223     else if (displayedNew > displayedOld)
224         endInsertRows();
225 }
226 #endif
227
228 // =========== CompletionList ===========
229
230 CompletionList::CompletionList(QWidget *parent)
231     : QTreeView(parent)
232 {
233     setRootIsDecorated(false);
234     setUniformRowHeights(true);
235     setMaximumWidth(900);
236     header()->hide();
237     header()->setStretchLastSection(true);
238     // This is too slow when done on all results
239     //header()->setResizeMode(QHeaderView::ResizeToContents);
240     setWindowFlags(Qt::ToolTip);
241 #ifdef Q_WS_MAC
242     if (horizontalScrollBar())
243         horizontalScrollBar()->setAttribute(Qt::WA_MacMiniSize);
244     if (verticalScrollBar())
245         verticalScrollBar()->setAttribute(Qt::WA_MacMiniSize);
246 #endif
247 }
248
249 void CompletionList::updatePreferredSize()
250 {
251     //header()->setStretchLastSection(false);
252     //updateGeometries();
253
254     const QStyleOptionViewItem &option = viewOptions();
255     const QSize shint = itemDelegate()->sizeHint(option, model()->index(0, 0));
256 #if 0
257     const int visibleItems = model()->rowCount();
258
259     // TODO: Look into enabling preferred height as well. Current problem is that this doesn't
260     // work nicely from the user perspective if the list is popping up instead of down.
261     //const int h = shint.height() * visibleItems;
262
263     const QScrollBar *vscroll = verticalScrollBar();
264     int preferredWidth = header()->length() + frameWidth() * 2
265                          + (vscroll->isVisibleTo(this) ? vscroll->width() : 0);
266     const int diff = preferredWidth - width();
267
268     // Avoid resizing the list widget when there are no results or when the preferred size is
269     // only a little smaller than our current size
270     if (visibleItems == 0 || (diff > -100 && diff < 0))
271         preferredWidth = width();
272 #endif
273
274     m_preferredSize = QSize(730, //qMax(600, preferredWidth),
275                             shint.height() * 17 + frameWidth() * 2);
276     //header()->setStretchLastSection(true);
277 }
278
279 // =========== LocatorWidget ===========
280
281 LocatorWidget::LocatorWidget(LocatorPlugin *qop) :
282      m_locatorPlugin(qop),
283      m_locatorModel(new LocatorModel(this)),
284      m_completionList(new CompletionList(this)),
285      m_filterMenu(new QMenu(this)),
286      m_refreshAction(new QAction(tr("Refresh"), this)),
287      m_configureAction(new QAction(tr("Configure..."), this)),
288      m_fileLineEdit(new Utils::FilterLineEdit)
289 {
290     // Explicitly hide the completion list popup.
291     m_completionList->hide();
292
293     setFocusProxy(m_fileLineEdit);
294     setWindowTitle(tr("Locate..."));
295     resize(200, 90);
296     QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
297     sizePolicy.setHorizontalStretch(0);
298     sizePolicy.setVerticalStretch(0);
299     setSizePolicy(sizePolicy);
300     setMinimumSize(QSize(200, 0));
301
302     QHBoxLayout *layout = new QHBoxLayout(this);
303     setLayout(layout);
304     layout->setMargin(0);
305     layout->addWidget(m_fileLineEdit);
306
307     setWindowIcon(QIcon(QLatin1String(":/locator/images/locator.png")));
308     QPixmap image(Core::Constants::ICON_MAGNIFIER);
309     m_fileLineEdit->setButtonPixmap(Utils::FancyLineEdit::Left, image);
310     m_fileLineEdit->setPlaceholderText(tr("Type to locate"));
311     m_fileLineEdit->setButtonToolTip(Utils::FancyLineEdit::Left, tr("Options"));
312     m_fileLineEdit->setFocusPolicy(Qt::ClickFocus);
313     m_fileLineEdit->setButtonVisible(Utils::FancyLineEdit::Left, true);
314     // We set click focus since otherwise you will always get two popups
315     m_fileLineEdit->setButtonFocusPolicy(Utils::FancyLineEdit::Left, Qt::ClickFocus);
316     m_fileLineEdit->setAttribute(Qt::WA_MacShowFocusRect, false);
317
318     m_fileLineEdit->installEventFilter(this);
319     this->installEventFilter(this);
320
321     m_completionList->setModel(m_locatorModel);
322     m_completionList->header()->resizeSection(0, 300);
323     m_completionList->updatePreferredSize();
324     m_completionList->resize(m_completionList->preferredSize());
325
326     m_filterMenu->addAction(m_refreshAction);
327     m_filterMenu->addAction(m_configureAction);
328
329     m_fileLineEdit->setButtonMenu(Utils::FancyLineEdit::Left, m_filterMenu);
330
331     connect(m_refreshAction, SIGNAL(triggered()), m_locatorPlugin, SLOT(refresh()));
332     connect(m_configureAction, SIGNAL(triggered()), this, SLOT(showConfigureDialog()));
333     connect(m_fileLineEdit, SIGNAL(textChanged(const QString&)),
334         this, SLOT(showPopup()));
335     connect(m_completionList, SIGNAL(activated(QModelIndex)),
336             this, SLOT(acceptCurrentEntry()));
337
338     m_entriesWatcher = new QFutureWatcher<FilterEntry>(this);
339     connect(m_entriesWatcher, SIGNAL(finished()), SLOT(updateEntries()));
340
341     m_showPopupTimer = new QTimer(this);
342     m_showPopupTimer->setInterval(100);
343     m_showPopupTimer->setSingleShot(true);
344     connect(m_showPopupTimer, SIGNAL(timeout()), SLOT(showPopupNow()));
345 }
346
347 void LocatorWidget::updateFilterList()
348 {
349     m_filterMenu->clear();
350
351     // update actions and menu
352     Core::ActionManager *am = Core::ICore::instance()->actionManager();
353     QMap<QString, QAction *> actionCopy = m_filterActionMap;
354     m_filterActionMap.clear();
355     // register new actions, update existent
356     foreach (ILocatorFilter *filter, m_locatorPlugin->filters()) {
357         if (filter->shortcutString().isEmpty() || filter->isHidden())
358             continue;
359         QString locatorId = QLatin1String("Locator.") + filter->id();
360         QAction *action = 0;
361         Core::Command *cmd = 0;
362         if (!actionCopy.contains(filter->id())) {
363             // register new action
364             action = new QAction(filter->displayName(), this);
365             cmd = am->registerAction(action, locatorId,
366                                Core::Context(Core::Constants::C_GLOBAL));
367             cmd->setAttribute(Core::Command::CA_UpdateText);
368             connect(action, SIGNAL(triggered()), this, SLOT(filterSelected()));
369             action->setData(qVariantFromValue(filter));
370         } else {
371             action = actionCopy.take(filter->id());
372             action->setText(filter->displayName());
373             cmd = am->command(locatorId);
374         }
375         m_filterActionMap.insert(filter->id(), action);
376         m_filterMenu->addAction(cmd->action());
377     }
378
379     // unregister actions that are deleted now
380     foreach (const QString &id, actionCopy.keys()) {
381         am->unregisterAction(actionCopy.value(id), QString(QLatin1String("Locator.") + id));
382     }
383     qDeleteAll(actionCopy);
384
385     m_filterMenu->addSeparator();
386     m_filterMenu->addAction(m_refreshAction);
387     m_filterMenu->addAction(m_configureAction);
388 }
389
390 bool LocatorWidget::eventFilter(QObject *obj, QEvent *event)
391 {
392     if (obj == m_fileLineEdit && event->type() == QEvent::KeyPress) {
393         QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
394         switch (keyEvent->key()) {
395         case Qt::Key_Up:
396         case Qt::Key_Down:
397         case Qt::Key_PageUp:
398         case Qt::Key_PageDown:
399             showCompletionList();
400             QApplication::sendEvent(m_completionList, event);
401             return true;
402         case Qt::Key_Enter:
403         case Qt::Key_Return:
404             acceptCurrentEntry();
405             return true;
406         case Qt::Key_Escape:
407             m_completionList->hide();
408             return true;
409         case Qt::Key_Tab:
410             m_completionList->next();
411             return true;
412         case Qt::Key_Backtab:
413             m_completionList->previous();
414             return true;
415         default:
416             break;
417         }
418     } else if (obj == m_fileLineEdit && event->type() == QEvent::FocusOut) {
419 #if defined(Q_OS_WIN)
420         QFocusEvent *fev = static_cast<QFocusEvent*>(event);
421         if (fev->reason() != Qt::ActiveWindowFocusReason ||
422             (fev->reason() == Qt::ActiveWindowFocusReason && !m_completionList->isActiveWindow()))
423 #endif
424             m_completionList->hide();
425     } else if (obj == m_fileLineEdit && event->type() == QEvent::FocusIn) {
426         showPopupNow();
427     } else if (obj == this && event->type() == QEvent::ShortcutOverride) {
428         QKeyEvent *ke = static_cast<QKeyEvent *>(event);
429         if (ke->key() == Qt::Key_Escape && !ke->modifiers()) {
430             event->accept();
431             QTimer::singleShot(0, Core::ModeManager::instance(), SLOT(setFocusToCurrentMode()));
432             return true;
433         }
434     }
435     return QWidget::eventFilter(obj, event);
436 }
437
438 void LocatorWidget::showCompletionList()
439 {
440     const int border = m_completionList->frameWidth();
441     const QSize size = m_completionList->preferredSize();
442     const QRect rect(mapToGlobal(QPoint(-border, -size.height() - border)), size);
443     m_completionList->setGeometry(rect);
444     m_completionList->show();
445 }
446
447 void LocatorWidget::showPopup()
448 {
449     m_showPopupTimer->start();
450 }
451
452 void LocatorWidget::showPopupNow()
453 {
454     m_showPopupTimer->stop();
455     updateCompletionList(m_fileLineEdit->text());
456     showCompletionList();
457 }
458
459 QList<ILocatorFilter*> LocatorWidget::filtersFor(const QString &text, QString &searchText)
460 {
461     QList<ILocatorFilter*> filters = m_locatorPlugin->filters();
462     const int whiteSpace = text.indexOf(QLatin1Char(' '));
463     QString prefix;
464     if (whiteSpace >= 0)
465         prefix = text.left(whiteSpace);
466     if (!prefix.isEmpty()) {
467         prefix = prefix.toLower();
468         QList<ILocatorFilter *> prefixFilters;
469         foreach (ILocatorFilter *filter, filters) {
470             if (prefix == filter->shortcutString()) {
471                 searchText = text.mid(whiteSpace+1);
472                 prefixFilters << filter;
473             }
474         }
475         if (!prefixFilters.isEmpty())
476             return prefixFilters;
477     }
478     searchText = text;
479     QList<ILocatorFilter*> activeFilters;
480     foreach (ILocatorFilter *filter, filters)
481         if (filter->isIncludedByDefault())
482             activeFilters << filter;
483     return activeFilters;
484 }
485
486 static void filter_helper(QFutureInterface<Locator::FilterEntry> &entries, QList<ILocatorFilter *> filters, QString searchText)
487 {
488     QSet<FilterEntry> alreadyAdded;
489     const bool checkDuplicates = (filters.size() > 1);
490     foreach (ILocatorFilter *filter, filters) {
491         if (entries.isCanceled())
492             break;
493
494         foreach (const FilterEntry &entry, filter->matchesFor(entries, searchText)) {
495             if (checkDuplicates && alreadyAdded.contains(entry))
496                 continue;
497             entries.reportResult(entry);
498             if (checkDuplicates)
499                 alreadyAdded.insert(entry);
500         }
501     }
502 }
503
504 void LocatorWidget::updateCompletionList(const QString &text)
505 {
506     QString searchText;
507     const QList<ILocatorFilter*> filters = filtersFor(text, searchText);
508
509     // cancel the old future
510     m_entriesWatcher->future().cancel();
511     m_entriesWatcher->future().waitForFinished();
512
513     QFuture<FilterEntry> future = QtConcurrent::run(filter_helper, filters, searchText);
514     m_entriesWatcher->setFuture(future);
515 }
516
517 void LocatorWidget::updateEntries()
518 {
519     if (m_entriesWatcher->future().isCanceled())
520         return;
521
522     const QList<FilterEntry> entries = m_entriesWatcher->future().results();
523     m_locatorModel->setEntries(entries);
524     if (m_locatorModel->rowCount() > 0) {
525         m_completionList->setCurrentIndex(m_locatorModel->index(0, 0));
526     }
527 #if 0
528     m_completionList->updatePreferredSize();
529 #endif
530 }
531
532 void LocatorWidget::acceptCurrentEntry()
533 {
534     if (!m_completionList->isVisible())
535         return;
536     const QModelIndex index = m_completionList->currentIndex();
537     if (!index.isValid())
538         return;
539     const FilterEntry entry = m_locatorModel->data(index, Qt::UserRole).value<FilterEntry>();
540     m_completionList->hide();
541     entry.filter->accept(entry);
542 }
543
544 void LocatorWidget::show(const QString &text, int selectionStart, int selectionLength)
545 {
546     if (!text.isEmpty())
547         m_fileLineEdit->setText(text);
548     if (!m_fileLineEdit->hasFocus())
549         m_fileLineEdit->setFocus();
550     else
551         showPopupNow();
552
553     if (selectionStart >= 0) {
554         m_fileLineEdit->setSelection(selectionStart, selectionLength);
555         if (selectionLength == 0) // make sure the cursor is at the right position (Mac-vs.-rest difference)
556             m_fileLineEdit->setCursorPosition(selectionStart);
557     } else {
558         m_fileLineEdit->selectAll();
559     }
560 }
561
562 void LocatorWidget::filterSelected()
563 {
564     QString searchText = tr("<type here>");
565     QAction *action = qobject_cast<QAction*>(sender());
566     QTC_ASSERT(action, return);
567     ILocatorFilter *filter = action->data().value<ILocatorFilter*>();
568     QTC_ASSERT(filter, return);
569     QString currentText = m_fileLineEdit->text().trimmed();
570     // add shortcut string at front or replace existing shortcut string
571     if (!currentText.isEmpty()) {
572         searchText = currentText;
573         foreach (ILocatorFilter *otherfilter, m_locatorPlugin->filters()) {
574             if (currentText.startsWith(otherfilter->shortcutString() + QLatin1Char(' '))) {
575                 searchText = currentText.mid(otherfilter->shortcutString().length()+1);
576                 break;
577             }
578         }
579     }
580     show(filter->shortcutString() + QLatin1Char(' ') + searchText,
581          filter->shortcutString().length() + 1,
582          searchText.length());
583     updateCompletionList(m_fileLineEdit->text());
584     m_fileLineEdit->setFocus();
585 }
586
587 void LocatorWidget::showEvent(QShowEvent *event)
588 {
589     QWidget::showEvent(event);
590 }
591
592 void LocatorWidget::showConfigureDialog()
593 {
594     Core::ICore::instance()->showOptionsDialog(Constants::LOCATOR_CATEGORY,
595           Constants::FILTER_OPTIONS_PAGE);
596 }