From c53e99ee416c8f749e229896e20f28beedc219e5 Mon Sep 17 00:00:00 2001 From: Jan NIJS Date: Sun, 22 Jan 2017 14:21:38 +0100 Subject: [PATCH 1/8] 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. --- ground/gcs/src/libs/utils/logfile.cpp | 359 +++++++++++++++++- ground/gcs/src/libs/utils/logfile.h | 30 +- ground/gcs/src/plugins/logging/logging.ui | 145 ++++++- .../plugins/logging/logginggadgetwidget.cpp | 188 +++++++++ .../src/plugins/logging/logginggadgetwidget.h | 22 +- 5 files changed, 714 insertions(+), 30 deletions(-) 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_ */ From ef3cb8bf4651a8ac8118f50522a3529d02757396 Mon Sep 17 00:00:00 2001 From: Jan NIJS Date: Sat, 28 Apr 2018 14:56:36 +0200 Subject: [PATCH 2/8] LP-597 Progress bar for GCS log replay - switch to double-function button for Play/Pause - remove separate play and pause buttons, combine these functions in one playPause button. - update icons & button texts - move resources (icons) to logging plugin's own images folder - explain why looking for the next timestamp after the last played one is useful. It causes the replay process to jump over parts of the logfile where no data has been recorded and jumps straight to the first timestamp after that part of the log where data is missing. --- ground/gcs/src/libs/utils/logfile.cpp | 231 +++++------------- ground/gcs/src/libs/utils/logfile.h | 15 +- .../gcs/src/plugins/logging/images/pause.png | Bin 0 -> 1342 bytes .../gcs/src/plugins/logging/images/play.png | Bin 0 -> 1443 bytes .../gcs/src/plugins/logging/images/stop.png | Bin 0 -> 970 bytes ground/gcs/src/plugins/logging/logging.pro | 2 + ground/gcs/src/plugins/logging/logging.ui | 139 +++++------ .../plugins/logging/logginggadgetwidget.cpp | 182 +++++++------- .../src/plugins/logging/logginggadgetwidget.h | 19 +- ground/gcs/src/plugins/logging/res.qrc | 7 + 10 files changed, 241 insertions(+), 354 deletions(-) create mode 100644 ground/gcs/src/plugins/logging/images/pause.png create mode 100644 ground/gcs/src/plugins/logging/images/play.png create mode 100644 ground/gcs/src/plugins/logging/images/stop.png create mode 100644 ground/gcs/src/plugins/logging/res.qrc diff --git a/ground/gcs/src/libs/utils/logfile.cpp b/ground/gcs/src/libs/utils/logfile.cpp index e1e0ece21..b7bf9eca1 100644 --- a/ground/gcs/src/libs/utils/logfile.cpp +++ b/ground/gcs/src/libs/utils/logfile.cpp @@ -154,46 +154,29 @@ 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(); /* - This code generates an advancing window. All samples that fit in the window + 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 : 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 + 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) ) { -// 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 @@ -229,7 +212,7 @@ void LogFile::timerFired() // rate-limit slider bar position updates to 10 updates per second if (m_timer_tick % 10 == 0) { - emit replayPosition(m_nextTimeStamp); + emit playbackPosition(m_nextTimeStamp); } // read next timestamp if (m_file.bytesAvailable() < (qint64)sizeof(m_nextTimeStamp)) { @@ -269,13 +252,11 @@ bool LogFile::isPlaying() const * 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. + * This is required for correct control of 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()) { @@ -325,8 +306,6 @@ bool LogFile::startReplay() */ bool LogFile::stopReplay() { - qDebug() << "stopReplay(): start of function, current Thread ID is: " << QThread::currentThreadId(); - if (!m_file.isOpen() || !m_timer.isActive()) { return false; } @@ -338,41 +317,59 @@ bool LogFile::stopReplay() return true; } - /** - * SLOT: restartReplay() + * SLOT: resumeReplay() * - * This function starts replay from the begining of the currently opened logfile. + * Resumes replay from the given position. + * If no position is given, resumes from the last position * */ -void LogFile::restartReplay() + +bool LogFile::resumeReplay(quint32 desiredPosition) { - qDebug() << "restartReplay(): start of function, current Thread ID is: " << QThread::currentThreadId(); + if (m_timer.isActive()) { + return false; + } - resumeReplayFrom(0); + // Clear the playout buffer: + m_mutex.lock(); + m_dataBuffer.clear(); + m_mutex.unlock(); - qDebug() << "restartReplay(): end of function, current Thread ID is: " << QThread::currentThreadId(); + 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) { + + m_file.seek(m_timeStampPositions.at(i)); + 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_replayStatus = PLAYING; + + m_timer.start(); + + // Notify UI that playback has resumed + emit replayStarted(); + return true; } -/** - * 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() * @@ -381,13 +378,6 @@ void LogFile::haltReplay() */ 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; } @@ -401,99 +391,27 @@ bool LogFile::pauseReplay() } /** - * SLOT: resumeReplay() + * SLOT: pauseAndResetPosition() * - * Resumes replay from the stored playback position + * Pauses replay and resets the playback position to the start of the logfile * */ -bool LogFile::resumeReplay() +bool LogFile::pauseAndResetPosition() { - 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_file.isOpen() || !m_timer.isActive()) { return false; } - qDebug() << "LogFile - resumeReplay"; - m_timeOffset = m_myTime.elapsed(); - m_timer.start(); - m_replayStatus = PLAYING; + m_timer.stop(); + m_replayStatus = STOPPED; + + m_timeOffset = 0; + m_lastPlayed = m_timeStamps.at(0); + m_previousTimeStamp = 0; + m_nextTimeStamp = 0; - // 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() * @@ -532,7 +450,6 @@ bool LogFile::buildIndex() 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; @@ -569,13 +486,11 @@ bool LogFile::buildIndex() // 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); @@ -589,9 +504,6 @@ bool LogFile::buildIndex() } } - 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 @@ -599,20 +511,3 @@ bool LogFile::buildIndex() 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 49530926e..f36173e3e 100644 --- a/ground/gcs/src/libs/utils/logfile.h +++ b/ground/gcs/src/libs/utils/logfile.h @@ -81,14 +81,17 @@ public: ReplayState getReplayStatus(); public slots: - void setReplaySpeed(double val); + void setReplaySpeed(double val) + { + m_playbackSpeed = val; + qDebug() << "Playback speed is now" << m_playbackSpeed; + }; bool startReplay(); bool stopReplay(); + + bool resumeReplay(quint32); bool pauseReplay(); - bool resumeReplay(); - void resumeReplayFrom(quint32); - void restartReplay(); - void haltReplay(); + bool pauseAndResetPosition(); protected slots: void timerFired(); @@ -96,7 +99,7 @@ protected slots: signals: void replayStarted(); void replayFinished(); - void replayPosition(quint32); + void playbackPosition(quint32); void updateBeginAndEndtimes(quint32, quint32); protected: diff --git a/ground/gcs/src/plugins/logging/images/pause.png b/ground/gcs/src/plugins/logging/images/pause.png new file mode 100644 index 0000000000000000000000000000000000000000..cd13b6431a011156cccc73d75d759b6ae3f733aa GIT binary patch literal 1342 zcmV-E1;P4>P)a^VXfOESCXK%ad-pk?|0I>YWz!VL=Xy4tRA3nVH_6>e#!}|Wi$s5h7{ab#r zcLwLJ1T)3V07d&24c)!&-zV3v+~ly?Jve*&;=7-`b>^ivMf?;Ihv|R~hv+cJhB?~L znEmU2XPZ{9uW`X5OM6CeU6=Y7ZJcA5<%n_5m`KBIzUIF&iB)4O`id8Fw%MZisOzCL@ahv}OR} zL8jn*GC>0iu?#5yngSlRf~Hu8sf&zhUqBQ_ZDg}Cv$QFI`q(KMfDQ?L3b3UFW?@W5 zT#Tawrrj7rLoDyefMKBpi-wVFSpd{5Y!zWhu>#RzGJwQF$O#2N6M&k58h}PckKM4H z0g~v@9LrK$_KAfgNbA=z;3Mi%0!@_mC4-pRF&Id9NNz>6FM>9WOcV_i0O}VQh^-i6 zUxyT63j?YF8`^ji674nuh#h628wFX~Fw-V(4FDYhXdj;)hXM7)?@dE$>8z!0p`57= zTP$6>33l>=d_^iN~biuDl==1KD~ZS<-+wEP6VS0gO6`U6&b9t27{^6Xsd2V!(781H%FX<2;y! zm^2W&a!HR}P&Sb^E^XGab&fz##j2iJh%AKB5AsSz)WX6DwhW-UR*zb_4fE(_0E(%O zosd1jY6eo>3V^x<>1rM)oTC`XBFy6#D=~5jDiLld0oCx3Ma0n}G!1BIs~ONzOV!N%dH+?crp5%p>bvKN$g+|q`YN)U}GKU)@ap9Y|; z0nJA3Kg@hPAKE#-@hfp^KY9GHYt8r;py^tB@1FqW{WG z4)1=XSO56!(JgWEOm_7Wto&FvRJl7m07*qoM6N<$f{uGm AqyPW_ literal 0 HcmV?d00001 diff --git a/ground/gcs/src/plugins/logging/images/play.png b/ground/gcs/src/plugins/logging/images/play.png new file mode 100644 index 0000000000000000000000000000000000000000..4698d78f9368c6abb5248562c66f16470f4a6f88 GIT binary patch literal 1443 zcmV;U1zh@xP)IK6D>Gc{4zQ=)vgA2X`O*%8tbyW+t2=j$-04 zCJG|9cek6nciugB%fcY(!{7tg$T2E|`@&R_5kA^LL&9&-rf#_Xk~fk9(b0DQ*~`^HAr95AS>UQ{9f?Hk{>#7W-?=e>FLu6vc;Dg0cGRXF zwdq71;xHyK0prYw3ZjBnkE*iRT?}{Kx~rSG65K9e%;*t_2_j zf>Ay~RLCVKFaZXGAQZkJ_c@u*N)YeJB`24B3aEk-6_!4^G#|I)`E%#b{n`E8{Yhr! zJ6GGNR#8(`KFG@I6PJ)=2?IA^m=77{Bhox2D>AZT4C1FjDwskFY+za1zO*>oo&CB= z%o(0$tfG3RE55l(nj|syr*Q zX=^G)#xPS6GfTn!^f*9|Qvjh?Pm(3I{04(Drk;0^asyTXmAsoO(NIKHH?m!Unyj{< zx#`v_(1d9ETUA+A-BhBMz8NVAo)h$VbJB#W^s_#J4eIKv9;h~8b5pLhqA7P(`G(1d zT%zCvKjZPKDG)@b6lnB-sv=AjXhN*Dq5@IY$Sy#_DoT%IJiC@F^x`$ilBo%pm^D$M z9?41tKRp@isxF;Q$v6i*!Z9{{ZHHdHUk6~Q8>qSOry#}v8_F%ffK@!d2Yx^gxBjNe z0u|}I{?r6CEokn8$_6ifz0_5tTp{BhD38(OZ<{5q0aSEi!MOUW2~iMavvHb^f`TEd z2*>I19Bf9o1^~rFDRo))wLI1c4-8oaP7?7XPjTs^JRZVz{=-!u0#)5d;8Ygv1D$CP)b25Fq0S z9!Kynr0ZcQW9?$DTYY$YlgsPtlXu7C{q6ZobU48EJ;-~Y>p(;aZ3e*30RSkkgkl7F z4~a27@7$t`b3oN8FN}zAxVfPL`0B~Ij=WF74+kg<5&O=*cpT0JnAU$npXiSvy!3?owsH}vLz)aHujP@p=7AV7DVSqz`1HdAn7>f}V zb~JDeVw!*$lUdoI*Rli{21F9|)(O>eb;TjfZrA`L84M~gTYvz-iwJleQ}ApsT4G9@ zPRxc0z2-VC2FxBH$*D*T3`3$ZyBnOTPJP%+UKpsEsKCI2(t9-nPzH>rFU%qfaj_}s z>g$x$VCvx2mT@l^_@1&{(l$#kPNAU{4E`Nv(p{=K-+{n4{JlUuH|*5@QQQLzHU2y9^+o@d|*< z!is^G7_C87Zn-)cH8o)9FCQk2ubrkCYb_W6d(DeFVe9y;08|S#11&LHAlYtr0cR}27)GF$<$dqbUyja;`H0nv-d%lQ8OsrR0)O2LL0KiVQ74E&;J s;I0F{3(@`6Z+~Ew&DYb<%LF|90puB-;JShk3jhEB07*qoM6N<$f}dTciU0rr literal 0 HcmV?d00001 diff --git a/ground/gcs/src/plugins/logging/logging.pro b/ground/gcs/src/plugins/logging/logging.pro index 66c61a34b..4371817c8 100644 --- a/ground/gcs/src/plugins/logging/logging.pro +++ b/ground/gcs/src/plugins/logging/logging.pro @@ -24,3 +24,5 @@ OTHER_FILES += LoggingGadget.pluginspec FORMS += logging.ui +RESOURCES += \ + res.qrc diff --git a/ground/gcs/src/plugins/logging/logging.ui b/ground/gcs/src/plugins/logging/logging.ui index 81a3e64db..ed7dbbe6f 100644 --- a/ground/gcs/src/plugins/logging/logging.ui +++ b/ground/gcs/src/plugins/logging/logging.ui @@ -7,7 +7,7 @@ 0 0 439 - 150 + 118 @@ -19,22 +19,22 @@ 100 - 150 + 118 Form - + - + - + QLayout::SetNoConstraint - + 0 @@ -48,35 +48,18 @@ - Play + Play - - :/notify/images/play.png:/notify/images/play.png + + :/logging/images/play.png:/logging/images/play.png - - - - - - - 0 - 0 - - - + - 30 - 0 + 16 + 16 - - Pause - - - - :/notify/images/stop.png:/notify/images/stop.png - @@ -94,11 +77,17 @@ - Stop + Stop - - :/notify/images/delete.png:/notify/images/delete.png + + :/logging/images/stop.png:/logging/images/stop.png + + + + 16 + 16 + @@ -115,6 +104,45 @@ + + + + Playback speed: + + + + + + + 1 + + + 0.100000000000000 + + + 10.000000000000000 + + + 0.100000000000000 + + + 1.000000000000000 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + @@ -137,49 +165,6 @@ - - - - - - Playback speed: - - - - - - - 1 - - - 0.10000000000000 - - - 10.000000000000000 - - - 0.10000000000000 - - - 1.000000000000000 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - @@ -290,7 +275,7 @@ 20 - 40 + 5 @@ -298,7 +283,7 @@ - + diff --git a/ground/gcs/src/plugins/logging/logginggadgetwidget.cpp b/ground/gcs/src/plugins/logging/logginggadgetwidget.cpp index 75a5fc2e2..655db48f9 100644 --- a/ground/gcs/src/plugins/logging/logginggadgetwidget.cpp +++ b/ground/gcs/src/plugins/logging/logginggadgetwidget.cpp @@ -43,12 +43,11 @@ LoggingGadgetWidget::LoggingGadgetWidget(QWidget *parent) : QWidget(parent), log disableButtons(); + // Configure timer to delay application of slider position action for 200ms sliderActionDelay.setSingleShot(true); - sliderActionDelay.setInterval(200); // Delay for 200 ms + sliderActionDelay.setInterval(200); - connect(&sliderActionDelay, SIGNAL(timeout()), this, SLOT(sendResumeReplayFrom())); - - m_storedPosition = 0; + connect(&sliderActionDelay, SIGNAL(timeout()), this, SLOT(sliderAction())); } LoggingGadgetWidget::~LoggingGadgetWidget() @@ -59,83 +58,90 @@ LoggingGadgetWidget::~LoggingGadgetWidget() 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); - 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->playPauseButton, &QPushButton::clicked, this, &LoggingGadgetWidget::playPauseButtonAction); + connect(m_logging->stopButton, &QPushButton::clicked, this, &LoggingGadgetWidget::stopButtonAction); + connect(m_logging->playBackPosition, &QSlider::valueChanged, this, &LoggingGadgetWidget::sliderMoved); + connect(m_logging->playbackSpeed, static_cast(&QDoubleSpinBox::valueChanged), logFile, &LogFile::setReplaySpeed); + // gadgetwidget functions to logfile actions + connect(this, &LoggingGadgetWidget::resumeReplay, logFile, &LogFile::resumeReplay); + connect(this, &LoggingGadgetWidget::pauseReplay, logFile, &LogFile::pauseReplay); + connect(this, &LoggingGadgetWidget::pauseAndResetPosition, logFile, &LogFile::pauseAndResetPosition); + + // gadgetwidget functions to scope actions + connect(this, &LoggingGadgetWidget::resumeReplay, scpPlugin, &ScopeGadgetFactory::startPlotting); + connect(this, &LoggingGadgetWidget::pauseReplay, scpPlugin, &ScopeGadgetFactory::stopPlotting); + connect(this, &LoggingGadgetWidget::pauseAndResetPosition, scpPlugin, &ScopeGadgetFactory::stopPlotting); + + // Feedback from logfile to GUI connect(loggingPlugin, &LoggingPlugin::stateChanged, this, &LoggingGadgetWidget::stateChanged); + connect(logFile, &LogFile::updateBeginAndEndtimes, this, &LoggingGadgetWidget::updateBeginAndEndtimes); + connect(logFile, &LogFile::playbackPosition, this, &LoggingGadgetWidget::playbackPosition); + connect(logFile, &LogFile::replayStarted, this, &LoggingGadgetWidget::enableButtons); + connect(logFile, &LogFile::replayFinished, this, &LoggingGadgetWidget::disableButtons); + + // Feedback from logfile to scope + connect(logFile, &LogFile::replayFinished, scpPlugin, &ScopeGadgetFactory::stopPlotting); + 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); +/* + setPlayPauseButtonToPlay() + + Changes the appearance of the playPause button to the PLAY appearance. +*/ +void LoggingGadgetWidget::setPlayPauseButtonToPlay() +{ + m_logging->playPauseButton->setIcon(QIcon(":/logging/images/play.png")); + m_logging->playPauseButton->setText(tr(" Play")); } -void LoggingGadgetWidget::pauseButton() +/* + setPlayPauseButtonToPlay() + + Changes the appearance of the playPause button to the PAUSE appearance. +*/ +void LoggingGadgetWidget::setPlayPauseButtonToPause() +{ + m_logging->playPauseButton->setIcon(QIcon(":/logging/images/pause.png")); + m_logging->playPauseButton->setText(tr(" Pause")); +} + +void LoggingGadgetWidget::playPauseButtonAction() { ReplayState replayState = (loggingPlugin->getLogfile())->getReplayStatus(); - if (replayState == PLAYING) { + if (replayState == PLAYING){ emit pauseReplay(); + setPlayPauseButtonToPlay(); + } else { + emit resumeReplay(m_logging->playBackPosition->value()); + setPlayPauseButtonToPause(); } - m_logging->playButton->setEnabled(true); - m_logging->pauseButton->setEnabled(false); m_logging->stopButton->setEnabled(true); } -void LoggingGadgetWidget::stopButton() +void LoggingGadgetWidget::stopButtonAction() { ReplayState replayState = (loggingPlugin->getLogfile())->getReplayStatus(); if (replayState != STOPPED) { - emit stopReplay(); + emit pauseAndResetPosition(); } - m_logging->playButton->setEnabled(true); - m_logging->pauseButton->setEnabled(false); m_logging->stopButton->setEnabled(false); + setPlayPauseButtonToPlay(); - replayPosition(0); + // Block signals while setting the slider to the start position + m_logging->playBackPosition->blockSignals(true); + m_logging->playBackPosition->setValue(m_logging->playBackPosition->minimum()); + m_logging->playBackPosition->blockSignals(false); } void LoggingGadgetWidget::stateChanged(LoggingPlugin::State state) @@ -146,6 +152,7 @@ void LoggingGadgetWidget::stateChanged(LoggingPlugin::State state) switch (state) { case LoggingPlugin::IDLE: status = tr("Idle"); + playbackPosition(0); break; case LoggingPlugin::LOGGING: status = tr("Logging"); @@ -158,9 +165,13 @@ 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->stopButton->setEnabled(enabled && playing); + m_logging->playPauseButton->setEnabled(enabled); + if (playing){ + setPlayPauseButtonToPause(); + } else { + setPlayPauseButtonToPlay(); + } } void LoggingGadgetWidget::updateBeginAndEndtimes(quint32 startTimeStamp, quint32 endTimeStamp) @@ -187,16 +198,17 @@ void LoggingGadgetWidget::updateBeginAndEndtimes(quint32 startTimeStamp, quint32 m_logging->playBackPosition->setTickPosition(QSlider::TicksBothSides); } -void LoggingGadgetWidget::replayPosition(quint32 positionTimeStamp) +void LoggingGadgetWidget::playbackPosition(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 + // update position label updatePositionLabel(positionTimeStamp); } } @@ -208,73 +220,59 @@ void LoggingGadgetWidget::enableButtons() switch (replayState) { case STOPPED: - m_logging->playButton->setEnabled(true); - m_logging->pauseButton->setEnabled(false); m_logging->stopButton->setEnabled(false); + setPlayPauseButtonToPlay(); break; case PLAYING: - m_logging->playButton->setEnabled(false); - m_logging->pauseButton->setEnabled(true); m_logging->stopButton->setEnabled(true); + setPlayPauseButtonToPause(); break; case PAUSED: - m_logging->playButton->setEnabled(true); - m_logging->pauseButton->setEnabled(false); m_logging->stopButton->setEnabled(true); + setPlayPauseButtonToPlay(); break; } + m_logging->playPauseButton->setEnabled(true); 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->playPauseButton->setEnabled(false); + setPlayPauseButtonToPlay(); 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 + // 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::sendResumeReplayFrom() +void LoggingGadgetWidget::sliderMoved(int position) { - qDebug() << "sendResumeReplayFrom(): start of function, stored position is: " << m_storedPosition; + // pause playback while the user is dragging the slider to change position + emit pauseReplay(); - emit resumeReplayFrom(m_storedPosition); + updatePositionLabel(position); - emit resumeReplay(); - - qDebug() << "sendResumeReplayFrom(): end of function, stored position is: " << m_storedPosition; + // 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()); +} + + /** * @} diff --git a/ground/gcs/src/plugins/logging/logginggadgetwidget.h b/ground/gcs/src/plugins/logging/logginggadgetwidget.h index 28c900915..f2fc451f3 100644 --- a/ground/gcs/src/plugins/logging/logginggadgetwidget.h +++ b/ground/gcs/src/plugins/logging/logginggadgetwidget.h @@ -50,31 +50,28 @@ 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 playbackPosition(quint32 positionTimeStamp); + void playPauseButtonAction(); + void stopButtonAction(); void enableButtons(); void disableButtons(); void sliderMoved(int); - void sendResumeReplayFrom(); + void sliderAction(); signals: - void startReplay(); - void stopReplay(); + void resumeReplay(quint32 positionTimeStamp); void pauseReplay(); - void resumeReplay(); - void resumeReplayFrom(quint32 positionTimeStamp); + void pauseAndResetPosition(); private: Ui_Logging *m_logging; LoggingPlugin *loggingPlugin; ScopeGadgetFactory *scpPlugin; QTimer sliderActionDelay; - quint32 m_storedPosition; void updatePositionLabel(quint32 positionTimeStamp); - + void setPlayPauseButtonToPlay(); + void setPlayPauseButtonToPause(); }; #endif /* LoggingGADGETWIDGET_H_ */ diff --git a/ground/gcs/src/plugins/logging/res.qrc b/ground/gcs/src/plugins/logging/res.qrc new file mode 100644 index 000000000..01e87b3ec --- /dev/null +++ b/ground/gcs/src/plugins/logging/res.qrc @@ -0,0 +1,7 @@ + + + images/play.png + images/pause.png + images/stop.png + + From d540c2a0435a690829c7d25902c5dd25fd27ad96 Mon Sep 17 00:00:00 2001 From: Jan NIJS Date: Sun, 29 Apr 2018 16:27:51 +0200 Subject: [PATCH 3/8] LP-597 Progress bar for GCS log replay - ensure the index vectors are emptied before adding elements to them. Caused problem when replaying multiple logfiles successively. - Bugfix: ensure the index vectors are emptied before adding elements to them. Caused problem when replaying multiple logfiles successively. - extra validations while indexing the logfile - Define TIMESTAMP_SIZE_BYTES and use that instead of the hardcoded timestamp size of 4 bytes. - modify variable case: updateBeginAndEndtimes -> updateBeginAndEndTimes - improve qWarning messages for logfile.cpp: add the name of the function because we read the logfile in two separate functions. --- ground/gcs/src/libs/utils/logfile.cpp | 81 ++++++++++++------- ground/gcs/src/libs/utils/logfile.h | 2 +- .../plugins/logging/logginggadgetwidget.cpp | 4 +- .../src/plugins/logging/logginggadgetwidget.h | 2 +- 4 files changed, 58 insertions(+), 31 deletions(-) diff --git a/ground/gcs/src/libs/utils/logfile.cpp b/ground/gcs/src/libs/utils/logfile.cpp index b7bf9eca1..cc70f1198 100644 --- a/ground/gcs/src/libs/utils/logfile.cpp +++ b/ground/gcs/src/libs/utils/logfile.cpp @@ -29,6 +29,8 @@ #include #include // DEBUG: to display the thread ID +#define TIMESTAMP_SIZE_BYTES 4 + LogFile::LogFile(QObject *parent) : QIODevice(parent), m_timer(this), m_previousTimeStamp(0), @@ -156,7 +158,7 @@ void LogFile::timerFired() } m_timer_tick++; - if (m_file.bytesAvailable() > 4) { + if (m_file.bytesAvailable() > TIMESTAMP_SIZE_BYTES) { int time; time = m_myTime.elapsed(); @@ -182,7 +184,7 @@ void LogFile::timerFired() // read data size qint64 dataSize; if (m_file.bytesAvailable() < (qint64)sizeof(dataSize)) { - qDebug() << "LogFile - end of log file reached"; + qDebug() << "LogFile replay - end of log file reached"; stopReplay(); return; } @@ -190,14 +192,14 @@ void LogFile::timerFired() // 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"; + qDebug() << "LogFile replay - end of log file reached"; stopReplay(); return; } @@ -216,7 +218,7 @@ void LogFile::timerFired() } // read next timestamp if (m_file.bytesAvailable() < (qint64)sizeof(m_nextTimeStamp)) { - qDebug() << "LogFile - end of log file reached"; + qDebug() << "LogFile replay - end of log file reached"; stopReplay(); return; } @@ -226,7 +228,7 @@ 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; } @@ -235,7 +237,7 @@ void LogFile::timerFired() time = m_myTime.elapsed(); // number of milliseconds since start of playback } } else { - qDebug() << "LogFile - end of log file reached"; + qDebug() << "LogFile replay - end of log file reached"; stopReplay(); } } @@ -324,12 +326,12 @@ bool LogFile::stopReplay() * 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(); @@ -346,7 +348,11 @@ bool LogFile::resumeReplay(quint32 desiredPosition) for (int i = 0; i < m_timeStamps.size(); i++) { if (m_timeStamps.at(i) >= desiredPosition) { - m_file.seek(m_timeStampPositions.at(i)); + 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; } @@ -401,6 +407,7 @@ bool LogFile::pauseAndResetPosition() if (!m_file.isOpen() || !m_timer.isActive()) { return false; } + qDebug() << "LogFile - pauseAndResetPosition"; m_timer.stop(); m_replayStatus = STOPPED; @@ -426,11 +433,11 @@ ReplayState LogFile::getReplayStatus() /** * FUNCTION: buildIndex() * - * Walk through the opened logfile and sets the start and end position timestamps + * 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 + * Returns true when indexing has completed successfully. + * Returns false when a problem was encountered. * */ bool LogFile::buildIndex() @@ -439,18 +446,28 @@ bool LogFile::buildIndex() qint64 totalSize; qint64 readPointer = 0; quint64 index = 0; + int bytesRead = 0; - QByteArray arr = m_file.readAll(); + 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 start timestamp - if (totalSize - readPointer >= 4) { - dataStream.readRawData((char *)&timeStamp, 4); + // 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 += 4; + readPointer += TIMESTAMP_SIZE_BYTES; index++; m_beginTimeStamp = timeStamp; m_endTimeStamp = timeStamp; @@ -461,22 +478,27 @@ bool LogFile::buildIndex() // 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"; + 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; } - // 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"; + qWarning() << "LogFile buildIndex - 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"; + qWarning() << "LogFile buildIndex - logfile corrupted! Unexpected end of file"; return false; } @@ -484,18 +506,23 @@ bool LogFile::buildIndex() readPointer += dataStream.skipRawData(dataSize); // read the next timestamp - if (totalSize - readPointer >= 4) { - dataStream.readRawData((char *)&timeStamp, 4); + 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) - qDebug() << "Error: Logfile corrupted! Unlikely timestamp " << timeStamp << " after " << m_endTimeStamp; + qWarning() << "LogFile buildIndex - logfile corrupted! Unlikely timestamp " << timeStamp << " after " << m_endTimeStamp; + return false; } m_timeStamps.append(timeStamp); m_timeStampPositions.append(readPointer); - readPointer += 4; + readPointer += TIMESTAMP_SIZE_BYTES; index++; m_endTimeStamp = timeStamp; } else { @@ -504,7 +531,7 @@ bool LogFile::buildIndex() } } - emit updateBeginAndEndtimes(m_beginTimeStamp, m_endTimeStamp); + emit updateBeginAndEndTimes(m_beginTimeStamp, m_endTimeStamp); // reset the read pointer to the start of the file m_file.seek(0); diff --git a/ground/gcs/src/libs/utils/logfile.h b/ground/gcs/src/libs/utils/logfile.h index f36173e3e..3246d6b12 100644 --- a/ground/gcs/src/libs/utils/logfile.h +++ b/ground/gcs/src/libs/utils/logfile.h @@ -100,7 +100,7 @@ signals: void replayStarted(); void replayFinished(); void playbackPosition(quint32); - void updateBeginAndEndtimes(quint32, quint32); + void updateBeginAndEndTimes(quint32, quint32); protected: QByteArray m_dataBuffer; diff --git a/ground/gcs/src/plugins/logging/logginggadgetwidget.cpp b/ground/gcs/src/plugins/logging/logginggadgetwidget.cpp index 655db48f9..5fdc2773f 100644 --- a/ground/gcs/src/plugins/logging/logginggadgetwidget.cpp +++ b/ground/gcs/src/plugins/logging/logginggadgetwidget.cpp @@ -79,7 +79,7 @@ void LoggingGadgetWidget::setPlugin(LoggingPlugin *p) // Feedback from logfile to GUI connect(loggingPlugin, &LoggingPlugin::stateChanged, this, &LoggingGadgetWidget::stateChanged); - connect(logFile, &LogFile::updateBeginAndEndtimes, this, &LoggingGadgetWidget::updateBeginAndEndtimes); + connect(logFile, &LogFile::updateBeginAndEndTimes, this, &LoggingGadgetWidget::updateBeginAndEndTimes); connect(logFile, &LogFile::playbackPosition, this, &LoggingGadgetWidget::playbackPosition); connect(logFile, &LogFile::replayStarted, this, &LoggingGadgetWidget::enableButtons); connect(logFile, &LogFile::replayFinished, this, &LoggingGadgetWidget::disableButtons); @@ -174,7 +174,7 @@ void LoggingGadgetWidget::stateChanged(LoggingPlugin::State state) } } -void LoggingGadgetWidget::updateBeginAndEndtimes(quint32 startTimeStamp, quint32 endTimeStamp) +void LoggingGadgetWidget::updateBeginAndEndTimes(quint32 startTimeStamp, quint32 endTimeStamp) { int startSec, startMin, endSec, endMin; diff --git a/ground/gcs/src/plugins/logging/logginggadgetwidget.h b/ground/gcs/src/plugins/logging/logginggadgetwidget.h index f2fc451f3..34491936e 100644 --- a/ground/gcs/src/plugins/logging/logginggadgetwidget.h +++ b/ground/gcs/src/plugins/logging/logginggadgetwidget.h @@ -49,7 +49,7 @@ public: protected slots: void stateChanged(LoggingPlugin::State state); - void updateBeginAndEndtimes(quint32 startTimeStamp, quint32 endTimeStamp); + void updateBeginAndEndTimes(quint32 startTimeStamp, quint32 endTimeStamp); void playbackPosition(quint32 positionTimeStamp); void playPauseButtonAction(); void stopButtonAction(); From fa70cb751c4d9b51823fc58b54de747eb751edd0 Mon Sep 17 00:00:00 2001 From: Jan NIJS Date: Sun, 29 Apr 2018 19:43:46 +0200 Subject: [PATCH 4/8] LP-597 Progress bar for GCS log replay - Resize playPause and stop buttons according to the available space for the widget. - also make pretty --- ground/gcs/src/libs/utils/logfile.cpp | 22 ++--- ground/gcs/src/plugins/logging/logging.ui | 50 ++++++++-- .../plugins/logging/logginggadgetwidget.cpp | 97 +++++++++++++++---- .../src/plugins/logging/logginggadgetwidget.h | 4 + 4 files changed, 133 insertions(+), 40 deletions(-) diff --git a/ground/gcs/src/libs/utils/logfile.cpp b/ground/gcs/src/libs/utils/logfile.cpp index cc70f1198..ffb0d97c8 100644 --- a/ground/gcs/src/libs/utils/logfile.cpp +++ b/ground/gcs/src/libs/utils/logfile.cpp @@ -165,7 +165,7 @@ void LogFile::timerFired() /* 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() @@ -176,8 +176,7 @@ void LogFile::timerFired() */ - while ( m_nextTimeStamp < (m_lastPlayed + (double)(time - m_timeOffset) * m_playbackSpeed) ) { - + 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); @@ -344,12 +343,11 @@ bool LogFile::resumeReplay(quint32 desiredPosition) 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); + bool seek_ok = m_file.seek(bytesToSkip); if (!seek_ok) { qWarning() << "LogFile resumeReplay - an error occurred while seeking through the logfile."; } @@ -365,7 +363,7 @@ bool LogFile::resumeReplay(quint32 desiredPosition) // Set the real-time interval to 0 to start with: m_myTime.restart(); - m_timeOffset = 0; + m_timeOffset = 0; m_replayStatus = PLAYING; @@ -409,7 +407,7 @@ bool LogFile::pauseAndResetPosition() } qDebug() << "LogFile - pauseAndResetPosition"; m_timer.stop(); - m_replayStatus = STOPPED; + m_replayStatus = STOPPED; m_timeOffset = 0; m_lastPlayed = m_timeStamps.at(0); @@ -467,10 +465,10 @@ bool LogFile::buildIndex() } m_timeStamps.append(timeStamp); m_timeStampPositions.append(readPointer); - readPointer += TIMESTAMP_SIZE_BYTES; + readPointer += TIMESTAMP_SIZE_BYTES; index++; m_beginTimeStamp = timeStamp; - m_endTimeStamp = timeStamp; + m_endTimeStamp = timeStamp; } while (true) { @@ -511,7 +509,7 @@ bool LogFile::buildIndex() 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 @@ -522,7 +520,7 @@ bool LogFile::buildIndex() m_timeStamps.append(timeStamp); m_timeStampPositions.append(readPointer); - readPointer += TIMESTAMP_SIZE_BYTES; + readPointer += TIMESTAMP_SIZE_BYTES; index++; m_endTimeStamp = timeStamp; } else { diff --git a/ground/gcs/src/plugins/logging/logging.ui b/ground/gcs/src/plugins/logging/logging.ui index ed7dbbe6f..421ecb2cd 100644 --- a/ground/gcs/src/plugins/logging/logging.ui +++ b/ground/gcs/src/plugins/logging/logging.ui @@ -36,8 +36,8 @@ - - 0 + + 80 0 @@ -47,11 +47,17 @@ 0 + + + 80 + 16777215 + + Play - + :/logging/images/play.png:/logging/images/play.png @@ -65,8 +71,8 @@ - - 0 + + 80 0 @@ -76,11 +82,17 @@ 0 + + + 80 + 16777215 + + Stop - + :/logging/images/stop.png:/logging/images/stop.png @@ -113,6 +125,12 @@ + + + 0 + 0 + + 1 @@ -203,6 +221,12 @@ 0 + + + 40 + 0 + + @@ -229,6 +253,12 @@ 0 + + + 40 + 0 + + @@ -255,6 +285,12 @@ 0 + + + 40 + 0 + + @@ -283,7 +319,7 @@ - + diff --git a/ground/gcs/src/plugins/logging/logginggadgetwidget.cpp b/ground/gcs/src/plugins/logging/logginggadgetwidget.cpp index 5fdc2773f..945c8f3f2 100644 --- a/ground/gcs/src/plugins/logging/logginggadgetwidget.cpp +++ b/ground/gcs/src/plugins/logging/logginggadgetwidget.cpp @@ -41,6 +41,9 @@ LoggingGadgetWidget::LoggingGadgetWidget(QWidget *parent) : QWidget(parent), log ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance(); scpPlugin = pm->getObject(); + // Store preferred button width before manipulating the button appearance + m_preferredButtonWidth = m_logging->playPauseButton->sizeHint().width(); + disableButtons(); // Configure timer to delay application of slider position action for 200ms @@ -60,65 +63,92 @@ void LoggingGadgetWidget::setPlugin(LoggingPlugin *p) loggingPlugin = p; LogFile *logFile = loggingPlugin->getLogfile(); - // GUI elements to gadgetwidget functions + // GUI elements to gadgetwidget functions connect(m_logging->playPauseButton, &QPushButton::clicked, this, &LoggingGadgetWidget::playPauseButtonAction); connect(m_logging->stopButton, &QPushButton::clicked, this, &LoggingGadgetWidget::stopButtonAction); connect(m_logging->playBackPosition, &QSlider::valueChanged, this, &LoggingGadgetWidget::sliderMoved); connect(m_logging->playbackSpeed, static_cast(&QDoubleSpinBox::valueChanged), logFile, &LogFile::setReplaySpeed); - // gadgetwidget functions to logfile actions + // gadgetwidget functions to logfile actions connect(this, &LoggingGadgetWidget::resumeReplay, logFile, &LogFile::resumeReplay); connect(this, &LoggingGadgetWidget::pauseReplay, logFile, &LogFile::pauseReplay); connect(this, &LoggingGadgetWidget::pauseAndResetPosition, logFile, &LogFile::pauseAndResetPosition); - // gadgetwidget functions to scope actions + // gadgetwidget functions to scope actions connect(this, &LoggingGadgetWidget::resumeReplay, scpPlugin, &ScopeGadgetFactory::startPlotting); connect(this, &LoggingGadgetWidget::pauseReplay, scpPlugin, &ScopeGadgetFactory::stopPlotting); connect(this, &LoggingGadgetWidget::pauseAndResetPosition, scpPlugin, &ScopeGadgetFactory::stopPlotting); - // Feedback from logfile to GUI + // Feedback from logfile to GUI connect(loggingPlugin, &LoggingPlugin::stateChanged, this, &LoggingGadgetWidget::stateChanged); connect(logFile, &LogFile::updateBeginAndEndTimes, this, &LoggingGadgetWidget::updateBeginAndEndTimes); connect(logFile, &LogFile::playbackPosition, this, &LoggingGadgetWidget::playbackPosition); connect(logFile, &LogFile::replayStarted, this, &LoggingGadgetWidget::enableButtons); connect(logFile, &LogFile::replayFinished, this, &LoggingGadgetWidget::disableButtons); - // Feedback from logfile to scope + // 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()); } +/* + resizeEvent() + + Determine button content policy based on button size. + + */ +void LoggingGadgetWidget::resizeEvent(QResizeEvent *event) +{ + int width = m_logging->playPauseButton->size().width(); + + if (width < m_preferredButtonWidth) { + m_iconOnlyButtons = true; + } else { + m_iconOnlyButtons = false; + } + updateButtonAppearance(); + + QWidget::resizeEvent(event); +} /* setPlayPauseButtonToPlay() Changes the appearance of the playPause button to the PLAY appearance. -*/ + */ void LoggingGadgetWidget::setPlayPauseButtonToPlay() { m_logging->playPauseButton->setIcon(QIcon(":/logging/images/play.png")); - m_logging->playPauseButton->setText(tr(" Play")); + if (m_iconOnlyButtons) { + m_logging->playPauseButton->setText(QString()); + } else { + m_logging->playPauseButton->setText(tr(" Play")); + } } /* - setPlayPauseButtonToPlay() + setPlayPauseButtonToPause() Changes the appearance of the playPause button to the PAUSE appearance. -*/ + */ void LoggingGadgetWidget::setPlayPauseButtonToPause() { m_logging->playPauseButton->setIcon(QIcon(":/logging/images/pause.png")); - m_logging->playPauseButton->setText(tr(" Pause")); + if (m_iconOnlyButtons) { + m_logging->playPauseButton->setText(QString()); + } else { + m_logging->playPauseButton->setText(tr(" Pause")); + } } void LoggingGadgetWidget::playPauseButtonAction() { ReplayState replayState = (loggingPlugin->getLogfile())->getReplayStatus(); - if (replayState == PLAYING){ + if (replayState == PLAYING) { emit pauseReplay(); setPlayPauseButtonToPlay(); } else { @@ -151,7 +181,7 @@ void LoggingGadgetWidget::stateChanged(LoggingPlugin::State state) switch (state) { case LoggingPlugin::IDLE: - status = tr("Idle"); + status = tr("Idle"); playbackPosition(0); break; case LoggingPlugin::LOGGING: @@ -167,7 +197,7 @@ void LoggingGadgetWidget::stateChanged(LoggingPlugin::State state) bool playing = loggingPlugin->getLogfile()->isPlaying(); m_logging->stopButton->setEnabled(enabled && playing); m_logging->playPauseButton->setEnabled(enabled); - if (playing){ + if (playing) { setPlayPauseButtonToPause(); } else { setPlayPauseButtonToPlay(); @@ -181,8 +211,8 @@ void LoggingGadgetWidget::updateBeginAndEndTimes(quint32 startTimeStamp, quint32 startSec = (startTimeStamp / 1000) % 60; startMin = startTimeStamp / (60 * 1000); - endSec = (endTimeStamp / 1000) % 60; - endMin = endTimeStamp / (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'))); @@ -202,7 +232,6 @@ void LoggingGadgetWidget::playbackPosition(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); @@ -217,8 +246,7 @@ void LoggingGadgetWidget::enableButtons() { ReplayState replayState = (loggingPlugin->getLogfile())->getReplayStatus(); - switch (replayState) - { + switch (replayState) { case STOPPED: m_logging->stopButton->setEnabled(false); setPlayPauseButtonToPlay(); @@ -240,7 +268,6 @@ void LoggingGadgetWidget::enableButtons() void LoggingGadgetWidget::disableButtons() { - m_logging->playPauseButton->setEnabled(false); setPlayPauseButtonToPlay(); m_logging->stopButton->setEnabled(false); @@ -248,11 +275,40 @@ void LoggingGadgetWidget::disableButtons() m_logging->playBackPosition->setEnabled(false); } +void LoggingGadgetWidget::updateButtonAppearance() +{ + ReplayState replayState; + + if (!loggingPlugin || !loggingPlugin->getLogfile()) { + // loggingPlugin has not been completely initialized: set to STOPPED state + replayState = STOPPED; + } else { + replayState = (loggingPlugin->getLogfile())->getReplayStatus(); + } + + // Update playPause button appearance + if (replayState == PLAYING) { + // Playing: playPause button must appear as a pause button + setPlayPauseButtonToPause(); + } else { + // Stopped or Paused: playPause button must appear as a play button + setPlayPauseButtonToPlay(); + } + + // Update stop button appearance + if (m_iconOnlyButtons) { + m_logging->stopButton->setText(QString()); + } else { + m_logging->stopButton->setText(tr(" Stop")); + } +} + 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'))); } @@ -273,7 +329,6 @@ void LoggingGadgetWidget::sliderAction() } - /** * @} * @} diff --git a/ground/gcs/src/plugins/logging/logginggadgetwidget.h b/ground/gcs/src/plugins/logging/logginggadgetwidget.h index 34491936e..d2603472e 100644 --- a/ground/gcs/src/plugins/logging/logginggadgetwidget.h +++ b/ground/gcs/src/plugins/logging/logginggadgetwidget.h @@ -68,10 +68,14 @@ private: LoggingPlugin *loggingPlugin; ScopeGadgetFactory *scpPlugin; QTimer sliderActionDelay; + bool m_iconOnlyButtons; + int m_preferredButtonWidth; void updatePositionLabel(quint32 positionTimeStamp); void setPlayPauseButtonToPlay(); void setPlayPauseButtonToPause(); + void updateButtonAppearance(); + void resizeEvent(QResizeEvent *event); }; #endif /* LoggingGADGETWIDGET_H_ */ From ecd6d20b131c52a7329c2f14acde2cf3a39da40f Mon Sep 17 00:00:00 2001 From: Jan NIJS Date: Mon, 30 Apr 2018 19:09:38 +0200 Subject: [PATCH 5/8] LP-597 Progress bar for GCS log replay - first batch of fixes from review and some others - remove extraneous parentheses - use Qt naming convention for playbackPosition -> setPlaybackPosition - consistently use playback instead of playBack - most places: use of "state" instead of "status" - removed empty line - updated file headers - removed QThread include (leftover from debugging) - renamed pauseAndResetPosition to pauseReplayAndResetPosition for consistency with the related functions --- ground/gcs/src/libs/utils/logfile.cpp | 34 +++++----- ground/gcs/src/libs/utils/logfile.h | 11 ++-- .../plugins/logging/logginggadgetwidget.cpp | 65 ++++++++++--------- .../src/plugins/logging/logginggadgetwidget.h | 13 ++-- 4 files changed, 61 insertions(+), 62 deletions(-) diff --git a/ground/gcs/src/libs/utils/logfile.cpp b/ground/gcs/src/libs/utils/logfile.cpp index ffb0d97c8..35eb93ff2 100644 --- a/ground/gcs/src/libs/utils/logfile.cpp +++ b/ground/gcs/src/libs/utils/logfile.cpp @@ -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 * @@ -27,7 +27,6 @@ #include #include #include -#include // DEBUG: to display the thread ID #define TIMESTAMP_SIZE_BYTES 4 @@ -38,7 +37,7 @@ LogFile::LogFile(QObject *parent) : QIODevice(parent), m_lastPlayed(0), m_timeOffset(0), m_playbackSpeed(1.0), - m_replayStatus(STOPPED), + m_replayState(STOPPED), m_useProvidedTimeStamp(false), m_providedTimeStamp(0), m_beginTimeStamp(0), @@ -150,10 +149,9 @@ qint64 LogFile::bytesAvailable() const This function is called at a 10 ms interval to fill the replay buffers. */ - void LogFile::timerFired() { - if (m_replayStatus != PLAYING) { + if (m_replayState != PLAYING) { return; } m_timer_tick++; @@ -213,7 +211,7 @@ void LogFile::timerFired() // rate-limit slider bar position updates to 10 updates per second if (m_timer_tick % 10 == 0) { - emit playbackPosition(m_nextTimeStamp); + emit setPlaybackPosition(m_nextTimeStamp); } // read next timestamp if (m_file.bytesAvailable() < (qint64)sizeof(m_nextTimeStamp)) { @@ -289,7 +287,7 @@ bool LogFile::startReplay() m_timer.setInterval(10); m_timer.start(); - m_replayStatus = PLAYING; + m_replayState = PLAYING; emit replayStarted(); return true; @@ -312,7 +310,7 @@ bool LogFile::stopReplay() } qDebug() << "LogFile - stopReplay"; m_timer.stop(); - m_replayStatus = STOPPED; + m_replayState = STOPPED; emit replayFinished(); return true; @@ -363,9 +361,9 @@ bool LogFile::resumeReplay(quint32 desiredPosition) // Set the real-time interval to 0 to start with: m_myTime.restart(); - m_timeOffset = 0; + m_timeOffset = 0; - m_replayStatus = PLAYING; + m_replayState = PLAYING; m_timer.start(); @@ -387,7 +385,7 @@ bool LogFile::pauseReplay() } qDebug() << "LogFile - pauseReplay"; m_timer.stop(); - m_replayStatus = PAUSED; + m_replayState = PAUSED; // hack to notify UI that replay paused emit replayStarted(); @@ -395,19 +393,19 @@ bool LogFile::pauseReplay() } /** - * SLOT: pauseAndResetPosition() + * SLOT: pauseReplayAndResetPosition() * * Pauses replay and resets the playback position to the start of the logfile * */ -bool LogFile::pauseAndResetPosition() +bool LogFile::pauseReplayAndResetPosition() { if (!m_file.isOpen() || !m_timer.isActive()) { return false; } - qDebug() << "LogFile - pauseAndResetPosition"; + qDebug() << "LogFile - pauseReplayAndResetPosition"; m_timer.stop(); - m_replayStatus = STOPPED; + m_replayState = STOPPED; m_timeOffset = 0; m_lastPlayed = m_timeStamps.at(0); @@ -418,14 +416,14 @@ bool LogFile::pauseAndResetPosition() } /** - * FUNCTION: getReplayStatus() + * FUNCTION: getReplayState() * * Returns the current replay status. * */ -ReplayState LogFile::getReplayStatus() +ReplayState LogFile::getReplayState() { - return m_replayStatus; + return m_replayState; } /** diff --git a/ground/gcs/src/libs/utils/logfile.h b/ground/gcs/src/libs/utils/logfile.h index 3246d6b12..ef1672267 100644 --- a/ground/gcs/src/libs/utils/logfile.h +++ b/ground/gcs/src/libs/utils/logfile.h @@ -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,7 +32,6 @@ #include #include #include -#include #include #include @@ -78,7 +77,7 @@ public: m_providedTimeStamp = providedTimestamp; } - ReplayState getReplayStatus(); + ReplayState getReplayState(); public slots: void setReplaySpeed(double val) @@ -91,7 +90,7 @@ public slots: bool resumeReplay(quint32); bool pauseReplay(); - bool pauseAndResetPosition(); + bool pauseReplayAndResetPosition(); protected slots: void timerFired(); @@ -99,7 +98,7 @@ protected slots: signals: void replayStarted(); void replayFinished(); - void playbackPosition(quint32); + void setPlaybackPosition(quint32); void updateBeginAndEndTimes(quint32, quint32); protected: @@ -116,7 +115,7 @@ protected: int m_timeOffset; double m_playbackSpeed; - ReplayState m_replayStatus; + ReplayState m_replayState; private: bool m_useProvidedTimeStamp; diff --git a/ground/gcs/src/plugins/logging/logginggadgetwidget.cpp b/ground/gcs/src/plugins/logging/logginggadgetwidget.cpp index 945c8f3f2..eebe3118e 100644 --- a/ground/gcs/src/plugins/logging/logginggadgetwidget.cpp +++ b/ground/gcs/src/plugins/logging/logginggadgetwidget.cpp @@ -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 @@ -66,24 +67,24 @@ void LoggingGadgetWidget::setPlugin(LoggingPlugin *p) // GUI elements to gadgetwidget functions connect(m_logging->playPauseButton, &QPushButton::clicked, this, &LoggingGadgetWidget::playPauseButtonAction); connect(m_logging->stopButton, &QPushButton::clicked, this, &LoggingGadgetWidget::stopButtonAction); - connect(m_logging->playBackPosition, &QSlider::valueChanged, this, &LoggingGadgetWidget::sliderMoved); + connect(m_logging->playbackPosition, &QSlider::valueChanged, this, &LoggingGadgetWidget::sliderMoved); connect(m_logging->playbackSpeed, static_cast(&QDoubleSpinBox::valueChanged), logFile, &LogFile::setReplaySpeed); // gadgetwidget functions to logfile actions connect(this, &LoggingGadgetWidget::resumeReplay, logFile, &LogFile::resumeReplay); connect(this, &LoggingGadgetWidget::pauseReplay, logFile, &LogFile::pauseReplay); - connect(this, &LoggingGadgetWidget::pauseAndResetPosition, logFile, &LogFile::pauseAndResetPosition); + 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::pauseAndResetPosition, 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::updateBeginAndEndTimes, this, &LoggingGadgetWidget::updateBeginAndEndTimes); - connect(logFile, &LogFile::playbackPosition, this, &LoggingGadgetWidget::playbackPosition); + connect(logFile, &LogFile::setPlaybackPosition, this, &LoggingGadgetWidget::setPlaybackPosition); connect(logFile, &LogFile::replayStarted, this, &LoggingGadgetWidget::enableButtons); connect(logFile, &LogFile::replayFinished, this, &LoggingGadgetWidget::disableButtons); @@ -146,13 +147,13 @@ void LoggingGadgetWidget::setPlayPauseButtonToPause() void LoggingGadgetWidget::playPauseButtonAction() { - ReplayState replayState = (loggingPlugin->getLogfile())->getReplayStatus(); + ReplayState replayState = loggingPlugin->getLogfile()->getReplayState(); if (replayState == PLAYING) { emit pauseReplay(); setPlayPauseButtonToPlay(); } else { - emit resumeReplay(m_logging->playBackPosition->value()); + emit resumeReplay(m_logging->playbackPosition->value()); setPlayPauseButtonToPause(); } m_logging->stopButton->setEnabled(true); @@ -160,18 +161,18 @@ void LoggingGadgetWidget::playPauseButtonAction() void LoggingGadgetWidget::stopButtonAction() { - ReplayState replayState = (loggingPlugin->getLogfile())->getReplayStatus(); + ReplayState replayState = loggingPlugin->getLogfile()->getReplayState(); if (replayState != STOPPED) { - emit pauseAndResetPosition(); + emit pauseReplayAndResetPosition(); } m_logging->stopButton->setEnabled(false); setPlayPauseButtonToPlay(); // Block signals while setting the slider to the start position - m_logging->playBackPosition->blockSignals(true); - m_logging->playBackPosition->setValue(m_logging->playBackPosition->minimum()); - m_logging->playBackPosition->blockSignals(false); + m_logging->playbackPosition->blockSignals(true); + m_logging->playbackPosition->setValue(m_logging->playbackPosition->minimum()); + m_logging->playbackPosition->blockSignals(false); } void LoggingGadgetWidget::stateChanged(LoggingPlugin::State state) @@ -182,7 +183,7 @@ void LoggingGadgetWidget::stateChanged(LoggingPlugin::State state) switch (state) { case LoggingPlugin::IDLE: status = tr("Idle"); - playbackPosition(0); + setPlaybackPosition(0); break; case LoggingPlugin::LOGGING: status = tr("Logging"); @@ -219,23 +220,23 @@ void LoggingGadgetWidget::updateBeginAndEndTimes(quint32 startTimeStamp, quint32 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->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); + 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::playbackPosition(quint32 positionTimeStamp) +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()) { + 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); + m_logging->playbackPosition->blockSignals(true); + m_logging->playbackPosition->setValue(positionTimeStamp); + m_logging->playbackPosition->blockSignals(false); // update position label updatePositionLabel(positionTimeStamp); @@ -244,7 +245,7 @@ void LoggingGadgetWidget::playbackPosition(quint32 positionTimeStamp) void LoggingGadgetWidget::enableButtons() { - ReplayState replayState = (loggingPlugin->getLogfile())->getReplayStatus(); + ReplayState replayState = loggingPlugin->getLogfile()->getReplayState(); switch (replayState) { case STOPPED: @@ -263,7 +264,7 @@ void LoggingGadgetWidget::enableButtons() break; } m_logging->playPauseButton->setEnabled(true); - m_logging->playBackPosition->setEnabled(true); + m_logging->playbackPosition->setEnabled(true); } void LoggingGadgetWidget::disableButtons() @@ -272,7 +273,7 @@ void LoggingGadgetWidget::disableButtons() setPlayPauseButtonToPlay(); m_logging->stopButton->setEnabled(false); - m_logging->playBackPosition->setEnabled(false); + m_logging->playbackPosition->setEnabled(false); } void LoggingGadgetWidget::updateButtonAppearance() @@ -283,7 +284,7 @@ void LoggingGadgetWidget::updateButtonAppearance() // loggingPlugin has not been completely initialized: set to STOPPED state replayState = STOPPED; } else { - replayState = (loggingPlugin->getLogfile())->getReplayStatus(); + replayState = loggingPlugin->getLogfile()->getReplayState(); } // Update playPause button appearance @@ -325,7 +326,7 @@ void LoggingGadgetWidget::sliderMoved(int position) void LoggingGadgetWidget::sliderAction() { - emit resumeReplay(m_logging->playBackPosition->value()); + emit resumeReplay(m_logging->playbackPosition->value()); } diff --git a/ground/gcs/src/plugins/logging/logginggadgetwidget.h b/ground/gcs/src/plugins/logging/logginggadgetwidget.h index d2603472e..b5f778759 100644 --- a/ground/gcs/src/plugins/logging/logginggadgetwidget.h +++ b/ground/gcs/src/plugins/logging/logginggadgetwidget.h @@ -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 @@ -50,7 +51,7 @@ public: protected slots: void stateChanged(LoggingPlugin::State state); void updateBeginAndEndTimes(quint32 startTimeStamp, quint32 endTimeStamp); - void playbackPosition(quint32 positionTimeStamp); + void setPlaybackPosition(quint32 positionTimeStamp); void playPauseButtonAction(); void stopButtonAction(); void enableButtons(); @@ -61,7 +62,7 @@ protected slots: signals: void resumeReplay(quint32 positionTimeStamp); void pauseReplay(); - void pauseAndResetPosition(); + void pauseReplayAndResetPosition(); private: Ui_Logging *m_logging; From 684849f33ca76c2b25af8aac95a2654d9ef18884 Mon Sep 17 00:00:00 2001 From: Jan NIJS Date: Fri, 4 May 2018 21:31:20 +0200 Subject: [PATCH 6/8] LP-597 again 3 separate buttons + various fixes & cleanups Suggested by Philippe Renon: - change the combined play-pause button to separate play and pause buttons. Hide button irrelevant button. Code has been simplified as a result. Fixes: - fix situation where replay is stopped/paused and logfile is disconnected through the Disconnect button. - remove unneeded stuff (resizing related) - rename updateBeginAndEndTimes -> setBeginAndEndTimes - less base height needed for the widget - wordwrap for "Playback speed" label. Wraps when space constrains. - set a fixed width for statusLabel to prevent jumping around when the text changes - Update the statusLabel to "Paused" when stopped/paused. - rename enableButtons -> enableWidgets - clear start and end text labels when playback has finished. --- ground/gcs/src/libs/utils/logfile.cpp | 12 +- ground/gcs/src/libs/utils/logfile.h | 2 +- ground/gcs/src/plugins/logging/logging.ui | 108 +++++++----- .../plugins/logging/logginggadgetwidget.cpp | 154 ++++++------------ .../src/plugins/logging/logginggadgetwidget.h | 15 +- 5 files changed, 135 insertions(+), 156 deletions(-) diff --git a/ground/gcs/src/libs/utils/logfile.cpp b/ground/gcs/src/libs/utils/logfile.cpp index 35eb93ff2..70a1060f1 100644 --- a/ground/gcs/src/libs/utils/logfile.cpp +++ b/ground/gcs/src/libs/utils/logfile.cpp @@ -305,11 +305,14 @@ bool LogFile::startReplay() */ bool LogFile::stopReplay() { - if (!m_file.isOpen() || !m_timer.isActive()) { + if (!m_file.isOpen()) { return false; } + if (m_timer.isActive()) { + m_timer.stop(); + } + qDebug() << "LogFile - stopReplay"; - m_timer.stop(); m_replayState = STOPPED; emit replayFinished(); @@ -386,9 +389,6 @@ bool LogFile::pauseReplay() qDebug() << "LogFile - pauseReplay"; m_timer.stop(); m_replayState = PAUSED; - - // hack to notify UI that replay paused - emit replayStarted(); return true; } @@ -527,7 +527,7 @@ bool LogFile::buildIndex() } } - emit updateBeginAndEndTimes(m_beginTimeStamp, m_endTimeStamp); + emit setBeginAndEndTimes(m_beginTimeStamp, m_endTimeStamp); // reset the read pointer to the start of the file m_file.seek(0); diff --git a/ground/gcs/src/libs/utils/logfile.h b/ground/gcs/src/libs/utils/logfile.h index ef1672267..7e19c02a0 100644 --- a/ground/gcs/src/libs/utils/logfile.h +++ b/ground/gcs/src/libs/utils/logfile.h @@ -99,7 +99,7 @@ signals: void replayStarted(); void replayFinished(); void setPlaybackPosition(quint32); - void updateBeginAndEndTimes(quint32, quint32); + void setBeginAndEndTimes(quint32, quint32); protected: QByteArray m_dataBuffer; diff --git a/ground/gcs/src/plugins/logging/logging.ui b/ground/gcs/src/plugins/logging/logging.ui index 421ecb2cd..e9537c935 100644 --- a/ground/gcs/src/plugins/logging/logging.ui +++ b/ground/gcs/src/plugins/logging/logging.ui @@ -7,7 +7,7 @@ 0 0 439 - 118 + 120 @@ -27,34 +27,43 @@ - + + + 3 + - + QLayout::SetNoConstraint - + + + true + - - 80 + + 0 0 - 30 - 0 + 39 + 38 - + - 80 - 16777215 + 39 + 38 - - Play + + Qt::NoFocus + + + false @@ -69,27 +78,44 @@ - + - - 80 + + 0 0 - 30 - 0 + 39 + 38 - + + + :/logging/images/pause.png:/logging/images/pause.png + + - 80 - 16777215 + 16 + 16 - - Stop + + + + + + + 0 + 0 + + + + + 39 + 38 + @@ -121,12 +147,15 @@ Playback speed: + + true + - + 0 0 @@ -170,6 +199,12 @@ + + + 65 + 0 + + 75 @@ -184,29 +219,20 @@ - + true Qt::Horizontal - - false - - - false - - - QSlider::TicksBothSides - - - 5 - + + 0 + 0 @@ -230,6 +256,9 @@ + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + @@ -262,6 +291,9 @@ + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + @@ -295,7 +327,7 @@ - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing diff --git a/ground/gcs/src/plugins/logging/logginggadgetwidget.cpp b/ground/gcs/src/plugins/logging/logginggadgetwidget.cpp index eebe3118e..6a84b1179 100644 --- a/ground/gcs/src/plugins/logging/logginggadgetwidget.cpp +++ b/ground/gcs/src/plugins/logging/logginggadgetwidget.cpp @@ -42,10 +42,7 @@ LoggingGadgetWidget::LoggingGadgetWidget(QWidget *parent) : QWidget(parent), log ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance(); scpPlugin = pm->getObject(); - // Store preferred button width before manipulating the button appearance - m_preferredButtonWidth = m_logging->playPauseButton->sizeHint().width(); - - disableButtons(); + disableWidgets(); // Configure timer to delay application of slider position action for 200ms sliderActionDelay.setSingleShot(true); @@ -65,7 +62,8 @@ void LoggingGadgetWidget::setPlugin(LoggingPlugin *p) LogFile *logFile = loggingPlugin->getLogfile(); // GUI elements to gadgetwidget functions - connect(m_logging->playPauseButton, &QPushButton::clicked, this, &LoggingGadgetWidget::playPauseButtonAction); + 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); @@ -83,10 +81,10 @@ void LoggingGadgetWidget::setPlugin(LoggingPlugin *p) // Feedback from logfile to GUI connect(loggingPlugin, &LoggingPlugin::stateChanged, this, &LoggingGadgetWidget::stateChanged); - connect(logFile, &LogFile::updateBeginAndEndTimes, this, &LoggingGadgetWidget::updateBeginAndEndTimes); + connect(logFile, &LogFile::setBeginAndEndTimes, this, &LoggingGadgetWidget::setBeginAndEndTimes); connect(logFile, &LogFile::setPlaybackPosition, this, &LoggingGadgetWidget::setPlaybackPosition); - connect(logFile, &LogFile::replayStarted, this, &LoggingGadgetWidget::enableButtons); - connect(logFile, &LogFile::replayFinished, this, &LoggingGadgetWidget::disableButtons); + connect(logFile, &LogFile::replayStarted, this, &LoggingGadgetWidget::enableWidgets); + connect(logFile, &LogFile::replayFinished, this, &LoggingGadgetWidget::disableWidgets); // Feedback from logfile to scope connect(logFile, &LogFile::replayFinished, scpPlugin, &ScopeGadgetFactory::stopPlotting); @@ -95,84 +93,52 @@ void LoggingGadgetWidget::setPlugin(LoggingPlugin *p) stateChanged(loggingPlugin->getState()); } -/* - resizeEvent() - - Determine button content policy based on button size. - - */ -void LoggingGadgetWidget::resizeEvent(QResizeEvent *event) +void LoggingGadgetWidget::playButtonAction() { - int width = m_logging->playPauseButton->size().width(); + ReplayState replayState = (loggingPlugin->getLogfile())->getReplayState(); - if (width < m_preferredButtonWidth) { - m_iconOnlyButtons = true; - } else { - m_iconOnlyButtons = false; + if (replayState != PLAYING) { + emit resumeReplay(m_logging->playbackPosition->value()); } - updateButtonAppearance(); - QWidget::resizeEvent(event); + m_logging->playButton->setVisible(false); + m_logging->pauseButton->setVisible(true); + m_logging->stopButton->setEnabled(true); } -/* - setPlayPauseButtonToPlay() - - Changes the appearance of the playPause button to the PLAY appearance. - */ -void LoggingGadgetWidget::setPlayPauseButtonToPlay() +void LoggingGadgetWidget::pauseButtonAction() { - m_logging->playPauseButton->setIcon(QIcon(":/logging/images/play.png")); - if (m_iconOnlyButtons) { - m_logging->playPauseButton->setText(QString()); - } else { - m_logging->playPauseButton->setText(tr(" Play")); - } -} - -/* - setPlayPauseButtonToPause() - - Changes the appearance of the playPause button to the PAUSE appearance. - */ -void LoggingGadgetWidget::setPlayPauseButtonToPause() -{ - m_logging->playPauseButton->setIcon(QIcon(":/logging/images/pause.png")); - if (m_iconOnlyButtons) { - m_logging->playPauseButton->setText(QString()); - } else { - m_logging->playPauseButton->setText(tr(" Pause")); - } -} - -void LoggingGadgetWidget::playPauseButtonAction() -{ - ReplayState replayState = loggingPlugin->getLogfile()->getReplayState(); + ReplayState replayState = (loggingPlugin->getLogfile())->getReplayState(); if (replayState == PLAYING) { emit pauseReplay(); - setPlayPauseButtonToPlay(); - } else { - emit resumeReplay(m_logging->playbackPosition->value()); - setPlayPauseButtonToPause(); } + + m_logging->playButton->setVisible(true); + m_logging->pauseButton->setVisible(false); m_logging->stopButton->setEnabled(true); + + m_logging->statusLabel->setText(tr("Paused")); } void LoggingGadgetWidget::stopButtonAction() { - ReplayState replayState = loggingPlugin->getLogfile()->getReplayState(); + ReplayState replayState = (loggingPlugin->getLogfile())->getReplayState(); if (replayState != STOPPED) { emit pauseReplayAndResetPosition(); } + + m_logging->playButton->setVisible(true); + m_logging->pauseButton->setVisible(false); m_logging->stopButton->setEnabled(false); - setPlayPauseButtonToPlay(); // Block signals while setting the slider to the start position m_logging->playbackPosition->blockSignals(true); m_logging->playbackPosition->setValue(m_logging->playbackPosition->minimum()); m_logging->playbackPosition->blockSignals(false); + + m_logging->statusLabel->setText(tr("Paused")); } void LoggingGadgetWidget::stateChanged(LoggingPlugin::State state) @@ -196,16 +162,20 @@ void LoggingGadgetWidget::stateChanged(LoggingPlugin::State state) m_logging->statusLabel->setText(status); bool playing = loggingPlugin->getLogfile()->isPlaying(); + m_logging->playButton->setEnabled(enabled); + m_logging->pauseButton->setEnabled(enabled); m_logging->stopButton->setEnabled(enabled && playing); - m_logging->playPauseButton->setEnabled(enabled); + if (playing) { - setPlayPauseButtonToPause(); + m_logging->playButton->setVisible(false); + m_logging->pauseButton->setVisible(true); } else { - setPlayPauseButtonToPlay(); + m_logging->playButton->setVisible(true); + m_logging->pauseButton->setVisible(false); } } -void LoggingGadgetWidget::updateBeginAndEndTimes(quint32 startTimeStamp, quint32 endTimeStamp) +void LoggingGadgetWidget::setBeginAndEndTimes(quint32 startTimeStamp, quint32 endTimeStamp) { int startSec, startMin, endSec, endMin; @@ -225,8 +195,6 @@ void LoggingGadgetWidget::updateBeginAndEndTimes(quint32 startTimeStamp, quint32 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::setPlaybackPosition(quint32 positionTimeStamp) @@ -243,65 +211,49 @@ void LoggingGadgetWidget::setPlaybackPosition(quint32 positionTimeStamp) } } -void LoggingGadgetWidget::enableButtons() +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); - setPlayPauseButtonToPlay(); + m_logging->playButton->setVisible(true); + m_logging->pauseButton->setVisible(false); break; case PLAYING: m_logging->stopButton->setEnabled(true); - setPlayPauseButtonToPause(); + m_logging->playButton->setVisible(false); + m_logging->pauseButton->setVisible(true); break; case PAUSED: m_logging->stopButton->setEnabled(true); - setPlayPauseButtonToPlay(); + m_logging->playButton->setVisible(true); + m_logging->pauseButton->setVisible(false); break; } - m_logging->playPauseButton->setEnabled(true); m_logging->playbackPosition->setEnabled(true); } -void LoggingGadgetWidget::disableButtons() +void LoggingGadgetWidget::disableWidgets() { - m_logging->playPauseButton->setEnabled(false); - setPlayPauseButtonToPlay(); + 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); -} -void LoggingGadgetWidget::updateButtonAppearance() -{ - ReplayState replayState; - - if (!loggingPlugin || !loggingPlugin->getLogfile()) { - // loggingPlugin has not been completely initialized: set to STOPPED state - replayState = STOPPED; - } else { - replayState = loggingPlugin->getLogfile()->getReplayState(); - } - - // Update playPause button appearance - if (replayState == PLAYING) { - // Playing: playPause button must appear as a pause button - setPlayPauseButtonToPause(); - } else { - // Stopped or Paused: playPause button must appear as a play button - setPlayPauseButtonToPlay(); - } - - // Update stop button appearance - if (m_iconOnlyButtons) { - m_logging->stopButton->setText(QString()); - } else { - m_logging->stopButton->setText(tr(" Stop")); - } + // reset start and end labels + m_logging->startTimeLabel->setText(""); + m_logging->endTimeLabel->setText(""); } void LoggingGadgetWidget::updatePositionLabel(quint32 positionTimeStamp) diff --git a/ground/gcs/src/plugins/logging/logginggadgetwidget.h b/ground/gcs/src/plugins/logging/logginggadgetwidget.h index b5f778759..f07f20304 100644 --- a/ground/gcs/src/plugins/logging/logginggadgetwidget.h +++ b/ground/gcs/src/plugins/logging/logginggadgetwidget.h @@ -50,12 +50,13 @@ public: protected slots: void stateChanged(LoggingPlugin::State state); - void updateBeginAndEndTimes(quint32 startTimeStamp, quint32 endTimeStamp); + void setBeginAndEndTimes(quint32 startTimeStamp, quint32 endTimeStamp); void setPlaybackPosition(quint32 positionTimeStamp); - void playPauseButtonAction(); + void playButtonAction(); + void pauseButtonAction(); void stopButtonAction(); - void enableButtons(); - void disableButtons(); + void enableWidgets(); + void disableWidgets(); void sliderMoved(int); void sliderAction(); @@ -69,14 +70,8 @@ private: LoggingPlugin *loggingPlugin; ScopeGadgetFactory *scpPlugin; QTimer sliderActionDelay; - bool m_iconOnlyButtons; - int m_preferredButtonWidth; void updatePositionLabel(quint32 positionTimeStamp); - void setPlayPauseButtonToPlay(); - void setPlayPauseButtonToPause(); - void updateButtonAppearance(); - void resizeEvent(QResizeEvent *event); }; #endif /* LoggingGADGETWIDGET_H_ */ From ba0869c2d24784d0e5a89edc1d5ba97049ffd330 Mon Sep 17 00:00:00 2001 From: Jan NIJS Date: Fri, 4 May 2018 22:20:54 +0200 Subject: [PATCH 7/8] LP-597 Don't disable playback when the end of the logfile has been reached. - instead, do the same as the user pressing the stop button. --- ground/gcs/src/libs/utils/logfile.cpp | 33 ++++++++++++-- ground/gcs/src/libs/utils/logfile.h | 4 +- ground/gcs/src/plugins/logging/logging.ui | 43 ++++++++++++++++--- .../plugins/logging/logginggadgetwidget.cpp | 14 ++---- 4 files changed, 73 insertions(+), 21 deletions(-) diff --git a/ground/gcs/src/libs/utils/logfile.cpp b/ground/gcs/src/libs/utils/logfile.cpp index 70a1060f1..547eb7f68 100644 --- a/ground/gcs/src/libs/utils/logfile.cpp +++ b/ground/gcs/src/libs/utils/logfile.cpp @@ -182,7 +182,7 @@ void LogFile::timerFired() qint64 dataSize; if (m_file.bytesAvailable() < (qint64)sizeof(dataSize)) { qDebug() << "LogFile replay - end of log file reached"; - stopReplay(); + resetReplay(); return; } m_file.read((char *)&dataSize, sizeof(dataSize)); @@ -197,7 +197,7 @@ void LogFile::timerFired() // read data if (m_file.bytesAvailable() < dataSize) { qDebug() << "LogFile replay - end of log file reached"; - stopReplay(); + resetReplay(); return; } QByteArray data = m_file.read(dataSize); @@ -216,7 +216,7 @@ void LogFile::timerFired() // read next timestamp if (m_file.bytesAvailable() < (qint64)sizeof(m_nextTimeStamp)) { qDebug() << "LogFile replay - end of log file reached"; - stopReplay(); + resetReplay(); return; } m_previousTimeStamp = m_nextTimeStamp; @@ -235,7 +235,7 @@ void LogFile::timerFired() } } else { qDebug() << "LogFile replay - end of log file reached"; - stopReplay(); + resetReplay(); } } @@ -319,6 +319,31 @@ bool LogFile::stopReplay() 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() * diff --git a/ground/gcs/src/libs/utils/logfile.h b/ground/gcs/src/libs/utils/logfile.h index 7e19c02a0..055fdabe2 100644 --- a/ground/gcs/src/libs/utils/logfile.h +++ b/ground/gcs/src/libs/utils/logfile.h @@ -97,7 +97,8 @@ protected slots: signals: 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 setPlaybackPosition(quint32); void setBeginAndEndTimes(quint32, quint32); @@ -127,6 +128,7 @@ private: QVector m_timeStampPositions; bool buildIndex(); + bool resetReplay(); }; #endif // LOGFILE_H diff --git a/ground/gcs/src/plugins/logging/logging.ui b/ground/gcs/src/plugins/logging/logging.ui index e9537c935..fff8804fc 100644 --- a/ground/gcs/src/plugins/logging/logging.ui +++ b/ground/gcs/src/plugins/logging/logging.ui @@ -32,7 +32,10 @@ 3 - + + + 4 + QLayout::SetNoConstraint @@ -50,13 +53,13 @@ 39 - 38 + 30 - + 39 - 38 + 30 @@ -88,7 +91,13 @@ 39 - 38 + 30 + + + + + 39 + 30 @@ -103,6 +112,22 @@ + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 4 + 20 + + + + @@ -114,7 +139,13 @@ 39 - 38 + 30 + + + + + 39 + 30 diff --git a/ground/gcs/src/plugins/logging/logginggadgetwidget.cpp b/ground/gcs/src/plugins/logging/logginggadgetwidget.cpp index 6a84b1179..42ae738a9 100644 --- a/ground/gcs/src/plugins/logging/logginggadgetwidget.cpp +++ b/ground/gcs/src/plugins/logging/logginggadgetwidget.cpp @@ -85,6 +85,7 @@ void LoggingGadgetWidget::setPlugin(LoggingPlugin *p) connect(logFile, &LogFile::setPlaybackPosition, 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); @@ -123,22 +124,15 @@ void LoggingGadgetWidget::pauseButtonAction() void LoggingGadgetWidget::stopButtonAction() { - ReplayState replayState = (loggingPlugin->getLogfile())->getReplayState(); - - if (replayState != STOPPED) { - emit pauseReplayAndResetPosition(); - } + emit pauseReplayAndResetPosition(); m_logging->playButton->setVisible(true); m_logging->pauseButton->setVisible(false); m_logging->stopButton->setEnabled(false); - // Block signals while setting the slider to the start position - m_logging->playbackPosition->blockSignals(true); - m_logging->playbackPosition->setValue(m_logging->playbackPosition->minimum()); - m_logging->playbackPosition->blockSignals(false); + setPlaybackPosition(0); - m_logging->statusLabel->setText(tr("Paused")); + m_logging->statusLabel->setText(tr("Stopped")); } void LoggingGadgetWidget::stateChanged(LoggingPlugin::State state) From bafc40fefa76c9286d5ac5b2d82ef6b7ee731b95 Mon Sep 17 00:00:00 2001 From: Jan NIJS Date: Sat, 5 May 2018 17:21:21 +0200 Subject: [PATCH 8/8] LP-597 fixes from review - m_timer_tick -> m_timerTick - rename signals: setPlaybackPosition -> playbackPositionChanged & setBeginAndEndTimes -> timesChanged - remove extraneous parentheses - make pretty --- ground/gcs/src/libs/utils/logfile.cpp | 12 ++++++------ ground/gcs/src/libs/utils/logfile.h | 10 +++++----- .../gcs/src/plugins/logging/logginggadgetwidget.cpp | 8 ++++---- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/ground/gcs/src/libs/utils/logfile.cpp b/ground/gcs/src/libs/utils/logfile.cpp index 547eb7f68..90d671e8a 100644 --- a/ground/gcs/src/libs/utils/logfile.cpp +++ b/ground/gcs/src/libs/utils/logfile.cpp @@ -42,7 +42,7 @@ LogFile::LogFile(QObject *parent) : QIODevice(parent), m_providedTimeStamp(0), m_beginTimeStamp(0), m_endTimeStamp(0), - m_timer_tick(0) + m_timerTick(0) { connect(&m_timer, &QTimer::timeout, this, &LogFile::timerFired); } @@ -154,7 +154,7 @@ void LogFile::timerFired() if (m_replayState != PLAYING) { return; } - m_timer_tick++; + m_timerTick++; if (m_file.bytesAvailable() > TIMESTAMP_SIZE_BYTES) { int time; @@ -210,8 +210,8 @@ void LogFile::timerFired() emit readyRead(); // rate-limit slider bar position updates to 10 updates per second - if (m_timer_tick % 10 == 0) { - emit setPlaybackPosition(m_nextTimeStamp); + if (m_timerTick % 10 == 0) { + emit playbackPositionChanged(m_nextTimeStamp); } // read next timestamp if (m_file.bytesAvailable() < (qint64)sizeof(m_nextTimeStamp)) { @@ -262,7 +262,7 @@ bool LogFile::startReplay() return false; } - m_timer_tick = 0; + m_timerTick = 0; if (!m_file.isOpen() || m_timer.isActive()) { return false; @@ -552,7 +552,7 @@ bool LogFile::buildIndex() } } - emit setBeginAndEndTimes(m_beginTimeStamp, m_endTimeStamp); + emit timesChanged(m_beginTimeStamp, m_endTimeStamp); // reset the read pointer to the start of the file m_file.seek(0); diff --git a/ground/gcs/src/libs/utils/logfile.h b/ground/gcs/src/libs/utils/logfile.h index 055fdabe2..f338cd6c3 100644 --- a/ground/gcs/src/libs/utils/logfile.h +++ b/ground/gcs/src/libs/utils/logfile.h @@ -97,10 +97,10 @@ protected slots: signals: void replayStarted(); - void replayFinished(); // Emitted on error during replay or when logfile disconnected - void replayCompleted(); // Emitted at the end of normal logfile playback - void setPlaybackPosition(quint32); - void setBeginAndEndTimes(quint32, quint32); + 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; @@ -123,7 +123,7 @@ private: qint32 m_providedTimeStamp; quint32 m_beginTimeStamp; quint32 m_endTimeStamp; - quint32 m_timer_tick; + quint32 m_timerTick; QVector m_timeStamps; QVector m_timeStampPositions; diff --git a/ground/gcs/src/plugins/logging/logginggadgetwidget.cpp b/ground/gcs/src/plugins/logging/logginggadgetwidget.cpp index 42ae738a9..b559ef652 100644 --- a/ground/gcs/src/plugins/logging/logginggadgetwidget.cpp +++ b/ground/gcs/src/plugins/logging/logginggadgetwidget.cpp @@ -81,8 +81,8 @@ void LoggingGadgetWidget::setPlugin(LoggingPlugin *p) // Feedback from logfile to GUI connect(loggingPlugin, &LoggingPlugin::stateChanged, this, &LoggingGadgetWidget::stateChanged); - connect(logFile, &LogFile::setBeginAndEndTimes, this, &LoggingGadgetWidget::setBeginAndEndTimes); - connect(logFile, &LogFile::setPlaybackPosition, this, &LoggingGadgetWidget::setPlaybackPosition); + 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); @@ -96,7 +96,7 @@ void LoggingGadgetWidget::setPlugin(LoggingPlugin *p) void LoggingGadgetWidget::playButtonAction() { - ReplayState replayState = (loggingPlugin->getLogfile())->getReplayState(); + ReplayState replayState = loggingPlugin->getLogfile()->getReplayState(); if (replayState != PLAYING) { emit resumeReplay(m_logging->playbackPosition->value()); @@ -109,7 +109,7 @@ void LoggingGadgetWidget::playButtonAction() void LoggingGadgetWidget::pauseButtonAction() { - ReplayState replayState = (loggingPlugin->getLogfile())->getReplayState(); + ReplayState replayState = loggingPlugin->getLogfile()->getReplayState(); if (replayState == PLAYING) { emit pauseReplay();