]> Shamusworld >> Repos - architektonas/blob - src/painter.cpp
Added pen and dropper tools, and printing support.
[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, aply zoom:     0, 80 ->  0, 40
48
49 or, is it:
50
51 1st, invert the Y: 10, 10 -> 10, 90
52 2nd, aply 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(QRectF rect, double radiusX, double radiusY)
377 {
378         if (!painter)
379                 return;
380
381         painter->drawRoundedRect(rect, radiusX, radiusY);
382 }
383
384 // The rect passed in is in Cartesian but we want to pad it by a set number of
385 // pixels (currently set at 8), so the pad looks the same regardless of zoom.
386 void Painter::DrawPaddedRect(QRectF rect)
387 {
388         if (!painter)
389                 return;
390
391         Vector v1 = CartesianToQtCoords(Vector(rect.x(), rect.y()));
392         Vector v2 = CartesianToQtCoords(Vector(rect.right(), rect.bottom()));
393         QRectF screenRect(QPointF(v1.x, v1.y), QPointF(v2.x, v2.y));
394         screenRect.adjust(-8, 8, 8, -8);        // Left/top, right/bottom
395         painter->drawRect(screenRect);
396 }
397
398 void Painter::DrawRect(QRectF rect)
399 {
400         if (!painter)
401                 return;
402
403         Vector v1 = CartesianToQtCoords(Vector(rect.x(), rect.y()));
404         Vector v2 = CartesianToQtCoords(Vector(rect.right(), rect.bottom()));
405         QRectF screenRect(QPointF(v1.x, v1.y), QPointF(v2.x, v2.y));
406         painter->drawRect(screenRect);
407 }
408
409 void Painter::DrawText(QRectF rect, int type, QString text)
410 {
411         if (!painter)
412                 return;
413
414         painter->drawText(rect, (Qt::AlignmentFlag)type, text);
415 }
416
417 void Painter::DrawArrowhead(Vector head, Vector tail, double size)
418 {
419         if (!painter)
420                 return;
421
422         QPolygonF arrow;
423
424         // We draw the arrowhead aligned along the line from tail to head
425         double angle = Vector(head - tail).Angle();
426         double orthoAngle = angle + QTR_TAU;
427         Vector orthogonal = Vector(cos(orthoAngle), sin(orthoAngle));
428         Vector unit = Vector(head - tail).Unit();
429
430         Point p1 = head - (unit * 9.0 * size);
431         Point p2 = p1 + (orthogonal * 3.0 * size);
432         Point p3 = p1 - (orthogonal * 3.0 * size);
433
434         Point p4 = CartesianToQtCoords(head);
435         Point p5 = CartesianToQtCoords(p2);
436         Point p6 = CartesianToQtCoords(p3);
437
438         arrow << QPointF(p4.x, p4.y) << QPointF(p5.x, p5.y) << QPointF(p6.x, p6.y);
439
440         painter->drawPolygon(arrow);
441 }
442
443 // Point is given in Cartesian coordinates
444 void Painter::DrawCrosshair(Vector point)
445 {
446         if (!painter)
447                 return;
448
449         Vector screenPoint = CartesianToQtCoords(point);
450         painter->drawLine(0, screenPoint.y, Global::screenSize.x, screenPoint.y);
451         painter->drawLine(screenPoint.x, 0, screenPoint.x, Global::screenSize.y);
452 }
453
454 void Painter::DrawInformativeText(QString text)
455 {
456         if (!painter)
457                 return;
458
459         painter->setFont(*Global::font);
460         QRectF bounds = painter->boundingRect(QRectF(), Qt::AlignVCenter, text);
461         bounds.moveTo(17.0, 17.0);
462         QRectF textRect = bounds;
463         textRect.adjust(-7.0, -7.0, 7.0, 7.0);
464
465         QPen pen = QPen(QColor(0x00, 0xFF, 0x00), 1.0, Qt::SolidLine);
466         painter->setPen(pen);
467         painter->setBrush(QBrush(QColor(0x40, 0xFF, 0x40, 0xD7)));
468         painter->drawRoundedRect(textRect, 7.0, 7.0);
469
470         pen = QPen(QColor(0x00, 0x5F, 0xDF));
471         painter->setPen(pen);
472         painter->drawText(bounds, Qt::AlignVCenter, text);
473 }