Warning, file /sdk/cervisia/logtree.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002  *  Copyright (C) 1999-2002 Bernd Gehrmann
0003  *                          bernd@mail.berlios.de
0004  *  Copyright (c) 2003-2004 Christian Loose <christian.loose@hamburg.de>
0005  *
0006  * This program is free software; you can redistribute it and/or modify
0007  * it under the terms of the GNU General Public License as published by
0008  * the Free Software Foundation; either version 2 of the License, or
0009  * (at your option) any later version.
0010  *
0011  * This program is distributed in the hope that it will be useful,
0012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0014  * GNU General Public License for more details.
0015  *
0016  * You should have received a copy of the GNU General Public License
0017  * along with this program; if not, write to the Free Software
0018  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
0019  */
0020 
0021 #include "logtree.h"
0022 
0023 #include <QApplication>
0024 #include <kcolorscheme.h>
0025 #include <qpainter.h>
0026 
0027 #include "loginfo.h"
0028 #include "tooltip.h"
0029 
0030 #include <QHeaderView>
0031 
0032 const int LogTreeView::BORDER = 5;
0033 const int LogTreeView::INSPACE = 3;
0034 
0035 namespace
0036 {
0037 bool static_initialized = false;
0038 int static_width;
0039 int static_height;
0040 }
0041 
0042 class LogTreeItem
0043 {
0044 public:
0045     Cervisia::LogInfo m_logInfo;
0046     QString branchpoint;
0047     bool firstonbranch;
0048     int row;
0049     int col;
0050     SelectedRevision selected;
0051 };
0052 
0053 class LogTreeConnection
0054 {
0055 public:
0056     LogTreeItem *start;
0057     LogTreeItem *end;
0058 };
0059 
0060 LogTreeView::LogTreeView(QWidget *parent, const char *name)
0061     : QTableView(parent)
0062     , rowCount(0)
0063     , columnCount(1)
0064 {
0065     setObjectName(QLatin1String(name));
0066 
0067     if (!static_initialized) {
0068         static_initialized = true;
0069         QFontMetrics fm(fontMetrics());
0070         static_width = fm.width("1234567890") + 2 * BORDER + 2 * INSPACE;
0071         static_height = 2 * fm.height() + 2 * BORDER + 3 * INSPACE;
0072     }
0073 
0074     setItemDelegate(new LogTreeDelegate(this));
0075     setModel(model = new LogTreeModel(this));
0076 
0077     setSelectionMode(QAbstractItemView::NoSelection);
0078     setShowGrid(false);
0079     horizontalHeader()->hide();
0080     verticalHeader()->hide();
0081     setFrameStyle(QFrame::WinPanel | QFrame::Sunken);
0082     setBackgroundRole(QPalette::Base);
0083     setFocusPolicy(Qt::NoFocus);
0084     setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
0085     setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
0086 
0087     auto toolTip = new Cervisia::ToolTip(viewport());
0088 
0089     connect(toolTip, SIGNAL(queryToolTip(QPoint, QRect &, QString &)), this, SLOT(slotQueryToolTip(QPoint, QRect &, QString &)));
0090 
0091     connect(this, SIGNAL(pressed(QModelIndex)), this, SLOT(mousePressed(QModelIndex)));
0092 }
0093 
0094 LogTreeView::~LogTreeView()
0095 {
0096     qDeleteAll(items);
0097     qDeleteAll(connections);
0098 }
0099 
0100 void LogTreeView::addRevision(const Cervisia::LogInfo &logInfo)
0101 {
0102     QString branchpoint, branchrev;
0103 
0104     const QString rev(logInfo.m_revision);
0105 
0106     // find branch
0107     int pos1, pos2;
0108     if ((pos2 = rev.lastIndexOf('.')) > 0 && (pos1 = rev.lastIndexOf('.', pos2 - 1)) > 0) {
0109         // e. g. for rev = 1.1.2.3 we have
0110         // branchrev = 1.1.2, branchpoint = 1.1
0111         branchrev = rev.left(pos2);
0112         branchpoint = rev.left(pos1);
0113     }
0114 
0115     if (branchrev.isEmpty()) {
0116         // Most probably we are on the trunk
0117         model->beginInsertRows(QModelIndex(), rowCount, rowCount);
0118         rowCount++;
0119         auto item = new LogTreeItem;
0120         item->m_logInfo = logInfo;
0121         item->branchpoint = branchpoint;
0122         item->firstonbranch = false;
0123         item->row = rowCount - 1;
0124         item->col = 0;
0125         item->selected = NoRevision;
0126         items.append(item);
0127         model->endInsertRows();
0128         return;
0129     }
0130 
0131     // look whether we have revisions on this branch
0132     // shift them up
0133     int row = -1, col = -1;
0134     foreach (LogTreeItem *item1, items) {
0135         if (branchrev == (item1->m_logInfo.m_revision).left(branchrev.length())) {
0136             item1->firstonbranch = false;
0137             row = item1->row;
0138             col = item1->col;
0139             item1->row--;
0140             // Are we at the top of the widget?
0141             if (row == 0) {
0142                 foreach (LogTreeItem *item2, items)
0143                     item2->row++;
0144                 model->beginInsertRows(QModelIndex(), rowCount, rowCount);
0145                 rowCount++;
0146                 row = 1;
0147                 model->endInsertRows();
0148             }
0149         }
0150     }
0151 
0152     if (row == -1) {
0153         // Ok, so we must open a new branch
0154         // Let's find the branch point
0155         QListIterator<LogTreeItem *> iter(items);
0156         iter.toBack();
0157         while (iter.hasPrevious()) {
0158             LogTreeItem *item1(iter.previous());
0159             if (branchpoint == item1->m_logInfo.m_revision) {
0160                 // Move existing branches to the right
0161                 foreach (LogTreeItem *item2, items)
0162                     if (item2->col > item1->col)
0163                         item2->col++;
0164                 model->beginInsertColumns(QModelIndex(), columnCount, columnCount);
0165                 columnCount++;
0166                 row = item1->row - 1;
0167                 col = item1->col + 1;
0168                 if (row == -1) {
0169                     foreach (LogTreeItem *item3, items)
0170                         item3->row++;
0171                     model->beginInsertRows(QModelIndex(), rowCount, rowCount);
0172                     rowCount++;
0173                     row = 0;
0174                     model->endInsertRows();
0175                 }
0176                 model->endInsertColumns();
0177                 break;
0178             }
0179         }
0180     }
0181 
0182     auto item = new LogTreeItem;
0183     item->m_logInfo = logInfo;
0184     item->branchpoint = branchpoint;
0185     item->firstonbranch = true;
0186     item->row = row;
0187     item->col = col;
0188     item->selected = NoRevision;
0189     items.append(item);
0190 
0191 #if 0
0192     qCDebug(log_cervisia) << "Dump:";
0193     qCDebug(log_cervisia) << "Rows:" << numRows() << "Cols:" << numCols();
0194     foreach (LogTreeItem* treeItem, items)
0195     {
0196         qCDebug(log_cervisia) << "Rev:" << treeItem->m_logInfo.m_revision;
0197         qCDebug(log_cervisia) << "row:" << treeItem->row << ", col:" << treeItem->col;
0198         qCDebug(log_cervisia) << "fob:" << treeItem->firstonbranch;
0199     }
0200     qCDebug(log_cervisia) << "End Dump";
0201 #endif
0202 }
0203 
0204 void LogTreeView::collectConnections()
0205 {
0206     for (LogTreeItemList::const_iterator it(items.begin()), itEnd(items.end()); it != itEnd; ++it) {
0207         QString rev = (*it)->m_logInfo.m_revision;
0208 
0209         LogTreeItemList::const_iterator it2(it);
0210         ++it2;
0211         for (; it2 != itEnd; ++it2)
0212             if ((*it2)->branchpoint == rev && (*it2)->firstonbranch) {
0213                 auto conn = new LogTreeConnection;
0214                 conn->start = (*it);
0215                 conn->end = (*it2);
0216                 connections.append(conn);
0217             }
0218     }
0219 }
0220 
0221 void LogTreeView::setSelectedPair(QString selectionA, QString selectionB)
0222 {
0223     foreach (LogTreeItem *item, items) {
0224         const SelectedRevision oldSelection = item->selected;
0225         SelectedRevision newSelection;
0226 
0227         if (selectionA == item->m_logInfo.m_revision)
0228             newSelection = RevisionA;
0229         else if (selectionB == item->m_logInfo.m_revision)
0230             newSelection = RevisionB;
0231         else
0232             newSelection = NoRevision;
0233 
0234         if (oldSelection != newSelection) {
0235             item->selected = newSelection;
0236             viewport()->update();
0237         }
0238     }
0239 }
0240 
0241 QSize LogTreeView::sizeHint() const
0242 {
0243     return {2 * static_width, 3 * static_height};
0244 }
0245 
0246 QString LogTreeView::text(int row, int col) const
0247 {
0248     LogTreeItem *item = 0;
0249 
0250     foreach (LogTreeItem *treeItem, items) {
0251         if (treeItem->col == col && treeItem->row == row) {
0252             item = treeItem;
0253             break;
0254         }
0255     }
0256 
0257     QString text;
0258 
0259     if (item && !item->m_logInfo.m_author.isNull())
0260         text = item->m_logInfo.createToolTipText();
0261 
0262     return text;
0263 }
0264 
0265 void LogTreeView::paintCell(QPainter *p, int row, int col)
0266 {
0267     bool followed, branched;
0268     LogTreeItem *item;
0269 
0270     branched = false;
0271     followed = false;
0272     item = 0;
0273 
0274     foreach (LogTreeItem *treeItem, items) {
0275         int itcol = treeItem->col;
0276         int itrow = treeItem->row;
0277         if (itrow == row - 1 && itcol == col)
0278             followed = true;
0279         if (itrow == row && itcol == col)
0280             item = treeItem;
0281     }
0282     foreach (LogTreeConnection *connection, connections) {
0283         int itcol1 = connection->start->col;
0284         int itcol2 = connection->end->col;
0285         int itrow = connection->start->row;
0286         if (itrow == row && itcol1 <= col && itcol2 > col)
0287             branched = true;
0288     }
0289 
0290     if (item)
0291         paintRevisionCell(p, row, col, item->m_logInfo, followed, branched, item->selected);
0292     else if (followed || branched)
0293         paintConnector(p, row, col, followed, branched);
0294 }
0295 
0296 void LogTreeView::paintConnector(QPainter *p, int row, int col, bool followed, bool branched)
0297 {
0298     const int midx = columnWidth(col) / 2;
0299     const int midy = rowHeight(row) / 2;
0300 
0301     p->drawLine(0, midy, branched ? columnWidth(col) : midx, midy);
0302     if (followed)
0303         p->drawLine(midx, midy, midx, 0);
0304 }
0305 
0306 QSize LogTreeView::computeSize(const Cervisia::LogInfo &logInfo, int *authorHeight, int *tagsHeight) const
0307 {
0308     const QFontMetrics fm(fontMetrics());
0309 
0310     const QString tags(logInfo.tagsToString(Cervisia::TagInfo::Branch | Cervisia::TagInfo::Tag, Cervisia::TagInfo::Branch));
0311 
0312     const QSize r1 = fm.size(Qt::AlignCenter, logInfo.m_revision);
0313     const QSize r3 = fm.size(Qt::AlignCenter, logInfo.m_author);
0314 
0315     if (authorHeight)
0316         *authorHeight = r3.height();
0317 
0318     int infoWidth = qMax(static_width - 2 * BORDER, qMax(r1.width(), r3.width()));
0319     int infoHeight = r1.height() + r3.height() + 3 * INSPACE;
0320 
0321     if (!tags.isEmpty()) {
0322         const QSize r2 = fm.size(Qt::AlignCenter, tags);
0323         infoWidth = qMax(infoWidth, r2.width());
0324         infoHeight += r2.height() + INSPACE;
0325         if (tagsHeight)
0326             *tagsHeight = r2.height();
0327     } else {
0328         if (tagsHeight)
0329             *tagsHeight = 0;
0330     }
0331     infoWidth += 2 * INSPACE;
0332 
0333     return {infoWidth, infoHeight};
0334 }
0335 
0336 void LogTreeView::paintRevisionCell(QPainter *p, int row, int col, const Cervisia::LogInfo &logInfo, bool followed, bool branched, SelectedRevision selected)
0337 {
0338     int authorHeight;
0339     int tagsHeight;
0340     const QSize infoSize(computeSize(logInfo, &authorHeight, &tagsHeight));
0341     const QSize cellSize(columnWidth(col), rowHeight(row));
0342 
0343     const int midx(cellSize.width() / 2);
0344     const int midy(cellSize.height() / 2);
0345 
0346     QRect rect(QPoint((cellSize.width() - infoSize.width()) / 2, (cellSize.height() - infoSize.height()) / 2), infoSize);
0347 
0348     // Connectors
0349     if (followed)
0350         p->drawLine(midx, 0, midx, rect.y()); // to the top
0351 
0352     if (branched)
0353         p->drawLine(rect.x() + infoSize.width(), midy, cellSize.width(), midy); // to the right
0354 
0355     p->drawLine(midx, rect.y() + infoSize.height(), midx, cellSize.height()); // to the bottom
0356 
0357     // The box itself
0358     if (selected == NoRevision) {
0359         p->drawRoundedRect(rect, 10, 10);
0360     } else {
0361         if (selected == RevisionA) {
0362             p->fillRect(rect, KColorScheme(QPalette::Active, KColorScheme::Selection).background());
0363             p->setPen(KColorScheme(QPalette::Active, KColorScheme::Selection).foreground().color());
0364             p->drawText(rect, Qt::AlignLeft | Qt::AlignTop, "A");
0365         } else {
0366             p->fillRect(rect, KColorScheme(QPalette::Active, KColorScheme::Selection).background().color().lighter(130));
0367             p->setPen(KColorScheme(QPalette::Active, KColorScheme::Selection).foreground().color().lighter(130));
0368             p->drawText(rect, Qt::AlignLeft | Qt::AlignTop, "B");
0369         }
0370     }
0371 
0372     rect.setY(rect.y() + INSPACE);
0373 
0374     p->drawText(rect, Qt::AlignHCenter, logInfo.m_author);
0375     rect.setY(rect.y() + authorHeight + INSPACE);
0376 
0377     const QString tags(logInfo.tagsToString(Cervisia::TagInfo::Branch | Cervisia::TagInfo::Tag, Cervisia::TagInfo::Branch));
0378     if (!tags.isEmpty()) {
0379         const QFont font(p->font());
0380         QFont underline(font);
0381         underline.setUnderline(true);
0382 
0383         p->setFont(underline);
0384         p->drawText(rect, Qt::AlignHCenter, tags);
0385         p->setFont(font);
0386 
0387         rect.setY(rect.y() + tagsHeight + INSPACE);
0388     }
0389 
0390     p->drawText(rect, Qt::AlignHCenter, logInfo.m_revision);
0391 }
0392 
0393 void LogTreeView::mousePressed(const QModelIndex &index)
0394 {
0395     Qt::MouseButtons buttons = QApplication::mouseButtons();
0396 
0397     if (buttons == Qt::MiddleButton || buttons == Qt::LeftButton) {
0398         int row = index.row();
0399         int col = index.column();
0400 
0401         foreach (LogTreeItem *item, items) {
0402             if (item->row == row && item->col == col) {
0403                 // Change selection for revision B if the middle mouse button or
0404                 // the left mouse button with the control key was pressed
0405                 bool changeRevB = (buttons == Qt::MiddleButton) || (buttons == Qt::LeftButton && QApplication::keyboardModifiers() & Qt::ControlModifier);
0406 
0407                 emit revisionClicked(item->m_logInfo.m_revision, changeRevB);
0408                 viewport()->update();
0409                 break;
0410             }
0411         }
0412     }
0413 }
0414 
0415 void LogTreeView::recomputeCellSizes()
0416 {
0417     // Compute maximum for each column and row
0418     foreach (const LogTreeItem *item, items) {
0419         const QSize cellSize(computeSize(item->m_logInfo) + QSize(2 * BORDER, 2 * BORDER));
0420 
0421         setColumnWidth(item->col, qMax(columnWidth(item->col), cellSize.width()));
0422         setRowHeight(item->row, qMax(rowHeight(item->row), cellSize.height()));
0423     }
0424 
0425     viewport()->update();
0426 }
0427 
0428 void LogTreeView::slotQueryToolTip(const QPoint &viewportPos, QRect &viewportRect, QString &tipText)
0429 {
0430     QModelIndex index(indexAt(viewportPos));
0431 
0432     tipText = text(index.row(), index.column());
0433     if (tipText.isEmpty())
0434         return;
0435 
0436     viewportRect = visualRect(index);
0437 }
0438 
0439 //---------------------------------------------------------------------
0440 
0441 void LogTreeDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
0442 {
0443     painter->save();
0444 
0445     painter->translate(option.rect.x(), option.rect.y());
0446     logView->paintCell(painter, index.row(), index.column());
0447 
0448     painter->restore();
0449 }
0450 
0451 // Local Variables:
0452 // c-basic-offset: 4
0453 // End: