mirror of
https://github.com/Murmele/Gittyup.git
synced 2024-08-15 13:40:32 +03:00
Use columns for file name, directory, and state when files are shown as a list in TreeViews. Resolves Dense layout issue #547
This commit is contained in:
parent
e7fd8da4b3
commit
1d6d53ba00
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,4 +1,5 @@
|
||||
build
|
||||
.cache
|
||||
.DS_Store
|
||||
.project
|
||||
.vscode/
|
||||
@ -8,3 +9,5 @@ cmake-build-release/
|
||||
build
|
||||
.idea/
|
||||
.venv
|
||||
compile_commands.json
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
// Author: Jason Haslam
|
||||
//
|
||||
|
||||
#include <array>
|
||||
#include "DiffTreeModel.h"
|
||||
#include "conf/Settings.h"
|
||||
#include "git/Blob.h"
|
||||
@ -16,15 +17,30 @@
|
||||
#include "git/Patch.h"
|
||||
#include <QStringBuilder>
|
||||
#include <QUrl>
|
||||
#include <qobjectdefs.h>
|
||||
|
||||
namespace {
|
||||
|
||||
const QString kLinkFmt = "<a href='%1'>%2</a>";
|
||||
|
||||
const std::array<QString, 3> kModelHeaders = {QObject::tr("File Name"),
|
||||
QObject::tr("Relative Path"),
|
||||
QObject::tr("State")};
|
||||
|
||||
bool asList() {
|
||||
return Settings::instance()
|
||||
->value(Setting::Id::ShowChangedFilesAsList, false)
|
||||
.toBool();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
DiffTreeModel::DiffTreeModel(const git::Repository &repo, QObject *parent)
|
||||
: QAbstractItemModel(parent), mRepo(repo) {}
|
||||
: QStandardItemModel(0, kModelHeaders.size(), parent), mRepo(repo) {
|
||||
for (int i = 0; i < kModelHeaders.size(); ++i) {
|
||||
setHeaderData(i, Qt::Horizontal, kModelHeaders[i]);
|
||||
}
|
||||
}
|
||||
|
||||
DiffTreeModel::~DiffTreeModel() { delete mRoot; }
|
||||
|
||||
@ -91,7 +107,9 @@ int DiffTreeModel::rowCount(const QModelIndex &parent) const {
|
||||
return mDiff ? node(parent)->children().size() : 0;
|
||||
}
|
||||
|
||||
int DiffTreeModel::columnCount(const QModelIndex &parent) const { return 1; }
|
||||
int DiffTreeModel::columnCount(const QModelIndex &parent) const {
|
||||
return asList() ? QStandardItemModel::columnCount(parent) : 1;
|
||||
}
|
||||
|
||||
bool DiffTreeModel::hasChildren(const QModelIndex &parent) const {
|
||||
return mRoot && node(parent)->hasChildren();
|
||||
@ -134,7 +152,7 @@ void DiffTreeModel::modelIndices(const QModelIndex &parent,
|
||||
}
|
||||
|
||||
for (int i = 0; i < n->children().length(); i++) {
|
||||
auto child = createIndex(i, 0, n->children()[i]);
|
||||
auto child = createIndex(i, parent.column(), n->children()[i]);
|
||||
if (recursive)
|
||||
modelIndices(child, list);
|
||||
else if (!node(child)->hasChildren())
|
||||
@ -195,9 +213,15 @@ QVariant DiffTreeModel::data(const QModelIndex &index, int role) const {
|
||||
return QVariant();
|
||||
|
||||
Node *node = this->node(index);
|
||||
|
||||
// Skip intermediate path elements for trees showing file lists only.
|
||||
if (node->hasChildren() && asList())
|
||||
return QVariant();
|
||||
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
return node->name();
|
||||
case Qt::DisplayRole: {
|
||||
return getDisplayRole(index);
|
||||
}
|
||||
|
||||
// case Qt::DecorationRole: {
|
||||
// QFileInfo info(node->path());
|
||||
@ -212,7 +236,7 @@ QVariant DiffTreeModel::data(const QModelIndex &index, int role) const {
|
||||
return node->path();
|
||||
|
||||
case Qt::CheckStateRole: {
|
||||
if (!mDiff.isValid() || !mDiff.isStatusDiff())
|
||||
if (!mDiff.isValid() || !mDiff.isStatusDiff() || index.column() > 0)
|
||||
return QVariant();
|
||||
|
||||
git::Index index = mDiff.index();
|
||||
@ -380,6 +404,22 @@ Node *DiffTreeModel::node(const QModelIndex &index) const {
|
||||
return index.isValid() ? static_cast<Node *>(index.internalPointer()) : mRoot;
|
||||
}
|
||||
|
||||
QVariant DiffTreeModel::getDisplayRole(const QModelIndex &index) const {
|
||||
Node *node = this->node(index);
|
||||
if (asList()) {
|
||||
QFileInfo fileInfo(node->path(true));
|
||||
switch (index.column()) {
|
||||
case 0:
|
||||
return fileInfo.fileName();
|
||||
case 1:
|
||||
return fileInfo.path();
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
return node->name();
|
||||
}
|
||||
|
||||
//#############################################################################
|
||||
//###### DiffTreeModel::Node ##############################################
|
||||
//#############################################################################
|
||||
|
@ -14,7 +14,8 @@
|
||||
#include "git/Index.h"
|
||||
#include "git/Tree.h"
|
||||
#include "git/Repository.h"
|
||||
#include <QAbstractItemModel>
|
||||
#include <QStandardItemModel>
|
||||
#include <QAbstractListModel>
|
||||
#include <QFileIconProvider>
|
||||
#include "git/Index.h"
|
||||
|
||||
@ -80,7 +81,7 @@ private:
|
||||
* This Treemodel is similar to the normal tree model, but handles only the
|
||||
* files in the diff it self and not the complete tree
|
||||
*/
|
||||
class DiffTreeModel : public QAbstractItemModel {
|
||||
class DiffTreeModel : public QStandardItemModel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
@ -157,6 +158,7 @@ private:
|
||||
|
||||
private:
|
||||
Node *node(const QModelIndex &index) const;
|
||||
QVariant getDisplayRole(const QModelIndex &index) const;
|
||||
|
||||
QFileIconProvider mIconProvider;
|
||||
|
||||
|
@ -15,7 +15,6 @@
|
||||
#include "StatePushButton.h"
|
||||
#include "TreeProxy.h"
|
||||
#include "TreeView.h"
|
||||
#include "ViewDelegate.h"
|
||||
#include "Debug.h"
|
||||
#include "conf/Settings.h"
|
||||
#include "DiffView/DiffView.h"
|
||||
@ -98,6 +97,7 @@ DoubleTreeWidget::DoubleTreeWidget(const git::Repository &repo, QWidget *parent)
|
||||
listView->setChecked(Settings::instance()
|
||||
->value(Setting::Id::ShowChangedFilesAsList, false)
|
||||
.toBool());
|
||||
RepoView::parentView(this)->refresh();
|
||||
connect(listView, &QAction::triggered, this, [this](bool checked) {
|
||||
Settings::instance()->setValue(Setting::Id::ShowChangedFilesAsList,
|
||||
checked);
|
||||
@ -160,13 +160,8 @@ DoubleTreeWidget::DoubleTreeWidget(const git::Repository &repo, QWidget *parent)
|
||||
repoView->updateSubmodules(submodules, recursive, init,
|
||||
force_checkout);
|
||||
});
|
||||
TreeProxy *treewrapperStaged = new TreeProxy(true, this);
|
||||
treewrapperStaged->setSourceModel(mDiffTreeModel);
|
||||
stagedFiles->setModel(treewrapperStaged);
|
||||
stagedFiles->setHeaderHidden(true);
|
||||
ViewDelegate *stagedDelegate = new ViewDelegate();
|
||||
stagedDelegate->setDrawArrow(false);
|
||||
stagedFiles->setItemDelegateForColumn(0, stagedDelegate);
|
||||
|
||||
stagedFiles->setModel(new TreeProxy(true, mDiffTreeModel, this));
|
||||
|
||||
QHBoxLayout *hBoxLayout = new QHBoxLayout();
|
||||
QLabel *label = new QLabel(kStagedFiles);
|
||||
@ -192,13 +187,7 @@ DoubleTreeWidget::DoubleTreeWidget(const git::Repository &repo, QWidget *parent)
|
||||
showFileContextMenu(pos, repoView, unstagedFiles, false);
|
||||
});
|
||||
|
||||
TreeProxy *treewrapperUnstaged = new TreeProxy(false, this);
|
||||
treewrapperUnstaged->setSourceModel(mDiffTreeModel);
|
||||
unstagedFiles->setModel(treewrapperUnstaged);
|
||||
unstagedFiles->setHeaderHidden(true);
|
||||
ViewDelegate *unstagedDelegate = new ViewDelegate();
|
||||
unstagedDelegate->setDrawArrow(false);
|
||||
unstagedFiles->setItemDelegateForColumn(0, unstagedDelegate);
|
||||
unstagedFiles->setModel(new TreeProxy(false, mDiffTreeModel, this));
|
||||
|
||||
hBoxLayout = new QHBoxLayout();
|
||||
mUnstagedCommitedFiles = new QLabel(kUnstagedFiles);
|
||||
|
@ -23,8 +23,10 @@ const QString kLinkFmt = "<a href='%1'>%2</a>";
|
||||
|
||||
} // namespace
|
||||
|
||||
TreeProxy::TreeProxy(bool staged, QObject *parent)
|
||||
: QSortFilterProxyModel(parent), mStaged(staged) {}
|
||||
TreeProxy::TreeProxy(bool staged, QAbstractItemModel *model, QObject *parent)
|
||||
: mStaged(staged), QSortFilterProxyModel(parent) {
|
||||
setSourceModel(model);
|
||||
}
|
||||
|
||||
TreeProxy::~TreeProxy() {}
|
||||
|
||||
|
@ -16,13 +16,14 @@
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QFileIconProvider>
|
||||
|
||||
class QAbstractItemModel;
|
||||
class TreeModel;
|
||||
|
||||
class TreeProxy : public QSortFilterProxyModel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
TreeProxy(bool staged, QObject *parent = nullptr);
|
||||
TreeProxy(bool staged, QAbstractItemModel *model, QObject *parent);
|
||||
virtual ~TreeProxy();
|
||||
bool setData(const QModelIndex &index, const QVariant &value,
|
||||
int role = Qt::EditRole, bool ignoreIndexChanges = false);
|
||||
@ -30,6 +31,10 @@ public:
|
||||
|
||||
void enableFilter(bool enable) { mFilter = enable; }
|
||||
|
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const override {
|
||||
return sourceModel()->columnCount();
|
||||
}
|
||||
|
||||
private:
|
||||
using QSortFilterProxyModel::setData;
|
||||
bool filterAcceptsRow(int source_row,
|
||||
|
@ -24,6 +24,9 @@
|
||||
#include "RepoView.h"
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include "conf/Settings.h"
|
||||
#include <QAbstractItemModel>
|
||||
#include <memory>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#define ICON_SIZE 48
|
||||
@ -41,8 +44,29 @@ const QString kLabelFmt = "<p style='color: gray; font-weight: bold'>%1</p>";
|
||||
} // namespace
|
||||
|
||||
TreeView::TreeView(QWidget *parent, const QString &name)
|
||||
: QTreeView(parent), mSharedDelegate(new ViewDelegate(this)), mName(name) {
|
||||
: QTreeView(parent), mDelegateCol(0),
|
||||
mFileListDelegatePtr(std::make_unique<ViewDelegate>(this, true)),
|
||||
mFileTreeDelegatePtr(std::make_unique<ViewDelegate>(this)), mName(name) {
|
||||
setObjectName(name);
|
||||
connect(RepoView::parentView(this)->repo().notifier(),
|
||||
&git::RepositoryNotifier::referenceUpdated, this,
|
||||
&TreeView::updateView);
|
||||
}
|
||||
|
||||
void TreeView::updateView() {
|
||||
QAbstractItemModel *itemModel = model();
|
||||
if (!itemModel)
|
||||
return;
|
||||
|
||||
// Remove any previous delegate on the current column, get the new current
|
||||
// column, and set the delegate on that.
|
||||
setItemDelegateForColumn(mDelegateCol, nullptr);
|
||||
mDelegateCol = itemModel->columnCount() - 1;
|
||||
setItemDelegateForColumn(mDelegateCol, mDelegateCol
|
||||
? mFileListDelegatePtr.get()
|
||||
: mFileTreeDelegatePtr.get());
|
||||
|
||||
setHeaderHidden(mDelegateCol ? false : true);
|
||||
}
|
||||
|
||||
void TreeView::setModel(QAbstractItemModel *model) {
|
||||
|
@ -11,6 +11,8 @@
|
||||
#define TREEVIEW_H
|
||||
|
||||
#include <QTreeView>
|
||||
#include <memory>
|
||||
#include "ViewDelegate.h"
|
||||
|
||||
class QItemDelegate;
|
||||
class DiffTreeModel;
|
||||
@ -96,9 +98,12 @@ private:
|
||||
bool suppressDeselectionHandling{false};
|
||||
int mCollapseCount; // Counts the number of collapsed folders.
|
||||
bool mSupressItemExpandStateChanged{false};
|
||||
void updateView();
|
||||
|
||||
QItemDelegate *mSharedDelegate;
|
||||
QString mName;
|
||||
std::unique_ptr<ViewDelegate> mFileListDelegatePtr;
|
||||
std::unique_ptr<ViewDelegate> mFileTreeDelegatePtr;
|
||||
int mDelegateCol = 0;
|
||||
};
|
||||
|
||||
#endif // TREEVIEW_H
|
||||
|
@ -10,37 +10,6 @@ void ViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
|
||||
QStyleOptionViewItem opt = option;
|
||||
drawBackground(painter, opt, index);
|
||||
|
||||
// Draw >.
|
||||
if (mDrawArrow && index.model()->hasChildren(index)) {
|
||||
painter->save();
|
||||
painter->setRenderHint(QPainter::Antialiasing, true);
|
||||
|
||||
QColor color = opt.palette.color(QPalette::Active, QPalette::BrightText);
|
||||
if (opt.state & QStyle::State_Selected)
|
||||
color =
|
||||
!opt.showDecorationSelected
|
||||
? opt.palette.color(QPalette::Active, QPalette::WindowText)
|
||||
: opt.palette.color(QPalette::Active, QPalette::HighlightedText);
|
||||
|
||||
painter->setPen(color);
|
||||
painter->setBrush(color);
|
||||
|
||||
int x = opt.rect.x() + opt.rect.width() - 3;
|
||||
int y = opt.rect.y() + (opt.rect.height() / 2);
|
||||
|
||||
QPainterPath path;
|
||||
path.moveTo(x, y);
|
||||
path.lineTo(x - 5, y - 3);
|
||||
path.lineTo(x - 5, y + 3);
|
||||
path.closeSubpath();
|
||||
painter->drawPath(path);
|
||||
|
||||
painter->restore();
|
||||
|
||||
// Adjust rect to exclude the arrow.
|
||||
opt.rect.adjust(0, 0, -11, 0);
|
||||
}
|
||||
|
||||
// Draw badges.
|
||||
QString status = index.data(TreeModel::StatusRole).toString();
|
||||
if (!status.isEmpty()) {
|
||||
@ -49,19 +18,30 @@ void ViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
|
||||
int width = size.width();
|
||||
int height = size.height();
|
||||
|
||||
auto startIter = status.cbegin(), endIter = status.cend();
|
||||
int leftAdjust = 0, rightAdjust = -3, leftWidth = 0, rightWidth = -width;
|
||||
if (mMultiColumn) {
|
||||
leftAdjust = 3;
|
||||
rightAdjust = 0;
|
||||
leftWidth = width;
|
||||
rightWidth = 0;
|
||||
std::reverse(status.begin(), status.end());
|
||||
}
|
||||
|
||||
// Add extra space.
|
||||
opt.rect.adjust(0, 0, -3, 0);
|
||||
opt.rect.adjust(leftAdjust, 0, rightAdjust, 0);
|
||||
|
||||
for (int i = 0; i < status.count(); ++i) {
|
||||
int x = opt.rect.x() + opt.rect.width();
|
||||
int y = opt.rect.y() + (opt.rect.height() / 2);
|
||||
QRect rect(x - width, y - (height / 2), width, height);
|
||||
QRect rect(mMultiColumn ? opt.rect.x() : x - width, y - (height / 2),
|
||||
width, height);
|
||||
Badge::paint(painter,
|
||||
{Badge::Label(Badge::Label::Type::Status, status.at(i))},
|
||||
rect, &opt);
|
||||
|
||||
// Adjust rect.
|
||||
opt.rect.adjust(0, 0, -width - 3, 0);
|
||||
opt.rect.adjust(leftWidth + leftAdjust, 0, rightWidth + rightAdjust, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,9 +9,8 @@
|
||||
*/
|
||||
class ViewDelegate : public QItemDelegate {
|
||||
public:
|
||||
ViewDelegate(QObject *parent = nullptr) : QItemDelegate(parent) {}
|
||||
|
||||
void setDrawArrow(bool enable) { mDrawArrow = enable; }
|
||||
ViewDelegate(QObject *parent, bool multiColumn = false)
|
||||
: QItemDelegate(parent), mMultiColumn(multiColumn) {}
|
||||
|
||||
void paint(QPainter *painter, const QStyleOptionViewItem &option,
|
||||
const QModelIndex &index) const override;
|
||||
@ -20,7 +19,7 @@ public:
|
||||
const QModelIndex &index) const override;
|
||||
|
||||
private:
|
||||
bool mDrawArrow = true;
|
||||
bool mMultiColumn = false;
|
||||
};
|
||||
|
||||
#endif // VIEWDELEGATE_H
|
||||
|
@ -3,7 +3,9 @@
|
||||
#include "ui/MainWindow.h"
|
||||
#include "ui/DoubleTreeWidget.h"
|
||||
#include "ui/TreeView.h"
|
||||
#include "ui/TreeProxy.h"
|
||||
#include "ui/FileContextMenu.h"
|
||||
#include "conf/Settings.h"
|
||||
|
||||
#include <QTextEdit>
|
||||
|
||||
@ -22,6 +24,18 @@ using namespace QTest;
|
||||
\
|
||||
RepoView *repoView = window.currentView();
|
||||
|
||||
static void disableListView(TreeView &treeView, RepoView &repoView) {
|
||||
auto treeProxy = dynamic_cast<TreeProxy *>(treeView.model());
|
||||
QVERIFY(treeProxy);
|
||||
|
||||
auto diffTreeModel = dynamic_cast<DiffTreeModel *>(treeProxy->sourceModel());
|
||||
QVERIFY(diffTreeModel);
|
||||
|
||||
diffTreeModel->enableListView(false);
|
||||
Settings::instance()->setValue(Setting::Id::ShowChangedFilesAsList, false);
|
||||
repoView.refresh();
|
||||
}
|
||||
|
||||
class TestTreeView : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
@ -46,6 +60,7 @@ void TestTreeView::restoreStagedFileAfterCommit() {
|
||||
{
|
||||
auto unstagedTree = doubleTree->findChild<TreeView *>("Unstaged");
|
||||
QVERIFY(unstagedTree);
|
||||
disableListView(*unstagedTree, *view);
|
||||
QAbstractItemModel *unstagedModel = unstagedTree->model();
|
||||
// Wait for refresh
|
||||
auto timeout = Timeout(10000, "Repository didn't refresh in time");
|
||||
|
@ -13,6 +13,8 @@
|
||||
#include "ui/MainWindow.h"
|
||||
#include "ui/RepoView.h"
|
||||
#include "ui/TreeView.h"
|
||||
#include "ui/TreeProxy.h"
|
||||
#include "conf/Settings.h"
|
||||
#include <QFile>
|
||||
#include <QTextEdit>
|
||||
#include <QTextStream>
|
||||
@ -20,6 +22,18 @@
|
||||
using namespace Test;
|
||||
using namespace QTest;
|
||||
|
||||
static void disableListView(TreeView &treeView, RepoView &repoView) {
|
||||
auto treeProxy = dynamic_cast<TreeProxy *>(treeView.model());
|
||||
QVERIFY(treeProxy);
|
||||
|
||||
auto diffTreeModel = dynamic_cast<DiffTreeModel *>(treeProxy->sourceModel());
|
||||
QVERIFY(diffTreeModel);
|
||||
|
||||
diffTreeModel->enableListView(false);
|
||||
Settings::instance()->setValue(Setting::Id::ShowChangedFilesAsList, false);
|
||||
repoView.refresh();
|
||||
}
|
||||
|
||||
class TestIndex : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
@ -56,6 +70,8 @@ void TestIndex::stageAddition() {
|
||||
auto unstagedFiles = doubleTree->findChild<TreeView *>("Unstaged");
|
||||
QVERIFY(unstagedFiles);
|
||||
|
||||
disableListView(*unstagedFiles, *view);
|
||||
|
||||
auto stagedFiles = doubleTree->findChild<TreeView *>("Staged");
|
||||
QVERIFY(stagedFiles);
|
||||
|
||||
@ -151,6 +167,8 @@ void TestIndex::stageDirectory() {
|
||||
auto unstagedFiles = doubleTree->findChild<TreeView *>("Unstaged");
|
||||
QVERIFY(unstagedFiles);
|
||||
|
||||
disableListView(*unstagedFiles, *view);
|
||||
|
||||
auto stagedFiles = doubleTree->findChild<TreeView *>("Staged");
|
||||
QVERIFY(stagedFiles);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user