summaryrefslogtreecommitdiffstats
path: root/src/graphedge.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/graphedge.cpp')
-rw-r--r--src/graphedge.cpp306
1 files changed, 306 insertions, 0 deletions
diff --git a/src/graphedge.cpp b/src/graphedge.cpp
new file mode 100644
index 0000000..283a5fe
--- /dev/null
+++ b/src/graphedge.cpp
@@ -0,0 +1,306 @@
+/***************************************************************************
+ *
+ * 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 <qpainter.h>
+#include "graphedge.h"
+#include "graphnode.h"
+
+int GraphEdge::RTTI = 1002;
+
+// Some definitions required by the ConvexHull class
+typedef int (*CompFunc)(const void*, const void*);
+#define ISLEFT(P0, P1, P2) \
+ (P1.x() - P0.x()) * (P2.y() - P0.y()) - \
+ (P2.x() - P0.x()) * (P1.y() - P0.y())
+#define FARTHEST(P0, P1, P2) \
+ ((P1.x() - P0.x()) * (P1.x() - P0.x()) - \
+ (P1.y() - P0.y()) * (P1.y() - P0.y())) - \
+ ((P2.x() - P0.x()) * (P2.x() - P0.x()) - \
+ (P2.y() - P0.y()) * (P2.y() - P0.y()))
+
+
+/**
+ * An array of QPoint objects that can compute the convex hull surrounding all
+ * points in the array.
+ * @author Elad Lahav
+ */
+class ConvexHull : public QPointArray
+{
+public:
+ /**
+ * Class constructor.
+ */
+ ConvexHull() : QPointArray() {}
+
+ /**
+ * Computes the convex hull of the points stored in the array, using
+ * Graham's scan.
+ * @param arrHull Holds the coordinates of the convex hull, upon return
+ */
+ void compute(QPointArray& arrHull) {
+ uint i, nPivot, nTop;
+
+ // Find the pivot point
+ nPivot = 0;
+ for (i = 1; i < size(); i++) {
+ if ((*this)[i].y() < (*this)[nPivot].y()) {
+ nPivot = i;
+ }
+ else if ((*this)[i].y() == (*this)[nPivot].y() &&
+ (*this)[i].x() < (*this)[nPivot].x()) {
+ nPivot = i;
+ }
+ }
+
+ // Sort points in radial order, relative to the pivot
+ s_ptPivot = (*this)[nPivot];
+ (*this)[nPivot] = (*this)[0];
+ (*this)[0] = s_ptPivot;
+ qsort(&(*this).data()[1], (*this).size() - 1, sizeof(QPoint),
+ (CompFunc)compRadial);
+
+ // Initialise Graham's scan algorithm
+ arrHull.resize(size() + 1);
+ arrHull[0] = (*this)[0];
+ arrHull[1] = (*this)[1];
+ nTop = 1;
+
+ // Compute the convex hull
+ for (i = 2; i < size();) {
+ // TODO: According to the algorithm, the condition should be >0
+ // for pushing the point into the stack. For some reason, it works
+ // only with <0. Why?
+ if (ISLEFT(arrHull[nTop - 1], arrHull[nTop], (*this)[i]) < 0) {
+ arrHull[++nTop] = (*this)[i];
+ i++;
+ }
+ else {
+ nTop--;
+ }
+ }
+
+ // Close the hull
+ arrHull[++nTop] = (*this)[0];
+ arrHull.truncate(nTop + 1);
+ }
+
+private:
+ /** The current pivot point, required by compRadial. */
+ static QPoint s_ptPivot;
+
+ /**
+ * Compares two points based on their angle relative to the current
+ * pivot point.
+ * This function is passed as the comparison function of qsort().
+ * @param pPt1 A pointer to the first point
+ * @param pPt2 A pointer to the second point
+ * @return >0 if the first point is to the left of the second, <0 otherwise
+ */
+ static int compRadial(const QPoint* pPt1, const QPoint* pPt2) {
+ int nResult;
+
+ // Determine which point is to the left of the other. If the angle is
+ // the same, the greater point is the one farther from the pivot
+ nResult = ISLEFT(s_ptPivot, (*pPt1), (*pPt2));
+ if (nResult == 0)
+ return FARTHEST(s_ptPivot, (*pPt1), (*pPt2));
+
+ return nResult;
+ }
+};
+
+QPoint ConvexHull::s_ptPivot;
+
+/**
+ * Class constructor.
+ * @param pCanvas The canvas on which to draw the edge
+ * @param pHead The edge's starting point
+ * @param pTail The edge's end point
+ */
+GraphEdge::GraphEdge(QCanvas* pCanvas, GraphNode* pHead, GraphNode* pTail) :
+ QCanvasPolygonalItem(pCanvas),
+ m_pHead(pHead),
+ m_pTail(pTail),
+ m_arrPoly(4)
+{
+}
+
+/**
+ * Class destructor.
+ */
+GraphEdge::~GraphEdge()
+{
+ // Classes derived from QCanvasPolygonalItem must call hide() in their
+ // detructor (according to the Qt documentation)
+ hide();
+}
+
+/**
+ * Calculates the area surrounding the edge, and the bounding rectangle of
+ * the edge's polygonal head.
+ * The calculated area is used to find items on the canvas and to display
+ * tips. The bounding rectangle defines the tip's region (@see QToolTip).
+ * TODO: The function assumes that the we can simply append the polygon's
+ * array to that of the splines. Is this always the case, or should we sort
+ * the points of the polygon in radial order?
+ * @param arrCurve The control points of the edge's spline
+ * @param ai Used to calculate the arrow head polygon
+ */
+void GraphEdge::setPoints(const QPointArray& arrCurve, const ArrowInfo& ai)
+{
+ ConvexHull ch;
+ uint i;
+ int nXpos, nYpos;
+
+ // Redraw an existing edge
+ if (m_arrArea.size() > 0)
+ invalidate();
+
+ // Store the point array for drawing
+ m_arrCurve = arrCurve;
+
+ // Calculate the arrowhead's polygon
+ makeArrowhead(ai);
+
+ // Compute the convex hull of the edge
+ ch.resize(m_arrCurve.size() + m_arrPoly.size() - 2);
+ ch.putPoints(0, m_arrCurve.size() - 1, m_arrCurve);
+ ch.putPoints(m_arrCurve.size() - 1, m_arrPoly.size() - 1, m_arrPoly);
+ ch.compute(m_arrArea);
+
+ // Calculate the head's bounding rectangle
+ m_rcTip = QRect(m_arrPoly[0], m_arrPoly[0]);
+ for (i = 1; i < m_arrPoly.size(); i++) {
+ nXpos = m_arrPoly[i].x();
+ if (nXpos < m_rcTip.left())
+ m_rcTip.setLeft(nXpos);
+ else if (nXpos > m_rcTip.right())
+ m_rcTip.setRight(nXpos);
+
+ nYpos = m_arrPoly[i].y();
+ if (nYpos < m_rcTip.top())
+ m_rcTip.setTop(nYpos);
+ else if (nYpos > m_rcTip.bottom())
+ m_rcTip.setBottom(nYpos);
+ }
+}
+
+/**
+ * Sets the call information associated with this edge.
+ * @param sFile The call's file path
+ * @param sLine The call's line number
+ * @param sText The call's text
+ */
+void GraphEdge::setCallInfo(const QString& sFile, const QString& sLine,
+ const QString& sText)
+{
+ m_sFile = sFile;
+ m_sLine = sLine;
+ m_sText = sText;
+}
+
+/**
+ * Constructs a tool-tip string for this edge.
+ * @return The tool-tip text
+ */
+QString GraphEdge::getTip() const
+{
+ QString sTip;
+
+ sTip = m_sText + "<br><i>" + m_sFile + "</i>:" + m_sLine;
+ return sTip;
+}
+
+/**
+ * Draws the spline as a sequence of cubic Bezier curves.
+ * @param painter Used for drawing the item on the canvas view
+ */
+void GraphEdge::drawShape(QPainter& painter)
+{
+ uint i;
+
+ // Draw the polygon
+ painter.drawConvexPolygon(m_arrPoly);
+
+ // Draw the Bezier curves
+ for (i = 0; i < m_arrCurve.size() - 1; i += 3)
+ painter.drawCubicBezier(m_arrCurve, i);
+
+#if 0
+ // Draws the convex hull of the edge
+ QPen pen = painter.pen();
+ QBrush br = painter.brush();
+ painter.setPen(QPen(QColor(255, 0, 0)));
+ painter.setBrush(QBrush());
+ painter.drawPolygon(m_arrArea);
+ painter.setPen(pen);
+ painter.setBrush(br);
+#endif
+}
+
+/**
+ * Computes the coordinates of the edge's arrow head, based on its curve.
+ * @param ai Pre-computed values used for all edges
+ */
+void GraphEdge::makeArrowhead(const ArrowInfo& ai)
+{
+ QPoint ptLast, ptPrev;
+ double dX1, dY1, dX0, dY0, dX, dY, dDeltaX, dDeltaY, dNormLen;
+
+ // The arrowhead follows the line from the second last to the last points
+ // in the curve
+ ptLast = m_arrCurve[m_arrCurve.size() - 1];
+ ptPrev = m_arrCurve[m_arrCurve.size() - 2];
+
+ // The first and last points of the polygon are the end of the curve
+ m_arrPoly.setPoint(0, ptLast.x(), ptLast.y());
+ m_arrPoly.setPoint(3, ptLast.x(), ptLast.y());
+
+ // Convert integer points to double precision values
+ dX1 = (double)ptLast.x();
+ dY1 = (double)ptLast.y();
+ dX0 = (double)ptPrev.x();
+ dY0 = (double)ptPrev.y();
+
+ // The values (x1-x0), (y1-y0) and sqrt(1 + tan(theta)^2) are useful
+ dDeltaX = dX1 - dX0;
+ dDeltaY = dY1 - dY0;
+
+ // The normalised length of the arrow's sides
+ dNormLen = ai.m_dLength / sqrt(dDeltaX * dDeltaX + dDeltaY * dDeltaY);
+
+ // Compute the other two points
+ dX = dX1 - ((dDeltaX - ai.m_dTan * dDeltaY) / ai.m_dSqrt) * dNormLen;
+ dY = dY1 - ((dDeltaY + ai.m_dTan * dDeltaX) / ai.m_dSqrt) * dNormLen;
+ m_arrPoly.setPoint(1, (int)dX, (int)dY);
+
+ dX = dX1 - ((dDeltaX + ai.m_dTan * dDeltaY) / ai.m_dSqrt) * dNormLen;
+ dY = dY1 - ((dDeltaY - ai.m_dTan * dDeltaX) / ai.m_dSqrt) * dNormLen;
+ m_arrPoly.setPoint(2, (int)dX, (int)dY);
+}