mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-13 08:08:42 +00:00
Deprecated BestIntervalDialog
Added Specification parameter to AddIntervalDialog::findPeaks So it can be used as a more general replacement for BestIntervalDialog::findBests and findBestsKPH
This commit is contained in:
400
deprecated/BestIntervalDialog.cpp
Normal file
400
deprecated/BestIntervalDialog.cpp
Normal file
@@ -0,0 +1,400 @@
|
||||
/*
|
||||
* Copyright (c) 2008 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 "BestIntervalDialog.h"
|
||||
#include "Athlete.h"
|
||||
#include "Context.h"
|
||||
#include "IntervalItem.h"
|
||||
#include "RideFile.h"
|
||||
#include "RideItem.h"
|
||||
#include "HelpWhatsThis.h"
|
||||
#include <QMap>
|
||||
#include <cmath>
|
||||
|
||||
BestIntervalDialog::BestIntervalDialog(Context *context) :
|
||||
context(context)
|
||||
{
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
setWindowTitle("Find Intervals");
|
||||
HelpWhatsThis *help = new HelpWhatsThis(this);
|
||||
this->setWhatsThis(help->getWhatsThisText(HelpWhatsThis::FindIntervals));
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout(this);
|
||||
|
||||
QHBoxLayout *intervalLengthLayout = new QHBoxLayout;
|
||||
QLabel *intervalLengthLabel = new QLabel(tr("Interval length: "), this);
|
||||
intervalLengthLayout->addWidget(intervalLengthLabel);
|
||||
hrsSpinBox = new QDoubleSpinBox(this);
|
||||
hrsSpinBox->setDecimals(0);
|
||||
hrsSpinBox->setMinimum(0.0);
|
||||
hrsSpinBox->setSuffix(" hrs");
|
||||
hrsSpinBox->setSingleStep(1.0);
|
||||
hrsSpinBox->setAlignment(Qt::AlignRight);
|
||||
intervalLengthLayout->addWidget(hrsSpinBox);
|
||||
minsSpinBox = new QDoubleSpinBox(this);
|
||||
minsSpinBox->setDecimals(0);
|
||||
minsSpinBox->setRange(0.0, 59.0);
|
||||
minsSpinBox->setSuffix(" mins");
|
||||
minsSpinBox->setSingleStep(1.0);
|
||||
minsSpinBox->setWrapping(true);
|
||||
minsSpinBox->setAlignment(Qt::AlignRight);
|
||||
minsSpinBox->setValue(1.0);
|
||||
intervalLengthLayout->addWidget(minsSpinBox);
|
||||
secsSpinBox = new QDoubleSpinBox(this);
|
||||
secsSpinBox->setDecimals(0);
|
||||
secsSpinBox->setRange(0.0, 59.0);
|
||||
secsSpinBox->setSuffix(" secs");
|
||||
secsSpinBox->setSingleStep(1.0);
|
||||
secsSpinBox->setWrapping(true);
|
||||
secsSpinBox->setAlignment(Qt::AlignRight);
|
||||
intervalLengthLayout->addWidget(secsSpinBox);
|
||||
mainLayout->addLayout(intervalLengthLayout);
|
||||
|
||||
QHBoxLayout *intervalCountLayout = new QHBoxLayout;
|
||||
QLabel *intervalCountLabel = new QLabel(tr("How many to find: "), this);
|
||||
intervalCountLayout->addWidget(intervalCountLabel);
|
||||
countSpinBox = new QDoubleSpinBox(this);
|
||||
countSpinBox->setDecimals(0);
|
||||
countSpinBox->setMinimum(1.0);
|
||||
countSpinBox->setValue(5.0); // lets default to the top 5 powers
|
||||
countSpinBox->setSingleStep(1.0);
|
||||
countSpinBox->setAlignment(Qt::AlignRight);
|
||||
intervalCountLayout->addWidget(countSpinBox);
|
||||
mainLayout->addLayout(intervalCountLayout);
|
||||
|
||||
QLabel *resultsLabel = new QLabel(tr("Results:"), this);
|
||||
mainLayout->addWidget(resultsLabel);
|
||||
|
||||
// user can select from the results to add
|
||||
// to the ride intervals
|
||||
resultsTable = new QTableWidget;
|
||||
mainLayout->addWidget(resultsTable);
|
||||
resultsTable->setColumnCount(5);
|
||||
resultsTable->setColumnHidden(3, true); // has start time in secs
|
||||
resultsTable->setColumnHidden(4, true); // has stop time in secs
|
||||
resultsTable->horizontalHeader()->hide();
|
||||
// resultsTable->verticalHeader()->hide();
|
||||
resultsTable->setShowGrid(false);
|
||||
|
||||
QHBoxLayout *buttonLayout = new QHBoxLayout;
|
||||
findButton = new QPushButton(tr("&Find Intervals"), this);
|
||||
buttonLayout->addWidget(findButton);
|
||||
doneButton = new QPushButton(tr("&Done"), this);
|
||||
buttonLayout->addWidget(doneButton);
|
||||
addButton = new QPushButton(tr("&Add to Intervals"));
|
||||
buttonLayout->addWidget(addButton);
|
||||
mainLayout->addLayout(buttonLayout);
|
||||
|
||||
connect(findButton, SIGNAL(clicked()), this, SLOT(findClicked()));
|
||||
connect(doneButton, SIGNAL(clicked()), this, SLOT(doneClicked()));
|
||||
connect(addButton, SIGNAL(clicked()), this, SLOT(addClicked()));
|
||||
}
|
||||
|
||||
|
||||
// little helper function
|
||||
static void
|
||||
clearResultsTable(QTableWidget *resultsTable)
|
||||
{
|
||||
// zap the 3 main cols and two hidden ones
|
||||
for (int i=0; i<resultsTable->rowCount(); i++) {
|
||||
for (int j=0; j<resultsTable->columnCount(); j++)
|
||||
delete resultsTable->takeItem(i,j);
|
||||
}
|
||||
}
|
||||
|
||||
static double
|
||||
intervalDuration(const RideFilePoint *start, const RideFilePoint *stop, const RideFile *ride)
|
||||
{
|
||||
return stop->secs - start->secs + ride->recIntSecs();
|
||||
}
|
||||
|
||||
static bool
|
||||
intervalsOverlap(const BestIntervalDialog::BestInterval &a,
|
||||
const BestIntervalDialog::BestInterval &b)
|
||||
{
|
||||
if ((a.start <= b.start) && (a.stop > b.start))
|
||||
return true;
|
||||
if ((b.start <= a.start) && (b.stop > a.start))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
struct CompareBests {
|
||||
// Sort by decreasing power and increasing start time.
|
||||
bool operator()(const BestIntervalDialog::BestInterval &a,
|
||||
const BestIntervalDialog::BestInterval &b) const {
|
||||
if (a.avg > b.avg)
|
||||
return true;
|
||||
if (b.avg > a.avg)
|
||||
return false;
|
||||
return a.start < b.start;
|
||||
}
|
||||
};
|
||||
|
||||
void
|
||||
BestIntervalDialog::findClicked()
|
||||
{
|
||||
const RideFile *ride = context->ride ? context->ride->ride() : NULL;
|
||||
if (!ride) {
|
||||
QMessageBox::critical(this, tr("Select Activity"), tr("No activity selected!"));
|
||||
return;
|
||||
}
|
||||
|
||||
int maxIntervals = (int) countSpinBox->value();
|
||||
double windowSizeSecs = (hrsSpinBox->value() * 3600.0
|
||||
+ minsSpinBox->value() * 60.0
|
||||
+ secsSpinBox->value());
|
||||
|
||||
if (windowSizeSecs == 0.0) {
|
||||
QMessageBox::critical(this, tr("Bad Interval Length"),
|
||||
tr("Interval length must be greater than zero!"));
|
||||
return;
|
||||
}
|
||||
|
||||
QList<BestInterval> results;
|
||||
findBests(ride, Specification(), windowSizeSecs, maxIntervals, results);
|
||||
|
||||
// clear the table
|
||||
clearResultsTable(resultsTable);
|
||||
|
||||
// populate the table
|
||||
resultsTable->setRowCount(results.size());
|
||||
int row = 0;
|
||||
foreach (const BestInterval &interval, results) {
|
||||
|
||||
double secs = interval.start;
|
||||
double mins = floor(secs / 60);
|
||||
secs = secs - mins * 60.0;
|
||||
double hrs = floor(mins / 60);
|
||||
mins = mins - hrs * 60.0;
|
||||
|
||||
// check box
|
||||
QCheckBox *c = new QCheckBox;
|
||||
c->setCheckState(Qt::Checked);
|
||||
resultsTable->setCellWidget(row, 0, c);
|
||||
|
||||
// start time
|
||||
QString start = "%1:%2:%3";
|
||||
start = start.arg(hrs, 0, 'f', 0);
|
||||
start = start.arg(mins, 2, 'f', 0, QLatin1Char('0'));
|
||||
start = start.arg(round(secs), 2, 'f', 0, QLatin1Char('0'));
|
||||
|
||||
QTableWidgetItem *t = new QTableWidgetItem;
|
||||
t->setText(start);
|
||||
t->setFlags(t->flags() & (~Qt::ItemIsEditable));
|
||||
resultsTable->setItem(row, 1, t);
|
||||
|
||||
// name
|
||||
int x = windowSizeSecs; // int is more help here
|
||||
QString name = "Best %2%3 #%1 (%4w)";
|
||||
name = name.arg(row + 1);
|
||||
// best n mins
|
||||
if (x < 60) {
|
||||
// whole seconds
|
||||
name = name.arg(x);
|
||||
name = name.arg("sec");
|
||||
} else if (x >= 60 && !(x%60)) {
|
||||
// whole minutes
|
||||
name = name.arg(x/60);
|
||||
name = name.arg("min");
|
||||
} else {
|
||||
double secs = x;
|
||||
double mins = ((int) secs) / 60;
|
||||
secs = secs - mins * 60.0;
|
||||
double hrs = ((int) mins) / 60;
|
||||
mins = mins - hrs * 60.0;
|
||||
QString tm = "%1:%2:%3";
|
||||
tm = tm.arg(hrs, 0, 'f', 0);
|
||||
tm = tm.arg(mins, 2, 'f', 0, QLatin1Char('0'));
|
||||
tm = tm.arg(secs, 2, 'f', 0, QLatin1Char('0'));
|
||||
|
||||
// mins and secs
|
||||
name = name.arg(tm);
|
||||
name = name.arg("");
|
||||
}
|
||||
name = name.arg(round(interval.avg));
|
||||
|
||||
QTableWidgetItem *n = new QTableWidgetItem;
|
||||
n->setText(name);
|
||||
n->setFlags(n->flags() | (Qt::ItemIsEditable));
|
||||
resultsTable->setItem(row, 2, n);
|
||||
|
||||
// hidden columns - start, stop
|
||||
QString strt = QString("%1").arg(interval.start); // can't use secs as it gets modified
|
||||
QTableWidgetItem *st = new QTableWidgetItem;
|
||||
st->setText(strt);
|
||||
resultsTable->setItem(row, 3, st);
|
||||
|
||||
QString stp = QString("%1").arg(interval.start + x);
|
||||
QTableWidgetItem *sp = new QTableWidgetItem;
|
||||
sp->setText(stp);
|
||||
resultsTable->setItem(row, 4, sp);
|
||||
|
||||
row++;
|
||||
}
|
||||
resultsTable->resizeColumnToContents(0);
|
||||
resultsTable->resizeColumnToContents(1);
|
||||
resultsTable->setColumnWidth(2,200);
|
||||
}
|
||||
|
||||
void
|
||||
BestIntervalDialog::findBests(const RideFile *ride, Specification spec, double windowSizeSecs,
|
||||
int maxIntervals, QList<BestInterval> &results)
|
||||
{
|
||||
QList<BestInterval> bests;
|
||||
|
||||
double secsDelta = ride->recIntSecs();
|
||||
double totalWatts = 0.0;
|
||||
QList<const RideFilePoint*> window;
|
||||
|
||||
// ride is shorter than the window size!
|
||||
if (windowSizeSecs > ride->dataPoints().last()->secs + secsDelta) return;
|
||||
|
||||
// We're looking for intervals with durations in [windowSizeSecs, windowSizeSecs + secsDelta).
|
||||
RideFileIterator it(const_cast<RideFile*>(ride), spec);
|
||||
while (it.hasNext()) {
|
||||
struct RideFilePoint *point = it.next();
|
||||
|
||||
// Discard points until interval duration is < windowSizeSecs + secsDelta.
|
||||
while (!window.empty() && (intervalDuration(window.first(), point, ride) >= windowSizeSecs + secsDelta)) {
|
||||
totalWatts -= window.first()->watts;
|
||||
window.takeFirst();
|
||||
}
|
||||
// Add points until interval duration is >= windowSizeSecs.
|
||||
totalWatts += point->watts;
|
||||
window.append(point);
|
||||
double duration = intervalDuration(window.first(), window.last(), ride);
|
||||
if (duration >= windowSizeSecs) {
|
||||
double start = window.first()->secs;
|
||||
double stop = start + duration;
|
||||
double avg = totalWatts * secsDelta / duration;
|
||||
bests.append(BestInterval(start, stop, avg));
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(bests.begin(), bests.end(), CompareBests());
|
||||
|
||||
while (!bests.empty() && (results.size() < maxIntervals)) {
|
||||
BestInterval candidate = bests.takeFirst();
|
||||
bool overlaps = false;
|
||||
foreach (const BestInterval &existing, results) {
|
||||
if (intervalsOverlap(candidate, existing)) {
|
||||
overlaps = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!overlaps)
|
||||
results.append(candidate);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
BestIntervalDialog::findBestsKPH(const RideFile *ride, Specification spec, double windowSizeSecs,
|
||||
int maxIntervals, QList<BestInterval> &results)
|
||||
{
|
||||
QList<BestInterval> bests;
|
||||
|
||||
double secsDelta = ride->recIntSecs();
|
||||
double totalKPH = 0.0;
|
||||
QList<const RideFilePoint*> window;
|
||||
|
||||
// ride is shorter than the window size!
|
||||
if (windowSizeSecs > ride->dataPoints().last()->secs + secsDelta) return;
|
||||
|
||||
// We're looking for intervals with durations in [windowSizeSecs, windowSizeSecs + secsDelta).
|
||||
RideFileIterator it(const_cast<RideFile*>(ride), spec);
|
||||
while (it.hasNext()) {
|
||||
struct RideFilePoint *point = it.next();
|
||||
|
||||
// Discard points until interval duration is < windowSizeSecs + secsDelta.
|
||||
while (!window.empty() && (intervalDuration(window.first(), point, ride) >= windowSizeSecs + secsDelta)) {
|
||||
totalKPH -= window.first()->kph;
|
||||
window.takeFirst();
|
||||
}
|
||||
// Add points until interval duration is >= windowSizeSecs.
|
||||
totalKPH += point->kph;
|
||||
window.append(point);
|
||||
double duration = intervalDuration(window.first(), window.last(), ride);
|
||||
if (duration >= windowSizeSecs) {
|
||||
double start = window.first()->secs;
|
||||
double stop = start + duration;
|
||||
double avg = totalKPH * secsDelta / duration;
|
||||
bests.append(BestInterval(start, stop, avg));
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(bests.begin(), bests.end(), CompareBests());
|
||||
|
||||
while (!bests.empty() && (results.size() < maxIntervals)) {
|
||||
BestInterval candidate = bests.takeFirst();
|
||||
bool overlaps = false;
|
||||
foreach (const BestInterval &existing, results) {
|
||||
if (intervalsOverlap(candidate, existing)) {
|
||||
overlaps = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!overlaps)
|
||||
results.append(candidate);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
BestIntervalDialog::doneClicked()
|
||||
{
|
||||
clearResultsTable(resultsTable); // clear out that table!
|
||||
done(0);
|
||||
}
|
||||
|
||||
void
|
||||
BestIntervalDialog::addClicked()
|
||||
{
|
||||
//XXX REFACTOR UPDATE WHEN DECIDE HOW TO ADD AN INTERVAL
|
||||
#if 0
|
||||
// run through the table row by row
|
||||
// and when the checkbox is shown
|
||||
// get name from column 2
|
||||
// get start in secs as a string from column 3
|
||||
// get stop in secs as a string from column 4
|
||||
for (int i=0; i<resultsTable->rowCount(); i++) {
|
||||
|
||||
// is it checked?
|
||||
QCheckBox *c = (QCheckBox *)resultsTable->cellWidget(i,0);
|
||||
if (c->isChecked()) {
|
||||
double start = resultsTable->item(i,3)->text().toDouble();
|
||||
double stop = resultsTable->item(i,4)->text().toDouble();
|
||||
QString name = resultsTable->item(i,2)->text();
|
||||
const RideFile *ride = context->ride ? context->ride->ride() : NULL;
|
||||
|
||||
QTreeWidgetItem *allIntervals = context->athlete->mutableIntervalItems();
|
||||
QTreeWidgetItem *last =
|
||||
new IntervalItem(ride, name, start, stop,
|
||||
ride->timeToDistance(start),
|
||||
ride->timeToDistance(stop),
|
||||
allIntervals->childCount()+1,
|
||||
RideFileInterval::PEAKPOWER); // TODO not always PEAK...
|
||||
last->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled);
|
||||
// add
|
||||
allIntervals->addChild(last);
|
||||
}
|
||||
}
|
||||
context->athlete->updateRideFileIntervals();
|
||||
#endif
|
||||
}
|
||||
71
deprecated/BestIntervalDialog.h
Normal file
71
deprecated/BestIntervalDialog.h
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (c) 2008 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
|
||||
*/
|
||||
|
||||
#ifndef _GC_BestIntervalDialog_h
|
||||
#define _GC_BestIntervalDialog_h 1
|
||||
#include "GoldenCheetah.h"
|
||||
|
||||
#include <QtGui>
|
||||
#include <QTableWidget>
|
||||
#include <QCheckBox>
|
||||
#include <QDialog>
|
||||
#include <QHeaderView>
|
||||
#include <QMessageBox>
|
||||
#include <QLabel>
|
||||
|
||||
class Context;
|
||||
class RideFile;
|
||||
class Specification;
|
||||
|
||||
class BestIntervalDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
G_OBJECT
|
||||
|
||||
|
||||
public:
|
||||
|
||||
struct BestInterval {
|
||||
double start, stop, avg;
|
||||
BestInterval(double start, double stop, double avg) :
|
||||
start(start), stop(stop), avg(avg) {}
|
||||
};
|
||||
|
||||
BestIntervalDialog(Context *context);
|
||||
|
||||
static void findBests(const RideFile *ride, Specification spec, double windowSizeSecs,
|
||||
int maxIntervals, QList<BestInterval> &results);
|
||||
|
||||
static void findBestsKPH(const RideFile *ride, Specification spec, double windowSizeSecs,
|
||||
int maxIntervals, QList<BestInterval> &results);
|
||||
|
||||
private slots:
|
||||
void findClicked();
|
||||
void doneClicked();
|
||||
void addClicked(); // add to inverval selections
|
||||
|
||||
private:
|
||||
|
||||
Context *context;
|
||||
QPushButton *findButton, *doneButton, *addButton;
|
||||
QDoubleSpinBox *hrsSpinBox, *minsSpinBox, *secsSpinBox, *countSpinBox;
|
||||
QTableWidget *resultsTable;
|
||||
};
|
||||
|
||||
#endif // _GC_BestIntervalDialog_h
|
||||
|
||||
Reference in New Issue
Block a user