1 /**********************************************************************************************
2     Copyright (C) 2014 Oliver Eichler <oliver.eichler@gmx.de>
3     Copyright (C) 2015 Christian Eichler <code@christian-eichler.de>
4 
5     This program is free software: you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation, either version 3 of the License, or
8     (at your option) any later version.
9 
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14 
15     You should have received a copy of the GNU General Public License
16     along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 
18 **********************************************************************************************/
19 
20 #include "canvas/CCanvas.h"
21 #include "helpers/CDraw.h"
22 
23 #include <QDebug>
24 #include <QImage>
25 #include <QPainterPath>
26 #include <QPointF>
27 #include <QtMath>
28 
29 QPen CDraw::penBorderBlue(QColor(10, 10, 150, 220), 2);
30 QPen CDraw::penBorderGray(Qt::lightGray, 2);
31 QPen CDraw::penBorderBlack(QColor(0, 0, 0, 200), 2);
32 QBrush CDraw::brushBackWhite(QColor(255, 255, 255, 255));
33 QBrush CDraw::brushBackYellow(QColor(0xff, 0xff, 0xcc, 0xE0));
34 
35 
createBasicArrow(const QBrush & brush,qreal scale)36 QImage CDraw::createBasicArrow(const QBrush& brush, qreal scale)
37 {
38     QImage arrow(21 * scale, 16 * scale, QImage::Format_ARGB32);
39     arrow.fill(qRgba(0, 0, 0, 0));
40 
41     QPainter painter(&arrow);
42     USE_ANTI_ALIASING(painter, true);
43 
44     // white background, same foreground as p
45     painter.setPen(QPen(Qt::white, 2));
46     painter.setBrush(brush);
47 
48     QPointF arrowPoints[4] =
49     {
50         QPointF(20.0 * scale, 7.0 * scale),  // front
51         QPointF( 0.0 * scale, 0.0 * scale),  // upper tail
52         QPointF( 5.0 * scale, 7.0 * scale),  // mid   tail
53         QPointF( 0.0 * scale, 15.0 * scale)  // lower tail
54     };
55     painter.drawPolygon(arrowPoints, 4);
56     painter.end();
57 
58     return arrow;
59 }
60 
61 /**
62    @brief   Calculates the square distance between two points
63    @return  (int) ( (x2 - x1)^2 + (y2 - y1)^2 )
64  */
65 
pointDistanceSquare(const QPointF & p1,const QPointF & p2)66 static inline int pointDistanceSquare(const QPointF& p1, const QPointF& p2)
67 {
68     return (p2.x() - p1.x()) * (p2.x() - p1.x()) + (p2.y() - p1.y()) * (p2.y() - p1.y());
69 }
70 
arrows(const QPolygonF & line,const QRectF & viewport,QPainter & p,int minPointDist,int minArrowDist,qreal scale)71 void CDraw::arrows(const QPolygonF& line, const QRectF& viewport, QPainter& p, int minPointDist, int minArrowDist, qreal scale)
72 {
73     QImage arrow = createBasicArrow(p.brush(), scale);
74     qreal xoff = qCeil(arrow.width() / 2.0);
75     qreal yoff = qFloor((arrow.height() - 1) / 2.0);
76 
77     const qreal minArrowDistSquare = minArrowDist * minArrowDist;
78     const qreal minPointDistSquare = minPointDist * minPointDist;
79 
80     QPointF prevArrow;
81     bool firstArrow = true;
82     for(int i = 1; i < line.size(); i++)
83     {
84         const QPointF& pt = line[i    ];
85         const QPointF& prevPt = line[i - 1];
86 
87         // ensure there is enough space between two line points
88         if( pointDistanceSquare(pt, prevPt) >= minPointDistSquare )
89         {
90             QPointF arrowPos = prevPt + (pt - prevPt) / 2;
91 
92             if( (viewport.contains(pt) || 0 == viewport.height()) // ensure the point is visible
93                 && (firstArrow || pointDistanceSquare(prevArrow, arrowPos) >= minArrowDistSquare) )
94             {
95                 p.save();
96 
97                 // rotate and draw the arrow (between bullets)
98                 p.translate(arrowPos);
99                 qreal direction = ( qAtan2((pt.y() - prevPt.y()), (pt.x() - prevPt.x())) * 180.) / M_PI;
100                 p.rotate(direction);
101                 p.drawImage(-xoff, -yoff, arrow);
102 
103                 p.restore();
104 
105                 prevArrow = arrowPos;
106                 firstArrow = false;
107             }
108         }
109     }
110 }
111 
text(const QString & str,QPainter & p,const QPoint & center,const QColor & color,const QFont & font)112 void CDraw::text(const QString& str, QPainter& p, const QPoint& center, const QColor& color, const QFont& font)
113 {
114     QFontMetrics fm(font);
115     QRect r = fm.boundingRect(str);
116 
117     r.moveCenter(center);
118     p.setFont(font);
119 
120     // draw the white `shadow`
121     p.setPen(Qt::white);
122     p.drawText(r.topLeft() - QPoint(-1, -1), str);
123     p.drawText(r.topLeft() - QPoint( 0, -1), str);
124     p.drawText(r.topLeft() - QPoint(+1, -1), str);
125 
126     p.drawText(r.topLeft() - QPoint(-1, 0), str);
127     p.drawText(r.topLeft() - QPoint(+1, 0), str);
128 
129     p.drawText(r.topLeft() - QPoint(-1, +1), str);
130     p.drawText(r.topLeft() - QPoint( 0, +1), str);
131     p.drawText(r.topLeft() - QPoint(+1, +1), str);
132 
133     p.setPen(color);
134     p.drawText(r.topLeft(), str);
135 }
136 
text(const QString & str,QPainter & p,const QRect & r,const QColor & color)137 void CDraw::text(const QString& str, QPainter& p, const QRect& r, const QColor& color)
138 {
139     p.setPen(Qt::white);
140     p.setFont(CMainWindow::self().getMapFont());
141 
142     // draw the white `shadow`
143     p.drawText(r.adjusted(-1, -1, -1, -1), Qt::AlignCenter, str);
144     p.drawText(r.adjusted( 0, -1, 0, -1), Qt::AlignCenter, str);
145     p.drawText(r.adjusted(+1, -1, +1, -1), Qt::AlignCenter, str);
146 
147     p.drawText(r.adjusted(-1, 0, -1, 0), Qt::AlignCenter, str);
148     p.drawText(r.adjusted(+1, 0, +1, 0), Qt::AlignCenter, str);
149 
150     p.drawText(r.adjusted(-1, +1, -1, +1), Qt::AlignCenter, str);
151     p.drawText(r.adjusted( 0, +1, 0, +1), Qt::AlignCenter, str);
152     p.drawText(r.adjusted(+1, +1, +1, +1), Qt::AlignCenter, str);
153 
154     p.setPen(color);
155     p.drawText(r, Qt::AlignCenter, str);
156 }
157 
bubble(QPainter & p,const QRect & contentRect,const QPoint & pointerPos,int pointerBaseWidth,float pointerBasePos)158 QPoint CDraw::bubble(QPainter& p, const QRect& contentRect, const QPoint& pointerPos, int pointerBaseWidth, float pointerBasePos)
159 {
160     QPainterPath bubblePath;
161     bubblePath.addRoundedRect(contentRect, RECT_RADIUS, RECT_RADIUS);
162 
163     // draw the arrow
164     int pointerBaseCenterX = (pointerBasePos <= 1)
165                              ? contentRect.left() + (pointerBasePos * contentRect.width())
166                              : contentRect.left() + (int) pointerBasePos;
167 
168     int pointerHeight = 0;
169     if(pointerPos.y() < contentRect.top())
170     {
171         pointerHeight = contentRect.top() - pointerPos.y() + 1;
172     }
173     else if(pointerPos.y() > contentRect.bottom())
174     {
175         pointerHeight = contentRect.bottom() - pointerPos.y() - 1;
176     }
177     else
178     {
179         qDebug() << "cannot calculate pointerHeight/pointerBaseCenterX due to invalid parameters";
180     }
181 
182     if(0 != pointerHeight)
183     {
184         QPolygonF pointerPoly;
185         pointerPoly << pointerPos
186                     << QPointF(pointerBaseCenterX - pointerBaseWidth / 2, pointerPos.y() + pointerHeight)
187                     << QPointF(pointerBaseCenterX + pointerBaseWidth / 2, pointerPos.y() + pointerHeight)
188                     << pointerPos;
189 
190         QPainterPath pointerPath;
191         pointerPath.addPolygon(pointerPoly);
192 
193         bubblePath = bubblePath.united(pointerPath);
194     }
195 
196     p.setPen  (CDraw::penBorderGray);
197     p.setBrush(CDraw::brushBackWhite);
198 
199     p.drawPolygon(bubblePath.toFillPolygon());
200 
201     return contentRect.topLeft();
202 }
203 
drawCrossHairDot(QPainter & p,const QPointF & pt)204 void CDraw::drawCrossHairDot(QPainter& p, const QPointF& pt)
205 {
206     USE_ANTI_ALIASING(p, false);
207     p.setBrush(Qt::NoBrush);
208     QRectF dot2(0, 0, 7, 7);
209     p.setPen(QPen(Qt::white, 3));
210     p.drawLine(pt.x(), pt.y() + 3, pt.x(), pt.y() + 20);
211     p.drawLine(pt.x(), pt.y() - 3, pt.x(), pt.y() - 20);
212     p.drawLine(pt.x() - 3, pt.y(), pt.x() - +20, pt.y());
213     p.drawLine(pt.x() + 3, pt.y(), pt.x() + 20, pt.y());
214     p.setPen(QPen(Qt::red, 1));
215     p.drawLine(pt.x(), pt.y() + 3, pt.x(), pt.y() + 20);
216     p.drawLine(pt.x(), pt.y() - 3, pt.x(), pt.y() - 20);
217     p.drawLine(pt.x() - 3, pt.y(), pt.x() - +20, pt.y());
218     p.drawLine(pt.x() + 3, pt.y(), pt.x() + 20, pt.y());
219 
220     dot2.moveCenter(pt);
221     p.setPen(QPen(Qt::white, 3));
222     p.drawRect(dot2);
223     p.setPen(QPen(Qt::red, 1));
224     p.drawRect(dot2);
225     USE_ANTI_ALIASING(p, true);
226 }
227 
drawRectangle(QPainter & p,const QRectF & rect,const QPen & pen,const QBrush & brush)228 void CDraw::drawRectangle(QPainter& p, const QRectF& rect, const QPen& pen, const QBrush& brush)
229 {
230     p.setBrush(brush);
231     p.setPen(QPen(Qt::white, pen.width() + 2));
232     p.drawRect(rect);
233     p.setPen(pen);
234     p.drawRect(rect);
235 }
236 
drawRectangle(QPainter & p,const QRectF & rect,const Qt::GlobalColor & pen,const Qt::GlobalColor & brush)237 void CDraw::drawRectangle(QPainter& p, const QRectF& rect, const Qt::GlobalColor& pen, const Qt::GlobalColor& brush)
238 {
239     drawRectangle(p, rect, QPen(pen), QBrush(brush));
240 }
241