branchesdialog.cpp 6.48 KB
Newer Older
1 2 3 4 5 6
/*
    SPDX-FileCopyrightText: 2021 Waqar Ahmed <waqar.17a@gmail.com>

    SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "branchesdialog.h"
7
#include "branchesdialogmodel.h"
8
#include "git/gitutils.h"
9
#include "kateprojectpluginview.h"
10 11 12 13 14 15 16 17 18 19

#include <QCoreApplication>
#include <QKeyEvent>
#include <QLineEdit>
#include <QPainter>
#include <QSortFilterProxyModel>
#include <QStyledItemDelegate>
#include <QTextDocument>
#include <QTreeView>
#include <QVBoxLayout>
Waqar Ahmed's avatar
Waqar Ahmed committed
20
#include <QWidget>
21
#include <QtConcurrentRun>
22

23
#include <KTextEditor/MainWindow>
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
#include <KTextEditor/Message>
#include <KTextEditor/View>

#include <KLocalizedString>

#include <kfts_fuzzy_match.h>

class BranchFilterModel : public QSortFilterProxyModel
{
public:
    BranchFilterModel(QObject *parent = nullptr)
        : QSortFilterProxyModel(parent)
    {
    }

    Q_SLOT void setFilterString(const QString &string)
    {
        beginResetModel();
        m_pattern = string;
        endResetModel();
    }

protected:
    bool lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const override
    {
Waqar Ahmed's avatar
Waqar Ahmed committed
49
        if (m_pattern.isEmpty()) {
50 51 52
            const int l = sourceLeft.data(BranchesDialogModel::OriginalSorting).toInt();
            const int r = sourceRight.data(BranchesDialogModel::OriginalSorting).toInt();
            return l > r;
Waqar Ahmed's avatar
Waqar Ahmed committed
53
        }
54 55
        const int l = sourceLeft.data(BranchesDialogModel::FuzzyScore).toInt();
        const int r = sourceRight.data(BranchesDialogModel::FuzzyScore).toInt();
56 57 58 59 60 61 62 63 64 65 66
        return l < r;
    }

    bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override
    {
        if (m_pattern.isEmpty()) {
            return true;
        }

        int score = 0;
        const auto idx = sourceModel()->index(sourceRow, 0, sourceParent);
67
        const QString string = idx.data().toString();
68
        const bool res = kfts::fuzzy_match(m_pattern, string, score);
69
        sourceModel()->setData(idx, score, BranchesDialogModel::FuzzyScore);
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
        return res;
    }

private:
    QString m_pattern;
};

class StyleDelegate : public QStyledItemDelegate
{
public:
    StyleDelegate(QObject *parent = nullptr)
        : QStyledItemDelegate(parent)
    {
    }

    void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
    {
        QStyleOptionViewItem options = option;
        initStyleOption(&options, index);

90
        auto name = index.data().toString();
91

92 93 94 95
        QVector<QTextLayout::FormatRange> formats;
        QTextCharFormat fmt;
        fmt.setForeground(options.palette.link());
        fmt.setFontWeight(QFont::Bold);
96

97 98 99
        const auto itemType = (BranchesDialogModel::ItemType)index.data(BranchesDialogModel::ItemTypeRole).toInt();
        const bool branchItem = itemType == BranchesDialogModel::BranchItem;
        const int offset = branchItem ? 0 : 2;
100

101 102 103 104 105 106 107 108 109 110 111
        formats = kfts::get_fuzzy_match_formats(m_filterString, name, offset, fmt);

        if (!branchItem) {
            name = QStringLiteral("+ ") + name;
        }

        const int nameLen = name.length();
        int len = 6;
        if (branchItem) {
            const auto refType = (GitUtils::RefType)index.data(BranchesDialogModel::RefType).toInt();
            using RefType = GitUtils::RefType;
112
            if (refType == RefType::Head) {
113
                name.append(QStringLiteral(" local"));
114
            } else if (refType == RefType::Remote) {
115 116
                name.append(QStringLiteral(" remote"));
                len = 7;
117 118
            }
        }
119 120 121 122
        QTextCharFormat lf;
        lf.setFontItalic(true);
        lf.setForeground(Qt::gray);
        formats.append({nameLen, len, lf});
123 124 125 126 127 128 129 130 131 132 133 134 135 136

        painter->save();

        // paint background
        if (option.state & QStyle::State_Selected) {
            painter->fillRect(option.rect, option.palette.highlight());
        } else {
            painter->fillRect(option.rect, option.palette.base());
        }

        options.text = QString(); // clear old text
        options.widget->style()->drawControl(QStyle::CE_ItemViewItem, &options, painter, options.widget);

        // leave space for icon
137 138 139 140
        if (itemType == BranchesDialogModel::BranchItem) {
            painter->translate(25, 0);
        }
        kfts::paintItemViewText(painter, name, options, formats);
141 142 143 144 145 146 147 148 149 150 151 152 153 154

        painter->restore();
    }

public Q_SLOTS:
    void setFilterString(const QString &text)
    {
        m_filterString = text;
    }

private:
    QString m_filterString;
};

155
BranchesDialog::BranchesDialog(QWidget *window, KateProjectPluginView *pluginView, QString projectPath)
156
    : QuickDialog(nullptr, window)
157
    , m_projectPath(projectPath)
Waqar Ahmed's avatar
Waqar Ahmed committed
158
    , m_pluginView(pluginView)
159
{
160
    m_model = new BranchesDialogModel(this);
161 162
    m_proxyModel = new BranchFilterModel(this);
    m_proxyModel->setSourceModel(m_model);
163
    m_treeView.setModel(m_proxyModel);
164

165
    auto delegate = new StyleDelegate(this);
166

167
    connect(&m_lineEdit, &QLineEdit::textChanged, this, [this, delegate](const QString &s) {
Waqar Ahmed's avatar
Waqar Ahmed committed
168
        static_cast<BranchFilterModel *>(m_proxyModel)->setFilterString(s);
169 170
        delegate->setFilterString(s);
    });
171 172
}

Waqar Ahmed's avatar
Waqar Ahmed committed
173
void BranchesDialog::openDialog(GitUtils::RefType r)
174
{
Waqar Ahmed's avatar
Waqar Ahmed committed
175
    m_lineEdit.setPlaceholderText(i18n("Select Branch..."));
176

Waqar Ahmed's avatar
Waqar Ahmed committed
177
    QVector<GitUtils::Branch> branches = GitUtils::getAllBranchesAndTags(m_projectPath, r);
178
    m_model->refresh(branches);
179 180 181 182

    reselectFirst();
    updateViewGeometry();
    setFocus();
Waqar Ahmed's avatar
Waqar Ahmed committed
183
    exec();
184 185
}

186 187
void BranchesDialog::slotReturnPressed()
{
Waqar Ahmed's avatar
Waqar Ahmed committed
188 189
    /** We want display role here */
    const auto branch = m_proxyModel->data(m_treeView.currentIndex(), Qt::DisplayRole).toString();
190
    const auto itemType = (BranchesDialogModel::ItemType)m_proxyModel->data(m_treeView.currentIndex(), BranchesDialogModel::ItemTypeRole).toInt();
Waqar Ahmed's avatar
Waqar Ahmed committed
191
    Q_ASSERT(itemType == BranchesDialogModel::BranchItem);
192

Waqar Ahmed's avatar
Waqar Ahmed committed
193 194
    m_branch = branch;
    Q_EMIT branchSelected(branch);
195

196
    clearLineEdit();
197 198 199 200 201 202
    hide();
}

void BranchesDialog::reselectFirst()
{
    QModelIndex index = m_proxyModel->index(0, 0);
203
    m_treeView.setCurrentIndex(index);
204 205
}

Christoph Cullmann's avatar
Christoph Cullmann committed
206
void BranchesDialog::sendMessage(const QString &plainText, bool warn)
207
{
Christoph Cullmann's avatar
Christoph Cullmann committed
208 209
    // use generic output view
    QVariantMap genericMessage;
210
    genericMessage.insert(QStringLiteral("type"), warn ? QStringLiteral("Error") : QStringLiteral("Info"));
Christoph Cullmann's avatar
Christoph Cullmann committed
211
    genericMessage.insert(QStringLiteral("category"), i18n("Git"));
Christoph Cullmann's avatar
Christoph Cullmann committed
212
    genericMessage.insert(QStringLiteral("categoryIcon"), QIcon(QStringLiteral(":/icons/icons/sc-apps-git.svg")));
213
    genericMessage.insert(QStringLiteral("text"), plainText);
Christoph Cullmann's avatar
Christoph Cullmann committed
214
    Q_EMIT m_pluginView->message(genericMessage);
215
}