mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-15 08:59:55 +00:00
.. page flow and join sorted
.. committing before resolving the merging code
- analyse() to identify offsets
- combine() to merge into a working copy
- ux for sliding offsets on a fullplot
.. next commit will complete this update to the
merge tool
NOTE: MERGE IS NOT CURRENTLY WORKING SO PLEASE
DO NOT USE IT !!!!
842 lines
26 KiB
C++
842 lines
26 KiB
C++
/*
|
|
* Copyright (c) 2011-13 Damien Grauser (Damien.Grauser@pev-geneve.ch)
|
|
* 2014 Mark Liversedgge (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 "MergeActivityWizard.h"
|
|
#include "Context.h"
|
|
#include "MainWindow.h"
|
|
|
|
/*----------------------------------------------------------------------
|
|
*
|
|
* Page Flow Summary
|
|
*
|
|
* 10 MergeWelcome Welcome message
|
|
*
|
|
* 20 MergeSource Select source (import or download)
|
|
* 25 MergeDownload Download from device
|
|
* 27 MergeChoose Select a ride from a list
|
|
*
|
|
* 30 MergeMode Select mode (join / merge)
|
|
*
|
|
* if merge (not join)
|
|
* 40 MergeSelect Select series to combine
|
|
* 50 MergeStrategy Select strategy for aligning data
|
|
* 60 MergeAdjust Manually adjust alignment
|
|
*
|
|
* 70 MergeConfirm Confirm and Save away
|
|
*
|
|
*---------------------------------------------------------------------*/
|
|
|
|
MergeActivityWizard::MergeActivityWizard(Context *context) : QWizard(context->mainWindow), context(context)
|
|
{
|
|
#ifdef Q_OS_MAX
|
|
setWizardStyle(QWizard::ModernStyle);
|
|
#endif
|
|
setWindowTitle(tr("Combine Activities"));
|
|
|
|
setFixedHeight(530);
|
|
setFixedWidth(550);
|
|
|
|
// initialise before setRide since it checks
|
|
// to see if memory needs to be freed first
|
|
ride1 = ride2 = NULL;
|
|
combined = NULL;
|
|
|
|
// current ride
|
|
current = const_cast<RideItem*>(context->currentRideItem());
|
|
recIntSecs= current->ride()->recIntSecs();
|
|
setRide(&ride1, current->ride());
|
|
|
|
// default to merge not join
|
|
// and merge on device clocks
|
|
mode = 0;
|
|
strategy = 0;
|
|
|
|
// 5 step process, although Conflict may be skipped
|
|
setPage(10, new MergeWelcome(this));
|
|
setPage(20, new MergeSource(this));
|
|
setPage(25, new MergeDownload(this));
|
|
setPage(27, new MergeChoose(this));
|
|
setPage(30, new MergeMode(this));
|
|
setPage(40, new MergeSelect(this));
|
|
setPage(50, new MergeStrategy(this));
|
|
setPage(60, new MergeAdjust(this)); // might need to rename to adjust
|
|
setPage(1000, new MergeConfirm(this));
|
|
}
|
|
|
|
void
|
|
MergeActivityWizard::setRide(RideFile **here, RideFile *with)
|
|
{
|
|
// wipe current
|
|
if (*here) delete *here;
|
|
|
|
// set with new resampled / filled
|
|
// you may be tempted to optimise this out
|
|
// but by cloning a working copy like this
|
|
// we start with a clean ride and no derived
|
|
// data to 'pollute' the process
|
|
if (with) *here = with->resample(recIntSecs);
|
|
else *here = NULL;
|
|
}
|
|
|
|
void
|
|
MergeActivityWizard::analyse()
|
|
{
|
|
// looking at the paramters determine the offset
|
|
// to be used by both rides XXX not written yet !
|
|
|
|
// XXX just make em start together for now !
|
|
offset1=0;
|
|
offset2=0;
|
|
}
|
|
|
|
void
|
|
MergeActivityWizard::combine()
|
|
{
|
|
// zap whatever we have
|
|
if (combined) delete combined;
|
|
|
|
// create a combined ride applying the parameters
|
|
// from the wizard for join or merge
|
|
|
|
if (mode == 1) { // JOIN
|
|
|
|
// easy peasy -- loop through one then the other !
|
|
combined = new RideFile(ride1);
|
|
|
|
RideFilePoint *lp = NULL;
|
|
|
|
foreach(RideFilePoint *p, ride1->dataPoints()) {
|
|
combined->appendPoint(*p);
|
|
lp = p;
|
|
}
|
|
|
|
// now add the data from the second one!
|
|
double distanceOffset=0;
|
|
double timeOffset=0;
|
|
bool first=true;
|
|
|
|
foreach(RideFilePoint *p, ride2->dataPoints()) {
|
|
if (first) {
|
|
|
|
if (lp) {
|
|
distanceOffset = lp->km + p->km;
|
|
timeOffset = lp->secs + p->secs + recIntSecs;
|
|
}
|
|
first = false;
|
|
}
|
|
|
|
RideFilePoint add = *p;
|
|
add.secs += timeOffset;
|
|
add.km += distanceOffset;
|
|
|
|
combined->appendPoint(add);
|
|
}
|
|
|
|
// any intervals with a number name? find the last
|
|
int intervalN=0;
|
|
foreach(RideFileInterval interval, ride1->intervals()) {
|
|
int x = interval.name.toInt();
|
|
if (interval.name == QString("%1").arg(x)) {
|
|
if (x > intervalN) intervalN = x;
|
|
}
|
|
}
|
|
|
|
// now run through the intervals for the second ride
|
|
// and add them but renumber any intervals that are just numbers
|
|
foreach(RideFileInterval interval, ride2->intervals()) {
|
|
int x = interval.name.toInt();
|
|
if (interval.name == QString("%1").arg(x)) {
|
|
interval.name = QString("%1").arg(x+intervalN);
|
|
}
|
|
combined->addInterval(interval.start + timeOffset,
|
|
interval.stop + timeOffset,
|
|
interval.name);
|
|
}
|
|
|
|
} else { // MERGE
|
|
|
|
}
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
* Wizard Pages
|
|
*--------------------------------------------------------------------*/
|
|
|
|
// welcome
|
|
MergeWelcome::MergeWelcome(MergeActivityWizard *parent) : QWizardPage(parent), wizard(parent)
|
|
{
|
|
setTitle(tr("Combine Activities"));
|
|
setSubTitle(tr("Lets get started"));
|
|
|
|
QVBoxLayout *layout = new QVBoxLayout;
|
|
setLayout(layout);
|
|
|
|
QLabel *label = new QLabel(tr("This wizard will help you combine data with "
|
|
"the currently selected activity.\n\n"
|
|
"You will be able to import or download data before "
|
|
"merging or joining the data and manually adjusting "
|
|
"the alignment of data series before it is saved."));
|
|
label->setWordWrap(true);
|
|
|
|
layout->addWidget(label);
|
|
layout->addStretch();
|
|
}
|
|
|
|
//Select source for merge
|
|
MergeSource::MergeSource(MergeActivityWizard *parent) : QWizardPage(parent), wizard(parent)
|
|
{
|
|
setTitle(tr("Select Source"));
|
|
setSubTitle(tr("Where is the data you want to combine ?"));
|
|
|
|
QVBoxLayout *layout = new QVBoxLayout;
|
|
setLayout(layout);
|
|
|
|
mapper = new QSignalMapper(this);
|
|
connect(mapper, SIGNAL(mapped(QString)), this, SLOT(clicked(QString)));
|
|
|
|
// select a file
|
|
QCommandLinkButton *p = new QCommandLinkButton(tr("Import from a File"),
|
|
tr("Import and combine from a file on your hard disk"
|
|
" or device mounted as a USB disk to combine with "
|
|
"the current activity."), this);
|
|
connect(p, SIGNAL(clicked()), mapper, SLOT(map()));
|
|
mapper->setMapping(p, "import");
|
|
layout->addWidget(p);
|
|
|
|
// download from a device
|
|
p = new QCommandLinkButton(tr("Download from Device"),
|
|
tr("Download data from a serial port device such as a Moxy Muscle Oxygen Monitor or"
|
|
" bike computer to combine with the current activity."), this);
|
|
connect(p, SIGNAL(clicked()), mapper, SLOT(map()));
|
|
mapper->setMapping(p, "download");
|
|
layout->addWidget(p);
|
|
|
|
// select from rides on same day
|
|
p = new QCommandLinkButton(tr("Existing Activity"),
|
|
tr("Combine data from an activity that has already been imported or downloaded into"
|
|
" GoldenCheetah. Selecting from a list of all available activities. "), this);
|
|
connect(p, SIGNAL(clicked()), mapper, SLOT(map()));
|
|
mapper->setMapping(p, "choose");
|
|
layout->addWidget(p);
|
|
|
|
label = new QLabel("", this);
|
|
layout->addWidget(label);
|
|
|
|
next = 30;
|
|
setFinalPage(false);
|
|
}
|
|
|
|
void
|
|
MergeSource::initializePage()
|
|
{
|
|
}
|
|
|
|
|
|
void
|
|
MergeSource::clicked(QString p)
|
|
{
|
|
// reset -- particularly since we might get here from
|
|
// other pages hitting 'Back'
|
|
initializePage();
|
|
|
|
// move on if we imported one
|
|
if (p == "import" && importFile() == true) {
|
|
next = 30;
|
|
wizard->next();
|
|
}
|
|
|
|
// move on if we downloaded one
|
|
if (p == "download") {
|
|
next = 25;
|
|
wizard->next();
|
|
}
|
|
|
|
// select from a list
|
|
if (p == "choose") {
|
|
next = 27;
|
|
wizard->next();
|
|
}
|
|
|
|
}
|
|
|
|
bool
|
|
MergeSource::importFile()
|
|
{
|
|
QVariant lastDirVar = appsettings->value(this, GC_SETTINGS_LAST_IMPORT_PATH);
|
|
QString lastDir = (lastDirVar != QVariant())
|
|
? lastDirVar.toString() : QDir::homePath();
|
|
|
|
const RideFileFactory &rff = RideFileFactory::instance();
|
|
QStringList suffixList = rff.suffixes();
|
|
suffixList.replaceInStrings(QRegExp("^"), "*.");
|
|
QStringList fileNames;
|
|
QStringList allFormats;
|
|
allFormats << QString(tr("All Supported Formats (%1)")).arg(suffixList.join(" "));
|
|
foreach(QString suffix, rff.suffixes())
|
|
allFormats << QString("%1 (*.%2)").arg(rff.description(suffix)).arg(suffix);
|
|
allFormats << tr("All files (*.*)");
|
|
fileNames = QFileDialog::getOpenFileNames(
|
|
this, tr("Import from File"), lastDir,
|
|
allFormats.join(";;"));
|
|
if (!fileNames.isEmpty()) {
|
|
lastDir = QFileInfo(fileNames.front()).absolutePath();
|
|
appsettings->setValue(GC_SETTINGS_LAST_IMPORT_PATH, lastDir);
|
|
QStringList fileNamesCopy = fileNames; // QT doc says iterate over a copy
|
|
return importFile(fileNamesCopy);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
MergeSource::importFile(QList<QString> files)
|
|
{
|
|
// get fullpath name for processing
|
|
QFileInfo filename = QFileInfo(files[0]).absoluteFilePath();
|
|
|
|
QFile thisfile(files[0]);
|
|
QFileInfo thisfileinfo(files[0]);
|
|
QStringList errors;
|
|
|
|
if (thisfileinfo.exists() && thisfileinfo.isFile() && thisfileinfo.isReadable()) {
|
|
|
|
// is it one we understand ?
|
|
QStringList suffixList = RideFileFactory::instance().suffixes();
|
|
QRegExp suffixes(QString("^(%1)$").arg(suffixList.join("|")));
|
|
suffixes.setCaseSensitivity(Qt::CaseInsensitive);
|
|
|
|
if (suffixes.exactMatch(thisfileinfo.suffix())) {
|
|
|
|
RideFile *ride = RideFileFactory::instance().openRideFile(wizard->context, thisfile, errors);
|
|
|
|
// did it parse ok?
|
|
if (ride) {
|
|
|
|
wizard->setRide(&wizard->ride2, ride);
|
|
return true;
|
|
}
|
|
|
|
} else {
|
|
|
|
wizard->setRide(&wizard->ride2, NULL);
|
|
errors.append(tr("Error - Unknown file type"));
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Download dialog embedded
|
|
MergeDownload::MergeDownload(MergeActivityWizard *parent) : QWizardPage(parent), wizard(parent)
|
|
{
|
|
setTitle(tr("Download Activity"));
|
|
setSubTitle(tr("Download Activity to Combine"));
|
|
|
|
QVBoxLayout *layout = new QVBoxLayout;
|
|
setLayout(layout);
|
|
|
|
// embed download dialog
|
|
QMdiArea *mdiarea = new QMdiArea(this);
|
|
layout->addWidget(mdiarea);
|
|
|
|
// get a download dialog -- embedded
|
|
DownloadRideDialog *download = new DownloadRideDialog(parent->context, true);
|
|
//download->setInputMode(QInputDialog.TextInput);
|
|
QMdiSubWindow *w = mdiarea->addSubWindow(download, Qt::FramelessWindowHint);
|
|
|
|
// maximize and no title bar etc
|
|
w->showMaximized();
|
|
layout->addStretch();
|
|
|
|
connect(download, SIGNAL(downloadStarts()), this, SLOT(downloadStarts()));
|
|
connect(download, SIGNAL(downloadEnds()), this, SLOT(downloadEnds()));
|
|
connect(download, SIGNAL(downloadFiles(QList<DeviceDownloadFile>)), this,
|
|
SLOT(downloadFiles(QList<DeviceDownloadFile>)));
|
|
}
|
|
|
|
void
|
|
MergeDownload::downloadFiles(QList<DeviceDownloadFile>files)
|
|
{
|
|
// Bingo ! only one ride so must be this one
|
|
// saves having to select from a pesky list
|
|
// XXX WE NEED TO UPDATE DOWNLOADRIDEDIALOG TO
|
|
// ALLOW SELECTION OF RIDES TO PROCESS AND
|
|
// ALSO MAKE IT LIMIT THIS TO ONE RIDE WHEN
|
|
// EMBEDDED IN MERGEDOWNLOAD ! XXX
|
|
if (files.count() == 1) {
|
|
|
|
QFile thisfile(files[0].name);
|
|
QStringList errors;
|
|
|
|
RideFileReader *reader = RideFileFactory::instance().readerForSuffix(files[0].extension);
|
|
if (!reader) return;
|
|
|
|
RideFile *ride = reader->openRideFile(thisfile, errors);
|
|
|
|
// did it parse ok?
|
|
if (ride) {
|
|
|
|
wizard->setRide(&wizard->ride2 ,ride);
|
|
next = 30;
|
|
wizard->next();
|
|
return;
|
|
} else {
|
|
|
|
wizard->setRide(&wizard->ride2, NULL);
|
|
errors.append(tr("Error - Unknown file type"));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
MergeDownload::downloadStarts()
|
|
{
|
|
wizard->button(QWizard::BackButton)->setEnabled(false);
|
|
}
|
|
|
|
void
|
|
MergeDownload::downloadEnds()
|
|
{
|
|
wizard->button(QWizard::BackButton)->setEnabled(true);
|
|
}
|
|
|
|
void
|
|
MergeDownload::initializePage()
|
|
{
|
|
// might be needed for download ????
|
|
}
|
|
|
|
// Choose from the ride list
|
|
MergeChoose::MergeChoose(MergeActivityWizard *parent) : QWizardPage(parent), wizard(parent)
|
|
{
|
|
setTitle(tr("Choose an Activity"));
|
|
setSubTitle(tr("Choose an Existing activity to Combine"));
|
|
|
|
chosen = false;
|
|
|
|
QVBoxLayout *layout = new QVBoxLayout;
|
|
setLayout(layout);
|
|
|
|
files = new QTreeWidget;
|
|
files->headerItem()->setText(0, tr("Filename"));
|
|
files->headerItem()->setText(1, tr("Date"));
|
|
files->headerItem()->setText(2, tr("Time"));
|
|
|
|
files->setColumnCount(3);
|
|
files->setColumnWidth(0, 190); // filename
|
|
files->setColumnWidth(1, 95); // date
|
|
files->setColumnWidth(2, 90); // time
|
|
files->setSelectionMode(QAbstractItemView::SingleSelection);
|
|
files->setUniformRowHeights(true);
|
|
files->setIndentation(0);
|
|
|
|
// populate with each ride in the ridelist
|
|
const QTreeWidgetItem *allRides = wizard->context->athlete->allRideItems();
|
|
|
|
for (int i=allRides->childCount()-1; i>=0; i--) {
|
|
|
|
RideItem *rideItem = static_cast<RideItem*>(allRides->child(i));
|
|
|
|
QTreeWidgetItem *add = new QTreeWidgetItem(files->invisibleRootItem());
|
|
add->setFlags(add->flags() & ~Qt::ItemIsEditable);
|
|
|
|
// we will wipe the original file
|
|
add->setText(0, rideItem->fileName);
|
|
add->setText(1, rideItem->dateTime.toString(tr("dd MMM yyyy")));
|
|
add->setText(2, rideItem->dateTime.toString("hh:mm:ss"));
|
|
}
|
|
|
|
layout->addWidget(files);
|
|
connect(files, SIGNAL(itemSelectionChanged()), this, SLOT(selected()));
|
|
}
|
|
|
|
void
|
|
MergeChoose::selected()
|
|
{
|
|
wizard->button(QWizard::BackButton)->setEnabled(true);
|
|
chosen = true;
|
|
next = 30;
|
|
emit completeChanged();
|
|
}
|
|
|
|
bool
|
|
MergeChoose::validatePage()
|
|
{
|
|
// make sure the currently selected ride has data
|
|
// and can be opened etc
|
|
QString filename = "none";
|
|
|
|
// which one is selected ?
|
|
if (files->currentItem()) filename=files->currentItem()->text(0);
|
|
|
|
// open it..
|
|
QStringList errors;
|
|
QList<RideFile*> rides;
|
|
QFile thisfile(QString(wizard->context->athlete->home->activities().absolutePath()+"/"+filename));
|
|
RideFile *ride = RideFileFactory::instance().openRideFile(wizard->context, thisfile, errors, &rides);
|
|
|
|
if (ride && ride->dataPoints().count()) {
|
|
|
|
wizard->setRide(&wizard->ride2, ride);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void
|
|
MergeChoose::initializePage()
|
|
{
|
|
}
|
|
|
|
//Select mode for merge
|
|
MergeMode::MergeMode(MergeActivityWizard *parent) : QWizardPage(parent), wizard(parent)
|
|
{
|
|
setTitle(tr("Select Mode"));
|
|
setSubTitle(tr("How would you like to combine the data ?"));
|
|
|
|
QVBoxLayout *layout = new QVBoxLayout;
|
|
setLayout(layout);
|
|
|
|
mapper = new QSignalMapper(this);
|
|
connect(mapper, SIGNAL(mapped(QString)), this, SLOT(clicked(QString)));
|
|
|
|
// merge
|
|
QCommandLinkButton *p = new QCommandLinkButton(tr("Merge Data to add another data series"),
|
|
tr("Merge data series from one recording into the current activity"
|
|
" where different types of data (e.g. O2 data from a Moxy) have been "
|
|
" recorded by different devices. Taking care to align the data in time."), this);
|
|
connect(p, SIGNAL(clicked()), mapper, SLOT(map()));
|
|
mapper->setMapping(p, "merge");
|
|
layout->addWidget(p);
|
|
|
|
// join
|
|
p = new QCommandLinkButton(tr("Join Data to form a longer activity"),
|
|
tr("Append the data to the end of the current activity "
|
|
" to create a longer activity that was recorded in multiple"
|
|
" parts."), this);
|
|
connect(p, SIGNAL(clicked()), mapper, SLOT(map()));
|
|
mapper->setMapping(p, "join");
|
|
layout->addWidget(p);
|
|
|
|
label = new QLabel("", this);
|
|
layout->addWidget(label);
|
|
|
|
next = 40;
|
|
setFinalPage(false);
|
|
}
|
|
|
|
void
|
|
MergeMode::initializePage()
|
|
{
|
|
}
|
|
|
|
|
|
void
|
|
MergeMode::clicked(QString p)
|
|
{
|
|
// reset -- particularly since we might get here from
|
|
// other pages hitting 'Back'
|
|
initializePage();
|
|
|
|
if (p == "join") {
|
|
wizard->mode = 1; // join is easy !
|
|
wizard->combine(); // analyse not required just combined them !
|
|
next = 1000;
|
|
} else {
|
|
// where to next ?
|
|
wizard->mode = 0; // merge ...
|
|
next = 40;
|
|
}
|
|
wizard->next();
|
|
}
|
|
|
|
//Select merge strategy
|
|
MergeStrategy::MergeStrategy(MergeActivityWizard *parent) : QWizardPage(parent), wizard(parent)
|
|
{
|
|
setTitle(tr("Select Strategy"));
|
|
setSubTitle(tr("How should we align the data ?"));
|
|
|
|
QVBoxLayout *layout = new QVBoxLayout;
|
|
setLayout(layout);
|
|
|
|
mapper = new QSignalMapper(this);
|
|
connect(mapper, SIGNAL(mapped(QString)), this, SLOT(clicked(QString)));
|
|
|
|
// time
|
|
QCommandLinkButton *p = new QCommandLinkButton(tr("Align using start time"),
|
|
tr("Align the data from the two activities based upon the start time "
|
|
"for the activities. This will work well if the devices used to "
|
|
"record the data have their clocks synchronised / close to each other."), this);
|
|
connect(p, SIGNAL(clicked()), mapper, SLOT(map()));
|
|
mapper->setMapping(p, "time");
|
|
layout->addWidget(p);
|
|
|
|
// shared data
|
|
shared = new QCommandLinkButton(tr("Align using shared data series"),
|
|
tr("If the two activities both contain the same data series, for example "
|
|
"where both devices recorded cadence or perhaps HR, then we can align the other "
|
|
"data series in time by matching the peaks and troughs in the shared data."), this);
|
|
connect(shared, SIGNAL(clicked()), mapper, SLOT(map()));
|
|
mapper->setMapping(shared, "shared");
|
|
layout->addWidget(shared);
|
|
|
|
// start at same time
|
|
p = new QCommandLinkButton(tr("Align starting together"),
|
|
tr("Regardless of the timestamp on the activity, align with both "
|
|
"activities starting at the same time."), this);
|
|
connect(p, SIGNAL(clicked()), mapper, SLOT(map()));
|
|
mapper->setMapping(p, "left");
|
|
layout->addWidget(p);
|
|
|
|
// start at same time
|
|
p = new QCommandLinkButton(tr("Align ending together"),
|
|
tr("Regardless of the timestamp on the activity, align with both "
|
|
"activities ending at the same time."), this);
|
|
connect(p, SIGNAL(clicked()), mapper, SLOT(map()));
|
|
mapper->setMapping(p, "right");
|
|
layout->addWidget(p);
|
|
|
|
label = new QLabel("", this);
|
|
layout->addWidget(label);
|
|
|
|
next = 60;
|
|
setFinalPage(false);
|
|
}
|
|
|
|
void
|
|
MergeStrategy::initializePage()
|
|
{
|
|
// are there any shared data ????
|
|
bool hasShared = false;
|
|
QMapIterator<RideFile::SeriesType, QCheckBox *> i(wizard->rightSeries);
|
|
while(i.hasNext()) {
|
|
i.next();
|
|
if (wizard->leftSeries.value(i.key(), NULL) != NULL)
|
|
hasShared = true;
|
|
}
|
|
shared->setEnabled(hasShared);
|
|
}
|
|
|
|
|
|
void
|
|
MergeStrategy::clicked(QString p)
|
|
{
|
|
// reset -- particularly since we might get here from
|
|
// other pages hitting 'Back'
|
|
initializePage();
|
|
|
|
if (p == "time") {
|
|
wizard->strategy = 0;
|
|
} else if (p == "shared" ) {
|
|
// where to next ?
|
|
wizard->strategy = 1; // merge ...
|
|
} else if (p == "left" ) {
|
|
wizard->strategy = 2; // merge ...
|
|
} else if (p == "right" ) {
|
|
wizard->strategy = 3; // merge ...
|
|
}
|
|
|
|
// now run strategy and get on
|
|
wizard->analyse();
|
|
wizard->combine();
|
|
|
|
// lets do this thing !
|
|
next = 60;
|
|
wizard->next();
|
|
}
|
|
|
|
// Synchronise start of files
|
|
MergeAdjust::MergeAdjust(MergeActivityWizard *parent) : QWizardPage(parent), wizard(parent)
|
|
{
|
|
setTitle(tr("Adjust Alignment"));
|
|
setSubTitle(tr("Adjust merge alignment in time"));
|
|
|
|
// Plot files
|
|
QVBoxLayout *layout = new QVBoxLayout;
|
|
setLayout(layout);
|
|
}
|
|
|
|
void
|
|
MergeAdjust::initializePage()
|
|
{
|
|
}
|
|
|
|
// select
|
|
MergeSelect::MergeSelect(MergeActivityWizard *parent) : QWizardPage(parent), wizard(parent)
|
|
{
|
|
setTitle(tr("Merge Data Series"));
|
|
setSubTitle(tr("Select the series to merge together"));
|
|
|
|
QVBoxLayout *mainlayout = new QVBoxLayout(this);
|
|
|
|
QFont def;
|
|
def.setWeight(QFont::Bold);
|
|
|
|
QHBoxLayout *names = new QHBoxLayout;
|
|
leftName = new QLabel("Current Selection", this);
|
|
rightName = new QLabel("right", this);
|
|
leftName->setFixedHeight(20);
|
|
leftName->setFont(def);
|
|
leftName->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
|
|
rightName->setFixedHeight(20);
|
|
rightName->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
|
|
rightName->setFont(def);
|
|
names->addWidget(leftName);
|
|
names->addWidget(rightName);
|
|
mainlayout->addLayout(names);
|
|
|
|
QHBoxLayout *leftright = new QHBoxLayout;
|
|
mainlayout->addLayout(leftright);
|
|
|
|
QScrollArea *left = new QScrollArea(this);
|
|
left->setAutoFillBackground(false);
|
|
left->setWidgetResizable(true);
|
|
left->setContentsMargins(0,0,0,0);
|
|
QScrollArea *right = new QScrollArea(this);
|
|
right->setAutoFillBackground(false);
|
|
right->setWidgetResizable(true);
|
|
right->setContentsMargins(0,0,0,0);
|
|
|
|
leftright->addWidget(left);
|
|
leftright->addWidget(right);
|
|
|
|
// checkboxes on the left
|
|
QWidget *leftWidget = new QWidget(this);
|
|
leftWidget->setContentsMargins(0,0,0,0);
|
|
leftLayout = new QVBoxLayout(leftWidget);
|
|
leftLayout->setContentsMargins(0,0,0,0);
|
|
leftLayout->setSpacing(0);
|
|
|
|
// checkboxes on the right
|
|
QWidget *rightWidget = new QWidget(this);
|
|
rightWidget->setContentsMargins(0,0,0,0);
|
|
rightLayout = new QVBoxLayout(rightWidget);
|
|
rightLayout->setContentsMargins(0,0,0,0);
|
|
rightLayout->setSpacing(0);
|
|
|
|
left->setWidget(leftWidget);
|
|
right->setWidget(rightWidget);
|
|
}
|
|
|
|
void
|
|
MergeSelect::initializePage()
|
|
{
|
|
rightName->setText(QString("%1 (%2)").arg(wizard->ride2->startTime().toString("d MMM yy hh:mm:ss"))
|
|
.arg(wizard->ride2->deviceType()));
|
|
|
|
// create a checkbox for each series present in
|
|
QMapIterator<RideFile::SeriesType, QCheckBox*> i(wizard->leftSeries);
|
|
while(i.hasNext()) {
|
|
i.next();
|
|
delete i.value();
|
|
}
|
|
QMapIterator<RideFile::SeriesType, QCheckBox*> j(wizard->rightSeries);
|
|
while(j.hasNext()) {
|
|
j.next();
|
|
delete j.value();
|
|
}
|
|
wizard->leftSeries.clear();
|
|
wizard->rightSeries.clear();
|
|
|
|
// get rid of the stretch
|
|
leftLayout->takeAt(0);
|
|
rightLayout->takeAt(0);
|
|
|
|
for(int i=0; i < static_cast<int>(RideFile::none); i++) {
|
|
|
|
// save us casting all the time
|
|
RideFile::SeriesType series = static_cast<RideFile::SeriesType>(i);
|
|
|
|
// the one thing we don't select !
|
|
if (series == RideFile::secs) continue;
|
|
|
|
bool lefthas = false;
|
|
if (wizard->ride1->isDataPresent(series)) {
|
|
lefthas = true;
|
|
|
|
QCheckBox *add = new QCheckBox(wizard->ride1->seriesName(series));
|
|
add->setChecked(true);
|
|
add->setEnabled(false);
|
|
|
|
leftLayout->addWidget(add);
|
|
wizard->leftSeries.insert(series, add);
|
|
}
|
|
|
|
if (wizard->ride2->isDataPresent(series)) {
|
|
QCheckBox *add = new QCheckBox(wizard->ride2->seriesName(series));
|
|
add->setChecked(!lefthas);
|
|
|
|
rightLayout->addWidget(add);
|
|
wizard->rightSeries.insert(series, add);
|
|
|
|
connect(add, SIGNAL(stateChanged(int)), this, SLOT(checkboxes()));
|
|
}
|
|
}
|
|
|
|
leftLayout->addStretch();
|
|
rightLayout->addStretch();
|
|
}
|
|
|
|
void
|
|
MergeSelect::checkboxes()
|
|
{
|
|
// as we turn on/off right side checkboxes we need to
|
|
// turn off/on left side checkboxes
|
|
QMapIterator<RideFile::SeriesType,QCheckBox*> i(wizard->rightSeries);
|
|
while(i.hasNext()) {
|
|
|
|
i.next();
|
|
QCheckBox *left=NULL;
|
|
if ((left=wizard->leftSeries.value(i.key(), NULL)) != NULL) {
|
|
left->setChecked(!i.value()->isChecked());
|
|
}
|
|
}
|
|
}
|
|
|
|
MergeConfirm::MergeConfirm(MergeActivityWizard *parent) : QWizardPage(parent), wizard(parent)
|
|
{
|
|
setTitle(tr("Confirm"));
|
|
setSubTitle(tr("Complete and Save"));
|
|
|
|
QVBoxLayout *layout = new QVBoxLayout;
|
|
setLayout(layout);
|
|
|
|
QLabel *label = new QLabel(tr("Press Finish to update the current ride with "
|
|
" the combined data.\n\n"
|
|
"The changes will not be saved until you save them "
|
|
" so you can check and revert or save.\n\n"
|
|
"If you continue the ride will be updated, if you "
|
|
"do not want to continue either go back and change "
|
|
"the settings or press cancel to abort."));
|
|
label->setWordWrap(true);
|
|
|
|
layout->addWidget(label);
|
|
layout->addStretch();
|
|
}
|
|
|
|
bool
|
|
MergeConfirm::validatePage()
|
|
{
|
|
// We are done -- update BUT DOESNT SAVE
|
|
// user can now check !
|
|
wizard->current->setRide(wizard->combined);
|
|
wizard->context->notifyRideDirty();
|
|
return true;
|
|
}
|