File indexing completed on 2024-05-12 05:44:26
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 "revgraphview.h" 0021 #include "../stopdlg.h" 0022 #include "graphtree_defines.h" 0023 #include "graphtreelabel.h" 0024 #include "pannerview.h" 0025 #include "settings/kdesvnsettings.h" 0026 #include "svnqt/client.h" 0027 0028 #include <KLocalizedString> 0029 #include <KMessageBox> 0030 #include <KProcess> 0031 0032 #include <QContextMenuEvent> 0033 #include <QDesktopWidget> 0034 #include <QFileDialog> 0035 #include <QFontDatabase> 0036 #include <QGraphicsScene> 0037 #include <QMenu> 0038 #include <QMouseEvent> 0039 #include <QPainter> 0040 #include <QPixmap> 0041 #include <QRegExp> 0042 #include <QResizeEvent> 0043 #include <QScrollBar> 0044 #include <QTemporaryFile> 0045 #include <QTransform> 0046 0047 #include <math.h> 0048 0049 #define LABEL_WIDTH 160 0050 #define LABEL_HEIGHT 90 0051 0052 RevGraphView::RevGraphView(const svn::ClientP &_client, QWidget *parent) 0053 : QGraphicsView(parent) 0054 , m_Scene(nullptr) 0055 , m_Marker(nullptr) 0056 , m_Client(_client) 0057 , m_Selected(nullptr) 0058 , m_dotTmpFile(nullptr) 0059 , m_renderProcess(nullptr) 0060 , m_xMargin(0) 0061 , m_yMargin(0) 0062 , m_CompleteView(new PannerView(this)) 0063 , m_cvZoom(0) 0064 , m_LastAutoPosition(TopLeft) 0065 , m_isMoving(false) 0066 , m_noUpdateZoomerPos(false) 0067 0068 { 0069 m_CompleteView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 0070 m_CompleteView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 0071 m_CompleteView->raise(); 0072 m_CompleteView->hide(); 0073 connect(m_CompleteView, &PannerView::zoomRectMoved, this, &RevGraphView::zoomRectMoved); 0074 connect(m_CompleteView, &PannerView::zoomRectMoveFinished, this, &RevGraphView::zoomRectMoveFinished); 0075 } 0076 0077 RevGraphView::~RevGraphView() 0078 { 0079 setScene(nullptr); 0080 delete m_Scene; 0081 delete m_dotTmpFile; 0082 delete m_CompleteView; 0083 delete m_renderProcess; 0084 } 0085 0086 void RevGraphView::showText(const QString &s) 0087 { 0088 clear(); 0089 m_Scene = new QGraphicsScene; 0090 m_Scene->addSimpleText(s); 0091 setScene(m_Scene); 0092 m_Scene->update(); 0093 m_CompleteView->hide(); 0094 } 0095 0096 void RevGraphView::clear() 0097 { 0098 if (m_Selected) { 0099 m_Selected->setSelected(false); 0100 m_Selected = nullptr; 0101 } 0102 if (m_Marker) { 0103 m_Marker->hide(); 0104 delete m_Marker; 0105 m_Marker = nullptr; 0106 } 0107 setScene(nullptr); 0108 m_CompleteView->setScene(nullptr); 0109 delete m_Scene; 0110 m_Scene = nullptr; 0111 } 0112 0113 void RevGraphView::beginInsert() 0114 { 0115 viewport()->setUpdatesEnabled(false); 0116 } 0117 0118 void RevGraphView::endInsert() 0119 { 0120 if (m_Scene) { 0121 /* 0122 _cvZoom = 0; 0123 updateSizes(); 0124 */ 0125 m_Scene->update(); 0126 } 0127 viewport()->setUpdatesEnabled(true); 0128 } 0129 0130 void RevGraphView::readDotOutput() 0131 { 0132 if (!m_renderProcess) { 0133 return; 0134 } 0135 m_dotOutput += QString::fromLocal8Bit(m_renderProcess->readAllStandardOutput()); 0136 } 0137 0138 void RevGraphView::dotExit(int exitcode, QProcess::ExitStatus exitStatus) 0139 { 0140 if (!m_renderProcess) { 0141 return; 0142 } 0143 if (exitStatus != QProcess::NormalExit || exitcode != 0) { 0144 QString error = i18n("Could not run process \"%1\".\n\nProcess stopped with message:\n%2", 0145 m_renderProcess->program().join(" "), 0146 QString::fromLocal8Bit(m_renderProcess->readAllStandardError())); 0147 showText(error); 0148 delete m_renderProcess; 0149 m_renderProcess = nullptr; 0150 return; 0151 } 0152 // remove line breaks when lines to long 0153 QRegExp endslash("\\\\\\n"); 0154 m_dotOutput.remove(endslash); 0155 double scale = 1.0; 0156 double dotWidth = 1.0, dotHeight = 1.0; 0157 QTextStream dotStream(&m_dotOutput, QIODevice::ReadOnly); 0158 QString cmd; 0159 int lineno = 0; 0160 beginInsert(); 0161 clear(); 0162 /* mostly taken from kcachegrind */ 0163 double scaleX = scale * 60; 0164 double scaleY = scale * 70; 0165 QRectF startRect; 0166 while (!dotStream.atEnd()) { 0167 QString line = dotStream.readLine(); 0168 if (line.isNull()) { 0169 break; 0170 } 0171 lineno++; 0172 if (line.isEmpty()) { 0173 continue; 0174 } 0175 QTextStream lineStream(&line, QIODevice::ReadOnly); 0176 lineStream >> cmd; 0177 if (cmd == QLatin1String("stop")) { 0178 break; 0179 } 0180 0181 if (cmd == QLatin1String("graph")) { 0182 lineStream >> scale >> dotWidth >> dotHeight; 0183 int w = qRound(scaleX * dotWidth); 0184 int h = qRound(scaleY * dotHeight); 0185 m_xMargin = 50; 0186 const QDesktopWidget *dw = QApplication::desktop(); 0187 if (w < dw->width()) { 0188 m_xMargin += (dw->width() - w) / 2; 0189 } 0190 m_yMargin = 50; 0191 if (h < dw->height()) { 0192 m_yMargin += (dw->height() - h) / 2; 0193 } 0194 m_Scene = new QGraphicsScene(0.0, 0.0, qreal(w + 2 * m_xMargin), qreal(h + 2 * m_yMargin)); 0195 m_Scene->setBackgroundBrush(Qt::white); 0196 continue; 0197 } 0198 if (m_dotTmpFile && (cmd != "node") && (cmd != "edge")) { 0199 qWarning() << "Ignoring unknown command '" << cmd << "' from dot (" << m_dotTmpFile->fileName() << ":" << lineno << ")" << Qt::endl; 0200 continue; 0201 } 0202 if (!m_Scene) { 0203 continue; 0204 } 0205 if (cmd == QLatin1String("node")) { 0206 QString nodeName, label; 0207 QString _x, _y, _w, _h; 0208 double x, y, width, height; 0209 lineStream >> nodeName >> _x >> _y >> _w >> _h; 0210 x = _x.toDouble(); 0211 y = _y.toDouble(); 0212 width = _w.toDouble(); 0213 height = _h.toDouble(); 0214 // better here 'cause dot may scramble utf8 labels so we regenerate it better 0215 // and do not read it in. 0216 label = getLabelstring(nodeName); 0217 double xx = (scaleX * x + m_xMargin); 0218 double yy = (scaleY * (dotHeight - y) + m_yMargin); 0219 double w = (scaleX * width); 0220 double h = (scaleY * height); 0221 QRectF r(xx - w / 2, yy - h / 2, w, h); 0222 GraphTreeLabel *t = new GraphTreeLabel(label, nodeName, r); 0223 m_Scene->addItem(t); 0224 if (isStart(nodeName)) { 0225 startRect = r; 0226 ensureVisible(startRect); 0227 } 0228 t->setBgColor(getBgColor(nodeName)); 0229 t->setZValue(1.0); 0230 t->show(); 0231 m_NodeList[nodeName] = t; 0232 t->setToolTip(toolTip(nodeName)); 0233 } else { 0234 QString node1Name, node2Name; 0235 QString _x, _y; 0236 double x, y; 0237 QPolygonF pa; 0238 int points, i; 0239 lineStream >> node1Name >> node2Name; 0240 lineStream >> points; 0241 pa.resize(points); 0242 for (i = 0; i < points; ++i) { 0243 if (lineStream.atEnd()) { 0244 break; 0245 } 0246 lineStream >> _x >> _y; 0247 x = _x.toDouble(); 0248 y = _y.toDouble(); 0249 double xx = (scaleX * x + m_xMargin); 0250 double yy = (scaleY * (dotHeight - y) + m_yMargin); 0251 #if 0 0252 if (0) qDebug(" P %d: ( %f / %f ) => ( %d / %d)", 0253 i, x, y, xx, yy); 0254 #endif 0255 pa[i] = QPointF(xx, yy); 0256 } 0257 if (i < points) { 0258 qDebug("CallGraphView: Can't read %d spline points (%d)", points, lineno); 0259 continue; 0260 } 0261 0262 GraphEdge *n = new GraphEdge(); 0263 QColor arrowColor = Qt::black; 0264 m_Scene->addItem(n); 0265 n->setPen(QPen(arrowColor, 1)); 0266 n->setControlPoints(pa); 0267 n->setZValue(0.5); 0268 n->show(); 0269 0270 /* arrow dir 0271 * eg. it is vx and vy for computing, NO absolute points! 0272 */ 0273 QPointF arrowDir; 0274 int indexHead = -1; 0275 0276 QMap<QString, GraphTreeLabel *>::Iterator it; 0277 it = m_NodeList.find(node2Name); 0278 if (it != m_NodeList.end()) { 0279 it.value()->setSource(node1Name); 0280 } 0281 it = m_NodeList.find(node1Name); 0282 if (it != m_NodeList.end()) { 0283 GraphTreeLabel *tlab = it.value(); 0284 if (tlab) { 0285 QPointF toCenter = tlab->rect().center(); 0286 qreal dx0 = pa[0].x() - toCenter.x(); 0287 qreal dy0 = pa[0].y() - toCenter.y(); 0288 qreal dx1 = pa[points - 1].x() - toCenter.x(); 0289 qreal dy1 = pa[points - 1].y() - toCenter.y(); 0290 0291 if (dx0 * dx0 + dy0 * dy0 > dx1 * dx1 + dy1 * dy1) { 0292 // start of spline is nearer to call target node 0293 indexHead = -1; 0294 while (arrowDir.isNull() && (indexHead < points - 2)) { 0295 indexHead++; 0296 arrowDir = pa[indexHead] - pa[indexHead + 1]; 0297 } 0298 } 0299 } 0300 } 0301 0302 if (arrowDir.isNull()) { 0303 indexHead = points; 0304 // sometimes the last spline points from dot are the same... 0305 while (arrowDir.isNull() && (indexHead > 1)) { 0306 indexHead--; 0307 arrowDir = pa[indexHead] - pa[indexHead - 1]; 0308 } 0309 } 0310 if (!arrowDir.isNull()) { 0311 QPointF baseDir = arrowDir; 0312 arrowDir *= 10.0 / sqrt(double(arrowDir.x() * arrowDir.x() + arrowDir.y() * arrowDir.y())); 0313 baseDir /= (sqrt(baseDir.x() * baseDir.x() + baseDir.y() * baseDir.y())); 0314 0315 QPointF t1(-baseDir.y() - baseDir.x(), baseDir.x() - baseDir.y()); 0316 QPointF t2(baseDir.y() - baseDir.x(), -baseDir.x() - baseDir.y()); 0317 QPolygonF a; 0318 t1 *= 3; 0319 t2 *= 3; 0320 a << pa[indexHead] + t1 << pa[indexHead] + arrowDir << pa[indexHead] + t2; 0321 0322 GraphEdgeArrow *aItem = new GraphEdgeArrow(n, nullptr); 0323 m_Scene->addItem(aItem); 0324 aItem->setPolygon(a); 0325 aItem->setBrush(arrowColor); 0326 aItem->setZValue(1.5); 0327 aItem->show(); 0328 } 0329 } 0330 } 0331 if (!m_Scene) { 0332 QString s = i18n("Error running the graph layouting tool.\n"); 0333 s += i18n("Please check that 'dot' is installed (package GraphViz)."); 0334 showText(s); 0335 } else { 0336 setScene(m_Scene); 0337 m_CompleteView->setScene(m_Scene); 0338 if (startRect.isValid()) { 0339 ensureVisible(startRect); 0340 } 0341 } 0342 endInsert(); 0343 delete m_renderProcess; 0344 m_renderProcess = nullptr; 0345 } 0346 0347 bool RevGraphView::isStart(const QString &nodeName) const 0348 { 0349 bool res = false; 0350 trevTree::ConstIterator it; 0351 it = m_Tree.find(nodeName); 0352 if (it == m_Tree.end()) { 0353 return res; 0354 } 0355 switch (it.value().Action) { 0356 case 'A': 0357 res = true; 0358 break; 0359 } 0360 return res; 0361 } 0362 0363 char RevGraphView::getAction(const QString &nodeName) const 0364 { 0365 trevTree::ConstIterator it; 0366 it = m_Tree.find(nodeName); 0367 if (it == m_Tree.end()) { 0368 return (char)0; 0369 } 0370 return it.value().Action; 0371 } 0372 0373 QColor RevGraphView::getBgColor(const QString &nodeName) const 0374 { 0375 trevTree::ConstIterator it; 0376 it = m_Tree.find(nodeName); 0377 QColor res = Qt::white; 0378 if (it == m_Tree.end()) { 0379 return res; 0380 } 0381 switch (it.value().Action) { 0382 case 'D': 0383 res = Kdesvnsettings::tree_delete_color(); 0384 break; 0385 case 'R': 0386 case 'M': 0387 res = Kdesvnsettings::tree_modify_color(); 0388 break; 0389 case 'A': 0390 res = Kdesvnsettings::tree_add_color(); 0391 break; 0392 case 'C': 0393 case 1: 0394 res = Kdesvnsettings::tree_copy_color(); 0395 break; 0396 case 2: 0397 res = Kdesvnsettings::tree_rename_color(); 0398 break; 0399 default: 0400 res = Kdesvnsettings::tree_modify_color(); 0401 break; 0402 } 0403 return res; 0404 } 0405 0406 QString RevGraphView::getLabelstring(const QString &nodeName) 0407 { 0408 QMap<QString, QString>::ConstIterator nIt; 0409 nIt = m_LabelMap.constFind(nodeName); 0410 if (nIt != m_LabelMap.constEnd()) { 0411 return nIt.value(); 0412 } 0413 trevTree::ConstIterator it1; 0414 it1 = m_Tree.constFind(nodeName); 0415 if (it1 == m_Tree.constEnd()) { 0416 return QString(); 0417 } 0418 QString res; 0419 QString revstring = svn::Revision(it1.value().rev).toString(); 0420 switch (it1.value().Action) { 0421 case 'D': 0422 res = i18n("Deleted at revision %1", revstring); 0423 break; 0424 case 'A': 0425 res = i18n("Added at revision %1 as %2", revstring, it1.value().name); 0426 break; 0427 case 'C': 0428 case 1: 0429 res = i18n("Copied to %1 at revision %2", it1.value().name, revstring); 0430 break; 0431 case 2: 0432 res = i18n("Renamed to %1 at revision %2", it1.value().name, revstring); 0433 break; 0434 case 'M': 0435 res = i18n("Modified at revision %1", revstring); 0436 break; 0437 case 'R': 0438 res = i18n("Replaced at revision %1", revstring); 0439 break; 0440 default: 0441 res = i18n("Revision %1", revstring); 0442 break; 0443 } 0444 m_LabelMap[nodeName] = res; 0445 return m_LabelMap[nodeName]; 0446 } 0447 0448 void RevGraphView::dumpRevtree() 0449 { 0450 if (m_dotTmpFile) { 0451 m_dotTmpFile->close(); 0452 delete m_dotTmpFile; 0453 } 0454 clear(); 0455 m_dotOutput.clear(); 0456 m_dotTmpFile = new QTemporaryFile(QLatin1String("XXXXXX.dot")); 0457 m_dotTmpFile->setAutoRemove(true); 0458 m_dotTmpFile->open(); 0459 0460 if (!m_dotTmpFile->open()) { 0461 showText(i18n("Could not open temporary file %1 for writing.", m_dotTmpFile->fileName())); 0462 return; 0463 } 0464 QTextStream stream(m_dotTmpFile); 0465 QFont f = QFontDatabase::systemFont(QFontDatabase::FixedFont); 0466 QFontMetrics _fm(f); 0467 int _fontsize = _fm.height(); 0468 if (_fontsize < 0) { 0469 _fontsize = 10; 0470 } 0471 0472 stream << "digraph \"callgraph\" {\n"; 0473 stream << " bgcolor=\"transparent\";\n"; 0474 int dir = Kdesvnsettings::tree_direction(); 0475 stream << QString(" rankdir=\""); 0476 switch (dir) { 0477 case 3: 0478 stream << "TB"; 0479 break; 0480 case 2: 0481 stream << "RL"; 0482 break; 0483 case 1: 0484 stream << "BT"; 0485 break; 0486 case 0: 0487 default: 0488 stream << "LR"; 0489 break; 0490 } 0491 stream << "\";\n"; 0492 0493 // stream << QString(" overlap=false;\n splines=true;\n"); 0494 0495 RevGraphView::trevTree::ConstIterator it1; 0496 for (it1 = m_Tree.constBegin(); it1 != m_Tree.constEnd(); ++it1) { 0497 stream << " " << it1.key() << "[ " 0498 << "shape=box, " 0499 << "label=\"" 0500 << "Zeile 1 geht ab Zeile 2 geht ab" /*getLabelstring(it1.key())*/ << "\"," 0501 << "fontsize=" << _fontsize << ",fontname=\"" << f.family() << "\"," 0502 << "];\n"; 0503 for (int j = 0; j < it1.value().targets.count(); ++j) { 0504 stream << " " << it1.key().toLatin1() << " " 0505 << "->" 0506 << " " << it1.value().targets[j].key << " [fontsize=" << _fontsize << ",fontname=\"" << f.family() << "\",style=\"solid\"];\n"; 0507 } 0508 } 0509 stream << "}\n" << flush; 0510 m_renderProcess = new KProcess; 0511 m_renderProcess->setEnv("LANG", "C"); 0512 *m_renderProcess << "dot"; 0513 *m_renderProcess << m_dotTmpFile->fileName() << "-Tplain"; 0514 connect(m_renderProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &RevGraphView::dotExit); 0515 connect(m_renderProcess, &QProcess::readyReadStandardOutput, this, &RevGraphView::readDotOutput); 0516 m_renderProcess->setOutputChannelMode(KProcess::SeparateChannels); 0517 m_renderProcess->start(); 0518 } 0519 0520 QString RevGraphView::toolTip(const QString &_nodename, bool full) const 0521 { 0522 QString res; 0523 trevTree::ConstIterator it = m_Tree.constFind(_nodename); 0524 if (it == m_Tree.constEnd()) { 0525 return res; 0526 } 0527 const QVector<QStringRef> sp = it.value().Message.splitRef(QLatin1Char('\n')); 0528 QString sm; 0529 if (sp.isEmpty()) { 0530 sm = it.value().Message; 0531 } else { 0532 if (!full) { 0533 sm = sp[0].toString() + QLatin1String("..."); 0534 } else { 0535 for (int j = 0; j < sp.count(); ++j) { 0536 if (j > 0) { 0537 sm += QLatin1String("<br/>"); 0538 } 0539 sm += sp[j].toString(); 0540 } 0541 } 0542 } 0543 if (!full && sm.length() > 50) { 0544 sm.truncate(47); 0545 sm += "..."; 0546 } 0547 static QLatin1String csep("</td><td>"); 0548 static QLatin1String rend("</td></tr>"); 0549 static QLatin1String rstart("<tr><td>"); 0550 res = QLatin1String("<html><body>"); 0551 0552 if (!full) { 0553 res += QString(QLatin1String("<b>%1</b>")).arg(it.value().name); 0554 res += i18n("<br>Revision: %1<br>Author: %2<br>Date: %3<br>Log: %4</html>", it.value().rev, it.value().Author, it.value().Date, sm); 0555 } else { 0556 res += QLatin1String("<table><tr><th colspan=\"2\"><b>") + it.value().name + QLatin1String("</b></th></tr>") + rstart 0557 + i18n("<b>Revision</b>%1%2%3", csep, it.value().rev, rend) + rstart + i18n("<b>Author</b>%1%2%3", csep, it.value().Author, rend) + rstart 0558 + i18n("<b>Date</b>%1%2%3", csep, it.value().Date, rend) + rstart + i18n("<b>Log</b>%1%2%3", csep, sm, rend) 0559 + QLatin1String("</table></body></html>"); 0560 } 0561 return res; 0562 } 0563 0564 void RevGraphView::updateSizes(QSize s) 0565 { 0566 if (!m_Scene) { 0567 return; 0568 } 0569 if (s == QSize(0, 0)) { 0570 s = size(); 0571 } 0572 0573 // the part of the canvas that should be visible 0574 qreal cWidth = m_Scene->width() - 2 * m_xMargin + 100; 0575 qreal cHeight = m_Scene->height() - 2 * m_yMargin + 100; 0576 0577 // hide birds eye view if no overview needed 0578 if (((cWidth < s.width()) && cHeight < s.height()) || m_NodeList.isEmpty()) { 0579 m_CompleteView->hide(); 0580 return; 0581 } 0582 0583 m_CompleteView->show(); 0584 0585 // first, assume use of 1/3 of width/height (possible larger) 0586 double zoom = .33 * s.width() / cWidth; 0587 if (zoom * cHeight < .33 * s.height()) { 0588 zoom = .33 * s.height() / cHeight; 0589 } 0590 0591 // fit to widget size 0592 if (cWidth * zoom > s.width()) { 0593 zoom = s.width() / (double)cWidth; 0594 } 0595 if (cHeight * zoom > s.height()) { 0596 zoom = s.height() / (double)cHeight; 0597 } 0598 0599 // scale to never use full height/width 0600 zoom = zoom * 3 / 4; 0601 0602 // at most a zoom of 1/3 0603 if (zoom > .33) { 0604 zoom = .33; 0605 } 0606 0607 if (zoom != m_cvZoom) { 0608 m_cvZoom = zoom; 0609 m_CompleteView->setTransform(QTransform::fromScale(zoom, zoom)); 0610 0611 // make it a little bigger to compensate for widget frame 0612 m_CompleteView->resize(int(cWidth * zoom) + 4, int(cHeight * zoom) + 4); 0613 0614 // update ZoomRect in completeView 0615 scrollContentsBy(0, 0); 0616 } 0617 0618 m_CompleteView->centerOn(m_Scene->width() / 2, m_Scene->height() / 2); 0619 updateZoomerPos(); 0620 } 0621 0622 void RevGraphView::updateZoomerPos() 0623 { 0624 int cvW = m_CompleteView->width(); 0625 int cvH = m_CompleteView->height(); 0626 int x = width() - cvW - verticalScrollBar()->width() - 2; 0627 int y = height() - cvH - horizontalScrollBar()->height() - 2; 0628 0629 QPoint oldZoomPos = m_CompleteView->pos(); 0630 QPoint newZoomPos = QPoint(0, 0); 0631 0632 int tlCols = items(QRect(0, 0, cvW, cvH)).count(); 0633 int trCols = items(QRect(x, 0, cvW, cvH)).count(); 0634 int blCols = items(QRect(0, y, cvW, cvH)).count(); 0635 int brCols = items(QRect(x, y, cvW, cvH)).count(); 0636 int minCols = tlCols; 0637 0638 ZoomPosition zp = m_LastAutoPosition; 0639 switch (zp) { 0640 case TopRight: 0641 minCols = trCols; 0642 break; 0643 case BottomLeft: 0644 minCols = blCols; 0645 break; 0646 case BottomRight: 0647 minCols = brCols; 0648 break; 0649 default: 0650 case TopLeft: 0651 minCols = tlCols; 0652 break; 0653 } 0654 if (minCols > tlCols) { 0655 minCols = tlCols; 0656 zp = TopLeft; 0657 } 0658 if (minCols > trCols) { 0659 minCols = trCols; 0660 zp = TopRight; 0661 } 0662 if (minCols > blCols) { 0663 minCols = blCols; 0664 zp = BottomLeft; 0665 } 0666 if (minCols > brCols) { 0667 minCols = brCols; 0668 zp = BottomRight; 0669 } 0670 0671 m_LastAutoPosition = zp; 0672 switch (zp) { 0673 case TopRight: 0674 newZoomPos = QPoint(x, 0); 0675 break; 0676 case BottomLeft: 0677 newZoomPos = QPoint(0, y); 0678 break; 0679 case BottomRight: 0680 newZoomPos = QPoint(x, y); 0681 break; 0682 default: 0683 break; 0684 } 0685 if (newZoomPos != oldZoomPos) { 0686 m_CompleteView->move(newZoomPos); 0687 } 0688 } 0689 0690 void RevGraphView::zoomRectMoved(qreal dx, qreal dy) 0691 { 0692 // if (leftMargin()>0) dx = 0; 0693 // if (topMargin()>0) dy = 0; 0694 m_noUpdateZoomerPos = true; 0695 QScrollBar *hBar = horizontalScrollBar(); 0696 QScrollBar *vBar = verticalScrollBar(); 0697 hBar->setValue(hBar->value() + int(dx)); 0698 vBar->setValue(vBar->value() + int(dy)); 0699 m_noUpdateZoomerPos = false; 0700 } 0701 0702 void RevGraphView::zoomRectMoveFinished() 0703 { 0704 #if 0 0705 if (_zoomPosition == Auto) 0706 #endif 0707 updateZoomerPos(); 0708 } 0709 0710 void RevGraphView::resizeEvent(QResizeEvent *e) 0711 { 0712 QGraphicsView::resizeEvent(e); 0713 if (m_Scene) { 0714 updateSizes(e->size()); 0715 } 0716 } 0717 0718 void RevGraphView::makeSelected(GraphTreeLabel *gtl) 0719 { 0720 if (m_Selected) { 0721 m_Selected->setSelected(false); 0722 } 0723 m_Selected = gtl; 0724 if (m_Marker) { 0725 m_Marker->hide(); 0726 delete m_Marker; 0727 m_Marker = nullptr; 0728 } 0729 if (gtl) { 0730 m_Marker = new GraphMark(gtl); 0731 m_Scene->addItem(m_Marker); 0732 m_Marker->setPos(gtl->pos()); 0733 m_Marker->setZValue(-1); 0734 } 0735 m_Scene->update(); 0736 m_CompleteView->update(); 0737 } 0738 0739 GraphTreeLabel *RevGraphView::firstLabelAt(const QPoint &pos) const 0740 { 0741 QList<QGraphicsItem *> its = items(pos); 0742 for (auto &it : its) { 0743 if (it->type() == GRAPHTREE_LABEL) { 0744 return static_cast<GraphTreeLabel *>(it); 0745 } 0746 } 0747 0748 return nullptr; 0749 } 0750 0751 void RevGraphView::mouseDoubleClickEvent(QMouseEvent *e) 0752 { 0753 setFocus(); 0754 if (e->button() == Qt::LeftButton) { 0755 GraphTreeLabel *i = firstLabelAt(e->pos()); 0756 if (i == nullptr) { 0757 return; 0758 } 0759 makeSelected(i); 0760 emit dispDetails(toolTip((i)->nodename(), true)); 0761 } 0762 } 0763 0764 void RevGraphView::mousePressEvent(QMouseEvent *e) 0765 { 0766 setFocus(); 0767 if (e->button() == Qt::LeftButton) { 0768 m_isMoving = true; 0769 m_lastPos = e->pos(); 0770 } 0771 } 0772 0773 void RevGraphView::mouseReleaseEvent(QMouseEvent *e) 0774 { 0775 if (e->button() == Qt::LeftButton && m_isMoving) { 0776 QPointF topLeft = mapToScene(QPoint(0, 0)); 0777 QPointF bottomRight = mapToScene(QPoint(width(), height())); 0778 QRectF z(topLeft, bottomRight); 0779 m_CompleteView->setZoomRect(z); 0780 m_isMoving = false; 0781 updateZoomerPos(); 0782 } 0783 } 0784 0785 void RevGraphView::scrollContentsBy(int dx, int dy) 0786 { 0787 // call QGraphicsView implementation 0788 QGraphicsView::scrollContentsBy(dx, dy); 0789 0790 QPointF topLeft = mapToScene(QPoint(0, 0)); 0791 QPointF bottomRight = mapToScene(QPoint(width(), height())); 0792 m_CompleteView->setZoomRect(QRectF(topLeft, bottomRight)); 0793 if (!m_noUpdateZoomerPos && !m_isMoving) { 0794 updateZoomerPos(); 0795 } 0796 } 0797 0798 void RevGraphView::mouseMoveEvent(QMouseEvent *e) 0799 { 0800 if (m_isMoving) { 0801 QPoint delta = e->pos() - m_lastPos; 0802 QScrollBar *hBar = horizontalScrollBar(); 0803 QScrollBar *vBar = verticalScrollBar(); 0804 hBar->setValue(hBar->value() - delta.x()); 0805 vBar->setValue(vBar->value() - delta.y()); 0806 m_lastPos = e->pos(); 0807 } 0808 } 0809 0810 void RevGraphView::setNewDirection(int dir) 0811 { 0812 if (dir < 0) { 0813 dir = 3; 0814 } else if (dir > 3) { 0815 dir = 0; 0816 } 0817 Kdesvnsettings::setTree_direction(dir); 0818 dumpRevtree(); 0819 } 0820 0821 void RevGraphView::contextMenuEvent(QContextMenuEvent *e) 0822 { 0823 if (!m_Scene) { 0824 return; 0825 } 0826 GraphTreeLabel *i = firstLabelAt(e->pos()); 0827 0828 QAction *ac; 0829 0830 QMenu popup; 0831 if (i) { 0832 if (!i->source().isEmpty() && getAction(i->nodename()) != 'D') { 0833 popup.addAction(i18n("Diff to previous"))->setData(301); 0834 } 0835 if (m_Selected && m_Selected != i && getAction(m_Selected->nodename()) != 'D' && getAction(i->nodename()) != 'D') { 0836 popup.addAction(i18n("Diff to selected item"))->setData(302); 0837 } 0838 if (getAction(i->nodename()) != 'D') { 0839 popup.addAction(i18n("Cat this version"))->setData(303); 0840 } 0841 if (m_Selected == i) { 0842 popup.addAction(i18n("Unselect item"))->setData(401); 0843 } else { 0844 popup.addAction(i18n("Select item"))->setData(402); 0845 } 0846 popup.addSeparator(); 0847 popup.addAction(i18n("Display details"))->setData(403); 0848 popup.addSeparator(); 0849 } 0850 popup.addAction(i18n("Rotate counter-clockwise"))->setData(101); 0851 popup.addAction(i18n("Rotate clockwise"))->setData(102); 0852 popup.addSeparator(); 0853 ac = popup.addAction(i18n("Diff in revision tree is recursive")); 0854 ac->setData(202); 0855 ac->setCheckable(true); 0856 ac->setChecked(Kdesvnsettings::tree_diff_rec()); 0857 popup.addAction(i18n("Save tree as PNG"))->setData(201); 0858 0859 ac = popup.exec(e->globalPos()); 0860 int r = 0; 0861 if (ac) { 0862 r = ac->data().toInt(); 0863 } 0864 0865 switch (r) { 0866 case 101: { 0867 int dir = Kdesvnsettings::tree_direction(); 0868 setNewDirection(++dir); 0869 } break; 0870 case 102: { 0871 int dir = Kdesvnsettings::tree_direction(); 0872 setNewDirection(--dir); 0873 } break; 0874 case 201: { 0875 QString fn = QFileDialog::getSaveFileName(this, i18n("Save tree as PNG"), QString(), i18n("Image (*.png)")); 0876 if (!fn.isEmpty()) { 0877 if (m_Marker) { 0878 m_Marker->hide(); 0879 } 0880 if (m_Selected) { 0881 m_Selected->setSelected(false); 0882 } 0883 QRect r = m_Scene->sceneRect().toRect(); 0884 QPixmap pix(r.width(), r.height()); 0885 pix.fill(); 0886 QPainter p(&pix); 0887 m_Scene->render(&p); 0888 pix.save(fn, "PNG"); 0889 if (m_Marker) { 0890 m_Marker->show(); 0891 } 0892 if (m_Selected) { 0893 m_Selected->setSelected(true); 0894 m_Scene->update(); 0895 m_CompleteView->updateCurrentRect(); 0896 } 0897 } 0898 } break; 0899 case 202: 0900 Kdesvnsettings::setTree_diff_rec(!Kdesvnsettings::tree_diff_rec()); 0901 break; 0902 case 301: 0903 if (i && i->type() == GRAPHTREE_LABEL && !i->source().isEmpty()) { 0904 makeDiffPrev(i); 0905 } 0906 break; 0907 case 302: 0908 if (i && m_Selected) { 0909 makeDiff(i->nodename(), m_Selected->nodename()); 0910 } 0911 break; 0912 case 303: 0913 if (i) { 0914 makeCat(i); 0915 } 0916 break; 0917 case 401: 0918 makeSelected(nullptr); 0919 break; 0920 case 402: 0921 makeSelected(i); 0922 break; 0923 case 403: 0924 if (i) { 0925 emit dispDetails(toolTip(i->nodename(), true)); 0926 } 0927 break; 0928 default: 0929 break; 0930 } 0931 } 0932 0933 void RevGraphView::makeCat(GraphTreeLabel *_l) 0934 { 0935 if (!_l) { 0936 return; 0937 } 0938 QString n1 = _l->nodename(); 0939 trevTree::ConstIterator it = m_Tree.constFind(n1); 0940 if (it == m_Tree.constEnd()) { 0941 return; 0942 } 0943 svn::Revision tr(it.value().rev); 0944 QString tp = m_basePath + it.value().name; 0945 emit makeCat(tr, tp, it.value().name, tr, QApplication::activeModalWidget()); 0946 } 0947 0948 void RevGraphView::makeDiffPrev(GraphTreeLabel *_l) 0949 { 0950 if (!_l) { 0951 return; 0952 } 0953 QString n1, n2; 0954 n1 = _l->nodename(); 0955 n2 = _l->source(); 0956 makeDiff(n1, n2); 0957 } 0958 0959 void RevGraphView::makeDiff(const QString &n1, const QString &n2) 0960 { 0961 if (n1.isEmpty() || n2.isEmpty()) { 0962 return; 0963 } 0964 trevTree::ConstIterator it; 0965 it = m_Tree.constFind(n2); 0966 if (it == m_Tree.constEnd()) { 0967 return; 0968 } 0969 svn::Revision sr(it.value().rev); 0970 QString sp = m_basePath + it.value().name; 0971 0972 it = m_Tree.constFind(n1); 0973 if (it == m_Tree.constEnd()) { 0974 return; 0975 } 0976 svn::Revision tr(it.value().rev); 0977 QString tp = m_basePath + it.value().name; 0978 if (Kdesvnsettings::tree_diff_rec()) { 0979 emit makeRecDiff(sp, sr, tp, tr, QApplication::activeModalWidget()); 0980 } else { 0981 emit makeNorecDiff(sp, sr, tp, tr, QApplication::activeModalWidget()); 0982 } 0983 } 0984 0985 void RevGraphView::setBasePath(const QString &_path) 0986 { 0987 m_basePath = _path; 0988 } 0989 0990 void RevGraphView::slotClientException(const QString &what) 0991 { 0992 KMessageBox::error(QApplication::activeModalWidget(), what, i18n("SVN Error")); 0993 } 0994 0995 #include "moc_revgraphview.cpp"