mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-13 16:18:42 +00:00
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:
committed by
Sean Rhea
parent
7941f0a9bc
commit
c4f82e19b6
@@ -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();
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
696
src/Pages.cpp
696
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<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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
123
src/Pages.h
123
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<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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
373
src/Zones.cpp
373
src/Zones.cpp
@@ -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();
|
||||
}
|
||||
|
||||
193
src/Zones.h
193
src/Zones.h
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user