LinechartWidget.cc 32.9 KB
Newer Older
1 2 3 4 5 6 7 8
/****************************************************************************
 *
 *   (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
 *
 * QGroundControl is licensed according to the terms in the file
 * COPYING.md in the root of the source code directory.
 *
 ****************************************************************************/
pixhawk's avatar
pixhawk committed
9 10 11 12 13 14 15


/**
 * @file
 *   @brief Line chart plot widget
 *
 *   @author Lorenz Meier <mavteam@student.ethz.ch>
16
 *   @author Thomas Gubler <thomasgubler@student.ethz.ch>
pixhawk's avatar
pixhawk committed
17 18 19 20 21 22 23 24
 */

#include <QDebug>
#include <QWidget>
#include <QHBoxLayout>
#include <QGridLayout>
#include <QComboBox>
#include <QToolButton>
25
#include <QSizePolicy>
pixhawk's avatar
pixhawk committed
26 27 28 29 30 31
#include <QScrollBar>
#include <QLabel>
#include <QMenu>
#include <QSpinBox>
#include <QColor>
#include <QPalette>
32
#include <QStandardPaths>
33
#include <QShortcut>
pixhawk's avatar
pixhawk committed
34 35 36 37

#include "LinechartWidget.h"
#include "LinechartPlot.h"
#include "LogCompressor.h"
lm's avatar
lm committed
38
#include "QGC.h"
pixhawk's avatar
pixhawk committed
39
#include "MG.h"
Don Gagne's avatar
Don Gagne committed
40
#include "QGCFileDialog.h"
Don Gagne's avatar
Don Gagne committed
41
#include "QGCMessageBox.h"
42
#include "QGCApplication.h"
pixhawk's avatar
pixhawk committed
43

44
LinechartWidget::LinechartWidget(int systemid, QWidget *parent) : QWidget(parent),
45 46 47 48 49 50 51 52 53 54 55 56 57 58
    sysid(systemid),
    activePlot(NULL),
    curvesLock(new QReadWriteLock()),
    plotWindowLock(),
    curveListIndex(0),
    curveListCounter(0),
    curveLabels(new QMap<QString, QLabel*>()),
    curveMeans(new QMap<QString, QLabel*>()),
    curveMedians(new QMap<QString, QLabel*>()),
    curveVariances(new QMap<QString, QLabel*>()),
    logFile(new QFile()),
    logindex(1),
    logging(false),
    logStartTime(0),
lm's avatar
lm committed
59
    updateTimer(new QTimer()),
60 61
    selectedMAV(-1),
    lastTimestamp(0)
pixhawk's avatar
pixhawk committed
62 63 64
{
    // Add elements defined in Qt Designer
    ui.setupUi(this);
Gus Grubba's avatar
Gus Grubba committed
65
    this->setMinimumSize(600, 400);
pixhawk's avatar
pixhawk committed
66 67 68 69

    // Add and customize curve list elements (left side)
    curvesWidget = new QWidget(ui.curveListWidget);
    ui.curveListWidget->setWidget(curvesWidget);
70
    curvesWidgetLayout = new QGridLayout(curvesWidget);
Gus Grubba's avatar
Gus Grubba committed
71 72
    curvesWidgetLayout->setMargin(6);
    curvesWidgetLayout->setSpacing(6);
73
    curvesWidgetLayout->setAlignment(Qt::AlignTop);
Gus Grubba's avatar
Gus Grubba committed
74
    curvesWidgetLayout->setColumnMinimumWidth(0, 10);
75 76

    curvesWidgetLayout->setColumnStretch(0, 0);
Gus Grubba's avatar
Gus Grubba committed
77
    curvesWidgetLayout->setColumnStretch(1, 10);
78 79 80 81
    curvesWidgetLayout->setColumnStretch(2, 80);
    curvesWidgetLayout->setColumnStretch(3, 50);
    curvesWidgetLayout->setColumnStretch(4, 50);
    curvesWidgetLayout->setColumnStretch(5, 50);
82
    curvesWidgetLayout->setColumnStretch(6, 50);
83

pixhawk's avatar
pixhawk committed
84 85
    curvesWidget->setLayout(curvesWidgetLayout);

86
    // Create curve list headings
87 88 89 90 91 92
    connect(ui.recolorButton, &QPushButton::clicked, this, &LinechartWidget::recolor);
    connect(ui.shortNameCheckBox, &QCheckBox::clicked, this, &LinechartWidget::setShortNames);
    connect(ui.plotFilterLineEdit, &QLineEdit::textChanged, this, &LinechartWidget::filterCurves);
    QShortcut *shortcut  = new QShortcut(this);
    shortcut->setKey(QKeySequence(Qt::CTRL + Qt::Key_F));
    connect(shortcut, &QShortcut::activated, this, &LinechartWidget::setPlotFilterLineEditFocus);
93

94 95
    int labelRow = curvesWidgetLayout->rowCount();

Gus Grubba's avatar
Gus Grubba committed
96
    selectAllCheckBox = new QCheckBox(this);
97
    connect(selectAllCheckBox, &QCheckBox::clicked, this, &LinechartWidget::selectAllCurves);
Gus Grubba's avatar
Gus Grubba committed
98
    curvesWidgetLayout->addWidget(selectAllCheckBox, labelRow, 0);
99

Gus Grubba's avatar
Gus Grubba committed
100 101 102 103
    QWidget* colorIcon = new QWidget(this);
    colorIcon->setMinimumSize(QSize(5, 14));
    colorIcon->setMaximumSize(QSize(5, 14));
    curvesWidgetLayout->addWidget(colorIcon, labelRow, 1);
104

Gus Grubba's avatar
Gus Grubba committed
105 106
    curvesWidgetLayout->addWidget(new QLabel(tr("Name")),     labelRow, 2);
    curvesWidgetLayout->addWidget(new QLabel(tr("Val")),      labelRow, 3, Qt::AlignRight);
107

Gus Grubba's avatar
Gus Grubba committed
108 109
    QLabel* pUnit = new QLabel(tr("Unit"));
    curvesWidgetLayout->addWidget(pUnit,                      labelRow, 4);
110

Gus Grubba's avatar
Gus Grubba committed
111 112
    curvesWidgetLayout->addWidget(new QLabel(tr("Mean")),     labelRow, 5, Qt::AlignRight);
    curvesWidgetLayout->addWidget(new QLabel(tr("Variance")), labelRow, 6, Qt::AlignRight);
113 114


pixhawk's avatar
pixhawk committed
115 116
    // Create the layout
    createLayout();
117

118
    // And make sure we're listening for future style changes
119
    connect(qgcApp(), &QGCApplication::styleChanged, this, &LinechartWidget::recolor);
120

LM's avatar
LM committed
121
    updateTimer->setInterval(updateInterval);
122
    connect(updateTimer, &QTimer::timeout, this, &LinechartWidget::refresh);
Gus Grubba's avatar
Gus Grubba committed
123 124
    connect(ui.uasSelectionBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &LinechartWidget::selectActiveSystem);

125
    readSettings();
Gus Grubba's avatar
Gus Grubba committed
126 127
    pUnit->setVisible(ui.showUnitsCheckBox->isChecked());
    connect(ui.showUnitsCheckBox, &QCheckBox::clicked, pUnit, &QLabel::setVisible);
pixhawk's avatar
pixhawk committed
128 129
}

130 131 132
LinechartWidget::~LinechartWidget()
{
    writeSettings();
pixhawk's avatar
pixhawk committed
133
    stopLogging();
134 135
    if (activePlot) delete activePlot;
    activePlot = NULL;
pixhawk's avatar
pixhawk committed
136 137
}

138 139 140 141 142 143 144 145 146 147 148
void LinechartWidget::selectActiveSystem(int mav)
{
    // -1: Unitialized, 0: all
    if (mav != selectedMAV && (selectedMAV != -1))
    {
        // Delete all curves
        // FIXME
    }
    selectedMAV = mav;
}

149 150 151
void LinechartWidget::selectAllCurves(bool all)
{
    QMap<QString, QLabel*>::iterator i;
152
    for (i = curveLabels->begin(); i != curveLabels->end(); ++i) {
153
        activePlot->setVisibleById(i.key(), all);
154 155 156
    }
}

157 158 159 160
void LinechartWidget::writeSettings()
{
    QSettings settings;
    settings.beginGroup("LINECHART");
161 162
    bool enforceGT = (!autoGroundTimeSet && timeButton->isChecked()) ? true : false;
    if (timeButton) settings.setValue("ENFORCE_GROUNDTIME", enforceGT);
163
    if (ui.showUnitsCheckBox) settings.setValue("SHOW_UNITS", ui.showUnitsCheckBox->isChecked());
164
    if (ui.shortNameCheckBox) settings.setValue("SHORT_NAMES", ui.shortNameCheckBox->isChecked());
165 166 167 168 169 170 171
    settings.endGroup();
}

void LinechartWidget::readSettings()
{
    QSettings settings;
    settings.beginGroup("LINECHART");
172
    if (activePlot) {
173 174
        timeButton->setChecked(settings.value("ENFORCE_GROUNDTIME", timeButton->isChecked()).toBool());
        activePlot->enforceGroundTime(settings.value("ENFORCE_GROUNDTIME", timeButton->isChecked()).toBool());
175
        timeButton->setChecked(settings.value("ENFORCE_GROUNDTIME", timeButton->isChecked()).toBool());
176
        //userGroundTimeSet = settings.value("USER_GROUNDTIME", timeButton->isChecked()).toBool();
177
    }
178
    if (ui.showUnitsCheckBox) ui.showUnitsCheckBox->setChecked(settings.value("SHOW_UNITS", ui.showUnitsCheckBox->isChecked()).toBool());
179
    if (ui.shortNameCheckBox) ui.shortNameCheckBox->setChecked(settings.value("SHORT_NAMES", ui.shortNameCheckBox->isChecked()).toBool());
180 181 182
    settings.endGroup();
}

pixhawk's avatar
pixhawk committed
183 184 185 186 187 188
void LinechartWidget::createLayout()
{
    // Create actions
    createActions();

    // Setup the plot group box area layout
189 190 191
    QVBoxLayout* vlayout = new QVBoxLayout(ui.diagramGroupBox);
    vlayout->setSpacing(4);
    vlayout->setMargin(2);
pixhawk's avatar
pixhawk committed
192 193

    // Create plot container widget
194 195 196
    activePlot = new LinechartPlot(this, sysid);
    // Activate automatic scrolling
    activePlot->setAutoScroll(true);
pixhawk's avatar
pixhawk committed
197 198 199 200 201

    // TODO Proper Initialization needed
    //    activePlot = getPlot(0);
    //    plotContainer->setPlot(activePlot);

202 203 204 205
    vlayout->addWidget(activePlot);

    QHBoxLayout *hlayout = new QHBoxLayout;
    vlayout->addLayout(hlayout);
pixhawk's avatar
pixhawk committed
206 207 208

    // Logarithmic scaling button
    scalingLogButton = createButton(this);
209
    scalingLogButton->setText(tr("LOG"));
pixhawk's avatar
pixhawk committed
210
    scalingLogButton->setCheckable(true);
211 212
    scalingLogButton->setToolTip(tr("Set logarithmic scale for Y axis"));
    scalingLogButton->setWhatsThis(tr("Set logarithmic scale for Y axis"));
213
    hlayout->addWidget(scalingLogButton);
pixhawk's avatar
pixhawk committed
214 215 216

    // Averaging spin box
    averageSpinBox = new QSpinBox(this);
217 218
    averageSpinBox->setToolTip(tr("Sliding window size to calculate mean and variance"));
    averageSpinBox->setWhatsThis(tr("Sliding window size to calculate mean and variance"));
pixhawk's avatar
pixhawk committed
219
    averageSpinBox->setMinimum(2);
220 221
    averageSpinBox->setValue(200);
    setAverageWindow(200);
222
    averageSpinBox->setMaximum(9999);
223
    hlayout->addWidget(averageSpinBox);
224
    connect(averageSpinBox,static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &LinechartWidget::setAverageWindow);
pixhawk's avatar
pixhawk committed
225 226 227

    // Log Button
    logButton = new QToolButton(this);
228 229
    logButton->setToolTip(tr("Start to log curve data into a CSV or TXT file"));
    logButton->setWhatsThis(tr("Start to log curve data into a CSV or TXT file"));
pixhawk's avatar
pixhawk committed
230
    logButton->setText(tr("Start Logging"));
231
    hlayout->addWidget(logButton);
232
    connect(logButton, &QToolButton::clicked, this, &LinechartWidget::startLogging);
pixhawk's avatar
pixhawk committed
233

234
    // Ground time button
235
    timeButton = new QCheckBox(this);
236
    timeButton->setText(tr("Ground Time"));
237 238
    timeButton->setToolTip(tr("Overwrite timestamp of data from vehicle with ground receive time. Helps if the plots are not visible because of missing or invalid onboard time."));
    timeButton->setWhatsThis(tr("Overwrite timestamp of data from vehicle with ground receive time. Helps if the plots are not visible because of missing or invalid onboard time."));
239
    hlayout->addWidget(timeButton);
240 241
    connect(timeButton.data(), &QCheckBox::clicked, activePlot, &LinechartPlot::enforceGroundTime);
    connect(timeButton.data(), &QCheckBox::clicked, this, &LinechartWidget::writeSettings);
242

243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263
    hlayout->addStretch();

    QLabel *timeScaleLabel = new QLabel("Time axis:");
    hlayout->addWidget(timeScaleLabel);

    timeScaleCmb = new QComboBox(this);
    timeScaleCmb->addItem("10 seconds", 10);
    timeScaleCmb->addItem("20 seconds", 20);
    timeScaleCmb->addItem("30 seconds", 30);
    timeScaleCmb->addItem("40 seconds", 40);
    timeScaleCmb->addItem("50 seconds", 50);
    timeScaleCmb->addItem("1 minute", 60);
    timeScaleCmb->addItem("2 minutes", 60*2);
    timeScaleCmb->addItem("3 minutes", 60*3);
    timeScaleCmb->addItem("4 minutes", 60*4);
    timeScaleCmb->addItem("5 minutes", 60*5);
    timeScaleCmb->addItem("10 minutes", 60*10);
    //timeScaleCmb->setSizeAdjustPolicy(QComboBox::AdjustToContents);
    timeScaleCmb->setMinimumContentsLength(12);

    hlayout->addWidget(timeScaleCmb);
264 265
    connect(timeScaleCmb, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
            this, &LinechartWidget::timeScaleChanged);
266

267 268
    // Initialize the "Show units" checkbox. This is configured in the .ui file, so all
    // we do here is attach the clicked() signal.
269
    connect(ui.showUnitsCheckBox, &QCheckBox::clicked, this, &LinechartWidget::writeSettings);
pixhawk's avatar
pixhawk committed
270

271 272 273 274
    // Add actions
    averageSpinBox->setValue(activePlot->getAverageWindow());

    // Connect notifications from the user interface to the plot
275
    connect(this, &LinechartWidget::curveRemoved, activePlot, &LinechartPlot::hideCurve);
276 277

    // Update scrollbar when plot window changes (via translator method setPlotWindowPosition()
278
//    connect(activePlot, SIGNAL(windowPositionChanged(quint64)), this, SLOT(setPlotWindowPosition(quint64)));
279
    connect(activePlot, &LinechartPlot::curveRemoved, this, &LinechartWidget::removeCurve);
280 281

    // Update plot when scrollbar is moved (via translator method setPlotWindowPosition()
282 283 284
    //TODO: impossible to
    connect(this, static_cast<void (LinechartWidget::*)(quint64)>(&LinechartWidget::plotWindowPositionUpdated),
            activePlot, &LinechartPlot::setWindowPosition);
285 286

    // Set scaling
287
    connect(scalingLogButton, &QToolButton::toggled, this, &LinechartWidget::toggleLogarithmicScaling);
288 289
}

290 291 292 293 294
void LinechartWidget::timeScaleChanged(int index)
{
    activePlot->setPlotInterval(timeScaleCmb->itemData(index).toInt()*1000);
}

295 296 297 298 299 300
void LinechartWidget::toggleLogarithmicScaling(bool checked)
{
    if(checked)
        activePlot->setLogarithmicScaling();
    else
        activePlot->setLinearScaling();
pixhawk's avatar
pixhawk committed
301 302
}

303
void LinechartWidget::appendData(int uasId, const QString& curve, const QString& unit, const QVariant &variant, quint64 usec)
304
{
305 306 307 308 309 310
    QMetaType::Type type = static_cast<QMetaType::Type>(variant.type());
    bool ok;
    double value = variant.toDouble(&ok);
    if(!ok || type == QMetaType::QByteArray || type == QMetaType::QString)
        return;
    bool isDouble = type == QMetaType::Float || type == QMetaType::Double;
Gus Grubba's avatar
Gus Grubba committed
311
    QString curveID = curve + unit;
312

lm's avatar
lm committed
313 314
    if ((selectedMAV == -1 && isVisible()) || (selectedMAV == uasId && isVisible()))
    {
315
        // Order matters here, first append to plot, then update curve list
Gus Grubba's avatar
Gus Grubba committed
316
        activePlot->appendData(curveID, usec, value);
317
        // Store data
Gus Grubba's avatar
Gus Grubba committed
318
        QLabel* label = curveLabels->value(curveID, NULL);
319
        // Make sure the curve will be created if it does not yet exist
lm's avatar
lm committed
320 321
        if(!label)
        {
322
            if(!isDouble)
Gus Grubba's avatar
Gus Grubba committed
323
                intData.insert(curveID, 0);
324 325
            addCurve(curve, unit);
        }
326 327

        // Add int data
328
        if(!isDouble)
Gus Grubba's avatar
Gus Grubba committed
329
            intData.insert(curveID, variant.toInt());
330 331
    }

332 333 334 335
    if (lastTimestamp == 0 && usec != 0)
    {
        lastTimestamp = usec;
    } else if (usec != 0) {
336 337
        // Difference larger than 3 secs, enforce ground time
        if (((qint64)usec - (qint64)lastTimestamp) > 3000)
338 339
        {
            autoGroundTimeSet = true;
340 341 342 343 344
            // Tick ground time checkbox, but avoid state switching
            timeButton->blockSignals(true);
            timeButton->setChecked(true);
            timeButton->blockSignals(false);
            if (activePlot) activePlot->enforceGroundTime(true);
345
        }
346
        lastTimestamp = usec;
347 348
    }

349
    // Log data
lm's avatar
lm committed
350 351
    if (logging)
    {
Gus Grubba's avatar
Gus Grubba committed
352
        if (activePlot->isVisible(curveID))
lm's avatar
lm committed
353
        {
354
            if (usec == 0) usec = QGC::groundTimeMilliseconds();
355 356 357 358
            if (logStartTime == 0) logStartTime = usec;
            qint64 time = usec - logStartTime;
            if (time < 0) time = 0;

359 360
            QString line = QString("%1\t%2\t%3\t%4\n").arg(time).arg(uasId).arg(curve).arg(value, 0, 'e', 15);
            logFile->write(line.toLatin1());
361 362 363 364
        }
    }
}

365 366
void LinechartWidget::refresh()
{
LM's avatar
LM committed
367
    setUpdatesEnabled(false);
368
    QString str;
369
    // Value
370
    QMap<QString, QLabel*>::iterator i;
371 372
    for (i = curveLabels->begin(); i != curveLabels->end(); ++i) {
        if (intData.contains(i.key())) {
lm's avatar
lm committed
373
            str.sprintf("% 11i", intData.value(i.key()));
374
        } else {
lm's avatar
lm committed
375 376
            double val = activePlot->getCurrentValue(i.key());
            int intval = static_cast<int>(val);
377
            if (intval >= 100000 || intval <= -100000) {
lm's avatar
lm committed
378
                str.sprintf("% 11i", intval);
379
            } else if (intval >= 10000 || intval <= -10000) {
lm's avatar
lm committed
380
                str.sprintf("% 11.2f", val);
381
            } else if (intval >= 1000 || intval <= -1000) {
lm's avatar
lm committed
382
                str.sprintf("% 11.4f", val);
383
            } else {
lm's avatar
lm committed
384 385
                str.sprintf("% 11.6f", val);
            }
386
        }
387 388 389 390 391
        // Value
        i.value()->setText(str);
    }
    // Mean
    QMap<QString, QLabel*>::iterator j;
392
    for (j = curveMeans->begin(); j != curveMeans->end(); ++j) {
393
        double val = activePlot->getMean(j.key());
lm's avatar
lm committed
394
        int intval = static_cast<int>(val);
395
        if (intval >= 100000 || intval <= -100000) {
lm's avatar
lm committed
396
            str.sprintf("% 11i", intval);
397
        } else if (intval >= 10000 || intval <= -10000) {
398
            str.sprintf("% 11.2f", val);
399
        } else if (intval >= 1000 || intval <= -1000) {
lm's avatar
lm committed
400
            str.sprintf("% 11.4f", val);
401
        } else {
402 403
            str.sprintf("% 11.6f", val);
        }
404 405
        j.value()->setText(str);
    }
406 407 408 409 410 411 412
//    QMap<QString, QLabel*>::iterator k;
//    for (k = curveMedians->begin(); k != curveMedians->end(); ++k)
//    {
//        // Median
//        str.sprintf("%+.2f", activePlot->getMedian(k.key()));
//        k.value()->setText(str);
//    }
413
    QMap<QString, QLabel*>::iterator l;
414 415 416 417 418
    for (l = curveVariances->begin(); l != curveVariances->end(); ++l) {
        // Variance
        str.sprintf("% 8.3e", activePlot->getVariance(l.key()));
        l.value()->setText(str);
    }
LM's avatar
LM committed
419
    setUpdatesEnabled(true);
420 421
}

pixhawk's avatar
pixhawk committed
422 423
void LinechartWidget::startLogging()
{
lm's avatar
lm committed
424
    // Check if any curve is enabled
425
    if (!activePlot->anyCurveVisible()) {
dogmaphobic's avatar
dogmaphobic committed
426 427 428
        QGCMessageBox::critical(
            tr("No curves selected for logging."),
            tr("Please check all curves you want to log. Currently no data would be logged. Aborting the logging."));
lm's avatar
lm committed
429 430 431 432
        return;
    }

    // Let user select the log file name
433
    // QDate date(QDate::currentDate());
lm's avatar
lm committed
434
    // QString("./pixhawk-log-" + date.toString("yyyy-MM-dd") + "-" + QString::number(logindex) + ".log")
dogmaphobic's avatar
dogmaphobic committed
435 436 437
    QString fileName = QGCFileDialog::getSaveFileName(this,
        tr("Save Log File"),
        QStandardPaths::writableLocation(QStandardPaths::DesktopLocation),
438
        tr("Log Files (*.log)"),
439
        "log"); // Default type
440

441
    qDebug() << "SAVE FILE " << fileName;
442

dogmaphobic's avatar
dogmaphobic committed
443
    if (!fileName.isEmpty()) {
444
        logFile = new QFile(fileName);
Lorenz Meier's avatar
Lorenz Meier committed
445
        if (logFile->open(QIODevice::Truncate | QIODevice::WriteOnly | QIODevice::Text)) {
446
            logging = true;
447 448
            logStartTime = 0;
            curvesWidget->setEnabled(false);
449 450
            logindex++;
            logButton->setText(tr("Stop logging"));
451 452
            disconnect(logButton, &QToolButton::clicked, this, &LinechartWidget::startLogging);
            connect(logButton, &QToolButton::clicked, this, &LinechartWidget::stopLogging);
453
        }
pixhawk's avatar
pixhawk committed
454 455 456 457 458 459
    }
}

void LinechartWidget::stopLogging()
{
    logging = false;
460
    curvesWidget->setEnabled(true);
461
    if (logFile->isOpen()) {
pixhawk's avatar
pixhawk committed
462 463 464
        logFile->flush();
        logFile->close();
        // Postprocess log file
465
        compressor = new LogCompressor(logFile->fileName(), logFile->fileName());
466
        connect(compressor, &LogCompressor::finishedFile, this, &LinechartWidget::logfileWritten);
467

dogmaphobic's avatar
dogmaphobic committed
468 469 470 471 472
        QMessageBox::StandardButton button = QGCMessageBox::question(
            tr("Starting Log Compression"),
            tr("Should empty fields (e.g. due to packet drops) be filled with the previous value of the same variable (zero order hold)?"),
            QMessageBox::Yes | QMessageBox::No,
            QMessageBox::No);
473
        bool fill = (button == QMessageBox::Yes);
474 475

        compressor->startCompression(fill);
pixhawk's avatar
pixhawk committed
476 477
    }
    logButton->setText(tr("Start logging"));
478 479
    disconnect(logButton, &QToolButton::clicked, this, &LinechartWidget::stopLogging);
    connect(logButton, &QToolButton::clicked, this, &LinechartWidget::startLogging);
pixhawk's avatar
pixhawk committed
480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502
}

/**
 * The average window size defines the width of the sliding average
 * filter. It also defines the width of the sliding median filter.
 *
 * @param windowSize with (in values) of the sliding average/median filter. Minimum is 2
 */
void LinechartWidget::setAverageWindow(int windowSize)
{
    if (windowSize > 1) activePlot->setAverageWindow(windowSize);
}

void LinechartWidget::createActions()
{
}

/**
 * @brief Add a curve to the curve list
 *
 * @param curve The id-string of the curve
 * @see removeCurve()
 **/
503
void LinechartWidget::addCurve(const QString& curve, const QString& unit)
pixhawk's avatar
pixhawk committed
504
{
505
    LinechartPlot* plot = activePlot;
Gus Grubba's avatar
Gus Grubba committed
506 507
    QString curveID = curve + unit;
    curveNames.insert(curveID, curve);
508 509
    int labelRow = curvesWidgetLayout->rowCount();

510
    // Checkbox
Gus Grubba's avatar
Gus Grubba committed
511
    QCheckBox* checkBox = new QCheckBox(this);
pixhawk's avatar
pixhawk committed
512
    checkBox->setCheckable(true);
Gus Grubba's avatar
Gus Grubba committed
513
    checkBox->setObjectName(curveID);
514 515
    checkBox->setToolTip(tr("Enable the curve in the graph window"));
    checkBox->setWhatsThis(tr("Enable the curve in the graph window"));
Gus Grubba's avatar
Gus Grubba committed
516
    checkBoxes.insert(curveID, checkBox);
517
    curvesWidgetLayout->addWidget(checkBox, labelRow, 0);
pixhawk's avatar
pixhawk committed
518

519
    // Icon
520
    QWidget* colorIcon = new QWidget(this);
Gus Grubba's avatar
Gus Grubba committed
521
    colorIcons.insert(curveID, colorIcon);
pixhawk's avatar
pixhawk committed
522
    colorIcon->setMinimumSize(QSize(5, 14));
Gus Grubba's avatar
Gus Grubba committed
523
    colorIcon->setMaximumSize(QSize(5, 14));
524
    curvesWidgetLayout->addWidget(colorIcon, labelRow, 1);
pixhawk's avatar
pixhawk committed
525

526
    // Label
Gus Grubba's avatar
Gus Grubba committed
527 528 529
    QLabel* label = new QLabel(this);
    label->setText(getCurveName(curveID, ui.shortNameCheckBox->isChecked()));
    curveNameLabels.insert(curveID, label);
530
    curvesWidgetLayout->addWidget(label, labelRow, 2);
531

pixhawk's avatar
pixhawk committed
532
    // Value
Gus Grubba's avatar
Gus Grubba committed
533
    QLabel* value = new QLabel(this);
pixhawk's avatar
pixhawk committed
534
    value->setNum(0.00);
535
    value->setStyleSheet(QString("QLabel {font-family:\"Courier\"; font-weight: bold;}"));
536 537
    value->setToolTip(tr("Current value of %1 in %2 units").arg(curve, unit));
    value->setWhatsThis(tr("Current value of %1 in %2 units").arg(curve, unit));
Gus Grubba's avatar
Gus Grubba committed
538 539
    curveLabels->insert(curveID, value);
    curvesWidgetLayout->addWidget(value, labelRow, 3, Qt::AlignRight);
pixhawk's avatar
pixhawk committed
540

541
    // Unit
Gus Grubba's avatar
Gus Grubba committed
542
    QLabel* unitLabel = new QLabel(this);
543 544 545
    unitLabel->setText(unit);
    unitLabel->setToolTip(tr("Unit of ") + curve);
    unitLabel->setWhatsThis(tr("Unit of ") + curve);
Gus Grubba's avatar
Gus Grubba committed
546
    curveUnits.insert(curveID, unitLabel);
547
    curvesWidgetLayout->addWidget(unitLabel, labelRow, 4);
548
    unitLabel->setVisible(ui.showUnitsCheckBox->isChecked());
549
    connect(ui.showUnitsCheckBox, &QCheckBox::clicked, unitLabel, &QLabel::setVisible);
550

pixhawk's avatar
pixhawk committed
551
    // Mean
Gus Grubba's avatar
Gus Grubba committed
552
    QLabel* mean = new QLabel(this);
pixhawk's avatar
pixhawk committed
553
    mean->setNum(0.00);
lm's avatar
lm committed
554
    mean->setStyleSheet(QString("QLabel {font-family:\"Courier\"; font-weight: bold;}"));
555 556
    mean->setToolTip(tr("Arithmetic mean of %1 in %2 units").arg(curve, unit));
    mean->setWhatsThis(tr("Arithmetic mean of %1 in %2 units").arg(curve, unit));
Gus Grubba's avatar
Gus Grubba committed
557 558
    curveMeans->insert(curveID, mean);
    curvesWidgetLayout->addWidget(mean, labelRow, 5, Qt::AlignRight);
pixhawk's avatar
pixhawk committed
559

560 561 562 563 564
//    // Median
//    median = new QLabel(form);
//    value->setNum(0.00);
//    curveMedians->insert(curve, median);
//    horizontalLayout->addWidget(median);
pixhawk's avatar
pixhawk committed
565

566
    // Variance
Gus Grubba's avatar
Gus Grubba committed
567
    QLabel* variance = new QLabel(this);
568
    variance->setNum(0.00);
lm's avatar
lm committed
569
    variance->setStyleSheet(QString("QLabel {font-family:\"Courier\"; font-weight: bold;}"));
570 571
    variance->setToolTip(tr("Variance of %1 in (%2)^2 units").arg(curve, unit));
    variance->setWhatsThis(tr("Variance of %1 in (%2)^2 units").arg(curve, unit));
Gus Grubba's avatar
Gus Grubba committed
572 573
    curveVariances->insert(curveID, variance);
    curvesWidgetLayout->addWidget(variance, labelRow, 6, Qt::AlignRight);
574

pixhawk's avatar
pixhawk committed
575 576 577 578 579 580 581 582 583 584 585
    /* Color picker
    QColor color = QColorDialog::getColor(Qt::green, this);
         if (color.isValid()) {
             colorLabel->setText(color.name());
             colorLabel->setPalette(QPalette(color));
             colorLabel->setAutoFillBackground(true);
         }
        */

    // Set stretch factors so that the label gets the whole space

586 587 588
    // Load visibility settings
    // TODO

pixhawk's avatar
pixhawk committed
589
    // Connect actions
590 591 592
    connect(selectAllCheckBox, &QCheckBox::clicked, checkBox, &QCheckBox::setChecked);
    QObject::connect(checkBox, &QCheckBox::clicked, this, &LinechartWidget::takeButtonClick);
    QObject::connect(this, &LinechartWidget::curveVisible, plot, &LinechartPlot::setVisibleById);
pixhawk's avatar
pixhawk committed
593 594 595

    // Set UI components to initial state
    checkBox->setChecked(false);
Gus Grubba's avatar
Gus Grubba committed
596
    plot->setVisibleById(curveID, false);
pixhawk's avatar
pixhawk committed
597 598 599 600 601 602 603 604
}

/**
 * @brief Remove the curve from the curve list.
 *
 * @param curve The curve to remove
 * @see addCurve()
 **/
605
void LinechartWidget::removeCurve(QString curve)
pixhawk's avatar
pixhawk committed
606
{
607
    Q_UNUSED(curve)
608 609 610 611 612 613 614 615 616 617 618 619 620 621

    QWidget* widget = NULL;
    widget = curveLabels->take(curve);
    curvesWidgetLayout->removeWidget(widget);
    widget->deleteLater();
    widget = curveMeans->take(curve);
    curvesWidgetLayout->removeWidget(widget);
    widget->deleteLater();
    widget = curveMedians->take(curve);
    curvesWidgetLayout->removeWidget(widget);
    widget->deleteLater();
    widget = curveVariances->take(curve);
    curvesWidgetLayout->removeWidget(widget);
    widget->deleteLater();
622 623 624 625 626 627 628 629
    widget = colorIcons.take(curve);
    curvesWidgetLayout->removeWidget(widget);
    widget->deleteLater();
    widget = curveNameLabels.take(curve);
    curvesWidgetLayout->removeWidget(widget);
    widget->deleteLater();
    widget = curveUnits.take(curve);
    curvesWidgetLayout->removeWidget(widget);
630
    widget->deleteLater();
631 632 633 634
    QCheckBox* checkbox;
    checkbox = checkBoxes.take(curve);
    curvesWidgetLayout->removeWidget(checkbox);
    checkbox->deleteLater();
635 636 637 638 639
//    intData->remove(curve);
}

void LinechartWidget::recolor()
{
640
    activePlot->styleChanged(qgcApp()->styleIsDark());
641
    foreach (const QString &key, colorIcons.keys())
642 643
    {
        QWidget* colorIcon = colorIcons.value(key, 0);
644
        if (colorIcon && !colorIcon->styleSheet().isEmpty())
645
        {
646 647 648
            QString colorstyle;
            QColor color = activePlot->getColorForCurve(key);
            colorstyle = colorstyle.sprintf("QWidget { background-color: #%02X%02X%02X; }", color.red(), color.green(), color.blue());
649 650 651 652 653
            colorIcon->setStyleSheet(colorstyle);
        }
    }
}

654 655 656 657 658
void LinechartWidget::setPlotFilterLineEditFocus()
{
    ui.plotFilterLineEdit->setFocus(Qt::ShortcutFocusReason);
}

659 660
void LinechartWidget::filterCurve(const QString &key, bool match)
{
661 662 663 664 665 666 667
        if (!checkBoxes[key]->isChecked())
        {
            colorIcons[key]->setVisible(match);
            curveNameLabels[key]->setVisible(match);
            (*curveLabels)[key]->setVisible(match);
            (*curveMeans)[key]->setVisible(match);
            (*curveVariances)[key]->setVisible(match);
Gus Grubba's avatar
Gus Grubba committed
668
            curveUnits[key]->setVisible(match && ui.showUnitsCheckBox->isChecked());
669 670
            checkBoxes[key]->setVisible(match);
        }
671 672 673 674 675 676 677 678 679 680
}

void LinechartWidget::filterCurves(const QString &filter)
{
    //qDebug() << "filterCurves: filter: " << filter;

    if (filter != "")
    {
        /* Hide Elements which do not match the filter pattern */
        QStringMatcher stringMatcher(filter, Qt::CaseInsensitive);
681
        foreach (const QString &key, colorIcons.keys())
682 683 684 685 686 687 688 689 690 691 692 693 694 695
        {
            if (stringMatcher.indexIn(key) < 0)
            {
                filterCurve(key, false);
            }
            else
            {
                filterCurve(key, true);
            }
        }
    }
    else
    {
        /* Show all Elements */
696
        foreach (const QString &key, colorIcons.keys())
697 698 699 700 701 702
        {
            filterCurve(key, true);
        }
    }
}

703
QString LinechartWidget::getCurveName(const QString& key, bool shortEnabled)
704
{
705
    if (shortEnabled)
706 707
    {
        QString name;
708 709
        QStringList parts = curveNames.value(key).split(".");
        if (parts.length() > 1)
710
        {
711 712 713 714 715 716
            name = parts.at(1);
        }
        else
        {
            name = parts.at(0);
        }
717

718
        const int sizeLimit = 20;
719

720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741
        // Replace known words with abbreviations
        if (name.length() > sizeLimit)
        {
            name.replace("gyroscope", "gyro");
            name.replace("accelerometer", "acc");
            name.replace("magnetometer", "mag");
            name.replace("distance", "dist");
            name.replace("ailerons", "ail");
            name.replace("altitude", "alt");
            name.replace("waypoint", "wp");
            name.replace("throttle", "thr");
            name.replace("elevator", "elev");
            name.replace("rudder", "rud");
            name.replace("error", "err");
            name.replace("version", "ver");
            name.replace("message", "msg");
            name.replace("count", "cnt");
            name.replace("value", "val");
            name.replace("source", "src");
            name.replace("index", "idx");
            name.replace("type", "typ");
            name.replace("mode", "mod");
742
        }
743 744 745

        // Check if sub-part is still exceeding N chars
        if (name.length() > sizeLimit)
746
        {
747 748 749 750 751
            name.replace("a", "");
            name.replace("e", "");
            name.replace("i", "");
            name.replace("o", "");
            name.replace("u", "");
752
        }
753 754 755 756 757 758 759 760 761 762 763

        return name;
    }
    else
    {
        return curveNames.value(key);
    }
}

void LinechartWidget::setShortNames(bool enable)
{
764
    foreach (const QString &key, curveNames.keys())
765 766
    {
        curveNameLabels.value(key)->setText(getCurveName(key, enable));
767
    }
768
}
pixhawk's avatar
pixhawk committed
769

770 771 772
void LinechartWidget::showEvent(QShowEvent* event)
{
    Q_UNUSED(event);
773 774 775 776 777 778 779
    setActive(true);
}

void LinechartWidget::hideEvent(QHideEvent* event)
{
    Q_UNUSED(event);
    setActive(false);
780 781
}

782 783
void LinechartWidget::setActive(bool active)
{
784
    if (activePlot) {
785 786
        activePlot->setActive(active);
    }
787
    if (active) {
788
        updateTimer->start(updateInterval);
789
    } else {
790
        updateTimer->stop();
pixhawk's avatar
pixhawk committed
791 792 793 794 795 796 797 798 799 800 801
    }
}

/**
 * @brief Set the position of the plot window.
 * The plot covers only a portion of the complete time series. The scrollbar
 * allows to select a window of the time series. The right edge of the window is
 * defined proportional to the position of the scrollbar.
 *
 * @param scrollBarValue The value of the scrollbar, in the range from MIN_TIME_SCROLLBAR_VALUE to MAX_TIME_SCROLLBAR_VALUE
 **/
802 803
void LinechartWidget::setPlotWindowPosition(int scrollBarValue)
{
pixhawk's avatar
pixhawk committed
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 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848
    plotWindowLock.lockForWrite();
    // Disable automatic scrolling immediately
    int scrollBarRange = (MAX_TIME_SCROLLBAR_VALUE - MIN_TIME_SCROLLBAR_VALUE);
    double position = (static_cast<double>(scrollBarValue) - MIN_TIME_SCROLLBAR_VALUE) / scrollBarRange;
    quint64 scrollInterval;

    // Activate automatic scrolling if scrollbar is at the right edge
    if(scrollBarValue > MAX_TIME_SCROLLBAR_VALUE - (MAX_TIME_SCROLLBAR_VALUE - MIN_TIME_SCROLLBAR_VALUE) * 0.01f) {
        activePlot->setAutoScroll(true);
    } else {
        activePlot->setAutoScroll(false);
        quint64 rightPosition;
        /* If the data exceeds the plot window, choose the position according to the scrollbar position */
        if(activePlot->getDataInterval() > activePlot->getPlotInterval()) {
            scrollInterval = activePlot->getDataInterval() - activePlot->getPlotInterval();
            rightPosition = activePlot->getMinTime() + activePlot->getPlotInterval() + (scrollInterval * position);
        } else {
            /* If the data interval is smaller as the plot interval, clamp the scrollbar to the right */
            rightPosition = activePlot->getMinTime() + activePlot->getPlotInterval();
        }
        emit plotWindowPositionUpdated(rightPosition);
    }


    // The slider position must be mapped onto an interval of datainterval - plotinterval,
    // because the slider position defines the right edge of the plot window. The leftmost
    // slider position must therefore map to the start of the data interval + plot interval
    // to ensure that the plot is not empty

    //  start> |-- plot interval --||-- (data interval - plotinterval) --| <end

    //@TODO Add notification of scrollbar here
    //plot->setWindowPosition(rightPosition);

    plotWindowLock.unlock();
}

/**
 * @brief Receive an updated plot window position.
 * The plot window can be changed by the arrival of new data or by
 * other user interaction. The scrollbar and other UI components
 * can be notified by calling this method.
 *
 * @param position The absolute position of the right edge of the plot window, in milliseconds
 **/
849 850
void LinechartWidget::setPlotWindowPosition(quint64 position)
{
pixhawk's avatar
pixhawk committed
851 852 853 854 855 856 857
    plotWindowLock.lockForWrite();
    // Calculate the relative position
    double pos;

    // A relative position makes only sense if the plot is filled
    if(activePlot->getDataInterval() > activePlot->getPlotInterval()) {
        //TODO @todo Implement the scrollbar enabling in a more elegant way
858
        //scrollbar->setDisabled(false);
pixhawk's avatar
pixhawk committed
859 860 861 862 863 864
        quint64 scrollInterval = position - activePlot->getMinTime() - activePlot->getPlotInterval();



        pos = (static_cast<double>(scrollInterval) / (activePlot->getDataInterval() - activePlot->getPlotInterval()));
    } else {
865
        //scrollbar->setDisabled(true);
pixhawk's avatar
pixhawk committed
866 867 868 869 870 871 872 873 874 875 876 877 878 879 880
        pos = 1;
    }
    plotWindowLock.unlock();

    emit plotWindowPositionUpdated(static_cast<int>(pos * (MAX_TIME_SCROLLBAR_VALUE - MIN_TIME_SCROLLBAR_VALUE)));
}

/**
 * @brief Set the time interval the plot displays.
 * The time interval of the plot can be adjusted by this method. If the
 * data covers less time than the interval, the plot will be filled from
 * the right to left
 *
 * @param interval The time interval to plot
 **/
881 882
void LinechartWidget::setPlotInterval(quint64 interval)
{
pixhawk's avatar
pixhawk committed
883 884 885 886 887
    activePlot->setPlotInterval(interval);
}

/**
 * @brief Take the click of a curve activation / deactivation button.
888 889 890
 * This method allows to map a button to a plot curve. The text of the
 * button must equal the curve name to activate / deactivate. If the checkbox
 * was clicked, show the curve color, otherwise clear the coloring.
pixhawk's avatar
pixhawk committed
891 892 893
 *
 * @param checked The visibility of the curve: true to display the curve, false otherwise
 **/
894 895
void LinechartWidget::takeButtonClick(bool checked)
{
pixhawk's avatar
pixhawk committed
896 897 898

    QCheckBox* button = qobject_cast<QCheckBox*>(QObject::sender());

899 900
    if(button != NULL)
    {
901
        activePlot->setVisibleById(button->objectName(), checked);
902 903
        QWidget* colorIcon = colorIcons.value(button->objectName(), 0);
        if (colorIcon)
904
        {
905 906 907 908 909 910 911 912 913 914 915
            if (checked)
            {
                QColor color = activePlot->getColorForCurve(button->objectName());
                if (color.isValid())
                {
                    QString colorstyle;
                    colorstyle = colorstyle.sprintf("QWidget { background-color: #%02X%02X%02X; }", color.red(), color.green(), color.blue());
                    colorIcon->setStyleSheet(colorstyle);
                }
            }
            else
916
            {
917
                colorIcon->setStyleSheet("");
918 919
            }
        }
pixhawk's avatar
pixhawk committed
920 921 922 923 924 925 926 927 928 929
    }
}

/**
 * @brief Factory method to create a new button.
 *
 * @param imagename The name of the image (should be placed at the standard icon location)
 * @param text The button text
 * @param parent The parent object (to ensure that the memory is freed after the deletion of the button)
 **/
930 931
QToolButton* LinechartWidget::createButton(QWidget* parent)
{
pixhawk's avatar
pixhawk committed
932 933 934 935 936 937
    QToolButton* button = new QToolButton(parent);
    button->setMinimumSize(QSize(20, 20));
    button->setMaximumSize(60, 20);
    button->setGeometry(button->x(), button->y(), 20, 20);
    return button;
}