From 1d6d53ba009e374f361d017a0238a583d49831bf Mon Sep 17 00:00:00 2001 From: devnull Date: Thu, 31 Aug 2023 16:30:36 -0400 Subject: [PATCH 01/12] Use columns for file name, directory, and state when files are shown as a list in TreeViews. Resolves Dense layout issue #547 --- .gitignore | 3 +++ src/ui/DiffTreeModel.cpp | 52 ++++++++++++++++++++++++++++++++----- src/ui/DiffTreeModel.h | 6 +++-- src/ui/DoubleTreeWidget.cpp | 19 +++----------- src/ui/TreeProxy.cpp | 6 +++-- src/ui/TreeProxy.h | 7 ++++- src/ui/TreeView.cpp | 26 ++++++++++++++++++- src/ui/TreeView.h | 7 ++++- src/ui/ViewDelegate.cpp | 48 ++++++++++------------------------ src/ui/ViewDelegate.h | 7 +++-- test/TreeView.cpp | 15 +++++++++++ test/index.cpp | 18 +++++++++++++ 12 files changed, 148 insertions(+), 66 deletions(-) diff --git a/.gitignore b/.gitignore index 6786e6c10..ca6f9e259 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ build +.cache .DS_Store .project .vscode/ @@ -8,3 +9,5 @@ cmake-build-release/ build .idea/ .venv +compile_commands.json + diff --git a/src/ui/DiffTreeModel.cpp b/src/ui/DiffTreeModel.cpp index a722705c6..6745d18dd 100644 --- a/src/ui/DiffTreeModel.cpp +++ b/src/ui/DiffTreeModel.cpp @@ -7,6 +7,7 @@ // Author: Jason Haslam // +#include #include "DiffTreeModel.h" #include "conf/Settings.h" #include "git/Blob.h" @@ -16,15 +17,30 @@ #include "git/Patch.h" #include #include +#include namespace { const QString kLinkFmt = "%2"; +const std::array 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(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 ############################################## //############################################################################# diff --git a/src/ui/DiffTreeModel.h b/src/ui/DiffTreeModel.h index 9b1d2dba4..0dc424fcc 100644 --- a/src/ui/DiffTreeModel.h +++ b/src/ui/DiffTreeModel.h @@ -14,7 +14,8 @@ #include "git/Index.h" #include "git/Tree.h" #include "git/Repository.h" -#include +#include +#include #include #include "git/Index.h" @@ -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: @@ -157,6 +158,7 @@ class DiffTreeModel : public QAbstractItemModel { private: Node *node(const QModelIndex &index) const; + QVariant getDisplayRole(const QModelIndex &index) const; QFileIconProvider mIconProvider; diff --git a/src/ui/DoubleTreeWidget.cpp b/src/ui/DoubleTreeWidget.cpp index c59746705..45a005f83 100644 --- a/src/ui/DoubleTreeWidget.cpp +++ b/src/ui/DoubleTreeWidget.cpp @@ -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); diff --git a/src/ui/TreeProxy.cpp b/src/ui/TreeProxy.cpp index f956c3a6b..2b447f855 100644 --- a/src/ui/TreeProxy.cpp +++ b/src/ui/TreeProxy.cpp @@ -23,8 +23,10 @@ const QString kLinkFmt = "%2"; } // 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() {} diff --git a/src/ui/TreeProxy.h b/src/ui/TreeProxy.h index 2e8d75ae2..93ab7a4fc 100644 --- a/src/ui/TreeProxy.h +++ b/src/ui/TreeProxy.h @@ -16,13 +16,14 @@ #include #include +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 @@ class TreeProxy : public QSortFilterProxyModel { 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, diff --git a/src/ui/TreeView.cpp b/src/ui/TreeView.cpp index a7895dd18..75c720b41 100644 --- a/src/ui/TreeView.cpp +++ b/src/ui/TreeView.cpp @@ -24,6 +24,9 @@ #include "RepoView.h" #include #include +#include "conf/Settings.h" +#include +#include #ifdef Q_OS_WIN #define ICON_SIZE 48 @@ -41,8 +44,29 @@ const QString kLabelFmt = "

%1

"; } // namespace TreeView::TreeView(QWidget *parent, const QString &name) - : QTreeView(parent), mSharedDelegate(new ViewDelegate(this)), mName(name) { + : QTreeView(parent), mDelegateCol(0), + mFileListDelegatePtr(std::make_unique(this, true)), + mFileTreeDelegatePtr(std::make_unique(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) { diff --git a/src/ui/TreeView.h b/src/ui/TreeView.h index 138fd34e0..87cc642f1 100644 --- a/src/ui/TreeView.h +++ b/src/ui/TreeView.h @@ -11,6 +11,8 @@ #define TREEVIEW_H #include +#include +#include "ViewDelegate.h" class QItemDelegate; class DiffTreeModel; @@ -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 mFileListDelegatePtr; + std::unique_ptr mFileTreeDelegatePtr; + int mDelegateCol = 0; }; #endif // TREEVIEW_H diff --git a/src/ui/ViewDelegate.cpp b/src/ui/ViewDelegate.cpp index 194a915a8..8e69662ef 100644 --- a/src/ui/ViewDelegate.cpp +++ b/src/ui/ViewDelegate.cpp @@ -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); } } diff --git a/src/ui/ViewDelegate.h b/src/ui/ViewDelegate.h index 03f168a08..fc81c09ae 100644 --- a/src/ui/ViewDelegate.h +++ b/src/ui/ViewDelegate.h @@ -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 @@ class ViewDelegate : public QItemDelegate { const QModelIndex &index) const override; private: - bool mDrawArrow = true; + bool mMultiColumn = false; }; #endif // VIEWDELEGATE_H diff --git a/test/TreeView.cpp b/test/TreeView.cpp index 246e8efbf..d85767227 100644 --- a/test/TreeView.cpp +++ b/test/TreeView.cpp @@ -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 @@ -22,6 +24,18 @@ using namespace QTest; \ RepoView *repoView = window.currentView(); +static void disableListView(TreeView &treeView, RepoView &repoView) { + auto treeProxy = dynamic_cast(treeView.model()); + QVERIFY(treeProxy); + + auto diffTreeModel = dynamic_cast(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("Unstaged"); QVERIFY(unstagedTree); + disableListView(*unstagedTree, *view); QAbstractItemModel *unstagedModel = unstagedTree->model(); // Wait for refresh auto timeout = Timeout(10000, "Repository didn't refresh in time"); diff --git a/test/index.cpp b/test/index.cpp index c6592afe6..f3fdc3c80 100644 --- a/test/index.cpp +++ b/test/index.cpp @@ -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 #include #include @@ -20,6 +22,18 @@ using namespace Test; using namespace QTest; +static void disableListView(TreeView &treeView, RepoView &repoView) { + auto treeProxy = dynamic_cast(treeView.model()); + QVERIFY(treeProxy); + + auto diffTreeModel = dynamic_cast(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("Unstaged"); QVERIFY(unstagedFiles); + disableListView(*unstagedFiles, *view); + auto stagedFiles = doubleTree->findChild("Staged"); QVERIFY(stagedFiles); @@ -151,6 +167,8 @@ void TestIndex::stageDirectory() { auto unstagedFiles = doubleTree->findChild("Unstaged"); QVERIFY(unstagedFiles); + disableListView(*unstagedFiles, *view); + auto stagedFiles = doubleTree->findChild("Staged"); QVERIFY(stagedFiles); From c416cc63cdcac1e8d70b539071f4c50d480abc82 Mon Sep 17 00:00:00 2001 From: devnull Date: Mon, 4 Sep 2023 16:20:40 -0400 Subject: [PATCH 02/12] Add sort by column. Force all columns headers to be initialized. --- src/ui/DiffTreeModel.cpp | 12 ++++++++++-- src/ui/DiffTreeModel.h | 1 + src/ui/TreeView.cpp | 4 ++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/ui/DiffTreeModel.cpp b/src/ui/DiffTreeModel.cpp index 6745d18dd..a9f5de35d 100644 --- a/src/ui/DiffTreeModel.cpp +++ b/src/ui/DiffTreeModel.cpp @@ -36,10 +36,17 @@ bool asList() { } // namespace DiffTreeModel::DiffTreeModel(const git::Repository &repo, QObject *parent) - : QStandardItemModel(0, kModelHeaders.size(), 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; } @@ -108,7 +115,8 @@ int DiffTreeModel::rowCount(const QModelIndex &parent) const { } int DiffTreeModel::columnCount(const QModelIndex &parent) const { - return asList() ? QStandardItemModel::columnCount(parent) : 1; + return asList() || !mConstructed ? QStandardItemModel::columnCount(parent) + : 1; } bool DiffTreeModel::hasChildren(const QModelIndex &parent) const { diff --git a/src/ui/DiffTreeModel.h b/src/ui/DiffTreeModel.h index 0dc424fcc..8cc14b6c7 100644 --- a/src/ui/DiffTreeModel.h +++ b/src/ui/DiffTreeModel.h @@ -167,6 +167,7 @@ class DiffTreeModel : public QStandardItemModel { git::Repository mRepo; bool mListView = false; + bool mConstructed = false; }; #endif /* DIFFTREEMODEL */ diff --git a/src/ui/TreeView.cpp b/src/ui/TreeView.cpp index 75c720b41..aa38d506c 100644 --- a/src/ui/TreeView.cpp +++ b/src/ui/TreeView.cpp @@ -81,6 +81,10 @@ void TreeView::setModel(QAbstractItemModel *model) { connect(model, &QAbstractItemModel::rowsInserted, this, QOverload::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) { From d54bb202535e8a92107acb743e5d2734ff5784a3 Mon Sep 17 00:00:00 2001 From: Martin Marmsoler Date: Sun, 12 Nov 2023 16:58:07 +0100 Subject: [PATCH 03/12] Use function to create actions to make it more readable --- src/conf/Setting.cpp | 1 + src/conf/Setting.h | 1 + src/ui/DoubleTreeWidget.cpp | 54 +++++++++++++++---------------------- src/ui/DoubleTreeWidget.h | 3 +++ 4 files changed, 27 insertions(+), 32 deletions(-) diff --git a/src/conf/Setting.cpp b/src/conf/Setting.cpp index e184ba3ae..d1a64d130 100644 --- a/src/conf/Setting.cpp +++ b/src/conf/Setting.cpp @@ -40,6 +40,7 @@ void Setting::initialize(QMap &keys) { keys[Id::ShowCommitsId] = "commit/id"; keys[Id::ShowChangedFilesAsList] = "doubletreeview/listview"; keys[Id::ShowChangedFilesInSingleView] = "doubletreeview/single"; + keys[Id::HideUntracked] = "untracked.hide"; } void Prompt::initialize(QMap &keys) { diff --git a/src/conf/Setting.h b/src/conf/Setting.h index 2d84b5760..008c9f6e6 100644 --- a/src/conf/Setting.h +++ b/src/conf/Setting.h @@ -63,6 +63,7 @@ class Setting : public SettingsTempl { ShowCommitsId, ShowChangedFilesAsList, ShowChangedFilesInSingleView, + HideUntracked, Language, }; Q_ENUM(Id) diff --git a/src/ui/DoubleTreeWidget.cpp b/src/ui/DoubleTreeWidget.cpp index 45a005f83..62fa6fcef 100644 --- a/src/ui/DoubleTreeWidget.cpp +++ b/src/ui/DoubleTreeWidget.cpp @@ -67,6 +67,18 @@ 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); + RepoView::parentView(this)->refresh(); + }); +} + DoubleTreeWidget::DoubleTreeWidget(const git::Repository &repo, QWidget *parent) : ContentWidget(parent) { // first column @@ -81,38 +93,16 @@ 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()); - RepoView::parentView(this)->refresh(); - 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( - "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); + 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(hideUntrackedFiles); diff --git a/src/ui/DoubleTreeWidget.h b/src/ui/DoubleTreeWidget.h index de518586a..e5768624a 100644 --- a/src/ui/DoubleTreeWidget.h +++ b/src/ui/DoubleTreeWidget.h @@ -14,6 +14,7 @@ #include "DetailView.h" #include "git/Index.h" #include +#include "conf/Settings.h" class QTreeView; class TreeView; @@ -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}; From 1bad8fa305301d2e3b93c8a1d14f68a7aaf2c1de Mon Sep 17 00:00:00 2001 From: Martin Marmsoler Date: Sun, 12 Nov 2023 16:58:30 +0100 Subject: [PATCH 04/12] remove mDelegateCol because it is already set to 0 in the header --- src/ui/TreeView.cpp | 2 +- src/ui/TreeView.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ui/TreeView.cpp b/src/ui/TreeView.cpp index aa38d506c..fd9c7684c 100644 --- a/src/ui/TreeView.cpp +++ b/src/ui/TreeView.cpp @@ -44,7 +44,7 @@ const QString kLabelFmt = "

%1

"; } // namespace TreeView::TreeView(QWidget *parent, const QString &name) - : QTreeView(parent), mDelegateCol(0), + : QTreeView(parent), mFileListDelegatePtr(std::make_unique(this, true)), mFileTreeDelegatePtr(std::make_unique(this)), mName(name) { setObjectName(name); diff --git a/src/ui/TreeView.h b/src/ui/TreeView.h index 87cc642f1..ab6af81c4 100644 --- a/src/ui/TreeView.h +++ b/src/ui/TreeView.h @@ -103,7 +103,7 @@ public slots: QString mName; std::unique_ptr mFileListDelegatePtr; std::unique_ptr mFileTreeDelegatePtr; - int mDelegateCol = 0; + int mDelegateCol{false}; }; #endif // TREEVIEW_H From 7bb4a7a5f048aba57f76412303336dcd4e6acb1e Mon Sep 17 00:00:00 2001 From: Martin Marmsoler Date: Sun, 12 Nov 2023 17:41:13 +0100 Subject: [PATCH 05/12] Use QAbstractItemModel because then mConstructed is not required and we gain more control --- src/ui/DiffTreeModel.cpp | 28 +++++++++++++++------------- src/ui/DiffTreeModel.h | 8 +++++--- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/ui/DiffTreeModel.cpp b/src/ui/DiffTreeModel.cpp index a9f5de35d..7658cadb2 100644 --- a/src/ui/DiffTreeModel.cpp +++ b/src/ui/DiffTreeModel.cpp @@ -36,17 +36,7 @@ bool asList() { } // namespace DiffTreeModel::DiffTreeModel(const git::Repository &repo, QObject *parent) - : 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; + : QAbstractItemModel(parent), mRepo(repo) { } DiffTreeModel::~DiffTreeModel() { delete mRoot; } @@ -114,9 +104,21 @@ int DiffTreeModel::rowCount(const QModelIndex &parent) const { return mDiff ? node(parent)->children().size() : 0; } +QVariant DiffTreeModel::headerData(int section, Qt::Orientation orientation, + int role) const { + if (section > 2) { + 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() || !mConstructed ? QStandardItemModel::columnCount(parent) - : 1; + return !asList() || !mMultiColumn ? 1 : kModelHeaders.size(); } bool DiffTreeModel::hasChildren(const QModelIndex &parent) const { diff --git a/src/ui/DiffTreeModel.h b/src/ui/DiffTreeModel.h index 8cc14b6c7..9aa53c7d9 100644 --- a/src/ui/DiffTreeModel.h +++ b/src/ui/DiffTreeModel.h @@ -14,7 +14,7 @@ #include "git/Index.h" #include "git/Tree.h" #include "git/Repository.h" -#include +#include #include #include #include "git/Index.h" @@ -81,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 QStandardItemModel { +class DiffTreeModel : public QAbstractItemModel { Q_OBJECT public: @@ -100,6 +100,8 @@ class DiffTreeModel : public QStandardItemModel { void setDiff(const git::Diff &diff = git::Diff()); void refresh(const QStringList &paths); + 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; @@ -167,7 +169,7 @@ class DiffTreeModel : public QStandardItemModel { git::Repository mRepo; bool mListView = false; - bool mConstructed = false; + bool mMultiColumn{true}; }; #endif /* DIFFTREEMODEL */ From 1f3e81ab0bc50ed65b94ef4f7a3fb004ca3df728 Mon Sep 17 00:00:00 2001 From: Martin Marmsoler Date: Sun, 12 Nov 2023 17:42:25 +0100 Subject: [PATCH 06/12] Add setting for multicolumn --- src/conf/Setting.cpp | 1 + src/conf/Setting.h | 1 + src/ui/DiffTreeModel.cpp | 11 +++++++++-- src/ui/DiffTreeModel.h | 1 + src/ui/DoubleTreeWidget.cpp | 8 ++++++++ 5 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/conf/Setting.cpp b/src/conf/Setting.cpp index d1a64d130..4ff5b8713 100644 --- a/src/conf/Setting.cpp +++ b/src/conf/Setting.cpp @@ -40,6 +40,7 @@ void Setting::initialize(QMap &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"; } diff --git a/src/conf/Setting.h b/src/conf/Setting.h index 008c9f6e6..b3f9f1252 100644 --- a/src/conf/Setting.h +++ b/src/conf/Setting.h @@ -62,6 +62,7 @@ class Setting : public SettingsTempl { ShowCommitsDate, ShowCommitsId, ShowChangedFilesAsList, + ShowChangedFilesMultiColumn, // For the list only ShowChangedFilesInSingleView, HideUntracked, Language, diff --git a/src/ui/DiffTreeModel.cpp b/src/ui/DiffTreeModel.cpp index 7658cadb2..75041c19c 100644 --- a/src/ui/DiffTreeModel.cpp +++ b/src/ui/DiffTreeModel.cpp @@ -37,10 +37,17 @@ bool asList() { DiffTreeModel::DiffTreeModel(const git::Repository &repo, QObject *parent) : 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) { @@ -416,7 +423,7 @@ Node *DiffTreeModel::node(const QModelIndex &index) const { QVariant DiffTreeModel::getDisplayRole(const QModelIndex &index) const { Node *node = this->node(index); - if (asList()) { + if (asList() && mMultiColumn) { QFileInfo fileInfo(node->path(true)); switch (index.column()) { case 0: @@ -424,7 +431,7 @@ QVariant DiffTreeModel::getDisplayRole(const QModelIndex &index) const { case 1: return fileInfo.path(); default: - return ""; + return ""; // State } } return node->name(); diff --git a/src/ui/DiffTreeModel.h b/src/ui/DiffTreeModel.h index 9aa53c7d9..1ffd6d578 100644 --- a/src/ui/DiffTreeModel.h +++ b/src/ui/DiffTreeModel.h @@ -99,6 +99,7 @@ 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; diff --git a/src/ui/DoubleTreeWidget.cpp b/src/ui/DoubleTreeWidget.cpp index 62fa6fcef..e78a2bfec 100644 --- a/src/ui/DoubleTreeWidget.cpp +++ b/src/ui/DoubleTreeWidget.cpp @@ -98,6 +98,8 @@ DoubleTreeWidget::DoubleTreeWidget(const git::Repository &repo, QWidget *parent) "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( @@ -105,6 +107,7 @@ DoubleTreeWidget::DoubleTreeWidget(const git::Repository &repo, QWidget *parent) contextMenu->addAction(singleTree); contextMenu->addAction(listView); + contextMenu->addAction(multiColumn); contextMenu->addAction(hideUntrackedFiles); QHBoxLayout *buttonLayout = new QHBoxLayout(); buttonLayout->addStretch(); @@ -411,9 +414,14 @@ 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); From ad5cfe306cf1d1fd6df422caf6b2f4d0b73ba26b Mon Sep 17 00:00:00 2001 From: Martin Marmsoler Date: Sun, 12 Nov 2023 17:42:49 +0100 Subject: [PATCH 07/12] forgotten to return value --- src/ui/DoubleTreeWidget.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ui/DoubleTreeWidget.cpp b/src/ui/DoubleTreeWidget.cpp index e78a2bfec..025eb299d 100644 --- a/src/ui/DoubleTreeWidget.cpp +++ b/src/ui/DoubleTreeWidget.cpp @@ -77,6 +77,7 @@ QAction *DoubleTreeWidget::setupAppearanceAction(const char *name, Settings::instance()->setValue(id, checked); RepoView::parentView(this)->refresh(); }); + return action; } DoubleTreeWidget::DoubleTreeWidget(const git::Repository &repo, QWidget *parent) From 9636a11923672f99d2d4ffe9d574978be7a0f757 Mon Sep 17 00:00:00 2001 From: Martin Marmsoler Date: Sun, 12 Nov 2023 17:43:45 +0100 Subject: [PATCH 08/12] reset selection, because when a list item is selected and treeview is used, no restore is possible with the collapse counter we use --- src/ui/DoubleTreeWidget.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ui/DoubleTreeWidget.cpp b/src/ui/DoubleTreeWidget.cpp index 025eb299d..eb191a178 100644 --- a/src/ui/DoubleTreeWidget.cpp +++ b/src/ui/DoubleTreeWidget.cpp @@ -75,6 +75,8 @@ QAction *DoubleTreeWidget::setupAppearanceAction(const char *name, 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; From d598ac21fa5b4a0e82e9768b25b4f459bf8fa9e2 Mon Sep 17 00:00:00 2001 From: Martin Marmsoler Date: Sun, 12 Nov 2023 17:44:04 +0100 Subject: [PATCH 09/12] updateView when updating the rest --- src/ui/DoubleTreeWidget.cpp | 3 +++ src/ui/TreeView.cpp | 3 --- src/ui/TreeView.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ui/DoubleTreeWidget.cpp b/src/ui/DoubleTreeWidget.cpp index eb191a178..36018da72 100644 --- a/src/ui/DoubleTreeWidget.cpp +++ b/src/ui/DoubleTreeWidget.cpp @@ -447,6 +447,9 @@ void DoubleTreeWidget::setDiff(const git::Diff &diff, const QString &file, mStagedWidget->setVisible(false); } + unstagedFiles->updateView(); + stagedFiles->updateView(); + // Clear editor. mEditor->clear(); diff --git a/src/ui/TreeView.cpp b/src/ui/TreeView.cpp index fd9c7684c..22a760be9 100644 --- a/src/ui/TreeView.cpp +++ b/src/ui/TreeView.cpp @@ -48,9 +48,6 @@ TreeView::TreeView(QWidget *parent, const QString &name) mFileListDelegatePtr(std::make_unique(this, true)), mFileTreeDelegatePtr(std::make_unique(this)), mName(name) { setObjectName(name); - connect(RepoView::parentView(this)->repo().notifier(), - &git::RepositoryNotifier::referenceUpdated, this, - &TreeView::updateView); } void TreeView::updateView() { diff --git a/src/ui/TreeView.h b/src/ui/TreeView.h index ab6af81c4..ad489bf77 100644 --- a/src/ui/TreeView.h +++ b/src/ui/TreeView.h @@ -42,6 +42,7 @@ class TreeView : public QTreeView { * \return */ int countCollapsed(QModelIndex parent = QModelIndex(), bool recursive = true); + void updateView(); public slots: /*! * \brief expandAll @@ -98,7 +99,6 @@ public slots: bool suppressDeselectionHandling{false}; int mCollapseCount; // Counts the number of collapsed folders. bool mSupressItemExpandStateChanged{false}; - void updateView(); QString mName; std::unique_ptr mFileListDelegatePtr; From 986cbf140e19f8ee0ad98975b2ba42c9c63dbecd Mon Sep 17 00:00:00 2001 From: Martin Marmsoler Date: Sun, 12 Nov 2023 18:10:12 +0100 Subject: [PATCH 10/12] updateView() must be done before expandAll is done, otherwise the collapse counter is wrong and an assert will be triggered --- src/ui/DoubleTreeWidget.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/ui/DoubleTreeWidget.cpp b/src/ui/DoubleTreeWidget.cpp index 36018da72..437b588c4 100644 --- a/src/ui/DoubleTreeWidget.cpp +++ b/src/ui/DoubleTreeWidget.cpp @@ -402,12 +402,6 @@ void DoubleTreeWidget::setDiff(const git::Diff &diff, const QString &file, TreeProxy *proxy = static_cast(unstagedFiles->model()); DiffTreeModel *model = static_cast(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 = @@ -431,6 +425,9 @@ void DoubleTreeWidget::setDiff(const git::Diff &diff, const QString &file, 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()) { @@ -447,8 +444,12 @@ void DoubleTreeWidget::setDiff(const git::Diff &diff, const QString &file, mStagedWidget->setVisible(false); } - unstagedFiles->updateView(); - stagedFiles->updateView(); + // 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(); From c836908b9e8aa43c0ed09fa2a0b559d90bc4d9ad Mon Sep 17 00:00:00 2001 From: Martin Marmsoler Date: Sun, 12 Nov 2023 18:19:12 +0100 Subject: [PATCH 11/12] Fix bug --- src/ui/DiffTreeModel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/DiffTreeModel.cpp b/src/ui/DiffTreeModel.cpp index 75041c19c..44dffd0b0 100644 --- a/src/ui/DiffTreeModel.cpp +++ b/src/ui/DiffTreeModel.cpp @@ -113,7 +113,7 @@ int DiffTreeModel::rowCount(const QModelIndex &parent) const { QVariant DiffTreeModel::headerData(int section, Qt::Orientation orientation, int role) const { - if (section > 2) { + if (section > 2 || section < 0) { assert(false); return QVariant(); } From 73e3cf329a1a88e4cea814e244076f474786ea59 Mon Sep 17 00:00:00 2001 From: Martin Marmsoler Date: Sun, 12 Nov 2023 18:22:36 +0100 Subject: [PATCH 12/12] Format --- src/ui/DoubleTreeWidget.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ui/DoubleTreeWidget.cpp b/src/ui/DoubleTreeWidget.cpp index 437b588c4..69567f899 100644 --- a/src/ui/DoubleTreeWidget.cpp +++ b/src/ui/DoubleTreeWidget.cpp @@ -425,7 +425,8 @@ void DoubleTreeWidget::setDiff(const git::Diff &diff, const QString &file, collapseButtonStagedFiles->setVisible(!listView); collapseButtonUnstagedFiles->setVisible(!listView); - unstagedFiles->updateView(); // Must be before expandAll/collapseAll is done, otherwise the collapse counter is wrong + 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 @@ -444,8 +445,8 @@ 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 + // 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