diff --git a/ground/gcs/src/libs/utils/logfile.cpp b/ground/gcs/src/libs/utils/logfile.cpp index 95b400d68..e1e0ece21 100644 --- a/ground/gcs/src/libs/utils/logfile.cpp +++ b/ground/gcs/src/libs/utils/logfile.cpp @@ -26,6 +26,8 @@ #include #include +#include +#include // DEBUG: to display the thread ID LogFile::LogFile(QObject *parent) : QIODevice(parent), m_timer(this), @@ -34,9 +36,12 @@ LogFile::LogFile(QObject *parent) : QIODevice(parent), m_lastPlayed(0), m_timeOffset(0), m_playbackSpeed(1.0), - paused(false), + m_replayStatus(STOPPED), 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); } @@ -137,14 +142,58 @@ 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_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) { 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 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); // read data size @@ -178,6 +227,10 @@ void LogFile::timerFired() 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 if (m_file.bytesAvailable() < (qint64)sizeof(m_nextTimeStamp)) { qDebug() << "LogFile - end of log file reached"; @@ -196,7 +249,7 @@ void LogFile::timerFired() } 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"; @@ -209,8 +262,28 @@ 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 correctly controlling the timer. + * + */ 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()) { return false; } @@ -221,7 +294,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,50 +307,312 @@ bool LogFile::startReplay() m_timer.setInterval(10); m_timer.start(); - paused = false; + m_replayStatus = 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)) { + qDebug() << "stopReplay(): start of function, current Thread ID is: " << QThread::currentThreadId(); + + if (!m_file.isOpen() || !m_timer.isActive()) { return false; } qDebug() << "LogFile - stopReplay"; m_timer.stop(); - paused = false; + m_replayStatus = STOPPED; emit replayFinished(); 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() { + 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()) { return false; } qDebug() << "LogFile - pauseReplay"; m_timer.stop(); - paused = true; + m_replayStatus = PAUSED; // hack to notify UI that replay paused emit replayStarted(); return true; } +/** + * SLOT: resumeReplay() + * + * Resumes replay from the stored playback position + * + */ 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()) { return false; } qDebug() << "LogFile - resumeReplay"; m_timeOffset = m_myTime.elapsed(); m_timer.start(); - paused = false; + m_replayStatus = PLAYING; - // hack to notify UI that replay resumed + // Notify UI that replay has been resumed emit replayStarted(); 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')); +} + + diff --git a/ground/gcs/src/libs/utils/logfile.h b/ground/gcs/src/libs/utils/logfile.h index 3c3616e20..49530926e 100644 --- a/ground/gcs/src/libs/utils/logfile.h +++ b/ground/gcs/src/libs/utils/logfile.h @@ -34,6 +34,9 @@ #include #include #include +#include + +typedef enum { PLAYING, PAUSED, STOPPED } ReplayState; class QTCREATOR_UTILS_EXPORT LogFile : public QIODevice { Q_OBJECT @@ -75,32 +78,34 @@ public: m_providedTimeStamp = providedTimestamp; } + ReplayState getReplayStatus(); + public slots: - void setReplaySpeed(double val) - { - m_playbackSpeed = val; - qDebug() << "Playback speed is now" << m_playbackSpeed; - }; + void setReplaySpeed(double val); bool startReplay(); bool stopReplay(); bool pauseReplay(); bool resumeReplay(); + void resumeReplayFrom(quint32); + void restartReplay(); + void haltReplay(); protected slots: void timerFired(); signals: - void readReady(); void replayStarted(); void replayFinished(); + void replayPosition(quint32); + void updateBeginAndEndtimes(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 +113,18 @@ protected: int m_timeOffset; double m_playbackSpeed; - bool paused; + ReplayState m_replayStatus; private: bool m_useProvidedTimeStamp; qint32 m_providedTimeStamp; + quint32 m_beginTimeStamp; + quint32 m_endTimeStamp; + quint32 m_timer_tick; + QVector m_timeStamps; + QVector m_timeStampPositions; + + bool buildIndex(); }; #endif // LOGFILE_H diff --git a/ground/gcs/src/plugins/logging/logging.ui b/ground/gcs/src/plugins/logging/logging.ui index 05bad61aa..81a3e64db 100644 --- a/ground/gcs/src/plugins/logging/logging.ui +++ b/ground/gcs/src/plugins/logging/logging.ui @@ -7,19 +7,19 @@ 0 0 439 - 122 + 150 - 100 - 80 + 1 + 1 100 - 80 + 150 @@ -27,9 +27,9 @@ - + - + QLayout::SetNoConstraint @@ -71,7 +71,7 @@ - Pause + Pause @@ -79,6 +79,29 @@ + + + + + 0 + 0 + + + + + 30 + 0 + + + + Stop + + + + :/notify/images/delete.png:/notify/images/delete.png + + + @@ -125,11 +148,17 @@ + + 1 + + + 0.10000000000000 + 10.000000000000000 - 0.100000000000000 + 0.10000000000000 1.000000000000000 @@ -151,6 +180,106 @@ + + + + true + + + Qt::Horizontal + + + false + + + false + + + QSlider::TicksBothSides + + + 5 + + + + + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + diff --git a/ground/gcs/src/plugins/logging/logginggadgetwidget.cpp b/ground/gcs/src/plugins/logging/logginggadgetwidget.cpp index 659a65b30..75a5fc2e2 100644 --- a/ground/gcs/src/plugins/logging/logginggadgetwidget.cpp +++ b/ground/gcs/src/plugins/logging/logginggadgetwidget.cpp @@ -40,6 +40,15 @@ LoggingGadgetWidget::LoggingGadgetWidget(QWidget *parent) : QWidget(parent), log ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance(); scpPlugin = pm->getObject(); + + disableButtons(); + + sliderActionDelay.setSingleShot(true); + sliderActionDelay.setInterval(200); // Delay for 200 ms + + connect(&sliderActionDelay, SIGNAL(timeout()), this, SLOT(sendResumeReplayFrom())); + + m_storedPosition = 0; } LoggingGadgetWidget::~LoggingGadgetWidget() @@ -51,6 +60,31 @@ void LoggingGadgetWidget::setPlugin(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->pauseButton, &QPushButton::clicked, scpPlugin, &ScopeGadgetFactory::stopPlotting); @@ -64,6 +98,46 @@ void LoggingGadgetWidget::setPlugin(LoggingPlugin *p) 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) { QString status; @@ -86,8 +160,122 @@ void LoggingGadgetWidget::stateChanged(LoggingPlugin::State state) bool playing = loggingPlugin->getLogfile()->isPlaying(); m_logging->playButton->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; +} + + /** * @} * @} diff --git a/ground/gcs/src/plugins/logging/logginggadgetwidget.h b/ground/gcs/src/plugins/logging/logginggadgetwidget.h index 60b268e21..28c900915 100644 --- a/ground/gcs/src/plugins/logging/logginggadgetwidget.h +++ b/ground/gcs/src/plugins/logging/logginggadgetwidget.h @@ -37,6 +37,7 @@ #include class Ui_Logging; +class QTimer; class LoggingGadgetWidget : public QWidget { Q_OBJECT @@ -48,15 +49,32 @@ public: protected slots: 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: - void pause(); - void play(); + void startReplay(); + void stopReplay(); + void pauseReplay(); + void resumeReplay(); + void resumeReplayFrom(quint32 positionTimeStamp); private: Ui_Logging *m_logging; LoggingPlugin *loggingPlugin; ScopeGadgetFactory *scpPlugin; + QTimer sliderActionDelay; + quint32 m_storedPosition; + + void updatePositionLabel(quint32 positionTimeStamp); + }; #endif /* LoggingGADGETWIDGET_H_ */