You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2735 lines
74 KiB
C++
2735 lines
74 KiB
C++
/* 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 <tdelocale.h>
|
|
#include <tdeconfig.h>
|
|
#include <tdetempfile.h>
|
|
#include <tdeapplication.h>
|
|
#include <kiconloader.h>
|
|
#include <tdefiledialog.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) tqDebug("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) tqDebug("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) tqDebug(" 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) tqDebug(" 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) tqDebug(" 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) tqDebug(" 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 or LL 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) tqDebug(" 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) tqDebug(" 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 = "edit-undo";
|
|
TDEIconLoader* loader = TDEApplication::kApplication()->iconLoader();
|
|
TQPixmap p= loader->loadIcon(icon, TDEIcon::Small, 0,
|
|
TDEIcon::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) tqDebug("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) tqDebug(" 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) {
|
|
tqDebug(" Result:");
|
|
for (i=0;i<2*len;i++)
|
|
tqDebug(" 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) tqDebug("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) tqDebug("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) tqDebug("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) tqDebug("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) tqDebug("%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) {
|
|
tqDebug("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) tqDebug(" 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) tqDebug(" P %d: ( %f / %f ) => ( %d / %d)",
|
|
i, x, y, xx, yy);
|
|
|
|
pa.setPoint(i, xx, yy);
|
|
}
|
|
if (i < points) {
|
|
tqDebug("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) tqDebug(" 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) tqDebug(" 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) tqDebug("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) tqDebug("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) tqDebug("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) tqDebug("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) tqDebug("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) tqDebug("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) tqDebug("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(TDEConfig* c,
|
|
TQString prefix, TQString postfix, bool)
|
|
{
|
|
TDEConfigGroup* g = configGroup(c, prefix, postfix);
|
|
|
|
if (0) tqDebug("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(TDEConfig* c,
|
|
TQString prefix, TQString postfix, bool)
|
|
{
|
|
TDEConfigGroup 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"
|
|
|