File indexing completed on 2024-04-28 05:49:04

0001 /*
0002     SPDX-FileCopyrightText: 2021 Waqar Ahmed <waqar.17a@gmail.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "comparebranchesview.h"
0008 #include "diffparams.h"
0009 #include "hostprocess.h"
0010 #include "kateprojectitem.h"
0011 #include "kateprojectpluginview.h"
0012 #include "kateprojectworker.h"
0013 #include "ktexteditor_utils.h"
0014 #include <gitprocess.h>
0015 
0016 #include <QDir>
0017 #include <QPainter>
0018 #include <QProcess>
0019 #include <QStyledItemDelegate>
0020 #include <QVBoxLayout>
0021 
0022 #include <KColorScheme>
0023 #include <KLocalizedString>
0024 
0025 // TODO: this is duplicated in libkateprivate as DiffStyleDelegate
0026 class CompareBranchesDiffStyleDelegate : public QStyledItemDelegate
0027 {
0028 public:
0029     CompareBranchesDiffStyleDelegate(QObject *parent)
0030         : QStyledItemDelegate(parent)
0031     {
0032     }
0033 
0034     void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
0035     {
0036         if (index.data(KateProjectItem::TypeRole).toInt() == KateProjectItem::Directory) {
0037             QStyledItemDelegate::paint(painter, option, index);
0038             return;
0039         }
0040 
0041         QStyleOptionViewItem options = option;
0042         initStyleOption(&options, index);
0043 
0044         painter->save();
0045 
0046         // paint background
0047         if (option.state & QStyle::State_Selected) {
0048             painter->fillRect(option.rect, option.palette.highlight());
0049         } else {
0050             painter->fillRect(option.rect, option.palette.base());
0051         }
0052 
0053         int add = index.data(Qt::UserRole + 2).toInt();
0054         int sub = index.data(Qt::UserRole + 3).toInt();
0055         QString adds = QString(QStringLiteral("+") + QString::number(add));
0056         QString subs = QString(QStringLiteral(" -") + QString::number(sub));
0057         QString file = options.text;
0058 
0059         options.text = QString(); // clear old text
0060         options.widget->style()->drawControl(QStyle::CE_ItemViewItem, &options, painter, options.widget);
0061 
0062         QRect r = options.rect;
0063 
0064         // don't draw over icon
0065         r.setX(r.x() + option.decorationSize.width() + 5);
0066 
0067         const QFontMetrics &fm = options.fontMetrics;
0068 
0069         // adds width
0070         int aw = fm.horizontalAdvance(adds);
0071         // subs width
0072         int sw = fm.horizontalAdvance(subs);
0073 
0074         // subtract this from total width of rect
0075         int totalw = r.width();
0076         totalw = totalw - (aw + sw);
0077 
0078         // get file name, elide if necessary
0079         QString filename = fm.elidedText(file, Qt::ElideRight, totalw);
0080 
0081         painter->drawText(r, Qt::AlignVCenter, filename);
0082 
0083         KColorScheme c;
0084         const auto red = c.shade(c.foreground(KColorScheme::NegativeText).color(), KColorScheme::MidlightShade, 1);
0085         const auto green = c.shade(c.foreground(KColorScheme::PositiveText).color(), KColorScheme::MidlightShade, 1);
0086 
0087         r.setX(r.x() + totalw);
0088         painter->setPen(green);
0089         painter->drawText(r, Qt::AlignVCenter, adds);
0090 
0091         painter->setPen(red);
0092         r.setX(r.x() + aw);
0093         painter->drawText(r, Qt::AlignVCenter, subs);
0094 
0095         painter->restore();
0096     }
0097 };
0098 
0099 static void createFileTree(QStandardItem *parent, const QString &basePath, const QList<GitUtils::StatusItem> &files)
0100 {
0101     QDir dir(basePath);
0102     const QString dirPath = dir.path() + QLatin1Char('/');
0103     QHash<QString, QStandardItem *> dir2Item;
0104     dir2Item[QString()] = parent;
0105     for (const auto &file : qAsConst(files)) {
0106         const QString filePath = QString::fromUtf8(file.file);
0107         /**
0108          * cheap file name computation
0109          * we do this A LOT, QFileInfo is very expensive just for this operation
0110          */
0111         const int slashIndex = filePath.lastIndexOf(QLatin1Char('/'));
0112         const QString fileName = (slashIndex < 0) ? filePath : filePath.mid(slashIndex + 1);
0113         const QString filePathName = (slashIndex < 0) ? QString() : filePath.left(slashIndex);
0114         const QString fullFilePath = dirPath + filePath;
0115 
0116         /**
0117          * construct the item with right directory prefix
0118          * already hang in directories in tree
0119          */
0120         KateProjectItem *fileItem = new KateProjectItem(KateProjectItem::File, fileName);
0121         fileItem->setData(fullFilePath, Qt::UserRole);
0122         fileItem->setData(file.statusChar, Qt::UserRole + 1);
0123         fileItem->setData(file.linesAdded, Qt::UserRole + 2);
0124         fileItem->setData(file.linesRemoved, Qt::UserRole + 3);
0125 
0126         // put in our item to the right directory parent
0127         KateProjectWorker::directoryParent(dir, dir2Item, filePathName)->appendRow(fileItem);
0128     }
0129 }
0130 
0131 CompareBranchesView::CompareBranchesView(QWidget *parent, const QString &gitPath, const QString fromB, const QString &toBr, QList<GitUtils::StatusItem> items)
0132     : QWidget(parent)
0133     , m_gitDir(gitPath)
0134     , m_fromBr(fromB)
0135     , m_toBr(toBr)
0136 {
0137     setLayout(new QVBoxLayout);
0138 
0139     QStandardItem root;
0140     createFileTree(&root, m_gitDir, items);
0141 
0142     m_model.clear();
0143     m_model.invisibleRootItem()->appendColumn(root.takeColumn(0));
0144 
0145     m_backBtn.setText(i18n("Back"));
0146     m_backBtn.setIcon(QIcon::fromTheme(QStringLiteral("go-previous")));
0147     connect(&m_backBtn, &QPushButton::clicked, this, &CompareBranchesView::backClicked);
0148     layout()->addWidget(&m_backBtn);
0149 
0150     m_tree.setModel(&m_model);
0151     layout()->addWidget(&m_tree);
0152 
0153     m_tree.setHeaderHidden(true);
0154     m_tree.setEditTriggers(QTreeView::NoEditTriggers);
0155     m_tree.setItemDelegate(new CompareBranchesDiffStyleDelegate(this));
0156     m_tree.expandAll();
0157 
0158     connect(&m_tree, &QTreeView::clicked, this, &CompareBranchesView::showDiff);
0159 }
0160 
0161 void CompareBranchesView::showDiff(const QModelIndex &idx)
0162 {
0163     auto file = idx.data(Qt::UserRole).toString().remove(m_gitDir + QLatin1Char('/'));
0164     QProcess git;
0165     if (!setupGitProcess(git, m_gitDir, {QStringLiteral("diff"), QStringLiteral("%1...%2").arg(m_fromBr).arg(m_toBr), QStringLiteral("--"), file})) {
0166         return;
0167     }
0168     startHostProcess(git, QProcess::ReadOnly);
0169 
0170     if (git.waitForStarted() && git.waitForFinished(-1)) {
0171         if (git.exitStatus() != QProcess::NormalExit || git.exitCode() != 0) {
0172             return;
0173         }
0174     }
0175 
0176     DiffParams d;
0177     d.tabTitle = QStringLiteral("Diff %1[%2 .. %3]").arg(Utils::fileNameFromPath(file)).arg(m_fromBr).arg(m_toBr);
0178     d.workingDir = m_gitDir;
0179     d.arguments = git.arguments();
0180     Utils::showDiff(git.readAllStandardOutput(), d, m_pluginView->mainWindow());
0181 }
0182 
0183 #include "moc_comparebranchesview.cpp"