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 }