1
0
mirror of https://bitbucket.org/librepilot/librepilot.git synced 2025-01-30 15:52:12 +01:00

LP-597 Progress bar for GCS log replay - add progress bar and a stop button

- Added stop button:
  - start: starts from 0 position after stop signal, resumes from current position after pause signal.
  - pause: freezes at current position, also freezes scopes.
  - stop: stops replay and resets start position to 0 (does not close the logfile)
- Creates an index of the timestamp positions in the logfile for quick seeking to the target position in the log.
- Start, End and current position timestamps are visible in the GUI below the position slider.
- Determine replay position by moving the position slider
- Update position label while changing position bar
- Speed widget: lowest multiplier is now 0.1 instead of 0.
  Only set one decimal of precision.
  More decimals seem useless at this time.
This commit is contained in:
Jan NIJS 2017-01-22 14:21:38 +01:00
parent a4c0bcfb1a
commit c53e99ee41
5 changed files with 714 additions and 30 deletions

View File

@ -26,6 +26,8 @@
#include <QDebug> #include <QDebug>
#include <QtGlobal> #include <QtGlobal>
#include <QDataStream>
#include <QThread> // DEBUG: to display the thread ID
LogFile::LogFile(QObject *parent) : QIODevice(parent), LogFile::LogFile(QObject *parent) : QIODevice(parent),
m_timer(this), m_timer(this),
@ -34,9 +36,12 @@ LogFile::LogFile(QObject *parent) : QIODevice(parent),
m_lastPlayed(0), m_lastPlayed(0),
m_timeOffset(0), m_timeOffset(0),
m_playbackSpeed(1.0), m_playbackSpeed(1.0),
paused(false), m_replayStatus(STOPPED),
m_useProvidedTimeStamp(false), m_useProvidedTimeStamp(false),
m_providedTimeStamp(0) m_providedTimeStamp(0),
m_beginTimeStamp(0),
m_endTimeStamp(0),
m_timer_tick(0)
{ {
connect(&m_timer, &QTimer::timeout, this, &LogFile::timerFired); connect(&m_timer, &QTimer::timeout, this, &LogFile::timerFired);
} }
@ -137,14 +142,58 @@ qint64 LogFile::bytesAvailable() const
return len; return len;
} }
/**
timerFired()
This function is called at a 10 ms interval to fill the replay buffers.
*/
void LogFile::timerFired() void LogFile::timerFired()
{ {
if (m_replayStatus != PLAYING) {
return;
}
m_timer_tick++;
if ( m_timer_tick % 100 == 0 ) {
qDebug() << "----------------------------------------------------------";
qDebug() << "LogFile::timerFired() -> Tick = " << m_timer_tick;
}
if (m_file.bytesAvailable() > 4) { if (m_file.bytesAvailable() > 4) {
int time; int time;
time = m_myTime.elapsed(); 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 window. All samples that fit in the window
are replayed. The window is about the size of the timer interval: 10 ms.
Description of used variables:
time : time passed since start of playback (in ms) - current
m_timeOffset : time passed since start of playback (in ms) - when timerFired() was previously run
m_lastPlayed : next log timestamp to advance to (in ms)
m_nextTimeStamp : timestamp of most recently read log entry (in ms)
m_playbackSpeed : 1 .. 10 replay speedup factor
*/
while ( m_nextTimeStamp < (m_lastPlayed + (double)(time - m_timeOffset) * m_playbackSpeed) ) {
// if ( m_timer_tick % 100 == 0 ) {
// if ( true ) {
// qDebug() << "LogFile::timerFired() -> m_lastPlayed = " << m_lastPlayed;
// qDebug() << "LogFile::timerFired() -> m_nextTimeStamp = " << m_nextTimeStamp;
// qDebug() << "LogFile::timerFired() -> time = " << time;
// qDebug() << "LogFile::timerFired() -> m_timeOffset = " << m_timeOffset;
// qDebug() << "---";
// qDebug() << "LogFile::timerFired() -> m_nextTimeStamp = " << m_nextTimeStamp;
// qDebug() << "LogFile::timerFired() -> (m_lastPlayed + (double)(time - m_timeOffset) * m_playbackSpeed) = " << (m_lastPlayed + (double)(time - m_timeOffset) * m_playbackSpeed);
// qDebug() << "---";
// }
// advance the replay window for the next time period
m_lastPlayed += ((double)(time - m_timeOffset) * m_playbackSpeed); m_lastPlayed += ((double)(time - m_timeOffset) * m_playbackSpeed);
// read data size // read data size
@ -178,6 +227,10 @@ void LogFile::timerFired()
emit readyRead(); emit readyRead();
// rate-limit slider bar position updates to 10 updates per second
if (m_timer_tick % 10 == 0) {
emit replayPosition(m_nextTimeStamp);
}
// read next timestamp // read next timestamp
if (m_file.bytesAvailable() < (qint64)sizeof(m_nextTimeStamp)) { if (m_file.bytesAvailable() < (qint64)sizeof(m_nextTimeStamp)) {
qDebug() << "LogFile - end of log file reached"; qDebug() << "LogFile - end of log file reached";
@ -196,7 +249,7 @@ void LogFile::timerFired()
} }
m_timeOffset = time; m_timeOffset = time;
time = m_myTime.elapsed(); time = m_myTime.elapsed(); // number of milliseconds since start of playback
} }
} else { } else {
qDebug() << "LogFile - end of log file reached"; qDebug() << "LogFile - end of log file reached";
@ -209,8 +262,28 @@ bool LogFile::isPlaying() const
return m_file.isOpen() && m_timer.isActive(); 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 correctly controlling the timer.
*
*/
bool LogFile::startReplay() bool LogFile::startReplay()
{ {
qDebug() << "startReplay(): start of function, current Thread ID is: " << QThread::currentThreadId();
// Walk through logfile and create timestamp index
// Don't start replay if there was a problem indexing the logfile.
if (!buildIndex()) {
return false;
}
m_timer_tick = 0;
if (!m_file.isOpen() || m_timer.isActive()) { if (!m_file.isOpen() || m_timer.isActive()) {
return false; return false;
} }
@ -221,7 +294,9 @@ bool LogFile::startReplay()
m_lastPlayed = 0; m_lastPlayed = 0;
m_previousTimeStamp = 0; m_previousTimeStamp = 0;
m_nextTimeStamp = 0; m_nextTimeStamp = 0;
m_mutex.lock();
m_dataBuffer.clear(); m_dataBuffer.clear();
m_mutex.unlock();
// read next timestamp // read next timestamp
if (m_file.bytesAvailable() < (qint64)sizeof(m_nextTimeStamp)) { if (m_file.bytesAvailable() < (qint64)sizeof(m_nextTimeStamp)) {
@ -232,50 +307,312 @@ bool LogFile::startReplay()
m_timer.setInterval(10); m_timer.setInterval(10);
m_timer.start(); m_timer.start();
paused = false; m_replayStatus = PLAYING;
emit replayStarted(); emit replayStarted();
return true; 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() bool LogFile::stopReplay()
{ {
if (!m_file.isOpen() || !(m_timer.isActive() || paused)) { qDebug() << "stopReplay(): start of function, current Thread ID is: " << QThread::currentThreadId();
if (!m_file.isOpen() || !m_timer.isActive()) {
return false; return false;
} }
qDebug() << "LogFile - stopReplay"; qDebug() << "LogFile - stopReplay";
m_timer.stop(); m_timer.stop();
paused = false; m_replayStatus = STOPPED;
emit replayFinished(); emit replayFinished();
return true; return true;
} }
/**
* SLOT: restartReplay()
*
* This function starts replay from the begining of the currently opened logfile.
*
*/
void LogFile::restartReplay()
{
qDebug() << "restartReplay(): start of function, current Thread ID is: " << QThread::currentThreadId();
resumeReplayFrom(0);
qDebug() << "restartReplay(): end of function, current Thread ID is: " << QThread::currentThreadId();
}
/**
* SLOT: haltReplay()
*
* Stops replay without storing the current playback position
*
*/
void LogFile::haltReplay()
{
qDebug() << "haltReplay(): start of function, current Thread ID is: " << QThread::currentThreadId();
qDebug() << "haltReplay() time = m_myTime.elapsed() = " << m_myTime.elapsed();
qDebug() << "haltReplay() m_timeOffset = " << m_timeOffset;
qDebug() << "haltReplay() m_nextTimeStamp = " << m_nextTimeStamp;
qDebug() << "haltReplay() m_lastPlayed = " << m_lastPlayed;
m_replayStatus = STOPPED;
qDebug() << "haltReplay(): end of function, current Thread ID is: " << QThread::currentThreadId();
}
/**
* SLOT: pauseReplay()
*
* Pauses replay while storing the current playback position
*
*/
bool LogFile::pauseReplay() bool LogFile::pauseReplay()
{ {
qDebug() << "pauseReplay(): start of function, current Thread ID is: " << QThread::currentThreadId();
qDebug() << "pauseReplay() time = m_myTime.elapsed() = " << m_myTime.elapsed();
qDebug() << "pauseReplay() m_timeOffset = " << m_timeOffset;
qDebug() << "pauseReplay() m_nextTimeStamp = " << m_nextTimeStamp;
qDebug() << "pauseReplay() m_lastPlayed = " << m_lastPlayed;
if (!m_timer.isActive()) { if (!m_timer.isActive()) {
return false; return false;
} }
qDebug() << "LogFile - pauseReplay"; qDebug() << "LogFile - pauseReplay";
m_timer.stop(); m_timer.stop();
paused = true; m_replayStatus = PAUSED;
// hack to notify UI that replay paused // hack to notify UI that replay paused
emit replayStarted(); emit replayStarted();
return true; return true;
} }
/**
* SLOT: resumeReplay()
*
* Resumes replay from the stored playback position
*
*/
bool LogFile::resumeReplay() bool LogFile::resumeReplay()
{ {
qDebug() << "resumeReplay(): start of function, current Thread ID is: " << QThread::currentThreadId();
m_mutex.lock();
m_dataBuffer.clear();
m_mutex.unlock();
m_file.seek(0);
for (int i = 0; i < m_timeStamps.size(); ++i) {
if (m_timeStamps.at(i) >= m_lastPlayed) {
m_file.seek(m_timeStampPositions.at(i));
break;
}
}
m_file.read((char *)&m_nextTimeStamp, sizeof(m_nextTimeStamp));
m_myTime.restart();
m_myTime = m_myTime.addMSecs(-m_timeOffset); // Set startpoint this far back in time.
qDebug() << "resumeReplay() time = m_myTime.elapsed() = " << m_myTime.elapsed();
qDebug() << "resumeReplay() m_timeOffset = " << m_timeOffset;
qDebug() << "resumeReplay() m_nextTimeStamp = " << m_nextTimeStamp;
qDebug() << "resumeReplay() m_lastPlayed = " << m_lastPlayed;
qDebug() << "resumeReplay(): end of function, current Thread ID is: " << QThread::currentThreadId();
if (m_timer.isActive()) { if (m_timer.isActive()) {
return false; return false;
} }
qDebug() << "LogFile - resumeReplay"; qDebug() << "LogFile - resumeReplay";
m_timeOffset = m_myTime.elapsed(); m_timeOffset = m_myTime.elapsed();
m_timer.start(); m_timer.start();
paused = false; m_replayStatus = PLAYING;
// hack to notify UI that replay resumed // Notify UI that replay has been resumed
emit replayStarted(); emit replayStarted();
return true; return true;
} }
/**
* SLOT: resumeReplayFrom()
*
* Resumes replay from the given position
*
*/
void LogFile::resumeReplayFrom(quint32 desiredPosition)
{
qDebug() << "resumeReplayFrom(): start of function, current Thread ID is: " << QThread::currentThreadId();
m_mutex.lock();
m_dataBuffer.clear();
m_mutex.unlock();
m_file.seek(0);
qint32 i;
for (i = 0; i < m_timeStamps.size(); ++i) {
if (m_timeStamps.at(i) >= desiredPosition) {
m_file.seek(m_timeStampPositions.at(i));
m_lastPlayed = m_timeStamps.at(i);
break;
}
}
m_file.read((char *)&m_nextTimeStamp, sizeof(m_nextTimeStamp));
if (m_nextTimeStamp != m_timeStamps.at(i)) {
qDebug() << "resumeReplayFrom() m_nextTimeStamp != m_timeStamps.at(i) -> " << m_nextTimeStamp << " != " << m_timeStamps.at(i);
}
// m_timeOffset = (m_lastPlayed - m_nextTimeStamp) / m_playbackSpeed;
m_timeOffset = 0;
m_myTime.restart();
// m_myTime = m_myTime.addMSecs(-m_timeOffset); // Set startpoint this far back in time.
// TODO: The above line is a possible memory leak. I'm not sure how to handle this correctly.
qDebug() << "resumeReplayFrom() time = m_myTime.elapsed() = " << m_myTime.elapsed();
qDebug() << "resumeReplayFrom() m_timeOffset = " << m_timeOffset;
qDebug() << "resumeReplayFrom() m_nextTimeStamp = " << m_nextTimeStamp;
qDebug() << "resumeReplayFrom() m_lastPlayed = " << m_lastPlayed;
m_replayStatus = PLAYING;
emit replayStarted();
qDebug() << "resumeReplayFrom(): end of function, current Thread ID is: " << QThread::currentThreadId();
}
/**
* FUNCTION: getReplayStatus()
*
* Returns the current replay status.
*
*/
ReplayState LogFile::getReplayStatus()
{
return m_replayStatus;
}
/**
* FUNCTION: buildIndex()
*
* Walk through the opened logfile and sets the start and end 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;
QByteArray arr = m_file.readAll();
totalSize = arr.size();
QDataStream dataStream(&arr, QIODevice::ReadOnly);
// set the start timestamp
if (totalSize - readPointer >= 4) {
dataStream.readRawData((char *)&timeStamp, 4);
m_timeStamps.append(timeStamp);
m_timeStampPositions.append(readPointer);
qDebug() << "LogFile::buildIndex() element index = " << index << " \t-> timestamp = " << timeStamp << " \t-> bytes in file = " << readPointer;
readPointer += 4;
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)) {
qDebug() << "Error: Logfile corrupted! Unexpected end of file";
return false;
}
// Read the dataSize field
dataStream.readRawData((char *)&dataSize, sizeof(dataSize));
readPointer += sizeof(dataSize);
if (dataSize < 1 || dataSize > (1024 * 1024)) {
qDebug() << "Error: Logfile corrupted! Unlikely packet size: " << dataSize << "\n";
return false;
}
// Check if there are enough bytes remaining
if (totalSize - readPointer < dataSize) {
qDebug() << "Error: 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 >= 4) {
dataStream.readRawData((char *)&timeStamp, 4);
qDebug() << "LogFile::buildIndex() element index = " << index << " \t-> timestamp = " << timeStamp << " \t-> bytes in file = " << readPointer;
// some validity checks
if (timeStamp < m_endTimeStamp // logfile goes back in time
|| (timeStamp - m_endTimeStamp) > (60 * 60 * 1000)) { // gap of more than 60 minutes)
qDebug() << "Error: Logfile corrupted! Unlikely timestamp " << timeStamp << " after " << m_endTimeStamp;
// return false;
}
m_timeStamps.append(timeStamp);
m_timeStampPositions.append(readPointer);
readPointer += 4;
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;
}
}
qDebug() << "buildIndex() -> first timestamp in log = " << m_beginTimeStamp;
qDebug() << "buildIndex() -> last timestamp in log = " << m_endTimeStamp;
emit updateBeginAndEndtimes(m_beginTimeStamp, m_endTimeStamp);
// reset the read pointer to the start of the file
m_file.seek(0);
return true;
}
/**
* FUNCTION: setReplaySpeed()
*
* Update the replay speed.
*
* FIXME: currently, changing the replay speed, while skipping through the logfile
* with the position bar causes position alignment to be lost.
*
*/
void LogFile::setReplaySpeed(double val)
{
m_playbackSpeed = val;
qDebug() << "Playback speed is now " << QString("%1").arg(m_playbackSpeed, 4, 'f', 2, QChar('0'));
}

View File

@ -34,6 +34,9 @@
#include <QDebug> #include <QDebug>
#include <QBuffer> #include <QBuffer>
#include <QFile> #include <QFile>
#include <QVector>
typedef enum { PLAYING, PAUSED, STOPPED } ReplayState;
class QTCREATOR_UTILS_EXPORT LogFile : public QIODevice { class QTCREATOR_UTILS_EXPORT LogFile : public QIODevice {
Q_OBJECT Q_OBJECT
@ -75,32 +78,34 @@ public:
m_providedTimeStamp = providedTimestamp; m_providedTimeStamp = providedTimestamp;
} }
ReplayState getReplayStatus();
public slots: public slots:
void setReplaySpeed(double val) void setReplaySpeed(double val);
{
m_playbackSpeed = val;
qDebug() << "Playback speed is now" << m_playbackSpeed;
};
bool startReplay(); bool startReplay();
bool stopReplay(); bool stopReplay();
bool pauseReplay(); bool pauseReplay();
bool resumeReplay(); bool resumeReplay();
void resumeReplayFrom(quint32);
void restartReplay();
void haltReplay();
protected slots: protected slots:
void timerFired(); void timerFired();
signals: signals:
void readReady();
void replayStarted(); void replayStarted();
void replayFinished(); void replayFinished();
void replayPosition(quint32);
void updateBeginAndEndtimes(quint32, quint32);
protected: protected:
QByteArray m_dataBuffer; QByteArray m_dataBuffer;
QTimer m_timer; QTimer m_timer;
QTime m_myTime; QTime m_myTime;
QFile m_file; QFile m_file;
qint32 m_previousTimeStamp; quint32 m_previousTimeStamp;
qint32 m_nextTimeStamp; quint32 m_nextTimeStamp;
double m_lastPlayed; double m_lastPlayed;
// QMutex wants to be mutable // QMutex wants to be mutable
// http://stackoverflow.com/questions/25521570/can-mutex-locking-function-be-marked-as-const // http://stackoverflow.com/questions/25521570/can-mutex-locking-function-be-marked-as-const
@ -108,11 +113,18 @@ protected:
int m_timeOffset; int m_timeOffset;
double m_playbackSpeed; double m_playbackSpeed;
bool paused; ReplayState m_replayStatus;
private: private:
bool m_useProvidedTimeStamp; bool m_useProvidedTimeStamp;
qint32 m_providedTimeStamp; qint32 m_providedTimeStamp;
quint32 m_beginTimeStamp;
quint32 m_endTimeStamp;
quint32 m_timer_tick;
QVector<quint32> m_timeStamps;
QVector<qint64> m_timeStampPositions;
bool buildIndex();
}; };
#endif // LOGFILE_H #endif // LOGFILE_H

View File

@ -7,19 +7,19 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>439</width> <width>439</width>
<height>122</height> <height>150</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred"> <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>100</horstretch> <horstretch>1</horstretch>
<verstretch>80</verstretch> <verstretch>1</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>100</width> <width>100</width>
<height>80</height> <height>150</height>
</size> </size>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -27,9 +27,9 @@
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_2"> <layout class="QVBoxLayout" name="verticalLayout_2">
<item> <item>
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0"> <layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,0,0">
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,0,0,0"> <layout class="QHBoxLayout" name="horizontalLayout" stretch="2,2,2,0,0,0">
<property name="sizeConstraint"> <property name="sizeConstraint">
<enum>QLayout::SetNoConstraint</enum> <enum>QLayout::SetNoConstraint</enum>
</property> </property>
@ -71,7 +71,7 @@
</size> </size>
</property> </property>
<property name="text"> <property name="text">
<string notr="true">Pause</string> <string>Pause</string>
</property> </property>
<property name="icon"> <property name="icon">
<iconset resource="../notify/res.qrc"> <iconset resource="../notify/res.qrc">
@ -79,6 +79,29 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QPushButton" name="stopButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>30</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Stop</string>
</property>
<property name="icon">
<iconset resource="../notify/res.qrc">
<normaloff>:/notify/images/delete.png</normaloff>:/notify/images/delete.png</iconset>
</property>
</widget>
</item>
<item> <item>
<spacer name="horizontalSpacer_2"> <spacer name="horizontalSpacer_2">
<property name="orientation"> <property name="orientation">
@ -125,11 +148,17 @@
</item> </item>
<item> <item>
<widget class="QDoubleSpinBox" name="playbackSpeed"> <widget class="QDoubleSpinBox" name="playbackSpeed">
<property name="decimals">
<number>1</number>
</property>
<property name="minimum">
<double>0.10000000000000</double>
</property>
<property name="maximum"> <property name="maximum">
<double>10.000000000000000</double> <double>10.000000000000000</double>
</property> </property>
<property name="singleStep"> <property name="singleStep">
<double>0.100000000000000</double> <double>0.10000000000000</double>
</property> </property>
<property name="value"> <property name="value">
<double>1.000000000000000</double> <double>1.000000000000000</double>
@ -151,6 +180,106 @@
</item> </item>
</layout> </layout>
</item> </item>
<item>
<widget class="QSlider" name="playBackPosition">
<property name="tracking">
<bool>true</bool>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="invertedAppearance">
<bool>false</bool>
</property>
<property name="invertedControls">
<bool>false</bool>
</property>
<property name="tickPosition">
<enum>QSlider::TicksBothSides</enum>
</property>
<property name="tickInterval">
<number>5</number>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_8">
<property name="topMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="startTimeLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<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="positionTimestampLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</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="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
</layout> </layout>
</item> </item>
<item> <item>

View File

@ -40,6 +40,15 @@ LoggingGadgetWidget::LoggingGadgetWidget(QWidget *parent) : QWidget(parent), log
ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance(); ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
scpPlugin = pm->getObject<ScopeGadgetFactory>(); scpPlugin = pm->getObject<ScopeGadgetFactory>();
disableButtons();
sliderActionDelay.setSingleShot(true);
sliderActionDelay.setInterval(200); // Delay for 200 ms
connect(&sliderActionDelay, SIGNAL(timeout()), this, SLOT(sendResumeReplayFrom()));
m_storedPosition = 0;
} }
LoggingGadgetWidget::~LoggingGadgetWidget() LoggingGadgetWidget::~LoggingGadgetWidget()
@ -51,6 +60,31 @@ void LoggingGadgetWidget::setPlugin(LoggingPlugin *p)
{ {
loggingPlugin = p; loggingPlugin = p;
connect(p->getLogfile(), SIGNAL(updateBeginAndEndtimes(quint32, quint32)), this, SLOT(updateBeginAndEndtimes(quint32, quint32)));
connect(p->getLogfile(), SIGNAL(replayPosition(quint32)), this, SLOT(replayPosition(quint32)));
connect(m_logging->playBackPosition, SIGNAL(valueChanged(int)), this, SLOT(sliderMoved(int)));
connect(this, SIGNAL(resumeReplayFrom(quint32)), p->getLogfile(), SLOT(resumeReplayFrom(quint32)));
connect(this, SIGNAL(startReplay()), p->getLogfile(), SLOT(restartReplay()));
connect(this, SIGNAL(stopReplay()), p->getLogfile(), SLOT(haltReplay()));
connect(this, SIGNAL(pauseReplay()), p->getLogfile(), SLOT(pauseReplay()));
connect(this, SIGNAL(resumeReplay()), p->getLogfile(), SLOT(resumeReplay()));
connect(this, SIGNAL(startReplay()), scpPlugin, SLOT(startPlotting()));
connect(this, SIGNAL(stopReplay()), scpPlugin, SLOT(stopPlotting()));
connect(this, SIGNAL(pauseReplay()), scpPlugin, SLOT(stopPlotting()));
connect(this, SIGNAL(resumeReplay()), scpPlugin, SLOT(startPlotting()));
connect(m_logging->playButton, SIGNAL(clicked()), this, SLOT(playButton()));
connect(m_logging->pauseButton, SIGNAL(clicked()), this, SLOT(pauseButton()));
connect(m_logging->stopButton, SIGNAL(clicked()), this, SLOT(stopButton()));
connect(p->getLogfile(), SIGNAL(replayStarted()), this, SLOT(enableButtons()));
connect(p->getLogfile(), SIGNAL(replayFinished()), this, SLOT(disableButtons()));
connect(p->getLogfile(), SIGNAL(replayFinished()), scpPlugin, SLOT(stopPlotting()));
connect(m_logging->playbackSpeed, SIGNAL(valueChanged(double)), p->getLogfile(), SLOT(setReplaySpeed(double)));
connect(m_logging->playButton, &QPushButton::clicked, scpPlugin, &ScopeGadgetFactory::startPlotting); connect(m_logging->playButton, &QPushButton::clicked, scpPlugin, &ScopeGadgetFactory::startPlotting);
connect(m_logging->pauseButton, &QPushButton::clicked, scpPlugin, &ScopeGadgetFactory::stopPlotting); connect(m_logging->pauseButton, &QPushButton::clicked, scpPlugin, &ScopeGadgetFactory::stopPlotting);
@ -64,6 +98,46 @@ void LoggingGadgetWidget::setPlugin(LoggingPlugin *p)
stateChanged(loggingPlugin->getState()); stateChanged(loggingPlugin->getState());
} }
void LoggingGadgetWidget::playButton()
{
ReplayState replayState = (loggingPlugin->getLogfile())->getReplayStatus();
if (replayState == STOPPED) {
emit startReplay();
} else if (replayState == PAUSED) {
emit resumeReplay();
}
m_logging->playButton->setEnabled(false);
m_logging->pauseButton->setEnabled(true);
m_logging->stopButton->setEnabled(true);
}
void LoggingGadgetWidget::pauseButton()
{
ReplayState replayState = (loggingPlugin->getLogfile())->getReplayStatus();
if (replayState == PLAYING) {
emit pauseReplay();
}
m_logging->playButton->setEnabled(true);
m_logging->pauseButton->setEnabled(false);
m_logging->stopButton->setEnabled(true);
}
void LoggingGadgetWidget::stopButton()
{
ReplayState replayState = (loggingPlugin->getLogfile())->getReplayStatus();
if (replayState != STOPPED) {
emit stopReplay();
}
m_logging->playButton->setEnabled(true);
m_logging->pauseButton->setEnabled(false);
m_logging->stopButton->setEnabled(false);
replayPosition(0);
}
void LoggingGadgetWidget::stateChanged(LoggingPlugin::State state) void LoggingGadgetWidget::stateChanged(LoggingPlugin::State state)
{ {
QString status; QString status;
@ -86,8 +160,122 @@ void LoggingGadgetWidget::stateChanged(LoggingPlugin::State state)
bool playing = loggingPlugin->getLogfile()->isPlaying(); bool playing = loggingPlugin->getLogfile()->isPlaying();
m_logging->playButton->setEnabled(enabled && !playing); m_logging->playButton->setEnabled(enabled && !playing);
m_logging->pauseButton->setEnabled(enabled && playing); m_logging->pauseButton->setEnabled(enabled && playing);
m_logging->stopButton->setEnabled(enabled && playing);
} }
void LoggingGadgetWidget::updateBeginAndEndtimes(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);
m_logging->playBackPosition->setTickInterval((endTimeStamp - startTimeStamp) / 10);
m_logging->playBackPosition->setTickPosition(QSlider::TicksBothSides);
}
void LoggingGadgetWidget::replayPosition(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 current position label
updatePositionLabel(positionTimeStamp);
}
}
void LoggingGadgetWidget::enableButtons()
{
ReplayState replayState = (loggingPlugin->getLogfile())->getReplayStatus();
switch (replayState)
{
case STOPPED:
m_logging->playButton->setEnabled(true);
m_logging->pauseButton->setEnabled(false);
m_logging->stopButton->setEnabled(false);
break;
case PLAYING:
m_logging->playButton->setEnabled(false);
m_logging->pauseButton->setEnabled(true);
m_logging->stopButton->setEnabled(true);
break;
case PAUSED:
m_logging->playButton->setEnabled(true);
m_logging->pauseButton->setEnabled(false);
m_logging->stopButton->setEnabled(true);
break;
}
m_logging->playBackPosition->setEnabled(true);
}
void LoggingGadgetWidget::disableButtons()
{
// m_logging->startTimeLabel->setText(QString(""));
// m_logging->endTimeLabel->setText(QString(""));
m_logging->playButton->setEnabled(false);
m_logging->pauseButton->setEnabled(false);
m_logging->stopButton->setEnabled(false);
m_logging->playBackPosition->setEnabled(false);
}
void LoggingGadgetWidget::sliderMoved(int position)
{
qDebug() << "sliderMoved(): start of function, stored position was: " << m_storedPosition;
m_storedPosition = position;
// pause
emit pauseButton();
updatePositionLabel(position);
// Start or restarts a time-out after which replay is resumed from the new position.
sliderActionDelay.start();
qDebug() << "sliderMoved(): end of function, stored position is now: " << m_storedPosition;
}
void LoggingGadgetWidget::updatePositionLabel(quint32 positionTimeStamp)
{
// update position timestamp label
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::sendResumeReplayFrom()
{
qDebug() << "sendResumeReplayFrom(): start of function, stored position is: " << m_storedPosition;
emit resumeReplayFrom(m_storedPosition);
emit resumeReplay();
qDebug() << "sendResumeReplayFrom(): end of function, stored position is: " << m_storedPosition;
}
/** /**
* @} * @}
* @} * @}

View File

@ -37,6 +37,7 @@
#include <QWidget> #include <QWidget>
class Ui_Logging; class Ui_Logging;
class QTimer;
class LoggingGadgetWidget : public QWidget { class LoggingGadgetWidget : public QWidget {
Q_OBJECT Q_OBJECT
@ -48,15 +49,32 @@ public:
protected slots: protected slots:
void stateChanged(LoggingPlugin::State state); void stateChanged(LoggingPlugin::State state);
void updateBeginAndEndtimes(quint32 startTimeStamp, quint32 endTimeStamp);
void replayPosition(quint32 positionTimeStamp);
void playButton();
void pauseButton();
void stopButton();
void enableButtons();
void disableButtons();
void sliderMoved(int);
void sendResumeReplayFrom();
signals: signals:
void pause(); void startReplay();
void play(); void stopReplay();
void pauseReplay();
void resumeReplay();
void resumeReplayFrom(quint32 positionTimeStamp);
private: private:
Ui_Logging *m_logging; Ui_Logging *m_logging;
LoggingPlugin *loggingPlugin; LoggingPlugin *loggingPlugin;
ScopeGadgetFactory *scpPlugin; ScopeGadgetFactory *scpPlugin;
QTimer sliderActionDelay;
quint32 m_storedPosition;
void updatePositionLabel(quint32 positionTimeStamp);
}; };
#endif /* LoggingGADGETWIDGET_H_ */ #endif /* LoggingGADGETWIDGET_H_ */