File indexing completed on 2024-04-28 05:41:30
0001 /* 0002 This file is part of KCachegrind. 0003 0004 SPDX-FileCopyrightText: 2007-2016 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> 0005 0006 SPDX-License-Identifier: GPL-2.0-only 0007 */ 0008 0009 /* 0010 * Callgraph View 0011 */ 0012 0013 #include "callgraphview.h" 0014 0015 #include <stdlib.h> 0016 #include <math.h> 0017 0018 #include <QApplication> 0019 #include <QDebug> 0020 #include <QDesktopServices> 0021 #include <QFile> 0022 #include <QFileDialog> 0023 #include <QTemporaryFile> 0024 #include <QTextStream> 0025 #include <QTransform> 0026 #include <QPair> 0027 #include <QPainter> 0028 #include <QStyle> 0029 #include <QScrollBar> 0030 #include <QResizeEvent> 0031 #include <QMouseEvent> 0032 #include <QFocusEvent> 0033 #include <QKeyEvent> 0034 #include <QStyleOptionGraphicsItem> 0035 #include <QContextMenuEvent> 0036 #include <QList> 0037 #include <QPixmap> 0038 #include <QScreen> 0039 #include <QProcess> 0040 #include <QMenu> 0041 0042 0043 #include "config.h" 0044 #include "globalguiconfig.h" 0045 #include "listutils.h" 0046 0047 0048 #define DEBUG_GRAPH 0 0049 0050 // CallGraphView defaults 0051 0052 #define DEFAULT_FUNCLIMIT .05 0053 #define DEFAULT_CALLLIMIT 1. 0054 #define DEFAULT_MAXCALLER 2 0055 #define DEFAULT_MAXCALLEE -1 0056 #define DEFAULT_SHOWSKIPPED false 0057 #define DEFAULT_EXPANDCYCLES false 0058 #define DEFAULT_CLUSTERGROUPS false 0059 #define DEFAULT_DETAILLEVEL 1 0060 #define DEFAULT_LAYOUT GraphOptions::TopDown 0061 #define DEFAULT_ZOOMPOS Auto 0062 0063 0064 // LessThen functors as helpers for sorting of graph edges 0065 // for keyboard navigation. Sorting is done according to 0066 // the angle at which a edge spline goes out or in of a function. 0067 0068 // Sort angles of outgoing edges (edge seen as attached to the caller) 0069 class CallerGraphEdgeLessThan 0070 { 0071 public: 0072 bool operator()(const GraphEdge* ge1, const GraphEdge* ge2) const 0073 { 0074 const CanvasEdge* ce1 = ge1->canvasEdge(); 0075 const CanvasEdge* ce2 = ge2->canvasEdge(); 0076 0077 // sort invisible edges (ie. without matching CanvasEdge) in front 0078 if (!ce1 && !ce2) { 0079 // strict ordering required for std::sort 0080 return (ge1 < ge2); 0081 } 0082 if (!ce1) return true; 0083 if (!ce2) return false; 0084 0085 QPolygon p1 = ce1->controlPoints(); 0086 QPolygon p2 = ce2->controlPoints(); 0087 QPoint d1 = p1.point(1) - p1.point(0); 0088 QPoint d2 = p2.point(1) - p2.point(0); 0089 double angle1 = atan2(double(d1.y()), double(d1.x())); 0090 double angle2 = atan2(double(d2.y()), double(d2.x())); 0091 0092 return (angle1 < angle2); 0093 } 0094 }; 0095 0096 // Sort angles of ingoing edges (edge seen as attached to the callee) 0097 class CalleeGraphEdgeLessThan 0098 { 0099 public: 0100 bool operator()(const GraphEdge* ge1, const GraphEdge* ge2) const 0101 { 0102 const CanvasEdge* ce1 = ge1->canvasEdge(); 0103 const CanvasEdge* ce2 = ge2->canvasEdge(); 0104 0105 // sort invisible edges (ie. without matching CanvasEdge) in front 0106 if (!ce1) return true; 0107 if (!ce2) return false; 0108 0109 QPolygon p1 = ce1->controlPoints(); 0110 QPolygon p2 = ce2->controlPoints(); 0111 QPoint d1 = p1.point(p1.count()-2) - p1.point(p1.count()-1); 0112 QPoint d2 = p2.point(p2.count()-2) - p2.point(p2.count()-1); 0113 double angle1 = atan2(double(d1.y()), double(d1.x())); 0114 double angle2 = atan2(double(d2.y()), double(d2.x())); 0115 0116 // for ingoing edges sort according to descending angles 0117 return (angle2 < angle1); 0118 } 0119 }; 0120 0121 0122 0123 // 0124 // GraphNode 0125 // 0126 0127 GraphNode::GraphNode() 0128 { 0129 _f=nullptr; 0130 self = incl = 0; 0131 _cn = nullptr; 0132 0133 _visible = false; 0134 _lastCallerIndex = _lastCalleeIndex = -1; 0135 0136 _lastFromCaller = true; 0137 } 0138 0139 void GraphNode::clearEdges() 0140 { 0141 callees.clear(); 0142 callers.clear(); 0143 } 0144 0145 CallerGraphEdgeLessThan callerGraphEdgeLessThan; 0146 CalleeGraphEdgeLessThan calleeGraphEdgeLessThan; 0147 0148 void GraphNode::sortEdges() 0149 { 0150 std::sort(callers.begin(), callers.end(), callerGraphEdgeLessThan); 0151 std::sort(callees.begin(), callees.end(), calleeGraphEdgeLessThan); 0152 } 0153 0154 void GraphNode::addCallee(GraphEdge* e) 0155 { 0156 if (e) 0157 callees.append(e); 0158 } 0159 0160 void GraphNode::addCaller(GraphEdge* e) 0161 { 0162 if (e) 0163 callers.append(e); 0164 } 0165 0166 void GraphNode::addUniqueCallee(GraphEdge* e) 0167 { 0168 if (e && (callees.count(e) == 0)) 0169 callees.append(e); 0170 } 0171 0172 void GraphNode::addUniqueCaller(GraphEdge* e) 0173 { 0174 if (e && (callers.count(e) == 0)) 0175 callers.append(e); 0176 } 0177 0178 void GraphNode::removeEdge(GraphEdge* e) 0179 { 0180 callers.removeAll(e); 0181 callees.removeAll(e); 0182 } 0183 0184 double GraphNode::calleeCostSum() 0185 { 0186 double sum = 0.0; 0187 0188 foreach(GraphEdge* e, callees) 0189 sum += e->cost; 0190 0191 return sum; 0192 } 0193 0194 double GraphNode::calleeCountSum() 0195 { 0196 double sum = 0.0; 0197 0198 foreach(GraphEdge* e, callees) 0199 sum += e->count; 0200 0201 return sum; 0202 } 0203 0204 double GraphNode::callerCostSum() 0205 { 0206 double sum = 0.0; 0207 0208 foreach(GraphEdge* e, callers) 0209 sum += e->cost; 0210 0211 return sum; 0212 } 0213 0214 double GraphNode::callerCountSum() 0215 { 0216 double sum = 0.0; 0217 0218 foreach(GraphEdge* e, callers) 0219 sum += e->count; 0220 0221 return sum; 0222 } 0223 0224 0225 TraceCall* GraphNode::visibleCaller() 0226 { 0227 if (0) 0228 qDebug("GraphNode::visibleCaller %s: last %d, count %d", 0229 qPrintable(_f->prettyName()), _lastCallerIndex, (int) callers.count()); 0230 0231 // can not use at(): index can be -1 (out of bounds), result is 0 then 0232 GraphEdge* e = callers.value(_lastCallerIndex); 0233 if (e && !e->isVisible()) 0234 e = nullptr; 0235 if (!e) { 0236 double maxCost = 0.0; 0237 GraphEdge* maxEdge = nullptr; 0238 for(int i = 0; i<callers.size(); i++) { 0239 e = callers[i]; 0240 if (e->isVisible() && (e->cost > maxCost)) { 0241 maxCost = e->cost; 0242 maxEdge = e; 0243 _lastCallerIndex = i; 0244 } 0245 } 0246 e = maxEdge; 0247 } 0248 return e ? e->call() : nullptr; 0249 } 0250 0251 TraceCall* GraphNode::visibleCallee() 0252 { 0253 if (0) 0254 qDebug("GraphNode::visibleCallee %s: last %d, count %d", 0255 qPrintable(_f->prettyName()), _lastCalleeIndex, (int) callees.count()); 0256 0257 GraphEdge* e = callees.value(_lastCalleeIndex); 0258 if (e && !e->isVisible()) 0259 e = nullptr; 0260 0261 if (!e) { 0262 double maxCost = 0.0; 0263 GraphEdge* maxEdge = nullptr; 0264 for(int i = 0; i<callees.size(); i++) { 0265 e = callees[i]; 0266 if (e->isVisible() && (e->cost > maxCost)) { 0267 maxCost = e->cost; 0268 maxEdge = e; 0269 _lastCalleeIndex = i; 0270 } 0271 } 0272 e = maxEdge; 0273 } 0274 return e ? e->call() : nullptr; 0275 } 0276 0277 void GraphNode::setCallee(GraphEdge* e) 0278 { 0279 _lastCalleeIndex = callees.indexOf(e); 0280 _lastFromCaller = false; 0281 } 0282 0283 void GraphNode::setCaller(GraphEdge* e) 0284 { 0285 _lastCallerIndex = callers.indexOf(e); 0286 _lastFromCaller = true; 0287 } 0288 0289 TraceFunction* GraphNode::nextVisible() 0290 { 0291 TraceCall* c; 0292 0293 if (_lastFromCaller) { 0294 c = nextVisibleCaller(); 0295 if (c) 0296 return c->called(true); 0297 c = nextVisibleCallee(); 0298 if (c) 0299 return c->caller(true); 0300 } else { 0301 c = nextVisibleCallee(); 0302 if (c) 0303 return c->caller(true); 0304 c = nextVisibleCaller(); 0305 if (c) 0306 return c->called(true); 0307 } 0308 return nullptr; 0309 } 0310 0311 TraceFunction* GraphNode::priorVisible() 0312 { 0313 TraceCall* c; 0314 0315 if (_lastFromCaller) { 0316 c = priorVisibleCaller(); 0317 if (c) 0318 return c->called(true); 0319 c = priorVisibleCallee(); 0320 if (c) 0321 return c->caller(true); 0322 } else { 0323 c = priorVisibleCallee(); 0324 if (c) 0325 return c->caller(true); 0326 c = priorVisibleCaller(); 0327 if (c) 0328 return c->called(true); 0329 } 0330 return nullptr; 0331 } 0332 0333 TraceCall* GraphNode::nextVisibleCaller(GraphEdge* e) 0334 { 0335 int idx = e ? callers.indexOf(e) : _lastCallerIndex; 0336 idx++; 0337 while(idx < callers.size()) { 0338 if (callers[idx]->isVisible()) { 0339 _lastCallerIndex = idx; 0340 return callers[idx]->call(); 0341 } 0342 idx++; 0343 } 0344 return nullptr; 0345 } 0346 0347 TraceCall* GraphNode::nextVisibleCallee(GraphEdge* e) 0348 { 0349 int idx = e ? callees.indexOf(e) : _lastCalleeIndex; 0350 idx++; 0351 while(idx < callees.size()) { 0352 if (callees[idx]->isVisible()) { 0353 _lastCalleeIndex = idx; 0354 return callees[idx]->call(); 0355 } 0356 idx++; 0357 } 0358 return nullptr; 0359 } 0360 0361 TraceCall* GraphNode::priorVisibleCaller(GraphEdge* e) 0362 { 0363 int idx = e ? callers.indexOf(e) : _lastCallerIndex; 0364 0365 idx = (idx<0) ? callers.size()-1 : idx-1; 0366 while(idx >= 0) { 0367 if (callers[idx]->isVisible()) { 0368 _lastCallerIndex = idx; 0369 return callers[idx]->call(); 0370 } 0371 idx--; 0372 } 0373 return nullptr; 0374 } 0375 0376 TraceCall* GraphNode::priorVisibleCallee(GraphEdge* e) 0377 { 0378 int idx = e ? callees.indexOf(e) : _lastCalleeIndex; 0379 0380 idx = (idx<0) ? callees.size()-1 : idx-1; 0381 while(idx >= 0) { 0382 if (callees[idx]->isVisible()) { 0383 _lastCalleeIndex = idx; 0384 return callees[idx]->call(); 0385 } 0386 idx--; 0387 } 0388 return nullptr; 0389 } 0390 0391 0392 // 0393 // GraphEdge 0394 // 0395 0396 GraphEdge::GraphEdge() 0397 { 0398 _c=nullptr; 0399 _from = _to = nullptr; 0400 _fromNode = _toNode = nullptr; 0401 cost = count = 0; 0402 _ce = nullptr; 0403 0404 _visible = false; 0405 _lastFromCaller = true; 0406 } 0407 0408 QString GraphEdge::prettyName() 0409 { 0410 if (_c) 0411 return _c->prettyName(); 0412 0413 if (_from) 0414 return QObject::tr("Call(s) from %1").arg(_from->prettyName()); 0415 0416 if (_to) 0417 return QObject::tr("Call(s) to %1").arg(_to->prettyName()); 0418 0419 return QObject::tr("(unknown call)"); 0420 } 0421 0422 TraceFunction* GraphEdge::visibleCaller() 0423 { 0424 if (_from) { 0425 _lastFromCaller = true; 0426 if (_fromNode) 0427 _fromNode->setCallee(this); 0428 return _from; 0429 } 0430 return nullptr; 0431 } 0432 0433 TraceFunction* GraphEdge::visibleCallee() 0434 { 0435 if (_to) { 0436 _lastFromCaller = false; 0437 if (_toNode) 0438 _toNode->setCaller(this); 0439 return _to; 0440 } 0441 return nullptr; 0442 } 0443 0444 TraceCall* GraphEdge::nextVisible() 0445 { 0446 TraceCall* res = nullptr; 0447 0448 if (_lastFromCaller && _fromNode) { 0449 res = _fromNode->nextVisibleCallee(this); 0450 if (!res && _toNode) 0451 res = _toNode->nextVisibleCaller(this); 0452 } else if (_toNode) { 0453 res = _toNode->nextVisibleCaller(this); 0454 if (!res && _fromNode) 0455 res = _fromNode->nextVisibleCallee(this); 0456 } 0457 return res; 0458 } 0459 0460 TraceCall* GraphEdge::priorVisible() 0461 { 0462 TraceCall* res = nullptr; 0463 0464 if (_lastFromCaller && _fromNode) { 0465 res = _fromNode->priorVisibleCallee(this); 0466 if (!res && _toNode) 0467 res = _toNode->priorVisibleCaller(this); 0468 } else if (_toNode) { 0469 res = _toNode->priorVisibleCaller(this); 0470 if (!res && _fromNode) 0471 res = _fromNode->priorVisibleCallee(this); 0472 } 0473 return res; 0474 } 0475 0476 0477 0478 // 0479 // GraphOptions 0480 // 0481 0482 QString GraphOptions::layoutString(Layout l) 0483 { 0484 if (l == Circular) 0485 return QStringLiteral("Circular"); 0486 if (l == LeftRight) 0487 return QStringLiteral("LeftRight"); 0488 return QStringLiteral("TopDown"); 0489 } 0490 0491 GraphOptions::Layout GraphOptions::layout(QString s) 0492 { 0493 if (s == QLatin1String("Circular")) 0494 return Circular; 0495 if (s == QLatin1String("LeftRight")) 0496 return LeftRight; 0497 return TopDown; 0498 } 0499 0500 0501 // 0502 // StorableGraphOptions 0503 // 0504 0505 StorableGraphOptions::StorableGraphOptions() 0506 { 0507 // default options 0508 _funcLimit = DEFAULT_FUNCLIMIT; 0509 _callLimit = DEFAULT_CALLLIMIT; 0510 _maxCallerDepth = DEFAULT_MAXCALLER; 0511 _maxCalleeDepth = DEFAULT_MAXCALLEE; 0512 _showSkipped = DEFAULT_SHOWSKIPPED; 0513 _expandCycles = DEFAULT_EXPANDCYCLES; 0514 _detailLevel = DEFAULT_DETAILLEVEL; 0515 _layout = DEFAULT_LAYOUT; 0516 } 0517 0518 0519 0520 0521 // 0522 // GraphExporter 0523 // 0524 0525 GraphExporter::GraphExporter() 0526 { 0527 _go = this; 0528 _tmpFile = nullptr; 0529 _item = nullptr; 0530 reset(nullptr, nullptr, nullptr, ProfileContext::InvalidType, QString()); 0531 } 0532 0533 GraphExporter::GraphExporter(TraceData* d, TraceFunction* f, 0534 EventType* ct, ProfileContext::Type gt, 0535 QString filename) 0536 { 0537 _go = this; 0538 _tmpFile = nullptr; 0539 _item = nullptr; 0540 reset(d, f, ct, gt, filename); 0541 } 0542 0543 GraphExporter::~GraphExporter() 0544 { 0545 if (_item && _tmpFile) { 0546 #if DEBUG_GRAPH 0547 _tmpFile->setAutoRemove(true); 0548 #endif 0549 delete _tmpFile; 0550 } 0551 } 0552 0553 0554 void GraphExporter::reset(TraceData*, CostItem* i, EventType* ct, 0555 ProfileContext::Type gt, QString filename) 0556 { 0557 _graphCreated = false; 0558 _nodeMap.clear(); 0559 _edgeMap.clear(); 0560 0561 if (_item && _tmpFile) { 0562 _tmpFile->setAutoRemove(true); 0563 delete _tmpFile; 0564 } 0565 0566 if (i) { 0567 switch (i->type()) { 0568 case ProfileContext::Function: 0569 case ProfileContext::FunctionCycle: 0570 case ProfileContext::Call: 0571 break; 0572 default: 0573 i = nullptr; 0574 } 0575 } 0576 0577 _item = i; 0578 _eventType = ct; 0579 _groupType = gt; 0580 if (!i) 0581 return; 0582 0583 if (filename.isEmpty()) { 0584 _tmpFile = new QTemporaryFile(); 0585 //_tmpFile->setSuffix(".dot"); 0586 _tmpFile->setAutoRemove(false); 0587 _tmpFile->open(); 0588 _dotName = _tmpFile->fileName(); 0589 _useBox = true; 0590 } else { 0591 _tmpFile = nullptr; 0592 _dotName = filename; 0593 _useBox = false; 0594 } 0595 } 0596 0597 0598 0599 void GraphExporter::setGraphOptions(GraphOptions* go) 0600 { 0601 if (go == nullptr) 0602 go = this; 0603 _go = go; 0604 } 0605 0606 void GraphExporter::createGraph() 0607 { 0608 if (!_item) 0609 return; 0610 if (_graphCreated) 0611 return; 0612 _graphCreated = true; 0613 0614 if ((_item->type() == ProfileContext::Function) ||(_item->type() 0615 == ProfileContext::FunctionCycle)) { 0616 TraceFunction* f = (TraceFunction*) _item; 0617 0618 double incl = f->inclusive()->subCost(_eventType); 0619 _realFuncLimit = incl * _go->funcLimit(); 0620 _realCallLimit = _realFuncLimit * _go->callLimit(); 0621 0622 buildGraph(f, 0, true, 1.0); // down to callees 0623 0624 // set costs of function back to 0, as it will be added again 0625 GraphNode& n = _nodeMap[f]; 0626 n.self = n.incl = 0.0; 0627 0628 buildGraph(f, 0, false, 1.0); // up to callers 0629 } else { 0630 TraceCall* c = (TraceCall*) _item; 0631 0632 double incl = c->subCost(_eventType); 0633 _realFuncLimit = incl * _go->funcLimit(); 0634 _realCallLimit = _realFuncLimit * _go->callLimit(); 0635 0636 // create edge 0637 TraceFunction *caller, *called; 0638 caller = c->caller(false); 0639 called = c->called(false); 0640 QPair<TraceFunction*,TraceFunction*> p(caller, called); 0641 GraphEdge& e = _edgeMap[p]; 0642 e.setCall(c); 0643 e.setCaller(p.first); 0644 e.setCallee(p.second); 0645 e.cost = c->subCost(_eventType); 0646 e.count = c->callCount(); 0647 0648 SubCost s = called->inclusive()->subCost(_eventType); 0649 buildGraph(called, 0, true, e.cost / s); // down to callees 0650 s = caller->inclusive()->subCost(_eventType); 0651 buildGraph(caller, 0, false, e.cost / s); // up to callers 0652 } 0653 } 0654 0655 0656 bool GraphExporter::writeDot(QIODevice* device) 0657 { 0658 if (!_item) 0659 return false; 0660 0661 QFile* file = nullptr; 0662 QTextStream* stream = nullptr; 0663 0664 if (device) 0665 stream = new QTextStream(device); 0666 else { 0667 if (_tmpFile) 0668 stream = new QTextStream(_tmpFile); 0669 else { 0670 file = new QFile(_dotName); 0671 if ( !file->open(QIODevice::WriteOnly ) ) { 0672 qDebug() << "Can not write dot file '"<< _dotName << "'"; 0673 delete file; 0674 return false; 0675 } 0676 stream = new QTextStream(file); 0677 } 0678 } 0679 0680 if (!_graphCreated) 0681 createGraph(); 0682 0683 /* Generate dot format... 0684 * When used for the CallGraphView (in contrast to "Export Callgraph..."), 0685 * the labels are only dummy placeholders to reserve space for our own 0686 * drawings. 0687 */ 0688 0689 *stream << "digraph \"callgraph\" {\n"; 0690 0691 if (_go->layout() == LeftRight) { 0692 *stream << QStringLiteral(" rankdir=LR;\n"); 0693 } else if (_go->layout() == Circular) { 0694 TraceFunction *f = nullptr; 0695 switch (_item->type()) { 0696 case ProfileContext::Function: 0697 case ProfileContext::FunctionCycle: 0698 f = (TraceFunction*) _item; 0699 break; 0700 case ProfileContext::Call: 0701 f = ((TraceCall*)_item)->caller(true); 0702 break; 0703 default: 0704 break; 0705 } 0706 if (f) 0707 *stream << QStringLiteral(" center=F%1;\n").arg((qptrdiff)f, 0, 16); 0708 *stream << QStringLiteral(" overlap=false;\n splines=true;\n"); 0709 } 0710 0711 // for clustering 0712 QMap<TraceCostItem*,QList<GraphNode*> > nLists; 0713 0714 GraphNodeMap::Iterator nit; 0715 for (nit = _nodeMap.begin(); nit != _nodeMap.end(); ++nit ) { 0716 GraphNode& n = *nit; 0717 0718 if (n.incl <= _realFuncLimit) 0719 continue; 0720 0721 // for clustering: get cost item group of function 0722 TraceCostItem* g; 0723 TraceFunction* f = n.function(); 0724 switch (_groupType) { 0725 case ProfileContext::Object: 0726 g = f->object(); 0727 break; 0728 case ProfileContext::Class: 0729 g = f->cls(); 0730 break; 0731 case ProfileContext::File: 0732 g = f->file(); 0733 break; 0734 case ProfileContext::FunctionCycle: 0735 g = f->cycle(); 0736 break; 0737 default: 0738 g = nullptr; 0739 break; 0740 } 0741 nLists[g].append(&n); 0742 } 0743 0744 QMap<TraceCostItem*,QList<GraphNode*> >::Iterator lit; 0745 int cluster = 0; 0746 for (lit = nLists.begin(); lit != nLists.end(); ++lit, cluster++) { 0747 QList<GraphNode*>& l = lit.value(); 0748 TraceCostItem* i = lit.key(); 0749 0750 if (_go->clusterGroups() && i) { 0751 QString iabr = GlobalConfig::shortenSymbol(i->prettyName()); 0752 // escape quotation marks in symbols to avoid invalid dot syntax 0753 iabr.replace("\"", "\\\""); 0754 *stream << QStringLiteral("subgraph \"cluster%1\" { label=\"%2\";\n") 0755 .arg(cluster).arg(iabr); 0756 } 0757 0758 foreach(GraphNode* np, l) { 0759 TraceFunction* f = np->function(); 0760 0761 QString abr = GlobalConfig::shortenSymbol(f->prettyName()); 0762 // escape quotation marks to avoid invalid dot syntax 0763 abr.replace("\"", "\\\""); 0764 *stream << QStringLiteral(" F%1 [").arg((qptrdiff)f, 0, 16); 0765 if (_useBox) { 0766 // we want a minimal size for cost display 0767 if ((int)abr.length() < 8) abr = abr + QString(8 - abr.length(),'_'); 0768 0769 // make label 3 lines for CallGraphView 0770 *stream << QStringLiteral("shape=box,label=\"** %1 **\\n**\\n%2\"];\n") 0771 .arg(abr) 0772 .arg(SubCost(np->incl).pretty()); 0773 } else 0774 *stream << QStringLiteral("label=\"%1\\n%2\"];\n") 0775 .arg(abr) 0776 .arg(SubCost(np->incl).pretty()); 0777 } 0778 0779 if (_go->clusterGroups() && i) 0780 *stream << QStringLiteral("}\n"); 0781 } 0782 0783 GraphEdgeMap::Iterator eit; 0784 for (eit = _edgeMap.begin(); eit != _edgeMap.end(); ++eit ) { 0785 GraphEdge& e = *eit; 0786 0787 if (e.cost < _realCallLimit) 0788 continue; 0789 if (!_go->expandCycles()) { 0790 // do not show inner cycle calls 0791 if (e.call()->inCycle()>0) 0792 continue; 0793 } 0794 0795 GraphNode& from = _nodeMap[e.from()]; 0796 GraphNode& to = _nodeMap[e.to()]; 0797 0798 e.setCallerNode(&from); 0799 e.setCalleeNode(&to); 0800 0801 if ((from.incl <= _realFuncLimit) ||(to.incl <= _realFuncLimit)) 0802 continue; 0803 0804 // remove dumped edges from n.callers/n.callees 0805 from.removeEdge(&e); 0806 to.removeEdge(&e); 0807 0808 *stream << QStringLiteral(" F%1 -> F%2 [weight=%3") 0809 .arg((qptrdiff)e.from(), 0, 16) 0810 .arg((qptrdiff)e.to(), 0, 16) 0811 .arg((long)log(log(e.cost))); 0812 0813 if (_go->detailLevel() ==1) { 0814 *stream << QStringLiteral(",label=\"%1 (%2x)\"") 0815 .arg(SubCost(e.cost).pretty()) 0816 .arg(SubCost(e.count).pretty()); 0817 } 0818 else if (_go->detailLevel() ==2) 0819 *stream << QStringLiteral(",label=\"%3\\n%4 x\"") 0820 .arg(SubCost(e.cost).pretty()) 0821 .arg(SubCost(e.count).pretty()); 0822 0823 *stream << QStringLiteral("];\n"); 0824 } 0825 0826 if (_go->showSkipped()) { 0827 0828 // Create sum-edges for skipped edges 0829 GraphEdge* e; 0830 double costSum, countSum; 0831 for (nit = _nodeMap.begin(); nit != _nodeMap.end(); ++nit ) { 0832 GraphNode& n = *nit; 0833 if (n.incl <= _realFuncLimit) 0834 continue; 0835 0836 // add edge for all skipped callers if cost sum is high enough 0837 costSum = n.callerCostSum(); 0838 countSum = n.callerCountSum(); 0839 if (costSum > _realCallLimit) { 0840 0841 QPair<TraceFunction*,TraceFunction*> p(nullptr, n.function()); 0842 e = &(_edgeMap[p]); 0843 e->setCallee(p.second); 0844 e->cost = costSum; 0845 e->count = countSum; 0846 0847 *stream << QStringLiteral(" R%1 [shape=point,label=\"\"];\n") 0848 .arg((qptrdiff)n.function(), 0, 16); 0849 *stream << QStringLiteral(" R%1 -> F%2 [label=\"%3\\n%4 x\",weight=%5];\n") 0850 .arg((qptrdiff)n.function(), 0, 16) 0851 .arg((qptrdiff)n.function(), 0, 16) 0852 .arg(SubCost(costSum).pretty()) 0853 .arg(SubCost(countSum).pretty()) 0854 .arg((int)log(costSum)); 0855 } 0856 0857 // add edge for all skipped callees if cost sum is high enough 0858 costSum = n.calleeCostSum(); 0859 countSum = n.calleeCountSum(); 0860 if (costSum > _realCallLimit) { 0861 0862 QPair<TraceFunction*,TraceFunction*> p(n.function(), nullptr); 0863 e = &(_edgeMap[p]); 0864 e->setCaller(p.first); 0865 e->cost = costSum; 0866 e->count = countSum; 0867 0868 *stream << QStringLiteral(" S%1 [shape=point,label=\"\"];\n") 0869 .arg((qptrdiff)n.function(), 0, 16); 0870 *stream << QStringLiteral(" F%1 -> S%2 [label=\"%3\\n%4 x\",weight=%5];\n") 0871 .arg((qptrdiff)n.function(), 0, 16) 0872 .arg((qptrdiff)n.function(), 0, 16) 0873 .arg(SubCost(costSum).pretty()) 0874 .arg(SubCost(countSum).pretty()) 0875 .arg((int)log(costSum)); 0876 } 0877 } 0878 } 0879 0880 // clear edges here completely. 0881 // Visible edges are inserted again on parsing in CallGraphView::refresh 0882 for (nit = _nodeMap.begin(); nit != _nodeMap.end(); ++nit ) { 0883 GraphNode& n = *nit; 0884 n.clearEdges(); 0885 } 0886 0887 *stream << "}\n"; 0888 0889 if (!device) { 0890 if (_tmpFile) { 0891 stream->flush(); 0892 _tmpFile->seek(0); 0893 } else { 0894 file->close(); 0895 delete file; 0896 } 0897 } 0898 delete stream; 0899 return true; 0900 } 0901 0902 bool GraphExporter::savePrompt(QWidget *parent, TraceData *data, 0903 TraceFunction *function, EventType *eventType, 0904 ProfileContext::Type groupType, 0905 CallGraphView* cgv) 0906 { 0907 // More formats can be added; it's a matter of adding the filter and if 0908 QFileDialog saveDialog(parent, QObject::tr("Export Graph")); 0909 saveDialog.setMimeTypeFilters( {"text/vnd.graphviz", "application/pdf", "application/postscript"} ); 0910 saveDialog.setFileMode(QFileDialog::AnyFile); 0911 saveDialog.setAcceptMode(QFileDialog::AcceptSave); 0912 0913 if (saveDialog.exec()) { 0914 QString intendedName = saveDialog.selectedFiles().first(); 0915 if (intendedName.isNull() || intendedName.isEmpty()) 0916 return false; 0917 bool wrote = false; 0918 QString dotName, dotRenderType; 0919 QTemporaryFile maybeTemp; 0920 maybeTemp.open(); 0921 const QString mime = saveDialog.selectedMimeTypeFilter(); 0922 if (mime == "text/vnd.graphviz") { 0923 dotName = intendedName; 0924 dotRenderType = ""; 0925 } 0926 else if (mime == "application/pdf") { 0927 dotName = maybeTemp.fileName(); 0928 dotRenderType = "-Tpdf"; 0929 } 0930 else if (mime == "application/postscript") { 0931 dotName = maybeTemp.fileName(); 0932 dotRenderType = "-Tps"; 0933 } 0934 GraphExporter ge(data, function, eventType, groupType, dotName); 0935 if (cgv != nullptr) 0936 ge.setGraphOptions(cgv); 0937 wrote = ge.writeDot(); 0938 if (wrote && mime != "text/vnd.graphviz") { 0939 QProcess proc; 0940 proc.setStandardOutputFile(intendedName, QFile::Truncate); 0941 proc.start("dot", { dotRenderType, dotName }, QProcess::ReadWrite); 0942 proc.waitForFinished(); 0943 wrote = proc.exitStatus() == QProcess::NormalExit; 0944 0945 // Open in the default app, except for GraphViz files. The .dot 0946 // association is usually for Microsoft Word templates and will 0947 // open a word processor instead, which isn't what we want. 0948 if (wrote) { 0949 QDesktopServices::openUrl(QUrl::fromLocalFile(intendedName)); 0950 } 0951 } 0952 0953 return wrote; 0954 } 0955 0956 return false; 0957 } 0958 0959 void GraphExporter::sortEdges() 0960 { 0961 GraphNodeMap::Iterator nit; 0962 for (nit = _nodeMap.begin(); nit != _nodeMap.end(); ++nit ) { 0963 GraphNode& n = *nit; 0964 n.sortEdges(); 0965 } 0966 } 0967 0968 TraceFunction* GraphExporter::toFunc(QString s) 0969 { 0970 if (s[0] != 'F') 0971 return nullptr; 0972 bool ok; 0973 TraceFunction* f = (TraceFunction*) s.mid(1).toULongLong(&ok, 16); 0974 if (!ok) 0975 return nullptr; 0976 0977 return f; 0978 } 0979 0980 GraphNode* GraphExporter::node(TraceFunction* f) 0981 { 0982 if (!f) 0983 return nullptr; 0984 0985 GraphNodeMap::Iterator it = _nodeMap.find(f); 0986 if (it == _nodeMap.end()) 0987 return nullptr; 0988 0989 return &(*it); 0990 } 0991 0992 GraphEdge* GraphExporter::edge(TraceFunction* f1, TraceFunction* f2) 0993 { 0994 GraphEdgeMap::Iterator it = _edgeMap.find(qMakePair(f1, f2)); 0995 if (it == _edgeMap.end()) 0996 return nullptr; 0997 0998 return &(*it); 0999 } 1000 1001 /** 1002 * We do a DFS and do not stop on already visited nodes/edges, 1003 * but add up costs. We only stop if limits/max depth is reached. 1004 * 1005 * For a node/edge, it can happen that the first time visited the 1006 * cost will below the limit, so the search is stopped. 1007 * If on a further visit of the node/edge the limit is reached, 1008 * we use the whole node/edge cost and continue search. 1009 */ 1010 void GraphExporter::buildGraph(TraceFunction* f, int depth, bool toCallees, 1011 double factor) 1012 { 1013 #if DEBUG_GRAPH 1014 qDebug() << "buildGraph(" << f->prettyName() << "," << d << "," << factor 1015 << ") [to " << (toCallees ? "Callees":"Callers") << "]"; 1016 #endif 1017 1018 double oldIncl = 0.0; 1019 GraphNode& n = _nodeMap[f]; 1020 if (n.function() == nullptr) { 1021 n.setFunction(f); 1022 } else 1023 oldIncl = n.incl; 1024 1025 double incl = f->inclusive()->subCost(_eventType) * factor; 1026 n.incl += incl; 1027 n.self += f->subCost(_eventType) * factor; 1028 if (0) 1029 qDebug(" Added Incl. %f, now %f", incl, n.incl); 1030 1031 // A negative depth limit means "unlimited" 1032 int maxDepth = toCallees ? _go->maxCalleeDepth() 1033 : _go->maxCallerDepth(); 1034 // Never go beyond a depth of 100 1035 if ((maxDepth < 0) || (maxDepth>100)) maxDepth = 100; 1036 if (depth >= maxDepth) { 1037 if (0) 1038 qDebug(" Cutoff, max depth reached"); 1039 return; 1040 } 1041 1042 // if we just reached the limit by summing, do a DFS 1043 // from here with full incl. cost because of previous cutoffs 1044 if ((n.incl >= _realFuncLimit) && (oldIncl < _realFuncLimit)) 1045 incl = n.incl; 1046 1047 if (f->cycle()) { 1048 // for cycles members, we never stop on first visit, but always on 2nd 1049 // note: a 2nd visit never should happen, as we do not follow inner-cycle 1050 // calls 1051 if (oldIncl > 0.0) { 1052 if (0) 1053 qDebug(" Cutoff, 2nd visit to Cycle Member"); 1054 // and takeback cost addition, as it is added twice 1055 n.incl = oldIncl; 1056 n.self -= f->subCost(_eventType) * factor; 1057 return; 1058 } 1059 } else if (incl <= _realFuncLimit) { 1060 if (0) 1061 qDebug(" Cutoff, below limit"); 1062 return; 1063 } 1064 1065 TraceFunction* f2; 1066 1067 // on entering a cycle, only go the FunctionCycle 1068 TraceCallList l = toCallees ? f->callings(false) : f->callers(false); 1069 1070 foreach(TraceCall* call, l) { 1071 1072 f2 = toCallees ? call->called(false) : call->caller(false); 1073 1074 double count = call->callCount() * factor; 1075 double cost = call->subCost(_eventType) * factor; 1076 1077 // ignore function calls with absolute cost < 3 per call 1078 // No: This would skip a lot of functions e.g. with L2 cache misses 1079 // if (count>0.0 && (cost/count < 3)) continue; 1080 1081 double oldCost = 0.0; 1082 QPair<TraceFunction*,TraceFunction*> p(toCallees ? f : f2, 1083 toCallees ? f2 : f); 1084 GraphEdge& e = _edgeMap[p]; 1085 if (e.call() == nullptr) { 1086 e.setCall(call); 1087 e.setCaller(p.first); 1088 e.setCallee(p.second); 1089 } else 1090 oldCost = e.cost; 1091 1092 e.cost += cost; 1093 e.count += count; 1094 if (0) 1095 qDebug(" Edge to %s, added cost %f, now %f", 1096 qPrintable(f2->prettyName()), cost, e.cost); 1097 1098 // if this call goes into a FunctionCycle, we also show the real call 1099 if (f2->cycle() == f2) { 1100 TraceFunction* realF; 1101 realF = toCallees ? call->called(true) : call->caller(true); 1102 QPair<TraceFunction*,TraceFunction*> 1103 realP(toCallees ? f : realF, toCallees ? realF : f); 1104 GraphEdge& e = _edgeMap[realP]; 1105 if (e.call() == nullptr) { 1106 e.setCall(call); 1107 e.setCaller(realP.first); 1108 e.setCallee(realP.second); 1109 } 1110 e.cost += cost; 1111 e.count += count; 1112 } 1113 1114 // - do not do a DFS on calls in recursion/cycle 1115 if (call->inCycle()>0) 1116 continue; 1117 if (call->isRecursion()) 1118 continue; 1119 1120 if (toCallees) 1121 n.addUniqueCallee(&e); 1122 else 1123 n.addUniqueCaller(&e); 1124 1125 // if we just reached the call limit (=func limit by summing, do a DFS 1126 // from here with full incl. cost because of previous cutoffs 1127 if ((e.cost >= _realCallLimit) && (oldCost < _realCallLimit)) 1128 cost = e.cost; 1129 if ((cost <= 0) || (cost <= _realCallLimit)) { 1130 if (0) 1131 qDebug(" Edge Cutoff, limit not reached"); 1132 continue; 1133 } 1134 1135 SubCost s; 1136 if (call->inCycle()) 1137 s = f2->cycle()->inclusive()->subCost(_eventType); 1138 else 1139 s = f2->inclusive()->subCost(_eventType); 1140 SubCost v = call->subCost(_eventType); 1141 1142 // Never recurse if s or v is 0 (can happen with bogus input) 1143 if ((v == 0) || (s== 0)) continue; 1144 1145 buildGraph(f2, depth+1, toCallees, factor * v / s); 1146 } 1147 } 1148 1149 // 1150 // PannerView 1151 // 1152 PanningView::PanningView(QWidget * parent) 1153 : QGraphicsView(parent) 1154 { 1155 _movingZoomRect = false; 1156 1157 // FIXME: Why does this not work? 1158 viewport()->setFocusPolicy(Qt::NoFocus); 1159 } 1160 1161 void PanningView::setZoomRect(const QRectF& r) 1162 { 1163 _zoomRect = r; 1164 viewport()->update(); 1165 } 1166 1167 void PanningView::drawForeground(QPainter * p, const QRectF&) 1168 { 1169 if (!_zoomRect.isValid()) 1170 return; 1171 1172 QColor red(Qt::red); 1173 QPen pen(red.darker()); 1174 pen.setWidthF(2.0 / transform().m11()); 1175 p->setPen(pen); 1176 1177 QColor c(red.darker()); 1178 c.setAlphaF(0.05); 1179 p->setBrush(QBrush(c)); 1180 1181 p->drawRect(QRectF(_zoomRect.x(), _zoomRect.y(), 1182 _zoomRect.width()-1, _zoomRect.height()-1)); 1183 } 1184 1185 void PanningView::mousePressEvent(QMouseEvent* e) 1186 { 1187 QPointF sPos = mapToScene(e->pos()); 1188 1189 if (_zoomRect.isValid()) { 1190 if (!_zoomRect.contains(sPos)) 1191 emit zoomRectMoved(sPos.x() - _zoomRect.center().x(), 1192 sPos.y() - _zoomRect.center().y()); 1193 1194 _movingZoomRect = true; 1195 _lastPos = sPos; 1196 } 1197 } 1198 1199 void PanningView::mouseMoveEvent(QMouseEvent* e) 1200 { 1201 QPointF sPos = mapToScene(e->pos()); 1202 if (_movingZoomRect) { 1203 emit zoomRectMoved(sPos.x() - _lastPos.x(), 1204 sPos.y() - _lastPos.y()); 1205 _lastPos = sPos; 1206 } 1207 } 1208 1209 void PanningView::mouseReleaseEvent(QMouseEvent*) 1210 { 1211 _movingZoomRect = false; 1212 emit zoomRectMoveFinished(); 1213 } 1214 1215 1216 1217 1218 1219 // 1220 // CanvasNode 1221 // 1222 1223 CanvasNode::CanvasNode(CallGraphView* v, GraphNode* n, int x, int y, int w, 1224 int h) : 1225 QGraphicsRectItem(QRect(x, y, w, h)), _node(n), _view(v) 1226 { 1227 setPosition(0, DrawParams::TopCenter); 1228 setPosition(1, DrawParams::BottomCenter); 1229 1230 updateGroup(); 1231 1232 if (!_node || !_view) 1233 return; 1234 1235 if (_node->function()) 1236 setText(0, _node->function()->prettyName()); 1237 1238 ProfileCostArray* totalCost; 1239 if (GlobalConfig::showExpanded()) { 1240 if (_view->activeFunction()) { 1241 if (_view->activeFunction()->cycle()) 1242 totalCost = _view->activeFunction()->cycle()->inclusive(); 1243 else 1244 totalCost = _view->activeFunction()->inclusive(); 1245 } else 1246 totalCost = (ProfileCostArray*) _view->activeItem(); 1247 } else 1248 totalCost = ((TraceItemView*)_view)->data(); 1249 double total = totalCost->subCost(_view->eventType()); 1250 double inclP = 100.0 * n->incl/ total; 1251 if (GlobalConfig::showPercentage()) 1252 setText(1, QStringLiteral("%1 %") 1253 .arg(inclP, 0, 'f', GlobalConfig::percentPrecision())); 1254 else 1255 setText(1, SubCost(n->incl).pretty()); 1256 setPixmap(1, percentagePixmap(25, 10, (int)(inclP+.5), Qt::blue, true)); 1257 1258 setToolTip(QStringLiteral("%1 (%2)").arg(text(0)).arg(text(1))); 1259 } 1260 1261 void CanvasNode::setSelected(bool s) 1262 { 1263 StoredDrawParams::setSelected(s); 1264 update(); 1265 } 1266 1267 void CanvasNode::updateGroup() 1268 { 1269 if (!_view || !_node) 1270 return; 1271 1272 QColor c = GlobalGUIConfig::functionColor(_view->groupType(), 1273 _node->function()); 1274 setBackColor(c); 1275 update(); 1276 } 1277 1278 void CanvasNode::paint(QPainter* p, 1279 const QStyleOptionGraphicsItem* option, 1280 QWidget*) 1281 { 1282 QRect r = rect().toRect(), origRect = r; 1283 1284 r.setRect(r.x()+1, r.y()+1, r.width()-2, r.height()-2); 1285 1286 RectDrawing d(r); 1287 d.drawBack(p, this); 1288 r.setRect(r.x()+2, r.y()+2, r.width()-4, r.height()-4); 1289 1290 #if 0 1291 if (StoredDrawParams::selected() && _view->hasFocus()) { 1292 _view->style().drawPrimitive( QStyle::PE_FocusRect, &p, r, 1293 _view->colorGroup()); 1294 } 1295 #endif 1296 1297 // draw afterwards to always get a frame even when zoomed 1298 p->setPen(StoredDrawParams::selected() ? Qt::red : Qt::black); 1299 p->drawRect(QRect(origRect.x(), origRect.y(), origRect.width()-1, 1300 origRect.height()-1)); 1301 1302 #if QT_VERSION >= 0x040600 1303 if (option->levelOfDetailFromTransform(p->transform()) < .5) 1304 return; 1305 #else 1306 if (option->levelOfDetail < .5) 1307 return; 1308 #endif 1309 1310 d.setRect(r); 1311 d.drawField(p, 0, this); 1312 d.drawField(p, 1, this); 1313 } 1314 1315 1316 // 1317 // CanvasEdgeLabel 1318 // 1319 1320 CanvasEdgeLabel::CanvasEdgeLabel(CallGraphView* v, CanvasEdge* ce, int x, 1321 int y, int w, int h) : 1322 QGraphicsRectItem(QRect(x, y, w, h)), _ce(ce), _view(v), _percentage(0.0) 1323 { 1324 GraphEdge* e = ce->edge(); 1325 if (!e) 1326 return; 1327 1328 setPosition(1, DrawParams::BottomCenter); 1329 ProfileCostArray* totalCost; 1330 if (GlobalConfig::showExpanded()) { 1331 if (_view->activeFunction()) { 1332 if (_view->activeFunction()->cycle()) 1333 totalCost = _view->activeFunction()->cycle()->inclusive(); 1334 else 1335 totalCost = _view->activeFunction()->inclusive(); 1336 } else 1337 totalCost = (ProfileCostArray*) _view->activeItem(); 1338 } else 1339 totalCost = ((TraceItemView*)_view)->data(); 1340 double total = totalCost->subCost(_view->eventType()); 1341 double inclP = 100.0 * e->cost/ total; 1342 if (GlobalConfig::showPercentage()) 1343 setText(1, QStringLiteral("%1 %") 1344 .arg(inclP, 0, 'f', GlobalConfig::percentPrecision())); 1345 else 1346 setText(1, SubCost(e->cost).pretty()); 1347 1348 int pixPos = 1; 1349 if (((TraceItemView*)_view)->data()->maxCallCount() > 0) { 1350 setPosition(0, DrawParams::TopCenter); 1351 SubCost count((e->count < 1.0) ? 1.0 : e->count); 1352 setText(0, QStringLiteral("%1 x").arg(count.pretty())); 1353 pixPos = 0; 1354 setToolTip(QStringLiteral("%1 (%2)").arg(text(0)).arg(text(1))); 1355 } 1356 setPixmap(pixPos, percentagePixmap(25, 10, (int)(inclP+.5), Qt::blue, true)); 1357 1358 _percentage = inclP; 1359 if (_percentage > 100.0) _percentage = 100.0; 1360 1361 if (e->call() && (e->call()->isRecursion() || e->call()->inCycle())) { 1362 QFontMetrics fm(font()); 1363 QPixmap p = QIcon::fromTheme(QStringLiteral("edit-undo")).pixmap(fm.height()); 1364 setPixmap(pixPos, p); // replace percentage pixmap 1365 } 1366 } 1367 1368 void CanvasEdgeLabel::paint(QPainter* p, 1369 const QStyleOptionGraphicsItem* option, QWidget*) 1370 { 1371 // draw nothing in PanningView 1372 #if QT_VERSION >= 0x040600 1373 if (option->levelOfDetailFromTransform(p->transform()) < .5) 1374 return; 1375 #else 1376 if (option->levelOfDetail < .5) 1377 return; 1378 #endif 1379 1380 QRect r = rect().toRect(); 1381 1382 RectDrawing d(r); 1383 d.drawField(p, 0, this); 1384 d.drawField(p, 1, this); 1385 } 1386 1387 1388 1389 // 1390 // CanvasEdgeArrow 1391 1392 CanvasEdgeArrow::CanvasEdgeArrow(CanvasEdge* ce) 1393 : _ce(ce) 1394 {} 1395 1396 void CanvasEdgeArrow::paint(QPainter* p, 1397 const QStyleOptionGraphicsItem *, QWidget *) 1398 { 1399 p->setRenderHint(QPainter::Antialiasing); 1400 p->setBrush(_ce->isSelected() ? Qt::red : Qt::black); 1401 p->drawPolygon(polygon(), Qt::OddEvenFill); 1402 } 1403 1404 1405 // 1406 // CanvasEdge 1407 // 1408 1409 CanvasEdge::CanvasEdge(GraphEdge* e) : 1410 _edge(e) 1411 { 1412 _label = nullptr; 1413 _arrow = nullptr; 1414 _thickness = 0; 1415 1416 setFlag(QGraphicsItem::ItemIsSelectable); 1417 } 1418 1419 void CanvasEdge::setLabel(CanvasEdgeLabel* l) 1420 { 1421 _label = l; 1422 1423 if (l) { 1424 QString tip = QStringLiteral("%1 (%2)").arg(l->text(0)).arg(l->text(1)); 1425 1426 setToolTip(tip); 1427 if (_arrow) _arrow->setToolTip(tip); 1428 1429 _thickness = log(l->percentage()); 1430 if (_thickness < .9) _thickness = .9; 1431 } 1432 } 1433 1434 void CanvasEdge::setArrow(CanvasEdgeArrow* a) 1435 { 1436 _arrow = a; 1437 1438 if (a && _label) a->setToolTip(QStringLiteral("%1 (%2)") 1439 .arg(_label->text(0)).arg(_label->text(1))); 1440 } 1441 1442 void CanvasEdge::setSelected(bool s) 1443 { 1444 QGraphicsItem::setSelected(s); 1445 update(); 1446 } 1447 1448 void CanvasEdge::setControlPoints(const QPolygon& pa) 1449 { 1450 _points = pa; 1451 1452 QPainterPath path; 1453 path.moveTo(pa[0]); 1454 for (int i = 1; i < pa.size(); i += 3) 1455 path.cubicTo(pa[i], pa[(i + 1) % pa.size()], pa[(i + 2) % pa.size()]); 1456 1457 setPath(path); 1458 } 1459 1460 1461 void CanvasEdge::paint(QPainter* p, 1462 const QStyleOptionGraphicsItem* option, QWidget*) 1463 { 1464 p->setRenderHint(QPainter::Antialiasing); 1465 1466 qreal levelOfDetail; 1467 #if QT_VERSION >= 0x040600 1468 levelOfDetail = option->levelOfDetailFromTransform(p->transform()); 1469 #else 1470 levelOfDetail = option->levelOfDetail; 1471 #endif 1472 1473 QPen mypen = pen(); 1474 mypen.setWidthF(1.0/levelOfDetail * _thickness); 1475 p->setPen(mypen); 1476 p->drawPath(path()); 1477 1478 if (isSelected()) { 1479 mypen.setColor(Qt::red); 1480 mypen.setWidthF(1.0/levelOfDetail * _thickness/2.0); 1481 p->setPen(mypen); 1482 p->drawPath(path()); 1483 } 1484 } 1485 1486 1487 // 1488 // CanvasFrame 1489 // 1490 1491 QPixmap* CanvasFrame::_p = nullptr; 1492 1493 CanvasFrame::CanvasFrame(CanvasNode* n) 1494 { 1495 if (!_p) { 1496 1497 int d = 5; 1498 float v1 = 130.0f, v2 = 10.0f, v = v1, f = 1.03f; 1499 1500 // calculate pix size 1501 QRect r(0, 0, 30, 30); 1502 while (v>v2) { 1503 r.setRect(r.x()-d, r.y()-d, r.width()+2*d, r.height()+2*d); 1504 v /= f; 1505 } 1506 1507 _p = new QPixmap(r.size()); 1508 _p->fill(Qt::white); 1509 QPainter p(_p); 1510 p.setPen(Qt::NoPen); 1511 1512 r.translate(-r.x(), -r.y()); 1513 1514 while (v<v1) { 1515 v *= f; 1516 p.setBrush(QColor(265-(int)v, 265-(int)v, 265-(int)v)); 1517 1518 p.drawRect(QRect(r.x(), r.y(), r.width(), d)); 1519 p.drawRect(QRect(r.x(), r.bottom()-d, r.width(), d)); 1520 p.drawRect(QRect(r.x(), r.y()+d, d, r.height()-2*d)); 1521 p.drawRect(QRect(r.right()-d, r.y()+d, d, r.height()-2*d)); 1522 1523 r.setRect(r.x()+d, r.y()+d, r.width()-2*d, r.height()-2*d); 1524 } 1525 } 1526 1527 setRect(QRectF(n->rect().center().x() - _p->width()/2, 1528 n->rect().center().y() - _p->height()/2, _p->width(), _p->height()) ); 1529 } 1530 1531 1532 void CanvasFrame::paint(QPainter* p, 1533 const QStyleOptionGraphicsItem* option, QWidget*) 1534 { 1535 qreal levelOfDetail; 1536 #if QT_VERSION >= 0x040600 1537 levelOfDetail = option->levelOfDetailFromTransform(p->transform()); 1538 #else 1539 levelOfDetail = option->levelOfDetail; 1540 #endif 1541 if (levelOfDetail < .5) { 1542 QRadialGradient g(rect().center(), rect().width()/3); 1543 g.setColorAt(0.0, Qt::gray); 1544 g.setColorAt(1.0, Qt::white); 1545 1546 p->setBrush(QBrush(g)); 1547 p->setPen(Qt::NoPen); 1548 p->drawRect(rect()); 1549 return; 1550 } 1551 1552 p->drawPixmap(int( rect().x()),int( rect().y()), *_p ); 1553 } 1554 1555 1556 1557 // 1558 // CallGraphView 1559 // 1560 CallGraphView::CallGraphView(TraceItemView* parentView, QWidget* parent, 1561 const QString& name) : 1562 QGraphicsView(parent), TraceItemView(parentView) 1563 { 1564 setObjectName(name); 1565 _zoomPosition = DEFAULT_ZOOMPOS; 1566 _lastAutoPosition = TopLeft; 1567 1568 _scene = nullptr; 1569 _xMargin = _yMargin = 0; 1570 _panningView = new PanningView(this); 1571 _panningZoom = 1; 1572 _selectedNode = nullptr; 1573 _selectedEdge = nullptr; 1574 _isMoving = false; 1575 1576 _exporter.setGraphOptions(this); 1577 1578 _panningView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 1579 _panningView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 1580 _panningView->raise(); 1581 _panningView->hide(); 1582 1583 setFocusPolicy(Qt::StrongFocus); 1584 setAttribute(Qt::WA_NoSystemBackground, true); 1585 1586 connect(_panningView, &PanningView::zoomRectMoved, this, &CallGraphView::zoomRectMoved); 1587 connect(_panningView, &PanningView::zoomRectMoveFinished, this, &CallGraphView::zoomRectMoveFinished); 1588 1589 this->setWhatsThis(whatsThis() ); 1590 1591 // tooltips... 1592 //_tip = new CallGraphTip(this); 1593 1594 _renderProcess = nullptr; 1595 _prevSelectedNode = nullptr; 1596 connect(&_renderTimer, &QTimer::timeout, 1597 this, &CallGraphView::showRenderWarning); 1598 } 1599 1600 CallGraphView::~CallGraphView() 1601 { 1602 clear(); 1603 delete _panningView; 1604 } 1605 1606 QString CallGraphView::whatsThis() const 1607 { 1608 return tr("<b>Call Graph around active Function</b>" 1609 "<p>Depending on configuration, this view shows " 1610 "the call graph environment of the active function. " 1611 "Note: the shown cost is <b>only</b> the cost which is " 1612 "spent while the active function was actually running; " 1613 "i.e. the cost shown for main() - if it is visible - should " 1614 "be the same as the cost of the active function, as that is " 1615 "the part of inclusive cost of main() spent while the active " 1616 "function was running.</p>" 1617 "<p>For cycles, blue call arrows indicate that this is an " 1618 "artificial call added for correct drawing which " 1619 "actually never happened.</p>" 1620 "<p>If the graph is larger than the widget area, an overview " 1621 "panner is shown in one edge. " 1622 "There are similar visualization options to the " 1623 "Call Treemap; the selected function is highlighted.</p>"); 1624 } 1625 1626 void CallGraphView::updateSizes(QSize s) 1627 { 1628 if (!_scene) 1629 return; 1630 1631 if (s == QSize(0, 0)) 1632 s = size(); 1633 1634 // the part of the scene that should be visible 1635 int cWidth = (int)_scene->width() - 2*_xMargin + 100; 1636 int cHeight = (int)_scene->height() - 2*_yMargin + 100; 1637 1638 // hide birds eye view if no overview needed 1639 if (!_data || !_activeItem || 1640 ((cWidth < s.width()) && (cHeight < s.height())) ) { 1641 _panningView->hide(); 1642 return; 1643 } 1644 1645 // first, assume use of 1/3 of width/height (possible larger) 1646 double zoom = .33 * s.width() / cWidth; 1647 if (zoom * cHeight < .33 * s.height()) 1648 zoom = .33 * s.height() / cHeight; 1649 1650 // fit to widget size 1651 if (cWidth * zoom > s.width()) 1652 zoom = s.width() / (double)cWidth; 1653 if (cHeight * zoom > s.height()) 1654 zoom = s.height() / (double)cHeight; 1655 1656 // scale to never use full height/width 1657 zoom = zoom * 3/4; 1658 1659 // at most a zoom of 1/3 1660 if (zoom > .33) 1661 zoom = .33; 1662 1663 if (zoom != _panningZoom) { 1664 _panningZoom = zoom; 1665 if (0) 1666 qDebug("Canvas Size: %fx%f, Content: %dx%d, Zoom: %f", 1667 _scene->width(), _scene->height(), cWidth, cHeight, zoom); 1668 1669 QTransform m; 1670 _panningView->setTransform(m.scale(zoom, zoom)); 1671 1672 // make it a little bigger to compensate for widget frame 1673 _panningView->resize(int(cWidth * zoom) + 4, int(cHeight * zoom) + 4); 1674 1675 // update ZoomRect in panningView 1676 scrollContentsBy(0, 0); 1677 } 1678 1679 _panningView->centerOn(_scene->width()/2, _scene->height()/2); 1680 1681 int cvW = _panningView->width(); 1682 int cvH = _panningView->height(); 1683 int x = width()- cvW - verticalScrollBar()->width() -2; 1684 int y = height()-cvH - horizontalScrollBar()->height() -2; 1685 QPoint oldZoomPos = _panningView->pos(); 1686 QPoint newZoomPos = QPoint(0, 0); 1687 ZoomPosition zp = _zoomPosition; 1688 if (zp == Auto) { 1689 int tlCols = items(QRect(0,0, cvW,cvH)).count(); 1690 int trCols = items(QRect(x,0, cvW,cvH)).count(); 1691 int blCols = items(QRect(0,y, cvW,cvH)).count(); 1692 int brCols = items(QRect(x,y, cvW,cvH)).count(); 1693 int minCols = tlCols; 1694 1695 zp = _lastAutoPosition; 1696 switch (zp) { 1697 case TopRight: 1698 minCols = trCols; 1699 break; 1700 case BottomLeft: 1701 minCols = blCols; 1702 break; 1703 case BottomRight: 1704 minCols = brCols; 1705 break; 1706 default: 1707 case TopLeft: 1708 minCols = tlCols; 1709 break; 1710 } 1711 1712 if (minCols > tlCols) { 1713 minCols = tlCols; 1714 zp = TopLeft; 1715 } 1716 if (minCols > trCols) { 1717 minCols = trCols; 1718 zp = TopRight; 1719 } 1720 if (minCols > blCols) { 1721 minCols = blCols; 1722 zp = BottomLeft; 1723 } 1724 if (minCols > brCols) { 1725 minCols = brCols; 1726 zp = BottomRight; 1727 } 1728 1729 _lastAutoPosition = zp; 1730 } 1731 1732 switch (zp) { 1733 case TopLeft: 1734 newZoomPos = QPoint(0, 0); 1735 break; 1736 case TopRight: 1737 newZoomPos = QPoint(x, 0); 1738 break; 1739 case BottomLeft: 1740 newZoomPos = QPoint(0, y); 1741 break; 1742 case BottomRight: 1743 newZoomPos = QPoint(x, y); 1744 break; 1745 default: 1746 break; 1747 } 1748 1749 if (newZoomPos != oldZoomPos) 1750 _panningView->move(newZoomPos); 1751 1752 if (zp == Hide) 1753 _panningView->hide(); 1754 else 1755 _panningView->show(); 1756 } 1757 1758 void CallGraphView::focusInEvent(QFocusEvent*) 1759 { 1760 if (!_scene) return; 1761 1762 if (_selectedNode && _selectedNode->canvasNode()) { 1763 _selectedNode->canvasNode()->setSelected(true); // requests item update 1764 _scene->update(); 1765 } 1766 } 1767 1768 void CallGraphView::focusOutEvent(QFocusEvent* e) 1769 { 1770 // trigger updates as in focusInEvent 1771 focusInEvent(e); 1772 } 1773 1774 void CallGraphView::keyPressEvent(QKeyEvent* e) 1775 { 1776 if (!_scene) { 1777 e->ignore(); 1778 return; 1779 } 1780 1781 if ((e->key() == Qt::Key_Return) ||(e->key() == Qt::Key_Space)) { 1782 if (_selectedNode) 1783 activated(_selectedNode->function()); 1784 else if (_selectedEdge && _selectedEdge->call()) 1785 activated(_selectedEdge->call()); 1786 return; 1787 } 1788 1789 // move selected node/edge 1790 if (!(e->modifiers() & (Qt::ShiftModifier | Qt::ControlModifier)) 1791 &&(_selectedNode || _selectedEdge)&&((e->key() == Qt::Key_Up) 1792 ||(e->key() == Qt::Key_Down)||(e->key() == Qt::Key_Left)||(e->key() 1793 == Qt::Key_Right))) { 1794 1795 TraceFunction* f = nullptr; 1796 TraceCall* c = nullptr; 1797 1798 // rotate arrow key meaning for LeftRight layout 1799 int key = e->key(); 1800 if (_layout == LeftRight) { 1801 switch (key) { 1802 case Qt::Key_Up: 1803 key = Qt::Key_Left; 1804 break; 1805 case Qt::Key_Down: 1806 key = Qt::Key_Right; 1807 break; 1808 case Qt::Key_Left: 1809 key = Qt::Key_Up; 1810 break; 1811 case Qt::Key_Right: 1812 key = Qt::Key_Down; 1813 break; 1814 default: 1815 break; 1816 } 1817 } 1818 1819 if (_selectedNode) { 1820 if (key == Qt::Key_Up) 1821 c = _selectedNode->visibleCaller(); 1822 if (key == Qt::Key_Down) 1823 c = _selectedNode->visibleCallee(); 1824 if (key == Qt::Key_Right) 1825 f = _selectedNode->nextVisible(); 1826 if (key == Qt::Key_Left) 1827 f = _selectedNode->priorVisible(); 1828 } else if (_selectedEdge) { 1829 if (key == Qt::Key_Up) 1830 f = _selectedEdge->visibleCaller(); 1831 if (key == Qt::Key_Down) 1832 f = _selectedEdge->visibleCallee(); 1833 if (key == Qt::Key_Right) 1834 c = _selectedEdge->nextVisible(); 1835 if (key == Qt::Key_Left) 1836 c = _selectedEdge->priorVisible(); 1837 } 1838 1839 if (c) 1840 selected(c); 1841 if (f) 1842 selected(f); 1843 return; 1844 } 1845 1846 // move canvas... 1847 QPointF center = mapToScene(viewport()->rect().center()); 1848 if (e->key() == Qt::Key_Home) 1849 centerOn(center + QPointF(-_scene->width(), 0)); 1850 else if (e->key() == Qt::Key_End) 1851 centerOn(center + QPointF(_scene->width(), 0)); 1852 else if (e->key() == Qt::Key_PageUp) { 1853 QPointF dy = mapToScene(0, height()) - mapToScene(0, 0); 1854 centerOn(center + QPointF(-dy.x()/2, -dy.y()/2)); 1855 } else if (e->key() == Qt::Key_PageDown) { 1856 QPointF dy = mapToScene(0, height()) - mapToScene(0, 0); 1857 centerOn(center + QPointF(dy.x()/2, dy.y()/2)); 1858 } else if (e->key() == Qt::Key_Left) { 1859 QPointF dx = mapToScene(width(), 0) - mapToScene(0, 0); 1860 centerOn(center + QPointF(-dx.x()/10, -dx.y()/10)); 1861 } else if (e->key() == Qt::Key_Right) { 1862 QPointF dx = mapToScene(width(), 0) - mapToScene(0, 0); 1863 centerOn(center + QPointF(dx.x()/10, dx.y()/10)); 1864 } else if (e->key() == Qt::Key_Down) { 1865 QPointF dy = mapToScene(0, height()) - mapToScene(0, 0); 1866 centerOn(center + QPointF(dy.x()/10, dy.y()/10)); 1867 } else if (e->key() == Qt::Key_Up) { 1868 QPointF dy = mapToScene(0, height()) - mapToScene(0, 0); 1869 centerOn(center + QPointF(-dy.x()/10, -dy.y()/10)); 1870 } else 1871 e->ignore(); 1872 } 1873 1874 void CallGraphView::resizeEvent(QResizeEvent* e) 1875 { 1876 QGraphicsView::resizeEvent(e); 1877 if (_scene) 1878 updateSizes(e->size()); 1879 } 1880 1881 CostItem* CallGraphView::canShow(CostItem* i) 1882 { 1883 if (i) { 1884 switch (i->type()) { 1885 case ProfileContext::Function: 1886 case ProfileContext::FunctionCycle: 1887 case ProfileContext::Call: 1888 return i; 1889 default: 1890 break; 1891 } 1892 } 1893 return nullptr; 1894 } 1895 1896 void CallGraphView::doUpdate(int changeType, bool) 1897 { 1898 // Special case ? 1899 if (changeType == eventType2Changed) 1900 return; 1901 1902 if (changeType == selectedItemChanged) { 1903 if (!_scene) 1904 return; 1905 1906 if (!_selectedItem) 1907 return; 1908 1909 GraphNode* n = nullptr; 1910 GraphEdge* e = nullptr; 1911 if ((_selectedItem->type() == ProfileContext::Function) 1912 ||(_selectedItem->type() == ProfileContext::FunctionCycle)) { 1913 n = _exporter.node((TraceFunction*)_selectedItem); 1914 if (n == _selectedNode) 1915 return; 1916 } else if (_selectedItem->type() == ProfileContext::Call) { 1917 TraceCall* c = (TraceCall*)_selectedItem; 1918 e = _exporter.edge(c->caller(false), c->called(false)); 1919 if (e == _selectedEdge) 1920 return; 1921 } 1922 1923 // unselect any selected item 1924 if (_selectedNode && _selectedNode->canvasNode()) { 1925 _selectedNode->canvasNode()->setSelected(false); 1926 } 1927 _selectedNode = nullptr; 1928 if (_selectedEdge && _selectedEdge->canvasEdge()) { 1929 _selectedEdge->canvasEdge()->setSelected(false); 1930 } 1931 _selectedEdge = nullptr; 1932 1933 // select 1934 CanvasNode* sNode = nullptr; 1935 if (n && n->canvasNode()) { 1936 _selectedNode = n; 1937 _selectedNode->canvasNode()->setSelected(true); 1938 1939 if (!_isMoving) 1940 sNode = _selectedNode->canvasNode(); 1941 } 1942 if (e && e->canvasEdge()) { 1943 _selectedEdge = e; 1944 _selectedEdge->canvasEdge()->setSelected(true); 1945 1946 #if 0 // do not change position when selecting edge 1947 if (!_isMoving) { 1948 if (_selectedEdge->fromNode()) 1949 sNode = _selectedEdge->fromNode()->canvasNode(); 1950 if (!sNode && _selectedEdge->toNode()) 1951 sNode = _selectedEdge->toNode()->canvasNode(); 1952 } 1953 #endif 1954 } 1955 if (sNode) 1956 ensureVisible(sNode); 1957 1958 _scene->update(); 1959 return; 1960 } 1961 1962 if (changeType == groupTypeChanged) { 1963 if (!_scene) 1964 return; 1965 1966 if (_clusterGroups) { 1967 refresh(); 1968 return; 1969 } 1970 1971 QList<QGraphicsItem *> l = _scene->items(); 1972 for (int i = 0; i < l.size(); ++i) 1973 if (l[i]->type() == CANVAS_NODE) 1974 ((CanvasNode*)l[i])->updateGroup(); 1975 1976 _scene->update(); 1977 return; 1978 } 1979 1980 if (changeType & dataChanged) { 1981 // invalidate old selection and graph part 1982 _exporter.reset(_data, _activeItem, _eventType, _groupType); 1983 _selectedNode = nullptr; 1984 _selectedEdge = nullptr; 1985 } 1986 1987 refresh(); 1988 } 1989 1990 void CallGraphView::clear() 1991 { 1992 if (!_scene) 1993 return; 1994 1995 _panningView->setScene(nullptr); 1996 setScene(nullptr); 1997 delete _scene; 1998 _scene = nullptr; 1999 } 2000 2001 void CallGraphView::showText(QString s) 2002 { 2003 clear(); 2004 _renderTimer.stop(); 2005 2006 _scene = new QGraphicsScene; 2007 2008 _scene->addSimpleText(s); 2009 centerOn(0, 0); 2010 setScene(_scene); 2011 _scene->update(); 2012 _panningView->hide(); 2013 } 2014 2015 void CallGraphView::showRenderWarning() 2016 { 2017 QString s; 2018 2019 if (_renderProcess) 2020 s = tr("Warning: a long lasting graph layouting is in progress.\n" 2021 "Reduce node/edge limits for speedup.\n"); 2022 else 2023 s = tr("Layouting stopped.\n"); 2024 2025 s.append(tr("The call graph has %1 nodes and %2 edges.\n") 2026 .arg(_exporter.nodeCount()).arg(_exporter.edgeCount())); 2027 2028 showText(s); 2029 } 2030 2031 void CallGraphView::showRenderError(QString s) 2032 { 2033 QString err; 2034 err = tr("No graph available because the layouting process failed.\n"); 2035 if (_renderProcess) 2036 err += tr("Trying to run the following command did not work:\n" 2037 "'%1'\n").arg(_renderProcessCmdLine); 2038 err += tr("Please check that 'dot' is installed (package GraphViz)."); 2039 2040 if (!s.isEmpty()) 2041 err += QStringLiteral("\n\n%1").arg(s); 2042 2043 showText(err); 2044 } 2045 2046 void CallGraphView::stopRendering() 2047 { 2048 if (!_renderProcess) 2049 return; 2050 2051 qDebug("CallGraphView::stopRendering: Killing QProcess %p", 2052 _renderProcess); 2053 2054 _renderProcess->kill(); 2055 2056 // forget about this process, not interesting any longer 2057 _renderProcess->deleteLater(); 2058 _renderProcess = nullptr; 2059 _unparsedOutput = QString(); 2060 2061 _renderTimer.setSingleShot(true); 2062 _renderTimer.start(200); 2063 } 2064 2065 void CallGraphView::refresh() 2066 { 2067 // trigger start of new layouting via 'dot' 2068 if (_renderProcess) 2069 stopRendering(); 2070 2071 // we want to keep a selected node item at the same global position 2072 _prevSelectedNode = _selectedNode; 2073 _prevSelectedPos = QPoint(-1, -1); 2074 if (_selectedNode) { 2075 QPointF center = _selectedNode->canvasNode()->rect().center(); 2076 _prevSelectedPos = mapFromScene(center); 2077 } 2078 2079 if (!_data || !_activeItem) { 2080 showText(tr("No item activated for which to " 2081 "draw the call graph.")); 2082 return; 2083 } 2084 2085 ProfileContext::Type t = _activeItem->type(); 2086 switch (t) { 2087 case ProfileContext::Function: 2088 case ProfileContext::FunctionCycle: 2089 case ProfileContext::Call: 2090 break; 2091 default: 2092 showText(tr("No call graph can be drawn for " 2093 "the active item.")); 2094 return; 2095 } 2096 2097 if (1) 2098 qDebug() << "CallGraphView::refresh"; 2099 2100 _selectedNode = nullptr; 2101 _selectedEdge = nullptr; 2102 2103 /* 2104 * Call 'dot' asynchronously in the background with the aim to 2105 * - have responsive GUI while layout task runs (potentially long!) 2106 * - notify user about a long run, using a timer 2107 * - kill long running 'dot' processes when another layout is 2108 * requested, as old data is not needed any more 2109 * 2110 * Even after killing a process, the QProcess needs some time 2111 * to make sure the process is destroyed; also, stdout data 2112 * still can be delivered after killing. Thus, there can/should be 2113 * multiple QProcess's at one time. 2114 * The QProcess we currently wait for data from is <_renderProcess> 2115 * Signals from other QProcesses are ignored with the exception of 2116 * the finished() signal, which triggers QProcess destruction. 2117 */ 2118 QString renderProgram; 2119 QStringList renderArgs; 2120 if (_layout == GraphOptions::Circular) 2121 renderProgram = QStringLiteral("twopi"); 2122 else 2123 renderProgram = QStringLiteral("dot"); 2124 renderArgs << QStringLiteral("-Tplain"); 2125 2126 _unparsedOutput = QString(); 2127 2128 // display warning if layouting takes > 1s 2129 _renderTimer.setSingleShot(true); 2130 _renderTimer.start(1000); 2131 2132 _renderProcess = new QProcess(this); 2133 connect(_renderProcess, &QProcess::readyReadStandardOutput, 2134 this, &CallGraphView::readDotOutput); 2135 connect(_renderProcess, SIGNAL(error(QProcess::ProcessError)), 2136 this, SLOT(dotError())); 2137 connect(_renderProcess, SIGNAL(finished(int,QProcess::ExitStatus)), 2138 this, SLOT(dotExited())); 2139 2140 _renderProcessCmdLine = renderProgram + QLatin1Char(' ') + renderArgs.join(QLatin1Char(' ')); 2141 qDebug("CallGraphView::refresh: Starting process %p, '%s'", 2142 _renderProcess, qPrintable(_renderProcessCmdLine)); 2143 2144 // _renderProcess can be set to 0 on error after start(). 2145 // thus, we use a local copy afterwards 2146 QProcess* p = _renderProcess; 2147 p->start(renderProgram, renderArgs); 2148 _exporter.reset(_data, _activeItem, _eventType, _groupType); 2149 _exporter.writeDot(p); 2150 p->closeWriteChannel(); 2151 } 2152 2153 void CallGraphView::readDotOutput() 2154 { 2155 QProcess* p = qobject_cast<QProcess*>(sender()); 2156 qDebug("CallGraphView::readDotOutput: QProcess %p", p); 2157 2158 // signal from old/uninteresting process? 2159 if ((_renderProcess == nullptr) || (p != _renderProcess)) { 2160 p->deleteLater(); 2161 return; 2162 } 2163 2164 _unparsedOutput.append(QString::fromLocal8Bit(_renderProcess->readAllStandardOutput())); 2165 } 2166 2167 void CallGraphView::dotError() 2168 { 2169 QProcess* p = qobject_cast<QProcess*>(sender()); 2170 qDebug("CallGraphView::dotError: Got %d from QProcess %p", 2171 p->error(), p); 2172 2173 // signal from old/uninteresting process? 2174 if ((_renderProcess == nullptr) || (p != _renderProcess)) { 2175 p->deleteLater(); 2176 return; 2177 } 2178 2179 showRenderError(QString::fromLocal8Bit(_renderProcess->readAllStandardError())); 2180 2181 // not interesting any longer 2182 _renderProcess->deleteLater(); 2183 _renderProcess = nullptr; 2184 } 2185 2186 2187 void CallGraphView::dotExited() 2188 { 2189 QProcess* p = qobject_cast<QProcess*>(sender()); 2190 qDebug("CallGraphView::dotExited: QProcess %p", p); 2191 2192 // signal from old/uninteresting process? 2193 if ((_renderProcess == nullptr) || (p != _renderProcess)) { 2194 p->deleteLater(); 2195 return; 2196 } 2197 2198 _unparsedOutput.append(QString::fromLocal8Bit(_renderProcess->readAllStandardOutput())); 2199 _renderProcess->deleteLater(); 2200 _renderProcess = nullptr; 2201 2202 QString line, cmd; 2203 CanvasNode *rItem; 2204 QGraphicsEllipseItem* eItem; 2205 CanvasEdge* sItem; 2206 CanvasEdgeLabel* lItem; 2207 QTextStream* dotStream; 2208 double scale = 1.0, scaleX = 1.0, scaleY = 1.0; 2209 double dotWidth = 0, dotHeight = 0; 2210 GraphNode* activeNode = nullptr; 2211 GraphEdge* activeEdge = nullptr; 2212 2213 _renderTimer.stop(); 2214 viewport()->setUpdatesEnabled(false); 2215 clear(); 2216 dotStream = new QTextStream(&_unparsedOutput, QIODevice::ReadOnly); 2217 2218 // First pass to adjust coordinate scaling by node height given from dot 2219 // Normal detail level (=1) should be 3 lines using general KDE font 2220 double nodeHeight = 0.0; 2221 while(1) { 2222 line = dotStream->readLine(); 2223 if (line.isNull()) break; 2224 if (line.isEmpty()) continue; 2225 QTextStream lineStream(&line, QIODevice::ReadOnly); 2226 lineStream >> cmd; 2227 if (cmd != QLatin1String("node")) continue; 2228 QString s, h; 2229 lineStream >> s /*name*/ >> s /*x*/ >> s /*y*/ >> s /*width*/ >> h /*height*/; 2230 nodeHeight = h.toDouble(); 2231 break; 2232 } 2233 if (nodeHeight > 0.0) { 2234 scaleY = (8 + (1 + 2 * _detailLevel) * fontMetrics().height()) / nodeHeight; 2235 scaleX = 80; 2236 } 2237 dotStream->seek(0); 2238 int lineno = 0; 2239 while (1) { 2240 line = dotStream->readLine(); 2241 if (line.isNull()) 2242 break; 2243 2244 lineno++; 2245 if (line.isEmpty()) 2246 continue; 2247 2248 QTextStream lineStream(&line, QIODevice::ReadOnly); 2249 lineStream >> cmd; 2250 2251 if (0) 2252 qDebug("%s:%d - line '%s', cmd '%s'", 2253 qPrintable(_exporter.filename()), 2254 lineno, qPrintable(line), qPrintable(cmd)); 2255 2256 if (cmd == QLatin1String("stop")) 2257 break; 2258 2259 if (cmd == QLatin1String("graph")) { 2260 QString dotWidthString, dotHeightString; 2261 // scale will not be used 2262 lineStream >> scale >> dotWidthString >> dotHeightString; 2263 dotWidth = dotWidthString.toDouble(); 2264 dotHeight = dotHeightString.toDouble(); 2265 2266 if (!_scene) { 2267 int w = (int)(scaleX * dotWidth); 2268 int h = (int)(scaleY * dotHeight); 2269 2270 // We use as minimum canvas size the desktop size. 2271 // Otherwise, the canvas would have to be resized on widget resize. 2272 _xMargin = 50; 2273 if (w < QApplication::primaryScreen()->size().width()) 2274 _xMargin += (QApplication::primaryScreen()->size().width()-w)/2; 2275 2276 _yMargin = 50; 2277 if (h < QApplication::primaryScreen()->size().height()) 2278 _yMargin += (QApplication::primaryScreen()->size().height()-h)/2; 2279 2280 _scene = new QGraphicsScene( 0.0, 0.0, 2281 qreal(w+2*_xMargin), qreal(h+2*_yMargin)); 2282 // Change background color for call graph from default system color to 2283 // white. It has to blend into the gradient for the selected function. 2284 _scene->setBackgroundBrush(Qt::white); 2285 2286 #if DEBUG_GRAPH 2287 qDebug() << qPrintable(_exporter.filename()) << ":" << lineno 2288 << " - graph (" << dotWidth << " x " << dotHeight 2289 << ") => (" << w << " x " << h << ")"; 2290 #endif 2291 } else 2292 qDebug() << "Ignoring 2nd 'graph' from dot (" 2293 << _exporter.filename() << ":"<< lineno << ")"; 2294 continue; 2295 } 2296 2297 if ((cmd != QLatin1String("node")) && (cmd != QLatin1String("edge"))) { 2298 qDebug() << "Ignoring unknown command '"<< cmd 2299 << "' from dot ("<< _exporter.filename() << ":"<< lineno 2300 << ")"; 2301 continue; 2302 } 2303 2304 if (_scene == nullptr) { 2305 qDebug() << "Ignoring '"<< cmd 2306 << "' without 'graph' from dot ("<< _exporter.filename() 2307 << ":"<< lineno << ")"; 2308 continue; 2309 } 2310 2311 if (cmd == QLatin1String("node")) { 2312 // x, y are centered in node 2313 QString nodeName, nodeX, nodeY, nodeWidth, nodeHeight; 2314 double x, y, width, height; 2315 lineStream >> nodeName >> nodeX >> nodeY >> nodeWidth 2316 >> nodeHeight; 2317 x = nodeX.toDouble(); 2318 y = nodeY.toDouble(); 2319 width = nodeWidth.toDouble(); 2320 height = nodeHeight.toDouble(); 2321 2322 GraphNode* n = _exporter.node(_exporter.toFunc(nodeName)); 2323 2324 int xx = (int)(scaleX * x + _xMargin); 2325 int yy = (int)(scaleY * (dotHeight - y)+ _yMargin); 2326 int w = (int)(scaleX * width); 2327 int h = (int)(scaleY * height); 2328 2329 #if DEBUG_GRAPH 2330 qDebug() << _exporter.filename() << ":" << lineno 2331 << " - node '" << nodeName << "' ( " 2332 << x << "/" << y << " - " 2333 << width << "x" << height << " ) => (" 2334 << xx-w/2 << "/" << yy-h/2 << " - " 2335 << w << "x" << h << ")" << endl; 2336 #endif 2337 2338 // Unnamed nodes with collapsed edges (with 'R' and 'S') 2339 if (nodeName[0] == 'R'|| nodeName[0] == 'S') { 2340 w = 10, h = 10; 2341 eItem = new QGraphicsEllipseItem( QRectF(xx-w/2, yy-h/2, w, h) ); 2342 _scene->addItem(eItem); 2343 eItem->setBrush(Qt::gray); 2344 eItem->setZValue(1.0); 2345 eItem->show(); 2346 continue; 2347 } 2348 2349 if (!n) { 2350 qDebug("Warning: Unknown function '%s' ?!", 2351 qPrintable(nodeName)); 2352 continue; 2353 } 2354 n->setVisible(true); 2355 2356 rItem = new CanvasNode(this, n, xx-w/2, yy-h/2, w, h); 2357 // limit symbol space to a maximal number of lines depending on detail level 2358 if (_detailLevel>0) rItem->setMaxLines(0, 2*_detailLevel); 2359 _scene->addItem(rItem); 2360 n->setCanvasNode(rItem); 2361 2362 if (n) { 2363 if (n->function() == activeItem()) 2364 activeNode = n; 2365 if (n->function() == selectedItem()) 2366 _selectedNode = n; 2367 rItem->setSelected(n == _selectedNode); 2368 } 2369 2370 rItem->setZValue(1.0); 2371 rItem->show(); 2372 2373 continue; 2374 } 2375 2376 // edge 2377 2378 QString node1Name, node2Name, label, edgeX, edgeY; 2379 double x, y; 2380 QPolygon poly; 2381 int points, i; 2382 lineStream >> node1Name >> node2Name >> points; 2383 2384 GraphEdge* e = _exporter.edge(_exporter.toFunc(node1Name), 2385 _exporter.toFunc(node2Name)); 2386 if (!e) { 2387 qDebug() << "Unknown edge '"<< node1Name << "'-'"<< node2Name 2388 << "' from dot ("<< _exporter.filename() << ":"<< lineno 2389 << ")"; 2390 continue; 2391 } 2392 e->setVisible(true); 2393 if (e->fromNode()) 2394 e->fromNode()->addCallee(e); 2395 if (e->toNode()) 2396 e->toNode()->addCaller(e); 2397 2398 if (0) 2399 qDebug(" Edge with %d points:", points); 2400 2401 poly.resize(points); 2402 for (i=0; i<points; ++i) { 2403 if (lineStream.atEnd()) 2404 break; 2405 lineStream >> edgeX >> edgeY; 2406 x = edgeX.toDouble(); 2407 y = edgeY.toDouble(); 2408 2409 int xx = (int)(scaleX * x + _xMargin); 2410 int yy = (int)(scaleY * (dotHeight - y)+ _yMargin); 2411 2412 if (0) 2413 qDebug(" P %d: ( %f / %f ) => ( %d / %d)", i, x, y, xx, yy); 2414 2415 poly.setPoint(i, xx, yy); 2416 } 2417 if (i < points) { 2418 qDebug("CallGraphView: Can not read %d spline points (%s:%d)", 2419 points, qPrintable(_exporter.filename()), lineno); 2420 continue; 2421 } 2422 2423 // calls into/out of cycles are special: make them blue 2424 QColor arrowColor = Qt::black; 2425 TraceFunction* caller = e->fromNode() ? e->fromNode()->function() : nullptr; 2426 TraceFunction* called = e->toNode() ? e->toNode()->function() : nullptr; 2427 if ( (caller && (caller->cycle() == caller)) || 2428 (called && (called->cycle() == called)) ) arrowColor = Qt::blue; 2429 2430 sItem = new CanvasEdge(e); 2431 _scene->addItem(sItem); 2432 e->setCanvasEdge(sItem); 2433 sItem->setControlPoints(poly); 2434 // width of pen will be adjusted in CanvasEdge::paint() 2435 sItem->setPen(QPen(arrowColor)); 2436 sItem->setZValue(0.5); 2437 sItem->show(); 2438 2439 if (e->call() == selectedItem()) 2440 _selectedEdge = e; 2441 if (e->call() == activeItem()) 2442 activeEdge = e; 2443 sItem->setSelected(e == _selectedEdge); 2444 2445 // Arrow head 2446 QPoint arrowDir; 2447 int indexHead = -1; 2448 2449 // check if head is at start of spline... 2450 // this is needed because dot always gives points from top to bottom 2451 CanvasNode* fromNode = e->fromNode() ? e->fromNode()->canvasNode() : nullptr; 2452 if (fromNode) { 2453 QPointF toCenter = fromNode->rect().center(); 2454 qreal dx0 = poly.point(0).x() - toCenter.x(); 2455 qreal dy0 = poly.point(0).y() - toCenter.y(); 2456 qreal dx1 = poly.point(points-1).x() - toCenter.x(); 2457 qreal dy1 = poly.point(points-1).y() - toCenter.y(); 2458 if (dx0*dx0+dy0*dy0 > dx1*dx1+dy1*dy1) { 2459 // start of spline is nearer to call target node 2460 indexHead=-1; 2461 while (arrowDir.isNull() && (indexHead<points-2)) { 2462 indexHead++; 2463 arrowDir = poly.point(indexHead) - poly.point(indexHead+1); 2464 } 2465 } 2466 } 2467 2468 if (arrowDir.isNull()) { 2469 indexHead = points; 2470 // sometimes the last spline points from dot are the same... 2471 while (arrowDir.isNull() && (indexHead>1)) { 2472 indexHead--; 2473 arrowDir = poly.point(indexHead) - poly.point(indexHead-1); 2474 } 2475 } 2476 2477 if (!arrowDir.isNull()) { 2478 // arrow around pa.point(indexHead) with direction arrowDir 2479 arrowDir *= 10.0/sqrt(double(arrowDir.x()*arrowDir.x() + 2480 arrowDir.y()*arrowDir.y())); 2481 2482 QPolygonF a; 2483 a << QPointF(poly.point(indexHead) + arrowDir); 2484 a << QPointF(poly.point(indexHead) + QPoint(arrowDir.y()/2, 2485 -arrowDir.x()/2)); 2486 a << QPointF(poly.point(indexHead) + QPoint(-arrowDir.y()/2, 2487 arrowDir.x()/2)); 2488 2489 if (0) 2490 qDebug(" Arrow: ( %f/%f, %f/%f, %f/%f)", a[0].x(), a[0].y(), 2491 a[1].x(), a[1].y(), a[2].x(), a[1].y()); 2492 2493 CanvasEdgeArrow* aItem = new CanvasEdgeArrow(sItem); 2494 _scene->addItem(aItem); 2495 aItem->setPolygon(a); 2496 aItem->setBrush(arrowColor); 2497 aItem->setZValue(1.5); 2498 aItem->show(); 2499 2500 sItem->setArrow(aItem); 2501 } 2502 2503 if (lineStream.atEnd()) 2504 continue; 2505 2506 // parse quoted label 2507 QChar c; 2508 lineStream >> c; 2509 while (c.isSpace()) 2510 lineStream >> c; 2511 if (c != '\"') { 2512 lineStream >> label; 2513 label = c + label; 2514 } else { 2515 lineStream >> c; 2516 while (!c.isNull() && (c != '\"')) { 2517 //if (c == '\\') lineStream >> c; 2518 2519 label += c; 2520 lineStream >> c; 2521 } 2522 } 2523 lineStream >> edgeX >> edgeY; 2524 x = edgeX.toDouble(); 2525 y = edgeY.toDouble(); 2526 2527 int xx = (int)(scaleX * x + _xMargin); 2528 int yy = (int)(scaleY * (dotHeight - y)+ _yMargin); 2529 2530 if (0) 2531 qDebug(" Label '%s': ( %f / %f ) => ( %d / %d)", 2532 qPrintable(label), x, y, xx, yy); 2533 2534 // Fixed Dimensions for Label: 100 x 40 2535 int w = 100; 2536 int h = _detailLevel * 20; 2537 lItem = new CanvasEdgeLabel(this, sItem, xx-w/2, yy-h/2, w, h); 2538 _scene->addItem(lItem); 2539 // edge labels above nodes 2540 lItem->setZValue(1.5); 2541 sItem->setLabel(lItem); 2542 if (h>0) 2543 lItem->show(); 2544 2545 } 2546 delete dotStream; 2547 2548 // for keyboard navigation 2549 _exporter.sortEdges(); 2550 2551 if (!_scene) { 2552 _scene = new QGraphicsScene; 2553 2554 QString s = tr("Error running the graph layouting tool.\n"); 2555 s += tr("Please check that 'dot' is installed (package GraphViz)."); 2556 _scene->addSimpleText(s); 2557 centerOn(0, 0); 2558 } else if (!activeNode && !activeEdge) { 2559 QString s = tr("There is no call graph available for function\n" 2560 "\t'%1'\n" 2561 "because it has no cost of the selected event type.") 2562 .arg(_activeItem->name()); 2563 _scene->addSimpleText(s); 2564 centerOn(0, 0); 2565 } 2566 2567 _panningView->setScene(_scene); 2568 setScene(_scene); 2569 2570 // if we do not have a selection, or the old selection is not 2571 // in visible graph, make active function selected for this view 2572 if ((!_selectedNode || !_selectedNode->canvasNode()) && 2573 (!_selectedEdge || !_selectedEdge->canvasEdge())) { 2574 if (activeNode) { 2575 _selectedNode = activeNode; 2576 _selectedNode->canvasNode()->setSelected(true); 2577 } else if (activeEdge) { 2578 _selectedEdge = activeEdge; 2579 _selectedEdge->canvasEdge()->setSelected(true); 2580 } 2581 } 2582 2583 CanvasNode* sNode = nullptr; 2584 if (_selectedNode) 2585 sNode = _selectedNode->canvasNode(); 2586 else if (_selectedEdge) { 2587 if (_selectedEdge->fromNode()) 2588 sNode = _selectedEdge->fromNode()->canvasNode(); 2589 if (!sNode && _selectedEdge->toNode()) 2590 sNode = _selectedEdge->toNode()->canvasNode(); 2591 } 2592 2593 if (sNode) { 2594 if (_prevSelectedNode) { 2595 QPointF prevPos = mapToScene(_prevSelectedPos); 2596 if (rect().contains(_prevSelectedPos)) { 2597 QPointF wCenter = mapToScene(viewport()->rect().center()); 2598 centerOn(sNode->rect().center() + 2599 wCenter - prevPos); 2600 } 2601 else 2602 ensureVisible(sNode); 2603 } else 2604 centerOn(sNode); 2605 } 2606 2607 if (activeNode) { 2608 CanvasNode* cn = activeNode->canvasNode(); 2609 CanvasFrame* f = new CanvasFrame(cn); 2610 _scene->addItem(f); 2611 f->setPos(cn->pos()); 2612 f->setZValue(-1); 2613 } 2614 2615 _panningZoom = 0; 2616 updateSizes(); 2617 2618 _scene->update(); 2619 viewport()->setUpdatesEnabled(true); 2620 2621 delete _renderProcess; 2622 _renderProcess = nullptr; 2623 } 2624 2625 2626 // Called by QAbstractScrollArea to notify about scrollbar changes 2627 void CallGraphView::scrollContentsBy(int dx, int dy) 2628 { 2629 // call QGraphicsView implementation 2630 QGraphicsView::scrollContentsBy(dx, dy); 2631 2632 QPointF topLeft = mapToScene(QPoint(0, 0)); 2633 QPointF bottomRight = mapToScene(QPoint(width(), height())); 2634 2635 QRectF z(topLeft, bottomRight); 2636 if (0) 2637 qDebug("CallGraphView::scrollContentsBy(dx %d, dy %d) - to (%f,%f - %f,%f)", 2638 dx, dy, topLeft.x(), topLeft.y(), 2639 bottomRight.x(), bottomRight.y()); 2640 _panningView->setZoomRect(z); 2641 } 2642 2643 // Handles zooming in and out with 'ctrl + mouse-wheel-up/down' 2644 void CallGraphView::wheelEvent(QWheelEvent* e) 2645 { 2646 if (e->modifiers() & Qt::ControlModifier) 2647 { 2648 int angle = e->angleDelta().y(); 2649 if ((_zoomLevel <= 0.5 && angle < 0) || (_zoomLevel >= 1.3 && angle > 0)) 2650 return; 2651 2652 const ViewportAnchor anchor = transformationAnchor(); 2653 setTransformationAnchor(QGraphicsView::AnchorViewCenter); 2654 qreal factor; 2655 if (angle > 0) 2656 factor = 1.1; 2657 else 2658 factor = 0.9; 2659 2660 scale(factor, factor); 2661 _zoomLevel = transform().m11(); 2662 setTransformationAnchor(anchor); 2663 return; 2664 } 2665 // Don't eat the event if we didn't hold 'ctrl' 2666 QGraphicsView::wheelEvent(e); 2667 } 2668 2669 2670 void CallGraphView::zoomRectMoved(qreal dx, qreal dy) 2671 { 2672 //FIXME if (leftMargin()>0) dx = 0; 2673 //FIXME if (topMargin()>0) dy = 0; 2674 2675 QScrollBar *hBar = horizontalScrollBar(); 2676 QScrollBar *vBar = verticalScrollBar(); 2677 hBar->setValue(hBar->value() + int(dx)); 2678 vBar->setValue(vBar->value() + int(dy)); 2679 } 2680 2681 void CallGraphView::zoomRectMoveFinished() 2682 { 2683 if (_zoomPosition == Auto) 2684 updateSizes(); 2685 } 2686 2687 void CallGraphView::mousePressEvent(QMouseEvent* e) 2688 { 2689 // clicking on the viewport sets focus 2690 setFocus(); 2691 2692 // activate scroll mode on left click 2693 if (e->button() == Qt::LeftButton) _isMoving = true; 2694 2695 QGraphicsItem* i = itemAt(e->pos()); 2696 if (i) { 2697 2698 if (i->type() == CANVAS_NODE) { 2699 GraphNode* n = ((CanvasNode*)i)->node(); 2700 if (0) 2701 qDebug("CallGraphView: Got Node '%s'", 2702 qPrintable(n->function()->prettyName())); 2703 2704 selected(n->function()); 2705 } 2706 2707 // redirect from label / arrow to edge 2708 if (i->type() == CANVAS_EDGELABEL) 2709 i = ((CanvasEdgeLabel*)i)->canvasEdge(); 2710 if (i->type() == CANVAS_EDGEARROW) 2711 i = ((CanvasEdgeArrow*)i)->canvasEdge(); 2712 2713 if (i->type() == CANVAS_EDGE) { 2714 GraphEdge* e = ((CanvasEdge*)i)->edge(); 2715 if (0) 2716 qDebug("CallGraphView: Got Edge '%s'", 2717 qPrintable(e->prettyName())); 2718 2719 if (e->call()) 2720 selected(e->call()); 2721 } 2722 } 2723 _lastPos = e->pos(); 2724 } 2725 2726 void CallGraphView::mouseMoveEvent(QMouseEvent* e) 2727 { 2728 if (_isMoving) { 2729 QPoint delta = e->pos() - _lastPos; 2730 2731 QScrollBar *hBar = horizontalScrollBar(); 2732 QScrollBar *vBar = verticalScrollBar(); 2733 hBar->setValue(hBar->value() - delta.x()); 2734 vBar->setValue(vBar->value() - delta.y()); 2735 2736 _lastPos = e->pos(); 2737 } 2738 } 2739 2740 void CallGraphView::mouseReleaseEvent(QMouseEvent*) 2741 { 2742 _isMoving = false; 2743 if (_zoomPosition == Auto) 2744 updateSizes(); 2745 } 2746 2747 void CallGraphView::mouseDoubleClickEvent(QMouseEvent* e) 2748 { 2749 QGraphicsItem* i = itemAt(e->pos()); 2750 if (i == nullptr) 2751 return; 2752 2753 if (i->type() == CANVAS_NODE) { 2754 GraphNode* n = ((CanvasNode*)i)->node(); 2755 if (0) 2756 qDebug("CallGraphView: Double Clicked on Node '%s'", 2757 qPrintable(n->function()->prettyName())); 2758 2759 activated(n->function()); 2760 } 2761 2762 // redirect from label / arrow to edge 2763 if (i->type() == CANVAS_EDGELABEL) 2764 i = ((CanvasEdgeLabel*)i)->canvasEdge(); 2765 if (i->type() == CANVAS_EDGEARROW) 2766 i = ((CanvasEdgeArrow*)i)->canvasEdge(); 2767 2768 if (i->type() == CANVAS_EDGE) { 2769 GraphEdge* e = ((CanvasEdge*)i)->edge(); 2770 if (e->call()) { 2771 if (0) 2772 qDebug("CallGraphView: Double Clicked On Edge '%s'", 2773 qPrintable(e->call()->prettyName())); 2774 2775 activated(e->call()); 2776 } 2777 } 2778 } 2779 2780 // helper functions for context menu: 2781 // submenu builders and trigger handlers 2782 2783 QAction* CallGraphView::addCallerDepthAction(QMenu* m, QString s, int d) 2784 { 2785 QAction* a; 2786 2787 a = m->addAction(s); 2788 a->setData(d); 2789 a->setCheckable(true); 2790 a->setChecked(_maxCallerDepth == d); 2791 2792 return a; 2793 } 2794 2795 QMenu* CallGraphView::addCallerDepthMenu(QMenu* menu) 2796 { 2797 QAction* a; 2798 QMenu* m; 2799 2800 m = menu->addMenu(tr("Caller Depth")); 2801 a = addCallerDepthAction(m, tr("Unlimited"), -1); 2802 a->setEnabled(_funcLimit>0.005); 2803 m->addSeparator(); 2804 addCallerDepthAction(m, tr("Depth 0", "None"), 0); 2805 addCallerDepthAction(m, tr("max. 1"), 1); 2806 addCallerDepthAction(m, tr("max. 2"), 2); 2807 addCallerDepthAction(m, tr("max. 5"), 5); 2808 addCallerDepthAction(m, tr("max. 10"), 10); 2809 addCallerDepthAction(m, tr("max. 15"), 15); 2810 2811 connect(m, &QMenu::triggered, 2812 this, &CallGraphView::callerDepthTriggered ); 2813 2814 return m; 2815 } 2816 2817 void CallGraphView::callerDepthTriggered(QAction* a) 2818 { 2819 _maxCallerDepth = a->data().toInt(nullptr); 2820 refresh(); 2821 } 2822 2823 QAction* CallGraphView::addCalleeDepthAction(QMenu* m, QString s, int d) 2824 { 2825 QAction* a; 2826 2827 a = m->addAction(s); 2828 a->setData(d); 2829 a->setCheckable(true); 2830 a->setChecked(_maxCalleeDepth == d); 2831 2832 return a; 2833 } 2834 2835 QMenu* CallGraphView::addCalleeDepthMenu(QMenu* menu) 2836 { 2837 QAction* a; 2838 QMenu* m; 2839 2840 m = menu->addMenu(tr("Callee Depth")); 2841 a = addCalleeDepthAction(m, tr("Unlimited"), -1); 2842 a->setEnabled(_funcLimit>0.005); 2843 m->addSeparator(); 2844 addCalleeDepthAction(m, tr("Depth 0", "None"), 0); 2845 addCalleeDepthAction(m, tr("max. 1"), 1); 2846 addCalleeDepthAction(m, tr("max. 2"), 2); 2847 addCalleeDepthAction(m, tr("max. 5"), 5); 2848 addCalleeDepthAction(m, tr("max. 10"), 10); 2849 addCalleeDepthAction(m, tr("max. 15"), 15); 2850 2851 connect(m, &QMenu::triggered, 2852 this, &CallGraphView::calleeDepthTriggered ); 2853 2854 return m; 2855 } 2856 2857 void CallGraphView::calleeDepthTriggered(QAction* a) 2858 { 2859 _maxCalleeDepth = a->data().toInt(nullptr); 2860 refresh(); 2861 } 2862 2863 QAction* CallGraphView::addNodeLimitAction(QMenu* m, QString s, double l) 2864 { 2865 QAction* a; 2866 2867 a = m->addAction(s); 2868 a->setData(l); 2869 a->setCheckable(true); 2870 a->setChecked(_funcLimit == l); 2871 2872 return a; 2873 } 2874 2875 QMenu* CallGraphView::addNodeLimitMenu(QMenu* menu) 2876 { 2877 QAction* a; 2878 QMenu* m; 2879 2880 m = menu->addMenu(tr("Min. Node Cost")); 2881 a = addNodeLimitAction(m, tr("No Minimum"), 0.0); 2882 // Unlimited node cost easily produces huge graphs such that 'dot' 2883 // would need a long time to layout. For responsiveness, we only allow 2884 // for unlimited node cost if a caller and callee depth limit is set. 2885 a->setEnabled((_maxCallerDepth>=0) && (_maxCalleeDepth>=0)); 2886 m->addSeparator(); 2887 addNodeLimitAction(m, tr("50 %"), .5); 2888 addNodeLimitAction(m, tr("20 %"), .2); 2889 addNodeLimitAction(m, tr("10 %"), .1); 2890 addNodeLimitAction(m, tr("5 %"), .05); 2891 addNodeLimitAction(m, tr("2 %"), .02); 2892 addNodeLimitAction(m, tr("1 %"), .01); 2893 2894 connect(m, &QMenu::triggered, 2895 this, &CallGraphView::nodeLimitTriggered ); 2896 2897 return m; 2898 } 2899 2900 void CallGraphView::nodeLimitTriggered(QAction* a) 2901 { 2902 _funcLimit = a->data().toDouble(nullptr); 2903 refresh(); 2904 } 2905 2906 QAction* CallGraphView::addCallLimitAction(QMenu* m, QString s, double l) 2907 { 2908 QAction* a; 2909 2910 a = m->addAction(s); 2911 a->setData(l); 2912 a->setCheckable(true); 2913 a->setChecked(_callLimit == l); 2914 2915 return a; 2916 } 2917 2918 QMenu* CallGraphView::addCallLimitMenu(QMenu* menu) 2919 { 2920 QMenu* m; 2921 2922 m = menu->addMenu(tr("Min. Call Cost")); 2923 addCallLimitAction(m, tr("Same as Node"), 1.0); 2924 // xgettext: no-c-format 2925 addCallLimitAction(m, tr("50 % of Node"), .5); 2926 // xgettext: no-c-format 2927 addCallLimitAction(m, tr("20 % of Node"), .2); 2928 // xgettext: no-c-format 2929 addCallLimitAction(m, tr("10 % of Node"), .1); 2930 2931 connect(m, &QMenu::triggered, 2932 this, &CallGraphView::callLimitTriggered ); 2933 2934 return m; 2935 } 2936 2937 void CallGraphView::callLimitTriggered(QAction* a) 2938 { 2939 _callLimit = a->data().toDouble(nullptr); 2940 refresh(); 2941 } 2942 2943 QAction* CallGraphView::addZoomPosAction(QMenu* m, QString s, 2944 CallGraphView::ZoomPosition p) 2945 { 2946 QAction* a; 2947 2948 a = m->addAction(s); 2949 a->setData((int)p); 2950 a->setCheckable(true); 2951 a->setChecked(_zoomPosition == p); 2952 2953 return a; 2954 } 2955 2956 QMenu* CallGraphView::addZoomPosMenu(QMenu* menu) 2957 { 2958 QMenu* m = menu->addMenu(tr("Birds-eye View")); 2959 addZoomPosAction(m, tr("Top Left"), TopLeft); 2960 addZoomPosAction(m, tr("Top Right"), TopRight); 2961 addZoomPosAction(m, tr("Bottom Left"), BottomLeft); 2962 addZoomPosAction(m, tr("Bottom Right"), BottomRight); 2963 addZoomPosAction(m, tr("Automatic"), Auto); 2964 addZoomPosAction(m, tr("Hide"), Hide); 2965 2966 connect(m, &QMenu::triggered, 2967 this, &CallGraphView::zoomPosTriggered ); 2968 2969 return m; 2970 } 2971 2972 2973 void CallGraphView::zoomPosTriggered(QAction* a) 2974 { 2975 _zoomPosition = (ZoomPosition) a->data().toInt(nullptr); 2976 updateSizes(); 2977 } 2978 2979 QAction* CallGraphView::addLayoutAction(QMenu* m, QString s, 2980 GraphOptions::Layout l) 2981 { 2982 QAction* a; 2983 2984 a = m->addAction(s); 2985 a->setData((int)l); 2986 a->setCheckable(true); 2987 a->setChecked(_layout == l); 2988 2989 return a; 2990 } 2991 2992 QMenu* CallGraphView::addLayoutMenu(QMenu* menu) 2993 { 2994 QMenu* m = menu->addMenu(tr("Layout")); 2995 addLayoutAction(m, tr("Top to Down"), TopDown); 2996 addLayoutAction(m, tr("Left to Right"), LeftRight); 2997 addLayoutAction(m, tr("Circular"), Circular); 2998 2999 connect(m, &QMenu::triggered, 3000 this, &CallGraphView::layoutTriggered ); 3001 3002 return m; 3003 } 3004 3005 3006 void CallGraphView::layoutTriggered(QAction* a) 3007 { 3008 _layout = (Layout) a->data().toInt(nullptr); 3009 refresh(); 3010 } 3011 3012 3013 void CallGraphView::contextMenuEvent(QContextMenuEvent* e) 3014 { 3015 _isMoving = false; 3016 3017 QGraphicsItem* i = itemAt(e->pos()); 3018 3019 QMenu popup; 3020 TraceFunction *f = nullptr, *cycle = nullptr; 3021 TraceCall* c = nullptr; 3022 3023 QAction* activateFunction = nullptr; 3024 QAction* activateCycle = nullptr; 3025 QAction* activateCall = nullptr; 3026 if (i) { 3027 if (i->type() == CANVAS_NODE) { 3028 GraphNode* n = ((CanvasNode*)i)->node(); 3029 if (0) 3030 qDebug("CallGraphView: Menu on Node '%s'", 3031 qPrintable(n->function()->prettyName())); 3032 3033 f = n->function(); 3034 cycle = f->cycle(); 3035 3036 QString name = f->prettyName(); 3037 QString menuStr = tr("Go to '%1'") 3038 .arg(GlobalConfig::shortenSymbol(name)); 3039 activateFunction = popup.addAction(menuStr); 3040 if (cycle && (cycle != f)) { 3041 name = GlobalConfig::shortenSymbol(cycle->prettyName()); 3042 activateCycle = popup.addAction(tr("Go to '%1'").arg(name)); 3043 } 3044 popup.addSeparator(); 3045 } 3046 3047 // redirect from label / arrow to edge 3048 if (i->type() == CANVAS_EDGELABEL) 3049 i = ((CanvasEdgeLabel*)i)->canvasEdge(); 3050 if (i->type() == CANVAS_EDGEARROW) 3051 i = ((CanvasEdgeArrow*)i)->canvasEdge(); 3052 3053 if (i->type() == CANVAS_EDGE) { 3054 GraphEdge* e = ((CanvasEdge*)i)->edge(); 3055 if (0) 3056 qDebug("CallGraphView: Menu on Edge '%s'", 3057 qPrintable(e->prettyName())); 3058 3059 c = e->call(); 3060 if (c) { 3061 QString name = c->prettyName(); 3062 QString menuStr = tr("Go to '%1'") 3063 .arg(GlobalConfig::shortenSymbol(name)); 3064 activateCall = popup.addAction(menuStr); 3065 3066 popup.addSeparator(); 3067 } 3068 } 3069 } 3070 3071 QAction* stopLayout = nullptr; 3072 if (_renderProcess) { 3073 stopLayout = popup.addAction(tr("Stop Layouting")); 3074 popup.addSeparator(); 3075 } 3076 3077 addGoMenu(&popup); 3078 popup.addSeparator(); 3079 3080 QMenu* epopup = popup.addMenu(tr("Export Graph")); 3081 QAction* exportAsDot = epopup->addAction(tr("As DOT file...")); 3082 QAction* exportAsImage = epopup->addAction(tr("As Image...")); 3083 popup.addSeparator(); 3084 3085 QMenu* gpopup = popup.addMenu(tr("Graph")); 3086 addCallerDepthMenu(gpopup); 3087 addCalleeDepthMenu(gpopup); 3088 addNodeLimitMenu(gpopup); 3089 addCallLimitMenu(gpopup); 3090 gpopup->addSeparator(); 3091 3092 QAction* toggleSkipped; 3093 toggleSkipped = gpopup->addAction(tr("Arrows for Skipped Calls")); 3094 toggleSkipped->setCheckable(true); 3095 toggleSkipped->setChecked(_showSkipped); 3096 3097 QAction* toggleExpand; 3098 toggleExpand = gpopup->addAction(tr("Inner-cycle Calls")); 3099 toggleExpand->setCheckable(true); 3100 toggleExpand->setChecked(_expandCycles); 3101 3102 QAction* toggleCluster; 3103 toggleCluster = gpopup->addAction(tr("Cluster Groups")); 3104 toggleCluster->setCheckable(true); 3105 toggleCluster->setChecked(_clusterGroups); 3106 3107 QMenu* vpopup = popup.addMenu(tr("Visualization")); 3108 QAction* layoutCompact = vpopup->addAction(tr("Compact")); 3109 layoutCompact->setCheckable(true); 3110 layoutCompact->setChecked(_detailLevel == 0); 3111 QAction* layoutNormal = vpopup->addAction(tr("Normal")); 3112 layoutNormal->setCheckable(true); 3113 layoutNormal->setChecked(_detailLevel == 1); 3114 QAction* layoutTall = vpopup->addAction(tr("Tall")); 3115 layoutTall->setCheckable(true); 3116 layoutTall->setChecked(_detailLevel == 2); 3117 3118 addLayoutMenu(&popup); 3119 addZoomPosMenu(&popup); 3120 3121 QAction* a = popup.exec(e->globalPos()); 3122 3123 if (a == activateFunction) 3124 activated(f); 3125 else if (a == activateCycle) 3126 activated(cycle); 3127 else if (a == activateCall) 3128 activated(c); 3129 3130 else if (a == stopLayout) 3131 stopRendering(); 3132 3133 else if (a == exportAsDot) { 3134 TraceFunction* f = activeFunction(); 3135 if (!f) return; 3136 3137 GraphExporter::savePrompt(this, TraceItemView::data(), f, eventType(), 3138 groupType(), this); 3139 } 3140 else if (a == exportAsImage) { 3141 // write current content of canvas as image to file 3142 if (!_scene) return; 3143 3144 QString n; 3145 n = QFileDialog::getSaveFileName(this, 3146 tr("Export Graph As Image"), 3147 QString(), 3148 tr("Images (*.png *.jpg)")); 3149 3150 if (!n.isEmpty()) { 3151 QRect r = _scene->sceneRect().toRect(); 3152 QPixmap pix(r.width(), r.height()); 3153 QPainter p(&pix); 3154 _scene->render( &p ); 3155 pix.save(n); 3156 } 3157 } 3158 3159 else if (a == toggleSkipped) { 3160 _showSkipped = !_showSkipped; 3161 refresh(); 3162 } 3163 else if (a == toggleExpand) { 3164 _expandCycles = !_expandCycles; 3165 refresh(); 3166 } 3167 else if (a == toggleCluster) { 3168 _clusterGroups = !_clusterGroups; 3169 refresh(); 3170 } 3171 3172 else if (a == layoutCompact) { 3173 _detailLevel = 0; 3174 refresh(); 3175 } 3176 else if (a == layoutNormal) { 3177 _detailLevel = 1; 3178 refresh(); 3179 } 3180 else if (a == layoutTall) { 3181 _detailLevel = 2; 3182 refresh(); 3183 } 3184 } 3185 3186 3187 CallGraphView::ZoomPosition CallGraphView::zoomPos(QString s) 3188 { 3189 if (s == QLatin1String("TopLeft")) 3190 return TopLeft; 3191 if (s == QLatin1String("TopRight")) 3192 return TopRight; 3193 if (s == QLatin1String("BottomLeft")) 3194 return BottomLeft; 3195 if (s == QLatin1String("BottomRight")) 3196 return BottomRight; 3197 if (s == QLatin1String("Automatic")) 3198 return Auto; 3199 if (s == QLatin1String("Hide")) 3200 return Hide; 3201 3202 return DEFAULT_ZOOMPOS; 3203 } 3204 3205 QString CallGraphView::zoomPosString(ZoomPosition p) 3206 { 3207 if (p == TopLeft) 3208 return QStringLiteral("TopLeft"); 3209 if (p == TopRight) 3210 return QStringLiteral("TopRight"); 3211 if (p == BottomLeft) 3212 return QStringLiteral("BottomLeft"); 3213 if (p == BottomRight) 3214 return QStringLiteral("BottomRight"); 3215 if (p == Auto) 3216 return QStringLiteral("Automatic"); 3217 if (p == Hide) 3218 return QStringLiteral("Hide"); 3219 3220 return QString(); 3221 } 3222 3223 void CallGraphView::restoreOptions(const QString& prefix, const QString& postfix) 3224 { 3225 ConfigGroup* g = ConfigStorage::group(prefix, postfix); 3226 3227 _maxCallerDepth = g->value(QStringLiteral("MaxCaller"), DEFAULT_MAXCALLER).toInt(); 3228 _maxCalleeDepth = g->value(QStringLiteral("MaxCallee"), DEFAULT_MAXCALLEE).toInt(); 3229 _funcLimit = g->value(QStringLiteral("FuncLimit"), DEFAULT_FUNCLIMIT).toDouble(); 3230 _callLimit = g->value(QStringLiteral("CallLimit"), DEFAULT_CALLLIMIT).toDouble(); 3231 _showSkipped = g->value(QStringLiteral("ShowSkipped"), DEFAULT_SHOWSKIPPED).toBool(); 3232 _expandCycles = g->value(QStringLiteral("ExpandCycles"), DEFAULT_EXPANDCYCLES).toBool(); 3233 _clusterGroups = g->value(QStringLiteral("ClusterGroups"), DEFAULT_CLUSTERGROUPS).toBool(); 3234 _detailLevel = g->value(QStringLiteral("DetailLevel"), DEFAULT_DETAILLEVEL).toInt(); 3235 _layout = GraphOptions::layout(g->value(QStringLiteral("Layout"), 3236 layoutString(DEFAULT_LAYOUT)).toString()); 3237 _zoomPosition = zoomPos(g->value(QStringLiteral("ZoomPosition"), 3238 zoomPosString(DEFAULT_ZOOMPOS)).toString()); 3239 3240 delete g; 3241 } 3242 3243 void CallGraphView::saveOptions(const QString& prefix, const QString& postfix) 3244 { 3245 ConfigGroup* g = ConfigStorage::group(prefix + postfix); 3246 3247 g->setValue(QStringLiteral("MaxCaller"), _maxCallerDepth, DEFAULT_MAXCALLER); 3248 g->setValue(QStringLiteral("MaxCallee"), _maxCalleeDepth, DEFAULT_MAXCALLEE); 3249 g->setValue(QStringLiteral("FuncLimit"), _funcLimit, DEFAULT_FUNCLIMIT); 3250 g->setValue(QStringLiteral("CallLimit"), _callLimit, DEFAULT_CALLLIMIT); 3251 g->setValue(QStringLiteral("ShowSkipped"), _showSkipped, DEFAULT_SHOWSKIPPED); 3252 g->setValue(QStringLiteral("ExpandCycles"), _expandCycles, DEFAULT_EXPANDCYCLES); 3253 g->setValue(QStringLiteral("ClusterGroups"), _clusterGroups, DEFAULT_CLUSTERGROUPS); 3254 g->setValue(QStringLiteral("DetailLevel"), _detailLevel, DEFAULT_DETAILLEVEL); 3255 g->setValue(QStringLiteral("Layout"), layoutString(_layout), layoutString(DEFAULT_LAYOUT)); 3256 g->setValue(QStringLiteral("ZoomPosition"), zoomPosString(_zoomPosition), 3257 zoomPosString(DEFAULT_ZOOMPOS)); 3258 3259 delete g; 3260 } 3261 3262 #include "moc_callgraphview.cpp"