mirror of
https://bitbucket.org/librepilot/librepilot.git
synced 2025-02-20 10:54:14 +01:00
Merged in Oblivium/librepilot/LP-597_Progress_bar_for_GCS_log_replay (pull request #511)
LP-597 Progress bar for GCS log replay Approved-by: Jan NIJS <dr.oblivium@gmail.com> Approved-by: Philippe Renon <philippe_renon@yahoo.fr> Approved-by: Lalanne Laurent <f5soh@free.fr>
This commit is contained in:
commit
2f075df628
@ -2,7 +2,7 @@
|
||||
******************************************************************************
|
||||
*
|
||||
* @file logfile.cpp
|
||||
* @author The LibrePilot Project, http://www.librepilot.org Copyright (C) 2017.
|
||||
* @author The LibrePilot Project, http://www.librepilot.org Copyright (C) 2017-2018.
|
||||
* The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
|
||||
* @see The GNU Public License (GPL) Version 3
|
||||
*
|
||||
@ -26,6 +26,9 @@
|
||||
|
||||
#include <QDebug>
|
||||
#include <QtGlobal>
|
||||
#include <QDataStream>
|
||||
|
||||
#define TIMESTAMP_SIZE_BYTES 4
|
||||
|
||||
LogFile::LogFile(QObject *parent) : QIODevice(parent),
|
||||
m_timer(this),
|
||||
@ -34,9 +37,12 @@ LogFile::LogFile(QObject *parent) : QIODevice(parent),
|
||||
m_lastPlayed(0),
|
||||
m_timeOffset(0),
|
||||
m_playbackSpeed(1.0),
|
||||
paused(false),
|
||||
m_replayState(STOPPED),
|
||||
m_useProvidedTimeStamp(false),
|
||||
m_providedTimeStamp(0)
|
||||
m_providedTimeStamp(0),
|
||||
m_beginTimeStamp(0),
|
||||
m_endTimeStamp(0),
|
||||
m_timerTick(0)
|
||||
{
|
||||
connect(&m_timer, &QTimer::timeout, this, &LogFile::timerFired);
|
||||
}
|
||||
@ -137,36 +143,61 @@ qint64 LogFile::bytesAvailable() const
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
timerFired()
|
||||
|
||||
This function is called at a 10 ms interval to fill the replay buffers.
|
||||
|
||||
*/
|
||||
void LogFile::timerFired()
|
||||
{
|
||||
if (m_file.bytesAvailable() > 4) {
|
||||
if (m_replayState != PLAYING) {
|
||||
return;
|
||||
}
|
||||
m_timerTick++;
|
||||
|
||||
if (m_file.bytesAvailable() > TIMESTAMP_SIZE_BYTES) {
|
||||
int time;
|
||||
time = m_myTime.elapsed();
|
||||
|
||||
// TODO: going back in time will be a problem
|
||||
while ((m_lastPlayed + ((double)(time - m_timeOffset) * m_playbackSpeed) > m_nextTimeStamp)) {
|
||||
/*
|
||||
This code generates an advancing playback window. All samples that fit the window
|
||||
are replayed. The window is about the size of the timer interval: 10 ms.
|
||||
|
||||
Description of used variables:
|
||||
|
||||
time : real-time interval since start of playback (in ms) - now()
|
||||
m_timeOffset : real-time interval since start of playback (in ms) - when timerFired() was previously run
|
||||
m_nextTimeStamp : read log until this log timestamp has been reached (in ms)
|
||||
m_lastPlayed : log referenced timestamp advanced to during previous cycle (in ms)
|
||||
m_playbackSpeed : 0.1 .. 1.0 .. 10 replay speedup factor
|
||||
|
||||
*/
|
||||
|
||||
while (m_nextTimeStamp < (m_lastPlayed + (double)(time - m_timeOffset) * m_playbackSpeed)) {
|
||||
// advance the replay window for the next time period
|
||||
m_lastPlayed += ((double)(time - m_timeOffset) * m_playbackSpeed);
|
||||
|
||||
// read data size
|
||||
qint64 dataSize;
|
||||
if (m_file.bytesAvailable() < (qint64)sizeof(dataSize)) {
|
||||
qDebug() << "LogFile - end of log file reached";
|
||||
stopReplay();
|
||||
qDebug() << "LogFile replay - end of log file reached";
|
||||
resetReplay();
|
||||
return;
|
||||
}
|
||||
m_file.read((char *)&dataSize, sizeof(dataSize));
|
||||
|
||||
// check size consistency
|
||||
if (dataSize < 1 || dataSize > (1024 * 1024)) {
|
||||
qWarning() << "LogFile - corrupted log file! Unlikely packet size:" << dataSize;
|
||||
qWarning() << "LogFile replay - corrupted log file! Unlikely packet size:" << dataSize;
|
||||
stopReplay();
|
||||
return;
|
||||
}
|
||||
|
||||
// read data
|
||||
if (m_file.bytesAvailable() < dataSize) {
|
||||
qDebug() << "LogFile - end of log file reached";
|
||||
stopReplay();
|
||||
qDebug() << "LogFile replay - end of log file reached";
|
||||
resetReplay();
|
||||
return;
|
||||
}
|
||||
QByteArray data = m_file.read(dataSize);
|
||||
@ -178,10 +209,14 @@ void LogFile::timerFired()
|
||||
|
||||
emit readyRead();
|
||||
|
||||
// rate-limit slider bar position updates to 10 updates per second
|
||||
if (m_timerTick % 10 == 0) {
|
||||
emit playbackPositionChanged(m_nextTimeStamp);
|
||||
}
|
||||
// read next timestamp
|
||||
if (m_file.bytesAvailable() < (qint64)sizeof(m_nextTimeStamp)) {
|
||||
qDebug() << "LogFile - end of log file reached";
|
||||
stopReplay();
|
||||
qDebug() << "LogFile replay - end of log file reached";
|
||||
resetReplay();
|
||||
return;
|
||||
}
|
||||
m_previousTimeStamp = m_nextTimeStamp;
|
||||
@ -190,17 +225,17 @@ void LogFile::timerFired()
|
||||
// some validity checks
|
||||
if ((m_nextTimeStamp < m_previousTimeStamp) // logfile goes back in time
|
||||
|| ((m_nextTimeStamp - m_previousTimeStamp) > 60 * 60 * 1000)) { // gap of more than 60 minutes
|
||||
qWarning() << "LogFile - corrupted log file! Unlikely timestamp:" << m_nextTimeStamp << "after" << m_previousTimeStamp;
|
||||
qWarning() << "LogFile replay - corrupted log file! Unlikely timestamp:" << m_nextTimeStamp << "after" << m_previousTimeStamp;
|
||||
stopReplay();
|
||||
return;
|
||||
}
|
||||
|
||||
m_timeOffset = time;
|
||||
time = m_myTime.elapsed();
|
||||
time = m_myTime.elapsed(); // number of milliseconds since start of playback
|
||||
}
|
||||
} else {
|
||||
qDebug() << "LogFile - end of log file reached";
|
||||
stopReplay();
|
||||
qDebug() << "LogFile replay - end of log file reached";
|
||||
resetReplay();
|
||||
}
|
||||
}
|
||||
|
||||
@ -209,8 +244,26 @@ bool LogFile::isPlaying() const
|
||||
return m_file.isOpen() && m_timer.isActive();
|
||||
}
|
||||
|
||||
/**
|
||||
* FUNCTION: startReplay()
|
||||
*
|
||||
* Starts replaying a newly opened logfile.
|
||||
* Starts a timer: m_timer
|
||||
*
|
||||
* This function and the stopReplay() function should only ever be called from the same thread.
|
||||
* This is required for correct control of the timer.
|
||||
*
|
||||
*/
|
||||
bool LogFile::startReplay()
|
||||
{
|
||||
// Walk through logfile and create timestamp index
|
||||
// Don't start replay if there was a problem indexing the logfile.
|
||||
if (!buildIndex()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_timerTick = 0;
|
||||
|
||||
if (!m_file.isOpen() || m_timer.isActive()) {
|
||||
return false;
|
||||
}
|
||||
@ -221,7 +274,9 @@ bool LogFile::startReplay()
|
||||
m_lastPlayed = 0;
|
||||
m_previousTimeStamp = 0;
|
||||
m_nextTimeStamp = 0;
|
||||
m_mutex.lock();
|
||||
m_dataBuffer.clear();
|
||||
m_mutex.unlock();
|
||||
|
||||
// read next timestamp
|
||||
if (m_file.bytesAvailable() < (qint64)sizeof(m_nextTimeStamp)) {
|
||||
@ -232,25 +287,125 @@ bool LogFile::startReplay()
|
||||
|
||||
m_timer.setInterval(10);
|
||||
m_timer.start();
|
||||
paused = false;
|
||||
m_replayState = PLAYING;
|
||||
|
||||
emit replayStarted();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* FUNCTION: stopReplay()
|
||||
*
|
||||
* Stops replaying the logfile.
|
||||
* Stops the timer: m_timer
|
||||
*
|
||||
* This function and the startReplay() function should only ever be called from the same thread.
|
||||
* This is a requirement to be able to control the timer.
|
||||
*
|
||||
*/
|
||||
bool LogFile::stopReplay()
|
||||
{
|
||||
if (!m_file.isOpen() || !(m_timer.isActive() || paused)) {
|
||||
if (!m_file.isOpen()) {
|
||||
return false;
|
||||
}
|
||||
if (m_timer.isActive()) {
|
||||
m_timer.stop();
|
||||
}
|
||||
|
||||
qDebug() << "LogFile - stopReplay";
|
||||
m_timer.stop();
|
||||
paused = false;
|
||||
m_replayState = STOPPED;
|
||||
|
||||
emit replayFinished();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* FUNCTION: resetReplay()
|
||||
*
|
||||
* Stops replaying the logfile.
|
||||
* Stops the timer: m_timer
|
||||
* Resets playback position to the start of the logfile
|
||||
* through the emission of a replayCompleted signal.
|
||||
*
|
||||
*/
|
||||
bool LogFile::resetReplay()
|
||||
{
|
||||
if (!m_file.isOpen()) {
|
||||
return false;
|
||||
}
|
||||
if (m_timer.isActive()) {
|
||||
m_timer.stop();
|
||||
}
|
||||
|
||||
qDebug() << "LogFile - resetReplay";
|
||||
m_replayState = STOPPED;
|
||||
|
||||
emit replayCompleted();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* SLOT: resumeReplay()
|
||||
*
|
||||
* Resumes replay from the given position.
|
||||
* If no position is given, resumes from the last position
|
||||
*
|
||||
*/
|
||||
bool LogFile::resumeReplay(quint32 desiredPosition)
|
||||
{
|
||||
if (m_timer.isActive()) {
|
||||
return false;
|
||||
}
|
||||
qDebug() << "LogFile - resumeReplay";
|
||||
|
||||
// Clear the playout buffer:
|
||||
m_mutex.lock();
|
||||
m_dataBuffer.clear();
|
||||
m_mutex.unlock();
|
||||
|
||||
m_file.seek(0);
|
||||
|
||||
/* Skip through the logfile until we reach the desired position.
|
||||
Looking for the next log timestamp after the desired position
|
||||
has the advantage that it skips over parts of the log
|
||||
where data might be missing.
|
||||
*/
|
||||
for (int i = 0; i < m_timeStamps.size(); i++) {
|
||||
if (m_timeStamps.at(i) >= desiredPosition) {
|
||||
int bytesToSkip = m_timeStampPositions.at(i);
|
||||
bool seek_ok = m_file.seek(bytesToSkip);
|
||||
if (!seek_ok) {
|
||||
qWarning() << "LogFile resumeReplay - an error occurred while seeking through the logfile.";
|
||||
}
|
||||
m_lastPlayed = m_timeStamps.at(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
m_file.read((char *)&m_nextTimeStamp, sizeof(m_nextTimeStamp));
|
||||
|
||||
// Real-time timestamps don't not need to match the log timestamps.
|
||||
// However the delta between real-time variables "m_timeOffset" and "m_myTime" is important.
|
||||
// This delta determines the number of log entries replayed per cycle.
|
||||
|
||||
// Set the real-time interval to 0 to start with:
|
||||
m_myTime.restart();
|
||||
m_timeOffset = 0;
|
||||
|
||||
m_replayState = PLAYING;
|
||||
|
||||
m_timer.start();
|
||||
|
||||
// Notify UI that playback has resumed
|
||||
emit replayStarted();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* SLOT: pauseReplay()
|
||||
*
|
||||
* Pauses replay while storing the current playback position
|
||||
*
|
||||
*/
|
||||
bool LogFile::pauseReplay()
|
||||
{
|
||||
if (!m_timer.isActive()) {
|
||||
@ -258,24 +413,149 @@ bool LogFile::pauseReplay()
|
||||
}
|
||||
qDebug() << "LogFile - pauseReplay";
|
||||
m_timer.stop();
|
||||
paused = true;
|
||||
|
||||
// hack to notify UI that replay paused
|
||||
emit replayStarted();
|
||||
m_replayState = PAUSED;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LogFile::resumeReplay()
|
||||
/**
|
||||
* SLOT: pauseReplayAndResetPosition()
|
||||
*
|
||||
* Pauses replay and resets the playback position to the start of the logfile
|
||||
*
|
||||
*/
|
||||
bool LogFile::pauseReplayAndResetPosition()
|
||||
{
|
||||
if (m_timer.isActive()) {
|
||||
if (!m_file.isOpen() || !m_timer.isActive()) {
|
||||
return false;
|
||||
}
|
||||
qDebug() << "LogFile - resumeReplay";
|
||||
m_timeOffset = m_myTime.elapsed();
|
||||
m_timer.start();
|
||||
paused = false;
|
||||
qDebug() << "LogFile - pauseReplayAndResetPosition";
|
||||
m_timer.stop();
|
||||
m_replayState = STOPPED;
|
||||
|
||||
m_timeOffset = 0;
|
||||
m_lastPlayed = m_timeStamps.at(0);
|
||||
m_previousTimeStamp = 0;
|
||||
m_nextTimeStamp = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* FUNCTION: getReplayState()
|
||||
*
|
||||
* Returns the current replay status.
|
||||
*
|
||||
*/
|
||||
ReplayState LogFile::getReplayState()
|
||||
{
|
||||
return m_replayState;
|
||||
}
|
||||
|
||||
/**
|
||||
* FUNCTION: buildIndex()
|
||||
*
|
||||
* Walk through the opened logfile and stores the first and last position timestamps.
|
||||
* Also builds an index for quickly skipping to a specific position in the logfile.
|
||||
*
|
||||
* Returns true when indexing has completed successfully.
|
||||
* Returns false when a problem was encountered.
|
||||
*
|
||||
*/
|
||||
bool LogFile::buildIndex()
|
||||
{
|
||||
quint32 timeStamp;
|
||||
qint64 totalSize;
|
||||
qint64 readPointer = 0;
|
||||
quint64 index = 0;
|
||||
int bytesRead = 0;
|
||||
|
||||
qDebug() << "LogFile - buildIndex";
|
||||
|
||||
// Ensure empty vectors:
|
||||
m_timeStampPositions.clear();
|
||||
m_timeStamps.clear();
|
||||
|
||||
QByteArray arr = m_file.readAll();
|
||||
totalSize = arr.size();
|
||||
QDataStream dataStream(&arr, QIODevice::ReadOnly);
|
||||
|
||||
// set the first timestamp
|
||||
if (totalSize - readPointer >= TIMESTAMP_SIZE_BYTES) {
|
||||
bytesRead = dataStream.readRawData((char *)&timeStamp, TIMESTAMP_SIZE_BYTES);
|
||||
if (bytesRead != TIMESTAMP_SIZE_BYTES) {
|
||||
qWarning() << "LogFile buildIndex - read first timeStamp: readRawData returned unexpected number of bytes:" << bytesRead << "at position" << readPointer << "\n";
|
||||
return false;
|
||||
}
|
||||
m_timeStamps.append(timeStamp);
|
||||
m_timeStampPositions.append(readPointer);
|
||||
readPointer += TIMESTAMP_SIZE_BYTES;
|
||||
index++;
|
||||
m_beginTimeStamp = timeStamp;
|
||||
m_endTimeStamp = timeStamp;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
qint64 dataSize;
|
||||
|
||||
// Check if there are enough bytes remaining for a correct "dataSize" field
|
||||
if (totalSize - readPointer < (qint64)sizeof(dataSize)) {
|
||||
qWarning() << "LogFile buildIndex - logfile corrupted! Unexpected end of file";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read the dataSize field and check for I/O errors
|
||||
bytesRead = dataStream.readRawData((char *)&dataSize, sizeof(dataSize));
|
||||
if (bytesRead != sizeof(dataSize)) {
|
||||
qWarning() << "LogFile buildIndex - read dataSize: readRawData returned unexpected number of bytes:" << bytesRead << "at position" << readPointer << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
readPointer += sizeof(dataSize);
|
||||
|
||||
if (dataSize < 1 || dataSize > (1024 * 1024)) {
|
||||
qWarning() << "LogFile buildIndex - logfile corrupted! Unlikely packet size: " << dataSize << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if there are enough bytes remaining
|
||||
if (totalSize - readPointer < dataSize) {
|
||||
qWarning() << "LogFile buildIndex - logfile corrupted! Unexpected end of file";
|
||||
return false;
|
||||
}
|
||||
|
||||
// skip reading the data (we don't need it at this point)
|
||||
readPointer += dataStream.skipRawData(dataSize);
|
||||
|
||||
// read the next timestamp
|
||||
if (totalSize - readPointer >= TIMESTAMP_SIZE_BYTES) {
|
||||
bytesRead = dataStream.readRawData((char *)&timeStamp, TIMESTAMP_SIZE_BYTES);
|
||||
if (bytesRead != TIMESTAMP_SIZE_BYTES) {
|
||||
qWarning() << "LogFile buildIndex - read timeStamp, readRawData returned unexpected number of bytes:" << bytesRead << "at position" << readPointer << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// some validity checks
|
||||
if (timeStamp < m_endTimeStamp // logfile goes back in time
|
||||
|| (timeStamp - m_endTimeStamp) > (60 * 60 * 1000)) { // gap of more than 60 minutes)
|
||||
qWarning() << "LogFile buildIndex - logfile corrupted! Unlikely timestamp " << timeStamp << " after " << m_endTimeStamp;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_timeStamps.append(timeStamp);
|
||||
m_timeStampPositions.append(readPointer);
|
||||
readPointer += TIMESTAMP_SIZE_BYTES;
|
||||
index++;
|
||||
m_endTimeStamp = timeStamp;
|
||||
} else {
|
||||
// Break without error (we expect to end at this location when we are at the end of the logfile)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
emit timesChanged(m_beginTimeStamp, m_endTimeStamp);
|
||||
|
||||
// reset the read pointer to the start of the file
|
||||
m_file.seek(0);
|
||||
|
||||
// hack to notify UI that replay resumed
|
||||
emit replayStarted();
|
||||
return true;
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
******************************************************************************
|
||||
*
|
||||
* @file logfile.h
|
||||
* @author The LibrePilot Project, http://www.librepilot.org Copyright (C) 2017.
|
||||
* @author The LibrePilot Project, http://www.librepilot.org Copyright (C) 2017-2018.
|
||||
* The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
|
||||
* @see The GNU Public License (GPL) Version 3
|
||||
*
|
||||
@ -32,8 +32,10 @@
|
||||
#include <QTimer>
|
||||
#include <QMutexLocker>
|
||||
#include <QDebug>
|
||||
#include <QBuffer>
|
||||
#include <QFile>
|
||||
#include <QVector>
|
||||
|
||||
typedef enum { PLAYING, PAUSED, STOPPED } ReplayState;
|
||||
|
||||
class QTCREATOR_UTILS_EXPORT LogFile : public QIODevice {
|
||||
Q_OBJECT
|
||||
@ -75,6 +77,8 @@ public:
|
||||
m_providedTimeStamp = providedTimestamp;
|
||||
}
|
||||
|
||||
ReplayState getReplayState();
|
||||
|
||||
public slots:
|
||||
void setReplaySpeed(double val)
|
||||
{
|
||||
@ -83,24 +87,28 @@ public slots:
|
||||
};
|
||||
bool startReplay();
|
||||
bool stopReplay();
|
||||
|
||||
bool resumeReplay(quint32);
|
||||
bool pauseReplay();
|
||||
bool resumeReplay();
|
||||
bool pauseReplayAndResetPosition();
|
||||
|
||||
protected slots:
|
||||
void timerFired();
|
||||
|
||||
signals:
|
||||
void readReady();
|
||||
void replayStarted();
|
||||
void replayFinished();
|
||||
void replayFinished(); // Emitted on error during replay or when logfile disconnected
|
||||
void replayCompleted(); // Emitted at the end of normal logfile playback
|
||||
void playbackPositionChanged(quint32);
|
||||
void timesChanged(quint32, quint32);
|
||||
|
||||
protected:
|
||||
QByteArray m_dataBuffer;
|
||||
QTimer m_timer;
|
||||
QTime m_myTime;
|
||||
QFile m_file;
|
||||
qint32 m_previousTimeStamp;
|
||||
qint32 m_nextTimeStamp;
|
||||
quint32 m_previousTimeStamp;
|
||||
quint32 m_nextTimeStamp;
|
||||
double m_lastPlayed;
|
||||
// QMutex wants to be mutable
|
||||
// http://stackoverflow.com/questions/25521570/can-mutex-locking-function-be-marked-as-const
|
||||
@ -108,11 +116,19 @@ protected:
|
||||
|
||||
int m_timeOffset;
|
||||
double m_playbackSpeed;
|
||||
bool paused;
|
||||
ReplayState m_replayState;
|
||||
|
||||
private:
|
||||
bool m_useProvidedTimeStamp;
|
||||
qint32 m_providedTimeStamp;
|
||||
quint32 m_beginTimeStamp;
|
||||
quint32 m_endTimeStamp;
|
||||
quint32 m_timerTick;
|
||||
QVector<quint32> m_timeStamps;
|
||||
QVector<qint64> m_timeStampPositions;
|
||||
|
||||
bool buildIndex();
|
||||
bool resetReplay();
|
||||
};
|
||||
|
||||
#endif // LOGFILE_H
|
||||
|
BIN
ground/gcs/src/plugins/logging/images/pause.png
Normal file
BIN
ground/gcs/src/plugins/logging/images/pause.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
BIN
ground/gcs/src/plugins/logging/images/play.png
Normal file
BIN
ground/gcs/src/plugins/logging/images/play.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
BIN
ground/gcs/src/plugins/logging/images/stop.png
Normal file
BIN
ground/gcs/src/plugins/logging/images/stop.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 970 B |
@ -24,3 +24,5 @@ OTHER_FILES += LoggingGadget.pluginspec
|
||||
|
||||
FORMS += logging.ui
|
||||
|
||||
RESOURCES += \
|
||||
res.qrc
|
||||
|
@ -7,75 +7,156 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>439</width>
|
||||
<height>122</height>
|
||||
<height>120</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>100</horstretch>
|
||||
<verstretch>80</verstretch>
|
||||
<horstretch>1</horstretch>
|
||||
<verstretch>1</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>80</height>
|
||||
<height>118</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,1">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="spacing">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,0,0,0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,0,0,2,0,0,2,0,0">
|
||||
<property name="spacing">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetNoConstraint</enum>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QPushButton" name="playButton">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>30</width>
|
||||
<height>0</height>
|
||||
<width>39</width>
|
||||
<height>30</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Play</string>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>39</width>
|
||||
<height>30</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::NoFocus</enum>
|
||||
</property>
|
||||
<property name="autoFillBackground">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../notify/res.qrc">
|
||||
<normaloff>:/notify/images/play.png</normaloff>:/notify/images/play.png</iconset>
|
||||
<iconset resource="res.qrc">
|
||||
<normaloff>:/logging/images/play.png</normaloff>:/logging/images/play.png</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>16</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pauseButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>30</width>
|
||||
<height>0</height>
|
||||
<width>39</width>
|
||||
<height>30</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true">Pause</string>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>39</width>
|
||||
<height>30</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../notify/res.qrc">
|
||||
<normaloff>:/notify/images/stop.png</normaloff>:/notify/images/stop.png</iconset>
|
||||
<iconset resource="res.qrc">
|
||||
<normaloff>:/logging/images/pause.png</normaloff>:/logging/images/pause.png</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>16</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_5">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>4</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="stopButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>39</width>
|
||||
<height>30</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>39</width>
|
||||
<height>30</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="res.qrc">
|
||||
<normaloff>:/logging/images/stop.png</normaloff>:/logging/images/stop.png</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>16</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -92,6 +173,54 @@
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Playback speed:</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDoubleSpinBox" name="playbackSpeed">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>0.100000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>10.000000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.100000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>1.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
@ -101,6 +230,12 @@
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="statusLabel">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>65</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>75</weight>
|
||||
@ -115,29 +250,50 @@
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<widget class="QSlider" name="playbackPosition">
|
||||
<property name="tracking">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_8">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<widget class="QLabel" name="startTimeLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Playback speed:</string>
|
||||
<string/>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDoubleSpinBox" name="playbackSpeed">
|
||||
<property name="maximum">
|
||||
<double>10.000000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.100000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>1.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
@ -149,6 +305,63 @@
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="positionTimestampLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="endTimeLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
@ -161,7 +374,7 @@
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
<height>5</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
@ -169,7 +382,7 @@
|
||||
</layout>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="../notify/res.qrc"/>
|
||||
<include location="res.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
@ -1,13 +1,14 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
*
|
||||
* @file GCSControlgadgetwidget.cpp
|
||||
* @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
|
||||
* @file logginggadgetwidget.cpp
|
||||
* @author The LibrePilot Project, http://www.librepilot.org Copyright (C) 2018.
|
||||
* The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
|
||||
* @addtogroup GCSPlugins GCS Plugins
|
||||
* @{
|
||||
* @addtogroup GCSControlGadgetPlugin GCSControl Gadget Plugin
|
||||
* @addtogroup LoggingGadgetPlugin Logging Gadget Plugin
|
||||
* @{
|
||||
* @brief A gadget to control the UAV, either from the keyboard or a joystick
|
||||
* @brief A gadget to control playback of a GCS log.
|
||||
*****************************************************************************/
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@ -40,6 +41,14 @@ LoggingGadgetWidget::LoggingGadgetWidget(QWidget *parent) : QWidget(parent), log
|
||||
|
||||
ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
|
||||
scpPlugin = pm->getObject<ScopeGadgetFactory>();
|
||||
|
||||
disableWidgets();
|
||||
|
||||
// Configure timer to delay application of slider position action for 200ms
|
||||
sliderActionDelay.setSingleShot(true);
|
||||
sliderActionDelay.setInterval(200);
|
||||
|
||||
connect(&sliderActionDelay, SIGNAL(timeout()), this, SLOT(sliderAction()));
|
||||
}
|
||||
|
||||
LoggingGadgetWidget::~LoggingGadgetWidget()
|
||||
@ -50,20 +59,82 @@ LoggingGadgetWidget::~LoggingGadgetWidget()
|
||||
void LoggingGadgetWidget::setPlugin(LoggingPlugin *p)
|
||||
{
|
||||
loggingPlugin = p;
|
||||
|
||||
connect(m_logging->playButton, &QPushButton::clicked, scpPlugin, &ScopeGadgetFactory::startPlotting);
|
||||
connect(m_logging->pauseButton, &QPushButton::clicked, scpPlugin, &ScopeGadgetFactory::stopPlotting);
|
||||
|
||||
LogFile *logFile = loggingPlugin->getLogfile();
|
||||
connect(m_logging->playButton, &QPushButton::clicked, logFile, &LogFile::resumeReplay);
|
||||
connect(m_logging->pauseButton, &QPushButton::clicked, logFile, &LogFile::pauseReplay);
|
||||
|
||||
// GUI elements to gadgetwidget functions
|
||||
connect(m_logging->playButton, &QPushButton::clicked, this, &LoggingGadgetWidget::playButtonAction);
|
||||
connect(m_logging->pauseButton, &QPushButton::clicked, this, &LoggingGadgetWidget::pauseButtonAction);
|
||||
connect(m_logging->stopButton, &QPushButton::clicked, this, &LoggingGadgetWidget::stopButtonAction);
|
||||
connect(m_logging->playbackPosition, &QSlider::valueChanged, this, &LoggingGadgetWidget::sliderMoved);
|
||||
|
||||
connect(m_logging->playbackSpeed, static_cast<void(QDoubleSpinBox::*) (double)>(&QDoubleSpinBox::valueChanged), logFile, &LogFile::setReplaySpeed);
|
||||
|
||||
connect(loggingPlugin, &LoggingPlugin::stateChanged, this, &LoggingGadgetWidget::stateChanged);
|
||||
// gadgetwidget functions to logfile actions
|
||||
connect(this, &LoggingGadgetWidget::resumeReplay, logFile, &LogFile::resumeReplay);
|
||||
connect(this, &LoggingGadgetWidget::pauseReplay, logFile, &LogFile::pauseReplay);
|
||||
connect(this, &LoggingGadgetWidget::pauseReplayAndResetPosition, logFile, &LogFile::pauseReplayAndResetPosition);
|
||||
|
||||
// gadgetwidget functions to scope actions
|
||||
connect(this, &LoggingGadgetWidget::resumeReplay, scpPlugin, &ScopeGadgetFactory::startPlotting);
|
||||
connect(this, &LoggingGadgetWidget::pauseReplay, scpPlugin, &ScopeGadgetFactory::stopPlotting);
|
||||
connect(this, &LoggingGadgetWidget::pauseReplayAndResetPosition, scpPlugin, &ScopeGadgetFactory::stopPlotting);
|
||||
|
||||
// Feedback from logfile to GUI
|
||||
connect(loggingPlugin, &LoggingPlugin::stateChanged, this, &LoggingGadgetWidget::stateChanged);
|
||||
connect(logFile, &LogFile::timesChanged, this, &LoggingGadgetWidget::setBeginAndEndTimes);
|
||||
connect(logFile, &LogFile::playbackPositionChanged, this, &LoggingGadgetWidget::setPlaybackPosition);
|
||||
connect(logFile, &LogFile::replayStarted, this, &LoggingGadgetWidget::enableWidgets);
|
||||
connect(logFile, &LogFile::replayFinished, this, &LoggingGadgetWidget::disableWidgets);
|
||||
connect(logFile, &LogFile::replayCompleted, this, &LoggingGadgetWidget::stopButtonAction);
|
||||
|
||||
// Feedback from logfile to scope
|
||||
connect(logFile, &LogFile::replayFinished, scpPlugin, &ScopeGadgetFactory::stopPlotting);
|
||||
|
||||
// Perform actions as if the plugin state has been changed
|
||||
stateChanged(loggingPlugin->getState());
|
||||
}
|
||||
|
||||
void LoggingGadgetWidget::playButtonAction()
|
||||
{
|
||||
ReplayState replayState = loggingPlugin->getLogfile()->getReplayState();
|
||||
|
||||
if (replayState != PLAYING) {
|
||||
emit resumeReplay(m_logging->playbackPosition->value());
|
||||
}
|
||||
|
||||
m_logging->playButton->setVisible(false);
|
||||
m_logging->pauseButton->setVisible(true);
|
||||
m_logging->stopButton->setEnabled(true);
|
||||
}
|
||||
|
||||
void LoggingGadgetWidget::pauseButtonAction()
|
||||
{
|
||||
ReplayState replayState = loggingPlugin->getLogfile()->getReplayState();
|
||||
|
||||
if (replayState == PLAYING) {
|
||||
emit pauseReplay();
|
||||
}
|
||||
|
||||
m_logging->playButton->setVisible(true);
|
||||
m_logging->pauseButton->setVisible(false);
|
||||
m_logging->stopButton->setEnabled(true);
|
||||
|
||||
m_logging->statusLabel->setText(tr("Paused"));
|
||||
}
|
||||
|
||||
void LoggingGadgetWidget::stopButtonAction()
|
||||
{
|
||||
emit pauseReplayAndResetPosition();
|
||||
|
||||
m_logging->playButton->setVisible(true);
|
||||
m_logging->pauseButton->setVisible(false);
|
||||
m_logging->stopButton->setEnabled(false);
|
||||
|
||||
setPlaybackPosition(0);
|
||||
|
||||
m_logging->statusLabel->setText(tr("Stopped"));
|
||||
}
|
||||
|
||||
void LoggingGadgetWidget::stateChanged(LoggingPlugin::State state)
|
||||
{
|
||||
QString status;
|
||||
@ -71,7 +142,8 @@ void LoggingGadgetWidget::stateChanged(LoggingPlugin::State state)
|
||||
|
||||
switch (state) {
|
||||
case LoggingPlugin::IDLE:
|
||||
status = tr("Idle");
|
||||
status = tr("Idle");
|
||||
setPlaybackPosition(0);
|
||||
break;
|
||||
case LoggingPlugin::LOGGING:
|
||||
status = tr("Logging");
|
||||
@ -84,10 +156,126 @@ void LoggingGadgetWidget::stateChanged(LoggingPlugin::State state)
|
||||
m_logging->statusLabel->setText(status);
|
||||
|
||||
bool playing = loggingPlugin->getLogfile()->isPlaying();
|
||||
m_logging->playButton->setEnabled(enabled && !playing);
|
||||
m_logging->pauseButton->setEnabled(enabled && playing);
|
||||
m_logging->playButton->setEnabled(enabled);
|
||||
m_logging->pauseButton->setEnabled(enabled);
|
||||
m_logging->stopButton->setEnabled(enabled && playing);
|
||||
|
||||
if (playing) {
|
||||
m_logging->playButton->setVisible(false);
|
||||
m_logging->pauseButton->setVisible(true);
|
||||
} else {
|
||||
m_logging->playButton->setVisible(true);
|
||||
m_logging->pauseButton->setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
void LoggingGadgetWidget::setBeginAndEndTimes(quint32 startTimeStamp, quint32 endTimeStamp)
|
||||
{
|
||||
int startSec, startMin, endSec, endMin;
|
||||
|
||||
startSec = (startTimeStamp / 1000) % 60;
|
||||
startMin = startTimeStamp / (60 * 1000);
|
||||
|
||||
endSec = (endTimeStamp / 1000) % 60;
|
||||
endMin = endTimeStamp / (60 * 1000);
|
||||
|
||||
// update start and end labels
|
||||
m_logging->startTimeLabel->setText(QString("%1:%2").arg(startMin, 2, 10, QChar('0')).arg(startSec, 2, 10, QChar('0')));
|
||||
m_logging->endTimeLabel->setText(QString("%1:%2").arg(endMin, 2, 10, QChar('0')).arg(endSec, 2, 10, QChar('0')));
|
||||
|
||||
// Update position bar
|
||||
m_logging->playbackPosition->setMinimum(startTimeStamp);
|
||||
m_logging->playbackPosition->setMaximum(endTimeStamp);
|
||||
|
||||
m_logging->playbackPosition->setSingleStep((endTimeStamp - startTimeStamp) / 100);
|
||||
m_logging->playbackPosition->setPageStep((endTimeStamp - startTimeStamp) / 10);
|
||||
}
|
||||
|
||||
void LoggingGadgetWidget::setPlaybackPosition(quint32 positionTimeStamp)
|
||||
{
|
||||
// Update position bar, but only if the user is not updating the slider position
|
||||
if (!m_logging->playbackPosition->isSliderDown() && !sliderActionDelay.isActive()) {
|
||||
// Block signals during slider position update:
|
||||
m_logging->playbackPosition->blockSignals(true);
|
||||
m_logging->playbackPosition->setValue(positionTimeStamp);
|
||||
m_logging->playbackPosition->blockSignals(false);
|
||||
|
||||
// update position label
|
||||
updatePositionLabel(positionTimeStamp);
|
||||
}
|
||||
}
|
||||
|
||||
void LoggingGadgetWidget::enableWidgets()
|
||||
{
|
||||
ReplayState replayState = loggingPlugin->getLogfile()->getReplayState();
|
||||
|
||||
m_logging->playButton->setEnabled(true);
|
||||
m_logging->pauseButton->setEnabled(true);
|
||||
|
||||
switch (replayState) {
|
||||
case STOPPED:
|
||||
m_logging->stopButton->setEnabled(false);
|
||||
m_logging->playButton->setVisible(true);
|
||||
m_logging->pauseButton->setVisible(false);
|
||||
break;
|
||||
|
||||
case PLAYING:
|
||||
m_logging->stopButton->setEnabled(true);
|
||||
m_logging->playButton->setVisible(false);
|
||||
m_logging->pauseButton->setVisible(true);
|
||||
break;
|
||||
|
||||
case PAUSED:
|
||||
m_logging->stopButton->setEnabled(true);
|
||||
m_logging->playButton->setVisible(true);
|
||||
m_logging->pauseButton->setVisible(false);
|
||||
break;
|
||||
}
|
||||
m_logging->playbackPosition->setEnabled(true);
|
||||
}
|
||||
|
||||
void LoggingGadgetWidget::disableWidgets()
|
||||
{
|
||||
m_logging->playButton->setVisible(true);
|
||||
m_logging->pauseButton->setVisible(false);
|
||||
|
||||
m_logging->playButton->setEnabled(false);
|
||||
m_logging->pauseButton->setEnabled(false);
|
||||
m_logging->stopButton->setEnabled(false);
|
||||
|
||||
m_logging->playbackPosition->setEnabled(false);
|
||||
|
||||
// reset start and end labels
|
||||
m_logging->startTimeLabel->setText("");
|
||||
m_logging->endTimeLabel->setText("");
|
||||
}
|
||||
|
||||
void LoggingGadgetWidget::updatePositionLabel(quint32 positionTimeStamp)
|
||||
{
|
||||
// update position label -> MM:SS
|
||||
int sec = (positionTimeStamp / 1000) % 60;
|
||||
int min = positionTimeStamp / (60 * 1000);
|
||||
|
||||
m_logging->positionTimestampLabel->setText(QString("%1:%2").arg(min, 2, 10, QChar('0')).arg(sec, 2, 10, QChar('0')));
|
||||
}
|
||||
|
||||
void LoggingGadgetWidget::sliderMoved(int position)
|
||||
{
|
||||
// pause playback while the user is dragging the slider to change position
|
||||
emit pauseReplay();
|
||||
|
||||
updatePositionLabel(position);
|
||||
|
||||
// Start or restarts a time-out after which replay will resume from the new position.
|
||||
sliderActionDelay.start();
|
||||
}
|
||||
|
||||
void LoggingGadgetWidget::sliderAction()
|
||||
{
|
||||
emit resumeReplay(m_logging->playbackPosition->value());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @}
|
||||
* @}
|
||||
|
@ -1,13 +1,14 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
*
|
||||
* @file GCSControlgadgetwidget.h
|
||||
* @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
|
||||
* @file logginggadgetwidget.h
|
||||
* @author The LibrePilot Project, http://www.librepilot.org Copyright (C) 2018.
|
||||
* The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
|
||||
* @addtogroup GCSPlugins GCS Plugins
|
||||
* @{
|
||||
* @addtogroup GCSControlGadgetPlugin GCSControl Gadget Plugin
|
||||
* @addtogroup LoggingGadgetPlugin Logging Gadget Plugin
|
||||
* @{
|
||||
* @brief A place holder gadget plugin
|
||||
* @brief A gadget to control playback of a GCS log.
|
||||
*****************************************************************************/
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@ -37,6 +38,7 @@
|
||||
#include <QWidget>
|
||||
|
||||
class Ui_Logging;
|
||||
class QTimer;
|
||||
|
||||
class LoggingGadgetWidget : public QWidget {
|
||||
Q_OBJECT
|
||||
@ -48,15 +50,28 @@ public:
|
||||
|
||||
protected slots:
|
||||
void stateChanged(LoggingPlugin::State state);
|
||||
void setBeginAndEndTimes(quint32 startTimeStamp, quint32 endTimeStamp);
|
||||
void setPlaybackPosition(quint32 positionTimeStamp);
|
||||
void playButtonAction();
|
||||
void pauseButtonAction();
|
||||
void stopButtonAction();
|
||||
void enableWidgets();
|
||||
void disableWidgets();
|
||||
void sliderMoved(int);
|
||||
void sliderAction();
|
||||
|
||||
signals:
|
||||
void pause();
|
||||
void play();
|
||||
void resumeReplay(quint32 positionTimeStamp);
|
||||
void pauseReplay();
|
||||
void pauseReplayAndResetPosition();
|
||||
|
||||
private:
|
||||
Ui_Logging *m_logging;
|
||||
LoggingPlugin *loggingPlugin;
|
||||
ScopeGadgetFactory *scpPlugin;
|
||||
QTimer sliderActionDelay;
|
||||
|
||||
void updatePositionLabel(quint32 positionTimeStamp);
|
||||
};
|
||||
|
||||
#endif /* LoggingGADGETWIDGET_H_ */
|
||||
|
7
ground/gcs/src/plugins/logging/res.qrc
Normal file
7
ground/gcs/src/plugins/logging/res.qrc
Normal file
@ -0,0 +1,7 @@
|
||||
<RCC>
|
||||
<qresource prefix="/logging">
|
||||
<file>images/play.png</file>
|
||||
<file>images/pause.png</file>
|
||||
<file>images/stop.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
Loading…
x
Reference in New Issue
Block a user