/* * Copyright (c) 2009 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 "CriticalPowerWindow.h" #include "CpintPlot.h" #include "MainWindow.h" #include "RideItem.h" #include "TimeUtils.h" #include #include #include #include "Season.h" #include "SeasonParser.h" #include #include CriticalPowerWindow::CriticalPowerWindow(const QDir &home, MainWindow *parent) : QWidget(parent), home(home), mainWindow(parent), active(false) { QVBoxLayout *vlayout = new QVBoxLayout; cpintPlot = new CpintPlot(home.path(), mainWindow->zones()); vlayout->addWidget(cpintPlot); QFormLayout *cpintPickerLayout = new QFormLayout; QFormLayout *cpintPickerLayout2 = new QFormLayout; QLabel *cpintTimeLabel = new QLabel(tr("Interval Duration:"), this); cpintTimeValue = new QLineEdit("0 s"); QLabel *cpintTodayLabel = new QLabel(tr("Today:"), this); cpintTodayValue = new QLineEdit(tr("no data")); QLabel *cpintAllLabel = new QLabel(tr("Best:"), this); cpintAllValue = new QLineEdit(tr("no data")); QLabel *cpintCPLabel = new QLabel(tr("CP Curve:"), this); cpintCPValue = new QLineEdit(tr("no data")); QFontMetrics metrics(QApplication::font()); int width = metrics.width("8888 watts (88/88/8888)") + 10; cpintAllValue->setFixedWidth(width); cpintCPValue->setFixedWidth(width); // so lines up nicely cpintTimeValue->setReadOnly(true); cpintTodayValue->setReadOnly(true); cpintAllValue->setReadOnly(true); cpintCPValue->setReadOnly(true); cpintPickerLayout->addRow(cpintTimeLabel, cpintTimeValue); cpintPickerLayout->addRow(cpintTodayLabel, cpintTodayValue); cpintPickerLayout2->addRow(cpintAllLabel, cpintAllValue); cpintPickerLayout2->addRow(cpintCPLabel, cpintCPValue); QHBoxLayout *bottomLayout = new QHBoxLayout; bottomLayout->addLayout(cpintPickerLayout); bottomLayout->addLayout(cpintPickerLayout2); QVBoxLayout *otherLayout = new QVBoxLayout; cComboSeason = new QComboBox(this); addSeasons(); cpintSetCPButton = new QPushButton(tr("&Save CP value"), this); cpintSetCPButton->setEnabled(false); otherLayout->addWidget(cpintSetCPButton); otherLayout->addWidget(cComboSeason); QComboBox *yAxisCombo = new QComboBox(this); yAxisCombo->addItem(tr("Y Axis Shows Power")); yAxisCombo->addItem(tr("Y Axis Shows Energy")); otherLayout->addWidget(yAxisCombo); bottomLayout->addLayout(otherLayout); vlayout->addLayout(bottomLayout); setLayout(vlayout); picker = new QwtPlotPicker(QwtPlot::xBottom, QwtPlot::yLeft, QwtPicker::PointSelection, QwtPicker::VLineRubberBand, QwtPicker::AlwaysOff, cpintPlot->canvas()); picker->setRubberBandPen(QColor(Qt::blue)); connect(picker, SIGNAL(moved(const QPoint &)), SLOT(pickerMoved(const QPoint &))); connect(cpintSetCPButton, SIGNAL(clicked()), this, SLOT(cpintSetCPButtonClicked())); connect(cComboSeason, SIGNAL(currentIndexChanged(int)), this, SLOT(seasonSelected(int))); connect(yAxisCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(setEnergyMode(int))); connect(mainWindow, SIGNAL(rideSelected()), this, SLOT(rideSelected())); } void CriticalPowerWindow::newRideAdded() { cpintPlot->needToScanRides = true; } void CriticalPowerWindow::deleteCpiFile(QString rideFilename) { cpintPlot->deleteCpiFile(home.absolutePath() + "/" + ride_filename_to_cpi_filename(rideFilename)); } void CriticalPowerWindow::setActive(bool new_value) { bool was_active = active; active = new_value; if (active && !was_active) { currentRide = mainWindow->rideItem(); if (currentRide) cpintPlot->calculate(currentRide); } } void CriticalPowerWindow::rideSelected() { currentRide = mainWindow->rideItem(); if (active && currentRide) { cpintPlot->calculate(currentRide); cpintSetCPButton->setEnabled(cpintPlot->cp > 0); } } void CriticalPowerWindow::setEnergyMode(int index) { cpintPlot->setEnergyMode(index != 0); cpintPlot->calculate(currentRide); } void CriticalPowerWindow::cpintSetCPButtonClicked() { int cp = (int) cpintPlot->cp; if (cp <= 0) { QMessageBox::critical( this, tr("Set CP value to extracted value"), tr("No non-zero extracted value was identified:\n") + tr("Zones were unchanged.")); return; } mainWindow->setCriticalPower(cp); } static unsigned curve_to_point(double x, const QwtPlotCurve *curve) { unsigned result = 0; if (curve) { const QwtData &data = curve->data(); if (data.size() > 0) { if (x < data.x(0) || x > data.x(data.size() - 1)) return 0; unsigned min = 0, mid = 0, max = data.size(); while (min < max - 1) { mid = (max - min) / 2 + min; if (x < data.x(mid)) { result = (unsigned) round(data.y(mid)); max = mid; } else { min = mid; } } } } return result; } void CriticalPowerWindow::pickerMoved(const QPoint &pos) { double minutes = cpintPlot->invTransform(QwtPlot::xBottom, pos.x()); cpintTimeValue->setText(interval_to_str(60.0*minutes)); // current ride { unsigned watts = curve_to_point(minutes, cpintPlot->getThisCurve()); QString label; if (watts > 0) label = QString(cpintPlot->energyMode() ? "%1 kJ" : "%1 watts").arg(watts); else label = tr("no data"); cpintTodayValue->setText(label); } // cp line if (cpintPlot->getCPCurve()) { unsigned watts = curve_to_point(minutes, cpintPlot->getCPCurve()); QString label; if (watts > 0) label = QString(cpintPlot->energyMode() ? "%1 kJ" : "%1 watts").arg(watts); else label = tr("no data"); cpintCPValue->setText(label); } // global ride { QString label; int index = (int) ceil(minutes * 60); if (cpintPlot->getBests().count() > index) { QDate date = cpintPlot->getBestDates()[index]; unsigned watts = cpintPlot->getBests()[index]; if (cpintPlot->energyMode()) label = QString("%1 kJ (%2)").arg(watts * minutes * 60.0 / 1000.0, 0, 'f', 0); else label = QString("%1 watts (%2)").arg(watts); label = label.arg(date.isValid() ? date.toString(tr("MM/dd/yyyy")) : tr("no date")); } else label = tr("no data"); cpintAllValue->setText(label); } } void CriticalPowerWindow::addSeasons() { QFile seasonFile(home.absolutePath() + "/seasons.xml"); QXmlInputSource source( &seasonFile ); QXmlSimpleReader xmlReader; SeasonParser( handler ); xmlReader.setContentHandler(&handler); xmlReader.setErrorHandler(&handler); bool ok = xmlReader.parse( source ); if(!ok) qWarning("Failed to parse seasons.xml"); seasons = handler.getSeasons(); Season season; season.setName(tr("All Seasons")); seasons.insert(0,season); foreach (Season season, seasons) cComboSeason->addItem(season.getName()); if (!seasons.empty()) { cComboSeason->setCurrentIndex(cComboSeason->count() - 1); Season season = seasons.last(); cpintPlot->changeSeason(season.getStart(), season.getEnd()); } } void CriticalPowerWindow::seasonSelected(int iSeason) { Season season = seasons.at(iSeason); cpintPlot->changeSeason(season.getStart(), season.getEnd()); cpintPlot->calculate(currentRide); }