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 + -
显示快捷键?