PrimaryFlightDisplay.cc 44.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
#include "PrimaryFlightDisplay.h"
#include "UASManager.h"

#include <QDebug>
#include <QRectF>
#include <cmath>
#include <QPen>
#include <QPainter>
#include <QPainterPath>
#include <QResizeEvent>
dongfang's avatar
dongfang committed
11
#include <QtCore/qmath.h>
12

13 14 15 16
static const float LINEWIDTH = 0.0036f;
static const float SMALL_TEXT_SIZE = 0.028f;
static const float MEDIUM_TEXT_SIZE = SMALL_TEXT_SIZE*1.2f;
static const float LARGE_TEXT_SIZE = MEDIUM_TEXT_SIZE*1.2f;
17

18
static const bool SHOW_ZERO_ON_SCALES = true;
19 20

// all in units of display height
21 22 23 24
static const float ROLL_SCALE_RADIUS = 0.42f;
static const float ROLL_SCALE_TICKMARKLENGTH = 0.04f;
static const float ROLL_SCALE_MARKERWIDTH = 0.06f;
static const float ROLL_SCALE_MARKERHEIGHT = 0.04f;
25
// scale max. degrees
26
static const int ROLL_SCALE_RANGE = 60;
27 28

// fraction of height to translate for each degree of pitch.
29 30 31 32
static const float PITCHTRANSLATION = 65;
// 5 degrees for each line
static const int PITCH_SCALE_RESOLUTION = 5;
static const float PITCH_SCALE_MAJORWIDTH = 0.1f;
33
static const float PITCH_SCALE_MINORWIDTH = 0.066f;
34 35 36 37

// Beginning from PITCH_SCALE_WIDTHREDUCTION_FROM degrees of +/- pitch, the
// width of the lines is reduced, down to PITCH_SCALE_WIDTHREDUCTION times
// the normal width. This helps keep orientation in extreme attitudes.
38 39
static const int PITCH_SCALE_WIDTHREDUCTION_FROM = 30;
static const float PITCH_SCALE_WIDTHREDUCTION = 0.3f;
40

41
static const int PITCH_SCALE_HALFRANGE = 15;
42 43 44 45 46

// The number of degrees to either side of the heading to draw the compass disk.
// 180 is valid, this will draw a complete disk. If the disk is partly clipped
// away, less will do.

47 48 49
static const int  COMPASS_DISK_MAJORTICK = 10;
static const int  COMPASS_DISK_ARROWTICK = 45;
static const int  COMPASS_DISK_RESOLUTION = 10;
50 51
static const float COMPASS_DISK_MARKERWIDTH = 0.2f;
static const float COMPASS_DISK_MARKERHEIGHT = 0.133f;
52

53
static const int  CROSSTRACK_MAX = 1000;
54
static const float CROSSTRACK_RADIUS = 0.6f;
55

56 57
static const float TAPE_GAUGES_TICKWIDTH_MAJOR = 0.25f;
static const float TAPE_GAUGES_TICKWIDTH_MINOR = 0.15f;
58 59

// The altitude difference between top and bottom of scale
60
static const int ALTIMETER_LINEAR_SPAN = 50;
61
// every 5 meters there is a tick mark
62
static const int ALTIMETER_LINEAR_RESOLUTION = 5;
63
// every 10 meters there is a number
64
static const int ALTIMETER_LINEAR_MAJOR_RESOLUTION = 10;
65 66

// min. and max. vertical velocity
67 68
static const int ALTIMETER_VVI_SPAN = 5;
static const float ALTIMETER_VVI_WIDTH = 0.2f;
69 70

// Now the same thing for airspeed!
71 72 73
static const int AIRSPEED_LINEAR_SPAN = 15;
static const int AIRSPEED_LINEAR_RESOLUTION = 1;
static const int AIRSPEED_LINEAR_MAJOR_RESOLUTION = 5;
74

75 76
/*
 *@TODO:
dongfang's avatar
dongfang committed
77
 * global fixed pens (and painters too?)
78 79 80
 * repaint on demand multiple canvases
 * multi implementation with shared model class
 */
dongfang's avatar
dongfang committed
81
double PrimaryFlightDisplay_round(double value, int digits=0)
82
{
83
    return floor(value * pow(10.0, digits) + 0.5) / pow(10.0, digits);
84
}
85

86 87 88 89 90 91
qreal PrimaryFlightDisplay_constrain(qreal value, qreal min, qreal max) {
    if (value<min) value=min;
    else if(value>max) value=max;
    return value;
}

92 93 94 95 96 97 98 99 100 101 102 103
const int PrimaryFlightDisplay::tickValues[] = {10, 20, 30, 45, 60};
const QString PrimaryFlightDisplay::compassWindNames[] = {
    QString("N"),
    QString("NE"),
    QString("E"),
    QString("SE"),
    QString("S"),
    QString("SW"),
    QString("W"),
    QString("NW")
};

104
PrimaryFlightDisplay::PrimaryFlightDisplay(QWidget *parent) :
105 106
    QWidget(parent),

107 108 109
    _valuesChanged(false),
    _valuesLastPainted(QGC::groundTimeMilliseconds()),

110
    uas(NULL),
111

112 113 114
    roll(0),
    pitch(0),
    heading(0),
115

Don Gagne's avatar
Don Gagne committed
116
    altitudeAMSL(std::numeric_limits<double>::quiet_NaN()),
117
    altitudeWGS84(std::numeric_limits<double>::quiet_NaN()),
Don Gagne's avatar
Don Gagne committed
118
    altitudeRelative(std::numeric_limits<double>::quiet_NaN()),
119

Don Gagne's avatar
Don Gagne committed
120 121 122
    groundSpeed(std::numeric_limits<double>::quiet_NaN()),
    airSpeed(std::numeric_limits<double>::quiet_NaN()),
    climbRate(std::numeric_limits<double>::quiet_NaN()),
123

124
    navigationCrosstrackError(0),
Don Gagne's avatar
Don Gagne committed
125
    navigationTargetBearing(std::numeric_limits<double>::quiet_NaN()),
126

127 128
    layout(COMPASS_INTEGRATED),
    style(OVERLAY_HSI),
129

dongfang's avatar
dongfang committed
130
    redColor(QColor::fromHsvF(0, 0.75, 0.9)),
dongfang's avatar
dongfang committed
131 132 133 134 135 136 137
    amberColor(QColor::fromHsvF(0.12, 0.6, 1.0)),
    greenColor(QColor::fromHsvF(0.25, 0.8, 0.8)),

    lineWidth(2),
    fineLineWidth(1),

    instrumentEdgePen(QColor::fromHsvF(0, 0, 0.65, 0.5)),
dongfang's avatar
dongfang committed
138
    instrumentBackground(QColor::fromHsvF(0, 0, 0.3, 0.3)),
139 140 141
    instrumentOpagueBackground(QColor::fromHsvF(0, 0, 0.3, 1.0)),

    font("Bitstream Vera Sans"),
142
    refreshTimer(new QTimer(this))
dongfang's avatar
dongfang committed
143
{
144 145 146
    setMinimumSize(120, 80);
    setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

Lorenz Meier's avatar
Lorenz Meier committed
147 148
    setActiveUAS(UASManager::instance()->getActiveUAS());

149
    // Connect with UAS signal
150
    connect(UASManager::instance(), SIGNAL(UASDeleted(UASInterface*)), this, SLOT(forgetUAS(UASInterface*)));
151
    connect(UASManager::instance(), SIGNAL(activeUASSet(UASInterface*)), this, SLOT(setActiveUAS(UASInterface*)));
152

153 154
    // Refresh timer
    refreshTimer->setInterval(updateInterval);
155
    connect(refreshTimer, SIGNAL(timeout()), this, SLOT(checkUpdate()));
156 157 158 159 160 161 162 163 164
}

PrimaryFlightDisplay::~PrimaryFlightDisplay()
{
    refreshTimer->stop();
}

QSize PrimaryFlightDisplay::sizeHint() const
{
165
    return QSize(width(), (int)(width() * 3.0f / 4.0f));
166 167
}

168

169 170
void PrimaryFlightDisplay::showEvent(QShowEvent* event)
{
171
    // React only to internal (pre-display) events
172 173 174 175 176 177 178
    QWidget::showEvent(event);
    refreshTimer->start(updateInterval);
    emit visibilityChanged(true);
}

void PrimaryFlightDisplay::hideEvent(QHideEvent* event)
{
179
    // React only to internal (pre-display) events
180 181 182 183 184
    refreshTimer->stop();
    QWidget::hideEvent(event);
    emit visibilityChanged(false);
}

dongfang's avatar
dongfang committed
185 186 187 188 189
void PrimaryFlightDisplay::resizeEvent(QResizeEvent *e) {
    QWidget::resizeEvent(e);

    qreal size = e->size().width();

190 191
    lineWidth = PrimaryFlightDisplay_constrain(size*LINEWIDTH, 1, 6);
    fineLineWidth = PrimaryFlightDisplay_constrain(size*LINEWIDTH*2/3, 1, 2);
dongfang's avatar
dongfang committed
192 193 194

    instrumentEdgePen.setWidthF(fineLineWidth);

195
    smallTextSize = size * SMALL_TEXT_SIZE;
dongfang's avatar
dongfang committed
196 197 198
    mediumTextSize = size * MEDIUM_TEXT_SIZE;
    largeTextSize = size * LARGE_TEXT_SIZE;
}
199

dongfang's avatar
dongfang committed
200 201 202 203 204
void PrimaryFlightDisplay::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event);
    doPaint();
}
205

206 207 208 209 210 211 212 213 214
void PrimaryFlightDisplay::checkUpdate()
{
    if (uas && (_valuesChanged || (QGC::groundTimeMilliseconds() - _valuesLastPainted) > 260)) {
        update();
        _valuesChanged = false;
        _valuesLastPainted = QGC::groundTimeMilliseconds();
    }
}

215
void PrimaryFlightDisplay::forgetUAS(UASInterface* uas)
216
{
217
    if (this->uas != NULL && this->uas == uas) {
218
        // Disconnect any previously connected active MAV
219 220 221
        disconnect(this->uas, SIGNAL(attitudeChanged(UASInterface*,double,double,double,quint64)), this, SLOT(updateAttitude(UASInterface*, double, double, double, quint64)));
        disconnect(this->uas, SIGNAL(attitudeChanged(UASInterface*,int,double,double,double,quint64)), this, SLOT(updateAttitude(UASInterface*,int,double, double, double, quint64)));
        disconnect(this->uas, SIGNAL(speedChanged(UASInterface*, double, double, quint64)), this, SLOT(updateSpeed(UASInterface*, double, double, quint64)));
222
        disconnect(this->uas, SIGNAL(altitudeChanged(UASInterface*, double, double, double, double, quint64)), this, SLOT(updateAltitude(UASInterface*, double, double, double, quint64)));
223
        disconnect(this->uas, SIGNAL(navigationControllerErrorsChanged(UASInterface*, double, double, double)), this, SLOT(updateNavigationControllerErrors(UASInterface*, double, double, double)));
224
    }
225 226 227 228 229 230 231 232
}

/**
 *
 * @param uas the UAS/MAV to monitor/display with the HUD
 */
void PrimaryFlightDisplay::setActiveUAS(UASInterface* uas)
{
tstellanova's avatar
tstellanova committed
233 234 235
    if (uas == this->uas)
        return; //no need to rewire

236 237
    // Disconnect the previous one (if any)
    forgetUAS(this->uas);
238 239 240 241 242 243

    if (uas) {
        // Now connect the new UAS
        // Setup communication
        connect(uas, SIGNAL(attitudeChanged(UASInterface*,double,double,double,quint64)), this, SLOT(updateAttitude(UASInterface*, double, double, double, quint64)));
        connect(uas, SIGNAL(attitudeChanged(UASInterface*,int,double,double,double,quint64)), this, SLOT(updateAttitude(UASInterface*,int,double, double, double, quint64)));
244
        connect(uas, SIGNAL(speedChanged(UASInterface*, double, double, quint64)), this, SLOT(updateSpeed(UASInterface*, double, double, quint64)));
245
        connect(uas, SIGNAL(altitudeChanged(UASInterface*, double, double, double, double, quint64)), this, SLOT(updateAltitude(UASInterface*, double, double, double, double, quint64)));
246
        connect(uas, SIGNAL(navigationControllerErrorsChanged(UASInterface*, double, double, double)), this, SLOT(updateNavigationControllerErrors(UASInterface*, double, double, double)));
247 248 249 250 251 252 253 254 255 256

        // Set new UAS
        this->uas = uas;
    }
}

void PrimaryFlightDisplay::updateAttitude(UASInterface* uas, double roll, double pitch, double yaw, quint64 timestamp)
{
    Q_UNUSED(uas);
    Q_UNUSED(timestamp);
257

258
        if (isinf(roll)) {
Don Gagne's avatar
Don Gagne committed
259
            this->roll = std::numeric_limits<double>::quiet_NaN();
260
        } else {
261 262 263 264 265 266 267 268

            float rolldeg = roll * (180.0 / M_PI);

            if (fabsf(roll - rolldeg) > 2.5f) {
                _valuesChanged = true;
            }

            this->roll = rolldeg;
269
        }
270

271
        if (isinf(pitch)) {
Don Gagne's avatar
Don Gagne committed
272
            this->pitch = std::numeric_limits<double>::quiet_NaN();
273
        } else {
274 275 276 277 278 279 280 281

            float pitchdeg = pitch * (180.0 / M_PI);

            if (fabsf(pitch - pitchdeg) > 2.5f) {
                _valuesChanged = true;
            }

            this->pitch = pitchdeg;
282 283
        }

284
        if (isinf(yaw)) {
Don Gagne's avatar
Don Gagne committed
285
            this->heading = std::numeric_limits<double>::quiet_NaN();
286
        } else {
287

288 289
            yaw = yaw * (180.0 / M_PI);
            if (yaw<0) yaw+=360;
290 291 292 293 294

            if (fabsf(heading - yaw) > 10.0f) {
                _valuesChanged = true;
            }

295 296
            this->heading = yaw;
        }
Lorenz Meier's avatar
Lorenz Meier committed
297

298 299 300 301 302
}

void PrimaryFlightDisplay::updateAttitude(UASInterface* uas, int component, double roll, double pitch, double yaw, quint64 timestamp)
{
    Q_UNUSED(component);
303
    this->updateAttitude(uas, roll, pitch, yaw, timestamp);
304 305
}

306
void PrimaryFlightDisplay::updateSpeed(UASInterface* uas, double _groundSpeed, double _airSpeed, quint64 timestamp)
307 308 309 310
{
    Q_UNUSED(uas);
    Q_UNUSED(timestamp);

311 312 313 314 315 316 317 318
    if (fabsf(groundSpeed - _groundSpeed) > 0.5f) {
        _valuesChanged = true;
    }

    if (fabsf(airSpeed - _airSpeed) > 1.0f) {
        _valuesChanged = true;
    }

319 320
    groundSpeed = _groundSpeed;
    airSpeed = _airSpeed;
321
}
322

323
void PrimaryFlightDisplay::updateAltitude(UASInterface* uas, double _altitudeAMSL, double _altitudeWGS84, double _altitudeRelative, double _climbRate, quint64 timestamp) {
324 325
    Q_UNUSED(uas);
    Q_UNUSED(timestamp);
326 327 328 329 330

    if (fabsf(altitudeAMSL - _altitudeAMSL) > 0.5f) {
        _valuesChanged = true;
    }

331 332 333 334
    if (fabsf(altitudeWGS84 - _altitudeWGS84) > 0.5f) {
        _valuesChanged = true;
    }

335 336 337 338 339 340 341 342
    if (fabsf(altitudeRelative - _altitudeRelative) > 0.5f) {
        _valuesChanged = true;
    }

    if (fabsf(climbRate - _climbRate) > 0.5f) {
        _valuesChanged = true;
    }

343
    altitudeAMSL = _altitudeAMSL;
344
    altitudeWGS84 = _altitudeWGS84;
345 346
    altitudeRelative = _altitudeRelative;
    climbRate = _climbRate;
347 348
}

349 350 351 352 353 354 355 356
void PrimaryFlightDisplay::updateNavigationControllerErrors(UASInterface* uas, double altitudeError, double speedError, double xtrackError) {
    Q_UNUSED(uas);
    this->navigationAltitudeError = altitudeError;
    this->navigationSpeedError = speedError;
    this->navigationCrosstrackError = xtrackError;
}


357 358 359
/*
 * Private and such
 */
360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375

// TODO: Move to UAS. Real working implementation.
bool PrimaryFlightDisplay::isAirplane() {
    if (!this->uas)
        return false;
    switch(this->uas->getSystemType()) {
    case MAV_TYPE_GENERIC:
    case MAV_TYPE_FIXED_WING:
    case MAV_TYPE_AIRSHIP:
    case MAV_TYPE_FLAPPING_WING:
        return true;
    default:
        return false;
    }
}

376 377 378 379 380 381 382
// TODO: Implement. Should return true when navigating.
// That would be (APM) in AUTO and RTL modes.
// This could forward to a virtual on UAS bool isNavigatingAutonomusly() or whatever.
bool PrimaryFlightDisplay::shouldDisplayNavigationData() {
    return true;
}

383 384 385
void PrimaryFlightDisplay::drawTextCenter (
        QPainter& painter,
        QString text,
dongfang's avatar
dongfang committed
386
        float pixelSize,
387 388 389
        float x,
        float y)
{
dongfang's avatar
dongfang committed
390
    font.setPixelSize(pixelSize);
391 392 393 394 395
    painter.setFont(font);

    QFontMetrics metrics = QFontMetrics(font);
    QRect bounds = metrics.boundingRect(text);
    int flags = Qt::AlignCenter |  Qt::TextDontClip; // For some reason the bounds rect is too small!
396
    painter.drawText(x - bounds.width()/2, y - bounds.height()/2, bounds.width(), bounds.height(), flags, text);
397 398 399 400 401
}

void PrimaryFlightDisplay::drawTextLeftCenter (
        QPainter& painter,
        QString text,
dongfang's avatar
dongfang committed
402
        float pixelSize,
403 404 405
        float x,
        float y)
{
dongfang's avatar
dongfang committed
406
    font.setPixelSize(pixelSize);
407 408 409 410 411
    painter.setFont(font);

    QFontMetrics metrics = QFontMetrics(font);
    QRect bounds = metrics.boundingRect(text);
    int flags = Qt::AlignLeft | Qt::TextDontClip; // For some reason the bounds rect is too small!
412
    painter.drawText(x, y - bounds.height()/2, bounds.width(), bounds.height(), flags, text);
413 414 415 416 417
}

void PrimaryFlightDisplay::drawTextRightCenter (
        QPainter& painter,
        QString text,
dongfang's avatar
dongfang committed
418
        float pixelSize,
419 420 421
        float x,
        float y)
{
dongfang's avatar
dongfang committed
422
    font.setPixelSize(pixelSize);
423 424 425 426 427
    painter.setFont(font);

    QFontMetrics metrics = QFontMetrics(font);
    QRect bounds = metrics.boundingRect(text);
    int flags = Qt::AlignRight | Qt::TextDontClip; // For some reason the bounds rect is too small!
428
    painter.drawText(x - bounds.width(), y - bounds.height()/2, bounds.width(), bounds.height(), flags, text);
429 430 431 432 433
}

void PrimaryFlightDisplay::drawTextCenterTop (
        QPainter& painter,
        QString text,
dongfang's avatar
dongfang committed
434
        float pixelSize,
435 436 437
        float x,
        float y)
{
dongfang's avatar
dongfang committed
438
    font.setPixelSize(pixelSize);
439 440 441 442 443
    painter.setFont(font);

    QFontMetrics metrics = QFontMetrics(font);
    QRect bounds = metrics.boundingRect(text);
    int flags = Qt::AlignCenter | Qt::TextDontClip; // For some reason the bounds rect is too small!
444
    painter.drawText(x - bounds.width()/2, y+bounds.height(), bounds.width(), bounds.height(), flags, text);
445 446 447 448 449
}

void PrimaryFlightDisplay::drawTextCenterBottom (
        QPainter& painter,
        QString text,
dongfang's avatar
dongfang committed
450
        float pixelSize,
451 452 453
        float x,
        float y)
{
dongfang's avatar
dongfang committed
454
    font.setPixelSize(pixelSize);
455 456
    painter.setFont(font);

457
    QFontMetrics metrics(font);
458 459
    QRect bounds = metrics.boundingRect(text);
    int flags = Qt::AlignCenter;
460
    painter.drawText(x - bounds.width()/2, y, bounds.width(), bounds.height(), flags, text);
461 462 463 464 465 466 467 468 469 470 471 472 473 474
}

void PrimaryFlightDisplay::drawInstrumentBackground(QPainter& painter, QRectF edge) {
    painter.setPen(instrumentEdgePen);
    painter.drawRect(edge);
}

void PrimaryFlightDisplay::fillInstrumentBackground(QPainter& painter, QRectF edge) {
    painter.setPen(instrumentEdgePen);
    painter.setBrush(instrumentBackground);
    painter.drawRect(edge);
    painter.setBrush(Qt::NoBrush);
}

dongfang's avatar
dongfang committed
475 476 477 478 479 480 481
void PrimaryFlightDisplay::fillInstrumentOpagueBackground(QPainter& painter, QRectF edge) {
    painter.setPen(instrumentEdgePen);
    painter.setBrush(instrumentOpagueBackground);
    painter.drawRect(edge);
    painter.setBrush(Qt::NoBrush);
}

482
qreal pitchAngleToTranslation(qreal viewHeight, float pitch) {
483
    if (isnan(pitch))
484
        return 0;
485
    return pitch * viewHeight / PITCHTRANSLATION;
486 487 488 489 490 491 492 493 494 495 496 497 498 499 500
}

void PrimaryFlightDisplay::drawAIAirframeFixedFeatures(QPainter& painter, QRectF area) {
    // red line from -7/10 to -5/10 half-width
    // red line from 7/10 to 5/10 half-width
    // red slanted line from -2/10 half-width to 0
    // red slanted line from 2/10 half-width to 0
    // red arrow thing under roll scale
    // prepareTransform(painter, width, height);
    painter.resetTransform();
    painter.translate(area.center());

    qreal w = area.width();
    qreal h = area.height();

dongfang's avatar
dongfang committed
501
    QPen pen;
502
    pen.setWidthF(lineWidth * 1.5f);
dongfang's avatar
dongfang committed
503 504
    pen.setColor(redColor);
    painter.setPen(pen);
505

506 507
    float length = 0.15f;
    float side = 0.5f;
dongfang's avatar
dongfang committed
508 509 510 511
    // The 2 lines at sides.
    painter.drawLine(QPointF(-side*w, 0), QPointF(-(side-length)*w, 0));
    painter.drawLine(QPointF(side*w, 0), QPointF((side-length)*w, 0));

512
    float rel = length/qSqrt(2.0f);
dongfang's avatar
dongfang committed
513 514 515 516 517 518 519 520
    // The gull
    painter.drawLine(QPointF(rel*w, rel*w/2), QPoint(0, 0));
    painter.drawLine(QPointF(-rel*w, rel*w/2), QPoint(0, 0));

    // The roll scale marker.
    QPainterPath markerPath(QPointF(0, -w*ROLL_SCALE_RADIUS+1));
    markerPath.lineTo(-h*ROLL_SCALE_MARKERWIDTH/2, -w*(ROLL_SCALE_RADIUS-ROLL_SCALE_MARKERHEIGHT)+1);
    markerPath.lineTo(h*ROLL_SCALE_MARKERWIDTH/2, -w*(ROLL_SCALE_RADIUS-ROLL_SCALE_MARKERHEIGHT)+1);
521 522 523 524
    markerPath.closeSubpath();
    painter.drawPath(markerPath);
}

525 526 527 528 529 530 531 532 533 534 535 536 537 538
inline qreal min4(qreal a, qreal b, qreal c, qreal d) {
    if(b<a) a=b;
    if(c<a) a=c;
    if(d<a) a=d;
    return a;
}

inline qreal max4(qreal a, qreal b, qreal c, qreal d) {
    if(b>a) a=b;
    if(c>a) a=c;
    if(d>a) a=d;
    return a;
}

539 540
void PrimaryFlightDisplay::drawAIGlobalFeatures(
        QPainter& painter,
541 542
        QRectF mainArea,
        QRectF paintArea) {
543

544
    float displayRoll = this->roll;
545
    if (isnan(displayRoll))
546 547
        displayRoll = 0;

548
    painter.resetTransform();
549
    painter.translate(mainArea.center());
550

551 552
    qreal pitchPixels = pitchAngleToTranslation(mainArea.height(), pitch);
    qreal gradientEnd = pitchAngleToTranslation(mainArea.height(), 60);
553

554
    painter.rotate(-displayRoll);
555 556 557 558 559 560 561 562 563
    painter.translate(0, pitchPixels);

    // Calculate the radius of area we need to paint to cover all.
    QTransform rtx = painter.transform().inverted();

    QPointF topLeft = rtx.map(paintArea.topLeft());
    QPointF topRight = rtx.map(paintArea.topRight());
    QPointF bottomLeft = rtx.map(paintArea.bottomLeft());
    QPointF bottomRight = rtx.map(paintArea.bottomRight());
564

565 566 567 568 569 570 571 572 573 574 575 576 577
    // Just KISS... make a rectangluar basis.
    qreal minx = min4(topLeft.x(), topRight.x(), bottomLeft.x(), bottomRight.x());
    qreal maxx = max4(topLeft.x(), topRight.x(), bottomLeft.x(), bottomRight.x());
    qreal miny = min4(topLeft.y(), topRight.y(), bottomLeft.y(), bottomRight.y());
    qreal maxy = max4(topLeft.y(), topRight.y(), bottomLeft.y(), bottomRight.y());

    QPointF hzonLeft = QPoint(minx, 0);
    QPointF hzonRight = QPoint(maxx, 0);

    QPainterPath skyPath(hzonLeft);
    skyPath.lineTo(QPointF(minx, miny));
    skyPath.lineTo(QPointF(maxx, miny));
    skyPath.lineTo(hzonRight);
578 579
    skyPath.closeSubpath();

580
    QLinearGradient skyGradient(0, -gradientEnd, 0, 0);
581 582 583 584 585
    skyGradient.setColorAt(0, QColor::fromHsvF(0.6, 1.0, 0.7));
    skyGradient.setColorAt(1, QColor::fromHsvF(0.6, 0.25, 0.9));
    QBrush skyBrush(skyGradient);
    painter.fillPath(skyPath, skyBrush);

586 587 588 589
    QPainterPath groundPath(hzonRight);
    groundPath.lineTo(maxx, maxy);
    groundPath.lineTo(minx, maxy);
    groundPath.lineTo(hzonLeft);
590 591
    groundPath.closeSubpath();

592
    QLinearGradient groundGradient(0, gradientEnd, 0, 0);
593 594 595 596 597
    groundGradient.setColorAt(0, QColor::fromHsvF(0.25, 1, 0.5));
    groundGradient.setColorAt(1, QColor::fromHsvF(0.25, 0.25, 0.5));
    QBrush groundBrush(groundGradient);
    painter.fillPath(groundPath, groundBrush);

dongfang's avatar
dongfang committed
598 599 600 601 602
    QPen pen;
    pen.setWidthF(lineWidth);
    pen.setColor(greenColor);
    painter.setPen(pen);

603 604
    QPointF start(-mainArea.width(), 0);
    QPoint end(mainArea.width(), 0);
605 606 607 608 609 610
    painter.drawLine(start, end);
}

void PrimaryFlightDisplay::drawPitchScale(
        QPainter& painter,
        QRectF area,
dongfang's avatar
dongfang committed
611
        float intrusion,
612 613 614 615
        bool drawNumbersLeft,
        bool drawNumbersRight
        ) {

616 617
    Q_UNUSED(intrusion);
    
618
    float displayPitch = this->pitch;
619
    if (isnan(displayPitch))
620 621
        displayPitch = 0;

dongfang's avatar
dongfang committed
622 623 624
    // The area should be quadratic but if not width is the major size.
    qreal w = area.width();
    if (w<area.height()) w = area.height();
625

dongfang's avatar
dongfang committed
626 627 628 629
    QPen pen;
    pen.setWidthF(lineWidth);
    pen.setColor(Qt::white);
    painter.setPen(pen);
630 631 632 633

    QTransform savedTransform = painter.transform();

    // find the mark nearest center
634
    int snap = qRound((double)(displayPitch/PITCH_SCALE_RESOLUTION))*PITCH_SCALE_RESOLUTION;
635 636 637 638
    int _min = snap-PITCH_SCALE_HALFRANGE;
    int _max = snap+PITCH_SCALE_HALFRANGE;
    for (int degrees=_min; degrees<=_max; degrees+=PITCH_SCALE_RESOLUTION) {
        bool isMajor = degrees % (PITCH_SCALE_RESOLUTION*2) == 0;
639 640 641 642 643 644 645 646 647 648 649 650
        float linewidth =  isMajor ? PITCH_SCALE_MAJORWIDTH : PITCH_SCALE_MINORWIDTH;
        if (abs(degrees) > PITCH_SCALE_WIDTHREDUCTION_FROM) {
            // we want: 1 at PITCH_SCALE_WIDTHREDUCTION_FROM and PITCH_SCALE_WIDTHREDUCTION at 90.
            // That is PITCH_SCALE_WIDTHREDUCTION + (1-PITCH_SCALE_WIDTHREDUCTION) * f(pitch)
            // where f(90)=0 and f(PITCH_SCALE_WIDTHREDUCTION_FROM)=1
            // f(p) = (90-p) * 1/(90-PITCH_SCALE_WIDTHREDUCTION_FROM)
            // or PITCH_SCALE_WIDTHREDUCTION + f(pitch) - f(pitch) * PITCH_SCALE_WIDTHREDUCTION
            // or PITCH_SCALE_WIDTHREDUCTION (1-f(pitch)) + f(pitch)
            int fromVertical = abs(pitch>=0 ? 90-pitch : -90-pitch);
            float temp = fromVertical * 1/(90.0f-PITCH_SCALE_WIDTHREDUCTION_FROM);
            linewidth *= (PITCH_SCALE_WIDTHREDUCTION * (1-temp) + temp);
        }
dongfang's avatar
dongfang committed
651

652
        float shift = pitchAngleToTranslation(w, displayPitch-degrees);
dongfang's avatar
dongfang committed
653 654 655 656

        // TODO: Intrusion detection and evasion. That is, don't draw
        // where the compass has intruded.

657
        painter.translate(0, shift);
dongfang's avatar
dongfang committed
658 659
        QPointF start(-linewidth*w, 0);
        QPointF end(linewidth*w, 0);
660 661 662 663 664 665 666
        painter.drawLine(start, end);

        if (isMajor && (drawNumbersLeft||drawNumbersRight)) {
            int displayDegrees = degrees;
            if(displayDegrees>90) displayDegrees = 180-displayDegrees;
            else if (displayDegrees<-90) displayDegrees = -180 - displayDegrees;
            if (SHOW_ZERO_ON_SCALES || degrees) {
667
                QString s_number;
668
                if (isnan(this->pitch))
669 670 671
                    s_number.sprintf("-");
                else
                    s_number.sprintf("%d", displayDegrees);
dongfang's avatar
dongfang committed
672 673
                if (drawNumbersLeft)  drawTextRightCenter(painter, s_number, mediumTextSize, -PITCH_SCALE_MAJORWIDTH * w-10, 0);
                if (drawNumbersRight) drawTextLeftCenter(painter, s_number, mediumTextSize, PITCH_SCALE_MAJORWIDTH * w+10, 0);
674 675 676 677 678 679 680 681 682 683 684 685 686
            }
        }

        painter.setTransform(savedTransform);
    }
}

void PrimaryFlightDisplay::drawRollScale(
        QPainter& painter,
        QRectF area,
        bool drawTicks,
        bool drawNumbers) {

687
    qreal w = area.width();
dongfang's avatar
dongfang committed
688
    if (w<area.height()) w = area.height();
689

dongfang's avatar
dongfang committed
690 691 692 693
    QPen pen;
    pen.setWidthF(lineWidth);
    pen.setColor(Qt::white);
    painter.setPen(pen);
694

695
    // We should really do these transforms but they are assumed done by caller:
696 697 698 699
    // painter.resetTransform();
    // painter.translate(area.center());
    // painter.rotate(roll);

700
    qreal _size = w * ROLL_SCALE_RADIUS*2;
dongfang's avatar
dongfang committed
701
    QRectF arcArea(-_size/2, - _size/2, _size, _size);
702 703 704 705 706 707 708 709 710 711 712 713 714 715
    painter.drawArc(arcArea, (90-ROLL_SCALE_RANGE)*16, ROLL_SCALE_RANGE*2*16);
    if (drawTicks) {
        int length = sizeof(tickValues)/sizeof(int);
        qreal previousRotation = 0;
        for (int i=0; i<length*2+1; i++) {
            int degrees = (i==length) ? 0 : (i>length) ?-tickValues[i-length-1] : tickValues[i];
            painter.rotate(degrees - previousRotation);
            previousRotation = degrees;

            QPointF start(0, -_size/2);
            QPointF end(0, -(1.0+ROLL_SCALE_TICKMARKLENGTH)*_size/2);

            painter.drawLine(start, end);

716
            QString s_number;
717 718 719 720
            if (SHOW_ZERO_ON_SCALES || degrees)
                s_number.sprintf("%d", abs(degrees));

            if (drawNumbers) {
721
                drawTextCenterBottom(painter, s_number, mediumTextSize, 0, -(ROLL_SCALE_RADIUS+ROLL_SCALE_TICKMARKLENGTH*1.7)*w);
722 723 724 725 726 727 728
            }
        }
    }
}

void PrimaryFlightDisplay::drawAIAttitudeScales(
        QPainter& painter,
dongfang's avatar
dongfang committed
729 730
        QRectF area,
        float intrusion
dongfang's avatar
dongfang committed
731
        ) {
732
    float displayRoll = this->roll;
733
    if (isnan(displayRoll))
734
        displayRoll = 0;
735 736 737
    // To save computations, we do these transformations once for both scales:
    painter.resetTransform();
    painter.translate(area.center());
738
    painter.rotate(-displayRoll);
739 740 741 742
    QTransform saved = painter.transform();

    drawRollScale(painter, area, true, true);
    painter.setTransform(saved);
dongfang's avatar
dongfang committed
743
    drawPitchScale(painter, area, intrusion, true, true);
744 745
}

dongfang's avatar
dongfang committed
746
void PrimaryFlightDisplay::drawAICompassDisk(QPainter& painter, QRectF area, float halfspan) {
747
    float displayHeading = this->heading;
748
    if (isnan(displayHeading))
749 750 751 752
        displayHeading = 0;

    float start = displayHeading - halfspan;
    float end = displayHeading + halfspan;
dongfang's avatar
dongfang committed
753

dongfang's avatar
dongfang committed
754 755
    int firstTick = ceil(start / COMPASS_DISK_RESOLUTION) * COMPASS_DISK_RESOLUTION;
    int lastTick = floor(end / COMPASS_DISK_RESOLUTION) * COMPASS_DISK_RESOLUTION;
756 757 758 759

    float radius = area.width()/2;
    float innerRadius = radius * 0.96;
    painter.resetTransform();
dongfang's avatar
dongfang committed
760 761
    painter.setBrush(instrumentBackground);
    painter.setPen(instrumentEdgePen);
762 763 764
    painter.drawEllipse(area);
    painter.setBrush(Qt::NoBrush);

dongfang's avatar
dongfang committed
765 766
    QPen scalePen(Qt::black);
    scalePen.setWidthF(fineLineWidth);
767

dongfang's avatar
dongfang committed
768
    for (int tickYaw = firstTick; tickYaw <= lastTick; tickYaw += COMPASS_DISK_RESOLUTION) {
769 770 771 772 773
        int displayTick = tickYaw;
        if (displayTick < 0) displayTick+=360;
        else if (displayTick>=360) displayTick-=360;

        // yaw is in center.
774
        float off = tickYaw - displayHeading;
775
        // wrap that to [-180..180]
dongfang's avatar
dongfang committed
776
        if (off<=-180) off+= 360; else if (off>180) off -= 360;
777 778 779 780

        painter.translate(area.center());
        painter.rotate(off);
        bool drewArrow = false;
dongfang's avatar
dongfang committed
781
        bool isMajor = displayTick % COMPASS_DISK_MAJORTICK == 0;
782

783
        // If heading unknown, still draw marks but no numbers.
784
        if (!isnan(this->heading) &&
785
                (displayTick==30 || displayTick==60 ||
dongfang's avatar
dongfang committed
786 787
                displayTick==120 || displayTick==150 ||
                displayTick==210 || displayTick==240 ||
788 789
                displayTick==300 || displayTick==330)
        ) {
790 791 792
            // draw a number
            QString s_number;
            s_number.sprintf("%d", displayTick/10);
dongfang's avatar
dongfang committed
793
            painter.setPen(scalePen);
794
            drawTextCenter(painter, s_number, smallTextSize, 0, -innerRadius*0.75);
795
        } else {
dongfang's avatar
dongfang committed
796
            if (displayTick % COMPASS_DISK_ARROWTICK == 0) {
797 798 799 800 801
                if (displayTick!=0) {
                    QPainterPath markerPath(QPointF(0, -innerRadius*(1-COMPASS_DISK_MARKERHEIGHT/2)));
                    markerPath.lineTo(innerRadius*COMPASS_DISK_MARKERWIDTH/4, -innerRadius);
                    markerPath.lineTo(-innerRadius*COMPASS_DISK_MARKERWIDTH/4, -innerRadius);
                    markerPath.closeSubpath();
dongfang's avatar
dongfang committed
802
                    painter.setPen(scalePen);
803 804 805 806 807
                    painter.setBrush(Qt::SolidPattern);
                    painter.drawPath(markerPath);
                    painter.setBrush(Qt::NoBrush);
                    drewArrow = true;
                }
808
                // If heading unknown, still draw marks but no N S E W.
809
                if (!isnan(this->heading) && displayTick%90 == 0) {
810
                    // Also draw a label
dongfang's avatar
dongfang committed
811 812 813
                    QString name = compassWindNames[displayTick / 45];
                    painter.setPen(scalePen);
                    drawTextCenter(painter, name, mediumTextSize, 0, -innerRadius*0.75);
814
                }
dongfang's avatar
dongfang committed
815
            }
816
        }
dongfang's avatar
dongfang committed
817
        // draw the scale lines. If an arrow was drawn, stay off from it.
818

dongfang's avatar
dongfang committed
819 820
        QPointF p_start = drewArrow ? QPoint(0, -innerRadius*0.94) : QPoint(0, -innerRadius);
        QPoint p_end = isMajor ? QPoint(0, -innerRadius*0.86) : QPoint(0, -innerRadius*0.90);
821

dongfang's avatar
dongfang committed
822
        painter.setPen(scalePen);
823 824 825 826
        painter.drawLine(p_start, p_end);
        painter.resetTransform();
    }

dongfang's avatar
dongfang committed
827
    painter.setPen(scalePen);
828 829 830 831 832 833 834
    painter.translate(area.center());
    QPainterPath markerPath(QPointF(0, -radius-2));
    markerPath.lineTo(radius*COMPASS_DISK_MARKERWIDTH/2,  -radius-radius*COMPASS_DISK_MARKERHEIGHT-2);
    markerPath.lineTo(-radius*COMPASS_DISK_MARKERWIDTH/2, -radius-radius*COMPASS_DISK_MARKERHEIGHT-2);
    markerPath.closeSubpath();
    painter.drawPath(markerPath);

dongfang's avatar
dongfang committed
835 836 837 838 839 840 841 842 843
    qreal digitalCompassYCenter = -radius*0.52;
    qreal digitalCompassHeight = radius*0.28;

    QPointF digitalCompassBottom(0, digitalCompassYCenter+digitalCompassHeight);
    QPointF  digitalCompassAbsoluteBottom = painter.transform().map(digitalCompassBottom);

    qreal digitalCompassUpshift = digitalCompassAbsoluteBottom.y()>height() ? digitalCompassAbsoluteBottom.y()-height() : 0;

    QRectF digitalCompassRect(-radius/3, -radius*0.52-digitalCompassUpshift, radius*2/3, radius*0.28);
844
    painter.setPen(instrumentEdgePen);
dongfang's avatar
dongfang committed
845
    painter.drawRoundedRect(digitalCompassRect, instrumentEdgePen.widthF()*2/3, instrumentEdgePen.widthF()*2/3);
846

dongfang's avatar
dongfang committed
847
    QString s_digitalCompass;
848

849
    if (isnan(this->heading))
850 851 852 853 854 855
        s_digitalCompass.sprintf("---");
    else {
    /* final safeguard for really stupid systems */
        int digitalCompassValue = static_cast<int>(qRound((double)heading)) % 360;
        s_digitalCompass.sprintf("%03d", digitalCompassValue);
    }
856

dongfang's avatar
dongfang committed
857 858 859 860
    QPen pen;
    pen.setWidthF(lineWidth);
    pen.setColor(Qt::white);
    painter.setPen(pen);
861

dongfang's avatar
dongfang committed
862
    drawTextCenter(painter, s_digitalCompass, largeTextSize, 0, -radius*0.38-digitalCompassUpshift);
863

864
    // The CDI
865
    if (shouldDisplayNavigationData() && !isnan(navigationTargetBearing) && !isinf(navigationCrosstrackError)) {
866 867 868 869 870 871 872 873 874 875 876 877 878
        painter.resetTransform();
        painter.translate(area.center());
        // TODO : Sign might be wrong?
        // TODO : The case where error exceeds max. Truncate to max. and make that visible somehow.
        bool errorBeyondRadius = false;
        if (abs(navigationCrosstrackError) > CROSSTRACK_MAX) {
            errorBeyondRadius = true;
            navigationCrosstrackError = navigationCrosstrackError>0 ? CROSSTRACK_MAX : -CROSSTRACK_MAX;
        }

        float r = radius * CROSSTRACK_RADIUS;
        float x = navigationCrosstrackError / CROSSTRACK_MAX * r;
        float y = qSqrt(r*r - x*x); // the positive y, there is also a negative.
dongfang's avatar
dongfang committed
879

880 881 882 883 884 885 886
        float sillyHeading = 0;
        float angle = sillyHeading - navigationTargetBearing; // TODO: sign.
        painter.rotate(-angle);

        QPen pen;
        pen.setWidthF(lineWidth);
        pen.setColor(Qt::black);
887 888 889
        if(errorBeyondRadius) {
            pen.setStyle(Qt::DotLine);
        }
890 891 892 893 894 895
        painter.setPen(pen);

        painter.drawLine(QPointF(x, y), QPointF(x, -y));
    }
}

896 897
void PrimaryFlightDisplay::drawAltimeter(
        QPainter& painter,
898
        QRectF area
899
    ) {
900

901
    float primaryAltitude = altitudeWGS84;
902
    float secondaryAltitude = std::numeric_limits<double>::quiet_NaN();
903

904
    painter.resetTransform();
905
    fillInstrumentBackground(painter, area);
906

dongfang's avatar
dongfang committed
907 908
    QPen pen;
    pen.setWidthF(lineWidth);
dongfang's avatar
dongfang committed
909

dongfang's avatar
dongfang committed
910 911
    pen.setColor(Qt::white);
    painter.setPen(pen);
912

dongfang's avatar
dongfang committed
913 914
    float h = area.height();
    float w = area.width();
915
    float secondaryAltitudeBoxHeight = mediumTextSize * 2;
dongfang's avatar
dongfang committed
916
    // The height where we being with new tickmarks.
dongfang's avatar
dongfang committed
917
    float effectiveHalfHeight = h*0.45;
918 919

    // not yet implemented: Display of secondary altitude.
920
    if (!isnan(secondaryAltitude)) {
921
        effectiveHalfHeight -= secondaryAltitudeBoxHeight;
922
    }
923

dongfang's avatar
dongfang committed
924 925 926 927 928 929
    float markerHalfHeight = mediumTextSize*0.8;
    float leftEdge = instrumentEdgePen.widthF()*2;
    float rightEdge = w-leftEdge;
    float tickmarkLeft = leftEdge;
    float tickmarkRightMajor = tickmarkLeft+TAPE_GAUGES_TICKWIDTH_MAJOR*w;
    float tickmarkRightMinor = tickmarkLeft+TAPE_GAUGES_TICKWIDTH_MINOR*w;
dongfang's avatar
dongfang committed
930
    float numbersLeft = 0.42*w;
dongfang's avatar
dongfang committed
931
    float markerTip = (tickmarkLeft*2+tickmarkRightMajor)/3;
932
    float scaleCenterAltitude = isnan(primaryAltitude) ? 0 : primaryAltitude;
933 934

    // altitude scale
dongfang's avatar
dongfang committed
935 936
    float start = scaleCenterAltitude - ALTIMETER_LINEAR_SPAN/2;
    float end = scaleCenterAltitude + ALTIMETER_LINEAR_SPAN/2;
937 938 939
    int firstTick = ceil(start / ALTIMETER_LINEAR_RESOLUTION) * ALTIMETER_LINEAR_RESOLUTION;
    int lastTick = floor(end / ALTIMETER_LINEAR_RESOLUTION) * ALTIMETER_LINEAR_RESOLUTION;
    for (int tickAlt = firstTick; tickAlt <= lastTick; tickAlt += ALTIMETER_LINEAR_RESOLUTION) {
dongfang's avatar
dongfang committed
940 941
        float y = (tickAlt-scaleCenterAltitude)*effectiveHalfHeight/(ALTIMETER_LINEAR_SPAN/2);
        bool isMajor = tickAlt % ALTIMETER_LINEAR_MAJOR_RESOLUTION == 0;
942

943 944
        painter.resetTransform();
        painter.translate(area.left(), area.center().y() - y);
dongfang's avatar
dongfang committed
945 946 947 948
        pen.setColor(tickAlt<0 ? redColor : Qt::white);
        painter.setPen(pen);
        if (isMajor) {
            painter.drawLine(tickmarkLeft, 0, tickmarkRightMajor, 0);
949
            QString s_alt;
dongfang's avatar
dongfang committed
950
            s_alt.sprintf("%d", abs(tickAlt));
dongfang's avatar
dongfang committed
951
            drawTextLeftCenter(painter, s_alt, mediumTextSize, numbersLeft, 0);
dongfang's avatar
dongfang committed
952 953
        } else {
            painter.drawLine(tickmarkLeft, 0, tickmarkRightMinor, 0);
954 955 956 957 958 959 960 961 962 963 964 965
        }
    }

    QPainterPath markerPath(QPoint(markerTip, 0));
    markerPath.lineTo(markerTip+markerHalfHeight, markerHalfHeight);
    markerPath.lineTo(rightEdge, markerHalfHeight);
    markerPath.lineTo(rightEdge, -markerHalfHeight);
    markerPath.lineTo(markerTip+markerHalfHeight, -markerHalfHeight);
    markerPath.closeSubpath();

    painter.resetTransform();
    painter.translate(area.left(), area.center().y());
dongfang's avatar
dongfang committed
966

dongfang's avatar
dongfang committed
967
    pen.setWidthF(lineWidth);
dongfang's avatar
dongfang committed
968 969 970
    pen.setColor(Qt::white);
    painter.setPen(pen);

971 972 973 974
    painter.setBrush(Qt::SolidPattern);
    painter.drawPath(markerPath);
    painter.setBrush(Qt::NoBrush);

dongfang's avatar
dongfang committed
975 976
    pen.setColor(Qt::white);
    painter.setPen(pen);
dongfang's avatar
dongfang committed
977

978
    QString s_alt;
979
    if (isnan(primaryAltitude))
dongfang's avatar
dongfang committed
980 981 982 983
        s_alt.sprintf("---");
    else
        s_alt.sprintf("%3.0f", primaryAltitude);

984
    float xCenter = (markerTip+rightEdge)/2;
985
    drawTextCenter(painter, s_alt, mediumTextSize, xCenter, 0);
dongfang's avatar
dongfang committed
986

987
    // draw simple in-tape VVI.
988 989 990 991
    if (!isnan(climbRate)) {
        float vvPixHeight = -climbRate/ALTIMETER_VVI_SPAN * effectiveHalfHeight;
        if (abs (vvPixHeight) < markerHalfHeight)
            return; // hidden behind marker.
dongfang's avatar
dongfang committed
992

993
        float vvSign = vvPixHeight>0 ? 1 : -1; // reverse y sign
dongfang's avatar
dongfang committed
994

995 996 997
        QPointF vvArrowBegin(rightEdge - w*ALTIMETER_VVI_WIDTH/2, markerHalfHeight*vvSign);
        QPointF vvArrowEnd(rightEdge - w*ALTIMETER_VVI_WIDTH/2, vvPixHeight);
        painter.drawLine(vvArrowBegin, vvArrowEnd);
dongfang's avatar
dongfang committed
998

999
        // Yeah this is a repetition of above code but we are going to trash it all anyway, so no fix.
1000 1001
        float vvArowHeadSize = abs(vvPixHeight - markerHalfHeight*vvSign);
        if (vvArowHeadSize > w*ALTIMETER_VVI_WIDTH/3) vvArowHeadSize = w*ALTIMETER_VVI_WIDTH/3;
1002

1003
        float xcenter = rightEdge-w*ALTIMETER_VVI_WIDTH/2;
1004

1005 1006
        QPointF vvArrowHead(xcenter+vvArowHeadSize, vvPixHeight - vvSign *vvArowHeadSize);
        painter.drawLine(vvArrowHead, vvArrowEnd);
dongfang's avatar
dongfang committed
1007

1008 1009
        vvArrowHead = QPointF(xcenter-vvArowHeadSize, vvPixHeight - vvSign * vvArowHeadSize);
        painter.drawLine(vvArrowHead, vvArrowEnd);
1010 1011 1012
    }

    // print secondary altitude
1013
    if (!isnan(secondaryAltitude)) {
1014 1015 1016 1017 1018 1019 1020
        QRectF saBox(area.x(), area.y()-secondaryAltitudeBoxHeight, w, secondaryAltitudeBoxHeight);
        painter.resetTransform();
        painter.translate(saBox.center());
        QString s_salt;
        s_salt.sprintf("%3.0f", secondaryAltitude);
        drawTextCenter(painter, s_salt, mediumTextSize, 0, 0);
    }
1021 1022
}

1023 1024
void PrimaryFlightDisplay::drawVelocityMeter(
        QPainter& painter,
1025
        QRectF area
1026 1027 1028
        ) {

    painter.resetTransform();
1029
    fillInstrumentBackground(painter, area);
1030 1031 1032 1033 1034 1035 1036

    QPen pen;
    pen.setWidthF(lineWidth);

    float h = area.height();
    float w = area.width();
    float effectiveHalfHeight = h*0.45;
dongfang's avatar
dongfang committed
1037
    float markerHalfHeight = mediumTextSize;
dongfang's avatar
dongfang committed
1038
    float leftEdge = instrumentEdgePen.widthF()*2;
dongfang's avatar
dongfang committed
1039 1040 1041 1042 1043 1044 1045
    float tickmarkRight = w-leftEdge;
    float tickmarkLeftMajor = tickmarkRight-w*TAPE_GAUGES_TICKWIDTH_MAJOR;
    float tickmarkLeftMinor = tickmarkRight-w*TAPE_GAUGES_TICKWIDTH_MINOR;
    float numbersRight = 0.42*w;
    float markerTip = (tickmarkLeftMajor+tickmarkRight*2)/3;

    // Select between air and ground speed:
1046
    float speed = (isAirplane() && !isnan(airSpeed)) ? airSpeed : groundSpeed;
1047
    float centerScaleSpeed = isnan(speed) ? 0 : speed;
dongfang's avatar
dongfang committed
1048 1049 1050

    float start = centerScaleSpeed - AIRSPEED_LINEAR_SPAN/2;
    float end = centerScaleSpeed + AIRSPEED_LINEAR_SPAN/2;
1051 1052 1053 1054

    int firstTick = ceil(start / AIRSPEED_LINEAR_RESOLUTION) * AIRSPEED_LINEAR_RESOLUTION;
    int lastTick = floor(end / AIRSPEED_LINEAR_RESOLUTION) * AIRSPEED_LINEAR_RESOLUTION;
    for (int tickSpeed = firstTick; tickSpeed <= lastTick; tickSpeed += AIRSPEED_LINEAR_RESOLUTION) {
dongfang's avatar
dongfang committed
1055 1056 1057 1058
        pen.setColor(tickSpeed<0 ? redColor : Qt::white);
        painter.setPen(pen);

        float y = (tickSpeed-centerScaleSpeed)*effectiveHalfHeight/(AIRSPEED_LINEAR_SPAN/2);
1059 1060
        bool hasText = tickSpeed % AIRSPEED_LINEAR_MAJOR_RESOLUTION == 0;
        painter.resetTransform();
dongfang's avatar
dongfang committed
1061

1062
        painter.translate(area.left(), area.center().y() - y);
dongfang's avatar
dongfang committed
1063

1064
        if (hasText) {
dongfang's avatar
dongfang committed
1065
            painter.drawLine(tickmarkLeftMajor, 0, tickmarkRight, 0);
1066
            QString s_speed;
dongfang's avatar
dongfang committed
1067
            s_speed.sprintf("%d", abs(tickSpeed));
dongfang's avatar
dongfang committed
1068
            drawTextRightCenter(painter, s_speed, mediumTextSize, numbersRight, 0);
dongfang's avatar
dongfang committed
1069 1070
        } else {
            painter.drawLine(tickmarkLeftMinor, 0, tickmarkRight, 0);
1071 1072 1073 1074
        }
    }

    QPainterPath markerPath(QPoint(markerTip, 0));
dongfang's avatar
dongfang committed
1075 1076 1077 1078
    markerPath.lineTo(markerTip-markerHalfHeight, markerHalfHeight);
    markerPath.lineTo(leftEdge, markerHalfHeight);
    markerPath.lineTo(leftEdge, -markerHalfHeight);
    markerPath.lineTo(markerTip-markerHalfHeight, -markerHalfHeight);
1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094
    markerPath.closeSubpath();

    painter.resetTransform();
    painter.translate(area.left(), area.center().y());

    pen.setWidthF(lineWidth);
    pen.setColor(Qt::white);
    painter.setPen(pen);

    painter.setBrush(Qt::SolidPattern);
    painter.drawPath(markerPath);
    painter.setBrush(Qt::NoBrush);

    pen.setColor(Qt::white);
    painter.setPen(pen);
    QString s_alt;
1095
    if (isnan(speed))
dongfang's avatar
dongfang committed
1096 1097
        s_alt.sprintf("---");
    else
1098
        s_alt.sprintf("%3.1f", speed);
dongfang's avatar
dongfang committed
1099
    float xCenter = (markerTip+leftEdge)/2;
1100
    drawTextCenter(painter, s_alt, mediumTextSize, xCenter, 0);
1101 1102
}

1103 1104 1105 1106
static const int TOP = (1<<0);
static const int BOTTOM = (1<<1);
static const int LEFT = (1<<2);
static const int RIGHT = (1<<3);
dongfang's avatar
dongfang committed
1107

1108 1109 1110 1111
static const int TOP_HALF = (1<<4);
static const int BOTTOM_HALF = (1<<5);
static const int LEFT_HALF = (1<<6);
static const int RIGHT_HALF = (1<<7);
dongfang's avatar
dongfang committed
1112 1113 1114 1115 1116 1117 1118 1119 1120

void applyMargin(QRectF& area, float margin, int where) {
    if (margin < 0.01) return;

    QRectF save(area);
    qreal consumed;

    if (where & LEFT) {
        area.setX(save.x() + (consumed = margin));
1121
    } else if (where & LEFT_HALF) {
dongfang's avatar
dongfang committed
1122 1123 1124 1125 1126 1127 1128
        area.setX(save.x() + (consumed = margin/2));
    } else {
        consumed = 0;
    }

    if (where & RIGHT) {
        area.setWidth(save.width()-consumed-margin);
1129
    } else if (where & RIGHT_HALF) {
dongfang's avatar
dongfang committed
1130 1131 1132 1133 1134 1135 1136
        area.setWidth(save.width()-consumed-margin/2);
    } else {
        area.setWidth(save.width()-consumed);
    }

    if (where & TOP) {
        area.setY(save.y() + (consumed = margin));
1137
    } else if (where & TOP_HALF) {
dongfang's avatar
dongfang committed
1138 1139 1140 1141 1142 1143 1144
        area.setY(save.y() + (consumed = margin/2));
    } else {
        consumed = 0;
    }

    if (where & BOTTOM) {
        area.setHeight(save.height()-consumed-margin);
1145
    } else if (where & BOTTOM_HALF) {
dongfang's avatar
dongfang committed
1146 1147 1148 1149 1150 1151 1152
        area.setHeight(save.height()-consumed-margin/2);
    } else {
        area.setHeight(save.height()-consumed);
    }
}

void setMarginsForInlineLayout(qreal margin, QRectF& panel1, QRectF& panel2, QRectF& panel3, QRectF& panel4) {
1153 1154 1155 1156
    applyMargin(panel1, margin, BOTTOM|LEFT|RIGHT_HALF);
    applyMargin(panel2, margin, BOTTOM|LEFT_HALF|RIGHT_HALF);
    applyMargin(panel3, margin, BOTTOM|LEFT_HALF|RIGHT_HALF);
    applyMargin(panel4, margin, BOTTOM|LEFT_HALF|RIGHT);
1157 1158
}

dongfang's avatar
dongfang committed
1159
void setMarginsForCornerLayout(qreal margin, QRectF& panel1, QRectF& panel2, QRectF& panel3, QRectF& panel4) {
1160 1161 1162 1163
    applyMargin(panel1, margin, BOTTOM|LEFT|RIGHT_HALF);
    applyMargin(panel2, margin, BOTTOM|LEFT_HALF|RIGHT_HALF);
    applyMargin(panel3, margin, BOTTOM|LEFT_HALF|RIGHT_HALF);
    applyMargin(panel4, margin, BOTTOM|LEFT_HALF|RIGHT);
dongfang's avatar
dongfang committed
1164 1165 1166 1167
}

inline qreal tapesGaugeWidthFor(qreal containerWidth, qreal preferredAIWidth) {
    qreal result = (containerWidth - preferredAIWidth) / 2.0f;
dongfang's avatar
dongfang committed
1168
    qreal minimum = containerWidth / 5.5f;
dongfang's avatar
dongfang committed
1169 1170 1171 1172 1173
    if (result < minimum) result = minimum;
    return result;
}

void PrimaryFlightDisplay::doPaint() {
1174 1175 1176 1177 1178
    QPainter painter;
    painter.begin(this);
    painter.setRenderHint(QPainter::Antialiasing, true);
    painter.setRenderHint(QPainter::HighQualityAntialiasing, true);

dongfang's avatar
dongfang committed
1179
    qreal margin = height()/100.0f;
1180

1181 1182 1183 1184 1185
    // The AI centers on this area.
    QRectF AIMainArea;
    // The AI paints on this area. It should contain the AIMainArea.
    QRectF AIPaintArea;

dongfang's avatar
dongfang committed
1186 1187 1188 1189 1190 1191 1192
    QRectF compassArea;
    QRectF altimeterArea;
    QRectF velocityMeterArea;
    QRectF sensorsStatsArea;
    QRectF linkStatsArea;
    QRectF sysStatsArea;
    QRectF missionStatsArea;
1193 1194

    painter.fillRect(rect(), Qt::black);
dongfang's avatar
dongfang committed
1195 1196
    qreal tapeGaugeWidth;

dongfang's avatar
dongfang committed
1197 1198 1199
    qreal compassHalfSpan = 180;
    float compassAIIntrusion = 0;

dongfang's avatar
dongfang committed
1200
    switch(layout) {
1201
    case COMPASS_INTEGRATED: {
dongfang's avatar
dongfang committed
1202
        tapeGaugeWidth = tapesGaugeWidthFor(width(), width());
1203
        qreal aiheight = height();
dongfang's avatar
dongfang committed
1204 1205
        qreal aiwidth = width()-tapeGaugeWidth*2;
        if (aiheight > aiwidth) aiheight = aiwidth;
1206 1207 1208 1209

        AIMainArea = QRectF(
                    tapeGaugeWidth,
                    0,
dongfang's avatar
dongfang committed
1210
                    aiwidth,
1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229
                    aiheight);

        AIPaintArea = QRectF(
                    0,
                    0,
                    width(),
                    height());

        // Tape gauges get so much width that the AI area not covered by them is perfectly square.
        velocityMeterArea = QRectF (0, 0, tapeGaugeWidth, aiheight);
        altimeterArea = QRectF(AIMainArea.right(), 0, tapeGaugeWidth, aiheight);

        if (style == NO_OVERLAYS) {
            applyMargin(AIMainArea, margin, TOP|BOTTOM);
            applyMargin(altimeterArea, margin, TOP|BOTTOM|RIGHT);
            applyMargin(velocityMeterArea, margin, TOP|BOTTOM|LEFT);
            setMarginsForInlineLayout(margin, sensorsStatsArea, linkStatsArea, sysStatsArea, missionStatsArea);
        }

dongfang's avatar
dongfang committed
1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256
        qreal compassRelativeWidth = 0.75;
        qreal compassBottomMargin = 0.78;

        qreal compassSize = compassRelativeWidth  * AIMainArea.width();  // Diameter is this times the width.

        qreal compassCenterY;
        compassCenterY = AIMainArea.bottom() + compassSize / 4;

        if (height() - compassCenterY > AIMainArea.width()/2*compassBottomMargin)
            compassCenterY = height()-AIMainArea.width()/2*compassBottomMargin;

        // TODO: This is bad style...
        compassCenterY = (compassCenterY * 2 + AIMainArea.bottom() + compassSize / 4) / 3;

        compassArea = QRectF(AIMainArea.x()+(1-compassRelativeWidth)/2*AIMainArea.width(),
                             compassCenterY-compassSize/2,
                             compassSize,
                             compassSize);

        if (height()-compassCenterY < compassSize/2) {
            compassHalfSpan = acos((compassCenterY-height())*2/compassSize) * 180/M_PI + COMPASS_DISK_RESOLUTION;
            if (compassHalfSpan > 180) compassHalfSpan = 180;
        }

        compassAIIntrusion = compassSize/2 + AIMainArea.bottom() - compassCenterY;
        if (compassAIIntrusion<0) compassAIIntrusion = 0;

1257 1258
        break;
    }
dongfang's avatar
dongfang committed
1259 1260 1261 1262
    case COMPASS_SEPARATED: {
        // A layout for containers higher than their width.
        tapeGaugeWidth = tapesGaugeWidthFor(width(), width());

1263 1264
        qreal aiheight = width() - tapeGaugeWidth*2;
        qreal panelsHeight = 0;
dongfang's avatar
dongfang committed
1265

1266 1267
        AIMainArea = QRectF(
                    tapeGaugeWidth,
dongfang's avatar
dongfang committed
1268
                    0,
1269
                    width()-tapeGaugeWidth*2,
dongfang's avatar
dongfang committed
1270 1271
                    aiheight);

1272 1273 1274 1275 1276 1277 1278
        AIPaintArea = style == OVERLAY_HSI ?
                    QRectF(
                    0,
                    0,
                    width(),
                    height() - panelsHeight) : AIMainArea;

dongfang's avatar
dongfang committed
1279
        velocityMeterArea = QRectF (0, 0, tapeGaugeWidth, aiheight);
1280
        altimeterArea = QRectF(AIMainArea.right(), 0, tapeGaugeWidth, aiheight);
dongfang's avatar
dongfang committed
1281

1282 1283
        QPoint compassCenter = QPoint(width()/2, AIMainArea.bottom()+width()/2);
        qreal compassDiam = width() * 0.8;
dongfang's avatar
dongfang committed
1284
        compassArea = QRectF(compassCenter.x()-compassDiam/2, compassCenter.y()-compassDiam/2, compassDiam, compassDiam);
dongfang's avatar
dongfang committed
1285 1286 1287
        break;
    }
    }
1288

dongfang's avatar
dongfang committed
1289
    bool hadClip = painter.hasClipping();
dongfang's avatar
dongfang committed
1290

dongfang's avatar
dongfang committed
1291
    painter.setClipping(true);
1292
    painter.setClipRect(AIPaintArea);
1293

1294
    drawAIGlobalFeatures(painter, AIMainArea, AIPaintArea);
dongfang's avatar
dongfang committed
1295
    drawAIAttitudeScales(painter, AIMainArea, compassAIIntrusion);
1296
    drawAIAirframeFixedFeatures(painter, AIMainArea);
1297

1298
    drawAICompassDisk(painter, compassArea, compassHalfSpan);
1299

dongfang's avatar
dongfang committed
1300
    painter.setClipping(hadClip);
1301

1302
    drawAltimeter(painter, altimeterArea);
1303

1304
    drawVelocityMeter(painter, velocityMeterArea);
1305

dongfang's avatar
dongfang committed
1306
    painter.end();
1307
}