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.
This commit is contained in:
Michel Dagenais
2020-06-20 13:41:19 -04:00
committed by GitHub
parent 32884b1c6a
commit 05e89e5c34
6 changed files with 214 additions and 57 deletions

View File

@@ -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); }

View File

@@ -1,4 +1,5 @@
<layout name="preset_1">
<layouts>
<layout name="Graphical Meters">
<meter name="Speedometer" container="Video" source="Speed" type="NeedleMeter">
<RelativeSize Width="15.0" Height="15.0" />
<RelativePosition X="12.0" Y="77.0" />
@@ -83,13 +84,85 @@
<MainColor R="255" G="0" B="0" A="180" />
<Text>load</Text>
</meter>
<!-- START Changes for elevation widget -->
<meter name="elevation" container="Video" source="Elevation" type="Elevation">
<RelativeSize Width="40.0" Height="15.0" />
<RelativePosition X="20.0" Y="92.0" />
<BackgroundColor R="200" G="200" B="200" A="125" />
<OutlineColor R="255" G="255" B="255" A="250" />
<MainColor R="255" G="0" B="0" A="250" />
</meter>
<!-- END Changes for elevation widget -->
<meter name="elevation" container="Video" source="Elevation" type="Elevation">
<RelativeSize Width="40.0" Height="15.0" />
<RelativePosition X="20.0" Y="92.0" />
<BackgroundColor R="200" G="200" B="200" A="125" />
<OutlineColor R="255" G="255" B="255" A="250" />
<MainColor R="255" G="0" B="0" A="250" />
</meter>
</layout>
<layout name="Text Meters">
<meter name="MeterPanel" container="Video" source="None" type="Text" Background="true">
<RelativeSize Width="15.0" Height="50.0" />
<RelativePosition X="10.0" Y="25.0" />
<MainColor R="250" G="250" B="250" A="200" />
<MainFont Name="Consolas" Size="64" />
<AltFont Name="Consolas" Size="64" />
<BackgroundColor R="50" G="50" B="50" A="100" />
<Text></Text>
</meter>
<meter name="Slope" container="MeterPanel" source="Load" type="Text" alignment="AlignLeft" textWidth="5">
<RelativeSize Width="100.0" Height="10.0" />
<RelativePosition X="50.0" Y="8.0" />
<MainColor R="250" G="250" B="250" A="200" />
<MainFont Name="Consolas" Size="64" />
<AltFont Name="Consolas" Size="64" />
</meter>
<meter name="Speed" container="MeterPanel" source="Speed" type="Text" alignment="AlignLeft" textWidth="5">
<RelativeSize Width="100.0" Height="10.0" />
<RelativePosition X="50.0" Y="20.0" />
<MainColor R="250" G="250" B="250" A="200" />
<MainFont Name="Consolas" Size="64" />
<AltFont Name="Consolas" Size="64" />
</meter>
<meter name="Distance" container="MeterPanel" source="Distance" type="Text" alignment="AlignLeft" textWidth="5">
<RelativeSize Width="100.0" Height="10.0" />
<RelativePosition X="50.0" Y="32.0" />
<MainColor R="250" G="250" B="250" A="200" />
<MainFont Name="Consolas" Size="64" />
<AltFont Name="Consolas" Size="64" />
</meter>
<meter name="Wattmeter" container="MeterPanel" source="Watt" type="Text" alignment="AlignLeft" textWidth="5">
<RelativeSize Width="100.0" Height="10.0" />
<RelativePosition X="50.0" Y="44.0" />
<MainColor R="250" G="250" B="250" A="200" />
<MainFont Name="Consolas" Size="64" />
<AltFont Name="Consolas" Size="64" />
<AltText> Watts</AltText>
</meter>
<meter name="Altitude" container="MeterPanel" source="Altitude" type="Text" alignment="AlignLeft" textWidth="5">
<RelativeSize Width="100.0" Height="10.0" />
<RelativePosition X="50.0" Y="56.0" />
<MainColor R="250" G="250" B="250" A="200" />
<MainFont Name="Consolas" Size="64" />
<AltFont Name="Consolas" Size="64" />
</meter>
<meter name="Cadencemeter" container="MeterPanel" source="Cadence" type="Text" alignment="AlignLeft" textWidth="5">
<RelativeSize Width="100.0" Height="10.0" />
<RelativePosition X="50.0" Y="68.0" />
<MainColor R="250" G="250" B="250" A="200" />
<MainFont Name="Consolas" Size="64" />
<AltFont Name="Consolas" Size="64" />
<AltText> RPM</AltText>
</meter>
<meter name="Elapsed" container="MeterPanel" source="Time" type="Text">
<RelativeSize Width="100.0" Height="10.0" />
<RelativePosition X="50.0" Y="80.0" />
<MainColor R="250" G="250" B="250" A="200" />
<MainFont Name="Consolas" Size="64" />
<AltFont Name="Consolas" Size="64" />
</meter>
<meter name="elevation" container="Video" source="Elevation" type="Elevation">
<RelativeSize Width="40.0" Height="15.0" />
<RelativePosition X="20.0" Y="92.0" />
<BackgroundColor R="200" G="200" B="200" A="125" />
<OutlineColor R="255" G="255" B="255" A="250" />
<MainColor R="255" G="0" B="0" A="250" />
</meter>
</layout>
<layout name="No Meters">
</layout>
</layouts>

View File

@@ -26,10 +26,12 @@
#include "VideoLayoutParser.h"
#include "MeterWidget.h"
VideoLayoutParser::VideoLayoutParser (QList<MeterWidget*>* metersWidget, QWidget* VideoContainer)
: metersWidget(metersWidget), VideoContainer(VideoContainer)
VideoLayoutParser::VideoLayoutParser (QList<MeterWidget*>* metersWidget, QList<QString>* 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;
}

View File

@@ -32,21 +32,25 @@
class VideoLayoutParser : public QXmlDefaultHandler
{
public:
VideoLayoutParser(QList<MeterWidget*>* metersWidget, QWidget* VideoContainer);
VideoLayoutParser(QList<MeterWidget*>* metersWidget, QList<QString>* 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<MeterWidget*>* metersWidget;
QList<QString>* layoutNames;
QWidget* VideoContainer;
QString buffer;
int nonameindex;
int layoutPosition;
bool skipLayout;
QString source;
QString meterName;

View File

@@ -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()

View File

@@ -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<MeterWidget*> m_metersWidget;
QPoint prevPosition;
private:
QList<QString> 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 ?
};