From 05e89e5c348c5a7d900f26ac4d46bb9cdbdbe459 Mon Sep 17 00:00:00 2001 From: Michel Dagenais Date: Sat, 20 Jun 2020 13:41:19 -0400 Subject: [PATCH] Add control to Video Player to select among several Meter Widget layouts (#3515) The video layout file is extended to contain possibly several named layouts. The file is read to list the layouts and offer a selection in the Video Player chart settings menu. The file is then read again to instantiate the selected layout. --- src/Core/Context.h | 3 +- src/Resources/xml/video-layout.xml | 93 ++++++++++++++++++--- src/Train/VideoLayoutParser.cpp | 28 ++++++- src/Train/VideoLayoutParser.h | 6 +- src/Train/VideoWindow.cpp | 129 +++++++++++++++++++---------- src/Train/VideoWindow.h | 12 +++ 6 files changed, 214 insertions(+), 57 deletions(-) diff --git a/src/Core/Context.h b/src/Core/Context.h index e5d081650..39506a106 100644 --- a/src/Core/Context.h +++ b/src/Core/Context.h @@ -97,6 +97,7 @@ class Context : public QObject DateRange dr_; ErgFile *workout; // the currently selected workout file VideoSyncFile *videosync; // the currently selected videosync file + QString videoFilename; long now; // point in time during train session SpecialFields specialFields; @@ -155,7 +156,7 @@ class Context : public QObject void notifyVideoSyncFileSelected(VideoSyncFile *x) { videosync=x; videoSyncFileSelected(x); } ErgFile *currentErgFile() { return workout; } VideoSyncFile *currentVideoSyncFile() { return videosync; } - void notifyMediaSelected( QString x) { mediaSelected(x); } + void notifyMediaSelected( QString x) { videoFilename = x; mediaSelected(x); } void notifySelectVideo(QString x) { selectMedia(x); } void notifySelectWorkout(QString x) { selectWorkout(x); } void notifySelectVideoSync(QString x) { selectVideoSync(x); } diff --git a/src/Resources/xml/video-layout.xml b/src/Resources/xml/video-layout.xml index 5f5dad050..6fe8b562d 100644 --- a/src/Resources/xml/video-layout.xml +++ b/src/Resources/xml/video-layout.xml @@ -1,4 +1,5 @@ - + + @@ -83,13 +84,85 @@ load - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Watts + + + + + + + + + + + + + + + RPM + + + + + + + + + + + + + + + + + + + + diff --git a/src/Train/VideoLayoutParser.cpp b/src/Train/VideoLayoutParser.cpp index 3c44b807f..f16bd2a7d 100644 --- a/src/Train/VideoLayoutParser.cpp +++ b/src/Train/VideoLayoutParser.cpp @@ -26,10 +26,12 @@ #include "VideoLayoutParser.h" #include "MeterWidget.h" -VideoLayoutParser::VideoLayoutParser (QList* metersWidget, QWidget* VideoContainer) - : metersWidget(metersWidget), VideoContainer(VideoContainer) +VideoLayoutParser::VideoLayoutParser (QList* metersWidget, QList* layoutNames, QWidget* VideoContainer) + : metersWidget(metersWidget), layoutNames(layoutNames), VideoContainer(VideoContainer) { nonameindex = 0; + skipLayout = false; + layoutPosition = 0; meterWidget = NULL; } @@ -75,9 +77,18 @@ bool VideoLayoutParser::startElement( const QString&, const QString&, const QString& qName, const QXmlAttributes& qAttributes) { + if(skipLayout) return true; + buffer.clear(); - if(qName == "meter") + if(qName == "layout") + { + int i = qAttributes.index("name"); + layoutNames->append(i >= 0 ? qAttributes.value(i) : QString("noname_") + QString::number(layoutPosition)); + + if(layoutPositionSelected != layoutPosition) skipLayout = true; + } + else if(qName == "meter") { int i = qAttributes.index("name"); // To be used to enclose another sub meter (typ. text within NeedleMeter) if(i >= 0) @@ -207,6 +218,15 @@ bool VideoLayoutParser::startElement( const QString&, const QString&, bool VideoLayoutParser::endElement( const QString&, const QString&, const QString& qName) { + if(qName == "layout") + { + layoutPosition++; + skipLayout = false; + return true; + } + + if(skipLayout) return true; + if (meterWidget) { if (qName == "Angle") @@ -231,6 +251,8 @@ bool VideoLayoutParser::endElement( const QString&, const QString&, const QStrin bool VideoLayoutParser::characters( const QString& str ) { + if(skipLayout) return true; + buffer += str; return true; } diff --git a/src/Train/VideoLayoutParser.h b/src/Train/VideoLayoutParser.h index 5e3e61ee6..e0c527700 100644 --- a/src/Train/VideoLayoutParser.h +++ b/src/Train/VideoLayoutParser.h @@ -32,21 +32,25 @@ class VideoLayoutParser : public QXmlDefaultHandler { public: - VideoLayoutParser(QList* metersWidget, QWidget* VideoContainer); + VideoLayoutParser(QList* metersWidget, QList* layoutNames, QWidget* VideoContainer); bool startElement( const QString&, const QString&, const QString&, const QXmlAttributes& ); bool endElement( const QString&, const QString&, const QString& ); bool characters( const QString& ); void SetDefaultValues(); + int layoutPositionSelected; private: QList* metersWidget; + QList* layoutNames; QWidget* VideoContainer; QString buffer; int nonameindex; + int layoutPosition; + bool skipLayout; QString source; QString meterName; diff --git a/src/Train/VideoWindow.cpp b/src/Train/VideoWindow.cpp index e08995654..2db10f67a 100644 --- a/src/Train/VideoWindow.cpp +++ b/src/Train/VideoWindow.cpp @@ -30,7 +30,7 @@ VideoWindow::VideoWindow(Context *context) : GcChartWindow(context), context(context), m_MediaChanged(false) { - setControls(NULL); + QWidget *c = NULL; setProperty("color", QColor(Qt::black)); QHBoxLayout *layout = new QHBoxLayout(); @@ -80,38 +80,25 @@ VideoWindow::VideoWindow(Context *context) : #endif #if defined(WIN32) || defined(Q_OS_LINUX) - // Video Overlays Initialization: if video config file is not present - // copy a default one to be used as a model by the user. - // An empty video-layout.xml file disables video overlays - QString filename = context->athlete->home->config().canonicalPath() + "/" + "video-layout.xml"; - QFile file(filename); - if (!file.exists()) - { - file.setFileName(":/xml/video-layout.xml"); - file.copy(filename); - QFile::setPermissions(filename, QFileDevice::ReadUser|QFileDevice::WriteUser); - } - if (file.exists()) - { - // clean previous layout - foreach(MeterWidget* p_meterWidget, m_metersWidget) - { - m_metersWidget.removeAll(p_meterWidget); - delete p_meterWidget; - } - VideoLayoutParser handler(&m_metersWidget, container); + // Read the video layouts just to list the names for the layout selector + readVideoLayout(-1); - QXmlInputSource source (&file); - QXmlSimpleReader reader; - reader.setContentHandler (&handler); + // Create the layout selector form + c = new QWidget; + QVBoxLayout *cl = new QVBoxLayout(c); + QFormLayout *controlsLayout = new QFormLayout(); + controlsLayout->setSpacing(0); + controlsLayout->setContentsMargins(5,5,5,5); + cl->addLayout(controlsLayout); + QLabel *layoutLabel = new QLabel(tr("Video meters layout"), this); + layoutLabel->setAutoFillBackground(true); + layoutSelector = new QComboBox(this); - reader.parse (source); - } - else - { - qDebug() << qPrintable(QString("file" + filename + " (video layout XML file) not found")); + for(int i = 0; i < layoutNames.length(); i++) { + layoutSelector->addItem(layoutNames[i], i); } + controlsLayout->addRow(layoutLabel, layoutSelector); #endif } else { @@ -131,6 +118,8 @@ VideoWindow::VideoWindow(Context *context) : layout->addWidget(wd); #endif + setControls(c); + if (init) { // get updates.. connect(context, SIGNAL(telemetryUpdate(RealtimeData)), this, SLOT(telemetryUpdate(RealtimeData))); @@ -140,6 +129,15 @@ VideoWindow::VideoWindow(Context *context) : connect(context, SIGNAL(seek(long)), this, SLOT(seekPlayback(long))); connect(context, SIGNAL(unpause()), this, SLOT(resumePlayback())); connect(context, SIGNAL(mediaSelected(QString)), this, SLOT(mediaSelected(QString))); + connect(context, SIGNAL(configChanged(qint32)), this, SLOT(layoutChanged())); + connect(layoutSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(layoutChanged())); + + // The video file may have been already selected + mediaSelected(context->videoFilename); + // We may add a video player while training is already running! + if(context->isRunning) startPlayback(); + // Instantiate a layout as initial default + layoutChanged(); } } @@ -169,6 +167,62 @@ VideoWindow::~VideoWindow() #endif } +void VideoWindow::layoutChanged() +{ + readVideoLayout(videoLayout()); +} + +void VideoWindow::readVideoLayout(int pos) +{ + // Video Overlays Initialization: if video config file is not present + // copy a default one to be used as a model by the user. + // An empty video-layout.xml file disables video overlays + QString filename = context->athlete->home->config().canonicalPath() + "/" + "video-layout.xml"; + QFile file(filename); + if (!file.exists()) + { + file.setFileName(":/xml/video-layout.xml"); + file.copy(filename); + QFile::setPermissions(filename, QFileDevice::ReadUser|QFileDevice::WriteUser); + } + if (file.exists()) + { + // clean previous layout + foreach(MeterWidget* p_meterWidget, m_metersWidget) + { + m_metersWidget.removeAll(p_meterWidget); + p_meterWidget->deleteLater(); + } + layoutNames.clear(); + + VideoLayoutParser handler(&m_metersWidget, &layoutNames, container); + QXmlInputSource source(&file); + QXmlSimpleReader reader; + handler.layoutPositionSelected = pos; + reader.setContentHandler(&handler); + reader.parse(source); + qDebug() << "Video Layout parsing: " << layoutNames; + if(context->isRunning) showMeters(); + } + else + { + qDebug() << qPrintable(QString("file" + filename + " (video layout XML file) not found")); + } +} + +void VideoWindow::showMeters() +{ + foreach(MeterWidget* p_meterWidget , m_metersWidget) + { + p_meterWidget->setWindowOpacity(1); // Show the widget + p_meterWidget->AdjustSizePos(); + p_meterWidget->update(); + p_meterWidget->raise(); + p_meterWidget->show(); + } + prevPosition = mapToGlobal(pos()); +} + void VideoWindow::resizeEvent(QResizeEvent * ) { foreach(MeterWidget* p_meterWidget , m_metersWidget) @@ -178,7 +232,7 @@ void VideoWindow::resizeEvent(QResizeEvent * ) void VideoWindow::startPlayback() { - if (context->currentVideoSyncFile()) { + if ((!context->isRunning) && context->currentVideoSyncFile()) { context->currentVideoSyncFile()->manualOffset = 0.0; context->currentVideoSyncFile()->km = 0.0; } @@ -210,21 +264,12 @@ void VideoWindow::startPlayback() mp->play(); #endif - foreach(MeterWidget* p_meterWidget , m_metersWidget) - { - p_meterWidget->setWindowOpacity(1); // Show the widget - p_meterWidget->AdjustSizePos(); - p_meterWidget->update(); - - p_meterWidget->raise(); - p_meterWidget->show(); - } - prevPosition = mapToGlobal(pos()); + showMeters(); } void VideoWindow::stopPlayback() { - if (context->currentVideoSyncFile()) + if ((!context->isRunning) && context->currentVideoSyncFile()) context->currentVideoSyncFile()->manualOffset = 0.0; #ifdef GC_VIDEO_VLC @@ -239,7 +284,6 @@ void VideoWindow::stopPlayback() #endif foreach(MeterWidget* p_meterWidget , m_metersWidget) p_meterWidget->hide(); - } void VideoWindow::pausePlayback() @@ -591,6 +635,7 @@ void VideoWindow::mediaSelected(QString filename) mc = QMediaContent(QUrl::fromLocalFile(filename)); mp->setMedia(mc); #endif + if(context->isRunning) startPlayback(); } MediaHelper::MediaHelper() diff --git a/src/Train/VideoWindow.h b/src/Train/VideoWindow.h index 41bfb48b1..6fba96991 100644 --- a/src/Train/VideoWindow.h +++ b/src/Train/VideoWindow.h @@ -143,14 +143,19 @@ class VideoWindow : public GcChartWindow Q_OBJECT G_OBJECT + // which layout to use + Q_PROPERTY(int videoLayout READ videoLayout WRITE setVideoLayout USER true) public: VideoWindow(Context *); ~VideoWindow(); + int videoLayout() const { return layoutSelector->currentIndex(); } + public slots: + void layoutChanged(); void startPlayback(); void stopPlayback(); void pausePlayback(); @@ -162,6 +167,7 @@ class VideoWindow : public GcChartWindow protected: void resizeEvent(QResizeEvent *); + void setVideoLayout(int x) { layoutSelector->setCurrentIndex(x); } // current data int curPosition; @@ -176,6 +182,11 @@ class VideoWindow : public GcChartWindow QList m_metersWidget; QPoint prevPosition; + private: + QList layoutNames; + void readVideoLayout(int x); + void showMeters(); + #ifdef GC_VIDEO_VLC // vlc for older QT @@ -193,6 +204,7 @@ class VideoWindow : public GcChartWindow #endif QWidget *container; + QComboBox *layoutSelector; bool init; // we initialised ok ? };