File indexing completed on 2024-05-12 17:16:09

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 "simple_logcb.h"
0024 #include "settings/kdesvnsettings.h"
0025 #include "svnqt/log_entry.h"
0026 #include "fronthelpers/cursorstack.h"
0027 #include "ksvnwidgets/encodingselector_impl.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     void localeChanged()
0063     {
0064         if (!codec_searched) {
0065             cc = QTextCodec::codecForName(Kdesvnsettings::locale_for_blame().toLocal8Bit());
0066             codec_searched = true;
0067         }
0068         if (cc) {
0069             m_tLine = cc->toUnicode(line().data(), line().size());
0070             m_tAuthor = cc->toUnicode(author().data(), author().size());
0071         } else {
0072             m_tLine = QString::fromUtf8(line().data(), line().size());
0073             m_tAuthor = QString::fromUtf8(author().data(), author().size());
0074         }
0075     }
0076 
0077     const QString &tAuthor()const
0078     {
0079         return m_tAuthor;
0080     }
0081     const QString &tLine()const
0082     {
0083         return m_tLine;
0084     }
0085 
0086     static void reset_codec()
0087     {
0088         codec_searched = false;
0089         cc = nullptr;
0090     }
0091 
0092 protected:
0093     QString m_tAuthor, m_tLine;
0094 
0095     static bool codec_searched;
0096     static QTextCodec *cc;
0097 };
0098 
0099 QTextCodec *LocalizedAnnotatedLine::cc = nullptr;
0100 bool LocalizedAnnotatedLine::codec_searched = false;
0101 
0102 class BlameTreeItem: public QTreeWidgetItem
0103 {
0104 public:
0105     BlameTreeItem(const svn::AnnotateLine &al, bool disp)
0106       : QTreeWidgetItem(TREE_ITEM_TYPE)
0107       , m_Content(al)
0108       , m_disp(disp)
0109     {
0110         for (int i = 0; i <= COL_LINE; ++i) {
0111             setTextAlignment(i, Qt::AlignLeft | Qt::AlignVCenter);
0112             setFont(i, QFontDatabase::systemFont(QFontDatabase::FixedFont));
0113         }
0114         display();
0115     }
0116 
0117     qlonglong lineNumber() const
0118     {
0119         return m_Content.lineNumber();
0120     }
0121     svn_revnum_t rev() const
0122     {
0123         return m_Content.revision();
0124     }
0125     void localeChanged()
0126     {
0127         m_Content.localeChanged();
0128         if (m_disp) {
0129             setText(COL_AUT, m_Content.tAuthor());
0130         }
0131         QString _line = m_Content.tLine();
0132         _line.replace(QLatin1Char('\t'), QLatin1String("    "));
0133         setText(COL_LINE, _line);
0134     }
0135 
0136 protected:
0137     LocalizedAnnotatedLine m_Content;
0138 
0139     bool m_disp;
0140     void display();
0141 };
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 class BlameDisplayData
0160 {
0161 public:
0162     BlameDisplayData()
0163         : max(-1)
0164         , min(INT_MAX - 1)
0165         , rev_count(0)
0166         , up(false)
0167         , m_cb(nullptr)
0168         , m_pbGoToLine(nullptr)
0169         , m_pbShowLog(nullptr)
0170     {}
0171 
0172     svn_revnum_t max, min;
0173     QMap<svn_revnum_t, QColor> m_shadingMap;
0174     QMap<svn_revnum_t, svn::LogEntry> m_logCache;
0175 
0176     QColor m_lastCalcColor;
0177     unsigned int rev_count;
0178     bool up;
0179     SimpleLogCb *m_cb;
0180     QString m_File;
0181 
0182     QString reposRoot;
0183     QPushButton *m_pbGoToLine;
0184     QPushButton *m_pbShowLog;
0185 };
0186 
0187 BlameDisplay::BlameDisplay(const QString &what, const svn::AnnotatedFile &blame, SimpleLogCb *cb, QWidget *parent)
0188     : KSvnDialog(QLatin1String("blame_display_dlg"), parent)
0189     , m_ui(new Ui::BlameDisplay)
0190     , m_Data(new BlameDisplayData)
0191 {
0192     m_ui->setupUi(this);
0193     m_Data->m_cb = cb;
0194 
0195     m_Data->m_pbShowLog = new QPushButton(QIcon::fromTheme(QStringLiteral("kdesvnlog")), i18n("Log message for revision"), this);
0196     connect(m_Data->m_pbShowLog, &QAbstractButton::clicked,
0197             this, &BlameDisplay::slotShowCurrentCommit);
0198     m_ui->buttonBox->addButton(m_Data->m_pbShowLog, QDialogButtonBox::ActionRole);
0199 
0200     m_Data->m_pbGoToLine = new QPushButton(i18n("Go to line"), this);
0201     connect(m_Data->m_pbGoToLine, &QAbstractButton::clicked,
0202             this, &BlameDisplay::slotGoLine);
0203     m_ui->buttonBox->addButton(m_Data->m_pbGoToLine, QDialogButtonBox::ActionRole);
0204 
0205     connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::accept);
0206 
0207     QAction *ac = new QAction(QIcon::fromTheme(QStringLiteral("kdesvnlog")), i18n("Log message for revision"), this);
0208     connect(ac, &QAction::triggered, this, &BlameDisplay::slotShowCurrentCommit);
0209     m_ui->m_BlameTree->addAction(ac);
0210 
0211     KTreeWidgetSearchLine *searchLine = m_ui->m_TreeSearch->searchLine();
0212     searchLine->addTreeWidget(m_ui->m_BlameTree);
0213 
0214     connect(m_ui->m_BlameTree, &QTreeWidget::itemDoubleClicked,
0215             this, &BlameDisplay::slotItemDoubleClicked);
0216     connect(m_ui->m_BlameTree, &QTreeWidget::currentItemChanged,
0217             this, &BlameDisplay::slotCurrentItemChanged);
0218     connect(m_ui->m_encodingSel, &EncodingSelector_impl::TextCodecChanged,
0219             this, &BlameDisplay::slotTextCodecChanged);
0220 
0221     setContent(what, blame);
0222 }
0223 
0224 BlameDisplay::~BlameDisplay()
0225 {
0226     delete m_Data;
0227     delete m_ui;
0228 }
0229 
0230 void BlameDisplay::setContent(const QString &what, const svn::AnnotatedFile &blame)
0231 {
0232     m_Data->m_File = what;
0233     m_Data->m_pbShowLog->setEnabled(false);
0234 
0235     svn::AnnotatedFile::const_iterator bit;
0236     //m_BlameList->setSorting(COL_LINENR,false);
0237     m_Data->max = -1;
0238     svn_revnum_t lastRev(-1);
0239     QColor a(160, 160, 160);
0240     int offset = 10;
0241     int r = 0; int g = 0; int b = 0;
0242     uint colinc = 0;
0243 
0244     QTime t;
0245     t.start();
0246     QList<QTreeWidgetItem *> _list;
0247     _list.reserve(blame.size());
0248     QBrush _b, _bt, _bb;
0249 
0250     bool _b_init = false, _bt_init = false;
0251 
0252     for (bit = blame.begin(); bit != blame.end(); ++bit) {
0253         bool disp = (*bit).revision() != lastRev || bit == blame.begin() ;
0254 
0255         if ((*bit).revision() > m_Data->max) {
0256             m_Data->max = (*bit).revision();
0257             ++(m_Data->rev_count);
0258         }
0259         if ((*bit).revision() < m_Data->min) {
0260             m_Data->min = (*bit).revision();
0261         }
0262         BlameTreeItem *item = new BlameTreeItem((*bit), disp);
0263         _list.append(item);
0264 
0265         if (disp) {
0266             lastRev = (*bit).revision();
0267         }
0268         if (Kdesvnsettings::self()->colored_blame()) {
0269             if (m_Data->m_shadingMap.find((*bit).revision()) == m_Data->m_shadingMap.end()) {
0270                 a.setRgb(a.red() + offset, a.green() + offset, a.blue() + offset);
0271                 m_Data->m_shadingMap[(*bit).revision()] = a;
0272                 if (a.red() > 245 || a.green() > 245 || a.blue() > 245) {
0273                     if (colinc == 0) {
0274                         ++colinc;
0275                     } else if (r >= 50 || g >= 50 || b >= 50) {
0276                         if (++colinc > 6) {
0277                             colinc = 0;
0278                             r = g = b = 0;
0279                         } else {
0280                             r = g = b = -10;
0281                         }
0282                     }
0283                     if (colinc & 0x1) {
0284                         r += 10;
0285                     }
0286                     if (colinc & 0x2) {
0287                         g += 10;
0288                     }
0289                     if (colinc & 0x4) {
0290                         b += 10;
0291                     }
0292                     a.setRgb(160 + r, 160 + g, 160 + b);
0293                 }
0294             }
0295             if (!_b_init) {
0296                 _b_init = true;
0297                 _b = item->foreground(COL_LINENR);
0298                 _b.setColor(KColorScheme(QPalette::Active, KColorScheme::Selection).foreground().color());
0299                 _bb = item->background(COL_LINENR);
0300                 _b.setStyle(Qt::SolidPattern);
0301                 _bb.setStyle(Qt::SolidPattern);
0302                 _bb.setColor(KColorScheme(QPalette::Active, KColorScheme::Selection).background().color());
0303             }
0304             item->setForeground(COL_LINENR, _b);
0305             item->setBackground(COL_LINENR, _bb);
0306 
0307             if (!_bt_init) {
0308                 _bt_init = true;
0309                 _bt = item->background(COL_REV);
0310                 _bt.setStyle(Qt::SolidPattern);
0311             }
0312             _bt.setColor(m_Data->m_shadingMap.value((*bit).revision()));
0313             item->setBackground(COL_REV, _bt);
0314             item->setBackground(COL_DATE, _bt);
0315             item->setBackground(COL_AUT, _bt);
0316             item->setBackground(COL_LINE, _bt);
0317         } else {
0318             m_Data->m_shadingMap[(*bit).revision()] = QColor();
0319         }
0320     }
0321     m_ui->m_BlameTree->addTopLevelItems(_list);
0322     qDebug("Time elapsed: %d ms", t.elapsed());
0323     m_ui->m_BlameTree->resizeColumnToContents(COL_REV);
0324     m_ui->m_BlameTree->resizeColumnToContents(COL_DATE);
0325     m_ui->m_BlameTree->resizeColumnToContents(COL_AUT);
0326     m_ui->m_BlameTree->resizeColumnToContents(COL_LINENR);
0327     m_ui->m_BlameTree->resizeColumnToContents(COL_LINE);
0328 }
0329 
0330 void BlameDisplay::slotGoLine()
0331 {
0332     bool ok = true;
0333     int line = QInputDialog::getInt(this, i18n("Show line"), i18n("Show line number"),
0334                                     1, 1, m_ui->m_BlameTree->topLevelItemCount(), 1, &ok);
0335     if (!ok) {
0336         return;
0337     }
0338     QTreeWidgetItemIterator it(m_ui->m_BlameTree);
0339     --line;
0340     while (*it) {
0341         BlameTreeItem *_it = static_cast<BlameTreeItem *>((*it));
0342         if (_it->lineNumber() == line) {
0343             m_ui->m_BlameTree->scrollToItem(*it);
0344             m_ui->m_BlameTree->setCurrentItem(*it);
0345             return;
0346         }
0347         ++it;
0348     }
0349 }
0350 
0351 void BlameDisplay::showCommit(BlameTreeItem *bti)
0352 {
0353     if (!bti) {
0354         return;
0355     }
0356     QString text;
0357     const QMap<svn_revnum_t, svn::LogEntry>::const_iterator it = m_Data->m_logCache.constFind(bti->rev());
0358     if (it != m_Data->m_logCache.constEnd()) {
0359         text = it.value().message;
0360     } else {
0361         CursorStack a(Qt::BusyCursor);
0362         svn::LogEntry t;
0363         if (m_Data->m_cb && m_Data->m_cb->getSingleLog(t, bti->rev(), m_Data->m_File, m_Data->max, m_Data->reposRoot)) {
0364             m_Data->m_logCache[bti->rev()] = t;
0365             text = t.message;
0366         }
0367     }
0368 
0369     QPointer<QDialog> dlg(new KSvnDialog(QLatin1String("simplelog_display"), this));
0370     dlg->setWindowTitle(i18nc("@title:window", "Log Message for Revision %1", bti->rev()));
0371     QVBoxLayout *vbox = new QVBoxLayout(dlg);
0372 
0373     KTextEdit *textEdit = new KTextEdit(dlg);
0374     vbox->addWidget(textEdit);
0375     textEdit->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
0376     textEdit->setReadOnly(true);
0377     textEdit->setWordWrapMode(QTextOption::NoWrap);
0378     textEdit->setPlainText(text);
0379 
0380     QDialogButtonBox *bbox = new QDialogButtonBox(dlg);
0381     bbox->setStandardButtons(QDialogButtonBox::Close);
0382     vbox->addWidget(bbox);
0383     // QDialogButtonBox::Close is a reject role
0384     connect(bbox, &QDialogButtonBox::rejected, dlg.data(), &QDialog::accept);
0385 
0386     dlg->exec();
0387     delete dlg;
0388 }
0389 
0390 void BlameDisplay::slotShowCurrentCommit()
0391 {
0392     QTreeWidgetItem *item = m_ui->m_BlameTree->currentItem();
0393     if (item == nullptr || item->type() != TREE_ITEM_TYPE) {
0394         return;
0395     }
0396     BlameTreeItem *bit = static_cast<BlameTreeItem *>(item);
0397     showCommit(bit);
0398 }
0399 
0400 void BlameDisplay::slotCurrentItemChanged(QTreeWidgetItem *item, QTreeWidgetItem *)
0401 {
0402     const bool enabled = item && item->type() == TREE_ITEM_TYPE;
0403     m_Data->m_pbShowLog->setEnabled(enabled);
0404 }
0405 
0406 void BlameDisplay::displayBlame(SimpleLogCb *_cb, const QString &item, const svn::AnnotatedFile &blame, QWidget *parent)
0407 {
0408     QPointer<BlameDisplay> dlg(new BlameDisplay(item, blame, _cb, parent ? parent : QApplication::activeModalWidget()));
0409     dlg->exec();
0410     delete dlg;
0411 }
0412 
0413 void BlameDisplay::slotItemDoubleClicked(QTreeWidgetItem *item, int)
0414 {
0415     if (item == nullptr || item->type() != TREE_ITEM_TYPE) {
0416         return;
0417     }
0418     BlameTreeItem *bit = static_cast<BlameTreeItem *>(item);
0419     showCommit(bit);
0420 }
0421 
0422 void BlameDisplay::slotTextCodecChanged(const QString &what)
0423 {
0424     if (Kdesvnsettings::locale_for_blame() != what) {
0425         Kdesvnsettings::setLocale_for_blame(what);
0426         Kdesvnsettings::self()->save();
0427         LocalizedAnnotatedLine::reset_codec();
0428 
0429         QTreeWidgetItemIterator it(m_ui->m_BlameTree);
0430 
0431         while (*it) {
0432             BlameTreeItem *_it = static_cast<BlameTreeItem *>((*it));
0433             _it->localeChanged();
0434             ++it;
0435         }
0436     }
0437 }