Unverified Commit 51c774e5 authored by Daniel Agar's avatar Daniel Agar Committed by GitHub

cmake fix build and delete obsolete files

parent 5ac6c722
......@@ -181,6 +181,7 @@ set(QGC_RESOURCES
HackFileDialog.qrc
qgcresources.qrc
qgroundcontrol.qrc
qgcimages.qrc
src/FirmwarePlugin/APM/APMResources.qrc
src/FirmwarePlugin/PX4/PX4Resources.qrc
......
......@@ -72,8 +72,8 @@ add_library(qgc
KMLFileHelper.cc
LogCompressor.cc
main.cc
QGC.cc
QGCApplication.cc
QGC.cc
QGCComboBox.cc
QGCDockWidget.cc
QGCFileDownload.cc
......@@ -81,21 +81,15 @@ add_library(qgc
QGCLoggingCategory.cc
QGCMapPalette.cc
QGCPalette.cc
QGCQFileDialog.cc
QGCQGeoCoordinate.cc
QGCQmlWidgetHolder.cpp
QGCQuickWidget.cc
QGCTemporaryFile.cc
QGCToolbox.cc
RunGuard.cc
ShapeFileHelper.cc
SHPFileHelper.cc
TerrainTile.cc
UTM.cpp
# UI
QGCQmlWidgetHolder.ui
QGCQmlWidgetHolder.h
UTM.cpp
)
set_source_files_properties(QGCApplication.cc PROPERTIES COMPILE_DEFINITIONS GIT_VERSION="${git_tag}")
......@@ -176,3 +170,4 @@ target_include_directories(qgc
${CMAKE_CURRENT_BINARY_DIR}/qgc_autogen/include # HACK: AUTOUIC paths not inheriting?
${CMAKE_CURRENT_BINARY_DIR}/qgc_autogen/include_Debug
)
......@@ -9,7 +9,6 @@ add_library(QmlControls
QGCImageProvider.cc
QGroundControlQmlGlobal.cc
QmlObjectListModel.cc
QmlTestWidget.cc
RCChannelMonitorController.cc
ScreenToolsController.cc
)
......
/****************************************************************************
*
* (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
/// @author Don Gagne <don@thegagnes.com>
#include "QmlTestWidget.h"
#include <QColorDialog>
QmlTestWidget::QmlTestWidget(void)
: QGCQmlWidgetHolder(QString(), NULL, NULL)
{
setAttribute(Qt::WA_DeleteOnClose);
resize(900, 500);
setVisible(true);
setContextPropertyObject("controller", this);
setSource(QUrl::fromUserInput("qrc:qml/QmlTest.qml"));
}
void QmlTestWidget::showColorDialog(QQuickItem* item)
{
Q_UNUSED(item)
QColorDialog colorDialog(this);
connect(&colorDialog, &QColorDialog::colorSelected, this, &QmlTestWidget::_colorSelected);
colorDialog.open();
}
void QmlTestWidget::_colorSelected(const QColor & color)
{
Q_UNUSED(color);
}
/****************************************************************************
*
* (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.
*
****************************************************************************/
#ifndef QmlTestWidget_h
#define QmlTestWidget_h
/// @file
/// @author Don Gagne <don@thegagnes.com>
#include "QGCQmlWidgetHolder.h"
/// This is used to create widgets which are implemented in QML.
class QmlTestWidget : public QGCQmlWidgetHolder
{
Q_OBJECT
public:
QmlTestWidget(void);
Q_INVOKABLE void showColorDialog(QQuickItem* item);
private slots:
void _colorSelected(const QColor & color);
};
#endif
......@@ -4,6 +4,7 @@ add_library(Settings
AppSettings.cc
AutoConnectSettings.cc
BrandImageSettings.cc
FirmwareUpgradeSettings.cc
FlightMapSettings.cc
FlyViewSettings.cc
OfflineMapsSettings.cc
......
add_library(ViewWidgets
CustomCommandWidget.cc
CustomCommandWidgetController.cc
ViewWidgetController.cc
)
......
/****************************************************************************
*
* (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.
*
****************************************************************************/
#include "CustomCommandWidget.h"
CustomCommandWidget::CustomCommandWidget(const QString& title, QAction* action, QWidget *parent) :
QGCQmlWidgetHolder(title, action, parent)
{
Q_UNUSED(title);
Q_UNUSED(action);
setSource(QUrl::fromUserInput("qrc:/qml/CustomCommandWidget.qml"));
loadSettings();
}
/****************************************************************************
*
* (c) 2009-2018 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
/// @author Don Gagne <don@thegagnes.com>
#pragma once
#include "QGCQmlWidgetHolder.h"
class CustomCommandWidget : public QGCQmlWidgetHolder
{
Q_OBJECT
public:
CustomCommandWidget(const QString& title, QAction* action, QWidget *parent = 0);
};
/****************************************************************************
*
* (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.
*
****************************************************************************/
#include "CustomCommandWidgetController.h"
#include "MultiVehicleManager.h"
#include "QGCMAVLink.h"
#include "QGCQFileDialog.h"
#include "UAS.h"
#include "QGCApplication.h"
#include <QSettings>
#include <QUrl>
const char* CustomCommandWidgetController::_settingsKey = "CustomCommand.QmlFile";
CustomCommandWidgetController::CustomCommandWidgetController(void) :
_vehicle(NULL)
{
if(qgcApp()->toolbox()->multiVehicleManager()->activeVehicle()) {
_vehicle = qgcApp()->toolbox()->multiVehicleManager()->activeVehicle();
}
QSettings settings;
_customQmlFile = settings.value(_settingsKey).toString();
}
void CustomCommandWidgetController::sendCommand(int commandId, QVariant componentId, QVariant confirm, QVariant param1, QVariant param2, QVariant param3, QVariant param4, QVariant param5, QVariant param6, QVariant param7)
{
Q_UNUSED(confirm);
if(_vehicle) {
_vehicle->sendMavCommand(componentId.toInt(),
(MAV_CMD)commandId,
true, // show error if fails
param1.toFloat(), param2.toFloat(), param3.toFloat(), param4.toFloat(), param5.toFloat(), param6.toFloat(), param7.toFloat());
}
}
void CustomCommandWidgetController::selectQmlFile(void)
{
QSettings settings;
QString qmlFile = QGCQFileDialog::getOpenFileName(NULL, tr("Select custom Qml file"), QString(), tr("Qml files (*.qml)"));
if (qmlFile.isEmpty()) {
_customQmlFile.clear();
settings.remove(_settingsKey);
} else {
QUrl url = QUrl::fromLocalFile(qmlFile);
_customQmlFile = url.toString();
settings.setValue(_settingsKey, _customQmlFile);
}
emit customQmlFileChanged(_customQmlFile);
}
void CustomCommandWidgetController::clearQmlFile(void)
{
_customQmlFile.clear();
QSettings settings;
settings.remove(_settingsKey);
emit customQmlFileChanged(_customQmlFile);
}
......@@ -16,7 +16,6 @@ add_library(comm
LogReplayLink.cc
MavlinkMessagesTimer.cc
MAVLinkProtocol.cc
QGCFlightGearLink.cc
QGCJSBSimLink.cc
QGCMAVLink.cc
QGCSerialPortInfo.cc
......@@ -30,7 +29,6 @@ add_library(comm
# HEADERS
# shouldn't be listed here, but aren't named properly for AUTOMOC
QGCFlightGearLink.h
QGCHilLink.h
QGCJSBSimLink.h
)
......
/****************************************************************************
*
* (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 Definition of UDP connection (server) for unmanned vehicles
* @see Flightgear Manual http://mapserver.flightgear.org/getstart.pdf
* @author Lorenz Meier <mavteam@student.ethz.ch>
* @author Thomas Gubler <thomasgubler@student.ethz.ch>
*
*/
#include <QTimer>
#include <QList>
#include <QDebug>
#include <QMutexLocker>
#include <QHostInfo>
#include <QMessageBox>
#include <QClipboard>
#include <iostream>
#include <Eigen/Eigen>
#include "QGCFlightGearLink.h"
#include "QGC.h"
//-- TODO: #include "QGCQFileDialog.h"
#include "QGCMessageBox.h"
#include "QGCApplication.h"
#include "Vehicle.h"
#include "UAS.h"
#include "QGroundControlQmlGlobal.h"
// FlightGear _fgProcess start and connection is quite fragile. Uncomment the define below to get higher level of debug output
// for tracking down problems.
//#define DEBUG_FLIGHTGEAR_CONNECT
QGCFlightGearLink::QGCFlightGearLink(Vehicle* vehicle, QString startupArguments, QString remoteHost, QHostAddress host, quint16 port)
: _vehicle(vehicle)
, _udpCommSocket(NULL)
, _fgProcess(NULL)
, flightGearVersion(3)
, startupArguments(startupArguments)
, _sensorHilEnabled(true)
, barometerOffsetkPa(0.0f)
{
// We're doing it wrong - because the Qt folks got the API wrong:
// http://blog.qt.digia.com/blog/2010/06/17/youre-doing-it-wrong/
moveToThread(this);
this->host = host;
this->port = port + _vehicle->id();
this->connectState = false;
this->currentPort = 49000 + _vehicle->id();
this->name = tr("FlightGear 3.0+ Link (port:%1)").arg(port);
setRemoteHost(remoteHost);
// We need a mechanism so show error message from our FGLink thread on the UI thread. This signal connection will do that for us.
connect(this, &QGCFlightGearLink::showCriticalMessageFromThread, qgcApp(), &QGCApplication::criticalMessageBoxOnMainThread);
connect(this, &QGCFlightGearLink::disconnectSim, this, &QGCFlightGearLink::disconnectSimulation);
}
QGCFlightGearLink::~QGCFlightGearLink()
{ //do not disconnect unless it is connected.
//disconnectSimulation will delete the memory that was allocated for process, terraSync and _udpCommSocket
if(connectState){
disconnectSimulation();
}
}
/// @brief Runs the simulation thread. We do setup work here which needs to happen in the separate thread.
void QGCFlightGearLink::run()
{
Q_ASSERT(_vehicle);
Q_ASSERT(!_fgProcessName.isEmpty());
// We communicate with FlightGear over a UDP _udpCommSocket
_udpCommSocket = new QUdpSocket(this);
Q_CHECK_PTR(_udpCommSocket);
_udpCommSocket->moveToThread(this);
_udpCommSocket->bind(host, port, QAbstractSocket::ReuseAddressHint);
QObject::connect(_udpCommSocket, &QUdpSocket::readyRead, this, &QGCFlightGearLink::readBytes);
// Connect to the various HIL signals that we use to then send information across the UDP protocol to FlightGear.
connect(_vehicle->uas(), &UAS::hilControlsChanged, this, &QGCFlightGearLink::updateControls);
connect(this, &QGCFlightGearLink::hilStateChanged, _vehicle->uas(), &UAS::sendHilState);
connect(this, &QGCFlightGearLink::sensorHilGpsChanged, _vehicle->uas(), &UAS::sendHilGps);
connect(this, &QGCFlightGearLink::sensorHilRawImuChanged, _vehicle->uas(), &UAS::sendHilSensors);
connect(this, &QGCFlightGearLink::sensorHilOpticalFlowChanged, _vehicle->uas(), &UAS::sendHilOpticalFlow);
// Start a new QProcess to run FlightGear in
_fgProcess = new QProcess(this);
Q_CHECK_PTR(_fgProcess);
_fgProcess->moveToThread(this);
connect(_fgProcess, static_cast<void (QProcess::*)(QProcess::ProcessError)>(&QProcess::error),
this, &QGCFlightGearLink::processError);
//#ifdef DEBUG_FLIGHTGEAR_CONNECT
connect(_fgProcess, &QProcess::readyReadStandardOutput, this, &QGCFlightGearLink::_printFgfsOutput);
connect(_fgProcess, &QProcess::readyReadStandardError, this, &QGCFlightGearLink::_printFgfsError);
//#endif
if (!_fgProcessWorkingDirPath.isEmpty()) {
_fgProcess->setWorkingDirectory(_fgProcessWorkingDirPath);
qDebug() << "Working directory" << _fgProcess->workingDirectory();
}
#ifdef Q_OS_WIN32
// On Windows we need to full qualify the location of the excecutable. The call to setWorkingDirectory only
// sets the QProcess context, not the QProcess::start context. For some strange reason this is not the case on
// OSX.
QDir fgProcessFullyQualified(_fgProcessWorkingDirPath);
_fgProcessName = fgProcessFullyQualified.absoluteFilePath(_fgProcessName);
#endif
#ifdef DEBUG_FLIGHTGEAR_CONNECT
qDebug() << "\nStarting FlightGear" << _fgProcessWorkingDirPath << _fgProcessName << _fgArgList << "\n";
#endif
_fgProcess->start(_fgProcessName, _fgArgList);
connectState = true;
emit simulationConnected(connectState);
emit simulationConnected();
exec();
}
void QGCFlightGearLink::setPort(int port)
{
this->port = port;
disconnectSimulation();
connectSimulation();
}
void QGCFlightGearLink::processError(QProcess::ProcessError err)
{
switch(err)
{
case QProcess::FailedToStart:
emit showCriticalMessageFromThread(tr("FlightGear Failed to Start"), _fgProcess->errorString());
break;
case QProcess::Crashed:
emit showCriticalMessageFromThread(tr("FlightGear Crashed"), tr("This is a FlightGear-related problem. Please upgrade FlightGear"));
break;
case QProcess::Timedout:
emit showCriticalMessageFromThread(tr("FlightGear Start Timed Out"), tr("Please check if the path and command is correct"));
break;
case QProcess::WriteError:
emit showCriticalMessageFromThread(tr("Could not Communicate with FlightGear"), tr("Please check if the path and command is correct"));
break;
case QProcess::ReadError:
emit showCriticalMessageFromThread(tr("Could not Communicate with FlightGear"), tr("Please check if the path and command is correct"));
break;
case QProcess::UnknownError:
default:
emit showCriticalMessageFromThread(tr("FlightGear Error"), tr("Please check if the path and command is correct."));
break;
}
}
/**
* @param host Hostname in standard formatting, e.g. localhost:14551 or 192.168.1.1:14551
*/
void QGCFlightGearLink::setRemoteHost(const QString& host)
{
if (host.contains(":"))
{
//qDebug() << "HOST: " << host.split(":").first();
QHostInfo info = QHostInfo::fromName(host.split(":").first());
if (info.error() == QHostInfo::NoError)
{
// Add host
QList<QHostAddress> hostAddresses = info.addresses();
QHostAddress address;
for (int i = 0; i < hostAddresses.size(); i++)
{
// Exclude loopback IPv4 and all IPv6 addresses
if (!hostAddresses.at(i).toString().contains(":"))
{
address = hostAddresses.at(i);
}
}
currentHost = address;
//qDebug() << "Address:" << address.toString();
// Set port according to user input
currentPort = host.split(":").last().toInt();
}
}
else
{
QHostInfo info = QHostInfo::fromName(host);
if (info.error() == QHostInfo::NoError)
{
// Add host
currentHost = info.addresses().first();
}
}
}
void QGCFlightGearLink::updateControls(quint64 time, float rollAilerons, float pitchElevator, float yawRudder, float throttle, quint8 systemMode, quint8 navMode)
{
// magnetos,aileron,elevator,rudder,throttle\n
//float magnetos = 3.0f;
Q_UNUSED(time);
Q_UNUSED(systemMode);
Q_UNUSED(navMode);
if(!qIsNaN(rollAilerons) && !qIsNaN(pitchElevator) && !qIsNaN(yawRudder) && !qIsNaN(throttle))
{
QString state("%1\t%2\t%3\t%4\t%5\n");
state = state.arg(rollAilerons).arg(pitchElevator).arg(yawRudder).arg(true).arg(throttle);
emit _invokeWriteBytes(state.toLatin1());
//qDebug() << "Updated controls" << rollAilerons << pitchElevator << yawRudder << throttle;
//qDebug() << "Updated controls" << state;
}
else
{
qDebug() << "HIL: Got NaN values from the hardware: isnan output: roll: " << qIsNaN(rollAilerons) << ", pitch: " << qIsNaN(pitchElevator) << ", yaw: " << qIsNaN(yawRudder) << ", throttle: " << qIsNaN(throttle);
}
}
void QGCFlightGearLink::_writeBytes(const QByteArray data)
{
//#define QGCFlightGearLink_DEBUG
#ifdef QGCFlightGearLink_DEBUG
QString bytes;
QString ascii;
for (int i=0, size = data.size(); i<size; i++)
{
unsigned char v = data[i];
bytes.append(QString().sprintf("%02x ", v));
if (data[i] > 31 && data[i] < 127)
{
ascii.append(data[i]);
}
else
{
ascii.append(219);
}
}
qDebug() << "Sent" << size << "bytes to" << currentHost.toString() << ":" << currentPort << "data:";
qDebug() << bytes;
qDebug() << "ASCII:" << ascii;
#endif
if (connectState && _udpCommSocket) _udpCommSocket->writeDatagram(data, currentHost, currentPort);
}
/**
* @brief Read a number of bytes from the interface.
*
* @param data Pointer to the data byte array to write the bytes to
* @param maxLength The maximum number of bytes to write
**/
void QGCFlightGearLink::readBytes()
{
const qint64 maxLength = 65536;
char data[maxLength];
QHostAddress sender;
quint16 senderPort;
unsigned int s = _udpCommSocket->pendingDatagramSize();
if (s > maxLength) std::cerr << __FILE__ << __LINE__ << " UDP datagram overflow, allowed to read less bytes than datagram size" << std::endl;
_udpCommSocket->readDatagram(data, maxLength, &sender, &senderPort);
QByteArray b(data, s);
// Print string
QString state(b);
//qDebug() << "FG LINK GOT:" << state;
QStringList values = state.split("\t");
// Check length
const int nValues = 22;
if (values.size() != nValues)
{
qDebug() << "RETURN LENGTH MISMATCHING EXPECTED" << nValues << "BUT GOT" << values.size();
qDebug() << state;
emit showCriticalMessageFromThread(tr("FlightGear HIL"),
tr("Flight Gear protocol file '%1' is out of date. Quit %2. Delete the file and restart %2 to fix.").arg(_fgProtocolFileFullyQualified).arg(qgcApp()->applicationName()));
disconnectSimulation();
return;
}
// Parse string
float roll, pitch, yaw, rollspeed, pitchspeed, yawspeed;
double lat, lon, alt;
float ind_airspeed;
float true_airspeed;
float vx, vy, vz, xacc, yacc, zacc;
float diff_pressure;
float temperature;
float abs_pressure;
float mag_variation, mag_dip, xmag_ned, ymag_ned, zmag_ned, xmag_body, ymag_body, zmag_body;
float alt_agl;
lat = values.at(1).toDouble();
lon = values.at(2).toDouble();
alt = values.at(3).toDouble();
roll = values.at(4).toFloat();
pitch = values.at(5).toFloat();
yaw = values.at(6).toFloat();
rollspeed = values.at(7).toFloat();
pitchspeed = values.at(8).toFloat();
yawspeed = values.at(9).toFloat();
xacc = values.at(10).toFloat();
yacc = values.at(11).toFloat();
zacc = values.at(12).toFloat();
vx = values.at(13).toFloat();
vy = values.at(14).toFloat();
vz = values.at(15).toFloat();
true_airspeed = values.at(16).toFloat();
mag_variation = values.at(17).toFloat();
mag_dip = values.at(18).toFloat();
temperature = values.at(19).toFloat();
abs_pressure = values.at(20).toFloat() * 1e2f; //convert to Pa from hPa
abs_pressure += barometerOffsetkPa * 1e3f; //add offset, convert from kPa to Pa
alt_agl = values.at(21).toFloat();
//calculate differential pressure
const float air_gas_constant = 287.1f; // J/(kg * K)
const float absolute_null_celsius = -273.15f; // °C
float density = abs_pressure / (air_gas_constant * (temperature - absolute_null_celsius));
diff_pressure = true_airspeed * true_airspeed * density / 2.0f;
//qDebug() << "diff_pressure: " << diff_pressure << "abs_pressure: " << abs_pressure;
/* Calculate indicated airspeed */
const float air_density_sea_level_15C = 1.225f; //kg/m^3
if (diff_pressure > 0)
{
ind_airspeed = sqrtf((2.0f*diff_pressure) / air_density_sea_level_15C);
} else
{
ind_airspeed = -sqrtf((2.0f*fabsf(diff_pressure)) / air_density_sea_level_15C);
}
//qDebug() << "ind_airspeed: " << ind_airspeed << "true_airspeed: " << true_airspeed;
// Send updated state
//qDebug() << "sensorHilEnabled: " << sensorHilEnabled;
if (_sensorHilEnabled)
{
quint16 fields_changed = 0xFFF; //set all 12 used bits
float pressure_alt = alt;
xmag_ned = cosf(mag_variation);
ymag_ned = sinf(mag_variation);
zmag_ned = sinf(mag_dip);
float tempMagLength = sqrtf(xmag_ned*xmag_ned + ymag_ned*ymag_ned + zmag_ned*zmag_ned);
xmag_ned = xmag_ned / tempMagLength;
ymag_ned = ymag_ned / tempMagLength;
zmag_ned = zmag_ned / tempMagLength;
//transform magnetic measurement to body frame coordinates
double cosPhi = cos(roll);
double sinPhi = sin(roll);
double cosThe = cos(pitch);
double sinThe = sin(pitch);
double cosPsi = cos(yaw);
double sinPsi = sin(yaw);
float R_B_N[3][3];
R_B_N[0][0] = cosThe * cosPsi;
R_B_N[0][1] = -cosPhi * sinPsi + sinPhi * sinThe * cosPsi;
R_B_N[0][2] = sinPhi * sinPsi + cosPhi * sinThe * cosPsi;
R_B_N[1][0] = cosThe * sinPsi;
R_B_N[1][1] = cosPhi * cosPsi + sinPhi * sinThe * sinPsi;
R_B_N[1][2] = -sinPhi * cosPsi + cosPhi * sinThe * sinPsi;
R_B_N[2][0] = -sinThe;
R_B_N[2][1] = sinPhi * cosThe;
R_B_N[2][2] = cosPhi * cosThe;
Eigen::Matrix3f R_B_N_M = Eigen::Map<Eigen::Matrix3f>((float*)R_B_N).eval();
Eigen::Vector3f mag_ned(xmag_ned, ymag_ned, zmag_ned);
Eigen::Vector3f mag_body = R_B_N_M * mag_ned;
xmag_body = mag_body(0);
ymag_body = mag_body(1);
zmag_body = mag_body(2);
emit sensorHilRawImuChanged(QGC::groundTimeUsecs(), xacc, yacc, zacc, rollspeed, pitchspeed, yawspeed,
xmag_body, ymag_body, zmag_body, abs_pressure*1e-2f, diff_pressure*1e-2f, pressure_alt, temperature, fields_changed); //Pressure in hPa for _vehicle->uas()link
// qDebug() << "sensorHilRawImuChanged " << xacc << yacc << zacc << rollspeed << pitchspeed << yawspeed << xmag << ymag << zmag << abs_pressure << diff_pressure << pressure_alt << temperature;
int gps_fix_type = 3;
float eph = 0.3f;
float epv = 0.6f;
float vel = sqrt(vx*vx + vy*vy + vz*vz);
float cog = yaw;
int satellites = 8;
emit sensorHilGpsChanged(QGC::groundTimeUsecs(), lat, lon, alt, gps_fix_type, eph, epv, vel, vx, vy, vz, cog, satellites);
// qDebug() << "sensorHilGpsChanged " << lat << lon << alt << vel;
// Send Optical Flow message. For now we set the flow quality to 0 and just write the ground_distance field
float distanceMeasurement = -1.0; // -1 means invalid value
if (fabsf(roll) < 0.87 && fabsf(pitch) < 0.87) // return a valid value only for decent angles
{
distanceMeasurement = fabsf((float)(1.0/cosPhi * 1.0/cosThe * alt_agl)); // assuming planar ground
}
emit sensorHilOpticalFlowChanged(QGC::groundTimeUsecs(), 0, 0, 0.0f,
0.0f, 0.0f, distanceMeasurement);
} else {
emit hilStateChanged(QGC::groundTimeUsecs(), roll, pitch, yaw, rollspeed,
pitchspeed, yawspeed, lat, lon, alt,
vx, vy, vz,
ind_airspeed, true_airspeed,
xacc, yacc, zacc);
//qDebug() << "hilStateChanged " << (qint32)lat << (qint32)lon << (qint32)alt;
}
// // Echo data for debugging purposes
// std::cerr << __FILE__ << __LINE__ << "Received datagram:" << std::endl;
// int i;
// for (i=0; i<s; i++)
// {
// unsigned int v=data[i];
// fprintf(stderr,"%02x ", v);
// }
// std::cerr << std::endl;
}
/**
* @brief Get the number of bytes to read.
*
* @return The number of bytes to read
**/
qint64 QGCFlightGearLink::bytesAvailable()
{
return _udpCommSocket->pendingDatagramSize();
}
/**
* @brief Disconnect the connection.
*
* @return True if connection has been disconnected, false if connection couldn't be disconnected.
**/
bool QGCFlightGearLink::disconnectSimulation()
{
disconnect(_fgProcess, static_cast<void (QProcess::*)(QProcess::ProcessError)>(&QProcess::error),
this, &QGCFlightGearLink::processError);
disconnect(_vehicle->uas(), &UAS::hilControlsChanged, this, &QGCFlightGearLink::updateControls);
disconnect(this, &QGCFlightGearLink::hilStateChanged, _vehicle->uas(), &UAS::sendHilState);
disconnect(this, &QGCFlightGearLink::sensorHilGpsChanged, _vehicle->uas(), &UAS::sendHilGps);
disconnect(this, &QGCFlightGearLink::sensorHilRawImuChanged, _vehicle->uas(), &UAS::sendHilSensors);
disconnect(this, &QGCFlightGearLink::sensorHilOpticalFlowChanged, _vehicle->uas(), &UAS::sendHilOpticalFlow);
if (_fgProcess)
{
_fgProcess->close();
_fgProcess->deleteLater();
_fgProcess = NULL;
}
if (_udpCommSocket)
{
_udpCommSocket->close();
_udpCommSocket->deleteLater();
_udpCommSocket = NULL;
}
connectState = false;
emit simulationDisconnected();
emit simulationConnected(false);
// Exit the thread
quit();
return !connectState;
}
/// @brief Splits a space separated set of command line arguments into a QStringList.
/// Quoted strings are allowed and handled correctly.
/// @param uiArgs Arguments to parse
/// @param argList Returned argument list
/// @return Returns false if the argument list has mistmatced quotes within in.
bool QGCFlightGearLink::parseUIArguments(QString uiArgs, QStringList& argList)
{
// FYI: The only reason this routine is public is so that we can reference it from a unit test.
// This is not as easy as it seams since some options can be quoted to preserve spaces within things like
// directories. There is likely some crazed regular expression which can do this. But after trying that
// route I gave up and instead here is the code which does it the hard way. Another thing to be aware of
// is that the QStringList passed to QProces::start is similar to what you would get in argv after the
// command line is processed. This means that quoted strings have the quotes removed before making it to argv.
bool inQuotedString = false;
bool previousSpace = false;
QString currentArg;
for (int i=0; i<uiArgs.size(); i++) {
const QChar chr = uiArgs[i];
if (chr == ' ') {
if (inQuotedString) {
// Space is inside quoted string leave it in
currentArg += chr;
continue;
} else {
if (previousSpace) {
// Disregard multiple spaces
continue;
} else {
// We have a space that is finishing an argument
previousSpace = true;
if (inQuotedString) {
QGCMessageBox::critical(tr("FlightGear HIL"), tr("FlightGear failed to start. There are mismatched quotes in specified command line options"));
return false;
}
if (!currentArg.isEmpty()) {
argList += currentArg;
currentArg.clear();
}
}
}
} else if (chr == '\"') {
// Flip the state of being in a quoted string. Note that we specifically do not add the
// quote to the string. This replicates standards command line parsing behaviour.
inQuotedString = !inQuotedString;
previousSpace = false;
} else {
previousSpace = false;
currentArg += chr;
}
}
// We should never end parsing on an unterminated quote
if (inQuotedString) {
return false;
}
// Finish up last arg
if (!currentArg.isEmpty()) {
argList += currentArg;
currentArg.clear();
}
return true;
}
/// @brief Locates the specified argument in the argument list, returning the value for it.
/// @param uiArgList Argument list to search through
/// @param argLabel Argument label to search for
/// @param argValue Returned argument value if found
/// @return Returns true if argument found and argValue returned
bool QGCFlightGearLink::_findUIArgument(const QStringList& uiArgList, const QString& argLabel, QString& argValue)
{
QString regExpStr = argLabel + "=(.*)";
int index = uiArgList.indexOf(QRegExp(regExpStr, Qt::CaseInsensitive));
if (index != -1) {
QRegExp regExp(regExpStr);
index = regExp.indexIn(uiArgList[index]);
Q_ASSERT(index != -1);
argValue = regExp.cap(1);
return true;
} else {
return false;
}
}
/**
* @brief Connect the connection.
*
* @return True if connection has been established, false if connection couldn't be established.
**/
bool QGCFlightGearLink::connectSimulation()
{
// We setup all the information we need to start FlightGear here such that if something goes
// wrong we can return false out of here. All of this happens on the main UI thread. Once we
// have that information setup we start the thread which will call run, which will in turn
// start the various FG processes on the separate thread.
if (!_vehicle->uas()) {
return false;
}
QString fgAppName;
QString fgRootPath; // FlightGear root data directory as specified by --fg-root
QStringList fgRootPathProposedList; // Directories we will attempt to search for --fg-root
bool fgRootDirOverride = false; // true: User has specified --fg-root from ui options
QString fgSceneryPath; // FlightGear scenery path as specified by --fg-scenery
bool fgSceneryDirOverride = false; // true: User has specified --fg-scenery from ui options
QDir fgAppDir; // Location of main FlightGear application
// Reset the list of arguments which will be provided to FG to the arguments set by the user via the UI
// First split the space separated command line arguments coming in from the ui into a QStringList since
// that is what QProcess::start needs.
QStringList uiArgList;
bool mismatchedQuotes = parseUIArguments(startupArguments, uiArgList);
if (!mismatchedQuotes) {
QGCMessageBox::critical(tr("FlightGear HIL"), tr("FlightGear failed to start. There are mismatched quotes in specified command line options"));
return false;
}
#ifdef DEBUG_FLIGHTGEAR_CONNECT
qDebug() << "\nSplit arguments" << uiArgList << "\n";
#endif
// Now set the FG arguments to the arguments from the UI
_fgArgList = uiArgList;
#if defined Q_OS_MAC
// Mac installs will default to the /Applications folder 99% of the time. Anything other than
// that is pretty non-standard so we don't try to get fancy beyond hardcoding that path.
fgAppDir.setPath("/Applications");
fgAppName = "FlightGear.app";
// new path
_fgProcessName = "./fgfs";
_fgProcessWorkingDirPath = "/Applications/FlightGear.app/Contents/MacOS/";
if(!QFileInfo(_fgProcessWorkingDirPath + _fgProcessName).exists()){
// old path
_fgProcessName = "./fgfs.sh";
_fgProcessWorkingDirPath = "/Applications/FlightGear.app/Contents/Resources/";
}
fgRootPathProposedList += "/Applications/FlightGear.app/Contents/Resources/data/";
#elif defined Q_OS_WIN32
_fgProcessName = "fgfs.exe";
// Windows installs are not as easy to determine. Default installation is to
// C:\Program Files\FlightGear, but that can be easily changed. That also doesn't
// tell us whether the user is running 32 or 64 bit which will both be installed there.
// The preferences for the fgrun app will tell us which version they are using
// and where it is. That is stored in the $APPDATA\fliggear.org\fgrun.prefs file. This
// looks to be a more stable location and way to determine app location so we use that.
fgAppName = "fgfs.exe";
const char* appdataEnv = "APPDATA";
if (!qgetenv(appdataEnv).isEmpty()) {
QString fgrunPrefsFile = QDir(qgetenv(appdataEnv).constData()).absoluteFilePath("flightgear.org/fgrun.prefs");
qDebug() << fgrunPrefsFile;
QFile file(fgrunPrefsFile);
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QTextStream in(&file);
QString lookahead; // lookahead for continuation lines
while (!in.atEnd() || !lookahead.isEmpty()) {
QString line;
QRegExp regExp;
// Prefs file has strange format where a line prepended with "+" is a continuation of the previous line.
// So do a lookahead to determine if we have a continuation or not.
if (!lookahead.isEmpty()) {
line = lookahead;
lookahead.clear();
} else {
line = in.readLine();
}
if (!in.atEnd()) {
lookahead = in.readLine();
regExp.setPattern("^\\+(.*)");
if (regExp.indexIn(lookahead) == 0) {
Q_ASSERT(regExp.captureCount() == 1);
line += regExp.cap(1);
lookahead.clear();
}
}
regExp.setPattern("^fg_exe:(.*)");
if (regExp.indexIn(line) == 0 && regExp.captureCount() == 1) {
QString fgExeLocationFullQualified = regExp.cap(1);
qDebug() << fgExeLocationFullQualified;
regExp.setPattern("(.*)\\\\fgfs.exe");
if (regExp.indexIn(fgExeLocationFullQualified) == 0 && regExp.captureCount() == 1) {
fgAppDir.setPath(regExp.cap(1));
_fgProcessWorkingDirPath = fgAppDir.absolutePath();
qDebug() << "fg_exe" << fgAppDir.absolutePath();
}
continue;
}
regExp.setPattern("^fg_root:(.*)");
if (regExp.indexIn(line) == 0 && regExp.captureCount() == 1) {
fgRootPathProposedList += QDir(regExp.cap(1)).absolutePath();
qDebug() << "fg_root" << fgRootPathProposedList[0];
continue;
}
regExp.setPattern("^fg_scenery:(.*)");
if (regExp.indexIn(line) == 0 && regExp.captureCount() == 1) {
// Scenery can contain multiple paths separated by ';' so don't do QDir::absolutePath on it
fgSceneryPath = regExp.cap(1);
qDebug() << "fg_scenery" << fgSceneryPath;
continue;
}
}
}
}
#elif defined Q_OS_LINUX
// Linux installs to a location on the path so we don't need a directory to run the executable
fgAppName = "fgfs";
_fgProcessName = "fgfs";
fgRootPathProposedList += "/usr/share/flightgear/data/"; // Default Archlinux location
fgRootPathProposedList += "/usr/share/games/flightgear/"; // Default Ubuntu location
#else
#error Unknown OS build flavor
#endif
#ifndef Q_OS_LINUX
// Validate the FlightGear application directory location. Linux runs from path so we don't validate on that OS.
Q_ASSERT(!fgAppName.isEmpty());
QString fgAppFullyQualified = fgAppDir.absoluteFilePath(fgAppName);
//-- TODO:
/*
while (!QFileInfo(fgAppFullyQualified).exists()) {
QMessageBox msgBox(QMessageBox::Critical,
tr("FlightGear application not found"),
tr("FlightGear application not found at: %1").arg(fgAppFullyQualified),
QMessageBox::Cancel,
MainWindow::instance());
msgBox.setWindowModality(Qt::ApplicationModal);
msgBox.addButton(tr("I'll specify directory"), QMessageBox::ActionRole);
if (msgBox.exec() == QMessageBox::Cancel) {
return false;
}
// Let the user pick the right directory
QString dirPath = QString(); //-- TODO: QGCQFileDialog::getExistingDirectory(MainWindow::instance(), tr("Please select directory of FlightGear application : ") + fgAppName);
if (dirPath.isEmpty()) {
return false;
}
fgAppDir.setPath(dirPath);
fgAppFullyQualified = fgAppDir.absoluteFilePath(fgAppName);
}
*/
#endif
// If we have an --fg-root coming in from the ui options, that setting overrides any internal searching of
// proposed locations.
QString argValue;
fgRootDirOverride = _findUIArgument(_fgArgList, "--fg-root", argValue);
if (fgRootDirOverride) {
fgRootPathProposedList.clear();
fgRootPathProposedList += argValue;
qDebug() << "--fg-root override" << argValue;
}
// See if we can find an --fg-root directory from the proposed list.
Q_ASSERT(fgRootPath.isEmpty());
for (int i=0; i<fgRootPathProposedList.count(); i++) {
fgRootPath = fgRootPathProposedList[i];
if (QFileInfo(fgRootPath).isDir()) {
// We found it
break;
} else {
fgRootPath.clear();
}
}
// Alert the user if we couldn't find an --fg-root
if (fgRootPath.isEmpty()) {
QString errMsg;
if (fgRootDirOverride) {
errMsg = tr("--fg-root directory specified from ui option not found: %1").arg(fgRootPath);
} else if (fgRootPath.isEmpty()) {
errMsg = tr("Unable to automatically determine --fg-root directory location. You will need to specify --fg-root=<directory> as an additional command line parameter from ui.");
}
QGCMessageBox::critical(tr("FlightGear HIL"), errMsg);
return false;
}
if (!fgRootDirOverride) {
_fgArgList += "--fg-root=" + fgRootPath;
}
// Add --fg-scenery command line arg. If --fg-scenery is specified from the ui we use that instead.
// On Windows --fg-scenery is required on the command line otherwise FlightGear won't boot.
fgSceneryDirOverride = _findUIArgument(_fgArgList, "--fg-scenery", argValue);
if (fgSceneryDirOverride) {
fgSceneryPath = argValue;
qDebug() << "--fg-scenery override" << argValue;
} else if (!fgSceneryPath.isEmpty()) {
_fgArgList += "--fg-scenery=" + fgSceneryPath;
}
#ifdef Q_OS_WIN32
// Windows won't start without an --fg-scenery set. We don't validate the directory in the path since
// it can be multiple paths.
if (fgSceneryPath.isEmpty()) {
QString errMsg;
if (fgSceneryDirOverride) {
errMsg = tr("--fg-scenery directory specified from ui option not found: %1").arg(fgSceneryPath);
} else {
errMsg = tr("Unable to automatically determine --fg-scenery directory location. You will need to specify --fg-scenery=directory as an additional command line parameter from ui.");
}
QGCMessageBox::critical(tr("FlightGear HIL"), errMsg);
return false;
}
#else
Q_UNUSED(fgSceneryDirOverride);
#endif
// Setup and verify directory which contains QGC provided aircraft files
QString qgcAircraftDir(QApplication::applicationDirPath() + "/flightgear/Aircraft");
if (!QFileInfo(qgcAircraftDir).isDir()) {
QGCMessageBox::critical(tr("FlightGear HIL"), tr("Incorrect %1 installation. Aircraft directory is missing: '%2'.").arg(qgcApp()->applicationName()).arg(qgcAircraftDir));
return false;
}
_fgArgList += "--fg-aircraft=" + qgcAircraftDir;
// Setup protocol we will be using to communicate with FlightGear
QString fgProtocol(_vehicle->vehicleType() == MAV_TYPE_QUADROTOR ? "qgroundcontrol-quadrotor" : "qgroundcontrol-fixed-wing");
QString fgProtocolArg("--generic=socket,%1,300,127.0.0.1,%2,udp,%3");
_fgArgList << fgProtocolArg.arg("out").arg(port).arg(fgProtocol);
_fgArgList << fgProtocolArg.arg("in").arg(currentPort).arg(fgProtocol);
// Verify directory where FlightGear stores communicaton protocols.
QDir fgProtocolDir(fgRootPath);
if (!fgProtocolDir.cd("Protocol")) {
QGCMessageBox::critical(tr("FlightGear HIL"), tr("Incorrect FlightGear setup. Protocol directory is missing: '%1'. Command line parameter for --fg-root may be set incorrectly.").arg(fgProtocolDir.path()));
return false;
}
// Verify directory which contains QGC provided FlightGear communication protocol files
QDir qgcProtocolDir(QApplication::applicationDirPath() + "/flightgear/Protocol/");
if (!qgcProtocolDir.isReadable()) {
QGCMessageBox::critical(tr("FlightGear HIL"), tr("Incorrect installation. Protocol directory is missing (%1).").arg(qgcProtocolDir.path()));
return false;
}
// Make sure we can find the communication protocol file in QGC install
QString fgProtocolXmlFile = fgProtocol + ".xml";
QString qgcProtocolFileFullyQualified = qgcProtocolDir.absoluteFilePath(fgProtocolXmlFile);
if (!QFileInfo(qgcProtocolFileFullyQualified).exists()) {
QGCMessageBox::critical(tr("FlightGear HIL"), tr("Incorrect installation. FlightGear protocol file missing: %1").arg(qgcProtocolFileFullyQualified));
return false;
}
// Communication protocol must be in FlightGear protocol directory. There does not appear to be any way
// around this by specifying something on the FlightGear command line. FG code does direct append
// of protocol xml file to $FG_ROOT and $FG_ROOT only allows a single directory to be specified.
_fgProtocolFileFullyQualified = fgProtocolDir.absoluteFilePath(fgProtocolXmlFile);
if (QFileInfo(_fgProtocolFileFullyQualified).exists()) {
// Verify that the file is current by comparing it against the one in QGC
QFile fgFile(_fgProtocolFileFullyQualified);
QFile qgcFile(qgcProtocolFileFullyQualified);
if (!fgFile.open(QIODevice::ReadOnly) ||
!qgcFile.open(QIODevice::ReadOnly)) {
QGCMessageBox::warning(tr("FlightGear HIL"), tr("Unable to verify that protocol file %1 is current. "
"If file is out of date, you may experience problems. "
"Safest approach is to delete the file manually and allow %2 install the latest file.").arg(qgcApp()->applicationName()).arg(_fgProtocolFileFullyQualified));
}
QByteArray fgBytes = fgFile.readAll();
QByteArray qgcBytes = qgcFile.readAll();
fgFile.close();
qgcFile.close();
if (fgBytes != qgcBytes) {
QGCMessageBox::warning(tr("FlightGear HIL"), tr("FlightGear protocol file %1 is out of date. It will be deleted, which will cause %2 to install the latest version of the file.").arg(_fgProtocolFileFullyQualified).arg(qgcApp()->applicationName()));
if (!QFile::remove(_fgProtocolFileFullyQualified)) {
QGCMessageBox::warning(tr("FlightGear HIL"), tr("Delete of protocol file failed. You will have to manually delete the file."));
return false;
}
}
}
if (!QFileInfo(_fgProtocolFileFullyQualified).exists()) {
QMessageBox msgBox(QMessageBox::Critical,
tr("FlightGear Failed to Start"),
tr("FlightGear Failed to Start. %1 protocol (%2) not installed to FlightGear Protocol directory (%3)").arg(qgcApp()->applicationName()).arg(fgProtocolXmlFile).arg(fgProtocolDir.path()),
QMessageBox::Cancel,
MainWindow::instance());
msgBox.setWindowModality(Qt::ApplicationModal);
msgBox.addButton(tr("Fix it for me"), QMessageBox::ActionRole);
if (msgBox.exec() == QMessageBox::Cancel) {
return false;
}
// Now that we made it this far, we should be able to try to copy the protocol file to FlightGear.
bool succeeded = QFile::copy(qgcProtocolFileFullyQualified, _fgProtocolFileFullyQualified);
if (!succeeded) {
#ifdef Q_OS_WIN32
QString copyCmd = QString("copy \"%1\" \"%2\"").arg(qgcProtocolFileFullyQualified).arg(_fgProtocolFileFullyQualified);
copyCmd.replace("/", "\\");
#else
QString copyCmd = QString("sudo cp %1 %2").arg(qgcProtocolFileFullyQualified).arg(_fgProtocolFileFullyQualified);
#endif
QMessageBox msgBox(QMessageBox::Critical,
tr("Copy failed"),
#ifdef Q_OS_WIN32
tr("Copy from (%1) to (%2) failed, possibly due to permissions issue. You will need to perform manually. Try pasting the following command into a Command Prompt which was started with Run as Administrator:\n\n").arg(qgcProtocolFileFullyQualified).arg(_fgProtocolFileFullyQualified) + copyCmd,
#else
tr("Copy from (%1) to (%2) failed, possibly due to permissions issue. You will need to perform manually. Try pasting the following command into a shell:\n\n").arg(qgcProtocolFileFullyQualified).arg(_fgProtocolFileFullyQualified) + copyCmd,
#endif
QMessageBox::Cancel,
MainWindow::instance());
msgBox.setWindowModality(Qt::ApplicationModal);
msgBox.addButton(tr("Copy to Clipboard"), QMessageBox::ActionRole);
if (msgBox.exec() != QMessageBox::Cancel) {
QApplication::clipboard()->setText(copyCmd);
}
return false;
}
}
// Start the engines to save a startup step
if (_vehicle->vehicleType() == MAV_TYPE_QUADROTOR) {
// Start all engines of the quad
_fgArgList << "--prop:/engines/engine[0]/running=true";
_fgArgList << "--prop:/engines/engine[1]/running=true";
_fgArgList << "--prop:/engines/engine[2]/running=true";
_fgArgList << "--prop:/engines/engine[3]/running=true";
} else {
_fgArgList << "--prop:/engines/engine/running=true";
}
// We start out at our home position
QGeoCoordinate homePosition = QGroundControlQmlGlobal::flightMapPosition();
_fgArgList << QString("--lat=%1").arg(homePosition.latitude());
_fgArgList << QString("--lon=%1").arg(homePosition.longitude());
// The altitude is not set because an altitude not equal to the ground altitude leads to a non-zero default throttle in flightgear
// Without the altitude-setting the aircraft is positioned on the ground
//_fgArgList << QString("--altitude=%1").arg(homePosition.altitude());
#ifdef DEBUG_FLIGHTGEAR_CONNECT
// This tell FlightGear to output highest debug level of log output. Handy for debuggin failures by looking at the FG
// log files.
_fgArgList << "--log-level=debug";
#endif
start(HighPriority);
return true;
}
void QGCFlightGearLink::_printFgfsOutput(void)
{
qDebug() << "fgfs stdout:";
QByteArray byteArray = _fgProcess->readAllStandardOutput();
QStringList strLines = QString(byteArray).split("\n");
for (const QString &line: strLines){
qDebug() << line;
}
}
void QGCFlightGearLink::_printFgfsError(void)
{
qDebug() << "fgfs stderr:";
QByteArray byteArray = _fgProcess->readAllStandardError();
QStringList strLines = QString(byteArray).split("\n");
for (const QString &line: strLines){
qDebug() << line;
}
}
/**
* @brief Set the startup arguments used to start flightgear
*
**/
void QGCFlightGearLink::setStartupArguments(QString startupArguments)
{
this->startupArguments = startupArguments;
}
/**
* @brief Check if connection is active.
*
* @return True if link is connected, false otherwise.
**/
bool QGCFlightGearLink::isConnected()
{
return connectState;
}
QString QGCFlightGearLink::getName()
{
return name;
}
QString QGCFlightGearLink::getRemoteHost()
{
return QString("%1:%2").arg(currentHost.toString(), currentPort);
}
void QGCFlightGearLink::setName(QString name)
{
this->name = name;
// emit nameChanged(this->name);
}
void QGCFlightGearLink::setBarometerOffset(float barometerOffsetkPa)
{
this->barometerOffsetkPa = barometerOffsetkPa;
}
/****************************************************************************
*
* (c) 2009-2018 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 UDP connection (server) for unmanned vehicles
* @author Lorenz Meier <mavteam@student.ethz.ch>
*
*/
#pragma once
#include <QString>
#include <QList>
#include <QMap>
#include <QMutex>
#include <QUdpSocket>
#include <QTimer>
#include <QProcess>
#include "LinkInterface.h"
#include "QGCConfig.h"
#include "UASInterface.h"
#include "QGCHilLink.h"
#include "QGCHilFlightGearConfiguration.h"
#include "Vehicle.h"
class QGCFlightGearLink : public QGCHilLink
{
Q_OBJECT
public:
QGCFlightGearLink(Vehicle* vehicle, QString startupArguments, QString remoteHost=QString("127.0.0.1:49000"), QHostAddress host = QHostAddress::Any, quint16 port = 49005);
~QGCFlightGearLink();
bool isConnected();
qint64 bytesAvailable();
int getPort() const {
return port;
}
/**
* @brief The human readable port name
*/
QString getName();
/**
* @brief Get remote host and port
* @return string in format <host>:<port>
*/
QString getRemoteHost();
QString getVersion()
{
return QString("FlightGear %1").arg(flightGearVersion);
}
int getAirFrameIndex()
{
return -1;
}
bool sensorHilEnabled() {
return _sensorHilEnabled;
}
void sensorHilEnabled(bool sensorHilEnabled) {
_sensorHilEnabled = sensorHilEnabled;
}
static bool parseUIArguments(QString uiArgs, QStringList& argList);
void run();
signals:
void showCriticalMessageFromThread(const QString& title, const QString& message);
public slots:
// void setAddress(QString address);
void setPort(int port);
/** @brief Add a new host to broadcast messages to */
void setRemoteHost(const QString& host);
/** @brief Send new control states to the simulation */
void updateControls(quint64 time, float rollAilerons, float pitchElevator, float yawRudder, float throttle, quint8 systemMode, quint8 navMode);
/** @brief Set the simulator version as text string */
void setVersion(const QString& version)
{
Q_UNUSED(version);
}
void selectAirframe(const QString& airframe)
{
Q_UNUSED(airframe);
}
void enableSensorHIL(bool enable) {
if (enable != _sensorHilEnabled)
_sensorHilEnabled = enable;
emit sensorHilChanged(enable);
}
void readBytes();
private slots:
/**
* @brief Write a number of bytes to the interface.
*
* @param data Pointer to the data byte array
* @param size The size of the bytes array
**/
void _writeBytes(const QByteArray data);
public slots:
bool connectSimulation();
bool disconnectSimulation();
void setStartupArguments(QString startupArguments);
void setBarometerOffset(float barometerOffsetkPa);
void processError(QProcess::ProcessError err);
protected:
void setName(QString name);
private slots:
void _printFgfsOutput(void);
void _printFgfsError(void);
private:
static bool _findUIArgument(const QStringList& uiArgList, const QString& argLabel, QString& argValue);
Vehicle* _vehicle;
QString _fgProcessName; ///< FlightGear process to start
QString _fgProcessWorkingDirPath; ///< Working directory to start FG process in, empty for none
QStringList _fgArgList; ///< Arguments passed to FlightGear process
QUdpSocket* _udpCommSocket; ///< UDP communication sockect between FG and QGC
QProcess* _fgProcess; ///< FlightGear process
QString _fgProtocolFileFullyQualified; ///< Fully qualified file name for protocol file
QString name;
QHostAddress host;
QHostAddress currentHost;
quint16 currentPort;
quint16 port;
int id;
bool connectState;
unsigned int flightGearVersion;
QString startupArguments;
bool _sensorHilEnabled;
float barometerOffsetkPa;
};
add_library(qgcunittest
FileDialogTest.cc
FileManagerTest.cc
FlightGearTest.cc
#FileDialogTest.cc
#FileManagerTest.cc
#FlightGearTest.cc
GeoTest.cc
LinkManagerTest.cc
MainWindowTest.cc
#MainWindowTest.cc
MavlinkLogTest.cc
MessageBoxTest.cc
#MessageBoxTest.cc
MultiSignalSpy.cc
RadioConfigTest.cc
#RadioConfigTest.cc
TCPLinkTest.cc
TCPLoopBackServer.cc
UnitTest.cc
......
add_library(ui
HILDockWidget.cc
linechart/ChartPlot.cc
linechart/IncrementalPlot.cc
linechart/LinechartPlot.cc
linechart/Linecharts.cc
linechart/LinechartWidget.cc
linechart/Scrollbar.cc
linechart/ScrollZoomer.cc
MainWindow.cc
MAVLinkDecoder.cc
MultiVehicleDockWidget.cc
QGCHilConfiguration.cc
QGCHilFlightGearConfiguration.cc
QGCHilJSBSimConfiguration.cc
QGCHilXPlaneConfiguration.cc
QGCMapRCToParamDialog.cpp
QGCMAVLinkLogPlayer.cc
QGCPluginHost.cc
QGCUASFileView.cc
QGCUASFileViewMulti.cc
uas/QGCUnconnectedInfoWidget.cc
# HEADERS
# shouldn't be listed here, but aren't named properly for AUTOMOC
QGCHilFlightGearConfiguration.h
QGCHilJSBSimConfiguration.h
MAVLinkDecoder.h
MultiVehicleDockWidget.h
QGCMapRCToParamDialog.h
QGCPluginHost.h
# UI
MainWindow.ui
MultiVehicleDockWidget.ui
QGCHilConfiguration.ui
QGCHilFlightGearConfiguration.ui
QGCHilJSBSimConfiguration.ui
QGCHilXPlaneConfiguration.ui
QGCMapRCToParamDialog.ui
QGCMAVLinkLogPlayer.ui
QGCPluginHost.ui
QGCUASFileView.ui
QGCUASFileViewMulti.ui
QMap3D.ui
uas/QGCUnconnectedInfoWidget.ui
)
target_link_libraries(ui
PUBLIC
qgc
PRIVATE
qwt
)
target_include_directories(ui
......
/****************************************************************************
*
* (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.
*
****************************************************************************/
#include "HILDockWidget.h"
#include "QGCHilConfiguration.h"
HILDockWidget::HILDockWidget(const QString& title, QAction* action, QWidget *parent)
: MultiVehicleDockWidget(title, action, parent)
{
init();
loadSettings();
}
HILDockWidget::~HILDockWidget()
{
}
QWidget* HILDockWidget::_newVehicleWidget(Vehicle* vehicle, QWidget* parent)
{
return new QGCHilConfiguration(vehicle, parent);
}
/****************************************************************************
*
* (c) 2009-2018 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.
*
****************************************************************************/
#pragma once
#include "MultiVehicleDockWidget.h"
class HILDockWidget : public MultiVehicleDockWidget
{
Q_OBJECT
public:
explicit HILDockWidget(const QString& title, QAction* action, QWidget *parent = 0);
~HILDockWidget();
protected:
// Override from MultiVehicleDockWidget
virtual QWidget* _newVehicleWidget(Vehicle* vehicle, QWidget* parent);
};
/****************************************************************************
*
* (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.
*
****************************************************************************/
#include <QSettings>
#include "QGCHilConfiguration.h"
#include "ui_QGCHilConfiguration.h"
#include "QGCHilFlightGearConfiguration.h"
#include "QGCHilJSBSimConfiguration.h"
#include "QGCHilXPlaneConfiguration.h"
#include "UAS.h"
QGCHilConfiguration::QGCHilConfiguration(Vehicle* vehicle, QWidget *parent)
: QWidget(parent)
, _vehicle(vehicle)
, ui(new Ui::QGCHilConfiguration)
{
ui->setupUi(this);
// XXX its quite wrong that this is implicitely a factory
// class, but this is something to clean up for later.
QSettings settings;
settings.beginGroup("QGC_HILCONFIG");
int i = settings.value("SIMULATOR_INDEX", -1).toInt();
if (i > 0) {
// ui->simComboBox->blockSignals(true);
ui->simComboBox->setCurrentIndex(i);
// ui->simComboBox->blockSignals(false);
on_simComboBox_currentIndexChanged(i);
}
settings.endGroup();
}
void QGCHilConfiguration::receiveStatusMessage(const QString& message)
{
ui->statusLabel->setText(message);
}
QGCHilConfiguration::~QGCHilConfiguration()
{
QSettings settings;
settings.beginGroup("QGC_HILCONFIG");
settings.setValue("SIMULATOR_INDEX", ui->simComboBox->currentIndex());
settings.endGroup();
delete ui;
}
void QGCHilConfiguration::setVersion(QString version)
{
Q_UNUSED(version);
}
void QGCHilConfiguration::on_simComboBox_currentIndexChanged(int index)
{
//clean up
QLayoutItem *child;
while ((child = ui->simulatorConfigurationLayout->takeAt(0)) != 0)
{
delete child->widget();
delete child;
}
if(1 == index)
{
// Ensure the sim exists and is disabled
_vehicle->uas()->enableHilFlightGear(false, "", true, this);
QGCHilFlightGearConfiguration* hfgconf = new QGCHilFlightGearConfiguration(_vehicle, this);
hfgconf->show();
ui->simulatorConfigurationLayout->addWidget(hfgconf);
QGCFlightGearLink* fg = dynamic_cast<QGCFlightGearLink*>(_vehicle->uas()->getHILSimulation());
if (fg)
{
connect(fg, &QGCFlightGearLink::statusMessage, ui->statusLabel, &QLabel::setText);
}
}
else if (2 == index || 3 == index)
{
// Ensure the sim exists and is disabled
_vehicle->uas()->enableHilXPlane(false);
QGCHilXPlaneConfiguration* hxpconf = new QGCHilXPlaneConfiguration(_vehicle->uas()->getHILSimulation(), this);
hxpconf->show();
ui->simulatorConfigurationLayout->addWidget(hxpconf);
// Select correct version of XPlane
QGCXPlaneLink* xplane = dynamic_cast<QGCXPlaneLink*>(_vehicle->uas()->getHILSimulation());
if (xplane)
{
xplane->setVersion((index == 2) ? 10 : 9);
connect(xplane, &QGCXPlaneLink::statusMessage, ui->statusLabel, &QLabel::setText);
}
}
// Disabling JSB Sim since its not well maintained,
// but as refactoring is pending we're not ditching the code yet
// else if (4)
// {
// // Ensure the sim exists and is disabled
// _vehicle->uas()->enableHilJSBSim(false, "");
// QGCHilJSBSimConfiguration* hfgconf = new QGCHilJSBSimConfiguration(_vehicle, this);
// hfgconf->show();
// ui->simulatorConfigurationLayout->addWidget(hfgconf);
// QGCJSBSimLink* jsb = dynamic_cast<QGCJSBSimLink*>(_vehicle->uas()->getHILSimulation());
// if (jsb)
// {
// connect(jsb, SIGNAL(statusMessage(QString)), ui->statusLabel, SLOT(setText(QString)));
// }
// }
}
/****************************************************************************
*
* (c) 2009-2018 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.
*
****************************************************************************/
#pragma once
#include <QWidget>
#include "Vehicle.h"
namespace Ui {
class QGCHilConfiguration;
}
class QGCHilConfiguration : public QWidget
{
Q_OBJECT
public:
QGCHilConfiguration(Vehicle* vehicle, QWidget *parent = 0);
~QGCHilConfiguration();
public slots:
/** @brief Receive status message */
void receiveStatusMessage(const QString& message);
void setVersion(QString version);
private slots:
void on_simComboBox_currentIndexChanged(int index);
private:
Vehicle* _vehicle;
Ui::QGCHilConfiguration *ui;
};
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QGCHilConfiguration</class>
<widget class="QWidget" name="QGCHilConfiguration">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>366</width>
<height>301</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>HIL Config</string>
</property>
<layout class="QGridLayout" name="gridLayout" rowstretch="1,100,1" columnstretch="40,0">
<item row="0" column="0">
<widget class="QLabel" name="simLabel">
<property name="text">
<string>Simulator</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="simComboBox">
<property name="editable">
<bool>true</bool>
</property>
<item>
<property name="text">
<string/>
</property>
</item>
<item>
<property name="text">
<string>FlightGear 3.0+</string>
</property>
</item>
<item>
<property name="text">
<string>X-Plane 10</string>
</property>
</item>
<item>
<property name="text">
<string>X-Plane 9</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0" colspan="2">
<layout class="QVBoxLayout" name="simulatorConfigurationLayout">
<property name="spacing">
<number>0</number>
</property>
</layout>
</item>
<item row="2" column="0" colspan="2">
<widget class="QLabel" name="statusLabel">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
#include "QGCHilFlightGearConfiguration.h"
#include "MainWindow.h"
#include "UAS.h"
#include <QMenu>
// Various settings groups and keys
const char* QGCHilFlightGearConfiguration::_settingsGroup = "QGC_HILCONFIG_FLIGHTGEAR";
const char* QGCHilFlightGearConfiguration::_mavSettingsSubGroupFixedWing = "FIXED_WING";
const char* QGCHilFlightGearConfiguration::_mavSettingsSubGroupQuadRotor = "QUADROTOR";
const char* QGCHilFlightGearConfiguration::_aircraftKey = "AIRCRAFT";
const char* QGCHilFlightGearConfiguration::_optionsKey = "OPTIONS";
const char* QGCHilFlightGearConfiguration::_barometerOffsetKey = "BARO_OFFSET";
const char* QGCHilFlightGearConfiguration::_sensorHilKey = "SENSOR_HIL";
// Default set of optional command line parameters. If FlightGear won't run HIL without it it should go into
// the QGCFlightGearLink code instead.
const char* QGCHilFlightGearConfiguration::_defaultOptions = "--roll=0 --pitch=0 --vc=0 --heading=300 --timeofday=noon --disable-hud-3d --disable-fullscreen --geometry=400x300 --disable-anti-alias-hud --wind=0@0 --turbulence=0.0 --disable-sound --disable-random-objects --disable-ai-traffic --shading-flat --fog-disable --disable-specular-highlight --disable-panel --disable-clouds --fdm=jsb --units-meters --enable-terrasync";
QGCHilFlightGearConfiguration::QGCHilFlightGearConfiguration(Vehicle* vehicle, QWidget *parent)
: QWidget(parent)
, _vehicle(vehicle)
, _mavSettingsSubGroup(NULL)
, _resetOptionsAction(tr("Reset to default options"), this)
{
_ui.setupUi(this);
QStringList items;
if (_vehicle->vehicleType() == MAV_TYPE_FIXED_WING)
{
/*items << "EasyStar";*/
items << "Rascal110-JSBSim";
/*items << "c172p";
items << "YardStik";
items << "Malolo1";*/
_mavSettingsSubGroup = _mavSettingsSubGroupFixedWing;
}
/*else if (_vehicle->vehicleType() == MAV_TYPE_QUADROTOR)
{
items << "arducopter";
_mavSettingsSubGroup = _mavSettingsSubGroupQuadRotor;
}*/
else
{
// FIXME: Should disable all input, won't work. Show error message in the status label thing.
items << "<aircraft>";
}
_ui.aircraftComboBox->addItems(items);
QSettings settings;
settings.beginGroup(_settingsGroup);
settings.beginGroup(_mavSettingsSubGroup);
QString aircraft = settings.value(_aircraftKey).toString();
QString options = settings.value(_optionsKey).toString();
QString baroOffset = settings.value(_barometerOffsetKey).toString();
bool sensorHil = settings.value(_sensorHilKey, QVariant(true)).toBool();
settings.endGroup();
settings.endGroup();
if (!aircraft.isEmpty()) {
int index = _ui.aircraftComboBox->findText(aircraft);
if (index != -1) {
_ui.aircraftComboBox->setCurrentIndex(index);
}
}
if (options.isEmpty()) {
options = _defaultOptions;
}
_ui.optionsPlainTextEdit->setPlainText(options);
_ui.barometerOffsetLineEdit->setText(baroOffset);
_ui.sensorHilCheckBox->setChecked(sensorHil);
// Provide an option on the context menu to reset the option back to default
_ui.optionsPlainTextEdit->setContextMenuPolicy(Qt::CustomContextMenu);
bool success = connect(&_resetOptionsAction, &QAction::triggered, this, &QGCHilFlightGearConfiguration::_setDefaultOptions);
Q_ASSERT(success);
success = connect(_ui.optionsPlainTextEdit, &QPlainTextEdit::customContextMenuRequested,
this, &QGCHilFlightGearConfiguration::_showContextMenu);
Q_ASSERT(success);
Q_UNUSED(success); // Silence release build unused variable warning
}
QGCHilFlightGearConfiguration::~QGCHilFlightGearConfiguration()
{
QString aircraft = _ui.aircraftComboBox->currentText();
QString options = _ui.optionsPlainTextEdit->toPlainText();
QString baroOffset = _ui.barometerOffsetLineEdit->text();
bool sensorHil = _ui.sensorHilCheckBox->isChecked();
QSettings settings;
settings.beginGroup(_settingsGroup);
settings.beginGroup(_mavSettingsSubGroup);
if (aircraft.isEmpty()) {
settings.remove(_aircraftKey);
} else {
settings.setValue(_aircraftKey, aircraft);
}
if (options.isEmpty() || options == _defaultOptions) {
settings.remove(_optionsKey);
} else {
settings.setValue(_optionsKey, options);
}
// Double QVariant is to convert from string to float. It will change to 0.0 if invalid.
settings.setValue(_barometerOffsetKey, QVariant(QVariant(baroOffset).toFloat()));
settings.setValue(_sensorHilKey, QVariant(sensorHil));
settings.endGroup();
settings.endGroup();
}
void QGCHilFlightGearConfiguration::on_startButton_clicked()
{
//XXX check validity of inputs
QString options = _ui.optionsPlainTextEdit->toPlainText();
options.append(" --aircraft=" + _ui.aircraftComboBox->currentText());
_vehicle->uas()->enableHilFlightGear(true, options, _ui.sensorHilCheckBox->isChecked(), this);
}
void QGCHilFlightGearConfiguration::on_stopButton_clicked()
{
_vehicle->uas()->stopHil();
}
void QGCHilFlightGearConfiguration::on_barometerOffsetLineEdit_textChanged(const QString& baroOffset)
{
emit barometerOffsetChanged(baroOffset.toFloat());
}
void QGCHilFlightGearConfiguration::_showContextMenu(const QPoint &pt)
{
QMenu* menu = _ui.optionsPlainTextEdit->createStandardContextMenu();
menu->addAction(&_resetOptionsAction);
menu->exec(_ui.optionsPlainTextEdit->mapToGlobal(pt));
delete menu;
}
void QGCHilFlightGearConfiguration::_setDefaultOptions(void)
{
_ui.optionsPlainTextEdit->setPlainText(_defaultOptions);
}
#pragma once
#include <QWidget>
#include "QGCHilLink.h"
#include "QGCFlightGearLink.h"
#include "Vehicle.h"
#include "ui_QGCHilFlightGearConfiguration.h"
namespace Ui {
class QGCHilFlightGearConfiguration;
}
class QGCHilFlightGearConfiguration : public QWidget
{
Q_OBJECT
public:
explicit QGCHilFlightGearConfiguration(Vehicle* vehicle, QWidget *parent = 0);
~QGCHilFlightGearConfiguration();
protected:
private slots:
void on_startButton_clicked();
void on_stopButton_clicked();
void on_barometerOffsetLineEdit_textChanged(const QString& baroOffset);
void _setDefaultOptions(void);
void _showContextMenu(const QPoint& pt);
private:
Vehicle* _vehicle;
Ui::QGCHilFlightGearConfiguration _ui;
static const char* _settingsGroup; /// Top level settings group
const char* _mavSettingsSubGroup; /// We maintain a settings sub group per mav type
static const char* _mavSettingsSubGroupFixedWing; /// Subgroup if mav type is MAV_TYPE_FIXED_WING
static const char* _mavSettingsSubGroupQuadRotor; /// Subgroup is mav type is MAV_TYPE_QUADROTOR
static const char* _aircraftKey; /// Settings key for aircraft selection
static const char* _optionsKey; /// Settings key for FlightGear cmd line options
static const char* _barometerOffsetKey; /// Settings key for barometer offset
static const char* _sensorHilKey; /// Settings key for Sensor Hil checkbox
static const char* _defaultOptions; /// Default set of FlightGEar command line options
QAction _resetOptionsAction; /// Context menu item to reset options to default
signals:
void barometerOffsetChanged(float barometerOffsetkPa);
};
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QGCHilFlightGearConfiguration</class>
<widget class="QWidget" name="QGCHilFlightGearConfiguration">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>237</width>
<height>209</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="margin">
<number>0</number>
</property>
<property name="spacing">
<number>6</number>
</property>
<item row="1" column="0" colspan="2">
<widget class="QComboBox" name="aircraftComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="optionsLabel">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Additional Options:&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="aircraftPlaintextInfoLabel">
<property name="text">
<string>Airframe:</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<widget class="QPlainTextEdit" name="optionsPlainTextEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="plainText">
<string></string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QPushButton" name="startButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Start</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QPushButton" name="stopButton">
<property name="text">
<string>Stop</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="sensorHilCheckBox">
<property name="text">
<string>Sensor HIL</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="barometerOffsetLabel">
<property name="text">
<string>Barometer Offset [kPa]:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="barometerOffsetLineEdit">
<property name="text">
<string>0</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
#include "QGCHilJSBSimConfiguration.h"
#include "ui_QGCHilJSBSimConfiguration.h"
#include "MainWindow.h"
#include "UAS.h"
QGCHilJSBSimConfiguration::QGCHilJSBSimConfiguration(Vehicle* vehicle, QWidget *parent)
: QWidget(parent)
, _vehicle(vehicle)
, ui(new Ui::QGCHilJSBSimConfiguration)
{
ui->setupUi(this);
QStringList items = QStringList();
if (_vehicle->vehicleType() == MAV_TYPE_FIXED_WING)
{
items << "EasyStar";
items << "Rascal110-JSBSim";
items << "c172p";
items << "YardStik";
items << "Malolo1";
}
else if (_vehicle->vehicleType() == MAV_TYPE_QUADROTOR)
{
items << "arducopter";
}
else
{
items << "<aircraft>";
}
ui->aircraftComboBox->addItems(items);
}
QGCHilJSBSimConfiguration::~QGCHilJSBSimConfiguration()
{
delete ui;
}
void QGCHilJSBSimConfiguration::on_startButton_clicked()
{
//XXX check validity of inputs
QString options = ui->optionsPlainTextEdit->toPlainText();
options.append(" --script=" + ui->aircraftComboBox->currentText());
_vehicle->uas()->enableHilJSBSim(true, options);
}
void QGCHilJSBSimConfiguration::on_stopButton_clicked()
{
_vehicle->uas()->stopHil();
}
#pragma once
#include <QWidget>
#include "QGCHilLink.h"
#include "QGCFlightGearLink.h"
#include "Vehicle.h"
namespace Ui {
class QGCHilJSBSimConfiguration;
}
class QGCHilJSBSimConfiguration : public QWidget
{
Q_OBJECT
public:
explicit QGCHilJSBSimConfiguration(Vehicle* vehicle, QWidget *parent = 0);
~QGCHilJSBSimConfiguration();
private slots:
void on_startButton_clicked();
void on_stopButton_clicked();
private:
Vehicle* _vehicle;
Ui::QGCHilJSBSimConfiguration *ui;
};
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QGCHilJSBSimConfiguration</class>
<widget class="QWidget" name="QGCHilJSBSimConfiguration">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>237</width>
<height>204</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<layout class="QGridLayout" name="gridLayout" rowminimumheight="0,0,0,0,0,0,0,0">
<property name="margin">
<number>0</number>
</property>
<property name="spacing">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="aircraftPlaintextInfoLabel">
<property name="text">
<string>Airframe:</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QComboBox" name="aircraftComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="optionsLabel">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Additional Options:&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QPlainTextEdit" name="optionsPlainTextEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="plainText">
<string>--in-air --roll=0 --pitch=0 --vc=90 --heading=300 --timeofday=noon --disable-hud-3d --disable-fullscreen --geometry=400x300 --disable-anti-alias-hud --wind=0@0 --turbulence=0.0 --prop:/sim/frame-rate-throttle-hz=30 --control=mouse --disable-intro-music --disable-sound --disable-random-objects --disable-ai-models --shading-flat --fog-disable --disable-specular-highlight --disable-random-objects --disable-panel --disable-clouds --fdm=jsb --units-meters --prop:/engines/engine/running=true</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QPushButton" name="startButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Start</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QPushButton" name="stopButton">
<property name="text">
<string>Stop</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
#include "QGCHilXPlaneConfiguration.h"
#include "ui_QGCHilXPlaneConfiguration.h"
#include "QGCXPlaneLink.h"
#include "QGCHilConfiguration.h"
QGCHilXPlaneConfiguration::QGCHilXPlaneConfiguration(QGCHilLink* link, QGCHilConfiguration *parent) :
QWidget(parent),
ui(new Ui::QGCHilXPlaneConfiguration)
{
ui->setupUi(this);
this->link = link;
connect(ui->startButton, &QPushButton::clicked, this, &QGCHilXPlaneConfiguration::toggleSimulation);
connect(ui->hostComboBox, static_cast<void (QComboBox::*)(const QString&)>(&QComboBox::activated),
link, &QGCHilLink::setRemoteHost);
connect(link, &QGCHilLink::remoteChanged, ui->hostComboBox, &QComboBox::setEditText);
connect(link, &QGCHilLink::statusMessage, parent, &QGCHilConfiguration::receiveStatusMessage);
// connect(mav->getHILSimulation(), SIGNAL(statusMessage(QString)), this, SLOT(receiveStatusMessage(QString)));
// connect(ui->simComboBox, SIGNAL(activated(QString)), mav->getHILSimulation(), SLOT(setVersion(QString)));
ui->startButton->setText(tr("Connect"));
QGCXPlaneLink* xplane = dynamic_cast<QGCXPlaneLink*>(link);
if (xplane)
{
// connect(ui->randomAttitudeButton, SIGNAL(clicked()), link, SLOT(setRandomAttitude()));
// connect(ui->randomPositionButton, SIGNAL(clicked()), link, SLOT(setRandomPosition()));
//ui->airframeComboBox->setCurrentIndex(link->getAirFrameIndex());
//connect(ui->airframeComboBox, SIGNAL(activated(QString)), link, SLOT(selectAirframe(QString)));
// XXX not implemented yet
//ui->airframeComboBox->hide();
ui->sensorHilCheckBox->setChecked(xplane->sensorHilEnabled());
ui->useHilActuatorControlsCheckBox->setChecked(true);
connect(xplane, &QGCXPlaneLink::sensorHilChanged, ui->sensorHilCheckBox, &QCheckBox::setChecked);
connect(ui->sensorHilCheckBox, &QCheckBox::clicked, xplane, &QGCXPlaneLink::enableSensorHIL);
connect(xplane, &QGCXPlaneLink::useHilActuatorControlsChanged, ui->useHilActuatorControlsCheckBox, &QCheckBox::setChecked);
connect(ui->useHilActuatorControlsCheckBox, &QCheckBox::clicked, xplane, &QGCXPlaneLink::enableHilActuatorControls);
connect(link, static_cast<void (QGCHilLink::*)(int)>(&QGCHilLink::versionChanged),
this, &QGCHilXPlaneConfiguration::setVersion);
}
ui->hostComboBox->clear();
ui->hostComboBox->addItem(link->getRemoteHost());
}
void QGCHilXPlaneConfiguration::setVersion(int version)
{
Q_UNUSED(version);
}
void QGCHilXPlaneConfiguration::toggleSimulation(bool connect)
{
if (!link) {
return;
}
Q_UNUSED(connect);
if (!link->isConnected())
{
link->setRemoteHost(ui->hostComboBox->currentText());
link->connectSimulation();
ui->startButton->setText(tr("Disconnect"));
}
else
{
link->disconnectSimulation();
ui->startButton->setText(tr("Connect"));
}
}
QGCHilXPlaneConfiguration::~QGCHilXPlaneConfiguration()
{
delete ui;
}
#pragma once
#include <QWidget>
#include "QGCHilLink.h"
class QGCHilConfiguration;
namespace Ui {
class QGCHilXPlaneConfiguration;
}
class QGCHilXPlaneConfiguration : public QWidget
{
Q_OBJECT
public:
explicit QGCHilXPlaneConfiguration(QGCHilLink* link, QGCHilConfiguration *parent = 0);
~QGCHilXPlaneConfiguration();
public slots:
/** @brief Start / stop simulation */
void toggleSimulation(bool connect);
/** @brief Set X-Plane version */
void setVersion(int version);
protected:
QGCHilLink* link;
private:
Ui::QGCHilXPlaneConfiguration *ui;
};
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QGCHilXPlaneConfiguration</class>
<widget class="QWidget" name="QGCHilXPlaneConfiguration">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>269</width>
<height>150</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout" rowstretch="0,0,0,0,0,0,0,0" columnstretch="20,0,0">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="1" column="1" colspan="2">
<widget class="QPushButton" name="startButton">
<property name="text">
<string>Start</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="hostLabel">
<property name="text">
<string>Host</string>
</property>
</widget>
</item>
<item row="7" column="0" colspan="3">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>1</height>
</size>
</property>
</spacer>
</item>
<item row="3" column="0" colspan="3">
<widget class="QCheckBox" name="sensorHilCheckBox">
<property name="text">
<string>Enable sensor level HIL</string>
</property>
</widget>
</item>
<item row="0" column="1" colspan="2">
<widget class="QComboBox" name="hostComboBox">
<property name="editable">
<bool>true</bool>
</property>
<item>
<property name="text">
<string>127.0.0.1:49000</string>
</property>
</item>
</widget>
</item>
<item row="4" column="0" colspan="3">
<widget class="QCheckBox" name="useHilActuatorControlsCheckBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Use newer actuator format</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
#include <QStandardPaths>
#include <QtEndian>
#include "MainWindow.h"
#ifndef NO_SERIAL_LINK
#include "SerialLink.h"
#endif
#include "QGCMAVLinkLogPlayer.h"
#include "QGC.h"
#include "ui_QGCMAVLinkLogPlayer.h"
#include "QGCApplication.h"
#include "SettingsManager.h"
#include "AppSettings.h"
#include "LinkManager.h"
#include "QGCQFileDialog.h"
#include "QGCMessageBox.h"
QGCMAVLinkLogPlayer::QGCMAVLinkLogPlayer(QWidget *parent)
: QWidget (parent)
, _replayLink (NULL)
, _lastCurrentTime (0)
, _ui (new Ui::QGCMAVLinkLogPlayer)
{
_ui->setupUi(this);
_ui->horizontalLayout->setAlignment(Qt::AlignTop);
// Setup buttons
connect(_ui->selectFileButton, &QPushButton::clicked, this, &QGCMAVLinkLogPlayer::_selectLogFileForPlayback);
connect(_ui->playButton, &QPushButton::clicked, this, &QGCMAVLinkLogPlayer::_playPauseToggle);
connect(_ui->positionSlider, &QSlider::valueChanged, this, &QGCMAVLinkLogPlayer::_setPlayheadFromSlider);
connect(_ui->positionSlider, &QSlider::sliderPressed, this, &QGCMAVLinkLogPlayer::_pause);
#if 0
// Speed slider is removed from 3.0 release. Too broken to fix.
connect(_ui->speedSlider, &QSlider::valueChanged, this, &QGCMAVLinkLogPlayer::_setAccelerationFromSlider);
_ui->speedSlider->setMinimum(-100);
_ui->speedSlider->setMaximum(100);
_ui->speedSlider->setValue(0);
#endif
_enablePlaybackControls(false);
_ui->positionSlider->setMinimum(0);
_ui->positionSlider->setMaximum(100);
}
QGCMAVLinkLogPlayer::~QGCMAVLinkLogPlayer()
{
delete _ui;
}
void QGCMAVLinkLogPlayer::_playPauseToggle(void)
{
if (_replayLink->isPlaying()) {
_pause();
} else {
_replayLink->play();
}
}
void QGCMAVLinkLogPlayer::_pause(void)
{
_replayLink->pause();
}
void QGCMAVLinkLogPlayer::_selectLogFileForPlayback(void)
{
// Disallow replay when any links are connected
if (qgcApp()->toolbox()->multiVehicleManager()->activeVehicle()) {
QGCMessageBox::information(tr("Log Replay"), tr("You must close all connections prior to replaying a log."));
return;
}
QString logFilename = QGCQFileDialog::getOpenFileName(
this,
tr("Load Telemetry Log File"),
qgcApp()->toolbox()->settingsManager()->appSettings()->telemetrySavePath(),
tr("MAVLink Log Files (*.tlog);;All Files (*)"));
if (logFilename.isEmpty()) {
return;
}
LogReplayLinkConfiguration* linkConfig = new LogReplayLinkConfiguration(QString("Log Replay"));
linkConfig->setLogFilename(logFilename);
linkConfig->setName(linkConfig->logFilenameShort());
_ui->logFileNameLabel->setText(linkConfig->logFilenameShort());
LinkManager* linkMgr = qgcApp()->toolbox()->linkManager();
SharedLinkConfigurationPointer sharedConfig = linkMgr->addConfiguration(linkConfig);
_replayLink = (LogReplayLink*)qgcApp()->toolbox()->linkManager()->createConnectedLink(sharedConfig);
connect(_replayLink, &LogReplayLink::logFileStats, this, &QGCMAVLinkLogPlayer::_logFileStats);
connect(_replayLink, &LogReplayLink::playbackStarted, this, &QGCMAVLinkLogPlayer::_playbackStarted);
connect(_replayLink, &LogReplayLink::playbackPaused, this, &QGCMAVLinkLogPlayer::_playbackPaused);
connect(_replayLink, &LogReplayLink::playbackPercentCompleteChanged, this, &QGCMAVLinkLogPlayer::_playbackPercentCompleteChanged);
connect(_replayLink, &LogReplayLink::currentLogTimeSecs, this, &QGCMAVLinkLogPlayer::_setCurrentLogTime);
connect(_replayLink, &LogReplayLink::disconnected, this, &QGCMAVLinkLogPlayer::_replayLinkDisconnected);
_ui->positionSlider->setValue(0);
#if 0
_ui->speedSlider->setValue(0);
#endif
}
void QGCMAVLinkLogPlayer::_playbackError(void)
{
_ui->logFileNameLabel->setText("Error");
_enablePlaybackControls(false);
}
QString QGCMAVLinkLogPlayer::_secondsToHMS(int seconds)
{
int secondsPart = seconds;
int minutesPart = secondsPart / 60;
int hoursPart = minutesPart / 60;
secondsPart -= 60 * minutesPart;
minutesPart -= 60 * hoursPart;
return QString("%1h:%2m:%3s").arg(hoursPart, 2).arg(minutesPart, 2).arg(secondsPart, 2);
}
/// Signalled from LogReplayLink once log file information is known
void QGCMAVLinkLogPlayer::_logFileStats(bool logTimestamped, ///< true: timestamped log
int logDurationSeconds, ///< Log duration
int binaryBaudRate) ///< Baud rate for non-timestamped log
{
Q_UNUSED(logTimestamped);
Q_UNUSED(binaryBaudRate);
qDebug() << "_logFileStats" << logDurationSeconds;
_logDurationSeconds = logDurationSeconds;
_ui->logLengthTime->setText(_secondsToHMS(logDurationSeconds));
}
/// Signalled from LogReplayLink when replay starts
void QGCMAVLinkLogPlayer::_playbackStarted(void)
{
_enablePlaybackControls(true);
_ui->playButton->setChecked(true);
_ui->playButton->setIcon(QIcon(":/res/Pause"));
}
/// Signalled from LogReplayLink when replay is paused
void QGCMAVLinkLogPlayer::_playbackPaused(void)
{
_ui->playButton->setIcon(QIcon(":/res/Play"));
_ui->playButton->setChecked(false);
}
void QGCMAVLinkLogPlayer::_playbackPercentCompleteChanged(int percentComplete)
{
_ui->positionSlider->blockSignals(true);
_ui->positionSlider->setValue(percentComplete);
_ui->positionSlider->blockSignals(false);
}
void QGCMAVLinkLogPlayer::_setPlayheadFromSlider(int value)
{
if (_replayLink) {
_replayLink->movePlayhead(value);
}
}
void QGCMAVLinkLogPlayer::_enablePlaybackControls(bool enabled)
{
_ui->playButton->setEnabled(enabled);
#if 0
_ui->speedSlider->setEnabled(enabled);
#endif
_ui->positionSlider->setEnabled(enabled);
}
#if 0
void QGCMAVLinkLogPlayer::_setAccelerationFromSlider(int value)
{
//qDebug() << value;
if (_replayLink) {
_replayLink->setAccelerationFactor(value);
}
// Factor: -100: 0.01x, 0: 1.0x, 100: 100x
float accelerationFactor;
if (value < 0) {
accelerationFactor = 0.01f;
value -= -100;
if (value > 0) {
accelerationFactor *= (float)value;
}
} else if (value > 0) {
accelerationFactor = 1.0f * (float)value;
} else {
accelerationFactor = 1.0f;
}
_ui->speedLabel->setText(QString("Speed: %1X").arg(accelerationFactor, 5, 'f', 2, '0'));
}
#endif
void QGCMAVLinkLogPlayer::_replayLinkDisconnected(void)
{
_enablePlaybackControls(false);
_replayLink = NULL;
}
void QGCMAVLinkLogPlayer::_setCurrentLogTime(int secs)
{
if (secs != _lastCurrentTime) {
_lastCurrentTime = secs;
_ui->logCurrentTime->setText(_secondsToHMS(secs));
}
}
#pragma once
#include <QWidget>
#include <QFile>
#include "MAVLinkProtocol.h"
#include "LinkInterface.h"
#include "LogReplayLink.h"
namespace Ui
{
class QGCMAVLinkLogPlayer;
}
/**
* @brief Replays MAVLink log files
*
* This class allows to replay MAVLink logs at varying speeds.
* captured flights can be replayed, shown to others and analyzed
* in-depth later on.
*/
class QGCMAVLinkLogPlayer : public QWidget
{
Q_OBJECT
public:
explicit QGCMAVLinkLogPlayer(QWidget *parent = 0);
~QGCMAVLinkLogPlayer();
private slots:
void _selectLogFileForPlayback(void);
void _playPauseToggle(void);
void _pause(void);
void _setPlayheadFromSlider(int value);
#if 0
void _setAccelerationFromSlider(int value);
#endif
void _logFileStats(bool logTimestamped, int logDurationSeconds, int binaryBaudRate);
void _playbackStarted(void);
void _playbackPaused(void);
void _playbackPercentCompleteChanged(int percentComplete);
void _playbackError(void);
void _replayLinkDisconnected(void);
void _setCurrentLogTime(int secs);
private:
void _finishPlayback(void);
QString _secondsToHMS(int seconds);
void _enablePlaybackControls(bool enabled);
LogReplayLink* _replayLink;
int _logDurationSeconds;
int _lastCurrentTime;
Ui::QGCMAVLinkLogPlayer* _ui;
};
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QGCMAVLinkLogPlayer</class>
<widget class="QWidget" name="QGCMAVLinkLogPlayer">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>948</width>
<height>38</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>12</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>4</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="logCurrentTime">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="playButton">
<property name="toolTip">
<string>Start to replay Flight Data</string>
</property>
<property name="statusTip">
<string>Start to replay Flight Data</string>
</property>
<property name="whatsThis">
<string>Start to replay Flight Data</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../qgcresources.qrc">
<normaloff>:/res/Play</normaloff>:/res/Play</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="timeLabel">
<property name="text">
<string>Time</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="positionSlider">
<property name="maximum">
<number>10000</number>
</property>
<property name="pageStep">
<number>100</number>
</property>
<property name="tracking">
<bool>true</bool>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="logFileNameLabel">
<property name="text">
<string>No Flight Data selected..</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="logLengthTime">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="selectFileButton">
<property name="toolTip">
<string>Select the Flight Data to replay</string>
</property>
<property name="statusTip">
<string>Select the Flight Data to replay</string>
</property>
<property name="whatsThis">
<string>Select the Flight Data to replay</string>
</property>
<property name="text">
<string>Replay Flight Data</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="../../qgcresources.qrc"/>
</resources>
<connections/>
</ui>
/****************************************************************************
*
* (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.
*
****************************************************************************/
#include "QGCUASFileView.h"
#include "FileManager.h"
#include "QGCQFileDialog.h"
#include "UAS.h"
#include <QFileDialog>
#include <QDir>
#include <QDebug>
QGCUASFileView::QGCUASFileView(QWidget *parent, Vehicle* vehicle)
: QWidget(parent)
, _manager(vehicle->uas()->getFileManager())
, _currentCommand(commandNone)
{
_ui.setupUi(this);
if (vehicle->px4Firmware()) {
_ui.progressBar->reset();
// Connect UI signals
connect(_ui.listFilesButton, &QPushButton::clicked, this, &QGCUASFileView::_refreshTree);
connect(_ui.downloadButton, &QPushButton::clicked, this, &QGCUASFileView::_downloadFile);
connect(_ui.uploadButton, &QPushButton::clicked, this, &QGCUASFileView::_uploadFile);
connect(_ui.treeWidget, &QTreeWidget::currentItemChanged, this, &QGCUASFileView::_currentItemChanged);
// Connect signals from FileManager
connect(_manager, &FileManager::commandProgress, this, &QGCUASFileView::_commandProgress);
connect(_manager, &FileManager::commandComplete, this, &QGCUASFileView::_commandComplete);
connect(_manager, &FileManager::commandError, this, &QGCUASFileView::_commandError);
connect(_manager, &FileManager::listEntry, this, &QGCUASFileView::_listEntryReceived);
} else {
_setAllButtonsEnabled(false);
_ui.statusText->setText(QStringLiteral("Onboard Files not supported by this Vehicle"));
}
}
/// @brief Downloads the file currently selected in the tree view
void QGCUASFileView::_downloadFile(void)
{
if (_currentCommand != commandNone) {
qWarning() << QString("Download attempted while another command was in progress: _currentCommand(%1)").arg(_currentCommand);
return;
}
_ui.statusText->clear();
QString downloadToHere = QGCQFileDialog::getExistingDirectory(this,
tr("Download Directory"),
QDir::homePath(),
QGCQFileDialog::ShowDirsOnly | QGCQFileDialog::DontResolveSymlinks);
// And now download to this location
QString path;
QString downloadFilename;
QTreeWidgetItem* item = _ui.treeWidget->currentItem();
if (item && item->type() == _typeFile) {
do {
QString name = item->text(0).split("\t")[0]; // Strip off file sizes
// If this is the file name and not a directory keep track of the download file name
if (downloadFilename.isEmpty()) {
downloadFilename = name;
}
path.prepend("/" + name);
item = item->parent();
} while (item);
_setAllButtonsEnabled(false);
_currentCommand = commandDownload;
_ui.statusText->setText(tr("Downloading: %1").arg(downloadFilename));
_manager->streamPath(path, QDir(downloadToHere));
}
}
/// @brief uploads a file into the currently selected directory the tree view
void QGCUASFileView::_uploadFile(void)
{
if (_currentCommand != commandNone) {
qWarning() << QString("Upload attempted while another command was in progress: _currentCommand(%1)").arg(_currentCommand);
return;
}
_ui.statusText->clear();
// get and check directory from list view
QTreeWidgetItem* item = _ui.treeWidget->currentItem();
if (item && item->type() != _typeDir) {
return;
}
// Find complete path for upload directory
QString path;
do {
QString name = item->text(0).split("\t")[0]; // Strip off file sizes
path.prepend("/" + name);
item = item->parent();
} while (item);
QString uploadFromHere = QGCQFileDialog::getOpenFileName(this, tr("Upload File"), QDir::homePath());
_ui.statusText->setText(tr("Uploading: %1").arg(uploadFromHere));
qDebug() << "Upload: " << uploadFromHere << "to path" << path;
_setAllButtonsEnabled(false);
_currentCommand = commandUpload;
_manager->uploadPath(path, uploadFromHere);
}
/// @brief Called to update the progress of the download.
/// @param value Progress bar value
void QGCUASFileView::_commandProgress(int value)
{
_ui.progressBar->setValue(value);
}
/// @brief Called when an error occurs during a download.
/// @param msg Error message
void QGCUASFileView::_commandError(const QString& msg)
{
_setAllButtonsEnabled(true);
_currentCommand = commandNone;
_ui.statusText->setText(tr("Error: %1").arg(msg));
}
/// @brief Refreshes the directory list tree.
void QGCUASFileView::_refreshTree(void)
{
if (_currentCommand != commandNone) {
qWarning() << QString("List attempted while another command was in progress: _currentCommand(%1)").arg(_currentCommand);
return;
}
_ui.treeWidget->clear();
_ui.statusText->clear();
_walkIndexStack.clear();
_walkItemStack.clear();
_walkIndexStack.append(0);
_walkItemStack.append(_ui.treeWidget->invisibleRootItem());
_setAllButtonsEnabled(false);
_currentCommand = commandList;
_requestDirectoryList("/");
}
/// @brief Adds the specified directory entry to the tree view.
void QGCUASFileView::_listEntryReceived(const QString& entry)
{
if (_currentCommand != commandList) {
qWarning() << QString("List entry received while no list command in progress: _currentCommand(%1)").arg(_currentCommand);
return;
}
int type;
if (entry.startsWith("F")) {
type = _typeFile;
} else if (entry.startsWith("D")) {
type = _typeDir;
if (entry == "D." || entry == "D..") {
return;
}
} else {
Q_ASSERT(false);
return; // Silence maybe-unitialized on type
}
QTreeWidgetItem* item;
item = new QTreeWidgetItem(_walkItemStack.last(), type);
Q_CHECK_PTR(item);
item->setText(0, entry.right(entry.size() - 1));
}
/// @brief Called when a command completes successfully
void QGCUASFileView::_commandComplete(void)
{
QString statusText;
if (_currentCommand == commandDownload) {
_currentCommand = commandNone;
_setAllButtonsEnabled(true);
statusText = "Download complete";
} else if (_currentCommand == commandUpload) {
_currentCommand = commandNone;
_setAllButtonsEnabled(true);
statusText = "Upload complete";
} else if (_currentCommand == commandList) {
_listComplete();
}
_ui.statusText->setText(statusText);
_ui.progressBar->reset();
}
void QGCUASFileView::_listComplete(void)
{
// Walk the current items, traversing down into directories
Again:
int walkIndex = _walkIndexStack.last();
QTreeWidgetItem* parentItem = _walkItemStack.last();
QTreeWidgetItem* childItem = parentItem->child(walkIndex);
// Loop until we hit a directory
while (childItem && childItem->type() != _typeDir) {
// Move to next index at current level
_walkIndexStack.last() = ++walkIndex;
childItem = parentItem->child(walkIndex);
}
if (childItem) {
// Move to the next item for processing at this level
_walkIndexStack.last() = ++walkIndex;
// Push this new directory on the stack
_walkItemStack.append(childItem);
_walkIndexStack.append(0);
// Ask for the directory list
QString dir;
for (int i=1; i<_walkItemStack.count(); i++) {
QTreeWidgetItem* item = _walkItemStack[i];
dir.append("/" + item->text(0));
}
_requestDirectoryList(dir);
} else {
// We have run out of items at the this level, pop the stack and keep going at that level
_walkIndexStack.removeLast();
_walkItemStack.removeLast();
if (_walkIndexStack.count() != 0) {
goto Again;
} else {
_setAllButtonsEnabled(true);
_currentCommand = commandNone;
}
}
}
void QGCUASFileView::_currentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous)
{
Q_UNUSED(previous);
_ui.downloadButton->setEnabled(current ? (current->type() == _typeFile) : false);
_ui.uploadButton->setEnabled(current ? (current->type() == _typeDir) : false);
}
void QGCUASFileView::_requestDirectoryList(const QString& dir)
{
_manager->listDirectory(dir);
}
void QGCUASFileView::_setAllButtonsEnabled(bool enabled)
{
_ui.treeWidget->setEnabled(enabled);
_ui.downloadButton->setEnabled(enabled);
_ui.listFilesButton->setEnabled(enabled);
_ui.uploadButton->setEnabled(enabled);
if (enabled) {
_currentItemChanged(_ui.treeWidget->currentItem(), NULL);
}
}
/****************************************************************************
*
* (c) 2009-2018 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.
*
****************************************************************************/
#pragma once
#include <QWidget>
#include <QTreeWidgetItem>
#include "Vehicle.h"
#include "uas/FileManager.h"
#include "ui_QGCUASFileView.h"
class QGCUASFileView : public QWidget
{
Q_OBJECT
public:
explicit QGCUASFileView(QWidget *parent, Vehicle* vehicle);
protected:
FileManager* _manager;
private slots:
void _listEntryReceived(const QString& entry);
void _refreshTree(void);
void _downloadFile(void);
void _uploadFile(void);
void _commandProgress(int value);
void _commandError(const QString& msg);
void _commandComplete(void);
void _currentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous);
private:
void _listComplete(void);
void _requestDirectoryList(const QString& dir);
void _setAllButtonsEnabled(bool enabled);
static const int _typeFile = QTreeWidgetItem::UserType + 1;
static const int _typeDir = QTreeWidgetItem::UserType + 2;
static const int _typeError = QTreeWidgetItem::UserType + 3;
QList<int> _walkIndexStack;
QList<QTreeWidgetItem*> _walkItemStack;
Ui::QGCUASFileView _ui;
enum CommandState {
commandNone, ///< No command active
commandList, ///< List command active
commandDownload, ///< Download command active
commandUpload ///< Upload command active
};
CommandState _currentCommand; ///< Current active command
};
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QGCUASFileView</class>
<widget class="QWidget" name="QGCUASFileView">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>216</width>
<height>518</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="5" column="1">
<widget class="QPushButton" name="listFilesButton">
<property name="text">
<string>List Files</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="3">
<widget class="QProgressBar" name="progressBar">
<property name="value">
<number>24</number>
</property>
<property name="textVisible">
<bool>false</bool>
</property>
</widget>
</item>
<item row="0" column="0" colspan="3">
<widget class="QTreeWidget" name="treeWidget">
<property name="contextMenuPolicy">
<enum>Qt::NoContextMenu</enum>
</property>
<property name="headerHidden">
<bool>true</bool>
</property>
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
</widget>
</item>
<item row="5" column="2">
<widget class="QPushButton" name="downloadButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Download File</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="3">
<widget class="QLabel" name="statusText">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="QPushButton" name="uploadButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Upload File</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
#include "QGCUASFileViewMulti.h"
#include "ui_QGCUASFileViewMulti.h"
#include "UASInterface.h"
#include "MultiVehicleManager.h"
#include "QGCUASFileView.h"
#include "QGCApplication.h"
QGCUASFileViewMulti::QGCUASFileViewMulti(const QString& title, QAction* action, QWidget *parent) :
QGCDockWidget(title, action, parent),
ui(new Ui::QGCUASFileViewMulti)
{
ui->setupUi(this);
setMinimumSize(600, 80);
connect(qgcApp()->toolbox()->multiVehicleManager(), &MultiVehicleManager::activeVehicleChanged, this, &QGCUASFileViewMulti::_activeVehicleChanged);
connect(qgcApp()->toolbox()->multiVehicleManager(), &MultiVehicleManager::vehicleAdded, this, &QGCUASFileViewMulti::_vehicleAdded);
connect(qgcApp()->toolbox()->multiVehicleManager(), &MultiVehicleManager::vehicleRemoved, this, &QGCUASFileViewMulti::_vehicleRemoved);
if (qgcApp()->toolbox()->multiVehicleManager()->activeVehicle()) {
_vehicleAdded(qgcApp()->toolbox()->multiVehicleManager()->activeVehicle());
_activeVehicleChanged(qgcApp()->toolbox()->multiVehicleManager()->activeVehicle());
}
loadSettings();
}
void QGCUASFileViewMulti::_vehicleRemoved(Vehicle* vehicle)
{
UAS* uas = vehicle->uas();
Q_ASSERT(uas);
QGCUASFileView* list = lists.value(uas, NULL);
if (list)
{
delete list;
lists.remove(uas);
}
}
void QGCUASFileViewMulti::_vehicleAdded(Vehicle* vehicle)
{
UAS* uas = vehicle->uas();
if (!lists.contains(uas)) {
QGCUASFileView* list = new QGCUASFileView(ui->stackedWidget, vehicle);
lists.insert(uas, list);
ui->stackedWidget->addWidget(list);
}
}
void QGCUASFileViewMulti::_activeVehicleChanged(Vehicle* vehicle)
{
if (vehicle) {
QGCUASFileView* list = lists.value(vehicle->uas(), NULL);
if (list) {
ui->stackedWidget->setCurrentWidget(list);
}
}
}
QGCUASFileViewMulti::~QGCUASFileViewMulti()
{
delete ui;
}
void QGCUASFileViewMulti::changeEvent(QEvent *e)
{
QWidget::changeEvent(e);
switch (e->type()) {
case QEvent::LanguageChange:
ui->retranslateUi(this);
break;
default:
break;
}
}
#pragma once
#include <QMap>
#include "QGCDockWidget.h"
#include "QGCUASFileView.h"
#include "UAS.h"
namespace Ui
{
class QGCUASFileViewMulti;
}
class QGCUASFileViewMulti : public QGCDockWidget
{
Q_OBJECT
public:
explicit QGCUASFileViewMulti(const QString& title, QAction* action, QWidget *parent = 0);
~QGCUASFileViewMulti();
protected:
void changeEvent(QEvent *e);
QMap<UAS*, QGCUASFileView*> lists;
private slots:
void _vehicleAdded(Vehicle* vehicle);
void _vehicleRemoved(Vehicle* vehicle);
void _activeVehicleChanged(Vehicle* vehicle);
private:
Ui::QGCUASFileViewMulti *ui;
};
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QGCUASFileViewMulti</class>
<widget class="QWidget" name="QGCUASFileViewMulti">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>200</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Onboard Files</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QStackedWidget" name="stackedWidget"/>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
#include "ChartPlot.h"
#include "QGCApplication.h"
#include "SettingsManager.h"
const QColor ChartPlot::baseColors[numColors] = {
QColor(242, 255, 128),
QColor(70, 80, 242),
QColor(232, 33, 47),
QColor(116, 251, 110),
QColor(81, 183, 244),
QColor(234, 38, 107),
QColor(92, 247, 217),
QColor(151, 59, 239),
QColor(231, 72, 28),
QColor(236, 48, 221),
QColor(75, 133, 243),
QColor(203, 254, 121),
QColor(104, 64, 240),
QColor(200, 54, 238),
QColor(104, 250, 138),
QColor(235, 43, 165),
QColor(98, 248, 176),
QColor(161, 252, 116),
QColor(87, 231, 246),
QColor(230, 126, 23)
};
ChartPlot::ChartPlot(QWidget* parent):
QwtPlot(parent),
_nextColorIndex(0),
_symbolWidth(2.0f),
_curveWidth(2.0f),
_gridWidth(0.8f)
{
// Initialize the list of curves.
_curves = QMap<QString, QwtPlotCurve*>();
// Set the grid. The colorscheme was already set in generateColorScheme().
_grid = new QwtPlotGrid;
_grid->enableXMin(true);
_grid->attach(this);
_colors = QList<QColor>();
///> Color map for plots, includes 20 colors
///> Map will start from beginning when the first 20 colors are exceeded
for(int i = 0; i < numColors; ++i) {
_colors.append(baseColors[i]);
}
// Now that all objects have been initialized, color everything.
styleChanged(qgcApp()->toolbox()->settingsManager()->appSettings()->indoorPalette()->rawValue().toBool());
}
ChartPlot::~ChartPlot()
{
}
QColor ChartPlot::getNextColor()
{
if(_nextColorIndex >= _colors.count()) {
_nextColorIndex = 0;
}
return _colors[_nextColorIndex++];
}
QColor ChartPlot::getColorForCurve(const QString& id)
{
return _curves.value(id)->pen().color();
}
void ChartPlot::shuffleColors()
{
foreach(QwtPlotCurve* curve, _curves) {
if(curve->isVisible()) {
QPen pen(curve->pen());
pen.setColor(getNextColor());
curve->setPen(pen);
}
}
}
void ChartPlot::styleChanged(bool styleIsDark)
{
// Generate a new color list for curves and recolor them.
for(int i = 0; i < numColors; ++i) {
_colors[i] = styleIsDark ? baseColors[i].lighter(150) : baseColors[i].darker(150);
}
shuffleColors();
// Configure the rest of the UI colors based on the current theme.
if(styleIsDark) {
// Set canvas background
setCanvasBackground(QColor(0, 0, 0));
// Configure the plot grid.
_grid->setMinorPen(QPen(QColor(64, 64, 64), _gridWidth, Qt::SolidLine));
_grid->setMajorPen(QPen(QColor(96, 96, 96), _gridWidth, Qt::SolidLine));
} else {
// Set canvas background
setCanvasBackground(QColor(0xFF, 0xFF, 0xFF));
// Configure the plot grid.
_grid->setMinorPen(QPen(QColor(192, 192, 192), _gridWidth, Qt::SolidLine));
_grid->setMajorPen(QPen(QColor(128, 128, 128), _gridWidth, Qt::SolidLine));
}
// And finally refresh the widget to make sure all color changes are redrawn.
replot();
}
#pragma once
#include <qwt_plot.h>
#include <qwt_plot_grid.h>
#include <qwt_plot_curve.h>
#include "MainWindow.h"
#include "ScrollZoomer.h"
class ChartPlot : public QwtPlot
{
Q_OBJECT
public:
ChartPlot(QWidget *parent = NULL);
virtual ~ChartPlot();
/** @brief Get next color of color map */
QColor getNextColor();
/** @brief Get color for curve id */
QColor getColorForCurve(const QString &id);
/** @brief Reset color map */
void shuffleColors();
public slots:
/** @brief Generate coloring for this plot canvas based on current window theme */
void styleChanged(bool styleIsDark);
protected:
const static int numColors = 20;
const static QColor baseColors[numColors];
QList<QColor> _colors; ///< Colormap for curves
int _nextColorIndex; ///< Next index in color map
QMap<QString, QwtPlotCurve* > _curves; ///< Plot curves
QwtPlotGrid* _grid; ///< Plot grid
float _symbolWidth; ///< Width of curve symbols in pixels
float _curveWidth; ///< Width of curve lines in pixels
float _gridWidth; ///< Width of gridlines in pixels
};
/****************************************************************************
*
* (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 Implementation of class IncrementalPlot
* @author Lorenz Meier <mavteam@student.ethz.ch>
*
*/
#include <qwt_plot.h>
#include <qwt_plot_canvas.h>
#include <qwt_plot_curve.h>
#include <qwt_symbol.h>
#include <qwt_plot_layout.h>
#include <qwt_plot_grid.h>
#include <qwt_scale_engine.h>
#include "IncrementalPlot.h"
#include <Scrollbar.h>
#include <ScrollZoomer.h>
#include <float.h>
#include <qpaintengine.h>
#include <QDebug>
CurveData::CurveData():
d_count(0)
{
}
void CurveData::append(double *x, double *y, int count)
{
int newSize = ( (d_count + count) / 1000 + 1 ) * 1000;
if ( newSize > size() ) {
d_x.resize(newSize);
d_y.resize(newSize);
}
for ( int i = 0; i < count; i++ ) {
d_x[d_count + i] = x[i];
d_y[d_count + i] = y[i];
}
d_count += count;
}
int CurveData::count() const
{
return d_count;
}
int CurveData::size() const
{
return d_x.size();
}
const double* CurveData::x() const
{
return d_x.data();
}
const double* CurveData::y() const
{
return d_y.data();
}
IncrementalPlot::IncrementalPlot(QWidget *parent):
ChartPlot(parent),
symmetric(false)
{
setStyleText("solid crosses");
plotLayout()->setAlignCanvasToScales(true);
QwtLinearScaleEngine* yScaleEngine = new QwtLinearScaleEngine();
setAxisScaleEngine(QwtPlot::yLeft, yScaleEngine);
setAxisAutoScale(xBottom);
setAxisAutoScale(yLeft);
resetScaling();
legend = NULL;
}
IncrementalPlot::~IncrementalPlot()
{
}
/**
* @param symmetric true will enforce that both axes have the same interval,
* centered around the data plot. A circle will thus remain a circle if true,
* if set to false it might become an ellipse because of axis scaling.
*/
void IncrementalPlot::setSymmetric(bool symmetric)
{
this->symmetric = symmetric;
updateScale(); // Updates the scaling at replots
}
void IncrementalPlot::handleLegendClick(QwtPlotItem* item, bool on)
{
item->setVisible(!on);
replot();
}
void IncrementalPlot::showLegend(bool show)
{
if (show) {
if (legend == NULL) {
legend = new QwtLegend;
legend->setFrameStyle(QFrame::Box);
legend->setDefaultItemMode(QwtLegendData::Checkable);
}
insertLegend(legend, QwtPlot::RightLegend);
} else {
delete legend;
legend = NULL;
}
updateScale(); // Updates the scaling at replots
}
/**
* Set datapoint and line style. This interface is intended
* to be directly connected to the UI and allows to parse
* human-readable, textual descriptions into plot specs.
*
* Data points: Either "circles", "crosses" or the default "dots"
* Lines: Either "dotted", ("solid"/"line") or no lines if not used
*
* No special formatting is needed, as long as the keywords are contained
* in the string. Lower/uppercase is ignored as well.
*
* @param style Formatting string for line/data point style
*/
void IncrementalPlot::setStyleText(const QString &style)
{
styleText = style.toLower();
foreach (QwtPlotCurve* curve, _curves) {
updateStyle(curve);
}
replot();
}
void IncrementalPlot::updateStyle(QwtPlotCurve *curve)
{
if(styleText.isNull())
return;
// Since the symbols always use the same color as the curve line, we just use that color.
// This saves us from having to deal with cases where the symbol is NULL.
QColor oldColor = curve->pen().color();
// Update the symbol style
QwtSymbol *newSymbol = NULL;
if (styleText.contains("circles")) {
newSymbol = new QwtSymbol(QwtSymbol::Ellipse, Qt::NoBrush, QPen(oldColor, _symbolWidth), QSize(6, 6));
} else if (styleText.contains("crosses")) {
newSymbol = new QwtSymbol(QwtSymbol::XCross, Qt::NoBrush, QPen(oldColor, _symbolWidth), QSize(5, 5));
} else if (styleText.contains("rect")) {
newSymbol = new QwtSymbol(QwtSymbol::Rect, Qt::NoBrush, QPen(oldColor, _symbolWidth), QSize(6, 6));
}
// Else-case already handled by NULL value, which indicates no symbol
curve->setSymbol(newSymbol);
// Update the line style
if (styleText.contains("dotted")) {
curve->setPen(QPen(oldColor, _curveWidth, Qt::DotLine));
} else if (styleText.contains("dashed")) {
curve->setPen(QPen(oldColor, _curveWidth, Qt::DashLine));
} else if (styleText.contains("line") || styleText.contains("solid")) {
curve->setPen(QPen(oldColor, _curveWidth, Qt::SolidLine));
} else {
curve->setPen(QPen(oldColor, _curveWidth, Qt::NoPen));
}
curve->setStyle(QwtPlotCurve::Lines);
}
void IncrementalPlot::resetScaling()
{
xmin = 0;
xmax = 500;
ymin = xmin;
ymax = xmax;
setAxisScale(xBottom, xmin+xmin*0.05, xmax+xmax*0.05);
setAxisScale(yLeft, ymin+ymin*0.05, ymax+ymax*0.05);
replot();
// Make sure the first data access hits these
xmin = DBL_MAX;
xmax = DBL_MIN;
ymin = DBL_MAX;
ymax = DBL_MIN;
}
/**
* Updates the scale calculation and re-plots the whole plot
*/
void IncrementalPlot::updateScale()
{
const double margin = 0.05;
if(xmin == DBL_MAX)
return;
double xMinRange = xmin-(qAbs(xmin*margin));
double xMaxRange = xmax+(qAbs(xmax*margin));
double yMinRange = ymin-(qAbs(ymin*margin));
double yMaxRange = ymax+(qAbs(ymax*margin));
if (symmetric) {
double xRange = xMaxRange - xMinRange;
double yRange = yMaxRange - yMinRange;
// Get the aspect ratio of the plot
float xSize = width();
if (legend != NULL) xSize -= legend->width();
float ySize = height();
float aspectRatio = xSize / ySize;
if (xRange > yRange) {
double yCenter = yMinRange + yRange/2.0;
double xCenter = xMinRange + xRange/2.0;
yMinRange = yCenter - xRange/2.0;
yMaxRange = yCenter + xRange/2.0;
xMinRange = xCenter - (xRange*aspectRatio)/2.0;
xMaxRange = xCenter + (xRange*aspectRatio)/2.0;
} else {
double xCenter = xMinRange + xRange/2.0;
xMinRange = xCenter - (yRange*aspectRatio)/2.0;
xMaxRange = xCenter + (yRange*aspectRatio)/2.0;
}
}
setAxisScale(xBottom, xMinRange, xMaxRange);
setAxisScale(yLeft, yMinRange, yMaxRange);
}
void IncrementalPlot::appendData(const QString &key, double x, double y)
{
appendData(key, &x, &y, 1);
}
void IncrementalPlot::appendData(const QString &key, double *x, double *y, int size)
{
CurveData* data;
QwtPlotCurve* curve;
if (!d_data.contains(key)) {
data = new CurveData;
d_data.insert(key, data);
} else {
data = d_data.value(key);
}
// If this is a new curve, create it.
if (!_curves.contains(key)) {
curve = new QwtPlotCurve(key);
_curves.insert(key, curve);
curve->setStyle(QwtPlotCurve::NoCurve);
curve->setPaintAttribute(QwtPlotCurve::FilterPoints);
// Set the color. Only the pen needs to be set
const QColor &c = getNextColor();
curve->setPen(c, _symbolWidth);
qDebug() << "Creating curve" << key << "with color" << c;
updateStyle(curve);
curve->attach(this);
} else {
curve = _curves.value(key);
}
data->append(x, y, size);
curve->setRawSamples(data->x(), data->y(), data->count());
bool scaleChanged = false;
// Update scales
for (int i = 0; i<size; i++) {
if (x[i] < xmin) {
xmin = x[i];
scaleChanged = true;
}
if (x[i] > xmax) {
xmax = x[i];
scaleChanged = true;
}
if (y[i] < ymin) {
ymin = y[i];
scaleChanged = true;
}
if (y[i] > ymax) {
ymax = y[i];
scaleChanged = true;
}
}
// setAxisScale(xBottom, xmin+xmin*0.05, xmax+xmax*0.05);
// setAxisScale(yLeft, ymin+ymin*0.05, ymax+ymax*0.05);
//#ifdef __GNUC__
//#warning better use QwtData
//#endif
//replot();
if(scaleChanged) {
updateScale();
} else {
QwtPlotCanvas *c = static_cast<QwtPlotCanvas*>(canvas());
const bool cacheMode = c->testPaintAttribute(QwtPlotCanvas::BackingStore);
c->setPaintAttribute(QwtPlotCanvas::BackingStore, false);
// FIXME Check if here all curves should be drawn
// QwtPlotCurve* plotCurve;
// foreach(plotCurve, curves)
// {
// plotCurve->draw(0, curve->dataSize()-1);
// }
// FIXME: Unsure what this call should be now.
//curve->draw(curve->dataSize() - size, curve->dataSize() - 1);
replot();
c->setPaintAttribute(QwtPlotCanvas::BackingStore, cacheMode);
}
}
/**
* @return Number of copied data points, 0 on failure
*/
int IncrementalPlot::data(const QString &key, double* r_x, double* r_y, int maxSize)
{
int result = 0;
if (d_data.contains(key)) {
CurveData* d = d_data.value(key);
if (maxSize >= d->count()) {
result = d->count();
memcpy(r_x, d->x(), sizeof(double) * d->count());
memcpy(r_y, d->y(), sizeof(double) * d->count());
} else {
result = 0;
}
}
return result;
}
/**
* @param show true to show the grid, false else
*/
void IncrementalPlot::showGrid(bool show)
{
_grid->setVisible(show);
replot();
}
bool IncrementalPlot::gridEnabled() const
{
return _grid->isVisible();
}
void IncrementalPlot::removeData()
{
foreach (QwtPlotCurve* curve, _curves) {
delete curve;
}
_curves.clear();
foreach (CurveData* data, d_data) {
delete data;
}
d_data.clear();
resetScaling();
replot();
}
/****************************************************************************
*
* (c) 2009-2018 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 Defition of class IncrementalPlot
* @author Lorenz Meier <mavteam@student.ethz.ch>
*
*/
#pragma once
#include <QTimer>
#include <qwt_plot.h>
#include <qwt_legend.h>
#include <QMap>
#include "ChartPlot.h"
class QwtPlotCurve;
/**
* @brief Plot data container for growing data
*/
class CurveData
{
public:
CurveData();
void append(double *x, double *y, int count);
/** @brief The number of datasets held in the data structure */
int count() const;
/** @brief The reserved size of the data structure in units */
int size() const;
const double *x() const;
const double *y() const;
private:
int d_count;
QVector<double> d_x;
QVector<double> d_y;
};
/**
* @brief Incremental plotting widget
*
* This widget plots data incrementally when new data arrives.
* It will only repaint the minimum screen content necessary to avoid
* a too high CPU consumption. It auto-scales the plot to new data.
*/
class IncrementalPlot : public ChartPlot
{
Q_OBJECT
public:
/** @brief Create a new, empty incremental plot */
IncrementalPlot(QWidget *parent = NULL);
virtual ~IncrementalPlot();
/** @brief Get the state of the grid */
bool gridEnabled() const;
/** @brief Read out data from a curve */
int data(const QString &key, double* r_x, double* r_y, int maxSize);
public slots:
/** @brief Append one data point */
void appendData(const QString &key, double x, double y);
/** @brief Append multiple data points */
void appendData(const QString &key, double* x, double* y, int size);
/** @brief Reset the plot scaling to the default value */
void resetScaling();
/** @brief Update the plot scale based on current data/symmetric mode */
void updateScale();
/** @brief Remove all data from the plot and repaint */
void removeData();
/** @brief Show the plot legend */
void showLegend(bool show);
/** @brief Show the plot grid */
void showGrid(bool show);
/** @brief Set new plot style */
void setStyleText(const QString &style);
/** @brief Set symmetric axis scaling mode */
void setSymmetric(bool symmetric);
protected slots:
/** @brief Handle the click on a legend item */
void handleLegendClick(QwtPlotItem* item, bool on);
protected:
bool symmetric; ///< Enable symmetric plotting
QwtLegend* legend; ///< Plot legend
double xmin; ///< Minimum x value seen
double xmax; ///< Maximum x value seen
double ymin; ///< Minimum y value seen
double ymax; ///< Maximum y value seen
QString styleText; ///< Curve style set by setStyleText
private:
QMap<QString, CurveData* > d_data; ///< Data points
/** Helper function to apply styleText style to the given curve */
void updateStyle(QwtPlotCurve *curve);
};
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>linechart</class>
<widget class="QWidget" name="linechart">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1337</width>
<height>585</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>3</horstretch>
<verstretch>2</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>300</width>
<height>200</height>
</size>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item>
<widget class="QSplitter" name="splitter">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="midLineWidth">
<number>1</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="handleWidth">
<number>10</number>
</property>
<widget class="QWidget" name="curveGroupBox" native="true">
<layout class="QGridLayout" name="gridLayout" rowstretch="50,0,0,0">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item row="0" column="0">
<widget class="QScrollArea" name="curveListWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>60</width>
<height>150</height>
</size>
</property>
<property name="baseSize">
<size>
<width>60</width>
<height>150</height>
</size>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>879</width>
<height>462</height>
</rect>
</property>
</widget>
</widget>
</item>
<item row="3" column="0">
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>4</number>
</property>
<item>
<widget class="QLineEdit" name="plotFilterLineEdit">
<property name="placeholderText">
<string>Filter... (Ctrl+F)</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="uasSelectionBox">
<item>
<property name="text">
<string>All MAVs</string>
</property>
</item>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0">
<property name="spacing">
<number>0</number>
</property>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="spacing">
<number>6</number>
</property>
<item>
<widget class="QCheckBox" name="shortNameCheckBox">
<property name="toolTip">
<string>Display only variable names in curve list</string>
</property>
<property name="text">
<string>Short names</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="showUnitsCheckBox">
<property name="toolTip">
<string>Display variable units in curve list</string>
</property>
<property name="whatsThis">
<string>Display variable units in curve list</string>
</property>
<property name="text">
<string>Show units</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="spacing">
<number>6</number>
</property>
<property name="sizeConstraint">
<enum>QLayout::SetMinimumSize</enum>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="recolorButton">
<property name="toolTip">
<string>Rotate color scheme for all curves</string>
</property>
<property name="text">
<string>Recolor</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Minimum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="diagramGroupBox" native="true"/>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
/*=====================================================================
======================================================================*/
/**
* @file
* @brief Line chart for vehicle data
*
* @author Lorenz Meier <mavteam@student.ethz.ch>
*
*/
#include "float.h"
#include <QDebug>
#include <QTimer>
#include <qwt_plot.h>
#include <qwt_plot_canvas.h>
#include <qwt_plot_curve.h>
#include <qwt_plot_grid.h>
#include <qwt_plot_layout.h>
#include <qwt_plot_zoomer.h>
#include <qwt_symbol.h>
#include <LinechartPlot.h>
#include <MG.h>
#include <QPaintEngine>
#include "ChartPlot.h"
#include "QGC.h"
/**
* @brief The default constructor
*
* @param parent The parent widget
* @param interval The maximum interval for which data is stored (default: 30 minutes) in milliseconds
**/
LinechartPlot::LinechartPlot(QWidget *parent, int plotid, quint64 interval):
ChartPlot(parent),
minTime(0),
lastTime(0),
maxTime(100),
maxInterval(MAX_STORAGE_INTERVAL),
plotPosition(0),
timeScaleStep(DEFAULT_SCALE_INTERVAL), // 10 seconds
automaticScrollActive(false),
m_active(false),
m_groundTime(true),
d_data(NULL),
d_curve(NULL)
{
this->plotid = plotid;
this->plotInterval = interval;
maxValue = -DBL_MAX;
minValue = DBL_MAX;
//lastMaxTimeAdded = QTime();
data = QMap<QString, TimeSeriesData*>();
scaleMaps = QMap<QString, QwtScaleMap*>();
yScaleEngine = new QwtLinearScaleEngine();
setAxisScaleEngine(QwtPlot::yLeft, yScaleEngine);
// Set left scale
//setAxisOptions(QwtPlot::yLeft, QwtAutoScale::Logarithmic);
// Set bottom scale
setAxisScaleDraw(QwtPlot::xBottom, new TimeScaleDraw());
setAxisLabelRotation(QwtPlot::xBottom, -25.0);
setAxisLabelAlignment(QwtPlot::xBottom, Qt::AlignLeft | Qt::AlignBottom);
// Add some space on the left and right side of the scale to prevent flickering
QwtScaleWidget* bottomScaleWidget = axisWidget(QwtPlot::xBottom);
const int fontMetricsX = QFontMetrics(bottomScaleWidget->font()).height();
bottomScaleWidget->setMinBorderDist(fontMetricsX * 2, fontMetricsX / 2);
plotLayout()->setAlignCanvasToScales(true);
// Start QTimer for plot update
updateTimer = new QTimer(this);
connect(updateTimer, &QTimer::timeout, this, &LinechartPlot::paintRealtime);
//updateTimer->start(DEFAULT_REFRESH_RATE);
connect(&timeoutTimer, &QTimer::timeout, this, &LinechartPlot::removeTimedOutCurves);
//timeoutTimer.start(5000);
}
LinechartPlot::~LinechartPlot()
{
// datalock.lock();
// // Delete curves
// QMap<QString, QwtPlotCurve*>::iterator i;
// for(i = curves.begin(); i != curves.end(); ++i) {
// // Remove from curve list
// QwtPlotCurve* curve = curves.take(i.key());
// // Delete the object
// delete curve;
// // Set the pointer null
// curve = NULL;
// }
// // Delete data
// QMap<QString, TimeSeriesData*>::iterator j;
// for(j = data.begin(); j != data.end(); ++j) {
// // Remove from data list
// TimeSeriesData* d = data.take(j.key());
// // Delete the object
// delete d;
// // Set the pointer null
// d = NULL;
// }
// datalock.unlock();
}
void LinechartPlot::showEvent(QShowEvent* event)
{
Q_UNUSED(event);
updateTimer->start(DEFAULT_REFRESH_RATE);
}
void LinechartPlot::hideEvent(QHideEvent* event)
{
Q_UNUSED(event);
updateTimer->stop();
}
int LinechartPlot::getPlotId()
{
return this->plotid;
}
/**
* @param id curve identifier
*/
double LinechartPlot::getCurrentValue(QString id)
{
return data.value(id)->getCurrentValue();
}
/**
* @param id curve identifier
*/
double LinechartPlot::getMean(QString id)
{
return data.value(id)->getMean();
}
/**
* @param id curve identifier
*/
double LinechartPlot::getMedian(QString id)
{
return data.value(id)->getMedian();
}
/**
* @param id curve identifier
*/
double LinechartPlot::getVariance(QString id)
{
return data.value(id)->getVariance();
}
int LinechartPlot::getAverageWindow()
{
return averageWindowSize;
}
/**
* @brief Set the plot refresh rate
* The default refresh rate is defined by LinechartPlot::DEFAULT_REFRESH_RATE.
* @param ms The refresh rate in milliseconds
**/
void LinechartPlot::setRefreshRate(int ms)
{
updateTimer->setInterval(ms);
}
void LinechartPlot::setActive(bool active)
{
m_active = active;
}
void LinechartPlot::removeTimedOutCurves()
{
foreach(const QString &key, lastUpdate.keys())
{
quint64 time = lastUpdate.value(key);
if (QGC::groundTimeMilliseconds() - time > 10000)
{
// Remove this curve
// Delete curves
QwtPlotCurve* curve = _curves.take(key);
// Delete the object
delete curve;
// Set the pointer null
curve = NULL;
// Notify connected components about the removal
emit curveRemoved(key);
// Remove from data list
TimeSeriesData* d = data.take(key);
// Delete the object
delete d;
// Set the pointer null
d = NULL;
emit curveRemoved(key);
}
}
}
/**
* @brief Set the zero (center line) value
* The zero value defines the centerline of the plot.
*
* @param id The id of the curve
* @param zeroValue The zero value
**/
void LinechartPlot::setZeroValue(QString id, double zeroValue)
{
if(data.contains(id)) {
data.value(id)->setZeroValue(zeroValue);
} else {
data.insert(id, new TimeSeriesData(this, id, maxInterval, zeroValue));
}
}
void LinechartPlot::appendData(QString dataname, quint64 ms, double value)
{
/* Lock resource to ensure data integrity */
datalock.lock();
/* Check if dataset identifier already exists */
if(!data.contains(dataname)) {
addCurve(dataname);
enforceGroundTime(m_groundTime);
// qDebug() << "ADDING CURVE WITH" << dataname << ms << value;
// qDebug() << "MINTIME:" << minTime << "MAXTIME:" << maxTime;
// qDebug() << "LASTTIME:" << lastTime;
}
// Add new value
TimeSeriesData* dataset = data.value(dataname);
quint64 time;
// Append data
if (!m_groundTime)
{
// Use timestamp from dataset
time = ms;
}
else
{
time = QGC::groundTimeMilliseconds();
}
dataset->append(time, value);
lastUpdate.insert(dataname, time);
// Scaling values
if(ms < minTime) minTime = ms;
if(ms > maxTime) maxTime = ms;
storageInterval = maxTime - minTime;
if(time > lastTime)
{
//qDebug() << "UPDATED LAST TIME!" << dataname << time << lastTime;
lastTime = time;
}
//
if (value < minValue) minValue = value;
if (value > maxValue) maxValue = value;
valueInterval = maxValue - minValue;
// Assign dataset to curve
QwtPlotCurve* curve = _curves.value(dataname);
curve->setRawSamples(dataset->getPlotX(), dataset->getPlotY(), dataset->getPlotCount());
// qDebug() << "mintime" << minTime << "maxtime" << maxTime << "last max time" << "window position" << getWindowPosition();
datalock.unlock();
}
/**
* @param enforce true to reset the data timestamp with the receive / ground timestamp
*/
void LinechartPlot::enforceGroundTime(bool enforce)
{
m_groundTime = enforce;
if (enforce)
{
lastTime = QGC::groundTimeMilliseconds();
plotPosition = lastTime;
maxTime = lastTime;
}
else
{
lastTime = 0;
plotPosition = 0;
minTime = 0;
maxTime = 100;
}
}
/**
* @return True if the data points are stamped with the packet receive time
*/
bool LinechartPlot::groundTime()
{
return m_groundTime;
}
void LinechartPlot::addCurve(QString id)
{
QColor currentColor = getNextColor();
// Create new curve and set style
QwtPlotCurve* curve = new QwtPlotCurve(id);
// Add curve to list
_curves.insert(id, curve);
curve->setStyle(QwtPlotCurve::Lines);
curve->setPaintAttribute(QwtPlotCurve::FilterPoints, true);
setCurveColor(id, currentColor);
//curve->setBrush(currentColor); Leads to a filled curve
// curve->setRenderHint(QwtPlotItem::RenderAntialiased);
curve->attach(this);
//@TODO Color differentiation between the curves will be necessary
/* Create symbol for datapoints on curve */
/*
* Symbols have significant performance penalty, better avoid them
*
QwtSymbol sym = QwtSymbol();
sym.setStyle(QwtSymbol::Ellipse);
sym.setPen(currentColor);
sym.setSize(3);
curve->setSymbol(sym);*/
// Create dataset
TimeSeriesData* dataset = new TimeSeriesData(this, id, this->plotInterval, maxInterval);
// Add dataset to list
data.insert(id, dataset);
// Notify connected components about new curve
emit curveAdded(id);
}
/**
* @brief Set the time window for the plot
* The time window defines which data is shown in the plot.
*
* @param end The end of the interval in milliseconds
* */
void LinechartPlot::setWindowPosition(quint64 end)
{
windowLock.lock();
if(end <= this->getMaxTime() && end >= (this->getMinTime() + this->getPlotInterval())) {
plotPosition = end;
setAxisScale(QwtPlot::xBottom, (plotPosition - getPlotInterval()), plotPosition, timeScaleStep);
}
//@TODO Update the rest of the plot and update drawing
windowLock.unlock();
}
/**
* @brief Get the time window position
* The position marks the right edge of the plot window
*
* @return The position of the plot window, in milliseconds
**/
quint64 LinechartPlot::getWindowPosition()
{
return plotPosition;
}
/**
* @brief Set the scaling of the (vertical) y axis
* The mapping of the variable values on the drawing pane can be
* adjusted with this method. The default is that the y axis will the chosen
* to fit all curves in their normal base units. This can however hide all
* details if large differences in the data values exist.
*
* The scaling can be changed to best fit, which fits all curves in a +100 to -100 interval.
* The logarithmic scaling does not fit the variables, but instead applies a log10
* scaling to all variables.
*
* @param scaling LinechartPlot::SCALE_ABSOLUTE for linear scaling, LinechartPlot::SCALE_BEST_FIT for the best fit scaling and LinechartPlot::SCALE_LOGARITHMIC for the logarithmic scaling.
**/
void LinechartPlot::setScaling(int scaling)
{
this->scaling = scaling;
switch (scaling) {
case LinechartPlot::SCALE_ABSOLUTE:
setLinearScaling();
break;
case LinechartPlot::SCALE_LOGARITHMIC:
setLogarithmicScaling();
break;
}
}
/**
* @brief Change the visibility of a curve
*
* @param id The string id of the curve
* @param visible The visibility: True to make it visible
**/
void LinechartPlot::setVisibleById(QString id, bool visible)
{
if(_curves.contains(id)) {
_curves.value(id)->setVisible(visible);
if(visible)
{
_curves.value(id)->attach(this);
}
else
{
_curves.value(id)->detach();
}
}
}
/**
* @brief Hide a curve.
*
* This is a convenience method and maps to setVisible(id, false).
*
* @param id The curve to hide
* @see setVisible() For the implementation
**/
void LinechartPlot::hideCurve(QString id)
{
setVisibleById(id, false);
}
/**
* @brief Show a curve.
*
* This is a convenience method and maps to setVisible(id, true);
*
* @param id The curve to show
* @see setVisible() For the implementation
**/
void LinechartPlot::showCurve(QString id)
{
setVisibleById(id, true);
}
//void LinechartPlot::showCurve(QString id, int position)
//{
// //@TODO Implement this position-dependent
// curves.value(id)->show();
//}
/**
* @brief Set the color of a curve and its symbols.
*
* @param id The id-string of the curve
* @param color The newly assigned color
**/
void LinechartPlot::setCurveColor(QString id, QColor color)
{
QwtPlotCurve* curve = _curves.value(id);
// Change the color of the curve.
curve->setPen(QPen(QBrush(color), _curveWidth));
//qDebug() << "Setting curve" << id << "to" << color;
// And change the color of the symbol, making sure to preserve the symbol style
const QwtSymbol *oldSymbol = curve->symbol();
QwtSymbol *newSymbol = NULL;
if (oldSymbol) {
newSymbol = new QwtSymbol(oldSymbol->style(), QBrush(color), QPen(color, _symbolWidth), QSize(_symbolWidth, _symbolWidth));
}
curve->setSymbol(newSymbol);
}
/**
* @brief Check the visibility of a curve
*
* @param id The id of the curve
* @return The visibility, true if it is visible, false otherwise
**/
bool LinechartPlot::isVisible(QString id)
{
return _curves.value(id)->isVisible();
}
/**
* @return The visibility, true if it is visible, false otherwise
**/
bool LinechartPlot::anyCurveVisible()
{
bool visible = false;
foreach (const QString &key, _curves.keys())
{
if (_curves.value(key)->isVisible())
{
visible = true;
}
}
return visible;
}
/**
* @brief Allows to block interference of the automatic scrolling with user interaction
* When the plot is updated very fast (at 1 ms for example) with new data, it might
* get impossible for an user to interact. Therefore the automatic scrolling must be
* explicitly activated.
*
* @param active The status of automatic scrolling, true to turn it on
**/
void LinechartPlot::setAutoScroll(bool active)
{
automaticScrollActive = active;
}
/**
* @brief Get a list of all curves (visible and not visible curves)
*
* @return The list of curves
**/
QList<QwtPlotCurve*> LinechartPlot::getCurves()
{
return _curves.values();
}
/**
* @brief Get the smallest time value in all datasets
*
* @return The smallest time value
**/
quint64 LinechartPlot::getMinTime()
{
return minTime;
}
/**
* @brief Get the biggest time value in all datasets
*
* @return The biggest time value
**/
quint64 LinechartPlot::getMaxTime()
{
return maxTime;
}
/**
* @brief Get the plot interval
* The plot interval is the time interval which is displayed on the plot
*
* @return The plot interval in milliseconds
* @see setPlotInterval()
* @see getDataInterval() To get the interval for which data is available
**/
quint64 LinechartPlot::getPlotInterval()
{
return plotInterval;
}
/**
* @brief Set the plot interval
*
* @param interval The time interval to plot, in milliseconds
* @see getPlotInterval()
**/
void LinechartPlot::setPlotInterval(int interval)
{
//Only ever increase the amount of stored data,
// so that we allow the user to change between
// different intervals without constantly losing
// data points
if((unsigned)interval > plotInterval) {
QMap<QString, TimeSeriesData*>::iterator j;
for(j = data.begin(); j != data.end(); ++j)
{
TimeSeriesData* d = data.value(j.key());
d->setInterval(interval);
}
}
plotInterval = interval;
if(plotInterval > 5*60*1000) //If the interval is longer than 4 minutes, change the time scale step to 2 minutes
timeScaleStep = 2*60*1000;
else if(plotInterval >= 4*60*1000) //If the interval is longer than 4 minutes, change the time scale step to 1 minutes
timeScaleStep = 1*60*1000;
else if(plotInterval >= 60*1000) //If the interval is longer than a minute, change the time scale step to 30 seconds
timeScaleStep = 30*1000;
else
timeScaleStep = DEFAULT_SCALE_INTERVAL;
}
/**
* @brief Get the data interval
* The data interval is defined by the time interval for which data
* values are available.
*
* @return The data interval
* @see getPlotInterval() To get the time interval which is currently displayed by the plot
**/
quint64 LinechartPlot::getDataInterval()
{
return storageInterval;
}
/**
* @brief Set logarithmic scaling for the curve
**/
void LinechartPlot::setLogarithmicScaling()
{
yScaleEngine = new QwtLogScaleEngine();
setAxisScaleEngine(QwtPlot::yLeft, yScaleEngine);
}
/**
* @brief Set linear scaling for the curve
**/
void LinechartPlot::setLinearScaling()
{
yScaleEngine = new QwtLinearScaleEngine();
setAxisScaleEngine(QwtPlot::yLeft, yScaleEngine);
}
void LinechartPlot::setAverageWindow(int windowSize)
{
this->averageWindowSize = windowSize;
foreach(TimeSeriesData* series, data)
{
series->setAverageWindowSize(windowSize);
}
}
/**
* @brief Paint immediately the plot
* This method is a replacement for replot(). In contrast to replot(), it takes the
* time window size and eventual zoom interaction into account.
**/
void LinechartPlot::paintRealtime()
{
if (m_active) {
#if (QGC_EVENTLOOP_DEBUG)
static quint64 timestamp = 0;
qDebug() << "EVENTLOOP: (" << MG::TIME::getGroundTimeNow() - timestamp << ")" << __FILE__ << __LINE__;
timestamp = MG::TIME::getGroundTimeNow();
#endif
// Update plot window value to new max time if the last time was also the max time
windowLock.lock();
if (automaticScrollActive)
{
// FIXME Check, but commenting this out should have been
// beneficial (does only add complexity)
// if (MG::TIME::getGroundTimeNow() > maxTime && abs(MG::TIME::getGroundTimeNow() - maxTime) < 5000000)
// {
// plotPosition = MG::TIME::getGroundTimeNow();
// }
// else
// {
plotPosition = lastTime;// + lastMaxTimeAdded.msec();
// }
setAxisScale(QwtPlot::xBottom, plotPosition - plotInterval, plotPosition, timeScaleStep);
// FIXME Last fix for scroll zoomer is here
//setAxisScale(QwtPlot::yLeft, minValue + minValue * 0.05, maxValue + maxValue * 0.05f, (maxValue - minValue) / 10.0);
/* Notify about change. Even if the window position was not changed
* itself, the relative position of the window to the interval must
* have changed, as the interval likely increased in length */
emit windowPositionChanged(getWindowPosition());
}
windowLock.unlock();
replot();
/*
QMap<QString, QwtPlotCurve*>::iterator i;
for(i = curves.begin(); i != curves.end(); ++i) {
const bool cacheMode = canvas()->testPaintAttribute(QwtPlotCanvas::PaintCached);
canvas()->setPaintAttribute(QwtPlotCanvas::PaintCached, false);
i.value()->drawItems();
canvas()->setPaintAttribute(QwtPlotCanvas::PaintCached, cacheMode);
}*/
}
}
/**
* @brief Removes all data and curves from the plot
**/
void LinechartPlot::removeAllData()
{
datalock.lock();
// Delete curves
QMap<QString, QwtPlotCurve*>::iterator i;
for(i = _curves.begin(); i != _curves.end(); ++i)
{
// Remove from curve list
QwtPlotCurve* curve = _curves.take(i.key());
// Delete the object
delete curve;
// Set the pointer null
curve = NULL;
// Notify connected components about the removal
emit curveRemoved(i.key());
}
// Delete data
QMap<QString, TimeSeriesData*>::iterator j;
for(j = data.begin(); j != data.end(); ++j)
{
// Remove from data list
TimeSeriesData* d = data.take(j.key());
// Delete the object
delete d;
// Set the pointer null
d = NULL;
}
datalock.unlock();
replot();
}
TimeSeriesData::TimeSeriesData(QwtPlot* plot, QString friendlyName, quint64 plotInterval, quint64 maxInterval, double zeroValue):
minValue(DBL_MAX),
maxValue(DBL_MIN),
zeroValue(0),
count(0),
mean(0.0),
median(0.0),
variance(0.0),
averageWindow(50)
{
this->plot = plot;
this->friendlyName = friendlyName;
this->maxInterval = maxInterval;
this->zeroValue = zeroValue;
this->plotInterval = plotInterval;
/* initialize time */
startTime = QUINT64_MAX;
stopTime = QUINT64_MIN;
plotCount = 0;
}
TimeSeriesData::~TimeSeriesData()
{
}
void TimeSeriesData::setInterval(quint64 ms)
{
plotInterval = ms;
}
void TimeSeriesData::setAverageWindowSize(int windowSize)
{
this->averageWindow = windowSize;
}
/**
* @brief Append a data point to this data set
*
* @param ms The time in milliseconds
* @param value The data value
**/
void TimeSeriesData::append(quint64 ms, double value)
{
dataMutex.lock();
// Qt will automatically use a smart growth strategy: http://doc.qt.io/qt-5/containers.html#growth-strategies
this->ms.append(ms);
this->value.append(value);
this->lastValue = value;
this->mean = 0;
//QList<double> medianList = QList<double>();
for (unsigned int i = 0; (i < averageWindow) && (((int)count - (int)i) >= 0); ++i) {
this->mean += this->value[count-i];
//medianList.append(this->value[count-i]);
}
this->mean = mean / static_cast<double>(qMin(averageWindow,static_cast<unsigned int>(count)));
this->variance = 0;
for (unsigned int i = 0; (i < averageWindow) && (((int)count - (int)i) >= 0); ++i) {
this->variance += (this->value[count-i] - mean) * (this->value[count-i] - mean);
}
this->variance = this->variance / static_cast<double>(qMin(averageWindow,static_cast<unsigned int>(count)));
// qSort(medianList);
// if (medianList.count() > 2)
// {
// if (medianList.count() % 2 == 0)
// {
// median = (medianList.at(medianList.count()/2) + medianList.at(medianList.count()/2+1)) / 2.0;
// }
// else
// {
// median = medianList.at(medianList.count()/2+1);
// }
// }
// Update statistical values
if(ms < startTime) startTime = ms;
if(ms > stopTime) stopTime = ms;
interval = stopTime - startTime;
if (interval > plotInterval) {
while (this->ms[count - plotCount] < stopTime - plotInterval) {
plotCount--;
}
}
count++;
plotCount++;
if(minValue > value) minValue = value;
if(maxValue < value) maxValue = value;
// Trim dataset if necessary
if(maxInterval > 0) {
// maxInterval = 0 means infinite
if(interval > maxInterval && !this->ms.isEmpty() && !this->value.isEmpty()) {
// The time at which this time series should be cut
double minTime = stopTime - maxInterval;
// Delete elements from the start of the list as long the time
// value of this elements is before the cut time
while(this->ms.first() < minTime) {
this->ms.pop_front();
this->value.pop_front();
}
}
}
dataMutex.unlock();
}
/**
* @brief Get the id of this data set
*
* @return The id-string
**/
int TimeSeriesData::getID()
{
return id;
}
/**
* @brief Get the minimum value in the data set
*
* @return The minimum value
**/
double TimeSeriesData::getMinValue()
{
return minValue;
}
/**
* @brief Get the maximum value in the data set
*
* @return The maximum value
**/
double TimeSeriesData::getMaxValue()
{
return maxValue;
}
/**
* @return the mean
*/
double TimeSeriesData::getMean()
{
return mean;
}
/**
* @return the median
*/
double TimeSeriesData::getMedian()
{
return median;
}
/**
* @return the variance
*/
double TimeSeriesData::getVariance()
{
return variance;
}
double TimeSeriesData::getCurrentValue()
{
return lastValue;
}
/**
* @brief Get the zero (center) value in the data set
* The zero value is not a statistical value, but instead manually defined
* when creating the data set.
* @return The zero value
**/
double TimeSeriesData::getZeroValue()
{
return zeroValue;
}
/**
* @brief Set the zero (center) value
*
* @param zeroValue The zero value
* @see getZeroValue()
**/
void TimeSeriesData::setZeroValue(double zeroValue)
{
this->zeroValue = zeroValue;
}
/**
* @brief Get the number of points in the dataset
*
* @return The number of points
**/
int TimeSeriesData::getCount() const
{
return count;
}
/**
* @brief Get the number of points in the plot selection
*
* @return The number of points
**/
int TimeSeriesData::getPlotCount() const
{
return plotCount;
}
/**
* @brief Get the data array size
* The data array size is \e NOT equal to the number of items in the data set, as
* array space is pre-allocated. Use getCount() to get the number of data points.
*
* @return The data array size
* @see getCount()
**/
int TimeSeriesData::size() const
{
return ms.size();
}
/**
* @brief Get the X (time) values
*
* @return The x values
**/
const double* TimeSeriesData::getX() const
{
return ms.data();
}
const double* TimeSeriesData::getPlotX() const
{
return ms.data() + (count - plotCount);
}
/**
* @brief Get the Y (data) values
*
* @return The y values
**/
const double* TimeSeriesData::getY() const
{
return value.data();
}
const double* TimeSeriesData::getPlotY() const
{
return value.data() + (count - plotCount);
}
/****************************************************************************
*
* (c) 2009-2018 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 Plot of a Linechart
*
* @author Lorenz Meier <mavteam@student.ethz.ch>
*
*/
#pragma once
#define QUINT64_MIN Q_UINT64_C(0)
#define QUINT64_MAX Q_UINT64_C(18446744073709551615)
#include <QMap>
#include <QList>
#include <QMutex>
#include <QTime>
#include <QTimer>
#include <qwt_plot_panner.h>
#include <qwt_plot_curve.h>
#include <qwt_scale_draw.h>
#include <qwt_scale_widget.h>
#include <qwt_scale_engine.h>
#include <qwt_plot.h>
#include "ChartPlot.h"
#include "MG.h"
class TimeScaleDraw: public QwtScaleDraw
{
public:
virtual QwtText label(double v) const {
QDateTime time = MG::TIME::msecToQDateTime(static_cast<quint64>(v));
return time.toString("hh:mm:ss"); // was hh:mm:ss:zzz
// Show seconds since system startup
//return QString::number(static_cast<int>(v)/1000000);
}
};
/**
* @brief Data container
*/
class QwtPlotCurve;
/**
* @brief Container class for the time series data
*
**/
class TimeSeriesData
{
public:
TimeSeriesData(QwtPlot* plot, QString friendlyName = "data", quint64 plotInterval = 10000, quint64 maxInterval = 0, double zeroValue = 0);
~TimeSeriesData();
void append(quint64 ms, double value);
QwtScaleMap* getScaleMap();
int getCount() const;
int size() const;
const double* getX() const;
const double* getY() const;
const double* getPlotX() const;
const double* getPlotY() const;
int getPlotCount() const;
int getID();
QString getFriendlyName();
double getMinValue();
double getMaxValue();
double getZeroValue();
/** @brief Get the short-term mean */
double getMean();
/** @brief Get the short-term median */
double getMedian();
/** @brief Get the short-term variance */
double getVariance();
/** @brief Get the current value */
double getCurrentValue();
void setZeroValue(double zeroValue);
void setInterval(quint64 ms);
void setAverageWindowSize(int windowSize);
protected:
QwtPlot* plot;
quint64 startTime;
quint64 stopTime;
quint64 interval;
quint64 plotInterval;
quint64 maxInterval;
int id;
quint64 plotCount;
QString friendlyName;
double lastValue; ///< The last inserted value
double minValue; ///< The smallest value in the dataset
double maxValue; ///< The largest value in the dataset
double zeroValue; ///< The expected value in the dataset
QMutex dataMutex;
QwtScaleMap* scaleMap;
void updateScaleMap();
private:
quint64 count;
QVector<double> ms;
QVector<double> value;
double mean;
double median;
double variance;
unsigned int averageWindow;
QVector<double> outputMs;
QVector<double> outputValue;
};
/**
* @brief Time series plot
**/
class LinechartPlot : public ChartPlot
{
Q_OBJECT
public:
LinechartPlot(QWidget *parent = NULL, int plotid=0, quint64 interval = LinechartPlot::DEFAULT_PLOT_INTERVAL);
virtual ~LinechartPlot();
void setZeroValue(QString id, double zeroValue);
void removeAllData();
QList<QwtPlotCurve*> getCurves();
bool isVisible(QString id);
/** @brief Check if any curve is visible */
bool anyCurveVisible();
int getPlotId();
/** @brief Get the number of values to average over */
int getAverageWindow();
quint64 getMinTime();
quint64 getMaxTime();
quint64 getPlotInterval();
quint64 getDataInterval();
quint64 getWindowPosition();
/** @brief Get the short-term mean of a curve */
double getMean(QString id);
/** @brief Get the short-term median of a curve */
double getMedian(QString id);
/** @brief Get the short-term variance of a curve */
double getVariance(QString id);
/** @brief Get the last inserted value */
double getCurrentValue(QString id);
static const int SCALE_ABSOLUTE = 0;
static const int SCALE_BEST_FIT = 1;
static const int SCALE_LOGARITHMIC = 2;
static const int DEFAULT_REFRESH_RATE = 100; ///< The default refresh rate is 10 Hz / every 100 ms
static const int DEFAULT_PLOT_INTERVAL = 1000 * 8; ///< The default plot interval is 15 seconds
static const int DEFAULT_SCALE_INTERVAL = 1000 * 8;
public slots:
void setRefreshRate(int ms);
/**
* @brief Append data to the plot
*
* The new data point is appended to the curve with the id-String id. If the curve
* doesn't yet exist it is created and added to the plot.
*
* @param uasId id of originating UAS
* @param dataname unique string (also used to label the data)
* @param ms time measure of the data point, in milliseconds
* @param value value of the data point
*/
void appendData(QString dataname, quint64 ms, double value);
void hideCurve(QString id);
void showCurve(QString id);
/** @brief Enable auto-refreshing of plot */
void setActive(bool active);
// Functions referring to the currently active plot
void setVisibleById(QString id, bool visible);
/**
* @brief Set the color of a curve and its symbols.
*
* @param id The id-string of the curve
* @param color The newly assigned color
**/
void setCurveColor(QString id, QColor color);
/** @brief Enforce the use of the receive timestamp */
void enforceGroundTime(bool enforce);
/** @brief Check if the receive timestamp is enforced */
bool groundTime();
// General interaction
void setWindowPosition(quint64 end);
void setPlotInterval(int interval);
void setScaling(int scaling);
void setAutoScroll(bool active);
void paintRealtime();
/** @brief Set logarithmic plot y-axis scaling */
void setLogarithmicScaling();
/** @brief Set linear plot y-axis scaling */
void setLinearScaling();
/** @brief Set the number of values to average over */
void setAverageWindow(int windowSize);
void removeTimedOutCurves();
protected:
QMap<QString, TimeSeriesData*> data;
QMap<QString, QwtScaleMap*> scaleMaps;
QMap<QString, quint64> lastUpdate;
//static const quint64 MAX_STORAGE_INTERVAL = Q_UINT64_C(300000);
static const quint64 MAX_STORAGE_INTERVAL = Q_UINT64_C(0); ///< The maximum interval which is stored
// TODO CHECK THIS!!!
int scaling;
QwtScaleEngine* yScaleEngine;
quint64 minTime; ///< The smallest timestamp occurred so far
quint64 lastTime; ///< Last added timestamp
quint64 maxTime; ///< The biggest timestamp occurred so far
quint64 maxInterval;
quint64 storageInterval;
double maxValue;
double minValue;
double valueInterval;
int averageWindowSize; ///< Size of sliding average / sliding median
quint64 plotInterval;
quint64 plotPosition;
QTimer* updateTimer;
QMutex datalock;
QMutex windowLock;
quint64 timeScaleStep;
bool automaticScrollActive;
QTime lastMaxTimeAdded;
int plotid;
bool m_active; ///< Decides wether the plot is active or not
bool m_groundTime; ///< Enforce the use of the receive timestamp instead of the data timestamp
QTimer timeoutTimer;
// Methods
void addCurve(QString id);
void showEvent(QShowEvent* event);
void hideEvent(QHideEvent* event);
private:
TimeSeriesData* d_data;
QwtPlotCurve* d_curve;
signals:
/**
* @brief This signal is emitted when a new curve is added
*
* @param color The curve color in the diagram
**/
void curveAdded(QString idstring);
/**
* @brief This signal is emitted when a curve is removed
*
* @param name The id-string of the curve
**/
void curveRemoved(QString name);
/**
* @brief This signal is emitted when the plot window position changes
*
* @param position The position of the right edge of the window, in milliseconds
**/
void windowPositionChanged(quint64 position);
};
/****************************************************************************
*
* (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::_restartFilterTimeout);
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);
_filterTimer.setInterval(500);
connect(&_filterTimer, &QTimer::timeout, this, &LinechartWidget::_filterTimeout);
}
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(tr("Time axis:"));
hlayout->addWidget(timeScaleLabel);
timeScaleCmb = new QComboBox(this);
timeScaleCmb->addItem(tr("10 seconds"), 10);
timeScaleCmb->addItem(tr("20 seconds"), 20);
timeScaleCmb->addItem(tr("30 seconds"), 30);
timeScaleCmb->addItem(tr("40 seconds"), 40);
timeScaleCmb->addItem(tr("50 seconds"), 50);
timeScaleCmb->addItem(tr("1 minute"), 60);
timeScaleCmb->addItem(tr("2 minutes"), 60*2);
timeScaleCmb->addItem(tr("3 minutes"), 60*3);
timeScaleCmb->addItem(tr("4 minutes"), 60*4);
timeScaleCmb->addItem(tr("5 minutes"), 60*5);
timeScaleCmb->addItem(tr("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::_restartFilterTimeout(void)
{
_filterTimer.start();
}
void LinechartWidget::_filterTimeout(void)
{
filterCurves(ui.plotFilterLineEdit->text());
}
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;
}
/****************************************************************************
*
* (c) 2009-2018 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 Definition of Line chart plot widget
*
* @author Lorenz Meier <mavteam@student.ethz.ch>
* @author Thomas Gubler <thomasgubler@student.ethz.ch>
*/
#pragma once
#include <QGridLayout>
#include <QWidget>
#include <QFrame>
#include <QComboBox>
#include <QVBoxLayout>
#include <QCheckBox>
#include <QScrollBar>
#include <QSpinBox>
#include <QMap>
#include <QString>
#include <QAction>
#include <QIcon>
#include <QLabel>
#include <QReadWriteLock>
#include <QToolButton>
#include <QTimer>
#include <qwt_plot_curve.h>
#include "LinechartPlot.h"
#include "UASInterface.h"
#include "ui_Linechart.h"
#include "LogCompressor.h"
/**
* @brief The linechart widget allows to visualize different timeseries as lineplot.
* The display interval, the timeseries and the scaling can be changed interactively
**/
class LinechartWidget : public QWidget
{
Q_OBJECT
public:
LinechartWidget(int systemid, QWidget *parent = 0);
~LinechartWidget();
static const int MIN_TIME_SCROLLBAR_VALUE = 0; ///< The minimum scrollbar value
static const int MAX_TIME_SCROLLBAR_VALUE = 16383; ///< The maximum scrollbar value
public slots:
void addCurve(const QString& curve, const QString& unit);
void removeCurve(QString curve);
/** @brief Recolor all curves */
void recolor();
/** @brief Set short names for curves */
void setShortNames(bool enable);
/** @brief Append data to the given curve. */
void appendData(int uasId, const QString& curve, const QString& unit, const QVariant& value, quint64 usec);
/** @brief Hide curves which do not match the filter pattern */
void filterCurves(const QString &filter);
void toggleLogarithmicScaling(bool toggled);
void takeButtonClick(bool checked);
void setPlotWindowPosition(int scrollBarValue);
void setPlotWindowPosition(quint64 position);
void setPlotInterval(quint64 interval);
/** @brief Start automatic updates once visible */
void showEvent(QShowEvent* event);
/** @brief Stop automatic updates once hidden */
void hideEvent(QHideEvent* event);
void setActive(bool active);
/** @brief Select one MAV for curve display */
void selectActiveSystem(int mav);
/** @brief Set the number of values to average over */
void setAverageWindow(int windowSize);
/** @brief Start logging to file */
void startLogging();
/** @brief Stop logging to file */
void stopLogging();
/** @brief Refresh the view */
void refresh();
/** @brief Write the current configuration to disk */
void writeSettings();
/** @brief Read the current configuration from disk */
void readSettings();
/** @brief Select all curves */
void selectAllCurves(bool all);
/** @brief Sets the focus to the LineEdit for plot-filtering */
void setPlotFilterLineEditFocus();
private slots:
/** Called when the user changes the time scale combobox. */
void timeScaleChanged(int index);
/** @brief Toggles visibility of curve based on bool match if corresponding checkbox is not checked */
void filterCurve(const QString &key, bool match);
protected:
void addCurveToList(QString curve);
void removeCurveFromList(QString curve);
QToolButton* createButton(QWidget* parent);
void createCurveItem(QString curve);
void createLayout();
/** @brief Get the name for a curve key */
QString getCurveName(const QString& key, bool shortEnabled);
int sysid; ///< ID of the unmanned system this plot belongs to
LinechartPlot* activePlot; ///< Plot for this system
QReadWriteLock* curvesLock; ///< A lock (mutex) for the concurrent access on the curves
QReadWriteLock plotWindowLock; ///< A lock (mutex) for the concurrent access on the window position
int curveListIndex;
int curveListCounter; ///< Counter of curves in curve list
QMap<QString, QLabel*>* curveLabels; ///< References to the curve labels
QMap<QString, QLabel*> curveNameLabels; ///< References to the curve labels
QMap<QString, QString> curveNames; ///< Full curve names
QMap<QString, QLabel*>* curveMeans; ///< References to the curve means
QMap<QString, QLabel*>* curveMedians; ///< References to the curve medians
QMap<QString, QWidget*> curveUnits; ///< References to the curve units
QMap<QString, QLabel*>* curveVariances; ///< References to the curve variances
QMap<QString, int> intData; ///< Current values for integer-valued curves
QMap<QString, QWidget*> colorIcons; ///< Reference to color icons
QMap<QString, QCheckBox*> checkBoxes; ///< Reference to checkboxes
QWidget* curvesWidget; ///< The QWidget containing the curve selection button
QGridLayout* curvesWidgetLayout; ///< The layout for the curvesWidget QWidget
QScrollBar* scrollbar; ///< The plot window scroll bar
QSpinBox* averageSpinBox; ///< Spin box to setup average window filter size
QAction* addNewCurve; ///< Add curve candidate to the active curves
QComboBox *timeScaleCmb;
QToolButton* scalingLogButton;
QToolButton* logButton;
QPointer<QCheckBox> timeButton;
QFile* logFile;
unsigned int logindex;
bool logging;
quint64 logStartTime;
QTimer* updateTimer;
LogCompressor* compressor;
QCheckBox* selectAllCheckBox;
int selectedMAV; ///< The MAV for which plot items are accepted, -1 for all systems
quint64 lastTimestamp;
bool userGroundTimeSet;
bool autoGroundTimeSet;
static const int updateInterval = 1000; ///< Time between number updates, in milliseconds
static const int MAX_CURVE_MENUITEM_NUMBER = 8;
static const int PAGESTEP_TIME_SCROLLBAR_VALUE = (MAX_TIME_SCROLLBAR_VALUE - MIN_TIME_SCROLLBAR_VALUE) / 10;
private:
Ui::linechart ui;
void createActions();
signals:
/**
* @brief This signal is emitted if a curve is removed from the list
*
* @param curve The removed plot curve
**/
void curveRemoved(QString curve);
/**
* @brief This signal is emitted if a curve has been moved or added
*
* @param curve The moved or added curve
* @param position The x-position of the curve (The centerline)
**/
void curveSet(QString curve, int position);
/**
* @brief This signal is emitted to change the visibility of a curve
*
* @param curve The changed curve
* @pram visible The visibility
**/
void curveVisible(QString curve, bool visible);
void plotWindowPositionUpdated(quint64 position);
void plotWindowPositionUpdated(int position);
/** @brief This signal is emitted once a logfile has been finished writing */
void logfileWritten(QString fileName);
private slots:
void _filterTimeout(void);
void _restartFilterTimeout(void);
private:
QTimer _filterTimer;
};
#include <QShowEvent>
#include "Linecharts.h"
#include "MultiVehicleManager.h"
#include "MainWindow.h"
#include "UAS.h"
Linecharts::Linecharts(const QString& title, QAction* action, MAVLinkDecoder* decoder, QWidget *parent)
: MultiVehicleDockWidget(title, action, parent)
, _mavlinkDecoder(decoder)
{
init();
this->setVisible(false);
}
QWidget* Linecharts::_newVehicleWidget(Vehicle* vehicle, QWidget* parent)
{
LinechartWidget* widget = new LinechartWidget(vehicle->id(), parent);
// Connect valueChanged signals
connect(vehicle->uas(), &UAS::valueChanged, widget, &LinechartWidget::appendData);
// Connect decoder
connect(_mavlinkDecoder, &MAVLinkDecoder::valueChanged, widget, &LinechartWidget::appendData);
// Select system
widget->setActive(true);
return widget;
}
#pragma once
#include <QStackedWidget>
#include <QMap>
#include <QVector>
#include "LinechartWidget.h"
#include "Vehicle.h"
#include "MultiVehicleDockWidget.h"
#include "MAVLinkDecoder.h"
class Linecharts : public MultiVehicleDockWidget
{
Q_OBJECT
public:
explicit Linecharts(const QString& title, QAction* action, MAVLinkDecoder* decoder, QWidget *parent = 0);
signals:
/** @brief This signal is emitted once a logfile has been finished writing */
void logfileWritten(QString fileName);
void visibilityChanged(bool visible);
protected:
// Override from MultiVehicleDockWidget
virtual QWidget* _newVehicleWidget(Vehicle* vehicle, QWidget* parent);
private:
MAVLinkDecoder* _mavlinkDecoder;
};
#include <qevent.h>
#include <qwt_plot_canvas.h>
#include <qwt_plot_layout.h>
#include <qwt_scale_engine.h>
#include <qwt_scale_widget.h>
#include <Scrollbar.h>
#include <ScrollZoomer.h>
class ScrollData
{
public:
ScrollData():
scrollBar(NULL),
position(ScrollZoomer::OppositeToScale),
#if QT_VERSION < 0x040000
mode(QScrollView::Auto)
#else
mode(Qt::ScrollBarAsNeeded)
#endif
{
}
~ScrollData() {
delete scrollBar;
}
ScrollBar *scrollBar;
ScrollZoomer::ScrollBarPosition position;
#if QT_VERSION < 0x040000
QScrollView::ScrollBarMode mode;
#else
Qt::ScrollBarPolicy mode;
#endif
};
ScrollZoomer::ScrollZoomer(QwtPlotCanvas *canvas):
QwtPlotZoomer(canvas),
d_cornerWidget(NULL),
d_hScrollData(NULL),
d_vScrollData(NULL),
d_inZoom(false)
{
if ( !canvas )
return;
d_hScrollData = new ScrollData;
d_vScrollData = new ScrollData;
}
ScrollZoomer::~ScrollZoomer()
{
delete d_cornerWidget;
delete d_vScrollData;
delete d_hScrollData;
}
void ScrollZoomer::rescale()
{
QwtScaleWidget *xScale = plot()->axisWidget(xAxis());
QwtScaleWidget *yScale = plot()->axisWidget(yAxis());
if ( zoomRectIndex() <= 0 ) {
if ( d_inZoom ) {
xScale->setMinBorderDist(0, 0);
yScale->setMinBorderDist(0, 0);
d_inZoom = false;
}
} else {
if ( !d_inZoom ) {
/*
We set a minimum border distance.
Otherwise the canvas size changes when scrolling,
between situations where the major ticks are at
the canvas borders (requiring extra space for the label)
and situations where all labels can be painted below/top
or left/right of the canvas.
*/
int start, end;
xScale->getBorderDistHint(start, end);
xScale->setMinBorderDist(start, end);
yScale->getBorderDistHint(start, end);
yScale->setMinBorderDist(start, end);
d_inZoom = false;
}
}
QwtPlotZoomer::rescale();
updateScrollBars();
}
ScrollBar *ScrollZoomer::scrollBar(Qt::Orientation o)
{
ScrollBar *&sb = (o == Qt::Vertical)
? d_vScrollData->scrollBar : d_hScrollData->scrollBar;
if ( sb == NULL ) {
sb = new ScrollBar(o, canvas());
sb->hide();
connect(sb, &ScrollBar::valueChanged, this, &ScrollZoomer::scrollBarMoved);
}
return sb;
}
ScrollBar *ScrollZoomer::horizontalScrollBar() const
{
return d_hScrollData->scrollBar;
}
ScrollBar *ScrollZoomer::verticalScrollBar() const
{
return d_vScrollData->scrollBar;
}
void ScrollZoomer::setHScrollBarMode(Qt::ScrollBarPolicy mode)
{
if ( hScrollBarMode() != mode ) {
d_hScrollData->mode = mode;
updateScrollBars();
}
}
void ScrollZoomer::setVScrollBarMode(Qt::ScrollBarPolicy mode)
{
if ( vScrollBarMode() != mode ) {
d_vScrollData->mode = mode;
updateScrollBars();
}
}
Qt::ScrollBarPolicy ScrollZoomer::hScrollBarMode() const
{
return d_hScrollData->mode;
}
Qt::ScrollBarPolicy ScrollZoomer::vScrollBarMode() const
{
return d_vScrollData->mode;
}
void ScrollZoomer::setHScrollBarPosition(ScrollBarPosition pos)
{
if ( d_hScrollData->position != pos ) {
d_hScrollData->position = pos;
updateScrollBars();
}
}
void ScrollZoomer::setVScrollBarPosition(ScrollBarPosition pos)
{
if ( d_vScrollData->position != pos ) {
d_vScrollData->position = pos;
updateScrollBars();
}
}
ScrollZoomer::ScrollBarPosition ScrollZoomer::hScrollBarPosition() const
{
return d_hScrollData->position;
}
ScrollZoomer::ScrollBarPosition ScrollZoomer::vScrollBarPosition() const
{
return d_vScrollData->position;
}
void ScrollZoomer::setCornerWidget(QWidget *w)
{
if ( w != d_cornerWidget ) {
if ( canvas() ) {
delete d_cornerWidget;
d_cornerWidget = w;
if ( d_cornerWidget->parent() != canvas() ) {
d_cornerWidget->setParent(canvas());
}
updateScrollBars();
}
}
}
QWidget *ScrollZoomer::cornerWidget() const
{
return d_cornerWidget;
}
bool ScrollZoomer::eventFilter(QObject *o, QEvent *e)
{
if ( o == canvas() ) {
switch(e->type()) {
case QEvent::Resize: {
const int fw = ((QwtPlotCanvas *)canvas())->frameWidth();
QRect rect;
rect.setSize(((QResizeEvent *)e)->size());
rect.setRect(rect.x() + fw, rect.y() + fw,
rect.width() - 2 * fw, rect.height() - 2 * fw);
layoutScrollBars(rect);
break;
}
case QEvent::ChildRemoved: {
const QObject *child = ((QChildEvent *)e)->child();
if ( child == d_cornerWidget )
d_cornerWidget = NULL;
else if ( child == d_hScrollData->scrollBar )
d_hScrollData->scrollBar = NULL;
else if ( child == d_vScrollData->scrollBar )
d_vScrollData->scrollBar = NULL;
break;
}
default:
break;
}
}
return QwtPlotZoomer::eventFilter(o, e);
}
bool ScrollZoomer::needScrollBar(Qt::Orientation o) const
{
Qt::ScrollBarPolicy mode;
double zoomMin, zoomMax, baseMin, baseMax;
if ( o == Qt::Horizontal ) {
mode = d_hScrollData->mode;
baseMin = zoomBase().left();
baseMax = zoomBase().right();
zoomMin = zoomRect().left();
zoomMax = zoomRect().right();
} else {
mode = d_vScrollData->mode;
baseMin = zoomBase().top();
baseMax = zoomBase().bottom();
zoomMin = zoomRect().top();
zoomMax = zoomRect().bottom();
}
bool needed = false;
switch(mode) {
case Qt::ScrollBarAlwaysOn:
needed = true;
break;
case Qt::ScrollBarAlwaysOff:
needed = false;
break;
default: {
if ( baseMin < zoomMin || baseMax > zoomMax )
needed = true;
break;
}
}
return needed;
}
void ScrollZoomer::updateScrollBars()
{
if ( !canvas() )
return;
const int xAxis = QwtPlotZoomer::xAxis();
const int yAxis = QwtPlotZoomer::yAxis();
int xScrollBarAxis = xAxis;
if ( hScrollBarPosition() == OppositeToScale )
xScrollBarAxis = oppositeAxis(xScrollBarAxis);
int yScrollBarAxis = yAxis;
if ( vScrollBarPosition() == OppositeToScale )
yScrollBarAxis = oppositeAxis(yScrollBarAxis);
QwtPlotLayout *layout = plot()->plotLayout();
bool showHScrollBar = needScrollBar(Qt::Horizontal);
if ( showHScrollBar ) {
ScrollBar *sb = scrollBar(Qt::Horizontal);
sb->setPalette(plot()->palette());
const QwtScaleEngine *se = plot()->axisScaleEngine(xAxis);
sb->setInverted(se->testAttribute(QwtScaleEngine::Inverted));
sb->setBase(zoomBase().left(), zoomBase().right());
sb->moveSlider(zoomRect().left(), zoomRect().right());
if ( !sb->isVisibleTo(canvas()) ) {
sb->show();
layout->setCanvasMargin(layout->canvasMargin(xScrollBarAxis)
+ sb->extent(), xScrollBarAxis);
}
} else {
if ( horizontalScrollBar() ) {
horizontalScrollBar()->hide();
layout->setCanvasMargin(layout->canvasMargin(xScrollBarAxis)
- horizontalScrollBar()->extent(), xScrollBarAxis);
}
}
bool showVScrollBar = needScrollBar(Qt::Vertical);
if ( showVScrollBar ) {
ScrollBar *sb = scrollBar(Qt::Vertical);
sb->setPalette(plot()->palette());
const QwtScaleEngine *se = plot()->axisScaleEngine(xAxis);
sb->setInverted(!(se->testAttribute(QwtScaleEngine::Inverted)));
sb->setBase(zoomBase().top(), zoomBase().bottom());
sb->moveSlider(zoomRect().top(), zoomRect().bottom());
if ( !sb->isVisibleTo(canvas()) ) {
sb->show();
layout->setCanvasMargin(layout->canvasMargin(yScrollBarAxis)
+ sb->extent(), yScrollBarAxis);
}
} else {
if ( verticalScrollBar() ) {
verticalScrollBar()->hide();
layout->setCanvasMargin(layout->canvasMargin(yScrollBarAxis)
- verticalScrollBar()->extent(), yScrollBarAxis);
}
}
if ( showHScrollBar && showVScrollBar ) {
if ( d_cornerWidget == NULL ) {
d_cornerWidget = new QWidget(canvas());
d_cornerWidget->setPalette(plot()->palette());
}
d_cornerWidget->show();
} else {
if ( d_cornerWidget )
d_cornerWidget->hide();
}
layoutScrollBars(((QwtPlotCanvas *)canvas())->contentsRect());
plot()->updateLayout();
}
void ScrollZoomer::layoutScrollBars(const QRect &rect)
{
int hPos = xAxis();
if ( hScrollBarPosition() == OppositeToScale )
hPos = oppositeAxis(hPos);
int vPos = yAxis();
if ( vScrollBarPosition() == OppositeToScale )
vPos = oppositeAxis(vPos);
ScrollBar *hScrollBar = horizontalScrollBar();
ScrollBar *vScrollBar = verticalScrollBar();
const int hdim = hScrollBar ? hScrollBar->extent() : 0;
const int vdim = vScrollBar ? vScrollBar->extent() : 0;
if ( hScrollBar && hScrollBar->isVisible() ) {
int x = rect.x();
int y = (hPos == QwtPlot::xTop)
? rect.top() : rect.bottom() - hdim + 1;
int w = rect.width();
if ( vScrollBar && vScrollBar->isVisible() ) {
if ( vPos == QwtPlot::yLeft )
x += vdim;
w -= vdim;
}
hScrollBar->setGeometry(x, y, w, hdim);
}
if ( vScrollBar && vScrollBar->isVisible() ) {
//-- TODO: What is this "pos"? It only gets
// assigned but never used within this
// scope.
int pos = yAxis();
if ( vScrollBarPosition() == OppositeToScale )
pos = oppositeAxis(pos);
int x = (vPos == QwtPlot::yLeft)
? rect.left() : rect.right() - vdim + 1;
int y = rect.y();
int h = rect.height();
if ( hScrollBar && hScrollBar->isVisible() ) {
if ( hPos == QwtPlot::xTop )
y += hdim;
h -= hdim;
}
vScrollBar->setGeometry(x, y, vdim, h);
}
if ( hScrollBar && hScrollBar->isVisible() &&
vScrollBar && vScrollBar->isVisible() ) {
if ( d_cornerWidget ) {
QRect cornerRect(
vScrollBar->pos().x(), hScrollBar->pos().y(),
vdim, hdim);
d_cornerWidget->setGeometry(cornerRect);
}
}
}
void ScrollZoomer::scrollBarMoved(Qt::Orientation o, double min, double)
{
if ( o == Qt::Horizontal )
move(QPoint(min, zoomRect().top()));
else
move(QPoint(zoomRect().left(), min));
emit zoomed(zoomRect());
}
int ScrollZoomer::oppositeAxis(int axis) const
{
switch(axis) {
case QwtPlot::xBottom:
return QwtPlot::xTop;
case QwtPlot::xTop:
return QwtPlot::xBottom;
case QwtPlot::yLeft:
return QwtPlot::yRight;
case QwtPlot::yRight:
return QwtPlot::yLeft;
default:
break;
}
return axis;
}
#pragma once
#include <qglobal.h>
#if QT_VERSION < 0x040000
#include <qscrollview.h>
#endif
#include <qwt_plot_zoomer.h>
#include <qwt_plot_canvas.h>
class ScrollData;
class ScrollBar;
class ScrollZoomer: public QwtPlotZoomer
{
Q_OBJECT
public:
enum ScrollBarPosition {
AttachedToScale,
OppositeToScale
};
ScrollZoomer(QwtPlotCanvas *);
virtual ~ScrollZoomer();
ScrollBar *horizontalScrollBar() const;
ScrollBar *verticalScrollBar() const;
#if QT_VERSION < 0x040000
void setHScrollBarMode(QScrollView::ScrollBarMode);
void setVScrollBarMode(QScrollView::ScrollBarMode);
QScrollView::ScrollBarMode vScrollBarMode () const;
QScrollView::ScrollBarMode hScrollBarMode () const;
#else
void setHScrollBarMode(Qt::ScrollBarPolicy);
void setVScrollBarMode(Qt::ScrollBarPolicy);
Qt::ScrollBarPolicy vScrollBarMode () const;
Qt::ScrollBarPolicy hScrollBarMode () const;
#endif
void setHScrollBarPosition(ScrollBarPosition);
void setVScrollBarPosition(ScrollBarPosition);
ScrollBarPosition hScrollBarPosition() const;
ScrollBarPosition vScrollBarPosition() const;
QWidget* cornerWidget() const;
virtual void setCornerWidget(QWidget *);
virtual bool eventFilter(QObject *, QEvent *);
virtual void rescale();
protected:
virtual ScrollBar *scrollBar(Qt::Orientation);
virtual void updateScrollBars();
virtual void layoutScrollBars(const QRect &);
private slots:
void scrollBarMoved(Qt::Orientation o, double min, double max);
private:
bool needScrollBar(Qt::Orientation) const;
int oppositeAxis(int) const;
QWidget *d_cornerWidget;
ScrollData *d_hScrollData;
ScrollData *d_vScrollData;
bool d_inZoom;
};
#include <qstyle.h>
#include <qstyleoption.h>
#include "Scrollbar.h"
ScrollBar::ScrollBar(QWidget * parent):
QScrollBar(parent)
{
init();
}
ScrollBar::ScrollBar(Qt::Orientation o,
QWidget *parent):
QScrollBar(o, parent)
{
init();
}
ScrollBar::ScrollBar(double minBase, double maxBase,
Qt::Orientation o, QWidget *parent):
QScrollBar(o, parent)
{
init();
setBase(minBase, maxBase);
moveSlider(minBase, maxBase);
}
void ScrollBar::init()
{
d_inverted = orientation() == Qt::Vertical;
d_baseTicks = 1000000;
d_minBase = 0.0;
d_maxBase = 1.0;
moveSlider(d_minBase, d_maxBase);
connect(this, &ScrollBar::sliderMoved, this, &ScrollBar::catchSliderMoved);
connect(this, &ScrollBar::valueChanged, this, &ScrollBar::catchValueChanged);
}
void ScrollBar::setInverted(bool inverted)
{
if ( d_inverted != inverted ) {
d_inverted = inverted;
moveSlider(minSliderValue(), maxSliderValue());
}
}
bool ScrollBar::isInverted() const
{
return d_inverted;
}
void ScrollBar::setBase(double min, double max)
{
if ( min != d_minBase || max != d_maxBase ) {
d_minBase = min;
d_maxBase = max;
moveSlider(minSliderValue(), maxSliderValue());
}
}
void ScrollBar::moveSlider(double min, double max)
{
const int sliderTicks = qRound((max - min) /
(d_maxBase - d_minBase) * d_baseTicks);
// setRange initiates a valueChanged of the scrollbars
// in some situations. So we block
// and unblock the signals.
blockSignals(true);
setRange(sliderTicks / 2, d_baseTicks - sliderTicks / 2);
int steps = sliderTicks / 200;
if ( steps <= 0 )
steps = 1;
setSingleStep(steps);
setPageStep(sliderTicks);
int tick = mapToTick(min + (max - min) / 2);
if ( isInverted() )
tick = d_baseTicks - tick;
setSliderPosition(tick);
blockSignals(false);
}
double ScrollBar::minBaseValue() const
{
return d_minBase;
}
double ScrollBar::maxBaseValue() const
{
return d_maxBase;
}
void ScrollBar::sliderRange(int value, double &min, double &max) const
{
if ( isInverted() )
value = d_baseTicks - value;
const int visibleTicks = pageStep();
min = mapFromTick(value - visibleTicks / 2);
max = mapFromTick(value + visibleTicks / 2);
}
double ScrollBar::minSliderValue() const
{
double min, dummy;
sliderRange(value(), min, dummy);
return min;
}
double ScrollBar::maxSliderValue() const
{
double max, dummy;
sliderRange(value(), dummy, max);
return max;
}
int ScrollBar::mapToTick(double v) const
{
return (int) ( ( v - d_minBase) / (d_maxBase - d_minBase ) * d_baseTicks );
}
double ScrollBar::mapFromTick(int tick) const
{
return d_minBase + ( d_maxBase - d_minBase ) * tick / d_baseTicks;
}
void ScrollBar::catchValueChanged(int value)
{
double min, max;
sliderRange(value, min, max);
emit valueChanged(orientation(), min, max);
}
void ScrollBar::catchSliderMoved(int value)
{
double min, max;
sliderRange(value, min, max);
emit sliderMoved(orientation(), min, max);
}
int ScrollBar::extent() const
{
QStyleOptionSlider opt;
opt.init(this);
opt.subControls = QStyle::SC_None;
opt.activeSubControls = QStyle::SC_None;
opt.orientation = orientation();
opt.minimum = minimum();
opt.maximum = maximum();
opt.sliderPosition = sliderPosition();
opt.sliderValue = value();
opt.singleStep = singleStep();
opt.pageStep = pageStep();
opt.upsideDown = invertedAppearance();
if (orientation() == Qt::Horizontal)
opt.state |= QStyle::State_Horizontal;
return style()->pixelMetric(QStyle::PM_ScrollBarExtent, &opt, this);
}
#pragma once
#include <qscrollbar.h>
class ScrollBar: public QScrollBar
{
Q_OBJECT
public:
ScrollBar(QWidget *parent = NULL);
ScrollBar(Qt::Orientation, QWidget *parent = NULL);
ScrollBar(double minBase, double maxBase,
Qt::Orientation o, QWidget *parent = NULL);
void setInverted(bool);
bool isInverted() const;
double minBaseValue() const;
double maxBaseValue() const;
double minSliderValue() const;
double maxSliderValue() const;
int extent() const;
signals:
void sliderMoved(Qt::Orientation, double, double);
void valueChanged(Qt::Orientation, double, double);
public slots:
virtual void setBase(double min, double max);
virtual void moveSlider(double min, double max);
protected:
void sliderRange(int value, double &min, double &max) const;
int mapToTick(double) const;
double mapFromTick(int) const;
private slots:
void catchValueChanged(int value);
void catchSliderMoved(int value);
private:
void init();
bool d_inverted;
double d_minBase;
double d_maxBase;
int d_baseTicks;
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment