File indexing completed on 2024-04-28 05:42:05

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 "helpers/windowgeometryhelper.h"
0022 #include "settings/kdesvnsettings.h"
0023 #include "svnactions.h"
0024 #include "svnfrontend/fronthelpers/revisionbuttonimpl.h"
0025 #include "svnfrontend/models/logitemmodel.h"
0026 #include "svnfrontend/models/logmodelhelper.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, this, &SvnLogDlgImp::slotSelectionChanged);
0139         m_LogTreeView->resizeColumnToContents(SvnLogModel::Revision);
0140         m_LogTreeView->resizeColumnToContents(SvnLogModel::Author);
0141         m_LogTreeView->resizeColumnToContents(SvnLogModel::Date);
0142     }
0143     m_startRevButton->setRevision(m_CurrentModel->max());
0144     m_endRevButton->setRevision(m_CurrentModel->min());
0145     QModelIndex ind = m_CurrentModel->index(m_CurrentModel->rowCount(QModelIndex()) - 1);
0146     if (ind.isValid()) {
0147         m_LogTreeView->selectionModel()->select(m_SortModel->mapFromSource(ind), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
0148     }
0149     m_LogTreeView->setFocus();
0150 }
0151 
0152 QString SvnLogDlgImp::genReplace(const QString &r1match)
0153 {
0154     static QString anf(QStringLiteral("<a href=\""));
0155     static QString mid(QStringLiteral("\">"));
0156     static QString end(QStringLiteral("</a>"));
0157     QString res;
0158     if (_r2.pattern().length() < 1) {
0159         res = _bugurl;
0160         res.replace(QStringLiteral("%BUGID%"), _r1.cap(1));
0161         res = anf + res + mid + r1match + end;
0162         return res;
0163     }
0164     int pos = 0;
0165     int count = 0;
0166     int oldpos;
0167 
0168     while (pos > -1) {
0169         oldpos = pos + count;
0170         pos = r1match.indexOf(_r2, pos + count);
0171         if (pos == -1) {
0172             break;
0173         }
0174         count = _r2.matchedLength();
0175         res += r1match.midRef(oldpos, pos - oldpos);
0176         QString sub = r1match.mid(pos, count);
0177         QString _url = _bugurl;
0178         _url.replace(QStringLiteral("%BUGID%"), sub);
0179         res += anf + _url + mid + sub + end;
0180     }
0181     res += r1match.midRef(oldpos);
0182     return res;
0183 }
0184 
0185 void SvnLogDlgImp::replaceBugids(QString &msg)
0186 {
0187     if (!_r1.isValid() || _r1.pattern().length() < 1 || _bugurl.isEmpty()) {
0188         return;
0189     }
0190     int pos = 0;
0191     int count = 0;
0192 
0193     pos = _r1.indexIn(msg, pos + count);
0194     count = _r1.matchedLength();
0195 
0196     while (pos > -1) {
0197         QString s1 = msg.mid(pos, count);
0198         QString rep = genReplace(s1);
0199         msg = msg.replace(pos, count, rep);
0200         pos = _r1.indexIn(msg, pos + rep.length());
0201         count = _r1.matchedLength();
0202     }
0203 }
0204 
0205 void SvnLogDlgImp::slotSelectionChanged(const QItemSelection &current, const QItemSelection &previous)
0206 {
0207     Q_UNUSED(previous);
0208     m_ChangedList->clear();
0209     QModelIndexList _l = current.indexes();
0210     if (_l.count() < 1) {
0211         m_DispPrevButton->setEnabled(false);
0212         buttonListFiles->setEnabled(false);
0213         buttonBlame->setEnabled(false);
0214         m_ChangedList->clear();
0215         return;
0216     }
0217     QModelIndex _index = m_SortModel->mapToSource(_l[0]);
0218     m_CurrentModel->fillChangedPaths(_index, m_ChangedList);
0219     QTextDocumentFragment _m = QTextDocumentFragment::fromPlainText(m_CurrentModel->fullMessage(_index));
0220     QString msg = _m.toHtml();
0221     replaceBugids(msg);
0222     m_LogDisplay->setHtml(msg);
0223     m_DispPrevButton->setEnabled(_index.row() > 0);
0224     buttonBlame->setEnabled(true);
0225 }
0226 
0227 /*!
0228     \fn SvnLogDlgImp::slotDispPrevious()
0229  */
0230 void SvnLogDlgImp::slotDispPrevious()
0231 {
0232     QModelIndex _index = selectedRow();
0233     if (!_index.isValid() || _index.row() == 0) {
0234         m_DispPrevButton->setEnabled(false);
0235         return;
0236     }
0237     QModelIndex _it = m_CurrentModel->index(_index.row() - 1);
0238     if (!_it.isValid()) {
0239         m_DispPrevButton->setEnabled(false);
0240         return;
0241     }
0242     const SvnLogModelNodePtr k = m_CurrentModel->indexNode(_index);
0243     const SvnLogModelNodePtr p = m_CurrentModel->indexNode(_it);
0244     if (!k || !p) {
0245         m_DispPrevButton->setEnabled(false);
0246         return;
0247     }
0248 
0249     const QString s(_base + k->realName());
0250     const QString e(_base + p->realName());
0251     emit makeDiff(e, p->revision(), s, k->revision(), this);
0252 }
0253 
0254 /*!
0255     \fn SvnLogDlgImp::saveSize()
0256  */
0257 void SvnLogDlgImp::saveSize()
0258 {
0259     WindowGeometryHelper::save(this, groupName);
0260 }
0261 
0262 void SvnLogDlgImp::slotRevisionSelected()
0263 {
0264     m_goButton->setFocus();
0265     // m_DispSpecDiff->setEnabled( m_first && m_second && m_first != m_second);
0266 }
0267 
0268 void SvnLogDlgImp::slotDispSelected()
0269 {
0270     SvnLogModelNodePtr m_first = m_CurrentModel->indexNode(m_CurrentModel->index(m_CurrentModel->leftRow()));
0271     SvnLogModelNodePtr m_second = m_CurrentModel->indexNode(m_CurrentModel->index(m_CurrentModel->rightRow()));
0272     if (m_first && m_second) {
0273         emit makeDiff(_base + m_first->realName(), m_first->revision(), _base + m_second->realName(), m_second->revision(), this);
0274     }
0275 }
0276 
0277 bool SvnLogDlgImp::getSingleLog(svn::LogEntry &t, const svn::Revision &r, const QString &what, const svn::Revision &peg, QString &root)
0278 {
0279     root = _base;
0280     const svn::LogEntriesMap::const_iterator it = m_Entries->constFind(r.revnum());
0281     if (it == m_Entries->constEnd()) {
0282         return m_Actions->getSingleLog(t, r, what, peg, root);
0283     }
0284     t = it.value();
0285     return true;
0286 }
0287 
0288 void SvnLogDlgImp::slotGetLogs()
0289 {
0290     svn::LogEntriesMapPtr lm = m_Actions->getLog(m_startRevButton->revision(),
0291                                                  m_endRevButton->revision(),
0292                                                  m_peg,
0293                                                  _base + _name,
0294                                                  Kdesvnsettings::self()->log_always_list_changed_files(),
0295                                                  0,
0296                                                  Kdesvnsettings::last_node_follow(),
0297                                                  this);
0298     if (lm) {
0299         dispLog(lm);
0300     }
0301 }
0302 
0303 void SvnLogDlgImp::slotPrevFifty()
0304 {
0305     svn::Revision now = m_CurrentModel->min();
0306     if (now == 1) {
0307         return;
0308     }
0309     svn::Revision begin = now.revnum() - 1;
0310     if (begin.revnum() < 1) {
0311         begin = 1;
0312     }
0313     svn::LogEntriesMapPtr lm = m_Actions->getLog(begin,
0314                                                  (begin.revnum() > 50 ? svn::Revision::START : svn::Revision::HEAD),
0315                                                  m_peg,
0316                                                  _base + _name,
0317                                                  Kdesvnsettings::self()->log_always_list_changed_files(),
0318                                                  50,
0319                                                  Kdesvnsettings::last_node_follow(),
0320                                                  this);
0321     if (lm) {
0322         dispLog(lm);
0323     }
0324 }
0325 
0326 void SvnLogDlgImp::slotBeginHead()
0327 {
0328     svn::LogEntriesMapPtr lm = m_Actions->getLog(svn::Revision::HEAD,
0329                                                  1,
0330                                                  m_peg,
0331                                                  _base + _name,
0332                                                  Kdesvnsettings::self()->log_always_list_changed_files(),
0333                                                  50,
0334                                                  Kdesvnsettings::last_node_follow(),
0335                                                  this);
0336     if (lm) {
0337         dispLog(lm);
0338     }
0339 }
0340 
0341 void SvnLogDlgImp::slotHelpRequested()
0342 {
0343     KHelpClient::invokeHelp(QLatin1String("logdisplay-dlg"), QLatin1String("kdesvn"));
0344 }
0345 
0346 void SvnLogDlgImp::slotListEntries()
0347 {
0348     QModelIndex _index = selectedRow();
0349     SvnLogModelNodePtr ptr = m_CurrentModel->indexNode(_index);
0350     if (!ptr) {
0351         buttonListFiles->setEnabled(false);
0352         return;
0353     }
0354     if (ptr->changedPaths().isEmpty()) {
0355         svn::LogEntriesMapPtr _log = m_Actions->getLog(ptr->revision(), ptr->revision(), ptr->revision(), _name, true, 0, Kdesvnsettings::last_node_follow());
0356         if (!_log) {
0357             return;
0358         }
0359         if (!_log->isEmpty()) {
0360             ptr->setChangedPaths(_log->value(ptr->revision()));
0361         }
0362     }
0363     if (ptr->changedPaths().isEmpty()) {
0364         m_CurrentModel->fillChangedPaths(_index, m_ChangedList);
0365     }
0366     buttonListFiles->setEnabled(false);
0367 }
0368 
0369 void SvnLogDlgImp::keyPressEvent(QKeyEvent *e)
0370 {
0371     if (!e) {
0372         return;
0373     }
0374     if (e->text().isEmpty() && e->key() == Qt::Key_Control) {
0375         m_ControlKeyDown = true;
0376     }
0377     QDialog::keyPressEvent(e);
0378 }
0379 
0380 void SvnLogDlgImp::keyReleaseEvent(QKeyEvent *e)
0381 {
0382     if (!e) {
0383         return;
0384     }
0385     if (e->text().isEmpty() && e->key() == Qt::Key_Control) {
0386         m_ControlKeyDown = false;
0387     }
0388     QDialog::keyReleaseEvent(e);
0389 }
0390 
0391 void SvnLogDlgImp::showEvent(QShowEvent *e)
0392 {
0393     QDialog::showEvent(e);
0394     WindowGeometryHelper::restore(this, groupName);
0395 }
0396 
0397 void SvnLogDlgImp::slotBlameItem()
0398 {
0399     QModelIndex ind = selectedRow();
0400     if (!ind.isValid()) {
0401         buttonBlame->setEnabled(false);
0402         return;
0403     }
0404     qlonglong rev = m_CurrentModel->toRevision(ind);
0405     svn::Revision start(svn::Revision::START);
0406     m_Actions->makeBlame(start, rev, _base + m_CurrentModel->realName(ind), QApplication::activeModalWidget(), rev, this);
0407 }
0408 
0409 /* it works 'cause we use single selection only */
0410 QModelIndex SvnLogDlgImp::selectedRow(int column)
0411 {
0412     QModelIndexList _mi = m_LogTreeView->selectionModel()->selectedRows(column);
0413     if (_mi.count() < 1) {
0414         return QModelIndex();
0415     }
0416     return m_SortModel->mapToSource(_mi[0]);
0417 }
0418 
0419 void SvnLogDlgImp::slotCustomContextMenu(const QPoint &e)
0420 {
0421     QModelIndex ind = m_LogTreeView->indexAt(e);
0422     QModelIndex bel;
0423     if (ind.isValid()) {
0424         bel = m_LogTreeView->indexBelow(ind);
0425         ind = m_SortModel->mapToSource(ind);
0426     }
0427     int row = -1;
0428     if (ind.isValid()) {
0429         row = ind.row();
0430     } else {
0431         return;
0432     }
0433 
0434     qlonglong rev = -1;
0435     if (bel.isValid()) {
0436         bel = m_SortModel->mapToSource(bel);
0437         rev = m_CurrentModel->toRevision(bel);
0438     }
0439     QMenu popup;
0440     QAction *ac;
0441     bool unset = false;
0442     if (row != m_CurrentModel->rightRow()) {
0443         ac = popup.addAction(QIcon::fromTheme(QStringLiteral("kdesvnright")), i18n("Set version as right side of diff"));
0444         ac->setData(101);
0445     } else {
0446         unset = true;
0447     }
0448     if (row != m_CurrentModel->leftRow()) {
0449         ac = popup.addAction(QIcon::fromTheme(QStringLiteral("kdesvnleft")), i18n("Set version as left side of diff"));
0450         ac->setData(102);
0451     } else {
0452         unset = true;
0453     }
0454     if (unset) {
0455         ac = popup.addAction(i18n("Unset version for diff"));
0456         ac->setData(103);
0457     }
0458     if (rev > -1 && !m_PegUrl.isUrl()) {
0459         ac = popup.addAction(i18n("Revert this commit"));
0460         ac->setData(104);
0461     }
0462     ac = popup.exec(m_LogTreeView->viewport()->mapToGlobal(e));
0463     if (!ac) {
0464         return;
0465     }
0466     int r = ac->data().toInt();
0467     switch (r) {
0468     case 101:
0469         m_CurrentModel->setRightRow(row);
0470         break;
0471     case 102:
0472         m_CurrentModel->setLeftRow(row);
0473         break;
0474     case 103:
0475         if (row != m_CurrentModel->leftRow()) {
0476             m_CurrentModel->setLeftRow(-1);
0477         }
0478         if (row != m_CurrentModel->rightRow()) {
0479             m_CurrentModel->setRightRow(-1);
0480         }
0481         break;
0482     case 104: {
0483         svn::Revision previous(rev);
0484         svn::Revision current(m_CurrentModel->toRevision(ind));
0485         QString _path = m_PegUrl.path();
0486         m_Actions->slotMergeWcRevisions(_path, current, previous, true, true, false, false, false);
0487     } break;
0488     }
0489     m_DispSpecDiff->setEnabled(m_CurrentModel->leftRow() != -1 && m_CurrentModel->rightRow() != -1 && m_CurrentModel->leftRow() != m_CurrentModel->rightRow());
0490 }
0491 
0492 void SvnLogDlgImp::slotChangedPathContextMenu(const QPoint &e)
0493 {
0494     QTreeWidgetItem *_item = m_ChangedList->currentItem();
0495     if (!_item) {
0496         return;
0497     }
0498 
0499     LogChangePathItem *item = static_cast<LogChangePathItem *>(_item);
0500     if (item->action() == 'D') {
0501         return;
0502     }
0503     QModelIndex ind = selectedRow();
0504     if (!ind.isValid()) {
0505         return;
0506     }
0507     const qlonglong rev = m_CurrentModel->toRevision(ind);
0508     QMenu popup;
0509     const QString name = item->path();
0510     const QString source = item->revision() > -1 ? item->source() : item->path();
0511     QAction *ac;
0512     ac = popup.addAction(i18n("Annotate"));
0513     if (ac) {
0514         ac->setData(101);
0515     }
0516     if (item->action() != 'A' || item->revision() > -1) {
0517         ac = popup.addAction(i18n("Diff previous"));
0518         if (ac) {
0519             ac->setData(102);
0520         }
0521     }
0522     ac = popup.addAction(i18n("Cat this version"));
0523     if (ac) {
0524         ac->setData(103);
0525     }
0526     ac = popup.exec(m_ChangedList->viewport()->mapToGlobal(e));
0527     if (!ac) {
0528         return;
0529     }
0530     int r = ac->data().toInt();
0531     svn::Revision start(svn::Revision::START);
0532     switch (r) {
0533     case 101: {
0534         m_Actions->makeBlame(start, rev, _base + name, QApplication::activeModalWidget(), rev, this);
0535         break;
0536     }
0537     case 102: {
0538         const svn_revnum_t prev = item->revision() > 0 ? item->revision() : rev - 1;
0539         emit makeDiff(_base + source, prev, _base + name, rev, this);
0540         break;
0541     }
0542     case 103: {
0543         emit makeCat(rev, _base + source, source, rev, QApplication::activeModalWidget());
0544     }
0545     default:
0546         break;
0547     }
0548 }
0549 
0550 void SvnLogDlgImp::slotSingleDoubleClicked(QTreeWidgetItem *_item, int)
0551 {
0552     if (!_item) {
0553         return;
0554     }
0555 
0556     const LogChangePathItem *item = static_cast<LogChangePathItem *>(_item);
0557     const QModelIndex ind = selectedRow();
0558     if (!ind.isValid()) {
0559         return;
0560     }
0561     svn::Revision start(svn::Revision::START);
0562     if (item->action() != 'D') {
0563         const QString name = item->path();
0564         const qlonglong rev = m_CurrentModel->toRevision(ind);
0565         m_Actions->makeBlame(start, rev, _base + name, QApplication::activeModalWidget(), rev, this);
0566     }
0567 }
0568 
0569 #include "moc_svnlogdlgimp.cpp"