Better Zones Configuration Page

The zone ranges configuration page caused a SEGV when deleting the
last zone. On inspection the zone configuration needed to be
revised since the UI was confusing and didn't allow fine grained
user editing (relying upon manual editing of the power.zones file).

The UI has been redesigned and fine grained editing of ranges, zones
and default zones is now supported.

The Zones class has been slightly modified to support the new UI and
existing members are better commented. In addition, the read/write
functions have been updated to always include the DEFAULTS section and
to set defaults according to manual zone setups when it is not present
(legacy support).

There are now 10 TimeInZone metrics to match the maximum of 10 zones
the user can define.

Fixes #78.

Fixes #34.
This commit is contained in:
Mark Liversedge
2010-05-17 13:57:04 +01:00
committed by Sean Rhea
parent 7941f0a9bc
commit c4f82e19b6
10 changed files with 991 additions and 556 deletions

View File

@@ -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();
}
//

View File

@@ -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);

View File

@@ -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)
{

View File

@@ -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

View File

@@ -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();
}

View File

@@ -185,8 +185,8 @@ ConfigurationPage::saveClicked()
metadataPage->saveClicked();
}
CyclistPage::CyclistPage(const Zones *_zones):
zones(_zones)
CyclistPage::CyclistPage(MainWindow *main) :
main(main)
{
boost::shared_ptr<QSettings> 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<FieldDefinition> &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<schemeitem> table;
ZoneScheme results;
// read back the details from the table
for (int i=0; i<scheme->invisibleRootItem()->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<ZoneInfo> 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; i<zoneinfos.count(); i++) {
if (i+1 <zoneinfos.count())
zoneinfos[i].hi = zoneinfos[i+1].lo;
else
zoneinfos[i].hi = INT_MAX;
}
current.zones = zoneinfos;
// now replace the current range struct
zonePage->zones.setZoneRange(index, current);
}
}
}

View File

@@ -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<FieldDefinition> 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

View File

@@ -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;
}

View File

@@ -27,34 +27,11 @@
#include <math.h>
#include <boost/crc.hpp>
static QList <int> zone_default;
static QList <bool> zone_default_is_pct;
static QList <QString> zone_default_name;
static QList <QString> 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<ZoneInfo> 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 <int> 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 <int> zone_default_new(nzones_default);
QVector <bool> zone_default_is_pct_new(nzones_default);
QVector <QString> zone_default_name_new(nzones_default);
QVector <QString> zone_default_desc_new(nzones_default);
// do we have a zone which is explicitly set?
for (int i=0; i<ranges.count(); i++) {
if (ranges[i].zonesSetFromCP == false) {
// set the defaults using this one!
scheme.nzones_default = ranges[i].zones.count();
for (int j=0; j<scheme.nzones_default; j++) {
scheme.zone_default.append(((double)ranges[i].zones[j].lo / (double)ranges[i].cp) * 100.00);
scheme.zone_default_is_pct.append(true);
scheme.zone_default_name.append(ranges[i].zones[j].name);
scheme.zone_default_desc.append(ranges[i].zones[j].desc);
}
}
}
for (int i = 0; i < nzones_default; i++) {
zone_default_new[i] = zone_default[zone_default_index[i]];
zone_default_is_pct_new[i] = zone_default_is_pct[zone_default_index[i]];
zone_default_name_new[i] = zone_default_name[zone_default_index[i]];
zone_default_desc_new[i] = zone_default_desc[zone_default_index[i]];
}
for (int i = 0; i < nzones_default; i++) {
zone_default[i] = zone_default_new[i];
zone_default_is_pct[i] = zone_default_is_pct_new[i];
zone_default_name[i] = zone_default_name_new[i];
zone_default_desc[i] = zone_default_desc_new[i];
}
// still not set then reset to defaults as usual
if (!scheme.nzones_default) initializeZoneParameters();
}
// resolve undefined endpoints in ranges and zones
@@ -410,7 +376,7 @@ bool Zones::read(QFile &file)
INT_MAX;
else if ((nz < ranges[nr].zones.size() - 1) &&
(ranges[nr].zones[nz].hi != ranges[nr].zones[nz + 1].lo)) {
if (abs(ranges[nr].zones[nz].hi - ranges[nr].zones[nz + 1].lo) > 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 <int> *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<ranges[i].zones.count(); j++) {
x = ranges[i].zones[j].lo;
CRC.process_bytes(&x, sizeof(int));
}
}
return CRC.checksum();
}

View File

@@ -21,99 +21,176 @@
#include <QtCore>
// 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 <int> zone_default;
QList <bool> zone_default_is_pct;
QList <QString> zone_default_name;
QList <QString> 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<ZoneInfo> 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<ZoneInfo> 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<ZoneRange> 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<double> &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 <int> *lows, int CP) const;
QList <int> getZoneLows(int rnum) const;
QList <int> getZoneHighs(int rnum) const;
QList <QString> getZoneNames(int rnum) const;
// get all highs/lows for zones (plot shading uses these)
int lowsFromCP(QList <int> *lows, int CP) const;
QList <int> getZoneLows(int rnum) const;
QList <int> getZoneHighs(int rnum) const;
QList <QString> 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);