mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-14 00:28:42 +00:00
The commit replaces the erroneous commit a3ae0ee. I thought the problem with
the zones file that motivated that commit was that some of the zones were of
length zero. In fact, the problem was just that the CP is zero in the first
zone range, and a CP of zero leads to a RI of inf.
827 lines
25 KiB
C++
827 lines
25 KiB
C++
/*
|
|
* Copyright (c) 2006 Sean C. Rhea (srhea@srhea.net)
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the Free
|
|
* Software Foundation; either version 2 of the License, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc., 51
|
|
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include <QMessageBox>
|
|
#include "Zones.h"
|
|
#include "TimeUtils.h"
|
|
#include <QtGui>
|
|
#include <QtAlgorithms>
|
|
#include <qcolor.h>
|
|
#include <assert.h>
|
|
#include <math.h>
|
|
|
|
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 )));
|
|
}
|
|
|
|
// initialize default static zone parameters
|
|
void Zones::initializeZoneParameters()
|
|
{
|
|
static int initial_zone_default[] = {
|
|
0, 55, 75, 90, 105, 120, 150
|
|
};
|
|
static const char *initial_zone_default_desc[] = {
|
|
"Active Recovery", "Endurance", "Tempo", "Threshold",
|
|
"VO2Max", "Anaerobic", "Neuromuscular"
|
|
};
|
|
static const char *initial_zone_default_name[] = {
|
|
"Z1", "Z2", "Z3", "Z4", "Z5", "Z6", "Z7"
|
|
};
|
|
|
|
static int initial_nzones_default =
|
|
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;
|
|
|
|
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]));
|
|
}
|
|
}
|
|
|
|
// read zone file, allowing for zones with or without end dates
|
|
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;
|
|
|
|
// set up possible warning dialog
|
|
warning = QString();
|
|
int warning_lines = 0;
|
|
const int max_warning_lines = 100;
|
|
|
|
// macro to append lines to the warning
|
|
#define append_to_warning(s) \
|
|
if (warning_lines < max_warning_lines) \
|
|
warning.append(s); \
|
|
else if (warning_lines == max_warning_lines) \
|
|
warning.append("...\n"); \
|
|
warning_lines ++;
|
|
|
|
// read using text mode takes care of end-lines
|
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
|
err = "can't open file";
|
|
return false;
|
|
}
|
|
QTextStream fileStream(&file);
|
|
|
|
QRegExp commentrx("\\s*#.*$");
|
|
QRegExp blankrx("^[ \t]*$");
|
|
QRegExp rangerx[] = {
|
|
QRegExp("^\\s*(?:from\\s+)?" // optional "from"
|
|
"((\\d\\d\\d\\d)[-/](\\d{1,2})[-/](\\d{1,2})|BEGIN)" // begin date
|
|
"\\s*([,:]?\\s*(FTP|CP)\\s*=\\s*(\\d+))?" // optional {CP/FTP = integer (optional %)}
|
|
"\\s*:?\\s*$", // optional :
|
|
Qt::CaseInsensitive),
|
|
QRegExp("^\\s*(?:from\\s+)?" // optional "from"
|
|
"((\\d\\d\\d\\d)[-/](\\d{1,2})[-/](\\d{1,2})|BEGIN)" // begin date
|
|
"\\s+(?:until|to|-)\\s+" // until
|
|
"((\\d\\d\\d\\d)[-/](\\d{1,2})[-/](\\d{1,2})|END)?" // end date
|
|
"\\s*:?,?\\s*((FTP|CP)\\s*=\\s*(\\d+))?" // optional {CP/FTP = integer (optional %)}
|
|
"\\s*:?\\s*$", // optional :
|
|
Qt::CaseInsensitive)
|
|
};
|
|
QRegExp zonerx("^\\s*([^ ,][^,]*),\\s*([^ ,][^,]*),\\s*"
|
|
"(\\d+)\\s*(%?)\\s*(?:,\\s*(\\d+|MAX)\\s*(%?)\\s*)?$",
|
|
Qt::CaseInsensitive);
|
|
QRegExp zonedefaultsx("^\\s*(?:zone)?\\s*defaults?\\s*:?\\s*$",
|
|
Qt::CaseInsensitive);
|
|
|
|
int lineno = 0;
|
|
|
|
// the current range in the file
|
|
// ZoneRange *range = NULL;
|
|
bool in_range = false;
|
|
QDate begin, end;
|
|
int cp;
|
|
QList<ZoneInfo> zoneInfos;
|
|
|
|
// true if zone defaults are found in the file (then we need to write them)
|
|
bool zones_are_defaults = false;
|
|
|
|
while (! fileStream.atEnd() ) {
|
|
++lineno;
|
|
QString line = fileStream.readLine();
|
|
int pos = commentrx.indexIn(line, 0);
|
|
if (pos != -1)
|
|
line = line.left(pos);
|
|
if (blankrx.indexIn(line, 0) == 0)
|
|
goto next_line;
|
|
|
|
// check for default zone range definition (may be followed by zone definitions)
|
|
if (zonedefaultsx.indexIn(line, 0) != -1) {
|
|
zones_are_defaults = true;
|
|
|
|
// defaults are allowed only at the beginning of the file
|
|
if (ranges.size()) {
|
|
err = "Zone defaults must be specified at head of power.zones file";
|
|
return false;
|
|
}
|
|
|
|
// only one set of defaults is allowed
|
|
if (nzones_default) {
|
|
err = "Only one set of zone defaults may be specified in power.zones file";
|
|
return false;
|
|
}
|
|
|
|
goto next_line;
|
|
}
|
|
|
|
// check for range specification (may be followed by zone definitions)
|
|
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);
|
|
}
|
|
|
|
ranges.append(range);
|
|
}
|
|
|
|
in_range = true;
|
|
zones_are_defaults = false;
|
|
zoneInfos.clear();
|
|
|
|
// QDate begin, end;
|
|
|
|
// 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();
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
// check for zone definition
|
|
if (zonerx.indexIn(line, 0) != -1) {
|
|
if (! (in_range || zones_are_defaults)) {
|
|
err = tr("line %1: read zone without "
|
|
"preceeding date range").arg(lineno);
|
|
file.close();
|
|
return false;
|
|
}
|
|
|
|
int lo = zonerx.cap(3).toInt();
|
|
|
|
// 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);
|
|
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;
|
|
}
|
|
|
|
int hi;
|
|
// if this is not a zone defaults specification, process possible hi end of zones
|
|
if (zones_are_defaults || zonerx.cap(5).isEmpty())
|
|
hi = -1; // signal an undefined number
|
|
else if (zonerx.cap(5) == "MAX")
|
|
hi = INT_MAX;
|
|
else {
|
|
hi = zonerx.cap(5).toInt();
|
|
|
|
// allow for zone specified as % of CP
|
|
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;
|
|
}
|
|
}
|
|
|
|
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));
|
|
defaults_from_user = true;
|
|
}
|
|
else {
|
|
ZoneInfo zone(zonerx.cap(1), zonerx.cap(2), lo, hi);
|
|
zoneInfos.append(zone);
|
|
}
|
|
}
|
|
next_line: {}
|
|
}
|
|
|
|
if (in_range) {
|
|
ZoneRange range(begin, end, cp);
|
|
range.zones = zoneInfos;
|
|
if (range.zones.empty()) {
|
|
if (range.cp > 0)
|
|
setZonesFromCP(range);
|
|
else {
|
|
err = tr("file ended without reading any zones for last range");
|
|
file.close();
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
qSort(range.zones.begin(), range.zones.end(), zoneptr_lessthan);
|
|
}
|
|
|
|
ranges.append(range);
|
|
}
|
|
file.close();
|
|
|
|
// sort the ranges
|
|
qSort(ranges.begin(), ranges.end(), rangeptr_lessthan);
|
|
|
|
// 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);
|
|
|
|
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);
|
|
|
|
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];
|
|
}
|
|
}
|
|
|
|
// resolve undefined endpoints in ranges and zones
|
|
for (int nr = 0; nr < ranges.size(); nr ++) {
|
|
// clean up gaps or overlaps in zone ranges
|
|
if (ranges[nr].end.isNull())
|
|
ranges[nr].end =
|
|
(nr < ranges.size() - 1) ?
|
|
ranges[nr + 1].begin :
|
|
date_infinity;
|
|
else if ((nr < ranges.size() - 1) &&
|
|
(ranges[nr + 1].begin != ranges[nr].end)) {
|
|
|
|
append_to_warning(tr("Setting end date of range %1 "
|
|
"to start date of range %2.\n").
|
|
arg(nr + 1).
|
|
arg(nr + 2)
|
|
);
|
|
|
|
ranges[nr].end = ranges[nr + 1].begin;
|
|
}
|
|
else if ((nr == ranges.size() - 1) &&
|
|
(ranges[nr].end < QDate::currentDate())) {
|
|
|
|
append_to_warning(tr("Extending final range %1 to infinite "
|
|
"to include present date.\n").arg(nr + 1));
|
|
|
|
ranges[nr].end = date_infinity;
|
|
}
|
|
|
|
if (ranges[nr].cp <= 0) {
|
|
err = tr("CP must be greater than zero in zone "
|
|
"range %1 of power.zones").arg(nr + 1);
|
|
return false;
|
|
}
|
|
|
|
if (ranges[nr].zones.size()) {
|
|
// check that the first zone starts with zero
|
|
ranges[nr].zones[0].lo = 0;
|
|
|
|
// resolve zone end powers
|
|
for (int nz = 0; nz < ranges[nr].zones.size(); nz ++) {
|
|
if (ranges[nr].zones[nz].hi == -1)
|
|
ranges[nr].zones[nz].hi =
|
|
(nz < ranges[nr].zones.size() - 1) ?
|
|
ranges[nr].zones[nz + 1].lo :
|
|
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)
|
|
append_to_warning(tr("Range %1: matching top of zone %2 "
|
|
"(%3) to bottom of zone %4 (%5).\n").
|
|
arg(nr + 1).
|
|
arg(ranges[nr].zones[nz].name).
|
|
arg(ranges[nr].zones[nz].hi).
|
|
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) &&
|
|
(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).
|
|
arg(ranges[nr].zones[nz].hi)
|
|
);
|
|
ranges[nr].zones[nz].hi = INT_MAX;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// mark zones as modified so pages which depend on zones can be updated
|
|
modificationTime = QDateTime::currentDateTime();
|
|
|
|
return true;
|
|
}
|
|
|
|
// note empty dates are treated as automatic matches for begin or
|
|
// end of range
|
|
int Zones::whichRange(const QDate &date) const
|
|
{
|
|
for (int rnum = 0; rnum < ranges.size(); ++rnum) {
|
|
const ZoneRange &range = ranges[rnum];
|
|
if (((date >= range.begin) || (range.begin.isNull())) &&
|
|
((date < range.end) || (range.end.isNull())))
|
|
return rnum;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int Zones::numZones(int rnum) const
|
|
{
|
|
assert(rnum < ranges.size());
|
|
return ranges[rnum].zones.size();
|
|
}
|
|
|
|
int Zones::whichZone(int rnum, double value) const
|
|
{
|
|
assert(rnum < ranges.size());
|
|
const ZoneRange &range = ranges[rnum];
|
|
for (int j = 0; j < range.zones.size(); ++j) {
|
|
const ZoneInfo &info = range.zones[j];
|
|
// note: the "end" of range is actually in the next zone
|
|
if ((value >= info.lo) && (value < info.hi))
|
|
return j;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void Zones::zoneInfo(int rnum, int znum,
|
|
QString &name, QString &description,
|
|
int &low, int &high) const
|
|
{
|
|
assert(rnum < ranges.size());
|
|
const ZoneRange &range = ranges[rnum];
|
|
assert(znum < range.zones.size());
|
|
const ZoneInfo &zone = range.zones[znum];
|
|
name = zone.name;
|
|
description = zone.desc;
|
|
low = zone.lo;
|
|
high = zone.hi;
|
|
}
|
|
|
|
int Zones::getCP(int rnum) const
|
|
{
|
|
assert(rnum < ranges.size());
|
|
return ranges[rnum].cp;
|
|
}
|
|
|
|
void Zones::setCP(int rnum, int cp)
|
|
{
|
|
ranges[rnum].cp = cp;
|
|
modificationTime = QDateTime::currentDateTime();
|
|
}
|
|
|
|
// 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]);
|
|
|
|
return nzones_default;
|
|
}
|
|
|
|
// access the zone name
|
|
QString Zones::getDefaultZoneName(int z) const {
|
|
return zone_default_name[z];
|
|
}
|
|
|
|
// access the zone description
|
|
QString Zones::getDefaultZoneDesc(int z) const {
|
|
return 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)
|
|
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];
|
|
int hi = lo;
|
|
ZoneInfo zone(zone_default_name[i], 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);
|
|
|
|
// set zone end dates
|
|
for (int i = 0; i < range.zones.size(); i ++)
|
|
range.zones[i].hi =
|
|
(i < nzones_default - 1) ?
|
|
range.zones[i + 1].lo :
|
|
INT_MAX;
|
|
|
|
// mark that the zones were set from CP, so if zones are subsequently
|
|
// written, only CP is saved
|
|
range.zonesSetFromCP = true;
|
|
}
|
|
|
|
void Zones::setZonesFromCP(int rnum) {
|
|
assert((rnum >= 0) && (rnum < ranges.size()));
|
|
setZonesFromCP(ranges[rnum]);
|
|
}
|
|
|
|
// return the list of starting values of zones for a given range
|
|
QList <int> Zones::getZoneLows(int rnum) const {
|
|
if (rnum >= ranges.size())
|
|
return QList <int>::QList();
|
|
const ZoneRange &range = ranges[rnum];
|
|
QList <int> return_values;
|
|
for (int i = 0; i < range.zones.size(); i ++)
|
|
return_values.append(ranges[rnum].zones[i].lo);
|
|
return return_values;
|
|
}
|
|
|
|
// return the list of ending values of zones for a given range
|
|
QList <int> Zones::getZoneHighs(int rnum) const {
|
|
if (rnum >= ranges.size())
|
|
return QList <int>::QList();
|
|
const ZoneRange &range = ranges[rnum];
|
|
QList <int> return_values;
|
|
for (int i = 0; i < range.zones.size(); i ++)
|
|
return_values.append(ranges[rnum].zones[i].hi);
|
|
return return_values;
|
|
}
|
|
|
|
// return the list of zone names
|
|
QList <QString> Zones::getZoneNames(int rnum) const {
|
|
if (rnum >= ranges.size())
|
|
return QList <QString>::QList();
|
|
const ZoneRange &range = ranges[rnum];
|
|
QList <QString> return_values;
|
|
for (int i = 0; i < range.zones.size(); i ++)
|
|
return_values.append(ranges[rnum].zones[i].name);
|
|
return return_values;
|
|
}
|
|
|
|
QString Zones::summarize(int rnum, QVector<double> &time_in_zone) const
|
|
{
|
|
assert(rnum < ranges.size());
|
|
const ZoneRange &range = ranges[rnum];
|
|
assert(time_in_zone.size() == range.zones.size());
|
|
QString summary;
|
|
if(range.cp > 0){
|
|
summary += "<table align=\"center\" width=\"70%\" border=\"0\">";
|
|
summary += "<tr><td align=\"center\">";
|
|
summary += tr("Critical Power: %1").arg(range.cp);
|
|
summary += "</td></tr></table>";
|
|
}
|
|
summary += "<table align=\"center\" width=\"70%\" ";
|
|
summary += "border=\"0\">";
|
|
summary += "<tr>";
|
|
summary += tr("<td align=\"center\">Zone</td>");
|
|
summary += tr("<td align=\"center\">Description</td>");
|
|
summary += tr("<td align=\"center\">Low</td>");
|
|
summary += tr("<td align=\"center\">High</td>");
|
|
summary += tr("<td align=\"center\">Time</td>");
|
|
summary += "</tr>";
|
|
QColor color = QApplication::palette().alternateBase().color();
|
|
color = QColor::fromHsv(color.hue(), color.saturation() * 2, color.value());
|
|
for (int zone = 0; zone < time_in_zone.size(); ++zone) {
|
|
if (time_in_zone[zone] > 0.0) {
|
|
QString name, desc;
|
|
int lo, hi;
|
|
zoneInfo(rnum, zone, name, desc, lo, hi);
|
|
if (zone % 2 == 0)
|
|
summary += "<tr bgcolor='" + color.name() + "'>";
|
|
else
|
|
summary += "<tr>";
|
|
summary += QString("<td align=\"center\">%1</td>").arg(name);
|
|
summary += QString("<td align=\"center\">%1</td>").arg(desc);
|
|
summary += QString("<td align=\"center\">%1</td>").arg(lo);
|
|
if (hi == INT_MAX)
|
|
summary += "<td align=\"center\">MAX</td>";
|
|
else
|
|
summary += QString("<td align=\"center\">%1</td>").arg(hi);
|
|
summary += QString("<td align=\"center\">%1</td>")
|
|
.arg(time_to_string((unsigned) round(time_in_zone[zone])));
|
|
summary += "</tr>";
|
|
}
|
|
}
|
|
summary += "</table>";
|
|
return summary;
|
|
}
|
|
|
|
#define USE_SHORT_POWER_ZONES_FORMAT true /* whether a less redundent format should be used */
|
|
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");
|
|
}
|
|
for (int i = 0; i < ranges.size(); i++) {
|
|
int cp = getCP(i);
|
|
|
|
// print header for range
|
|
// 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);
|
|
strzones += QString("\n");
|
|
|
|
// step through and print the zones if they've been explicitly set
|
|
if (! ranges[i].zonesSetFromCP) {
|
|
for (int j = 0; j < ranges[i].zones.size(); j ++) {
|
|
const ZoneInfo &zi = ranges[i].zones[j];
|
|
strzones += QString("%1,%2,%3\n").arg(zi.name).arg(zi.desc).arg(zi.lo);
|
|
}
|
|
strzones += QString("\n");
|
|
}
|
|
#else
|
|
if(ranges.size() <= 1)
|
|
strzones += QString("FROM BEGIN UNTIL END, CP=%1:").arg(cp);
|
|
else if (i == 0)
|
|
strzones += QString("FROM BEGIN UNTIL %1, CP=%2:").arg(getEndDate(i).toString("yyyy/MM/dd")).arg(cp);
|
|
else if (i == ranges.size() - 1)
|
|
strzones += QString("FROM %1 UNTIL END, CP=%2:").arg(getStartDate(i).toString("yyyy/MM/dd")).arg(cp);
|
|
else
|
|
strzones += QString("FROM %1 UNTIL %2, CP=%3:").arg(getStartDate(i).toString("yyyy/MM/dd")).arg(getEndDate(i).toString("yyyy/MM/dd")).arg(cp);
|
|
strzones += QString("\n");
|
|
for (int j = 0; j < ranges[i].zones.size(); j ++) {
|
|
const ZoneInfo &zi = ranges[i].zones[j];
|
|
if (ranges[i].zones[j].hi == INT_MAX)
|
|
strzones += QString("%1,%2,%3,MAX\n").arg(zi.name).arg(zi.desc).arg(zi.lo);
|
|
else
|
|
strzones += QString("%1,%2,%3,%4\n").arg(zi.name).arg(zi.desc).arg(zi.lo).arg(zi.hi);
|
|
}
|
|
strzones += QString("\n");
|
|
#endif
|
|
}
|
|
|
|
QFile file(home.absolutePath() + "/power.zones");
|
|
if (file.open(QFile::WriteOnly))
|
|
{
|
|
QTextStream stream(&file);
|
|
stream << strzones;
|
|
file.close();
|
|
}
|
|
}
|
|
|
|
void Zones::addZoneRange(QDate _start, QDate _end, int _cp)
|
|
{
|
|
ranges.append(ZoneRange(_start, _end, _cp));
|
|
}
|
|
|
|
void Zones::addZoneRange()
|
|
{
|
|
ranges.append(ZoneRange(date_zero, date_infinity));
|
|
}
|
|
|
|
void Zones::setEndDate(int rnum, QDate endDate)
|
|
{
|
|
ranges[rnum].end = endDate;
|
|
modificationTime = QDateTime::currentDateTime();
|
|
}
|
|
void Zones::setStartDate(int rnum, QDate startDate)
|
|
{
|
|
ranges[rnum].begin = startDate;
|
|
modificationTime = QDateTime::currentDateTime();
|
|
}
|
|
|
|
QDate Zones::getStartDate(int rnum) const
|
|
{
|
|
assert(rnum >= 0);
|
|
return ranges[rnum].begin;
|
|
}
|
|
|
|
QString Zones::getStartDateString(int rnum) const
|
|
{
|
|
assert(rnum >= 0);
|
|
QDate d = ranges[rnum].begin;
|
|
return (d.isNull() ? "BEGIN" : d.toString());
|
|
}
|
|
|
|
QDate Zones::getEndDate(int rnum) const
|
|
{
|
|
assert(rnum >= 0);
|
|
return ranges[rnum].end;
|
|
}
|
|
|
|
QString Zones::getEndDateString(int rnum) const
|
|
{
|
|
assert(rnum >= 0);
|
|
QDate d = ranges[rnum].end;
|
|
return (d.isNull() ? "END" : d.toString());
|
|
}
|
|
|
|
int Zones::getRangeSize() const
|
|
{
|
|
return ranges.size();
|
|
}
|
|
|
|
// generate a zone color with a specific number of zones
|
|
QColor zoneColor(int z, int num_zones) {
|
|
assert ((z >= 0) && (z < num_zones));
|
|
if (num_zones == 1)
|
|
return QColor(128, 128, 128);
|
|
QColor color;
|
|
|
|
// pick a color from violet (z=0) to red (z=num_zones)
|
|
color.setHsv(int(300 * (num_zones - z - 1) / (num_zones - 1)), 255, 255);
|
|
|
|
return color;
|
|
}
|
|
|
|
// 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
|
|
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));
|
|
}
|
|
|
|
// drop higher ranges down one slot
|
|
for (int r = rnum; r < ranges.size() - 1; r ++)
|
|
ranges[r] = ranges[r + 1];
|
|
|
|
// reduce the number of ranges by one
|
|
ranges.removeLast();
|
|
|
|
return return_rnum;
|
|
}
|
|
|
|
// insert a new range starting at the given date extending to the end of the zone currently
|
|
// containing that date. If the start date of that zone is prior to the specified start
|
|
// date, then that zone range is shorted.
|
|
int Zones::insertRangeAtDate(QDate date, int cp) {
|
|
assert(date.isValid());
|
|
int rnum;
|
|
|
|
if (ranges.empty())
|
|
addZoneRange();
|
|
else {
|
|
rnum = whichRange(date);
|
|
assert(rnum >= 0);
|
|
QDate date1 = getStartDate(rnum);
|
|
|
|
// if the old range has dates before the specified, then truncate the old range
|
|
// and shift up the existing ranges
|
|
if (date > date1) {
|
|
QDate endDate = getEndDate(rnum);
|
|
setEndDate(rnum, date);
|
|
ranges.insert(++ rnum, ZoneRange(date, endDate));
|
|
}
|
|
}
|
|
|
|
if (cp > 0) {
|
|
setCP(rnum, cp);
|
|
setZonesFromCP(rnum);
|
|
}
|
|
|
|
return rnum;
|
|
}
|