Files
GoldenCheetah/src/SplitActivityWizard.cpp
Mark Liversedge 0fc18dc891 Retain intervals when splitting rides
Will keep all intervals that start within a split
but will truncate for intervals that are longer
than the split so we keep any 'Lap markers'.
2011-10-11 09:24:44 +01:00

837 lines
29 KiB
C++

/*
* Copyright (c) 2011 Mark Liversedge (liversedge@gmail.com)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "SplitActivityWizard.h"
// Minimum gap in recording to find a natural break to split
static const double defaultMinimumGap = 15; // 15 minutes
// Minimum size of segment to identify as a new activity
static const double defaultMinimumSegmentSize = 20; // 20 minutes
// Main wizard
SplitActivityWizard::SplitActivityWizard(MainWindow *main) : QWizard(main), main(main)
{
// delete when done
setAttribute(Qt::WA_DeleteOnClose);
// Minimum 600x500 for when selecting intervals
setMinimumHeight(500);
setMinimumWidth(600);
// title
setWindowTitle(tr("Split Activity"));
// set ride - unconst since we will wipe it away eventually
rideItem = const_cast<RideItem*>(main->currentRideItem());
// Set sensible defaults
keepOriginal = false;
minimumGap = defaultMinimumGap;
minimumSegmentSize = defaultMinimumSegmentSize;
usedMinimumSegmentSize = usedMinimumGap = -1;
// set initial intervals list, will be adjusted
// if the user modifies the default paramters
intervals = new QTreeWidget;
intervals->headerItem()->setText(0, tr(""));
intervals->headerItem()->setText(1, tr("Start"));
intervals->headerItem()->setText(2, tr(""));
intervals->headerItem()->setText(3, tr("Stop"));
intervals->headerItem()->setText(4, tr("Duration"));
intervals->headerItem()->setText(5, tr("Distance"));
intervals->headerItem()->setText(6, tr("Interval Name"));
intervals->setColumnCount(7);
intervals->setColumnWidth(0,30);
intervals->setColumnWidth(1,80);
intervals->setColumnWidth(2,30);
intervals->setColumnWidth(3,80);
intervals->setColumnWidth(4,80);
intervals->setColumnWidth(5,80);
intervals->setSelectionMode(QAbstractItemView::NoSelection);
intervals->setEditTriggers(QAbstractItemView::SelectedClicked); // allow edit
intervals->setUniformRowHeights(true);
intervals->setIndentation(0);
files = new QTreeWidget;
files->headerItem()->setText(0, tr("Filename"));
files->headerItem()->setText(1, tr("Date"));
files->headerItem()->setText(2, tr("Time"));
files->headerItem()->setText(3, tr("Duration"));
files->headerItem()->setText(4, tr("Distance"));
files->headerItem()->setText(5, tr("Action"));
files->setColumnCount(6);
files->setColumnWidth(0, 190); // filename
files->setColumnWidth(1, 95); // date
files->setColumnWidth(2, 90); // time
files->setColumnWidth(3, 75); // duration
files->setColumnWidth(4, 75); // distance
files->setSelectionMode(QAbstractItemView::SingleSelection);
files->setEditTriggers(QAbstractItemView::SelectedClicked); // allow edit
files->setUniformRowHeights(true);
files->setIndentation(0);
// just the hr and power as a plot
smallPlot = new SmallPlot(this);
smallPlot->setFixedHeight(100);
smallPlot->setData(rideItem);
bg = new SplitBackground(this);
bg->attach(smallPlot);
// 5 step process, although Conflict may be skipped
addPage(new SplitWelcome(this));
addPage(new SplitKeep(this));
addPage(new SplitParameters(this));
addPage(new SplitSelect(this));
addPage(new SplitConfirm(this));
done = false;
}
void
SplitActivityWizard::setIntervalsList(SplitSelect *selector)
{
// didn't change so no need to rebuild
if (usedMinimumGap == minimumGap && usedMinimumSegmentSize == minimumSegmentSize) return;
// clear the table
intervals->clear();
// convert to seconds
int minimumGap = this->minimumGap * 60;
int minimumSegmentSize = this->minimumSegmentSize * 60;
// remember the last ones we used
usedMinimumSegmentSize = this->minimumSegmentSize;
usedMinimumGap = this->minimumGap;
// find segments where gap is greater than minimumGap
// and segment size is > minimumSize. If a segment is shorter
// than minimumSize then ignore it (i.e. treat is as part of the gap)
QList<RideFileInterval> segments;
double segmentStart = 0;
double segmentEnd = 0;
double lastSecs = 0;
bool first = true;
int counter = 0;
foreach (RideFilePoint *p, rideItem->ride()->dataPoints()) {
if (first == true) {
segmentStart = segmentEnd = lastSecs = p->secs;
first = false;
} else {
if ((p->secs - segmentEnd) >= minimumGap) {
if ((segmentEnd-segmentStart) >= minimumSegmentSize) {
// we have a candidate
segments.append(RideFileInterval(segmentStart, segmentEnd,
QString("Activity Segment #%1").arg(++counter)));
}
segmentEnd = segmentStart = p->secs;
} else {
// keep accumulating
segmentEnd = p->secs;
}
}
lastSecs = p->secs;
}
// we got to the end, is there a segment here?
if ((segmentEnd-segmentStart) >= minimumSegmentSize) {
// we have a candidate
segments.append(RideFileInterval(segmentStart, segmentEnd,
QString("Activity Segment #%1").arg(++counter)));
}
// now look at the segments and add any gaps in recording
// because these MUST be honoured (i.e. user cannot unmark
// these points, otherwise gaps in recording are retained
// in the resulting file
foreach(RideFileInterval *p, gaps) { delete p; } gaps.clear(); // wip old ones
double lastsecs = rideItem->ride()->dataPoints().first()->secs;
int gapnum = 0;
foreach(RideFileInterval activity, segments) {
if (activity.start > lastsecs) {
// we have a gap
gapnum++;
// add to gap list
RideFileInterval *gap = new RideFileInterval(lastsecs,
activity.start,
QString("Gap in recording #%1").arg(gapnum));
gaps.append(gap);
// add to interval list
segments.append(RideFileInterval(*gap));
}
lastsecs = activity.stop;
}
if (lastsecs < rideItem->ride()->dataPoints().last()->secs) {
// gap at the end
gapnum++;
// add to gap list
RideFileInterval *gap = new RideFileInterval(lastsecs,
rideItem->ride()->dataPoints().last()->secs,
QString("Gap in recording #%1").arg(gapnum));
gaps.append(gap);
// add to interval list
segments.append(RideFileInterval(*gap));
}
// first entry in list should always be entire file
// so we can mark the start and stop for splitting
segments.insert(0, RideFileInterval(rideItem->ride()->dataPoints().first()->secs,
rideItem->ride()->dataPoints().last()->secs,
"Entire Activity"));
// now fold in the ride intervals
segments.append(rideItem->ride()->intervals());
// now lets sort the segments in start order
qSort(segments.begin(), segments.end());
// first just add all the current ride intervals
counter = 0;
QChar zero = QLatin1Char('0');
foreach (RideFileInterval interval, segments) {
// DO NOT skip intervals that are too short
//if (interval.stop - interval.start < minimumSegmentSize) continue;
QTreeWidgetItem *add = new QTreeWidgetItem(intervals->invisibleRootItem());
add->setFlags(add->flags() | Qt::ItemIsEditable);
// we set these intervals as checked by default
bool checkit = (interval.name.startsWith("Gap in recording") ||
interval.name == "Entire Activity");
// disable checkbox editing (i.e. mandatory split) at gaps in recording
bool disableit = (interval.name.startsWith("Gap in recording"));
// selector start
QCheckBox *checkBox = new QCheckBox("", this);
checkBox->setChecked(checkit);
checkBox->setEnabled(!disableit);
intervals->setItemWidget(add, 0, checkBox);
connect(checkBox, SIGNAL(stateChanged(int)), selector, SLOT(refreshMarkers()));
// interval start
int secs = interval.start;
add->setText(1, QString("%1:%2:%3")
.arg(secs/3600,2,10,zero)
.arg(secs%3600/60,2,10,zero)
.arg(secs%60,2,10,zero));
// selector stop
checkBox = new QCheckBox("", this);
checkBox->setChecked(checkit);
checkBox->setEnabled(!disableit);
intervals->setItemWidget(add, 2, checkBox);
connect(checkBox, SIGNAL(stateChanged(int)), selector, SLOT(refreshMarkers()));
// interval start
secs = interval.stop;
add->setText(3, QString("%1:%2:%3")
.arg(secs/3600,2,10,zero)
.arg(secs%3600/60,2,10,zero)
.arg(secs%60,2,10,zero));
// interval duration
secs = interval.stop - interval.start;
add->setText(4, QString("%1:%2:%3")
.arg(secs/3600,2,10,zero)
.arg(secs%3600/60,2,10,zero)
.arg(secs%60,2,10,zero));
// interval distance
double distance = rideItem->ride()->timeToDistance(interval.stop) -
rideItem->ride()->timeToDistance(interval.start);
add->setText(5, QString("%1 %2")
.arg(distance * (main->useMetricUnits ? 1 : MILES_PER_KM), 0, 'f', 2)
.arg(main->useMetricUnits ? "km" : "mi"));
// interval name
add->setText(6, interval.name);
// hiddden columns with dataPoint from/to
add->setText(7, QString("%1").arg(rideItem->ride()->timeIndex(interval.start)));
add->setText(8, QString("%1").arg(rideItem->ride()->timeIndex(interval.stop)));
counter++;
}
smallPlot->replot();
}
void
SplitActivityWizard::setFilesList()
{
// clear the table
files->clear();
// fold in current ride -- if we are removing
if (keepOriginal == false) {
// we will wipe the original file
QTreeWidgetItem *add = new QTreeWidgetItem(files->invisibleRootItem());
add->setFlags(add->flags() | Qt::ItemIsEditable);
add->setText(0, rideItem->fileName);
add->setText(1, rideItem->ride()->startTime().toString(tr("dd MMM yyyy")));
add->setText(2, rideItem->ride()->startTime().toString(tr("hh:mm:ss ap")));
// get duration and distance, yuk, dup of code below, and from RideImportWizard
int secs=0;
double km=0;
if (!rideItem->ride()->dataPoints().isEmpty() && rideItem->ride()->dataPoints().last() != NULL) {
if (!secs) secs = rideItem->ride()->dataPoints().last()->secs;
if (!km) km = rideItem->ride()->dataPoints().last()->km;
}
// set duration
QChar zero = QLatin1Char ( '0' );
QString time = QString("%1:%2:%3").arg(secs/3600,2,10,zero)
.arg(secs%3600/60,2,10,zero)
.arg(secs%60,2,10,zero);
add->setText(3, time);
// set distance
QString dist = main->useMetricUnits
? QString ("%1 km").arg(km, 0, 'f', 1)
: QString ("%1 mi").arg(km * MILES_PER_KM, 0, 'f', 1);
add->setText(4, dist);
// interval action
add->setText(5, "Remove");
}
// create a row for each file and action
QChar zero = QLatin1Char('0');
foreach (RideFile *ride, activities) {
QTreeWidgetItem *add = new QTreeWidgetItem(files->invisibleRootItem());
add->setFlags(add->flags() | Qt::ItemIsEditable);
QString filename = QString ("%1_%2_%3_%4_%5_%6.json")
.arg(ride->startTime().date().year(), 4, 10, zero)
.arg(ride->startTime().date().month(), 2, 10, zero)
.arg(ride->startTime().date().day(), 2, 10, zero)
.arg(ride->startTime().time().hour(), 2, 10, zero)
.arg(ride->startTime().time().minute(), 2, 10, zero)
.arg(ride->startTime().time().second(), 2, 10, zero);
// filename
add->setText(0, filename);
// date and time
add->setText(1, ride->startTime().toString(tr("dd MMM yyyy")));
add->setText(2, ride->startTime().toString(tr("hh:mm:ss ap")));
// get duration and distance
int secs=0;
double km=0;
if (!ride->dataPoints().isEmpty() && ride->dataPoints().last() != NULL) {
if (!secs) secs = ride->dataPoints().last()->secs;
if (!km) km = ride->dataPoints().last()->km;
}
// set duration
QChar zero = QLatin1Char ( '0' );
QString time = QString("%1:%2:%3").arg(secs/3600,2,10,zero)
.arg(secs%3600/60,2,10,zero)
.arg(secs%60,2,10,zero);
add->setText(3, time);
// set distance
QString dist = main->useMetricUnits
? QString ("%1 km").arg(km, 0, 'f', 1)
: QString ("%1 mi").arg(km * MILES_PER_KM, 0, 'f', 1);
add->setText(4, dist);
// interval action
add->setText(5, "Create");
}
// tidy up column widths
files->resizeColumnToContents(5);
}
// if the file in question has a backup file already then
// return the filename (without the path), otherwise return
// an empty string
QString
SplitActivityWizard::hasBackup(QString filename)
{
QString backupFilename = main->home.absolutePath() + "/" + filename + ".bak";
if (QFile(backupFilename).exists()) {
return QString(filename + ".bak");
} else {
return "";
}
}
QStringList
SplitActivityWizard::conflicts(QDateTime datetime)
{
QStringList returning;
// Check if an existing ride has the same starttime
QChar zero = QLatin1Char('0');
QString targetnosuffix = QString ("%1_%2_%3_%4_%5_%6")
.arg(datetime.date().year(), 4, 10, zero)
.arg(datetime.date().month(), 2, 10, zero)
.arg(datetime.date().day(), 2, 10, zero)
.arg(datetime.time().hour(), 2, 10, zero)
.arg(datetime.time().minute(), 2, 10, zero)
.arg(datetime.time().second(), 2, 10, zero);
// now make a regexp for all know ride types
foreach(QString suffix, RideFileFactory::instance().suffixes()) {
QString conflict = main->home.absolutePath() + "/" + targetnosuffix + "." + suffix;
if (QFile(conflict).exists()) returning << conflict;
}
return returning;
}
/*----------------------------------------------------------------------
* Wizard Pages
*--------------------------------------------------------------------*/
// welcome
SplitWelcome::SplitWelcome(SplitActivityWizard *parent) : QWizardPage(parent), wizard(parent)
{
setTitle(tr("Split Activity"));
setSubTitle(tr("Lets get started"));
QVBoxLayout *layout = new QVBoxLayout;
setLayout(layout);
QLabel *label = new QLabel("This wizard will help you split the current activity "
"into multiple activities\n\n"
"The wizard will identify segments of uninterrupted "
"activity and allow you to select which ones to "
"save as new activities. You will also be able to "
"select any currently defined intervals too.\n\n"
"If the newly created activity clashes with an existing "
"activity (same date and time) then the wizard will adjust "
"the start time by one or more seconds to avoid losing or "
"overwriting any existing activities.");
label->setWordWrap(true);
layout->addWidget(label);
layout->addStretch();
}
// Keep original?
SplitKeep::SplitKeep(SplitActivityWizard *parent) : QWizardPage(parent), wizard(parent)
{
setTitle(tr("Keep original"));
setSubTitle(tr("Do you want to keep the original activity?"));
QVBoxLayout *layout = new QVBoxLayout;
setLayout(layout);
QLabel *label = new QLabel("If you want to keep the current activity then you "
"should ensure you have clicked on the \"Keep original "
"activity\" check box below.\n\n"
"If you do not choose to keep the original activity "
"it will be backed up before removing it from the "
"activity history.\n\n");
label->setWordWrap(true);
keepOriginal = new QCheckBox("Keep original activity", this);
keepOriginal->setChecked(wizard->keepOriginal);
warning = new QLabel(this);
warning->setWordWrap(true);
QFont font;
font.setWeight(QFont::Bold);
warning->setFont(font);
setWarning();
layout->addWidget(label);
layout->addWidget(keepOriginal);
layout->addStretch();
layout->addWidget(warning);
connect(keepOriginal, SIGNAL(stateChanged(int)), this, SLOT(keepOriginalChanged()));
}
// paramters
SplitParameters::SplitParameters(SplitActivityWizard *parent) : QWizardPage(parent), wizard(parent)
{
setTitle(tr("Split Parameters"));
setSubTitle(tr("Configure how segments are found"));
QVBoxLayout *layout = new QVBoxLayout;
setLayout(layout);
QLabel *label = new QLabel("This wizard will find segments of activity to save "
"by looking for gaps in recording. \n\n"
"You can define the minimum length, in time, a gap "
"in recording should be in order to mark the end of "
"one segment and the beginning of another.\n\n"
"In addition, you can set a minimum segment size. "
"Any segment smaller than this limit will be ignored.\n\n");
label->setWordWrap(true);
layout->addWidget(label);
QGridLayout *grid = new QGridLayout;
QLabel *minGap = new QLabel("Minimum Gap (minutes)", this);
QLabel *minSize = new QLabel("Minimum Segment Size (minutes)", this);
minimumGap = new QDoubleSpinBox(this);
minimumGap->setDecimals(0);
minimumGap->setSingleStep(1.0);
minimumGap->setValue(wizard->minimumGap);
minimumSegmentSize = new QDoubleSpinBox(this);
minimumSegmentSize->setDecimals(0);
minimumSegmentSize->setSingleStep(1.0);
minimumSegmentSize->setValue(wizard->minimumSegmentSize);
grid->addWidget(minGap,0,0);
grid->addWidget(minimumGap,0,1,Qt::AlignLeft);
grid->addWidget(minSize,1,0);
grid->addWidget(minimumSegmentSize,1,1,Qt::AlignLeft);
layout->addLayout(grid);
layout->addStretch();
connect (minimumGap, SIGNAL(valueChanged(double)), this, SLOT(valueChanged()));
connect (minimumSegmentSize, SIGNAL(valueChanged(double)), this, SLOT(valueChanged()));
}
void
SplitParameters::valueChanged()
{
wizard->minimumGap = minimumGap->value();
wizard->minimumSegmentSize = minimumSegmentSize->value();
}
void
SplitKeep::keepOriginalChanged()
{
wizard->keepOriginal = keepOriginal->isChecked();
setWarning();
}
void
SplitKeep::setWarning()
{
if (!keepOriginal->isChecked()) {
if (wizard->hasBackup(wizard->rideItem->fileName) != "") {
warning->setText("WARNING: The current ride will be backed up and "
"removed, but a backup already exists. The existing "
"backup will therefore be overwritten.");
return;
}
}
warning->setText("");
}
// Select
SplitSelect::SplitSelect(SplitActivityWizard *parent) : QWizardPage(parent), wizard(parent)
{
setTitle(tr("Select Split Markers"));
setSubTitle(tr("Activity will be split between marker points selected"));
QVBoxLayout *layout = new QVBoxLayout;
setLayout(layout);
layout->addWidget(wizard->smallPlot);
layout->addWidget(wizard->intervals);
}
void
SplitSelect::initializePage()
{
wizard->setIntervalsList(this);
refreshMarkers();
}
void
SplitSelect::refreshMarkers()
{
// update the markers on the plot
foreach (QwtPlotMarker *m, wizard->markers) {
// remove from the plot and delete
m->detach();
delete m;
}
wizard->markers.clear();
wizard->marks.clear(); // dataPoint indexes
// now refresh them
for(int i=0; i<wizard->intervals->invisibleRootItem()->childCount(); i++) {
QTreeWidgetItem *current = wizard->intervals->invisibleRootItem()->child(i);
// add marker for start?
if (static_cast<QCheckBox*>(wizard->intervals->itemWidget(current,0))->isChecked()) {
long index = current->text(7).toInt();
double point = wizard->rideItem->ride()->dataPoints().at(index)->secs;
wizard->marks.append(index);
QwtPlotMarker *add = new QwtPlotMarker;
wizard->markers.append(add);
// vertical line will do for now
add->setLineStyle(QwtPlotMarker::VLine);
add->setLabelAlignment(Qt::AlignRight | Qt::AlignTop);
add->setLinePen(QPen(GColor(CPLOTMARKER), 0, Qt::DashDotLine));
add->setValue(point / 60.0, 0.0);
add->attach(wizard->smallPlot);
}
// add marker for stop?
if (static_cast<QCheckBox*>(wizard->intervals->itemWidget(current,2))->isChecked()) {
long index = current->text(8).toInt();
double point = wizard->rideItem->ride()->dataPoints().at(index)->secs;
wizard->marks.append(index);
QwtPlotMarker *add = new QwtPlotMarker;
wizard->markers.append(add);
// vertical line will do for now
add->setLineStyle(QwtPlotMarker::VLine);
add->setLabelAlignment(Qt::AlignRight | Qt::AlignTop);
add->setLinePen(QPen(GColor(CPLOTMARKER), 0, Qt::DashDotLine));
add->setValue(point / 60.0, 0.0);
add->attach(wizard->smallPlot);
}
}
wizard->smallPlot->replot();
}
// Confirm
SplitConfirm::SplitConfirm(SplitActivityWizard *parent) : QWizardPage(parent), wizard(parent)
{
setTitle(tr("Confirm"));
setSubTitle(tr("Split Activity cannot be undone"));
setCommitPage(true);
setButtonText(QWizard::CommitButton, "Confirm");
QVBoxLayout *layout = new QVBoxLayout;
setLayout(layout);
layout->addWidget(wizard->files);
}
// create an array of rides
void
SplitConfirm::initializePage()
{
// clear the current array
foreach(RideFile *ride, wizard->activities) {
delete ride;
}
wizard->activities.clear();
// create a sorted list of markers, since we
// may have duplicates and the sequence is
// not guaranteed to be ordered
QList<long> points;
foreach(long mark, wizard->marks) points.append(mark); // marks are indexes to ensure absolute accuracy
qSort(points.begin(), points.end());
// Create a new activity for each marked segment
long lastmark = -1;
foreach(long mark, points) {
if (mark == lastmark) continue;
if (lastmark != -1) {
// ignore gaps!
if (!wizard->bg->isGap(wizard->rideItem->ride()->dataPoints().at(lastmark)->secs/60.0,
wizard->rideItem->ride()->dataPoints().at(mark)->secs/60.0)) {
RideFile *add = createRideFile(lastmark, mark);
wizard->activities.append(add);
}
}
lastmark = mark;
}
// now lets adjust the starttime to avoid conflicts
// and record adjustment in a metadata field for transparency
// it will always conflict with current ride, so we pick that
// up as a special case.
// we check against existing rides AND the rides we WILL create
QString originalFileName = wizard->main->home.absolutePath() + "/" + wizard->rideItem->fileName;
QList<QDateTime> toBeCreated;
foreach(RideFile *ride, wizard->activities) {
int adjust = 0;
QStringList conflicts = wizard->conflicts(ride->startTime());
while (conflicts.count() || toBeCreated.contains(ride->startTime())) {
// if the only conflict is with the original filename
// and it will be wiped then don't worry about it
// we also check conflicts of rides the WILl be created
if (conflicts.count() == 1 &&
conflicts.at(0) == originalFileName &&
wizard->keepOriginal == false &&
!toBeCreated.contains(ride->startTime())) {
break;
}
adjust++;
ride->setStartTime(ride->startTime().addSecs(1));
conflicts = wizard->conflicts(ride->startTime());
}
// record the fact we adjusted...
if (adjust) {
ride->setTag("Start Time Adjust", QString("%1 seconds").arg(adjust));
}
toBeCreated.append(ride->startTime());
}
wizard->setFilesList();
}
//create a new ride file from the current ride file
//by using datapoints from index start to stop
RideFile *
SplitConfirm::createRideFile(long start, long stop)
{
RideFile *returning = new RideFile; // target
RideFile *ride = wizard->rideItem->ride(); // source
// set offset in seconds, make sure in bounds too
double offset = 0;
double distanceoffset = 0;
if (start < ride->dataPoints().count() && start >= 0) {
offset = ride->dataPoints().at(start)->secs;
distanceoffset = ride->dataPoints().at(start)->km;
}
// copy first class variables (adjust starttime to include offset)
returning->setStartTime(ride->startTime().addSecs(offset));
returning->setRecIntSecs(ride->recIntSecs());
returning->setDeviceType(ride->deviceType());
// lets keep the metadata too
const_cast<QMap<QString,QString>&>(returning->tags()) = QMap<QString,QString>(ride->tags());
// now the dataPoints, check in bounds too!
for(long i=start; i<stop && i<ride->dataPoints().count(); i++) {
RideFilePoint *p = ride->dataPoints().at(i);
returning->appendPoint(p->secs - offset, // start from zero!
p->cad, p->hr, p->km - distanceoffset, p->kph,
p->nm, p->watts, p->alt, p->lon, p->lat,
p->headwind, p->interval);
}
// lets keep intervals that start in our section truncating them
// if neccessary (some folks want to keep lap markers)
double startTime = wizard->rideItem->ride()->dataPoints().at(start)->secs;
double stopTime = wizard->rideItem->ride()->dataPoints().at(stop)->secs;
foreach (RideFileInterval interval, wizard->rideItem->ride()->intervals()) {
if (interval.start >= startTime && interval.start <= stopTime) {
if (interval.stop > stopTime)
returning->addInterval(interval.start - offset, stopTime, interval.name);
else
returning->addInterval(interval.start - offset, interval.stop - offset, interval.name);
}
}
return returning;
}
bool
SplitConfirm::validatePage()
{
if(QMessageBox::question(this, "Confirm",
QString("%1 file(s) will be created.\n\nAre you sure you wish to proceed?")
.arg(wizard->activities.count()),
QMessageBox::Ok|QMessageBox::Cancel, QMessageBox::Cancel) == QMessageBox::Ok) {
// LETS DO IT NOW!
// first do we need to remove the current ride?
if (wizard->keepOriginal == false) {
wizard->main->removeCurrentRide();
QTreeWidgetItem *current = wizard->files->invisibleRootItem()->child(0);
current->setText(5, "Removed");
}
// whizz through and create each new activity
int off = wizard->keepOriginal ? 0 : 1; // skip first line to remove or not?
for(int i=0; i<wizard->activities.count(); i++) {
QTreeWidgetItem *current = wizard->files->invisibleRootItem()->child(i+off);
QString target = wizard->main->home.absolutePath() + "/" + current->text(0);
JsonFileReader reader;
QFile out(target);
reader.writeRideFile(wizard->activities.at(i), out);
current->setText(5, "Saved");
wizard->main->addRide(QFileInfo(out).fileName(), true);
}
// now make this page the last (so we can see what was done)
setTitle("Completed");
setSubTitle("Split Activity Completed");
wizard->done = true;
return true;
} else return false;
}
bool
SplitConfirm::isComplete() const
{
return !wizard->done;
}