mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-04-15 05:32:21 +00:00
Text Cues on TrainBottom display pane (#3544)
Import Texts from Erg files in TrainerRoad format, Zwo files and from Lap names in json files. Display texts on TrainBottom for both, erg and slope mode, at the corresponding time/distance for the specified duration. Export Texts in erg, mrc and zwo formats. Fixes #1118 Fixes #2967 Prerequisite for #2098
This commit is contained in:
committed by
GitHub
parent
911b9bd966
commit
1f8bafb334
@@ -180,6 +180,7 @@ class RideFile : public QObject // QObject to emit signals
|
|||||||
friend class ManualRideDialog;
|
friend class ManualRideDialog;
|
||||||
friend class PolarFileReader;
|
friend class PolarFileReader;
|
||||||
friend class Strava;
|
friend class Strava;
|
||||||
|
friend class ErgFile; // access to intervals
|
||||||
// split and mergers
|
// split and mergers
|
||||||
friend class MergeActivityWizard;
|
friend class MergeActivityWizard;
|
||||||
friend class SplitActivityWizard;
|
friend class SplitActivityWizard;
|
||||||
|
|||||||
@@ -140,6 +140,7 @@ void ErgFile::parseZwift()
|
|||||||
format = ERG; // default to couse until we know
|
format = ERG; // default to couse until we know
|
||||||
Points.clear();
|
Points.clear();
|
||||||
Laps.clear();
|
Laps.clear();
|
||||||
|
Texts.clear();
|
||||||
|
|
||||||
gpi.Reset();
|
gpi.Reset();
|
||||||
|
|
||||||
@@ -201,6 +202,7 @@ void ErgFile::parseErg2(QString p)
|
|||||||
mode = format = ERG; // default to couse until we know
|
mode = format = ERG; // default to couse until we know
|
||||||
Points.clear();
|
Points.clear();
|
||||||
Laps.clear();
|
Laps.clear();
|
||||||
|
Texts.clear();
|
||||||
|
|
||||||
gpi.Reset();
|
gpi.Reset();
|
||||||
|
|
||||||
@@ -268,6 +270,7 @@ void ErgFile::parseTacx()
|
|||||||
format = CRS; // default to couse until we know
|
format = CRS; // default to couse until we know
|
||||||
Points.clear();
|
Points.clear();
|
||||||
Laps.clear();
|
Laps.clear();
|
||||||
|
Texts.clear();
|
||||||
|
|
||||||
gpi.Reset();
|
gpi.Reset();
|
||||||
|
|
||||||
@@ -452,6 +455,7 @@ void ErgFile::parseComputrainer(QString p)
|
|||||||
int lapcounter = 0;
|
int lapcounter = 0;
|
||||||
format = ERG; // either ERG or MRC
|
format = ERG; // either ERG or MRC
|
||||||
Points.clear();
|
Points.clear();
|
||||||
|
Texts.clear();
|
||||||
|
|
||||||
// start by assuming the input file is Metric
|
// start by assuming the input file is Metric
|
||||||
bool bIsMetric = true;
|
bool bIsMetric = true;
|
||||||
@@ -470,6 +474,8 @@ void ErgFile::parseComputrainer(QString p)
|
|||||||
QRegExp endHeader("^.*\\[END COURSE HEADER\\].*$", Qt::CaseInsensitive);
|
QRegExp endHeader("^.*\\[END COURSE HEADER\\].*$", Qt::CaseInsensitive);
|
||||||
QRegExp startData("^.*\\[COURSE DATA\\].*$", Qt::CaseInsensitive);
|
QRegExp startData("^.*\\[COURSE DATA\\].*$", Qt::CaseInsensitive);
|
||||||
QRegExp endData("^.*\\[END COURSE DATA\\].*$", Qt::CaseInsensitive);
|
QRegExp endData("^.*\\[END COURSE DATA\\].*$", Qt::CaseInsensitive);
|
||||||
|
QRegExp startText("^.*\\[COURSE TEXT\\].*$", Qt::CaseInsensitive);
|
||||||
|
QRegExp endText("^.*\\[END COURSE TEXT\\].*$", Qt::CaseInsensitive);
|
||||||
// ignore whitespace and support for ';' comments (a GC extension)
|
// ignore whitespace and support for ';' comments (a GC extension)
|
||||||
QRegExp ignore("^(;.*|[ \\t\\n]*)$", Qt::CaseInsensitive);
|
QRegExp ignore("^(;.*|[ \\t\\n]*)$", Qt::CaseInsensitive);
|
||||||
// workout settings
|
// workout settings
|
||||||
@@ -492,6 +498,9 @@ void ErgFile::parseComputrainer(QString p)
|
|||||||
QRegExp lapmarker("^[ \\t]*([0-9\\.]+)[ \\t]*LAP[ \\t\\n]*(.*)$", Qt::CaseInsensitive);
|
QRegExp lapmarker("^[ \\t]*([0-9\\.]+)[ \\t]*LAP[ \\t\\n]*(.*)$", Qt::CaseInsensitive);
|
||||||
QRegExp crslapmarker("^[ \\t]*LAP[ \\t\\n]*(.*)$", Qt::CaseInsensitive);
|
QRegExp crslapmarker("^[ \\t]*LAP[ \\t\\n]*(.*)$", Qt::CaseInsensitive);
|
||||||
|
|
||||||
|
// text cue records
|
||||||
|
QRegExp textCue("^([0-9\\.]+)[ \\t]*([^\\t]+)[\\t]+([0-9]+)[ \\t\\n]*$", Qt::CaseInsensitive);
|
||||||
|
|
||||||
// ok. opened ok lets parse.
|
// ok. opened ok lets parse.
|
||||||
QTextStream inputStream(&ergFile);
|
QTextStream inputStream(&ergFile);
|
||||||
QTextStream stringStream(&p);
|
QTextStream stringStream(&p);
|
||||||
@@ -523,6 +532,10 @@ void ErgFile::parseComputrainer(QString p)
|
|||||||
section = DATA;
|
section = DATA;
|
||||||
} else if (endData.exactMatch(line)) {
|
} else if (endData.exactMatch(line)) {
|
||||||
section = END;
|
section = END;
|
||||||
|
} else if (startText.exactMatch(line)) {
|
||||||
|
section = TEXTS;
|
||||||
|
} else if (endText.exactMatch(line)) {
|
||||||
|
section = END;
|
||||||
} else if (ergformat.exactMatch(line)) {
|
} else if (ergformat.exactMatch(line)) {
|
||||||
// save away the format
|
// save away the format
|
||||||
mode = format = ERG;
|
mode = format = ERG;
|
||||||
@@ -642,6 +655,12 @@ void ErgFile::parseComputrainer(QString p)
|
|||||||
|
|
||||||
} else if (ignore.exactMatch(line)) {
|
} else if (ignore.exactMatch(line)) {
|
||||||
// do nothing for this line
|
// do nothing for this line
|
||||||
|
|
||||||
|
} else if (section == TEXTS && textCue.exactMatch(line)) {
|
||||||
|
// x, text cue, duration
|
||||||
|
double x = textCue.cap(1).toDouble() * 1000.; // convert to msecs or m
|
||||||
|
int duration = textCue.cap(3).toInt(); // duration in secs
|
||||||
|
Texts<<ErgFileText(x, duration, textCue.cap(2));
|
||||||
} else {
|
} else {
|
||||||
// ignore bad lines for now. just bark.
|
// ignore bad lines for now. just bark.
|
||||||
//qDebug()<<"huh?" << line;
|
//qDebug()<<"huh?" << line;
|
||||||
@@ -721,6 +740,7 @@ void ErgFile::parseFromRideFileFactory()
|
|||||||
format = mode = CRS;
|
format = mode = CRS;
|
||||||
Points.clear();
|
Points.clear();
|
||||||
Laps.clear();
|
Laps.clear();
|
||||||
|
Texts.clear();
|
||||||
|
|
||||||
gpi.Reset();
|
gpi.Reset();
|
||||||
|
|
||||||
@@ -815,6 +835,14 @@ void ErgFile::parseFromRideFileFactory()
|
|||||||
Points.append(add);
|
Points.append(add);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add interval names as text cues
|
||||||
|
foreach(const RideFileInterval* lap, ride->intervals()) {
|
||||||
|
double x = ride->timeToDistance(lap->start) * 1000.0;
|
||||||
|
int duration = lap->stop - lap->start + 1;
|
||||||
|
if (x > 0 && duration > 0 && !lap->name.isEmpty())
|
||||||
|
Texts<<ErgFileText(x, duration, lap->name);
|
||||||
|
}
|
||||||
|
|
||||||
gpxFile.close();
|
gpxFile.close();
|
||||||
|
|
||||||
valid = true;
|
valid = true;
|
||||||
@@ -847,6 +875,7 @@ void ErgFile::parseTTS()
|
|||||||
format = mode = CRS;
|
format = mode = CRS;
|
||||||
Points.clear();
|
Points.clear();
|
||||||
Laps.clear();
|
Laps.clear();
|
||||||
|
Texts.clear();
|
||||||
|
|
||||||
gpi.Reset();
|
gpi.Reset();
|
||||||
|
|
||||||
@@ -1114,6 +1143,17 @@ ErgFile::save(QStringList &errors)
|
|||||||
}
|
}
|
||||||
|
|
||||||
out << "[END COURSE DATA]\n";
|
out << "[END COURSE DATA]\n";
|
||||||
|
|
||||||
|
// TEXTS in TrainerRoad compatible format
|
||||||
|
if (Texts.count() > 0) {
|
||||||
|
out << "[COURSE TEXT]\n";
|
||||||
|
foreach(ErgFileText cue, Texts)
|
||||||
|
out <<QString("%1\t%2\t%3\n").arg(cue.x/1000)
|
||||||
|
.arg(cue.text)
|
||||||
|
.arg(cue.duration);
|
||||||
|
out << "[END COURSE TEXT]\n";
|
||||||
|
}
|
||||||
|
|
||||||
f.close();
|
f.close();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1191,6 +1231,17 @@ ErgFile::save(QStringList &errors)
|
|||||||
}
|
}
|
||||||
|
|
||||||
out << "[END COURSE DATA]\n";
|
out << "[END COURSE DATA]\n";
|
||||||
|
|
||||||
|
// TEXTS in TrainerRoad compatible format
|
||||||
|
if (Texts.count() > 0) {
|
||||||
|
out << "[COURSE TEXT]\n";
|
||||||
|
foreach(ErgFileText cue, Texts)
|
||||||
|
out <<QString("%1\t%2\t%3\n").arg(cue.x/1000)
|
||||||
|
.arg(cue.text)
|
||||||
|
.arg(cue.duration);
|
||||||
|
out << "[END COURSE TEXT]\n";
|
||||||
|
}
|
||||||
|
|
||||||
f.close();
|
f.close();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1243,6 +1294,7 @@ ErgFile::save(QStringList &errors)
|
|||||||
|
|
||||||
out << " <workout>\n";
|
out << " <workout>\n";
|
||||||
QList<ErgFileSection> sections = Sections();
|
QList<ErgFileSection> sections = Sections();
|
||||||
|
int msecs = 0;
|
||||||
for(int i=0; i<sections.count(); i++) {
|
for(int i=0; i<sections.count(); i++) {
|
||||||
|
|
||||||
// are there repeated sections of efforts and recovery?
|
// are there repeated sections of efforts and recovery?
|
||||||
@@ -1270,8 +1322,22 @@ ErgFile::save(QStringList &errors)
|
|||||||
<< "PowerOnLow=\"" << sections[i].start/CP << "\" "
|
<< "PowerOnLow=\"" << sections[i].start/CP << "\" "
|
||||||
<< "PowerOnHigh=\"" << sections[i].end/CP << "\" "
|
<< "PowerOnHigh=\"" << sections[i].end/CP << "\" "
|
||||||
<< "PowerOffLow=\"" << sections[i+1].start/CP << "\" "
|
<< "PowerOffLow=\"" << sections[i+1].start/CP << "\" "
|
||||||
<< "PowerOffHigh=\"" << sections[i+1].end/CP << "\" />\n";
|
<< "PowerOffHigh=\"" << sections[i+1].end/CP << "\" ";
|
||||||
|
|
||||||
|
int d = (count+1)*(sections[i].duration+sections[i+1].duration);
|
||||||
|
if (Texts.count() > 0) {
|
||||||
|
out << ">\n";
|
||||||
|
foreach (ErgFileText cue, Texts)
|
||||||
|
if (cue.x >= msecs && cue.x <= msecs+d)
|
||||||
|
out << " <textevent "
|
||||||
|
<< "timeoffset=\""<<(cue.x-msecs)/1000
|
||||||
|
<< "\" message=\"" << cue.text
|
||||||
|
<< "\" duration=\"" << cue.duration << "\"/>\n";
|
||||||
|
out << " </IntervalsT>\n";
|
||||||
|
} else {
|
||||||
|
out << "/>\n";
|
||||||
|
}
|
||||||
|
msecs += d;
|
||||||
// skip on, bearing in mind the main loop increases i by 1
|
// skip on, bearing in mind the main loop increases i by 1
|
||||||
i += 1 + (count*2);
|
i += 1 + (count*2);
|
||||||
|
|
||||||
@@ -1286,8 +1352,20 @@ ErgFile::save(QStringList &errors)
|
|||||||
|
|
||||||
out << " <" << tag << " Duration=\""<<sections[i].duration/1000 << "\" "
|
out << " <" << tag << " Duration=\""<<sections[i].duration/1000 << "\" "
|
||||||
<< "PowerLow=\"" <<sections[i].start/CP << "\" "
|
<< "PowerLow=\"" <<sections[i].start/CP << "\" "
|
||||||
<< "PowerHigh=\"" <<sections[i].end/CP << "\" />\n";
|
<< "PowerHigh=\"" <<sections[i].end/CP << "\"";
|
||||||
|
if (Texts.count() > 0) {
|
||||||
|
out << ">\n";
|
||||||
|
foreach (ErgFileText cue, Texts)
|
||||||
|
if (cue.x >= msecs && cue.x <= msecs+sections[i].duration)
|
||||||
|
out << " <textevent "
|
||||||
|
<< "timeoffset=\""<<(cue.x-msecs)/1000
|
||||||
|
<< "\" message=\"" << cue.text
|
||||||
|
<< "\" duration=\"" << cue.duration << "\"/>\n";
|
||||||
|
out << " </" << tag << ">\n";
|
||||||
|
} else {
|
||||||
|
out << "/>\n";
|
||||||
|
}
|
||||||
|
msecs += sections[i].duration;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
out << " </workout>\n";
|
out << " </workout>\n";
|
||||||
@@ -1522,6 +1600,20 @@ ErgFile::currentLap(long x)
|
|||||||
return -1; // No matching lap
|
return -1; // No matching lap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Retrieve the index of next text cue.
|
||||||
|
// Params: x - current workout distance (m) / time (ms)
|
||||||
|
// Returns: index of next text cue.
|
||||||
|
int ErgFile::nextText(long x)
|
||||||
|
{
|
||||||
|
if (!isValid()) return -1; // not a valid ergfile
|
||||||
|
|
||||||
|
// If the current position is before the text, then the text is next
|
||||||
|
for (int i=0; i<Texts.count(); i++) {
|
||||||
|
if (x <= Texts.at(i).x) return i;
|
||||||
|
}
|
||||||
|
return -1; // nope, no marker ahead of there
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
ErgFile::calculateMetrics()
|
ErgFile::calculateMetrics()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
#define SETTINGS 1
|
#define SETTINGS 1
|
||||||
#define DATA 2
|
#define DATA 2
|
||||||
#define END 3
|
#define END 3
|
||||||
|
#define TEXTS 4
|
||||||
|
|
||||||
// is this in .erg or .mrc format?
|
// is this in .erg or .mrc format?
|
||||||
#define ERG 1
|
#define ERG 1
|
||||||
@@ -75,11 +76,11 @@ class ErgFileSection
|
|||||||
class ErgFileText
|
class ErgFileText
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ErgFileText() : x(0), pos(0), text("") {}
|
ErgFileText() : x(0), duration(0), text("") {}
|
||||||
ErgFileText(double x, int pos, QString text) : x(x), pos(pos), text(text) {}
|
ErgFileText(double x, int duration, QString text) : x(x), duration(duration), text(text) {}
|
||||||
|
|
||||||
double x;
|
double x;
|
||||||
int pos;
|
int duration;
|
||||||
QString text;
|
QString text;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -128,6 +129,8 @@ class ErgFile
|
|||||||
int nextLap(long); // return the start value (erg - time(ms) or slope - distance(m)) for the next lap
|
int nextLap(long); // return the start value (erg - time(ms) or slope - distance(m)) for the next lap
|
||||||
int currentLap(long); // return the start value (erg - time(ms) or slope - distance(m)) for the current lap
|
int currentLap(long); // return the start value (erg - time(ms) or slope - distance(m)) for the current lap
|
||||||
|
|
||||||
|
int nextText(long); // return the index for the next text cue
|
||||||
|
|
||||||
// turn the ergfile into a series of sections rather
|
// turn the ergfile into a series of sections rather
|
||||||
// than a list of points
|
// than a list of points
|
||||||
QList<ErgFileSection> Sections();
|
QList<ErgFileSection> Sections();
|
||||||
|
|||||||
@@ -1808,6 +1808,21 @@ void TrainSidebar::guiUpdate() // refreshes the telemetry
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Text Cues
|
||||||
|
if (ergFile && ergFile->Texts.count() > 0) {
|
||||||
|
// find the next cue
|
||||||
|
double pos = status&RT_MODE_ERGO ? load_msecs : displayWorkoutDistance*1000;
|
||||||
|
int idx = ergFile->nextText(pos);
|
||||||
|
if (idx >= 0) {
|
||||||
|
ErgFileText cue = ergFile->Texts.at(idx);
|
||||||
|
// show when we are approaching it
|
||||||
|
if (((status&RT_MODE_ERGO) && cue.x<load_msecs+1000) ||
|
||||||
|
((status&RT_MODE_SLOPE) && cue.x < displayWorkoutDistance*1000 + 10)) {
|
||||||
|
emit setNotification(cue.text, cue.duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(lapTimeRemaining < 0) {
|
if(lapTimeRemaining < 0) {
|
||||||
if (ergFile) lapTimeRemaining = ergFile->Duration - load_msecs;
|
if (ergFile) lapTimeRemaining = ergFile->Duration - load_msecs;
|
||||||
if (lapTimeRemaining < 0)
|
if (lapTimeRemaining < 0)
|
||||||
|
|||||||
@@ -531,7 +531,7 @@ WorkoutWindow::saveAs()
|
|||||||
{
|
{
|
||||||
QString filename = QFileDialog::getSaveFileName(this, tr("Save Workout File"),
|
QString filename = QFileDialog::getSaveFileName(this, tr("Save Workout File"),
|
||||||
appsettings->value(this, GC_WORKOUTDIR, "").toString(),
|
appsettings->value(this, GC_WORKOUTDIR, "").toString(),
|
||||||
"ERG workout (*.erg);;Zwift workout (*.zwo)");
|
"ERG workout (*.erg);;MRC workout (*.mrc);;Zwift workout (*.zwo)");
|
||||||
|
|
||||||
// if they didn't select, give up.
|
// if they didn't select, give up.
|
||||||
if (filename.isEmpty()) {
|
if (filename.isEmpty()) {
|
||||||
@@ -539,7 +539,7 @@ WorkoutWindow::saveAs()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// filetype defaults to .erg
|
// filetype defaults to .erg
|
||||||
if(!filename.endsWith(".erg") && !filename.endsWith(".zwo")) {
|
if(!filename.endsWith(".erg") && !filename.endsWith(".mrc") && !filename.endsWith(".zwo")) {
|
||||||
filename.append(".erg");
|
filename.append(".erg");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ bool ZwoParser::startDocument()
|
|||||||
{
|
{
|
||||||
buffer.clear();
|
buffer.clear();
|
||||||
secs = 0;
|
secs = 0;
|
||||||
|
sSecs = 0;
|
||||||
watts = 0;
|
watts = 0;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -52,6 +53,7 @@ ZwoParser::startElement(const QString &, const QString &, const QString &qName,
|
|||||||
double PowerLow = attrs.value("PowerLow").toDouble();
|
double PowerLow = attrs.value("PowerLow").toDouble();
|
||||||
double PowerHigh = attrs.value("PowerHigh").toDouble();
|
double PowerHigh = attrs.value("PowerHigh").toDouble();
|
||||||
double Power = attrs.value("Power").toDouble();
|
double Power = attrs.value("Power").toDouble();
|
||||||
|
bool ftpTest = attrs.value("ftptest").toInt();
|
||||||
|
|
||||||
// Either Power or PowerLow / PowerHigh are available
|
// Either Power or PowerLow / PowerHigh are available
|
||||||
// PowerHigh may be optional and should be same as low.
|
// PowerHigh may be optional and should be same as low.
|
||||||
@@ -66,7 +68,7 @@ ZwoParser::startElement(const QString &, const QString &, const QString &qName,
|
|||||||
// POINTS
|
// POINTS
|
||||||
|
|
||||||
// basic from/to, with different names for some odd reason
|
// basic from/to, with different names for some odd reason
|
||||||
if (qName == "Warmup" || qName == "SteadyState" || qName == "Cooldown" || qName == "FreeRide") {
|
if (qName == "Warmup" || qName == "SteadyState" || qName == "Cooldown" || qName == "FreeRide" || qName == "Freeride") {
|
||||||
|
|
||||||
int from = int(100.0 * PowerLow);
|
int from = int(100.0 * PowerLow);
|
||||||
int to = int(100.0 * PowerHigh);
|
int to = int(100.0 * PowerHigh);
|
||||||
@@ -77,8 +79,9 @@ ZwoParser::startElement(const QString &, const QString &, const QString &qName,
|
|||||||
from = to = ap;
|
from = to = ap;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (qName == "FreeRide") {
|
if (qName == "FreeRide" || qName == "Freeride") {
|
||||||
if (watts == 0) from = to = 70;
|
if (ftpTest) from = to = 100; // if marked as FTP test, assume 100%
|
||||||
|
else if (watts == 0) from = to = 70;
|
||||||
else from = to = watts; // whatever we were just doing keep doing it
|
else from = to = watts; // whatever we were just doing keep doing it
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,6 +90,7 @@ ZwoParser::startElement(const QString &, const QString &, const QString &qName,
|
|||||||
points << ErgFilePoint(secs * 1000, from, from);
|
points << ErgFilePoint(secs * 1000, from, from);
|
||||||
watts = from;
|
watts = from;
|
||||||
}
|
}
|
||||||
|
sSecs = secs;
|
||||||
secs += Duration;
|
secs += Duration;
|
||||||
points << ErgFilePoint(secs * 1000, to, to);
|
points << ErgFilePoint(secs * 1000, to, to);
|
||||||
watts = to;
|
watts = to;
|
||||||
@@ -125,6 +129,7 @@ ZwoParser::startElement(const QString &, const QString &, const QString &qName,
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
sSecs = secs;
|
||||||
while (count--) {
|
while (count--) {
|
||||||
|
|
||||||
// add if not already on that wattage
|
// add if not already on that wattage
|
||||||
@@ -156,8 +161,10 @@ ZwoParser::startElement(const QString &, const QString &, const QString &qName,
|
|||||||
} else if (qName == "textevent") {
|
} else if (qName == "textevent") {
|
||||||
int offset = attrs.value("timeoffset").toInt();
|
int offset = attrs.value("timeoffset").toInt();
|
||||||
QString message=attrs.value("message");
|
QString message=attrs.value("message");
|
||||||
|
int duration = attrs.value("duration").toInt();
|
||||||
int pos = attrs.value("y").toInt();
|
int pos = attrs.value("y").toInt();
|
||||||
texts << ErgFileText((secs+offset) * 1000, pos, message);
|
if (pos >= 240) pos -= 240; // no position, use excess as delay
|
||||||
|
texts << ErgFileText((sSecs+offset+pos)*1000, duration, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// and clear anything left in the buffer
|
// and clear anything left in the buffer
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ public:
|
|||||||
|
|
||||||
QString buffer;
|
QString buffer;
|
||||||
int secs; // rolling secs as points read
|
int secs; // rolling secs as points read
|
||||||
|
int sSecs; // rolling starting secs as points read
|
||||||
int watts; // watts set at last point
|
int watts; // watts set at last point
|
||||||
|
|
||||||
// the data in it
|
// the data in it
|
||||||
|
|||||||
31
test/workouts/TRwithTextCues.mrc
Normal file
31
test/workouts/TRwithTextCues.mrc
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
[COURSE HEADER]
|
||||||
|
VERSION = 2
|
||||||
|
UNITS = ENGLISH
|
||||||
|
DESCRIPTION = A description
|
||||||
|
FILE NAME = blah.mrc
|
||||||
|
MINUTES PERCENT
|
||||||
|
[END COURSE HEADER]
|
||||||
|
[COURSE DATA]
|
||||||
|
0.00 50
|
||||||
|
6.60 50
|
||||||
|
6.60 100
|
||||||
|
7.98 140
|
||||||
|
7.98 50
|
||||||
|
9.07 50
|
||||||
|
9.07 150
|
||||||
|
10.10 150
|
||||||
|
10.10 50
|
||||||
|
14.07 50
|
||||||
|
14.07 115
|
||||||
|
22.07 115
|
||||||
|
22.07 50
|
||||||
|
32.08 50
|
||||||
|
32.08 115
|
||||||
|
40.08 115
|
||||||
|
40.08 50
|
||||||
|
51.88 50
|
||||||
|
[END COURSE DATA]
|
||||||
|
[COURSE TEXT]
|
||||||
|
600 This is a new message 10
|
||||||
|
615 This message will show up at 10:15 into the workout 10
|
||||||
|
[END COURSE TEXT]
|
||||||
Reference in New Issue
Block a user