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: