Skip to content

Commit

Permalink
Merge pull request #664 from Murmele/multicolumn
Browse files Browse the repository at this point in the history
Use columns for file name, directory, and state when files are shown as a list in TreeViews.
  • Loading branch information
Murmele authored Nov 20, 2023
2 parents bbb6b88 + 73e3cf3 commit 6984d51
Show file tree
Hide file tree
Showing 15 changed files with 218 additions and 101 deletions.
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

2 changes: 2 additions & 0 deletions src/conf/Setting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ void Setting::initialize(QMap<Id, QString> &keys) {
keys[Id::ShowCommitsId] = "commit/id";
keys[Id::ShowChangedFilesAsList] = "doubletreeview/listview";
keys[Id::ShowChangedFilesInSingleView] = "doubletreeview/single";
keys[Id::ShowChangedFilesMultiColumn] = "doubletreeview/listviewmulticolumn";
keys[Id::HideUntracked] = "untracked.hide";
}

void Prompt::initialize(QMap<Kind, QString> &keys) {
Expand Down
2 changes: 2 additions & 0 deletions src/conf/Setting.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ class Setting : public SettingsTempl<Setting> {
ShowCommitsDate,
ShowCommitsId,
ShowChangedFilesAsList,
ShowChangedFilesMultiColumn, // For the list only
ShowChangedFilesInSingleView,
HideUntracked,
Language,
};
Q_ENUM(Id)
Expand Down
69 changes: 63 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,18 +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) {}
: QAbstractItemModel(parent), mRepo(repo) {
setMultiColumn(mMultiColumn);
}

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

void DiffTreeModel::setMultiColumn(bool multi) {
beginResetModel();
mMultiColumn = multi;
endResetModel(); // Notify view about the change
}

void DiffTreeModel::createDiffTree() {

for (int patchNum = 0; patchNum < mDiff.count(); ++patchNum) {
Expand Down Expand Up @@ -91,7 +111,22 @@ int DiffTreeModel::rowCount(const QModelIndex &parent) const {
return mDiff ? node(parent)->children().size() : 0;
}

int DiffTreeModel::columnCount(const QModelIndex &parent) const { return 1; }
QVariant DiffTreeModel::headerData(int section, Qt::Orientation orientation,
int role) const {
if (section > 2 || section < 0) {
assert(false);
return QVariant();
}
if (orientation == Qt::Orientation::Vertical)
return QVariant();
if (role != Qt::DisplayRole)
return QVariant();
return kModelHeaders.at(section);
}

int DiffTreeModel::columnCount(const QModelIndex &parent) const {
return !asList() || !mMultiColumn ? 1 : kModelHeaders.size();
}

bool DiffTreeModel::hasChildren(const QModelIndex &parent) const {
return mRoot && node(parent)->hasChildren();
Expand Down Expand Up @@ -134,7 +169,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 +230,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 +253,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 +421,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() && mMultiColumn) {
QFileInfo fileInfo(node->path(true));
switch (index.column()) {
case 0:
return fileInfo.fileName();
case 1:
return fileInfo.path();
default:
return ""; // State
}
}
return node->name();
}

//#############################################################################
//###### DiffTreeModel::Node ##############################################
//#############################################################################
Expand Down
6 changes: 6 additions & 0 deletions src/ui/DiffTreeModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "git/Tree.h"
#include "git/Repository.h"
#include <QAbstractItemModel>
#include <QAbstractListModel>
#include <QFileIconProvider>
#include "git/Index.h"

Expand Down Expand Up @@ -98,7 +99,10 @@ class DiffTreeModel : public QAbstractItemModel {

void setDiff(const git::Diff &diff = git::Diff());
void refresh(const QStringList &paths);
void setMultiColumn(bool);

QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
bool hasChildren(const QModelIndex &parent = QModelIndex()) const override;
Expand Down Expand Up @@ -157,6 +161,7 @@ class DiffTreeModel : public QAbstractItemModel {

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

QFileIconProvider mIconProvider;

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

bool mListView = false;
bool mMultiColumn{true};
};

#endif /* DIFFTREEMODEL */
99 changes: 47 additions & 52 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 @@ -68,6 +67,21 @@ class SegmentedButton : public QWidget {

} // namespace

QAction *DoubleTreeWidget::setupAppearanceAction(const char *name,
Setting::Id id,
bool defaultValue) {
QAction *action = new QAction(tr(name));
action->setCheckable(true);
action->setChecked(Settings::instance()->value(id, defaultValue).toBool());
connect(action, &QAction::triggered, this, [this, id](bool checked) {
Settings::instance()->setValue(id, checked);
mSelectedFile.filename =
""; // When switching view, it is not possible to restore
RepoView::parentView(this)->refresh();
});
return action;
}

DoubleTreeWidget::DoubleTreeWidget(const git::Repository &repo, QWidget *parent)
: ContentWidget(parent) {
// first column
Expand All @@ -82,39 +96,21 @@ DoubleTreeWidget::DoubleTreeWidget(const git::Repository &repo, QWidget *parent)
ContextMenuButton *contextButton = new ContextMenuButton(this);
QMenu *contextMenu = new QMenu(this);
contextButton->setMenu(contextMenu);
QAction *singleTree = new QAction(tr("Single Tree View"));
singleTree->setCheckable(true);
singleTree->setChecked(
Settings::instance()
->value(Setting::Id::ShowChangedFilesInSingleView, false)
.toBool());
connect(singleTree, &QAction::triggered, this, [this](bool checked) {
Settings::instance()->setValue(Setting::Id::ShowChangedFilesInSingleView,
checked);
RepoView::parentView(this)->refresh();
});
QAction *listView = new QAction(tr("List View"));
listView->setCheckable(true);
listView->setChecked(Settings::instance()
->value(Setting::Id::ShowChangedFilesAsList, false)
.toBool());
connect(listView, &QAction::triggered, this, [this](bool checked) {
Settings::instance()->setValue(Setting::Id::ShowChangedFilesAsList,
checked);
RepoView::parentView(this)->refresh();
});
QAction *hideUntrackedFiles = new QAction(tr("Hide Untracked Files"));
hideUntrackedFiles->setCheckable(true);
hideUntrackedFiles->setChecked(
RepoView::parentView(parent)->repo().appConfig().value<bool>(
"untracked.hide", false));
connect(hideUntrackedFiles, &QAction::triggered, this, [this](bool checked) {
RepoView::parentView(this)->repo().appConfig().setValue("untracked.hide",
checked);
RepoView::parentView(this)->refresh();
});

QAction *singleTree = setupAppearanceAction(
"Single View", Setting::Id::ShowChangedFilesInSingleView);
QAction *listView =
setupAppearanceAction("List View", Setting::Id::ShowChangedFilesAsList);
QAction *multiColumn = setupAppearanceAction(
"Multi Column", Setting::Id::ShowChangedFilesMultiColumn, true);
RepoView::parentView(this)->refresh(); // apply read settings

QAction *hideUntrackedFiles = setupAppearanceAction(
"Hide Untracked Files", Setting::Id::HideUntracked, false);

contextMenu->addAction(singleTree);
contextMenu->addAction(listView);
contextMenu->addAction(multiColumn);
contextMenu->addAction(hideUntrackedFiles);
QHBoxLayout *buttonLayout = new QHBoxLayout();
buttonLayout->addStretch();
Expand Down Expand Up @@ -160,13 +156,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);
Expand All @@ -192,13 +183,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);
Expand Down Expand Up @@ -417,12 +402,6 @@ void DoubleTreeWidget::setDiff(const git::Diff &diff, const QString &file,
TreeProxy *proxy = static_cast<TreeProxy *>(unstagedFiles->model());
DiffTreeModel *model = static_cast<DiffTreeModel *>(proxy->sourceModel());
model->setDiff(diff);
// do not expand if to many files exist, it takes really long
// So do it only when there are less than 100
if (diff.isValid() && diff.count() < fileCountExpansionThreshold)
unstagedFiles->expandAll();
else
unstagedFiles->collapseAll();

// Single tree & list view.
bool singleTree =
Expand All @@ -432,15 +411,24 @@ void DoubleTreeWidget::setDiff(const git::Diff &diff, const QString &file,
bool listView = Settings::instance()
->value(Setting::Id::ShowChangedFilesAsList, false)
.toBool();
const bool multiColumn =
Settings::instance()
->value(Setting::Id::ShowChangedFilesMultiColumn, true)
.toBool();

// Widget modifications.
model->enableListView(listView);
model->setMultiColumn(multiColumn);
stagedFiles->setRootIsDecorated(!listView);
unstagedFiles->setRootIsDecorated(!listView);
// mUnstagedCommitedFiles->setVisible(!singleTree);
collapseButtonStagedFiles->setVisible(!listView);
collapseButtonUnstagedFiles->setVisible(!listView);

unstagedFiles->updateView(); // Must be before expandAll/collapseAll is done,
// otherwise the collapse counter is wrong
stagedFiles->updateView();

// If statusDiff, there exist no staged/unstaged, but only
// the commited files must be shown
if (!diff.isValid() || diff.isStatusDiff()) {
Expand All @@ -457,6 +445,13 @@ void DoubleTreeWidget::setDiff(const git::Diff &diff, const QString &file,
mStagedWidget->setVisible(false);
}

// do not expand if to many files exist, it takes really long
// So do it only when there are less than 100
if (diff.isValid() && diff.count() < fileCountExpansionThreshold)
unstagedFiles->expandAll();
else
unstagedFiles->collapseAll();

// Clear editor.
mEditor->clear();

Expand Down
3 changes: 3 additions & 0 deletions src/ui/DoubleTreeWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "DetailView.h"
#include "git/Index.h"
#include <QModelIndexList>
#include "conf/Settings.h"

class QTreeView;
class TreeView;
Expand Down Expand Up @@ -67,6 +68,8 @@ private slots:
void loadEditorContent(const QModelIndexList &indexes);
void toggleCollapseStagedFiles();
void toggleCollapseUnstagedFiles();
QAction *setupAppearanceAction(const char *name, Setting::Id id,
bool defaultValue = false);

DiffTreeModel *mDiffTreeModel{nullptr};
TreeView *stagedFiles{nullptr};
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) {}
TreeProxy::TreeProxy(bool staged, QAbstractItemModel *model, QObject *parent)
: mStaged(staged), QSortFilterProxyModel(parent) {
setSourceModel(model);
}

TreeProxy::~TreeProxy() {}

Expand Down
Loading

0 comments on commit 6984d51

Please sign in to comment.