/***************************************************************************
 *
 * Copyright (C) 2005 Elad Lahav (elad_lahav@users.sourceforge.net)
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 ***************************************************************************/

#include <math.h>
#include <stdlib.h>
#include <qfile.h>
#include <qpainter.h>
#include <qtooltip.h>
#include <klocale.h>
#include <kmessagebox.h>
#include "graphwidget.h"
#include "graphnode.h"
#include "graphedge.h"
#include "kscopeconfig.h"
#include "queryviewdlg.h"
#include "encoder.h"
#include "progressdlg.h"

const char* GRAPH_DIRS[] = { "TB", "LR", "BT", "RL" };

const char TMP_TMPL[] = "/tmp/kscope_dot.XXXXXX";
#define TMP_TMPL_SIZE	(sizeof(TMP_TMPL) + 1)

/**
 * Displays a tool tip on the graph.
 * Note that we cannot use the standard tool tip class here, since graph
 * items are neither rectangular nor is their position known in advance.
 * @author	Elad Lahav
 */
class GraphTip : public QToolTip
{
public:
	/**
	 * Class constructor.
	 * @param	pWidget	Owner graph widget
	 */
	GraphTip(GraphWidget* pWidget) : QToolTip(pWidget->viewport()),
		m_pGraphWidget(pWidget) {}
	
	/**
	 * Class destructor.
	 */
	virtual ~GraphTip() {}
	
protected:
	/**
	 * Called when the pre-conditions for a tool tip are met.
	 * Asks the owner for a tip to display and, if one is returned, shows
	 * the tool tip.
	 * @param	ptPos	Current mouse position
	 */
	virtual void maybeTip(const QPoint& ptPos) {
		QString sText;
		QRect rc;
		
		// Display a tip, if required by the owner
		sText = m_pGraphWidget->getTip(ptPos, rc);
		if (sText != QString::null)
			tip(rc, sText);
	}
	
private:
	/** The parent graph widget. */
	GraphWidget* m_pGraphWidget;
};

/**
 * Provides a menu separator with text.
 * The separator is added with QMenuData::insertItem(QWidget*).
 * @author Elad Lahav
 */
class MenuLabel : public QLabel
{
public:
	/**
	 * Class constructor.
	 * @param	sText	The text to display
	 * @param	pParent	The parent widget
	 */
	MenuLabel(const QString& sText, QWidget* pParent) :
		QLabel(sText, pParent) {
		// Set the appropriate visual properties
		setFrameShape(MenuBarPanel);
		setAlignment(AlignHCenter | AlignVCenter);
		setIndent(0);
	}
};

ArrowInfo GraphWidget::s_ai;

/**
 * Class constructor.
 * @param	pParent	The parent widget
 * @param	szName	The widget's name
 */
GraphWidget::GraphWidget(QWidget* pParent, const char* szName) :
	QCanvasView(pParent, szName),
	m_progress(this),
	m_dot(this),
	m_dZoom(1.0),
	m_nMaxNodeDegree(10), // will be overriden by CallTreeDlg
	m_nMultiCallNum(0),
	m_pProgressDlg(NULL)					 
{
	// Automatically delete nodes when they are removed
	m_dictNodes.setAutoDelete(true);
	
	// Create a canvas
	setCanvas(new QCanvas(this));
	canvas()->setBackgroundColor(Config().getColor(KScopeConfig::GraphBack));

	// Create a persistent Cscope process
	m_pCscope = new CscopeFrontend();
	
	// Add records output by the Cscope process
	connect(m_pCscope, SIGNAL(dataReady(FrontendToken*)), this,
		SLOT(slotDataReady(FrontendToken*)));
	
	// Display query progress information
	connect(m_pCscope, SIGNAL(progress(int, int)), this,
		SLOT(slotProgress(int, int)));	
		
	// Draw the graph when the process has finished
	connect(m_pCscope, SIGNAL(finished(uint)), this,
		SLOT(slotFinished(uint)));
	
	// Show a multi-call node when a query results in too many records
	connect(m_pCscope, SIGNAL(aborted()), this,
		SLOT(slotAborted()));
	
	// Redraw the graph when Dot exits
	connect(&m_dot, SIGNAL(finished(uint)), this, SLOT(slotDotFinished()));	
	
	// Create the node popup menu
	m_pNodePopup = new QPopupMenu(this);
	
	m_pNodePopup->insertItem(new MenuLabel(i18n("<b>Called Functions</b>"),
		m_pNodePopup));
	m_pNodePopup->insertItem(i18n("Show"), this,
		SLOT(slotShowCalled()));
	m_pNodePopup->insertItem(i18n("List/Filter..."), this,
		SLOT(slotListCalled()));
	m_pNodePopup->insertItem(i18n("Hide"), this,
		SLOT(slotHideCalled()));
	
	m_pNodePopup->insertItem(new MenuLabel(i18n("<b>Calling Functions</b>"),
		m_pNodePopup));
	m_pNodePopup->insertItem(i18n("Show"), this,
		SLOT(slotShowCalling()));
	m_pNodePopup->insertItem(i18n("List/Filter..."), this,
		SLOT(slotListCalling()));
	m_pNodePopup->insertItem(i18n("Hide"), this,
		SLOT(slotHideCalling()));
	
	m_pNodePopup->insertItem(new MenuLabel(i18n("<b>This Function</b>"),
		m_pNodePopup));
	m_pNodePopup->insertItem(i18n("Find Definition"), this,
		SLOT(slotFindDef()));
	m_pNodePopup->insertItem(i18n("Remove"), this, SLOT(slotRemoveNode()));
		
	// Create the multi-call node popup menu
	m_pMultiCallPopup = new QPopupMenu(this);
	m_pMultiCallPopup->insertItem(i18n("List..."), this,
		SLOT(slotMultiCallDetails()));
	m_pMultiCallPopup->insertSeparator();
	m_pMultiCallPopup->insertItem(i18n("Remove"), this,
		SLOT(slotRemoveNode()));
	
	// Create the edge menu
	m_pEdgePopup = new QPopupMenu(this);
	m_pEdgePopup->insertItem(i18n("Open Call"), this, SLOT(slotOpenCall()));
		
	(void)new GraphTip(this);
}

/**
 * Class destructor.
 */
GraphWidget::~GraphWidget()
{
}

/**
 * Creates a root node for the graph.
 * The root node defines the connected component which is always displayed
 * (all other connected components are removed when they are no longer
 * strongly connected to the root).
 * @param	sFunc	The function name for the root node
 */
void GraphWidget::setRoot(const QString& sFunc)
{
	// Insert a new node to the graph
	addNode(sFunc);
	draw();
}

/**
 * Locates a node by its name and, if one does not exist, creates a new node.
 * @param	sFunc	The name of a function
 * @return	The node corresponding to the given name
 */
GraphNode* GraphWidget::addNode(const QString& sFunc, bool bMultiCall)
{
	GraphNode* pNode;
	
	// Look for a node with the given name
	if ((pNode = m_dictNodes.find(sFunc)) == NULL) {
		// Node not found, create it
		pNode = new GraphNode(canvas(), sFunc, bMultiCall);
		m_dictNodes.insert(sFunc, pNode);
	}
	
	// Return the found/created node
	return pNode;
}

/**
 * Adds a call to the graph.
 * A call is made between two functions, the caller and the callee.
 * @param	data	Contains information on the call
 */
void GraphWidget::addCall(const CallData& data)
{
	GraphNode* pCaller, * pCallee;
	GraphEdge* pEdge;
	
	// Find the relevant nodes (create new nodes if necessary)
	pCaller = addNode(data.m_sCaller);
	pCallee = addNode(data.m_sCallee);
	
	// Create a new edge
	pEdge = pCaller->addOutEdge(pCallee);
	pEdge->setCallInfo(data.m_sFile, data.m_sLine, data.m_sText);
}

/**
 * Creates a special node representing multiple calls to/from a function.
 * Such a node is creates when the number of calls to/from a function exceeds
 * a certain number. Thus the graph does not become too cluttered.
 * A multiple call node can be replaced by some/all of the actual calls by
 * using the "Details..." action in the node's popup menu.
 * @param	sFunc	The parent function
 * @param	bCalled	true if the multiple calls are called from that function,
 *					false if they are calling the function
 */
void GraphWidget::addMultiCall(const QString& sFunc, bool bCalled)
{
	QString sMulti;
	GraphNode* pCaller, * pCallee;
	GraphEdge* pEdge;
	
	// Create a unique name for the new node.
	// The name is of the form 0XXX, where XXX is a hexadecimal number. 
	// We assume that no function starts with a digit, and that there are no
	// more than 0xfff multi-call nodes in the graph.
	sMulti.sprintf("0%.3x", m_nMultiCallNum);
	m_nMultiCallNum = (m_nMultiCallNum + 1) & 0xfff;
	
	// Find the relevant nodes (create new nodes if necessary)
	if (bCalled) {
		pCaller = addNode(sFunc);
		pCallee = addNode(sMulti, true);
	}
	else {
		pCaller = addNode(sMulti, true);
		pCallee = addNode(sFunc);
	}
	
	// Create a new edge
	pEdge = pCaller->addOutEdge(pCallee);
}

/**
 * Draws the graph on the canvas using the graphviz engine.
 * A new canvas is created, so all items need to be regenerated.
 * TODO: Can we use the same canvas and only reposition existing items?
 */
void GraphWidget::draw()
{
	QWMatrix mtx;
	char szTempFile[TMP_TMPL_SIZE];
	int nFd;
	FILE* pFile;
	
	// Do nothing if drawing process has already started
	if (m_dot.isRunning())
		return;
	
	// Apply the zoom factor
	mtx.scale(m_dZoom, m_dZoom);
	setWorldMatrix(mtx);

	// Do not draw until the Dot process finishes
	setUpdatesEnabled(false);

	// Open a temporary file
	strcpy(szTempFile, TMP_TMPL);
	nFd = mkstemp(szTempFile);
	if ((pFile = fdopen(nFd, "w")) == NULL)
		return;
	
	// Remember the file name (so it can be deleted later)
	m_sDrawFilePath = szTempFile;
	
	// Write the graph contents to the temporary file
	{
		QTextStream str(pFile, IO_WriteOnly);
		write(str, "graph", "--", false);
	}
	
	// Close the file
	fclose(pFile);
	
	// Draw the graph
	if (m_dot.run(szTempFile)) {
		// Create the progress dialogue
		m_pProgressDlg = new ProgressDlg(i18n("KScope"), 
			i18n("Generating graph, please wait"), this);
		m_pProgressDlg->setMinimumDuration(1000);
		m_pProgressDlg->setValue(0);
		
		// TODO:
		// Implement cancel (what do we do when the drawing process is 
		// terminated, even though the nodes and edges were already added by
		// Cscope?)
		// m_pProgressDlg->setAllowCancel(true);
	}
}

/**
 * Stores a graph on a file.
 * The file uses the dot language to describe the graph.
 * @param	pFile	An open file to write to
 */
void GraphWidget::save(FILE* pFile)
{
	// Write the graph using the dot language
	QTextStream str(pFile, IO_WriteOnly);
	write(str, "digraph", "->", true);
}

/**
 * Exports a graph to a dot file.
 * @param	sFile	The full path of the file to export to
 */
void GraphWidget::save(const QString& sFile)
{
	QFile file(sFile);
	
	// Open/create the file
	if (!file.open(IO_WriteOnly))
		return;
		
	QTextStream str(&file);
	write(str, "digraph", "->", false);
}

/**
 * Changes the zoom factor.
 * @param	bIn	true to zoom in, false to zoom out
 */
void GraphWidget::zoom(bool bIn)
{
	QWMatrix mtx;
	
	// Set the new zoom factor
	if (bIn)
		m_dZoom *= 2.0;
	else
		m_dZoom /= 2.0;
		
	// Apply the transformation matrix
	mtx.scale(m_dZoom, m_dZoom);
	setWorldMatrix(mtx);
}

/**
 * Determines the initial zoom factor.
 * This method is called from the file parser and therefore does not redraw
 * the widget.
 * @param	dZoom	The zoom factor to use
 */
void GraphWidget::setZoom(double dZoom)
{
	m_dZoom = dZoom;
}

/**
 * Changes the graph's direction 90 degrees counter-clockwise.
 */
void GraphWidget::rotate()
{
	QString sDir;
	int i;
	
	// Get the current direction
	sDir = Config().getGraphOrientation();
	
	// Find the next direction
	for (i = 0; i < 4 && sDir != GRAPH_DIRS[i]; i++);
	if (i == 4)
		i = 0;
	else
		i = (i + 1) % 4;

	// Set the new direction
	sDir = GRAPH_DIRS[i];
	Config().setGraphOrientation(sDir);
}

/**
 * Checks if a tool tip is required for the given position.
 * NOTE: We currently return a tool tip for edges only
 * @param	ptPos	The position to query
 * @param	rc		Holds the tip's rectangle, upon return
 * @return	The tip's text, or QString::null if no tip is required
 */
QString GraphWidget::getTip(const QPoint& ptPos, QRect& rc)
{
	QPoint ptRealPos, ptTopLeft, ptBottomRight;
	QCanvasItemList il;
	QCanvasItemList::Iterator itr;
	GraphEdge* pEdge;
	QString sText, sFile, sLine;
	
	ptRealPos = viewportToContents(ptPos);
	ptRealPos /= m_dZoom;
	pEdge = NULL;
	
	// Check if there is an edge at this position
	il = canvas()->collisions(ptRealPos);
	for (itr = il.begin(); itr != il.end(); ++itr) {
		pEdge = dynamic_cast<GraphEdge*>(*itr);
		if (pEdge != NULL)
			break;
	}
	
	// No tip if no edge was found
	if (pEdge == NULL)
		return QString::null;
	
	// Set the rectangle for the tip (the tip is closed when the mouse leaves
	// this area)
	rc = pEdge->tipRect();
	ptTopLeft = rc.topLeft();
	ptBottomRight = rc.bottomRight();
	ptTopLeft *= m_dZoom;	
	ptBottomRight *= m_dZoom;
	ptTopLeft = contentsToViewport(ptTopLeft);
	ptBottomRight = contentsToViewport(ptBottomRight);
	rc = QRect(ptTopLeft, ptBottomRight);
	
	// Create a tip for this edge
	return pEdge->getTip();
}

/**
 * Resizes the canvas.
 * @param	nWidth	The new width
 * @param	nHiehgt	The new height
 */
void GraphWidget::resize(int nWidth, int nHeight)
{
	canvas()->resize(nWidth + 2, nHeight + 2);
}

/**
 * Displays a node on the canvas.
 * Sets the parameters used for drawing the node on the canvas.
 * @param	sFunc	The function corresponding to the node to draw
 * @param	rect	The coordinates of the node's rectangle
 */
void GraphWidget::drawNode(const QString& sFunc, const QRect& rect)
{
	GraphNode* pNode;
	
	// Find the node
	pNode = addNode(sFunc);
	
	// Set the visual aspects of the node
	pNode->setRect(rect);
	pNode->setZ(2.0);
	pNode->setPen(QPen(Qt::black));
	pNode->setFont(Config().getFont(KScopeConfig::Graph));
	
	if (pNode->isMultiCall())
		pNode->setBrush(Config().getColor(KScopeConfig::GraphMultiCall));
	else
		pNode->setBrush(Config().getColor(KScopeConfig::GraphNode));
	
	// Draw the node
	pNode->show();
}

/**
 * Displays an edge on the canvas.
 * Sets the parameters used for drawing the edge on the canvas.
 * @param	sCaller		Identifies the edge's head node
 * @param	sCallee		Identifies the edge's tail node
 * @param	arrCurve	Control points for the edge's spline
 */
void GraphWidget::drawEdge(const QString& sCaller, const QString& sCallee,
	const QPointArray& arrCurve)
{
	GraphNode* pCaller, * pCallee;
	GraphEdge* pEdge;
	
	// Find the edge
	pCaller = addNode(sCaller);
	pCallee = addNode(sCallee);
	pEdge = pCaller->addOutEdge(pCallee);
	
	// Set the visual aspects of the edge
	pEdge->setPoints(arrCurve, s_ai);
	pEdge->setZ(1.0);
	pEdge->setPen(QPen(Qt::black));
	pEdge->setBrush(QBrush(Qt::black));
	
	// Draw the edge
	pEdge->show();
}

#define PI 3.14159265

/**
 * Sets and computes values used for drawing arrows.
 * Initialises the static ArroInfo structure, which is passed in drawEdge().
 * @param	nLength		The arrow head length
 * @param	nDegrees	The angle, in degrees, between the base line and each
 *						of the arrow head's sides
 */
void GraphWidget::setArrowInfo(int nLength, int nDegrees)
{
	double dRad;
	
	// Turn degrees into radians
	dRad = ((double)nDegrees) * PI / 180.0;
	
	s_ai.m_dLength = (double)nLength;
	s_ai.m_dTan = tan(dRad);
	s_ai.m_dSqrt = sqrt(1 + s_ai.m_dTan * s_ai.m_dTan);
}

/**
 * Draws the contents of the canvas on this view.
 * NOTE: This method is overriden to fix a strange bug in Qt that leaves
 * a border around the canvas part of the view. It should be deleted once
 * this bug is fixed.
 * TODO: Is there a better way of erasing the border?
 * @param	pPainter	Used to paint on the view
 * @param	nX			The horizontal origin of the area to draw
 * @param	nY			The vertical origin of the area to draw
 * @param	nWidth		The width of the area to draw
 * @param	nHeight		The height of the area to draw
 */
void GraphWidget::drawContents(QPainter* pPainter, int nX, int nY, 
	int nWidth, int nHeight)
{
	// Draw the contents of the canvas
	QCanvasView::drawContents(pPainter, nX, nY, nWidth, nHeight);
	
	// Erase the canvas's area border
	if (canvas() != NULL) {
		QRect rect = canvas()->rect();
		pPainter->setBrush(QBrush()); // Null brush
		pPainter->setPen(Config().getColor(KScopeConfig::GraphBack));
		pPainter->drawRect(-1, -1, rect.width() + 2, rect.height() + 2);
	}
}

/**
 * Handles mouse clicks over the graph view.
 * @param	pEvent	Includes information on the mouse press event
 */
void GraphWidget::contentsMousePressEvent(QMouseEvent* pEvent)
{
	QPoint ptRealPos;
	QCanvasItemList il;
	QCanvasItemList::Iterator itr;
	QString sFunc;
	GraphNode* pNode;
	GraphEdge* pEdge;
	
	pNode = NULL;
	pEdge = NULL;
	
	// Handle right-clicks only
	if (pEvent->button() != Qt::RightButton) {
		QCanvasView::contentsMousePressEvent(pEvent);
		return;
	}
	
	// Take the zoom factor into consideration
	ptRealPos = pEvent->pos();
	ptRealPos /= m_dZoom;
	
	// Check if an item was clicked
	il = canvas()->collisions(ptRealPos);
	for (itr = il.begin(); itr != il.end(); ++itr) {
		if (dynamic_cast<GraphNode*>(*itr) != NULL)
			pNode = dynamic_cast<GraphNode*>(*itr);
		else if (dynamic_cast<GraphEdge*>(*itr) != NULL)
			pEdge = dynamic_cast<GraphEdge*>(*itr);
	}
	
	// Handle clicks over different types of items
	if (pNode != NULL) {
		// Show a context menu for nodes
		showNodeMenu(pNode, pEvent->globalPos());
	}
	else if (pEdge != NULL) {
		// Show a context menu for edges
		showEdgeMenu(pEdge, pEvent->globalPos());
	}
	else {
		// Take the default action
		QCanvasView::contentsMousePressEvent(pEvent);
	}
}

/**
 * Writes a description of the graph to the given stream, using the Dot 
 * language.
 * The method allows for both directed graphs and non-directed graphs, the
 * latter are required for drawing purposes (since Dot will not produce the
 * arrow heads and the splines terminate before reaching the nodes).
 * @param	str			The stream to write to
 * @param	sType		Either "graph" or "digraph"
 * @param	sEdge		The edge connector ("--" or "->")
 * @param	bWriteCall	true to write call information, false otherwise
 */
void GraphWidget::write(QTextStream& str, const QString& sType, 
	const QString& sEdge, bool bWriteCall)
{
	QFont font;
	QDictIterator<GraphNode> itr(m_dictNodes);
	GraphEdge* pEdge;
	Encoder enc;
	
	font = Config().getFont(KScopeConfig::Graph);

	// Header
	str << sType << " G {\n";
	
	// Graph attributes
	str << "\tgraph [rankdir=" << Config().getGraphOrientation() << ", "
		<< "kscope_zoom=" << m_dZoom 
		<< "];\n";
	
	// Default node attributes
	str << "\tnode [shape=box, height=\"0.01\", style=filled, "
		<< "fillcolor=\"" << Config().getColor(KScopeConfig::GraphNode).name()
		<< "\", "
		<< "fontcolor=\"" << Config().getColor(KScopeConfig::GraphText).name()
		<< "\", "
		<< "fontname=\"" << font.family() << "\", "
		<< "fontsize=" << QString::number(font.pointSize())
		<< "];\n";
	
	// Iterate over all nodes
	for (; itr.current(); ++itr) {
		// Write a node
		str << "\t" << itr.current()->getFunc() << ";\n";
		
		// Iterate over all edges leaving this node		
		QDictIterator<GraphEdge> itrEdge(itr.current()->getOutEdges());
		for (; itrEdge.current(); ++itrEdge) {
			pEdge = itrEdge.current();
			str << "\t" << pEdge->getHead()->getFunc() << sEdge
				<< pEdge->getTail()->getFunc();
				
			// Write call information
			if (bWriteCall) {
				str << " ["
					<< "kscope_file=\"" << pEdge->getFile() << "\","
					<< "kscope_line=" << pEdge->getLine() << ","
					<< "kscope_text=\"" << enc.encode(pEdge->getText()) << "\"" 
					<< "]";
			}
			
			str << ";\n";
		}
	}
	
	// Close the graph
	str << "}\n";
}

/**
 * Removes all edges attached to a function node at the given direction.
 * Any strongly connected components that are no longer connected to that
 * function are deleted.
 * @param	pNode	The node for which to remove the edges
 * @param	bOut	true for outgoing edges, false for incoming
 */
void GraphWidget::removeEdges(GraphNode* pNode, bool bOut)
{
	// Remove the edges
	if (bOut)
		pNode->removeOutEdges();
	else
		pNode->removeInEdges();
		
	// Remove all graph components no longer connected to this one
	removeDisconnected(pNode);
}

/**
 * Removes all edges and nodes that are not weakly connected to the given node.
 * This function is called to clean up the graph after edges were removed from
 * the given node.
 * @param	pNode	The node to which all other nodes have to be connected
 */
void GraphWidget::removeDisconnected(GraphNode* pNode)
{
	QDictIterator<GraphNode> itr(m_dictNodes);
	
	// Find all weakly connected components attached to this node
	pNode->dfs();
	
	// Delete all unmarked nodes, reset marked ones
	while (itr.current()) {
		if (!(*itr)->dfsVisited()) {
			m_dictNodes.remove((*itr)->getFunc());
		}
		else {
			(*itr)->dfsReset();
			++itr;
		}
	}
}

/**
 * Shows a popup menu for a node.
 * This menu is shown after a node has been right-clicked.
 * @param	pNode	The node for which to show the menu
 * @param	ptPos	The position of the menu 
 */
void GraphWidget::showNodeMenu(GraphNode* pNode, const QPoint& ptPos)
{
	// Remember the node
	m_pMenuItem = pNode;
	
	// Show the popup menu.
	if (pNode->isMultiCall())
		m_pMultiCallPopup->popup(ptPos);
	else
		m_pNodePopup->popup(ptPos);
}

/**
 * Shows a popup menu for an edge.
 * This menu is shown after an edge has been right-clicked.
 * @param	pEdge	The edge for which to show the menu
 * @param	ptPos	The position of the menu 
 */
void GraphWidget::showEdgeMenu(GraphEdge* pEdge, const QPoint& ptPos)
{
	// Remember the edge
	m_pMenuItem = pEdge;
	
	// Show the popup menu.
	m_pEdgePopup->popup(ptPos);
}

/**
 * Redraws the widget when new instructions are available.
 * This slot is connected to the finished() signal emitted by the dot front-
 * end.
 */
void GraphWidget::slotDotFinished()
{
	// Delete the temporary file
	if (m_sDrawFilePath != "") {
		QFile::remove(m_sDrawFilePath);
		m_sDrawFilePath = "";
	}
	
	// Delete the progress dialogue
	if (m_pProgressDlg) {
		delete m_pProgressDlg;
		m_pProgressDlg = NULL;
	}
	
	setUpdatesEnabled(true);
	canvas()->update();
}

/**
 * Adds an entry to the tree, as the child of the active item.
 * Called by a CscopeFrontend object, when a new entry was received in its
 * whole from the Cscope back-end process. The entry contains the data of a
 * function calling the function described by the active item.
 * @param	pToken	The first token in the entry
 */
void GraphWidget::slotDataReady(FrontendToken* pToken)
{
	CallData data;
	QString sFunc;
	
	// Get the file name
	data.m_sFile = pToken->getData();
	pToken = pToken->getNext();

	// Get the function name
	sFunc = pToken->getData();
	pToken = pToken->getNext();

	// Get the line number (do not accept global information on a call tree)
	data.m_sLine = pToken->getData();
	if (data.m_sLine.toUInt() == 0)
		return;
		
	pToken = pToken->getNext();

	// Get the line's text
	data.m_sText = pToken->getData();
		
	// Determine the caller and the callee
	if (m_bCalled) {
		data.m_sCaller = m_sQueriedFunc;
		data.m_sCallee = sFunc;
	}
	else {
		data.m_sCaller = sFunc;
		data.m_sCallee = m_sQueriedFunc;
	}
		
	// Add the call to the graph
	addCall(data);
}

/**
 * Displays search progress information.
 * This slot is connected to the progress() signal emitted by a
 * CscopeFrontend object.
 * @param	nProgress	The current progress value
 * @param	nTotal		The expected final value
 */
void GraphWidget::slotProgress(int nProgress, int nTotal)
{
	m_progress.setProgress(nProgress, nTotal);
}

/**
 * Disables the expandability feature of an item, if no functions calling it
 * were found.
 * @param	nRecords	Number of records reported by the query
 */
void GraphWidget::slotFinished(uint /*nRecords*/)
{
	// Destroy the progress bar
	m_progress.finished();
		
	// Redraw the graph
	draw();
}

/**
 * Adds a multiple call node when a query results in too many entries.
 * This slot is attached to the aborted() signal of the Cscope process.
 */
void GraphWidget::slotAborted()
{	
	KMessageBox::information(this, i18n("The query produced too many results.\n"
		"A multiple-call node will appear in the graph instead.\n"
		"Hint: The maximum number of in/out edges\n"
		"can be adjusted by clicking the dialogue's \"Preferences\" button"));
	
	addMultiCall(m_sQueriedFunc, m_bCalled);
	draw();
}

/**
 * Shows functions called from the current function node.
 * This slot is connected to the "Show Called Functions" popup menu
 * action.
 */
void GraphWidget::slotShowCalled()
{
	GraphNode* pNode;
	
	// Make sure the menu item is a node
	pNode = dynamic_cast<GraphNode*>(m_pMenuItem);
	if (pNode == NULL)
		return;
	
	// Run a query for called functions
	m_sQueriedFunc = pNode->getFunc();
	m_bCalled = true;
	m_pCscope->query(CscopeFrontend::Called, m_sQueriedFunc, true,
		Config().getGraphMaxNodeDegree());
}

/**
 * Shows a list of function calls from the current node.
 * The list is displayed in a query dialogue. The user can the select which
 * calls should be displayed in the graph.
 * This slot is connected to the "List Called Functions" popup menu
 * action.
 */
void GraphWidget::slotListCalled()
{
	GraphNode* pNode;

	// Make sure the menu item is a node
	pNode = dynamic_cast<GraphNode*>(m_pMenuItem);
	if (pNode == NULL)
		return;
	
	QueryViewDlg dlg(0, (QWidget*)parent());
	
	// Show the query view dialogue
	dlg.query(CscopeFrontend::Called, pNode->getFunc());
	if (dlg.exec() != QDialog::Accepted)
		return;
		
	// The OK button was clicked, replace current calls with the listed ones
	pNode->removeOutEdges();
	
	QueryView::Iterator itr;
	CallData data;
	
	data.m_sCaller = pNode->getFunc();
	
	// Add all listed calls
	for (itr = dlg.getIterator(); !itr.isEOF(); itr.next()) {
		data.m_sCallee = itr.getFunc();
		data.m_sFile = itr.getFile();
		data.m_sLine = itr.getLine();
		data.m_sText = itr.getText();
		
		addCall(data);
	}
	
	// Clean-up and redraw the graph
	removeDisconnected(pNode);
	draw();
}

/**
 * Hides functions called from the current function node.
 * This slot is connected to the "Hide Called Functions" popup menu
 * action.
 */
void GraphWidget::slotHideCalled()
{
	GraphNode* pNode;
	
	// Make sure the menu item is a node
	pNode = dynamic_cast<GraphNode*>(m_pMenuItem);
	if (pNode == NULL)
		return;
	
	// Remove edges and redraw the graph
	removeEdges(pNode, true);
	draw();
}

/**
 * Shows functions calling tothe current function node.
 * This slot is connected to the "Show Calling Functions" popup menu
 * action.
 */
void GraphWidget::slotShowCalling()
{
	GraphNode* pNode;
	
	// Make sure the menu item is a node
	pNode = dynamic_cast<GraphNode*>(m_pMenuItem);
	if (pNode == NULL)
		return;
	
	// Run a query for called functions
	m_sQueriedFunc = pNode->getFunc();
	m_bCalled = false;
	m_pCscope->query(CscopeFrontend::Calling, m_sQueriedFunc, true,
		Config().getGraphMaxNodeDegree());
}

/**
 * Shows a list of function calls to the current node.
 * The list is displayed in a query dialogue. The user can the select which
 * calls should be displayed in the graph.
 * This slot is connected to the "List Calling Functions" popup menu
 * action.
 */
void GraphWidget::slotListCalling()
{
	GraphNode* pNode;
	
	// Make sure the menu item is a node
	pNode = dynamic_cast<GraphNode*>(m_pMenuItem);
	if (pNode == NULL)
		return;
	
	QueryViewDlg dlg(0, (QWidget*)parent());
	
	// Show the query view dialogue
	dlg.query(CscopeFrontend::Calling, pNode->getFunc());
	if (dlg.exec() != QDialog::Accepted)
		return;
		
	// The OK button was clicked, replace current calls with the listed ones
	pNode->removeInEdges();
	
	QueryView::Iterator itr;
	CallData data;
	
	data.m_sCallee = pNode->getFunc();
	
	// Add all listed calls
	for (itr = dlg.getIterator(); !itr.isEOF(); itr.next()) {
		data.m_sCaller = itr.getFunc();
		data.m_sFile = itr.getFile();
		data.m_sLine = itr.getLine();
		data.m_sText = itr.getText();
		
		addCall(data);
	}
	
	// Clean-up and redraw the graph
	removeDisconnected(pNode);
	draw();
}

/**
 * Hides functions calling to the current function node.
 * This slot is connected to the "Hide CallingFunctions" popup menu
 * action.
 */
void GraphWidget::slotHideCalling()
{
	GraphNode* pNode;
	
	// Make sure the menu item is a node
	pNode = dynamic_cast<GraphNode*>(m_pMenuItem);
	if (pNode == NULL)
		return;
	
	// Remove edges and redraw the graph
	removeEdges(pNode, false);
	draw();
}

/**
 * Looks up the definition of the current function node.
 * This slot is connected to the "Find Definition" popup menu action.
 */
void GraphWidget::slotFindDef()
{
	GraphNode* pNode;
	QueryViewDlg* pDlg;
	
	// Make sure the menu item is a node
	pNode = dynamic_cast<GraphNode*>(m_pMenuItem);
	if (pNode == NULL)
		return;
	
	// Create a query view dialogue
	pDlg = new QueryViewDlg(QueryViewDlg::DestroyOnSelect, this);
	
	// Display a line when it is selected in the dialogue
	connect(pDlg, SIGNAL(lineRequested(const QString&, uint)), this,
		SIGNAL(lineRequested(const QString&, uint)));
		
	// Start the query
	pDlg->query(CscopeFrontend::Definition, pNode->getFunc());
}

/**
 * Deletes a node from the graph (alogn with all edges connected to this 
 * node).
 * The node removed is the one over which the context menu was invoked.
 * This slot is connected to the "Remove" popup menu action.
 */
void GraphWidget::slotRemoveNode()
{
	GraphNode* pNode;
	
	// Make sure the menu item is a node
	pNode = dynamic_cast<GraphNode*>(m_pMenuItem);
	if (pNode == NULL)
		return;
	
	// Remove all incoming edges
	pNode->removeInEdges();
	
	// Remove the node (also deletes the object and its outgoing edges)
	m_dictNodes.remove(pNode->getFunc());
	
	// Redraw the graph
	draw();
}

/**
 * Shows the list of calls that is represented by a single multi-call node.
 * This slot handles the "Details..." command of the multi-call node menu.
 */
void GraphWidget::slotMultiCallDetails()
{
	GraphNode* pNode, * pParent;
	bool bCalled;
	
	// Make sure the menu item is a node
	pNode = dynamic_cast<GraphNode*>(m_pMenuItem);
	if (pNode == NULL || !pNode->isMultiCall())
		return;
	
	// Get the required information from the node
	pNode->getFirstNeighbour(pParent, bCalled);
	if (!pParent)
		return;
		
	QueryViewDlg dlg(0, (QWidget*)parent());
	
	dlg.query(bCalled ? CscopeFrontend::Calling : CscopeFrontend::Called, 
		pParent->getFunc());
	dlg.exec();
}

/**
 * Emits a signal to open an editor at the file and line matching the call
 * information of the current edge.
 * This slot is connected to the "Open Call" popup menu action (for edges).
 */
void GraphWidget::slotOpenCall()
{
	GraphEdge* pEdge;
	QString sFile, sLine;
	
	// Make sure the menu item is an edge
	pEdge = dynamic_cast<GraphEdge*>(m_pMenuItem);
	if (pEdge != NULL && pEdge->getFile() != "")
		emit lineRequested(pEdge->getFile(), pEdge->getLine());
}

#include "graphwidget.moc"
