Separate locator input widget from result list & popup

The input field may not care whether the result list is actually in a
popup or not.

Change-Id: Ia15f9a32441243de458e4e55d2daef6204b9dd59
Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
Eike Ziller
2017-06-16 10:24:26 +02:00
parent 3a45d763ca
commit e133ee8928
3 changed files with 240 additions and 114 deletions

View File

@@ -104,6 +104,7 @@ void Locator::initialize(CorePlugin *corePlugin, const QStringList &, QString *)
mtools->addAction(cmd); mtools->addAction(cmd);
auto locatorWidget = new LocatorWidget(this); auto locatorWidget = new LocatorWidget(this);
new LocatorPopup(locatorWidget, locatorWidget); // child of locatorWidget
StatusBarWidget *view = new StatusBarWidget; StatusBarWidget *view = new StatusBarWidget;
view->setWidget(locatorWidget); view->setWidget(locatorWidget);
view->setContext(Context("LocatorWidget")); view->setContext(Context("LocatorWidget"));

View File

@@ -102,38 +102,16 @@ class CompletionList : public Utils::TreeView
public: public:
CompletionList(QWidget *parent = 0); CompletionList(QWidget *parent = 0);
void resize(); void setModel(QAbstractItemModel *model);
void resizeHeaders(); void resizeHeaders();
QSize preferredSize() const { return m_preferredSize; }
void focusOutEvent (QFocusEvent *event) { void next();
if (event->reason() == Qt::ActiveWindowFocusReason) void previous();
hide();
QTreeView::focusOutEvent(event);
}
void next() { void showCurrentItemToolTip();
int index = currentIndex().row();
++index;
if (index >= model()->rowCount(QModelIndex())) {
// wrap
index = 0;
}
setCurrentIndex(model()->index(index, 0));
}
void previous() { void keyPressEvent(QKeyEvent *event);
int index = currentIndex().row();
--index;
if (index < 0) {
// wrap
index = model()->rowCount(QModelIndex()) - 1;
}
setCurrentIndex(model()->index(index, 0));
}
private:
QSize m_preferredSize;
}; };
// =========== LocatorModel =========== // =========== LocatorModel ===========
@@ -239,46 +217,200 @@ CompletionList::CompletionList(QWidget *parent)
header()->setStretchLastSection(true); header()->setStretchLastSection(true);
// This is too slow when done on all results // This is too slow when done on all results
//header()->setSectionResizeMode(QHeaderView::ResizeToContents); //header()->setSectionResizeMode(QHeaderView::ResizeToContents);
setWindowFlags(Qt::ToolTip);
if (Utils::HostOsInfo::isMacHost()) { if (Utils::HostOsInfo::isMacHost()) {
if (horizontalScrollBar()) if (horizontalScrollBar())
horizontalScrollBar()->setAttribute(Qt::WA_MacMiniSize); horizontalScrollBar()->setAttribute(Qt::WA_MacMiniSize);
if (verticalScrollBar()) if (verticalScrollBar())
verticalScrollBar()->setAttribute(Qt::WA_MacMiniSize); verticalScrollBar()->setAttribute(Qt::WA_MacMiniSize);
} }
const QStyleOptionViewItem &option = viewOptions();
const QSize shint = itemDelegate()->sizeHint(option, QModelIndex());
setFixedHeight(shint.height() * 17 + frameWidth() * 2);
} }
void CompletionList::resize() void CompletionList::setModel(QAbstractItemModel *newModel)
{ {
const QStyleOptionViewItem &option = viewOptions(); if (model()) {
const QSize shint = itemDelegate()->sizeHint(option, model()->index(0, 0)); disconnect(model(), &QAbstractItemModel::columnsInserted,
const QSize windowSize = ICore::mainWindow()->size(); this, &CompletionList::resizeHeaders);
}
QTreeView::setModel(newModel);
if (newModel) {
connect(newModel, &QAbstractItemModel::columnsInserted,
this, &CompletionList::resizeHeaders);
}
}
const int width = qMax(730, windowSize.width() * 2 / 3); void LocatorPopup::resize()
m_preferredSize = QSize(width, shint.height() * 17 + frameWidth() * 2); {
QTreeView::resize(m_preferredSize); static const int MIN_WIDTH = 730;
resizeHeaders(); const QSize windowSize = m_window ? m_window->size() : QSize(MIN_WIDTH, 0);
const int width = qMax(MIN_WIDTH, windowSize.width() * 2 / 3);
m_preferredSize = QSize(width, sizeHint().height());
QWidget::resize(m_preferredSize);
m_tree->resizeHeaders();
}
QSize LocatorPopup::preferredSize() const
{
return m_preferredSize;
}
void LocatorPopup::updateWindow()
{
QWidget *w = parentWidget() ? parentWidget()->window() : nullptr;
if (m_window != w) {
if (m_window)
m_window->removeEventFilter(this);
m_window = w;
if (m_window)
m_window->installEventFilter(this);
}
}
bool LocatorPopup::event(QEvent *event)
{
if (event->type() == QEvent::ParentChange)
updateWindow();
return QWidget::event(event);
}
bool LocatorPopup::eventFilter(QObject *watched, QEvent *event)
{
if (watched == m_window && event->type() == QEvent::Resize)
resize();
return QWidget::eventFilter(watched, event);
}
void LocatorPopup::showPopup()
{
QTC_ASSERT(parentWidget(), return);
const QSize size = preferredSize();
const QRect rect(parentWidget()->mapToGlobal(QPoint(0, -size.height())), size);
setGeometry(rect);
show();
} }
void CompletionList::resizeHeaders() void CompletionList::resizeHeaders()
{ {
header()->resizeSection(0, m_preferredSize.width() / 2); header()->resizeSection(0, width() / 2);
header()->resizeSection(1, 0); // last section is auto resized because of stretchLastSection header()->resizeSection(1, 0); // last section is auto resized because of stretchLastSection
} }
LocatorPopup::LocatorPopup(LocatorWidget *locatorWidget, QWidget *parent)
: QWidget(parent),
m_tree(new CompletionList(this))
{
setWindowFlags(Qt::ToolTip);
m_tree->setFrameStyle(QFrame::NoFrame);
m_tree->setModel(locatorWidget->model());
auto layout = new QVBoxLayout;
layout->setSizeConstraint(QLayout::SetMinimumSize);
setLayout(layout);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
layout->addWidget(m_tree);
connect(locatorWidget, &LocatorWidget::parentChanged, this, &LocatorPopup::updateWindow);
connect(locatorWidget, &LocatorWidget::showPopup, this, &LocatorPopup::showPopup);
connect(locatorWidget, &LocatorWidget::hidePopup, this, &LocatorPopup::hide);
connect(locatorWidget, &LocatorWidget::selectRow, m_tree, [this](int row) {
m_tree->setCurrentIndex(m_tree->model()->index(row, 0));
});
connect(locatorWidget, &LocatorWidget::showCurrentItemToolTip,
m_tree, &CompletionList::showCurrentItemToolTip);
connect(locatorWidget, &LocatorWidget::handleKey, this, [this](QKeyEvent *keyEvent) {
QApplication::sendEvent(m_tree, keyEvent);
}, Qt::DirectConnection); // must be handled directly before event is deleted
connect(m_tree, &QAbstractItemView::activated, locatorWidget,
[this, locatorWidget](const QModelIndex &index) {
if (isVisible())
locatorWidget->scheduleAcceptEntry(index);
});
resize();
}
CompletionList *LocatorPopup::completionList() const
{
return m_tree;
}
void LocatorPopup::focusOutEvent(QFocusEvent *event) {
if (event->reason() == Qt::ActiveWindowFocusReason)
hide();
QWidget::focusOutEvent(event);
}
void CompletionList::next() {
int index = currentIndex().row();
++index;
if (index >= model()->rowCount(QModelIndex())) {
// wrap
index = 0;
}
setCurrentIndex(model()->index(index, 0));
}
void CompletionList::previous() {
int index = currentIndex().row();
--index;
if (index < 0) {
// wrap
index = model()->rowCount(QModelIndex()) - 1;
}
setCurrentIndex(model()->index(index, 0));
}
void CompletionList::showCurrentItemToolTip()
{
QTC_ASSERT(model(), return);
if (!isVisible())
return;
const QModelIndex index = currentIndex();
if (index.isValid()) {
QToolTip::showText(mapToGlobal(pos() + visualRect(index).topRight()),
model()->data(index, Qt::ToolTipRole).toString());
}
}
void CompletionList::keyPressEvent(QKeyEvent *event)
{
switch (event->key()) {
case Qt::Key_Tab:
case Qt::Key_Down:
next();
return;
case Qt::Key_Backtab:
case Qt::Key_Up:
previous();
return;
case Qt::Key_P:
case Qt::Key_N:
if (event->modifiers() == Qt::KeyboardModifiers(Utils::HostOsInfo::controlModifier())) {
if (event->key() == Qt::Key_P)
previous();
else
next();
return;
}
break;
}
Utils::TreeView::keyPressEvent(event);
}
// =========== LocatorWidget =========== // =========== LocatorWidget ===========
LocatorWidget::LocatorWidget(Locator *locator) : LocatorWidget::LocatorWidget(Locator *locator) :
m_locatorModel(new LocatorModel(this)), m_locatorModel(new LocatorModel(this)),
m_completionList(new CompletionList(this)),
m_filterMenu(new QMenu(this)), m_filterMenu(new QMenu(this)),
m_refreshAction(new QAction(tr("Refresh"), this)), m_refreshAction(new QAction(tr("Refresh"), this)),
m_configureAction(new QAction(ICore::msgShowOptionsDialog(), this)), m_configureAction(new QAction(ICore::msgShowOptionsDialog(), this)),
m_fileLineEdit(new Utils::FancyLineEdit) m_fileLineEdit(new Utils::FancyLineEdit)
{ {
// Explicitly hide the completion list popup.
m_completionList->hide();
setAttribute(Qt::WA_Hover); setAttribute(Qt::WA_Hover);
setFocusProxy(m_fileLineEdit); setFocusProxy(m_fileLineEdit);
resize(200, 90); resize(200, 90);
@@ -306,11 +438,6 @@ LocatorWidget::LocatorWidget(Locator *locator) :
m_fileLineEdit->installEventFilter(this); m_fileLineEdit->installEventFilter(this);
this->installEventFilter(this); this->installEventFilter(this);
m_completionList->setModel(m_locatorModel);
m_completionList->resize();
connect(m_locatorModel, &QAbstractItemModel::columnsInserted,
m_completionList, &CompletionList::resizeHeaders);
m_filterMenu->addAction(m_refreshAction); m_filterMenu->addAction(m_refreshAction);
m_filterMenu->addAction(m_configureAction); m_filterMenu->addAction(m_configureAction);
@@ -320,9 +447,7 @@ LocatorWidget::LocatorWidget(Locator *locator) :
locator, [locator]() { locator->refresh(); }); locator, [locator]() { locator->refresh(); });
connect(m_configureAction, &QAction::triggered, this, &LocatorWidget::showConfigureDialog); connect(m_configureAction, &QAction::triggered, this, &LocatorWidget::showConfigureDialog);
connect(m_fileLineEdit, &QLineEdit::textChanged, connect(m_fileLineEdit, &QLineEdit::textChanged,
this, &LocatorWidget::showPopup); this, &LocatorWidget::showPopupDelayed);
connect(m_completionList, &QAbstractItemView::activated,
this, &LocatorWidget::scheduleAcceptEntry);
m_entriesWatcher = new QFutureWatcher<LocatorFilterEntry>(this); m_entriesWatcher = new QFutureWatcher<LocatorFilterEntry>(this);
connect(m_entriesWatcher, &QFutureWatcher<LocatorFilterEntry>::resultsReadyAt, connect(m_entriesWatcher, &QFutureWatcher<LocatorFilterEntry>::resultsReadyAt,
@@ -398,34 +523,30 @@ bool LocatorWidget::eventFilter(QObject *obj, QEvent *event)
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event); QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
switch (keyEvent->key()) { switch (keyEvent->key()) {
case Qt::Key_Up:
case Qt::Key_Down:
case Qt::Key_PageUp: case Qt::Key_PageUp:
case Qt::Key_PageDown: case Qt::Key_PageDown:
showCompletionList(); case Qt::Key_Down:
QApplication::sendEvent(m_completionList, event); case Qt::Key_Tab:
case Qt::Key_Up:
case Qt::Key_Backtab:
emit showPopup();
emit handleKey(keyEvent);
return true; return true;
case Qt::Key_Home: case Qt::Key_Home:
case Qt::Key_End: case Qt::Key_End:
if (Utils::HostOsInfo::isMacHost() if (Utils::HostOsInfo::isMacHost()
!= (keyEvent->modifiers() == Qt::KeyboardModifiers(Qt::ControlModifier))) { != (keyEvent->modifiers() == Qt::KeyboardModifiers(Qt::ControlModifier))) {
showCompletionList(); emit showPopup();
QApplication::sendEvent(m_completionList, event); emit handleKey(keyEvent);
return true; return true;
} }
break; break;
case Qt::Key_Enter: case Qt::Key_Enter:
case Qt::Key_Return: case Qt::Key_Return:
QApplication::sendEvent(m_completionList, event); emit handleKey(keyEvent);
return true; return true;
case Qt::Key_Escape: case Qt::Key_Escape:
m_completionList->hide(); emit hidePopup();
return true;
case Qt::Key_Tab:
m_completionList->next();
return true;
case Qt::Key_Backtab:
m_completionList->previous();
return true; return true;
case Qt::Key_Alt: case Qt::Key_Alt:
if (keyEvent->modifiers() == Qt::AltModifier) { if (keyEvent->modifiers() == Qt::AltModifier) {
@@ -435,12 +556,9 @@ bool LocatorWidget::eventFilter(QObject *obj, QEvent *event)
break; break;
case Qt::Key_P: case Qt::Key_P:
case Qt::Key_N: case Qt::Key_N:
if (keyEvent->modifiers() == Qt::KeyboardModifiers(Utils::HostOsInfo::controlModifier())) if (keyEvent->modifiers() == Qt::KeyboardModifiers(Utils::HostOsInfo::controlModifier())) {
{ emit showPopup();
if (keyEvent->key() == Qt::Key_P) emit handleKey(keyEvent);
m_completionList->previous();
else
m_completionList->next();
return true; return true;
} }
break; break;
@@ -451,35 +569,20 @@ bool LocatorWidget::eventFilter(QObject *obj, QEvent *event)
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event); QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (m_possibleToolTipRequest) { if (m_possibleToolTipRequest) {
m_possibleToolTipRequest = false; m_possibleToolTipRequest = false;
if (m_completionList->isVisible() if ((keyEvent->key() == Qt::Key_Alt)
&& (keyEvent->key() == Qt::Key_Alt)
&& (keyEvent->modifiers() == Qt::NoModifier)) { && (keyEvent->modifiers() == Qt::NoModifier)) {
const QModelIndex index = m_completionList->currentIndex(); emit showCurrentItemToolTip();
if (index.isValid()) { return true;
QToolTip::showText(m_completionList->pos() + m_completionList->visualRect(index).topRight(),
m_locatorModel->data(index, Qt::ToolTipRole).toString());
return true;
}
} }
} }
} else if (obj == m_fileLineEdit && event->type() == QEvent::FocusOut) { } else if (obj == m_fileLineEdit && event->type() == QEvent::FocusOut) {
QFocusEvent *fev = static_cast<QFocusEvent *>(event); emit hidePopup();
if (fev->reason() != Qt::ActiveWindowFocusReason || !m_completionList->isActiveWindow())
m_completionList->hide();
} else if (obj == m_fileLineEdit && event->type() == QEvent::FocusIn) { } else if (obj == m_fileLineEdit && event->type() == QEvent::FocusIn) {
QFocusEvent *fev = static_cast<QFocusEvent *>(event); QFocusEvent *fev = static_cast<QFocusEvent *>(event);
if (fev->reason() != Qt::ActiveWindowFocusReason) if (fev->reason() != Qt::ActiveWindowFocusReason)
showPopupNow(); showPopupNow();
} else if (obj == m_window && event->type() == QEvent::Resize) {
m_completionList->resize();
} else if (obj == this && event->type() == QEvent::ParentChange) { } else if (obj == this && event->type() == QEvent::ParentChange) {
if (m_window != window()) { emit parentChanged();
if (m_window)
m_window->removeEventFilter(this);
m_window = window();
if (m_window)
m_window->installEventFilter(this);
}
} else if (obj == this && event->type() == QEvent::ShortcutOverride) { } else if (obj == this && event->type() == QEvent::ShortcutOverride) {
QKeyEvent *ke = static_cast<QKeyEvent *>(event); QKeyEvent *ke = static_cast<QKeyEvent *>(event);
switch (ke->key()) { switch (ke->key()) {
@@ -508,16 +611,7 @@ void LocatorWidget::setFocusToCurrentMode()
ModeManager::setFocusToCurrentMode(); ModeManager::setFocusToCurrentMode();
} }
void LocatorWidget::showCompletionList() void LocatorWidget::showPopupDelayed()
{
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 LocatorWidget::showPopup()
{ {
m_updateRequested = true; m_updateRequested = true;
m_showPopupTimer.start(); m_showPopupTimer.start();
@@ -527,7 +621,7 @@ void LocatorWidget::showPopupNow()
{ {
m_showPopupTimer.stop(); m_showPopupTimer.stop();
updateCompletionList(m_fileLineEdit->text()); updateCompletionList(m_fileLineEdit->text());
showCompletionList(); emit showPopup();
} }
QList<ILocatorFilter *> LocatorWidget::filtersFor(const QString &text, QString &searchText) QList<ILocatorFilter *> LocatorWidget::filtersFor(const QString &text, QString &searchText)
@@ -633,9 +727,7 @@ void LocatorWidget::scheduleAcceptEntry(const QModelIndex &index)
void LocatorWidget::acceptEntry(int row) void LocatorWidget::acceptEntry(int row)
{ {
if (!m_completionList->isVisible()) if (row < 0 || row >= m_locatorModel->rowCount())
return;
if (row >= m_locatorModel->rowCount())
return; return;
const QModelIndex index = m_locatorModel->index(row, 0); const QModelIndex index = m_locatorModel->index(row, 0);
if (!index.isValid()) if (!index.isValid())
@@ -647,7 +739,7 @@ void LocatorWidget::acceptEntry(int row)
int selectionLength = 0; int selectionLength = 0;
entry.filter->accept(entry, &newText, &selectionStart, &selectionLength); entry.filter->accept(entry, &newText, &selectionStart, &selectionLength);
if (newText.isEmpty()) { if (newText.isEmpty()) {
m_completionList->hide(); emit hidePopup();
m_fileLineEdit->clearFocus(); m_fileLineEdit->clearFocus();
} else { } else {
showText(newText, selectionStart, selectionLength); showText(newText, selectionStart, selectionLength);
@@ -660,7 +752,7 @@ void LocatorWidget::showText(const QString &text, int selectionStart, int select
m_fileLineEdit->setText(text); m_fileLineEdit->setText(text);
m_fileLineEdit->setFocus(); m_fileLineEdit->setFocus();
showPopupNow(); showPopupNow();
ICore::raiseWindow(m_window); ICore::raiseWindow(window());
if (selectionStart >= 0) { if (selectionStart >= 0) {
m_fileLineEdit->setSelection(selectionStart, selectionLength); m_fileLineEdit->setSelection(selectionStart, selectionLength);
@@ -676,6 +768,11 @@ QString LocatorWidget::currentText() const
return m_fileLineEdit->text(); return m_fileLineEdit->text();
} }
QAbstractItemModel *LocatorWidget::model() const
{
return m_locatorModel;
}
void LocatorWidget::showConfigureDialog() void LocatorWidget::showConfigureDialog()
{ {
ICore::showOptionsDialog(Constants::FILTER_OPTIONS_PAGE); ICore::showOptionsDialog(Constants::FILTER_OPTIONS_PAGE);
@@ -693,7 +790,7 @@ void LocatorWidget::addSearchResults(int firstIndex, int endIndex)
entries.append(m_entriesWatcher->resultAt(i)); entries.append(m_entriesWatcher->resultAt(i));
m_locatorModel->addEntries(entries); m_locatorModel->addEntries(entries);
if (selectFirst) { if (selectFirst) {
m_completionList->setCurrentIndex(m_locatorModel->index(0, 0)); emit selectRow(0);
if (m_rowRequestedForAccept >= 0) if (m_rowRequestedForAccept >= 0)
m_rowRequestedForAccept = 0; m_rowRequestedForAccept = 0;
} }

View File

@@ -54,33 +54,40 @@ class LocatorWidget
public: public:
explicit LocatorWidget(Locator *locator); explicit LocatorWidget(Locator *locator);
void updateFilterList();
void showText(const QString &text, int selectionStart = -1, int selectionLength = 0); void showText(const QString &text, int selectionStart = -1, int selectionLength = 0);
QString currentText() const; QString currentText() const;
QAbstractItemModel *model() const;
void updatePlaceholderText(Command *command); void updatePlaceholderText(Command *command);
private: void scheduleAcceptEntry(const QModelIndex &index);
signals:
void showCurrentItemToolTip();
void hidePopup();
void selectRow(int row);
void handleKey(QKeyEvent *keyEvent); // only use with DirectConnection, event is deleted
void parentChanged();
void showPopup(); void showPopup();
private:
void showPopupDelayed();
void showPopupNow(); void showPopupNow();
void acceptEntry(int row); void acceptEntry(int row);
void showConfigureDialog(); void showConfigureDialog();
void addSearchResults(int firstIndex, int endIndex); void addSearchResults(int firstIndex, int endIndex);
void handleSearchFinished(); void handleSearchFinished();
void scheduleAcceptEntry(const QModelIndex &index);
void setFocusToCurrentMode(); void setFocusToCurrentMode();
void updateFilterList();
bool eventFilter(QObject *obj, QEvent *event); bool eventFilter(QObject *obj, QEvent *event);
void showCompletionList();
void updateCompletionList(const QString &text); void updateCompletionList(const QString &text);
QList<ILocatorFilter*> filtersFor(const QString &text, QString &searchText); QList<ILocatorFilter*> filtersFor(const QString &text, QString &searchText);
void setProgressIndicatorVisible(bool visible); void setProgressIndicatorVisible(bool visible);
LocatorModel *m_locatorModel; LocatorModel *m_locatorModel;
CompletionList *m_completionList;
QMenu *m_filterMenu; QMenu *m_filterMenu;
QAction *m_refreshAction; QAction *m_refreshAction;
QAction *m_configureAction; QAction *m_configureAction;
@@ -93,9 +100,30 @@ private:
bool m_possibleToolTipRequest = false; bool m_possibleToolTipRequest = false;
int m_rowRequestedForAccept = -1; int m_rowRequestedForAccept = -1;
QWidget *m_progressIndicator; QWidget *m_progressIndicator;
QPointer<QWidget> m_window;
QTimer m_showProgressTimer; QTimer m_showProgressTimer;
}; };
class LocatorPopup : public QWidget
{
public:
LocatorPopup(LocatorWidget *locatorWidget, QWidget *parent = 0);
CompletionList *completionList() const;
void focusOutEvent (QFocusEvent *event) override;
void resize();
QSize preferredSize() const;
bool event(QEvent *event) override;
bool eventFilter(QObject *watched, QEvent *event) override;
private:
void showPopup();
CompletionList *m_tree;
QSize m_preferredSize;
QPointer<QWidget> m_window;
void updateWindow();
};
} // namespace Internal } // namespace Internal
} // namespace Core } // namespace Core