graphwidget.cpp

来自「This a source insight software in Linux.」· C++ 代码 · 共 1,093 行 · 第 1/2 页

CPP
1,093
字号
/*************************************************************************** * * 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 <qfile.h>#include <qpainter.h>#include <qtooltip.h>#include <klocale.h>#include "graphwidget.h"#include "graphnode.h"#include "graphedge.h"#include "kscopeconfig.h"#include "queryviewdlg.h"#include "encoder.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{	// 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(slotRepaint()));			// 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){	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);		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;	Agnode_t* pFunc, * pMulti;		// Create a unique name for the new node.	sMulti = sFunc + "$MULTI_" + (bCalled ? "CALLED" : "CALLING");		// Find the relevant nodes (create new nodes if necessary)	pFunc = agnode(m_pGraph, (char*)sFunc.latin1());	pMulti = agnode(m_pGraph, (char*)sMulti.latin1());		// Set properties of the new node	SET_NODE_ATTR(pMulti, "label", "...");	SET_NODE_ATTR(pMulti, "fillcolor",		Config().getColor(KScopeConfig::GraphMultiCall).name().latin1());	SET_NODE_ATTR(pMulti, "kscope_multi", ATTR_TRUE);	SET_NODE_ATTR(pMulti, "kscope_multi_parent", sFunc.latin1());	SET_NODE_ATTR(pMulti, "kscope_multi_called", 		bCalled ? ATTR_TRUE : ATTR_FALSE);		// Create a directed edge	if (bCalled)		agedge(m_pGraph, pFunc, pMulti);	else			agedge(m_pGraph, pMulti, pFunc);*/}/** * 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;		// 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;		// Write the graph contrents to the temporary file	{		QTextStream str(pFile, IO_WriteOnly);		write(str, "graph", "--", false);	}		// Close the file	fclose(pFile);		// Draw the graph	m_dot.run(szTempFile);}/** * 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	size	The new size to use */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->setBrush(Config().getColor(KScopeConfig::GraphNode));	pNode->setFont(Config().getFont(KScopeConfig::Graph));		// 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

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?