You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
502 lines
14 KiB
502 lines
14 KiB
/*
|
|
* Copyright (C) 1999-2002 Bernd Gehrmann
|
|
* bernd@mail.berlios.de
|
|
* Copyright (c) 2003-2004 Christian Loose <christian.loose@hamburg.de>
|
|
*
|
|
* 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 "logtree.h"
|
|
|
|
#include <tqpainter.h>
|
|
#include <kdebug.h>
|
|
#include <kglobal.h>
|
|
#include <kglobalsettings.h>
|
|
|
|
#include "loginfo.h"
|
|
#include "tooltip.h"
|
|
|
|
|
|
const int LogTreeView::BORDER = 8;
|
|
const int LogTreeView::INSPACE = 3;
|
|
|
|
namespace
|
|
{
|
|
bool static_initialized = false;
|
|
int static_width;
|
|
int static_height;
|
|
}
|
|
|
|
class LogTreeItem
|
|
{
|
|
public:
|
|
Cervisia::LogInfo m_logInfo;
|
|
TQString branchpoint;
|
|
bool firstonbranch;
|
|
int row;
|
|
int col;
|
|
bool selected;
|
|
};
|
|
|
|
|
|
class LogTreeConnection
|
|
{
|
|
public:
|
|
LogTreeItem *start;
|
|
LogTreeItem *end;
|
|
};
|
|
|
|
|
|
LogTreeView::LogTreeView(TQWidget *parent, const char *name)
|
|
: TQTable(parent, name)
|
|
{
|
|
if (!static_initialized)
|
|
{
|
|
static_initialized = true;
|
|
TQFontMetrics fm( fontMetrics() );
|
|
static_width = fm.width("1234567890") + 2*BORDER + 2*INSPACE;
|
|
static_height = 2*fm.height() + 2*BORDER + 3*INSPACE;
|
|
}
|
|
|
|
setNumCols(0);
|
|
setNumRows(0);
|
|
setReadOnly(true);
|
|
setFocusStyle(TQTable::FollowStyle);
|
|
setSelectionMode(TQTable::NoSelection);
|
|
setShowGrid(false);
|
|
horizontalHeader()->hide();
|
|
setTopMargin(0);
|
|
verticalHeader()->hide();
|
|
setLeftMargin(0);
|
|
setFrameStyle( TQFrame::WinPanel | TQFrame::Sunken );
|
|
setBackgroundMode(PaletteBase);
|
|
setFocusPolicy(TQ_NoFocus);
|
|
|
|
currentRow = -1;
|
|
currentCol = -1;
|
|
|
|
items.setAutoDelete(true);
|
|
connections.setAutoDelete(true);
|
|
|
|
Cervisia::ToolTip* toolTip = new Cervisia::ToolTip(viewport());
|
|
|
|
connect(toolTip, TQT_SIGNAL(queryToolTip(const TQPoint&, TQRect&, TQString&)),
|
|
this, TQT_SLOT(slotQueryToolTip(const TQPoint&, TQRect&, TQString&)));
|
|
}
|
|
|
|
|
|
void LogTreeView::addRevision(const Cervisia::LogInfo& logInfo)
|
|
{
|
|
TQString branchpoint, branchrev;
|
|
|
|
const TQString rev(logInfo.m_revision);
|
|
|
|
// find branch
|
|
int pos1, pos2;
|
|
if ((pos2 = rev.findRev('.')) > 0 &&
|
|
(pos1 = rev.findRev('.', pos2-1)) > 0)
|
|
{
|
|
// e. g. for rev = 1.1.2.3 we have
|
|
// branchrev = 1.1.2, branchpoint = 1.1
|
|
branchrev = rev.left(pos2);
|
|
branchpoint = rev.left(pos1);
|
|
}
|
|
|
|
if (branchrev.isEmpty())
|
|
{
|
|
// Most probably we are on the trunk
|
|
setNumRows(numRows()+1);
|
|
setNumCols(1);
|
|
LogTreeItem *item = new LogTreeItem;
|
|
item->m_logInfo = logInfo;
|
|
item->branchpoint = branchpoint;
|
|
item->firstonbranch = false;
|
|
item->row = numRows()-1;
|
|
item->col = 0;
|
|
item->selected = false;
|
|
items.append(item);
|
|
return;
|
|
}
|
|
|
|
// look whether we have revisions on this branch
|
|
// shift them up
|
|
int row=-1, col=-1;
|
|
TQPtrListIterator<LogTreeItem> it(items);
|
|
for (; it.current(); ++it)
|
|
{
|
|
if (branchrev == (it.current()->m_logInfo.m_revision).left(branchrev.length()))
|
|
{
|
|
it.current()->firstonbranch = false;
|
|
row = it.current()->row;
|
|
col = it.current()->col;
|
|
it.current()->row--;
|
|
// Are we at the top of the widget?
|
|
if (row == 0)
|
|
{
|
|
TQPtrListIterator<LogTreeItem> it2(items);
|
|
for (; it2.current(); ++it2)
|
|
it2.current()->row++;
|
|
setNumRows(numRows()+1);
|
|
row = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (row == -1)
|
|
{
|
|
// Ok, so we must open a new branch
|
|
// Let's find the branch point
|
|
TQPtrListIterator<LogTreeItem> it3(items);
|
|
for (it3.toLast(); it3.current(); --it3)
|
|
{
|
|
if (branchpoint == it3.current()->m_logInfo.m_revision)
|
|
{
|
|
// Move existing branches to the right
|
|
TQPtrListIterator<LogTreeItem> it4(items);
|
|
for (; it4.current(); ++it4)
|
|
if (it4.current()->col > it3.current()->col)
|
|
{
|
|
it4.current()->col++;
|
|
}
|
|
setNumCols(numCols()+1);
|
|
row = it3.current()->row-1;
|
|
col = it3.current()->col+1;
|
|
if (row == -1)
|
|
{
|
|
TQPtrListIterator<LogTreeItem> it5(items);
|
|
for (; it5.current(); ++it5)
|
|
it5.current()->row++;
|
|
setNumRows(numRows()+1);
|
|
row = 0;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
LogTreeItem *item = new LogTreeItem;
|
|
item->m_logInfo = logInfo;
|
|
item->branchpoint = branchpoint;
|
|
item->firstonbranch = true;
|
|
item->row = row;
|
|
item->col = col;
|
|
item->selected = false;
|
|
items.append(item);
|
|
|
|
#if 0
|
|
cout << "Dump: " << endl;
|
|
cout << "Rows: " << numRows() << "Cols: " << numCols() << endl;
|
|
TQPtrListIterator<LogTreeItem> it5(items);
|
|
for (; it5.current(); ++it5)
|
|
{
|
|
cout << "Rev: "<< it5.current()->rev << endl;
|
|
cout << "row: "<< it5.current()->row << ", col: " << it5.current()->col << endl;
|
|
cout << "fob: "<< it5.current()->firstonbranch << endl;
|
|
}
|
|
cout << "End Dump" << endl;
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
void LogTreeView::collectConnections()
|
|
{
|
|
TQPtrListIterator<LogTreeItem> it(items);
|
|
for (; it.current(); ++it)
|
|
{
|
|
TQString rev = it.current()->m_logInfo.m_revision;
|
|
|
|
TQPtrListIterator<LogTreeItem> it2(items);
|
|
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();
|
|
connections.append(conn);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void LogTreeView::setSelectedPair(TQString selectionA, TQString selectionB)
|
|
{
|
|
TQPtrListIterator<LogTreeItem> it(items);
|
|
for(; it.current(); ++it)
|
|
{
|
|
bool oldstate = it.current()->selected;
|
|
bool newstate = ( selectionA == it.current()->m_logInfo.m_revision ||
|
|
selectionB == it.current()->m_logInfo.m_revision );
|
|
if (oldstate != newstate)
|
|
{
|
|
it.current()->selected = newstate;
|
|
repaint(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
TQSize LogTreeView::sizeHint() const
|
|
{
|
|
return TQSize(2 * static_width, 3 * static_height);
|
|
}
|
|
|
|
|
|
TQString LogTreeView::text(int row, int col) const
|
|
{
|
|
LogTreeItem* item = 0;
|
|
|
|
TQPtrListIterator<LogTreeItem> it(items);
|
|
for( ; it.current(); ++it )
|
|
{
|
|
if( it.current()->col == col && it.current()->row == row )
|
|
{
|
|
item = it.current();
|
|
break;
|
|
}
|
|
}
|
|
|
|
TQString text;
|
|
|
|
if( item && !item->m_logInfo.m_author.isNull() )
|
|
text = item->m_logInfo.createToolTipText();
|
|
|
|
return text;
|
|
}
|
|
|
|
|
|
void LogTreeView::paintCell(TQPainter *p, int row, int col, const TQRect& cr,
|
|
bool selected, const TQColorGroup& cg)
|
|
{
|
|
Q_UNUSED(selected)
|
|
Q_UNUSED(cr)
|
|
bool followed, branched;
|
|
LogTreeItem *item;
|
|
|
|
branched = false;
|
|
followed = false;
|
|
item = 0;
|
|
|
|
TQPtrListIterator<LogTreeItem> it(items);
|
|
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();
|
|
}
|
|
TQPtrListIterator<LogTreeConnection> it2(connections);
|
|
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, columnWidth(col), rowHeight(row),
|
|
cg.base());
|
|
p->setPen(cg.text());
|
|
if (item)
|
|
paintRevisionCell(p, row, col, item->m_logInfo,
|
|
followed, branched, item->selected);
|
|
else if (followed || branched)
|
|
paintConnector(p, row, col, followed, branched);
|
|
}
|
|
|
|
|
|
void LogTreeView::paintConnector(TQPainter *p,
|
|
int row, int col, bool followed, bool branched)
|
|
{
|
|
const int midx = columnWidth(col) / 2;
|
|
const int midy = rowHeight(row) / 2;
|
|
|
|
p->drawLine(0, midy, branched ? columnWidth(col) : midx, midy);
|
|
if (followed)
|
|
p->drawLine(midx, midy, midx, 0);
|
|
}
|
|
|
|
|
|
TQSize LogTreeView::computeSize(const Cervisia::LogInfo& logInfo,
|
|
int* authorHeight,
|
|
int* tagsHeight) const
|
|
{
|
|
const TQFontMetrics fm(fontMetrics());
|
|
|
|
const TQString tags(logInfo.tagsToString(Cervisia::TagInfo::Branch | Cervisia::TagInfo::Tag,
|
|
Cervisia::TagInfo::Branch));
|
|
|
|
const TQSize r1 = fm.size(AlignCenter, logInfo.m_revision);
|
|
const TQSize r3 = fm.size(AlignCenter, logInfo.m_author);
|
|
|
|
if (authorHeight)
|
|
*authorHeight = r3.height();
|
|
|
|
int infoWidth = kMax(static_width - 2 * BORDER, kMax(r1.width(), r3.width()));
|
|
int infoHeight = r1.height() + r3.height() + 3 * INSPACE;
|
|
|
|
if (!tags.isEmpty())
|
|
{
|
|
const TQSize r2 = fm.size(AlignCenter, tags);
|
|
infoWidth = kMax(infoWidth, r2.width());
|
|
infoHeight += r2.height() + INSPACE;
|
|
if (tagsHeight)
|
|
*tagsHeight = r2.height();
|
|
}
|
|
else
|
|
{
|
|
if (tagsHeight)
|
|
*tagsHeight = 0;
|
|
}
|
|
infoWidth += 2 * INSPACE;
|
|
|
|
return TQSize(infoWidth, infoHeight);
|
|
}
|
|
|
|
|
|
void LogTreeView::paintRevisionCell(TQPainter *p,
|
|
int row, int col,
|
|
const Cervisia::LogInfo& logInfo,
|
|
bool followed, bool branched, bool selected)
|
|
{
|
|
int authorHeight;
|
|
int tagsHeight;
|
|
const TQSize infoSize(computeSize(logInfo, &authorHeight, &tagsHeight));
|
|
const TQSize cellSize(columnWidth(col), rowHeight(row));
|
|
|
|
const int midx(cellSize.width() / 2);
|
|
const int midy(cellSize.height() / 2);
|
|
|
|
TQRect rect(TQPoint((cellSize.width() - infoSize.width()) / 2,
|
|
(cellSize.height() - infoSize.height()) / 2),
|
|
infoSize);
|
|
|
|
// Connectors
|
|
if (followed)
|
|
p->drawLine(midx, 0, midx, rect.y()); // to the top
|
|
|
|
if (branched)
|
|
p->drawLine(rect.x() + infoSize.width(), midy, cellSize.width(), midy); // to the right
|
|
|
|
p->drawLine(midx, rect.y() + infoSize.height(), midx, cellSize.height()); // to the bottom
|
|
|
|
// The box itself
|
|
if (selected)
|
|
{
|
|
p->fillRect(rect, KGlobalSettings::highlightColor());
|
|
p->setPen(KGlobalSettings::highlightedTextColor());
|
|
}
|
|
else
|
|
{
|
|
p->drawRoundRect(rect, 10, 10);
|
|
}
|
|
|
|
rect.setY(rect.y() + INSPACE);
|
|
|
|
p->drawText(rect, AlignHCenter, logInfo.m_author);
|
|
rect.setY(rect.y() + authorHeight + INSPACE);
|
|
|
|
const TQString tags(logInfo.tagsToString(Cervisia::TagInfo::Branch | Cervisia::TagInfo::Tag,
|
|
Cervisia::TagInfo::Branch));
|
|
if (!tags.isEmpty())
|
|
{
|
|
const TQFont font(p->font());
|
|
TQFont underline(font);
|
|
underline.setUnderline(true);
|
|
|
|
p->setFont(underline);
|
|
p->drawText(rect, AlignHCenter, tags);
|
|
p->setFont(font);
|
|
|
|
rect.setY(rect.y() + tagsHeight + INSPACE);
|
|
}
|
|
|
|
p->drawText(rect, AlignHCenter, logInfo.m_revision);
|
|
}
|
|
|
|
|
|
void LogTreeView::contentsMousePressEvent(TQMouseEvent *e)
|
|
{
|
|
if ( e->button() == Qt::MidButton ||
|
|
e->button() == Qt::LeftButton)
|
|
{
|
|
int row = rowAt( e->pos().y() );
|
|
int col = columnAt( e->pos().x() );
|
|
|
|
TQPtrListIterator<LogTreeItem> it(items);
|
|
for(; it.current(); ++it)
|
|
if (it.current()->row == row
|
|
&& it.current()->col == col)
|
|
{
|
|
// Change selection for revision B if the middle mouse button or
|
|
// the left mouse button with the control key was pressed
|
|
bool changeRevB = (e->button() == Qt::MidButton) ||
|
|
(e->button() == Qt::LeftButton &&
|
|
e->state() & ControlButton);
|
|
|
|
emit revisionClicked(it.current()->m_logInfo.m_revision, changeRevB);
|
|
break;
|
|
}
|
|
}
|
|
|
|
viewport()->update();
|
|
}
|
|
|
|
|
|
void LogTreeView::recomputeCellSizes ()
|
|
{
|
|
// Compute maximum for each column and row
|
|
for (TQPtrListIterator<LogTreeItem> it(items); it.current(); ++it)
|
|
{
|
|
const LogTreeItem *item = it.current();
|
|
|
|
const TQSize cellSize(computeSize(item->m_logInfo) + TQSize(2 * BORDER, 2 * BORDER));
|
|
|
|
setColumnWidth(item->col, kMax(columnWidth(item->col), cellSize.width()));
|
|
setRowHeight(item->row, kMax(rowHeight(item->row), cellSize.height()));
|
|
}
|
|
|
|
viewport()->update();
|
|
}
|
|
|
|
|
|
void LogTreeView::slotQueryToolTip(const TQPoint& viewportPos,
|
|
TQRect& viewportRect,
|
|
TQString& tipText)
|
|
{
|
|
const TQPoint contentsPos(viewportToContents(viewportPos));
|
|
const int column(columnAt(contentsPos.x()));
|
|
const int row(rowAt(contentsPos.y()));
|
|
|
|
tipText = text(row, column);
|
|
if (tipText.isEmpty())
|
|
return;
|
|
|
|
viewportRect = cellGeometry(row, column);
|
|
viewportRect.moveTopLeft(contentsToViewport(viewportRect.topLeft()));
|
|
}
|
|
|
|
|
|
#include "logtree.moc"
|
|
|
|
|
|
// Local Variables:
|
|
// c-basic-offset: 4
|
|
// End:
|