Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use columns for file name, directory, and state when files are shown as a list in TreeViews. Resolves Dense layout issue #547 #632

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
build
.cache
.DS_Store
.project
.vscode/
Expand All @@ -8,3 +9,5 @@ cmake-build-release/
build
.idea/
.venv
compile_commands.json

60 changes: 54 additions & 6 deletions src/ui/DiffTreeModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
// Author: Jason Haslam
//

#include <array>
#include "DiffTreeModel.h"
#include "conf/Settings.h"
#include "git/Blob.h"
Expand All @@ -16,15 +17,37 @@
#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),
mConstructed(false) {
// mConstructed only exists so that columnCount() returns the maximum number
// of columns when it is called internally by setHeaderData(). Without this,
// columnCount() will return 1 in the case that "List View" isn't selected and
// that means columns beyond the first won't be assigned the proper header
// value in the loop below.
for (int i = 0; i < kModelHeaders.size(); ++i) {
setHeaderData(i, Qt::Horizontal, kModelHeaders[i]);
}
mConstructed = true;
}

DiffTreeModel::~DiffTreeModel() { delete mRoot; }

Expand Down Expand Up @@ -91,7 +114,10 @@ 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() || !mConstructed ? QStandardItemModel::columnCount(parent)
: 1;
}

bool DiffTreeModel::hasChildren(const QModelIndex &parent) const {
return mRoot && node(parent)->hasChildren();
Expand Down Expand Up @@ -134,7 +160,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())
Expand Down Expand Up @@ -195,9 +221,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());
Expand All @@ -212,7 +244,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();
Expand Down Expand Up @@ -380,6 +412,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 ##############################################
//#############################################################################
Expand Down
7 changes: 5 additions & 2 deletions src/ui/DiffTreeModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -80,7 +81,7 @@ class Node : public QObject // item of the model
* 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:
Expand Down Expand Up @@ -157,6 +158,7 @@ class DiffTreeModel : public QAbstractItemModel {

private:
Node *node(const QModelIndex &index) const;
QVariant getDisplayRole(const QModelIndex &index) const;

QFileIconProvider mIconProvider;

Expand All @@ -165,6 +167,7 @@ class DiffTreeModel : public QAbstractItemModel {
git::Repository mRepo;

bool mListView = false;
bool mConstructed = false;
};

#endif /* DIFFTREEMODEL */
19 changes: 4 additions & 15 deletions src/ui/DoubleTreeWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Comment on lines -163 to -169
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fine


stagedFiles->setModel(new TreeProxy(true, mDiffTreeModel, this));

QHBoxLayout *hBoxLayout = new QHBoxLayout();
QLabel *label = new QLabel(kStagedFiles);
Expand All @@ -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);
Comment on lines -195 to -201
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fine

unstagedFiles->setModel(new TreeProxy(false, mDiffTreeModel, this));

hBoxLayout = new QHBoxLayout();
mUnstagedCommitedFiles = new QLabel(kUnstagedFiles);
Expand Down
6 changes: 4 additions & 2 deletions src/ui/TreeProxy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ const QString kLinkFmt = "<a href='%1'>%2</a>";

} // namespace

TreeProxy::TreeProxy(bool staged, QObject *parent)
: QSortFilterProxyModel(parent), mStaged(staged) {}
Comment on lines -26 to -27
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fine

TreeProxy::TreeProxy(bool staged, QAbstractItemModel *model, QObject *parent)
: mStaged(staged), QSortFilterProxyModel(parent) {
setSourceModel(model);
}

TreeProxy::~TreeProxy() {}

Expand Down
7 changes: 6 additions & 1 deletion src/ui/TreeProxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,25 @@
#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);
bool staged() { return mStaged; }

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,
Expand Down
30 changes: 29 additions & 1 deletion src/ui/TreeView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
Comment on lines +51 to +53
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need this explicitly? An update comes from the DoubleTreewidget

}

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) {
Expand All @@ -57,6 +81,10 @@ void TreeView::setModel(QAbstractItemModel *model) {
connect(model, &QAbstractItemModel::rowsInserted, this,
QOverload<const QModelIndex &, int, int>::of(
&TreeView::updateCollapseCount));

// Allow column sorting and set the default column and sort order.
setSortingEnabled(true);
sortByColumn(0, Qt::AscendingOrder);
}

void TreeView::discard(const QModelIndex &index, const bool force) {
Expand Down
7 changes: 6 additions & 1 deletion src/ui/TreeView.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
#define TREEVIEW_H

#include <QTreeView>
#include <memory>
#include "ViewDelegate.h"

class QItemDelegate;
class DiffTreeModel;
Expand Down Expand Up @@ -96,9 +98,12 @@ public slots:
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
48 changes: 14 additions & 34 deletions src/ui/ViewDelegate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Comment on lines -13 to -43
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fine, can be removed because it will be set always to false in the DoubleTreeWidget

// Draw badges.
QString status = index.data(TreeModel::StatusRole).toString();
if (!status.isEmpty()) {
Expand All @@ -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) {
Murmele marked this conversation as resolved.
Show resolved Hide resolved
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);
}
}

Expand Down
Loading
Loading