]> Shamusworld >> Repos - architektonas/blob - src/painter.cpp
Preliminary support for Polylines.
[architektonas] / src / painter.cpp
1 //
2 // painter.cpp: Paint abstraction layer between Archtektonas and Qt
3 //
4 // Part of the Architektonas Project
5 // (C) 2011 Underground Software
6 // See the README and GPLv3 files for licensing and warranty information
7 //
8 // JLH = James Hammons <jlhamm@acm.org>
9 //
10 // WHO  WHEN        WHAT
11 // ---  ----------  ------------------------------------------------------------
12 // JLH  09/20/2011  Created this file
13 //
14
15 #include "painter.h"
16 #include "global.h"
17 #include "mathconstants.h"
18
19 Painter::Painter(QPainter * p/*= NULL*/): painter(p)
20 {
21 }
22
23 Painter::~Painter()
24 {
25 }
26
27 Vector Painter::CartesianToQtCoords(Vector v)
28 {
29         // Convert regular Cartesian coordinates to the inverted Y-axis Qt
30         // coordinates at the current origin and zoom level.
31         return Vector((v.x - Global::origin.x) * Global::zoom, Global::screenSize.y - ((v.y - Global::origin.y) * Global::zoom));
32 }
33
34 Vector Painter::QtToCartesianCoords(Vector v)
35 {
36         // Convert screen location, with inverted Y-axis coordinates, to regular
37         // Cartesian coordinates at the current zoom level.
38         return Vector((v.x / Global::zoom) + Global::origin.x, ((Global::screenSize.y - v.y) / Global::zoom) + Global::origin.y);
39 /*
40 How to do it:
41
42 e.g., we have a point on the screen at Qt coords of 10, 10, screenSize is 100, 100.
43 origin is -10, -10 and zoom level is 2 (200%)
44
45 1st, invert the Y: 10, 10 -> 10, 90
46 2nd, add origin:   10, 90 ->  0, 80 (no, not right--err, yes, it is)
47 3rd, apply zoom:    0, 80 ->  0, 40
48
49 or, is it:
50
51 1st, invert the Y: 10, 10 -> 10, 90
52 2nd, apply zoom:   10, 90 ->  5, 45
53 3rd, add origin:    5, 45 -> -5, 35
54
55 it depends on whether or not origin is in Qt coords or cartesian. If Qt, then the 1st
56 is correct, otherwise, the 2nd is correct.
57
58 The way we calculate the Cartesian to Qt shows the 2nd (origin is cartesian) to be correct.
59 */
60 }
61
62 void Painter::SetRenderHint(int hint)
63 {
64         if (!painter)
65                 return;
66
67         painter->setRenderHint((QPainter::RenderHint)hint);
68 }
69
70 void Painter::SetPen(QPen pen)
71 {
72         if (!painter)
73                 return;
74
75         painter->setPen(pen);
76 }
77
78 void Painter::SetPen(uint32_t color, float size/*= 0*/, int style/*= 0*/)
79 {
80         if (!painter)
81                 return;
82
83         // We can cast style as Qt:PenStyle because they line up 1-to-1
84         painter->setPen(QPen(QColor(color >> 16, (color >> 8) & 0xFF, color & 0xFF, 255),
85                 size, (Qt::PenStyle)style));
86 }
87
88 void Painter::SetBrush(QBrush brush)
89 {
90         if (!painter)
91                 return;
92
93         painter->setBrush(brush);
94 }
95
96 void Painter::SetBrush(uint32_t color)
97 {
98         if (!painter)
99                 return;
100
101         painter->setBrush(QBrush(QColor(color >> 16, (color >> 8) & 0xFF, color & 0xFF, 255)));
102 }
103
104 void Painter::SetFont(QFont font)
105 {
106         if (!painter)
107                 return;
108
109         painter->setFont(font);
110 }
111
112 void Painter::DrawAngledText(Vector center, double angle, QString text, double size)
113 {
114         if (!painter)
115                 return;
116
117         // Strategy: Since Qt doesn't have any rotated text drawing functions,
118         // we instead translate the origin to the center of the text to be drawn and
119         // then rotate the frame to the desired angle.
120         center = CartesianToQtCoords(center);
121
122         // We may need this stuff... If dimension text is large enough.
123 //      int textWidth = QFontMetrics(painter->font()).width(text);
124 //      int textHeight = QFontMetrics(painter->font()).height();
125         QRectF textBox(-100.0 * Global::zoom * size, -100.0 * Global::zoom * size, 200.0 * Global::zoom * size, 200.0 * Global::zoom * size);   // x, y, w, h; x/y = upper left corner
126
127         // This is in pixels. Might not render correctly at all zoom levels.
128         // Need to figure out if dimensions are always rendered at one size
129         // regardless of zoom, or if they have a definite size, and are thus
130         // zoomable.
131 //      float yOffset = -12.0 * Global::zoom * size;
132         float yOffset = -8.0 * Global::zoom * size;
133
134         // Fix text so it isn't upside down...
135         if ((angle > QTR_TAU) && (angle < THREE_QTR_TAU))
136         {
137                 angle += HALF_TAU;
138 //              yOffset = 12.0 * Global::zoom * size;
139                 yOffset = 8.0 * Global::zoom * size;
140         }
141
142         textBox.translate(0, yOffset);
143         painter->save();
144         painter->translate(center.x, center.y);
145         // Angles are backwards in the Qt coord system, so we flip ours...
146         painter->rotate(-angle * RADIANS_TO_DEGREES);
147 //Need to fix this so the text scales as well...
148         painter->drawText(textBox, Qt::AlignCenter, text);
149         painter->restore();
150 }
151
152 //
153 // Draw angled text. Draws text using point p as the upper left corner.
154 // Size is point size, angle is in radians (defaults to 0).
155 //
156 void Painter::DrawTextObject(Point p, QString text, double size, double angle/*= 0*/)
157 {
158         if (!painter)
159                 return;
160
161         p = CartesianToQtCoords(p);
162         painter->setFont(QFont("Arial", Global::zoom * size));
163         int textWidth = QFontMetrics(painter->font()).horizontalAdvance(text);
164         int textHeight = QFontMetrics(painter->font()).height();
165
166         QRectF textBox(0, 0, textWidth, textHeight);
167         painter->save();
168         painter->translate(p.x, p.y);
169         // Angles are backwards in the Qt coord system, so we flip ours...
170         painter->rotate(-angle * RADIANS_TO_DEGREES);
171         painter->drawText(textBox, Qt::AlignLeft | Qt::AlignTop , text);
172         painter->restore();
173 }
174
175 //
176 // Return the non-rotated rectangle containing the extents of the text in
177 // Cartesian coordiates (starting from <0, 0>, the lower left hand side)
178 //
179 Rect Painter::MeasureTextObject(QString text, double size)
180 {
181         if (!painter)
182                 return Rect();
183
184         painter->setFont(QFont("Arial", Global::zoom * size));
185         int textWidth = QFontMetrics(painter->font()).horizontalAdvance(text);
186         int textHeight = QFontMetrics(painter->font()).height();
187         Point measured((double)textWidth / Global::zoom, (double)textHeight / Global::zoom);
188
189         return Rect(Point(0, 0), measured);
190 }
191
192 void Painter::DrawArc(Vector center, double radius, double startAngle, double span)
193 {
194         if (!painter)
195                 return;
196
197         center = CartesianToQtCoords(center);
198         // Need to multiply scalar quantities by the zoom factor as well...
199         radius *= Global::zoom;
200         QRectF rectangle(QPointF(center.x - radius, center.y - radius),
201                 QPointF(center.x + radius, center.y + radius));
202         int angle1 = (int)(startAngle * RADIANS_TO_DEGREES * 16.0);
203         int angle2 = (int)(span * RADIANS_TO_DEGREES * 16.0);
204         painter->drawArc(rectangle, angle1, angle2);
205 }
206
207 void Painter::DrawEllipse(Vector center, double axis1, double axis2)
208 {
209         if (!painter)
210                 return;
211
212         // Need to multiply scalar quantities by the zoom factor as well...
213         center = CartesianToQtCoords(center);
214         painter->drawEllipse(QPointF(center.x, center.y), axis1 * Global::zoom, axis2 * Global::zoom);
215 }
216
217 // This function is for drawing object handles without regard for zoom level;
218 // we don't want our object handle size to depend on the zoom level!
219 void Painter::DrawHandle(Vector center)
220 {
221         if (!painter)
222                 return;
223
224         center = CartesianToQtCoords(center);
225         painter->setPen(QPen(Qt::red, 2.0, Qt::DotLine));
226         painter->setBrush(Qt::NoBrush);
227         painter->drawEllipse(QPointF(center.x, center.y), 4.0, 4.0);
228 }
229
230 // This function is for drawing object handles without regard for zoom level;
231 // we don't want our object handle size to depend on the zoom level!
232 void Painter::DrawSmallHandle(Vector center)
233 {
234         if (!painter)
235                 return;
236
237         center = CartesianToQtCoords(center);
238         painter->setPen(QPen(Qt::red, 2.0, Qt::DotLine));
239         painter->setBrush(Qt::NoBrush);
240         painter->drawEllipse(QPointF(center.x, center.y), 2.0, 2.0);
241 }
242
243 // This function is for drawing feedback points without regard for zoom level;
244 // we don't want our feedback point size to depend on the zoom level!
245 void Painter::DrawCross(Vector point)
246 {
247         if (!painter)
248                 return;
249
250         point = CartesianToQtCoords(point);
251         painter->setPen(QPen(Qt::red, 2.0, Qt::SolidLine));
252         painter->drawLine(point.x - 8.0, point.y, point.x + 8.0, point.y);
253         painter->drawLine(point.x, point.y - 8.0, point.x, point.y + 8.0);
254 }
255
256 // This function is for drawing feedback points without regard for zoom level;
257 // we don't want our feedback point size to depend on the zoom level!
258 void Painter::DrawRectCorners(Rect rect)
259 {
260         if (!painter)
261                 return;
262
263         Vector v1 = CartesianToQtCoords(Vector(rect.l, rect.t));
264         Vector v2 = CartesianToQtCoords(Vector(rect.r, rect.b));
265         v1 += Vector(-8.0, -8.0);
266         v2 += Vector(+8.0, +8.0);
267         painter->setPen(QPen(Qt::red, 2.0, Qt::DashLine));
268         painter->drawLine(v1.x, v1.y, v1.x + 24, v1.y);
269         painter->drawLine(v1.x, v1.y, v1.x, v1.y + 24);
270         painter->drawLine(v2.x, v1.y, v2.x - 24, v1.y);
271         painter->drawLine(v2.x, v1.y, v2.x, v1.y + 24);
272         painter->drawLine(v2.x, v2.y, v2.x - 24, v2.y);
273         painter->drawLine(v2.x, v2.y, v2.x, v2.y - 24);
274         painter->drawLine(v1.x, v2.y, v1.x + 24, v2.y);
275         painter->drawLine(v1.x, v2.y, v1.x, v2.y - 24);
276
277 }
278
279 // This function is for drawing object handles without regard for zoom level;
280 // we don't want our object handle size to depend on the zoom level!
281 void Painter::DrawArrowHandle(Vector center, double angle)
282 {
283         if (!painter)
284                 return;
285
286         center = CartesianToQtCoords(center);
287         QPolygonF arrow;
288
289         // Since we're drawing directly on the screen, the Y is inverted. So we use
290         // the mirror of the angle.
291         double orthoAngle = -angle + QTR_TAU;
292         Vector orthogonal = Vector(cos(orthoAngle), sin(orthoAngle));
293         Vector unit = Vector(cos(-angle), sin(-angle));
294
295         Point p0 = center + (unit * 6.0);
296         Point p1 = center + (unit * 21.0);
297         Point p1b = center + (unit * 11.0);
298         Point p2 = p1b + (orthogonal * 5.0);
299         Point p3 = p1b - (orthogonal * 5.0);
300
301         painter->drawLine(p0.x, p0.y, p1.x, p1.y);
302         arrow << QPointF(p1.x, p1.y) << QPointF(p2.x, p2.y) << QPointF(p3.x, p3.y);
303
304         painter->drawPolygon(arrow);
305 }
306
307 // This function is for drawing object handles without regard for zoom level;
308 // we don't want our object handle size to depend on the zoom level!
309 void Painter::DrawArrowToLineHandle(Vector center, double angle)
310 {
311         if (!painter)
312                 return;
313
314         DrawArrowHandle(center, angle);
315         center = CartesianToQtCoords(center);
316
317         // Since we're drawing directly on the screen, the Y is inverted. So we use
318         // the mirror of the angle.
319         double orthoAngle = -angle + QTR_TAU;
320         Vector orthogonal = Vector(cos(orthoAngle), sin(orthoAngle));
321         Vector unit = Vector(cos(-angle), sin(-angle));
322
323         Point p1 = center + (unit * 21.0);
324         Point p2 = p1 + (orthogonal * 7.0);
325         Point p3 = p1 - (orthogonal * 7.0);
326
327         painter->drawLine(p2.x, p2.y, p3.x, p3.y);
328 }
329
330 void Painter::DrawLine(int x1, int y1, int x2, int y2)
331 {
332         if (!painter)
333                 return;
334
335         Vector v1 = CartesianToQtCoords(Vector(x1, y1));
336         Vector v2 = CartesianToQtCoords(Vector(x2, y2));
337         painter->drawLine(v1.x, v1.y, v2.x, v2.y);
338 }
339
340 void Painter::DrawLine(Vector v1, Vector v2)
341 {
342         if (!painter)
343                 return;
344
345         v1 = CartesianToQtCoords(v1);
346         v2 = CartesianToQtCoords(v2);
347         painter->drawLine(QPointF(v1.x, v1.y), QPointF(v2.x, v2.y));
348 }
349
350 void Painter::DrawHLine(double ypos)
351 {
352         double width = Global::screenSize.x / Global::zoom;
353         Vector v1 = CartesianToQtCoords(Vector(Global::origin.x, ypos));
354         Vector v2 = CartesianToQtCoords(Vector(Global::origin.x + width, ypos));
355         painter->drawLine(QPointF(v1.x, v1.y), QPointF(v2.x, v2.y));
356 }
357
358 void Painter::DrawVLine(double xpos)
359 {
360         double height = Global::screenSize.y / Global::zoom;
361         Vector v1 = CartesianToQtCoords(Vector(xpos, Global::origin.y));
362         Vector v2 = CartesianToQtCoords(Vector(xpos, Global::origin.y + height));
363         painter->drawLine(QPointF(v1.x, v1.y), QPointF(v2.x, v2.y));
364 }
365
366 void Painter::DrawPoint(int x, int y)
367 {
368         if (!painter)
369                 return;
370
371         Vector v = CartesianToQtCoords(Vector(x, y));
372         painter->drawPoint(v.x, v.y);
373 }
374
375 // The rect passed in is in Qt coordinates... (?)
376 void Painter::DrawRoundedRect(Rect rect, double radiusX, double radiusY)
377 {
378         if (!painter)
379                 return;
380
381         QRectF r(QPointF(rect.l, rect.t), QPointF(rect.r, rect.b));
382         painter->drawRoundedRect(r, radiusX, radiusY);
383 }
384
385 // The rect passed in is in Qt coordinates...
386 void Painter::DrawRoundedRect(QRectF rect, double radiusX, double radiusY)
387 {
388         if (!painter)
389                 return;
390
391         painter->drawRoundedRect(rect, radiusX, radiusY);
392 }
393
394 // The rect passed in is in Cartesian but we want to pad it by a set number of
395 // pixels (currently set at 8), so the pad looks the same regardless of zoom.
396 void Painter::DrawPaddedRect(Rect rect)
397 {
398         if (!painter)
399                 return;
400
401         Vector v1 = CartesianToQtCoords(rect.TopLeft());
402         Vector v2 = CartesianToQtCoords(rect.BottomRight());
403         QRectF screenRect(QPointF(v1.x, v1.y), QPointF(v2.x, v2.y));
404         screenRect.adjust(-8, 8, 8, -8);        // Left/top, right/bottom
405         painter->drawRect(screenRect);
406 }
407
408 void Painter::DrawRect(Rect rect)
409 {
410         if (!painter)
411                 return;
412
413         Vector v1 = CartesianToQtCoords(rect.TopLeft());
414         Vector v2 = CartesianToQtCoords(rect.BottomRight());
415         QRectF screenRect(QPointF(v1.x, v1.y), QPointF(v2.x, v2.y));
416         painter->drawRect(screenRect);
417 }
418
419 void Painter::DrawText(Rect rect, int type, QString text)
420 {
421         if (!painter)
422                 return;
423
424         QRectF r(QPointF(rect.l, rect.t), QPointF(rect.r, rect.b));
425         painter->drawText(r, (Qt::AlignmentFlag)type, text);
426 }
427
428 void Painter::DrawArrowhead(Vector head, Vector tail, double size)
429 {
430         if (!painter)
431                 return;
432
433         QPolygonF arrow;
434
435         // We draw the arrowhead aligned along the line from tail to head
436         double angle = Vector(head - tail).Angle();
437         double orthoAngle = angle + QTR_TAU;
438         Vector orthogonal = Vector(cos(orthoAngle), sin(orthoAngle));
439         Vector unit = Vector(head - tail).Unit();
440
441         Point p1 = head - (unit * 9.0 * size);
442         Point p2 = p1 + (orthogonal * 3.0 * size);
443         Point p3 = p1 - (orthogonal * 3.0 * size);
444
445         Point p4 = CartesianToQtCoords(head);
446         Point p5 = CartesianToQtCoords(p2);
447         Point p6 = CartesianToQtCoords(p3);
448
449         arrow << QPointF(p4.x, p4.y) << QPointF(p5.x, p5.y) << QPointF(p6.x, p6.y);
450
451         painter->drawPolygon(arrow);
452 }
453
454 // Point is given in Cartesian coordinates
455 void Painter::DrawCrosshair(Vector point)
456 {
457         if (!painter)
458                 return;
459
460         Vector screenPoint = CartesianToQtCoords(point);
461         painter->drawLine(0, screenPoint.y, Global::screenSize.x, screenPoint.y);
462         painter->drawLine(screenPoint.x, 0, screenPoint.x, Global::screenSize.y);
463 }
464
465 void Painter::DrawInformativeText(QString text)
466 {
467         if (!painter)
468                 return;
469
470         painter->setFont(*Global::font);
471         QRectF bounds = painter->boundingRect(QRectF(), Qt::AlignVCenter, text);
472         bounds.moveTo(17.0, 17.0);
473         QRectF textRect = bounds;
474         textRect.adjust(-7.0, -7.0, 7.0, 7.0);
475
476         QPen pen = QPen(QColor(0x00, 0xFF, 0x00), 1.0, Qt::SolidLine);
477         painter->setPen(pen);
478         painter->setBrush(QBrush(QColor(0x40, 0xFF, 0x40, 0xD7)));
479         painter->drawRoundedRect(textRect, 7.0, 7.0);
480
481         pen = QPen(QColor(0x00, 0x5F, 0xDF));
482         painter->setPen(pen);
483         painter->drawText(bounds, Qt::AlignVCenter, text);
484 }