From 8a09fe0ca2c2e4ec7f3cbc141e205c8d2591bf24 Mon Sep 17 00:00:00 2001 From: Mark Liversedge Date: Sat, 27 Jun 2015 12:30:27 +0100 Subject: [PATCH] Enable user to configure autodiscovery .. select which kind of intervals we want .. makes it faster and also reduces the size of the rideDB.json file quite dramatically which may be useful for some users. --- src/AllPlotInterval.cpp | 2 +- src/ConfigDialog.cpp | 38 ++++++++++++++++++++++++- src/ConfigDialog.h | 20 ++++++++++++++ src/Context.h | 1 + src/Pages.cpp | 49 +++++++++++++++++++++++++++++++++ src/Pages.h | 26 ++++++++++++++++++ src/RideCache.cpp | 8 +----- src/RideCache.h | 1 - src/RideDB.h | 3 +- src/RideFile.cpp | 35 +++++++++++++++++++---- src/RideFile.h | 24 +++++++++------- src/RideItem.cpp | 58 +++++++++++++++++++++++++-------------- src/RideSummaryWindow.cpp | 2 ++ src/Settings.h | 3 ++ 14 files changed, 223 insertions(+), 47 deletions(-) diff --git a/src/AllPlotInterval.cpp b/src/AllPlotInterval.cpp index 48a91cab2..c9b5880c5 100644 --- a/src/AllPlotInterval.cpp +++ b/src/AllPlotInterval.cpp @@ -175,7 +175,7 @@ AllPlotInterval::sortIntervals(QList &intervals, QList< QListtype == RideFileInterval::MATCH) { + if (groupMatch && interval->type == RideFileInterval::USER) { matchesGroup.append(interval); intervals.removeOne(interval); //intervals.move(i, place++); diff --git a/src/ConfigDialog.cpp b/src/ConfigDialog.cpp index 17af22072..0d69eae12 100644 --- a/src/ConfigDialog.cpp +++ b/src/ConfigDialog.cpp @@ -65,6 +65,7 @@ ConfigDialog::ConfigDialog(QDir _home, Zones *_zones, Context *context) : static QIcon appearanceIcon(QPixmap(":/images/toolbar/color.png")); static QIcon dataIcon(QPixmap(":/images/toolbar/data.png")); static QIcon metricsIcon(QPixmap(":/images/toolbar/abacus.png")); + static QIcon intervalIcon(QPixmap(":/images/stopwatch.png")); static QIcon devicesIcon(QPixmap(":/images/devices/kickr.png")); // Setup the signal mapping so the right config @@ -100,10 +101,15 @@ ConfigDialog::ConfigDialog(QDir _home, Zones *_zones, Context *context) : connect(added, SIGNAL(triggered()), iconMapper, SLOT(map())); iconMapper->setMapping(added, 5); - added =head->addAction(devicesIcon, tr("Train Devices")); + added =head->addAction(intervalIcon, tr("Intervals")); connect(added, SIGNAL(triggered()), iconMapper, SLOT(map())); iconMapper->setMapping(added, 6); + + added =head->addAction(devicesIcon, tr("Train Devices")); + connect(added, SIGNAL(triggered()), iconMapper, SLOT(map())); + iconMapper->setMapping(added, 7); + // more space spacer = new QWidget(this); spacer->setAutoFillBackground(false); @@ -144,6 +150,11 @@ ConfigDialog::ConfigDialog(QDir _home, Zones *_zones, Context *context) : metric->setWhatsThis(metricHelp->getWhatsThisText(HelpWhatsThis::Preferences_Metrics)); pagesWidget->addWidget(metric); + interval = new IntervalConfig(_home, _zones, context); + //HelpWhatsThis *intervalHelp = new HelpWhatsThis(interval); + //interval->setWhatsThis(intervalHelp->getWhatsThisText(HelpWhatsThis::Preferences_Intervals)); + pagesWidget->addWidget(interval); + device = new DeviceConfig(_home, _zones, context); HelpWhatsThis *deviceHelp = new HelpWhatsThis(device); device->setWhatsThis(deviceHelp->getWhatsThisText(HelpWhatsThis::Preferences_TrainDevices)); @@ -208,6 +219,7 @@ void ConfigDialog::saveClicked() changed |= metric->saveClicked(); changed |= data->saveClicked(); changed |= device->saveClicked(); + changed |= interval->saveClicked(); hide(); @@ -410,6 +422,30 @@ qint32 MetricConfig::saveClicked() return state; } +IntervalConfig::IntervalConfig(QDir home, Zones *zones, Context *context) : + home(home), zones(zones), context(context) +{ + // the widgets + intervalsPage = new IntervalsPage(context); + + setContentsMargins(0,0,0,0); + QHBoxLayout *mainLayout = new QHBoxLayout(this); + mainLayout->setSpacing(0); + mainLayout->setContentsMargins(0,0,0,0); + + mainLayout->addWidget(intervalsPage); + mainLayout->addStretch(); +} + +qint32 IntervalConfig::saveClicked() +{ + qint32 state = 0; + + state |= intervalsPage->saveClicked(); + + return state; +} + // GENERAL CONFIG DeviceConfig::DeviceConfig(QDir home, Zones *zones, Context *context) : home(home), zones(zones), context(context) diff --git a/src/ConfigDialog.h b/src/ConfigDialog.h index 7d6e8e1ef..03f840ff8 100644 --- a/src/ConfigDialog.h +++ b/src/ConfigDialog.h @@ -176,6 +176,25 @@ class DeviceConfig : public QWidget DevicePage *devicePage; }; +// INTERCAL PAGE +class IntervalConfig : public QWidget +{ + Q_OBJECT + + public: + IntervalConfig(QDir home, Zones *zones, Context *context); + + public slots: + qint32 saveClicked(); + + private: + QDir home; + Zones *zones; + Context *context; + + IntervalsPage *intervalsPage; +}; + class ConfigDialog : public QMainWindow { Q_OBJECT @@ -206,6 +225,7 @@ class ConfigDialog : public QMainWindow PasswordConfig *password; DataConfig *data; MetricConfig *metric; + IntervalConfig *interval; DeviceConfig *device; }; #endif diff --git a/src/Context.h b/src/Context.h index cec7cbf6f..521a218a8 100644 --- a/src/Context.h +++ b/src/Context.h @@ -51,6 +51,7 @@ #define CONFIG_PMC 0x1000 // PMC constants #define CONFIG_WBAL 0x2000 // which w'bal formula to use ? #define CONFIG_WORKOUTS 0x4000 // workout location / files +#define CONFIG_DISCOVERY 0x8000 // interval discovery class RideItem; class IntervalItem; diff --git a/src/Pages.cpp b/src/Pages.cpp index 96fe777a3..a691d4f51 100644 --- a/src/Pages.cpp +++ b/src/Pages.cpp @@ -5522,3 +5522,52 @@ AutoImportPage::browseImportDir() } } +IntervalsPage::IntervalsPage(Context *context) : context(context) +{ + // get config + b4.discovery = appsettings->cvalue(context->athlete->cyclist, GC_DISCOVERY, 63).toInt(); + + QVBoxLayout *mainLayout = new QVBoxLayout(this); + QGridLayout *layout = new QGridLayout(); + mainLayout->addLayout(layout); + mainLayout->addStretch(); + + QLabel *heading = new QLabel(tr("Enable interval auto-discovery:")); + heading->setFixedHeight(QFontMetrics(heading->font()).height() + 4); + + int row = 0; + layout->addWidget(heading, row, 0, Qt::AlignRight); + + user = 99; + for(int i=0; i< static_cast(RideFileInterval::last()); i++) { + + // ignore until we get past user interval type + if (i == static_cast(RideFileInterval::USER)) user=i; + + // if past user then add + if (i>user) { + QCheckBox *add = new QCheckBox(RideFileInterval::typeDescriptionLong(static_cast(i))); + checkBoxes << add; + add->setChecked(b4.discovery & RideFileInterval::intervalTypeBits(static_cast(i))); + layout->addWidget(add, row++, 1, Qt::AlignLeft); + } + } +} + +qint32 +IntervalsPage::saveClicked() +{ + int discovery = 0; + + // now update discovery ! + for(int i=0; i< checkBoxes.count(); i++) + if (checkBoxes[i]->isChecked()) + discovery += RideFileInterval::intervalTypeBits(static_cast(i+user+1)); + + // new value returned + appsettings->setCValue(context->athlete->cyclist, GC_DISCOVERY, discovery); + + // return change ! + if (b4.discovery != discovery) return CONFIG_DISCOVERY; + else return 0; +} diff --git a/src/Pages.h b/src/Pages.h index b686b7c3a..9f2c3c4ad 100644 --- a/src/Pages.h +++ b/src/Pages.h @@ -972,5 +972,31 @@ class AutoImportPage : public QWidget }; +class IntervalsPage : public QWidget +{ + Q_OBJECT + G_OBJECT + + public: + IntervalsPage(Context *context); + qint32 saveClicked(); + + unsigned int discoveryWAS; + + public slots: + + private: + Context *context; + int user; //index of user interval type + + QList checkBoxes; + + struct { + int discovery; + } b4; + + private slots: +}; + #endif diff --git a/src/RideCache.cpp b/src/RideCache.cpp index e8969963c..b042835c9 100644 --- a/src/RideCache.cpp +++ b/src/RideCache.cpp @@ -45,12 +45,6 @@ RideCache::RideCache(Context *context) : context(context) progress_ = 100; exiting = false; - // get the new zone configuration fingerprint - fingerprint = static_cast(context->athlete->zones()->getFingerprint()) - + static_cast(context->athlete->paceZones()->getFingerprint()) - + static_cast(context->athlete->hrZones()->getFingerprint()) - + static_cast(context->athlete->routes->getFingerprint()); - // set the list // populate ride list RideItem *last = NULL; @@ -123,7 +117,7 @@ RideCache::configChanged(qint32 what) // if zones or weight has changed refresh metrics // will add more as they come - qint32 want = CONFIG_ATHLETE | CONFIG_ZONES | CONFIG_NOTECOLOR | CONFIG_GENERAL; + qint32 want = CONFIG_ATHLETE | CONFIG_ZONES | CONFIG_NOTECOLOR | CONFIG_DISCOVERY | CONFIG_GENERAL; if (what & want) { // restart ! diff --git a/src/RideCache.h b/src/RideCache.h index f98541dc7..a84476ad3 100644 --- a/src/RideCache.h +++ b/src/RideCache.h @@ -132,7 +132,6 @@ class RideCache : public QObject RideCacheModel *model_; bool exiting; double progress_; // percent - unsigned long fingerprint; // zone configuration fingerprint QFuture future; QFutureWatcher watcher; diff --git a/src/RideDB.h b/src/RideDB.h index e3b061188..cb73ee6fb 100644 --- a/src/RideDB.h +++ b/src/RideDB.h @@ -35,8 +35,9 @@ // 1.0 Dec 2014 Mark Liversedge initial version // 1.1 12 Dec 14 Mark Liversedge added color, isRun and present // 1.2 03 May 15 Mark Liversedge added intervals, samples bool and metric <> 0 +// 1.3 27 Jun 15 Mark Liversedge rationalised all the discovery intervals -#define RIDEDB_VERSION "1.2" +#define RIDEDB_VERSION "1.3" #endif diff --git a/src/RideFile.cpp b/src/RideFile.cpp index 5bc1b1af8..7a7d8a030 100644 --- a/src/RideFile.cpp +++ b/src/RideFile.cpp @@ -396,15 +396,40 @@ QString RideFileInterval::typeDescription(intervaltype x) case ALL : return tr("ALL"); break; case DEVICE : return tr("DEVICE"); break; case USER : return tr("USER"); break; + case PEAKPACE : return tr("PEAK PACE"); break; case PEAKPOWER : return tr("PEAK POWER"); break; - case SPRINT : return tr("SPRINT"); break; case ROUTE : return tr("SEGMENTS"); break; - case PEAKHR : return tr("PEAK HR"); break; case CLIMB : return tr("CLIMBING"); break; case EFFORT : return tr("EFFORTS"); break; - case MATCH : return tr("MATCHES"); break; - case TTE : return tr("EXHAUSTION"); break; - case PEAKPACE : return tr("PEAK PACE"); break; + } +} +QString RideFileInterval::typeDescriptionLong(intervaltype x) +{ + switch (x) { + default: + case ALL : return tr("The entire activity"); break; + case DEVICE : return tr("Device specific intervals"); break; + case USER : return tr("User defined laps or marked intervals"); break; + case PEAKPACE : return tr("Peak pace for running and swimming"); break; + case PEAKPOWER : return tr("Peak powers for cycling 1s thru 1hr"); break; + case ROUTE : return tr("Route segments using GPS data"); break; + case CLIMB : return tr("Ascents for hills and mountains"); break; + case EFFORT : return tr("Sustained efforts and matches using power"); break; + } +} +qint32 +RideFileInterval::intervalTypeBits(intervaltype x) // used for config what is/isn't autodiscovered +{ + switch (x) { + default: + case ALL : return 1; + case DEVICE : return 0; + case USER : return 0; + case PEAKPACE : return 2; + case PEAKPOWER : return 4; + case ROUTE : return 8; + case CLIMB : return 16; + case EFFORT : return 32; } } diff --git a/src/RideFile.h b/src/RideFile.h index b0ec3bac2..f4c96a809 100644 --- a/src/RideFile.h +++ b/src/RideFile.h @@ -90,22 +90,26 @@ class RideFileInterval Q_DECLARE_TR_FUNCTIONS(RideFileInterval); public: - enum intervaltype { ALL, // Entire workout - DEVICE, // Came from Device (Calibration?) + enum intervaltype { DEVICE, // Came from Device (Calibration?) USER, // User defined + + // DISCOVERED ALWAYS AFTER USER BELOW + ALL, // Entire workout PEAKPOWER, // Peak Power incl. ranking 1-10 in ride - ROUTE, // GPS Route - PEAKHR, // PEAK HR - CLIMB, // Hills and Cols + PEAKPACE, // Peak Pace EFFORT, // Sustained effort - MATCH, // W'bal based "match from a matchbook" - TTE, // A true TTE effort according to classic CP model - SPRINT, // Sprint - PEAKPACE // Peak Pace - } types; // ALWAYS ADD TO END (RideDB.json uses int values) + ROUTE, // GPS Route + CLIMB, // Hills and Cols + // ADD NEW ONES HERE AND UPDATE last() below + + } types; + + static enum intervaltype last() { return CLIMB; } // update to last above! typedef enum intervaltype IntervalType; static QString typeDescription(IntervalType); // return a string to represent the type + static QString typeDescriptionLong(IntervalType); // return a longer string to represent the type + static qint32 intervalTypeBits(IntervalType); // returns the bit value or'ed into GC_DISCOVERY QString typeString; IntervalType type; diff --git a/src/RideItem.cpp b/src/RideItem.cpp index 1da3b4fef..6f937f328 100644 --- a/src/RideItem.cpp +++ b/src/RideItem.cpp @@ -440,7 +440,8 @@ RideItem::checkStale() + static_cast(context->athlete->paceZones(false)->getFingerprint(dateTime.date())) + static_cast(context->athlete->paceZones(true)->getFingerprint(dateTime.date())) + static_cast(context->athlete->hrZones()->getFingerprint(dateTime.date())) - + static_cast(context->athlete->routes->getFingerprint()); + + static_cast(context->athlete->routes->getFingerprint()) + + appsettings->cvalue(context->athlete->cyclist, GC_DISCOVERY, 63).toInt(); if (fingerprint != rfingerprint) { @@ -546,7 +547,8 @@ RideItem::refresh() + static_cast(context->athlete->paceZones(false)->getFingerprint(dateTime.date())) + static_cast(context->athlete->paceZones(true)->getFingerprint(dateTime.date())) + static_cast(context->athlete->hrZones()->getFingerprint(dateTime.date())) - + static_cast(context->athlete->routes->getFingerprint()); + + static_cast(context->athlete->routes->getFingerprint()) + + + appsettings->cvalue(context->athlete->cyclist, GC_DISCOVERY, 63).toInt(); dbversion = DBSchemaVersion; timestamp = QDateTime::currentDateTime().toTime_t(); @@ -645,6 +647,9 @@ static bool intervalGreaterThanZone(const IntervalItem *a, const IntervalItem *b void RideItem::updateIntervals() { + // what do we need ? + int discovery = appsettings->cvalue(context->athlete->cyclist, GC_DISCOVERY, 63).toInt(); + // DO NOT USE ride() since it will call a refresh ! RideFile *f = ride_; @@ -652,7 +657,7 @@ RideItem::updateIntervals() intervals_.clear(); // no ride data available ? - if (!samples) { + if (!samples || !discovery) { context->notifyIntervalsUpdate(this); return; } @@ -693,19 +698,23 @@ RideItem::updateIntervals() RideFilePoint *begin = f->dataPoints().first(); RideFilePoint *end = f->dataPoints().last(); - // add entire ride using ride metrics - IntervalItem *entire = new IntervalItem(this, tr("Entire Activity"), - begin->secs, end->secs, - f->timeToDistance(begin->secs), - f->timeToDistance(end->secs), - 0, - QColor(Qt::darkBlue), - RideFileInterval::ALL); + // ALL interval + if (discovery & RideFileInterval::intervalTypeBits(RideFileInterval::ALL)) { - // same as the whole ride, not need to compute - entire->metrics() = metrics(); - entire->rideInterval = NULL; - intervals_ << entire; + // add entire ride using ride metrics + IntervalItem *entire = new IntervalItem(this, tr("Entire Activity"), + begin->secs, end->secs, + f->timeToDistance(begin->secs), + f->timeToDistance(end->secs), + 0, + QColor(Qt::darkBlue), + RideFileInterval::ALL); + + // same as the whole ride, not need to compute + entire->metrics() = metrics(); + entire->rideInterval = NULL; + intervals_ << entire; + } int count = 0; foreach(RideFileInterval *interval, f->intervals()) { @@ -748,7 +757,8 @@ RideItem::updateIntervals() // DISCOVERY //qDebug() << "SEARCH PEAK POWERS" - if (!f->isRun() && !f->isSwim() && f->isDataPresent(RideFile::watts)) { + if ((discovery & RideFileInterval::intervalTypeBits(RideFileInterval::PEAKPOWER)) && + !f->isRun() && !f->isSwim() && f->isDataPresent(RideFile::watts)) { // what we looking for ? static int durations[] = { 1, 5, 10, 15, 20, 30, 60, 300, 600, 1200, 1800, 2700, 3600, 0 }; @@ -780,7 +790,8 @@ RideItem::updateIntervals() } //qDebug() << "SEARCH PEAK PACE" - if ((f->isRun() || f->isSwim()) && f->isDataPresent(RideFile::kph)) { + if ((discovery & RideFileInterval::intervalTypeBits(RideFileInterval::PEAKPACE)) && + (f->isRun() || f->isSwim()) && f->isDataPresent(RideFile::kph)) { // what we looking for ? static int durations[] = { 10, 15, 20, 30, 60, 300, 600, 1200, 1800, 2700, 3600, 0 }; @@ -819,7 +830,8 @@ RideItem::updateIntervals() QList candidates[10]; QList candidates_sprint; - if (CP > 0 && WPRIME > 0 && PMAX > 0 && !f->isRun() && !f->isSwim() && f->isDataPresent(RideFile::watts)) { + if ((discovery & RideFileInterval::intervalTypeBits(RideFileInterval::EFFORT)) && + CP > 0 && WPRIME > 0 && PMAX > 0 && !f->isRun() && !f->isSwim() && f->isDataPresent(RideFile::watts)) { const int SAMPLERATE = 1000; // 1000ms samplerate = 1 second samples @@ -1130,7 +1142,9 @@ RideItem::updateIntervals() } // if arraySize is in bounds, no indent from above //qDebug() << "SEARCH HILLS"; - if (!f->isSwim() && f->isDataPresent(RideFile::alt)) { + if ((discovery & RideFileInterval::intervalTypeBits(RideFileInterval::CLIMB)) && + !f->isSwim() && f->isDataPresent(RideFile::alt)) { + // log of progress QFile log(context->athlete->home->logs().canonicalPath() + "/" + "climb.log"); log.open(QIODevice::ReadWrite); @@ -1241,7 +1255,7 @@ RideItem::updateIntervals() //Search routes - if (f->isDataPresent(RideFile::lon)) { + if ((discovery & RideFileInterval::intervalTypeBits(RideFileInterval::ROUTE)) && f->isDataPresent(RideFile::lon)) { // set intervals for routes QList here; @@ -1256,7 +1270,8 @@ RideItem::updateIntervals() } // Search W' MATCHES incl. those that take us to EXHAUSTION - if (f->isDataPresent(RideFile::watts) && f->wprimeData()) { + if ((discovery & RideFileInterval::intervalTypeBits(RideFileInterval::EFFORT)) && + f->isDataPresent(RideFile::watts) && f->wprimeData()) { // add one for each foreach(struct Match match, f->wprimeData()->matches) { @@ -1302,6 +1317,7 @@ RideItem::updateIntervals() // aggregate in this array before updating the metric QList efforts = intervals(RideFileInterval::EFFORT); + // if not discovering then there won't be any! if (efforts.count()) { // we have some efforts so some time was in a sustained effort diff --git a/src/RideSummaryWindow.cpp b/src/RideSummaryWindow.cpp index b314336d4..a9dfa1bcf 100644 --- a/src/RideSummaryWindow.cpp +++ b/src/RideSummaryWindow.cpp @@ -361,6 +361,7 @@ RideSummaryWindow::refresh() } } +#if 0 // not used at present static QString rankingString(int number) { QString ext=""; @@ -388,6 +389,7 @@ static QString rankingString(int number) } return QString("%1%2").arg(number).arg(ext); } +#endif QString RideSummaryWindow::htmlSummary() diff --git a/src/Settings.h b/src/Settings.h index 6528f2a1e..ccee653bb 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -235,6 +235,9 @@ // wbal formula to use #define GC_WBALFORM "wbal/formula" +// intervals to discover +#define GC_DISCOVERY "intervals/discovery" + // success tracking of more complex upgrades stored on athlete level #define GC_UPGRADE_FOLDER_SUCCESS "upgradesuccess/folder"