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