From cf781640cf7b38d19ecd2d95d7476206fcb3d7c9 Mon Sep 17 00:00:00 2001 From: Eike Ziller Date: Wed, 22 Nov 2017 16:34:31 +0100 Subject: [PATCH] Fix that double-click in file system view sometimes does not work When clicking on an item that changes the height of the crumble path, a double-click has great chances of not succeeding, because before the second click, the item might move away from under the mouse. If the tree has a scroll bar that can move wide enough, we solve that by scrolling the tree such that the same item remains under cursor when the crumble path height changes. (We have to synchronize the scroll bar value change with the relayouting though, to avoid flicker.) If there is no scroll bar, or it cannot move enough in the needed direction, we delay the re-layouting by the maximum double-click interval to guarantee a double-click will still have the same item under the mouse. Change-Id: I3b296925d9be2d2ab5affbbb64df67173d9715d4 Reviewed-by: Tobias Hunger --- .../foldernavigationwidget.cpp | 114 +++++++++++++++--- .../projectexplorer/foldernavigationwidget.h | 5 +- 2 files changed, 104 insertions(+), 15 deletions(-) diff --git a/src/plugins/projectexplorer/foldernavigationwidget.cpp b/src/plugins/projectexplorer/foldernavigationwidget.cpp index c330bf43e3c..c89656c5fa7 100644 --- a/src/plugins/projectexplorer/foldernavigationwidget.cpp +++ b/src/plugins/projectexplorer/foldernavigationwidget.cpp @@ -48,19 +48,20 @@ #include #include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include +#include +#include #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include const int PATH_ROLE = Qt::UserRole; const int ID_ROLE = Qt::UserRole + 1; @@ -84,6 +85,27 @@ static QWidget *createHLine() return widget; } +// Call delayLayoutOnce to delay reporting the new heightForWidget by the double-click interval. +// Call setScrollBarOnce to set a scroll bar's value once during layouting (where heightForWidget +// is called). +class DelayedFileCrumbLabel : public Utils::FileCrumbLabel +{ +public: + DelayedFileCrumbLabel(QWidget *parent) : Utils::FileCrumbLabel(parent) {} + + int immediateHeightForWidth(int w) const; + int heightForWidth(int w) const final; + void delayLayoutOnce(); + void setScrollBarOnce(QScrollBar *bar, int value); + +private: + void setScrollBarOnce() const; + + QPointer m_bar; + int m_barValue = 0; + bool m_delaying = false; +}; + // FolderNavigationModel: Shows path as tooltip. class FolderNavigationModel : public QFileSystemModel { @@ -142,7 +164,7 @@ FolderNavigationWidget::FolderNavigationWidget(QWidget *parent) : QWidget(parent m_filterHiddenFilesAction(new QAction(tr("Show Hidden Files"), this)), m_toggleSync(new QToolButton(this)), m_rootSelector(new QComboBox), - m_crumbLabel(new Utils::FileCrumbLabel(this)) + m_crumbLabel(new DelayedFileCrumbLabel(this)) { setBackgroundRole(QPalette::Base); setAutoFillBackground(true); @@ -190,13 +212,13 @@ FolderNavigationWidget::FolderNavigationWidget(QWidget *parent) : QWidget(parent // connections connect(m_listView, &QAbstractItemView::activated, this, [this](const QModelIndex &index) { openItem(index); }); + // use QueuedConnection for updating crumble path, because that can scroll, which doesn't + // work well when done directly in currentChanged (the wrong item can get highlighted) connect(m_listView->selectionModel(), &QItemSelectionModel::currentChanged, this, - [this](const QModelIndex ¤t, const QModelIndex &) { - m_crumbLabel->setPath( - Utils::FileName::fromString(m_fileSystemModel->filePath(current))); - }); + &FolderNavigationWidget::setCrumblePath, + Qt::QueuedConnection); connect(m_crumbLabel, &Utils::FileCrumbLabel::pathClicked, [this](const Utils::FileName &path) { const QModelIndex rootIndex = m_listView->rootIndex(); const QModelIndex fileIndex = m_fileSystemModel->index(path.toString()); @@ -388,6 +410,27 @@ void FolderNavigationWidget::openProjectsInDirectory(const QModelIndex &index) Core::ICore::instance()->openFiles(projectFiles); } +void FolderNavigationWidget::setCrumblePath(const QModelIndex &index, const QModelIndex &) +{ + const int width = m_crumbLabel->width(); + const int previousHeight = m_crumbLabel->immediateHeightForWidth(width); + m_crumbLabel->setPath(Utils::FileName::fromString(m_fileSystemModel->filePath(index))); + const int currentHeight = m_crumbLabel->immediateHeightForWidth(width); + const int diff = currentHeight - previousHeight; + if (diff != 0 && m_crumbLabel->isVisible()) { + // try to fix scroll position, otherwise delay layouting + QScrollBar *bar = m_listView->verticalScrollBar(); + const int newBarValue = bar ? bar->value() + diff : 0; + if (bar && bar->minimum() <= newBarValue && bar->maximum() >= newBarValue) { + // we need to set the scroll bar when the layout request from the crumble path is + // handled, otherwise it will flicker + m_crumbLabel->setScrollBarOnce(bar, newBarValue); + } else { + m_crumbLabel->delayLayoutOnce(); + } + } +} + void FolderNavigationWidget::contextMenuEvent(QContextMenuEvent *ev) { QMenu menu; @@ -573,5 +616,48 @@ void FolderNavigationWidgetFactory::updateProjectsDirectoryRoot() Utils::Icons::PROJECT.icon()}); } +int DelayedFileCrumbLabel::immediateHeightForWidth(int w) const +{ + return Utils::FileCrumbLabel::heightForWidth(w); +} + +int DelayedFileCrumbLabel::heightForWidth(int w) const +{ + static QHash oldHeight; + setScrollBarOnce(); + int newHeight = Utils::FileCrumbLabel::heightForWidth(w); + if (!m_delaying || !oldHeight.contains(w)) { + oldHeight.insert(w, newHeight); + } else if (oldHeight.value(w) != newHeight){ + auto that = const_cast(this); + QTimer::singleShot(QApplication::doubleClickInterval(), that, [that, w, newHeight] { + oldHeight.insert(w, newHeight); + that->m_delaying = false; + that->updateGeometry(); + }); + } + return oldHeight.value(w); +} + +void DelayedFileCrumbLabel::delayLayoutOnce() +{ + m_delaying = true; +} + +void DelayedFileCrumbLabel::setScrollBarOnce(QScrollBar *bar, int value) +{ + m_bar = bar; + m_barValue = value; +} + +void DelayedFileCrumbLabel::setScrollBarOnce() const +{ + if (!m_bar) + return; + auto that = const_cast(this); + that->m_bar->setValue(m_barValue); + that->m_bar.clear(); +} + } // namespace Internal } // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/foldernavigationwidget.h b/src/plugins/projectexplorer/foldernavigationwidget.h index aeabd82b21b..73d7fdc1955 100644 --- a/src/plugins/projectexplorer/foldernavigationwidget.h +++ b/src/plugins/projectexplorer/foldernavigationwidget.h @@ -48,6 +48,8 @@ QT_END_NAMESPACE namespace ProjectExplorer { namespace Internal { +class DelayedFileCrumbLabel; + class FolderNavigationWidgetFactory : public Core::INavigationWidgetFactory { Q_OBJECT @@ -111,6 +113,7 @@ private: void openItem(const QModelIndex &index); QStringList projectsInDirectory(const QModelIndex &index) const; void openProjectsInDirectory(const QModelIndex &index); + void setCrumblePath(const QModelIndex &index, const QModelIndex &); Utils::NavigationTreeView *m_listView = nullptr; QFileSystemModel *m_fileSystemModel = nullptr; @@ -118,7 +121,7 @@ private: bool m_autoSync = false; QToolButton *m_toggleSync = nullptr; QComboBox *m_rootSelector = nullptr; - Utils::FileCrumbLabel *m_crumbLabel = nullptr; + DelayedFileCrumbLabel *m_crumbLabel = nullptr; // FolderNavigationWidgetFactory needs private members to build a menu friend class FolderNavigationWidgetFactory;