summaryrefslogtreecommitdiffstats
path: root/src/graphedge.cpp
blob: 95ca270f5dbe1dc081947001ce8e57ddaa787ff0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
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 <tqpainter.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 TQPoint objects that can compute the convex hull surrounding all
 * points in the array.
 * @author Elad Lahav
 */
class ConvexHull : public TQPointArray
{
public:
	/**
	 * Class constructor.
	 */
	ConvexHull() : TQPointArray() {}
	
	/**
	 * 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(TQPointArray& 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(TQPoint), 
			(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 TQPoint 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 TQPoint* pPt1, const TQPoint* 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;
	}
};

TQPoint 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(TQCanvas* pCanvas, GraphNode* pHead, GraphNode* pTail) :
	TQCanvasPolygonalItem(pCanvas),
	m_pHead(pHead),
	m_pTail(pTail),
	m_arrPoly(4)
{
}

/**
 * Class destructor.
 */
GraphEdge::~GraphEdge()
{
	// Classes derived from TQCanvasPolygonalItem must call hide() in their
	// detructor (according to the TQt 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 TQToolTip).
 * 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 TQPointArray& 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 = TQRect(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 TQString& sFile, const TQString& sLine,
	const TQString& sText)
{
	m_sFile = sFile;
	m_sLine = sLine;
	m_sText = sText;
} 

/**
 * Constructs a tool-tip string for this edge.
 * @return	The tool-tip text
 */
TQString GraphEdge::getTip() const
{
	TQString 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(TQPainter& 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
	TQPen pen = painter.pen();
	TQBrush br = painter.brush();
	painter.setPen(TQPen(TQColor(255, 0, 0)));
	painter.setBrush(TQBrush());
	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)
{
	TQPoint 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);
}