1 /*
2     This file is part of KCachegrind.
3 
4     SPDX-FileCopyrightText: 2007-2016 Josef Weidendorfer <Josef.Weidendorfer@gmx.de>
5 
6     SPDX-License-Identifier: GPL-2.0-only
7 */
8 
9 /*
10  * Callgraph View
11  */
12 
13 #include "callgraphview.h"
14 
15 #include <stdlib.h>
16 #include <math.h>
17 
18 #include <QApplication>
19 #include <QDebug>
20 #include <QDesktopServices>
21 #include <QFile>
22 #include <QFileDialog>
23 #include <QTemporaryFile>
24 #include <QTextStream>
25 #include <QTransform>
26 #include <QPair>
27 #include <QPainter>
28 #include <QStyle>
29 #include <QScrollBar>
30 #include <QResizeEvent>
31 #include <QMouseEvent>
32 #include <QFocusEvent>
33 #include <QKeyEvent>
34 #include <QStyleOptionGraphicsItem>
35 #include <QContextMenuEvent>
36 #include <QList>
37 #include <QPixmap>
38 #include <QScreen>
39 #include <QProcess>
40 #include <QMenu>
41 
42 
43 #include "config.h"
44 #include "globalguiconfig.h"
45 #include "listutils.h"
46 
47 
48 #define DEBUG_GRAPH 0
49 
50 // CallGraphView defaults
51 
52 #define DEFAULT_FUNCLIMIT     .05
53 #define DEFAULT_CALLLIMIT     1.
54 #define DEFAULT_MAXCALLER     2
55 #define DEFAULT_MAXCALLEE     -1
56 #define DEFAULT_SHOWSKIPPED   false
57 #define DEFAULT_EXPANDCYCLES  false
58 #define DEFAULT_CLUSTERGROUPS false
59 #define DEFAULT_DETAILLEVEL   1
60 #define DEFAULT_LAYOUT        GraphOptions::TopDown
61 #define DEFAULT_ZOOMPOS       Auto
62 
63 
64 // LessThen functors as helpers for sorting of graph edges
65 // for keyboard navigation. Sorting is done according to
66 // the angle at which a edge spline goes out or in of a function.
67 
68 // Sort angles of outgoing edges (edge seen as attached to the caller)
69 class CallerGraphEdgeLessThan
70 {
71 public:
operator ()(const GraphEdge * ge1,const GraphEdge * ge2) const72     bool operator()(const GraphEdge* ge1, const GraphEdge* ge2) const
73     {
74         const CanvasEdge* ce1 = ge1->canvasEdge();
75         const CanvasEdge* ce2 = ge2->canvasEdge();
76 
77         // sort invisible edges (ie. without matching CanvasEdge) in front
78         if (!ce1 && !ce2) {
79             // strict ordering required for std::sort
80             return (ge1 < ge2);
81         }
82         if (!ce1) return true;
83         if (!ce2) return false;
84 
85         QPolygon p1 = ce1->controlPoints();
86         QPolygon p2 = ce2->controlPoints();
87         QPoint d1 = p1.point(1) - p1.point(0);
88         QPoint d2 = p2.point(1) - p2.point(0);
89         double angle1 = atan2(double(d1.y()), double(d1.x()));
90         double angle2 = atan2(double(d2.y()), double(d2.x()));
91 
92         return (angle1 < angle2);
93     }
94 };
95 
96 // Sort angles of ingoing edges (edge seen as attached to the callee)
97 class CalleeGraphEdgeLessThan
98 {
99 public:
operator ()(const GraphEdge * ge1,const GraphEdge * ge2) const100     bool operator()(const GraphEdge* ge1, const GraphEdge* ge2) const
101     {
102         const CanvasEdge* ce1 = ge1->canvasEdge();
103         const CanvasEdge* ce2 = ge2->canvasEdge();
104 
105         // sort invisible edges (ie. without matching CanvasEdge) in front
106         if (!ce1) return true;
107         if (!ce2) return false;
108 
109         QPolygon p1 = ce1->controlPoints();
110         QPolygon p2 = ce2->controlPoints();
111         QPoint d1 = p1.point(p1.count()-2) - p1.point(p1.count()-1);
112         QPoint d2 = p2.point(p2.count()-2) - p2.point(p2.count()-1);
113         double angle1 = atan2(double(d1.y()), double(d1.x()));
114         double angle2 = atan2(double(d2.y()), double(d2.x()));
115 
116         // for ingoing edges sort according to descending angles
117         return (angle2 < angle1);
118     }
119 };
120 
121 
122 
123 //
124 // GraphNode
125 //
126 
GraphNode()127 GraphNode::GraphNode()
128 {
129     _f=nullptr;
130     self = incl = 0;
131     _cn = nullptr;
132 
133     _visible = false;
134     _lastCallerIndex = _lastCalleeIndex = -1;
135 
136     _lastFromCaller = true;
137 }
138 
clearEdges()139 void GraphNode::clearEdges()
140 {
141     callees.clear();
142     callers.clear();
143 }
144 
145 CallerGraphEdgeLessThan callerGraphEdgeLessThan;
146 CalleeGraphEdgeLessThan calleeGraphEdgeLessThan;
147 
sortEdges()148 void GraphNode::sortEdges()
149 {
150     std::sort(callers.begin(), callers.end(), callerGraphEdgeLessThan);
151     std::sort(callees.begin(), callees.end(), calleeGraphEdgeLessThan);
152 }
153 
addCallee(GraphEdge * e)154 void GraphNode::addCallee(GraphEdge* e)
155 {
156     if (e)
157         callees.append(e);
158 }
159 
addCaller(GraphEdge * e)160 void GraphNode::addCaller(GraphEdge* e)
161 {
162     if (e)
163         callers.append(e);
164 }
165 
addUniqueCallee(GraphEdge * e)166 void GraphNode::addUniqueCallee(GraphEdge* e)
167 {
168     if (e && (callees.count(e) == 0))
169         callees.append(e);
170 }
171 
addUniqueCaller(GraphEdge * e)172 void GraphNode::addUniqueCaller(GraphEdge* e)
173 {
174     if (e && (callers.count(e) == 0))
175         callers.append(e);
176 }
177 
removeEdge(GraphEdge * e)178 void GraphNode::removeEdge(GraphEdge* e)
179 {
180     callers.removeAll(e);
181     callees.removeAll(e);
182 }
183 
calleeCostSum()184 double GraphNode::calleeCostSum()
185 {
186     double sum = 0.0;
187 
188     foreach(GraphEdge* e, callees)
189         sum += e->cost;
190 
191     return sum;
192 }
193 
calleeCountSum()194 double GraphNode::calleeCountSum()
195 {
196     double sum = 0.0;
197 
198     foreach(GraphEdge* e, callees)
199         sum += e->count;
200 
201     return sum;
202 }
203 
callerCostSum()204 double GraphNode::callerCostSum()
205 {
206     double sum = 0.0;
207 
208     foreach(GraphEdge* e, callers)
209         sum += e->cost;
210 
211     return sum;
212 }
213 
callerCountSum()214 double GraphNode::callerCountSum()
215 {
216     double sum = 0.0;
217 
218     foreach(GraphEdge* e, callers)
219         sum += e->count;
220 
221     return sum;
222 }
223 
224 
visibleCaller()225 TraceCall* GraphNode::visibleCaller()
226 {
227     if (0)
228         qDebug("GraphNode::visibleCaller %s: last %d, count %d",
229                qPrintable(_f->prettyName()), _lastCallerIndex, (int) callers.count());
230 
231     // can not use at(): index can be -1 (out of bounds), result is 0 then
232     GraphEdge* e = callers.value(_lastCallerIndex);
233     if (e && !e->isVisible())
234         e = nullptr;
235     if (!e) {
236         double maxCost = 0.0;
237         GraphEdge* maxEdge = nullptr;
238         for(int i = 0; i<callers.size(); i++) {
239             e = callers[i];
240             if (e->isVisible() && (e->cost > maxCost)) {
241                 maxCost = e->cost;
242                 maxEdge = e;
243                 _lastCallerIndex = i;
244             }
245         }
246         e = maxEdge;
247     }
248     return e ? e->call() : nullptr;
249 }
250 
visibleCallee()251 TraceCall* GraphNode::visibleCallee()
252 {
253     if (0)
254         qDebug("GraphNode::visibleCallee %s: last %d, count %d",
255                qPrintable(_f->prettyName()), _lastCalleeIndex, (int) callees.count());
256 
257     GraphEdge* e = callees.value(_lastCalleeIndex);
258     if (e && !e->isVisible())
259         e = nullptr;
260 
261     if (!e) {
262         double maxCost = 0.0;
263         GraphEdge* maxEdge = nullptr;
264         for(int i = 0; i<callees.size(); i++) {
265             e = callees[i];
266             if (e->isVisible() && (e->cost > maxCost)) {
267                 maxCost = e->cost;
268                 maxEdge = e;
269                 _lastCalleeIndex = i;
270             }
271         }
272         e = maxEdge;
273     }
274     return e ? e->call() : nullptr;
275 }
276 
setCallee(GraphEdge * e)277 void GraphNode::setCallee(GraphEdge* e)
278 {
279     _lastCalleeIndex = callees.indexOf(e);
280     _lastFromCaller = false;
281 }
282 
setCaller(GraphEdge * e)283 void GraphNode::setCaller(GraphEdge* e)
284 {
285     _lastCallerIndex = callers.indexOf(e);
286     _lastFromCaller = true;
287 }
288 
nextVisible()289 TraceFunction* GraphNode::nextVisible()
290 {
291     TraceCall* c;
292 
293     if (_lastFromCaller) {
294         c = nextVisibleCaller();
295         if (c)
296             return c->called(true);
297         c = nextVisibleCallee();
298         if (c)
299             return c->caller(true);
300     } else {
301         c = nextVisibleCallee();
302         if (c)
303             return c->caller(true);
304         c = nextVisibleCaller();
305         if (c)
306             return c->called(true);
307     }
308     return nullptr;
309 }
310 
priorVisible()311 TraceFunction* GraphNode::priorVisible()
312 {
313     TraceCall* c;
314 
315     if (_lastFromCaller) {
316         c = priorVisibleCaller();
317         if (c)
318             return c->called(true);
319         c = priorVisibleCallee();
320         if (c)
321             return c->caller(true);
322     } else {
323         c = priorVisibleCallee();
324         if (c)
325             return c->caller(true);
326         c = priorVisibleCaller();
327         if (c)
328             return c->called(true);
329     }
330     return nullptr;
331 }
332 
nextVisibleCaller(GraphEdge * e)333 TraceCall* GraphNode::nextVisibleCaller(GraphEdge* e)
334 {
335     int idx = e ? callers.indexOf(e) : _lastCallerIndex;
336     idx++;
337     while(idx < callers.size()) {
338         if (callers[idx]->isVisible()) {
339             _lastCallerIndex = idx;
340             return callers[idx]->call();
341         }
342         idx++;
343     }
344     return nullptr;
345 }
346 
nextVisibleCallee(GraphEdge * e)347 TraceCall* GraphNode::nextVisibleCallee(GraphEdge* e)
348 {
349     int idx = e ? callees.indexOf(e) : _lastCalleeIndex;
350     idx++;
351     while(idx < callees.size()) {
352         if (callees[idx]->isVisible()) {
353             _lastCalleeIndex = idx;
354             return callees[idx]->call();
355         }
356         idx++;
357     }
358     return nullptr;
359 }
360 
priorVisibleCaller(GraphEdge * e)361 TraceCall* GraphNode::priorVisibleCaller(GraphEdge* e)
362 {
363     int idx = e ? callers.indexOf(e) : _lastCallerIndex;
364 
365     idx = (idx<0) ? callers.size()-1 : idx-1;
366     while(idx >= 0) {
367         if (callers[idx]->isVisible()) {
368             _lastCallerIndex = idx;
369             return callers[idx]->call();
370         }
371         idx--;
372     }
373     return nullptr;
374 }
375 
priorVisibleCallee(GraphEdge * e)376 TraceCall* GraphNode::priorVisibleCallee(GraphEdge* e)
377 {
378     int idx = e ? callees.indexOf(e) : _lastCalleeIndex;
379 
380     idx = (idx<0) ? callees.size()-1 : idx-1;
381     while(idx >= 0) {
382         if (callees[idx]->isVisible()) {
383             _lastCalleeIndex = idx;
384             return callees[idx]->call();
385         }
386         idx--;
387     }
388     return nullptr;
389 }
390 
391 
392 //
393 // GraphEdge
394 //
395 
GraphEdge()396 GraphEdge::GraphEdge()
397 {
398     _c=nullptr;
399     _from = _to = nullptr;
400     _fromNode = _toNode = nullptr;
401     cost = count = 0;
402     _ce = nullptr;
403 
404     _visible = false;
405     _lastFromCaller = true;
406 }
407 
prettyName()408 QString GraphEdge::prettyName()
409 {
410     if (_c)
411         return _c->prettyName();
412 
413     if (_from)
414         return QObject::tr("Call(s) from %1").arg(_from->prettyName());
415 
416     if (_to)
417         return QObject::tr("Call(s) to %1").arg(_to->prettyName());
418 
419     return QObject::tr("(unknown call)");
420 }
421 
visibleCaller()422 TraceFunction* GraphEdge::visibleCaller()
423 {
424     if (_from) {
425         _lastFromCaller = true;
426         if (_fromNode)
427             _fromNode->setCallee(this);
428         return _from;
429     }
430     return nullptr;
431 }
432 
visibleCallee()433 TraceFunction* GraphEdge::visibleCallee()
434 {
435     if (_to) {
436         _lastFromCaller = false;
437         if (_toNode)
438             _toNode->setCaller(this);
439         return _to;
440     }
441     return nullptr;
442 }
443 
nextVisible()444 TraceCall* GraphEdge::nextVisible()
445 {
446     TraceCall* res = nullptr;
447 
448     if (_lastFromCaller && _fromNode) {
449         res = _fromNode->nextVisibleCallee(this);
450         if (!res && _toNode)
451             res = _toNode->nextVisibleCaller(this);
452     } else if (_toNode) {
453         res = _toNode->nextVisibleCaller(this);
454         if (!res && _fromNode)
455             res = _fromNode->nextVisibleCallee(this);
456     }
457     return res;
458 }
459 
priorVisible()460 TraceCall* GraphEdge::priorVisible()
461 {
462     TraceCall* res = nullptr;
463 
464     if (_lastFromCaller && _fromNode) {
465         res = _fromNode->priorVisibleCallee(this);
466         if (!res && _toNode)
467             res = _toNode->priorVisibleCaller(this);
468     } else if (_toNode) {
469         res = _toNode->priorVisibleCaller(this);
470         if (!res && _fromNode)
471             res = _fromNode->priorVisibleCallee(this);
472     }
473     return res;
474 }
475 
476 
477 
478 //
479 // GraphOptions
480 //
481 
layoutString(Layout l)482 QString GraphOptions::layoutString(Layout l)
483 {
484     if (l == Circular)
485         return QStringLiteral("Circular");
486     if (l == LeftRight)
487         return QStringLiteral("LeftRight");
488     return QStringLiteral("TopDown");
489 }
490 
layout(QString s)491 GraphOptions::Layout GraphOptions::layout(QString s)
492 {
493     if (s == QLatin1String("Circular"))
494         return Circular;
495     if (s == QLatin1String("LeftRight"))
496         return LeftRight;
497     return TopDown;
498 }
499 
500 
501 //
502 // StorableGraphOptions
503 //
504 
StorableGraphOptions()505 StorableGraphOptions::StorableGraphOptions()
506 {
507     // default options
508     _funcLimit         = DEFAULT_FUNCLIMIT;
509     _callLimit         = DEFAULT_CALLLIMIT;
510     _maxCallerDepth    = DEFAULT_MAXCALLER;
511     _maxCalleeDepth    = DEFAULT_MAXCALLEE;
512     _showSkipped       = DEFAULT_SHOWSKIPPED;
513     _expandCycles      = DEFAULT_EXPANDCYCLES;
514     _detailLevel       = DEFAULT_DETAILLEVEL;
515     _layout            = DEFAULT_LAYOUT;
516 }
517 
518 
519 
520 
521 //
522 // GraphExporter
523 //
524 
GraphExporter()525 GraphExporter::GraphExporter()
526 {
527     _go = this;
528     _tmpFile = nullptr;
529     _item = nullptr;
530     reset(nullptr, nullptr, nullptr, ProfileContext::InvalidType, QString());
531 }
532 
GraphExporter(TraceData * d,TraceFunction * f,EventType * ct,ProfileContext::Type gt,QString filename)533 GraphExporter::GraphExporter(TraceData* d, TraceFunction* f,
534                              EventType* ct, ProfileContext::Type gt,
535                              QString filename)
536 {
537     _go = this;
538     _tmpFile = nullptr;
539     _item = nullptr;
540     reset(d, f, ct, gt, filename);
541 }
542 
~GraphExporter()543 GraphExporter::~GraphExporter()
544 {
545     if (_item && _tmpFile) {
546 #if DEBUG_GRAPH
547         _tmpFile->setAutoRemove(true);
548 #endif
549         delete _tmpFile;
550     }
551 }
552 
553 
reset(TraceData *,CostItem * i,EventType * ct,ProfileContext::Type gt,QString filename)554 void GraphExporter::reset(TraceData*, CostItem* i, EventType* ct,
555                           ProfileContext::Type gt, QString filename)
556 {
557     _graphCreated = false;
558     _nodeMap.clear();
559     _edgeMap.clear();
560 
561     if (_item && _tmpFile) {
562         _tmpFile->setAutoRemove(true);
563         delete _tmpFile;
564     }
565 
566     if (i) {
567         switch (i->type()) {
568         case ProfileContext::Function:
569         case ProfileContext::FunctionCycle:
570         case ProfileContext::Call:
571             break;
572         default:
573             i = nullptr;
574         }
575     }
576 
577     _item = i;
578     _eventType = ct;
579     _groupType = gt;
580     if (!i)
581         return;
582 
583     if (filename.isEmpty()) {
584         _tmpFile = new QTemporaryFile();
585         //_tmpFile->setSuffix(".dot");
586         _tmpFile->setAutoRemove(false);
587         _tmpFile->open();
588         _dotName = _tmpFile->fileName();
589         _useBox = true;
590     } else {
591         _tmpFile = nullptr;
592         _dotName = filename;
593         _useBox = false;
594     }
595 }
596 
597 
598 
setGraphOptions(GraphOptions * go)599 void GraphExporter::setGraphOptions(GraphOptions* go)
600 {
601     if (go == nullptr)
602         go = this;
603     _go = go;
604 }
605 
createGraph()606 void GraphExporter::createGraph()
607 {
608     if (!_item)
609         return;
610     if (_graphCreated)
611         return;
612     _graphCreated = true;
613 
614     if ((_item->type() == ProfileContext::Function) ||(_item->type()
615                                                        == ProfileContext::FunctionCycle)) {
616         TraceFunction* f = (TraceFunction*) _item;
617 
618         double incl = f->inclusive()->subCost(_eventType);
619         _realFuncLimit = incl * _go->funcLimit();
620         _realCallLimit = _realFuncLimit * _go->callLimit();
621 
622         buildGraph(f, 0, true, 1.0); // down to callees
623 
624         // set costs of function back to 0, as it will be added again
625         GraphNode& n = _nodeMap[f];
626         n.self = n.incl = 0.0;
627 
628         buildGraph(f, 0, false, 1.0); // up to callers
629     } else {
630         TraceCall* c = (TraceCall*) _item;
631 
632         double incl = c->subCost(_eventType);
633         _realFuncLimit = incl * _go->funcLimit();
634         _realCallLimit = _realFuncLimit * _go->callLimit();
635 
636         // create edge
637         TraceFunction *caller, *called;
638         caller = c->caller(false);
639         called = c->called(false);
640         QPair<TraceFunction*,TraceFunction*> p(caller, called);
641         GraphEdge& e = _edgeMap[p];
642         e.setCall(c);
643         e.setCaller(p.first);
644         e.setCallee(p.second);
645         e.cost = c->subCost(_eventType);
646         e.count = c->callCount();
647 
648         SubCost s = called->inclusive()->subCost(_eventType);
649         buildGraph(called, 0, true, e.cost / s); // down to callees
650         s = caller->inclusive()->subCost(_eventType);
651         buildGraph(caller, 0, false, e.cost / s); // up to callers
652     }
653 }
654 
655 
writeDot(QIODevice * device)656 bool GraphExporter::writeDot(QIODevice* device)
657 {
658     if (!_item)
659         return false;
660 
661     QFile* file = nullptr;
662     QTextStream* stream = nullptr;
663 
664     if (device)
665         stream = new QTextStream(device);
666     else {
667         if (_tmpFile)
668             stream = new QTextStream(_tmpFile);
669         else {
670             file = new QFile(_dotName);
671             if ( !file->open(QIODevice::WriteOnly ) ) {
672                 qDebug() << "Can not write dot file '"<< _dotName << "'";
673                 delete file;
674                 return false;
675             }
676             stream = new QTextStream(file);
677         }
678     }
679 
680     if (!_graphCreated)
681         createGraph();
682 
683     /* Generate dot format...
684      * When used for the CallGraphView (in contrast to "Export Callgraph..."),
685      * the labels are only dummy placeholders to reserve space for our own
686      * drawings.
687      */
688 
689     *stream << "digraph \"callgraph\" {\n";
690 
691     if (_go->layout() == LeftRight) {
692         *stream << QStringLiteral("  rankdir=LR;\n");
693     } else if (_go->layout() == Circular) {
694         TraceFunction *f = nullptr;
695         switch (_item->type()) {
696         case ProfileContext::Function:
697         case ProfileContext::FunctionCycle:
698             f = (TraceFunction*) _item;
699             break;
700         case ProfileContext::Call:
701             f = ((TraceCall*)_item)->caller(true);
702             break;
703         default:
704             break;
705         }
706         if (f)
707             *stream << QStringLiteral("  center=F%1;\n").arg((qptrdiff)f, 0, 16);
708         *stream << QStringLiteral("  overlap=false;\n  splines=true;\n");
709     }
710 
711     // for clustering
712     QMap<TraceCostItem*,QList<GraphNode*> > nLists;
713 
714     GraphNodeMap::Iterator nit;
715     for (nit = _nodeMap.begin(); nit != _nodeMap.end(); ++nit ) {
716         GraphNode& n = *nit;
717 
718         if (n.incl <= _realFuncLimit)
719             continue;
720 
721         // for clustering: get cost item group of function
722         TraceCostItem* g;
723         TraceFunction* f = n.function();
724         switch (_groupType) {
725         case ProfileContext::Object:
726             g = f->object();
727             break;
728         case ProfileContext::Class:
729             g = f->cls();
730             break;
731         case ProfileContext::File:
732             g = f->file();
733             break;
734         case ProfileContext::FunctionCycle:
735             g = f->cycle();
736             break;
737         default:
738             g = nullptr;
739             break;
740         }
741         nLists[g].append(&n);
742     }
743 
744     QMap<TraceCostItem*,QList<GraphNode*> >::Iterator lit;
745     int cluster = 0;
746     for (lit = nLists.begin(); lit != nLists.end(); ++lit, cluster++) {
747         QList<GraphNode*>& l = lit.value();
748         TraceCostItem* i = lit.key();
749 
750         if (_go->clusterGroups() && i) {
751             QString iabr = GlobalConfig::shortenSymbol(i->prettyName());
752             // escape quotation marks in symbols to avoid invalid dot syntax
753             iabr.replace("\"", "\\\"");
754             *stream << QStringLiteral("subgraph \"cluster%1\" { label=\"%2\";\n")
755                        .arg(cluster).arg(iabr);
756         }
757 
758         foreach(GraphNode* np, l) {
759             TraceFunction* f = np->function();
760 
761             QString abr = GlobalConfig::shortenSymbol(f->prettyName());
762             // escape quotation marks to avoid invalid dot syntax
763             abr.replace("\"", "\\\"");
764             *stream << QStringLiteral("  F%1 [").arg((qptrdiff)f, 0, 16);
765             if (_useBox) {
766                 // we want a minimal size for cost display
767                 if ((int)abr.length() < 8) abr = abr + QString(8 - abr.length(),'_');
768 
769                 // make label 3 lines for CallGraphView
770                 *stream << QStringLiteral("shape=box,label=\"** %1 **\\n**\\n%2\"];\n")
771                            .arg(abr)
772                            .arg(SubCost(np->incl).pretty());
773             } else
774                 *stream << QStringLiteral("label=\"%1\\n%2\"];\n")
775                            .arg(abr)
776                            .arg(SubCost(np->incl).pretty());
777         }
778 
779         if (_go->clusterGroups() && i)
780             *stream << QStringLiteral("}\n");
781     }
782 
783     GraphEdgeMap::Iterator eit;
784     for (eit = _edgeMap.begin(); eit != _edgeMap.end(); ++eit ) {
785         GraphEdge& e = *eit;
786 
787         if (e.cost < _realCallLimit)
788             continue;
789         if (!_go->expandCycles()) {
790             // do not show inner cycle calls
791             if (e.call()->inCycle()>0)
792                 continue;
793         }
794 
795         GraphNode& from = _nodeMap[e.from()];
796         GraphNode& to = _nodeMap[e.to()];
797 
798         e.setCallerNode(&from);
799         e.setCalleeNode(&to);
800 
801         if ((from.incl <= _realFuncLimit) ||(to.incl <= _realFuncLimit))
802             continue;
803 
804         // remove dumped edges from n.callers/n.callees
805         from.removeEdge(&e);
806         to.removeEdge(&e);
807 
808         *stream << QStringLiteral("  F%1 -> F%2 [weight=%3")
809                    .arg((qptrdiff)e.from(), 0, 16)
810                    .arg((qptrdiff)e.to(), 0, 16)
811                    .arg((long)log(log(e.cost)));
812 
813         if (_go->detailLevel() ==1) {
814             *stream << QStringLiteral(",label=\"%1 (%2x)\"")
815                        .arg(SubCost(e.cost).pretty())
816                        .arg(SubCost(e.count).pretty());
817         }
818         else if (_go->detailLevel() ==2)
819             *stream << QStringLiteral(",label=\"%3\\n%4 x\"")
820                        .arg(SubCost(e.cost).pretty())
821                        .arg(SubCost(e.count).pretty());
822 
823         *stream << QStringLiteral("];\n");
824     }
825 
826     if (_go->showSkipped()) {
827 
828         // Create sum-edges for skipped edges
829         GraphEdge* e;
830         double costSum, countSum;
831         for (nit = _nodeMap.begin(); nit != _nodeMap.end(); ++nit ) {
832             GraphNode& n = *nit;
833             if (n.incl <= _realFuncLimit)
834                 continue;
835 
836             // add edge for all skipped callers if cost sum is high enough
837             costSum = n.callerCostSum();
838             countSum = n.callerCountSum();
839             if (costSum > _realCallLimit) {
840 
841                 QPair<TraceFunction*,TraceFunction*> p(nullptr, n.function());
842                 e = &(_edgeMap[p]);
843                 e->setCallee(p.second);
844                 e->cost = costSum;
845                 e->count = countSum;
846 
847                 *stream << QStringLiteral("  R%1 [shape=point,label=\"\"];\n")
848                            .arg((qptrdiff)n.function(), 0, 16);
849                 *stream << QStringLiteral("  R%1 -> F%2 [label=\"%3\\n%4 x\",weight=%5];\n")
850                            .arg((qptrdiff)n.function(), 0, 16)
851                            .arg((qptrdiff)n.function(), 0, 16)
852                            .arg(SubCost(costSum).pretty())
853                            .arg(SubCost(countSum).pretty())
854                            .arg((int)log(costSum));
855             }
856 
857             // add edge for all skipped callees if cost sum is high enough
858             costSum = n.calleeCostSum();
859             countSum = n.calleeCountSum();
860             if (costSum > _realCallLimit) {
861 
862                 QPair<TraceFunction*,TraceFunction*> p(n.function(), nullptr);
863                 e = &(_edgeMap[p]);
864                 e->setCaller(p.first);
865                 e->cost = costSum;
866                 e->count = countSum;
867 
868                 *stream << QStringLiteral("  S%1 [shape=point,label=\"\"];\n")
869                            .arg((qptrdiff)n.function(), 0, 16);
870                 *stream << QStringLiteral("  F%1 -> S%2 [label=\"%3\\n%4 x\",weight=%5];\n")
871                            .arg((qptrdiff)n.function(), 0, 16)
872                            .arg((qptrdiff)n.function(), 0, 16)
873                            .arg(SubCost(costSum).pretty())
874                            .arg(SubCost(countSum).pretty())
875                            .arg((int)log(costSum));
876             }
877         }
878     }
879 
880     // clear edges here completely.
881     // Visible edges are inserted again on parsing in CallGraphView::refresh
882     for (nit = _nodeMap.begin(); nit != _nodeMap.end(); ++nit ) {
883         GraphNode& n = *nit;
884         n.clearEdges();
885     }
886 
887     *stream << "}\n";
888 
889     if (!device) {
890         if (_tmpFile) {
891             stream->flush();
892             _tmpFile->seek(0);
893         } else {
894             file->close();
895             delete file;
896         }
897     }
898     delete stream;
899     return true;
900 }
901 
savePrompt(QWidget * parent,TraceData * data,TraceFunction * function,EventType * eventType,ProfileContext::Type groupType,CallGraphView * cgv)902 bool GraphExporter::savePrompt(QWidget *parent, TraceData *data,
903                                TraceFunction *function, EventType *eventType,
904                                ProfileContext::Type groupType,
905                                CallGraphView* cgv)
906 {
907     // More formats can be added; it's a matter of adding the filter and if
908     QFileDialog saveDialog(parent, QObject::tr("Export Graph"));
909     saveDialog.setMimeTypeFilters( {"text/vnd.graphviz", "application/pdf", "application/postscript"} );
910     saveDialog.setFileMode(QFileDialog::AnyFile);
911     saveDialog.setAcceptMode(QFileDialog::AcceptSave);
912 
913     if (saveDialog.exec()) {
914         QString intendedName = saveDialog.selectedFiles().first();
915         if (intendedName.isNull() || intendedName.isEmpty())
916             return false;
917         bool wrote = false;
918         QString dotName, dotRenderType;
919         QTemporaryFile maybeTemp;
920         maybeTemp.open();
921         const QString mime = saveDialog.selectedMimeTypeFilter();
922         if (mime == "text/vnd.graphviz") {
923             dotName = intendedName;
924             dotRenderType = "";
925         }
926         else if (mime == "application/pdf") {
927             dotName = maybeTemp.fileName();
928             dotRenderType = "-Tpdf";
929         }
930         else if (mime == "application/postscript") {
931             dotName = maybeTemp.fileName();
932             dotRenderType = "-Tps";
933         }
934         GraphExporter ge(data, function, eventType, groupType, dotName);
935         if (cgv != nullptr)
936             ge.setGraphOptions(cgv);
937         wrote = ge.writeDot();
938         if (wrote && mime != "text/vnd.graphviz") {
939             QProcess proc;
940             proc.setStandardOutputFile(intendedName, QFile::Truncate);
941             proc.start("dot", { dotRenderType, dotName }, QProcess::ReadWrite);
942             proc.waitForFinished();
943             wrote = proc.exitStatus() == QProcess::NormalExit;
944 
945             // Open in the default app, except for GraphViz files. The .dot
946             // association is usually for Microsoft Word templates and will
947             // open a word processor instead, which isn't what we want.
948             if (wrote) {
949                 QDesktopServices::openUrl(QUrl::fromLocalFile(intendedName));
950             }
951         }
952 
953         return wrote;
954     }
955 
956     return false;
957 }
958 
sortEdges()959 void GraphExporter::sortEdges()
960 {
961     GraphNodeMap::Iterator nit;
962     for (nit = _nodeMap.begin(); nit != _nodeMap.end(); ++nit ) {
963         GraphNode& n = *nit;
964         n.sortEdges();
965     }
966 }
967 
toFunc(QString s)968 TraceFunction* GraphExporter::toFunc(QString s)
969 {
970     if (s[0] != 'F')
971         return nullptr;
972     bool ok;
973     TraceFunction* f = (TraceFunction*) s.mid(1).toULongLong(&ok, 16);
974     if (!ok)
975         return nullptr;
976 
977     return f;
978 }
979 
node(TraceFunction * f)980 GraphNode* GraphExporter::node(TraceFunction* f)
981 {
982     if (!f)
983         return nullptr;
984 
985     GraphNodeMap::Iterator it = _nodeMap.find(f);
986     if (it == _nodeMap.end())
987         return nullptr;
988 
989     return &(*it);
990 }
991 
edge(TraceFunction * f1,TraceFunction * f2)992 GraphEdge* GraphExporter::edge(TraceFunction* f1, TraceFunction* f2)
993 {
994     GraphEdgeMap::Iterator it = _edgeMap.find(qMakePair(f1, f2));
995     if (it == _edgeMap.end())
996         return nullptr;
997 
998     return &(*it);
999 }
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  */
buildGraph(TraceFunction * f,int depth,bool toCallees,double factor)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 //
PanningView(QWidget * parent)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 
setZoomRect(const QRectF & r)1161 void PanningView::setZoomRect(const QRectF& r)
1162 {
1163     _zoomRect = r;
1164     viewport()->update();
1165 }
1166 
drawForeground(QPainter * p,const QRectF &)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 
mousePressEvent(QMouseEvent * e)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 
mouseMoveEvent(QMouseEvent * e)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 
mouseReleaseEvent(QMouseEvent *)1209 void PanningView::mouseReleaseEvent(QMouseEvent*)
1210 {
1211     _movingZoomRect = false;
1212     emit zoomRectMoveFinished();
1213 }
1214 
1215 
1216 
1217 
1218 
1219 //
1220 // CanvasNode
1221 //
1222 
CanvasNode(CallGraphView * v,GraphNode * n,int x,int y,int w,int h)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 
setSelected(bool s)1261 void CanvasNode::setSelected(bool s)
1262 {
1263     StoredDrawParams::setSelected(s);
1264     update();
1265 }
1266 
updateGroup()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 
paint(QPainter * p,const QStyleOptionGraphicsItem * option,QWidget *)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 
CanvasEdgeLabel(CallGraphView * v,CanvasEdge * ce,int x,int y,int w,int h)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 
paint(QPainter * p,const QStyleOptionGraphicsItem * option,QWidget *)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 
CanvasEdgeArrow(CanvasEdge * ce)1392 CanvasEdgeArrow::CanvasEdgeArrow(CanvasEdge* ce)
1393     : _ce(ce)
1394 {}
1395 
paint(QPainter * p,const QStyleOptionGraphicsItem *,QWidget *)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 
CanvasEdge(GraphEdge * e)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 
setLabel(CanvasEdgeLabel * l)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 
setArrow(CanvasEdgeArrow * a)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 
setSelected(bool s)1442 void CanvasEdge::setSelected(bool s)
1443 {
1444     QGraphicsItem::setSelected(s);
1445     update();
1446 }
1447 
setControlPoints(const QPolygon & pa)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 
paint(QPainter * p,const QStyleOptionGraphicsItem * option,QWidget *)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 
CanvasFrame(CanvasNode * n)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 
paint(QPainter * p,const QStyleOptionGraphicsItem * option,QWidget *)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 //
CallGraphView(TraceItemView * parentView,QWidget * parent,const QString & name)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 
~CallGraphView()1600 CallGraphView::~CallGraphView()
1601 {
1602     clear();
1603     delete _panningView;
1604 }
1605 
whatsThis() const1606 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 
updateSizes(QSize s)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 
focusInEvent(QFocusEvent *)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 
focusOutEvent(QFocusEvent * e)1768 void CallGraphView::focusOutEvent(QFocusEvent* e)
1769 {
1770     // trigger updates as in focusInEvent
1771     focusInEvent(e);
1772 }
1773 
keyPressEvent(QKeyEvent * e)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 
resizeEvent(QResizeEvent * e)1874 void CallGraphView::resizeEvent(QResizeEvent* e)
1875 {
1876     QGraphicsView::resizeEvent(e);
1877     if (_scene)
1878         updateSizes(e->size());
1879 }
1880 
canShow(CostItem * i)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 
doUpdate(int changeType,bool)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 
clear()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 
showText(QString s)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 
showRenderWarning()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 
showRenderError(QString s)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 
stopRendering()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 
refresh()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 
readDotOutput()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 
dotError()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 
dotExited()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
scrollContentsBy(int dx,int dy)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 
zoomRectMoved(qreal dx,qreal dy)2643 void CallGraphView::zoomRectMoved(qreal dx, qreal dy)
2644 {
2645     //FIXME if (leftMargin()>0) dx = 0;
2646     //FIXME if (topMargin()>0) dy = 0;
2647 
2648     QScrollBar *hBar = horizontalScrollBar();
2649     QScrollBar *vBar = verticalScrollBar();
2650     hBar->setValue(hBar->value() + int(dx));
2651     vBar->setValue(vBar->value() + int(dy));
2652 }
2653 
zoomRectMoveFinished()2654 void CallGraphView::zoomRectMoveFinished()
2655 {
2656     if (_zoomPosition == Auto)
2657         updateSizes();
2658 }
2659 
mousePressEvent(QMouseEvent * e)2660 void CallGraphView::mousePressEvent(QMouseEvent* e)
2661 {
2662     // clicking on the viewport sets focus
2663     setFocus();
2664 
2665     // activate scroll mode on left click
2666     if (e->button() == Qt::LeftButton)	_isMoving = true;
2667 
2668     QGraphicsItem* i = itemAt(e->pos());
2669     if (i) {
2670 
2671         if (i->type() == CANVAS_NODE) {
2672             GraphNode* n = ((CanvasNode*)i)->node();
2673             if (0)
2674                 qDebug("CallGraphView: Got Node '%s'",
2675                        qPrintable(n->function()->prettyName()));
2676 
2677             selected(n->function());
2678         }
2679 
2680         // redirect from label / arrow to edge
2681         if (i->type() == CANVAS_EDGELABEL)
2682             i = ((CanvasEdgeLabel*)i)->canvasEdge();
2683         if (i->type() == CANVAS_EDGEARROW)
2684             i = ((CanvasEdgeArrow*)i)->canvasEdge();
2685 
2686         if (i->type() == CANVAS_EDGE) {
2687             GraphEdge* e = ((CanvasEdge*)i)->edge();
2688             if (0)
2689                 qDebug("CallGraphView: Got Edge '%s'",
2690                        qPrintable(e->prettyName()));
2691 
2692             if (e->call())
2693                 selected(e->call());
2694         }
2695     }
2696     _lastPos = e->pos();
2697 }
2698 
mouseMoveEvent(QMouseEvent * e)2699 void CallGraphView::mouseMoveEvent(QMouseEvent* e)
2700 {
2701     if (_isMoving) {
2702         QPoint delta = e->pos() - _lastPos;
2703 
2704         QScrollBar *hBar = horizontalScrollBar();
2705         QScrollBar *vBar = verticalScrollBar();
2706         hBar->setValue(hBar->value() - delta.x());
2707         vBar->setValue(vBar->value() - delta.y());
2708 
2709         _lastPos = e->pos();
2710     }
2711 }
2712 
mouseReleaseEvent(QMouseEvent *)2713 void CallGraphView::mouseReleaseEvent(QMouseEvent*)
2714 {
2715     _isMoving = false;
2716     if (_zoomPosition == Auto)
2717         updateSizes();
2718 }
2719 
mouseDoubleClickEvent(QMouseEvent * e)2720 void CallGraphView::mouseDoubleClickEvent(QMouseEvent* e)
2721 {
2722     QGraphicsItem* i = itemAt(e->pos());
2723     if (i == nullptr)
2724         return;
2725 
2726     if (i->type() == CANVAS_NODE) {
2727         GraphNode* n = ((CanvasNode*)i)->node();
2728         if (0)
2729             qDebug("CallGraphView: Double Clicked on Node '%s'",
2730                    qPrintable(n->function()->prettyName()));
2731 
2732         activated(n->function());
2733     }
2734 
2735     // redirect from label / arrow to edge
2736     if (i->type() == CANVAS_EDGELABEL)
2737         i = ((CanvasEdgeLabel*)i)->canvasEdge();
2738     if (i->type() == CANVAS_EDGEARROW)
2739         i = ((CanvasEdgeArrow*)i)->canvasEdge();
2740 
2741     if (i->type() == CANVAS_EDGE) {
2742         GraphEdge* e = ((CanvasEdge*)i)->edge();
2743         if (e->call()) {
2744             if (0)
2745                 qDebug("CallGraphView: Double Clicked On Edge '%s'",
2746                        qPrintable(e->call()->prettyName()));
2747 
2748             activated(e->call());
2749         }
2750     }
2751 }
2752 
2753 // helper functions for context menu:
2754 // submenu builders and trigger handlers
2755 
addCallerDepthAction(QMenu * m,QString s,int d)2756 QAction* CallGraphView::addCallerDepthAction(QMenu* m, QString s, int d)
2757 {
2758     QAction* a;
2759 
2760     a = m->addAction(s);
2761     a->setData(d);
2762     a->setCheckable(true);
2763     a->setChecked(_maxCallerDepth == d);
2764 
2765     return a;
2766 }
2767 
addCallerDepthMenu(QMenu * menu)2768 QMenu* CallGraphView::addCallerDepthMenu(QMenu* menu)
2769 {
2770     QAction* a;
2771     QMenu* m;
2772 
2773     m = menu->addMenu(tr("Caller Depth"));
2774     a = addCallerDepthAction(m, tr("Unlimited"), -1);
2775     a->setEnabled(_funcLimit>0.005);
2776     m->addSeparator();
2777     addCallerDepthAction(m, tr("Depth 0", "None"), 0);
2778     addCallerDepthAction(m, tr("max. 1"), 1);
2779     addCallerDepthAction(m, tr("max. 2"), 2);
2780     addCallerDepthAction(m, tr("max. 5"), 5);
2781     addCallerDepthAction(m, tr("max. 10"), 10);
2782     addCallerDepthAction(m, tr("max. 15"), 15);
2783 
2784     connect(m, &QMenu::triggered,
2785             this, &CallGraphView::callerDepthTriggered );
2786 
2787     return m;
2788 }
2789 
callerDepthTriggered(QAction * a)2790 void CallGraphView::callerDepthTriggered(QAction* a)
2791 {
2792     _maxCallerDepth = a->data().toInt(nullptr);
2793     refresh();
2794 }
2795 
addCalleeDepthAction(QMenu * m,QString s,int d)2796 QAction* CallGraphView::addCalleeDepthAction(QMenu* m, QString s, int d)
2797 {
2798     QAction* a;
2799 
2800     a = m->addAction(s);
2801     a->setData(d);
2802     a->setCheckable(true);
2803     a->setChecked(_maxCalleeDepth == d);
2804 
2805     return a;
2806 }
2807 
addCalleeDepthMenu(QMenu * menu)2808 QMenu* CallGraphView::addCalleeDepthMenu(QMenu* menu)
2809 {
2810     QAction* a;
2811     QMenu* m;
2812 
2813     m = menu->addMenu(tr("Callee Depth"));
2814     a = addCalleeDepthAction(m, tr("Unlimited"), -1);
2815     a->setEnabled(_funcLimit>0.005);
2816     m->addSeparator();
2817     addCalleeDepthAction(m, tr("Depth 0", "None"), 0);
2818     addCalleeDepthAction(m, tr("max. 1"), 1);
2819     addCalleeDepthAction(m, tr("max. 2"), 2);
2820     addCalleeDepthAction(m, tr("max. 5"), 5);
2821     addCalleeDepthAction(m, tr("max. 10"), 10);
2822     addCalleeDepthAction(m, tr("max. 15"), 15);
2823 
2824     connect(m, &QMenu::triggered,
2825             this, &CallGraphView::calleeDepthTriggered );
2826 
2827     return m;
2828 }
2829 
calleeDepthTriggered(QAction * a)2830 void CallGraphView::calleeDepthTriggered(QAction* a)
2831 {
2832     _maxCalleeDepth = a->data().toInt(nullptr);
2833     refresh();
2834 }
2835 
addNodeLimitAction(QMenu * m,QString s,double l)2836 QAction* CallGraphView::addNodeLimitAction(QMenu* m, QString s, double l)
2837 {
2838     QAction* a;
2839 
2840     a = m->addAction(s);
2841     a->setData(l);
2842     a->setCheckable(true);
2843     a->setChecked(_funcLimit == l);
2844 
2845     return a;
2846 }
2847 
addNodeLimitMenu(QMenu * menu)2848 QMenu* CallGraphView::addNodeLimitMenu(QMenu* menu)
2849 {
2850     QAction* a;
2851     QMenu* m;
2852 
2853     m = menu->addMenu(tr("Min. Node Cost"));
2854     a = addNodeLimitAction(m, tr("No Minimum"), 0.0);
2855     // Unlimited node cost easily produces huge graphs such that 'dot'
2856     // would need a long time to layout. For responsiveness, we only allow
2857     // for unlimited node cost if a caller and callee depth limit is set.
2858     a->setEnabled((_maxCallerDepth>=0) && (_maxCalleeDepth>=0));
2859     m->addSeparator();
2860     addNodeLimitAction(m, tr("50 %"), .5);
2861     addNodeLimitAction(m, tr("20 %"), .2);
2862     addNodeLimitAction(m, tr("10 %"), .1);
2863     addNodeLimitAction(m, tr("5 %"), .05);
2864     addNodeLimitAction(m, tr("2 %"), .02);
2865     addNodeLimitAction(m, tr("1 %"), .01);
2866 
2867     connect(m, &QMenu::triggered,
2868             this, &CallGraphView::nodeLimitTriggered );
2869 
2870     return m;
2871 }
2872 
nodeLimitTriggered(QAction * a)2873 void CallGraphView::nodeLimitTriggered(QAction* a)
2874 {
2875     _funcLimit = a->data().toDouble(nullptr);
2876     refresh();
2877 }
2878 
addCallLimitAction(QMenu * m,QString s,double l)2879 QAction* CallGraphView::addCallLimitAction(QMenu* m, QString s, double l)
2880 {
2881     QAction* a;
2882 
2883     a = m->addAction(s);
2884     a->setData(l);
2885     a->setCheckable(true);
2886     a->setChecked(_callLimit == l);
2887 
2888     return a;
2889 }
2890 
addCallLimitMenu(QMenu * menu)2891 QMenu* CallGraphView::addCallLimitMenu(QMenu* menu)
2892 {
2893     QMenu* m;
2894 
2895     m = menu->addMenu(tr("Min. Call Cost"));
2896     addCallLimitAction(m, tr("Same as Node"), 1.0);
2897     // xgettext: no-c-format
2898     addCallLimitAction(m, tr("50 % of Node"), .5);
2899     // xgettext: no-c-format
2900     addCallLimitAction(m, tr("20 % of Node"), .2);
2901     // xgettext: no-c-format
2902     addCallLimitAction(m, tr("10 % of Node"), .1);
2903 
2904     connect(m, &QMenu::triggered,
2905             this, &CallGraphView::callLimitTriggered );
2906 
2907     return m;
2908 }
2909 
callLimitTriggered(QAction * a)2910 void CallGraphView::callLimitTriggered(QAction* a)
2911 {
2912     _callLimit = a->data().toDouble(nullptr);
2913     refresh();
2914 }
2915 
addZoomPosAction(QMenu * m,QString s,CallGraphView::ZoomPosition p)2916 QAction* CallGraphView::addZoomPosAction(QMenu* m, QString s,
2917                                          CallGraphView::ZoomPosition p)
2918 {
2919     QAction* a;
2920 
2921     a = m->addAction(s);
2922     a->setData((int)p);
2923     a->setCheckable(true);
2924     a->setChecked(_zoomPosition == p);
2925 
2926     return a;
2927 }
2928 
addZoomPosMenu(QMenu * menu)2929 QMenu* CallGraphView::addZoomPosMenu(QMenu* menu)
2930 {
2931     QMenu* m = menu->addMenu(tr("Birds-eye View"));
2932     addZoomPosAction(m, tr("Top Left"), TopLeft);
2933     addZoomPosAction(m, tr("Top Right"), TopRight);
2934     addZoomPosAction(m, tr("Bottom Left"), BottomLeft);
2935     addZoomPosAction(m, tr("Bottom Right"), BottomRight);
2936     addZoomPosAction(m, tr("Automatic"), Auto);
2937     addZoomPosAction(m, tr("Hide"), Hide);
2938 
2939     connect(m, &QMenu::triggered,
2940             this, &CallGraphView::zoomPosTriggered );
2941 
2942     return m;
2943 }
2944 
2945 
zoomPosTriggered(QAction * a)2946 void CallGraphView::zoomPosTriggered(QAction* a)
2947 {
2948     _zoomPosition = (ZoomPosition) a->data().toInt(nullptr);
2949     updateSizes();
2950 }
2951 
addLayoutAction(QMenu * m,QString s,GraphOptions::Layout l)2952 QAction* CallGraphView::addLayoutAction(QMenu* m, QString s,
2953                                         GraphOptions::Layout l)
2954 {
2955     QAction* a;
2956 
2957     a = m->addAction(s);
2958     a->setData((int)l);
2959     a->setCheckable(true);
2960     a->setChecked(_layout == l);
2961 
2962     return a;
2963 }
2964 
addLayoutMenu(QMenu * menu)2965 QMenu* CallGraphView::addLayoutMenu(QMenu* menu)
2966 {
2967     QMenu* m = menu->addMenu(tr("Layout"));
2968     addLayoutAction(m, tr("Top to Down"), TopDown);
2969     addLayoutAction(m, tr("Left to Right"), LeftRight);
2970     addLayoutAction(m, tr("Circular"), Circular);
2971 
2972     connect(m, &QMenu::triggered,
2973             this, &CallGraphView::layoutTriggered );
2974 
2975     return m;
2976 }
2977 
2978 
layoutTriggered(QAction * a)2979 void CallGraphView::layoutTriggered(QAction* a)
2980 {
2981     _layout = (Layout) a->data().toInt(nullptr);
2982     refresh();
2983 }
2984 
2985 
contextMenuEvent(QContextMenuEvent * e)2986 void CallGraphView::contextMenuEvent(QContextMenuEvent* e)
2987 {
2988     _isMoving = false;
2989 
2990     QGraphicsItem* i = itemAt(e->pos());
2991 
2992     QMenu popup;
2993     TraceFunction *f = nullptr, *cycle = nullptr;
2994     TraceCall* c = nullptr;
2995 
2996     QAction* activateFunction = nullptr;
2997     QAction* activateCycle = nullptr;
2998     QAction* activateCall = nullptr;
2999     if (i) {
3000         if (i->type() == CANVAS_NODE) {
3001             GraphNode* n = ((CanvasNode*)i)->node();
3002             if (0)
3003                 qDebug("CallGraphView: Menu on Node '%s'",
3004                        qPrintable(n->function()->prettyName()));
3005 
3006             f = n->function();
3007             cycle = f->cycle();
3008 
3009             QString name = f->prettyName();
3010             QString menuStr = tr("Go to '%1'")
3011                               .arg(GlobalConfig::shortenSymbol(name));
3012             activateFunction = popup.addAction(menuStr);
3013             if (cycle && (cycle != f)) {
3014                 name = GlobalConfig::shortenSymbol(cycle->prettyName());
3015                 activateCycle = popup.addAction(tr("Go to '%1'").arg(name));
3016             }
3017             popup.addSeparator();
3018         }
3019 
3020         // redirect from label / arrow to edge
3021         if (i->type() == CANVAS_EDGELABEL)
3022             i = ((CanvasEdgeLabel*)i)->canvasEdge();
3023         if (i->type() == CANVAS_EDGEARROW)
3024             i = ((CanvasEdgeArrow*)i)->canvasEdge();
3025 
3026         if (i->type() == CANVAS_EDGE) {
3027             GraphEdge* e = ((CanvasEdge*)i)->edge();
3028             if (0)
3029                 qDebug("CallGraphView: Menu on Edge '%s'",
3030                        qPrintable(e->prettyName()));
3031 
3032             c = e->call();
3033             if (c) {
3034                 QString name = c->prettyName();
3035                 QString menuStr = tr("Go to '%1'")
3036                                   .arg(GlobalConfig::shortenSymbol(name));
3037                 activateCall = popup.addAction(menuStr);
3038 
3039                 popup.addSeparator();
3040             }
3041         }
3042     }
3043 
3044     QAction* stopLayout = nullptr;
3045     if (_renderProcess) {
3046         stopLayout = popup.addAction(tr("Stop Layouting"));
3047         popup.addSeparator();
3048     }
3049 
3050     addGoMenu(&popup);
3051     popup.addSeparator();
3052 
3053     QMenu* epopup = popup.addMenu(tr("Export Graph"));
3054     QAction* exportAsDot = epopup->addAction(tr("As DOT file..."));
3055     QAction* exportAsImage = epopup->addAction(tr("As Image..."));
3056     popup.addSeparator();
3057 
3058     QMenu* gpopup = popup.addMenu(tr("Graph"));
3059     addCallerDepthMenu(gpopup);
3060     addCalleeDepthMenu(gpopup);
3061     addNodeLimitMenu(gpopup);
3062     addCallLimitMenu(gpopup);
3063     gpopup->addSeparator();
3064 
3065     QAction* toggleSkipped;
3066     toggleSkipped = gpopup->addAction(tr("Arrows for Skipped Calls"));
3067     toggleSkipped->setCheckable(true);
3068     toggleSkipped->setChecked(_showSkipped);
3069 
3070     QAction* toggleExpand;
3071     toggleExpand = gpopup->addAction(tr("Inner-cycle Calls"));
3072     toggleExpand->setCheckable(true);
3073     toggleExpand->setChecked(_expandCycles);
3074 
3075     QAction* toggleCluster;
3076     toggleCluster = gpopup->addAction(tr("Cluster Groups"));
3077     toggleCluster->setCheckable(true);
3078     toggleCluster->setChecked(_clusterGroups);
3079 
3080     QMenu* vpopup = popup.addMenu(tr("Visualization"));
3081     QAction* layoutCompact = vpopup->addAction(tr("Compact"));
3082     layoutCompact->setCheckable(true);
3083     layoutCompact->setChecked(_detailLevel == 0);
3084     QAction* layoutNormal = vpopup->addAction(tr("Normal"));
3085     layoutNormal->setCheckable(true);
3086     layoutNormal->setChecked(_detailLevel == 1);
3087     QAction* layoutTall = vpopup->addAction(tr("Tall"));
3088     layoutTall->setCheckable(true);
3089     layoutTall->setChecked(_detailLevel == 2);
3090 
3091     addLayoutMenu(&popup);
3092     addZoomPosMenu(&popup);
3093 
3094     QAction* a = popup.exec(e->globalPos());
3095 
3096     if (a == activateFunction)
3097         activated(f);
3098     else if (a == activateCycle)
3099         activated(cycle);
3100     else if (a == activateCall)
3101         activated(c);
3102 
3103     else if (a == stopLayout)
3104         stopRendering();
3105 
3106     else if (a == exportAsDot) {
3107         TraceFunction* f = activeFunction();
3108         if (!f) return;
3109 
3110         GraphExporter::savePrompt(this, TraceItemView::data(), f, eventType(),
3111                                   groupType(), this);
3112     }
3113     else if (a == exportAsImage) {
3114         // write current content of canvas as image to file
3115         if (!_scene) return;
3116 
3117         QString n;
3118         n = QFileDialog::getSaveFileName(this,
3119                                          tr("Export Graph As Image"),
3120                                          QString(),
3121                                          tr("Images (*.png *.jpg)"));
3122 
3123         if (!n.isEmpty()) {
3124             QRect r = _scene->sceneRect().toRect();
3125             QPixmap pix(r.width(), r.height());
3126             QPainter p(&pix);
3127             _scene->render( &p );
3128             pix.save(n);
3129         }
3130     }
3131 
3132     else if (a == toggleSkipped) {
3133         _showSkipped = !_showSkipped;
3134         refresh();
3135     }
3136     else if (a == toggleExpand) {
3137         _expandCycles = !_expandCycles;
3138         refresh();
3139     }
3140     else if (a == toggleCluster) {
3141         _clusterGroups = !_clusterGroups;
3142         refresh();
3143     }
3144 
3145     else if (a == layoutCompact) {
3146         _detailLevel = 0;
3147         refresh();
3148     }
3149     else if (a == layoutNormal) {
3150         _detailLevel = 1;
3151         refresh();
3152     }
3153     else if (a == layoutTall) {
3154         _detailLevel = 2;
3155         refresh();
3156     }
3157 }
3158 
3159 
zoomPos(QString s)3160 CallGraphView::ZoomPosition CallGraphView::zoomPos(QString s)
3161 {
3162     if (s == QLatin1String("TopLeft"))
3163         return TopLeft;
3164     if (s == QLatin1String("TopRight"))
3165         return TopRight;
3166     if (s == QLatin1String("BottomLeft"))
3167         return BottomLeft;
3168     if (s == QLatin1String("BottomRight"))
3169         return BottomRight;
3170     if (s == QLatin1String("Automatic"))
3171         return Auto;
3172     if (s == QLatin1String("Hide"))
3173         return Hide;
3174 
3175     return DEFAULT_ZOOMPOS;
3176 }
3177 
zoomPosString(ZoomPosition p)3178 QString CallGraphView::zoomPosString(ZoomPosition p)
3179 {
3180     if (p == TopLeft)
3181         return QStringLiteral("TopLeft");
3182     if (p == TopRight)
3183         return QStringLiteral("TopRight");
3184     if (p == BottomLeft)
3185         return QStringLiteral("BottomLeft");
3186     if (p == BottomRight)
3187         return QStringLiteral("BottomRight");
3188     if (p == Auto)
3189         return QStringLiteral("Automatic");
3190     if (p == Hide)
3191         return QStringLiteral("Hide");
3192 
3193     return QString();
3194 }
3195 
restoreOptions(const QString & prefix,const QString & postfix)3196 void CallGraphView::restoreOptions(const QString& prefix, const QString& postfix)
3197 {
3198     ConfigGroup* g = ConfigStorage::group(prefix, postfix);
3199 
3200     _maxCallerDepth = g->value(QStringLiteral("MaxCaller"), DEFAULT_MAXCALLER).toInt();
3201     _maxCalleeDepth = g->value(QStringLiteral("MaxCallee"), DEFAULT_MAXCALLEE).toInt();
3202     _funcLimit = g->value(QStringLiteral("FuncLimit"), DEFAULT_FUNCLIMIT).toDouble();
3203     _callLimit = g->value(QStringLiteral("CallLimit"), DEFAULT_CALLLIMIT).toDouble();
3204     _showSkipped = g->value(QStringLiteral("ShowSkipped"), DEFAULT_SHOWSKIPPED).toBool();
3205     _expandCycles = g->value(QStringLiteral("ExpandCycles"), DEFAULT_EXPANDCYCLES).toBool();
3206     _clusterGroups = g->value(QStringLiteral("ClusterGroups"), DEFAULT_CLUSTERGROUPS).toBool();
3207     _detailLevel = g->value(QStringLiteral("DetailLevel"), DEFAULT_DETAILLEVEL).toInt();
3208     _layout = GraphOptions::layout(g->value(QStringLiteral("Layout"),
3209                                             layoutString(DEFAULT_LAYOUT)).toString());
3210     _zoomPosition = zoomPos(g->value(QStringLiteral("ZoomPosition"),
3211                                      zoomPosString(DEFAULT_ZOOMPOS)).toString());
3212 
3213     delete g;
3214 }
3215 
saveOptions(const QString & prefix,const QString & postfix)3216 void CallGraphView::saveOptions(const QString& prefix, const QString& postfix)
3217 {
3218     ConfigGroup* g = ConfigStorage::group(prefix + postfix);
3219 
3220     g->setValue(QStringLiteral("MaxCaller"), _maxCallerDepth, DEFAULT_MAXCALLER);
3221     g->setValue(QStringLiteral("MaxCallee"), _maxCalleeDepth, DEFAULT_MAXCALLEE);
3222     g->setValue(QStringLiteral("FuncLimit"), _funcLimit, DEFAULT_FUNCLIMIT);
3223     g->setValue(QStringLiteral("CallLimit"), _callLimit, DEFAULT_CALLLIMIT);
3224     g->setValue(QStringLiteral("ShowSkipped"), _showSkipped, DEFAULT_SHOWSKIPPED);
3225     g->setValue(QStringLiteral("ExpandCycles"), _expandCycles, DEFAULT_EXPANDCYCLES);
3226     g->setValue(QStringLiteral("ClusterGroups"), _clusterGroups, DEFAULT_CLUSTERGROUPS);
3227     g->setValue(QStringLiteral("DetailLevel"), _detailLevel, DEFAULT_DETAILLEVEL);
3228     g->setValue(QStringLiteral("Layout"), layoutString(_layout), layoutString(DEFAULT_LAYOUT));
3229     g->setValue(QStringLiteral("ZoomPosition"), zoomPosString(_zoomPosition),
3230                 zoomPosString(DEFAULT_ZOOMPOS));
3231 
3232     delete g;
3233 }
3234 
3235 
3236