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"