summaryrefslogtreecommitdiffstats
path: root/src/svnfrontend/graphtree/revgraphview.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/svnfrontend/graphtree/revgraphview.cpp')
-rw-r--r--src/svnfrontend/graphtree/revgraphview.cpp950
1 files changed, 950 insertions, 0 deletions
diff --git a/src/svnfrontend/graphtree/revgraphview.cpp b/src/svnfrontend/graphtree/revgraphview.cpp
new file mode 100644
index 0000000..cc85c0e
--- /dev/null
+++ b/src/svnfrontend/graphtree/revgraphview.cpp
@@ -0,0 +1,950 @@
+/***************************************************************************
+ * Copyright (C) 2006-2007 by Rajko Albrecht *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
+ ***************************************************************************/
+#include "revgraphview.h"
+#include "graphtreelabel.h"
+#include "pannerview.h"
+#include "graphtree_defines.h"
+#include "src/settings/kdesvnsettings.h"
+#include "../stopdlg.h"
+#include "src/svnqt/client.hpp"
+
+#include <kapp.h>
+#include <kdebug.h>
+#include <ktempfile.h>
+#include <ktempdir.h>
+#include <kprocess.h>
+#include <klocale.h>
+#include <kfiledialog.h>
+#include <kmessagebox.h>
+
+#include <qtooltip.h>
+#include <qwmatrix.h>
+#include <qpopupmenu.h>
+#include <qpainter.h>
+#include <qregexp.h>
+
+#include <math.h>
+
+#define LABEL_WIDTH 160
+#define LABEL_HEIGHT 90
+
+
+class GraphViewTip:public QToolTip
+{
+public:
+ GraphViewTip( QWidget* p ):QToolTip(p) {}
+ virtual ~GraphViewTip(){}
+
+protected:
+ void maybeTip( const QPoint & );
+};
+
+void GraphViewTip::maybeTip( const QPoint & pos)
+{
+ if (!parentWidget()->inherits( "RevGraphView" )) return;
+ RevGraphView* cgv = (RevGraphView*)parentWidget();
+ QPoint cPos = cgv->viewportToContents(pos);
+ QCanvasItemList l = cgv->canvas()->collisions(cPos);
+ if (l.count() == 0) return;
+ QCanvasItem* i = l.first();
+ if (i->rtti() == GRAPHTREE_LABEL) {
+ GraphTreeLabel*tl = (GraphTreeLabel*)i;
+ QString nm = tl->nodename();
+ QString tipStr = cgv->toolTip(nm);
+ if (tipStr.length()>0) {
+ QPoint vPosTL = cgv->contentsToViewport(i->boundingRect().topLeft());
+ QPoint vPosBR = cgv->contentsToViewport(i->boundingRect().bottomRight());
+ tip(QRect(vPosTL, vPosBR), tipStr);
+ }
+ }
+}
+
+RevGraphView::RevGraphView(QObject*aListener,svn::Client*_client,QWidget * parent, const char * name, WFlags f)
+ : QCanvasView(parent,name,f)
+{
+ m_Canvas = 0L;
+ m_Client = _client;
+ m_Listener = aListener;
+ dotTmpFile = 0;
+ m_Selected = 0;
+ renderProcess = 0;
+ m_Marker = 0;
+ m_Tip = new GraphViewTip(this);
+ m_CompleteView = new PannerView(this);
+ m_CompleteView->setVScrollBarMode(QScrollView::AlwaysOff);
+ m_CompleteView->setHScrollBarMode(QScrollView::AlwaysOff);
+ m_CompleteView->raise();
+ m_CompleteView->hide();
+ connect(this, SIGNAL(contentsMoving(int,int)),
+ this, SLOT(contentsMovingSlot(int,int)));
+ connect(m_CompleteView, SIGNAL(zoomRectMoved(int,int)),
+ this, SLOT(zoomRectMoved(int,int)));
+ connect(m_CompleteView, SIGNAL(zoomRectMoveFinished()),
+ this, SLOT(zoomRectMoveFinished()));
+ m_LastAutoPosition = TopLeft;
+ _isMoving = false;
+ _noUpdateZoomerPos = false;
+ m_LabelMap[""]="";
+}
+
+RevGraphView::~RevGraphView()
+{
+ setCanvas(0);
+ delete m_Canvas;
+ delete dotTmpFile;
+ delete m_CompleteView;
+ delete m_Tip;
+ delete renderProcess;
+}
+
+void RevGraphView::showText(const QString&s)
+{
+ clear();
+ m_Canvas = new QCanvas(QApplication::desktop()->width(),
+ QApplication::desktop()->height());
+
+ QCanvasText* t = new QCanvasText(s, m_Canvas);
+ t->move(5, 5);
+ t->show();
+ center(0,0);
+ setCanvas(m_Canvas);
+ m_Canvas->update();
+ m_CompleteView->hide();
+}
+
+void RevGraphView::clear()
+{
+ if (m_Selected) {
+ m_Selected->setSelected(false);
+ m_Selected=0;
+ }
+ if (m_Marker) {
+ m_Marker->hide();
+ delete m_Marker;
+ m_Marker=0;
+ }
+ if (!m_Canvas) return;
+ delete m_Canvas;
+ m_Canvas = 0;
+ setCanvas(0);
+ m_CompleteView->setCanvas(0);
+}
+
+void RevGraphView::beginInsert()
+{
+ viewport()->setUpdatesEnabled(false);
+}
+
+void RevGraphView::endInsert()
+{
+ if (m_Canvas) {
+ _cvZoom = 0;
+ updateSizes();
+ m_Canvas->update();
+ }
+ viewport()->setUpdatesEnabled(true);
+}
+
+void RevGraphView::readDotOutput(KProcess*,char * buffer,int buflen)
+{
+ dotOutput+=QString::fromLocal8Bit(buffer, buflen);
+}
+
+void RevGraphView::dotExit(KProcess*p)
+{
+ if (p!=renderProcess)return;
+ // remove line breaks when lines to long
+ QRegExp endslash("\\\\\\n");
+ dotOutput.replace(endslash,"");
+ double scale = 1.0, scaleX = 1.0, scaleY = 1.0;
+ double dotWidth, dotHeight;
+ QTextStream* dotStream;
+ dotStream = new QTextStream(dotOutput, IO_ReadOnly);
+ QString line,cmd;
+ int lineno=0;
+ clear();
+ beginInsert();
+ /* mostly taken from kcachegrind */
+ while (1) {
+ line = dotStream->readLine();
+ if (line.isNull()) break;
+ lineno++;
+ if (line.isEmpty()) continue;
+ QTextStream lineStream(line, IO_ReadOnly);
+ lineStream >> cmd;
+ if (cmd == "stop") {break; }
+
+ if (cmd == "graph") {
+ lineStream >> scale >> dotWidth >> dotHeight;
+ scaleX = scale * 60; scaleY = scale * 100;
+ int w = (int)(scaleX * dotWidth);
+ int h = (int)(scaleY * dotHeight);
+
+ _xMargin = 50;
+ if (w < QApplication::desktop()->width())
+ _xMargin += (QApplication::desktop()->width()-w)/2;
+ _yMargin = 50;
+ if (h < QApplication::desktop()->height())
+ _yMargin += (QApplication::desktop()->height()-h)/2;
+ m_Canvas = new QCanvas(int(w+2*_xMargin), int(h+2*_yMargin));
+ continue;
+ }
+ if ((cmd != "node") && (cmd != "edge")) {
+ kdWarning() << "Ignoring unknown command '" << cmd << "' from dot ("
+ << dotTmpFile->name() << ":" << lineno << ")" << endl;
+ continue;
+ }
+ if (cmd=="node") {
+ QString nodeName, label;
+ QString _x,_y,_w,_h;
+ double x, y, width, height;
+ lineStream >> nodeName >> _x >> _y >> _w >> _h;
+ x=_x.toDouble();
+ y=_y.toDouble();
+ width=_w.toDouble();
+ height=_h.toDouble();
+ // better here 'cause dot may scramble utf8 labels so we regenerate it better
+ // and do not read it in.
+ label = getLabelstring(nodeName);
+ int xx = (int)(scaleX * x + _xMargin);
+ int yy = (int)(scaleY * (dotHeight - y) + _yMargin);
+ int w = (int)(scaleX * width);
+ int h = (int)(scaleY * height);
+ QRect r(xx-w/2, yy-h/2, w, h);
+ GraphTreeLabel*t=new GraphTreeLabel(label,nodeName,r,m_Canvas);
+ if (isStart(nodeName)) {
+ ensureVisible(r.x(),r.y());
+ }
+ t->setBgColor(getBgColor(nodeName));
+ t->setZ(1.0);
+ t->show();
+ m_NodeList[nodeName]=t;
+ } else {
+ QString node1Name, node2Name, label;
+ QString _x,_y;
+ double x, y;
+ QPointArray pa;
+ int points, i;
+ lineStream >> node1Name >> node2Name;
+ lineStream >> points;
+ pa.resize(points);
+ for (i=0;i<points;++i) {
+ if (lineStream.atEnd()) break;
+ lineStream >> _x >> _y;
+ x=_x.toDouble();
+ y=_y.toDouble();
+ int xx = (int)(scaleX * x + _xMargin);
+ int yy = (int)(scaleY * (dotHeight - y) + _yMargin);
+
+ if (0) qDebug(" P %d: ( %f / %f ) => ( %d / %d)",
+ i, x, y, xx, yy);
+ pa.setPoint(i, xx, yy);
+ }
+ if (i < points) {
+ qDebug("CallGraphView: Can't read %d spline points (%d)",
+ points, lineno);
+ continue;
+ }
+
+ GraphEdge * n = new GraphEdge(m_Canvas);
+ QColor arrowColor = Qt::black;
+ n->setPen(QPen(arrowColor,1));
+ n->setControlPoints(pa,false);
+ n->setZ(0.5);
+ n->show();
+
+ /* arrow */
+ QPoint arrowDir;
+ int indexHead = -1;
+
+ QMap<QString,GraphTreeLabel*>::Iterator it;
+ it = m_NodeList.find(node2Name);
+ if (it!=m_NodeList.end()) {
+ it.data()->setSource(node1Name);
+ }
+ it = m_NodeList.find(node1Name);
+ if (it!=m_NodeList.end()) {
+ GraphTreeLabel*tlab = it.data();
+ if (tlab) {
+ QPoint toCenter = tlab->rect().center();
+ int dx0 = pa.point(0).x() - toCenter.x();
+ int dy0 = pa.point(0).y() - toCenter.y();
+ int dx1 = pa.point(points-1).x() - toCenter.x();
+ int dy1 = pa.point(points-1).y() - toCenter.y();
+ if (dx0*dx0+dy0*dy0 > dx1*dx1+dy1*dy1) {
+ // start of spline is nearer to call target node
+ indexHead=-1;
+ while(arrowDir.isNull() && (indexHead<points-2)) {
+ indexHead++;
+ arrowDir = pa.point(indexHead) - pa.point(indexHead+1);
+ }
+ }
+ }
+ }
+
+ if (arrowDir.isNull()) {
+ indexHead = points;
+ // sometimes the last spline points from dot are the same...
+ while(arrowDir.isNull() && (indexHead>1)) {
+ indexHead--;
+ arrowDir = pa.point(indexHead) - pa.point(indexHead-1);
+ }
+ }
+ if (!arrowDir.isNull()) {
+ arrowDir *= 10.0/sqrt(double(arrowDir.x()*arrowDir.x() +
+ arrowDir.y()*arrowDir.y()));
+ QPointArray a(3);
+ a.setPoint(0, pa.point(indexHead) + arrowDir);
+ a.setPoint(1, pa.point(indexHead) + QPoint(arrowDir.y()/2,
+ -arrowDir.x()/2));
+ a.setPoint(2, pa.point(indexHead) + QPoint(-arrowDir.y()/2,
+ arrowDir.x()/2));
+ GraphEdgeArrow* aItem = new GraphEdgeArrow(n,m_Canvas);
+ aItem->setPoints(a);
+ aItem->setBrush(arrowColor);
+ aItem->setZ(1.5);
+ aItem->show();
+// sItem->setArrow(aItem);
+ }
+ }
+ }
+ if (!m_Canvas) {
+ QString s = i18n("Error running the graph layouting tool.\n");
+ s += i18n("Please check that 'dot' is installed (package GraphViz).");
+ showText(s);
+ } else {
+ setCanvas(m_Canvas);
+ m_CompleteView->setCanvas(m_Canvas);
+ }
+ endInsert();
+ delete p;
+ renderProcess=0;
+}
+
+bool RevGraphView::isStart(const QString&nodeName)const
+{
+ bool res = false;
+ trevTree::ConstIterator it;
+ it = m_Tree.find(nodeName);
+ if (it==m_Tree.end()) {
+ return res;
+ }
+ switch (it.data().Action) {
+ case 'A':
+ res = true;
+ break;
+ }
+ return res;
+}
+
+char RevGraphView::getAction(const QString&nodeName)const
+{
+ trevTree::ConstIterator it;
+ it = m_Tree.find(nodeName);
+ if (it==m_Tree.end()) {
+ return (char)0;
+ }
+ return it.data().Action;
+}
+
+QColor RevGraphView::getBgColor(const QString&nodeName)const
+{
+ trevTree::ConstIterator it;
+ it = m_Tree.find(nodeName);
+ QColor res = Qt::white;
+ if (it==m_Tree.end()) {
+ return res;
+ }
+ switch (it.data().Action) {
+ case 'D':
+ res = Kdesvnsettings::tree_delete_color();
+ break;
+ case 'R':
+ case 'M':
+ res = Kdesvnsettings::tree_modify_color();
+ break;
+ case 'A':
+ res = Kdesvnsettings::tree_add_color();
+ break;
+ case 'C':
+ case 1:
+ res = Kdesvnsettings::tree_copy_color();
+ break;
+ case 2:
+ res = Kdesvnsettings::tree_rename_color();
+ break;
+ default:
+ res = Kdesvnsettings::tree_modify_color();
+ break;
+ }
+ return res;
+}
+
+const QString&RevGraphView::getLabelstring(const QString&nodeName)
+{
+ QMap<QString,QString>::ConstIterator nIt;
+ nIt = m_LabelMap.find(nodeName);
+ if (nIt!=m_LabelMap.end()) {
+ return nIt.data();
+ }
+ trevTree::ConstIterator it1;
+ it1 = m_Tree.find(nodeName);
+ if (it1==m_Tree.end()) {
+ return m_LabelMap[""];
+ }
+ QString res;
+ switch (it1.data().Action) {
+ case 'D':
+ res = i18n("Deleted at revision %1").arg(it1.data().rev);
+ break;
+ case 'A':
+ res = i18n("Added at revision %1 as %2")
+ .arg(it1.data().rev)
+ .arg(it1.data().name);
+ break;
+ case 'C':
+ case 1:
+ res = i18n("Copied to %1 at revision %2").arg(it1.data().name).arg(it1.data().rev);
+ break;
+ case 2:
+ res = i18n("Renamed to %1 at revision %2").arg(it1.data().name).arg(it1.data().rev);
+ break;
+ case 'M':
+ res = i18n("Modified at revision %1").arg(it1.data().rev);
+ break;
+ case 'R':
+ res = i18n("Replaced at revision %1").arg(it1.data().rev);
+ break;
+ default:
+ res=i18n("Revision %1").arg(it1.data().rev);
+ break;
+ }
+ m_LabelMap[nodeName]=res;
+ return m_LabelMap[nodeName];
+}
+
+void RevGraphView::dumpRevtree()
+{
+ delete dotTmpFile;
+ clear();
+ dotOutput = "";
+ dotTmpFile = new KTempFile(QString::null,".dot");
+ dotTmpFile->setAutoDelete(true);
+
+ QTextStream* stream = dotTmpFile->textStream();
+ if (!stream) {
+ showText(i18n("Could not open tempfile %1 for writing.").arg(dotTmpFile->name()));
+ return;
+ }
+
+ *stream << "digraph \"callgraph\" {\n";
+ *stream << " bgcolor=\"transparent\";\n";
+ int dir = Kdesvnsettings::tree_direction();
+ *stream << QString(" rankdir=\"");
+ switch (dir) {
+ case 3:
+ *stream << "TB";
+ break;
+ case 2:
+ *stream << "RL";
+ break;
+ case 1:
+ *stream << "BT";
+ break;
+ case 0:
+ default:
+ *stream << "LR";
+ break;
+ }
+ *stream << "\";\n";
+
+ //*stream << QString(" overlap=false;\n splines=true;\n");
+
+ RevGraphView::trevTree::ConstIterator it1;
+ for (it1=m_Tree.begin();it1!=m_Tree.end();++it1) {
+ *stream << " " << it1.key()
+ << "[ "
+ << "shape=box, "
+ << "label=\""<<getLabelstring(it1.key())<<"\","
+ << "];\n";
+ for (unsigned j=0;j<it1.data().targets.count();++j) {
+ *stream<<" "<<it1.key().latin1()<< " "
+ << "->"<<" "<<it1.data().targets[j].key
+ << " [fontsize=10,style=\"solid\"];\n";
+ }
+ }
+ *stream << "}\n"<<flush;
+ renderProcess = new KProcess();
+ renderProcess->setEnvironment("LANG","C");
+ *renderProcess << "dot";
+ *renderProcess << dotTmpFile->name() << "-Tplain";
+ connect(renderProcess,SIGNAL(processExited(KProcess*)),this,SLOT(dotExit(KProcess*)));
+ connect(renderProcess,SIGNAL(receivedStdout(KProcess*,char*,int)),
+ this,SLOT(readDotOutput(KProcess*,char*,int)) );
+ if (!renderProcess->start(KProcess::NotifyOnExit,KProcess::Stdout)) {
+ QString arguments;
+ for (unsigned c=0;c<renderProcess->args().count();++c) {
+ arguments+=QString(" %1").arg(renderProcess->args()[c]);
+ }
+ QString error = i18n("Could not start process \"%1\".").arg(arguments);
+ showText(error);
+ renderProcess=0;
+ //delete renderProcess;<
+ }
+}
+
+QString RevGraphView::toolTip(const QString&_nodename,bool full)const
+{
+ QString res = QString::null;
+ trevTree::ConstIterator it;
+ it = m_Tree.find(_nodename);
+ if (it==m_Tree.end()) {
+ return res;
+ }
+ QStringList sp = QStringList::split("\n",it.data().Message);
+ QString sm;
+ if (sp.count()==0) {
+ sm = it.data().Message;
+ } else {
+ if (!full) {
+ sm = sp[0]+"...";
+ } else {
+ for (unsigned j = 0; j<sp.count(); ++j) {
+ if (j>0) sm+="<br>";
+ sm+=sp[j];
+ }
+ }
+ }
+ if (!full && sm.length()>50) {
+ sm.truncate(47);
+ sm+="...";
+ }
+ static QString csep = "</td><td>";
+ static QString rend = "</td></tr>";
+ static QString rstart = "<tr><td>";
+ res = QString("<html><body>");
+
+ if (!full) {
+ res+=QString("<b>%1</b>").arg(it.data().name);
+ res += i18n("<br>Revision: %1<br>Author: %2<br>Date: %3<br>Log: %4</html>")
+ .arg(it.data().rev)
+ .arg(it.data().Author)
+ .arg(it.data().Date)
+ .arg(sm);
+ } else {
+ res+="<table><tr><th colspan=\"2\"><b>"+it.data().name+"</b></th></tr>";
+ res+=rstart;
+ res+=i18n("<b>Revision</b>%1%2%3").arg(csep).arg(it.data().rev).arg(rend);
+ res+=rstart+i18n("<b>Author</b>%1%2%3").arg(csep).arg(it.data().Author).arg(rend);
+ res+=rstart+i18n("<b>Date</b>%1%2%3").arg(csep).arg(it.data().Date).arg(rend);
+ res+=rstart+i18n("<b>Log</b>%1%2%3").arg(csep).arg(sm).arg(rend);
+ res+="</table></body></html>";
+ }
+ return res;
+}
+
+void RevGraphView::updateSizes(QSize s)
+{
+ if (!m_Canvas) return;
+ if (s == QSize(0,0)) s = size();
+
+ // the part of the canvas that should be visible
+ int cWidth = m_Canvas->width() - 2*_xMargin + 100;
+ int cHeight = m_Canvas->height() - 2*_yMargin + 100;
+
+ // hide birds eye view if no overview needed
+ if (((cWidth < s.width()) && cHeight < s.height())||m_NodeList.count()==0) {
+ m_CompleteView->hide();
+ return;
+ }
+
+ m_CompleteView->show();
+
+ // first, assume use of 1/3 of width/height (possible larger)
+ double zoom = .33 * s.width() / cWidth;
+ if (zoom * cHeight < .33 * s.height()) zoom = .33 * s.height() / cHeight;
+
+ // fit to widget size
+ if (cWidth * zoom > s.width()) zoom = s.width() / (double)cWidth;
+ if (cHeight * zoom > s.height()) zoom = s.height() / (double)cHeight;
+
+ // scale to never use full height/width
+ zoom = zoom * 3/4;
+
+ // at most a zoom of 1/3
+ if (zoom > .33) zoom = .33;
+
+ if (zoom != _cvZoom) {
+ _cvZoom = zoom;
+ if (0) qDebug("Canvas Size: %dx%d, Visible: %dx%d, Zoom: %f",
+ m_Canvas->width(), m_Canvas->height(),
+ cWidth, cHeight, zoom);
+
+ QWMatrix wm;
+ wm.scale( zoom, zoom );
+ m_CompleteView->setWorldMatrix(wm);
+
+ // make it a little bigger to compensate for widget frame
+ m_CompleteView->resize(int(cWidth * zoom) + 4,
+ int(cHeight * zoom) + 4);
+
+ // update ZoomRect in completeView
+ contentsMovingSlot(contentsX(), contentsY());
+ }
+
+ m_CompleteView->setContentsPos(int(zoom*(_xMargin-50)),
+ int(zoom*(_yMargin-50)));
+ updateZoomerPos();
+}
+
+void RevGraphView::updateZoomerPos()
+{
+ int cvW = m_CompleteView->width();
+ int cvH = m_CompleteView->height();
+ int x = width()- cvW - verticalScrollBar()->width() -2;
+ int y = height()-cvH - horizontalScrollBar()->height() -2;
+
+ QPoint oldZoomPos = m_CompleteView->pos();
+ QPoint newZoomPos = QPoint(0,0);
+
+#if 0
+ ZoomPosition zp = _zoomPosition;
+ if (zp == Auto) {
+#else
+ ZoomPosition zp = m_LastAutoPosition;
+#endif
+ QPoint tl1Pos = viewportToContents(QPoint(0,0));
+ QPoint tl2Pos = viewportToContents(QPoint(cvW,cvH));
+ QPoint tr1Pos = viewportToContents(QPoint(x,0));
+ QPoint tr2Pos = viewportToContents(QPoint(x+cvW,cvH));
+ QPoint bl1Pos = viewportToContents(QPoint(0,y));
+ QPoint bl2Pos = viewportToContents(QPoint(cvW,y+cvH));
+ QPoint br1Pos = viewportToContents(QPoint(x,y));
+ QPoint br2Pos = viewportToContents(QPoint(x+cvW,y+cvH));
+ int tlCols = m_Canvas->collisions(QRect(tl1Pos,tl2Pos)).count();
+ int trCols = m_Canvas->collisions(QRect(tr1Pos,tr2Pos)).count();
+ int blCols = m_Canvas->collisions(QRect(bl1Pos,bl2Pos)).count();
+ int brCols = m_Canvas->collisions(QRect(br1Pos,br2Pos)).count();
+ int minCols = tlCols;
+ zp = m_LastAutoPosition;
+ switch(zp) {
+ case TopRight: minCols = trCols; break;
+ case BottomLeft: minCols = blCols; break;
+ case BottomRight: minCols = brCols; break;
+ default:
+ case TopLeft: minCols = tlCols; break;
+ }
+ if (minCols > tlCols) { minCols = tlCols; zp = TopLeft; }
+ if (minCols > trCols) { minCols = trCols; zp = TopRight; }
+ if (minCols > blCols) { minCols = blCols; zp = BottomLeft; }
+ if (minCols > brCols) { minCols = brCols; zp = BottomRight; }
+
+ m_LastAutoPosition = zp;
+#if 0
+ }
+#endif
+ switch(zp) {
+ case TopRight:
+ newZoomPos = QPoint(x,0);
+ break;
+ case BottomLeft:
+ newZoomPos = QPoint(0,y);
+ break;
+ case BottomRight:
+ newZoomPos = QPoint(x,y);
+ break;
+ default:
+ break;
+ }
+ if (newZoomPos != oldZoomPos) m_CompleteView->move(newZoomPos);
+}
+
+void RevGraphView::contentsMovingSlot(int x,int y)
+{
+ QRect z(int(x * _cvZoom), int(y * _cvZoom),
+ int(visibleWidth() * _cvZoom)-1, int(visibleHeight() * _cvZoom)-1);
+ if (0) qDebug("moving: (%d,%d) => (%d/%d - %dx%d)",
+ x, y, z.x(), z.y(), z.width(), z.height());
+ m_CompleteView->setZoomRect(z);
+ if (!_noUpdateZoomerPos) {
+ updateZoomerPos();
+ }
+}
+
+void RevGraphView::zoomRectMoved(int dx,int dy)
+{
+ if (leftMargin()>0) dx = 0;
+ if (topMargin()>0) dy = 0;
+ _noUpdateZoomerPos = true;
+ scrollBy(int(dx/_cvZoom),int(dy/_cvZoom));
+ _noUpdateZoomerPos = false;
+}
+
+void RevGraphView::zoomRectMoveFinished()
+{
+#if 0
+ if (_zoomPosition == Auto)
+#endif
+ updateZoomerPos();
+}
+
+void RevGraphView::resizeEvent(QResizeEvent*e)
+{
+ QCanvasView::resizeEvent(e);
+ if (m_Canvas) updateSizes(e->size());
+}
+
+void RevGraphView::makeSelected(GraphTreeLabel*gtl)
+{
+ if (m_Selected) {
+ m_Selected->setSelected(false);
+ }
+ m_Selected=gtl;
+ if (m_Marker) {
+ m_Marker->hide();
+ delete m_Marker;
+ m_Marker=0;
+ }
+ if (gtl) {
+ m_Marker = new GraphMark(gtl,m_Canvas);
+ m_Marker->setZ(-1);
+ m_Marker->show();
+ m_Selected->setSelected(true);
+ }
+ m_Canvas->update();
+ m_CompleteView->updateCurrentRect();
+}
+
+void RevGraphView::contentsMouseDoubleClickEvent ( QMouseEvent * e )
+{
+ setFocus();
+ if (e->button() == Qt::LeftButton) {
+ QCanvasItemList l = canvas()->collisions(e->pos());
+ if (l.count()>0) {
+ QCanvasItem* i = l.first();
+ if (i->rtti()==GRAPHTREE_LABEL) {
+ makeSelected( (GraphTreeLabel*)i);
+ emit dispDetails(toolTip(((GraphTreeLabel*)i)->nodename(),true));
+ }
+ }
+ }
+}
+
+void RevGraphView::contentsMousePressEvent ( QMouseEvent * e )
+{
+ setFocus();
+ _isMoving = true;
+ _lastPos = e->globalPos();
+}
+
+void RevGraphView::contentsMouseReleaseEvent ( QMouseEvent * )
+{
+ _isMoving = false;
+ updateZoomerPos();
+}
+
+void RevGraphView::contentsMouseMoveEvent ( QMouseEvent * e )
+{
+ if (_isMoving) {
+ int dx = e->globalPos().x() - _lastPos.x();
+ int dy = e->globalPos().y() - _lastPos.y();
+ _noUpdateZoomerPos = true;
+ scrollBy(-dx, -dy);
+ _noUpdateZoomerPos = false;
+ _lastPos = e->globalPos();
+ }
+}
+
+void RevGraphView::setNewDirection(int dir)
+{
+ if (dir<0)dir=3;
+ else if (dir>3)dir=0;
+ Kdesvnsettings::setTree_direction(dir);
+ dumpRevtree();
+}
+
+void RevGraphView::contentsContextMenuEvent(QContextMenuEvent* e)
+{
+ if (!m_Canvas) return;
+ QCanvasItemList l = canvas()->collisions(e->pos());
+ QCanvasItem* i = (l.count() == 0) ? 0 : l.first();
+
+ QPopupMenu popup;
+ if (i && i->rtti()==GRAPHTREE_LABEL) {
+ if (!((GraphTreeLabel*)i)->source().isEmpty() && getAction(((GraphTreeLabel*)i)->nodename())!='D') {
+ popup.insertItem(i18n("Diff to previous"),301);
+ }
+ if (m_Selected && m_Selected != i && getAction(m_Selected->nodename())!='D'
+ && getAction(((GraphTreeLabel*)i)->nodename())!='D') {
+ popup.insertItem(i18n("Diff to selected item"),302);
+ }
+ if (getAction(((GraphTreeLabel*)i)->nodename())!='D') {
+ popup.insertItem(i18n("Cat this version"),303);
+ }
+ if (m_Selected == i) {
+ popup.insertItem(i18n("Unselect item"),401);
+ } else {
+ popup.insertItem(i18n("Select item"),402);
+ }
+ popup.insertSeparator();
+ popup.insertItem(i18n("Display details"),403);
+ popup.insertSeparator();
+ }
+ popup.insertItem(i18n("Rotate counter-clockwise"),101);
+ popup.insertItem(i18n("Rotate clockwise"),102);
+ popup.insertSeparator();
+ int it = popup.insertItem(i18n("Diff in revisiontree is recursive"),202);
+ popup.setCheckable(true);
+ popup.setItemChecked(it,Kdesvnsettings::tree_diff_rec());
+ popup.insertItem(i18n("Save tree as png"),201);
+
+ int r = popup.exec(e->globalPos());
+
+ switch (r) {
+ case 101:
+ {
+ int dir = Kdesvnsettings::tree_direction();
+ setNewDirection(++dir);
+ }
+ break;
+ case 102:
+ {
+ int dir = Kdesvnsettings::tree_direction();
+ setNewDirection(--dir);
+ }
+ break;
+ case 201:
+ {
+ QString fn = KFileDialog::getSaveFileName(":","*.png");
+ if (!fn.isEmpty()) {
+ if (m_Marker) {
+ m_Marker->hide();
+ }
+ if (m_Selected) {
+ m_Selected->setSelected(false);
+ }
+ QPixmap pix(m_Canvas->size());
+ QPainter p(&pix);
+ m_Canvas->drawArea( m_Canvas->rect(), &p );
+ pix.save(fn,"PNG");
+ if (m_Marker) {
+ m_Marker->show();
+ }
+ if (m_Selected) {
+ m_Selected->setSelected(true);
+ m_Canvas->update();
+ m_CompleteView->updateCurrentRect();
+ }
+ }
+ }
+ case 202:
+ {
+ Kdesvnsettings::setTree_diff_rec(!Kdesvnsettings::tree_diff_rec());
+ break;
+ }
+ break;
+ case 301:
+ if (i && i->rtti()==GRAPHTREE_LABEL && !((GraphTreeLabel*)i)->source().isEmpty()) {
+ makeDiffPrev((GraphTreeLabel*)i);
+ }
+ break;
+ case 302:
+ if (i && i->rtti()==GRAPHTREE_LABEL && m_Selected) {
+ makeDiff(((GraphTreeLabel*)i)->nodename(),m_Selected->nodename());
+ }
+ break;
+ case 303:
+ if (i && i->rtti()==GRAPHTREE_LABEL) {
+ makeCat((GraphTreeLabel*)i);
+ }
+ break;
+ case 401:
+ makeSelected(0);
+ break;
+ case 402:
+ makeSelected((GraphTreeLabel*)i);
+ break;
+ case 403:
+ emit dispDetails(toolTip(((GraphTreeLabel*)i)->nodename(),true));
+ break;
+ default:
+ break;
+ }
+}
+
+void RevGraphView::makeCat(GraphTreeLabel*_l)
+{
+ if (!_l) {
+ return;
+ }
+ QString n1 = _l->nodename();
+ trevTree::ConstIterator it = m_Tree.find(n1);
+ if (it==m_Tree.end()) {
+ return;
+ }
+ svn::Revision tr(it.data().rev);
+ QString tp = _basePath+it.data().name;
+ emit makeCat(tr,tp,it.data().name,tr,kapp->activeModalWidget());
+}
+
+void RevGraphView::makeDiffPrev(GraphTreeLabel*_l)
+{
+ if (!_l) return;
+ QString n1,n2;
+ n1 = _l->nodename();
+ n2 = _l->source();
+ makeDiff(n1,n2);
+}
+
+void RevGraphView::makeDiff(const QString&n1,const QString&n2)
+{
+ if (n1.isEmpty()||n2.isEmpty()) return;
+ trevTree::ConstIterator it;
+ it = m_Tree.find(n2);
+ if (it==m_Tree.end()) {
+ return;
+ }
+ svn::Revision sr(it.data().rev);
+ QString sp = _basePath+it.data().name;
+
+ it = m_Tree.find(n1);
+ if (it==m_Tree.end()) {
+ return;
+ }
+ svn::Revision tr(it.data().rev);
+ QString tp = _basePath+it.data().name;
+ if (Kdesvnsettings::tree_diff_rec()) {
+ emit makeRecDiff(sp,sr,tp,tr,kapp->activeModalWidget());
+ } else {
+ emit makeNorecDiff(sp,sr,tp,tr,kapp->activeModalWidget());
+ }
+}
+
+void RevGraphView::setBasePath(const QString&_path)
+{
+ _basePath = _path;
+}
+
+void RevGraphView::slotClientException(const QString&what)
+{
+ KMessageBox::sorry(KApplication::activeModalWidget(),what,i18n("SVN Error"));
+}
+
+#include "revgraphview.moc"