diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-03-15 17:32:48 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-03-15 17:32:48 +0000 |
commit | e2f541c98dfa4081fa3ab3d28f08ea2309281884 (patch) | |
tree | cb721a55bc88753ddeb9754dc98ef45e2850ce30 /src/svnfrontend/graphtree | |
download | tdesvn-e2f541c98dfa4081fa3ab3d28f08ea2309281884.tar.gz tdesvn-e2f541c98dfa4081fa3ab3d28f08ea2309281884.zip |
Added KDE3 version of kdesvn
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/kdesvn@1103685 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'src/svnfrontend/graphtree')
-rw-r--r-- | src/svnfrontend/graphtree/drawparams.cpp | 708 | ||||
-rw-r--r-- | src/svnfrontend/graphtree/drawparams.h | 200 | ||||
-rw-r--r-- | src/svnfrontend/graphtree/elogentry.cpp | 76 | ||||
-rw-r--r-- | src/svnfrontend/graphtree/elogentry.h | 37 | ||||
-rw-r--r-- | src/svnfrontend/graphtree/graphtree_defines.h | 31 | ||||
-rw-r--r-- | src/svnfrontend/graphtree/graphtreelabel.cpp | 220 | ||||
-rw-r--r-- | src/svnfrontend/graphtree/graphtreelabel.h | 89 | ||||
-rw-r--r-- | src/svnfrontend/graphtree/pannerview.cpp | 101 | ||||
-rw-r--r-- | src/svnfrontend/graphtree/pannerview.h | 53 | ||||
-rw-r--r-- | src/svnfrontend/graphtree/revgraphview.cpp | 950 | ||||
-rw-r--r-- | src/svnfrontend/graphtree/revgraphview.h | 151 | ||||
-rw-r--r-- | src/svnfrontend/graphtree/revisiontree.cpp | 544 | ||||
-rw-r--r-- | src/svnfrontend/graphtree/revisiontree.h | 75 | ||||
-rw-r--r-- | src/svnfrontend/graphtree/revtreewidget.cpp | 115 | ||||
-rw-r--r-- | src/svnfrontend/graphtree/revtreewidget.h | 74 |
15 files changed, 3424 insertions, 0 deletions
diff --git a/src/svnfrontend/graphtree/drawparams.cpp b/src/svnfrontend/graphtree/drawparams.cpp new file mode 100644 index 0000000..ced7bd4 --- /dev/null +++ b/src/svnfrontend/graphtree/drawparams.cpp @@ -0,0 +1,708 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002, 2003 Josef Weidendorfer <[email protected]> + Adapted for the needs of kdesvn by Rajko Albrecht <[email protected]> + + KCachegrind 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, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +/* + * A Widget for visualizing hierarchical metrics as areas. + * The API is similar to QListView. + */ + +#include <math.h> + +#include <qpainter.h> +#include <qtooltip.h> +#include <qregexp.h> +#include <qstyle.h> +#include <qpopupmenu.h> + +#include <klocale.h> +#include <kconfig.h> +#include <kdebug.h> + +#include "drawparams.h" + + +// set this to 1 to enable debug output +#define DEBUG_DRAWING 0 +#define MAX_FIELD 12 + + +// +// StoredDrawParams +// +StoredDrawParams::StoredDrawParams() +{ + _selected = false; + _current = false; + _shaded = true; + _rotated = false; + + _backColor = Qt::white; + + // field array has size 0 +} + +StoredDrawParams::StoredDrawParams(QColor c, + bool selected, bool current) +{ + _backColor = c; + + _selected = selected; + _current = current; + _shaded = true; + _rotated = false; + _drawFrame = true; + + // field array has size 0 +} + +QString StoredDrawParams::text(int f) const +{ + if ((f<0) || (f >= (int)_field.size())) + return QString::null; + + return _field[f].text; +} + +QPixmap StoredDrawParams::pixmap(int f) const +{ + if ((f<0) || (f >= (int)_field.size())) + return QPixmap(); + + return _field[f].pix; +} + +DrawParams::Position StoredDrawParams::position(int f) const +{ + if ((f<0) || (f >= (int)_field.size())) + return Default; + + return _field[f].pos; +} + +int StoredDrawParams::maxLines(int f) const +{ + if ((f<0) || (f >= (int)_field.size())) + return 0; + + return _field[f].maxLines; +} + +const QFont& StoredDrawParams::font() const +{ + static QFont* f = 0; + if (!f) f = new QFont(QApplication::font()); + + return *f; +} + +void StoredDrawParams::ensureField(int f) +{ + static Field* def = 0; + if (!def) { + def = new Field(); + def->pos = Default; + def->maxLines = 0; + } + + if (f<0 || f>=MAX_FIELD) return; + + if ((int)_field.size() < f+1) _field.resize(f+1, *def); +} + + +void StoredDrawParams::setField(int f, QString t, QPixmap pm, + Position p, int maxLines) +{ + if (f<0 || f>=MAX_FIELD) return; + ensureField(f); + + _field[f].text = t; + _field[f].pix = pm; + _field[f].pos = p; + _field[f].maxLines = maxLines; +} + +void StoredDrawParams::setText(int f, QString t) +{ + if (f<0 || f>=MAX_FIELD) return; + ensureField(f); + + _field[f].text = t; +} + +void StoredDrawParams::setPixmap(int f, QPixmap pm) +{ + if (f<0 || f>=MAX_FIELD) return; + ensureField(f); + + _field[f].pix = pm; +} + +void StoredDrawParams::setPosition(int f, Position p) +{ + if (f<0 || f>=MAX_FIELD) return; + ensureField(f); + + _field[f].pos = p; +} + +void StoredDrawParams::setMaxLines(int f, int m) +{ + if (f<0 || f>=MAX_FIELD) return; + ensureField(f); + + _field[f].maxLines = m; +} + + + +// +// RectDrawing +// + +RectDrawing::RectDrawing(QRect r) +{ + _fm = 0; + _dp = 0; + setRect(r); +} + + +RectDrawing::~RectDrawing() +{ + delete _fm; + delete _dp; +} + +DrawParams* RectDrawing::drawParams() +{ + if (!_dp) + _dp = new StoredDrawParams(); + + return _dp; +} + + +void RectDrawing::setDrawParams(DrawParams* dp) +{ + if (_dp) delete _dp; + _dp = dp; +} + +void RectDrawing::setRect(QRect r) +{ + _rect = r; + + _usedTopLeft = 0; + _usedTopCenter = 0; + _usedTopRight = 0; + _usedBottomLeft = 0; + _usedBottomCenter = 0; + _usedBottomRight = 0; + + _fontHeight = 0; +} + +QRect RectDrawing::remainingRect(DrawParams* dp) +{ + if (!dp) dp = drawParams(); + + if ((_usedTopLeft >0) || + (_usedTopCenter >0) || + (_usedTopRight >0)) { + if (dp->rotated()) + _rect.setLeft(_rect.left() + _fontHeight); + else + _rect.setTop(_rect.top() + _fontHeight); + } + + if ((_usedBottomLeft >0) || + (_usedBottomCenter >0) || + (_usedBottomRight >0)) { + if (dp->rotated()) + _rect.setRight(_rect.right() - _fontHeight); + else + _rect.setBottom(_rect.bottom() - _fontHeight); + } + return _rect; +} + + +void RectDrawing::drawBack(QPainter* p, DrawParams* dp) +{ + if (!dp) dp = drawParams(); + if (_rect.width()<=0 || _rect.height()<=0) return; + + QRect r = _rect; + QColor normal = dp->backColor(); + if (dp->selected()) normal = normal.light(); + bool isCurrent = dp->current(); + + if (dp->drawFrame() || isCurrent) { + // 3D raised/sunken frame effect... + QColor high = normal.light(); + QColor low = normal.dark(); + p->setPen( isCurrent ? low:high); + p->drawLine(r.left(), r.top(), r.right(), r.top()); + p->drawLine(r.left(), r.top(), r.left(), r.bottom()); + p->setPen( isCurrent ? high:low); + p->drawLine(r.right(), r.top(), r.right(), r.bottom()); + p->drawLine(r.left(), r.bottom(), r.right(), r.bottom()); + r.setRect(r.x()+1, r.y()+1, r.width()-2, r.height()-2); + } + if (r.width()<=0 || r.height()<=0) return; + + if (dp->shaded()) { + // some shading + bool goDark = qGray(normal.rgb())>128; + int rBase, gBase, bBase; + normal.rgb(&rBase, &gBase, &bBase); + p->setBrush(QBrush::NoBrush); + + // shade parameters: + int d = 7; + float factor = 0.1, forth=0.7, back1 =0.9, toBack2 = .7, back2 = 0.97; + + // coefficient corrections because of rectangle size + int s = r.width(); + if (s > r.height()) s = r.height(); + if (s<100) { + forth -= .3 * (100-s)/100; + back1 -= .2 * (100-s)/100; + back2 -= .02 * (100-s)/100; + } + + + // maximal color difference + int rDiff = goDark ? -rBase/d : (255-rBase)/d; + int gDiff = goDark ? -gBase/d : (255-gBase)/d; + int bDiff = goDark ? -bBase/d : (255-bBase)/d; + + QColor shadeColor; + while (factor<.95) { + shadeColor.setRgb((int)(rBase+factor*rDiff+.5), + (int)(gBase+factor*gDiff+.5), + (int)(bBase+factor*bDiff+.5)); + p->setPen(shadeColor); + p->drawRect(r); + r.setRect(r.x()+1, r.y()+1, r.width()-2, r.height()-2); + if (r.width()<=0 || r.height()<=0) return; + factor = 1.0 - ((1.0 - factor) * forth); + } + + // and back (1st half) + while (factor>toBack2) { + shadeColor.setRgb((int)(rBase+factor*rDiff+.5), + (int)(gBase+factor*gDiff+.5), + (int)(bBase+factor*bDiff+.5)); + p->setPen(shadeColor); + p->drawRect(r); + r.setRect(r.x()+1, r.y()+1, r.width()-2, r.height()-2); + if (r.width()<=0 || r.height()<=0) return; + factor = 1.0 - ((1.0 - factor) / back1); + } + + // and back (2nd half) + while ( factor>.01) { + shadeColor.setRgb((int)(rBase+factor*rDiff+.5), + (int)(gBase+factor*gDiff+.5), + (int)(bBase+factor*bDiff+.5)); + p->setPen(shadeColor); + p->drawRect(r); + r.setRect(r.x()+1, r.y()+1, r.width()-2, r.height()-2); + if (r.width()<=0 || r.height()<=0) return; + + factor = factor * back2; + } + } + + // fill inside + p->setPen(QPen::NoPen); + p->setBrush(normal); + p->drawRect(r); +} + + +bool RectDrawing::drawField(QPainter* p, int f, DrawParams* dp) +{ + if (!dp) dp = drawParams(); + + if (!_fm) { + _fm = new QFontMetrics(dp->font()); + _fontHeight = _fm->height(); + } + + QRect r = _rect; + + if (0) kdDebug(90100) << "DrawField: Rect " << r.x() << "/" << r.y() + << " - " << r.width() << "x" << r.height() << endl; + + int h = _fontHeight; + bool rotate = dp->rotated(); + int width = (rotate ? r.height() : r.width()) -4; + int height = (rotate ? r.width() : r.height()); + int lines = height / h; + + // stop if we have no space available + if (lines<1) return false; + + // calculate free space in first line (<unused>) + int pos = dp->position(f); + if (pos == DrawParams::Default) { + switch(f%4) { + case 0: pos = DrawParams::TopLeft; break; + case 1: pos = DrawParams::TopRight; break; + case 2: pos = DrawParams::BottomRight; break; + case 3: pos = DrawParams::BottomLeft; break; + } + } + + int unused = 0; + bool isBottom = false; + bool isCenter = false; + bool isRight = false; + int* used = 0; + switch(pos) { + case DrawParams::TopLeft: + used = &_usedTopLeft; + if (_usedTopLeft == 0) { + if (_usedTopCenter) + unused = (width - _usedTopCenter)/2; + else + unused = width - _usedTopRight; + } + break; + + case DrawParams::TopCenter: + isCenter = true; + used = &_usedTopCenter; + if (_usedTopCenter == 0) { + if (_usedTopLeft > _usedTopRight) + unused = width - 2 * _usedTopLeft; + else + unused = width - 2 * _usedTopRight; + } + break; + + case DrawParams::TopRight: + isRight = true; + used = &_usedTopRight; + if (_usedTopRight == 0) { + if (_usedTopCenter) + unused = (width - _usedTopCenter)/2; + else + unused = width - _usedTopLeft; + } + break; + + case DrawParams::BottomLeft: + isBottom = true; + used = &_usedBottomLeft; + if (_usedBottomLeft == 0) { + if (_usedBottomCenter) + unused = (width - _usedBottomCenter)/2; + else + unused = width - _usedBottomRight; + } + break; + + case DrawParams::BottomCenter: + isCenter = true; + isBottom = true; + used = &_usedBottomCenter; + if (_usedBottomCenter == 0) { + if (_usedBottomLeft > _usedBottomRight) + unused = width - 2 * _usedBottomLeft; + else + unused = width - 2 * _usedBottomRight; + } + break; + + case DrawParams::BottomRight: + isRight = true; + isBottom = true; + used = &_usedBottomRight; + if (_usedBottomRight == 0) { + if (_usedBottomCenter) + unused = (width - _usedBottomCenter)/2; + else + unused = width - _usedBottomLeft; + } + break; + } + + if (isBottom) { + if ((_usedTopLeft >0) || + (_usedTopCenter >0) || + (_usedTopRight >0)) + lines--; + } + else if (!isBottom) { + if ((_usedBottomLeft >0) || + (_usedBottomCenter >0) || + (_usedBottomRight >0)) + lines--; + } + if (lines<1) return false; + + + int y = isBottom ? height - h : 0; + + if (unused < 0) unused = 0; + if (unused == 0) { + // no space available in last line at this position + y = isBottom ? (y-h) : (y+h); + lines--; + + if (lines<1) return false; + + // new line: reset used space + if (isBottom) + _usedBottomLeft = _usedBottomCenter = _usedBottomRight = 0; + else + _usedTopLeft = _usedTopCenter = _usedTopRight = 0; + + unused = width; + } + + // stop as soon as possible when there's no space for "..." + static int dotW = 0; + if (!dotW) dotW = _fm->width("..."); + if (width < dotW) return false; + + // get text and pixmap now, only if we need to, because it is possible + // that they are calculated on demand (and this can take some time) + QString name = dp->text(f); + if (name.isEmpty()) return 0; + QPixmap pix = dp->pixmap(f); + + // check if pixmap can be drawn + int pixW = pix.width(); + int pixH = pix.height(); + int pixY = 0; + bool pixDrawn = true; + if (pixW>0) { + pixW += 2; // X distance from pix + if ((width < pixW + dotW) || (height < pixH)) { + // don't draw + pixW = 0; + } + else + pixDrawn = false; + } + + // width of text and pixmap to be drawn + int w = pixW + _fm->width(name); + + if (0) kdDebug(90100) << " For '" << name << "': Unused " << unused + << ", StrW " << w << ", Width " << width << endl; + + // if we have limited space at 1st line: + // use it only if whole name does fit in last line... + if ((unused < width) && (w > unused)) { + y = isBottom ? (y-h) : (y+h); + lines--; + + if (lines<1) return false; + + // new line: reset used space + if (isBottom) + _usedBottomLeft = _usedBottomCenter = _usedBottomRight = 0; + else + _usedTopLeft = _usedTopCenter = _usedTopRight = 0; + } + + p->save(); + p->setPen( (qGray(dp->backColor().rgb())>100) ? Qt::black : Qt::white); + p->setFont(dp->font()); + if (rotate) { + //p->translate(r.x()+2, r.y()+r.height()); + p->translate(r.x(), r.y()+r.height()-2); + p->rotate(270); + } + else + p->translate(r.x()+2, r.y()); + + + // adjust available lines according to maxLines + int max = dp->maxLines(f); + if ((max > 0) && (lines>max)) lines = max; + + /* loop over name parts to break up string depending on available width. + * every char category change is supposed a possible break, + * with the exception Uppercase=>Lowercase. + * It's good enough for numbers, Symbols... + * + * If the text is to be written at the bottom, we start with the + * end of the string (so everything is reverted) + */ + QString remaining; + int origLines = lines; + while (lines>0) { + + if (w>width && lines>1) { + int lastBreakPos = name.length(), lastWidth = w; + int len = name.length(); + QChar::Category caOld, ca; + + if (!isBottom) { + // start with comparing categories of last 2 chars + caOld = name[len-1].category(); + while (len>2) { + len--; + ca = name[len-1].category(); + if (ca != caOld) { + // "Aa" has no break between... + if (ca == QChar::Letter_Uppercase && + caOld == QChar::Letter_Lowercase) { + caOld = ca; + continue; + } + caOld = ca; + lastBreakPos = len; + w = pixW + _fm->width(name, len); + lastWidth = w; + if (w <= width) break; + } + } + w = lastWidth; + remaining = name.mid(lastBreakPos); + // remove space on break point + if (name[lastBreakPos-1].category() == QChar::Separator_Space) + name = name.left(lastBreakPos-1); + else + name = name.left(lastBreakPos); + } + else { // bottom + int l = len; + caOld = name[l-len].category(); + while (len>2) { + len--; + ca = name[l-len].category(); + + if (ca != caOld) { + // "Aa" has no break between... + if (caOld == QChar::Letter_Uppercase && + ca == QChar::Letter_Lowercase) { + caOld = ca; + continue; + } + caOld = ca; + lastBreakPos = len; + w = pixW + _fm->width(name.right(len)); + lastWidth = w; + if (w <= width) break; + } + } + w = lastWidth; + remaining = name.left(l-lastBreakPos); + // remove space on break point + if (name[l-lastBreakPos].category() == QChar::Separator_Space) + name = name.right(lastBreakPos-1); + else + name = name.right(lastBreakPos); + } + } + else + remaining = QString::null; + + /* truncate and add ... if needed */ + if (w>width) { + int len = name.length(); + w += dotW; + while (len>2 && (w > width)) { + len--; + w = pixW + _fm->width(name, len) + dotW; + } + // stop drawing: we cannot draw 2 chars + "..." + if (w>width) break; + + name = name.left(len) + "..."; + } + + int x = 0; + if (isCenter) + x = (width - w)/2; + else if (isRight) + x = width - w; + + if (!pixDrawn) { + pixY = y+(h-pixH)/2; // default: center vertically + if (pixH > h) pixY = isBottom ? y-(pixH-h) : y; + + p->drawPixmap( x, pixY, pix); + + // for distance to next text + pixY = isBottom ? (pixY - h - 2) : (pixY + pixH + 2); + pixDrawn = true; + } + + + if (0) kdDebug(90100) << " Drawing '" << name << "' at " + << x+pixW << "/" << y << endl; + + p->drawText( x+pixW, y, + width - pixW, h, + Qt::AlignLeft, name); + y = isBottom ? (y-h) : (y+h); + lines--; + + if (remaining.isEmpty()) break; + name = remaining; + w = pixW + _fm->width(name); + } + + // make sure the pix stays visible + if (pixDrawn && (pixY>0)) { + if (isBottom && (pixY<y)) y = pixY; + if (!isBottom && (pixY>y)) y = pixY; + } + + if (origLines > lines) { + // if only 1 line written, don't reset _used* vars + if (lines - origLines >1) { + if (isBottom) + _usedBottomLeft = _usedBottomCenter = _usedBottomRight = 0; + else + _usedTopLeft = _usedTopCenter = _usedTopRight = 0; + } + + // take back one line + y = isBottom ? (y+h) : (y-h); + if (used) *used = w; + } + + // update free space + if (!isBottom) { + if (rotate) + _rect.setRect(r.x()+y, r.y(), r.width()-y, r.height()); + else + _rect.setRect(r.x(), r.y()+y, r.width(), r.height()-y); + } + else { + if (rotate) + _rect.setRect(r.x(), r.y(), y+h, r.height()); + else + _rect.setRect(r.x(), r.y(), r.width(), y+h); + } + + p->restore(); + + return true; +} diff --git a/src/svnfrontend/graphtree/drawparams.h b/src/svnfrontend/graphtree/drawparams.h new file mode 100644 index 0000000..4898111 --- /dev/null +++ b/src/svnfrontend/graphtree/drawparams.h @@ -0,0 +1,200 @@ +/* This file is part of KCachegrind. + Copyright (C) 2002, 2003 Josef Weidendorfer <[email protected]> + Adapted for the needs of kdesvn by Rajko Albrecht <[email protected]> + + KCachegrind 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, version 2. + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +/** + * A Widget for visualizing hierarchical metrics as areas. + * The API is similar to QListView. + * + * This file defines the following classes: + * DrawParams, RectDrawing, TreeMapItem, TreeMapWidget + * + * DrawParams/RectDrawing allows reusing of TreeMap drawing + * functions in other widgets. + */ + +#ifndef DRAWPARAMS_H +#define DRAWPARAMS_H + +#include <qstring.h> +#include <qwidget.h> +#include <qpixmap.h> +#include <qptrlist.h> +#include <qvaluevector.h> +#include <qcolor.h> +#include <qapplication.h> +#include <qstringlist.h> + +class QPopupMenu; +class QString; + +class KConfigGroup; + + +/** + * Drawing parameters for an object. + * A Helper Interface for RectDrawing. + */ +class DrawParams +{ +public: + /** + * Positions for drawing into a rectangle. + * + * The specified position assumes no rotation. + * If there is more than one text for one position, it is put + * nearer to the center of the item. + * + * Drawing at top positions cuts free space from top, + * drawing at bottom positions cuts from bottom. + * Default usually gives positions clockwise according to field number. + */ + enum Position { TopLeft, TopCenter, TopRight, + BottomLeft, BottomCenter, BottomRight, + Default, Unknown}; + + // no constructor as this is an abstract class + virtual ~DrawParams() {} + + virtual QString text(int) const = 0; + virtual QPixmap pixmap(int) const = 0; + virtual Position position(int) const = 0; + // 0: no limit, negative: leave at least -maxLines() free + virtual int maxLines(int) const { return 0; } + virtual int fieldCount() const { return 0; } + + virtual QColor backColor() const { return Qt::white; } + virtual const QFont& font() const = 0; + + virtual bool selected() const { return false; } + virtual bool current() const { return false; } + virtual bool shaded() const { return true; } + virtual bool rotated() const { return false; } + virtual bool drawFrame() const { return true; } +}; + + +/* + * DrawParam with attributes stored + */ +class StoredDrawParams: public DrawParams +{ +public: + StoredDrawParams(); + StoredDrawParams(QColor c, + bool selected = false, bool current = false); + + // getters + QString text(int) const; + QPixmap pixmap(int) const; + Position position(int) const; + int maxLines(int) const; + int fieldCount() const { return _field.size(); } + + QColor backColor() const { return _backColor; } + bool selected() const { return _selected; } + bool current() const { return _current; } + bool shaded() const { return _shaded; } + bool rotated() const { return _rotated; } + bool drawFrame() const { return _drawFrame; } + + const QFont& font() const; + + // attribute setters + void setField(int f, QString t, QPixmap pm = QPixmap(), + Position p = Default, int maxLines = 0); + void setText(int f, QString); + void setPixmap(int f, QPixmap); + void setPosition(int f, Position); + void setMaxLines(int f, int); + void setBackColor(QColor c) { _backColor = c; } + void setSelected(bool b) { _selected = b; } + void setCurrent(bool b) { _current = b; } + void setShaded(bool b) { _shaded = b; } + void setRotated(bool b) { _rotated = b; } + void drawFrame(bool b) { _drawFrame = b; } + +protected: + QColor _backColor; + bool _selected :1; + bool _current :1; + bool _shaded :1; + bool _rotated :1; + bool _drawFrame :1; + +private: + // resize field array if needed to allow to access field <f> + void ensureField(int f); + + struct Field { + QString text; + QPixmap pix; + Position pos; + int maxLines; + }; + + QValueVector<Field> _field; +}; + + +/* State for drawing on a rectangle. + * + * Following drawing functions are provided: + * - background drawing with shading and 3D frame + * - successive pixmap/text drawing at various positions with wrap-around + * optimized for minimal space usage (e.g. if a text is drawn at top right + * after text on top left, the same line is used if space allows) + * + */ +class RectDrawing +{ +public: + RectDrawing(QRect); + ~RectDrawing(); + + // The default DrawParams object used. + DrawParams* drawParams(); + // we take control over the given object (i.e. delete at destruction) + void setDrawParams(DrawParams*); + + // draw on a given QPainter, use this class as info provider per default + void drawBack(QPainter*, DrawParams* dp = 0); + /* Draw field at position() from pixmap()/text() with maxLines(). + * Returns true if something was drawn + */ + bool drawField(QPainter*, int f, DrawParams* dp = 0); + + // resets rectangle for free space + void setRect(QRect); + + // Returns the rectangle area still free of text/pixmaps after + // a number of drawText() calls. + QRect remainingRect(DrawParams* dp = 0); + +private: + int _usedTopLeft, _usedTopCenter, _usedTopRight; + int _usedBottomLeft, _usedBottomCenter, _usedBottomRight; + QRect _rect; + + // temporary + int _fontHeight; + QFontMetrics* _fm; + DrawParams* _dp; +}; + +#endif diff --git a/src/svnfrontend/graphtree/elogentry.cpp b/src/svnfrontend/graphtree/elogentry.cpp new file mode 100644 index 0000000..924332c --- /dev/null +++ b/src/svnfrontend/graphtree/elogentry.cpp @@ -0,0 +1,76 @@ +/*************************************************************************** + * Copyright (C) 2005-2007 by Rajko Albrecht * + * [email protected] * + * * + * 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 "elogentry.h" + +#include <kdebug.h> + +eLog_Entry::eLog_Entry(const svn::LogEntry&old) + : svn::LogEntry(old) +{ +} + +eLog_Entry::eLog_Entry() + : svn::LogEntry() +{ +} + +eLog_Entry::~eLog_Entry() +{ +} + +void eLog_Entry::addCopyTo(const QString¤t,const QString&target, + svn_revnum_t target_rev,char _action,svn_revnum_t from_rev) +{ + svn::LogChangePathEntry _entry; + _entry.copyToPath=target; + _entry.path = current; + _entry.copyToRevision = target_rev; + _entry.action=_action; + _entry.copyFromRevision = from_rev; + switch (_action) { + case 'A': + if (!target.isEmpty()) { + //kdDebug()<<"Adding a history "<< current << " -> " << target << endl; + _entry.action = 'H'; + }else{ + } + break; + case 'D': + break; + case 'R': +#if 0 + if (!target.isEmpty()) { + kdDebug()<<"Adding a rename "<< current << " -> " << target << endl; + } +#endif + break; + case 'M': + break; + default: + break; + } + /* make sure that ALL writing operations are BEFORE deletion of item, + * otherwise search will fail */ + if (_action=='D') { + changedPaths.push_back(_entry); + } else { + changedPaths.push_front(_entry); + } +} diff --git a/src/svnfrontend/graphtree/elogentry.h b/src/svnfrontend/graphtree/elogentry.h new file mode 100644 index 0000000..0fa63ef --- /dev/null +++ b/src/svnfrontend/graphtree/elogentry.h @@ -0,0 +1,37 @@ +/*************************************************************************** + * Copyright (C) 2005-2007 by Rajko Albrecht * + * [email protected] * + * * + * 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. * + ***************************************************************************/ +#ifndef ELOGENTRY_H +#define ELOGENTRY_H + +#include <svnqt/log_entry.hpp> + +/** + @author Rajko Albrecht <[email protected]> +*/ +struct eLog_Entry : public svn::LogEntry +{ + eLog_Entry(); + eLog_Entry(const svn::LogEntry&); + ~eLog_Entry(); + + void addCopyTo(const QString&,const QString&,svn_revnum_t,char _action,svn_revnum_t fromRev=-1); + void addAction(const QString&,char _action); +}; +#endif diff --git a/src/svnfrontend/graphtree/graphtree_defines.h b/src/svnfrontend/graphtree/graphtree_defines.h new file mode 100644 index 0000000..596cb7d --- /dev/null +++ b/src/svnfrontend/graphtree/graphtree_defines.h @@ -0,0 +1,31 @@ +/*************************************************************************** + * Copyright (C) 2006 by Rajko Albrecht * + * [email protected] * + * * + * 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. * + ***************************************************************************/ + +#ifndef __GRAPHTREE_DEFINES +#define __GRAPHTREE_DEFINES + +enum { + GRAPHTREE_LABEL = 1100, + GRAPHTREE_LINE, + GRAPHTREE_ARROW, + GRAPHTREE_MARK +}; + +#endif diff --git a/src/svnfrontend/graphtree/graphtreelabel.cpp b/src/svnfrontend/graphtree/graphtreelabel.cpp new file mode 100644 index 0000000..9770fef --- /dev/null +++ b/src/svnfrontend/graphtree/graphtreelabel.cpp @@ -0,0 +1,220 @@ +/*************************************************************************** + * Copyright (C) 2006-2007 by Rajko Albrecht * + * [email protected] * + * * + * 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 "graphtreelabel.h" +#include "graphtree_defines.h" +#include <qpainter.h> + +GraphTreeLabel::GraphTreeLabel(const QString&text, const QString&_nodename,const QRect&r,QCanvas*c) + : QCanvasRectangle(r,c),StoredDrawParams() +{ + m_Nodename = _nodename; + m_SourceNode = QString::null; + setText(0,text); + setPosition(0, DrawParams::TopCenter); + drawFrame(true); +} + +GraphTreeLabel::~GraphTreeLabel() +{ +} + +const QString&GraphTreeLabel::nodename()const +{ + return m_Nodename; +} + +int GraphTreeLabel::rtti()const +{ + return GRAPHTREE_LABEL; +} + +void GraphTreeLabel::setBgColor(const QColor&c) +{ + _backColor=c; +} + +void GraphTreeLabel::drawShape(QPainter& p) +{ + QRect r = rect(); +/* + p.setPen(blue); + p.drawRect(r); +*/ + RectDrawing d(r); + d.drawBack(&p,this); + d.drawField(&p, 0, this); + d.drawField(&p, 1, this); +} + +void GraphTreeLabel::setSelected(bool s) +{ + QCanvasRectangle::setSelected(s); + StoredDrawParams::setSelected(s); + update(); +} + +const QString&GraphTreeLabel::source()const +{ + return m_SourceNode; +} + +void GraphTreeLabel::setSource(const QString&_s) +{ + m_SourceNode=_s; +} + +GraphEdge::GraphEdge(QCanvas*c) + : QCanvasSpline(c) +{ +} + +GraphEdge::~GraphEdge() +{ +} + +void GraphEdge::drawShape(QPainter& p) +{ + p.drawPolyline(poly); +} + +QPointArray GraphEdge::areaPoints() const +{ + int minX = poly[0].x(), minY = poly[0].y(); + int maxX = minX, maxY = minY; + int i; + + if (0) qDebug("GraphEdge::areaPoints\n P 0: %d/%d", minX, minY); + int len = poly.count(); + for (i=1;i<len;i++) { + if (poly[i].x() < minX) minX = poly[i].x(); + if (poly[i].y() < minY) minY = poly[i].y(); + if (poly[i].x() > maxX) maxX = poly[i].x(); + if (poly[i].y() > maxY) maxY = poly[i].y(); + if (0) qDebug(" P %d: %d/%d", i, poly[i].x(), poly[i].y()); + } + QPointArray a = poly.copy(), b = poly.copy(); + if (minX == maxX) { + a.translate(-2, 0); + b.translate(2, 0); + } + else { + a.translate(0, -2); + b.translate(0, 2); + } + a.resize(2*len); + for (i=0;i<len;i++) + a[2 * len - 1 -i] = b[i]; + + if (0) { + qDebug(" Result:"); + for (i=0;i<2*len;i++) + qDebug(" P %d: %d/%d", i, a[i].x(), a[i].y()); + } + + return a; + +} + +int GraphEdge::rtti()const +{ + return GRAPHTREE_LINE; +} + +GraphEdgeArrow::GraphEdgeArrow(GraphEdge*_parent,QCanvas*c) + : QCanvasPolygon(c),_edge(_parent) +{ +} + +void GraphEdgeArrow::drawShape(QPainter&p) +{ + QCanvasPolygon::drawShape(p); +} + +int GraphEdgeArrow::rtti()const +{ + return GRAPHTREE_ARROW; +} + +GraphEdge*GraphEdgeArrow::edge() +{ + return _edge; +} + +/* taken from KCacheGrind project */ +QPixmap*GraphMark::_p=0; + +GraphMark::GraphMark(GraphTreeLabel*n,QCanvas*c) + : QCanvasRectangle(c) +{ + if (!_p) { + + int d = 5; + float v1 = 130.0, v2 = 10.0, v = v1, f = 1.03; + + // calculate pix size + QRect r(0, 0, 30, 30); + while (v>v2) { + r.setRect(r.x()-d, r.y()-d, r.width()+2*d, r.height()+2*d); + v /= f; + } + + _p = new QPixmap(r.size()); + _p->fill(Qt::white); + QPainter p(_p); + p.setPen(Qt::NoPen); + + r.moveBy(-r.x(), -r.y()); + + while (v<v1) { + v *= f; + p.setBrush(QColor(265-(int)v, 265-(int)v, 265-(int)v)); + + p.drawRect(QRect(r.x(), r.y(), r.width(), d)); + p.drawRect(QRect(r.x(), r.bottom()-d, r.width(), d)); + p.drawRect(QRect(r.x(), r.y()+d, d, r.height()-2*d)); + p.drawRect(QRect(r.right()-d, r.y()+d, d, r.height()-2*d)); + + r.setRect(r.x()+d, r.y()+d, r.width()-2*d, r.height()-2*d); + } + } + + setSize(_p->width(), _p->height()); + move(n->rect().center().x()-_p->width()/2, + n->rect().center().y()-_p->height()/2); +} + +GraphMark::~ GraphMark() +{ +} + +bool GraphMark::hit(const QPoint&)const +{ + return false; +} + +int GraphMark::rtti()const +{ + return GRAPHTREE_MARK; +} + +void GraphMark::drawShape(QPainter&p) +{ + p.drawPixmap( int(x()), int(y()), *_p ); +} diff --git a/src/svnfrontend/graphtree/graphtreelabel.h b/src/svnfrontend/graphtree/graphtreelabel.h new file mode 100644 index 0000000..dec788f --- /dev/null +++ b/src/svnfrontend/graphtree/graphtreelabel.h @@ -0,0 +1,89 @@ +/*************************************************************************** + * Copyright (C) 2006-2007 by Rajko Albrecht * + * [email protected] * + * * + * 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. * + ***************************************************************************/ +#ifndef GRAPHTREELABEL_H +#define GRAPHTREELABEL_H + +#include "graphtree/drawparams.h" +#include <qcanvas.h> + +/** + @author Rajko Albrecht <[email protected]> +*/ +class GraphTreeLabel : public QCanvasRectangle,StoredDrawParams +{ +public: + GraphTreeLabel(const QString&,const QString&,const QRect&r,QCanvas*c); + virtual ~GraphTreeLabel(); + + virtual int rtti()const; + virtual void drawShape(QPainter& p); + + void setBgColor(const QColor&); + + const QString&nodename()const; + const QString&source()const; + void setSource(const QString&); + virtual void setSelected(bool); + +protected: + QString m_Nodename; + QString m_SourceNode; +}; + +class GraphEdge; + +class GraphEdgeArrow:public QCanvasPolygon +{ +public: + GraphEdgeArrow(GraphEdge*,QCanvas*); + GraphEdge*edge(); + virtual void drawShape(QPainter&); + virtual int rtti()const; + +private: + GraphEdge*_edge; +}; + +/* line */ +class GraphEdge:public QCanvasSpline +{ +public: + GraphEdge(QCanvas*); + virtual ~GraphEdge(); + + virtual void drawShape(QPainter&); + QPointArray areaPoints() const; + virtual int rtti()const; +}; + +class GraphMark:public QCanvasRectangle +{ +public: + GraphMark(GraphTreeLabel*,QCanvas*); + virtual ~GraphMark(); + virtual int rtti()const; + virtual bool hit(const QPoint&)const; + + virtual void drawShape(QPainter&); +private: + static QPixmap*_p; +}; + +#endif diff --git a/src/svnfrontend/graphtree/pannerview.cpp b/src/svnfrontend/graphtree/pannerview.cpp new file mode 100644 index 0000000..5f14e82 --- /dev/null +++ b/src/svnfrontend/graphtree/pannerview.cpp @@ -0,0 +1,101 @@ +/*************************************************************************** + * Copyright (C) 2006-2007 by Rajko Albrecht * + * [email protected] * + * * + * 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 "pannerview.h" +#include <qpainter.h> + +PannerView::PannerView(QWidget* parent, const char* name) + : QCanvasView(parent, name,WNoAutoErase | WStaticContents ) +{ + m_Moving = false; + viewport()->setBackgroundMode(Qt::NoBackground); + setBackgroundMode(Qt::NoBackground); +} + +PannerView::~PannerView() +{ +} + +void PannerView::drawContents(QPainter* p, int clipx, int clipy, int clipw, int cliph) +{ + p->save(); + QCanvasView::drawContents(p,clipx,clipy,clipw,cliph); + p->restore(); + if (m_ZoomRect.isValid()) { + p->setPen(red.dark()); + p->drawRect(m_ZoomRect); + p->setPen( red); + p->drawRect(QRect(m_ZoomRect.x()+1, m_ZoomRect.y()+1, + m_ZoomRect.width()-2, m_ZoomRect.height()-2)); + } +} + +void PannerView::setZoomRect(const QRect& theValue) +{ + QRect oldRect = m_ZoomRect; + m_ZoomRect = theValue; + updateContents(oldRect); + updateContents(m_ZoomRect); +} + +/*! + \fn PannerView::contentsMouseMoveEvent(QMouseEvent* e) + */ +void PannerView::contentsMouseMoveEvent(QMouseEvent* e) +{ + if (m_Moving) { + emit zoomRectMoved(e->pos().x() - m_LastPos.x(), e->pos().y() - m_LastPos.y()); + m_LastPos = e->pos(); + } +} + +/*! + \fn PannerView::contentsMousePressEvent(QMouseEvent* e) + */ +void PannerView::contentsMousePressEvent(QMouseEvent* e) +{ + if (m_ZoomRect.isValid()) { + if (!m_ZoomRect.contains(e->pos())) { + emit zoomRectMoved(e->pos().x() - m_ZoomRect.center().x(), + e->pos().y() - m_ZoomRect.center().y()); + } + m_Moving = true; + m_LastPos = e->pos(); + } +} + +/*! + \fn PannerView::contentsMouseReleaseEvent(QMouseEvent*) + */ +void PannerView::contentsMouseReleaseEvent(QMouseEvent*) +{ + m_Moving = false; + emit zoomRectMoveFinished(); +} + +/*! + \fn PannerView::updateCurrentRect() + */ +void PannerView::updateCurrentRect() +{ + if (m_ZoomRect.isValid()) updateContents(m_ZoomRect); +} + +#include "pannerview.moc" + diff --git a/src/svnfrontend/graphtree/pannerview.h b/src/svnfrontend/graphtree/pannerview.h new file mode 100644 index 0000000..6c1a310 --- /dev/null +++ b/src/svnfrontend/graphtree/pannerview.h @@ -0,0 +1,53 @@ +/*************************************************************************** + * Copyright (C) 2006-2007 by Rajko Albrecht * + * [email protected] * + * * + * 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. * + ***************************************************************************/ +#ifndef PANNERVIEW_H +#define PANNERVIEW_H + +#include <qcanvas.h> + +/** + @author Rajko Albrecht <[email protected]> +*/ +class PannerView : public QCanvasView +{ +Q_OBJECT +public: + PannerView(QWidget* parent=0, const char* name=0); + virtual ~PannerView(); + + void setZoomRect(const QRect& theValue); + void updateCurrentRect(); + +signals: + void zoomRectMoved(int dx, int dy); + void zoomRectMoveFinished(); + +protected: + virtual void drawContents(QPainter* p, int clipx, int clipy, int clipw, int cliph); + virtual void contentsMouseMoveEvent(QMouseEvent* e); + virtual void contentsMousePressEvent(QMouseEvent* e); + virtual void contentsMouseReleaseEvent(QMouseEvent*); +protected: + QRect m_ZoomRect; + bool m_Moving; + QPoint m_LastPos; +}; + +#endif 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 * + * [email protected] * + * * + * 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" diff --git a/src/svnfrontend/graphtree/revgraphview.h b/src/svnfrontend/graphtree/revgraphview.h new file mode 100644 index 0000000..039bc4b --- /dev/null +++ b/src/svnfrontend/graphtree/revgraphview.h @@ -0,0 +1,151 @@ +/*************************************************************************** + * Copyright (C) 2006-2007 by Rajko Albrecht * + * [email protected] * + * * + * 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. * + ***************************************************************************/ +#ifndef REVGRAPHVIEW_H +#define REVGRAPHVIEW_H + +#include <svnqt/revision.hpp> + +#include <qcanvas.h> + +namespace svn { + class LogEntry; + class Client; +} + +class KTempFile; +class KProcess; +class RevisionTree; +class GraphTreeLabel; +class GraphViewTip; +class GraphMark; +class PannerView; +class CContextListener; + +/** + @author Rajko Albrecht <[email protected]> +*/ +class RevGraphView : public QCanvasView +{ + Q_OBJECT +public: + enum ZoomPosition { TopLeft, TopRight, BottomLeft, BottomRight, Auto }; + /* avoid large copy operations */ + friend class RevisionTree; + + RevGraphView(QObject*,svn::Client*,QWidget * parent = 0, const char * name = 0, WFlags f = 0); + virtual ~RevGraphView(); + + void showText(const QString&s); + void clear(); + + void beginInsert(); + void endInsert(); + + struct targetData { + char Action; + QString key; + targetData(const QString&n,char _a) + { + key = n; + Action = _a; + } + targetData(){Action=0;key="";} + }; + typedef QValueList<targetData> tlist; + + struct keyData { + QString name,Author,Date,Message; + long rev; + char Action; + tlist targets; + }; + + typedef QMap<QString,keyData> trevTree; + + QString toolTip(const QString&nodename,bool full=false)const; + + void setBasePath(const QString&); + void dumpRevtree(); + +signals: + void dispDetails(const QString&); + void makeCat(const svn::Revision&,const QString&,const QString&,const svn::Revision&,QWidget*); + void makeNorecDiff(const QString&,const svn::Revision&,const QString&,const svn::Revision&,QWidget*); + void makeRecDiff(const QString&,const svn::Revision&,const QString&,const svn::Revision&,QWidget*); + +public slots: + virtual void contentsMovingSlot(int,int); + virtual void zoomRectMoved(int,int); + virtual void zoomRectMoveFinished(); + virtual void slotClientException(const QString&what); + +protected slots: + virtual void readDotOutput(KProcess * proc,char * buffer,int buflen); + virtual void dotExit(KProcess*); + +protected: + QCanvas*m_Canvas; + GraphMark*m_Marker; + svn::Client*m_Client; + GraphTreeLabel*m_Selected; + QObject*m_Listener; + KTempFile*dotTmpFile; + QString dotOutput; + KProcess*renderProcess; + trevTree m_Tree; + QColor getBgColor(const QString&nodeName)const; + bool isStart(const QString&nodeName)const; + char getAction(const QString&)const; + const QString&getLabelstring(const QString&nodeName); + + QMap<QString,GraphTreeLabel*> m_NodeList; + QMap<QString,QString> m_LabelMap; + + int _xMargin,_yMargin; + GraphViewTip*m_Tip; + PannerView*m_CompleteView; + double _cvZoom; + ZoomPosition m_LastAutoPosition; + + virtual void resizeEvent(QResizeEvent*); + virtual void contentsMousePressEvent ( QMouseEvent * e ); + virtual void contentsMouseReleaseEvent ( QMouseEvent * e ); + virtual void contentsMouseMoveEvent ( QMouseEvent*e); + virtual void contentsContextMenuEvent(QContextMenuEvent*e); + virtual void contentsMouseDoubleClickEvent ( QMouseEvent * e ); + + bool _isMoving; + QPoint _lastPos; + + bool _noUpdateZoomerPos; + + QString _basePath; + +private: + void updateSizes(QSize s = QSize(0,0)); + void updateZoomerPos(); + void setNewDirection(int dir); + void makeDiffPrev(GraphTreeLabel*); + void makeDiff(const QString&,const QString&); + void makeSelected(GraphTreeLabel*); + void makeCat(GraphTreeLabel*_l); +}; + +#endif diff --git a/src/svnfrontend/graphtree/revisiontree.cpp b/src/svnfrontend/graphtree/revisiontree.cpp new file mode 100644 index 0000000..bb64cf7 --- /dev/null +++ b/src/svnfrontend/graphtree/revisiontree.cpp @@ -0,0 +1,544 @@ +/*************************************************************************** + * Copyright (C) 2005-2007 by Rajko Albrecht * + * [email protected] * + * * + * 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 "revisiontree.h" +#include "../stopdlg.h" +#include "src/svnqt/log_entry.hpp" +#include "src/svnqt/cache/LogCache.hpp" +#include "src/svnqt/cache/ReposLog.hpp" +#include "src/svnqt/url.hpp" +#include "helpers/sub2qt.h" +#include "revtreewidget.h" +#include "revgraphview.h" +#include "elogentry.h" +#include "src/svnfrontend/fronthelpers/cursorstack.h" +#include "src/settings/kdesvnsettings.h" + +#include <kdebug.h> +#include <kprogress.h> +#include <klocale.h> +#include <kapp.h> +#include <klistview.h> +#include <kmdcodec.h> +#include <kmessagebox.h> + +#include <qwidget.h> +#include <qdatetime.h> +#include <qlabel.h> +#include <qfile.h> +#include <qtextstream.h> +#include <qregexp.h> + +#define INTERNALCOPY 1 +#define INTERNALRENAME 2 + +class RtreeData +{ +public: + RtreeData(); + virtual ~RtreeData(); + + QMap<long,eLog_Entry> m_History; + + svn::LogEntriesMap m_OldHistory; + + long max_rev,min_rev; + KProgressDialog*progress; + QTime m_stopTick; + + QWidget*dlgParent; + RevTreeWidget*m_TreeDisplay; + + svn::Client*m_Client; + QObject* m_Listener; + + bool getLogs(const QString&,const svn::Revision&startr,const svn::Revision&endr,const QString&origin); +}; + +RtreeData::RtreeData() + : max_rev(-1),min_rev(-1) +{ + progress=0; + m_TreeDisplay = 0; + m_Client = 0; + dlgParent = 0; + m_Listener = 0; +} + +RtreeData::~RtreeData() +{ + delete progress; +} + +bool RtreeData::getLogs(const QString&reposRoot,const svn::Revision&startr,const svn::Revision&endr,const QString&origin) +{ + if (!m_Listener||!m_Client) { + return false; + } + try { + CursorStack a(Qt::BusyCursor); + StopDlg sdlg(m_Listener,dlgParent, + 0,"Logs",i18n("Getting logs - hit cancel for abort")); + if (svn::Url::isLocal(reposRoot) ) { + m_Client->log(reposRoot,endr,startr,m_OldHistory,startr,true,false,0); + } else { + svn::cache::ReposLog rl(m_Client,reposRoot); + if (rl.isValid()) { + rl.simpleLog(m_OldHistory,startr,endr,!Kdesvnsettings::network_on()); + } else if (Kdesvnsettings::network_on()) { + m_Client->log(reposRoot,endr,startr,m_OldHistory,startr,true,false,0); + } else { + KMessageBox::error(0,i18n("Could not retrieve logs, reason:\n%1").arg(i18n("No logcache possible due broken database and networking not allowed."))); + return false; + } + } + } catch (const svn::Exception&ce) { + kdDebug()<<ce.msg() << endl; + KMessageBox::error(0,i18n("Could not retrieve logs, reason:\n%1").arg(ce.msg())); + return false; + } + return true; +} + +RevisionTree::RevisionTree(svn::Client*aClient, + QObject*aListener, + const QString& reposRoot, + const svn::Revision&startr,const svn::Revision&endr, + const QString&origin, + const svn::Revision& baserevision, + QWidget*treeParent,QWidget*parent) + :m_InitialRevsion(0),m_Path(origin),m_Valid(false) +{ + m_Data = new RtreeData; + m_Data->m_Client=aClient; + m_Data->m_Listener=aListener; + m_Data->dlgParent=parent; + + if (!m_Data->getLogs(reposRoot,startr,endr,origin)) { + return; + } + + long possible_rev=-1; + kdDebug()<<"Origin: "<<origin << endl; + + m_Data->progress=new KProgressDialog( + parent,"progressdlg",i18n("Scanning logs"),i18n("Scanning the logs for %1").arg(origin),true); + m_Data->progress->setMinimumDuration(100); + m_Data->progress->show(); + m_Data->progress->setAllowCancel(true); + m_Data->progress->progressBar()->setTotalSteps(m_Data->m_OldHistory.size()); + m_Data->progress->setAutoClose(false); + m_Data->progress->show(); + bool cancel=false; + svn::LogEntriesMap::Iterator it; + unsigned count = 0; + for (it=m_Data->m_OldHistory.begin();it!=m_Data->m_OldHistory.end();++it) { + m_Data->progress->progressBar()->setProgress(count); + kapp->processEvents(); + if (m_Data->progress->wasCancelled()) { + cancel=true; + break; + } + if (it.key()>m_Data->max_rev) { + m_Data->max_rev=it.key(); + } + if (it.key()<m_Data->min_rev||m_Data->min_rev==-1) { + m_Data->min_rev=it.key(); + } + if (baserevision.kind()==svn_opt_revision_date) { + if (baserevision.date()<=it.data().date && possible_rev==-1||possible_rev>it.key()) { + possible_rev=it.key(); + } + } + ++count; + } + if (baserevision.kind()==svn_opt_revision_head||baserevision.kind()==svn_opt_revision_working) { + m_Baserevision=m_Data->max_rev; + } else if (baserevision.kind()==svn_opt_revision_number) { + m_Baserevision=baserevision.revnum(); + } else if (baserevision.kind()==svn_opt_revision_date) { + m_Baserevision=possible_rev; + } + if (!cancel) { + kdDebug( )<<" max revision " << m_Data->max_rev + << " min revision " << m_Data->min_rev << endl; + if (topDownScan()) { + kdDebug()<<"topdown end"<<endl; + m_Data->progress->setAutoReset(true); + m_Data->progress->progressBar()->setTotalSteps(100); + m_Data->progress->progressBar()->setPercentageVisible(false); + m_Data->m_stopTick.restart(); + m_Data->m_TreeDisplay=new RevTreeWidget(m_Data->m_Listener,m_Data->m_Client,treeParent); + if (bottomUpScan(m_InitialRevsion,0,m_Path,0)) { + kdDebug()<<"Bottom up end"<<endl; + m_Valid=true; + m_Data->m_TreeDisplay->setBasePath(reposRoot); + m_Data->m_TreeDisplay->dumpRevtree(); + } else { + delete m_Data->m_TreeDisplay; + m_Data->m_TreeDisplay = 0; + } + } + } else { + kdDebug()<<"Canceld"<<endl; + } + m_Data->progress->hide(); +} + +RevisionTree::~RevisionTree() +{ + delete m_Data; +} + +bool RevisionTree::isDeleted(long revision,const QString&path) +{ + for (unsigned i = 0;i<m_Data->m_History[revision].changedPaths.count();++i) { + if (isParent(m_Data->m_History[revision].changedPaths[i].path,path) && + m_Data->m_History[revision].changedPaths[i].action=='D') { + return true; + } + } + return false; +} + +bool RevisionTree::topDownScan() +{ + m_Data->progress->progressBar()->setTotalSteps(m_Data->max_rev-m_Data->min_rev); + bool cancel=false; + QString label; + QString olabel = m_Data->progress->labelText(); + for (long j=m_Data->max_rev;j>=m_Data->min_rev;--j) { + m_Data->progress->progressBar()->setProgress(m_Data->max_rev-j); + kapp->processEvents(); + if (m_Data->progress->wasCancelled()) { + cancel=true; + break; + } + for (unsigned i = 0; i<m_Data->m_OldHistory[j].changedPaths.count();++i) { + if (i>0 && i%100==0) { + if (m_Data->progress->wasCancelled()) { + cancel=true; + break; + } + label = i18n("%1<br>Check change entry %2 of %3") + .arg(olabel).arg(i).arg(m_Data->m_OldHistory[j].changedPaths.count()); + m_Data->progress->setLabel(label); + kapp->processEvents(); + } + /* find min revision of item */ + if (m_Data->m_OldHistory[j].changedPaths[i].action=='A'&& + isParent(m_Data->m_OldHistory[j].changedPaths[i].path,m_Path)) + { + if (!m_Data->m_OldHistory[j].changedPaths[i].copyFromPath.isEmpty()) { + if (m_InitialRevsion<m_Data->m_OldHistory[j].revision) { + QString tmpPath = m_Path; + QString r = m_Path.mid(m_Data->m_OldHistory[j].changedPaths[i].path.length()); + m_Path=m_Data->m_OldHistory[j].changedPaths[i].copyFromPath; + m_Path+=r; + } + } else if (m_Data->m_OldHistory[j].changedPaths[i].path==m_Path && m_Data->m_OldHistory[j].changedPaths[i].copyToPath.isEmpty()){ + // here it is added + m_InitialRevsion = m_Data->m_OldHistory[j].revision; + } + } + } + } + kdDebug()<<"Stage one done"<<endl; + if (cancel==true) { + return false; + } + m_Data->progress->setLabel(olabel); + /* find forward references and filter them out */ + for (long j=m_Data->max_rev;j>=m_Data->min_rev;--j) { + m_Data->progress->progressBar()->setProgress(m_Data->max_rev-j); + kapp->processEvents(); + if (m_Data->progress->wasCancelled()) { + cancel=true; + break; + } + for (unsigned i = 0; i<m_Data->m_OldHistory[j].changedPaths.count();++i) { + if (i>0 && i%100==0) { + if (m_Data->progress->wasCancelled()) { + cancel=true; + break; + } + label = i18n("%1<br>Check change entry %2 of %3").arg(olabel).arg(i).arg(m_Data->m_OldHistory[j].changedPaths.count()); + m_Data->progress->setLabel(label); + kapp->processEvents(); + } + if (!m_Data->m_OldHistory[j].changedPaths[i].copyFromPath.isEmpty()) { + long r = m_Data->m_OldHistory[j].changedPaths[i].copyFromRevision; + QString sourcepath = m_Data->m_OldHistory[j].changedPaths[i].copyFromPath; + char a = m_Data->m_OldHistory[j].changedPaths[i].action; + if (m_Data->m_OldHistory[j].changedPaths[i].path.isEmpty()) { + kdDebug()<<"Empty entry! rev " << j << " source " << sourcepath << endl; + continue; + } + if (a=='R') { + m_Data->m_OldHistory[j].changedPaths[i].action=0; + } else if (a=='A'){ + a=INTERNALCOPY; + for (unsigned z = 0;z<m_Data->m_OldHistory[j].changedPaths.count();++z) { + if (m_Data->m_OldHistory[j].changedPaths[z].action=='D' + && isParent(m_Data->m_OldHistory[j].changedPaths[z].path,sourcepath) ) { + a=INTERNALRENAME; + m_Data->m_OldHistory[j].changedPaths[z].action=0; + break; + } + } + m_Data->m_History[r].addCopyTo(sourcepath,m_Data->m_OldHistory[j].changedPaths[i].path,j,a,r); + m_Data->m_OldHistory[j].changedPaths[i].action=0; + } else { + kdDebug()<<"Action with source path but wrong action \""<<a<<"\" found!"<<endl; + } + } + } + } + kdDebug()<<"Stage two done"<<endl; + if (cancel==true) { + return false; + } + m_Data->progress->setLabel(olabel); + for (long j=m_Data->max_rev;j>=m_Data->min_rev;--j) { + m_Data->progress->progressBar()->setProgress(m_Data->max_rev-j); + kapp->processEvents(); + if (m_Data->progress->wasCancelled()) { + cancel=true; + break; + } + for (unsigned i = 0; i<m_Data->m_OldHistory[j].changedPaths.count();++i) { + if (m_Data->m_OldHistory[j].changedPaths[i].action==0) { + continue; + } + if (i>0 && i%100==0) { + if (m_Data->progress->wasCancelled()) { + cancel=true; + break; + } + label = i18n("%1<br>Check change entry %2 of %3").arg(olabel).arg(i).arg(m_Data->m_OldHistory[j].changedPaths.count()); + m_Data->progress->setLabel(label); + kapp->processEvents(); + } + m_Data->m_History[j].addCopyTo(m_Data->m_OldHistory[j].changedPaths[i].path,QString::null,-1,m_Data->m_OldHistory[j].changedPaths[i].action); + } + m_Data->m_History[j].author=m_Data->m_OldHistory[j].author; + m_Data->m_History[j].date=m_Data->m_OldHistory[j].date; + m_Data->m_History[j].revision=m_Data->m_OldHistory[j].revision; + m_Data->m_History[j].message=m_Data->m_OldHistory[j].message; + } + kdDebug()<<"Stage three done"<<endl; + return !cancel; +} + +bool RevisionTree::isParent(const QString&_par,const QString&tar) +{ + if (_par==tar) return true; + QString par = _par+(_par.endsWith("/")?"":"/"); + return tar.startsWith(par); +} + +bool RevisionTree::isValid()const +{ + return m_Valid; +} + +static QString uniqueNodeName(long rev,const QString&path) +{ + QString res = KCodecs::base64Encode(path.local8Bit(),false); + res.replace("\"","_quot_"); + res.replace(" ","_space_"); + QString n; n.sprintf("%05ld",rev); + res = "\""+n+QString("_%1\"").arg(res); + return res; +} + +bool RevisionTree::bottomUpScan(long startrev,unsigned recurse,const QString&_path,long _last) +{ +#define REVENTRY m_Data->m_History[j] +#define FORWARDENTRY m_Data->m_History[j].changedPaths[i] + + QString path = _path; + long lastrev = _last; + /* this is required if an item will modified AND copied at same revision.*/ + long trev = -1; +#ifdef DEBUG_PARSE + kdDebug()<<"Searching for "<<path<< " at revision " << startrev + << " recursion " << recurse << endl; +#endif + bool cancel = false; + for (long j=startrev;j<=m_Data->max_rev;++j) { + if (m_Data->m_stopTick.elapsed()>500) { + m_Data->progress->progressBar()->advance(1); + kapp->processEvents(); + m_Data->m_stopTick.restart(); + } + if (m_Data->progress->wasCancelled()) { + cancel=true; + break; + } + for (unsigned i=0;i<REVENTRY.changedPaths.count();++i) { + if (!isParent(FORWARDENTRY.path,path)) { + continue; + } + QString n1,n2; + if (isParent(FORWARDENTRY.path,path)) { + bool get_out = false; + if (FORWARDENTRY.path!=path) { +#ifdef DEBUG_PARSE + kdDebug()<<"Parent rename? "<< FORWARDENTRY.path << " -> " << FORWARDENTRY.copyToPath << " -> " << FORWARDENTRY.copyFromPath << endl; +#endif + } + if (FORWARDENTRY.action==INTERNALCOPY || + FORWARDENTRY.action==INTERNALRENAME ) { + bool ren = FORWARDENTRY.action==INTERNALRENAME; + QString tmpPath = path; + QString recPath; + if (FORWARDENTRY.copyToPath.length()==0) { + continue; + } + QString r = path.mid(FORWARDENTRY.path.length()); + recPath= FORWARDENTRY.copyToPath; + recPath+=r; + n1 = uniqueNodeName(lastrev,tmpPath); + n2 = uniqueNodeName(FORWARDENTRY.copyToRevision,recPath); + if (lastrev>0) { + m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[n1].targets.append(RevGraphView::targetData(n2,FORWARDENTRY.action)); + } + m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[n2].name=recPath; + m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[n2].rev = FORWARDENTRY.copyToRevision; + m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[n2].Action=FORWARDENTRY.action; + m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[n2].Author=m_Data->m_History[FORWARDENTRY.copyToRevision].author; + m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[n2].Message=m_Data->m_History[FORWARDENTRY.copyToRevision].message; + m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[n2].Date=helpers::sub2qt::apr_time2qtString(m_Data->m_History[FORWARDENTRY.copyToRevision].date); + if (ren) { + lastrev = FORWARDENTRY.copyToRevision; + /* skip items between */ + j=lastrev; +#ifdef DEBUG_PARSE + kdDebug()<<"Renamed to "<< recPath << " at revision " << FORWARDENTRY.copyToRevision << endl; +#endif + path=recPath; + } else { +#ifdef DEBUG_PARSE + kdDebug()<<"Copy to "<< recPath << endl; +#endif + if (!bottomUpScan(FORWARDENTRY.copyToRevision,recurse+1,recPath,FORWARDENTRY.copyToRevision)) { + return false; + } + } + } else if (FORWARDENTRY.path==path) { + switch (FORWARDENTRY.action) { + case 'A': +#ifdef DEBUG_PARSE + kdDebug()<<"Inserting adding base item"<<endl; +#endif + n1 = uniqueNodeName(j,FORWARDENTRY.path); + m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[n1].Action=FORWARDENTRY.action; + fillItem(j,i,n1,path); + lastrev=j; + break; + case 'M': + case 'R': +#ifdef DEBUG_PARSE + kdDebug()<<"Item modified at revision "<< j << " recurse " << recurse << endl; +#endif + n1 = uniqueNodeName(j,FORWARDENTRY.path); + n2 = uniqueNodeName(lastrev,FORWARDENTRY.path); + if (lastrev>0) m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[n2].targets.append(RevGraphView::targetData(n1,FORWARDENTRY.action)); + fillItem(j,i,n1,path); + /* modify of same item (in same recurse) should be only once at a revision + * so check if lastrev==j must not be done but will cost cpu ticks so I always + * set trev and lastrev. + */ + trev = lastrev; + lastrev = j; + break; + case 'D': +#ifdef DEBUG_PARSE + kdDebug()<<"(Sloppy match) Item deleted at revision "<< j << " recurse " << recurse << endl; +#endif + n1 = uniqueNodeName(j,path); + n2 = uniqueNodeName(lastrev,path); + if (n1==n2) { + /* cvs import - copy and deletion at same revision. + * CVS sucks. + */ + n1 = uniqueNodeName(j,"D_"+path); + } + if (lastrev>0) m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[n2].targets.append(RevGraphView::targetData(n1,FORWARDENTRY.action)); + fillItem(j,i,n1,path); + lastrev = j; + get_out= true; + break; + default: + break; + } + } else { + switch (FORWARDENTRY.action) { + case 'D': +#ifdef DEBUG_PARSE + kdDebug()<<"(Exact match) Item deleted at revision "<< j << " recurse " << recurse << endl; +#endif + n1 = uniqueNodeName(j,path); + n2 = uniqueNodeName(lastrev,path); + if (n1==n2) { + /* cvs import - copy and deletion at same revision. + * CVS sucks. + */ + n1 = uniqueNodeName(j,"D_"+path); + } + if (lastrev>0) m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[n2].targets.append(RevGraphView::targetData(n1,FORWARDENTRY.action)); + fillItem(j,i,n1,path); + lastrev = j; + get_out = true; + break; + default: + break; + } + } + if (get_out) { + return true; + } + } + } + } + return !cancel; +} + +QWidget*RevisionTree::getView() +{ + return m_Data->m_TreeDisplay; +} + +void RevisionTree::fillItem(long rev,int pathIndex,const QString&nodeName,const QString&path) +{ + m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[nodeName].name=path; + m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[nodeName].rev = rev; + if (pathIndex>=0) { + m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[nodeName].Action=m_Data->m_History[rev].changedPaths[pathIndex].action; + m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[nodeName].Author=m_Data->m_History[rev].author; + m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[nodeName].Message=m_Data->m_History[rev].message; + m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[nodeName].Date=helpers::sub2qt::apr_time2qtString(m_Data->m_History[rev].date); + } else { + m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[nodeName].Action=0; + m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[nodeName].Author=""; + m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[nodeName].Message=""; + m_Data->m_TreeDisplay->m_RevGraphView->m_Tree[nodeName].Date=helpers::sub2qt::apr_time2qtString(0); + } +} diff --git a/src/svnfrontend/graphtree/revisiontree.h b/src/svnfrontend/graphtree/revisiontree.h new file mode 100644 index 0000000..508306f --- /dev/null +++ b/src/svnfrontend/graphtree/revisiontree.h @@ -0,0 +1,75 @@ +/*************************************************************************** + * Copyright (C) 2005-2007 by Rajko Albrecht * + * [email protected] * + * * + * 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. * + ***************************************************************************/ +#ifndef REVISIONTREE_H +#define REVISIONTREE_H + +#include "src/svnqt/log_entry.hpp" +#include "src/svnqt/revision.hpp" +#include "src/svnqt/client.hpp" + +#include <qstring.h> +#include <qmap.h> +#include <qpixmap.h> + +class RtreeData; +class QWidget; +class KListViewItem; +class KListView; +class CContextListener; + +namespace svn +{ + class Client; +} + +/** + @author Rajko Albrecht <[email protected]> +*/ +class RevisionTree{ +public: + RevisionTree(svn::Client*, + QObject*aListener, + const QString& reposRoot, + const svn::Revision&startr,const svn::Revision&endr, + const QString&,const svn::Revision& baserevision,QWidget*treeParent, + QWidget*parent=0); + virtual ~RevisionTree(); + + bool isValid()const; + QWidget*getView(); + +protected: + long m_Baserevision; + long m_InitialRevsion; + QString m_Path; + bool m_Valid; + + RtreeData*m_Data; + + bool topDownScan(); + bool bottomUpScan(long startrev,unsigned recurse,const QString&path,long sRev = -1); + bool isDeleted(long revision,const QString&); + + static bool isParent(const QString&_par,const QString&tar); + + void fillItem(long revIndex,int pathIndex,const QString&nodeName,const QString&path); +}; + +#endif diff --git a/src/svnfrontend/graphtree/revtreewidget.cpp b/src/svnfrontend/graphtree/revtreewidget.cpp new file mode 100644 index 0000000..35b5258 --- /dev/null +++ b/src/svnfrontend/graphtree/revtreewidget.cpp @@ -0,0 +1,115 @@ +/*************************************************************************** + * Copyright (C) 2006-2007 by Rajko Albrecht * + * [email protected] * + * * + * 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 "revtreewidget.h" +#include "src/settings/kdesvnsettings.h" + +#include <qvariant.h> +#include <qsplitter.h> +#include <ktextbrowser.h> +#include <qlayout.h> +#include <qtooltip.h> +#include <qwhatsthis.h> +#include "revgraphview.h" +#include "ktextbrowser.h" + +/* + * Constructs a RevTreeWidget as a child of 'parent', with the + * name 'name' and widget flags set to 'f'. + */ +RevTreeWidget::RevTreeWidget(QObject*lt,svn::Client*cl, QWidget* parent, const char* name, WFlags fl ) + : QWidget( parent, name, fl ) +{ + if ( !name ) + setName( "RevTreeWidget" ); + RevTreeWidgetLayout = new QVBoxLayout( this, 11, 6, "RevTreeWidgetLayout"); + + m_Splitter = new QSplitter( this, "m_Splitter" ); + m_Splitter->setOrientation( QSplitter::Vertical ); + + m_RevGraphView = new RevGraphView(lt,cl, m_Splitter, "m_RevGraphView" ); + m_RevGraphView->setSizePolicy( QSizePolicy( (QSizePolicy::SizeType)5, (QSizePolicy::SizeType)5, 0, 2, m_RevGraphView->sizePolicy().hasHeightForWidth() ) ); + connect(m_RevGraphView,SIGNAL(dispDetails(const QString&)),this,SLOT(setDetailText(const QString&))); + connect(m_RevGraphView, + SIGNAL(makeNorecDiff(const QString&,const svn::Revision&,const QString&,const svn::Revision&,QWidget*)), + this, + SIGNAL(makeNorecDiff(const QString&,const svn::Revision&,const QString&,const svn::Revision&,QWidget*)) + ); + connect(m_RevGraphView, + SIGNAL(makeRecDiff(const QString&,const svn::Revision&,const QString&,const svn::Revision&,QWidget*)), + this, + SIGNAL(makeRecDiff(const QString&,const svn::Revision&,const QString&,const svn::Revision&,QWidget*)) + ); + connect(m_RevGraphView, + SIGNAL(makeCat(const svn::Revision&,const QString&,const QString&,const svn::Revision&,QWidget*)), + this, + SIGNAL(makeCat(const svn::Revision&,const QString&,const QString&,const svn::Revision&,QWidget*)) + ); + + m_Detailstext = new KTextBrowser( m_Splitter, "m_Detailstext" ); + m_Detailstext->setSizePolicy( QSizePolicy( (QSizePolicy::SizeType)7, (QSizePolicy::SizeType)7, 0, 0, m_Detailstext->sizePolicy().hasHeightForWidth() ) ); + m_Detailstext->setResizePolicy( KTextBrowser::Manual ); + RevTreeWidgetLayout->addWidget( m_Splitter ); + resize( QSize(600, 480).expandedTo(minimumSizeHint()) ); + clearWState( WState_Polished ); + QValueList<int> list = Kdesvnsettings::tree_detail_height(); + if (list.count()==2 && (list[0]>0||list[1]>0)) { + m_Splitter->setSizes(list); + } +} + +/* + * Destroys the object and frees any allocated resources + */ +RevTreeWidget::~RevTreeWidget() +{ + // no need to delete child widgets, Qt does it all for us + QValueList<int> list = m_Splitter->sizes(); + if (list.count()==2) { + Kdesvnsettings::setTree_detail_height(list); + Kdesvnsettings::writeConfig(); + } +} + +void RevTreeWidget::setBasePath(const QString&_p) +{ + m_RevGraphView->setBasePath(_p); +} + +void RevTreeWidget::dumpRevtree() +{ + m_RevGraphView->dumpRevtree(); +} + +void RevTreeWidget::setDetailText(const QString&_s) +{ + m_Detailstext->setText(_s); + QValueList<int> list = m_Splitter->sizes(); + if (list.count()!=2) return; + if (list[1]==0) { + int h = height(); + int th = h/10; + list[0]=h-th; + list[1]=th; + m_Splitter->setSizes(list); + } +} + +#include "revtreewidget.moc" + diff --git a/src/svnfrontend/graphtree/revtreewidget.h b/src/svnfrontend/graphtree/revtreewidget.h new file mode 100644 index 0000000..324f0ba --- /dev/null +++ b/src/svnfrontend/graphtree/revtreewidget.h @@ -0,0 +1,74 @@ +/*************************************************************************** + * Copyright (C) 2006-2007 by Rajko Albrecht * + * [email protected] * + * * + * 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. * + ***************************************************************************/ +#ifndef REVTREEWIDGET_H +#define REVTREEWIDGET_H + +#include <svnqt/revision.hpp> + +#include <qvariant.h> +#include <qpixmap.h> +#include <qwidget.h> + +class QVBoxLayout; +class QHBoxLayout; +class QGridLayout; +class QSpacerItem; +class RevGraphView; +class QSplitter; +class KTextBrowser; +class CContextListener; + +namespace svn { + class LogEntry; + class Client; +} + +class RevTreeWidget : public QWidget +{ + Q_OBJECT + +public: + RevTreeWidget(QObject*,svn::Client*,QWidget* parent = 0, const char* name = 0, WFlags fl = 0 ); + ~RevTreeWidget(); + + QSplitter* m_Splitter; + RevGraphView* m_RevGraphView; + + void setBasePath(const QString&); + void dumpRevtree(); + +protected: + QVBoxLayout* RevTreeWidgetLayout; + KTextBrowser* m_Detailstext; + +signals: + void makeCat(const svn::Revision&,const QString&,const QString&,const svn::Revision&,QWidget*); + void makeNorecDiff(const QString&,const svn::Revision&,const QString&,const svn::Revision&,QWidget*); + void makeRecDiff(const QString&,const svn::Revision&,const QString&,const svn::Revision&,QWidget*); + +protected slots: + virtual void setDetailText(const QString&); + +private: + QPixmap image0; + +}; + +#endif // REVTREEWIDGET_H |