diff --git a/src/ConfigDialog.cpp b/src/ConfigDialog.cpp index 08ce106ed..2650941fa 100644 --- a/src/ConfigDialog.cpp +++ b/src/ConfigDialog.cpp @@ -30,7 +30,7 @@ ConfigDialog::ConfigDialog(QDir _home, Zones *_zones, MainWindow *mainWindow) : home = _home; - cyclistPage = new CyclistPage(zones); + cyclistPage = new CyclistPage(mainWindow); contentsWidget = new QListWidget; contentsWidget->setViewMode(QListView::IconMode); @@ -43,7 +43,6 @@ ConfigDialog::ConfigDialog(QDir _home, Zones *_zones, MainWindow *mainWindow) : contentsWidget->setUniformItemSizes(true); configPage = new ConfigurationPage(mainWindow); - devicePage = new DevicePage(this); pagesWidget = new QStackedWidget; @@ -60,10 +59,6 @@ ConfigDialog::ConfigDialog(QDir _home, Zones *_zones, MainWindow *mainWindow) : // connect(closeButton, SIGNAL(clicked()), this, SLOT(reject())); // connect(saveButton, SIGNAL(clicked()), this, SLOT(accept())); connect(closeButton, SIGNAL(clicked()), this, SLOT(accept())); - connect(cyclistPage->btnBack, SIGNAL(clicked()), this, SLOT(back_Clicked())); - connect(cyclistPage->btnForward, SIGNAL(clicked()), this, SLOT(forward_Clicked())); - connect(cyclistPage->btnDelete, SIGNAL(clicked()), this, SLOT(delete_Clicked())); - connect(cyclistPage->calendar, SIGNAL(selectionChanged()), this, SLOT(calendarDateChanged())); // connect the pieces... connect(devicePage->typeSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(changedType(int))); @@ -127,10 +122,6 @@ void ConfigDialog::createIcons() } -void ConfigDialog::createNewRange() -{ -} - void ConfigDialog::changePage(QListWidgetItem *current, QListWidgetItem *previous) { if (!current) @@ -177,31 +168,8 @@ void ConfigDialog::save_Clicked() settings->setValue(GC_SB_NAME, settings->value(GC_SB_NAME,tr("Stress Balance"))); settings->setValue(GC_SB_ACRONYM, settings->value(GC_SB_ACRONYM,tr("SB"))); - - // if the CP text entry reads invalid, there's nothing we can do - int cp = cyclistPage->getCP(); - if (cp == 0) { - QMessageBox::warning(this, tr("Invalid CP"), "Please enter valid CP and try again."); - cyclistPage->setCPFocus(); - return; - } - - // if for some reason we have no zones yet, then create them - int range = cyclistPage->getCurrentRange(); - - // if this is new mode, or if no zone ranges are yet defined, set up the new range - if ((range == -1) || (cyclistPage->isNewMode())) - cyclistPage->setCurrentRange(range = zones->insertRangeAtDate(cyclistPage->selectedDate(), cp)); - else - zones->setCP(range, cyclistPage->getText().toInt()); - - zones->setZonesFromCP(range); - - // update the "new zone" checkbox to visible and unchecked - cyclistPage->checkboxNew->setChecked(Qt::Unchecked); - cyclistPage->checkboxNew->setEnabled(true); - - zones->write(home); + // Save Cyclist page stuff + cyclistPage->saveClicked(); // save interval metrics and ride data pages configPage->saveClicked(); @@ -213,70 +181,9 @@ void ConfigDialog::save_Clicked() // Tell MainWindow we changed config, so it can emit the signal // configChanged() to all its children mainWindow->notifyConfigChanged(); -} -void ConfigDialog::moveCalendarToCurrentRange() { - int range = cyclistPage->getCurrentRange(); - - if (range < 0) - return; - - QDate date; - - // put the cursor at the beginning of the selected range if it's not the first - if (range > 0) - date = zones->getStartDate(cyclistPage->getCurrentRange()); - // unless the range is the first range, in which case it goes at the end of that range - // use JulianDay to subtract one day from the end date, which is actually the first - // day of the following range - else - date = QDate::fromJulianDay(zones->getEndDate(cyclistPage->getCurrentRange()).toJulianDay() - 1); - - cyclistPage->setSelectedDate(date); -} - -void ConfigDialog::back_Clicked() -{ - QDate date; - cyclistPage->setCurrentRange(cyclistPage->getCurrentRange() - 1); - moveCalendarToCurrentRange(); -} - -void ConfigDialog::forward_Clicked() -{ - QDate date; - cyclistPage->setCurrentRange(cyclistPage->getCurrentRange() + 1); - moveCalendarToCurrentRange(); -} - -void ConfigDialog::delete_Clicked() { - int range = cyclistPage->getCurrentRange(); - int num_ranges = zones->getRangeSize(); - assert (num_ranges > 1); - QMessageBox msgBox; - msgBox.setText( - tr("Are you sure you want to delete the zone range\n" - "from %1 to %2?\n" - "(%3 range will extend to this date range):") . - arg(zones->getStartDateString(cyclistPage->getCurrentRange())) . - arg(zones->getEndDateString(cyclistPage->getCurrentRange())) . - arg((range > 0) ? "previous" : "next") - ); - QPushButton *deleteButton = msgBox.addButton(tr("Delete"),QMessageBox::YesRole); - msgBox.setStandardButtons(QMessageBox::Cancel); - msgBox.setDefaultButton(QMessageBox::Cancel); - msgBox.setIcon(QMessageBox::Critical); - msgBox.exec(); - if(msgBox.clickedButton() == deleteButton) - cyclistPage->setCurrentRange(zones->deleteRange(range)); - - zones->write(home); -} - -void ConfigDialog::calendarDateChanged() { - int range = zones->whichRange(cyclistPage->selectedDate()); - assert(range >= 0); - cyclistPage->setCurrentRange(range); + // close + accept(); } // diff --git a/src/ConfigDialog.h b/src/ConfigDialog.h index 1eb3892cd..a6e912ee1 100644 --- a/src/ConfigDialog.h +++ b/src/ConfigDialog.h @@ -21,10 +21,6 @@ class ConfigDialog : public QDialog public slots: void changePage(QListWidgetItem *current, QListWidgetItem *previous); void save_Clicked(); - void back_Clicked(); - void forward_Clicked(); - void delete_Clicked(); - void calendarDateChanged(); // device config slots void changedType(int); diff --git a/src/DBAccess.cpp b/src/DBAccess.cpp index ba6ea64f6..554cd5980 100644 --- a/src/DBAccess.cpp +++ b/src/DBAccess.cpp @@ -40,7 +40,7 @@ // DB Schema Version - YOU MUST UPDATE THIS IF THE SCHEMA VERSION CHANGES!!! // Schema version will change if a) the default metadata.xml is updated // or b) new metrics are added / old changed -static int DBSchemaVersion = 13; +static int DBSchemaVersion = 14; DBAccess::DBAccess(MainWindow* main, QDir home) : main(main), home(home) { diff --git a/src/LTMPlot.cpp b/src/LTMPlot.cpp index d9a8372e4..b5c1175a0 100644 --- a/src/LTMPlot.cpp +++ b/src/LTMPlot.cpp @@ -614,6 +614,7 @@ LTMPlot::pointHover(QwtPlotCurve *curve, int index) if (units == "seconds") { units = "hours"; // we translate from seconds to hours value = ceil(curve->y(index)*10.0)/10.0; + precision = 1; // new more precision since converting to hours } // output the tooltip diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index df62235fc..7a9b56852 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -122,7 +122,6 @@ MainWindow::MainWindow(const QDir &home) : if (!zones_->read(zonesFile)) { QMessageBox::critical(this, tr("Zones File Error"), zones_->errorString()); - zones_->clear(); } else if (! zones_->warningString().isEmpty()) QMessageBox::warning(this, tr("Reading Zones File"), zones_->warningString()); @@ -1335,6 +1334,18 @@ void MainWindow::dateChanged(const QDate &date) void MainWindow::notifyConfigChanged() { + // re-read Zones in case it changed + QFile zonesFile(home.absolutePath() + "/power.zones"); + if (zonesFile.exists()) { + if (!zones_->read(zonesFile)) { + QMessageBox::critical(this, tr("Zones File Error"), + zones_->errorString()); + } + else if (! zones_->warningString().isEmpty()) + QMessageBox::warning(this, tr("Reading Zones File"), zones_->warningString()); + } + + // now tell everyone else configChanged(); } diff --git a/src/Pages.cpp b/src/Pages.cpp index 5f3bf4cc3..424899cc6 100644 --- a/src/Pages.cpp +++ b/src/Pages.cpp @@ -185,8 +185,8 @@ ConfigurationPage::saveClicked() metadataPage->saveClicked(); } -CyclistPage::CyclistPage(const Zones *_zones): - zones(_zones) +CyclistPage::CyclistPage(MainWindow *main) : + main(main) { boost::shared_ptr settings = GetApplicationSettings(); @@ -198,38 +198,8 @@ CyclistPage::CyclistPage(const Zones *_zones): QVBoxLayout *cpLayout = new QVBoxLayout(cpTab); QVBoxLayout *pmLayout = new QVBoxLayout(pmTab); - lblThreshold = new QLabel(tr("Critical Power:")); - txtThreshold = new QLineEdit(); - - // the validator will prevent numbers above the upper limit - // from being entered, but will not prevent non-negative numbers - // below the lower limit (since it is still plausible a valid - // entry will result) - txtThresholdValidator = new QIntValidator(20,999,this); - txtThreshold->setValidator(txtThresholdValidator); - - btnBack = new QPushButton(this); - btnBack->setText(tr("Back")); - btnForward = new QPushButton(this); - btnForward->setText(tr("Forward")); - btnDelete = new QPushButton(this); - btnDelete->setText(tr("Delete Range")); - checkboxNew = new QCheckBox(this); - checkboxNew->setText(tr("New Range from Date")); - btnForward->setEnabled(false); - txtStartDate = new QLabel("BEGIN"); - txtEndDate = new QLabel("END"); - lblStartDate = new QLabel("Start: "); - lblStartDate->setAlignment(Qt::AlignRight); - lblEndDate = new QLabel("End: "); - lblEndDate->setAlignment(Qt::AlignRight); - - - calendar = new QCalendarWidget(this); - - lblCurRange = new QLabel(this); - lblCurRange->setFrameStyle(QFrame::Panel | QFrame::Sunken); - lblCurRange->setText(QString("Current Zone Range: %1").arg(currentRange + 1)); + zonePage = new ZonePage(main); + cpLayout->addWidget(zonePage); perfManLabel = new QLabel(tr("Performance Manager")); showSBToday = new QCheckBox(tr("Show Stress Balance Today"), this); @@ -256,46 +226,6 @@ CyclistPage::CyclistPage(const Zones *_zones): perfManLTSavg = new QLineEdit(perfManLTSVal.toString(),this); perfManLTSavg->setValidator(perfManLTSavgValidator); - - QDate today = QDate::currentDate(); - calendar->setSelectedDate(today); - - if (zones->getRangeSize() == 0) - setCurrentRange(); - else - { - setCurrentRange(zones->whichRange(today)); - btnDelete->setEnabled(true); - checkboxNew->setCheckState(Qt::Unchecked); - } - - int cp = currentRange != -1 ? zones->getCP(currentRange) : 0; - if (cp > 0) - setCP(cp); - - //Layout - powerLayout = new QHBoxLayout(); - powerLayout->addWidget(lblThreshold); - powerLayout->addWidget(txtThreshold); - - rangeLayout = new QHBoxLayout(); - rangeLayout->addWidget(lblCurRange); - - dateRangeLayout = new QHBoxLayout(); - dateRangeLayout->addWidget(lblStartDate); - dateRangeLayout->addWidget(txtStartDate); - dateRangeLayout->addWidget(lblEndDate); - dateRangeLayout->addWidget(txtEndDate); - - zoneLayout = new QHBoxLayout(); - zoneLayout->addWidget(btnBack); - zoneLayout->addWidget(btnForward); - zoneLayout->addWidget(btnDelete); - zoneLayout->addWidget(checkboxNew); - - calendarLayout = new QHBoxLayout(); - calendarLayout->addWidget(calendar); - // performance manager perfManLayout = new QVBoxLayout(); // outer perfManStartValLayout = new QHBoxLayout(); @@ -313,17 +243,7 @@ CyclistPage::CyclistPage(const Zones *_zones): perfManLayout->addLayout(perfManLTSavgLayout); perfManLayout->addStretch(); - - - cyclistLayout = new QVBoxLayout; - cyclistLayout->addLayout(powerLayout); - cyclistLayout->addLayout(rangeLayout); - cyclistLayout->addLayout(zoneLayout); - cyclistLayout->addLayout(dateRangeLayout); - cyclistLayout->addLayout(calendarLayout); - cyclistLayout->addStretch(); - - cpLayout->addLayout(cyclistLayout); + //cpLayout->addLayout(cyclistLayout); pmLayout->addLayout(perfManLayout); mainLayout = new QVBoxLayout; @@ -331,6 +251,13 @@ CyclistPage::CyclistPage(const Zones *_zones): setLayout(mainLayout); } +void +CyclistPage::saveClicked() +{ + // save zone config (other stuff is saved by configdialog) + zonePage->saveClicked(); +} + void ConfigurationPage::browseWorkoutDir() { @@ -339,84 +266,6 @@ ConfigurationPage::browseWorkoutDir() workoutDirectory->setText(dir); } -QString CyclistPage::getText() -{ - return txtThreshold->text(); -} - -int CyclistPage::getCP() -{ - int cp = txtThreshold->text().toInt(); - return ( - ( - (cp >= txtThresholdValidator->bottom()) && - (cp <= txtThresholdValidator->top()) - ) ? - cp : - 0 - ); -} - -void CyclistPage::setCP(int cp) -{ - txtThreshold->setText(QString("%1").arg(cp)); -} - -void CyclistPage::setSelectedDate(QDate date) -{ - calendar->setSelectedDate(date); -} - -void CyclistPage::setCurrentRange(int range) -{ - int num_ranges = zones->getRangeSize(); - if ((num_ranges == 0) || (range < 0)) { - btnBack->setEnabled(false); - btnDelete->setEnabled(false); - btnForward->setEnabled(false); - calendar->setEnabled(false); - checkboxNew->setCheckState(Qt::Checked); - checkboxNew->setEnabled(false); - currentRange = -1; - lblCurRange->setText("no Current Zone Range"); - txtEndDate->setText("undefined"); - txtStartDate->setText("undefined"); - return; - } - - assert ((range >= 0) && (range < num_ranges)); - currentRange = range; - - // update the labels - lblCurRange->setText(QString("Current Zone Range: %1").arg(currentRange + 1)); - - // update the visibility of the range select buttons - btnForward->setEnabled(currentRange < num_ranges - 1); - btnBack->setEnabled(currentRange > 0); - - // if we have ranges to set to, then the calendar must be on - calendar->setEnabled(true); - - // update the CP display - setCP(zones->getCP(currentRange)); - - // update date limits - txtStartDate->setText(zones->getStartDateString(currentRange)); - txtEndDate->setText(zones->getEndDateString(currentRange)); -} - - -int CyclistPage::getCurrentRange() -{ - return currentRange; -} - - -bool CyclistPage::isNewMode() -{ - return (checkboxNew->checkState() == Qt::Checked); -} - DevicePage::DevicePage(QWidget *parent) : QWidget(parent) { QTabWidget *tabs = new QTabWidget(this); @@ -1369,3 +1218,524 @@ FieldsPage::getDefinitions(QList &fieldList) fieldList.append(add); } } + +// +// Zone Config page +// +ZonePage::ZonePage(MainWindow *main) : main(main) +{ + QVBoxLayout *layout = new QVBoxLayout(this); + + // get current config by reading it in (leave mainwindow zones alone) + QFile zonesFile(main->home.absolutePath() + "/power.zones"); + if (zonesFile.exists()) { + zones.read(zonesFile); + zonesFile.close(); + } + + // setup maintenance pages using current config + schemePage = new SchemePage(this); + cpPage = new CPPage(this); + + tabs = new QTabWidget(this); + tabs->addTab(cpPage, tr("Critical Power History")); + tabs->addTab(schemePage, tr("Default Zones")); + + layout->addWidget(tabs); +} + +void +ZonePage::saveClicked() +{ + zones.setScheme(schemePage->getScheme()); + zones.write(main->home); +} + +SchemePage::SchemePage(ZonePage* zonePage) : zonePage(zonePage) +{ + QGridLayout *mainLayout = new QGridLayout(this); + + addButton = new QPushButton(tr("Add")); + renameButton = new QPushButton(tr("Rename")); + deleteButton = new QPushButton(tr("Delete")); + + QVBoxLayout *actionButtons = new QVBoxLayout; + actionButtons->addWidget(addButton); + actionButtons->addWidget(renameButton); + actionButtons->addWidget(deleteButton); + actionButtons->addStretch(); + + scheme = new QTreeWidget; + scheme->headerItem()->setText(0, tr("Short")); + scheme->headerItem()->setText(1, tr("Long")); + scheme->headerItem()->setText(2, tr("Percent of CP")); + scheme->setColumnCount(3); + scheme->setSelectionMode(QAbstractItemView::SingleSelection); + scheme->setEditTriggers(QAbstractItemView::SelectedClicked); // allow edit + scheme->setUniformRowHeights(true); + scheme->setIndentation(0); + scheme->header()->resizeSection(0,90); + scheme->header()->resizeSection(1,200); + scheme->header()->resizeSection(2,80); + + // setup list + for (int i=0; i< zonePage->zones.getScheme().nzones_default; i++) { + + QTreeWidgetItem *add = new QTreeWidgetItem(scheme->invisibleRootItem()); + add->setFlags(add->flags() | Qt::ItemIsEditable); + + // tab name + add->setText(0, zonePage->zones.getScheme().zone_default_name[i]); + // field name + add->setText(1, zonePage->zones.getScheme().zone_default_desc[i]); + + // low + QDoubleSpinBox *loedit = new QDoubleSpinBox(this); + loedit->setMinimum(0); + loedit->setMaximum(1000); + loedit->setSingleStep(1.0); + loedit->setDecimals(0); + loedit->setValue(zonePage->zones.getScheme().zone_default[i]); + scheme->setItemWidget(add, 2, loedit); + } + + mainLayout->addWidget(scheme, 0,0); + mainLayout->addLayout(actionButtons, 0,1); + + // button connect + connect(addButton, SIGNAL(clicked()), this, SLOT(addClicked())); + connect(renameButton, SIGNAL(clicked()), this, SLOT(renameClicked())); + connect(deleteButton, SIGNAL(clicked()), this, SLOT(deleteClicked())); +} + +void +SchemePage::addClicked() +{ + // are we at maximum already? + if (scheme->invisibleRootItem()->childCount() == 10) { + QMessageBox err; + err.setText("Maximum of 10 zones reached."); + err.setIcon(QMessageBox::Warning); + err.exec(); + return; + } + + int index = scheme->invisibleRootItem()->childCount(); + + // new item + QTreeWidgetItem *add = new QTreeWidgetItem; + add->setFlags(add->flags() | Qt::ItemIsEditable); + + QDoubleSpinBox *loedit = new QDoubleSpinBox(this); + loedit->setMinimum(0); + loedit->setMaximum(1000); + loedit->setSingleStep(1.0); + loedit->setDecimals(0); + loedit->setValue(100); + + scheme->invisibleRootItem()->insertChild(index, add); + scheme->setItemWidget(add, 2, loedit); + + // Short + QString text = "New"; + for (int i=0; scheme->findItems(text, Qt::MatchExactly, 0).count() > 0; i++) { + text = QString("New (%1)").arg(i+1); + } + add->setText(0, text); + + // long + text = "New"; + for (int i=0; scheme->findItems(text, Qt::MatchExactly, 0).count() > 0; i++) { + text = QString("New (%1)").arg(i+1); + } + add->setText(1, text); +} + +void +SchemePage::renameClicked() +{ + // which one is selected? + if (scheme->currentItem()) scheme->editItem(scheme->currentItem(), 0); +} + +void +SchemePage::deleteClicked() +{ + if (scheme->currentItem()) { + int index = scheme->invisibleRootItem()->indexOfChild(scheme->currentItem()); + delete scheme->invisibleRootItem()->takeChild(index); + } +} + +// just for qSorting +struct schemeitem { + QString name, desc; + int lo; + bool operator<(schemeitem right) const { return lo < right.lo; } +}; + +ZoneScheme +SchemePage::getScheme() +{ + // read the scheme widget and return a scheme object + QList table; + ZoneScheme results; + + // read back the details from the table + for (int i=0; iinvisibleRootItem()->childCount(); i++) { + + schemeitem add; + add.name = scheme->invisibleRootItem()->child(i)->text(0); + add.desc = scheme->invisibleRootItem()->child(i)->text(1); + add.lo = ((QDoubleSpinBox *)(scheme->itemWidget(scheme->invisibleRootItem()->child(i), 2)))->value(); + table.append(add); + } + + // sort the list into ascending order + qSort(table); + + // now update the results + results.nzones_default = 0; + foreach(schemeitem zone, table) { + results.nzones_default++; + results.zone_default.append(zone.lo); + results.zone_default_is_pct.append(true); + results.zone_default_name.append(zone.name); + results.zone_default_desc.append(zone.desc); + } + + return results; +} + + +CPPage::CPPage(ZonePage* zonePage) : zonePage(zonePage) +{ + active = false; + + QGridLayout *mainLayout = new QGridLayout(this); + + addButton = new QPushButton(tr("Add CP")); + deleteButton = new QPushButton(tr("Delete CP")); + defaultButton = new QPushButton(tr("Default")); + defaultButton->hide(); + + addZoneButton = new QPushButton(tr("Add Zone")); + deleteZoneButton = new QPushButton(tr("Delete Zone")); + + QVBoxLayout *actionButtons = new QVBoxLayout; + actionButtons->addWidget(addButton); + actionButtons->addWidget(deleteButton); + actionButtons->addWidget(defaultButton); + actionButtons->addStretch(); + + QVBoxLayout *zoneButtons = new QVBoxLayout; + zoneButtons->addWidget(addZoneButton); + zoneButtons->addWidget(deleteZoneButton); + zoneButtons->addStretch(); + + QHBoxLayout *addLayout = new QHBoxLayout; + QLabel *dateLabel = new QLabel(tr("From Date")); + QLabel *cpLabel = new QLabel(tr("Critical Power")); + dateEdit = new QDateEdit; + dateEdit->setDate(QDate::currentDate()); + + cpEdit = new QDoubleSpinBox; + cpEdit->setMinimum(0); + cpEdit->setMaximum(1000); + cpEdit->setSingleStep(1.0); + cpEdit->setDecimals(0); + + addLayout->addWidget(dateLabel); + addLayout->addWidget(dateEdit); + addLayout->addWidget(cpLabel); + addLayout->addWidget(cpEdit); + addLayout->addStretch(); + + ranges = new QTreeWidget; + ranges->headerItem()->setText(0, tr("From Date")); + ranges->headerItem()->setText(1, tr("Critical Power")); + ranges->setColumnCount(2); + ranges->setSelectionMode(QAbstractItemView::SingleSelection); + //ranges->setEditTriggers(QAbstractItemView::SelectedClicked); // allow edit + ranges->setUniformRowHeights(true); + ranges->setIndentation(0); + ranges->header()->resizeSection(0,180); + + // setup list of ranges + for (int i=0; i< zonePage->zones.getRangeSize(); i++) { + + QTreeWidgetItem *add = new QTreeWidgetItem(ranges->invisibleRootItem()); + add->setFlags(add->flags() & ~Qt::ItemIsEditable); + + // Embolden ranges with manually configured zones + QFont font; + font.setWeight(zonePage->zones.getZoneRange(i).zonesSetFromCP ? + QFont::Normal : QFont::Black); + + // date + add->setText(0, zonePage->zones.getStartDate(i).toString("MMM d, yyyy")); + add->setFont(0, font); + + // CP + add->setText(1, QString("%1").arg(zonePage->zones.getCP(i))); + add->setFont(1, font); + } + + zones = new QTreeWidget; + zones->headerItem()->setText(0, tr("Short")); + zones->headerItem()->setText(1, tr("Long")); + zones->headerItem()->setText(2, tr("From Watts")); + zones->setColumnCount(3); + zones->setSelectionMode(QAbstractItemView::SingleSelection); + zones->setEditTriggers(QAbstractItemView::SelectedClicked); // allow edit + zones->setUniformRowHeights(true); + zones->setIndentation(0); + zones->header()->resizeSection(0,80); + zones->header()->resizeSection(1,150); + + mainLayout->addLayout(addLayout, 0,0); + mainLayout->addWidget(ranges, 2,0); + mainLayout->addWidget(zones, 4,0); + mainLayout->addLayout(actionButtons, 0,1,0,3); + mainLayout->addLayout(zoneButtons, 4,1); + + // button connect + connect(addButton, SIGNAL(clicked()), this, SLOT(addClicked())); + connect(deleteButton, SIGNAL(clicked()), this, SLOT(deleteClicked())); + connect(defaultButton, SIGNAL(clicked()), this, SLOT(defaultClicked())); + connect(addZoneButton, SIGNAL(clicked()), this, SLOT(addZoneClicked())); + connect(deleteZoneButton, SIGNAL(clicked()), this, SLOT(deleteZoneClicked())); + connect(ranges, SIGNAL(itemSelectionChanged()), this, SLOT(rangeSelectionChanged())); + connect(zones, SIGNAL(itemChanged(QTreeWidgetItem*, int)), this, SLOT(zonesChanged())); +} + +void +CPPage::addClicked() +{ + // get current scheme + zonePage->zones.setScheme(zonePage->schemePage->getScheme()); + + //int index = ranges->invisibleRootItem()->childCount(); + int index = zonePage->zones.addZoneRange(dateEdit->date(), cpEdit->value()); + + // new item + QTreeWidgetItem *add = new QTreeWidgetItem; + add->setFlags(add->flags() & ~Qt::ItemIsEditable); + ranges->invisibleRootItem()->insertChild(index, add); + + // date + add->setText(0, dateEdit->date().toString("MMM d, yyyy")); + + // CP + add->setText(1, QString("%1").arg(cpEdit->value())); +} + +void +CPPage::deleteClicked() +{ + if (ranges->currentItem()) { + int index = ranges->invisibleRootItem()->indexOfChild(ranges->currentItem()); + delete ranges->invisibleRootItem()->takeChild(index); + zonePage->zones.deleteRange(index); + } +} + +void +CPPage::defaultClicked() +{ + if (ranges->currentItem()) { + + int index = ranges->invisibleRootItem()->indexOfChild(ranges->currentItem()); + ZoneRange current = zonePage->zones.getZoneRange(index); + + // unbold + QFont font; + font.setWeight(QFont::Normal); + ranges->currentItem()->setFont(0, font); + ranges->currentItem()->setFont(1, font); + + // set the range to use defaults on the scheme page + zonePage->zones.setScheme(zonePage->schemePage->getScheme()); + zonePage->zones.setZonesFromCP(index); + + // hide the default button since we are now using defaults + defaultButton->hide(); + + // update the zones display + rangeSelectionChanged(); + } +} + +void +CPPage::rangeSelectionChanged() +{ + active = true; + + // wipe away current contents of zones + foreach (QTreeWidgetItem *item, zones->invisibleRootItem()->takeChildren()) { + delete zones->itemWidget(item, 2); + delete item; + } + + // fill with current details + if (ranges->currentItem()) { + + int index = ranges->invisibleRootItem()->indexOfChild(ranges->currentItem()); + ZoneRange current = zonePage->zones.getZoneRange(index); + + if (current.zonesSetFromCP) { + + // reapply the scheme in case it has been changed + zonePage->zones.setScheme(zonePage->schemePage->getScheme()); + zonePage->zones.setZonesFromCP(index); + current = zonePage->zones.getZoneRange(index); + + defaultButton->hide(); + + } else defaultButton->show(); + + for (int i=0; i< current.zones.count(); i++) { + + QTreeWidgetItem *add = new QTreeWidgetItem(zones->invisibleRootItem()); + add->setFlags(add->flags() | Qt::ItemIsEditable); + + // tab name + add->setText(0, current.zones[i].name); + // field name + add->setText(1, current.zones[i].desc); + + // low + QDoubleSpinBox *loedit = new QDoubleSpinBox(this); + loedit->setMinimum(0); + loedit->setMaximum(1000); + loedit->setSingleStep(1.0); + loedit->setDecimals(0); + loedit->setValue(current.zones[i].lo); + zones->setItemWidget(add, 2, loedit); + connect(loedit, SIGNAL(editingFinished()), this, SLOT(zonesChanged())); + } + } + + active = false; +} + +void +CPPage::addZoneClicked() +{ + // no range selected + if (!ranges->currentItem()) return; + + // are we at maximum already? + if (zones->invisibleRootItem()->childCount() == 10) { + QMessageBox err; + err.setText("Maximum of 10 zones reached."); + err.setIcon(QMessageBox::Warning); + err.exec(); + return; + } + + active = true; + int index = zones->invisibleRootItem()->childCount(); + + // new item + QTreeWidgetItem *add = new QTreeWidgetItem; + add->setFlags(add->flags() | Qt::ItemIsEditable); + + QDoubleSpinBox *loedit = new QDoubleSpinBox(this); + loedit->setMinimum(0); + loedit->setMaximum(1000); + loedit->setSingleStep(1.0); + loedit->setDecimals(0); + loedit->setValue(100); + + zones->invisibleRootItem()->insertChild(index, add); + zones->setItemWidget(add, 2, loedit); + connect(loedit, SIGNAL(editingFinished()), this, SLOT(zonesChanged())); + + // Short + QString text = "New"; + for (int i=0; zones->findItems(text, Qt::MatchExactly, 0).count() > 0; i++) { + text = QString("New (%1)").arg(i+1); + } + add->setText(0, text); + + // long + text = "New"; + for (int i=0; zones->findItems(text, Qt::MatchExactly, 0).count() > 0; i++) { + text = QString("New (%1)").arg(i+1); + } + add->setText(1, text); + active = false; + + zonesChanged(); +} + +void +CPPage::deleteZoneClicked() +{ + // no range selected + if (ranges->invisibleRootItem()->indexOfChild(ranges->currentItem()) == -1) + return; + + active = true; + if (zones->currentItem()) { + int index = zones->invisibleRootItem()->indexOfChild(zones->currentItem()); + delete zones->invisibleRootItem()->takeChild(index); + } + active = false; + + zonesChanged(); +} + +void +CPPage::zonesChanged() +{ + // only take changes when they are not done programmatically + // the active flag is set when the tree is being modified + // programmatically, but not when users interact with the widgets + if (active == false) { + // get the current zone range + if (ranges->currentItem()) { + + int index = ranges->invisibleRootItem()->indexOfChild(ranges->currentItem()); + ZoneRange current = zonePage->zones.getZoneRange(index); + + // embolden that range on the list to show it has been edited + QFont font; + font.setWeight(QFont::Black); + ranges->currentItem()->setFont(0, font); + ranges->currentItem()->setFont(1, font); + + // show the default button to undo + defaultButton->show(); + + // we manually edited so save in full + current.zonesSetFromCP = false; + + // create the new zoneinfos for this range + QList zoneinfos; + for (int i=0; i< zones->invisibleRootItem()->childCount(); i++) { + QTreeWidgetItem *item = zones->invisibleRootItem()->child(i); + zoneinfos << ZoneInfo(item->text(0), + item->text(1), + ((QDoubleSpinBox*)zones->itemWidget(item, 2))->value(), + 0); + } + + // now sort the list + qSort(zoneinfos); + + // now fill the highs + for(int i=0; izones.setZoneRange(index, current); + } + } +} diff --git a/src/Pages.h b/src/Pages.h index 1b5e11462..b8e45e0fe 100644 --- a/src/Pages.h +++ b/src/Pages.h @@ -31,6 +31,7 @@ class MetadataPage; class KeywordsPage; class FieldsPage; class Colors; +class ZonePage; class ConfigurationPage : public QWidget { @@ -75,23 +76,9 @@ class CyclistPage : public QWidget { Q_OBJECT public: - CyclistPage(const Zones *_zones); - int thresholdPower; - QString getText(); - int getCP(); - void setCP(int cp); - void setSelectedDate(QDate date); - void setCurrentRange(int range = -1); - QPushButton *btnBack; - QPushButton *btnForward; - QPushButton *btnDelete; - QCheckBox *checkboxNew; - QCalendarWidget *calendar; - QLabel *lblCurRange; - QLabel *txtStartDate; - QLabel *txtEndDate; - QLabel *lblStartDate; - QLabel *lblEndDate; + CyclistPage(MainWindow *mainWindow); + void saveClicked(); + QLabel *perfManLabel; QLabel *perfManStartLabel; QLabel *perfManSTSLabel; @@ -101,33 +88,15 @@ class CyclistPage : public QWidget QLineEdit *perfManLTSavg; QCheckBox *showSBToday; - int getCurrentRange(); - bool isNewMode(); - - inline void setCPFocus() { - txtThreshold->setFocus(); - } - - inline QDate selectedDate() { - return calendar->selectedDate(); - } - private: + ZonePage *zonePage; + MainWindow *main; + QGroupBox *cyclistGroup; - const Zones *zones; - int currentRange; - QLabel *lblThreshold; - QLineEdit *txtThreshold; - QIntValidator *txtThresholdValidator; QVBoxLayout *perfManLayout; QHBoxLayout *perfManStartValLayout; QHBoxLayout *perfManSTSavgLayout; QHBoxLayout *perfManLTSavgLayout; - QHBoxLayout *powerLayout; - QHBoxLayout *rangeLayout; - QHBoxLayout *dateRangeLayout; - QHBoxLayout *zoneLayout; - QHBoxLayout *calendarLayout; QVBoxLayout *cyclistLayout; QVBoxLayout *mainLayout; QIntValidator *perfManStartValidator; @@ -322,4 +291,82 @@ class MetadataPage : public QWidget QList fieldDefinitions; }; +class SchemePage : public QWidget +{ + Q_OBJECT + + public: + SchemePage(ZonePage *parent); + ZoneScheme getScheme(); + void saveClicked(); + + public slots: + void addClicked(); + void deleteClicked(); + void renameClicked(); + + private: + ZonePage *zonePage; + QTreeWidget *scheme; + QPushButton *addButton, *renameButton, *deleteButton; +}; + +class CPPage : public QWidget +{ + Q_OBJECT + + public: + CPPage(ZonePage *parent); + + public slots: + void addClicked(); + void deleteClicked(); + void defaultClicked(); + void rangeSelectionChanged(); + void addZoneClicked(); + void deleteZoneClicked(); + void zonesChanged(); + + private: + bool active; + + QDateEdit *dateEdit; + QDoubleSpinBox *cpEdit; + + ZonePage *zonePage; + QTreeWidget *ranges; + QTreeWidget *zones; + QPushButton *addButton, *deleteButton, *defaultButton; + QPushButton *addZoneButton, *deleteZoneButton; +}; + +class ZonePage : public QWidget +{ + Q_OBJECT + + public: + + ZonePage(MainWindow *); + void saveClicked(); + + //ZoneScheme scheme; + Zones zones; + + // Children talk to each other + SchemePage *schemePage; + CPPage *cpPage; + + public slots: + + + protected: + + MainWindow *main; + bool changed; + + QTabWidget *tabs; + + // local versions for modification +}; + #endif diff --git a/src/TimeInZone.cpp b/src/TimeInZone.cpp index 93cb9ea25..dce17578d 100644 --- a/src/TimeInZone.cpp +++ b/src/TimeInZone.cpp @@ -146,6 +146,42 @@ class ZoneTime7 : public ZoneTime { RideMetric *clone() const { return new ZoneTime7(*this); } }; +class ZoneTime8 : public ZoneTime { + + public: + ZoneTime8() + { + setLevel(8); + setSymbol("time_in_zone_L8"); + setName(tr("L8 Time in Zone")); + } + RideMetric *clone() const { return new ZoneTime8(*this); } +}; + +class ZoneTime9 : public ZoneTime { + + public: + ZoneTime9() + { + setLevel(9); + setSymbol("time_in_zone_L9"); + setName(tr("L9 Time in Zone")); + } + RideMetric *clone() const { return new ZoneTime9(*this); } +}; + +class ZoneTime10 : public ZoneTime { + + public: + ZoneTime10() + { + setLevel(10); + setSymbol("time_in_zone_L10"); + setName(tr("L10 Time in Zone")); + } + RideMetric *clone() const { return new ZoneTime10(*this); } +}; + static bool addAllZones() { @@ -156,6 +192,9 @@ static bool addAllZones() { RideMetricFactory::instance().addMetric(ZoneTime5()); RideMetricFactory::instance().addMetric(ZoneTime6()); RideMetricFactory::instance().addMetric(ZoneTime7()); + RideMetricFactory::instance().addMetric(ZoneTime8()); + RideMetricFactory::instance().addMetric(ZoneTime9()); + RideMetricFactory::instance().addMetric(ZoneTime10()); return true; } diff --git a/src/Zones.cpp b/src/Zones.cpp index fa37f9b8e..e98c90611 100644 --- a/src/Zones.cpp +++ b/src/Zones.cpp @@ -27,34 +27,11 @@ #include #include -static QList zone_default; -static QList zone_default_is_pct; -static QList zone_default_name; -static QList zone_default_desc; -static int nzones_default = 0; -// the infinity endpoints are indicated with empty dates -static const QDate date_zero = QDate::QDate(); -static const QDate date_infinity = QDate::QDate(); - -// functions used for sorting zones and ranges -bool Zones::zone_default_index_lessthan(int i1, int i2) { - return (zone_default[i1] * (zone_default_is_pct[i1] ? 250 : 1) < - zone_default[i2] * (zone_default_is_pct[i2] ? 250 : 1)); -} - -bool Zones::zoneptr_lessthan(const ZoneInfo &z1, const ZoneInfo &z2) { - return ((z1.lo < z2.lo) || - ((z1.lo == z2.lo) && (z1.hi < z2.hi))); -} - -bool Zones::rangeptr_lessthan(const ZoneRange &r1, const ZoneRange &r2) { - return (((! r2.begin.isNull()) && - ( r1.begin.isNull() || r1.begin < r2.begin )) || - ((r1.begin == r2.begin) && - (! r1.end.isNull()) && - ( r2.end.isNull() || r1.end < r2.end ))); -} +// the infinity endpoints are indicated with extreme date ranges +// but not zero dates so we can edit and compare them +static const QDate date_zero(1900, 01, 01); +static const QDate date_infinity(9999,12,31); // initialize default static zone parameters void Zones::initializeZoneParameters() @@ -74,19 +51,19 @@ void Zones::initializeZoneParameters() sizeof(initial_zone_default) / sizeof(initial_zone_default[0]); - zone_default.clear(); - zone_default_is_pct.clear(); - zone_default_desc.clear(); - zone_default_name.clear(); - nzones_default = 0; + scheme.zone_default.clear(); + scheme.zone_default_is_pct.clear(); + scheme.zone_default_desc.clear(); + scheme.zone_default_name.clear(); + scheme.nzones_default = 0; - nzones_default = initial_nzones_default; + scheme.nzones_default = initial_nzones_default; - for (int z = 0; z < nzones_default; z ++) { - zone_default.append(initial_zone_default[z]); - zone_default_is_pct.append(true); - zone_default_name.append(QString(initial_zone_default_name[z])); - zone_default_desc.append(QString(initial_zone_default_desc[z])); + for (int z = 0; z < scheme.nzones_default; z ++) { + scheme.zone_default.append(initial_zone_default[z]); + scheme.zone_default_is_pct.append(true); + scheme.zone_default_name.append(QString(initial_zone_default_name[z])); + scheme.zone_default_desc.append(QString(initial_zone_default_desc[z])); } } @@ -94,11 +71,12 @@ void Zones::initializeZoneParameters() bool Zones::read(QFile &file) { defaults_from_user = false; - zone_default.clear(); - zone_default_is_pct.clear(); - zone_default_name.clear(); - zone_default_desc.clear(); - nzones_default = 0; + scheme.zone_default.clear(); + scheme.zone_default_is_pct.clear(); + scheme.zone_default_name.clear(); + scheme.zone_default_desc.clear(); + scheme.nzones_default = 0; + ranges.clear(); // set up possible warning dialog warning = QString(); @@ -147,7 +125,7 @@ bool Zones::read(QFile &file) // the current range in the file // ZoneRange *range = NULL; bool in_range = false; - QDate begin, end; + QDate begin = date_zero, end = date_infinity; int cp; QList zoneInfos; @@ -174,7 +152,7 @@ bool Zones::read(QFile &file) } // only one set of defaults is allowed - if (nzones_default) { + if (scheme.nzones_default) { err = "Only one set of zone defaults may be specified in power.zones file"; return false; } @@ -183,70 +161,65 @@ bool Zones::read(QFile &file) } // check for range specification (may be followed by zone definitions) - for (int r = 0; r < 2; r++) { - if (rangerx[r].indexIn(line, 0) != -1) { + for (int r=0; r<2; r++) { + if (rangerx[r].indexIn(line, 0) != -1) { - if (in_range) { - // if zones are empty, then generate them - ZoneRange range(begin, end, cp); - range.zones = zoneInfos; - if (range.zones.empty()) { - if (range.cp > 0) - setZonesFromCP(range); - else { - err = tr("line %1: read new range without reading " - "any zones for previous one").arg(lineno); - file.close(); - return false; - } - } - // else sort them - else { - qSort(range.zones.begin(), range.zones.end(), zoneptr_lessthan); - } + if (in_range) { - ranges.append(range); - } + // if zones are empty, then generate them + ZoneRange range(begin, end, cp); + range.zones = zoneInfos; - in_range = true; - zones_are_defaults = false; - zoneInfos.clear(); + if (range.zones.empty()) { + if (range.cp > 0) setZonesFromCP(range); + else { + err = tr("line %1: read new range without reading " + "any zones for previous one").arg(lineno); + file.close(); + return false; + } + } else { + qSort(range.zones); + } + ranges.append(range); + } - // QDate begin, end; + in_range = true; + zones_are_defaults = false; + zoneInfos.clear(); - // process the beginning date - if (rangerx[r].cap(1) == "BEGIN") - begin = date_zero; - else { - begin = QDate(rangerx[r].cap(2).toInt(), - rangerx[r].cap(3).toInt(), - rangerx[r].cap(4).toInt() - ); - } + // process the beginning date + if (rangerx[r].cap(1) == "BEGIN") + begin = date_zero; + else { + begin = QDate(rangerx[r].cap(2).toInt(), + rangerx[r].cap(3).toInt(), + rangerx[r].cap(4).toInt()); + } - // process an end date, if any, else it is null - if (rangerx[r].cap(5) == "END") - end = date_infinity; - else if (rangerx[r].cap(6).toInt() || rangerx[r].cap(7).toInt() || rangerx[r].cap(8).toInt()) { - end = QDate(rangerx[r].cap(6).toInt(), - rangerx[r].cap(7).toInt(), - rangerx[r].cap(8).toInt() - ); - } - else { - end = QDate(); - } + // process an end date, if any, else it is null + if (rangerx[r].cap(5) == "END") end = date_infinity; + else if (rangerx[r].cap(6).toInt() || rangerx[r].cap(7).toInt() || + rangerx[r].cap(8).toInt()) { - // set up the range, capturing CP if it's specified - // range = new ZoneRange(begin, end); - int nCP = (r ? 11 : 7); - if (rangerx[r].numCaptures() == (nCP)) - cp = rangerx[r].cap(nCP).toInt(); - else - cp = 0; - goto next_line; - } - } + end = QDate(rangerx[r].cap(6).toInt(), + rangerx[r].cap(7).toInt(), + rangerx[r].cap(8).toInt()); + + } else { + end = QDate(); + } + + // set up the range, capturing CP if it's specified + // range = new ZoneRange(begin, end); + int nCP = (r ? 11 : 7); + if (rangerx[r].numCaptures() == (nCP)) cp = rangerx[r].cap(nCP).toInt(); + else cp = 0; + + // bleck + goto next_line; + } + } // check for zone definition if (zonerx.indexIn(line, 0) != -1) { @@ -261,11 +234,9 @@ bool Zones::read(QFile &file) // allow for zone specified as % of CP bool lo_is_pct = false; - if (zonerx.cap(4) == "%") - if (zones_are_defaults) - lo_is_pct = true; - else if (cp > 0) - lo = int(lo * cp / 100); + if (zonerx.cap(4) == "%") { + if (zones_are_defaults) lo_is_pct = true; + else if (cp > 0) lo = int(lo * cp / 100); else { err = tr("attempt to set zone based on % of " "CP without setting CP in line number %1.\n"). @@ -273,6 +244,7 @@ bool Zones::read(QFile &file) file.close(); return false; } + } int hi; // if this is not a zone defaults specification, process possible hi end of zones @@ -284,24 +256,25 @@ bool Zones::read(QFile &file) hi = zonerx.cap(5).toInt(); // allow for zone specified as % of CP - if (zonerx.cap(5) == "%") + if (zonerx.cap(5) == "%") { if (cp > 0) hi = int(hi * cp / 100); else { - err = tr("attempt to set zone based on % of CP " - "without setting CP in line number %1.\n"). - arg(lineno); - file.close(); - return false; + err = tr("attempt to set zone based on % of CP " + "without setting CP in line number %1.\n"). + arg(lineno); + file.close(); + return false; } + } } if (zones_are_defaults) { - nzones_default ++; - zone_default_is_pct.append(lo_is_pct); - zone_default.append(lo); - zone_default_name.append(zonerx.cap(1)); - zone_default_desc.append(zonerx.cap(2)); + scheme.nzones_default ++; + scheme.zone_default_is_pct.append(lo_is_pct); + scheme.zone_default.append(lo); + scheme.zone_default_name.append(zonerx.cap(1)); + scheme.zone_default_desc.append(zonerx.cap(2)); defaults_from_user = true; } else { @@ -325,7 +298,7 @@ bool Zones::read(QFile &file) } } else { - qSort(range.zones.begin(), range.zones.end(), zoneptr_lessthan); + qSort(range.zones); } ranges.append(range); @@ -333,34 +306,27 @@ bool Zones::read(QFile &file) file.close(); // sort the ranges - qSort(ranges.begin(), ranges.end(), rangeptr_lessthan); + qSort(ranges); - // sort the zone defaults, as best we can (may not be right if there's - // a mix of % and absolute ranges) - if (nzones_default) { - QVector zone_default_index(nzones_default); - for (int i = 0; i < nzones_default; i++) - zone_default_index[i] = i; - qSort(zone_default_index.begin(), zone_default_index.end(), zone_default_index_lessthan); + // set the default zones if not in file + if (!scheme.nzones_default) { - QVector zone_default_new(nzones_default); - QVector zone_default_is_pct_new(nzones_default); - QVector zone_default_name_new(nzones_default); - QVector zone_default_desc_new(nzones_default); + // do we have a zone which is explicitly set? + for (int i=0; i 4) + if (abs(ranges[nr].zones[nz].hi - ranges[nr].zones[nz + 1].lo) > 4) { append_to_warning(tr("Range %1: matching top of zone %2 " "(%3) to bottom of zone %4 (%5).\n"). arg(nr + 1). @@ -419,10 +385,12 @@ bool Zones::read(QFile &file) arg(ranges[nr].zones[nz + 1].name). arg(ranges[nr].zones[nz + 1].lo) ); + } ranges[nr].zones[nz].hi = ranges[nr].zones[nz + 1].lo; - } - else if ((nz == ranges[nr].zones.size() - 1) && + + } else if ((nz == ranges[nr].zones.size() - 1) && (ranges[nr].zones[nz].hi < INT_MAX)) { + append_to_warning(tr("Range %1: setting top of zone %2 from %3 to MAX.\n"). arg(nr + 1). arg(ranges[nr].zones[nz].name). @@ -500,49 +468,48 @@ void Zones::setCP(int rnum, int cp) // generate a list of zones from CP int Zones::lowsFromCP(QList *lows, int cp) const { - if (nzones_default == 0) - initializeZoneParameters(); lows->clear(); - for (int z = 0; z < nzones_default; z++) - lows->append(zone_default_is_pct[z] ? zone_default[z] * cp / 100 : zone_default[z]); + for (int z = 0; z < scheme.nzones_default; z++) + lows->append(scheme.zone_default_is_pct[z] ? + scheme.zone_default[z] * cp / 100 : scheme.zone_default[z]); - return nzones_default; + return scheme.nzones_default; } // access the zone name QString Zones::getDefaultZoneName(int z) const { - return zone_default_name[z]; + return scheme.zone_default_name[z]; } // access the zone description QString Zones::getDefaultZoneDesc(int z) const { - return zone_default_desc[z]; + return scheme.zone_default_desc[z]; } // set the zones from the CP value (the cp variable) void Zones::setZonesFromCP(ZoneRange &range) { range.zones.clear(); - if (nzones_default == 0) + if (scheme.nzones_default == 0) initializeZoneParameters(); - for (int i = 0; i < nzones_default; i++) { - int lo = zone_default_is_pct[i] ? zone_default[i] * range.cp / 100 : zone_default[i]; + for (int i = 0; i < scheme.nzones_default; i++) { + int lo = scheme.zone_default_is_pct[i] ? scheme.zone_default[i] * range.cp / 100 : scheme.zone_default[i]; int hi = lo; - ZoneInfo zone(zone_default_name[i], zone_default_desc[i], lo, hi); + ZoneInfo zone(scheme.zone_default_name[i], scheme.zone_default_desc[i], lo, hi); range.zones.append(zone); } // sort the zones (some may be pct, others absolute, so zones need to be sorted, // rather than the defaults - qSort(range.zones.begin(), range.zones.end(), zoneptr_lessthan); + qSort(range.zones); // set zone end dates for (int i = 0; i < range.zones.size(); i ++) range.zones[i].hi = - (i < nzones_default - 1) ? + (i < scheme.nzones_default - 1) ? range.zones[i + 1].lo : INT_MAX; @@ -642,17 +609,16 @@ void Zones::write(QDir home) { QString strzones; - // write the defaults if they were specified by the user - if (defaults_from_user) { - strzones += QString("DEFAULTS:\n"); - for (int z = 0 ; z < nzones_default; z ++) - strzones += QString("%1,%2,%3%4\n"). - arg(zone_default_name[z]). - arg(zone_default_desc[z]). - arg(zone_default[z]). - arg(zone_default_is_pct[z]?"%":""); - strzones += QString("\n"); - } + // always write the defaults (config pane can adjust) + strzones += QString("DEFAULTS:\n"); + for (int z = 0 ; z < scheme.nzones_default; z ++) + strzones += QString("%1,%2,%3%4\n"). + arg(scheme.zone_default_name[z]). + arg(scheme.zone_default_desc[z]). + arg(scheme.zone_default[z]). + arg(scheme.zone_default_is_pct[z]?"%":""); + strzones += QString("\n"); + for (int i = 0; i < ranges.size(); i++) { int cp = getCP(i); @@ -660,10 +626,9 @@ void Zones::write(QDir home) // note this explicitly sets the first and last ranges such that all time is spanned #if USE_SHORT_POWER_ZONES_FORMAT - if (i == 0) - strzones += QString("BEGIN: CP=%1").arg(cp); - else - strzones += QString("%1: CP=%2").arg(getStartDate(i).toString("yyyy/MM/dd")).arg(cp); + // note: BEGIN is not needed anymore + // since it becomes Jan 01 1900 + strzones += QString("%1: CP=%2").arg(getStartDate(i).toString("yyyy/MM/dd")).arg(cp); strzones += QString("\n"); // step through and print the zones if they've been explicitly set @@ -709,6 +674,31 @@ void Zones::addZoneRange(QDate _start, QDate _end, int _cp) ranges.append(ZoneRange(_start, _end, _cp)); } +// insert a new zone range using the current scheme +// return the range number +int Zones::addZoneRange(QDate _start, int _cp) +{ + int rnum; + + // where to add this range? + for(rnum=0; rnum < ranges.count(); rnum++) if (ranges[rnum].begin > _start) break; + + // at the end ? + if (rnum == ranges.count()) ranges.append(ZoneRange(_start, date_infinity, _cp)); + else ranges.insert(rnum, ZoneRange(_start, ranges[rnum].begin, _cp)); + + // modify previous end date + if (rnum) ranges[rnum-1].end = _start; + + // set zones from CP + if (_cp > 0) { + setCP(rnum, _cp); + setZonesFromCP(rnum); + } + + return rnum; +} + void Zones::addZoneRange() { ranges.append(ZoneRange(date_zero, date_infinity)); @@ -776,29 +766,20 @@ QColor zoneColor(int z, int) { // delete a range, extend an adjacent (prior if available, otherwise next) // range to cover the same time period, then return the number of the new range -// covering the date range of the deleted range +// covering the date range of the deleted range or -1 if none left int Zones::deleteRange(int rnum) { - assert((ranges.size() > 1) && (rnum >= 0) && (rnum < ranges.size())); - int return_rnum; - // extend an adjacent range - if (rnum == 0) { - return_rnum = 0; - setStartDate(rnum + 1, getStartDate(rnum)); - } - else { - return_rnum = rnum - 1; - setEndDate(return_rnum, getEndDate(rnum)); - } + // check bounds - silently fail, don't assert + assert (rnum < ranges.count() && rnum >= 0); - // drop higher ranges down one slot - for (int r = rnum; r < ranges.size() - 1; r ++) - ranges[r] = ranges[r + 1]; + // extend the previous range to the end of this range + // but only if we have a previous range + if (rnum > 0) setEndDate(rnum-1, getEndDate(rnum)); - // reduce the number of ranges by one - ranges.removeLast(); + // delete this range then + ranges.removeAt(rnum); - return return_rnum; + return rnum-1; } // insert a new range starting at the given date extending to the end of the zone currently @@ -851,6 +832,12 @@ Zones::getFingerprint() const // CP x = ranges[i].cp; CRC.process_bytes(&x, sizeof(int)); + + // each zone definition (manual edit/default changed) + for (int j=0; j +// A zone "scheme" defines how power zones +// are calculated as a percentage of CP +// The default is to use Coggan percentages +// but this can be overriden in power.zones +struct ZoneScheme { + QList zone_default; + QList zone_default_is_pct; + QList zone_default_name; + QList zone_default_desc; + int nzones_default; +}; + +// A zone "info" defines a *single zone* +// in absolute watts terms e.g. +// "L4" "Threshold" is between 270w and 315w +struct ZoneInfo { + QString name, desc; + int lo, hi; + ZoneInfo(const QString &n, const QString &d, int l, int h) : + name(n), desc(d), lo(l), hi(h) {} + + // used by qSort() + bool operator< (ZoneInfo right) const { + return ((lo < right.lo) || ((lo == right.lo) && (hi < right.hi))); + } +}; + +// A zone "range" defines the power zones +// that are active for a *specific date period* +// e.g. between 01/01/2008 and 01/04/2008 +// my CP was 290 and I chose to setup +// 7 zoneinfos from Active Recovery through +// to muscular Endurance +struct ZoneRange { + QDate begin, end; + int cp; + QList zones; + bool zonesSetFromCP; + ZoneRange(const QDate &b, const QDate &e) : + begin(b), end(e), cp(0), zonesSetFromCP(false) {} + ZoneRange(const QDate &b, const QDate &e, int _cp) : + begin(b), end(e), cp(_cp), zonesSetFromCP(false) {} + + // used by qSort() + bool operator< (ZoneRange right) const { + return (((! right.begin.isNull()) && + (begin.isNull() || begin < right.begin )) || + ((begin == right.begin) && (! end.isNull()) && + ( right.end.isNull() || end < right.end ))); + } +}; + + class Zones : public QObject { Q_OBJECT - protected: - struct ZoneInfo { - QString name, desc; - int lo, hi; - ZoneInfo(const QString &n, const QString &d, int l, int h) : - name(n), desc(d), lo(l), hi(h) {} - }; - - struct ZoneRange { - QDate begin, end; - int cp; - QList zones; - bool zonesSetFromCP; - ZoneRange(const QDate &b, const QDate &e) : - begin(b), end(e), cp(0), zonesSetFromCP(false) {} - ZoneRange(const QDate &b, const QDate &e, int _cp) : - begin(b), end(e), cp(_cp), zonesSetFromCP(false) {} - }; + private: + // Scheme + bool defaults_from_user; + ZoneScheme scheme; + // CP History QList ranges; + + // utility QString err, warning; - - void setZonesFromCP(ZoneRange &range); - - static bool zoneptr_lessthan(const ZoneInfo &z1, const ZoneInfo &z2); - static bool rangeptr_lessthan(const ZoneRange &r1, const ZoneRange &r2); - static bool zone_default_index_lessthan(int i1, int i2); - - bool defaults_from_user; + void setZonesFromCP(ZoneRange &range); public: - Zones() : defaults_from_user(false) {} - void clear() { - ranges.clear(); - err = warning = ""; - defaults_from_user = false; + Zones() : defaults_from_user(false) { + initializeZoneParameters(); } + // + // Zone settings - Scheme (& default scheme) + // + ZoneScheme getScheme() const { return scheme; } + void setScheme(ZoneScheme x) { scheme = x; } + + // get defaults from the current scheme + QString getDefaultZoneName(int z) const; + QString getDefaultZoneDesc(int z) const; + + // set zone parameters to either user-specified defaults + // or to defaults using Coggan's coefficients + void initializeZoneParameters(); + + // + // Zone history - Ranges + // + // How many ranges in our history + int getRangeSize() const; + + // Add ranges void addZoneRange(QDate _start, QDate _end, int _cp); + int addZoneRange(QDate _start, int _cp); void addZoneRange(); + // insert a range from the given date to the end date of the range + // presently including the date + int insertRangeAtDate(QDate date, int cp = 0); + + // Get / Set ZoneRange details + ZoneRange getZoneRange(int rnum) { return ranges[rnum]; } + void setZoneRange(int rnum, ZoneRange x) { ranges[rnum] = x; } + + // get and set CP for a given range + int getCP(int rnum) const; + void setCP(int rnum, int cp); + + // calculate and then set zoneinfo for a given range + void setZonesFromCP(int rnum); + + // delete the range rnum, and adjust dates on adjacent zone; return + // the range number of the range extended to cover the deleted zone + int deleteRange(const int rnum); + + // + // read and write power.zones + // bool read(QFile &file); void write(QDir home); const QString &errorString() const { return err; } const QString &warningString() const { return warning; } + + // + // Typical APIs to get details of ranges and zones + // + + // which range is active for a particular date int whichRange(const QDate &date) const; - int numZones(int range) const; + + // which zone is the power value in for a given range int whichZone(int range, double value) const; + + // how many zones are there for a given range + int numZones(int range) const; + + // get zoneinfo for a given range and zone void zoneInfo(int range, int zone, QString &name, QString &description, int &low, int &high) const; + QString summarize(int rnum, QVector &time_in_zone) const; - int getCP(int rnum) const; - void setCP(int rnum, int cp); - QString getDefaultZoneName(int z) const; - QString getDefaultZoneDesc(int z) const; - void setZonesFromCP(int rnum); - int lowsFromCP(QList *lows, int CP) const; - QList getZoneLows(int rnum) const; - QList getZoneHighs(int rnum) const; - QList getZoneNames(int rnum) const; + + // get all highs/lows for zones (plot shading uses these) + int lowsFromCP(QList *lows, int CP) const; + QList getZoneLows(int rnum) const; + QList getZoneHighs(int rnum) const; + QList getZoneNames(int rnum) const; + + // get/set range start and end date QDate getStartDate(int rnum) const; QDate getEndDate(int rnum) const; QString getStartDateString(int rnum) const; QString getEndDateString(int rnum) const; void setEndDate(int rnum, QDate date); void setStartDate(int rnum, QDate date); - int getRangeSize() const; - QDateTime modificationTime; - // set zone parameters to either user-specified defaults - // or to defaults using Coggan's coefficients - static void initializeZoneParameters(); + // When was this last updated? + QDateTime modificationTime; - // delete the range rnum, and adjust dates on adjacent zone; return - // the range number of the range extended to cover the deleted zone - int deleteRange(const int rnum); - - // insert a range from the given date to the end date of the range - // presently including the date - int insertRangeAtDate(QDate date, int cp = 0); - - // calculate a CRC for the zones data - used to see if zones - // data is changed since last referenced in Metric code - // could also be used in Configuration pages (later) - unsigned long getFingerprint() const; + // calculate a CRC for the zones data - used to see if zones + // data is changed since last referenced in Metric code + // could also be used in Configuration pages (later) + unsigned long getFingerprint() const; }; QColor zoneColor(int zone, int num_zones);