File System View: Add option to show folders on top

Task-number: QTCREATORBUG-7818
Change-Id: I37a36a03c9baf6fba7b3eedcb52ee5912a57a47a
Reviewed-by: Tobias Hunger <tobias.hunger@qt.io>
This commit is contained in:
Eike Ziller
2018-03-29 14:56:24 +02:00
parent 99bcc00199
commit d44ed66a9f
2 changed files with 88 additions and 14 deletions

View File

@@ -70,6 +70,7 @@
#include <QMessageBox> #include <QMessageBox>
#include <QScrollBar> #include <QScrollBar>
#include <QSize> #include <QSize>
#include <QSortFilterProxyModel>
#include <QTimer> #include <QTimer>
#include <QToolButton> #include <QToolButton>
#include <QVBoxLayout> #include <QVBoxLayout>
@@ -128,6 +129,10 @@ private:
class FolderNavigationModel : public QFileSystemModel class FolderNavigationModel : public QFileSystemModel
{ {
public: public:
enum Roles {
IsFolderRole = Qt::UserRole + 50 // leave some gap for the custom roles in QFileSystemModel
};
explicit FolderNavigationModel(QObject *parent = nullptr); explicit FolderNavigationModel(QObject *parent = nullptr);
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const final; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const final;
Qt::DropActions supportedDragActions() const final; Qt::DropActions supportedDragActions() const final;
@@ -135,6 +140,37 @@ public:
bool setData(const QModelIndex &index, const QVariant &value, int role) final; bool setData(const QModelIndex &index, const QVariant &value, int role) final;
}; };
// Sorts folders on top if wanted
class FolderSortProxyModel : public QSortFilterProxyModel
{
public:
FolderSortProxyModel(QObject *parent = nullptr);
protected:
bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const;
};
FolderSortProxyModel::FolderSortProxyModel(QObject *parent)
: QSortFilterProxyModel(parent)
{
}
bool FolderSortProxyModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
{
const QAbstractItemModel *src = sourceModel();
if (sortRole() == FolderNavigationModel::IsFolderRole) {
const bool leftIsFolder = src->data(source_left, FolderNavigationModel::IsFolderRole)
.toBool();
const bool rightIsFolder = src->data(source_right, FolderNavigationModel::IsFolderRole)
.toBool();
if (leftIsFolder != rightIsFolder)
return leftIsFolder;
}
const QString leftName = src->data(source_left, QFileSystemModel::FileNameRole).toString();
const QString rightName = src->data(source_right, QFileSystemModel::FileNameRole).toString();
return Utils::FileName::fromString(leftName) < Utils::FileName::fromString(rightName);
}
FolderNavigationModel::FolderNavigationModel(QObject *parent) : QFileSystemModel(parent) FolderNavigationModel::FolderNavigationModel(QObject *parent) : QFileSystemModel(parent)
{ } { }
@@ -142,6 +178,8 @@ QVariant FolderNavigationModel::data(const QModelIndex &index, int role) const
{ {
if (role == Qt::ToolTipRole) if (role == Qt::ToolTipRole)
return QDir::toNativeSeparators(QDir::cleanPath(filePath(index))); return QDir::toNativeSeparators(QDir::cleanPath(filePath(index)));
else if (role == IsFolderRole)
return isDir(index);
else else
return QFileSystemModel::data(index, role); return QFileSystemModel::data(index, role);
} }
@@ -250,8 +288,10 @@ static bool isChildOf(const QModelIndex &index, const QModelIndex &parent)
FolderNavigationWidget::FolderNavigationWidget(QWidget *parent) : QWidget(parent), FolderNavigationWidget::FolderNavigationWidget(QWidget *parent) : QWidget(parent),
m_listView(new Utils::NavigationTreeView(this)), m_listView(new Utils::NavigationTreeView(this)),
m_fileSystemModel(new FolderNavigationModel(this)), m_fileSystemModel(new FolderNavigationModel(this)),
m_sortProxyModel(new FolderSortProxyModel(m_fileSystemModel)),
m_filterHiddenFilesAction(new QAction(tr("Show Hidden Files"), this)), m_filterHiddenFilesAction(new QAction(tr("Show Hidden Files"), this)),
m_showBreadCrumbsAction(new QAction(tr("Show Bread Crumbs"), this)), m_showBreadCrumbsAction(new QAction(tr("Show Bread Crumbs"), this)),
m_showFoldersOnTopAction(new QAction(tr("Show Folders on Top"), this)),
m_toggleSync(new QToolButton(this)), m_toggleSync(new QToolButton(this)),
m_toggleRootSync(new QToolButton(this)), m_toggleRootSync(new QToolButton(this)),
m_rootSelector(new QComboBox), m_rootSelector(new QComboBox),
@@ -264,6 +304,9 @@ FolderNavigationWidget::FolderNavigationWidget(QWidget *parent) : QWidget(parent
setBackgroundRole(QPalette::Base); setBackgroundRole(QPalette::Base);
setAutoFillBackground(true); setAutoFillBackground(true);
m_sortProxyModel->setSourceModel(m_fileSystemModel);
m_sortProxyModel->setSortRole(FolderNavigationModel::IsFolderRole);
m_sortProxyModel->sort(0);
m_fileSystemModel->setResolveSymlinks(false); m_fileSystemModel->setResolveSymlinks(false);
m_fileSystemModel->setIconProvider(Core::FileIconProvider::iconProvider()); m_fileSystemModel->setIconProvider(Core::FileIconProvider::iconProvider());
QDir::Filters filters = QDir::AllEntries | QDir::NoDotAndDotDot; QDir::Filters filters = QDir::AllEntries | QDir::NoDotAndDotDot;
@@ -275,9 +318,11 @@ FolderNavigationWidget::FolderNavigationWidget(QWidget *parent) : QWidget(parent
setHiddenFilesFilter(false); setHiddenFilesFilter(false);
m_showBreadCrumbsAction->setCheckable(true); m_showBreadCrumbsAction->setCheckable(true);
setShowBreadCrumbs(true); setShowBreadCrumbs(true);
m_showFoldersOnTopAction->setCheckable(true);
setShowFoldersOnTop(true);
m_listView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); m_listView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
m_listView->setIconSize(QSize(16,16)); m_listView->setIconSize(QSize(16,16));
m_listView->setModel(m_fileSystemModel); m_listView->setModel(m_sortProxyModel);
m_listView->setEditTriggers(QAbstractItemView::NoEditTriggers); m_listView->setEditTriggers(QAbstractItemView::NoEditTriggers);
m_listView->setDragEnabled(true); m_listView->setDragEnabled(true);
m_listView->setDragDropMode(QAbstractItemView::DragOnly); m_listView->setDragDropMode(QAbstractItemView::DragOnly);
@@ -319,17 +364,21 @@ FolderNavigationWidget::FolderNavigationWidget(QWidget *parent) : QWidget(parent
// connections // connections
connect(Core::EditorManager::instance(), &Core::EditorManager::currentEditorChanged, connect(Core::EditorManager::instance(), &Core::EditorManager::currentEditorChanged,
this, &FolderNavigationWidget::handleCurrentEditorChanged); this, &FolderNavigationWidget::handleCurrentEditorChanged);
connect(m_listView, &QAbstractItemView::activated, connect(m_listView, &QAbstractItemView::activated, this, [this](const QModelIndex &index) {
this, [this](const QModelIndex &index) { openItem(index); }); openItem(m_sortProxyModel->mapToSource(index));
});
// use QueuedConnection for updating crumble path, because that can scroll, which doesn't // 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) // 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,
&FolderNavigationWidget::setCrumblePath, [this](const QModelIndex &current, const QModelIndex &previous) {
setCrumblePath(m_sortProxyModel->mapToSource(current),
m_sortProxyModel->mapToSource(previous));
},
Qt::QueuedConnection); Qt::QueuedConnection);
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_sortProxyModel->mapToSource(m_listView->rootIndex());
const QModelIndex fileIndex = m_fileSystemModel->index(path.toString()); const QModelIndex fileIndex = m_fileSystemModel->index(path.toString());
if (!isChildOf(fileIndex, rootIndex)) if (!isChildOf(fileIndex, rootIndex))
selectBestRootForFile(path); selectBestRootForFile(path);
@@ -343,6 +392,10 @@ FolderNavigationWidget::FolderNavigationWidget(QWidget *parent) : QWidget(parent
&QAction::toggled, &QAction::toggled,
this, this,
&FolderNavigationWidget::setShowBreadCrumbs); &FolderNavigationWidget::setShowBreadCrumbs);
connect(m_showFoldersOnTopAction,
&QAction::toggled,
this,
&FolderNavigationWidget::setShowFoldersOnTop);
connect(m_toggleSync, connect(m_toggleSync,
&QAbstractButton::clicked, &QAbstractButton::clicked,
this, this,
@@ -356,8 +409,8 @@ FolderNavigationWidget::FolderNavigationWidget(QWidget *parent) : QWidget(parent
const auto directory = m_rootSelector->itemData(index).value<Utils::FileName>(); const auto directory = m_rootSelector->itemData(index).value<Utils::FileName>();
m_rootSelector->setToolTip(directory.toString()); m_rootSelector->setToolTip(directory.toString());
setRootDirectory(directory); setRootDirectory(directory);
const QModelIndex rootIndex = m_listView->rootIndex(); const QModelIndex rootIndex = m_sortProxyModel->mapToSource(m_listView->rootIndex());
const QModelIndex fileIndex = m_listView->currentIndex(); const QModelIndex fileIndex = m_sortProxyModel->mapToSource(m_listView->currentIndex());
if (!isChildOf(fileIndex, rootIndex)) if (!isChildOf(fileIndex, rootIndex))
selectFile(directory); selectFile(directory);
}); });
@@ -382,6 +435,13 @@ void FolderNavigationWidget::setShowBreadCrumbs(bool show)
m_crumbLabel->setVisible(show); m_crumbLabel->setVisible(show);
} }
void FolderNavigationWidget::setShowFoldersOnTop(bool onTop)
{
m_showFoldersOnTopAction->setChecked(onTop);
m_sortProxyModel->setSortRole(onTop ? FolderNavigationModel::IsFolderRole
: QFileSystemModel::FileNameRole);
}
static bool itemLessThan(QComboBox *combo, static bool itemLessThan(QComboBox *combo,
int index, int index,
const FolderNavigationWidgetFactory::RootDirectory &directory) const FolderNavigationWidgetFactory::RootDirectory &directory)
@@ -433,7 +493,7 @@ void FolderNavigationWidget::removeRootDirectory(const QString &id)
void FolderNavigationWidget::addNewItem() void FolderNavigationWidget::addNewItem()
{ {
const QModelIndex current = m_listView->currentIndex(); const QModelIndex current = m_sortProxyModel->mapToSource(m_listView->currentIndex());
if (!current.isValid()) if (!current.isValid())
return; return;
const auto filePath = Utils::FileName::fromString(m_fileSystemModel->filePath(current)); const auto filePath = Utils::FileName::fromString(m_fileSystemModel->filePath(current));
@@ -448,7 +508,7 @@ void FolderNavigationWidget::addNewItem()
void FolderNavigationWidget::editCurrentItem() void FolderNavigationWidget::editCurrentItem()
{ {
const QModelIndex current = m_listView->currentIndex(); const QModelIndex current = m_listView->currentIndex();
if (m_fileSystemModel->flags(current) & Qt::ItemIsEditable) if (m_listView->model()->flags(current) & Qt::ItemIsEditable)
m_listView->edit(current); m_listView->edit(current);
} }
@@ -467,7 +527,7 @@ static QVector<FolderNode *> removableFolderNodes(const Utils::FileName &filePat
void FolderNavigationWidget::removeCurrentItem() void FolderNavigationWidget::removeCurrentItem()
{ {
const QModelIndex current = m_listView->currentIndex(); const QModelIndex current = m_sortProxyModel->mapToSource(m_listView->currentIndex());
if (!current.isValid() || m_fileSystemModel->isDir(current)) if (!current.isValid() || m_fileSystemModel->isDir(current))
return; return;
const QString filePath = m_fileSystemModel->filePath(current); const QString filePath = m_fileSystemModel->filePath(current);
@@ -543,7 +603,8 @@ void FolderNavigationWidget::selectBestRootForFile(const Utils::FileName &filePa
void FolderNavigationWidget::selectFile(const Utils::FileName &filePath) void FolderNavigationWidget::selectFile(const Utils::FileName &filePath)
{ {
const QModelIndex fileIndex = m_fileSystemModel->index(filePath.toString()); const QModelIndex fileIndex = m_sortProxyModel->mapFromSource(
m_fileSystemModel->index(filePath.toString()));
if (fileIndex.isValid() || filePath.isEmpty() /* Computer root */) { if (fileIndex.isValid() || filePath.isEmpty() /* Computer root */) {
// TODO This only scrolls to the right position if all directory contents are loaded. // TODO This only scrolls to the right position if all directory contents are loaded.
// Unfortunately listening to directoryLoaded was still not enough (there might also // Unfortunately listening to directoryLoaded was still not enough (there might also
@@ -564,7 +625,8 @@ void FolderNavigationWidget::selectFile(const Utils::FileName &filePath)
void FolderNavigationWidget::setRootDirectory(const Utils::FileName &directory) void FolderNavigationWidget::setRootDirectory(const Utils::FileName &directory)
{ {
const QModelIndex index = m_fileSystemModel->setRootPath(directory.toString()); const QModelIndex index = m_sortProxyModel->mapFromSource(
m_fileSystemModel->setRootPath(directory.toString()));
m_listView->setRootIndex(index); m_listView->setRootIndex(index);
} }
@@ -624,7 +686,8 @@ void FolderNavigationWidget::createNewFolder(const QModelIndex &parent)
const Utils::FileName name = Utils::makeUniquelyNumbered(Utils::FileName::fromString(baseName), const Utils::FileName name = Utils::makeUniquelyNumbered(Utils::FileName::fromString(baseName),
existingItems); existingItems);
// create directory and edit // create directory and edit
const QModelIndex index = m_fileSystemModel->mkdir(parent, name.toString()); const QModelIndex index = m_sortProxyModel->mapFromSource(
m_fileSystemModel->mkdir(parent, name.toString()));
if (!index.isValid()) if (!index.isValid())
return; return;
m_listView->setCurrentIndex(index); m_listView->setCurrentIndex(index);
@@ -662,7 +725,7 @@ void FolderNavigationWidget::contextMenuEvent(QContextMenuEvent *ev)
{ {
QMenu menu; QMenu menu;
// Open current item // Open current item
const QModelIndex current = m_listView->currentIndex(); const QModelIndex current = m_sortProxyModel->mapToSource(m_listView->currentIndex());
const bool hasCurrentItem = current.isValid(); const bool hasCurrentItem = current.isValid();
QAction *actionOpenFile = nullptr; QAction *actionOpenFile = nullptr;
QAction *actionOpenProjects = nullptr; QAction *actionOpenProjects = nullptr;
@@ -753,6 +816,11 @@ bool FolderNavigationWidget::isShowingBreadCrumbs() const
return m_showBreadCrumbsAction->isChecked(); return m_showBreadCrumbsAction->isChecked();
} }
bool FolderNavigationWidget::isShowingFoldersOnTop() const
{
return m_showFoldersOnTopAction->isChecked();
}
QStringList FolderNavigationWidget::projectFilesInDirectory(const QString &path) QStringList FolderNavigationWidget::projectFilesInDirectory(const QString &path)
{ {
QDir dir(path); QDir dir(path);
@@ -812,6 +880,7 @@ Core::NavigationView FolderNavigationWidgetFactory::createWidget()
auto filterMenu = new QMenu(filter); auto filterMenu = new QMenu(filter);
filterMenu->addAction(fnw->m_filterHiddenFilesAction); filterMenu->addAction(fnw->m_filterHiddenFilesAction);
filterMenu->addAction(fnw->m_showBreadCrumbsAction); filterMenu->addAction(fnw->m_showBreadCrumbsAction);
filterMenu->addAction(fnw->m_showFoldersOnTopAction);
filter->setMenu(filterMenu); filter->setMenu(filterMenu);
n.dockToolBarWidgets << filter << fnw->m_toggleSync; n.dockToolBarWidgets << filter << fnw->m_toggleSync;
return n; return n;

View File

@@ -46,6 +46,7 @@ class QAction;
class QComboBox; class QComboBox;
class QFileSystemModel; class QFileSystemModel;
class QModelIndex; class QModelIndex;
class QSortFilterProxyModel;
QT_END_NAMESPACE QT_END_NAMESPACE
namespace ProjectExplorer { namespace ProjectExplorer {
@@ -100,10 +101,12 @@ public:
bool autoSynchronization() const; bool autoSynchronization() const;
bool hiddenFilesFilter() const; bool hiddenFilesFilter() const;
bool isShowingBreadCrumbs() const; bool isShowingBreadCrumbs() const;
bool isShowingFoldersOnTop() const;
void setAutoSynchronization(bool sync); void setAutoSynchronization(bool sync);
void toggleAutoSynchronization(); void toggleAutoSynchronization();
void setShowBreadCrumbs(bool show); void setShowBreadCrumbs(bool show);
void setShowFoldersOnTop(bool onTop);
void insertRootDirectory(const FolderNavigationWidgetFactory::RootDirectory &directory); void insertRootDirectory(const FolderNavigationWidgetFactory::RootDirectory &directory);
void removeRootDirectory(const QString &id); void removeRootDirectory(const QString &id);
@@ -133,8 +136,10 @@ private:
Core::IContext *m_context = nullptr; Core::IContext *m_context = nullptr;
Utils::NavigationTreeView *m_listView = nullptr; Utils::NavigationTreeView *m_listView = nullptr;
QFileSystemModel *m_fileSystemModel = nullptr; QFileSystemModel *m_fileSystemModel = nullptr;
QSortFilterProxyModel *m_sortProxyModel = nullptr;
QAction *m_filterHiddenFilesAction = nullptr; QAction *m_filterHiddenFilesAction = nullptr;
QAction *m_showBreadCrumbsAction = nullptr; QAction *m_showBreadCrumbsAction = nullptr;
QAction *m_showFoldersOnTopAction = nullptr;
bool m_autoSync = false; bool m_autoSync = false;
bool m_rootAutoSync = true; bool m_rootAutoSync = true;
QToolButton *m_toggleSync = nullptr; QToolButton *m_toggleSync = nullptr;