diff options
Diffstat (limited to 'tdecachegrind/tdecachegrind/callgraphview.cpp')
-rw-r--r-- | tdecachegrind/tdecachegrind/callgraphview.cpp | 2734 |
1 files changed, 2734 insertions, 0 deletions
diff --git a/tdecachegrind/tdecachegrind/callgraphview.cpp b/tdecachegrind/tdecachegrind/callgraphview.cpp new file mode 100644 index 00000000..bc01da8b --- /dev/null +++ b/tdecachegrind/tdecachegrind/callgraphview.cpp @@ -0,0 +1,2734 @@ +/* This file is part of KCachegrind. + Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de> + + KCachegrind is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, version 2. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +/* + * Callgraph View + */ + +#include <stdlib.h> +#include <math.h> + +#include <tqtooltip.h> +#include <tqfile.h> +#include <tqtextstream.h> +#include <tqwhatsthis.h> +#include <tqcanvas.h> +#include <tqwmatrix.h> +#include <tqpair.h> +#include <tqpainter.h> +#include <tqpopupmenu.h> +#include <tqstyle.h> +#include <tqprocess.h> + +#include <kdebug.h> +#include <klocale.h> +#include <kconfig.h> +#include <ktempfile.h> +#include <kapplication.h> +#include <kiconloader.h> +#include <kfiledialog.h> + +#include "configuration.h" +#include "callgraphview.h" +#include "toplevel.h" +#include "listutils.h" + + +/* + * TODO: + * - Zooming option for work canvas? (e.g. 1:1 - 1:3) + */ + +#define DEBUG_GRAPH 0 + +// CallGraphView defaults + +#define DEFAULT_FUNCLIMIT .05 +#define DEFAULT_CALLLIMIT .05 +#define DEFAULT_MAXCALLER 2 +#define DEFAULT_MAXCALLING -1 +#define DEFAULT_SHOWSKIPPED false +#define DEFAULT_EXPANDCYCLES false +#define DEFAULT_CLUSTERGROUPS false +#define DEFAULT_DETAILLEVEL 1 +#define DEFAULT_LAYOUT GraphOptions::TopDown +#define DEFAULT_ZOOMPOS Auto + + +// +// GraphEdgeList +// + +GraphEdgeList::GraphEdgeList() + : _sortCallerPos(true) +{} + +int GraphEdgeList::compareItems(Item item1, Item item2) +{ + CanvasEdge* e1 = ((GraphEdge*)item1)->canvasEdge(); + CanvasEdge* e2 = ((GraphEdge*)item2)->canvasEdge(); + + // edges without arrow visualisations are sorted as low + if (!e1) return -1; + if (!e2) return 1; + + int dx1, dy1, dx2, dy2; + int x, y; + if (_sortCallerPos) { + e1->controlPoints().point(0,&x,&y); + e2->controlPoints().point(0,&dx1,&dy1); + dx1 -= x; dy1 -= y; + } + else { + TQPointArray a1 = e1->controlPoints(); + TQPointArray a2 = e2->controlPoints(); + a1.point(a1.count()-2,&x,&y); + a2.point(a2.count()-1,&dx2,&dy2); + dx2 -= x; dy2 -= y; + } + double at1 = atan2(double(dx1), double(dy1)); + double at2 = atan2(double(dx2), double(dy2)); + + return (at1 < at2) ? 1:-1; +} + + + + +// +// GraphNode +// + +GraphNode::GraphNode() +{ + _f=0; + self = incl = 0; + _cn = 0; + + _visible = false; + _lastCallerIndex = _lastCallingIndex = -1; + + callers.setSortCallerPos(false); + callings.setSortCallerPos(true); + _lastFromCaller = true; +} + +TraceCall* GraphNode::visibleCaller() +{ + if (0) qDebug("GraphNode::visibleCaller %s: last %d, count %d", + _f->prettyName().ascii(), _lastCallerIndex, callers.count()); + + GraphEdge* e = callers.at(_lastCallerIndex); + if (e && !e->isVisible()) e = 0; + if (!e) { + double maxCost = 0.0; + GraphEdge* maxEdge = 0; + int idx = 0; + for(e = callers.first();e; e=callers.next(),idx++) + if (e->isVisible() && (e->cost > maxCost)) { + maxCost = e->cost; + maxEdge = e; + _lastCallerIndex = idx; + } + e = maxEdge; + } + return e ? e->call() : 0; +} + +TraceCall* GraphNode::visibleCalling() +{ + if (0) qDebug("GraphNode::visibleCalling %s: last %d, count %d", + _f->prettyName().ascii(), _lastCallingIndex, callings.count()); + + GraphEdge* e = callings.at(_lastCallingIndex); + if (e && !e->isVisible()) e = 0; + if (!e) { + double maxCost = 0.0; + GraphEdge* maxEdge = 0; + int idx = 0; + for(e = callings.first();e; e=callings.next(),idx++) + if (e->isVisible() && (e->cost > maxCost)) { + maxCost = e->cost; + maxEdge = e; + _lastCallingIndex = idx; + } + e = maxEdge; + } + return e ? e->call() : 0; +} + +void GraphNode::setCalling(GraphEdge* e) +{ + _lastCallingIndex = callings.findRef(e); + _lastFromCaller = false; +} + +void GraphNode::setCaller(GraphEdge* e) +{ + _lastCallerIndex = callers.findRef(e); + _lastFromCaller = true; +} + +TraceFunction* GraphNode::nextVisible() +{ + TraceCall* c; + if (_lastFromCaller) { + c = nextVisibleCaller(callers.at(_lastCallerIndex)); + if (c) return c->called(true); + c = nextVisibleCalling(callings.at(_lastCallingIndex)); + if (c) return c->caller(true); + } + else { + c = nextVisibleCalling(callings.at(_lastCallingIndex)); + if (c) return c->caller(true); + c = nextVisibleCaller(callers.at(_lastCallerIndex)); + if (c) return c->called(true); + } + return 0; +} + +TraceFunction* GraphNode::priorVisible() +{ + TraceCall* c; + if (_lastFromCaller) { + c = priorVisibleCaller(callers.at(_lastCallerIndex)); + if (c) return c->called(true); + c = priorVisibleCalling(callings.at(_lastCallingIndex)); + if (c) return c->caller(true); + } + else { + c = priorVisibleCalling(callings.at(_lastCallingIndex)); + if (c) return c->caller(true); + c = priorVisibleCaller(callers.at(_lastCallerIndex)); + if (c) return c->called(true); + } + return 0; +} + +TraceCall* GraphNode::nextVisibleCaller(GraphEdge* last) +{ + GraphEdge* e; + bool found = false; + int idx = 0; + for(e = callers.first();e; e=callers.next(),idx++) { + if (found && e->isVisible()) { + _lastCallerIndex = idx; + return e->call(); + } + if (e == last) found = true; + } + return 0; +} + +TraceCall* GraphNode::nextVisibleCalling(GraphEdge* last) +{ + GraphEdge* e; + bool found = false; + int idx = 0; + for(e = callings.first();e; e=callings.next(),idx++) { + if (found && e->isVisible()) { + _lastCallingIndex = idx; + return e->call(); + } + if (e == last) found = true; + } + return 0; +} + +TraceCall* GraphNode::priorVisibleCaller(GraphEdge* last) +{ + GraphEdge *e, *prev = 0; + int prevIdx = -1, idx = 0; + for(e = callers.first(); e; e=callers.next(),idx++) { + if (e == last) { + _lastCallerIndex = prevIdx; + return prev ? prev->call() : 0; + } + if (e->isVisible()) { + prev = e; + prevIdx = idx; + } + } + return 0; +} + +TraceCall* GraphNode::priorVisibleCalling(GraphEdge* last) +{ + GraphEdge *e, *prev = 0; + int prevIdx = -1, idx = 0; + for(e = callings.first(); e; e=callings.next(),idx++) { + if (e == last) { + _lastCallingIndex = prevIdx; + return prev ? prev->call() : 0; + } + if (e->isVisible()) { + prev = e; + prevIdx = idx; + } + } + return 0; +} + +// +// GraphEdge +// + +GraphEdge::GraphEdge() +{ + _c=0; + _from = _to = 0; + _fromNode = _toNode = 0; + cost = count = 0; + _ce = 0; + + _visible = false; + _lastFromCaller = true; +} + +TQString GraphEdge::prettyName() +{ + if (_c) return _c->prettyName(); + if (_from) return i18n("Call(s) from %1").arg(_from->prettyName()); + if (_to) return i18n("Call(s) to %1").arg(_to->prettyName()); + return i18n("(unknown call)"); +} + + +TraceFunction* GraphEdge::visibleCaller() +{ + if (_from) { + _lastFromCaller = true; + if (_fromNode) _fromNode->setCalling(this); + return _from; + } + return 0; +} + +TraceFunction* GraphEdge::visibleCalling() +{ + if (_to) { + _lastFromCaller = false; + if (_toNode) _toNode->setCaller(this); + return _to; + } + return 0; +} + +TraceCall* GraphEdge::nextVisible() +{ + TraceCall* res = 0; + + if (_lastFromCaller && _fromNode) { + res = _fromNode->nextVisibleCalling(this); + if (!res && _toNode) + res = _toNode->nextVisibleCaller(this); + } + else if (_toNode) { + res = _toNode->nextVisibleCaller(this); + if (!res && _fromNode) + res = _fromNode->nextVisibleCalling(this); + } + return res; +} + +TraceCall* GraphEdge::priorVisible() +{ + TraceCall* res = 0; + + if (_lastFromCaller && _fromNode) { + res = _fromNode->priorVisibleCalling(this); + if (!res && _toNode) + res = _toNode->priorVisibleCaller(this); + } + else if (_toNode) { + res = _toNode->priorVisibleCaller(this); + if (!res && _fromNode) + res = _fromNode->priorVisibleCalling(this); + } + return res; +} + + + +// +// GraphOptions +// + +TQString GraphOptions::layoutString(Layout l) +{ + if (l == Circular) return TQString("Circular"); + if (l == LeftRight) return TQString("LeftRight"); + return TQString("TopDown"); +} + +GraphOptions::Layout GraphOptions::layout(TQString s) +{ + if (s == TQString("Circular")) return Circular; + if (s == TQString("LeftRight")) return LeftRight; + return TopDown; +} + + +// +// StorableGraphOptions +// + +StorableGraphOptions::StorableGraphOptions() +{ + // default options + _funcLimit = DEFAULT_FUNCLIMIT; + _callLimit = DEFAULT_CALLLIMIT; + _maxCallerDepth = DEFAULT_MAXCALLER; + _maxCallingDepth = DEFAULT_MAXCALLING; + _showSkipped = DEFAULT_SHOWSKIPPED; + _expandCycles = DEFAULT_EXPANDCYCLES; + _detailLevel = DEFAULT_DETAILLEVEL; + _layout = DEFAULT_LAYOUT; +} + + + + +// +// GraphExporter +// + +GraphExporter::GraphExporter() +{ + _go = this; + _tmpFile = 0; + _item = 0; + reset(0, 0, 0, TraceItem::NoCostType, TQString()); +} + + +GraphExporter::GraphExporter(TraceData* d, TraceFunction* f, TraceCostType* ct, + TraceItem::CostType gt, TQString filename) +{ + _go = this; + _tmpFile = 0; + _item = 0; + reset(d, f, ct, gt, filename); +} + + +GraphExporter::~GraphExporter() +{ + if (_item && _tmpFile) { +#if DEBUG_GRAPH + _tmpFile->unlink(); +#endif + delete _tmpFile; + } +} + + +void GraphExporter::reset(TraceData*, TraceItem* i, TraceCostType* ct, + TraceItem::CostType gt, TQString filename) +{ + _graphCreated = false; + _nodeMap.clear(); + _edgeMap.clear(); + + if (_item && _tmpFile) { + _tmpFile->unlink(); + delete _tmpFile; + } + + if (i) { + switch(i->type()) { + case TraceItem::Function: + case TraceItem::FunctionCycle: + case TraceItem::Call: + break; + default: + i = 0; + } + } + + _item = i; + _costType = ct; + _groupType = gt; + if (!i) return; + + if (filename.isEmpty()) { + _tmpFile = new KTempFile(TQString(), ".dot"); + _dotName = _tmpFile->name(); + _useBox = true; + } + else { + _tmpFile = 0; + _dotName = filename; + _useBox = false; + } +} + + + +void GraphExporter::setGraphOptions(GraphOptions* go) +{ + if (go == 0) go = this; + _go = go; +} + +void GraphExporter::createGraph() +{ + if (!_item) return; + if (_graphCreated) return; + _graphCreated = true; + + if ((_item->type() == TraceItem::Function) || + (_item->type() == TraceItem::FunctionCycle)) { + TraceFunction* f = (TraceFunction*) _item; + + double incl = f->inclusive()->subCost(_costType); + _realFuncLimit = incl * _go->funcLimit(); + _realCallLimit = incl * _go->callLimit(); + + buildGraph(f, 0, true, 1.0); // down to callings + + // set costs of function back to 0, as it will be added again + GraphNode& n = _nodeMap[f]; + n.self = n.incl = 0.0; + + buildGraph(f, 0, false, 1.0); // up to callers + } + else { + TraceCall* c = (TraceCall*) _item; + + double incl = c->subCost(_costType); + _realFuncLimit = incl * _go->funcLimit(); + _realCallLimit = incl * _go->callLimit(); + + // create edge + TraceFunction *caller, *called; + caller = c->caller(false); + called = c->called(false); + TQPair<TraceFunction*,TraceFunction*> p(caller, called); + GraphEdge& e = _edgeMap[p]; + e.setCall(c); + e.setCaller(p.first); + e.setCalling(p.second); + e.cost = c->subCost(_costType); + e.count = c->callCount(); + + SubCost s = called->inclusive()->subCost(_costType); + buildGraph(called, 0, true, e.cost / s); // down to callings + s = caller->inclusive()->subCost(_costType); + buildGraph(caller, 0, false, e.cost / s); // up to callers + } +} + +void GraphExporter::writeDot() +{ + if (!_item) return; + + TQFile* file = 0; + TQTextStream* stream = 0; + + if (_tmpFile) + stream = _tmpFile->textStream(); + else { + file = new TQFile(_dotName); + if ( !file->open( IO_WriteOnly ) ) { + kdError() << "Can't write dot file '" << _dotName << "'" << endl; + return; + } + stream = new TQTextStream(file); + } + + if (!_graphCreated) createGraph(); + + /* Generate dot format... + * When used for the CallGraphView (in contrast to "Export Callgraph..."), + * the labels are only dummy placeholders to reserve space for our own + * drawings. + */ + + *stream << "digraph \"callgraph\" {\n"; + + if (_go->layout() == LeftRight) { + *stream << TQString(" rankdir=LR;\n"); + } + else if (_go->layout() == Circular) { + TraceFunction *f = 0; + switch(_item->type()) { + case TraceItem::Function: + case TraceItem::FunctionCycle: + f = (TraceFunction*) _item; + break; + case TraceItem::Call: + f = ((TraceCall*)_item)->caller(true); + break; + default: + break; + } + if (f) + *stream << TQString(" center=F%1;\n").arg((long)f, 0, 16); + *stream << TQString(" overlap=false;\n splines=true;\n"); + } + + // for clustering + TQMap<TraceCostItem*,TQPtrList<GraphNode> > nLists; + + GraphNodeMap::Iterator nit; + for ( nit = _nodeMap.begin(); + nit != _nodeMap.end(); ++nit ) { + GraphNode& n = *nit; + + if (n.incl <= _realFuncLimit) continue; + + // for clustering: get cost item group of function + TraceCostItem* g; + TraceFunction* f = n.function(); + switch(_groupType) { + case TraceItem::Object: g = f->object(); break; + case TraceItem::Class: g = f->cls(); break; + case TraceItem::File: g = f->file(); break; + case TraceItem::FunctionCycle: g = f->cycle(); break; + default: g = 0; break; + } + nLists[g].append(&n); + } + + TQMap<TraceCostItem*,TQPtrList<GraphNode> >::Iterator lit; + int cluster = 0; + for ( lit = nLists.begin(); + lit != nLists.end(); ++lit, cluster++ ) { + TQPtrList<GraphNode>& l = lit.data(); + TraceCostItem* i = lit.key(); + + if (_go->clusterGroups() && i) { + TQString iabr = i->prettyName(); + if ((int)iabr.length() > Configuration::maxSymbolLength()) + iabr = iabr.left(Configuration::maxSymbolLength()) + "..."; + + *stream << TQString("subgraph \"cluster%1\" { label=\"%2\";\n") + .arg(cluster).arg(iabr); + } + + GraphNode* np; + for(np = l.first(); np; np = l.next() ) { + TraceFunction* f = np->function(); + + TQString abr = f->prettyName(); + if ((int)abr.length() > Configuration::maxSymbolLength()) + abr = abr.left(Configuration::maxSymbolLength()) + "..."; + + *stream << TQString(" F%1 [").arg((long)f, 0, 16); + if (_useBox) { + // make label 3 lines for CallGraphView + *stream << TQString("shape=box,label=\"** %1 **\\n**\\n%2\"];\n") + .arg(abr) + .arg(SubCost(np->incl).pretty()); + } + else + *stream << TQString("label=\"%1\\n%2\"];\n") + .arg(abr) + .arg(SubCost(np->incl).pretty()); + } + + if (_go->clusterGroups() && i) + *stream << TQString("}\n"); + } + + GraphEdgeMap::Iterator eit; + for ( eit = _edgeMap.begin(); + eit != _edgeMap.end(); ++eit ) { + GraphEdge& e = *eit; + + if (e.cost < _realCallLimit) continue; + if (!_go->expandCycles()) { + // don't show inner cycle calls + if (e.call()->inCycle()>0) continue; + } + + + GraphNode& from = _nodeMap[e.from()]; + GraphNode& to = _nodeMap[e.to()]; + + e.setCallerNode(&from); + e.setCallingNode(&to); + + if ((from.incl <= _realFuncLimit) || + (to.incl <= _realFuncLimit)) continue; + + // remove dumped edges from n.callers/n.callings + from.callings.removeRef(&e); + to.callers.removeRef(&e); + from.callingSet.remove(&e); + to.callerSet.remove(&e); + + *stream << TQString(" F%1 -> F%2 [weight=%3") + .arg((long)e.from(), 0, 16) + .arg((long)e.to(), 0, 16) + .arg((long)log(log(e.cost))); + + if (_go->detailLevel() ==1) + *stream << TQString(",label=\"%1\"") + .arg(SubCost(e.cost).pretty()); + else if (_go->detailLevel() ==2) + *stream << TQString(",label=\"%3\\n%4 x\"") + .arg(SubCost(e.cost).pretty()) + .arg(SubCost(e.count).pretty()); + + *stream << TQString("];\n"); + } + + if (_go->showSkipped()) { + + // Create sum-edges for skipped edges + GraphEdge* e; + double costSum, countSum; + for ( nit = _nodeMap.begin(); + nit != _nodeMap.end(); ++nit ) { + GraphNode& n = *nit; + if (n.incl <= _realFuncLimit) continue; + + costSum = countSum = 0.0; + for (e=n.callers.first();e;e=n.callers.next()) { + costSum += e->cost; + countSum += e->count; + } + if (costSum > _realCallLimit) { + + TQPair<TraceFunction*,TraceFunction*> p(0, n.function()); + e = &(_edgeMap[p]); + e->setCalling(p.second); + e->cost = costSum; + e->count = countSum; + + *stream << TQString(" R%1 [shape=point,label=\"\"];\n") + .arg((long)n.function(), 0, 16); + *stream << TQString(" R%1 -> F%2 [label=\"%3\\n%4 x\",weight=%5];\n") + .arg((long)n.function(), 0, 16) + .arg((long)n.function(), 0, 16) + .arg(SubCost(costSum).pretty()) + .arg(SubCost(countSum).pretty()) + .arg((int)log(costSum)); + } + + costSum = countSum = 0.0; + for (e=n.callings.first();e;e=n.callings.next()) { + costSum += e->cost; + countSum += e->count; + } + if (costSum > _realCallLimit) { + + TQPair<TraceFunction*,TraceFunction*> p(n.function(), 0); + e = &(_edgeMap[p]); + e->setCaller(p.first); + e->cost = costSum; + e->count = countSum; + + *stream << TQString(" S%1 [shape=point,label=\"\"];\n") + .arg((long)n.function(), 0, 16); + *stream << TQString(" F%1 -> S%2 [label=\"%3\\n%4 x\",weight=%5];\n") + .arg((long)n.function(), 0, 16) + .arg((long)n.function(), 0, 16) + .arg(SubCost(costSum).pretty()) + .arg(SubCost(countSum).pretty()) + .arg((int)log(costSum)); + } + } + } + + // clear edges here completely. + // Visible edges are inserted again on parsing in CallGraphView::refresh + for ( nit = _nodeMap.begin(); + nit != _nodeMap.end(); ++nit ) { + GraphNode& n = *nit; + n.callers.clear(); + n.callings.clear(); + n.callerSet.clear(); + n.callingSet.clear(); + } + + *stream << "}\n"; + + if (_tmpFile) { + _tmpFile->close(); + } + else { + file->close(); + delete file; + delete stream; + } +} + +void GraphExporter::sortEdges() +{ + GraphNodeMap::Iterator nit; + for ( nit = _nodeMap.begin(); + nit != _nodeMap.end(); ++nit ) { + GraphNode& n = *nit; + + n.callers.sort(); + n.callings.sort(); + } +} + +TraceFunction* GraphExporter::toFunc(TQString s) +{ + if (s[0] != 'F') return 0; + bool ok; + TraceFunction* f = (TraceFunction*) s.mid(1).toULong(&ok, 16); + if (!ok) return 0; + + return f; +} + +GraphNode* GraphExporter::node(TraceFunction* f) +{ + if (!f) return 0; + + GraphNodeMap::Iterator it = _nodeMap.find(f); + if (it == _nodeMap.end()) return 0; + + return &(*it); +} + +GraphEdge* GraphExporter::edge(TraceFunction* f1, TraceFunction* f2) +{ + GraphEdgeMap::Iterator it = _edgeMap.find(tqMakePair(f1, f2)); + if (it == _edgeMap.end()) return 0; + + return &(*it); +} + + +/** + * We do a DFS and don't stop on already visited nodes/edges, + * but add up costs. We only stop if limits/max depth is reached. + * + * For a node/edge, it can happen that the first time visited the + * cost will below the limit, so the search is stopped. + * If on a further visit of the node/edge the limit is reached, + * we use the whole node/edge cost and continue search. + */ +void GraphExporter::buildGraph(TraceFunction* f, int d, + bool toCallings, double factor) +{ +#if DEBUG_GRAPH + kdDebug() << "buildGraph(" << f->prettyName() << "," << d << "," << factor + << ") [to " << (toCallings ? "Callings":"Callers") << "]" << endl; +#endif + + double oldIncl = 0.0; + GraphNode& n = _nodeMap[f]; + if (n.function() == 0) { + n.setFunction(f); + } + else + oldIncl = n.incl; + + double incl = f->inclusive()->subCost(_costType) * factor; + n.incl += incl; + n.self += f->subCost(_costType) * factor; + if (0) qDebug(" Added Incl. %f, now %f", incl, n.incl); + + // A negative depth limit means "unlimited" + int maxDepth = toCallings ? _go->maxCallingDepth() : _go->maxCallerDepth(); + if ((maxDepth>=0) && (d >= maxDepth)) { + if (0) qDebug(" Cutoff, max depth reached"); + return; + } + + // if we just reached the limit by summing, do a DFS + // from here with full incl. cost because of previous cutoffs + if ((n.incl >= _realFuncLimit) && (oldIncl < _realFuncLimit)) incl = n.incl; + + if (f->cycle()) { + // for cycles members, we never stop on first visit, but always on 2nd + // note: a 2nd visit never should happen, as we don't follow inner-cycle + // calls + if (oldIncl > 0.0) { + if (0) qDebug(" Cutoff, 2nd visit to Cycle Member"); + // and takeback cost addition, as it's added twice + n.incl = oldIncl; + n.self -= f->subCost(_costType) * factor; + return; + } + } + else if (incl <= _realFuncLimit) { + if (0) qDebug(" Cutoff, below limit"); + return; + } + + TraceCall* call; + TraceFunction* f2; + + + // on entering a cycle, only go the FunctionCycle + TraceCallList l = toCallings ? + f->callings(false) : f->callers(false); + + for (call=l.first();call;call=l.next()) { + + f2 = toCallings ? call->called(false) : call->caller(false); + + double count = call->callCount() * factor; + double cost = call->subCost(_costType) * factor; + + // ignore function calls with absolute cost < 3 per call + // No: This would skip a lot of functions e.g. with L2 cache misses + // if (count>0.0 && (cost/count < 3)) continue; + + double oldCost = 0.0; + TQPair<TraceFunction*,TraceFunction*> p(toCallings ? f:f2, + toCallings ? f2:f); + GraphEdge& e = _edgeMap[p]; + if (e.call() == 0) { + e.setCall(call); + e.setCaller(p.first); + e.setCalling(p.second); + } + else + oldCost = e.cost; + + e.cost += cost; + e.count += count; + if (0) qDebug(" Edge to %s, added cost %f, now %f", + f2->prettyName().ascii(), cost, e.cost); + + // if this call goes into a FunctionCycle, we also show the real call + if (f2->cycle() == f2) { + TraceFunction* realF; + realF = toCallings ? call->called(true) : call->caller(true); + TQPair<TraceFunction*,TraceFunction*> realP(toCallings ? f:realF, + toCallings ? realF:f); + GraphEdge& e = _edgeMap[realP]; + if (e.call() == 0) { + e.setCall(call); + e.setCaller(realP.first); + e.setCalling(realP.second); + } + e.cost += cost; + e.count += count; + } + + // - don't do a DFS on calls in recursion/cycle + if (call->inCycle()>0) continue; + if (call->isRecursion()) continue; + + if (toCallings) { + GraphEdgeSet::Iterator it = n.callingSet.find(&e); + if (it == n.callingSet.end()) { + n.callings.append(&e); + n.callingSet.insert(&e, 1 ); + } + } + else { + GraphEdgeSet::Iterator it = n.callerSet.find(&e); + if (it == n.callerSet.end()) { + n.callers.append(&e); + n.callerSet.insert(&e, 1 ); + } + } + + // if we just reached the call limit (=func limit by summing, do a DFS + // from here with full incl. cost because of previous cutoffs + if ((e.cost >= _realCallLimit) && (oldCost < _realCallLimit)) cost = e.cost; + if (cost < _realCallLimit) { + if (0) qDebug(" Edge Cutoff, limit not reached"); + continue; + } + + SubCost s; + if (call->inCycle()) + s = f2->cycle()->inclusive()->subCost(_costType); + else + s = f2->inclusive()->subCost(_costType); + SubCost v = call->subCost(_costType); + buildGraph(f2, d+1, toCallings, factor * v / s); + } +} + + +// +// PannerView +// +PannerView::PannerView(TQWidget * parent, const char * name) + : TQCanvasView(parent, name, WNoAutoErase | WStaticContents) +{ + _movingZoomRect = false; + + // why doesn't this avoid flicker ? + viewport()->setBackgroundMode(TQt::NoBackground); + setBackgroundMode(TQt::NoBackground); +} + +void PannerView::setZoomRect(TQRect r) +{ + TQRect oldRect = _zoomRect; + _zoomRect = r; + updateContents(oldRect); + updateContents(_zoomRect); +} + +void PannerView::drawContents(TQPainter * p, int clipx, int clipy, int clipw, int cliph) +{ + // save/restore around TQCanvasView::drawContents seems to be needed + // for QT 3.0 to get the red rectangle drawn correct + p->save(); + TQCanvasView::drawContents(p,clipx,clipy,clipw,cliph); + p->restore(); + if (_zoomRect.isValid()) { + p->setPen(red.dark()); + p->drawRect(_zoomRect); + p->setPen(red); + p->drawRect(TQRect(_zoomRect.x()+1, _zoomRect.y()+1, + _zoomRect.width()-2, _zoomRect.height()-2)); + } +} + +void PannerView::contentsMousePressEvent(TQMouseEvent* e) +{ + if (_zoomRect.isValid()) { + if (!_zoomRect.contains(e->pos())) + emit zoomRectMoved(e->pos().x() - _zoomRect.center().x(), + e->pos().y() - _zoomRect.center().y()); + + _movingZoomRect = true; + _lastPos = e->pos(); + } +} + +void PannerView::contentsMouseMoveEvent(TQMouseEvent* e) +{ + if (_movingZoomRect) { + emit zoomRectMoved(e->pos().x() - _lastPos.x(), e->pos().y() - _lastPos.y()); + _lastPos = e->pos(); + } +} + +void PannerView::contentsMouseReleaseEvent(TQMouseEvent*) +{ + _movingZoomRect = false; + emit zoomRectMoveFinished(); +} + + + + + +// +// CanvasNode +// + +CanvasNode::CanvasNode(CallGraphView* v, GraphNode* n, + int x, int y, int w, int h, TQCanvas* c) + : TQCanvasRectangle(x, y, w, h, c), _node(n), _view(v) +{ + setPosition(0, DrawParams::TopCenter); + setPosition(1, DrawParams::BottomCenter); + + updateGroup(); + + if (!_node || !_view) return; + + if (_node->function()) + setText(0, _node->function()->prettyName()); + + TraceCost* totalCost; + if (_view->topLevel()->showExpanded()) { + if (_view->activeFunction()) { + if (_view->activeFunction()->cycle()) + totalCost = _view->activeFunction()->cycle()->inclusive(); + else + totalCost = _view->activeFunction()->inclusive(); + } + else + totalCost = (TraceCost*) _view->activeItem(); + } + else + totalCost = _view->TraceItemView::data(); + double total = totalCost->subCost(_view->costType()); + double inclP = 100.0 * n->incl / total; + if (_view->topLevel()->showPercentage()) + setText(1, TQString("%1 %") + .arg(inclP, 0, 'f', Configuration::percentPrecision())); + else + setText(1, SubCost(n->incl).pretty()); + setPixmap(1, percentagePixmap(25,10,(int)(inclP+.5), TQt::blue, true)); +} + +void CanvasNode::setSelected(bool s) +{ + StoredDrawParams::setSelected(s); + update(); +} + +void CanvasNode::updateGroup() +{ + if (!_view || !_node) return; + + TQColor c = Configuration::functionColor(_view->groupType(), + _node->function()); + setBackColor(c); + update(); +} + +void CanvasNode::drawShape(TQPainter& p) +{ + TQRect r = rect(), origRect = r; + + r.setRect(r.x()+1, r.y()+1, r.width()-2, r.height()-2); + + RectDrawing d(r); + d.drawBack(&p, this); + r.setRect(r.x()+2, r.y()+2, r.width()-4, r.height()-4); + + if (StoredDrawParams::selected() && _view->hasFocus()) { + _view->style().tqdrawPrimitive( TQStyle::PE_FocusRect, &p, r, + _view->colorGroup()); + } + + // draw afterwards to always get a frame even when zoomed + p.setPen(StoredDrawParams::selected() ? red : black); + p.drawRect(origRect); + + d.setRect(r); + d.drawField(&p, 0, this); + d.drawField(&p, 1, this); +} + + +// +// CanvasEdgeLabel +// + +CanvasEdgeLabel::CanvasEdgeLabel(CallGraphView* v, CanvasEdge* ce, + int x, int y, int w, int h, TQCanvas* c) + : TQCanvasRectangle(x, y, w, h, c), _ce(ce), _view(v) +{ + GraphEdge* e = ce->edge(); + if (!e) return; + + setPosition(1, DrawParams::TopCenter); + setText(1, TQString("%1 x").arg(SubCost(e->count).pretty())); + + setPosition(0, DrawParams::BottomCenter); + + TraceCost* totalCost; + if (_view->topLevel()->showExpanded()) { + if (_view->activeFunction()) { + if (_view->activeFunction()->cycle()) + totalCost = _view->activeFunction()->cycle()->inclusive(); + else + totalCost = _view->activeFunction()->inclusive(); + } + else + totalCost = (TraceCost*) _view->activeItem(); + } + else + totalCost = _view->TraceItemView::data(); + double total = totalCost->subCost(_view->costType()); + double inclP = 100.0 * e->cost / total; + if (_view->topLevel()->showPercentage()) + setText(0, TQString("%1 %") + .arg(inclP, 0, 'f', Configuration::percentPrecision())); + else + setText(0, SubCost(e->cost).pretty()); + setPixmap(0, percentagePixmap(25,10,(int)(inclP+.5), TQt::blue, true)); + + if (e->call() && (e->call()->isRecursion() || e->call()->inCycle())) { + TQString icon = "undo"; + KIconLoader* loader = KApplication::kApplication()->iconLoader(); + TQPixmap p= loader->loadIcon(icon, KIcon::Small, 0, + KIcon::DefaultState, 0, true); + setPixmap(0, p); + } +} + +void CanvasEdgeLabel::drawShape(TQPainter& p) +{ + TQRect r = rect(); + //p.setPen(blue); + //p.drawRect(r); + RectDrawing d(r); + d.drawField(&p, 0, this); + d.drawField(&p, 1, this); +} + +// +// CanvasEdgeArrow + +CanvasEdgeArrow::CanvasEdgeArrow(CanvasEdge* ce, TQCanvas* c) + : TQCanvasPolygon(c), _ce(ce) +{} + +void CanvasEdgeArrow::drawShape(TQPainter& p) +{ + if (_ce->isSelected()) p.setBrush(TQt::red); + + TQCanvasPolygon::drawShape(p); +} + +// +// CanvasEdge +// + +CanvasEdge::CanvasEdge(GraphEdge* e, TQCanvas* c) + : TQCanvasSpline(c), _edge(e) +{ + _label = 0; + _arrow = 0; +} + +void CanvasEdge::setSelected(bool s) +{ + TQCanvasItem::setSelected(s); + update(); + if (_arrow) _arrow->setSelected(s); +} + +TQPointArray CanvasEdge::areaPoints() const +{ + int minX = poly[0].x(), minY = poly[0].y(); + int maxX = minX, maxY = minY; + int i; + + if (0) qDebug("CanvasEdge::areaPoints\n P 0: %d/%d", minX, minY); + int len = poly.count(); + for (i=1;i<len;i++) { + if (poly[i].x() < minX) minX = poly[i].x(); + if (poly[i].y() < minY) minY = poly[i].y(); + if (poly[i].x() > maxX) maxX = poly[i].x(); + if (poly[i].y() > maxY) maxY = poly[i].y(); + if (0) qDebug(" P %d: %d/%d", i, poly[i].x(), poly[i].y()); + } + TQPointArray a = poly.copy(), b = poly.copy(); + if (minX == maxX) { + a.translate(-2, 0); + b.translate(2, 0); + } + else { + a.translate(0, -2); + b.translate(0, 2); + } + a.resize(2*len); + for (i=0;i<len;i++) + a[2 * len - 1 -i] = b[i]; + + if (0) { + qDebug(" Result:"); + for (i=0;i<2*len;i++) + qDebug(" P %d: %d/%d", i, a[i].x(), a[i].y()); + } + + return a; +} + +void CanvasEdge::drawShape(TQPainter& p) +{ + if (isSelected()) p.setPen(TQt::red); + + p.drawPolyline(poly); +} + + +// +// CanvasFrame +// + +TQPixmap* CanvasFrame::_p = 0; + +CanvasFrame::CanvasFrame(CanvasNode* n, TQCanvas* c) + : TQCanvasRectangle(c) +{ + if (!_p) { + + int d = 5; + float v1 = 130.0, v2 = 10.0, v = v1, f = 1.03; + + // calculate pix size + TQRect r(0, 0, 30, 30); + while (v>v2) { + r.setRect(r.x()-d, r.y()-d, r.width()+2*d, r.height()+2*d); + v /= f; + } + + _p = new TQPixmap(r.size()); + _p->fill(TQt::white); + TQPainter p(_p); + p.setPen(TQt::NoPen); + + r.moveBy(-r.x(), -r.y()); + + while (v<v1) { + v *= f; + p.setBrush(TQColor(265-(int)v, 265-(int)v, 265-(int)v)); + + p.drawRect(TQRect(r.x(), r.y(), r.width(), d)); + p.drawRect(TQRect(r.x(), r.bottom()-d, r.width(), d)); + p.drawRect(TQRect(r.x(), r.y()+d, d, r.height()-2*d)); + p.drawRect(TQRect(r.right()-d, r.y()+d, d, r.height()-2*d)); + + r.setRect(r.x()+d, r.y()+d, r.width()-2*d, r.height()-2*d); + } + } + + setSize(_p->width(), _p->height()); + move(n->rect().center().x()-_p->width()/2, + n->rect().center().y()-_p->height()/2); +} + + +void CanvasFrame::drawShape(TQPainter& p) +{ + p.drawPixmap( int(x()), int(y()), *_p ); +} + + + + +// +// Tooltips for CallGraphView +// + +class CallGraphTip: public TQToolTip +{ +public: + CallGraphTip( TQWidget* p ):TQToolTip(p) {} + +protected: + void maybeTip( const TQPoint & ); +}; + +void CallGraphTip::maybeTip( const TQPoint& pos ) +{ + if (!parentWidget()->inherits( "CallGraphView" )) return; + CallGraphView* cgv = (CallGraphView*)parentWidget(); + + TQPoint cPos = cgv->viewportToContents(pos); + + if (0) qDebug("CallGraphTip for (%d/%d) -> (%d/%d) ?", + pos.x(), pos.y(), cPos.x(), cPos.y()); + + TQCanvasItemList l = cgv->canvas()->collisions(cPos); + if (l.count() == 0) return; + TQCanvasItem* i = l.first(); + + if (i->rtti() == CANVAS_NODE) { + CanvasNode* cn = (CanvasNode*)i; + GraphNode* n = cn->node(); + if (0) qDebug("CallGraphTip: Mouse on Node '%s'", + n->function()->prettyName().ascii()); + + TQString tipStr = TQString("%1 (%2)").arg(cn->text(0)).arg(cn->text(1)); + TQPoint vPosTL = cgv->contentsToViewport(i->boundingRect().topLeft()); + TQPoint vPosBR = cgv->contentsToViewport(i->boundingRect().bottomRight()); + tip(TQRect(vPosTL, vPosBR), tipStr); + + return; + } + + // redirect from label / arrow to edge + if (i->rtti() == CANVAS_EDGELABEL) + i = ((CanvasEdgeLabel*)i)->canvasEdge(); + if (i->rtti() == CANVAS_EDGEARROW) + i = ((CanvasEdgeArrow*)i)->canvasEdge(); + + if (i->rtti() == CANVAS_EDGE) { + CanvasEdge* ce = (CanvasEdge*)i; + GraphEdge* e = ce->edge(); + if (0) qDebug("CallGraphTip: Mouse on Edge '%s'", + e->prettyName().ascii()); + + TQString tipStr; + if (!ce->label()) + tipStr = e->prettyName(); + else + tipStr = TQString("%1 (%2)") + .arg(ce->label()->text(0)).arg(ce->label()->text(1)); + tip(TQRect(pos.x()-5,pos.y()-5,pos.x()+5,pos.y()+5), tipStr); + } +} + + + + +// +// CallGraphView +// +CallGraphView::CallGraphView(TraceItemView* parentView, + TQWidget* parent, const char* name) + : TQCanvasView(parent, name), TraceItemView(parentView) +{ + _zoomPosition = DEFAULT_ZOOMPOS; + _lastAutoPosition = TopLeft; + + _canvas = 0; + _xMargin = _yMargin = 0; + _completeView = new PannerView(this); + _cvZoom = 1; + _selectedNode = 0; + _selectedEdge = 0; + + _exporter.setGraphOptions(this); + + _completeView->setVScrollBarMode(TQScrollView::AlwaysOff); + _completeView->setHScrollBarMode(TQScrollView::AlwaysOff); + _completeView->raise(); + _completeView->hide(); + + setFocusPolicy(TQ_StrongFocus); + setBackgroundMode(TQt::NoBackground); + + connect(this, TQT_SIGNAL(contentsMoving(int,int)), + this, TQT_SLOT(contentsMovingSlot(int,int))); + connect(_completeView, TQT_SIGNAL(zoomRectMoved(int,int)), + this, TQT_SLOT(zoomRectMoved(int,int))); + connect(_completeView, TQT_SIGNAL(zoomRectMoveFinished()), + this, TQT_SLOT(zoomRectMoveFinished())); + + TQWhatsThis::add( this, whatsThis() ); + + // tooltips... + _tip = new CallGraphTip(this); + + _renderProcess = 0; + _prevSelectedNode = 0; + connect(&_renderTimer, TQT_SIGNAL(timeout()), + this, TQT_SLOT(showRenderWarning())); +} + +CallGraphView::~CallGraphView() +{ + delete _completeView; + delete _tip; + + if (_canvas) { + setCanvas(0); + delete _canvas; + } +} + +TQString CallGraphView::whatsThis() const +{ + return i18n( "<b>Call Graph around active Function</b>" + "<p>Depending on configuration, this view shows " + "the call graph environment of the active function. " + "Note: the shown cost is <b>only</b> the cost which is " + "spent while the active function was actually running; " + "i.e. the cost shown for main() - if it's visible - should " + "be the same as the cost of the active function, as that's " + "the part of inclusive cost of main() spent while the active " + "function was running.</p>" + "<p>For cycles, blue call arrows indicate that this is an " + "artificial call added for correct drawing which " + "actually never happened.</p>" + "<p>If the graph is larger than the widget area, an overview " + "panner is shown in one edge. " + "There are similar visualization options to the " + "Call Treemap; the selected function is highlighted.<p>"); +} + +void CallGraphView::updateSizes(TQSize s) +{ + if (!_canvas) return; + + if (s == TQSize(0,0)) s = size(); + + // the part of the canvas that should be visible + int cWidth = _canvas->width() - 2*_xMargin + 100; + int cHeight = _canvas->height() - 2*_yMargin + 100; + + // hide birds eye view if no overview needed + if (!_data || !_activeItem || + ((cWidth < s.width()) && cHeight < s.height())) { + _completeView->hide(); + return; + } + _completeView->show(); + + // first, assume use of 1/3 of width/height (possible larger) + double zoom = .33 * s.width() / cWidth; + if (zoom * cHeight < .33 * s.height()) zoom = .33 * s.height() / cHeight; + + // fit to widget size + if (cWidth * zoom > s.width()) zoom = s.width() / (double)cWidth; + if (cHeight * zoom > s.height()) zoom = s.height() / (double)cHeight; + + // scale to never use full height/width + zoom = zoom * 3/4; + + // at most a zoom of 1/3 + if (zoom > .33) zoom = .33; + + if (zoom != _cvZoom) { + _cvZoom = zoom; + if (0) qDebug("Canvas Size: %dx%d, Visible: %dx%d, Zoom: %f", + _canvas->width(), _canvas->height(), + cWidth, cHeight, zoom); + + TQWMatrix wm; + wm.scale( zoom, zoom ); + _completeView->setWorldMatrix(wm); + + // make it a little bigger to compensate for widget frame + _completeView->resize(int(cWidth * zoom) + 4, + int(cHeight * zoom) + 4); + + // update ZoomRect in completeView + contentsMovingSlot(contentsX(), contentsY()); + } + + _completeView->setContentsPos(int(zoom*(_xMargin-50)), + int(zoom*(_yMargin-50))); + + int cvW = _completeView->width(); + int cvH = _completeView->height(); + int x = width()- cvW - verticalScrollBar()->width() -2; + int y = height()-cvH - horizontalScrollBar()->height() -2; + TQPoint oldZoomPos = _completeView->pos(); + TQPoint newZoomPos = TQPoint(0,0); + ZoomPosition zp = _zoomPosition; + if (zp == Auto) { + TQPoint tl1Pos = viewportToContents(TQPoint(0,0)); + TQPoint tl2Pos = viewportToContents(TQPoint(cvW,cvH)); + TQPoint tr1Pos = viewportToContents(TQPoint(x,0)); + TQPoint tr2Pos = viewportToContents(TQPoint(x+cvW,cvH)); + TQPoint bl1Pos = viewportToContents(TQPoint(0,y)); + TQPoint bl2Pos = viewportToContents(TQPoint(cvW,y+cvH)); + TQPoint br1Pos = viewportToContents(TQPoint(x,y)); + TQPoint br2Pos = viewportToContents(TQPoint(x+cvW,y+cvH)); + int tlCols = _canvas->collisions(TQRect(tl1Pos,tl2Pos)).count(); + int trCols = _canvas->collisions(TQRect(tr1Pos,tr2Pos)).count(); + int blCols = _canvas->collisions(TQRect(bl1Pos,bl2Pos)).count(); + int brCols = _canvas->collisions(TQRect(br1Pos,br2Pos)).count(); + int minCols = tlCols; + zp = _lastAutoPosition; + switch(zp) { + case TopRight: minCols = trCols; break; + case BottomLeft: minCols = blCols; break; + case BottomRight: minCols = brCols; break; + default: + case TopLeft: minCols = tlCols; break; + } + if (minCols > tlCols) { minCols = tlCols; zp = TopLeft; } + if (minCols > trCols) { minCols = trCols; zp = TopRight; } + if (minCols > blCols) { minCols = blCols; zp = BottomLeft; } + if (minCols > brCols) { minCols = brCols; zp = BottomRight; } + + _lastAutoPosition = zp; + } + + switch(zp) { + case TopRight: + newZoomPos = TQPoint(x,0); + break; + case BottomLeft: + newZoomPos = TQPoint(0,y); + break; + case BottomRight: + newZoomPos = TQPoint(x,y); + break; + default: + break; + } + if (newZoomPos != oldZoomPos) _completeView->move(newZoomPos); +} + +void CallGraphView::focusInEvent(TQFocusEvent*) +{ + if (!_canvas) return; + + if (_selectedNode && _selectedNode->canvasNode()) { + _selectedNode->canvasNode()->setSelected(true); // requests item update + _canvas->update(); + } +} + +void CallGraphView::focusOutEvent(TQFocusEvent* e) +{ + // trigger updates as in focusInEvent + focusInEvent(e); +} + +void CallGraphView::keyPressEvent(TQKeyEvent* e) +{ + if (!_canvas) { + e->ignore(); + return; + } + + if ((e->key() == Key_Return) || + (e->key() == Key_Space)) { + if (_selectedNode) + activated(_selectedNode->function()); + else if (_selectedEdge && _selectedEdge->call()) + activated(_selectedEdge->call()); + return; + } + + // move selected node/edge + if (!(e->state() & (ShiftButton | ControlButton)) && + (_selectedNode || _selectedEdge) && + ((e->key() == Key_Up) || + (e->key() == Key_Down) || + (e->key() == Key_Left) || + (e->key() == Key_Right))) { + + TraceFunction* f = 0; + TraceCall* c = 0; + + // rotate arrow key meaning for LeftRight layout + int key = e->key(); + if (_layout == LeftRight) { + switch(key) { + case Key_Up: key = Key_Left; break; + case Key_Down: key = Key_Right; break; + case Key_Left: key = Key_Up; break; + case Key_Right: key = Key_Down; break; + default: break; + } + } + + if (_selectedNode) { + if (key == Key_Up) c = _selectedNode->visibleCaller(); + if (key == Key_Down) c = _selectedNode->visibleCalling(); + if (key == Key_Right) f = _selectedNode->nextVisible(); + if (key == Key_Left) f = _selectedNode->priorVisible(); + } + else if (_selectedEdge) { + if (key == Key_Up) f = _selectedEdge->visibleCaller(); + if (key == Key_Down) f = _selectedEdge->visibleCalling(); + if (key == Key_Right) c = _selectedEdge->nextVisible(); + if (key == Key_Left) c = _selectedEdge->priorVisible(); + } + + if (c) selected(c); + if (f) selected(f); + return; + } + + // move canvas... + if (e->key() == Key_Home) + scrollBy(-_canvas->width(),0); + else if (e->key() == Key_End) + scrollBy(_canvas->width(),0); + else if (e->key() == Key_Prior) + scrollBy(0,-visibleHeight()/2); + else if (e->key() == Key_Next) + scrollBy(0,visibleHeight()/2); + else if (e->key() == Key_Left) + scrollBy(-visibleWidth()/10,0); + else if (e->key() == Key_Right) + scrollBy(visibleWidth()/10,0); + else if (e->key() == Key_Down) + scrollBy(0,visibleHeight()/10); + else if (e->key() == Key_Up) + scrollBy(0,-visibleHeight()/10); + else e->ignore(); +} + +void CallGraphView::resizeEvent(TQResizeEvent* e) +{ + TQCanvasView::resizeEvent(e); + if (_canvas) updateSizes(e->size()); +} + +TraceItem* CallGraphView::canShow(TraceItem* i) +{ + if (i) { + switch(i->type()) { + case TraceItem::Function: + case TraceItem::FunctionCycle: + case TraceItem::Call: + return i; + default: + break; + } + } + return 0; +} + +void CallGraphView::doUpdate(int changeType) +{ + // Special case ? + if (changeType == costType2Changed) return; + + if (changeType == selectedItemChanged) { + if (!_canvas) return; + + if (!_selectedItem) return; + + GraphNode* n = 0; + GraphEdge* e = 0; + if ((_selectedItem->type() == TraceItem::Function) || + (_selectedItem->type() == TraceItem::FunctionCycle)) { + n = _exporter.node((TraceFunction*)_selectedItem); + if (n == _selectedNode) return; + } + else if (_selectedItem->type() == TraceItem::Call) { + TraceCall* c = (TraceCall*)_selectedItem; + e = _exporter.edge(c->caller(false), c->called(false)); + if (e == _selectedEdge) return; + } + + // unselected any selected item + if (_selectedNode && _selectedNode->canvasNode()) { + _selectedNode->canvasNode()->setSelected(false); + } + _selectedNode = 0; + if (_selectedEdge && _selectedEdge->canvasEdge()) { + _selectedEdge->canvasEdge()->setSelected(false); + } + _selectedEdge = 0; + + // select + CanvasNode* sNode = 0; + if (n && n->canvasNode()) { + _selectedNode = n; + _selectedNode->canvasNode()->setSelected(true); + + if (!_isMoving) sNode = _selectedNode->canvasNode(); + } + if (e && e->canvasEdge()) { + _selectedEdge = e; + _selectedEdge->canvasEdge()->setSelected(true); + +#if 0 // don't change position when selecting edge + if (!_isMoving) { + if (_selectedEdge->fromNode()) + sNode = _selectedEdge->fromNode()->canvasNode(); + if (!sNode && _selectedEdge->toNode()) + sNode = _selectedEdge->toNode()->canvasNode(); + } +#endif + } + if (sNode) { + double x = sNode->x() + sNode->width()/2; + double y = sNode->y() + sNode->height()/2; + + ensureVisible(int(x),int(y), + sNode->width()/2+50, sNode->height()/2+50); + } + + _canvas->update(); + return; + } + + if (changeType == groupTypeChanged) { + if (!_canvas) return; + + if (_clusterGroups) { + refresh(); + return; + } + + TQCanvasItemList l = _canvas->allItems(); + TQCanvasItemList::iterator it; + for (it = l.begin();it != l.end(); ++it) + if ((*it)->rtti() == CANVAS_NODE) + ((CanvasNode*) (*it))->updateGroup(); + + _canvas->update(); + return; + } + + if (changeType & dataChanged) { + // invalidate old selection and graph part + _exporter.reset(_data, _activeItem, _costType, _groupType); + _selectedNode = 0; + _selectedEdge = 0; + } + + refresh(); +} + +void CallGraphView::clear() +{ + if (!_canvas) return; + + delete _canvas; + _canvas = 0; + _completeView->setCanvas(0); + setCanvas(0); +} + +void CallGraphView::showText(TQString s) +{ + clear(); + _renderTimer.stop(); + + _canvas = new TQCanvas(TQApplication::desktop()->width(), + TQApplication::desktop()->height()); + + TQCanvasText* t = new TQCanvasText(s, _canvas); + t->move(5, 5); + t->show(); + center(0,0); + setCanvas(_canvas); + _canvas->update(); + _completeView->hide(); +} + +void CallGraphView::showRenderWarning() +{ + TQString s; + + if (_renderProcess) + s =i18n("Warning: a long lasting graph layouting is in progress.\n" + "Reduce node/edge limits for speedup.\n"); + else + s = i18n("Layouting stopped.\n"); + + s.append(i18n("The call graph has %1 nodes and %2 edges.\n") + .arg(_exporter.nodeCount()) + .arg(_exporter.edgeCount())); + + showText(s); +} + +void CallGraphView::stopRendering() +{ + if (!_renderProcess) return; + + _renderProcess->kill(); + delete _renderProcess; + _renderProcess = 0; + _unparsedOutput = TQString(); + + _renderTimer.start(200, true); +} + +void CallGraphView::refresh() +{ + // trigger start of background rendering + if (_renderProcess) stopRendering(); + + // we want to keep a selected node item at the same global position + _prevSelectedNode = _selectedNode; + _prevSelectedPos = TQPoint(-1,-1); + if (_selectedNode) { + TQPoint center = _selectedNode->canvasNode()->boundingRect().center(); + _prevSelectedPos = contentsToViewport(center); + } + + if (!_data || !_activeItem) { + showText(i18n("No item activated for which to draw the call graph.")); + return; + } + + TraceItem::CostType t = _activeItem->type(); + switch(t) { + case TraceItem::Function: + case TraceItem::FunctionCycle: + case TraceItem::Call: + break; + default: + showText(i18n("No call graph can be drawn for the active item.")); + return; + } + + if (1) kdDebug() << "CallGraphView::refresh" << endl; + + _selectedNode = 0; + _selectedEdge = 0; + _exporter.reset(_data, _activeItem, _costType, _groupType); + _exporter.writeDot(); + + _renderProcess = new TQProcess(TQT_TQOBJECT(this)); + if (_layout == GraphOptions::Circular) + _renderProcess->addArgument( "twopi" ); + else + _renderProcess->addArgument( "dot" ); + _renderProcess->addArgument(_exporter.filename()); + _renderProcess->addArgument( "-Tplain" ); + + connect( _renderProcess, TQT_SIGNAL(readyReadStdout()), + this, TQT_SLOT(readDotOutput()) ); + connect( _renderProcess, TQT_SIGNAL(processExited()), + this, TQT_SLOT(dotExited()) ); + + if (1) kdDebug() << "Running '" + << _renderProcess->arguments().join(" ") + << "'..." << endl; + + if ( !_renderProcess->start() ) { + TQString e = i18n("No call graph is available because the following\n" + "command cannot be run:\n'%1'\n") + .arg(_renderProcess->arguments().join(" ")); + e += i18n("Please check that 'dot' is installed (package GraphViz)."); + showText(e); + + delete _renderProcess; + _renderProcess = 0; + + return; + } + + _unparsedOutput = TQString(); + + // layouting of more than seconds is dubious + _renderTimer.start(1000, true); +} + +void CallGraphView::readDotOutput() +{ + _unparsedOutput.append( _renderProcess->readStdout() ); +} + +void CallGraphView::dotExited() +{ + TQString line, cmd; + CanvasNode *rItem; + TQCanvasEllipse* eItem; + CanvasEdge* sItem; + CanvasEdgeLabel* lItem; + TQTextStream* dotStream; + double scale = 1.0, scaleX = 1.0, scaleY = 1.0; + double dotWidth, dotHeight; + GraphNode* activeNode = 0; + GraphEdge* activeEdge = 0; + + _renderTimer.stop(); + viewport()->setUpdatesEnabled(false); + clear(); + dotStream = new TQTextStream(_unparsedOutput, IO_ReadOnly); + + int lineno = 0; + while (1) { + line = dotStream->readLine(); + if (line.isNull()) break; + lineno++; + if (line.isEmpty()) continue; + + TQTextStream lineStream(line, IO_ReadOnly); + lineStream >> cmd; + + if (0) qDebug("%s:%d - line '%s', cmd '%s'", + _exporter.filename().ascii(), lineno, + line.ascii(), cmd.ascii()); + + if (cmd == "stop") break; + + if (cmd == "graph") { + TQString dotWidthString, dotHeightString; + lineStream >> scale >> dotWidthString >> dotHeightString; + dotWidth = dotWidthString.toDouble(); + dotHeight = dotHeightString.toDouble(); + + if (_detailLevel == 0) { scaleX = scale * 70; scaleY = scale * 40; } + else if (_detailLevel == 1) { scaleX = scale * 80; scaleY = scale * 70; } + else { scaleX = scale * 60; scaleY = scale * 100; } + + if (!_canvas) { + int w = (int)(scaleX * dotWidth); + int h = (int)(scaleY * dotHeight); + + // We use as minimum canvas size the desktop size. + // Otherwise, the canvas would have to be resized on widget resize. + _xMargin = 50; + if (w < TQApplication::desktop()->width()) + _xMargin += (TQApplication::desktop()->width()-w)/2; + + _yMargin = 50; + if (h < TQApplication::desktop()->height()) + _yMargin += (TQApplication::desktop()->height()-h)/2; + + _canvas = new TQCanvas(int(w+2*_xMargin), int(h+2*_yMargin)); + +#if DEBUG_GRAPH + kdDebug() << _exporter.filename().ascii() << ":" << lineno + << " - graph (" << dotWidth << " x " << dotHeight + << ") => (" << w << " x " << h << ")" << endl; +#endif + } + else + kdWarning() << "Ignoring 2nd 'graph' from dot (" + << _exporter.filename() << ":" << lineno << ")" << endl; + continue; + } + + if ((cmd != "node") && (cmd != "edge")) { + kdWarning() << "Ignoring unknown command '" << cmd << "' from dot (" + << _exporter.filename() << ":" << lineno << ")" << endl; + continue; + } + + if (_canvas == 0) { + kdWarning() << "Ignoring '" << cmd << "' without 'graph' from dot (" + << _exporter.filename() << ":" << lineno << ")" << endl; + continue; + } + + if (cmd == "node") { + // x, y are centered in node + TQString nodeName, label, nodeX, nodeY, nodeWidth, nodeHeight; + double x, y, width, height; + lineStream >> nodeName >> nodeX >> nodeY >> nodeWidth >> nodeHeight; + x = nodeX.toDouble(); + y = nodeY.toDouble(); + width = nodeWidth.toDouble(); + height = nodeHeight.toDouble(); + + GraphNode* n = _exporter.node(_exporter.toFunc(nodeName)); + + int xx = (int)(scaleX * x + _xMargin); + int yy = (int)(scaleY * (dotHeight - y) + _yMargin); + int w = (int)(scaleX * width); + int h = (int)(scaleY * height); + +#if DEBUG_GRAPH + kdDebug() << _exporter.filename() << ":" << lineno + << " - node '" << nodeName << "' ( " + << x << "/" << y << " - " + << width << "x" << height << " ) => (" + << xx-w/2 << "/" << yy-h/2 << " - " + << w << "x" << h << ")" << endl; +#endif + + + // Unnamed nodes with collapsed edges (with 'R' and 'S') + if (nodeName[0] == 'R' || nodeName[0] == 'S') { + w = 10, h = 10; + eItem = new TQCanvasEllipse(w, h, _canvas); + eItem->move(xx, yy); + eItem->setBrush(TQt::gray); + eItem->setZ(1.0); + eItem->show(); + continue; + } + + if (!n) { + qDebug("Warning: Unknown function '%s' ?!", nodeName.ascii()); + continue; + } + n->setVisible(true); + + rItem = new CanvasNode(this, n, xx-w/2, yy-h/2, w, h, _canvas); + n->setCanvasNode(rItem); + + if (n) { + if (n->function() == activeItem()) activeNode = n; + if (n->function() == selectedItem()) _selectedNode = n; + rItem->setSelected(n == _selectedNode); + } + + rItem->setZ(1.0); + rItem->show(); + + continue; + } + + // edge + + TQString node1Name, node2Name, label, edgeX, edgeY; + double x, y; + TQPointArray pa; + int points, i; + lineStream >> node1Name >> node2Name >> points; + + GraphEdge* e = _exporter.edge(_exporter.toFunc(node1Name), + _exporter.toFunc(node2Name)); + if (!e) { + kdWarning() << "Unknown edge '" << node1Name << "'-'" + << node2Name << "' from dot (" + << _exporter.filename() << ":" << lineno << ")" << endl; + continue; + } + e->setVisible(true); + if (e->fromNode()) e->fromNode()->callings.append(e); + if (e->toNode()) e->toNode()->callers.append(e); + + if (0) qDebug(" Edge with %d points:", points); + + pa.resize(points); + for (i=0;i<points;i++) { + if (lineStream.atEnd()) break; + lineStream >> edgeX >> edgeY; + x = edgeX.toDouble(); + y = edgeY.toDouble(); + + int xx = (int)(scaleX * x + _xMargin); + int yy = (int)(scaleY * (dotHeight - y) + _yMargin); + + if (0) qDebug(" P %d: ( %f / %f ) => ( %d / %d)", + i, x, y, xx, yy); + + pa.setPoint(i, xx, yy); + } + if (i < points) { + qDebug("CallGraphView: Can't read %d spline points (%s:%d)", + points, _exporter.filename().ascii(), lineno); + continue; + } + + // calls into/out of cycles are special: make them blue + TQColor arrowColor = TQt::black; + TraceFunction* caller = e->fromNode() ? e->fromNode()->function() : 0; + TraceFunction* called = e->toNode() ? e->toNode()->function() : 0; + if ( (caller && (caller->cycle() == caller)) || + (called && (called->cycle() == called)) ) arrowColor = TQt::blue; + + sItem = new CanvasEdge(e, _canvas); + e->setCanvasEdge(sItem); + sItem->setControlPoints(pa, false); + sItem->setPen(TQPen(arrowColor, 1 /*(int)log(log(e->cost))*/ )); + sItem->setZ(0.5); + sItem->show(); + + if (e->call() == selectedItem()) _selectedEdge = e; + if (e->call() == activeItem()) activeEdge = e; + sItem->setSelected(e == _selectedEdge); + + // Arrow head + TQPoint arrowDir; + int indexHead = -1; + + // check if head is at start of spline... + // this is needed because dot always gives points from top to bottom + CanvasNode* fromNode = e->fromNode() ? e->fromNode()->canvasNode() : 0; + if (fromNode) { + TQPoint toCenter = fromNode->rect().center(); + int dx0 = pa.point(0).x() - toCenter.x(); + int dy0 = pa.point(0).y() - toCenter.y(); + int dx1 = pa.point(points-1).x() - toCenter.x(); + int dy1 = pa.point(points-1).y() - toCenter.y(); + if (dx0*dx0+dy0*dy0 > dx1*dx1+dy1*dy1) { + // start of spline is nearer to call target node + indexHead=-1; + while(arrowDir.isNull() && (indexHead<points-2)) { + indexHead++; + arrowDir = pa.point(indexHead) - pa.point(indexHead+1); + } + } + } + + if (arrowDir.isNull()) { + indexHead = points; + // sometimes the last spline points from dot are the same... + while(arrowDir.isNull() && (indexHead>1)) { + indexHead--; + arrowDir = pa.point(indexHead) - pa.point(indexHead-1); + } + } + + if (!arrowDir.isNull()) { + // arrow around pa.point(indexHead) with direction arrowDir + arrowDir *= 10.0/sqrt(double(arrowDir.x()*arrowDir.x() + + arrowDir.y()*arrowDir.y())); + TQPointArray a(3); + a.setPoint(0, pa.point(indexHead) + arrowDir); + a.setPoint(1, pa.point(indexHead) + TQPoint(arrowDir.y()/2, + -arrowDir.x()/2)); + a.setPoint(2, pa.point(indexHead) + TQPoint(-arrowDir.y()/2, + arrowDir.x()/2)); + + if (0) qDebug(" Arrow: ( %d/%d, %d/%d, %d/%d)", + a.point(0).x(), a.point(0).y(), + a.point(1).x(), a.point(1).y(), + a.point(2).x(), a.point(2).y()); + + CanvasEdgeArrow* aItem = new CanvasEdgeArrow(sItem,_canvas); + aItem->setPoints(a); + aItem->setBrush(arrowColor); + aItem->setZ(1.5); + aItem->show(); + + sItem->setArrow(aItem); + } + + if (lineStream.atEnd()) continue; + + // parse quoted label + TQChar c; + lineStream >> c; + while (c.isSpace()) lineStream >> c; + if (c != '\"') { + lineStream >> label; + label = c + label; + } + else { + lineStream >> c; + while(!c.isNull() && (c != '\"')) { + //if (c == '\\') lineStream >> c; + + label += c; + lineStream >> c; + } + } + lineStream >> edgeX >> edgeY; + x = edgeX.toDouble(); + y = edgeY.toDouble(); + + int xx = (int)(scaleX * x + _xMargin); + int yy = (int)(scaleY * (dotHeight - y) + _yMargin); + + if (0) qDebug(" Label '%s': ( %f / %f ) => ( %d / %d)", + label.ascii(), x, y, xx, yy); + + // Fixed Dimensions for Label: 100 x 40 + int w = 100; + int h = _detailLevel * 20; + lItem = new CanvasEdgeLabel(this, sItem, xx-w/2, yy-h/2, w, h, _canvas); + // edge labels above nodes + lItem->setZ(1.5); + sItem->setLabel(lItem); + if (h>0) lItem->show(); + + } + delete dotStream; + + // for keyboard navigation + // TODO: Edge sorting. Better keep left-to-right edge order from dot now + // _exporter.sortEdges(); + + if (!_canvas) { + _canvas = new TQCanvas(size().width(),size().height()); + TQString s = i18n("Error running the graph layouting tool.\n"); + s += i18n("Please check that 'dot' is installed (package GraphViz)."); + TQCanvasText* t = new TQCanvasText(s, _canvas); + t->move(5, 5); + t->show(); + center(0,0); + } + else if (!activeNode && !activeEdge) { + TQString s = i18n("There is no call graph available for function\n" + "\t'%1'\n" + "because it has no cost of the selected event type."); + TQCanvasText* t = new TQCanvasText(s.arg(_activeItem->name()), _canvas); + // t->setTextFlags(TQt::AlignHCenter | TQt::AlignVCenter); + t->move(5,5); + t->show(); + center(0,0); + } + + _completeView->setCanvas(_canvas); + setCanvas(_canvas); + + // if we don't have a selection, or the old selection is not + // in visible graph, make active function selected for this view + if ((!_selectedNode || !_selectedNode->canvasNode()) && + (!_selectedEdge || !_selectedEdge->canvasEdge())) { + if (activeNode) { + _selectedNode = activeNode; + _selectedNode->canvasNode()->setSelected(true); + } + else if (activeEdge) { + _selectedEdge = activeEdge; + _selectedEdge->canvasEdge()->setSelected(true); + } + } + + CanvasNode* sNode = 0; + if (_selectedNode) + sNode = _selectedNode->canvasNode(); + else if (_selectedEdge) { + if (_selectedEdge->fromNode()) + sNode = _selectedEdge->fromNode()->canvasNode(); + if (!sNode && _selectedEdge->toNode()) + sNode = _selectedEdge->toNode()->canvasNode(); + } + if (sNode) { + int x = int(sNode->x() + sNode->width()/2); + int y = int(sNode->y() + sNode->height()/2); + + if (_prevSelectedNode) { + if (rect().contains(_prevSelectedPos)) + setContentsPos(x-_prevSelectedPos.x(), + y-_prevSelectedPos.y()); + else + ensureVisible(x,y, + sNode->width()/2+50, sNode->height()/2+50); + } + else center(x,y); + } + + if (activeNode) { + CanvasNode* cn = activeNode->canvasNode(); + CanvasFrame* f = new CanvasFrame(cn, _canvas); + f->setZ(-1); + f->show(); + } + + _cvZoom = 0; + updateSizes(); + + _canvas->update(); + viewport()->setUpdatesEnabled(true); + + delete _renderProcess; + _renderProcess = 0; +} + +void CallGraphView::contentsMovingSlot(int x, int y) +{ + TQRect z(int(x * _cvZoom), int(y * _cvZoom), + int(visibleWidth() * _cvZoom)-1, int(visibleHeight() * _cvZoom)-1); + if (0) qDebug("moving: (%d,%d) => (%d/%d - %dx%d)", + x, y, z.x(), z.y(), z.width(), z.height()); + _completeView->setZoomRect(z); +} + +void CallGraphView::zoomRectMoved(int dx, int dy) +{ + if (leftMargin()>0) dx = 0; + if (topMargin()>0) dy = 0; + scrollBy(int(dx/_cvZoom),int(dy/_cvZoom)); +} + +void CallGraphView::zoomRectMoveFinished() +{ + if (_zoomPosition == Auto) updateSizes(); +} + +void CallGraphView::contentsMousePressEvent(TQMouseEvent* e) +{ + // clicking on the viewport sets focus + setFocus(); + + _isMoving = true; + + TQCanvasItemList l = canvas()->collisions(e->pos()); + if (l.count()>0) { + TQCanvasItem* i = l.first(); + + if (i->rtti() == CANVAS_NODE) { + GraphNode* n = ((CanvasNode*)i)->node(); + if (0) qDebug("CallGraphView: Got Node '%s'", + n->function()->prettyName().ascii()); + + selected(n->function()); + } + + // redirect from label / arrow to edge + if (i->rtti() == CANVAS_EDGELABEL) + i = ((CanvasEdgeLabel*)i)->canvasEdge(); + if (i->rtti() == CANVAS_EDGEARROW) + i = ((CanvasEdgeArrow*)i)->canvasEdge(); + + if (i->rtti() == CANVAS_EDGE) { + GraphEdge* e = ((CanvasEdge*)i)->edge(); + if (0) qDebug("CallGraphView: Got Edge '%s'", + e->prettyName().ascii()); + + if (e->call()) selected(e->call()); + } + } + _lastPos = e->globalPos(); +} + +void CallGraphView::contentsMouseMoveEvent(TQMouseEvent* e) +{ + if (_isMoving) { + int dx = e->globalPos().x() - _lastPos.x(); + int dy = e->globalPos().y() - _lastPos.y(); + scrollBy(-dx, -dy); + _lastPos = e->globalPos(); + } +} + +void CallGraphView::contentsMouseReleaseEvent(TQMouseEvent*) +{ + _isMoving = false; + if (_zoomPosition == Auto) updateSizes(); +} + +void CallGraphView::contentsMouseDoubleClickEvent(TQMouseEvent* e) +{ + TQCanvasItemList l = canvas()->collisions(e->pos()); + if (l.count() == 0) return; + TQCanvasItem* i = l.first(); + + if (i->rtti() == CANVAS_NODE) { + GraphNode* n = ((CanvasNode*)i)->node(); + if (0) qDebug("CallGraphView: Double Clicked on Node '%s'", + n->function()->prettyName().ascii()); + + activated(n->function()); + } + + // redirect from label / arrow to edge + if (i->rtti() == CANVAS_EDGELABEL) + i = ((CanvasEdgeLabel*)i)->canvasEdge(); + if (i->rtti() == CANVAS_EDGEARROW) + i = ((CanvasEdgeArrow*)i)->canvasEdge(); + + if (i->rtti() == CANVAS_EDGE) { + GraphEdge* e = ((CanvasEdge*)i)->edge(); + if (e->call()) { + if (0) qDebug("CallGraphView: Double Clicked On Edge '%s'", + e->call()->prettyName().ascii()); + + activated(e->call()); + } + } +} + +void CallGraphView::contentsContextMenuEvent(TQContextMenuEvent* e) +{ + TQCanvasItemList l = canvas()->collisions(e->pos()); + TQCanvasItem* i = (l.count() == 0) ? 0 : l.first(); + + TQPopupMenu popup; + TraceFunction *f = 0, *cycle = 0; + TraceCall* c = 0; + + if (i) { + if (i->rtti() == CANVAS_NODE) { + GraphNode* n = ((CanvasNode*)i)->node(); + if (0) qDebug("CallGraphView: Menu on Node '%s'", + n->function()->prettyName().ascii()); + f = n->function(); + cycle = f->cycle(); + + TQString name = f->prettyName(); + popup.insertItem(i18n("Go to '%1'") + .arg(Configuration::shortenSymbol(name)), 93); + if (cycle && (cycle != f)) { + name = Configuration::shortenSymbol(cycle->prettyName()); + popup.insertItem(i18n("Go to '%1'").arg(name), 94); + } + popup.insertSeparator(); + } + + // redirect from label / arrow to edge + if (i->rtti() == CANVAS_EDGELABEL) + i = ((CanvasEdgeLabel*)i)->canvasEdge(); + if (i->rtti() == CANVAS_EDGEARROW) + i = ((CanvasEdgeArrow*)i)->canvasEdge(); + + if (i->rtti() == CANVAS_EDGE) { + GraphEdge* e = ((CanvasEdge*)i)->edge(); + if (0) qDebug("CallGraphView: Menu on Edge '%s'", + e->prettyName().ascii()); + c = e->call(); + if (c) { + TQString name = c->prettyName(); + popup.insertItem(i18n("Go to '%1'") + .arg(Configuration::shortenSymbol(name)), 95); + + popup.insertSeparator(); + } + } + } + + if (_renderProcess) { + popup.insertItem(i18n("Stop Layouting"), 999); + popup.insertSeparator(); + } + + addGoMenu(&popup); + popup.insertSeparator(); + + TQPopupMenu epopup; + epopup.insertItem(i18n("As PostScript"), 201); + epopup.insertItem(i18n("As Image ..."), 202); + + popup.insertItem(i18n("Export Graph"), &epopup, 200); + popup.insertSeparator(); + + TQPopupMenu gpopup1; + gpopup1.setCheckable(true); + gpopup1.insertItem(i18n("Unlimited"), 100); + gpopup1.setItemEnabled(100, (_funcLimit>0.005)); + gpopup1.insertSeparator(); + gpopup1.insertItem(i18n("None"), 101); + gpopup1.insertItem(i18n("max. 2"), 102); + gpopup1.insertItem(i18n("max. 5"), 103); + gpopup1.insertItem(i18n("max. 10"), 104); + gpopup1.insertItem(i18n("max. 15"), 105); + if (_maxCallerDepth<-1) _maxCallerDepth=-1; + switch(_maxCallerDepth) { + case -1: gpopup1.setItemChecked(100,true); break; + case 0: gpopup1.setItemChecked(101,true); break; + case 2: gpopup1.setItemChecked(102,true); break; + case 5: gpopup1.setItemChecked(103,true); break; + case 10: gpopup1.setItemChecked(104,true); break; + case 15: gpopup1.setItemChecked(105,true); break; + default: + gpopup1.insertItem(i18n("< %1").arg(_maxCallerDepth), 106); + gpopup1.setItemChecked(106,true); break; + } + + TQPopupMenu gpopup2; + gpopup2.setCheckable(true); + gpopup2.insertItem(i18n("Unlimited"), 110); + gpopup2.setItemEnabled(110, (_funcLimit>0.005)); + gpopup2.insertSeparator(); + gpopup2.insertItem(i18n("None"), 111); + gpopup2.insertItem(i18n("max. 2"), 112); + gpopup2.insertItem(i18n("max. 5"), 113); + gpopup2.insertItem(i18n("max. 10"), 114); + gpopup2.insertItem(i18n("max. 15"), 115); + if (_maxCallingDepth<-1) _maxCallingDepth=-1; + switch(_maxCallingDepth) { + case -1: gpopup2.setItemChecked(110,true); break; + case 0: gpopup2.setItemChecked(111,true); break; + case 2: gpopup2.setItemChecked(112,true); break; + case 5: gpopup2.setItemChecked(113,true); break; + case 10: gpopup2.setItemChecked(114,true); break; + case 15: gpopup2.setItemChecked(115,true); break; + default: + gpopup2.insertItem(i18n("< %1").arg(_maxCallingDepth), 116); + gpopup2.setItemChecked(116,true); break; + } + + TQPopupMenu gpopup3; + gpopup3.setCheckable(true); + gpopup3.insertItem(i18n("No Minimum"), 120); + gpopup3.setItemEnabled(120, + (_maxCallerDepth>=0) && (_maxCallingDepth>=0)); + gpopup3.insertSeparator(); + gpopup3.insertItem(i18n("50 %"), 121); + gpopup3.insertItem(i18n("20 %"), 122); + gpopup3.insertItem(i18n("10 %"), 123); + gpopup3.insertItem(i18n("5 %"), 124); + gpopup3.insertItem(i18n("3 %"), 125); + gpopup3.insertItem(i18n("2 %"), 126); + gpopup3.insertItem(i18n("1.5 %"), 127); + gpopup3.insertItem(i18n("1 %"), 128); + if (_funcLimit<0) _funcLimit = DEFAULT_FUNCLIMIT; + if (_funcLimit>.5) _funcLimit = .5; + if (_funcLimit == 0.0) gpopup3.setItemChecked(120,true); + else if (_funcLimit >= 0.5) gpopup3.setItemChecked(121,true); + else if (_funcLimit >= 0.2) gpopup3.setItemChecked(122,true); + else if (_funcLimit >= 0.1) gpopup3.setItemChecked(123,true); + else if (_funcLimit >= 0.05) gpopup3.setItemChecked(124,true); + else if (_funcLimit >= 0.03) gpopup3.setItemChecked(125,true); + else if (_funcLimit >= 0.02) gpopup3.setItemChecked(126,true); + else if (_funcLimit >= 0.015) gpopup3.setItemChecked(127,true); + else gpopup3.setItemChecked(128,true); + double oldFuncLimit = _funcLimit; + + TQPopupMenu gpopup4; + gpopup4.setCheckable(true); + gpopup4.insertItem(i18n("Same as Node"), 160); + gpopup4.insertItem(i18n("50 % of Node"), 161); + gpopup4.insertItem(i18n("20 % of Node"), 162); + gpopup4.insertItem(i18n("10 % of Node"), 163); + if (_callLimit<0) _callLimit = DEFAULT_CALLLIMIT; + if (_callLimit >= _funcLimit) _callLimit = _funcLimit; + if (_callLimit == _funcLimit) gpopup4.setItemChecked(160,true); + else if (_callLimit >= 0.5 * _funcLimit) gpopup4.setItemChecked(161,true); + else if (_callLimit >= 0.2 * _funcLimit) gpopup4.setItemChecked(162,true); + else gpopup4.setItemChecked(163,true); + + TQPopupMenu gpopup; + gpopup.setCheckable(true); + gpopup.insertItem(i18n("Caller Depth"), &gpopup1, 80); + gpopup.insertItem(i18n("Callee Depth"), &gpopup2, 81); + gpopup.insertItem(i18n("Min. Node Cost"), &gpopup3, 82); + gpopup.insertItem(i18n("Min. Call Cost"), &gpopup4, 83); + gpopup.insertSeparator(); + gpopup.insertItem(i18n("Arrows for Skipped Calls"), 130); + gpopup.setItemChecked(130,_showSkipped); + gpopup.insertItem(i18n("Inner-cycle Calls"), 131); + gpopup.setItemChecked(131,_expandCycles); + gpopup.insertItem(i18n("Cluster Groups"), 132); + gpopup.setItemChecked(132,_clusterGroups); + + TQPopupMenu vpopup; + vpopup.setCheckable(true); + vpopup.insertItem(i18n("Compact"), 140); + vpopup.insertItem(i18n("Normal"), 141); + vpopup.insertItem(i18n("Tall"), 142); + vpopup.setItemChecked(140,_detailLevel == 0); + vpopup.setItemChecked(141,_detailLevel == 1); + vpopup.setItemChecked(142,_detailLevel == 2); + vpopup.insertSeparator(); + vpopup.insertItem(i18n("Top to Down"), 150); + vpopup.insertItem(i18n("Left to Right"), 151); + vpopup.insertItem(i18n("Circular"), 152); + vpopup.setItemChecked(150,_layout == TopDown); + vpopup.setItemChecked(151,_layout == LeftRight); + vpopup.setItemChecked(152,_layout == Circular); + + TQPopupMenu opopup; + opopup.insertItem(i18n("TopLeft"), 170); + opopup.insertItem(i18n("TopRight"), 171); + opopup.insertItem(i18n("BottomLeft"), 172); + opopup.insertItem(i18n("BottomRight"), 173); + opopup.insertItem(i18n("Automatic"), 174); + opopup.setItemChecked(170,_zoomPosition == TopLeft); + opopup.setItemChecked(171,_zoomPosition == TopRight); + opopup.setItemChecked(172,_zoomPosition == BottomLeft); + opopup.setItemChecked(173,_zoomPosition == BottomRight); + opopup.setItemChecked(174,_zoomPosition == Auto); + + popup.insertItem(i18n("Graph"), &gpopup, 70); + popup.insertItem(i18n("Visualization"), &vpopup, 71); + popup.insertItem(i18n("Birds-eye View"), &opopup, 72); + + int r = popup.exec(e->globalPos()); + + switch(r) { + case 93: activated(f); break; + case 94: activated(cycle); break; + case 95: activated(c); break; + + case 999: stopRendering(); break; + + case 201: + { + TraceFunction* f = activeFunction(); + if (!f) break; + + GraphExporter ge(TraceItemView::data(), f, costType(), groupType(), + TQString("callgraph.dot")); + ge.setGraphOptions(this); + ge.writeDot(); + + system("(dot callgraph.dot -Tps > callgraph.ps; kghostview callgraph.ps)&"); + } + break; + + case 202: + // write current content of canvas as image to file + { + if (!_canvas) return; + + TQString fn = KFileDialog::getSaveFileName(":","*.png"); + + if (!fn.isEmpty()) { + TQPixmap pix(_canvas->size()); + TQPainter p(&pix); + _canvas->drawArea( _canvas->rect(), &p ); + pix.save(fn,"PNG"); + } + } + break; + + case 100: _maxCallerDepth = -1; break; + case 101: _maxCallerDepth = 0; break; + case 102: _maxCallerDepth = 2; break; + case 103: _maxCallerDepth = 5; break; + case 104: _maxCallerDepth = 10; break; + case 105: _maxCallerDepth = 15; break; + + case 110: _maxCallingDepth = -1; break; + case 111: _maxCallingDepth = 0; break; + case 112: _maxCallingDepth = 2; break; + case 113: _maxCallingDepth = 5; break; + case 114: _maxCallingDepth = 10; break; + case 115: _maxCallingDepth = 15; break; + + case 120: _funcLimit = 0; break; + case 121: _funcLimit = 0.5; break; + case 122: _funcLimit = 0.2; break; + case 123: _funcLimit = 0.1; break; + case 124: _funcLimit = 0.05; break; + case 125: _funcLimit = 0.03; break; + case 126: _funcLimit = 0.02; break; + case 127: _funcLimit = 0.015; break; + case 128: _funcLimit = 0.01; break; + + case 130: _showSkipped = !_showSkipped; break; + case 131: _expandCycles = !_expandCycles; break; + case 132: _clusterGroups = !_clusterGroups; break; + + case 140: _detailLevel = 0; break; + case 141: _detailLevel = 1; break; + case 142: _detailLevel = 2; break; + + case 150: _layout = TopDown; break; + case 151: _layout = LeftRight; break; + case 152: _layout = Circular; break; + + case 160: _callLimit = _funcLimit; break; + case 161: _callLimit = .5 * _funcLimit; break; + case 162: _callLimit = .2 * _funcLimit; break; + case 163: _callLimit = .1 * _funcLimit; break; + + case 170: _zoomPosition = TopLeft; break; + case 171: _zoomPosition = TopRight; break; + case 172: _zoomPosition = BottomLeft; break; + case 173: _zoomPosition = BottomRight; break; + case 174: _zoomPosition = Auto; break; + + default: break; + } + if (r>=120 && r<130) _callLimit *= _funcLimit / oldFuncLimit; + + if (r>99 && r<170) refresh(); + if (r>169 && r<180) updateSizes(); +} + +CallGraphView::ZoomPosition CallGraphView::zoomPos(TQString s) +{ + if (s == TQString("TopLeft")) return TopLeft; + if (s == TQString("TopRight")) return TopRight; + if (s == TQString("BottomLeft")) return BottomLeft; + if (s == TQString("BottomRight")) return BottomRight; + if (s == TQString("Automatic")) return Auto; + + return DEFAULT_ZOOMPOS; +} + +TQString CallGraphView::zoomPosString(ZoomPosition p) +{ + if (p == TopRight) return TQString("TopRight"); + if (p == BottomLeft) return TQString("BottomLeft"); + if (p == BottomRight) return TQString("BottomRight"); + if (p == Auto) return TQString("Automatic"); + + return TQString("TopLeft"); +} + +void CallGraphView::readViewConfig(KConfig* c, + TQString prefix, TQString postfix, bool) +{ + KConfigGroup* g = configGroup(c, prefix, postfix); + + if (0) qDebug("CallGraphView::readViewConfig"); + + _maxCallerDepth = g->readNumEntry("MaxCaller", DEFAULT_MAXCALLER); + _maxCallingDepth = g->readNumEntry("MaxCalling", DEFAULT_MAXCALLING); + _funcLimit = g->readDoubleNumEntry("FuncLimit", DEFAULT_FUNCLIMIT); + _callLimit = g->readDoubleNumEntry("CallLimit", DEFAULT_CALLLIMIT); + _showSkipped = g->readBoolEntry("ShowSkipped", DEFAULT_SHOWSKIPPED); + _expandCycles = g->readBoolEntry("ExpandCycles", DEFAULT_EXPANDCYCLES); + _clusterGroups = g->readBoolEntry("ClusterGroups", + DEFAULT_CLUSTERGROUPS); + _detailLevel = g->readNumEntry("DetailLevel", DEFAULT_DETAILLEVEL); + _layout = GraphOptions::layout(g->readEntry("Layout", + layoutString(DEFAULT_LAYOUT))); + _zoomPosition = zoomPos(g->readEntry("ZoomPosition", + zoomPosString(DEFAULT_ZOOMPOS))); + + delete g; +} + +void CallGraphView::saveViewConfig(KConfig* c, + TQString prefix, TQString postfix, bool) +{ + KConfigGroup g(c, (prefix+postfix).ascii()); + + writeConfigEntry(&g, "MaxCaller", _maxCallerDepth, DEFAULT_MAXCALLER); + writeConfigEntry(&g, "MaxCalling", _maxCallingDepth, DEFAULT_MAXCALLING); + writeConfigEntry(&g, "FuncLimit", _funcLimit, DEFAULT_FUNCLIMIT); + writeConfigEntry(&g, "CallLimit", _callLimit, DEFAULT_CALLLIMIT); + writeConfigEntry(&g, "ShowSkipped", _showSkipped, DEFAULT_SHOWSKIPPED); + writeConfigEntry(&g, "ExpandCycles", _expandCycles, DEFAULT_EXPANDCYCLES); + writeConfigEntry(&g, "ClusterGroups", _clusterGroups, + DEFAULT_CLUSTERGROUPS); + writeConfigEntry(&g, "DetailLevel", _detailLevel, DEFAULT_DETAILLEVEL); + writeConfigEntry(&g, "Layout", + layoutString(_layout), layoutString(DEFAULT_LAYOUT).utf8().data()); + writeConfigEntry(&g, "ZoomPosition", + zoomPosString(_zoomPosition), + zoomPosString(DEFAULT_ZOOMPOS).utf8().data()); +} + +#include "callgraphview.moc" + |