Version bump (0.3-3)
[gpsdata] / src / compass.cpp
1 /*
2  *  GPSData for Maemo.
3  *  Copyright (C) 2011 Roman Moravcik
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 2 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, write to the Free Software
17  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  */
19
20 #include <QtGui>
21
22 #include "compass.h"
23
24 #ifndef M_PI
25 #define M_PI 3.14159265358979323846
26 #endif
27
28 #ifdef Q_WS_MAEMO_5
29 #define LABEL_FONT_HIGH 44
30 #else
31 #define LABEL_FONT_HIGH 14
32 #endif
33
34 #ifdef Q_WS_MAEMO_5
35 #define LABEL_FONT_SMALL 26
36 #else
37 #define LABEL_FONT_SMALL 8
38 #endif
39
40 #define GPS_MIN_GROUND_SPEED 1  /* 3.6 km/h */
41 #define SENSOR_CALIBRATION_THRESHOLD 0.5
42
43 #define WIDGET_MARGIN 8
44
45 void Compass::updateWidget(const int azimuth, double calibration)
46 {
47     m_azimuth = azimuth;
48     if (m_azimuth == 360)
49         m_azimuth = 0;
50
51     m_sensorCalibLevel = calibration;
52
53     if (calibration > 0) {
54         m_locked = true;
55
56         if (m_azimuth != m_compassAzimuth) {
57             if (m_rotateTimer->isActive())
58                 m_rotateTimer->stop();
59
60             m_rotateTimer->start();
61         }
62     } else {
63         if (calibration == 0)
64             m_locked = false;
65
66         if (m_rotateTimer->isActive())
67             m_rotateTimer->stop();
68
69         update();
70     }
71 }
72
73 void Compass::updateWidget(const int azimuth, bool locked, int speed)
74 {
75     m_azimuth = azimuth;
76     if (m_azimuth == 360)
77         m_azimuth = 0;
78
79     m_locked = locked;
80     m_gpsSpeed = speed;
81
82     if (speed >= GPS_MIN_GROUND_SPEED) {
83         if (m_azimuth != m_compassAzimuth) {
84             if (m_rotateTimer->isActive())
85                 m_rotateTimer->stop();
86
87             m_rotateTimer->start();
88         }
89     } else {
90         if (m_rotateTimer->isActive())
91             m_rotateTimer->stop();
92
93         update();
94     }
95 }
96
97 void Compass::paintEvent(QPaintEvent * /* event */)
98 {
99     QPainter painter(this);
100     painter.setRenderHint(QPainter::Antialiasing);
101
102     double widget_width = 0;
103     double widget_height = 0;
104     double widget_x = 0;
105     double widget_y = 0;
106
107     if (rect().width() < rect().height()) {
108         /* Portrait orientation */
109         widget_width = rect().width() - 2 * WIDGET_MARGIN;
110         widget_height = widget_width;
111         widget_x = rect().x() + WIDGET_MARGIN;
112         widget_y = rect().y() + (rect().height() - widget_height) / 2.0;
113     } else {
114         /* Landscape orientation */
115         widget_height = rect().height() - 2 * WIDGET_MARGIN;
116         widget_width = widget_height;
117         widget_x = rect().x() + (rect().width() - widget_width) / 2.0;
118         widget_y = rect().y() + WIDGET_MARGIN;
119     }
120
121     /* Draw azimuth/warning */
122     paintAzimuth(painter, rect());
123
124     QMatrix matrix;
125     matrix.translate(widget_x + widget_width / 2.0, widget_y + widget_height / 2.0);
126     matrix.rotate(360 - m_compassAzimuth);
127     painter.setMatrix(matrix);
128
129     /* Draw compass */
130     QRectF compassArea(0, 0, widget_width, widget_height);
131     paintCompass(painter, compassArea);
132 }
133
134 void Compass::paintAzimuth(QPainter &painter, const QRectF &area)
135 {
136     QFont font = QApplication::font();
137     font.setPointSize(LABEL_FONT_SMALL);
138     painter.setFont(font);
139     painter.setPen(QApplication::palette().color(QPalette::Text));
140
141     double azimuth_offset = (area.height() - area.width()) / 4.0 -
142                             painter.fontMetrics().height() + 2.0 * WIDGET_MARGIN;
143     QRectF azimuthArea(area.x(), area.y() + azimuth_offset, area.width(), painter.fontMetrics().height());
144
145     if (m_locked) {
146         QString s;
147         QChar degree(0x00B0);
148
149         s = QString("%1%2")
150             .arg(QString::number(m_compassAzimuth))
151             .arg(degree);
152
153         if (m_type == Compass::Gps) {
154             if (m_gpsSpeed < GPS_MIN_GROUND_SPEED)
155                 painter.setPen(m_azimuthLabelWarnColor);
156         } else if (m_type == Compass::Sensor) {
157             if (m_sensorCalibLevel < SENSOR_CALIBRATION_THRESHOLD)
158                 painter.setPen(m_azimuthLabelWarnColor);
159         }
160         painter.drawText(azimuthArea, Qt::AlignCenter, s);
161     } else {
162         if (m_type == Compass::Gps)
163             painter.drawText(azimuthArea, Qt::AlignCenter, tr("GPS not locked"));
164         else if (m_type == Compass::Sensor)
165             painter.drawText(azimuthArea, Qt::AlignCenter, tr("Compass not calibrated"));
166     }
167 }
168
169 void Compass::paintCompass(QPainter &painter, const QRectF &area)
170 {
171     double compass_radius = area.width() / 2.0;
172     double compass_x = area.x() - compass_radius;
173     double compass_y = area.y() - compass_radius;
174
175     /* Labels */
176     QString label[8] = {tr("N"), tr("NE"), tr("E"), tr("SE"), tr("S"), tr("SW"), tr("W"), tr("NW")};
177     double label_height = area.height() / 3.6;
178     double label_offset = area.width() / 14.4;
179     for (int i = 0; i < 8; i++) {
180         QFont font = QApplication::font();
181         double label_y;        
182
183         if ((i % 2) == 0) {
184             font.setPointSize(LABEL_FONT_HIGH);
185             label_y = compass_y;
186         } else {
187             font.setPointSize(LABEL_FONT_SMALL);
188             label_y = compass_y + label_offset;
189         }
190         QRectF labelArea(compass_x, label_y, area.width(), label_height);
191         painter.setFont(font);
192         painter.drawText(labelArea, Qt::AlignCenter, label[i]);
193         painter.rotate(45);
194     }
195
196     /* Compass scale */
197     QRectF compassArea(compass_x, compass_y, area.width(), area.height());
198     painter.setPen(QPen(m_scaleColor, 3));
199     painter.drawArc(compassArea, 0, 5760);
200
201     for (int angle = 0; angle < 360; angle += 2) {
202         double angle_rad = angle * M_PI / 180.0;
203
204         double x1 = (compass_radius - (area.width() / 18.0)) * qCos(angle_rad);
205         double y1 = (compass_radius - (area.width() / 18.0)) * qSin(angle_rad);
206         double x2 = compass_radius * qCos(angle_rad);
207         double y2 = compass_radius * qSin(angle_rad);
208
209         if (angle % 30)
210             painter.setPen(QPen(m_scaleColor, 0));
211         else
212             painter.setPen(QPen(m_scaleColor, 3));
213
214         painter.drawLine(QLineF(x1, y1, x2, y2));
215     }
216
217     /* Compass windrose NE/SE/SW/NW */
218     painter.setPen(m_windroseBckColor);
219     for (int angle = 45; angle < 360; angle += 90) {
220         double angle_rad1 = angle * M_PI / 180.0;
221         double angle_rad2 = (angle - 90) * M_PI / 180.0;
222         double angle_rad3 = (angle + 90) * M_PI / 180.0;
223         double temp1 = compass_radius - (area.width() / 3.6);
224         double temp2 = area.width() / 16.0;
225
226         double x1 = temp1 * qCos(angle_rad1);
227         double y1 = temp1 * qSin(angle_rad1);
228         double x2 = temp2 * qCos(angle_rad2);
229         double y2 = temp2 * qSin(angle_rad2);
230         double x3 = temp2 * qCos(angle_rad3);
231         double y3 = temp2 * qSin(angle_rad3);
232         QPointF triangle1[3] = { QPointF(x1, y1), QPointF(x2, y2), QPointF(area.x(), area.y()) };
233         painter.setBrush(m_windroseBckColor);
234         painter.drawPolygon(triangle1, 3);
235
236         QPointF triangle2[3] = { QPointF(x1, y1), QPointF(x3, y3), QPointF(area.x(), area.y()) };
237         painter.setBrush(m_windroseColor);
238         painter.drawPolygon(triangle2, 3);
239     }
240
241     /* Compass windrose N/E/S/W */
242     for (int angle = 0; angle < 360; angle += 90) {
243         double angle_rad1 = angle * M_PI / 180.0;
244         double angle_rad2 = (angle - 45) * M_PI / 180.0;
245         double angle_rad3 = (angle + 45) * M_PI / 180.0;
246         double temp1 = compass_radius - (area.width() / 4.5);
247         double temp2 = area.width() / 16.0;
248
249         double x1 = temp1 * qCos(angle_rad1);
250         double y1 = temp1 * qSin(angle_rad1);
251         double x2 = temp2 * qCos(angle_rad2);
252         double y2 = temp2 * qSin(angle_rad2);
253         double x3 = temp2 * qCos(angle_rad3);
254         double y3 = temp2 * qSin(angle_rad3);
255         QPointF triangle1[3] = { QPointF(x1, y1), QPointF(x2, y2), QPointF(area.x(), area.y()) };
256         if (angle == 270)
257             painter.setBrush(m_windroseNorthColor);
258         else
259             painter.setBrush(m_windroseBckColor);
260
261         painter.drawPolygon(triangle1, 3);
262
263         QPointF triangle2[3] = { QPointF(x1, y1), QPointF(x3, y3), QPointF(area.x(), area.y()) };
264         painter.setBrush(m_windroseColor);
265         painter.drawPolygon(triangle2, 3);
266     }
267
268     /* Red cursor */
269     double x1 = area.x() + (area.width() / 12.0);
270     double y1 = compass_y - 5;
271     double x2 = area.x() - (area.width() / 12.0);
272     double y2 = compass_y - 5;
273     double x3 = area.x();
274     double y3 = compass_y + (area.height() / 18.0) - 5;
275     QPointF triangle[3] = { QPointF(x1, y1), QPointF(x2, y2), QPointF(x3, y3) };
276
277     painter.rotate(m_compassAzimuth - 360);
278     painter.setPen(m_windroseNorthColor);
279     painter.setBrush(m_windroseNorthColor);
280     painter.drawPolygon(triangle, 3);
281 }
282
283 void Compass::moveTimerEvent()
284 {
285     if (m_locked)
286     {
287         int delta = qAbs(m_azimuth - m_compassAzimuth);
288         if (delta > 180)
289             delta = 360 - delta;
290
291         int step = 1;
292         if (delta > 30)
293             step = 4;
294         else if (delta > 20)
295             step = 3;
296         else if (delta > 10)
297             step = 2;
298         else
299             step = 1;
300
301         if (m_azimuth > m_compassAzimuth) {
302             if ((m_azimuth - m_compassAzimuth) < 180)
303                 m_compassAzimuth += step;
304             else
305                 m_compassAzimuth -= step;
306         } else {
307             if ((m_compassAzimuth - m_azimuth) < 180)
308                 m_compassAzimuth -= step;
309             else
310                 m_compassAzimuth += step;
311         }
312
313         if (m_compassAzimuth >= 360)
314             m_compassAzimuth = m_compassAzimuth - 360;
315         else if (m_compassAzimuth < 0)
316             m_compassAzimuth = 360 + m_compassAzimuth;
317
318         if (m_azimuth == m_compassAzimuth)
319             m_rotateTimer->stop();
320     } else {
321         m_rotateTimer->stop();
322     }
323
324     update();
325 }
326
327 #ifdef QT_NO_OPENGL
328 Compass::Compass(CompassType type) : QWidget()
329 #else
330 Compass::Compass(CompassType type) : QGLWidget(QGLFormat(QGL::SampleBuffers), 0)
331 #endif
332 {
333     m_type = type;
334     m_compassAzimuth = 0;
335     m_azimuth = 0;
336     m_locked = false;
337     m_gpsSpeed = 0;
338     m_sensorCalibLevel = 0;
339
340     m_azimuthLabelWarnColor = QColor(255, 0, 0);
341     m_scaleColor = QColor(153, 153, 153);
342     m_windroseNorthColor = QColor(255, 0, 0);
343     m_windroseColor = QColor(153, 153, 153);
344     m_windroseBckColor = QColor(74, 69, 66);
345
346     m_rotateTimer = new QTimer();
347     m_rotateTimer->setInterval(70);
348     connect(m_rotateTimer, SIGNAL(timeout()), this, SLOT(moveTimerEvent()));
349 }
350
351 Compass::~Compass()
352 {
353     if (m_rotateTimer->isActive())
354         m_rotateTimer->stop();
355 }