/****************************************************************************
**
** Copyright (C) 2000-2006 Frank Hemer <frank@hemer.org>,
** Tilo Riemer <riemer@crossvc.com>,
** Wim Delvaux <wim.delvaux@chello.be>,
** Jose Hernandez <joseh@tesco.net>
**
**
**----------------------------------------------------------------------------
**
**----------------------------------------------------------------------------
**
** CrossVC is available under two different licenses:
**
** If CrossVC is linked against the GPLed version of Qt
** CrossVC is released under the terms of GPL also.
**
** If CrossVC is linked against a nonGPLed version of Qt
** CrossVC is released under the terms of the
** CrossVC License for non-Unix platforms (CLNU)
**
**
** CrossVC License for non-Unix platforms (CLNU):
**
** Redistribution and use in binary form, without modification,
** are permitted provided that the following conditions are met:
**
** 1. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 2. It is not permitted to distribute the binary package under a name
** different than CrossVC.
** 3. The name of the authors may not be used to endorse or promote
** products derived from this software without specific prior written
** permission.
** 4. The source code is the creative property of the authors.
** Extensions and development under the terms of the Gnu Public License
** are limited to the Unix platform. Any distribution or compilation of
** the source code against libraries licensed other than gpl requires
** the written permission of the authors.
**
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
** GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**
**
**
** CrossVC License for Unix platforms:
**
** 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, version 2 of the License.
** 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 version 2 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
**
*****************************************************************************/
#include "logtree.h"
#include "config.h"
#include <iostream>
#include <qpainter.h>
#include <qapplication.h>
#include <qtooltip.h>
#include <qregexp.h>
#include <qscrollbar.h>
#include <qtimer.h>
#include "globals.h"
static const int BORDER = 8;
static const int INSPACE = 3;
static const int BASEBLOCK = 10;
//needed as static for sizeHint() const
static int static_base_width;
static int static_base_height;
QColor LogTreeView::SelColor [2];
// -------------------- Tooltip --------------------------------------------------------------------------
class DynamicTip : public QToolTip
{
public:
DynamicTip(LogTreeView *parent) : QToolTip(parent) {}
protected:
virtual void maybeTip(const QPoint &pos);
};
// -------------------- LogTreeItem --------------------------------------------------------------------------
LogTreeView::LogTreeItem::LogTreeItem()
: firstonbranch(false), followed(false), branched(false),
row(0), col(0), selected(false), selection(0)
{
}
LogTreeView::LogTreeItem::LogTreeItem(QString _file,
QString _rev,
QString _authorLocked,
QString _date,
QString _author,
QString _state,
QString _lines,
QString _kopt,
QString _commitid,
QString _mergepoint,
QString _filename,
QString _comment,
QString _branch,
QString _tagcomment,
QString _BranchTagList,
QString _RegularTagList)
: file(_file), rev(_rev), locked(QString::null),authorLocked(_authorLocked),
date(_date), author(_author), state(_state), lines(_lines), kopt(_kopt),
commitid(_commitid), mergepoint(_mergepoint), filename(_filename), comment(_comment),
branch(_branch), tagcomment(_tagcomment), BranchTagList(_BranchTagList),
RegularTagList(_RegularTagList), branchpoint(QString::null),
firstonbranch(false), followed(false), branched(false),
row(0), col(0), selected(false), selection(0)
{
}
LogTreeView::LogTreeItem::~LogTreeItem()
{
}
// -------------------- ItemList --------------------------------------------------------------------------
LogTreeView::ItemList::ItemList() : QPtrList<LogTreeView::LogTreeItem>() {
setAutoDelete(false);
}
LogTreeView::ItemList::~ItemList() {
}
int LogTreeView::ItemList::compareItems ( QPtrCollection::Item item1, QPtrCollection::Item item2 ) {
QString r1 = static_cast<LogTreeView::LogTreeItem*>(item1)->rev;
QString r2 = static_cast<LogTreeView::LogTreeItem*>(item2)->rev;
int c1 = r1.contains('.');
int c2 = r2.contains('.');
if (c1 != c2) {
return (c1-c2);
} else if (c1 == 1) {
QString s1 = r1.section('.',0,0);
QString s2 = r2.section('.',0,0);
if (s1 != s2) {
return (s2.toInt()-s1.toInt());
}
s1 = r1.section('.',1,1);
s2 = r2.section('.',1,1);
return (s2.toInt()-s1.toInt());
} else {
int i = 0;
QString s1 = r1.section('.',i,i);
QString s2 = r2.section('.',i,i);
do {
if (s1 != s2) {
return (s1.toInt()-s2.toInt());
}
++i;
s1 = r1.section('.',i,i);
s2 = r2.section('.',i,i);
} while (i < c1);
return (s2.toInt()-s1.toInt());
}
}
// -------------------- LogTreeView --------------------------------------------------------------------------
void LogTreeView::setSelectionColor (const unsigned char selection, const QColor &color)
{
SelColor [selection] = color;
}
QColor LogTreeView::getSelectionColor (const unsigned char selection) const
{
return SelColor [selection];
}
LogTreeView::LogTreeView(QWidget *parent, const char *name, WFlags f)
: QtTableView(parent, name, f), bEnableBranchTags(false),
bEnableRegularTags(false) {
m_Font = font();
m_pFM = new QFontMetrics( m_Font);
static_base_width = m_pFM->width("123456789") + 2*BORDER + 2*INSPACE;
static_base_height = 2*m_pFM->height() + 2*BORDER + 3*INSPACE;
// Set default selection color to black
for (int i=0; i<2; i++) {
setSelectionColor (i, QColor("black"));
}
setNumCols(0);
setNumRows(0);
setAutoUpdate(false);
setTableFlags( Tbl_autoVScrollBar | Tbl_autoHScrollBar |
Tbl_smoothVScrolling | Tbl_smoothHScrolling );
setFrameStyle( QFrame::WinPanel | QFrame::Sunken );
setBackgroundMode(PaletteBase);
setMouseTracking(true);
setFocusPolicy(ClickFocus);
focus.col = -1;
focus.row = -1;
setCellWidth(0);
setCellHeight(0);
currentRow = -1;
currentCol = -1;
currentTree = 0;
insertRow = 0;
startCol = 0;
newFile = TRUE;
itemFileList.setAutoDelete(true);
connectionFileList.setAutoDelete(true);
currentItems = new QPtrList<LogTreeItem>();
currentItems->setAutoDelete(true);
itemFileList.append(currentItems);
currentConnections = new QPtrList<LogTreeConnection>();
currentConnections->setAutoDelete(true);
connectionFileList.append( currentConnections );
(void) new DynamicTip(this);
connect(verticalScrollBar(),SIGNAL(valueChanged(int)),this,SLOT(scrollValueChanged(int)));
connect(horizontalScrollBar(),SIGNAL(valueChanged(int)),this,SLOT(scrollValueChanged(int)));
connect(verticalScrollBar(),SIGNAL(sliderMoved(int)),this,SLOT(scrollValueChanged(int)));
connect(horizontalScrollBar(),SIGNAL(sliderMoved(int)),this,SLOT(scrollValueChanged(int)));
}
LogTreeView::~LogTreeView () {
}
void LogTreeView::zoomIn() {
zoom(1);
}
void LogTreeView::zoomOut() {
zoom(-1);
}
void LogTreeView::zoom(int val) {
int size = m_Font.pointSize();
int oldSize = m_pFM->width("123456789");
size += val;
while (size>0 && size<40) {
m_Font.setPointSize(size);
delete m_pFM;
m_pFM = new QFontMetrics(m_Font);
if (m_pFM->width("123456789") != oldSize) {// not supported ? -> ignore it
static_base_width = m_pFM->width("123456789") + 2*BORDER + 2*INSPACE;
static_base_height = 2*m_pFM->height() + 2*BORDER + 3*INSPACE;
setFont(m_Font);
recomputeCellSizes();
repaint (true);
break;
}
size += val;
}
}
void LogTreeView::wheelEvent( QWheelEvent *e) {
if (viewRect().contains(e->pos())) {
int d = e->delta();
int v = verticalScrollBar()->value();
int l = verticalScrollBar()->lineStep();
l *= QApplication::wheelScrollLines();
if (d > 0) {
d = v - l;
} else {
d = v + l;
}
verticalScrollBar()->setValue(d);
e->accept();
} else {
e->ignore();
}
}
void LogTreeView::keyPressEvent ( QKeyEvent * e) {
switch (e->key()) {
case Qt::Key_Up: {
moveFocus(0,-1);
e->accept();
break;
}
case Qt::Key_Down: {
moveFocus(0,1);
e->accept();
break;
}
case Qt::Key_Left: {
moveFocus(-1,0);
e->accept();
break;
}
case Qt::Key_Right: {
moveFocus(1,0);
e->accept();
break;
}
case Qt::Key_Prior: {
if (focus.col == -1) focus.col = leftCell();
if (focus.row == -1) focus.row = lastRowVisible();
moveFocus(0,-focus.row);
e->accept();
break;
}
case Qt::Key_Next: {
if (focus.col == -1) focus.col = leftCell();
if (focus.row == -1) focus.row = lastRowVisible();
moveFocus(0,numRows() - 1 - focus.row);
e->accept();
break;
}
case Qt::Key_Home: {
if (focus.col == -1) focus.col = leftCell();
if (focus.row == -1) focus.row = lastRowVisible();
moveFocus(-focus.col,0);
e->accept();
break;
}
case Qt::Key_End: {
if (focus.col == -1) focus.col = leftCell();
if (focus.row == -1) focus.row = lastRowVisible();
moveFocus(numCols() - 1 - focus.col,0);
e->accept();
break;
}
case Qt::Key_Space: {
if (focus.col > -1 && focus.row > -1) {
if (e->state() == Qt::NoButton || e->state() == Qt::ShiftButton) {
QPtrList<LogTreeItem> * listItem = itemFileList.at(treeForCol(focus.col));
QPtrListIterator<LogTreeItem> it(*listItem);
for(; it.current(); ++it) {
if ((it.current()->row == focus.row) &&
(it.current()->col == focus.col) &&
(it.current()->state != "dead")) {
emit revisionClicked (it.current(), (e->state() == Qt::ShiftButton));
break;
}
}
}
}
break;
}
default: {
break;
}
}
}
void LogTreeView::moveFocus( int dx, int dy) {
if (focus.col == -1 || focus.row == -1 || !rowIsVisible(focus.row) || !colIsVisible(focus.col) ) {
focus.col = leftCell();
focus.row = lastRowVisible();
} else if (focus.col+dx >= 0 && focus.col+dx < numCols() && focus.row+dy >= 0 && focus.row+dy < numRows()) {
focus.col += dx;
focus.row += dy;
} else return;
int xoff=0;
int yoff=0;
int i;
for (i=0; i<focus.col-1; ++i) {
xoff += cellWidth(i);
}
for (i=0; i<=focus.row; ++i) {
yoff += cellHeight(i);
}
if (focus.row+1 < numRows()) yoff += cellHeight(focus.row+1);
setOffset(xoff,yoff-maxViewY());
repaint(true);
}
void LogTreeView::scrollDown() {
setOffset(0,totalHeight()-maxViewY());
}
QString LogTreeView::findCollision (int row, int col) const {
QPtrListIterator<LogTreeItem> it(*currentItems);
QString branch ("");
for (it.toFirst(); it.current(); ++it) {
if ((it.current()->row == row) && (it.current()->col == col)) {
branch = it.current()->rev;
branch.replace (QRegExp ("\\.[0-9]+$"), "");
}
}
return (branch);
}
void LogTreeView::shiftBranch (QString branch) {
// Move branches to the right
QPtrListIterator<LogTreeItem> it(*currentItems);
if (!branch.isEmpty()) {
for (it.toFirst(); it.current(); ++it)
if (it.current()->rev.startsWith(branch)) {
shiftBranch (findCollision (it.current()->row, it.current()->col + 1));
if (++it.current()->col >= numCols())
setNumCols (it.current()->col + 1);
if (it.current()->rev.contains (QRegExp("\\.1$"))) //FIXME revision number might not end with '.1' because an indiv. number might be added
shiftBranch (findCollision (it.current()->row + 1, it.current()->col));
}
}
}
void LogTreeView::shiftAll(int shift) {
// Shift all items by 'shift'
QPtrList<LogTreeItem> * listItem;
for (listItem = itemFileList.first(); listItem; listItem = itemFileList.next()) {
QPtrListIterator<LogTreeItem> it(*listItem);
for (; it.current(); ++it) {
it.current()->row += shift;
}
}
}
void LogTreeView::shiftCurrent(int shift) {
// Shift current items by 'shift'
QPtrListIterator<LogTreeItem> it(*currentItems);
for (; it.current(); ++it) {
it.current()->row += shift;
}
}
void LogTreeView::addRevisionList(ItemList & list) {
list.sort();
LogTreeItem * lte;
for ( lte = list.first(); lte; lte = list.next() ) {
addRevision(lte);
}
}
void LogTreeView::addRevision( LogTreeItem * item) {
QString revision, branch;
int pos;
revision = item->rev;
branch = "";
//analyze the revision
if ((pos = revision.findRev('.')) != -1) {
revision.truncate(pos);
if ((pos = revision.findRev('.')) != -1) {// we're on a branch
// e. g. for rev = 1.1.2.3 we have
// branch = 1.1.2, revision = 1.1
branch = revision;
revision.truncate(pos);
} else {// we're on the main trunk
int revNum = item->rev.mid(item->rev.findRev(".")+1).toInt();
if (newFile) {
trunkRev = revision.toInt();
rowOffset = revNum;
if (trunkRev > 1) ++rowOffset;// there might be a rev *.0
insertRow = numRows() - rowOffset;
if (insertRow < 0) {
/* Has more revisions than all prev. files
* so shift all other revisions up
* and adjust table size in advance
*/
shiftAll(-insertRow);
setNumRows(numRows() - insertRow);
insertRow = 0;
}
setNumCols (startCol+1);
newFile = FALSE;
} else if (revision.toInt() != trunkRev) {
/* There's a jump in the master rev numbers
* e.g. 3.0 followed by 2.7
*/
// qDebug("master trunk rev jump detected: "+file+
// ", oldtrunk: "+QString::number(trunkRev)+
// ", newtrunk: "+revision+
// ", oldrev: "+QString::number(lastRevNum)+
// ", newrev: "+QString::number(revNum));
trunkRev = revision.toInt();
int rows = revNum;
if (trunkRev > 1) ++rows;// we assume there's a rev *.0
/* Insert space for the new rows
* and shift current rev's up if
* there's a jump in the rev numbers
* e.g. 3.2 followed by 2.7
*/
shiftCurrent(lastRevNum-rows);
rowOffset += rows-lastRevNum;
insertRow += lastRevNum-rows;
/* And resize the table
* if necessary
*/
int insert = rowOffset - numRows();
if (insert > 0) {
shiftAll(insert);
setNumRows(rowOffset);
insertRow += insert;
}
} else if (revNum < lastRevNum-1) {
/* There's a jump in the rev numbers
* e.g. 2.7 followed by 2.3
*/
// qDebug("rev jump detected: "+file+
// ", oldrev: "+QString::number(lastRevNum)+
// ", newrev: "+QString::number(revNum));
int jump = lastRevNum - 1 - revNum;
shiftCurrent(jump);
insertRow += jump;
rowOffset -= jump;
}
lastRevNum = revNum;
}
}
item->tree = currentTree;
if (!item->authorLocked.isEmpty()) {
item->locked = " locked";
}
item->branchpoint = revision;
item->selected = false;
if (branch.isEmpty()) {
// We are on the main trunk
item->firstonbranch = false;
item->row = insertRow++;
item->col = startCol;
currentItems->append(item);
return;
}
// look whether we have revisions on this branch
// shift them up
int row = -1, col = -1;
QPtrListIterator<LogTreeItem> it(*currentItems);
for (; it.current(); ++it) {
if (it.current()->rev.startsWith(branch)) {
it.current()->firstonbranch = false;
row = it.current()->row;
col = it.current()->col;
it.current()->row--;
/* Are we inserting at the top of the widget?
* If so, we need to shift all the rows down.
*/
if (row == 0) {
shiftAll(1);
setNumRows(numRows()+1);
row = 1;
}
}
}
if (row == -1) {
/* Ok, so we must open a new branch
* Let's find the branch point
*/
QPtrListIterator<LogTreeItem> it3(*currentItems);
for (it3.toLast(); it3.current(); --it3) {
if (revision == it3.current()->rev) {
row = it3.current()->row-1;
col = it3.current()->col+1;
/* Display a compacted log tree so we can get
* more stuff on less screen. This mode is
* basically a slightly smarted layout algorithm
* that attempts to position branches in a
* space saving way.
*/
shiftBranch (findCollision (row, col));
shiftBranch (findCollision (row + 1, col));
if (col >= numCols()) {
setNumCols (col + 1);
}
/* Are we inserting above the top of the widget?
* If so, we need to shift all the rows down.
*/
if (row == -1) {
shiftAll(1);
setNumRows(numRows()+1);
row = 0;
}
break;
}
}
}
item->firstonbranch = true;
item->row = row;
item->col = col;
currentItems->append(item);
}
void LogTreeView::nextFile() {
collectConnections();
treeList.append(numCols()-1);
startCol = numCols()+1;
++currentTree;
insertRow = 0;
newFile = TRUE;
currentItems = new QPtrList<LogTreeItem>();
currentItems->setAutoDelete(true);
itemFileList.append(currentItems);
currentConnections = new QPtrList<LogTreeConnection>();
currentConnections->setAutoDelete(true);
connectionFileList.append( currentConnections );
}
unsigned int LogTreeView::treeForCol(int col) {
unsigned int tree = 0;
QValueList<int>::iterator it;
for ( it = treeList.begin(); it != treeList.end(); ++it,++tree ) {
if (col <= *it) return tree;
}
qDebug("tree not found");
return 0;
}
void LogTreeView::collectConnections() {
QPtrListIterator<LogTreeItem> it(*currentItems);
for (; it.current(); ++it) {
QString rev = it.current()->rev;
QPtrListIterator<LogTreeItem> it2(*currentItems);
for (it2=it,++it2; it2.current(); ++it2) {
if (it2.current()->branchpoint == rev &&
it2.current()->firstonbranch) {
LogTreeConnection *conn = new LogTreeConnection;
conn->start = it.current();
conn->end = it2.current();
currentConnections->append(conn);
}
}
}
}
void LogTreeView::setSelectedPair (int treeA, int treeB, QString &selectionA, QString &selectionB) {
bool selected, refresh = false;
unsigned char selection;
QPtrList<LogTreeItem> * listItem;
for (listItem = itemFileList.first(); listItem; listItem = itemFileList.next()) {
for (QPtrListIterator<LogTreeItem> it(*listItem); it.current(); ++it) {
if ( (it.current()->tree == treeA) && (selectionA == it.current()->rev) ) {
selected = true;
selection = 0;
} else if ( (it.current()->tree == treeB) && (selectionB == it.current()->rev) ) {
selected = true;
selection = 1;
} else {
selected = false;
selection = 0;
}
if ((selected != it.current()->selected) ||
(selection != it.current()->selection)) {
it.current()->selected = selected;
it.current()->selection = selection;
// mark for refresh if there are any changes
refresh = true;
}
}
}
repaint (refresh);
}
QSize LogTreeView::sizeHint() const {
return QSize (2 * static_base_width, 3 * static_base_height);
}
void LogTreeView::setupPainter(QPainter *p) {
p->setBackgroundColor(colorGroup().base());
}
void LogTreeView::scrollValueChanged(int) {
//need to update to remove the prev focus frame
if (hasFocus()) {
repaint();
}
}
void LogTreeView::paintEvent(QPaintEvent * e) {
QtTableView::paintEvent(e);
if (hasFocus()) {
QRect r = viewRect();
int x, y, w, h;
r.rect(&x, &y, &w, &h);
int mass = 2;
QPainter p(this);
do {
p.drawRect(x,y,w,h);
x += 1;
y += 1;
w -= 2;
h -= 2;
} while (--mass > 0);
}
}
void LogTreeView::paintCell(QPainter *p, int row, int col) {
bool followed, branched;
LogTreeItem *item;
branched = false;
followed = false;
item = 0;
unsigned int tree = treeForCol(col);
QPtrList<LogTreeItem> * listItem = itemFileList.at(tree);
QPtrListIterator<LogTreeItem> it(*listItem);
for(; it.current(); ++it) {
int itcol = it.current()->col;
int itrow = it.current()->row;
if (itrow == row-1 && itcol == col) {
followed = true;
}
if (itrow == row && itcol == col) {
item = it.current();
}
}
QPtrList<LogTreeConnection> * listConnection = connectionFileList.at(tree);
QPtrListIterator<LogTreeConnection> it2(*listConnection);
for (; it2.current(); ++it2) {
int itcol1 = it2.current()->start->col;
int itcol2 = it2.current()->end->col;
int itrow = it2.current()->start->row;
if (itrow == row && itcol1 <= col && itcol2 > col) {
branched = true;
}
}
p->fillRect (0, 0, cellWidth(col), cellHeight(row), colorGroup().base());
//draw focus
if (focus.col == col && focus.row == row) {
int w = 1;
p->setPen(QPen(black,w,DotLine));
p->drawLine(0,0,cellWidth(col)-w,0);
p->drawLine(0,0,0,cellHeight(row)-w);
p->drawLine(0,cellHeight(row)-w,cellWidth(col)-w,cellHeight(row)-w);
p->drawLine(cellWidth(col)-w,0,cellWidth(col)-w,cellHeight(row)-w);
}
p->setPen (colorGroup().text());
if (item) {
paintRevisionCell (p, item, followed, branched);
} else if (followed || branched) {
paintConnector(p, row, col, followed, branched);
}
}
void LogTreeView::paintConnector (QPainter *p, int row, int col,
bool followed, bool branched) {
int midx = colWidths[col] / 2;
int midy = rowHeights[row] / 2;
p->drawLine(0, midy, branched ? colWidths[col] : midx, midy);
if (followed) {
p->drawLine(midx, midy, midx, 0);
}
}
void LogTreeView::paintRevisionCell (QPainter *p, LogTreeView::LogTreeItem *item,
bool followed, bool branched) {
int alignment = AlignVCenter;
bool baseRev = FALSE;
QSize r1 = m_pFM->size(alignment, item->author);
QSize r2 = m_pFM->size(alignment, item->rev+item->locked);
QSize r3 = m_pFM->size(alignment, item->BranchTagList);
QSize r4 = m_pFM->size(alignment, item->RegularTagList);
int boxwidth, boxheight;
boxwidth = QMAX(static_base_width-2*BORDER, QMAX(r1.width(), r2.width()));
boxheight = r1.height() + r2.height() + 3*INSPACE;
if ((bEnableBranchTags) && (!item->BranchTagList.isEmpty())) {
boxwidth = QMAX(boxwidth, r3.width());
boxheight += r3.height() + INSPACE;
}
if ((bEnableRegularTags) && (!item->RegularTagList.isEmpty())) {
boxwidth = QMAX(boxwidth, r4.width());
boxheight += r4.height() + INSPACE;
}
if (item->row+1 == numRows()) {// rev. 1.1
baseRev = TRUE;
QSize r5 = m_pFM->size(alignment, item->file);
boxwidth = QMAX(boxwidth, r5.width());
boxheight += r5.height() + BASEBLOCK + INSPACE;
}
boxwidth += 2*INSPACE;
int x = (colWidths[item->col] - boxwidth) / 2;
int midx = colWidths[item->col] / 2;
int y = (rowHeights[item->row] - boxheight ) / 2;
int midy = rowHeights[item->row] / 2;
// Connectors
if (followed) {
p->drawLine(midx, 0, midx, y);
}
if (branched) {
p->drawLine(midx + boxwidth / 2, midy, colWidths[item->col], midy);
}
if (!baseRev) p->drawLine(midx, y + boxheight, midx, rowHeights[item->row]);
/* Draw the box itself. Revisions marked as dead by cvs are
* a special case because we can't run normal CVS operations
* on them.
*/
if (item->state == "dead") {
p->setBrush(QColor(221,221,221));
p->setPen (QPen(black, 0, DotLine));
p->drawRoundRect(x, y, boxwidth, boxheight, 10, 10);
} else {
if (item->selected) {
int h, s, v;
QColor FillColor (getSelectionColor(item->selection));
FillColor.hsv (&h, &s, &v);
p->setBrush(FillColor);
p->setPen ((v > 128) ? QColor("black") : QColor("white"));
} else {
p->setBrush(QColor(255,240,220));
}
p->drawRoundRect(x, y, boxwidth, boxheight, 10, 10);
}
if (baseRev) {
p->setBrush(QColor("black"));
p->drawRoundRect(x, y + boxheight - BASEBLOCK, boxwidth, BASEBLOCK, 25, 25);
p->fillRect(x, y + boxheight - BASEBLOCK, boxwidth, BASEBLOCK / 2, QColor("black") );
}
x += INSPACE;
y += INSPACE;
boxwidth -= 2*INSPACE;
p->drawText(x, y, boxwidth, boxheight, AlignHCenter, item->author);
y += r1.height() + INSPACE;
if ((bEnableRegularTags) && (!item->RegularTagList.isEmpty())) {
QFont font(p->font());
QFont underline(font);
underline.setUnderline(TRUE);
p->setFont(underline);
if (!item->selected) {
QColor col = p->pen().color();
p->setPen (QColor("blue"));
p->drawText(x, y, boxwidth, boxheight, AlignHCenter, item->RegularTagList);
p->setPen (col);
} else {
p->drawText(x, y, boxwidth, boxheight, AlignHCenter, item->RegularTagList);
}
p->setFont(font);
y += r4.height() + INSPACE;
}
if ((bEnableBranchTags) && (!item->BranchTagList.isEmpty())) {
QFont font(p->font());
QFont underline(font);
underline.setUnderline(TRUE);
p->setFont(underline);
if (!item->selected) {
QColor col = p->pen().color();
p->setPen (QColor("red"));
p->drawText(x, y, boxwidth, boxheight, AlignHCenter, item->BranchTagList);
p->setPen (col);
} else {
p->drawText(x, y, boxwidth, boxheight, AlignHCenter, item->BranchTagList);
}
p->setFont(font);
y += r3.height() + INSPACE;
}
p->drawText(x, y, boxwidth, boxheight, AlignHCenter, item->rev+item->locked);
y += r2.height() + INSPACE;
if (baseRev) {// rev. 1.1
QFont font(p->font());
QFont italic(font);
italic.setItalic(TRUE);
p->setFont(italic);
if (!item->selected) {
QColor col = p->pen().color();
p->setPen (QColor("magenta"));
p->drawText(x, y, boxwidth, boxheight, AlignHCenter, item->file);
p->setPen (col);
} else {
p->drawText(x, y, boxwidth, boxheight, AlignHCenter, item->file);
}
p->setFont(font);
}
}
/*!!***************************************************************************
*!! FUNCTION NAME
*!! LogTreeView::mouseDoubleClickEvent
*!!
*!! PARAMETERS
*!! QMouseEvent *e
*!!
*!! RETURN VALUE
*!! none
*!!
*!! GLOBALS ACCESSED
*!! ?
*!!
*!! DESCRIPTION
*!! Double clicking on a revision has the effect of bringing up a file
*!! view window. The exception to this are dead revisions.
*!!**************************************************************************/
void LogTreeView::mouseDoubleClickEvent (QMouseEvent *e) {
if (e->button() == LeftButton) {
int row = findRow (e->pos().y());
int col = findCol (e->pos().x());
QPtrList<LogTreeItem> * listItem = itemFileList.at(treeForCol(col));
QPtrListIterator<LogTreeItem> it(*listItem);
for (; it.current(); ++it) {
if ((it.current()->row == row) &&
(it.current()->col == col) &&
(it.current()->state != "dead")) {
emit revisionDblClicked (it.current());
break;
}
}
}
}
void LogTreeView::mousePressEvent(QMouseEvent *e) {
if ( (e->button() == LeftButton) ||
(e->button() == RightButton)) {
int row = findRow( e->pos().y() );
int col = findCol( e->pos().x() );
QPtrList<LogTreeItem> * listItem = itemFileList.at(treeForCol(col));
QPtrListIterator<LogTreeItem> it(*listItem);
for(; it.current(); ++it) {
if ((it.current()->row == row) &&
(it.current()->col == col) &&
(it.current()->state != "dead")) {
emit revisionClicked (it.current(), (e->button() == RightButton));
break;
}
}
}
}
void LogTreeView::recomputeCellSizes () {
// Store current scrollbar focus
int xCenteredCell = (leftCell()+lastColVisible()) / 2;
int yCenteredCell = (topCell()+lastRowVisible()) / 2;
int xScrollVal = horizontalScrollBar()->value();
int yScrollVal = verticalScrollBar()->value();
QRect centeredCellRect = cellPos(xCenteredCell,yCenteredCell);
int oldXCellCenter = (centeredCellRect.left()+centeredCellRect.right()) / 2;
int oldYCellCenter = (centeredCellRect.top()+centeredCellRect.bottom()) / 2;
// Fill with default
int v = static_base_width;
colWidths.fill(v, numCols());
v = static_base_height;
rowHeights.fill(v, numRows());
// block view
setAutoUpdate(false);
// For all files (trees) ...
QPtrList<LogTreeItem> * listItem;
for (listItem = itemFileList.first(); listItem; listItem = itemFileList.next()) {
// Compute maximum for each column and row
QPtrListIterator<LogTreeItem> it(*listItem);
for (; it.current(); ++it) {
LogTreeItem *item = it.current();
QSize r1 = m_pFM->size(AlignVCenter, item->rev+item->locked);
QSize r2 = m_pFM->size(AlignVCenter, item->author);
int boxwidth = QMAX(r1.width(), r2.width());
int boxheight = r1.height() + r2.height() + 3*INSPACE;
if ((bEnableBranchTags) && (!item->BranchTagList.isEmpty())) {
QSize r3 = m_pFM->size(AlignVCenter, item->BranchTagList);
boxwidth = QMAX(boxwidth, r3.width());
boxheight += r3.height() + INSPACE;
}
if ((bEnableRegularTags) && (!item->RegularTagList.isEmpty())) {
QSize r4 = m_pFM->size(AlignVCenter, item->RegularTagList);
boxwidth = QMAX(boxwidth, r4.width());
boxheight += r4.height() + INSPACE;
}
if (item->row+1 == numRows()) {// rev. 1.1
QSize r5 = m_pFM->size(AlignVCenter, item->file);
boxwidth = QMAX(boxwidth, r5.width());
boxheight += r5.height() + BASEBLOCK + INSPACE;
}
boxwidth += 2*INSPACE;
colWidths[item->col] = QMAX(colWidths[item->col], boxwidth + 2*BORDER);
rowHeights[item->row] = QMAX(rowHeights[item->row], boxheight + 2*BORDER);
}
}
setAutoUpdate(true);
updateTableSize();
update();
// restore scrollbar focus
centeredCellRect = cellPos(xCenteredCell,yCenteredCell);
int newXCellCenter = (centeredCellRect.left()+centeredCellRect.right()) / 2;
int newYCellCenter = (centeredCellRect.top()+centeredCellRect.bottom()) / 2;
if (horizontalScrollBar()->isVisible()) horizontalScrollBar()->setValue(xScrollVal+newXCellCenter-oldXCellCenter);
if (verticalScrollBar()->isVisible()) verticalScrollBar()->setValue(yScrollVal+newYCellCenter-oldYCellCenter);
updateScrollBars();
}
int LogTreeView::cellWidth(int col) {
if (col < 0 || col >= (int)colWidths.size()) {
return 0;
}
return colWidths[col];
}
int LogTreeView::cellHeight(int row) {
if (row < 0 || row >= (int)rowHeights.size()) {
return 0;
}
return rowHeights[row];
}
QRect LogTreeView::cellPos(int col,int row) {
int left = 0;
int top = 0;
int width = 0;
int height = 0;
int i;
for (i=0; i<col; ++i) {
left += cellWidth(i);
}
width = cellWidth(i);
for (i=0; i<row; ++i) {
top += cellHeight(i);
}
height = cellHeight(i);
return QRect(left,top,width,height);
}
void LogTreeView::toggleBranchTags (bool enable) {
bEnableBranchTags = enable;
recomputeCellSizes();
repaint (true);
}
void LogTreeView::toggleRegularTags (bool enable) {
bEnableRegularTags = enable;
recomputeCellSizes();
repaint (true);
}
void LogTreeView::lookupTip (const QPoint &p, QRect *r, QString *s) {
int row = findRow (p.y());
int col = findCol (p.x());
*r = QRect(0, 0, -1, -1);
*s = "";
QPtrList<LogTreeItem> * listItem = itemFileList.at(treeForCol(col));
for (QPtrListIterator<LogTreeItem> it(*listItem); it.current(); ++it) {
if ((it.current()->row == row) && (it.current()->col == col)) {
QString text = "<qt><nobr><b>";
text += it.current()->rev;
text += "</b> ";
text += it.current()->author;
text += " <b>";
text += it.current()->date;
text += "</b></nobr>";
if (!it.current()->locked.isEmpty()) {
text += "<br><nobr><b>";
text += it.current()->locked;
text += "</b> by: ";
text += " <b>";
text += it.current()->authorLocked;
text += "</b></nobr>";
}
if (!it.current()->lines.isEmpty()) {
text += "<br><nobr>lines: ";
text += "<b>";
text += it.current()->lines;
text += "</b></nobr>";
}
if (!it.current()->commitid.isEmpty()) {
text += "<br><nobr>commitid: ";
text += "<b>";
text += it.current()->commitid;
text += "</b></nobr>";
}
if (!it.current()->mergepoint.isEmpty()) {
text += "<br><nobr>mergepoint: ";
text += "<b>";
text += it.current()->mergepoint;
text += "</b></nobr>";
}
if (!it.current()->filename.isEmpty()) {
text += "<br><nobr>filename: ";
text += "<b>";
text += it.current()->filename;
text += "</b></nobr>";
}
QStringList list2 = QStringList::split("\n", it.current()->comment);
QStringList::Iterator it2;
for (it2 = list2.begin(); it2 != list2.end(); ++it2) {
text += "<br>";
text += escapeHtmlTags(*it2);
}
if (!it.current()->tagcomment.isEmpty()) {
text += "<i>";
QStringList list3 = QStringList::split("\n", it.current()->tagcomment);
QStringList::Iterator it3;
for (it3 = list3.begin(); it3 != list3.end(); ++it3) {
text += "<br>";
text += (*it3);
}
text += "</i>";
}
text += "</qt>";
int left; colXPos(col, &left); //!!!left += cellWidth(col);
int top; rowYPos(row, &top);
int width = cellWidth(col);
int height = cellHeight(row);
r->setRect(left, top, width, height);
*s = text;
return;
}
}
}
QString LogTreeView::escapeHtmlTags(const QString & txt) {
QString text = txt;
text.replace(QRegExp("<"),"<");
text.replace(QRegExp(">"),">");
return text;
}
void DynamicTip::maybeTip(const QPoint &pos) {
if (!parentWidget()->inherits("LogTreeView")) {
return;
}
LogTreeView *logtree = static_cast<LogTreeView*>(parentWidget());
QRect r;
QString s;
logtree->lookupTip(pos, &r, &s);
if (r.isValid()) {
tip( r, s );
}
}
syntax highlighted by Code2HTML, v. 0.9.1