diff options
author | Slávek Banko <slavek.banko@axis.cz> | 2013-10-07 23:28:24 +0200 |
---|---|---|
committer | Slávek Banko <slavek.banko@axis.cz> | 2013-10-08 05:14:51 +0200 |
commit | 3af5832abe7c802a384cd58d34f7cc4433595ced (patch) | |
tree | cc300d6fae4cb5798f0cf5db3ece63026f34379a /src/graphwidget.cpp | |
download | kscope-3af5832abe7c802a384cd58d34f7cc4433595ced.tar.gz kscope-3af5832abe7c802a384cd58d34f7cc4433595ced.zip |
Initial import of kscope 1.6.2
Diffstat (limited to 'src/graphwidget.cpp')
-rw-r--r-- | src/graphwidget.cpp | 1162 |
1 files changed, 1162 insertions, 0 deletions
diff --git a/src/graphwidget.cpp b/src/graphwidget.cpp new file mode 100644 index 0000000..00ca733 --- /dev/null +++ b/src/graphwidget.cpp @@ -0,0 +1,1162 @@ +/*************************************************************************** + * + * 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" |