HDDisplay.cc 36.6 KB
Newer Older
pixhawk's avatar
pixhawk committed
1
2
3
4
5
6
7
8
9
10
11
12
13
/*=====================================================================
======================================================================*/

/**
 * @file
 *   @brief Implementation of Head Down Display (HDD)
 *
 *   @author Lorenz Meier <mavteam@student.ethz.ch>
 *
 */

#include <QFile>
#include <QGLWidget>
pixhawk's avatar
pixhawk committed
14
#include <QStringList>
15
#include <QGraphicsTextItem>
16
17
#include <QDockWidget>
#include <QInputDialog>
18
#include <QMouseEvent>
19
20
#include <QMenu>
#include <QSettings>
21
#include <qmath.h>
pixhawk's avatar
pixhawk committed
22
23
24
#include "UASManager.h"
#include "HDDisplay.h"
#include "ui_HDDisplay.h"
25
#include "MG.h"
26
#include "QGC.h"
27
#include "QGCApplication.h"
pixhawk's avatar
pixhawk committed
28
29
#include <QDebug>

30
HDDisplay::HDDisplay(const QStringList &plotList, QString title, QWidget *parent) :
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
    QGraphicsView(parent),
    uas(NULL),
    xCenterOffset(0.0f),
    yCenterOffset(0.0f),
    vwidth(80.0f),
    vheight(80.0f),
    backgroundColor(QColor(0, 0, 0)),
    defaultColor(QColor(70, 200, 70)),
    setPointColor(QColor(200, 20, 200)),
    warningColor(Qt::yellow),
    criticalColor(Qt::red),
    infoColor(QColor(20, 200, 20)),
    fuelColor(criticalColor),
    warningBlinkRate(5),
    refreshTimer(new QTimer(this)),
    hardwareAcceleration(true),
    strongStrokeWidth(1.5f),
    normalStrokeWidth(1.0f),
    fineStrokeWidth(0.5f),
    acceptList(new QStringList()),
    acceptUnitList(new QStringList()),
    lastPaintTime(0),
    columns(3),
54
55
    valuesChanged(true),
    m_ui(NULL)
pixhawk's avatar
pixhawk committed
56
{
57
    setWindowTitle(title);
pixhawk's avatar
pixhawk committed
58
59
    //m_ui->setupUi(this);

60
61
    setAutoFillBackground(true);

62
    // Add all items in accept list to gauge
63
64
    for(int i = 0; i < plotList.length(); ++i)
        addGauge(plotList.at(i));
65

66
    restoreState();
67
68
69
    // Set preferred size
    setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
    createActions();

    //    setBackgroundBrush(QBrush(backgroundColor));
    //    setDragMode(QGraphicsView::ScrollHandDrag);
    //    setCacheMode(QGraphicsView::CacheBackground);
    //    // FIXME Handle full update with care - ressource intensive
    //    setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
    //
    //    setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
    //
    //    //Set-up the scene
    //    QGraphicsScene* Scene = new QGraphicsScene(this);
    //    setScene(Scene);
    //
    //    //Populate the scene
    //    for(int x = 0; x < 1000; x = x + 25) {
    //        for(int y = 0; y < 1000; y = y + 25) {
    //
    //            if(x % 100 == 0 && y % 100 == 0) {
    //                Scene->addRect(x, y, 2, 2);
    //
    //                QString pointString;
    //                QTextStream stream(&pointString);
    //                stream << "(" << x << "," << y << ")";
    //                QGraphicsTextItem* item = Scene->addText(pointString);
    //                item->setPos(x, y);
    //            } else {
    //                Scene->addRect(x, y, 1, 1);
    //            }
    //        }
    //    }
    //
    //    //Set-up the view
    //    setSceneRect(0, 0, 1000, 1000);
    //    setCenter(QPointF(500.0, 500.0)); //A modified version of centerOn(), handles special cases
    //    setCursor(Qt::OpenHandCursor);
106
107


108
    // Set minimum size
109
110
    this->setMinimumHeight(125);
    this->setMinimumWidth(100);
pixhawk's avatar
pixhawk committed
111

LM's avatar
LM committed
112
113
    scalingFactor = this->width()/vwidth;

pixhawk's avatar
pixhawk committed
114
    // Refresh timer
115
    refreshTimer->setInterval(180); //
116
    connect(refreshTimer, SIGNAL(timeout()), this, SLOT(triggerUpdate()));
pixhawk's avatar
pixhawk committed
117
118
119
    //connect(refreshTimer, SIGNAL(timeout()), this, SLOT(paintGL()));

    fontDatabase = QFontDatabase();
Don Gagne's avatar
Don Gagne committed
120
    const QString fontFileName = ":/res/fonts/vera.ttf"; ///< Font file is part of the QRC file and compiled into the app
pixhawk's avatar
pixhawk committed
121
122
123
124
    const QString fontFamilyName = "Bitstream Vera Sans";
    if(!QFile::exists(fontFileName)) qDebug() << "ERROR! font file: " << fontFileName << " DOES NOT EXIST!";

    fontDatabase.addApplicationFont(fontFileName);
lm's avatar
lm committed
125
    font = fontDatabase.font(fontFamilyName, "Roman", qMax(5, (int)(10*scalingFactor*1.2f+0.5f)));
pixhawk's avatar
pixhawk committed
126
127
128
    if (font.family() != fontFamilyName) qDebug() << "ERROR! Font not loaded: " << fontFamilyName;

    // Connect with UAS
Lorenz Meier's avatar
Lorenz Meier committed
129
130
    connect(UASManager::instance(), SIGNAL(activeUASSet(UASInterface*)), this, SLOT(setActiveUAS(UASInterface*)), Qt::UniqueConnection);
    setActiveUAS(UASManager::instance()->getActiveUAS());
pixhawk's avatar
pixhawk committed
131
132
133
134
}

HDDisplay::~HDDisplay()
{
135
    saveState();
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
	if(this->refreshTimer)
	{
		delete this->refreshTimer;
	}
	if(this->acceptList)
	{
		delete this->acceptList;
	}
	if(this->acceptUnitList)
	{
		delete this->acceptUnitList;
	}
	if(this->m_ui)
	{
		delete m_ui;
	}
pixhawk's avatar
pixhawk committed
152
153
}

154
155
QSize HDDisplay::sizeHint() const
{
pixhawk's avatar
pixhawk committed
156
    return QSize(400, 400.0f*(vwidth/vheight)*1.2f);
157
158
}

159
160
void HDDisplay::enableGLRendering(bool enable)
{
161
    Q_UNUSED(enable);
162
163
}

164
165
166
void HDDisplay::triggerUpdate()
{
    // Only repaint the regions necessary
167
    update(this->geometry());
168
169
}

170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
//void HDDisplay::updateValue(UASInterface* uas, const QString& name, const QString& unit, double value, quint64 msec)
//{
//    // UAS is not needed
//    Q_UNUSED(uas);

//    if (!isnan(value) && !isinf(value))
//    {
//        // Update mean
//        const float oldMean = valuesMean.value(name, 0.0f);
//        const int meanCount = valuesCount.value(name, 0);
//        double mean = (oldMean * meanCount +  value) / (meanCount + 1);
//        if (isnan(mean) || isinf(mean)) mean = 0.0;
//        valuesMean.insert(name, mean);
//        valuesCount.insert(name, meanCount + 1);
//        // Two-value sliding average
//        double dot = (valuesDot.value(name) + (value - values.value(name, 0.0f)) / ((msec - lastUpdate.value(name, 0))/1000.0f))/2.0f;
//        if (isnan(dot) || isinf(dot))
//        {
//            dot = 0.0;
//        }
//        valuesDot.insert(name, dot);
//        values.insert(name, value);
//        lastUpdate.insert(name, msec);
//        //}

//        //qDebug() << __FILE__ << __LINE__ << "VALUE:" << value << "MEAN:" << mean << "DOT:" << dot << "COUNT:" << meanCount;
//    }
//}

pixhawk's avatar
pixhawk committed
199
200
void HDDisplay::paintEvent(QPaintEvent * event)
{
201
    Q_UNUSED(event);
lm's avatar
lm committed
202
    //qDebug() << "INTERVAL:" << MG::TIME::getGroundTimeNow() - interval << __FILE__ << __LINE__;
203
    renderOverlay();
204
205
}

206
207
208
209
210
211
212
void HDDisplay::contextMenuEvent (QContextMenuEvent* event)
{
    QMenu menu(this);
    menu.addAction(addGaugeAction);
    menu.addActions(getItemRemoveActions());
    menu.addSeparator();
    menu.addAction(setColumnsAction);
213
214
215
216
217
218
    // Title change would ruin settings
    // this can only be allowed once
    // HDDisplays are instantiated
    // by a factory method based on
    // QSettings
    //menu.addAction(setTitleAction);
219
220
221
222
223
224
225
226
227
    menu.exec(event->globalPos());
}

void HDDisplay::saveState()
{
    QSettings settings;

    QString instruments;
    // Restore instrument settings
228
    for (int i = 0; i < acceptList->count(); i++) {
229
        QString key = acceptList->at(i);
230
        instruments += "|" + QString::number(minValues.value(key, -1.0))+","+key+","+acceptUnitList->at(i)+","+QString::number(maxValues.value(key, +1.0))+","+customNames.value(key, "")+","+((symmetric.value(key, false)) ? "s" : "");
231
232
    }

233
    // qDebug() << "Saving" << instruments;
234
235
236
237
238
239
240
241

    settings.setValue(windowTitle()+"_gauges", instruments);
}

void HDDisplay::restoreState()
{
    QSettings settings;

242
243
    acceptList->clear();

244
    QStringList instruments = settings.value(windowTitle()+"_gauges").toString().split('|');
245
    for (int i = 0; i < instruments.count(); i++) {
246
247
248
249
250
251
252
        addGauge(instruments.at(i));
    }
}

QList<QAction*> HDDisplay::getItemRemoveActions()
{
    QList<QAction*> actions;
253
    for(int i = 0; i < acceptList->length(); ++i) {
254
255
256
257
258
259
260
261
262
263
264
265
266
        QString gauge = acceptList->at(i);
        QAction* remove = new QAction(tr("Remove %1 gauge").arg(gauge), this);
        remove->setStatusTip(tr("Removes the %1 gauge from the view.").arg(gauge));
        remove->setData(gauge);
        connect(remove, SIGNAL(triggered()), this, SLOT(removeItemByAction()));
        actions.append(remove);
    }
    return actions;
}

void HDDisplay::removeItemByAction()
{
    QAction* trigger = qobject_cast<QAction*>(QObject::sender());
267
    if (trigger) {
268
269
270
271
272
        QString item = trigger->data().toString();
        int index = acceptList->indexOf(item);
        acceptList->removeAt(index);
        minValues.remove(item);
        maxValues.remove(item);
pixhawk's avatar
pixhawk committed
273
274
        symmetric.remove(item);
        adjustGaugeAspectRatio();
275
276
277
278
279
280
    }
}

void HDDisplay::addGauge()
{
    QStringList items;
281
    for (int i = 0; i < values.count(); ++i) {
282
        QString key = values.keys().at(i);
283
284
285
286
287
288
289
        QString label = key;
        QStringList keySplit = key.split(".");
        if (keySplit.size() > 1)
        {
            keySplit.removeFirst();
            label = keySplit.join(".");
        }
290
        QString unit = units.value(key);
291
        if (unit.contains("deg") || unit.contains("rad")) {
292
            items.append(QString("%1,%2,%3,%4,%5,s").arg("-180").arg(key).arg(unit).arg("+180").arg(label));
293
        } else {
294
            items.append(QString("%1,%2,%3,%4,%5").arg("0").arg(key).arg(unit).arg("+100").arg(label));
295
        }
296
297
298
    }
    bool ok;
    QString item = QInputDialog::getItem(this, tr("Add Gauge Instrument"),
299
                                         tr("Format: min, data name, unit, max, label [,s]"), items, 0, true, &ok);
300
    if (ok && !item.isEmpty()) {
301
302
303
304
305
306
        addGauge(item);
    }
}

void HDDisplay::addGauge(const QString& gauge)
{
307
    if (gauge.length() > 0) {
308
        QStringList parts = gauge.split(',');
309
        if (parts.count() > 2) {
310
311
            double val;
            bool ok;
lm's avatar
lm committed
312
            bool success = true;
313
314

            QString key = parts.at(1);
lm's avatar
lm committed
315
            QString unit = parts.at(2);
316

317
            if (!acceptList->contains(key)) {
318
319
                // Convert min to double number
                val = parts.first().toDouble(&ok);
lm's avatar
lm committed
320
                success &= ok;
321
322
                if (ok) minValues.insert(key, val);
                // Convert max to double number
lm's avatar
lm committed
323
324
                val = parts.at(3).toDouble(&ok);
                success &= ok;
325
                if (ok) maxValues.insert(key, val);
326
327
328
329
330
331
332
333
                // Convert name
                if (parts.length() >= 5)
                {
                    if (parts.at(4).length() > 0)
                    {
                        customNames.insert(key, parts.at(4));
                    }
                }
pixhawk's avatar
pixhawk committed
334
                // Convert symmetric flag
335
336
337
338
                if (parts.length() >= 6)
                {
                    if (parts.at(5).contains("s"))
                    {
pixhawk's avatar
pixhawk committed
339
340
341
                        symmetric.insert(key, true);
                    }
                }
342
                if (success) {
lm's avatar
lm committed
343
344
345
346
                    // Add value to acceptlist
                    acceptList->append(key);
                    acceptUnitList->append(unit);
                }
347
            }
348
349
        } else if (parts.count() > 1) {
            if (!acceptList->contains(gauge)) {
lm's avatar
lm committed
350
351
                acceptList->append(parts.at(0));
                acceptUnitList->append(parts.at(1));
352
353
354
            }
        }
    }
355
    adjustGaugeAspectRatio();
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
}

void HDDisplay::createActions()
{
    addGaugeAction = new QAction(tr("New &Gauge"), this);
    addGaugeAction->setStatusTip(tr("Add a new gauge to the view by adding its name from the linechart"));
    connect(addGaugeAction, SIGNAL(triggered()), this, SLOT(addGauge()));

    setTitleAction = new QAction(tr("Set Widget Title"), this);
    setTitleAction->setStatusTip(tr("Set the title caption of this tool widget"));
    connect(setTitleAction, SIGNAL(triggered()), this, SLOT(setTitle()));

    setColumnsAction = new QAction(tr("Set Number of Instrument Columns"), this);
    setColumnsAction->setStatusTip(tr("Set number of columns to draw"));
    connect(setColumnsAction, SIGNAL(triggered()), this, SLOT(setColumns()));
}


void HDDisplay::setColumns()
{
    bool ok;
    int i = QInputDialog::getInt(this, tr("Number of Instrument Columns"),
                                 tr("Columns:"), columns, 1, 15, 1, &ok);
379
    if (ok) {
380
381
382
383
384
385
386
        columns = i;
    }
}

void HDDisplay::setColumns(int cols)
{
    columns = cols;
387
388
389
390
391
392
393
394
395
396
    adjustGaugeAspectRatio();
}

void HDDisplay::adjustGaugeAspectRatio()
{
    // Adjust vheight dynamically according to the number of rows
    float vColWidth = vwidth / columns;
    int vRows = ceil(acceptList->length()/(float)columns);
    // Assuming square instruments, vheight is column width*row count
    vheight = vColWidth * vRows;
397
398
399
400
401
}

void HDDisplay::setTitle()
{
    QDockWidget* parent = dynamic_cast<QDockWidget*>(this->parentWidget());
402
    if (parent) {
403
404
405
406
407
408
409
410
411
412
        bool ok;
        QString text = QInputDialog::getText(this, tr("New title"),
                                             tr("Widget title:"), QLineEdit::Normal,
                                             parent->windowTitle(), &ok);
        if (ok && !text.isEmpty())
            parent->setWindowTitle(text);
        this->setWindowTitle(text);
    }
}

413
void HDDisplay::renderOverlay()
414
{
415
416
    if (!valuesChanged || !isVisible()) return;

417
418
419
#if (QGC_EVENTLOOP_DEBUG)
    qDebug() << "EVENTLOOP:" << __FILE__ << __LINE__;
#endif
420
    quint64 refreshInterval = 100;
421
    quint64 currTime = MG::TIME::getGroundTimeNow();
422
    if (currTime - lastPaintTime < refreshInterval) {
423
424
425
426
        // FIXME Need to find the source of the spurious paint events
        //return;
    }
    lastPaintTime = currTime;
pixhawk's avatar
pixhawk committed
427
428
429
430
431
    // Draw instruments
    // TESTING THIS SHOULD BE MOVED INTO A QGRAPHICSVIEW
    // Update scaling factor
    // adjust scaling to fit both horizontally and vertically
    scalingFactor = this->width()/vwidth;
432

pixhawk's avatar
pixhawk committed
433
434
435
    double scalingFactorH = this->height()/vheight;
    if (scalingFactorH < scalingFactor) scalingFactor = scalingFactorH;

436
    QPainter painter(viewport());
pixhawk's avatar
pixhawk committed
437
438
    painter.setRenderHint(QPainter::Antialiasing, true);
    painter.setRenderHint(QPainter::HighQualityAntialiasing, true);
439
    //painter.fillRect(QRect(0, 0, width(), height()), backgroundColor);
pixhawk's avatar
pixhawk committed
440
    const float spacing = 0.4f; // 40% of width
441
    const float gaugeWidth = vwidth / (((float)columns) + (((float)columns+1) * spacing + spacing * 0.5f));
442
    QColor gaugeColor;
443
    gaugeColor = qgcApp()->styleIsDark() ? gaugeColor = QColor(255, 255, 255) : gaugeColor = QColor(0, 0, 0);
pixhawk's avatar
pixhawk committed
444
445
446
447
448
449
450
451
452
453
454
    //drawSystemIndicator(10.0f-gaugeWidth/2.0f, 20.0f, 10.0f, 40.0f, 15.0f, &painter);
    //drawGauge(15.0f, 15.0f, gaugeWidth/2.0f, 0, 1.0f, "thrust", values.value("thrust", 0.0f), gaugeColor, &painter, qMakePair(0.45f, 0.8f), qMakePair(0.8f, 1.0f), true);
    //drawGauge(15.0f+gaugeWidth*1.7f, 15.0f, gaugeWidth/2.0f, 0, 10.0f, "altitude", values.value("altitude", 0.0f), gaugeColor, &painter, qMakePair(1.0f, 2.5f), qMakePair(0.0f, 0.5f), true);

    // Left spacing from border / other gauges, measured from left edge to center
    float leftSpacing = gaugeWidth * spacing;
    float xCoord = leftSpacing + gaugeWidth/2.0f;

    float topSpacing = leftSpacing;
    float yCoord = topSpacing + gaugeWidth/2.0f;

455
456
    for (int i = 0; i < acceptList->size(); ++i)
    {
pixhawk's avatar
pixhawk committed
457
        QString value = acceptList->at(i);
458
459
        QString label = customNames.value(value);
        drawGauge(xCoord, yCoord, gaugeWidth/2.0f, minValues.value(value, -1.0f), maxValues.value(value, 1.0f), label, values.value(value, minValues.value(value, 0.0f)), gaugeColor, &painter, symmetric.value(value, false), goodRanges.value(value, qMakePair(0.0f, 0.5f)), critRanges.value(value, qMakePair(0.7f, 1.0f)), true);
pixhawk's avatar
pixhawk committed
460
461
        xCoord += gaugeWidth + leftSpacing;
        // Move one row down if necessary
462
463
        if (xCoord + gaugeWidth*0.9f > vwidth)
        {
pixhawk's avatar
pixhawk committed
464
465
466
467
            yCoord += topSpacing + gaugeWidth;
            xCoord = leftSpacing + gaugeWidth/2.0f;
        }
    }
pixhawk's avatar
pixhawk committed
468
469
470
471
472
473
474
475
}

/**
 *
 * @param uas the UAS/MAV to monitor/display with the HUD
 */
void HDDisplay::setActiveUAS(UASInterface* uas)
{
476
    // Disconnect any previously connected active UAS
477
    if (this->uas != NULL) {
478
        removeSource(this->uas);
Don Gagne's avatar
Don Gagne committed
479
        this->uas = NULL;
pixhawk's avatar
pixhawk committed
480
481
    }

Don Gagne's avatar
Don Gagne committed
482
483
484
485
486
    if (uas) {
        // Now connect the new UAS
        addSource(uas);
        this->uas = uas;
    }
pixhawk's avatar
pixhawk committed
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
}

/**
 * Rotate a polygon around a point
 *
 * @param p polygon to rotate
 * @param origin the rotation center
 * @param angle rotation angle, in radians
 * @return p Polygon p rotated by angle around the origin point
 */
void HDDisplay::rotatePolygonClockWiseRad(QPolygonF& p, float angle, QPointF origin)
{
    // Standard 2x2 rotation matrix, counter-clockwise
    //
    //   |  cos(phi)   sin(phi) |
    //   | -sin(phi)   cos(phi) |
    //
504
    for (int i = 0; i < p.size(); i++) {
pixhawk's avatar
pixhawk committed
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
        QPointF curr = p.at(i);

        const float x = curr.x();
        const float y = curr.y();

        curr.setX(((cos(angle) * (x-origin.x())) + (-sin(angle) * (y-origin.y()))) + origin.x());
        curr.setY(((sin(angle) * (x-origin.x())) + (cos(angle) * (y-origin.y()))) + origin.y());
        p.replace(i, curr);
    }
}

void HDDisplay::drawPolygon(QPolygonF refPolygon, QPainter* painter)
{
    // Scale coordinates
    QPolygonF draw(refPolygon.size());
520
    for (int i = 0; i < refPolygon.size(); i++) {
pixhawk's avatar
pixhawk committed
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
        QPointF curr;
        curr.setX(refToScreenX(refPolygon.at(i).x()));
        curr.setY(refToScreenY(refPolygon.at(i).y()));
        draw.replace(i, curr);
    }
    painter->drawPolygon(draw);
}

void HDDisplay::drawChangeRateStrip(float xRef, float yRef, float height, float minRate, float maxRate, float value, QPainter* painter)
{
    QBrush brush(defaultColor, Qt::NoBrush);
    painter->setBrush(brush);
    QPen rectPen(Qt::SolidLine);
    rectPen.setWidth(0);
    rectPen.setColor(defaultColor);
    painter->setPen(rectPen);

    float scaledValue = value;

    // Saturate value
    if (value > maxRate) scaledValue = maxRate;
    if (value < minRate) scaledValue = minRate;

    //           x (Origin: xRef, yRef)
    //           -
    //           |
    //           |
    //           |
    //           =
    //           |
    //   -0.005 >|
    //           |
    //           -

    const float width = height / 8.0f;
    const float lineWidth = 0.5f;

    // Indicator lines
    // Top horizontal line
    drawLine(xRef, yRef, xRef+width, yRef, lineWidth, defaultColor, painter);
    // Vertical main line
    drawLine(xRef+width/2.0f, yRef, xRef+width/2.0f, yRef+height, lineWidth, defaultColor, painter);
    // Zero mark
    drawLine(xRef, yRef+height/2.0f, xRef+width, yRef+height/2.0f, lineWidth, defaultColor, painter);
    // Horizontal bottom line
    drawLine(xRef, yRef+height, xRef+width, yRef+height, lineWidth, defaultColor, painter);

    // Text
    QString label;
    label.sprintf("< %06.2f", value);
    paintText(label, defaultColor, 3.0f, xRef+width/2.0f, yRef+height-((scaledValue - minRate)/(maxRate-minRate))*height - 1.6f, painter);
}

pixhawk's avatar
pixhawk committed
574
void HDDisplay::drawGauge(float xRef, float yRef, float radius, float min, float max, QString name, float value, const QColor& color, QPainter* painter, bool symmetric, QPair<float, float> goodRange, QPair<float, float> criticalRange, bool solid)
pixhawk's avatar
pixhawk committed
575
{
576
577
578
    // Select color scheme based on light or dark theme.
    QColor valueColor;
    QColor backgroundColor;
579
    if (qgcApp()->styleIsDark())
580
    {
581
582
        valueColor = QGC::colorCyan;
        backgroundColor = QColor(34, 34, 34);
583
584
585
    }
    else
    {
586
587
        valueColor = QColor(26, 75, 95);
        backgroundColor = QColor(246, 246, 246);
588
589
    }

pixhawk's avatar
pixhawk committed
590
591
592
593
    // Draw the circle
    QPen circlePen(Qt::SolidLine);

    // Rotate the whole gauge with this angle (in radians) for the zero position
pixhawk's avatar
pixhawk committed
594
    float zeroRotation;
595
    if (symmetric) {
pixhawk's avatar
pixhawk committed
596
        zeroRotation = 1.35f;
597
    } else {
pixhawk's avatar
pixhawk committed
598
599
        zeroRotation = 0.49f;
    }
pixhawk's avatar
pixhawk committed
600
601
602

    // Scale the rotation so that the gauge does one revolution
    // per max. change
pixhawk's avatar
pixhawk committed
603
    float rangeScale;
604
    if (symmetric) {
pixhawk's avatar
pixhawk committed
605
        rangeScale = ((2.0f * M_PI) / (max - min)) * 0.57f;
606
    } else {
pixhawk's avatar
pixhawk committed
607
608
        rangeScale = ((2.0f * M_PI) / (max - min)) * 0.72f;
    }
pixhawk's avatar
pixhawk committed
609

610
611
612
    const float scaledValue = (value-min)*rangeScale;

    float nameHeight = radius / 2.6f;
pixhawk's avatar
pixhawk committed
613
614
    paintText(name.toUpper(), color, nameHeight*0.7f, xRef-radius, yRef-radius, painter);

615
616
617
    // Ensure some space
    nameHeight *= 1.2f;

618
    if (!solid) {
pixhawk's avatar
pixhawk committed
619
620
621
622
        circlePen.setStyle(Qt::DotLine);
    }
    circlePen.setWidth(refLineWidthToPen(radius/12.0f));
    circlePen.setColor(color);
623

624
    if (symmetric) {
625
626
        circlePen.setStyle(Qt::DashLine);
    }
pixhawk's avatar
pixhawk committed
627
628
    painter->setBrush(Qt::NoBrush);
    painter->setPen(circlePen);
629
630
    drawCircle(xRef, yRef+nameHeight, radius, 0.0f, color, painter);
    //drawCircle(xRef, yRef+nameHeight, radius, 0.0f, 170.0f, 1.0f, color, painter);
pixhawk's avatar
pixhawk committed
631
632

    QString label;
633
634

    // Show integer values without decimal places
635
    if (intValues.contains(name)) {
636
        label.sprintf("% 05d", (int)value);
637
    } else {
638
639
        label.sprintf("% 06.1f", value);
    }
pixhawk's avatar
pixhawk committed
640
641
642
643


    // Text
    // height
644
645
    const float textHeight = radius/2.1f;
    const float textX = xRef-radius/3.0f;
pixhawk's avatar
pixhawk committed
646
647
648
    const float textY = yRef+radius/2.0f;

    // Draw background rectangle
649
    QBrush brush(backgroundColor, Qt::SolidPattern);
pixhawk's avatar
pixhawk committed
650
651
    painter->setBrush(brush);
    painter->setPen(Qt::NoPen);
pixhawk's avatar
pixhawk committed
652

653
    if (symmetric) {
pixhawk's avatar
pixhawk committed
654
        painter->drawRect(refToScreenX(xRef-radius), refToScreenY(yRef+nameHeight+radius/4.0f), refToScreenX(radius+radius), refToScreenY((radius - radius/4.0f)*1.2f));
655
    } else {
pixhawk's avatar
pixhawk committed
656
657
        painter->drawRect(refToScreenX(xRef-radius/2.5f), refToScreenY(yRef+nameHeight+radius/4.0f), refToScreenX(radius+radius/2.0f), refToScreenY((radius - radius/4.0f)*1.2f));
    }
pixhawk's avatar
pixhawk committed
658
659

    // Draw good value and crit. value markers
660
    if (goodRange.first != goodRange.second) {
pixhawk's avatar
pixhawk committed
661
662
663
664
665
666
667
        QRectF rectangle(refToScreenX(xRef-radius/2.0f), refToScreenY(yRef+nameHeight-radius/2.0f), refToScreenX(radius*2.0f), refToScreenX(radius*2.0f));
        painter->setPen(Qt::green);
        //int start = ((goodRange.first*rangeScale+zeroRotation)/M_PI)*180.0f * 16.0f;// + 16.0f * 60.0f;
        //int span  = start - ((goodRange.second*rangeScale+zeroRotation)/M_PI)*180.0f * 16.0f;
        //painter->drawArc(rectangle, start, span);
    }

668
    if (criticalRange.first != criticalRange.second) {
pixhawk's avatar
pixhawk committed
669
670
671
672
673
674
675
676
677
        QRectF rectangle(refToScreenX(xRef-radius/2.0f-3.0f), refToScreenY(yRef+nameHeight-radius/2.0f-3.0f), refToScreenX(radius*2.0f), refToScreenX(radius*2.0f));
        painter->setPen(Qt::yellow);
        //int start = ((criticalRange.first*rangeScale+zeroRotation)/M_PI)*180.0f * 16.0f - 180.0f*16.0f;// + 16.0f * 60.0f;
        //int span  = start - ((criticalRange.second*rangeScale+zeroRotation)/M_PI)*180.0f * 16.0f + 180.0f*16.0f;
        //painter->drawArc(rectangle, start, span);
    }

    // Draw the value
    //painter->setPen(textColor);
678
    paintText(label, valueColor, textHeight, textX, textY+nameHeight, painter);
pixhawk's avatar
pixhawk committed
679
680
681
682
683
684
685
686
687
688
    //paintText(label, color, ((radius - radius/3.0f)*1.1f), xRef-radius/2.5f, yRef+radius/3.0f, painter);

    // Draw the needle

    const float maxWidth = radius / 6.0f;
    const float minWidth = maxWidth * 0.3f;

    QPolygonF p(6);

    p.replace(0, QPointF(xRef-maxWidth/2.0f, yRef+nameHeight+radius * 0.05f));
689
690
    p.replace(1, QPointF(xRef-minWidth/2.0f, yRef+nameHeight+radius * 0.89f));
    p.replace(2, QPointF(xRef+minWidth/2.0f, yRef+nameHeight+radius * 0.89f));
pixhawk's avatar
pixhawk committed
691
692
693
694
695
    p.replace(3, QPointF(xRef+maxWidth/2.0f, yRef+nameHeight+radius * 0.05f));
    p.replace(4, QPointF(xRef,               yRef+nameHeight+radius * 0.0f));
    p.replace(5, QPointF(xRef-maxWidth/2.0f, yRef+nameHeight+radius * 0.05f));


696
    rotatePolygonClockWiseRad(p, scaledValue+zeroRotation, QPointF(xRef, yRef+nameHeight));
pixhawk's avatar
pixhawk committed
697
698
699
700
701
702
703
704
705
706
707
708

    QBrush indexBrush;
    indexBrush.setColor(color);
    indexBrush.setStyle(Qt::SolidPattern);
    painter->setPen(Qt::NoPen);
    painter->setBrush(indexBrush);
    drawPolygon(p, painter);
}


void HDDisplay::drawSystemIndicator(float xRef, float yRef, int maxNum, float maxWidth, float maxHeight, QPainter* painter)
{
709
    if (values.size() > 0) {
pixhawk's avatar
pixhawk committed
710
711
712
713
714
715
        QString selectedKey = values.begin().key();
        //   | | | | | |
        //   | | | | | |
        //   x speed: 2.54

        // One column per value
716
        QMapIterator<QString, double> value(values);
pixhawk's avatar
pixhawk committed
717
718
719
720
721
722
723
724
725
726

        float x = xRef;
        float y = yRef;

        const float vspacing = 1.0f;
        float width = 1.5f;
        float height = 1.5f;
        const float hspacing = 0.6f;

        int i = 0;
727
        while (value.hasNext() && i < maxNum && x < maxWidth && y < maxHeight) {
pixhawk's avatar
pixhawk committed
728
729
730
731
            value.next();
            QBrush brush(Qt::SolidPattern);


732
            if (value.value() < 0.01f && value.value() > -0.01f) {
pixhawk's avatar
pixhawk committed
733
                brush.setColor(Qt::gray);
734
            } else if (value.value() > 0.01f) {
pixhawk's avatar
pixhawk committed
735
                brush.setColor(Qt::blue);
736
            } else {
pixhawk's avatar
pixhawk committed
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
                brush.setColor(Qt::yellow);
            }

            painter->setBrush(brush);
            painter->setPen(Qt::NoPen);

            // Draw current value colormap
            painter->drawRect(refToScreenX(x), refToScreenY(y), refToScreenX(width), refToScreenY(height));

            // Draw change rate colormap
            painter->drawRect(refToScreenX(x), refToScreenY(y+height+hspacing), refToScreenX(width), refToScreenY(height));

            // Draw mean value colormap
            painter->drawRect(refToScreenX(x), refToScreenY(y+2.0f*(height+hspacing)), refToScreenX(width), refToScreenY(height));

            // Add spacing
            x += width+vspacing;

            // Iterate
            i++;
        }

        // Draw detail label
        QString detail = "NO DATA AVAILABLE";

762
        if (values.contains(selectedKey)) {
pixhawk's avatar
pixhawk committed
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
            detail = values.find(selectedKey).key();
            detail.append(": ");
            detail.append(QString::number(values.find(selectedKey).value()));
        }
        paintText(detail, QColor(255, 255, 255), 3.0f, xRef, yRef+3.0f*(height+hspacing)+1.0f, painter);
    }
}

void HDDisplay::drawChangeIndicatorGauge(float xRef, float yRef, float radius, float expectedMaxChange, float value, const QColor& color, QPainter* painter, bool solid)
{
    // Draw the circle
    QPen circlePen(Qt::SolidLine);
    if (!solid) circlePen.setStyle(Qt::DotLine);
    circlePen.setWidth(refLineWidthToPen(0.5f));
    circlePen.setColor(defaultColor);
    painter->setBrush(Qt::NoBrush);
    painter->setPen(circlePen);
780
781
    drawCircle(xRef, yRef, radius, 200.0f, color, painter);
    //drawCircle(xRef, yRef, radius, 200.0f, 170.0f, 1.0f, color, painter);
pixhawk's avatar
pixhawk committed
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830

    QString label;
    label.sprintf("%05.1f", value);

    // Draw the value
    paintText(label, color, 4.5f, xRef-7.5f, yRef-2.0f, painter);

    // Draw the needle
    // Scale the rotation so that the gauge does one revolution
    // per max. change
    const float rangeScale = (2.0f * M_PI) / expectedMaxChange;
    const float maxWidth = radius / 10.0f;
    const float minWidth = maxWidth * 0.3f;

    QPolygonF p(6);

    p.replace(0, QPointF(xRef-maxWidth/2.0f, yRef-radius * 0.5f));
    p.replace(1, QPointF(xRef-minWidth/2.0f, yRef-radius * 0.9f));
    p.replace(2, QPointF(xRef+minWidth/2.0f, yRef-radius * 0.9f));
    p.replace(3, QPointF(xRef+maxWidth/2.0f, yRef-radius * 0.5f));
    p.replace(4, QPointF(xRef,               yRef-radius * 0.46f));
    p.replace(5, QPointF(xRef-maxWidth/2.0f, yRef-radius * 0.5f));

    rotatePolygonClockWiseRad(p, value*rangeScale, QPointF(xRef, yRef));

    QBrush indexBrush;
    indexBrush.setColor(defaultColor);
    indexBrush.setStyle(Qt::SolidPattern);
    painter->setPen(Qt::SolidLine);
    painter->setPen(defaultColor);
    painter->setBrush(indexBrush);
    drawPolygon(p, painter);
}

/**
 * Paint text on top of the image and OpenGL drawings
 *
 * @param text chars to write
 * @param color text color
 * @param fontSize text size in mm
 * @param refX position in reference units (mm of the real instrument). This is relative to the measurement unit position, NOT in pixels.
 * @param refY position in reference units (mm of the real instrument). This is relative to the measurement unit position, NOT in pixels.
 */
void HDDisplay::paintText(QString text, QColor color, float fontSize, float refX, float refY, QPainter* painter)
{
    QPen prevPen = painter->pen();
    float pPositionX = refToScreenX(refX) - (fontSize*scalingFactor*0.072f);
    float pPositionY = refToScreenY(refY) - (fontSize*scalingFactor*0.212f);

831
832
833
834
    QFont font("Bitstream Vera Sans");
    // Enforce minimum font size of 5 pixels
    int fSize = qMax(5, (int)(fontSize*scalingFactor*1.26f));
    font.setPixelSize(fSize);
pixhawk's avatar
pixhawk committed
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853

    QFontMetrics metrics = QFontMetrics(font);
    int border = qMax(4, metrics.leading());
    QRect rect = metrics.boundingRect(0, 0, width() - 2*border, int(height()*0.125),
                                      Qt::AlignLeft | Qt::TextWordWrap, text);
    painter->setPen(color);
    painter->setFont(font);
    painter->setRenderHint(QPainter::TextAntialiasing);
    painter->drawText(pPositionX, pPositionY,
                      rect.width(), rect.height(),
                      Qt::AlignCenter | Qt::TextWordWrap, text);
    painter->setPen(prevPen);
}

float HDDisplay::refLineWidthToPen(float line)
{
    return line * 2.50f;
}

854
// Connect a generic source
855
856
void HDDisplay::addSource(QObject* obj)
{
857
    connect(obj, SIGNAL(valueChanged(int,QString,QString,QVariant,quint64)), this, SLOT(updateValue(int,QString,QString,QVariant,quint64)));
858
859
}

860
861
862
// Disconnect a generic source
void HDDisplay::removeSource(QObject* obj)
{
863
    disconnect(obj, SIGNAL(valueChanged(int,QString,QString,QVariant,quint64)), this, SLOT(updateValue(int,QString,QString,QVariant,quint64)));
864
865
}

866
void HDDisplay::updateValue(const int uasId, const QString& name, const QString& unit, const QVariant &variant, const quint64 msec)
867
{
868
869
    Q_UNUSED(uasId);
    Q_UNUSED(unit);
870

871
872
873
    QMetaType::Type type = static_cast< QMetaType::Type>(variant.type());
    if(type == QMetaType::QByteArray || type == QMetaType::QString)
        return;
874

875
876
877
878
    bool ok;
    double value = variant.toDouble(&ok);
    if(!ok)
        return;
879

880
881
882
883
884
885
    if(type == QMetaType::Int || type == QMetaType::UInt || type == QMetaType::Long || type == QMetaType::LongLong
       || type == QMetaType::Short || type == QMetaType::Char || type == QMetaType::ULong || type == QMetaType::ULongLong
       || type == QMetaType::UShort || type == QMetaType::UChar || type == QMetaType::Bool ) {
            if (!intValues.contains(name))
                intValues.insert(name, true);
    }
886

887
888
889
890
891
892
    // Update mean
    const float oldMean = valuesMean.value(name, 0.0f);
    const int meanCount = valuesCount.value(name, 0);
    valuesMean.insert(name, (oldMean * meanCount +  value) / (meanCount + 1));
    valuesCount.insert(name, meanCount + 1);
    valuesDot.insert(name, (value - values.value(name, 0.0f)) / ((msec - lastUpdate.value(name, 0))/1000.0f));
893
    if (values.value(name, 0.0) != value) valuesChanged = true;
894
    values.insert(name, value);
895
    units.insert(name, unit);
896
    lastUpdate.insert(name, msec);
pixhawk's avatar
pixhawk committed
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
}

/**
 * @param y coordinate in pixels to be converted to reference mm units
 * @return the screen coordinate relative to the QGLWindow origin
 */
float HDDisplay::refToScreenX(float x)
{
    return (scalingFactor * x);
}

/**
 * @param x coordinate in pixels to be converted to reference mm units
 * @return the screen coordinate relative to the QGLWindow origin
 */
float HDDisplay::refToScreenY(float y)
{
    return (scalingFactor * y);
}

917
918
919
920
921
922
923
924
925
926
float HDDisplay::screenToRefX(float x)
{
    return x/scalingFactor;
}

float HDDisplay::screenToRefY(float y)
{
    return y/scalingFactor;
}

pixhawk's avatar
pixhawk committed
927
928
929
930
931
932
933
934
935
void HDDisplay::drawLine(float refX1, float refY1, float refX2, float refY2, float width, const QColor& color, QPainter* painter)
{
    QPen pen(Qt::SolidLine);
    pen.setWidth(refLineWidthToPen(width));
    pen.setColor(color);
    painter->setPen(pen);
    painter->drawLine(QPoint(refToScreenX(refX1), refToScreenY(refY1)), QPoint(refToScreenX(refX2), refToScreenY(refY2)));
}

936
void HDDisplay::drawEllipse(float refX, float refY, float radiusX, float radiusY, float lineWidth, const QColor& color, QPainter* painter)
pixhawk's avatar
pixhawk committed
937
938
939
940
941
942
943
944
{
    QPen pen(painter->pen().style());
    pen.setWidth(refLineWidthToPen(lineWidth));
    pen.setColor(color);
    painter->setPen(pen);
    painter->drawEllipse(QPointF(refToScreenX(refX), refToScreenY(refY)), refToScreenX(radiusX), refToScreenY(radiusY));
}

945
void HDDisplay::drawCircle(float refX, float refY, float radius, float lineWidth, const QColor& color, QPainter* painter)
pixhawk's avatar
pixhawk committed
946
{
947
    drawEllipse(refX, refY, radius, radius, lineWidth, color, painter);
pixhawk's avatar
pixhawk committed
948
949
950
951
952
953
954
955
956
957
958
959
960
}

void HDDisplay::changeEvent(QEvent *e)
{
    QWidget::changeEvent(e);
    switch (e->type()) {
    case QEvent::LanguageChange:
        m_ui->retranslateUi(this);
        break;
    default:
        break;
    }
}
961
962


963
964
965
966
void HDDisplay::showEvent(QShowEvent* event)
{
    // React only to internal (pre-display)
    // events
967
968
    Q_UNUSED(event);
    refreshTimer->start(updateInterval);
969
970
971
972
973
974
}

void HDDisplay::hideEvent(QHideEvent* event)
{
    // React only to internal (pre-display)
    // events
975
976
977
    Q_UNUSED(event);
    refreshTimer->stop();
    saveState();
978
}
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000


///**
//  * Sets the current centerpoint.  Also updates the scene's center point.
//  * Unlike centerOn, which has no way of getting the floating point center
//  * back, SetCenter() stores the center point.  It also handles the special
//  * sidebar case.  This function will claim the centerPoint to sceneRec ie.
//  * the centerPoint must be within the sceneRec.
//  */
////Set the current centerpoint in the
//void HDDisplay::setCenter(const QPointF& centerPoint) {
//    //Get the rectangle of the visible area in scene coords
//    QRectF visibleArea = mapToScene(rect()).boundingRect();
//
//    //Get the scene area
//    QRectF sceneBounds = sceneRect();
//
//    double boundX = visibleArea.width() / 2.0;
//    double boundY = visibleArea.height() / 2.0;
//    double boundWidth = sceneBounds.width() - 2.0 * boundX;
//    double boundHeight = sceneBounds.height() - 2.0 * boundY;
//
For faster browsing, not all history is shown. View entire blame