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;