diff --git a/qgroundcontrol.pro b/qgroundcontrol.pro index 33ffa7ea068d6885e8b06b437c69bfcb9e031db1..9dcea3cdf1b5ea9d1a24502468bd7f1f7dcb03c8 100644 --- a/qgroundcontrol.pro +++ b/qgroundcontrol.pro @@ -604,6 +604,7 @@ HEADERS += \ src/comm/LinkConfiguration.h \ src/comm/LinkInterface.h \ src/comm/LinkManager.h \ + src/comm/LogReplayLink.h \ src/comm/MAVLinkProtocol.h \ src/comm/ProtocolInterface.h \ src/comm/QGCMAVLink.h \ @@ -664,7 +665,6 @@ HEADERS += \ src/GPS/vehicle_gps_position.h \ src/Joystick/JoystickSDL.h \ src/RunGuard.h \ - src/comm/LogReplayLink.h \ src/comm/QGCHilLink.h \ src/comm/QGCJSBSimLink.h \ src/comm/QGCXPlaneLink.h \ @@ -780,6 +780,7 @@ SOURCES += \ src/comm/LinkConfiguration.cc \ src/comm/LinkInterface.cc \ src/comm/LinkManager.cc \ + src/comm/LogReplayLink.cc \ src/comm/MAVLinkProtocol.cc \ src/comm/QGCMAVLink.cc \ src/comm/TCPLink.cc \ @@ -822,7 +823,6 @@ SOURCES += \ src/GPS/RTCM/RTCMMavlink.cc \ src/Joystick/JoystickSDL.cc \ src/RunGuard.cc \ - src/comm/LogReplayLink.cc \ src/comm/QGCJSBSimLink.cc \ src/comm/QGCXPlaneLink.cc \ src/uas/FileManager.cc \ diff --git a/qgroundcontrol.qrc b/qgroundcontrol.qrc index d5517633cc55309b478d40fddc9302aedd7c3bb3..8de7ae380fead56ae74a6508e900e9851b821419 100644 --- a/qgroundcontrol.qrc +++ b/qgroundcontrol.qrc @@ -77,6 +77,7 @@ src/PlanView/GeoFenceMapVisuals.qml src/QmlControls/IndicatorButton.qml src/QmlControls/JoystickThumbPad.qml + src/QmlControls/LogReplayStatusBar.qml src/QmlControls/MAVLinkMessageButton.qml src/QmlControls/MissionCommandDialog.qml src/PlanView/MissionItemEditor.qml diff --git a/src/FlightDisplay/FlightDisplayView.qml b/src/FlightDisplay/FlightDisplayView.qml index 5f913079dee2a250d2954e82de3a59d7115fdcb8..4ee65f955204d5429338cbd04e23f85b4ab09d67 100644 --- a/src/FlightDisplay/FlightDisplayView.qml +++ b/src/FlightDisplay/FlightDisplayView.qml @@ -312,7 +312,10 @@ Item { Item { id: _mapAndVideo - anchors.fill: parent + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: logReplayStatusBar.top //-- Map View Item { @@ -737,6 +740,14 @@ Item { } } + LogReplayStatusBar { + id: logReplayStatusBar + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + visible: QGroundControl.settingsManager.flyViewSettings.showLogReplayStatusBar.rawValue &&_flightMapContainer.state === "fullMode" + } + //-- Airspace Indicator Rectangle { id: airspaceIndicator diff --git a/src/FlightDisplay/FlightDisplayViewMap.qml b/src/FlightDisplay/FlightDisplayViewMap.qml index a5ef10dbfbe76ec43663955e6fbc54efc7c60d4b..6aaea9570ccc77265b0ac030cd2e23f657b59382 100644 --- a/src/FlightDisplay/FlightDisplayViewMap.qml +++ b/src/FlightDisplay/FlightDisplayViewMap.qml @@ -26,7 +26,6 @@ import QGroundControl.Vehicle 1.0 FlightMap { id: flightMap - anchors.fill: parent mapName: _mapName allowGCSLocationCenter: !userPanned allowVehicleLocationCenter: !_keepVehicleCentered diff --git a/src/QGCApplication.cc b/src/QGCApplication.cc index 8c4cf014c352839a8fa10aea7dacea8d2ede10fe..d258e8c31aa8b976b32eba4b358507c059b6551c 100644 --- a/src/QGCApplication.cc +++ b/src/QGCApplication.cc @@ -100,6 +100,7 @@ #include "MavlinkConsoleController.h" #include "MAVLinkInspectorController.h" #include "GeoTagController.h" +#include "LogReplayLink.h" #ifndef __mobile__ #include "FirmwareUpgradeController.h" @@ -485,6 +486,9 @@ void QGCApplication::_initCommon() qmlRegisterUncreatableType ("QGroundControl", 1, 0, "QmlObjectListModel", kRefOnly); qmlRegisterUncreatableType ("QGroundControl", 1, 0, "MissionCommandTree", kRefOnly); qmlRegisterUncreatableType ("QGroundControl", 1, 0, "CameraCalc", kRefOnly); + qmlRegisterUncreatableType ("QGroundControl", 1, 0, "LogReplayLink", kRefOnly); + + qmlRegisterType ("QGroundControl", 1, 0, "LogReplayLinkController"); qmlRegisterUncreatableType ("QGroundControl.AutoPilotPlugin", 1, 0, "AutoPilotPlugin", kRefOnly); qmlRegisterUncreatableType ("QGroundControl.AutoPilotPlugin", 1, 0, "VehicleComponent", kRefOnly); diff --git a/src/QmlControls/LogReplayStatusBar.qml b/src/QmlControls/LogReplayStatusBar.qml new file mode 100644 index 0000000000000000000000000000000000000000..98d67d642163aaf5a2b0477419efb0106520aea8 --- /dev/null +++ b/src/QmlControls/LogReplayStatusBar.qml @@ -0,0 +1,91 @@ +import QtQuick 2.3 +import QtQuick.Controls 1.2 +import QtQuick.Layouts 1.11 +import QtQuick.Dialogs 1.2 + +import QGroundControl 1.0 +import QGroundControl.Palette 1.0 +import QGroundControl.ScreenTools 1.0 + +Rectangle { + height: visible ? (rowLayout.height + (_margins * 2)) : 0 + color: qgcPal.window + + property real _margins: ScreenTools.defaultFontPixelHeight / 4 + property var _logReplayLink: null + + function pickLogFile() { + if (mainWindow.activeVehicle) { + mainWindow.showMessageDialog(qsTr("Log Replay"), qsTr("You must close all connections prior to replaying a log."), StandardButton.Ok) + return + } + + filePicker.openForLoad() + } + + QGCPalette { id: qgcPal } + + QGCFileDialog { + id: filePicker + title: qsTr("Select Telemetery Log") + nameFilters: [qsTr("Telemetry Logs (*.%1)").arg(QGroundControl.settingsManager.appSettings.telemetryFileExtension), qsTr("All Files (*)")] + selectExisting: true + folder: QGroundControl.settingsManager.appSettings.telemetrySavePath + onAcceptedForLoad: { + controller.link = QGroundControl.linkManager.startLogReplay(file) + close() + } + } + + LogReplayLinkController { + id: controller + + onPercentCompleteChanged: slider.updatePercentComplete(percentComplete) + } + + RowLayout { + id: rowLayout + anchors.margins: _margins + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + QGCButton { + text: controller.isPlaying ? qsTr("Pause") : qsTr("Play") + enabled: controller.link + onClicked: controller.isPlaying = !controller.isPlaying + } + + QGCLabel { text: controller.playheadTime } + + Slider { + id: slider + Layout.fillWidth: true + minimumValue: 0 + maximumValue: 100 + enabled: controller.link + + property bool manualUpdate: false + + function updatePercentComplete(percentComplete) { + manualUpdate = true + value = percentComplete + manualUpdate = false + } + + onValueChanged: { + if (!manualUpdate) { + controller.percentComplete = value + } + } + } + + QGCLabel { text: controller.totalTime } + + QGCButton { + text: qsTr("Load Telemetry Log") + onClicked: pickLogFile() + visible: !controller.link + } + } +} diff --git a/src/QmlControls/QGroundControl/Controls/qmldir b/src/QmlControls/QGroundControl/Controls/qmldir index c4850c1067d53e54e747b75ab13f938301416a45..b96999f620ba92cb538c4027df64d75888222fc6 100644 --- a/src/QmlControls/QGroundControl/Controls/qmldir +++ b/src/QmlControls/QGroundControl/Controls/qmldir @@ -20,6 +20,7 @@ GeoFenceMapVisuals 1.0 GeoFenceMapVisuals.qml HackFileDialog 1.0 HackFileDialog.qml IndicatorButton 1.0 IndicatorButton.qml JoystickThumbPad 1.0 JoystickThumbPad.qml +LogReplayStatusBar 1.0 LogReplayStatusBar.qml MAVLinkMessageButton 1.0 MAVLinkMessageButton.qml MissionCommandDialog 1.0 MissionCommandDialog.qml MissionItemEditor 1.0 MissionItemEditor.qml diff --git a/src/comm/LinkConfiguration.cc b/src/comm/LinkConfiguration.cc index e6a61144de8f36318c0e14b0580a800ebe95e2d8..6168ccf6ea3c4dc7085aca7abab8bb7b21dc432f 100644 --- a/src/comm/LinkConfiguration.cc +++ b/src/comm/LinkConfiguration.cc @@ -20,9 +20,7 @@ #endif #include "UDPLink.h" #include "TCPLink.h" -#if !defined(__mobile__) #include "LogReplayLink.h" -#endif #ifdef QGC_ENABLE_BLUETOOTH #include "BluetoothLink.h" #endif @@ -98,11 +96,9 @@ LinkConfiguration* LinkConfiguration::createSettings(int type, const QString& na config = new BluetoothConfiguration(name); break; #endif -#ifndef __mobile__ case LinkConfiguration::TypeLogReplay: config = new LogReplayLinkConfiguration(name); break; -#endif #ifdef QT_DEBUG case LinkConfiguration::TypeMock: config = new MockConfiguration(name); @@ -136,11 +132,9 @@ LinkConfiguration* LinkConfiguration::duplicateSettings(LinkConfiguration* sourc dupe = new BluetoothConfiguration(dynamic_cast(source)); break; #endif -#ifndef __mobile__ case TypeLogReplay: dupe = new LogReplayLinkConfiguration(dynamic_cast(source)); break; -#endif #ifdef QT_DEBUG case TypeMock: dupe = new MockConfiguration(dynamic_cast(source)); diff --git a/src/comm/LinkConfiguration.h b/src/comm/LinkConfiguration.h index a32e65f53e2caa0c0189315e43c3faf1a36adb30..94f37cf9e5cd469c6d386514c7bc1240fe5ee668 100644 --- a/src/comm/LinkConfiguration.h +++ b/src/comm/LinkConfiguration.h @@ -57,9 +57,7 @@ public: #ifdef QT_DEBUG TypeMock, ///< Mock Link for Unitesting #endif -#ifndef __mobile__ TypeLogReplay, -#endif TypeLast // Last type value (type >= TypeLast == invalid) }; Q_ENUM(LinkType) diff --git a/src/comm/LinkManager.cc b/src/comm/LinkManager.cc index 9724cab9c75dd71fa9f0ab81bf6e3bb14c7367a3..4ed7a1719b6f0ec7607368cd0090a68e89327174 100644 --- a/src/comm/LinkManager.cc +++ b/src/comm/LinkManager.cc @@ -21,6 +21,7 @@ #include "UDPLink.h" #include "TCPLink.h" #include "SettingsManager.h" +#include "LogReplayLink.h" #ifdef QGC_ENABLE_BLUETOOTH #include "BluetoothLink.h" #endif @@ -139,11 +140,9 @@ LinkInterface* LinkManager::createConnectedLink(SharedLinkConfigurationPointer& pLink = new BluetoothLink(config); break; #endif -#ifndef __mobile__ case LinkConfiguration::TypeLogReplay: pLink = new LogReplayLink(config); break; -#endif #ifdef QT_DEBUG case LinkConfiguration::TypeMock: pLink = new MockLink(config); @@ -393,11 +392,9 @@ void LinkManager::loadLinkConfigurationList() pLink = dynamic_cast(new BluetoothConfiguration(name)); break; #endif -#ifndef __mobile__ case LinkConfiguration::TypeLogReplay: pLink = dynamic_cast(new LogReplayLinkConfiguration(name)); break; -#endif #ifdef QT_DEBUG case LinkConfiguration::TypeMock: pLink = dynamic_cast(new MockConfiguration(name)); @@ -847,7 +844,6 @@ void LinkManager::_fixUnnamed(LinkConfiguration* config) } break; #endif -#ifndef __mobile__ case LinkConfiguration::TypeLogReplay: { LogReplayLinkConfiguration* tconfig = dynamic_cast(config); if(tconfig) { @@ -855,7 +851,6 @@ void LinkManager::_fixUnnamed(LinkConfiguration* config) } } break; -#endif #ifdef QT_DEBUG case LinkConfiguration::TypeMock: config->setName(QString("Mock Link")); @@ -1015,3 +1010,13 @@ void LinkManager::_freeMavlinkChannel(int channel) void LinkManager::_mavlinkMessageReceived(LinkInterface* link, mavlink_message_t message) { link->startMavlinkMessagesTimer(message.sysid); } + +LogReplayLink* LinkManager::startLogReplay(const QString& logFile) +{ + LogReplayLinkConfiguration* linkConfig = new LogReplayLinkConfiguration(tr("Log Replay")); + linkConfig->setLogFilename(logFile); + linkConfig->setName(linkConfig->logFilenameShort()); + + SharedLinkConfigurationPointer sharedConfig = addConfiguration(linkConfig); + return qobject_cast(createConnectedLink(sharedConfig)); +} diff --git a/src/comm/LinkManager.h b/src/comm/LinkManager.h index 8322e1c6f593296ac3e64155226efa060ada8706..e6f278bc75dae230356901f7c7de5522936f3c55 100644 --- a/src/comm/LinkManager.h +++ b/src/comm/LinkManager.h @@ -43,6 +43,7 @@ Q_DECLARE_LOGGING_CATEGORY(LinkManagerVerboseLog) class QGCApplication; class UDPConfiguration; class AutoConnectSettings; +class LogReplayLink; /// Manage communication links /// @@ -129,6 +130,8 @@ public: // Called to signal app shutdown. Disconnects all links while turning off auto-connect. Q_INVOKABLE void shutdown(void); + Q_INVOKABLE LogReplayLink* startLogReplay(const QString& logFile); + #ifdef QT_DEBUG // Only used by unit test tp restart after a shutdown void restart(void) { setConnectionsAllowed(); } diff --git a/src/comm/LogReplayLink.cc b/src/comm/LogReplayLink.cc index 579b45322e9d84da8ea2816f6d3d607160b06bfc..06cbbe574163e4a8029dddbb8dc56b547b84a8db 100644 --- a/src/comm/LogReplayLink.cc +++ b/src/comm/LogReplayLink.cc @@ -19,13 +19,13 @@ const char* LogReplayLinkConfiguration::_logFilenameKey = "logFilename"; LogReplayLinkConfiguration::LogReplayLinkConfiguration(const QString& name) - : LinkConfiguration(name) + : LinkConfiguration(name) { } LogReplayLinkConfiguration::LogReplayLinkConfiguration(LogReplayLinkConfiguration* copy) - : LinkConfiguration(copy) + : LinkConfiguration(copy) { _logFilename = copy->logFilename(); } @@ -448,7 +448,7 @@ void LogReplayLink::_resetPlaybackToBeginning(void) _logCurrentTimeUSecs = _logStartTimeUSecs; } -void LogReplayLink::movePlayhead(int percentComplete) +void LogReplayLink::movePlayhead(qreal percentComplete) { if (isPlaying()) { _pauseOnThread(); @@ -459,17 +459,19 @@ void LogReplayLink::movePlayhead(int percentComplete) } } - if (percentComplete < 0 || percentComplete > 100) { - qWarning() << "Bad percentage value" << percentComplete; - return; + if (percentComplete < 0) { + percentComplete = 0; + } + if (percentComplete > 100) { + percentComplete = 100; } - float floatPercentComplete = (float)percentComplete / 100.0f; + qreal percentCompleteMult = percentComplete / 100.0; if (_logTimestamped) { // But if we have a timestamped MAVLink log, then actually aim to hit that percentage in terms of // time through the file. - qint64 newFilePos = (qint64)(floatPercentComplete * (float)_logFile.size()); + qint64 newFilePos = (qint64)(percentCompleteMult * (qreal)_logFile.size()); // Now seek to the appropriate position, failing gracefully if we can't. if (!_logFile.seek(newFilePos)) { @@ -482,13 +484,13 @@ void LogReplayLink::movePlayhead(int percentComplete) _logCurrentTimeUSecs = _seekToNextMavlinkMessage(&dummy); // Now calculate the current file location based on time. - float newRelativeTimeUSecs = (float)(_logCurrentTimeUSecs - _logStartTimeUSecs); + qreal newRelativeTimeUSecs = (qreal)(_logCurrentTimeUSecs - _logStartTimeUSecs); // Calculate the effective baud rate of the file in bytes/s. - float baudRate = _logFile.size() / (float)_logDurationUSecs / 1e6; + qreal baudRate = _logFile.size() / (qreal)_logDurationUSecs / 1e6; // And the desired time is: - float desiredTimeUSecs = floatPercentComplete * _logDurationUSecs; + qreal desiredTimeUSecs = percentCompleteMult * _logDurationUSecs; // And now jump the necessary number of bytes in the proper direction qint64 offset = (newRelativeTimeUSecs - desiredTimeUSecs) * baudRate; @@ -503,13 +505,13 @@ void LogReplayLink::movePlayhead(int percentComplete) _signalCurrentLogTimeSecs(); // Now update the UI with our actual final position. - newRelativeTimeUSecs = (float)(_logCurrentTimeUSecs - _logStartTimeUSecs); + newRelativeTimeUSecs = (qreal)(_logCurrentTimeUSecs - _logStartTimeUSecs); percentComplete = (newRelativeTimeUSecs / _logDurationUSecs) * 100; emit playbackPercentCompleteChanged(percentComplete); } else { // If we're working with a non-timestamped file, we just jump to that percentage of the file, // align to the next MAVLink message and roll with it. No reason to do anything more complicated. - qint64 newFilePos = (qint64)(floatPercentComplete * (float)_logFile.size()); + qint64 newFilePos = (qint64)(percentCompleteMult * (qreal)_logFile.size()); // Now seek to the appropriate position, failing gracefully if we can't. if (!_logFile.seek(newFilePos)) { @@ -560,15 +562,117 @@ void LogReplayLink::_finishPlayback(void) emit playbackAtEnd(); } -/// @brief Called when an error occurs during playback to reset playback system state. -void LogReplayLink::_playbackError(void) +void LogReplayLink::_signalCurrentLogTimeSecs(void) { - _pause(); - _logFile.close(); - emit playbackError(); + emit currentLogTimeSecs((_logCurrentTimeUSecs - _logStartTimeUSecs) / 1000000); } -void LogReplayLink::_signalCurrentLogTimeSecs(void) +LogReplayLinkController::LogReplayLinkController(void) + : _link (nullptr) + , _isPlaying (false) + , _percentComplete (0) + , _playheadSecs (0) { - emit currentLogTimeSecs((_logCurrentTimeUSecs - _logStartTimeUSecs) / 1000000); + +} + +void LogReplayLinkController::setLink(LogReplayLink* link) +{ + if (_link) { + disconnect(_link); + _isPlaying = false; + _percentComplete = 0; + _playheadTime.clear(); + _totalTime.clear(); + _link = nullptr; + emit isPlayingChanged(false); + emit percentCompleteChanged(0); + emit playheadTimeChanged(QString()); + emit totalTimeChanged(QString()); + emit linkChanged(nullptr); + } + + + if (link) { + _link = link; + connect(_link, &LogReplayLink::logFileStats, this, &LogReplayLinkController::_logFileStats); + connect(_link, &LogReplayLink::playbackStarted, this, &LogReplayLinkController::_playbackStarted); + connect(_link, &LogReplayLink::playbackPaused, this, &LogReplayLinkController::_playbackPaused); + connect(_link, &LogReplayLink::playbackPercentCompleteChanged, this, &LogReplayLinkController::_playbackPercentCompleteChanged); + connect(_link, &LogReplayLink::currentLogTimeSecs, this, &LogReplayLinkController::_currentLogTimeSecs); + connect(_link, &LogReplayLink::disconnected, this, &LogReplayLinkController::_linkDisconnected); + emit linkChanged(_link); + } +} + +void LogReplayLinkController::setIsPlaying(bool isPlaying) +{ + if (isPlaying) { + _link->play(); + } else { + _link->pause(); + } +} + +void LogReplayLinkController::setPercentComplete(qreal percentComplete) +{ + _link->movePlayhead(percentComplete); +} + +void LogReplayLinkController::_logFileStats(bool logTimestamped, int logDurationSecs, int binaryBaudRate) +{ + Q_UNUSED(logTimestamped); + Q_UNUSED(binaryBaudRate); + + _totalTime = _secondsToHMS(logDurationSecs); + emit totalTimeChanged(_totalTime); +} + +void LogReplayLinkController::_playbackStarted(void) +{ + _isPlaying = true; + emit isPlayingChanged(true); +} + +void LogReplayLinkController::_playbackPaused(void) +{ + _isPlaying = false; + emit isPlayingChanged(true); +} + +void LogReplayLinkController::_playbackAtEnd(void) +{ + _isPlaying = false; + emit isPlayingChanged(true); +} + +void LogReplayLinkController::_playbackPercentCompleteChanged(qreal percentComplete) +{ + _percentComplete = percentComplete; + emit percentCompleteChanged(_percentComplete); +} + +void LogReplayLinkController::_currentLogTimeSecs(int secs) +{ + if (_playheadSecs != secs) { + _playheadSecs = secs; + _playheadTime = _secondsToHMS(secs); + emit playheadTimeChanged(_playheadTime); + } +} + +void LogReplayLinkController::_linkDisconnected(void) +{ + setLink(nullptr); +} + +QString LogReplayLinkController::_secondsToHMS(int seconds) +{ + int secondsPart = seconds; + int minutesPart = secondsPart / 60; + int hoursPart = minutesPart / 60; + secondsPart -= 60 * minutesPart; + minutesPart -= 60 * hoursPart; + + return tr("%1h:%2m:%3s").arg(hoursPart, 2).arg(minutesPart, 2).arg(secondsPart, 2); } diff --git a/src/comm/LogReplayLink.h b/src/comm/LogReplayLink.h index fb5de6ee6abd74edca13dfbaf483cd84b3490c73..6bb8bc17083174584eb707fb91c6f468cd4d446a 100644 --- a/src/comm/LogReplayLink.h +++ b/src/comm/LogReplayLink.h @@ -7,11 +7,9 @@ * ****************************************************************************/ - #pragma once -#include "LinkInterface.h" -#include "LinkConfiguration.h" +#include "LinkManager.h" #include "MAVLinkProtocol.h" #include @@ -22,7 +20,6 @@ class LogReplayLinkConfiguration : public LinkConfiguration Q_OBJECT public: - Q_PROPERTY(QString fileName READ logFilename WRITE setLogFilename NOTIFY fileNameChanged) LogReplayLinkConfiguration(const QString& name); @@ -41,6 +38,7 @@ public: void updateSettings (); QString settingsURL () { return "LogReplaySettings.qml"; } QString settingsTitle () { return tr("Log Replay Link Settings"); } + signals: void fileNameChanged(); @@ -66,7 +64,7 @@ public: void pause(void) { emit _pauseOnThread(); } /// Move the playhead to the specified percent complete - void movePlayhead(int percentComplete); + void movePlayhead(qreal percentComplete); /// Sets the acceleration factor: -100: 0.01X, 0: 1.0X, 100: 100.0X void setAccelerationFactor(int factor) { emit _setAccelerationFactorOnThread(factor); } @@ -92,8 +90,7 @@ signals: void playbackStarted(void); void playbackPaused(void); void playbackAtEnd(void); - void playbackError(void); - void playbackPercentCompleteChanged(int percentComplete); + void playbackPercentCompleteChanged(qreal percentComplete); void currentLogTimeSecs(int secs); // Internal signals @@ -118,7 +115,6 @@ private: quint64 _readNextMavlinkMessage(QByteArray& bytes); bool _loadLogFile(void); void _finishPlayback(void); - void _playbackError(void); void _resetPlaybackToBeginning(void); void _signalCurrentLogTimeSecs(void); @@ -156,3 +152,51 @@ private: static const int cbTimestamp = sizeof(quint64); }; +class LogReplayLinkController : public QObject +{ + Q_OBJECT + +public: + Q_PROPERTY(LogReplayLink* link READ link WRITE setLink NOTIFY linkChanged) + Q_PROPERTY(bool isPlaying READ isPlaying WRITE setIsPlaying NOTIFY isPlayingChanged) + Q_PROPERTY(qreal percentComplete READ percentComplete WRITE setPercentComplete NOTIFY percentCompleteChanged) + Q_PROPERTY(QString totalTime MEMBER _totalTime NOTIFY totalTimeChanged) + Q_PROPERTY(QString playheadTime MEMBER _playheadTime NOTIFY playheadTimeChanged) + + LogReplayLinkController(void); + + LogReplayLink* link (void) { return _link; } + bool isPlaying (void) { return _isPlaying; } + qreal percentComplete (void) { return _percentComplete; } + + void setLink (LogReplayLink* link); + void setIsPlaying (bool isPlaying); + void setPercentComplete (qreal percentComplete); + +signals: + void linkChanged (LogReplayLink* link); + void isPlayingChanged (bool isPlaying); + void percentCompleteChanged (qreal percentComplete); + void playheadTimeChanged (QString playheadTime); + void totalTimeChanged (QString totalTime); + +private slots: + void _logFileStats (bool logTimestamped, int logDurationSecs, int binaryBaudRate); + void _playbackStarted (void); + void _playbackPaused (void); + void _playbackAtEnd (void); + void _playbackPercentCompleteChanged (qreal percentComplete); + void _currentLogTimeSecs (int secs); + void _linkDisconnected (void); + +private: + QString _secondsToHMS(int seconds); + + LogReplayLink* _link; + bool _isPlaying; + qreal _percentComplete; + int _playheadSecs; + QString _playheadTime; + QString _totalTime; +}; +