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"