forked from qt-creator/qt-creator
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 <tobias.hunger@qt.io>
This commit is contained in:
@@ -48,19 +48,20 @@
|
|||||||
#include <utils/navigationtreeview.h>
|
#include <utils/navigationtreeview.h>
|
||||||
#include <utils/utilsicons.h>
|
#include <utils/utilsicons.h>
|
||||||
|
|
||||||
#include <QComboBox>
|
|
||||||
#include <QHeaderView>
|
|
||||||
#include <QScrollBar>
|
|
||||||
#include <QSize>
|
|
||||||
#include <QTimer>
|
|
||||||
#include <QFileSystemModel>
|
|
||||||
#include <QVBoxLayout>
|
|
||||||
#include <QToolButton>
|
|
||||||
#include <QAction>
|
#include <QAction>
|
||||||
#include <QMenu>
|
#include <QApplication>
|
||||||
|
#include <QComboBox>
|
||||||
#include <QContextMenuEvent>
|
#include <QContextMenuEvent>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
|
#include <QFileSystemModel>
|
||||||
|
#include <QHeaderView>
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QScrollBar>
|
||||||
|
#include <QSize>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QToolButton>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
const int PATH_ROLE = Qt::UserRole;
|
const int PATH_ROLE = Qt::UserRole;
|
||||||
const int ID_ROLE = Qt::UserRole + 1;
|
const int ID_ROLE = Qt::UserRole + 1;
|
||||||
@@ -84,6 +85,27 @@ static QWidget *createHLine()
|
|||||||
return widget;
|
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<QScrollBar> m_bar;
|
||||||
|
int m_barValue = 0;
|
||||||
|
bool m_delaying = false;
|
||||||
|
};
|
||||||
|
|
||||||
// FolderNavigationModel: Shows path as tooltip.
|
// FolderNavigationModel: Shows path as tooltip.
|
||||||
class FolderNavigationModel : public QFileSystemModel
|
class FolderNavigationModel : public QFileSystemModel
|
||||||
{
|
{
|
||||||
@@ -142,7 +164,7 @@ FolderNavigationWidget::FolderNavigationWidget(QWidget *parent) : QWidget(parent
|
|||||||
m_filterHiddenFilesAction(new QAction(tr("Show Hidden Files"), this)),
|
m_filterHiddenFilesAction(new QAction(tr("Show Hidden Files"), this)),
|
||||||
m_toggleSync(new QToolButton(this)),
|
m_toggleSync(new QToolButton(this)),
|
||||||
m_rootSelector(new QComboBox),
|
m_rootSelector(new QComboBox),
|
||||||
m_crumbLabel(new Utils::FileCrumbLabel(this))
|
m_crumbLabel(new DelayedFileCrumbLabel(this))
|
||||||
{
|
{
|
||||||
setBackgroundRole(QPalette::Base);
|
setBackgroundRole(QPalette::Base);
|
||||||
setAutoFillBackground(true);
|
setAutoFillBackground(true);
|
||||||
@@ -190,13 +212,13 @@ FolderNavigationWidget::FolderNavigationWidget(QWidget *parent) : QWidget(parent
|
|||||||
// connections
|
// connections
|
||||||
connect(m_listView, &QAbstractItemView::activated,
|
connect(m_listView, &QAbstractItemView::activated,
|
||||||
this, [this](const QModelIndex &index) { openItem(index); });
|
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(),
|
connect(m_listView->selectionModel(),
|
||||||
&QItemSelectionModel::currentChanged,
|
&QItemSelectionModel::currentChanged,
|
||||||
this,
|
this,
|
||||||
[this](const QModelIndex ¤t, const QModelIndex &) {
|
&FolderNavigationWidget::setCrumblePath,
|
||||||
m_crumbLabel->setPath(
|
Qt::QueuedConnection);
|
||||||
Utils::FileName::fromString(m_fileSystemModel->filePath(current)));
|
|
||||||
});
|
|
||||||
connect(m_crumbLabel, &Utils::FileCrumbLabel::pathClicked, [this](const Utils::FileName &path) {
|
connect(m_crumbLabel, &Utils::FileCrumbLabel::pathClicked, [this](const Utils::FileName &path) {
|
||||||
const QModelIndex rootIndex = m_listView->rootIndex();
|
const QModelIndex rootIndex = m_listView->rootIndex();
|
||||||
const QModelIndex fileIndex = m_fileSystemModel->index(path.toString());
|
const QModelIndex fileIndex = m_fileSystemModel->index(path.toString());
|
||||||
@@ -388,6 +410,27 @@ void FolderNavigationWidget::openProjectsInDirectory(const QModelIndex &index)
|
|||||||
Core::ICore::instance()->openFiles(projectFiles);
|
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)
|
void FolderNavigationWidget::contextMenuEvent(QContextMenuEvent *ev)
|
||||||
{
|
{
|
||||||
QMenu menu;
|
QMenu menu;
|
||||||
@@ -573,5 +616,48 @@ void FolderNavigationWidgetFactory::updateProjectsDirectoryRoot()
|
|||||||
Utils::Icons::PROJECT.icon()});
|
Utils::Icons::PROJECT.icon()});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int DelayedFileCrumbLabel::immediateHeightForWidth(int w) const
|
||||||
|
{
|
||||||
|
return Utils::FileCrumbLabel::heightForWidth(w);
|
||||||
|
}
|
||||||
|
|
||||||
|
int DelayedFileCrumbLabel::heightForWidth(int w) const
|
||||||
|
{
|
||||||
|
static QHash<int, int> 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<DelayedFileCrumbLabel *>(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<DelayedFileCrumbLabel *>(this);
|
||||||
|
that->m_bar->setValue(m_barValue);
|
||||||
|
that->m_bar.clear();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Internal
|
} // namespace Internal
|
||||||
} // namespace ProjectExplorer
|
} // namespace ProjectExplorer
|
||||||
|
@@ -48,6 +48,8 @@ QT_END_NAMESPACE
|
|||||||
namespace ProjectExplorer {
|
namespace ProjectExplorer {
|
||||||
namespace Internal {
|
namespace Internal {
|
||||||
|
|
||||||
|
class DelayedFileCrumbLabel;
|
||||||
|
|
||||||
class FolderNavigationWidgetFactory : public Core::INavigationWidgetFactory
|
class FolderNavigationWidgetFactory : public Core::INavigationWidgetFactory
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@@ -111,6 +113,7 @@ private:
|
|||||||
void openItem(const QModelIndex &index);
|
void openItem(const QModelIndex &index);
|
||||||
QStringList projectsInDirectory(const QModelIndex &index) const;
|
QStringList projectsInDirectory(const QModelIndex &index) const;
|
||||||
void openProjectsInDirectory(const QModelIndex &index);
|
void openProjectsInDirectory(const QModelIndex &index);
|
||||||
|
void setCrumblePath(const QModelIndex &index, const QModelIndex &);
|
||||||
|
|
||||||
Utils::NavigationTreeView *m_listView = nullptr;
|
Utils::NavigationTreeView *m_listView = nullptr;
|
||||||
QFileSystemModel *m_fileSystemModel = nullptr;
|
QFileSystemModel *m_fileSystemModel = nullptr;
|
||||||
@@ -118,7 +121,7 @@ private:
|
|||||||
bool m_autoSync = false;
|
bool m_autoSync = false;
|
||||||
QToolButton *m_toggleSync = nullptr;
|
QToolButton *m_toggleSync = nullptr;
|
||||||
QComboBox *m_rootSelector = nullptr;
|
QComboBox *m_rootSelector = nullptr;
|
||||||
Utils::FileCrumbLabel *m_crumbLabel = nullptr;
|
DelayedFileCrumbLabel *m_crumbLabel = nullptr;
|
||||||
|
|
||||||
// FolderNavigationWidgetFactory needs private members to build a menu
|
// FolderNavigationWidgetFactory needs private members to build a menu
|
||||||
friend class FolderNavigationWidgetFactory;
|
friend class FolderNavigationWidgetFactory;
|
||||||
|
Reference in New Issue
Block a user