File indexing completed on 2024-04-28 05:41:30

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