File indexing completed on 2024-04-28 05:41:59

0001 /***************************************************************************
0002  *   Copyright (C) 2006-2009 by Rajko Albrecht                             *
0003  *   ral@alwins-world.de                                                   *
0004  *                                                                         *
0005  *   This program is free software; you can redistribute it and/or modify  *
0006  *   it under the terms of the GNU General Public License as published by  *
0007  *   the Free Software Foundation; either version 2 of the License, or     *
0008  *   (at your option) any later version.                                   *
0009  *                                                                         *
0010  *   This program is distributed in the hope that it will be useful,       *
0011  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0012  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0013  *   GNU General Public License for more details.                          *
0014  *                                                                         *
0015  *   You should have received a copy of the GNU General Public License     *
0016  *   along with this program; if not, write to the                         *
0017  *   Free Software Foundation, Inc.,                                       *
0018  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
0019  ***************************************************************************/
0020 #include "blamedisplay.h"
0021 #include "ui_blamedisplay.h"
0022 
0023 #include "fronthelpers/cursorstack.h"
0024 #include "ksvnwidgets/encodingselector_impl.h"
0025 #include "settings/kdesvnsettings.h"
0026 #include "simple_logcb.h"
0027 #include "svnqt/log_entry.h"
0028 
0029 #include <KColorScheme>
0030 #include <KTextEdit>
0031 #include <KTreeWidgetSearchLine>
0032 
0033 #include <QAction>
0034 #include <QBrush>
0035 #include <QFontDatabase>
0036 #include <QInputDialog>
0037 #include <QMap>
0038 #include <QPushButton>
0039 #include <QTextCodec>
0040 #include <QTime>
0041 #include <QTreeWidget>
0042 #include <QTreeWidgetItem>
0043 #include <QTreeWidgetItemIterator>
0044 
0045 #define COL_LINENR 0
0046 #define COL_REV 1
0047 #define COL_DATE 2
0048 #define COL_AUT 3
0049 #define COL_LINE 4
0050 
0051 #define TREE_ITEM_TYPE QTreeWidgetItem::UserType + 1
0052 
0053 #define BORDER 4
0054 
0055 class LocalizedAnnotatedLine : public svn::AnnotateLine
0056 {
0057 public:
0058     explicit LocalizedAnnotatedLine(const svn::AnnotateLine &al)
0059         : svn::AnnotateLine(al)
0060     {
0061     }
0062 
0063     void localeChanged()
0064     {
0065         if (!codec_searched) {
0066             cc = QTextCodec::codecForName(Kdesvnsettings::locale_for_blame().toLocal8Bit());
0067             codec_searched = true;
0068         }
0069         if (cc) {
0070             m_tLine = cc->toUnicode(line().data(), line().size());
0071             m_tAuthor = cc->toUnicode(author().data(), author().size());
0072         } else {
0073             m_tLine = QString::fromUtf8(line().data(), line().size());
0074             m_tAuthor = QString::fromUtf8(author().data(), author().size());
0075         }
0076     }
0077 
0078     const QString &tAuthor() const
0079     {
0080         return m_tAuthor;
0081     }
0082     const QString &tLine() const
0083     {
0084         return m_tLine;
0085     }
0086 
0087     static void reset_codec()
0088     {
0089         codec_searched = false;
0090         cc = nullptr;
0091     }
0092 
0093 protected:
0094     QString m_tAuthor, m_tLine;
0095 
0096     static bool codec_searched;
0097     static QTextCodec *cc;
0098 };
0099 
0100 QTextCodec *LocalizedAnnotatedLine::cc = nullptr;
0101 bool LocalizedAnnotatedLine::codec_searched = false;
0102 
0103 class BlameTreeItem : public QTreeWidgetItem
0104 {
0105 public:
0106     BlameTreeItem(const svn::AnnotateLine &al, bool disp)
0107         : QTreeWidgetItem(TREE_ITEM_TYPE)
0108         , m_Content(al)
0109         , m_disp(disp)
0110     {
0111         for (int i = 0; i <= COL_LINE; ++i) {
0112             setTextAlignment(i, Qt::AlignLeft | Qt::AlignVCenter);
0113             setFont(i, QFontDatabase::systemFont(QFontDatabase::FixedFont));
0114         }
0115         display();
0116     }
0117 
0118     qlonglong lineNumber() const
0119     {
0120         return m_Content.lineNumber();
0121     }
0122     svn_revnum_t rev() const
0123     {
0124         return m_Content.revision();
0125     }
0126     void localeChanged()
0127     {
0128         m_Content.localeChanged();
0129         if (m_disp) {
0130             setText(COL_AUT, m_Content.tAuthor());
0131         }
0132         QString _line = m_Content.tLine();
0133         _line.replace(QLatin1Char('\t'), QLatin1String("    "));
0134         setText(COL_LINE, _line);
0135     }
0136 
0137 protected:
0138     LocalizedAnnotatedLine m_Content;
0139 
0140     bool m_disp;
0141     void display();
0142 };
0143 
0144 void BlameTreeItem::display()
0145 {
0146     setTextAlignment(COL_LINENR, Qt::AlignRight | Qt::AlignVCenter);
0147 
0148     if (m_disp) {
0149         setTextAlignment(COL_REV, Qt::AlignRight | Qt::AlignVCenter);
0150         setText(COL_REV, QString::number(m_Content.revision()));
0151         if (m_Content.date().isValid()) {
0152             setText(COL_DATE, m_Content.date().toString(Qt::SystemLocaleShortDate));
0153         }
0154     }
0155     setText(COL_LINENR, QString::number(m_Content.lineNumber() + 1));
0156     localeChanged();
0157 }
0158 
0159 struct BlameDisplayData {
0160     QHash<svn_revnum_t, QColor> m_shadingMap;
0161     QHash<svn_revnum_t, svn::LogEntry> m_logCache;
0162 
0163     QColor m_lastCalcColor;
0164     SimpleLogCb *m_cb = nullptr;
0165     QString m_File;
0166 
0167     QString reposRoot;
0168     QPushButton *m_pbGoToLine = nullptr;
0169     QPushButton *m_pbShowLog = nullptr;
0170     svn_revnum_t max = -1;
0171     svn_revnum_t min = std::numeric_limits<int>::max() - 1;
0172     unsigned int rev_count = 0;
0173     bool up = false;
0174 };
0175 
0176 BlameDisplay::BlameDisplay(const QString &what, const svn::AnnotatedFile &blame, SimpleLogCb *cb, QWidget *parent)
0177     : KSvnDialog(QLatin1String("blame_display_dlg"), parent)
0178     , m_ui(new Ui::BlameDisplay)
0179     , m_Data(new BlameDisplayData)
0180 {
0181     m_ui->setupUi(this);
0182     m_Data->m_cb = cb;
0183 
0184     m_Data->m_pbShowLog = new QPushButton(QIcon::fromTheme(QStringLiteral("kdesvnlog")), i18n("Log message for revision"), this);
0185     connect(m_Data->m_pbShowLog, &QAbstractButton::clicked, this, &BlameDisplay::slotShowCurrentCommit);
0186     m_ui->buttonBox->addButton(m_Data->m_pbShowLog, QDialogButtonBox::ActionRole);
0187 
0188     m_Data->m_pbGoToLine = new QPushButton(i18n("Go to line"), this);
0189     connect(m_Data->m_pbGoToLine, &QAbstractButton::clicked, this, &BlameDisplay::slotGoLine);
0190     m_ui->buttonBox->addButton(m_Data->m_pbGoToLine, QDialogButtonBox::ActionRole);
0191 
0192     connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::accept);
0193 
0194     QAction *ac = new QAction(QIcon::fromTheme(QStringLiteral("kdesvnlog")), i18n("Log message for revision"), this);
0195     connect(ac, &QAction::triggered, this, &BlameDisplay::slotShowCurrentCommit);
0196     m_ui->m_BlameTree->addAction(ac);
0197 
0198     KTreeWidgetSearchLine *searchLine = m_ui->m_TreeSearch->searchLine();
0199     searchLine->addTreeWidget(m_ui->m_BlameTree);
0200 
0201     connect(m_ui->m_BlameTree, &QTreeWidget::itemDoubleClicked, this, &BlameDisplay::slotItemDoubleClicked);
0202     connect(m_ui->m_BlameTree, &QTreeWidget::currentItemChanged, this, &BlameDisplay::slotCurrentItemChanged);
0203     connect(m_ui->m_encodingSel, &EncodingSelector_impl::TextCodecChanged, this, &BlameDisplay::slotTextCodecChanged);
0204 
0205     setContent(what, blame);
0206 }
0207 
0208 BlameDisplay::~BlameDisplay()
0209 {
0210     delete m_Data;
0211     delete m_ui;
0212 }
0213 
0214 void BlameDisplay::setContent(const QString &what, const svn::AnnotatedFile &blame)
0215 {
0216     m_Data->m_File = what;
0217     m_Data->m_pbShowLog->setEnabled(false);
0218 
0219     // m_BlameList->setSorting(COL_LINENR,false);
0220     m_Data->max = -1;
0221     svn_revnum_t lastRev(-1);
0222     QColor a(160, 160, 160);
0223     int offset = 10;
0224     int r = 0;
0225     int g = 0;
0226     int b = 0;
0227     uint colinc = 0;
0228 
0229     QTime t;
0230     t.start();
0231     QList<QTreeWidgetItem *> _list;
0232     _list.reserve(blame.size());
0233     QBrush _b, _bt, _bb;
0234 
0235     bool _b_init = false, _bt_init = false;
0236 
0237     for (auto bit = blame.begin(); bit != blame.end(); ++bit) {
0238         const svn::AnnotateLine &line = *bit;
0239         bool disp = line.revision() != lastRev || bit == blame.begin();
0240 
0241         if (line.revision() > m_Data->max) {
0242             m_Data->max = line.revision();
0243             ++(m_Data->rev_count);
0244         }
0245         if (line.revision() < m_Data->min) {
0246             m_Data->min = line.revision();
0247         }
0248         BlameTreeItem *item = new BlameTreeItem(line, disp);
0249         _list.append(item);
0250 
0251         if (disp) {
0252             lastRev = line.revision();
0253         }
0254         if (Kdesvnsettings::self()->colored_blame()) {
0255             auto it = m_Data->m_shadingMap.find(line.revision());
0256             if (it == m_Data->m_shadingMap.end()) {
0257                 a.setRgb(a.red() + offset, a.green() + offset, a.blue() + offset);
0258                 m_Data->m_shadingMap.insert(line.revision(), a);
0259                 if (a.red() > 245 || a.green() > 245 || a.blue() > 245) {
0260                     if (colinc == 0) {
0261                         ++colinc;
0262                     } else if (r >= 50 || g >= 50 || b >= 50) {
0263                         if (++colinc > 6) {
0264                             colinc = 0;
0265                             r = g = b = 0;
0266                         } else {
0267                             r = g = b = -10;
0268                         }
0269                     }
0270                     if (colinc & 0x1) {
0271                         r += 10;
0272                     }
0273                     if (colinc & 0x2) {
0274                         g += 10;
0275                     }
0276                     if (colinc & 0x4) {
0277                         b += 10;
0278                     }
0279                     a.setRgb(160 + r, 160 + g, 160 + b);
0280                 }
0281             }
0282             if (!_b_init) {
0283                 _b_init = true;
0284                 _b = item->foreground(COL_LINENR);
0285                 _b.setColor(KColorScheme(QPalette::Active, KColorScheme::Selection).foreground().color());
0286                 _bb = item->background(COL_LINENR);
0287                 _b.setStyle(Qt::SolidPattern);
0288                 _bb.setStyle(Qt::SolidPattern);
0289                 _bb.setColor(KColorScheme(QPalette::Active, KColorScheme::Selection).background().color());
0290             }
0291             item->setForeground(COL_LINENR, _b);
0292             item->setBackground(COL_LINENR, _bb);
0293 
0294             if (!_bt_init) {
0295                 _bt_init = true;
0296                 _bt = item->background(COL_REV);
0297                 _bt.setStyle(Qt::SolidPattern);
0298             }
0299             _bt.setColor(m_Data->m_shadingMap.value(line.revision()));
0300             item->setBackground(COL_REV, _bt);
0301             item->setBackground(COL_DATE, _bt);
0302             item->setBackground(COL_AUT, _bt);
0303             item->setBackground(COL_LINE, _bt);
0304         } else {
0305             m_Data->m_shadingMap[line.revision()] = QColor();
0306         }
0307     }
0308     m_ui->m_BlameTree->addTopLevelItems(_list);
0309     qDebug("Time elapsed: %d ms", t.elapsed());
0310     m_ui->m_BlameTree->resizeColumnToContents(COL_REV);
0311     m_ui->m_BlameTree->resizeColumnToContents(COL_DATE);
0312     m_ui->m_BlameTree->resizeColumnToContents(COL_AUT);
0313     m_ui->m_BlameTree->resizeColumnToContents(COL_LINENR);
0314     m_ui->m_BlameTree->resizeColumnToContents(COL_LINE);
0315 }
0316 
0317 void BlameDisplay::slotGoLine()
0318 {
0319     bool ok = true;
0320     int line = QInputDialog::getInt(this, i18n("Show line"), i18n("Show line number"), 1, 1, m_ui->m_BlameTree->topLevelItemCount(), 1, &ok);
0321     if (!ok) {
0322         return;
0323     }
0324     QTreeWidgetItemIterator it(m_ui->m_BlameTree);
0325     --line;
0326     while (*it) {
0327         BlameTreeItem *_it = static_cast<BlameTreeItem *>((*it));
0328         if (_it->lineNumber() == line) {
0329             m_ui->m_BlameTree->scrollToItem(*it);
0330             m_ui->m_BlameTree->setCurrentItem(*it);
0331             return;
0332         }
0333         ++it;
0334     }
0335 }
0336 
0337 void BlameDisplay::showCommit(BlameTreeItem *bti)
0338 {
0339     if (!bti) {
0340         return;
0341     }
0342     QString text;
0343     const auto it = m_Data->m_logCache.constFind(bti->rev());
0344     if (it != m_Data->m_logCache.constEnd()) {
0345         text = it.value().message;
0346     } else {
0347         CursorStack a(Qt::BusyCursor);
0348         svn::LogEntry t;
0349         if (m_Data->m_cb && m_Data->m_cb->getSingleLog(t, bti->rev(), m_Data->m_File, m_Data->max, m_Data->reposRoot)) {
0350             m_Data->m_logCache[bti->rev()] = t;
0351             text = t.message;
0352         }
0353     }
0354 
0355     QPointer<QDialog> dlg(new KSvnDialog(QLatin1String("simplelog_display"), this));
0356     dlg->setWindowTitle(i18nc("@title:window", "Log Message for Revision %1", bti->rev()));
0357     QVBoxLayout *vbox = new QVBoxLayout(dlg);
0358 
0359     KTextEdit *textEdit = new KTextEdit(dlg);
0360     vbox->addWidget(textEdit);
0361     textEdit->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
0362     textEdit->setReadOnly(true);
0363     textEdit->setWordWrapMode(QTextOption::NoWrap);
0364     textEdit->setPlainText(text);
0365 
0366     QDialogButtonBox *bbox = new QDialogButtonBox(dlg);
0367     bbox->setStandardButtons(QDialogButtonBox::Close);
0368     vbox->addWidget(bbox);
0369     // QDialogButtonBox::Close is a reject role
0370     connect(bbox, &QDialogButtonBox::rejected, dlg.data(), &QDialog::accept);
0371 
0372     dlg->exec();
0373     delete dlg;
0374 }
0375 
0376 void BlameDisplay::slotShowCurrentCommit()
0377 {
0378     QTreeWidgetItem *item = m_ui->m_BlameTree->currentItem();
0379     if (item == nullptr || item->type() != TREE_ITEM_TYPE) {
0380         return;
0381     }
0382     BlameTreeItem *bit = static_cast<BlameTreeItem *>(item);
0383     showCommit(bit);
0384 }
0385 
0386 void BlameDisplay::slotCurrentItemChanged(QTreeWidgetItem *item, QTreeWidgetItem *)
0387 {
0388     const bool enabled = item && item->type() == TREE_ITEM_TYPE;
0389     m_Data->m_pbShowLog->setEnabled(enabled);
0390 }
0391 
0392 void BlameDisplay::displayBlame(SimpleLogCb *_cb, const QString &item, const svn::AnnotatedFile &blame, QWidget *parent)
0393 {
0394     QPointer<BlameDisplay> dlg(new BlameDisplay(item, blame, _cb, parent ? parent : QApplication::activeModalWidget()));
0395     dlg->exec();
0396     delete dlg;
0397 }
0398 
0399 void BlameDisplay::slotItemDoubleClicked(QTreeWidgetItem *item, int)
0400 {
0401     if (item == nullptr || item->type() != TREE_ITEM_TYPE) {
0402         return;
0403     }
0404     BlameTreeItem *bit = static_cast<BlameTreeItem *>(item);
0405     showCommit(bit);
0406 }
0407 
0408 void BlameDisplay::slotTextCodecChanged(const QString &what)
0409 {
0410     if (Kdesvnsettings::locale_for_blame() != what) {
0411         Kdesvnsettings::setLocale_for_blame(what);
0412         Kdesvnsettings::self()->save();
0413         LocalizedAnnotatedLine::reset_codec();
0414 
0415         QTreeWidgetItemIterator it(m_ui->m_BlameTree);
0416 
0417         while (*it) {
0418             BlameTreeItem *_it = static_cast<BlameTreeItem *>((*it));
0419             _it->localeChanged();
0420             ++it;
0421         }
0422     }
0423 }
0424 
0425 #include "moc_blamedisplay.cpp"