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);
auto locatorWidget = new LocatorWidget(this);
new LocatorPopup(locatorWidget, locatorWidget); // child of locatorWidget
StatusBarWidget *view = new StatusBarWidget;
view->setWidget(locatorWidget);
view->setContext(Context("LocatorWidget"));

View File

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

View File

@@ -54,33 +54,40 @@ class LocatorWidget
public:
explicit LocatorWidget(Locator *locator);
void updateFilterList();
void showText(const QString &text, int selectionStart = -1, int selectionLength = 0);
QString currentText() const;
QAbstractItemModel *model() const;
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();
private:
void showPopupDelayed();
void showPopupNow();
void acceptEntry(int row);
void showConfigureDialog();
void addSearchResults(int firstIndex, int endIndex);
void handleSearchFinished();
void scheduleAcceptEntry(const QModelIndex &index);
void setFocusToCurrentMode();
void updateFilterList();
bool eventFilter(QObject *obj, QEvent *event);
void showCompletionList();
void updateCompletionList(const QString &text);
QList<ILocatorFilter*> filtersFor(const QString &text, QString &searchText);
void setProgressIndicatorVisible(bool visible);
LocatorModel *m_locatorModel;
CompletionList *m_completionList;
QMenu *m_filterMenu;
QAction *m_refreshAction;
QAction *m_configureAction;
@@ -93,9 +100,30 @@ private:
bool m_possibleToolTipRequest = false;
int m_rowRequestedForAccept = -1;
QWidget *m_progressIndicator;
QPointer<QWidget> m_window;
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 Core