/* 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"

