logchangedialog.cpp 10.1 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
2
**
3
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
hjk's avatar
hjk committed
4
** Contact: http://www.qt-project.org/legal
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
7
**
hjk's avatar
hjk committed
8 9 10 11 12 13 14
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
15 16
**
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17 18 19 20 21 22 23 24 25
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
26 27
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
28
****************************************************************************/
29

30
#include "logchangedialog.h"
31 32 33
#include "gitplugin.h"
#include "gitclient.h"

34 35
#include <vcsbase/vcsbaseoutputwindow.h>

36 37
#include <utils/qtcassert.h>

38 39 40 41
#include <QTreeView>
#include <QLabel>
#include <QPushButton>
#include <QStandardItemModel>
Petar Perisin's avatar
Petar Perisin committed
42
#include <QDialogButtonBox>
43 44
#include <QItemSelectionModel>
#include <QVBoxLayout>
Petar Perisin's avatar
Petar Perisin committed
45
#include <QComboBox>
46
#include <QPainter>
47 48 49 50 51 52 53 54 55 56 57

namespace Git {
namespace Internal {

enum Columns
{
    Sha1Column,
    SubjectColumn,
    ColumnCount
};

58 59
LogChangeWidget::LogChangeWidget(QWidget *parent)
    : QTreeView(parent)
60
    , m_model(new QStandardItemModel(0, ColumnCount, this))
61
    , m_hasCustomDelegate(false)
62 63 64 65
{
    QStringList headers;
    headers << tr("Sha1")<< tr("Subject");
    m_model->setHorizontalHeaderLabels(headers);
66 67 68 69 70
    setModel(m_model);
    setMinimumWidth(300);
    setUniformRowHeights(true);
    setRootIsDecorated(false);
    setSelectionBehavior(QAbstractItemView::SelectRows);
71
    connect(this, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(emitDoubleClicked(QModelIndex)));
72 73
}

Orgad Shaneh's avatar
Orgad Shaneh committed
74
bool LogChangeWidget::init(const QString &repository, const QString &commit, bool includeRemote)
75
{
76 77 78 79
    if (!populateLog(repository, commit, includeRemote))
        return false;
    if (!m_model->rowCount()) {
        VcsBase::VcsBaseOutputWindow::instance()->appendError(
80
                    GitPlugin::instance()->gitClient()->msgNoCommits(includeRemote));
81
        return false;
82
    }
83
    return true;
84 85
}

86
QString LogChangeWidget::commit() const
87 88
{
    if (const QStandardItem *sha1Item = currentItem(Sha1Column))
89
        return sha1Item->text();
90 91 92
    return QString();
}

93
int LogChangeWidget::commitIndex() const
94
{
95
    const QModelIndex currentIndex = selectionModel()->currentIndex();
96 97 98 99 100
    if (currentIndex.isValid())
        return currentIndex.row();
    return -1;
}

101 102 103 104 105 106 107 108 109 110
QString LogChangeWidget::earliestCommit() const
{
    int rows = m_model->rowCount();
    if (rows) {
        if (const QStandardItem *item = m_model->item(rows - 1, Sha1Column))
            return item->text();
    }
    return QString();
}

111 112 113 114 115 116
void LogChangeWidget::setItemDelegate(QAbstractItemDelegate *delegate)
{
    QTreeView::setItemDelegate(delegate);
    m_hasCustomDelegate = true;
}

117 118 119 120 121 122 123 124 125
void LogChangeWidget::emitDoubleClicked(const QModelIndex &index)
{
    if (index.isValid()) {
        QString commit = index.sibling(index.row(), Sha1Column).data().toString();
        if (!commit.isEmpty())
            emit doubleClicked(commit);
    }
}

126 127 128 129 130 131 132
void LogChangeWidget::selectionChanged(const QItemSelection &selected,
                                       const QItemSelection &deselected)
{
    QTreeView::selectionChanged(selected, deselected);
    if (!m_hasCustomDelegate)
        return;
    const QModelIndexList previousIndexes = deselected.indexes();
Orgad Shaneh's avatar
Orgad Shaneh committed
133 134
    if (previousIndexes.isEmpty())
        return;
135 136 137 138 139 140 141 142 143 144 145
    const QModelIndex current = currentIndex();
    int row = current.row();
    int previousRow = previousIndexes.first().row();
    if (row < previousRow)
        qSwap(row, previousRow);
    for (int r = previousRow; r <= row; ++r) {
        update(current.sibling(r, 0));
        update(current.sibling(r, 1));
    }
}

Orgad Shaneh's avatar
Orgad Shaneh committed
146
bool LogChangeWidget::populateLog(const QString &repository, const QString &commit, bool includeRemote)
147
{
148 149
    const QString currentCommit = this->commit();
    int selected = currentCommit.isEmpty() ? 0 : -1;
150 151 152
    if (const int rowCount = m_model->rowCount())
        m_model->removeRows(0, rowCount);

153
    // Retrieve log using a custom format "Sha1:Subject [(refs)]"
154 155
    GitClient *client = GitPlugin::instance()->gitClient();
    QStringList arguments;
156
    arguments << QLatin1String("--max-count=40") << QLatin1String("--format=%h:%s %d");
Orgad Shaneh's avatar
Orgad Shaneh committed
157 158 159
    arguments << (commit.isEmpty() ? QLatin1String("HEAD") : commit);
    if (!includeRemote)
        arguments << QLatin1String("--not") << QLatin1String("--remotes");
160 161 162 163 164 165 166 167 168 169
    QString output;
    if (!client->synchronousLog(repository, arguments, &output))
        return false;
    foreach (const QString &line, output.split(QLatin1Char('\n'))) {
        const int colonPos = line.indexOf(QLatin1Char(':'));
        if (colonPos != -1) {
            QList<QStandardItem *> row;
            for (int c = 0; c < ColumnCount; ++c) {
                QStandardItem *item = new QStandardItem;
                item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
170 171
                if (line.endsWith(QLatin1Char(')'))) {
                    QFont font = item->font();
172
                    font.setBold(true);
173 174
                    item->setFont(font);
                }
175 176
                row.push_back(item);
            }
177 178
            const QString sha1 = line.left(colonPos);
            row[Sha1Column]->setText(sha1);
179 180
            row[SubjectColumn]->setText(line.right(line.size() - colonPos - 1));
            m_model->appendRow(row);
181 182
            if (selected == -1 && currentCommit == sha1)
                selected = m_model->rowCount() - 1;
183 184
        }
    }
185
    setCurrentIndex(m_model->index(selected, 0));
186 187 188
    return true;
}

189
const QStandardItem *LogChangeWidget::currentItem(int column) const
190
{
191
    const QModelIndex currentIndex = selectionModel()->currentIndex();
192 193 194 195 196
    if (currentIndex.isValid())
        return m_model->item(currentIndex.row(), column);
    return 0;
}

197 198
LogChangeDialog::LogChangeDialog(bool isReset, QWidget *parent) :
    QDialog(parent)
199
    , m_widget(new LogChangeWidget)
200 201 202 203 204
    , m_dialogButtonBox(new QDialogButtonBox(this))
    , m_resetTypeComboBox(0)
{
    QVBoxLayout *layout = new QVBoxLayout(this);
    layout->addWidget(new QLabel(isReset ? tr("Reset to:") : tr("Select change:"), this));
205
    layout->addWidget(m_widget);
206 207 208 209 210
    QHBoxLayout *popUpLayout = new QHBoxLayout;
    if (isReset) {
        popUpLayout->addWidget(new QLabel(tr("Reset type:"), this));
        m_resetTypeComboBox = new QComboBox(this);
        m_resetTypeComboBox->addItem(tr("Hard"), QLatin1String("--hard"));
Orgad Shaneh's avatar
Orgad Shaneh committed
211
        m_resetTypeComboBox->addItem(tr("Mixed"), QLatin1String("--mixed"));
212
        m_resetTypeComboBox->addItem(tr("Soft"), QLatin1String("--soft"));
Orgad Shaneh's avatar
Orgad Shaneh committed
213 214 215
        GitClient *client = GitPlugin::instance()->gitClient();
        m_resetTypeComboBox->setCurrentIndex(client->settings()->intValue(
                                                 GitSettings::lastResetIndexKey));
216 217 218 219 220 221 222 223 224 225 226 227
        popUpLayout->addWidget(m_resetTypeComboBox);
        popUpLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Ignored));
    }

    popUpLayout->addWidget(m_dialogButtonBox);
    m_dialogButtonBox->addButton(QDialogButtonBox::Cancel);
    QPushButton *okButton = m_dialogButtonBox->addButton(QDialogButtonBox::Ok);
    layout->addLayout(popUpLayout);

    connect(m_dialogButtonBox, SIGNAL(accepted()), this, SLOT(accept()));
    connect(m_dialogButtonBox, SIGNAL(rejected()), this, SLOT(reject()));

228
    connect(m_widget, SIGNAL(doubleClicked(QModelIndex)), okButton, SLOT(animateClick()));
229 230 231 232 233

    setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
    resize(600, 400);
}

Orgad Shaneh's avatar
Orgad Shaneh committed
234
bool LogChangeDialog::runDialog(const QString &repository, const QString &commit, bool includeRemote)
235
{
236
    if (!m_widget->init(repository, commit, includeRemote))
237
        return false;
238

Orgad Shaneh's avatar
Orgad Shaneh committed
239 240 241 242 243 244 245 246 247
    if (QDialog::exec() == QDialog::Accepted) {
        if (m_resetTypeComboBox) {
            GitClient *client = GitPlugin::instance()->gitClient();
            client->settings()->setValue(GitSettings::lastResetIndexKey,
                                         m_resetTypeComboBox->currentIndex());
        }
        return true;
    }
    return false;
248 249 250 251
}

QString LogChangeDialog::commit() const
{
252
    return m_widget->commit();
253 254 255 256
}

int LogChangeDialog::commitIndex() const
{
257
    return m_widget->commitIndex();
258 259 260 261 262 263 264 265 266
}

QString LogChangeDialog::resetFlag() const
{
    if (!m_resetTypeComboBox)
        return QString();
    return m_resetTypeComboBox->itemData(m_resetTypeComboBox->currentIndex()).toString();
}

267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301
LogChangeWidget *LogChangeDialog::widget() const
{
    return m_widget;
}

LogItemDelegate::LogItemDelegate(LogChangeWidget *widget) : m_widget(widget)
{
    m_widget->setItemDelegate(this);
}

int LogItemDelegate::currentRow() const
{
    return m_widget->commitIndex();
}

IconItemDelegate::IconItemDelegate(LogChangeWidget *widget, const QString &icon)
    : LogItemDelegate(widget)
    , m_icon(icon)
{
}

void IconItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
                             const QModelIndex &index) const
{
    QStyleOptionViewItem o = option;
    if (index.column() == 0 && hasIcon(index.row())) {
        const QSize size = option.decorationSize;
        painter->save();
        painter->drawPixmap(o.rect.x(), o.rect.y(), m_icon.pixmap(size.width(), size.height()));
        painter->restore();
        o.rect.translate(size.width(), 0);
    }
    QStyledItemDelegate::paint(painter, o, index);
}

302 303
} // namespace Internal
} // namespace Git