Initial import of version 0.3
[gpsdata] / src / compass.cpp
diff --git a/src/compass.cpp b/src/compass.cpp
new file mode 100644 (file)
index 0000000..7636b46
--- /dev/null
@@ -0,0 +1,355 @@
+/*
+ *  GPSData for Maemo.
+ *  Copyright (C) 2011 Roman Moravcik
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <QtGui>
+
+#include "compass.h"
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+#ifdef Q_WS_MAEMO_5
+#define LABEL_FONT_HIGH 44
+#else
+#define LABEL_FONT_HIGH 14
+#endif
+
+#ifdef Q_WS_MAEMO_5
+#define LABEL_FONT_SMALL 26
+#else
+#define LABEL_FONT_SMALL 8
+#endif
+
+#define GPS_MIN_GROUND_SPEED 1  /* 3.6 km/h */
+#define SENSOR_CALIBRATION_THRESHOLD 0.5
+
+#define WIDGET_MARGIN 8
+
+void Compass::updateWidget(const int azimuth, double calibration)
+{
+    m_azimuth = azimuth;
+    if (m_azimuth == 360)
+        m_azimuth = 0;
+
+    m_sensorCalibLevel = calibration;
+
+    if (calibration > 0) {
+        m_locked = true;
+
+        if (m_azimuth != m_compassAzimuth) {
+            if (m_rotateTimer->isActive())
+                m_rotateTimer->stop();
+
+            m_rotateTimer->start();
+        }
+    } else {
+        if (calibration == 0)
+            m_locked = false;
+
+        if (m_rotateTimer->isActive())
+            m_rotateTimer->stop();
+
+        update();
+    }
+}
+
+void Compass::updateWidget(const int azimuth, bool locked, int speed)
+{
+    m_azimuth = azimuth;
+    if (m_azimuth == 360)
+        m_azimuth = 0;
+
+    m_locked = locked;
+    m_gpsSpeed = speed;
+
+    if (speed >= GPS_MIN_GROUND_SPEED) {
+        if (m_azimuth != m_compassAzimuth) {
+            if (m_rotateTimer->isActive())
+                m_rotateTimer->stop();
+
+            m_rotateTimer->start();
+        }
+    } else {
+        if (m_rotateTimer->isActive())
+            m_rotateTimer->stop();
+
+        update();
+    }
+}
+
+void Compass::paintEvent(QPaintEvent * /* event */)
+{
+    QPainter painter(this);
+    painter.setRenderHint(QPainter::Antialiasing);
+
+    double widget_width = 0;
+    double widget_height = 0;
+    double widget_x = 0;
+    double widget_y = 0;
+
+    if (rect().width() < rect().height()) {
+        /* Portrait orientation */
+        widget_width = rect().width() - 2 * WIDGET_MARGIN;
+        widget_height = widget_width;
+        widget_x = rect().x() + WIDGET_MARGIN;
+        widget_y = rect().y() + (rect().height() - widget_height) / 2.0;
+    } else {
+        /* Landscape orientation */
+        widget_height = rect().height() - 2 * WIDGET_MARGIN;
+        widget_width = widget_height;
+        widget_x = rect().x() + (rect().width() - widget_width) / 2.0;
+        widget_y = rect().y() + WIDGET_MARGIN;
+    }
+
+    /* Draw azimuth/warning */
+    paintAzimuth(painter, rect());
+
+    QMatrix matrix;
+    matrix.translate(widget_x + widget_width / 2.0, widget_y + widget_height / 2.0);
+    matrix.rotate(360 - m_compassAzimuth);
+    painter.setMatrix(matrix);
+
+    /* Draw compass */
+    QRectF compassArea(0, 0, widget_width, widget_height);
+    paintCompass(painter, compassArea);
+}
+
+void Compass::paintAzimuth(QPainter &painter, const QRectF &area)
+{
+    QFont font = QApplication::font();
+    font.setPointSize(LABEL_FONT_SMALL);
+    painter.setFont(font);
+    painter.setPen(QApplication::palette().color(QPalette::Text));
+
+    double azimuth_offset = (area.height() - area.width()) / 4.0 -
+                            painter.fontMetrics().height() + 2.0 * WIDGET_MARGIN;
+    QRectF azimuthArea(area.x(), area.y() + azimuth_offset, area.width(), painter.fontMetrics().height());
+
+    if (m_locked) {
+        QString s;
+        QChar degree(0x00B0);
+
+        s = QString("%1%2")
+            .arg(QString::number(m_compassAzimuth))
+            .arg(degree);
+
+        if (m_type == Compass::Gps) {
+            if (m_gpsSpeed < GPS_MIN_GROUND_SPEED)
+                painter.setPen(m_azimuthLabelWarnColor);
+        } else if (m_type == Compass::Sensor) {
+            if (m_sensorCalibLevel < SENSOR_CALIBRATION_THRESHOLD)
+                painter.setPen(m_azimuthLabelWarnColor);
+        }
+        painter.drawText(azimuthArea, Qt::AlignCenter, s);
+    } else {
+        if (m_type == Compass::Gps)
+            painter.drawText(azimuthArea, Qt::AlignCenter, tr("GPS not locked"));
+        else if (m_type == Compass::Sensor)
+            painter.drawText(azimuthArea, Qt::AlignCenter, tr("Compass not calibrated"));
+    }
+}
+
+void Compass::paintCompass(QPainter &painter, const QRectF &area)
+{
+    double compass_radius = area.width() / 2.0;
+    double compass_x = area.x() - compass_radius;
+    double compass_y = area.y() - compass_radius;
+
+    /* Labels */
+    QString label[8] = {tr("N"), tr("NE"), tr("E"), tr("SE"), tr("S"), tr("SW"), tr("W"), tr("NW")};
+    double label_height = area.height() / 3.6;
+    double label_offset = area.width() / 14.4;
+    for (int i = 0; i < 8; i++) {
+        QFont font = QApplication::font();
+        double label_y;        
+
+        if ((i % 2) == 0) {
+            font.setPointSize(LABEL_FONT_HIGH);
+            label_y = compass_y;
+        } else {
+            font.setPointSize(LABEL_FONT_SMALL);
+            label_y = compass_y + label_offset;
+        }
+        QRectF labelArea(compass_x, label_y, area.width(), label_height);
+        painter.setFont(font);
+        painter.drawText(labelArea, Qt::AlignCenter, label[i]);
+        painter.rotate(45);
+    }
+
+    /* Compass scale */
+    QRectF compassArea(compass_x, compass_y, area.width(), area.height());
+    painter.setPen(QPen(m_scaleColor, 3));
+    painter.drawArc(compassArea, 0, 5760);
+
+    for (int angle = 0; angle < 360; angle += 2) {
+        double angle_rad = angle * M_PI / 180.0;
+
+        double x1 = (compass_radius - (area.width() / 18.0)) * qCos(angle_rad);
+        double y1 = (compass_radius - (area.width() / 18.0)) * qSin(angle_rad);
+        double x2 = compass_radius * qCos(angle_rad);
+        double y2 = compass_radius * qSin(angle_rad);
+
+        if (angle % 30)
+            painter.setPen(QPen(m_scaleColor, 0));
+        else
+            painter.setPen(QPen(m_scaleColor, 3));
+
+        painter.drawLine(QLineF(x1, y1, x2, y2));
+    }
+
+    /* Compass windrose NE/SE/SW/NW */
+    painter.setPen(m_windroseBckColor);
+    for (int angle = 45; angle < 360; angle += 90) {
+        double angle_rad1 = angle * M_PI / 180.0;
+        double angle_rad2 = (angle - 90) * M_PI / 180.0;
+        double angle_rad3 = (angle + 90) * M_PI / 180.0;
+        double temp1 = compass_radius - (area.width() / 3.6);
+        double temp2 = area.width() / 16.0;
+
+        double x1 = temp1 * qCos(angle_rad1);
+        double y1 = temp1 * qSin(angle_rad1);
+        double x2 = temp2 * qCos(angle_rad2);
+        double y2 = temp2 * qSin(angle_rad2);
+        double x3 = temp2 * qCos(angle_rad3);
+        double y3 = temp2 * qSin(angle_rad3);
+        QPointF triangle1[3] = { QPointF(x1, y1), QPointF(x2, y2), QPointF(area.x(), area.y()) };
+        painter.setBrush(m_windroseBckColor);
+        painter.drawPolygon(triangle1, 3);
+
+        QPointF triangle2[3] = { QPointF(x1, y1), QPointF(x3, y3), QPointF(area.x(), area.y()) };
+        painter.setBrush(m_windroseColor);
+        painter.drawPolygon(triangle2, 3);
+    }
+
+    /* Compass windrose N/E/S/W */
+    for (int angle = 0; angle < 360; angle += 90) {
+        double angle_rad1 = angle * M_PI / 180.0;
+        double angle_rad2 = (angle - 45) * M_PI / 180.0;
+        double angle_rad3 = (angle + 45) * M_PI / 180.0;
+        double temp1 = compass_radius - (area.width() / 4.5);
+        double temp2 = area.width() / 16.0;
+
+        double x1 = temp1 * qCos(angle_rad1);
+        double y1 = temp1 * qSin(angle_rad1);
+        double x2 = temp2 * qCos(angle_rad2);
+        double y2 = temp2 * qSin(angle_rad2);
+        double x3 = temp2 * qCos(angle_rad3);
+        double y3 = temp2 * qSin(angle_rad3);
+        QPointF triangle1[3] = { QPointF(x1, y1), QPointF(x2, y2), QPointF(area.x(), area.y()) };
+        if (angle == 270)
+            painter.setBrush(m_windroseNorthColor);
+        else
+            painter.setBrush(m_windroseBckColor);
+
+        painter.drawPolygon(triangle1, 3);
+
+        QPointF triangle2[3] = { QPointF(x1, y1), QPointF(x3, y3), QPointF(area.x(), area.y()) };
+        painter.setBrush(m_windroseColor);
+        painter.drawPolygon(triangle2, 3);
+    }
+
+    /* Red cursor */
+    double x1 = area.x() + (area.width() / 12.0);
+    double y1 = compass_y - 5;
+    double x2 = area.x() - (area.width() / 12.0);
+    double y2 = compass_y - 5;
+    double x3 = area.x();
+    double y3 = compass_y + (area.height() / 18.0) - 5;
+    QPointF triangle[3] = { QPointF(x1, y1), QPointF(x2, y2), QPointF(x3, y3) };
+
+    painter.rotate(m_compassAzimuth - 360);
+    painter.setPen(m_windroseNorthColor);
+    painter.setBrush(m_windroseNorthColor);
+    painter.drawPolygon(triangle, 3);
+}
+
+void Compass::moveTimerEvent()
+{
+    if (m_locked)
+    {
+        int delta = qAbs(m_azimuth - m_compassAzimuth);
+        if (delta > 180)
+            delta = 360 - delta;
+
+        int step = 1;
+        if (delta > 30)
+            step = 4;
+        else if (delta > 20)
+            step = 3;
+        else if (delta > 10)
+            step = 2;
+        else
+            step = 1;
+
+        if (m_azimuth > m_compassAzimuth) {
+            if ((m_azimuth - m_compassAzimuth) < 180)
+                m_compassAzimuth += step;
+            else
+                m_compassAzimuth -= step;
+        } else {
+            if ((m_compassAzimuth - m_azimuth) < 180)
+                m_compassAzimuth -= step;
+            else
+                m_compassAzimuth += step;
+        }
+
+        if (m_compassAzimuth >= 360)
+            m_compassAzimuth = m_compassAzimuth - 360;
+        else if (m_compassAzimuth < 0)
+            m_compassAzimuth = 360 + m_compassAzimuth;
+
+        if (m_azimuth == m_compassAzimuth)
+            m_rotateTimer->stop();
+    } else {
+        m_rotateTimer->stop();
+    }
+
+    update();
+}
+
+#ifdef QT_NO_OPENGL
+Compass::Compass(CompassType type) : QWidget()
+#else
+Compass::Compass(CompassType type) : QGLWidget(QGLFormat(QGL::SampleBuffers), 0)
+#endif
+{
+    m_type = type;
+    m_compassAzimuth = 0;
+    m_azimuth = 0;
+    m_locked = false;
+    m_gpsSpeed = 0;
+    m_sensorCalibLevel = 0;
+
+    m_azimuthLabelWarnColor = QColor(255, 0, 0);
+    m_scaleColor = QColor(153, 153, 153);
+    m_windroseNorthColor = QColor(255, 0, 0);
+    m_windroseColor = QColor(153, 153, 153);
+    m_windroseBckColor = QColor(74, 69, 66);
+
+    m_rotateTimer = new QTimer();
+    m_rotateTimer->setInterval(70);
+    connect(m_rotateTimer, SIGNAL(timeout()), this, SLOT(moveTimerEvent()));
+}
+
+Compass::~Compass()
+{
+    if (m_rotateTimer->isActive())
+        m_rotateTimer->stop();
+}