/* * Copyright (C) 1999-2002 Bernd Gehrmann * bernd@mail.berlios.de * Copyright (c) 2003-2004 Christian Loose * * 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 #include #include #include #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(TQWidget::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 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 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 it3(items); for (it3.toLast(); it3.current(); --it3) { if (branchpoint == it3.current()->m_logInfo.m_revision) { // Move existing branches to the right TQPtrListIterator 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 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 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 it(items); for (; it.current(); ++it) { TQString rev = it.current()->m_logInfo.m_revision; TQPtrListIterator 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 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 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 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 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, TDEGlobalSettings::highlightColor()); p->setPen(TDEGlobalSettings::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 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 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"