地面站终端 App
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

938 lines
33 KiB

/****************************************************************************
*
* (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.
*
****************************************************************************/
/**
* @file
* @brief Line chart plot widget
*
* @author Lorenz Meier <mavteam@student.ethz.ch>
* @author Thomas Gubler <thomasgubler@student.ethz.ch>
*/
#include <QDebug>
#include <QWidget>
#include <QHBoxLayout>
#include <QGridLayout>
#include <QComboBox>
#include <QToolButton>
#include <QSizePolicy>
#include <QScrollBar>
#include <QLabel>
#include <QMenu>
#include <QSpinBox>
#include <QColor>
#include <QPalette>
#include <QStandardPaths>
#include <QShortcut>
#include "LinechartWidget.h"
#include "LinechartPlot.h"
#include "LogCompressor.h"
#include "QGC.h"
#include "MG.h"
#include "QGCQFileDialog.h"
#include "QGCMessageBox.h"
#include "QGCApplication.h"
#include "SettingsManager.h"
LinechartWidget::LinechartWidget(int systemid, QWidget *parent) : QWidget(parent),
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),
updateTimer(new QTimer()),
selectedMAV(-1),
lastTimestamp(0)
{
// Add elements defined in Qt Designer
ui.setupUi(this);
this->setMinimumSize(600, 400);
// Add and customize curve list elements (left side)
curvesWidget = new QWidget(ui.curveListWidget);
ui.curveListWidget->setWidget(curvesWidget);
curvesWidgetLayout = new QGridLayout(curvesWidget);
curvesWidgetLayout->setMargin(6);
curvesWidgetLayout->setSpacing(6);
curvesWidgetLayout->setAlignment(Qt::AlignTop);
curvesWidgetLayout->setColumnMinimumWidth(0, 10);
curvesWidgetLayout->setColumnStretch(0, 0);
curvesWidgetLayout->setColumnStretch(1, 10);
curvesWidgetLayout->setColumnStretch(2, 80);
curvesWidgetLayout->setColumnStretch(3, 50);
curvesWidgetLayout->setColumnStretch(4, 50);
curvesWidgetLayout->setColumnStretch(5, 50);
curvesWidgetLayout->setColumnStretch(6, 50);
curvesWidget->setLayout(curvesWidgetLayout);
// Create curve list headings
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);
int labelRow = curvesWidgetLayout->rowCount();
selectAllCheckBox = new QCheckBox(this);
connect(selectAllCheckBox, &QCheckBox::clicked, this, &LinechartWidget::selectAllCurves);
curvesWidgetLayout->addWidget(selectAllCheckBox, labelRow, 0);
QWidget* colorIcon = new QWidget(this);
colorIcon->setMinimumSize(QSize(5, 14));
colorIcon->setMaximumSize(QSize(5, 14));
curvesWidgetLayout->addWidget(colorIcon, labelRow, 1);
curvesWidgetLayout->addWidget(new QLabel(tr("Name")), labelRow, 2);
curvesWidgetLayout->addWidget(new QLabel(tr("Val")), labelRow, 3, Qt::AlignRight);
QLabel* pUnit = new QLabel(tr("Unit"));
curvesWidgetLayout->addWidget(pUnit, labelRow, 4);
curvesWidgetLayout->addWidget(new QLabel(tr("Mean")), labelRow, 5, Qt::AlignRight);
curvesWidgetLayout->addWidget(new QLabel(tr("Variance")), labelRow, 6, Qt::AlignRight);
// Create the layout
createLayout();
// And make sure we're listening for future style changes
connect(qgcApp()->toolbox()->settingsManager()->appSettings()->indoorPalette(), &Fact::rawValueChanged, this, &LinechartWidget::recolor);
updateTimer->setInterval(updateInterval);
connect(updateTimer, &QTimer::timeout, this, &LinechartWidget::refresh);
connect(ui.uasSelectionBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &LinechartWidget::selectActiveSystem);
readSettings();
pUnit->setVisible(ui.showUnitsCheckBox->isChecked());
connect(ui.showUnitsCheckBox, &QCheckBox::clicked, pUnit, &QLabel::setVisible);
}
LinechartWidget::~LinechartWidget()
{
writeSettings();
stopLogging();
if (activePlot) delete activePlot;
activePlot = NULL;
}
void LinechartWidget::selectActiveSystem(int mav)
{
// -1: Unitialized, 0: all
if (mav != selectedMAV && (selectedMAV != -1))
{
// Delete all curves
// FIXME
}
selectedMAV = mav;
}
void LinechartWidget::selectAllCurves(bool all)
{
QMap<QString, QLabel*>::iterator i;
for (i = curveLabels->begin(); i != curveLabels->end(); ++i) {
activePlot->setVisibleById(i.key(), all);
}
}
void LinechartWidget::writeSettings()
{
QSettings settings;
settings.beginGroup("LINECHART");
bool enforceGT = (!autoGroundTimeSet && timeButton->isChecked()) ? true : false;
if (timeButton) settings.setValue("ENFORCE_GROUNDTIME", enforceGT);
if (ui.showUnitsCheckBox) settings.setValue("SHOW_UNITS", ui.showUnitsCheckBox->isChecked());
if (ui.shortNameCheckBox) settings.setValue("SHORT_NAMES", ui.shortNameCheckBox->isChecked());
settings.endGroup();
}
void LinechartWidget::readSettings()
{
QSettings settings;
settings.beginGroup("LINECHART");
if (activePlot) {
timeButton->setChecked(settings.value("ENFORCE_GROUNDTIME", timeButton->isChecked()).toBool());
activePlot->enforceGroundTime(settings.value("ENFORCE_GROUNDTIME", timeButton->isChecked()).toBool());
timeButton->setChecked(settings.value("ENFORCE_GROUNDTIME", timeButton->isChecked()).toBool());
//userGroundTimeSet = settings.value("USER_GROUNDTIME", timeButton->isChecked()).toBool();
}
if (ui.showUnitsCheckBox) ui.showUnitsCheckBox->setChecked(settings.value("SHOW_UNITS", ui.showUnitsCheckBox->isChecked()).toBool());
if (ui.shortNameCheckBox) ui.shortNameCheckBox->setChecked(settings.value("SHORT_NAMES", ui.shortNameCheckBox->isChecked()).toBool());
settings.endGroup();
}
void LinechartWidget::createLayout()
{
// Create actions
createActions();
// Setup the plot group box area layout
QVBoxLayout* vlayout = new QVBoxLayout(ui.diagramGroupBox);
vlayout->setSpacing(4);
vlayout->setMargin(2);
// Create plot container widget
activePlot = new LinechartPlot(this, sysid);
// Activate automatic scrolling
activePlot->setAutoScroll(true);
// TODO Proper Initialization needed
// activePlot = getPlot(0);
// plotContainer->setPlot(activePlot);
vlayout->addWidget(activePlot);
QHBoxLayout *hlayout = new QHBoxLayout;
vlayout->addLayout(hlayout);
// Logarithmic scaling button
scalingLogButton = createButton(this);
scalingLogButton->setText(tr("LOG"));
scalingLogButton->setCheckable(true);
scalingLogButton->setToolTip(tr("Set logarithmic scale for Y axis"));
scalingLogButton->setWhatsThis(tr("Set logarithmic scale for Y axis"));
hlayout->addWidget(scalingLogButton);
// Averaging spin box
averageSpinBox = new QSpinBox(this);
averageSpinBox->setToolTip(tr("Sliding window size to calculate mean and variance"));
averageSpinBox->setWhatsThis(tr("Sliding window size to calculate mean and variance"));
averageSpinBox->setMinimum(2);
averageSpinBox->setValue(200);
setAverageWindow(200);
averageSpinBox->setMaximum(9999);
hlayout->addWidget(averageSpinBox);
connect(averageSpinBox,static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &LinechartWidget::setAverageWindow);
// Log Button
logButton = new QToolButton(this);
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"));
logButton->setText(tr("Start Logging"));
hlayout->addWidget(logButton);
connect(logButton, &QToolButton::clicked, this, &LinechartWidget::startLogging);
// Ground time button
timeButton = new QCheckBox(this);
timeButton->setText(tr("Ground Time"));
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."));
hlayout->addWidget(timeButton);
connect(timeButton.data(), &QCheckBox::clicked, activePlot, &LinechartPlot::enforceGroundTime);
connect(timeButton.data(), &QCheckBox::clicked, this, &LinechartWidget::writeSettings);
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);
connect(timeScaleCmb, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
this, &LinechartWidget::timeScaleChanged);
// Initialize the "Show units" checkbox. This is configured in the .ui file, so all
// we do here is attach the clicked() signal.
connect(ui.showUnitsCheckBox, &QCheckBox::clicked, this, &LinechartWidget::writeSettings);
// Add actions
averageSpinBox->setValue(activePlot->getAverageWindow());
// Connect notifications from the user interface to the plot
connect(this, &LinechartWidget::curveRemoved, activePlot, &LinechartPlot::hideCurve);
// Update scrollbar when plot window changes (via translator method setPlotWindowPosition()
// connect(activePlot, SIGNAL(windowPositionChanged(quint64)), this, SLOT(setPlotWindowPosition(quint64)));
connect(activePlot, &LinechartPlot::curveRemoved, this, &LinechartWidget::removeCurve);
// Update plot when scrollbar is moved (via translator method setPlotWindowPosition()
//TODO: impossible to
connect(this, static_cast<void (LinechartWidget::*)(quint64)>(&LinechartWidget::plotWindowPositionUpdated),
activePlot, &LinechartPlot::setWindowPosition);
// Set scaling
connect(scalingLogButton, &QToolButton::toggled, this, &LinechartWidget::toggleLogarithmicScaling);
}
void LinechartWidget::timeScaleChanged(int index)
{
activePlot->setPlotInterval(timeScaleCmb->itemData(index).toInt()*1000);
}
void LinechartWidget::toggleLogarithmicScaling(bool checked)
{
if(checked)
activePlot->setLogarithmicScaling();
else
activePlot->setLinearScaling();
}
void LinechartWidget::appendData(int uasId, const QString& curve, const QString& unit, const QVariant &variant, quint64 usec)
{
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;
QString curveID = curve + unit;
if ((selectedMAV == -1 && isVisible()) || (selectedMAV == uasId && isVisible()))
{
// Order matters here, first append to plot, then update curve list
activePlot->appendData(curveID, usec, value);
// Store data
QLabel* label = curveLabels->value(curveID, NULL);
// Make sure the curve will be created if it does not yet exist
if(!label)
{
if(!isDouble)
intData.insert(curveID, 0);
addCurve(curve, unit);
}
// Add int data
if(!isDouble)
intData.insert(curveID, variant.toInt());
}
if (lastTimestamp == 0 && usec != 0)
{
lastTimestamp = usec;
} else if (usec != 0) {
// Difference larger than 3 secs, enforce ground time
if (((qint64)usec - (qint64)lastTimestamp) > 3000)
{
autoGroundTimeSet = true;
// Tick ground time checkbox, but avoid state switching
timeButton->blockSignals(true);
timeButton->setChecked(true);
timeButton->blockSignals(false);
if (activePlot) activePlot->enforceGroundTime(true);
}
lastTimestamp = usec;
}
// Log data
if (logging)
{
if (activePlot->isVisible(curveID))
{
if (usec == 0) usec = QGC::groundTimeMilliseconds();
if (logStartTime == 0) logStartTime = usec;
qint64 time = usec - logStartTime;
if (time < 0) time = 0;
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());
}
}
}
void LinechartWidget::refresh()
{
setUpdatesEnabled(false);
QString str;
// Value
QMap<QString, QLabel*>::iterator i;
for (i = curveLabels->begin(); i != curveLabels->end(); ++i) {
if (intData.contains(i.key())) {
str.sprintf("% 11i", intData.value(i.key()));
} else {
double val = activePlot->getCurrentValue(i.key());
int intval = static_cast<int>(val);
if (intval >= 100000 || intval <= -100000) {
str.sprintf("% 11i", intval);
} else if (intval >= 10000 || intval <= -10000) {
str.sprintf("% 11.2f", val);
} else if (intval >= 1000 || intval <= -1000) {
str.sprintf("% 11.4f", val);
} else {
str.sprintf("% 11.6f", val);
}
}
// Value
i.value()->setText(str);
}
// Mean
QMap<QString, QLabel*>::iterator j;
for (j = curveMeans->begin(); j != curveMeans->end(); ++j) {
double val = activePlot->getMean(j.key());
int intval = static_cast<int>(val);
if (intval >= 100000 || intval <= -100000) {
str.sprintf("% 11i", intval);
} else if (intval >= 10000 || intval <= -10000) {
str.sprintf("% 11.2f", val);
} else if (intval >= 1000 || intval <= -1000) {
str.sprintf("% 11.4f", val);
} else {
str.sprintf("% 11.6f", val);
}
j.value()->setText(str);
}
// 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);
// }
QMap<QString, QLabel*>::iterator l;
for (l = curveVariances->begin(); l != curveVariances->end(); ++l) {
// Variance
str.sprintf("% 8.3e", activePlot->getVariance(l.key()));
l.value()->setText(str);
}
setUpdatesEnabled(true);
}
void LinechartWidget::startLogging()
{
// Check if any curve is enabled
if (!activePlot->anyCurveVisible()) {
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."));
return;
}
// Let user select the log file name
// QDate date(QDate::currentDate());
// QString("./pixhawk-log-" + date.toString("yyyy-MM-dd") + "-" + QString::number(logindex) + ".log")
QString fileName = QGCQFileDialog::getSaveFileName(this,
tr("Save Log File"),
QStandardPaths::writableLocation(QStandardPaths::DesktopLocation),
tr("Log Files (*.log)"),
"log"); // Default type
qDebug() << "SAVE FILE " << fileName;
if (!fileName.isEmpty()) {
logFile = new QFile(fileName);
if (logFile->open(QIODevice::Truncate | QIODevice::WriteOnly | QIODevice::Text)) {
logging = true;
logStartTime = 0;
curvesWidget->setEnabled(false);
logindex++;
logButton->setText(tr("Stop logging"));
disconnect(logButton, &QToolButton::clicked, this, &LinechartWidget::startLogging);
connect(logButton, &QToolButton::clicked, this, &LinechartWidget::stopLogging);
}
}
}
void LinechartWidget::stopLogging()
{
logging = false;
curvesWidget->setEnabled(true);
if (logFile->isOpen()) {
logFile->flush();
logFile->close();
// Postprocess log file
compressor = new LogCompressor(logFile->fileName(), logFile->fileName());
connect(compressor, &LogCompressor::finishedFile, this, &LinechartWidget::logfileWritten);
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);
bool fill = (button == QMessageBox::Yes);
compressor->startCompression(fill);
}
logButton->setText(tr("Start logging"));
disconnect(logButton, &QToolButton::clicked, this, &LinechartWidget::stopLogging);
connect(logButton, &QToolButton::clicked, this, &LinechartWidget::startLogging);
}
/**
* 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()
**/
void LinechartWidget::addCurve(const QString& curve, const QString& unit)
{
LinechartPlot* plot = activePlot;
QString curveID = curve + unit;
curveNames.insert(curveID, curve);
int labelRow = curvesWidgetLayout->rowCount();
// Checkbox
QCheckBox* checkBox = new QCheckBox(this);
checkBox->setCheckable(true);
checkBox->setObjectName(curveID);
checkBox->setToolTip(tr("Enable the curve in the graph window"));
checkBox->setWhatsThis(tr("Enable the curve in the graph window"));
checkBoxes.insert(curveID, checkBox);
curvesWidgetLayout->addWidget(checkBox, labelRow, 0);
// Icon
QWidget* colorIcon = new QWidget(this);
colorIcons.insert(curveID, colorIcon);
colorIcon->setMinimumSize(QSize(5, 14));
colorIcon->setMaximumSize(QSize(5, 14));
curvesWidgetLayout->addWidget(colorIcon, labelRow, 1);
// Label
QLabel* label = new QLabel(this);
label->setText(getCurveName(curveID, ui.shortNameCheckBox->isChecked()));
curveNameLabels.insert(curveID, label);
curvesWidgetLayout->addWidget(label, labelRow, 2);
// Value
QLabel* value = new QLabel(this);
value->setNum(0.00);
value->setStyleSheet(QString("QLabel {font-family:\"Courier\"; font-weight: bold;}"));
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));
curveLabels->insert(curveID, value);
curvesWidgetLayout->addWidget(value, labelRow, 3, Qt::AlignRight);
// Unit
QLabel* unitLabel = new QLabel(this);
unitLabel->setText(unit);
unitLabel->setToolTip(tr("Unit of ") + curve);
unitLabel->setWhatsThis(tr("Unit of ") + curve);
curveUnits.insert(curveID, unitLabel);
curvesWidgetLayout->addWidget(unitLabel, labelRow, 4);
unitLabel->setVisible(ui.showUnitsCheckBox->isChecked());
connect(ui.showUnitsCheckBox, &QCheckBox::clicked, unitLabel, &QLabel::setVisible);
// Mean
QLabel* mean = new QLabel(this);
mean->setNum(0.00);
mean->setStyleSheet(QString("QLabel {font-family:\"Courier\"; font-weight: bold;}"));
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));
curveMeans->insert(curveID, mean);
curvesWidgetLayout->addWidget(mean, labelRow, 5, Qt::AlignRight);
// // Median
// median = new QLabel(form);
// value->setNum(0.00);
// curveMedians->insert(curve, median);
// horizontalLayout->addWidget(median);
// Variance
QLabel* variance = new QLabel(this);
variance->setNum(0.00);
variance->setStyleSheet(QString("QLabel {font-family:\"Courier\"; font-weight: bold;}"));
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));
curveVariances->insert(curveID, variance);
curvesWidgetLayout->addWidget(variance, labelRow, 6, Qt::AlignRight);
/* 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
// Load visibility settings
// TODO
// Connect actions
connect(selectAllCheckBox, &QCheckBox::clicked, checkBox, &QCheckBox::setChecked);
QObject::connect(checkBox, &QCheckBox::clicked, this, &LinechartWidget::takeButtonClick);
QObject::connect(this, &LinechartWidget::curveVisible, plot, &LinechartPlot::setVisibleById);
// Set UI components to initial state
checkBox->setChecked(false);
plot->setVisibleById(curveID, false);
}
/**
* @brief Remove the curve from the curve list.
*
* @param curve The curve to remove
* @see addCurve()
**/
void LinechartWidget::removeCurve(QString curve)
{
Q_UNUSED(curve)
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();
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);
widget->deleteLater();
QCheckBox* checkbox;
checkbox = checkBoxes.take(curve);
curvesWidgetLayout->removeWidget(checkbox);
checkbox->deleteLater();
// intData->remove(curve);
}
void LinechartWidget::recolor()
{
activePlot->styleChanged(qgcApp()->toolbox()->settingsManager()->appSettings()->indoorPalette()->rawValue().toBool());
foreach (const QString &key, colorIcons.keys())
{
QWidget* colorIcon = colorIcons.value(key, 0);
if (colorIcon && !colorIcon->styleSheet().isEmpty())
{
QString colorstyle;
QColor color = activePlot->getColorForCurve(key);
colorstyle = colorstyle.sprintf("QWidget { background-color: #%02X%02X%02X; }", color.red(), color.green(), color.blue());
colorIcon->setStyleSheet(colorstyle);
}
}
}
void LinechartWidget::setPlotFilterLineEditFocus()
{
ui.plotFilterLineEdit->setFocus(Qt::ShortcutFocusReason);
}
void LinechartWidget::filterCurve(const QString &key, bool match)
{
if (!checkBoxes[key]->isChecked())
{
colorIcons[key]->setVisible(match);
curveNameLabels[key]->setVisible(match);
(*curveLabels)[key]->setVisible(match);
(*curveMeans)[key]->setVisible(match);
(*curveVariances)[key]->setVisible(match);
curveUnits[key]->setVisible(match && ui.showUnitsCheckBox->isChecked());
checkBoxes[key]->setVisible(match);
}
}
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);
foreach (const QString &key, colorIcons.keys())
{
if (stringMatcher.indexIn(key) < 0)
{
filterCurve(key, false);
}
else
{
filterCurve(key, true);
}
}
}
else
{
/* Show all Elements */
foreach (const QString &key, colorIcons.keys())
{
filterCurve(key, true);
}
}
}
QString LinechartWidget::getCurveName(const QString& key, bool shortEnabled)
{
if (shortEnabled)
{
QString name;
QStringList parts = curveNames.value(key).split(".");
if (parts.length() > 1)
{
name = parts.at(1);
}
else
{
name = parts.at(0);
}
const int sizeLimit = 20;
// 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");
}
// Check if sub-part is still exceeding N chars
if (name.length() > sizeLimit)
{
name.replace("a", "");
name.replace("e", "");
name.replace("i", "");
name.replace("o", "");
name.replace("u", "");
}
return name;
}
else
{
return curveNames.value(key);
}
}
void LinechartWidget::setShortNames(bool enable)
{
foreach (const QString &key, curveNames.keys())
{
curveNameLabels.value(key)->setText(getCurveName(key, enable));
}
}
void LinechartWidget::showEvent(QShowEvent* event)
{
Q_UNUSED(event);
setActive(true);
}
void LinechartWidget::hideEvent(QHideEvent* event)
{
Q_UNUSED(event);
setActive(false);
}
void LinechartWidget::setActive(bool active)
{
if (activePlot) {
activePlot->setActive(active);
}
if (active) {
updateTimer->start(updateInterval);
} else {
updateTimer->stop();
}
}
/**
* @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
**/
void LinechartWidget::setPlotWindowPosition(int scrollBarValue)
{
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
**/
void LinechartWidget::setPlotWindowPosition(quint64 position)
{
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
//scrollbar->setDisabled(false);
quint64 scrollInterval = position - activePlot->getMinTime() - activePlot->getPlotInterval();
pos = (static_cast<double>(scrollInterval) / (activePlot->getDataInterval() - activePlot->getPlotInterval()));
} else {
//scrollbar->setDisabled(true);
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
**/
void LinechartWidget::setPlotInterval(quint64 interval)
{
activePlot->setPlotInterval(interval);
}
/**
* @brief Take the click of a curve activation / deactivation button.
* 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.
*
* @param checked The visibility of the curve: true to display the curve, false otherwise
**/
void LinechartWidget::takeButtonClick(bool checked)
{
QCheckBox* button = qobject_cast<QCheckBox*>(QObject::sender());
if(button != NULL)
{
activePlot->setVisibleById(button->objectName(), checked);
QWidget* colorIcon = colorIcons.value(button->objectName(), 0);
if (colorIcon)
{
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
{
colorIcon->setStyleSheet("");
}
}
}
}
/**
* @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)
**/
QToolButton* LinechartWidget::createButton(QWidget* parent)
{
QToolButton* button = new QToolButton(parent);
button->setMinimumSize(QSize(20, 20));
button->setMaximumSize(60, 20);
button->setGeometry(button->x(), button->y(), 20, 20);
return button;
}