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"