1 /***************************************************************************
2 * Copyright (C) 2005-2009 by Rajko Albrecht *
3 * ral@alwins-world.de *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
20 #include "svnlogdlgimp.h"
21 #include "settings/kdesvnsettings.h"
22 #include "svnactions.h"
23 #include "svnfrontend/fronthelpers/revisionbuttonimpl.h"
24 #include "svnfrontend/models/logitemmodel.h"
25 #include "svnfrontend/models/logmodelhelper.h"
26 #include "helpers/windowgeometryhelper.h"
27
28 #include <kconfig.h>
29
30 #include <KHelpClient>
31 #include <KStandardGuiItem>
32 #include <QDialogButtonBox>
33 #include <QKeyEvent>
34 #include <QMenu>
35 #include <QSortFilterProxyModel>
36 #include <QTextDocumentFragment>
37
38 const QLatin1String groupName("log_dialog_size");
39
SvnLogDlgImp(SvnActions * ac,bool modal,QWidget * parent)40 SvnLogDlgImp::SvnLogDlgImp(SvnActions *ac, bool modal, QWidget *parent)
41 : QDialog(parent)
42 {
43 setupUi(this);
44 setModal(modal);
45 m_pbClose->setDefault(true);
46 m_pbClose->setShortcut(Qt::CTRL | Qt::Key_Return);
47 KStandardGuiItem::assign(m_pbClose, KStandardGuiItem::Close);
48 KStandardGuiItem::assign(m_pbHelp, KStandardGuiItem::Help);
49 m_DispPrevButton->setIcon(QIcon::fromTheme(QStringLiteral("kdesvndiff")));
50 m_DispSpecDiff->setIcon(QIcon::fromTheme(QStringLiteral("kdesvndiff")));
51 buttonBlame->setIcon(QIcon::fromTheme(QStringLiteral("kdesvnblame")));
52 m_SortModel = nullptr;
53 m_CurrentModel = nullptr;
54 m_ControlKeyDown = false;
55
56 if (Kdesvnsettings::self()->log_always_list_changed_files()) {
57 buttonListFiles->hide();
58 } else {
59 m_ChangedList->hide();
60 }
61 m_Actions = ac;
62 KConfigGroup cs(Kdesvnsettings::self()->config(), groupName);
63 QByteArray t1 = cs.readEntry("logsplitter", QByteArray());
64 if (!t1.isEmpty()) {
65 m_centralSplitter->restoreState(t1);
66 }
67 t1 = cs.readEntry("right_logsplitter", QByteArray());
68 if (!t1.isEmpty()) {
69 if (cs.readEntry("laststate", false) == m_ChangedList->isHidden()) {
70 m_rightSplitter->restoreState(t1);
71 }
72 }
73 }
74
~SvnLogDlgImp()75 SvnLogDlgImp::~SvnLogDlgImp()
76 {
77 KConfigGroup cs(Kdesvnsettings::self()->config(), groupName);
78 cs.writeEntry("right_logsplitter", m_rightSplitter->saveState());
79 cs.writeEntry("logsplitter", m_centralSplitter->saveState());
80 cs.writeEntry("laststate", m_ChangedList->isHidden());
81 delete m_SortModel;
82 }
83
dispLog(const svn::LogEntriesMapPtr & log,const QString & what,const QString & root,const svn::Revision & peg,const QString & pegUrl)84 void SvnLogDlgImp::dispLog(const svn::LogEntriesMapPtr &log, const QString &what, const QString &root, const svn::Revision &peg, const QString &pegUrl)
85 {
86 m_peg = peg;
87 m_PegUrl = pegUrl;
88 m_startRevButton->setNoWorking(m_PegUrl.isUrl());
89 m_endRevButton->setNoWorking(m_PegUrl.isUrl());
90 if (!m_PegUrl.isUrl() || Kdesvnsettings::remote_special_properties()) {
91 QString s = m_Actions->searchProperty(_bugurl, QStringLiteral("bugtraq:url"), pegUrl, peg, true);
92 if (!s.isEmpty()) {
93 QString reg;
94 s = m_Actions->searchProperty(reg, QStringLiteral("bugtraq:logregex"), pegUrl, peg, true);
95 if (!s.isNull() && !reg.isEmpty()) {
96 const QVector<QStringRef> s1 = reg.splitRef(QLatin1Char('\n'));
97 if (!s1.isEmpty()) {
98 _r1.setPattern(s1.at(0).toString());
99 if (s1.size() > 1) {
100 _r2.setPattern(s1.at(1).toString());
101 }
102 }
103 }
104 }
105 }
106 _base = root;
107 m_Entries = log;
108 if (!what.isEmpty()) {
109 setWindowTitle(i18nc("@title:window", "SVN Log of %1", what));
110 } else {
111 setWindowTitle(i18nc("@title:window", "SVN Log"));
112 }
113 _name = what;
114 if (!_name.startsWith(QLatin1Char('/'))) {
115 _name = QLatin1Char('/') + _name;
116 }
117 dispLog(log);
118 }
119
dispLog(const svn::LogEntriesMapPtr & _log)120 void SvnLogDlgImp::dispLog(const svn::LogEntriesMapPtr &_log)
121 {
122 if (!_log) {
123 return;
124 }
125 bool must_init = false;
126 if (!m_SortModel) {
127 m_SortModel = new SvnLogSortModel(m_LogTreeView);
128 m_CurrentModel = new SvnLogModel(_log, _name, m_SortModel);
129 m_SortModel->setSourceModel(m_CurrentModel);
130 must_init = true;
131 } else {
132 m_CurrentModel->setLogData(_log, _name);
133 }
134
135 if (must_init) {
136 m_LogTreeView->setModel(m_SortModel);
137 m_LogTreeView->sortByColumn(SvnLogModel::Revision, Qt::DescendingOrder);
138 connect(m_LogTreeView->selectionModel(), &QItemSelectionModel::selectionChanged,
139 this, &SvnLogDlgImp::slotSelectionChanged);
140 m_LogTreeView->resizeColumnToContents(SvnLogModel::Revision);
141 m_LogTreeView->resizeColumnToContents(SvnLogModel::Author);
142 m_LogTreeView->resizeColumnToContents(SvnLogModel::Date);
143 }
144 m_startRevButton->setRevision(m_CurrentModel->max());
145 m_endRevButton->setRevision(m_CurrentModel->min());
146 QModelIndex ind = m_CurrentModel->index(m_CurrentModel->rowCount(QModelIndex()) - 1);
147 if (ind.isValid()) {
148 m_LogTreeView->selectionModel()->select(m_SortModel->mapFromSource(ind),
149 QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
150 }
151 m_LogTreeView->setFocus();
152 }
153
genReplace(const QString & r1match)154 QString SvnLogDlgImp::genReplace(const QString &r1match)
155 {
156 static QString anf(QStringLiteral("<a href=\""));
157 static QString mid(QStringLiteral("\">"));
158 static QString end(QStringLiteral("</a>"));
159 QString res;
160 if (_r2.pattern().length() < 1) {
161 res = _bugurl;
162 res.replace(QStringLiteral("%BUGID%"), _r1.cap(1));
163 res = anf + res + mid + r1match + end;
164 return res;
165 }
166 int pos = 0;
167 int count = 0;
168 int oldpos;
169
170 while (pos > -1) {
171 oldpos = pos + count;
172 pos = r1match.indexOf(_r2, pos + count);
173 if (pos == -1) {
174 break;
175 }
176 count = _r2.matchedLength();
177 res += r1match.midRef(oldpos, pos - oldpos);
178 QString sub = r1match.mid(pos, count);
179 QString _url = _bugurl;
180 _url.replace(QStringLiteral("%BUGID%"), sub);
181 res += anf + _url + mid + sub + end;
182 }
183 res += r1match.midRef(oldpos);
184 return res;
185 }
186
replaceBugids(QString & msg)187 void SvnLogDlgImp::replaceBugids(QString &msg)
188 {
189 if (!_r1.isValid() || _r1.pattern().length() < 1 || _bugurl.isEmpty()) {
190 return;
191 }
192 int pos = 0;
193 int count = 0;
194
195 pos = _r1.indexIn(msg, pos + count);
196 count = _r1.matchedLength();
197
198 while (pos > -1) {
199 QString s1 = msg.mid(pos, count);
200 QString rep = genReplace(s1);
201 msg = msg.replace(pos, count, rep);
202 pos = _r1.indexIn(msg, pos + rep.length());
203 count = _r1.matchedLength();
204 }
205 }
206
slotSelectionChanged(const QItemSelection & current,const QItemSelection & previous)207 void SvnLogDlgImp::slotSelectionChanged(const QItemSelection ¤t, const QItemSelection &previous)
208 {
209 Q_UNUSED(previous);
210 m_ChangedList->clear();
211 QModelIndexList _l = current.indexes();
212 if (_l.count() < 1) {
213 m_DispPrevButton->setEnabled(false);
214 buttonListFiles->setEnabled(false);
215 buttonBlame->setEnabled(false);
216 m_ChangedList->clear();
217 return;
218 }
219 QModelIndex _index = m_SortModel->mapToSource(_l[0]);
220 m_CurrentModel->fillChangedPaths(_index, m_ChangedList);
221 QTextDocumentFragment _m = QTextDocumentFragment::fromPlainText(m_CurrentModel->fullMessage(_index));
222 QString msg = _m.toHtml();
223 replaceBugids(msg);
224 m_LogDisplay->setHtml(msg);
225 m_DispPrevButton->setEnabled(_index.row() > 0);
226 buttonBlame->setEnabled(true);
227 }
228
229
230 /*!
231 \fn SvnLogDlgImp::slotDispPrevious()
232 */
slotDispPrevious()233 void SvnLogDlgImp::slotDispPrevious()
234 {
235 QModelIndex _index = selectedRow();
236 if (!_index.isValid() || _index.row() == 0) {
237 m_DispPrevButton->setEnabled(false);
238 return;
239 }
240 QModelIndex _it = m_CurrentModel->index(_index.row() - 1);
241 if (!_it.isValid()) {
242 m_DispPrevButton->setEnabled(false);
243 return;
244 }
245 const SvnLogModelNodePtr k = m_CurrentModel->indexNode(_index);
246 const SvnLogModelNodePtr p = m_CurrentModel->indexNode(_it);
247 if (!k || !p) {
248 m_DispPrevButton->setEnabled(false);
249 return;
250 }
251
252 const QString s(_base + k->realName());
253 const QString e(_base + p->realName());
254 emit makeDiff(e, p->revision(), s, k->revision(), this);
255 }
256
257
258 /*!
259 \fn SvnLogDlgImp::saveSize()
260 */
saveSize()261 void SvnLogDlgImp::saveSize()
262 {
263 WindowGeometryHelper::save(this, groupName);
264 }
265
slotRevisionSelected()266 void SvnLogDlgImp::slotRevisionSelected()
267 {
268 m_goButton->setFocus();
269 //m_DispSpecDiff->setEnabled( m_first && m_second && m_first != m_second);
270 }
271
slotDispSelected()272 void SvnLogDlgImp::slotDispSelected()
273 {
274 SvnLogModelNodePtr m_first = m_CurrentModel->indexNode(m_CurrentModel->index(m_CurrentModel->leftRow()));
275 SvnLogModelNodePtr m_second = m_CurrentModel->indexNode(m_CurrentModel->index(m_CurrentModel->rightRow()));
276 if (m_first && m_second) {
277 emit makeDiff(_base + m_first->realName(), m_first->revision(), _base + m_second->realName(), m_second->revision(), this);
278 }
279 }
280
getSingleLog(svn::LogEntry & t,const svn::Revision & r,const QString & what,const svn::Revision & peg,QString & root)281 bool SvnLogDlgImp::getSingleLog(svn::LogEntry &t, const svn::Revision &r, const QString &what, const svn::Revision &peg, QString &root)
282 {
283 root = _base;
284 const svn::LogEntriesMap::const_iterator it = m_Entries->constFind(r.revnum());
285 if (it == m_Entries->constEnd()) {
286 return m_Actions->getSingleLog(t, r, what, peg, root);
287 }
288 t = it.value();
289 return true;
290 }
291
slotGetLogs()292 void SvnLogDlgImp::slotGetLogs()
293 {
294 svn::LogEntriesMapPtr lm = m_Actions->getLog(m_startRevButton->revision(),
295 m_endRevButton->revision(), m_peg,
296 _base + _name, Kdesvnsettings::self()->log_always_list_changed_files(), 0, Kdesvnsettings::last_node_follow(), this);
297 if (lm) {
298 dispLog(lm);
299 }
300 }
301
slotPrevFifty()302 void SvnLogDlgImp::slotPrevFifty()
303 {
304 svn::Revision now = m_CurrentModel->min();
305 if (now == 1) {
306 return;
307 }
308 svn::Revision begin = now.revnum() - 1;
309 if (begin.revnum() < 1) {
310 begin = 1;
311 }
312 svn::LogEntriesMapPtr lm = m_Actions->getLog(begin,
313 (begin.revnum() > 50 ? svn::Revision::START : svn::Revision::HEAD), m_peg,
314 _base + _name, Kdesvnsettings::self()->log_always_list_changed_files(), 50, Kdesvnsettings::last_node_follow(), this);
315 if (lm) {
316 dispLog(lm);
317 }
318 }
319
slotBeginHead()320 void SvnLogDlgImp::slotBeginHead()
321 {
322 svn::LogEntriesMapPtr lm = m_Actions->getLog(svn::Revision::HEAD,
323 1, m_peg,
324 _base + _name, Kdesvnsettings::self()->log_always_list_changed_files(), 50, Kdesvnsettings::last_node_follow(), this);
325 if (lm) {
326 dispLog(lm);
327 }
328 }
329
slotHelpRequested()330 void SvnLogDlgImp::slotHelpRequested()
331 {
332 KHelpClient::invokeHelp(QLatin1String("logdisplay-dlg"), QLatin1String("kdesvn"));
333 }
334
335
slotListEntries()336 void SvnLogDlgImp::slotListEntries()
337 {
338 QModelIndex _index = selectedRow();
339 SvnLogModelNodePtr ptr = m_CurrentModel->indexNode(_index);
340 if (!ptr) {
341 buttonListFiles->setEnabled(false);
342 return;
343 }
344 if (ptr->changedPaths().isEmpty()) {
345 svn::LogEntriesMapPtr _log = m_Actions->getLog(ptr->revision(), ptr->revision(), ptr->revision(),
346 _name, true, 0, Kdesvnsettings::last_node_follow());
347 if (!_log) {
348 return;
349 }
350 if (!_log->isEmpty()) {
351 ptr->setChangedPaths(_log->value(ptr->revision()));
352 }
353 }
354 if (ptr->changedPaths().isEmpty()) {
355 m_CurrentModel->fillChangedPaths(_index, m_ChangedList);
356 }
357 buttonListFiles->setEnabled(false);
358 }
359
keyPressEvent(QKeyEvent * e)360 void SvnLogDlgImp::keyPressEvent(QKeyEvent *e)
361 {
362 if (!e) {
363 return;
364 }
365 if (e->text().isEmpty() && e->key() == Qt::Key_Control) {
366 m_ControlKeyDown = true;
367 }
368 QDialog::keyPressEvent(e);
369 }
370
keyReleaseEvent(QKeyEvent * e)371 void SvnLogDlgImp::keyReleaseEvent(QKeyEvent *e)
372 {
373 if (!e) {
374 return;
375 }
376 if (e->text().isEmpty() && e->key() == Qt::Key_Control) {
377 m_ControlKeyDown = false;
378 }
379 QDialog::keyReleaseEvent(e);
380 }
381
showEvent(QShowEvent * e)382 void SvnLogDlgImp::showEvent(QShowEvent *e)
383 {
384 QDialog::showEvent(e);
385 WindowGeometryHelper::restore(this, groupName);
386 }
387
388
slotBlameItem()389 void SvnLogDlgImp::slotBlameItem()
390 {
391 QModelIndex ind = selectedRow();
392 if (!ind.isValid()) {
393 buttonBlame->setEnabled(false);
394 return;
395 }
396 qlonglong rev = m_CurrentModel->toRevision(ind);
397 svn::Revision start(svn::Revision::START);
398 m_Actions->makeBlame(start, rev, _base + m_CurrentModel->realName(ind), QApplication::activeModalWidget(), rev, this);
399 }
400
401 /* it works 'cause we use single selection only */
selectedRow(int column)402 QModelIndex SvnLogDlgImp::selectedRow(int column)
403 {
404 QModelIndexList _mi = m_LogTreeView->selectionModel()->selectedRows(column);
405 if (_mi.count() < 1) {
406 return QModelIndex();
407 }
408 return m_SortModel->mapToSource(_mi[0]);
409 }
410
slotCustomContextMenu(const QPoint & e)411 void SvnLogDlgImp::slotCustomContextMenu(const QPoint &e)
412 {
413 QModelIndex ind = m_LogTreeView->indexAt(e);
414 QModelIndex bel;
415 if (ind.isValid()) {
416 bel = m_LogTreeView->indexBelow(ind);
417 ind = m_SortModel->mapToSource(ind);
418 }
419 int row = -1;
420 if (ind.isValid()) {
421 row = ind.row();
422 } else {
423 return;
424 }
425
426 qlonglong rev = -1;
427 if (bel.isValid()) {
428 bel = m_SortModel->mapToSource(bel);
429 rev = m_CurrentModel->toRevision(bel);
430 }
431 QMenu popup;
432 QAction *ac;
433 bool unset = false;
434 if (row != m_CurrentModel->rightRow()) {
435 ac = popup.addAction(QIcon::fromTheme(QStringLiteral("kdesvnright")), i18n("Set version as right side of diff"));
436 ac->setData(101);
437 } else {
438 unset = true;
439 }
440 if (row != m_CurrentModel->leftRow()) {
441 ac = popup.addAction(QIcon::fromTheme(QStringLiteral("kdesvnleft")), i18n("Set version as left side of diff"));
442 ac->setData(102);
443 } else {
444 unset = true;
445 }
446 if (unset) {
447 ac = popup.addAction(i18n("Unset version for diff"));
448 ac->setData(103);
449 }
450 if (rev > -1 && !m_PegUrl.isUrl()) {
451 ac = popup.addAction(i18n("Revert this commit"));
452 ac->setData(104);
453 }
454 ac = popup.exec(m_LogTreeView->viewport()->mapToGlobal(e));
455 if (!ac) {
456 return;
457 }
458 int r = ac->data().toInt();
459 switch (r) {
460 case 101:
461 m_CurrentModel->setRightRow(row);
462 break;
463 case 102:
464 m_CurrentModel->setLeftRow(row);
465 break;
466 case 103:
467 if (row != m_CurrentModel->leftRow()) {
468 m_CurrentModel->setLeftRow(-1);
469 }
470 if (row != m_CurrentModel->rightRow()) {
471 m_CurrentModel->setRightRow(-1);
472 }
473 break;
474 case 104: {
475 svn::Revision previous(rev);
476 svn::Revision current(m_CurrentModel->toRevision(ind));
477 QString _path = m_PegUrl.path();
478 m_Actions->slotMergeWcRevisions(_path, current, previous, true, true, false, false, false);
479 }
480 break;
481 }
482 m_DispSpecDiff->setEnabled(m_CurrentModel->leftRow() != -1 && m_CurrentModel->rightRow() != -1 && m_CurrentModel->leftRow() != m_CurrentModel->rightRow());
483 }
484
slotChangedPathContextMenu(const QPoint & e)485 void SvnLogDlgImp::slotChangedPathContextMenu(const QPoint &e)
486 {
487 QTreeWidgetItem *_item = m_ChangedList->currentItem();
488 if (!_item) {
489 return;
490 }
491
492 LogChangePathItem *item = static_cast<LogChangePathItem *>(_item);
493 if (item->action() == 'D') {
494 return;
495 }
496 QModelIndex ind = selectedRow();
497 if (!ind.isValid()) {
498 return;
499 }
500 const qlonglong rev = m_CurrentModel->toRevision(ind);
501 QMenu popup;
502 const QString name = item->path();
503 const QString source = item->revision() > -1 ? item->source() : item->path();
504 QAction *ac;
505 ac = popup.addAction(i18n("Annotate"));
506 if (ac) {
507 ac->setData(101);
508 }
509 if (item->action() != 'A' || item->revision() > -1) {
510 ac = popup.addAction(i18n("Diff previous"));
511 if (ac) {
512 ac->setData(102);
513 }
514 }
515 ac = popup.addAction(i18n("Cat this version"));
516 if (ac) {
517 ac->setData(103);
518 }
519 ac = popup.exec(m_ChangedList->viewport()->mapToGlobal(e));
520 if (!ac) {
521 return;
522 }
523 int r = ac->data().toInt();
524 svn::Revision start(svn::Revision::START);
525 switch (r) {
526 case 101: {
527 m_Actions->makeBlame(start, rev, _base + name, QApplication::activeModalWidget(), rev, this);
528 break;
529 }
530 case 102: {
531 const svn_revnum_t prev = item->revision() > 0 ? item->revision() : rev - 1;
532 emit makeDiff(_base + source, prev, _base + name, rev, this);
533 break;
534 }
535 case 103: {
536 emit makeCat(rev, _base + source, source, rev, QApplication::activeModalWidget());
537 }
538 default:
539 break;
540 }
541 }
542
slotSingleDoubleClicked(QTreeWidgetItem * _item,int)543 void SvnLogDlgImp::slotSingleDoubleClicked(QTreeWidgetItem *_item, int)
544 {
545 if (!_item) {
546 return;
547 }
548
549 const LogChangePathItem *item = static_cast<LogChangePathItem *>(_item);
550 const QModelIndex ind = selectedRow();
551 if (!ind.isValid()) {
552 return;
553 }
554 svn::Revision start(svn::Revision::START);
555 if (item->action() != 'D') {
556 const QString name = item->path();
557 const qlonglong rev = m_CurrentModel->toRevision(ind);
558 m_Actions->makeBlame(start, rev, _base + name, QApplication::activeModalWidget(), rev, this);
559 }
560 }
561