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

0001 /***************************************************************************
0002  *   Copyright (C) 2005-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 "svnlogdlgimp.h"
0021 #include "settings/kdesvnsettings.h"
0022 #include "svnactions.h"
0023 #include "svnfrontend/fronthelpers/revisionbuttonimpl.h"
0024 #include "svnfrontend/models/logitemmodel.h"
0025 #include "svnfrontend/models/logmodelhelper.h"
0026 #include "helpers/windowgeometryhelper.h"
0027 
0028 #include <kconfig.h>
0029 
0030 #include <KHelpClient>
0031 #include <KStandardGuiItem>
0032 #include <QDialogButtonBox>
0033 #include <QKeyEvent>
0034 #include <QMenu>
0035 #include <QSortFilterProxyModel>
0036 #include <QTextDocumentFragment>
0037 
0038 const QLatin1String groupName("log_dialog_size");
0039 
0040 SvnLogDlgImp::SvnLogDlgImp(SvnActions *ac, bool modal, QWidget *parent)
0041     : QDialog(parent)
0042 {
0043     setupUi(this);
0044     setModal(modal);
0045     m_pbClose->setDefault(true);
0046     m_pbClose->setShortcut(Qt::CTRL | Qt::Key_Return);
0047     KStandardGuiItem::assign(m_pbClose, KStandardGuiItem::Close);
0048     KStandardGuiItem::assign(m_pbHelp, KStandardGuiItem::Help);
0049     m_DispPrevButton->setIcon(QIcon::fromTheme(QStringLiteral("kdesvndiff")));
0050     m_DispSpecDiff->setIcon(QIcon::fromTheme(QStringLiteral("kdesvndiff")));
0051     buttonBlame->setIcon(QIcon::fromTheme(QStringLiteral("kdesvnblame")));
0052     m_SortModel = nullptr;
0053     m_CurrentModel = nullptr;
0054     m_ControlKeyDown = false;
0055 
0056     if (Kdesvnsettings::self()->log_always_list_changed_files()) {
0057         buttonListFiles->hide();
0058     } else {
0059         m_ChangedList->hide();
0060     }
0061     m_Actions = ac;
0062     KConfigGroup cs(Kdesvnsettings::self()->config(), groupName);
0063     QByteArray t1 = cs.readEntry("logsplitter", QByteArray());
0064     if (!t1.isEmpty()) {
0065         m_centralSplitter->restoreState(t1);
0066     }
0067     t1 = cs.readEntry("right_logsplitter", QByteArray());
0068     if (!t1.isEmpty()) {
0069         if (cs.readEntry("laststate", false) == m_ChangedList->isHidden()) {
0070             m_rightSplitter->restoreState(t1);
0071         }
0072     }
0073 }
0074 
0075 SvnLogDlgImp::~SvnLogDlgImp()
0076 {
0077     KConfigGroup cs(Kdesvnsettings::self()->config(), groupName);
0078     cs.writeEntry("right_logsplitter", m_rightSplitter->saveState());
0079     cs.writeEntry("logsplitter", m_centralSplitter->saveState());
0080     cs.writeEntry("laststate", m_ChangedList->isHidden());
0081     delete m_SortModel;
0082 }
0083 
0084 void SvnLogDlgImp::dispLog(const svn::LogEntriesMapPtr &log, const QString &what, const QString &root, const svn::Revision &peg, const QString &pegUrl)
0085 {
0086     m_peg = peg;
0087     m_PegUrl = pegUrl;
0088     m_startRevButton->setNoWorking(m_PegUrl.isUrl());
0089     m_endRevButton->setNoWorking(m_PegUrl.isUrl());
0090     if (!m_PegUrl.isUrl() || Kdesvnsettings::remote_special_properties()) {
0091         QString s = m_Actions->searchProperty(_bugurl, QStringLiteral("bugtraq:url"), pegUrl, peg, true);
0092         if (!s.isEmpty()) {
0093             QString reg;
0094             s = m_Actions->searchProperty(reg, QStringLiteral("bugtraq:logregex"), pegUrl, peg, true);
0095             if (!s.isNull() && !reg.isEmpty()) {
0096                 const QVector<QStringRef> s1 = reg.splitRef(QLatin1Char('\n'));
0097                 if (!s1.isEmpty()) {
0098                     _r1.setPattern(s1.at(0).toString());
0099                     if (s1.size() > 1) {
0100                         _r2.setPattern(s1.at(1).toString());
0101                     }
0102                 }
0103             }
0104         }
0105     }
0106     _base = root;
0107     m_Entries = log;
0108     if (!what.isEmpty()) {
0109         setWindowTitle(i18nc("@title:window", "SVN Log of %1", what));
0110     } else {
0111         setWindowTitle(i18nc("@title:window", "SVN Log"));
0112     }
0113     _name = what;
0114     if (!_name.startsWith(QLatin1Char('/'))) {
0115         _name = QLatin1Char('/') + _name;
0116     }
0117     dispLog(log);
0118 }
0119 
0120 void SvnLogDlgImp::dispLog(const svn::LogEntriesMapPtr &_log)
0121 {
0122     if (!_log) {
0123         return;
0124     }
0125     bool must_init = false;
0126     if (!m_SortModel) {
0127         m_SortModel = new SvnLogSortModel(m_LogTreeView);
0128         m_CurrentModel = new SvnLogModel(_log, _name, m_SortModel);
0129         m_SortModel->setSourceModel(m_CurrentModel);
0130         must_init = true;
0131     } else {
0132         m_CurrentModel->setLogData(_log, _name);
0133     }
0134 
0135     if (must_init) {
0136         m_LogTreeView->setModel(m_SortModel);
0137         m_LogTreeView->sortByColumn(SvnLogModel::Revision, Qt::DescendingOrder);
0138         connect(m_LogTreeView->selectionModel(), &QItemSelectionModel::selectionChanged,
0139                 this, &SvnLogDlgImp::slotSelectionChanged);
0140         m_LogTreeView->resizeColumnToContents(SvnLogModel::Revision);
0141         m_LogTreeView->resizeColumnToContents(SvnLogModel::Author);
0142         m_LogTreeView->resizeColumnToContents(SvnLogModel::Date);
0143     }
0144     m_startRevButton->setRevision(m_CurrentModel->max());
0145     m_endRevButton->setRevision(m_CurrentModel->min());
0146     QModelIndex ind = m_CurrentModel->index(m_CurrentModel->rowCount(QModelIndex()) - 1);
0147     if (ind.isValid()) {
0148         m_LogTreeView->selectionModel()->select(m_SortModel->mapFromSource(ind),
0149                                                 QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
0150     }
0151     m_LogTreeView->setFocus();
0152 }
0153 
0154 QString SvnLogDlgImp::genReplace(const QString &r1match)
0155 {
0156     static QString anf(QStringLiteral("<a href=\""));
0157     static QString mid(QStringLiteral("\">"));
0158     static QString end(QStringLiteral("</a>"));
0159     QString res;
0160     if (_r2.pattern().length() < 1) {
0161         res = _bugurl;
0162         res.replace(QStringLiteral("%BUGID%"), _r1.cap(1));
0163         res = anf + res + mid + r1match + end;
0164         return res;
0165     }
0166     int pos = 0;
0167     int count = 0;
0168     int oldpos;
0169 
0170     while (pos > -1) {
0171         oldpos = pos + count;
0172         pos = r1match.indexOf(_r2, pos + count);
0173         if (pos == -1) {
0174             break;
0175         }
0176         count = _r2.matchedLength();
0177         res += r1match.midRef(oldpos, pos - oldpos);
0178         QString sub = r1match.mid(pos, count);
0179         QString _url = _bugurl;
0180         _url.replace(QStringLiteral("%BUGID%"), sub);
0181         res += anf + _url + mid + sub + end;
0182     }
0183     res += r1match.midRef(oldpos);
0184     return res;
0185 }
0186 
0187 void SvnLogDlgImp::replaceBugids(QString &msg)
0188 {
0189     if (!_r1.isValid() || _r1.pattern().length() < 1 || _bugurl.isEmpty()) {
0190         return;
0191     }
0192     int pos = 0;
0193     int count = 0;
0194 
0195     pos = _r1.indexIn(msg, pos + count);
0196     count = _r1.matchedLength();
0197 
0198     while (pos > -1) {
0199         QString s1 = msg.mid(pos, count);
0200         QString rep = genReplace(s1);
0201         msg = msg.replace(pos, count, rep);
0202         pos = _r1.indexIn(msg, pos + rep.length());
0203         count = _r1.matchedLength();
0204     }
0205 }
0206 
0207 void SvnLogDlgImp::slotSelectionChanged(const QItemSelection &current, const QItemSelection &previous)
0208 {
0209     Q_UNUSED(previous);
0210     m_ChangedList->clear();
0211     QModelIndexList _l = current.indexes();
0212     if (_l.count() < 1) {
0213         m_DispPrevButton->setEnabled(false);
0214         buttonListFiles->setEnabled(false);
0215         buttonBlame->setEnabled(false);
0216         m_ChangedList->clear();
0217         return;
0218     }
0219     QModelIndex _index = m_SortModel->mapToSource(_l[0]);
0220     m_CurrentModel->fillChangedPaths(_index, m_ChangedList);
0221     QTextDocumentFragment _m = QTextDocumentFragment::fromPlainText(m_CurrentModel->fullMessage(_index));
0222     QString msg = _m.toHtml();
0223     replaceBugids(msg);
0224     m_LogDisplay->setHtml(msg);
0225     m_DispPrevButton->setEnabled(_index.row() > 0);
0226     buttonBlame->setEnabled(true);
0227 }
0228 
0229 
0230 /*!
0231     \fn SvnLogDlgImp::slotDispPrevious()
0232  */
0233 void SvnLogDlgImp::slotDispPrevious()
0234 {
0235     QModelIndex _index = selectedRow();
0236     if (!_index.isValid() || _index.row() == 0) {
0237         m_DispPrevButton->setEnabled(false);
0238         return;
0239     }
0240     QModelIndex _it = m_CurrentModel->index(_index.row() - 1);
0241     if (!_it.isValid()) {
0242         m_DispPrevButton->setEnabled(false);
0243         return;
0244     }
0245     const SvnLogModelNodePtr k = m_CurrentModel->indexNode(_index);
0246     const SvnLogModelNodePtr p = m_CurrentModel->indexNode(_it);
0247     if (!k || !p) {
0248         m_DispPrevButton->setEnabled(false);
0249         return;
0250     }
0251 
0252     const QString s(_base + k->realName());
0253     const QString e(_base + p->realName());
0254     emit makeDiff(e, p->revision(), s, k->revision(), this);
0255 }
0256 
0257 
0258 /*!
0259     \fn SvnLogDlgImp::saveSize()
0260  */
0261 void SvnLogDlgImp::saveSize()
0262 {
0263     WindowGeometryHelper::save(this, groupName);
0264 }
0265 
0266 void SvnLogDlgImp::slotRevisionSelected()
0267 {
0268     m_goButton->setFocus();
0269     //m_DispSpecDiff->setEnabled( m_first && m_second && m_first != m_second);
0270 }
0271 
0272 void SvnLogDlgImp::slotDispSelected()
0273 {
0274     SvnLogModelNodePtr m_first = m_CurrentModel->indexNode(m_CurrentModel->index(m_CurrentModel->leftRow()));
0275     SvnLogModelNodePtr m_second = m_CurrentModel->indexNode(m_CurrentModel->index(m_CurrentModel->rightRow()));
0276     if (m_first && m_second) {
0277         emit makeDiff(_base + m_first->realName(), m_first->revision(), _base + m_second->realName(), m_second->revision(), this);
0278     }
0279 }
0280 
0281 bool SvnLogDlgImp::getSingleLog(svn::LogEntry &t, const svn::Revision &r, const QString &what, const svn::Revision &peg, QString &root)
0282 {
0283     root = _base;
0284     const svn::LogEntriesMap::const_iterator it = m_Entries->constFind(r.revnum());
0285     if (it == m_Entries->constEnd()) {
0286         return m_Actions->getSingleLog(t, r, what, peg, root);
0287     }
0288     t = it.value();
0289     return true;
0290 }
0291 
0292 void SvnLogDlgImp::slotGetLogs()
0293 {
0294     svn::LogEntriesMapPtr lm = m_Actions->getLog(m_startRevButton->revision(),
0295                                                  m_endRevButton->revision(), m_peg,
0296                                                  _base + _name, Kdesvnsettings::self()->log_always_list_changed_files(), 0, Kdesvnsettings::last_node_follow(), this);
0297     if (lm) {
0298         dispLog(lm);
0299     }
0300 }
0301 
0302 void SvnLogDlgImp::slotPrevFifty()
0303 {
0304     svn::Revision now = m_CurrentModel->min();
0305     if (now == 1) {
0306         return;
0307     }
0308     svn::Revision begin = now.revnum() - 1;
0309     if (begin.revnum() < 1) {
0310         begin = 1;
0311     }
0312     svn::LogEntriesMapPtr lm = m_Actions->getLog(begin,
0313                                                  (begin.revnum() > 50 ? svn::Revision::START : svn::Revision::HEAD), m_peg,
0314                                                  _base + _name, Kdesvnsettings::self()->log_always_list_changed_files(), 50, Kdesvnsettings::last_node_follow(), this);
0315     if (lm) {
0316         dispLog(lm);
0317     }
0318 }
0319 
0320 void SvnLogDlgImp::slotBeginHead()
0321 {
0322     svn::LogEntriesMapPtr lm = m_Actions->getLog(svn::Revision::HEAD,
0323                                                  1, m_peg,
0324                                                  _base + _name, Kdesvnsettings::self()->log_always_list_changed_files(), 50, Kdesvnsettings::last_node_follow(), this);
0325     if (lm) {
0326         dispLog(lm);
0327     }
0328 }
0329 
0330 void SvnLogDlgImp::slotHelpRequested()
0331 {
0332     KHelpClient::invokeHelp(QLatin1String("logdisplay-dlg"), QLatin1String("kdesvn"));
0333 }
0334 
0335 
0336 void SvnLogDlgImp::slotListEntries()
0337 {
0338     QModelIndex _index = selectedRow();
0339     SvnLogModelNodePtr ptr = m_CurrentModel->indexNode(_index);
0340     if (!ptr) {
0341         buttonListFiles->setEnabled(false);
0342         return;
0343     }
0344     if (ptr->changedPaths().isEmpty()) {
0345         svn::LogEntriesMapPtr _log = m_Actions->getLog(ptr->revision(), ptr->revision(), ptr->revision(),
0346                                                        _name, true, 0, Kdesvnsettings::last_node_follow());
0347         if (!_log) {
0348             return;
0349         }
0350         if (!_log->isEmpty()) {
0351             ptr->setChangedPaths(_log->value(ptr->revision()));
0352         }
0353     }
0354     if (ptr->changedPaths().isEmpty()) {
0355         m_CurrentModel->fillChangedPaths(_index, m_ChangedList);
0356     }
0357     buttonListFiles->setEnabled(false);
0358 }
0359 
0360 void SvnLogDlgImp::keyPressEvent(QKeyEvent *e)
0361 {
0362     if (!e) {
0363         return;
0364     }
0365     if (e->text().isEmpty() && e->key() == Qt::Key_Control) {
0366         m_ControlKeyDown = true;
0367     }
0368     QDialog::keyPressEvent(e);
0369 }
0370 
0371 void SvnLogDlgImp::keyReleaseEvent(QKeyEvent *e)
0372 {
0373     if (!e) {
0374         return;
0375     }
0376     if (e->text().isEmpty() && e->key() == Qt::Key_Control) {
0377         m_ControlKeyDown = false;
0378     }
0379     QDialog::keyReleaseEvent(e);
0380 }
0381 
0382 void SvnLogDlgImp::showEvent(QShowEvent *e)
0383 {
0384     QDialog::showEvent(e);
0385     WindowGeometryHelper::restore(this, groupName);
0386 }
0387 
0388 
0389 void SvnLogDlgImp::slotBlameItem()
0390 {
0391     QModelIndex ind = selectedRow();
0392     if (!ind.isValid()) {
0393         buttonBlame->setEnabled(false);
0394         return;
0395     }
0396     qlonglong rev = m_CurrentModel->toRevision(ind);
0397     svn::Revision start(svn::Revision::START);
0398     m_Actions->makeBlame(start, rev, _base + m_CurrentModel->realName(ind), QApplication::activeModalWidget(), rev, this);
0399 }
0400 
0401 /* it works 'cause we use single selection only */
0402 QModelIndex SvnLogDlgImp::selectedRow(int column)
0403 {
0404     QModelIndexList _mi = m_LogTreeView->selectionModel()->selectedRows(column);
0405     if (_mi.count() < 1) {
0406         return QModelIndex();
0407     }
0408     return m_SortModel->mapToSource(_mi[0]);
0409 }
0410 
0411 void SvnLogDlgImp::slotCustomContextMenu(const QPoint &e)
0412 {
0413     QModelIndex ind = m_LogTreeView->indexAt(e);
0414     QModelIndex bel;
0415     if (ind.isValid()) {
0416         bel = m_LogTreeView->indexBelow(ind);
0417         ind = m_SortModel->mapToSource(ind);
0418     }
0419     int row = -1;
0420     if (ind.isValid()) {
0421         row = ind.row();
0422     } else {
0423         return;
0424     }
0425 
0426     qlonglong rev = -1;
0427     if (bel.isValid()) {
0428         bel = m_SortModel->mapToSource(bel);
0429         rev = m_CurrentModel->toRevision(bel);
0430     }
0431     QMenu popup;
0432     QAction *ac;
0433     bool unset = false;
0434     if (row != m_CurrentModel->rightRow()) {
0435         ac = popup.addAction(QIcon::fromTheme(QStringLiteral("kdesvnright")), i18n("Set version as right side of diff"));
0436         ac->setData(101);
0437     } else {
0438         unset = true;
0439     }
0440     if (row != m_CurrentModel->leftRow()) {
0441         ac = popup.addAction(QIcon::fromTheme(QStringLiteral("kdesvnleft")), i18n("Set version as left side of diff"));
0442         ac->setData(102);
0443     } else {
0444         unset = true;
0445     }
0446     if (unset) {
0447         ac = popup.addAction(i18n("Unset version for diff"));
0448         ac->setData(103);
0449     }
0450     if (rev > -1 && !m_PegUrl.isUrl()) {
0451         ac = popup.addAction(i18n("Revert this commit"));
0452         ac->setData(104);
0453     }
0454     ac = popup.exec(m_LogTreeView->viewport()->mapToGlobal(e));
0455     if (!ac) {
0456         return;
0457     }
0458     int r = ac->data().toInt();
0459     switch (r) {
0460     case 101:
0461         m_CurrentModel->setRightRow(row);
0462         break;
0463     case 102:
0464         m_CurrentModel->setLeftRow(row);
0465         break;
0466     case 103:
0467         if (row != m_CurrentModel->leftRow()) {
0468             m_CurrentModel->setLeftRow(-1);
0469         }
0470         if (row != m_CurrentModel->rightRow()) {
0471             m_CurrentModel->setRightRow(-1);
0472         }
0473         break;
0474     case 104: {
0475         svn::Revision previous(rev);
0476         svn::Revision current(m_CurrentModel->toRevision(ind));
0477         QString _path = m_PegUrl.path();
0478         m_Actions->slotMergeWcRevisions(_path, current, previous, true, true, false, false, false);
0479     }
0480     break;
0481     }
0482     m_DispSpecDiff->setEnabled(m_CurrentModel->leftRow() != -1 && m_CurrentModel->rightRow() != -1 && m_CurrentModel->leftRow() != m_CurrentModel->rightRow());
0483 }
0484 
0485 void SvnLogDlgImp::slotChangedPathContextMenu(const QPoint &e)
0486 {
0487     QTreeWidgetItem *_item = m_ChangedList->currentItem();
0488     if (!_item) {
0489         return;
0490     }
0491 
0492     LogChangePathItem *item = static_cast<LogChangePathItem *>(_item);
0493     if (item->action() == 'D') {
0494         return;
0495     }
0496     QModelIndex ind = selectedRow();
0497     if (!ind.isValid()) {
0498         return;
0499     }
0500     const qlonglong rev = m_CurrentModel->toRevision(ind);
0501     QMenu popup;
0502     const QString name = item->path();
0503     const QString source = item->revision() > -1 ? item->source() : item->path();
0504     QAction *ac;
0505     ac = popup.addAction(i18n("Annotate"));
0506     if (ac) {
0507         ac->setData(101);
0508     }
0509     if (item->action() != 'A' || item->revision() > -1) {
0510         ac = popup.addAction(i18n("Diff previous"));
0511         if (ac) {
0512             ac->setData(102);
0513         }
0514     }
0515     ac = popup.addAction(i18n("Cat this version"));
0516     if (ac) {
0517         ac->setData(103);
0518     }
0519     ac = popup.exec(m_ChangedList->viewport()->mapToGlobal(e));
0520     if (!ac) {
0521         return;
0522     }
0523     int r = ac->data().toInt();
0524     svn::Revision start(svn::Revision::START);
0525     switch (r) {
0526     case 101: {
0527         m_Actions->makeBlame(start, rev, _base + name, QApplication::activeModalWidget(), rev, this);
0528         break;
0529     }
0530     case 102: {
0531         const svn_revnum_t prev = item->revision() > 0 ? item->revision() : rev - 1;
0532         emit makeDiff(_base + source, prev, _base + name, rev, this);
0533         break;
0534     }
0535     case 103: {
0536         emit makeCat(rev, _base + source, source, rev, QApplication::activeModalWidget());
0537     }
0538     default:
0539         break;
0540     }
0541 }
0542 
0543 void SvnLogDlgImp::slotSingleDoubleClicked(QTreeWidgetItem *_item, int)
0544 {
0545     if (!_item) {
0546         return;
0547     }
0548 
0549     const LogChangePathItem *item = static_cast<LogChangePathItem *>(_item);
0550     const QModelIndex ind = selectedRow();
0551     if (!ind.isValid()) {
0552         return;
0553     }
0554     svn::Revision start(svn::Revision::START);
0555     if (item->action() != 'D') {
0556         const QString name = item->path();
0557         const qlonglong rev = m_CurrentModel->toRevision(ind);
0558         m_Actions->makeBlame(start, rev, _base + name, QApplication::activeModalWidget(), rev, this);
0559     }
0560 }